PyQtGraph release 0.9.9
This commit is contained in:
commit
8f638776b3
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -2,3 +2,8 @@ __pycache__
|
|||
build
|
||||
*.pyc
|
||||
*.swp
|
||||
MANIFEST
|
||||
deb_build
|
||||
dist
|
||||
.idea
|
||||
rtr.cvs
|
||||
|
|
230
.travis.yml
Normal file
230
.travis.yml
Normal 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
102
CHANGELOG
|
@ -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
51
CONTRIBUTING.txt
Normal 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.
|
|
@ -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
|
||||
|
||||
|
|
26
README.md
26
README.md
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'))
|
||||
|
|
8
doc/source/graphicsItems/errorbaritem.rst
Normal file
8
doc/source/graphicsItems/errorbaritem.rst
Normal file
|
@ -0,0 +1,8 @@
|
|||
ErrorBarItem
|
||||
============
|
||||
|
||||
.. autoclass:: pyqtgraph.ErrorBarItem
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.ErrorBarItem.__init__
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
|
@ -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::
|
||||
|
|
|
@ -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
|
||||
----------------------------
|
||||
|
|
6
doc/source/widgets/consolewidget.rst
Normal file
6
doc/source/widgets/consolewidget.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
ConsoleWidget
|
||||
=============
|
||||
|
||||
.. autoclass:: pyqtgraph.console.ConsoleWidget
|
||||
:members:
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
41
examples/BarGraphItem.py
Normal 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
136
examples/CustomGraphItem.py
Normal 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_()
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
50
examples/FillBetweenItem.py
Normal file
50
examples/FillBetweenItem.py
Normal 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_()
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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')
|
||||
|
|
37
examples/MouseSelection.py
Normal file
37
examples/MouseSelection.py
Normal 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_()
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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_()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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><---></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><---></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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
90
examples/ViewBoxFeatures.py
Normal file
90
examples/ViewBoxFeatures.py
Normal 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
15
examples/ViewLimits.py
Normal 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_()
|
|
@ -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
142
examples/contextMenu.py
Normal 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_()
|
|
@ -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")
|
||||
|
|
20
examples/cx_freeze/plotTest.py
Normal file
20
examples/cx_freeze/plotTest.py
Normal 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/cx_freeze/setup.py
Normal file
36
examples/cx_freeze/setup.py
Normal 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)])
|
||||
|
||||
|
46
examples/designerExample.py
Normal file
46
examples/designerExample.py
Normal 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_()
|
38
examples/designerExample.ui
Normal file
38
examples/designerExample.ui
Normal 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>
|
|
@ -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
155
examples/hdf5.py
Normal 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_()
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
98
examples/imageAnalysis.py
Normal 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_()
|
|
@ -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()
|
||||
|
|
1
examples/optics/__init__.py
Normal file
1
examples/optics/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .pyoptic import *
|
582
examples/optics/pyoptic.py
Normal file
582
examples/optics/pyoptic.py
Normal 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)
|
||||
|
BIN
examples/optics/schott_glasses.csv.gz
Normal file
BIN
examples/optics/schott_glasses.csv.gz
Normal file
Binary file not shown.
170
examples/optics_demos.py
Normal file
170
examples/optics_demos.py
Normal 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_()
|
|
@ -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()
|
||||
|
|
20
examples/py2exe/plotTest.py
Normal file
20
examples/py2exe/plotTest.py
Normal 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
36
examples/py2exe/setup.py
Normal 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,
|
||||
)
|
1
examples/relativity/__init__.py
Normal file
1
examples/relativity/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from relativity import *
|
411
examples/relativity/presets/Grid Expansion.cfg
Normal file
411
examples/relativity/presets/Grid Expansion.cfg
Normal 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']
|
667
examples/relativity/presets/Twin Paradox (grid).cfg
Normal file
667
examples/relativity/presets/Twin Paradox (grid).cfg
Normal 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']
|
538
examples/relativity/presets/Twin Paradox.cfg
Normal file
538
examples/relativity/presets/Twin Paradox.cfg
Normal 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']
|
773
examples/relativity/relativity.py
Normal file
773
examples/relativity/relativity.py
Normal 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)
|
||||
|
23
examples/relativity_demo.py
Normal file
23
examples/relativity_demo.py
Normal 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
118
examples/scrollingPlots.py
Normal 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_()
|
1
examples/verlet_chain/__init__.py
Normal file
1
examples/verlet_chain/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .chain import ChainSim
|
115
examples/verlet_chain/chain.py
Normal file
115
examples/verlet_chain/chain.py
Normal 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
3
examples/verlet_chain/make
Executable file
|
@ -0,0 +1,3 @@
|
|||
gcc -fPIC -c relax.c
|
||||
gcc -shared -o maths.so relax.o
|
||||
|
48
examples/verlet_chain/relax.c
Normal file
48
examples/verlet_chain/relax.c
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
70
examples/verlet_chain/relax.py
Normal file
70
examples/verlet_chain/relax.py
Normal 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
|
126
examples/verlet_chain_demo.py
Normal file
126
examples/verlet_chain_demo.py
Normal 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_()
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
<customwidget>
|
||||
<class>ParameterTree</class>
|
||||
<extends>QTreeWidget</extends>
|
||||
<header>pyqtgraph.parametertree</header>
|
||||
<header>..parametertree</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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.
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user