PyQtGraph release 0.9.9

This commit is contained in:
Luke Campagnola 2014-12-23 19:13:54 -05:00
commit 8f638776b3
300 changed files with 15515 additions and 8575 deletions

5
.gitignore vendored
View File

@ -2,3 +2,8 @@ __pycache__
build
*.pyc
*.swp
MANIFEST
deb_build
dist
.idea
rtr.cvs

230
.travis.yml Normal file
View File

@ -0,0 +1,230 @@
language: python
# Credit: Original .travis.yml lifted from VisPy
# Here we use anaconda for 2.6 and 3.3, since it provides the simplest
# interface for running different versions of Python. We could also use
# it for 2.7, but the Ubuntu system has installable 2.7 Qt4-GL, which
# allows for more complete testing.
notifications:
email: false
virtualenv:
system_site_packages: true
env:
# Enable python 2 and python 3 builds
# Note that the 2.6 build doesn't get flake8, and runs old versions of
# Pyglet and GLFW to make sure we deal with those correctly
#- PYTHON=2.6 QT=pyqt TEST=standard
- PYTHON=2.7 QT=pyqt TEST=extra
- PYTHON=2.7 QT=pyside TEST=standard
- PYTHON=3.2 QT=pyqt TEST=standard
- PYTHON=3.2 QT=pyside TEST=standard
#- PYTHON=3.2 QT=pyqt5 TEST=standard
before_install:
- TRAVIS_DIR=`pwd`
- travis_retry sudo apt-get update;
# - if [ "${PYTHON}" != "2.7" ]; then
# wget http://repo.continuum.io/miniconda/Miniconda-2.2.2-Linux-x86_64.sh -O miniconda.sh &&
# chmod +x miniconda.sh &&
# ./miniconda.sh -b &&
# export PATH=/home/$USER/anaconda/bin:$PATH &&
# conda update --yes conda &&
# travis_retry sudo apt-get -qq -y install libgl1-mesa-dri;
# fi;
- if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
GIT_TARGET_EXTRA="+refs/heads/${TRAVIS_BRANCH}";
GIT_SOURCE_EXTRA="+refs/pull/${TRAVIS_PULL_REQUEST}/merge";
else
GIT_TARGET_EXTRA="";
GIT_SOURCE_EXTRA="";
fi;
# to aid in debugging
- echo ${TRAVIS_BRANCH}
- echo ${TRAVIS_REPO_SLUG}
- echo ${GIT_TARGET_EXTRA}
- echo ${GIT_SOURCE_EXTRA}
install:
# Dependencies
- if [ "${PYTHON}" == "2.7" ]; then
travis_retry sudo apt-get -qq -y install python-numpy &&
export PIP=pip &&
sudo ${PIP} install pytest &&
sudo ${PIP} install flake8 &&
export PYTEST=py.test;
else
travis_retry sudo apt-get -qq -y install python3-numpy &&
curl http://python-distribute.org/distribute_setup.py | sudo python3 &&
curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | sudo python3 &&
export PIP=pip3.2 &&
sudo ${PIP} install pytest &&
sudo ${PIP} install flake8 &&
export PYTEST=py.test-3.2;
fi;
# Qt
- if [ "${PYTHON}" == "2.7" ]; then
if [ ${QT} == 'pyqt' ]; then
travis_retry sudo apt-get -qq -y install python-qt4 python-qt4-gl;
else
travis_retry sudo apt-get -qq -y install python-pyside.qtcore python-pyside.qtgui python-pyside.qtsvg python-pyside.qtopengl;
fi;
elif [ "${PYTHON}" == "3.2" ]; then
if [ ${QT} == 'pyqt' ]; then
travis_retry sudo apt-get -qq -y install python3-pyqt4;
elif [ ${QT} == 'pyside' ]; then
travis_retry sudo apt-get -qq -y install python3-pyside;
else
${PIP} search PyQt5;
${PIP} install PyQt5;
cat /home/travis/.pip/pip.log;
fi;
else
conda create -n testenv --yes --quiet pip python=$PYTHON &&
source activate testenv &&
if [ ${QT} == 'pyqt' ]; then
conda install --yes --quiet pyside;
else
conda install --yes --quiet pyside;
fi;
fi;
# Install PyOpenGL
- if [ "${PYTHON}" == "2.7" ]; then
echo "Using OpenGL stable version (apt)";
travis_retry sudo apt-get -qq -y install python-opengl;
else
echo "Using OpenGL stable version (pip)";
${PIP} install -q PyOpenGL;
cat /home/travis/.pip/pip.log;
fi;
# Debugging helpers
- uname -a
- cat /etc/issue
- if [ "${PYTHON}" == "2.7" ]; then
python --version;
else
python3 --version;
fi;
- apt-cache search python3-pyqt
- apt-cache search python3-pyside
- apt-cache search pytest
- apt-cache search python pip
- apt-cache search python qt5
before_script:
# We need to create a (fake) display on Travis, let's use a funny resolution
- export DISPLAY=:99.0
- /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1400x900x24 -ac +extension GLX +render
# Make sure everyone uses the correct python
- mkdir ~/bin && ln -s `which python${PYTHON}` ~/bin/python
- export PATH=/home/travis/bin:$PATH
- which python
- python --version
# Help color output from each test
- RESET='\033[0m';
RED='\033[00;31m';
GREEN='\033[00;32m';
YELLOW='\033[00;33m';
BLUE='\033[00;34m';
PURPLE='\033[00;35m';
CYAN='\033[00;36m';
WHITE='\033[00;37m';
start_test() {
echo -e "${BLUE}======== Starting $1 ========${RESET}";
};
check_output() {
ret=$?;
if [ $ret == 0 ]; then
echo -e "${GREEN}>>>>>> $1 passed <<<<<<${RESET}";
else
echo -e "${RED}>>>>>> $1 FAILED <<<<<<${RESET}";
fi;
return $ret;
};
- if [ "${TEST}" == "extra" ]; then
start_test "repo size check";
mkdir ~/repo-clone && cd ~/repo-clone &&
git init && git remote add -t ${TRAVIS_BRANCH} origin git://github.com/${TRAVIS_REPO_SLUG}.git &&
git fetch origin ${GIT_TARGET_EXTRA} &&
git checkout -qf FETCH_HEAD &&
git tag travis-merge-target &&
git gc --aggressive &&
TARGET_SIZE=`du -s . | sed -e "s/\t.*//"` &&
git pull origin ${GIT_SOURCE_EXTRA} &&
git gc --aggressive &&
MERGE_SIZE=`du -s . | sed -e "s/\t.*//"` &&
if [ "${MERGE_SIZE}" != "${TARGET_SIZE}" ]; then
SIZE_DIFF=`expr \( ${MERGE_SIZE} - ${TARGET_SIZE} \)`;
else
SIZE_DIFF=0;
fi;
fi;
- cd $TRAVIS_DIR
script:
# Run unit tests
- start_test "unit tests";
PYTHONPATH=. ${PYTEST} pyqtgraph/;
check_output "unit tests";
# check line endings
- if [ "${TEST}" == "extra" ]; then
start_test "line ending check";
! find ./ -name "*.py" | xargs file | grep CRLF &&
! find ./ -name "*.rst" | xargs file | grep CRLF;
check_output "line ending check";
fi;
# Check repo size does not expand too much
- if [ "${TEST}" == "extra" ]; then
start_test "repo size check";
echo -e "Estimated content size difference = ${SIZE_DIFF} kB" &&
test ${SIZE_DIFF} -lt 100;
check_output "repo size check";
fi;
# Check for style issues
- if [ "${TEST}" == "extra" ]; then
start_test "style check";
cd ~/repo-clone &&
git reset -q travis-merge-target &&
python setup.py style &&
check_output "style check";
fi;
- cd $TRAVIS_DIR
# Check install works
- start_test "install test";
sudo python${PYTHON} setup.py --quiet install;
check_output "install test";
# Check double-install fails
# Note the bash -c is because travis strips off the ! otherwise.
- start_test "double install test";
bash -c "! sudo python${PYTHON} setup.py --quiet install";
check_output "double install test";
# Check we can import pg
- start_test "import test";
echo "import sys; print(sys.path)" | python &&
cd /; echo "import pyqtgraph.examples" | python;
check_output "import test";

102
CHANGELOG
View File

@ -1,3 +1,105 @@
pyqtgraph-0.9.9
API / behavior changes:
- Dynamic import system abandoned; pg now uses static imports throughout.
- Flowcharts and exporters have new pluggin systems
- Version strings:
- __init__.py in git repo now contains latest release version string
(previously, only packaged releases had version strings).
- installing from git checkout that does not correspond to a release
commit will result in a more descriptive version string.
- Speed improvements in functions.makeARGB
- ImageItem is faster by avoiding makeQImage(transpose=True)
- ComboBox will raise error when adding multiple items of the same name
- ArrowItem.setStyle now updates style options rather than replacing them
- Renamed GraphicsView signals to avoid collision with ViewBox signals that
are wrapped in PlotWidget: sigRangeChanged => sigDeviceRangeChanged and
sigTransformChanged => sigDeviceTransformChanged.
- GLViewWidget.itemsAt() now measures y from top of widget to match mouse
event position.
- Made setPen() methods consistent throughout the package
- Fix in GLScatterPlotItem requires that points will appear slightly more opaque
(so you may need to adjust to lower alpha to achieve the same results)
New Features:
- Added ViewBox.setLimits() method
- Adde ImageItem downsampling
- New HDF5 example for working with very large datasets
- Removed all dependency on scipy
- Added Qt.loadUiType function for PySide
- Simplified Profilers; can be activated with environmental variables
- Added Dock.raiseDock() method
- ComboBox updates:
- Essentially a graphical interface to dict; all items have text and value
- Assigns previously-selected text after list is cleared and repopulated
- Get, set current value
- Flowchart updates
- Added Flowchart.sigChartChanged
- Custom nodes may now be registered in sub-menu trees
- ImageItem.getHistogram is more clever about constructing histograms
- Added FillBetweenItem.setCurves()
- MultiPlotWidget now has setMinimumPlotHeight method and displays scroll bar
when plots do not fit inside the widget.
- Added BarGraphItem.shape() to allow better mouse interaction
- Added MeshData.cylinder
- Added ViewBox.setBackgroundColor() and GLViewWidget.setBackgroundColor()
- Utilities / debugging tools
- Mutex used for tracing deadlocks
- Color output on terminal
- Multiprocess debugging colors messages by process
- Stdout filter that colors text by thread
- PeriodicTrace used to report deadlocks
- Added AxisItem.setStyle()
- Added configurable formatting for TableWidget
- Added 'stepMode' argument to PlotDataItem()
- Added ViewBox.invertX()
- Docks now have optional close button
- Added InfiniteLine.setHoverPen
- Added GLVolumeItem.setData
- Added PolyLineROI.setPoints, clearPoints, saveState, setState
- Added ErrorBarItem.setData
Bugfixes:
- PlotCurveItem now has correct clicking behavior--clicks within a few px
of the line will trigger a signal.
- Fixes related to CSV exporter:
- CSV headers include data names, if available
- Exporter correctly handles items with no data
- pg.plot() avoids creating empty data item
- removed call to reduce() from exporter; not available in python 3
- Gave .name() methods to PlotDataItem, PlotCurveItem, and ScatterPlotItem
- fixed ImageItem handling of rgb images
- fixed makeARGB re-ordering of color channels
- fixed unicode usage in AxisItem tick strings
- fixed PlotCurveItem generating exceptions when data has length=0
- fixed ImageView.setImage only working once
- PolyLineROI.setPen() now changes the pen of its segments as well
- Prevent divide-by-zero in AxisItem
- Major speedup when using ScatterPlotItem in pxMode
- PlotCurveItem ignores clip-to-view when auto range is enabled
- FillBetweenItem now forces PlotCurveItem to generate path
- Fixed import errors and py3 issues in MultiPlotWidget
- Isosurface works for arrays with shapes > 255
- Fixed ImageItem exception building histogram when image has only one value
- Fixed MeshData exception caused when vertexes have no matching faces
- Fixed GLViewWidget exception handler
- Fixed unicode support in Dock
- Fixed PySide crash caused by emitting signal from GraphicsObject.itemChange
- Fixed possible infinite loop from FiniteCache
- Allow images with NaN in ImageView
- MeshData can generate edges from face-indexed vertexes
- Fixed multiprocess deadlocks on windows
- Fixed GLGridItem.setSize
- Fixed parametertree.Parameter.sigValueChanging
- Fixed AxisItem.__init__(showValues=False)
- Fixed TableWidget append / sort issues
- Fixed AxisItem not resizing text area when setTicks() is used
- Removed a few cyclic references
- Fixed Parameter 'readonly' option for bool, color, and text parameter types
- Fixed alpha on GLScatterPlotItem spots (formerly maxed out at alpha=200)
- Fixed a few bugs causing exit crashes
pyqtgraph-0.9.8 2013-11-24
API / behavior changes:

51
CONTRIBUTING.txt Normal file
View File

@ -0,0 +1,51 @@
Contributions to pyqtgraph are welcome!
Please use the following guidelines when preparing changes:
* The preferred method for submitting changes is by github pull request
against the "develop" branch. If this is inconvenient, don't hesitate to
submit by other means.
* Pull requests should include only a focused and related set of changes.
Mixed features and unrelated changes (such as .gitignore) will usually be
rejected.
* For major changes, it is recommended to discuss your plans on the mailing
list or in a github issue before putting in too much effort.
* Along these lines, please note that pyqtgraph.opengl will be deprecated
soon and replaced with VisPy.
* Writing proper documentation and unit tests is highly encouraged. PyQtGraph
uses nose / py.test style testing, so tests should usually be included in a
tests/ directory adjacent to the relevant code.
* Documentation is generated with sphinx; please check that docstring changes
compile correctly.
* Style guidelines:
* PyQtGraph prefers PEP8 for most style issues, but this is not enforced
rigorously as long as the code is clean and readable.
* Use `python setup.py style` to see whether your code follows
the mandatory style guidelines checked by flake8.
* Exception 1: All variable names should use camelCase rather than
underscore_separation. This is done for consistency with Qt
* Exception 2: Function docstrings use ReStructuredText tables for
describing arguments:
```
============== ========================================================
**Arguments:**
argName1 (type) Description of argument
argName2 (type) Description of argument. Longer descriptions must
be wrapped within the column guidelines defined by the
"====" header and footer.
============== ========================================================
```
QObject subclasses that implement new signals should also describe
these in a similar table.

View File

@ -1,8 +1,9 @@
recursive-include pyqtgraph *.py *.ui *.m README *.txt
recursive-include tests *.py *.ui
recursive-include examples *.py *.ui
recursive-include examples *.py *.ui *.gz *.cfg
recursive-include doc *.rst *.py *.svg *.png *.jpg
recursive-include doc/build/html *
recursive-include tools *
include doc/Makefile doc/make.bat README.txt LICENSE.txt
include doc/Makefile doc/make.bat README.md LICENSE.txt CHANGELOG
global-exclude *.pyc

View File

@ -10,7 +10,7 @@ Copyright 2012 Luke Campagnola, University of North Carolina at Chapel Hill
Maintainer
----------
* Luke Campagnola ('luke.campagnola@%s.com' % 'gmail')
* Luke Campagnola <luke.campagnola@gmail.com>
Contributors
------------
@ -23,14 +23,24 @@ Contributors
* Ulrich Leutner
* Felix Schill
* Guillaume Poulin
* Antony Lee
* Mattias Põldaru
* Thomas S.
* Fabio Zadrozny
* Mikhail Terekhov
* Pietro Zambelli
* Stefan Holzmann
* Nicholas TJ
* John David Reaver
* David Kaplan
Requirements
------------
* PyQt 4.7+ or PySide
* python 2.6, 2.7, or 3.x
* numpy, scipy
* For 3D graphics: pyopengl
* NumPy
* For 3D graphics: pyopengl and qt-opengl
* Known to run on Windows, Linux, and Mac.
Support
@ -42,10 +52,14 @@ Installation Methods
--------------------
* To use with a specific project, simply copy the pyqtgraph subdirectory
anywhere that is importable from your project
anywhere that is importable from your project. PyQtGraph may also be
used as a git subtree by cloning the git-core repository from github.
* To install system-wide from source distribution:
`$ python setup.py install`
* For instalation packages, see the website (pyqtgraph.org)
* For installation packages, see the website (pyqtgraph.org)
* On debian-like systems, pyqtgraph requires the following packages:
python-numpy, python-qt4 | python-pyside
For 3D support: python-opengl, python-qt4-gl | python-pyside.qtopengl
Documentation
-------------
@ -61,4 +75,4 @@ Some (incomplete) documentation exists at this time.
`$ make html`
Please feel free to pester Luke or post to the forum if you need a specific
section of documentation.
section of documentation to be expanded.

View File

@ -1,7 +1,7 @@
3D Graphics
===========
Pyqtgraph uses OpenGL to provide a 3D scenegraph system. This system is functional but still early in development.
PyQtGraph uses OpenGL to provide a 3D scenegraph system. This system is functional but still early in development.
Current capabilities include:
* 3D view widget with zoom/rotate controls (mouse drag and wheel)

View File

@ -1,11 +1,14 @@
Pyqtgraph's 3D Graphics System
PyQtGraph's 3D Graphics System
==============================
The 3D graphics system in pyqtgraph is composed of a :class:`view widget <pyqtgraph.opengl.GLViewWidget>` and
several graphics items (all subclasses of :class:`GLGraphicsItem <pyqtgraph.opengl.GLGraphicsItem>`) which
can be added to a view widget.
**Note:** use of this system requires python-opengl bindings. Linux users should install the python-opengl
**Note 1:** pyqtgraph.opengl is based on the deprecated OpenGL fixed-function pipeline. Although it is
currently a functioning system, it is likely to be superceded in the future by `VisPy <http://vispy.org>`_.
**Note 2:** use of this system requires python-opengl bindings. Linux users should install the python-opengl
packages from their distribution. Windows/OSX users can download from `<http://pyopengl.sourceforge.net>`_.
Contents:

View File

@ -50,9 +50,9 @@ copyright = '2011, Luke Campagnola'
# built documents.
#
# The short X.Y version.
version = ''
version = '0.9.9'
# The full version, including alpha/beta/rc tags.
release = ''
release = '0.9.9'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -39,13 +39,14 @@ Exporting from the API
To export a file programatically, follow this example::
import pyqtgraph as pg
import pyqtgraph.exporters
# generate something to export
plt = pg.plot([1,5,2,4,3])
# create an exporter instance, as an argument give it
# the item you wish to export
exporter = pg.exporters.ImageExporter.ImageExporter(plt.plotItem)
exporter = pg.exporters.ImageExporter(plt.plotItem)
# set export parameters if needed
exporter.parameters()['width'] = 100 # (note this also affects height parameter)

View File

@ -1,4 +1,4 @@
Pyqtgraph's Helper Functions
PyQtGraph's Helper Functions
============================
Simple Data Display Functions
@ -13,7 +13,7 @@ Simple Data Display Functions
Color, Pen, and Brush Functions
-------------------------------
Qt uses the classes QColor, QPen, and QBrush to determine how to draw lines and fill shapes. These classes are highly capable but somewhat awkward to use. Pyqtgraph offers the functions :func:`~pyqtgraph.mkColor`, :func:`~pyqtgraph.mkPen`, and :func:`~pyqtgraph.mkBrush` to simplify the process of creating these classes. In most cases, however, it will be unnecessary to call these functions directly--any function or method that accepts *pen* or *brush* arguments will make use of these functions for you. For example, the following three lines all have the same effect::
Qt uses the classes QColor, QPen, and QBrush to determine how to draw lines and fill shapes. These classes are highly capable but somewhat awkward to use. PyQtGraph offers the functions :func:`~pyqtgraph.mkColor`, :func:`~pyqtgraph.mkPen`, and :func:`~pyqtgraph.mkBrush` to simplify the process of creating these classes. In most cases, however, it will be unnecessary to call these functions directly--any function or method that accepts *pen* or *brush* arguments will make use of these functions for you. For example, the following three lines all have the same effect::
pg.plot(xdata, ydata, pen='r')
pg.plot(xdata, ydata, pen=pg.mkPen('r'))

View File

@ -0,0 +1,8 @@
ErrorBarItem
============
.. autoclass:: pyqtgraph.ErrorBarItem
:members:
.. automethod:: pyqtgraph.ErrorBarItem.__init__

View File

@ -1,4 +1,4 @@
Pyqtgraph's Graphics Items
PyQtGraph's Graphics Items
==========================
Since pyqtgraph relies on Qt's GraphicsView framework, most of its graphics functionality is implemented as QGraphicsItem subclasses. This has two important consequences: 1) virtually anything you want to draw can be easily accomplished using the functionality provided by Qt. 2) Many of pyqtgraph's GraphicsItem classes can be used in any normal QGraphicsScene.
@ -23,6 +23,7 @@ Contents:
isocurveitem
axisitem
textitem
errorbaritem
arrowitem
fillbetweenitem
curvepoint

View File

@ -4,5 +4,25 @@ ROI
.. autoclass:: pyqtgraph.ROI
:members:
.. automethod:: pyqtgraph.ROI.__init__
.. autoclass:: pyqtgraph.RectROI
:members:
.. autoclass:: pyqtgraph.EllipseROI
:members:
.. autoclass:: pyqtgraph.CircleROI
:members:
.. autoclass:: pyqtgraph.LineSegmentROI
:members:
.. autoclass:: pyqtgraph.PolyLineROI
:members:
.. autoclass:: pyqtgraph.LineROI
:members:
.. autoclass:: pyqtgraph.MultiRectROI
:members:

View File

@ -12,7 +12,7 @@ There are a few suggested ways to use pyqtgraph:
Command-line use
----------------
Pyqtgraph makes it very easy to visualize data from the command line. Observe::
PyQtGraph makes it very easy to visualize data from the command line. Observe::
import pyqtgraph as pg
pg.plot(data) # data can be a list of values or a numpy array
@ -43,7 +43,7 @@ While I consider this approach somewhat lazy, it is often the case that 'lazy' i
Embedding widgets inside PyQt applications
------------------------------------------
For the serious application developer, all of the functionality in pyqtgraph is available via :ref:`widgets <api_widgets>` that can be embedded just like any other Qt widgets. Most importantly, see: :class:`PlotWidget <pyqtgraph.PlotWidget>`, :class:`ImageView <pyqtgraph.ImageView>`, :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>`, and :class:`GraphicsView <pyqtgraph.GraphicsView>`. Pyqtgraph's widgets can be included in Designer's ui files via the "Promote To..." functionality:
For the serious application developer, all of the functionality in pyqtgraph is available via :ref:`widgets <api_widgets>` that can be embedded just like any other Qt widgets. Most importantly, see: :class:`PlotWidget <pyqtgraph.PlotWidget>`, :class:`ImageView <pyqtgraph.ImageView>`, :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>`, and :class:`GraphicsView <pyqtgraph.GraphicsView>`. PyQtGraph's widgets can be included in Designer's ui files via the "Promote To..." functionality:
#. In Designer, create a QGraphicsView widget ("Graphics View" under the "Display Widgets" category).
#. Right-click on the QGraphicsView and select "Promote To...".
@ -51,13 +51,13 @@ For the serious application developer, all of the functionality in pyqtgraph is
#. Under "Header file", enter "pyqtgraph".
#. Click "Add", then click "Promote".
See the designer documentation for more information on promoting widgets.
See the designer documentation for more information on promoting widgets. The "VideoSpeedTest" and "ScatterPlotSpeedTest" examples both demonstrate the use of .ui files that are compiled to .py modules using pyuic4 or pyside-uic. The "designerExample" example demonstrates dynamically generating python classes from .ui files (no pyuic4 / pyside-uic needed).
PyQt and PySide
---------------
Pyqtgraph supports two popular python wrappers for the Qt library: PyQt and PySide. Both packages provide nearly identical
PyQtGraph supports two popular python wrappers for the Qt library: PyQt and PySide. Both packages provide nearly identical
APIs and functionality, but for various reasons (discussed elsewhere) you may prefer to use one package or the other. When
pyqtgraph is first imported, it automatically determines which library to use by making the fillowing checks:
@ -71,3 +71,53 @@ make sure it is imported before pyqtgraph::
import PySide ## this will force pyqtgraph to use PySide instead of PyQt4
import pyqtgraph as pg
Embedding PyQtGraph as a sub-package of a larger project
--------------------------------------------------------
When writing applications or python packages that make use of pyqtgraph, it is most common to install pyqtgraph system-wide (or within a virtualenv) and simply call `import pyqtgraph` from within your application. The main benefit to this is that pyqtgraph is configured independently of your application and thus you (or your users) are free to install newer versions of pyqtgraph without changing anything in your application. This is standard practice when developing with python.
However, it is also often the case, especially for scientific applications, that software is written for a very specific purpose and then archived. If we want to ensure that the software will still work ten years later, then it is preferrable to tie the application to a very specific version of pyqtgraph and *avoid* importing the system-installed version of pyqtgraph, which may be much newer (and potentially incompatible). This is especially the case when the application requires site-specific modifications to the pyqtgraph package which may not be present in the main releases.
PyQtGraph facilitates this usage through two mechanisms. First, all internal import statements in pyqtgraph are relative, which allows the package to be renamed or used as a sub-package without any naming conflicts with other versions of pyqtgraph on the system (that is, pyqtgraph never refers to itself internally as 'pyqtgraph'). Second, a git subtree repository is available at https://github.com/pyqtgraph/pyqtgraph-core.git that contains only the 'pyqtgraph/' subtree, allowing the code to be cloned directly as a subtree of the application which uses it.
The basic approach is to clone the repository into the appropriate location in your package. When you import pyqtgraph from within your package, be sure to use the full name to avoid importing any system-installed pyqtgraph packages. For example, imagine a simple project has the following structure::
my_project/
__init__.py
plotting.py
"""Plotting functions used by this package"""
import pyqtgraph as pg
def my_plot_function(*data):
pg.plot(*data)
To embed a specific version of pyqtgraph, we would clone the pyqtgraph-core repository inside the project::
my_project$ git clone https://github.com/pyqtgraph/pyqtgraph-core.git
Then adjust the import statements accordingly::
my_project/
__init__.py
pyqtgraph/
plotting.py
"""Plotting functions used by this package"""
import my_project.pyqtgraph as pg # be sure to use the local subpackage
# rather than any globally-installed
# versions.
def my_plot_function(*data):
pg.plot(*data)
Use ``git checkout pyqtgraph-core-x.x.x`` to select a specific version of the repository, or use ``git pull`` to pull pyqtgraph updates from upstream (see the git documentation for more information).
For projects that already use git for code control, it is also possible to include pyqtgraph as a git subtree within your own repository. The major advantage to this approach is that, in addition to being able to pull pyqtgraph updates from the upstream repository, it is also possible to commit your local pyqtgraph changes into the project repository and push those changes upstream::
my_project$ git remote add pyqtgraph-core https://github.com/pyqtgraph/pyqtgraph-core.git
my_project$ git fetch pyqtgraph-core
my_project$ git merge -s ours --no-commit pyqtgraph-core/core
my_project$ mkdir pyqtgraph
my_project$ git read-tree -u --prefix=pyqtgraph/ pyqtgraph-core/core
my_project$ git commit -m "Added pyqtgraph to project repository"
See the ``git subtree`` documentation for more information.

View File

@ -1,7 +1,7 @@
Displaying images and video
===========================
Pyqtgraph displays 2D numpy arrays as images and provides tools for determining how to translate between the numpy data type and RGB values on the screen. If you want to display data from common image and video file formats, you will need to load the data first using another library (PIL works well for images and built-in numpy conversion).
PyQtGraph displays 2D numpy arrays as images and provides tools for determining how to translate between the numpy data type and RGB values on the screen. If you want to display data from common image and video file formats, you will need to load the data first using another library (PIL works well for images and built-in numpy conversion).
The easiest way to display 2D or 3D data is using the :func:`pyqtgraph.image` function::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -13,7 +13,7 @@
height="268.51233"
id="svg2"
version="1.1"
inkscape:version="0.48.1 r9760"
inkscape:version="0.48.4 r9939"
sodipodi:docname="plottingClasses.svg"
inkscape:export-filename="/home/luke/work/manis_lab/code/pyqtgraph/documentation/source/images/plottingClasses.png"
inkscape:export-xdpi="124.99"
@ -50,12 +50,12 @@
inkscape:cx="383.64946"
inkscape:cy="21.059243"
inkscape:document-units="px"
inkscape:current-layer="g3978"
inkscape:current-layer="g3891"
showgrid="false"
inkscape:window-width="1400"
inkscape:window-width="1918"
inkscape:window-height="1030"
inkscape:window-x="-3"
inkscape:window-y="-3"
inkscape:window-x="1"
inkscape:window-y="0"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
@ -69,7 +69,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@ -345,7 +345,7 @@
id="tspan3897"
x="124.24876"
y="376.57013"
style="font-size:18px">GraphicsLayoutItem(GraphicsItem)</tspan></text>
style="font-size:18px">GraphicsLayout(GraphicsItem)</tspan></text>
</g>
<g
transform="translate(17.172593,259.49748)"

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,7 +1,7 @@
Installation
============
Pyqtgraph does not really require any installation scripts. All that is needed is for the pyqtgraph folder to be placed someplace importable. Most people will prefer to simply place this folder within a larger project folder. If you want to make pyqtgraph available system-wide, use one of the methods listed below:
PyQtGraph does not really require any installation scripts. All that is needed is for the pyqtgraph folder to be placed someplace importable. Most people will prefer to simply place this folder within a larger project folder. If you want to make pyqtgraph available system-wide, use one of the methods listed below:
* **Debian, Ubuntu, and similar Linux:** Download the .deb file linked at the top of the pyqtgraph web page or install using apt by putting "deb http://luke.campagnola.me/debian dev/" in your /etc/apt/sources.list file and install the python-pyqtgraph package.
* **Arch Linux:** Looks like someone has posted unofficial packages for Arch (thanks windel). (https://aur.archlinux.org/packages.php?ID=62577)

View File

@ -6,9 +6,9 @@ Introduction
What is pyqtgraph?
------------------
Pyqtgraph is a graphics and user interface library for Python that provides functionality commonly required in engineering and science applications. Its primary goals are 1) to provide fast, interactive graphics for displaying data (plots, video, etc.) and 2) to provide tools to aid in rapid application development (for example, property trees such as used in Qt Designer).
PyQtGraph is a graphics and user interface library for Python that provides functionality commonly required in engineering and science applications. Its primary goals are 1) to provide fast, interactive graphics for displaying data (plots, video, etc.) and 2) to provide tools to aid in rapid application development (for example, property trees such as used in Qt Designer).
Pyqtgraph makes heavy use of the Qt GUI platform (via PyQt or PySide) for its high-performance graphics and numpy for heavy number crunching. In particular, pyqtgraph uses Qt's GraphicsView framework which is a highly capable graphics system on its own; we bring optimized and simplified primitives to this framework to allow data visualization with minimal effort.
PyQtGraph makes heavy use of the Qt GUI platform (via PyQt or PySide) for its high-performance graphics and numpy for heavy number crunching. In particular, pyqtgraph uses Qt's GraphicsView framework which is a highly capable graphics system on its own; we bring optimized and simplified primitives to this framework to allow data visualization with minimal effort.
It is known to run on Linux, Windows, and OSX
@ -33,7 +33,7 @@ Amongst the core features of pyqtgraph are:
Examples
--------
Pyqtgraph includes an extensive set of examples that can be accessed by running::
PyQtGraph includes an extensive set of examples that can be accessed by running::
import pyqtgraph.examples
pyqtgraph.examples.run()

View File

@ -8,7 +8,7 @@ This feature is commonly seen, for example, in user interface design application
Parameters generally have a name, a data type (int, float, string, color, etc), and a value matching the data type. Parameters may be grouped and nested
to form hierarchies and may be subclassed to provide custom behavior and display widgets.
Pyqtgraph's parameter tree system works similarly to the model-view architecture used by some components of Qt: Parameters are purely data-handling classes
PyQtGraph's parameter tree system works similarly to the model-view architecture used by some components of Qt: Parameters are purely data-handling classes
that exist independent of any graphical interface. A ParameterTree is a widget that automatically generates a graphical interface which represents
the state of a haierarchy of Parameter objects and allows the user to edit the values within that hierarchy. This separation of data (model) and graphical
interface (view) allows the same data to be represented multiple times and in a variety of different ways.

View File

@ -3,12 +3,12 @@ Plotting in pyqtgraph
There are a few basic ways to plot data in pyqtgraph:
================================================================ ==================================================
:func:`pyqtgraph.plot` Create a new plot window showing your data
:func:`PlotWidget.plot() <pyqtgraph.PlotWidget.plot>` Add a new set of data to an existing plot widget
:func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>` Add a new set of data to an existing plot widget
:func:`GraphicsWindow.addPlot() <pyqtgraph.GraphicsWindow.plot>` Add a new plot to a grid of plots
================================================================ ==================================================
=================================================================== ==================================================
:func:`pyqtgraph.plot` Create a new plot window showing your data
:func:`PlotWidget.plot() <pyqtgraph.PlotWidget.plot>` Add a new set of data to an existing plot widget
:func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>` Add a new set of data to an existing plot widget
:func:`GraphicsLayout.addPlot() <pyqtgraph.GraphicsLayout.addPlot>` Add a new plot to a grid of plots
=================================================================== ==================================================
All of these will accept the same basic arguments which control how the plot data is interpreted and displayed:
@ -28,20 +28,20 @@ All of the above functions also return handles to the objects that are created,
Organization of Plotting Classes
--------------------------------
There are several classes invloved in displaying plot data. Most of these classes are instantiated automatically, but it is useful to understand how they are organized and relate to each other. Pyqtgraph is based heavily on Qt's GraphicsView framework--if you are not already familiar with this, it's worth reading about (but not essential). Most importantly: 1) Qt GUIs are composed of QWidgets, 2) A special widget called QGraphicsView is used for displaying complex graphics, and 3) QGraphicsItems define the objects that are displayed within a QGraphicsView.
There are several classes invloved in displaying plot data. Most of these classes are instantiated automatically, but it is useful to understand how they are organized and relate to each other. PyQtGraph is based heavily on Qt's GraphicsView framework--if you are not already familiar with this, it's worth reading about (but not essential). Most importantly: 1) Qt GUIs are composed of QWidgets, 2) A special widget called QGraphicsView is used for displaying complex graphics, and 3) QGraphicsItems define the objects that are displayed within a QGraphicsView.
* Data Classes (all subclasses of QGraphicsItem)
* PlotCurveItem - Displays a plot line given x,y data
* ScatterPlotItem - Displays points given x,y data
* :class:`PlotDataItem <pyqtgraph.graphicsItems.PlotDataItem.PlotDataItem>` - Combines PlotCurveItem and ScatterPlotItem. The plotting functions discussed above create objects of this type.
* :class:`PlotCurveItem <pyqtgraph.PlotCurveItem>` - Displays a plot line given x,y data
* :class:`ScatterPlotItem <pyqtgraph.ScatterPlotItem>` - Displays points given x,y data
* :class:`PlotDataItem <pyqtgraph.PlotDataItem>` - Combines PlotCurveItem and ScatterPlotItem. The plotting functions discussed above create objects of this type.
* Container Classes (subclasses of QGraphicsItem; contain other QGraphicsItem objects and must be viewed from within a GraphicsView)
* PlotItem - Contains a ViewBox for displaying data as well as AxisItems and labels for displaying the axes and title. This is a QGraphicsItem subclass and thus may only be used from within a GraphicsView
* GraphicsLayoutItem - QGraphicsItem subclass which displays a grid of items. This is used to display multiple PlotItems together.
* ViewBox - A QGraphicsItem subclass for displaying data. The user may scale/pan the contents of a ViewBox using the mouse. Typically all PlotData/PlotCurve/ScatterPlotItems are displayed from within a ViewBox.
* AxisItem - Displays axis values, ticks, and labels. Most commonly used with PlotItem.
* :class:`PlotItem <pyqtgraph.PlotItem>` - Contains a ViewBox for displaying data as well as AxisItems and labels for displaying the axes and title. This is a QGraphicsItem subclass and thus may only be used from within a GraphicsView
* :class:`GraphicsLayout <pyqtgraph.GraphicsLayout>` - QGraphicsItem subclass which displays a grid of items. This is used to display multiple PlotItems together.
* :class:`ViewBox <pyqtgraph.ViewBox>` - A QGraphicsItem subclass for displaying data. The user may scale/pan the contents of a ViewBox using the mouse. Typically all PlotData/PlotCurve/ScatterPlotItems are displayed from within a ViewBox.
* :class:`AxisItem <pyqtgraph.AxisItem>` - Displays axis values, ticks, and labels. Most commonly used with PlotItem.
* Container Classes (subclasses of QWidget; may be embedded in PyQt GUIs)
* PlotWidget - A subclass of GraphicsView with a single PlotItem displayed. Most of the methods provided by PlotItem are also available through PlotWidget.
* GraphicsLayoutWidget - QWidget subclass displaying a single GraphicsLayoutItem. Most of the methods provided by GraphicsLayoutItem are also available through GraphicsLayoutWidget.
* :class:`PlotWidget <pyqtgraph.PlotWidget>` - A subclass of GraphicsView with a single PlotItem displayed. Most of the methods provided by PlotItem are also available through PlotWidget.
* :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>` - QWidget subclass displaying a single GraphicsLayoutItem. Most of the methods provided by GraphicsLayoutItem are also available through GraphicsLayoutWidget.
.. image:: images/plottingClasses.png

View File

@ -3,7 +3,7 @@ Rapid GUI prototyping
[Just an overview; documentation is not complete yet]
Pyqtgraph offers several powerful features which are commonly used in engineering and scientific applications.
PyQtGraph offers several powerful features which are commonly used in engineering and scientific applications.
Parameter Trees
---------------
@ -16,7 +16,7 @@ See the `parametertree documentation <parametertree>`_ for more information.
Visual Programming Flowcharts
-----------------------------
Pyqtgraph's flowcharts provide a visual programming environment similar in concept to LabView--functional modules are added to a flowchart and connected by wires to define a more complex and arbitrarily configurable algorithm. A small number of predefined modules (called Nodes) are included with pyqtgraph, but most flowchart developers will want to define their own library of Nodes. At their core, the Nodes are little more than 1) a Python function 2) a list of input/output terminals, and 3) an optional widget providing a control panel for the Node. Nodes may transmit/receive any type of Python object via their terminals.
PyQtGraph's flowcharts provide a visual programming environment similar in concept to LabView--functional modules are added to a flowchart and connected by wires to define a more complex and arbitrarily configurable algorithm. A small number of predefined modules (called Nodes) are included with pyqtgraph, but most flowchart developers will want to define their own library of Nodes. At their core, the Nodes are little more than 1) a Python function 2) a list of input/output terminals, and 3) an optional widget providing a control panel for the Node. Nodes may transmit/receive any type of Python object via their terminals.
See the `flowchart documentation <flowchart>`_ and the flowchart examples for more information.
@ -30,6 +30,6 @@ The Canvas is a system designed to allow the user to add/remove items to a 2D ca
Dockable Widgets
----------------
The dockarea system allows the design of user interfaces which can be rearranged by the user at runtime. Docks can be moved, resized, stacked, and torn out of the main window. This is similar in principle to the docking system built into Qt, but offers a more deterministic dock placement API (in Qt it is very difficult to programatically generate complex dock arrangements). Additionally, Qt's docks are designed to be used as small panels around the outer edge of a window. Pyqtgraph's docks were created with the notion that the entire window (or any portion of it) would consist of dockable components.
The dockarea system allows the design of user interfaces which can be rearranged by the user at runtime. Docks can be moved, resized, stacked, and torn out of the main window. This is similar in principle to the docking system built into Qt, but offers a more deterministic dock placement API (in Qt it is very difficult to programatically generate complex dock arrangements). Additionally, Qt's docks are designed to be used as small panels around the outer edge of a window. PyQtGraph's docks were created with the notion that the entire window (or any portion of it) would consist of dockable components.

View File

@ -1,7 +1,7 @@
Qt Crash Course
===============
Pyqtgraph makes extensive use of Qt for generating nearly all of its visual output and interfaces. Qt's documentation is very well written and we encourage all pyqtgraph developers to familiarize themselves with it. The purpose of this section is to provide an introduction to programming with Qt (using either PyQt or PySide) for the pyqtgraph developer.
PyQtGraph makes extensive use of Qt for generating nearly all of its visual output and interfaces. Qt's documentation is very well written and we encourage all pyqtgraph developers to familiarize themselves with it. The purpose of this section is to provide an introduction to programming with Qt (using either PyQt or PySide) for the pyqtgraph developer.
QWidgets and Layouts
--------------------
@ -12,7 +12,7 @@ A Qt GUI is almost always composed of a few basic components:
* Multiple QWidget instances such as QPushButton, QLabel, QComboBox, etc.
* QLayout instances (optional, but strongly encouraged) which automatically manage the positioning of widgets to allow the GUI to resize in a usable way.
Pyqtgraph fits into this scheme by providing its own QWidget subclasses to be inserted into your GUI.
PyQtGraph fits into this scheme by providing its own QWidget subclasses to be inserted into your GUI.
Example::

View File

@ -1,7 +1,7 @@
Interactive Data Selection Controls
===================================
Pyqtgraph includes graphics items which allow the user to select and mark regions of data.
PyQtGraph includes graphics items which allow the user to select and mark regions of data.
Linear Selection and Marking
----------------------------

View File

@ -0,0 +1,6 @@
ConsoleWidget
=============
.. autoclass:: pyqtgraph.console.ConsoleWidget
:members:

View File

@ -1,9 +1,9 @@
.. _api_widgets:
Pyqtgraph's Widgets
PyQtGraph's Widgets
===================
Pyqtgraph provides several QWidget subclasses which are useful for building user interfaces. These widgets can generally be used in any Qt application and provide functionality that is frequently useful in science and engineering applications.
PyQtGraph provides several QWidget subclasses which are useful for building user interfaces. These widgets can generally be used in any Qt application and provide functionality that is frequently useful in science and engineering applications.
Contents:
@ -17,10 +17,10 @@ Contents:
gradientwidget
histogramlutwidget
parametertree
consolewidget
colormapwidget
scatterplotwidget
graphicsview
rawimagewidget
datatreewidget
tablewidget
treewidget

View File

@ -2,7 +2,7 @@
"""
Display an animated arrowhead following a curve.
This example uses the CurveArrow class, which is a combination
of ArrowItem and CurvePoint.
of ArrowItem and CurvePoint.
To place a static arrow anywhere in a scene, use ArrowItem.
To attach other types of item to a curve, use CurvePoint.
@ -45,6 +45,7 @@ p.setRange(QtCore.QRectF(-20, -10, 60, 20))
## Animated arrow following curve
c = p2.plot(x=np.sin(np.linspace(0, 2*np.pi, 1000)), y=np.cos(np.linspace(0, 6*np.pi, 1000)))
a = pg.CurveArrow(c)
a.setStyle(headLen=40)
p2.addItem(a)
anim = a.makeAnimation(loop=-1)
anim.start()

41
examples/BarGraphItem.py Normal file
View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
"""
Simple example using BarGraphItem
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
win = pg.plot()
win.setWindowTitle('pyqtgraph example: BarGraphItem')
x = np.arange(10)
y1 = np.sin(x)
y2 = 1.1 * np.sin(x+1)
y3 = 1.2 * np.sin(x+2)
bg1 = pg.BarGraphItem(x=x, height=y1, width=0.3, brush='r')
bg2 = pg.BarGraphItem(x=x+0.33, height=y2, width=0.3, brush='g')
bg3 = pg.BarGraphItem(x=x+0.66, height=y3, width=0.3, brush='b')
win.addItem(bg1)
win.addItem(bg2)
win.addItem(bg3)
# Final example shows how to handle mouse clicks:
class BarGraph(pg.BarGraphItem):
def mouseClickEvent(self, event):
print("clicked")
bg = BarGraph(x=x, y=y1*0.3+2, height=0.4+y1*0.2, width=0.8)
win.addItem(bg)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

136
examples/CustomGraphItem.py Normal file
View File

@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
"""
Simple example of subclassing GraphItem.
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
# Enable antialiasing for prettier plots
pg.setConfigOptions(antialias=True)
w = pg.GraphicsWindow()
w.setWindowTitle('pyqtgraph example: CustomGraphItem')
v = w.addViewBox()
v.setAspectLocked()
class Graph(pg.GraphItem):
def __init__(self):
self.dragPoint = None
self.dragOffset = None
self.textItems = []
pg.GraphItem.__init__(self)
self.scatter.sigClicked.connect(self.clicked)
def setData(self, **kwds):
self.text = kwds.pop('text', [])
self.data = kwds
if 'pos' in self.data:
npts = self.data['pos'].shape[0]
self.data['data'] = np.empty(npts, dtype=[('index', int)])
self.data['data']['index'] = np.arange(npts)
self.setTexts(self.text)
self.updateGraph()
def setTexts(self, text):
for i in self.textItems:
i.scene().removeItem(i)
self.textItems = []
for t in text:
item = pg.TextItem(t)
self.textItems.append(item)
item.setParentItem(self)
def updateGraph(self):
pg.GraphItem.setData(self, **self.data)
for i,item in enumerate(self.textItems):
item.setPos(*self.data['pos'][i])
def mouseDragEvent(self, ev):
if ev.button() != QtCore.Qt.LeftButton:
ev.ignore()
return
if ev.isStart():
# We are already one step into the drag.
# Find the point(s) at the mouse cursor when the button was first
# pressed:
pos = ev.buttonDownPos()
pts = self.scatter.pointsAt(pos)
if len(pts) == 0:
ev.ignore()
return
self.dragPoint = pts[0]
ind = pts[0].data()[0]
self.dragOffset = self.data['pos'][ind] - pos
elif ev.isFinish():
self.dragPoint = None
return
else:
if self.dragPoint is None:
ev.ignore()
return
ind = self.dragPoint.data()[0]
self.data['pos'][ind] = ev.pos() + self.dragOffset
self.updateGraph()
ev.accept()
def clicked(self, pts):
print("clicked: %s" % pts)
g = Graph()
v.addItem(g)
## Define positions of nodes
pos = np.array([
[0,0],
[10,0],
[0,10],
[10,10],
[5,5],
[15,5]
], dtype=float)
## Define the set of connections in the graph
adj = np.array([
[0,1],
[1,3],
[3,2],
[2,0],
[1,5],
[3,5],
])
## Define the symbol to use for each node (this is optional)
symbols = ['o','o','o','o','t','+']
## Define the line style for each connection (this is optional)
lines = np.array([
(255,0,0,255,1),
(255,0,255,255,2),
(255,0,255,255,3),
(255,255,0,255,2),
(255,0,0,255,1),
(255,255,255,255,4),
], dtype=[('red',np.ubyte),('green',np.ubyte),('blue',np.ubyte),('alpha',np.ubyte),('width',float)])
## Define text to show next to each symbol
texts = ["Point %d" % i for i in range(6)]
## Update the graph
g.setData(pos=pos, adj=adj, pen=lines, size=1, symbol=symbols, pxMode=False, text=texts)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -11,7 +11,6 @@ a 2D plane and interpolate data along that plane to generate a slice image
import initExample
import numpy as np
import scipy
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg

View File

@ -7,7 +7,7 @@ Demonstrates basic use of ErrorBarItem
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg

View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
"""
Demonstrates use of FillBetweenItem to fill the space between two plot curves.
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
win = pg.plot()
win.setWindowTitle('pyqtgraph example: FillBetweenItem')
win.setXRange(-10, 10)
win.setYRange(-10, 10)
N = 200
x = np.linspace(-10, 10, N)
gauss = np.exp(-x**2 / 20.)
mn = mx = np.zeros(len(x))
curves = [win.plot(x=x, y=np.zeros(len(x)), pen='k') for i in range(4)]
brushes = [0.5, (100, 100, 255), 0.5]
fills = [pg.FillBetweenItem(curves[i], curves[i+1], brushes[i]) for i in range(3)]
for f in fills:
win.addItem(f)
def update():
global mx, mn, curves, gauss, x
a = 5 / abs(np.random.normal(loc=1, scale=0.2))
y1 = -np.abs(a*gauss + np.random.normal(size=len(x)))
y2 = np.abs(a*gauss + np.random.normal(size=len(x)))
s = 0.01
mn = np.where(y1<mn, y1, mn) * (1-s) + y1 * s
mx = np.where(y2>mx, y2, mx) * (1-s) + y2 * s
curves[0].setData(x, mn)
curves[1].setData(x, y1)
curves[2].setData(x, y2)
curves[3].setData(x, mx)
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(30)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -58,11 +58,15 @@ fc.setInput(dataIn=data)
## populate the flowchart with a basic set of processing nodes.
## (usually we let the user do this)
plotList = {'Top Plot': pw1, 'Bottom Plot': pw2}
pw1Node = fc.createNode('PlotWidget', pos=(0, -150))
pw1Node.setPlotList(plotList)
pw1Node.setPlot(pw1)
pw2Node = fc.createNode('PlotWidget', pos=(150, -150))
pw2Node.setPlot(pw2)
pw2Node.setPlotList(plotList)
fNode = fc.createNode('GaussianFilter', pos=(0, 0))
fNode.ctrls['sigma'].setValue(5)

View File

@ -12,7 +12,6 @@ from pyqtgraph.flowchart.library.common import CtrlNode
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import numpy as np
import scipy.ndimage
app = QtGui.QApplication([])
@ -44,7 +43,7 @@ win.show()
## generate random input data
data = np.random.normal(size=(100,100))
data = 25 * scipy.ndimage.gaussian_filter(data, (5,5))
data = 25 * pg.gaussianFilter(data, (5,5))
data += np.random.normal(size=(100,100))
data[40:60, 40:60] += 15.0
data[30:50, 30:50] += 15.0
@ -83,15 +82,14 @@ class ImageViewNode(Node):
else:
self.view.setImage(data)
## register the class so it will appear in the menu of node types.
## It will appear in the 'display' sub-menu.
fclib.registerNodeType(ImageViewNode, [('Display',)])
## We will define an unsharp masking filter node as a subclass of CtrlNode.
## CtrlNode is just a convenience class that automatically creates its
## control widget based on a simple data structure.
class UnsharpMaskNode(CtrlNode):
"""Return the input data passed through scipy.ndimage.gaussian_filter."""
"""Return the input data passed through pg.gaussianFilter."""
nodeName = "UnsharpMask"
uiTemplate = [
('sigma', 'spin', {'value': 1.0, 'step': 1.0, 'range': [0.0, None]}),
@ -111,14 +109,30 @@ class UnsharpMaskNode(CtrlNode):
# CtrlNode has created self.ctrls, which is a dict containing {ctrlName: widget}
sigma = self.ctrls['sigma'].value()
strength = self.ctrls['strength'].value()
output = dataIn - (strength * scipy.ndimage.gaussian_filter(dataIn, (sigma,sigma)))
output = dataIn - (strength * pg.gaussianFilter(dataIn, (sigma,sigma)))
return {'dataOut': output}
## To make our custom node classes available in the flowchart context menu,
## we can either register them with the default node library or make a
## new library.
## register the class so it will appear in the menu of node types.
## It will appear in a new 'image' sub-menu.
fclib.registerNodeType(UnsharpMaskNode, [('Image',)])
## Method 1: Register to global default library:
#fclib.registerNodeType(ImageViewNode, [('Display',)])
#fclib.registerNodeType(UnsharpMaskNode, [('Image',)])
## Method 2: If we want to make our custom node available only to this flowchart,
## then instead of registering the node type globally, we can create a new
## NodeLibrary:
library = fclib.LIBRARY.copy() # start with the default node set
library.addNodeType(ImageViewNode, [('Display',)])
# Add the unsharp mask node to two locations in the menu to demonstrate
# that we can create arbitrary menu structures
library.addNodeType(UnsharpMaskNode, [('Image',),
('Submenu_test','submenu2','submenu3')])
fc.setLibrary(library)
## Now we will programmatically add nodes to define the function of the flowchart.
## Normally, the user will do this manually or by loading a pre-generated

View File

@ -12,7 +12,6 @@ from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import pyqtgraph as pg
import numpy as np
import scipy.ndimage as ndi
app = QtGui.QApplication([])
w = gl.GLViewWidget()
@ -22,8 +21,8 @@ w.setWindowTitle('pyqtgraph example: GLImageItem')
## create volume data set to slice three images from
shape = (100,100,70)
data = ndi.gaussian_filter(np.random.normal(size=shape), (4,4,4))
data += ndi.gaussian_filter(np.random.normal(size=shape), (15,15,15))*15
data = pg.gaussianFilter(np.random.normal(size=shape), (4,4,4))
data += pg.gaussianFilter(np.random.normal(size=shape), (15,15,15))*15
## slice out three planes, convert to RGBA for OpenGL texture
levels = (-0.08, 0.08)

View File

@ -53,21 +53,26 @@ m1.translate(5, 5, 0)
m1.setGLOptions('additive')
w.addItem(m1)
## Example 2:
## Array of vertex positions, three per face
verts = np.empty((36, 3, 3), dtype=np.float32)
theta = np.linspace(0, 2*np.pi, 37)[:-1]
verts[:,0] = np.vstack([2*np.cos(theta), 2*np.sin(theta), [0]*36]).T
verts[:,1] = np.vstack([4*np.cos(theta+0.2), 4*np.sin(theta+0.2), [-1]*36]).T
verts[:,2] = np.vstack([4*np.cos(theta-0.2), 4*np.sin(theta-0.2), [1]*36]).T
## Colors are specified per-vertex
verts = verts[faces] ## Same mesh geometry as example 2, but now we are passing in 12 vertexes
colors = np.random.random(size=(verts.shape[0], 3, 4))
#colors[...,3] = 1.0
m2 = gl.GLMeshItem(vertexes=verts, vertexColors=colors, smooth=False, shader='balloon')
m2 = gl.GLMeshItem(vertexes=verts, vertexColors=colors, smooth=False, shader='balloon',
drawEdges=True, edgeColor=(1, 1, 0, 1))
m2.translate(-5, 5, 0)
w.addItem(m2)
## Example 3:
## icosahedron
## sphere
md = gl.MeshData.sphere(rows=10, cols=20)
#colors = np.random.random(size=(md.faceCount(), 4))
@ -79,7 +84,7 @@ colors[:,1] = np.linspace(0, 1, colors.shape[0])
md.setFaceColors(colors)
m3 = gl.GLMeshItem(meshdata=md, smooth=False)#, shader='balloon')
#m3.translate(-5, -5, 0)
m3.translate(5, -5, 0)
w.addItem(m3)
@ -91,49 +96,29 @@ m4 = gl.GLMeshItem(meshdata=md, smooth=False, drawFaces=False, drawEdges=True, e
m4.translate(0,10,0)
w.addItem(m4)
# Example 5:
# cylinder
md = gl.MeshData.cylinder(rows=10, cols=20, radius=[1., 2.0], length=5.)
md2 = gl.MeshData.cylinder(rows=10, cols=20, radius=[2., 0.5], length=10.)
colors = np.ones((md.faceCount(), 4), dtype=float)
colors[::2,0] = 0
colors[:,1] = np.linspace(0, 1, colors.shape[0])
md.setFaceColors(colors)
m5 = gl.GLMeshItem(meshdata=md, smooth=True, drawEdges=True, edgeColor=(1,0,0,1), shader='balloon')
colors = np.ones((md.faceCount(), 4), dtype=float)
colors[::2,0] = 0
colors[:,1] = np.linspace(0, 1, colors.shape[0])
md2.setFaceColors(colors)
m6 = gl.GLMeshItem(meshdata=md2, smooth=True, drawEdges=False, shader='balloon')
m6.translate(0,0,7.5)
m6.rotate(0., 0, 1, 1)
#m5.translate(-3,3,0)
w.addItem(m5)
w.addItem(m6)
#def psi(i, j, k, offset=(25, 25, 50)):
#x = i-offset[0]
#y = j-offset[1]
#z = k-offset[2]
#th = np.arctan2(z, (x**2+y**2)**0.5)
#phi = np.arctan2(y, x)
#r = (x**2 + y**2 + z **2)**0.5
#a0 = 1
##ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th)
#ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1)
#return ps
##return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2
#print("Generating scalar field..")
#data = np.abs(np.fromfunction(psi, (50,50,100)))
##data = np.fromfunction(lambda i,j,k: np.sin(0.2*((i-25)**2+(j-15)**2+k**2)**0.5), (50,50,50));
#print("Generating isosurface..")
#verts = pg.isosurface(data, data.max()/4.)
#md = gl.MeshData.MeshData(vertexes=verts)
#colors = np.ones((md.vertexes(indexed='faces').shape[0], 4), dtype=float)
#colors[:,3] = 0.3
#colors[:,2] = np.linspace(0, 1, colors.shape[0])
#m1 = gl.GLMeshItem(meshdata=md, color=colors, smooth=False)
#w.addItem(m1)
#m1.translate(-25, -25, -20)
#m2 = gl.GLMeshItem(vertexes=verts, color=colors, smooth=True)
#w.addItem(m2)
#m2.translate(-25, -25, -50)

View File

@ -10,7 +10,6 @@ import initExample
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.opengl as gl
import scipy.ndimage as ndi
import numpy as np
## Create a GL View widget to display data
@ -29,7 +28,7 @@ w.addItem(g)
## Simple surface plot example
## x, y values are not specified, so assumed to be 0:50
z = ndi.gaussian_filter(np.random.normal(size=(50,50)), (1,1))
z = pg.gaussianFilter(np.random.normal(size=(50,50)), (1,1))
p1 = gl.GLSurfacePlotItem(z=z, shader='shaded', color=(0.5, 0.5, 1, 1))
p1.scale(16./49., 16./49., 1.0)
p1.translate(-18, 2, 0)
@ -46,7 +45,7 @@ w.addItem(p2)
## Manually specified colors
z = ndi.gaussian_filter(np.random.normal(size=(50,50)), (1,1))
z = pg.gaussianFilter(np.random.normal(size=(50,50)), (1,1))
x = np.linspace(-12, 12, 50)
y = np.linspace(-12, 12, 50)
colors = np.ones((50,50,4), dtype=float)

View File

@ -7,7 +7,6 @@ Use a HistogramLUTWidget to control the contrast / coloration of an image.
import initExample
import numpy as np
import scipy.ndimage as ndi
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
@ -34,7 +33,7 @@ l.addWidget(v, 0, 0)
w = pg.HistogramLUTWidget()
l.addWidget(w, 0, 1)
data = ndi.gaussian_filter(np.random.normal(size=(256, 256)), (20, 20))
data = pg.gaussianFilter(np.random.normal(size=(256, 256)), (20, 20))
for i in range(32):
for j in range(32):
data[i*8, j*8] += .1

View File

@ -14,7 +14,6 @@ displaying and analyzing 2D and 3D data. ImageView provides:
import initExample
import numpy as np
import scipy
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
@ -29,7 +28,7 @@ win.show()
win.setWindowTitle('pyqtgraph example: ImageView')
## Create random 3D data set with noisy signals
img = scipy.ndimage.gaussian_filter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
img = pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
img = img[np.newaxis,:,:]
decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis]
data = np.random.normal(size=(100, 200, 200))

View File

@ -14,7 +14,7 @@ plt.addLegend()
#l = pg.LegendItem((100,60), offset=(70,30)) # args are (size, offset)
#l.setParentItem(plt.graphicsItem()) # Note we do NOT call plt.addItem in this case
c1 = plt.plot([1,3,2,4], pen='r', name='red plot')
c1 = plt.plot([1,3,2,4], pen='r', symbol='o', symbolPen='r', symbolBrush=0.5, name='red plot')
c2 = plt.plot([2,1,4,3], pen='g', fillLevel=0, fillBrush=(255,255,255,30), name='green plot')
#l.addItem(c1, 'red plot')
#l.addItem(c2, 'green plot')

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
"""
Demonstrates selecting plot curves by mouse click
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
win = pg.plot()
win.setWindowTitle('pyqtgraph example: Plot data selection')
curves = [
pg.PlotCurveItem(y=np.sin(np.linspace(0, 20, 1000)), pen='r', clickable=True),
pg.PlotCurveItem(y=np.sin(np.linspace(1, 21, 1000)), pen='g', clickable=True),
pg.PlotCurveItem(y=np.sin(np.linspace(2, 22, 1000)), pen='b', clickable=True),
]
def plotClicked(curve):
global curves
for i,c in enumerate(curves):
if c is curve:
c.setPen('rgb'[i], width=3)
else:
c.setPen('rgb'[i], width=1)
for c in curves:
win.addItem(c)
c.sigClicked.connect(plotClicked)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -22,17 +22,25 @@ p.setWindowTitle('pyqtgraph example: MultiPlotSpeedTest')
#p.setRange(QtCore.QRectF(0, -10, 5000, 20))
p.setLabel('bottom', 'Index', units='B')
nPlots = 10
nPlots = 100
nSamples = 500
#curves = [p.plot(pen=(i,nPlots*1.3)) for i in range(nPlots)]
curves = [pg.PlotCurveItem(pen=(i,nPlots*1.3)) for i in range(nPlots)]
for c in curves:
curves = []
for i in range(nPlots):
c = pg.PlotCurveItem(pen=(i,nPlots*1.3))
p.addItem(c)
c.setPos(0,i*6)
curves.append(c)
rgn = pg.LinearRegionItem([1,100])
p.setYRange(0, nPlots*6)
p.setXRange(0, nSamples)
p.resize(600,900)
rgn = pg.LinearRegionItem([nSamples/5.,nSamples/3.])
p.addItem(rgn)
data = np.random.normal(size=(53,5000/nPlots))
data = np.random.normal(size=(nPlots*23,nSamples))
ptr = 0
lastTime = time()
fps = None
@ -42,7 +50,8 @@ def update():
count += 1
#print "---------", count
for i in range(nPlots):
curves[i].setData(i+data[(ptr+i)%data.shape[0]])
curves[i].setData(data[(ptr+i)%data.shape[0]])
#print " setData done."
ptr += nPlots
now = time()

View File

@ -10,11 +10,11 @@ from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
from pyqtgraph import MultiPlotWidget
try:
from metaarray import *
from pyqtgraph.metaarray import *
except:
print("MultiPlot is only used with MetaArray for now (and you do not have the metaarray package)")
exit()
app = QtGui.QApplication([])
mw = QtGui.QMainWindow()
mw.resize(800,800)
@ -22,7 +22,15 @@ pw = MultiPlotWidget()
mw.setCentralWidget(pw)
mw.show()
ma = MetaArray(random.random((3, 1000)), info=[{'name': 'Signal', 'cols': [{'name': 'Col1'}, {'name': 'Col2'}, {'name': 'Col3'}]}, {'name': 'Time', 'vals': linspace(0., 1., 1000)}])
data = random.normal(size=(3, 1000)) * np.array([[0.1], [1e-5], [1]])
ma = MetaArray(data, info=[
{'name': 'Signal', 'cols': [
{'name': 'Col1', 'units': 'V'},
{'name': 'Col2', 'units': 'A'},
{'name': 'Col3'},
]},
{'name': 'Time', 'values': linspace(0., 1., 1000), 'units': 's'}
])
pw.plot(ma)
## Start Qt event loop unless running in interactive mode.

View File

@ -27,9 +27,9 @@ pg.setConfigOptions(antialias=True)
p1 = win.addPlot(title="Basic array plotting", y=np.random.normal(size=100))
p2 = win.addPlot(title="Multiple curves")
p2.plot(np.random.normal(size=100), pen=(255,0,0))
p2.plot(np.random.normal(size=100)+5, pen=(0,255,0))
p2.plot(np.random.normal(size=100)+10, pen=(0,0,255))
p2.plot(np.random.normal(size=100), pen=(255,0,0), name="Red curve")
p2.plot(np.random.normal(size=110)+5, pen=(0,255,0), name="Blue curve")
p2.plot(np.random.normal(size=120)+10, pen=(0,0,255), name="Green curve")
p3 = win.addPlot(title="Drawing with points")
p3.plot(np.random.normal(size=100), pen=(200,200,200), symbolBrush=(255,0,0), symbolPen='w')

View File

@ -132,7 +132,7 @@ label4 = w4.addLabel(text, row=0, col=0)
v4 = w4.addViewBox(row=1, col=0, lockAspect=True)
g = pg.GridItem()
v4.addItem(g)
r4 = pg.ROI([0,0], [100,100])
r4 = pg.ROI([0,0], [100,100], removable=True)
r4.addRotateHandle([1,0], [0.5, 0.5])
r4.addRotateHandle([0,1], [0.5, 0.5])
img4 = pg.ImageItem(arr)
@ -142,6 +142,12 @@ img4.setParentItem(r4)
v4.disableAutoRange('xy')
v4.autoRange()
# Provide a callback to remove the ROI (and its children) when
# "remove" is selected from the context menu.
def remove():
v4.removeItem(r4)
r4.sigRemoveRequested.connect(remove)

View File

@ -58,8 +58,9 @@ s1.sigClicked.connect(clicked)
## 2) Spots are transform-invariant, but not identical (top-right plot).
## In this case, drawing is as fast as 1), but there is more startup overhead
## and memory usage since each spot generates its own pre-rendered image.
## In this case, drawing is almsot as fast as 1), but there is more startup
## overhead and memory usage since each spot generates its own pre-rendered
## image.
s2 = pg.ScatterPlotItem(size=10, pen=pg.mkPen('w'), pxMode=True)
pos = np.random.normal(size=(2,n), scale=1e-5)

View File

@ -32,6 +32,7 @@ ui.setupUi(win)
win.show()
p = ui.plot
p.setRange(xRange=[-500, 500], yRange=[-500, 500])
data = np.random.normal(size=(50,500), scale=100)
sizeArray = (np.random.random(500) * 20.).astype(int)
@ -45,7 +46,9 @@ def update():
size = sizeArray
else:
size = ui.sizeSpin.value()
curve = pg.ScatterPlotItem(x=data[ptr%50], y=data[(ptr+1)%50], pen='w', brush='b', size=size, pxMode=ui.pixelModeCheck.isChecked())
curve = pg.ScatterPlotItem(x=data[ptr%50], y=data[(ptr+1)%50],
pen='w', brush='b', size=size,
pxMode=ui.pixelModeCheck.isChecked())
p.addItem(curve)
ptr += 1
now = time()

View File

@ -1,15 +1,12 @@
import initExample ## Add path to library (just for examples; you do not need this)
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import pyqtgraph.exporters
import numpy as np
plt = pg.plot(np.random.normal(size=100), title="Simplest possible plotting example")
plt.getAxis('bottom').setTicks([[(x*20, str(x*20)) for x in range(6)]])
## Start Qt event loop unless running in interactive mode or using pyside.
ex = pg.exporters.SVGExporter.SVGExporter(plt.plotItem.scene())
ex.export('/home/luke/tmp/test.svg')
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if sys.flags.interactive != 1 or not hasattr(QtCore, 'PYQT_VERSION'):
if sys.flags.interactive != 1 or not hasattr(pg.QtCore, 'PYQT_VERSION'):
pg.QtGui.QApplication.exec_()

View File

@ -13,7 +13,6 @@ import initExample ## Add path to library (just for examples; you do not need th
from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
import numpy as np
import pyqtgraph as pg
import scipy.ndimage as ndi
import pyqtgraph.ptime as ptime
if USE_PYSIDE:
@ -71,40 +70,67 @@ ui.rgbLevelsCheck.toggled.connect(updateScale)
cache = {}
def mkData():
global data, cache, ui
dtype = (ui.dtypeCombo.currentText(), ui.rgbCheck.isChecked())
if dtype not in cache:
if dtype[0] == 'uint8':
dt = np.uint8
loc = 128
scale = 64
mx = 255
elif dtype[0] == 'uint16':
dt = np.uint16
loc = 4096
scale = 1024
mx = 2**16
elif dtype[0] == 'float':
dt = np.float
loc = 1.0
scale = 0.1
if ui.rgbCheck.isChecked():
data = np.random.normal(size=(20,512,512,3), loc=loc, scale=scale)
data = ndi.gaussian_filter(data, (0, 6, 6, 0))
else:
data = np.random.normal(size=(20,512,512), loc=loc, scale=scale)
data = ndi.gaussian_filter(data, (0, 6, 6))
if dtype[0] != 'float':
data = np.clip(data, 0, mx)
data = data.astype(dt)
cache[dtype] = data
data = cache[dtype]
updateLUT()
with pg.BusyCursor():
global data, cache, ui
frames = ui.framesSpin.value()
width = ui.widthSpin.value()
height = ui.heightSpin.value()
dtype = (ui.dtypeCombo.currentText(), ui.rgbCheck.isChecked(), frames, width, height)
if dtype not in cache:
if dtype[0] == 'uint8':
dt = np.uint8
loc = 128
scale = 64
mx = 255
elif dtype[0] == 'uint16':
dt = np.uint16
loc = 4096
scale = 1024
mx = 2**16
elif dtype[0] == 'float':
dt = np.float
loc = 1.0
scale = 0.1
if ui.rgbCheck.isChecked():
data = np.random.normal(size=(frames,width,height,3), loc=loc, scale=scale)
data = pg.gaussianFilter(data, (0, 6, 6, 0))
else:
data = np.random.normal(size=(frames,width,height), loc=loc, scale=scale)
data = pg.gaussianFilter(data, (0, 6, 6))
if dtype[0] != 'float':
data = np.clip(data, 0, mx)
data = data.astype(dt)
cache = {dtype: data} # clear to save memory (but keep one to prevent unnecessary regeneration)
data = cache[dtype]
updateLUT()
updateSize()
def updateSize():
global ui
frames = ui.framesSpin.value()
width = ui.widthSpin.value()
height = ui.heightSpin.value()
dtype = np.dtype(str(ui.dtypeCombo.currentText()))
rgb = 3 if ui.rgbCheck.isChecked() else 1
ui.sizeLabel.setText('%d MB' % (frames * width * height * rgb * dtype.itemsize / 1e6))
mkData()
ui.dtypeCombo.currentIndexChanged.connect(mkData)
ui.rgbCheck.toggled.connect(mkData)
ui.widthSpin.editingFinished.connect(mkData)
ui.heightSpin.editingFinished.connect(mkData)
ui.framesSpin.editingFinished.connect(mkData)
ui.widthSpin.valueChanged.connect(updateSize)
ui.heightSpin.valueChanged.connect(updateSize)
ui.framesSpin.valueChanged.connect(updateSize)
ptr = 0
lastTime = ptime.time()
@ -115,6 +141,8 @@ def update():
useLut = LUT
else:
useLut = None
downsample = ui.downsampleCheck.isChecked()
if ui.scaleCheck.isChecked():
if ui.rgbLevelsCheck.isChecked():
@ -134,7 +162,7 @@ def update():
ui.rawGLImg.setImage(data[ptr%data.shape[0]], lut=useLut, levels=useScale)
ui.stack.setCurrentIndex(2)
else:
img.setImage(data[ptr%data.shape[0]], autoLevels=False, levels=useScale, lut=useLut)
img.setImage(data[ptr%data.shape[0]], autoLevels=False, levels=useScale, lut=useLut, autoDownsample=downsample)
ui.stack.setCurrentIndex(0)
#img.setImage(data[ptr%data.shape[0]], autoRange=False)

View File

@ -15,6 +15,20 @@
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="downsampleCheck">
<property name="text">
<string>Auto downsample</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="scaleCheck">
<property name="text">
<string>Scale Data</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
@ -78,14 +92,7 @@
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Data type</string>
</property>
</widget>
</item>
<item row="2" column="2">
<item row="3" column="2">
<widget class="QComboBox" name="dtypeCombo">
<item>
<property name="text">
@ -105,40 +112,20 @@
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="scaleCheck">
<widget class="QLabel" name="label">
<property name="text">
<string>Scale Data</string>
<string>Data type</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<widget class="QCheckBox" name="rgbLevelsCheck">
<property name="text">
<string>RGB</string>
</property>
</widget>
</item>
<item row="3" column="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="SpinBox" name="minSpin1"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;---&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="SpinBox" name="maxSpin1"/>
</item>
</layout>
</item>
<item row="4" column="2">
<item row="5" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="SpinBox" name="minSpin2">
@ -166,7 +153,27 @@
</item>
</layout>
</item>
<item row="5" column="2">
<item row="4" column="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="SpinBox" name="minSpin1"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;---&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="SpinBox" name="maxSpin1"/>
</item>
</layout>
</item>
<item row="6" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="SpinBox" name="minSpin3">
@ -194,21 +201,21 @@
</item>
</layout>
</item>
<item row="6" column="0">
<item row="7" column="0">
<widget class="QCheckBox" name="lutCheck">
<property name="text">
<string>Use Lookup Table</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="7" column="1">
<widget class="QCheckBox" name="alphaCheck">
<property name="text">
<string>alpha</string>
</property>
</widget>
</item>
<item row="6" column="2" colspan="2">
<item row="7" column="2" colspan="2">
<widget class="GradientWidget" name="gradient" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
@ -218,7 +225,7 @@
</property>
</widget>
</item>
<item row="2" column="3">
<item row="3" column="3">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -246,13 +253,67 @@
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QCheckBox" name="rgbCheck">
<property name="text">
<string>RGB</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Image size</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QSpinBox" name="framesSpin">
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="widthSpin">
<property name="buttonSymbols">
<enum>QAbstractSpinBox::PlusMinus</enum>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>512</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="heightSpin">
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>512</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="3">
<widget class="QLabel" name="sizeLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './VideoTemplate.ui'
# Form implementation generated from reading ui file './examples/VideoTemplate.ui'
#
# Created: Sat Nov 16 20:07:09 2013
# by: PyQt4 UI code generator 4.10
# Created: Mon Feb 17 20:39:30 2014
# by: PyQt4 UI code generator 4.10.3
#
# WARNING! All changes made in this file will be lost!
@ -31,6 +31,12 @@ class Ui_MainWindow(object):
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.downsampleCheck = QtGui.QCheckBox(self.centralwidget)
self.downsampleCheck.setObjectName(_fromUtf8("downsampleCheck"))
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
self.scaleCheck = QtGui.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName(_fromUtf8("scaleCheck"))
self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1)
self.gridLayout = QtGui.QGridLayout()
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.rawRadio = QtGui.QRadioButton(self.centralwidget)
@ -76,34 +82,18 @@ class Ui_MainWindow(object):
self.rawGLRadio.setObjectName(_fromUtf8("rawGLRadio"))
self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1)
self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4)
self.label = QtGui.QLabel(self.centralwidget)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1)
self.dtypeCombo = QtGui.QComboBox(self.centralwidget)
self.dtypeCombo.setObjectName(_fromUtf8("dtypeCombo"))
self.dtypeCombo.addItem(_fromUtf8(""))
self.dtypeCombo.addItem(_fromUtf8(""))
self.dtypeCombo.addItem(_fromUtf8(""))
self.gridLayout_2.addWidget(self.dtypeCombo, 2, 2, 1, 1)
self.scaleCheck = QtGui.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName(_fromUtf8("scaleCheck"))
self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1)
self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1)
self.label = QtGui.QLabel(self.centralwidget)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1)
self.rgbLevelsCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbLevelsCheck.setObjectName(_fromUtf8("rgbLevelsCheck"))
self.gridLayout_2.addWidget(self.rgbLevelsCheck, 3, 1, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.minSpin1 = SpinBox(self.centralwidget)
self.minSpin1.setObjectName(_fromUtf8("minSpin1"))
self.horizontalLayout.addWidget(self.minSpin1)
self.label_2 = QtGui.QLabel(self.centralwidget)
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.horizontalLayout.addWidget(self.label_2)
self.maxSpin1 = SpinBox(self.centralwidget)
self.maxSpin1.setObjectName(_fromUtf8("maxSpin1"))
self.horizontalLayout.addWidget(self.maxSpin1)
self.gridLayout_2.addLayout(self.horizontalLayout, 3, 2, 1, 1)
self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1)
self.horizontalLayout_2 = QtGui.QHBoxLayout()
self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2"))
self.minSpin2 = SpinBox(self.centralwidget)
@ -118,7 +108,20 @@ class Ui_MainWindow(object):
self.maxSpin2.setEnabled(False)
self.maxSpin2.setObjectName(_fromUtf8("maxSpin2"))
self.horizontalLayout_2.addWidget(self.maxSpin2)
self.gridLayout_2.addLayout(self.horizontalLayout_2, 4, 2, 1, 1)
self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.minSpin1 = SpinBox(self.centralwidget)
self.minSpin1.setObjectName(_fromUtf8("minSpin1"))
self.horizontalLayout.addWidget(self.minSpin1)
self.label_2 = QtGui.QLabel(self.centralwidget)
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.horizontalLayout.addWidget(self.label_2)
self.maxSpin1 = SpinBox(self.centralwidget)
self.maxSpin1.setObjectName(_fromUtf8("maxSpin1"))
self.horizontalLayout.addWidget(self.maxSpin1)
self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1)
self.horizontalLayout_3 = QtGui.QHBoxLayout()
self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3"))
self.minSpin3 = SpinBox(self.centralwidget)
@ -133,13 +136,13 @@ class Ui_MainWindow(object):
self.maxSpin3.setEnabled(False)
self.maxSpin3.setObjectName(_fromUtf8("maxSpin3"))
self.horizontalLayout_3.addWidget(self.maxSpin3)
self.gridLayout_2.addLayout(self.horizontalLayout_3, 5, 2, 1, 1)
self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1)
self.lutCheck = QtGui.QCheckBox(self.centralwidget)
self.lutCheck.setObjectName(_fromUtf8("lutCheck"))
self.gridLayout_2.addWidget(self.lutCheck, 6, 0, 1, 1)
self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1)
self.alphaCheck = QtGui.QCheckBox(self.centralwidget)
self.alphaCheck.setObjectName(_fromUtf8("alphaCheck"))
self.gridLayout_2.addWidget(self.alphaCheck, 6, 1, 1, 1)
self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1)
self.gradient = GradientWidget(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
@ -147,9 +150,9 @@ class Ui_MainWindow(object):
sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth())
self.gradient.setSizePolicy(sizePolicy)
self.gradient.setObjectName(_fromUtf8("gradient"))
self.gridLayout_2.addWidget(self.gradient, 6, 2, 1, 2)
self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2)
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout_2.addItem(spacerItem, 2, 3, 1, 1)
self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1)
self.fpsLabel = QtGui.QLabel(self.centralwidget)
font = QtGui.QFont()
font.setPointSize(12)
@ -159,7 +162,34 @@ class Ui_MainWindow(object):
self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4)
self.rgbCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbCheck.setObjectName(_fromUtf8("rgbCheck"))
self.gridLayout_2.addWidget(self.rgbCheck, 2, 1, 1, 1)
self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1)
self.label_5 = QtGui.QLabel(self.centralwidget)
self.label_5.setObjectName(_fromUtf8("label_5"))
self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1)
self.horizontalLayout_4 = QtGui.QHBoxLayout()
self.horizontalLayout_4.setObjectName(_fromUtf8("horizontalLayout_4"))
self.framesSpin = QtGui.QSpinBox(self.centralwidget)
self.framesSpin.setButtonSymbols(QtGui.QAbstractSpinBox.NoButtons)
self.framesSpin.setProperty("value", 10)
self.framesSpin.setObjectName(_fromUtf8("framesSpin"))
self.horizontalLayout_4.addWidget(self.framesSpin)
self.widthSpin = QtGui.QSpinBox(self.centralwidget)
self.widthSpin.setButtonSymbols(QtGui.QAbstractSpinBox.PlusMinus)
self.widthSpin.setMaximum(10000)
self.widthSpin.setProperty("value", 512)
self.widthSpin.setObjectName(_fromUtf8("widthSpin"))
self.horizontalLayout_4.addWidget(self.widthSpin)
self.heightSpin = QtGui.QSpinBox(self.centralwidget)
self.heightSpin.setButtonSymbols(QtGui.QAbstractSpinBox.NoButtons)
self.heightSpin.setMaximum(10000)
self.heightSpin.setProperty("value", 512)
self.heightSpin.setObjectName(_fromUtf8("heightSpin"))
self.horizontalLayout_4.addWidget(self.heightSpin)
self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2)
self.sizeLabel = QtGui.QLabel(self.centralwidget)
self.sizeLabel.setText(_fromUtf8(""))
self.sizeLabel.setObjectName(_fromUtf8("sizeLabel"))
self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
@ -168,22 +198,24 @@ class Ui_MainWindow(object):
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample", None))
self.scaleCheck.setText(_translate("MainWindow", "Scale Data", None))
self.rawRadio.setText(_translate("MainWindow", "RawImageWidget", None))
self.gfxRadio.setText(_translate("MainWindow", "GraphicsView + ImageItem", None))
self.rawGLRadio.setText(_translate("MainWindow", "RawGLImageWidget", None))
self.label.setText(_translate("MainWindow", "Data type", None))
self.dtypeCombo.setItemText(0, _translate("MainWindow", "uint8", None))
self.dtypeCombo.setItemText(1, _translate("MainWindow", "uint16", None))
self.dtypeCombo.setItemText(2, _translate("MainWindow", "float", None))
self.scaleCheck.setText(_translate("MainWindow", "Scale Data", None))
self.label.setText(_translate("MainWindow", "Data type", None))
self.rgbLevelsCheck.setText(_translate("MainWindow", "RGB", None))
self.label_2.setText(_translate("MainWindow", "<--->", None))
self.label_3.setText(_translate("MainWindow", "<--->", None))
self.label_2.setText(_translate("MainWindow", "<--->", None))
self.label_4.setText(_translate("MainWindow", "<--->", None))
self.lutCheck.setText(_translate("MainWindow", "Use Lookup Table", None))
self.alphaCheck.setText(_translate("MainWindow", "alpha", None))
self.fpsLabel.setText(_translate("MainWindow", "FPS", None))
self.rgbCheck.setText(_translate("MainWindow", "RGB", None))
self.label_5.setText(_translate("MainWindow", "Image size", None))
from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget, RawImageWidget
from pyqtgraph import GradientWidget, SpinBox, GraphicsView

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './VideoTemplate.ui'
# Form implementation generated from reading ui file './examples/VideoTemplate.ui'
#
# Created: Sat Nov 16 20:07:10 2013
# Created: Mon Feb 17 20:39:30 2014
# by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!
@ -17,6 +17,12 @@ class Ui_MainWindow(object):
self.centralwidget.setObjectName("centralwidget")
self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.downsampleCheck = QtGui.QCheckBox(self.centralwidget)
self.downsampleCheck.setObjectName("downsampleCheck")
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
self.scaleCheck = QtGui.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName("scaleCheck")
self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1)
self.gridLayout = QtGui.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.rawRadio = QtGui.QRadioButton(self.centralwidget)
@ -62,34 +68,18 @@ class Ui_MainWindow(object):
self.rawGLRadio.setObjectName("rawGLRadio")
self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1)
self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4)
self.label = QtGui.QLabel(self.centralwidget)
self.label.setObjectName("label")
self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1)
self.dtypeCombo = QtGui.QComboBox(self.centralwidget)
self.dtypeCombo.setObjectName("dtypeCombo")
self.dtypeCombo.addItem("")
self.dtypeCombo.addItem("")
self.dtypeCombo.addItem("")
self.gridLayout_2.addWidget(self.dtypeCombo, 2, 2, 1, 1)
self.scaleCheck = QtGui.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName("scaleCheck")
self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1)
self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1)
self.label = QtGui.QLabel(self.centralwidget)
self.label.setObjectName("label")
self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1)
self.rgbLevelsCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbLevelsCheck.setObjectName("rgbLevelsCheck")
self.gridLayout_2.addWidget(self.rgbLevelsCheck, 3, 1, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.minSpin1 = SpinBox(self.centralwidget)
self.minSpin1.setObjectName("minSpin1")
self.horizontalLayout.addWidget(self.minSpin1)
self.label_2 = QtGui.QLabel(self.centralwidget)
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
self.maxSpin1 = SpinBox(self.centralwidget)
self.maxSpin1.setObjectName("maxSpin1")
self.horizontalLayout.addWidget(self.maxSpin1)
self.gridLayout_2.addLayout(self.horizontalLayout, 3, 2, 1, 1)
self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1)
self.horizontalLayout_2 = QtGui.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.minSpin2 = SpinBox(self.centralwidget)
@ -104,7 +94,20 @@ class Ui_MainWindow(object):
self.maxSpin2.setEnabled(False)
self.maxSpin2.setObjectName("maxSpin2")
self.horizontalLayout_2.addWidget(self.maxSpin2)
self.gridLayout_2.addLayout(self.horizontalLayout_2, 4, 2, 1, 1)
self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.minSpin1 = SpinBox(self.centralwidget)
self.minSpin1.setObjectName("minSpin1")
self.horizontalLayout.addWidget(self.minSpin1)
self.label_2 = QtGui.QLabel(self.centralwidget)
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
self.maxSpin1 = SpinBox(self.centralwidget)
self.maxSpin1.setObjectName("maxSpin1")
self.horizontalLayout.addWidget(self.maxSpin1)
self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1)
self.horizontalLayout_3 = QtGui.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.minSpin3 = SpinBox(self.centralwidget)
@ -119,13 +122,13 @@ class Ui_MainWindow(object):
self.maxSpin3.setEnabled(False)
self.maxSpin3.setObjectName("maxSpin3")
self.horizontalLayout_3.addWidget(self.maxSpin3)
self.gridLayout_2.addLayout(self.horizontalLayout_3, 5, 2, 1, 1)
self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1)
self.lutCheck = QtGui.QCheckBox(self.centralwidget)
self.lutCheck.setObjectName("lutCheck")
self.gridLayout_2.addWidget(self.lutCheck, 6, 0, 1, 1)
self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1)
self.alphaCheck = QtGui.QCheckBox(self.centralwidget)
self.alphaCheck.setObjectName("alphaCheck")
self.gridLayout_2.addWidget(self.alphaCheck, 6, 1, 1, 1)
self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1)
self.gradient = GradientWidget(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
@ -133,9 +136,9 @@ class Ui_MainWindow(object):
sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth())
self.gradient.setSizePolicy(sizePolicy)
self.gradient.setObjectName("gradient")
self.gridLayout_2.addWidget(self.gradient, 6, 2, 1, 2)
self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2)
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout_2.addItem(spacerItem, 2, 3, 1, 1)
self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1)
self.fpsLabel = QtGui.QLabel(self.centralwidget)
font = QtGui.QFont()
font.setPointSize(12)
@ -145,7 +148,34 @@ class Ui_MainWindow(object):
self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4)
self.rgbCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbCheck.setObjectName("rgbCheck")
self.gridLayout_2.addWidget(self.rgbCheck, 2, 1, 1, 1)
self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1)
self.label_5 = QtGui.QLabel(self.centralwidget)
self.label_5.setObjectName("label_5")
self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1)
self.horizontalLayout_4 = QtGui.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.framesSpin = QtGui.QSpinBox(self.centralwidget)
self.framesSpin.setButtonSymbols(QtGui.QAbstractSpinBox.NoButtons)
self.framesSpin.setProperty("value", 10)
self.framesSpin.setObjectName("framesSpin")
self.horizontalLayout_4.addWidget(self.framesSpin)
self.widthSpin = QtGui.QSpinBox(self.centralwidget)
self.widthSpin.setButtonSymbols(QtGui.QAbstractSpinBox.PlusMinus)
self.widthSpin.setMaximum(10000)
self.widthSpin.setProperty("value", 512)
self.widthSpin.setObjectName("widthSpin")
self.horizontalLayout_4.addWidget(self.widthSpin)
self.heightSpin = QtGui.QSpinBox(self.centralwidget)
self.heightSpin.setButtonSymbols(QtGui.QAbstractSpinBox.NoButtons)
self.heightSpin.setMaximum(10000)
self.heightSpin.setProperty("value", 512)
self.heightSpin.setObjectName("heightSpin")
self.horizontalLayout_4.addWidget(self.heightSpin)
self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2)
self.sizeLabel = QtGui.QLabel(self.centralwidget)
self.sizeLabel.setText("")
self.sizeLabel.setObjectName("sizeLabel")
self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
@ -154,22 +184,24 @@ class Ui_MainWindow(object):
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
self.downsampleCheck.setText(QtGui.QApplication.translate("MainWindow", "Auto downsample", None, QtGui.QApplication.UnicodeUTF8))
self.scaleCheck.setText(QtGui.QApplication.translate("MainWindow", "Scale Data", None, QtGui.QApplication.UnicodeUTF8))
self.rawRadio.setText(QtGui.QApplication.translate("MainWindow", "RawImageWidget", None, QtGui.QApplication.UnicodeUTF8))
self.gfxRadio.setText(QtGui.QApplication.translate("MainWindow", "GraphicsView + ImageItem", None, QtGui.QApplication.UnicodeUTF8))
self.rawGLRadio.setText(QtGui.QApplication.translate("MainWindow", "RawGLImageWidget", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("MainWindow", "Data type", None, QtGui.QApplication.UnicodeUTF8))
self.dtypeCombo.setItemText(0, QtGui.QApplication.translate("MainWindow", "uint8", None, QtGui.QApplication.UnicodeUTF8))
self.dtypeCombo.setItemText(1, QtGui.QApplication.translate("MainWindow", "uint16", None, QtGui.QApplication.UnicodeUTF8))
self.dtypeCombo.setItemText(2, QtGui.QApplication.translate("MainWindow", "float", None, QtGui.QApplication.UnicodeUTF8))
self.scaleCheck.setText(QtGui.QApplication.translate("MainWindow", "Scale Data", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("MainWindow", "Data type", None, QtGui.QApplication.UnicodeUTF8))
self.rgbLevelsCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_4.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.lutCheck.setText(QtGui.QApplication.translate("MainWindow", "Use Lookup Table", None, QtGui.QApplication.UnicodeUTF8))
self.alphaCheck.setText(QtGui.QApplication.translate("MainWindow", "alpha", None, QtGui.QApplication.UnicodeUTF8))
self.fpsLabel.setText(QtGui.QApplication.translate("MainWindow", "FPS", None, QtGui.QApplication.UnicodeUTF8))
self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
self.label_5.setText(QtGui.QApplication.translate("MainWindow", "Image size", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget, RawImageWidget
from pyqtgraph import GradientWidget, SpinBox, GraphicsView

View File

@ -14,7 +14,6 @@ import initExample
## This example uses a ViewBox to create a PlotWidget-like interface
#from scipy import random
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg

View File

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
"""
ViewBox is the general-purpose graphical container that allows the user to
zoom / pan to inspect any area of a 2D coordinate system.
This example demonstrates many of the features ViewBox provides.
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
x = np.arange(1000, dtype=float)
y = np.random.normal(size=1000)
y += 5 * np.sin(x/100)
win = pg.GraphicsWindow()
win.setWindowTitle('pyqtgraph example: ____')
win.resize(1000, 800)
win.ci.setBorder((50, 50, 100))
sub1 = win.addLayout()
sub1.addLabel("<b>Standard mouse interaction:</b><br>left-drag to pan, right-drag to zoom.")
sub1.nextRow()
v1 = sub1.addViewBox()
l1 = pg.PlotDataItem(y)
v1.addItem(l1)
sub2 = win.addLayout()
sub2.addLabel("<b>One-button mouse interaction:</b><br>left-drag zoom to box, wheel to zoom out.")
sub2.nextRow()
v2 = sub2.addViewBox()
v2.setMouseMode(v2.RectMode)
l2 = pg.PlotDataItem(y)
v2.addItem(l2)
win.nextRow()
sub3 = win.addLayout()
sub3.addLabel("<b>Locked aspect ratio when zooming.</b>")
sub3.nextRow()
v3 = sub3.addViewBox()
v3.setAspectLocked(1.0)
l3 = pg.PlotDataItem(y)
v3.addItem(l3)
sub4 = win.addLayout()
sub4.addLabel("<b>View limits:</b><br>prevent panning or zooming past limits.")
sub4.nextRow()
v4 = sub4.addViewBox()
v4.setLimits(xMin=-100, xMax=1100,
minXRange=20, maxXRange=500,
yMin=-10, yMax=10,
minYRange=1, maxYRange=10)
l4 = pg.PlotDataItem(y)
v4.addItem(l4)
win.nextRow()
sub5 = win.addLayout()
sub5.addLabel("<b>Linked axes:</b> Data in this plot is always X-aligned to<br>the plot above.")
sub5.nextRow()
v5 = sub5.addViewBox()
v5.setXLink(v3)
l5 = pg.PlotDataItem(y)
v5.addItem(l5)
sub6 = win.addLayout()
sub6.addLabel("<b>Disable mouse:</b> Per-axis control over mouse input.<br>"
"<b>Auto-scale-visible:</b> Automatically fit *visible* data within view<br>"
"(try panning left-right).")
sub6.nextRow()
v6 = sub6.addViewBox()
v6.setMouseEnabled(x=True, y=False)
v6.enableAutoRange(x=False, y=True)
v6.setXRange(300, 450)
v6.setAutoVisible(x=False, y=True)
l6 = pg.PlotDataItem(y)
v6.addItem(l6)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

15
examples/ViewLimits.py Normal file
View File

@ -0,0 +1,15 @@
import initExample ## Add path to library (just for examples; you do not need this)
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import numpy as np
plt = pg.plot(np.random.normal(size=100), title="View limit example")
plt.centralWidget.vb.setLimits(xMin=-20, xMax=120, minXRange=5, maxXRange=100)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if sys.flags.interactive != 1 or not hasattr(QtCore, 'PYQT_VERSION'):
pg.QtGui.QApplication.exec_()

View File

@ -8,6 +8,7 @@ if __name__ == "__main__" and (__package__ is None or __package__==''):
from . import initExample
from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
import pyqtgraph as pg
if USE_PYSIDE:
from .exampleLoaderTemplate_pyside import Ui_Form
@ -25,17 +26,26 @@ examples = OrderedDict([
('Crosshair / Mouse interaction', 'crosshair.py'),
('Data Slicing', 'DataSlicing.py'),
('Plot Customization', 'customPlot.py'),
('Image Analysis', 'imageAnalysis.py'),
('Dock widgets', 'dockarea.py'),
('Console', 'ConsoleWidget.py'),
('Histograms', 'histogram.py'),
('Auto-range', 'PlotAutoRange.py'),
('Remote Plotting', 'RemoteSpeedTest.py'),
('Scrolling plots', 'scrollingPlots.py'),
('HDF5 big data', 'hdf5.py'),
('Demos', OrderedDict([
('Optics', 'optics_demos.py'),
('Special relativity', 'relativity_demo.py'),
('Verlet chain', 'verlet_chain_demo.py'),
])),
('GraphicsItems', OrderedDict([
('Scatter Plot', 'ScatterPlot.py'),
#('PlotItem', 'PlotItem.py'),
('IsocurveItem', 'isocurve.py'),
('GraphItem', 'GraphItem.py'),
('ErrorBarItem', 'ErrorBarItem.py'),
('FillBetweenItem', 'FillBetweenItem.py'),
('ImageItem - video', 'ImageItem.py'),
('ImageItem - draw', 'Draw.py'),
('Region-of-Interest', 'ROIExamples.py'),
@ -51,6 +61,7 @@ examples = OrderedDict([
('Video speed test', 'VideoSpeedTest.py'),
('Line Plot update', 'PlotSpeedTest.py'),
('Scatter Plot update', 'ScatterPlotSpeedTest.py'),
('Multiple plots', 'MultiPlotSpeedTest.py'),
])),
('3D Graphics', OrderedDict([
('Volumetric', 'GLVolumeItem.py'),
@ -281,6 +292,9 @@ except:
if __name__ == '__main__':
if '--test' in sys.argv[1:]:
# get rid of orphaned cache files first
pg.renamePyc(path)
files = buildFileList(examples)
if '--pyside' in sys.argv[1:]:
lib = 'PySide'

142
examples/contextMenu.py Normal file
View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
"""
Demonstrates adding a custom context menu to a GraphicsItem
and extending the context menu of a ViewBox.
PyQtGraph implements a system that allows each item in a scene to implement its
own context menu, and for the menus of its parent items to be automatically
displayed as well.
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
win = pg.GraphicsWindow()
win.setWindowTitle('pyqtgraph example: context menu')
view = win.addViewBox()
# add two new actions to the ViewBox context menu:
zoom1 = view.menu.addAction('Zoom to box 1')
zoom2 = view.menu.addAction('Zoom to box 2')
# define callbacks for these actions
def zoomTo1():
# note that box1 is defined below
view.autoRange(items=[box1])
zoom1.triggered.connect(zoomTo1)
def zoomTo2():
# note that box1 is defined below
view.autoRange(items=[box2])
zoom2.triggered.connect(zoomTo2)
class MenuBox(pg.GraphicsObject):
"""
This class draws a rectangular area. Right-clicking inside the area will
raise a custom context menu which also includes the context menus of
its parents.
"""
def __init__(self, name):
self.name = name
self.pen = pg.mkPen('r')
# menu creation is deferred because it is expensive and often
# the user will never see the menu anyway.
self.menu = None
# note that the use of super() is often avoided because Qt does not
# allow to inherit from multiple QObject subclasses.
pg.GraphicsObject.__init__(self)
# All graphics items must have paint() and boundingRect() defined.
def boundingRect(self):
return QtCore.QRectF(0, 0, 10, 10)
def paint(self, p, *args):
p.setPen(self.pen)
p.drawRect(self.boundingRect())
# On right-click, raise the context menu
def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton:
if self.raiseContextMenu(ev):
ev.accept()
def raiseContextMenu(self, ev):
menu = self.getContextMenus()
# Let the scene add on to the end of our context menu
# (this is optional)
menu = self.scene().addParentContextMenus(self, menu, ev)
pos = ev.screenPos()
menu.popup(QtCore.QPoint(pos.x(), pos.y()))
return True
# This method will be called when this item's _children_ want to raise
# a context menu that includes their parents' menus.
def getContextMenus(self, event=None):
if self.menu is None:
self.menu = QtGui.QMenu()
self.menu.setTitle(self.name+ " options..")
green = QtGui.QAction("Turn green", self.menu)
green.triggered.connect(self.setGreen)
self.menu.addAction(green)
self.menu.green = green
blue = QtGui.QAction("Turn blue", self.menu)
blue.triggered.connect(self.setBlue)
self.menu.addAction(blue)
self.menu.green = blue
alpha = QtGui.QWidgetAction(self.menu)
alphaSlider = QtGui.QSlider()
alphaSlider.setOrientation(QtCore.Qt.Horizontal)
alphaSlider.setMaximum(255)
alphaSlider.setValue(255)
alphaSlider.valueChanged.connect(self.setAlpha)
alpha.setDefaultWidget(alphaSlider)
self.menu.addAction(alpha)
self.menu.alpha = alpha
self.menu.alphaSlider = alphaSlider
return self.menu
# Define context menu callbacks
def setGreen(self):
self.pen = pg.mkPen('g')
# inform Qt that this item must be redrawn.
self.update()
def setBlue(self):
self.pen = pg.mkPen('b')
self.update()
def setAlpha(self, a):
self.setOpacity(a/255.)
# This box's context menu will include the ViewBox's menu
box1 = MenuBox("Menu Box #1")
view.addItem(box1)
# This box's context menu will include both the ViewBox's menu and box1's menu
box2 = MenuBox("Menu Box #2")
box2.setParentItem(box1)
box2.setPos(5, 5)
box2.scale(0.2, 0.2)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -7,7 +7,6 @@ the mouse.
import initExample ## Add path to library (just for examples; you do not need this)
import numpy as np
import scipy.ndimage as ndi
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.Point import Point
@ -33,8 +32,8 @@ p1.setAutoVisible(y=True)
#create numpy arrays
#make the numbers large to show that the xrange shows data from 10000 to all the way 0
data1 = 10000 + 15000 * ndi.gaussian_filter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
data2 = 15000 + 15000 * ndi.gaussian_filter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
data1 = 10000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
data2 = 15000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
p1.plot(data1, pen="r")
p1.plot(data2, pen="g")

View File

@ -0,0 +1,20 @@
import sys
from PyQt4 import QtGui
import pyqtgraph as pg
from pyqtgraph.graphicsItems import TextItem
# For packages that require scipy, these may be needed:
# from scipy.stats import futil
# from scipy.sparse.csgraph import _validation
from pyqtgraph import setConfigOption
pg.setConfigOption('background','w')
pg.setConfigOption('foreground','k')
app = QtGui.QApplication(sys.argv)
pw = pg.plot(x = [0, 1, 2, 4], y = [4, 5, 9, 6])
pw.showGrid(x=True,y=True)
text = pg.TextItem(html='<div style="text-align: center"><span style="color: #000000;"> %s</span></div>' % "here",anchor=(0.0, 0.0))
text.setPos(1.0, 5.0)
pw.addItem(text)
status = app.exec_()
sys.exit(status)

View File

@ -0,0 +1,36 @@
# Build with `python setup.py build_exe`
from cx_Freeze import setup, Executable
import shutil
from glob import glob
# Remove the build folder
shutil.rmtree("build", ignore_errors=True)
shutil.rmtree("dist", ignore_errors=True)
import sys
includes = ['PyQt4.QtCore', 'PyQt4.QtGui', 'sip', 'pyqtgraph.graphicsItems',
'numpy', 'atexit']
excludes = ['cvxopt','_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger',
'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl','tables',
'Tkconstants', 'Tkinter', 'zmq','PySide','pysideuic','scipy','matplotlib']
if sys.version[0] == '2':
# causes syntax error on py2
excludes.append('PyQt4.uic.port_v3')
base = None
if sys.platform == "win32":
base = "Win32GUI"
build_exe_options = {'excludes': excludes,
'includes':includes, 'include_msvcr':True,
'compressed':True, 'copy_dependent_files':True, 'create_shared_zip':True,
'include_in_shared_zip':True, 'optimize':2}
setup(name = "cx_freeze plot test",
version = "0.1",
description = "cx_freeze plot test",
options = {"build_exe": build_exe_options},
executables = [Executable("plotTest.py", base=base)])

View File

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
"""
Simple example of loading UI template created with Qt Designer.
This example uses uic.loadUiType to parse and load the ui at runtime. It is also
possible to pre-compile the .ui file using pyuic (see VideoSpeedTest and
ScatterPlotSpeedTest examples; these .ui files have been compiled with the
tools/rebuildUi.py script).
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import os
pg.mkQApp()
## Define main window class from template
path = os.path.dirname(os.path.abspath(__file__))
uiFile = os.path.join(path, 'designerExample.ui')
WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile)
class MainWindow(TemplateBaseClass):
def __init__(self):
TemplateBaseClass.__init__(self)
self.setWindowTitle('pyqtgraph example: Qt Designer')
# Create the main window
self.ui = WindowTemplate()
self.ui.setupUi(self)
self.ui.plotBtn.clicked.connect(self.plot)
self.show()
def plot(self):
self.ui.plot.plot(np.random.normal(size=100), clear=True)
win = MainWindow()
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="plotBtn">
<property name="text">
<string>Plot!</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="PlotWidget" name="plot"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -35,7 +35,7 @@ win.setWindowTitle('pyqtgraph example: dockarea')
## Note that size arguments are only a suggestion; docks will still have to
## fill the entire dock area and obey the limits of their internal widgets.
d1 = Dock("Dock1", size=(1, 1)) ## give this dock the minimum possible size
d2 = Dock("Dock2 - Console", size=(500,300))
d2 = Dock("Dock2 - Console", size=(500,300), closable=True)
d3 = Dock("Dock3", size=(500,400))
d4 = Dock("Dock4 (tabbed) - Plot", size=(500,200))
d5 = Dock("Dock5 - Image", size=(500,200))

155
examples/hdf5.py Normal file
View File

@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
"""
In this example we create a subclass of PlotCurveItem for displaying a very large
data set from an HDF5 file that does not fit in memory.
The basic approach is to override PlotCurveItem.viewRangeChanged such that it
reads only the portion of the HDF5 data that is necessary to display the visible
portion of the data. This is further downsampled to reduce the number of samples
being displayed.
A more clever implementation of this class would employ some kind of caching
to avoid re-reading the entire visible waveform at every update.
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import h5py
import sys, os
pg.mkQApp()
plt = pg.plot()
plt.setWindowTitle('pyqtgraph example: HDF5 big data')
plt.enableAutoRange(False, False)
plt.setXRange(0, 500)
class HDF5Plot(pg.PlotCurveItem):
def __init__(self, *args, **kwds):
self.hdf5 = None
self.limit = 10000 # maximum number of samples to be plotted
pg.PlotCurveItem.__init__(self, *args, **kwds)
def setHDF5(self, data):
self.hdf5 = data
self.updateHDF5Plot()
def viewRangeChanged(self):
self.updateHDF5Plot()
def updateHDF5Plot(self):
if self.hdf5 is None:
self.setData([])
return
vb = self.getViewBox()
if vb is None:
return # no ViewBox yet
# Determine what data range must be read from HDF5
xrange = vb.viewRange()[0]
start = max(0,int(xrange[0])-1)
stop = min(len(self.hdf5), int(xrange[1]+2))
# Decide by how much we should downsample
ds = int((stop-start) / self.limit) + 1
if ds == 1:
# Small enough to display with no intervention.
visible = self.hdf5[start:stop]
scale = 1
else:
# Here convert data into a down-sampled array suitable for visualizing.
# Must do this piecewise to limit memory usage.
samples = 1 + ((stop-start) // ds)
visible = np.zeros(samples*2, dtype=self.hdf5.dtype)
sourcePtr = start
targetPtr = 0
# read data in chunks of ~1M samples
chunkSize = (1000000//ds) * ds
while sourcePtr < stop-1:
chunk = self.hdf5[sourcePtr:min(stop,sourcePtr+chunkSize)]
sourcePtr += len(chunk)
# reshape chunk to be integral multiple of ds
chunk = chunk[:(len(chunk)//ds) * ds].reshape(len(chunk)//ds, ds)
# compute max and min
chunkMax = chunk.max(axis=1)
chunkMin = chunk.min(axis=1)
# interleave min and max into plot data to preserve envelope shape
visible[targetPtr:targetPtr+chunk.shape[0]*2:2] = chunkMin
visible[1+targetPtr:1+targetPtr+chunk.shape[0]*2:2] = chunkMax
targetPtr += chunk.shape[0]*2
visible = visible[:targetPtr]
scale = ds * 0.5
self.setData(visible) # update the plot
self.setPos(start, 0) # shift to match starting index
self.resetTransform()
self.scale(scale, 1) # scale to match downsampling
def createFile(finalSize=2000000000):
"""Create a large HDF5 data file for testing.
Data consists of 1M random samples tiled through the end of the array.
"""
chunk = np.random.normal(size=1000000).astype(np.float32)
f = h5py.File('test.hdf5', 'w')
f.create_dataset('data', data=chunk, chunks=True, maxshape=(None,))
data = f['data']
nChunks = finalSize // (chunk.size * chunk.itemsize)
with pg.ProgressDialog("Generating test.hdf5...", 0, nChunks) as dlg:
for i in range(nChunks):
newshape = [data.shape[0] + chunk.shape[0]]
data.resize(newshape)
data[-chunk.shape[0]:] = chunk
dlg += 1
if dlg.wasCanceled():
f.close()
os.remove('test.hdf5')
sys.exit()
dlg += 1
f.close()
if len(sys.argv) > 1:
fileName = sys.argv[1]
else:
fileName = 'test.hdf5'
if not os.path.isfile(fileName):
size, ok = QtGui.QInputDialog.getDouble(None, "Create HDF5 Dataset?", "This demo requires a large HDF5 array. To generate a file, enter the array size (in GB) and press OK.", 2.0)
if not ok:
sys.exit(0)
else:
createFile(int(size*1e9))
#raise Exception("No suitable HDF5 file found. Use createFile() to generate an example file.")
f = h5py.File(fileName, 'r')
curve = HDF5Plot()
curve.setHDF5(f['data'])
plt.addItem(curve)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -2,8 +2,6 @@
"""
In this example we draw two different kinds of histogram.
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
@ -22,11 +20,9 @@ vals = np.hstack([np.random.normal(size=500), np.random.normal(size=260, loc=4)]
## compute standard histogram
y,x = np.histogram(vals, bins=np.linspace(-3, 8, 40))
## Using stepMode=True causes the plot to draw two lines for each sample.
## notice that len(x) == len(y)+1
## We are required to use stepMode=True so that PlotCurveItem will interpret this data correctly.
curve = pg.PlotCurveItem(x, y, stepMode=True, fillLevel=0, brush=(0, 0, 255, 80))
plt1.addItem(curve)
plt1.plot(x, y, stepMode=True, fillLevel=0, brush=(0,0,255,150))
## Now draw all points as a nicely-spaced scatter plot
y = pg.pseudoScatter(vals, spacing=0.15)

98
examples/imageAnalysis.py Normal file
View File

@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
"""
Demonstrates common image analysis tools.
Many of the features demonstrated here are already provided by the ImageView
widget, but here we present a lower-level approach that provides finer control
over the user interface.
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
pg.mkQApp()
win = pg.GraphicsLayoutWidget()
win.setWindowTitle('pyqtgraph example: Image Analysis')
# A plot area (ViewBox + axes) for displaying the image
p1 = win.addPlot()
# Item for displaying image data
img = pg.ImageItem()
p1.addItem(img)
# Custom ROI for selecting an image region
roi = pg.ROI([-8, 14], [6, 5])
roi.addScaleHandle([0.5, 1], [0.5, 0.5])
roi.addScaleHandle([0, 0.5], [0.5, 0.5])
p1.addItem(roi)
roi.setZValue(10) # make sure ROI is drawn above image
# Isocurve drawing
iso = pg.IsocurveItem(level=0.8, pen='g')
iso.setParentItem(img)
iso.setZValue(5)
# Contrast/color control
hist = pg.HistogramLUTItem()
hist.setImageItem(img)
win.addItem(hist)
# Draggable line for setting isocurve level
isoLine = pg.InfiniteLine(angle=0, movable=True, pen='g')
hist.vb.addItem(isoLine)
hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier
isoLine.setValue(0.8)
isoLine.setZValue(1000) # bring iso line above contrast controls
# Another plot area for displaying ROI data
win.nextRow()
p2 = win.addPlot(colspan=2)
p2.setMaximumHeight(250)
win.resize(800, 800)
win.show()
# Generate image data
data = np.random.normal(size=(100, 200))
data[20:80, 20:80] += 2.
data = pg.gaussianFilter(data, (3, 3))
data += np.random.normal(size=(100, 200)) * 0.1
img.setImage(data)
hist.setLevels(data.min(), data.max())
# build isocurves from smoothed data
iso.setData(pg.gaussianFilter(data, (2, 2)))
# set position and scale of image
img.scale(0.2, 0.2)
img.translate(-50, 0)
# zoom to fit imageo
p1.autoRange()
# Callbacks for handling user interaction
def updatePlot():
global img, roi, data, p2
selected = roi.getArrayRegion(data, img)
p2.plot(selected.mean(axis=1), clear=True)
roi.sigRegionChanged.connect(updatePlot)
updatePlot()
def updateIsocurve():
global isoLine, iso
iso.setLevel(isoLine.value())
isoLine.sigDragged.connect(updateIsocurve)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -10,7 +10,6 @@ import initExample ## Add path to library (just for examples; you do not need th
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
import scipy.ndimage as ndi
app = QtGui.QApplication([])
@ -18,7 +17,7 @@ app = QtGui.QApplication([])
frames = 200
data = np.random.normal(size=(frames,30,30), loc=0, scale=100)
data = np.concatenate([data, data], axis=0)
data = ndi.gaussian_filter(data, (10, 10, 10))[frames/2:frames + frames/2]
data = pg.gaussianFilter(data, (10, 10, 10))[frames/2:frames + frames/2]
data[:, 15:16, 15:17] += 1
win = pg.GraphicsWindow()

View File

@ -0,0 +1 @@
from .pyoptic import *

582
examples/optics/pyoptic.py Normal file
View File

@ -0,0 +1,582 @@
# -*- coding: utf-8 -*-
from PyQt4 import QtGui, QtCore
import pyqtgraph as pg
#from pyqtgraph.canvas import Canvas, CanvasItem
import numpy as np
import csv, gzip, os
from pyqtgraph import Point
class GlassDB:
"""
Database of dispersion coefficients for Schott glasses
+ Corning 7980
"""
def __init__(self, fileName='schott_glasses.csv'):
path = os.path.dirname(__file__)
fh = gzip.open(os.path.join(path, 'schott_glasses.csv.gz'), 'rb')
r = csv.reader(map(str, fh.readlines()))
lines = [x for x in r]
self.data = {}
header = lines[0]
for l in lines[1:]:
info = {}
for i in range(1, len(l)):
info[header[i]] = l[i]
self.data[l[0]] = info
self.data['Corning7980'] = { ## Thorlabs UV fused silica--not in schott catalog.
'B1': 0.68374049400,
'B2': 0.42032361300,
'B3': 0.58502748000,
'C1': 0.00460352869,
'C2': 0.01339688560,
'C3': 64.49327320000,
'TAUI25/250': 0.95, ## transmission data is fabricated, but close.
'TAUI25/1400': 0.98,
}
for k in self.data:
self.data[k]['ior_cache'] = {}
def ior(self, glass, wl):
"""
Return the index of refraction for *glass* at wavelength *wl*.
The *glass* argument must be a key in self.data.
"""
info = self.data[glass]
cache = info['ior_cache']
if wl not in cache:
B = list(map(float, [info['B1'], info['B2'], info['B3']]))
C = list(map(float, [info['C1'], info['C2'], info['C3']]))
w2 = (wl/1000.)**2
n = np.sqrt(1.0 + (B[0]*w2 / (w2-C[0])) + (B[1]*w2 / (w2-C[1])) + (B[2]*w2 / (w2-C[2])))
cache[wl] = n
return cache[wl]
def transmissionCurve(self, glass):
data = self.data[glass]
keys = [int(x[7:]) for x in data.keys() if 'TAUI25' in x]
keys.sort()
curve = np.empty((2,len(keys)))
for i in range(len(keys)):
curve[0][i] = keys[i]
key = 'TAUI25/%d' % keys[i]
val = data[key]
if val == '':
val = 0
else:
val = float(val)
curve[1][i] = val
return curve
GLASSDB = GlassDB()
def wlPen(wl):
"""Return a pen representing the given wavelength"""
l1 = 400
l2 = 700
hue = np.clip(((l2-l1) - (wl-l1)) * 0.8 / (l2-l1), 0, 0.8)
val = 1.0
if wl > 700:
val = 1.0 * (((700-wl)/700.) + 1)
elif wl < 400:
val = wl * 1.0/400.
#print hue, val
color = pg.hsvColor(hue, 1.0, val)
pen = pg.mkPen(color)
return pen
class ParamObj:
# Just a helper for tracking parameters and responding to changes
def __init__(self):
self.__params = {}
def __setitem__(self, item, val):
self.setParam(item, val)
def setParam(self, param, val):
self.setParams(**{param:val})
def setParams(self, **params):
"""Set parameters for this optic. This is a good function to override for subclasses."""
self.__params.update(params)
self.paramStateChanged()
def paramStateChanged(self):
pass
def __getitem__(self, item):
return self.getParam(item)
def getParam(self, param):
return self.__params[param]
class Optic(pg.GraphicsObject, ParamObj):
sigStateChanged = QtCore.Signal()
def __init__(self, gitem, **params):
ParamObj.__init__(self)
pg.GraphicsObject.__init__(self) #, [0,0], [1,1])
self.gitem = gitem
self.surfaces = gitem.surfaces
gitem.setParentItem(self)
self.roi = pg.ROI([0,0], [1,1])
self.roi.addRotateHandle([1, 1], [0.5, 0.5])
self.roi.setParentItem(self)
defaults = {
'pos': Point(0,0),
'angle': 0,
}
defaults.update(params)
self._ior_cache = {}
self.roi.sigRegionChanged.connect(self.roiChanged)
self.setParams(**defaults)
def updateTransform(self):
self.resetTransform()
self.setPos(0, 0)
self.translate(Point(self['pos']))
self.rotate(self['angle'])
def setParam(self, param, val):
ParamObj.setParam(self, param, val)
def paramStateChanged(self):
"""Some parameters of the optic have changed."""
# Move graphics item
self.gitem.setPos(Point(self['pos']))
self.gitem.resetTransform()
self.gitem.rotate(self['angle'])
# Move ROI to match
try:
self.roi.sigRegionChanged.disconnect(self.roiChanged)
br = self.gitem.boundingRect()
o = self.gitem.mapToParent(br.topLeft())
self.roi.setAngle(self['angle'])
self.roi.setPos(o)
self.roi.setSize([br.width(), br.height()])
finally:
self.roi.sigRegionChanged.connect(self.roiChanged)
self.sigStateChanged.emit()
def roiChanged(self, *args):
pos = self.roi.pos()
# rotate gitem temporarily so we can decide where it will need to move
self.gitem.resetTransform()
self.gitem.rotate(self.roi.angle())
br = self.gitem.boundingRect()
o1 = self.gitem.mapToParent(br.topLeft())
self.setParams(angle=self.roi.angle(), pos=pos + (self.gitem.pos() - o1))
def boundingRect(self):
return QtCore.QRectF()
def paint(self, p, *args):
pass
def ior(self, wavelength):
return GLASSDB.ior(self['glass'], wavelength)
class Lens(Optic):
def __init__(self, **params):
defaults = {
'dia': 25.4, ## diameter of lens
'r1': 50., ## positive means convex, use 0 for planar
'r2': 0, ## negative means convex
'd': 4.0,
'glass': 'N-BK7',
'reflect': False,
}
defaults.update(params)
d = defaults.pop('d')
defaults['x1'] = -d/2.
defaults['x2'] = d/2.
gitem = CircularSolid(brush=(100, 100, 130, 100), **defaults)
Optic.__init__(self, gitem, **defaults)
def propagateRay(self, ray):
"""Refract, reflect, absorb, and/or scatter ray. This function may create and return new rays"""
"""
NOTE:: We can probably use this to compute refractions faster: (from GLSL 120 docs)
For the incident vector I and surface normal N, and the
ratio of indices of refraction eta, return the refraction
vector. The result is computed by
k = 1.0 - eta * eta * (1.0 - dot(N, I) * dot(N, I))
if (k < 0.0)
return genType(0.0)
else
return eta * I - (eta * dot(N, I) + sqrt(k)) * N
The input parameters for the incident vector I and the
surface normal N must already be normalized to get the
desired results. eta == ratio of IORs
For reflection:
For the incident vector I and surface orientation N,
returns the reflection direction:
I 2 dot(N, I) N
N must already be normalized in order to achieve the
desired result.
"""
iors = [self.ior(ray['wl']), 1.0]
for i in [0,1]:
surface = self.surfaces[i]
ior = iors[i]
p1, ai = surface.intersectRay(ray)
#print "surface intersection:", p1, ai*180/3.14159
#trans = self.sceneTransform().inverted()[0] * surface.sceneTransform()
#p1 = trans.map(p1)
if p1 is None:
ray.setEnd(None)
break
p1 = surface.mapToItem(ray, p1)
#print "adjusted position:", p1
#ior = self.ior(ray['wl'])
rd = ray['dir']
a1 = np.arctan2(rd[1], rd[0])
ar = a1 - ai + np.arcsin((np.sin(ai) * ray['ior'] / ior))
#print [x for x in [a1, ai, (np.sin(ai) * ray['ior'] / ior), ar]]
#print ai, np.sin(ai), ray['ior'], ior
ray.setEnd(p1)
dp = Point(np.cos(ar), np.sin(ar))
#p2 = p1+dp
#p1p = self.mapToScene(p1)
#p2p = self.mapToScene(p2)
#dpp = Point(p2p-p1p)
ray = Ray(parent=ray, ior=ior, dir=dp)
return [ray]
class Mirror(Optic):
def __init__(self, **params):
defaults = {
'r1': 0,
'r2': 0,
'd': 0.01,
}
defaults.update(params)
d = defaults.pop('d')
defaults['x1'] = -d/2.
defaults['x2'] = d/2.
gitem = CircularSolid(brush=(100,100,100,255), **defaults)
Optic.__init__(self, gitem, **defaults)
def propagateRay(self, ray):
"""Refract, reflect, absorb, and/or scatter ray. This function may create and return new rays"""
surface = self.surfaces[0]
p1, ai = surface.intersectRay(ray)
if p1 is not None:
p1 = surface.mapToItem(ray, p1)
rd = ray['dir']
a1 = np.arctan2(rd[1], rd[0])
ar = a1 + np.pi - 2*ai
ray.setEnd(p1)
dp = Point(np.cos(ar), np.sin(ar))
ray = Ray(parent=ray, dir=dp)
else:
ray.setEnd(None)
return [ray]
class CircularSolid(pg.GraphicsObject, ParamObj):
"""GraphicsObject with two circular or flat surfaces."""
def __init__(self, pen=None, brush=None, **opts):
"""
Arguments for each surface are:
x1,x2 - position of center of _physical surface_
r1,r2 - radius of curvature
d1,d2 - diameter of optic
"""
defaults = dict(x1=-2, r1=100, d1=25.4, x2=2, r2=100, d2=25.4)
defaults.update(opts)
ParamObj.__init__(self)
self.surfaces = [CircleSurface(defaults['r1'], defaults['d1']), CircleSurface(-defaults['r2'], defaults['d2'])]
pg.GraphicsObject.__init__(self)
for s in self.surfaces:
s.setParentItem(self)
if pen is None:
self.pen = pg.mkPen((220,220,255,200), width=1, cosmetic=True)
else:
self.pen = pg.mkPen(pen)
if brush is None:
self.brush = pg.mkBrush((230, 230, 255, 30))
else:
self.brush = pg.mkBrush(brush)
self.setParams(**defaults)
def paramStateChanged(self):
self.updateSurfaces()
def updateSurfaces(self):
self.surfaces[0].setParams(self['r1'], self['d1'])
self.surfaces[1].setParams(-self['r2'], self['d2'])
self.surfaces[0].setPos(self['x1'], 0)
self.surfaces[1].setPos(self['x2'], 0)
self.path = QtGui.QPainterPath()
self.path.connectPath(self.surfaces[0].path.translated(self.surfaces[0].pos()))
self.path.connectPath(self.surfaces[1].path.translated(self.surfaces[1].pos()).toReversed())
self.path.closeSubpath()
def boundingRect(self):
return self.path.boundingRect()
def shape(self):
return self.path
def paint(self, p, *args):
p.setRenderHints(p.renderHints() | p.Antialiasing)
p.setPen(self.pen)
p.fillPath(self.path, self.brush)
p.drawPath(self.path)
class CircleSurface(pg.GraphicsObject):
def __init__(self, radius=None, diameter=None):
"""center of physical surface is at 0,0
radius is the radius of the surface. If radius is None, the surface is flat.
diameter is of the optic's edge."""
pg.GraphicsObject.__init__(self)
self.r = radius
self.d = diameter
self.mkPath()
def setParams(self, r, d):
self.r = r
self.d = d
self.mkPath()
def mkPath(self):
self.prepareGeometryChange()
r = self.r
d = self.d
h2 = d/2.
self.path = QtGui.QPainterPath()
if r == 0: ## flat surface
self.path.moveTo(0, h2)
self.path.lineTo(0, -h2)
else:
## half-height of surface can't be larger than radius
h2 = min(h2, abs(r))
#dx = abs(r) - (abs(r)**2 - abs(h2)**2)**0.5
#p.moveTo(-d*w/2.+ d*dx, d*h2)
arc = QtCore.QRectF(0, -r, r*2, r*2)
#self.surfaces.append((arc.center(), r, h2))
a1 = np.arcsin(h2/r) * 180. / np.pi
a2 = -2*a1
a1 += 180.
self.path.arcMoveTo(arc, a1)
self.path.arcTo(arc, a1, a2)
#if d == -1:
#p1 = QtGui.QPainterPath()
#p1.addRect(arc)
#self.paths.append(p1)
self.h2 = h2
def boundingRect(self):
return self.path.boundingRect()
def paint(self, p, *args):
return ## usually we let the optic draw.
#p.setPen(pg.mkPen('r'))
#p.drawPath(self.path)
def intersectRay(self, ray):
## return the point of intersection and the angle of incidence
#print "intersect ray"
h = self.h2
r = self.r
p, dir = ray.currentState(relativeTo=self) # position and angle of ray in local coords.
#print " ray: ", p, dir
p = p - Point(r, 0) ## move position so center of circle is at 0,0
#print " adj: ", p, r
if r == 0:
#print " flat"
if dir[0] == 0:
y = 0
else:
y = p[1] - p[0] * dir[1]/dir[0]
if abs(y) > h:
return None, None
else:
return (Point(0, y), np.arctan2(dir[1], dir[0]))
else:
#print " curve"
## find intersection of circle and line (quadratic formula)
dx = dir[0]
dy = dir[1]
dr = (dx**2 + dy**2) ** 0.5
D = p[0] * (p[1]+dy) - (p[0]+dx) * p[1]
idr2 = 1.0 / dr**2
disc = r**2 * dr**2 - D**2
if disc < 0:
return None, None
disc2 = disc**0.5
if dy < 0:
sgn = -1
else:
sgn = 1
br = self.path.boundingRect()
x1 = (D*dy + sgn*dx*disc2) * idr2
y1 = (-D*dx + abs(dy)*disc2) * idr2
if br.contains(x1+r, y1):
pt = Point(x1, y1)
else:
x2 = (D*dy - sgn*dx*disc2) * idr2
y2 = (-D*dx - abs(dy)*disc2) * idr2
pt = Point(x2, y2)
if not br.contains(x2+r, y2):
return None, None
raise Exception("No intersection!")
norm = np.arctan2(pt[1], pt[0])
if r < 0:
norm += np.pi
#print " norm:", norm*180/3.1415
dp = p - pt
#print " dp:", dp
ang = np.arctan2(dp[1], dp[0])
#print " ang:", ang*180/3.1415
#print " ai:", (ang-norm)*180/3.1415
#print " intersection:", pt
return pt + Point(r, 0), ang-norm
class Ray(pg.GraphicsObject, ParamObj):
"""Represents a single straight segment of a ray"""
sigStateChanged = QtCore.Signal()
def __init__(self, **params):
ParamObj.__init__(self)
defaults = {
'ior': 1.0,
'wl': 500,
'end': None,
'dir': Point(1,0),
}
self.params = {}
pg.GraphicsObject.__init__(self)
self.children = []
parent = params.get('parent', None)
if parent is not None:
defaults['start'] = parent['end']
defaults['wl'] = parent['wl']
self['ior'] = parent['ior']
self['dir'] = parent['dir']
parent.addChild(self)
defaults.update(params)
defaults['dir'] = Point(defaults['dir'])
self.setParams(**defaults)
self.mkPath()
def clearChildren(self):
for c in self.children:
c.clearChildren()
c.setParentItem(None)
self.scene().removeItem(c)
self.children = []
def paramStateChanged(self):
pass
def addChild(self, ch):
self.children.append(ch)
ch.setParentItem(self)
def currentState(self, relativeTo=None):
pos = self['start']
dir = self['dir']
if relativeTo is None:
return pos, dir
else:
trans = self.itemTransform(relativeTo)[0]
p1 = trans.map(pos)
p2 = trans.map(pos + dir)
return Point(p1), Point(p2-p1)
def setEnd(self, end):
self['end'] = end
self.mkPath()
def boundingRect(self):
return self.path.boundingRect()
def paint(self, p, *args):
#p.setPen(pg.mkPen((255,0,0, 150)))
p.setRenderHints(p.renderHints() | p.Antialiasing)
p.setCompositionMode(p.CompositionMode_Plus)
p.setPen(wlPen(self['wl']))
p.drawPath(self.path)
def mkPath(self):
self.prepareGeometryChange()
self.path = QtGui.QPainterPath()
self.path.moveTo(self['start'])
if self['end'] is not None:
self.path.lineTo(self['end'])
else:
self.path.lineTo(self['start']+500*self['dir'])
def trace(rays, optics):
if len(optics) < 1 or len(rays) < 1:
return
for r in rays:
r.clearChildren()
o = optics[0]
r2 = o.propagateRay(r)
trace(r2, optics[1:])
class Tracer(QtCore.QObject):
"""
Simple ray tracer.
Initialize with a list of rays and optics;
calling trace() will cause rays to be extended by propagating them through
each optic in sequence.
"""
def __init__(self, rays, optics):
QtCore.QObject.__init__(self)
self.optics = optics
self.rays = rays
for o in self.optics:
o.sigStateChanged.connect(self.trace)
self.trace()
def trace(self):
trace(self.rays, self.optics)

Binary file not shown.

170
examples/optics_demos.py Normal file
View File

@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
"""
Optical system design demo
"""
import initExample ## Add path to library (just for examples; you do not need this)
from optics import *
import pyqtgraph as pg
import numpy as np
from pyqtgraph import Point
app = pg.QtGui.QApplication([])
w = pg.GraphicsWindow(border=0.5)
w.resize(1000, 900)
w.show()
### Curved mirror demo
view = w.addViewBox()
view.setAspectLocked()
#grid = pg.GridItem()
#view.addItem(grid)
view.setRange(pg.QtCore.QRectF(-50, -30, 100, 100))
optics = []
rays = []
m1 = Mirror(r1=-100, pos=(5,0), d=5, angle=-15)
optics.append(m1)
m2 = Mirror(r1=-70, pos=(-40, 30), d=6, angle=180-15)
optics.append(m2)
allRays = []
for y in np.linspace(-10, 10, 21):
r = Ray(start=Point(-100, y))
view.addItem(r)
allRays.append(r)
for o in optics:
view.addItem(o)
t1 = Tracer(allRays, optics)
### Dispersion demo
optics = []
view = w.addViewBox()
view.setAspectLocked()
#grid = pg.GridItem()
#view.addItem(grid)
view.setRange(pg.QtCore.QRectF(-10, -50, 90, 60))
optics = []
rays = []
l1 = Lens(r1=20, r2=20, d=10, angle=8, glass='Corning7980')
optics.append(l1)
allRays = []
for wl in np.linspace(355,1040, 25):
for y in [10]:
r = Ray(start=Point(-100, y), wl=wl)
view.addItem(r)
allRays.append(r)
for o in optics:
view.addItem(o)
t2 = Tracer(allRays, optics)
### Scanning laser microscopy demo
w.nextRow()
view = w.addViewBox(colspan=2)
optics = []
#view.setAspectLocked()
view.setRange(QtCore.QRectF(200, -50, 500, 200))
## Scan mirrors
scanx = 250
scany = 20
m1 = Mirror(dia=4.2, d=0.001, pos=(scanx, 0), angle=315)
m2 = Mirror(dia=8.4, d=0.001, pos=(scanx, scany), angle=135)
## Scan lenses
l3 = Lens(r1=23.0, r2=0, d=5.8, pos=(scanx+50, scany), glass='Corning7980') ## 50mm UVFS (LA4148)
l4 = Lens(r1=0, r2=69.0, d=3.2, pos=(scanx+250, scany), glass='Corning7980') ## 150mm UVFS (LA4874)
## Objective
obj = Lens(r1=15, r2=15, d=10, dia=8, pos=(scanx+400, scany), glass='Corning7980')
IROptics = [m1, m2, l3, l4, obj]
## Scan mirrors
scanx = 250
scany = 30
m1a = Mirror(dia=4.2, d=0.001, pos=(scanx, 2*scany), angle=315)
m2a = Mirror(dia=8.4, d=0.001, pos=(scanx, 3*scany), angle=135)
## Scan lenses
l3a = Lens(r1=46, r2=0, d=3.8, pos=(scanx+50, 3*scany), glass='Corning7980') ## 100mm UVFS (LA4380)
l4a = Lens(r1=0, r2=46, d=3.8, pos=(scanx+250, 3*scany), glass='Corning7980') ## 100mm UVFS (LA4380)
## Objective
obja = Lens(r1=15, r2=15, d=10, dia=8, pos=(scanx+400, 3*scany), glass='Corning7980')
IROptics2 = [m1a, m2a, l3a, l4a, obja]
for o in set(IROptics+IROptics2):
view.addItem(o)
IRRays = []
IRRays2 = []
for dy in [-0.4, -0.15, 0, 0.15, 0.4]:
IRRays.append(Ray(start=Point(-50, dy), dir=(1, 0), wl=780))
IRRays2.append(Ray(start=Point(-50, dy+2*scany), dir=(1, 0), wl=780))
for r in set(IRRays+IRRays2):
view.addItem(r)
IRTracer = Tracer(IRRays, IROptics)
IRTracer2 = Tracer(IRRays2, IROptics2)
phase = 0.0
def update():
global phase
if phase % (8*np.pi) > 4*np.pi:
m1['angle'] = 315 + 1.5*np.sin(phase)
m1a['angle'] = 315 + 1.5*np.sin(phase)
else:
m2['angle'] = 135 + 1.5*np.sin(phase)
m2a['angle'] = 135 + 1.5*np.sin(phase)
phase += 0.2
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(40)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -123,6 +123,17 @@ def change(param, changes):
p.sigTreeStateChanged.connect(change)
def valueChanging(param, value):
print("Value changing (not finalized):", param, value)
# Too lazy for recursion:
for child in p.children():
child.sigValueChanging.connect(valueChanging)
for ch2 in child.children():
ch2.sigValueChanging.connect(valueChanging)
def save():
global state
state = p.saveState()

View File

@ -0,0 +1,20 @@
import sys
from PyQt4 import QtGui
import pyqtgraph as pg
from pyqtgraph.graphicsItems import TextItem
# For packages that require scipy, these may be needed:
# from scipy.stats import futil
# from scipy.sparse.csgraph import _validation
from pyqtgraph import setConfigOption
pg.setConfigOption('background','w')
pg.setConfigOption('foreground','k')
app = QtGui.QApplication(sys.argv)
pw = pg.plot(x = [0, 1, 2, 4], y = [4, 5, 9, 6])
pw.showGrid(x=True,y=True)
text = pg.TextItem(html='<div style="text-align: center"><span style="color: #000000;"> %s</span></div>' % "here",anchor=(0.0, 0.0))
text.setPos(1.0, 5.0)
pw.addItem(text)
status = app.exec_()
sys.exit(status)

36
examples/py2exe/setup.py Normal file
View File

@ -0,0 +1,36 @@
from distutils.core import setup
import shutil
from glob import glob
# Remove the build folder
shutil.rmtree("build", ignore_errors=True)
shutil.rmtree("dist", ignore_errors=True)
import py2exe
import sys
includes = ['PyQt4', 'PyQt4.QtGui', 'PyQt4.QtSvg', 'sip', 'pyqtgraph.graphicsItems']
excludes = ['_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger',
'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl',
'Tkconstants', 'Tkinter', 'zmq']
if sys.version[0] == '2':
# causes syntax error on py2
excludes.append('PyQt4.uic.port_v3')
packages = []
dll_excludes = ['libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll', 'tcl84.dll',
'tk84.dll', 'MSVCP90.dll']
icon_resources = []
bitmap_resources = []
other_resources = []
data_files = []
setup(
data_files=data_files,
console=['plotTest.py'] ,
options={"py2exe": {"excludes": excludes,
"includes": includes,
"dll_excludes": dll_excludes,
"optimize": 0,
"compressed": 2,
"bundle_files": 1}},
zipfile=None,
)

View File

@ -0,0 +1 @@
from relativity import *

View File

@ -0,0 +1,411 @@
name: 'params'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'group'
children:
Load Preset..:
name: 'Load Preset..'
limits: ['', 'Twin Paradox (grid)', 'Twin Paradox']
strictNaming: False
default: None
renamable: False
enabled: True
value: 'Twin Paradox (grid)'
visible: True
readonly: False
values: []
removable: False
type: 'list'
children:
Duration:
name: 'Duration'
limits: [0.1, None]
strictNaming: False
default: 10.0
renamable: False
enabled: True
readonly: False
value: 20.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Reference Frame:
name: 'Reference Frame'
limits: ['Grid00', 'Grid01', 'Grid02', 'Grid03', 'Grid04']
strictNaming: False
default: None
renamable: False
enabled: True
value: 'Grid02'
visible: True
readonly: False
values: []
removable: False
type: 'list'
children:
Animate:
name: 'Animate'
strictNaming: False
default: True
renamable: False
enabled: True
value: True
visible: True
readonly: False
removable: False
type: 'bool'
children:
Animation Speed:
name: 'Animation Speed'
limits: [0.0001, None]
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 1.0
visible: True
step: 0.1
removable: False
dec: True
type: 'float'
children:
Recalculate Worldlines:
name: 'Recalculate Worldlines'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'action'
children:
Save:
name: 'Save'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'action'
children:
Load:
name: 'Load'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'action'
children:
Objects:
name: 'Objects'
strictNaming: False
default: None
renamable: False
addText: 'Add New..'
enabled: True
value: None
visible: True
readonly: False
removable: False
type: None
children:
Grid:
name: 'Grid'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: 'Grid'
autoIncrementName: True
children:
Number of Clocks:
name: 'Number of Clocks'
limits: [1, None]
strictNaming: False
default: 5
renamable: False
enabled: True
value: 5
visible: True
readonly: False
removable: False
type: 'int'
children:
Spacing:
name: 'Spacing'
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 1.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
ClockTemplate:
name: 'ClockTemplate'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: 'Clock'
autoIncrementName: True
children:
Initial Position:
name: 'Initial Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: -2.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: None
renamable: False
addText: 'Add Command..'
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'AccelerationGroup'
children:
Command:
name: 'Command'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 0.0
renamable: False
enabled: True
value: 1.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.5
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command2:
name: 'Command2'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 2.0
renamable: False
enabled: True
value: 3.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command3:
name: 'Command3'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 4.0
renamable: False
enabled: True
value: 11.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: -0.5
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command4:
name: 'Command4'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 8.0
renamable: False
enabled: True
value: 13.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Rest Mass:
name: 'Rest Mass'
limits: [1e-09, None]
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 1.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Color:
name: 'Color'
strictNaming: False
default: (100, 100, 150)
renamable: False
enabled: True
value: (100, 100, 150, 255)
visible: True
readonly: False
removable: False
type: 'color'
children:
Size:
name: 'Size'
strictNaming: False
default: 0.5
renamable: False
enabled: True
value: 0.5
visible: True
readonly: False
removable: False
type: 'float'
children:
Vertical Position:
name: 'Vertical Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
addList: ['Clock', 'Grid']

View File

@ -0,0 +1,667 @@
name: 'params'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'group'
children:
Load Preset..:
name: 'Load Preset..'
limits: ['', 'Twin Paradox (grid)', 'Twin Paradox']
strictNaming: False
default: None
renamable: False
enabled: True
value: 'Twin Paradox (grid)'
visible: True
readonly: False
values: []
removable: False
type: 'list'
children:
Duration:
name: 'Duration'
limits: [0.1, None]
strictNaming: False
default: 10.0
renamable: False
enabled: True
readonly: False
value: 27.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Reference Frame:
name: 'Reference Frame'
limits: ['Grid00', 'Grid01', 'Grid02', 'Grid03', 'Grid04', 'Grid05', 'Grid06', 'Grid07', 'Grid08', 'Grid09', 'Grid10', 'Alice', 'Bob']
strictNaming: False
default: None
renamable: False
enabled: True
value: 'Alice'
visible: True
readonly: False
values: []
removable: False
type: 'list'
children:
Animate:
name: 'Animate'
strictNaming: False
default: True
renamable: False
enabled: True
value: True
visible: True
readonly: False
removable: False
type: 'bool'
children:
Animation Speed:
name: 'Animation Speed'
limits: [0.0001, None]
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 1.0
visible: True
step: 0.1
removable: False
dec: True
type: 'float'
children:
Recalculate Worldlines:
name: 'Recalculate Worldlines'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'action'
children:
Save:
name: 'Save'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'action'
children:
Load:
name: 'Load'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'action'
children:
Objects:
name: 'Objects'
strictNaming: False
default: None
renamable: False
addText: 'Add New..'
enabled: True
value: None
visible: True
readonly: False
removable: False
type: None
children:
Grid:
name: 'Grid'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: 'Grid'
autoIncrementName: True
children:
Number of Clocks:
name: 'Number of Clocks'
limits: [1, None]
strictNaming: False
default: 5
renamable: False
enabled: True
value: 11
visible: True
readonly: False
removable: False
type: 'int'
children:
Spacing:
name: 'Spacing'
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 2.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
ClockTemplate:
name: 'ClockTemplate'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: 'Clock'
autoIncrementName: True
children:
Initial Position:
name: 'Initial Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: -10.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: None
renamable: False
addText: 'Add Command..'
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'AccelerationGroup'
children:
Rest Mass:
name: 'Rest Mass'
limits: [1e-09, None]
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 1.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Color:
name: 'Color'
strictNaming: False
default: (100, 100, 150)
renamable: False
enabled: True
value: (77, 77, 77, 255)
visible: True
readonly: False
removable: False
type: 'color'
children:
Size:
name: 'Size'
strictNaming: False
default: 0.5
renamable: False
enabled: True
value: 1.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Vertical Position:
name: 'Vertical Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: -2.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Alice:
name: 'Alice'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: 'Clock'
autoIncrementName: True
children:
Initial Position:
name: 'Initial Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: None
renamable: False
addText: 'Add Command..'
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'AccelerationGroup'
children:
Command:
name: 'Command'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 0.0
renamable: False
enabled: True
value: 1.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.5
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command2:
name: 'Command2'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 2.0
renamable: False
enabled: True
value: 3.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command3:
name: 'Command3'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 3.0
renamable: False
enabled: True
value: 8.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: -0.5
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command4:
name: 'Command4'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 4.0
renamable: False
enabled: True
value: 12.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command5:
name: 'Command5'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 6.0
renamable: False
enabled: True
value: 17.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.5
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command6:
name: 'Command6'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 7.0
renamable: False
enabled: True
value: 19.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Rest Mass:
name: 'Rest Mass'
limits: [1e-09, None]
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 1.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Color:
name: 'Color'
strictNaming: False
default: (100, 100, 150)
renamable: False
enabled: True
value: (82, 123, 44, 255)
visible: True
readonly: False
removable: False
type: 'color'
children:
Size:
name: 'Size'
strictNaming: False
default: 0.5
renamable: False
enabled: True
value: 1.5
visible: True
readonly: False
removable: False
type: 'float'
children:
Vertical Position:
name: 'Vertical Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 3.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Bob:
name: 'Bob'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: 'Clock'
autoIncrementName: True
children:
Initial Position:
name: 'Initial Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: None
renamable: False
addText: 'Add Command..'
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'AccelerationGroup'
children:
Rest Mass:
name: 'Rest Mass'
limits: [1e-09, None]
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 1.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Color:
name: 'Color'
strictNaming: False
default: (100, 100, 150)
renamable: False
enabled: True
value: (69, 69, 126, 255)
visible: True
readonly: False
removable: False
type: 'color'
children:
Size:
name: 'Size'
strictNaming: False
default: 0.5
renamable: False
enabled: True
value: 1.5
visible: True
readonly: False
removable: False
type: 'float'
children:
Vertical Position:
name: 'Vertical Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
addList: ['Clock', 'Grid']

View File

@ -0,0 +1,538 @@
name: 'params'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'group'
children:
Load Preset..:
name: 'Load Preset..'
limits: ['', 'Twin Paradox', 'test']
strictNaming: False
default: None
renamable: False
enabled: True
value: 'Twin Paradox'
visible: True
readonly: False
values: []
removable: False
type: 'list'
children:
Duration:
name: 'Duration'
limits: [0.1, None]
strictNaming: False
default: 10.0
renamable: False
enabled: True
readonly: False
value: 27.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Reference Frame:
name: 'Reference Frame'
limits: ['Alice', 'Bob']
strictNaming: False
default: None
renamable: False
enabled: True
value: 'Alice'
visible: True
readonly: False
values: []
removable: False
type: 'list'
children:
Animate:
name: 'Animate'
strictNaming: False
default: True
renamable: False
enabled: True
value: True
visible: True
readonly: False
removable: False
type: 'bool'
children:
Animation Speed:
name: 'Animation Speed'
limits: [0.0001, None]
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 1.0
visible: True
step: 0.1
removable: False
dec: True
type: 'float'
children:
Recalculate Worldlines:
name: 'Recalculate Worldlines'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'action'
children:
Save:
name: 'Save'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'action'
children:
Load:
name: 'Load'
strictNaming: False
default: None
renamable: False
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'action'
children:
Objects:
name: 'Objects'
strictNaming: False
default: None
renamable: False
addText: 'Add New..'
enabled: True
value: None
visible: True
readonly: False
removable: False
type: None
children:
Alice:
name: 'Alice'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: 'Clock'
autoIncrementName: True
children:
Initial Position:
name: 'Initial Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: None
renamable: False
addText: 'Add Command..'
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'AccelerationGroup'
children:
Command:
name: 'Command'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 0.0
renamable: False
enabled: True
value: 1.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.5
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command2:
name: 'Command2'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 2.0
renamable: False
enabled: True
value: 3.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command3:
name: 'Command3'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 3.0
renamable: False
enabled: True
value: 8.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: -0.5
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command4:
name: 'Command4'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 4.0
renamable: False
enabled: True
value: 12.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command5:
name: 'Command5'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 6.0
renamable: False
enabled: True
value: 17.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.5
visible: True
step: 0.1
removable: False
type: 'float'
children:
Command6:
name: 'Command6'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: None
autoIncrementName: True
children:
Proper Time:
name: 'Proper Time'
strictNaming: False
default: 7.0
renamable: False
enabled: True
value: 19.0
visible: True
readonly: False
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Rest Mass:
name: 'Rest Mass'
limits: [1e-09, None]
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 1.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Color:
name: 'Color'
strictNaming: False
default: (100, 100, 150)
renamable: False
enabled: True
value: (82, 123, 44, 255)
visible: True
readonly: False
removable: False
type: 'color'
children:
Size:
name: 'Size'
strictNaming: False
default: 0.5
renamable: False
enabled: True
value: 0.5
visible: True
readonly: False
removable: False
type: 'float'
children:
Vertical Position:
name: 'Vertical Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.5
visible: True
step: 0.1
removable: False
type: 'float'
children:
Bob:
name: 'Bob'
strictNaming: False
default: None
renamable: True
enabled: True
value: None
visible: True
readonly: False
removable: True
type: 'Clock'
autoIncrementName: True
children:
Initial Position:
name: 'Initial Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Acceleration:
name: 'Acceleration'
strictNaming: False
default: None
renamable: False
addText: 'Add Command..'
enabled: True
value: None
visible: True
readonly: False
removable: False
type: 'AccelerationGroup'
children:
Rest Mass:
name: 'Rest Mass'
limits: [1e-09, None]
strictNaming: False
default: 1.0
renamable: False
enabled: True
readonly: False
value: 1.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
Color:
name: 'Color'
strictNaming: False
default: (100, 100, 150)
renamable: False
enabled: True
value: (69, 69, 126, 255)
visible: True
readonly: False
removable: False
type: 'color'
children:
Size:
name: 'Size'
strictNaming: False
default: 0.5
renamable: False
enabled: True
value: 0.5
visible: True
readonly: False
removable: False
type: 'float'
children:
Vertical Position:
name: 'Vertical Position'
strictNaming: False
default: 0.0
renamable: False
enabled: True
readonly: False
value: 0.0
visible: True
step: 0.1
removable: False
type: 'float'
children:
addList: ['Clock', 'Grid']

View File

@ -0,0 +1,773 @@
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.parametertree import Parameter, ParameterTree
from pyqtgraph.parametertree import types as pTypes
import pyqtgraph.configfile
import numpy as np
import user
import collections
import sys, os
class RelativityGUI(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.animations = []
self.animTimer = QtCore.QTimer()
self.animTimer.timeout.connect(self.stepAnimation)
self.animTime = 0
self.animDt = .016
self.lastAnimTime = 0
self.setupGUI()
self.objectGroup = ObjectGroupParam()
self.params = Parameter.create(name='params', type='group', children=[
dict(name='Load Preset..', type='list', values=[]),
#dict(name='Unit System', type='list', values=['', 'MKS']),
dict(name='Duration', type='float', value=10.0, step=0.1, limits=[0.1, None]),
dict(name='Reference Frame', type='list', values=[]),
dict(name='Animate', type='bool', value=True),
dict(name='Animation Speed', type='float', value=1.0, dec=True, step=0.1, limits=[0.0001, None]),
dict(name='Recalculate Worldlines', type='action'),
dict(name='Save', type='action'),
dict(name='Load', type='action'),
self.objectGroup,
])
self.tree.setParameters(self.params, showTop=False)
self.params.param('Recalculate Worldlines').sigActivated.connect(self.recalculate)
self.params.param('Save').sigActivated.connect(self.save)
self.params.param('Load').sigActivated.connect(self.load)
self.params.param('Load Preset..').sigValueChanged.connect(self.loadPreset)
self.params.sigTreeStateChanged.connect(self.treeChanged)
## read list of preset configs
presetDir = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'presets')
if os.path.exists(presetDir):
presets = [os.path.splitext(p)[0] for p in os.listdir(presetDir)]
self.params.param('Load Preset..').setLimits(['']+presets)
def setupGUI(self):
self.layout = QtGui.QVBoxLayout()
self.layout.setContentsMargins(0,0,0,0)
self.setLayout(self.layout)
self.splitter = QtGui.QSplitter()
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.layout.addWidget(self.splitter)
self.tree = ParameterTree(showHeader=False)
self.splitter.addWidget(self.tree)
self.splitter2 = QtGui.QSplitter()
self.splitter2.setOrientation(QtCore.Qt.Vertical)
self.splitter.addWidget(self.splitter2)
self.worldlinePlots = pg.GraphicsLayoutWidget()
self.splitter2.addWidget(self.worldlinePlots)
self.animationPlots = pg.GraphicsLayoutWidget()
self.splitter2.addWidget(self.animationPlots)
self.splitter2.setSizes([int(self.height()*0.8), int(self.height()*0.2)])
self.inertWorldlinePlot = self.worldlinePlots.addPlot()
self.refWorldlinePlot = self.worldlinePlots.addPlot()
self.inertAnimationPlot = self.animationPlots.addPlot()
self.inertAnimationPlot.setAspectLocked(1)
self.refAnimationPlot = self.animationPlots.addPlot()
self.refAnimationPlot.setAspectLocked(1)
self.inertAnimationPlot.setXLink(self.inertWorldlinePlot)
self.refAnimationPlot.setXLink(self.refWorldlinePlot)
def recalculate(self):
## build 2 sets of clocks
clocks1 = collections.OrderedDict()
clocks2 = collections.OrderedDict()
for cl in self.params.param('Objects'):
clocks1.update(cl.buildClocks())
clocks2.update(cl.buildClocks())
## Inertial simulation
dt = self.animDt * self.params['Animation Speed']
sim1 = Simulation(clocks1, ref=None, duration=self.params['Duration'], dt=dt)
sim1.run()
sim1.plot(self.inertWorldlinePlot)
self.inertWorldlinePlot.autoRange(padding=0.1)
## reference simulation
ref = self.params['Reference Frame']
dur = clocks1[ref].refData['pt'][-1] ## decide how long to run the reference simulation
sim2 = Simulation(clocks2, ref=clocks2[ref], duration=dur, dt=dt)
sim2.run()
sim2.plot(self.refWorldlinePlot)
self.refWorldlinePlot.autoRange(padding=0.1)
## create animations
self.refAnimationPlot.clear()
self.inertAnimationPlot.clear()
self.animTime = 0
self.animations = [Animation(sim1), Animation(sim2)]
self.inertAnimationPlot.addItem(self.animations[0])
self.refAnimationPlot.addItem(self.animations[1])
## create lines representing all that is visible to a particular reference
#self.inertSpaceline = Spaceline(sim1, ref)
#self.refSpaceline = Spaceline(sim2)
self.inertWorldlinePlot.addItem(self.animations[0].items[ref].spaceline())
self.refWorldlinePlot.addItem(self.animations[1].items[ref].spaceline())
def setAnimation(self, a):
if a:
self.lastAnimTime = pg.ptime.time()
self.animTimer.start(self.animDt*1000)
else:
self.animTimer.stop()
def stepAnimation(self):
now = pg.ptime.time()
dt = (now-self.lastAnimTime) * self.params['Animation Speed']
self.lastAnimTime = now
self.animTime += dt
if self.animTime > self.params['Duration']:
self.animTime = 0
for a in self.animations:
a.restart()
for a in self.animations:
a.stepTo(self.animTime)
def treeChanged(self, *args):
clocks = []
for c in self.params.param('Objects'):
clocks.extend(c.clockNames())
#for param, change, data in args[1]:
#if change == 'childAdded':
self.params.param('Reference Frame').setLimits(clocks)
self.setAnimation(self.params['Animate'])
def save(self):
fn = str(pg.QtGui.QFileDialog.getSaveFileName(self, "Save State..", "untitled.cfg", "Config Files (*.cfg)"))
if fn == '':
return
state = self.params.saveState()
pg.configfile.writeConfigFile(state, fn)
def load(self):
fn = str(pg.QtGui.QFileDialog.getOpenFileName(self, "Save State..", "", "Config Files (*.cfg)"))
if fn == '':
return
state = pg.configfile.readConfigFile(fn)
self.loadState(state)
def loadPreset(self, param, preset):
if preset == '':
return
path = os.path.abspath(os.path.dirname(__file__))
fn = os.path.join(path, 'presets', preset+".cfg")
state = pg.configfile.readConfigFile(fn)
self.loadState(state)
def loadState(self, state):
if 'Load Preset..' in state['children']:
del state['children']['Load Preset..']['limits']
del state['children']['Load Preset..']['value']
self.params.param('Objects').clearChildren()
self.params.restoreState(state, removeChildren=False)
self.recalculate()
class ObjectGroupParam(pTypes.GroupParameter):
def __init__(self):
pTypes.GroupParameter.__init__(self, name="Objects", addText="Add New..", addList=['Clock', 'Grid'])
def addNew(self, typ):
if typ == 'Clock':
self.addChild(ClockParam())
elif typ == 'Grid':
self.addChild(GridParam())
class ClockParam(pTypes.GroupParameter):
def __init__(self, **kwds):
defs = dict(name="Clock", autoIncrementName=True, renamable=True, removable=True, children=[
dict(name='Initial Position', type='float', value=0.0, step=0.1),
#dict(name='V0', type='float', value=0.0, step=0.1),
AccelerationGroup(),
dict(name='Rest Mass', type='float', value=1.0, step=0.1, limits=[1e-9, None]),
dict(name='Color', type='color', value=(100,100,150)),
dict(name='Size', type='float', value=0.5),
dict(name='Vertical Position', type='float', value=0.0, step=0.1),
])
#defs.update(kwds)
pTypes.GroupParameter.__init__(self, **defs)
self.restoreState(kwds, removeChildren=False)
def buildClocks(self):
x0 = self['Initial Position']
y0 = self['Vertical Position']
color = self['Color']
m = self['Rest Mass']
size = self['Size']
prog = self.param('Acceleration').generate()
c = Clock(x0=x0, m0=m, y0=y0, color=color, prog=prog, size=size)
return {self.name(): c}
def clockNames(self):
return [self.name()]
pTypes.registerParameterType('Clock', ClockParam)
class GridParam(pTypes.GroupParameter):
def __init__(self, **kwds):
defs = dict(name="Grid", autoIncrementName=True, renamable=True, removable=True, children=[
dict(name='Number of Clocks', type='int', value=5, limits=[1, None]),
dict(name='Spacing', type='float', value=1.0, step=0.1),
ClockParam(name='ClockTemplate'),
])
#defs.update(kwds)
pTypes.GroupParameter.__init__(self, **defs)
self.restoreState(kwds, removeChildren=False)
def buildClocks(self):
clocks = {}
template = self.param('ClockTemplate')
spacing = self['Spacing']
for i in range(self['Number of Clocks']):
c = template.buildClocks().values()[0]
c.x0 += i * spacing
clocks[self.name() + '%02d' % i] = c
return clocks
def clockNames(self):
return [self.name() + '%02d' % i for i in range(self['Number of Clocks'])]
pTypes.registerParameterType('Grid', GridParam)
class AccelerationGroup(pTypes.GroupParameter):
def __init__(self, **kwds):
defs = dict(name="Acceleration", addText="Add Command..")
pTypes.GroupParameter.__init__(self, **defs)
self.restoreState(kwds, removeChildren=False)
def addNew(self):
nextTime = 0.0
if self.hasChildren():
nextTime = self.children()[-1]['Proper Time'] + 1
self.addChild(Parameter.create(name='Command', autoIncrementName=True, type=None, renamable=True, removable=True, children=[
dict(name='Proper Time', type='float', value=nextTime),
dict(name='Acceleration', type='float', value=0.0, step=0.1),
]))
def generate(self):
prog = []
for cmd in self:
prog.append((cmd['Proper Time'], cmd['Acceleration']))
return prog
pTypes.registerParameterType('AccelerationGroup', AccelerationGroup)
class Clock(object):
nClocks = 0
def __init__(self, x0=0.0, y0=0.0, m0=1.0, v0=0.0, t0=0.0, color=None, prog=None, size=0.5):
Clock.nClocks += 1
self.pen = pg.mkPen(color)
self.brush = pg.mkBrush(color)
self.y0 = y0
self.x0 = x0
self.v0 = v0
self.m0 = m0
self.t0 = t0
self.prog = prog
self.size = size
def init(self, nPts):
## Keep records of object from inertial frame as well as reference frame
self.inertData = np.empty(nPts, dtype=[('x', float), ('t', float), ('v', float), ('pt', float), ('m', float), ('f', float)])
self.refData = np.empty(nPts, dtype=[('x', float), ('t', float), ('v', float), ('pt', float), ('m', float), ('f', float)])
## Inertial frame variables
self.x = self.x0
self.v = self.v0
self.m = self.m0
self.t = 0.0 ## reference clock always starts at 0
self.pt = self.t0 ## proper time starts at t0
## reference frame variables
self.refx = None
self.refv = None
self.refm = None
self.reft = None
self.recordFrame(0)
def recordFrame(self, i):
f = self.force()
self.inertData[i] = (self.x, self.t, self.v, self.pt, self.m, f)
self.refData[i] = (self.refx, self.reft, self.refv, self.pt, self.refm, f)
def force(self, t=None):
if len(self.prog) == 0:
return 0.0
if t is None:
t = self.pt
ret = 0.0
for t1,f in self.prog:
if t >= t1:
ret = f
return ret
def acceleration(self, t=None):
return self.force(t) / self.m0
def accelLimits(self):
## return the proper time values which bound the current acceleration command
if len(self.prog) == 0:
return -np.inf, np.inf
t = self.pt
ind = -1
for i, v in enumerate(self.prog):
t1,f = v
if t >= t1:
ind = i
if ind == -1:
return -np.inf, self.prog[0][0]
elif ind == len(self.prog)-1:
return self.prog[-1][0], np.inf
else:
return self.prog[ind][0], self.prog[ind+1][0]
def getCurve(self, ref=True):
if ref is False:
data = self.inertData
else:
data = self.refData[1:]
x = data['x']
y = data['t']
curve = pg.PlotCurveItem(x=x, y=y, pen=self.pen)
#x = self.data['x'] - ref.data['x']
#y = self.data['t']
step = 1.0
#mod = self.data['pt'] % step
#inds = np.argwhere(abs(mod[1:] - mod[:-1]) > step*0.9)
inds = [0]
pt = data['pt']
for i in range(1,len(pt)):
diff = pt[i] - pt[inds[-1]]
if abs(diff) >= step:
inds.append(i)
inds = np.array(inds)
#t = self.data['t'][inds]
#x = self.data['x'][inds]
pts = []
for i in inds:
x = data['x'][i]
y = data['t'][i]
if i+1 < len(data):
dpt = data['pt'][i+1]-data['pt'][i]
dt = data['t'][i+1]-data['t'][i]
else:
dpt = 1
if dpt > 0:
c = pg.mkBrush((0,0,0))
else:
c = pg.mkBrush((200,200,200))
pts.append({'pos': (x, y), 'brush': c})
points = pg.ScatterPlotItem(pts, pen=self.pen, size=7)
return curve, points
class Simulation:
def __init__(self, clocks, ref, duration, dt):
self.clocks = clocks
self.ref = ref
self.duration = duration
self.dt = dt
@staticmethod
def hypTStep(dt, v0, x0, tau0, g):
## Hyperbolic step.
## If an object has proper acceleration g and starts at position x0 with speed v0 and proper time tau0
## as seen from an inertial frame, then return the new v, x, tau after time dt has elapsed.
if g == 0:
return v0, x0 + v0*dt, tau0 + dt * (1. - v0**2)**0.5
v02 = v0**2
g2 = g**2
tinit = v0 / (g * (1 - v02)**0.5)
B = (1 + (g2 * (dt+tinit)**2))**0.5
v1 = g * (dt+tinit) / B
dtau = (np.arcsinh(g * (dt+tinit)) - np.arcsinh(g * tinit)) / g
tau1 = tau0 + dtau
x1 = x0 + (1.0 / g) * ( B - 1. / (1.-v02)**0.5 )
return v1, x1, tau1
@staticmethod
def tStep(dt, v0, x0, tau0, g):
## Linear step.
## Probably not as accurate as hyperbolic step, but certainly much faster.
gamma = (1. - v0**2)**-0.5
dtau = dt / gamma
return v0 + dtau * g, x0 + v0*dt, tau0 + dtau
@staticmethod
def tauStep(dtau, v0, x0, t0, g):
## linear step in proper time of clock.
## If an object has proper acceleration g and starts at position x0 with speed v0 at time t0
## as seen from an inertial frame, then return the new v, x, t after proper time dtau has elapsed.
## Compute how much t will change given a proper-time step of dtau
gamma = (1. - v0**2)**-0.5
if g == 0:
dt = dtau * gamma
else:
v0g = v0 * gamma
dt = (np.sinh(dtau * g + np.arcsinh(v0g)) - v0g) / g
#return v0 + dtau * g, x0 + v0*dt, t0 + dt
v1, x1, t1 = Simulation.hypTStep(dt, v0, x0, t0, g)
return v1, x1, t0+dt
@staticmethod
def hypIntersect(x0r, t0r, vr, x0, t0, v0, g):
## given a reference clock (seen from inertial frame) has rx, rt, and rv,
## and another clock starts at x0, t0, and v0, with acceleration g,
## compute the intersection time of the object clock's hyperbolic path with
## the reference plane.
## I'm sure we can simplify this...
if g == 0: ## no acceleration, path is linear (and hyperbola is undefined)
#(-t0r + t0 v0 vr - vr x0 + vr x0r)/(-1 + v0 vr)
t = (-t0r + t0 *v0 *vr - vr *x0 + vr *x0r)/(-1 + v0 *vr)
return t
gamma = (1.0-v0**2)**-0.5
sel = (1 if g>0 else 0) + (1 if vr<0 else 0)
sel = sel%2
if sel == 0:
#(1/(g^2 (-1 + vr^2)))(-g^2 t0r + g gamma vr + g^2 t0 vr^2 -
#g gamma v0 vr^2 - g^2 vr x0 +
#g^2 vr x0r + \[Sqrt](g^2 vr^2 (1 + gamma^2 (v0 - vr)^2 - vr^2 +
#2 g gamma (v0 - vr) (-t0 + t0r + vr (x0 - x0r)) +
#g^2 (t0 - t0r + vr (-x0 + x0r))^2)))
t = (1./(g**2 *(-1. + vr**2)))*(-g**2 *t0r + g *gamma *vr + g**2 *t0 *vr**2 - g *gamma *v0 *vr**2 - g**2 *vr *x0 + g**2 *vr *x0r + np.sqrt(g**2 *vr**2 *(1. + gamma**2 *(v0 - vr)**2 - vr**2 + 2 *g *gamma *(v0 - vr)* (-t0 + t0r + vr *(x0 - x0r)) + g**2 *(t0 - t0r + vr* (-x0 + x0r))**2)))
else:
#-(1/(g^2 (-1 + vr^2)))(g^2 t0r - g gamma vr - g^2 t0 vr^2 +
#g gamma v0 vr^2 + g^2 vr x0 -
#g^2 vr x0r + \[Sqrt](g^2 vr^2 (1 + gamma^2 (v0 - vr)^2 - vr^2 +
#2 g gamma (v0 - vr) (-t0 + t0r + vr (x0 - x0r)) +
#g^2 (t0 - t0r + vr (-x0 + x0r))^2)))
t = -(1./(g**2 *(-1. + vr**2)))*(g**2 *t0r - g *gamma* vr - g**2 *t0 *vr**2 + g *gamma *v0 *vr**2 + g**2* vr* x0 - g**2 *vr *x0r + np.sqrt(g**2* vr**2 *(1. + gamma**2 *(v0 - vr)**2 - vr**2 + 2 *g *gamma *(v0 - vr) *(-t0 + t0r + vr *(x0 - x0r)) + g**2 *(t0 - t0r + vr *(-x0 + x0r))**2)))
return t
def run(self):
nPts = int(self.duration/self.dt)+1
for cl in self.clocks.itervalues():
cl.init(nPts)
if self.ref is None:
self.runInertial(nPts)
else:
self.runReference(nPts)
def runInertial(self, nPts):
clocks = self.clocks
dt = self.dt
tVals = np.linspace(0, dt*(nPts-1), nPts)
for cl in self.clocks.itervalues():
for i in xrange(1,nPts):
nextT = tVals[i]
while True:
tau1, tau2 = cl.accelLimits()
x = cl.x
v = cl.v
tau = cl.pt
g = cl.acceleration()
v1, x1, tau1 = self.hypTStep(dt, v, x, tau, g)
if tau1 > tau2:
dtau = tau2-tau
cl.v, cl.x, cl.t = self.tauStep(dtau, v, x, cl.t, g)
cl.pt = tau2
else:
cl.v, cl.x, cl.pt = v1, x1, tau1
cl.t += dt
if cl.t >= nextT:
cl.refx = cl.x
cl.refv = cl.v
cl.reft = cl.t
cl.recordFrame(i)
break
def runReference(self, nPts):
clocks = self.clocks
ref = self.ref
dt = self.dt
dur = self.duration
## make sure reference clock is not present in the list of clocks--this will be handled separately.
clocks = clocks.copy()
for k,v in clocks.iteritems():
if v is ref:
del clocks[k]
break
ref.refx = 0
ref.refv = 0
ref.refm = ref.m0
## These are the set of proper times (in the reference frame) that will be simulated
ptVals = np.linspace(ref.pt, ref.pt + dt*(nPts-1), nPts)
for i in xrange(1,nPts):
## step reference clock ahead one time step in its proper time
nextPt = ptVals[i] ## this is where (when) we want to end up
while True:
tau1, tau2 = ref.accelLimits()
dtau = min(nextPt-ref.pt, tau2-ref.pt) ## do not step past the next command boundary
g = ref.acceleration()
v, x, t = Simulation.tauStep(dtau, ref.v, ref.x, ref.t, g)
ref.pt += dtau
ref.v = v
ref.x = x
ref.t = t
ref.reft = ref.pt
if ref.pt >= nextPt:
break
#else:
#print "Stepped to", tau2, "instead of", nextPt
ref.recordFrame(i)
## determine plane visible to reference clock
## this plane goes through the point ref.x, ref.t and has slope = ref.v
## update all other clocks
for cl in clocks.itervalues():
while True:
g = cl.acceleration()
tau1, tau2 = cl.accelLimits()
##Given current position / speed of clock, determine where it will intersect reference plane
#t1 = (ref.v * (cl.x - cl.v * cl.t) + (ref.t - ref.v * ref.x)) / (1. - cl.v)
t1 = Simulation.hypIntersect(ref.x, ref.t, ref.v, cl.x, cl.t, cl.v, g)
dt1 = t1 - cl.t
## advance clock by correct time step
v, x, tau = Simulation.hypTStep(dt1, cl.v, cl.x, cl.pt, g)
## check to see whether we have gone past an acceleration command boundary.
## if so, we must instead advance the clock to the boundary and start again
if tau < tau1:
dtau = tau1 - cl.pt
cl.v, cl.x, cl.t = Simulation.tauStep(dtau, cl.v, cl.x, cl.t, g)
cl.pt = tau1-0.000001
continue
if tau > tau2:
dtau = tau2 - cl.pt
cl.v, cl.x, cl.t = Simulation.tauStep(dtau, cl.v, cl.x, cl.t, g)
cl.pt = tau2
continue
## Otherwise, record the new values and exit the loop
cl.v = v
cl.x = x
cl.pt = tau
cl.t = t1
cl.m = None
break
## transform position into reference frame
x = cl.x - ref.x
t = cl.t - ref.t
gamma = (1.0 - ref.v**2) ** -0.5
vg = -ref.v * gamma
cl.refx = gamma * (x - ref.v * t)
cl.reft = ref.pt # + gamma * (t - ref.v * x) # this term belongs here, but it should always be equal to 0.
cl.refv = (cl.v - ref.v) / (1.0 - cl.v * ref.v)
cl.refm = None
cl.recordFrame(i)
t += dt
def plot(self, plot):
plot.clear()
for cl in self.clocks.itervalues():
c, p = cl.getCurve()
plot.addItem(c)
plot.addItem(p)
class Animation(pg.ItemGroup):
def __init__(self, sim):
pg.ItemGroup.__init__(self)
self.sim = sim
self.clocks = sim.clocks
self.items = {}
for name, cl in self.clocks.items():
item = ClockItem(cl)
self.addItem(item)
self.items[name] = item
#self.timer = timer
#self.timer.timeout.connect(self.step)
#def run(self, run):
#if not run:
#self.timer.stop()
#else:
#self.timer.start(self.dt)
def restart(self):
for cl in self.items.values():
cl.reset()
def stepTo(self, t):
for i in self.items.values():
i.stepTo(t)
class ClockItem(pg.ItemGroup):
def __init__(self, clock):
pg.ItemGroup.__init__(self)
self.size = clock.size
self.item = QtGui.QGraphicsEllipseItem(QtCore.QRectF(0, 0, self.size, self.size))
self.item.translate(-self.size*0.5, -self.size*0.5)
self.item.setPen(pg.mkPen(100,100,100))
self.item.setBrush(clock.brush)
self.hand = QtGui.QGraphicsLineItem(0, 0, 0, self.size*0.5)
self.hand.setPen(pg.mkPen('w'))
self.hand.setZValue(10)
self.flare = QtGui.QGraphicsPolygonItem(QtGui.QPolygonF([
QtCore.QPointF(0, -self.size*0.25),
QtCore.QPointF(0, self.size*0.25),
QtCore.QPointF(self.size*1.5, 0),
QtCore.QPointF(0, -self.size*0.25),
]))
self.flare.setPen(pg.mkPen('y'))
self.flare.setBrush(pg.mkBrush(255,150,0))
self.flare.setZValue(-10)
self.addItem(self.hand)
self.addItem(self.item)
self.addItem(self.flare)
self.clock = clock
self.i = 1
self._spaceline = None
def spaceline(self):
if self._spaceline is None:
self._spaceline = pg.InfiniteLine()
self._spaceline.setPen(self.clock.pen)
return self._spaceline
def stepTo(self, t):
data = self.clock.refData
while self.i < len(data)-1 and data['t'][self.i] < t:
self.i += 1
while self.i > 1 and data['t'][self.i-1] >= t:
self.i -= 1
self.setPos(data['x'][self.i], self.clock.y0)
t = data['pt'][self.i]
self.hand.setRotation(-0.25 * t * 360.)
self.resetTransform()
v = data['v'][self.i]
gam = (1.0 - v**2)**0.5
self.scale(gam, 1.0)
f = data['f'][self.i]
self.flare.resetTransform()
if f < 0:
self.flare.translate(self.size*0.4, 0)
else:
self.flare.translate(-self.size*0.4, 0)
self.flare.scale(-f * (0.5+np.random.random()*0.1), 1.0)
if self._spaceline is not None:
self._spaceline.setPos(pg.Point(data['x'][self.i], data['t'][self.i]))
self._spaceline.setAngle(data['v'][self.i] * 45.)
def reset(self):
self.i = 1
#class Spaceline(pg.InfiniteLine):
#def __init__(self, sim, frame):
#self.sim = sim
#self.frame = frame
#pg.InfiniteLine.__init__(self)
#self.setPen(sim.clocks[frame].pen)
#def stepTo(self, t):
#self.setAngle(0)
#pass
if __name__ == '__main__':
pg.mkQApp()
#import pyqtgraph.console
#cw = pyqtgraph.console.ConsoleWidget()
#cw.show()
#cw.catchNextException()
win = RelativityGUI()
win.setWindowTitle("Relativity!")
win.show()
win.resize(1100,700)
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
#win.params.param('Objects').restoreState(state, removeChildren=False)

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
"""
Special relativity simulation
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from relativity import RelativityGUI
pg.mkQApp()
win = RelativityGUI()
win.setWindowTitle("Relativity!")
win.resize(1100,700)
win.show()
win.loadPreset(None, 'Twin Paradox (grid)')
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(pg.QtCore, 'PYQT_VERSION'):
pg.QtGui.QApplication.instance().exec_()

118
examples/scrollingPlots.py Normal file
View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
"""
Various methods of drawing scrolling plots.
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
win = pg.GraphicsWindow()
win.setWindowTitle('pyqtgraph example: Scrolling Plots')
# 1) Simplest approach -- update data in the array such that plot appears to scroll
# In these examples, the array size is fixed.
p1 = win.addPlot()
p2 = win.addPlot()
data1 = np.random.normal(size=300)
curve1 = p1.plot(data1)
curve2 = p2.plot(data1)
ptr1 = 0
def update1():
global data1, curve1, ptr1
data1[:-1] = data1[1:] # shift data in the array one sample left
# (see also: np.roll)
data1[-1] = np.random.normal()
curve1.setData(data1)
ptr1 += 1
curve2.setData(data1)
curve2.setPos(ptr1, 0)
# 2) Allow data to accumulate. In these examples, the array doubles in length
# whenever it is full.
win.nextRow()
p3 = win.addPlot()
p4 = win.addPlot()
# Use automatic downsampling and clipping to reduce the drawing load
p3.setDownsampling(mode='peak')
p4.setDownsampling(mode='peak')
p3.setClipToView(True)
p4.setClipToView(True)
p3.setRange(xRange=[-100, 0])
p3.setLimits(xMax=0)
curve3 = p3.plot()
curve4 = p4.plot()
data3 = np.empty(100)
ptr3 = 0
def update2():
global data3, ptr3
data3[ptr3] = np.random.normal()
ptr3 += 1
if ptr3 >= data3.shape[0]:
tmp = data3
data3 = np.empty(data3.shape[0] * 2)
data3[:tmp.shape[0]] = tmp
curve3.setData(data3[:ptr3])
curve3.setPos(-ptr3, 0)
curve4.setData(data3[:ptr3])
# 3) Plot in chunks, adding one new plot curve for every 100 samples
chunkSize = 100
# Remove chunks after we have 10
maxChunks = 10
startTime = pg.ptime.time()
win.nextRow()
p5 = win.addPlot(colspan=2)
p5.setLabel('bottom', 'Time', 's')
p5.setXRange(-10, 0)
curves = []
data5 = np.empty((chunkSize+1,2))
ptr5 = 0
def update3():
global p5, data5, ptr5, curves
now = pg.ptime.time()
for c in curves:
c.setPos(-(now-startTime), 0)
i = ptr5 % chunkSize
if i == 0:
curve = p5.plot()
curves.append(curve)
last = data5[-1]
data5 = np.empty((chunkSize+1,2))
data5[0] = last
while len(curves) > maxChunks:
c = curves.pop(0)
p5.removeItem(c)
else:
curve = curves[-1]
data5[i+1,0] = now - startTime
data5[i+1,1] = np.random.normal()
curve.setData(x=data5[:i+2, 0], y=data5[:i+2, 1])
ptr5 += 1
# update all plots
def update():
update1()
update2()
update3()
timer = pg.QtCore.QTimer()
timer.timeout.connect(update)
timer.start(50)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -0,0 +1 @@
from .chain import ChainSim

View File

@ -0,0 +1,115 @@
import pyqtgraph as pg
import numpy as np
import time
from . import relax
class ChainSim(pg.QtCore.QObject):
stepped = pg.QtCore.Signal()
relaxed = pg.QtCore.Signal()
def __init__(self):
pg.QtCore.QObject.__init__(self)
self.damping = 0.1 # 0=full damping, 1=no damping
self.relaxPerStep = 10
self.maxTimeStep = 0.01
self.pos = None # (Npts, 2) float
self.mass = None # (Npts) float
self.fixed = None # (Npts) bool
self.links = None # (Nlinks, 2), uint
self.lengths = None # (Nlinks), float
self.push = None # (Nlinks), bool
self.pull = None # (Nlinks), bool
self.initialized = False
self.lasttime = None
self.lastpos = None
def init(self):
if self.initialized:
return
assert None not in [self.pos, self.mass, self.links, self.lengths]
if self.fixed is None:
self.fixed = np.zeros(self.pos.shape[0], dtype=bool)
if self.push is None:
self.push = np.ones(self.links.shape[0], dtype=bool)
if self.pull is None:
self.pull = np.ones(self.links.shape[0], dtype=bool)
# precompute relative masses across links
l1 = self.links[:,0]
l2 = self.links[:,1]
m1 = self.mass[l1]
m2 = self.mass[l2]
self.mrel1 = (m1 / (m1+m2))[:,np.newaxis]
self.mrel1[self.fixed[l1]] = 1 # fixed point constraint
self.mrel1[self.fixed[l2]] = 0
self.mrel2 = 1.0 - self.mrel1
for i in range(10):
self.relax(n=10)
self.initialized = True
def makeGraph(self):
#g1 = pg.GraphItem(pos=self.pos, adj=self.links[self.rope], pen=0.2, symbol=None)
brushes = np.where(self.fixed, pg.mkBrush(0,0,0,255), pg.mkBrush(50,50,200,255))
g2 = pg.GraphItem(pos=self.pos, adj=self.links[self.push & self.pull], pen=0.5, brush=brushes, symbol='o', size=(self.mass**0.33), pxMode=False)
p = pg.ItemGroup()
#p.addItem(g1)
p.addItem(g2)
return p
def update(self):
# approximate physics with verlet integration
now = pg.ptime.time()
if self.lasttime is None:
dt = 0
else:
dt = now - self.lasttime
self.lasttime = now
# limit amount of work to be done between frames
if not relax.COMPILED:
dt = self.maxTimeStep
if self.lastpos is None:
self.lastpos = self.pos
# remember fixed positions
fixedpos = self.pos[self.fixed]
while dt > 0:
dt1 = min(self.maxTimeStep, dt)
dt -= dt1
# compute motion since last timestep
dx = self.pos - self.lastpos
self.lastpos = self.pos
# update positions for gravity and inertia
acc = np.array([[0, -5]]) * dt1
inertia = dx * (self.damping**(dt1/self.mass))[:,np.newaxis] # with mass-dependent damping
self.pos = self.pos + inertia + acc
self.pos[self.fixed] = fixedpos # fixed point constraint
# correct for link constraints
self.relax(self.relaxPerStep)
self.stepped.emit()
def relax(self, n=50):
# speed up with C magic if possible
relax.relax(self.pos, self.links, self.mrel1, self.mrel2, self.lengths, self.push, self.pull, n)
self.relaxed.emit()

3
examples/verlet_chain/make Executable file
View File

@ -0,0 +1,3 @@
gcc -fPIC -c relax.c
gcc -shared -o maths.so relax.o

View File

@ -0,0 +1,48 @@
#include <math.h>
#include <stdio.h>
void relax(
double* pos,
long* links,
double* mrel1,
double* mrel2,
double* lengths,
char* push,
char* pull,
int nlinks,
int iters)
{
int i, l, p1, p2;
double x1, x2, y1, y2, dx, dy, dist, change;
// printf("%d, %d\n", iters, nlinks);
for( i=0; i<iters; i++ ) {
for( l=0; l<nlinks; l++ ) {
p1 = 2*links[l*2];
p2 = 2*links[l*2 + 1];
x1 = pos[p1];
y1 = pos[p1 + 1];
x2 = pos[p2];
y2 = pos[p2 + 1];
dx = x2 - x1;
dy = y2 - y1;
// dist = pow(dx*dx + dy*dy, 0.5);
dist = sqrt(dx*dx + dy*dy);
if( push[l]==0 && dist < lengths[l] )
dist = lengths[l];
if( pull[l]==0 && dist > lengths[l] )
dist = lengths[l];
change = (lengths[l]-dist) / dist;
dx *= change;
dy *= change;
pos[p1] -= mrel2[l] * dx;
pos[p1+1] -= mrel2[l] * dy;
pos[p2] += mrel1[l] * dx;
pos[p2+1] += mrel1[l] * dy;
}
}
}

View File

@ -0,0 +1,70 @@
import ctypes
import os
so = os.path.join(os.path.dirname(__file__), 'maths.so')
try:
lib = ctypes.CDLL(so)
COMPILED = True
except OSError:
COMPILED = False
if COMPILED:
lib.relax.argtypes = [
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_int,
ctypes.c_int,
]
def relax(pos, links, mrel1, mrel2, lengths, push, pull, iters):
nlinks = links.shape[0]
lib.relax(pos.ctypes, links.ctypes, mrel1.ctypes, mrel2.ctypes, lengths.ctypes, push.ctypes, pull.ctypes, nlinks, iters)
else:
def relax(pos, links, mrel1, mrel2, lengths, push, pull, iters):
lengths2 = lengths**2
for i in range(iters):
#p1 = links[:, 0]
#p2 = links[:, 1]
#x1 = pos[p1]
#x2 = pos[p2]
#dx = x2 - x1
#dist = (dx**2).sum(axis=1)**0.5
#mask = (npush & (dist < lengths)) | (npull & (dist > lengths))
##dist[mask] = lengths[mask]
#change = (lengths-dist) / dist
#change[mask] = 0
#dx *= change[:, np.newaxis]
#print dx
##pos[p1] -= mrel2 * dx
##pos[p2] += mrel1 * dx
#for j in range(links.shape[0]):
#pos[links[j,0]] -= mrel2[j] * dx[j]
#pos[links[j,1]] += mrel1[j] * dx[j]
for l in range(links.shape[0]):
p1, p2 = links[l];
x1 = pos[p1]
x2 = pos[p2]
dx = x2 - x1
dist2 = (dx**2).sum()
if (push[l] and dist2 < lengths2[l]) or (pull[l] and dist2 > lengths2[l]):
dist = dist2 ** 0.5
change = (lengths[l]-dist) / dist
dx *= change
pos[p1] -= mrel2[l] * dx
pos[p2] += mrel1[l] * dx

View File

@ -0,0 +1,126 @@
"""
Mechanical simulation of a chain using verlet integration.
Use the mouse to interact with one of the chains.
By default, this uses a slow, pure-python integrator to solve the chain link
positions. Unix users may compile a small math library to speed this up by
running the `examples/verlet_chain/make` script.
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import verlet_chain
sim = verlet_chain.ChainSim()
if verlet_chain.relax.COMPILED:
# Use more complex chain if compiled mad library is available.
chlen1 = 80
chlen2 = 60
linklen = 1
else:
chlen1 = 10
chlen2 = 8
linklen = 8
npts = chlen1 + chlen2
sim.mass = np.ones(npts)
sim.mass[int(chlen1 * 0.8)] = 100
sim.mass[chlen1-1] = 500
sim.mass[npts-1] = 200
sim.fixed = np.zeros(npts, dtype=bool)
sim.fixed[0] = True
sim.fixed[chlen1] = True
sim.pos = np.empty((npts, 2))
sim.pos[:chlen1, 0] = 0
sim.pos[chlen1:, 0] = 10
sim.pos[:chlen1, 1] = np.arange(chlen1) * linklen
sim.pos[chlen1:, 1] = np.arange(chlen2) * linklen
# to prevent miraculous balancing acts:
sim.pos += np.random.normal(size=sim.pos.shape, scale=1e-3)
links1 = [(j, i+j+1) for i in range(chlen1) for j in range(chlen1-i-1)]
links2 = [(j, i+j+1) for i in range(chlen2) for j in range(chlen2-i-1)]
sim.links = np.concatenate([np.array(links1), np.array(links2)+chlen1, np.array([[chlen1-1, npts-1]])])
p1 = sim.pos[sim.links[:,0]]
p2 = sim.pos[sim.links[:,1]]
dif = p2-p1
sim.lengths = (dif**2).sum(axis=1) ** 0.5
sim.lengths[(chlen1-1):len(links1)] *= 1.05 # let auxiliary links stretch a little
sim.lengths[(len(links1)+chlen2-1):] *= 1.05
sim.lengths[-1] = 7
push1 = np.ones(len(links1), dtype=bool)
push1[chlen1:] = False
push2 = np.ones(len(links2), dtype=bool)
push2[chlen2:] = False
sim.push = np.concatenate([push1, push2, np.array([True], dtype=bool)])
sim.pull = np.ones(sim.links.shape[0], dtype=bool)
sim.pull[-1] = False
# move chain initially just to generate some motion if the mouse is not over the window
mousepos = np.array([30, 20])
def display():
global view, sim
view.clear()
view.addItem(sim.makeGraph())
def relaxed():
global app
display()
app.processEvents()
def mouse(pos):
global mousepos
pos = view.mapSceneToView(pos)
mousepos = np.array([pos.x(), pos.y()])
def update():
global mousepos
#sim.pos[0] = sim.pos[0] * 0.9 + mousepos * 0.1
s = 0.9
sim.pos[0] = sim.pos[0] * s + mousepos * (1.0-s)
sim.update()
app = pg.mkQApp()
win = pg.GraphicsLayoutWidget()
win.show()
view = win.addViewBox()
view.setAspectLocked(True)
view.setXRange(-100, 100)
#view.autoRange()
view.scene().sigMouseMoved.connect(mouse)
#display()
#app.processEvents()
sim.relaxed.connect(relaxed)
sim.init()
sim.relaxed.disconnect(relaxed)
sim.stepped.connect(display)
timer = pg.QtCore.QTimer()
timer.timeout.connect(update)
timer.start(16)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -1,19 +1,11 @@
from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.python2_3 import sortList
#try:
#from PyQt4 import QtOpenGL
#HAVE_OPENGL = True
#except ImportError:
#HAVE_OPENGL = False
from ..Qt import QtCore, QtGui
from ..python2_3 import sortList
import weakref
from pyqtgraph.Point import Point
import pyqtgraph.functions as fn
import pyqtgraph.ptime as ptime
from ..Point import Point
from .. import functions as fn
from .. import ptime as ptime
from .mouseEvents import *
import pyqtgraph.debug as debug
from . import exportDialog
from .. import debug as debug
if hasattr(QtCore, 'PYQT_VERSION'):
try:
@ -92,23 +84,19 @@ class GraphicsScene(QtGui.QGraphicsScene):
cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj
def __init__(self, clickRadius=2, moveDistance=5):
QtGui.QGraphicsScene.__init__(self)
def __init__(self, clickRadius=2, moveDistance=5, parent=None):
QtGui.QGraphicsScene.__init__(self, parent)
self.setClickRadius(clickRadius)
self.setMoveDistance(moveDistance)
self.exportDirectory = None
self.clickEvents = []
self.dragButtons = []
self.prepItems = weakref.WeakKeyDictionary() ## set of items with prepareForPaintMethods
self.mouseGrabber = None
self.dragItem = None
self.lastDrag = None
self.hoverItems = weakref.WeakKeyDictionary()
self.lastHoverEvent = None
#self.searchRect = QtGui.QGraphicsRectItem()
#self.searchRect.setPen(fn.mkPen(200,0,0))
#self.addItem(self.searchRect)
self.contextMenu = [QtGui.QAction("Export...", self)]
self.contextMenu[0].triggered.connect(self.showExportDialog)
@ -147,8 +135,13 @@ class GraphicsScene(QtGui.QGraphicsScene):
def mousePressEvent(self, ev):
#print 'scenePress'
QtGui.QGraphicsScene.mousePressEvent(self, ev)
#print "mouseGrabberItem: ", self.mouseGrabberItem()
if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events
if self.lastHoverEvent is not None:
# If the mouse has moved since the last hover event, send a new one.
# This can happen if a context menu is open while the mouse is moving.
if ev.scenePos() != self.lastHoverEvent.scenePos():
self.sendHoverEvents(ev)
self.clickEvents.append(MouseClickEvent(ev))
## set focus on the topmost focusable item under this click
@ -157,10 +150,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
if i.isEnabled() and i.isVisible() and int(i.flags() & i.ItemIsFocusable) > 0:
i.setFocus(QtCore.Qt.MouseFocusReason)
break
#else:
#addr = sip.unwrapinstance(sip.cast(self.mouseGrabberItem(), QtGui.QGraphicsItem))
#item = GraphicsScene._addressCache.get(addr, self.mouseGrabberItem())
#print "click grabbed by:", item
def mouseMoveEvent(self, ev):
self.sigMouseMoved.emit(ev.scenePos())
@ -201,7 +190,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
def mouseReleaseEvent(self, ev):
#print 'sceneRelease'
if self.mouseGrabberItem() is None:
#print "sending click/drag event"
if ev.button() in self.dragButtons:
if self.sendDragEvent(ev, final=True):
#print "sent drag event"
@ -243,6 +231,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
prevItems = list(self.hoverItems.keys())
#print "hover prev items:", prevItems
#print "hover test items:", items
for item in items:
if hasattr(item, 'hoverEvent'):
event.currentItem = item
@ -260,6 +250,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
event.enter = False
event.exit = True
#print "hover exit items:", prevItems
for item in prevItems:
event.currentItem = item
try:
@ -269,9 +260,13 @@ class GraphicsScene(QtGui.QGraphicsScene):
finally:
del self.hoverItems[item]
if hasattr(ev, 'buttons') and int(ev.buttons()) == 0:
# Update last hover event unless:
# - mouse is dragging (move+buttons); in this case we want the dragged
# item to continue receiving events until the drag is over
# - event is not a mouse event (QEvent.Leave sometimes appears here)
if (ev.type() == ev.GraphicsSceneMousePress or
(ev.type() == ev.GraphicsSceneMouseMove and int(ev.buttons()) == 0)):
self.lastHoverEvent = event ## save this so we can ask about accepted events later.
def sendDragEvent(self, ev, init=False, final=False):
## Send a MouseDragEvent to the current dragItem or to
@ -335,7 +330,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None)
else:
acceptedItem = None
if acceptedItem is not None:
ev.currentItem = acceptedItem
try:
@ -357,22 +351,9 @@ class GraphicsScene(QtGui.QGraphicsScene):
if int(item.flags() & item.ItemIsFocusable) > 0:
item.setFocus(QtCore.Qt.MouseFocusReason)
break
#if not ev.isAccepted() and ev.button() is QtCore.Qt.RightButton:
#print "GraphicsScene emitting sigSceneContextMenu"
#self.sigMouseClicked.emit(ev)
#ev.accept()
self.sigMouseClicked.emit(ev)
return ev.isAccepted()
#def claimEvent(self, item, button, eventType):
#key = (button, eventType)
#if key in self.claimedEvents:
#return False
#self.claimedEvents[key] = item
#print "event", key, "claimed by", item
#return True
def items(self, *args):
#print 'args:', args
items = QtGui.QGraphicsScene.items(self, *args)
@ -445,10 +426,10 @@ class GraphicsScene(QtGui.QGraphicsScene):
for item in items:
if hoverable and not hasattr(item, 'hoverEvent'):
continue
shape = item.shape()
shape = item.shape() # Note: default shape() returns boundingRect()
if shape is None:
continue
if item.mapToScene(shape).contains(point):
if shape.contains(item.mapFromScene(point)):
items2.append(item)
## Sort by descending Z-order (don't trust scene.itms() to do this either)
@ -489,7 +470,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
#return v
#else:
#return widget
def addParentContextMenus(self, item, menu, event):
"""
Can be called by any item in the scene to expand its context menu to include parent context menus.
@ -519,30 +500,23 @@ class GraphicsScene(QtGui.QGraphicsScene):
event The original event that triggered the menu to appear.
============== ==================================================
"""
#items = self.itemsNearEvent(ev)
menusToAdd = []
while item is not self:
item = item.parentItem()
if item is None:
item = self
if not hasattr(item, "getContextMenus"):
continue
subMenus = item.getContextMenus(event)
if subMenus is None:
continue
if type(subMenus) is not list: ## so that some items (like FlowchartViewBox) can return multiple menus
subMenus = [subMenus]
for sm in subMenus:
menusToAdd.append(sm)
if len(menusToAdd) > 0:
subMenus = item.getContextMenus(event) or []
if isinstance(subMenus, list): ## so that some items (like FlowchartViewBox) can return multiple menus
menusToAdd.extend(subMenus)
else:
menusToAdd.append(subMenus)
if menusToAdd:
menu.addSeparator()
for m in menusToAdd:
if isinstance(m, QtGui.QMenu):
menu.addMenu(m)
@ -559,6 +533,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
def showExportDialog(self):
if self.exportDialog is None:
from . import exportDialog
self.exportDialog = exportDialog.ExportDialog(self)
self.exportDialog.show(self.contextMenuItem)

View File

@ -1,6 +1,8 @@
from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
import pyqtgraph as pg
import pyqtgraph.exporters as exporters
from ..Qt import QtCore, QtGui, USE_PYSIDE
from .. import exporters as exporters
from .. import functions as fn
from ..graphicsItems.ViewBox import ViewBox
from ..graphicsItems.PlotItem import PlotItem
if USE_PYSIDE:
from . import exportDialogTemplate_pyside as exportDialogTemplate
@ -18,7 +20,7 @@ class ExportDialog(QtGui.QWidget):
self.scene = scene
self.selectBox = QtGui.QGraphicsRectItem()
self.selectBox.setPen(pg.mkPen('y', width=3, style=QtCore.Qt.DashLine))
self.selectBox.setPen(fn.mkPen('y', width=3, style=QtCore.Qt.DashLine))
self.selectBox.hide()
self.scene.addItem(self.selectBox)
@ -35,10 +37,10 @@ class ExportDialog(QtGui.QWidget):
def show(self, item=None):
if item is not None:
## Select next exportable parent of the item originally clicked on
while not isinstance(item, pg.ViewBox) and not isinstance(item, pg.PlotItem) and item is not None:
while not isinstance(item, ViewBox) and not isinstance(item, PlotItem) and item is not None:
item = item.parentItem()
## if this is a ViewBox inside a PlotItem, select the parent instead.
if isinstance(item, pg.ViewBox) and isinstance(item.parentItem(), pg.PlotItem):
if isinstance(item, ViewBox) and isinstance(item.parentItem(), PlotItem):
item = item.parentItem()
self.updateItemList(select=item)
self.setVisible(True)
@ -64,9 +66,9 @@ class ExportDialog(QtGui.QWidget):
def updateItemTree(self, item, treeItem, select=None):
si = None
if isinstance(item, pg.ViewBox):
if isinstance(item, ViewBox):
si = QtGui.QTreeWidgetItem(['ViewBox'])
elif isinstance(item, pg.PlotItem):
elif isinstance(item, PlotItem):
si = QtGui.QTreeWidgetItem(['Plot'])
if si is not None:

View File

@ -92,7 +92,7 @@
<customwidget>
<class>ParameterTree</class>
<extends>QTreeWidget</extends>
<header>pyqtgraph.parametertree</header>
<header>..parametertree</header>
</customwidget>
</customwidgets>
<resources/>

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './GraphicsScene/exportDialogTemplate.ui'
# Form implementation generated from reading ui file './pyqtgraph/GraphicsScene/exportDialogTemplate.ui'
#
# Created: Wed Jan 30 21:02:28 2013
# by: PyQt4 UI code generator 4.9.3
# Created: Mon Dec 23 10:10:52 2013
# by: PyQt4 UI code generator 4.10
#
# WARNING! All changes made in this file will be lost!
@ -12,7 +12,16 @@ from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_Form(object):
def setupUi(self, Form):
@ -57,12 +66,12 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("Form", "Item to export:", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("Form", "Export format", None, QtGui.QApplication.UnicodeUTF8))
self.exportBtn.setText(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8))
self.closeBtn.setText(QtGui.QApplication.translate("Form", "Close", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8))
self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8))
Form.setWindowTitle(_translate("Form", "Export", None))
self.label.setText(_translate("Form", "Item to export:", None))
self.label_2.setText(_translate("Form", "Export format", None))
self.exportBtn.setText(_translate("Form", "Export", None))
self.closeBtn.setText(_translate("Form", "Close", None))
self.label_3.setText(_translate("Form", "Export options", None))
self.copyBtn.setText(_translate("Form", "Copy", None))
from pyqtgraph.parametertree import ParameterTree
from ..parametertree import ParameterTree

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './GraphicsScene/exportDialogTemplate.ui'
# Form implementation generated from reading ui file './pyqtgraph/GraphicsScene/exportDialogTemplate.ui'
#
# Created: Wed Jan 30 21:02:28 2013
# by: pyside-uic 0.2.13 running on PySide 1.1.1
# Created: Mon Dec 23 10:10:53 2013
# by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!
@ -60,4 +60,4 @@ class Ui_Form(object):
self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8))
self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph.parametertree import ParameterTree
from ..parametertree import ParameterTree

View File

@ -1,7 +1,7 @@
from pyqtgraph.Point import Point
from pyqtgraph.Qt import QtCore, QtGui
from ..Point import Point
from ..Qt import QtCore, QtGui
import weakref
import pyqtgraph.ptime as ptime
from .. import ptime as ptime
class MouseDragEvent(object):
"""
@ -131,8 +131,12 @@ class MouseDragEvent(object):
return self.finish
def __repr__(self):
lp = self.lastPos()
p = self.pos()
if self.currentItem is None:
lp = self._lastScenePos
p = self._scenePos
else:
lp = self.lastPos()
p = self.pos()
return "<MouseDragEvent (%g,%g)->(%g,%g) buttons=%d start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isStart()), str(self.isFinish()))
def modifiers(self):
@ -221,9 +225,15 @@ class MouseClickEvent(object):
return self._modifiers
def __repr__(self):
p = self.pos()
return "<MouseClickEvent (%g,%g) button=%d>" % (p.x(), p.y(), int(self.button()))
try:
if self.currentItem is None:
p = self._scenePos
else:
p = self.pos()
return "<MouseClickEvent (%g,%g) button=%d>" % (p.x(), p.y(), int(self.button()))
except:
return "<MouseClickEvent button=%d>" % (int(self.button()))
def time(self):
return self._time
@ -345,8 +355,15 @@ class HoverEvent(object):
return Point(self.currentItem.mapFromScene(self._lastScenePos))
def __repr__(self):
lp = self.lastPos()
p = self.pos()
if self.exit:
return "<HoverEvent exit=True>"
if self.currentItem is None:
lp = self._lastScenePos
p = self._scenePos
else:
lp = self.lastPos()
p = self.pos()
return "<HoverEvent (%g,%g)->(%g,%g) buttons=%d enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isEnter()), str(self.isExit()))
def modifiers(self):

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +0,0 @@
The file Image.py is a drop-in replacement for the same file in PIL 1.1.6.
It adds support for reading 16-bit TIFF files and converting then to numpy arrays.
(I submitted the changes to the PIL folks long ago, but to my knowledge the code
is not being used by them.)
To use, copy this file into
/usr/lib/python2.6/dist-packages/PIL/
or
C:\Python26\lib\site-packages\PIL\
..or wherever your system keeps its python modules.

View File

@ -1,6 +1,18 @@
## Do all Qt imports from here to allow easier PyQt / PySide compatibility
"""
This module exists to smooth out some of the differences between PySide and PyQt4:
* Automatically import either PyQt4 or PySide depending on availability
* Allow to import QtCore/QtGui pyqtgraph.Qt without specifying which Qt wrapper
you want to use.
* Declare QtCore.Signal, .Slot in PyQt4
* Declare loadUiType function for Pyside
"""
import sys, re
from .python2_3 import asUnicode
## Automatically determine whether to use PyQt or PySide.
## This is done by first checking to see whether one of the libraries
## is already imported. If not, then attempt to import PyQt4, then PySide.
@ -21,10 +33,75 @@ else:
if USE_PYSIDE:
from PySide import QtGui, QtCore, QtOpenGL, QtSvg
try:
from PySide import QtTest
except ImportError:
pass
import PySide
try:
from PySide import shiboken
isQObjectAlive = shiboken.isValid
except ImportError:
def isQObjectAlive(obj):
try:
if hasattr(obj, 'parent'):
obj.parent()
elif hasattr(obj, 'parentItem'):
obj.parentItem()
else:
raise Exception("Cannot determine whether Qt object %s is still alive." % obj)
except RuntimeError:
return False
else:
return True
VERSION_INFO = 'PySide ' + PySide.__version__
# Make a loadUiType function like PyQt has
# Credit:
# http://stackoverflow.com/questions/4442286/python-code-genration-with-pyside-uic/14195313#14195313
class StringIO(object):
"""Alternative to built-in StringIO needed to circumvent unicode/ascii issues"""
def __init__(self):
self.data = []
def write(self, data):
self.data.append(data)
def getvalue(self):
return ''.join(map(asUnicode, self.data)).encode('utf8')
def loadUiType(uiFile):
"""
Pyside "loadUiType" command like PyQt4 has one, so we have to convert the ui file to py code in-memory first and then execute it in a special frame to retrieve the form_class.
"""
import pysideuic
import xml.etree.ElementTree as xml
#from io import StringIO
parsed = xml.parse(uiFile)
widget_class = parsed.find('widget').get('class')
form_class = parsed.find('class').text
with open(uiFile, 'r') as f:
o = StringIO()
frame = {}
pysideuic.compileUi(f, o, indent=0)
pyc = compile(o.getvalue(), '<string>', 'exec')
exec(pyc, frame)
#Fetch the base_class and form class based on their type in the xml from designer
form_class = frame['Ui_%s'%form_class]
base_class = eval('QtGui.%s'%widget_class)
return form_class, base_class
else:
from PyQt4 import QtGui, QtCore
from PyQt4 import QtGui, QtCore, uic
try:
from PyQt4 import QtSvg
except ImportError:
@ -33,6 +110,16 @@ else:
from PyQt4 import QtOpenGL
except ImportError:
pass
try:
from PyQt4 import QtTest
except ImportError:
pass
import sip
def isQObjectAlive(obj):
return not sip.isdeleted(obj)
loadUiType = uic.loadUiType
QtCore.Signal = QtCore.pyqtSignal
VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
@ -43,6 +130,6 @@ versionReq = [4, 7]
QtVersion = PySide.QtCore.__version__ if USE_PYSIDE else QtCore.QT_VERSION_STR
m = re.match(r'(\d+)\.(\d+).*', QtVersion)
if m is not None and list(map(int, m.groups())) < versionReq:
print(map(int, m.groups()))
print(list(map(int, m.groups())))
raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion))

View File

@ -2,7 +2,6 @@
from .Qt import QtCore, QtGui
from .Point import Point
import numpy as np
import pyqtgraph as pg
class SRTTransform(QtGui.QTransform):
"""Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate
@ -77,7 +76,7 @@ class SRTTransform(QtGui.QTransform):
self.update()
def setFromMatrix4x4(self, m):
m = pg.SRTTransform3D(m)
m = SRTTransform3D(m)
angle, axis = m.getRotation()
if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1):
print("angle: %s axis: %s" % (str(angle), str(axis)))
@ -256,4 +255,4 @@ if __name__ == '__main__':
w1.sigRegionChanged.connect(update)
#w2.sigRegionChanged.connect(update2)
from .SRTTransform3D import SRTTransform3D

View File

@ -1,17 +1,16 @@
# -*- coding: utf-8 -*-
from .Qt import QtCore, QtGui
from .Vector import Vector
from .SRTTransform import SRTTransform
import pyqtgraph as pg
from .Transform3D import Transform3D
from .Vector import Vector
import numpy as np
import scipy.linalg
class SRTTransform3D(pg.Transform3D):
class SRTTransform3D(Transform3D):
"""4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate
This transform has no shear; angles are always preserved.
"""
def __init__(self, init=None):
pg.Transform3D.__init__(self)
Transform3D.__init__(self)
self.reset()
if init is None:
return
@ -44,14 +43,14 @@ class SRTTransform3D(pg.Transform3D):
def getScale(self):
return pg.Vector(self._state['scale'])
return Vector(self._state['scale'])
def getRotation(self):
"""Return (angle, axis) of rotation"""
return self._state['angle'], pg.Vector(self._state['axis'])
return self._state['angle'], Vector(self._state['axis'])
def getTranslation(self):
return pg.Vector(self._state['pos'])
return Vector(self._state['pos'])
def reset(self):
self._state = {
@ -118,11 +117,13 @@ class SRTTransform3D(pg.Transform3D):
The input matrix must be affine AND have no shear,
otherwise the conversion will most likely fail.
"""
import numpy.linalg
for i in range(4):
self.setRow(i, m.row(i))
m = self.matrix().reshape(4,4)
## translation is 4th column
self._state['pos'] = m[:3,3]
self._state['pos'] = m[:3,3]
## scale is vector-length of first three columns
scale = (m[:3,:3]**2).sum(axis=0)**0.5
## see whether there is an inversion
@ -132,9 +133,9 @@ class SRTTransform3D(pg.Transform3D):
self._state['scale'] = scale
## rotation axis is the eigenvector with eigenvalue=1
r = m[:3, :3] / scale[:, np.newaxis]
r = m[:3, :3] / scale[np.newaxis, :]
try:
evals, evecs = scipy.linalg.eig(r)
evals, evecs = numpy.linalg.eig(r)
except:
print("Rotation matrix: %s" % str(r))
print("Scale: %s" % str(scale))
@ -169,7 +170,7 @@ class SRTTransform3D(pg.Transform3D):
def as2D(self):
"""Return a QTransform representing the x,y portion of this transform (if possible)"""
return pg.SRTTransform(self)
return SRTTransform(self)
#def __div__(self, t):
#"""A / B == B^-1 * A"""
@ -202,11 +203,11 @@ class SRTTransform3D(pg.Transform3D):
self.update()
def update(self):
pg.Transform3D.setToIdentity(self)
Transform3D.setToIdentity(self)
## modifications to the transform are multiplied on the right, so we need to reverse order here.
pg.Transform3D.translate(self, *self._state['pos'])
pg.Transform3D.rotate(self, self._state['angle'], *self._state['axis'])
pg.Transform3D.scale(self, *self._state['scale'])
Transform3D.translate(self, *self._state['pos'])
Transform3D.rotate(self, self._state['angle'], *self._state['axis'])
Transform3D.scale(self, *self._state['scale'])
def __repr__(self):
return str(self.saveState())
@ -311,4 +312,4 @@ if __name__ == '__main__':
w1.sigRegionChanged.connect(update)
#w2.sigRegionChanged.connect(update2)
from .SRTTransform import SRTTransform

Some files were not shown because too many files have changed in this diff Show More