Merge remote-tracking branch 'campagnola/develop' into datatree-arrays

This commit is contained in:
Luke Campagnola 2017-01-16 11:34:42 -08:00
commit 6cdad190ab
175 changed files with 6904 additions and 2515 deletions

19
.coveragerc Normal file
View File

@ -0,0 +1,19 @@
[run]
source = pyqtgraph
branch = True
[report]
omit =
*/python?.?/*
*/site-packages/nose/*
*test*
*/__pycache__/*
*.pyc
exclude_lines =
pragma: no cover
def __repr__
if self\.debug
raise AssertionError
raise NotImplementedError
if 0:
if __name__ == .__main__.:
ignore_errors = True

110
.gitignore vendored
View File

@ -1,9 +1,109 @@
__pycache__ # Byte-compiled / optimized / DLL files
build __pycache__/
*.pyc *.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
doc/_build
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
cover/
.coverage
.cache
nosetests.xml
coverage.xml
.coverage.*
# Translations
*.mo
*.pot
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
*.pot
# Sphinx documentation
docs/_build/
#mac
.DS_Store
*~
#vim
*.swp *.swp
#pycharm
.idea/*
#Dolphin browser files
.directory/
.directory
#Binary data files
*.volume
*.am
*.tiff
*.tif
*.dat
*.DAT
#generated documntation files
doc/resource/api/generated/
# Enaml
__enamlcache__/
# PyBuilder
target/
# sphinx docs
generated/
MANIFEST MANIFEST
deb_build deb_build
dist
.idea
rtr.cvs rtr.cvs
# pytest parallel
.coverage
# ctags
.tags*

View File

@ -1,5 +1,5 @@
language: python language: python
sudo: false
# Credit: Original .travis.yml lifted from VisPy # Credit: Original .travis.yml lifted from VisPy
# Here we use anaconda for 2.6 and 3.3, since it provides the simplest # Here we use anaconda for 2.6 and 3.3, since it provides the simplest
@ -17,25 +17,21 @@ env:
# Enable python 2 and python 3 builds # Enable python 2 and python 3 builds
# Note that the 2.6 build doesn't get flake8, and runs old versions of # 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 # Pyglet and GLFW to make sure we deal with those correctly
#- PYTHON=2.6 QT=pyqt TEST=standard - PYTHON=2.6 QT=pyqt4 TEST=standard
- PYTHON=2.7 QT=pyqt TEST=extra - PYTHON=2.7 QT=pyqt4 TEST=extra
- PYTHON=2.7 QT=pyside TEST=standard - PYTHON=2.7 QT=pyside TEST=standard
- PYTHON=3.2 QT=pyqt TEST=standard - PYTHON=3.4 QT=pyqt5 TEST=standard
- PYTHON=3.2 QT=pyside TEST=standard # - PYTHON=3.4 QT=pyside TEST=standard # pyside isn't available for 3.4 with conda
#- PYTHON=3.2 QT=pyqt5 TEST=standard #- PYTHON=3.2 QT=pyqt5 TEST=standard
before_install: before_install:
- TRAVIS_DIR=`pwd` - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then wget http://repo.continuum.io/miniconda/Miniconda-3.5.5-Linux-x86_64.sh -O miniconda.sh; else wget http://repo.continuum.io/miniconda/Miniconda3-3.5.5-Linux-x86_64.sh -O miniconda.sh; fi
- travis_retry sudo apt-get update; - chmod +x miniconda.sh
# - if [ "${PYTHON}" != "2.7" ]; then - ./miniconda.sh -b -p /home/travis/mc
# wget http://repo.continuum.io/miniconda/Miniconda-2.2.2-Linux-x86_64.sh -O miniconda.sh && - export PATH=/home/travis/mc/bin:$PATH
# chmod +x miniconda.sh &&
# ./miniconda.sh -b && # not sure what is if block is for
# 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 - if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
GIT_TARGET_EXTRA="+refs/heads/${TRAVIS_BRANCH}"; GIT_TARGET_EXTRA="+refs/heads/${TRAVIS_BRANCH}";
GIT_SOURCE_EXTRA="+refs/pull/${TRAVIS_PULL_REQUEST}/merge"; GIT_SOURCE_EXTRA="+refs/pull/${TRAVIS_PULL_REQUEST}/merge";
@ -51,60 +47,31 @@ before_install:
- echo ${GIT_SOURCE_EXTRA} - echo ${GIT_SOURCE_EXTRA}
install: install:
# Dependencies - export GIT_FULL_HASH=`git rev-parse HEAD`
- if [ "${PYTHON}" == "2.7" ]; then - conda update conda --yes
travis_retry sudo apt-get -qq -y install python-numpy && - conda create -n test_env python=${PYTHON} --yes
export PIP=pip && - source activate test_env
sudo ${PIP} install pytest && - conda install numpy scipy pyopengl pytest flake8 six coverage --yes
sudo ${PIP} install flake8 && - echo ${QT}
export PYTEST=py.test; - echo ${TEST}
else - echo ${PYTHON}
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 [ "${QT}" == "pyqt5" ]; then
- if [ "${PYTHON}" == "2.7" ]; then conda install pyqt --yes;
echo "Using OpenGL stable version (apt)"; fi;
travis_retry sudo apt-get -qq -y install python-opengl; - if [ "${QT}" == "pyqt4" ]; then
else conda install pyqt=4 --yes;
echo "Using OpenGL stable version (pip)"; fi;
${PIP} install -q PyOpenGL; - if [ "${QT}" == "pyside" ]; then
cat /home/travis/.pip/pip.log; conda install pyside --yes;
fi;
- pip install pytest-xdist # multi-thread py.test
- pip install pytest-cov # add coverage stats
# required for example testing on python 2.6
- if [ "${PYTHON}" == "2.6" ]; then
pip install importlib;
fi; fi;
# Debugging helpers # Debugging helpers
- uname -a - uname -a
@ -114,23 +81,18 @@ install:
else else
python3 --version; python3 --version;
fi; 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: before_script:
# We need to create a (fake) display on Travis, let's use a funny resolution # We need to create a (fake) display on Travis, let's use a funny resolution
- export DISPLAY=:99.0 - export DISPLAY=:99.0
- "sh -e /etc/init.d/xvfb start"
- /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 - /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 # Make sure everyone uses the correct python (this is handled by conda)
- mkdir ~/bin && ln -s `which python${PYTHON}` ~/bin/python
- export PATH=/home/travis/bin:$PATH
- which python - which python
- python --version - python --version
- pwd
- ls
# Help color output from each test # Help color output from each test
- RESET='\033[0m'; - RESET='\033[0m';
RED='\033[00;31m'; RED='\033[00;31m';
@ -157,12 +119,12 @@ before_script:
start_test "repo size check"; start_test "repo size check";
mkdir ~/repo-clone && cd ~/repo-clone && mkdir ~/repo-clone && cd ~/repo-clone &&
git init && git remote add -t ${TRAVIS_BRANCH} origin git://github.com/${TRAVIS_REPO_SLUG}.git && git init && git remote add -t ${TRAVIS_BRANCH} origin git://github.com/${TRAVIS_REPO_SLUG}.git &&
git fetch origin ${GIT_TARGET_EXTRA} && git fetch origin ${GIT_TARGET_EXTRA} &&
git checkout -qf FETCH_HEAD && git checkout -qf FETCH_HEAD &&
git tag travis-merge-target && git tag travis-merge-target &&
git gc --aggressive && git gc --aggressive &&
TARGET_SIZE=`du -s . | sed -e "s/\t.*//"` && TARGET_SIZE=`du -s . | sed -e "s/\t.*//"` &&
git pull origin ${GIT_SOURCE_EXTRA} && git pull origin ${GIT_SOURCE_EXTRA} &&
git gc --aggressive && git gc --aggressive &&
MERGE_SIZE=`du -s . | sed -e "s/\t.*//"` && MERGE_SIZE=`du -s . | sed -e "s/\t.*//"` &&
if [ "${MERGE_SIZE}" != "${TARGET_SIZE}" ]; then if [ "${MERGE_SIZE}" != "${TARGET_SIZE}" ]; then
@ -171,18 +133,21 @@ before_script:
SIZE_DIFF=0; SIZE_DIFF=0;
fi; fi;
fi; fi;
- cd $TRAVIS_DIR
script: script:
- source activate test_env
# Check system info
- python -c "import pyqtgraph as pg; pg.systemInfo()"
# Run unit tests # Run unit tests
- start_test "unit tests"; - start_test "unit tests";
PYTHONPATH=. ${PYTEST} pyqtgraph/; PYTHONPATH=. py.test --cov pyqtgraph -sv;
check_output "unit tests"; check_output "unit tests";
- echo "test script finished. Current directory:"
- pwd
# check line endings # check line endings
- if [ "${TEST}" == "extra" ]; then - if [ "${TEST}" == "extra" ]; then
start_test "line ending check"; start_test "line ending check";
@ -208,23 +173,26 @@ script:
check_output "style check"; check_output "style check";
fi; fi;
- cd $TRAVIS_DIR
# Check install works # Check install works
- start_test "install test"; - start_test "install test";
sudo python${PYTHON} setup.py --quiet install; python setup.py --quiet install;
check_output "install test"; check_output "install test";
# Check double-install fails # Check double-install fails
# Note the bash -c is because travis strips off the ! otherwise. # Note the bash -c is because travis strips off the ! otherwise.
- start_test "double install test"; - start_test "double install test";
bash -c "! sudo python${PYTHON} setup.py --quiet install"; bash -c "! python setup.py --quiet install";
check_output "double install test"; check_output "double install test";
# Check we can import pg # Check we can import pg
- start_test "import test"; - start_test "import test";
echo "import sys; print(sys.path)" | python && echo "import sys; print(sys.path)" | python &&
cd /; echo "import pyqtgraph.examples" | python; cd /; echo "import pyqtgraph.examples" | python;
check_output "import test"; check_output "import test";
after_success:
- cd /home/travis/build/pyqtgraph/pyqtgraph
- pip install codecov --upgrade # add coverage integration service
- codecov
- pip install coveralls --upgrade # add another coverage integration service
- coveralls

View File

@ -1,4 +1,60 @@
pyqtgraph-0.9.9 [unreleased] pyqtgraph-0.10.0
New Features:
- PyQt5 support
- Options for interpreting image data as either row-major or col-major
- InfiniteLine and LinearRegionItem can have attached labels
- DockArea:
- Dock titles can be changed after creation
- Added Dock.sigClosed
- Added TextItem.setColor()
- FillBetweenItem supports finite-connected curves (those that exclude nan/inf)
API / behavior changes:
- Improved ImageItem performance for some data types by scaling LUT instead of image
- Change the defaut color kwarg to None in TextItem.setText() to avoid changing
the color every time the text is changed.
- FFT plots skip first sample if x-axis uses log scaling
- Multiprocessing system adds bytes and unicode to the default list of no-proxy data types
- Version number scheme changed to be PEP440-compliant (only affects installations from non-
release git commits)
Bugfixes:
- Fix for numpy API change that caused casting errors for inplace operations
- Fixed git version string generation on python3
- Fixed setting default values for out-of-bound points in pg.interpolateArray
- Fixed plot downsampling bug on python 3
- Fixed invalid slice in ImageItem.getHistogram
- DockArea:
- Fixed adding Docks to DockArea after all Docks have been removed
- Fixed DockArea save/restoreState when area is empty
- Properly remove select box when export dialog is closed using window decorations
- Remove all modifications to python builtins
- Better Python 2.6 compatibility
- Fix SpinBox decimals
- Fixed numerous issues with ImageItem automatic downsampling
- Fixed PlotItem average curves using incorrect stepMode
- Fixed TableWidget eating key events
- Prevent redundant updating of flowchart nodes with multiple inputs
- Ignore wheel events in GraphicsView if mouse interaction is disabled
- Correctly pass calls to QWidget.close() up the inheritance chain
- ColorMap forces color inputs to be sorted
- Fixed memory mapping for RemoteGraphicsView in OSX
- Fixed QPropertyAnimation str/bytes handling
- Fixed __version__ string update when using `setup.py install` with newer setuptools
Maintenance:
- Image comparison system for unit testing plus tests for several graphics items
- Travis CI and coveralls/codecov support
- Add examples to unit tests
pyqtgraph-0.9.10
Fixed installation issues with more recent pip versions.
pyqtgraph-0.9.9
API / behavior changes: API / behavior changes:
- Dynamic import system abandoned; pg now uses static imports throughout. - Dynamic import system abandoned; pg now uses static imports throughout.
@ -33,7 +89,9 @@ pyqtgraph-0.9.9 [unreleased]
- Essentially a graphical interface to dict; all items have text and value - Essentially a graphical interface to dict; all items have text and value
- Assigns previously-selected text after list is cleared and repopulated - Assigns previously-selected text after list is cleared and repopulated
- Get, set current value - Get, set current value
- Added Flowchart.sigChartChanged - Flowchart updates
- Added Flowchart.sigChartChanged
- Custom nodes may now be registered in sub-menu trees
- ImageItem.getHistogram is more clever about constructing histograms - ImageItem.getHistogram is more clever about constructing histograms
- Added FillBetweenItem.setCurves() - Added FillBetweenItem.setCurves()
- MultiPlotWidget now has setMinimumPlotHeight method and displays scroll bar - MultiPlotWidget now has setMinimumPlotHeight method and displays scroll bar
@ -95,6 +153,8 @@ pyqtgraph-0.9.9 [unreleased]
- Removed a few cyclic references - Removed a few cyclic references
- Fixed Parameter 'readonly' option for bool, color, and text parameter types - Fixed Parameter 'readonly' option for bool, color, and text parameter types
- Fixed alpha on GLScatterPlotItem spots (formerly maxed out at alpha=200) - 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 pyqtgraph-0.9.8 2013-11-24

View File

@ -3,12 +3,10 @@ Contributions to pyqtgraph are welcome!
Please use the following guidelines when preparing changes: Please use the following guidelines when preparing changes:
* The preferred method for submitting changes is by github pull request * The preferred method for submitting changes is by github pull request
against the "develop" branch. If this is inconvenient, don't hesitate to against the "develop" branch.
submit by other means.
* Pull requests should include only a focused and related set of changes. * Pull requests should include only a focused and related set of changes.
Mixed features and unrelated changes (such as .gitignore) will usually be Mixed features and unrelated changes may be rejected.
rejected.
* For major changes, it is recommended to discuss your plans on the mailing * 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. list or in a github issue before putting in too much effort.
@ -49,3 +47,12 @@ Please use the following guidelines when preparing changes:
QObject subclasses that implement new signals should also describe QObject subclasses that implement new signals should also describe
these in a similar table. these in a similar table.
* Setting up a test environment.
Tests for a module should ideally cover all code in that module,
i.e., statement coverage should be at 100%.
To measure the test coverage, install py.test, pytest-cov and pytest-xdist.
Then run 'py.test --cov -n 4' to run the test suite with coverage on 4 cores.

View File

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

View File

@ -1,3 +1,6 @@
[![Build Status](https://travis-ci.org/pyqtgraph/pyqtgraph.svg?branch=develop)](https://travis-ci.org/pyqtgraph/pyqtgraph)
[![codecov.io](http://codecov.io/github/pyqtgraph/pyqtgraph/coverage.svg?branch=develop)](http://codecov.io/github/pyqtgraph/pyqtgraph?branch=develop)
PyQtGraph PyQtGraph
========= =========
@ -30,13 +33,20 @@ Contributors
* Mikhail Terekhov * Mikhail Terekhov
* Pietro Zambelli * Pietro Zambelli
* Stefan Holzmann * Stefan Holzmann
* Nicholas TJ
* John David Reaver
* David Kaplan
* Martin Fitzpatrick
* Daniel Lidstrom
* Eric Dill
* Vincent LeSaux
Requirements Requirements
------------ ------------
* PyQt 4.7+ or PySide * PyQt 4.7+, PySide, or PyQt5
* python 2.6, 2.7, or 3.x * python 2.6, 2.7, or 3.x
* NumPy * NumPy
* For 3D graphics: pyopengl and qt-opengl * For 3D graphics: pyopengl and qt-opengl
* Known to run on Windows, Linux, and Mac. * Known to run on Windows, Linux, and Mac.

View File

@ -9,6 +9,6 @@ path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
for a, b in dirs: for a, b in dirs:
rst = [os.path.splitext(x)[0].lower() for x in os.listdir(os.path.join(path, 'documentation', 'source', a))] rst = [os.path.splitext(x)[0].lower() for x in os.listdir(os.path.join(path, 'documentation', 'source', a))]
py = [os.path.splitext(x)[0].lower() for x in os.listdir(os.path.join(path, b))] py = [os.path.splitext(x)[0].lower() for x in os.listdir(os.path.join(path, b))]
print a print(a)
for x in set(py) - set(rst): for x in set(py) - set(rst):
print " ", x print( " ", x)

View File

@ -6,6 +6,7 @@ Contents:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
config_options
functions functions
graphicsItems/index graphicsItems/index
widgets/index widgets/index

View File

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

View File

@ -0,0 +1,41 @@
.. currentmodule:: pyqtgraph
.. _apiref_config:
Global Configuration Options
============================
PyQtGraph has several global configuration options that allow you to change its
default behavior. These can be accessed using the :func:`setConfigOptions` and
:func:`getConfigOption` functions:
================== =================== ================== ================================================================================
**Option** **Type** **Default**
leftButtonPan bool True If True, dragging the left mouse button over a ViewBox
causes the view to be panned. If False, then dragging
the left mouse button draws a rectangle that the
ViewBox will zoom to.
foreground See :func:`mkColor` 'd' Default foreground color for text, lines, axes, etc.
background See :func:`mkColor` 'k' Default background for :class:`GraphicsView`.
antialias bool False Enabling antialiasing causes lines to be drawn with
smooth edges at the cost of reduced performance.
imageAxisOrder str 'col-major' For 'row-major', image data is expected in the standard row-major
(row, col) order. For 'col-major', image data is expected in
reversed column-major (col, row) order.
The default is 'col-major' for backward compatibility, but this may
change in the future.
editorCommand str or None None Command used to invoke code editor from ConsoleWidget.
exitCleanup bool True Attempt to work around some exit crash bugs in PyQt and PySide.
useWeave bool False Use weave to speed up some operations, if it is available.
weaveDebug bool False Print full error message if weave compile fails.
useOpenGL bool False Enable OpenGL in GraphicsView. This can have unpredictable effects on stability
and performance.
enableExperimental bool False Enable experimental features (the curious can search for this key in the code).
crashWarning bool False If True, print warnings about situations that may result in a crash.
================== =================== ================== ================================================================================
.. autofunction:: pyqtgraph.setConfigOptions
.. autofunction:: pyqtgraph.getConfigOption

View File

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

View File

@ -91,6 +91,8 @@ Mesh Generation Functions
Miscellaneous Functions Miscellaneous Functions
----------------------- -----------------------
.. autofunction:: pyqtgraph.eq
.. autofunction:: pyqtgraph.arrayToQPath .. autofunction:: pyqtgraph.arrayToQPath
.. autofunction:: pyqtgraph.pseudoScatter .. autofunction:: pyqtgraph.pseudoScatter

View File

@ -23,7 +23,7 @@ ViewBox
VTickGroup""".split('\n') VTickGroup""".split('\n')
for f in files: for f in files:
print f print(f)
fh = open(f.lower()+'.rst', 'w') fh = open(f.lower()+'.rst', 'w')
fh.write( fh.write(
"""%s """%s

View File

@ -6,9 +6,17 @@ Introduction
What is pyqtgraph? 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 It is known to run on Linux, Windows, and OSX
@ -22,10 +30,13 @@ Amongst the core features of pyqtgraph are:
* Fast enough for realtime update of video/plot data * Fast enough for realtime update of video/plot data
* Interactive scaling/panning, averaging, FFTs, SVG/PNG export * Interactive scaling/panning, averaging, FFTs, SVG/PNG export
* Widgets for marking/selecting plot regions * Widgets for marking/selecting plot regions
* Widgets for marking/selecting image region-of-interest and automatically slicing multi-dimensional image data * Widgets for marking/selecting image region-of-interest and automatically
slicing multi-dimensional image data
* Framework for building customized image region-of-interest widgets * Framework for building customized image region-of-interest widgets
* Docking system that replaces/complements Qt's dock system to allow more complex (and more predictable) docking arrangements * Docking system that replaces/complements Qt's dock system to allow more
* ParameterTree widget for rapid prototyping of dynamic interfaces (Similar to the property trees in Qt Designer and many other applications) complex (and more predictable) docking arrangements
* ParameterTree widget for rapid prototyping of dynamic interfaces (Similar to
the property trees in Qt Designer and many other applications)
.. _examples: .. _examples:
@ -33,19 +44,43 @@ Amongst the core features of pyqtgraph are:
Examples 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 import pyqtgraph.examples
pyqtgraph.examples.run() pyqtgraph.examples.run()
This will start a launcher with a list of available examples. Select an item from the list to view its source code and double-click an item to run the example. Or by running ``python examples/`` from the source root.
This will start a launcher with a list of available examples. Select an item
from the list to view its source code and double-click an item to run the
example.
Note If you have installed pyqtgraph with ``python setup.py develop``
then the examples are incorrectly exposed as a top-level module. In this case,
use ``import examples; examples.run()``.
How does it compare to... How does it compare to...
------------------------- -------------------------
* matplotlib: For plotting, pyqtgraph is not nearly as complete/mature as matplotlib, but runs much faster. Matplotlib is more aimed toward making publication-quality graphics, whereas pyqtgraph is intended for use in data acquisition and analysis applications. Matplotlib is more intuitive for matlab programmers; pyqtgraph is more intuitive for python/qt programmers. Matplotlib (to my knowledge) does not include many of pyqtgraph's features such as image interaction, volumetric rendering, parameter trees, flowcharts, etc. * matplotlib: For plotting, pyqtgraph is not nearly as complete/mature as
matplotlib, but runs much faster. Matplotlib is more aimed toward making
publication-quality graphics, whereas pyqtgraph is intended for use in data
acquisition and analysis applications. Matplotlib is more intuitive for
matlab programmers; pyqtgraph is more intuitive for python/qt programmers.
Matplotlib (to my knowledge) does not include many of pyqtgraph's features
such as image interaction, volumetric rendering, parameter trees,
flowcharts, etc.
* pyqwt5: About as fast as pyqwt5, but not quite as complete for plotting functionality. Image handling in pyqtgraph is much more complete (again, no ROI widgets in qwt). Also, pyqtgraph is written in pure python, so it is more portable than pyqwt, which often lags behind pyqt in development (I originally used pyqwt, but decided it was too much trouble to rely on it as a dependency in my projects). Like matplotlib, pyqwt (to my knowledge) does not include many of pyqtgraph's features such as image interaction, volumetric rendering, parameter trees, flowcharts, etc. * pyqwt5: About as fast as pyqwt5, but not quite as complete for plotting
functionality. Image handling in pyqtgraph is much more complete (again, no
ROI widgets in qwt). Also, pyqtgraph is written in pure python, so it is
more portable than pyqwt, which often lags behind pyqt in development (I
originally used pyqwt, but decided it was too much trouble to rely on it
as a dependency in my projects). Like matplotlib, pyqwt (to my knowledge)
does not include many of pyqtgraph's features such as image interaction,
volumetric rendering, parameter trees, flowcharts, etc.
(My experience with these libraries is somewhat outdated; please correct me if I am wrong here) (My experience with these libraries is somewhat outdated; please correct me if
I am wrong here)

View File

@ -17,7 +17,7 @@ TreeWidget
VerticalLabel""".split('\n') VerticalLabel""".split('\n')
for f in files: for f in files:
print f print(f)
fh = open(f.lower()+'.rst', 'w') fh = open(f.lower()+'.rst', 'w')
fh.write( fh.write(
"""%s """%s

View File

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

View File

@ -8,6 +8,8 @@ import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
import numpy as np import numpy as np
#FIXME: When running on Qt5, not as perfect as on Qt4
win = pg.plot() win = pg.plot()
win.setWindowTitle('pyqtgraph example: FillBetweenItem') win.setWindowTitle('pyqtgraph example: FillBetweenItem')
win.setXRange(-10, 10) win.setXRange(-10, 10)

View File

@ -2,7 +2,7 @@
""" """
This example demonstrates a very basic use of flowcharts: filter data, This example demonstrates a very basic use of flowcharts: filter data,
displaying both the input and output of the filter. The behavior of displaying both the input and output of the filter. The behavior of
he filter can be reprogrammed by the user. the filter can be reprogrammed by the user.
Basic steps are: Basic steps are:
- create a flowchart and two plots - create a flowchart and two plots

View File

@ -89,11 +89,11 @@ class ImageViewNode(Node):
## CtrlNode is just a convenience class that automatically creates its ## CtrlNode is just a convenience class that automatically creates its
## control widget based on a simple data structure. ## control widget based on a simple data structure.
class UnsharpMaskNode(CtrlNode): class UnsharpMaskNode(CtrlNode):
"""Return the input data passed through pg.gaussianFilter.""" """Return the input data passed through an unsharp mask."""
nodeName = "UnsharpMask" nodeName = "UnsharpMask"
uiTemplate = [ uiTemplate = [
('sigma', 'spin', {'value': 1.0, 'step': 1.0, 'range': [0.0, None]}), ('sigma', 'spin', {'value': 1.0, 'step': 1.0, 'bounds': [0.0, None]}),
('strength', 'spin', {'value': 1.0, 'dec': True, 'step': 0.5, 'minStep': 0.01, 'range': [0.0, None]}), ('strength', 'spin', {'value': 1.0, 'dec': True, 'step': 0.5, 'minStep': 0.01, 'bounds': [0.0, None]}),
] ]
def __init__(self, name): def __init__(self, name):
## Define the input / output terminals available on this node ## Define the input / output terminals available on this node
@ -127,7 +127,10 @@ class UnsharpMaskNode(CtrlNode):
## NodeLibrary: ## NodeLibrary:
library = fclib.LIBRARY.copy() # start with the default node set library = fclib.LIBRARY.copy() # start with the default node set
library.addNodeType(ImageViewNode, [('Display',)]) library.addNodeType(ImageViewNode, [('Display',)])
library.addNodeType(UnsharpMaskNode, [('Image',)]) # 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) fc.setLibrary(library)

View File

@ -16,7 +16,7 @@ app = QtGui.QApplication([])
w = QtGui.QMainWindow() w = QtGui.QMainWindow()
w.show() w.show()
w.setWindowTitle('pyqtgraph example: GradientWidget') w.setWindowTitle('pyqtgraph example: GradientWidget')
w.resize(400,400) w.setGeometry(10, 50, 400, 400)
cw = QtGui.QWidget() cw = QtGui.QWidget()
w.setCentralWidget(cw) w.setCentralWidget(cw)

View File

@ -17,6 +17,9 @@ import numpy as np
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg import pyqtgraph as pg
# Interpret image data as row-major instead of col-major
pg.setConfigOptions(imageAxisOrder='row-major')
app = QtGui.QApplication([]) app = QtGui.QApplication([])
## Create window with ImageView widget ## Create window with ImageView widget
@ -42,12 +45,24 @@ sig[40:] += np.exp(-np.linspace(1,10, 60))
sig[70:] += np.exp(-np.linspace(1,10, 30)) sig[70:] += np.exp(-np.linspace(1,10, 30))
sig = sig[:,np.newaxis,np.newaxis] * 3 sig = sig[:,np.newaxis,np.newaxis] * 3
data[:,50:60,50:60] += sig data[:,50:60,30:40] += sig
## Display the data and assign each frame a time value from 1.0 to 3.0 ## Display the data and assign each frame a time value from 1.0 to 3.0
imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0])) imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0]))
## Set a custom color map
colors = [
(0, 0, 0),
(45, 5, 61),
(84, 42, 55),
(150, 87, 60),
(208, 171, 141),
(255, 255, 255)
]
cmap = pg.ColorMap(pos=np.linspace(0.0, 1.0, 6), color=colors)
imv.setColorMap(cmap)
## Start Qt event loop unless running in interactive mode. ## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys

45
examples/InfiniteLine.py Normal file
View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
"""
This example demonstrates some of the plotting items available in pyqtgraph.
"""
import initExample ## Add path to library (just for examples; you do not need this)
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
app = QtGui.QApplication([])
win = pg.GraphicsWindow(title="Plotting items examples")
win.resize(1000,600)
# Enable antialiasing for prettier plots
pg.setConfigOptions(antialias=True)
# Create a plot with some random data
p1 = win.addPlot(title="Plot Items example", y=np.random.normal(size=100, scale=10), pen=0.5)
p1.setYRange(-40, 40)
# Add three infinite lines with labels
inf1 = pg.InfiniteLine(movable=True, angle=90, label='x={value:0.2f}',
labelOpts={'position':0.1, 'color': (200,200,100), 'fill': (200,200,200,50), 'movable': True})
inf2 = pg.InfiniteLine(movable=True, angle=0, pen=(0, 0, 200), bounds = [-20, 20], hoverPen=(0,200,0), label='y={value:0.2f}mm',
labelOpts={'color': (200,0,0), 'movable': True, 'fill': (0, 0, 200, 100)})
inf3 = pg.InfiniteLine(movable=True, angle=45, pen='g', label='diagonal',
labelOpts={'rotateAxis': [1, 0], 'fill': (0, 200, 0, 100), 'movable': True})
inf1.setPos([2,2])
p1.addItem(inf1)
p1.addItem(inf2)
p1.addItem(inf3)
# Add a linear region with a label
lr = pg.LinearRegionItem(values=[70, 80])
p1.addItem(lr)
label = pg.InfLineLabel(lr.lines[1], "region 1", position=0.95, rotateAxis=(1,0), anchor=(1, 1))
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -37,7 +37,7 @@ p1.setPen((200,200,100))
## Add in some extra graphics ## Add in some extra graphics
rect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, 0, 1, 5e-11)) rect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, 0, 1, 5e-11))
rect.setPen(QtGui.QPen(QtGui.QColor(100, 200, 100))) rect.setPen(pg.mkPen(100, 200, 100))
pw.addItem(rect) pw.addItem(rect)
pw.setLabel('left', 'Value', units='V') pw.setLabel('left', 'Value', units='V')

View File

@ -28,8 +28,8 @@ p1 = win.addPlot(title="Basic array plotting", y=np.random.normal(size=100))
p2 = win.addPlot(title="Multiple curves") p2 = win.addPlot(title="Multiple curves")
p2.plot(np.random.normal(size=100), pen=(255,0,0), name="Red curve") 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=110)+5, pen=(0,255,0), name="Green curve")
p2.plot(np.random.normal(size=120)+10, pen=(0,0,255), name="Green curve") p2.plot(np.random.normal(size=120)+10, pen=(0,0,255), name="Blue curve")
p3 = win.addPlot(title="Drawing with points") p3 = win.addPlot(title="Drawing with points")
p3.plot(np.random.normal(size=100), pen=(200,200,200), symbolBrush=(255,0,0), symbolPen='w') p3.plot(np.random.normal(size=100), pen=(200,200,200), symbolBrush=(255,0,0), symbolPen='w')

View File

@ -11,6 +11,7 @@ import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import numpy as np import numpy as np
pg.setConfigOptions(imageAxisOrder='row-major')
## Create image to display ## Create image to display
arr = np.ones((100, 100), dtype=float) arr = np.ones((100, 100), dtype=float)
@ -24,6 +25,11 @@ arr[:, 50] = 10
arr += np.sin(np.linspace(0, 20, 100)).reshape(1, 100) arr += np.sin(np.linspace(0, 20, 100)).reshape(1, 100)
arr += np.random.normal(size=(100,100)) arr += np.random.normal(size=(100,100))
# add an arrow for asymmetry
arr[10, :50] = 10
arr[9:12, 44:48] = 10
arr[8:13, 44:46] = 10
## create GUI ## create GUI
app = QtGui.QApplication([]) app = QtGui.QApplication([])

View File

@ -8,23 +8,15 @@ from pyqtgraph.Qt import QtCore, QtGui
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
pg.setConfigOptions(imageAxisOrder='row-major')
## create GUI ## create GUI
app = QtGui.QApplication([]) app = QtGui.QApplication([])
w = pg.GraphicsWindow(size=(800,800), border=True) w = pg.GraphicsWindow(size=(800,800), border=True)
v = w.addViewBox(colspan=2) v = w.addViewBox(colspan=2)
#w = QtGui.QMainWindow()
#w.resize(800,800)
#v = pg.GraphicsView()
v.invertY(True) ## Images usually have their Y-axis pointing downward v.invertY(True) ## Images usually have their Y-axis pointing downward
v.setAspectLocked(True) v.setAspectLocked(True)
#v.enableMouse(True)
#v.autoPixelScale = False
#w.setCentralWidget(v)
#s = v.scene()
#v.setRange(QtCore.QRectF(-2, -2, 220, 220))
## Create image to display ## Create image to display
@ -37,6 +29,11 @@ arr[:, 75] = 5
arr[50, :] = 10 arr[50, :] = 10
arr[:, 50] = 10 arr[:, 50] = 10
# add an arrow for asymmetry
arr[10, :50] = 10
arr[9:12, 44:48] = 10
arr[8:13, 44:46] = 10
## Create image items, add to scene and set position ## Create image items, add to scene and set position
im1 = pg.ImageItem(arr) im1 = pg.ImageItem(arr)
im2 = pg.ImageItem(arr) im2 = pg.ImageItem(arr)
@ -44,6 +41,7 @@ v.addItem(im1)
v.addItem(im2) v.addItem(im2)
im2.moveBy(110, 20) im2.moveBy(110, 20)
v.setRange(QtCore.QRectF(0, 0, 200, 120)) v.setRange(QtCore.QRectF(0, 0, 200, 120))
im1.scale(0.8, 0.5)
im3 = pg.ImageItem() im3 = pg.ImageItem()
v2 = w.addViewBox(1,0) v2 = w.addViewBox(1,0)

View File

@ -12,7 +12,7 @@ For testing rapid updates of ScatterPlotItem under various conditions.
import initExample import initExample
from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE, USE_PYQT5
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph.ptime import time from pyqtgraph.ptime import time
@ -22,6 +22,8 @@ app = QtGui.QApplication([])
#mw.resize(800,800) #mw.resize(800,800)
if USE_PYSIDE: if USE_PYSIDE:
from ScatterPlotSpeedTestTemplate_pyside import Ui_Form from ScatterPlotSpeedTestTemplate_pyside import Ui_Form
elif USE_PYQT5:
from ScatterPlotSpeedTestTemplate_pyqt5 import Ui_Form
else: else:
from ScatterPlotSpeedTestTemplate_pyqt import Ui_Form from ScatterPlotSpeedTestTemplate_pyqt import Ui_Form

View File

@ -1,4 +1,22 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Demonstration of ScatterPlotWidget for exploring structure in tabular data.
The widget consists of four components:
1) A list of column names from which the user may select 1 or 2 columns
to plot. If one column is selected, the data for that column will be
plotted in a histogram-like manner by using pg.pseudoScatter().
If two columns are selected, then the
scatter plot will be generated with x determined by the first column
that was selected and y by the second.
2) A DataFilter that allows the user to select a subset of the data by
specifying multiple selection criteria.
3) A ColorMap that allows the user to determine how points are colored by
specifying multiple criteria.
4) A PlotWidget for displaying the data.
"""
import initExample ## Add path to library (just for examples; you do not need this) import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg import pyqtgraph as pg
@ -7,42 +25,42 @@ import numpy as np
pg.mkQApp() pg.mkQApp()
# Make up some tabular data with structure
data = np.empty(1000, dtype=[('x_pos', float), ('y_pos', float),
('count', int), ('amplitude', float),
('decay', float), ('type', 'S10')])
strings = ['Type-A', 'Type-B', 'Type-C', 'Type-D', 'Type-E']
typeInds = np.random.randint(5, size=1000)
data['type'] = np.array(strings)[typeInds]
data['x_pos'] = np.random.normal(size=1000)
data['x_pos'][data['type'] == 'Type-A'] -= 1
data['x_pos'][data['type'] == 'Type-B'] -= 1
data['x_pos'][data['type'] == 'Type-C'] += 2
data['x_pos'][data['type'] == 'Type-D'] += 2
data['x_pos'][data['type'] == 'Type-E'] += 2
data['y_pos'] = np.random.normal(size=1000) + data['x_pos']*0.1
data['y_pos'][data['type'] == 'Type-A'] += 3
data['y_pos'][data['type'] == 'Type-B'] += 3
data['amplitude'] = data['x_pos'] * 1.4 + data['y_pos'] + np.random.normal(size=1000, scale=0.4)
data['count'] = (np.random.exponential(size=1000, scale=100) * data['x_pos']).astype(int)
data['decay'] = np.random.normal(size=1000, scale=1e-3) + data['amplitude'] * 1e-4
data['decay'][data['type'] == 'Type-A'] /= 2
data['decay'][data['type'] == 'Type-E'] *= 3
# Create ScatterPlotWidget and configure its fields
spw = pg.ScatterPlotWidget() spw = pg.ScatterPlotWidget()
spw.show()
data = np.array([
(1, 1, 3, 4, 'x'),
(2, 3, 3, 7, 'y'),
(3, 2, 5, 2, 'z'),
(4, 4, 6, 9, 'z'),
(5, 3, 6, 7, 'x'),
(6, 5, 4, 6, 'x'),
(7, 5, 8, 2, 'z'),
(8, 1, 2, 4, 'x'),
(9, 2, 3, 7, 'z'),
(0, 6, 0, 2, 'z'),
(1, 3, 1, 2, 'z'),
(2, 5, 4, 6, 'y'),
(3, 4, 8, 1, 'y'),
(4, 7, 6, 8, 'z'),
(5, 8, 7, 4, 'y'),
(6, 1, 2, 3, 'y'),
(7, 5, 3, 9, 'z'),
(8, 9, 3, 1, 'x'),
(9, 2, 6, 2, 'z'),
(0, 3, 4, 6, 'x'),
(1, 5, 9, 3, 'y'),
], dtype=[('col1', float), ('col2', float), ('col3', int), ('col4', int), ('col5', 'S10')])
spw.setFields([ spw.setFields([
('col1', {'units': 'm'}), ('x_pos', {'units': 'm'}),
('col2', {'units': 'm'}), ('y_pos', {'units': 'm'}),
('col3', {}), ('count', {}),
('col4', {}), ('amplitude', {'units': 'V'}),
('col5', {'mode': 'enum', 'values': ['x', 'y', 'z']}), ('decay', {'units': 's'}),
('type', {'mode': 'enum', 'values': strings}),
]) ])
spw.setData(data) spw.setData(data)
spw.show()
## Start Qt event loop unless running in interactive mode or using pyside. ## Start Qt event loop unless running in interactive mode or using pyside.

View File

@ -13,18 +13,33 @@ import initExample ## Add path to library (just for examples; you do not need th
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import numpy as np import numpy as np
import ast
app = QtGui.QApplication([]) app = QtGui.QApplication([])
spins = [ spins = [
("Floating-point spin box, min=0, no maximum.", pg.SpinBox(value=5.0, bounds=[0, None])), ("Floating-point spin box, min=0, no maximum.",
("Integer spin box, dec stepping<br>(1-9, 10-90, 100-900, etc)", pg.SpinBox(value=10, int=True, dec=True, minStep=1, step=1)), pg.SpinBox(value=5.0, bounds=[0, None])),
("Float with SI-prefixed units<br>(n, u, m, k, M, etc)", pg.SpinBox(value=0.9, suffix='V', siPrefix=True)), ("Integer spin box, dec stepping<br>(1-9, 10-90, 100-900, etc), decimals=4",
("Float with SI-prefixed units,<br>dec step=0.1, minStep=0.1", pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.1, minStep=0.1)), pg.SpinBox(value=10, int=True, dec=True, minStep=1, step=1, decimals=4)),
("Float with SI-prefixed units,<br>dec step=0.5, minStep=0.01", pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.5, minStep=0.01)), ("Float with SI-prefixed units<br>(n, u, m, k, M, etc)",
("Float with SI-prefixed units,<br>dec step=1.0, minStep=0.001", pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=1.0, minStep=0.001)), pg.SpinBox(value=0.9, suffix='V', siPrefix=True)),
("Float with SI-prefixed units,<br>dec step=0.1, minStep=0.1",
pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.1, minStep=0.1)),
("Float with SI-prefixed units,<br>dec step=0.5, minStep=0.01",
pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.5, minStep=0.01)),
("Float with SI-prefixed units,<br>dec step=1.0, minStep=0.001",
pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=1.0, minStep=0.001)),
("Float with custom formatting",
pg.SpinBox(value=23.07, format='${value:0.02f}',
regex='\$?(?P<number>(-?\d+(\.\d+)?)|(-?\.\d+))$')),
("Int with custom formatting",
pg.SpinBox(value=4567, step=1, int=True, bounds=[0,None], format='0x{value:X}',
regex='(0x)?(?P<number>[0-9a-fA-F]+)$',
evalFunc=lambda s: ast.literal_eval('0x'+s))),
("Integer with bounds=[10, 20] and wrapping",
pg.SpinBox(value=10, bounds=[10, 20], int=False, minStep=1, step=1, wrapping=True)),
] ]

38
examples/Symbols.py Executable file
View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""
This example shows all the scatter plot symbols available in pyqtgraph.
These symbols are used to mark point locations for scatter plots and some line
plots, similar to "markers" in matplotlib and vispy.
"""
import initExample ## Add path to library (just for examples; you do not need this)
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
app = QtGui.QApplication([])
win = pg.GraphicsWindow(title="Scatter Plot Symbols")
win.resize(1000,600)
pg.setConfigOptions(antialias=True)
plot = win.addPlot(title="Plotting with symbols")
plot.addLegend()
plot.plot([0, 1, 2, 3, 4], pen=(0,0,200), symbolBrush=(0,0,200), symbolPen='w', symbol='o', symbolSize=14, name="symbol='o'")
plot.plot([1, 2, 3, 4, 5], pen=(0,128,0), symbolBrush=(0,128,0), symbolPen='w', symbol='t', symbolSize=14, name="symbol='t'")
plot.plot([2, 3, 4, 5, 6], pen=(19,234,201), symbolBrush=(19,234,201), symbolPen='w', symbol='t1', symbolSize=14, name="symbol='t1'")
plot.plot([3, 4, 5, 6, 7], pen=(195,46,212), symbolBrush=(195,46,212), symbolPen='w', symbol='t2', symbolSize=14, name="symbol='t2'")
plot.plot([4, 5, 6, 7, 8], pen=(250,194,5), symbolBrush=(250,194,5), symbolPen='w', symbol='t3', symbolSize=14, name="symbol='t3'")
plot.plot([5, 6, 7, 8, 9], pen=(54,55,55), symbolBrush=(55,55,55), symbolPen='w', symbol='s', symbolSize=14, name="symbol='s'")
plot.plot([6, 7, 8, 9, 10], pen=(0,114,189), symbolBrush=(0,114,189), symbolPen='w', symbol='p', symbolSize=14, name="symbol='p'")
plot.plot([7, 8, 9, 10, 11], pen=(217,83,25), symbolBrush=(217,83,25), symbolPen='w', symbol='h', symbolSize=14, name="symbol='h'")
plot.plot([8, 9, 10, 11, 12], pen=(237,177,32), symbolBrush=(237,177,32), symbolPen='w', symbol='star', symbolSize=14, name="symbol='star'")
plot.plot([9, 10, 11, 12, 13], pen=(126,47,142), symbolBrush=(126,47,142), symbolPen='w', symbol='+', symbolSize=14, name="symbol='+'")
plot.plot([10, 11, 12, 13, 14], pen=(119,172,48), symbolBrush=(119,172,48), symbolPen='w', symbol='d', symbolSize=14, name="symbol='d'")
plot.setXRange(-2, 4)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -10,27 +10,37 @@ is used by the view widget
import initExample ## Add path to library (just for examples; you do not need this) import initExample ## Add path to library (just for examples; you do not need this)
from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE, USE_PYQT5
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
import pyqtgraph.ptime as ptime import pyqtgraph.ptime as ptime
if USE_PYSIDE: if USE_PYSIDE:
import VideoTemplate_pyside as VideoTemplate import VideoTemplate_pyside as VideoTemplate
elif USE_PYQT5:
import VideoTemplate_pyqt5 as VideoTemplate
else: else:
import VideoTemplate_pyqt as VideoTemplate import VideoTemplate_pyqt as VideoTemplate
#QtGui.QApplication.setGraphicsSystem('raster') #QtGui.QApplication.setGraphicsSystem('raster')
app = QtGui.QApplication([]) app = QtGui.QApplication([])
#mw = QtGui.QMainWindow()
#mw.resize(800,800)
win = QtGui.QMainWindow() win = QtGui.QMainWindow()
win.setWindowTitle('pyqtgraph example: VideoSpeedTest') win.setWindowTitle('pyqtgraph example: VideoSpeedTest')
ui = VideoTemplate.Ui_MainWindow() ui = VideoTemplate.Ui_MainWindow()
ui.setupUi(win) ui.setupUi(win)
win.show() win.show()
try:
from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget
except ImportError:
ui.rawGLRadio.setEnabled(False)
ui.rawGLRadio.setText(ui.rawGLRadio.text() + " (OpenGL not available)")
else:
ui.rawGLImg = RawImageGLWidget()
ui.stack.addWidget(ui.rawGLImg)
ui.maxSpin1.setOpts(value=255, step=1) ui.maxSpin1.setOpts(value=255, step=1)
ui.minSpin1.setOpts(value=0, step=1) ui.minSpin1.setOpts(value=0, step=1)
@ -101,6 +111,9 @@ def mkData():
if dtype[0] != 'float': if dtype[0] != 'float':
data = np.clip(data, 0, mx) data = np.clip(data, 0, mx)
data = data.astype(dt) data = data.astype(dt)
data[:, 10, 10:50] = mx
data[:, 9:12, 48] = mx
data[:, 8:13, 47] = mx
cache = {dtype: data} # clear to save memory (but keep one to prevent unnecessary regeneration) cache = {dtype: data} # clear to save memory (but keep one to prevent unnecessary regeneration)
data = cache[dtype] data = cache[dtype]

View File

@ -51,7 +51,7 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QStackedWidget" name="stack"> <widget class="QStackedWidget" name="stack">
<property name="currentIndex"> <property name="currentIndex">
<number>2</number> <number>1</number>
</property> </property>
<widget class="QWidget" name="page"> <widget class="QWidget" name="page">
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
@ -74,13 +74,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="page_3">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="RawImageGLWidget" name="rawGLImg" native="true"/>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="4" column="0">
@ -340,12 +333,6 @@
<extends>QDoubleSpinBox</extends> <extends>QDoubleSpinBox</extends>
<header>pyqtgraph</header> <header>pyqtgraph</header>
</customwidget> </customwidget>
<customwidget>
<class>RawImageGLWidget</class>
<extends>QWidget</extends>
<header>pyqtgraph.widgets.RawImageWidget</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './examples/VideoTemplate.ui' # Form implementation generated from reading ui file 'examples/VideoTemplate.ui'
# #
# Created: Mon Feb 17 20:39:30 2014 # Created by: PyQt4 UI code generator 4.11.4
# by: PyQt4 UI code generator 4.10.3
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@ -69,14 +68,6 @@ class Ui_MainWindow(object):
self.rawImg.setObjectName(_fromUtf8("rawImg")) self.rawImg.setObjectName(_fromUtf8("rawImg"))
self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1)
self.stack.addWidget(self.page_2) self.stack.addWidget(self.page_2)
self.page_3 = QtGui.QWidget()
self.page_3.setObjectName(_fromUtf8("page_3"))
self.gridLayout_5 = QtGui.QGridLayout(self.page_3)
self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5"))
self.rawGLImg = RawImageGLWidget(self.page_3)
self.rawGLImg.setObjectName(_fromUtf8("rawGLImg"))
self.gridLayout_5.addWidget(self.rawGLImg, 0, 0, 1, 1)
self.stack.addWidget(self.page_3)
self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) self.gridLayout.addWidget(self.stack, 0, 0, 1, 1)
self.rawGLRadio = QtGui.QRadioButton(self.centralwidget) self.rawGLRadio = QtGui.QRadioButton(self.centralwidget)
self.rawGLRadio.setObjectName(_fromUtf8("rawGLRadio")) self.rawGLRadio.setObjectName(_fromUtf8("rawGLRadio"))
@ -193,7 +184,7 @@ class Ui_MainWindow(object):
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow) self.retranslateUi(MainWindow)
self.stack.setCurrentIndex(2) self.stack.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow): def retranslateUi(self, MainWindow):
@ -217,5 +208,5 @@ class Ui_MainWindow(object):
self.rgbCheck.setText(_translate("MainWindow", "RGB", None)) self.rgbCheck.setText(_translate("MainWindow", "RGB", None))
self.label_5.setText(_translate("MainWindow", "Image size", None)) self.label_5.setText(_translate("MainWindow", "Image size", None))
from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget, RawImageWidget from pyqtgraph import GradientWidget, GraphicsView, SpinBox
from pyqtgraph import GradientWidget, SpinBox, GraphicsView from pyqtgraph.widgets.RawImageWidget import RawImageWidget

View File

@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'examples/VideoTemplate.ui'
#
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(695, 798)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget)
self.downsampleCheck.setObjectName("downsampleCheck")
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
self.scaleCheck = QtWidgets.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName("scaleCheck")
self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1)
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.rawRadio = QtWidgets.QRadioButton(self.centralwidget)
self.rawRadio.setObjectName("rawRadio")
self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1)
self.gfxRadio = QtWidgets.QRadioButton(self.centralwidget)
self.gfxRadio.setChecked(True)
self.gfxRadio.setObjectName("gfxRadio")
self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1)
self.stack = QtWidgets.QStackedWidget(self.centralwidget)
self.stack.setObjectName("stack")
self.page = QtWidgets.QWidget()
self.page.setObjectName("page")
self.gridLayout_3 = QtWidgets.QGridLayout(self.page)
self.gridLayout_3.setObjectName("gridLayout_3")
self.graphicsView = GraphicsView(self.page)
self.graphicsView.setObjectName("graphicsView")
self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1)
self.stack.addWidget(self.page)
self.page_2 = QtWidgets.QWidget()
self.page_2.setObjectName("page_2")
self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2)
self.gridLayout_4.setObjectName("gridLayout_4")
self.rawImg = RawImageWidget(self.page_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth())
self.rawImg.setSizePolicy(sizePolicy)
self.rawImg.setObjectName("rawImg")
self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1)
self.stack.addWidget(self.page_2)
self.gridLayout.addWidget(self.stack, 0, 0, 1, 1)
self.rawGLRadio = QtWidgets.QRadioButton(self.centralwidget)
self.rawGLRadio.setObjectName("rawGLRadio")
self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1)
self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4)
self.dtypeCombo = QtWidgets.QComboBox(self.centralwidget)
self.dtypeCombo.setObjectName("dtypeCombo")
self.dtypeCombo.addItem("")
self.dtypeCombo.addItem("")
self.dtypeCombo.addItem("")
self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1)
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setObjectName("label")
self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1)
self.rgbLevelsCheck = QtWidgets.QCheckBox(self.centralwidget)
self.rgbLevelsCheck.setObjectName("rgbLevelsCheck")
self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.minSpin2 = SpinBox(self.centralwidget)
self.minSpin2.setEnabled(False)
self.minSpin2.setObjectName("minSpin2")
self.horizontalLayout_2.addWidget(self.minSpin2)
self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setAlignment(QtCore.Qt.AlignCenter)
self.label_3.setObjectName("label_3")
self.horizontalLayout_2.addWidget(self.label_3)
self.maxSpin2 = SpinBox(self.centralwidget)
self.maxSpin2.setEnabled(False)
self.maxSpin2.setObjectName("maxSpin2")
self.horizontalLayout_2.addWidget(self.maxSpin2)
self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.minSpin1 = SpinBox(self.centralwidget)
self.minSpin1.setObjectName("minSpin1")
self.horizontalLayout.addWidget(self.minSpin1)
self.label_2 = QtWidgets.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 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.minSpin3 = SpinBox(self.centralwidget)
self.minSpin3.setEnabled(False)
self.minSpin3.setObjectName("minSpin3")
self.horizontalLayout_3.addWidget(self.minSpin3)
self.label_4 = QtWidgets.QLabel(self.centralwidget)
self.label_4.setAlignment(QtCore.Qt.AlignCenter)
self.label_4.setObjectName("label_4")
self.horizontalLayout_3.addWidget(self.label_4)
self.maxSpin3 = SpinBox(self.centralwidget)
self.maxSpin3.setEnabled(False)
self.maxSpin3.setObjectName("maxSpin3")
self.horizontalLayout_3.addWidget(self.maxSpin3)
self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1)
self.lutCheck = QtWidgets.QCheckBox(self.centralwidget)
self.lutCheck.setObjectName("lutCheck")
self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1)
self.alphaCheck = QtWidgets.QCheckBox(self.centralwidget)
self.alphaCheck.setObjectName("alphaCheck")
self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1)
self.gradient = GradientWidget(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth())
self.gradient.setSizePolicy(sizePolicy)
self.gradient.setObjectName("gradient")
self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1)
self.fpsLabel = QtWidgets.QLabel(self.centralwidget)
font = QtGui.QFont()
font.setPointSize(12)
self.fpsLabel.setFont(font)
self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter)
self.fpsLabel.setObjectName("fpsLabel")
self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4)
self.rgbCheck = QtWidgets.QCheckBox(self.centralwidget)
self.rgbCheck.setObjectName("rgbCheck")
self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1)
self.label_5 = QtWidgets.QLabel(self.centralwidget)
self.label_5.setObjectName("label_5")
self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.framesSpin = QtWidgets.QSpinBox(self.centralwidget)
self.framesSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
self.framesSpin.setProperty("value", 10)
self.framesSpin.setObjectName("framesSpin")
self.horizontalLayout_4.addWidget(self.framesSpin)
self.widthSpin = QtWidgets.QSpinBox(self.centralwidget)
self.widthSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.PlusMinus)
self.widthSpin.setMaximum(10000)
self.widthSpin.setProperty("value", 512)
self.widthSpin.setObjectName("widthSpin")
self.horizontalLayout_4.addWidget(self.widthSpin)
self.heightSpin = QtWidgets.QSpinBox(self.centralwidget)
self.heightSpin.setButtonSymbols(QtWidgets.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 = QtWidgets.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)
self.stack.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample"))
self.scaleCheck.setText(_translate("MainWindow", "Scale Data"))
self.rawRadio.setText(_translate("MainWindow", "RawImageWidget"))
self.gfxRadio.setText(_translate("MainWindow", "GraphicsView + ImageItem"))
self.rawGLRadio.setText(_translate("MainWindow", "RawGLImageWidget"))
self.dtypeCombo.setItemText(0, _translate("MainWindow", "uint8"))
self.dtypeCombo.setItemText(1, _translate("MainWindow", "uint16"))
self.dtypeCombo.setItemText(2, _translate("MainWindow", "float"))
self.label.setText(_translate("MainWindow", "Data type"))
self.rgbLevelsCheck.setText(_translate("MainWindow", "RGB"))
self.label_3.setText(_translate("MainWindow", "<--->"))
self.label_2.setText(_translate("MainWindow", "<--->"))
self.label_4.setText(_translate("MainWindow", "<--->"))
self.lutCheck.setText(_translate("MainWindow", "Use Lookup Table"))
self.alphaCheck.setText(_translate("MainWindow", "alpha"))
self.fpsLabel.setText(_translate("MainWindow", "FPS"))
self.rgbCheck.setText(_translate("MainWindow", "RGB"))
self.label_5.setText(_translate("MainWindow", "Image size"))
from pyqtgraph import GradientWidget, GraphicsView, SpinBox
from pyqtgraph.widgets.RawImageWidget import RawImageWidget

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './examples/VideoTemplate.ui' # Form implementation generated from reading ui file 'examples/VideoTemplate.ui'
# #
# Created: Mon Feb 17 20:39:30 2014 # Created: Wed Oct 26 09:21:01 2016
# by: pyside-uic 0.2.14 running on PySide 1.1.2 # by: pyside-uic 0.2.15 running on PySide 1.2.2
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@ -55,14 +55,6 @@ class Ui_MainWindow(object):
self.rawImg.setObjectName("rawImg") self.rawImg.setObjectName("rawImg")
self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1)
self.stack.addWidget(self.page_2) self.stack.addWidget(self.page_2)
self.page_3 = QtGui.QWidget()
self.page_3.setObjectName("page_3")
self.gridLayout_5 = QtGui.QGridLayout(self.page_3)
self.gridLayout_5.setObjectName("gridLayout_5")
self.rawGLImg = RawImageGLWidget(self.page_3)
self.rawGLImg.setObjectName("rawGLImg")
self.gridLayout_5.addWidget(self.rawGLImg, 0, 0, 1, 1)
self.stack.addWidget(self.page_3)
self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) self.gridLayout.addWidget(self.stack, 0, 0, 1, 1)
self.rawGLRadio = QtGui.QRadioButton(self.centralwidget) self.rawGLRadio = QtGui.QRadioButton(self.centralwidget)
self.rawGLRadio.setObjectName("rawGLRadio") self.rawGLRadio.setObjectName("rawGLRadio")
@ -179,7 +171,7 @@ class Ui_MainWindow(object):
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow) self.retranslateUi(MainWindow)
self.stack.setCurrentIndex(2) self.stack.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow): def retranslateUi(self, MainWindow):
@ -203,5 +195,5 @@ class Ui_MainWindow(object):
self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", 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)) self.label_5.setText(QtGui.QApplication.translate("MainWindow", "Image size", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget, RawImageWidget from pyqtgraph.widgets.RawImageWidget import RawImageWidget
from pyqtgraph import GradientWidget, SpinBox, GraphicsView from pyqtgraph import SpinBox, GradientWidget, GraphicsView

View File

@ -42,7 +42,7 @@ class movableRect(QtGui.QGraphicsRectItem):
self.setAcceptHoverEvents(True) self.setAcceptHoverEvents(True)
def hoverEnterEvent(self, ev): def hoverEnterEvent(self, ev):
self.savedPen = self.pen() self.savedPen = self.pen()
self.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255))) self.setPen(pg.mkPen(255, 255, 255))
ev.ignore() ev.ignore()
def hoverLeaveEvent(self, ev): def hoverLeaveEvent(self, ev):
self.setPen(self.savedPen) self.setPen(self.savedPen)
@ -57,7 +57,7 @@ class movableRect(QtGui.QGraphicsRectItem):
self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) self.setPos(self.mapToParent(ev.pos()) - self.pressDelta)
rect = movableRect(QtCore.QRectF(0, 0, 1, 1)) rect = movableRect(QtCore.QRectF(0, 0, 1, 1))
rect.setPen(QtGui.QPen(QtGui.QColor(100, 200, 100))) rect.setPen(pg.mkPen(100, 200, 100))
vb.addItem(rect) vb.addItem(rect)
l.addItem(vb, 0, 1) l.addItem(vb, 0, 1)

View File

@ -1,101 +1,23 @@
import sys, os, subprocess, time import sys, os
if __name__ == "__main__" and (__package__ is None or __package__==''): if __name__ == "__main__" and (__package__ is None or __package__==''):
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parent_dir) sys.path.insert(0, parent_dir)
import examples import examples
__package__ = "examples" __package__ = "examples"
from . import initExample
from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
import pyqtgraph as pg import pyqtgraph as pg
import subprocess
from pyqtgraph.python2_3 import basestring
from pyqtgraph.Qt import QtGui, USE_PYSIDE, USE_PYQT5
from .utils import buildFileList, testFile, path, examples
if USE_PYSIDE: if USE_PYSIDE:
from .exampleLoaderTemplate_pyside import Ui_Form from .exampleLoaderTemplate_pyside import Ui_Form
elif USE_PYQT5:
from .exampleLoaderTemplate_pyqt5 import Ui_Form
else: else:
from .exampleLoaderTemplate_pyqt import Ui_Form from .exampleLoaderTemplate_pyqt import Ui_Form
import os, sys
from pyqtgraph.pgcollections import OrderedDict
examples = OrderedDict([
('Command-line usage', 'CLIexample.py'),
('Basic Plotting', 'Plotting.py'),
('ImageView', 'ImageView.py'),
('ParameterTree', 'parametertree.py'),
('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'),
('GraphicsLayout', 'GraphicsLayout.py'),
('LegendItem', 'Legend.py'),
('Text Item', 'text.py'),
('Linked Views', 'linkedViews.py'),
('Arrow', 'Arrow.py'),
('ViewBox', 'ViewBox.py'),
('Custom Graphics', 'customGraphicsItem.py'),
])),
('Benchmarks', 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'),
('Isosurface', 'GLIsosurface.py'),
('Surface Plot', 'GLSurfacePlot.py'),
('Scatter Plot', 'GLScatterPlotItem.py'),
('Shaders', 'GLshaders.py'),
('Line Plot', 'GLLinePlotItem.py'),
('Mesh', 'GLMeshItem.py'),
('Image', 'GLImageItem.py'),
])),
('Widgets', OrderedDict([
('PlotWidget', 'PlotWidget.py'),
('SpinBox', 'SpinBox.py'),
('ConsoleWidget', 'ConsoleWidget.py'),
('Histogram / lookup table', 'HistogramLUT.py'),
('TreeWidget', 'TreeWidget.py'),
('DataTreeWidget', 'DataTreeWidget.py'),
('GradientWidget', 'GradientWidget.py'),
('TableWidget', 'TableWidget.py'),
('ColorButton', 'ColorButton.py'),
#('CheckTable', '../widgets/CheckTable.py'),
#('VerticalLabel', '../widgets/VerticalLabel.py'),
('JoystickButton', 'JoystickButton.py'),
])),
#('GraphicsScene', 'GraphicsScene.py'),
('Flowcharts', 'Flowchart.py'),
('Custom Flowchart Nodes', 'FlowchartCustomNode.py'),
#('Canvas', '../canvas'),
#('MultiPlotWidget', 'MultiPlotWidget.py'),
])
path = os.path.abspath(os.path.dirname(__file__))
class ExampleLoader(QtGui.QMainWindow): class ExampleLoader(QtGui.QMainWindow):
def __init__(self): def __init__(self):
@ -104,39 +26,28 @@ class ExampleLoader(QtGui.QMainWindow):
self.cw = QtGui.QWidget() self.cw = QtGui.QWidget()
self.setCentralWidget(self.cw) self.setCentralWidget(self.cw)
self.ui.setupUi(self.cw) self.ui.setupUi(self.cw)
self.codeBtn = QtGui.QPushButton('Run Edited Code') self.codeBtn = QtGui.QPushButton('Run Edited Code')
self.codeLayout = QtGui.QGridLayout() self.codeLayout = QtGui.QGridLayout()
self.ui.codeView.setLayout(self.codeLayout) self.ui.codeView.setLayout(self.codeLayout)
self.codeLayout.addItem(QtGui.QSpacerItem(100,100,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding), 0, 0) self.codeLayout.addItem(QtGui.QSpacerItem(100,100,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding), 0, 0)
self.codeLayout.addWidget(self.codeBtn, 1, 1) self.codeLayout.addWidget(self.codeBtn, 1, 1)
self.codeBtn.hide() self.codeBtn.hide()
global examples global examples
self.itemCache = [] self.itemCache = []
self.populateTree(self.ui.exampleTree.invisibleRootItem(), examples) self.populateTree(self.ui.exampleTree.invisibleRootItem(), examples)
self.ui.exampleTree.expandAll() self.ui.exampleTree.expandAll()
self.resize(1000,500) self.resize(1000,500)
self.show() self.show()
self.ui.splitter.setSizes([250,750]) self.ui.splitter.setSizes([250,750])
self.ui.loadBtn.clicked.connect(self.loadFile) self.ui.loadBtn.clicked.connect(self.loadFile)
self.ui.exampleTree.currentItemChanged.connect(self.showFile) self.ui.exampleTree.currentItemChanged.connect(self.showFile)
self.ui.exampleTree.itemDoubleClicked.connect(self.loadFile) self.ui.exampleTree.itemDoubleClicked.connect(self.loadFile)
self.ui.pyqtCheck.toggled.connect(self.pyqtToggled)
self.ui.pysideCheck.toggled.connect(self.pysideToggled)
self.ui.codeView.textChanged.connect(self.codeEdited) self.ui.codeView.textChanged.connect(self.codeEdited)
self.codeBtn.clicked.connect(self.runEditedCode) self.codeBtn.clicked.connect(self.runEditedCode)
def pyqtToggled(self, b):
if b:
self.ui.pysideCheck.setChecked(False)
def pysideToggled(self, b):
if b:
self.ui.pyqtCheck.setChecked(False)
def populateTree(self, root, examples): def populateTree(self, root, examples):
for key, val in examples.items(): for key, val in examples.items():
item = QtGui.QTreeWidgetItem([key]) item = QtGui.QTreeWidgetItem([key])
@ -148,32 +59,25 @@ class ExampleLoader(QtGui.QMainWindow):
else: else:
self.populateTree(item, val) self.populateTree(item, val)
root.addChild(item) root.addChild(item)
def currentFile(self): def currentFile(self):
item = self.ui.exampleTree.currentItem() item = self.ui.exampleTree.currentItem()
if hasattr(item, 'file'): if hasattr(item, 'file'):
global path global path
return os.path.join(path, item.file) return os.path.join(path, item.file)
return None return None
def loadFile(self, edited=False):
extra = []
if self.ui.pyqtCheck.isChecked():
extra.append('pyqt')
elif self.ui.pysideCheck.isChecked():
extra.append('pyside')
if self.ui.forceGraphicsCheck.isChecked():
extra.append(str(self.ui.forceGraphicsCombo.currentText()))
def loadFile(self, edited=False):
#if sys.platform.startswith('win'):
#os.spawnl(os.P_NOWAIT, sys.executable, '"'+sys.executable+'"', '"' + fn + '"', *extra) extra = []
#else: qtLib = str(self.ui.qtLibCombo.currentText())
#os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, fn, *extra) gfxSys = str(self.ui.graphicsSystemCombo.currentText())
if qtLib != 'default':
extra.append(qtLib.lower())
elif gfxSys != 'default':
extra.append(gfxSys)
if edited: if edited:
path = os.path.abspath(os.path.dirname(__file__)) path = os.path.abspath(os.path.dirname(__file__))
proc = subprocess.Popen([sys.executable, '-'] + extra, stdin=subprocess.PIPE, cwd=path) proc = subprocess.Popen([sys.executable, '-'] + extra, stdin=subprocess.PIPE, cwd=path)
@ -188,7 +92,7 @@ class ExampleLoader(QtGui.QMainWindow):
os.spawnl(os.P_NOWAIT, sys.executable, '"'+sys.executable+'"', '"' + fn + '"', *extra) os.spawnl(os.P_NOWAIT, sys.executable, '"'+sys.executable+'"', '"' + fn + '"', *extra)
else: else:
os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, fn, *extra) os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, fn, *extra)
def showFile(self): def showFile(self):
fn = self.currentFile() fn = self.currentFile()
if fn is None: if fn is None:
@ -200,106 +104,34 @@ class ExampleLoader(QtGui.QMainWindow):
self.ui.codeView.setPlainText(text) self.ui.codeView.setPlainText(text)
self.ui.loadedFileLabel.setText(fn) self.ui.loadedFileLabel.setText(fn)
self.codeBtn.hide() self.codeBtn.hide()
def codeEdited(self): def codeEdited(self):
self.codeBtn.show() self.codeBtn.show()
def runEditedCode(self): def runEditedCode(self):
self.loadFile(edited=True) self.loadFile(edited=True)
def run(): def run():
app = QtGui.QApplication([]) app = QtGui.QApplication([])
loader = ExampleLoader() loader = ExampleLoader()
app.exec_() app.exec_()
def buildFileList(examples, files=None):
if files == None:
files = []
for key, val in examples.items():
#item = QtGui.QTreeWidgetItem([key])
if isinstance(val, basestring):
#item.file = val
files.append((key,val))
else:
buildFileList(val, files)
return files
def testFile(name, f, exe, lib, graphicsSystem=None):
global path
fn = os.path.join(path,f)
#print "starting process: ", fn
os.chdir(path)
sys.stdout.write(name)
sys.stdout.flush()
import1 = "import %s" % lib if lib != '' else ''
import2 = os.path.splitext(os.path.split(fn)[1])[0]
graphicsSystem = '' if graphicsSystem is None else "pg.QtGui.QApplication.setGraphicsSystem('%s')" % graphicsSystem
code = """
try:
%s
import initExample
import pyqtgraph as pg
%s
import %s
import sys
print("test complete")
sys.stdout.flush()
import time
while True: ## run a little event loop
pg.QtGui.QApplication.processEvents()
time.sleep(0.01)
except:
print("test failed")
raise
""" % (import1, graphicsSystem, import2)
if sys.platform.startswith('win'):
process = subprocess.Popen([exe], stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
process.stdin.write(code.encode('UTF-8'))
process.stdin.close()
else:
process = subprocess.Popen(['exec %s -i' % (exe)], shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
process.stdin.write(code.encode('UTF-8'))
process.stdin.close() ##?
output = ''
fail = False
while True:
c = process.stdout.read(1).decode()
output += c
#sys.stdout.write(c)
#sys.stdout.flush()
if output.endswith('test complete'):
break
if output.endswith('test failed'):
fail = True
break
time.sleep(1)
process.kill()
#res = process.communicate()
res = (process.stdout.read(), process.stderr.read())
if fail or 'exception' in res[1].decode().lower() or 'error' in res[1].decode().lower():
print('.' * (50-len(name)) + 'FAILED')
print(res[0].decode())
print(res[1].decode())
else:
print('.' * (50-len(name)) + 'passed')
if __name__ == '__main__': if __name__ == '__main__':
if '--test' in sys.argv[1:]:
args = sys.argv[1:]
if '--test' in args:
# get rid of orphaned cache files first # get rid of orphaned cache files first
pg.renamePyc(path) pg.renamePyc(path)
files = buildFileList(examples) files = buildFileList(examples)
if '--pyside' in sys.argv[1:]: if '--pyside' in args:
lib = 'PySide' lib = 'PySide'
elif '--pyqt' in sys.argv[1:]: elif '--pyqt' in args or '--pyqt4' in args:
lib = 'PyQt4' lib = 'PyQt4'
elif '--pyqt5' in args:
lib = 'PyQt5'
else: else:
lib = '' lib = ''

View File

@ -6,28 +6,22 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>623</width> <width>846</width>
<height>380</height> <height>552</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout_2">
<property name="margin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QSplitter" name="splitter"> <widget class="QSplitter" name="splitter">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<widget class="QWidget" name=""> <widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QGridLayout" name="gridLayout">
<item> <item row="0" column="0" colspan="2">
<widget class="QTreeWidget" name="exampleTree"> <widget class="QTreeWidget" name="exampleTree">
<attribute name="headerVisible"> <attribute name="headerVisible">
<bool>false</bool> <bool>false</bool>
@ -39,55 +33,69 @@
</column> </column>
</widget> </widget>
</item> </item>
<item> <item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout"> <widget class="QComboBox" name="graphicsSystemCombo">
<item> <item>
<widget class="QCheckBox" name="pyqtCheck"> <property name="text">
<property name="text"> <string>default</string>
<string>Force PyQt</string> </property>
</property>
</widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="pysideCheck"> <property name="text">
<property name="text"> <string>native</string>
<string>Force PySide</string> </property>
</property>
</widget>
</item> </item>
</layout> <item>
<property name="text">
<string>raster</string>
</property>
</item>
<item>
<property name="text">
<string>opengl</string>
</property>
</item>
</widget>
</item> </item>
<item> <item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2"> <widget class="QComboBox" name="qtLibCombo">
<item> <item>
<widget class="QCheckBox" name="forceGraphicsCheck"> <property name="text">
<property name="text"> <string>default</string>
<string>Force Graphics System:</string> </property>
</property>
</widget>
</item> </item>
<item> <item>
<widget class="QComboBox" name="forceGraphicsCombo"> <property name="text">
<item> <string>PyQt4</string>
<property name="text"> </property>
<string>native</string>
</property>
</item>
<item>
<property name="text">
<string>raster</string>
</property>
</item>
<item>
<property name="text">
<string>opengl</string>
</property>
</item>
</widget>
</item> </item>
</layout> <item>
<property name="text">
<string>PySide</string>
</property>
</item>
<item>
<property name="text">
<string>PyQt5</string>
</property>
</item>
</widget>
</item> </item>
<item> <item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Graphics System:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Qt Library:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="loadBtn"> <widget class="QPushButton" name="loadBtn">
<property name="text"> <property name="text">
<string>Run Example</string> <string>Run Example</string>
@ -97,7 +105,7 @@
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name=""> <widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QLabel" name="loadedFileLabel"> <widget class="QLabel" name="loadedFileLabel">
<property name="font"> <property name="font">

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './exampleLoaderTemplate.ui' # Form implementation generated from reading ui file 'exampleLoaderTemplate.ui'
# #
# Created: Mon Feb 25 09:02:09 2013 # Created: Sat Feb 28 10:30:29 2015
# by: PyQt4 UI code generator 4.9.3 # by: PyQt4 UI code generator 4.10.4
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@ -12,58 +12,64 @@ from PyQt4 import QtCore, QtGui
try: try:
_fromUtf8 = QtCore.QString.fromUtf8 _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError: 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): class Ui_Form(object):
def setupUi(self, Form): def setupUi(self, Form):
Form.setObjectName(_fromUtf8("Form")) Form.setObjectName(_fromUtf8("Form"))
Form.resize(623, 380) Form.resize(846, 552)
self.gridLayout = QtGui.QGridLayout(Form) self.gridLayout_2 = QtGui.QGridLayout(Form)
self.gridLayout.setMargin(0) self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.gridLayout.setSpacing(0)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.splitter = QtGui.QSplitter(Form) self.splitter = QtGui.QSplitter(Form)
self.splitter.setOrientation(QtCore.Qt.Horizontal) self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName(_fromUtf8("splitter")) self.splitter.setObjectName(_fromUtf8("splitter"))
self.widget = QtGui.QWidget(self.splitter) self.widget = QtGui.QWidget(self.splitter)
self.widget.setObjectName(_fromUtf8("widget")) self.widget.setObjectName(_fromUtf8("widget"))
self.verticalLayout = QtGui.QVBoxLayout(self.widget) self.gridLayout = QtGui.QGridLayout(self.widget)
self.verticalLayout.setMargin(0) self.gridLayout.setMargin(0)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.exampleTree = QtGui.QTreeWidget(self.widget) self.exampleTree = QtGui.QTreeWidget(self.widget)
self.exampleTree.setObjectName(_fromUtf8("exampleTree")) self.exampleTree.setObjectName(_fromUtf8("exampleTree"))
self.exampleTree.headerItem().setText(0, _fromUtf8("1")) self.exampleTree.headerItem().setText(0, _fromUtf8("1"))
self.exampleTree.header().setVisible(False) self.exampleTree.header().setVisible(False)
self.verticalLayout.addWidget(self.exampleTree) self.gridLayout.addWidget(self.exampleTree, 0, 0, 1, 2)
self.horizontalLayout = QtGui.QHBoxLayout() self.graphicsSystemCombo = QtGui.QComboBox(self.widget)
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.graphicsSystemCombo.setObjectName(_fromUtf8("graphicsSystemCombo"))
self.pyqtCheck = QtGui.QCheckBox(self.widget) self.graphicsSystemCombo.addItem(_fromUtf8(""))
self.pyqtCheck.setObjectName(_fromUtf8("pyqtCheck")) self.graphicsSystemCombo.addItem(_fromUtf8(""))
self.horizontalLayout.addWidget(self.pyqtCheck) self.graphicsSystemCombo.addItem(_fromUtf8(""))
self.pysideCheck = QtGui.QCheckBox(self.widget) self.graphicsSystemCombo.addItem(_fromUtf8(""))
self.pysideCheck.setObjectName(_fromUtf8("pysideCheck")) self.gridLayout.addWidget(self.graphicsSystemCombo, 2, 1, 1, 1)
self.horizontalLayout.addWidget(self.pysideCheck) self.qtLibCombo = QtGui.QComboBox(self.widget)
self.verticalLayout.addLayout(self.horizontalLayout) self.qtLibCombo.setObjectName(_fromUtf8("qtLibCombo"))
self.horizontalLayout_2 = QtGui.QHBoxLayout() self.qtLibCombo.addItem(_fromUtf8(""))
self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) self.qtLibCombo.addItem(_fromUtf8(""))
self.forceGraphicsCheck = QtGui.QCheckBox(self.widget) self.qtLibCombo.addItem(_fromUtf8(""))
self.forceGraphicsCheck.setObjectName(_fromUtf8("forceGraphicsCheck")) self.qtLibCombo.addItem(_fromUtf8(""))
self.horizontalLayout_2.addWidget(self.forceGraphicsCheck) self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1)
self.forceGraphicsCombo = QtGui.QComboBox(self.widget) self.label_2 = QtGui.QLabel(self.widget)
self.forceGraphicsCombo.setObjectName(_fromUtf8("forceGraphicsCombo")) self.label_2.setObjectName(_fromUtf8("label_2"))
self.forceGraphicsCombo.addItem(_fromUtf8("")) self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
self.forceGraphicsCombo.addItem(_fromUtf8("")) self.label = QtGui.QLabel(self.widget)
self.forceGraphicsCombo.addItem(_fromUtf8("")) self.label.setObjectName(_fromUtf8("label"))
self.horizontalLayout_2.addWidget(self.forceGraphicsCombo) self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.loadBtn = QtGui.QPushButton(self.widget) self.loadBtn = QtGui.QPushButton(self.widget)
self.loadBtn.setObjectName(_fromUtf8("loadBtn")) self.loadBtn.setObjectName(_fromUtf8("loadBtn"))
self.verticalLayout.addWidget(self.loadBtn) self.gridLayout.addWidget(self.loadBtn, 3, 1, 1, 1)
self.widget1 = QtGui.QWidget(self.splitter) self.widget1 = QtGui.QWidget(self.splitter)
self.widget1.setObjectName(_fromUtf8("widget1")) self.widget1.setObjectName(_fromUtf8("widget1"))
self.verticalLayout_2 = QtGui.QVBoxLayout(self.widget1) self.verticalLayout = QtGui.QVBoxLayout(self.widget1)
self.verticalLayout_2.setMargin(0) self.verticalLayout.setMargin(0)
self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.loadedFileLabel = QtGui.QLabel(self.widget1) self.loadedFileLabel = QtGui.QLabel(self.widget1)
font = QtGui.QFont() font = QtGui.QFont()
font.setBold(True) font.setBold(True)
@ -72,25 +78,29 @@ class Ui_Form(object):
self.loadedFileLabel.setText(_fromUtf8("")) self.loadedFileLabel.setText(_fromUtf8(""))
self.loadedFileLabel.setAlignment(QtCore.Qt.AlignCenter) self.loadedFileLabel.setAlignment(QtCore.Qt.AlignCenter)
self.loadedFileLabel.setObjectName(_fromUtf8("loadedFileLabel")) self.loadedFileLabel.setObjectName(_fromUtf8("loadedFileLabel"))
self.verticalLayout_2.addWidget(self.loadedFileLabel) self.verticalLayout.addWidget(self.loadedFileLabel)
self.codeView = QtGui.QPlainTextEdit(self.widget1) self.codeView = QtGui.QPlainTextEdit(self.widget1)
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily(_fromUtf8("FreeMono")) font.setFamily(_fromUtf8("FreeMono"))
self.codeView.setFont(font) self.codeView.setFont(font)
self.codeView.setObjectName(_fromUtf8("codeView")) self.codeView.setObjectName(_fromUtf8("codeView"))
self.verticalLayout_2.addWidget(self.codeView) self.verticalLayout.addWidget(self.codeView)
self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1)
self.retranslateUi(Form) self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(_translate("Form", "Form", None))
self.pyqtCheck.setText(QtGui.QApplication.translate("Form", "Force PyQt", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(0, _translate("Form", "default", None))
self.pysideCheck.setText(QtGui.QApplication.translate("Form", "Force PySide", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(1, _translate("Form", "native", None))
self.forceGraphicsCheck.setText(QtGui.QApplication.translate("Form", "Force Graphics System:", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(2, _translate("Form", "raster", None))
self.forceGraphicsCombo.setItemText(0, QtGui.QApplication.translate("Form", "native", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(3, _translate("Form", "opengl", None))
self.forceGraphicsCombo.setItemText(1, QtGui.QApplication.translate("Form", "raster", None, QtGui.QApplication.UnicodeUTF8)) self.qtLibCombo.setItemText(0, _translate("Form", "default", None))
self.forceGraphicsCombo.setItemText(2, QtGui.QApplication.translate("Form", "opengl", None, QtGui.QApplication.UnicodeUTF8)) self.qtLibCombo.setItemText(1, _translate("Form", "PyQt4", None))
self.loadBtn.setText(QtGui.QApplication.translate("Form", "Run Example", None, QtGui.QApplication.UnicodeUTF8)) self.qtLibCombo.setItemText(2, _translate("Form", "PySide", None))
self.qtLibCombo.setItemText(3, _translate("Form", "PyQt5", None))
self.label_2.setText(_translate("Form", "Graphics System:", None))
self.label.setText(_translate("Form", "Qt Library:", None))
self.loadBtn.setText(_translate("Form", "Run Example", None))

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'exampleLoaderTemplate.ui'
#
# Created: Sat Feb 28 10:28:50 2015
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(846, 552)
self.gridLayout_2 = QtWidgets.QGridLayout(Form)
self.gridLayout_2.setObjectName("gridLayout_2")
self.splitter = QtWidgets.QSplitter(Form)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.widget = QtWidgets.QWidget(self.splitter)
self.widget.setObjectName("widget")
self.gridLayout = QtWidgets.QGridLayout(self.widget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.exampleTree = QtWidgets.QTreeWidget(self.widget)
self.exampleTree.setObjectName("exampleTree")
self.exampleTree.headerItem().setText(0, "1")
self.exampleTree.header().setVisible(False)
self.gridLayout.addWidget(self.exampleTree, 0, 0, 1, 2)
self.graphicsSystemCombo = QtWidgets.QComboBox(self.widget)
self.graphicsSystemCombo.setObjectName("graphicsSystemCombo")
self.graphicsSystemCombo.addItem("")
self.graphicsSystemCombo.addItem("")
self.graphicsSystemCombo.addItem("")
self.graphicsSystemCombo.addItem("")
self.gridLayout.addWidget(self.graphicsSystemCombo, 2, 1, 1, 1)
self.qtLibCombo = QtWidgets.QComboBox(self.widget)
self.qtLibCombo.setObjectName("qtLibCombo")
self.qtLibCombo.addItem("")
self.qtLibCombo.addItem("")
self.qtLibCombo.addItem("")
self.qtLibCombo.addItem("")
self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(self.widget)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
self.label = QtWidgets.QLabel(self.widget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
self.loadBtn = QtWidgets.QPushButton(self.widget)
self.loadBtn.setObjectName("loadBtn")
self.gridLayout.addWidget(self.loadBtn, 3, 1, 1, 1)
self.widget1 = QtWidgets.QWidget(self.splitter)
self.widget1.setObjectName("widget1")
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget1)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.loadedFileLabel = QtWidgets.QLabel(self.widget1)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.loadedFileLabel.setFont(font)
self.loadedFileLabel.setText("")
self.loadedFileLabel.setAlignment(QtCore.Qt.AlignCenter)
self.loadedFileLabel.setObjectName("loadedFileLabel")
self.verticalLayout.addWidget(self.loadedFileLabel)
self.codeView = QtWidgets.QPlainTextEdit(self.widget1)
font = QtGui.QFont()
font.setFamily("FreeMono")
self.codeView.setFont(font)
self.codeView.setObjectName("codeView")
self.verticalLayout.addWidget(self.codeView)
self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.graphicsSystemCombo.setItemText(0, _translate("Form", "default"))
self.graphicsSystemCombo.setItemText(1, _translate("Form", "native"))
self.graphicsSystemCombo.setItemText(2, _translate("Form", "raster"))
self.graphicsSystemCombo.setItemText(3, _translate("Form", "opengl"))
self.qtLibCombo.setItemText(0, _translate("Form", "default"))
self.qtLibCombo.setItemText(1, _translate("Form", "PyQt4"))
self.qtLibCombo.setItemText(2, _translate("Form", "PySide"))
self.qtLibCombo.setItemText(3, _translate("Form", "PyQt5"))
self.label_2.setText(_translate("Form", "Graphics System:"))
self.label.setText(_translate("Form", "Qt Library:"))
self.loadBtn.setText(_translate("Form", "Run Example"))

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './exampleLoaderTemplate.ui' # Form implementation generated from reading ui file 'exampleLoaderTemplate.ui'
# #
# Created: Mon Feb 25 09:02:09 2013 # Created: Sat Feb 28 10:31:57 2015
# by: pyside-uic 0.2.13 running on PySide 1.1.1 # by: pyside-uic 0.2.15 running on PySide 1.2.1
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@ -12,53 +12,50 @@ from PySide import QtCore, QtGui
class Ui_Form(object): class Ui_Form(object):
def setupUi(self, Form): def setupUi(self, Form):
Form.setObjectName("Form") Form.setObjectName("Form")
Form.resize(623, 380) Form.resize(846, 552)
self.gridLayout = QtGui.QGridLayout(Form) self.gridLayout_2 = QtGui.QGridLayout(Form)
self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setObjectName("gridLayout_2")
self.gridLayout.setSpacing(0)
self.gridLayout.setObjectName("gridLayout")
self.splitter = QtGui.QSplitter(Form) self.splitter = QtGui.QSplitter(Form)
self.splitter.setOrientation(QtCore.Qt.Horizontal) self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter") self.splitter.setObjectName("splitter")
self.widget = QtGui.QWidget(self.splitter) self.widget = QtGui.QWidget(self.splitter)
self.widget.setObjectName("widget") self.widget.setObjectName("widget")
self.verticalLayout = QtGui.QVBoxLayout(self.widget) self.gridLayout = QtGui.QGridLayout(self.widget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout") self.gridLayout.setObjectName("gridLayout")
self.exampleTree = QtGui.QTreeWidget(self.widget) self.exampleTree = QtGui.QTreeWidget(self.widget)
self.exampleTree.setObjectName("exampleTree") self.exampleTree.setObjectName("exampleTree")
self.exampleTree.headerItem().setText(0, "1") self.exampleTree.headerItem().setText(0, "1")
self.exampleTree.header().setVisible(False) self.exampleTree.header().setVisible(False)
self.verticalLayout.addWidget(self.exampleTree) self.gridLayout.addWidget(self.exampleTree, 0, 0, 1, 2)
self.horizontalLayout = QtGui.QHBoxLayout() self.graphicsSystemCombo = QtGui.QComboBox(self.widget)
self.horizontalLayout.setObjectName("horizontalLayout") self.graphicsSystemCombo.setObjectName("graphicsSystemCombo")
self.pyqtCheck = QtGui.QCheckBox(self.widget) self.graphicsSystemCombo.addItem("")
self.pyqtCheck.setObjectName("pyqtCheck") self.graphicsSystemCombo.addItem("")
self.horizontalLayout.addWidget(self.pyqtCheck) self.graphicsSystemCombo.addItem("")
self.pysideCheck = QtGui.QCheckBox(self.widget) self.graphicsSystemCombo.addItem("")
self.pysideCheck.setObjectName("pysideCheck") self.gridLayout.addWidget(self.graphicsSystemCombo, 2, 1, 1, 1)
self.horizontalLayout.addWidget(self.pysideCheck) self.qtLibCombo = QtGui.QComboBox(self.widget)
self.verticalLayout.addLayout(self.horizontalLayout) self.qtLibCombo.setObjectName("qtLibCombo")
self.horizontalLayout_2 = QtGui.QHBoxLayout() self.qtLibCombo.addItem("")
self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.qtLibCombo.addItem("")
self.forceGraphicsCheck = QtGui.QCheckBox(self.widget) self.qtLibCombo.addItem("")
self.forceGraphicsCheck.setObjectName("forceGraphicsCheck") self.qtLibCombo.addItem("")
self.horizontalLayout_2.addWidget(self.forceGraphicsCheck) self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1)
self.forceGraphicsCombo = QtGui.QComboBox(self.widget) self.label_2 = QtGui.QLabel(self.widget)
self.forceGraphicsCombo.setObjectName("forceGraphicsCombo") self.label_2.setObjectName("label_2")
self.forceGraphicsCombo.addItem("") self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
self.forceGraphicsCombo.addItem("") self.label = QtGui.QLabel(self.widget)
self.forceGraphicsCombo.addItem("") self.label.setObjectName("label")
self.horizontalLayout_2.addWidget(self.forceGraphicsCombo) self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.loadBtn = QtGui.QPushButton(self.widget) self.loadBtn = QtGui.QPushButton(self.widget)
self.loadBtn.setObjectName("loadBtn") self.loadBtn.setObjectName("loadBtn")
self.verticalLayout.addWidget(self.loadBtn) self.gridLayout.addWidget(self.loadBtn, 3, 1, 1, 1)
self.widget1 = QtGui.QWidget(self.splitter) self.widget1 = QtGui.QWidget(self.splitter)
self.widget1.setObjectName("widget1") self.widget1.setObjectName("widget1")
self.verticalLayout_2 = QtGui.QVBoxLayout(self.widget1) self.verticalLayout = QtGui.QVBoxLayout(self.widget1)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2") self.verticalLayout.setObjectName("verticalLayout")
self.loadedFileLabel = QtGui.QLabel(self.widget1) self.loadedFileLabel = QtGui.QLabel(self.widget1)
font = QtGui.QFont() font = QtGui.QFont()
font.setWeight(75) font.setWeight(75)
@ -67,25 +64,29 @@ class Ui_Form(object):
self.loadedFileLabel.setText("") self.loadedFileLabel.setText("")
self.loadedFileLabel.setAlignment(QtCore.Qt.AlignCenter) self.loadedFileLabel.setAlignment(QtCore.Qt.AlignCenter)
self.loadedFileLabel.setObjectName("loadedFileLabel") self.loadedFileLabel.setObjectName("loadedFileLabel")
self.verticalLayout_2.addWidget(self.loadedFileLabel) self.verticalLayout.addWidget(self.loadedFileLabel)
self.codeView = QtGui.QPlainTextEdit(self.widget1) self.codeView = QtGui.QPlainTextEdit(self.widget1)
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("FreeMono") font.setFamily("FreeMono")
self.codeView.setFont(font) self.codeView.setFont(font)
self.codeView.setObjectName("codeView") self.codeView.setObjectName("codeView")
self.verticalLayout_2.addWidget(self.codeView) self.verticalLayout.addWidget(self.codeView)
self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1)
self.retranslateUi(Form) self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.pyqtCheck.setText(QtGui.QApplication.translate("Form", "Force PyQt", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(0, QtGui.QApplication.translate("Form", "default", None, QtGui.QApplication.UnicodeUTF8))
self.pysideCheck.setText(QtGui.QApplication.translate("Form", "Force PySide", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(1, QtGui.QApplication.translate("Form", "native", None, QtGui.QApplication.UnicodeUTF8))
self.forceGraphicsCheck.setText(QtGui.QApplication.translate("Form", "Force Graphics System:", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(2, QtGui.QApplication.translate("Form", "raster", None, QtGui.QApplication.UnicodeUTF8))
self.forceGraphicsCombo.setItemText(0, QtGui.QApplication.translate("Form", "native", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(3, QtGui.QApplication.translate("Form", "opengl", None, QtGui.QApplication.UnicodeUTF8))
self.forceGraphicsCombo.setItemText(1, QtGui.QApplication.translate("Form", "raster", None, QtGui.QApplication.UnicodeUTF8)) self.qtLibCombo.setItemText(0, QtGui.QApplication.translate("Form", "default", None, QtGui.QApplication.UnicodeUTF8))
self.forceGraphicsCombo.setItemText(2, QtGui.QApplication.translate("Form", "opengl", None, QtGui.QApplication.UnicodeUTF8)) self.qtLibCombo.setItemText(1, QtGui.QApplication.translate("Form", "PyQt4", None, QtGui.QApplication.UnicodeUTF8))
self.qtLibCombo.setItemText(2, QtGui.QApplication.translate("Form", "PySide", None, QtGui.QApplication.UnicodeUTF8))
self.qtLibCombo.setItemText(3, QtGui.QApplication.translate("Form", "PyQt5", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("Form", "Graphics System:", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("Form", "Qt Library:", None, QtGui.QApplication.UnicodeUTF8))
self.loadBtn.setText(QtGui.QApplication.translate("Form", "Run Example", None, QtGui.QApplication.UnicodeUTF8)) self.loadBtn.setText(QtGui.QApplication.translate("Form", "Run Example", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -14,11 +14,11 @@ 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 initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg import sys, os
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np import numpy as np
import h5py import h5py
import sys, os import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
pg.mkQApp() pg.mkQApp()

View File

@ -12,8 +12,11 @@ import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import numpy as np import numpy as np
pg.mkQApp()
# Interpret image data as row-major instead of col-major
pg.setConfigOptions(imageAxisOrder='row-major')
pg.mkQApp()
win = pg.GraphicsLayoutWidget() win = pg.GraphicsLayoutWidget()
win.setWindowTitle('pyqtgraph example: Image Analysis') win.setWindowTitle('pyqtgraph example: Image Analysis')
@ -57,10 +60,10 @@ win.show()
# Generate image data # Generate image data
data = np.random.normal(size=(100, 200)) data = np.random.normal(size=(200, 100))
data[20:80, 20:80] += 2. data[20:80, 20:80] += 2.
data = pg.gaussianFilter(data, (3, 3)) data = pg.gaussianFilter(data, (3, 3))
data += np.random.normal(size=(100, 200)) * 0.1 data += np.random.normal(size=(200, 100)) * 0.1
img.setImage(data) img.setImage(data)
hist.setLevels(data.min(), data.max()) hist.setLevels(data.min(), data.max())
@ -79,7 +82,7 @@ p1.autoRange()
def updatePlot(): def updatePlot():
global img, roi, data, p2 global img, roi, data, p2
selected = roi.getArrayRegion(data, img) selected = roi.getArrayRegion(data, img)
p2.plot(selected.mean(axis=1), clear=True) p2.plot(selected.mean(axis=0), clear=True)
roi.sigRegionChanged.connect(updatePlot) roi.sigRegionChanged.connect(updatePlot)
updatePlot() updatePlot()

View File

@ -0,0 +1,52 @@
#!/usr/bin/python
import initExample ## Add path to library (just for examples; you do not need this)
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from pyqtgraph.ptime import time
app = QtGui.QApplication([])
p = pg.plot()
p.setWindowTitle('pyqtgraph performance: InfiniteLine')
p.setRange(QtCore.QRectF(0, -10, 5000, 20))
p.setLabel('bottom', 'Index', units='B')
curve = p.plot()
# Add a large number of horizontal InfiniteLine to plot
for i in range(100):
line = pg.InfiniteLine(pos=np.random.randint(5000), movable=True)
p.addItem(line)
data = np.random.normal(size=(50, 5000))
ptr = 0
lastTime = time()
fps = None
def update():
global curve, data, ptr, p, lastTime, fps
curve.setData(data[ptr % 10])
ptr += 1
now = time()
dt = now - lastTime
lastTime = now
if fps is None:
fps = 1.0/dt
else:
s = np.clip(dt*3., 0, 1)
fps = fps * (1-s) + (1.0/dt) * s
p.setTitle('%0.2f fps' % fps)
app.processEvents() # force complete redraw for every plot
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)
# Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

View File

@ -24,15 +24,23 @@ if 'pyside' in sys.argv:
from PySide import QtGui from PySide import QtGui
elif 'pyqt' in sys.argv: elif 'pyqt' in sys.argv:
from PyQt4 import QtGui from PyQt4 import QtGui
elif 'pyqt5' in sys.argv:
from PyQt5 import QtGui
else: else:
from pyqtgraph.Qt import QtGui from pyqtgraph.Qt import QtGui
import pyqtgraph as pg
## Force use of a specific graphics system ## Force use of a specific graphics system
use_gs = 'default'
for gs in ['raster', 'native', 'opengl']: for gs in ['raster', 'native', 'opengl']:
if gs in sys.argv: if gs in sys.argv:
use_gs = gs
QtGui.QApplication.setGraphicsSystem(gs) QtGui.QApplication.setGraphicsSystem(gs)
break break
print("Using %s (%s graphics system)" % (pg.Qt.QT_LIB, use_gs))
## Enable fault handling to give more helpful error messages on crash. ## Enable fault handling to give more helpful error messages on crash.
## Only available in python 3.3+ ## Only available in python 3.3+
try: try:

View File

@ -23,8 +23,8 @@ def plot():
pts = 100 pts = 100
x = np.linspace(0, 0.8, pts) x = np.linspace(0, 0.8, pts)
y = np.random.random(size=pts)*0.8 y = np.random.random(size=pts)*0.8
for i in xrange(n): for i in range(n):
for j in xrange(n): for j in range(n):
## calling PlotWidget.plot() generates a PlotDataItem, which ## calling PlotWidget.plot() generates a PlotDataItem, which
## has a bit more overhead than PlotCurveItem, which is all ## has a bit more overhead than PlotCurveItem, which is all
## we need here. This overhead adds up quickly and makes a big ## we need here. This overhead adds up quickly and makes a big

View File

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from PyQt4 import QtGui, QtCore
import pyqtgraph as pg import pyqtgraph as pg
#from pyqtgraph.canvas import Canvas, CanvasItem from pyqtgraph.Qt import QtGui, QtCore
import numpy as np import numpy as np
import csv, gzip, os import csv, gzip, os
from pyqtgraph import Point from pyqtgraph import Point
@ -90,7 +89,7 @@ def wlPen(wl):
return pen return pen
class ParamObj: class ParamObj(object):
# Just a helper for tracking parameters and responding to changes # Just a helper for tracking parameters and responding to changes
def __init__(self): def __init__(self):
self.__params = {} self.__params = {}
@ -110,7 +109,8 @@ class ParamObj:
pass pass
def __getitem__(self, item): def __getitem__(self, item):
return self.getParam(item) # bug in pyside 1.2.2 causes getitem to be called inside QGraphicsObject.parentItem:
return self.getParam(item) # PySide bug: https://bugreports.qt.io/browse/PYSIDE-441
def getParam(self, param): def getParam(self, param):
return self.__params[param] return self.__params[param]

View File

@ -1,9 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import initExample ## Add path to library (just for examples; you do not need this) import initExample ## Add path to library (just for examples; you do not need this)
import time
import numpy as np import numpy as np
import pyqtgraph.multiprocess as mp import pyqtgraph.multiprocess as mp
import pyqtgraph as pg import pyqtgraph as pg
import time from pyqtgraph.python2_3 import xrange
print( "\n=================\nParallelize") print( "\n=================\nParallelize")

View File

@ -124,7 +124,7 @@ p.sigTreeStateChanged.connect(change)
def valueChanging(param, value): def valueChanging(param, value):
print("Value changing (not finalized):", param, value) print("Value changing (not finalized): %s %s" % (param, value))
# Too lazy for recursion: # Too lazy for recursion:
for child in p.children(): for child in p.children():

View File

@ -1 +1 @@
from relativity import * from .relativity import *

View File

@ -1,13 +1,12 @@
import numpy as np
import collections
import sys, os
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.parametertree import Parameter, ParameterTree from pyqtgraph.parametertree import Parameter, ParameterTree
from pyqtgraph.parametertree import types as pTypes from pyqtgraph.parametertree import types as pTypes
import pyqtgraph.configfile import pyqtgraph.configfile
import numpy as np from pyqtgraph.python2_3 import xrange
import user
import collections
import sys, os
class RelativityGUI(QtGui.QWidget): class RelativityGUI(QtGui.QWidget):
@ -247,7 +246,7 @@ class GridParam(pTypes.GroupParameter):
template = self.param('ClockTemplate') template = self.param('ClockTemplate')
spacing = self['Spacing'] spacing = self['Spacing']
for i in range(self['Number of Clocks']): for i in range(self['Number of Clocks']):
c = template.buildClocks().values()[0] c = list(template.buildClocks().values())[0]
c.x0 += i * spacing c.x0 += i * spacing
clocks[self.name() + '%02d' % i] = c clocks[self.name() + '%02d' % i] = c
return clocks return clocks
@ -502,7 +501,7 @@ class Simulation:
def run(self): def run(self):
nPts = int(self.duration/self.dt)+1 nPts = int(self.duration/self.dt)+1
for cl in self.clocks.itervalues(): for cl in self.clocks.values():
cl.init(nPts) cl.init(nPts)
if self.ref is None: if self.ref is None:
@ -514,7 +513,7 @@ class Simulation:
clocks = self.clocks clocks = self.clocks
dt = self.dt dt = self.dt
tVals = np.linspace(0, dt*(nPts-1), nPts) tVals = np.linspace(0, dt*(nPts-1), nPts)
for cl in self.clocks.itervalues(): for cl in self.clocks.values():
for i in xrange(1,nPts): for i in xrange(1,nPts):
nextT = tVals[i] nextT = tVals[i]
while True: while True:
@ -549,7 +548,7 @@ class Simulation:
## make sure reference clock is not present in the list of clocks--this will be handled separately. ## make sure reference clock is not present in the list of clocks--this will be handled separately.
clocks = clocks.copy() clocks = clocks.copy()
for k,v in clocks.iteritems(): for k,v in clocks.items():
if v is ref: if v is ref:
del clocks[k] del clocks[k]
break break
@ -586,7 +585,7 @@ class Simulation:
## update all other clocks ## update all other clocks
for cl in clocks.itervalues(): for cl in clocks.values():
while True: while True:
g = cl.acceleration() g = cl.acceleration()
tau1, tau2 = cl.accelLimits() tau1, tau2 = cl.accelLimits()
@ -635,7 +634,7 @@ class Simulation:
def plot(self, plot): def plot(self, plot):
plot.clear() plot.clear()
for cl in self.clocks.itervalues(): for cl in self.clocks.values():
c, p = cl.getCurve() c, p = cl.getCurve()
plot.addItem(c) plot.addItem(c)
plot.addItem(p) plot.addItem(p)

View File

@ -21,7 +21,7 @@ curve1 = p1.plot(data1)
curve2 = p2.plot(data1) curve2 = p2.plot(data1)
ptr1 = 0 ptr1 = 0
def update1(): def update1():
global data1, curve1, ptr1 global data1, ptr1
data1[:-1] = data1[1:] # shift data in the array one sample left data1[:-1] = data1[1:] # shift data in the array one sample left
# (see also: np.roll) # (see also: np.roll)
data1[-1] = np.random.normal() data1[-1] = np.random.normal()

View File

@ -11,6 +11,8 @@ import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import numpy as np import numpy as np
app = QtGui.QApplication([])
# win.setWindowTitle('pyqtgraph example: ____') # win.setWindowTitle('pyqtgraph example: ____')
## Start Qt event loop unless running in interactive mode or using pyside. ## Start Qt event loop unless running in interactive mode or using pyside.

65
examples/test_examples.py Normal file
View File

@ -0,0 +1,65 @@
from __future__ import print_function, division, absolute_import
from pyqtgraph import Qt
from . import utils
import itertools
import pytest
import os, sys
# printing on travis ci frequently leads to "interrupted system call" errors.
# as a workaround, we overwrite the built-in print function (bleh)
if os.getenv('TRAVIS') is not None:
if sys.version_info[0] < 3:
import __builtin__ as builtins
else:
import builtins
def flaky_print(*args):
"""Wrapper for print that retries in case of IOError.
"""
count = 0
while count < 5:
count += 1
try:
orig_print(*args)
break
except IOError:
if count >= 5:
raise
pass
orig_print = builtins.print
builtins.print = flaky_print
print("Installed wrapper for flaky print.")
# apparently importlib does not exist in python 2.6...
try:
import importlib
except ImportError:
# we are on python 2.6
print("If you want to test the examples, please install importlib from "
"pypi\n\npip install importlib\n\n")
pass
files = utils.buildFileList(utils.examples)
frontends = {Qt.PYQT4: False, Qt.PYSIDE: False}
# sort out which of the front ends are available
for frontend in frontends.keys():
try:
importlib.import_module(frontend)
frontends[frontend] = True
except ImportError:
pass
@pytest.mark.parametrize(
"frontend, f", itertools.product(sorted(list(frontends.keys())), files))
def test_examples(frontend, f):
# Test the examples with all available front-ends
print('frontend = %s. f = %s' % (frontend, f))
if not frontends[frontend]:
pytest.skip('%s is not installed. Skipping tests' % frontend)
utils.testFile(f[0], f[1], utils.sys.executable, frontend)
if __name__ == "__main__":
pytest.cmdline.main()

View File

@ -23,7 +23,7 @@ plot.setWindowTitle('pyqtgraph example: text')
curve = plot.plot(x,y) ## add a single curve curve = plot.plot(x,y) ## add a single curve
## Create text object, use HTML tags to specify color/size ## Create text object, use HTML tags to specify color/size
text = pg.TextItem(html='<div style="text-align: center"><span style="color: #FFF;">This is the</span><br><span style="color: #FF0; font-size: 16pt;">PEAK</span></div>', anchor=(-0.3,1.3), border='w', fill=(0, 0, 255, 100)) text = pg.TextItem(html='<div style="text-align: center"><span style="color: #FFF;">This is the</span><br><span style="color: #FF0; font-size: 16pt;">PEAK</span></div>', anchor=(-0.3,0.5), angle=45, border='w', fill=(0, 0, 255, 100))
plot.addItem(text) plot.addItem(text)
text.setPos(0, y.max()) text.setPos(0, y.max())
@ -46,7 +46,6 @@ def update():
global curvePoint, index global curvePoint, index
index = (index + 1) % len(x) index = (index + 1) % len(x)
curvePoint.setPos(float(index)/(len(x)-1)) curvePoint.setPos(float(index)/(len(x)-1))
#text2.viewRangeChanged()
text2.setText('[%0.1f, %0.1f]' % (x[index], y[index])) text2.setText('[%0.1f, %0.1f]' % (x[index], y[index]))
timer = QtCore.QTimer() timer = QtCore.QTimer()

165
examples/utils.py Normal file
View File

@ -0,0 +1,165 @@
from __future__ import division, print_function, absolute_import
import subprocess
import time
import os
import sys
from pyqtgraph.pgcollections import OrderedDict
from pyqtgraph.python2_3 import basestring
path = os.path.abspath(os.path.dirname(__file__))
examples = OrderedDict([
('Command-line usage', 'CLIexample.py'),
('Basic Plotting', 'Plotting.py'),
('ImageView', 'ImageView.py'),
('ParameterTree', 'parametertree.py'),
('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'),
('Beeswarm plot', 'beeswarm.py'),
('Symbols', 'Symbols.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'),
('Bar Graph', 'BarGraphItem.py'),
('GraphicsLayout', 'GraphicsLayout.py'),
('LegendItem', 'Legend.py'),
('Text Item', 'text.py'),
('Linked Views', 'linkedViews.py'),
('Arrow', 'Arrow.py'),
('ViewBox', 'ViewBox.py'),
('Custom Graphics', 'customGraphicsItem.py'),
('Labeled Graph', 'CustomGraphItem.py'),
])),
('Benchmarks', 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'),
('Isosurface', 'GLIsosurface.py'),
('Surface Plot', 'GLSurfacePlot.py'),
('Scatter Plot', 'GLScatterPlotItem.py'),
('Shaders', 'GLshaders.py'),
('Line Plot', 'GLLinePlotItem.py'),
('Mesh', 'GLMeshItem.py'),
('Image', 'GLImageItem.py'),
])),
('Widgets', OrderedDict([
('PlotWidget', 'PlotWidget.py'),
('SpinBox', 'SpinBox.py'),
('ConsoleWidget', 'ConsoleWidget.py'),
('Histogram / lookup table', 'HistogramLUT.py'),
('TreeWidget', 'TreeWidget.py'),
('ScatterPlotWidget', 'ScatterPlotWidget.py'),
('DataTreeWidget', 'DataTreeWidget.py'),
('GradientWidget', 'GradientWidget.py'),
('TableWidget', 'TableWidget.py'),
('ColorButton', 'ColorButton.py'),
#('CheckTable', '../widgets/CheckTable.py'),
#('VerticalLabel', '../widgets/VerticalLabel.py'),
('JoystickButton', 'JoystickButton.py'),
])),
('Flowcharts', 'Flowchart.py'),
('Custom Flowchart Nodes', 'FlowchartCustomNode.py'),
])
def buildFileList(examples, files=None):
if files == None:
files = []
for key, val in examples.items():
#item = QtGui.QTreeWidgetItem([key])
if isinstance(val, basestring):
#item.file = val
files.append((key,val))
else:
buildFileList(val, files)
return files
def testFile(name, f, exe, lib, graphicsSystem=None):
global path
fn = os.path.join(path,f)
#print "starting process: ", fn
os.chdir(path)
sys.stdout.write(name)
sys.stdout.flush()
import1 = "import %s" % lib if lib != '' else ''
import2 = os.path.splitext(os.path.split(fn)[1])[0]
graphicsSystem = '' if graphicsSystem is None else "pg.QtGui.QApplication.setGraphicsSystem('%s')" % graphicsSystem
code = """
try:
%s
import initExample
import pyqtgraph as pg
%s
import %s
import sys
print("test complete")
sys.stdout.flush()
import time
while True: ## run a little event loop
pg.QtGui.QApplication.processEvents()
time.sleep(0.01)
except:
print("test failed")
raise
""" % (import1, graphicsSystem, import2)
if sys.platform.startswith('win'):
process = subprocess.Popen([exe], stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
process.stdin.write(code.encode('UTF-8'))
process.stdin.close()
else:
process = subprocess.Popen(['exec %s -i' % (exe)], shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
process.stdin.write(code.encode('UTF-8'))
process.stdin.close() ##?
output = ''
fail = False
while True:
c = process.stdout.read(1).decode()
output += c
#sys.stdout.write(c)
#sys.stdout.flush()
if output.endswith('test complete'):
break
if output.endswith('test failed'):
fail = True
break
time.sleep(1)
process.kill()
#res = process.communicate()
res = (process.stdout.read(), process.stderr.read())
if fail or 'exception' in res[1].decode().lower() or 'error' in res[1].decode().lower():
print('.' * (50-len(name)) + 'FAILED')
print(res[0].decode())
print(res[1].decode())
else:
print('.' * (50-len(name)) + 'passed')

View File

@ -1,7 +1,7 @@
import pyqtgraph as pg import pyqtgraph as pg
import numpy as np import numpy as np
import time import time
from .relax import relax from . import relax
class ChainSim(pg.QtCore.QObject): class ChainSim(pg.QtCore.QObject):
@ -52,7 +52,7 @@ class ChainSim(pg.QtCore.QObject):
self.mrel1[self.fixed[l2]] = 0 self.mrel1[self.fixed[l2]] = 0
self.mrel2 = 1.0 - self.mrel1 self.mrel2 = 1.0 - self.mrel1
for i in range(100): for i in range(10):
self.relax(n=10) self.relax(n=10)
self.initialized = True self.initialized = True
@ -75,6 +75,10 @@ class ChainSim(pg.QtCore.QObject):
else: else:
dt = now - self.lasttime dt = now - self.lasttime
self.lasttime = now self.lasttime = now
# limit amount of work to be done between frames
if not relax.COMPILED:
dt = self.maxTimeStep
if self.lastpos is None: if self.lastpos is None:
self.lastpos = self.pos self.lastpos = self.pos
@ -103,8 +107,9 @@ class ChainSim(pg.QtCore.QObject):
def relax(self, n=50): def relax(self, n=50):
# speed up with C magic # speed up with C magic if possible
relax(self.pos, self.links, self.mrel1, self.mrel2, self.lengths, self.push, self.pull, n) relax.relax(self.pos, self.links, self.mrel1, self.mrel2, self.lengths, self.push, self.pull, n)
self.relaxed.emit() self.relaxed.emit()

Binary file not shown.

View File

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

View File

@ -1,26 +1,38 @@
""" """
Mechanical simulation of a chain using verlet integration. 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 initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import numpy as np import numpy as np
from verlet_chain import ChainSim import verlet_chain
sim = ChainSim() sim = verlet_chain.ChainSim()
if verlet_chain.relax.COMPILED:
chlen1 = 80 # Use more complex chain if compiled mad library is available.
chlen2 = 60 chlen1 = 80
chlen2 = 60
linklen = 1
else:
chlen1 = 10
chlen2 = 8
linklen = 8
npts = chlen1 + chlen2 npts = chlen1 + chlen2
sim.mass = np.ones(npts) sim.mass = np.ones(npts)
sim.mass[chlen1-15] = 100 sim.mass[int(chlen1 * 0.8)] = 100
sim.mass[chlen1-1] = 500 sim.mass[chlen1-1] = 500
sim.mass[npts-1] = 200 sim.mass[npts-1] = 200
@ -31,8 +43,10 @@ sim.fixed[chlen1] = True
sim.pos = np.empty((npts, 2)) sim.pos = np.empty((npts, 2))
sim.pos[:chlen1, 0] = 0 sim.pos[:chlen1, 0] = 0
sim.pos[chlen1:, 0] = 10 sim.pos[chlen1:, 0] = 10
sim.pos[:chlen1, 1] = np.arange(chlen1) sim.pos[:chlen1, 1] = np.arange(chlen1) * linklen
sim.pos[chlen1:, 1] = np.arange(chlen2) 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)] 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)] links2 = [(j, i+j+1) for i in range(chlen2) for j in range(chlen2-i-1)]
@ -55,7 +69,8 @@ sim.push = np.concatenate([push1, push2, np.array([True], dtype=bool)])
sim.pull = np.ones(sim.links.shape[0], dtype=bool) sim.pull = np.ones(sim.links.shape[0], dtype=bool)
sim.pull[-1] = False sim.pull[-1] = False
mousepos = sim.pos[0] # move chain initially just to generate some motion if the mouse is not over the window
mousepos = np.array([30, 20])
def display(): def display():

View File

@ -1,12 +1,13 @@
from ..Qt import QtCore, QtGui
from ..python2_3 import sortList
import weakref import weakref
from ..Qt import QtCore, QtGui
from ..python2_3 import sortList, cmp
from ..Point import Point from ..Point import Point
from .. import functions as fn from .. import functions as fn
from .. import ptime as ptime from .. import ptime as ptime
from .mouseEvents import * from .mouseEvents import *
from .. import debug as debug from .. import debug as debug
if hasattr(QtCore, 'PYQT_VERSION'): if hasattr(QtCore, 'PYQT_VERSION'):
try: try:
import sip import sip
@ -84,8 +85,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj
def __init__(self, clickRadius=2, moveDistance=5): def __init__(self, clickRadius=2, moveDistance=5, parent=None):
QtGui.QGraphicsScene.__init__(self) QtGui.QGraphicsScene.__init__(self, parent)
self.setClickRadius(clickRadius) self.setClickRadius(clickRadius)
self.setMoveDistance(moveDistance) self.setMoveDistance(moveDistance)
self.exportDirectory = None self.exportDirectory = None
@ -97,6 +98,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
self.lastDrag = None self.lastDrag = None
self.hoverItems = weakref.WeakKeyDictionary() self.hoverItems = weakref.WeakKeyDictionary()
self.lastHoverEvent = None self.lastHoverEvent = None
self.minDragTime = 0.5 # drags shorter than 0.5 sec are interpreted as clicks
self.contextMenu = [QtGui.QAction("Export...", self)] self.contextMenu = [QtGui.QAction("Export...", self)]
self.contextMenu[0].triggered.connect(self.showExportDialog) self.contextMenu[0].triggered.connect(self.showExportDialog)
@ -133,7 +135,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
self._moveDistance = d self._moveDistance = d
def mousePressEvent(self, ev): def mousePressEvent(self, ev):
#print 'scenePress'
QtGui.QGraphicsScene.mousePressEvent(self, ev) QtGui.QGraphicsScene.mousePressEvent(self, ev)
if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events
if self.lastHoverEvent is not None: if self.lastHoverEvent is not None:
@ -171,8 +172,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
continue continue
if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet
cev = [e for e in self.clickEvents if int(e.button()) == int(btn)][0] cev = [e for e in self.clickEvents if int(e.button()) == int(btn)][0]
dist = Point(ev.screenPos() - cev.screenPos()) dist = Point(ev.scenePos() - cev.scenePos()).length()
if dist.length() < self._moveDistance and now - cev.time() < 0.5: if dist == 0 or (dist < self._moveDistance and now - cev.time() < self.minDragTime):
continue continue
init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True
self.dragButtons.append(int(btn)) self.dragButtons.append(int(btn))
@ -185,10 +186,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
def leaveEvent(self, ev): ## inform items that mouse is gone def leaveEvent(self, ev): ## inform items that mouse is gone
if len(self.dragButtons) == 0: if len(self.dragButtons) == 0:
self.sendHoverEvents(ev, exitOnly=True) self.sendHoverEvents(ev, exitOnly=True)
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
#print 'sceneRelease'
if self.mouseGrabberItem() is None: if self.mouseGrabberItem() is None:
if ev.button() in self.dragButtons: if ev.button() in self.dragButtons:
if self.sendDragEvent(ev, final=True): if self.sendDragEvent(ev, final=True):
@ -231,8 +230,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
prevItems = list(self.hoverItems.keys()) prevItems = list(self.hoverItems.keys())
#print "hover prev items:", prevItems
#print "hover test items:", items
for item in items: for item in items:
if hasattr(item, 'hoverEvent'): if hasattr(item, 'hoverEvent'):
event.currentItem = item event.currentItem = item
@ -247,7 +244,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
item.hoverEvent(event) item.hoverEvent(event)
except: except:
debug.printExc("Error sending hover event:") debug.printExc("Error sending hover event:")
event.enter = False event.enter = False
event.exit = True event.exit = True
#print "hover exit items:", prevItems #print "hover exit items:", prevItems

View File

@ -1,4 +1,4 @@
from ..Qt import QtCore, QtGui, USE_PYSIDE from ..Qt import QtCore, QtGui, USE_PYSIDE, USE_PYQT5
from .. import exporters as exporters from .. import exporters as exporters
from .. import functions as fn from .. import functions as fn
from ..graphicsItems.ViewBox import ViewBox from ..graphicsItems.ViewBox import ViewBox
@ -6,6 +6,8 @@ from ..graphicsItems.PlotItem import PlotItem
if USE_PYSIDE: if USE_PYSIDE:
from . import exportDialogTemplate_pyside as exportDialogTemplate from . import exportDialogTemplate_pyside as exportDialogTemplate
elif USE_PYQT5:
from . import exportDialogTemplate_pyqt5 as exportDialogTemplate
else: else:
from . import exportDialogTemplate_pyqt as exportDialogTemplate from . import exportDialogTemplate_pyqt as exportDialogTemplate
@ -137,5 +139,6 @@ class ExportDialog(QtGui.QWidget):
self.selectBox.setVisible(False) self.selectBox.setVisible(False)
self.setVisible(False) self.setVisible(False)
def closeEvent(self, event):
self.close()
QtGui.QWidget.closeEvent(self, event)

View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './pyqtgraph/GraphicsScene/exportDialogTemplate.ui'
#
# Created: Wed Mar 26 15:09:29 2014
# by: PyQt5 UI code generator 5.0.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(241, 367)
self.gridLayout = QtWidgets.QGridLayout(Form)
self.gridLayout.setSpacing(0)
self.gridLayout.setObjectName("gridLayout")
self.label = QtWidgets.QLabel(Form)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 3)
self.itemTree = QtWidgets.QTreeWidget(Form)
self.itemTree.setObjectName("itemTree")
self.itemTree.headerItem().setText(0, "1")
self.itemTree.header().setVisible(False)
self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3)
self.label_2 = QtWidgets.QLabel(Form)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3)
self.formatList = QtWidgets.QListWidget(Form)
self.formatList.setObjectName("formatList")
self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3)
self.exportBtn = QtWidgets.QPushButton(Form)
self.exportBtn.setObjectName("exportBtn")
self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1)
self.closeBtn = QtWidgets.QPushButton(Form)
self.closeBtn.setObjectName("closeBtn")
self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1)
self.paramTree = ParameterTree(Form)
self.paramTree.setObjectName("paramTree")
self.paramTree.headerItem().setText(0, "1")
self.paramTree.header().setVisible(False)
self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3)
self.label_3 = QtWidgets.QLabel(Form)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3)
self.copyBtn = QtWidgets.QPushButton(Form)
self.copyBtn.setObjectName("copyBtn")
self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Export"))
self.label.setText(_translate("Form", "Item to export:"))
self.label_2.setText(_translate("Form", "Export format"))
self.exportBtn.setText(_translate("Form", "Export"))
self.closeBtn.setText(_translate("Form", "Close"))
self.label_3.setText(_translate("Form", "Export options"))
self.copyBtn.setText(_translate("Form", "Copy"))
from ..parametertree import ParameterTree

View File

@ -276,8 +276,6 @@ class HoverEvent(object):
self._modifiers = moveEvent.modifiers() self._modifiers = moveEvent.modifiers()
else: else:
self.exit = True self.exit = True
def isEnter(self): def isEnter(self):
"""Returns True if the mouse has just entered the item's shape""" """Returns True if the mouse has just entered the item's shape"""

View File

@ -4,37 +4,58 @@ This module exists to smooth out some of the differences between PySide and PyQt
* Automatically import either PyQt4 or PySide depending on availability * Automatically import either PyQt4 or PySide depending on availability
* Allow to import QtCore/QtGui pyqtgraph.Qt without specifying which Qt wrapper * Allow to import QtCore/QtGui pyqtgraph.Qt without specifying which Qt wrapper
you want to use. you want to use.
* Declare QtCore.Signal, .Slot in PyQt4 * Declare QtCore.Signal, .Slot in PyQt4
* Declare loadUiType function for Pyside * Declare loadUiType function for Pyside
""" """
import sys, re import os, sys, re, time
from .python2_3 import asUnicode from .python2_3 import asUnicode
## Automatically determine whether to use PyQt or PySide. PYSIDE = 'PySide'
PYQT4 = 'PyQt4'
PYQT5 = 'PyQt5'
QT_LIB = os.getenv('PYQTGRAPH_QT_LIB')
## Automatically determine whether to use PyQt or PySide (unless specified by
## environment variable).
## This is done by first checking to see whether one of the libraries ## 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. ## is already imported. If not, then attempt to import PyQt4, then PySide.
if 'PyQt4' in sys.modules: if QT_LIB is None:
USE_PYSIDE = False libOrder = [PYQT4, PYSIDE, PYQT5]
elif 'PySide' in sys.modules:
USE_PYSIDE = True
else:
try:
import PyQt4
USE_PYSIDE = False
except ImportError:
try:
import PySide
USE_PYSIDE = True
except ImportError:
raise Exception("PyQtGraph requires either PyQt4 or PySide; neither package could be imported.")
if USE_PYSIDE: for lib in libOrder:
if lib in sys.modules:
QT_LIB = lib
break
if QT_LIB is None:
for lib in libOrder:
try:
__import__(lib)
QT_LIB = lib
break
except ImportError:
pass
if QT_LIB is None:
raise Exception("PyQtGraph requires one of PyQt4, PyQt5 or PySide; none of these packages could be imported.")
if QT_LIB == PYSIDE:
from PySide import QtGui, QtCore, QtOpenGL, QtSvg from PySide import QtGui, QtCore, QtOpenGL, QtSvg
try: try:
from PySide import QtTest from PySide import QtTest
if not hasattr(QtTest.QTest, 'qWait'):
@staticmethod
def qWait(msec):
start = time.time()
QtGui.QApplication.processEvents()
while time.time() < start + msec * 0.001:
QtGui.QApplication.processEvents()
QtTest.QTest.qWait = qWait
except ImportError: except ImportError:
pass pass
import PySide import PySide
@ -59,7 +80,7 @@ if USE_PYSIDE:
# Make a loadUiType function like PyQt has # Make a loadUiType function like PyQt has
# Credit: # Credit:
# http://stackoverflow.com/questions/4442286/python-code-genration-with-pyside-uic/14195313#14195313 # http://stackoverflow.com/questions/4442286/python-code-genration-with-pyside-uic/14195313#14195313
class StringIO(object): class StringIO(object):
@ -75,7 +96,15 @@ if USE_PYSIDE:
def loadUiType(uiFile): 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. 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.
from stackoverflow: http://stackoverflow.com/a/14195313/3781327
seems like this might also be a legitimate solution, but I'm not sure
how to make PyQt4 and pyside look the same...
http://stackoverflow.com/a/8717832
""" """
import pysideuic import pysideuic
import xml.etree.ElementTree as xml import xml.etree.ElementTree as xml
@ -98,9 +127,9 @@ if USE_PYSIDE:
base_class = eval('QtGui.%s'%widget_class) base_class = eval('QtGui.%s'%widget_class)
return form_class, base_class return form_class, base_class
elif QT_LIB == PYQT4:
else:
from PyQt4 import QtGui, QtCore, uic from PyQt4 import QtGui, QtCore, uic
try: try:
from PyQt4 import QtSvg from PyQt4 import QtSvg
@ -115,21 +144,98 @@ else:
except ImportError: except ImportError:
pass pass
VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
elif QT_LIB == PYQT5:
# We're using PyQt5 which has a different structure so we're going to use a shim to
# recreate the Qt4 structure for Qt5
from PyQt5 import QtGui, QtCore, QtWidgets, uic
try:
from PyQt5 import QtSvg
except ImportError:
pass
try:
from PyQt5 import QtOpenGL
except ImportError:
pass
try:
from PyQt5 import QtTest
QtTest.QTest.qWaitForWindowShown = QtTest.QTest.qWaitForWindowExposed
except ImportError:
pass
# Re-implement deprecated APIs
__QGraphicsItem_scale = QtWidgets.QGraphicsItem.scale
def scale(self, *args):
if args:
sx, sy = args
tr = self.transform()
tr.scale(sx, sy)
self.setTransform(tr)
else:
return __QGraphicsItem_scale(self)
QtWidgets.QGraphicsItem.scale = scale
def rotate(self, angle):
tr = self.transform()
tr.rotate(angle)
self.setTransform(tr)
QtWidgets.QGraphicsItem.rotate = rotate
def translate(self, dx, dy):
tr = self.transform()
tr.translate(dx, dy)
self.setTransform(tr)
QtWidgets.QGraphicsItem.translate = translate
def setMargin(self, i):
self.setContentsMargins(i, i, i, i)
QtWidgets.QGridLayout.setMargin = setMargin
def setResizeMode(self, *args):
self.setSectionResizeMode(*args)
QtWidgets.QHeaderView.setResizeMode = setResizeMode
QtGui.QApplication = QtWidgets.QApplication
QtGui.QGraphicsScene = QtWidgets.QGraphicsScene
QtGui.QGraphicsObject = QtWidgets.QGraphicsObject
QtGui.QGraphicsWidget = QtWidgets.QGraphicsWidget
QtGui.QApplication.setGraphicsSystem = None
# Import all QtWidgets objects into QtGui
for o in dir(QtWidgets):
if o.startswith('Q'):
setattr(QtGui, o, getattr(QtWidgets,o) )
VERSION_INFO = 'PyQt5 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
else:
raise ValueError("Invalid Qt lib '%s'" % QT_LIB)
# Common to PyQt4 and 5
if QT_LIB.startswith('PyQt'):
import sip import sip
def isQObjectAlive(obj): def isQObjectAlive(obj):
return not sip.isdeleted(obj) return not sip.isdeleted(obj)
loadUiType = uic.loadUiType loadUiType = uic.loadUiType
QtCore.Signal = QtCore.pyqtSignal QtCore.Signal = QtCore.pyqtSignal
VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
## Make sure we have Qt >= 4.7 ## Make sure we have Qt >= 4.7
versionReq = [4, 7] versionReq = [4, 7]
QtVersion = PySide.QtCore.__version__ if USE_PYSIDE else QtCore.QT_VERSION_STR USE_PYSIDE = QT_LIB == PYSIDE
USE_PYQT4 = QT_LIB == PYQT4
USE_PYQT5 = QT_LIB == PYQT5
QtVersion = PySide.QtCore.__version__ if QT_LIB == PYSIDE else QtCore.QT_VERSION_STR
m = re.match(r'(\d+)\.(\d+).*', QtVersion) m = re.match(r'(\d+)\.(\d+).*', QtVersion)
if m is not None and list(map(int, m.groups())) < versionReq: if m is not None and list(map(int, m.groups())) < versionReq:
print(list(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)) raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion))

View File

@ -3,6 +3,7 @@ from .Qt import QtCore, QtGui
from .Point import Point from .Point import Point
import numpy as np import numpy as np
class SRTTransform(QtGui.QTransform): class SRTTransform(QtGui.QTransform):
"""Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate """Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate
This transform has no shear; angles are always preserved. This transform has no shear; angles are always preserved.
@ -165,6 +166,7 @@ class SRTTransform(QtGui.QTransform):
def matrix(self): def matrix(self):
return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]]) return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]])
if __name__ == '__main__': if __name__ == '__main__':
from . import widgets from . import widgets

View File

@ -8,7 +8,7 @@ This class addresses the problem of having to save and restore the state
of a large group of widgets. of a large group of widgets.
""" """
from .Qt import QtCore, QtGui from .Qt import QtCore, QtGui, USE_PYQT5
import weakref, inspect import weakref, inspect
from .python2_3 import asUnicode from .python2_3 import asUnicode
@ -60,9 +60,13 @@ def setComboState(w, v):
class WidgetGroup(QtCore.QObject): class WidgetGroup(QtCore.QObject):
"""This class takes a list of widgets and keeps an internal record of their state which is always up to date. Allows reading and writing from groups of widgets simultaneously.""" """This class takes a list of widgets and keeps an internal record of their
state that is always up to date.
## List of widget types which can be handled by WidgetGroup. Allows reading and writing from groups of widgets simultaneously.
"""
## List of widget types that can be handled by WidgetGroup.
## The value for each type is a tuple (change signal function, get function, set function, [auto-add children]) ## The value for each type is a tuple (change signal function, get function, set function, [auto-add children])
## The change signal function that takes an object and returns a signal that is emitted any time the state of the widget changes, not just ## The change signal function that takes an object and returns a signal that is emitted any time the state of the widget changes, not just
## when it is changed by user interaction. (for example, 'clicked' is not a valid signal here) ## when it is changed by user interaction. (for example, 'clicked' is not a valid signal here)
@ -200,51 +204,35 @@ class WidgetGroup(QtCore.QObject):
if hasattr(obj, 'widgetGroupInterface'): if hasattr(obj, 'widgetGroupInterface'):
return True return True
return False return False
#return (type(obj) in WidgetGroup.classes)
def setScale(self, widget, scale): def setScale(self, widget, scale):
val = self.readWidget(widget) val = self.readWidget(widget)
self.scales[widget] = scale self.scales[widget] = scale
self.setWidget(widget, val) self.setWidget(widget, val)
#print "scaling %f to %f" % (val, self.readWidget(widget))
def mkChangeCallback(self, w): def mkChangeCallback(self, w):
return lambda *args: self.widgetChanged(w, *args) return lambda *args: self.widgetChanged(w, *args)
def widgetChanged(self, w, *args): def widgetChanged(self, w, *args):
#print "widget changed"
n = self.widgetList[w] n = self.widgetList[w]
v1 = self.cache[n] v1 = self.cache[n]
v2 = self.readWidget(w) v2 = self.readWidget(w)
if v1 != v2: if v1 != v2:
#print "widget", n, " = ", v2 if not USE_PYQT5:
self.emit(QtCore.SIGNAL('changed'), self.widgetList[w], v2) # Old signal kept for backward compatibility.
self.emit(QtCore.SIGNAL('changed'), self.widgetList[w], v2)
self.sigChanged.emit(self.widgetList[w], v2) self.sigChanged.emit(self.widgetList[w], v2)
def state(self): def state(self):
for w in self.uncachedWidgets: for w in self.uncachedWidgets:
self.readWidget(w) self.readWidget(w)
#cc = self.cache.copy()
#if 'averageGroup' in cc:
#val = cc['averageGroup']
#w = self.findWidget('averageGroup')
#self.readWidget(w)
#if val != self.cache['averageGroup']:
#print " AverageGroup did not match cached value!"
#else:
#print " AverageGroup OK"
return self.cache.copy() return self.cache.copy()
def setState(self, s): def setState(self, s):
#print "SET STATE", self, s
for w in self.widgetList: for w in self.widgetList:
n = self.widgetList[w] n = self.widgetList[w]
#print " restore %s?" % n
if n not in s: if n not in s:
continue continue
#print " restore state", w, n, s[n]
self.setWidget(w, s[n]) self.setWidget(w, s[n])
def readWidget(self, w): def readWidget(self, w):

View File

@ -4,7 +4,7 @@ PyQtGraph - Scientific Graphics and GUI Library for Python
www.pyqtgraph.org www.pyqtgraph.org
""" """
__version__ = '0.9.8' __version__ = '0.10.0'
### import all the goodies and add some helper functions for easy CLI use ### import all the goodies and add some helper functions for easy CLI use
@ -41,13 +41,15 @@ elif 'darwin' in sys.platform: ## openGL can have a major impact on mac, but als
useOpenGL = False useOpenGL = False
if QtGui.QApplication.instance() is not None: if QtGui.QApplication.instance() is not None:
print('Warning: QApplication was created before pyqtgraph was imported; there may be problems (to avoid bugs, call QApplication.setGraphicsSystem("raster") before the QApplication is created).') print('Warning: QApplication was created before pyqtgraph was imported; there may be problems (to avoid bugs, call QApplication.setGraphicsSystem("raster") before the QApplication is created).')
QtGui.QApplication.setGraphicsSystem('raster') ## work around a variety of bugs in the native graphics system if QtGui.QApplication.setGraphicsSystem:
QtGui.QApplication.setGraphicsSystem('raster') ## work around a variety of bugs in the native graphics system
else: else:
useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff. useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff.
CONFIG_OPTIONS = { CONFIG_OPTIONS = {
'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl. 'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl.
'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox 'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox
# foreground/background take any arguments to the 'mkColor' in /pyqtgraph/functions.py
'foreground': 'd', ## default foreground color for axes, labels, etc. 'foreground': 'd', ## default foreground color for axes, labels, etc.
'background': 'k', ## default background for GraphicsWidget 'background': 'k', ## default background for GraphicsWidget
'antialias': False, 'antialias': False,
@ -57,16 +59,32 @@ CONFIG_OPTIONS = {
'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide 'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide
'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code) 'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code)
'crashWarning': False, # If True, print warnings about situations that may result in a crash 'crashWarning': False, # If True, print warnings about situations that may result in a crash
'imageAxisOrder': 'col-major', # For 'row-major', image data is expected in the standard (row, col) order.
# For 'col-major', image data is expected in reversed (col, row) order.
# The default is 'col-major' for backward compatibility, but this may
# change in the future.
} }
def setConfigOption(opt, value): def setConfigOption(opt, value):
global CONFIG_OPTIONS
if opt not in CONFIG_OPTIONS:
raise KeyError('Unknown configuration option "%s"' % opt)
if opt == 'imageAxisOrder' and value not in ('row-major', 'col-major'):
raise ValueError('imageAxisOrder must be either "row-major" or "col-major"')
CONFIG_OPTIONS[opt] = value CONFIG_OPTIONS[opt] = value
def setConfigOptions(**opts): def setConfigOptions(**opts):
CONFIG_OPTIONS.update(opts) """Set global configuration options.
Each keyword argument sets one global option.
"""
for k,v in opts.items():
setConfigOption(k, v)
def getConfigOption(opt): def getConfigOption(opt):
"""Return the value of a single global configuration option.
"""
return CONFIG_OPTIONS[opt] return CONFIG_OPTIONS[opt]
@ -271,7 +289,12 @@ from .Qt import isQObjectAlive
## Attempts to work around exit crashes: ## Attempts to work around exit crashes:
import atexit import atexit
_cleanupCalled = False
def cleanup(): def cleanup():
global _cleanupCalled
if _cleanupCalled:
return
if not getConfigOption('exitCleanup'): if not getConfigOption('exitCleanup'):
return return
@ -281,7 +304,10 @@ def cleanup():
## ALL QGraphicsItems must have a scene before they are deleted. ## ALL QGraphicsItems must have a scene before they are deleted.
## This is potentially very expensive, but preferred over crashing. ## This is potentially very expensive, but preferred over crashing.
## Note: this appears to be fixed in PySide as of 2012.12, but it should be left in for a while longer.. ## Note: this appears to be fixed in PySide as of 2012.12, but it should be left in for a while longer..
if QtGui.QApplication.instance() is None: app = QtGui.QApplication.instance()
if app is None or not isinstance(app, QtGui.QApplication):
# app was never constructed is already deleted or is an
# QCoreApplication/QGuiApplication and not a full QApplication
return return
import gc import gc
s = QtGui.QGraphicsScene() s = QtGui.QGraphicsScene()
@ -296,8 +322,22 @@ def cleanup():
s.addItem(o) s.addItem(o)
except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object
continue continue
_cleanupCalled = True
atexit.register(cleanup) atexit.register(cleanup)
# Call cleanup when QApplication quits. This is necessary because sometimes
# the QApplication will quit before the atexit callbacks are invoked.
# Note: cannot connect this function until QApplication has been created, so
# instead we have GraphicsView.__init__ call this for us.
_cleanupConnected = False
def _connectCleanup():
global _cleanupConnected
if _cleanupConnected:
return
QtGui.QApplication.instance().aboutToQuit.connect(cleanup)
_cleanupConnected = True
## Optional function for exiting immediately (with some manual teardown) ## Optional function for exiting immediately (with some manual teardown)
def exit(): def exit():
@ -327,7 +367,7 @@ def exit():
## close file handles ## close file handles
if sys.platform == 'darwin': if sys.platform == 'darwin':
for fd in xrange(3, 4096): for fd in range(3, 4096):
if fd not in [7]: # trying to close 7 produces an illegal instruction on the Mac. if fd not in [7]: # trying to close 7 produces an illegal instruction on the Mac.
os.close(fd) os.close(fd)
else: else:

View File

@ -4,15 +4,17 @@ if __name__ == '__main__':
md = os.path.dirname(os.path.abspath(__file__)) md = os.path.dirname(os.path.abspath(__file__))
sys.path = [os.path.dirname(md), os.path.join(md, '..', '..', '..')] + sys.path sys.path = [os.path.dirname(md), os.path.join(md, '..', '..', '..')] + sys.path
from ..Qt import QtGui, QtCore, USE_PYSIDE from ..Qt import QtGui, QtCore, QT_LIB
from ..graphicsItems.ROI import ROI from ..graphicsItems.ROI import ROI
from ..graphicsItems.ViewBox import ViewBox from ..graphicsItems.ViewBox import ViewBox
from ..graphicsItems.GridItem import GridItem from ..graphicsItems.GridItem import GridItem
if USE_PYSIDE: if QT_LIB == 'PySide':
from .CanvasTemplate_pyside import * from .CanvasTemplate_pyside import *
else: elif QT_LIB == 'PyQt4':
from .CanvasTemplate_pyqt import * from .CanvasTemplate_pyqt import *
elif QT_LIB == 'PyQt5':
from .CanvasTemplate_pyqt5 import *
import numpy as np import numpy as np
from .. import debug from .. import debug
@ -378,7 +380,7 @@ class Canvas(QtGui.QWidget):
z = citem.zValue() z = citem.zValue()
if z is None: if z is None:
zvals = [i.zValue() for i in siblings] zvals = [i.zValue() for i in siblings]
if parent == self.itemList.invisibleRootItem(): if parent is self.itemList.invisibleRootItem():
if len(zvals) == 0: if len(zvals) == 0:
z = 0 z = 0
else: else:

View File

@ -1,11 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from ..Qt import QtGui, QtCore, QtSvg, USE_PYSIDE from ..Qt import QtGui, QtCore, QtSvg, QT_LIB
from ..graphicsItems.ROI import ROI from ..graphicsItems.ROI import ROI
from .. import SRTTransform, ItemGroup from .. import SRTTransform, ItemGroup
if USE_PYSIDE: if QT_LIB == 'PySide':
from . import TransformGuiTemplate_pyside as TransformGuiTemplate from . import TransformGuiTemplate_pyside as TransformGuiTemplate
else: elif QT_LIB == 'PyQt4':
from . import TransformGuiTemplate_pyqt as TransformGuiTemplate from . import TransformGuiTemplate_pyqt as TransformGuiTemplate
elif QT_LIB == 'PyQt5':
from . import TransformGuiTemplate_pyqt5 as TransformGuiTemplate
from .. import debug from .. import debug

View File

@ -127,7 +127,7 @@
<customwidget> <customwidget>
<class>CanvasCombo</class> <class>CanvasCombo</class>
<extends>QComboBox</extends> <extends>QComboBox</extends>
<header>CanvasManager</header> <header>.CanvasManager</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>

View File

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'acq4/pyqtgraph/canvas/CanvasTemplate.ui' # Form implementation generated from reading ui file 'pyqtgraph/canvas/CanvasTemplate.ui'
# #
# Created: Thu Jan 2 11:13:07 2014 # Created by: PyQt4 UI code generator 4.11.4
# by: PyQt4 UI code generator 4.9
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@ -12,7 +11,16 @@ from PyQt4 import QtCore, QtGui
try: try:
_fromUtf8 = QtCore.QString.fromUtf8 _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError: 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): class Ui_Form(object):
def setupUi(self, Form): def setupUi(self, Form):
@ -30,7 +38,6 @@ class Ui_Form(object):
self.layoutWidget = QtGui.QWidget(self.splitter) self.layoutWidget = QtGui.QWidget(self.splitter)
self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) self.layoutWidget.setObjectName(_fromUtf8("layoutWidget"))
self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget) self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget)
self.gridLayout_2.setMargin(0)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget) self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
@ -79,14 +86,14 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(_translate("Form", "Form", None))
self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8)) self.autoRangeBtn.setText(_translate("Form", "Auto Range", None))
self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8)) self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas.", None))
self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8)) self.redirectCheck.setText(_translate("Form", "Redirect", None))
self.resetTransformsBtn.setText(QtGui.QApplication.translate("Form", "Reset Transforms", None, QtGui.QApplication.UnicodeUTF8)) self.resetTransformsBtn.setText(_translate("Form", "Reset Transforms", None))
self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8)) self.mirrorSelectionBtn.setText(_translate("Form", "Mirror Selection", None))
self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8)) self.reflectSelectionBtn.setText(_translate("Form", "MirrorXY", None))
from ..widgets.TreeWidget import TreeWidget
from CanvasManager import CanvasCombo
from ..widgets.GraphicsView import GraphicsView from ..widgets.GraphicsView import GraphicsView
from ..widgets.TreeWidget import TreeWidget
from .CanvasManager import CanvasCombo

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'pyqtgraph/canvas/CanvasTemplate.ui'
#
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(490, 414)
self.gridLayout = QtWidgets.QGridLayout(Form)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setSpacing(0)
self.gridLayout.setObjectName("gridLayout")
self.splitter = QtWidgets.QSplitter(Form)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.view = GraphicsView(self.splitter)
self.view.setObjectName("view")
self.layoutWidget = QtWidgets.QWidget(self.splitter)
self.layoutWidget.setObjectName("layoutWidget")
self.gridLayout_2 = QtWidgets.QGridLayout(self.layoutWidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.autoRangeBtn = QtWidgets.QPushButton(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(1)
sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth())
self.autoRangeBtn.setSizePolicy(sizePolicy)
self.autoRangeBtn.setObjectName("autoRangeBtn")
self.gridLayout_2.addWidget(self.autoRangeBtn, 2, 0, 1, 2)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.redirectCheck = QtWidgets.QCheckBox(self.layoutWidget)
self.redirectCheck.setObjectName("redirectCheck")
self.horizontalLayout.addWidget(self.redirectCheck)
self.redirectCombo = CanvasCombo(self.layoutWidget)
self.redirectCombo.setObjectName("redirectCombo")
self.horizontalLayout.addWidget(self.redirectCombo)
self.gridLayout_2.addLayout(self.horizontalLayout, 5, 0, 1, 2)
self.itemList = TreeWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(100)
sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth())
self.itemList.setSizePolicy(sizePolicy)
self.itemList.setHeaderHidden(True)
self.itemList.setObjectName("itemList")
self.itemList.headerItem().setText(0, "1")
self.gridLayout_2.addWidget(self.itemList, 6, 0, 1, 2)
self.ctrlLayout = QtWidgets.QGridLayout()
self.ctrlLayout.setSpacing(0)
self.ctrlLayout.setObjectName("ctrlLayout")
self.gridLayout_2.addLayout(self.ctrlLayout, 10, 0, 1, 2)
self.resetTransformsBtn = QtWidgets.QPushButton(self.layoutWidget)
self.resetTransformsBtn.setObjectName("resetTransformsBtn")
self.gridLayout_2.addWidget(self.resetTransformsBtn, 7, 0, 1, 1)
self.mirrorSelectionBtn = QtWidgets.QPushButton(self.layoutWidget)
self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn")
self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 3, 0, 1, 1)
self.reflectSelectionBtn = QtWidgets.QPushButton(self.layoutWidget)
self.reflectSelectionBtn.setObjectName("reflectSelectionBtn")
self.gridLayout_2.addWidget(self.reflectSelectionBtn, 3, 1, 1, 1)
self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.autoRangeBtn.setText(_translate("Form", "Auto Range"))
self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas."))
self.redirectCheck.setText(_translate("Form", "Redirect"))
self.resetTransformsBtn.setText(_translate("Form", "Reset Transforms"))
self.mirrorSelectionBtn.setText(_translate("Form", "Mirror Selection"))
self.reflectSelectionBtn.setText(_translate("Form", "MirrorXY"))
from ..widgets.GraphicsView import GraphicsView
from ..widgets.TreeWidget import TreeWidget
from .CanvasManager import CanvasCombo

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './pyqtgraph/canvas/CanvasTemplate.ui' # Form implementation generated from reading ui file 'pyqtgraph/canvas/CanvasTemplate.ui'
# #
# Created: Mon Dec 23 10:10:52 2013 # Created: Wed Nov 9 18:02:00 2016
# by: pyside-uic 0.2.14 running on PySide 1.1.2 # by: pyside-uic 0.2.15 running on PySide 1.2.2
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@ -27,12 +27,6 @@ class Ui_Form(object):
self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget) self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget)
self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.gridLayout_2.setObjectName("gridLayout_2") self.gridLayout_2.setObjectName("gridLayout_2")
self.storeSvgBtn = QtGui.QPushButton(self.layoutWidget)
self.storeSvgBtn.setObjectName("storeSvgBtn")
self.gridLayout_2.addWidget(self.storeSvgBtn, 1, 0, 1, 1)
self.storePngBtn = QtGui.QPushButton(self.layoutWidget)
self.storePngBtn.setObjectName("storePngBtn")
self.gridLayout_2.addWidget(self.storePngBtn, 1, 1, 1, 1)
self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget) self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
@ -40,7 +34,7 @@ class Ui_Form(object):
sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth())
self.autoRangeBtn.setSizePolicy(sizePolicy) self.autoRangeBtn.setSizePolicy(sizePolicy)
self.autoRangeBtn.setObjectName("autoRangeBtn") self.autoRangeBtn.setObjectName("autoRangeBtn")
self.gridLayout_2.addWidget(self.autoRangeBtn, 3, 0, 1, 2) self.gridLayout_2.addWidget(self.autoRangeBtn, 2, 0, 1, 2)
self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setSpacing(0) self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setObjectName("horizontalLayout")
@ -50,7 +44,7 @@ class Ui_Form(object):
self.redirectCombo = CanvasCombo(self.layoutWidget) self.redirectCombo = CanvasCombo(self.layoutWidget)
self.redirectCombo.setObjectName("redirectCombo") self.redirectCombo.setObjectName("redirectCombo")
self.horizontalLayout.addWidget(self.redirectCombo) self.horizontalLayout.addWidget(self.redirectCombo)
self.gridLayout_2.addLayout(self.horizontalLayout, 6, 0, 1, 2) self.gridLayout_2.addLayout(self.horizontalLayout, 5, 0, 1, 2)
self.itemList = TreeWidget(self.layoutWidget) self.itemList = TreeWidget(self.layoutWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
@ -60,20 +54,20 @@ class Ui_Form(object):
self.itemList.setHeaderHidden(True) self.itemList.setHeaderHidden(True)
self.itemList.setObjectName("itemList") self.itemList.setObjectName("itemList")
self.itemList.headerItem().setText(0, "1") self.itemList.headerItem().setText(0, "1")
self.gridLayout_2.addWidget(self.itemList, 7, 0, 1, 2) self.gridLayout_2.addWidget(self.itemList, 6, 0, 1, 2)
self.ctrlLayout = QtGui.QGridLayout() self.ctrlLayout = QtGui.QGridLayout()
self.ctrlLayout.setSpacing(0) self.ctrlLayout.setSpacing(0)
self.ctrlLayout.setObjectName("ctrlLayout") self.ctrlLayout.setObjectName("ctrlLayout")
self.gridLayout_2.addLayout(self.ctrlLayout, 11, 0, 1, 2) self.gridLayout_2.addLayout(self.ctrlLayout, 10, 0, 1, 2)
self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget) self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget)
self.resetTransformsBtn.setObjectName("resetTransformsBtn") self.resetTransformsBtn.setObjectName("resetTransformsBtn")
self.gridLayout_2.addWidget(self.resetTransformsBtn, 8, 0, 1, 1) self.gridLayout_2.addWidget(self.resetTransformsBtn, 7, 0, 1, 1)
self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget) self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget)
self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn")
self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 3, 0, 1, 1)
self.reflectSelectionBtn = QtGui.QPushButton(self.layoutWidget) self.reflectSelectionBtn = QtGui.QPushButton(self.layoutWidget)
self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") self.reflectSelectionBtn.setObjectName("reflectSelectionBtn")
self.gridLayout_2.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) self.gridLayout_2.addWidget(self.reflectSelectionBtn, 3, 1, 1, 1)
self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1)
self.retranslateUi(Form) self.retranslateUi(Form)
@ -81,8 +75,6 @@ class Ui_Form(object):
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.storeSvgBtn.setText(QtGui.QApplication.translate("Form", "Store SVG", None, QtGui.QApplication.UnicodeUTF8))
self.storePngBtn.setText(QtGui.QApplication.translate("Form", "Store PNG", None, QtGui.QApplication.UnicodeUTF8))
self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8)) self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8))
self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8)) self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8))
self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8)) self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8))
@ -90,6 +82,6 @@ class Ui_Form(object):
self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8)) self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8))
self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8)) self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8))
from .CanvasManager import CanvasCombo
from ..widgets.TreeWidget import TreeWidget from ..widgets.TreeWidget import TreeWidget
from CanvasManager import CanvasCombo
from ..widgets.GraphicsView import GraphicsView from ..widgets.GraphicsView import GraphicsView

View File

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './pyqtgraph/canvas/TransformGuiTemplate.ui' # Form implementation generated from reading ui file 'pyqtgraph/canvas/TransformGuiTemplate.ui'
# #
# Created: Mon Dec 23 10:10:52 2013 # Created by: PyQt4 UI code generator 4.11.4
# by: PyQt4 UI code generator 4.10
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@ -33,8 +32,8 @@ class Ui_Form(object):
sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth())
Form.setSizePolicy(sizePolicy) Form.setSizePolicy(sizePolicy)
self.verticalLayout = QtGui.QVBoxLayout(Form) self.verticalLayout = QtGui.QVBoxLayout(Form)
self.verticalLayout.setSpacing(1)
self.verticalLayout.setMargin(0) self.verticalLayout.setMargin(0)
self.verticalLayout.setSpacing(1)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.translateLabel = QtGui.QLabel(Form) self.translateLabel = QtGui.QLabel(Form)
self.translateLabel.setObjectName(_fromUtf8("translateLabel")) self.translateLabel.setObjectName(_fromUtf8("translateLabel"))

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'pyqtgraph/canvas/TransformGuiTemplate.ui'
#
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(224, 117)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth())
Form.setSizePolicy(sizePolicy)
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setSpacing(1)
self.verticalLayout.setObjectName("verticalLayout")
self.translateLabel = QtWidgets.QLabel(Form)
self.translateLabel.setObjectName("translateLabel")
self.verticalLayout.addWidget(self.translateLabel)
self.rotateLabel = QtWidgets.QLabel(Form)
self.rotateLabel.setObjectName("rotateLabel")
self.verticalLayout.addWidget(self.rotateLabel)
self.scaleLabel = QtWidgets.QLabel(Form)
self.scaleLabel.setObjectName("scaleLabel")
self.verticalLayout.addWidget(self.scaleLabel)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.mirrorImageBtn = QtWidgets.QPushButton(Form)
self.mirrorImageBtn.setToolTip("")
self.mirrorImageBtn.setObjectName("mirrorImageBtn")
self.horizontalLayout.addWidget(self.mirrorImageBtn)
self.reflectImageBtn = QtWidgets.QPushButton(Form)
self.reflectImageBtn.setObjectName("reflectImageBtn")
self.horizontalLayout.addWidget(self.reflectImageBtn)
self.verticalLayout.addLayout(self.horizontalLayout)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.translateLabel.setText(_translate("Form", "Translate:"))
self.rotateLabel.setText(_translate("Form", "Rotate:"))
self.scaleLabel.setText(_translate("Form", "Scale:"))
self.mirrorImageBtn.setText(_translate("Form", "Mirror"))
self.reflectImageBtn.setText(_translate("Form", "Reflect"))

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './pyqtgraph/canvas/TransformGuiTemplate.ui' # Form implementation generated from reading ui file 'pyqtgraph/canvas/TransformGuiTemplate.ui'
# #
# Created: Mon Dec 23 10:10:52 2013 # Created: Wed Nov 9 17:57:16 2016
# by: pyside-uic 0.2.14 running on PySide 1.1.2 # by: pyside-uic 0.2.15 running on PySide 1.2.2
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!

View File

@ -1,5 +1,7 @@
import numpy as np import numpy as np
from .Qt import QtGui, QtCore from .Qt import QtGui, QtCore
from .python2_3 import basestring
class ColorMap(object): class ColorMap(object):
""" """
@ -64,7 +66,9 @@ class ColorMap(object):
=============== ============================================================== =============== ==============================================================
""" """
self.pos = np.array(pos) self.pos = np.array(pos)
self.color = np.array(color) order = np.argsort(self.pos)
self.pos = self.pos[order]
self.color = np.array(color)[order]
if mode is None: if mode is None:
mode = np.ones(len(pos)) mode = np.ones(len(pos))
self.mode = mode self.mode = mode

View File

@ -10,14 +10,15 @@ as it can be converted to/from a string using repr and eval.
""" """
import re, os, sys import re, os, sys
import numpy
from .pgcollections import OrderedDict from .pgcollections import OrderedDict
GLOBAL_PATH = None # so not thread safe.
from . import units from . import units
from .python2_3 import asUnicode from .python2_3 import asUnicode, basestring
from .Qt import QtCore from .Qt import QtCore
from .Point import Point from .Point import Point
from .colormap import ColorMap from .colormap import ColorMap
import numpy GLOBAL_PATH = None # so not thread safe.
class ParseError(Exception): class ParseError(Exception):
def __init__(self, message, lineNum, line, fileName=None): def __init__(self, message, lineNum, line, fileName=None):

View File

@ -1,14 +1,17 @@
from ..Qt import QtCore, QtGui, USE_PYSIDE
import sys, re, os, time, traceback, subprocess import sys, re, os, time, traceback, subprocess
import pickle
from ..Qt import QtCore, QtGui, USE_PYSIDE, USE_PYQT5
from ..python2_3 import basestring
from .. import exceptionHandling as exceptionHandling
from .. import getConfigOption
if USE_PYSIDE: if USE_PYSIDE:
from . import template_pyside as template from . import template_pyside as template
elif USE_PYQT5:
from . import template_pyqt5 as template
else: else:
from . import template_pyqt as template from . import template_pyqt as template
from .. import exceptionHandling as exceptionHandling
import pickle
from .. import getConfigOption
class ConsoleWidget(QtGui.QWidget): class ConsoleWidget(QtGui.QWidget):
""" """
@ -45,6 +48,7 @@ class ConsoleWidget(QtGui.QWidget):
QtGui.QWidget.__init__(self, parent) QtGui.QWidget.__init__(self, parent)
if namespace is None: if namespace is None:
namespace = {} namespace = {}
namespace['__console__'] = self
self.localNamespace = namespace self.localNamespace = namespace
self.editor = editor self.editor = editor
self.multiline = None self.multiline = None
@ -131,7 +135,7 @@ class ConsoleWidget(QtGui.QWidget):
if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): if frame is not None and self.ui.runSelectedFrameCheck.isChecked():
return self.currentFrame().tb_frame.f_globals return self.currentFrame().tb_frame.f_globals
else: else:
return globals() return self.localNamespace
def locals(self): def locals(self):
frame = self.currentFrame() frame = self.currentFrame()

View File

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './pyqtgraph/console/template.ui'
#
# Created: Wed Mar 26 15:09:29 2014
# by: PyQt5 UI code generator 5.0.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(710, 497)
self.gridLayout = QtWidgets.QGridLayout(Form)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setSpacing(0)
self.gridLayout.setObjectName("gridLayout")
self.splitter = QtWidgets.QSplitter(Form)
self.splitter.setOrientation(QtCore.Qt.Vertical)
self.splitter.setObjectName("splitter")
self.layoutWidget = QtWidgets.QWidget(self.splitter)
self.layoutWidget.setObjectName("layoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.output = QtWidgets.QPlainTextEdit(self.layoutWidget)
font = QtGui.QFont()
font.setFamily("Monospace")
self.output.setFont(font)
self.output.setReadOnly(True)
self.output.setObjectName("output")
self.verticalLayout.addWidget(self.output)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.input = CmdInput(self.layoutWidget)
self.input.setObjectName("input")
self.horizontalLayout.addWidget(self.input)
self.historyBtn = QtWidgets.QPushButton(self.layoutWidget)
self.historyBtn.setCheckable(True)
self.historyBtn.setObjectName("historyBtn")
self.horizontalLayout.addWidget(self.historyBtn)
self.exceptionBtn = QtWidgets.QPushButton(self.layoutWidget)
self.exceptionBtn.setCheckable(True)
self.exceptionBtn.setObjectName("exceptionBtn")
self.horizontalLayout.addWidget(self.exceptionBtn)
self.verticalLayout.addLayout(self.horizontalLayout)
self.historyList = QtWidgets.QListWidget(self.splitter)
font = QtGui.QFont()
font.setFamily("Monospace")
self.historyList.setFont(font)
self.historyList.setObjectName("historyList")
self.exceptionGroup = QtWidgets.QGroupBox(self.splitter)
self.exceptionGroup.setObjectName("exceptionGroup")
self.gridLayout_2 = QtWidgets.QGridLayout(self.exceptionGroup)
self.gridLayout_2.setSpacing(0)
self.gridLayout_2.setContentsMargins(-1, 0, -1, 0)
self.gridLayout_2.setObjectName("gridLayout_2")
self.catchAllExceptionsBtn = QtWidgets.QPushButton(self.exceptionGroup)
self.catchAllExceptionsBtn.setCheckable(True)
self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn")
self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1)
self.catchNextExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup)
self.catchNextExceptionBtn.setCheckable(True)
self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn")
self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1)
self.onlyUncaughtCheck = QtWidgets.QCheckBox(self.exceptionGroup)
self.onlyUncaughtCheck.setChecked(True)
self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck")
self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 2, 1, 1)
self.exceptionStackList = QtWidgets.QListWidget(self.exceptionGroup)
self.exceptionStackList.setAlternatingRowColors(True)
self.exceptionStackList.setObjectName("exceptionStackList")
self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 5)
self.runSelectedFrameCheck = QtWidgets.QCheckBox(self.exceptionGroup)
self.runSelectedFrameCheck.setChecked(True)
self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck")
self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 5)
self.exceptionInfoLabel = QtWidgets.QLabel(self.exceptionGroup)
self.exceptionInfoLabel.setObjectName("exceptionInfoLabel")
self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 5)
self.clearExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup)
self.clearExceptionBtn.setEnabled(False)
self.clearExceptionBtn.setObjectName("clearExceptionBtn")
self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 4, 1, 1)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout_2.addItem(spacerItem, 0, 3, 1, 1)
self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Console"))
self.historyBtn.setText(_translate("Form", "History.."))
self.exceptionBtn.setText(_translate("Form", "Exceptions.."))
self.exceptionGroup.setTitle(_translate("Form", "Exception Handling"))
self.catchAllExceptionsBtn.setText(_translate("Form", "Show All Exceptions"))
self.catchNextExceptionBtn.setText(_translate("Form", "Show Next Exception"))
self.onlyUncaughtCheck.setText(_translate("Form", "Only Uncaught Exceptions"))
self.runSelectedFrameCheck.setText(_translate("Form", "Run commands in selected stack frame"))
self.exceptionInfoLabel.setText(_translate("Form", "Exception Info"))
self.clearExceptionBtn.setText(_translate("Form", "Clear Exception"))
from .CmdInput import CmdInput

View File

@ -83,8 +83,9 @@ class Tracer(object):
funcname = cls.__name__ + "." + funcname funcname = cls.__name__ + "." + funcname
return "%s: %s %s: %s" % (callline, filename, lineno, funcname) return "%s: %s %s: %s" % (callline, filename, lineno, funcname)
def warnOnException(func): def warnOnException(func):
"""Decorator which catches/ignores exceptions and prints a stack trace.""" """Decorator that catches/ignores exceptions and prints a stack trace."""
def w(*args, **kwds): def w(*args, **kwds):
try: try:
func(*args, **kwds) func(*args, **kwds)
@ -92,11 +93,9 @@ def warnOnException(func):
printExc('Ignored exception:') printExc('Ignored exception:')
return w return w
def getExc(indent=4, prefix='| ', skip=1): def getExc(indent=4, prefix='| ', skip=1):
lines = (traceback.format_stack()[:-skip] lines = formatException(*sys.exc_info(), skip=skip)
+ [" ---- exception caught ---->\n"]
+ traceback.format_tb(sys.exc_info()[2])
+ traceback.format_exception_only(*sys.exc_info()[:2]))
lines2 = [] lines2 = []
for l in lines: for l in lines:
lines2.extend(l.strip('\n').split('\n')) lines2.extend(l.strip('\n').split('\n'))
@ -112,6 +111,7 @@ def printExc(msg='', indent=4, prefix='|'):
print(" "*indent + prefix + '='*30 + '>>') print(" "*indent + prefix + '='*30 + '>>')
print(exc) print(exc)
print(" "*indent + prefix + '='*30 + '<<') print(" "*indent + prefix + '='*30 + '<<')
def printTrace(msg='', indent=4, prefix='|'): def printTrace(msg='', indent=4, prefix='|'):
"""Print an error message followed by an indented stack trace""" """Print an error message followed by an indented stack trace"""
@ -126,7 +126,30 @@ def printTrace(msg='', indent=4, prefix='|'):
def backtrace(skip=0): def backtrace(skip=0):
return ''.join(traceback.format_stack()[:-(skip+1)]) return ''.join(traceback.format_stack()[:-(skip+1)])
def formatException(exctype, value, tb, skip=0):
"""Return a list of formatted exception strings.
Similar to traceback.format_exception, but displays the entire stack trace
rather than just the portion downstream of the point where the exception is
caught. In particular, unhandled exceptions that occur during Qt signal
handling do not usually show the portion of the stack that emitted the
signal.
"""
lines = traceback.format_exception(exctype, value, tb)
lines = [lines[0]] + traceback.format_stack()[:-(skip+1)] + [' --- exception caught here ---\n'] + lines[1:]
return lines
def printException(exctype, value, traceback):
"""Print an exception with its full traceback.
Set `sys.excepthook = printException` to ensure that exceptions caught
inside Qt signal handlers are printed with their full stack trace.
"""
print(''.join(formatException(exctype, value, traceback, skip=1)))
def listObjs(regex='Q', typ=None): def listObjs(regex='Q', typ=None):
"""List all objects managed by python gc with class name matching regex. """List all objects managed by python gc with class name matching regex.
@ -723,7 +746,6 @@ class ObjTracker(object):
for k in self.startCount: for k in self.startCount:
c1[k] = c1.get(k, 0) - self.startCount[k] c1[k] = c1.get(k, 0) - self.startCount[k]
typs = list(c1.keys()) typs = list(c1.keys())
#typs.sort(lambda a,b: cmp(c1[a], c1[b]))
typs.sort(key=lambda a: c1[a]) typs.sort(key=lambda a: c1[a])
for t in typs: for t in typs:
if c1[t] == 0: if c1[t] == 0:
@ -824,7 +846,6 @@ class ObjTracker(object):
c = count.get(typ, [0,0]) c = count.get(typ, [0,0])
count[typ] = [c[0]+1, c[1]+objectSize(obj)] count[typ] = [c[0]+1, c[1]+objectSize(obj)]
typs = list(count.keys()) typs = list(count.keys())
#typs.sort(lambda a,b: cmp(count[a][1], count[b][1]))
typs.sort(key=lambda a: count[a][1]) typs.sort(key=lambda a: count[a][1])
for t in typs: for t in typs:
@ -1097,46 +1118,44 @@ def pretty(data, indent=''):
return ret return ret
class PeriodicTrace(object): class ThreadTrace(object):
""" """
Used to debug freezing by starting a new thread that reports on the Used to debug freezing by starting a new thread that reports on the
location of the main thread periodically. location of other threads periodically.
""" """
class ReportThread(QtCore.QThread): def __init__(self, interval=10.0):
def __init__(self): self.interval = interval
self.frame = None self.lock = Mutex()
self.ind = 0 self._stop = False
self.lastInd = None self.start()
self.lock = Mutex()
QtCore.QThread.__init__(self)
def notify(self, frame): def stop(self):
with self.lock: with self.lock:
self.frame = frame self._stop = True
self.ind += 1
def run(self): def start(self, interval=None):
while True: if interval is not None:
time.sleep(1) self.interval = interval
with self.lock: self._stop = False
if self.lastInd != self.ind: self.thread = threading.Thread(target=self.run)
print("== Trace %d: ==" % self.ind) self.thread.daemon = True
traceback.print_stack(self.frame)
self.lastInd = self.ind
def __init__(self):
self.mainThread = threading.current_thread()
self.thread = PeriodicTrace.ReportThread()
self.thread.start() self.thread.start()
sys.settrace(self.trace)
def trace(self, frame, event, arg):
if threading.current_thread() is self.mainThread: # and 'threading' not in frame.f_code.co_filename:
self.thread.notify(frame)
# print("== Trace ==", event, arg)
# traceback.print_stack(frame)
return self.trace
def run(self):
while True:
with self.lock:
if self._stop is True:
return
print("\n============= THREAD FRAMES: ================")
for id, frame in sys._current_frames().items():
if id == threading.current_thread().ident:
continue
print("<< thread %d >>" % id)
traceback.print_stack(frame)
print("===============================================\n")
time.sleep(self.interval)
class ThreadColor(object): class ThreadColor(object):

View File

@ -7,10 +7,13 @@ from ..python2_3 import asUnicode
class Dock(QtGui.QWidget, DockDrop): class Dock(QtGui.QWidget, DockDrop):
sigStretchChanged = QtCore.Signal() sigStretchChanged = QtCore.Signal()
sigClosed = QtCore.Signal(object)
def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True, closable=False): def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True, closable=False):
QtGui.QWidget.__init__(self) QtGui.QWidget.__init__(self)
DockDrop.__init__(self) DockDrop.__init__(self)
self._container = None
self._name = name
self.area = area self.area = area
self.label = DockLabel(name, self, closable) self.label = DockLabel(name, self, closable)
if closable: if closable:
@ -126,6 +129,18 @@ class Dock(QtGui.QWidget, DockDrop):
self.labelHidden = False self.labelHidden = False
self.allowedAreas.add('center') self.allowedAreas.add('center')
self.updateStyle() self.updateStyle()
def title(self):
"""
Gets the text displayed in the title bar for this dock.
"""
return asUnicode(self.label.text())
def setTitle(self, text):
"""
Sets the text displayed in title bar for this Dock.
"""
self.label.setText(text)
def setOrientation(self, o='auto', force=False): def setOrientation(self, o='auto', force=False):
""" """
@ -170,7 +185,7 @@ class Dock(QtGui.QWidget, DockDrop):
self.resizeOverlay(self.size()) self.resizeOverlay(self.size())
def name(self): def name(self):
return asUnicode(self.label.text()) return self._name
def container(self): def container(self):
return self._container return self._container
@ -223,6 +238,7 @@ class Dock(QtGui.QWidget, DockDrop):
self.label.setParent(None) self.label.setParent(None)
self._container.apoptose() self._container.apoptose()
self._container = None self._container = None
self.sigClosed.emit(self)
def __repr__(self): def __repr__(self):
return "<Dock %s %s>" % (self.name(), self.stretch()) return "<Dock %s %s>" % (self.name(), self.stretch())

View File

@ -1,17 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import weakref
from ..Qt import QtCore, QtGui from ..Qt import QtCore, QtGui
from .Container import * from .Container import *
from .DockDrop import * from .DockDrop import *
from .Dock import Dock from .Dock import Dock
from .. import debug as debug from .. import debug as debug
import weakref from ..python2_3 import basestring
## TODO:
# - containers should be drop areas, not docks. (but every slot within a container must have its own drop areas?)
# - drop between tabs
# - nest splitters inside tab boxes, etc.
class DockArea(Container, QtGui.QWidget, DockDrop): class DockArea(Container, QtGui.QWidget, DockDrop):
@ -102,9 +96,12 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
'below': 'after' 'below': 'after'
}[position] }[position]
#print "request insert", dock, insertPos, neighbor #print "request insert", dock, insertPos, neighbor
old = dock.container()
container.insert(dock, insertPos, neighbor) container.insert(dock, insertPos, neighbor)
dock.area = self dock.area = self
self.docks[dock.name()] = dock self.docks[dock.name()] = dock
if old is not None:
old.apoptose()
return dock return dock
@ -112,12 +109,10 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
""" """
Move an existing Dock to a new location. Move an existing Dock to a new location.
""" """
old = dock.container()
## Moving to the edge of a tabbed dock causes a drop outside the tab box ## Moving to the edge of a tabbed dock causes a drop outside the tab box
if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab': if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab':
neighbor = neighbor.container() neighbor = neighbor.container()
self.addDock(dock, position, neighbor) self.addDock(dock, position, neighbor)
old.apoptose()
def getContainer(self, obj): def getContainer(self, obj):
if obj is None: if obj is None:
@ -171,8 +166,7 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
if self.home is None: if self.home is None:
area = DockArea(temporary=True, home=self) area = DockArea(temporary=True, home=self)
self.tempAreas.append(area) self.tempAreas.append(area)
win = QtGui.QMainWindow() win = TempAreaWindow(area)
win.setCentralWidget(area)
area.win = win area.win = win
win.show() win.show()
else: else:
@ -196,7 +190,13 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
""" """
Return a serialized (storable) representation of the state of Return a serialized (storable) representation of the state of
all Docks in this DockArea.""" all Docks in this DockArea."""
state = {'main': self.childState(self.topContainer), 'float': []}
if self.topContainer is None:
main = None
else:
main = self.childState(self.topContainer)
state = {'main': main, 'float': []}
for a in self.tempAreas: for a in self.tempAreas:
geo = a.win.geometry() geo = a.win.geometry()
geo = (geo.x(), geo.y(), geo.width(), geo.height()) geo = (geo.x(), geo.y(), geo.width(), geo.height())
@ -228,7 +228,8 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
#print "found docks:", docks #print "found docks:", docks
## 2) create container structure, move docks into new containers ## 2) create container structure, move docks into new containers
self.buildFromState(state['main'], docks, self) if state['main'] is not None:
self.buildFromState(state['main'], docks, self)
## 3) create floating areas, populate ## 3) create floating areas, populate
for s in state['float']: for s in state['float']:
@ -296,10 +297,16 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
def apoptose(self): def apoptose(self):
#print "apoptose area:", self.temporary, self.topContainer, self.topContainer.count() #print "apoptose area:", self.temporary, self.topContainer, self.topContainer.count()
if self.temporary and self.topContainer.count() == 0: if self.topContainer.count() == 0:
self.topContainer = None self.topContainer = None
self.home.removeTempArea(self) if self.temporary:
#self.close() self.home.removeTempArea(self)
#self.close()
def clear(self):
docks = self.findAll()[1]
for dock in docks.values():
dock.close()
## PySide bug: We need to explicitly redefine these methods ## PySide bug: We need to explicitly redefine these methods
## or else drag/drop events will not be delivered. ## or else drag/drop events will not be delivered.
@ -315,5 +322,12 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
def dropEvent(self, *args): def dropEvent(self, *args):
DockDrop.dropEvent(self, *args) DockDrop.dropEvent(self, *args)
class TempAreaWindow(QtGui.QMainWindow):
def __init__(self, area, **kwargs):
QtGui.QMainWindow.__init__(self, **kwargs)
self.setCentralWidget(area)
def closeEvent(self, *args, **kwargs):
self.centralWidget().clear()
QtGui.QMainWindow.closeEvent(self, *args, **kwargs)

View File

@ -1,6 +1,6 @@
from ..widgets.FileDialog import FileDialog from ..widgets.FileDialog import FileDialog
from ..Qt import QtGui, QtCore, QtSvg from ..Qt import QtGui, QtCore, QtSvg
from ..python2_3 import asUnicode from ..python2_3 import asUnicode, basestring
from ..GraphicsScene import GraphicsScene from ..GraphicsScene import GraphicsScene
import os, re import os, re
LastExportDirectory = None LastExportDirectory = None

View File

@ -45,41 +45,6 @@ class SVGExporter(Exporter):
if toBytes is False and copy is False and fileName is None: if toBytes is False and copy is False and fileName is None:
self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)") self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)")
return return
#self.svg = QtSvg.QSvgGenerator()
#self.svg.setFileName(fileName)
#dpi = QtGui.QDesktopWidget().physicalDpiX()
### not really sure why this works, but it seems to be important:
#self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.))
#self.svg.setResolution(dpi)
##self.svg.setViewBox()
#targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height'])
#sourceRect = self.getSourceRect()
#painter = QtGui.QPainter(self.svg)
#try:
#self.setExportMode(True)
#self.render(painter, QtCore.QRectF(targetRect), sourceRect)
#finally:
#self.setExportMode(False)
#painter.end()
## Workaround to set pen widths correctly
#data = open(fileName).readlines()
#for i in range(len(data)):
#line = data[i]
#m = re.match(r'(<g .*)stroke-width="1"(.*transform="matrix\(([^\)]+)\)".*)', line)
#if m is not None:
##print "Matched group:", line
#g = m.groups()
#matrix = list(map(float, g[2].split(',')))
##print "matrix:", matrix
#scale = max(abs(matrix[0]), abs(matrix[3]))
#if scale == 0 or scale == 1.0:
#continue
#data[i] = g[0] + ' stroke-width="%0.2g" ' % (1.0/scale) + g[1] + '\n'
##print "old line:", line
##print "new line:", data[i]
#open(fileName, 'w').write(''.join(data))
## Qt's SVG generator is not complete. (notably, it lacks clipping) ## Qt's SVG generator is not complete. (notably, it lacks clipping)
## Instead, we will use Qt to generate SVG for each item independently, ## Instead, we will use Qt to generate SVG for each item independently,

View File

View File

@ -1,16 +1,23 @@
""" """
SVG export test SVG export test
""" """
from __future__ import division, print_function, absolute_import
import pyqtgraph as pg import pyqtgraph as pg
import pyqtgraph.exporters
import csv import csv
import os
import tempfile
app = pg.mkQApp() app = pg.mkQApp()
def approxeq(a, b): def approxeq(a, b):
return (a-b) <= ((a + b) * 1e-6) return (a-b) <= ((a + b) * 1e-6)
def test_CSVExporter(): def test_CSVExporter():
tempfilename = tempfile.NamedTemporaryFile(suffix='.csv').name
print("using %s as a temporary file" % tempfilename)
plt = pg.plot() plt = pg.plot()
y1 = [1,3,2,3,1,6,9,8,4,2] y1 = [1,3,2,3,1,6,9,8,4,2]
plt.plot(y=y1, name='myPlot') plt.plot(y=y1, name='myPlot')
@ -24,9 +31,9 @@ def test_CSVExporter():
plt.plot(x=x3, y=y3, stepMode=True) plt.plot(x=x3, y=y3, stepMode=True)
ex = pg.exporters.CSVExporter(plt.plotItem) ex = pg.exporters.CSVExporter(plt.plotItem)
ex.export(fileName='test.csv') ex.export(fileName=tempfilename)
r = csv.reader(open('test.csv', 'r')) r = csv.reader(open(tempfilename, 'r'))
lines = [line for line in r] lines = [line for line in r]
header = lines.pop(0) header = lines.pop(0)
assert header == ['myPlot_x', 'myPlot_y', 'x0001', 'y0001', 'x0002', 'y0002'] assert header == ['myPlot_x', 'myPlot_y', 'x0001', 'y0001', 'x0002', 'y0002']
@ -43,7 +50,8 @@ def test_CSVExporter():
assert (i >= len(x3) and vals[4] == '') or approxeq(float(vals[4]), x3[i]) assert (i >= len(x3) and vals[4] == '') or approxeq(float(vals[4]), x3[i])
assert (i >= len(y3) and vals[5] == '') or approxeq(float(vals[5]), y3[i]) assert (i >= len(y3) and vals[5] == '') or approxeq(float(vals[5]), y3[i])
i += 1 i += 1
os.unlink(tempfilename)
if __name__ == '__main__': if __name__ == '__main__':
test_CSVExporter() test_CSVExporter()

View File

@ -1,11 +1,18 @@
""" """
SVG export test SVG export test
""" """
from __future__ import division, print_function, absolute_import
import pyqtgraph as pg import pyqtgraph as pg
import pyqtgraph.exporters import tempfile
import os
app = pg.mkQApp() app = pg.mkQApp()
def test_plotscene(): def test_plotscene():
tempfilename = tempfile.NamedTemporaryFile(suffix='.svg').name
print("using %s as a temporary file" % tempfilename)
pg.setConfigOption('foreground', (0,0,0)) pg.setConfigOption('foreground', (0,0,0))
w = pg.GraphicsWindow() w = pg.GraphicsWindow()
w.show() w.show()
@ -18,10 +25,13 @@ def test_plotscene():
app.processEvents() app.processEvents()
ex = pg.exporters.SVGExporter(w.scene()) ex = pg.exporters.SVGExporter(w.scene())
ex.export(fileName='test.svg') ex.export(fileName=tempfilename)
# clean up after the test is done
os.unlink(tempfilename)
def test_simple(): def test_simple():
tempfilename = tempfile.NamedTemporaryFile(suffix='.svg').name
print("using %s as a temporary file" % tempfilename)
scene = pg.QtGui.QGraphicsScene() scene = pg.QtGui.QGraphicsScene()
#rect = pg.QtGui.QGraphicsRectItem(0, 0, 100, 100) #rect = pg.QtGui.QGraphicsRectItem(0, 0, 100, 100)
#scene.addItem(rect) #scene.addItem(rect)
@ -51,17 +61,17 @@ def test_simple():
#el = pg.QtGui.QGraphicsEllipseItem(0, 0, 100, 50) #el = pg.QtGui.QGraphicsEllipseItem(0, 0, 100, 50)
#el.translate(10,-5) #el.translate(10,-5)
#el.scale(0.5,2) #el.scale(0.5,2)
#el.setParentItem(rect2) #el.setParentItem(rect2)
grp2 = pg.ItemGroup() grp2 = pg.ItemGroup()
scene.addItem(grp2) scene.addItem(grp2)
grp2.scale(100,100) grp2.scale(100,100)
rect3 = pg.QtGui.QGraphicsRectItem(0,0,2,2) rect3 = pg.QtGui.QGraphicsRectItem(0,0,2,2)
rect3.setPen(pg.mkPen(width=1, cosmetic=False)) rect3.setPen(pg.mkPen(width=1, cosmetic=False))
grp2.addItem(rect3) grp2.addItem(rect3)
ex = pg.exporters.SVGExporter(scene)
ex.export(fileName='test.svg')
ex = pg.exporters.SVGExporter(scene)
ex.export(fileName=tempfilename)
os.unlink(tempfilename)

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from ..Qt import QtCore, QtGui, USE_PYSIDE from ..Qt import QtCore, QtGui, USE_PYSIDE, USE_PYQT5
from .Node import * from .Node import *
from ..pgcollections import OrderedDict from ..pgcollections import OrderedDict
from ..widgets.TreeWidget import * from ..widgets.TreeWidget import *
@ -9,6 +9,9 @@ from .. import FileDialog, DataTreeWidget
if USE_PYSIDE: if USE_PYSIDE:
from . import FlowchartTemplate_pyside as FlowchartTemplate from . import FlowchartTemplate_pyside as FlowchartTemplate
from . import FlowchartCtrlTemplate_pyside as FlowchartCtrlTemplate from . import FlowchartCtrlTemplate_pyside as FlowchartCtrlTemplate
elif USE_PYQT5:
from . import FlowchartTemplate_pyqt5 as FlowchartTemplate
from . import FlowchartCtrlTemplate_pyqt5 as FlowchartCtrlTemplate
else: else:
from . import FlowchartTemplate_pyqt as FlowchartTemplate from . import FlowchartTemplate_pyqt as FlowchartTemplate
from . import FlowchartCtrlTemplate_pyqt as FlowchartCtrlTemplate from . import FlowchartCtrlTemplate_pyqt as FlowchartCtrlTemplate
@ -349,7 +352,6 @@ class Flowchart(Node):
#tdeps[t] = lastNode #tdeps[t] = lastNode
if lastInd is not None: if lastInd is not None:
dels.append((lastInd+1, t)) dels.append((lastInd+1, t))
#dels.sort(lambda a,b: cmp(b[0], a[0]))
dels.sort(key=lambda a: a[0], reverse=True) dels.sort(key=lambda a: a[0], reverse=True)
for i, t in dels: for i, t in dels:
ops.insert(i, ('d', t)) ops.insert(i, ('d', t))
@ -379,22 +381,22 @@ class Flowchart(Node):
terms = set(startNode.outputs().values()) terms = set(startNode.outputs().values())
#print "======= Updating", startNode #print "======= Updating", startNode
#print "Order:", order # print("Order:", order)
for node in order[1:]: for node in order[1:]:
#print "Processing node", node # print("Processing node", node)
update = False
for term in list(node.inputs().values()): for term in list(node.inputs().values()):
#print " checking terminal", term # print(" checking terminal", term)
deps = list(term.connections().keys()) deps = list(term.connections().keys())
update = False
for d in deps: for d in deps:
if d in terms: if d in terms:
#print " ..input", d, "changed" # print(" ..input", d, "changed")
update = True update |= True
term.inputChanged(d, process=False) term.inputChanged(d, process=False)
if update: if update:
#print " processing.." # print(" processing..")
node.update() node.update()
terms |= set(node.outputs().values()) terms |= set(node.outputs().values())
finally: finally:
self.processing = False self.processing = False
@ -464,7 +466,6 @@ class Flowchart(Node):
self.clear() self.clear()
Node.restoreState(self, state) Node.restoreState(self, state)
nodes = state['nodes'] nodes = state['nodes']
#nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0]))
nodes.sort(key=lambda a: a['pos'][0]) nodes.sort(key=lambda a: a['pos'][0])
for n in nodes: for n in nodes:
if n['name'] in self._nodes: if n['name'] in self._nodes:
@ -619,7 +620,10 @@ class FlowchartCtrlWidget(QtGui.QWidget):
self.cwWin.resize(1000,800) self.cwWin.resize(1000,800)
h = self.ui.ctrlList.header() h = self.ui.ctrlList.header()
h.setResizeMode(0, h.Stretch) if not USE_PYQT5:
h.setResizeMode(0, h.Stretch)
else:
h.setSectionResizeMode(0, h.Stretch)
self.ui.ctrlList.itemChanged.connect(self.itemChanged) self.ui.ctrlList.itemChanged.connect(self.itemChanged)
self.ui.loadBtn.clicked.connect(self.loadClicked) self.ui.loadBtn.clicked.connect(self.loadClicked)
@ -823,16 +827,20 @@ class FlowchartWidget(dockarea.DockArea):
self.buildMenu() self.buildMenu()
def buildMenu(self, pos=None): def buildMenu(self, pos=None):
def buildSubMenu(node, rootMenu, subMenus, pos=None):
for section, node in node.items():
menu = QtGui.QMenu(section)
rootMenu.addMenu(menu)
if isinstance(node, OrderedDict):
buildSubMenu(node, menu, subMenus, pos=pos)
subMenus.append(menu)
else:
act = rootMenu.addAction(section)
act.nodeType = section
act.pos = pos
self.nodeMenu = QtGui.QMenu() self.nodeMenu = QtGui.QMenu()
self.subMenus = [] self.subMenus = []
for section, nodes in self.chart.library.getNodeTree().items(): buildSubMenu(self.chart.library.getNodeTree(), self.nodeMenu, self.subMenus, pos=pos)
menu = QtGui.QMenu(section)
self.nodeMenu.addMenu(menu)
for name in nodes:
act = menu.addAction(name)
act.nodeType = name
act.pos = pos
self.subMenus.append(menu)
self.nodeMenu.triggered.connect(self.nodeMenuTriggered) self.nodeMenu.triggered.connect(self.nodeMenuTriggered)
return self.nodeMenu return self.nodeMenu

View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartCtrlTemplate.ui'
#
# Created: Wed Mar 26 15:09:28 2014
# by: PyQt5 UI code generator 5.0.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(217, 499)
self.gridLayout = QtWidgets.QGridLayout(Form)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setVerticalSpacing(0)
self.gridLayout.setObjectName("gridLayout")
self.loadBtn = QtWidgets.QPushButton(Form)
self.loadBtn.setObjectName("loadBtn")
self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1)
self.saveBtn = FeedbackButton(Form)
self.saveBtn.setObjectName("saveBtn")
self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2)
self.saveAsBtn = FeedbackButton(Form)
self.saveAsBtn.setObjectName("saveAsBtn")
self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1)
self.reloadBtn = FeedbackButton(Form)
self.reloadBtn.setCheckable(False)
self.reloadBtn.setFlat(False)
self.reloadBtn.setObjectName("reloadBtn")
self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2)
self.showChartBtn = QtWidgets.QPushButton(Form)
self.showChartBtn.setCheckable(True)
self.showChartBtn.setObjectName("showChartBtn")
self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2)
self.ctrlList = TreeWidget(Form)
self.ctrlList.setObjectName("ctrlList")
self.ctrlList.headerItem().setText(0, "1")
self.ctrlList.header().setVisible(False)
self.ctrlList.header().setStretchLastSection(False)
self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4)
self.fileNameLabel = QtWidgets.QLabel(Form)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.fileNameLabel.setFont(font)
self.fileNameLabel.setText("")
self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter)
self.fileNameLabel.setObjectName("fileNameLabel")
self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.loadBtn.setText(_translate("Form", "Load.."))
self.saveBtn.setText(_translate("Form", "Save"))
self.saveAsBtn.setText(_translate("Form", "As.."))
self.reloadBtn.setText(_translate("Form", "Reload Libs"))
self.showChartBtn.setText(_translate("Form", "Flowchart"))
from ..widgets.FeedbackButton import FeedbackButton
from ..widgets.TreeWidget import TreeWidget

View File

@ -4,72 +4,27 @@ from ..widgets.GraphicsView import GraphicsView
from ..GraphicsScene import GraphicsScene from ..GraphicsScene import GraphicsScene
from ..graphicsItems.ViewBox import ViewBox from ..graphicsItems.ViewBox import ViewBox
#class FlowchartGraphicsView(QtGui.QGraphicsView):
class FlowchartGraphicsView(GraphicsView): class FlowchartGraphicsView(GraphicsView):
sigHoverOver = QtCore.Signal(object) sigHoverOver = QtCore.Signal(object)
sigClicked = QtCore.Signal(object) sigClicked = QtCore.Signal(object)
def __init__(self, widget, *args): def __init__(self, widget, *args):
#QtGui.QGraphicsView.__init__(self, *args)
GraphicsView.__init__(self, *args, useOpenGL=False) GraphicsView.__init__(self, *args, useOpenGL=False)
#self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(255,255,255)))
self._vb = FlowchartViewBox(widget, lockAspect=True, invertY=True) self._vb = FlowchartViewBox(widget, lockAspect=True, invertY=True)
self.setCentralItem(self._vb) self.setCentralItem(self._vb)
#self.scene().addItem(self.vb)
#self.setMouseTracking(True)
#self.lastPos = None
#self.setTransformationAnchor(self.AnchorViewCenter)
#self.setRenderHints(QtGui.QPainter.Antialiasing)
self.setRenderHint(QtGui.QPainter.Antialiasing, True) self.setRenderHint(QtGui.QPainter.Antialiasing, True)
#self.setDragMode(QtGui.QGraphicsView.RubberBandDrag)
#self.setRubberBandSelectionMode(QtCore.Qt.ContainsItemBoundingRect)
def viewBox(self): def viewBox(self):
return self._vb return self._vb
#def mousePressEvent(self, ev):
#self.moved = False
#self.lastPos = ev.pos()
#return QtGui.QGraphicsView.mousePressEvent(self, ev)
#def mouseMoveEvent(self, ev):
#self.moved = True
#callSuper = False
#if ev.buttons() & QtCore.Qt.RightButton:
#if self.lastPos is not None:
#dif = ev.pos() - self.lastPos
#self.scale(1.01**-dif.y(), 1.01**-dif.y())
#elif ev.buttons() & QtCore.Qt.MidButton:
#if self.lastPos is not None:
#dif = ev.pos() - self.lastPos
#self.translate(dif.x(), -dif.y())
#else:
##self.emit(QtCore.SIGNAL('hoverOver'), self.items(ev.pos()))
#self.sigHoverOver.emit(self.items(ev.pos()))
#callSuper = True
#self.lastPos = ev.pos()
#if callSuper:
#QtGui.QGraphicsView.mouseMoveEvent(self, ev)
#def mouseReleaseEvent(self, ev):
#if not self.moved:
##self.emit(QtCore.SIGNAL('clicked'), ev)
#self.sigClicked.emit(ev)
#return QtGui.QGraphicsView.mouseReleaseEvent(self, ev)
class FlowchartViewBox(ViewBox): class FlowchartViewBox(ViewBox):
def __init__(self, widget, *args, **kwargs): def __init__(self, widget, *args, **kwargs):
ViewBox.__init__(self, *args, **kwargs) ViewBox.__init__(self, *args, **kwargs)
self.widget = widget self.widget = widget
#self.menu = None
#self._subMenus = None ## need a place to store the menus otherwise they dissappear (even though they've been added to other menus) ((yes, it doesn't make sense))
def getMenu(self, ev): def getMenu(self, ev):
## called by ViewBox to create a new context menu ## called by ViewBox to create a new context menu
@ -84,26 +39,3 @@ class FlowchartViewBox(ViewBox):
menu = self.widget.buildMenu(ev.scenePos()) menu = self.widget.buildMenu(ev.scenePos())
menu.setTitle("Add node") menu.setTitle("Add node")
return [menu, ViewBox.getMenu(self, ev)] return [menu, ViewBox.getMenu(self, ev)]
##class FlowchartGraphicsScene(QtGui.QGraphicsScene):
#class FlowchartGraphicsScene(GraphicsScene):
#sigContextMenuEvent = QtCore.Signal(object)
#def __init__(self, *args):
##QtGui.QGraphicsScene.__init__(self, *args)
#GraphicsScene.__init__(self, *args)
#def mouseClickEvent(self, ev):
##QtGui.QGraphicsScene.contextMenuEvent(self, ev)
#if not ev.button() in [QtCore.Qt.RightButton]:
#self.sigContextMenuEvent.emit(ev)

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartTemplate.ui'
#
# Created: Wed Mar 26 15:09:28 2014
# by: PyQt5 UI code generator 5.0.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(529, 329)
self.selInfoWidget = QtWidgets.QWidget(Form)
self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222))
self.selInfoWidget.setObjectName("selInfoWidget")
self.gridLayout = QtWidgets.QGridLayout(self.selInfoWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.selDescLabel = QtWidgets.QLabel(self.selInfoWidget)
self.selDescLabel.setText("")
self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.selDescLabel.setWordWrap(True)
self.selDescLabel.setObjectName("selDescLabel")
self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1)
self.selNameLabel = QtWidgets.QLabel(self.selInfoWidget)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.selNameLabel.setFont(font)
self.selNameLabel.setText("")
self.selNameLabel.setObjectName("selNameLabel")
self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1)
self.selectedTree = DataTreeWidget(self.selInfoWidget)
self.selectedTree.setObjectName("selectedTree")
self.selectedTree.headerItem().setText(0, "1")
self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2)
self.hoverText = QtWidgets.QTextEdit(Form)
self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81))
self.hoverText.setObjectName("hoverText")
self.view = FlowchartGraphicsView(Form)
self.view.setGeometry(QtCore.QRect(0, 0, 256, 192))
self.view.setObjectName("view")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
from ..widgets.DataTreeWidget import DataTreeWidget
from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView

View File

@ -6,7 +6,6 @@ from .Terminal import *
from ..pgcollections import OrderedDict from ..pgcollections import OrderedDict
from ..debug import * from ..debug import *
import numpy as np import numpy as np
from .eq import *
def strDict(d): def strDict(d):
@ -261,7 +260,7 @@ class Node(QtCore.QObject):
for k, v in args.items(): for k, v in args.items():
term = self._inputs[k] term = self._inputs[k]
oldVal = term.value() oldVal = term.value()
if not eq(oldVal, v): if not fn.eq(oldVal, v):
changed = True changed = True
term.setValue(v, process=False) term.setValue(v, process=False)
if changed and '_updatesHandled_' not in args: if changed and '_updatesHandled_' not in args:

View File

@ -4,8 +4,7 @@ import weakref
from ..graphicsItems.GraphicsObject import GraphicsObject from ..graphicsItems.GraphicsObject import GraphicsObject
from .. import functions as fn from .. import functions as fn
from ..Point import Point from ..Point import Point
#from PySide import QtCore, QtGui
from .eq import *
class Terminal(object): class Terminal(object):
def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None): def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None):
@ -29,9 +28,6 @@ class Terminal(object):
============== ================================================================================= ============== =================================================================================
""" """
self._io = io self._io = io
#self._isOutput = opts[0] in ['out', 'io']
#self._isInput = opts[0]] in ['in', 'io']
#self._isIO = opts[0]=='io'
self._optional = optional self._optional = optional
self._multi = multi self._multi = multi
self._node = weakref.ref(node) self._node = weakref.ref(node)
@ -68,7 +64,7 @@ class Terminal(object):
"""If this is a single-value terminal, val should be a single value. """If this is a single-value terminal, val should be a single value.
If this is a multi-value terminal, val should be a dict of terminal:value pairs""" If this is a multi-value terminal, val should be a dict of terminal:value pairs"""
if not self.isMultiValue(): if not self.isMultiValue():
if eq(val, self._value): if fn.eq(val, self._value):
return return
self._value = val self._value = val
else: else:
@ -81,11 +77,6 @@ class Terminal(object):
if self.isInput() and process: if self.isInput() and process:
self.node().update() self.node().update()
## Let the flowchart handle this.
#if self.isOutput():
#for c in self.connections():
#if c.isInput():
#c.inputChanged(self)
self.recolor() self.recolor()
def setOpts(self, **opts): def setOpts(self, **opts):
@ -94,7 +85,6 @@ class Terminal(object):
self._multiable = opts.get('multiable', self._multiable) self._multiable = opts.get('multiable', self._multiable)
if 'multi' in opts: if 'multi' in opts:
self.setMultiValue(opts['multi']) self.setMultiValue(opts['multi'])
def connected(self, term): def connected(self, term):
"""Called whenever this terminal has been connected to another. (note--this function is called on both terminals)""" """Called whenever this terminal has been connected to another. (note--this function is called on both terminals)"""
@ -109,12 +99,10 @@ class Terminal(object):
if self.isMultiValue() and term in self._value: if self.isMultiValue() and term in self._value:
del self._value[term] del self._value[term]
self.node().update() self.node().update()
#self.recolor()
else: else:
if self.isInput(): if self.isInput():
self.setValue(None) self.setValue(None)
self.node().disconnected(self, term) self.node().disconnected(self, term)
#self.node().update()
def inputChanged(self, term, process=True): def inputChanged(self, term, process=True):
"""Called whenever there is a change to the input value to this terminal. """Called whenever there is a change to the input value to this terminal.
@ -178,7 +166,6 @@ class Terminal(object):
return term in self.connections() return term in self.connections()
def hasInput(self): def hasInput(self):
#conn = self.extendedConnections()
for t in self.connections(): for t in self.connections():
if t.isOutput(): if t.isOutput():
return True return True
@ -186,17 +173,10 @@ class Terminal(object):
def inputTerminals(self): def inputTerminals(self):
"""Return the terminal(s) that give input to this one.""" """Return the terminal(s) that give input to this one."""
#terms = self.extendedConnections()
#for t in terms:
#if t.isOutput():
#return t
return [t for t in self.connections() if t.isOutput()] return [t for t in self.connections() if t.isOutput()]
def dependentNodes(self): def dependentNodes(self):
"""Return the list of nodes which receive input from this terminal.""" """Return the list of nodes which receive input from this terminal."""
#conn = self.extendedConnections()
#del conn[self]
return set([t.node() for t in self.connections() if t.isInput()]) return set([t.node() for t in self.connections() if t.isInput()])
def connectTo(self, term, connectionItem=None): def connectTo(self, term, connectionItem=None):
@ -210,12 +190,6 @@ class Terminal(object):
for t in [self, term]: for t in [self, term]:
if t.isInput() and not t._multi and len(t.connections()) > 0: if t.isInput() and not t._multi and len(t.connections()) > 0:
raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, list(t.connections().keys()))) raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, list(t.connections().keys())))
#if self.hasInput() and term.hasInput():
#raise Exception('Target terminal already has input')
#if term in self.node().terminals.values():
#if self.isOutput() or term.isOutput():
#raise Exception('Can not connect an output back to the same node.')
except: except:
if connectionItem is not None: if connectionItem is not None:
connectionItem.close() connectionItem.close()
@ -223,18 +197,12 @@ class Terminal(object):
if connectionItem is None: if connectionItem is None:
connectionItem = ConnectionItem(self.graphicsItem(), term.graphicsItem()) connectionItem = ConnectionItem(self.graphicsItem(), term.graphicsItem())
#self.graphicsItem().scene().addItem(connectionItem)
self.graphicsItem().getViewBox().addItem(connectionItem) self.graphicsItem().getViewBox().addItem(connectionItem)
#connectionItem.setParentItem(self.graphicsItem().parent().parent())
self._connections[term] = connectionItem self._connections[term] = connectionItem
term._connections[self] = connectionItem term._connections[self] = connectionItem
self.recolor() self.recolor()
#if self.isOutput() and term.isInput():
#term.inputChanged(self)
#if term.isInput() and term.isOutput():
#self.inputChanged(term)
self.connected(term) self.connected(term)
term.connected(self) term.connected(self)
@ -244,8 +212,6 @@ class Terminal(object):
if not self.connectedTo(term): if not self.connectedTo(term):
return return
item = self._connections[term] item = self._connections[term]
#print "removing connection", item
#item.scene().removeItem(item)
item.close() item.close()
del self._connections[term] del self._connections[term]
del term._connections[self] del term._connections[self]
@ -254,10 +220,6 @@ class Terminal(object):
self.disconnected(term) self.disconnected(term)
term.disconnected(self) term.disconnected(self)
#if self.isOutput() and term.isInput():
#term.inputChanged(self)
#if term.isInput() and term.isOutput():
#self.inputChanged(term)
def disconnectAll(self): def disconnectAll(self):
@ -270,7 +232,7 @@ class Terminal(object):
color = QtGui.QColor(0,0,0) color = QtGui.QColor(0,0,0)
elif self.isInput() and not self.hasInput(): ## input terminal with no connected output terminals elif self.isInput() and not self.hasInput(): ## input terminal with no connected output terminals
color = QtGui.QColor(200,200,0) color = QtGui.QColor(200,200,0)
elif self._value is None or eq(self._value, {}): ## terminal is connected but has no data (possibly due to processing error) elif self._value is None or fn.eq(self._value, {}): ## terminal is connected but has no data (possibly due to processing error)
color = QtGui.QColor(255,255,255) color = QtGui.QColor(255,255,255)
elif self.valueIsAcceptable() is None: ## terminal has data, but it is unknown if the data is ok elif self.valueIsAcceptable() is None: ## terminal has data, but it is unknown if the data is ok
color = QtGui.QColor(200, 200, 0) color = QtGui.QColor(200, 200, 0)
@ -283,7 +245,6 @@ class Terminal(object):
if recurse: if recurse:
for t in self.connections(): for t in self.connections():
t.recolor(color, recurse=False) t.recolor(color, recurse=False)
def rename(self, name): def rename(self, name):
oldName = self._name oldName = self._name
@ -294,17 +255,6 @@ class Terminal(object):
def __repr__(self): def __repr__(self):
return "<Terminal %s.%s>" % (str(self.node().name()), str(self.name())) return "<Terminal %s.%s>" % (str(self.node().name()), str(self.name()))
#def extendedConnections(self, terms=None):
#"""Return list of terminals (including this one) that are directly or indirectly wired to this."""
#if terms is None:
#terms = {}
#terms[self] = None
#for t in self._connections:
#if t in terms:
#continue
#terms.update(t.extendedConnections(terms))
#return terms
def __hash__(self): def __hash__(self):
return id(self) return id(self)
@ -318,18 +268,15 @@ class Terminal(object):
return {'io': self._io, 'multi': self._multi, 'optional': self._optional, 'renamable': self._renamable, 'removable': self._removable, 'multiable': self._multiable} return {'io': self._io, 'multi': self._multi, 'optional': self._optional, 'renamable': self._renamable, 'removable': self._removable, 'multiable': self._multiable}
#class TerminalGraphicsItem(QtGui.QGraphicsItem):
class TerminalGraphicsItem(GraphicsObject): class TerminalGraphicsItem(GraphicsObject):
def __init__(self, term, parent=None): def __init__(self, term, parent=None):
self.term = term self.term = term
#QtGui.QGraphicsItem.__init__(self, parent)
GraphicsObject.__init__(self, parent) GraphicsObject.__init__(self, parent)
self.brush = fn.mkBrush(0,0,0) self.brush = fn.mkBrush(0,0,0)
self.box = QtGui.QGraphicsRectItem(0, 0, 10, 10, self) self.box = QtGui.QGraphicsRectItem(0, 0, 10, 10, self)
self.label = QtGui.QGraphicsTextItem(self.term.name(), self) self.label = QtGui.QGraphicsTextItem(self.term.name(), self)
self.label.scale(0.7, 0.7) self.label.scale(0.7, 0.7)
#self.setAcceptHoverEvents(True)
self.newConnection = None self.newConnection = None
self.setFiltersChildEvents(True) ## to pick up mouse events on the rectitem self.setFiltersChildEvents(True) ## to pick up mouse events on the rectitem
if self.term.isRenamable(): if self.term.isRenamable():
@ -338,7 +285,6 @@ class TerminalGraphicsItem(GraphicsObject):
self.label.keyPressEvent = self.labelKeyPress self.label.keyPressEvent = self.labelKeyPress
self.setZValue(1) self.setZValue(1)
self.menu = None self.menu = None
def labelFocusOut(self, ev): def labelFocusOut(self, ev):
QtGui.QGraphicsTextItem.focusOutEvent(self.label, ev) QtGui.QGraphicsTextItem.focusOutEvent(self.label, ev)
@ -471,8 +417,6 @@ class TerminalGraphicsItem(GraphicsObject):
break break
if not gotTarget: if not gotTarget:
#print "remove unused connection"
#self.scene().removeItem(self.newConnection)
self.newConnection.close() self.newConnection.close()
self.newConnection = None self.newConnection = None
else: else:
@ -488,12 +432,6 @@ class TerminalGraphicsItem(GraphicsObject):
self.box.setBrush(self.brush) self.box.setBrush(self.brush)
self.update() self.update()
#def hoverEnterEvent(self, ev):
#self.hover = True
#def hoverLeaveEvent(self, ev):
#self.hover = False
def connectPoint(self): def connectPoint(self):
## return the connect position of this terminal in view coords ## return the connect position of this terminal in view coords
return self.mapToView(self.mapFromItem(self.box, self.box.boundingRect().center())) return self.mapToView(self.mapFromItem(self.box, self.box.boundingRect().center()))
@ -503,11 +441,9 @@ class TerminalGraphicsItem(GraphicsObject):
item.updateLine() item.updateLine()
#class ConnectionItem(QtGui.QGraphicsItem):
class ConnectionItem(GraphicsObject): class ConnectionItem(GraphicsObject):
def __init__(self, source, target=None): def __init__(self, source, target=None):
#QtGui.QGraphicsItem.__init__(self)
GraphicsObject.__init__(self) GraphicsObject.__init__(self)
self.setFlags( self.setFlags(
self.ItemIsSelectable | self.ItemIsSelectable |
@ -528,14 +464,12 @@ class ConnectionItem(GraphicsObject):
'selectedColor': (200, 200, 0), 'selectedColor': (200, 200, 0),
'selectedWidth': 3.0, 'selectedWidth': 3.0,
} }
#self.line = QtGui.QGraphicsLineItem(self)
self.source.getViewBox().addItem(self) self.source.getViewBox().addItem(self)
self.updateLine() self.updateLine()
self.setZValue(0) self.setZValue(0)
def close(self): def close(self):
if self.scene() is not None: if self.scene() is not None:
#self.scene().removeItem(self.line)
self.scene().removeItem(self) self.scene().removeItem(self)
def setTarget(self, target): def setTarget(self, target):
@ -575,8 +509,11 @@ class ConnectionItem(GraphicsObject):
return path return path
def keyPressEvent(self, ev): def keyPressEvent(self, ev):
if not self.isSelected():
ev.ignore()
return
if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace:
#if isinstance(self.target, TerminalGraphicsItem):
self.source.disconnect(self.target) self.source.disconnect(self.target)
ev.accept() ev.accept()
else: else:
@ -590,6 +527,7 @@ class ConnectionItem(GraphicsObject):
ev.accept() ev.accept()
sel = self.isSelected() sel = self.isSelected()
self.setSelected(True) self.setSelected(True)
self.setFocus()
if not sel and self.isSelected(): if not sel and self.isSelected():
self.update() self.update()
@ -600,12 +538,9 @@ class ConnectionItem(GraphicsObject):
self.hovered = False self.hovered = False
self.update() self.update()
def boundingRect(self): def boundingRect(self):
return self.shape().boundingRect() return self.shape().boundingRect()
##return self.line.boundingRect()
#px = self.pixelWidth()
#return QtCore.QRectF(-5*px, 0, 10*px, self.length)
def viewRangeChanged(self): def viewRangeChanged(self):
self.shapePath = None self.shapePath = None
self.prepareGeometryChange() self.prepareGeometryChange()
@ -628,7 +563,5 @@ class ConnectionItem(GraphicsObject):
p.setPen(fn.mkPen(self.style['hoverColor'], width=self.style['hoverWidth'])) p.setPen(fn.mkPen(self.style['hoverColor'], width=self.style['hoverWidth']))
else: else:
p.setPen(fn.mkPen(self.style['color'], width=self.style['width'])) p.setPen(fn.mkPen(self.style['color'], width=self.style['width']))
#p.drawLine(0, 0, 0, self.length)
p.drawPath(self.path) p.drawPath(self.path)

View File

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
from numpy import ndarray, bool_
from ..metaarray import MetaArray
def eq(a, b):
"""The great missing equivalence function: Guaranteed evaluation to a single bool value."""
if a is b:
return True
try:
e = a==b
except ValueError:
return False
except AttributeError:
return False
except:
print("a:", str(type(a)), str(a))
print("b:", str(type(b)), str(b))
raise
t = type(e)
if t is bool:
return e
elif t is bool_:
return bool(e)
elif isinstance(e, ndarray) or (hasattr(e, 'implements') and e.implements('MetaArray')):
try: ## disaster: if a is an empty array and b is not, then e.all() is True
if a.shape != b.shape:
return False
except:
return False
if (hasattr(e, 'implements') and e.implements('MetaArray')):
return e.asarray().all()
else:
return e.all()
else:
raise Exception("== operator returned type %s" % str(type(e)))

View File

@ -1,11 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import numpy as np
from ...Qt import QtCore, QtGui from ...Qt import QtCore, QtGui
from ..Node import Node from ..Node import Node
from . import functions from . import functions
from ... import functions as pgfn from ... import functions as pgfn
from .common import * from .common import *
import numpy as np from ...python2_3 import xrange
from ... import PolyLineROI from ... import PolyLineROI
from ... import Point from ... import Point
from ... import metaarray as metaarray from ... import metaarray as metaarray
@ -164,8 +164,15 @@ class Gaussian(CtrlNode):
import scipy.ndimage import scipy.ndimage
except ImportError: except ImportError:
raise Exception("GaussianFilter node requires the package scipy.ndimage.") raise Exception("GaussianFilter node requires the package scipy.ndimage.")
return pgfn.gaussianFilter(data, self.ctrls['sigma'].value())
if hasattr(data, 'implements') and data.implements('MetaArray'):
info = data.infoCopy()
filt = pgfn.gaussianFilter(data.asarray(), self.ctrls['sigma'].value())
if 'values' in info[0]:
info[0]['values'] = info[0]['values'][:filt.shape[0]]
return metaarray.MetaArray(filt, info=info)
else:
return pgfn.gaussianFilter(data, self.ctrls['sigma'].value())
class Derivative(CtrlNode): class Derivative(CtrlNode):
"""Returns the pointwise derivative of the input""" """Returns the pointwise derivative of the input"""

View File

@ -1,5 +1,7 @@
import numpy as np import numpy as np
from ...metaarray import MetaArray from ...metaarray import MetaArray
from ...python2_3 import basestring, xrange
def downsample(data, n, axis=0, xvals='subsample'): def downsample(data, n, axis=0, xvals='subsample'):
"""Downsample by averaging points together across axis. """Downsample by averaging points together across axis.

View File

@ -6,8 +6,18 @@ Distributed under MIT/X11 license. See license.txt for more infomation.
""" """
from __future__ import division from __future__ import division
from .python2_3 import asUnicode import warnings
import numpy as np
import decimal, re
import ctypes
import sys, struct
from .python2_3 import asUnicode, basestring
from .Qt import QtGui, QtCore, USE_PYSIDE from .Qt import QtGui, QtCore, USE_PYSIDE
from . import getConfigOption, setConfigOptions
from . import debug
Colors = { Colors = {
'b': QtGui.QColor(0,0,255,255), 'b': QtGui.QColor(0,0,255,255),
'g': QtGui.QColor(0,255,0,255), 'g': QtGui.QColor(0,255,0,255),
@ -24,18 +34,13 @@ Colors = {
SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY') SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY')
SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY' SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY'
SI_PREFIX_EXPONENTS = dict([(SI_PREFIXES[i], (i-8)*3) for i in range(len(SI_PREFIXES))])
SI_PREFIX_EXPONENTS['u'] = -6
FLOAT_REGEX = re.compile(r'(?P<number>[+-]?((\d+(\.\d*)?)|(\d*\.\d+))([eE][+-]?\d+)?)\s*((?P<siPrefix>[u' + SI_PREFIXES + r']?)(?P<suffix>\w.*))?$')
INT_REGEX = re.compile(r'(?P<number>[+-]?\d+)\s*(?P<siPrefix>[u' + SI_PREFIXES + r']?)(?P<suffix>.*)$')
from .Qt import QtGui, QtCore, USE_PYSIDE
from . import getConfigOption, setConfigOptions
import numpy as np
import decimal, re
import ctypes
import sys, struct
from . import debug
def siScale(x, minVal=1e-25, allowUnicode=True): def siScale(x, minVal=1e-25, allowUnicode=True):
""" """
Return the recommended scale factor and SI prefix string for x. Return the recommended scale factor and SI prefix string for x.
@ -74,6 +79,7 @@ def siScale(x, minVal=1e-25, allowUnicode=True):
return (p, pref) return (p, pref)
def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, allowUnicode=True): def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, allowUnicode=True):
""" """
Return the number x formatted in engineering notation with SI prefix. Return the number x formatted in engineering notation with SI prefix.
@ -102,31 +108,56 @@ def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, al
plusminus = " +/- " plusminus = " +/- "
fmt = "%." + str(precision) + "g%s%s%s%s" fmt = "%." + str(precision) + "g%s%s%s%s"
return fmt % (x*p, pref, suffix, plusminus, siFormat(error, precision=precision, suffix=suffix, space=space, minVal=minVal)) return fmt % (x*p, pref, suffix, plusminus, siFormat(error, precision=precision, suffix=suffix, space=space, minVal=minVal))
def siParse(s, regex=FLOAT_REGEX):
"""Convert a value written in SI notation to a tuple (number, si_prefix, suffix).
def siEval(s): Example::
siParse('100 μV") # returns ('100', 'μ', 'V')
""" """
Convert a value written in SI notation to its equivalent prefixless value s = asUnicode(s)
m = regex.match(s)
if m is None:
raise ValueError('Cannot parse number "%s"' % s)
try:
sip = m.group('siPrefix')
except IndexError:
sip = ''
try:
suf = m.group('suffix')
except IndexError:
suf = ''
return m.group('number'), '' if sip is None else sip, '' if suf is None else suf
def siEval(s, typ=float, regex=FLOAT_REGEX):
"""
Convert a value written in SI notation to its equivalent prefixless value.
Example:: Example::
siEval("100 μV") # returns 0.0001 siEval("100 μV") # returns 0.0001
""" """
val, siprefix, suffix = siParse(s, regex)
v = typ(val)
return siApply(val, siprefix)
s = asUnicode(s) def siApply(val, siprefix):
m = re.match(r'(-?((\d+(\.\d*)?)|(\.\d+))([eE]-?\d+)?)\s*([u' + SI_PREFIXES + r']?).*$', s) """
if m is None: """
raise Exception("Can't convert string '%s' to number." % s) n = SI_PREFIX_EXPONENTS[siprefix] if siprefix != '' else 0
v = float(m.groups()[0]) if n > 0:
p = m.groups()[6] return val * 10**n
#if p not in SI_PREFIXES: elif n < 0:
#raise Exception("Can't convert string '%s' to number--unknown prefix." % s) # this case makes it possible to use Decimal objects here
if p == '': return val / 10**-n
n = 0
elif p == 'u':
n = -2
else: else:
n = SI_PREFIXES.index(p) - 8 return val
return v * 1000**n
class Color(QtGui.QColor): class Color(QtGui.QColor):
@ -243,6 +274,7 @@ def mkBrush(*args, **kwds):
color = args color = args
return QtGui.QBrush(mkColor(color)) return QtGui.QBrush(mkColor(color))
def mkPen(*args, **kargs): def mkPen(*args, **kargs):
""" """
Convenience function for constructing QPen. Convenience function for constructing QPen.
@ -292,6 +324,7 @@ def mkPen(*args, **kargs):
pen.setDashPattern(dash) pen.setDashPattern(dash)
return pen return pen
def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0): def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0):
"""Generate a QColor from HSVa values. (all arguments are float 0.0-1.0)""" """Generate a QColor from HSVa values. (all arguments are float 0.0-1.0)"""
c = QtGui.QColor() c = QtGui.QColor()
@ -303,10 +336,12 @@ def colorTuple(c):
"""Return a tuple (R,G,B,A) from a QColor""" """Return a tuple (R,G,B,A) from a QColor"""
return (c.red(), c.green(), c.blue(), c.alpha()) return (c.red(), c.green(), c.blue(), c.alpha())
def colorStr(c): def colorStr(c):
"""Generate a hex string code from a QColor""" """Generate a hex string code from a QColor"""
return ('%02x'*4) % colorTuple(c) return ('%02x'*4) % colorTuple(c)
def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255, **kargs): def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255, **kargs):
""" """
Creates a QColor from a single index. Useful for stepping through a predefined list of colors. Creates a QColor from a single index. Useful for stepping through a predefined list of colors.
@ -331,6 +366,7 @@ def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, mi
c.setAlpha(alpha) c.setAlpha(alpha)
return c return c
def glColor(*args, **kargs): def glColor(*args, **kargs):
""" """
Convert a color to OpenGL color format (r,g,b,a) floats 0.0-1.0 Convert a color to OpenGL color format (r,g,b,a) floats 0.0-1.0
@ -367,12 +403,80 @@ def makeArrowPath(headLen=20, tipAngle=20, tailLen=20, tailWidth=3, baseAngle=0)
return path return path
def eq(a, b):
"""The great missing equivalence function: Guaranteed evaluation to a single bool value."""
if a is b:
return True
try:
with warnings.catch_warnings(module=np): # ignore numpy futurewarning (numpy v. 1.10)
e = a==b
except ValueError:
return False
except AttributeError:
return False
except:
print('failed to evaluate equivalence for:')
print(" a:", str(type(a)), str(a))
print(" b:", str(type(b)), str(b))
raise
t = type(e)
if t is bool:
return e
elif t is np.bool_:
return bool(e)
elif isinstance(e, np.ndarray) or (hasattr(e, 'implements') and e.implements('MetaArray')):
try: ## disaster: if a is an empty array and b is not, then e.all() is True
if a.shape != b.shape:
return False
except:
return False
if (hasattr(e, 'implements') and e.implements('MetaArray')):
return e.asarray().all()
else:
return e.all()
else:
raise Exception("== operator returned type %s" % str(type(e)))
def affineSliceCoords(shape, origin, vectors, axes):
"""Return the array of coordinates used to sample data arrays in affineSlice().
"""
# sanity check
if len(shape) != len(vectors):
raise Exception("shape and vectors must have same length.")
if len(origin) != len(axes):
raise Exception("origin and axes must have same length.")
for v in vectors:
if len(v) != len(axes):
raise Exception("each vector must be same length as axes.")
shape = list(map(np.ceil, shape))
## make sure vectors are arrays
if not isinstance(vectors, np.ndarray):
vectors = np.array(vectors)
if not isinstance(origin, np.ndarray):
origin = np.array(origin)
origin.shape = (len(axes),) + (1,)*len(shape)
## Build array of sample locations.
grid = np.mgrid[tuple([slice(0,x) for x in shape])] ## mesh grid of indexes
x = (grid[np.newaxis,...] * vectors.transpose()[(Ellipsis,) + (np.newaxis,)*len(shape)]).sum(axis=1) ## magic
x += origin
return x
def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs): def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs):
""" """
Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data. Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays
such as MRI images for viewing as 1D or 2D data.
The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates if it is available (see the scipy documentation for more information about this). If scipy is not available, then a slower implementation of map_coordinates is used. The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is
possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger
datasets. The original data is interpolated onto a new array of coordinates using either interpolateArray if order<2
or scipy.ndimage.map_coordinates otherwise.
For a graphical interface to this function, see :func:`ROI.getArrayRegion <pyqtgraph.ROI.getArrayRegion>` For a graphical interface to this function, see :func:`ROI.getArrayRegion <pyqtgraph.ROI.getArrayRegion>`
@ -411,49 +515,24 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3)) affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3))
""" """
try: x = affineSliceCoords(shape, origin, vectors, axes)
import scipy.ndimage
have_scipy = True
except ImportError:
have_scipy = False
have_scipy = False
# sanity check
if len(shape) != len(vectors):
raise Exception("shape and vectors must have same length.")
if len(origin) != len(axes):
raise Exception("origin and axes must have same length.")
for v in vectors:
if len(v) != len(axes):
raise Exception("each vector must be same length as axes.")
shape = list(map(np.ceil, shape))
## transpose data so slice axes come first ## transpose data so slice axes come first
trAx = list(range(data.ndim)) trAx = list(range(data.ndim))
for x in axes: for ax in axes:
trAx.remove(x) trAx.remove(ax)
tr1 = tuple(axes) + tuple(trAx) tr1 = tuple(axes) + tuple(trAx)
data = data.transpose(tr1) data = data.transpose(tr1)
#print "tr1:", tr1 #print "tr1:", tr1
## dims are now [(slice axes), (other axes)] ## dims are now [(slice axes), (other axes)]
## make sure vectors are arrays if order > 1:
if not isinstance(vectors, np.ndarray): try:
vectors = np.array(vectors) import scipy.ndimage
if not isinstance(origin, np.ndarray): except ImportError:
origin = np.array(origin) raise ImportError("Interpolating with order > 1 requires the scipy.ndimage module, but it could not be imported.")
origin.shape = (len(axes),) + (1,)*len(shape)
# iterate manually over unused axes since map_coordinates won't do it for us
## Build array of sample locations.
grid = np.mgrid[tuple([slice(0,x) for x in shape])] ## mesh grid of indexes
#print shape, grid.shape
x = (grid[np.newaxis,...] * vectors.transpose()[(Ellipsis,) + (np.newaxis,)*len(shape)]).sum(axis=1) ## magic
x += origin
#print "X values:"
#print x
## iterate manually over unused axes since map_coordinates won't do it for us
if have_scipy:
extraShape = data.shape[len(axes):] extraShape = data.shape[len(axes):]
output = np.empty(tuple(shape) + extraShape, dtype=data.dtype) output = np.empty(tuple(shape) + extraShape, dtype=data.dtype)
for inds in np.ndindex(*extraShape): for inds in np.ndindex(*extraShape):
@ -462,9 +541,8 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
else: else:
# map_coordinates expects the indexes as the first axis, whereas # map_coordinates expects the indexes as the first axis, whereas
# interpolateArray expects indexes at the last axis. # interpolateArray expects indexes at the last axis.
tr = tuple(range(1,x.ndim)) + (0,) tr = tuple(range(1, x.ndim)) + (0,)
output = interpolateArray(data, x.transpose(tr)) output = interpolateArray(data, x.transpose(tr), order=order)
tr = list(range(output.ndim)) tr = list(range(output.ndim))
trb = [] trb = []
@ -481,18 +559,26 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
else: else:
return output return output
def interpolateArray(data, x, default=0.0):
def interpolateArray(data, x, default=0.0, order=1):
""" """
N-dimensional interpolation similar scipy.ndimage.map_coordinates. N-dimensional interpolation similar to scipy.ndimage.map_coordinates.
This function returns linearly-interpolated values sampled from a regular This function returns linearly-interpolated values sampled from a regular
grid of data. grid of data. It differs from `ndimage.map_coordinates` by allowing broadcasting
within the input array.
*data* is an array of any shape containing the values to be interpolated. ============== ===========================================================================================
*x* is an array with (shape[-1] <= data.ndim) containing the locations **Arguments:**
within *data* to interpolate. *data* Array of any shape containing the values to be interpolated.
*x* Array with (shape[-1] <= data.ndim) containing the locations within *data* to interpolate.
(note: the axes for this argument are transposed relative to the same argument for
`ndimage.map_coordinates`).
*default* Value to return for locations in *x* that are outside the bounds of *data*.
*order* Order of interpolation: 0=nearest, 1=linear.
============== ===========================================================================================
Returns array of shape (x.shape[:-1] + data.shape) Returns array of shape (x.shape[:-1] + data.shape[x.shape[-1]:])
For example, assume we have the following 2D image data:: For example, assume we have the following 2D image data::
@ -535,58 +621,75 @@ def interpolateArray(data, x, default=0.0):
This is useful for interpolating from arrays of colors, vertexes, etc. This is useful for interpolating from arrays of colors, vertexes, etc.
""" """
if order not in (0, 1):
raise ValueError("interpolateArray requires order=0 or 1 (got %s)" % order)
prof = debug.Profiler() prof = debug.Profiler()
nd = data.ndim nd = data.ndim
md = x.shape[-1] md = x.shape[-1]
if md > nd:
raise TypeError("x.shape[-1] must be less than or equal to data.ndim")
# First we generate arrays of indexes that are needed to
# extract the data surrounding each point
fields = np.mgrid[(slice(0,2),) * md]
xmin = np.floor(x).astype(int)
xmax = xmin + 1
indexes = np.concatenate([xmin[np.newaxis, ...], xmax[np.newaxis, ...]])
fieldInds = []
totalMask = np.ones(x.shape[:-1], dtype=bool) # keep track of out-of-bound indexes totalMask = np.ones(x.shape[:-1], dtype=bool) # keep track of out-of-bound indexes
for ax in range(md): if order == 0:
mask = (xmin[...,ax] >= 0) & (x[...,ax] <= data.shape[ax]-1) xinds = np.round(x).astype(int) # NOTE: for 0.5 this rounds to the nearest *even* number
# keep track of points that need to be set to default for ax in range(md):
totalMask &= mask mask = (xinds[...,ax] >= 0) & (xinds[...,ax] <= data.shape[ax]-1)
xinds[...,ax][~mask] = 0
# keep track of points that need to be set to default
totalMask &= mask
result = data[tuple([xinds[...,i] for i in range(xinds.shape[-1])])]
# ..and keep track of indexes that are out of bounds elif order == 1:
# (note that when x[...,ax] == data.shape[ax], then xmax[...,ax] will be out # First we generate arrays of indexes that are needed to
# of bounds, but the interpolation will work anyway) # extract the data surrounding each point
mask &= (xmax[...,ax] < data.shape[ax]) fields = np.mgrid[(slice(0,order+1),) * md]
axisIndex = indexes[...,ax][fields[ax]] xmin = np.floor(x).astype(int)
#axisMask = mask.astype(np.ubyte).reshape((1,)*(fields.ndim-1) + mask.shape) xmax = xmin + 1
axisIndex[axisIndex < 0] = 0 indexes = np.concatenate([xmin[np.newaxis, ...], xmax[np.newaxis, ...]])
axisIndex[axisIndex >= data.shape[ax]] = 0 fieldInds = []
fieldInds.append(axisIndex) for ax in range(md):
prof() mask = (xmin[...,ax] >= 0) & (x[...,ax] <= data.shape[ax]-1)
# keep track of points that need to be set to default
totalMask &= mask
# ..and keep track of indexes that are out of bounds
# (note that when x[...,ax] == data.shape[ax], then xmax[...,ax] will be out
# of bounds, but the interpolation will work anyway)
mask &= (xmax[...,ax] < data.shape[ax])
axisIndex = indexes[...,ax][fields[ax]]
axisIndex[axisIndex < 0] = 0
axisIndex[axisIndex >= data.shape[ax]] = 0
fieldInds.append(axisIndex)
prof()
# Get data values surrounding each requested point
fieldData = data[tuple(fieldInds)]
prof()
# Get data values surrounding each requested point ## Interpolate
# fieldData[..., i] contains all 2**nd values needed to interpolate x[i] s = np.empty((md,) + fieldData.shape, dtype=float)
fieldData = data[tuple(fieldInds)] dx = x - xmin
prof() # reshape fields for arithmetic against dx
for ax in range(md):
## Interpolate f1 = fields[ax].reshape(fields[ax].shape + (1,)*(dx.ndim-1))
s = np.empty((md,) + fieldData.shape, dtype=float) sax = f1 * dx[...,ax] + (1-f1) * (1-dx[...,ax])
dx = x - xmin sax = sax.reshape(sax.shape + (1,) * (s.ndim-1-sax.ndim))
# reshape fields for arithmetic against dx s[ax] = sax
for ax in range(md): s = np.product(s, axis=0)
f1 = fields[ax].reshape(fields[ax].shape + (1,)*(dx.ndim-1)) result = fieldData * s
sax = f1 * dx[...,ax] + (1-f1) * (1-dx[...,ax]) for i in range(md):
sax = sax.reshape(sax.shape + (1,) * (s.ndim-1-sax.ndim)) result = result.sum(axis=0)
s[ax] = sax
s = np.product(s, axis=0)
result = fieldData * s
for i in range(md):
result = result.sum(axis=0)
prof() prof()
totalMask.shape = totalMask.shape + (1,) * (nd - md)
result[~totalMask] = default if totalMask.ndim > 0:
result[~totalMask] = default
else:
if totalMask is False:
result[:] = default
prof() prof()
return result return result
@ -773,12 +876,11 @@ def solveBilinearTransform(points1, points2):
return matrix return matrix
def rescaleData(data, scale, offset, dtype=None): def rescaleData(data, scale, offset, dtype=None, clip=None):
"""Return data rescaled and optionally cast to a new dtype:: """Return data rescaled and optionally cast to a new dtype::
data => (data-offset) * scale data => (data-offset) * scale
Uses scipy.weave (if available) to improve performance.
""" """
if dtype is None: if dtype is None:
dtype = data.dtype dtype = data.dtype
@ -823,9 +925,21 @@ def rescaleData(data, scale, offset, dtype=None):
setConfigOptions(useWeave=False) setConfigOptions(useWeave=False)
#p = np.poly1d([scale, -offset*scale]) #p = np.poly1d([scale, -offset*scale])
#data = p(data).astype(dtype) #d2 = p(data)
d2 = data-offset d2 = data - float(offset)
d2 *= scale d2 *= scale
# Clip before converting dtype to avoid overflow
if dtype.kind in 'ui':
lim = np.iinfo(dtype)
if clip is None:
# don't let rescale cause integer overflow
d2 = np.clip(d2, lim.min, lim.max)
else:
d2 = np.clip(d2, max(clip[0], lim.min), min(clip[1], lim.max))
else:
if clip is not None:
d2 = np.clip(d2, *clip)
data = d2.astype(dtype) data = d2.astype(dtype)
return data return data
@ -847,15 +961,18 @@ def makeRGBA(*args, **kwds):
kwds['useRGBA'] = True kwds['useRGBA'] = True
return makeARGB(*args, **kwds) return makeARGB(*args, **kwds)
def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False): def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
""" """
Convert an array of values into an ARGB array suitable for building QImages, OpenGL textures, etc. Convert an array of values into an ARGB array suitable for building QImages,
OpenGL textures, etc.
Returns the ARGB array (values 0-255) and a boolean indicating whether there is alpha channel data. Returns the ARGB array (unsigned byte) and a boolean indicating whether
This is a two stage process: there is alpha channel data. This is a two stage process:
1) Rescale the data based on the values in the *levels* argument (min, max). 1) Rescale the data based on the values in the *levels* argument (min, max).
2) Determine the final output by passing the rescaled values through a lookup table. 2) Determine the final output by passing the rescaled values through a
lookup table.
Both stages are optional. Both stages are optional.
@ -874,55 +991,70 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
channel). The use of this feature requires that levels.shape[0] == data.shape[-1]. channel). The use of this feature requires that levels.shape[0] == data.shape[-1].
scale The maximum value to which data will be rescaled before being passed through the scale The maximum value to which data will be rescaled before being passed through the
lookup table (or returned if there is no lookup table). By default this will lookup table (or returned if there is no lookup table). By default this will
be set to the length of the lookup table, or 256 is no lookup table is provided. be set to the length of the lookup table, or 255 if no lookup table is provided.
For OpenGL color specifications (as in GLColor4f) use scale=1.0
lut Optional lookup table (array with dtype=ubyte). lut Optional lookup table (array with dtype=ubyte).
Values in data will be converted to color by indexing directly from lut. Values in data will be converted to color by indexing directly from lut.
The output data shape will be input.shape + lut.shape[1:]. The output data shape will be input.shape + lut.shape[1:].
Lookup tables can be built using ColorMap or GradientWidget.
Note: the output of makeARGB will have the same dtype as the lookup table, so
for conversion to QImage, the dtype must be ubyte.
Lookup tables can be built using GradientWidget.
useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures). useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures).
The default is False, which returns in ARGB order for use with QImage The default is False, which returns in ARGB order for use with QImage
(Note that 'ARGB' is a term used by the Qt documentation; the _actual_ order (Note that 'ARGB' is a term used by the Qt documentation; the *actual* order
is BGRA). is BGRA).
============== ================================================================================== ============== ==================================================================================
""" """
profile = debug.Profiler() profile = debug.Profiler()
if data.ndim not in (2, 3):
raise TypeError("data must be 2D or 3D")
if data.ndim == 3 and data.shape[2] > 4:
raise TypeError("data.shape[2] must be <= 4")
if lut is not None and not isinstance(lut, np.ndarray): if lut is not None and not isinstance(lut, np.ndarray):
lut = np.array(lut) lut = np.array(lut)
if levels is not None and not isinstance(levels, np.ndarray):
levels = np.array(levels)
if levels is not None: if levels is None:
if levels.ndim == 1: # automatically decide levels based on data dtype
if len(levels) != 2: if data.dtype.kind == 'u':
raise Exception('levels argument must have length 2') levels = np.array([0, 2**(data.itemsize*8)-1])
elif levels.ndim == 2: elif data.dtype.kind == 'i':
if lut is not None and lut.ndim > 1: s = 2**(data.itemsize*8 - 1)
raise Exception('Cannot make ARGB data when bot levels and lut have ndim > 2') levels = np.array([-s, s-1])
if levels.shape != (data.shape[-1], 2): elif data.dtype.kind == 'b':
raise Exception('levels must have shape (data.shape[-1], 2)') levels = np.array([0,1])
else: else:
print(levels) raise Exception('levels argument is required for float input types')
raise Exception("levels argument must be 1D or 2D.") if not isinstance(levels, np.ndarray):
levels = np.array(levels)
if levels.ndim == 1:
if levels.shape[0] != 2:
raise Exception('levels argument must have length 2')
elif levels.ndim == 2:
if lut is not None and lut.ndim > 1:
raise Exception('Cannot make ARGB data when both levels and lut have ndim > 2')
if levels.shape != (data.shape[-1], 2):
raise Exception('levels must have shape (data.shape[-1], 2)')
else:
raise Exception("levels argument must be 1D or 2D (got shape=%s)." % repr(levels.shape))
profile() profile()
# Decide on maximum scaled value
if scale is None: if scale is None:
if lut is not None: if lut is not None:
scale = lut.shape[0] scale = lut.shape[0] - 1
else: else:
scale = 255. scale = 255.
## Apply levels if given # Decide on the dtype we want after scaling
if lut is None:
dtype = np.ubyte
else:
dtype = np.min_scalar_type(lut.shape[0]-1)
# Apply levels if given
if levels is not None: if levels is not None:
if isinstance(levels, np.ndarray) and levels.ndim == 2: if isinstance(levels, np.ndarray) and levels.ndim == 2:
## we are going to rescale each channel independently # we are going to rescale each channel independently
if levels.shape[0] != data.shape[-1]: if levels.shape[0] != data.shape[-1]:
raise Exception("When rescaling multi-channel data, there must be the same number of levels as channels (data.shape[-1] == levels.shape[0])") raise Exception("When rescaling multi-channel data, there must be the same number of levels as channels (data.shape[-1] == levels.shape[0])")
newData = np.empty(data.shape, dtype=int) newData = np.empty(data.shape, dtype=int)
@ -930,20 +1062,20 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
minVal, maxVal = levels[i] minVal, maxVal = levels[i]
if minVal == maxVal: if minVal == maxVal:
maxVal += 1e-16 maxVal += 1e-16
newData[...,i] = rescaleData(data[...,i], scale/(maxVal-minVal), minVal, dtype=int) newData[...,i] = rescaleData(data[...,i], scale/(maxVal-minVal), minVal, dtype=dtype)
data = newData data = newData
else: else:
# Apply level scaling unless it would have no effect on the data
minVal, maxVal = levels minVal, maxVal = levels
if minVal == maxVal: if minVal != 0 or maxVal != scale:
maxVal += 1e-16 if minVal == maxVal:
if maxVal == minVal: maxVal += 1e-16
data = rescaleData(data, 1, minVal, dtype=int) data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=dtype)
else:
data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int)
profile() profile()
## apply LUT if given # apply LUT if given
if lut is not None: if lut is not None:
data = applyLookupTable(data, lut) data = applyLookupTable(data, lut)
else: else:
@ -952,16 +1084,18 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
profile() profile()
## copy data into ARGB ordered array # this will be the final image array
imgData = np.empty(data.shape[:2]+(4,), dtype=np.ubyte) imgData = np.empty(data.shape[:2]+(4,), dtype=np.ubyte)
profile() profile()
# decide channel order
if useRGBA: if useRGBA:
order = [0,1,2,3] ## array comes out RGBA order = [0,1,2,3] # array comes out RGBA
else: else:
order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image. order = [2,1,0,3] # for some reason, the colors line up as BGR in the final image.
# copy data into image array
if data.ndim == 2: if data.ndim == 2:
# This is tempting: # This is tempting:
# imgData[..., :3] = data[..., np.newaxis] # imgData[..., :3] = data[..., np.newaxis]
@ -976,7 +1110,8 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
imgData[..., i] = data[..., order[i]] imgData[..., i] = data[..., order[i]]
profile() profile()
# add opaque alpha channel if needed
if data.ndim == 2 or data.shape[2] == 3: if data.ndim == 2 or data.shape[2] == 3:
alpha = False alpha = False
imgData[..., 3] = 255 imgData[..., 3] = 255
@ -1106,10 +1241,9 @@ def imageToArray(img, copy=False, transpose=True):
# If this works on all platforms, then there is no need to use np.asarray.. # If this works on all platforms, then there is no need to use np.asarray..
arr = np.frombuffer(ptr, np.ubyte, img.byteCount()) arr = np.frombuffer(ptr, np.ubyte, img.byteCount())
arr = arr.reshape(img.height(), img.width(), 4)
if fmt == img.Format_RGB32: if fmt == img.Format_RGB32:
arr = arr.reshape(img.height(), img.width(), 3) arr[...,3] = 255
elif fmt == img.Format_ARGB32 or fmt == img.Format_ARGB32_Premultiplied:
arr = arr.reshape(img.height(), img.width(), 4)
if copy: if copy:
arr = arr.copy() arr = arr.copy()
@ -1304,22 +1438,17 @@ def arrayToQPath(x, y, connect='all'):
arr[1:-1]['y'] = y arr[1:-1]['y'] = y
# decide which points are connected by lines # decide which points are connected by lines
if connect == 'pairs': if eq(connect, 'all'):
connect = np.empty((n/2,2), dtype=np.int32)
if connect.size != n:
raise Exception("x,y array lengths must be multiple of 2 to use connect='pairs'")
connect[:,0] = 1
connect[:,1] = 0
connect = connect.flatten()
if connect == 'finite':
connect = np.isfinite(x) & np.isfinite(y)
arr[1:-1]['c'] = connect
if connect == 'all':
arr[1:-1]['c'] = 1 arr[1:-1]['c'] = 1
elif eq(connect, 'pairs'):
arr[1:-1]['c'][::2] = 1
arr[1:-1]['c'][1::2] = 0
elif eq(connect, 'finite'):
arr[1:-1]['c'] = np.isfinite(x) & np.isfinite(y)
elif isinstance(connect, np.ndarray): elif isinstance(connect, np.ndarray):
arr[1:-1]['c'] = connect arr[1:-1]['c'] = connect
else: else:
raise Exception('connect argument must be "all", "pairs", or array') raise Exception('connect argument must be "all", "pairs", "finite", or array')
#profiler('fill array') #profiler('fill array')
# write last 0 # write last 0
@ -1510,7 +1639,7 @@ def isocurve(data, level, connected=False, extendToEdge=False, path=False):
#vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme #vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme
vertIndex = i+2*j vertIndex = i+2*j
#print i,j,k," : ", fields[i,j,k], 2**vertIndex #print i,j,k," : ", fields[i,j,k], 2**vertIndex
index += fields[i,j] * 2**vertIndex np.add(index, fields[i,j] * 2**vertIndex, out=index, casting='unsafe')
#print index #print index
#print index #print index
@ -1660,7 +1789,7 @@ def isosurface(data, level):
See Paul Bourke, "Polygonising a Scalar Field" See Paul Bourke, "Polygonising a Scalar Field"
(http://paulbourke.net/geometry/polygonise/) (http://paulbourke.net/geometry/polygonise/)
*data* 3D numpy array of scalar values *data* 3D numpy array of scalar values. Must be contiguous.
*level* The level at which to generate an isosurface *level* The level at which to generate an isosurface
Returns an array of vertex coordinates (Nv, 3) and an array of Returns an array of vertex coordinates (Nv, 3) and an array of
@ -2012,7 +2141,10 @@ def isosurface(data, level):
else: else:
faceShiftTables, edgeShifts, edgeTable, nTableFaces = IsosurfaceDataCache faceShiftTables, edgeShifts, edgeTable, nTableFaces = IsosurfaceDataCache
# We use strides below, which means we need contiguous array input.
# Ideally we can fix this just by removing the dependency on strides.
if not data.flags['C_CONTIGUOUS']:
raise TypeError("isosurface input data must be c-contiguous.")
## mark everything below the isosurface level ## mark everything below the isosurface level
mask = data < level mask = data < level
@ -2026,7 +2158,7 @@ def isosurface(data, level):
for k in [0,1]: for k in [0,1]:
fields[i,j,k] = mask[slices[i], slices[j], slices[k]] fields[i,j,k] = mask[slices[i], slices[j], slices[k]]
vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme
index += fields[i,j,k] * 2**vertIndex np.add(index, fields[i,j,k] * 2**vertIndex, out=index, casting='unsafe')
### Generate table of edges that have been cut ### Generate table of edges that have been cut
cutEdges = np.zeros([x+1 for x in index.shape]+[3], dtype=np.uint32) cutEdges = np.zeros([x+1 for x in index.shape]+[3], dtype=np.uint32)
@ -2095,7 +2227,7 @@ def isosurface(data, level):
### expensive: ### expensive:
verts = faceShiftTables[i][cellInds] verts = faceShiftTables[i][cellInds]
#profiler() #profiler()
verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges np.add(verts[...,:3], cells[:,np.newaxis,np.newaxis,:], out=verts[...,:3], casting='unsafe') ## we now have indexes into cutEdges
verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:]) verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:])
#profiler() #profiler()

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