PyQtGraph release 0.10.0
This commit is contained in:
commit
1426e334e1
19
.coveragerc
Normal file
19
.coveragerc
Normal 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
110
.gitignore
vendored
@ -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*
|
||||||
|
|
||||||
|
154
.travis.yml
154
.travis.yml
@ -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 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
|
||||||
|
56
CHANGELOG
56
CHANGELOG
@ -1,3 +1,59 @@
|
|||||||
|
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
|
pyqtgraph-0.9.9
|
||||||
|
|
||||||
API / behavior changes:
|
API / behavior changes:
|
||||||
|
@ -49,3 +49,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.
|
||||||
|
|
||||||
|
11
README.md
11
README.md
@ -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
|
||||||
=========
|
=========
|
||||||
|
|
||||||
@ -33,13 +36,17 @@ Contributors
|
|||||||
* Nicholas TJ
|
* Nicholas TJ
|
||||||
* John David Reaver
|
* John David Reaver
|
||||||
* David Kaplan
|
* 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.
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -6,6 +6,7 @@ Contents:
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
config_options
|
||||||
functions
|
functions
|
||||||
graphicsItems/index
|
graphicsItems/index
|
||||||
widgets/index
|
widgets/index
|
||||||
|
@ -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.10'
|
version = '0.10.0'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.9.10'
|
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.
|
||||||
|
41
doc/source/config_options.rst
Normal file
41
doc/source/config_options.rst
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
RawImageWidget
|
|
||||||
==============
|
|
||||||
|
|
||||||
.. autoclass:: pyqtgraph.RawImageWidget
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. automethod:: pyqtgraph.RawImageWidget.__init__
|
|
||||||
|
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
45
examples/InfiniteLine.py
Normal 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_()
|
@ -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')
|
||||||
|
@ -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')
|
||||||
|
@ -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([])
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -19,12 +19,18 @@ 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)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
38
examples/Symbols.py
Executable file
38
examples/Symbols.py
Executable 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_()
|
@ -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]
|
||||||
|
@ -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/>
|
||||||
|
@ -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
|
||||||
|
199
examples/VideoTemplate_pyqt5.py
Normal file
199
examples/VideoTemplate_pyqt5.py
Normal 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
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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 = ''
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
93
examples/exampleLoaderTemplate_pyqt5.py
Normal file
93
examples/exampleLoaderTemplate_pyqt5.py
Normal 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"))
|
||||||
|
|
@ -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))
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
52
examples/infiniteline_performance.py
Normal file
52
examples/infiniteline_performance.py
Normal 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_()
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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():
|
||||||
|
@ -1 +1 @@
|
|||||||
from relativity import *
|
from .relativity import *
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
37
examples/test_examples.py
Normal file
37
examples/test_examples.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from __future__ import print_function, division, absolute_import
|
||||||
|
from pyqtgraph import Qt
|
||||||
|
from . import utils
|
||||||
|
import itertools
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# 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()
|
@ -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
165
examples/utils.py
Normal 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')
|
@ -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
|
||||||
@ -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
|
||||||
|
@ -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)
|
||||||
|
64
pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt5.py
Normal file
64
pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt5.py
Normal 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
|
@ -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"""
|
||||||
|
160
pyqtgraph/Qt.py
160
pyqtgraph/Qt.py
@ -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))
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -4,7 +4,7 @@ PyQtGraph - Scientific Graphics and GUI Library for Python
|
|||||||
www.pyqtgraph.org
|
www.pyqtgraph.org
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '0.9.10'
|
__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]
|
||||||
|
|
||||||
|
|
||||||
@ -345,7 +363,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:
|
||||||
|
96
pyqtgraph/canvas/CanvasTemplate_pyqt5.py
Normal file
96
pyqtgraph/canvas/CanvasTemplate_pyqt5.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Form implementation generated from reading ui file './pyqtgraph/canvas/CanvasTemplate.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(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.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||||
|
self.storeSvgBtn = QtWidgets.QPushButton(self.layoutWidget)
|
||||||
|
self.storeSvgBtn.setObjectName("storeSvgBtn")
|
||||||
|
self.gridLayout_2.addWidget(self.storeSvgBtn, 1, 0, 1, 1)
|
||||||
|
self.storePngBtn = QtWidgets.QPushButton(self.layoutWidget)
|
||||||
|
self.storePngBtn.setObjectName("storePngBtn")
|
||||||
|
self.gridLayout_2.addWidget(self.storePngBtn, 1, 1, 1, 1)
|
||||||
|
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, 3, 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, 6, 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, 7, 0, 1, 2)
|
||||||
|
self.ctrlLayout = QtWidgets.QGridLayout()
|
||||||
|
self.ctrlLayout.setSpacing(0)
|
||||||
|
self.ctrlLayout.setObjectName("ctrlLayout")
|
||||||
|
self.gridLayout_2.addLayout(self.ctrlLayout, 11, 0, 1, 2)
|
||||||
|
self.resetTransformsBtn = QtWidgets.QPushButton(self.layoutWidget)
|
||||||
|
self.resetTransformsBtn.setObjectName("resetTransformsBtn")
|
||||||
|
self.gridLayout_2.addWidget(self.resetTransformsBtn, 8, 0, 1, 1)
|
||||||
|
self.mirrorSelectionBtn = QtWidgets.QPushButton(self.layoutWidget)
|
||||||
|
self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn")
|
||||||
|
self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1)
|
||||||
|
self.reflectSelectionBtn = QtWidgets.QPushButton(self.layoutWidget)
|
||||||
|
self.reflectSelectionBtn.setObjectName("reflectSelectionBtn")
|
||||||
|
self.gridLayout_2.addWidget(self.reflectSelectionBtn, 4, 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.storeSvgBtn.setText(_translate("Form", "Store SVG"))
|
||||||
|
self.storePngBtn.setText(_translate("Form", "Store PNG"))
|
||||||
|
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
|
56
pyqtgraph/canvas/TransformGuiTemplate_pyqt5.py
Normal file
56
pyqtgraph/canvas/TransformGuiTemplate_pyqt5.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Form implementation generated from reading ui file './pyqtgraph/canvas/TransformGuiTemplate.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(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.setSpacing(1)
|
||||||
|
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
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"))
|
||||||
|
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
107
pyqtgraph/console/template_pyqt5.py
Normal file
107
pyqtgraph/console/template_pyqt5.py
Normal 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
|
@ -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):
|
||||||
|
@ -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())
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
0
pyqtgraph/exporters/tests/__init__.py
Normal file
0
pyqtgraph/exporters/tests/__init__.py
Normal 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()
|
||||||
|
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
67
pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt5.py
Normal file
67
pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt5.py
Normal 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
|
@ -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)
|
|
55
pyqtgraph/flowchart/FlowchartTemplate_pyqt5.py
Normal file
55
pyqtgraph/flowchart/FlowchartTemplate_pyqt5.py
Normal 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
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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)))
|
|
@ -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"""
|
||||||
|
@ -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.
|
||||||
|
@ -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),
|
||||||
@ -27,14 +37,6 @@ SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY'
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
@ -243,6 +245,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 +295,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 +307,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 +337,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,6 +374,41 @@ 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 affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs):
|
def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs):
|
||||||
"""
|
"""
|
||||||
@ -447,11 +489,9 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
|
|
||||||
## Build array of sample locations.
|
## Build array of sample locations.
|
||||||
grid = np.mgrid[tuple([slice(0,x) for x in shape])] ## mesh grid of indexes
|
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 = (grid[np.newaxis,...] * vectors.transpose()[(Ellipsis,) + (np.newaxis,)*len(shape)]).sum(axis=1) ## magic
|
||||||
x += origin
|
x += origin
|
||||||
#print "X values:"
|
|
||||||
#print x
|
|
||||||
## iterate manually over unused axes since map_coordinates won't do it for us
|
## iterate manually over unused axes since map_coordinates won't do it for us
|
||||||
if have_scipy:
|
if have_scipy:
|
||||||
extraShape = data.shape[len(axes):]
|
extraShape = data.shape[len(axes):]
|
||||||
@ -464,7 +504,6 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
# 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))
|
||||||
|
|
||||||
|
|
||||||
tr = list(range(output.ndim))
|
tr = list(range(output.ndim))
|
||||||
trb = []
|
trb = []
|
||||||
@ -483,7 +522,7 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
|
|||||||
|
|
||||||
def interpolateArray(data, x, default=0.0):
|
def interpolateArray(data, x, default=0.0):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -492,7 +531,7 @@ def interpolateArray(data, x, default=0.0):
|
|||||||
*x* is an array with (shape[-1] <= data.ndim) containing the locations
|
*x* is an array with (shape[-1] <= data.ndim) containing the locations
|
||||||
within *data* to interpolate.
|
within *data* to interpolate.
|
||||||
|
|
||||||
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,11 +574,12 @@ 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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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
|
# First we generate arrays of indexes that are needed to
|
||||||
# extract the data surrounding each point
|
# extract the data surrounding each point
|
||||||
@ -552,21 +592,19 @@ def interpolateArray(data, x, default=0.0):
|
|||||||
for ax in range(md):
|
for ax in range(md):
|
||||||
mask = (xmin[...,ax] >= 0) & (x[...,ax] <= data.shape[ax]-1)
|
mask = (xmin[...,ax] >= 0) & (x[...,ax] <= data.shape[ax]-1)
|
||||||
# keep track of points that need to be set to default
|
# keep track of points that need to be set to default
|
||||||
totalMask &= mask
|
totalMask &= mask
|
||||||
|
|
||||||
# ..and keep track of indexes that are out of bounds
|
# ..and keep track of indexes that are out of bounds
|
||||||
# (note that when x[...,ax] == data.shape[ax], then xmax[...,ax] will be out
|
# (note that when x[...,ax] == data.shape[ax], then xmax[...,ax] will be out
|
||||||
# of bounds, but the interpolation will work anyway)
|
# of bounds, but the interpolation will work anyway)
|
||||||
mask &= (xmax[...,ax] < data.shape[ax])
|
mask &= (xmax[...,ax] < data.shape[ax])
|
||||||
axisIndex = indexes[...,ax][fields[ax]]
|
axisIndex = indexes[...,ax][fields[ax]]
|
||||||
#axisMask = mask.astype(np.ubyte).reshape((1,)*(fields.ndim-1) + mask.shape)
|
|
||||||
axisIndex[axisIndex < 0] = 0
|
axisIndex[axisIndex < 0] = 0
|
||||||
axisIndex[axisIndex >= data.shape[ax]] = 0
|
axisIndex[axisIndex >= data.shape[ax]] = 0
|
||||||
fieldInds.append(axisIndex)
|
fieldInds.append(axisIndex)
|
||||||
prof()
|
prof()
|
||||||
|
|
||||||
# Get data values surrounding each requested point
|
# Get data values surrounding each requested point
|
||||||
# fieldData[..., i] contains all 2**nd values needed to interpolate x[i]
|
|
||||||
fieldData = data[tuple(fieldInds)]
|
fieldData = data[tuple(fieldInds)]
|
||||||
prof()
|
prof()
|
||||||
|
|
||||||
@ -585,8 +623,13 @@ def interpolateArray(data, x, default=0.0):
|
|||||||
result = result.sum(axis=0)
|
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 +816,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 +865,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 +901,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 +931,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 +1002,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 +1024,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 +1050,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 +1181,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 +1378,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 +1579,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 +1729,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 +2081,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 +2098,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 +2167,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()
|
||||||
|
|
||||||
|
@ -91,6 +91,11 @@ class CurvePoint(GraphicsObject):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1):
|
def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1):
|
||||||
|
# In Python 3, a bytes object needs to be used as a property name in
|
||||||
|
# QPropertyAnimation. PyQt stopped automatically encoding a str when a
|
||||||
|
# QByteArray was expected in v5.5 (see qbytearray.sip).
|
||||||
|
if not isinstance(prop, bytes):
|
||||||
|
prop = prop.encode('latin-1')
|
||||||
anim = QtCore.QPropertyAnimation(self, prop)
|
anim = QtCore.QPropertyAnimation(self, prop)
|
||||||
anim.setDuration(duration)
|
anim.setDuration(duration)
|
||||||
anim.setStartValue(start)
|
anim.setStartValue(start)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from ..Qt import QtGui
|
from ..Qt import QtGui, USE_PYQT5, USE_PYQT4, USE_PYSIDE
|
||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
from .PlotDataItem import PlotDataItem
|
from .PlotDataItem import PlotDataItem
|
||||||
from .PlotCurveItem import PlotCurveItem
|
from .PlotCurveItem import PlotCurveItem
|
||||||
@ -7,17 +7,24 @@ class FillBetweenItem(QtGui.QGraphicsPathItem):
|
|||||||
"""
|
"""
|
||||||
GraphicsItem filling the space between two PlotDataItems.
|
GraphicsItem filling the space between two PlotDataItems.
|
||||||
"""
|
"""
|
||||||
def __init__(self, curve1=None, curve2=None, brush=None):
|
def __init__(self, curve1=None, curve2=None, brush=None, pen=None):
|
||||||
QtGui.QGraphicsPathItem.__init__(self)
|
QtGui.QGraphicsPathItem.__init__(self)
|
||||||
self.curves = None
|
self.curves = None
|
||||||
if curve1 is not None and curve2 is not None:
|
if curve1 is not None and curve2 is not None:
|
||||||
self.setCurves(curve1, curve2)
|
self.setCurves(curve1, curve2)
|
||||||
elif curve1 is not None or curve2 is not None:
|
elif curve1 is not None or curve2 is not None:
|
||||||
raise Exception("Must specify two curves to fill between.")
|
raise Exception("Must specify two curves to fill between.")
|
||||||
|
|
||||||
if brush is not None:
|
if brush is not None:
|
||||||
self.setBrush(fn.mkBrush(brush))
|
self.setBrush(brush)
|
||||||
|
self.setPen(pen)
|
||||||
self.updatePath()
|
self.updatePath()
|
||||||
|
|
||||||
|
def setBrush(self, *args, **kwds):
|
||||||
|
QtGui.QGraphicsPathItem.setBrush(self, fn.mkBrush(*args, **kwds))
|
||||||
|
|
||||||
|
def setPen(self, *args, **kwds):
|
||||||
|
QtGui.QGraphicsPathItem.setPen(self, fn.mkPen(*args, **kwds))
|
||||||
|
|
||||||
def setCurves(self, curve1, curve2):
|
def setCurves(self, curve1, curve2):
|
||||||
"""Set the curves to fill between.
|
"""Set the curves to fill between.
|
||||||
@ -26,14 +33,13 @@ class FillBetweenItem(QtGui.QGraphicsPathItem):
|
|||||||
|
|
||||||
Added in version 0.9.9
|
Added in version 0.9.9
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.curves is not None:
|
if self.curves is not None:
|
||||||
for c in self.curves:
|
for c in self.curves:
|
||||||
try:
|
try:
|
||||||
c.sigPlotChanged.disconnect(self.curveChanged)
|
c.sigPlotChanged.disconnect(self.curveChanged)
|
||||||
except (TypeError, RuntimeError):
|
except (TypeError, RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
curves = [curve1, curve2]
|
curves = [curve1, curve2]
|
||||||
for c in curves:
|
for c in curves:
|
||||||
if not isinstance(c, PlotDataItem) and not isinstance(c, PlotCurveItem):
|
if not isinstance(c, PlotDataItem) and not isinstance(c, PlotCurveItem):
|
||||||
@ -43,7 +49,7 @@ class FillBetweenItem(QtGui.QGraphicsPathItem):
|
|||||||
curve2.sigPlotChanged.connect(self.curveChanged)
|
curve2.sigPlotChanged.connect(self.curveChanged)
|
||||||
self.setZValue(min(curve1.zValue(), curve2.zValue())-1)
|
self.setZValue(min(curve1.zValue(), curve2.zValue())-1)
|
||||||
self.curveChanged()
|
self.curveChanged()
|
||||||
|
|
||||||
def setBrush(self, *args, **kwds):
|
def setBrush(self, *args, **kwds):
|
||||||
"""Change the fill brush. Acceps the same arguments as pg.mkBrush()"""
|
"""Change the fill brush. Acceps the same arguments as pg.mkBrush()"""
|
||||||
QtGui.QGraphicsPathItem.setBrush(self, fn.mkBrush(*args, **kwds))
|
QtGui.QGraphicsPathItem.setBrush(self, fn.mkBrush(*args, **kwds))
|
||||||
@ -61,13 +67,17 @@ class FillBetweenItem(QtGui.QGraphicsPathItem):
|
|||||||
paths.append(c.curve.getPath())
|
paths.append(c.curve.getPath())
|
||||||
elif isinstance(c, PlotCurveItem):
|
elif isinstance(c, PlotCurveItem):
|
||||||
paths.append(c.getPath())
|
paths.append(c.getPath())
|
||||||
|
|
||||||
path = QtGui.QPainterPath()
|
path = QtGui.QPainterPath()
|
||||||
p1 = paths[0].toSubpathPolygons()
|
transform = QtGui.QTransform()
|
||||||
p2 = paths[1].toReversed().toSubpathPolygons()
|
ps1 = paths[0].toSubpathPolygons(transform)
|
||||||
if len(p1) == 0 or len(p2) == 0:
|
ps2 = paths[1].toReversed().toSubpathPolygons(transform)
|
||||||
|
ps2.reverse()
|
||||||
|
if len(ps1) == 0 or len(ps2) == 0:
|
||||||
self.setPath(QtGui.QPainterPath())
|
self.setPath(QtGui.QPainterPath())
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
path.addPolygon(p1[0] + p2[0])
|
for p1, p2 in zip(ps1, ps2):
|
||||||
|
path.addPolygon(p1 + p2)
|
||||||
self.setPath(path)
|
self.setPath(path)
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
|
import weakref
|
||||||
|
import numpy as np
|
||||||
from ..Qt import QtGui, QtCore
|
from ..Qt import QtGui, QtCore
|
||||||
from ..python2_3 import sortList
|
from ..python2_3 import sortList
|
||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
from .GraphicsObject import GraphicsObject
|
from .GraphicsObject import GraphicsObject
|
||||||
from .GraphicsWidget import GraphicsWidget
|
from .GraphicsWidget import GraphicsWidget
|
||||||
from ..widgets.SpinBox import SpinBox
|
from ..widgets.SpinBox import SpinBox
|
||||||
import weakref
|
|
||||||
from ..pgcollections import OrderedDict
|
from ..pgcollections import OrderedDict
|
||||||
from ..colormap import ColorMap
|
from ..colormap import ColorMap
|
||||||
|
from ..python2_3 import cmp
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
__all__ = ['TickSliderItem', 'GradientEditorItem']
|
__all__ = ['TickSliderItem', 'GradientEditorItem']
|
||||||
|
|
||||||
|
|
||||||
Gradients = OrderedDict([
|
Gradients = OrderedDict([
|
||||||
('thermal', {'ticks': [(0.3333, (185, 0, 0, 255)), (0.6666, (255, 220, 0, 255)), (1, (255, 255, 255, 255)), (0, (0, 0, 0, 255))], 'mode': 'rgb'}),
|
('thermal', {'ticks': [(0.3333, (185, 0, 0, 255)), (0.6666, (255, 220, 0, 255)), (1, (255, 255, 255, 255)), (0, (0, 0, 0, 255))], 'mode': 'rgb'}),
|
||||||
('flame', {'ticks': [(0.2, (7, 0, 220, 255)), (0.5, (236, 0, 134, 255)), (0.8, (246, 246, 0, 255)), (1.0, (255, 255, 255, 255)), (0.0, (0, 0, 0, 255))], 'mode': 'rgb'}),
|
('flame', {'ticks': [(0.2, (7, 0, 220, 255)), (0.5, (236, 0, 134, 255)), (0.8, (246, 246, 0, 255)), (1.0, (255, 255, 255, 255)), (0.0, (0, 0, 0, 255))], 'mode': 'rgb'}),
|
||||||
@ -24,7 +24,12 @@ Gradients = OrderedDict([
|
|||||||
('grey', {'ticks': [(0.0, (0, 0, 0, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'}),
|
('grey', {'ticks': [(0.0, (0, 0, 0, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def addGradientListToDocstring():
|
||||||
|
"""Decorator to add list of current pre-defined gradients to the end of a function docstring."""
|
||||||
|
def dec(fn):
|
||||||
|
fn.__doc__ = fn.__doc__ + str(Gradients.keys()).strip('[').strip(']')
|
||||||
|
return fn
|
||||||
|
return dec
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -117,16 +122,20 @@ class TickSliderItem(GraphicsWidget):
|
|||||||
self.resetTransform()
|
self.resetTransform()
|
||||||
ort = orientation
|
ort = orientation
|
||||||
if ort == 'top':
|
if ort == 'top':
|
||||||
self.scale(1, -1)
|
transform = QtGui.QTransform.fromScale(1, -1)
|
||||||
self.translate(0, -self.height())
|
transform.translate(0, -self.height())
|
||||||
|
self.setTransform(transform)
|
||||||
elif ort == 'left':
|
elif ort == 'left':
|
||||||
self.rotate(270)
|
transform = QtGui.QTransform()
|
||||||
self.scale(1, -1)
|
transform.rotate(270)
|
||||||
self.translate(-self.height(), -self.maxDim)
|
transform.scale(1, -1)
|
||||||
|
transform.translate(-self.height(), -self.maxDim)
|
||||||
|
self.setTransform(transform)
|
||||||
elif ort == 'right':
|
elif ort == 'right':
|
||||||
self.rotate(270)
|
transform = QtGui.QTransform()
|
||||||
self.translate(-self.height(), 0)
|
transform.rotate(270)
|
||||||
#self.setPos(0, -self.height())
|
transform.translate(-self.height(), 0)
|
||||||
|
self.setTransform(transform)
|
||||||
elif ort != 'bottom':
|
elif ort != 'bottom':
|
||||||
raise Exception("%s is not a valid orientation. Options are 'left', 'right', 'top', and 'bottom'" %str(ort))
|
raise Exception("%s is not a valid orientation. Options are 'left', 'right', 'top', and 'bottom'" %str(ort))
|
||||||
|
|
||||||
@ -238,7 +247,7 @@ class TickSliderItem(GraphicsWidget):
|
|||||||
self.addTick(pos.x()/self.length)
|
self.addTick(pos.x()/self.length)
|
||||||
elif ev.button() == QtCore.Qt.RightButton:
|
elif ev.button() == QtCore.Qt.RightButton:
|
||||||
self.showMenu(ev)
|
self.showMenu(ev)
|
||||||
|
|
||||||
#if ev.button() == QtCore.Qt.RightButton:
|
#if ev.button() == QtCore.Qt.RightButton:
|
||||||
#if self.moving:
|
#if self.moving:
|
||||||
#ev.accept()
|
#ev.accept()
|
||||||
@ -468,11 +477,12 @@ class GradientEditorItem(TickSliderItem):
|
|||||||
act = self.sender()
|
act = self.sender()
|
||||||
self.loadPreset(act.name)
|
self.loadPreset(act.name)
|
||||||
|
|
||||||
|
@addGradientListToDocstring()
|
||||||
def loadPreset(self, name):
|
def loadPreset(self, name):
|
||||||
"""
|
"""
|
||||||
Load a predefined gradient.
|
Load a predefined gradient. Currently defined gradients are:
|
||||||
|
"""## TODO: provide image with names of defined gradients
|
||||||
""" ## TODO: provide image with names of defined gradients
|
|
||||||
#global Gradients
|
#global Gradients
|
||||||
self.restoreState(Gradients[name])
|
self.restoreState(Gradients[name])
|
||||||
|
|
||||||
@ -783,11 +793,15 @@ class GradientEditorItem(TickSliderItem):
|
|||||||
self.updateGradient()
|
self.updateGradient()
|
||||||
self.sigGradientChangeFinished.emit(self)
|
self.sigGradientChangeFinished.emit(self)
|
||||||
|
|
||||||
|
|
||||||
class Tick(QtGui.QGraphicsObject): ## NOTE: Making this a subclass of GraphicsObject instead results in
|
class Tick(QtGui.QGraphicsWidget): ## NOTE: Making this a subclass of GraphicsObject instead results in
|
||||||
## activating this bug: https://bugreports.qt-project.org/browse/PYSIDE-86
|
## activating this bug: https://bugreports.qt-project.org/browse/PYSIDE-86
|
||||||
## private class
|
## private class
|
||||||
|
|
||||||
|
# When making Tick a subclass of QtGui.QGraphicsObject as origin,
|
||||||
|
# ..GraphicsScene.items(self, *args) will get Tick object as a
|
||||||
|
# class of QtGui.QMultimediaWidgets.QGraphicsVideoItem in python2.7-PyQt5(5.4.0)
|
||||||
|
|
||||||
sigMoving = QtCore.Signal(object)
|
sigMoving = QtCore.Signal(object)
|
||||||
sigMoved = QtCore.Signal(object)
|
sigMoved = QtCore.Signal(object)
|
||||||
|
|
||||||
@ -805,7 +819,7 @@ class Tick(QtGui.QGraphicsObject): ## NOTE: Making this a subclass of GraphicsO
|
|||||||
self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale))
|
self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale))
|
||||||
self.pg.closeSubpath()
|
self.pg.closeSubpath()
|
||||||
|
|
||||||
QtGui.QGraphicsObject.__init__(self)
|
QtGui.QGraphicsWidget.__init__(self)
|
||||||
self.setPos(pos[0], pos[1])
|
self.setPos(pos[0], pos[1])
|
||||||
if self.movable:
|
if self.movable:
|
||||||
self.setZValue(1)
|
self.setZValue(1)
|
||||||
|
@ -37,9 +37,6 @@ class GraphicsItem(object):
|
|||||||
if register:
|
if register:
|
||||||
GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items()
|
GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getViewWidget(self):
|
def getViewWidget(self):
|
||||||
"""
|
"""
|
||||||
Return the view widget for this item.
|
Return the view widget for this item.
|
||||||
@ -95,7 +92,6 @@ class GraphicsItem(object):
|
|||||||
def forgetViewBox(self):
|
def forgetViewBox(self):
|
||||||
self._viewBox = None
|
self._viewBox = None
|
||||||
|
|
||||||
|
|
||||||
def deviceTransform(self, viewportTransform=None):
|
def deviceTransform(self, viewportTransform=None):
|
||||||
"""
|
"""
|
||||||
Return the transform that converts local item coordinates to device coordinates (usually pixels).
|
Return the transform that converts local item coordinates to device coordinates (usually pixels).
|
||||||
|
@ -139,6 +139,9 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
#self.region.setBounds([vr.top(), vr.bottom()])
|
#self.region.setBounds([vr.top(), vr.bottom()])
|
||||||
|
|
||||||
def setImageItem(self, img):
|
def setImageItem(self, img):
|
||||||
|
"""Set an ImageItem to have its levels and LUT automatically controlled
|
||||||
|
by this HistogramLUTItem.
|
||||||
|
"""
|
||||||
self.imageItem = weakref.ref(img)
|
self.imageItem = weakref.ref(img)
|
||||||
img.sigImageChanged.connect(self.imageChanged)
|
img.sigImageChanged.connect(self.imageChanged)
|
||||||
img.setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
img.setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
||||||
@ -163,6 +166,9 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
self.sigLookupTableChanged.emit(self)
|
self.sigLookupTableChanged.emit(self)
|
||||||
|
|
||||||
def getLookupTable(self, img=None, n=None, alpha=None):
|
def getLookupTable(self, img=None, n=None, alpha=None):
|
||||||
|
"""Return a lookup table from the color gradient defined by this
|
||||||
|
HistogramLUTItem.
|
||||||
|
"""
|
||||||
if n is None:
|
if n is None:
|
||||||
if img.dtype == np.uint8:
|
if img.dtype == np.uint8:
|
||||||
n = 256
|
n = 256
|
||||||
@ -173,8 +179,8 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
return self.lut
|
return self.lut
|
||||||
|
|
||||||
def regionChanged(self):
|
def regionChanged(self):
|
||||||
#if self.imageItem is not None:
|
if self.imageItem() is not None:
|
||||||
#self.imageItem.setLevels(self.region.getRegion())
|
self.imageItem().setLevels(self.region.getRegion())
|
||||||
self.sigLevelChangeFinished.emit(self)
|
self.sigLevelChangeFinished.emit(self)
|
||||||
#self.update()
|
#self.update()
|
||||||
|
|
||||||
@ -199,7 +205,11 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
profiler('set region')
|
profiler('set region')
|
||||||
|
|
||||||
def getLevels(self):
|
def getLevels(self):
|
||||||
|
"""Return the min and max levels.
|
||||||
|
"""
|
||||||
return self.region.getRegion()
|
return self.region.getRegion()
|
||||||
|
|
||||||
def setLevels(self, mn, mx):
|
def setLevels(self, mn, mx):
|
||||||
|
"""Set the min and max levels.
|
||||||
|
"""
|
||||||
self.region.setRegion([mn, mx])
|
self.region.setRegion([mn, mx])
|
||||||
|
@ -7,6 +7,8 @@ from .. import functions as fn
|
|||||||
from .. import debug as debug
|
from .. import debug as debug
|
||||||
from .GraphicsObject import GraphicsObject
|
from .GraphicsObject import GraphicsObject
|
||||||
from ..Point import Point
|
from ..Point import Point
|
||||||
|
from .. import getConfigOption
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ImageItem']
|
__all__ = ['ImageItem']
|
||||||
|
|
||||||
@ -28,7 +30,6 @@ class ImageItem(GraphicsObject):
|
|||||||
for controlling the levels and lookup table used to display the image.
|
for controlling the levels and lookup table used to display the image.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
sigImageChanged = QtCore.Signal()
|
sigImageChanged = QtCore.Signal()
|
||||||
sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu
|
sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu
|
||||||
|
|
||||||
@ -47,6 +48,12 @@ class ImageItem(GraphicsObject):
|
|||||||
self.lut = None
|
self.lut = None
|
||||||
self.autoDownsample = False
|
self.autoDownsample = False
|
||||||
|
|
||||||
|
self.axisOrder = getConfigOption('imageAxisOrder')
|
||||||
|
|
||||||
|
# In some cases, we use a modified lookup table to handle both rescaling
|
||||||
|
# and LUT more efficiently
|
||||||
|
self._effectiveLut = None
|
||||||
|
|
||||||
self.drawKernel = None
|
self.drawKernel = None
|
||||||
self.border = None
|
self.border = None
|
||||||
self.removable = False
|
self.removable = False
|
||||||
@ -74,11 +81,6 @@ class ImageItem(GraphicsObject):
|
|||||||
"""
|
"""
|
||||||
self.paintMode = mode
|
self.paintMode = mode
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
## use setOpacity instead.
|
|
||||||
#def setAlpha(self, alpha):
|
|
||||||
#self.setOpacity(alpha)
|
|
||||||
#self.updateImage()
|
|
||||||
|
|
||||||
def setBorder(self, b):
|
def setBorder(self, b):
|
||||||
self.border = fn.mkPen(b)
|
self.border = fn.mkPen(b)
|
||||||
@ -87,28 +89,20 @@ class ImageItem(GraphicsObject):
|
|||||||
def width(self):
|
def width(self):
|
||||||
if self.image is None:
|
if self.image is None:
|
||||||
return None
|
return None
|
||||||
return self.image.shape[0]
|
axis = 0 if self.axisOrder == 'col-major' else 1
|
||||||
|
return self.image.shape[axis]
|
||||||
|
|
||||||
def height(self):
|
def height(self):
|
||||||
if self.image is None:
|
if self.image is None:
|
||||||
return None
|
return None
|
||||||
return self.image.shape[1]
|
axis = 1 if self.axisOrder == 'col-major' else 0
|
||||||
|
return self.image.shape[axis]
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
if self.image is None:
|
if self.image is None:
|
||||||
return QtCore.QRectF(0., 0., 0., 0.)
|
return QtCore.QRectF(0., 0., 0., 0.)
|
||||||
return QtCore.QRectF(0., 0., float(self.width()), float(self.height()))
|
return QtCore.QRectF(0., 0., float(self.width()), float(self.height()))
|
||||||
|
|
||||||
#def setClipLevel(self, level=None):
|
|
||||||
#self.clipLevel = level
|
|
||||||
#self.updateImage()
|
|
||||||
|
|
||||||
#def paint(self, p, opt, widget):
|
|
||||||
#pass
|
|
||||||
#if self.pixmap is not None:
|
|
||||||
#p.drawPixmap(0, 0, self.pixmap)
|
|
||||||
#print "paint"
|
|
||||||
|
|
||||||
def setLevels(self, levels, update=True):
|
def setLevels(self, levels, update=True):
|
||||||
"""
|
"""
|
||||||
Set image scaling levels. Can be one of:
|
Set image scaling levels. Can be one of:
|
||||||
@ -119,9 +113,13 @@ class ImageItem(GraphicsObject):
|
|||||||
Only the first format is compatible with lookup tables. See :func:`makeARGB <pyqtgraph.makeARGB>`
|
Only the first format is compatible with lookup tables. See :func:`makeARGB <pyqtgraph.makeARGB>`
|
||||||
for more details on how levels are applied.
|
for more details on how levels are applied.
|
||||||
"""
|
"""
|
||||||
self.levels = levels
|
if levels is not None:
|
||||||
if update:
|
levels = np.asarray(levels)
|
||||||
self.updateImage()
|
if not fn.eq(levels, self.levels):
|
||||||
|
self.levels = levels
|
||||||
|
self._effectiveLut = None
|
||||||
|
if update:
|
||||||
|
self.updateImage()
|
||||||
|
|
||||||
def getLevels(self):
|
def getLevels(self):
|
||||||
return self.levels
|
return self.levels
|
||||||
@ -137,9 +135,11 @@ class ImageItem(GraphicsObject):
|
|||||||
Ordinarily, this table is supplied by a :class:`HistogramLUTItem <pyqtgraph.HistogramLUTItem>`
|
Ordinarily, this table is supplied by a :class:`HistogramLUTItem <pyqtgraph.HistogramLUTItem>`
|
||||||
or :class:`GradientEditorItem <pyqtgraph.GradientEditorItem>`.
|
or :class:`GradientEditorItem <pyqtgraph.GradientEditorItem>`.
|
||||||
"""
|
"""
|
||||||
self.lut = lut
|
if lut is not self.lut:
|
||||||
if update:
|
self.lut = lut
|
||||||
self.updateImage()
|
self._effectiveLut = None
|
||||||
|
if update:
|
||||||
|
self.updateImage()
|
||||||
|
|
||||||
def setAutoDownsample(self, ads):
|
def setAutoDownsample(self, ads):
|
||||||
"""
|
"""
|
||||||
@ -152,7 +152,11 @@ class ImageItem(GraphicsObject):
|
|||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setOpts(self, update=True, **kargs):
|
def setOpts(self, update=True, **kargs):
|
||||||
|
if 'axisOrder' in kargs:
|
||||||
|
val = kargs['axisOrder']
|
||||||
|
if val not in ('row-major', 'col-major'):
|
||||||
|
raise ValueError('axisOrder must be either "row-major" or "col-major"')
|
||||||
|
self.axisOrder = val
|
||||||
if 'lut' in kargs:
|
if 'lut' in kargs:
|
||||||
self.setLookupTable(kargs['lut'], update=update)
|
self.setLookupTable(kargs['lut'], update=update)
|
||||||
if 'levels' in kargs:
|
if 'levels' in kargs:
|
||||||
@ -195,7 +199,7 @@ class ImageItem(GraphicsObject):
|
|||||||
image (numpy array) Specifies the image data. May be 2D (width, height) or
|
image (numpy array) Specifies the image data. May be 2D (width, height) or
|
||||||
3D (width, height, RGBa). The array dtype must be integer or floating
|
3D (width, height, RGBa). The array dtype must be integer or floating
|
||||||
point of any bit depth. For 3D arrays, the third dimension must
|
point of any bit depth. For 3D arrays, the third dimension must
|
||||||
be of length 3 (RGB) or 4 (RGBA).
|
be of length 3 (RGB) or 4 (RGBA). See *notes* below.
|
||||||
autoLevels (bool) If True, this forces the image to automatically select
|
autoLevels (bool) If True, this forces the image to automatically select
|
||||||
levels based on the maximum and minimum values in the data.
|
levels based on the maximum and minimum values in the data.
|
||||||
By default, this argument is true unless the levels argument is
|
By default, this argument is true unless the levels argument is
|
||||||
@ -206,12 +210,26 @@ class ImageItem(GraphicsObject):
|
|||||||
data. By default, this will be set to the minimum and maximum values
|
data. By default, this will be set to the minimum and maximum values
|
||||||
in the image. If the image array has dtype uint8, no rescaling is necessary.
|
in the image. If the image array has dtype uint8, no rescaling is necessary.
|
||||||
opacity (float 0.0-1.0)
|
opacity (float 0.0-1.0)
|
||||||
compositionMode see :func:`setCompositionMode <pyqtgraph.ImageItem.setCompositionMode>`
|
compositionMode See :func:`setCompositionMode <pyqtgraph.ImageItem.setCompositionMode>`
|
||||||
border Sets the pen used when drawing the image border. Default is None.
|
border Sets the pen used when drawing the image border. Default is None.
|
||||||
autoDownsample (bool) If True, the image is automatically downsampled to match the
|
autoDownsample (bool) If True, the image is automatically downsampled to match the
|
||||||
screen resolution. This improves performance for large images and
|
screen resolution. This improves performance for large images and
|
||||||
reduces aliasing.
|
reduces aliasing.
|
||||||
================= =========================================================================
|
================= =========================================================================
|
||||||
|
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
|
||||||
|
For backward compatibility, image data is assumed to be in column-major order (column, row).
|
||||||
|
However, most image data is stored in row-major order (row, column) and will need to be
|
||||||
|
transposed before calling setImage()::
|
||||||
|
|
||||||
|
imageitem.setImage(imagedata.T)
|
||||||
|
|
||||||
|
This requirement can be changed by calling ``image.setOpts(axisOrder='row-major')`` or
|
||||||
|
by changing the ``imageAxisOrder`` :ref:`global configuration option <apiref_config>`.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
profile = debug.Profiler()
|
profile = debug.Profiler()
|
||||||
|
|
||||||
@ -222,7 +240,10 @@ class ImageItem(GraphicsObject):
|
|||||||
else:
|
else:
|
||||||
gotNewData = True
|
gotNewData = True
|
||||||
shapeChanged = (self.image is None or image.shape != self.image.shape)
|
shapeChanged = (self.image is None or image.shape != self.image.shape)
|
||||||
self.image = image.view(np.ndarray)
|
image = image.view(np.ndarray)
|
||||||
|
if self.image is None or image.dtype != self.image.dtype:
|
||||||
|
self._effectiveLut = None
|
||||||
|
self.image = image
|
||||||
if self.image.shape[0] > 2**15-1 or self.image.shape[1] > 2**15-1:
|
if self.image.shape[0] > 2**15-1 or self.image.shape[1] > 2**15-1:
|
||||||
if 'autoDownsample' not in kargs:
|
if 'autoDownsample' not in kargs:
|
||||||
kargs['autoDownsample'] = True
|
kargs['autoDownsample'] = True
|
||||||
@ -261,6 +282,53 @@ class ImageItem(GraphicsObject):
|
|||||||
if gotNewData:
|
if gotNewData:
|
||||||
self.sigImageChanged.emit()
|
self.sigImageChanged.emit()
|
||||||
|
|
||||||
|
def dataTransform(self):
|
||||||
|
"""Return the transform that maps from this image's input array to its
|
||||||
|
local coordinate system.
|
||||||
|
|
||||||
|
This transform corrects for the transposition that occurs when image data
|
||||||
|
is interpreted in row-major order.
|
||||||
|
"""
|
||||||
|
# Might eventually need to account for downsampling / clipping here
|
||||||
|
tr = QtGui.QTransform()
|
||||||
|
if self.axisOrder == 'row-major':
|
||||||
|
# transpose
|
||||||
|
tr.scale(1, -1)
|
||||||
|
tr.rotate(-90)
|
||||||
|
return tr
|
||||||
|
|
||||||
|
def inverseDataTransform(self):
|
||||||
|
"""Return the transform that maps from this image's local coordinate
|
||||||
|
system to its input array.
|
||||||
|
|
||||||
|
See dataTransform() for more information.
|
||||||
|
"""
|
||||||
|
tr = QtGui.QTransform()
|
||||||
|
if self.axisOrder == 'row-major':
|
||||||
|
# transpose
|
||||||
|
tr.scale(1, -1)
|
||||||
|
tr.rotate(-90)
|
||||||
|
return tr
|
||||||
|
|
||||||
|
def mapToData(self, obj):
|
||||||
|
tr = self.inverseDataTransform()
|
||||||
|
return tr.map(obj)
|
||||||
|
|
||||||
|
def mapFromData(self, obj):
|
||||||
|
tr = self.dataTransform()
|
||||||
|
return tr.map(obj)
|
||||||
|
|
||||||
|
def quickMinMax(self, targetSize=1e6):
|
||||||
|
"""
|
||||||
|
Estimate the min/max values of the image data by subsampling.
|
||||||
|
"""
|
||||||
|
data = self.image
|
||||||
|
while data.size > targetSize:
|
||||||
|
ax = np.argmax(data.shape)
|
||||||
|
sl = [slice(None)] * data.ndim
|
||||||
|
sl[ax] = slice(None, None, 2)
|
||||||
|
data = data[sl]
|
||||||
|
return nanmin(data), nanmax(data)
|
||||||
|
|
||||||
def updateImage(self, *args, **kargs):
|
def updateImage(self, *args, **kargs):
|
||||||
## used for re-rendering qimage from self.image.
|
## used for re-rendering qimage from self.image.
|
||||||
@ -291,14 +359,48 @@ class ImageItem(GraphicsObject):
|
|||||||
y = self.mapToDevice(QtCore.QPointF(0,1))
|
y = self.mapToDevice(QtCore.QPointF(0,1))
|
||||||
w = Point(x-o).length()
|
w = Point(x-o).length()
|
||||||
h = Point(y-o).length()
|
h = Point(y-o).length()
|
||||||
xds = max(1, int(1/w))
|
if w == 0 or h == 0:
|
||||||
yds = max(1, int(1/h))
|
self.qimage = None
|
||||||
image = fn.downsample(self.image, xds, axis=0)
|
return
|
||||||
image = fn.downsample(image, yds, axis=1)
|
xds = max(1, int(1.0 / w))
|
||||||
|
yds = max(1, int(1.0 / h))
|
||||||
|
axes = [1, 0] if self.axisOrder == 'row-major' else [0, 1]
|
||||||
|
image = fn.downsample(self.image, xds, axis=axes[0])
|
||||||
|
image = fn.downsample(image, yds, axis=axes[1])
|
||||||
|
self._lastDownsample = (xds, yds)
|
||||||
else:
|
else:
|
||||||
image = self.image
|
image = self.image
|
||||||
|
|
||||||
|
# if the image data is a small int, then we can combine levels + lut
|
||||||
|
# into a single lut for better performance
|
||||||
|
levels = self.levels
|
||||||
|
if levels is not None and levels.ndim == 1 and image.dtype in (np.ubyte, np.uint16):
|
||||||
|
if self._effectiveLut is None:
|
||||||
|
eflsize = 2**(image.itemsize*8)
|
||||||
|
ind = np.arange(eflsize)
|
||||||
|
minlev, maxlev = levels
|
||||||
|
levdiff = maxlev - minlev
|
||||||
|
levdiff = 1 if levdiff == 0 else levdiff # don't allow division by 0
|
||||||
|
if lut is None:
|
||||||
|
efflut = fn.rescaleData(ind, scale=255./levdiff,
|
||||||
|
offset=minlev, dtype=np.ubyte)
|
||||||
|
else:
|
||||||
|
lutdtype = np.min_scalar_type(lut.shape[0]-1)
|
||||||
|
efflut = fn.rescaleData(ind, scale=(lut.shape[0]-1)/levdiff,
|
||||||
|
offset=minlev, dtype=lutdtype, clip=(0, lut.shape[0]-1))
|
||||||
|
efflut = lut[efflut]
|
||||||
|
|
||||||
|
self._effectiveLut = efflut
|
||||||
|
lut = self._effectiveLut
|
||||||
|
levels = None
|
||||||
|
|
||||||
argb, alpha = fn.makeARGB(image.transpose((1, 0, 2)[:image.ndim]), lut=lut, levels=self.levels)
|
# Assume images are in column-major order for backward compatibility
|
||||||
|
# (most images are in row-major order)
|
||||||
|
|
||||||
|
if self.axisOrder == 'col-major':
|
||||||
|
image = image.transpose((1, 0, 2)[:image.ndim])
|
||||||
|
|
||||||
|
argb, alpha = fn.makeARGB(image, lut=lut, levels=levels)
|
||||||
self.qimage = fn.makeQImage(argb, alpha, transpose=False)
|
self.qimage = fn.makeQImage(argb, alpha, transpose=False)
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
@ -314,7 +416,8 @@ class ImageItem(GraphicsObject):
|
|||||||
p.setCompositionMode(self.paintMode)
|
p.setCompositionMode(self.paintMode)
|
||||||
profile('set comp mode')
|
profile('set comp mode')
|
||||||
|
|
||||||
p.drawImage(QtCore.QRectF(0,0,self.image.shape[0],self.image.shape[1]), self.qimage)
|
shape = self.image.shape[:2] if self.axisOrder == 'col-major' else self.image.shape[:2][::-1]
|
||||||
|
p.drawImage(QtCore.QRectF(0,0,*shape), self.qimage)
|
||||||
profile('p.drawImage')
|
profile('p.drawImage')
|
||||||
if self.border is not None:
|
if self.border is not None:
|
||||||
p.setPen(self.border)
|
p.setPen(self.border)
|
||||||
@ -347,8 +450,8 @@ class ImageItem(GraphicsObject):
|
|||||||
if self.image is None:
|
if self.image is None:
|
||||||
return None,None
|
return None,None
|
||||||
if step == 'auto':
|
if step == 'auto':
|
||||||
step = (np.ceil(self.image.shape[0] / targetImageSize),
|
step = (int(np.ceil(self.image.shape[0] / targetImageSize)),
|
||||||
np.ceil(self.image.shape[1] / targetImageSize))
|
int(np.ceil(self.image.shape[1] / targetImageSize)))
|
||||||
if np.isscalar(step):
|
if np.isscalar(step):
|
||||||
step = (step, step)
|
step = (step, step)
|
||||||
stepData = self.image[::step[0], ::step[1]]
|
stepData = self.image[::step[0], ::step[1]]
|
||||||
@ -365,6 +468,7 @@ class ImageItem(GraphicsObject):
|
|||||||
bins = 500
|
bins = 500
|
||||||
|
|
||||||
kwds['bins'] = bins
|
kwds['bins'] = bins
|
||||||
|
stepData = stepData[np.isfinite(stepData)]
|
||||||
hist = np.histogram(stepData, **kwds)
|
hist = np.histogram(stepData, **kwds)
|
||||||
|
|
||||||
return hist[1][:-1], hist[0]
|
return hist[1][:-1], hist[0]
|
||||||
@ -400,21 +504,6 @@ class ImageItem(GraphicsObject):
|
|||||||
self.qimage = None
|
self.qimage = None
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
#def mousePressEvent(self, ev):
|
|
||||||
#if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton:
|
|
||||||
#self.drawAt(ev.pos(), ev)
|
|
||||||
#ev.accept()
|
|
||||||
#else:
|
|
||||||
#ev.ignore()
|
|
||||||
|
|
||||||
#def mouseMoveEvent(self, ev):
|
|
||||||
##print "mouse move", ev.pos()
|
|
||||||
#if self.drawKernel is not None:
|
|
||||||
#self.drawAt(ev.pos(), ev)
|
|
||||||
|
|
||||||
#def mouseReleaseEvent(self, ev):
|
|
||||||
#pass
|
|
||||||
|
|
||||||
def mouseDragEvent(self, ev):
|
def mouseDragEvent(self, ev):
|
||||||
if ev.button() != QtCore.Qt.LeftButton:
|
if ev.button() != QtCore.Qt.LeftButton:
|
||||||
ev.ignore()
|
ev.ignore()
|
||||||
@ -451,24 +540,18 @@ class ImageItem(GraphicsObject):
|
|||||||
self.menu.remAct = remAct
|
self.menu.remAct = remAct
|
||||||
return self.menu
|
return self.menu
|
||||||
|
|
||||||
|
|
||||||
def hoverEvent(self, ev):
|
def hoverEvent(self, ev):
|
||||||
if not ev.isExit() and self.drawKernel is not None and ev.acceptDrags(QtCore.Qt.LeftButton):
|
if not ev.isExit() and self.drawKernel is not None and ev.acceptDrags(QtCore.Qt.LeftButton):
|
||||||
ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it.
|
ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it.
|
||||||
ev.acceptClicks(QtCore.Qt.RightButton)
|
ev.acceptClicks(QtCore.Qt.RightButton)
|
||||||
#self.box.setBrush(fn.mkBrush('w'))
|
|
||||||
elif not ev.isExit() and self.removable:
|
elif not ev.isExit() and self.removable:
|
||||||
ev.acceptClicks(QtCore.Qt.RightButton) ## accept context menu clicks
|
ev.acceptClicks(QtCore.Qt.RightButton) ## accept context menu clicks
|
||||||
#else:
|
|
||||||
#self.box.setBrush(self.brush)
|
|
||||||
#self.update()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def tabletEvent(self, ev):
|
def tabletEvent(self, ev):
|
||||||
print(ev.device())
|
pass
|
||||||
print(ev.pointerType())
|
#print(ev.device())
|
||||||
print(ev.pressure())
|
#print(ev.pointerType())
|
||||||
|
#print(ev.pressure())
|
||||||
|
|
||||||
def drawAt(self, pos, ev=None):
|
def drawAt(self, pos, ev=None):
|
||||||
pos = [int(pos.x()), int(pos.y())]
|
pos = [int(pos.x()), int(pos.y())]
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
from ..Qt import QtGui, QtCore
|
from ..Qt import QtGui, QtCore
|
||||||
from ..Point import Point
|
from ..Point import Point
|
||||||
from .GraphicsObject import GraphicsObject
|
from .GraphicsObject import GraphicsObject
|
||||||
|
from .TextItem import TextItem
|
||||||
|
from .ViewBox import ViewBox
|
||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['InfiniteLine']
|
__all__ = ['InfiniteLine', 'InfLineLabel']
|
||||||
|
|
||||||
|
|
||||||
class InfiniteLine(GraphicsObject):
|
class InfiniteLine(GraphicsObject):
|
||||||
"""
|
"""
|
||||||
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
||||||
|
|
||||||
Displays a line of infinite length.
|
Displays a line of infinite length.
|
||||||
This line may be dragged to indicate a position in data coordinates.
|
This line may be dragged to indicate a position in data coordinates.
|
||||||
|
|
||||||
=============================== ===================================================
|
=============================== ===================================================
|
||||||
**Signals:**
|
**Signals:**
|
||||||
sigDragged(self)
|
sigDragged(self)
|
||||||
@ -21,12 +25,13 @@ class InfiniteLine(GraphicsObject):
|
|||||||
sigPositionChanged(self)
|
sigPositionChanged(self)
|
||||||
=============================== ===================================================
|
=============================== ===================================================
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sigDragged = QtCore.Signal(object)
|
sigDragged = QtCore.Signal(object)
|
||||||
sigPositionChangeFinished = QtCore.Signal(object)
|
sigPositionChangeFinished = QtCore.Signal(object)
|
||||||
sigPositionChanged = QtCore.Signal(object)
|
sigPositionChanged = QtCore.Signal(object)
|
||||||
|
|
||||||
def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None):
|
def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None,
|
||||||
|
hoverPen=None, label=None, labelOpts=None, name=None):
|
||||||
"""
|
"""
|
||||||
=============== ==================================================================
|
=============== ==================================================================
|
||||||
**Arguments:**
|
**Arguments:**
|
||||||
@ -37,13 +42,26 @@ class InfiniteLine(GraphicsObject):
|
|||||||
for :func:`mkPen <pyqtgraph.mkPen>`. Default pen is transparent
|
for :func:`mkPen <pyqtgraph.mkPen>`. Default pen is transparent
|
||||||
yellow.
|
yellow.
|
||||||
movable If True, the line can be dragged to a new position by the user.
|
movable If True, the line can be dragged to a new position by the user.
|
||||||
|
hoverPen Pen to use when drawing line when hovering over it. Can be any
|
||||||
|
arguments that are valid for :func:`mkPen <pyqtgraph.mkPen>`.
|
||||||
|
Default pen is red.
|
||||||
bounds Optional [min, max] bounding values. Bounds are only valid if the
|
bounds Optional [min, max] bounding values. Bounds are only valid if the
|
||||||
line is vertical or horizontal.
|
line is vertical or horizontal.
|
||||||
|
label Text to be displayed in a label attached to the line, or
|
||||||
|
None to show no label (default is None). May optionally
|
||||||
|
include formatting strings to display the line value.
|
||||||
|
labelOpts A dict of keyword arguments to use when constructing the
|
||||||
|
text label. See :class:`InfLineLabel`.
|
||||||
|
name Name of the item
|
||||||
=============== ==================================================================
|
=============== ==================================================================
|
||||||
"""
|
"""
|
||||||
|
self._boundingRect = None
|
||||||
|
self._line = None
|
||||||
|
|
||||||
|
self._name = name
|
||||||
|
|
||||||
GraphicsObject.__init__(self)
|
GraphicsObject.__init__(self)
|
||||||
|
|
||||||
if bounds is None: ## allowed value boundaries for orthogonal lines
|
if bounds is None: ## allowed value boundaries for orthogonal lines
|
||||||
self.maxRange = [None, None]
|
self.maxRange = [None, None]
|
||||||
else:
|
else:
|
||||||
@ -53,64 +71,70 @@ class InfiniteLine(GraphicsObject):
|
|||||||
self.mouseHovering = False
|
self.mouseHovering = False
|
||||||
self.p = [0, 0]
|
self.p = [0, 0]
|
||||||
self.setAngle(angle)
|
self.setAngle(angle)
|
||||||
|
|
||||||
if pos is None:
|
if pos is None:
|
||||||
pos = Point(0,0)
|
pos = Point(0,0)
|
||||||
self.setPos(pos)
|
self.setPos(pos)
|
||||||
|
|
||||||
if pen is None:
|
if pen is None:
|
||||||
pen = (200, 200, 100)
|
pen = (200, 200, 100)
|
||||||
|
|
||||||
self.setPen(pen)
|
self.setPen(pen)
|
||||||
self.setHoverPen(color=(255,0,0), width=self.pen.width())
|
if hoverPen is None:
|
||||||
|
self.setHoverPen(color=(255,0,0), width=self.pen.width())
|
||||||
|
else:
|
||||||
|
self.setHoverPen(hoverPen)
|
||||||
self.currentPen = self.pen
|
self.currentPen = self.pen
|
||||||
#self.setFlag(self.ItemSendsScenePositionChanges)
|
|
||||||
|
if label is not None:
|
||||||
|
labelOpts = {} if labelOpts is None else labelOpts
|
||||||
|
self.label = InfLineLabel(self, text=label, **labelOpts)
|
||||||
|
|
||||||
def setMovable(self, m):
|
def setMovable(self, m):
|
||||||
"""Set whether the line is movable by the user."""
|
"""Set whether the line is movable by the user."""
|
||||||
self.movable = m
|
self.movable = m
|
||||||
self.setAcceptHoverEvents(m)
|
self.setAcceptHoverEvents(m)
|
||||||
|
|
||||||
def setBounds(self, bounds):
|
def setBounds(self, bounds):
|
||||||
"""Set the (minimum, maximum) allowable values when dragging."""
|
"""Set the (minimum, maximum) allowable values when dragging."""
|
||||||
self.maxRange = bounds
|
self.maxRange = bounds
|
||||||
self.setValue(self.value())
|
self.setValue(self.value())
|
||||||
|
|
||||||
def setPen(self, *args, **kwargs):
|
def setPen(self, *args, **kwargs):
|
||||||
"""Set the pen for drawing the line. Allowable arguments are any that are valid
|
"""Set the pen for drawing the line. Allowable arguments are any that are valid
|
||||||
for :func:`mkPen <pyqtgraph.mkPen>`."""
|
for :func:`mkPen <pyqtgraph.mkPen>`."""
|
||||||
self.pen = fn.mkPen(*args, **kwargs)
|
self.pen = fn.mkPen(*args, **kwargs)
|
||||||
if not self.mouseHovering:
|
if not self.mouseHovering:
|
||||||
self.currentPen = self.pen
|
self.currentPen = self.pen
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setHoverPen(self, *args, **kwargs):
|
def setHoverPen(self, *args, **kwargs):
|
||||||
"""Set the pen for drawing the line while the mouse hovers over it.
|
"""Set the pen for drawing the line while the mouse hovers over it.
|
||||||
Allowable arguments are any that are valid
|
Allowable arguments are any that are valid
|
||||||
for :func:`mkPen <pyqtgraph.mkPen>`.
|
for :func:`mkPen <pyqtgraph.mkPen>`.
|
||||||
|
|
||||||
If the line is not movable, then hovering is also disabled.
|
If the line is not movable, then hovering is also disabled.
|
||||||
|
|
||||||
Added in version 0.9.9."""
|
Added in version 0.9.9."""
|
||||||
self.hoverPen = fn.mkPen(*args, **kwargs)
|
self.hoverPen = fn.mkPen(*args, **kwargs)
|
||||||
if self.mouseHovering:
|
if self.mouseHovering:
|
||||||
self.currentPen = self.hoverPen
|
self.currentPen = self.hoverPen
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setAngle(self, angle):
|
def setAngle(self, angle):
|
||||||
"""
|
"""
|
||||||
Takes angle argument in degrees.
|
Takes angle argument in degrees.
|
||||||
0 is horizontal; 90 is vertical.
|
0 is horizontal; 90 is vertical.
|
||||||
|
|
||||||
Note that the use of value() and setValue() changes if the line is
|
Note that the use of value() and setValue() changes if the line is
|
||||||
not vertical or horizontal.
|
not vertical or horizontal.
|
||||||
"""
|
"""
|
||||||
self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135
|
self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135
|
||||||
self.resetTransform()
|
self.resetTransform()
|
||||||
self.rotate(self.angle)
|
self.rotate(self.angle)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setPos(self, pos):
|
def setPos(self, pos):
|
||||||
|
|
||||||
if type(pos) in [list, tuple]:
|
if type(pos) in [list, tuple]:
|
||||||
newPos = pos
|
newPos = pos
|
||||||
elif isinstance(pos, QtCore.QPointF):
|
elif isinstance(pos, QtCore.QPointF):
|
||||||
@ -122,10 +146,10 @@ class InfiniteLine(GraphicsObject):
|
|||||||
newPos = [0, pos]
|
newPos = [0, pos]
|
||||||
else:
|
else:
|
||||||
raise Exception("Must specify 2D coordinate for non-orthogonal lines.")
|
raise Exception("Must specify 2D coordinate for non-orthogonal lines.")
|
||||||
|
|
||||||
## check bounds (only works for orthogonal lines)
|
## check bounds (only works for orthogonal lines)
|
||||||
if self.angle == 90:
|
if self.angle == 90:
|
||||||
if self.maxRange[0] is not None:
|
if self.maxRange[0] is not None:
|
||||||
newPos[0] = max(newPos[0], self.maxRange[0])
|
newPos[0] = max(newPos[0], self.maxRange[0])
|
||||||
if self.maxRange[1] is not None:
|
if self.maxRange[1] is not None:
|
||||||
newPos[0] = min(newPos[0], self.maxRange[1])
|
newPos[0] = min(newPos[0], self.maxRange[1])
|
||||||
@ -134,24 +158,24 @@ class InfiniteLine(GraphicsObject):
|
|||||||
newPos[1] = max(newPos[1], self.maxRange[0])
|
newPos[1] = max(newPos[1], self.maxRange[0])
|
||||||
if self.maxRange[1] is not None:
|
if self.maxRange[1] is not None:
|
||||||
newPos[1] = min(newPos[1], self.maxRange[1])
|
newPos[1] = min(newPos[1], self.maxRange[1])
|
||||||
|
|
||||||
if self.p != newPos:
|
if self.p != newPos:
|
||||||
self.p = newPos
|
self.p = newPos
|
||||||
|
self._invalidateCache()
|
||||||
GraphicsObject.setPos(self, Point(self.p))
|
GraphicsObject.setPos(self, Point(self.p))
|
||||||
self.update()
|
|
||||||
self.sigPositionChanged.emit(self)
|
self.sigPositionChanged.emit(self)
|
||||||
|
|
||||||
def getXPos(self):
|
def getXPos(self):
|
||||||
return self.p[0]
|
return self.p[0]
|
||||||
|
|
||||||
def getYPos(self):
|
def getYPos(self):
|
||||||
return self.p[1]
|
return self.p[1]
|
||||||
|
|
||||||
def getPos(self):
|
def getPos(self):
|
||||||
return self.p
|
return self.p
|
||||||
|
|
||||||
def value(self):
|
def value(self):
|
||||||
"""Return the value of the line. Will be a single number for horizontal and
|
"""Return the value of the line. Will be a single number for horizontal and
|
||||||
vertical lines, and a list of [x,y] values for diagonal lines."""
|
vertical lines, and a list of [x,y] values for diagonal lines."""
|
||||||
if self.angle%180 == 0:
|
if self.angle%180 == 0:
|
||||||
return self.getYPos()
|
return self.getYPos()
|
||||||
@ -159,10 +183,10 @@ class InfiniteLine(GraphicsObject):
|
|||||||
return self.getXPos()
|
return self.getXPos()
|
||||||
else:
|
else:
|
||||||
return self.getPos()
|
return self.getPos()
|
||||||
|
|
||||||
def setValue(self, v):
|
def setValue(self, v):
|
||||||
"""Set the position of the line. If line is horizontal or vertical, v can be
|
"""Set the position of the line. If line is horizontal or vertical, v can be
|
||||||
a single value. Otherwise, a 2D coordinate must be specified (list, tuple and
|
a single value. Otherwise, a 2D coordinate must be specified (list, tuple and
|
||||||
QPointF are all acceptable)."""
|
QPointF are all acceptable)."""
|
||||||
self.setPos(v)
|
self.setPos(v)
|
||||||
|
|
||||||
@ -175,26 +199,35 @@ class InfiniteLine(GraphicsObject):
|
|||||||
#else:
|
#else:
|
||||||
#print "ignore", change
|
#print "ignore", change
|
||||||
#return GraphicsObject.itemChange(self, change, val)
|
#return GraphicsObject.itemChange(self, change, val)
|
||||||
|
|
||||||
|
def _invalidateCache(self):
|
||||||
|
self._line = None
|
||||||
|
self._boundingRect = None
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
#br = UIGraphicsItem.boundingRect(self)
|
if self._boundingRect is None:
|
||||||
br = self.viewRect()
|
#br = UIGraphicsItem.boundingRect(self)
|
||||||
## add a 4-pixel radius around the line for mouse interaction.
|
br = self.viewRect()
|
||||||
|
if br is None:
|
||||||
px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line
|
return QtCore.QRectF()
|
||||||
if px is None:
|
|
||||||
px = 0
|
## add a 4-pixel radius around the line for mouse interaction.
|
||||||
w = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * px
|
px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line
|
||||||
br.setBottom(-w)
|
if px is None:
|
||||||
br.setTop(w)
|
px = 0
|
||||||
return br.normalized()
|
w = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * px
|
||||||
|
br.setBottom(-w)
|
||||||
|
br.setTop(w)
|
||||||
|
|
||||||
|
br = br.normalized()
|
||||||
|
self._boundingRect = br
|
||||||
|
self._line = QtCore.QLineF(br.right(), 0.0, br.left(), 0.0)
|
||||||
|
return self._boundingRect
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
br = self.boundingRect()
|
|
||||||
p.setPen(self.currentPen)
|
p.setPen(self.currentPen)
|
||||||
p.drawLine(Point(br.right(), 0), Point(br.left(), 0))
|
p.drawLine(self._line)
|
||||||
#p.drawRect(self.boundingRect())
|
|
||||||
|
|
||||||
def dataBounds(self, axis, frac=1.0, orthoRange=None):
|
def dataBounds(self, axis, frac=1.0, orthoRange=None):
|
||||||
if axis == 0:
|
if axis == 0:
|
||||||
return None ## x axis should never be auto-scaled
|
return None ## x axis should never be auto-scaled
|
||||||
@ -208,20 +241,16 @@ class InfiniteLine(GraphicsObject):
|
|||||||
self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos())
|
self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos())
|
||||||
self.startPosition = self.pos()
|
self.startPosition = self.pos()
|
||||||
ev.accept()
|
ev.accept()
|
||||||
|
|
||||||
if not self.moving:
|
if not self.moving:
|
||||||
return
|
return
|
||||||
|
|
||||||
#pressDelta = self.mapToParent(ev.buttonDownPos()) - Point(self.p)
|
|
||||||
self.setPos(self.cursorOffset + self.mapToParent(ev.pos()))
|
self.setPos(self.cursorOffset + self.mapToParent(ev.pos()))
|
||||||
self.sigDragged.emit(self)
|
self.sigDragged.emit(self)
|
||||||
if ev.isFinish():
|
if ev.isFinish():
|
||||||
self.moving = False
|
self.moving = False
|
||||||
self.sigPositionChangeFinished.emit(self)
|
self.sigPositionChangeFinished.emit(self)
|
||||||
#else:
|
|
||||||
#print ev
|
|
||||||
|
|
||||||
|
|
||||||
def mouseClickEvent(self, ev):
|
def mouseClickEvent(self, ev):
|
||||||
if self.moving and ev.button() == QtCore.Qt.RightButton:
|
if self.moving and ev.button() == QtCore.Qt.RightButton:
|
||||||
ev.accept()
|
ev.accept()
|
||||||
@ -246,30 +275,196 @@ class InfiniteLine(GraphicsObject):
|
|||||||
else:
|
else:
|
||||||
self.currentPen = self.pen
|
self.currentPen = self.pen
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
#def hoverEnterEvent(self, ev):
|
|
||||||
#print "line hover enter"
|
|
||||||
#ev.ignore()
|
|
||||||
#self.updateHoverPen()
|
|
||||||
|
|
||||||
#def hoverMoveEvent(self, ev):
|
def viewTransformChanged(self):
|
||||||
#print "line hover move"
|
"""
|
||||||
#ev.ignore()
|
Called whenever the transformation matrix of the view has changed.
|
||||||
#self.updateHoverPen()
|
(eg, the view range has changed or the view was resized)
|
||||||
|
"""
|
||||||
#def hoverLeaveEvent(self, ev):
|
self._invalidateCache()
|
||||||
#print "line hover leave"
|
|
||||||
#ev.ignore()
|
|
||||||
#self.updateHoverPen(False)
|
|
||||||
|
|
||||||
#def updateHoverPen(self, hover=None):
|
def setName(self, name):
|
||||||
#if hover is None:
|
self._name = name
|
||||||
#scene = self.scene()
|
|
||||||
#hover = scene.claimEvent(self, QtCore.Qt.LeftButton, scene.Drag)
|
|
||||||
|
|
||||||
#if hover:
|
|
||||||
#self.currentPen = fn.mkPen(255, 0,0)
|
|
||||||
#else:
|
|
||||||
#self.currentPen = self.pen
|
|
||||||
#self.update()
|
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
|
||||||
|
class InfLineLabel(TextItem):
|
||||||
|
"""
|
||||||
|
A TextItem that attaches itself to an InfiniteLine.
|
||||||
|
|
||||||
|
This class extends TextItem with the following features:
|
||||||
|
|
||||||
|
* Automatically positions adjacent to the line at a fixed position along
|
||||||
|
the line and within the view box.
|
||||||
|
* Automatically reformats text when the line value has changed.
|
||||||
|
* Can optionally be dragged to change its location along the line.
|
||||||
|
* Optionally aligns to its parent line.
|
||||||
|
|
||||||
|
=============== ==================================================================
|
||||||
|
**Arguments:**
|
||||||
|
line The InfiniteLine to which this label will be attached.
|
||||||
|
text String to display in the label. May contain a {value} formatting
|
||||||
|
string to display the current value of the line.
|
||||||
|
movable Bool; if True, then the label can be dragged along the line.
|
||||||
|
position Relative position (0.0-1.0) within the view to position the label
|
||||||
|
along the line.
|
||||||
|
anchors List of (x,y) pairs giving the text anchor positions that should
|
||||||
|
be used when the line is moved to one side of the view or the
|
||||||
|
other. This allows text to switch to the opposite side of the line
|
||||||
|
as it approaches the edge of the view. These are automatically
|
||||||
|
selected for some common cases, but may be specified if the
|
||||||
|
default values give unexpected results.
|
||||||
|
=============== ==================================================================
|
||||||
|
|
||||||
|
All extra keyword arguments are passed to TextItem. A particularly useful
|
||||||
|
option here is to use `rotateAxis=(1, 0)`, which will cause the text to
|
||||||
|
be automatically rotated parallel to the line.
|
||||||
|
"""
|
||||||
|
def __init__(self, line, text="", movable=False, position=0.5, anchors=None, **kwds):
|
||||||
|
self.line = line
|
||||||
|
self.movable = movable
|
||||||
|
self.moving = False
|
||||||
|
self.orthoPos = position # text will always be placed on the line at a position relative to view bounds
|
||||||
|
self.format = text
|
||||||
|
self.line.sigPositionChanged.connect(self.valueChanged)
|
||||||
|
self._endpoints = (None, None)
|
||||||
|
if anchors is None:
|
||||||
|
# automatically pick sensible anchors
|
||||||
|
rax = kwds.get('rotateAxis', None)
|
||||||
|
if rax is not None:
|
||||||
|
if tuple(rax) == (1,0):
|
||||||
|
anchors = [(0.5, 0), (0.5, 1)]
|
||||||
|
else:
|
||||||
|
anchors = [(0, 0.5), (1, 0.5)]
|
||||||
|
else:
|
||||||
|
if line.angle % 180 == 0:
|
||||||
|
anchors = [(0.5, 0), (0.5, 1)]
|
||||||
|
else:
|
||||||
|
anchors = [(0, 0.5), (1, 0.5)]
|
||||||
|
|
||||||
|
self.anchors = anchors
|
||||||
|
TextItem.__init__(self, **kwds)
|
||||||
|
self.setParentItem(line)
|
||||||
|
self.valueChanged()
|
||||||
|
|
||||||
|
def valueChanged(self):
|
||||||
|
if not self.isVisible():
|
||||||
|
return
|
||||||
|
value = self.line.value()
|
||||||
|
self.setText(self.format.format(value=value))
|
||||||
|
self.updatePosition()
|
||||||
|
|
||||||
|
def getEndpoints(self):
|
||||||
|
# calculate points where line intersects view box
|
||||||
|
# (in line coordinates)
|
||||||
|
if self._endpoints[0] is None:
|
||||||
|
lr = self.line.boundingRect()
|
||||||
|
pt1 = Point(lr.left(), 0)
|
||||||
|
pt2 = Point(lr.right(), 0)
|
||||||
|
|
||||||
|
if self.line.angle % 90 != 0:
|
||||||
|
# more expensive to find text position for oblique lines.
|
||||||
|
view = self.getViewBox()
|
||||||
|
if not self.isVisible() or not isinstance(view, ViewBox):
|
||||||
|
# not in a viewbox, skip update
|
||||||
|
return (None, None)
|
||||||
|
p = QtGui.QPainterPath()
|
||||||
|
p.moveTo(pt1)
|
||||||
|
p.lineTo(pt2)
|
||||||
|
p = self.line.itemTransform(view)[0].map(p)
|
||||||
|
vr = QtGui.QPainterPath()
|
||||||
|
vr.addRect(view.boundingRect())
|
||||||
|
paths = vr.intersected(p).toSubpathPolygons(QtGui.QTransform())
|
||||||
|
if len(paths) > 0:
|
||||||
|
l = list(paths[0])
|
||||||
|
pt1 = self.line.mapFromItem(view, l[0])
|
||||||
|
pt2 = self.line.mapFromItem(view, l[1])
|
||||||
|
self._endpoints = (pt1, pt2)
|
||||||
|
return self._endpoints
|
||||||
|
|
||||||
|
def updatePosition(self):
|
||||||
|
# update text position to relative view location along line
|
||||||
|
self._endpoints = (None, None)
|
||||||
|
pt1, pt2 = self.getEndpoints()
|
||||||
|
if pt1 is None:
|
||||||
|
return
|
||||||
|
pt = pt2 * self.orthoPos + pt1 * (1-self.orthoPos)
|
||||||
|
self.setPos(pt)
|
||||||
|
|
||||||
|
# update anchor to keep text visible as it nears the view box edge
|
||||||
|
vr = self.line.viewRect()
|
||||||
|
if vr is not None:
|
||||||
|
self.setAnchor(self.anchors[0 if vr.center().y() < 0 else 1])
|
||||||
|
|
||||||
|
def setVisible(self, v):
|
||||||
|
TextItem.setVisible(self, v)
|
||||||
|
if v:
|
||||||
|
self.updateText()
|
||||||
|
self.updatePosition()
|
||||||
|
|
||||||
|
def setMovable(self, m):
|
||||||
|
"""Set whether this label is movable by dragging along the line.
|
||||||
|
"""
|
||||||
|
self.movable = m
|
||||||
|
self.setAcceptHoverEvents(m)
|
||||||
|
|
||||||
|
def setPosition(self, p):
|
||||||
|
"""Set the relative position (0.0-1.0) of this label within the view box
|
||||||
|
and along the line.
|
||||||
|
|
||||||
|
For horizontal (angle=0) and vertical (angle=90) lines, a value of 0.0
|
||||||
|
places the text at the bottom or left of the view, respectively.
|
||||||
|
"""
|
||||||
|
self.orthoPos = p
|
||||||
|
self.updatePosition()
|
||||||
|
|
||||||
|
def setFormat(self, text):
|
||||||
|
"""Set the text format string for this label.
|
||||||
|
|
||||||
|
May optionally contain "{value}" to include the lines current value
|
||||||
|
(the text will be reformatted whenever the line is moved).
|
||||||
|
"""
|
||||||
|
self.format = text
|
||||||
|
self.valueChanged()
|
||||||
|
|
||||||
|
def mouseDragEvent(self, ev):
|
||||||
|
if self.movable and ev.button() == QtCore.Qt.LeftButton:
|
||||||
|
if ev.isStart():
|
||||||
|
self._moving = True
|
||||||
|
self._cursorOffset = self._posToRel(ev.buttonDownPos())
|
||||||
|
self._startPosition = self.orthoPos
|
||||||
|
ev.accept()
|
||||||
|
|
||||||
|
if not self._moving:
|
||||||
|
return
|
||||||
|
|
||||||
|
rel = self._posToRel(ev.pos())
|
||||||
|
self.orthoPos = np.clip(self._startPosition + rel - self._cursorOffset, 0, 1)
|
||||||
|
self.updatePosition()
|
||||||
|
if ev.isFinish():
|
||||||
|
self._moving = False
|
||||||
|
|
||||||
|
def mouseClickEvent(self, ev):
|
||||||
|
if self.moving and ev.button() == QtCore.Qt.RightButton:
|
||||||
|
ev.accept()
|
||||||
|
self.orthoPos = self._startPosition
|
||||||
|
self.moving = False
|
||||||
|
|
||||||
|
def hoverEvent(self, ev):
|
||||||
|
if not ev.isExit() and self.movable:
|
||||||
|
ev.acceptDrags(QtCore.Qt.LeftButton)
|
||||||
|
|
||||||
|
def viewTransformChanged(self):
|
||||||
|
self.updatePosition()
|
||||||
|
TextItem.viewTransformChanged(self)
|
||||||
|
|
||||||
|
def _posToRel(self, pos):
|
||||||
|
# convert local position to relative position along line between view bounds
|
||||||
|
pt1, pt2 = self.getEndpoints()
|
||||||
|
if pt1 is None:
|
||||||
|
return 0
|
||||||
|
view = self.getViewBox()
|
||||||
|
pos = self.mapToParent(pos)
|
||||||
|
return (pos.x() - pt1.x()) / (pt2.x()-pt1.x())
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
|
from .. import getConfigOption
|
||||||
|
|
||||||
from .GraphicsObject import *
|
from .GraphicsObject import *
|
||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
from ..Qt import QtGui, QtCore
|
from ..Qt import QtGui, QtCore
|
||||||
@ -9,12 +8,10 @@ class IsocurveItem(GraphicsObject):
|
|||||||
"""
|
"""
|
||||||
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
||||||
|
|
||||||
Item displaying an isocurve of a 2D array.To align this item correctly with an
|
Item displaying an isocurve of a 2D array. To align this item correctly with an
|
||||||
ImageItem,call isocurve.setParentItem(image)
|
ImageItem, call ``isocurve.setParentItem(image)``.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, data=None, level=0, pen='w', axisOrder=None):
|
||||||
|
|
||||||
def __init__(self, data=None, level=0, pen='w'):
|
|
||||||
"""
|
"""
|
||||||
Create a new isocurve item.
|
Create a new isocurve item.
|
||||||
|
|
||||||
@ -25,6 +22,9 @@ class IsocurveItem(GraphicsObject):
|
|||||||
level The cutoff value at which to draw the isocurve.
|
level The cutoff value at which to draw the isocurve.
|
||||||
pen The color of the curve item. Can be anything valid for
|
pen The color of the curve item. Can be anything valid for
|
||||||
:func:`mkPen <pyqtgraph.mkPen>`
|
:func:`mkPen <pyqtgraph.mkPen>`
|
||||||
|
axisOrder May be either 'row-major' or 'col-major'. By default this uses
|
||||||
|
the ``imageAxisOrder``
|
||||||
|
:ref:`global configuration option <apiref_config>`.
|
||||||
============== ===============================================================
|
============== ===============================================================
|
||||||
"""
|
"""
|
||||||
GraphicsObject.__init__(self)
|
GraphicsObject.__init__(self)
|
||||||
@ -32,9 +32,9 @@ class IsocurveItem(GraphicsObject):
|
|||||||
self.level = level
|
self.level = level
|
||||||
self.data = None
|
self.data = None
|
||||||
self.path = None
|
self.path = None
|
||||||
|
self.axisOrder = getConfigOption('imageAxisOrder') if axisOrder is None else axisOrder
|
||||||
self.setPen(pen)
|
self.setPen(pen)
|
||||||
self.setData(data, level)
|
self.setData(data, level)
|
||||||
|
|
||||||
|
|
||||||
def setData(self, data, level=None):
|
def setData(self, data, level=None):
|
||||||
"""
|
"""
|
||||||
@ -54,7 +54,6 @@ class IsocurveItem(GraphicsObject):
|
|||||||
self.path = None
|
self.path = None
|
||||||
self.prepareGeometryChange()
|
self.prepareGeometryChange()
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
def setLevel(self, level):
|
def setLevel(self, level):
|
||||||
"""Set the level at which the isocurve is drawn."""
|
"""Set the level at which the isocurve is drawn."""
|
||||||
@ -62,7 +61,6 @@ class IsocurveItem(GraphicsObject):
|
|||||||
self.path = None
|
self.path = None
|
||||||
self.prepareGeometryChange()
|
self.prepareGeometryChange()
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
def setPen(self, *args, **kwargs):
|
def setPen(self, *args, **kwargs):
|
||||||
"""Set the pen used to draw the isocurve. Arguments can be any that are valid
|
"""Set the pen used to draw the isocurve. Arguments can be any that are valid
|
||||||
@ -75,18 +73,8 @@ class IsocurveItem(GraphicsObject):
|
|||||||
for :func:`mkBrush <pyqtgraph.mkBrush>`"""
|
for :func:`mkBrush <pyqtgraph.mkBrush>`"""
|
||||||
self.brush = fn.mkBrush(*args, **kwargs)
|
self.brush = fn.mkBrush(*args, **kwargs)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
def updateLines(self, data, level):
|
def updateLines(self, data, level):
|
||||||
##print "data:", data
|
|
||||||
##print "level", level
|
|
||||||
#lines = fn.isocurve(data, level)
|
|
||||||
##print len(lines)
|
|
||||||
#self.path = QtGui.QPainterPath()
|
|
||||||
#for line in lines:
|
|
||||||
#self.path.moveTo(*line[0])
|
|
||||||
#self.path.lineTo(*line[1])
|
|
||||||
#self.update()
|
|
||||||
self.setData(data, level)
|
self.setData(data, level)
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
@ -100,7 +88,13 @@ class IsocurveItem(GraphicsObject):
|
|||||||
if self.data is None:
|
if self.data is None:
|
||||||
self.path = None
|
self.path = None
|
||||||
return
|
return
|
||||||
lines = fn.isocurve(self.data, self.level, connected=True, extendToEdge=True)
|
|
||||||
|
if self.axisOrder == 'row-major':
|
||||||
|
data = self.data.T
|
||||||
|
else:
|
||||||
|
data = self.data
|
||||||
|
|
||||||
|
lines = fn.isocurve(data, self.level, connected=True, extendToEdge=True)
|
||||||
self.path = QtGui.QPainterPath()
|
self.path = QtGui.QPainterPath()
|
||||||
for line in lines:
|
for line in lines:
|
||||||
self.path.moveTo(*line[0])
|
self.path.moveTo(*line[0])
|
||||||
|
@ -126,10 +126,18 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
|
|
||||||
## Get min/max (or percentiles) of the requested data range
|
## Get min/max (or percentiles) of the requested data range
|
||||||
if frac >= 1.0:
|
if frac >= 1.0:
|
||||||
|
# include complete data range
|
||||||
|
# first try faster nanmin/max function, then cut out infs if needed.
|
||||||
b = (np.nanmin(d), np.nanmax(d))
|
b = (np.nanmin(d), np.nanmax(d))
|
||||||
|
if any(np.isinf(b)):
|
||||||
|
mask = np.isfinite(d)
|
||||||
|
d = d[mask]
|
||||||
|
b = (d.min(), d.max())
|
||||||
|
|
||||||
elif frac <= 0.0:
|
elif frac <= 0.0:
|
||||||
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
||||||
else:
|
else:
|
||||||
|
# include a percentile of data range
|
||||||
mask = np.isfinite(d)
|
mask = np.isfinite(d)
|
||||||
d = d[mask]
|
d = d[mask]
|
||||||
b = np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)])
|
b = np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)])
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
|
import numpy as np
|
||||||
from .. import metaarray as metaarray
|
from .. import metaarray as metaarray
|
||||||
from ..Qt import QtCore
|
from ..Qt import QtCore
|
||||||
from .GraphicsObject import GraphicsObject
|
from .GraphicsObject import GraphicsObject
|
||||||
from .PlotCurveItem import PlotCurveItem
|
from .PlotCurveItem import PlotCurveItem
|
||||||
from .ScatterPlotItem import ScatterPlotItem
|
from .ScatterPlotItem import ScatterPlotItem
|
||||||
import numpy as np
|
|
||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
from .. import debug as debug
|
from .. import debug as debug
|
||||||
from .. import getConfigOption
|
from .. import getConfigOption
|
||||||
|
|
||||||
|
|
||||||
class PlotDataItem(GraphicsObject):
|
class PlotDataItem(GraphicsObject):
|
||||||
"""
|
"""
|
||||||
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
||||||
@ -522,6 +523,10 @@ class PlotDataItem(GraphicsObject):
|
|||||||
#y = y[::ds]
|
#y = y[::ds]
|
||||||
if self.opts['fftMode']:
|
if self.opts['fftMode']:
|
||||||
x,y = self._fourierTransform(x, y)
|
x,y = self._fourierTransform(x, y)
|
||||||
|
# Ignore the first bin for fft data if we have a logx scale
|
||||||
|
if self.opts['logMode'][0]:
|
||||||
|
x=x[1:]
|
||||||
|
y=y[1:]
|
||||||
if self.opts['logMode'][0]:
|
if self.opts['logMode'][0]:
|
||||||
x = np.log10(x)
|
x = np.log10(x)
|
||||||
if self.opts['logMode'][1]:
|
if self.opts['logMode'][1]:
|
||||||
@ -569,11 +574,11 @@ class PlotDataItem(GraphicsObject):
|
|||||||
x = x[::ds]
|
x = x[::ds]
|
||||||
y = y[::ds]
|
y = y[::ds]
|
||||||
elif self.opts['downsampleMethod'] == 'mean':
|
elif self.opts['downsampleMethod'] == 'mean':
|
||||||
n = len(x) / ds
|
n = len(x) // ds
|
||||||
x = x[:n*ds:ds]
|
x = x[:n*ds:ds]
|
||||||
y = y[:n*ds].reshape(n,ds).mean(axis=1)
|
y = y[:n*ds].reshape(n,ds).mean(axis=1)
|
||||||
elif self.opts['downsampleMethod'] == 'peak':
|
elif self.opts['downsampleMethod'] == 'peak':
|
||||||
n = len(x) / ds
|
n = len(x) // ds
|
||||||
x1 = np.empty((n,2))
|
x1 = np.empty((n,2))
|
||||||
x1[:] = x[:n*ds:ds,np.newaxis]
|
x1[:] = x[:n*ds:ds,np.newaxis]
|
||||||
x = x1.reshape(n*2)
|
x = x1.reshape(n*2)
|
||||||
|
@ -16,20 +16,14 @@ This class is very heavily featured:
|
|||||||
- Control panel with a huge feature set including averaging, decimation,
|
- Control panel with a huge feature set including averaging, decimation,
|
||||||
display, power spectrum, svg/png export, plot linking, and more.
|
display, power spectrum, svg/png export, plot linking, and more.
|
||||||
"""
|
"""
|
||||||
from ...Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
|
|
||||||
from ... import pixmaps
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if USE_PYSIDE:
|
|
||||||
from .plotConfigTemplate_pyside import *
|
|
||||||
else:
|
|
||||||
from .plotConfigTemplate_pyqt import *
|
|
||||||
|
|
||||||
from ... import functions as fn
|
|
||||||
from ...widgets.FileDialog import FileDialog
|
|
||||||
import weakref
|
import weakref
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import os
|
import os
|
||||||
|
from ...Qt import QtGui, QtCore, QT_LIB
|
||||||
|
from ... import pixmaps
|
||||||
|
from ... import functions as fn
|
||||||
|
from ...widgets.FileDialog import FileDialog
|
||||||
from .. PlotDataItem import PlotDataItem
|
from .. PlotDataItem import PlotDataItem
|
||||||
from .. ViewBox import ViewBox
|
from .. ViewBox import ViewBox
|
||||||
from .. AxisItem import AxisItem
|
from .. AxisItem import AxisItem
|
||||||
@ -39,6 +33,14 @@ from .. GraphicsWidget import GraphicsWidget
|
|||||||
from .. ButtonItem import ButtonItem
|
from .. ButtonItem import ButtonItem
|
||||||
from .. InfiniteLine import InfiniteLine
|
from .. InfiniteLine import InfiniteLine
|
||||||
from ...WidgetGroup import WidgetGroup
|
from ...WidgetGroup import WidgetGroup
|
||||||
|
from ...python2_3 import basestring
|
||||||
|
|
||||||
|
if QT_LIB == 'PyQt4':
|
||||||
|
from .plotConfigTemplate_pyqt import *
|
||||||
|
elif QT_LIB == 'PySide':
|
||||||
|
from .plotConfigTemplate_pyside import *
|
||||||
|
elif QT_LIB == 'PyQt5':
|
||||||
|
from .plotConfigTemplate_pyqt5 import *
|
||||||
|
|
||||||
__all__ = ['PlotItem']
|
__all__ = ['PlotItem']
|
||||||
|
|
||||||
@ -168,7 +170,10 @@ class PlotItem(GraphicsWidget):
|
|||||||
axisItems = {}
|
axisItems = {}
|
||||||
self.axes = {}
|
self.axes = {}
|
||||||
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
|
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
|
||||||
axis = axisItems.get(k, AxisItem(orientation=k, parent=self))
|
if k in axisItems:
|
||||||
|
axis = axisItems[k]
|
||||||
|
else:
|
||||||
|
axis = AxisItem(orientation=k, parent=self)
|
||||||
axis.linkToView(self.vb)
|
axis.linkToView(self.vb)
|
||||||
self.axes[k] = {'item': axis, 'pos': pos}
|
self.axes[k] = {'item': axis, 'pos': pos}
|
||||||
self.layout.addItem(axis, *pos)
|
self.layout.addItem(axis, *pos)
|
||||||
@ -469,12 +474,13 @@ class PlotItem(GraphicsWidget):
|
|||||||
|
|
||||||
### Average data together
|
### Average data together
|
||||||
(x, y) = curve.getData()
|
(x, y) = curve.getData()
|
||||||
|
stepMode = curve.opts['stepMode']
|
||||||
if plot.yData is not None and y.shape == plot.yData.shape:
|
if plot.yData is not None and y.shape == plot.yData.shape:
|
||||||
# note that if shapes do not match, then the average resets.
|
# note that if shapes do not match, then the average resets.
|
||||||
newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n)
|
newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n)
|
||||||
plot.setData(plot.xData, newData)
|
plot.setData(plot.xData, newData, stepMode=stepMode)
|
||||||
else:
|
else:
|
||||||
plot.setData(x, y)
|
plot.setData(x, y, stepMode=stepMode)
|
||||||
|
|
||||||
def autoBtnClicked(self):
|
def autoBtnClicked(self):
|
||||||
if self.autoBtn.mode == 'auto':
|
if self.autoBtn.mode == 'auto':
|
||||||
@ -768,14 +774,6 @@ class PlotItem(GraphicsWidget):
|
|||||||
y = pos.y() * sy
|
y = pos.y() * sy
|
||||||
|
|
||||||
fh.write('<circle cx="%f" cy="%f" r="1" fill="#%s" stroke="none" fill-opacity="%f"/>\n' % (x, y, color, opacity))
|
fh.write('<circle cx="%f" cy="%f" r="1" fill="#%s" stroke="none" fill-opacity="%f"/>\n' % (x, y, color, opacity))
|
||||||
#fh.write('<path fill="none" stroke="#%s" stroke-opacity="%f" stroke-width="1" d="M%f,%f ' % (color, opacity, x[0], y[0]))
|
|
||||||
#for i in xrange(1, len(x)):
|
|
||||||
#fh.write('L%f,%f ' % (x[i], y[i]))
|
|
||||||
|
|
||||||
#fh.write('"/>')
|
|
||||||
|
|
||||||
## get list of curves, scatter plots
|
|
||||||
|
|
||||||
|
|
||||||
fh.write("</svg>\n")
|
fh.write("</svg>\n")
|
||||||
|
|
||||||
@ -787,42 +785,9 @@ class PlotItem(GraphicsWidget):
|
|||||||
fileName = str(fileName)
|
fileName = str(fileName)
|
||||||
PlotItem.lastFileDir = os.path.dirname(fileName)
|
PlotItem.lastFileDir = os.path.dirname(fileName)
|
||||||
|
|
||||||
self.svg = QtSvg.QSvgGenerator()
|
from ...exporters import SVGExporter
|
||||||
self.svg.setFileName(fileName)
|
ex = SVGExporter(self)
|
||||||
res = 120.
|
ex.export(fileName)
|
||||||
view = self.scene().views()[0]
|
|
||||||
bounds = view.viewport().rect()
|
|
||||||
bounds = QtCore.QRectF(0, 0, bounds.width(), bounds.height())
|
|
||||||
|
|
||||||
self.svg.setResolution(res)
|
|
||||||
self.svg.setViewBox(bounds)
|
|
||||||
|
|
||||||
self.svg.setSize(QtCore.QSize(bounds.width(), bounds.height()))
|
|
||||||
|
|
||||||
painter = QtGui.QPainter(self.svg)
|
|
||||||
view.render(painter, bounds)
|
|
||||||
|
|
||||||
painter.end()
|
|
||||||
|
|
||||||
## Workaround to set pen widths correctly
|
|
||||||
import re
|
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
def writeImage(self, fileName=None):
|
def writeImage(self, fileName=None):
|
||||||
if fileName is None:
|
if fileName is None:
|
||||||
|
169
pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt5.py
Normal file
169
pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt5.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.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(481, 840)
|
||||||
|
self.averageGroup = QtWidgets.QGroupBox(Form)
|
||||||
|
self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182))
|
||||||
|
self.averageGroup.setCheckable(True)
|
||||||
|
self.averageGroup.setChecked(False)
|
||||||
|
self.averageGroup.setObjectName("averageGroup")
|
||||||
|
self.gridLayout_5 = QtWidgets.QGridLayout(self.averageGroup)
|
||||||
|
self.gridLayout_5.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.gridLayout_5.setSpacing(0)
|
||||||
|
self.gridLayout_5.setObjectName("gridLayout_5")
|
||||||
|
self.avgParamList = QtWidgets.QListWidget(self.averageGroup)
|
||||||
|
self.avgParamList.setObjectName("avgParamList")
|
||||||
|
self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1)
|
||||||
|
self.decimateGroup = QtWidgets.QFrame(Form)
|
||||||
|
self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171))
|
||||||
|
self.decimateGroup.setObjectName("decimateGroup")
|
||||||
|
self.gridLayout_4 = QtWidgets.QGridLayout(self.decimateGroup)
|
||||||
|
self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.gridLayout_4.setSpacing(0)
|
||||||
|
self.gridLayout_4.setObjectName("gridLayout_4")
|
||||||
|
self.clipToViewCheck = QtWidgets.QCheckBox(self.decimateGroup)
|
||||||
|
self.clipToViewCheck.setObjectName("clipToViewCheck")
|
||||||
|
self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3)
|
||||||
|
self.maxTracesCheck = QtWidgets.QCheckBox(self.decimateGroup)
|
||||||
|
self.maxTracesCheck.setObjectName("maxTracesCheck")
|
||||||
|
self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2)
|
||||||
|
self.downsampleCheck = QtWidgets.QCheckBox(self.decimateGroup)
|
||||||
|
self.downsampleCheck.setObjectName("downsampleCheck")
|
||||||
|
self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3)
|
||||||
|
self.peakRadio = QtWidgets.QRadioButton(self.decimateGroup)
|
||||||
|
self.peakRadio.setChecked(True)
|
||||||
|
self.peakRadio.setObjectName("peakRadio")
|
||||||
|
self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2)
|
||||||
|
self.maxTracesSpin = QtWidgets.QSpinBox(self.decimateGroup)
|
||||||
|
self.maxTracesSpin.setObjectName("maxTracesSpin")
|
||||||
|
self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1)
|
||||||
|
self.forgetTracesCheck = QtWidgets.QCheckBox(self.decimateGroup)
|
||||||
|
self.forgetTracesCheck.setObjectName("forgetTracesCheck")
|
||||||
|
self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3)
|
||||||
|
self.meanRadio = QtWidgets.QRadioButton(self.decimateGroup)
|
||||||
|
self.meanRadio.setObjectName("meanRadio")
|
||||||
|
self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2)
|
||||||
|
self.subsampleRadio = QtWidgets.QRadioButton(self.decimateGroup)
|
||||||
|
self.subsampleRadio.setObjectName("subsampleRadio")
|
||||||
|
self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2)
|
||||||
|
self.autoDownsampleCheck = QtWidgets.QCheckBox(self.decimateGroup)
|
||||||
|
self.autoDownsampleCheck.setChecked(True)
|
||||||
|
self.autoDownsampleCheck.setObjectName("autoDownsampleCheck")
|
||||||
|
self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1)
|
||||||
|
spacerItem = QtWidgets.QSpacerItem(30, 20, QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Minimum)
|
||||||
|
self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1)
|
||||||
|
self.downsampleSpin = QtWidgets.QSpinBox(self.decimateGroup)
|
||||||
|
self.downsampleSpin.setMinimum(1)
|
||||||
|
self.downsampleSpin.setMaximum(100000)
|
||||||
|
self.downsampleSpin.setProperty("value", 1)
|
||||||
|
self.downsampleSpin.setObjectName("downsampleSpin")
|
||||||
|
self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1)
|
||||||
|
self.transformGroup = QtWidgets.QFrame(Form)
|
||||||
|
self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79))
|
||||||
|
self.transformGroup.setObjectName("transformGroup")
|
||||||
|
self.gridLayout = QtWidgets.QGridLayout(self.transformGroup)
|
||||||
|
self.gridLayout.setObjectName("gridLayout")
|
||||||
|
self.fftCheck = QtWidgets.QCheckBox(self.transformGroup)
|
||||||
|
self.fftCheck.setObjectName("fftCheck")
|
||||||
|
self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1)
|
||||||
|
self.logXCheck = QtWidgets.QCheckBox(self.transformGroup)
|
||||||
|
self.logXCheck.setObjectName("logXCheck")
|
||||||
|
self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1)
|
||||||
|
self.logYCheck = QtWidgets.QCheckBox(self.transformGroup)
|
||||||
|
self.logYCheck.setObjectName("logYCheck")
|
||||||
|
self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1)
|
||||||
|
self.pointsGroup = QtWidgets.QGroupBox(Form)
|
||||||
|
self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58))
|
||||||
|
self.pointsGroup.setCheckable(True)
|
||||||
|
self.pointsGroup.setObjectName("pointsGroup")
|
||||||
|
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.pointsGroup)
|
||||||
|
self.verticalLayout_5.setObjectName("verticalLayout_5")
|
||||||
|
self.autoPointsCheck = QtWidgets.QCheckBox(self.pointsGroup)
|
||||||
|
self.autoPointsCheck.setChecked(True)
|
||||||
|
self.autoPointsCheck.setObjectName("autoPointsCheck")
|
||||||
|
self.verticalLayout_5.addWidget(self.autoPointsCheck)
|
||||||
|
self.gridGroup = QtWidgets.QFrame(Form)
|
||||||
|
self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81))
|
||||||
|
self.gridGroup.setObjectName("gridGroup")
|
||||||
|
self.gridLayout_2 = QtWidgets.QGridLayout(self.gridGroup)
|
||||||
|
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||||
|
self.xGridCheck = QtWidgets.QCheckBox(self.gridGroup)
|
||||||
|
self.xGridCheck.setObjectName("xGridCheck")
|
||||||
|
self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2)
|
||||||
|
self.yGridCheck = QtWidgets.QCheckBox(self.gridGroup)
|
||||||
|
self.yGridCheck.setObjectName("yGridCheck")
|
||||||
|
self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2)
|
||||||
|
self.gridAlphaSlider = QtWidgets.QSlider(self.gridGroup)
|
||||||
|
self.gridAlphaSlider.setMaximum(255)
|
||||||
|
self.gridAlphaSlider.setProperty("value", 128)
|
||||||
|
self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal)
|
||||||
|
self.gridAlphaSlider.setObjectName("gridAlphaSlider")
|
||||||
|
self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1)
|
||||||
|
self.label = QtWidgets.QLabel(self.gridGroup)
|
||||||
|
self.label.setObjectName("label")
|
||||||
|
self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1)
|
||||||
|
self.alphaGroup = QtWidgets.QGroupBox(Form)
|
||||||
|
self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60))
|
||||||
|
self.alphaGroup.setCheckable(True)
|
||||||
|
self.alphaGroup.setObjectName("alphaGroup")
|
||||||
|
self.horizontalLayout = QtWidgets.QHBoxLayout(self.alphaGroup)
|
||||||
|
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||||
|
self.autoAlphaCheck = QtWidgets.QCheckBox(self.alphaGroup)
|
||||||
|
self.autoAlphaCheck.setChecked(False)
|
||||||
|
self.autoAlphaCheck.setObjectName("autoAlphaCheck")
|
||||||
|
self.horizontalLayout.addWidget(self.autoAlphaCheck)
|
||||||
|
self.alphaSlider = QtWidgets.QSlider(self.alphaGroup)
|
||||||
|
self.alphaSlider.setMaximum(1000)
|
||||||
|
self.alphaSlider.setProperty("value", 1000)
|
||||||
|
self.alphaSlider.setOrientation(QtCore.Qt.Horizontal)
|
||||||
|
self.alphaSlider.setObjectName("alphaSlider")
|
||||||
|
self.horizontalLayout.addWidget(self.alphaSlider)
|
||||||
|
|
||||||
|
self.retranslateUi(Form)
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||||
|
|
||||||
|
def retranslateUi(self, Form):
|
||||||
|
_translate = QtCore.QCoreApplication.translate
|
||||||
|
Form.setWindowTitle(_translate("Form", "Form"))
|
||||||
|
self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available)."))
|
||||||
|
self.averageGroup.setTitle(_translate("Form", "Average"))
|
||||||
|
self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced."))
|
||||||
|
self.clipToViewCheck.setText(_translate("Form", "Clip to View"))
|
||||||
|
self.maxTracesCheck.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed."))
|
||||||
|
self.maxTracesCheck.setText(_translate("Form", "Max Traces:"))
|
||||||
|
self.downsampleCheck.setText(_translate("Form", "Downsample"))
|
||||||
|
self.peakRadio.setToolTip(_translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower."))
|
||||||
|
self.peakRadio.setText(_translate("Form", "Peak"))
|
||||||
|
self.maxTracesSpin.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed."))
|
||||||
|
self.forgetTracesCheck.setToolTip(_translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden)."))
|
||||||
|
self.forgetTracesCheck.setText(_translate("Form", "Forget hidden traces"))
|
||||||
|
self.meanRadio.setToolTip(_translate("Form", "Downsample by taking the mean of N samples."))
|
||||||
|
self.meanRadio.setText(_translate("Form", "Mean"))
|
||||||
|
self.subsampleRadio.setToolTip(_translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate."))
|
||||||
|
self.subsampleRadio.setText(_translate("Form", "Subsample"))
|
||||||
|
self.autoDownsampleCheck.setToolTip(_translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced."))
|
||||||
|
self.autoDownsampleCheck.setText(_translate("Form", "Auto"))
|
||||||
|
self.downsampleSpin.setToolTip(_translate("Form", "Downsample data before plotting. (plot every Nth sample)"))
|
||||||
|
self.downsampleSpin.setSuffix(_translate("Form", "x"))
|
||||||
|
self.fftCheck.setText(_translate("Form", "Power Spectrum (FFT)"))
|
||||||
|
self.logXCheck.setText(_translate("Form", "Log X"))
|
||||||
|
self.logYCheck.setText(_translate("Form", "Log Y"))
|
||||||
|
self.pointsGroup.setTitle(_translate("Form", "Points"))
|
||||||
|
self.autoPointsCheck.setText(_translate("Form", "Auto"))
|
||||||
|
self.xGridCheck.setText(_translate("Form", "Show X Grid"))
|
||||||
|
self.yGridCheck.setText(_translate("Form", "Show Y Grid"))
|
||||||
|
self.label.setText(_translate("Form", "Opacity"))
|
||||||
|
self.alphaGroup.setTitle(_translate("Form", "Alpha"))
|
||||||
|
self.autoAlphaCheck.setText(_translate("Form", "Auto"))
|
||||||
|
|
@ -21,6 +21,7 @@ from math import cos, sin
|
|||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
from .GraphicsObject import GraphicsObject
|
from .GraphicsObject import GraphicsObject
|
||||||
from .UIGraphicsItem import UIGraphicsItem
|
from .UIGraphicsItem import UIGraphicsItem
|
||||||
|
from .. import getConfigOption
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'ROI',
|
'ROI',
|
||||||
@ -213,20 +214,30 @@ class ROI(GraphicsObject):
|
|||||||
"""Return the angle of the ROI in degrees."""
|
"""Return the angle of the ROI in degrees."""
|
||||||
return self.getState()['angle']
|
return self.getState()['angle']
|
||||||
|
|
||||||
def setPos(self, pos, update=True, finish=True):
|
def setPos(self, pos, y=None, update=True, finish=True):
|
||||||
"""Set the position of the ROI (in the parent's coordinate system).
|
"""Set the position of the ROI (in the parent's coordinate system).
|
||||||
By default, this will cause both sigRegionChanged and sigRegionChangeFinished to be emitted.
|
|
||||||
|
|
||||||
If finish is False, then sigRegionChangeFinished will not be emitted. You can then use
|
Accepts either separate (x, y) arguments or a single :class:`Point` or
|
||||||
stateChangeFinished() to cause the signal to be emitted after a series of state changes.
|
``QPointF`` argument.
|
||||||
|
|
||||||
If update is False, the state change will be remembered but not processed and no signals
|
By default, this method causes both ``sigRegionChanged`` and
|
||||||
|
``sigRegionChangeFinished`` to be emitted. If *finish* is False, then
|
||||||
|
``sigRegionChangeFinished`` will not be emitted. You can then use
|
||||||
|
stateChangeFinished() to cause the signal to be emitted after a series
|
||||||
|
of state changes.
|
||||||
|
|
||||||
|
If *update* is False, the state change will be remembered but not processed and no signals
|
||||||
will be emitted. You can then use stateChanged() to complete the state change. This allows
|
will be emitted. You can then use stateChanged() to complete the state change. This allows
|
||||||
multiple change functions to be called sequentially while minimizing processing overhead
|
multiple change functions to be called sequentially while minimizing processing overhead
|
||||||
and repeated signals. Setting update=False also forces finish=False.
|
and repeated signals. Setting ``update=False`` also forces ``finish=False``.
|
||||||
"""
|
"""
|
||||||
|
if y is None:
|
||||||
pos = Point(pos)
|
pos = Point(pos)
|
||||||
|
else:
|
||||||
|
# avoid ambiguity where update is provided as a positional argument
|
||||||
|
if isinstance(y, bool):
|
||||||
|
raise TypeError("Positional arguments to setPos() must be numerical.")
|
||||||
|
pos = Point(pos, y)
|
||||||
self.state['pos'] = pos
|
self.state['pos'] = pos
|
||||||
QtGui.QGraphicsItem.setPos(self, pos)
|
QtGui.QGraphicsItem.setPos(self, pos)
|
||||||
if update:
|
if update:
|
||||||
@ -526,7 +537,7 @@ class ROI(GraphicsObject):
|
|||||||
if isinstance(handle, Handle):
|
if isinstance(handle, Handle):
|
||||||
index = [i for i, info in enumerate(self.handles) if info['item'] is handle]
|
index = [i for i, info in enumerate(self.handles) if info['item'] is handle]
|
||||||
if len(index) == 0:
|
if len(index) == 0:
|
||||||
raise Exception("Cannot remove handle; it is not attached to this ROI")
|
raise Exception("Cannot return handle index; not attached to this ROI")
|
||||||
return index[0]
|
return index[0]
|
||||||
else:
|
else:
|
||||||
return handle
|
return handle
|
||||||
@ -636,11 +647,20 @@ class ROI(GraphicsObject):
|
|||||||
if self.mouseHovering == hover:
|
if self.mouseHovering == hover:
|
||||||
return
|
return
|
||||||
self.mouseHovering = hover
|
self.mouseHovering = hover
|
||||||
if hover:
|
self._updateHoverColor()
|
||||||
self.currentPen = fn.mkPen(255, 255, 0)
|
|
||||||
|
def _updateHoverColor(self):
|
||||||
|
pen = self._makePen()
|
||||||
|
if self.currentPen != pen:
|
||||||
|
self.currentPen = pen
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def _makePen(self):
|
||||||
|
# Generate the pen color for this ROI based on its current state.
|
||||||
|
if self.mouseHovering:
|
||||||
|
return fn.mkPen(255, 255, 0)
|
||||||
else:
|
else:
|
||||||
self.currentPen = self.pen
|
return self.pen
|
||||||
self.update()
|
|
||||||
|
|
||||||
def contextMenuEnabled(self):
|
def contextMenuEnabled(self):
|
||||||
return self.removable
|
return self.removable
|
||||||
@ -919,8 +939,9 @@ class ROI(GraphicsObject):
|
|||||||
if self.lastState is None:
|
if self.lastState is None:
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
for k in list(self.state.keys()):
|
state = self.getState()
|
||||||
if self.state[k] != self.lastState[k]:
|
for k in list(state.keys()):
|
||||||
|
if state[k] != self.lastState[k]:
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
self.prepareGeometryChange()
|
self.prepareGeometryChange()
|
||||||
@ -940,10 +961,11 @@ class ROI(GraphicsObject):
|
|||||||
self.sigRegionChanged.emit(self)
|
self.sigRegionChanged.emit(self)
|
||||||
|
|
||||||
self.freeHandleMoved = False
|
self.freeHandleMoved = False
|
||||||
self.lastState = self.stateCopy()
|
self.lastState = self.getState()
|
||||||
|
|
||||||
if finish:
|
if finish:
|
||||||
self.stateChangeFinished()
|
self.stateChangeFinished()
|
||||||
|
self.informViewBoundsChanged()
|
||||||
|
|
||||||
def stateChangeFinished(self):
|
def stateChangeFinished(self):
|
||||||
self.sigRegionChangeFinished.emit(self)
|
self.sigRegionChangeFinished.emit(self)
|
||||||
@ -988,20 +1010,18 @@ class ROI(GraphicsObject):
|
|||||||
# p.restore()
|
# p.restore()
|
||||||
|
|
||||||
def getArraySlice(self, data, img, axes=(0,1), returnSlice=True):
|
def getArraySlice(self, data, img, axes=(0,1), returnSlice=True):
|
||||||
"""Return a tuple of slice objects that can be used to slice the region from data covered by this ROI.
|
"""Return a tuple of slice objects that can be used to slice the region
|
||||||
Also returns the transform which maps the ROI into data coordinates.
|
from *data* that is covered by the bounding rectangle of this ROI.
|
||||||
|
Also returns the transform that maps the ROI into data coordinates.
|
||||||
|
|
||||||
If returnSlice is set to False, the function returns a pair of tuples with the values that would have
|
If returnSlice is set to False, the function returns a pair of tuples with the values that would have
|
||||||
been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))
|
been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))
|
||||||
|
|
||||||
If the slice can not be computed (usually because the scene/transforms are not properly
|
If the slice cannot be computed (usually because the scene/transforms are not properly
|
||||||
constructed yet), then the method returns None.
|
constructed yet), then the method returns None.
|
||||||
"""
|
"""
|
||||||
#print "getArraySlice"
|
|
||||||
|
|
||||||
## Determine shape of array along ROI axes
|
## Determine shape of array along ROI axes
|
||||||
dShape = (data.shape[axes[0]], data.shape[axes[1]])
|
dShape = (data.shape[axes[0]], data.shape[axes[1]])
|
||||||
#print " dshape", dShape
|
|
||||||
|
|
||||||
## Determine transform that maps ROI bounding box to image coordinates
|
## Determine transform that maps ROI bounding box to image coordinates
|
||||||
try:
|
try:
|
||||||
@ -1010,25 +1030,28 @@ class ROI(GraphicsObject):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
## Modify transform to scale from image coords to data coords
|
## Modify transform to scale from image coords to data coords
|
||||||
#m = QtGui.QTransform()
|
axisOrder = img.axisOrder
|
||||||
tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height())
|
if axisOrder == 'row-major':
|
||||||
#tr = tr * m
|
tr.scale(float(dShape[1]) / img.width(), float(dShape[0]) / img.height())
|
||||||
|
else:
|
||||||
|
tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height())
|
||||||
|
|
||||||
## Transform ROI bounds into data bounds
|
## Transform ROI bounds into data bounds
|
||||||
dataBounds = tr.mapRect(self.boundingRect())
|
dataBounds = tr.mapRect(self.boundingRect())
|
||||||
#print " boundingRect:", self.boundingRect()
|
|
||||||
#print " dataBounds:", dataBounds
|
|
||||||
|
|
||||||
## Intersect transformed ROI bounds with data bounds
|
## Intersect transformed ROI bounds with data bounds
|
||||||
intBounds = dataBounds.intersect(QtCore.QRectF(0, 0, dShape[0], dShape[1]))
|
if axisOrder == 'row-major':
|
||||||
#print " intBounds:", intBounds
|
intBounds = dataBounds.intersected(QtCore.QRectF(0, 0, dShape[1], dShape[0]))
|
||||||
|
else:
|
||||||
|
intBounds = dataBounds.intersected(QtCore.QRectF(0, 0, dShape[0], dShape[1]))
|
||||||
|
|
||||||
## Determine index values to use when referencing the array.
|
## Determine index values to use when referencing the array.
|
||||||
bounds = (
|
bounds = (
|
||||||
(int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))),
|
(int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))),
|
||||||
(int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top())))
|
(int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top())))
|
||||||
)
|
)
|
||||||
#print " bounds:", bounds
|
if axisOrder == 'row-major':
|
||||||
|
bounds = bounds[::-1]
|
||||||
|
|
||||||
if returnSlice:
|
if returnSlice:
|
||||||
## Create slice objects
|
## Create slice objects
|
||||||
@ -1052,7 +1075,10 @@ class ROI(GraphicsObject):
|
|||||||
Used to determine the relationship between the
|
Used to determine the relationship between the
|
||||||
ROI and the boundaries of *data*.
|
ROI and the boundaries of *data*.
|
||||||
axes (length-2 tuple) Specifies the axes in *data* that
|
axes (length-2 tuple) Specifies the axes in *data* that
|
||||||
correspond to the x and y axes of *img*.
|
correspond to the (x, y) axes of *img*. If the
|
||||||
|
image's axis order is set to
|
||||||
|
'row-major', then the axes are instead specified in
|
||||||
|
(y, x) order.
|
||||||
returnMappedCoords (bool) If True, the array slice is returned along
|
returnMappedCoords (bool) If True, the array slice is returned along
|
||||||
with a corresponding array of coordinates that were
|
with a corresponding array of coordinates that were
|
||||||
used to extract data from the original array.
|
used to extract data from the original array.
|
||||||
@ -1061,8 +1087,8 @@ class ROI(GraphicsObject):
|
|||||||
=================== ====================================================
|
=================== ====================================================
|
||||||
|
|
||||||
This method uses :func:`affineSlice <pyqtgraph.affineSlice>` to generate
|
This method uses :func:`affineSlice <pyqtgraph.affineSlice>` to generate
|
||||||
the slice from *data* and uses :func:`getAffineSliceParams <pyqtgraph.ROI.getAffineSliceParams>` to determine the parameters to
|
the slice from *data* and uses :func:`getAffineSliceParams <pyqtgraph.ROI.getAffineSliceParams>`
|
||||||
pass to :func:`affineSlice <pyqtgraph.affineSlice>`.
|
to determine the parameters to pass to :func:`affineSlice <pyqtgraph.affineSlice>`.
|
||||||
|
|
||||||
If *returnMappedCoords* is True, then the method returns a tuple (result, coords)
|
If *returnMappedCoords* is True, then the method returns a tuple (result, coords)
|
||||||
such that coords is the set of coordinates used to interpolate values from the original
|
such that coords is the set of coordinates used to interpolate values from the original
|
||||||
@ -1072,59 +1098,91 @@ class ROI(GraphicsObject):
|
|||||||
|
|
||||||
All extra keyword arguments are passed to :func:`affineSlice <pyqtgraph.affineSlice>`.
|
All extra keyword arguments are passed to :func:`affineSlice <pyqtgraph.affineSlice>`.
|
||||||
"""
|
"""
|
||||||
|
# this is a hidden argument for internal use
|
||||||
|
fromBR = kwds.pop('fromBoundingRect', False)
|
||||||
|
|
||||||
shape, vectors, origin = self.getAffineSliceParams(data, img, axes)
|
shape, vectors, origin = self.getAffineSliceParams(data, img, axes, fromBoundingRect=fromBR)
|
||||||
if not returnMappedCoords:
|
if not returnMappedCoords:
|
||||||
return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
|
rgn = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
|
||||||
|
return rgn
|
||||||
else:
|
else:
|
||||||
kwds['returnCoords'] = True
|
kwds['returnCoords'] = True
|
||||||
result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
|
result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
|
||||||
#tr = fn.transformToArray(img.transform())[:2] ## remove perspective transform values
|
|
||||||
|
|
||||||
### separate translation from scale/rotate
|
|
||||||
#translate = tr[:,2]
|
|
||||||
#tr = tr[:,:2]
|
|
||||||
#tr = tr.reshape((2,2) + (1,)*(coords.ndim-1))
|
|
||||||
#coords = coords[np.newaxis, ...]
|
|
||||||
|
|
||||||
### map coordinates and return
|
### map coordinates and return
|
||||||
#mapped = (tr*coords).sum(axis=0) ## apply scale/rotate
|
|
||||||
#mapped += translate.reshape((2,1,1))
|
|
||||||
mapped = fn.transformCoordinates(img.transform(), coords)
|
mapped = fn.transformCoordinates(img.transform(), coords)
|
||||||
return result, mapped
|
return result, mapped
|
||||||
|
|
||||||
def getAffineSliceParams(self, data, img, axes=(0,1)):
|
def getAffineSliceParams(self, data, img, axes=(0,1), fromBoundingRect=False):
|
||||||
"""
|
"""
|
||||||
Returns the parameters needed to use :func:`affineSlice <pyqtgraph.affineSlice>` to
|
Returns the parameters needed to use :func:`affineSlice <pyqtgraph.affineSlice>`
|
||||||
extract a subset of *data* using this ROI and *img* to specify the subset.
|
(shape, vectors, origin) to extract a subset of *data* using this ROI
|
||||||
|
and *img* to specify the subset.
|
||||||
|
|
||||||
|
If *fromBoundingRect* is True, then the ROI's bounding rectangle is used
|
||||||
|
rather than the shape of the ROI.
|
||||||
|
|
||||||
See :func:`getArrayRegion <pyqtgraph.ROI.getArrayRegion>` for more information.
|
See :func:`getArrayRegion <pyqtgraph.ROI.getArrayRegion>` for more information.
|
||||||
"""
|
"""
|
||||||
if self.scene() is not img.scene():
|
if self.scene() is not img.scene():
|
||||||
raise Exception("ROI and target item must be members of the same scene.")
|
raise Exception("ROI and target item must be members of the same scene.")
|
||||||
|
|
||||||
shape = self.state['size']
|
origin = img.mapToData(self.mapToItem(img, QtCore.QPointF(0, 0)))
|
||||||
|
|
||||||
origin = self.mapToItem(img, QtCore.QPointF(0, 0))
|
|
||||||
|
|
||||||
## vx and vy point in the directions of the slice axes, but must be scaled properly
|
## vx and vy point in the directions of the slice axes, but must be scaled properly
|
||||||
vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin
|
vx = img.mapToData(self.mapToItem(img, QtCore.QPointF(1, 0))) - origin
|
||||||
vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin
|
vy = img.mapToData(self.mapToItem(img, QtCore.QPointF(0, 1))) - origin
|
||||||
|
|
||||||
lvx = np.sqrt(vx.x()**2 + vx.y()**2)
|
lvx = np.sqrt(vx.x()**2 + vx.y()**2)
|
||||||
lvy = np.sqrt(vy.x()**2 + vy.y()**2)
|
lvy = np.sqrt(vy.x()**2 + vy.y()**2)
|
||||||
pxLen = img.width() / float(data.shape[axes[0]])
|
#pxLen = img.width() / float(data.shape[axes[0]])
|
||||||
#img.width is number of pixels or width of item?
|
##img.width is number of pixels, not width of item.
|
||||||
#need pxWidth and pxHeight instead of pxLen ?
|
##need pxWidth and pxHeight instead of pxLen ?
|
||||||
sx = pxLen / lvx
|
#sx = pxLen / lvx
|
||||||
sy = pxLen / lvy
|
#sy = pxLen / lvy
|
||||||
|
sx = 1.0 / lvx
|
||||||
|
sy = 1.0 / lvy
|
||||||
|
|
||||||
vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy))
|
vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy))
|
||||||
shape = self.state['size']
|
if fromBoundingRect is True:
|
||||||
|
shape = self.boundingRect().width(), self.boundingRect().height()
|
||||||
|
origin = img.mapToData(self.mapToItem(img, self.boundingRect().topLeft()))
|
||||||
|
origin = (origin.x(), origin.y())
|
||||||
|
else:
|
||||||
|
shape = self.state['size']
|
||||||
|
origin = (origin.x(), origin.y())
|
||||||
|
|
||||||
shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
|
shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
|
||||||
|
|
||||||
origin = (origin.x(), origin.y())
|
if img.axisOrder == 'row-major':
|
||||||
|
# transpose output
|
||||||
|
vectors = vectors[::-1]
|
||||||
|
shape = shape[::-1]
|
||||||
|
|
||||||
return shape, vectors, origin
|
return shape, vectors, origin
|
||||||
|
|
||||||
|
def renderShapeMask(self, width, height):
|
||||||
|
"""Return an array of 0.0-1.0 into which the shape of the item has been drawn.
|
||||||
|
|
||||||
|
This can be used to mask array selections.
|
||||||
|
"""
|
||||||
|
if width == 0 or height == 0:
|
||||||
|
return np.empty((width, height), dtype=float)
|
||||||
|
|
||||||
|
# QImage(width, height, format)
|
||||||
|
im = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32)
|
||||||
|
im.fill(0x0)
|
||||||
|
p = QtGui.QPainter(im)
|
||||||
|
p.setPen(fn.mkPen(None))
|
||||||
|
p.setBrush(fn.mkBrush('w'))
|
||||||
|
shape = self.shape()
|
||||||
|
bounds = shape.boundingRect()
|
||||||
|
p.scale(im.width() / bounds.width(), im.height() / bounds.height())
|
||||||
|
p.translate(-bounds.topLeft())
|
||||||
|
p.drawPath(shape)
|
||||||
|
p.end()
|
||||||
|
mask = fn.imageToArray(im, transpose=True)[:,:,0].astype(float) / 255.
|
||||||
|
return mask
|
||||||
|
|
||||||
def getGlobalTransform(self, relativeTo=None):
|
def getGlobalTransform(self, relativeTo=None):
|
||||||
"""Return global transformation (rotation angle+translation) required to move
|
"""Return global transformation (rotation angle+translation) required to move
|
||||||
@ -1138,8 +1196,6 @@ class ROI(GraphicsObject):
|
|||||||
relativeTo['scale'] = relativeTo['size']
|
relativeTo['scale'] = relativeTo['size']
|
||||||
st['scale'] = st['size']
|
st['scale'] = st['size']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
t1 = SRTTransform(relativeTo)
|
t1 = SRTTransform(relativeTo)
|
||||||
t2 = SRTTransform(st)
|
t2 = SRTTransform(st)
|
||||||
return t2/t1
|
return t2/t1
|
||||||
@ -1586,10 +1642,10 @@ class MultiRectROI(QtGui.QGraphicsObject):
|
|||||||
pos.append(self.mapFromScene(l.getHandles()[1].scenePos()))
|
pos.append(self.mapFromScene(l.getHandles()[1].scenePos()))
|
||||||
return pos
|
return pos
|
||||||
|
|
||||||
def getArrayRegion(self, arr, img=None, axes=(0,1)):
|
def getArrayRegion(self, arr, img=None, axes=(0,1), **kwds):
|
||||||
rgns = []
|
rgns = []
|
||||||
for l in self.lines:
|
for l in self.lines:
|
||||||
rgn = l.getArrayRegion(arr, img, axes=axes)
|
rgn = l.getArrayRegion(arr, img, axes=axes, **kwds)
|
||||||
if rgn is None:
|
if rgn is None:
|
||||||
continue
|
continue
|
||||||
#return None
|
#return None
|
||||||
@ -1598,6 +1654,8 @@ class MultiRectROI(QtGui.QGraphicsObject):
|
|||||||
|
|
||||||
## make sure orthogonal axis is the same size
|
## make sure orthogonal axis is the same size
|
||||||
## (sometimes fp errors cause differences)
|
## (sometimes fp errors cause differences)
|
||||||
|
if img.axisOrder == 'row-major':
|
||||||
|
axes = axes[::-1]
|
||||||
ms = min([r.shape[axes[1]] for r in rgns])
|
ms = min([r.shape[axes[1]] for r in rgns])
|
||||||
sl = [slice(None)] * rgns[0].ndim
|
sl = [slice(None)] * rgns[0].ndim
|
||||||
sl[axes[1]] = slice(0,ms)
|
sl[axes[1]] = slice(0,ms)
|
||||||
@ -1659,6 +1717,7 @@ class MultiLineROI(MultiRectROI):
|
|||||||
def __init__(self, *args, **kwds):
|
def __init__(self, *args, **kwds):
|
||||||
MultiRectROI.__init__(self, *args, **kwds)
|
MultiRectROI.__init__(self, *args, **kwds)
|
||||||
print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)")
|
print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)")
|
||||||
|
|
||||||
|
|
||||||
class EllipseROI(ROI):
|
class EllipseROI(ROI):
|
||||||
"""
|
"""
|
||||||
@ -1689,19 +1748,27 @@ class EllipseROI(ROI):
|
|||||||
|
|
||||||
p.drawEllipse(r)
|
p.drawEllipse(r)
|
||||||
|
|
||||||
def getArrayRegion(self, arr, img=None):
|
def getArrayRegion(self, arr, img=None, axes=(0, 1), **kwds):
|
||||||
"""
|
"""
|
||||||
Return the result of ROI.getArrayRegion() masked by the elliptical shape
|
Return the result of ROI.getArrayRegion() masked by the elliptical shape
|
||||||
of the ROI. Regions outside the ellipse are set to 0.
|
of the ROI. Regions outside the ellipse are set to 0.
|
||||||
"""
|
"""
|
||||||
arr = ROI.getArrayRegion(self, arr, img)
|
# Note: we could use the same method as used by PolyLineROI, but this
|
||||||
if arr is None or arr.shape[0] == 0 or arr.shape[1] == 0:
|
# implementation produces a nicer mask.
|
||||||
return None
|
arr = ROI.getArrayRegion(self, arr, img, axes, **kwds)
|
||||||
w = arr.shape[0]
|
if arr is None or arr.shape[axes[0]] == 0 or arr.shape[axes[1]] == 0:
|
||||||
h = arr.shape[1]
|
return arr
|
||||||
|
w = arr.shape[axes[0]]
|
||||||
|
h = arr.shape[axes[1]]
|
||||||
## generate an ellipsoidal mask
|
## generate an ellipsoidal mask
|
||||||
mask = np.fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h))
|
mask = np.fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h))
|
||||||
|
|
||||||
|
# reshape to match array axes
|
||||||
|
if axes[0] > axes[1]:
|
||||||
|
mask = mask.T
|
||||||
|
shape = [(n if i in axes else 1) for i,n in enumerate(arr.shape)]
|
||||||
|
mask = mask.reshape(shape)
|
||||||
|
|
||||||
return arr * mask
|
return arr * mask
|
||||||
|
|
||||||
def shape(self):
|
def shape(self):
|
||||||
@ -1782,6 +1849,7 @@ class PolygonROI(ROI):
|
|||||||
#sc['handles'] = self.handles
|
#sc['handles'] = self.handles
|
||||||
return sc
|
return sc
|
||||||
|
|
||||||
|
|
||||||
class PolyLineROI(ROI):
|
class PolyLineROI(ROI):
|
||||||
"""
|
"""
|
||||||
Container class for multiple connected LineSegmentROIs.
|
Container class for multiple connected LineSegmentROIs.
|
||||||
@ -1811,12 +1879,6 @@ class PolyLineROI(ROI):
|
|||||||
ROI.__init__(self, pos, size=[1,1], **args)
|
ROI.__init__(self, pos, size=[1,1], **args)
|
||||||
|
|
||||||
self.setPoints(positions)
|
self.setPoints(positions)
|
||||||
#for p in positions:
|
|
||||||
#self.addFreeHandle(p)
|
|
||||||
|
|
||||||
#start = -1 if self.closed else 0
|
|
||||||
#for i in range(start, len(self.handles)-1):
|
|
||||||
#self.addSegment(self.handles[i]['item'], self.handles[i+1]['item'])
|
|
||||||
|
|
||||||
def setPoints(self, points, closed=None):
|
def setPoints(self, points, closed=None):
|
||||||
"""
|
"""
|
||||||
@ -1834,6 +1896,8 @@ class PolyLineROI(ROI):
|
|||||||
if closed is not None:
|
if closed is not None:
|
||||||
self.closed = closed
|
self.closed = closed
|
||||||
|
|
||||||
|
self.clearPoints()
|
||||||
|
|
||||||
for p in points:
|
for p in points:
|
||||||
self.addFreeHandle(p)
|
self.addFreeHandle(p)
|
||||||
|
|
||||||
@ -1841,13 +1905,18 @@ class PolyLineROI(ROI):
|
|||||||
for i in range(start, len(self.handles)-1):
|
for i in range(start, len(self.handles)-1):
|
||||||
self.addSegment(self.handles[i]['item'], self.handles[i+1]['item'])
|
self.addSegment(self.handles[i]['item'], self.handles[i+1]['item'])
|
||||||
|
|
||||||
|
|
||||||
def clearPoints(self):
|
def clearPoints(self):
|
||||||
"""
|
"""
|
||||||
Remove all handles and segments.
|
Remove all handles and segments.
|
||||||
"""
|
"""
|
||||||
while len(self.handles) > 0:
|
while len(self.handles) > 0:
|
||||||
self.removeHandle(self.handles[0]['item'])
|
self.removeHandle(self.handles[0]['item'])
|
||||||
|
|
||||||
|
def getState(self):
|
||||||
|
state = ROI.getState(self)
|
||||||
|
state['closed'] = self.closed
|
||||||
|
state['points'] = [Point(h.pos()) for h in self.getHandles()]
|
||||||
|
return state
|
||||||
|
|
||||||
def saveState(self):
|
def saveState(self):
|
||||||
state = ROI.saveState(self)
|
state = ROI.saveState(self)
|
||||||
@ -1857,11 +1926,10 @@ class PolyLineROI(ROI):
|
|||||||
|
|
||||||
def setState(self, state):
|
def setState(self, state):
|
||||||
ROI.setState(self, state)
|
ROI.setState(self, state)
|
||||||
self.clearPoints()
|
|
||||||
self.setPoints(state['points'], closed=state['closed'])
|
self.setPoints(state['points'], closed=state['closed'])
|
||||||
|
|
||||||
def addSegment(self, h1, h2, index=None):
|
def addSegment(self, h1, h2, index=None):
|
||||||
seg = LineSegmentROI(handles=(h1, h2), pen=self.pen, parent=self, movable=False)
|
seg = _PolyLineSegment(handles=(h1, h2), pen=self.pen, parent=self, movable=False)
|
||||||
if index is None:
|
if index is None:
|
||||||
self.segments.append(seg)
|
self.segments.append(seg)
|
||||||
else:
|
else:
|
||||||
@ -1877,11 +1945,12 @@ class PolyLineROI(ROI):
|
|||||||
## Inform all the ROI's segments that the mouse is(not) hovering over it
|
## Inform all the ROI's segments that the mouse is(not) hovering over it
|
||||||
ROI.setMouseHover(self, hover)
|
ROI.setMouseHover(self, hover)
|
||||||
for s in self.segments:
|
for s in self.segments:
|
||||||
s.setMouseHover(hover)
|
s.setParentHover(hover)
|
||||||
|
|
||||||
def addHandle(self, info, index=None):
|
def addHandle(self, info, index=None):
|
||||||
h = ROI.addHandle(self, info, index=index)
|
h = ROI.addHandle(self, info, index=index)
|
||||||
h.sigRemoveRequested.connect(self.removeHandle)
|
h.sigRemoveRequested.connect(self.removeHandle)
|
||||||
|
self.stateChanged(finish=True)
|
||||||
return h
|
return h
|
||||||
|
|
||||||
def segmentClicked(self, segment, ev=None, pos=None): ## pos should be in this item's coordinate system
|
def segmentClicked(self, segment, ev=None, pos=None): ## pos should be in this item's coordinate system
|
||||||
@ -1909,11 +1978,12 @@ class PolyLineROI(ROI):
|
|||||||
|
|
||||||
if len(segments) == 1:
|
if len(segments) == 1:
|
||||||
self.removeSegment(segments[0])
|
self.removeSegment(segments[0])
|
||||||
else:
|
elif len(segments) > 1:
|
||||||
handles = [h['item'] for h in segments[1].handles]
|
handles = [h['item'] for h in segments[1].handles]
|
||||||
handles.remove(handle)
|
handles.remove(handle)
|
||||||
segments[0].replaceHandle(handle, handles[0])
|
segments[0].replaceHandle(handle, handles[0])
|
||||||
self.removeSegment(segments[1])
|
self.removeSegment(segments[1])
|
||||||
|
self.stateChanged(finish=True)
|
||||||
|
|
||||||
def removeSegment(self, seg):
|
def removeSegment(self, seg):
|
||||||
for handle in seg.handles[:]:
|
for handle in seg.handles[:]:
|
||||||
@ -1930,20 +2000,10 @@ class PolyLineROI(ROI):
|
|||||||
return len(self.handles) > 2
|
return len(self.handles) > 2
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
#for s in self.segments:
|
|
||||||
#s.update()
|
|
||||||
#p.setPen(self.currentPen)
|
|
||||||
#p.setPen(fn.mkPen('w'))
|
|
||||||
#p.drawRect(self.boundingRect())
|
|
||||||
#p.drawPath(self.shape())
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
return self.shape().boundingRect()
|
return self.shape().boundingRect()
|
||||||
#r = QtCore.QRectF()
|
|
||||||
#for h in self.handles:
|
|
||||||
#r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs
|
|
||||||
#return r
|
|
||||||
|
|
||||||
def shape(self):
|
def shape(self):
|
||||||
p = QtGui.QPainterPath()
|
p = QtGui.QPainterPath()
|
||||||
@ -1953,32 +2013,31 @@ class PolyLineROI(ROI):
|
|||||||
for i in range(len(self.handles)):
|
for i in range(len(self.handles)):
|
||||||
p.lineTo(self.handles[i]['item'].pos())
|
p.lineTo(self.handles[i]['item'].pos())
|
||||||
p.lineTo(self.handles[0]['item'].pos())
|
p.lineTo(self.handles[0]['item'].pos())
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds):
|
def getArrayRegion(self, data, img, axes=(0,1), **kwds):
|
||||||
"""
|
"""
|
||||||
Return the result of ROI.getArrayRegion(), masked by the shape of the
|
Return the result of ROI.getArrayRegion(), masked by the shape of the
|
||||||
ROI. Values outside the ROI shape are set to 0.
|
ROI. Values outside the ROI shape are set to 0.
|
||||||
"""
|
"""
|
||||||
sl = self.getArraySlice(data, img, axes=(0,1))
|
br = self.boundingRect()
|
||||||
if sl is None:
|
if br.width() > 1000:
|
||||||
return None
|
raise Exception()
|
||||||
sliced = data[sl[0]]
|
sliced = ROI.getArrayRegion(self, data, img, axes=axes, fromBoundingRect=True, **kwds)
|
||||||
im = QtGui.QImage(sliced.shape[axes[0]], sliced.shape[axes[1]], QtGui.QImage.Format_ARGB32)
|
|
||||||
im.fill(0x0)
|
if img.axisOrder == 'col-major':
|
||||||
p = QtGui.QPainter(im)
|
mask = self.renderShapeMask(sliced.shape[axes[0]], sliced.shape[axes[1]])
|
||||||
p.setPen(fn.mkPen(None))
|
else:
|
||||||
p.setBrush(fn.mkBrush('w'))
|
mask = self.renderShapeMask(sliced.shape[axes[1]], sliced.shape[axes[0]])
|
||||||
p.setTransform(self.itemTransform(img)[0])
|
mask = mask.T
|
||||||
bounds = self.mapRectToItem(img, self.boundingRect())
|
|
||||||
p.translate(-bounds.left(), -bounds.top())
|
# reshape mask to ensure it is applied to the correct data axes
|
||||||
p.drawPath(self.shape())
|
|
||||||
p.end()
|
|
||||||
mask = fn.imageToArray(im)[:,:,0].astype(float) / 255.
|
|
||||||
shape = [1] * data.ndim
|
shape = [1] * data.ndim
|
||||||
shape[axes[0]] = sliced.shape[axes[0]]
|
shape[axes[0]] = sliced.shape[axes[0]]
|
||||||
shape[axes[1]] = sliced.shape[axes[1]]
|
shape[axes[1]] = sliced.shape[axes[1]]
|
||||||
return sliced * mask.reshape(shape)
|
mask = mask.reshape(shape)
|
||||||
|
|
||||||
|
return sliced * mask
|
||||||
|
|
||||||
def setPen(self, *args, **kwds):
|
def setPen(self, *args, **kwds):
|
||||||
ROI.setPen(self, *args, **kwds)
|
ROI.setPen(self, *args, **kwds)
|
||||||
@ -2050,7 +2109,7 @@ class LineSegmentROI(ROI):
|
|||||||
|
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def getArrayRegion(self, data, img, axes=(0,1)):
|
def getArrayRegion(self, data, img, axes=(0,1), order=1, **kwds):
|
||||||
"""
|
"""
|
||||||
Use the position of this ROI relative to an imageItem to pull a slice
|
Use the position of this ROI relative to an imageItem to pull a slice
|
||||||
from an array.
|
from an array.
|
||||||
@ -2066,12 +2125,38 @@ class LineSegmentROI(ROI):
|
|||||||
for i in range(len(imgPts)-1):
|
for i in range(len(imgPts)-1):
|
||||||
d = Point(imgPts[i+1] - imgPts[i])
|
d = Point(imgPts[i+1] - imgPts[i])
|
||||||
o = Point(imgPts[i])
|
o = Point(imgPts[i])
|
||||||
r = fn.affineSlice(data, shape=(int(d.length()),), vectors=[Point(d.norm())], origin=o, axes=axes, order=1)
|
r = fn.affineSlice(data, shape=(int(d.length()),), vectors=[Point(d.norm())], origin=o, axes=axes, order=order, **kwds)
|
||||||
rgns.append(r)
|
rgns.append(r)
|
||||||
|
|
||||||
return np.concatenate(rgns, axis=axes[0])
|
return np.concatenate(rgns, axis=axes[0])
|
||||||
|
|
||||||
|
|
||||||
|
class _PolyLineSegment(LineSegmentROI):
|
||||||
|
# Used internally by PolyLineROI
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
self._parentHovering = False
|
||||||
|
LineSegmentROI.__init__(self, *args, **kwds)
|
||||||
|
|
||||||
|
def setParentHover(self, hover):
|
||||||
|
# set independently of own hover state
|
||||||
|
if self._parentHovering != hover:
|
||||||
|
self._parentHovering = hover
|
||||||
|
self._updateHoverColor()
|
||||||
|
|
||||||
|
def _makePen(self):
|
||||||
|
if self.mouseHovering or self._parentHovering:
|
||||||
|
return fn.mkPen(255, 255, 0)
|
||||||
|
else:
|
||||||
|
return self.pen
|
||||||
|
|
||||||
|
def hoverEvent(self, ev):
|
||||||
|
# accept drags even though we discard them to prevent competition with parent ROI
|
||||||
|
# (unless parent ROI is not movable)
|
||||||
|
if self.parentItem().translatable:
|
||||||
|
ev.acceptDrags(QtCore.Qt.LeftButton)
|
||||||
|
return LineSegmentROI.hoverEvent(self, ev)
|
||||||
|
|
||||||
|
|
||||||
class SpiralROI(ROI):
|
class SpiralROI(ROI):
|
||||||
def __init__(self, pos=None, size=None, **args):
|
def __init__(self, pos=None, size=None, **args):
|
||||||
if size == None:
|
if size == None:
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
from ..Qt import QtGui, QtCore, USE_PYSIDE
|
|
||||||
from ..Point import Point
|
|
||||||
from .. import functions as fn
|
|
||||||
from .GraphicsItem import GraphicsItem
|
|
||||||
from .GraphicsObject import GraphicsObject
|
|
||||||
from itertools import starmap, repeat
|
from itertools import starmap, repeat
|
||||||
try:
|
try:
|
||||||
from itertools import imap
|
from itertools import imap
|
||||||
@ -10,26 +5,42 @@ except ImportError:
|
|||||||
imap = map
|
imap = map
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import weakref
|
import weakref
|
||||||
|
from ..Qt import QtGui, QtCore, USE_PYSIDE, USE_PYQT5
|
||||||
|
from ..Point import Point
|
||||||
|
from .. import functions as fn
|
||||||
|
from .GraphicsItem import GraphicsItem
|
||||||
|
from .GraphicsObject import GraphicsObject
|
||||||
from .. import getConfigOption
|
from .. import getConfigOption
|
||||||
from .. import debug as debug
|
|
||||||
from ..pgcollections import OrderedDict
|
from ..pgcollections import OrderedDict
|
||||||
from .. import debug
|
from .. import debug
|
||||||
|
from ..python2_3 import basestring
|
||||||
|
|
||||||
__all__ = ['ScatterPlotItem', 'SpotItem']
|
__all__ = ['ScatterPlotItem', 'SpotItem']
|
||||||
|
|
||||||
|
|
||||||
## Build all symbol paths
|
## Build all symbol paths
|
||||||
Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in ['o', 's', 't', 'd', '+', 'x']])
|
Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in ['o', 's', 't', 't1', 't2', 't3','d', '+', 'x', 'p', 'h', 'star']])
|
||||||
Symbols['o'].addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))
|
Symbols['o'].addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))
|
||||||
Symbols['s'].addRect(QtCore.QRectF(-0.5, -0.5, 1, 1))
|
Symbols['s'].addRect(QtCore.QRectF(-0.5, -0.5, 1, 1))
|
||||||
coords = {
|
coords = {
|
||||||
't': [(-0.5, -0.5), (0, 0.5), (0.5, -0.5)],
|
't': [(-0.5, -0.5), (0, 0.5), (0.5, -0.5)],
|
||||||
|
't1': [(-0.5, 0.5), (0, -0.5), (0.5, 0.5)],
|
||||||
|
't2': [(-0.5, -0.5), (-0.5, 0.5), (0.5, 0)],
|
||||||
|
't3': [(0.5, 0.5), (0.5, -0.5), (-0.5, 0)],
|
||||||
'd': [(0., -0.5), (-0.4, 0.), (0, 0.5), (0.4, 0)],
|
'd': [(0., -0.5), (-0.4, 0.), (0, 0.5), (0.4, 0)],
|
||||||
'+': [
|
'+': [
|
||||||
(-0.5, -0.05), (-0.5, 0.05), (-0.05, 0.05), (-0.05, 0.5),
|
(-0.5, -0.05), (-0.5, 0.05), (-0.05, 0.05), (-0.05, 0.5),
|
||||||
(0.05, 0.5), (0.05, 0.05), (0.5, 0.05), (0.5, -0.05),
|
(0.05, 0.5), (0.05, 0.05), (0.5, 0.05), (0.5, -0.05),
|
||||||
(0.05, -0.05), (0.05, -0.5), (-0.05, -0.5), (-0.05, -0.05)
|
(0.05, -0.05), (0.05, -0.5), (-0.05, -0.5), (-0.05, -0.05)
|
||||||
],
|
],
|
||||||
|
'p': [(0, -0.5), (-0.4755, -0.1545), (-0.2939, 0.4045),
|
||||||
|
(0.2939, 0.4045), (0.4755, -0.1545)],
|
||||||
|
'h': [(0.433, 0.25), (0., 0.5), (-0.433, 0.25), (-0.433, -0.25),
|
||||||
|
(0, -0.5), (0.433, -0.25)],
|
||||||
|
'star': [(0, -0.5), (-0.1123, -0.1545), (-0.4755, -0.1545),
|
||||||
|
(-0.1816, 0.059), (-0.2939, 0.4045), (0, 0.1910),
|
||||||
|
(0.2939, 0.4045), (0.1816, 0.059), (0.4755, -0.1545),
|
||||||
|
(0.1123, -0.1545)]
|
||||||
}
|
}
|
||||||
for k, c in coords.items():
|
for k, c in coords.items():
|
||||||
Symbols[k].moveTo(*c[0])
|
Symbols[k].moveTo(*c[0])
|
||||||
@ -40,7 +51,7 @@ tr = QtGui.QTransform()
|
|||||||
tr.rotate(45)
|
tr.rotate(45)
|
||||||
Symbols['x'] = tr.map(Symbols['+'])
|
Symbols['x'] = tr.map(Symbols['+'])
|
||||||
|
|
||||||
|
|
||||||
def drawSymbol(painter, symbol, size, pen, brush):
|
def drawSymbol(painter, symbol, size, pen, brush):
|
||||||
if symbol is None:
|
if symbol is None:
|
||||||
return
|
return
|
||||||
@ -53,13 +64,13 @@ def drawSymbol(painter, symbol, size, pen, brush):
|
|||||||
symbol = list(Symbols.values())[symbol % len(Symbols)]
|
symbol = list(Symbols.values())[symbol % len(Symbols)]
|
||||||
painter.drawPath(symbol)
|
painter.drawPath(symbol)
|
||||||
|
|
||||||
|
|
||||||
def renderSymbol(symbol, size, pen, brush, device=None):
|
def renderSymbol(symbol, size, pen, brush, device=None):
|
||||||
"""
|
"""
|
||||||
Render a symbol specification to QImage.
|
Render a symbol specification to QImage.
|
||||||
Symbol may be either a QPainterPath or one of the keys in the Symbols dict.
|
Symbol may be either a QPainterPath or one of the keys in the Symbols dict.
|
||||||
If *device* is None, a new QPixmap will be returned. Otherwise,
|
If *device* is None, a new QPixmap will be returned. Otherwise,
|
||||||
the symbol will be rendered into the device specified (See QPainter documentation
|
the symbol will be rendered into the device specified (See QPainter documentation
|
||||||
for more information).
|
for more information).
|
||||||
"""
|
"""
|
||||||
## Render a spot with the given parameters to a pixmap
|
## Render a spot with the given parameters to a pixmap
|
||||||
@ -80,33 +91,33 @@ def makeSymbolPixmap(size, pen, brush, symbol):
|
|||||||
## deprecated
|
## deprecated
|
||||||
img = renderSymbol(symbol, size, pen, brush)
|
img = renderSymbol(symbol, size, pen, brush)
|
||||||
return QtGui.QPixmap(img)
|
return QtGui.QPixmap(img)
|
||||||
|
|
||||||
class SymbolAtlas(object):
|
class SymbolAtlas(object):
|
||||||
"""
|
"""
|
||||||
Used to efficiently construct a single QPixmap containing all rendered symbols
|
Used to efficiently construct a single QPixmap containing all rendered symbols
|
||||||
for a ScatterPlotItem. This is required for fragment rendering.
|
for a ScatterPlotItem. This is required for fragment rendering.
|
||||||
|
|
||||||
Use example:
|
Use example:
|
||||||
atlas = SymbolAtlas()
|
atlas = SymbolAtlas()
|
||||||
sc1 = atlas.getSymbolCoords('o', 5, QPen(..), QBrush(..))
|
sc1 = atlas.getSymbolCoords('o', 5, QPen(..), QBrush(..))
|
||||||
sc2 = atlas.getSymbolCoords('t', 10, QPen(..), QBrush(..))
|
sc2 = atlas.getSymbolCoords('t', 10, QPen(..), QBrush(..))
|
||||||
pm = atlas.getAtlas()
|
pm = atlas.getAtlas()
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# symbol key : QRect(...) coordinates where symbol can be found in atlas.
|
# symbol key : QRect(...) coordinates where symbol can be found in atlas.
|
||||||
# note that the coordinate list will always be the same list object as
|
# note that the coordinate list will always be the same list object as
|
||||||
# long as the symbol is in the atlas, but the coordinates may
|
# long as the symbol is in the atlas, but the coordinates may
|
||||||
# change if the atlas is rebuilt.
|
# change if the atlas is rebuilt.
|
||||||
# weak value; if all external refs to this list disappear,
|
# weak value; if all external refs to this list disappear,
|
||||||
# the symbol will be forgotten.
|
# the symbol will be forgotten.
|
||||||
self.symbolMap = weakref.WeakValueDictionary()
|
self.symbolMap = weakref.WeakValueDictionary()
|
||||||
|
|
||||||
self.atlasData = None # numpy array of atlas image
|
self.atlasData = None # numpy array of atlas image
|
||||||
self.atlas = None # atlas as QPixmap
|
self.atlas = None # atlas as QPixmap
|
||||||
self.atlasValid = False
|
self.atlasValid = False
|
||||||
self.max_width=0
|
self.max_width=0
|
||||||
|
|
||||||
def getSymbolCoords(self, opts):
|
def getSymbolCoords(self, opts):
|
||||||
"""
|
"""
|
||||||
Given a list of spot records, return an object representing the coordinates of that symbol within the atlas
|
Given a list of spot records, return an object representing the coordinates of that symbol within the atlas
|
||||||
@ -131,7 +142,7 @@ class SymbolAtlas(object):
|
|||||||
keyi = key
|
keyi = key
|
||||||
sourceRecti = newRectSrc
|
sourceRecti = newRectSrc
|
||||||
return sourceRect
|
return sourceRect
|
||||||
|
|
||||||
def buildAtlas(self):
|
def buildAtlas(self):
|
||||||
# get rendered array for all symbols, keep track of avg/max width
|
# get rendered array for all symbols, keep track of avg/max width
|
||||||
rendered = {}
|
rendered = {}
|
||||||
@ -145,12 +156,12 @@ class SymbolAtlas(object):
|
|||||||
arr = fn.imageToArray(img, copy=False, transpose=False)
|
arr = fn.imageToArray(img, copy=False, transpose=False)
|
||||||
else:
|
else:
|
||||||
(y,x,h,w) = sourceRect.getRect()
|
(y,x,h,w) = sourceRect.getRect()
|
||||||
arr = self.atlasData[x:x+w, y:y+w]
|
arr = self.atlasData[int(x):int(x+w), int(y):int(y+w)]
|
||||||
rendered[key] = arr
|
rendered[key] = arr
|
||||||
w = arr.shape[0]
|
w = arr.shape[0]
|
||||||
avgWidth += w
|
avgWidth += w
|
||||||
maxWidth = max(maxWidth, w)
|
maxWidth = max(maxWidth, w)
|
||||||
|
|
||||||
nSymbols = len(rendered)
|
nSymbols = len(rendered)
|
||||||
if nSymbols > 0:
|
if nSymbols > 0:
|
||||||
avgWidth /= nSymbols
|
avgWidth /= nSymbols
|
||||||
@ -158,10 +169,10 @@ class SymbolAtlas(object):
|
|||||||
else:
|
else:
|
||||||
avgWidth = 0
|
avgWidth = 0
|
||||||
width = 0
|
width = 0
|
||||||
|
|
||||||
# sort symbols by height
|
# sort symbols by height
|
||||||
symbols = sorted(rendered.keys(), key=lambda x: rendered[x].shape[1], reverse=True)
|
symbols = sorted(rendered.keys(), key=lambda x: rendered[x].shape[1], reverse=True)
|
||||||
|
|
||||||
self.atlasRows = []
|
self.atlasRows = []
|
||||||
|
|
||||||
x = width
|
x = width
|
||||||
@ -180,14 +191,14 @@ class SymbolAtlas(object):
|
|||||||
self.atlasRows[-1][2] = x
|
self.atlasRows[-1][2] = x
|
||||||
height = y + rowheight
|
height = y + rowheight
|
||||||
|
|
||||||
self.atlasData = np.zeros((width, height, 4), dtype=np.ubyte)
|
self.atlasData = np.zeros((int(width), int(height), 4), dtype=np.ubyte)
|
||||||
for key in symbols:
|
for key in symbols:
|
||||||
y, x, h, w = self.symbolMap[key].getRect()
|
y, x, h, w = self.symbolMap[key].getRect()
|
||||||
self.atlasData[x:x+w, y:y+h] = rendered[key]
|
self.atlasData[int(x):int(x+w), int(y):int(y+h)] = rendered[key]
|
||||||
self.atlas = None
|
self.atlas = None
|
||||||
self.atlasValid = True
|
self.atlasValid = True
|
||||||
self.max_width = maxWidth
|
self.max_width = maxWidth
|
||||||
|
|
||||||
def getAtlas(self):
|
def getAtlas(self):
|
||||||
if not self.atlasValid:
|
if not self.atlasValid:
|
||||||
self.buildAtlas()
|
self.buildAtlas()
|
||||||
@ -197,27 +208,27 @@ class SymbolAtlas(object):
|
|||||||
img = fn.makeQImage(self.atlasData, copy=False, transpose=False)
|
img = fn.makeQImage(self.atlasData, copy=False, transpose=False)
|
||||||
self.atlas = QtGui.QPixmap(img)
|
self.atlas = QtGui.QPixmap(img)
|
||||||
return self.atlas
|
return self.atlas
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ScatterPlotItem(GraphicsObject):
|
class ScatterPlotItem(GraphicsObject):
|
||||||
"""
|
"""
|
||||||
Displays a set of x/y points. Instances of this class are created
|
Displays a set of x/y points. Instances of this class are created
|
||||||
automatically as part of PlotDataItem; these rarely need to be instantiated
|
automatically as part of PlotDataItem; these rarely need to be instantiated
|
||||||
directly.
|
directly.
|
||||||
|
|
||||||
The size, shape, pen, and fill brush may be set for each point individually
|
The size, shape, pen, and fill brush may be set for each point individually
|
||||||
or for all points.
|
or for all points.
|
||||||
|
|
||||||
|
|
||||||
======================== ===============================================
|
======================== ===============================================
|
||||||
**Signals:**
|
**Signals:**
|
||||||
sigPlotChanged(self) Emitted when the data being plotted has changed
|
sigPlotChanged(self) Emitted when the data being plotted has changed
|
||||||
sigClicked(self, points) Emitted when the curve is clicked. Sends a list
|
sigClicked(self, points) Emitted when the curve is clicked. Sends a list
|
||||||
of all the points under the mouse pointer.
|
of all the points under the mouse pointer.
|
||||||
======================== ===============================================
|
======================== ===============================================
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#sigPointClicked = QtCore.Signal(object, object)
|
#sigPointClicked = QtCore.Signal(object, object)
|
||||||
sigClicked = QtCore.Signal(object, object) ## self, points
|
sigClicked = QtCore.Signal(object, object) ## self, points
|
||||||
@ -228,17 +239,17 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
"""
|
"""
|
||||||
profiler = debug.Profiler()
|
profiler = debug.Profiler()
|
||||||
GraphicsObject.__init__(self)
|
GraphicsObject.__init__(self)
|
||||||
|
|
||||||
self.picture = None # QPicture used for rendering when pxmode==False
|
self.picture = None # QPicture used for rendering when pxmode==False
|
||||||
self.fragmentAtlas = SymbolAtlas()
|
self.fragmentAtlas = SymbolAtlas()
|
||||||
|
|
||||||
self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('data', object), ('item', object), ('sourceRect', object), ('targetRect', object), ('width', float)])
|
self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('data', object), ('item', object), ('sourceRect', object), ('targetRect', object), ('width', float)])
|
||||||
self.bounds = [None, None] ## caches data bounds
|
self.bounds = [None, None] ## caches data bounds
|
||||||
self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots
|
self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots
|
||||||
self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots
|
self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots
|
||||||
self.opts = {
|
self.opts = {
|
||||||
'pxMode': True,
|
'pxMode': True,
|
||||||
'useCache': True, ## If useCache is False, symbols are re-drawn on every paint.
|
'useCache': True, ## If useCache is False, symbols are re-drawn on every paint.
|
||||||
'antialias': getConfigOption('antialias'),
|
'antialias': getConfigOption('antialias'),
|
||||||
'name': None,
|
'name': None,
|
||||||
}
|
}
|
||||||
@ -252,14 +263,14 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
profiler('setData')
|
profiler('setData')
|
||||||
|
|
||||||
#self.setCacheMode(self.DeviceCoordinateCache)
|
#self.setCacheMode(self.DeviceCoordinateCache)
|
||||||
|
|
||||||
def setData(self, *args, **kargs):
|
def setData(self, *args, **kargs):
|
||||||
"""
|
"""
|
||||||
**Ordered Arguments:**
|
**Ordered Arguments:**
|
||||||
|
|
||||||
* If there is only one unnamed argument, it will be interpreted like the 'spots' argument.
|
* If there is only one unnamed argument, it will be interpreted like the 'spots' argument.
|
||||||
* If there are two unnamed arguments, they will be interpreted as sequences of x and y values.
|
* If there are two unnamed arguments, they will be interpreted as sequences of x and y values.
|
||||||
|
|
||||||
====================== ===============================================================================================
|
====================== ===============================================================================================
|
||||||
**Keyword Arguments:**
|
**Keyword Arguments:**
|
||||||
*spots* Optional list of dicts. Each dict specifies parameters for a single spot:
|
*spots* Optional list of dicts. Each dict specifies parameters for a single spot:
|
||||||
@ -285,8 +296,8 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
it is in the item's local coordinate system.
|
it is in the item's local coordinate system.
|
||||||
*data* a list of python objects used to uniquely identify each spot.
|
*data* a list of python objects used to uniquely identify each spot.
|
||||||
*identical* *Deprecated*. This functionality is handled automatically now.
|
*identical* *Deprecated*. This functionality is handled automatically now.
|
||||||
*antialias* Whether to draw symbols with antialiasing. Note that if pxMode is True, symbols are
|
*antialias* Whether to draw symbols with antialiasing. Note that if pxMode is True, symbols are
|
||||||
always rendered with antialiasing (since the rendered symbols can be cached, this
|
always rendered with antialiasing (since the rendered symbols can be cached, this
|
||||||
incurs very little performance cost)
|
incurs very little performance cost)
|
||||||
*name* The name of this item. Names are used for automatically
|
*name* The name of this item. Names are used for automatically
|
||||||
generating LegendItem entries and by some exporters.
|
generating LegendItem entries and by some exporters.
|
||||||
@ -298,10 +309,10 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
|
|
||||||
def addPoints(self, *args, **kargs):
|
def addPoints(self, *args, **kargs):
|
||||||
"""
|
"""
|
||||||
Add new points to the scatter plot.
|
Add new points to the scatter plot.
|
||||||
Arguments are the same as setData()
|
Arguments are the same as setData()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
## deal with non-keyword arguments
|
## deal with non-keyword arguments
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
kargs['spots'] = args[0]
|
kargs['spots'] = args[0]
|
||||||
@ -310,7 +321,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
kargs['y'] = args[1]
|
kargs['y'] = args[1]
|
||||||
elif len(args) > 2:
|
elif len(args) > 2:
|
||||||
raise Exception('Only accepts up to two non-keyword arguments.')
|
raise Exception('Only accepts up to two non-keyword arguments.')
|
||||||
|
|
||||||
## convert 'pos' argument to 'x' and 'y'
|
## convert 'pos' argument to 'x' and 'y'
|
||||||
if 'pos' in kargs:
|
if 'pos' in kargs:
|
||||||
pos = kargs['pos']
|
pos = kargs['pos']
|
||||||
@ -329,7 +340,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
y.append(p[1])
|
y.append(p[1])
|
||||||
kargs['x'] = x
|
kargs['x'] = x
|
||||||
kargs['y'] = y
|
kargs['y'] = y
|
||||||
|
|
||||||
## determine how many spots we have
|
## determine how many spots we have
|
||||||
if 'spots' in kargs:
|
if 'spots' in kargs:
|
||||||
numPts = len(kargs['spots'])
|
numPts = len(kargs['spots'])
|
||||||
@ -339,16 +350,16 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
kargs['x'] = []
|
kargs['x'] = []
|
||||||
kargs['y'] = []
|
kargs['y'] = []
|
||||||
numPts = 0
|
numPts = 0
|
||||||
|
|
||||||
## Extend record array
|
## Extend record array
|
||||||
oldData = self.data
|
oldData = self.data
|
||||||
self.data = np.empty(len(oldData)+numPts, dtype=self.data.dtype)
|
self.data = np.empty(len(oldData)+numPts, dtype=self.data.dtype)
|
||||||
## note that np.empty initializes object fields to None and string fields to ''
|
## note that np.empty initializes object fields to None and string fields to ''
|
||||||
|
|
||||||
self.data[:len(oldData)] = oldData
|
self.data[:len(oldData)] = oldData
|
||||||
#for i in range(len(oldData)):
|
#for i in range(len(oldData)):
|
||||||
#oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array
|
#oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array
|
||||||
|
|
||||||
newData = self.data[len(oldData):]
|
newData = self.data[len(oldData):]
|
||||||
newData['size'] = -1 ## indicates to use default size
|
newData['size'] = -1 ## indicates to use default size
|
||||||
|
|
||||||
@ -376,12 +387,12 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
elif 'y' in kargs:
|
elif 'y' in kargs:
|
||||||
newData['x'] = kargs['x']
|
newData['x'] = kargs['x']
|
||||||
newData['y'] = kargs['y']
|
newData['y'] = kargs['y']
|
||||||
|
|
||||||
if 'pxMode' in kargs:
|
if 'pxMode' in kargs:
|
||||||
self.setPxMode(kargs['pxMode'])
|
self.setPxMode(kargs['pxMode'])
|
||||||
if 'antialias' in kargs:
|
if 'antialias' in kargs:
|
||||||
self.opts['antialias'] = kargs['antialias']
|
self.opts['antialias'] = kargs['antialias']
|
||||||
|
|
||||||
## Set any extra parameters provided in keyword arguments
|
## Set any extra parameters provided in keyword arguments
|
||||||
for k in ['pen', 'brush', 'symbol', 'size']:
|
for k in ['pen', 'brush', 'symbol', 'size']:
|
||||||
if k in kargs:
|
if k in kargs:
|
||||||
@ -397,32 +408,32 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
self.invalidate()
|
self.invalidate()
|
||||||
self.updateSpots(newData)
|
self.updateSpots(newData)
|
||||||
self.sigPlotChanged.emit(self)
|
self.sigPlotChanged.emit(self)
|
||||||
|
|
||||||
def invalidate(self):
|
def invalidate(self):
|
||||||
## clear any cached drawing state
|
## clear any cached drawing state
|
||||||
self.picture = None
|
self.picture = None
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def getData(self):
|
def getData(self):
|
||||||
return self.data['x'], self.data['y']
|
return self.data['x'], self.data['y']
|
||||||
|
|
||||||
def setPoints(self, *args, **kargs):
|
def setPoints(self, *args, **kargs):
|
||||||
##Deprecated; use setData
|
##Deprecated; use setData
|
||||||
return self.setData(*args, **kargs)
|
return self.setData(*args, **kargs)
|
||||||
|
|
||||||
def implements(self, interface=None):
|
def implements(self, interface=None):
|
||||||
ints = ['plotData']
|
ints = ['plotData']
|
||||||
if interface is None:
|
if interface is None:
|
||||||
return ints
|
return ints
|
||||||
return interface in ints
|
return interface in ints
|
||||||
|
|
||||||
def name(self):
|
def name(self):
|
||||||
return self.opts.get('name', None)
|
return self.opts.get('name', None)
|
||||||
|
|
||||||
def setPen(self, *args, **kargs):
|
def setPen(self, *args, **kargs):
|
||||||
"""Set the pen(s) used to draw the outline around each spot.
|
"""Set the pen(s) used to draw the outline around each spot.
|
||||||
If a list or array is provided, then the pen for each spot will be set separately.
|
If a list or array is provided, then the pen for each spot will be set separately.
|
||||||
Otherwise, the arguments are passed to pg.mkPen and used as the default pen for
|
Otherwise, the arguments are passed to pg.mkPen and used as the default pen for
|
||||||
all spots which do not have a pen explicitly set."""
|
all spots which do not have a pen explicitly set."""
|
||||||
update = kargs.pop('update', True)
|
update = kargs.pop('update', True)
|
||||||
dataSet = kargs.pop('dataSet', self.data)
|
dataSet = kargs.pop('dataSet', self.data)
|
||||||
@ -436,44 +447,42 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
dataSet['pen'] = pens
|
dataSet['pen'] = pens
|
||||||
else:
|
else:
|
||||||
self.opts['pen'] = fn.mkPen(*args, **kargs)
|
self.opts['pen'] = fn.mkPen(*args, **kargs)
|
||||||
|
|
||||||
dataSet['sourceRect'] = None
|
dataSet['sourceRect'] = None
|
||||||
if update:
|
if update:
|
||||||
self.updateSpots(dataSet)
|
self.updateSpots(dataSet)
|
||||||
|
|
||||||
def setBrush(self, *args, **kargs):
|
def setBrush(self, *args, **kargs):
|
||||||
"""Set the brush(es) used to fill the interior of each spot.
|
"""Set the brush(es) used to fill the interior of each spot.
|
||||||
If a list or array is provided, then the brush for each spot will be set separately.
|
If a list or array is provided, then the brush for each spot will be set separately.
|
||||||
Otherwise, the arguments are passed to pg.mkBrush and used as the default brush for
|
Otherwise, the arguments are passed to pg.mkBrush and used as the default brush for
|
||||||
all spots which do not have a brush explicitly set."""
|
all spots which do not have a brush explicitly set."""
|
||||||
update = kargs.pop('update', True)
|
update = kargs.pop('update', True)
|
||||||
dataSet = kargs.pop('dataSet', self.data)
|
dataSet = kargs.pop('dataSet', self.data)
|
||||||
|
|
||||||
if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)):
|
if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)):
|
||||||
brushes = args[0]
|
brushes = args[0]
|
||||||
if 'mask' in kargs and kargs['mask'] is not None:
|
if 'mask' in kargs and kargs['mask'] is not None:
|
||||||
brushes = brushes[kargs['mask']]
|
brushes = brushes[kargs['mask']]
|
||||||
if len(brushes) != len(dataSet):
|
if len(brushes) != len(dataSet):
|
||||||
raise Exception("Number of brushes does not match number of points (%d != %d)" % (len(brushes), len(dataSet)))
|
raise Exception("Number of brushes does not match number of points (%d != %d)" % (len(brushes), len(dataSet)))
|
||||||
#for i in xrange(len(brushes)):
|
|
||||||
#self.data[i]['brush'] = fn.mkBrush(brushes[i], **kargs)
|
|
||||||
dataSet['brush'] = brushes
|
dataSet['brush'] = brushes
|
||||||
else:
|
else:
|
||||||
self.opts['brush'] = fn.mkBrush(*args, **kargs)
|
self.opts['brush'] = fn.mkBrush(*args, **kargs)
|
||||||
#self._spotPixmap = None
|
#self._spotPixmap = None
|
||||||
|
|
||||||
dataSet['sourceRect'] = None
|
dataSet['sourceRect'] = None
|
||||||
if update:
|
if update:
|
||||||
self.updateSpots(dataSet)
|
self.updateSpots(dataSet)
|
||||||
|
|
||||||
def setSymbol(self, symbol, update=True, dataSet=None, mask=None):
|
def setSymbol(self, symbol, update=True, dataSet=None, mask=None):
|
||||||
"""Set the symbol(s) used to draw each spot.
|
"""Set the symbol(s) used to draw each spot.
|
||||||
If a list or array is provided, then the symbol for each spot will be set separately.
|
If a list or array is provided, then the symbol for each spot will be set separately.
|
||||||
Otherwise, the argument will be used as the default symbol for
|
Otherwise, the argument will be used as the default symbol for
|
||||||
all spots which do not have a symbol explicitly set."""
|
all spots which do not have a symbol explicitly set."""
|
||||||
if dataSet is None:
|
if dataSet is None:
|
||||||
dataSet = self.data
|
dataSet = self.data
|
||||||
|
|
||||||
if isinstance(symbol, np.ndarray) or isinstance(symbol, list):
|
if isinstance(symbol, np.ndarray) or isinstance(symbol, list):
|
||||||
symbols = symbol
|
symbols = symbol
|
||||||
if mask is not None:
|
if mask is not None:
|
||||||
@ -484,19 +493,19 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
else:
|
else:
|
||||||
self.opts['symbol'] = symbol
|
self.opts['symbol'] = symbol
|
||||||
self._spotPixmap = None
|
self._spotPixmap = None
|
||||||
|
|
||||||
dataSet['sourceRect'] = None
|
dataSet['sourceRect'] = None
|
||||||
if update:
|
if update:
|
||||||
self.updateSpots(dataSet)
|
self.updateSpots(dataSet)
|
||||||
|
|
||||||
def setSize(self, size, update=True, dataSet=None, mask=None):
|
def setSize(self, size, update=True, dataSet=None, mask=None):
|
||||||
"""Set the size(s) used to draw each spot.
|
"""Set the size(s) used to draw each spot.
|
||||||
If a list or array is provided, then the size for each spot will be set separately.
|
If a list or array is provided, then the size for each spot will be set separately.
|
||||||
Otherwise, the argument will be used as the default size for
|
Otherwise, the argument will be used as the default size for
|
||||||
all spots which do not have a size explicitly set."""
|
all spots which do not have a size explicitly set."""
|
||||||
if dataSet is None:
|
if dataSet is None:
|
||||||
dataSet = self.data
|
dataSet = self.data
|
||||||
|
|
||||||
if isinstance(size, np.ndarray) or isinstance(size, list):
|
if isinstance(size, np.ndarray) or isinstance(size, list):
|
||||||
sizes = size
|
sizes = size
|
||||||
if mask is not None:
|
if mask is not None:
|
||||||
@ -507,21 +516,21 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
else:
|
else:
|
||||||
self.opts['size'] = size
|
self.opts['size'] = size
|
||||||
self._spotPixmap = None
|
self._spotPixmap = None
|
||||||
|
|
||||||
dataSet['sourceRect'] = None
|
dataSet['sourceRect'] = None
|
||||||
if update:
|
if update:
|
||||||
self.updateSpots(dataSet)
|
self.updateSpots(dataSet)
|
||||||
|
|
||||||
def setPointData(self, data, dataSet=None, mask=None):
|
def setPointData(self, data, dataSet=None, mask=None):
|
||||||
if dataSet is None:
|
if dataSet is None:
|
||||||
dataSet = self.data
|
dataSet = self.data
|
||||||
|
|
||||||
if isinstance(data, np.ndarray) or isinstance(data, list):
|
if isinstance(data, np.ndarray) or isinstance(data, list):
|
||||||
if mask is not None:
|
if mask is not None:
|
||||||
data = data[mask]
|
data = data[mask]
|
||||||
if len(data) != len(dataSet):
|
if len(data) != len(dataSet):
|
||||||
raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(dataSet)))
|
raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(dataSet)))
|
||||||
|
|
||||||
## Bug: If data is a numpy record array, then items from that array must be copied to dataSet one at a time.
|
## Bug: If data is a numpy record array, then items from that array must be copied to dataSet one at a time.
|
||||||
## (otherwise they are converted to tuples and thus lose their field names.
|
## (otherwise they are converted to tuples and thus lose their field names.
|
||||||
if isinstance(data, np.ndarray) and (data.dtype.fields is not None)and len(data.dtype.fields) > 1:
|
if isinstance(data, np.ndarray) and (data.dtype.fields is not None)and len(data.dtype.fields) > 1:
|
||||||
@ -529,14 +538,14 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
dataSet['data'][i] = rec
|
dataSet['data'][i] = rec
|
||||||
else:
|
else:
|
||||||
dataSet['data'] = data
|
dataSet['data'] = data
|
||||||
|
|
||||||
def setPxMode(self, mode):
|
def setPxMode(self, mode):
|
||||||
if self.opts['pxMode'] == mode:
|
if self.opts['pxMode'] == mode:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.opts['pxMode'] = mode
|
self.opts['pxMode'] = mode
|
||||||
self.invalidate()
|
self.invalidate()
|
||||||
|
|
||||||
def updateSpots(self, dataSet=None):
|
def updateSpots(self, dataSet=None):
|
||||||
if dataSet is None:
|
if dataSet is None:
|
||||||
dataSet = self.data
|
dataSet = self.data
|
||||||
@ -549,9 +558,9 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
opts = self.getSpotOpts(dataSet[mask])
|
opts = self.getSpotOpts(dataSet[mask])
|
||||||
sourceRect = self.fragmentAtlas.getSymbolCoords(opts)
|
sourceRect = self.fragmentAtlas.getSymbolCoords(opts)
|
||||||
dataSet['sourceRect'][mask] = sourceRect
|
dataSet['sourceRect'][mask] = sourceRect
|
||||||
|
|
||||||
self.fragmentAtlas.getAtlas() # generate atlas so source widths are available.
|
self.fragmentAtlas.getAtlas() # generate atlas so source widths are available.
|
||||||
|
|
||||||
dataSet['width'] = np.array(list(imap(QtCore.QRectF.width, dataSet['sourceRect'])))/2
|
dataSet['width'] = np.array(list(imap(QtCore.QRectF.width, dataSet['sourceRect'])))/2
|
||||||
dataSet['targetRect'] = None
|
dataSet['targetRect'] = None
|
||||||
self._maxSpotPxWidth = self.fragmentAtlas.max_width
|
self._maxSpotPxWidth = self.fragmentAtlas.max_width
|
||||||
@ -587,9 +596,9 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
recs['pen'][np.equal(recs['pen'], None)] = fn.mkPen(self.opts['pen'])
|
recs['pen'][np.equal(recs['pen'], None)] = fn.mkPen(self.opts['pen'])
|
||||||
recs['brush'][np.equal(recs['brush'], None)] = fn.mkBrush(self.opts['brush'])
|
recs['brush'][np.equal(recs['brush'], None)] = fn.mkBrush(self.opts['brush'])
|
||||||
return recs
|
return recs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def measureSpotSizes(self, dataSet):
|
def measureSpotSizes(self, dataSet):
|
||||||
for rec in dataSet:
|
for rec in dataSet:
|
||||||
## keep track of the maximum spot size and pixel size
|
## keep track of the maximum spot size and pixel size
|
||||||
@ -607,8 +616,8 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
self._maxSpotWidth = max(self._maxSpotWidth, width)
|
self._maxSpotWidth = max(self._maxSpotWidth, width)
|
||||||
self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth)
|
self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth)
|
||||||
self.bounds = [None, None]
|
self.bounds = [None, None]
|
||||||
|
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Remove all spots from the scatter plot"""
|
"""Remove all spots from the scatter plot"""
|
||||||
#self.clearItems()
|
#self.clearItems()
|
||||||
@ -619,23 +628,23 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
def dataBounds(self, ax, frac=1.0, orthoRange=None):
|
def dataBounds(self, ax, frac=1.0, orthoRange=None):
|
||||||
if frac >= 1.0 and orthoRange is None and self.bounds[ax] is not None:
|
if frac >= 1.0 and orthoRange is None and self.bounds[ax] is not None:
|
||||||
return self.bounds[ax]
|
return self.bounds[ax]
|
||||||
|
|
||||||
#self.prepareGeometryChange()
|
#self.prepareGeometryChange()
|
||||||
if self.data is None or len(self.data) == 0:
|
if self.data is None or len(self.data) == 0:
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
if ax == 0:
|
if ax == 0:
|
||||||
d = self.data['x']
|
d = self.data['x']
|
||||||
d2 = self.data['y']
|
d2 = self.data['y']
|
||||||
elif ax == 1:
|
elif ax == 1:
|
||||||
d = self.data['y']
|
d = self.data['y']
|
||||||
d2 = self.data['x']
|
d2 = self.data['x']
|
||||||
|
|
||||||
if orthoRange is not None:
|
if orthoRange is not None:
|
||||||
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
|
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
|
||||||
d = d[mask]
|
d = d[mask]
|
||||||
d2 = d2[mask]
|
d2 = d2[mask]
|
||||||
|
|
||||||
if frac >= 1.0:
|
if frac >= 1.0:
|
||||||
self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072)
|
self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072)
|
||||||
return self.bounds[ax]
|
return self.bounds[ax]
|
||||||
@ -658,11 +667,11 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
if ymn is None or ymx is None:
|
if ymn is None or ymx is None:
|
||||||
ymn = 0
|
ymn = 0
|
||||||
ymx = 0
|
ymx = 0
|
||||||
|
|
||||||
px = py = 0.0
|
px = py = 0.0
|
||||||
pxPad = self.pixelPadding()
|
pxPad = self.pixelPadding()
|
||||||
if pxPad > 0:
|
if pxPad > 0:
|
||||||
# determine length of pixel in local x, y directions
|
# determine length of pixel in local x, y directions
|
||||||
px, py = self.pixelVectors()
|
px, py = self.pixelVectors()
|
||||||
try:
|
try:
|
||||||
px = 0 if px is None else px.length()
|
px = 0 if px is None else px.length()
|
||||||
@ -672,7 +681,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
py = 0 if py is None else py.length()
|
py = 0 if py is None else py.length()
|
||||||
except OverflowError:
|
except OverflowError:
|
||||||
py = 0
|
py = 0
|
||||||
|
|
||||||
# return bounds expanded by pixel size
|
# return bounds expanded by pixel size
|
||||||
px *= pxPad
|
px *= pxPad
|
||||||
py *= pxPad
|
py *= pxPad
|
||||||
@ -690,7 +699,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
|
|
||||||
|
|
||||||
def mapPointsToDevice(self, pts):
|
def mapPointsToDevice(self, pts):
|
||||||
# Map point locations to device
|
# Map point locations to device
|
||||||
tr = self.deviceTransform()
|
tr = self.deviceTransform()
|
||||||
if tr is None:
|
if tr is None:
|
||||||
return None
|
return None
|
||||||
@ -701,7 +710,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
pts = fn.transformCoordinates(tr, pts)
|
pts = fn.transformCoordinates(tr, pts)
|
||||||
pts -= self.data['width']
|
pts -= self.data['width']
|
||||||
pts = np.clip(pts, -2**30, 2**30) ## prevent Qt segmentation fault.
|
pts = np.clip(pts, -2**30, 2**30) ## prevent Qt segmentation fault.
|
||||||
|
|
||||||
return pts
|
return pts
|
||||||
|
|
||||||
def getViewMask(self, pts):
|
def getViewMask(self, pts):
|
||||||
@ -715,50 +724,50 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
mask = ((pts[0] + w > viewBounds.left()) &
|
mask = ((pts[0] + w > viewBounds.left()) &
|
||||||
(pts[0] - w < viewBounds.right()) &
|
(pts[0] - w < viewBounds.right()) &
|
||||||
(pts[1] + w > viewBounds.top()) &
|
(pts[1] + w > viewBounds.top()) &
|
||||||
(pts[1] - w < viewBounds.bottom())) ## remove out of view points
|
(pts[1] - w < viewBounds.bottom())) ## remove out of view points
|
||||||
return mask
|
return mask
|
||||||
|
|
||||||
|
|
||||||
@debug.warnOnException ## raising an exception here causes crash
|
@debug.warnOnException ## raising an exception here causes crash
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
|
|
||||||
#p.setPen(fn.mkPen('r'))
|
#p.setPen(fn.mkPen('r'))
|
||||||
#p.drawRect(self.boundingRect())
|
#p.drawRect(self.boundingRect())
|
||||||
|
|
||||||
if self._exportOpts is not False:
|
if self._exportOpts is not False:
|
||||||
aa = self._exportOpts.get('antialias', True)
|
aa = self._exportOpts.get('antialias', True)
|
||||||
scale = self._exportOpts.get('resolutionScale', 1.0) ## exporting to image; pixel resolution may have changed
|
scale = self._exportOpts.get('resolutionScale', 1.0) ## exporting to image; pixel resolution may have changed
|
||||||
else:
|
else:
|
||||||
aa = self.opts['antialias']
|
aa = self.opts['antialias']
|
||||||
scale = 1.0
|
scale = 1.0
|
||||||
|
|
||||||
if self.opts['pxMode'] is True:
|
if self.opts['pxMode'] is True:
|
||||||
p.resetTransform()
|
p.resetTransform()
|
||||||
|
|
||||||
# Map point coordinates to device
|
# Map point coordinates to device
|
||||||
pts = np.vstack([self.data['x'], self.data['y']])
|
pts = np.vstack([self.data['x'], self.data['y']])
|
||||||
pts = self.mapPointsToDevice(pts)
|
pts = self.mapPointsToDevice(pts)
|
||||||
if pts is None:
|
if pts is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Cull points that are outside view
|
# Cull points that are outside view
|
||||||
viewMask = self.getViewMask(pts)
|
viewMask = self.getViewMask(pts)
|
||||||
#pts = pts[:,mask]
|
#pts = pts[:,mask]
|
||||||
#data = self.data[mask]
|
#data = self.data[mask]
|
||||||
|
|
||||||
if self.opts['useCache'] and self._exportOpts is False:
|
if self.opts['useCache'] and self._exportOpts is False:
|
||||||
# Draw symbols from pre-rendered atlas
|
# Draw symbols from pre-rendered atlas
|
||||||
atlas = self.fragmentAtlas.getAtlas()
|
atlas = self.fragmentAtlas.getAtlas()
|
||||||
|
|
||||||
# Update targetRects if necessary
|
# Update targetRects if necessary
|
||||||
updateMask = viewMask & np.equal(self.data['targetRect'], None)
|
updateMask = viewMask & np.equal(self.data['targetRect'], None)
|
||||||
if np.any(updateMask):
|
if np.any(updateMask):
|
||||||
updatePts = pts[:,updateMask]
|
updatePts = pts[:,updateMask]
|
||||||
width = self.data[updateMask]['width']*2
|
width = self.data[updateMask]['width']*2
|
||||||
self.data['targetRect'][updateMask] = list(imap(QtCore.QRectF, updatePts[0,:], updatePts[1,:], width, width))
|
self.data['targetRect'][updateMask] = list(imap(QtCore.QRectF, updatePts[0,:], updatePts[1,:], width, width))
|
||||||
|
|
||||||
data = self.data[viewMask]
|
data = self.data[viewMask]
|
||||||
if USE_PYSIDE:
|
if USE_PYSIDE or USE_PYQT5:
|
||||||
list(imap(p.drawPixmap, data['targetRect'], repeat(atlas), data['sourceRect']))
|
list(imap(p.drawPixmap, data['targetRect'], repeat(atlas), data['sourceRect']))
|
||||||
else:
|
else:
|
||||||
p.drawPixmapFragments(data['targetRect'].tolist(), data['sourceRect'].tolist(), atlas)
|
p.drawPixmapFragments(data['targetRect'].tolist(), data['sourceRect'].tolist(), atlas)
|
||||||
@ -784,16 +793,16 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
p2.translate(rec['x'], rec['y'])
|
p2.translate(rec['x'], rec['y'])
|
||||||
drawSymbol(p2, *self.getSpotOpts(rec, scale))
|
drawSymbol(p2, *self.getSpotOpts(rec, scale))
|
||||||
p2.end()
|
p2.end()
|
||||||
|
|
||||||
p.setRenderHint(p.Antialiasing, aa)
|
p.setRenderHint(p.Antialiasing, aa)
|
||||||
self.picture.play(p)
|
self.picture.play(p)
|
||||||
|
|
||||||
def points(self):
|
def points(self):
|
||||||
for rec in self.data:
|
for rec in self.data:
|
||||||
if rec['item'] is None:
|
if rec['item'] is None:
|
||||||
rec['item'] = SpotItem(rec, self)
|
rec['item'] = SpotItem(rec, self)
|
||||||
return self.data['item']
|
return self.data['item']
|
||||||
|
|
||||||
def pointsAt(self, pos):
|
def pointsAt(self, pos):
|
||||||
x = pos.x()
|
x = pos.x()
|
||||||
y = pos.y()
|
y = pos.y()
|
||||||
@ -815,9 +824,8 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
#else:
|
#else:
|
||||||
#print "No hit:", (x, y), (sx, sy)
|
#print "No hit:", (x, y), (sx, sy)
|
||||||
#print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y)
|
#print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y)
|
||||||
#pts.sort(lambda a,b: cmp(b.zValue(), a.zValue()))
|
|
||||||
return pts[::-1]
|
return pts[::-1]
|
||||||
|
|
||||||
|
|
||||||
def mouseClickEvent(self, ev):
|
def mouseClickEvent(self, ev):
|
||||||
if ev.button() == QtCore.Qt.LeftButton:
|
if ev.button() == QtCore.Qt.LeftButton:
|
||||||
@ -836,7 +844,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
class SpotItem(object):
|
class SpotItem(object):
|
||||||
"""
|
"""
|
||||||
Class referring to individual spots in a scatter plot.
|
Class referring to individual spots in a scatter plot.
|
||||||
These can be retrieved by calling ScatterPlotItem.points() or
|
These can be retrieved by calling ScatterPlotItem.points() or
|
||||||
by connecting to the ScatterPlotItem's click signals.
|
by connecting to the ScatterPlotItem's click signals.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -847,34 +855,34 @@ class SpotItem(object):
|
|||||||
#self.setParentItem(plot)
|
#self.setParentItem(plot)
|
||||||
#self.setPos(QtCore.QPointF(data['x'], data['y']))
|
#self.setPos(QtCore.QPointF(data['x'], data['y']))
|
||||||
#self.updateItem()
|
#self.updateItem()
|
||||||
|
|
||||||
def data(self):
|
def data(self):
|
||||||
"""Return the user data associated with this spot."""
|
"""Return the user data associated with this spot."""
|
||||||
return self._data['data']
|
return self._data['data']
|
||||||
|
|
||||||
def size(self):
|
def size(self):
|
||||||
"""Return the size of this spot.
|
"""Return the size of this spot.
|
||||||
If the spot has no explicit size set, then return the ScatterPlotItem's default size instead."""
|
If the spot has no explicit size set, then return the ScatterPlotItem's default size instead."""
|
||||||
if self._data['size'] == -1:
|
if self._data['size'] == -1:
|
||||||
return self._plot.opts['size']
|
return self._plot.opts['size']
|
||||||
else:
|
else:
|
||||||
return self._data['size']
|
return self._data['size']
|
||||||
|
|
||||||
def pos(self):
|
def pos(self):
|
||||||
return Point(self._data['x'], self._data['y'])
|
return Point(self._data['x'], self._data['y'])
|
||||||
|
|
||||||
def viewPos(self):
|
def viewPos(self):
|
||||||
return self._plot.mapToView(self.pos())
|
return self._plot.mapToView(self.pos())
|
||||||
|
|
||||||
def setSize(self, size):
|
def setSize(self, size):
|
||||||
"""Set the size of this spot.
|
"""Set the size of this spot.
|
||||||
If the size is set to -1, then the ScatterPlotItem's default size
|
If the size is set to -1, then the ScatterPlotItem's default size
|
||||||
will be used instead."""
|
will be used instead."""
|
||||||
self._data['size'] = size
|
self._data['size'] = size
|
||||||
self.updateItem()
|
self.updateItem()
|
||||||
|
|
||||||
def symbol(self):
|
def symbol(self):
|
||||||
"""Return the symbol of this spot.
|
"""Return the symbol of this spot.
|
||||||
If the spot has no explicit symbol set, then return the ScatterPlotItem's default symbol instead.
|
If the spot has no explicit symbol set, then return the ScatterPlotItem's default symbol instead.
|
||||||
"""
|
"""
|
||||||
symbol = self._data['symbol']
|
symbol = self._data['symbol']
|
||||||
@ -886,7 +894,7 @@ class SpotItem(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return symbol
|
return symbol
|
||||||
|
|
||||||
def setSymbol(self, symbol):
|
def setSymbol(self, symbol):
|
||||||
"""Set the symbol for this spot.
|
"""Set the symbol for this spot.
|
||||||
If the symbol is set to '', then the ScatterPlotItem's default symbol will be used instead."""
|
If the symbol is set to '', then the ScatterPlotItem's default symbol will be used instead."""
|
||||||
@ -898,35 +906,35 @@ class SpotItem(object):
|
|||||||
if pen is None:
|
if pen is None:
|
||||||
pen = self._plot.opts['pen']
|
pen = self._plot.opts['pen']
|
||||||
return fn.mkPen(pen)
|
return fn.mkPen(pen)
|
||||||
|
|
||||||
def setPen(self, *args, **kargs):
|
def setPen(self, *args, **kargs):
|
||||||
"""Set the outline pen for this spot"""
|
"""Set the outline pen for this spot"""
|
||||||
pen = fn.mkPen(*args, **kargs)
|
pen = fn.mkPen(*args, **kargs)
|
||||||
self._data['pen'] = pen
|
self._data['pen'] = pen
|
||||||
self.updateItem()
|
self.updateItem()
|
||||||
|
|
||||||
def resetPen(self):
|
def resetPen(self):
|
||||||
"""Remove the pen set for this spot; the scatter plot's default pen will be used instead."""
|
"""Remove the pen set for this spot; the scatter plot's default pen will be used instead."""
|
||||||
self._data['pen'] = None ## Note this is NOT the same as calling setPen(None)
|
self._data['pen'] = None ## Note this is NOT the same as calling setPen(None)
|
||||||
self.updateItem()
|
self.updateItem()
|
||||||
|
|
||||||
def brush(self):
|
def brush(self):
|
||||||
brush = self._data['brush']
|
brush = self._data['brush']
|
||||||
if brush is None:
|
if brush is None:
|
||||||
brush = self._plot.opts['brush']
|
brush = self._plot.opts['brush']
|
||||||
return fn.mkBrush(brush)
|
return fn.mkBrush(brush)
|
||||||
|
|
||||||
def setBrush(self, *args, **kargs):
|
def setBrush(self, *args, **kargs):
|
||||||
"""Set the fill brush for this spot"""
|
"""Set the fill brush for this spot"""
|
||||||
brush = fn.mkBrush(*args, **kargs)
|
brush = fn.mkBrush(*args, **kargs)
|
||||||
self._data['brush'] = brush
|
self._data['brush'] = brush
|
||||||
self.updateItem()
|
self.updateItem()
|
||||||
|
|
||||||
def resetBrush(self):
|
def resetBrush(self):
|
||||||
"""Remove the brush set for this spot; the scatter plot's default brush will be used instead."""
|
"""Remove the brush set for this spot; the scatter plot's default brush will be used instead."""
|
||||||
self._data['brush'] = None ## Note this is NOT the same as calling setBrush(None)
|
self._data['brush'] = None ## Note this is NOT the same as calling setBrush(None)
|
||||||
self.updateItem()
|
self.updateItem()
|
||||||
|
|
||||||
def setData(self, data):
|
def setData(self, data):
|
||||||
"""Set the user-data associated with this spot"""
|
"""Set the user-data associated with this spot"""
|
||||||
self._data['data'] = data
|
self._data['data'] = data
|
||||||
@ -941,14 +949,14 @@ class SpotItem(object):
|
|||||||
#QtGui.QGraphicsPixmapItem.__init__(self)
|
#QtGui.QGraphicsPixmapItem.__init__(self)
|
||||||
#self.setFlags(self.flags() | self.ItemIgnoresTransformations)
|
#self.setFlags(self.flags() | self.ItemIgnoresTransformations)
|
||||||
#SpotItem.__init__(self, data, plot)
|
#SpotItem.__init__(self, data, plot)
|
||||||
|
|
||||||
#def setPixmap(self, pixmap):
|
#def setPixmap(self, pixmap):
|
||||||
#QtGui.QGraphicsPixmapItem.setPixmap(self, pixmap)
|
#QtGui.QGraphicsPixmapItem.setPixmap(self, pixmap)
|
||||||
#self.setOffset(-pixmap.width()/2.+0.5, -pixmap.height()/2.)
|
#self.setOffset(-pixmap.width()/2.+0.5, -pixmap.height()/2.)
|
||||||
|
|
||||||
#def updateItem(self):
|
#def updateItem(self):
|
||||||
#symbolOpts = (self._data['pen'], self._data['brush'], self._data['size'], self._data['symbol'])
|
#symbolOpts = (self._data['pen'], self._data['brush'], self._data['size'], self._data['symbol'])
|
||||||
|
|
||||||
### If all symbol options are default, use default pixmap
|
### If all symbol options are default, use default pixmap
|
||||||
#if symbolOpts == (None, None, -1, ''):
|
#if symbolOpts == (None, None, -1, ''):
|
||||||
#pixmap = self._plot.defaultSpotPixmap()
|
#pixmap = self._plot.defaultSpotPixmap()
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user