Compare commits
153 Commits
pyqtgraph-
...
master
Author | SHA1 | Date |
---|---|---|
Kyle Sunden | 6fa4a0a3eb | |
Kyle Sunden | 0f3804e32c | |
Ogi Moore | af8c766de3 | |
Ogi Moore | 58325ee91b | |
Ogi Moore | 936108ec60 | |
Ogi Moore | 9b67e51230 | |
Ogi Moore | db74522003 | |
Kyle Sunden | 9ae38e6d21 | |
Kyle Sunden | 34c6981b0f | |
Ogi Moore | b8ca314597 | |
Kyle Sunden | ae484ba411 | |
Ogi Moore | fd6985565c | |
KIU Shueng Chuan | 2da769d6a5 | |
Ogi Moore | fb809e5260 | |
KIU Shueng Chuan | 4aeb0ce1dd | |
Martin Chase | a6bb2c6edd | |
Ogi Moore | e8b77a11e5 | |
Ogi Moore | e04ecdf554 | |
Nils Nemitz | 0cc3580687 | |
Ogi Moore | bc542ae1c4 | |
Ogi Moore | 5917e5e666 | |
Ogi Moore | 95c302fd0d | |
KIU Shueng Chuan | 4b59734629 | |
ntjess | 8d3e6cbd22 | |
Ogi Moore | e9d3b6ddd2 | |
Dane Owens | 1132c138a4 | |
Ogi Moore | b075035b0d | |
Jan Kotanski | 4334cd14de | |
Ogi Moore | 126b7e0925 | |
magnium | 6f4b6b1b88 | |
ntjess | babd037cf7 | |
Petras Jokubauskas | 745b04baa5 | |
Ogi Moore | 2a66460afc | |
Ogi Moore | 446a2c324c | |
Ogi Moore | d54da71138 | |
Ogi Moore | 13ab4de209 | |
Ogi Moore | c3dba791d1 | |
KIU Shueng Chuan | b5475cad6b | |
KIU Shueng Chuan | 6763b14fb9 | |
Ogi Moore | 98d7bcf560 | |
KIU Shueng Chuan | 6f1ea29fe4 | |
Ogi Moore | 175f84ecbc | |
KIU Shueng Chuan | 11b4a1bb47 | |
Ogi Moore | e752336b55 | |
KIU Shueng Chuan | 0cd926ff8e | |
KIU Shueng Chuan | e541c9ba1e | |
KIU Shueng Chuan | 44e32f00c7 | |
KIU Shueng Chuan | 679c1002c1 | |
KIU Shueng Chuan | 7e31a5e4ee | |
KIU Shueng Chuan | fa826bd4cb | |
Ogi Moore | 0d8eba36a6 | |
Ogi Moore | 690a416d5d | |
Ogi Moore | a537bc2b23 | |
KIU Shueng Chuan | 287ec9fbea | |
Nathan Jessurun | 503507ba50 | |
Nathan Jessurun | 3d15ca1038 | |
Nathan Jessurun | 6f44d27f2d | |
Ogi Moore | f16125c378 | |
Tim Gates | a2078f8a87 | |
Ogi Moore | 239e15a6f8 | |
KIU Shueng Chuan | 178e693a4d | |
KIU Shueng Chuan | 371facb17f | |
KIU Shueng Chuan | 482b92d700 | |
KIU Shueng Chuan | efa662415e | |
ntjess | 4bf1866c2a | |
Ogi Moore | fb2e684f45 | |
ntjess | e18d1fb1f2 | |
Ogi Moore | d0961cc320 | |
njessurun | c0eb3267d2 | |
Ogi Moore | b0bcec1ff2 | |
njessurun | ef99d3fbf6 | |
Nathan Jessurun | 84b491fb9b | |
Kyle Sunden | a472f8c5de | |
Ogi Moore | 1ddbfc8321 | |
KIU Shueng Chuan | aebc66dcaa | |
Kyle Sunden | 6a59b7e5b5 | |
Ogi Moore | 2d90e54441 | |
KIU Shueng Chuan | 04673ac98b | |
KIU Shueng Chuan | f64290be9e | |
KIU Shueng Chuan | 9355ecf469 | |
KIU Shueng Chuan | 75654b8495 | |
KIU Shueng Chuan | 9913f7c1e7 | |
Ogi Moore | 7aa5d0cbf4 | |
KIU Shueng Chuan | 643dd87800 | |
Ogi Moore | fe10b5e8dc | |
Ogi Moore | 0cfc9cd440 | |
Ogi Moore | d3755520d0 | |
KIU Shueng Chuan | 0afd8adcd8 | |
Martin Schulz | 96cccd96ec | |
Nils Nemitz | 5084d9b537 | |
Martin Chase | eb8965c2f4 | |
KIU Shueng Chuan | 1a34c8857f | |
Nils Nemitz | 7009084e4c | |
ntjess | 81823768c0 | |
Nils Nemitz | 8f96c78715 | |
Ogi Moore | d396d33799 | |
Martin Chase | 1d40d50b89 | |
KIU Shueng Chuan | 1184e3fcce | |
KIU Shueng Chuan | 394b0dd75c | |
KIU Shueng Chuan | 8a6640c419 | |
Ogi Moore | 66ec0996f4 | |
Ogi Moore | 699ed578f4 | |
Kenneth Lyons | 0ae6b753fc | |
Ogi Moore | 4e293dd17d | |
Nils Nemitz | 8b4db67ae9 | |
KIU Shueng Chuan | d9726dbcc1 | |
Ogi Moore | d5990bf32c | |
Dennis Goeries | cdb427ff2f | |
Ogi Moore | ed66eef203 | |
Ogi Moore | 58aac09387 | |
Kenneth Lyons | ba7129a719 | |
KIU Shueng Chuan | cbc9b4d310 | |
Ogi Moore | ddab4180e9 | |
KIU Shueng Chuan | 6f49ede5c1 | |
Ogi Moore | 76f3612245 | |
Artturin | 2de5cd78da | |
Ogi Moore | ddf73c2ecf | |
KIU Shueng Chuan | fa77dae941 | |
KIU Shueng Chuan | 5283eeb71b | |
KIU Shueng Chuan | aca627ac8c | |
KIU Shueng Chuan | 31e10fdc1d | |
KIU Shueng Chuan | f85a1015ad | |
KIU Shueng Chuan | e10dbfd9e1 | |
KIU Shueng Chuan | 6ca81fdddb | |
KIU Shueng Chuan | 7a17cda956 | |
KIU Shueng Chuan | dfd5b5dc1b | |
KIU Shueng Chuan | 5b2674c9d5 | |
KIU Shueng Chuan | f43f795950 | |
KIU Shueng Chuan | 9bf6c01f58 | |
KIU Shueng Chuan | bc52a2afe0 | |
KIU Shueng Chuan | fab505a431 | |
KIU Shueng Chuan | 1814ff535d | |
KIU Shueng Chuan | f698ccc06e | |
KIU Shueng Chuan | ce4c6d95ed | |
KIU Shueng Chuan | b5fc3d2a7e | |
KIU Shueng Chuan | 1b00d3448a | |
KIU Shueng Chuan | 5d7dd101f2 | |
KIU Shueng Chuan | 025ca08574 | |
KIU Shueng Chuan | ee9b1565bd | |
KIU Shueng Chuan | e158034c07 | |
KIU Shueng Chuan | 31f9d0024a | |
Martin Chase | bcb629495c | |
Ogi Moore | d28e1c8075 | |
Ogi Moore | b79171dc3a | |
Ogi Moore | 910142aa6f | |
KIU Shueng Chuan | 7442ab1e52 | |
Ogi Moore | b9e079b10c | |
Scott Talbert | db8180d88e | |
Ogi Moore | 1ce9da36d3 | |
Kenneth Lyons | bb94290d8e | |
KIU Shueng Chuan | e17428b018 | |
Ogi Moore | 6b4385ce0d | |
Jennifer Manriquez | 0c074ea005 |
|
@ -16,27 +16,27 @@ jobs:
|
|||
- python-version: "3.7"
|
||||
qt-lib: "pyqt"
|
||||
qt-version: "PyQt5~=5.12.0"
|
||||
numpy-version: "~=1.17.0"
|
||||
numpy-version: "~=1.18.0"
|
||||
- python-version: "3.7"
|
||||
qt-lib: "pyside"
|
||||
qt-version: "PySide2~=5.12.0"
|
||||
numpy-version: "~=1.17.0"
|
||||
numpy-version: "~=1.18.0"
|
||||
- python-version: "3.8"
|
||||
qt-lib: "pyqt"
|
||||
qt-version: "PyQt5~=5.15.0"
|
||||
numpy-version: "~=1.20.0"
|
||||
numpy-version: "~=1.21.0"
|
||||
- python-version: "3.8"
|
||||
qt-lib: "pyside"
|
||||
qt-version: "PySide2~=5.15.0"
|
||||
numpy-version: "~=1.20.0"
|
||||
numpy-version: "~=1.21.0"
|
||||
- python-version: "3.9"
|
||||
qt-lib: "pyqt"
|
||||
qt-version: "PyQt6"
|
||||
numpy-version: "~=1.20.0"
|
||||
numpy-version: "~=1.21.0"
|
||||
- python-version: "3.9"
|
||||
qt-lib: "pyside"
|
||||
qt-version: "PySide6"
|
||||
numpy-version: "~=1.20.0"
|
||||
numpy-version: "~=1.21.0"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
@ -101,8 +101,7 @@ jobs:
|
|||
- name: Run Tests
|
||||
run: |
|
||||
mkdir $SCREENSHOT_DIR
|
||||
pytest tests -v \
|
||||
--junitxml pytest.xml
|
||||
pytest tests -v
|
||||
pytest examples -v
|
||||
shell: bash
|
||||
- name: Upload Screenshots
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# This workflows will upload a Python Package using setup.py/twine when a release is created
|
||||
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
||||
|
||||
name: Upload Python Package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install wheel twine setuptools
|
||||
- name: Build and publish
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
# The PYPI_PASSWORD must be a pypi token with the "pypi-" prefix with sufficient permissions to upload this package
|
||||
# https://pypi.org/help/#apitoken
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
run: |
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload dist/*
|
75
CHANGELOG
75
CHANGELOG
|
@ -1,3 +1,78 @@
|
|||
pyqtgraph-0.12.3
|
||||
|
||||
Highlights:
|
||||
- PlotCurveItem render speed is now substantially faster
|
||||
- #1868/#1873 Example app now has filter text input
|
||||
- #1910 PlotSpeedTest now has parameter tree control panel
|
||||
|
||||
New Features:
|
||||
- #1844 More parameter item types (File, Calendar, ProgressBar, Font, Pen, Slider)
|
||||
- #1865 Matplotlib colormaps viridis, plasma, magma and inferno are now included in pyqtgraph
|
||||
- #1911 Extend Colormap with HSL cycles and subset generation
|
||||
- #1932 Make anti-aliasing optional for paintGL in PlotCurveItem
|
||||
- #1944 Expand use of QColor functions/methods, including setNamedColor
|
||||
- #1952 Add checklist parameter item
|
||||
- #1998 ThreadTrace can now save to a file
|
||||
|
||||
Performance Enhancement:
|
||||
- #1927 Reduce ColorMap inefficiencies
|
||||
- #1956 use QByteArray as backing store in arrayToQPath
|
||||
- #1965 perform arrayToQPath in chunks
|
||||
|
||||
Bug Fixes:
|
||||
- #1845 Fix zoom behavior with showGrid by separating mouse events stolen by AxisItem
|
||||
- #1860 RemoteGraphicsView and RemoteSpeedTest now work under windows venv environments
|
||||
- #1865 Fixed matplotlib colormap importer code
|
||||
- #1869 Fix ColorBarItem tick position on export
|
||||
- #1871 Allow adding items to GLViewWidget before showing plot
|
||||
- #1875 Fix calls in mouse methods in GLViewWidgets due to missing event.localPos() in PyQt6
|
||||
- #1876 Fix for improper placement of ROI handle positions in some cases
|
||||
- #1889/#2003 Fix call to drawText in GLTextItem and GLGradientLegendItem on Python 3.10
|
||||
- #1897/#1902 Re-enable "experimental" feature with fix for PlotCurveItem with OpenGL on Windows
|
||||
- #1907 Fix GLVolumeItem example for arm64 platforms
|
||||
- #1909 Check if AxisItem.label is None before and exit early in resizeEvent
|
||||
- #1920 arrayToQPath can handle empty paths
|
||||
- #1936 QPolygonF creation can now handle empty arrays
|
||||
- #1968 Fix output of clip_array in colormap.modulatedBarData not being assigned
|
||||
- #1973 Fix PlotItem.updateDecimate unhiding intentionally hidden curves
|
||||
- #1974 Fix ImageView levelMode with levelMode == 'rgba'
|
||||
- #1987 Fix HistogramLUTItem itemChanged with use of autoLevel
|
||||
- #2009 Fix ROI curves hidding in ImageView
|
||||
|
||||
API/Behavior Changes:
|
||||
- #1992 Reverted to traditional log10 mode for PlotDataItem
|
||||
- #1840 Allow border=False in GraphicsLayout
|
||||
- #1846 Reduced pollution to pg.namespace
|
||||
- #1853 ColorMap.getColors and getStops behavior changes
|
||||
- #1864 Draw GradientLegend in ViewBox coordinates
|
||||
- #1885 Raise TypeError instead of general Exception if functions.eq is unable to determine equality
|
||||
- #1903 Cleanup GLViewWidget
|
||||
- #1908 More readable parameters for ColorBarItem
|
||||
- #1914 Emit deprecation warning for use of pyqtgraph.ptime
|
||||
- #1928 Restore previous signature of TargetItem.setPos
|
||||
- #1940 fix log mode by reverting to previous formulation
|
||||
- #1954 Deprecate use of values opt for list parameter type
|
||||
- #1995 ColorButton now takes optional padding argument instead of hardcoded value of 6
|
||||
|
||||
Other:
|
||||
- #1862/#1901 MetaArray now under deprecation warning, to be removed in a future version
|
||||
- #1892 Add GLPainterItem Example
|
||||
- #1844 Debugged elusive intermitted CI segfault
|
||||
- #1870/#1891 Updated README.md
|
||||
- #1895 Update CONTRIBUTING.md
|
||||
- #1913 Bump sphinx and theme versions
|
||||
- #1919 Re-organize paramtypes
|
||||
- #1935 Remove some unused imports
|
||||
- #1939 Remove usage of python2_3.py
|
||||
- #1941 Remove str casting of QTextEdit.toPlainText output
|
||||
- #1942 Add EOF newline to files missing it
|
||||
- #1943 Remove python2 code paths
|
||||
- #1951 Fix typos in docs
|
||||
- #1957 Bump minimum numpy version to 1.18
|
||||
- #1968 Fix ImageView calling deprecated QGraphicsItem.scale()
|
||||
- #1985 delegate float LUTs to makeARGB with warning
|
||||
- #2014 Replace couple absolute imports with relative imports
|
||||
|
||||
pyqtgraph-0.12.2
|
||||
|
||||
Highlights
|
||||
|
|
|
@ -4,77 +4,86 @@ Contributions to pyqtgraph are welcome! Be kind and respectful! See [our Code of
|
|||
|
||||
Please use the following guidelines when preparing changes:
|
||||
|
||||
## Submitting Code Changes
|
||||
## Development Environment Creation
|
||||
|
||||
* The preferred method for submitting changes is by github pull request against the "master" branch.
|
||||
* Pull requests should include only a focused and related set of changes. Mixed features and unrelated changes may be rejected.
|
||||
* For major changes, it is recommended to discuss your plans on the mailing list or in a github issue before putting in too much effort.
|
||||
* The following deprecations are being considered by the maintainers
|
||||
* `pyqtgraph.opengl` may be deprecated and replaced with `VisPy` functionality
|
||||
* After v0.11, pyqtgraph will adopt [NEP-29](https://numpy.org/neps/nep-0029-deprecation_policy.html) which will effectively mean that python2 support will be deprecated
|
||||
* Qt4 will be deprecated shortly, as well as Qt5<5.9 (and potentially <5.12)
|
||||
First thing to do is fork the repository, and clone your own fork to your local computer.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<username>/pyqtgraph.git
|
||||
cd pyqtgraph
|
||||
```
|
||||
|
||||
While there is nothing preventing users from using `conda` environments, as a general principle, we recommend using the `venv` module for creating an otherwise empty virtual environment. Furthermore, at this time, WSL is not supported (it can likely be made to work, but you're on your own if you go down this route).
|
||||
|
||||
```bash
|
||||
python3.9 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
# on windows this would be .venv/Scripts/activate
|
||||
python -m pip install --upgrade wheel setuptools pip
|
||||
python -m pip install numpy scipy pyside6 -e .
|
||||
```
|
||||
|
||||
Before making changes to the code-base, create a different branch with a name that should be unique (this makes it easier for maintainers to examine the proposed changes locally).
|
||||
|
||||
```bash
|
||||
git switch -c my-new-feature
|
||||
```
|
||||
|
||||
When you're ready to submit the pull request, do so via the github, and the target of the pull request should be the `master` branch in the pyqtgraph repo.
|
||||
|
||||
Pull requests should include only a focused and related set of changes. Mixed features and unrelated changes may be rejected.
|
||||
|
||||
For major changes, it is recommended to discuss your plans on the mailing list or in a github issue/discussion before putting in too much effort.
|
||||
|
||||
PyQtGraph has adopted [NEP-29](https://numpy.org/neps/nep-0029-deprecation_policy.html) which governs the timeline for phasing out support for numpy and python versions.
|
||||
|
||||
## Documentation
|
||||
|
||||
* Writing proper documentation and unit tests is highly encouraged. PyQtGraph uses pytest style testing, so tests should usually be included in a tests/ directory adjacent to the relevant code.
|
||||
* Documentation is generated with sphinx; please check that docstring changes compile correctly
|
||||
* Writing proper documentation and unit tests is highly encouraged. PyQtGraph uses [`pytest`](https://docs.pytest.org/) for testing.
|
||||
* Documentation is generated with sphinx, and usage of [numpy-docstyle](https://numpydoc.readthedocs.io/en/latest/format.html) is encouraged (many places in the library do not use this docstring style at present, it's a gradual process to migrate).
|
||||
* The docs built for this PR can be previewed by clicking on the "Details" link for the read-the-docs entry in the checks section of the PR conversation page.
|
||||
|
||||
## Style guidelines
|
||||
|
||||
### Rules
|
||||
### Formatting ~~Rules~~ Suggestions
|
||||
|
||||
* PyQtGraph prefers PEP8 for most style issues, but this is not enforced rigorously as long as the code is clean and readable.
|
||||
* Use `python setup.py style` to see whether your code follows the mandatory style guidelines checked by flake8.
|
||||
* Exception 1: All variable names should use camelCase rather than underscore_separation. This is done for consistency with Qt
|
||||
* Exception 2: Function docstrings use ReStructuredText tables for describing arguments:
|
||||
|
||||
```text
|
||||
============== ========================================================
|
||||
**Arguments:**
|
||||
argName1 (type) Description of argument
|
||||
argName2 (type) Description of argument. Longer descriptions must
|
||||
be wrapped within the column guidelines defined by the
|
||||
"====" header and footer.
|
||||
============== ========================================================
|
||||
```
|
||||
|
||||
QObject subclasses that implement new signals should also describe
|
||||
these in a similar table.
|
||||
* Variable and Function/Methods that are intended to be part of the public API should be camelCase.
|
||||
* "Private" methods/variables should have a leading underscore (`_`) before the name.
|
||||
|
||||
### Pre-Commit
|
||||
|
||||
PyQtGraph developers are highly encouraged to (but not required) to use [`pre-commit`](https://pre-commit.com/). `pre-commit` does a number of checks when attempting to commit the code to ensure it conforms to various standards, such as `flake8`, utf-8 encoding pragma, line-ending fixers, and so on. If any of the checks fail, the commit will be rejected, and you will have the opportunity to make the necessary fixes before adding and committing a file again. This ensures that every commit made conforms to (most) of the styling standards that the library enforces; and you will most likely pass the code style checks by the CI.
|
||||
PyQtGraph developers are highly encouraged to (but not required) to use [`pre-commit`](https://pre-commit.com/). `pre-commit` does a number of checks when attempting to commit the code to being committed, such as ensuring no large files are accidentally added, address mixed-line-endings types and so on. Check the [pre-commit documentation](https://pre-commit.com) on how to setup.
|
||||
|
||||
To make use of `pre-commit`, have it available in your `$PATH` and run `pre-commit install` from the root directory of PyQtGraph.
|
||||
## Testing
|
||||
|
||||
## Testing Setting up a test environment
|
||||
|
||||
### Dependencies
|
||||
### Basic Setup
|
||||
|
||||
* tox
|
||||
* tox-conda
|
||||
* pytest
|
||||
* pytest-cov
|
||||
* pytest-xdist
|
||||
* Optional: pytest-xvfb
|
||||
* Optional: pytest-xvfb (used on linux with headless displays)
|
||||
|
||||
If you have `pytest<5` (used in python2), you may also want to install `pytest-faulthandler==1.6` plugin to output extra debugging information in case of test failures. This isn't necessary with `pytest>=5`
|
||||
To run the test suite, after installing the above dependencies run
|
||||
|
||||
```bash
|
||||
python -m pytest examples tests
|
||||
```
|
||||
|
||||
### Tox
|
||||
|
||||
As PyQtGraph supports a wide array of Qt-bindings, and python versions, we make use of `tox` to test against most of the configurations in our test matrix. As some of the qt-bindings are only installable via `conda`, `conda` needs to be in your `PATH`, and we utilize the `tox-conda` plugin.
|
||||
As PyQtGraph supports a wide array of Qt-bindings, and python versions, we make use of `tox` to test against as many supported configurations as feasible. With tox installed, simply run `tox` and it will run through all the configurations. This should be done if there is uncertainty regarding changes working on specific combinations of PyQt bindings and/or python versions.
|
||||
|
||||
* 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, un `pytest --cov -n 4` to run the test suite with coverage on 4 cores.
|
||||
### Continuous Integration
|
||||
|
||||
### Continous Integration
|
||||
|
||||
For our Continuous Integration, we utilize Azure Pipelines. Tested configurations are visible on [README](README.md). More information on coverage and test failures can be found on the respective tabs of the [build results page](https://dev.azure.com/pyqtgraph/pyqtgraph/_build?definitionId=1)
|
||||
For our Continuous Integration, we utilize Github Actions. Tested configurations are visible on [README](README.md).
|
||||
|
||||
### Benchmarks
|
||||
|
||||
( *Still under development* ) To ensure this library is performant, we use [Air Speed Velocity (asv)](https://asv.readthedocs.io/en/stable/) to run benchmarks. For developing on core functions and classes, be aware of any impact your changes have on their speed. To configure and run asv:
|
||||
```
|
||||
|
||||
```bash
|
||||
pip install asv
|
||||
python setup.py asv_config
|
||||
asv run
|
||||
|
|
25
README.md
25
README.md
|
@ -35,22 +35,13 @@ Currently this means:
|
|||
|
||||
* Python 3.7+
|
||||
* Qt 5.12-5.15, 6.1
|
||||
* Required
|
||||
* [PyQt5](https://www.riverbankcomputing.com/software/pyqt/), [PyQt6](https://www.riverbankcomputing.com/software/pyqt/), [PySide2](https://wiki.qt.io/Qt_for_Python) or [PySide6](https://wiki.qt.io/Qt_for_Python)
|
||||
* [`numpy`](https://github.com/numpy/numpy) 1.17+
|
||||
* Optional
|
||||
* `scipy` for image processing
|
||||
* `pyopengl` for 3D graphics
|
||||
* `pyopengl` on macOS Big Sur only works with python 3.9.1+
|
||||
* `hdf5` for large hdf5 binary format support
|
||||
* `colorcet` for supplemental colormaps
|
||||
* [`cupy`](https://docs.cupy.dev/en/stable/install.html) for CUDA-enhanced image processing
|
||||
* On Windows, CUDA toolkit must be >= 11.1
|
||||
* [`numba`] used to accelerate repeated image display for ImageItem
|
||||
* [PyQt5](https://www.riverbankcomputing.com/software/pyqt/),
|
||||
[PyQt6](https://www.riverbankcomputing.com/software/pyqt/),
|
||||
[PySide2](https://wiki.qt.io/Qt_for_Python), or
|
||||
[PySide6](https://wiki.qt.io/Qt_for_Python)
|
||||
* [`numpy`](https://github.com/numpy/numpy) 1.18+
|
||||
|
||||
|
||||
Optional added functionalities
|
||||
------------------------------
|
||||
### Optional added functionalities
|
||||
|
||||
Through 3rd part libraries, additional functionality may be added to PyQtGraph, see the table below for a summary.
|
||||
|
||||
|
@ -100,7 +91,7 @@ Support
|
|||
Installation Methods
|
||||
--------------------
|
||||
|
||||
* From PyPI:
|
||||
* From PyPI:
|
||||
* Last released version: `pip install pyqtgraph`
|
||||
* Latest development version: `pip install git+https://github.com/pyqtgraph/pyqtgraph@master`
|
||||
* From conda
|
||||
|
@ -115,7 +106,7 @@ Documentation
|
|||
|
||||
The official documentation lives at [pyqtgraph.readthedocs.io](https://pyqtgraph.readthedocs.io)
|
||||
|
||||
The easiest way to learn PyQtGraph is to browse through the examples; run `python -m pyqtgraph.examples` to launch the examples application.
|
||||
The easiest way to learn PyQtGraph is to browse through the examples; run `python -m pyqtgraph.examples` to launch the examples application.
|
||||
|
||||
Used By
|
||||
-------
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
|
||||
rng = np.random.default_rng(12345)
|
||||
|
||||
class _TimeSuite:
|
||||
params = ([10_000, 100_000, 1_000_000], ['all', 'finite', 'pairs', 'array'])
|
||||
|
||||
def setup(self, nelems, connect):
|
||||
self.xdata = np.arange(nelems, dtype=np.float64)
|
||||
self.ydata = rng.standard_normal(nelems, dtype=np.float64)
|
||||
if connect == 'array':
|
||||
self.connect_array = np.ones(nelems, dtype=bool)
|
||||
if self.have_nonfinite:
|
||||
self.ydata[::5000] = np.nan
|
||||
|
||||
def time_test(self, nelems, connect):
|
||||
if connect == 'array':
|
||||
connect = self.connect_array
|
||||
pg.arrayToQPath(self.xdata, self.ydata, connect=connect)
|
||||
|
||||
class TimeSuiteAllFinite(_TimeSuite):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.have_nonfinite = False
|
||||
|
||||
class TimeSuiteWithNonFinite(_TimeSuite):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.have_nonfinite = True
|
|
@ -1,5 +1,5 @@
|
|||
pyside2
|
||||
numpy
|
||||
pyopengl
|
||||
sphinx==3.4.3
|
||||
sphinx_rtd_theme==0.5.1
|
||||
sphinx==4.1.1
|
||||
sphinx_rtd_theme==0.5.2
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
GLGraphItem
|
||||
===========
|
||||
|
||||
.. autoclass:: pyqtgraph.opengl.GLGraphItem
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.opengl.GLGraphItem.__init__
|
|
@ -19,6 +19,7 @@ Contents:
|
|||
glviewwidget
|
||||
|
||||
glgriditem
|
||||
glgraphitem
|
||||
glsurfaceplotitem
|
||||
glvolumeitem
|
||||
glimageitem
|
||||
|
|
|
@ -34,12 +34,6 @@ Qt uses the classes QColor, QPen, and QBrush to determine how to draw lines and
|
|||
|
||||
.. autofunction:: pyqtgraph.colorCIELab
|
||||
|
||||
.. autofunction:: pyqtgraph.colorTuple
|
||||
|
||||
.. autofunction:: pyqtgraph.colorStr
|
||||
|
||||
.. autofunction:: pyqtgraph.glColor
|
||||
|
||||
.. autofunction:: pyqtgraph.colorDistance
|
||||
|
||||
|
||||
|
@ -108,3 +102,16 @@ Miscellaneous Functions
|
|||
.. autofunction:: pyqtgraph.systemInfo
|
||||
|
||||
.. autofunction:: pyqtgraph.exit
|
||||
|
||||
|
||||
Legacy Color Helper Functions
|
||||
-------------------------------
|
||||
|
||||
The following helper functions should no longer be used. The functionality that they implement is trivial and it is suggested that the user use the equivalent QColor methods directly.
|
||||
|
||||
|
||||
.. autofunction:: pyqtgraph.colorTuple
|
||||
|
||||
.. autofunction:: pyqtgraph.colorStr
|
||||
|
||||
.. autofunction:: pyqtgraph.glColor
|
||||
|
|
|
@ -101,7 +101,7 @@ Embedding PyQtGraph as a sub-package of a larger project
|
|||
|
||||
When writing applications or python packages that make use of pyqtgraph, it is most common to install pyqtgraph system-wide (or within a virtualenv) and simply call `import pyqtgraph` from within your application. The main benefit to this is that pyqtgraph is configured independently of your application and thus you (or your users) are free to install newer versions of pyqtgraph without changing anything in your application. This is standard practice when developing with python.
|
||||
|
||||
Occasionally, a specific program needs to be kept in working order for an extended amount of time after development has been completed. This is often the case for single-purpose scientific applications. If we want to ensure that the software will still work ten years later, then it is preferrable to tie it to a very specific version of pyqtgraph and *avoid* importing the system-installed version, which may be much newer and potentially incompatible. This is especially true when the application requires site-specific modifications to the pyqtgraph package.
|
||||
Occasionally, a specific program needs to be kept in working order for an extended amount of time after development has been completed. This is often the case for single-purpose scientific applications. If we want to ensure that the software will still work ten years later, then it is preferable to tie it to a very specific version of pyqtgraph and *avoid* importing the system-installed version, which may be much newer and potentially incompatible. This is especially true when the application requires site-specific modifications to the pyqtgraph package.
|
||||
|
||||
To support such a separate local installation, all internal import statements in pyqtgraph are relative. That means that pyqtgraph never refers to itself internally as 'pyqtgraph'. This allows the package to be renamed or used as a sub-package without any naming conflicts with other versions of pyqtgraph on the system.
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ Parameter
|
|||
|
||||
.. autofunction:: pyqtgraph.parametertree.registerParameterType
|
||||
|
||||
.. autofunction:: pyqtgraph.parametertree.registerParameterItemType
|
||||
|
||||
.. autoclass:: pyqtgraph.parametertree.Parameter
|
||||
:members:
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
..
|
||||
This file is auto-generated from pyqtgraph/tools/rebuildPtreeRst.py. Do not modify by hand! Instead, rerun the
|
||||
generation script with `python pyqtgraph/tools/rebuildPtreeRst.py`.
|
||||
|
||||
Built-in Parameter Types
|
||||
========================
|
||||
|
||||
|
@ -6,7 +10,25 @@ Built-in Parameter Types
|
|||
Parameters
|
||||
----------
|
||||
|
||||
.. autoclass:: SimpleParameter
|
||||
.. autoclass:: ActionParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: CalendarParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: ChecklistParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: ColorMapParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: ColorParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: FileParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: FontParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: GroupParameter
|
||||
|
@ -15,16 +37,46 @@ Parameters
|
|||
.. autoclass:: ListParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: TextParameter
|
||||
.. autoclass:: PenParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: ActionParameter
|
||||
.. autoclass:: ProgressBarParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: SimpleParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: SliderParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: TextParameter
|
||||
:members:
|
||||
|
||||
ParameterItems
|
||||
--------------
|
||||
|
||||
.. autoclass:: WidgetParameterItem
|
||||
.. autoclass:: ActionParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: BoolParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: CalendarParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: ChecklistParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: ColorMapParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: ColorParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: FileParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: FontParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: GroupParameterItem
|
||||
|
@ -33,8 +85,20 @@ ParameterItems
|
|||
.. autoclass:: ListParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: TextParameterItem
|
||||
.. autoclass:: NumericParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: ActionParameterItem
|
||||
.. autoclass:: PenParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: ProgressBarParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: SliderParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: StrParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: TextParameterItem
|
||||
:members:
|
||||
|
|
|
@ -16,7 +16,7 @@ To select a 2D region from an image, pyqtgraph uses the :class:`ROI <pyqtgraph.R
|
|||
|
||||
To automatically extract a region of image data using an ROI and an ImageItem, use :func:`ROI.getArrayRegion <pyqtgraph.ROI.getArrayRegion>`. ROI classes use the :func:`affineSlice <pyqtgraph.affineSlice>` function to perform this extraction.
|
||||
|
||||
ROI can also be used as a control for moving/rotating/scaling items in a scene similar to most vetctor graphics editing applications.
|
||||
ROI can also be used as a control for moving/rotating/scaling items in a scene similar to most vector graphics editing applications.
|
||||
|
||||
See the ROITypes example for more information.
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
|
||||
cmap = pg.colormap.get('CET-L9')
|
||||
bar = pg.ColorBarItem(
|
||||
interactive=False, values= (0, 30_000), cmap=cmap,
|
||||
interactive=False, values= (0, 30_000), colorMap=cmap,
|
||||
label='vertical fixed color bar'
|
||||
)
|
||||
bar.setImageItem( i1, insert_in=p1 )
|
||||
|
@ -59,11 +59,11 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
cmap = pg.colormap.get('CET-L4')
|
||||
bar = pg.ColorBarItem(
|
||||
values = (0, 30_000),
|
||||
cmap=cmap,
|
||||
colorMap=cmap,
|
||||
label='horizontal color bar',
|
||||
limits = (0, None),
|
||||
rounding=1000,
|
||||
orientation = 'horizontal',
|
||||
orientation = 'h',
|
||||
pen='#8888FF', hoverPen='#EEEEFF', hoverBrush='#EEEEFF80'
|
||||
)
|
||||
bar.setImageItem( i2, insert_in=p2 )
|
||||
|
@ -83,7 +83,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
limits = (-30_000, 30_000), # start with full range...
|
||||
rounding=1000,
|
||||
width = 10,
|
||||
cmap=cmap )
|
||||
colorMap=cmap )
|
||||
bar.setImageItem( [i3, i4] )
|
||||
bar.setLevels( low=-5_000, high=15_000) # ... then adjust to retro sunset.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Demonstrate use of GLLinePlotItem to draw cross-sections of a surface.
|
||||
This example demonstrates the use of GLBarGraphItem.
|
||||
|
||||
"""
|
||||
## Add path to library (just for examples; you do not need this)
|
||||
|
@ -13,9 +13,9 @@ import numpy as np
|
|||
|
||||
app = pg.mkQApp("GLBarGraphItem Example")
|
||||
w = gl.GLViewWidget()
|
||||
w.opts['distance'] = 40
|
||||
w.show()
|
||||
w.setWindowTitle('pyqtgraph example: GLBarGraphItem')
|
||||
w.setCameraPosition(distance=40)
|
||||
|
||||
gx = gl.GLGridItem()
|
||||
gx.rotate(90, 0, 1, 0)
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import initExample
|
||||
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.opengl as gl
|
||||
import numpy
|
||||
|
||||
app = pg.mkQApp()
|
||||
w = gl.GLViewWidget()
|
||||
w.show()
|
||||
w.setWindowTitle("pyqtgraph example: GLGradientLegendItem")
|
||||
w.setCameraPosition(distance=60)
|
||||
|
||||
gx = gl.GLGridItem()
|
||||
gx.rotate(90, 0, 1, 0)
|
||||
w.addItem(gx)
|
||||
|
||||
md = gl.MeshData.cylinder(rows=10, cols=20, radius=[5.0, 5], length=20.0)
|
||||
md._vertexes[:, 2] = md._vertexes[:, 2] - 10
|
||||
|
||||
# set color based on z coordinates
|
||||
color_map = pg.colormap.get("CET-L10")
|
||||
|
||||
h = md.vertexes()[:, 2]
|
||||
# remember these
|
||||
h_max, h_min = h.max(), h.min()
|
||||
h = (h - h_min) / (h_max - h_min)
|
||||
colors = color_map.map(h, mode="float")
|
||||
md.setFaceColors(colors)
|
||||
m = gl.GLMeshItem(meshdata=md, smooth=True)
|
||||
w.addItem(m)
|
||||
|
||||
legendLabels = numpy.linspace(h_max, h_min, 5)
|
||||
legendPos = numpy.linspace(1, 0, 5)
|
||||
legend = dict(zip(map(str, legendLabels), legendPos))
|
||||
|
||||
gll = gl.GLGradientLegendItem(
|
||||
pos=(10, 10), size=(50, 300), gradient=color_map, labels=legend
|
||||
)
|
||||
w.addItem(gll)
|
||||
|
||||
## Start Qt event loop unless running in interactive mode.
|
||||
if __name__ == "__main__":
|
||||
pg.exec()
|
|
@ -0,0 +1,47 @@
|
|||
"""
|
||||
Demonstrates use of GLGraphItem
|
||||
|
||||
"""
|
||||
|
||||
## Add path to library (just for examples; you do not need this)
|
||||
import initExample
|
||||
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.opengl as gl
|
||||
import numpy as np
|
||||
|
||||
app = pg.mkQApp("GLGraphItem Example")
|
||||
w = gl.GLViewWidget()
|
||||
w.setCameraPosition(distance=20)
|
||||
w.show()
|
||||
|
||||
edges = np.array([
|
||||
[0, 2],
|
||||
[0, 3],
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 3]
|
||||
])
|
||||
|
||||
nodes = np.array(
|
||||
[
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[1, 1, 1]
|
||||
]
|
||||
)
|
||||
|
||||
edgeColor=pg.glColor("w")
|
||||
|
||||
gi = gl.GLGraphItem(
|
||||
edges=edges,
|
||||
nodePositions=nodes,
|
||||
edgeWidth=1.,
|
||||
nodeSize=10.
|
||||
)
|
||||
|
||||
w.addItem(gi)
|
||||
|
||||
if __name__ == "__main__":
|
||||
pg.exec()
|
|
@ -15,9 +15,9 @@ import numpy as np
|
|||
|
||||
app = pg.mkQApp("GLImageItem Example")
|
||||
w = gl.GLViewWidget()
|
||||
w.opts['distance'] = 200
|
||||
w.show()
|
||||
w.setWindowTitle('pyqtgraph example: GLImageItem')
|
||||
w.setCameraPosition(distance=200)
|
||||
|
||||
## create volume data set to slice three images from
|
||||
shape = (100,100,70)
|
||||
|
|
|
@ -6,16 +6,15 @@ Demonstrate use of GLLinePlotItem to draw cross-sections of a surface.
|
|||
## Add path to library (just for examples; you do not need this)
|
||||
import initExample
|
||||
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import pyqtgraph.opengl as gl
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
|
||||
app = pg.mkQApp("GLLinePlotItem Example")
|
||||
w = gl.GLViewWidget()
|
||||
w.opts['distance'] = 40
|
||||
w.show()
|
||||
w.setWindowTitle('pyqtgraph example: GLLinePlotItem')
|
||||
w.setCameraPosition(distance=40)
|
||||
|
||||
gx = gl.GLGridItem()
|
||||
gx.rotate(90, 0, 1, 0)
|
||||
|
@ -37,7 +36,7 @@ for i in range(n):
|
|||
d = np.hypot(x, yi)
|
||||
z = 10 * np.cos(d) / (d+1)
|
||||
pts = np.column_stack([x, np.full_like(x, yi), z])
|
||||
plt = gl.GLLinePlotItem(pos=pts, color=pg.glColor((i,n*1.3)), width=(i+1)/10., antialias=True)
|
||||
plt = gl.GLLinePlotItem(pos=pts, color=pg.mkColor((i,n*1.3)), width=(i+1)/10., antialias=True)
|
||||
w.addItem(plt)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
"""
|
||||
Demonstrate using QPainter on a subclass of GLGraphicsItem.
|
||||
"""
|
||||
## Add path to library (just for examples; you do not need this)
|
||||
import initExample
|
||||
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.opengl
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import OpenGL.GL as GL
|
||||
|
||||
SIZE = 32
|
||||
|
||||
class GLPainterItem(pg.opengl.GLGraphicsItem.GLGraphicsItem):
|
||||
def __init__(self, **kwds):
|
||||
super().__init__()
|
||||
glopts = kwds.pop('glOptions', 'additive')
|
||||
self.setGLOptions(glopts)
|
||||
|
||||
def compute_projection(self):
|
||||
modelview = GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX)
|
||||
projection = GL.glGetDoublev(GL.GL_PROJECTION_MATRIX)
|
||||
mvp = projection.T @ modelview.T
|
||||
mvp = QtGui.QMatrix4x4(mvp.ravel().tolist())
|
||||
|
||||
# note that QRectF.bottom() != QRect.bottom()
|
||||
rect = QtCore.QRectF(self.view().rect())
|
||||
ndc_to_viewport = QtGui.QMatrix4x4()
|
||||
ndc_to_viewport.viewport(rect.left(), rect.bottom(), rect.width(), -rect.height())
|
||||
|
||||
return ndc_to_viewport * mvp
|
||||
|
||||
def paint(self):
|
||||
self.setupGLState()
|
||||
|
||||
painter = QtGui.QPainter(self.view())
|
||||
self.draw(painter)
|
||||
painter.end()
|
||||
|
||||
def draw(self, painter):
|
||||
painter.setPen(QtCore.Qt.GlobalColor.white)
|
||||
painter.setRenderHints(QtGui.QPainter.RenderHint.Antialiasing | QtGui.QPainter.RenderHint.TextAntialiasing)
|
||||
|
||||
rect = self.view().rect()
|
||||
af = QtCore.Qt.AlignmentFlag
|
||||
|
||||
painter.drawText(rect, af.AlignTop | af.AlignRight, 'TR')
|
||||
painter.drawText(rect, af.AlignBottom | af.AlignLeft, 'BL')
|
||||
painter.drawText(rect, af.AlignBottom | af.AlignRight, 'BR')
|
||||
|
||||
opts = self.view().cameraParams()
|
||||
lines = []
|
||||
center = opts['center']
|
||||
lines.append(f"center : ({center.x():.1f}, {center.y():.1f}, {center.z():.1f})")
|
||||
for key in ['distance', 'fov', 'elevation', 'azimuth']:
|
||||
lines.append(f"{key} : {opts[key]:.1f}")
|
||||
xyz = self.view().cameraPosition()
|
||||
lines.append(f"xyz : ({xyz.x():.1f}, {xyz.y():.1f}, {xyz.z():.1f})")
|
||||
info = "\n".join(lines)
|
||||
painter.drawText(rect, af.AlignTop | af.AlignLeft, info)
|
||||
|
||||
project = self.compute_projection()
|
||||
|
||||
hsize = SIZE // 2
|
||||
for xi in range(-hsize, hsize+1):
|
||||
for yi in range(-hsize, hsize+1):
|
||||
if xi == -hsize and yi == -hsize:
|
||||
# skip one corner for visual orientation
|
||||
continue
|
||||
vec3 = QtGui.QVector3D(xi, yi, 0)
|
||||
pos = project.map(vec3).toPointF()
|
||||
painter.drawEllipse(pos, 1, 1)
|
||||
|
||||
|
||||
pg.mkQApp("GLPainterItem Example")
|
||||
glv = pg.opengl.GLViewWidget()
|
||||
glv.show()
|
||||
glv.setWindowTitle('pyqtgraph example: GLPainterItem')
|
||||
glv.setCameraPosition(distance=50, elevation=90, azimuth=0)
|
||||
|
||||
griditem = pg.opengl.GLGridItem()
|
||||
griditem.setSize(SIZE, SIZE)
|
||||
griditem.setSpacing(1, 1)
|
||||
glv.addItem(griditem)
|
||||
|
||||
axisitem = pg.opengl.GLAxisItem()
|
||||
axisitem.setSize(SIZE/2, SIZE/2, 1)
|
||||
glv.addItem(axisitem)
|
||||
|
||||
paintitem = GLPainterItem()
|
||||
glv.addItem(paintitem)
|
||||
|
||||
if __name__ == '__main__':
|
||||
pg.exec()
|
|
@ -15,9 +15,9 @@ import numpy as np
|
|||
|
||||
app = pg.mkQApp("GLScatterPlotItem Example")
|
||||
w = gl.GLViewWidget()
|
||||
w.opts['distance'] = 20
|
||||
w.show()
|
||||
w.setWindowTitle('pyqtgraph example: GLScatterPlotItem')
|
||||
w.setCameraPosition(distance=20)
|
||||
|
||||
g = gl.GLGridItem()
|
||||
w.addItem(g)
|
||||
|
|
|
@ -11,9 +11,9 @@ import pyqtgraph.opengl as gl
|
|||
|
||||
pg.mkQApp("GLViewWidget Example")
|
||||
w = gl.GLViewWidget()
|
||||
w.opts['distance'] = 20
|
||||
w.show()
|
||||
w.setWindowTitle('pyqtgraph example: GLViewWidget')
|
||||
w.setCameraPosition(distance=20)
|
||||
|
||||
ax = gl.GLAxisItem()
|
||||
ax.setSize(5,5,5)
|
||||
|
|
|
@ -14,9 +14,9 @@ from pyqtgraph import functions as fn
|
|||
|
||||
app = pg.mkQApp("GLVolumeItem Example")
|
||||
w = gl.GLViewWidget()
|
||||
w.opts['distance'] = 200
|
||||
w.show()
|
||||
w.setWindowTitle('pyqtgraph example: GLVolumeItem')
|
||||
w.setCameraPosition(distance=200)
|
||||
|
||||
g = gl.GLGridItem()
|
||||
g.scale(10, 10, 1)
|
||||
|
@ -46,8 +46,27 @@ with np.errstate(divide = 'ignore'):
|
|||
negative = np.log(fn.clip_array(-data, 0, -data.min())**2)
|
||||
|
||||
d2 = np.empty(data.shape + (4,), dtype=np.ubyte)
|
||||
d2[..., 0] = positive * (255./positive.max())
|
||||
d2[..., 1] = negative * (255./negative.max())
|
||||
|
||||
# Original Code
|
||||
# d2[..., 0] = positive * (255./positive.max())
|
||||
# d2[..., 1] = negative * (255./negative.max())
|
||||
|
||||
# Reformulated Code
|
||||
# Both positive.max() and negative.max() are negative-valued.
|
||||
# Thus the next 2 lines are _not_ bounded to [0, 255]
|
||||
positive = positive * (255./positive.max())
|
||||
negative = negative * (255./negative.max())
|
||||
# When casting to ubyte, the original code relied on +Inf to be
|
||||
# converted to 0. On arm64, it gets converted to 255.
|
||||
# Thus the next 2 lines change +Inf explicitly to 0 instead.
|
||||
positive[np.isinf(positive)] = 0
|
||||
negative[np.isinf(negative)] = 0
|
||||
# When casting to ubyte, the original code relied on the conversion
|
||||
# to do modulo 256. The next 2 lines do it explicitly instead as
|
||||
# documentation.
|
||||
d2[..., 0] = positive.astype(int) % 256
|
||||
d2[..., 1] = negative.astype(int) % 256
|
||||
|
||||
d2[..., 2] = d2[...,1]
|
||||
d2[..., 3] = d2[..., 0]*0.3 + d2[..., 1]*0.3
|
||||
d2[..., 3] = (d2[..., 3].astype(float) / 255.) **2 * 255
|
||||
|
|
|
@ -9,7 +9,7 @@ import initExample
|
|||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.ptime as ptime
|
||||
from time import perf_counter
|
||||
|
||||
app = pg.mkQApp("ImageItem Example")
|
||||
|
||||
|
@ -33,27 +33,27 @@ view.setRange(QtCore.QRectF(0, 0, 600, 600))
|
|||
data = np.random.normal(size=(15, 600, 600), loc=1024, scale=64).astype(np.uint16)
|
||||
i = 0
|
||||
|
||||
updateTime = ptime.time()
|
||||
fps = 0
|
||||
updateTime = perf_counter()
|
||||
elapsed = 0
|
||||
|
||||
timer = QtCore.QTimer()
|
||||
timer.setSingleShot(True)
|
||||
# not using QTimer.singleShot() because of persistence on PyQt. see PR #1605
|
||||
|
||||
def updateData():
|
||||
global img, data, i, updateTime, fps
|
||||
global img, data, i, updateTime, elapsed
|
||||
|
||||
## Display the data
|
||||
img.setImage(data[i])
|
||||
i = (i+1) % data.shape[0]
|
||||
|
||||
timer.start(1)
|
||||
now = ptime.time()
|
||||
fps2 = 1.0 / (now-updateTime)
|
||||
now = perf_counter()
|
||||
elapsed_now = now - updateTime
|
||||
updateTime = now
|
||||
fps = fps * 0.9 + fps2 * 0.1
|
||||
|
||||
#print "%0.1f fps" % fps
|
||||
elapsed = elapsed * 0.9 + elapsed_now * 0.1
|
||||
|
||||
# print(f"{1 / elapsed:.1f} fps")
|
||||
|
||||
timer.timeout.connect(updateData)
|
||||
updateData()
|
||||
|
|
|
@ -11,7 +11,8 @@ import initExample
|
|||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.ptime import time
|
||||
|
||||
from time import perf_counter
|
||||
# pg.setConfigOptions(useOpenGL=True)
|
||||
app = pg.mkQApp("MultiPlot Speed Test")
|
||||
|
||||
|
@ -38,7 +39,7 @@ plot.addItem(rgn)
|
|||
|
||||
data = np.random.normal(size=(nPlots*23,nSamples))
|
||||
ptr = 0
|
||||
lastTime = time()
|
||||
lastTime = perf_counter()
|
||||
fps = None
|
||||
count = 0
|
||||
def update():
|
||||
|
@ -49,7 +50,7 @@ def update():
|
|||
curves[i].setData(data[(ptr+i)%data.shape[0]])
|
||||
|
||||
ptr += nPlots
|
||||
now = time()
|
||||
now = perf_counter()
|
||||
dt = now - lastTime
|
||||
lastTime = now
|
||||
if fps is None:
|
||||
|
|
|
@ -6,10 +6,9 @@ Demonstrates very basic use of PColorMeshItem
|
|||
## Add path to library (just for examples; you do not need this)
|
||||
import initExample
|
||||
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
from pyqtgraph.Qt import QtCore
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.ptime as ptime
|
||||
|
||||
app = pg.mkQApp("PColorMesh Example")
|
||||
|
||||
|
|
|
@ -7,46 +7,200 @@ Update a simple plot as rapidly as possible to measure speed.
|
|||
## Add path to library (just for examples; you do not need this)
|
||||
import initExample
|
||||
|
||||
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
from collections import deque
|
||||
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, QT_LIB
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.ptime import time
|
||||
from time import perf_counter
|
||||
import pyqtgraph.parametertree as ptree
|
||||
import pyqtgraph.functions as fn
|
||||
import itertools
|
||||
import argparse
|
||||
|
||||
if QT_LIB.startswith('PyQt'):
|
||||
wrapinstance = pg.Qt.sip.wrapinstance
|
||||
else:
|
||||
wrapinstance = pg.Qt.shiboken.wrapInstance
|
||||
|
||||
# defaults here result in the same configuration as the original PlotSpeedTest
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--noise', dest='noise', action='store_true')
|
||||
parser.add_argument('--no-noise', dest='noise', action='store_false')
|
||||
parser.set_defaults(noise=True)
|
||||
parser.add_argument('--nsamples', default=5000, type=int)
|
||||
parser.add_argument('--frames', default=50, type=int)
|
||||
parser.add_argument('--fsample', default=1000, type=float)
|
||||
parser.add_argument('--frequency', default=0, type=float)
|
||||
parser.add_argument('--amplitude', default=5, type=float)
|
||||
parser.add_argument('--opengl', dest='use_opengl', action='store_true')
|
||||
parser.add_argument('--no-opengl', dest='use_opengl', action='store_false')
|
||||
parser.set_defaults(use_opengl=None)
|
||||
parser.add_argument('--allow-opengl-toggle', action='store_true',
|
||||
help="""Allow on-the-fly change of OpenGL setting. This may cause unwanted side effects.
|
||||
""")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.use_opengl is not None:
|
||||
pg.setConfigOption('useOpenGL', args.use_opengl)
|
||||
pg.setConfigOption('enableExperimental', args.use_opengl)
|
||||
|
||||
# don't limit frame rate to vsync
|
||||
sfmt = QtGui.QSurfaceFormat()
|
||||
sfmt.setSwapInterval(0)
|
||||
QtGui.QSurfaceFormat.setDefaultFormat(sfmt)
|
||||
|
||||
class LineInstances:
|
||||
def __init__(self):
|
||||
self.alloc(0)
|
||||
|
||||
def alloc(self, size):
|
||||
self.arr = np.empty((size, 4), dtype=np.float64)
|
||||
self.ptrs = list(map(wrapinstance,
|
||||
itertools.count(self.arr.ctypes.data, self.arr.strides[0]),
|
||||
itertools.repeat(QtCore.QLineF, self.arr.shape[0])))
|
||||
|
||||
def array(self, size):
|
||||
if size > self.arr.shape[0]:
|
||||
self.alloc(size + 16)
|
||||
return self.arr[:size]
|
||||
|
||||
def instances(self, size):
|
||||
return self.ptrs[:size]
|
||||
|
||||
class MonkeyCurveItem(pg.PlotCurveItem):
|
||||
def __init__(self, *args, **kwds):
|
||||
super().__init__(*args, **kwds)
|
||||
self.monkey_mode = ''
|
||||
self._lineInstances = LineInstances()
|
||||
|
||||
def setMethod(self, param, value):
|
||||
self.monkey_mode = value
|
||||
|
||||
def paint(self, painter, opt, widget):
|
||||
if self.monkey_mode not in ['drawPolyline', 'drawLines']:
|
||||
return super().paint(painter, opt, widget)
|
||||
|
||||
painter.setRenderHint(painter.RenderHint.Antialiasing, self.opts['antialias'])
|
||||
painter.setPen(pg.mkPen(self.opts['pen']))
|
||||
|
||||
if self.monkey_mode == 'drawPolyline':
|
||||
painter.drawPolyline(fn.arrayToQPolygonF(self.xData, self.yData))
|
||||
elif self.monkey_mode == 'drawLines':
|
||||
lines = self._lineInstances
|
||||
npts = len(self.xData)
|
||||
even_slice = slice(0, 0+(npts-0)//2*2)
|
||||
odd_slice = slice(1, 1+(npts-1)//2*2)
|
||||
for sl in [even_slice, odd_slice]:
|
||||
npairs = (sl.stop - sl.start) // 2
|
||||
memory = lines.array(npairs).reshape((-1, 2))
|
||||
memory[:, 0] = self.xData[sl]
|
||||
memory[:, 1] = self.yData[sl]
|
||||
painter.drawLines(lines.instances(npairs))
|
||||
|
||||
app = pg.mkQApp("Plot Speed Test")
|
||||
|
||||
p = pg.plot()
|
||||
p.setWindowTitle('pyqtgraph example: PlotSpeedTest')
|
||||
p.setRange(QtCore.QRectF(0, -10, 5000, 20))
|
||||
p.setLabel('bottom', 'Index', units='B')
|
||||
curve = p.plot()
|
||||
default_pen = pg.mkPen()
|
||||
|
||||
#curve.setFillBrush((0, 0, 100, 100))
|
||||
#curve.setFillLevel(0)
|
||||
children = [
|
||||
dict(name='sigopts', title='Signal Options', type='group', children=[
|
||||
dict(name='noise', type='bool', value=args.noise),
|
||||
dict(name='nsamples', type='int', limits=[0, None], value=args.nsamples),
|
||||
dict(name='frames', type='int', limits=[1, None], value=args.frames),
|
||||
dict(name='fsample', title='sample rate', type='float', value=args.fsample, units='Hz'),
|
||||
dict(name='frequency', type='float', value=args.frequency, units='Hz'),
|
||||
dict(name='amplitude', type='float', value=args.amplitude),
|
||||
]),
|
||||
dict(name='useOpenGL', type='bool', value=pg.getConfigOption('useOpenGL'),
|
||||
readonly=not args.allow_opengl_toggle),
|
||||
dict(name='enableExperimental', type='bool', value=pg.getConfigOption('enableExperimental')),
|
||||
dict(name='pen', type='pen', value=default_pen),
|
||||
dict(name='antialias', type='bool', value=pg.getConfigOption('antialias')),
|
||||
dict(name='connect', type='list', limits=['all', 'pairs', 'finite', 'array'], value='all'),
|
||||
dict(name='skipFiniteCheck', type='bool', value=False),
|
||||
dict(name='plotMethod', title='Plot Method', type='list', limits=['pyqtgraph', 'drawPolyline', 'drawLines'])
|
||||
]
|
||||
|
||||
#lr = pg.LinearRegionItem([100, 4900])
|
||||
#p.addItem(lr)
|
||||
params = ptree.Parameter.create(name='Parameters', type='group', children=children)
|
||||
pt = ptree.ParameterTree(showHeader=False)
|
||||
pt.setParameters(params)
|
||||
pw = pg.PlotWidget()
|
||||
splitter = QtWidgets.QSplitter()
|
||||
splitter.addWidget(pt)
|
||||
splitter.addWidget(pw)
|
||||
splitter.show()
|
||||
|
||||
data = np.random.normal(size=(50,5000))
|
||||
ptr = 0
|
||||
lastTime = time()
|
||||
fps = None
|
||||
pw.setWindowTitle('pyqtgraph example: PlotSpeedTest')
|
||||
pw.setLabel('bottom', 'Index', units='B')
|
||||
curve = MonkeyCurveItem(pen=default_pen)
|
||||
pw.addItem(curve)
|
||||
|
||||
rollingAverageSize = 1000
|
||||
elapsed = deque(maxlen=rollingAverageSize)
|
||||
|
||||
def resetTimings(*args):
|
||||
elapsed.clear()
|
||||
|
||||
def makeData(*args):
|
||||
global data, connect_array, ptr
|
||||
sigopts = params.child('sigopts')
|
||||
nsamples = sigopts['nsamples']
|
||||
frames = sigopts['frames']
|
||||
Fs = sigopts['fsample']
|
||||
A = sigopts['amplitude']
|
||||
F = sigopts['frequency']
|
||||
ttt = np.arange(frames * nsamples, dtype=np.float64) / Fs
|
||||
data = A*np.sin(2*np.pi*F*ttt).reshape((frames, nsamples))
|
||||
if sigopts['noise']:
|
||||
data += np.random.normal(size=data.shape)
|
||||
connect_array = np.ones(data.shape[-1], dtype=bool)
|
||||
ptr = 0
|
||||
pw.setRange(QtCore.QRectF(0, -10, nsamples, 20))
|
||||
|
||||
def onUseOpenGLChanged(param, enable):
|
||||
pw.useOpenGL(enable)
|
||||
|
||||
def onEnableExperimentalChanged(param, enable):
|
||||
pg.setConfigOption('enableExperimental', enable)
|
||||
|
||||
def onPenChanged(param, pen):
|
||||
curve.setPen(pen)
|
||||
|
||||
params.child('sigopts').sigTreeStateChanged.connect(makeData)
|
||||
params.child('useOpenGL').sigValueChanged.connect(onUseOpenGLChanged)
|
||||
params.child('enableExperimental').sigValueChanged.connect(onEnableExperimentalChanged)
|
||||
params.child('pen').sigValueChanged.connect(onPenChanged)
|
||||
params.child('plotMethod').sigValueChanged.connect(curve.setMethod)
|
||||
params.sigTreeStateChanged.connect(resetTimings)
|
||||
|
||||
makeData()
|
||||
|
||||
fpsLastUpdate = perf_counter()
|
||||
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
|
||||
global curve, data, ptr, elapsed, fpsLastUpdate
|
||||
|
||||
options = ['antialias', 'connect', 'skipFiniteCheck']
|
||||
kwds = { k : params[k] for k in options }
|
||||
if kwds['connect'] == 'array':
|
||||
kwds['connect'] = connect_array
|
||||
|
||||
# Measure
|
||||
t_start = perf_counter()
|
||||
curve.setData(data[ptr], **kwds)
|
||||
app.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents)
|
||||
t_end = perf_counter()
|
||||
elapsed.append(t_end - t_start)
|
||||
ptr = (ptr + 1) % data.shape[0]
|
||||
|
||||
# update fps at most once every 0.2 secs
|
||||
if t_end - fpsLastUpdate > 0.2:
|
||||
fpsLastUpdate = t_end
|
||||
average = np.mean(elapsed)
|
||||
fps = 1 / average
|
||||
pw.setTitle('%0.2f fps - %0.1f ms avg' % (fps, average * 1_000))
|
||||
|
||||
timer = QtCore.QTimer()
|
||||
timer.timeout.connect(update)
|
||||
timer.start(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pg.exec()
|
||||
|
|
|
@ -16,6 +16,7 @@ from pyqtgraph.Qt import QtGui, QtCore
|
|||
import pyqtgraph as pg
|
||||
import pyqtgraph.widgets.RemoteGraphicsView
|
||||
import numpy as np
|
||||
from time import perf_counter
|
||||
|
||||
app = pg.mkQApp()
|
||||
|
||||
|
@ -45,7 +46,7 @@ rplt = view.pg.PlotItem()
|
|||
rplt._setProxyOptions(deferGetattr=True) ## speeds up access to rplt.plot
|
||||
view.setCentralItem(rplt)
|
||||
|
||||
lastUpdate = pg.ptime.time()
|
||||
lastUpdate = perf_counter()
|
||||
avgFps = 0.0
|
||||
|
||||
def update():
|
||||
|
@ -62,7 +63,7 @@ def update():
|
|||
if lcheck.isChecked():
|
||||
lplt.plot(data, clear=True)
|
||||
|
||||
now = pg.ptime.time()
|
||||
now = perf_counter()
|
||||
fps = 1.0 / (now - lastUpdate)
|
||||
lastUpdate = now
|
||||
avgFps = avgFps * 0.8 + fps * 0.2
|
||||
|
|
|
@ -12,9 +12,9 @@ import initExample
|
|||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtGui, QtCore, QtWidgets
|
||||
from pyqtgraph.ptime import time
|
||||
import pyqtgraph.parametertree as ptree
|
||||
import pyqtgraph.graphicsItems.ScatterPlotItem
|
||||
from time import perf_counter
|
||||
|
||||
translate = QtCore.QCoreApplication.translate
|
||||
|
||||
|
@ -26,7 +26,7 @@ param = ptree.Parameter.create(name=translate('ScatterPlot', 'Parameters'), type
|
|||
dict(name='randomize', title=translate('ScatterPlot', 'Randomize: '), type='bool', value=False),
|
||||
dict(name='pxMode', title='pxMode: ', type='bool', value=True),
|
||||
dict(name='useCache', title='useCache: ', type='bool', value=True),
|
||||
dict(name='mode', title=translate('ScatterPlot', 'Mode: '), type='list', values={translate('ScatterPlot', 'New Item'): 'newItem', translate('ScatterPlot', 'Reuse Item'): 'reuseItem', translate('ScatterPlot', 'Simulate Pan/Zoom'): 'panZoom', translate('ScatterPlot', 'Simulate Hover'): 'hover'}, value='reuseItem'),
|
||||
dict(name='mode', title=translate('ScatterPlot', 'Mode: '), type='list', limits={translate('ScatterPlot', 'New Item'): 'newItem', translate('ScatterPlot', 'Reuse Item'): 'reuseItem', translate('ScatterPlot', 'Simulate Pan/Zoom'): 'panZoom', translate('ScatterPlot', 'Simulate Hover'): 'hover'}, value='reuseItem'),
|
||||
])
|
||||
for c in param.children():
|
||||
c.setDefault(c.value())
|
||||
|
@ -43,7 +43,7 @@ data = {}
|
|||
item = pg.ScatterPlotItem()
|
||||
hoverBrush = pg.mkBrush('y')
|
||||
ptr = 0
|
||||
lastTime = time()
|
||||
lastTime = perf_counter()
|
||||
fps = None
|
||||
timer = QtCore.QTimer()
|
||||
|
||||
|
@ -104,7 +104,7 @@ def update():
|
|||
new.setBrush(hoverBrush)
|
||||
|
||||
ptr += 1
|
||||
now = time()
|
||||
now = perf_counter()
|
||||
dt = now - lastTime
|
||||
lastTime = now
|
||||
if fps is None:
|
||||
|
|
|
@ -15,8 +15,9 @@ import sys
|
|||
import numpy as np
|
||||
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.ptime as ptime
|
||||
from pyqtgraph.Qt import QtGui, QtCore, QT_LIB
|
||||
from time import perf_counter
|
||||
|
||||
|
||||
pg.setConfigOption('imageAxisOrder', 'row-major')
|
||||
|
||||
|
@ -247,7 +248,7 @@ ui.numbaCheck.toggled.connect(noticeNumbaCheck)
|
|||
|
||||
|
||||
ptr = 0
|
||||
lastTime = ptime.time()
|
||||
lastTime = perf_counter()
|
||||
fps = None
|
||||
def update():
|
||||
global ui, ptr, lastTime, fps, LUT, img
|
||||
|
@ -281,7 +282,7 @@ def update():
|
|||
#img.setImage(data[ptr%data.shape[0]], autoRange=False)
|
||||
|
||||
ptr += 1
|
||||
now = ptime.time()
|
||||
now = perf_counter()
|
||||
dt = now - lastTime
|
||||
lastTime = now
|
||||
if fps is None:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
ViewBox is the general-purpose graphical container that allows the user to
|
||||
zoom / pan to inspect any area of a 2D coordinate system.
|
||||
|
||||
This unimaginative example demonstrates the constrution of a ViewBox-based
|
||||
This unimaginative example demonstrates the construction of a ViewBox-based
|
||||
plot area with axes, very similar to the way PlotItem is built.
|
||||
"""
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
from pyqtgraph.parametertree import Parameter
|
||||
from pyqtgraph.parametertree.Parameter import PARAM_TYPES
|
||||
from pyqtgraph.parametertree.parameterTypes import GroupParameter
|
||||
from ._paramtreecfg import cfg
|
||||
|
||||
_encounteredTypes = {'group'}
|
||||
|
||||
def makeChild(chType, cfgDict):
|
||||
_encounteredTypes.add(chType)
|
||||
param = Parameter.create(name='widget', type=chType)
|
||||
param.setDefault(param.value())
|
||||
|
||||
def setOpt(_param, _val):
|
||||
# Treat blank strings as "None" to allow 'unsetting' that option
|
||||
if isinstance(_val, str) and _val == '':
|
||||
_val = None
|
||||
param.setOpts(**{_param.name(): _val})
|
||||
|
||||
optsChildren = []
|
||||
metaChildren = []
|
||||
for optName, optVals in cfgDict.items():
|
||||
child = Parameter.create(name=optName, **optVals)
|
||||
if ' ' in optName:
|
||||
metaChildren.append(child)
|
||||
else:
|
||||
optsChildren.append(child)
|
||||
child.sigValueChanged.connect(setOpt)
|
||||
# Poplate initial options
|
||||
for p in optsChildren:
|
||||
setOpt(p, p.value())
|
||||
|
||||
grp = Parameter.create(name=f'Sample {chType.title()}', type='group', children=metaChildren + [param] + optsChildren)
|
||||
grp.setOpts(expanded=False)
|
||||
return grp
|
||||
|
||||
def makeMetaChild(name, cfgDict):
|
||||
children = []
|
||||
for chName, chOpts in cfgDict.items():
|
||||
if not isinstance(chOpts, dict):
|
||||
ch = Parameter.create(name=chName, type=chName, value=chOpts)
|
||||
else:
|
||||
ch = Parameter.create(name=chName, **chOpts)
|
||||
_encounteredTypes.add(ch.type())
|
||||
children.append(ch)
|
||||
param = Parameter.create(name=name, type='group', children=children)
|
||||
param.setOpts(expanded=False)
|
||||
return param
|
||||
|
||||
def makeAllParamTypes():
|
||||
children = []
|
||||
for name, paramCfg in cfg.items():
|
||||
if ' ' in name:
|
||||
children.append(makeMetaChild(name, paramCfg))
|
||||
else:
|
||||
children.append(makeChild(name, paramCfg))
|
||||
|
||||
params = Parameter.create(name='Example Parameters', type='group', children=children)
|
||||
|
||||
# Slider needs minor tweak
|
||||
sliderGrp = params.child('Sample Slider')
|
||||
slider = sliderGrp.child('widget')
|
||||
slider.setOpts(limits=[0, 100])
|
||||
|
||||
# Also minor tweak to meta opts
|
||||
def setOpt(_param, _val):
|
||||
infoChild.setOpts(**{_param.name(): _val})
|
||||
meta = params.child('Applies to All Types')
|
||||
infoChild = meta.child('Extra Information')
|
||||
for child in meta.children()[1:]:
|
||||
child.sigValueChanged.connect(setOpt)
|
||||
|
||||
def onChange(_param, _val):
|
||||
if _val == 'Use span':
|
||||
span = slider.opts.pop('span', None)
|
||||
slider.setOpts(span=span)
|
||||
else:
|
||||
limits = slider.opts.pop('limits', None)
|
||||
slider.setOpts(limits=limits)
|
||||
sliderGrp.child('How to Set').sigValueChanged.connect(onChange)
|
||||
|
||||
def activate(action):
|
||||
for ch in params:
|
||||
if isinstance(ch, GroupParameter):
|
||||
ch.setOpts(expanded=action.name() == 'Expand All')
|
||||
|
||||
for name in 'Collapse', 'Expand':
|
||||
btn = Parameter.create(name=f'{name} All', type='action')
|
||||
btn.sigActivated.connect(activate)
|
||||
params.insertChild(0, btn)
|
||||
missing = set(PARAM_TYPES).difference(_encounteredTypes)
|
||||
if missing:
|
||||
raise RuntimeError(f'{missing} parameters are not represented')
|
||||
return params
|
|
@ -0,0 +1,187 @@
|
|||
import numpy as np
|
||||
|
||||
from pyqtgraph.Qt import QtWidgets
|
||||
from pyqtgraph.parametertree.parameterTypes import QtEnumParameter as enum
|
||||
|
||||
dlg = QtWidgets.QFileDialog
|
||||
|
||||
cfg = {
|
||||
'list': {
|
||||
'limits': {
|
||||
'type': 'checklist',
|
||||
'limits': ['a', 'b', 'c']
|
||||
}
|
||||
},
|
||||
'file': {
|
||||
'acceptMode': {
|
||||
'type': 'list',
|
||||
'limits': list(enum(dlg.AcceptMode, dlg).enumMap)
|
||||
},
|
||||
'fileMode': {
|
||||
'type': 'list',
|
||||
'limits': list(enum(dlg.FileMode, dlg).enumMap)
|
||||
},
|
||||
'viewMode': {
|
||||
'type': 'list',
|
||||
'limits': list(enum(dlg.ViewMode, dlg).enumMap)
|
||||
},
|
||||
'dialogLabel': {
|
||||
'type': 'list',
|
||||
'limits': list(enum(dlg.DialogLabel, dlg).enumMap)
|
||||
},
|
||||
'relativeTo': {
|
||||
'type': 'str',
|
||||
'value': None
|
||||
},
|
||||
'directory': {
|
||||
'type': 'str',
|
||||
'value': None
|
||||
},
|
||||
'windowTitle': {
|
||||
'type': 'str',
|
||||
'value': None
|
||||
},
|
||||
'nameFilter': {
|
||||
'type': 'str',
|
||||
'value': None
|
||||
}
|
||||
},
|
||||
'float': {
|
||||
'Float Information': {
|
||||
'type': 'str',
|
||||
'readonly': True,
|
||||
'value': 'Note that all options except "finite" also apply to "int" parameters',
|
||||
},
|
||||
'step': {
|
||||
'type': 'float',
|
||||
'limits': [0, None],
|
||||
'value': 1,
|
||||
},
|
||||
'limits': {
|
||||
'type': 'list',
|
||||
'limits': {'[0, None]': [0, None], '[1, 5]': [1, 5]},
|
||||
},
|
||||
'suffix': {
|
||||
'type': 'list',
|
||||
'limits': ['Hz', 's', 'm'],
|
||||
},
|
||||
'siPrefix': {
|
||||
'type': 'bool',
|
||||
'value': True
|
||||
},
|
||||
'finite': {
|
||||
'type': 'bool',
|
||||
'value': True,
|
||||
},
|
||||
'dec': {
|
||||
'type': 'bool',
|
||||
'value': False,
|
||||
},
|
||||
'minStep': {
|
||||
'type': 'float',
|
||||
'value': 1.0e-12,
|
||||
},
|
||||
},
|
||||
|
||||
'checklist': {
|
||||
'limits': {
|
||||
'type': 'checklist',
|
||||
'limits': ['one', 'two', 'three', 'four'],
|
||||
},
|
||||
'exclusive': {
|
||||
'type': 'bool',
|
||||
'value': False,
|
||||
}
|
||||
},
|
||||
|
||||
'pen': {
|
||||
'Pen Information': {
|
||||
'type': 'str',
|
||||
'value': 'Click the button to see options',
|
||||
'readonly': True,
|
||||
},
|
||||
},
|
||||
|
||||
'slider': {
|
||||
'step': {
|
||||
'type': 'float',
|
||||
'limits': [0, None],
|
||||
'value': 1, },
|
||||
'format': {
|
||||
'type': 'str',
|
||||
'value': '{0:>3}',
|
||||
},
|
||||
'precision': {
|
||||
'type': 'int',
|
||||
'value': 2,
|
||||
'limits': [1, None],
|
||||
},
|
||||
'span': {
|
||||
'type': 'list',
|
||||
'limits': {'linspace(-pi, pi)': np.linspace(-np.pi, np.pi), 'arange(10)**2': np.arange(10) ** 2},
|
||||
},
|
||||
|
||||
'How to Set': {
|
||||
'type': 'list',
|
||||
'limits': ['Use span', 'Use step + limits'],
|
||||
}
|
||||
},
|
||||
|
||||
'calendar': {
|
||||
'format': {
|
||||
'type': 'str',
|
||||
'value': 'MM DD',
|
||||
}
|
||||
},
|
||||
|
||||
'Applies to All Types': {
|
||||
'Extra Information': {
|
||||
'type': 'text',
|
||||
'value': 'These apply to all parameters. Watch how this text box is altered by any setting you change.',
|
||||
'default': 'These apply to all parameters. Watch how this text box is altered by any setting you change.',
|
||||
'readonly': True,
|
||||
},
|
||||
'readonly': {
|
||||
'type': 'bool',
|
||||
'value': True,
|
||||
},
|
||||
'removable': {
|
||||
'type': 'bool',
|
||||
'tip': 'Adds a context menu option to remove this parameter',
|
||||
'value': False,
|
||||
},
|
||||
'visible': {
|
||||
'type': 'bool',
|
||||
'value': True,
|
||||
},
|
||||
'disabled': {
|
||||
'type': 'bool',
|
||||
'value': False,
|
||||
},
|
||||
'title': {
|
||||
'type': 'str',
|
||||
'value': 'Meta Options',
|
||||
},
|
||||
'default': {
|
||||
'tip': 'The default value that gets set when clicking the arrow in the right column',
|
||||
'type': 'str',
|
||||
},
|
||||
'expanded': {
|
||||
'type': 'bool',
|
||||
'value': True,
|
||||
},
|
||||
},
|
||||
|
||||
'No Extra Options': {
|
||||
'text': 'Unlike the other parameters shown, these don\'t have extra settable options.\n' \
|
||||
+ 'Note: "int" *does* have the same options as float, mentioned above',
|
||||
'int': 10,
|
||||
'str': 'Hi, world!',
|
||||
'color': '#fff',
|
||||
'bool': False,
|
||||
'colormap': None,
|
||||
'progress': 50,
|
||||
'action': None,
|
||||
'font': 'Inter',
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Demonstrates some customized mouse interaction by drawing a crosshair that follows
|
||||
the mouse.
|
||||
|
@ -31,14 +32,16 @@ p1.setAutoVisible(y=True)
|
|||
|
||||
|
||||
#create numpy arrays
|
||||
#make the numbers large to show that the xrange shows data from 10000 to all the way 0
|
||||
#make the numbers large to show that the range shows data from 10000 to all the way 0
|
||||
data1 = 10000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
|
||||
data2 = 15000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
|
||||
|
||||
p1.plot(data1, pen="r")
|
||||
p1.plot(data2, pen="g")
|
||||
|
||||
p2.plot(data1, pen="w")
|
||||
p2d = p2.plot(data1, pen="w")
|
||||
# bound the LinearRegionItem to the plotted data
|
||||
region.setClipItem(p2d)
|
||||
|
||||
def update():
|
||||
region.setZValue(10)
|
||||
|
|
|
@ -117,4 +117,4 @@ class Ui_Form(object):
|
|||
self.searchFiles.setItemText(1, QCoreApplication.translate("Form", u"Content Search", None))
|
||||
|
||||
self.loadedFileLabel.setText("")
|
||||
# retranslateUi
|
||||
# retranslateUi
|
||||
|
|
|
@ -117,4 +117,4 @@ class Ui_Form(object):
|
|||
self.searchFiles.setItemText(1, QCoreApplication.translate("Form", u"Content Search", None))
|
||||
|
||||
self.loadedFileLabel.setText("")
|
||||
# retranslateUi
|
||||
# retranslateUi
|
||||
|
|
|
@ -51,9 +51,9 @@ class HDF5Plot(pg.PlotCurveItem):
|
|||
return # no ViewBox yet
|
||||
|
||||
# Determine what data range must be read from HDF5
|
||||
xrange = vb.viewRange()[0]
|
||||
start = max(0,int(xrange[0])-1)
|
||||
stop = min(len(self.hdf5), int(xrange[1]+2))
|
||||
range_ = vb.viewRange()[0]
|
||||
start = max(0,int(range_[0])-1)
|
||||
stop = min(len(self.hdf5), int(range_[1]+2))
|
||||
|
||||
# Decide by how much we should downsample
|
||||
ds = int((stop-start) / self.limit) + 1
|
||||
|
|
|
@ -106,7 +106,7 @@ def imageHoverEvent(event):
|
|||
val = data[i, j]
|
||||
ppos = img.mapToParent(pos)
|
||||
x, y = ppos.x(), ppos.y()
|
||||
p1.setTitle("pos: (%0.1f, %0.1f) pixel: (%d, %d) value: %g" % (x, y, i, j, val))
|
||||
p1.setTitle("pos: (%0.1f, %0.1f) pixel: (%d, %d) value: %.3g" % (x, y, i, j, val))
|
||||
|
||||
# Monkey-patch the image to use our custom hover function.
|
||||
# This is generally discouraged (you should subclass ImageItem instead),
|
||||
|
|
|
@ -4,7 +4,8 @@ import initExample ## Add path to library (just for examples; you do not need th
|
|||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.ptime import time
|
||||
from time import perf_counter
|
||||
|
||||
app = pg.mkQApp("Infinite Line Performance")
|
||||
|
||||
p = pg.plot()
|
||||
|
@ -20,7 +21,7 @@ for i in range(100):
|
|||
|
||||
data = np.random.normal(size=(50, 5000))
|
||||
ptr = 0
|
||||
lastTime = time()
|
||||
lastTime = perf_counter()
|
||||
fps = None
|
||||
|
||||
|
||||
|
@ -28,7 +29,7 @@ def update():
|
|||
global curve, data, ptr, p, lastTime, fps
|
||||
curve.setData(data[ptr % 10])
|
||||
ptr += 1
|
||||
now = time()
|
||||
now = perf_counter()
|
||||
dt = now - lastTime
|
||||
lastTime = now
|
||||
if fps is None:
|
||||
|
|
|
@ -29,4 +29,4 @@ try:
|
|||
import faulthandler
|
||||
faulthandler.enable()
|
||||
except ImportError:
|
||||
pass
|
||||
pass
|
||||
|
|
|
@ -1,38 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Test programmatically setting log transformation modes.
|
||||
Demonstrate programmatic setting of log transformation modes.
|
||||
"""
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
import numpy as np
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph as pg
|
||||
|
||||
|
||||
app = pg.mkQApp("Log Axis Example")
|
||||
|
||||
w = pg.GraphicsLayoutWidget(show=True)
|
||||
w.setWindowTitle('pyqtgraph example: logAxis')
|
||||
p1 = w.addPlot(0,0, title="X Semilog")
|
||||
w.resize(800,800)
|
||||
w.setWindowTitle('pyqtgraph example: Log Axis, or How to Recognise Different Types of Curves from Quite a Long Way Away')
|
||||
|
||||
p0 = w.addPlot(0,0, title="Linear")
|
||||
p1 = w.addPlot(0,1, title="X Semilog")
|
||||
p2 = w.addPlot(1,0, title="Y Semilog")
|
||||
p3 = w.addPlot(2,0, title="XY Log")
|
||||
p1.showGrid(True, True)
|
||||
p2.showGrid(True, True)
|
||||
p3.showGrid(True, True)
|
||||
p3 = w.addPlot(1,1, title="XY Log")
|
||||
# configure logarithmic axis scaling:
|
||||
p1.setLogMode(True, False)
|
||||
p2.setLogMode(False, True)
|
||||
p3.setLogMode(True, True)
|
||||
|
||||
# 1000 points from 0.1 to 10, chosen to give a compatible range of values across curves:
|
||||
x = np.logspace(-1, 1, 1000)
|
||||
plotdata = ( # legend entry, color, and plotted equation:
|
||||
('1 / 3x' , '#ff9d47', 1./(3*x) ),
|
||||
('sqrt x' , '#b3cf00', 1/np.sqrt(x) ),
|
||||
('exp. decay', '#00a0b5', 5 * np.exp(-x/1) ),
|
||||
('-log x' , '#a54dff', - np.log10(x) )
|
||||
)
|
||||
p0.addLegend(offset=(-20,20)) # include legend only in top left plot
|
||||
for p in (p0, p1, p2, p3): # draw identical numerical data in all four plots
|
||||
p.showGrid(True, True) # turn on grid for all four plots
|
||||
p.showAxes(True, size=(40,None)) # show a full frame, and reserve identical room for y labels
|
||||
for name, color, y in plotdata: # draw all four curves as defined in plotdata
|
||||
pen = pg.mkPen(color, width=2)
|
||||
p.plot( x,y, pen=pen, name=name )
|
||||
|
||||
w.show()
|
||||
|
||||
y = np.random.normal(size=1000)
|
||||
x = np.linspace(0, 1, 1000)
|
||||
p1.plot(x, y)
|
||||
p2.plot(x, y)
|
||||
p3.plot(x, y)
|
||||
|
||||
|
||||
|
||||
#p.getAxis('bottom').setLogMode(True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
pg.exec()
|
||||
|
|
|
@ -4,6 +4,7 @@ import initExample ## Add path to library (just for examples; you do not need th
|
|||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
from time import perf_counter
|
||||
|
||||
app = pg.mkQApp()
|
||||
plt = pg.PlotWidget()
|
||||
|
@ -18,7 +19,7 @@ plt.show()
|
|||
plt.enableAutoRange(False, False)
|
||||
|
||||
def plot():
|
||||
start = pg.ptime.time()
|
||||
start = perf_counter()
|
||||
n = 15
|
||||
pts = 100
|
||||
x = np.linspace(0, 0.8, pts)
|
||||
|
@ -38,8 +39,8 @@ def plot():
|
|||
#item.setPen(pg.mkPen('w'))
|
||||
#plt.addItem(item)
|
||||
|
||||
dt = pg.ptime.time() - start
|
||||
print("Create plots took: %0.3fms" % (dt*1000))
|
||||
dt = perf_counter() - start
|
||||
print(f"Create plots tooks {dt * 1000:.3f} ms")
|
||||
|
||||
## Plot and clear 5 times, printing the time it took
|
||||
for i in range(5):
|
||||
|
@ -55,7 +56,7 @@ for i in range(5):
|
|||
def fastPlot():
|
||||
## Different approach: generate a single item with all data points.
|
||||
## This runs about 20x faster.
|
||||
start = pg.ptime.time()
|
||||
start = perf_counter()
|
||||
n = 15
|
||||
pts = 100
|
||||
x = np.linspace(0, 0.8, pts)
|
||||
|
@ -71,7 +72,7 @@ def fastPlot():
|
|||
item.setPen(pg.mkPen('w'))
|
||||
plt.addItem(item)
|
||||
|
||||
dt = pg.ptime.time() - start
|
||||
dt = perf_counter() - start
|
||||
print("Create plots took: %0.3fms" % (dt*1000))
|
||||
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
from .pyoptic import *
|
||||
from .pyoptic import *
|
||||
|
|
|
@ -5,7 +5,6 @@ import time
|
|||
import numpy as np
|
||||
import pyqtgraph.multiprocess as mp
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.python2_3 import xrange
|
||||
|
||||
print( "\n=================\nParallelize")
|
||||
|
||||
|
@ -32,7 +31,7 @@ start = time.time()
|
|||
with pg.ProgressDialog('processing serially..', maximum=len(tasks)) as dlg:
|
||||
for i, x in enumerate(tasks):
|
||||
tot = 0
|
||||
for j in xrange(size):
|
||||
for j in range(size):
|
||||
tot += j * x
|
||||
results[i] = tot
|
||||
dlg += 1
|
||||
|
@ -46,7 +45,7 @@ start = time.time()
|
|||
with mp.Parallelize(enumerate(tasks), results=results2, workers=1, progressDialog='processing serially (using Parallelizer)..') as tasker:
|
||||
for i, x in tasker:
|
||||
tot = 0
|
||||
for j in xrange(size):
|
||||
for j in range(size):
|
||||
tot += j * x
|
||||
tasker.results[i] = tot
|
||||
print( "\nParallel time, 1 worker: %0.2f" % (time.time() - start))
|
||||
|
@ -57,9 +56,8 @@ start = time.time()
|
|||
with mp.Parallelize(enumerate(tasks), results=results3, progressDialog='processing in parallel..') as tasker:
|
||||
for i, x in tasker:
|
||||
tot = 0
|
||||
for j in xrange(size):
|
||||
for j in range(size):
|
||||
tot += j * x
|
||||
tasker.results[i] = tot
|
||||
print( "\nParallel time, %d workers: %0.2f" % (mp.Parallelize.suggestedWorkerCount(), time.time() - start))
|
||||
print( "Results match serial: %s" % str(results3 == results))
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This example demonstrates the use of pyqtgraph's parametertree system. This provides
|
||||
a simple way to generate user interfaces that control sets of parameters. The example
|
||||
|
@ -6,17 +5,20 @@ demonstrates a variety of different parameter types (int, float, list, etc.)
|
|||
as well as some customized parameter types
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
# `makeAllParamTypes` creates several parameters from a dictionary of config specs.
|
||||
# This contains information about the options for each parameter so they can be directly
|
||||
# inserted into the example parameter tree. To create your own parameters, simply follow
|
||||
# the guidelines demonstrated by other parameters created here.
|
||||
from examples._buildParamTypes import makeAllParamTypes
|
||||
from pyqtgraph.Qt import QtGui
|
||||
|
||||
|
||||
app = pg.mkQApp("Parameter Tree Example")
|
||||
import pyqtgraph.parametertree.parameterTypes as pTypes
|
||||
from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType
|
||||
from pyqtgraph.parametertree import Parameter, ParameterTree
|
||||
|
||||
|
||||
## test subclassing parameters
|
||||
|
@ -62,29 +64,7 @@ class ScalableGroup(pTypes.GroupParameter):
|
|||
|
||||
|
||||
params = [
|
||||
{'name': 'Basic parameter data types', 'type': 'group', 'children': [
|
||||
{'name': 'Integer', 'type': 'int', 'value': 10},
|
||||
{'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1, 'finite': False},
|
||||
{'name': 'String', 'type': 'str', 'value': "hi", 'tip': 'Well hello'},
|
||||
{'name': 'List', 'type': 'list', 'values': [1,2,3], 'value': 2},
|
||||
{'name': 'Named List', 'type': 'list', 'values': {"one": 1, "two": "twosies", "three": [3,3,3]}, 'value': 2},
|
||||
{'name': 'Boolean', 'type': 'bool', 'value': True, 'tip': "This is a checkbox"},
|
||||
{'name': 'Color', 'type': 'color', 'value': "#FF0", 'tip': "This is a color button"},
|
||||
{'name': 'Gradient', 'type': 'colormap'},
|
||||
{'name': 'Subgroup', 'type': 'group', 'children': [
|
||||
{'name': 'Sub-param 1', 'type': 'int', 'value': 10},
|
||||
{'name': 'Sub-param 2', 'type': 'float', 'value': 1.2e6},
|
||||
]},
|
||||
{'name': 'Text Parameter', 'type': 'text', 'value': 'Some text...'},
|
||||
{'name': 'Action Parameter', 'type': 'action', 'tip': 'Click me'},
|
||||
]},
|
||||
{'name': 'Numerical Parameter Options', 'type': 'group', 'children': [
|
||||
{'name': 'Units + SI prefix', 'type': 'float', 'value': 1.2e-6, 'step': 1e-6, 'siPrefix': True, 'suffix': 'V'},
|
||||
{'name': 'Limits (min=7;max=15)', 'type': 'int', 'value': 11, 'limits': (7, 15), 'default': -6},
|
||||
{'name': 'Int suffix', 'type': 'int', 'value': 9, 'suffix': 'V'},
|
||||
{'name': 'DEC stepping', 'type': 'float', 'value': 1.2e6, 'dec': True, 'step': 1, 'minStep': 1.0e-12, 'siPrefix': True, 'suffix': 'Hz'},
|
||||
|
||||
]},
|
||||
makeAllParamTypes(),
|
||||
{'name': 'Save/Restore functionality', 'type': 'group', 'children': [
|
||||
{'name': 'Save State', 'type': 'action'},
|
||||
{'name': 'Restore State', 'type': 'action', 'children': [
|
||||
|
@ -92,12 +72,6 @@ params = [
|
|||
{'name': 'Remove extra items', 'type': 'bool', 'value': True},
|
||||
]},
|
||||
]},
|
||||
{'name': 'Extra Parameter Options', 'type': 'group', 'children': [
|
||||
{'name': 'Read-only', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'readonly': True},
|
||||
{'name': 'Disabled', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'enabled': False},
|
||||
{'name': 'Renamable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'renamable': True},
|
||||
{'name': 'Removable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'removable': True},
|
||||
]},
|
||||
{'name': 'Custom context menu', 'type': 'group', 'children': [
|
||||
{'name': 'List contextMenu', 'type': 'float', 'value': 0, 'context': [
|
||||
'menu1',
|
||||
|
@ -145,11 +119,10 @@ for child in p.children():
|
|||
ch2.sigValueChanging.connect(valueChanging)
|
||||
|
||||
|
||||
|
||||
def save():
|
||||
global state
|
||||
state = p.saveState()
|
||||
|
||||
|
||||
def restore():
|
||||
global state
|
||||
add = p['Save/Restore functionality', 'Restore State', 'Add missing items']
|
||||
|
@ -175,8 +148,10 @@ layout.addWidget(t2, 1, 1, 1, 1)
|
|||
win.show()
|
||||
|
||||
## test save/restore
|
||||
s = p.saveState()
|
||||
p.restoreState(s)
|
||||
state = p.saveState()
|
||||
p.restoreState(state)
|
||||
compareState = p.saveState()
|
||||
assert pg.eq(compareState, state)
|
||||
|
||||
if __name__ == '__main__':
|
||||
pg.exec()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import numpy as np
|
||||
import collections
|
||||
import sys, os
|
||||
|
@ -6,7 +7,8 @@ from pyqtgraph.Qt import QtGui, QtCore
|
|||
from pyqtgraph.parametertree import Parameter, ParameterTree
|
||||
from pyqtgraph.parametertree import types as pTypes
|
||||
import pyqtgraph.configfile
|
||||
from pyqtgraph.python2_3 import xrange
|
||||
|
||||
from time import perf_counter
|
||||
|
||||
|
||||
class RelativityGUI(QtGui.QWidget):
|
||||
|
@ -25,10 +27,10 @@ class RelativityGUI(QtGui.QWidget):
|
|||
self.objectGroup = ObjectGroupParam()
|
||||
|
||||
self.params = Parameter.create(name='params', type='group', children=[
|
||||
dict(name='Load Preset..', type='list', values=[]),
|
||||
#dict(name='Unit System', type='list', values=['', 'MKS']),
|
||||
dict(name='Load Preset..', type='list', limits=[]),
|
||||
#dict(name='Unit System', type='list', limits=['', 'MKS']),
|
||||
dict(name='Duration', type='float', value=10.0, step=0.1, limits=[0.1, None]),
|
||||
dict(name='Reference Frame', type='list', values=[]),
|
||||
dict(name='Reference Frame', type='list', limits=[]),
|
||||
dict(name='Animate', type='bool', value=True),
|
||||
dict(name='Animation Speed', type='float', value=1.0, dec=True, step=0.1, limits=[0.0001, None]),
|
||||
dict(name='Recalculate Worldlines', type='action'),
|
||||
|
@ -130,13 +132,13 @@ class RelativityGUI(QtGui.QWidget):
|
|||
|
||||
def setAnimation(self, a):
|
||||
if a:
|
||||
self.lastAnimTime = pg.ptime.time()
|
||||
self.lastAnimTime = perf_counter()
|
||||
self.animTimer.start(int(self.animDt*1000))
|
||||
else:
|
||||
self.animTimer.stop()
|
||||
|
||||
def stepAnimation(self):
|
||||
now = pg.ptime.time()
|
||||
now = perf_counter()
|
||||
dt = (now-self.lastAnimTime) * self.params['Animation Speed']
|
||||
self.lastAnimTime = now
|
||||
self.animTime += dt
|
||||
|
@ -518,7 +520,7 @@ class Simulation:
|
|||
dt = self.dt
|
||||
tVals = np.linspace(0, dt*(nPts-1), nPts)
|
||||
for cl in self.clocks.values():
|
||||
for i in xrange(1,nPts):
|
||||
for i in range(1,nPts):
|
||||
nextT = tVals[i]
|
||||
while True:
|
||||
tau1, tau2 = cl.accelLimits()
|
||||
|
@ -564,7 +566,7 @@ class Simulation:
|
|||
## These are the set of proper times (in the reference frame) that will be simulated
|
||||
ptVals = np.linspace(ref.pt, ref.pt + dt*(nPts-1), nPts)
|
||||
|
||||
for i in xrange(1,nPts):
|
||||
for i in range(1,nPts):
|
||||
|
||||
## step reference clock ahead one time step in its proper time
|
||||
nextPt = ptVals[i] ## this is where (when) we want to end up
|
||||
|
|
|
@ -7,6 +7,7 @@ import initExample ## Add path to library (just for examples; you do not need th
|
|||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
from time import perf_counter
|
||||
|
||||
win = pg.GraphicsLayoutWidget(show=True)
|
||||
win.setWindowTitle('pyqtgraph example: Scrolling Plots')
|
||||
|
@ -67,7 +68,7 @@ def update2():
|
|||
chunkSize = 100
|
||||
# Remove chunks after we have 10
|
||||
maxChunks = 10
|
||||
startTime = pg.ptime.time()
|
||||
startTime = perf_counter()
|
||||
win.nextRow()
|
||||
p5 = win.addPlot(colspan=2)
|
||||
p5.setLabel('bottom', 'Time', 's')
|
||||
|
@ -78,7 +79,7 @@ ptr5 = 0
|
|||
|
||||
def update3():
|
||||
global p5, data5, ptr5, curves
|
||||
now = pg.ptime.time()
|
||||
now = perf_counter()
|
||||
for c in curves:
|
||||
c.setPos(-(now-startTime), 0)
|
||||
|
||||
|
|
|
@ -70,51 +70,15 @@ conditionalExamples = {
|
|||
False,
|
||||
reason="Test is being problematic on CI machines"
|
||||
),
|
||||
'GLVolumeItem.py': exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
),
|
||||
'GLIsosurface.py': exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
),
|
||||
'GLSurfacePlot.py': exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
),
|
||||
'GLScatterPlotItem.py': exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
),
|
||||
'GLshaders.py': exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
),
|
||||
'GLTextItem.py': exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
),
|
||||
'GLLinePlotItem.py': exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
),
|
||||
'GLMeshItem.py': exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
),
|
||||
'GLImageItem.py': exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
),
|
||||
'GLBarGraphItem.py': exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
),
|
||||
'GLViewWidget.py': exceptionCondition(
|
||||
}
|
||||
|
||||
openglExamples = ['GLViewWidget.py']
|
||||
openglExamples.extend(utils.examples_['3D Graphics'].values())
|
||||
for key in openglExamples:
|
||||
conditionalExamples[key] = exceptionCondition(
|
||||
not darwin_opengl_broken,
|
||||
reason=darwin_opengl_reason
|
||||
)
|
||||
}
|
||||
|
||||
@pytest.mark.skipif(
|
||||
Qt.QT_LIB == "PySide2"
|
||||
|
|
|
@ -74,6 +74,9 @@ examples_ = OrderedDict([
|
|||
('Mesh', 'GLMeshItem.py'),
|
||||
('Image', 'GLImageItem.py'),
|
||||
('Text', 'GLTextItem.py'),
|
||||
('BarGraph', 'GLBarGraphItem.py'),
|
||||
('Painter', 'GLPainterItem.py'),
|
||||
('Gradient Legend', 'GLGradientLegendItem.py')
|
||||
])),
|
||||
('Widgets', OrderedDict([
|
||||
('PlotWidget', 'PlotWidget.py'),
|
||||
|
@ -105,7 +108,6 @@ others = dict([
|
|||
('ScaleBar', 'ScaleBar.py'),
|
||||
('ViewBox', 'ViewBox.py'),
|
||||
('GradientEditor', 'GradientEditor.py'),
|
||||
('GLBarGraphItem', 'GLBarGraphItem.py'),
|
||||
('GLViewWidget', 'GLViewWidget.py'),
|
||||
('DiffTreeWidget', 'DiffTreeWidget.py'),
|
||||
('MultiPlotWidget', 'MultiPlotWidget.py'),
|
||||
|
|
|
@ -1 +1 @@
|
|||
from .chain import ChainSim
|
||||
from .chain import ChainSim
|
||||
|
|
|
@ -67,7 +67,7 @@ class ChainSim(pg.QtCore.QObject):
|
|||
def update(self):
|
||||
# approximate physics with verlet integration
|
||||
|
||||
now = pg.ptime.time()
|
||||
now = time.perf_counter()
|
||||
if self.lasttime is None:
|
||||
dt = 0
|
||||
else:
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import weakref
|
||||
import warnings
|
||||
from time import perf_counter, perf_counter_ns
|
||||
|
||||
from ..Qt import QtCore, QtGui, QT_LIB, isQObjectAlive
|
||||
from ..Point import Point
|
||||
from .. import functions as fn
|
||||
from .. import ptime as ptime
|
||||
from .mouseEvents import *
|
||||
from .. import debug as debug
|
||||
from .. import getConfigOption
|
||||
|
||||
getMillis = lambda: int(round(time.time() * 1000))
|
||||
getMillis = lambda: perf_counter_ns() // 10 ** 6
|
||||
|
||||
|
||||
if QT_LIB.startswith('PyQt'):
|
||||
|
@ -30,15 +28,15 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
|||
events, but this turned out to be impossible because the constructor for QGraphicsMouseEvent
|
||||
is private)
|
||||
|
||||
* Generates MouseClicked events in addition to the usual press/move/release events.
|
||||
(This works around a problem where it is impossible to have one item respond to a
|
||||
drag if another is watching for a click.)
|
||||
* Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects
|
||||
* Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu.
|
||||
* Allows items to decide _before_ a mouse click which item will be the recipient of mouse events.
|
||||
This lets us indicate unambiguously to the user which item they are about to click/drag on
|
||||
* Eats mouseMove events that occur too soon after a mouse press.
|
||||
* Reimplements items() and itemAt() to circumvent PyQt bug
|
||||
* Generates MouseClicked events in addition to the usual press/move/release events.
|
||||
(This works around a problem where it is impossible to have one item respond to a
|
||||
drag if another is watching for a click.)
|
||||
* Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects
|
||||
* Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu.
|
||||
* Allows items to decide _before_ a mouse click which item will be the recipient of mouse events.
|
||||
This lets us indicate unambiguously to the user which item they are about to click/drag on
|
||||
* Eats mouseMove events that occur too soon after a mouse press.
|
||||
* Reimplements items() and itemAt() to circumvent PyQt bug
|
||||
|
||||
====================== ====================================================================
|
||||
**Signals**
|
||||
|
@ -198,7 +196,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
|||
# button is pressed' send mouseMoveEvents and mouseDragEvents
|
||||
super().mouseMoveEvent(ev)
|
||||
if self.mouseGrabberItem() is None:
|
||||
now = ptime.time()
|
||||
now = perf_counter()
|
||||
init = False
|
||||
## keep track of which buttons are involved in dragging
|
||||
for btn in [QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.MouseButton.MiddleButton, QtCore.Qt.MouseButton.RightButton]:
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from time import perf_counter
|
||||
from ..Point import Point
|
||||
from ..Qt import QtCore, QtGui
|
||||
from ..Qt import QtCore
|
||||
import weakref
|
||||
from .. import ptime as ptime
|
||||
|
||||
class MouseDragEvent(object):
|
||||
"""
|
||||
|
@ -164,7 +165,7 @@ class MouseClickEvent(object):
|
|||
self._button = pressEvent.button()
|
||||
self._buttons = pressEvent.buttons()
|
||||
self._modifiers = pressEvent.modifiers()
|
||||
self._time = ptime.time()
|
||||
self._time = perf_counter()
|
||||
self.acceptedItem = None
|
||||
|
||||
def accept(self):
|
||||
|
@ -377,4 +378,4 @@ class HoverEvent(object):
|
|||
return self.__dragItems
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ This module exists to smooth out some of the differences between PySide and PyQt
|
|||
|
||||
import os, sys, re, time, subprocess, warnings
|
||||
|
||||
from ..python2_3 import asUnicode
|
||||
|
||||
PYSIDE = 'PySide'
|
||||
PYSIDE2 = 'PySide2'
|
||||
|
@ -73,7 +72,7 @@ class _StringIO(object):
|
|||
self.data.append(data)
|
||||
|
||||
def getvalue(self):
|
||||
return ''.join(map(asUnicode, self.data)).encode('utf8')
|
||||
return ''.join(map(str, self.data)).encode('utf8')
|
||||
|
||||
|
||||
def _loadUiType(uiFile):
|
||||
|
@ -152,7 +151,12 @@ if QT_LIB == PYQT5:
|
|||
_copy_attrs(PyQt5.QtGui, QtGui)
|
||||
_copy_attrs(PyQt5.QtWidgets, QtWidgets)
|
||||
|
||||
from PyQt5 import sip, uic
|
||||
try:
|
||||
from PyQt5 import sip
|
||||
except ImportError:
|
||||
# some Linux distros package it this way (e.g. Ubuntu)
|
||||
import sip
|
||||
from PyQt5 import uic
|
||||
|
||||
try:
|
||||
from PyQt5 import QtSvg
|
||||
|
@ -203,11 +207,9 @@ elif QT_LIB == PYSIDE2:
|
|||
except ImportError as err:
|
||||
QtTest = FailedImport(err)
|
||||
|
||||
import shiboken2
|
||||
isQObjectAlive = shiboken2.isValid
|
||||
import shiboken2 as shiboken
|
||||
import PySide2
|
||||
VERSION_INFO = 'PySide2 ' + PySide2.__version__ + ' Qt ' + QtCore.__version__
|
||||
|
||||
elif QT_LIB == PYSIDE6:
|
||||
import PySide6.QtCore, PySide6.QtGui, PySide6.QtWidgets
|
||||
_copy_attrs(PySide6.QtCore, QtCore)
|
||||
|
@ -227,8 +229,7 @@ elif QT_LIB == PYSIDE6:
|
|||
except ImportError as err:
|
||||
QtTest = FailedImport(err)
|
||||
|
||||
import shiboken6
|
||||
isQObjectAlive = shiboken6.isValid
|
||||
import shiboken6 as shiboken
|
||||
import PySide6
|
||||
VERSION_INFO = 'PySide6 ' + PySide6.__version__ + ' Qt ' + QtCore.__version__
|
||||
|
||||
|
@ -314,6 +315,7 @@ if QT_LIB in [PYQT6, PYSIDE6]:
|
|||
if QT_LIB in [PYSIDE2, PYSIDE6]:
|
||||
QtVersion = QtCore.__version__
|
||||
loadUiType = _loadUiType
|
||||
isQObjectAlive = shiboken.isValid
|
||||
|
||||
# PySide does not implement qWait
|
||||
if not isinstance(QtTest, FailedImport):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from time import perf_counter
|
||||
import weakref
|
||||
|
||||
from .Qt import QtCore
|
||||
from .ptime import time
|
||||
from . import ThreadsafeTimer
|
||||
from .functions import SignalBlock
|
||||
|
||||
|
@ -60,7 +60,7 @@ class SignalProxy(QtCore.QObject):
|
|||
self.timer.stop()
|
||||
self.timer.start(int(self.delay * 1000) + 1)
|
||||
else:
|
||||
now = time()
|
||||
now = perf_counter()
|
||||
if self.lastFlushTime is None:
|
||||
leakTime = 0
|
||||
else:
|
||||
|
@ -76,7 +76,7 @@ class SignalProxy(QtCore.QObject):
|
|||
return False
|
||||
args, self.args = self.args, None
|
||||
self.timer.stop()
|
||||
self.lastFlushTime = time()
|
||||
self.lastFlushTime = perf_counter()
|
||||
self.sigDelayed.emit(args)
|
||||
return True
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from .Qt import QtCore, QtGui
|
||||
from .Qt import QtCore
|
||||
|
||||
__all__ = ['ThreadsafeTimer']
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from .Qt import QtCore, QtGui
|
||||
from .Qt import QtGui
|
||||
from . import functions as fn
|
||||
from .Vector import Vector
|
||||
import numpy as np
|
||||
|
|
|
@ -8,9 +8,8 @@ This class addresses the problem of having to save and restore the state
|
|||
of a large group of widgets.
|
||||
"""
|
||||
|
||||
from .Qt import QtCore, QtGui, QT_LIB
|
||||
from .Qt import QtCore, QtGui
|
||||
import weakref, inspect
|
||||
from .python2_3 import asUnicode
|
||||
|
||||
|
||||
__all__ = ['WidgetGroup']
|
||||
|
@ -45,7 +44,7 @@ def comboState(w):
|
|||
except AttributeError:
|
||||
pass
|
||||
if data is None:
|
||||
return asUnicode(w.itemText(ind))
|
||||
return str(w.itemText(ind))
|
||||
else:
|
||||
return data
|
||||
|
||||
|
@ -280,4 +279,4 @@ class WidgetGroup(QtCore.QObject):
|
|||
#print "%s: Cached value %s != set value %s" % (name, str(self.cache[name]), str(v1))
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@ PyQtGraph - Scientific Graphics and GUI Library for Python
|
|||
www.pyqtgraph.org
|
||||
"""
|
||||
|
||||
__version__ = '0.12.2'
|
||||
__version__ = '0.12.3'
|
||||
|
||||
### import all the goodies and add some helper functions for easy CLI use
|
||||
|
||||
## 'Qt' is a local module; it is intended mainly to cover up the differences
|
||||
## between PyQt4 and PySide.
|
||||
## between PyQt and PySide.
|
||||
from .Qt import QtCore, QtGui, mkQApp
|
||||
from .Qt import exec_ as exec
|
||||
|
||||
|
@ -22,14 +22,6 @@ import numpy ## pyqtgraph requires numpy
|
|||
|
||||
import os, sys
|
||||
|
||||
## check python version
|
||||
## Allow anything >= 2.7
|
||||
if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] < 6):
|
||||
raise Exception("Pyqtgraph requires Python version 2.6 or greater (this is %d.%d)" % (sys.version_info[0], sys.version_info[1]))
|
||||
|
||||
## helpers for 2/3 compatibility
|
||||
from . import python2_3
|
||||
|
||||
## in general openGL is poorly supported with Qt+GraphicsView.
|
||||
## we only enable it where the performance benefit is critical.
|
||||
## Note this only applies to 2D graphics; 3D graphics always use OpenGL.
|
||||
|
@ -137,9 +129,6 @@ def renamePyc(startDir):
|
|||
os.rename(fileName, name2)
|
||||
|
||||
path = os.path.split(__file__)[0]
|
||||
if __version__ is None and not hasattr(sys, 'frozen') and sys.version_info[0] == 2: ## If we are frozen, there's a good chance we don't have the original .py files anymore.
|
||||
renamePyc(path)
|
||||
|
||||
|
||||
## Import almost everything to make it available from a single namespace
|
||||
## don't import the more complex systems--canvas, parametertree, flowchart, dockarea
|
||||
|
@ -282,7 +271,6 @@ from .ThreadsafeTimer import *
|
|||
|
||||
# indirect imports used within library
|
||||
from .GraphicsScene import GraphicsScene
|
||||
from .python2_3 import asUnicode
|
||||
from .util.cupy_helper import getCupy
|
||||
|
||||
# indirect imports known to be used outside of the library
|
||||
|
@ -360,9 +348,9 @@ def exit():
|
|||
This function does the following in an attempt to 'safely' terminate
|
||||
the process:
|
||||
|
||||
* Invoke atexit callbacks
|
||||
* Close all open file handles
|
||||
* os._exit()
|
||||
* Invoke atexit callbacks
|
||||
* Close all open file handles
|
||||
* os._exit()
|
||||
|
||||
Note: there is some potential for causing damage with this function if you
|
||||
are using objects that _require_ their destructors to be called (for example,
|
||||
|
|
|
@ -9,8 +9,6 @@ import importlib
|
|||
ui_template = importlib.import_module(
|
||||
f'.CanvasTemplate_{QT_LIB.lower()}', package=__package__)
|
||||
|
||||
import numpy as np
|
||||
from .. import debug
|
||||
import weakref
|
||||
import gc
|
||||
from .CanvasManager import CanvasManager
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from .Canvas import *
|
||||
from .CanvasItem import *
|
||||
from .CanvasItem import *
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import numpy as np
|
||||
from .Qt import QtGui, QtCore
|
||||
from .functions import mkColor, eq, colorDistance
|
||||
from .functions import mkColor, eq, colorDistance, clip_scalar, clip_array
|
||||
from os import path, listdir
|
||||
from collections.abc import Callable, Sequence
|
||||
import warnings
|
||||
|
@ -18,10 +19,10 @@ def listMaps(source=None):
|
|||
Parameters
|
||||
----------
|
||||
source: str, optional
|
||||
Color map source. If omitted, locally stored maps are listed. Otherwise
|
||||
Color map source. If omitted, locally stored maps are listed. Otherwise:
|
||||
|
||||
- 'matplotlib' lists maps that can be imported from Matplotlib
|
||||
- 'colorcet' lists maps that can be imported from ColorCET
|
||||
- 'matplotlib' lists maps that can be imported from Matplotlib
|
||||
- 'colorcet' lists maps that can be imported from ColorCET
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -33,7 +34,7 @@ def listMaps(source=None):
|
|||
files = listdir( pathname )
|
||||
list_of_maps = []
|
||||
for filename in files:
|
||||
if filename[-4:] == '.csv':
|
||||
if filename[-4:] == '.csv' or filename[-4:] == '.hex':
|
||||
list_of_maps.append(filename[:-4])
|
||||
return list_of_maps
|
||||
elif source.lower() == 'matplotlib':
|
||||
|
@ -68,10 +69,10 @@ def get(name, source=None, skipCache=False):
|
|||
be a path to a file in the local folder. See the files in the
|
||||
``pyqtgraph/colors/maps/`` folder for examples of the format.
|
||||
source: str, optional
|
||||
If omitted, a locally stored map is returned. Otherwise
|
||||
If omitted, a locally stored map is returned. Otherwise:
|
||||
|
||||
- 'matplotlib' imports a map defined by Matplotlib.
|
||||
- 'colorcet' imports a map defined by ColorCET.
|
||||
- 'matplotlib' imports a map defined by Matplotlib.
|
||||
- 'colorcet' imports a map defined by ColorCET.
|
||||
|
||||
skipCache: bool, optional
|
||||
If `skipCache=True`, the internal cache is skipped and a new
|
||||
|
@ -95,11 +96,11 @@ def _getFromFile(name):
|
|||
filename = path.join(dirname, 'colors/maps/'+filename)
|
||||
if not path.isfile( filename ): # try suffixes if file is not found:
|
||||
if path.isfile( filename+'.csv' ): filename += '.csv'
|
||||
elif path.isfile( filename+'.txt' ): filename += '.txt'
|
||||
elif path.isfile( filename+'.hex' ): filename += '.hex'
|
||||
with open(filename,'r') as fh:
|
||||
idx = 0
|
||||
color_list = []
|
||||
if filename[-4:].lower() != '.txt':
|
||||
if filename[-4:].lower() != '.hex':
|
||||
csv_mode = True
|
||||
else:
|
||||
csv_mode = False
|
||||
|
@ -122,18 +123,21 @@ def _getFromFile(name):
|
|||
elif len(hex_str) == 4: # parse as abbreviated RGBA
|
||||
hex_str = 2*hex_str[0] + 2*hex_str[1] + 2*hex_str[2] + 2*hex_str[3]
|
||||
if len(hex_str) < 6: continue # not enough information
|
||||
color_tuple = tuple( bytes.fromhex( hex_str ) )
|
||||
try:
|
||||
color_tuple = tuple( bytes.fromhex( hex_str ) )
|
||||
except ValueError as e:
|
||||
raise ValueError(f"failed to convert hexadecimal value '{hex_str}'.") from e
|
||||
color_list.append( color_tuple )
|
||||
idx += 1
|
||||
# end of line reading loop
|
||||
# end of open
|
||||
cm = ColorMap(
|
||||
cmap = ColorMap( name=name,
|
||||
pos=np.linspace(0.0, 1.0, len(color_list)),
|
||||
color=color_list) #, names=color_names)
|
||||
if cm is not None:
|
||||
cm.name = name
|
||||
_mapCache[name] = cm
|
||||
return cm
|
||||
if cmap is not None:
|
||||
cmap.name = name
|
||||
_mapCache[name] = cmap
|
||||
return cmap
|
||||
|
||||
def getFromMatplotlib(name):
|
||||
"""
|
||||
|
@ -146,7 +150,7 @@ def getFromMatplotlib(name):
|
|||
import matplotlib.pyplot as mpl_plt
|
||||
except ModuleNotFoundError:
|
||||
return None
|
||||
cm = None
|
||||
cmap = None
|
||||
col_map = mpl_plt.get_cmap(name)
|
||||
if hasattr(col_map, '_segmentdata'): # handle LinearSegmentedColormap
|
||||
data = col_map._segmentdata
|
||||
|
@ -164,21 +168,22 @@ def getFromMatplotlib(name):
|
|||
positions[idx2] = tup[0]
|
||||
comp_vals[idx2] = tup[1] # these are sorted in the raw data
|
||||
col_data[:,idx] = np.interp(col_data[:,3], positions, comp_vals)
|
||||
cm = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
|
||||
cmap = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
|
||||
# some color maps (gnuplot in particular) are defined by RGB component functions:
|
||||
elif ('red' in data) and isinstance(data['red'], Callable):
|
||||
col_data = np.zeros((64, 4))
|
||||
col_data[:,-1] = np.linspace(0., 1., 64)
|
||||
for idx, key in enumerate(['red','green','blue']):
|
||||
col_data[:,idx] = np.clip( data[key](col_data[:,-1]), 0, 1)
|
||||
cm = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
|
||||
cmap = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
|
||||
elif hasattr(col_map, 'colors'): # handle ListedColormap
|
||||
col_data = np.array(col_map.colors)
|
||||
cm = ColorMap(pos=np.linspace(0.0, 1.0, col_data.shape[0]), color=255*col_data[:,:3]+0.5 )
|
||||
if cm is not None:
|
||||
cm.name = name
|
||||
_mapCache[name] = cm
|
||||
return cm
|
||||
cmap = ColorMap( name=name,
|
||||
pos = np.linspace(0.0, 1.0, col_data.shape[0]), color=255*col_data[:,:3]+0.5 )
|
||||
if cmap is not None:
|
||||
cmap.name = name
|
||||
_mapCache[name] = cmap
|
||||
return cmap
|
||||
|
||||
def getFromColorcet(name):
|
||||
""" Generates a ColorMap object from a colorcet definition. Same as ``colormap.get(name, source='colorcet')``. """
|
||||
|
@ -191,20 +196,65 @@ def getFromColorcet(name):
|
|||
for hex_str in color_strings:
|
||||
if hex_str[0] != '#': continue
|
||||
if len(hex_str) != 7:
|
||||
raise ValueError('Invalid color string '+str(hex_str)+' in colorcet import.')
|
||||
raise ValueError(f"Invalid color string '{hex_str}' in colorcet import.")
|
||||
color_tuple = tuple( bytes.fromhex( hex_str[1:] ) )
|
||||
color_list.append( color_tuple )
|
||||
if len(color_list) == 0:
|
||||
return None
|
||||
cm = ColorMap(
|
||||
pos=np.linspace(0.0, 1.0, len(color_list)),
|
||||
cmap = ColorMap( name=name,
|
||||
pos=np.linspace(0.0, 1.0, len(color_list)),
|
||||
color=color_list) #, names=color_names)
|
||||
if cm is not None:
|
||||
cm.name = name
|
||||
_mapCache[name] = cm
|
||||
return cm
|
||||
if cmap is not None:
|
||||
cmap.name = name
|
||||
_mapCache[name] = cmap
|
||||
return cmap
|
||||
|
||||
def makeMonochrome(color='green'):
|
||||
def makeHslCycle( hue=0.0, saturation=1.0, lightness=0.5, steps=36 ):
|
||||
"""
|
||||
Returns a ColorMap object that traces a circular or spiraling path around the HSL color space.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hue : float or tuple of floats
|
||||
Starting point or (start, end) for hue. Values can lie outside the [0 to 1] range
|
||||
to realize multiple cycles. For a single value, one full hue cycle is generated.
|
||||
The default starting hue is 0.0 (red).
|
||||
saturation : float or tuple of floats, optional
|
||||
Saturation value for the colors in the cycle, in the range of [0 to 1].
|
||||
If a (start, end) tuple is given, saturation gradually changes between these values.
|
||||
The default saturation is 1.0.
|
||||
lightness : float or tuple of floats, optional
|
||||
Lightness value for the colors in the cycle, in the range of [0 to 1].
|
||||
If a (start, end) tuple is given, lightness gradually changes between these values.
|
||||
The default lightness is 1.0.
|
||||
steps: int, optional
|
||||
Number of steps in the cycle. Between these steps, the color map will interpolate in RGB space.
|
||||
The default number of steps is 36, generating a color map with 37 stops.
|
||||
"""
|
||||
if isinstance( hue, (tuple, list) ):
|
||||
hueA, hueB = hue
|
||||
else:
|
||||
hueA = hue
|
||||
hueB = hueA + 1.0
|
||||
if isinstance( saturation, (tuple, list) ):
|
||||
satA, satB = saturation
|
||||
else:
|
||||
satA = satB = saturation
|
||||
if isinstance( lightness, (tuple, list) ):
|
||||
lgtA, lgtB = lightness
|
||||
else:
|
||||
lgtA = lgtB = lightness
|
||||
hue_vals = np.linspace(hueA, hueB, num=steps+1)
|
||||
sat_vals = np.linspace(satA, satB, num=steps+1)
|
||||
lgt_vals = np.linspace(lgtA, lgtB, num=steps+1)
|
||||
color_list = []
|
||||
for hue, sat, lgt in zip( hue_vals, sat_vals, lgt_vals):
|
||||
qcol = QtGui.QColor.fromHslF( hue%1.0, sat, lgt )
|
||||
color_list.append( qcol )
|
||||
name = f'Hue {hueA:0.2f}-{hueB:0.2f}'
|
||||
return ColorMap( None, color_list, name=name )
|
||||
|
||||
def makeMonochrome(color='neutral'):
|
||||
"""
|
||||
Returns a ColorMap object with a dark to bright ramp and adjustable tint.
|
||||
|
||||
|
@ -257,11 +307,10 @@ def makeMonochrome(color='green'):
|
|||
h_val, s_val, l_min, l_max = color
|
||||
else:
|
||||
raise ValueError(f"Invalid color descriptor '{color}'")
|
||||
l_vals = np.linspace(l_min, l_max, num=10)
|
||||
l_vals = np.linspace(l_min, l_max, num=16)
|
||||
color_list = []
|
||||
for l_val in l_vals:
|
||||
qcol = QtGui.QColor()
|
||||
qcol.setHslF( h_val, s_val, l_val )
|
||||
qcol = QtGui.QColor.fromHslF( h_val, s_val, l_val )
|
||||
color_list.append( qcol )
|
||||
return ColorMap( None, color_list, name=name, linearize=True )
|
||||
|
||||
|
@ -282,7 +331,7 @@ def modulatedBarData(length=768, width=32):
|
|||
data = np.zeros( (length, width) )
|
||||
for idx in range(width):
|
||||
data[:,idx] = gradient + (idx/(width-1)) * modulation
|
||||
np.clip(data, 0.0, 1.0)
|
||||
clip_array(data, 0.0, 1.0, out=data)
|
||||
return data
|
||||
|
||||
class ColorMap(object):
|
||||
|
@ -389,8 +438,9 @@ class ColorMap(object):
|
|||
if mapping in [self.CLIP, self.REPEAT, self.DIVERGING, self.MIRROR]:
|
||||
self.mapping_mode = mapping # only allow defined values
|
||||
else:
|
||||
raise ValueError("Undefined mapping type '{:s}'".format(str(mapping)) )
|
||||
|
||||
raise ValueError(f"Undefined mapping type '{mapping}'")
|
||||
self.stopsCache = {}
|
||||
|
||||
def __str__(self):
|
||||
""" provide human-readable identifier """
|
||||
if self.name is None:
|
||||
|
@ -427,6 +477,73 @@ class ColorMap(object):
|
|||
self.pos = 1.0 - np.flip( self.pos )
|
||||
self.color = np.flip( self.color, axis=0 )
|
||||
self.stopsCache = {}
|
||||
|
||||
def getSubset(self, start, span):
|
||||
"""
|
||||
Returns a new ColorMap object that extracts the subset specified by 'start' and 'length'
|
||||
to the full 0.0 to 1.0 range. A negative length results in a color map that is reversed
|
||||
relative to the original.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : float (0.0 to 1.0)
|
||||
Starting value that defines the 0.0 value of the new color map.
|
||||
span : float (-1.0 to 1.0)
|
||||
span of the extracted region. The orignal color map will be trated as cyclical
|
||||
if the extracted interval exceeds the 0.0 to 1.0 range.
|
||||
"""
|
||||
pos, col = self.getStops( mode=ColorMap.FLOAT )
|
||||
start = clip_scalar(start, 0.0, 1.0)
|
||||
span = clip_scalar(span, -1.0, 1.0)
|
||||
|
||||
if span == 0.0:
|
||||
raise ValueError("'length' needs to be non-zero")
|
||||
stop = (start + span)
|
||||
if stop > 1.0 or stop < 0.0: stop = stop % 1.0
|
||||
# find indices *inside* range, start and end will be added by sampling later
|
||||
if span > 0:
|
||||
ref_pos = start # lowest position value at start
|
||||
idxA = np.searchsorted( pos, start, side='right' )
|
||||
idxB = np.searchsorted( pos, stop , side='left' ) # + 1 # right-side element of interval
|
||||
wraps = bool( stop < start ) # wraps around?
|
||||
else:
|
||||
ref_pos = stop # lowest position value at stop
|
||||
idxA = np.searchsorted( pos, stop , side='right')
|
||||
idxB = np.searchsorted( pos, start, side='left' ) # + 1 # right-side element of interval
|
||||
wraps = bool( stop > start ) # wraps around?
|
||||
|
||||
if wraps: # wraps around:
|
||||
length1 = (len(pos)-idxA) # before wrap
|
||||
length2 = idxB # after wrap
|
||||
new_length = length1 + length2 + 2 # combined; plus edge elements
|
||||
new_pos = np.zeros( new_length )
|
||||
new_col = np.zeros( (new_length, 4) )
|
||||
new_pos[ 1:length1+1] = (0 + pos[idxA:] - ref_pos) / span # starting point lie in 0 to 1 range
|
||||
new_pos[length1+1:-1] = (1 + pos[:idxB] - ref_pos) / span # end point wrapped to -1 to 0 range
|
||||
new_pos[length1] -= np.copysign(1e-6, span) # breaks degeneracy of shifted 0.0 and 1.0 values
|
||||
new_col[ 1:length1+1] = col[idxA:]
|
||||
new_col[length1+1:-1] = col[:idxB]
|
||||
else: # does not wrap around:
|
||||
new_length = (idxB - idxA) + 2 # two additional edge values will be added
|
||||
new_pos = np.zeros( new_length )
|
||||
new_col = np.zeros( (new_length, 4) )
|
||||
new_pos[1:-1] = (pos[idxA:idxB] - ref_pos) / span
|
||||
new_col[1:-1] = col[idxA:idxB]
|
||||
|
||||
if span < 0: # for reversed subsets, positions now progress 0 to -1 and need to be flipped
|
||||
new_pos += 1.0
|
||||
new_pos = np.flip( new_pos)
|
||||
new_col = np.flip( new_col, axis=0 )
|
||||
|
||||
new_pos[ 0] = 0.0
|
||||
new_col[ 0] = self.mapToFloat(start)
|
||||
new_pos[-1] = 1.0
|
||||
new_col[-1] = self.mapToFloat(stop)
|
||||
|
||||
cmap = ColorMap( pos=new_pos, color=255.*new_col )
|
||||
cmap.name = f"{self.name}[{start:.2f}({span:+.2f})]"
|
||||
return cmap
|
||||
|
||||
|
||||
def map(self, data, mode=BYTE):
|
||||
"""
|
||||
|
@ -443,9 +560,9 @@ class ColorMap(object):
|
|||
mode: str or int, optional
|
||||
Determines return format:
|
||||
|
||||
- `ColorMap.BYTE` or 'byte': Colors are returned as 0-255 unsigned bytes. (default)
|
||||
- `ColorMap.FLOAT` or 'float': Colors are returned as 0.0-1.0 floats.
|
||||
- `ColorMap.QCOLOR` or 'qcolor': Colors are returned as QColor objects.
|
||||
- `ColorMap.BYTE` or 'byte': Colors are returned as 0-255 unsigned bytes. (default)
|
||||
- `ColorMap.FLOAT` or 'float': Colors are returned as 0.0-1.0 floats.
|
||||
- `ColorMap.QCOLOR` or 'qcolor': Colors are returned as QColor objects.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -462,7 +579,7 @@ class ColorMap(object):
|
|||
mode = self.enumMap[mode.lower()]
|
||||
|
||||
if mode == self.QCOLOR:
|
||||
pos, color = self.getStops(self.BYTE)
|
||||
pos, color = self.getStops(self.FLOAT)
|
||||
else:
|
||||
pos, color = self.getStops(mode)
|
||||
|
||||
|
@ -487,9 +604,9 @@ class ColorMap(object):
|
|||
# Convert to QColor if requested
|
||||
if mode == self.QCOLOR:
|
||||
if np.isscalar(data):
|
||||
return QtGui.QColor(*interp)
|
||||
return QtGui.QColor.fromRgbF(*interp)
|
||||
else:
|
||||
return [QtGui.QColor(*x) for x in interp]
|
||||
return [QtGui.QColor.fromRgbF(*x.tolist()) for x in interp]
|
||||
else:
|
||||
return interp
|
||||
|
||||
|
@ -516,6 +633,9 @@ class ColorMap(object):
|
|||
|
||||
When no parameters are given for `p1` and `p2`, the gradient is mapped to the
|
||||
`y` coordinates 0 to 1, unless the color map is defined for a more limited range.
|
||||
|
||||
This is a somewhat expensive operation, and it is recommended to store and reuse the returned
|
||||
gradient instead of repeatedly regenerating it.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -531,8 +651,7 @@ class ColorMap(object):
|
|||
p2 = QtCore.QPointF(self.pos.max()-self.pos.min(),0)
|
||||
grad = QtGui.QLinearGradient(p1, p2)
|
||||
|
||||
pos, color = self.getStops(mode=self.BYTE)
|
||||
color = [QtGui.QColor(*x) for x in color]
|
||||
pos, color = self.getStops(mode=self.QCOLOR)
|
||||
if self.mapping_mode == self.MIRROR:
|
||||
pos_n = (1. - np.flip(pos)) / 2
|
||||
col_n = np.flip( color, axis=0 )
|
||||
|
@ -550,20 +669,22 @@ class ColorMap(object):
|
|||
Returns a QBrush painting with the color map applied over the selected span of plot values.
|
||||
When the mapping mode is set to `ColorMap.MIRROR`, the selected span includes the color map twice,
|
||||
first in reversed order and then normal.
|
||||
|
||||
It is recommended to store and reuse this gradient brush instead of regenerating it repeatedly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
span : tuple (min, max), default (0.0, 1.0)
|
||||
Span of data values covered by the gradient:
|
||||
|
||||
- Color map value 0.0 will appear at `min`,
|
||||
- Color map value 1.0 will appear at `max`.
|
||||
- Color map value 0.0 will appear at `min`,
|
||||
- Color map value 1.0 will appear at `max`.
|
||||
|
||||
orientation : str, default 'vertical'
|
||||
Orientiation of the gradient:
|
||||
|
||||
- 'vertical': `span` corresponds to the `y` coordinate.
|
||||
- 'horizontal': `span` corresponds to the `x` coordinate.
|
||||
- 'vertical': `span` corresponds to the `y` coordinate.
|
||||
- 'horizontal': `span` corresponds to the `x` coordinate.
|
||||
"""
|
||||
if orientation == 'vertical':
|
||||
grad = self.getGradient( p1=QtCore.QPointF(0.,span[0]), p2=QtCore.QPointF(0.,span[1]) )
|
||||
|
@ -576,20 +697,23 @@ class ColorMap(object):
|
|||
def getPen(self, span=(0.,1.), orientation='vertical', width=1.0):
|
||||
"""
|
||||
Returns a QPen that draws according to the color map based on vertical or horizontal position.
|
||||
|
||||
It is recommended to store and reuse this gradient pen instead of regenerating it repeatedly.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
span : tuple (min, max), default (0.0, 1.0)
|
||||
Span of the data values covered by the gradient:
|
||||
|
||||
- Color map value 0.0 will appear at `min`.
|
||||
- Color map value 1.0 will appear at `max`.
|
||||
- Color map value 0.0 will appear at `min`.
|
||||
- Color map value 1.0 will appear at `max`.
|
||||
|
||||
orientation : str, default 'vertical'
|
||||
Orientiation of the gradient:
|
||||
|
||||
- 'vertical' creates a vertical gradient, where `span` corresponds to the `y` coordinate.
|
||||
- 'horizontal' creates a horizontal gradient, where `span` correspnds to the `x` coordinate.
|
||||
- 'vertical' creates a vertical gradient, where `span` corresponds to the `y` coordinate.
|
||||
- 'horizontal' creates a horizontal gradient, where `span` correspnds to the `x` coordinate.
|
||||
|
||||
width : int or float
|
||||
Width of the pen in pixels on screen.
|
||||
|
@ -636,8 +760,10 @@ class ColorMap(object):
|
|||
color = color.astype(float) / 255.
|
||||
elif mode == self.QCOLOR:
|
||||
if color.dtype.kind == 'f':
|
||||
color = (color*255).astype(np.ubyte)
|
||||
color = [QtGui.QColor(*x) for x in color]
|
||||
factory = QtGui.QColor.fromRgbF
|
||||
else:
|
||||
factory = QtGui.QColor.fromRgb
|
||||
color = [factory(*x.tolist()) for x in color]
|
||||
self.stopsCache[mode] = (self.pos, color)
|
||||
return self.stopsCache[mode]
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
; PyQtGraph's "relaxed" plot color palette
|
||||
; This is the darker variant for plotting on a light background ("light mode")
|
||||
;
|
||||
#f97f10 ; orange
|
||||
#e5bb00 ; yellow
|
||||
#94ab00 ; grass
|
||||
#12a12a ; green
|
||||
#007c8c ; sea
|
||||
#0e56c2 ; blue
|
||||
#813be3 ; indigo
|
||||
#c01188 ; purple
|
||||
#e23512 ; red
|
||||
#f97f10 ; orange
|
|
@ -0,0 +1,13 @@
|
|||
; PyQtGraph's "relaxed" plot color palette
|
||||
; This is the brighter variant for plotting on a dark background ("dark mode")
|
||||
;
|
||||
#ff9d47 ; orange
|
||||
#f7e100 ; yellow
|
||||
#b3cf00 ; grass
|
||||
#1ec23a ; green
|
||||
#00a0b5 ; sea
|
||||
#1f78ff ; blue
|
||||
#a54dff ; indigo
|
||||
#e22ca8 ; purple
|
||||
#ff532b ; red
|
||||
#ff9d47 ; orange
|
|
@ -14,7 +14,6 @@ import numpy
|
|||
from collections import OrderedDict
|
||||
import tempfile
|
||||
from . import units
|
||||
from .python2_3 import asUnicode, basestring
|
||||
from .Qt import QtCore
|
||||
from .Point import Point
|
||||
from .colormap import ColorMap
|
||||
|
@ -40,11 +39,11 @@ class ParseError(Exception):
|
|||
|
||||
def writeConfigFile(data, fname):
|
||||
s = genString(data)
|
||||
with open(fname, 'w') as fd:
|
||||
with open(fname, 'wt') as fd:
|
||||
fd.write(s)
|
||||
|
||||
|
||||
def readConfigFile(fname):
|
||||
def readConfigFile(fname, **scope):
|
||||
#cwd = os.getcwd()
|
||||
global GLOBAL_PATH
|
||||
if GLOBAL_PATH is not None:
|
||||
|
@ -53,14 +52,29 @@ def readConfigFile(fname):
|
|||
fname = fname2
|
||||
|
||||
GLOBAL_PATH = os.path.dirname(os.path.abspath(fname))
|
||||
|
||||
local = {**scope, **units.allUnits}
|
||||
local['OrderedDict'] = OrderedDict
|
||||
local['readConfigFile'] = readConfigFile
|
||||
local['Point'] = Point
|
||||
local['QtCore'] = QtCore
|
||||
local['ColorMap'] = ColorMap
|
||||
local['datetime'] = datetime
|
||||
# Needed for reconstructing numpy arrays
|
||||
local['array'] = numpy.array
|
||||
for dtype in ['int8', 'uint8',
|
||||
'int16', 'uint16', 'float16',
|
||||
'int32', 'uint32', 'float32',
|
||||
'int64', 'uint64', 'float64']:
|
||||
local[dtype] = getattr(numpy, dtype)
|
||||
|
||||
try:
|
||||
#os.chdir(newDir) ## bad.
|
||||
with open(fname) as fd:
|
||||
s = asUnicode(fd.read())
|
||||
with open(fname, "rt") as fd:
|
||||
s = fd.read()
|
||||
s = s.replace("\r\n", "\n")
|
||||
s = s.replace("\r", "\n")
|
||||
data = parseString(s)[1]
|
||||
data = parseString(s, **local)[1]
|
||||
except ParseError:
|
||||
sys.exc_info()[1].fileName = fname
|
||||
raise
|
||||
|
@ -73,7 +87,7 @@ def readConfigFile(fname):
|
|||
|
||||
def appendConfigFile(data, fname):
|
||||
s = genString(data)
|
||||
with open(fname, 'a') as fd:
|
||||
with open(fname, 'at') as fd:
|
||||
fd.write(s)
|
||||
|
||||
|
||||
|
@ -94,10 +108,10 @@ def genString(data, indent=''):
|
|||
s += indent + sk + ': ' + repr(data[k]).replace("\n", "\\\n") + '\n'
|
||||
return s
|
||||
|
||||
def parseString(lines, start=0):
|
||||
def parseString(lines, start=0, **scope):
|
||||
|
||||
data = OrderedDict()
|
||||
if isinstance(lines, basestring):
|
||||
if isinstance(lines, str):
|
||||
lines = lines.replace("\\\n", "")
|
||||
lines = lines.split('\n')
|
||||
lines = [l for l in lines if re.search(r'\S', l) and not re.match(r'\s*#', l)] ## remove empty lines
|
||||
|
@ -136,33 +150,19 @@ def parseString(lines, start=0):
|
|||
v = v.strip()
|
||||
|
||||
## set up local variables to use for eval
|
||||
local = units.allUnits.copy()
|
||||
local['OrderedDict'] = OrderedDict
|
||||
local['readConfigFile'] = readConfigFile
|
||||
local['Point'] = Point
|
||||
local['QtCore'] = QtCore
|
||||
local['ColorMap'] = ColorMap
|
||||
local['datetime'] = datetime
|
||||
# Needed for reconstructing numpy arrays
|
||||
local['array'] = numpy.array
|
||||
for dtype in ['int8', 'uint8',
|
||||
'int16', 'uint16', 'float16',
|
||||
'int32', 'uint32', 'float32',
|
||||
'int64', 'uint64', 'float64']:
|
||||
local[dtype] = getattr(numpy, dtype)
|
||||
|
||||
if len(k) < 1:
|
||||
raise ParseError('Missing name preceding colon', ln+1, l)
|
||||
if k[0] == '(' and k[-1] == ')': ## If the key looks like a tuple, try evaluating it.
|
||||
try:
|
||||
k1 = eval(k, local)
|
||||
k1 = eval(k, scope)
|
||||
if type(k1) is tuple:
|
||||
k = k1
|
||||
except:
|
||||
# If tuple conversion fails, keep the string
|
||||
pass
|
||||
if re.search(r'\S', v) and v[0] != '#': ## eval the value
|
||||
try:
|
||||
val = eval(v, local)
|
||||
val = eval(v, scope)
|
||||
except:
|
||||
ex = sys.exc_info()[1]
|
||||
raise ParseError("Error evaluating expression '%s': [%s: %s]" % (v, ex.__class__.__name__, str(ex)), (ln+1), l)
|
||||
|
@ -172,7 +172,7 @@ def parseString(lines, start=0):
|
|||
val = {}
|
||||
else:
|
||||
#print "Going deeper..", ln+1
|
||||
(ln, val) = parseString(lines, start=ln+1)
|
||||
(ln, val) = parseString(lines, start=ln+1, **scope)
|
||||
data[k] = val
|
||||
#print k, repr(val)
|
||||
except ParseError:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from ..Qt import QtCore, QtGui
|
||||
from ..python2_3 import asUnicode
|
||||
|
||||
class CmdInput(QtGui.QLineEdit):
|
||||
|
||||
|
@ -25,10 +25,10 @@ class CmdInput(QtGui.QLineEdit):
|
|||
self.execCmd()
|
||||
else:
|
||||
super().keyPressEvent(ev)
|
||||
self.history[0] = asUnicode(self.text())
|
||||
self.history[0] = self.text()
|
||||
|
||||
def execCmd(self):
|
||||
cmd = asUnicode(self.text())
|
||||
cmd = self.text()
|
||||
if len(self.history) == 1 or cmd != self.history[1]:
|
||||
self.history.insert(1, cmd)
|
||||
self.history[0] = ""
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import sys, re, os, time, traceback, subprocess
|
||||
import sys, re, traceback, subprocess
|
||||
import pickle
|
||||
|
||||
from ..Qt import QtCore, QtGui, QT_LIB
|
||||
from ..python2_3 import basestring
|
||||
from .. import exceptionHandling as exceptionHandling
|
||||
from .. import getConfigOption
|
||||
from ..functions import SignalBlock
|
||||
|
@ -17,18 +16,18 @@ class ConsoleWidget(QtGui.QWidget):
|
|||
Widget displaying console output and accepting command input.
|
||||
Implements:
|
||||
|
||||
- eval python expressions / exec python statements
|
||||
- storable history of commands
|
||||
- exception handling allowing commands to be interpreted in the context of any level in the exception stack frame
|
||||
- eval python expressions / exec python statements
|
||||
- storable history of commands
|
||||
- exception handling allowing commands to be interpreted in the context of any level in the exception stack frame
|
||||
|
||||
Why not just use python in an interactive shell (or ipython) ? There are a few reasons:
|
||||
|
||||
- pyside does not yet allow Qt event processing and interactive shell at the same time
|
||||
- on some systems, typing in the console _blocks_ the qt event loop until the user presses enter. This can
|
||||
be baffling and frustrating to users since it would appear the program has frozen.
|
||||
- some terminals (eg windows cmd.exe) have notoriously unfriendly interfaces
|
||||
- ability to add extra features like exception stack introspection
|
||||
- ability to have multiple interactive prompts, including for spawned sub-processes
|
||||
- pyside does not yet allow Qt event processing and interactive shell at the same time
|
||||
- on some systems, typing in the console _blocks_ the qt event loop until the user presses enter. This can
|
||||
be baffling and frustrating to users since it would appear the program has frozen.
|
||||
- some terminals (eg windows cmd.exe) have notoriously unfriendly interfaces
|
||||
- ability to add extra features like exception stack introspection
|
||||
- ability to have multiple interactive prompts, including for spawned sub-processes
|
||||
"""
|
||||
_threadException = QtCore.Signal(object)
|
||||
|
||||
|
@ -454,7 +453,7 @@ class ConsoleWidget(QtGui.QWidget):
|
|||
if filterStr != '':
|
||||
if isinstance(exc, Exception):
|
||||
msg = traceback.format_exception_only(type(exc), exc)
|
||||
elif isinstance(exc, basestring):
|
||||
elif isinstance(exc, str):
|
||||
msg = exc
|
||||
else:
|
||||
msg = repr(exc)
|
||||
|
|
|
@ -1 +1 @@
|
|||
from .Console import ConsoleWidget
|
||||
from .Console import ConsoleWidget
|
||||
|
|
|
@ -9,8 +9,9 @@ Distributed under MIT/X11 license. See license.txt for more information.
|
|||
from __future__ import print_function
|
||||
|
||||
import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile, threading
|
||||
import contextlib
|
||||
import warnings
|
||||
from . import ptime
|
||||
from time import perf_counter
|
||||
from numpy import ndarray
|
||||
from .Qt import QtCore, QT_LIB
|
||||
from .util import cprint
|
||||
|
@ -21,6 +22,17 @@ if sys.version.startswith("3.8") and QT_LIB == "PySide2":
|
|||
from .util.mutex import Mutex
|
||||
|
||||
|
||||
# credit to Wolph: https://stackoverflow.com/a/17603000
|
||||
@contextlib.contextmanager
|
||||
def open_maybe_console(filename=None):
|
||||
fh = sys.stdout if filename is None else open(filename, "w", encoding='utf-8')
|
||||
try:
|
||||
yield fh
|
||||
finally:
|
||||
if fh is not sys.stdout:
|
||||
fh.close()
|
||||
|
||||
|
||||
__ftraceDepth = 0
|
||||
def ftrace(func):
|
||||
"""Decorator used for marking the beginning and end of function calls.
|
||||
|
@ -167,13 +179,15 @@ def listObjs(regex='Q', typ=None):
|
|||
|
||||
|
||||
|
||||
def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ignore=None):
|
||||
def findRefPath(startObj, endObj, maxLen=8, restart=True, seen=None, path=None, ignore=None):
|
||||
"""Determine all paths of object references from startObj to endObj"""
|
||||
refs = []
|
||||
if path is None:
|
||||
path = [endObj]
|
||||
if ignore is None:
|
||||
ignore = {}
|
||||
if seen is None:
|
||||
seen = {}
|
||||
ignore[id(sys._getframe())] = None
|
||||
ignore[id(path)] = None
|
||||
ignore[id(seen)] = None
|
||||
|
@ -529,7 +543,7 @@ class Profiler(object):
|
|||
obj._delayed = delayed
|
||||
obj._markCount = 0
|
||||
obj._finished = False
|
||||
obj._firstTime = obj._lastTime = ptime.time()
|
||||
obj._firstTime = obj._lastTime = perf_counter()
|
||||
obj._newMsg("> Entering " + obj._name)
|
||||
return obj
|
||||
|
||||
|
@ -541,7 +555,7 @@ class Profiler(object):
|
|||
if msg is None:
|
||||
msg = str(self._markCount)
|
||||
self._markCount += 1
|
||||
newTime = ptime.time()
|
||||
newTime = perf_counter()
|
||||
self._newMsg(" %s: %0.4f ms",
|
||||
msg, (newTime - self._lastTime) * 1000)
|
||||
self._lastTime = newTime
|
||||
|
@ -569,7 +583,7 @@ class Profiler(object):
|
|||
if msg is not None:
|
||||
self(msg)
|
||||
self._newMsg("< Exiting %s, total time: %0.4f ms",
|
||||
self._name, (ptime.time() - self._firstTime) * 1000)
|
||||
self._name, (perf_counter() - self._firstTime) * 1000)
|
||||
type(self)._depth -= 1
|
||||
if self._depth < 1:
|
||||
self.flush()
|
||||
|
@ -582,6 +596,7 @@ class Profiler(object):
|
|||
|
||||
def profile(code, name='profile_run', sort='cumulative', num=30):
|
||||
"""Common-use for cProfile"""
|
||||
import pstats
|
||||
cProfile.run(code, name)
|
||||
stats = pstats.Stats(name)
|
||||
stats.sort_stats(sort)
|
||||
|
@ -800,7 +815,7 @@ class ObjTracker(object):
|
|||
continue
|
||||
|
||||
try:
|
||||
ref = weakref.ref(obj)
|
||||
ref = weakref.ref(o)
|
||||
except:
|
||||
ref = None
|
||||
refs[oid] = ref
|
||||
|
@ -1132,10 +1147,11 @@ class ThreadTrace(object):
|
|||
Used to debug freezing by starting a new thread that reports on the
|
||||
location of other threads periodically.
|
||||
"""
|
||||
def __init__(self, interval=10.0):
|
||||
def __init__(self, interval=10.0, logFile=None):
|
||||
self.interval = interval
|
||||
self.lock = Mutex()
|
||||
self._stop = False
|
||||
self.logFile = logFile
|
||||
self.start()
|
||||
|
||||
def stop(self):
|
||||
|
@ -1151,35 +1167,41 @@ class ThreadTrace(object):
|
|||
self.thread.start()
|
||||
|
||||
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
|
||||
iter = 0
|
||||
with open_maybe_console(self.logFile) as printFile:
|
||||
while True:
|
||||
with self.lock:
|
||||
if self._stop is True:
|
||||
return
|
||||
|
||||
# try to determine a thread name
|
||||
try:
|
||||
name = threading._active.get(id, None)
|
||||
except:
|
||||
name = None
|
||||
if name is None:
|
||||
printFile.write(f"\n============= THREAD FRAMES {iter}: ================\n")
|
||||
for id, frame in sys._current_frames().items():
|
||||
if id == threading.current_thread().ident:
|
||||
continue
|
||||
|
||||
# try to determine a thread name
|
||||
try:
|
||||
# QThread._names must be manually set by thread creators.
|
||||
name = QtCore.QThread._names.get(id)
|
||||
name = threading._active.get(id, None)
|
||||
except:
|
||||
name = None
|
||||
if name is None:
|
||||
name = "???"
|
||||
if name is None:
|
||||
try:
|
||||
# QThread._names must be manually set by thread creators.
|
||||
name = QtCore.QThread._names.get(id)
|
||||
except:
|
||||
name = None
|
||||
if name is None:
|
||||
name = "???"
|
||||
|
||||
print("<< thread %d \"%s\" >>" % (id, name))
|
||||
traceback.print_stack(frame)
|
||||
print("===============================================\n")
|
||||
|
||||
time.sleep(self.interval)
|
||||
printFile.write("<< thread %d \"%s\" >>\n" % (id, name))
|
||||
tb = str(''.join(traceback.format_stack(frame)))
|
||||
printFile.write(tb)
|
||||
printFile.write("\n")
|
||||
printFile.write("===============================================\n\n")
|
||||
printFile.flush()
|
||||
|
||||
iter += 1
|
||||
time.sleep(self.interval)
|
||||
|
||||
|
||||
class ThreadColor(object):
|
||||
|
@ -1229,4 +1251,3 @@ def enableFaulthandler():
|
|||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from ..Qt import QtCore, QtGui, QtWidgets
|
||||
import weakref
|
||||
from .Dock import Dock
|
||||
|
||||
|
||||
class Container(object):
|
||||
#sigStretchChanged = QtCore.Signal() ## can't do this here; not a QObject.
|
||||
|
@ -245,7 +247,7 @@ class TContainer(Container, QtGui.QWidget):
|
|||
|
||||
|
||||
def _insertItem(self, item, index):
|
||||
if not isinstance(item, Dock.Dock):
|
||||
if not isinstance(item, Dock):
|
||||
raise Exception("Tab containers may hold only docks, not other containers.")
|
||||
self.stack.insertWidget(index, item)
|
||||
self.hTabLayout.insertWidget(index, item.label)
|
||||
|
@ -288,5 +290,3 @@ class TContainer(Container, QtGui.QWidget):
|
|||
x = max(x, wx)
|
||||
y = max(y, wy)
|
||||
self.setStretch(x, y)
|
||||
|
||||
from . import Dock
|
||||
|
|
|
@ -3,7 +3,6 @@ from ..Qt import QtCore, QtGui
|
|||
|
||||
from .DockDrop import *
|
||||
from ..widgets.VerticalLabel import VerticalLabel
|
||||
from ..python2_3 import asUnicode
|
||||
|
||||
|
||||
class Dock(QtGui.QWidget, DockDrop):
|
||||
|
@ -125,7 +124,7 @@ class Dock(QtGui.QWidget, DockDrop):
|
|||
"""
|
||||
Gets the text displayed in the title bar for this dock.
|
||||
"""
|
||||
return asUnicode(self.label.text())
|
||||
return self.label.text()
|
||||
|
||||
def setTitle(self, text):
|
||||
"""
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import weakref
|
||||
from ..Qt import QtCore, QtGui
|
||||
from ..Qt import QtGui
|
||||
from .Container import *
|
||||
from .DockDrop import *
|
||||
from .Dock import Dock
|
||||
from .. import debug as debug
|
||||
from ..python2_3 import basestring
|
||||
|
||||
|
||||
class DockArea(Container, QtGui.QWidget, DockDrop):
|
||||
|
@ -62,7 +60,7 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
|||
container = self.topContainer
|
||||
neighbor = None
|
||||
else:
|
||||
if isinstance(relativeTo, basestring):
|
||||
if isinstance(relativeTo, str):
|
||||
relativeTo = self.docks[relativeTo]
|
||||
container = self.getContainer(relativeTo)
|
||||
if container is None:
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
from .DockArea import DockArea
|
||||
from .Dock import Dock
|
||||
from .Dock import Dock
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from ..Qt import QtGui, QtCore
|
||||
from ..Qt import QtCore
|
||||
from .Exporter import Exporter
|
||||
from ..parametertree import Parameter
|
||||
from .. import PlotItem
|
||||
from ..python2_3 import asUnicode
|
||||
|
||||
translate = QtCore.QCoreApplication.translate
|
||||
|
||||
|
@ -16,9 +15,9 @@ class CSVExporter(Exporter):
|
|||
def __init__(self, item):
|
||||
Exporter.__init__(self, item)
|
||||
self.params = Parameter(name='params', type='group', children=[
|
||||
{'name': 'separator', 'title': translate("Exporter", 'separator'), 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']},
|
||||
{'name': 'separator', 'title': translate("Exporter", 'separator'), 'type': 'list', 'value': 'comma', 'limits': ['comma', 'tab']},
|
||||
{'name': 'precision', 'title': translate("Exporter", 'precision'), 'type': 'int', 'value': 10, 'limits': [0, None]},
|
||||
{'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']}
|
||||
{'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list', 'limits': ['(x,y) per plot', '(x,y,y,y) for all plots']}
|
||||
])
|
||||
|
||||
def parameters(self):
|
||||
|
@ -60,7 +59,7 @@ class CSVExporter(Exporter):
|
|||
sep = '\t'
|
||||
|
||||
with open(fileName, 'w') as fd:
|
||||
fd.write(sep.join(map(asUnicode, header)) + '\n')
|
||||
fd.write(sep.join(map(str, header)) + '\n')
|
||||
i = 0
|
||||
numFormat = '%%0.%dg' % self.params['precision']
|
||||
numRows = max([len(d[0]) for d in data])
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from ..widgets.FileDialog import FileDialog
|
||||
from ..Qt import QtGui, QtCore, QtSvg
|
||||
from ..python2_3 import asUnicode, basestring
|
||||
from ..Qt import QtGui, QtCore
|
||||
from ..GraphicsScene import GraphicsScene
|
||||
import os, re
|
||||
LastExportDirectory = None
|
||||
|
@ -48,7 +48,7 @@ class Exporter(object):
|
|||
self.fileDialog.setFileMode(QtGui.QFileDialog.FileMode.AnyFile)
|
||||
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptMode.AcceptSave)
|
||||
if filter is not None:
|
||||
if isinstance(filter, basestring):
|
||||
if isinstance(filter, str):
|
||||
self.fileDialog.setNameFilter(filter)
|
||||
elif isinstance(filter, list):
|
||||
self.fileDialog.setNameFilters(filter)
|
||||
|
@ -62,13 +62,12 @@ class Exporter(object):
|
|||
return
|
||||
|
||||
def fileSaveFinished(self, fileName):
|
||||
fileName = asUnicode(fileName)
|
||||
global LastExportDirectory
|
||||
LastExportDirectory = os.path.split(fileName)[0]
|
||||
|
||||
## If file name does not match selected extension, append it now
|
||||
ext = os.path.splitext(fileName)[1].lower().lstrip('.')
|
||||
selectedExt = re.search(r'\*\.(\w+)\b', asUnicode(self.fileDialog.selectedNameFilter()))
|
||||
selectedExt = re.search(r'\*\.(\w+)\b', self.fileDialog.selectedNameFilter())
|
||||
if selectedExt is not None:
|
||||
selectedExt = selectedExt.groups()[0].lower()
|
||||
if ext != selectedExt:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from ..Qt import QtGui, QtCore
|
||||
# -*- coding: utf-8 -*-
|
||||
from ..Qt import QtCore
|
||||
from .Exporter import Exporter
|
||||
from ..parametertree import Parameter
|
||||
from .. import PlotItem
|
||||
|
@ -25,7 +26,7 @@ class HDF5Exporter(Exporter):
|
|||
self.params = Parameter(name='params', type='group', children=[
|
||||
{'name': 'Name', 'title': translate("Exporter", 'Name'), 'type': 'str', 'value': 'Export', },
|
||||
{'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list',
|
||||
'values': ['(x,y) per plot', '(x,y,y,y) for all plots']},
|
||||
'limits': ['(x,y) per plot', '(x,y,y,y) for all plots']},
|
||||
])
|
||||
|
||||
def parameters(self):
|
||||
|
|
|
@ -98,7 +98,7 @@ class ImageExporter(Exporter):
|
|||
painter.end()
|
||||
|
||||
if self.params['invertValue']:
|
||||
bg = fn.qimage_to_ndarray(self.png)
|
||||
bg = fn.ndarray_from_qimage(self.png)
|
||||
if sys.byteorder == 'little':
|
||||
cv = slice(0, 3)
|
||||
else:
|
||||
|
|
|
@ -95,19 +95,19 @@ class MatplotlibExporter(Exporter):
|
|||
linestyle = ''
|
||||
else:
|
||||
linestyle = '-'
|
||||
color = tuple([c/255. for c in fn.colorTuple(pen.color())])
|
||||
color = pen.color().getRgbF()
|
||||
symbol = opts['symbol']
|
||||
if symbol == 't':
|
||||
symbol = '^'
|
||||
symbolPen = fn.mkPen(opts['symbolPen'])
|
||||
symbolBrush = fn.mkBrush(opts['symbolBrush'])
|
||||
markeredgecolor = tuple([c/255. for c in fn.colorTuple(symbolPen.color())])
|
||||
markerfacecolor = tuple([c/255. for c in fn.colorTuple(symbolBrush.color())])
|
||||
markeredgecolor = symbolPen.color().getRgbF()
|
||||
markerfacecolor = symbolBrush.color().getRgbF()
|
||||
markersize = opts['symbolSize']
|
||||
|
||||
if opts['fillLevel'] is not None and opts['fillBrush'] is not None:
|
||||
fillBrush = fn.mkBrush(opts['fillBrush'])
|
||||
fillcolor = tuple([c/255. for c in fn.colorTuple(fillBrush.color())])
|
||||
fillcolor = fillBrush.color().getRgbF()
|
||||
ax.fill_between(x=x, y1=y, y2=opts['fillLevel'], facecolor=fillcolor)
|
||||
|
||||
pl = ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from .Exporter import Exporter
|
||||
from ..parametertree import Parameter
|
||||
from ..Qt import QtGui, QtCore, QtSvg
|
||||
import re
|
||||
from ..Qt import QtGui, QtCore
|
||||
|
||||
translate = QtCore.QCoreApplication.translate
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from .Exporter import Exporter
|
||||
from ..python2_3 import asUnicode
|
||||
from ..parametertree import Parameter
|
||||
from ..Qt import QtGui, QtCore, QtSvg, QT_LIB
|
||||
from ..Qt import QtGui, QtCore, QtSvg
|
||||
from .. import debug
|
||||
from .. import functions as fn
|
||||
import re
|
||||
|
@ -78,7 +78,7 @@ class SVGExporter(Exporter):
|
|||
QtGui.QApplication.clipboard().setMimeData(md)
|
||||
else:
|
||||
with open(fileName, 'wb') as fh:
|
||||
fh.write(asUnicode(xml).encode('utf-8'))
|
||||
fh.write(str(xml).encode('utf-8'))
|
||||
|
||||
# Includes space for extra attributes
|
||||
xmlHeader = """\
|
||||
|
@ -119,7 +119,7 @@ def generateSvg(item, options={}):
|
|||
defsXml += "</defs>\n"
|
||||
svgAttributes = ' viewBox ="0 0 %f %f"' % (options["width"], options["height"])
|
||||
c = options['background']
|
||||
backgroundtag = '<rect width="100%%" height="100%%" style="fill:rgba(%f, %f, %f, %d)" />\n' % (c.red(), c.blue(), c.green(), c.alpha()/255.0)
|
||||
backgroundtag = '<rect width="100%%" height="100%%" style="fill:rgba(%d, %d, %d, %f)" />\n' % (c.red(), c.green(), c.blue(), c.alphaF())
|
||||
return (xmlHeader % svgAttributes) + backgroundtag + defsXml + node.toprettyxml(indent=' ') + "\n</svg>\n"
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ from .. import configfile as configfile
|
|||
from .. import dockarea as dockarea
|
||||
from . import FlowchartGraphicsView
|
||||
from .. import functions as fn
|
||||
from ..python2_3 import asUnicode
|
||||
|
||||
def strDict(d):
|
||||
return dict([(str(k), v) for k, v in d.items()])
|
||||
|
@ -512,7 +511,6 @@ class Flowchart(Node):
|
|||
self.fileDialog.fileSelected.connect(self.loadFile)
|
||||
return
|
||||
## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs..
|
||||
fileName = asUnicode(fileName)
|
||||
state = configfile.readConfigFile(fileName)
|
||||
self.restoreState(state, clear=True)
|
||||
self.viewBox.autoRange()
|
||||
|
@ -532,7 +530,6 @@ class Flowchart(Node):
|
|||
self.fileDialog.show()
|
||||
self.fileDialog.fileSelected.connect(self.saveFile)
|
||||
return
|
||||
fileName = asUnicode(fileName)
|
||||
configfile.writeConfigFile(self.saveState(), fileName)
|
||||
self.sigFileSaved.emit(fileName)
|
||||
|
||||
|
@ -653,7 +650,7 @@ class FlowchartCtrlWidget(QtGui.QWidget):
|
|||
#self.setCurrentFile(newFile)
|
||||
|
||||
def fileSaved(self, fileName):
|
||||
self.setCurrentFile(asUnicode(fileName))
|
||||
self.setCurrentFile(fileName)
|
||||
self.ui.saveBtn.success("Saved.")
|
||||
|
||||
def saveClicked(self):
|
||||
|
@ -682,7 +679,7 @@ class FlowchartCtrlWidget(QtGui.QWidget):
|
|||
#self.setCurrentFile(newFile)
|
||||
|
||||
def setCurrentFile(self, fileName):
|
||||
self.currentFileName = asUnicode(fileName)
|
||||
self.currentFileName = fileName
|
||||
if fileName is None:
|
||||
self.ui.fileNameLabel.setText("<b>[ new ]</b>")
|
||||
else:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from ..Qt import QtGui, QtCore
|
||||
from ..widgets.GraphicsView import GraphicsView
|
||||
from ..GraphicsScene import GraphicsScene
|
||||
from ..graphicsItems.ViewBox import ViewBox
|
||||
|
||||
translate = QtCore.QCoreApplication.translate
|
||||
|
|
|
@ -5,7 +5,6 @@ from .. import functions as fn
|
|||
from .Terminal import *
|
||||
from collections import OrderedDict
|
||||
from ..debug import *
|
||||
import numpy as np
|
||||
import warnings
|
||||
|
||||
translate = QtCore.QCoreApplication.translate
|
||||
|
@ -18,7 +17,7 @@ class Node(QtCore.QObject):
|
|||
Node represents the basic processing unit of a flowchart.
|
||||
A Node subclass implements at least:
|
||||
|
||||
1) A list of input / ouptut terminals and their properties
|
||||
1) A list of input / output terminals and their properties
|
||||
2) a process() function which takes the names of input terminals as keyword arguments and returns a dict with the names of output terminals as keys.
|
||||
|
||||
A flowchart thus consists of multiple instances of Node subclasses, each of which is connected
|
||||
|
@ -505,7 +504,7 @@ class NodeGraphicsItem(GraphicsObject):
|
|||
#GraphicsObject.setZValue(self, z)
|
||||
|
||||
def labelChanged(self):
|
||||
newName = str(self.nameItem.toPlainText())
|
||||
newName = self.nameItem.toPlainText()
|
||||
if newName != self.node.name():
|
||||
self.node.rename(newName)
|
||||
|
||||
|
|
|
@ -311,7 +311,7 @@ class TerminalGraphicsItem(GraphicsObject):
|
|||
self.menu = None
|
||||
|
||||
def labelChanged(self):
|
||||
newName = str(self.label.toPlainText())
|
||||
newName = self.label.toPlainText()
|
||||
if newName != self.term.name():
|
||||
self.term.rename(newName)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from .Flowchart import *
|
||||
|
||||
from .library import getNodeType, registerNodeType, getNodeTree
|
||||
from .library import getNodeType, registerNodeType, getNodeTree
|
||||
|
|
|
@ -4,8 +4,6 @@ from ...Qt import QtGui, QtCore, QtWidgets
|
|||
import numpy as np
|
||||
import sys
|
||||
from .common import *
|
||||
from ...SRTTransform import SRTTransform
|
||||
from ...Point import Point
|
||||
from ...widgets.TreeWidget import TreeWidget
|
||||
from ...graphicsItems.LinearRegionItem import LinearRegionItem
|
||||
|
||||
|
@ -179,7 +177,7 @@ class TextEdit(QtWidgets.QTextEdit):
|
|||
self.lastText = None
|
||||
|
||||
def focusOutEvent(self, ev):
|
||||
text = str(self.toPlainText())
|
||||
text = self.toPlainText()
|
||||
if text != self.lastText:
|
||||
self.lastText = text
|
||||
self.on_update()
|
||||
|
@ -236,26 +234,23 @@ class EvalNode(Node):
|
|||
l.update(args)
|
||||
## try eval first, then exec
|
||||
try:
|
||||
text = str(self.text.toPlainText()).replace('\n', ' ')
|
||||
text = self.text.toPlainText().replace('\n', ' ')
|
||||
output = eval(text, globals(), l)
|
||||
except SyntaxError:
|
||||
fn = "def fn(**args):\n"
|
||||
run = "\noutput=fn(**args)\n"
|
||||
text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run
|
||||
if sys.version_info.major == 2:
|
||||
exec(text)
|
||||
elif sys.version_info.major == 3:
|
||||
ldict = locals()
|
||||
exec(text, globals(), ldict)
|
||||
output = ldict['output']
|
||||
text = fn + "\n".join([" "+l for l in self.text.toPlainText().split('\n')]) + run
|
||||
ldict = locals()
|
||||
exec(text, globals(), ldict)
|
||||
output = ldict['output']
|
||||
except:
|
||||
print("Error processing node: %s" % self.name())
|
||||
print(f"Error processing node: {self.name()}")
|
||||
raise
|
||||
return output
|
||||
|
||||
def saveState(self):
|
||||
state = Node.saveState(self)
|
||||
state['text'] = str(self.text.toPlainText())
|
||||
state['text'] = self.text.toPlainText()
|
||||
#state['terminals'] = self.saveTerminals()
|
||||
return state
|
||||
|
||||
|
@ -481,4 +476,3 @@ class AsType(CtrlNode):
|
|||
def processData(self, data):
|
||||
s = self.stateGroup.state()
|
||||
return data.astype(s['dtype'])
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from ..Node import Node
|
||||
import weakref
|
||||
from ...Qt import QtCore, QtGui
|
||||
from ...graphicsItems.ScatterPlotItem import ScatterPlotItem
|
||||
from ...graphicsItems.PlotCurveItem import PlotCurveItem
|
||||
from ... import PlotDataItem, ComboBox
|
||||
|
||||
from .common import *
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import numpy as np
|
||||
from ...Qt import QtCore, QtGui
|
||||
from ..Node import Node
|
||||
from . import functions
|
||||
from ... import functions as pgfn
|
||||
from .common import *
|
||||
from ...python2_3 import xrange
|
||||
from ... import PolyLineROI
|
||||
from ... import Point
|
||||
from ... import metaarray as metaarray
|
||||
|
@ -322,7 +319,7 @@ class RemovePeriodic(CtrlNode):
|
|||
|
||||
## flatten spikes at f0 and harmonics
|
||||
f0 = self.ctrls['f0'].value()
|
||||
for i in xrange(1, self.ctrls['harmonics'].value()+2):
|
||||
for i in range(1, self.ctrls['harmonics'].value()+2):
|
||||
f = f0 * i # target frequency
|
||||
|
||||
## determine index range to check for this frequency
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import numpy as np
|
||||
from ...metaarray import MetaArray
|
||||
from ...python2_3 import basestring, xrange
|
||||
|
||||
|
||||
def downsample(data, n, axis=0, xvals='subsample'):
|
||||
|
@ -307,7 +307,7 @@ def suggestDType(x):
|
|||
return float
|
||||
elif isinstance(x, int):
|
||||
return int
|
||||
#elif isinstance(x, basestring): ## don't try to guess correct string length; use object instead.
|
||||
#elif isinstance(x, str): ## don't try to guess correct string length; use object instead.
|
||||
#return '<U%d' % len(x)
|
||||
else:
|
||||
return object
|
||||
|
@ -330,7 +330,7 @@ def removePeriodic(data, f0=60.0, dt=None, harmonics=10, samples=4):
|
|||
freqs = np.linspace(0.0, (len(ft)-1) * df, len(ft))
|
||||
|
||||
## flatten spikes at f0 and harmonics
|
||||
for i in xrange(1, harmonics + 2):
|
||||
for i in range(1, harmonics + 2):
|
||||
f = f0 * i # target frequency
|
||||
|
||||
## determine index range to check for this frequency
|
||||
|
@ -354,4 +354,4 @@ def removePeriodic(data, f0=60.0, dt=None, harmonics=10, samples=4):
|
|||
return data2
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ Distributed under MIT/X11 license. See license.txt for more information.
|
|||
|
||||
from __future__ import division
|
||||
|
||||
import ctypes
|
||||
import decimal
|
||||
import re
|
||||
import struct
|
||||
|
@ -24,7 +23,6 @@ from .Qt import QtGui, QtCore, QT_LIB, QtVersion
|
|||
from . import Qt
|
||||
from .metaarray import MetaArray
|
||||
from collections import OrderedDict
|
||||
from .python2_3 import asUnicode, basestring
|
||||
|
||||
# in order of appearance in this file.
|
||||
# add new functions to this list only if they are to reside in pg namespace.
|
||||
|
@ -42,7 +40,7 @@ __all__ = [
|
|||
'makeRGBA', 'makeARGB',
|
||||
# 'try_fastpath_argb', 'ndarray_to_qimage',
|
||||
'makeQImage',
|
||||
# 'qimage_to_ndarray',
|
||||
# 'ndarray_from_qimage',
|
||||
'imageToArray', 'colorToAlpha',
|
||||
'gaussianFilter', 'downsample', 'arrayToQPath',
|
||||
# 'ndarray_from_qpolygonf', 'create_qpolygonf', 'arrayToQPolygonF',
|
||||
|
@ -65,7 +63,7 @@ Colors = {
|
|||
's': QtGui.QColor(100,100,150,255),
|
||||
}
|
||||
|
||||
SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY')
|
||||
SI_PREFIXES = 'yzafpnµm kMGTPEZY'
|
||||
SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY'
|
||||
SI_PREFIX_EXPONENTS = dict([(SI_PREFIXES[i], (i-8)*3) for i in range(len(SI_PREFIXES))])
|
||||
SI_PREFIX_EXPONENTS['u'] = -6
|
||||
|
@ -132,7 +130,7 @@ def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, al
|
|||
return fmt % (x*p, pref, suffix)
|
||||
else:
|
||||
if allowUnicode:
|
||||
plusminus = space + asUnicode("±") + space
|
||||
plusminus = space + "±" + space
|
||||
else:
|
||||
plusminus = " +/- "
|
||||
fmt = "%." + str(precision) + "g%s%s%s%s"
|
||||
|
@ -164,7 +162,6 @@ def siParse(s, regex=FLOAT_REGEX, suffix=None):
|
|||
contains a suffix, it is discarded. This enables interpreting
|
||||
characters following the numerical value as an SI prefix.
|
||||
"""
|
||||
s = asUnicode(s)
|
||||
s = s.strip()
|
||||
if suffix is not None and len(suffix) > 0:
|
||||
if s[-len(suffix):] != suffix:
|
||||
|
@ -227,7 +224,7 @@ class Color(QtGui.QColor):
|
|||
|
||||
def glColor(self):
|
||||
"""Return (r,g,b,a) normalized for use in opengl"""
|
||||
return (self.red()/255., self.green()/255., self.blue()/255., self.alpha()/255.)
|
||||
return self.getRgbF()
|
||||
|
||||
def __getitem__(self, ind):
|
||||
return (self.red, self.green, self.blue, self.alpha)[ind]()
|
||||
|
@ -254,13 +251,23 @@ def mkColor(*args):
|
|||
"""
|
||||
err = 'Not sure how to make a color from "%s"' % str(args)
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], basestring):
|
||||
if isinstance(args[0], str):
|
||||
c = args[0]
|
||||
if len(c) == 1:
|
||||
try:
|
||||
return Colors[c]
|
||||
except KeyError:
|
||||
raise ValueError('No color named "%s"' % c)
|
||||
have_alpha = len(c) in [5, 9] and c[0] == '#' # "#RGBA" and "#RRGGBBAA"
|
||||
if not have_alpha:
|
||||
# try parsing SVG named colors, including "#RGB" and "#RRGGBB".
|
||||
# note that QColor.setNamedColor() treats a 9-char hex string as "#AARRGGBB".
|
||||
qcol = QtGui.QColor()
|
||||
qcol.setNamedColor(c)
|
||||
if qcol.isValid():
|
||||
return qcol
|
||||
# on failure, fallback to pyqtgraph parsing
|
||||
# this includes the deprecated case of non-#-prefixed hex strings
|
||||
if c[0] == '#':
|
||||
c = c[1:]
|
||||
else:
|
||||
|
@ -393,9 +400,7 @@ def mkPen(*args, **kargs):
|
|||
|
||||
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)"""
|
||||
c = QtGui.QColor()
|
||||
c.setHsvF(hue, sat, val, alpha)
|
||||
return c
|
||||
return QtGui.QColor.fromHsvF(hue, sat, val, alpha)
|
||||
|
||||
# Matrices and math taken from "CIELab Color Space" by Gernot Hoffmann
|
||||
# http://docs-hoffmann.de/cielab03022003.pdf
|
||||
|
@ -478,10 +483,7 @@ def CIELabColor(L, a, b, alpha=1.0):
|
|||
else:
|
||||
arr_sRGB[idx] = 12.92 * val # (s)
|
||||
arr_sRGB = clip_array( arr_sRGB, 0.0, 1.0 ) # avoid QColor errors
|
||||
qcol = QtGui.QColor()
|
||||
qcol.setRgbF( *arr_sRGB )
|
||||
if alpha < 1.0: qcol.setAlpha(alpha)
|
||||
return qcol
|
||||
return QtGui.QColor.fromRgbF( *arr_sRGB, alpha )
|
||||
|
||||
def colorCIELab(qcol):
|
||||
"""
|
||||
|
@ -558,7 +560,7 @@ def colorDistance(colors, metric='CIE76'):
|
|||
|
||||
def colorTuple(c):
|
||||
"""Return a tuple (R,G,B,A) from a QColor"""
|
||||
return (c.red(), c.green(), c.blue(), c.alpha())
|
||||
return c.getRgb()
|
||||
|
||||
def colorStr(c):
|
||||
"""Generate a hex string code from a QColor"""
|
||||
|
@ -584,10 +586,7 @@ def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, mi
|
|||
v = maxValue
|
||||
h = minHue + (indh * (maxHue-minHue)) // hues
|
||||
|
||||
c = QtGui.QColor()
|
||||
c.setHsv(h, sat, v)
|
||||
c.setAlpha(alpha)
|
||||
return c
|
||||
return QtGui.QColor.fromHsv(h, sat, v, alpha)
|
||||
|
||||
|
||||
def glColor(*args, **kargs):
|
||||
|
@ -596,7 +595,7 @@ def glColor(*args, **kargs):
|
|||
Accepts same arguments as :func:`mkColor <pyqtgraph.mkColor>`.
|
||||
"""
|
||||
c = mkColor(*args, **kargs)
|
||||
return (c.red()/255., c.green()/255., c.blue()/255., c.alpha()/255.)
|
||||
return c.getRgbF()
|
||||
|
||||
|
||||
|
||||
|
@ -722,7 +721,7 @@ def eq(a, b):
|
|||
else:
|
||||
return e.all()
|
||||
else:
|
||||
raise Exception("== operator returned type %s" % str(type(e)))
|
||||
raise TypeError("== operator returned type %s" % str(type(e)))
|
||||
|
||||
|
||||
def affineSliceCoords(shape, origin, vectors, axes):
|
||||
|
@ -1124,7 +1123,10 @@ def transformCoordinates(tr, coords, transpose=False):
|
|||
m = m[:, :-1]
|
||||
|
||||
## map coordinates and return
|
||||
mapped = (m*coords).sum(axis=1) ## apply scale/rotate
|
||||
# nan or inf points will not plot, but should not generate warnings
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", RuntimeWarning)
|
||||
mapped = (m*coords).sum(axis=1) ## apply scale/rotate
|
||||
mapped += translate
|
||||
|
||||
if transpose:
|
||||
|
@ -1682,24 +1684,28 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True):
|
|||
return ndarray_to_qimage(imgData, imgFormat)
|
||||
|
||||
|
||||
def qimage_to_ndarray(qimg):
|
||||
def ndarray_from_qimage(qimg):
|
||||
img_ptr = qimg.bits()
|
||||
|
||||
if hasattr(img_ptr, 'setsize'): # PyQt sip.voidptr
|
||||
if img_ptr is None:
|
||||
raise ValueError("Null QImage not supported")
|
||||
|
||||
h, w = qimg.height(), qimg.width()
|
||||
bpl = qimg.bytesPerLine()
|
||||
depth = qimg.depth()
|
||||
logical_bpl = w * depth // 8
|
||||
|
||||
if QT_LIB.startswith('PyQt'):
|
||||
# sizeInBytes() was introduced in Qt 5.10
|
||||
# however PyQt5 5.12 will fail with:
|
||||
# "TypeError: QImage.sizeInBytes() is a private method"
|
||||
# note that sizeInBytes() works fine with:
|
||||
# PyQt5 5.15, PySide2 5.12, PySide2 5.15
|
||||
try:
|
||||
# 64-bits size
|
||||
nbytes = qimg.sizeInBytes()
|
||||
except (TypeError, AttributeError):
|
||||
# 32-bits size
|
||||
nbytes = qimg.byteCount()
|
||||
img_ptr.setsize(nbytes)
|
||||
img_ptr.setsize(h * bpl)
|
||||
|
||||
memory = np.frombuffer(img_ptr, dtype=np.ubyte).reshape((h, bpl))
|
||||
memory = memory[:, :logical_bpl]
|
||||
|
||||
depth = qimg.depth()
|
||||
if depth in (8, 24, 32):
|
||||
dtype = np.uint8
|
||||
nchan = depth // 8
|
||||
|
@ -1708,10 +1714,12 @@ def qimage_to_ndarray(qimg):
|
|||
nchan = depth // 16
|
||||
else:
|
||||
raise ValueError("Unsupported Image Type")
|
||||
shape = qimg.height(), qimg.width()
|
||||
|
||||
shape = h, w
|
||||
if nchan != 1:
|
||||
shape = shape + (nchan,)
|
||||
return np.frombuffer(img_ptr, dtype=dtype).reshape(shape)
|
||||
arr = memory.view(dtype).reshape(shape)
|
||||
return arr
|
||||
|
||||
|
||||
def imageToArray(img, copy=False, transpose=True):
|
||||
|
@ -1721,7 +1729,7 @@ def imageToArray(img, copy=False, transpose=True):
|
|||
the QImage is collected before the array, there may be trouble).
|
||||
The array will have shape (width, height, (b,g,r,a)).
|
||||
"""
|
||||
arr = qimage_to_ndarray(img)
|
||||
arr = ndarray_from_qimage(img)
|
||||
|
||||
fmt = img.format()
|
||||
if fmt == img.Format.Format_RGB32:
|
||||
|
@ -1865,6 +1873,156 @@ def downsample(data, n, axis=0, xvals='subsample'):
|
|||
return MetaArray(d2, info=info)
|
||||
|
||||
|
||||
def _compute_backfill_indices(isfinite):
|
||||
# the presence of inf/nans result in an empty QPainterPath being generated
|
||||
# this behavior started in Qt 5.12.3 and was introduced in this commit
|
||||
# https://github.com/qt/qtbase/commit/c04bd30de072793faee5166cff866a4c4e0a9dd7
|
||||
# We therefore replace non-finite values
|
||||
|
||||
# credit: Divakar https://stackoverflow.com/a/41191127/643629
|
||||
mask = ~isfinite
|
||||
idx = np.arange(len(isfinite))
|
||||
idx[mask] = -1
|
||||
np.maximum.accumulate(idx, out=idx)
|
||||
first = np.searchsorted(idx, 0)
|
||||
if first < len(isfinite):
|
||||
# Replace all non-finite entries from beginning of arr with the first finite one
|
||||
idx[:first] = first
|
||||
return idx
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _arrayToQPath_all(x, y, finiteCheck):
|
||||
n = x.shape[0]
|
||||
if n == 0:
|
||||
return QtGui.QPainterPath()
|
||||
|
||||
backfill_idx = None
|
||||
if finiteCheck:
|
||||
isfinite = np.isfinite(x) & np.isfinite(y)
|
||||
if not np.all(isfinite):
|
||||
backfill_idx = _compute_backfill_indices(isfinite)
|
||||
|
||||
chunksize = 10000
|
||||
numchunks = (n + chunksize - 1) // chunksize
|
||||
minchunks = 3
|
||||
|
||||
if numchunks < minchunks:
|
||||
# too few chunks, batching would be a pessimization
|
||||
poly = create_qpolygonf(n)
|
||||
arr = ndarray_from_qpolygonf(poly)
|
||||
|
||||
if backfill_idx is None:
|
||||
arr[:, 0] = x
|
||||
arr[:, 1] = y
|
||||
else:
|
||||
arr[:, 0] = x[backfill_idx]
|
||||
arr[:, 1] = y[backfill_idx]
|
||||
|
||||
path = QtGui.QPainterPath()
|
||||
if hasattr(path, 'reserve'): # Qt 5.13
|
||||
path.reserve(n)
|
||||
path.addPolygon(poly)
|
||||
return path
|
||||
|
||||
# at this point, we have numchunks >= minchunks
|
||||
|
||||
path = QtGui.QPainterPath()
|
||||
if hasattr(path, 'reserve'): # Qt 5.13
|
||||
path.reserve(n)
|
||||
subpoly = QtGui.QPolygonF()
|
||||
subpath = None
|
||||
for idx in range(numchunks):
|
||||
sl = slice(idx*chunksize, min((idx+1)*chunksize, n))
|
||||
currsize = sl.stop - sl.start
|
||||
if currsize != subpoly.size():
|
||||
if hasattr(subpoly, 'resize'):
|
||||
subpoly.resize(currsize)
|
||||
else:
|
||||
subpoly.fill(QtCore.QPointF(), currsize)
|
||||
subarr = ndarray_from_qpolygonf(subpoly)
|
||||
if backfill_idx is None:
|
||||
subarr[:, 0] = x[sl]
|
||||
subarr[:, 1] = y[sl]
|
||||
else:
|
||||
bfv = backfill_idx[sl] # view
|
||||
subarr[:, 0] = x[bfv]
|
||||
subarr[:, 1] = y[bfv]
|
||||
if subpath is None:
|
||||
subpath = QtGui.QPainterPath()
|
||||
subpath.addPolygon(subpoly)
|
||||
path.connectPath(subpath)
|
||||
if hasattr(subpath, 'clear'): # Qt 5.13
|
||||
subpath.clear()
|
||||
else:
|
||||
subpath = None
|
||||
return path
|
||||
|
||||
|
||||
def _arrayToQPath_finite(x, y, isfinite=None):
|
||||
n = x.shape[0]
|
||||
if n == 0:
|
||||
return QtGui.QPainterPath()
|
||||
|
||||
if isfinite is None:
|
||||
isfinite = np.isfinite(x) & np.isfinite(y)
|
||||
|
||||
path = QtGui.QPainterPath()
|
||||
if hasattr(path, 'reserve'): # Qt 5.13
|
||||
path.reserve(n)
|
||||
|
||||
sidx = np.nonzero(~isfinite)[0] + 1
|
||||
# note: the chunks are views
|
||||
xchunks = np.split(x, sidx)
|
||||
ychunks = np.split(y, sidx)
|
||||
chunks = list(zip(xchunks, ychunks))
|
||||
|
||||
# create a single polygon able to hold the largest chunk
|
||||
maxlen = max(len(chunk) for chunk in xchunks)
|
||||
subpoly = create_qpolygonf(maxlen)
|
||||
subarr = ndarray_from_qpolygonf(subpoly)
|
||||
|
||||
# resize and fill do not change the capacity
|
||||
if hasattr(subpoly, 'resize'):
|
||||
subpoly_resize = subpoly.resize
|
||||
else:
|
||||
# PyQt will be less efficient
|
||||
subpoly_resize = lambda n, v=QtCore.QPointF() : subpoly.fill(v, n)
|
||||
|
||||
# notes:
|
||||
# - we backfill the non-finite in order to get the same image as the
|
||||
# old codepath on the CI. somehow P1--P2 gets rendered differently
|
||||
# from P1--P2--P2
|
||||
# - we do not generate MoveTo(s) that are not followed by a LineTo,
|
||||
# thus the QPainterPath can be different from the old codepath's
|
||||
|
||||
# all chunks except the last chunk have a trailing non-finite
|
||||
for xchunk, ychunk in chunks[:-1]:
|
||||
lc = len(xchunk)
|
||||
if lc <= 1:
|
||||
# len 1 means we have a string of non-finite
|
||||
continue
|
||||
subpoly_resize(lc)
|
||||
subarr[:lc, 0] = xchunk
|
||||
subarr[:lc, 1] = ychunk
|
||||
subarr[lc-1] = subarr[lc-2] # fill non-finite with its neighbour
|
||||
path.addPolygon(subpoly)
|
||||
|
||||
# handle last chunk, which is either all-finite or empty
|
||||
for xchunk, ychunk in chunks[-1:]:
|
||||
lc = len(xchunk)
|
||||
if lc <= 1:
|
||||
# can't draw a line with just 1 point
|
||||
continue
|
||||
subpoly_resize(lc)
|
||||
subarr[:lc, 0] = xchunk
|
||||
subarr[:lc, 1] = ychunk
|
||||
path.addPolygon(subpoly)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def arrayToQPath(x, y, connect='all', finiteCheck=True):
|
||||
"""
|
||||
Convert an array of x,y coordinates to QPainterPath as efficiently as
|
||||
|
@ -1887,7 +2045,7 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True):
|
|||
only values with 1 will connect to the previous point. Def
|
||||
finiteCheck : bool, default Ture
|
||||
When false, the check for finite values will be skipped, which can
|
||||
improve performance. If finite values are present in `x` or `y`,
|
||||
improve performance. If nonfinite values are present in `x` or `y`,
|
||||
an empty QPainterPath will be generated.
|
||||
|
||||
Returns
|
||||
|
@ -1920,165 +2078,108 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True):
|
|||
This binary format may change in future versions of Qt
|
||||
"""
|
||||
|
||||
path = QtGui.QPainterPath()
|
||||
n = x.shape[0]
|
||||
if n == 0:
|
||||
return QtGui.QPainterPath()
|
||||
|
||||
connect_array = None
|
||||
if isinstance(connect, np.ndarray):
|
||||
# make connect argument contain only str type
|
||||
connect_array, connect = connect, 'array'
|
||||
|
||||
use_qpolygonf = connect == 'all'
|
||||
|
||||
isfinite = None
|
||||
|
||||
if connect == 'finite':
|
||||
isfinite = np.isfinite(x) & np.isfinite(y)
|
||||
if not finiteCheck:
|
||||
# if user specified to skip finite check, then that forces use_qpolygonf
|
||||
use_qpolygonf = True
|
||||
# if user specified to skip finite check, then we skip the heuristic
|
||||
return _arrayToQPath_finite(x, y)
|
||||
|
||||
# otherwise use a heuristic
|
||||
# if non-finite aren't that many, then use_qpolyponf
|
||||
isfinite = np.isfinite(x) & np.isfinite(y)
|
||||
nonfinite_cnt = n - np.sum(isfinite)
|
||||
all_isfinite = nonfinite_cnt == 0
|
||||
if all_isfinite:
|
||||
# delegate to connect='all'
|
||||
connect = 'all'
|
||||
finiteCheck = False
|
||||
elif nonfinite_cnt / n < 2 / 100:
|
||||
return _arrayToQPath_finite(x, y, isfinite)
|
||||
else:
|
||||
# otherwise use a heuristic
|
||||
# if non-finite aren't that many, then use_qpolyponf
|
||||
nonfinite_cnt = n - np.sum(isfinite)
|
||||
if nonfinite_cnt / n < 2 / 100:
|
||||
use_qpolygonf = True
|
||||
finiteCheck = False
|
||||
if nonfinite_cnt == 0:
|
||||
connect = 'all'
|
||||
# delegate to connect=ndarray
|
||||
# finiteCheck=True, all_isfinite=False
|
||||
connect = 'array'
|
||||
connect_array = isfinite
|
||||
|
||||
if use_qpolygonf:
|
||||
backstore = create_qpolygonf(n)
|
||||
arr = np.frombuffer(ndarray_from_qpolygonf(backstore), dtype=[('x', 'f8'), ('y', 'f8')])
|
||||
else:
|
||||
backstore = bytearray(4 + n*20 + 8)
|
||||
arr = np.frombuffer(backstore, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')],
|
||||
count=n, offset=4)
|
||||
struct.pack_into('>i', backstore, 0, n)
|
||||
# cStart, fillRule (Qt.FillRule.OddEvenFill)
|
||||
struct.pack_into('>ii', backstore, 4+n*20, 0, 0)
|
||||
if connect == 'all':
|
||||
return _arrayToQPath_all(x, y, finiteCheck)
|
||||
|
||||
# Fill array with vertex values
|
||||
arr['x'] = x
|
||||
arr['y'] = y
|
||||
backstore = QtCore.QByteArray()
|
||||
backstore.resize(4 + n*20 + 8) # contents uninitialized
|
||||
backstore.replace(0, 4, struct.pack('>i', n))
|
||||
# cStart, fillRule (Qt.FillRule.OddEvenFill)
|
||||
backstore.replace(4+n*20, 8, struct.pack('>ii', 0, 0))
|
||||
arr = np.frombuffer(backstore, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')],
|
||||
count=n, offset=4)
|
||||
|
||||
# the presence of inf/nans result in an empty QPainterPath being generated
|
||||
# this behavior started in Qt 5.12.3 and was introduced in this commit
|
||||
# https://github.com/qt/qtbase/commit/c04bd30de072793faee5166cff866a4c4e0a9dd7
|
||||
# We therefore replace non-finite values
|
||||
backfill_idx = None
|
||||
if finiteCheck:
|
||||
if isfinite is None:
|
||||
isfinite = np.isfinite(x) & np.isfinite(y)
|
||||
if not np.all(isfinite):
|
||||
# credit: Divakar https://stackoverflow.com/a/41191127/643629
|
||||
mask = ~isfinite
|
||||
idx = np.arange(len(x))
|
||||
idx[mask] = -1
|
||||
np.maximum.accumulate(idx, out=idx)
|
||||
first = np.searchsorted(idx, 0)
|
||||
if first < len(x):
|
||||
# Replace all non-finite entries from beginning of arr with the first finite one
|
||||
idx[:first] = first
|
||||
arr[:] = arr[:][idx]
|
||||
all_isfinite = np.all(isfinite)
|
||||
if not all_isfinite:
|
||||
backfill_idx = _compute_backfill_indices(isfinite)
|
||||
|
||||
if backfill_idx is None:
|
||||
arr['x'] = x
|
||||
arr['y'] = y
|
||||
else:
|
||||
arr['x'] = x[backfill_idx]
|
||||
arr['y'] = y[backfill_idx]
|
||||
|
||||
# decide which points are connected by lines
|
||||
if connect == 'all':
|
||||
path.addPolygon(backstore)
|
||||
return path
|
||||
elif connect == 'pairs':
|
||||
arr['c'][::2] = 0
|
||||
if connect == 'pairs':
|
||||
arr['c'][0::2] = 0
|
||||
arr['c'][1::2] = 1 # connect every 2nd point to every 1st one
|
||||
elif connect == 'finite':
|
||||
elif connect == 'array':
|
||||
# Let's call a point with either x or y being nan is an invalid point.
|
||||
# A point will anyway not connect to an invalid point regardless of the
|
||||
# 'c' value of the invalid point. Therefore, we should set 'c' to 0 for
|
||||
# the next point of an invalid point.
|
||||
if not use_qpolygonf:
|
||||
arr[1:]['c'] = isfinite[:-1]
|
||||
else:
|
||||
sidx = np.nonzero(~isfinite)[0] + 1
|
||||
chunks = np.split(arr, sidx) # note: the chunks are views
|
||||
|
||||
# create a single polygon able to hold the largest chunk
|
||||
maxlen = max(len(chunk) for chunk in chunks)
|
||||
subpoly = create_qpolygonf(maxlen)
|
||||
subarr = np.frombuffer(ndarray_from_qpolygonf(subpoly), dtype=arr.dtype)
|
||||
|
||||
# resize and fill do not change the capacity
|
||||
if hasattr(subpoly, 'resize'):
|
||||
subpoly_resize = subpoly.resize
|
||||
else:
|
||||
# PyQt will be less efficient
|
||||
subpoly_resize = lambda n, v=QtCore.QPointF() : subpoly.fill(v, n)
|
||||
|
||||
# notes:
|
||||
# - we backfill the non-finite in order to get the same image as the
|
||||
# old codepath on the CI. somehow P1--P2 gets rendered differently
|
||||
# from P1--P2--P2
|
||||
# - we do not generate MoveTo(s) that are not followed by a LineTo,
|
||||
# thus the QPainterPath can be different from the old codepath's
|
||||
|
||||
# all chunks except the last chunk have a trailing non-finite
|
||||
for chunk in chunks[:-1]:
|
||||
lc = len(chunk)
|
||||
if lc <= 1:
|
||||
# len 1 means we have a string of non-finite
|
||||
continue
|
||||
subpoly_resize(lc)
|
||||
subarr[:lc] = chunk
|
||||
subarr[lc-1] = subarr[lc-2] # fill non-finite with its neighbour
|
||||
path.addPolygon(subpoly)
|
||||
|
||||
# handle last chunk, which is either all-finite or empty
|
||||
for chunk in chunks[-1:]:
|
||||
lc = len(chunk)
|
||||
if lc <= 1:
|
||||
# can't draw a line with just 1 point
|
||||
continue
|
||||
subpoly_resize(lc)
|
||||
subarr[:lc] = chunk
|
||||
path.addPolygon(subpoly)
|
||||
|
||||
return path
|
||||
elif connect == 'array':
|
||||
arr[1:]['c'] = connect_array[:-1]
|
||||
arr['c'][:1] = 0 # the first vertex has no previous vertex to connect
|
||||
arr['c'][1:] = connect_array[:-1]
|
||||
else:
|
||||
raise ValueError('connect argument must be "all", "pairs", "finite", or array')
|
||||
|
||||
arr[0]['c'] = 0 # the first vertex has no previous vertex to connect
|
||||
path = QtGui.QPainterPath()
|
||||
if hasattr(path, 'reserve'): # Qt 5.13
|
||||
path.reserve(n)
|
||||
|
||||
# create QDataStream object and stream into QPainterPath
|
||||
path.strn = backstore
|
||||
if QT_LIB == "PyQt6" and QtCore.PYQT_VERSION < 0x60101:
|
||||
# due to issue detailed here:
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2021-May/043942.html
|
||||
buf = QtCore.QByteArray(path.strn, len(path.strn))
|
||||
else:
|
||||
buf = QtCore.QByteArray(path.strn)
|
||||
ds = QtCore.QDataStream(buf)
|
||||
ds = QtCore.QDataStream(backstore)
|
||||
ds >> path
|
||||
return path
|
||||
|
||||
def ndarray_from_qpolygonf(polyline):
|
||||
nbytes = 2 * len(polyline) * 8
|
||||
if QT_LIB == "PySide2":
|
||||
buffer = Qt.shiboken2.VoidPtr(polyline.data(), nbytes, True)
|
||||
elif QT_LIB == "PySide6":
|
||||
buffer = Qt.shiboken6.VoidPtr(polyline.data(), nbytes, True)
|
||||
else:
|
||||
if QT_LIB.startswith('PyQt'):
|
||||
buffer = polyline.data()
|
||||
if buffer is None:
|
||||
buffer = Qt.sip.voidptr(0)
|
||||
buffer.setsize(nbytes)
|
||||
else:
|
||||
ptr = polyline.data()
|
||||
if ptr is None:
|
||||
ptr = 0
|
||||
buffer = Qt.shiboken.VoidPtr(ptr, nbytes, True)
|
||||
memory = np.frombuffer(buffer, np.double).reshape((-1, 2))
|
||||
return memory
|
||||
|
||||
def create_qpolygonf(size):
|
||||
if QtVersion.startswith("5"):
|
||||
polyline = QtGui.QPolygonF(size)
|
||||
polyline = QtGui.QPolygonF()
|
||||
if QT_LIB.startswith('PyQt'):
|
||||
polyline.fill(QtCore.QPointF(), size)
|
||||
else:
|
||||
polyline = QtGui.QPolygonF()
|
||||
if QT_LIB == "PySide6":
|
||||
polyline.resize(size)
|
||||
else:
|
||||
polyline.fill(QtCore.QPointF(), size)
|
||||
polyline.resize(size)
|
||||
return polyline
|
||||
|
||||
def arrayToQPolygonF(x, y):
|
||||
|
@ -2192,9 +2293,9 @@ def arrayToQPolygonF(x, y):
|
|||
#index = tetFields[0] + tetFields[1]*2 + tetFields[2]*4 + tetFields[3]*8
|
||||
|
||||
### add facets
|
||||
#for i in xrange(index.shape[0]): # data x-axis
|
||||
#for j in xrange(index.shape[1]): # data y-axis
|
||||
#for k in xrange(index.shape[2]): # data z-axis
|
||||
#for i in range(index.shape[0]): # data x-axis
|
||||
#for j in range(index.shape[1]): # data y-axis
|
||||
#for k in range(index.shape[2]): # data z-axis
|
||||
#for f in indexFacets[index[i,j,k]]: # faces to generate for this tet
|
||||
#pts = []
|
||||
#for l in [0,1,2]: # points in this face
|
||||
|
@ -2397,11 +2498,6 @@ def traceImage(image, values, smooth=0.5):
|
|||
If image is RGB or RGBA, then the shape of values should be (nvals, 3/4)
|
||||
The parameter *smooth* is expressed in pixels.
|
||||
"""
|
||||
try:
|
||||
import scipy.ndimage as ndi
|
||||
except ImportError:
|
||||
raise Exception("traceImage() requires the package scipy.ndimage, but it is not importable.")
|
||||
|
||||
if values.ndim == 2:
|
||||
values = values.T
|
||||
values = values[np.newaxis, np.newaxis, ...].astype(float)
|
||||
|
@ -3089,9 +3185,9 @@ def disconnect(signal, slot):
|
|||
|
||||
This method augments Qt's Signal.disconnect():
|
||||
|
||||
* Return bool indicating whether disconnection was successful, rather than
|
||||
raising an exception
|
||||
* Attempt to disconnect prior versions of the slot when using pg.reload
|
||||
* Return bool indicating whether disconnection was successful, rather than
|
||||
raising an exception
|
||||
* Attempt to disconnect prior versions of the slot when using pg.reload
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from math import hypot
|
||||
from ..Qt import QtGui, QtCore
|
||||
from ..Qt import QtGui
|
||||
from .. import functions as fn
|
||||
import numpy as np
|
||||
__all__ = ['ArrowItem']
|
||||
|
||||
class ArrowItem(QtGui.QGraphicsPathItem):
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from ..Qt import QtGui, QtCore
|
||||
from ..python2_3 import asUnicode
|
||||
import numpy as np
|
||||
from ..Point import Point
|
||||
from .. import debug as debug
|
||||
|
@ -158,7 +157,7 @@ class AxisItem(GraphicsWidget):
|
|||
tickAlpha (float or int or None) If None, pyqtgraph will draw the
|
||||
ticks with the alpha it deems appropriate. Otherwise,
|
||||
the alpha will be fixed at the value passed. With int,
|
||||
accepted values are [0..255]. With vaule of type
|
||||
accepted values are [0..255]. With value of type
|
||||
float, accepted values are from [0..1].
|
||||
=================== =======================================================
|
||||
|
||||
|
@ -233,6 +232,10 @@ class AxisItem(GraphicsWidget):
|
|||
|
||||
## Set the position of the label
|
||||
nudge = 5
|
||||
if self.label is None: # self.label is set to None on close, but resize events can still occur.
|
||||
self.picture = None
|
||||
return
|
||||
|
||||
br = self.label.boundingRect()
|
||||
p = QtCore.QPointF(0, 0)
|
||||
if self.orientation == 'left':
|
||||
|
@ -310,16 +313,15 @@ class AxisItem(GraphicsWidget):
|
|||
if not self.autoSIPrefix or self.autoSIPrefixScale == 1.0:
|
||||
units = ''
|
||||
else:
|
||||
units = asUnicode('(x%g)') % (1.0/self.autoSIPrefixScale)
|
||||
units = '(x%g)' % (1.0/self.autoSIPrefixScale)
|
||||
else:
|
||||
#print repr(self.labelUnitPrefix), repr(self.labelUnits)
|
||||
units = asUnicode('(%s%s)') % (asUnicode(self.labelUnitPrefix), asUnicode(self.labelUnits))
|
||||
units = '(%s%s)' % (self.labelUnitPrefix, self.labelUnits)
|
||||
|
||||
s = asUnicode('%s %s') % (asUnicode(self.labelText), asUnicode(units))
|
||||
s = '%s %s' % (self.labelText, units)
|
||||
|
||||
style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle])
|
||||
|
||||
return asUnicode("<span style='%s'>%s</span>") % (style, asUnicode(s))
|
||||
return "<span style='%s'>%s</span>" % (style, s)
|
||||
|
||||
def _updateMaxTextSize(self, x):
|
||||
## Informs that the maximum tick size orthogonal to the axis has
|
||||
|
@ -431,7 +433,7 @@ class AxisItem(GraphicsWidget):
|
|||
self._pen = fn.mkPen(*args, **kwargs)
|
||||
else:
|
||||
self._pen = fn.mkPen(getConfigOption('foreground'))
|
||||
self.labelStyle['color'] = '#' + fn.colorStr(self._pen.color())[:6]
|
||||
self.labelStyle['color'] = self._pen.color().name() # #RRGGBB
|
||||
self._updateLabel()
|
||||
|
||||
def textPen(self):
|
||||
|
@ -449,7 +451,7 @@ class AxisItem(GraphicsWidget):
|
|||
self._textPen = fn.mkPen(*args, **kwargs)
|
||||
else:
|
||||
self._textPen = fn.mkPen(getConfigOption('foreground'))
|
||||
self.labelStyle['color'] = '#' + fn.colorStr(self._textPen.color())[:6]
|
||||
self.labelStyle['color'] = self._textPen.color().name() # #RRGGBB
|
||||
self._updateLabel()
|
||||
|
||||
def setScale(self, scale=None):
|
||||
|
@ -841,36 +843,31 @@ class AxisItem(GraphicsWidget):
|
|||
|
||||
def logTickStrings(self, values, scale, spacing):
|
||||
estrings = ["%0.1g"%x for x in 10 ** np.array(values).astype(float) * np.array(scale)]
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
# python 2 does not support unicode strings like that
|
||||
return estrings
|
||||
else: # python 3+
|
||||
convdict = {"0": "⁰",
|
||||
"1": "¹",
|
||||
"2": "²",
|
||||
"3": "³",
|
||||
"4": "⁴",
|
||||
"5": "⁵",
|
||||
"6": "⁶",
|
||||
"7": "⁷",
|
||||
"8": "⁸",
|
||||
"9": "⁹",
|
||||
}
|
||||
dstrings = []
|
||||
for e in estrings:
|
||||
if e.count("e"):
|
||||
v, p = e.split("e")
|
||||
sign = "⁻" if p[0] == "-" else ""
|
||||
pot = "".join([convdict[pp] for pp in p[1:].lstrip("0")])
|
||||
if v == "1":
|
||||
v = ""
|
||||
else:
|
||||
v = v + "·"
|
||||
dstrings.append(v + "10" + sign + pot)
|
||||
convdict = {"0": "⁰",
|
||||
"1": "¹",
|
||||
"2": "²",
|
||||
"3": "³",
|
||||
"4": "⁴",
|
||||
"5": "⁵",
|
||||
"6": "⁶",
|
||||
"7": "⁷",
|
||||
"8": "⁸",
|
||||
"9": "⁹",
|
||||
}
|
||||
dstrings = []
|
||||
for e in estrings:
|
||||
if e.count("e"):
|
||||
v, p = e.split("e")
|
||||
sign = "⁻" if p[0] == "-" else ""
|
||||
pot = "".join([convdict[pp] for pp in p[1:].lstrip("0")])
|
||||
if v == "1":
|
||||
v = ""
|
||||
else:
|
||||
dstrings.append(e)
|
||||
return dstrings
|
||||
v = v + "·"
|
||||
dstrings.append(v + "10" + sign + pot)
|
||||
else:
|
||||
dstrings.append(e)
|
||||
return dstrings
|
||||
|
||||
def generateDrawSpecs(self, p):
|
||||
"""
|
||||
|
@ -1066,7 +1063,7 @@ class AxisItem(GraphicsWidget):
|
|||
if s is None:
|
||||
rects.append(None)
|
||||
else:
|
||||
br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignmentFlag.AlignCenter, asUnicode(s))
|
||||
br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignmentFlag.AlignCenter, s)
|
||||
## boundingRect is usually just a bit too large
|
||||
## (but this probably depends on per-font metrics?)
|
||||
br.setHeight(br.height() * 0.8)
|
||||
|
@ -1108,7 +1105,6 @@ class AxisItem(GraphicsWidget):
|
|||
vstr = strings[j]
|
||||
if vstr is None: ## this tick was ignored because it is out of bounds
|
||||
continue
|
||||
vstr = asUnicode(vstr)
|
||||
x = tickPositions[i][j]
|
||||
#textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignmentFlag.AlignCenter, vstr)
|
||||
textRect = rects[j]
|
||||
|
|
|
@ -7,6 +7,7 @@ from .LinearRegionItem import LinearRegionItem
|
|||
|
||||
import weakref
|
||||
import math
|
||||
import warnings
|
||||
import numpy as np
|
||||
|
||||
__all__ = ['ColorBarItem']
|
||||
|
@ -35,33 +36,40 @@ class ColorBarItem(PlotItem):
|
|||
sigLevelsChanged = QtCore.Signal(object)
|
||||
sigLevelsChangeFinished = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, values=(0,1), width=25, cmap=None, label=None,
|
||||
def __init__(self, values=(0,1), width=25, colorMap=None, label=None,
|
||||
interactive=True, limits=None, rounding=1,
|
||||
orientation='vertical', pen='w', hoverPen='r', hoverBrush='#FF000080' ):
|
||||
orientation='vertical', pen='w', hoverPen='r', hoverBrush='#FF000080', cmap=None ):
|
||||
"""
|
||||
Create a new ColorBarItem.
|
||||
Creates a new ColorBarItem.
|
||||
|
||||
============== ===========================================================================
|
||||
**Arguments:**
|
||||
values The range of values as tuple (min, max)
|
||||
width (default=25) The width of the displayed color bar
|
||||
cmap ColorMap object, look-up table is also applied to assigned ImageItem(s)
|
||||
colorMap ColorMap object, look-up table is also applied to assigned ImageItem(s)
|
||||
label (default=None) Label applied to color bar axis
|
||||
interactive (default=True) Handles are displayed to interactively adjust level range
|
||||
limits Limits to adjustment range as (low, high) tuple, None disables limit
|
||||
rounding (default=1) Adjusted range values are rounded to multiples of this values
|
||||
orientation (default='vertical') 'horizontal' gives a horizontal color bar
|
||||
orientation (default='vertical') 'horizontal' or 'h' gives a horizontal color bar.
|
||||
pen color of adjustement handles in interactive mode
|
||||
hoverPen color of adjustement handles when hovered over
|
||||
hoverBrush color of movable center region when hovered over
|
||||
============== ===========================================================================
|
||||
"""
|
||||
super().__init__()
|
||||
if cmap is not None:
|
||||
warnings.warn(
|
||||
"The parameter 'cmap' has been renamed to 'colorMap' for clarity. "
|
||||
"The old name will no longer be available in any version of PyQtGraph released after July 2022.",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
colorMap = cmap
|
||||
self.img_list = [] # list of controlled ImageItems
|
||||
self.values = values
|
||||
self.cmap = cmap
|
||||
self._colorMap = colorMap
|
||||
self.rounding = rounding
|
||||
self.horizontal = bool(orientation == 'horizontal')
|
||||
self.horizontal = bool( orientation in ('h', 'horizontal') )
|
||||
|
||||
self.lo_prv, self.hi_prv = self.values # remember previous values while adjusting range
|
||||
if limits is None:
|
||||
|
@ -107,7 +115,7 @@ class ColorBarItem(PlotItem):
|
|||
self.bar.setImage( np.linspace(0, 1, 256).reshape( (1,-1) ) )
|
||||
if label is not None: self.getAxis('left').setLabel(label)
|
||||
self.addItem(self.bar)
|
||||
if cmap is not None: self.setCmap(cmap)
|
||||
if cmap is not None: self.setColorMap(cmap)
|
||||
|
||||
if interactive:
|
||||
if self.horizontal:
|
||||
|
@ -156,13 +164,36 @@ class ColorBarItem(PlotItem):
|
|||
insert_in.layout.setColumnFixedWidth(4, 5) # enforce some space to axis on the left
|
||||
self._update_items( update_cmap = True )
|
||||
|
||||
# Maintain compatibility for old name of color bar setting method.
|
||||
def setCmap(self, cmap):
|
||||
warnings.warn(
|
||||
"The method 'setCmap' has been renamed to 'setColorMap' for clarity. "
|
||||
"The old name will no longer be available in any version of PyQtGraph released after July 2022.",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
self.setColorMap(cmap)
|
||||
|
||||
def setColorMap(self, colorMap):
|
||||
"""
|
||||
sets a ColorMap object to determine the ColorBarItem's look-up table. The same
|
||||
Sets a ColorMap object to determine the ColorBarItem's look-up table. The same
|
||||
look-up table is applied to any assigned ImageItem.
|
||||
"""
|
||||
self.cmap = cmap
|
||||
self._colorMap = colorMap
|
||||
self._update_items( update_cmap = True )
|
||||
|
||||
def colorMap(self):
|
||||
"""
|
||||
Returns the assigned ColorMap object.
|
||||
"""
|
||||
return self._colorMap
|
||||
|
||||
@property
|
||||
def cmap(self):
|
||||
warnings.warn(
|
||||
"Direct access to ColorMap.cmap is deprecated and will no longer be available in any "
|
||||
"version of PyQtGraph released after July 2022. Please use 'ColorMap.colorMap()' instead.",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return self._colorMap
|
||||
|
||||
def setLevels(self, values=None, low=None, high=None ):
|
||||
"""
|
||||
|
@ -170,7 +201,7 @@ class ColorBarItem(PlotItem):
|
|||
|
||||
============== ===========================================================================
|
||||
**Arguments:**
|
||||
values Specify levels by tuple (low, high). Either value can be None to leave
|
||||
values specifies levels as tuple (low, high). Either value can be None to leave
|
||||
to previous value unchanged. Takes precedence over low and high parameters.
|
||||
low new low level to be applied to color bar and assigned images
|
||||
high new high level to be applied to color bar and assigned images
|
||||
|
@ -199,15 +230,15 @@ class ColorBarItem(PlotItem):
|
|||
""" internal: update color maps for bar and assigned ImageItems """
|
||||
# update color bar:
|
||||
self.axis.setRange( self.values[0], self.values[1] )
|
||||
if update_cmap and self.cmap is not None:
|
||||
self.bar.setLookupTable( self.cmap.getLookupTable(nPts=256) )
|
||||
if update_cmap and self._colorMap is not None:
|
||||
self.bar.setLookupTable( self._colorMap.getLookupTable(nPts=256) )
|
||||
# update assigned ImageItems, too:
|
||||
for img_weakref in self.img_list:
|
||||
img = img_weakref()
|
||||
if img is None: continue # dereference weakref
|
||||
img.setLevels( self.values ) # (min,max) tuple
|
||||
if update_cmap and self.cmap is not None:
|
||||
img.setLookupTable( self.cmap.getLookupTable(nPts=256) )
|
||||
if update_cmap and self._colorMap is not None:
|
||||
img.setLookupTable( self._colorMap.getLookupTable(nPts=256) )
|
||||
|
||||
def _regionChanged(self):
|
||||
""" internal: snap adjusters back to default positions on release """
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from math import atan2, degrees
|
||||
from ..Qt import QtGui, QtCore
|
||||
from . import ArrowItem
|
||||
from ..functions import clip_scalar
|
||||
from ..Point import Point
|
||||
import weakref
|
||||
from .GraphicsObject import GraphicsObject
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from ..Qt import QtGui, QtCore
|
||||
# -*- coding: utf-8 -*-
|
||||
from ..Qt import QtGui
|
||||
from .GraphicsObject import GraphicsObject
|
||||
from .. import getConfigOption
|
||||
from .. import functions as fn
|
||||
import numpy as np
|
||||
|
||||
__all__ = ['ErrorBarItem']
|
||||
|
||||
|
@ -34,15 +34,15 @@ class ErrorBarItem(GraphicsObject):
|
|||
Valid keyword options are:
|
||||
x, y, height, width, top, bottom, left, right, beam, pen
|
||||
|
||||
* x and y must be numpy arrays specifying the coordinates of data points.
|
||||
* height, width, top, bottom, left, right, and beam may be numpy arrays,
|
||||
single values, or None to disable. All values should be positive.
|
||||
* top, bottom, left, and right specify the lengths of bars extending
|
||||
in each direction.
|
||||
* If height is specified, it overrides top and bottom.
|
||||
* If width is specified, it overrides left and right.
|
||||
* beam specifies the width of the beam at the end of each bar.
|
||||
* pen may be any single argument accepted by pg.mkPen().
|
||||
* x and y must be numpy arrays specifying the coordinates of data points.
|
||||
* height, width, top, bottom, left, right, and beam may be numpy arrays,
|
||||
single values, or None to disable. All values should be positive.
|
||||
* top, bottom, left, and right specify the lengths of bars extending
|
||||
in each direction.
|
||||
* If height is specified, it overrides top and bottom.
|
||||
* If width is specified, it overrides left and right.
|
||||
* beam specifies the width of the beam at the end of each bar.
|
||||
* pen may be any single argument accepted by pg.mkPen().
|
||||
|
||||
This method was added in version 0.9.9. For prior versions, use setOpts.
|
||||
"""
|
||||
|
|
|
@ -4,7 +4,6 @@ import weakref
|
|||
import numpy as np
|
||||
from ..Qt import QtGui, QtCore
|
||||
from .. import functions as fn
|
||||
from .GraphicsObject import GraphicsObject
|
||||
from .GraphicsWidget import GraphicsWidget
|
||||
from ..widgets.SpinBox import SpinBox
|
||||
from collections import OrderedDict
|
||||
|
@ -582,7 +581,7 @@ class GradientEditorItem(TickSliderItem):
|
|||
for t,x in self.listTicks():
|
||||
pos.append(x)
|
||||
c = t.color
|
||||
color.append([c.red(), c.green(), c.blue(), c.alpha()])
|
||||
color.append(c.getRgb())
|
||||
return ColorMap(np.array(pos), np.array(color, dtype=np.ubyte))
|
||||
|
||||
def updateGradient(self):
|
||||
|
@ -672,13 +671,13 @@ class GradientEditorItem(TickSliderItem):
|
|||
if toQColor:
|
||||
return QtGui.QColor(c) # always copy colors before handing them out
|
||||
else:
|
||||
return (c.red(), c.green(), c.blue(), c.alpha())
|
||||
return c.getRgb()
|
||||
if x >= ticks[-1][1]:
|
||||
c = ticks[-1][0].color
|
||||
if toQColor:
|
||||
return QtGui.QColor(c) # always copy colors before handing them out
|
||||
else:
|
||||
return (c.red(), c.green(), c.blue(), c.alpha())
|
||||
return c.getRgb()
|
||||
|
||||
x2 = ticks[0][1]
|
||||
for i in range(1,len(ticks)):
|
||||
|
@ -709,12 +708,11 @@ class GradientEditorItem(TickSliderItem):
|
|||
h = h1 * (1.-f) + h2 * f
|
||||
s = s1 * (1.-f) + s2 * f
|
||||
v = v1 * (1.-f) + v2 * f
|
||||
c = QtGui.QColor()
|
||||
c.setHsv(*map(int, [h,s,v]))
|
||||
c = QtGui.QColor.fromHsv(int(h), int(s), int(v))
|
||||
if toQColor:
|
||||
return c
|
||||
else:
|
||||
return (c.red(), c.green(), c.blue(), c.alpha())
|
||||
return c.getRgb()
|
||||
|
||||
def getLookupTable(self, nPts, alpha=None):
|
||||
"""
|
||||
|
@ -758,8 +756,8 @@ class GradientEditorItem(TickSliderItem):
|
|||
return False
|
||||
if ticks[0][1] != 0.0 or ticks[1][1] != 1.0:
|
||||
return False
|
||||
c1 = fn.colorTuple(ticks[0][0].color)
|
||||
c2 = fn.colorTuple(ticks[1][0].color)
|
||||
c1 = ticks[0][0].color.getRgb()
|
||||
c2 = ticks[1][0].color.getRgb()
|
||||
if c1 != (0,0,0,255) or c2 != (255,255,255,255):
|
||||
return False
|
||||
return True
|
||||
|
@ -795,7 +793,7 @@ class GradientEditorItem(TickSliderItem):
|
|||
ticks = []
|
||||
for t in self.ticks:
|
||||
c = t.color
|
||||
ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha())))
|
||||
ticks.append((self.ticks[t], c.getRgb()))
|
||||
state = {'mode': self.colorMode,
|
||||
'ticks': ticks,
|
||||
'ticksVisible': next(iter(self.ticks)).isVisible()}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from .. import functions as fn
|
||||
from .GraphicsObject import GraphicsObject
|
||||
from .ScatterPlotItem import ScatterPlotItem
|
||||
|
@ -36,14 +37,14 @@ class GraphItem(GraphicsObject):
|
|||
pen The pen to use when drawing lines between connected
|
||||
nodes. May be one of:
|
||||
|
||||
* QPen
|
||||
* a single argument to pass to pg.mkPen
|
||||
* a record array of length M
|
||||
with fields (red, green, blue, alpha, width). Note
|
||||
that using this option may have a significant performance
|
||||
cost.
|
||||
* None (to disable connection drawing)
|
||||
* 'default' to use the default foreground color.
|
||||
* QPen
|
||||
* a single argument to pass to pg.mkPen
|
||||
* a record array of length M
|
||||
with fields (red, green, blue, alpha, width). Note
|
||||
that using this option may have a significant performance
|
||||
cost.
|
||||
* None (to disable connection drawing)
|
||||
* 'default' to use the default foreground color.
|
||||
|
||||
symbolPen The pen(s) used for drawing nodes.
|
||||
symbolBrush The brush(es) used for drawing nodes.
|
||||
|
@ -84,11 +85,11 @@ class GraphItem(GraphicsObject):
|
|||
Set the pen used to draw graph lines.
|
||||
May be:
|
||||
|
||||
* None to disable line drawing
|
||||
* Record array with fields (red, green, blue, alpha, width)
|
||||
* Any set of arguments and keyword arguments accepted by
|
||||
:func:`mkPen <pyqtgraph.mkPen>`.
|
||||
* 'default' to use the default foreground color.
|
||||
* None to disable line drawing
|
||||
* Record array with fields (red, green, blue, alpha, width)
|
||||
* Any set of arguments and keyword arguments accepted by
|
||||
:func:`mkPen <pyqtgraph.mkPen>`.
|
||||
* 'default' to use the default foreground color.
|
||||
"""
|
||||
if len(args) == 1 and len(kwargs) == 0:
|
||||
self.pen = args[0]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from ..Qt import QtGui, QtCore
|
||||
# -*- coding: utf-8 -*-
|
||||
from ..Qt import QtGui
|
||||
from .. import functions as fn
|
||||
from .GraphicsWidget import GraphicsWidget
|
||||
## Must be imported at the end to avoid cyclic-dependency hell:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from ..Qt import QtGui, QtCore, QT_LIB
|
||||
# -*- coding: utf-8 -*-
|
||||
from ..Qt import QtGui, QT_LIB
|
||||
if QT_LIB.startswith('PyQt'):
|
||||
from ..Qt import sip
|
||||
from .GraphicsItem import GraphicsItem
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue