pax_global_header00006660000000000000000000000064142104550740014514gustar00rootroot0000000000000052 comment=cd855bd79b22476d6de3211cf57150abbe7edf5b pyqtgraph-pyqtgraph-0.12.4/000077500000000000000000000000001421045507400156345ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/.coveragerc000066400000000000000000000005131421045507400177540ustar00rootroot00000000000000[run] source = pyqtgraph branch = True [report] omit = */python?.?/* */site-packages/nose/* *test* */__pycache__/* *.pyc exclude_lines = pragma: no cover def __repr__ if self\.debug raise AssertionError raise NotImplementedError if 0: if __name__ == .__main__.: ignore_errors = True pyqtgraph-pyqtgraph-0.12.4/.flake8000066400000000000000000000002351421045507400170070ustar00rootroot00000000000000[flake8] exclude = .git,.tox,__pycache__,doc,old,build,dist show_source = True statistics = True verbose = 2 max-line-length = 88 extend-ignore = E203, W503 pyqtgraph-pyqtgraph-0.12.4/.github/000077500000000000000000000000001421045507400171745ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/.github/ISSUE_TEMPLATE/000077500000000000000000000000001421045507400213575ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000021101421045507400240430ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- ### Short description ### Code to reproduce ```python import pyqtgraph as pg import numpy as np ``` ### Expected behavior ### Real behavior ``` An error occurred? Post the full traceback inside these 'code fences'! ``` ### Tested environment(s) * PyQtGraph version: * Qt Python binding: * Python version: * NumPy version: * Operating system: * Installation method: ### Additional context pyqtgraph-pyqtgraph-0.12.4/.github/workflows/000077500000000000000000000000001421045507400212315ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/.github/workflows/codeql-analysis.yml000066400000000000000000000035671421045507400250570ustar00rootroot00000000000000name: codeql on: [push, pull_request] jobs: analyze: name: analyze runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 - name: Setup Python uses: actions/setup-python@v2 with: python-version: '3.9' - name: Install Dependencies run: | python -m pip install --upgrade pip pip install PyQt5 numpy scipy echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: 'python' # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. queries: +security-and-quality setup-python-dependencies: false # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # πŸ“š https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 pyqtgraph-pyqtgraph-0.12.4/.github/workflows/main.yml000066400000000000000000000133351421045507400227050ustar00rootroot00000000000000name: main on: [push, pull_request] concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: fail-fast: false matrix: os: [ubuntu-20.04 , windows-latest, macos-latest] qt-lib: [pyqt, pyside] python-version: ["3.7", "3.8", "3.9", "3.10"] include: - python-version: "3.7" qt-lib: "pyqt" qt-version: "PyQt5~=5.12.0" numpy-version: "~=1.18.0" - python-version: "3.7" qt-lib: "pyside" qt-version: "PySide2~=5.12.0" numpy-version: "~=1.18.0" - python-version: "3.8" qt-lib: "pyqt" qt-version: "PyQt5~=5.15.0" numpy-version: "~=1.19.0" - python-version: "3.8" qt-lib: "pyside" qt-version: "PySide2~=5.15.0" numpy-version: "~=1.19.0" - python-version: "3.9" qt-lib: "pyqt" qt-version: "PyQt6~=6.1.0" numpy-version: "~=1.22.0" - python-version: "3.9" qt-lib: "pyside" qt-version: "PySide6~=6.1.0" numpy-version: "~=1.22.0" - python-version: "3.10" qt-lib: "pyqt" qt-version: "PyQt6" numpy-version: "~=1.22.0" - python-version: "3.10" qt-lib: "pyside" qt-version: "PySide6" numpy-version: "~=1.22.0" steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: # Semantic version range syntax or exact version of a Python version python-version: ${{ matrix.python-version }} - name: "Install Windows-Mesa OpenGL DLL" if: runner.os == 'Windows' run: | curl -LJO https://github.com/pal1000/mesa-dist-win/releases/download/19.2.7/mesa3d-19.2.7-release-msvc.7z 7z x mesa3d-19.2.7-release-msvc.7z cd x64 xcopy opengl32.dll C:\windows\system32\mesadrv.dll* xcopy opengl32.dll C:\windows\syswow64\mesadrv.dll* REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v DLL /t REG_SZ /d "mesadrv.dll" /f REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v DriverVersion /t REG_DWORD /d 1 /f REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v Flags /t REG_DWORD /d 1 /f REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v Version /t REG_DWORD /d 2 /f REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v DLL /t REG_SZ /d "mesadrv.dll" /f REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v DriverVersion /t REG_DWORD /d 1 /f REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v Flags /t REG_DWORD /d 1 /f REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v Version /t REG_DWORD /d 2 /f shell: cmd - name: Install Dependencies run: | python -m pip install --upgrade pip python -m pip install ${{ matrix.qt-version }} numpy${{ matrix.numpy-version }} scipy pyopengl h5py matplotlib pytest pytest-xdist . - name: Optionally Install Numba if: matrix.python-version != '3.10' run: | python -m pip install numba - name: "Install Linux VirtualDisplay" if: runner.os == 'Linux' run: | sudo apt-get update -y sudo apt-get install -y libxkbcommon-x11-0 x11-utils sudo apt-get install --no-install-recommends -y libyaml-dev libegl1-mesa libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 sudo apt-get install -y libopengl0 python -m pip install pytest-xvfb - name: 'Debug Info' run: | echo python location: `which python` echo python version: `python --version` echo pytest location: `which pytest` echo installed packages python -m pip list echo pyqtgraph system info python -c "import pyqtgraph as pg; pg.systemInfo()" shell: bash env: QT_DEBUG_PLUGINS: 1 - name: 'XVFB Display Info' run: | xvfb-run --server-args="-screen 0, 1920x1200x24 -ac +extension GLX +render -noreset" python -m pyqtgraph.util.glinfo xvfb-run --server-args="-screen 0, 1920x1200x24 -ac +extension GLX +render -noreset" python -m pyqtgraph.util.get_resolution if: runner.os == 'Linux' - name: 'Display Info' run: | python -m pyqtgraph.util.glinfo python -m pyqtgraph.util.get_resolution if: runner.os != 'Linux' - name: Run Tests run: | mkdir $SCREENSHOT_DIR pytest tests -v pytest pyqtgraph/examples -v -n auto shell: bash - name: Upload Screenshots uses: actions/upload-artifact@v2 with: name: Screenshots (Python ${{ matrix.python-version }} - Qt-Bindings ${{ matrix.qt-lib }} - OS ${{ matrix.os }}) path: $SCREENSHOT_DIR if-no-files-found: ignore env: SCREENSHOT_DIR: ./screenshots build-wheel: name: build wheel runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Python 3.9 uses: actions/setup-python@v2 with: python-version: '3.9' - name: Build Wheel run: | python -m pip install setuptools wheel python setup.py bdist_wheel pyqtgraph-pyqtgraph-0.12.4/.github/workflows/python-publish.yml000066400000000000000000000017751421045507400247530ustar00rootroot00000000000000# 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/* pyqtgraph-pyqtgraph-0.12.4/.gitignore000066400000000000000000000021671421045507400176320ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ bin/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg doc/_build # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ cover/ .coverage .cache nosetests.xml coverage.xml .coverage.* # Translations *.mo *.pot # Mr Developer .mr.developer.cfg .project .pydevproject # Rope .ropeproject # Django stuff: *.log *.pot # Sphinx documentation docs/_build/ #mac .DS_Store *~ #vim *.swp #pycharm .idea/* #Dolphin browser files .directory/ .directory #Binary data files *.volume *.am *.tiff *.tif *.dat *.DAT #generated documntation files doc/resource/api/generated/ # Enaml __enamlcache__/ # PyBuilder target/ # sphinx docs generated/ MANIFEST deb_build rtr.cvs # pytest parallel .coverage # ctags .tags* .asv/ asv.conf.json pyqtgraph-pyqtgraph-0.12.4/.pre-commit-config.yaml000066400000000000000000000010151421045507400221120ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: - id: check-added-large-files args: ['--maxkb=100'] - id: check-case-conflict - id: end-of-file-fixer - id: fix-encoding-pragma args: ['--remove'] - id: mixed-line-ending args: [--fix=lf] - id: check-merge-conflict - id: check-toml - id: debug-statements - id: check-ast - repo: https://github.com/pycqa/isort rev: 5.9.3 hooks: - id: isort pyqtgraph-pyqtgraph-0.12.4/.readthedocs.yml000066400000000000000000000003261421045507400207230ustar00rootroot00000000000000# Read the Docs configuration file # https://docs.readthedocs.io/en/stable/config-file/v2.html version: 2 python: version: 3 install: - requirements: doc/requirements.txt sphinx: fail_on_warning: true pyqtgraph-pyqtgraph-0.12.4/CHANGELOG000066400000000000000000001732751421045507400170650ustar00rootroot00000000000000pyqtgraph-0.12.4 Highlights: - #2055 Jupyter Support via jupyter-rfb - #2011 Experimental High Performance With Lines > 1px Thickness - #2059/#2153 Python 3.10 Support New Features: - #2041 Allow unsetting various options in PlotDataItem and PlotCurveItem - #2052 PlotItem Average pen and shadow pen are now accessible - #2090 More coninient methods for color maps and bars Performance Enhancements: - #2023 PColorMeshItem Performance Improvements - #2032 Speed up PlotCurveItem fillLevel - #2036 Speed up arrayToQPath for connect='all' with non-finite values - #2111 PlotCurveItem OpenGL avoid automatic conversion from float64 to float32 - #2124 Go back to using np.clip on Windows with numpy 1.22 or newer - #2131 Avoid PyOpenGL automatic array conversion - #2198 Cache values used in GraphicsWidget .boundingRect() and .shape() methods - #2199 Avoid unnecessary call to viewRange if autoRange is disabled - #2202 Cachce ViewBox view pixel size Bug Fixes: - #2034 Fix Mouse Event possitioning issue with grid - #2047 Fixed WidgetGroup Handling QSplitter - #2054 Limit ViewBox based on double precision limitations - #2085 Reverse coordinates when drawing on row-major images - #2087 Fix broken imports in some examples - #2089 Don't raise exception when close method of an already-closed dock is called - #2101 Change GroupParameterItem palette to look ok in darkmode on macOS - #2103 Fix stuck ColorBarItem - #2132 Workaround for PySide6 6.2.2 breaking change - #2130 RangeColorMapItem derives from ColorMapParameter - #2147 Fire correct signal for Checklist ParameterItem type - #2148 Avoid comparing a string with a np.ndarray - #2170 Fix formatting on minimum value of GradientLegend API/Behavior Changes: - #2081 Separate x and y flags for AxisItem.setLogMode - #2086 ParameterTree PenParameter now uses GrouParameter instead of popup button - #2097 Add a proxy delay to checklist ParameterItem changes via children edits - #2192 Added option to makeARGB to disable masking NaNs Other: - #1915 Deprecate QtWidgets accessed through QtGui - #2002 isort and pycln prun over entire repo - #2038 Fixed various deprecations - #2045 Examples Directory moved inside project directory - #2051 Implement Pickle Protocol for RemoteGraphicsView - #2053 Disable unneeded call to ViewBox.prepareForPaint() - #2057 Avoid re-dispatching mouse events from the AxisItem - #2064 Add a quantization limit to ViewBox - #2073 Implementing glInfo without PyOpenGL - #2077 Improved error message for invalid PYQTGRAPH_QT_LIB - #2083 Added check for ROI to make sure its in a GraphicsScene - #2093 Add helpful exceptions for invalid inputs to some methods - #2096 Be lazier about importing h5py - #2098 Remove polluting import namespace using * - #2099 Convert == None to is None checks - #2100 Avoid re-using variables in nested loops - #2124 Fixed variety of deprecation warnings - #2154 Fix typos and formatting errors in comments/docstrings - #2194 stop using deprecated numpy.fromstring and use numpy.frombuffer instead 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 - Qt6 6.0 support has been removed in favor of Qt6 6.1+ - More numba utilization, specifically for makeARGB - Substantial ImageItem performance improvements have been made thanks to @pijyoi and @outofculture - Significant performance improvements made to ScatterPlotItem and LinePlots - More ColorMap features/support (more are coming!) New Features - #1318 Added TargetItem - #1707 Added Qt 6.1 support - #1729 Allow gradient position to be configured on a histogram - #1742 Better support for plotting with gradients - #1776 Add GLTextItem - #1797 Add ColorMap linearization (using CIELab calculations), colorDistance functionality - #1865 Include viridis, magma, plasma, inferno, and cividis colormaps - #1868/#1873 Example app now has a filter text box to search for relevant examples Performance enhancement: - #1738, #1695, 1786, #1768, 1794 - ImageItem/makeARGB performance improvements - #1724 Use math module for scalar values math instead of numpy functions - #1796 Greatly speed up line plots with use-case of connect='all' - #1817 Speed up some cases of connect='finite' (few discontinuities) - #1829 Use QPainter.drawPixmapFragments for ScatterPlotItem Bug Fixes: - #1700 Fixed ROI getArrayRegion - #1748 Fixed bug when plotting boolean arrays in PlotDataItem - #1791 Callable LUTs being used on the ImageItem substrates - #1783 Fix memory leak in GLMeshItem - #1802 Updated cx_freeze example and added workaround for template files - #1804 Fix mouseClick handling for Qt6 on ROIs - #1799 Force cameraPosition() to return a Vector in GLViewWidget - #1809 Sanitize ShowGrid Alpha Input PlotItem - #1816 Fix bug with Parameter value failing with numpy array-like values - #1827 Fix BusyCursor to use internal stack provided by setOverrideCursor/restoreOverrideCursor - #1833 Fix ScatterPlot render issues for PyQt6 6.1.0 - #1843 Fix zoom only applied to y-axis with show grid - #1860 Fix pyqtgraph multiprocessing on Windows inside a venv environment - #1869 Fix color bar ticks not being drawn correctly during export - #1865 Fix matplotlib colormap import code - #1876 Fix LineROI handle positions being way off-base in some circumstances - #1871 Allow adding items to GLViewWidget before calling GLViewWidget.show() - #1864 Draw GradientLegend in ViewBox coordinate system with correct orientation - #1875 Fixed mouse events in GLViewWidget for PyQt6 bindings API/Behavior Changes: - #519 Expose clickable property in PlotDataItem - #1772 Keep ColorMap values for RGBA as uint8 - #1736 RemoteGraphicsView is now hidpi aware - #1779 Have SpinBox use fallback minStep in dec mode - #1706 Colors defined with hex string values must start with a # - #1819 Added method to disable autoscaling for HistogramLUTItem - #1638 Expose number of subsamples in ImageItem auto-level determination - #1824 Remove little-endian assumption for image export Other - #1807 Merge pyqtgraph/test-data repo into main repo, move test files to tests directory - #1862 Scheduled deprecation for MetaArray module - #1846 Cleaned up pg namespace pyqtgraph-0.12.1 New Features - #1596 - Add ColorBarItem for simplified image level adjustment Performance enhancement: - #1650 Don't use clip_array on scalers in DynamicPlotRange - #1617 Optimize makeARGB for ubyte images - #1648 Implement blocked variant of rescaleData API/Behavior Change: - #1690 Parameter Item default created from value if not present Bug Fixes: - #1665 Don't pass brush to mkPen in LegendItem.paint() - #1660 Include colormaps in the python wheel - #1653 Fix accidental disabling of style updates in PlotDataItem - #1647 Handle empty adjacency array for GraphItem - #1680 Fix test suite for big-endian architectures - #1694 DateAxisItem now accounts for Daylight Savings Time - #1691 Make sure dynamic range limiter runs on PlotDataItem pyqtgraph-0.12.0 Deprecations: - Qt < 5.12, Python < 3.7, and NumPy < 1.17 are no longer supported New Features: - Qt6 Compatibility (thank you so much @pijyoi !) - #1466 CuPy/CUDA support for ImageItem! (thanks you so much @outofculture !) - #1520/#1518 i18n Localization support for dialogs - #1497 Allow toggling visibility via mouse click on LegendItem - #1527 Expand parameter tree documentation - #1563 Fixes to FlowChart documentation - #1534 Extend pixmaps for GraphIcons - #1566 Toggle-able options for ScatterPlotSpeedTest.py - #1572 Arbitrary scale center ROI - #1581 Equilateral Triangle ROI Performance enhancement: - #1493 significant speedup to invertQTransform() - #1501 various ImageItem performance improvements - #1509 mkQApp will now have settings for better HiDPI settings - #1518 Small Optimizations for functions.rescaleData() - #1556 Reduce reallocation in PlotDataItem dynamic range limiter - #1560 Cache scatter-plot items by hashable properties - #1564 Correct id-based keyring of scatter plot pixmap cache - #1569 Fix ScatterPlotItem performance regression - #1619 Stop PlotDataItem from always sending full style information to PlotCurveItem/ScatterPlotItem - #1630 Combine levels and lut only if both are present - #1632/#1641/#1649 workaround for np.clip regression since numpy 1.17 - #1641 Introduce functions.clip_array as faster replacement for currently slow np.clip - #1637 PlotDataItem Fix viewRange <-> dynamic range limit - #1650 Introduce functions.clip_scalar to clip scalar values API/Behavior Change: - #1476 Use log modulus transform for y-axis log scaling - #1522 InfiniteLine emits clicked signal event - #1525 Use QOpenGLWidget instead of QGLWidget - #1540 Support siPrefix with no suffix in SpinBox - #1541 Use qWaitForWindowExposed instead of qWaitForWindowShown - #1554 Disable paint in GLScatterPlotItem if it has no data - #1573/1576 Add deprecation warnings to portions of library - #1587 qApp.property('darkMode') is now a dynamic property - #1613 Added keys() method to Parameter class - #1646 Removed unhelpful warnings - #1645 Make main stanza PyQt6 compatible - #1644 Deprecate use of hex strings that do not start with "#" in mkColor Bug Fixes: - #1487 Fix InfiniteLabel object has no attribute 'updateText' - #1491 LinearRegionItem would break with setSpan - #1496 enableMenu setting now preserved when passing ViewBox to PlotItem - #1498 PlotDataItem now signals on setPos() - #1500 AlignCenter should have been AlignHCenter - #1506 Fix "camerPosition" typo in GLViewWidget - #1510 Fix RemoteGraphicsView mouse interactions on Qt 5.12 - #1517 Fix RemoteSpeedTest Shutdown Errors - #1528 Support suffix for int parameters in SpinBox - #1546 ImageView guards against key events when there is no time axis - #1558 Fix Small Heights in ErrorBarItem - #1567 Handle 0-d arrays in InfiniteLine.setPos() - #1583 RawImageWidget fix port to QOpenGLWidget - #1594 PlotItem removeItem regression fixed - #1595 Workaround for CuPy Indexing Bug - #1597 Fix RawImageWidget transpose did not handle luminance only images - #1598 Remove references to self from lambdas for Signals - #1618 Install sys.excepthook for PyQt6 - #1639 Fix transformations in GradientLegend - #1647 Have GraphItem handle empty adjacency array - #1653 Fix accidentally styled updates in PlotDataItem - #1651 Use collections.abc for collections metaclasses in colormap.py pyqtgraph-0.11.1 New Features: - #800 Legend for bar graphs - #1244 Arrow scatter symbols - #161 Allow hiding individual points in scatter plot - #395 LegendItem display options - #1310 Added `Pa` to Units - #1310 `debug.ThreadTrace` add support for thread names - #117 Flow Chart Nodes now resized based on number of inputs/outputs - #1154 `DateAxisItem` - #1285 Improve control over ROI/handle pens - #1273 `PColorMeshItem` - #1397 `LegendItem` enable customization of label text size - #1422 Permit entry of non-finite values into float `SpinBox` - #1442 `TickSliderItem.allowRemote` property added - #1441 `Tick.removeAllowed` is now a regular property and used with `TickSliderItem` - #1388 Emit a signal when `GraphicsScene` `addItem` or `removeItem` methods called Performance enhancement: - #1240/#345 GLImageItem clear need update flag - #977 Faster computation option for pseudoscatter - #1297 Improve ArrowItem performance - #1296 Update `h5py` deps in metaarray - #1295 Improve TextItem performance - #1283 Performance improvements to arrayToQPath - #816 Avoid constructing shadow pens when no shadow pen is set - #1311 HistogramLUTItem detect trivial gradients - #1312 avoid extra work when setLabelAngle would have no effect - #1391 cache viewRect of `GraphicsItem` to reduce CPU load - #150 Slight speedup to ViewBox panning - #1420 Many ScatterPlot Improvements API/Behavior Change: - #496 Always antialias lines between gradient and region in HistogramLUTItem - #385 Add headWidth parameter to arrows - #551 fps variable on ImageView - #1251 Allow explicit utcOffset timezone in DateAxisItem - #1310 Add `SignalProxy.block` for temporary disabling of signal forwarding - #1310 `InfiniteLine.setPos` add support for array argument - #1310 Rate-limit Qt event processing in ProgressDialog if it is modal - #1289 Disable autoSIPrefix for DateAxisItem by default - #1274 Add tickAlpha to AxisItem Style Options - #402 Added `clear()` method to `GLViewWidget` - #1264 Added exception to checkOpenGLVersion to highlight OpenGL ES incompatibility - #1257 Make painter tick font dependent for AxisItem - #1256 Added `setState`, `setState` and `saveState` to `ROI` - #1324 Pass through kwargs from MultiPlotItem.plot to PlotItem.plot - #1387 ScatterPlotItem: Make + and x symbols thicker - #1362 Make flowchart.Terminal sortable - #1360 Add "left" and "right" step Modes to PlotCurveItem and PlotDataItem - #1414 Emit event with mouse clicks for some items - #1413 `InfiniteLine.viewTransformChanged` now calls superclass-method - #1411 Hide `WidgetParameterItem.defaultBtn` if param has no default - #1410 add fontSize kwarg to `Dock` - #159 Add wrapping option to `SpinBox` - #330 Set parameter default button `autoDefault` value to `False` - #157 Provide `WidgetGroupInterface` to `GradientWidget` - #151 Optional provide custom `PlotItem` to `PlotWidget` - #1140 Dynamic range limiting in `PlotDataItem` - #1383 GraphicsView set a transparent background palette - #1428 Add default color for `ColorMap` type in `ColorMapWidget` Bug Fixes: - #1239 Avoid adding PlotItem twice - #508 siScale precision - #503 Fix butg in RawImageWidget which resulted in mirrored image - #1242,#1267 Add the mouse event to the sigClicked signal in PlotCurveItem - #1247 Restore the now-deprecated PlotWindow and ImageWindow classes - #1249 Remove ScatterPlotItem's SpotItems during addItem call - #1252 Fix incorrect tick text boundaries calculation on axis by setting the font - #1310 Fixed `Vector.__init__` when used with `QVector3D` argument - #1310 Fix console exception filtering for python3 - #1310 BusyCursor only restore cursor after all nested levels have exited - #1310 `SimpleParameter.setValue` coerces argument to int if parameter type is int - #1307 Fixed `reload` methods for python3 - #1294 Various console fixes - #1293 Various Python3 code fixes - #1282 Handle Axis SI prefix scaling in MatplotlibExporter - #1276 Fix problems with high frequency gaming mice - #1270 Various fixes with AxisItem space being taken - #1272 `LegendItem.clear()` fixed - #1268 Check for container before setting dock orientation - #1312 Avoid divide by 0 condition in TargetItem - #1309 Properly retain and use hoverPen argument in _PolyLineSegment - #1371 Explicitly set line width in PlotCurveItem when using OpenGL - #1319 don't automatically reload modules without pyc - #1319 make ptime.time on py3 return precision wall-clock time - #1334 Edge case detection in PlotCurveItem - #1339 fix handling of QVector3D args in Vector.__init__ - #1336 Make `parent` an explicit kwarg of ArrowItem.__init__, avoid passing into setStyle - #1368 Disconnect from correct slots in Flowchart - #1364 fix log scaling - #963 Allow last image in stack to be selected by slider in ImageView - #1045 Raise AttributeError in __getattr__ in TabWindow - #960 Work around PySide setOverrideCursor bug in BusyCursor - #309 Encode QPropertyAnimation property name if not passed as bytes - #1072 Fix storing of ragged curves in HDF5Exporter - #1275 Fix Parameter.hasDefault - #1291 Get ImageView ROI working with both row and col major data - #1377 Consolidate and fix downsample factor computation in ImageItem - #1376 Fix PlotItem.setAxisItems - #1392 SignalProxy: Correct initialization without slot argument - #1306 Fix incorrect rendering of overlapping object in renderToArray() - #1349 Avoid calling method on undefined attribute - #1367 AxisItem: Account for empty strings in the visibility of text and units - #1356 Fix `ParameterTree` tree name and title handling - #1419 Fix `DataTreeWidget` dict sorting crash - #1408 Fix mouse interaction issues with `DockLabel` - #329 Fix bug where `int` and `float` parameter limits are not always set - #158 Make `DockArea` compatible with Qt Designer - #1405 Fix name setting in `ScatterPlotItem` options - #1403 Do not apply transparent background to Qt4 - #1468 Allow zero step in `ImageItem` - #1464 Remove `ViewBox.childGroup`'s `ItemClipsChildrenToShape` flag - #1461 arrayToQPath revert to old behavior of `connect=ndarray` parameter - #1459 Fix `TickSliderItem` to avoid ghost ticks - #1456 Resolve issue with `PlotCurveItem` with merging PRs in an incorrect order - #1287 Fill in non-finite plot values for Qt versions >= 5.12.3 - #1447 Clipped AxisItem tick value labels to prevent drawing artifacts - #1435 Fix autosize not taking to the correct range with `TextItem` in view - #1452 merge `InfiniteLine` caching calls - #1446 Fix `PlotDataItem.setData([], [])` - #1443 Fix Viewbox axis zoom in RectMode - #1439 Fix `TickSliderItem.setTickValue` when it references a `GradientEditorItem` method - #1361 Prevent item duplication in `Node` context menu - #1423 Fix typo in `kwargs` for `GridItem.setTextPen` - #1401 Fix width, height and background in SVG exporter - #1416 Handle case in `ROI` when `shape`, `vectors`, or `origin` keywords are passed in Maintenance: - #389 Revert workaround for upstream QT bug regarding mouse events - #1243 Ensure setPos in ROI is initialized correctly - #1241 Pin pytest-xvfb version on py2 - #1310 Added tests for `functions.subArray` - #1307 Add `ThreadSafeTimer` to `__init__.py` - #467 derivatePlots cleanup - #400 `ImageView.Timeline` better visibility - #1265 Make the documentation reproducible - #1319 clean up exception messages in console - #356 Fix some NumPy warnings - #1326 Improve docs for MultiPlotWidget and MultiPlotItem - #1331 Migrate imports of PyQt5's sip module to new namespace - #1370 Fix deprecation warning in multiprocess module - #308 Fix opt name for SpinBox: range -> bounds in UnsharpMaskNode example - #887 Update collections.abc imports - #1142 Miscellaneous doc fixups - #1169 Avoid using mutable default argument value - #1073 Python3 fixes - #1284 Update doc strings to clarify getArrayRegion API for ROI subclasses - #1042 Close windows at the end of test functions - #1374 Test warnings cleanup - #1375 Add targeted Vector test coverage - #1384 GLViewWidget.pan docstring typo - #1382 Autoformat LegendItem - #1396 Add tests for GraphicsView - #1399 Disable mouse rate limiting on test_ROI - #1409 Prepend conda-forge channel prior to env creation in CI - #1302 Fix Example app now works with Qt4/Python2 - #1402 Handle case of version string having no `+` - #1400 Fix sphinx warnings on `PColorMeshItem` - #1328 Add docs build job to CI - #1464 Use `conda-forge` on pyside2+linux - #1448 Fixes for `examples/CustomPlot.py` - #1432 ExampleApp fix to use `pg` module from directory pyqtgraph-0.11.0 NOTICE: This is the _last_ feature release to support Python 2 and Qt 4 (PyQt4 or pyside 1) New Features: - #101: GridItem formatting options - #410: SpinBox custom formatting options - #415: ROI.getArrayRegion supports nearest-neighbor interpolation (especially handy for label images) - #428: DataTreeWidget: - Add DiffTreeWidget, which highlights differences between two DataTreeWidgets - Improved support for displaying tracebacks - Use TableWidget to represent arrays rather than plain text - #446: Added Perceptually Uniform Sequential colormaps from the matplotlib 2.0 release - #476: Add option to set composition mode for scatterplotitem - #518: TreeWidget: - Add new signals: sigItemCheckStateChanged, sigItemTextChanged, sigColumnCountChanged - Allow setting expansion state of items before they are added to a treewidget - Support for using TreeWidget.invisibleRootItem() (see also #592, #595) - #542: Add collapsible QGroupBox widgets - #543: Add TargetItem: simple graphicsitem that draws a scale-invariant circle + crosshair - #544: Make DockArea.restoreState behavior configurable in cases where either a dock to be restored is missing, or an extra dock exists that is not mentioned in the restore state. - #545: Allow more types to be mapped through Transform3D - #548: Adds a disconnect() function that allows to conditionally disconnect signals, including after reload. Also, a SignalBlock class used to temporarily block a signal-slot pair - #557: Allow console stack to be set outside of exceptions (see also: pg.stack) - #558: CanvasItem save/restore, make Canvas ui easier to embed - #559: Image exporter gets option to invert value while leaving hue fixed - #560: Add function to enable faulthandler on all threads, also allow Mutex to be used as drop-in replacement for python's Lock - #567: Flowchart - Add several new data nodes - Add floordiv node - Add EvalNode.setCode - Binary operator nodes can select output array type - #568: LinearRegionItem - InfiniteLine can draw markers attached to the line - InfiniteLine can limit the region of the viewbox over which it is drawn - LinearRegionItem gets customizable line swap behavior (lines can block or push each other) - Added LinearRegionItem.setHoverBrush - #580: Allow calling sip.setapi in subprocess before pyqtgraph is imported - #582: Add ComboBox save/restoreState methods - #586: ParameterTree - Add GroupParameter.sigAddNew signal - systemsolver: add method for checking constraints / DOF - add systemsolver copy method - Parameter.child raises KeyError if requested child name does not exist - #587: Make PathButton margin customizable - #588: Add PlotCurveItem composition mode - #589: Add RulerROI - #591: Add nested progress dialogs - #597: Fancy new interactive fractal demo - #621: RGB mode for HistogramLUTWidget - #628,670: Add point selection in ScatterPlotWidget - #635: PySide2 support - #671: Add SVG export option to force non-scaling stroke - #676: OpenGL allow for panning in the plane of the camera - #683: Allow data filter entries to be updated after they are created - #685: Add option to set enum default values in DataFilterWidget - #710: Adds ability to rotate/scale ROIs by mouse drag on the ROI itself (using alt/shift modifiers) - #813,814,817: Performance improvements - #837: Added options for field variables in ColorMapWidget - #840, 932: Improve clipping behavior - #841: Set color of tick-labels separately - #922: Curve fill for fill-patches - #996: Allow the update of LegendItem - #1023: Add bookkeeping exporter parameters - #1072: HDF5Exporter handling of ragged curves with tests - #1124: Syntax highlighting for examples. - #1154: Date axis item - #393: NEW show/hide gradient ticks NEW link gradientEditor to others - #1211: Add support for running pyside2-uic binary to dynamically compile ui files API / behavior changes: - Deprecated graphicsWindow classes; these have been unnecessary for many years because widgets can be placed into a new window just by calling show(). - #158: Make DockArea compatible with Qt Designer - #406: Applying alpha mask on numpy.nan data values - #566: ArrowItem's `angle` option now rotates the arrow without affecting its coordinate system. The result is visually the same, but children of ArrowItem are no longer rotated (this allows screen-aligned text to be attached more easily). To mimic the old behavior, use ArrowItem.rotate() instead of the `angle` argument. - #673: Integer values in ParameterTree are now formatted as integer (%d) by default, rather than scientific notation (%g). This can be overridden by providing `format={value:g}` when creating the parameter. - #374: ConsoleWidget uses the console's namespace as both global and local scope, which - #410: SpinBox siPrefix without suffix is not longer allowed, select only numerical portion of text on focus-in allows functions defined in the console to access the global namespace. - #479,521: ParameterTree simple parameters check types before setting value - #555: multiprocess using callSync='sync' no longer returns a future in case of timeout - #583: eq() no longer compares array values if they have different shape - #589: Remove SpiralROI (this was unintentionally added in the first case) - #593: Override qAbort on slot exceptions for PyQt>=5.5 - #657: When a floating Dock window is closed, the dock is now returned home - #771: Suppress RuntimeWarning for arrays containing zeros in logscale - #942: If the visible GraphicsView is garbage collected, a warning is issued. - #958: Nicer Legend - #963: Last image in image-stack can now be selected with the z-slider - #992: Added a setter for GlGridItem.color. - #999: Make outline around fillLevel optional. - #1014: Enable various arguments as color in colormap. - #1044: Raise AttributeError in __getattr__ in graphicsWindows (deprecated) - #1055: Remove global for CONFIG_OPTIONS in setConfigOption - #1066: Add RemoteGraphicsView to __init__.py - #1069: Allow actions to display title instead of name - #1074: Validate min/max text inputs in ViewBoxMenu - #1076: Reset currentRow and currentCol on GraphicsLayout.clear() - #1079: Improve performance of updateData PlotCurveItem - #1082: Allow MetaArray.__array__ to accept an optional dtype arg - #841: set color of tick-labels separately - #1111: Add name label to GradientEditorItem - #1145: Pass showAxRect keyword arguments to setRange - #1184: improve SymbolAtlas.getSymbolCoords performance - #1198: improve SymbolAtlas.getSymbolCoords and ScatterPlotItem.plot performance - #1197: Disable remove ROI menu action in handle context menu - #1188: Added support for plot curve to handle both fill and connect args - #801: Remove use of GraphicsScene._addressCache in translateGraphicsItem - Deprecates registerObject meethod of GraphicsScene - Deprecates regstar argument to GraphicsScene.__init__ - #1166: pg.mkQApp: Pass non-empty string array to QApplication() as default - #1199: Pass non-empty sys.argv to QApplication - #1090: dump ExportDialog.exporterParameters - #1173: GraphicsLayout: Always call layout.activate() after adding items - #1097: pretty-print log-scale axes labels - #755: Check lastDownsample in viewTransformChanged - #1216: Add cache for mapRectFromView - #444: Fix duplicate menus in GradientEditorItem - #151: Optionally provide custom PlotItem to PlotWidget - #1093: Fix aspectRatio and zoom range issues when zooming - #390: moved some functionality from method 'export' to new method - #468: Patch/window handling - #392: new method 'getAxpectRatio' with code taken from 'setAspectLocked' - #1206: Added context menu option to parametertree - #1228: Minor improvements to LegendItem Bugfixes: - #88: Fixed image scatterplot export - #356: Fix some NumPy warnings - #408: Fix `cleanup` when the running qt application is not a QApplication - #410: SpinBox fixes - fixed bug with exponents disappearing after edit - fixed parsing of values with junk after suffix - fixed red border - reverted default decimals to 6 - make suffix editable (but show red border if it's wrong) - revert invalid text on focus lost - siPrefix without suffix is no longer allowed - fixed parametree sending invalid options to spinbox - fix spinbox wrapping (merged #159 from @lidstrom83) - fixed parametertree ignoring spinbox bounds (merged #329 from @lidstrom83) - fixed spinbox height too small for font size - ROI subclass getArrayRegion methods are a bit more consistent (still need work) - #424: Fix crash when running pyqtgraph with python -OO - #429: Fix fft premature slicing away of 0 freq bin - #458: Fixed image export problems with new numpy API - #478: Fixed PySide image memory leak - #475: Fixed unicode error when exporting to SVG with non-ascii symbols - #477: Fix handling of the value argument to functions.intColor - #485: Fixed incorrect height in VTickGroup - #514: Fixes bug where ViewBox emits sigRangeChanged before it has marked its transform dirty. - #516,668: Fix GL Views being half size on hidpi monitors - #526: Fix autorange exception with empty scatterplot - #528: Prevent image downsampling causing exception in makeQImage - #530: Fixed issue where setData only updated opts if data is given - #541: Fixed issue where render would error because 'mapToDevice' would return None if the view size was too small. - #553: Fixed legend size after remove item - #555: Fixed console color issues, problems with subprocess closing - #559: HDF5 exporter: check for ragged array length - #563: Prevent viewbox auto-scaling to items that are not in the same scene. (This could happen if an item that was previously added to the viewbox is then removed using scene.removeItem(). - #564: Allow console exception label to wrap text (prevents console growing too large for long exception messages) - #565: Fixed AxisItem preventing mouse events reaching the ViewBox if it is displaying grid lines and has its Z value set higher than the ViewBox. - #567: fix flowchart spinbox bounds - #569: PlotItem.addLegend will not try to add more than once - #570: ViewBox: make sure transform is up to date in all mapping functions - #577: Fix bargraphitem plotting horizontal bars - #581: Fix colormapwidget saveState - #586: ParameterTree - Make parameter name,value inint args go through setValue and setName - Fix colormapwidget saveState - #589: Fix click area for small ellipse/circle ROIs - #592,595: Fix InvisibleRootItem issues introduced in #518 - #596: Fix polyline click causing lines to bedrawn to the wrong node - #598: Better ParameterTree support for dark themes - #599: Prevent invalid list access in GraphicsScene - #623: Fix PyQt5 / ScatterPlot issue with custom symbols - #626: Fix OpenGL texture state leaking to wrong items - #627: Fix ConsoleWidget stack handling on python 3.5 - #633: Fix OpenGL cylinder geometry - #637: Fix TypeError in isosurface - #641,642: Fix SVG export on Qt5 / high-DPI displays - #645: ScatterPlotWidget behaves nicely when data contains infs - #653: ScatterPlotItem: Fix a GC memory leak due to numpy issue 6581 - #648: fix color ignored in GLGridItem - #671: Fixed SVG export failing if the first value of a plot is nan - #674: Fixed parallelizer leaking file handles - #675: Gracefully handle case where image data has size==0 - #679: Fix overflow in Point.length() - #682: Fix: mkQApp returned None if a QApplication was already created elsewhere - #689: ViewBox fix: don't call setRange with empty args - #693: Fix GLLinePlotItem setting color - #696: Fix error when using PlotDataItem with both stepMode and symbol - #697: Fix SpinBox validation on python 3 - #699: Fix nan handling in ImageItem.setData - #713: ConsoleWidget: Fixed up/down arrows sometimes unable to get back to the original (usually blank) input state - #715: Fix file dialog handling in Qt 5 - #718: Fix SVG export with items that require option.exposedRect - #721: Fixes mouse wheel ignoring disabled mouse axes -- although the scaling was correct, it was causing auto range to be disabled. - #723: Fix axis ticks when using self.scale - #739: Fix handling of 2-axis mouse wheel events - #742: Fix Metaarray in python 3 - #758: Fix remote graphicsview "ValueError: mmap length is greater than file size" on OSX. - #763: Fix OverflowError when using Auto Downsampling. - #767: Fix Image display for images with the same value everywhere. - #770: Fix GLVieWidget.setCameraPosition ignoring first parameter. - #782: Fix missing FileForwarder thread termination. - #787: Fix encoding errors in checkOpenGLVersion. - #793: Fix wrong default scaling in makeARGB - #815: Fixed mirroring of x-axis with "invert Axis" submenu. - #824: Fix several issues related with mouse movement and GraphicsView. - #832: Fix Permission error in tests due to unclosed filehandle. - #836: Fix tickSpacing bug that lead to axis not being drawn. - #861: Fix crash of PlotWidget if empty ErrorBarItem is added. - #868: Fix segfault on repeated closing of matplotlib exporter. - #875,876,887,934,947,980: Fix deprecation warnings. - #886: Fix flowchart saving on python3. - #888: Fix TreeWidget.topLevelItems in python3. - #924: Fix QWheelEvent in RemoteGraphicsView with pyqt5. - #935: Fix PlotItem.addLine with 'pos' and 'angle' parameter. - #949: Fix multiline parameters (such as arrays) reading from config files. - #951: Fix event firing from scale handler. - #952: Fix RotateFree handle dragging - #953: Fix HistogramLUTWidget with background parameter - #968: Fix Si units in AxisItem leading to an incorrect unit. - #970: Always update transform when setting angle of a TextItem - #971: Fix a segfault stemming from incorrect signal disconnection. - #972: Correctly include SI units for log AxisItems - #974: Fix recursion error when instancing CtrlNode. - #987: Fix visibility reset when PlotItems are removed. - #998: Fix QtProcess proxy being unable to handle numpy arrays with dtype uint8. - #1010: Fix matplotlib/CSV export. - #1012: Fix circular texture centering - #1015: Iterators are now converted to NumPy arrays. - #1016: Fix synchronisation of multiple ImageViews with time axis. - #1017: Fix duplicate paint calls emitted by Items on ViewBox. - #1019: Fix disappearing GLGridItems when PlotItems are removed and readded. - #1024: Prevent element-wise string comparison - #1031: Reset ParentItem to None on removing from PlotItem/ViewBox - #1044: Fix PlotCurveItem.paintGL - #1048: Fix bounding box for InfiniteLine - #1062: Fix flowchart context menu redundant menu - #1062: Fix a typo - #1073: Fix Python3 compatibility - #1083: Fix SVG export of scatter plots - #1085: Fix ofset when drawing symbol - #1101: Fix small oversight in LegendItem - #1113: Correctly call hasFaceIndexedData function - #1139: Bug fix in LegendItem for `setPen`, `setBrush` etc (Call update instead of paint) - #1110: fix for makeARGB error after #955 - #1063: Fix: AttributeError in ViewBox.setEnableMenu - #1151: ImageExporter py2-pyside fix with test - #1133: compatibility-fix for py2/pyside - #1152: Nanmask fix in makeARGB - #1159: Fix: Update axes after data is set - #1156: SVGExporter: Correct image pixelation - #1169: Replace default list arg with None - #770: Do not ignore pos argument of setCameraPosition - #1180: Fix: AxisItem tickFont is defined in two places while only one is used - #1168: GroupParameterItem: Did not pass changed options to ParameterItem - #1174: Fixed a possible race condition with linked views - #809: Fix selection of FlowchartWidget input/output nodes - #1071: Fix py3 execution in flowchart - #1212: Fix PixelVectors cache - #1161: Correctly import numpy where needed - #1218: Fix ParameterTree.clear() - #1175: Fix: Parameter tree ignores user-set 'expanded' state - #1219: Encode csv export header as unicode - #507: Fix Dock close event QLabel still running with no parent - #1222: py3 fix for ScatterPlotWidget.setSelectedFields - #1203: Image axis order bugfix - #1225: ParameterTree: Fix custom context menu Maintenance: - Lots of new unit tests - Lots of code cleanup - A lot of work on CI pipelines, test coverage and test passing (see e.g. #903,911) - #546: Add check for EINTR during example testing to avoid sporadic test failures on travis - #624: TravisCI no longer running python 2.6 tests - #695: "dev0" added to version string - #865,873,877 (and more): Implement Azure CI pipelines, fix Travis CI - #991: Use Azure Pipelines to do style checks, Add .pre-commit-config.yaml - #1042: Close windows at the end of test functions - #1046: Establish minimum numpy version, remove legacy workarounds - #1067: Make scipy dependency optional - #1114: doc: Fix small mistake in introduction - #1131: Update CI/tox and Enable More Tests - #1142: Miscellaneous doc fixups - #1179: DateAxisItem: AxisItem unlinking tests and doc fixed - #1201: Get readthedocs working - #1214: Pin PyVirtualDisplay Version - #1215: Skip test when on qt 5.9 - #1221: Identify pyqt5 5.15 ci issue - #1223: Remove workaround for memory leak in QImage - #1217: Get docs version and copyright year dynamically - #1229: Wrap text in tables in docs - #1231: Update readme for 0.11 release pyqtgraph-0.10.0 New Features: - PyQt5 support - Options for interpreting image data as either row-major or col-major - InfiniteLine and LinearRegionItem can have attached labels - DockArea: - Dock titles can be changed after creation - Added Dock.sigClosed - Added TextItem.setColor() - FillBetweenItem supports finite-connected curves (those that exclude nan/inf) API / behavior changes: - Improved ImageItem performance for some data types by scaling LUT instead of image - Change the defaut color kwarg to None in TextItem.setText() to avoid changing the color every time the text is changed. - FFT plots skip first sample if x-axis uses log scaling - Multiprocessing system adds bytes and unicode to the default list of no-proxy data types - Version number scheme changed to be PEP440-compliant (only affects installations from non- release git commits) Bugfixes: - Fix for numpy API change that caused casting errors for inplace operations - Fixed git version string generation on python3 - Fixed setting default values for out-of-bound points in pg.interpolateArray - Fixed plot downsampling bug on python 3 - Fixed invalid slice in ImageItem.getHistogram - DockArea: - Fixed adding Docks to DockArea after all Docks have been removed - Fixed DockArea save/restoreState when area is empty - Properly remove select box when export dialog is closed using window decorations - Remove all modifications to python builtins - Better Python 2.6 compatibility - Fix SpinBox decimals - Fixed numerous issues with ImageItem automatic downsampling - Fixed PlotItem average curves using incorrect stepMode - Fixed TableWidget eating key events - Prevent redundant updating of flowchart nodes with multiple inputs - Ignore wheel events in GraphicsView if mouse interaction is disabled - Correctly pass calls to QWidget.close() up the inheritance chain - ColorMap forces color inputs to be sorted - Fixed memory mapping for RemoteGraphicsView in OSX - Fixed QPropertyAnimation str/bytes handling - Fixed __version__ string update when using `setup.py install` with newer setuptools Maintenance: - Image comparison system for unit testing plus tests for several graphics items - Travis CI and coveralls/codecov support - Add examples to unit tests pyqtgraph-0.9.10 Fixed installation issues with more recent pip versions. pyqtgraph-0.9.9 API / behavior changes: - Dynamic import system abandoned; pg now uses static imports throughout. - Flowcharts and exporters have new pluggin systems - Version strings: - __init__.py in git repo now contains latest release version string (previously, only packaged releases had version strings). - installing from git checkout that does not correspond to a release commit will result in a more descriptive version string. - Speed improvements in functions.makeARGB - ImageItem is faster by avoiding makeQImage(transpose=True) - ComboBox will raise error when adding multiple items of the same name - ArrowItem.setStyle now updates style options rather than replacing them - Renamed GraphicsView signals to avoid collision with ViewBox signals that are wrapped in PlotWidget: sigRangeChanged => sigDeviceRangeChanged and sigTransformChanged => sigDeviceTransformChanged. - GLViewWidget.itemsAt() now measures y from top of widget to match mouse event position. - Made setPen() methods consistent throughout the package - Fix in GLScatterPlotItem requires that points will appear slightly more opaque (so you may need to adjust to lower alpha to achieve the same results) New Features: - Added ViewBox.setLimits() method - Adde ImageItem downsampling - New HDF5 example for working with very large datasets - Removed all dependency on scipy - Added Qt.loadUiType function for PySide - Simplified Profilers; can be activated with environmental variables - Added Dock.raiseDock() method - ComboBox updates: - Essentially a graphical interface to dict; all items have text and value - Assigns previously-selected text after list is cleared and repopulated - Get, set current value - Flowchart updates - Added Flowchart.sigChartChanged - Custom nodes may now be registered in sub-menu trees - ImageItem.getHistogram is more clever about constructing histograms - Added FillBetweenItem.setCurves() - MultiPlotWidget now has setMinimumPlotHeight method and displays scroll bar when plots do not fit inside the widget. - Added BarGraphItem.shape() to allow better mouse interaction - Added MeshData.cylinder - Added ViewBox.setBackgroundColor() and GLViewWidget.setBackgroundColor() - Utilities / debugging tools - Mutex used for tracing deadlocks - Color output on terminal - Multiprocess debugging colors messages by process - Stdout filter that colors text by thread - PeriodicTrace used to report deadlocks - Added AxisItem.setStyle() - Added configurable formatting for TableWidget - Added 'stepMode' argument to PlotDataItem() - Added ViewBox.invertX() - Docks now have optional close button - Added InfiniteLine.setHoverPen - Added GLVolumeItem.setData - Added PolyLineROI.setPoints, clearPoints, saveState, setState - Added ErrorBarItem.setData Bugfixes: - PlotCurveItem now has correct clicking behavior--clicks within a few px of the line will trigger a signal. - Fixes related to CSV exporter: - CSV headers include data names, if available - Exporter correctly handles items with no data - pg.plot() avoids creating empty data item - removed call to reduce() from exporter; not available in python 3 - Gave .name() methods to PlotDataItem, PlotCurveItem, and ScatterPlotItem - fixed ImageItem handling of rgb images - fixed makeARGB re-ordering of color channels - fixed unicode usage in AxisItem tick strings - fixed PlotCurveItem generating exceptions when data has length=0 - fixed ImageView.setImage only working once - PolyLineROI.setPen() now changes the pen of its segments as well - Prevent divide-by-zero in AxisItem - Major speedup when using ScatterPlotItem in pxMode - PlotCurveItem ignores clip-to-view when auto range is enabled - FillBetweenItem now forces PlotCurveItem to generate path - Fixed import errors and py3 issues in MultiPlotWidget - Isosurface works for arrays with shapes > 255 - Fixed ImageItem exception building histogram when image has only one value - Fixed MeshData exception caused when vertexes have no matching faces - Fixed GLViewWidget exception handler - Fixed unicode support in Dock - Fixed PySide crash caused by emitting signal from GraphicsObject.itemChange - Fixed possible infinite loop from FiniteCache - Allow images with NaN in ImageView - MeshData can generate edges from face-indexed vertexes - Fixed multiprocess deadlocks on windows - Fixed GLGridItem.setSize - Fixed parametertree.Parameter.sigValueChanging - Fixed AxisItem.__init__(showValues=False) - Fixed TableWidget append / sort issues - Fixed AxisItem not resizing text area when setTicks() is used - Removed a few cyclic references - Fixed Parameter 'readonly' option for bool, color, and text parameter types - Fixed alpha on GLScatterPlotItem spots (formerly maxed out at alpha=200) - Fixed a few bugs causing exit crashes pyqtgraph-0.9.8 2013-11-24 API / behavior changes: - ViewBox will auto-range when ImageItem changes shape - AxisItem: - Smarter about deciding which ticks get text - AxisItem.setScale(float) has the usual behavior, but .setScale(None) is deprecated. Instead use: AxisItem.enableAutoSIPrefix(bool) to enable/disable SI prefix scaling - Removed inf/nan checking from PlotDataItem and PlotCurveItem; improved performance New Features: - Support for dynamic downsampling and view clipping in PlotDataItem and PlotItem - Added 'connect' option to PlotDataItem and PlotCurveItem to affect which line segments are drawn - Support for FFT with non-uniform time sampling - Added BarGraphItem - OpenGL: - Added export methods to GLViewWidget - Wireframe meshes - GLLinePLotItem gets antialiasing, accepts array of colors - GLMeshItem accepts ShaderProgram or name of predefined program - Added GLBarGraphItem - LegendItem: - User-draggable - Allow custom ItemSamples - Symbol support - Support for removing items - ScatterPlotWidget, ColorMapWidget, and DataFilterWidget are stable - TableWidget: - Made numerically sortable - Added setEditable method - AxisItem ability to truncate axis lines at the last tick - arrayToQPath() added 'finite' connection mode which omits non-finite values from connections - pg.plot() and pg.PlotWidget() now accept background argument - Allow QtProcess without local QApplication - Support for dashing in mkPen() - Added Dock.close() - Added style options to flowchart connection lines - Added parentChanged and viewChanged hooks to GraphicsItem - Bidirectional pseudoScatter for beeswarm plots - Added exit() function for working around PyQt exit crashes - Added PolylineROI.getArrayRegion() Bugfixes: - Many Python 3 compatibility fixes - AxisItem: - Correctly handles scaling with values that are not power of 10 - Did not update grid line length when plot stretches - Fixed unicode handling in AxisItem label - ViewBox: - Overhauled to fix issues with aspect locking - ViewBox context menu elements are no longer deleted when using flowchart with pyside - Fixed view linking with inverted y axis - Prevent auto-range disabling when dragging with one mouse axis diabled - Ignore inf and nan when auto-ranging - ParameterTree: - fixed TextParameter editor disappearing after focus lost - ListParameter: allow unhashable types as parameter values. - Exporting: - ImageExporter correctly handles QBrush with style=NoBrush - SVGExporter text, gradients working correctly - SVGExporter correctly handles coordinate corrections for groups with mixed elements - ImageView: - Fixed auto-levelling when normalization options change - Added autoHistogramRange argument to setImage - ScatterPlotItem: - Fixed crashes caused by ScatterPlotItem - Fixed antialiasing - arrayToQPath performance improved for python 3 - Fixed makeQImage on many platforms (notably, on newer PyQt APIs) - Removed unnecessary scipy imports for faster import - GraphItem reports pixel margins to improve auto-range - Add backport ordereddict to repository; old OrderedDict class is removed - Corrected behavior of GraphicsView.setBackground - Fixed PySide bug listing image formats - Fixed QString -> str conversions in flowchart - Unicode file name support when exporting - Fixed MatplotlibWidget + PySide - Fixed 3D view updating after every scene change - Fixed handling of non-native dtypes when optimizing with weave - RemoteGraphicsView fixed for PyQt 4.10, Python 3 - Fixed GLLinePlotItem line width option - HistogramLUTWidget obeys default background color - ScaleBar complete rewrite - GraphItem obeys antialiasing flag - Workaround for PySide/QByteArray memory leak - Fixed example --test on windows, python3 - Luke finished dissertation pyqtgraph-0.9.7 2013-02-25 Bugfixes: - ArrowItem auto range now works correctly - Dock drag/drop fixed on PySide - Made padding behavior consistent across ViewBox methods - Fixed MeshData / python2.6 incompatibility - Fixed ScatterPlotItem.setSize and .setPointData - Workaround for PySide bug; GradientEditor fixed - Prefer initially selecting PlotItem rather then ViewBox when exporting - Fixed python3 import error with flowcharts Cleaned up examples, made code editable from example loader Minor documentation updates Features: - Added GraphItem class for displaying networks/trees - Added ColorMap class for mapping linear gradients and generating lookup tables (Provides gradient editor functionality without the GUI) - Added ColorMapWidget for complex user-defined color mapping - Added ScatterPlotWidget for exploring relationships in multi-column tables - Added ErrorBarItem - SVG and image exporters can now copy to clipboard - PlotItem gets new methods: addLine, setLabels, and listDataItems - AxisItem gets setTickFont method - Added functions.arrayToQPath, shared between GraphItem and PlotCurveItem - Added gradient editors to parametertree - Expanded documentation, added beginning of Qt crash course Bugfixes: - Fixed auto-ranging bugs: ViewBox now properly handles pixel-padding around data items - ViewBox ignores bounds of zoom-rect when auto ranging - Fixed AxisItem artifacts - Fixed GraphicsItem.pixelVector caching bugs and simplified workaround for fp-precision errors - LinearRegionItem.hoverEvent obeys 'movable' flag - Fixed PlotDataItem nan masking bugs - Workaround for segmentation fault in QPainter.drawPixmapFragments - multiprocess and RemoteGraphicsView work correctly in Windows. - Expanded python 3 support - Silenced weave errors by default - Fixed " 'win' in sys.platform " occurrences matching 'darwin' (duh) - Workaround for change in QImage API (PyQt 4.9.6) - Fixed axis ordering bug in GLScatterPlotItem pyqtgraph-0.9.6 2013-02-14 Features: - Added GraphItem class for displaying networks/trees - Added ColorMap class for mapping linear gradients and generating lookup tables (Provides gradient editor functionality without the GUI) - Added ColorMapWidget for complex user-defined color mapping - Added ScatterPlotWidget for exploring relationships in multi-column tables - Added ErrorBarItem - SVG and image exporters can now copy to clipboard - PlotItem gets new methods: addLine, setLabels, and listDataItems - AxisItem gets setTickFont method - Added functions.arrayToQPath, shared between GraphItem and PlotCurveItem - Added gradient editors to parametertree - Expanded documentation, added beginning of Qt crash course Bugfixes: - Fixed auto-ranging bugs: ViewBox now properly handles pixel-padding around data items - ViewBox ignores bounds of zoom-rect when auto ranging - Fixed AxisItem artifacts - Fixed GraphicsItem.pixelVector caching bugs and simplified workaround for fp-precision errors - LinearRegionItem.hoverEvent obeys 'movable' flag - Fixed PlotDataItem nan masking bugs - Workaround for segmentation fault in QPainter.drawPixmapFragments - multiprocess and RemoteGraphicsView work correctly in Windows. - Expanded python 3 support - Silenced weave errors by default - Fixed " 'win' in sys.platform " occurrences matching 'darwin' (duh) - Workaround for change in QImage API (PyQt 4.9.6) - Fixed axis ordering bug in GLScatterPlotItem Plotting performance improvements: - AxisItem shows fewer tick levels in some cases. - Lots of boundingRect and dataBounds caching (improves ViewBox auto-range performance, especially with multiple plots) - GraphicsScene avoids testing for hover intersections with non-hoverable items (much less slowdown when moving mouse over plots) Improved performance for remote plotting: - reduced cost of transferring arrays between processes (pickle is too slow) - avoid unnecessary synchronous calls Added RemoteSpeedTest example pyqtgraph-0.9.5 2013-01-11 Plotting performance improvements: - AxisItem shows fewer tick levels in some cases. - Lots of boundingRect and dataBounds caching (improves ViewBox auto-range performance, especially with multiple plots) - GraphicsScene avoids testing for hover intersections with non-hoverable items (much less slowdown when moving mouse over plots) Improved performance for remote plotting: - reduced cost of transferring arrays between processes (pickle is too slow) - avoid unnecessary synchronous calls Added RemoteSpeedTest example Documentation: - Added documentation on export system - Added flowchart documentation and custom node example Bugfixes: - prevent PlotCurveItem drawing shadow when unnecessary - deprecated flowchart.Node.__getattr__ -- causes too many problems. pyqtgraph-0.9.4 2013-01-07 Documentation: - Added documentation on export system - Added flowchart documentation and custom node example Bugfixes: - prevent PlotCurveItem drawing shadow when unnecessary - deprecated flowchart.Node.__getattr__ -- causes too many problems. Bugfix: prevent adding invalid entry to sys.path when running examples pyqtgraph-0.9.3 2012-12-29 Bugfix: prevent adding invalid entry to sys.path when running examples Bugfixes: - SVG export text elements use generic font-family as backup, corrected item transformation issues - Fixed RuntimeError caused when clearing item hierarchies from ViewBox - Fixed example execution bug Packaging maintenance: - Added missing files to MANIFEST.in, fixed setup.py package detection - Added debian control files for building source packages - Fixed version numbering in doc, __init__.py pyqtgraph-0.9.2 2012-12-29 Bugfixes: - SVG export text elements use generic font-family as backup, corrected item transformation issues - Fixed RuntimeError caused when clearing item hierarchies from ViewBox - Fixed example execution bug Packaging maintenance: - Added missing files to MANIFEST.in, fixed setup.py package detection - Added debian control files for building source packages - Fixed version numbering in doc, __init__.py pyqtgraph-0.9.1 2012-12-27 Removed incorrect version numbers Correction to setup.py - use install_requires to inform pip of dependencies. Fixed doc version (again) Added debian control files bugfixes for new package structure pyqtgraph-0.9.0 2012-12-27 * Initial release. pyqtgraph-pyqtgraph-0.12.4/CODE_OF_CONDUCT.md000066400000000000000000000176311421045507400204430ustar00rootroot00000000000000# Code of Conduct Diversity is one of our huge strengths, but it can also lead to communication issues. To support a welcoming environment for all, regardless of individual differences, we have a few ground rules that we ask people to adhere to when they participate in this community. These rules apply equally to founders, organizers, contributors, users and affiliates β€” in short, to all participants. This isn’t an exhaustive list of things that you must do, or can’t do. Rather, take it in the spirit in which it’s intended. It’s a guide to make it easier to enrich all of us and the technical communities in which we participate, and which we represent. ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of background or identity. This includes, but is not limited to, members of any race, ethnicity, culture, national origin, color, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, level of experience, and mental or physical ability. We pledge to be respectful. Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the PyQtGraph community should be respectful when dealing with each other as well as with people outside the community. We pledge to be welcoming, supportive, kind and professional. We pledge to act and interact in ways that contribute to an open, diverse, inclusive, and healthy community. We pledge not insult or put down other participants, individually or as a group. Harassment and other exclusionary behavior aren’t acceptable. ## Examples Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * Violent threats or language directed against another person. * Discriminatory jokes and language. * Posting sexually explicit or violent material. * Posting (or threatening to post) other people’s personally identifying information ("doxing"). * Personal insults, especially those using racist or sexist terms. * Unwelcome sexual attention. * Repeated harassment of others. In general, if someone asks you to stop, then stop. * Advocating for, or encouraging, any of the above behavior. ## Where does this code of conduct apply This Code of Conduct applies within all online community spaces, official organized meetups, and when an individual is officially representing the community in public spaces, online or offline. ## What to do in case of violations If you believe someone is violating the code of conduct, we ask that you report it by contacting any of the following individuals, being sure to include mention of your message being about a "PyQtGraph conduct violation": * Ognyan Moore @j9ac9k * Martin Chase @outofculture * Nathan Jessurun @ntjess All complaints will be reviewed and investigated promptly and fairly. All reports will be kept confidential. In some cases, we may determine that a public statement will need to be made. If that’s the case, the identities of all victims and reporters will remain confidential unless those individuals instruct us otherwise. If you believe anyone is in physical danger, please notify appropriate law enforcement first. If you are unsure which law enforcement agency is appropriate, please include this in your report and we will attempt to notify them. ### What to include in the report * Your contact information (so we can get in touch with you if we need to follow up) * Names of any individuals involved (real names, nicknames, or pseudonyms). If there were other witnesses besides you, please try to include them as well. * When and where the incident occurred. Please be as specific as possible. * Your account of what occurred. If there is a publicly available record (e.g., a mailing list archive or a public chat logger) please include a link. Our Slack is currently on a free plan that does not retain more than 10,000 messages of history, if an incident occurred on Slack please take a screenshot so it is not lost. * Any extra context you believe existed for the incident. * Whether you believe this incident is ongoing. * Any other information you believe we should have. ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. This removal should be as transparent as possible, with information available in the same scope of context as the original moderated content, linking to an incident report, the acting moderators, and/or this document as appropriate. All community leaders are obligated to respect the privacy and security of the reporter of any incident. Upon receiving a report of misconduct, the staff must review the incident and determine the following: * What happened. * Whether this event constitutes a code of conduct violation. * Who the bad actor(s) was. * Whether this is an ongoing situation. * If any previous disiplinary measures are relevent and in effect. * If there is a threat to anyone’s physical safety. * What impact this has had on the community. After the conduct committee has a complete account of the events they need to make a decision as to how to resolve the case. Resolutions might include: * Nothing (if we determine no violation occurred). * A private reprimand from the staff to the individual(s) involved, providing clarity around the nature of the violation, and an explanation of why the behavior was inappropriate. * A request for a public or private apology. * A public reprimand. * An imposed vacation (for example, asking someone to "take a week off" from Slack). * A permanent or temporary ban from some or all PyQtGraph spaces (Slack, github, mailing list or events) or from communicating, in public or in private, with some are all of the members of our community. * Contacting law enforcement. A response must be sent within one week to the person who filed the report with either a resolution or an explanation of why the situation is not yet resolved. A Code of Conduct transparency report will then be published to [the PyQtGraph users' list](https://groups.google.com/g/pyqtgraph) with anonymized information about any violations that might have occurred. This report should be handled with care not to divulge personally identifying information about victims, reporters, and violators, and should serve as a means to ensure that members will be comfortable reporting violations and that our community will be kept accountable for supporting and encouraging safe spaces. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant, version 2.0](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html) and the [PuPPy Code of Conduct](https://www.pspython.com/pages/code-of-conduct/). pyqtgraph-pyqtgraph-0.12.4/CONTRIBUTING.md000066400000000000000000000103211421045507400200620ustar00rootroot00000000000000# Contributing to PyQtGraph Contributions to pyqtgraph are welcome! Be kind and respectful! See [our Code of Conduct](CODE_OF_CONDUCT.md) for details. Please use the following guidelines when preparing changes: ## Development Environment Creation First thing to do is fork the repository, and clone your own fork to your local computer. ```bash git clone https://github.com//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`](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 ### 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. * 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 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. ## Testing ### Basic Setup * tox * pytest * pytest-cov * pytest-xdist * Optional: pytest-xvfb (used on linux with headless displays) To run the test suite, after installing the above dependencies run ```bash python -m pytest pyqtgraph/examples tests ``` ### Tox 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. ### Continuous Integration 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 ``` ( TODO publish results ) pyqtgraph-pyqtgraph-0.12.4/LICENSE.txt000066400000000000000000000022101421045507400174520ustar00rootroot00000000000000Copyright (c) 2012 University of North Carolina at Chapel Hill Luke Campagnola ('luke.campagnola@%s.com' % 'gmail') The MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pyqtgraph-pyqtgraph-0.12.4/MANIFEST.in000066400000000000000000000005541421045507400173760ustar00rootroot00000000000000recursive-include pyqtgraph *.py *.ui *.m README.* *.txt *.gz *.cfg recursive-include doc *.rst *.py *.svg *.png recursive-include doc/build/html * recursive-include tools * recursive-include tests * recursive-include benchmarks *.py include doc/Makefile doc/make.bat README.md LICENSE.txt CHANGELOG CODE_OF_CONDUCT.md CONTRIBUTING.md tox.ini global-exclude *.pyc pyqtgraph-pyqtgraph-0.12.4/README.md000066400000000000000000000157551421045507400171300ustar00rootroot00000000000000PyQtGraph ========= [![PyPi](https://img.shields.io/pypi/v/pyqtgraph.svg)](https://pypi.org/project/pyqtgraph/) [![conda-forge](https://img.shields.io/conda/vn/conda-forge/pyqtgraph.svg)](https://anaconda.org/conda-forge/pyqtgraph) [![Build Status](https://github.com/pyqtgraph/pyqtgraph/workflows/main/badge.svg)](https://github.com/pyqtgraph/pyqtgraph/actions/?query=workflow%3Amain) [![CodeQL Status](https://github.com/pyqtgraph/pyqtgraph/workflows/codeql/badge.svg)](https://github.com/pyqtgraph/pyqtgraph/actions/?query=workflow%3Acodeql) [![Documentation Status](https://readthedocs.org/projects/pyqtgraph/badge/?version=latest)](https://pyqtgraph.readthedocs.io/en/latest/?badge=latest) [![Total alerts](https://img.shields.io/lgtm/alerts/g/pyqtgraph/pyqtgraph.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/pyqtgraph/pyqtgraph/alerts/) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/pyqtgraph/pyqtgraph.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/pyqtgraph/pyqtgraph/context:python) A pure-Python graphics library for PyQt5/PyQt6/PySide2/PySide6 Copyright 2020 Luke Campagnola, University of North Carolina at Chapel Hill PyQtGraph is intended for use in mathematics / scientific / engineering applications. Despite being written entirely in python, the library is fast due to its heavy leverage of numpy for number crunching, Qt's GraphicsView framework for 2D display, and OpenGL for 3D display. Requirements ------------ PyQtGraph has adopted [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html). This project supports: * All minor versions of Python released 42 months prior to the project, and at minimum the two latest minor versions. * All minor versions of numpy released in the 24 months prior to the project, and at minimum the last three minor versions. * All Qt5 versions from 5.12-5.15, and Qt6 6.1+ Currently this means: * Python 3.7+ * Qt 5.12-5.15, 6.1+ * [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 Through 3rd part libraries, additional functionality may be added to PyQtGraph, see the table below for a summary. | Library | Added functionality | |----------------|-| | [`scipy`] |
  • Image processing through [`ndimage`]
  • Data array filtering through [`signal`]
    • | | [`pyopengl`] |
      • 3D graphics
      • Faster image processing
      • Note: on macOS Big Sur only works with python 3.9.1+
      | | [`h5py`] |
      • Export in hdf5 format
      | | [`colorcet`] |
      • Add a collection of perceptually uniform colormaps
      | | [`matplotlib`] |
      • Export of PlotItem in matplotlib figure
      • Add matplotlib collection of colormaps
      | | [`cupy`] |
      • CUDA-enhanced image processing
      • Note: On Windows, CUDA toolkit must be >= 11.1
      | | [`numba`] |
      • Faster image processing
      | | [`jupyter_rfb`]|
      • Jupyter Notebook support
      • [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/pyqtgraph/pyqtgraph/HEAD?labpath=pyqtgraph%2Fexamples%2Fnotebooks)
      | [`scipy`]: https://github.com/scipy/scipy [`ndimage`]: https://docs.scipy.org/doc/scipy/reference/ndimage.html [`signal`]: https://docs.scipy.org/doc/scipy/reference/signal.html [`pyopengl`]: https://github.com/mcfletch/pyopengl [`h5py`]: https://github.com/h5py/h5py [`colorcet`]: https://github.com/holoviz/colorcet [`matplotlib`]: https://github.com/matplotlib/matplotlib [`numba`]: https://github.com/numba/numba [`cupy`]: https://docs.cupy.dev/en/stable/install.html [`jupyter_rfb`]: https://github.com/vispy/jupyter_rfb Qt Bindings Test Matrix ----------------------- The following table represents the python environments we test in our CI system. Our CI system uses Ubuntu 20.04, Windows Server 2019, and macOS 10.15 base images. | Qt-Bindings | Python 3.7 | Python 3.8 | Python 3.9 | Python 3.10 | | :------------- | :----------------: | :----------------: | :----------------: | :----------------: | | PySide2-5.12 | :white_check_mark: | :x: | :x: | :x: | | PyQt5-5.12 | :white_check_mark: | | :x: | :x: | | PySide2-5.15 | | :white_check_mark: | | | | PyQt5-5.15 | | :white_check_mark: | | | | PySide6-6.2 | | | :white_check_mark: | :white_check_mark: | | PyQt6-6.2 | | | :white_check_mark: | :white_check_mark: | * :x: - Not compatible * :white_check_mark: - Tested Support ------- * Report issues on the [GitHub issue tracker](https://github.com/pyqtgraph/pyqtgraph/issues) * Post questions to the [mailing list / forum](https://groups.google.com/forum/?fromgroups#!forum/pyqtgraph) or [StackOverflow](https://stackoverflow.com/questions/tagged/pyqtgraph) Installation Methods -------------------- * From PyPI: * Last released version: `pip install pyqtgraph` * Latest development version: `pip install git+https://github.com/pyqtgraph/pyqtgraph@master` * From conda * Last released version: `conda install -c conda-forge pyqtgraph` * To install system-wide from source distribution: `python setup.py install` * Many linux package repositories have release versions. * To use with a specific project, simply copy the PyQtGraph subdirectory anywhere that is importable from your project. 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. Used By ------- Here is a partial listing of some of the applications that make use of PyQtGraph! * [ACQ4](https://github.com/acq4/acq4) * [Orange3](https://orangedatamining.com/) * [neurotic](https://neurotic.readthedocs.io) * [ephyviewer](https://ephyviewer.readthedocs.io) * [Joulescope](https://www.joulescope.com/) * [rapidtide](https://rapidtide.readthedocs.io/en/latest/) * [argos](https://github.com/titusjan/argos) * [PySpectra](http://hasyweb.desy.de/services/computing/Spock/node138.html) * [Semi-Supervised Semantic Annotator](https://gitlab.com/ficsresearch/s3ah) * [PyMeasure](https://github.com/pymeasure/pymeasure) * [Exo-Striker](https://github.com/3fon3fonov/exostriker) * [HussariX](https://github.com/sem-geologist/HussariX) * [EnMAP-Box](https://enmap-box.readthedocs.io) * [EO Time Series Viewer](https://eo-time-series-viewer.readthedocs.io) Do you use PyQtGraph in your own project, and want to add it to the list? Submit a pull request to update this listing! pyqtgraph-pyqtgraph-0.12.4/benchmarks/000077500000000000000000000000001421045507400177515ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/benchmarks/__init__.py000066400000000000000000000000001421045507400220500ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/benchmarks/arrayToQPath.py000066400000000000000000000016541421045507400227100ustar00rootroot00000000000000import 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 pyqtgraph-pyqtgraph-0.12.4/benchmarks/renderImageItem.py000066400000000000000000000106061421045507400233670ustar00rootroot00000000000000import numpy as np import pyqtgraph as pg try: import cupy as cp pg.setConfigOption("useCupy", True) except ImportError: cp = None try: import numba except ImportError: numba = None def renderQImage(*args, **kwargs): imgitem = pg.ImageItem(axisOrder='row-major') if 'autoLevels' not in kwargs: kwargs['autoLevels'] = False imgitem.setImage(*args, **kwargs) imgitem.render() def prime_numba(): shape = (64, 64) lut_small = np.random.randint(256, size=(256, 3), dtype=np.uint8) lut_big = np.random.randint(256, size=(512, 3), dtype=np.uint8) for lut in [lut_small, lut_big]: renderQImage(np.zeros(shape, dtype=np.uint8), levels=(20, 220), lut=lut) renderQImage(np.zeros(shape, dtype=np.uint16), levels=(250, 3000), lut=lut) renderQImage(np.zeros(shape, dtype=np.float32), levels=(-4.0, 4.0), lut=lut) class _TimeSuite(object): def __init__(self): super(_TimeSuite, self).__init__() self.size = None self.float_data = None self.uint8_data = None self.uint8_lut = None self.uint16_data = None self.uint16_lut = None self.cupy_uint16_lut = None self.cupy_uint8_lut = None def setup(self): size = (self.size, self.size) self.float_data, self.uint16_data, self.uint8_data, self.uint16_lut, self.uint8_lut = self._create_data( size, np ) if numba is not None: # ensure JIT compilation pg.setConfigOption("useNumba", True) prime_numba() pg.setConfigOption("useNumba", False) if cp: _d1, _d2, _d3, self.cupy_uint16_lut, self.cupy_uint8_lut = self._create_data(size, cp) renderQImage(cp.asarray(self.uint16_data["data"])) # prime the gpu @property def numba_uint16_lut(self): return self.uint16_lut @property def numba_uint8_lut(self): return self.uint8_lut @property def numpy_uint16_lut(self): return self.uint16_lut @property def numpy_uint8_lut(self): return self.uint8_lut @staticmethod def _create_data(size, xp): float_data = { "data": xp.random.normal(size=size).astype("float32"), "levels": [-4.0, 4.0], } uint16_data = { "data": xp.random.randint(100, 4500, size=size).astype("uint16"), "levels": [250, 3000], } uint8_data = { "data": xp.random.randint(0, 255, size=size).astype("ubyte"), "levels": [20, 220], } c_map = xp.array([[-500.0, 255.0], [-255.0, 255.0], [0.0, 500.0]]) uint8_lut = xp.zeros((256, 4), dtype="ubyte") for i in range(3): uint8_lut[:, i] = xp.clip(xp.linspace(c_map[i][0], c_map[i][1], 256), 0, 255) uint8_lut[:, 3] = 255 uint16_lut = xp.zeros((2 ** 16, 4), dtype="ubyte") for i in range(3): uint16_lut[:, i] = xp.clip(xp.linspace(c_map[i][0], c_map[i][1], 2 ** 16), 0, 255) uint16_lut[:, 3] = 255 return float_data, uint16_data, uint8_data, uint16_lut, uint8_lut def make_test(dtype, kind, use_levels, lut_name, func_name): def time_test(self): data = getattr(self, dtype + "_data") levels = data["levels"] if use_levels else None lut = getattr(self, f"{kind}_{lut_name}_lut", None) if lut_name is not None else None pg.setConfigOption("useNumba", kind == "numba") img_data = data["data"] if kind == "cupy": img_data = cp.asarray(img_data) renderQImage(img_data, lut=lut, levels=levels) time_test.__name__ = func_name return time_test for option in ["cupy", "numba", "numpy"]: if option == "cupy" and cp is None: continue if option == "numba" and numba is None: continue for data_type in ["float", "uint16", "uint8"]: for lvls in [True, False]: if data_type == "float" and not lvls: continue for lutname in [None, "uint8", "uint16"]: name = ( f'time_1x_renderImageItem_{option}_{data_type}_{"" if lvls else "no"}levels_{lutname or "no"}lut' ) setattr(_TimeSuite, name, make_test(data_type, option, lvls, lutname, name)) class Time4096Suite(_TimeSuite): def __init__(self): super(Time4096Suite, self).__init__() self.size = 4096 pyqtgraph-pyqtgraph-0.12.4/binder/000077500000000000000000000000001421045507400170775ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/binder/apt.txt000066400000000000000000000000201421045507400204140ustar00rootroot00000000000000libgl1-mesa-glx pyqtgraph-pyqtgraph-0.12.4/binder/requirements.txt000066400000000000000000000001231421045507400223570ustar00rootroot00000000000000git+https://github.com/pyqtgraph/pyqtgraph.git pyqt5 jupyter_rfb numpy simplejpeg pyqtgraph-pyqtgraph-0.12.4/binder/start000066400000000000000000000000721421045507400201560ustar00rootroot00000000000000#!/bin/bash export QT_QPA_PLATFORM=offscreen exec "$@" pyqtgraph-pyqtgraph-0.12.4/doc/000077500000000000000000000000001421045507400164015ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/Makefile000066400000000000000000000107761421045507400200540ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyqtgraph.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyqtgraph.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/pyqtgraph" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyqtgraph" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." pyqtgraph-pyqtgraph-0.12.4/doc/extensions/000077500000000000000000000000001421045507400206005ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/extensions/qt_doc.py000066400000000000000000000112671421045507400224320ustar00rootroot00000000000000""" Extension for building Qt-like documentation. - Method lists preceding the actual method documentation - Inherited members documented separately - Members inherited from Qt have links to qt-project documentation - Signal documentation """ def setup(app): # probably we will be making a wrapper around autodoc app.setup_extension('sphinx.ext.autodoc') # would it be useful to define a new domain? #app.add_domain(QtDomain) ## Add new configuration options app.add_config_value('todo_include_todos', False, False) ## Nodes are the basic objects representing documentation directives ## and roles app.add_node(Todolist) app.add_node(Todo, html=(visit_todo_node, depart_todo_node), latex=(visit_todo_node, depart_todo_node), text=(visit_todo_node, depart_todo_node)) ## New directives like ".. todo:" app.add_directive('todo', TodoDirective) app.add_directive('todolist', TodolistDirective) ## Connect callbacks to specific hooks in the build process app.connect('doctree-resolved', process_todo_nodes) app.connect('env-purge-doc', purge_todos) from docutils import nodes from sphinx.util.compat import Directive from sphinx.util.compat import make_admonition # Just a general node class Todolist(nodes.General, nodes.Element): pass # .. and its directive class TodolistDirective(Directive): # all directives have 'run' method that returns a list of nodes def run(self): return [Todolist('')] # Admonition classes are like notes or warnings class Todo(nodes.Admonition, nodes.Element): pass def visit_todo_node(self, node): self.visit_admonition(node) def depart_todo_node(self, node): self.depart_admonition(node) class TodoDirective(Directive): # this enables content in the directive has_content = True def run(self): env = self.state.document.settings.env # create a new target node for linking to targetid = "todo-%d" % env.new_serialno('todo') targetnode = nodes.target('', '', ids=[targetid]) # make the admonition node ad = make_admonition(Todo, self.name, [('Todo')], self.options, self.content, self.lineno, self.content_offset, self.block_text, self.state, self.state_machine) # store a handle in a global list of all todos if not hasattr(env, 'todo_all_todos'): env.todo_all_todos = [] env.todo_all_todos.append({ 'docname': env.docname, 'lineno': self.lineno, 'todo': ad[0].deepcopy(), 'target': targetnode, }) # return both the linking target and the node itself return [targetnode] + ad # env data is persistent across source files so we purge whenever the source file has changed. def purge_todos(app, env, docname): if not hasattr(env, 'todo_all_todos'): return env.todo_all_todos = [todo for todo in env.todo_all_todos if todo['docname'] != docname] # called at the end of resolving phase; we will convert temporary nodes # into finalized nodes def process_todo_nodes(app, doctree, fromdocname): if not app.config.todo_include_todos: for node in doctree.traverse(Todo): node.parent.remove(node) # Replace all todolist nodes with a list of the collected todos. # Augment each todo with a backlink to the original location. env = app.builder.env for node in doctree.traverse(Todolist): if not app.config.todo_include_todos: node.replace_self([]) continue content = [] for todo_info in env.todo_all_todos: para = nodes.paragraph() filename = env.doc2path(todo_info['docname'], base=None) description = ( ('(The original entry is located in %s, line %d and can be found ') % (filename, todo_info['lineno'])) para += nodes.Text(description, description) # Create a reference newnode = nodes.reference('', '') innernode = nodes.emphasis(('here'), ('here')) newnode['refdocname'] = todo_info['docname'] newnode['refuri'] = app.builder.get_relative_uri( fromdocname, todo_info['docname']) newnode['refuri'] += '#' + todo_info['target']['refid'] newnode.append(innernode) para += newnode para += nodes.Text('.)', '.)') # Insert into the todolist content.append(todo_info['todo']) content.append(para) node.replace_self(content) pyqtgraph-pyqtgraph-0.12.4/doc/listmissing.py000066400000000000000000000007431421045507400213240ustar00rootroot00000000000000import os dirs = [ ('graphicsItems', 'graphicsItems'), ('3dgraphics', 'opengl/items'), ('widgets', 'widgets'), ] path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..') for a, b in dirs: rst = [os.path.splitext(x)[0].lower() for x in os.listdir(os.path.join(path, 'documentation', 'source', a))] py = [os.path.splitext(x)[0].lower() for x in os.listdir(os.path.join(path, b))] print(a) for x in set(py) - set(rst): print( " ", x) pyqtgraph-pyqtgraph-0.12.4/doc/make.bat000066400000000000000000000100241421045507400200030ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyqtgraph.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyqtgraph.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end pyqtgraph-pyqtgraph-0.12.4/doc/requirements.txt000066400000000000000000000000751421045507400216670ustar00rootroot00000000000000pyside2 numpy pyopengl sphinx==4.1.1 sphinx_rtd_theme==0.5.2 pyqtgraph-pyqtgraph-0.12.4/doc/source/000077500000000000000000000000001421045507400177015ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics.rst000066400000000000000000000025131421045507400224630ustar00rootroot000000000000003D Graphics =========== PyQtGraph uses OpenGL to provide a 3D scenegraph system. This system is functional but still early in development. Current capabilities include: * 3D view widget with zoom/rotate controls (mouse drag and wheel) * Scenegraph allowing items to be added/removed from scene with per-item transformations and parent/child relationships. * Triangular meshes * Basic mesh computation functions: isosurfaces, per-vertex normals * Volumetric rendering item * Grid/axis items See the :doc:`API Reference ` and the Volumetric (GLVolumeItem.py) and Isosurface (GLMeshItem.py) examples for more information. Basic usage example:: ## build a QApplication before building other widgets import pyqtgraph as pg pg.mkQApp() ## make a widget for displaying 3D objects import pyqtgraph.opengl as gl view = gl.GLViewWidget() view.show() ## create three grids, add each to the view xgrid = gl.GLGridItem() ygrid = gl.GLGridItem() zgrid = gl.GLGridItem() view.addItem(xgrid) view.addItem(ygrid) view.addItem(zgrid) ## rotate x and y grids to face the correct direction xgrid.rotate(90, 0, 1, 0) ygrid.rotate(90, 1, 0, 0) ## scale each grid differently xgrid.scale(0.2, 0.1, 0.1) ygrid.scale(0.2, 0.1, 0.1) zgrid.scale(0.1, 0.2, 0.1) pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/000077500000000000000000000000001421045507400217305ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/glaxisitem.rst000066400000000000000000000002121421045507400246230ustar00rootroot00000000000000GLAxisItem ========== .. autoclass:: pyqtgraph.opengl.GLAxisItem :members: .. automethod:: pyqtgraph.opengl.GLAxisItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/glgraphicsitem.rst000066400000000000000000000002701421045507400254630ustar00rootroot00000000000000GLGraphicsItem ============== .. autoclass:: pyqtgraph.opengl.GLGraphicsItem.GLGraphicsItem :members: .. automethod:: pyqtgraph.opengl.GLGraphicsItem.GLGraphicsItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/glgraphitem.rst000066400000000000000000000002101421045507400247560ustar00rootroot00000000000000GLGraphItem =========== .. autoclass:: pyqtgraph.opengl.GLGraphItem :members: .. automethod:: pyqtgraph.opengl.GLGraphItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/glgriditem.rst000066400000000000000000000002121421045507400246040ustar00rootroot00000000000000GLGridItem ========== .. autoclass:: pyqtgraph.opengl.GLGridItem :members: .. automethod:: pyqtgraph.opengl.GLGridItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/glimageitem.rst000066400000000000000000000002161421045507400247450ustar00rootroot00000000000000GLImageItem =========== .. autoclass:: pyqtgraph.opengl.GLImageItem :members: .. automethod:: pyqtgraph.opengl.GLImageItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/gllineplotitem.rst000066400000000000000000000002321421045507400255070ustar00rootroot00000000000000GLLinePlotItem ============== .. autoclass:: pyqtgraph.opengl.GLLinePlotItem :members: .. automethod:: pyqtgraph.opengl.GLLinePlotItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/glmeshitem.rst000066400000000000000000000002121421045507400246130ustar00rootroot00000000000000GLMeshItem ========== .. autoclass:: pyqtgraph.opengl.GLMeshItem :members: .. automethod:: pyqtgraph.opengl.GLMeshItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/glscatterplotitem.rst000066400000000000000000000002461421045507400262320ustar00rootroot00000000000000GLScatterPlotItem ================= .. autoclass:: pyqtgraph.opengl.GLScatterPlotItem :members: .. automethod:: pyqtgraph.opengl.GLScatterPlotItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/glsurfaceplotitem.rst000066400000000000000000000002461421045507400262150ustar00rootroot00000000000000GLSurfacePlotItem ================= .. autoclass:: pyqtgraph.opengl.GLSurfacePlotItem :members: .. automethod:: pyqtgraph.opengl.GLSurfacePlotItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/glviewwidget.rst000066400000000000000000000002221421045507400251570ustar00rootroot00000000000000GLViewWidget ============ .. autoclass:: pyqtgraph.opengl.GLViewWidget :members: .. automethod:: pyqtgraph.opengl.GLViewWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/glvolumeitem.rst000066400000000000000000000002221421045507400251670ustar00rootroot00000000000000GLVolumeItem ============ .. autoclass:: pyqtgraph.opengl.GLVolumeItem :members: .. automethod:: pyqtgraph.opengl.GLVolumeItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/index.rst000066400000000000000000000017411421045507400235740ustar00rootroot00000000000000PyQtGraph's 3D Graphics System ============================== The 3D graphics system in pyqtgraph is composed of a :class:`view widget ` and several graphics items (all subclasses of :class:`GLGraphicsItem `) which can be added to a view widget. **Note 1:** pyqtgraph.opengl is based on the deprecated OpenGL fixed-function pipeline. Although it is currently a functioning system, it is likely to be superceded in the future by `VisPy `_. **Note 2:** use of this system requires python-opengl bindings. Linux users should install the python-opengl packages from their distribution. Windows/OSX users can download from ``_. Contents: .. toctree:: :maxdepth: 2 glviewwidget glgriditem glgraphitem glsurfaceplotitem glvolumeitem glimageitem glmeshitem gllineplotitem glaxisitem glgraphicsitem glscatterplotitem meshdata pyqtgraph-pyqtgraph-0.12.4/doc/source/3dgraphics/meshdata.rst000066400000000000000000000002021421045507400242420ustar00rootroot00000000000000MeshData ======== .. autoclass:: pyqtgraph.opengl.MeshData :members: .. automethod:: pyqtgraph.opengl.MeshData.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/_static/000077500000000000000000000000001421045507400213275ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/source/_static/custom.css000066400000000000000000000006071421045507400233560ustar00rootroot00000000000000/* Customizations to the theme */ /* override table width restrictions */ /* https://github.com/readthedocs/sphinx_rtd_theme/issues/117 */ @media screen and (min-width: 768px) { .wy-table-responsive table td, .wy-table-responsive table th { white-space: normal !important; } .wy-table-responsive { overflow: visible !important; max-width: 100%; } } pyqtgraph-pyqtgraph-0.12.4/doc/source/apireference.rst000066400000000000000000000004311421045507400230610ustar00rootroot00000000000000API Reference ============= Contents: .. toctree:: :maxdepth: 2 config_options functions graphicsItems/index widgets/index 3dgraphics/index colormap parametertree/apiref dockarea graphicsscene/index flowchart/index graphicswindow pyqtgraph-pyqtgraph-0.12.4/doc/source/colormap.rst000066400000000000000000000057721421045507400222620ustar00rootroot00000000000000.. _apiref_colormap: Color Maps ========== A color map defines a relationship between scalar data values and a range of colors. Color maps are commonly used to generate false color images, color scatter-plot points, and illustrate the height of surface plots. PyQtGraph's :class:`~pyqtgraph.ColorMap` can conveniently be applied to images and interactively adjusted by using :class:`~pyqtgraph.ColorBarItem`. To provide interactively user-defined color mappings, see :class:`~pyqtgraph.GradientEditorItem` and :class:`~pyqtgraph.GradientWidget`, which wraps it. :class:`~pyqtgraph.GradientEditorItem` combines the editing with a histogram and controls for interactively adjusting image levels. Setting a color map and adding a color bar can be as simple as:: plot.addItem( img_item := pg.ImageItem(img_data) ) plot.addColorBar( img_item, colorMap='viridis', values=(0, 1) ) ``` ColorMap can also be used a convenient source of colors from a consistent palette or to generate :class:`QPen` and :class:`QBrush` objects used to draw lines and fills that are colored according to their values along the horizontal or vertical axis. Sources for color maps ---------------------- Color maps can be user defined by assigning a number of *stops* over the range of 0 to 1. A color is given for each stop, and the in-between values are generated by interpolation. When map colors directly represent values, an improperly designed map can obscure detail over certain ranges of values, while creating false detail in others. PyQtGraph includes the perceptually uniform color maps provided by the `Colorcet project `_. Color maps can also be imported from the ``colorcet`` library or from ``matplotlib``, if either of these is installed. To see all available color maps, please run the `ColorMap` demonstration available in the suite of :ref:`examples`. Examples -------- False color display of a 2D data set. Display levels are controlled by a :class:`ColorBarItem `: .. literalinclude:: images/gen_example_false_color_image.py :lines: 18-28 :dedent: 8 Using QtGui.QPen and QtGui.QBrush to color plots according to the plotted value: .. literalinclude:: images/gen_example_gradient_plot.py :lines: 16-33 :dedent: 8 .. image:: images/example_false_color_image.png :width: 49% :alt: Example of a false color image .. image:: images/example_gradient_plot.png :width: 49% :alt: Example of drawing and filling plots with gradients The use of color maps is also demonstrated in the `ImageView`, `Color Gradient Plots` and `ColorBarItem` :ref:`examples`. API Reference ------------- .. autofunction:: pyqtgraph.colormap.listMaps .. autofunction:: pyqtgraph.colormap.get .. autofunction:: pyqtgraph.colormap.getFromMatplotlib .. autofunction:: pyqtgraph.colormap.getFromColorcet .. autofunction:: pyqtgraph.colormap.modulatedBarData .. autoclass:: pyqtgraph.ColorMap :members: .. automethod:: pyqtgraph.ColorMap.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/conf.py000066400000000000000000000165611421045507400212110ustar00rootroot00000000000000# # pyqtgraph documentation build configuration file, created by # sphinx-quickstart on Fri Nov 18 19:33:12 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import time import sys import os from datetime import datetime # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(path, '..', '..')) sys.path.insert(0, os.path.join(path, '..', 'extensions')) import pyqtgraph # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'pyqtgraph' now = datetime.utcfromtimestamp( int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) ) copyright = '2011 - {}, Luke Campagnola'.format(now.year) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = pyqtgraph.__version__ # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] autodoc_inherit_docstrings = False autodoc_mock_imports = [ "scipy", "h5py", "matplotlib", ] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # add the theme customizations def setup(app): app.add_css_file("custom.css") # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'pyqtgraphdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'pyqtgraph.tex', 'pyqtgraph Documentation', 'Luke Campagnola', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pyqtgraph', 'pyqtgraph Documentation', ['Luke Campagnola'], 1) ] pyqtgraph-pyqtgraph-0.12.4/doc/source/config_options.rst000066400000000000000000000070671421045507400234650ustar00rootroot00000000000000.. currentmodule:: pyqtgraph .. _apiref_config: Global Configuration Options ============================ PyQtGraph has several global configuration options that allow you to change its default behavior. These can be accessed using the :func:`setConfigOptions` and :func:`getConfigOption` functions: ================== =================== ================== ================================================================================ **Option** **Type** **Default** leftButtonPan bool True If True, dragging the left mouse button over a ViewBox causes the view to be panned. If False, then dragging the left mouse button draws a rectangle that the ViewBox will zoom to. foreground See :func:`mkColor` 'd' Default foreground color for text, lines, axes, etc. background See :func:`mkColor` 'k' Default background for :class:`GraphicsView`. antialias bool False Enabling antialiasing causes lines to be drawn with smooth edges at the cost of reduced performance. imageAxisOrder str 'col-major' For 'row-major', image data is expected in the standard row-major (row, col) order. For 'col-major', image data is expected in reversed column-major (col, row) order. The default is 'col-major' for backward compatibility, but this may change in the future. editorCommand str or None None Command used to invoke code editor from ConsoleWidget. exitCleanup bool True Attempt to work around some exit crash bugs in PyQt and PySide. useOpenGL bool False Enable OpenGL in GraphicsView. useCupy bool False Use cupy to perform calculations on the GPU. Only currently applies to ImageItem and its associated functions. useNumba bool False Use numba acceleration where implemented. enableExperimental bool False Enable experimental features (the curious can search for this key in the code). In combination with useOpenGL, this makes PlotCurveItem use PyOpenGL for curve drawing. **Caveats** * Only a very limited subset of the full options of PlotCurveItem is implemented. * Single precision is used. This may cause drawing artifacts. crashWarning bool False If True, print warnings about situations that may result in a crash. ================== =================== ================== ================================================================================ .. autofunction:: pyqtgraph.setConfigOptions .. autofunction:: pyqtgraph.getConfigOption pyqtgraph-pyqtgraph-0.12.4/doc/source/dockarea.rst000066400000000000000000000003041421045507400222010ustar00rootroot00000000000000Dock Area Module ================ .. automodule:: pyqtgraph.dockarea :members: .. autoclass:: pyqtgraph.dockarea.DockArea :members: .. autoclass:: pyqtgraph.dockarea.Dock :members: pyqtgraph-pyqtgraph-0.12.4/doc/source/exporting.rst000066400000000000000000000062511421045507400224560ustar00rootroot00000000000000Exporting ========= PyQtGraph provides a variety of export formats for all 2D graphics. For 3D graphics, see `Exporting 3D Graphics`_ below. Exporting from the GUI ---------------------- Any 2D graphics can be exported by right-clicking on the graphic, then selecting 'export' from the context menu. This will display the export dialog in which the user must: #. Select an item (or the entire scene) to export. Selecting an item will cause the item to be hilighted in the original graphic window (but this hilight will not be displayed in the exported file). #. Select an export format. #. Change any desired export options. #. Click the 'export' button. Export Formats -------------- * Image - PNG is the default format. The exact set of image formats supported will depend on your Qt libraries. However, common formats such as PNG, JPG, and TIFF are almost always available. * SVG - Graphics exported as SVG are targeted to work as well as possible with both Inkscape and Adobe Illustrator. For high quality SVG export, please use PyQtGraph version 0.9.3 or later. This is the preferred method for generating publication graphics from PyQtGraph. * CSV - Exports plotted data as CSV. This exporter _only_ works if a PlotItem is selected for export. * Matplotlib - This exporter opens a new window and attempts to re-plot the data using matplotlib (if available). Note that some graphic features are either not implemented for this exporter or not available in matplotlib. This exporter _only_ works if a PlotItem is selected for export. * Printer - Exports to the operating system's printing service. This exporter is provided for completeness, but is not well supported due to problems with Qt's printing system. * HDF5 - Exports data from a :class:`~pyqtgraph.PlotItem` to a HDF5 file if h5py_ is installed. This exporter supports :class:`~pyqtgraph.PlotItem` objects containing multiple curves, stacking the data into a single HDF5 dataset based on the ``columnMode`` parameter. If data items aren't the same size, each one is given its own dataset. .. _h5py: https://www.h5py.org/ Exporting from the API ---------------------- To export a file programatically, follow this example: .. code-block:: python import pyqtgraph as pg import pyqtgraph.exporters # generate something to export plt = pg.plot([1,5,2,4,3]) # create an exporter instance, as an argument give it # the item you wish to export exporter = pg.exporters.ImageExporter(plt.plotItem) # set export parameters if needed exporter.parameters()['width'] = 100 # (note this also affects height parameter) # save to file exporter.export('fileName.png') To export the overall layout of a GraphicsLayoutWidget `grl`, the exporter initialization is .. code-block:: python exporter = pg.exporters.ImageExporter( grl.scene() ) instead. Exporting 3D Graphics --------------------- The exporting functionality described above is not yet available for 3D graphics. However, it is possible to generate an image from a GLViewWidget by using QGLWidget.grabFrameBuffer or QGLWidget.renderPixmap:: glview.grabFrameBuffer().save('fileName.png') See the Qt documentation for more information. pyqtgraph-pyqtgraph-0.12.4/doc/source/flowchart/000077500000000000000000000000001421045507400216725ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/source/flowchart/flowchart.rst000066400000000000000000000002401421045507400244110ustar00rootroot00000000000000flowchart.Flowchart =================== .. autoclass:: pyqtgraph.flowchart.Flowchart :members: .. automethod:: pyqtgraph.flowchart.Flowchart.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/flowchart/index.rst000066400000000000000000000241131421045507400235340ustar00rootroot00000000000000Visual Programming with Flowcharts ================================== PyQtGraph's flowcharts provide a visual programming environment similar in concept to LabView--functional modules are added to a flowchart and connected by wires to define a more complex and arbitrarily configurable algorithm. A small number of predefined modules (called Nodes) are included with pyqtgraph, but most flowchart developers will want to define their own library of Nodes. At their core, the Nodes are little more than 1) a python function 2) a list of input/output terminals, and 3) an optional widget providing a control panel for the Node. Nodes may transmit/receive any type of Python object via their terminals. One major limitation of flowcharts is that there is no mechanism for looping within a flowchart. (however individual Nodes may contain loops (they may contain any Python code at all), and an entire flowchart may be executed from within a loop). There are two distinct modes of executing the code in a flowchart: 1. Provide data to the input terminals of the flowchart. This method is slower and will provide a graphical representation of the data as it passes through the flowchart. This is useful for debugging as it allows the user to inspect the data at each terminal and see where exceptions occurred within the flowchart. 2. Call :func:`Flowchart.process() `. This method does not update the displayed state of the flowchart and only retains the state of each terminal as long as it is needed. Additionally, Nodes which do not contribute to the output values of the flowchart (such as plotting nodes) are ignored. This mode allows for faster processing of large data sets and avoids memory issues which can occur if too much data is present in the flowchart at once (e.g., when processing image data through several stages). See the flowchart example for more information. API Reference: .. toctree:: :maxdepth: 2 flowchart node terminal Basic Use --------- Flowcharts are most useful in situations where you have a processing stage in your application that you would like to be arbitrarily configurable by the user. Rather than giving a pre-defined algorithm with parameters for the user to tweak, you supply a set of pre-defined functions and allow the user to arrange and connect these functions how they like. A very common example is the use of filter networks in audio / video processing applications. To begin, you must decide what the input and output variables will be for your flowchart. Create a flowchart with one terminal defined for each variable:: ## This example creates just a single input and a single output. ## Flowcharts may define any number of terminals, though. from pyqtgraph.flowchart import Flowchart fc = Flowchart(terminals={ 'nameOfInputTerminal': {'io': 'in'}, 'nameOfOutputTerminal': {'io': 'out'} }) In the example above, each terminal is defined by a dictionary of options which define the behavior of that terminal (see :func:`Terminal.__init__() ` for more information and options). Note that Terminals are not typed; any python object may be passed from one Terminal to another. Once the flowchart is created, add its control widget to your application:: ctrl = fc.widget() myLayout.addWidget(ctrl) ## read Qt docs on QWidget and layouts for more information The control widget provides several features: * Displays a list of all nodes in the flowchart containing the control widget for each node. * Provides access to the flowchart design window via the 'flowchart' button * Interface for saving / restoring flowcharts to disk. At this point your user has the ability to generate flowcharts based on the built-in node library. It is recommended to provide a default set of flowcharts for your users to build from. All that remains is to process data through the flowchart. As noted above, there are two ways to do this: .. _processing methods: 1. Set the values of input terminals with :func:`Flowchart.setInput() `, then read the values of output terminals with :func:`Flowchart.output() `:: fc.setInput(nameOfInputTerminal=newValue) output = fc.output() # returns {terminalName:value} This method updates all of the values displayed in the flowchart design window, allowing the user to inspect values at all terminals in the flowchart and indicating the location of errors that occurred during processing. 2. Call :func:`Flowchart.process() `:: output = fc.process(nameOfInputTerminal=newValue) This method processes data without updating any of the displayed terminal values. Additionally, all :func:`Node.process() ` methods are called with display=False to request that they not invoke any custom display code. This allows data to be processed both more quickly and with a smaller memory footprint, but errors that occur during Flowchart.process() will be more difficult for the user to diagnose. It is thus recommended to use this method for batch processing through flowcharts that have already been tested and debugged with method 1. Implementing Custom Nodes ------------------------- PyQtGraph includes a small library of built-in flowchart nodes. This library is intended to cover some of the most commonly-used functions as well as provide examples for some more exotic Node types. Most applications that use the flowchart system will find the built-in library insufficient and will thus need to implement custom Node classes. A node subclass implements at least: 1) A list of input / output terminals and their properties 2) A :func:`process() ` function which takes the names of input terminals as keyword arguments and returns a dict with the names of output terminals as keys. Optionally, a Node subclass can implement the :func:`ctrlWidget() ` method, which must return a QWidget (usually containing other widgets) that will be displayed in the flowchart control panel. A minimal Node subclass looks like:: class SpecialFunctionNode(Node): """SpecialFunction: short description This description will appear in the flowchart design window when the user selects a node of this type. """ nodeName = 'SpecialFunction' # Node type name that will appear to the user. def __init__(self, name): # all Nodes are provided a unique name when they # are created. Node.__init__(self, name, terminals={ # Initialize with a dict # describing the I/O terminals # on this Node. 'inputTerminalName': {'io': 'in'}, 'anotherInputTerminal': {'io': 'in'}, 'outputTerminalName': {'io': 'out'}, }) def process(self, **kwds): # kwds will have one keyword argument per input terminal. return {'outputTerminalName': result} def ctrlWidget(self): # this method is optional return someQWidget Some nodes implement fairly complex control widgets, but most nodes follow a simple form-like pattern: a list of parameter names and a single value (represented as spin box, check box, etc..) for each parameter. To make this easier, the :class:`~pyqtgraph.flowchart.library.common.CtrlNode` subclass allows you to instead define a simple data structure that CtrlNode will use to automatically generate the control widget. This is used in many of the built-in library nodes (especially the filters). There are many other optional parameters for nodes and terminals -- whether the user is allowed to add/remove/rename terminals, whether one terminal may be connected to many others or just one, etc. See the documentation on the :class:`~pyqtgraph.flowchart.Node` and :class:`~pyqtgraph.flowchart.Terminal` classes for more details. After implementing a new Node subclass, you will most likely want to register the class so that it appears in the menu of Nodes the user can select from:: import pyqtgraph.flowchart.library as fclib fclib.registerNodeType(SpecialFunctionNode, [('Category', 'Sub-Category')]) The second argument to registerNodeType is a list of tuples, with each tuple describing a menu location in which SpecialFunctionNode should appear. See the FlowchartCustomNode example for more information. Debugging Custom Nodes ^^^^^^^^^^^^^^^^^^^^^^ When designing flowcharts or custom Nodes, it is important to set the input of the flowchart with data that at least has the same types and structure as the data you intend to process (see `processing methods`_ #1 above). When you use :func:`Flowchart.setInput() `, the flowchart displays visual feedback in its design window that can tell you what data is present at any terminal and whether there were errors in processing. Nodes that generated errors are displayed with a red border. If you select a Node, its input and output values will be displayed as well as the exception that occurred while the node was processing, if any. Using Nodes Without Flowcharts ------------------------------ Flowchart Nodes implement a very useful generalization in data processing by combining a function with a GUI for configuring that function. This generalization is useful even outside the context of a flowchart. For example:: ## We defined a useful filter Node for use in flowcharts, but would like to ## re-use its processing code and GUI without having a flowchart present. filterNode = MyFilterNode("filterNodeName") ## get the Node's control widget and place it inside the main window filterCtrl = filterNode.ctrlWidget() someLayout.addWidget(filterCtrl) ## later on, process data through the node filteredData = filterNode.process(inputTerminal=rawData) pyqtgraph-pyqtgraph-0.12.4/doc/source/flowchart/node.rst000066400000000000000000000002141421045507400233460ustar00rootroot00000000000000flowchart.Node ============== .. autoclass:: pyqtgraph.flowchart.Node :members: .. automethod:: pyqtgraph.flowchart.Node.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/flowchart/terminal.rst000066400000000000000000000002341421045507400242360ustar00rootroot00000000000000flowchart.Terminal ================== .. autoclass:: pyqtgraph.flowchart.Terminal :members: .. automethod:: pyqtgraph.flowchart.Terminal.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/functions.rst000066400000000000000000000054311421045507400224460ustar00rootroot00000000000000PyQtGraph's Helper Functions ============================ Simple Data Display Functions ----------------------------- .. autofunction:: pyqtgraph.plot .. autofunction:: pyqtgraph.image .. autofunction:: pyqtgraph.dbg Color, Pen, and Brush Functions ------------------------------- Qt uses the classes QColor, QPen, and QBrush to determine how to draw lines and fill shapes. These classes are highly capable but somewhat awkward to use. PyQtGraph offers the functions :func:`~pyqtgraph.mkColor`, :func:`~pyqtgraph.mkPen`, and :func:`~pyqtgraph.mkBrush` to simplify the process of creating these classes. In most cases, however, it will be unnecessary to call these functions directly--any function or method that accepts *pen* or *brush* arguments will make use of these functions for you. For example, the following three lines all have the same effect:: pg.plot(xdata, ydata, pen='r') pg.plot(xdata, ydata, pen=pg.mkPen('r')) pg.plot(xdata, ydata, pen=QPen(QColor(255, 0, 0))) .. autofunction:: pyqtgraph.mkColor .. autofunction:: pyqtgraph.mkPen .. autofunction:: pyqtgraph.mkBrush .. autofunction:: pyqtgraph.hsvColor .. autofunction:: pyqtgraph.intColor .. autofunction:: pyqtgraph.CIELabColor .. autofunction:: pyqtgraph.colorCIELab .. autofunction:: pyqtgraph.colorDistance Data Slicing ------------ .. autofunction:: pyqtgraph.affineSlice Coordinate Transformation ------------------------- .. autofunction:: pyqtgraph.transformToArray .. autofunction:: pyqtgraph.transformCoordinates .. autofunction:: pyqtgraph.solve3DTransform .. autofunction:: pyqtgraph.solveBilinearTransform SI Unit Conversion Functions ---------------------------- .. autofunction:: pyqtgraph.siFormat .. autofunction:: pyqtgraph.siScale .. autofunction:: pyqtgraph.siEval .. autofunction:: pyqtgraph.siParse Image Preparation Functions --------------------------- .. autofunction:: pyqtgraph.makeARGB .. autofunction:: pyqtgraph.makeQImage .. autofunction:: pyqtgraph.applyLookupTable .. autofunction:: pyqtgraph.rescaleData .. autofunction:: pyqtgraph.imageToArray Mesh Generation Functions ------------------------- .. autofunction:: pyqtgraph.isocurve .. autofunction:: pyqtgraph.isosurface Miscellaneous Functions ----------------------- .. autofunction:: pyqtgraph.eq .. autofunction:: pyqtgraph.arrayToQPath .. autofunction:: pyqtgraph.pseudoScatter .. 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 pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/000077500000000000000000000000001421045507400225035ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/arrowitem.rst000066400000000000000000000001701421045507400252440ustar00rootroot00000000000000ArrowItem ========= .. autoclass:: pyqtgraph.ArrowItem :members: .. automethod:: pyqtgraph.ArrowItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/axisitem.rst000066400000000000000000000001641421045507400250610ustar00rootroot00000000000000AxisItem ======== .. autoclass:: pyqtgraph.AxisItem :members: .. automethod:: pyqtgraph.AxisItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/bargraphitem.rst000066400000000000000000000002041421045507400256760ustar00rootroot00000000000000BarGraphItem ============ .. autoclass:: pyqtgraph.BarGraphItem :members: .. automethod:: pyqtgraph.BarGraphItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/buttonitem.rst000066400000000000000000000001741421045507400254310ustar00rootroot00000000000000ButtonItem ========== .. autoclass:: pyqtgraph.ButtonItem :members: .. automethod:: pyqtgraph.ButtonItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/colorbaritem.rst000066400000000000000000000002041421045507400257130ustar00rootroot00000000000000ColorBarItem ============ .. autoclass:: pyqtgraph.ColorBarItem :members: .. automethod:: pyqtgraph.ColorBarItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/curvearrow.rst000066400000000000000000000001741421045507400254360ustar00rootroot00000000000000CurveArrow ========== .. autoclass:: pyqtgraph.CurveArrow :members: .. automethod:: pyqtgraph.CurveArrow.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/curvepoint.rst000066400000000000000000000001741421045507400254350ustar00rootroot00000000000000CurvePoint ========== .. autoclass:: pyqtgraph.CurvePoint :members: .. automethod:: pyqtgraph.CurvePoint.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/dateaxisitem.rst000066400000000000000000000002041421045507400257120ustar00rootroot00000000000000DateAxisItem ============ .. autoclass:: pyqtgraph.DateAxisItem :members: .. automethod:: pyqtgraph.DateAxisItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/errorbaritem.rst000066400000000000000000000002041421045507400257260ustar00rootroot00000000000000ErrorBarItem ============ .. autoclass:: pyqtgraph.ErrorBarItem :members: .. automethod:: pyqtgraph.ErrorBarItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/fillbetweenitem.rst000066400000000000000000000002201421045507400264060ustar00rootroot00000000000000FillBetweenItem =============== .. autoclass:: pyqtgraph.FillBetweenItem :members: .. automethod:: pyqtgraph.FillBetweenItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/gradienteditoritem.rst000066400000000000000000000004731421045507400271240ustar00rootroot00000000000000GradientEditorItem ================== .. autoclass:: pyqtgraph.GradientEditorItem :members: .. automethod:: pyqtgraph.GradientEditorItem.__init__ TickSliderItem ================== .. autoclass:: pyqtgraph.TickSliderItem :members: .. automethod:: pyqtgraph.TickSliderItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/gradientlegend.rst000066400000000000000000000002141421045507400262060ustar00rootroot00000000000000GradientLegend ============== .. autoclass:: pyqtgraph.GradientLegend :members: .. automethod:: pyqtgraph.GradientLegend.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/graphicsitem.rst000066400000000000000000000001171421045507400257130ustar00rootroot00000000000000GraphicsItem ============ .. autoclass:: pyqtgraph.GraphicsItem :members: pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/graphicslayout.rst000066400000000000000000000002141421045507400262700ustar00rootroot00000000000000GraphicsLayout ============== .. autoclass:: pyqtgraph.GraphicsLayout :members: .. automethod:: pyqtgraph.GraphicsLayout.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/graphicsobject.rst000066400000000000000000000002141421045507400262210ustar00rootroot00000000000000GraphicsObject ============== .. autoclass:: pyqtgraph.GraphicsObject :members: .. automethod:: pyqtgraph.GraphicsObject.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/graphicswidget.rst000066400000000000000000000002141421045507400262360ustar00rootroot00000000000000GraphicsWidget ============== .. autoclass:: pyqtgraph.GraphicsWidget :members: .. automethod:: pyqtgraph.GraphicsWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/graphicswidgetanchor.rst000066400000000000000000000002441421045507400274340ustar00rootroot00000000000000GraphicsWidgetAnchor ==================== .. autoclass:: pyqtgraph.GraphicsWidgetAnchor :members: .. automethod:: pyqtgraph.GraphicsWidgetAnchor.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/graphitem.rst000066400000000000000000000001701421045507400252130ustar00rootroot00000000000000GraphItem ========= .. autoclass:: pyqtgraph.GraphItem :members: .. automethod:: pyqtgraph.GraphItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/griditem.rst000066400000000000000000000001641421045507400250420ustar00rootroot00000000000000GridItem ======== .. autoclass:: pyqtgraph.GridItem :members: .. automethod:: pyqtgraph.GridItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/histogramlutitem.rst000066400000000000000000000002241421045507400266340ustar00rootroot00000000000000HistogramLUTItem ================ .. autoclass:: pyqtgraph.HistogramLUTItem :members: .. automethod:: pyqtgraph.HistogramLUTItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/imageitem.rst000066400000000000000000000055701421045507400252050ustar00rootroot00000000000000ImageItem ========= :class:`~pyqtgraph.ImageItem` displays images inside a :class:`~pyqtgraph.GraphicsView`, or a :class:`~pyqtgraph.ViewBox`, which may itself be part of a :class:`~pyqtgraph.PlotItem`. It is designed for rapid updates as needed for a video display. The supplied data is optionally scaled (see :func:`~pyqtgraph.ImageItem.setLevels`) and/or colored according to a lookup table (see :func:`~pyqtgraph.ImageItem.setColorMap` and :func:`~pyqtgraph.ImageItem.setLookupTable`). Data is provided as a NumPy array with an ordering of either * `col-major`, where the shape of the array represents (width, height) or * `row-major`, where the shape of the array represents (height, width). While `col-major` is the default, `row-major` ordering typically has the best performance. In either ordering, a third dimension can be added to the array to hold individual ``[R,G,B]`` or ``[R,G,B,A]`` components. Notes ----- Data ordering can be set for each ImageItem, or in the :ref:`global configuration options ` by :: pyqtgraph.setConfigOption('imageAxisOrder', 'row-major') # best performance An image can be placed into a plot area of a given extent directly through the :func:`~pyqtgraph.ImageItem.setRect` method or the ``rect`` keyword. This is internally realized through assigning a ``QtGui.QTransform``. For other translation, scaling or rotations effects that persist for all later image data, the user can also directly define and assign such a transform, as shown in the example below. ImageItem is frequently used in conjunction with :class:`~pyqtgraph.ColorBarItem` to provide a color map display and interactive level adjustments, or with :class:`~pyqtgraph.HistogramLUTItem` or :class:`~pyqtgraph.HistogramLUTWidget` for a full GUI to control the levels and lookup table used to display the image. If performance is critial, the following points may be worth investigating: * Use row-major ordering and C-contiguous image data. * Manually provide ``level`` information to avoid autoLevels sampling of the image. * Prefer `float32` to `float64` for floating point data, avoid NaN values. * Use lookup tables with <= 256 entries for false color images. * Avoid individual level adjustments RGB components. * Use the latest version of NumPy. Notably, SIMD code added in version 1.20 significantly improved performance on Linux platforms. * Enable Numba with ``pyqtgraph.setConfigOption('useNumba', True)``, although the JIT compilation will only accelerate repeated image display. .. _ImageItem_examples: Examples -------- .. literalinclude:: ../images/gen_example_imageitem_transform.py :lines: 19-28 :dedent: 8 .. image:: ../images/example_imageitem_transform.png :width: 49% :alt: Example of transformed image display .. autoclass:: pyqtgraph.ImageItem :members: .. automethod:: pyqtgraph.ImageItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/index.rst000066400000000000000000000021251421045507400243440ustar00rootroot00000000000000PyQtGraph's Graphics Items ========================== Since pyqtgraph relies on Qt's GraphicsView framework, most of its graphics functionality is implemented as QGraphicsItem subclasses. This has two important consequences: 1) virtually anything you want to draw can be easily accomplished using the functionality provided by Qt. 2) Many of pyqtgraph's GraphicsItem classes can be used in any normal QGraphicsScene. Contents: .. toctree:: :maxdepth: 2 plotdataitem plotitem imageitem colorbaritem pcolormeshitem graphitem viewbox linearregionitem infiniteline roi graphicslayout multiplotitem plotcurveitem scatterplotitem isocurveitem axisitem textitem errorbaritem bargraphitem arrowitem fillbetweenitem curvepoint curvearrow griditem scalebar labelitem vtickgroup legenditem gradienteditoritem histogramlutitem gradientlegend buttonitem graphicsobject graphicswidget graphicsitem uigraphicsitem graphicswidgetanchor dateaxisitem targetitem pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/infiniteline.rst000066400000000000000000000002041421045507400257060ustar00rootroot00000000000000InfiniteLine ============ .. autoclass:: pyqtgraph.InfiniteLine :members: .. automethod:: pyqtgraph.InfiniteLine.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/isocurveitem.rst000066400000000000000000000002041421045507400257470ustar00rootroot00000000000000IsocurveItem ============ .. autoclass:: pyqtgraph.IsocurveItem :members: .. automethod:: pyqtgraph.IsocurveItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/labelitem.rst000066400000000000000000000001701421045507400251710ustar00rootroot00000000000000LabelItem ========= .. autoclass:: pyqtgraph.LabelItem :members: .. automethod:: pyqtgraph.LabelItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/legenditem.rst000066400000000000000000000001741421045507400253540ustar00rootroot00000000000000LegendItem ========== .. autoclass:: pyqtgraph.LegendItem :members: .. automethod:: pyqtgraph.LegendItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/linearregionitem.rst000066400000000000000000000002241421045507400265700ustar00rootroot00000000000000LinearRegionItem ================ .. autoclass:: pyqtgraph.LinearRegionItem :members: .. automethod:: pyqtgraph.LinearRegionItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/make000066400000000000000000000010421421045507400233400ustar00rootroot00000000000000files = """ArrowItem AxisItem ButtonItem CurvePoint DateAxisItem GradientEditorItem GradientLegend GraphicsLayout GraphicsObject GraphicsWidget GridItem HistogramLUTItem ImageItem PColorMeshItem InfiniteLine LabelItem LinearRegionItem PlotCurveItem PlotDataItem ROI ScaleBar ScatterPlotItem UIGraphicsItem ViewBox VTickGroup""".split('\n') for f in files: print(f) fh = open(f.lower()+'.rst', 'w') fh.write( """%s %s .. autoclass:: pyqtgraph.%s :members: .. automethod:: pyqtgraph.%s.__init__ """ % (f, '='*len(f), f, f)) pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/multiplotitem.rst000066400000000000000000000001221421045507400261400ustar00rootroot00000000000000MultiPlotItem ============= .. autoclass:: pyqtgraph.MultiPlotItem :members: pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/pcolormeshitem.rst000066400000000000000000000002141421045507400262640ustar00rootroot00000000000000PColorMeshItem ============== .. autoclass:: pyqtgraph.PColorMeshItem :members: .. automethod:: pyqtgraph.PColorMeshItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/plotcurveitem.rst000066400000000000000000000002101421045507400261300ustar00rootroot00000000000000PlotCurveItem ============= .. autoclass:: pyqtgraph.PlotCurveItem :members: .. automethod:: pyqtgraph.PlotCurveItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/plotdataitem.rst000066400000000000000000000002041421045507400257200ustar00rootroot00000000000000PlotDataItem ============ .. autoclass:: pyqtgraph.PlotDataItem :members: .. automethod:: pyqtgraph.PlotDataItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/plotitem.rst000066400000000000000000000001721421045507400250720ustar00rootroot00000000000000PlotItem ======== .. autoclass:: pyqtgraph.PlotItem() :members: .. automethod:: pyqtgraph.PlotItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/roi.rst000066400000000000000000000006311421045507400240260ustar00rootroot00000000000000ROI === .. autoclass:: pyqtgraph.ROI :members: .. autoclass:: pyqtgraph.RectROI :members: .. autoclass:: pyqtgraph.EllipseROI :members: .. autoclass:: pyqtgraph.CircleROI :members: .. autoclass:: pyqtgraph.LineSegmentROI :members: .. autoclass:: pyqtgraph.PolyLineROI :members: .. autoclass:: pyqtgraph.LineROI :members: .. autoclass:: pyqtgraph.MultiRectROI :members: pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/scalebar.rst000066400000000000000000000001641421045507400250120ustar00rootroot00000000000000ScaleBar ======== .. autoclass:: pyqtgraph.ScaleBar :members: .. automethod:: pyqtgraph.ScaleBar.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/scatterplotitem.rst000066400000000000000000000002201421045507400264520ustar00rootroot00000000000000ScatterPlotItem =============== .. autoclass:: pyqtgraph.ScatterPlotItem :members: .. automethod:: pyqtgraph.ScatterPlotItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/targetitem.rst000066400000000000000000000004221421045507400254000ustar00rootroot00000000000000TargetItem ========== .. autoclass:: pyqtgraph.TargetItem :members: .. automethod:: pyqtgraph.TargetItem.__init__ TargetLabel ================== .. autoclass:: pyqtgraph.TargetLabel :members: .. automethod:: pyqtgraph.TargetLabel.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/textitem.rst000066400000000000000000000001641421045507400251010ustar00rootroot00000000000000TextItem ======== .. autoclass:: pyqtgraph.TextItem :members: .. automethod:: pyqtgraph.TextItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/uigraphicsitem.rst000066400000000000000000000002141421045507400262470ustar00rootroot00000000000000UIGraphicsItem ============== .. autoclass:: pyqtgraph.UIGraphicsItem :members: .. automethod:: pyqtgraph.UIGraphicsItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/viewbox.rst000066400000000000000000000001601421045507400247150ustar00rootroot00000000000000ViewBox ======= .. autoclass:: pyqtgraph.ViewBox :members: .. automethod:: pyqtgraph.ViewBox.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsItems/vtickgroup.rst000066400000000000000000000001741421045507400254340ustar00rootroot00000000000000VTickGroup ========== .. autoclass:: pyqtgraph.VTickGroup :members: .. automethod:: pyqtgraph.VTickGroup.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsscene/000077500000000000000000000000001421045507400225175ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsscene/graphicsscene.rst000066400000000000000000000002101421045507400260600ustar00rootroot00000000000000GraphicsScene ============= .. autoclass:: pyqtgraph.GraphicsScene :members: .. automethod:: pyqtgraph.GraphicsScene.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsscene/hoverevent.rst000066400000000000000000000001431421045507400254340ustar00rootroot00000000000000HoverEvent ========== .. autoclass:: pyqtgraph.GraphicsScene.mouseEvents.HoverEvent :members: pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsscene/index.rst000066400000000000000000000002611421045507400243570ustar00rootroot00000000000000GraphicsScene and Mouse Events ============================== Contents: .. toctree:: :maxdepth: 2 graphicsscene hoverevent mouseclickevent mousedragevent pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsscene/mouseclickevent.rst000066400000000000000000000001621421045507400264500ustar00rootroot00000000000000MouseClickEvent =============== .. autoclass:: pyqtgraph.GraphicsScene.mouseEvents.MouseClickEvent :members: pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicsscene/mousedragevent.rst000066400000000000000000000001571421045507400263040ustar00rootroot00000000000000MouseDragEvent ============== .. autoclass:: pyqtgraph.GraphicsScene.mouseEvents.MouseDragEvent :members: pyqtgraph-pyqtgraph-0.12.4/doc/source/graphicswindow.rst000066400000000000000000000004571421045507400234710ustar00rootroot00000000000000Deprecated Window Classes ========================= .. automodule:: pyqtgraph.graphicsWindows .. autoclass:: pyqtgraph.GraphicsWindow :members: .. autoclass:: pyqtgraph.TabWindow :members: .. autoclass:: pyqtgraph.PlotWindow :members: .. autoclass:: pyqtgraph.ImageWindow :members: pyqtgraph-pyqtgraph-0.12.4/doc/source/how_to_use.rst000066400000000000000000000222361421045507400226130ustar00rootroot00000000000000How to use pyqtgraph ==================== There are a few suggested ways to use pyqtgraph: * From the interactive shell (python -i, ipython, etc) * Displaying pop-up windows from an application * Embedding widgets in a PyQt application Command-line use ---------------- PyQtGraph makes it very easy to visualize data from the command line. Observe:: import pyqtgraph as pg pg.plot(data) # data can be a list of values or a numpy array The example above would open a window displaying a line plot of the data given. The call to :func:`pg.plot ` returns a handle to the :class:`plot widget ` that is created, allowing more data to be added to the same window. **Note:** interactive plotting from the python prompt is only available with PyQt; PySide does not run the Qt event loop while the interactive prompt is running. If you wish to use pyqtgraph interactively with PySide, see the 'console' :ref:`example `. Further examples:: pw = pg.plot(xVals, yVals, pen='r') # plot x vs y in red pw.plot(xVals, yVals2, pen='b') win = pg.GraphicsWindow() # Automatically generates grids with multiple items win.addPlot(data1, row=0, col=0) win.addPlot(data2, row=0, col=1) win.addPlot(data3, row=1, col=0, colspan=2) pg.show(imageData) # imageData must be a numpy array with 2 to 4 dimensions We're only scratching the surface here--these functions accept many different data formats and options for customizing the appearance of your data. Displaying windows from within an application --------------------------------------------- While I consider this approach somewhat lazy, it is often the case that 'lazy' is indistinguishable from 'highly efficient'. The approach here is simply to use the very same functions that would be used on the command line, but from within an existing application. I often use this when I simply want to get a immediate feedback about the state of data in my application without taking the time to build a user interface for it. Embedding widgets inside PyQt applications ------------------------------------------ For the serious application developer, all of the functionality in pyqtgraph is available via :ref:`widgets ` that can be embedded just like any other Qt widgets. Most importantly, see: :class:`PlotWidget `, :class:`ImageView `, :class:`GraphicsLayoutWidget `, and :class:`GraphicsView `. PyQtGraph's widgets can be included in Designer's ui files via the "Promote To..." functionality: #. In Designer, create a QGraphicsView widget ("Graphics View" under the "Display Widgets" category). #. Right-click on the QGraphicsView and select "Promote To...". #. Under "Promoted class name", enter the class name you wish to use ("PlotWidget", "GraphicsLayoutWidget", etc). #. Under "Header file", enter "pyqtgraph". #. Click "Add", then click "Promote". See the designer documentation for more information on promoting widgets. The "VideoSpeedTest" and "ScatterPlotSpeedTest" examples both demonstrate the use of .ui files that are compiled to .py modules using pyuic5 or pyside-uic. The "designerExample" example demonstrates dynamically generating python classes from .ui files (no pyuic5 / pyside-uic needed). HiDPI Displays -------------- PyQtGraph has a method :func:`mkQApp ` that by default sets what we have tested to be the best combination of options to support hidpi displays, when in combination with non-hidpi secondary displays. For your application, you may have instantiated ``QApplication`` yourself, in which case we advise setting these options *before* runing ``QApplication.exec_()``. For Qt6 bindings, this functionally "just works" without having to set any attributes. On Versions of Qt >= 5.14 and < 6; you can get ideal behavior with the following lines:: os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" QApplication.setHighDpiScaleFactorRoundingPolicy(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) If you are on Qt >= 5.6 and < 5.14; you can get near ideal behavior with the following lines:: QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps) With the later, ideal behavior was not achieved. .. autofunction:: pyqtgraph.Qt.mkQApp PyQt and PySide --------------- PyQtGraph supports two popular python wrappers for the Qt library: PyQt and PySide. Both packages provide nearly identical APIs and functionality, but for various reasons (discussed elsewhere) you may prefer to use one package or the other. When pyqtgraph is first imported, it automatically determines which library to use by making the fillowing checks: #. If PyQt5 is already imported, use that #. Else, if PySide2 is already imported, use that #. Else, if PySide6 is already imported, use that #. Else, if PyQt6 is already imported, use that #. Else, attempt to import PyQt5, PySide2, PySide6, PyQt6, in that order. If you have both libraries installed on your system and you wish to force pyqtgraph to use one or the other, simply make sure it is imported before pyqtgraph:: import PySide2 ## this will force pyqtgraph to use PySide2 instead of PyQt5 import pyqtgraph as pg Embedding PyQtGraph as a sub-package of a larger project -------------------------------------------------------- When writing applications or python packages that make use of pyqtgraph, it is most common to install pyqtgraph system-wide (or within a virtualenv) and simply call `import pyqtgraph` from within your application. The main benefit to this is that pyqtgraph is configured independently of your application and thus you (or your users) are free to install newer versions of pyqtgraph without changing anything in your application. This is standard practice when developing with python. 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. The basic approach is to clone the repository into the appropriate location in your project. When you import pyqtgraph, be sure to use the full name to avoid importing any system-installed pyqtgraph packages. For example, imagine a simple project has the following structure:: my_project/ __init__.py plotting.py """Plotting functions used by this package""" import pyqtgraph as pg def my_plot_function(*data): pg.plot(*data) To embed a specific version of pyqtgraph, we would clone the pyqtgraph repository inside the project, with a directory name that distinguishes it from a system-wide installation:: my_project$ git clone https://github.com/pyqtgraph/pyqtgraph.git local_pyqtgraph Then adjust the import statements accordingly:: my_project/ __init__.py local_pyqtgraph/ plotting.py """Plotting functions used by this package""" import local_pyqtgraph.pyqtgraph as pg # be sure to use the local subpackage # rather than any globally-installed # version. def my_plot_function(*data): pg.plot(*data) Use ``git checkout pyqtgraph-x.x.x`` to select a specific library version from the repository, or use ``git pull`` to pull pyqtgraph updates from upstream (see the git documentation for more information). If you do not plan to make use of git's versioning features, adding the option ``--depth 1`` to the ``git clone`` command retrieves only the latest version. For projects that already use git for code control, it is also possible to include pyqtgraph as a git subtree within your own repository. The major advantage to this approach is that, in addition to being able to pull pyqtgraph updates from the upstream repository, it is also possible to commit your local pyqtgraph changes into the project repository and push those changes upstream:: my_project$ git remote add pyqtgraph https://github.com/pyqtgraph/pyqtgraph.git my_project$ git fetch pyqtgraph my_project$ git merge -s ours --allow-unrelated-histories --no-commit pyqtgraph/master my_project$ mkdir local_pyqtgraph my_project$ git read-tree -u --prefix=local_pyqtgraph/ pyqtgraph/master my_project$ git commit -m "Added pyqtgraph to project repository" See the ``git subtree`` documentation for more information. pyqtgraph-pyqtgraph-0.12.4/doc/source/images.rst000066400000000000000000000035721421045507400217070ustar00rootroot00000000000000Displaying images and video =========================== PyQtGraph displays 2D numpy arrays as images and provides tools for determining how to translate between the numpy data type and RGB values on the screen. If you want to display data from common image and video file formats, you will need to load the data first using another library (PIL works well for images and built-in numpy conversion). The easiest way to display 2D or 3D data is using the :func:`pyqtgraph.image` function:: import pyqtgraph as pg pg.image(imageData) This function will accept any floating-point or integer data types and displays a single :class:`~pyqtgraph.ImageView` widget containing your data. This widget includes controls for determining how the image data will be converted to 32-bit RGBa values. Conversion happens in two steps (both are optional): 1. Scale and offset the data (by selecting the dark/light levels on the displayed histogram) 2. Convert the data to color using a lookup table (determined by the colors shown in the gradient editor) If the data is 3D (time, x, y), then a time axis will be shown with a slider that can set the currently displayed frame. (if the axes in your data are ordered differently, use numpy.transpose to rearrange them) There are a few other methods for displaying images as well: * The :class:`~pyqtgraph.ImageView` class can also be instantiated directly and embedded in Qt applications. * Instances of :class:`~pyqtgraph.ImageItem` can be used inside a :class:`ViewBox ` or :class:`GraphicsView `. * For higher performance, use :class:`~pyqtgraph.RawImageWidget`. Any of these classes are acceptable for displaying video by calling setImage() to display a new frame. For more information, see the classes listed above and the 'VideoSpeedTest', 'ImageItem', 'ImageView', and 'HistogramLUT' :ref:`examples`. pyqtgraph-pyqtgraph-0.12.4/doc/source/images/000077500000000000000000000000001421045507400211465ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/source/images/example_false_color_image.png000066400000000000000000001065151421045507400270310ustar00rootroot00000000000000‰PNG  IHDR€α§Ž pHYsΔΔ•+ IDATxœμ½i¬$YzφEdDδžωΆΪΧήͺ{¦{f4MΙ!)’"-z@–iΩ†(Ψ†% † Ά~Ψ† ύ°‘τςЕl‹ήAB°,‹΄5M„gΔαp¦gΊ‡ΣϋVϋ^ο½ά3φΈχΖw²2^WχdΎͺ̚u3–{oά|7γ|ηžΟ"’œ@ xΒ°Ÿt@ ’I +Y@°I +Y@°I +Y@°pžt@°.]ΊT”/^ΌψΨκΉό'ΙςŸό·ž]Ίt©τί‡]­e‡«―@ >=ϋεqB(;@ XΧλOZ€.]Ί΄Φ ”,H@°$όΥΏς―κυώψόlέ#"QΩ ΑΪγiXŒˆδ I –;=φ{>-‹‘,H@°4X4~"χ}8~uρβEΊxρb©\{Yυ‡Y`ρI ΔAŸ-«~ِI –…όΙΌ!=-I –‹&OΊ k Y`YΘ§OΊk ‘} `% oH@°,δώ“nΑZC$@ XdAZBΩ `%π™ίΚv―B @ xβΚn!|¦©ΜΝφαΚόϋ z@ xZ‘SpΘw¨ςυŸ,>e' Š@ |ςπp{Κ±2’†²·―O³Ύπg[τω?Χ&"’^Π.κm;/Κ^%&"’QΠ,κΆύ’μΪ ݟnuYΖkυΙΦ.]ž,κ.o€‹S—ˆˆœ‡³ξς«»eΝ·eΛcΖλ£γEy³6 "’0s‹Ί$γλ:VͺΪ—[Eέ‰Ζ~QΎ5έ!"’ηΪwŠΊšσ΅ 'γu}¬Ÿπ―.›²’όBηΆΊρ½Ϊ•¨(ί :DDtΟί,κvκξ‹£-—ΟΉξwŠςXίχlc―¨σμ΄(Η𠞫«v}μsέ$­εΖΓΰΥο ι:νFά– ŽιΫPy„½ΠμΞ]œπ―αIΚc|]_€?χŽxόcU5O-‹ΫlΓϋ™j£Ÿπ5»R”O5Ά‰ˆh³ϋ₯’.‰οε›ύŠς;#5ΟΟΧEέ‰Ο󍿣ξuί,κnMξΓxfσBQΆ*<ξNϋΏ$"’ŒZE›ώSξΛΰ7ω"–ƒzχ―U±ύΟΝέ α&¨(§Ιeu™κΏΝuΦ±OσωiΖ6:j˜τψKέͺςΞ(QμpAjx|$Σ–Ν»Χηϋvj! RώkΓβTΆ m΅Έ­Γ±ϊc±Σε?€υ Γ‚Τ©v; ΑigӞ»WΧαrθ«ϋN'|ώfƒϋ΅SUηυŠ*OΈ­N¬ϊu€ΕΧ¬Ω\Ž2.ŸnͺςhΒu£„ϋ’”$?>³ cδͺΟkπ#€ΎϊMBΣ”Ο9Yεηφ\»Bcσ=G ΄[iB“NT‘/uu-σ’hvAš¦κOžcΝǞo©έΩβE2ŽΨ²¦ΊΗΟ` w=9Ϋβ²}φsκ^]žƒΥα|__<ΒsίπχΘΩx‘ˆˆ2β^ς^QžΐwΖ,HΝ­sEUdnξ^/>Q”ΣDuΖͺ½PΤ%Φ©O·εψρΏ\”£Ρ―©ΎnύΛEέ`oq[kj<>Ϊ}»¨{εά_εvCz{C>φκϋψς‰ŸQΧΏŸuυγߞ;ί ώfQΎr›)ΏcέgˆˆΘ ώύ’²ωOζΞGΤΫΏ\”'ύί%"’<‹:\π„ρ#©μP1· 94`ηΡ£ˆΟ΄ ­j @ X Θ‚΄֞²‚UΌ!-†΅_’Μ₯0VͺŸKŽG!ΛPMŒhq\γA»Ύ’Αbάιd›₯―Χz*n’ΑΆ-?β`Δ1- ΗΈGˆΒ”UMF%χόΦΥ’ξΘΝ―”Œυ(Δ•Ž9.p”„«[γ;ܞrTμ}0Umω ¨.-›η“Υώ-""Ї_ίfšιλ$ε?΄ψGΧk(Yud|QχΎX”1Kγ“PΕαšΗ_bpΜ(υ¬κ_,κŽm|§(W%—―6!FŸ Λζ؝㩸¬]Ήˆ³O kΏ Αͺ ΟE0±dA‚eA€…°φ ’cΗTΥIΞ†±+Γ4R―ϊn…eΔ½€eΥ†κΓ ¬="ۚ¦ε·έ`iνGΤζ@Ηαλ7ΐ‰aτaSKΣο€l»χ5ew dΫηΊ,5Έ9:R”«Σ"»΅‹d‡eΘ·ΰ^Έαχœ¦ΧnωZ§[Lglh vηψ ‡7τӚz#"ΊύγݏωY\ ωMν¬γŽτf $θ?©ϋցV=]eΚμύ‰ΊG”–OιΣ5υ ―Γ6‘›>·λ mυΌΎ±ΗΟ*šμ~Θ'nΈŠjϋxΒνλΒmίΧ…wOϊΥγLΟ½3TTηΠ€ρ½lνΰΠ‹™v}΅Λσu*ϊοψΞOužσ>΄•·?Χ΄OΦ™ ν?ψΫEΩu~ƒˆˆͺΝWŠΊ# QΗjnžmεφU΅’Ω?Ac{λg‹ςΞ”ες{Ϊβl’-Cκέ’<σ–‡H»aœ:ϋQˆΓοελ{oΡΠtΈΰ cν$@ Xδ$oH‹@$@ X$†΄Φ~Aͺ:aAΉέά¦¨ο6™zͺΊŠn1 7"’)(»Œϊn /ΤdΖ‘ΐΆψU?jιΤζU""zΤR†qΒχ2”]”[‡Ά5₯5JρζˆύΌbγ›ώv p78RτY \)φPω νcνڐ%w{F+ 7ΰϊvΜΤ•rhWωs€?©Ο€²ο­!+ΌΆτXL‘}M џξ¨Vn:¨²γq΅΅ͺpτΰGšΎ$"Ϊμͺq}ΆΖ}=[εΆ~o¨hΆΠΊοNx,~’Γσα¦―¨Ί3uΎΧφX©hΪέv˜³λΗ|――lŸWΧ0»αςLSυŒΞ€ία‡@>ΧUηΏw œΐ‰αZΐχ=WSΧνE IνoεkγΏW”#-ϋώΚΛάΧGνϊ©TΕ’|ηΎJμ7 >½ΣΓgΖ!-HηO^#"’oΠOκs'ΥΆ,e'‚•ΐΪΏ! Αͺ Ο³G$8kΏ Ea‡F•Η­•Λ9ϋΎ’œ* ?vΐQΐP~μςΗrΥλγxΈΠ}!ΧτRk»ϋΟe”“7 M泌7ϊΝΈ&ΰ96”G‘jΧδΣΖύˆ%Ψ}Ÿ₯·[u¦[Ϊ5.οi#Τ 0GEͺ8μ€‘μ  Τ6υuλ0–wΑ\ΥHοΡ|εθχ5upGLΉ=¨0½fάz1Σ³7¦L%ϊšφλΊ“Ή:"’£žjγ‡S¦ΡΆ\ΎοχuΞ+l ‹·CΎοZͺΧ|nίqΎέΥΨ7`ΐ7zLΑΎά4cΐcνzΎ©Ζ%ξ_j3eWΣςgς!U+άΎ^ΔΟγΈ§»απά}ωΔΟeΧSΤu²»BΧγηυRSυs?yp―ψΑ/Qsγ/uw&μ’R±Ή-OΝΩ<γq«fί,Κ‰ύ§Τ½όy>\#“irϋ'‹ΊζI6O-ƒ?ψ―‹r]KΧϋΝUŸΠ‚dbEHΛCZVύa`ν$@ ψqΗA‹ΔaΧ/² Α’pX*»¬ύ‚dΩ)Yšf‰&š¨”W½ΞκšQŸ•>αX™—Ϊ.S_ 0gh•ž*ΈLΣ{»{ΰž<1됨΄Hιij φ†œ½­―…&©χA1x|ϋ#"šuͺ@Ε]c†ͺΤΤ\k4Xͺέ Π½Υ}=_ν’AΡxR˜›Ο8Ώι75Šj8·@‹ΦUθǎΗ}‰τξώΛΪύˆθNό (#…Sήωω6S‘Ϊͺέ{ XDuΰΩ?Γ7υ4ω\“Ÿ± ΟΠ¨w@•ωlζ‹q•h0=ωα”ηžΙ­dΟ(ϋψϊ5ή“Ÿ1dάο Τ±gλLe~qψqQΊJρw|·¨σ!]ωGκ{΄ B֏Ϋzl‹)Zƒg6x>ήσ|ˆτu³”Ÿ[8ωΧόΥλ|ώfΐγ~_ηVju؜•{]»Βs³ζ©gοTϊΎ0$†΄Φ~A‚UAώΘ%RπIΩ·@ Vλ†d₯dkš§ΪαtΗ~οω’μΆUύd ”P,ζ7M&€Σ)SΉ6αLΐŒΟwλŠJHΊͺ5xσ©šσ’)o&uκLK˜Mͺ PG]Ha>*Ά\ίšΛlψu!‡‘ ω–z ξ«kϊlΰΓΖX0…5Ή—ξ‚²ξX›©Pχ );TΆυ⍏;n65΄b hΌ Q/l\/ΚΧ'jσρΫ°Ωτ8Πw&΅ΊTν-Θou/T4Φ;»PΗΟε-­ΤΔάMΨ—γ;ιωΊ’2ΏΩγ~#%fŒo―ƒβπ¨ΗΟ°YQcπΪ€•sUψiψφX=ϋm ΖΞΤψ—χυ©jcr(=πy ߝπ±/6U»«yLξMΤζγ;χ{?ζ1άΤ!*<μtΛtχp¨ΤyUŸ©Ή«}ήd‹ιԟm) 4ŽψΉ5Ϊ‘(›tθYΒ*Ώ;.'šskΏΔηΠ<œπΧ‹ς½ώ;EΉUUs?ΟΟ‘[bH‹aύ$@ XδΉPv‹@(;@ ¬Vζ ιΥW_-Κ―Ώώϊl‰@ όh•έbX™ιG]„ςΈI‰–{;έkόΔβ‘’cŒΘ{ŠΑύγ2©–%££‚ΏΟΙΐš;*‘™‰κB0,MCH2ζ©Έ]e=ρ9ΦPΔ“ F…ξ ζΓΡ ƒLSΛΒ6l½±Œ=νλk€·Ωήΰ1t΅S χΫύ³EΉ£₯Π-pΨ…ϋž?ςΊΟύΗdGΙ’‡/Ϋ¬³ϋ#ΈΦΩΆŠuL ­3F¬ϊ5\~· †σ“;ͺ-ίλq'LX1wJSΧ7¦δ—{ιΎ°ς썫πNΦΤά¨ΫόŒR| Πτ[­ΞΞ#?`SΫ ‚όŽΥ>srnΕL}Wέί""’QΒκΗ#¦½‘iΟ½έ―u;Gx,3ϊܞώυ‡»IDDτs›¦‡η¦‰—έBXϋI VBΩ-Y`IΘDφ½$†$‚•ΐΪΏ!Y•ˆ,XΟ‚XˆRζ\ΗXΠ‘;sΌΘΈ6X¨Μynv»GW1χ΅›,·-K8Σn¨C x²³”ΉqŒwe:…³ΐcΛΈSΧκ)Kά[ΰˆ>ΪW ΗZRˆ…L§j QΦέ€Duϋ:ΊZtΐ ΌcDχGNJ:$ΰ‘–cάγa7ΐ‰αsΫΚ=γFτΞe“ΠΟοAlΚHΌ_οqΔΖ:zάήƒdƒgλ3ό™ gόhΒqΘλ2?Qεv½―ΗώΆωΉ\π3ΌΠ9‘ϋΒηΏΠΰηω{κ^/58φSΗ8AžW{ˆˆ’bo>Ο½ΆΓsσXUΕƒ=`χ†ΟύηE9œώ6΅Ϊœθς£ΫμΦύ₯ŽšΗΛ£$ΕΤ°mžο Ή]A¦φρ-&§†…°φ ’@ ¬ „²[ BΩ `%°ώoHΉM€ι˜dz€τCŸe1H†Α=‘ΈTΘτBžΜ_Λη”xΫ Eε₯ Οa—½ N FZ^iqB3„Χ½ͺŽΓU”›ΫZJΩQ!Ο+PVΏ1,0DΝAv> Ή‘5‘2³α<#m―·Ή­άΛЎ6Πh˜¬Ο$γσΐa hgΪ ”ΫLeθcSŠ'|Ž’‘Z λFσΤ ¦{vφ4}ˆ"¦ή›ι—pΎHτcΎ»>χΑΘΎ_ά`ΊΘO™FϋΑHQ… šΞˆΫrS›ͺ> rύ&όt4TέΛž£5›eι― ω౦"ƒŒοUͺ°]UniΖ}ύƒ=¦άlK·3½y₯Οζ«_ΨόWιa %φζ˜Ηθ|Mέχt“۝η|―Š£Œo―ήaYχW·˜φ|½―Ζγ玣f#"z}Wm6­ύψΣGή+Κϋš­½ισx±ΙΟυ!+?6©έαΉφ€23eά‹τ`_«&ο‚RςLΛ§\ΥΗύ€ιjTΩYΟR΄dΓα~wOΏQ”γρΏCDD“)_k˜πΤ4e·α±"1 ~›oΠϊ_Υυ»L?ήΎώwŠς :eό¦;%’rεη:γ₯KtρβΕz"šωΜΤ-ZΏl¬ύ‚$«‚'εφ ΖΓυ/ Χ™ΦϊΓ€lŒ‚%!ΛσCύο τft˜o3‡yC‚%α°ή<ψόtήγ’Ϊ–…΅_ςά. 5zgΐο›|U0ΛL|–δFZ’Ϋθή*κRΞšN4βδrhβιhχβ ΘΖΡαΑΔ›r01Mΐέ ‡$ d‹ξ (wwul,q/"’*$σ χ/ΐyΥ™ώYθ΄ γEyΞ±„‘˜'ΑfQη8V‘ΤU,₯YοΡ'eγ?ƒ:ΔxšZΆνΦ9φwwΘrϊ@Η{άί+€z‰–£O"ξ_ ΜUG匁cy œͺ` {OΗ«lCΑA’—«ρ8 1)\χΎT‰1$#QGYϊ±*·΅’θ]9ΎžΟAIDDGu¬MiλnΛ¦§ζώ8fG…¦ΓςM“<j~©Χkj>ΎΨzΆ¨ϋΰ#vZΈζ3 S―¨ρͺ Ψω£yλ'вXŸcβ>DDOU_.Δ±Ι*Ρ¬•Ο±ͺƒfeJDλGΫ}L¬i9£3Xvύa`ν$@ X<)ΩχA‹ΔaΧ/² Α’ ι'ΓR€'Ή»7'«0ϊ¬Θ©z`ͺi‹0`κͺ ΉtFΪ8ΣŠ₯Υaj+Ι M™ΎΛ Q¦Jfr/½iZ15[­ΑNΑHν|―6Y‘•Meλ²ξΑ•yFIˆΤRoy΄¦¦κςˆΗ-…²1Z !ŸLn&O«ΰΰ^YΘceTv œ3AEŸΎ>¦M―x¬πŠA½gThΌ9 ΥΈdΨU¦ρκ39Œε‚ς.jΚPvxΞpΚ΄ξWN}Ώ(οκϋ’ωjΞ»9Q)ιC 7oLyάΏΊ₯œήςXσsm΄κΓykΐΟψψEɈi§AΒσ Zί* σ‹@!ΏζρΎν«ϋ>ίdZψ#ž―Ζ φ P­'jw\P'Ύ’ϋrάceά»#žǚŠ2»2`m’r»0FU½ωχDη£?ϊfQξO՜νV™Šύ`Ο(Pύώ‹§ωΉ67ώrQŽƒ?PΧ™πw)΄Υχό~tx{…dAZ ‡FΩ}V|–EH O„²‚%AD ‹A$@ X2±Z‡Cz\»{£¨IΓΡ1""ͺΊΌƒγE#ύy’_ΧaNΌ©ͺ>Η$<ˆM|Εi£,ά9¦ΰΉ°Ν^£ŽRnΈq•hAόb2bιͺΉ/š§Z0q—Ιˆ“Ώ9`ήλΈCdΔ8X`2@γΊΰUy7|ŒlΓIΐ(ΦΫzΏ(g%Nn‹ω{ΒΈŽk¬Ϋ•ω|}<%άSν^Pƒ~ḘgA²Βϊ;QΆŠqeIτ~ΐ±Š"f}ͺz<‡Β”λMμηcxnΰ>(©u·Ι1¨Mpj8UWγ.:lδϊŽgY`Έϊ Χ±#›»B5ˆϋόΙή3EΉc†(OΰGόξͺΨΟ+‰ϋκ˜β΅€TρxΎG‰v)IΈn/ζ{έiώ†£Ζν­Ο·Zεc(«ωxuΜcυڐηC˜©Οϋ!˜Ώφ~ƒ[}DՎZ­¨ϋgο±ωκγ}ης†΄–ς†τ€wχ `ύ! ϊ`IΘςOίΩΕg:>ϋ1xωZϋRžε™’Pr<™0MεGJϊΪ³“:ψšRΛ2¦ΙΖ>S 5OΡU m”JmƒM Η ΝsΥ4)M€ u»7Α݁€*lhͺ/Π‡\<Ξ}¦ΑPΞ>c:[΄•₯ΦhZ1&@¦v»0f³θDτ.ΪQγfMF‰£)8Œζ62τš«ΦΐDt¬Νfm4\ y\ŒkΒΈi\οηΆθ>’lΌ0Ί.ΌΉ―ϊxhΧΫ}¦ ©¬Tκmp}0F¬§ ο Ξε]ν‚ω˜ήλΏΪώˆˆ~0β6«σ|FΉψXo@Κο˜ΗΟσd]ΡύηφΫc–e_ΧTζOiΩ<ѐMߚ¨1ˆΐαω:SvaΖΧ½¨Ή+όΥ€ §=ŒΙ­(oΊL}gΉšGϋe~eλ?-Κ ©gL>SηΎΌ3Vm±‘Vϋ΄’†—Ÿύ-κ4ΏBυΪ‹τφε_;΄φ¬δ I –„O“†όεg“Ά:ΏLUχ$mu~™^~φ7Naώ΄`νߐ`UπΉKŸψωΞΖΧΘuΆ‰ςœς<%ΫͺΣΞƟ£Ÿϋ5Ϊν?z/ζϋίυyΜ:cν€$uΘ7N ρ<ED”j*)=€Μ†S₯ΰΪj³* M8 Πύ‘7fήa»£θΡΤhΰˆPT”8ϊžLΉ5kLk ΣC ξ ­ήCsV€ΌFZωUJ">³τyΓ>ΣEFq¨.lh,0O£ΥšV†…cV›a[βΑy"zH™γnΉ'@gU 7Rιάk]₯³ΐΊs΄ϋςά΅^4ξ ˆ`rΪBzOΟ!tΨλ1ύxττχΈ‰ΊΧςk!…kζΦݽ犺WΟ|·(wt{π ‹ηΘι­+κ:@!Υζ›cΥƟμΖsuDDΟw™κ›j σ!E9?γΧϊŠ6<βρsΫDη ­ό(ΰΣΫLUtΊρmT§ Έs,.Ÿ«©~hφ›σάΟ2₯ΎCϊ°Yα·_Sσ»_σΞ­€(oŸ}‹ˆˆ6F$LeU"ˆ—ŽΑψ»Ÿψy³ώ"Ή•M²,0?ΞςÏyΒlαjcν$@ XάΨύŸωωΉcž=ρ7Θu6)NztωΞ―Σ΅{ϋSήαΏ]Ό‘+ Yΰ1Β,>§vώ]Ί΅ϋ?†ΕθιΗΪ/HžSKS^¨V¬λ6Π35T-Ω̚]γΣ›(z­ηt°™4™7ΑD΅ͺμw)5l·ΙΗ”@NC)‘Ρ끅Α,άί fZ²‘ΦJ7ΙͺΞ† ’6(Ϋ".|F±Ή£άͺI*>HΛOΤ†ε²tσκ#=8N‹SUΗΓσDDTiπ¦JΌΦH+C Ι*@_Ζ#E[V»WΈ6_ο3}Έ£iΝ>*^±!˜Ι‘Π½5έ_6Ύ’ŠoΏ―(ΠNƒsMAαy+Scηό¬Ζ0QΡgΖ«ωŒ.CΒΣΛ^‘[{²= Y`IsΥΕ ϋ@°X7$+/xr\][΅ω€p~±ŒOdκσέΗ ίίl*n{±šΙΆ›k΄ρž·&)΄c©ΈEŠςdˆu˜xžƒHu\γ]– ΏΚτ΅Π©"ϋUp*λ]ψağ£ΉͺqUh€#ΚΆM’%Εx}[Ηh2¨ˆΘW#F“T ϋ RhΧ›ΜάS]ΜU«*>υ8ѝƒ φτ½Π\Άή£V-ΕN@]mrΏw@oοM#ŽΕσ‹rUί†ΈΝ}MŸΡυχNlxΝ±―=νΤΰ@r;LΊ·‘Η"€ΨcΪR‡-οͺρθz<7cˆΣF­­ ΟG„«ο‹Iϋξ‡\ώς†jλnΘΧ 2W”ΖƒΧ“U~.–ΕqΆcΫ―Ρ;—Ÿ’ξMphλgΧKψώΥΦP”Σπ&"’pzξΙߍ7t³ξΝ‡—†{ŸΓΔϊ/H@°"Κn1e'‚•ΐΪΏ!M‚…}E=m6Y‹4X˜)ŠΕs˜φ@J¬©ΝS{¦P0qJ¨ƒΩf”0΅΅ΥR”ήΠηΊͺΗΤP”˜i:AΤ«LcEQ »Κ‡{££τ0°―Γ‘ϊΌξ˜/ ι5c&ΫΣYόάτi:Μνdœ/Πυi΄"_μΜGΩΈ1e΅A ŽΉ›¦ τ1HΉ‘¬ΫίΏ0wtj0r4dνXʝΪ`ΧgΩz­ρFQή9»o€Τ\˜†VΔηv¦Γξ έΜ±t\!*zkΐ(౞z¦‘θCζhΏ-?Πω˜ˆˆšuEeήχ™6݁œR?»©ϊπώ„Ϋί€ω’jŽψΠ֎Γύz{¨Μ|§)? h8|†ZŠͺμ%όωή”ι»n¦ϊ=†k†ylΜ$*h΅~X“D±ΙΡDDT΅ωO\³’ΎS}xIτ„²[ kΏ Αͺ@ςΕ.Y`Iψqpδ>L¬‚”[”§κ]>ΙΚ»cuaΜ΄Rb“P)Ά[œŸ&jw¨θšnƒ©ΔήHΡ!Hσ‘r-s`¬hhƒ‘Ο4Y«6i3Q₯Βm5Ζ=‚ˆ(˘9Ά‘Œ/Ρδt2eΊ&Χ‡vSQZHΉMnΛFηΎΎ>·ΏΥaw„PΣkθ413₯Ψ4κ< Φ2 ‘|m ‹ιζ=ΘDΠ‡,ΠιΞ1΅»ΓT§1_ΗμD‘ƒ,išΜJ$*F‘ξ Σ›£;ρ}ξ£v2H0wΈYŒ΅k\΄ζ@™νλ_”#M‘ΎΠβΝ΄―ƒJ/€Ν¨ζΩ90_kπ5ΈͺΉρΡ„ηώs°Iw iΓ;@ϋ~αχ{ΗSθ;cϋviΡηfΖsΣαοζ[{οeΟVΗz(`aξv]Ebœ¦Ϋb§…ΌΜŒΜY³@Ν‘ρπ,9Ν{|M³‰:QVW±ŽŠΝ’β±φo}•ˆˆluΗp­“-%1Ώ ξ[·ΫgμΉάξ“ZΒ=Y»c•Λ˜›:αβ]hλ0ͺ«ϊ»νq λ{ϋͺι6΄a^l‚q°ŸκΉξ ŸoσΈόρ>Έa:ζD•γJΟ΅9δ'ͺ]χ6‚mΓwΚ`]MΑ•a0QN Wϊζ>|“ϊiDDεί‰u…‘ζ>Ν›QΞαΏ”ϊΓΐΪ/H@°*xRζͺŸfϊ4ΗΦϊece€W_}΅(ΏώϊΌ"I VBΩ-†•Y~ΤEΘvcrZ“ΉztMpjκ΅=μ³tΧmηΞAωt1Ε ½Wλ SΣ‰Ξ³”L6Ίl†9™ΞΣh(‡u=¦ •h¨;"’!PzZΒ~PΎ$“sέ†Σnι±3¦± ΑN΅μ» Ή€"  MցχQ̟·΅|εΣ1δjΤ3Χ!š₯1Sλ¨’”20_MώάΈ=€ ΝΗs+Υ5eη5ψšώξηŠ2•P F™GDΤΨόΈ(Ϋz·Ι6δ8Ϊ…vεΪ "˜*έλŸγcλŠβDuδ‰­«EωΖm΅+ίv™Š… HΤΕ 0>Ώ7`eYΟΓΠ‹Ɂωβ”0Ώ¨¨ϋ@§@€ΉκΟo±Šγͺωπ­_?ΙΈ­ϋ ͺμhUζς!aΎ’–§Ύ“―υΨaγ™&nς?u]~ξqΔΗΪ6ηV2xkΜΤϊ=ύ<&锐r_&{RΉΧΜΣρ²ΑJ`νߐ`UpΨ”έΣώ†΄φ ’ΊdU–ΆΐΈsΒΤMΦΤ”15fm`ς)!Ν‡e+Τ獇σ.6_H© oΚOέΧΦΧd…TζρyuM™αωΝ6S^† DΚ)94‡EͺrωŽ\M†@α΅π<€ΤŒi:6sΪ%ζͺf³,QS«χ†£c₯χGz7¬ŸƒΚ,Ρκ>ΜCΥ=ΒƝM!M–Αωަτ*o σ8E°‰7Φ΄₯›IӈŸW¬kn½\eΧ€12Έ3:ΞΠΤo ”_ά½Y”ί(ͺ3HY9‡ Ρ4›ΟϋuςT=9­Lξ‘3ΠΦk«¦θΕs ϋ£>›ΏάQcτΥ žWίπ|Έ2fΊϊžή|α_λ₯ ή„ϋ^_™ιήG3aψ¦½Ά§ςSύΚρψ€|^1WwωY%0…?ΧT}yΧ=ΌUCdί‹A(;@ ¬Φώ I VΩS―ƒ;\Θ‚$KΒ'%Ρ<OΗ‚€'=…ΈL‰h+Τ‰ζf(³*H₯}-χ†ψ‚崦b!6œoΐ± 2ΓΙ!ž”€Ή*Ηeψϊx]ίWρ »Κq†‰ΟRkWKΘMŸˆˆΠ~$φM™GG‰x«ΖqPΛΝ3hŸq“†Š]υ†¨Ίhψ©Κχϊόω± 6­VU έjK™£γE8E ‡μ„°QQχ²kμ 1ι/Κ«TΐPβ>A $ήve>~CΔ‰ωΠI“ ¦ΰ@aδζiζXΝnΥσŒΑύ`λΔχŠςT'ή» ύ«‚αιΈΪΣ}α8  ‰ O7UΌ+Ζ½]εqNΩHΥ«ΨΤ §^+κ΁\|«kάWˆΔΖnλ1ώbkΏtβE9HΥ»Οq#^ncΜPKΟLRlλ]ξW]ΕΜ’ŒϋrΓgι½q¨xΆΞΆ’ˆγ«΅:O 0¦σΆ6ލ§DδΟ» ΰvΑg‡Δ@°x:ސ` 1€Ε°φ ’EL―Y Σ Φ„_ώ J­DΖLDΕ{"JΑ;ƒϊ\Ÿ\tζΥ Χ·#ΨΉƒ”XKΗ³ZRz¬m¨8d–j󹏐~΄r?kJ― ”_ΣczbΉ‹Bmڊτ Κ‡CM-™ΌID³ξ†ͺ+dσ4λΊ0υƒRρ²ΌEύ οζ?HY5Θ«ku¦οŒ+DσXLΐό΄Ω½‘ ΠΎ™|Eš^›N™fCΩy³ΑRεŽ–Mǘ› ΛΊ˜*ϊπ¦v“؁J·‘~Ττ]Τcωu²λަ―ŒY.”^‚Άmε²q½ΗςκΊΓcΌcŒ\‘―χ'μpΡΦτήFσw5½2U4œυ›»ά—£m–λŸΠyœ˜C]Μx΅\ϋΥS†ϋ1Sr7΄t}š’3 Πw‘ΪFπAŸhλ6Ο‘cš2Δφ/"ϋ^ BΩ `%°φoH@°*Κn1¬ύ‚”Ϋ9ežz―LΉ;Yvόkš¬ 5;ΫZEσdJΪLΨϊ<MΉίgτ³Ώ Ήc{οεΟm>Gγ8šZ2ΜBšύ².’²[ kΏ Αͺ = q¦ΰΣAFO +΅C²2‹μHρcδI±B Hš‚r.«Α±Z‡›eέ!P(ΊήP{κ`.ζšζ²*Έ™6M=ηŒͺϊsΎΦ ΥXWχšΙΝ”žQςΩΣ26¨ s^ΊRηΎ€S¦¦œσIλρ^p+»£(₯­&SGC΅%_Ώέε[¨T[mΞS33Ν6 ΥyΈqx£Νj8…3)ν-ά”­Ζψ(ςš}Kon~­Ηωš KDδjE_”!’­ηόΎw`3+šε:kωš^ξp»ΌŠ·oο]-κήνŸ-ΚΗŠκ;jΧIΒ΄g’©ο€uψNšψN™ρ² 1€Ε°φ ’@ ¬ $†΄„²ΑJ@ސ`IΘDΤ°Φ~AΚ‰c83υ蚠'I φ0žd20δΊ.Ψ3ΗeυCU»ά©ΑΚΰ5^+!89ΐ½rν αL!Ύ€ύΣκⴎNΰP‘’Δ?Ϊ,Υ}ήO•‰4Δ%L’ΐκστ>8!‰ψLlξ_HŒ m΅TΌi IΗ·o»ΝIγbνΡ€Q½Αςa_Η«PbžBάΓΡ²νpΕv΅[*Ξ5—t‚ 9ωt Ÿ;΅`ξX„qhΈ Ζ£ˆxp^]€δ(»›Χ‰ˆθ8ΘΒ1vq duΌd€Ψ›KΌ;υ…φν’ξοΎΒmΡσΝ&ΗO@ll’‡{”π|ξžΟ[φUbΑ—;|―~Δs³ϋϊόέjΓ΅ŒΓΓeŸηΠ/=]”΅mέ>žOΐ|υχξ©xΤ~ΈGDεΙ…ĐΓΪ/H@°*bχK@ ¬Φώ ΙJ,²½‚;Κ—kCeŠ0 ef¨9’O'a#τА=Ό­>ϊ€ζj‚Y€‘ς`#yŽτ ΎΊF̘ΒZšž©x»Υ-0zΝ5στ"Aςͺ’žϊγ|Η :ό*œφ™’©u]rΐή $πύ©Ί1yΕk]0 ­*Κ-˜²€eίFΚ‹τ‡ΫG”Ι§ν€Q-Θ§ΪΈsθ³yk£Ή‘‚’jσR«Κ΄ Rg#αF)w<εœQ#mXZ―uTf’)·ΠgϊΠέΈLcœ0}:!άΧΘδw OhΣT4<ΕάJ»χ•Lτ MσωW:|ώ?έ‡-π<ά}rKνEμ€ρB[εnΊζίΰφΓάΊ>Rσ¨ —έ2nυ>$"’“ ήZπƒ!Kσ'Ϊ™#ΞοΟ^φγ_Ίt‰.^Ό8Wg€Ÿ-«~ΩXϋI VOвΓλ^T.^ΌΈ΄ϊΓ€Pv@°ζ([ σMζ°°΄7$³B?ξΧ<+\¨’ ηUv3ΧΠ¦ͺψΉk΅.Ξ|τ^nvŽ@ΣΝ¨οL[ }θΤP\ i> ΡLΊs€gTxΊ);+šw<Π'Ξ]F˜ΝSœυ&(φ4=‡χwZLχΨ†ƒΟ[ V=ν5 fs[Ρ6‡ͺ£ΪReΰhΒ4™1Ν‘ύ›-V–#U ξ•BŽ"cˆΊΣaσV€+ V™EΖ΅h.€Γ@Q‘^ruƒαιXӎ¨L§LkšάOUΈ§ϊ Α IDAT 4U’)ΉΫͺό68Έ@†#eΊjΥ9myR―t

      (φHηvͺL™J@…η9εΉ‰†[區llm΅l(•WΘj54Ι4iΤ­ ΧMAqηΊκΈΩa(·{}¦ΞΊu¦Ύͺ#ή€ιhΕY”Ÿ[e5W€MY uGDTΧ†¬D¬<;²y΅¨»0ΝειM¬IΐŠΕ$ΰD·΄μIHξΉόŒBHažLΡΦΡ·Š:ΜωTΧTΰχχ9]zr„ΟΧךΙΧ΄ΑTξŽΧ6PΠŽωΉοΒFl«dϋ^ΔΟ#ΙΥηG«|~ ΄q’«r_§ e*so|“ˆˆj6aΆ€ ΄ΚΟ; gΦΣ†u[Œˆ–΄ -ƒšϋ,‹@ ¬"ž΄Κ,T‚Έxρb‘œΓ:σΟRΩ·@ , OΚΛξ³ͺμϊμ³Φ/«Ex ΰΗ Ώ!=ιΧ<+%²C-ΫFus>{ Q~€“ƒΙ}–AόΔ0Ζcώζ¬ΰ΄`$Ϊ7Κ½Œ\γωWϊΌΗjΧ…J Iϋ<ˆ•”ΙΒ‘šΧ‡f ΝWsΈ–₯ιLπ‡±©¬¦ψύ8)—ΫΪδ2sα€’ζiΓM bI~’bGɏÐλbHˆ’αD'φk7Ψ1`βCŒ¦ξλΆ–Oi?˜—R'k ΄9+:ΤͺΓrš―±lΥn ΐ)ΑΧ±«­ν«E]:εΈL₯₯€ΤCŸγB˜”/¨dyIΜρΊφI–£ΣςεΔ°‚Η"ΣWγΤ‚;ΒHΠΧΦΟΖ΅Y Žp<%]ο6ωώ/unε}mΆ{Μ‚[Η11αxͺ$Ψο8Nχσ›,ΏͺρΎ q§*Θέ‡ΊΏ7vӘ€ƒzζΘƘΫ:Œ·{η‰ˆθ‡ˆψy.βe·–Cϊ4υ@π4BάΎƒΔ`I|H‹aύ€Œˆ ³ƒ4Ξ Ν(ΝόvΙ珡}ΜwTNΉu¨ ·ησΝΘ²ΑόΤ84 μΫΈ7Μ΄ Α QΥ(ίΖλc0ŸIέ ι;Σ “P7†n ˜R«$@S•ε‘‚#W―ΞΤ½Tfœ@U"}VΥΠT!8 ͺ.Κnπ±gΊΪq UΡόΤδQB™±‘ŠΕ#ή’XΡk (ΕΙθœ§h¦ύ½σEέ±gΎY”Δ<Y·rt“»Ι)y0ΰ­΄SΊ3̘Ά:Ζ6n_ƒΤ难ΓάLiθ·†’[¦s=0 MΥ…­)žy|}άΩoŒXw:l˜Š0ά½λ–«ΕβH)·Όδ0—Ή~·ΑtxΚ*Έz•ϋXΣ;ύ—•qΈFeΩL:uPΩΉm₯RσΪμ"Π»ω³Ey8Pτΰ89 ΪΔΤκ%}ΑcSƒSηΊ3-V‘έΤΚ7lΏτ‘1_b¦?#Θ'΄ιͺ±ΈdΖΐΏ1?R³ iΊ 6ΡΪfΓm6ΣΞδy2'A[²±ΦŸWζ§{'ήp‹›|m‹Ο³t> άψZ–‡iζsTα™/'<4\M­™v<|>*ήjFΉ6f ‘isΥ r(mu™ή‹4uTxSe―Οy}κZΕ7xSfL_·w>(ΚN[™xζγΘqx’]¨'L>Φ_gcT―)Ϊp`6^£ΝU!†”Τζ-r†ΜΙΫΪ‰a&Α BΛΉ­&Λ£'>Λ€ΝPΨŠΉ&QΦαv…ΪΔs»Ν²ν‘Ορ +R·κ<Ψhγβ–Δ \pˆθU¬₯κr܍`ΗvJhk‡†ΜQQvhCΟ0—0?uλ*β@|$œηϋjσPάP–}ύΑ‹DDδγA»Εf‘wο}Ή(Χ::ή±+ΔΟ1CΪσωσJ•eεαΎΊWύΘ›άV}΄[F”BΎ;L<³‘jo€€υC…_σωΨg΅4}7ⱌ NΧπT1坐c’/λψη1θ«ΗΟ¨©M_\τ­#Φ~A‚UΘΎƒ,H@°$e·ΦAsU€αfάτ€φ¬2‰6ώΈA“PΝ8εΈι</ΫΜΐΜΚΚKFy†R‰zaNPbΘͺώ‘λ±/ΨoΛ΄:€ή­•y‰9ήeα…k„4½pdωp υ―Ε œsJi©v^α  ΣC2fJήbzΚsεΪ1 O?ωjΰoeC5«κ!†ό9ή3šΚr•9g †ͺ–9Ÿ΄Δάeš°™v=@β¨9„ ½ˆωž:>Λ²λέkΊΝCξK‚nl:ήWF§ν£ou] ϊŒ‘)›Ψ“O)φ·‹Ί άλΈ–Ζί£X€τ¦ qoo~€ξ{&2ψ’^φΥ<@YΈ‘Έ 5•iΓ9ηκό oOw‰ˆ(Ιψσdη#-ύR¦— 5,=@ ¬Φ I VBΩ-†΅_¬œ1fΤnΨscΊΤWY>‘GˆsΠ‘΅8Fί7?ˆ4}Α|JΰΪP\―τ‘Q ‚ΚoF7Cί=tO"’*Π)Z‘7“{)ΆζΚh$‹©ΫӎPC§ΩSnXZΧ&₯Θi3Σ>ξXU;!<2u”aκuMΝ “DΜ”—­Ujνζ>_(5C³Y`ώZ‡\<υCS£`#"κ?xiξΊ9ŒΛdΉ’άάϊ˜ˆˆH§ξCŠqΧΣά/Pv€[―kΚ­νοƒšΝΣW㠁ω–¦έ8,΄<ζ›=ΘΉŸψό 8= ΄*r hΒΧ`,jmNwκ> φBƒyπa’Ϊ:IωZ7,h‹6Wύ!(jƒ{/n½@DDίΉς:•!ΦΉ°3‰ž85,‘μ@°Xϋ7$@ XH σΕπt,H†%Βή”)ξπmiͺ²―ΈIVŸ_¦fC”š°•η^BJpΖ(uώ^ΆΕ€SΒsp_jΙU€±fhKΝ€ΝPΘξiΕ]ωeR₯¬cx_³Ι΅Ζ“ƒ«3Υ›YAQh“7˜2돔ŠΛE^½ΞΤT|Ÿ7{T»¬B3Ή‰*v7Φϊ&gάΏ?eΚ ©ΎS6οš‹(α6ΧΐR‰SΎω[ers”}c<ɜW&[§‡’ τΰ€²š»BΌΗάeίhήjLS“‚` §ΐΥm>@–ηΉu IRž²Έη#i¨ζŒ8f0€‘ι–ϊ|ΐ1 A  Έ&&ϋs@žhsVχFQ··{‘(›xΥ(δΈOdέU£Ι@―οBάg8R±4W΅Ž—™ΈΚύK½1Ζ“!©I˜NψΨtϋ}zAΚηcβΑ$PqΪ:Yͺ}vKŎϊ·ixOꁩλDΗ“Ύp”οoβFDD―΄T<κΏΏΖm½;δ$Љ–ίsΨΖ6›³i(‰ψ½KΫϊ8π$.]ΊD/^œ«3ΐΟ–UΏl¬‚$?ζΐλ^T.^ΌΈ΄ϊÀЁ`IΘsϋP;‡ωΦς8±2oH―ΎϊjQ~ύυςΦ₯H‰©Έ ¨₯Bφ uH―™2ΚΎe”ΩŒ9«Ρ…ΓERsϋΚ?7snF^βτ0σ9R‚ζ8μ*ΘΡΡiJœςΉ»νσ!«ΝηSΒ±΄C>ΦΈ6T Ωΰϊζό™|N@ίU*`žZSΗԌ›γ;DDԏω ‹Ή™Œ+…η΄?cqF ιεψKKΏζͺbe€Ο΄ Α β0dίo:œρεθ‹KΏώ*A(;@ ¬Vζ ιGFNLyΝ¨Εΰ†Mΐε7+‘οπ|TιΉΦ|k{Έ/Μ(φΌ’Ο1yI[fς8™c‘2ΔΆθϊd‘ήΣΗΜδnΒϋš30R₯Ι)ΞG R;έVυύ)›Φ¦οΘ8=TZwŠΊέ™άGΚ<υΈ/ yk4δ|H^ηΪ\ϋ Ή7<[ŸΗe»ΓNχ|Žˆfσ!]™2UΨΥTά.Ίœ…Ά«U‰~8&"6Ά]&Dφ½ΦA‚Α“JanTv¨€3 9ƒeΧdA‚5ΗA‹ΔaΧ/² Α’KX~!< #άζŽ#"JK΄Ψ91YΉ}ΐ±ζZ(5?Θ9ά„E’šKϊ22ύΒλ—$œΚΦq\JβU3ζΰϞ …Δόύ1ήebD ² Ÿι€z*ͺƒμ{RΚΚΏπ•Pwμ‡ γžΰ‡μπ ϊ†cΒ\†qΘ±?`'‚OΊ>ΛΝ“„¦RγΜWρš;ΠNΤD,χ7j΅ ™ŸŽGμbPν°k„½ϋ"ίKSHώ>;I4αX“,/)u ϊ,έFŒwMB‹ΛS;ͺΤ“ :MŽ=₯ΊτΥlώ"\φΥή x,vϋg‹r£­΅Sugκ, oVTnGόyrϊ¬ˆΝ^œGbH‹αιX`π€bHO dτ@°xΊήp?(5O-Π3o#λFsΦ²k%%4 Ρ¬m‚9Ν]Λ$ΰψdpΓώ£\#ʜPʍ~’ζX€K$κφ~·ΜHηυωΈρΪgkz-·Π©›Ξ·i&Ya ¨BνĐՀώ½zZUC£WDΥQλOXͺΙί¬H rV-`’:$Λ‹΅{h7ξ½ΐ]Π¦°6ΘΖ-| ΊŒ†§-7_½υ…™6ΉΣPŽ– » φ‡μ‚P«³΄}¬]ŠD}D”‚+Rm·ˆ(Πn•:Σxέ폊ςy-£Ώ2M‡H£6όCΫu0…}³Ή[”wτ6‚ΫLσ}pοσEΩΘμοƒάށΙ{¬θΓ³!χυ·',gχ4νX)λσ’ oH‹αιZΰ B€Ε £'‚•ΐϊΏ!εΔ”U†yuJ¨¬*Λw„ηγ±a>{Ρ,ufŽυPΨ”9<δϊ`~" Q«Ÿm-ΏUq-€Α έφΫτ•q3y˜¬Ω6Ρ¬ω©¨Οσ2!ž‡~`΄jς%/θŒk΅Ζ]}a “r›Λ•©VP‘"\β†6_šF­C³AϋrpzΨ²aι©£Š²Š#¦©ΒΥ^d«5«ψΰ•Ί2UΕ_ΣAοω’Εͺ-qΒퟌNπεŠ*lo\)κ ±ΎR†ΆΝnς U=¦$#—Λ§θ―<αΎφΐ•ακT)βξ€ϋC± /.ʎv{hzL©=SηΆ΄*κΩ|kŸϋR«Ή/‰Κm„*½Φγ ύo΅ΤD{sΜsΐ©π„ BEυ%οiΩ•έbXI VBΩ-Y`Ii1¬‚”ηL΅9ε―ΛΉVΗYe9’ˆ˜fš‘μJ5c¨Zr#Tα4/˚X’’;¦­x\™lIvξ‡ο_PeΨm€χ΄oeΪ:@=hŽΕtκphVbžZͺ„ΔσjΝ«%χΊΖNΈ\½ΒΖέ΄1―˜sAΉΑΖUCΥΩ)δsJΈάmp^Ho u`³k½Ζ&Ÿa_™Ž,ήΨΊ³O•ςΛ†JC =W]iΐ&(Ο,/h<`γR·ΛτέθΞOεF}8sΡCΉ™΄ΔΣ₯g+Σl{~ w@7NjsŸoΆΨΘ5lί,ΚŽΞ³Τ„άIοΓζηŸθ¨ ‰ζͺΨGCΧ=ωFQυ§7ωόΧχΥ†ίlςmBκw³X­ΆΊXI Vς†΄dA‚%αΠ)»§όεN–s@ ¬žͺ7€c8(‹6Λ.Κ‹!ξb™Έ ƍΚ~‰ΐ99nΌ―ΜΗmfξ_6Κω#ΚX—•HΔλ}ΔD|¨r-‹ƒa˜\r­τ΅§Δ€L2@lΚΖ#uΖ…²ΘΆuά“ώ‘λCŽJj-G§†™Ψ—ŽS‘»ƒ1 ?¨ιΆπη&VCD”i‡‡Μ“T0rΕ_Ύ-%ξCάΗ\ŸΤξ!Η2"ˆ»Υ:= L}σq™ €ΚΊŠΕ£S||Žού‘’P7:·ψPˆϋDΪpԞ±Θΰ‡<ΡN]ˆ p_ΠΨΩψ΅]–°£œ½ΦU ϊ<ˆK¬ς3ΨΣΟ°qμ­Rε؝‰g]/κ’όrQφτ<ϋ™.ΗΓnNωϊΫz φͺμz±lΘbxͺ$@ x’BI –‘}/†΅_ςŒ(/Λ1„”™Ÿ‘ι@"ž—˜’ZθN`LDA6n‘¬ZηC²fθ°ή­ ­h@ƒ™κπNΞΠ‹Ρ“¦υr3!Σ¨«–Hγ‘Ι,σ₯|ύˆξ (7N₯D3ν6T]eβ•kL%| ].»jx’ς”§L 9Ϊ•"zΫ:σPϘ›‚„eίΡHQrY“ι"“γˆˆ(Kηέ`ήΊίWF¦hΊωŠ-…ΆΠ³ 4Zšςΐ9&·š«%Ψn+eΩ$d5¦8―mΏ«ξ9Ž&wٝΑƒΤμ–¦pλΰΠ‡Τη†ͺslž0y 樉κ+ζXκO™Rko(W…QΜm !…9φΡPŒwΖL£=ΧΊ[”:ΟΤK›Χ‹Ί?μ³Sƒq›πSV Ύ>d*σLMQ©M˜£ΈiT4%h?r⠞ΦA‚ˆƒ,H@°,H i!,uAΊtι]ΌxqζίXΏT€TΠX9RWq‰’Υfeσ¦,ν9{ gΟΧ•pP”ήŒι«ͺμ0ίP‘ϋ©€}D<HΞPvζϊ€S/Ι—4“ά4ϋ Κ6sύ7ꍳ˜6Θ./W|gύY•λ *ϊϊεη›Ό8&ΧQιΝOifC1*γΪ|±šVž}ή κVYΩ•j£V“ˆ¨κρη†ΖΚ@Ωζε5¨Ό=• RfIΐy‰*~0“ 5wl§Λe6†ͺΞš.…©&’›Y«.·+τe θC(Α(δΧυζΪMPα­2½hΎ¦Ώ{η<·%`Ε QιM`“ρΫάο MuμG«δiЦJ₯—&ΌΙwι7€…°΄ σC[”@°φXΚr.‹@ zC:̞r,ό†΄¬Εθk_ϋZQώϊΧΏΎπυΰ±γ Ő ,«ώqa)”vβG] Y„LΨd&§^i‚=t-‰›π$/‘ZΟΔJ²Ήgέ͜―c3±Žl^vm‘cB™;B6£υώδΟρZ›*3jEΉΉσΠivΨ Χ†2‰=AΐΓU"*‡‘C‘sΥ΄†ζ§σךqΛ€Ψ[ͺϋε1:” μs`2αKΦUu iίδζ3|]δ/―p[οŽεfλ>Ujλ¨Ψ|lξͺIN8V‚²οj€bLNγ>· γIp­XΗΑβ°SΤωnΛFcˆΨ‚ˆ¨ ςύ3DDΤj=ΰϋC‚½Ž>ΆΙ1`dέQο""Ί 1€Λpέ/iΉ{“Μmςω©–“£λΕ†Γ“σ½±Ίξ‡S^ΗΛ|¬Ο*I:ΈΞ8(<²¬ϊΗ‰…€'݁@ Xˆμ{1ˆμ[ –…C’μžu:~fήnπίλŠC[žΔ !#gј³Ζ%ΏF‘R„σs_»*ΰ)>;-X€‘2l·ΨΘԝΦB^[›‡Ϊ?ψ­Σs†~«DLζ3²¦=ž$˜/ΙΤ“ RοδΥaΒj™»[εφW2Ϋ«|I쎰;fΩvUSrVΩƒ#’–n š«:ixK|­ζΌ‘iΧaΉψ‡SεΐΠΚπ6τ«βͺϊvϋ2Nyr_h©qύξθOΧLSyzΐ„[ςG‘eΰγHιŸO₯΅TΫ²°ΤιαAXΧAΑγ‡Pv@°,H i!¬‚”ευt`Ž£J –—|u9š|–ΠΒh€Z8”6ΫbΪx€HΞ΄{ζžH³iϊnζώ(²3χBΚŠΌDe‡”Έ2Z1‡ŒΪχΒΆ`jχ•έ ₯fŽ=ΐ!#zΒΦΚ΅¬‚vPΤνΖ|Mhjθ―*8ΔϋLzi@œOH™ΕΪtoΔΚΆ2 9kœπΐ6t}Τjέ&ΣhΖT5Kxw"’DSj˜ήΫksŠςmPδέλ+J.š*œ²’ξτρ7‰ˆθ6€·lγ=ύμΩ₯}¬UΤΈπάH΄M8έ:j ΫvΥC°A6nƒ¬ΊHˆηǐΐβ9 λ°-Ž»ΔΠXγ&‘»|―8ͺα―‘Ο±”ΚDΕ{’:·Ο9†ΤΩQ6˜„’C–Ÿ)ΚΣ@]Λ±uu}§m%ΑΞ3nŸ°Mηͺ‡Tw4, ΄[Ευ1_ D’£οNψώθΚ`ΒlƒΪΠ―d¬bSυγ|άqΝW1’ζ+ŽQaͺz€λΔ2 bΡΑJ`ύߐ`E oH‹aύ€œΚΝOΛ=Hjm˜‘R}7•S~eΧGšιΏΧΧyWˆ™[•˜«ΞδV2wr[ΐ[NΘw!6γKsχš‘=MΠυ hΝ±Hωαh s&‡Lξ%sΤ!έν€ϋ•βΕΰ<έΗ™ΏΠ—ΚTQKΦίήΉΔHrΩωˆ|Χ»šl6I™βC42­ι!ž›†{3[ήc―½υΞ+Γeΐ+mΉΨ°<«`ψŒ Π3I‰€(6Ιξf?κ‘•οŒŒπβήΉ7*«:3«2³Ο4::7nDfυ­8χΔωλj΅ž4T†« …Γ[QœτTžλ֌Όv5’’@θ+ήΆqͺβΔΚw1ΨΆw›j>=3ν&}MwHwυψaηŽιίϊt~§Xήnt`½‘ίš‡j₯oi%—zWA:ΫΌXώμΛ˜6‘nZΌ??ΕkGΔ—Ξ‘_Uksο4!£ςΩ+‘b*~ ΅ƒEDdΏ‘ [°λgΦώ¬δiͺΆo—±ΤpUHs±ώ!„¬ ζ‚wBΘJ°φOHyώŒKΜ(aξΥΒΔ‚@ωkά^p}™glχφ Υ[¬£ΞSτ†α]§ΪQΘ+‘ΛxΪlϊΎα½Œ\*Β¬ΪP‘σΓΉ0π4‡πΦ(”:ς]Κ‹–0oA­›?0[» dΊ¬ΧbƒVS(a^ικrlλ α/ΈθβK·υΒ]ψi΅¦7υ†'[ k‚T˜ΪΤ„!Θ\ύ±&)dM—j«χr·₯υ”κ[F>‹j uƒ\ ͺˆΚ* Ρ?ώŽ^δα/Ν9ϋb:μί*–·jFΓT‰τυΞΦy₯₯ιΩ~]ϋ½χe±μΚ¨£€χΝΦ“b9΅χ+/O‘ŒϋΉiχɎ†³ώΌ£ΛgίΘ’_ ΄ύjUϋ=°eήσ €/Jvσ±φ!„¬ Ω’ τ½(πξBY 6λ ε€P #(υνΧθ ½‰eΓg8γ’ΐρ“’sΪχœmΆ-tΞαρ‘@T―΄’s›‘τ–„i…λ°LjLZ€χr°•QήΔzJ#ΏO"Ύ|W\7ή«¨δΊ­<—t1I‹rιp8”#O­ΪΟl¨jΡc&;Σ’ ˆΘ~ΛΌ¬9Ζζ'ϊ2iΦr ·zxy*Ώι΅ F:2?–Qͺ|άVImχεlυ˜ΤFΪkκ‹£OFΖQvr/ξΌφ‹εfέμ;¬ͺ3o/ρvGFžάκυa9s·|u‡bχΖpάΨJ}ϋ;χ‹u)H\{ΆΆ­§Ϊm•όF½[φϊτZΏ»­ξΐŠΥΦΐ1Έ χ₯[sαͺhλ\,”μζc³$BΉA8 Ν$BYζƒwBΘJ°YOH8Όβ³u—stVλΐ\ŽΧVήξ…ŸZ’P’ˆ& ΐ\ŠgG/φ1…xη·sPeύ .„`Π@Έͺ7o4œn+ΊΚ―5³,δΠΧζ€’ͺ΅eW`;„ŸFnZ ό’?ΜA©ε“’alΧι1^ϋαζUΠQ•Υ θτΔΜν€ΫΊnwKηh΅ͺ—sEDD&φύƒΌͺλβhϊ;–A"Αlί#L"ΘΜέΪ{¨«†ϋzήθž=~!£@ψθyW­ΰ·‘/_œ+vŠκ₯=―B»xœ˜© }ύΕΩ7ŠεΨbŸj8,>qTν|ΠγΆŒ_¨X~jSU_©kO Ψ`uΛάƒ] Ÿζc³$BΉA2HsΑ»G!d%X'€’pUΟ‘νμΙΰÍŠΚUžmΪν[" Ξ LυlΡξο²_άyke}½8ι!”Zαδ²νΎ•ΞλΎαΝΪV‰k[¬:–Γ·ΜSƒάz°j{t”:έqpί"tο&ΟμχL[E"¨‡”ΐύYέ\D< „Ο>έ‡t>Τ€ BuR`Χ/ΦCΫΐΟ:Θ|͚ΖqœŒ$Xιi›[5•ΔάMΜΐφ=©νϏMžžiΘι7?.–ΫΆ6R£₯VλQ]εEgqΗpΥφρ·‹ε—o•ˆˆά+φ°zŸœ¨$·Ώoμή•D―υ{δp2vR(Τ©J5@vd“ΆΰKτΞUώ{Γ†ώ κ-!›I‹&ρ"—fύ$BY8‡4!dApi>Vf@Ί{χn±όα‡^ώΐ,Ίά=Wη[΄χ™=Fϋ²Υ:ю[Ι /Ζ6:ZοθιΐΌΜΊΞ< ΌX+’eΦOΪzώέ†ΑΦcs\£’ύC_«5 /ώ>©σοΏdξΧϊ\]vcp"&φψ(Y^ΈκMrtt$""οΏώΤΊyΧ_λ? BȊpSOHGGGSΘ³λάΏ―Ίώ:α€D! β&\v71p, H„² –υbμχw/ž;Ίi©mQΌ’•΄½9$˜‰Bw! Μα/?xΰ~)ςMuΧPΧE˜¨œ£‚Ν³ ŽΫEΒsSI`Ζ+Ί8VpΎΘΞηx‰ ΨWΧV(‘A€(π'…ς’Ή1w1΄ηΓμaYMO– ‘Χ/΄Ϋƒν;†byΥ-3Η1:ΧpΣxŒσMφ”ϊP«κΕtϋfξ'†Ή– ΛΝ“Ή”‘J¬γζBβ†ΞυLΪ―A[0Ÿe瑞€ΕόνWΥΡΪΪz$""ύώa±Ξ9ά1Εcm|R,·ϋΖ_O œ΅’©#˜ΫΩωZDDΝΣb]Ž;›ωžG]-F˜BαΏΔΞύ΄ϋΕΊΜ'=šy¬7·4Uβ“§ίΆμάW>tˆuαgmσ]ψΓ’ζ›–ΪΕ‹1 BΘ5ΐcηƒ!„,ˆŒYvs±φRžAΘgόœ_†$rβΊΐΎψΛQy*„έ·$4τ}φ$?wΘm^h¬Ϋ7”ϋμϊ(°%+{”"½{μ|ΩXw’ Ϋ²*@αšRžέΆ£TθξΨ―£A ζΪΆQΦL]¨xjι‹]₯rbΈ―y]?„‘K]HΒύ.¬ι°nœκΕμέϊBDDN@F§τZ1G+9Φ8ΚlΰhΦQ9¬Vf jνυ ΆΧΔ ,E©Ξ1iͺΔqΫΘc/νiRCDe°ƒWώτΎΈΘ‹2ΨkAlδΑΤ&(μCΊΒ €QtOή‘VM­βo4uΩ)ΐχAΒr2’a΅›6ηΓ/rύu²φ!„¬ 7υ„T6x,jύuΑ‰BV&Wgs€,ΰCΉ)›–ΌΚ~·qξΌ¨L+ZΨ&60#ΥαbCέlGξλL8(maκΊχ&Ι ‘$Εt‡Ιtg=™ G(ωAg]’C F“ˆŸT V±ςBPρېms}I_Λ‘·ΰ3ςΚ‚_α7[ϋyϊΈΣŽΌ¬₯;δ}ν JuΕΩ‘/IoϊΗ]vCλl«T5€΄ υ’Ί=p¦L[£±ž?Ou{œ˜²ήγΎ&1 ϋ/²υ£vw4πT œuh]tO \utόbΉYWI­Χ3ςΩ!”nχΰ7Ες±• OϊZb}άΣΤ…ζΆ‘άŽΟή(֝άωY±|fΏ /wwΏ*–OΤ˜c‰χΓzHσΑαœBΘJ°ΉOH„rΝΠφ=›5 …dΊΛl”8Η}#ϋ²§'-yςŸΫoφy‹₯’@X·=†P”ΙŠxϋεΤ=i°μgΔ­χ^‡λnL—fχ^ώ Im^ιxwPΙωD•ιϋ*/›β}Γ~–Ρvέ5’ζKvs‚δ‡’œ$F>Kπ2hgv΄UΡ S#Υ+*-Ε±n7Ν…%CuφuGꌻύŠ‘2pΛα €ΡΚ©Ϋv«νk:ΠΎΖMσroΞ΅‘'ο™ϋUV υΞqΑ=8ΣTόz} €u²_\_βύ‹“·΅­ζ±ˆˆ4@«€γn2ά1ΧRS+δϊυwχΝyΫXΏνs­ν”ΜuηΈΊh(ΩΝ‡sB!+Αf=!BΘ ²ό}› $BY”μζbύ€ό™ΠΤK΄O—Μ1ν—ΨΖ‹ζ顏σ"…=Ή€_nΪ¦¬(ŸΟ) ouΛΈέKB₯2ΰΟΞΉ)ο[(Φ‡ν{ΧκΪ„ΓgόΌβΌΪΥ‹ΤΌ-Έ\Μ‡Α|[}ϊd8oαo³n58‚1œuΠ4›fŽcx¬s1 |έŠg[‡χSφ·LΈh§ΏS¬Γ$‡Θύ§Ÿα°―sD“±±G'kIΑ*νψ“Μœwά½S¬jΦΥB>l™’¦zc0<΅gmί·wλ½Οtί“oκΉμ©65υ‘ν};7τΡ@η0T…"‚ŽGP€πλΡγ©ν5°Ζ&I ‡¦†Ήΰέ#„²p@"„E‘G—ώσ―ιο\iA\ΙN$ŽΪδ‘νŽκ₯2<‡B5„PβΑ€W[©L† \#&%Άο€Œ'βˊξ& δαiΊΆf$=Dαν…m<›!ι•…ΦN¦—KοKζοWΆ/ΚhΡ8πŽεfΰΎ%ύQdFjΛkz‚J{Ί­κ!Ε£ιnOΟΫ*ΉU±νΏΏ»£ϋf6°΄Zoλ΅@ !ΔΩΝΫ ξe*6@>s$±Κl}$ολmΣˆΪ¨€XxτŸalCU[`εBjBΗ¦=D˜Λύž ‚ΥθW‘Θo'vρ‹¦;€P)©4‚€‰…sΙAγƒρCωΫoΚoΏΊ+ψο|yύY3ψ„D! "Κfωΰ~(πύΫςϊaCώΰϋ·εƒ?ϊα₯Ž+ύelƒΨŒ'$BYώγ?έ ·£»―Κ­νšδ"2ΙriΦωΗΏχͺ|ρώ‘όΧ\x¬ˆΘΰ£?]POW“΅ςC^f5wνΒTŠTϋαgH•€€8ίε,ήΨ΄}‡ξ+nwsH“πo°ξΫ(4―$"X©e«oχΕ9"œ³›><Δθs)"α²ή€·:‹χ,ΝCH$>ΜGΩγbΈ°lΫΓαC³€/Œaήfl τννBRΔπ X~6σ2ΕΌ–ˆΔ uŽ(KΜυvΞ4 βoέώ¨XΨ‰ΰΛ`>Θ%TΤλ:/υx€scΝDη‹“‘ΞmΉ9¨,[’—λςΣΨ$ΐΒL ”ξ!/:”μζcn» #B!‹`αΆoctpN;\·2 Ώ]ΩD /yήvR]©­<σχ3λf²βrΐφ2ώΪPτϊϊΛΚ)Ν°₯βvw9^8+ΚsEΈ*HΓπμΣψ€­ν„2†Ÿ6@B΅Χ…¬ή"*–hNώΚ ₯Δ"΅δάx¬;§£ΪΤφdΗΈg?#Ψ-«©•Ή^1ΕΊCη]ΨΩvξE£¦VκZΝWϋtja_ΤXMT?¬@ jΝΚc½ΚpHGH­dwU+ψνw~S,GV ΜαwΫΑι[ΪoΈΖvoίuͺXwΏ§A¨ίΫΏgϋ’R$Z΄΅½ ΆςΚ Xή³ί‘oοjΟW{_Λ[σ)ŽQΛ]0”μζb©.;>9B^(^€ΤeΒ€BYœCšυrQyξ ΞΊ %2_™”w^9σwΩ“Ρœ\SvŒ“ΔB2Ÿi͝UΧ€BξΏ ε7KŒj”•Ό<—]((kUq½u/’B‚ΗηΣξFο3`α8σΆΓ²“ύB±xΚΪΔ:NΦd—Τn⚽q]pΆΝŠΕ€–0χΆ;₯³ͺ τ»ΫΊά2Η5vτf`ΊAΆ«2YΤ1Ξ²S_έ³wό[SύJ!=!™t@ΣΎΚloΎρΏMŸRuιuwU&;Ώ·XΨ>VšOŠuϋPbό§&”um%‰^—+a>kΠl-Φk9¨˜{ψeO]„έΣ7υόΫχED$”BίBŽyΦ_λ? BΘͺpƒsHΟΞε_5EguX BEΎδ?%lŠΫy³žΚœu!)/΄oR²|Ιφ=‡Θ=^Ωp μrη‘ η½dλΦAϋžγ.π‚%Κh‘rκ^Ώπ\VRΓIi ]xή€;U™Έ/3έ‘Ψ}<ή]wΩ€²3ΡΑ}υΚ™»γ°Κ›m8ΎEθ#τ/«λ…U+FΓαƒi€~ڞvρUύ©uγΎ:γ& ³Εm•΄œΛ.NΌ·|΅Ϋ5SO(K$½Κ–‘βXϋΧ΄ΡΓφ{MΪΪΗPW&Όψεω+Ες7vLPλπΰ7z­4±‘ͺΨ—O;ZΊύ j^ς}£₯’ΰ§ϋΏΦΎΈrΧpžηέ;½m›2‰lΪ€D!7Ι’$»Ÿ>°Ώdό ΌεΊu 8 BΘ‚ˆPΛζ¦η} $BY|1v.^Œ鲩 Ο‘ξΰWf;Ν]8&˜΄ΰ₯BL'9”ž3&αΝg'…E uΆπŠΓa[ΙΕm9k»—ώ€χ ·§ΐ’{QθΊπxlמ7ΐ›CΨξ>C/C“>$5TΝ|M2„όŒάq8 8Π}έM³ͺσBƒΗ‡αkpημ꼐μ‹ˆH΅yR¬ϊκΡ·΄+ Q阾VφtήIͺ&©‘ϊv±ξΌ―σIγ±Ήα힦'μχtή'³}θBQΏ’·΅/6Ua αί{γΟ‹ε–=ξgή-Φ Ϊί(–{}Σ‡κŽ΅ϋώ[Cχ όόζ/°o«,-ηͺλ―“c@"„λΰ†ŸžDΚ•«Ώ.8 BΘ’ d7λ? e—―λSFPZšU[iΦφ™,Π¨­δjΏ,θΥGi ­Μ^jB Θ-δπTίΦ}q:‚“β’Fpsaϋφd@ ,Θ§­οQθό"ϊyΰΊP©$Ό=rκ:άΑΆΧ¦ο7¦>Δ#νuηuΨΞ›YM/_ω€₯ϊbb 06―jgjVκA"Χ/°¨gΥιzHˆ«δ€;³―ΜE΄φ55‡ΪK.ˆ΅UΣΔ…―ξ°X~t¦Άμ½¦Ωη ₯©AκλΨ†Γ GνΧUjάΆηB[ω―{j}ΕξϋK[£ID€ΧΉ],­,™?²š¬€D!+BΙΈO. $BYζ‚’@hHΊy~χέeΟ¬‡€«05$ίy2[¨ύIX+«Μ…Ί%εЃ۝”‰?€3\†A—΄ι ήηε$1,QŽυ’²ιu^ZΌ½χͺͺmΕ}Χφtυ!ρ›rA­X[j8]Ό?-ά}υcύQνΨΊBMHgΘ@ήLΊτ`Wχ†zt 2Y%v©κ’«WΥ%—Υ§x+»χŠεšuΖ%Π ϊUI`}>­GοCΉςšMpψΕύί-Φ zκ>t΅‘vΆλ^†pΦ-{ον© ο>ΘwγΣwDD$Ž–ψΝςsρβz" !„¬|B"„EAΙn.6k@šε|›A™[―TΚ Χ΅5λ›ωΌ}½lit”ρͺθ²›Υ―€|8«žΫ ξ•»F”Ξ°­P‰t ΕΆœ‘]‚θΞKΧjS4²/ρ‚σ-]μτΒUΡρf_E}4«λω“Ύ-—ο²Nκ’K3σc·ΫTΉix e»”ˆέƒπΥύ=#Y₯ ω5Af;mB ‘3#ŸE ©‡ϊβkγ₯Ÿ‹ˆΘΐŠ– 70}I uWη\εΦ'¦/ύ―€ υ†φZgz]φ†βωΡύΧ°eΦγ$P¨KD2{ίZ mσηgϊβμΫΫ&œυ—m dνuΥew-p@š Jv„BV‚ΝzB"„„Άοωΰ€D!‹‚\¬€”KΨ~|gg`ŽΔK7Μ-͜W*γ9§A«7€D‘O­β0o”{μ@ϊ@ ζ\>«ΟΨ?¬-ηϊ€ύΓ9 Π91]?—΄m?L¨bμ‚ύk.ΐWάCΈVœ―Κ+ΨI³«vΦ$ƒŒ΅ύzΓΜΑ$ΈπΕΓߞ:FD$·ηΐ€†ζe’ͺYιΝjΏXξ6Ν<Ϊ³¬Φu›μέ¨jϋƒΊΞ%=΅]wϋf>η₯¦ΪΊ;pήέ—DD€EχΖ0ίU­šλuολ>λhšw&ƎώζΓβΊΞQΊΖ’žOfΜΧΝΗ£Ή dG!d%Xϋ'$BYψ„4; ]E Κ{!Iν*I W‘°―Z΅§ίΖMxπ?l!wϋβυ{vqΧFY₯]ι Α W<>”Ϊ€ς$ͺ)x_άqx0’χ₯·e(g7Η€ςσ‹ΖΣαuΩΝޚκ ŒΦ;Q ·³˜£-}»½MπnΟ’ͺΊ%‹4Κƒ)„“fVΎj|Z¬{rϊš?2ηͺlig2Wέ΅Άν―ί.ΦUšOυΊ~WΫ²4†@Υ.¬)π<ΡεΕ¦•*₯$iαonωπΏΨτ‘IO—c+5FΔ‡…Αi.6v@"„k‡ΆοΉΰ!„•€OH’2X©3o‘Oψ3ڊ¬€6Λ%Εe:š·—ΈΰB²ζΜJ1Η†―zςŸ}u>·š'ΩΉγΚ$Ð|‰}Ξξ?ΌT j8 ϊ΅•άΎιΝμ;}“&`j4pmκΊIS~iΫ€*‰π ‰B֚MŒDψ„δαš.r¨φŠήΝΩV ©)R ^QΎYΏΑa»nΛ«ΗVδρtcή=,ζ›p2-ΰ3 όϋW²l/ϋΑ΄Naρ.›Nsm…ζžι£KBp)S}  Ύ£±±HΗ0Χ“W΄Θ~G2°šcB’˜²‰^`g sDYMΏdgMOΤvήάz’ηM›""’B iŠ9¨qS/ΐKO0ςӎžNΐŽi§έ}Ή§YM-wΐφ]―λ|ΣyΧΜWUk:χΆWΡω¬Ηv-†#MυM†&Τv©αͺ7τb,Ϊ΅EŒΡΑ9ηpϋϋ*λ―H„²(nΐφ}ΡΐQΆνͺλ― Jv„BV>!-§”£.2ιΑύ 1K˜•δ€ L£6uΙsžΤιδ9Έώ`ν€’Hžuήζ…―ΒφρŝŒ¬²γ%*„κ!•ΨΖ1\Υ“κάΨξhj³w*V>Ϋͺ©œ5¨Lεڊ‡zΞρH/ΌnΣύύbJ_1ΤNΚλ.@W/kU¬ν»Ίυu±n»ρz±άο™k8[ψPΟ;²΅‹vλ!ιδ½ν†‘Ϊ*`ϋώΥΙ›Εςέ—MΓ‰­‹$"’¦zέ‰­C€ΑΒ~Η½ώΦήWΕΊϋΠ~j―;[¦ν›αͺsΑ‰BΓUη‚!„, >"Ν€’4Υα9­’ WώΜrθξ°’²BžCe ]vι΄ V$.ˆH>΄‹’Κg‘rθpΕyΛΚ‘}‚σ£Μ欈 m‘dη…«ΪvQσd:§(Aϋ“-νΈϋ‘{rvK/K}pΫAž Ν ’_κ)_Ÿ:FDk+Uΐe‡5†ςΤ΄•Cψκj+Ε©ΩΧOo€λΆι )$=D ©aιs'5Ž:Zoi»₯εΜ]ιρ!8ώP^KSsŽ~[Σϊ‡šΔπNΣ„·ώΙ}­Α”BjCΕ–Yό‘U‚!„, JvsΑ‰B»Ήΰ€΄δe/ƒΫ§_fY/ Ϋ-ΫΧ5‹’ΌxκdG―†Q¨¨€ύΒΩV²=ΊŠ?π‘Γ°ž:ςάρΨfθ₯‡(ΌμΧQ2LνL\Υ“ΉzH(9&]Πο4UΙ§/&kΐ½†sU*Σ’ΣώV»X>y’’W!5ΪχΞ•κ‹―ψblV37΄‚υΤ+κ¬Y¬o_:‰"΅6«ϊβͺ“χ œ5};Ά΄zužZπo’˜:P;Ÿ―aΛήοΧZzΜ½Ρnp_²šp@"„±¬΄οH„²(θ²›‹₯H7‹D!Χ Ÿζbi*”ΓέHμΌG„!§0‡TX±±Π\ζB‘¬Έη`κ3ζ‘μ_ΩQ±νΫX8Π-NJ&†Bplkˆ~s›H€σFδθc4€ΈΞ”Μ1e ?΅VζxφgΌovΊΘK‚€~Η±i«ήΠ€]·qA°Ξ²mŽ+΅΅?Η‰zΝΗ©ΞQ₯ΫΊΎzf,δ΅*t,ά•mcv~―XΧ¬jΏF=c›Nš:o„Άξ‰΅{§Ύ Ϋ+1ή{<Μ!4ΞτΌΣο/Πκ=Φ@=k??ω–^ίΑ½bω؞κd¨A³IK(rΫΗ(⨱ͺP²#„AΕn>Vf@z6>]δ²2_[rωrφnׁWΊΰβ§‹ΏΉΈ―ϋεmVώό’η55q]θ%OSΕΣ4€ΗΙΘώ& 3ο 'P~ΒΫwhŽχž ΰΊ2W’ά+;χ–7|j‚—Q]Ήs/Kξ«{—ίοΔ—Y£~ΰ %kΓuΫςΎΫ…ϋνξ+~¬UxŠ­θSAΡfϋ\χνΉ7–aϋ©>‘€©9~˜=ΥuΗχuίsέ7λ˜'˜Ι±–|NΛInΞ;|’λ~HyΫ\L«‹oπϊnςιF˜»ΧΡ—]ΣcΝΈ+ŽG`{’ΒΠΎ°Ϊ¬χbψϊΥ7ϋͺz-§πδχΐ6Ϋ>Φ6Oτ\ιωS{}ύ©>-Œ’²0δr¬Μ€τΌr^&&™όΩ‚{#Ο§γ1ΣJE9Έο°t/BDDδΡ3_„ϋο n‡vΌΒΟΠG—ή3 φϋΑ=pϋοS[Ώ†εΏ†ε\,έƒ΅ΈLV•!dνατΤ\p@"„Αχζci*”Γ%„k…sHs±Τ'$B„B. %;BY”μζƒ!„, HsΚ;&„B>!BΘ‚Θij˜ H„²(f%¬ α€D!‹‚\p‰BΘJ°R(˜•BB,σ‹|’/υΟ¦³&ςή{οέt–―k½ΨΤλZ“%)αθθ¨ψ³Ξp@Ίwοή½ι.,^ΧzΑλ"ˆ+~κώ¬σ DS!„,&5Μ$BYξθ2UͺH‘ψuNo„u~Δ$„Όx¬Rp΄“μΚώ½N¬ΔΊήŸ•ΥǏ7 E΅ΗUvDΈδβ Ψ°ώ{δƒͺ”ΰΔρCxψαŒnƍ·ζeΙθ¦ΥQfΞΈBu=RpΗm7έ,"L˜0ΊRl–²l½_οa]eΗΥvdE’$aδΘ !„[s©aΓ,τξΥM76%λ Β3‡ΔUvdeέ»w†P΅Ι'?Ώΐ€ΦύaάΨ‹αΪ―ΙΜH¦[Τ@d6γǍΠ œ2 5D7v˜‘:QδΉdβ‘]B+IŠ­!ςJNNΔ…ƒ{λΖ&K.j`B"ͺB›ΦΝΡΎ]sΥ£ύ’`B"γŒqvI?6aΙQUƍβ·$ΒiάΨΑU.¬αQ„?v„κηŽw‘ί3% ΅ΨΨ\4’ŸΨGHD₯QΓLτκu.TΏ“Ζ!‘1.ά ‰‰± @βQδ;f ξrοr8B"cL;¨ΚΨX²#Š(Oθέ†Ε !QψΩν6L? ΚΨX²#ŠYυΣq~ίΞ€κ?ιΘPΓΨ""―Ί!)!ΆΚΨΈμ›(bŒΎ¨/$‘:wΐ—“…ίΕγΞ―6€υ‘KΈθεͺ²QΨν6\oΕ‘KΖφυn Tαψθ³ΥšΟΙ…έnΓΈ‹ziβπƒ…«!TE'>™ˆ,-+3{Ÿλ­ΡW8|ςζsχ²£0Ψ·#κ₯%TŠΑ’R,ωjTEΡΖ§υς琈*š0²·wn¨ΒΊ}ΏΓ†wiζ”Έμ›Βiκψ~š\Ί|ŠŠJ!6>-˜‘˜ˆ*˜:Ύ―·όQΑ‚OΏ…ͺͺšΟs·o —Ψ;Ζ ΟΦΔΰŸ~P6>­—˜ˆΚΣΈϊto₯YRϋαg?@Bσy™‹(L†κŠ”ΔΚ7Γ;±dŏΰMFΎρΙ„Dd]“Gυ”ΚυΆ]‡°yΫo=ήηkœ€₯pΉdΤyšψ[τΕz””zwϋV=ΝΧ-˜κ–rssΟ~œ““SηΧiθσ4eΈ>_!I·D'IRΩΧ‰B#)1c.μͺΝΕkΟ~¬*ŠζλV\φ]λ„”››[)Ήψώ½¦―#2R‡ΦΠ­}ο=,Xδ½θ…š―ή ,™(”Ζι†ψ[₯ψ;•_„ε«·œύ»(Ώ¨.j ²¨©cΞΣΤΰσ6οΓ}GΟώ]υx4½N ͺάπŽ¨Ž¦Žξ©‰Ν—n€ΛύΗͺ:Uρh^#Y0.CžrrrjT²ΛΞΞ>ϋq^^^ΘΪETN’$\:²'$ŸζϋŸ――όBUδ3s$Λώ¦Ιͺ—Œαύ:hF?ο~ΆΆς UΏ!ι¨iɎIˆΒ­_χ–hΦ 3‹ͺ |°΄r,ͺn6Ÿ+Ɗuz²ŽIΓ{ΐ·Η³ΠIόπγξJŸS=ŠζuVŒL–μ(κ]:²§¦ΊrΝv:–_ιsͺGΡ¬₯΅βEOΦ1}l/νΌζβ PΥΚε8έ9$ v–B²r΅b‰ŽΘΜβbμ˜2Ό«fΫ•w?_§y­πy TΕ’=YC‡– Π­mCMΜ½½H›Ί[ΠζΊͺυ)ΠΉ‘šΞ!…Σ¨σ;"%>¦RﲨąOVlΌVxHφΚ}8Ι‚uz²†™cΞΣΜ mάϊ;Άν=ͺ}±ͺ7‡ΚΦ…FJvU%‘@^Gd΄™c²5₯ŽOΎϊE%.Νk…’ͺ­η˜(l²ŒΙC»hbσνΕt_ο!qQ‘e5ΘHΖπ^m4ς›Ÿλ_τBη’·ΰ5OpAΟVh’™\)ή<ŠŠΛ~Τ}½ͺh琬šLH΅¦θIˆJwΆώv$ίόΈGχυ‘ %σ›5F»ΠfΙ·ΏβΨ©"έΧλί²ζ… E%I’pΥθšύΏή^’§YΑTNxν~vLHd©IqΣ―­&ΦήZ²Ροχ¨:±iΕΘdB’¨Τ«C΄iœιUΎυΕO~ΏGo„Δ|DΑ6uHgΔΪδJ±v"ΏKΎίξχ{8B"²°+GuΧlBχν¦ύΨyΰ„ίοͺΠ|GHl3/Ζζ»Λ7Αερό-UU5ίcΕΘdB’¨“ηΐ€ηjξl}±’ΰηnx&$ ’­ »uMœ½VMl EΥ|;KLHu& κˆδX{₯Ga‰ ―ήZυ7κξŠR΄Ίκ’ξšΛΫq›χθά{TˆΨdB’¨sεπ€ΟΒ…χWnA‘Ξ½Gy—Φ²dG‘cΗeƒ;jbμΥ%ώη5ΛιΕ¦#“ ‰’J‡¦™θΫ1 ψ\τU,f(§zTΝχ±dGΑrρ€HŽ©cΕN7ήύzKίε%‘‰MΩ‚)‰ ‰’Κμέ4΅φΝϋŽaέφƒΥ~―~>¨Ν£(6η’ξšψϊ`Υœ)vVϋ½ͺNlςyHD&cΗ•C:kJ―|ρS@O}U=zef$ͺ»ŽΝ2Ρ«MCΨό9 οͺΠ–“-›LH5&υoΔ{₯ε±ΕN7ή  $Bh—Φr„DΑ0Gg©χ–ύΗ±vGυ#wΐΟ²o Ζ&Eλ/κ‘©³°z (‰€κΡΦι9‡Du•ηΐeύ;hbλΕ₯άύ9$+Žή™(*τh™…ξΝ35w³Ώΰg³J=ϊ{Ω₯yΕ¦.’|nC(rΊρφκ_ώz;5p‰Θ€ή]ΣƒάΈη(6μ:πΟΠ]ΙΔՁ$Χι’‰«wΏΩπΘT²#²†Œ€8LξΣV3ιϋβςΐ&ŒΛEΚ½dύΫ5Fη&υ4qυWΪDVΕ;B²ώό&EΌ«uB¬$UZ›_μΔ‚όoV©Gxt–Φr„Duπ§!]51΅fΧaό¨χTΨ*θ.ϋΆ^Ŏ ‰"›]–qνΰΞ@εk―Ϊ‚"§»F?Λ»΄Άς瘎¨Ά§'β’μ6š˜ϊοςšŽ@¨Πό+–“™(’ιήMΣ’*•3„^X±ΉΖ?K(:χzXπ’'sΈfPη²DώρΉ£ΕψxύΞ,έrr΄rssΟ~œ““Πk«{Q0ε\ΨMΣs\όσ^μ>–_γŸ₯*ΪΥFΌΓŽ9ƒ΄#χWώg™π'κGHΉΉΉ•’‹οί«z-Q8toZη·j€Y}”ϋUυϋΦιρΦιΉ¨κnZοvH‹­OnEΕ‹«j>rτWΩEέ)LFd”?ξͺι5n9t+·¨ΥΟ "’JΖ’$`ސξšXϊ`ύN)(ΥΟTuζ7­›a™CͺIi/;;ϋμΗyyy!kEΆΖ©‰˜–έπ©~<»ςη€ο~χεν…Vώœυ.y2Ϊ°ΝΠΆ^š&–ζΧrδ”-Έρωy2GHϊ-νLBΤ2Pi―E₯x{]Ν–zW$aNuwΛνΎu«wďŽΥϊgͺͺ66­š\eG'9.Χτλδ-±Uπίo6£Δν©υΟU%Ώξ«ΣΟΥ‹Ν¨Ϊ\5''' Ή‘@_G ±vn }Dω[ΆαXaI~6οC’ΊhY/wn₯‰‘gΎώjm'6Λθ?ΙzκT²«* ς:’`»Ό{;4HH¨4Α+πτͺΐwυφ‡‹¨.nθί²Z9bŽ•ΰwΤωg«:‹Έμ›Θ@6YΊy{‹,ά²;ΧόFX_BšŸmΕ²…_VR<κy&~ώύνΟušΧ,§(ΪΨ΄b9™ ‰"ΖΕ[£MZͺ¦§ψ―Uƒςσ9B’ښ۷+μB?….7^\ϋKP~Ύή²o+v–˜("Hώ_{³αŠ=ΏcΓΑΪ/§­Hw{ ^τ^iq±ΈΎWMμΌ΄n ςK]AyݍY²#2Ζ¨v-Π©~=o¨ΰ‰ΥΑεe‘ΚŸγ’ͺΞυ½» VΆUЧ’`ώ5{WUTmlZ°³Δ„D–'ΈkΐyJε pνοG°jοοA{Uζ=X΄£ͺ€ΔΖΰ†σΊiβζ΅Όm8RX»m‚τθΖ¦οCbB"Λ»¨ust­Ÿ©)Y<ών³j‘W±βZ ŸΉ½Ί!Ρξ¨7nUΕ“?oδ”ν"Β‘±$χ θ£ιnσΊτΤ”'>ή»??χŽ”νY(4ϊ7hŒ‘›kb䩟ςpΪε ι{ U›LHD!vg·Ύp{₯Ρ‘[UρPޚΏ·ξ‰ ‰ΰ½αΑσiβγχβBΌπkπφ¬σGα’’πκ˜–‰ι­;Αχᚯlߌ½gκώΌ£κ(* iMku.:₯ΥΧΔζ?~\ƒR₯ξΟ;ͺŽ*DDΔ&Y‚ΰ‘žƒ5½ΐ· Ϊ΄.,mΠ]ZkΑ‹ž‚+Ρξΐϋibcσ©cX°gkXΪ ΏμΫz;-Φ)!εζζžύ8Η”ηζζςqζT+γ›ΆCοz΅—Ψ΄'%aiƒξ²o –E(Έζuμ…¬ΨDMlά·ρ¨ΎC¦Ρ[φmE΅NHΎΙ₯ΊdS1yΥDΌΝŽ{ΊœΥ§Έ·π4^ήϊϊ|9U…¦ !E·VIιΈΝyšΈXτϋN|wτ@ΨΪ!τbΣ‚#€°΄˜##ͺ‹ΪχF£ΨUͺtάϋΣ*ΈU₯ϊ$Š Mx'Rτ’<Πu0d!WŠ §GΕƒ›V‡΅-ŠΠ‰M ŽήC>‡TΣd”}φγΌήΩLΦΠ*)sΫh7P]qt/Ύ:Ό'¬mΡ[e'[π’§ΰΡ°5eΆΠΔζsΫ7`QθΩT€7‡dΕΨ Λ’†ŠεΊκ“•“ί–ε(Qά†΄IoɊΗT{vIΖCFJε2r©κΑύ[–‡l§ωκ¨:sHP­ΧYͺSΙΞ_ͺιη‰*\―&4θ€Ή―bρΡmXq|·1Bω^v|R4ϋSσ>87±&6Ÿήυ=φ—„w!CEB'6­8ΏΙΘTRνqx°νHΝ='ώoϋW΅ΚKQτξυ°ήEO΅Σ61σš„ο[‹Žα₯ύαΩ-ΔEšΨ„j½ΨdB"SΉ£Υ…ΘŠIΦμ φθξ•8ζ ΝΣ6₯·_οCŠvIΖ“Ζ’r©N…ΐ]ΫΓcπ6 zϋ,FYɎ(˜†d΄Ε%Yέ ϊ\Ϋߝދ‡~2¦Q¨Bβ²ο(•ΣlΪΔgibσ…k°ωΜacU’joIΰ²o’ZΚp$ΰα6c5U±βΒ=;6Y\‘’jουΰ’†ΘΧ5Ή1ζ4 ω·ίSrΩήόΡ»Ɋεd&$2œΰαVγ‘$Εkκσνω ΖMW€»—χ £ΐ%Ψbπh« €"£βΰHwξψ ₯jψο9£ mlΒ‚±Ι„D†»¬A/τOi£)‡|“Ώ ކφΡΟ5‘@Υ,₯΅^/”wW³‹Π$¦ž&6Ÿ?Έ› Σ(ͺ^l2!ΥL‡„†ΈΉ‰v –ΣžbάΏη3S”κΚιΥι­X‘ΐŒΚθŒqέ5Ιθ—’ƒxξΰ*cε‡ΠΩΦʊεd&$2L’-6Ÿ ›j‡ο₯Ώνύ ΗέΖͺσ₯W²³b/”ͺΧ"Άξ?gΌζί»Tuγ=ΎͺΞ—ώ²oλΕ&Bpο9γpNL†f‰χ‚γλ±2›!νͺŠwΩwεΟY―JΥ‰“ψg‹KαμšοΗ,Α~ηIcVmlZbZf_ Mι¬)‡μ,=‚'-3¦QΥπ.ϋΆ~žό“ αξ&γΠ##—πΰΞο’HuΣ°¨Bhc“ ‰ΘΏ895šd$jz ž^ƒe›ŒiX€τοCβ)4v€γ‘FΣ΅£ ω;Jί‘*z±ι»Ωͺ0!QXȐq_ƒ©hΣͺOΥcSι>Μ?ΎΨ˜†Υ€"Υ·ΧiΑ^(U–(ΗαρΖ³/ΕkbsaώZ|^`ž{αόQ±Ι„Daq]½‘θ—p.Ÿ ώ„§χznί™*„¦ύ\ggmvΙ†NGc{¦ζίvSι>q½tcσϋxζΤΛ“Œ€?ζ*Ε¦d½„TλRNNN­–}WχZ2ΏD99ιΧ CL;έIβm˜ςy”ˆ’°·-”TŠδ“€,xΡG²†φ˜—q=²l™Ί±ωuρ·x#]ΛΟωR!4±)|cΥκT²σ—X*~žΙ'²4q4Ɵ3G=[τϊ—?–ώ„O½·…WΣω£@ΐγs‘«Όθ#UχΈnΈ&ύ*8$‡nl.:³ŸžYdιΥtώ@›œC’HΦ/‘.O›²ξuΡJΌŸ~Δυ>Λ©<š₯΅Φ»θ# γRΖaDE ‰M*ή=ύV―γΒD…ΠΔ¦bΑΡ;U+VŠΓΤ΄Λp^BoοsW|Ύ. πQώ¬*\‘½ΟrŠ€ν…Z±,Im˜•1-bZιv’JE)^9ωΆ–n {ΫΒIΥ½3!Q„iΣ33A†½žnMΎD-Ζk'_ΔΆΏΰ?uzސ “Π Sg VŠΥΝcž£xωΔ³8βΆΦF©΅‘›V,'3!‘.»δΐˆ”qΈ Ω[Ρ›:β>ˆWO<‹ž£αmœAnXΏjuIr2&¦_Ž.ρή›θυbskι&Όsςe”¨ΕαmœA„ΠΖ¦bΑΞi΄Œm‡‹3@={–n~,ώŸœ|.α kی€»¨ΛΎΓF‚„ ½1!}:bε8έΨXžΏ+ >θς±/½’ηΘmΙ™:έϋ€nΔ-\ψμΤ[ψ±θ»¨Ίΰ²gΞψ& ^τVTίΡγg El;ϊ±Y œΖϋ'^ΐ^ηφπ6Ξτχ²³^l2!l’½“‡apκΨ%‡n Ίφβγγ/ΰ„ηHXΫg άΎ½PސB*NNΐΰΤ θ•<€~y~-ή€E'_CIlQUΠΖ¦;KLHQL‚„Ž ½1$} ’mιτ{žͺPπMΑB|_°jμϋU[*΄ Ȋ½P+°KτLΊRΗ!NNЍK(Q‹°μΤ[Ψ\΄ˆ²{Eͺ&6­XNfBŠB$΄MΘΖ€΄‰Θt4ΰΏηyΨ΅KŽΏ‚cξαk I鍐Έ¨!Έμ’]“.@ΏΤqH°%π›ΫŠΧaωΙ·P€δ‡―&₯‚#$²Y²‘}B_τJ Gcώ/v§Z‚οNΏŸΟ¬„ˆΠ]kJΠYΙd½^¨ΕΘqθ’t!ΞKx9 €Ψ,πNJ“o`OΙOαk Ι ΨτXpτΞ„βlΙθœ<έR†#ή–@{7ϋΆœY…οO}€₯ \M΄€Gφ)ΩΙΦ»θΝ$Υ‘…)ΓΡ9ωBΨ$ο―#±ι.l8ύ6ζ/GΈΒΧH šΨT,›LHKB£Έφθ”:­{ŸύlU—ρ’ΝXsβ]œpν}σ,H€Λ§,β»Τ–ͺ'Kv4OθŽsSγœψ.Όs—ώg'ΆŸYu§>D‘ηd˜Zi-j„Δ&R„I‹iŒΦIΠ&yμή… Υmsz¬tςN~ˆƒ%›Cί@ S%·δϋ9λ]τƐΧ­“ URΔΘ‰ͺΝ}E°ρδ‡8εβfUThcΣΓ9$ ? ι±ΝΠ,©š%υF²£αΩ―TwΛκρΨ|ς,ή„h^‘(Nί^(ηό’$²βΪαœΔžhšΤλlI ΊΨΨ_ΈΏœϊ§œ­BšΨt3!Q8ΔΪ’Q?Ύ#&vE£Δξˆ-›ͺ.Ιy ,ΜΓφӟγxIτέ@Xϊ%;λ]τ‘”θΘDVB4LθŠ†‰]a“bΞ~­ΊΨT„ϋ VcϋιΕ8γŠόύη‚I@ΥΔ¦[Ά^g‰ Ιτ$$8κ## 2Ϊ#3Ύ’bWzE Σ».₯Ώ¬ΒžΣΛQ쎎½η‚MΰςΉΖ­Έ΄6X$ΘHŽm‚τΈ6¨—Π™ρη"lT7/τ‡bχQμΛ_‰}ω+αR"η Γα€›ΎΛΐ­€ ΙDdɎ„˜†HŽm†δΨζH‰k”ΨpΨ+½.πέγN‚ίσWαΘ™uP#π‘yα€[²³ΰE_69I1MΫτΨŒk Yͺό+$ΠΨT…G 7ΰ@™γsώ IDATώΧ8Y΄9κΆ‘ 6!΄±ι²`g‰ )¬$Ψm ˆ³ΧC¬#ρŽϊˆsd!!Άb#ΞQ_σ*j’€ΌΞ”ξΖΡ‚οq΄ΰ{8=§‚rOHV¬Σλ‘ ΑaOA¬½βYˆ‹©xGΔΕd!!¦1b+Œ|ΚΥ΄{# βTΡ/8ZπŽŸYO”μΔB'6™όΘΝΝ=ϋqd<\‚,Ω!Λ±°Ιq°Ι °Ιρ°Ϋa³%ΒnK„έ– »œ‡= {*bμιpΨR Λ±Ί?Q(©ek„Pp¦dNΩ€“…λΰrŸ¨υω§@hώ|oF4žYvΐ&ΕΑf‹‡,ΗΑ.'xγRNπΖ₯- v[2bΚbΣaKEŒ#πσ’’φ±©¨₯((ڌ“…λqΊp# Έ{Χΐ}Ψ—Γχ3-‘ δΨ@ύΙAό©LHΦ!@8₯P€Z(Εή„’œ<…ή?•ΐSxNžS€Rφ(-JΈOηά$ŸgtkόorQή?•’²ψ,”ςΈ,¬—ωήΨd©Νڎ½ού3¨I)Ό"3!°y·°‡¨8CT6±|v‚Y-ϋΈόO₯,Ρ”nοΕ-<€κςφU§χO–Π’“πžRR].α³ΠA-ϋ\ω¬QΕΨτTŽΛ³qκς‰Ν²ƒ ‰^Ηήœϋ[j~ˆκC5ό+"CžrrrΒΏμ»`Mθίƒ’—π§Wέ "}knA­…e„χQ(YοV^""ŠHLHDDd LHDDd LHDDd Ÿτv~ 2Ζ&™•Q±ρ ‰‚g̘1F7Θ/Ζ§υ1!‘)0!‘)0!‘)H0ΡNŸœδ%"ŠLμΨcͺ„DDDΡ‹%;""2&$""2&$""2…Θ|@ώg0Eˆάά\Νωςw.yŽ«VΣσΖσY5žΟΰσ½ήΝp.E€ΉΉΉUώ‡σVέΉ+;Οqυη²&ηη“ηӈsZρΌ˜α\²dGg±<<—ΑΕσ\z•3ˆΨ’‘Y˜υβ·’ςΟgν™99B" !3_όV”““ƒœœήD_GΉΉΉgΟ‘™Ξ%GHD!ΒdDfδ»XΑL1ΚQ˜νB·:3υβ)t"vλ .ω¬9.« ½_ εηˆη³vŸΑgΆeί›ˆˆΘZX²#""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S0UBβCΈˆˆ’—©E/&$""2…°%$–γˆˆ¨*aIHLFDDT°$€œœœpΌ Y˜έθψ3fΜُ-Zd`Kˆˆ(œL—˜„ˆˆ’WΩ‘)0!‘)„u•WΫ‘?a™Cβ*;"’π9bŽ;†7έ”aɎ¨n›7OσŸF7ƒΘ―ΆmΫβ£?@ΫΆmnJ1!¨aΓ†Έmnκ₯§έ"Ώή~ιδΈ²l½_οΦk1‘A-xžoΏ‚Νf3Ί)DΊώό§9HΜϋS§,§LHDΘ™3iΏνBιΎ]°[πB§ΘΧ AΜ›~ ίz’P™ˆ"QZZnΊrNΝ‚ ‰Liα;oC}ηH6`B"ŠL/=ω„χB·Ϋ –ΌΠ)²ύeލΘ:ϊŠ6εyγΒ’qjΊ­ƒˆΜ$gΞt³ypfσΘvlvλ]θΉš4i‚ΉΣ§ατΧ@Ά{ΗV-Ω1!ωΡΈqc<ςΐί±{ϊpΰμΕ-`·ρ²!σxα‰Ηα|ε© 1κeΕUvΌ²ˆόψτέwpτ[ ϋ\θVμyRdΊ}ή<΄;}E»·WŠSIX³dg½Jwήr3RΆ¬CΙξmlΆ?²— ―KηΞΈρΚι(^ψVε΅Ω,›L7BΚΞΞ>ϋq^^ž-‘hΥ₯sgΜq!Jσ€wΕR%,Ω‘9¬ψb)Žί=G'FαλdBͺ;&!2ΪΟώβΕ”­Vς!I–ΌΠ)²ΌόŸ£`Α+πœ>©§VMH¬=UπϊσΟ!eνr¨…dYsXu9-EŽρcΗ`Π9υQ²j±nŒJ²lΩ’Q™Y3gΰΒ6ΝQ²v%$»MΈμ› “€Ηz'\oύΗŒΪm$²#²¬† βιΗΓΑ›// ωc͞'E†_ _…ZM²±jɎ ‰ΐ²?Δ‰Ηο„ΐElΕ ¬ο‘ΏέΆ§ΐy`Oυq*¬§LHυžύΧ?‘’·₯ΗW{‘K’YΆή…NΦ6zΤHL;Ώ7\―?P§Ι»ΤzqΚ„DQνŠΛ/Γπζ αόμMH&+φ<ΙΊ222πΪσΕΙϋ 8F%!ΰΠ[%jrΦk1Qthί½fΔ[σμu‚ΛΎ)μ6mXΒg ½ύ&Τ/?„ηΔ‘'$›KΛLH•Ύυ:Φ,…KqCͺIOR†fo;’PΈυ¦yΘNrΐ΅ϊ»šΕ(H!YΒγό]=§αή³­š%ήZ!Q8 |nΈτ(oΝ―qŒ({€υ~½[―ΕDupετι˜Τ½Δ—} b\I`γ’"sψλ­·`RΗ–°ύτmp/t™›«RptλΪ/?ύ$ΔϋΟ5FΛq„Dd9Χ^‹k‡ž{ήΧΑ+”‘$©v7*UΠ¬YS,zοΰƒηƒRJ֐$nDd΄+§_ŽΫ.€ΨuΛCrΏ [p9-™Gjj ΎYΌςG/†τž6+––™(bάuϋmΈnΒ(Δ|·ΈΦ7ΎVG’e–μ¨Φl6ςV}˜Ο_YŒ°μ\'E„IOΔε}»#φ‡/‚^¦«H’d²£ZΫΊaΏ|7€1 ”έ‡Δ9$’π›rΙΕxό/7"nέ—!™D–X²£³ΩlΨ΅ι'Δ}ρvθcΈuQψMŸv)žχgΔ­C2IΝ$4E¬ΤΤlίΌ ψψ₯πΔ(HœC" «9W_»―žψΌ!/TΔ’ͺI“&Ψ°z€… kŒJ-9’gB"KΊuލΈaβhΔoώ.|½Ξ2BH’!DXί—¬₯k—.XψΦk—†©LWΰ±Dαρ―G©½Ί"vΗΖ°φ:Λ U…ΝfƒΗγ ϋ{“5 2ύΗƒHό~‰!1*q•Qθ-xσ ΘJ‚cΟζ°χ:Λ1!QUf]1χ] ’7­6,FΉμ;Hψψ ςη‡―W’΅σ€ίww‘PΛ‘―Gψ;f „ψmλ Q&€ a"_iiiψξ«εhxpΤ“G )T$i½ϋϊkΤ, Ž?£QΠ Π―ζώΫΧCΈœΑΫ΅»GHδcΥ²₯θ(—B:°ΓΨ‘QοŽ"\eG4σζώ7͚‰”-?@&θuz©ͺ`B"ή•tΌυκφ+”ό“¦‰QaΡI2!‘)½ωΏαŽ­·ϋ'Sτ8+Β’½O 9³―Ζ_oΘAΚ―?@L1z/ΗUvDAΠ¨Q#|ρΩ§8§δ$ΔoΫΝ—Œΐ9$^ω% νΦq»~4eŒBfB"ͺ“Λ¦MΓΓwί…τƒΫ‘²9ΓS–μ’U«V­πΡ»ο ©ϋ °›ic°YrWz³žMŠ2/<›‹Q½Ί#qΟΟ0WωΓ5D§ΩW_…»oΊυŽμ…ZRdΞ‘Q9‹nΜ„D†κέ»7ήyύ5€Ÿ> ήkšIαͺp„]dYΖ‚·ήDΏv-Ώ+TΐόqjΡΗ€0!‘aώρΠCΈbΚ%HόνW—Λά=Ξ ˜’Η”Ι“πμΏη#fί6¨Gφ[&F%™«μˆ»woδ>ω/΄L°ΓΆηS-ιRΔ‹‰‰Α«/½ˆΑΩέ`ι;kŒŠ*’dΨ¬Τή2LHVσŸzγ‡FzώQ¨'KBϋηΰ²οHvΥ¬YΈϋΆΏ ΛYυπkΖ¨lγ‘?Σ/ΏO=ρO8ŽŽύζνqJΦ»`p„‘:uκ„?υ$:4¨‡ψ#{¬£’dΙN…T§Nπδ㏑kΛfˆΩρ3 Tΐ‚Οi©HΦ|'ι³Ωl˜ΤS˜0z$RσB9uΔς1κέ:Θz1jν³N¦•‘‘G|]0™ξ"('~d μ?ΦΖRΔΈaξ\άrγ\dxJ€C» €K]² I–ŒnE™.!ρρΦ&Λ2~πAΜΌl’KNCœ>β½Θ-Ψ[σG€ ΙκfȁΫoΉ γμˆ9ώ›χ“£ήeίΦϋ1]Bb²&Y–qην·cnΞυHtGχyWΟYΌτα’5Mž<wο=¨Ÿ‹Έ‚“g\‘£²sH}π—›oΖ•3¦£ž μσ~Α‚½³š°βΕΝ.:7έ0-ΤGbi!ΤΣΦ»έ ?AΡ€eΛ–ΈύΆ[1ωβ‹S\δχ~!{›:8B2?Y–qγ 7`Ξ5³Q/. b¨…§Ό«η"=N% EΊ‘C‡bΞ5³ΡχΌž¨η‘-«ΏGΣ/h²3΅vνΪαΪΩWγΚ+―„£΄φ’|ˆβΣο‘T’Iβ’Š@™™™ΈjΦ,ΜΌόr€'Ε£ž]†§θ ΤRDlΙ£:LHζ3eΚ\1}::΅o‡¬ΔX¨Η~€ˆ.ΝU…%;Š('NΔεΣ¦αόώύ ©°•CΈKαρ ΊFDΎ$&$³θέ»7fNΏ“§L…μ,F”’B¨…ΞθŽQxχ\”eͺͺέ”€1!Q%#GŽΔΤ)S0xΰ@ΔΫ$€8lPŠσDoOS’qΊwοŽργΗc쨑h˜Yυ⠜9q·ΤEωcR˜Θ2RRR0lΨ0Œ3C‡ A¬MBJŒξ3ω€ (ND}OS˚Oγ΄²AƒaΤΘ‘˜H’šQ wοή8όΠΏ?:uμˆ„ΨX€'%AR=Pœ₯Oi)GB’8B ΆΨΨXL:ϊχGί>}Y―bμˆ•%xJJxJŠΌ/ζΉHω’•0!EΈαΓ‡γν7ή@¬ Hͺ€β,¨e²ή^sKvAχ‹/`ά¨‘pΐγ*…Pΐν‚`9–¬ψ I&€Χ±cGΔΪmP‹Λz—VέNίLΈΚ.θš5k»ά₯e%Ζi1!ω‘››{φ㜜œpΌ%•±Ωl€$Cp$D!›Νfƒ`œ’ŽάάάJIΘχοZ6› ’Μ =¨8‡t6»‚qT*™,Λή¬·ˆYILHAg“eΖiq„Λ{εj:’ϊί‹/£m›ΆΑj’₯Σ¬)^σM<σΜΣF7%bά}Ο=ΈϋΞΏβΪY³nJΔΘΚj€oΉkΦ¬1Ί)cωW_αγΒιtΦκϋwμ܁YΧ\δVUΝt )εΌ-Z‘]ϋsƒΠλKHŠΗ±γΗπλΦ­F7%b¨ͺІ#-5Γθ¦D;°{ΟΖiΩl6΄jέŠR»ά%Θ-ͺžιR0HϊŸ%AQΒX‘Μγρή•Ι ‚qdŠ’Βn—!Ό›~՘ρΉ Iπ—¨*/τ`σx<‚1lŒΣΰRU₯NΏ #2!εδδ„}Ω·ͺ΅₯FU0!›$I°;μ€δu9„*³ILHΑ&Iή§ΖΊΤڝW#φd Λ)άΛΌΩ{ύƒͺςBΆnΈG :wΤ4$&¦ 66ΞRT΅vεbœΫμΩWγφΫο@σζ-‡ΔΔdΈ\*<ξΐΞ³ΏC#²d§ c²»©ͺ°ΤφσVPZZŠY³4kΦ }ϊτΑθΡc0xπ`H’ ρ)ͺ —ΛSΝO’ЧΑ΅dΙ,Y² 0`† ŽΑƒ/@f½,$Δ'ΑξˆGi‰ΐθ[EdB PΛQjΔ,Ω…Τώύϋ±~,X°ΠΉsg 2£FΒΉηvDlL"$ΔΐYΚδT !…Jqq1–-[†eΛ–5j„ .ΈγΗOΐˆ#ΰ,q#&& %Εnˆ IHπΟ‘ IΖdw3β’†πΪΌy36oތgžy™™™?~<.½τ2΄mΫ±Ž$HR œNώ{hH\ .‡Β;οΌƒwήy0zτhLœ8ƒ^€ΨΈ$$Δ§’ΈΘΝR0tκΤ ρ‰ρ,Ω•αΙ8ǏΗΛ/ΏŒ—_~©©©˜όsΐ¨Q£0cΖL 6-[7BJJ ΒΦ–ˆKHN§͚e )ѷǏS†Ϋ½ΩI^θfŸŸ—^z/½τ"Ϊ΅k‡«Ί “'OΓ–›=₯ΕΡ]“$™qj‹/ΖβΕ‹‹Ω³g£[·nψζ›oΒφώ—vξά‰Φ­[!)) W]ufΜΈ™ΰ°§ $ /zސΜgϋφνΈλ»pΧ]waάΈq˜=ϋZtιά ±1ι(:}1 pΙlœN§ξ6n‘±),,ΔόωσΡ§O/Μ½ρZό²υ{Δ'9‘˜svŽ)&$sϋτΣO1qβxŒ7KΎX€” ’eΓγ&ά$‰«μ(ςFHzΚ‡‘=zτΐ_οΊ=zœ‡-…QΠeB²†_ύΧ\3iiiΈiήΝΈόςˆ‰ICQAtάOΗ9$"x„€gγƍ˜2uFŽŠ₯_Ύ{l>’μPΚvvˆΔCήίa%§OŸΖίώο~΄οΠΟ>χ8’ΣK˜b|…ϊ`B"ΐ„ );;ϋμ*;wξĜ9Χ`ό„‘X³v ’SKoƒPEΔΰΙ’„xτΡ Y³&xνυ\dd–")Y6<žBuΘLH&€ΌΌΌ³G¨mΫΆ Σ.›ŠΙSΖcΟΎuˆO,Ν.CU1‡'‹­ξΎϋξA»φmπΑG/!&ώ4ν†ΗU°IfB"&$#lΨ°#.†›n™ƒ’’έHJQ‘(ˆˆ‚ )γΆΫώ‚±γF`γΟ_"%Ν ›M6<Ύ‚u°dGR%Ÿώ9ϊτν‰ηž ›Έ‘œl‡Paι`BŠ$»vνΒΤ©“3χ*Δ$œ@jš0<Ζ‚qH\eG`Bυď£{ΞψuΗ*$§—B@@Q­y€>‹HK—.E‡mπΏΧŸBRZ>m†ΗZ]Y捱ĄδΧργΗ1qβ8άvϋυHH9”TΒ»FΐZŸ3ΡzθAŒu!vνϋ©nγγ­–Kv0!UλΣO’sηφXωΝ€Χ+…ͺΐR|ϋφνΓψρ£ρτ3Η9-Œϊg Ϋ$Γc‹(PLH΅0ρβqΘ}φAd4(€l“ ŸzQR4zτ±G0}ΖD$₯FJšΉcT|ͺ1y1!ΥsΟεbςδQHJ;Œ”TΙπΌΏƒ>‹^?ύτΊwο„m;V 3Λcx,VuˆŠ*₯¨Ε„TΫΆmC°ΐwΘj€Ύc²ήΑE 4ν²Ιxυυ'Π¨i±αρ¨w°\G^LHApΙ€ρxηέωhtŽωVαsHΰ‰'ώ‰kη\Žf­ +—•FG*ΐ'ηΐ„4?ςnΉe6š4Ο‡,ͺ*LqH`ɎΌΎϋξ[tξάͺmRΜ£B(!˜‚jΩ—_`ΔEQΏΡQ$&™c“V²£Š<ϋW «‘bx|ͺ*oM ?˜.!…γρ‘tθΠ!τΘξ·Ψ‚΄ aψΕ‰;i]>}*–}υš4s£Bp‰ΌLχΔΨp‰“ͺσΕK0sζ΄ιPhLΙ€’0!‘ Kv‘θρώΗOǟζάƒ½»βΓϊή\Τ@ΨΌy.ΊhΎωf6¬‰ λ{ •sHδΕ„&―Ός"Μ)ΐ]w>†m[ΒW―磑)P‡FηΞm°oίA¬Xζ}”E8Αgv‘R½ώ{p:KρΐߍΝ?…§Κ9$ͺ §Σ‰† λaίήƒXύ΅–€Δ’•cB ³O?]·[Α_οz{w₯„όύΈ‹2ΥFσρΛ/»·61δI‰ΛΎ©’–,Y„sΟν€ΣoΔΟC;RβƒΟ¨Ά:ujŸ~ڎΌ΅Ι!}‘ ¨!ΈΚΞ0O>ω|θ/θΤΝGCs•ΥEϞΡ»IHc ¨ŒQβΙP}τ>’““qν5χcλ/±!y.j Ίπx<θίΏΦΩ„U+!y!£δΕ’Α^{νΌ·ΰi΄nλ Ν}H|44ΥQ~~>.9ƒ†¨!Ϊ:ˆ‹Θ‹ ΙζΟ_­x-Ϋx‚ώ4NΞ!Q0μέ»3f\Œ ‡ι³\φM^LH&ρΰCχcΛ―Ÿ‘i³`˜(8Φ­[‹ΫοΘΑ€AΑ!q•LH¦rΣMΧ£°x ²ΚPTε`Ɏ‚ιγ?Δ‹/>Žέ• Ε¨ΰ3»¨Œι5T|μD€μό]—]v1Ύός;΅Ζ©SuΏHyΫόωOβœsš’{·iΨςKp~&&LHј„| Φ?ΌΛ–Φ}ί;–μ(ξΈγΌχ^k4kΦ{χΦ=Ύc”X²3­‰G`ΰ ’:—Cο^aDΑ6uκ4kq ©iΆΊ•μοC"/&$“Ϊ½{7ξ½οΟθΫΟοΜj~ε…F―^]0~B„¨]Œ–Η§‡#$’©-]Ίό,ΊtUkΥσTΉG…Α A}1i²£‹8‡D^LH&χ―=†ί~[-δ/§xΓ!…ήΦ­[qο½Α°a5Ρς8UY²#0!YΒuΧ]‰ΜΜέHK·Υθ†CUސ(,ή~ϋ ¬Y³=²εZάΛ’y1!YΔ”)£Ρ―oQ-ξο`B’πΈρΖ됖vηœS³ςΐ’y1!YDAAξΈγ: μͺ  C¨\aGαuώ€σ0j€€€γΕclΓΙ˜,dως/±ό«7έS‚"Pν!$Ξ!Qψ7£G{ŠQE$x8B"0!YΞƒά‹”δ=hΦΜPmžχwPΈεεεα΅Χώ…Ύ½ΥηGHΔ„dIΓ‡ ΔuΧf•mJYΕκ%>‰“ ‘ϋŸgpμθj΄iν`•η:Ι‹ Ι’† Š+. ΰώ^θdŒk―ŽNΐξ¨z³`€sHδΕ„dQ+Wΐ‡½Œ~}ό/³eB"£Ν{%ƍrWY²˜Θ‹ ΙΒώv_λΨ†&MτGJœC"£mΨ°Ÿώ*.Όΐξ„$y•NΔ„dqW͚Œ ϊϊ­Νs‰ŒφπCC“ΗΡͺEL;аγD&LHΩΩΩgͺή©S§πΜΣ‡ΡΓββm|π™Β…Ÿ`ϋΦUθ{^,ƒη44τ*#F‰¨’‘™ι‚ε%#žΣΊ3[lr„DBf»ΰ#AωΣx#}pδζζšfΏQސˆB„ΙˆΜΞw±‚ΡρΚQ˜αβŽ4Fχή)τ"jλ .ρ¬=.§ .½_žεη‰η΄φ§‘c†eί•ˆˆΘΊX²#""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""S`B"""Sψ,B²WyˌIENDB`‚pyqtgraph-pyqtgraph-0.12.4/doc/source/images/example_imageitem_transform.png000066400000000000000000000052071421045507400274270ustar00rootroot00000000000000‰PNG  IHDR€α§Ž pHYsΔΔ•+ 9IDATxœνέMnΫX@QΊαAm₯*3mΓ;α‚ή*jκm(³ZKΝάƒ†TŠαjK$#]‡η¨ ?θ‡7z$¬§išή&x°kw @άεq7ι£ψœΆ?[²!€}ΉυΈS,Σπ³,>‡ty…¬΅ψ<Ο.ϋ`3«.j!Άβ75 H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ Οk~xŒqޞηyυ0μΧ⠍1~ˆΠϋϋp‹ΕKvβΐ–V-ٝ\σιθεεεΌύϊϊΊΕnˆΊ<ζ_λ¦ Ξ-Yͺ!€ύΈ<ζ_§›‚τ><Ξ°•ΕηΔ€-mvΩχ4ΉΠ€εI|Ψ’ίΤ@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@Β&_a~ίΏŸžžž=z{{{τlΜ{”5|B AH$ €A AH$ €A AH$ €A auΖ[ΜΐΞ­ ’°•UAšηy«9ΨΉ»}Aίαp8oΗ{ν€Ή<ξ_γ¦ –θ–|2!€}ΉυΈS,Ρπ³Έμ€„M²s΅k­Ί¨Α[±d@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ Οk~xŒqޞηyυ0μΧ⠍1~ˆΠϋϋp Kv$¬Z²»Εεςή‰OTΏ–ŽυΧΊ[ΔΰΧχΡ±ώΪHY² AHXΌd7Ο³ΛΎΨΜͺsH"ΐV,ِ H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ «ƒ4ΖΨbvnUΔ€­¬ <Ο[ΝΐΞ=ίkG‡ΓαΌ}<ο΅[δςΈ»I„φεΦγΎ«μH$6ΉΚΞΥv¬΅κ’«μ؊%; €A AH$ €A AH$ €A AHΈΫτΑ{OOO½½½=z6t8¦οίΏίm>! H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$¬ϊϊ‰1Ζy{žηΥΓ°_‹ƒ4Ζψ!Bοοΐ-/Ω‰[Ϊδc―ωtτςςrή~}}έb·D]σ―΅:HΧ.Υ‰ΐ~\σ―Σͺ«μœ7`+‹ƒ$Fli³ΛΎ§Ι…,·8Hβΐ–ό¦ €A AH$ €A AH$ €A AH$ €A AH$ €A AH$ €A AH$ €A αy͏1ΞΫσ<―€ύZ€1Ζzn±xΙN|Ψͺ%»iϊgΩξ³@‡σφρx\»[β.ϋΧX€Sˆ>[²!€}ΉυΈο*;ις ;Xkρ’έ<Ο.ϋ`3«Ξ!‰[q €A AH$ €A AH$ €A AH$ €A AH$ €A AH$ €A AH$ €A AHΨ$HcŒ-ώvluΔ€-¬ cšηy«YΨ±η₯?xkŒ^^^ΞΫ―――Kw ΐpyΜΏΦβ MӏΛuŸJ„φγς˜mœι2>–ξXΛeί$ ›Ιrkω„@‚  H$ ‚@‚  H$ ‚@‚  H$ «Ύ ΰ_ύυθΨΠί}Χύ °™oίΎ=zΎ0Kv$ ‚@‚  H$ ‚@‚  H$ ‚@‚ ΑŒ1=δ  » αpxτΚlΛ”g«*?ff[¦<Ϋgv$Z €„»}AŸ“ΊμχόOΣ4½=z°d@‚  H$άν’†šΛΜσVοΥ^cΣΤ|>OΚ³]k·A Ε’ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$ ‚@‚  H$όοό`˜_¦/IENDB`‚pyqtgraph-pyqtgraph-0.12.4/doc/source/images/gen_example_false_color_image.py000066400000000000000000000031221421045507400275140ustar00rootroot00000000000000""" generates 'example_false_color_image.png' """ import numpy as np import pyqtgraph as pg import pyqtgraph.exporters as exp from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, mkQApp class MainWindow(pg.GraphicsLayoutWidget): """ example application main window """ def __init__(self): super().__init__() self.resize(420,400) self.show() plot = self.addPlot() # title="non-interactive") # prepare demonstration data: data = np.fromfunction(lambda i, j: (1+0.3*np.sin(i)) * (i)**2 + (j)**2, (100, 100)) noisy_data = data * (1 + 0.2 * np.random.random(data.shape) ) # Example: False color image with interactive level adjustment img = pg.ImageItem(image=noisy_data) # create monochrome image from demonstration data plot.addItem( img ) # add to PlotItem 'plot' cm = pg.colormap.get('CET-L9') # prepare a linear color map bar = pg.ColorBarItem( values= (0, 20_000), cmap=cm ) # prepare interactive color bar # Have ColorBarItem control colors of img and appear in 'plot': bar.setImageItem( img, insert_in=plot ) self.timer = pg.QtCore.QTimer( singleShot=True ) self.timer.timeout.connect(self.export) self.timer.start(100) def export(self): print('exporting') exporter = exp.ImageExporter(self.scene()) exporter.parameters()['width'] = 420 exporter.export('example_false_color_image.png') mkQApp("False color image example") main_window = MainWindow() ## Start Qt event loop if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/doc/source/images/gen_example_gradient_plot.py000066400000000000000000000037531421045507400267270ustar00rootroot00000000000000""" generates 'example_gradient_plot.png' """ import numpy as np import pyqtgraph as pg import pyqtgraph.exporters as exp from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, mkQApp class MainWindow(pg.GraphicsLayoutWidget): """ example application main window """ def __init__(self): super().__init__() self.resize(420,400) self.show() # Prepare demonstration data raw = np.linspace(0.0, 2.0, 400) y_data1 = ( (raw+0.1)%1 ) ** 4 y_data2 = ( (raw+0.1)%1 ) ** 4 - ( (raw+0.6)%1 ) ** 4 # Example 1: Gradient pen cm = pg.colormap.get('CET-L17') # prepare a linear color map cm.reverse() # reverse it to put light colors at the top pen = cm.getPen( span=(0.0,1.0), width=5 ) # gradient from blue (y=0) to white (y=1) # plot a curve drawn with a pen colored according to y value: curve1 = pg.PlotDataItem( y=y_data1, pen=pen ) # Example 2: Gradient brush cm = pg.colormap.get('CET-D1') # prepare a diverging color map cm.setMappingMode('diverging') # set mapping mode brush = cm.getBrush( span=(-1., 1.) ) # gradient from blue at -1 to red at +1 # plot a curve that is filled to zero with the gradient brush: curve2 = pg.PlotDataItem( y=y_data2, pen='w', brush=brush, fillLevel=0.0 ) for idx, curve in enumerate( (curve1, curve2) ): plot = self.addPlot(row=idx, col=0) plot.getAxis('left').setWidth(25) plot.addItem( curve ) self.timer = pg.QtCore.QTimer( singleShot=True ) self.timer.timeout.connect(self.export) self.timer.start(100) def export(self): print('exporting') exporter = exp.ImageExporter(self.scene()) exporter.parameters()['width'] = 420 exporter.export('example_gradient_plot.png') mkQApp("Gradient plotting example") main_window = MainWindow() ## Start Qt event loop if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/doc/source/images/gen_example_imageitem_transform.py000066400000000000000000000026561421045507400301310ustar00rootroot00000000000000""" generates 'example_false_color_image.png' """ import numpy as np import pyqtgraph as pg import pyqtgraph.exporters as exp from pyqtgraph.Qt import QtGui, mkQApp class MainWindow(pg.GraphicsLayoutWidget): """ example application main window """ def __init__(self): super().__init__() self.resize(420,400) self.show() plot = self.addPlot() # Example: Transformed display of ImageItem tr = QtGui.QTransform() # prepare ImageItem transformation: tr.scale(6.0, 3.0) # scale horizontal and vertical axes tr.translate(-1.5, -1.5) # move 3x3 image to locate center at axis origin img = pg.ImageItem( image=np.eye(3), levels=(0,1) ) # create example image img.setTransform(tr) # assign transform plot.addItem( img ) # add ImageItem to PlotItem plot.showAxes(True) # frame it with a full set of axes plot.invertY(True) # vertical axis counts top to bottom self.timer = pg.QtCore.QTimer( singleShot=True ) self.timer.timeout.connect(self.export) self.timer.start(100) def export(self): print('exporting') exporter = exp.ImageExporter(self.scene()) exporter.parameters()['width'] = 420 exporter.export('example_imageitem_transform.png') mkQApp("ImageItem transform example") main_window = MainWindow() ## Start Qt event loop if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/doc/source/images/plottingClasses.png000066400000000000000000001264321421045507400250420ustar00rootroot00000000000000‰PNG  IHDRˆ Œά”LsBIT|dˆ pHYs Χ ΧB(›xtEXtSoftwarewww.inkscape.org›ξ< IDATxœμwXTΗΧ€ί₯*MΊbCQΑ†Š½7°w£±wc‰kμ½bο½£Ψ{EŠ‚ ½wXΎ?Vl ‰Fb’οwίηΩ‡[ζΜ™½œ=sζ̌€  €B$‰€ππΣ‡"EDDDώΛω[PPT~B"‘˜ϊE/ύΞ±ˆˆˆΘ‘—@Ζ§cAώVJ H$Οƒ’Χ~ωDDDDΎ ©@>†R ΏΘ±œb”H$§€ί_L‘οBcΐηΣqςΊQΠ‡JŸTΚgEDDDΎ©@9>†ΉE>|Ί^Œ*UͺQœΞwPDDDδοζνΫ@²²2ΚjΘw˜ ?R‰DBAAAR‘aeŽΊŠŽŽξ? ΆˆˆˆHι’žžΖ€Α@6"RΘ²‘ ­~²$‰τ·Γ+ΗΝ¦SGϋο*³ˆˆˆΘίΕΐ!yδ ƒlΤΈ°Γœύι“σι\*‘H"ΚWDOΟ€?Γύι#j˜ΤΖΐΰοs:zzΉcdT‰ŠFUŠέ{ξω„*•Œ©P‘R©–φ.˜΄΄TκΥ΅’»ώiΎTΛ*Jxxρ ±XΥoTμ^hXιιιΕd*-nέΎDλVPQQύ¦|\ίΓ’^Ργ"ς―!55₯πP¨L¦‰@Β§{RŠ ­ό~―Όρχχ&;'›zu­¨cfI™2eK]ξήύZqpίΕbKι ύ‘+§ŽίFEEε«συxInnυ-––¨ίΜ#—ΫT­jB5γΒ΅—ΎžHσσ±²j,\ {Μϋχ!θκθ³g&Άn:R,/oŸη9Ά‹λφ—ͺŒ>ΌgιςYlΫrLhχΐ@_όό½IOO£Ήζu,QSS/ΥrλΜΖuϋ‹ύΦΠ§kξw¦œ–φWεω><”7΄nΥ©Ψ½€ΐ—€€$τ6€œœ~1ω›δύζ;veΣ†ƒ(((|S^"₯J%dδ ™^Lύτ·™‘X €ό„ϊΨΡΝΎ)έμ›bί§‹ύ‰»χ χ·ο\C@ΰΛ?•ΐΧΟ‹δ”$ήΏΖ‘99ΩΒ½?ΠΝΎ)ώŸσ‘J₯τάoŸηΈ?}DxxX‰ωξΫ·oŸη_Τ Ώ‡ί+oγ…σœœlfΞ]D–|ŽŸΨΛΐ!±kcF^͘·`‘aAίTnIΈΉ?ΰψΙ½%ή ΐΫηΩ_Κwξό‰œw:Vμz||,=z5#2*œKWΐ•υ—ς/JΔ‡w¬Z;›σωP,€:€)2o’ (Ί/ωΝ$=ŒŒtfΜΕ’%ӈϊψ””$w¬₯ί v$$Ζ•ͺ „½ &_š_μ^™2eψuρ&”••Rή·n_Βωιoρ› {Λκu ™xμψnΉϋkΧ/,¦KŽίΝ#Χ;T―nΚ”IσJΜ733ƒΘΘπo’νΓ‡χ,_5GξΪ²•?co?rrr˜;"?2žππ0ΣΣΨ»3½ϋ΅":&κ›Κ.‰wοCΘΛΛ+v]"‘πλβMh¨k|už*Κ*όΟ9yϊ^/ž’Ÿ/ϋ‘—JσΉtε ii©\Ώy‘Σg!WfNv6·ο^Αω)’“?=W@hΨ[ dΞΞ¬¬L]wβœΣQΉϋŒŒt\ίγΤ™ƒψω½ „‰Ϊ%ς6ψ5眎rα 22™Nφπt“KχΧ“Wώ²ψφ˜Ψ\p>Ι±{xιλ)€ρ{εM` ―pΖΣg.$%%pκΜAόύ½Ήsχ*-š·γ‰ϋC!]bb<Ι)Idgg-\βώ»ζνΘΚΚ’ΣΫoίrόΔ^>ΊU¬žo‚ό9qrΟ=“'§›βγcΉxι$'Nν~Χ’S’8qjώ/Ή}η2—œ‘  €{pψθΒΓΓ8uμ?M[ΘΔρ³Ω½γ4φ=rυΪy@f¨ήΊ}‰θ˜(Nœ’y3₯||ύΌ8yϊΟ=Λ}—―ž%%5™›·œ9yϊŸλ——Λ½ϋΧqΊxBCφ–ό|YG"''‡7/rζάa"£>ΙYY™ΈΉ?δδιψΌτ@*•R‘B%l›΄ββ₯Srεϊφ.˜ξ]ϋ’˜O|Β粂CήpκΜAœ.ή5_?/9›ΰι3ό^y η^/žΔΜι‹9rl—œ\"85z€2c± Λg¨Θ'±DΤΥ5ΠΤΤBOΧ€vm»±CO._=[,]@ΰKFŒ²§kΟ&t³o*(ΗmΫWανσŒEΏNηώƒ4·mΓ·Βsέξ3 ίΉknξhΨ-™.x-Ÿ{<¦Wί–τκΫ’5λRπyV6οaί§½ϋ΅bΟΎM,Z2]P‘aAŒΧ‡½šαΠΗηO;w―ηΉΗ–.ŸΕ­Ϋ—HJJΰψΙ½Lš λ9ϊΌδώΓ›lX»SS304¨ΐΔρ³Y΅|;šB>ΗNμaθ]ΩΎs-{φmbππ.τιί†%KgŸ ΐ瓬߸˜_—Ο’G―fτιί―O…z(((²jΝ/τΨ–1γϋβληΐ™s‡Ω²mιι©Μ_8™=›0όΗ\»ξ@ZZ* —Lcΐΰ άΗ«θΤ‘'qqΡrήΦ‚‚.8Ÿ oοat··<©οΡo`[z84cππ.xϋ<'??>ύΫŠΙΟοέμ› ήέΠ° †Ψ&MœΓΗ•ΏχJ‰ˆόS(ΖΘ<‡y5Udυώ0~δ’σ Ǝž^, cϊΤ 8 €ψψΦm\Μe³OVΗUμΨ΅%%%ŽΨÌO#ωωR–,Αͺ5σˆ‹‹ΖΛϋƒ†v’ϋρwά±šwο‚ yCΏAνΘΞΞdΙδζζ’››ΛΘΡά½ττ4FŒ²Ηη₯#G;ΰυΒEEE6nYΚιO:ωΈπ3fHnn.^/ž³WsςςςΘΘHgήό‰‚q °tωlγ bπΠΞψωy‘Ε¬9cpΊp€7/pϋξα™>Ο8z|7)‰I δηKΙΙΝ‘Ic;b…‘·§°iΠ kάžΚ Η°wΑ$$ΔΈQ BΒ‚pάΎ€Wώ>ό8Ά±qΡ<χ|CΫ>—ηύ”QcϋΞγ'χYΈh»χnd^Β‘?tεΝrss˜5g ·n_‚‚“ζη“ύi΄λξ½ktκΠSΘχ‚σIF8΅Ψpς„q³„αΨΤ΄V­ω…e+fωu26n^Š’’眎1aς@αΩε+fωʟωΙ+oο,ΧaΨ΅w#AoˆŒ|O―Ύ-ΙΟΟC*•½CιιiH₯RƌοΛυΘΞΞbμψώBΫM˜<ˆΗnχQQVaΗΞ΅<Ό€Ύ}†qρ)€Ο‘NOΠ₯“šάΎ{…7/ππΡ-&OBzz*αο1ʞа ‚C^³οΐαωΥkη³mϋ*α|εκy$'%’§g@]s+\ίϋ·Oδ 2#±Ÿ')k#›Ό’„LgώΉr,$θm€`T’——Η¬9c<`ƒώΘ›7ώŒί33 ¦LšΗ•kηXΊx3ffH₯ωμΪ³‘™Σ“ŸŸΗ οg,^Έγ'χ’šš‚¦¦Oά­KoβbΙΘLG*Νgαβi 6A~δήύλœ=w˜ήCΘΝΝeρŸ;j:ύϋΐως)ΪFK»H₯ωΜϊy,φ2h QQ ‘ΊζVL7‹«ΧΞ³hΑz,κYγαι†Q…ΚhjjΰηηE½ΊVθλkK α8==•k7œX΄`=ΦV }Γ·μί}UUU~Y8…‹Ξ§5r YY™8]<ΑžgYψΛvνΩΘ²³Ήpξ jΩΆω(σζ¬δΰανœ;‹zΦ€₯₯’˜$ —rάΉ–|©”³§ξ’š’Μΰα]°²jΔν;WP+«Ξι·ΙΝΝeέ†EΌ{‚qUΊwοΗ…‹'„ψFO/wΣhΧΆ σηηησργ~ž7Ž[ce՘ϋn0{ΞX^r§bΕΚxzΉΣ‘]wžΈ?@OWŸgΟ]©R₯OŸΉRέΨσ:υyχ.˜ΜΜ Κ–Uϋ’ΧJDδ{ AΦ;YŒ:2γ°¨χ°D=˜””@rJ5Lj Χ²³³ΘΙΙΞ55΅PPT$::’•ΛΆΡΐͺ Φ šπΓπ θκθΣ±CO:vi@bb<εΚΙbΖZ·ξL‡vέYά᝻WιΧg8͚΅ΌV7o;σφm FFŸγΠnίΉLvvΦΚ†ξΜλXϋ‘ήυ1‚Q?NE]Mƒ]zϋ§ €―gΘ¦υ©aR wχ‡|όψ€ζΝΪ"‘Hxβφ€ΝΫ’••Ιυ›Ω»σL±οlΫφՌύ½™™Ξρ“ϋ;ϊ'Άm_MAAοήS½zMήΏ%''‡Τ΄dββ’±΄”ύ^šΦ¨ΝΫ·Ώϋ^ˆ|w ιΐ2ΘβyUψδ=$JΏχτ‰SϋΡΡΦ%'7OO7TUΛΠ‘}wΉ4oƒ‰ŽŽΔΑ~ Š˜™Y`fV7χ‡r†€mΣΦό²p ?~ κc&Υk’₯YŽυστ™ ΆM[βληΙΚεŽrΟ}ψNLμG:w²G"‘ΠΆM45d†άϋπP’’θή­/‰‡žƒ„ήeHθ[">„Qί²!Ao07―Οc·{˜ššΙ•ϊ†5>$&% ­ύy6·TšΟΖΝK…sss+™r“H0©V“F6Ν0©^‹Cϋ‰ΖλΕS΄ΛιπμΉ+£FNA"‘PΓ€–0„έΉ“=ϋl!9% €š¦f4΄±dΰ—―G}δr›ιS ¦Ίš7―z’¬¬Œͺj<½άyππ&M›΄δ—ΉŸ{q}{ eππ.̚ρ+ššZ\t>}ΟΕβ˜?ΉOεJU)«¦Ξλ7―¨X± yωyψΌ€™m<=έθΠ;Ϟ»2rΔdnήΊDŸήCyώό1;Ιfz–ΣFOΟ€ΰΰΧΤ«Χ ˜ό"" e?}T‘)ΑΒ^ςο‡jj(++“™™!\Ϋ±kΰ½OˆΕυΑk•¨kώy2Y₯JƜw:†‡§™Yδηη“˜τΩ@4«UOH[«V]’’>η…Ί@»œ.±ρΡrbHθjΧώό|£†ΝΩAΫ6]ιΩ«9ΝmΫΠ¦MgZΩuόΣΖ1ͺP™;χβΈc5ρ ±DΗD ‘4½sιΚZΪuΰΖΝ‹tοΦEBΓή±ˆwΝάά’ΈΈ²Ώ2¦ΉE‹vΈ>ΎΛΰA£qwΔOΣ’Ÿ—Ο¦-ːJ₯ΈΉ?ΐEϋbΟEF†Σ€q αά²ž΅` ~ˆ|/'›•Uc_ϋ²α¬¬LfΜ–y³²2yχ>DΞ›Vx=κc„Π9PVVAMMCξ]8pΘQˆσŽOˆεΦ5/aBc‘qP©b.]9ƒ§—iι©H₯ωΔΗΗbdT@]«œ±‘u‘wA[·XˆSHh΅kΥύ\Χϊ#άΑ~0}μ°m’6­»Π¦΅lbŠ‚‚½γtρ8-š·εφέ+T36ΑΜΜB.οόό<Βήsλφ%!,::Š2eΚbhPA¦οCήΰιω„†6ΝΠΤΠβ₯―'qqΡ4mΪ ELLj £‹" 4ι½ΌOΗe(ω]±RΕ*‘€¨ΔΠAcJœ1œ˜žžκκšΒ΅Κ•«‘šš\\ MX5Βνι#"#ίΣ΄IKš6m…›ϋ”””0©^‹ς†FrΟ%''R¦LYtudH$Œ*Κf6gd€‘«£/WΎ‘a™lIρH$!@GG=½β^Α¨¨ΉΩf΅λύ&–NB΅j2/Ωs'ΈΉ?zΏE{“QQό4λGLkΤ¦R%cβb‘ 6ΧΡΡŽ+U¬ @jЬ­Κ—―(άSRR"/Ώx`rBBF* η…Fήΐώ#ΡΠΠδμΉ#,^ϊv-Ϊ3oΞJΤΥ4¨VΝ”zu­Έvύ<]Ίτζήƒœ;]ά՟˜OJJ²0Π€±JŠJ4oΦ†σNΗΘΚΚ$%5™–-ΪRήωxΏ|Ξ’E…g*U!βΓ;Ρ@ω7!A¦λ?}І8‚’’’‚Ym ^ψ<£J•jό4m!?M[ˆTšOΓ¦ΖBΪ2ͺe„aθόό<¦LΖΟ3—2dΠΚ”)ƒ­©\ήιiΒq^^.JJŸΥqΡγ’PTT’3T„ŠJ$,YΈψψX<ΌΙΞ]λΈsη +–9–ΛgV―›IυZ,YΈrεt=p―G·~μή³‘€€nέΎΜν§>Ι H^^.''‹ΛžU‚¬…Ψ5oΗVΗ•ψ½zžž λυυ xε§sf-/±ΉΉŸΛOKf­£­­+ηWTNeee¬4‘G·~Ψ&ΏEAA‹z πφyŽΩ'ƒnόΨ™Œ; Ψχ[Ϋ]PPΐτ™#7v&ŒD]Mƒ–mλΘ₯-ŒωΘΝΝ•›―€τΗ“’ΙΜ*Ή}ηΞ^ΞΈΡ?ρΠεmγΚ΅slήp‡žƒΨw`+qq1\p>)x―ε묈’’"}z ‘¦ιg™UTeοysΫ6xz>αΩσnj7 M<<Ÿϋ‘ζΝΪό‘ά"(*Θf*« I)ԏr:ρw΅P«–ε^ˆ’00¨@ll4)©ΙΒ?CHΘΪΆι\bϊΝΫράγ1ήρσ ™GΞΆIKΜ=„ŠjμZ΄+φŒŽŽYY™DGGRΎ|Eςςς y€i 3’’…ς₯R)>Ε|T ''‡Ω3EOχ—ξ12ͺΜ³ηΒΉ₯… oƒρυσΒ’ž5 ‚‹?77!mјφ3ηΣΜΆ5S'Θb½ŠLΜyσΖ_8~χ>DV?έΟFγŸahXΰΰ@Α ωΧ“*•«‘££Gχ}…ΰβ³GqξόQ~6€Ύ½‡qδΨ.”””ihc[βA††F¨¨ͺ²zŎΛΞΙΝζξ½kXY6BQQ‰*•«qνΖŒkCσ‘QαTd\b"" ’>_Dί>ΓΨΉ{= κ7ŒDΟO1Δ%-υϊ?yyΉ΄i-Σ…χά 77W˜TΰSΔΠxύζm[wωβΚ˜Υ‡ΣΕγH₯ω(((rΑω$ρρ1tμΠ“ΠΠ Z΅μHŸήC©]«.σNϊΓΌ€|žΈ=`ϊ””+§ΓǏπ{ε-xΰΚ•ΣΑΞ=›·­ bΕ*Tͺ$λܚ™YΰλχBΘΗΟο&Υk’€€DŊUετδγ"±ζ‰DΞ`«\Ω£ •8v|ΰ8™σΰψΙ}U¨,”Y”ͺUMψ<Ζη₯‡0QΕ’ž5ώώŸ'LΈΈ~ž YΗΜ’Π°·B§?**‚χα‘T«fŠ„Ο²•)S£ • y-, Τ§ΧPΦ¬_@γFΝ1©ώΩ9πΧ“ΌΌ<$ ’"—Ιxχ>„ψψX:w”‚=}ζBZZͺάLuoŸηX7…&Ό ς§Oο‘Εκϋ{˜ΥΗΎύ[ΘΛΛCII‰λ7/Hο^CπχIϋvέpθ9K ƌlλλbΧ’ΗOξ#4δM‰k~J$jΧͺGdT8Ϊχd“SΚζΛb0›ΩΆζτΩCΌ Δ΄†jjκ\Ί|šΈΈ&NψYΘ'$δυŸΪ"ί ²Ž²B‘γbzρ»©B “ZΤͺiΞΑCŽŒ1™'Oξςš•Ÿz«κZ„„½ΕΨΈeΛͺaΧ’GΙ–407· Z5S²²³pqΉΓŠeۊ•Q±b*W6ζθ‰=Œ>‰Σg‘¬€Œ΄ €²eΥ¨aR‹½ϋ73hΐ\ΈxRθ½W5ΑΒ†={71vΜO$%%ςλς™¬ZΆJ•ͺ’‘‘IhΨ[j˜ΤΖ€z-NώΖΣΈlΙfΜΕ΄)σ±nΠ„ΤΤ<=έ8|l'ΖΝ*±=TTTyJff―ό}πzαNBBΌΰ’šŒΣ…γ΄oί§‹'¨oΩu΅/_¦ cϋ\p>IέΊ HIIbζμQ\8χˆε«ζ`T‘2Œ$??Ÿ‚‚κ*hΣΊ kΧ/βδιL\ς­ZvdΓ¦%œ=„{ρΧ‹}Ά²Σρ$***4³mΓώCۘπ©·lcmΛΑCΫιΦυ³§!9%‰ψψXΉαz‘:=Ίυ#55…£ν±¨gv9‚ή’’šΔν§JŒ·5«]U-™Žj™2h—ΣΑͺ~#φάΖΌ9²‰\Ϟ?&πΝ+ΒΓCQVRŒΙ/‘Uˎœ>{ˆήύZccέ”'nΨ²ι0Ί:ϊLš2„»χa`P7χ‡ :^xΞΝύ“¦~ŽTTTdλ¦#΄΄λΐ―Λgb^§>qqΡτι5„+WΟΠΖ“κ΅θν0„±ϊΙy"‡Ο¨q½™4uzΊs–Mΐ³kўγ'χ²mσQ!Ϋ&­˜ϊΣ <¦Δ6θΫ{#GΫ3mΖ²²2Ρ*'ŒΨ΄²λΐ•«g?i eΛͺQήΠH˜}ϋΓπ ŒΧ—Qcϋ`nnΙƒ‡7™9}±LΆšuΨΌm«ΧΞgφΜ_11©Epπg±]Ϋ€€&3z\_,κ6@GWŸΧΔΖΕ°qύ~τt ŠΕ|V3‘‘σLB»œΚ**Ψ6mΕ‘£»Xπ‹,$ΚΧΟ‹…K¦CzzΪW-Πή€±66Ά8τ±£iΣ–<~rŸΥ+v «£ΟŽ]kΉΰ:FF•yϊΜEξ](lΓΙΣ†1 TUΛ”˜Œι‹˜9{4ώΎhjhςτ™+Ϋ·Ι&#YΥoΜΟσΖccέ‰DB₯ŠU‰‰ύˆ–f99ΗΜΫΰΧΨ6mυΕuωΫω’ΞrαΠΛΐπφu™‹ΒδA,˜·¦Δ^Θf'uλΪ‡ϊ– ‰ŽŽd‹γJήb Wž†O q#Y\Θ•kη8sφ½†ΠΛ~³ζŒ‘j•κ‚— `߁-xϋΓ„žγ™³‡PRR‚²cc£Ω½o#Ύ4j،GNaιςYT3Ύ~y^x?£j•κΌ#55kλ¦BgΡΓΣ «ϊ„af€—TlŒ†Ί^/žaέ  H₯R^Ύτ #3ƒ†6Ν„EœsssyδO|| υλ7R–M`ωη2‘uS@f€δζζR―‰>/=01©‰Ž>‘Qαό0²'W/ΉΛΝζΞΜΜΐη₯ΩΩYΤ«Χ@Ξ(HKKΕλ…;Υͺ™RœΡΡ‘Τͺ)ϋ>}IMOβ·γb }‹₯…Pœœ^ϊzR½Ί©ojj οΓC©k^€ΈΈ|ύΌ¨\٘J«$Œ°πκ•7UMΈqσ"ή>ΟηENNφ'Ή³±΄΄‘[κλυ›W€€$ΡΠ¦—―žεκ΅σμή!Ώ~d~~o4]0y IDAT‚όωψ1’κΥL©Z΅Ίo—››ΛK_O‘] Σ{xΊ‘©Y³ΪυΘΜLΗο•7υ-¦ƒgNά!1)ž€€¬4VΚxαύ”ΊζVB›Ύφ£BωŠhkλΚ½'ψϊy‘’’L£†Νc///·oˆŽ‰ΒΒF.Μ©°Ό^<Ε΄FmΉ…Ω?DΎGš/<ζII ψϊy‘I½ΊVrο@@ΰK΄΄΄…°© ·(+«‹ŸΗΗΗ2hX'νw.qτJδϋQd'•*ΟΘ–εIOOz΄₯Ν©3yρβ)kVνϊ[ς§‘J₯ ٝ1£¦ΣͺεŸΕ‹ˆόέ”ΆXZΖ.ή»υRn2άΏ‘‚‚βγcYτλtš6iYΜυoΕΧΟ‹΅λ²zεNrssY΄d#†O’m›/Ζ/dό€τμ1@ˆ;/mlνL9wϊž``ύγ—…“1―cω—Χσ)=ΎΦ@όΟο}σ><”±ϋ³qσR~Σ ϋžΎΪ87fΟ=žΘ-τ)ςεœ9w˜Κ•«‰Ζ‘ˆΘŸ"‘šq iAώ”Π ¦ώ4œΊζυS?πυ¬ιΪ₯+WΟcΫφU 8š–vώR^ Y‹³σIΉ₯Jγͺ&:εΏΚλ7―HOKeπΐΡžXδ_Ηήƒ2φ›·ώXΦ³–›Ρό΅όή^Μ₯ΑϋπPγUϋ –&β^Μ"6ώ­D‘‚―υ ~Σ$• zzΨκ}{lΡ γK›ͺUͺS΅JυΏ-šΒaEDDDDDDώϋόη‡˜EDDDDDDDDJΡ@‘C4EDDDDDDDDδ(•ΔΣgςΨ½ψφm""""₯I΅ͺ¦Μψ΄¨±ˆˆˆˆΘίG©ˆ!ao¨[·₯‘ˆˆˆH19tθΨ?-†ˆˆˆΘ₯6‹ΩΜ̌ΝΏ|«(‘―A[[[4EDDDΎ/–Ήω·ϋ‘Wώβ‚ϋ"""?­μ:ΚmŸ[šˆ’ˆˆˆH)ςβΕSΆn_Ν?-ŠˆˆΘc]»Κ“GAr{c—&’("""RΚ΄hή‚3gOΣbˆˆˆό?FU΅Μߚw]ζfŊ̝;—Ήsη²vνZόόό„{σζΝcΧ]ψ|nn.ΉΉΉΌxρ‚Γ‡² ε333KMȘš4iBzz:IIIlΩ²…qγΖ±dΙnάΈQje΄nέŸbΧηϟώ;Ύ:Ώϋχογμμ @~~>ΩΩΩί,£‡‡έΊuϋζ|DDDDDDDώύ|WqλΦ­Τ¨Qƒ¦M›’­­Ν„ ˜7o™™™jΘlέΊ•Λ—/πκΥ+Ν;€··7³gΟ.59§M›Ζ΄iΣPWW' +++bccιΣ§UͺTaΜ9¬Z΅ͺΤΚKMM%??ΏΨυ+V0qβΔ―ΞΟΝ͍[·npωςeΆlΩςΝ26lؐςεΛsπΰΑoΞKDDDDDDδίΝwbξΤ©U«V sηΞX[[³lΩ2Ή4>>>,]Ί”ΘΘH,,,X³f ^^^lΩ²===TTT„΄ρρρόψγ$$$`ddΔόωσ9zτ(GŽA*•ςΓ?0|ψp™={6ΆΆΆό-2ξέ»—ŒŒŒbΧ―_ΏΞ›7oΎ:ΏΌΌΌGŽΎάέέK5O‘]Ύ»8sζLΑ«6eΚ”bήC///TUUqpp@CCƒ‘#G ήΊ/α₯KXXXπκΥ+όόό°΄΄δΪ΅kH$€R)C† AGG+++ZΆl‰νΫ·ΗΫ[Ά,…――/B ’–– SŽ>―^½B"‘ššΚ¬Y³ΠΥΥ₯F\Ύ|nέΊ…ͺͺ*Ϟ=d m̘1¨««3hΠ \\\ΉŒŽŽ7ΖΣΣSN€§§Σ₯K΄΅΅yόψ1-[ΆDGG‡[·nqκΤ)LLLδ Ξ’Έ|ω2xzzŒΉΉ97oήD"‘ ££CηΝ)_Ύr ¨¨X¬W†‘‘‘p.‘H„ΆάΊu+Τ―_Ÿ_~ωεOΫ&&&Fh›vνΪQ―^=‘-ώ¨m@φ}DEEύa9"""ΞΑƒ9|ψ0ξξξΈ»»σζΝζΜ™Γ€I“Oqqq‚W(??Ÿηϟγαα!δ“ŸŸ——YYY<~όΈD}ιγγ#tTjΤ¨A͚5…σΤΤT=z$gθdggγκκΚΫ·oΏ¨>ΉΉΉ**ŠχοίΞ† Έwο)))Ό{χŽ;v°tιR@6Ι188˜ΐΐ@?~Lhh(vvvBŒ{JJ AAA„…… xVV>”σώ₯¦¦Hrr2...δδδΘΥ1//'OžΘ’XYYQ‘BαόγǏ<}ϊ”‚‚Ή||˜±cΗ2qβDΣΣ9r€699™ΰΰ`jΤ¨――/ΆΆΆ_$S“&M !//%%%Nž<‰±±1κκκ4hΠ;;;222¨P‘‚ 0‹RX~:uPSSΪ& ccγ= ΏΗ›7ohΫΆν§)™υλΧ³}ϋvΚ—//\λίΏ?ύϋχdF‚½½=¦¦¦tιlllhΣ¦ ωωωŒ?žηϟ“‘‘A«V­θΫ·/*T`άΈq̝;—‘C‡°oί> ‰ŠŠBUU•γǏsθΠ!ΣΣY±b[·nΕΙΙ‰F1fΜ>|Hzz:φφφτλ׏ΐΐ@444Ψ»wοοΦ%-- ΰΰΰ@BBαααάΊu‹ύϋχ£――/xW¬XAωςειίΏ?:t }ϋφΔΕΕ1mΪ4άάάπφφfώόω4kΦ¬X°eΛ–,[ΆŒάά\όύύιή½;GŽaήΌy0{φl¬¬¬8wξΛ—/"™”••3f ­[·ΖΦΦ–‹/ςτιSΟ?ΟδΙ“ιΡ£τνΫ·˜B033cλΦ­˜ššςΣO?ΡΉsg²²²PWWηƍ}ϊPΏ~}‚‚‚;v, Έ»»£¬¬L™2e8xπ ?fκΤ©hkk°gΟjΥͺ€ššΧ]`βĉψ²eˈ‰‰αεΛ—¬\ΉR0;v숷·7φφφ‚§uθΠ‘Β’A?όπ&&&B|€§§'hjj²dΙTUU©Y³¦œw`Λ–-T―^y/ =Ύžžžώ‘XDDδΟQPPV>(Τ%Σ¦M###ƒΐΐ@ιΨ±#ͺͺͺB‡LKK‹/^°~222ζγǏB~…+˜››Λur›4i€ͺͺ*iiir8)[Ά,ššštνڐx#GŽΔΤΤ”nέΊύιr[ššš~όΘργΗqww/ΡKαΓ΄΅΅‘H$άΈqƒ¨¨(YΗςΠ‘Cdggsξά9μμμΎH---ΜΝΝ9|ψ0iii 0€ .°kΧ.Ž9‚žžθλλ †jrr2aaaΒ'99™>žΠ3gΐ‘‘!Θ6`ΐξάΉΓύϋχ…ΞiηΝ9{φ,ΡΡΡΰββB§N°΄΄δέ»w$&&7„!q555ab`αJ…lίΎQ£FqγΖ RSSyχξ›6m"==]©ωm[κκκ"•JΩ»w/ΚΚΚ‚Ό)))œ;wŽΜΜL._ΎόΕmΩ°aCήΏΟΓ‡IHH I“&2kΦ,\]]©Z΅*½{χ.1ά©hέΊvνΚ‘C‡HLL$>>ž‘C‡’ššϊE2"†‰”’X ¬X±BΗω­θθθ°rεΚRΛο{cee%τώEDDΎΪ΅kγμ쌏]Ίt‘_Ώ~Έ»»sηΞ7nŒ’’’0š0|ψpςςςhΫΆ-oήΌaϞ=œ:uŠΠΠPΚ–-K•*UθΤ©ιιιΒd΅ϊυλΛΕl·jΥ ‰DBΥͺU111dA 'ζΩΪΪ2pΰ@ϊυ뇫«+vvvLš4‰M›6Q¦LͺU«F@@#FŒ>W―^e€I„„„Π‘C²²²ΨΆm[·n%>>555ΪΆmK·nέ„ιQ£FΡ΅kWzχξΝψργY³f 4@CCƒ Π³gO/^ΜΌyσ„’Ÿ~ϊ‰yσζqγΖ lllδ–…ιΠ‘{φμΑΩΩ™–-[2zτhβββxώό9εΛ—§\Ήr4hΠ@H?gΞξίΏO·nέ044d͚5,Z΄ˆΌΌ<6lH`` :uΒΪښό€V‘¦¦&Œp™››chhˆ‚‚Χ]γψργτοߟŸώ ~όρGvνΪEσζΝΩΊu+ΫΆm V­ZBG`δΘ‘899αθθȜ9s°°° {χξŒ1‚>}ϊ ©©‰žžžœΡΧ€I!,ΛΘΘH˜•G^^+VόΆTδ ²aζ€ανλ/ΠΣ3ψκLV­G³ζΦBLˆˆˆˆHiγξξΞψρ“9ΈΧωΟ©©)΄jg.œρΐ;ΰΰ ψοD (\κΰΠΥςνtκ(?Μχ%άΌεŒ—χCΜ=ύΥΟώ222000(Տ$==Fqχξ]Ή%ΜΎ…άά\š6mΚ‘#G¨[·n©δ πτιS¦M›φŸ^lzμΨ±4kΦLˆω‹ͺjέσGEEυ‹‘7Aώy@2†L'ϊπY'&Y’QDDDDδoαΔ‰XXX0eΚ”R3AΆ²ΓŽ;˜8qb±YΩΛΈΊΊ*‡"₯ΒΏn™›Ώ“όόόb3ΟDDώΧΡΧΧΧKϋSΪρ₯ΙΰΑƒ…}νK›&MšππαΓRΟσΏμ=lΡ’·oίώ§ΕωΒΤ―BPPuλΦECCγŸEDδ_AZZžžžr³=EDDDDDώ§ D­yxεΚ•Z ‘}ϊτω§Eω"Ζ ŠˆˆˆˆˆˆˆˆΘ!ˆ"""""""""rˆ’ˆˆˆˆˆˆˆˆˆ’ψ7qνΪ5N:%χΉ~ύ:[·nΕίί_.νξέ»ρσσϋn²Ή»»ωEiχνΫΗΛ—/Ώ(νυλΧ…νœœœώ²|%ΚΦ­[…s///Ž9‚““ΑΑΑߜNN3gΞ,vέΙΙ‰[·n}U^₯UχΔΔDaΑc‘ο‰h ώMhhh ₯₯Ε³gΟπττDKK uuuƎKνΪ΅‰ŒŒdως儇‡ Ϋ<}.\ΈΐλΧ―Ώ(mDD„°ηιŸ+lxΏzυjΒΒΒX»vν_τ,_Ύœ^½z°hΡ"V­Z…ͺͺ*̚5‹7n|SR©΄Δ6ιή½;mΪ΄ωͺΌ λώώύ{Φ¬Yσ—eΡΡA[[›K—.ύε~όˆ……7ζΘ‘#DDDP³fM† ΐςεΛΡΣΣγεΛ—τοߟ   ^½zE―^½θΡ£wοήΕΤΤ”*UͺΰααΑ£GΈ>‰€ΆmΫΐώύϋ‘J₯ΈΊΊrψπaφοߏ··7ΚΚΚ :kkkΆlΩ‚––>>>¨ͺͺ2mΪ4tuuΨΈq#!!!˜™™1yςdœΡΠΠ K—.\Ήr…[·nQΆlY¦M›FωςεYΉr%±±±(**2{φlΉν­–.]Jdd$–––%Φ]"‘°tιR πρρ‘«»ƒƒ={φdάΈqΒ±ˆˆˆˆˆΘχBτ ~g"##ΙΘΘ W―^΄hΡ‚=zχ^ΌxA`` 7n€_Ώ~¬_Ώώ»Ι•••Err2ΫΆmcΜ9lήΌYΈΝΝ;iήΌ9GŽ`εΚ•ΜŸ?Ÿ+V°wοήίέLΎW―^΄nݚ.]ΊpβΔ LMMΩ°a\Ύ|‰DΒΥ«W– €¨¨¨zίέχ_ζη>v–…ϋs]\ξΞΜΜdΟήε܌5 rAΚα IDATtttΘΘΘ@OOO:¦X±boΌN__ŸŒŒ jΦ¬Ipp0S§NE&“ uα˜˜˜―&²„††²hΡ"ΣΣiםtLNς™ššŠΉΉω;―Γάά\š¬£P(ˆΗΚʊM›6ρθΡ#Φ―_ΟΥ«W™9sζ[_omm͈#€!:::dff~πή222000xgl‚ ‚ΫD‚¨!¦¦¦ƒ‹‹ σηΟηβΕ‹\½z•””jΥͺΕ­[·θΪ΅+εΛ—'&&ζχήΌys~ωε† Bhh(₯K—¦I“&Ό.@XX˜Τ²(7n$ @Σaω,00Ίuλ~τρσηΟgχξέy‘PύφΫoΨΩΩi: ‘ ζ΅ϊυλ££σC=[·n½½=666\Ώ~«W―βκκJι₯)]Ί4K–,αΐααα‘'1΅lΩ’»wοJέΒ666tι…/^°oί>:tθ@΅jΥ8}ϊ4͚5ΓΪښ?όΎϋξ;ΰUω›cǎqπΰAϊτ郉‰ ΅kΧF₯RΰεεΐΧ_ΝνΫ·₯IΥͺUγ·ί~£|ως|χέwdggΣ£G)ΆώύϋΏρΨΩΩ™­[·­[7|}}Ήtι—/_&""‚† 2~όx©{ΩΦΦx΅p}\\Ώύφ;v€J•*œ9s€6mΪpμΨ17nŒ««+J₯’^½zIΧvss£dΙ’8;;£――‘‘!7ndίΎ}”,Y’6mΪ §§GJJ [Άl‘rεΚtξάYνή4hΐ­[·Έ~ύ:]»v₯jΥͺ½·lΩ…BρΑ{E&“affφe?t‘@‰ŽŽ¦T©RτμΩSΣ‘ωdζΜ™]"Gxx85kΦ€eΛ–y•PЌ1Bκ1Σ4‘ ζ±zυκ©=½λvȐ!ooggΗ Aƒς4¦οΎϋNJτ^χΓ?HΛ•+§Ά―f͚jΟυυυqss{η19I’ΎΎ>C‡•ΆηŒίΛ‘§§§–$υλΧοΗυκΥcǎ\ΉrEϊτνμμ,#|]γƍ₯ΗΊΊΊRH]ΫgϞ₯dΙ’j3ƒεrΉZ‚˜Σe]²dIi›₯₯εIϋΫfΏλޝœœprr’žλθθ¨%o»χ 0i€7!h?jΥͺ₯ι0„|’3τεS•.]Zόž!―;41‹YΠ 'NδΝ;Ήr.΅Δ― ϊχίωκ«―€rC‚ ‚_D ’ ,,,€Ί‰_ͺcǎΉržΌfaaAŸ>}4† ‚PΉ1##ƒλΧ―k: A(RSS5‚ ‚P©ΡΠАR₯JεkjA(Θ¬­­₯Ωμ‚ ‚£H%ˆΦΦΦ_΄6 F‘œ‚ ‚P°©ραΓ‡΄mΫ–Ϊ΅kk:A(nάΈΑ™3gΔ‰κιΣ§œ}ZͺκββΒΜ™3?κΌ§OŸF₯RqυκU233‰gΨ°aXXXP½zu¬¬¬¨S§:::¨T* Εkž šgllL“&M ₯V­Z<{φŒϊυλ“’’€J₯βόωσRΛσσηΟ100 iΣ¦œ>}šψψx:uκΔ‘#Gˆ‹‹ΓάάjΤ¨ΉΉ9ζζζ>|˜¦M›ςΧ_Ρ Atuu)Q’:::ΔΔΔΠ°aCώψγΆmΫ&Zœ ‘ j±γǏckkK΅jΥήyΜ³gΟX΅jΣ§OλώK—.aggGι₯s%&₯RΙΤ©S™Ο₯K—°΅΅₯L™2_ΐΌyσ8p V¬Π"h—ƍΏ·ϊ‚…… $%%aiiωQη΅²²’S§N—9kΖΏ>‘E.—«­%/\=zτ`Ρ’EDFFJλΦηΙdΨΨΨ0}ϊti’‰\.G₯RHll,Γ‡ητιΣ„††€Iι΅΅kΧΖΖΖ?~Μ‰'(Q’wοή•ŽΙΘΘΐΜ̌ΜΜLΒΓΓ)^Ό8χοί—Z!ν$ώηk©ΔΔD–/_ώΑ²=666L˜0αϋϋν7nίΎ ΐ°aΓΎ8M›6QΉre 9~ό8ύϊυγΑƒΨΪΪ²kΧ.Ə―6~κsDFF’žžΆΝΖΖ†qγΖ}yφοߟkχώΓ?0kΦ¬/:‡ |ŽζΝ›³mΫ6ξή½ΛκΥ«₯uΦMLLΈyσ¦Τ혳-::š˜˜š5kΖ† xόψ1{φμaώύšΊ!T\™ττtBBBήΊZTΓ† ωε—_ˆ‰‰aΙ’%ά½{—bŊQΌxq>|ˆ­­-uλΦe͚54kΦLzέ΅kΧΈxρ";wξδθΡ£ΈΊΊ€INœ8Α•+WΨ½{7eΛ–ΕΐΐΊwοΞΜ™3™6mš4^QΠN’QKνΫ·]»rδΘ"""¨P‘gΜαςεˌ9’›7orπΰAΘ²e˘3g{φματιΣdff];Ϊ΄i#oϋφν\ΈpΉsηβλλΛΝ;9wξtοޝʕ+γηηGεΚ•9qβ666|ϋν·μΪ΅ kkkfΜIVVΫΆmγΐ¨T*όόόπχχ§^½z΄jՊσηΟ“Mxx8δφνΫ >₯RΙƍQ(8;;Σ―_?._ΎΜ©S§ˆ‹‹#&&†Ύ}ϋ΄iSNœ8ΑςεΛΡΥΥeςδΙdff²bΕ f͚Ε?όΓƍILL€cǎ΄hΡ‚½{χςΧ_‘™™‰››mΫΆ•ξ}ǎjχΎkΧ.Ξ=‹ΉΉ9έ»w§J•*L›6J•*qβΔ ¬­­iΡ’;wξΔΚʊY³fQ΅jU Ή|ω²X;YΘ5εΚ•£eΛ–oέΧ‘CΜΝΝ)W₯J•βΘ‘#΄nέZϊ6~όxŽ?N͚5ιίΏ?ϊϊϊ|υΥW4i„K—.Ρ];Κ–-Λ‘C‡°··§C‡τοί_ΊΖλϋφν«6φQ(xFΕγǏ111@OOOZsάΈq\Έp={φΰμμŒ““πκη Όϊ°‘šš*•Rκέ»7aaaΘd2Κ”)ΓζΝ›±²²`ύϊυμίΏ{{{–.]Jzz:₯K—¦]»v 2„ˆˆъ¨ΕD‚¨₯φξέΛ† 055eǎLœ8‘ƍ³eΛώώϋo–,YΒ‚ ΘΜΜ$** …BΑΪ΅k9xπ …βΦ‚]»ˆ――/αααœE‘PP§N΅λ|ύυΧdff²oί>vνΪ…­­-7ndΒ„ ΨΩΩΡ«W/ΪΆmKZZ‡ζΰΑƒ€¦¦»wo)AΜΚΚbΩ²e¬]»–£G°aC}Zνήοέ»' τnάΈρ;οέΡΡ‘Κ•+sπΰΑω ‚ B&D-΄}ϋvjΦ¬)Υ©ͺR₯ GΕΝ͍λΧ―γδδΔ_ύE=Τ^7yςd’““ ₯OŸ>>|ψ­η·²²ΒΥΥU­₯!g`sΞΏ2™L̞Σνd`` ξ΅΄΄ΔΠА«W―RΏ~}ϊχοOώύ @©T`jj Ό*κ:uκTV―^₯₯%ƒ–›3ψ^ „Ξι:Ι™θςΏ]^ϊϊϊjγγββ033ΓΧΧWνήCBBή{οƒ zγή_ΌΆ)ΣΣΡΧΧλyAA›ˆI*Z&66–;wξ0qβDΌΌΌπςςbάΈqμΨ±ƒΈΈ8‚ƒƒρχχΗΡΡ‘νΫ·K―KLLdĈΔΔΔP©R%,--Υf'κθ萝ΝύϋχiάΈ1GεφνΫάΈqƒωσηΤΨ£J•*&=Ÿ={6~~~¬^½š7n°mΫ6ώόσOΥ^§R©HNN&>>ž^ΌxΑ£G€W-x§NβΟ?ΔάάCCΓχΖ —Λ©Z΅*›6mβΚ•+xyy‘˜˜ΘπαΓί{ο …‚{χξΡΈqcŽ;ΖνΫ·Ήyσ&σζΝϋθqWχξέ“ZP!―唝_ωσ%EhAΤ2/^Ό`βΔ‰jIK₯J•hΡ’wξά‘ΚΛx{{³uλVΜΜΜθάΉ3¦¦¦ 8ŒYΈp!:::ΈΊΊRΆlYd2S¦L!$$„αΓ‡³vνZ8€L&cȐ!΄oί Z·n-uγ6oޜ²eΛRΊtiβββHNN¦xρβ|ύυΧ¬Z΅Š³gΟrδΘ*V¬Θ―ΏώŠ©©)111΄nέxΥ8wξ\φοίO½zυX²d ϋφν£D‰4mΪ”θθhΣΣ ΐΝΝM*εQΉre,,,077—ΊΝ-ZΔ±cΗΈ|ω2K—.ΕΒΒwwwιή,X€\.ΗΥΥd2?ό3!!!Œ1β­χή];J”(Όšl“Σ5ή¬Y3iŒΨ₯K—€iAΘkIIIDEE‰24ω@©TR₯J±±P€ˆQΛT­Z•ͺU«Ύ±ύυΑδπͺ[4gbΘ»ΐ ΰκκ*=nέΊ΅”΄YYY1pΰ@΅c__ ΎU«VγΧ#5Š€€|}}pppP«W˜£dΙ’΅@}@=ΐˆ#8}ϊ42™μnnnγΚ•+SΉre)AΤΥΥU›‘ύ{Οǘs?9χτ9χ~όψq€X!?˜ššJ3N…ΌσzΝ?A(*ΔGO!W}ϋν·ΨΨؐ––φΕη²³³ΣšΑŒ3FΣa‚ B-ˆB{½LΒ—ptt|cΌbA•[χ,‚ A‘K“’’Ψ»w―¦Γ„!>>^Σ!‚ P‘JΝΜΜhΣ¦ wξάΡt(‚P |χέwδAϋ€§§σμΩ3M‡Qθ‰YΜBQT`D•J%ΥΤΛ ϊϊϊΨΩΩρλ―ΏζΪ9‘0ΙΞΞV[·χKθθθˆŸyL‘P--“&δ­/]C^΄MMωηͺW―ώΦ‚ΔŸ*++KZ[·Z΅jΉ ­Z΅β?ώ`Κ”),Z΄(WΚ¦T­ZU*δ.δ Ή\Ž‘‘φφφš₯Π λP ENM*T¨+K—υκΥKzμθθ(–C„ο―Ώώbǎσ#F¨­ σ9ξή½ΛΤ©SΏ44α#ˆ–Ϊό!’C‘(enAA5"AAAΤθ.fAαέ²²²D©’| T*5‚ δ;‘ ‚ 0Ož<ωΰ1:::(•J^Όx‘mΊΊΊ$&&’™™ωΑcmmmΕϊΨB‘ DA„ζεΛ—”*Uκϋ₯ ωV‘Ozz:ιιι˜››ΏσΈ˜˜lll U‚xχξ]ͺV­ͺι0  β'R(Θd2Θλ8ήuώΜΜLΝšΜΚΚΚ•²G‚  ¬¬¬ήΉ/;;›¬¬,τυυί{œ;rΦ–Πχ»°΅ζfff2tθP|||pssΣt8B>+<srΡΦ­[ωϋοΏΥΆ………ΜͺU«ΈvνΪgχόωσŒ3†1cΖ0{φl~ύχΟ:B‘ΐΓΓƒύW:οδΙ“:t(K–,!""β³ΞϋΊΉsηrκΤ)΅mΩΩΩ >ό“ΞsμΨ1Nœ8ΐƒΎ(¦+Vpψπα/:‡ ‚πqΆoߎ……σζΝ“ήo„’C$ˆoaooΟΊuλΤΆmΩ²KKK<==©U«–΄=99Yzœ™™IVVπ*™JOO^UΰOMM%** &MšDΧ]Ω±cΗIXRR’τ8--MZβI©T’’’ΐΪ΅kiΫΆ-–––μίΏŸ Π΄iSζΝ›‡““Γ‡ηίE₯R‘––FFF†τZ₯R©φ]‘PžžŽR©$11ροEJJŠ΄’.‹-zgΌπjι―Χ·έ»wΘΘH233ρσσSPŸžžΆ„UΞσ„„iE€€$ι˜!C†°aΓވSAΘ=ιιι¬^½š9sζΠ±cGfϞ­ι„|&Ί˜ί’I“&Μ™3‡—/_beeEzz:§NbβΔ‰Μœ9“Φ­[γββΒ΄iΣ(V¬iii|ύχάΈqƒQ£F±aΓNœ8Α† Έ}ϋ6kΧ₯Aƒaee…•••*U"..€#GްgΟ¬¬¬HKKΓΟϏS§Nqώόyf͚Epp0zzzΈ»»³cΗŽ= @@@~~~4ix΅Άn“&M000 &&†‘#G’««ΛΠ‘CΙΞΞfνΪ΅”+WŽgϞ±jΥ*"""˜4iŽŽŽ$$$`gg'9>xπ όρQQQŒ9’Ίuλ‘CώόσOΝ;Ηƍ177GGG‡Y³fHxx8Ŋ#;;› HίΣσηΟsσζM–.]ŠŸŸ .”ξ½zυκτνΫ—Ω³g£T*ΙΞΞζϊυλtλ֍{χξρΟ?°fΝ,--iΥͺΏύφύϊυΛ·ίA„’fγƍԯ_ŸͺU«R\9~ψαŽ?Ξ·ί~ϋΡη8zτ(.\ eΛ–Τ―_Ή\ž‡ ΉM$ˆo‘££CΧ]Ω·ožžž„„„Π’E €c.\Έ@Ι’%=z4III >œ+V°~ύz\Ή‚΅΅5©©©\Έpf͚‘žžΞαΓ‡Ή{χ./_ΎΔήޞ &°fΝΦ¬Yƒ©©)Λ–-γΜ™3΄k׎'N°cΗώώϋo‚‚‚xώό9VVVΘd2233yως% 4^Ι™ύhkk‹.·oίζΒ… rνΪ5/^Œ••~~~œ;wŽR₯JρόωsΆmΫ†\.§S§NR `ΉrεπφφζτιΣ„„„P·n]ιώΧ―_ό3(Y²$ϋχο'))‰ƒ„½½=αααj­ƒM›6ΕΤΤ”)S¦πμΩ3ΒΓΓ ΰΗ€W―^θκκbkkΛΰΑƒ™6mIIIΜ›7E‹qρβEΪΆmKεΚ• Ι»Ύ B—””ΔϊυλΩ΄i̜9“ργΗSΏ~}LLL>xŽ“'O2}ϊt:wξΜάΉsyώό9΄jՊ† ŠρδZ@t1ΏCηΝٿ?»wο¦Gjϋ/_ΎΜΉsηπφφΖΧΧ•J…ΎΎ>ΊΊΊ$$$ T*qvvζςεΛ\ΌxQjαkΣ¦ «W―fΓ† Τ©S‡iΣ¦‘žžNll,¦¦¦ΐ«εsΖ9ϊψψ0mΪ4† ‚ŽŽ< |ωςθιιallLLL ‘‘‘όώϋο,]Ί”={φHη244 66&L˜ΐ΅kΧ€ξ^铝ƒƒwοήΐΙΙ ##£7Ɵ„……Q²dI:t耉‰ S¦LaκΤ©τκΥ‹7nΌσΣβίMTTήήήx{{chhΘ£G€΅²MMM©T©fffR·rωςε‰ŒŒόΈ’ ‚πΙΦ­[GσζΝ₯χgggš7oΦ3τ.ηϟg€I¬X±‚1cΖ°oί>ΆmΫFωςεY±b7fάΈq9rDŠ%<’ρJ”(AΥͺUΩ·o2™ GGG΅ύΥ«WΗΘȈ‘C‡‘‘. 4ΰ—_~‘f͚8;;³cΗΣΣ±΄΄T{½‰‰ ­Z΅bĈ`hhHzz:DEEQ£F V­ZΕΈqγX΅jΑΑΑΨΨΨ¨Ν”svv&$$///\\\pqqaλΦ­RςχϊŒγ%K–°sηN 7nœ΄ύργΗγgϞQ‘B…~μνν₯.ψ .P΅jUͺW―ΞΪ΅k‰Εέݝzυκ½υ΅ΥͺU£rεΚ,]ΊTϊήιλλκkžΎmύΣηϟcccσΑψA„OχοΏ²uλVvοήύΖΎ±cΗΎ}{Ο?ΟΧ_ύΦΧ_Ήr…±cΗ²dΙ΅ρϊxxxΰααΑ³gΟ8vμ›6mΒΧΧ—FΡ²eKάάάD7t"ΔχθΡ£C† ΑΟΟο}7fσζΝθιι‘˜˜HBBΣ¦M£I“& 0€ΰΰ`*T¨ΐΙ“'ΥΚ\Ήr…εΛ—“’’ΒΥ«Wρππ K—.Lœ8‘š5krξά98yς$ΩΩΩ 8ττt~ύυWϊυλΗΓ‡₯σ͘1ƒAƒIcC’’’ˆˆˆ`ζΜ™oΔlaaΑζΝ›IMME&“qκΤ)‘ΛεΜ™3SSSΜΝΝίHfί¦cǎόόσΟΈΈΈpψπa6n܈»»;-[ΆDOOCCCμννΥ^SͺT)V―^ΝΐΙΜΜΔίߟ%JπΧ_τQ?“°°0©eQAΘ]«W―ζϋοΏkΞβΕ‹3mΪ4¦NΚώύϋΥ†]όχΏeψπα,X°ggηw^ΓΦΦ–>}ϊΠ§Oβββ eύϊυ„……αγγ“λχ”Ϋ /_Ύ”zΡ +‘ ΎGέΊuYΉr₯Ϊ§ OOOJ”(©©)›6mβƍθιιQΉreΰUΛβΚ•+©Y³&2™ lmmpuu•šμŒŒπρρ‘>- 8ψψxΉP· BQ"“Ι˜>}:666Œ1BͺΝϋ!ΡΡΡ>|OOΟknnŽ――/S¦L!<<ƌσΕ-U«VeςδɌ1β­uy ‚γǏSΌxq\\\4JžΣΚΔηϟσΛ/Ώpχξ]’££ΡΧΧ§B… 4nܘnέΊi|Όό3ΓψKΏw¬HA’SΞGΘ§NβΠ‘C„……‘˜˜ˆ΅΅5+V€W―^j­Τ‚W …˜ΐπ‘ttt˜3gγƍcτθΡ,]Ίτƒί»+VΠ»wοχ5ύΊοΏžC‡Iγηψα‡άοΏž[·n1vμX‚‚‚ άϋypp°49΅°+Xίω°sηNƎKέΊu™5kGŽaΧ] 4ˆΨΨX<==₯’)‚ |™„„|||8~ό8?ώψ#6l 44”€€ΪΆmK@@K–,A©Tj:T‘ΛΞΞ&,,ŒΨΨXM‡’5δr9 .`άΈqjuiWDD§Nbΐ€Ÿt©S§2aΒ„7Κΐ}©1cΖ P(X²dIžχK]ΈpŒŒ š5k¦ιPς…Φ΅ κι驍½ƒWcߜœœprrβΡ£GΔΔΔ¨§ασ<ώœ~ύϊ½1Φ6gΜΐΐ€   nέΊ%-—wύϊuξίΏO·nέ4έ»ΉΉΉακꊏΖΖ:‡‡‡sλΦ-©³¨ΠΪDWWW©SΏ]»Ύsy7A>Ÿ““+V¬ΰΤ©SRmK™LVΰΊ₯„Β!++‹‡R¦L΅εBsΘεrΚ—/Οƒxόψρ5`…·322"88www–.]Κ΅kΧ6lΊΊ;;v,žžž,_Ύœ#Fδϋυƒƒƒ0`@>ε6­½Ϋΰΰ`ƌ#QΔΨCAΘ#'OžDOOOmuΡΕ,δ…œI)VVVονz=IŒŒŒΔΐΐ™L&}jΟsΎLLLŠάύλLLLX³f  33“:h:€Κ™lΣ­[7jΧ­ΆΰB^‹ŽŽζάΉsLŸ>=ίYPhνGGΗ"5XT4ΙΤΤ΅•x!/DGG£§§χQcΚutt(_Ύ€bŊζgϊτιS’’’(_Ύ|žΕ$“Ι(]Ί4/^<ίZ‘„ά3~όxΊwοNǎ±²²ϊ€ΧEff&™™™ddd••%=Ξٞσ<##ƒΩ °Z› ΊΊΊ’œœΜΥ«Wωζ›oHKK“ΖF ‚»J–,IΛ–-yπΰιιι”+Wξ­“„‚+..ŽgϞagg‡……=βΙ“'Ÿΰ‘R©xόψ1™™™8::ζy—§VVVDGGωfmTΊtiΊv튿Ώ?³gΟώθΧmήΌ™?όwwwτττΠΣΣC___zόΏ_9ϋτυυσπn 6­MwνΪΕή½{IKKγ›oΎaδΘ‘Μ˜1CmŠ ΉΗΣΣͺU«†««+;wξdΦ¬YšKψ…BAtt48::Joxφφφά»w„„ΜΜΜ4›R©δΑƒΘεr*T¨oŸ¬­­ΉΎθjΦRƒζϋοΏηΏύ/NNN<ώφνΫ¬X±‚­[·RΆlΩ|ˆ°p(} ŸαμΩ³lήΌYϊΨ―_?Μ9£Ω ‘Ίrε Ν›7gκΤ©ΤͺU‹¬¬,²²²4™π>©©©άΏΉ\NŊΥZCtttppp ::ZZηC /^Ό@₯R}qlΩΩΩ„‡‡£――OΩ²eσuVΌL&£L™2<}ϊTόk!###FυQ-ˆΙΙɌ=šΙ“'‹δπim‚(“ΙԊfFDDhΌ«D +###RRRΤΆ₯€€ˆ1\Ψ‹/xπΰΆΆΆΨΫΫΏ5344ΔΪښG}0ιS*•DFFGDDΔ%VάΏ333΅IOωιυfAϋtμΨ…BΑή{άΤ©SiΠ nnnωYα‘΅ bΛ–-ιάΉ3Χ]cΐ€œΆΆΆFGG‡˜˜˜w£R©xψπ!ϊϊϊT©RSSSξέ»Gbbβ'Η–ššJxx8666ΨΨΨ|ςλs“΅΅5YYYΔΕΕi4αΣΙd2&MšΔβΕ‹IKK{λ1ΫΆm#<<œI“&εst…ƒΦŽAtqq‘eΛ–άΈqτττˆγI!€§§3wξ\RSS‰ŠŠΒΙΙ‰§OŸj:,ᬬ¬044όθnΫ2eΚpοή=LLL066~cγǏ€^kkkŒŒŒxτθΙΙΙΨΩΩ½χZJ₯’ΤΤTRRRˆ₯t阚š~ƝεœζΤΤTM‡"|†Ϊ΅kγμμΜκΥ«ίXaεξέ»,[ΆŒΝ›7ΈrNΪBλZ ψϊϊ…™™ μΪ΅‹C‡i:>333’’’πυυ%==]Σα oaddτIcϊtuu)S¦ =B‘P¨ν{ϊτ)™™™888¨ΣΨؘJ•*‘™™IxxΈΪ8ΖΜΜLβγγyςδ χξέγφνΫΔΔΔ R©¨P‘BHsh]=@αŒ3†­[·ͺ­ΛššΚθΡ£™8q’X3ώ h] bVVΫ·oηΦ­[L›6MΪnii‰ζ„B*<<œcǎρβΕ BCCW-/ 6ŸΜ ‘βΕ‹cffΖ£G€Ι/_Ύ$)) GGΗ·Φ&”Λε”+WŽ—/_rώ}ŒIMME₯Radd„±±1φφφŸΤš)ŸΒΦΦ–Ύ}ϋ²`Α–,Yΐ΄iΣ¨W―žV,#Xi]‚h``€――/mΫΆ₯vνښG ½Ίuλ2oήi-oαέ΄2AtttD©T’œœŒ©©)'Ož€|ωςΨΩΩi:…S?ΡυλΧωρΗ₯R%ΎΎΎ˜ššŠgA„BJkD•JΕ΅kΧHJJΒΑΑ¨¨(ΜΝΝ5–P€=~ό˜νΫ·³}ϋvž?Ξƍ5’Φ°··ηζΝ›lίΎV­Z‘T*yως%zzzšMAΘZ› φμΩ“ 60~όxΞ=KΓ† 5•P-X°€Ύ}ϋβΰΰΐβΕ‹ βφνۚK+T\SSS^Ύ|I§N8}ϊ΄XjO‘ΣΪ‘ϊuλΦΕΤΤ”*Uͺ‹··7&&&šK( .^ΌΘΝ›7Y°`πjΙ°)S¦0zτhφμΩσΦεΕ„£££CǎΡΧΧ—VžhΠ ¦ΓAςˆΦΆ Nš4‰;wξ―ΦωτυυεΑƒš J(”J%sηΞeμΨ±j΅±Ϊ΄iC£FΤVδή...ŽŸ~ϊIsψψρcƌ£α¨A„Ό’΅ bRR?όππjαψAƒqωςe G%D»vνΒΐΐ€vνΪ½±oβΔ‰ά»wέ»wk 2νρχίΣ·o_i%•6mΪ ««KvvΆ†#Aς‚Φ&ˆΦΦΦώώώ,^Ό˜ϋχοηstΪγλ―ΏζπαΓ\ΈpΘΘHφοߏŽŽŽ¨')‚PHiν_woooφνΫΗή½{±··§ώ”,YRΣa ΜͺU«h€ _}υΥ;)_Ύ<γǏgτθΡμάΉS,Ρτ&&&xxxπǁ³³³θbA(Δ΄.ALIIΑΨΨCCCzτθA=€}YYY½n¨PψEEE±gΟφοίΑc;vμΘωση™={63gΞ̇贃B‘ ;;Ή\N΅jΥ¨V­š¦CAςΦ%ˆ&L`ωςε 6Œ€€$΅}ήήή΄hΡBC‘ Νόωσ8p 666uόΤ©SιΪ΅+‡ΒΝΝ-£ΣΧ―_ηϊυλT«Vωση«ν“ΙdμΩ³GC‘~ο+BP(ς1!‡R©|οΟE₯Rεc4‚·΄.A\Ύ|96lΠp$BAvφμYΒΒΒπχχθΧ²xρb<<<ψκ«―pppΘΓ΅CέΊu©[·.{χξΥp4E‡žžο=ΖΔΔ„„„©šƒw %J” 99ω½?­Φ/oΠΊ1ΗφνΫΩ²e ΙΙΙ6ΪΆm«Α¨„‚@‘P0wξ\ƍ‡ΎΎώ'½ΆJ•*x{{3zτhΆmΫ&†,όιιιψϊϊrσζM©•DGG‡£Gj8²Β©jΥͺ<&))ι^!oθκκbeeE©R₯4Š δ­M>ΜφνΫΕ„α Ϋ·oΗάܜ֭[Φλ{τθΑ… XΈp‘X³ω;{φ,Υ«W€Y!o©T*τυυ±··Χt(…^xxΈ¦C„|§΅ ’……Ε'· …_bb"+V¬`νΪ΅_tž3fΠ₯K4h Ζ΅666DFFj: αθθθˆυ°σL&Σt‚ο΄.A\΄hIIIdeeρΓ?P«V-iάGǎ©S§Ž†#4iωςεΈΊΊ~Tέϋ˜˜˜°hΡ"† F5€ΡEΝύϋχΩ΄i2™ŒkΧqι%ι{!“ΙπσσΣp„‚ B^ΠΊ±AƒdddΌu_Q}^‰ˆˆΰΐ₯Fb–w••E||Ό¦Γ(τ”J₯¦C„|§u bŽωσησίώ—κΥ«Η­[·Ψ±cΕ‹Χth‚œ:uŠHerΣάΉsιάΉ3 4 Q£FΉ~~m‘‘A§N¨R₯ ΆΆΆμή½›₯K3mΪ4M‡Vd+V ===5J‘WΌxqQΒF(r΄6AΌsη7n”ž>|˜?ώψƒ.]Ίh0*A²³³™?>&LΘ“²4ζζζ,\Έ1cΖ°vνZ,,,ΠΣΣ“ΎŠΒφS§NΡ»wozχξ-m6l˜X½Hƒ E+ yFkDGGGbcc±΄΄D‘PpηΞ1Ϋ΄ˆΪ²e %K–ΔΥΥ5ΟQ―^=ϊυλ‡——™™™ddd™™Ivv6ΊΊΊθλλ«%―?Χ>}}}©H__ŸφνΫΨ:kUͺTαΦ­[¨T*d2‰‰‰Θεr‘ ‚ RZ› ώσΟ?|ϋν·ΨΩΩρβΕ τττ8uκ}ϊτ-‰ED||<όϊλ―y~-<<<ΤΆ©T*233₯―œΔ1ηίέώ}7nάΰργΗvθηϟ³{χnΆn݊ΉΉ9=ΒΑΑN:°~ύzΜΜΜ4₯ ‚[΄6A\Ύ|ΉΪ¬””βγγ±··υ‹eΛ–Ρ¦M*Uͺ€‘λΛd2τυυΏψw.))‰Φ­[γννMΙ’%s)ΊάS³fM<¨6+,,ŒΚ•+ˆ±ΏŸ!33S¬„R„dggΦλ222ΔοIR&Dim‚¨P(Ψ»w/Ϟ= 22’φνΫqύ;A{ά»wΓ‡σϋοΏk:”/fbbBηΝY·n'NΤt8oΠΣΣγΦ­[„††’••…R©δόωσμΫ·―H–ωRμέ»W¬o]Δθκ~Ϊ[±±1+WdεΚ•y‘P” QZ› N›6Ϊ΅k†««+qqqbζ"fξάΉ 2sssM‡’+ @ϋφν δ==}ϊ”ωσηΣ A’’’Λεxxxˆδπ35ŠQ£Fi: ‘€σχχΛ[ S0ΤΟδαၭ­-}ϋφ₯uλ֜]]]ΊwοΞ]»xπΰAž–9ω‘‘‘xyyαηηGJJ gΜΡtHZ+33“‘#G2pΰ@κΧ――ιpr]ι₯iΤ¨Ϋ·oΧt(jΚ–-‹§§'Ŋ£m̟ۢ?777QζF‘Ϊ1ˆgϞ₯}ϋφΐ«UU ͺGαααΑΈqγhΥͺzzz,]Ί”ƍk:4­4sζLΚ”)Γ€4JžρςςΒΛΛ‹ή½{˜ε#ΓΓΓ)Q’ϊϊϊ΄mΫVŒχA(δ΄ΆqίΎ}<~όXΣaΌΧ³gΟpww租~ΒΝΝ €ζΝ›#“Ι ΥptΪgηΝܼy“Y³fi:”|Xj}Φ€―ΎϊŠ;v˜ϊ\Ϊ.44”ύϋχk: A  αΓ‡S±bEM‡‘ bll,&&&¬ΰ&MšπΝ7ίh2,ρππΐΝ͍ΎσΈ‘#G2eΚZ·n] Zˆ *…BΑ¨Q£θή½{ψωζ§Aƒ±`Α‚‘ ΖΖΖbggΗ₯K—8vμήήήŽJ{]Ίt‰K—.ΡΌysM‡"B²eΛ:uκ$ΔΟƒ§§'AAA˜™™qρβEΌ½½ρχχ§vνΪRβ¨ ΙΙΙxzz΄iS†ϊήcλΦ­Kι₯ΩΏΏ΄ž­π¦ωσηcffΖΰΑƒ5JΎkΤ¨ŊγδΙ“4kΦLcqμΩ³‡γǏΐνΫ·ΡΥΥΕΜΜ ¦Nͺ±Έ΄““ύϊυΣt‚  ǏΧt­λ/:yς$}ϊτ‘T©R”(Q‚-ZΰζζΖί­±Έ2duκΤaτθΡυš‘#G²rεΚΟ^£³°ΫΏ?gΜaώόωEv¬ζ Aƒ h {χξU«ΰδδΔ AƒΈwοž£Aς’Φ%ˆχοί§zυκΐ«1‡ξξξ”*UJc³š3226l+VΔΧΧχ£_W£F ͺV­ΚΝ;σ0:νtηΞ,X@@@ΖΖƚGcZ΄hABB—/_ΦX ΩΩΩ―†rΤ¬Y€bŊ‘žž±ΈA„Ό£u ’΅΅΅Τ+—Λ₯z‚όρUͺTΙχx²²²1b%K–ΔΟΟο“_?bΔ‚ƒƒΙΘΘΘƒθ΄S||<#FŒ`ϊτιT¨PAΣαh”ŽŽŽ4€BSώύχ_’££pttΔήޞΈΈ8^ΌxQ`&† ‚ ΉKλD777φοίΟφνΫyώό9‘‘‘%΅lδ…BΑ˜1c066fφμΩŸΥ Z©R%κΧ―ΟΆmΫς Bν£T*;v,νΪ΅£E‹š§@hίΎ=αααάΎ}[#Χο₯ Σ¦Mγ―Ώώ"55•Σ§O3eΚ:tθ ‘xA„Ό§u b©R₯X΄h‡’]»vxzzƚ5kΠΧΧΟ·8”J%γǏG©T²pαΒ/š‰μννΝΪ΅kσtύ]₯RΙπαΓyπΰAž]#7ψϋϋ#—Λ>|Έ¦C)0tuuqwwgυκΥΉΎ»»;_ύ5sζΜα›oΎaΩ²e8;;γεε₯‘xA„Ό§u³˜―^½J:uΨ°aΓ[χ§€€πδΙ*Uͺ”g1¨T*¦L™Bbb"+Wόβ25eΛ–₯iΣ¦lάΈ1ΟfλnΩ²…;wξΰξξΞƍ±··Ο“λ|‰?ώψƒ#GްsηNQoοtνΪ•ΐΐ@€tιβ• MλώZ„††2zτhξίΏR©”Ά§€€Β Aƒς|<ίΜ™3‰ŽŽ& €bŊεΚ9ϊι'6nάHRRRœοuOŸ>eŊαεεΕΐ‰‰‰Ιυλ|‰ππpfΜɲeΛ055Υt8ށ}ϊτa͚5ωz]Ή\Ž6l ..NmίΣ§O™;w.kΦ¬΅<σΘ… 6l6l`εΚ•τμΩ“ί~ϋ €ήωΪ΅kΧ’––ΐΐIII!==΅kΧζKμ‚ h7­kAτρραΪ΅k̟?Ÿ`bbBjj*zzz4i„Υ«WK3.σΒΌyσΈsηkΧΝΥϊvvv΄iΣ†uλΦ1bĈ\;/€ŸŸύϊυΓΡΡGGGpwwgΓ† XZZζκ΅>GRRήήήψϊϊjd’‘Άθέ»7­[·ζΩ³gΨΪΪζΛ5mmmY·nλΧ―gΠ A$$$`nnΞΏώ‹­­- ΰ»οΎΛ—XŠͺZ΅j1mΪ4ΰUΑς>}ϊΌ1ώσΚ•+9rgggš5kΖΥ«WΩ΄iΡΡΡLž©œ—’ΛεR³R©$!!!Χ~?…O“‘‘±±±Ϊ„Έœeϋ&NœΘΎ}ϋΈzυ*ήήήθιιΡΏ΅ΦέΊuλK“&M8xπ .\`δΘ‘¬[·ŽππpzχξMpp0ΎΎΎtξά™ΑƒσΣO?Ρ³gO|||Ψ»w―&n[ ΠΚΰΕ‹œ9s†/^Hۚ6mšg-P?~œ 6δΩj-ΦΦΦtμΨ‘5kΦ0nάΈ/>_\\ , 00πϊ§Ÿ~"-- ///~ωεΥ\Ήr%©©©ψψψhδϊΪfΐ€΄k׎!C†δ{’vγΖ ]»Fff¦΄ΝΣΣ3_c(ŠrV‹ΚΚΚ";;›1cΖ¨ν?uκ;wΖΞΎž={2pΰ@|||Λε”(QBνΨβΕ‹c``@ρβΕ9yς$ΆΆΆ„††bbbΒΩ³gιέ»7ΊΊΊόπΓΘεrd2]»vE__Ÿδδd …N E„֍AΜ1zτh’’’011‘Ύrk<ΰΪ±cΰ—_~ΑΜΜ,O‘ΓΣΣ“ί~ϋM-ρύ\³gΟ¦C‡899½uΏNNN 2D#Ož<ɞ={XΌx±xΣωH–––΄mΫ–7ζλuοܹÌ3P(/^œβΕ‹ktYΛ’€N:̝;Φ]K£FΤφ'''KΓ]tuu?z vzz:φφφΨΫΫγδδ$-:`ll,ύ400ͺCˆ .‚P΄hm ’©©)#GŽΜ—k5mΪ”f͚εΛx= ~όρGωωηŸ?ϋ<'NœΰΏ―½;‹ΊΞ8ώšnΉUΌΡ]“„δΧaZaύΦtΝΦΫΦ4WΛΦRΧL³΅ΤΌ]iΩαXvz€΅y<Δ3Ν΅υ‚ΌAŽafζχΗ8£β οηγ1™™οχϋy>Όηs9ΒτιΣoxά€I“˜4i£GfαΒ…56υ·ί~γ7ή`ρβΕΧ΄rˆ6l}ϋφeΨ°a5Φς{ρβEϊφνKίΎ}k€<ρ;OOΟ&γ=τ[·nεψί½cίnŒF£ΣΉwΏILLδ₯Kτλ׏γǏsζΜ™κ½!„[qۏ„=zτ`€I€§§³aΓ6lΨ@vvv΅”IDDD΅\ϋz† Β¦M›8ώό―Σι˜:u*S§N½ιD…BΑ΄iΣ $55³Ω|GeήƒΑΐθΡ£7n\₯­›’r7ζoϋ[ξ᝔”āX°`ddd°aΓ†+ΏΎ ͺt7‘ΨΨX|||xόρΗ‰‰‰aόψρœ;wΞ1ήΈώΌφΪkΠΎ}{<<oίΎ}|ύχlάΈρΆΚS©TΌϋ5Š)S¦0mΪ΄*IΞ=ΛζΝ›ΩΌy3ΏύφΙΙɌ3Ζ±‡Άpyyy }:“&MΊνkœ:uŠM›6±iΣ&ςσσyτΡG;v,:u’Ι(nκώϋο'-- Nη΄ μ—-„u“Ϋ&ˆ`Ϋ_xίΎ}Žηaaau&AψӟώĊ+8~όψ-mΈpαBZ·n}W΄}||XΌx1C‡eφμٌ?ώ–ΞϋΟώΓζΝ›Ω΄i₯₯₯<φΨcL™2…φνΫKWdpωςe7nΜΉsη8wξœγuI…’nrΫρ•W^‘€€„””„Α`ΐΧΧΧΥaU)oooFŒΑΌyσxύχoxμ±cΗHOOwlΓu7όύύYΆlƒ ΒΟϏ‘#G^sŒΥjεΠ‘ClΪ΄‰Ν›7γεεEχξέ™5kνΪ΅»λDνΛoΌAVVF£‘˜˜Ω{Y!κ0·MΧ­[GFFƒ€€$ƎΛ?ώρΫ‚¬¦τξέ›εΛ—σΛ/ΏpΟ=χ\χ³ΩΜ€I“?~|•-ΕΘςεΛ8p ΎΎΎ <³ΩLff&›7ofΛ–-Σ½{w/^L‹-ͺ€\Q{ >₯RIΫΆmyψα‡Y»vνM’a6›9Bά₯RιX΄>qΫqηΝ|όρǎ]Ȏ;κά zFΕΌyσœfV΄bΕ 4 ϋΏ[₯eσχ 0€ΜΜLωδL&S΅-P/ͺVII —.]’/!nƒΥjE©T΄iSW‡Rγά6AT(X,ΗσS§NUΫ6{–’’Β²eΛΨΏ?ρρρNούφΫo,_Ύœ΅kΧVKΩααα|πΑlΫΆΙ“'ΧΉZqkόόόΠιtN―ιt:I6άL@@€ό qŒF#/^tu.αΆ βc=ΖψGŠ‹‹ϊ(§OŸΖΗΗ‡‰'sέ²Φ¬YSi—εϊυλ±Z­τλΧο†ΗέΜΌyσxζ™g0Lš4‰μμlbcc9}ϊ4S¦L‘uλΦ·}Ν’’">ώψc:wξ|G1νή½›]»vρΚ+―άρ½:u ­VΛγ?~G1άΜΙ“'Ω²e —.]bλΦ­€νKBBΒMχϊ΅‡ΥjE§ΣqφμYW‡"„ΫΈΫwζv ’=ΡθΩ³g₯{”Φe/Ώό²c9›χή{―ΪΛkίΎ½#_Ύ|9[ΆlqΌg4ωςΛ/9~ό8 tι…ώσŸ„‡‡3yςdš6mκ8χΰΑƒŒ;–Ο?œ“'O’‘‘B‘ OŸ>δεε±xρbΚΛΛyφΩgYΊt)Z­–]»ςΠC9Κ[½z5+V¬ Q£FΔΖΖ²~ύzŒF#)))4k֌₯K—’V«9tθ `ύϊυσK/qςδIŒF#mΫΆeι₯4i„ώσŸ\Έp_ύ•Φ­[³dΙόόόhΩ²% 4`γƍxxxΠ―_?"##YΈp!J₯’ΒΒBžώyΌ½½1™L,X°­Vː!C0deeΡ];ΆlΩΒΝ;ιΠ‘)))lΫΆ;vΰοοο΄ΎδφνΫYΌx1~~~tλ֍ŒŒ rssιΡ£χή{/K—.%**Κ1φoλΦ­X,ƌΓsΟ=Η AƒxτΡG«e§šψψxfΜ‰Υj• nΜj΅b6›₯ΥWˆΫ`΅Zλνd<·K7mΪΔΆmΫ[ΒPΡ³Ο>Kǎ]VIHH uλΦ€¦¦Φθmaa!‡&!!Λ—/πΝ7ί`4™0acǎ₯]»vΔΗΗ“œœŒΏΏΏΣωqqq( ._ΎΜ‘C‡1b,\Έ™3gFώύ9zτ(<π:ubθΠ‘<ψΰƒŽktνΪ•ΜΜLyύυΧ1b‘‘‘€¦¦²dΙΣΣyύχ c€I|ϊι§€₯₯‘ΝώύϋιΠ‘{χξeάΈq€­‹788ΨQΞχίΟ_ώςX³f £G&;;›eΛ–1eΚV¬XΑ·ί~K^^sηΞeΒ„ >|˜·ήz‹#Gް~ύzψυΧ_iΣ¦ ?όπS§NeΙ’%h΅Z–/_ΞͺU«8vμgΜqά[rr2‹-β©§žbώόωtκΤ‰~ύϊ1jΤ(–-[ΖΝ;Ψ»Σώυ―πτΣOΆδ°aΓ†¨Υκλ~Ώ«Β‘C‡θΫ·//^Δj΅2qβD&Mšδ£¨ύ C½O%ĝ(//wϊTŸΈm‚h΅Z9xπ Z­–¦M›’™™IPP«Γͺ—μΙ‘έ­Μ€΅o|^ρΟ~^Ε„γκ_Μ«Χ|ΌΥ‰:^^^Œ1‚―ΎϊΚiΏξ›-Σruœφ1~·3[ψκ++S‘P8]χV―Ο?ό¦-¨w«qγΖ|ρΕ?~œξέ»c±XΘΛΛ“δΠΕ 9997<ΖΣΣ“¨¨(, V«•’’’ŠNˆΊA‘P Σιπχχηόωσ7\»6 €°°°ŒϊΈm‚ψάsΟρα‡2aΒΐΆXpΕu…ΈZ||ό5{YίΚΖcΊψργ«½ŒΦ­[ΘωσηωΛ_ώΒO?ύDbbb΅—+nΜήβ\ΩΜd“Ιδ˜Θ€P(πρρqŒiBܚK—.9ΦC4™L„††:*ιt †š―ΪΈ]‚hΟβΫΆmλ›₯ΥjIKK“E{…¨bf³™ςςrT*•c?n“ΙD‡;ΑFΤ<₯RyΛ-Ή*•κΊΨ„•»^ΟΥυ~ηξvW¬ΪΖνΔΧ^{ωση3jΤ¨kΦf=z4<ςˆ‹"’ξ9tθ‡’mΫΆΧ΄ž* Φ―_ο’Θ„BT'·Kηϟΐ‡~xΝ{f³Ή¦Γ’N«Ψ-Ÿ‘‘ατžόΎ !Dέ₯Όω!΅Σ˜1c8ώ<`λςš={ΆΣ"ΕBˆͺσΓ?°hΡ"GR˜••Ε Aƒ\•Bˆκβv-ˆvΓ‡ηΥW_₯{χξlέΊ•””ΗNBˆͺ•˜˜Θ‰'>|8‰‰‰ότΣO·΄>£BχδΆ-ˆχί?ϊӟXΎ|9 6$%%ΕΥ! Qg©T*ϊφν‹ΏΏ?«V­bΐ€ΥΊ0·BΧrΫqμΨ±όςΛ/lήΌ™Gy„ΑƒsβΔ W‡%DtβΔ  DŸ>}ψϊλ―ωχΏΝk―½ζκ°„BT·νb2dˆc·Šξέ»ΝΩ³giΩ²₯‹#’ξρςςβ_ϊΑΑΑόύοηƒ>pqTB!ͺ‹ΫΆ ΖΕΕa΅ZΩ±ccƌαε—_&00ΠΥa Q'5mΪ”ΰΰ`Š‹‹ω裏θΥ«»vνruXB!ͺ‰[Ά ζηη³~ύzΎψβ T*O<ρsηΞ­·ϋ% Qέ<ΘκΥ«9|ψ0:ŽO?ύ΄έ;„BΈ?·kA\»v-#GŽ$ €O>ω„‘C‡¨Q#I…¨&δ“O>α駟fγƍ΄lΩR’C!„¨γά.AŒŠŠB₯RqπΰAŽ=ŠΥjuuHBΤiχί?§Nβΐ\ΌxΡΥα!ͺAII &“ΙΥaˆZΔνΊ˜HHHΰΐ|φΩgόψγ<όπΓtξά™ππpW‡'DσΚ+―`4ωκ«―;v,'OždΣ¦M<όπΓxxΈ]"„¨@§Σqι% εεεx{{Σ Aόύύ]šp1·kA΄λΠ‘oΏύ6ί|σ -[ΆdθΠ‘lΩ²ΕΥa Q'ωψψΠ»woV―^ΝΚ•+ωρΗιΩ³§«ΓBά!£ΡΘ™3gΘΝΝ%44”θθhš7oŽF£!??ŸS§NqωςeΩR³sϋAAA 2„Αƒ£Χλ]Žuή=χάΓ›oΎ‰V«uu(BˆΫd2™Έt郁ΠΠPcψ j΅΅ZMii)œ>}š€€‚‚‚πρρqqτ’&Ή}‚h§P(€I\ˆ€V«]‚β™ΝfςςςΠj΅Σ°aΓNξτφφ&22³ΩLqq1.\@©Tδ”TŠΊ«Ξ$ˆB!„pf±X(((   FC³fΝP©T·|ΎJ₯’Aƒ4hНNGaa!—.]B£Ρ„§§g5F/\ID!„’ޱZ­‘ŸŸŸŸΡΡΡwΜωϋϋγοοΙd’°°μμl|||hΠ ~~~²X‡H‚(„BΤ!Z­–ΌΌ<<==‰ŠŠΒΫΫ»J―οιιIXX‘‘‘“——Gii©γ}«Υκ”(ήμλ[9ΦΟϏΠΠP”J·[λv$ABQ#¬V+f³Y–Gͺ&z½ή±dMDD„£U―Ί( 4 ζΊοW\§ψf_ίθ}«ΥJAAYYYDFFVϋ} ω-BQm¬V+z½­VKII V«•   BBB€5¨ Ψ'‘a΅Z ­5Θ*k Ό 6D§Σ‘““ƒŸŸαααςσSΝ$ABQ₯, :Ž’’t:^^^R©$//¬¬,Η2+βφιυz Ρλυψϋϋ^η[Φόύύ‰‰‰α₯KdeeN@@€«Γͺ³$ABqΧΜf3:­V‹^―ΗΗΗ΅ZMXXΨ5]ʍFrss)**"<<ΌΚΗΙΥEεεεQTTδXr&22²^΅€)•J"""Πλυ\Όx­VKxxψmΝΜ·FD!„wΔl6;ΊŽ ~~~yΣ?Ψ>>>4mΪ”’’"Ξ=‹Z­±IWO’¨Ν¬V+%%%a4Q«Υ4jΤ¨ή/ZνηηGLLŒ£5:<<ΌΦt­Χ’ !„Έe&“‰’’΄Z-₯₯₯ψϋϋ£ΡhhΤ¨Ρ%w†€€ςςς8}ϊ4aaaΥΦνl2™Έ|ω2ΕΕΕx{{£V« ¨•kω•••QTTDqq1^^^h47nμ6‰mMP(„……‘V«ΙΙΙ‘ΈΈ˜ˆˆ™UEδ»(„β†ΚΚΚ-…&“‰€€‚ƒƒρχχ―’„E₯RF£!77—ΒΒB"""ͺ¬ΫΉ¬¬ŒΛ—/SRRBPP͚5£΄΄­VK~~>žžž V«ρςςͺ’2ο„ΕbA«ΥRTT„Ιd"00&MšΈ4&wΰγγCtt4ωωωdggZιΜjqλ$ABQ)«ΥΚωσηρυυ%,, __ίjkΕͺκnηRςσσ1 ΡΌysΗ΅<<<πχχΗj΅b0Πj΅œ9s•JεhY¬‰q‘‹…RŠŠŠ())ΑΧΧ·J“οϊB‘P8fpηδδPZZJxxΈ«Γrk’ !„¨”B‘ &&¦FΛΌΊΫωv[„ŒF#ωωω”–– Aƒξ;¬P(πσσΓΟϏˆˆ %%%œ;w…BαHow̟Εb‘ΌΌάρ0›ΝNΟν°-<­V«‰‰‰‘ξΡ»δννMΣ¦M1›ΝΕνΙO’BˆZηκngϋlη%jz½žόό|ΚΛΛ ¦Q£F·έ ηλλλh-΅wCηδδ`±XΙ’J₯Ί₯ΔΟΓΓγš‡*•ΚρΌ>Ν@) …Bν* ίA!„΅VΕnηsηΞ@hh¨Σ,iNG~~>‹…ΰΰ`Τju•tΟz{{γννMhh¨cfnn.‹E?QηI‚(„’Φ«Ψνl_d[©T’ŸŸR©$88ΈZMφςς"$$„j+CˆΪDD!„nακng…BQ/vΒ$ABαVμέΞBˆκS«ΔΌΌ—•-„BT5IέΨΐyο½χnxŒΑ`ΰΠ‘C•Ύ?sζL~ψα6oή|Χ1egg3wξ\|||())aΪ΄iτθΡƒ?ωΟ 2„ΜΜΜ».cώόωΧ쎣Χλo;Aœ5kΫ·oξώήχμΩΓ† ξκB!DmQο&©Τϋφν#::šmΫΆρK/αεεΕΙ“'Q©TΔΔΔPVVΖΎ}ϋˆ‹‹£]»v€-YΜΜΜD©TŸŸ%%%”––rμΨ1ζΜ™C@@ X,vνΪEtt4QQQμίΏŸ˜˜φμΩC«V­hΤ¨;vμ mΫΆ4nά€όγLš4 €΄΄4BBBX³f dee1kΦ,ξΏ~ 999¬Y3BBB8tθEEE$$$ΰλλK^^EEE Š‹‹IHH@‘PZΘ… [IDATΆΓ­[·E›6mπυυ₯mΫΆŽοOVVηΝγΐΓΓ£ΡȞ={P(tμΨ???΄Zm₯χΎ{χnš6mκtοΡΡΡdff:έ{ll,QQQ >œAƒ‘””DHHHύa08}ϊτuί³Z­Žί°}ΚΙΙ©©Π„¨ŒF#j΅°- vώόy§ί+;‹Ε‚ŸŸ_M‡Wm$AtS«W―¦ώ¨ΥjΎϋξ;RRRπχχη―ύ+}τK—.%<<œΖσξ»οςρΗ3rδHž~ϊiτz=ιιιN­F£‘όό|ΚΚΚΠλυ 2„>}ϊπΣO?αεεΕΈqγX°`‘‘‘ΔΗΗ3jΤ(zθ!bcc:t(θυzJJJhΦ¬z½žoΎω†ό‘ΐΐ@bbbXΈp!ǎ#--:0|ψpfΜ‰··7­Z΅’ώ€§§sτθQf̘ΑsΟ=G~~>Ÿ|ς σηΟ`Ŋ$''3sζL^|ρE"##yο½χX΅jοΎϋ.F£‘˜˜V¬XΑ²eΛxρΕιΥ«F£‘ττtζΜ™γΈχ²²2ςςς(++Γ`00xπ`Η½{xxššΚ’E‹ £cǎNχ>lΨ0Φ―_ΏΏ?;wfηΝ€€€ΤΰO‚¨Ο|}}‰ŽŽΎα1φ?d*• L&SM„&Dαλ닏 6ΌαZΚJeέι˜•Ρ pόψqβββΠh4όύοwμ£;pΰ@^ύuΚΚΚ3f Ώύφf³™ .HϞ=yώωηyί}χαλλK·nέΨΊu+±±±ΔΗΗΣ‘CƏΟΈqγP©TτμΩ“ΔΔDΆmΫF§NxόρΗΩ»w/GŽΑl6ΣͺU+rssi€‰£5mΧ]9r€δδd εεεL™2€1cΖ P(8yς$ώώώ=z₯RITT {χξŽ?n½zυ"99³ΩΜξέ»yζ™gχ²mΫ66nά@||<&“ΙqοO<ρύϋχwΊχ{ξΉΗqοί=mΪ΄qΊχΤΤT”J%=zτΰ‘‡bΫΆmΔΗΗΣ³gOφνΫΗαΓ‡ι₯ ­Z΅βΧ_­k!nD©TβεεuKΗZ,¬V«γ›βζL&z½ή±%©ύίϊ@D7΄~ύzŠ‹‹yα…8|ψ0§OŸ¦Y³ftλ֍·ήz‹±cΗ:£R©XΆlλΦ­γύχί瑇"55υΊΧ?uκgϞ%##€€€$t:5ΐΫΫ›ΘΘH|||(--%//ΟρZ“&MΈxρ"yyy„††BσζΝωκ«―§Y³f΄hΡΒQζ{g‡‡Ν›7Ηd2QVVΨφδΆ $//Ο)OOO§ 1Z­Φι^bccXΆlιιι̟?Ÿ€€$Əέ{?}ϊ4ηΝ»ξ½Ϋ»Ρ}||hΨ°‘γk{¬‘‘‘όψγΧ½΅———l7*Δm0ΧLЬ/κN[h=a΅ZYΏ~=kΦ¬aΩ²e,[ΆŒ©S§²vνZΐΆ3ΖμΩ³ωβ‹/œΖ•””pι%RSSΙΘΘΰλ―Ώ¦΄΄ΤιΪ‹€x€ΘΘHƏΟψργIJJΒίί¦±ΕΔЕ•ΨGy„+V`4iέΊ5ΙΙΙ(•JG——½)Ύ΄΄”έ»wσΖoΠ»woŠŠŠ±Ψ[δ¬V+:Ξ‘˜UF­Vγεεεψ…ž3g—/_ζ₯KŒ7ŽŒŒ Ύύφ[§€R‘P`6›θάΉ3·}ο`χx³ξ>!„ΒH ’›ΩΉs§cR‡]χξέ™?>±±±¨Υj|πAxύυΧIKKl;Θ|τΡG¬Y³°uΡz{{;‘R©ˆŠŠbΒ„ ̜9“7’ššŠΙdβψ;wΎil-[ΆδψργŽη3fΜ`Ϊ΄iτξέ›¨¨(JJJHJJβ‰'žΰθΡ£ŽγΌ½½iΣ¦ γǏΗίߟ?ώρ,Z΄ˆΑƒΔΘ‘#)++st5ίΜΘ‘#7n^^^ΔΖΖΔͺU«χώδ“O:Ζ“€-QŽŽζΥW_eΦ¬Y8ξ½cǎ·tούοιΤ©Σ-+„BΤf lIβ9 |σ7 »ν‹Μ˜5‘»ΖσςΛ/Wu|UκΨ±c<υΤS|ωε—ΕeL&J₯•JuΝ{V«•RGςT^^ŽR©Ό­A·oΌρ)))tιΕιu£Ρθ””]Oii©#i5›ΝμΪ΅‹-[Ά––†ΙdΊν±εεεxxόώ¨:ο½°°#F°rεΚ›ήgm»wo>ώψcβββ\ΚMνή½›_ΝΛ>Ώ₯γ΅ΪbΊ=ΞρΘ²_€ƒΐQΰ7 0ζ+Η~τρζοώτmΗωέ¦ΟΩπΦ¬]]ι13gΞδΤ©S•σ¨EEEθt:ΒΓΓk¬L!ܝ}ψTMυ 2„3fœœ|Σc½½}ψρϋ£xyyίτX€~ΟwηΏΗ”zlubΆ:ρΏΧ‰…€QZλ™%Y …Β)Ή©˜\έͺ &πK/Ρ±cG§²n%iΊΊE³’;|uόΥyο³gΟ&55Υ­’CQΏ(•J ΩΩΩE·R_λuIE•h4¬Z΅ͺJ•˜˜Hbbb•\«ΊM›6ΝΥ!qCj΅Ϊ±–›B܌LRB!„Nκ] bvv6=φ˜«Γ’VΈxρ’«CBQ Υ«±E‹N³l…Ώ―))jΦgŸ}Ζ7ί|γκ0„΅Hnn«Cp¨W ’§§'111CQύ9’~ύϊΉ: !D-dίpΒΥκU‚(„΅F£A£ΡΈ: !„¨”LRB!„N$AB!„Nͺ¬‹ωΫoΏ₯°°°ͺ.'„NΞ=λκ„’ή¨’1ρΑG8ϊŸŸΙ9―­ŠΛ !Δ5<”z=%;„’&TI‚˜”ψ(I‰VΕ₯„B!„‹Ι,f!„¨bΖR#999C!ξ˜$ˆBQ…Ό½}Ψ΅kχέΧήΥ‘!κ°@u  ¨ΆλK‚(„U(ΉΫγ$w{άΥa!Δ]‘en„B!„I…B!„ιbBˆλ0 h΅ΕC!ͺ„ΕbΎ­γΨ’Δs@ψ:>ˆ‡§guΔ%„5Κl6³'σ'ϋΣb Θ~Gί€ΐΨkΟΟ€Ύ5¬BԜr@­NΜβΪ:±0:΅ fξΫY³! !DΝ°\yX―z\[B©ΈςP]y(―<μ― !„;ΊΊΌn}hO‡ €0  4Β5ΰ…­‚”JQαN¬ΨZK²+ς+―U–$ΎόΒVΆΪν€h πΑVJ(„p'Vlu ½4sν‡gΰχρ h ΄ΖΦέbΉr’© …ξΙ^–Z@ψ=Q΄WŒΧ;ΟzεύrΐtεΖ++R' !܏½N΄ΧeeΨκ7{’h?~―ΝWΤcλΞΓΦrhΌ‘D!„ϋ©X·Ωλ΅"l‰b•·$Ϊ“C{b¨ΓΦν\pε}©…ξΘ^'±ΥgΕΨκ7#WυΨ[νŸ’ Ψ*Ρ‹€η•ηκ+_«j.~!„¨2fl‰^ Άρ"Ά$ΡΐרּUόΠloyΜΗVZ―<·»Bwc―ۊΰ2ΆϊΡ>YΟ©Ρ^κ―¨Ίrrΰ{ε8Y3QαŽ*~.ΖV―εsU+’Υj΅* Ξ­‡zlŸ²/\ΉV1ΰ­N”QαŽμ9Ÿ[Ξw[=gΈςΊ…+-ˆWW†Jl•©[r(T„ξ¬β‡`ΆOΚΕΨκ;GeΆ,ρJ’hΑ–<Ϊ+P|€mLΆ½N”zQαNμ=$Ηfςϋ‡f§œ-Z*œdοZ–ŠPα*v›°%~φΑΩεT>ώΠΎVWŽ-Ζ6φΠή£"u’Β]=ΖZ_αaOQX­V|bV`«τT8―ϋ%Ι‘ΒέUœ•lζͺε¬V«S’x₯NTbK=°υ€ΨΏ–δPαξ^₯Αώ0«ΥjUΨλΕ+"8'…R !κ’k‡½:9„kκʏŠυ’ԏBwd­π―=Q¬ψΐ)A΄«P1‚T€BˆΊΕQα]/1ΌZ…ήσ―BΈ3λΥV¬ZšiMΆυ4‹IENDB`‚pyqtgraph-pyqtgraph-0.12.4/doc/source/images/plottingClasses.svg000066400000000000000000000612341421045507400250530ustar00rootroot00000000000000 image/svg+xml PlotWidget(GraphicsView) PlotItem(GraphicsItem) ViewBox(GraphicsItem) AxisItem(GraphicsItem) AxisItem(GraphicsItem) AxisItem(GraphicsItem) AxisItem(GraphicsItem) Title - LabelItem(GraphicsItem) PlotDataItem(GraphicsItem) GraphicsLayoutWidget(GraphicsView) GraphicsLayout(GraphicsItem) PlotItem ViewBox PlotItem pyqtgraph-pyqtgraph-0.12.4/doc/source/index.rst000066400000000000000000000013101421045507400215350ustar00rootroot00000000000000.. pyqtgraph documentation master file, created by sphinx-quickstart on Fri Nov 18 19:33:12 2011. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to the documentation for pyqtgraph ========================================== Contents: .. toctree:: :maxdepth: 2 introduction mouse_interaction how_to_use installation qtcrashcourse plotting images 3dgraphics style region_of_interest exporting prototyping parametertree/index flowchart/index internals apireference Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pyqtgraph-pyqtgraph-0.12.4/doc/source/installation.rst000066400000000000000000000034251421045507400231400ustar00rootroot00000000000000Installation ============ PyQtGraph depends on: * Python 3.7+ * A Qt library such as PyQt5, or PySide2 * numpy The easiest way to meet these dependencies is with ``pip`` or with a scientific python distribution like Anaconda. There are many different ways to install pyqtgraph, depending on your needs: pip --- The most common way to install pyqtgraph is with pip:: $ pip install pyqtgraph Some users may need to call ``pip3`` instead. This method should work on all platforms. conda ----- pyqtgraph is on the default Anaconda channel:: $ conda install pyqtgraph It is also available in the conda-forge channel:: $ conda install -c conda-forge pyqtgraph From Source ----------- To get access to the very latest features and bugfixes you have three choices: 1. Clone pyqtgraph from github:: $ git clone https://github.com/pyqtgraph/pyqtgraph $ cd pyqtgraph Now you can install pyqtgraph from the source:: $ pip install . 2. Directly install from GitHub repo:: $ pip install git+git://github.com/pyqtgraph/pyqtgraph.git@master You can change ``master`` of the above command to the branch name or the commit you prefer. 3. You can simply place the pyqtgraph folder someplace importable, such as inside the root of another project. PyQtGraph does not need to be "built" or compiled in any way. Other Packages -------------- Packages for pyqtgraph are also available in a few other forms: * **Debian, Ubuntu, and similar Linux:** Use ``apt install python-pyqtgraph`` or download the .deb file linked at the top of the pyqtgraph web page. * **Arch Linux:** https://www.archlinux.org/packages/community/any/python-pyqtgraph/ * **Windows:** Download and run the .exe installer file linked at the top of the pyqtgraph web page: http://pyqtgraph.org pyqtgraph-pyqtgraph-0.12.4/doc/source/internals.rst000066400000000000000000000002631421045507400224330ustar00rootroot00000000000000Internals - Extensions to Qt's GraphicsView =========================================== * GraphicsView * GraphicsScene (mouse events) * GraphicsObject * GraphicsWidget * ViewBox pyqtgraph-pyqtgraph-0.12.4/doc/source/introduction.rst000066400000000000000000000063631421045507400231640ustar00rootroot00000000000000Introduction ============ What is pyqtgraph? ------------------ PyQtGraph is a graphics and user interface library for Python that provides functionality commonly required in engineering and science applications. Its primary goals are 1) to provide fast, interactive graphics for displaying data (plots, video, etc.) and 2) to provide tools to aid in rapid application development (for example, property trees such as used in Qt Designer). PyQtGraph makes heavy use of the Qt GUI platform (via PyQt or PySide) for its high-performance graphics and numpy for heavy number crunching. In particular, pyqtgraph uses Qt's GraphicsView framework which is a highly capable graphics system on its own; we bring optimized and simplified primitives to this framework to allow data visualization with minimal effort. It is known to run on Linux, Windows, and OSX What can it do? --------------- Amongst the core features of pyqtgraph are: * Basic data visualization primitives: Images, line and scatter plots * Fast enough for realtime update of video/plot data * Interactive scaling/panning, averaging, FFTs, SVG/PNG export * Widgets for marking/selecting plot regions * Widgets for marking/selecting image region-of-interest and automatically slicing multi-dimensional image data * Framework for building customized image region-of-interest widgets * Docking system that replaces/complements Qt's dock system to allow more complex (and more predictable) docking arrangements * ParameterTree widget for rapid prototyping of dynamic interfaces (Similar to the property trees in Qt Designer and many other applications) .. _examples: Examples -------- PyQtGraph includes an extensive set of examples that can be accessed by running either ``python -m pyqtgraph.examples`` or :: import pyqtgraph.examples pyqtgraph.examples.run() This will start a launcher with a list of available examples. Select an item from the list to view its source code and double-click an item to run the example. How does it compare to... ------------------------- * matplotlib: For plotting, pyqtgraph is not nearly as complete/mature as matplotlib, but runs much faster. Matplotlib is more aimed toward making publication-quality graphics, whereas pyqtgraph is intended for use in data acquisition and analysis applications. Matplotlib is more intuitive for matlab programmers; pyqtgraph is more intuitive for python/qt programmers. Matplotlib (to my knowledge) does not include many of pyqtgraph's features such as image interaction, volumetric rendering, parameter trees, flowcharts, etc. * pyqwt5: About as fast as pyqtgraph, but not quite as complete for plotting functionality. Image handling in pyqtgraph is much more complete (again, no ROI widgets in qwt). Also, pyqtgraph is written in pure python, so it is more portable than pyqwt, which often lags behind pyqt in development (I originally used pyqwt, but decided it was too much trouble to rely on it as a dependency in my projects). Like matplotlib, pyqwt (to my knowledge) does not include many of pyqtgraph's features such as image interaction, volumetric rendering, parameter trees, flowcharts, etc. (My experience with these libraries is somewhat outdated; please correct me if I am wrong here) .. rubric:: Footnotes pyqtgraph-pyqtgraph-0.12.4/doc/source/mouse_interaction.rst000066400000000000000000000053111421045507400241620ustar00rootroot00000000000000Mouse Interaction ================= Most applications that use pyqtgraph's data visualization will generate widgets that can be interactively scaled, panned, and otherwise configured using the mouse. This section describes mouse interaction with these widgets. 2D Graphics ----------- In pyqtgraph, most 2D visualizations follow the following mouse interaction: * **Left button:** Interacts with items in the scene (select/move objects, etc). If there are no movable objects under the mouse cursor, then dragging with the left button will pan the scene instead. * **Right button drag:** Scales the scene. Dragging left/right scales horizontally; dragging up/down scales vertically (although some scenes will have their x/y scales locked together). If there are x/y axes visible in the scene, then right-dragging over the axis will _only_ affect that axis. * **Right button click:** Clicking the right button in most cases will show a context menu with a variety of options depending on the object(s) under the mouse cursor. * **Middle button (or wheel) drag:** Dragging the mouse with the wheel pressed down will always pan the scene (this is useful in instances where panning with the left button is prevented by other objects in the scene). * **Wheel spin:** Zooms the scene in and out. For machines where dragging with the right or middle buttons is difficult (usually Mac), another mouse interaction mode exists. In this mode, dragging with the left mouse button draws a box over a region of the scene. After the button is released, the scene is scaled and panned to fit the box. This mode can be accessed in the context menu or by calling:: pyqtgraph.setConfigOption('leftButtonPan', False) Context Menu ------------ Right-clicking on most scenes will show a context menu with various options for changing the behavior of the scene. Some of the options available in this menu are: * Enable/disable automatic scaling when the data range changes * Link the axes of multiple views together * Enable disable mouse interaction per axis * Explicitly set the visible range values The exact set of items available in the menu depends on the contents of the scene and the object clicked on. 3D Graphics ----------- 3D visualizations use the following mouse interaction: * **Left button drag:** Rotates the scene around a central point * **Middle button drag:** Pan the scene by moving the central "look-at" point within the x-y plane * **Middle button drag + CTRL:** Pan the scene by moving the central "look-at" point along the z axis * **Wheel spin:** zoom in/out * **Wheel + CTRL:** change field-of-view angle And keyboard controls: * Arrow keys rotate around central point, just like dragging the left mouse button pyqtgraph-pyqtgraph-0.12.4/doc/source/parametertree/000077500000000000000000000000001421045507400225415ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/source/parametertree/apiref.rst000066400000000000000000000003461421045507400245440ustar00rootroot00000000000000ParameterTree API Reference =========================== Also see the 'parametertree' example included with pyqtgraph Contents: .. toctree:: :maxdepth: 2 parameter parametertree parametertypes parameteritem pyqtgraph-pyqtgraph-0.12.4/doc/source/parametertree/index.rst000066400000000000000000000030511421045507400244010ustar00rootroot00000000000000.. _parametertree: Parameter Trees =============== .. currentmodule:: pyqtgraph.parametertree Parameter trees are a system for handling hierarchies of parameters while automatically generating one or more GUIs to display and interact with the parameters. This feature is commonly seen, for example, in user interface design applications which display a list of editable properties for each widget. Parameters generally have a name, a data type (int, float, string, color, etc), and a value matching the data type. Parameters may be grouped and nested to form hierarchies and may be subclassed to provide custom behavior and display widgets. PyQtGraph's parameter tree system works similarly to the model-view architecture used by some components of Qt: - A :class:`Parameter` is a purely data-handling class that exists independent of any graphical interface. - A :class:`ParameterItem` is an interactive graphical representation of a :class:`Parameter`. - A :class:`ParameterTree` is a widget that automatically generates a graphical interface which represents the state of a hierarchy of Parameter objects and allows the user to edit the values within that hierarchy. This separation of data (model) and graphical interface (view) allows the same data to be represented multiple times and in a variety of different ways. For example, a floating point number parameter could be represented by a slider or a spinbox, or both. For more information, see the 'parametertree' example included with pyqtgraph and the API reference: .. toctree:: :maxdepth: 2 apiref pyqtgraph-pyqtgraph-0.12.4/doc/source/parametertree/parameter.rst000066400000000000000000000004321421045507400252520ustar00rootroot00000000000000Parameter ========= .. autofunction:: pyqtgraph.parametertree.registerParameterType .. autofunction:: pyqtgraph.parametertree.registerParameterItemType .. autoclass:: pyqtgraph.parametertree.Parameter :members: .. automethod:: pyqtgraph.parametertree.Parameter.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/parametertree/parameteritem.rst000066400000000000000000000002441421045507400261320ustar00rootroot00000000000000ParameterItem ============= .. autoclass:: pyqtgraph.parametertree.ParameterItem :members: .. automethod:: pyqtgraph.parametertree.ParameterItem.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/parametertree/parametertree.rst000066400000000000000000000002441421045507400261330ustar00rootroot00000000000000ParameterTree ============= .. autoclass:: pyqtgraph.parametertree.ParameterTree :members: .. automethod:: pyqtgraph.parametertree.ParameterTree.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/parametertree/parametertypes.rst000066400000000000000000000033361421045507400263450ustar00rootroot00000000000000.. 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 ======================== .. currentmodule:: pyqtgraph.parametertree.parameterTypes Parameters ---------- .. autoclass:: ActionParameter :members: .. autoclass:: CalendarParameter :members: .. autoclass:: ChecklistParameter :members: .. autoclass:: ColorMapParameter :members: .. autoclass:: ColorParameter :members: .. autoclass:: FileParameter :members: .. autoclass:: FontParameter :members: .. autoclass:: GroupParameter :members: .. autoclass:: ListParameter :members: .. autoclass:: PenParameter :members: .. autoclass:: ProgressBarParameter :members: .. autoclass:: SimpleParameter :members: .. autoclass:: SliderParameter :members: .. autoclass:: TextParameter :members: ParameterItems -------------- .. 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 :members: .. autoclass:: ListParameterItem :members: .. autoclass:: NumericParameterItem :members: .. autoclass:: PenParameterItem :members: .. autoclass:: ProgressBarParameterItem :members: .. autoclass:: SliderParameterItem :members: .. autoclass:: StrParameterItem :members: .. autoclass:: TextParameterItem :members: pyqtgraph-pyqtgraph-0.12.4/doc/source/plotting.rst000066400000000000000000000117021421045507400222740ustar00rootroot00000000000000Plotting in pyqtgraph ===================== There are a few basic ways to plot data in pyqtgraph: =================================================================== ================================================== :func:`pyqtgraph.plot` Create a new plot window showing your data :func:`PlotWidget.plot() ` Add a new set of data to an existing plot widget :func:`PlotItem.plot() ` Add a new set of data to an existing plot widget :func:`GraphicsLayout.addPlot() ` Add a new plot to a grid of plots =================================================================== ================================================== All of these will accept the same basic arguments which control how the plot data is interpreted and displayed: * x - Optional X data; if not specified, then a range of integers will be generated automatically. * y - Y data. * pen - The pen to use when drawing plot lines, or None to disable lines. * symbol - A string describing the shape of symbols to use for each point. Optionally, this may also be a sequence of strings with a different symbol for each point. * symbolPen - The pen (or sequence of pens) to use when drawing the symbol outline. * symbolBrush - The brush (or sequence of brushes) to use when filling the symbol. * fillLevel - Fills the area under the plot curve to this Y-value. * brush - The brush to use when filling under the curve. See the 'plotting' :ref:`example ` for a demonstration of these arguments. All of the above functions also return handles to the objects that are created, allowing the plots and data to be further modified. Organization of Plotting Classes -------------------------------- There are several classes invloved in displaying plot data. Most of these classes are instantiated automatically, but it is useful to understand how they are organized and relate to each other. PyQtGraph is based heavily on Qt's GraphicsView framework--if you are not already familiar with this, it's worth reading about (but not essential). Most importantly: 1) Qt GUIs are composed of QWidgets, 2) A special widget called QGraphicsView is used for displaying complex graphics, and 3) QGraphicsItems define the objects that are displayed within a QGraphicsView. * Data Classes (all subclasses of QGraphicsItem) * :class:`PlotCurveItem ` - Displays a plot line given x,y data * :class:`ScatterPlotItem ` - Displays points given x,y data * :class:`PlotDataItem ` - Combines PlotCurveItem and ScatterPlotItem. The plotting functions discussed above create objects of this type. * Container Classes (subclasses of QGraphicsItem; contain other QGraphicsItem objects and must be viewed from within a GraphicsView) * :class:`PlotItem ` - Contains a ViewBox for displaying data as well as AxisItems and labels for displaying the axes and title. This is a QGraphicsItem subclass and thus may only be used from within a GraphicsView * :class:`GraphicsLayout ` - QGraphicsItem subclass which displays a grid of items. This is used to display multiple PlotItems together. * :class:`ViewBox ` - A QGraphicsItem subclass for displaying data. The user may scale/pan the contents of a ViewBox using the mouse. Typically all PlotData/PlotCurve/ScatterPlotItems are displayed from within a ViewBox. * :class:`AxisItem ` - Displays axis values, ticks, and labels. Most commonly used with PlotItem. * Container Classes (subclasses of QWidget; may be embedded in PyQt GUIs) * :class:`PlotWidget ` - A subclass of GraphicsView with a single PlotItem displayed. Most of the methods provided by PlotItem are also available through PlotWidget. * :class:`GraphicsLayoutWidget ` - QWidget subclass displaying a single :class:`~pyqtgraph.GraphicsLayout`. Most of the methods provided by :class:`~pyqtgraph.GraphicsLayout` are also available through GraphicsLayoutWidget. .. image:: images/plottingClasses.png Examples -------- See the 'plotting' and 'PlotWidget' :ref:`examples included with pyqtgraph ` for more information. Show x,y data as scatter plot:: import pyqtgraph as pg import numpy as np x = np.random.normal(size=1000) y = np.random.normal(size=1000) pg.plot(x, y, pen=None, symbol='o') ## setting pen=None disables line drawing Create/show a plot widget, display three data curves:: import pyqtgraph as pg import numpy as np x = np.arange(1000) y = np.random.normal(size=(3, 1000)) plotWidget = pg.plot(title="Three plot curves") for i in range(3): plotWidget.plot(x, y[i], pen=(i,3)) ## setting pen=(i,3) automaticaly creates three different-colored pens pyqtgraph-pyqtgraph-0.12.4/doc/source/prototyping.rst000066400000000000000000000047601421045507400230400ustar00rootroot00000000000000Rapid GUI prototyping ===================== [Just an overview; documentation is not complete yet] PyQtGraph offers several powerful features which are commonly used in engineering and scientific applications. Parameter Trees --------------- The parameter tree system provides a widget displaying a tree of modifiable values similar to those used in most GUI editor applications. This allows a large number of variables to be controlled by the user with relatively little programming effort. The system also provides separation between the data being controlled and the user interface controlling it (model/view architecture). Parameters may be grouped/nested to any depth and custom parameter types can be built by subclassing from Parameter and ParameterItem. See the `parametertree documentation `_ for more information. Visual Programming Flowcharts ----------------------------- PyQtGraph's flowcharts provide a visual programming environment similar in concept to LabView--functional modules are added to a flowchart and connected by wires to define a more complex and arbitrarily configurable algorithm. A small number of predefined modules (called Nodes) are included with pyqtgraph, but most flowchart developers will want to define their own library of Nodes. At their core, the Nodes are little more than 1) a Python function 2) a list of input/output terminals, and 3) an optional widget providing a control panel for the Node. Nodes may transmit/receive any type of Python object via their terminals. See the `flowchart documentation `_ and the flowchart examples for more information. Graphical Canvas ---------------- The Canvas is a system designed to allow the user to add/remove items to a 2D canvas similar to most vector graphics applications. Items can be translated/scaled/rotated and each item may define its own custom control interface. Dockable Widgets ---------------- The dockarea system allows the design of user interfaces which can be rearranged by the user at runtime. Docks can be moved, resized, stacked, and torn out of the main window. This is similar in principle to the docking system built into Qt, but offers a more deterministic dock placement API (in Qt it is very difficult to programatically generate complex dock arrangements). Additionally, Qt's docks are designed to be used as small panels around the outer edge of a window. PyQtGraph's docks were created with the notion that the entire window (or any portion of it) would consist of dockable components. pyqtgraph-pyqtgraph-0.12.4/doc/source/qtcrashcourse.rst000066400000000000000000000101141421045507400233160ustar00rootroot00000000000000Qt Crash Course =============== PyQtGraph makes extensive use of Qt for generating nearly all of its visual output and interfaces. Qt's documentation is very well written and we encourage all pyqtgraph developers to familiarize themselves with it. The purpose of this section is to provide an introduction to programming with Qt (using either PyQt or PySide) for the pyqtgraph developer. QWidgets and Layouts -------------------- A Qt GUI is almost always composed of a few basic components: * A window. This is often provided by QMainWindow, but note that all QWidgets can be displayed in their window by simply calling widget.show() if the widget does not have a parent. * Multiple QWidget instances such as QPushButton, QLabel, QComboBox, etc. * QLayout instances (optional, but strongly encouraged) which automatically manage the positioning of widgets to allow the GUI to resize in a usable way. PyQtGraph fits into this scheme by providing its own QWidget subclasses to be inserted into your GUI. Example:: from PyQt5 import QtGui # (the example applies equally well to PySide2) import pyqtgraph as pg ## Always start by initializing Qt (only once per application) app = QtGui.QApplication([]) ## Define a top-level widget to hold everything w = QtGui.QWidget() ## Create some widgets to be placed inside btn = QtGui.QPushButton('press me') text = QtGui.QLineEdit('enter text') listw = QtGui.QListWidget() plot = pg.PlotWidget() ## Create a grid layout to manage the widgets size and position layout = QtGui.QGridLayout() w.setLayout(layout) ## Add widgets to the layout in their proper positions layout.addWidget(btn, 0, 0) # button goes in upper-left layout.addWidget(text, 1, 0) # text edit goes in middle-left layout.addWidget(listw, 2, 0) # list widget goes in bottom-left layout.addWidget(plot, 0, 1, 3, 1) # plot goes on right side, spanning 3 rows ## Display the widget as a new window w.show() ## Start the Qt event loop app.exec_() More complex interfaces may be designed graphically using Qt Designer, which allows you to simply drag widgets into your window to define its appearance. Naming Conventions ------------------ Virtually every class in pyqtgraph is an extension of base classes provided by Qt. When reading the documentation, remember that all of Qt's classes start with the letter 'Q', whereas pyqtgraph's classes do not. When reading through the methods for any class, it is often helpful to see which Qt base classes are used and look through the Qt documentation as well. Most of Qt's classes define signals which can be difficult to tell apart from regular methods. Almost all signals explicity defined by pyqtgraph are named beginning with 'sig' to indicate that these signals are not defined at the Qt level. In most cases, classes which end in 'Widget' are subclassed from QWidget and can therefore be used as a GUI element in a Qt window. Classes which end in 'Item' are subclasses of QGraphicsItem and can only be displayed within a QGraphicsView instance (such as GraphicsLayoutWidget or PlotWidget). Signals, Slots, and Events -------------------------- [ to be continued.. please post a request on the pyqtgraph forum if you'd like to read more ] Qt detects and reacts to user interaction by executing its *event loop*. - what happens in the event loop? - when do I need to use QApplication.exec_() ? - what control do I have over event loop execution? (QApplication.processEvents) GraphicsView and GraphicsItems ------------------------------ More information about the architecture of Qt GraphicsView: http://qt-project.org/doc/qt-4.8/graphicsview.html Coordinate Systems and Transformations -------------------------------------- More information about the coordinate systems in Qt GraphicsView: http://qt-project.org/doc/qt-4.8/graphicsview.html#the-graphics-view-coordinate-system Mouse and Keyboard Input ------------------------ QTimer, Multi-Threading ----------------------- Multi-threading vs Multi-processing in Qt ----------------------------------------- pyqtgraph-pyqtgraph-0.12.4/doc/source/region_of_interest.rst000066400000000000000000000037751421045507400243330ustar00rootroot00000000000000Interactive Data Selection Controls =================================== PyQtGraph includes graphics items which allow the user to select and mark regions of data. Linear Selection and Marking ---------------------------- Two classes allow marking and selecting 1-dimensional data: :class:`LinearRegionItem ` and :class:`InfiniteLine `. The first class, :class:`LinearRegionItem `, may be added to any ViewBox or PlotItem to mark either a horizontal or vertical region. The region can be dragged and its bounding edges can be moved independently. The second class, :class:`InfiniteLine `, is usually used to mark a specific position along the x or y axis. These may be dragged by the user. 2D Selection and Marking ------------------------ To select a 2D region from an image, pyqtgraph uses the :class:`ROI ` class or any of its subclasses. By default, :class:`ROI ` simply displays a rectangle which can be moved by the user to mark a specific region (most often this will be a region of an image, but this is not required). To allow the ROI to be resized or rotated, there are several methods for adding handles (:func:`addScaleHandle `, :func:`addRotateHandle `, etc.) which can be dragged by the user. These handles may be placed at any location relative to the ROI and may scale/rotate the ROI around any arbitrary center point. There are several ROI subclasses with a variety of shapes and modes of interaction. To automatically extract a region of image data using an ROI and an ImageItem, use :func:`ROI.getArrayRegion `. ROI classes use the :func:`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 vector graphics editing applications. See the ROITypes example for more information. pyqtgraph-pyqtgraph-0.12.4/doc/source/style.rst000066400000000000000000000040551421045507400215770ustar00rootroot00000000000000Line, Fill, and Color ===================== Qt relies on its QColor, QPen and QBrush classes for specifying line and fill styles for all of its drawing. Internally, pyqtgraph uses the same system but also allows many shorthand methods of specifying the same style options. Many functions and methods in pyqtgraph accept arguments specifying the line style (pen), fill style (brush), or color. For most of these function arguments, the following values may be used: * single-character string representing color (b, g, r, c, m, y, k, w) * (r, g, b) or (r, g, b, a) tuple * single greyscale value (0.0 - 1.0) * (index, maximum) tuple for automatically iterating through colors (see :func:`intColor `) * QColor * QPen / QBrush where appropriate Notably, more complex pens and brushes can be easily built using the :func:`mkPen() ` / :func:`mkBrush() ` functions or with Qt's QPen and QBrush classes:: mkPen('y', width=3, style=QtCore.Qt.DashLine) ## Make a dashed yellow line 2px wide mkPen(0.5) ## solid grey line 1px wide mkPen(color=(200, 200, 255), style=QtCore.Qt.DotLine) ## Dotted pale-blue line See the Qt documentation for 'QPen' and 'PenStyle' for more line-style options and 'QBrush' for more fill options. Colors can also be built using :func:`mkColor() `, :func:`intColor() `, :func:`hsvColor() `, or Qt's QColor class. Default Background and Foreground Colors ---------------------------------------- By default, pyqtgraph uses a black background for its plots and grey for axes, text, and plot lines. These defaults can be changed using pyqtgraph.setConfigOption():: import pyqtgraph as pg ## Switch to using white background and black foreground pg.setConfigOption('background', 'w') pg.setConfigOption('foreground', 'k') ## The following plot has inverted colors pg.plot([1,4,2,3,5]) (Note that this must be set *before* creating any widgets) pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/000077500000000000000000000000001421045507400213475ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/busycursor.rst000066400000000000000000000001741421045507400243230ustar00rootroot00000000000000BusyCursor ========== .. autoclass:: pyqtgraph.BusyCursor :members: .. automethod:: pyqtgraph.BusyCursor.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/checktable.rst000066400000000000000000000001741421045507400241700ustar00rootroot00000000000000CheckTable ========== .. autoclass:: pyqtgraph.CheckTable :members: .. automethod:: pyqtgraph.CheckTable.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/colorbutton.rst000066400000000000000000000002001421045507400244430ustar00rootroot00000000000000ColorButton =========== .. autoclass:: pyqtgraph.ColorButton :members: .. automethod:: pyqtgraph.ColorButton.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/colormapwidget.rst000066400000000000000000000004571421045507400251270ustar00rootroot00000000000000ColorMapWidget ============== .. autoclass:: pyqtgraph.ColorMapWidget :members: .. automethod:: pyqtgraph.ColorMapWidget.__init__ .. automethod:: pyqtgraph.widgets.ColorMapWidget.ColorMapParameter.setFields .. automethod:: pyqtgraph.widgets.ColorMapWidget.ColorMapParameter.map pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/combobox.rst000066400000000000000000000001641421045507400237120ustar00rootroot00000000000000ComboBox ======== .. autoclass:: pyqtgraph.ComboBox :members: .. automethod:: pyqtgraph.ComboBox.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/consolewidget.rst000066400000000000000000000001371421045507400247500ustar00rootroot00000000000000ConsoleWidget ============= .. autoclass:: pyqtgraph.console.ConsoleWidget :members: pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/datatreewidget.rst000066400000000000000000000002141421045507400250730ustar00rootroot00000000000000DataTreeWidget ============== .. autoclass:: pyqtgraph.DataTreeWidget :members: .. automethod:: pyqtgraph.DataTreeWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/feedbackbutton.rst000066400000000000000000000002141421045507400250560ustar00rootroot00000000000000FeedbackButton ============== .. autoclass:: pyqtgraph.FeedbackButton :members: .. automethod:: pyqtgraph.FeedbackButton.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/filedialog.rst000066400000000000000000000001741421045507400242020ustar00rootroot00000000000000FileDialog ========== .. autoclass:: pyqtgraph.FileDialog :members: .. automethod:: pyqtgraph.FileDialog.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/gradientwidget.rst000066400000000000000000000002141421045507400250770ustar00rootroot00000000000000GradientWidget ============== .. autoclass:: pyqtgraph.GradientWidget :members: .. automethod:: pyqtgraph.GradientWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/graphicslayoutwidget.rst000066400000000000000000000002441421045507400263430ustar00rootroot00000000000000GraphicsLayoutWidget ==================== .. autoclass:: pyqtgraph.GraphicsLayoutWidget :members: .. automethod:: pyqtgraph.GraphicsLayoutWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/graphicsview.rst000066400000000000000000000002041421045507400245700ustar00rootroot00000000000000GraphicsView ============ .. autoclass:: pyqtgraph.GraphicsView :members: .. automethod:: pyqtgraph.GraphicsView.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/histogramlutwidget.rst000066400000000000000000000002341421045507400260260ustar00rootroot00000000000000HistogramLUTWidget ================== .. autoclass:: pyqtgraph.HistogramLUTWidget :members: .. automethod:: pyqtgraph.HistogramLUTWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/imageview.rst000066400000000000000000000001701421045507400240540ustar00rootroot00000000000000ImageView ========= .. autoclass:: pyqtgraph.ImageView :members: .. automethod:: pyqtgraph.ImageView.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/index.rst000066400000000000000000000015101421045507400232050ustar00rootroot00000000000000.. _api_widgets: PyQtGraph's Widgets =================== PyQtGraph provides several QWidget subclasses which are useful for building user interfaces. These widgets can generally be used in any Qt application and provide functionality that is frequently useful in science and engineering applications. Contents: .. toctree:: :maxdepth: 2 plotwidget imageview spinbox gradientwidget histogramlutwidget consolewidget colormapwidget scatterplotwidget graphicsview datatreewidget tablewidget treewidget checktable colorbutton graphicslayoutwidget progressdialog filedialog joystickbutton multiplotwidget verticallabel remotegraphicsview matplotlibwidget feedbackbutton combobox layoutwidget pathbutton valuelabel busycursor pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/joystickbutton.rst000066400000000000000000000002141421045507400251710ustar00rootroot00000000000000JoystickButton ============== .. autoclass:: pyqtgraph.JoystickButton :members: .. automethod:: pyqtgraph.JoystickButton.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/layoutwidget.rst000066400000000000000000000002041421045507400246160ustar00rootroot00000000000000LayoutWidget ============ .. autoclass:: pyqtgraph.LayoutWidget :members: .. automethod:: pyqtgraph.LayoutWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/make000066400000000000000000000007221421045507400222100ustar00rootroot00000000000000files = """CheckTable ColorButton DataTreeWidget FileDialog GradientWidget GraphicsLayoutWidget GraphicsView HistogramLUTWidget JoystickButton MultiPlotWidget PlotWidget ProgressDialog RawImageWidget SpinBox TableWidget TreeWidget VerticalLabel""".split('\n') for f in files: print(f) fh = open(f.lower()+'.rst', 'w') fh.write( """%s %s .. autoclass:: pyqtgraph.%s :members: .. automethod:: pyqtgraph.%s.__init__ """ % (f, '='*len(f), f, f)) pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/matplotlibwidget.rst000066400000000000000000000003061421045507400254530ustar00rootroot00000000000000MatplotlibWidget ================ .. autoclass:: pyqtgraph.widgets.MatplotlibWidget.MatplotlibWidget :members: .. automethod:: pyqtgraph.widgets.MatplotlibWidget.MatplotlibWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/multiplotwidget.rst000066400000000000000000000002201421045507400253300ustar00rootroot00000000000000MultiPlotWidget =============== .. autoclass:: pyqtgraph.MultiPlotWidget :members: .. automethod:: pyqtgraph.MultiPlotWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/pathbutton.rst000066400000000000000000000001741421045507400242730ustar00rootroot00000000000000PathButton ========== .. autoclass:: pyqtgraph.PathButton :members: .. automethod:: pyqtgraph.PathButton.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/plotwidget.rst000066400000000000000000000001741421045507400242650ustar00rootroot00000000000000PlotWidget ========== .. autoclass:: pyqtgraph.PlotWidget :members: .. automethod:: pyqtgraph.PlotWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/progressdialog.rst000066400000000000000000000002141421045507400251220ustar00rootroot00000000000000ProgressDialog ============== .. autoclass:: pyqtgraph.ProgressDialog :members: .. automethod:: pyqtgraph.ProgressDialog.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/remotegraphicsview.rst000066400000000000000000000003221421045507400260050ustar00rootroot00000000000000RemoteGraphicsView ================== .. autoclass:: pyqtgraph.widgets.RemoteGraphicsView.RemoteGraphicsView :members: .. automethod:: pyqtgraph.widgets.RemoteGraphicsView.RemoteGraphicsView.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/scatterplotwidget.rst000066400000000000000000000002301421045507400256440ustar00rootroot00000000000000ScatterPlotWidget ================= .. autoclass:: pyqtgraph.ScatterPlotWidget :members: .. automethod:: pyqtgraph.ScatterPlotWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/spinbox.rst000066400000000000000000000001601421045507400235600ustar00rootroot00000000000000SpinBox ======= .. autoclass:: pyqtgraph.SpinBox :members: .. automethod:: pyqtgraph.SpinBox.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/tablewidget.rst000066400000000000000000000002001421045507400243640ustar00rootroot00000000000000TableWidget =========== .. autoclass:: pyqtgraph.TableWidget :members: .. automethod:: pyqtgraph.TableWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/treewidget.rst000066400000000000000000000001741421045507400242460ustar00rootroot00000000000000TreeWidget ========== .. autoclass:: pyqtgraph.TreeWidget :members: .. automethod:: pyqtgraph.TreeWidget.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/valuelabel.rst000066400000000000000000000001741421045507400242170ustar00rootroot00000000000000ValueLabel ========== .. autoclass:: pyqtgraph.ValueLabel :members: .. automethod:: pyqtgraph.ValueLabel.__init__ pyqtgraph-pyqtgraph-0.12.4/doc/source/widgets/verticallabel.rst000066400000000000000000000002101421045507400247030ustar00rootroot00000000000000VerticalLabel ============= .. autoclass:: pyqtgraph.VerticalLabel :members: .. automethod:: pyqtgraph.VerticalLabel.__init__ pyqtgraph-pyqtgraph-0.12.4/pyproject.toml000066400000000000000000000011631421045507400205510ustar00rootroot00000000000000[tool.black] line-length = 88 target-version = ['py37'] include = '\.pyi?$' exclude = ''' ( /( \.eggs # exclude a few common directories in the | \.git # root of the project | \.hg | \.mypy_cache | \.tox | \.venv* | _build | buck-out | build | dist )/ ) ''' [tool.isort] profile = "black" honor_noqa = true color_output = true py_version = 37 src_paths = ["pyqtgraph", "tests"] skip_glob = ["**/*Template*.py", "**/colorama"] skip_gitignore = true known_third_party = ["QtCore", "QtGui", "QtWidgets"] [tool.pycln] all = true exclude = '(Template|__init__.py)' pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/000077500000000000000000000000001421045507400176535ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/GraphicsScene/000077500000000000000000000000001421045507400223715ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/GraphicsScene/GraphicsScene.py000066400000000000000000000625361421045507400254750ustar00rootroot00000000000000import warnings import weakref from time import perf_counter, perf_counter_ns from .. import debug as debug from .. import getConfigOption from ..Point import Point from ..Qt import QT_LIB, QtCore, QtGui, QtWidgets, isQObjectAlive from .mouseEvents import HoverEvent, MouseClickEvent, MouseDragEvent getMillis = lambda: perf_counter_ns() // 10 ** 6 if QT_LIB.startswith('PyQt'): from ..Qt import sip HAVE_SIP = True else: HAVE_SIP = False __all__ = ['GraphicsScene'] class GraphicsScene(QtWidgets.QGraphicsScene): """ Extension of QGraphicsScene that implements a complete, parallel mouse event system. (It would have been preferred to just alter the way QGraphicsScene creates and delivers 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 ====================== ==================================================================== **Signals** sigMouseClicked(event) Emitted when the mouse is clicked over the scene. Use ev.pos() to get the click position relative to the item that was clicked on, or ev.scenePos() to get the click position in scene coordinates. See :class:`pyqtgraph.GraphicsScene.MouseClickEvent`. sigMouseMoved(pos) Emitted when the mouse cursor moves over the scene. The position is given in scene coordinates. sigMouseHover(items) Emitted when the mouse is moved over the scene. Items is a list of items under the cursor. sigItemAdded(item) Emitted when an item is added via addItem(). The item is given. sigItemRemoved(item) Emitted when an item is removed via removeItem(). The item is given. ====================== ==================================================================== Mouse interaction is as follows: 1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents as well as custom HoverEvents. 2) Items are sent HoverEvents in Z-order and each item may optionally call event.acceptClicks(button), acceptDrags(button) or both. If this method call returns True, this informs the item that _if_ the user clicks/drags the specified mouse button, the item is guaranteed to be the recipient of click/drag events (the item may wish to change its appearance to indicate this). If the call to acceptClicks/Drags returns False, then the item is guaranteed to *not* receive the requested event (because another item has already accepted it). 3) If the mouse is clicked, a mousePressEvent is generated as usual. If any items accept this press event, then No click/drag events will be generated and mouse interaction proceeds as defined by Qt. This allows items to function properly if they are expecting the usual press/move/release sequence of events. (It is recommended that items do NOT accept press events, and instead use click/drag events) Note: The default implementation of QGraphicsItem.mousePressEvent will *accept* the event if the item is has its Selectable or Movable flags enabled. You may need to override this behavior. 4) If no item accepts the mousePressEvent, then the scene will begin delivering mouseDrag and/or mouseClick events. If the mouse is moved a sufficient distance (or moved slowly enough) before the button is released, then a mouseDragEvent is generated. If no drag events are generated before the button is released, then a mouseClickEvent is generated. 5) Click/drag events are delivered to the item that called acceptClicks/acceptDrags on the HoverEvent in step 1. If no such items exist, then the scene attempts to deliver the events to items near the event. ClickEvents may be delivered in this way even if no item originally claimed it could accept the click. DragEvents may only be delivered this way if it is the initial move in a drag. """ sigMouseHover = QtCore.Signal(object) ## emits a list of objects hovered over sigMouseMoved = QtCore.Signal(object) ## emits position of mouse on every move sigMouseClicked = QtCore.Signal(object) ## emitted when mouse is clicked. Check for event.isAccepted() to see whether the event has already been acted on. sigPrepareForPaint = QtCore.Signal() ## emitted immediately before the scene is about to be rendered sigItemAdded = QtCore.Signal(object) ## emits the item object just added sigItemRemoved = QtCore.Signal(object) ## emits the item object just removed _addressCache = weakref.WeakValueDictionary() ExportDirectory = None @classmethod def registerObject(cls, obj): warnings.warn( "'registerObject' is deprecated and does nothing.", DeprecationWarning, stacklevel=2 ) def __init__(self, clickRadius=2, moveDistance=5, parent=None): QtWidgets.QGraphicsScene.__init__(self, parent) self.setClickRadius(clickRadius) self.setMoveDistance(moveDistance) self.exportDirectory = None self.clickEvents = [] self.dragButtons = [] self.mouseGrabber = None self.dragItem = None self.lastDrag = None self.hoverItems = weakref.WeakKeyDictionary() self.lastHoverEvent = None self.minDragTime = 0.5 # drags shorter than 0.5 sec are interpreted as clicks self.contextMenu = [QtGui.QAction(QtCore.QCoreApplication.translate("GraphicsScene", "Export..."), self)] self.contextMenu[0].triggered.connect(self.showExportDialog) self.exportDialog = None self._lastMoveEventTime = 0 def render(self, *args): self.prepareForPaint() return QtWidgets.QGraphicsScene.render(self, *args) def prepareForPaint(self): """Called before every render. This method will inform items that the scene is about to be rendered by emitting sigPrepareForPaint. This allows items to delay expensive processing until they know a paint will be required.""" self.sigPrepareForPaint.emit() def setClickRadius(self, r): """ Set the distance away from mouse clicks to search for interacting items. When clicking, the scene searches first for items that directly intersect the click position followed by any other items that are within a rectangle that extends r pixels away from the click position. """ self._clickRadius = r def setMoveDistance(self, d): """ Set the distance the mouse must move after a press before mouseMoveEvents will be delivered. This ensures that clicks with a small amount of movement are recognized as clicks instead of drags. """ self._moveDistance = d def mousePressEvent(self, ev): super().mousePressEvent(ev) if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events if self.lastHoverEvent is not None: # If the mouse has moved since the last hover event, send a new one. # This can happen if a context menu is open while the mouse is moving. if ev.scenePos() != self.lastHoverEvent.scenePos(): self.sendHoverEvents(ev) self.clickEvents.append(MouseClickEvent(ev)) ## set focus on the topmost focusable item under this click items = self.items(ev.scenePos()) for i in items: if i.isEnabled() and i.isVisible() and (i.flags() & i.GraphicsItemFlag.ItemIsFocusable): i.setFocus(QtCore.Qt.FocusReason.MouseFocusReason) break def _moveEventIsAllowed(self): # For ignoring events that are too close together # Max number of events per second rateLimit = getConfigOption('mouseRateLimit') if rateLimit <= 0: return True # Delay between events (in milliseconds) delay = 1000.0 / rateLimit if getMillis() - self._lastMoveEventTime >= delay: return True return False def mouseMoveEvent(self, ev): # ignore high frequency events if self._moveEventIsAllowed(): self._lastMoveEventTime = getMillis() self.sigMouseMoved.emit(ev.scenePos()) # First allow QGraphicsScene to eliver hoverEvent/Move/Exit Events super().mouseMoveEvent(ev) # Next Deliver our own Hover Events self.sendHoverEvents(ev) if ev.buttons(): # button is pressed' send mouseMoveEvents and mouseDragEvents super().mouseMoveEvent(ev) if self.mouseGrabberItem() is None: 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]: if not (ev.buttons() & btn): continue if btn not in self.dragButtons: ## see if we've dragged far enough yet cev = [e for e in self.clickEvents if e.button() == btn] if cev: cev = cev[0] dist = Point(ev.scenePos() - cev.scenePos()).length() if dist == 0 or (dist < self._moveDistance and now - cev.time() < self.minDragTime): continue init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True self.dragButtons.append(btn) ## if we have dragged buttons, deliver a drag event if len(self.dragButtons) > 0: if self.sendDragEvent(ev, init=init): ev.accept() else: super().mouseMoveEvent(ev) # if you do not accept event (which is ignored) then cursor will disappear ev.accept() def leaveEvent(self, ev): ## inform items that mouse is gone if len(self.dragButtons) == 0: self.sendHoverEvents(ev, exitOnly=True) def mouseReleaseEvent(self, ev): if self.mouseGrabberItem() is None: if ev.button() in self.dragButtons: if self.sendDragEvent(ev, final=True): #print "sent drag event" ev.accept() self.dragButtons.remove(ev.button()) else: cev = [e for e in self.clickEvents if e.button() == ev.button()] if cev: if self.sendClickEvent(cev[0]): #print "sent click event" ev.accept() self.clickEvents.remove(cev[0]) if not ev.buttons(): self.dragItem = None self.dragButtons = [] self.clickEvents = [] self.lastDrag = None super().mouseReleaseEvent(ev) self.sendHoverEvents(ev) ## let items prepare for next click/drag def mouseDoubleClickEvent(self, ev): super().mouseDoubleClickEvent(ev) if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events self.clickEvents.append(MouseClickEvent(ev, double=True)) def sendHoverEvents(self, ev, exitOnly=False): ## if exitOnly, then just inform all previously hovered items that the mouse has left. if exitOnly: acceptable=False items = [] event = HoverEvent(None, acceptable) else: acceptable = not ev.buttons() ## if we are in mid-drag, do not allow items to accept the hover event. event = HoverEvent(ev, acceptable) items = self.itemsNearEvent(event, hoverable=True) self.sigMouseHover.emit(items) prevItems = list(self.hoverItems.keys()) for item in items: if hasattr(item, 'hoverEvent'): event.currentItem = item if item not in self.hoverItems: self.hoverItems[item] = None event.enter = True else: prevItems.remove(item) event.enter = False try: item.hoverEvent(event) except: debug.printExc("Error sending hover event:") event.enter = False event.exit = True #print "hover exit items:", prevItems for item in prevItems: event.currentItem = item try: # NOTE: isQObjectAlive(item) was added for PySide6 where # verlet_chain_demo.py triggers a RuntimeError. if isQObjectAlive(item) and item.scene() is self: item.hoverEvent(event) except: debug.printExc("Error sending hover exit event:") finally: del self.hoverItems[item] # Update last hover event unless: # - mouse is dragging (move+buttons); in this case we want the dragged # item to continue receiving events until the drag is over # - event is not a mouse event (QEvent.Type.Leave sometimes appears here) if (ev.type() == ev.Type.GraphicsSceneMousePress or (ev.type() == ev.Type.GraphicsSceneMouseMove and not ev.buttons())): self.lastHoverEvent = event ## save this so we can ask about accepted events later. def sendDragEvent(self, ev, init=False, final=False): ## Send a MouseDragEvent to the current dragItem or to ## items near the beginning of the drag event = MouseDragEvent(ev, self.clickEvents[0], self.lastDrag, start=init, finish=final) #print "dragEvent: init=", init, 'final=', final, 'self.dragItem=', self.dragItem if init and self.dragItem is None: if self.lastHoverEvent is not None: acceptedItem = self.lastHoverEvent.dragItems().get(event.button(), None) else: acceptedItem = None if acceptedItem is not None and acceptedItem.scene() is self: #print "Drag -> pre-selected item:", acceptedItem self.dragItem = acceptedItem event.currentItem = self.dragItem try: self.dragItem.mouseDragEvent(event) except: debug.printExc("Error sending drag event:") else: #print "drag -> new item" for item in self.itemsNearEvent(event): #print "check item:", item if not item.isVisible() or not item.isEnabled(): continue if hasattr(item, 'mouseDragEvent'): event.currentItem = item try: item.mouseDragEvent(event) except: debug.printExc("Error sending drag event:") if event.isAccepted(): #print " --> accepted" self.dragItem = item if item.flags() & item.GraphicsItemFlag.ItemIsFocusable: item.setFocus(QtCore.Qt.FocusReason.MouseFocusReason) break elif self.dragItem is not None: event.currentItem = self.dragItem try: self.dragItem.mouseDragEvent(event) except: debug.printExc("Error sending hover exit event:") self.lastDrag = event return event.isAccepted() def sendClickEvent(self, ev): ## if we are in mid-drag, click events may only go to the dragged item. if self.dragItem is not None and hasattr(self.dragItem, 'mouseClickEvent'): ev.currentItem = self.dragItem self.dragItem.mouseClickEvent(ev) ## otherwise, search near the cursor else: if self.lastHoverEvent is not None: acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None) else: acceptedItem = None if acceptedItem is not None: ev.currentItem = acceptedItem try: acceptedItem.mouseClickEvent(ev) except: debug.printExc("Error sending click event:") else: for item in self.itemsNearEvent(ev): if not item.isVisible() or not item.isEnabled(): continue if hasattr(item, 'mouseClickEvent'): ev.currentItem = item try: item.mouseClickEvent(ev) except: debug.printExc("Error sending click event:") if ev.isAccepted(): if item.flags() & item.GraphicsItemFlag.ItemIsFocusable: item.setFocus(QtCore.Qt.FocusReason.MouseFocusReason) break self.sigMouseClicked.emit(ev) return ev.isAccepted() def addItem(self, item): # extend QGraphicsScene.addItem to emit a sigItemAdded signal ret = QtWidgets.QGraphicsScene.addItem(self, item) self.sigItemAdded.emit(item) return ret def removeItem(self, item): # extend QGraphicsScene.removeItem to emit a sigItemRemoved signal ret = QtWidgets.QGraphicsScene.removeItem(self, item) self.sigItemRemoved.emit(item) return ret def items(self, *args): items = QtWidgets.QGraphicsScene.items(self, *args) return self.translateGraphicsItems(items) def selectedItems(self, *args): items = QtWidgets.QGraphicsScene.selectedItems(self, *args) return self.translateGraphicsItems(items) def itemAt(self, *args): item = QtWidgets.QGraphicsScene.itemAt(self, *args) return self.translateGraphicsItem(item) def itemsNearEvent(self, event, selMode=QtCore.Qt.ItemSelectionMode.IntersectsItemShape, sortOrder=QtCore.Qt.SortOrder.DescendingOrder, hoverable=False): """ Return an iterator that iterates first through the items that directly intersect point (in Z order) followed by any other items that are within the scene's click radius. """ #tr = self.getViewWidget(event.widget()).transform() view = self.views()[0] tr = view.viewportTransform() r = self._clickRadius rect = view.mapToScene(QtCore.QRect(0, 0, 2*r, 2*r)).boundingRect() seen = set() if hasattr(event, 'buttonDownScenePos'): point = event.buttonDownScenePos() else: point = event.scenePos() w = rect.width() h = rect.height() rgn = QtCore.QRectF(point.x()-w, point.y()-h, 2*w, 2*h) #self.searchRect.setRect(rgn) items = self.items(point, selMode, sortOrder, tr) ## remove items whose shape does not contain point (scene.items() apparently sucks at this) items2 = [] for item in items: if hoverable and not hasattr(item, 'hoverEvent'): continue if item.scene() is not self: continue shape = item.shape() # Note: default shape() returns boundingRect() if shape is None: continue if shape.contains(item.mapFromScene(point)): items2.append(item) ## Sort by descending Z-order (don't trust scene.itms() to do this either) ## use 'absolute' z value, which is the sum of all item/parent ZValues def absZValue(item): if item is None: return 0 return item.zValue() + absZValue(item.parentItem()) items2.sort(key=absZValue, reverse=True) return items2 #for item in items: ##seen.add(item) #shape = item.mapToScene(item.shape()) #if not shape.contains(point): #continue #yield item #for item in self.items(rgn, selMode, sortOrder, tr): ##if item not in seen: #yield item def getViewWidget(self): return self.views()[0] #def getViewWidget(self, widget): ### same pyqt bug -- mouseEvent.widget() doesn't give us the original python object. ### [[doesn't seem to work correctly]] #if HAVE_SIP and isinstance(self, sip.wrapper): #addr = sip.unwrapinstance(sip.cast(widget, QtWidgets.QWidget)) ##print "convert", widget, addr #for v in self.views(): #addr2 = sip.unwrapinstance(sip.cast(v, QtWidgets.QWidget)) ##print " check:", v, addr2 #if addr2 == addr: #return v #else: #return widget def addParentContextMenus(self, item, menu, event): """ Can be called by any item in the scene to expand its context menu to include parent context menus. Parents may implement getContextMenus to add new menus / actions to the existing menu. getContextMenus must accept 1 argument (the event that generated the original menu) and return a single QMenu or a list of QMenus. The final menu will look like: | Original Item 1 | Original Item 2 | ... | Original Item N | ------------------ | Parent Item 1 | Parent Item 2 | ... | Grandparent Item 1 | ... ============== ================================================== **Arguments:** item The item that initially created the context menu (This is probably the item making the call to this function) menu The context menu being shown by the item event The original event that triggered the menu to appear. ============== ================================================== """ menusToAdd = [] while item is not self: item = item.parentItem() if item is None: item = self if not hasattr(item, "getContextMenus"): continue subMenus = item.getContextMenus(event) or [] if isinstance(subMenus, list): ## so that some items (like FlowchartViewBox) can return multiple menus menusToAdd.extend(subMenus) else: menusToAdd.append(subMenus) if menusToAdd: menu.addSeparator() for m in menusToAdd: if isinstance(m, QtWidgets.QMenu): menu.addAction(m.menuAction()) elif isinstance(m, QtGui.QAction): menu.addAction(m) else: raise Exception("Cannot add object %s (type=%s) to QMenu." % (str(m), str(type(m)))) return menu def getContextMenus(self, event): self.contextMenuItem = event.acceptedItem return self.contextMenu def showExportDialog(self): if self.exportDialog is None: from . import exportDialog self.exportDialog = exportDialog.ExportDialog(self) self.exportDialog.show(self.contextMenuItem) @staticmethod def translateGraphicsItem(item): # This function is intended as a workaround for a problem with older # versions of PyQt (< 4.9?), where methods returning 'QGraphicsItem *' # lose the type of the QGraphicsObject subclasses and instead return # generic QGraphicsItem wrappers. if HAVE_SIP and isinstance(item, sip.wrapper): obj = item.toGraphicsObject() if obj is not None: item = obj return item @staticmethod def translateGraphicsItems(items): return list(map(GraphicsScene.translateGraphicsItem, items)) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/GraphicsScene/__init__.py000066400000000000000000000000351421045507400245000ustar00rootroot00000000000000from .GraphicsScene import * pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/GraphicsScene/exportDialog.py000066400000000000000000000124271421045507400254120ustar00rootroot00000000000000import importlib from .. import exporters as exporters from .. import functions as fn from ..graphicsItems.PlotItem import PlotItem from ..graphicsItems.ViewBox import ViewBox from ..Qt import QT_LIB, QtCore, QtWidgets ui_template = importlib.import_module( f'.exportDialogTemplate_{QT_LIB.lower()}', package=__package__) class FormatExportListWidgetItem(QtWidgets.QListWidgetItem): def __init__(self, expClass, *args, **kwargs): QtWidgets.QListWidgetItem.__init__(self, *args, **kwargs) self.expClass = expClass class ExportDialog(QtWidgets.QWidget): def __init__(self, scene): QtWidgets.QWidget.__init__(self) self.setVisible(False) self.setWindowTitle("Export") self.shown = False self.currentExporter = None self.scene = scene self.selectBox = QtWidgets.QGraphicsRectItem() self.selectBox.setPen(fn.mkPen('y', width=3, style=QtCore.Qt.PenStyle.DashLine)) self.selectBox.hide() self.scene.addItem(self.selectBox) self.ui = ui_template.Ui_Form() self.ui.setupUi(self) self.ui.closeBtn.clicked.connect(self.close) self.ui.exportBtn.clicked.connect(self.exportClicked) self.ui.copyBtn.clicked.connect(self.copyClicked) self.ui.itemTree.currentItemChanged.connect(self.exportItemChanged) self.ui.formatList.currentItemChanged.connect(self.exportFormatChanged) def show(self, item=None): if item is not None: ## Select next exportable parent of the item originally clicked on while not isinstance(item, ViewBox) and not isinstance(item, PlotItem) and item is not None: item = item.parentItem() ## if this is a ViewBox inside a PlotItem, select the parent instead. if isinstance(item, ViewBox) and isinstance(item.parentItem(), PlotItem): item = item.parentItem() self.updateItemList(select=item) self.setVisible(True) self.activateWindow() self.raise_() self.selectBox.setVisible(True) if not self.shown: self.shown = True vcenter = self.scene.getViewWidget().geometry().center() self.setGeometry(int(vcenter.x() - self.width() / 2), int(vcenter.y() - self.height() / 2), self.width(), self.height()) def updateItemList(self, select=None): self.ui.itemTree.clear() si = QtWidgets.QTreeWidgetItem(["Entire Scene"]) si.gitem = self.scene self.ui.itemTree.addTopLevelItem(si) self.ui.itemTree.setCurrentItem(si) si.setExpanded(True) for child in self.scene.items(): if child.parentItem() is None: self.updateItemTree(child, si, select=select) def updateItemTree(self, item, treeItem, select=None): si = None if isinstance(item, ViewBox): si = QtWidgets.QTreeWidgetItem(['ViewBox']) elif isinstance(item, PlotItem): si = QtWidgets.QTreeWidgetItem(['Plot']) if si is not None: si.gitem = item treeItem.addChild(si) treeItem = si if si.gitem is select: self.ui.itemTree.setCurrentItem(si) for ch in item.childItems(): self.updateItemTree(ch, treeItem, select=select) def exportItemChanged(self, item, prev): if item is None: return if item.gitem is self.scene: newBounds = self.scene.views()[0].viewRect() else: newBounds = item.gitem.sceneBoundingRect() self.selectBox.setRect(newBounds) self.selectBox.show() self.updateFormatList() def updateFormatList(self): current = self.ui.formatList.currentItem() self.ui.formatList.clear() gotCurrent = False for exp in exporters.listExporters(): item = FormatExportListWidgetItem(exp, QtCore.QCoreApplication.translate('Exporter', exp.Name)) self.ui.formatList.addItem(item) if item == current: self.ui.formatList.setCurrentRow(self.ui.formatList.count() - 1) gotCurrent = True if not gotCurrent: self.ui.formatList.setCurrentRow(0) def exportFormatChanged(self, item, prev): if item is None: self.currentExporter = None self.ui.paramTree.clear() return expClass = item.expClass exp = expClass(item=self.ui.itemTree.currentItem().gitem) params = exp.parameters() if params is None: self.ui.paramTree.clear() else: self.ui.paramTree.setParameters(params) self.currentExporter = exp self.ui.copyBtn.setEnabled(exp.allowCopy) def exportClicked(self): self.selectBox.hide() self.currentExporter.export() def copyClicked(self): self.selectBox.hide() self.currentExporter.export(copy=True) def close(self): self.selectBox.setVisible(False) self.setVisible(False) def closeEvent(self, event): self.close() super().closeEvent(event) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/GraphicsScene/exportDialogTemplate.ui000066400000000000000000000050301421045507400270630ustar00rootroot00000000000000 Form 0 0 241 367 Export 0 Item to export: false 1 Export format Export Close 2 false 1 Export options Copy ParameterTree QTreeWidget

      ..parametertree
      pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt5.py000066400000000000000000000055251421045507400302510ustar00rootroot00000000000000 # Form implementation generated from reading ui file '.\exportDialogTemplate.ui' # # Created by: PyQt5 UI code generator 5.15.4 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(241, 367) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.label = QtWidgets.QLabel(Form) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 0, 0, 1, 3) self.itemTree = QtWidgets.QTreeWidget(Form) self.itemTree.setObjectName("itemTree") self.itemTree.headerItem().setText(0, "1") self.itemTree.header().setVisible(False) self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) self.label_2 = QtWidgets.QLabel(Form) self.label_2.setObjectName("label_2") self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) self.formatList = QtWidgets.QListWidget(Form) self.formatList.setObjectName("formatList") self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) self.exportBtn = QtWidgets.QPushButton(Form) self.exportBtn.setObjectName("exportBtn") self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) self.closeBtn = QtWidgets.QPushButton(Form) self.closeBtn.setObjectName("closeBtn") self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) self.paramTree = ParameterTree(Form) self.paramTree.setColumnCount(2) self.paramTree.setObjectName("paramTree") self.paramTree.headerItem().setText(0, "1") self.paramTree.header().setVisible(False) self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) self.label_3 = QtWidgets.QLabel(Form) self.label_3.setObjectName("label_3") self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) self.copyBtn = QtWidgets.QPushButton(Form) self.copyBtn.setObjectName("copyBtn") self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Export")) self.label.setText(_translate("Form", "Item to export:")) self.label_2.setText(_translate("Form", "Export format")) self.exportBtn.setText(_translate("Form", "Export")) self.closeBtn.setText(_translate("Form", "Close")) self.label_3.setText(_translate("Form", "Export options")) self.copyBtn.setText(_translate("Form", "Copy")) from ..parametertree import ParameterTree pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt6.py000066400000000000000000000055541421045507400302540ustar00rootroot00000000000000# Form implementation generated from reading ui file '../pyqtgraph/GraphicsScene/exportDialogTemplate.ui' # # Created by: PyQt6 UI code generator 6.1.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(241, 367) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.label = QtWidgets.QLabel(Form) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 0, 0, 1, 3) self.itemTree = QtWidgets.QTreeWidget(Form) self.itemTree.setObjectName("itemTree") self.itemTree.headerItem().setText(0, "1") self.itemTree.header().setVisible(False) self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) self.label_2 = QtWidgets.QLabel(Form) self.label_2.setObjectName("label_2") self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) self.formatList = QtWidgets.QListWidget(Form) self.formatList.setObjectName("formatList") self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) self.exportBtn = QtWidgets.QPushButton(Form) self.exportBtn.setObjectName("exportBtn") self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) self.closeBtn = QtWidgets.QPushButton(Form) self.closeBtn.setObjectName("closeBtn") self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) self.paramTree = ParameterTree(Form) self.paramTree.setColumnCount(2) self.paramTree.setObjectName("paramTree") self.paramTree.headerItem().setText(0, "1") self.paramTree.header().setVisible(False) self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) self.label_3 = QtWidgets.QLabel(Form) self.label_3.setObjectName("label_3") self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) self.copyBtn = QtWidgets.QPushButton(Form) self.copyBtn.setObjectName("copyBtn") self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Export")) self.label.setText(_translate("Form", "Item to export:")) self.label_2.setText(_translate("Form", "Export format")) self.exportBtn.setText(_translate("Form", "Export")) self.closeBtn.setText(_translate("Form", "Close")) self.label_3.setText(_translate("Form", "Export options")) self.copyBtn.setText(_translate("Form", "Copy")) from ..parametertree import ParameterTree pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside2.py000066400000000000000000000064161421045507400305460ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'exportDialogTemplate.ui' ## ## Created by: Qt User Interface Compiler version 5.15.2 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide2.QtCore import * from PySide2.QtGui import * from PySide2.QtWidgets import * from ..parametertree import ParameterTree class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(241, 367) self.gridLayout = QGridLayout(Form) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName(u"gridLayout") self.label = QLabel(Form) self.label.setObjectName(u"label") self.gridLayout.addWidget(self.label, 0, 0, 1, 3) self.itemTree = QTreeWidget(Form) __qtreewidgetitem = QTreeWidgetItem() __qtreewidgetitem.setText(0, u"1"); self.itemTree.setHeaderItem(__qtreewidgetitem) self.itemTree.setObjectName(u"itemTree") self.itemTree.header().setVisible(False) self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) self.label_2 = QLabel(Form) self.label_2.setObjectName(u"label_2") self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) self.formatList = QListWidget(Form) self.formatList.setObjectName(u"formatList") self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) self.exportBtn = QPushButton(Form) self.exportBtn.setObjectName(u"exportBtn") self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) self.closeBtn = QPushButton(Form) self.closeBtn.setObjectName(u"closeBtn") self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) self.paramTree = ParameterTree(Form) __qtreewidgetitem1 = QTreeWidgetItem() __qtreewidgetitem1.setText(0, u"1"); self.paramTree.setHeaderItem(__qtreewidgetitem1) self.paramTree.setObjectName(u"paramTree") self.paramTree.setColumnCount(2) self.paramTree.header().setVisible(False) self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) self.label_3 = QLabel(Form) self.label_3.setObjectName(u"label_3") self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) self.copyBtn = QPushButton(Form) self.copyBtn.setObjectName(u"copyBtn") self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"Export", None)) self.label.setText(QCoreApplication.translate("Form", u"Item to export:", None)) self.label_2.setText(QCoreApplication.translate("Form", u"Export format", None)) self.exportBtn.setText(QCoreApplication.translate("Form", u"Export", None)) self.closeBtn.setText(QCoreApplication.translate("Form", u"Close", None)) self.label_3.setText(QCoreApplication.translate("Form", u"Export options", None)) self.copyBtn.setText(QCoreApplication.translate("Form", u"Copy", None)) # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside6.py000066400000000000000000000064151421045507400305510ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'exportDialogTemplate.ui' ## ## Created by: Qt User Interface Compiler version 6.1.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * from ..parametertree import ParameterTree class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(241, 367) self.gridLayout = QGridLayout(Form) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName(u"gridLayout") self.label = QLabel(Form) self.label.setObjectName(u"label") self.gridLayout.addWidget(self.label, 0, 0, 1, 3) self.itemTree = QTreeWidget(Form) __qtreewidgetitem = QTreeWidgetItem() __qtreewidgetitem.setText(0, u"1"); self.itemTree.setHeaderItem(__qtreewidgetitem) self.itemTree.setObjectName(u"itemTree") self.itemTree.header().setVisible(False) self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) self.label_2 = QLabel(Form) self.label_2.setObjectName(u"label_2") self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) self.formatList = QListWidget(Form) self.formatList.setObjectName(u"formatList") self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) self.exportBtn = QPushButton(Form) self.exportBtn.setObjectName(u"exportBtn") self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) self.closeBtn = QPushButton(Form) self.closeBtn.setObjectName(u"closeBtn") self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) self.paramTree = ParameterTree(Form) __qtreewidgetitem1 = QTreeWidgetItem() __qtreewidgetitem1.setText(0, u"1"); self.paramTree.setHeaderItem(__qtreewidgetitem1) self.paramTree.setObjectName(u"paramTree") self.paramTree.setColumnCount(2) self.paramTree.header().setVisible(False) self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) self.label_3 = QLabel(Form) self.label_3.setObjectName(u"label_3") self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) self.copyBtn = QPushButton(Form) self.copyBtn.setObjectName(u"copyBtn") self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"Export", None)) self.label.setText(QCoreApplication.translate("Form", u"Item to export:", None)) self.label_2.setText(QCoreApplication.translate("Form", u"Export format", None)) self.exportBtn.setText(QCoreApplication.translate("Form", u"Export", None)) self.closeBtn.setText(QCoreApplication.translate("Form", u"Close", None)) self.label_3.setText(QCoreApplication.translate("Form", u"Export options", None)) self.copyBtn.setText(QCoreApplication.translate("Form", u"Copy", None)) # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/GraphicsScene/mouseEvents.py000066400000000000000000000336731421045507400252740ustar00rootroot00000000000000__all__ = ["MouseDragEvent", "MouseClickEvent", "HoverEvent"] import weakref from time import perf_counter from ..Point import Point from ..Qt import QtCore class MouseDragEvent(object): """ Instances of this class are delivered to items in a :class:`GraphicsScene ` via their mouseDragEvent() method when the item is being mouse-dragged. """ def __init__(self, moveEvent, pressEvent, lastEvent, start=False, finish=False): self.start = start self.finish = finish self.accepted = False self.currentItem = None self._buttonDownScenePos = {} self._buttonDownScreenPos = {} for btn in [QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.MouseButton.MiddleButton, QtCore.Qt.MouseButton.RightButton]: self._buttonDownScenePos[btn] = moveEvent.buttonDownScenePos(btn) self._buttonDownScreenPos[btn] = moveEvent.buttonDownScreenPos(btn) self._scenePos = moveEvent.scenePos() self._screenPos = moveEvent.screenPos() if lastEvent is None: self._lastScenePos = pressEvent.scenePos() self._lastScreenPos = pressEvent.screenPos() else: self._lastScenePos = lastEvent.scenePos() self._lastScreenPos = lastEvent.screenPos() self._buttons = moveEvent.buttons() self._button = pressEvent.button() self._modifiers = moveEvent.modifiers() self.acceptedItem = None def accept(self): """An item should call this method if it can handle the event. This will prevent the event being delivered to any other items.""" self.accepted = True self.acceptedItem = self.currentItem def ignore(self): """An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items.""" self.accepted = False def isAccepted(self): return self.accepted def scenePos(self): """Return the current scene position of the mouse.""" return Point(self._scenePos) def screenPos(self): """Return the current screen position (pixels relative to widget) of the mouse.""" return Point(self._screenPos) def buttonDownScenePos(self, btn=None): """ Return the scene position of the mouse at the time *btn* was pressed. If *btn* is omitted, then the button that initiated the drag is assumed. """ if btn is None: btn = self.button() return Point(self._buttonDownScenePos[btn]) def buttonDownScreenPos(self, btn=None): """ Return the screen position (pixels relative to widget) of the mouse at the time *btn* was pressed. If *btn* is omitted, then the button that initiated the drag is assumed. """ if btn is None: btn = self.button() return Point(self._buttonDownScreenPos[btn]) def lastScenePos(self): """ Return the scene position of the mouse immediately prior to this event. """ return Point(self._lastScenePos) def lastScreenPos(self): """ Return the screen position of the mouse immediately prior to this event. """ return Point(self._lastScreenPos) def buttons(self): """ Return the buttons currently pressed on the mouse. (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) """ return self._buttons def button(self): """Return the button that initiated the drag (may be different from the buttons currently pressed) (see QGraphicsSceneMouseEvent::button in the Qt documentation) """ return self._button def pos(self): """ Return the current position of the mouse in the coordinate system of the item that the event was delivered to. """ return Point(self.currentItem.mapFromScene(self._scenePos)) def lastPos(self): """ Return the previous position of the mouse in the coordinate system of the item that the event was delivered to. """ return Point(self.currentItem.mapFromScene(self._lastScenePos)) def buttonDownPos(self, btn=None): """ Return the position of the mouse at the time the drag was initiated in the coordinate system of the item that the event was delivered to. """ if btn is None: btn = self.button() return Point(self.currentItem.mapFromScene(self._buttonDownScenePos[btn])) def isStart(self): """Returns True if this event is the first since a drag was initiated.""" return self.start def isFinish(self): """Returns False if this is the last event in a drag. Note that this event will have the same position as the previous one.""" return self.finish def __repr__(self): if self.currentItem is None: lp = self._lastScenePos p = self._scenePos else: lp = self.lastPos() p = self.pos() return "(%g,%g) buttons=%s start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), str(self.buttons()), str(self.isStart()), str(self.isFinish())) def modifiers(self): """Return any keyboard modifiers currently pressed. (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) """ return self._modifiers class MouseClickEvent(object): """ Instances of this class are delivered to items in a :class:`GraphicsScene ` via their mouseClickEvent() method when the item is clicked. """ def __init__(self, pressEvent, double=False): self.accepted = False self.currentItem = None self._double = double self._scenePos = pressEvent.scenePos() self._screenPos = pressEvent.screenPos() self._button = pressEvent.button() self._buttons = pressEvent.buttons() self._modifiers = pressEvent.modifiers() self._time = perf_counter() self.acceptedItem = None def accept(self): """An item should call this method if it can handle the event. This will prevent the event being delivered to any other items.""" self.accepted = True self.acceptedItem = self.currentItem def ignore(self): """An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items.""" self.accepted = False def isAccepted(self): return self.accepted def scenePos(self): """Return the current scene position of the mouse.""" return Point(self._scenePos) def screenPos(self): """Return the current screen position (pixels relative to widget) of the mouse.""" return Point(self._screenPos) def buttons(self): """ Return the buttons currently pressed on the mouse. (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) """ return self._buttons def button(self): """Return the mouse button that generated the click event. (see QGraphicsSceneMouseEvent::button in the Qt documentation) """ return self._button def double(self): """Return True if this is a double-click.""" return self._double def pos(self): """ Return the current position of the mouse in the coordinate system of the item that the event was delivered to. """ return Point(self.currentItem.mapFromScene(self._scenePos)) def lastPos(self): """ Return the previous position of the mouse in the coordinate system of the item that the event was delivered to. """ return Point(self.currentItem.mapFromScene(self._lastScenePos)) def modifiers(self): """Return any keyboard modifiers currently pressed. (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) """ return self._modifiers def __repr__(self): try: if self.currentItem is None: p = self._scenePos else: p = self.pos() return "" % (p.x(), p.y(), str(self.button())) except: return "" % (str(self.button())) def time(self): return self._time class HoverEvent(object): """ Instances of this class are delivered to items in a :class:`GraphicsScene ` via their hoverEvent() method when the mouse is hovering over the item. This event class both informs items that the mouse cursor is nearby and allows items to communicate with one another about whether each item will accept *potential* mouse events. It is common for multiple overlapping items to receive hover events and respond by changing their appearance. This can be misleading to the user since, in general, only one item will respond to mouse events. To avoid this, items make calls to event.acceptClicks(button) and/or acceptDrags(button). Each item may make multiple calls to acceptClicks/Drags, each time for a different button. If the method returns True, then the item is guaranteed to be the recipient of the claimed event IF the user presses the specified mouse button before moving. If claimEvent returns False, then this item is guaranteed NOT to get the specified event (because another has already claimed it) and the item should change its appearance accordingly. event.isEnter() returns True if the mouse has just entered the item's shape; event.isExit() returns True if the mouse has just left. """ def __init__(self, moveEvent, acceptable): self.enter = False self.acceptable = acceptable self.exit = False self.__clickItems = weakref.WeakValueDictionary() self.__dragItems = weakref.WeakValueDictionary() self.currentItem = None if moveEvent is not None: self._scenePos = moveEvent.scenePos() self._screenPos = moveEvent.screenPos() self._lastScenePos = moveEvent.lastScenePos() self._lastScreenPos = moveEvent.lastScreenPos() self._buttons = moveEvent.buttons() self._modifiers = moveEvent.modifiers() else: self.exit = True def isEnter(self): """Returns True if the mouse has just entered the item's shape""" return self.enter def isExit(self): """Returns True if the mouse has just exited the item's shape""" return self.exit def acceptClicks(self, button): """Inform the scene that the item (that the event was delivered to) would accept a mouse click event if the user were to click before moving the mouse again. Returns True if the request is successful, otherwise returns False (indicating that some other item would receive an incoming click). """ if not self.acceptable: return False if button not in self.__clickItems: self.__clickItems[button] = self.currentItem return True return False def acceptDrags(self, button): """Inform the scene that the item (that the event was delivered to) would accept a mouse drag event if the user were to drag before the next hover event. Returns True if the request is successful, otherwise returns False (indicating that some other item would receive an incoming drag event). """ if not self.acceptable: return False if button not in self.__dragItems: self.__dragItems[button] = self.currentItem return True return False def scenePos(self): """Return the current scene position of the mouse.""" return Point(self._scenePos) def screenPos(self): """Return the current screen position of the mouse.""" return Point(self._screenPos) def lastScenePos(self): """Return the previous scene position of the mouse.""" return Point(self._lastScenePos) def lastScreenPos(self): """Return the previous screen position of the mouse.""" return Point(self._lastScreenPos) def buttons(self): """ Return the buttons currently pressed on the mouse. (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) """ return self._buttons def pos(self): """ Return the current position of the mouse in the coordinate system of the item that the event was delivered to. """ return Point(self.currentItem.mapFromScene(self._scenePos)) def lastPos(self): """ Return the previous position of the mouse in the coordinate system of the item that the event was delivered to. """ return Point(self.currentItem.mapFromScene(self._lastScenePos)) def __repr__(self): if self.exit: return "" if self.currentItem is None: lp = self._lastScenePos p = self._scenePos else: lp = self.lastPos() p = self.pos() return "(%g,%g) buttons=%s enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), str(self.buttons()), str(self.isEnter()), str(self.isExit())) def modifiers(self): """Return any keyboard modifiers currently pressed. (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) """ return self._modifiers def clickItems(self): return self.__clickItems def dragItems(self): return self.__dragItems pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/PlotData.py000066400000000000000000000030441421045507400217360ustar00rootroot00000000000000import numpy as np class PlotData(object): """ Class used for managing plot data - allows data sharing between multiple graphics items (curve, scatter, graph..) - each item may define the columns it needs - column groupings ('pos' or x, y, z) - efficiently appendable - log, fft transformations - color mode conversion (float/byte/qcolor) - pen/brush conversion - per-field cached masking - allows multiple masking fields (different graphics need to mask on different criteria) - removal of nan/inf values - option for single value shared by entire column - cached downsampling - cached min / max / hasnan / isuniform """ def __init__(self): self.fields = {} self.maxVals = {} ## cache for max/min self.minVals = {} def addFields(self, **fields): for f in fields: if f not in self.fields: self.fields[f] = None def hasField(self, f): return f in self.fields def __getitem__(self, field): return self.fields[field] def __setitem__(self, field, val): self.fields[field] = val def max(self, field): mx = self.maxVals.get(field, None) if mx is None: mx = np.max(self[field]) self.maxVals[field] = mx return mx def min(self, field): mn = self.minVals.get(field, None) if mn is None: mn = np.min(self[field]) self.minVals[field] = mn return mn pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Point.py000066400000000000000000000106041421045507400213170ustar00rootroot00000000000000""" Point.py - Extension of QPointF which adds a few missing methods. Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ from math import atan2, degrees, hypot from .Qt import QtCore class Point(QtCore.QPointF): """Extension of QPointF which adds a few missing methods.""" __slots__ = () def __init__(self, *args): if len(args) == 1: if isinstance(args[0], (QtCore.QSize, QtCore.QSizeF)): super().__init__(float(args[0].width()), float(args[0].height())) return elif isinstance(args[0], (int, float)): super().__init__(float(args[0]), float(args[0])) return elif hasattr(args[0], '__getitem__'): super().__init__(float(args[0][0]), float(args[0][1])) return elif len(args) == 2: super().__init__(args[0], args[1]) return super().__init__(*args) def __len__(self): return 2 def __reduce__(self): return (Point, (self.x(), self.y())) def __getitem__(self, i): if i == 0: return self.x() elif i == 1: return self.y() else: raise IndexError("Point has no index %s" % str(i)) def __iter__(self): yield(self.x()) yield(self.y()) def __setitem__(self, i, x): if i == 0: return self.setX(x) elif i == 1: return self.setY(x) else: raise IndexError("Point has no index %s" % str(i)) def __radd__(self, a): return self._math_('__radd__', a) def __add__(self, a): return self._math_('__add__', a) def __rsub__(self, a): return self._math_('__rsub__', a) def __sub__(self, a): return self._math_('__sub__', a) def __rmul__(self, a): return self._math_('__rmul__', a) def __mul__(self, a): return self._math_('__mul__', a) def __rdiv__(self, a): return self._math_('__rdiv__', a) def __div__(self, a): return self._math_('__div__', a) def __truediv__(self, a): return self._math_('__truediv__', a) def __rtruediv__(self, a): return self._math_('__rtruediv__', a) def __rpow__(self, a): return self._math_('__rpow__', a) def __pow__(self, a): return self._math_('__pow__', a) def _math_(self, op, x): if not isinstance(x, QtCore.QPointF): x = Point(x) return Point(getattr(self.x(), op)(x.x()), getattr(self.y(), op)(x.y())) def length(self): """Returns the vector length of this Point.""" return hypot(self.x(), self.y()) # length def norm(self): """Returns a vector in the same direction with unit length.""" return self / self.length() def angle(self, a, units="degrees"): """ Returns the angle in degrees from the vector a to self. Parameters ---------- a : Point, QPointF or QPoint The Point to return the angle with units : str, optional The units with which to compute the angle with, "degrees" or "radians", default "degrees" Returns ------- float The angle between two vectors """ rads = atan2(self.y(), self.x()) - atan2(a.y(), a.x()) if units == "radians": return rads return degrees(rads) def dot(self, a): """Returns the dot product of a and this Point.""" if not isinstance(a, QtCore.QPointF): a = Point(a) return Point.dotProduct(self, a) def cross(self, a): if not isinstance(a, QtCore.QPointF): a = Point(a) return self.x() * a.y() - self.y() * a.x() def proj(self, b): """Return the projection of this vector onto the vector b""" b1 = b.norm() return self.dot(b1) * b1 def __repr__(self): return "Point(%f, %f)" % (self.x(), self.y()) def min(self): return min(self.x(), self.y()) def max(self): return max(self.x(), self.y()) def copy(self): return Point(self) def toQPoint(self): return self.toPoint() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Qt/000077500000000000000000000000001421045507400202375ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Qt/QtCore/000077500000000000000000000000001421045507400214345ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Qt/QtCore/__init__.py000066400000000000000000000000001421045507400235330ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Qt/QtGui/000077500000000000000000000000001421045507400212705ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Qt/QtGui/__init__.py000066400000000000000000000000001421045507400233670ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Qt/QtWidgets/000077500000000000000000000000001421045507400221525ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Qt/QtWidgets/__init__.py000066400000000000000000000000001421045507400242510ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Qt/__init__.py000066400000000000000000000344011421045507400223520ustar00rootroot00000000000000""" This module exists to smooth out some of the differences between Qt versions. * Automatically import Qt lib depending on availability * Allow you to import QtCore/QtGui from pyqtgraph.Qt without specifying which Qt wrapper you want to use. """ import os import re import subprocess import sys import time import warnings PYSIDE = 'PySide' PYSIDE2 = 'PySide2' PYSIDE6 = 'PySide6' PYQT4 = 'PyQt4' PYQT5 = 'PyQt5' PYQT6 = 'PyQt6' QT_LIB = os.getenv('PYQTGRAPH_QT_LIB') if QT_LIB is not None: try: __import__(QT_LIB) except ModuleNotFoundError: raise ModuleNotFoundError(f"Environment variable PYQTGRAPH_QT_LIB is set to '{os.getenv('PYQTGRAPH_QT_LIB')}', but no module with this name was found.") ## Automatically determine which Qt package to use (unless specified by ## environment variable). ## This is done by first checking to see whether one of the libraries ## is already imported. If not, then attempt to import in the order ## specified in libOrder. if QT_LIB is None: libOrder = [PYQT5, PYSIDE2, PYSIDE6, PYQT6] for lib in libOrder: if lib in sys.modules: QT_LIB = lib break if QT_LIB is None: for lib in libOrder: try: __import__(lib) QT_LIB = lib break except ImportError: pass if QT_LIB is None: raise Exception("PyQtGraph requires one of PyQt5, PyQt6, PySide2 or PySide6; none of these packages could be imported.") class FailedImport(object): """Used to defer ImportErrors until we are sure the module is needed. """ def __init__(self, err): self.err = err def __getattr__(self, attr): raise self.err # Make a loadUiType function like PyQt has # Credit: # http://stackoverflow.com/questions/4442286/python-code-genration-with-pyside-uic/14195313#14195313 class _StringIO(object): """Alternative to built-in StringIO needed to circumvent unicode/ascii issues""" def __init__(self): self.data = [] def write(self, data): self.data.append(data) def getvalue(self): return ''.join(map(str, self.data)).encode('utf8') def _loadUiType(uiFile): """ PySide lacks a "loadUiType" command like PyQt4's, so we have to convert the ui file to py code in-memory first and then execute it in a special frame to retrieve the form_class. from stackoverflow: http://stackoverflow.com/a/14195313/3781327 seems like this might also be a legitimate solution, but I'm not sure how to make PyQt4 and pyside look the same... http://stackoverflow.com/a/8717832 """ pyside2uic = None if QT_LIB == PYSIDE2: try: import pyside2uic except ImportError: # later versions of pyside2 have dropped pyside2uic; use the uic binary instead. pyside2uic = None if pyside2uic is None: pyside2version = tuple(map(int, PySide2.__version__.split("."))) if (5, 14) <= pyside2version < (5, 14, 2, 2): warnings.warn('For UI compilation, it is recommended to upgrade to PySide >= 5.15') # get class names from ui file import xml.etree.ElementTree as xml parsed = xml.parse(uiFile) widget_class = parsed.find('widget').get('class') form_class = parsed.find('class').text # convert ui file to python code if pyside2uic is None: uic_executable = QT_LIB.lower() + '-uic' uipy = subprocess.check_output([uic_executable, uiFile]) else: o = _StringIO() with open(uiFile, 'r') as f: pyside2uic.compileUi(f, o, indent=0) uipy = o.getvalue() # execute python code pyc = compile(uipy, '', 'exec') frame = {} exec(pyc, frame) # fetch the base_class and form class based on their type in the xml from designer form_class = frame['Ui_%s'%form_class] base_class = eval('QtWidgets.%s'%widget_class) return form_class, base_class # For historical reasons, pyqtgraph maintains a Qt4-ish interface back when # there wasn't a QtWidgets module. This _was_ done by monkey-patching all of # QtWidgets into the QtGui module. This monkey-patching modifies QtGui at a # global level. # To avoid this, we now maintain a local "mirror" of QtCore, QtGui and QtWidgets. # Thus, when monkey-patching happens later on in this file, they will only affect # the local modules and not the global modules. def _copy_attrs(src, dst): for o in dir(src): if not hasattr(dst, o): setattr(dst, o, getattr(src, o)) from . import QtCore, QtGui, QtWidgets if QT_LIB == PYQT5: # We're using PyQt5 which has a different structure so we're going to use a shim to # recreate the Qt4 structure for Qt5 import PyQt5.QtCore import PyQt5.QtGui import PyQt5.QtWidgets _copy_attrs(PyQt5.QtCore, QtCore) _copy_attrs(PyQt5.QtGui, QtGui) _copy_attrs(PyQt5.QtWidgets, QtWidgets) 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 except ImportError as err: QtSvg = FailedImport(err) try: from PyQt5 import QtTest except ImportError as err: QtTest = FailedImport(err) VERSION_INFO = 'PyQt5 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR elif QT_LIB == PYQT6: import PyQt6.QtCore import PyQt6.QtGui import PyQt6.QtWidgets _copy_attrs(PyQt6.QtCore, QtCore) _copy_attrs(PyQt6.QtGui, QtGui) _copy_attrs(PyQt6.QtWidgets, QtWidgets) from PyQt6 import sip, uic try: from PyQt6 import QtSvg except ImportError as err: QtSvg = FailedImport(err) try: from PyQt6 import QtOpenGLWidgets except ImportError as err: QtOpenGLWidgets = FailedImport(err) try: from PyQt6 import QtTest except ImportError as err: QtTest = FailedImport(err) VERSION_INFO = 'PyQt6 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR elif QT_LIB == PYSIDE2: import PySide2.QtCore import PySide2.QtGui import PySide2.QtWidgets _copy_attrs(PySide2.QtCore, QtCore) _copy_attrs(PySide2.QtGui, QtGui) _copy_attrs(PySide2.QtWidgets, QtWidgets) try: from PySide2 import QtSvg except ImportError as err: QtSvg = FailedImport(err) try: from PySide2 import QtTest except ImportError as err: QtTest = FailedImport(err) import PySide2 import shiboken2 as shiboken VERSION_INFO = 'PySide2 ' + PySide2.__version__ + ' Qt ' + QtCore.__version__ elif QT_LIB == PYSIDE6: import PySide6.QtCore import PySide6.QtGui import PySide6.QtWidgets _copy_attrs(PySide6.QtCore, QtCore) _copy_attrs(PySide6.QtGui, QtGui) _copy_attrs(PySide6.QtWidgets, QtWidgets) try: from PySide6 import QtSvg except ImportError as err: QtSvg = FailedImport(err) try: from PySide6 import QtOpenGLWidgets except ImportError as err: QtOpenGLWidgets = FailedImport(err) try: from PySide6 import QtTest except ImportError as err: QtTest = FailedImport(err) import PySide6 import shiboken6 as shiboken VERSION_INFO = 'PySide6 ' + PySide6.__version__ + ' Qt ' + QtCore.__version__ else: raise ValueError("Invalid Qt lib '%s'" % QT_LIB) # common to PyQt5, PyQt6, PySide2 and PySide6 if QT_LIB in [PYQT5, PYQT6, PYSIDE2, PYSIDE6]: # We're using Qt5 which has a different structure so we're going to use a shim to # recreate the Qt4 structure if QT_LIB in [PYQT5, PYSIDE2]: __QGraphicsItem_scale = QtWidgets.QGraphicsItem.scale def scale(self, *args): warnings.warn( "Deprecated Qt API, will be removed in 0.13.0.", DeprecationWarning, stacklevel=2 ) if args: sx, sy = args tr = self.transform() tr.scale(sx, sy) self.setTransform(tr) else: return __QGraphicsItem_scale(self) QtWidgets.QGraphicsItem.scale = scale def rotate(self, angle): warnings.warn( "Deprecated Qt API, will be removed in 0.13.0.", DeprecationWarning, stacklevel=2 ) tr = self.transform() tr.rotate(angle) self.setTransform(tr) QtWidgets.QGraphicsItem.rotate = rotate def translate(self, dx, dy): warnings.warn( "Deprecated Qt API, will be removed in 0.13.0.", DeprecationWarning, stacklevel=2 ) tr = self.transform() tr.translate(dx, dy) self.setTransform(tr) QtWidgets.QGraphicsItem.translate = translate def setMargin(self, i): warnings.warn( "Deprecated Qt API, will be removed in 0.13.0.", DeprecationWarning, stacklevel=2 ) self.setContentsMargins(i, i, i, i) QtWidgets.QGridLayout.setMargin = setMargin def setResizeMode(self, *args): warnings.warn( "Deprecated Qt API, will be removed in 0.13.0.", DeprecationWarning, stacklevel=2 ) self.setSectionResizeMode(*args) QtWidgets.QHeaderView.setResizeMode = setResizeMode # Import all QtWidgets objects into QtGui _fallbacks = dir(QtWidgets) def lazyGetattr(name): if not (name in _fallbacks and name.startswith('Q')): raise AttributeError(f"module 'QtGui' has no attribute '{name}'") # This whitelist is attrs which are not shared between PyQt6.QtGui and PyQt5.QtGui, but which can be found on # one of the QtWidgets. whitelist = [ "QAction", "QActionGroup", "QFileSystemModel", "QPagedPaintDevice", "QPaintEvent", "QShortcut", "QUndoCommand", "QUndoGroup", "QUndoStack", ] if name not in whitelist: warnings.warn( "Accessing pyqtgraph.QtWidgets through QtGui is deprecated and will be removed sometime" f" after May 2022. Use QtWidgets.{name} instead.", DeprecationWarning, stacklevel=2 ) attr = getattr(QtWidgets, name) setattr(QtGui, name, attr) return attr QtGui.__getattr__ = lazyGetattr QtWidgets.QApplication.setGraphicsSystem = None if QT_LIB in [PYQT6, PYSIDE6]: # We're using Qt6 which has a different structure so we're going to use a shim to # recreate the Qt5 structure if not isinstance(QtOpenGLWidgets, FailedImport): QtWidgets.QOpenGLWidget = QtOpenGLWidgets.QOpenGLWidget # Common to PySide2 and 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): if not hasattr(QtTest.QTest, 'qWait'): @staticmethod def qWait(msec): start = time.time() QtWidgets.QApplication.processEvents() while time.time() < start + msec * 0.001: QtWidgets.QApplication.processEvents() QtTest.QTest.qWait = qWait # Common to PyQt5 and PyQt6 if QT_LIB in [PYQT5, PYQT6]: QtVersion = QtCore.QT_VERSION_STR # PyQt, starting in v5.5, calls qAbort when an exception is raised inside # a slot. To maintain backward compatibility (and sanity for interactive # users), we install a global exception hook to override this behavior. if sys.excepthook == sys.__excepthook__: sys_excepthook = sys.excepthook def pyqt_qabort_override(*args, **kwds): return sys_excepthook(*args, **kwds) sys.excepthook = pyqt_qabort_override def isQObjectAlive(obj): return not sip.isdeleted(obj) loadUiType = uic.loadUiType QtCore.Signal = QtCore.pyqtSignal # USE_XXX variables are deprecated USE_PYSIDE = QT_LIB == PYSIDE USE_PYQT4 = QT_LIB == PYQT4 USE_PYQT5 = QT_LIB == PYQT5 ## Make sure we have Qt >= 5.12 versionReq = [5, 12] m = re.match(r'(\d+)\.(\d+).*', QtVersion) if m is not None and list(map(int, m.groups())) < versionReq: print(list(map(int, m.groups()))) raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion)) App = QtWidgets.QApplication # subclassing QApplication causes segfaults on PySide{2, 6} / Python 3.8.7+ QAPP = None def mkQApp(name=None): """ Creates new QApplication or returns current instance if existing. ============== ======================================================== **Arguments:** name (str) Application name, passed to Qt ============== ======================================================== """ global QAPP def onPaletteChange(palette): color = palette.base().color() app = QtWidgets.QApplication.instance() darkMode = color.lightnessF() < 0.5 app.setProperty('darkMode', darkMode) QAPP = QtWidgets.QApplication.instance() if QAPP is None: # hidpi handling qtVersionCompare = tuple(map(int, QtVersion.split("."))) if qtVersionCompare > (6, 0): # Qt6 seems to support hidpi without needing to do anything so continue pass elif qtVersionCompare > (5, 14): os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) else: # qt 5.12 and 5.13 QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps) QAPP = QtWidgets.QApplication(sys.argv or ["pyqtgraph"]) QAPP.paletteChanged.connect(onPaletteChange) QAPP.paletteChanged.emit(QAPP.palette()) if name is not None: QAPP.setApplicationName(name) return QAPP # exec() is used within _loadUiType, so we define as exec_() here and rename in pg namespace def exec_(): app = mkQApp() return app.exec() if hasattr(app, 'exec') else app.exec_() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/SRTTransform.py000066400000000000000000000177501421045507400226030ustar00rootroot00000000000000import warnings from math import atan2, degrees import numpy as np from .Point import Point from .Qt import QtCore, QtGui, QtWidgets class SRTTransform(QtGui.QTransform): """Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate This transform has no shear; angles are always preserved. """ def __init__(self, init=None): QtGui.QTransform.__init__(self) self.reset() if init is None: return elif isinstance(init, dict): self.restoreState(init) elif isinstance(init, SRTTransform): self._state = { 'pos': Point(init._state['pos']), 'scale': Point(init._state['scale']), 'angle': init._state['angle'] } self.update() elif isinstance(init, QtGui.QTransform): self.setFromQTransform(init) elif isinstance(init, QtGui.QMatrix4x4): self.setFromMatrix4x4(init) else: raise Exception("Cannot create SRTTransform from input type: %s" % str(type(init))) def getScale(self): return self._state['scale'] def getAngle(self): warnings.warn( 'SRTTransform.getAngle() is deprecated, use SRTTransform.getRotation() instead' 'will be removed in 0.13', DeprecationWarning, stacklevel=2 ) return self.getRotation() def getRotation(self): return self._state['angle'] def getTranslation(self): return self._state['pos'] def reset(self): self._state = { 'pos': Point(0,0), 'scale': Point(1,1), 'angle': 0.0 ## in degrees } self.update() def setFromQTransform(self, tr): p1 = Point(tr.map(0., 0.)) p2 = Point(tr.map(1., 0.)) p3 = Point(tr.map(0., 1.)) dp2 = Point(p2-p1) dp3 = Point(p3-p1) ## detect flipped axes if dp2.angle(dp3, units="radians") > 0: da = 0 sy = -1.0 else: da = 0 sy = 1.0 self._state = { 'pos': Point(p1), 'scale': Point(dp2.length(), dp3.length() * sy), 'angle': degrees(atan2(dp2[1], dp2[0])) + da } self.update() def setFromMatrix4x4(self, m): m = SRTTransform3D(m) angle, axis = m.getRotation() if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1): print("angle: %s axis: %s" % (str(angle), str(axis))) raise Exception("Can only convert 4x4 matrix to 3x3 if rotation is around Z-axis.") self._state = { 'pos': Point(m.getTranslation()), 'scale': Point(m.getScale()), 'angle': angle } self.update() def translate(self, *args): """Acceptable arguments are: x, y [x, y] Point(x,y)""" t = Point(*args) self.setTranslate(self._state['pos']+t) def setTranslate(self, *args): """Acceptable arguments are: x, y [x, y] Point(x,y)""" self._state['pos'] = Point(*args) self.update() def scale(self, *args): """Acceptable arguments are: x, y [x, y] Point(x,y)""" s = Point(*args) self.setScale(self._state['scale'] * s) def setScale(self, *args): """Acceptable arguments are: x, y [x, y] Point(x,y)""" self._state['scale'] = Point(*args) self.update() def rotate(self, angle): """Rotate the transformation by angle (in degrees)""" self.setRotate(self._state['angle'] + angle) def setRotate(self, angle): """Set the transformation rotation to angle (in degrees)""" self._state['angle'] = angle self.update() def __truediv__(self, t): """A / B == B^-1 * A""" dt = t.inverted()[0] * self return SRTTransform(dt) def __div__(self, t): return self.__truediv__(t) def __mul__(self, t): return SRTTransform(QtGui.QTransform.__mul__(self, t)) def saveState(self): p = self._state['pos'] s = self._state['scale'] #if s[0] == 0: #raise Exception('Invalid scale: %s' % str(s)) return {'pos': (p[0], p[1]), 'scale': (s[0], s[1]), 'angle': self._state['angle']} def restoreState(self, state): self._state['pos'] = Point(state.get('pos', (0,0))) self._state['scale'] = Point(state.get('scale', (1.,1.))) self._state['angle'] = state.get('angle', 0) self.update() def update(self): QtGui.QTransform.reset(self) ## modifications to the transform are multiplied on the right, so we need to reverse order here. QtGui.QTransform.translate(self, *self._state['pos']) QtGui.QTransform.rotate(self, self._state['angle']) QtGui.QTransform.scale(self, *self._state['scale']) def __repr__(self): return str(self.saveState()) def matrix(self): return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]]) if __name__ == '__main__': import GraphicsView from . import widgets from .functions import * app = pg.mkQApp() win = QtWidgets.QMainWindow() win.show() cw = GraphicsView.GraphicsView() #cw.enableMouse() win.setCentralWidget(cw) s = QtWidgets.QGraphicsScene() cw.setScene(s) win.resize(600,600) cw.enableMouse() cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) class Item(QtWidgets.QGraphicsItem): def __init__(self): QtWidgets.QGraphicsItem.__init__(self) self.b = QtWidgets.QGraphicsRectItem(20, 20, 20, 20, self) self.b.setPen(QtGui.QPen(mkPen('y'))) self.t1 = QtWidgets.QGraphicsTextItem(self) self.t1.setHtml('R') self.t1.translate(20, 20) self.l1 = QtWidgets.QGraphicsLineItem(10, 0, -10, 0, self) self.l2 = QtWidgets.QGraphicsLineItem(0, 10, 0, -10, self) self.l1.setPen(QtGui.QPen(mkPen('y'))) self.l2.setPen(QtGui.QPen(mkPen('y'))) def boundingRect(self): return QtCore.QRectF() def paint(self, *args): pass #s.addItem(b) #s.addItem(t1) item = Item() s.addItem(item) l1 = QtWidgets.QGraphicsLineItem(10, 0, -10, 0) l2 = QtWidgets.QGraphicsLineItem(0, 10, 0, -10) l1.setPen(QtGui.QPen(mkPen('r'))) l2.setPen(QtGui.QPen(mkPen('r'))) s.addItem(l1) s.addItem(l2) tr1 = SRTTransform() tr2 = SRTTransform() tr3 = QtGui.QTransform() tr3.translate(20, 0) tr3.rotate(45) print("QTransform -> Transform:", SRTTransform(tr3)) print("tr1:", tr1) tr2.translate(20, 0) tr2.rotate(45) print("tr2:", tr2) dt = tr2/tr1 print("tr2 / tr1 = ", dt) print("tr2 * tr1 = ", tr2*tr1) tr4 = SRTTransform() tr4.scale(-1, 1) tr4.rotate(30) print("tr1 * tr4 = ", tr1*tr4) w1 = widgets.TestROI((19,19), (22, 22), invertible=True) #w2 = widgets.TestROI((0,0), (150, 150)) w1.setZValue(10) s.addItem(w1) #s.addItem(w2) w1Base = w1.getState() #w2Base = w2.getState() def update(): tr1 = w1.getGlobalTransform(w1Base) #tr2 = w2.getGlobalTransform(w2Base) item.setTransform(tr1) #def update2(): #tr1 = w1.getGlobalTransform(w1Base) #tr2 = w2.getGlobalTransform(w2Base) #t1.setTransform(tr1) #w1.setState(w1Base) #w1.applyGlobalTransform(tr2) w1.sigRegionChanged.connect(update) #w2.sigRegionChanged.connect(update2) from .SRTTransform3D import SRTTransform3D pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/SRTTransform3D.py000066400000000000000000000252261421045507400227670ustar00rootroot00000000000000from math import atan2, degrees import numpy as np from .Qt import QtCore, QtGui, QtWidgets from .Transform3D import Transform3D from .Vector import Vector class SRTTransform3D(Transform3D): """4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate This transform has no shear; angles are always preserved. """ def __init__(self, init=None): Transform3D.__init__(self) self.reset() if init is None: return if init.__class__ is QtGui.QTransform: init = SRTTransform(init) if isinstance(init, dict): self.restoreState(init) elif isinstance(init, SRTTransform3D): self._state = { 'pos': Vector(init._state['pos']), 'scale': Vector(init._state['scale']), 'angle': init._state['angle'], 'axis': Vector(init._state['axis']), } self.update() elif isinstance(init, SRTTransform): self._state = { 'pos': Vector(init._state['pos']), 'scale': Vector(init._state['scale']), 'angle': init._state['angle'], 'axis': Vector(0, 0, 1), } self._state['scale'][2] = 1.0 self.update() elif isinstance(init, QtGui.QMatrix4x4): self.setFromMatrix(init) else: raise Exception("Cannot build SRTTransform3D from argument type:", type(init)) def getScale(self): return Vector(self._state['scale']) def getRotation(self): """Return (angle, axis) of rotation""" return self._state['angle'], Vector(self._state['axis']) def getTranslation(self): return Vector(self._state['pos']) def reset(self): self._state = { 'pos': Vector(0,0,0), 'scale': Vector(1,1,1), 'angle': 0.0, ## in degrees 'axis': (0, 0, 1) } self.update() def translate(self, *args): """Adjust the translation of this transform""" t = Vector(*args) self.setTranslate(self._state['pos']+t) def setTranslate(self, *args): """Set the translation of this transform""" self._state['pos'] = Vector(*args) self.update() def scale(self, *args): """adjust the scale of this transform""" ## try to prevent accidentally setting 0 scale on z axis if len(args) == 1 and hasattr(args[0], '__len__'): args = args[0] if len(args) == 2: args = args + (1,) s = Vector(*args) self.setScale(self._state['scale'] * s) def setScale(self, *args): """Set the scale of this transform""" if len(args) == 1 and hasattr(args[0], '__len__'): args = args[0] if len(args) == 2: args = args + (1,) self._state['scale'] = Vector(*args) self.update() def rotate(self, angle, axis=(0,0,1)): """Adjust the rotation of this transform""" origAxis = self._state['axis'] if axis[0] == origAxis[0] and axis[1] == origAxis[1] and axis[2] == origAxis[2]: self.setRotate(self._state['angle'] + angle) else: m = QtGui.QMatrix4x4() m.translate(*self._state['pos']) m.rotate(self._state['angle'], *self._state['axis']) m.rotate(angle, *axis) m.scale(*self._state['scale']) self.setFromMatrix(m) def setRotate(self, angle, axis=(0,0,1)): """Set the transformation rotation to angle (in degrees)""" self._state['angle'] = angle self._state['axis'] = Vector(axis) self.update() def setFromMatrix(self, m): """ Set this transform based on the elements of *m* The input matrix must be affine AND have no shear, otherwise the conversion will most likely fail. """ import numpy.linalg for i in range(4): self.setRow(i, m.row(i)) m = self.matrix().reshape(4,4) ## translation is 4th column self._state['pos'] = m[:3,3] ## scale is vector-length of first three columns scale = (m[:3,:3]**2).sum(axis=0)**0.5 ## see whether there is an inversion z = np.cross(m[0, :3], m[1, :3]) if np.dot(z, m[2, :3]) < 0: scale[1] *= -1 ## doesn't really matter which axis we invert self._state['scale'] = scale ## rotation axis is the eigenvector with eigenvalue=1 r = m[:3, :3] / scale[np.newaxis, :] try: evals, evecs = numpy.linalg.eig(r) except: print("Rotation matrix: %s" % str(r)) print("Scale: %s" % str(scale)) print("Original matrix: %s" % str(m)) raise eigIndex = np.argwhere(np.abs(evals-1) < 1e-6) if len(eigIndex) < 1: print("eigenvalues: %s" % str(evals)) print("eigenvectors: %s" % str(evecs)) print("index: %s, %s" % (str(eigIndex), str(evals-1))) raise Exception("Could not determine rotation axis.") axis = evecs[:,eigIndex[0,0]].real axis /= ((axis**2).sum())**0.5 self._state['axis'] = axis ## trace(r) == 2 cos(angle) + 1, so: cos = (r.trace()-1)*0.5 ## this only gets us abs(angle) ## The off-diagonal values can be used to correct the angle ambiguity, ## but we need to figure out which element to use: axisInd = np.argmax(np.abs(axis)) rInd,sign = [((1,2), -1), ((0,2), 1), ((0,1), -1)][axisInd] ## Then we have r-r.T = sin(angle) * 2 * sign * axis[axisInd]; ## solve for sin(angle) sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd]) ## finally, we get the complete angle from arctan(sin/cos) self._state['angle'] = degrees(atan2(sin, cos)) if self._state['angle'] == 0: self._state['axis'] = (0,0,1) def as2D(self): """Return a QTransform representing the x,y portion of this transform (if possible)""" return SRTTransform(self) #def __div__(self, t): #"""A / B == B^-1 * A""" #dt = t.inverted()[0] * self #return SRTTransform(dt) #def __mul__(self, t): #return SRTTransform(QtGui.QTransform.__mul__(self, t)) def saveState(self): p = self._state['pos'] s = self._state['scale'] ax = self._state['axis'] #if s[0] == 0: #raise Exception('Invalid scale: %s' % str(s)) return { 'pos': (p[0], p[1], p[2]), 'scale': (s[0], s[1], s[2]), 'angle': self._state['angle'], 'axis': (ax[0], ax[1], ax[2]) } def restoreState(self, state): self._state['pos'] = Vector(state.get('pos', (0.,0.,0.))) scale = state.get('scale', (1.,1.,1.)) scale = tuple(scale) + (1.,) * (3-len(scale)) self._state['scale'] = Vector(scale) self._state['angle'] = state.get('angle', 0.) self._state['axis'] = state.get('axis', (0, 0, 1)) self.update() def update(self): Transform3D.setToIdentity(self) ## modifications to the transform are multiplied on the right, so we need to reverse order here. Transform3D.translate(self, *self._state['pos']) Transform3D.rotate(self, self._state['angle'], *self._state['axis']) Transform3D.scale(self, *self._state['scale']) def __repr__(self): return str(self.saveState()) def matrix(self, nd=3): if nd == 3: return np.array(self.copyDataTo()).reshape(4,4) elif nd == 2: m = np.array(self.copyDataTo()).reshape(4,4) m[2] = m[3] m[:,2] = m[:,3] return m[:3,:3] else: raise Exception("Argument 'nd' must be 2 or 3") if __name__ == '__main__': import GraphicsView from . import widgets from .functions import * app = pg.mkQApp() win = QtWidgets.QMainWindow() win.show() cw = GraphicsView.GraphicsView() #cw.enableMouse() win.setCentralWidget(cw) s = QtWidgets.QGraphicsScene() cw.setScene(s) win.resize(600,600) cw.enableMouse() cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) class Item(QtWidgets.QGraphicsItem): def __init__(self): QtWidgets.QGraphicsItem.__init__(self) self.b = QtWidgets.QGraphicsRectItem(20, 20, 20, 20, self) self.b.setPen(QtGui.QPen(mkPen('y'))) self.t1 = QtWidgets.QGraphicsTextItem(self) self.t1.setHtml('R') self.t1.translate(20, 20) self.l1 = QtWidgets.QGraphicsLineItem(10, 0, -10, 0, self) self.l2 = QtWidgets.QGraphicsLineItem(0, 10, 0, -10, self) self.l1.setPen(QtGui.QPen(mkPen('y'))) self.l2.setPen(QtGui.QPen(mkPen('y'))) def boundingRect(self): return QtCore.QRectF() def paint(self, *args): pass #s.addItem(b) #s.addItem(t1) item = Item() s.addItem(item) l1 = QtWidgets.QGraphicsLineItem(10, 0, -10, 0) l2 = QtWidgets.QGraphicsLineItem(0, 10, 0, -10) l1.setPen(QtGui.QPen(mkPen('r'))) l2.setPen(QtGui.QPen(mkPen('r'))) s.addItem(l1) s.addItem(l2) tr1 = SRTTransform() tr2 = SRTTransform() tr3 = QtGui.QTransform() tr3.translate(20, 0) tr3.rotate(45) print("QTransform -> Transform: %s" % str(SRTTransform(tr3))) print("tr1: %s" % str(tr1)) tr2.translate(20, 0) tr2.rotate(45) print("tr2: %s" % str(tr2)) dt = tr2/tr1 print("tr2 / tr1 = %s" % str(dt)) print("tr2 * tr1 = %s" % str(tr2*tr1)) tr4 = SRTTransform() tr4.scale(-1, 1) tr4.rotate(30) print("tr1 * tr4 = %s" % str(tr1*tr4)) w1 = widgets.TestROI((19,19), (22, 22), invertible=True) #w2 = widgets.TestROI((0,0), (150, 150)) w1.setZValue(10) s.addItem(w1) #s.addItem(w2) w1Base = w1.getState() #w2Base = w2.getState() def update(): tr1 = w1.getGlobalTransform(w1Base) #tr2 = w2.getGlobalTransform(w2Base) item.setTransform(tr1) #def update2(): #tr1 = w1.getGlobalTransform(w1Base) #tr2 = w2.getGlobalTransform(w2Base) #t1.setTransform(tr1) #w1.setState(w1Base) #w1.applyGlobalTransform(tr2) w1.sigRegionChanged.connect(update) #w2.sigRegionChanged.connect(update2) from .SRTTransform import SRTTransform pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/SignalProxy.py000066400000000000000000000070071421045507400225100ustar00rootroot00000000000000import weakref from time import perf_counter from . import ThreadsafeTimer from .functions import SignalBlock from .Qt import QtCore __all__ = ['SignalProxy'] class SignalProxy(QtCore.QObject): """Object which collects rapid-fire signals and condenses them into a single signal or a rate-limited stream of signals. Used, for example, to prevent a SpinBox from generating multiple signals when the mouse wheel is rolled over it. Emits sigDelayed after input signals have stopped for a certain period of time. """ sigDelayed = QtCore.Signal(object) def __init__(self, signal, delay=0.3, rateLimit=0, slot=None): """Initialization arguments: signal - a bound Signal or pyqtSignal instance delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s) slot - Optional function to connect sigDelayed to. rateLimit - (signals/sec) if greater than 0, this allows signals to stream out at a steady rate while they are being received. """ QtCore.QObject.__init__(self) self.delay = delay self.rateLimit = rateLimit self.args = None self.timer = ThreadsafeTimer.ThreadsafeTimer() self.timer.timeout.connect(self.flush) self.lastFlushTime = None self.signal = signal self.signal.connect(self.signalReceived) if slot is not None: self.blockSignal = False self.sigDelayed.connect(slot) self.slot = weakref.ref(slot) else: self.blockSignal = True self.slot = None def setDelay(self, delay): self.delay = delay def signalReceived(self, *args): """Received signal. Cancel previous timer and store args to be forwarded later.""" if self.blockSignal: return self.args = args if self.rateLimit == 0: self.timer.stop() self.timer.start(int(self.delay * 1000) + 1) else: now = perf_counter() if self.lastFlushTime is None: leakTime = 0 else: lastFlush = self.lastFlushTime leakTime = max(0, (lastFlush + (1.0 / self.rateLimit)) - now) self.timer.stop() self.timer.start(int(min(leakTime, self.delay) * 1000) + 1) def flush(self): """If there is a signal queued up, send it now.""" if self.args is None or self.blockSignal: return False args, self.args = self.args, None self.timer.stop() self.lastFlushTime = perf_counter() self.sigDelayed.emit(args) return True def disconnect(self): self.blockSignal = True try: self.signal.disconnect(self.signalReceived) except: pass try: # XXX: This is a weakref, however segfaulting on PySide and # Python 2. We come back later self.sigDelayed.disconnect(self.slot) except: pass finally: self.slot = None def connectSlot(self, slot): """Connect the `SignalProxy` to an external slot""" assert self.slot is None, "Slot was already connected!" self.slot = weakref.ref(slot) self.sigDelayed.connect(slot) self.blockSignal = False def block(self): """Return a SignalBlocker that temporarily blocks input signals to this proxy. """ return SignalBlock(self.signal, self.signalReceived) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/ThreadsafeTimer.py000066400000000000000000000031131421045507400232720ustar00rootroot00000000000000from .Qt import QtCore __all__ = ['ThreadsafeTimer'] class ThreadsafeTimer(QtCore.QObject): """ Thread-safe replacement for QTimer. """ timeout = QtCore.Signal() sigTimerStopRequested = QtCore.Signal() sigTimerStartRequested = QtCore.Signal(object) def __init__(self): QtCore.QObject.__init__(self) self.timer = QtCore.QTimer() self.timer.timeout.connect(self.timerFinished) self.timer.moveToThread(QtCore.QCoreApplication.instance().thread()) self.moveToThread(QtCore.QCoreApplication.instance().thread()) self.sigTimerStopRequested.connect(self.stop, QtCore.Qt.ConnectionType.QueuedConnection) self.sigTimerStartRequested.connect(self.start, QtCore.Qt.ConnectionType.QueuedConnection) def start(self, timeout): isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() if isGuiThread: #print "start timer", self, "from gui thread" self.timer.start(int(timeout)) else: #print "start timer", self, "from remote thread" self.sigTimerStartRequested.emit(timeout) def stop(self): isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() if isGuiThread: #print "stop timer", self, "from gui thread" self.timer.stop() else: #print "stop timer", self, "from remote thread" self.sigTimerStopRequested.emit() def timerFinished(self): self.timeout.emit() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Transform3D.py000066400000000000000000000034541421045507400223750ustar00rootroot00000000000000import numpy as np from . import functions as fn from .Qt import QtGui from .Vector import Vector class Transform3D(QtGui.QMatrix4x4): """ Extension of QMatrix4x4 with some helpful methods added. """ def __init__(self, *args): if len(args) == 1: if isinstance(args[0], (list, tuple, np.ndarray)): args = [x for y in args[0] for x in y] if len(args) != 16: raise TypeError("Single argument to Transform3D must have 16 elements.") elif isinstance(args[0], QtGui.QMatrix4x4): args = list(args[0].copyDataTo()) QtGui.QMatrix4x4.__init__(self, *args) def matrix(self, nd=3): if nd == 3: return np.array(self.copyDataTo()).reshape(4,4) elif nd == 2: m = np.array(self.copyDataTo()).reshape(4,4) m[2] = m[3] m[:,2] = m[:,3] return m[:3,:3] else: raise Exception("Argument 'nd' must be 2 or 3") def map(self, obj): """ Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates """ if isinstance(obj, np.ndarray) and obj.shape[0] in (2,3): if obj.ndim >= 2: return fn.transformCoordinates(self, obj) elif obj.ndim == 1: v = QtGui.QMatrix4x4.map(self, Vector(obj)) return np.array([v.x(), v.y(), v.z()])[:obj.shape[0]] elif isinstance(obj, (list, tuple)): v = QtGui.QMatrix4x4.map(self, Vector(obj)) return type(obj)([v.x(), v.y(), v.z()])[:len(obj)] else: return QtGui.QMatrix4x4.map(self, obj) def inverted(self): inv, b = QtGui.QMatrix4x4.inverted(self) return Transform3D(inv), b pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/Vector.py000066400000000000000000000073701421045507400214760ustar00rootroot00000000000000""" Vector.py - Extension of QVector3D which adds a few missing methods. Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ from math import acos, degrees from . import functions as fn from .Qt import QT_LIB, QtCore, QtGui class Vector(QtGui.QVector3D): """Extension of QVector3D which adds a few helpful methods.""" def __init__(self, *args): """ Handle additional constructions of a Vector ============== ================================================================================================ **Arguments:** *args* Could be any of: * 3 numerics (x, y, and z) * 2 numerics (x, y, and `0` assumed for z) * Either of the previous in a list-like collection * 1 QSizeF (`0` assumed for z) * 1 QPointF (`0` assumed for z) * Any other valid QVector3D init args. ============== ================================================================================================ """ initArgs = args if len(args) == 1: if isinstance(args[0], QtCore.QSizeF): initArgs = (float(args[0].width()), float(args[0].height()), 0) elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF): initArgs = (float(args[0].x()), float(args[0].y()), 0) elif hasattr(args[0], '__getitem__') and not isinstance(args[0], QtGui.QVector3D): vals = list(args[0]) if len(vals) == 2: vals.append(0) if len(vals) != 3: raise Exception('Cannot init Vector with sequence of length %d' % len(args[0])) initArgs = vals elif isinstance(args[0], QtGui.QVector3D): # PySide6 6.1 does not accept initialization from QVector3D initArgs = args[0].x(), args[0].y(), args[0].z() elif len(args) == 2: initArgs = (args[0], args[1], 0) QtGui.QVector3D.__init__(self, *initArgs) def __len__(self): return 3 def __add__(self, b): # workaround for pyside bug. see https://bugs.launchpad.net/pyqtgraph/+bug/1223173 if QT_LIB == 'PySide' and isinstance(b, QtGui.QVector3D): b = Vector(b) return QtGui.QVector3D.__add__(self, b) #def __reduce__(self): #return (Point, (self.x(), self.y())) def __getitem__(self, i): if i == 0: return self.x() elif i == 1: return self.y() elif i == 2: return self.z() else: raise IndexError("Point has no index %s" % str(i)) def __setitem__(self, i, x): if i == 0: return self.setX(x) elif i == 1: return self.setY(x) elif i == 2: return self.setZ(x) else: raise IndexError("Point has no index %s" % str(i)) def __iter__(self): yield(self.x()) yield(self.y()) yield(self.z()) def angle(self, a): """Returns the angle in degrees between this vector and the vector a.""" n1 = self.length() n2 = a.length() if n1 == 0. or n2 == 0.: return None ## Probably this should be done with arctan2 instead.. rads = acos(fn.clip_scalar(QtGui.QVector3D.dotProduct(self, a) / (n1 * n2), -1.0, 1.0)) ### in radians # c = self.crossProduct(a) # if c > 0: # ang *= -1. return degrees(rads) def __abs__(self): return Vector(abs(self.x()), abs(self.y()), abs(self.z())) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/WidgetGroup.py000066400000000000000000000226371421045507400224770ustar00rootroot00000000000000""" WidgetGroup.py - WidgetGroup class for easily managing lots of Qt widgets Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. This class addresses the problem of having to save and restore the state of a large group of widgets. """ import inspect import weakref from .Qt import QtCore, QtWidgets __all__ = ['WidgetGroup'] def splitterState(w): s = w.saveState().toPercentEncoding().data().decode() return s def restoreSplitter(w, s): if type(s) is list: w.setSizes(s) elif type(s) is str: w.restoreState(QtCore.QByteArray.fromPercentEncoding(s.encode())) else: print("Can't configure QSplitter using object of type", type(s)) if w.count() > 0: # make sure at least one item is not collapsed for i in w.sizes(): if i > 0: return w.setSizes([50] * w.count()) def comboState(w): ind = w.currentIndex() data = w.itemData(ind) if data is not None: try: if not data.isValid(): data = None else: data = data.toInt()[0] except AttributeError: pass if data is None: return str(w.itemText(ind)) else: return data def setComboState(w, v): if type(v) is int: ind = w.findData(v) if ind > -1: w.setCurrentIndex(ind) return w.setCurrentIndex(w.findText(str(v))) class WidgetGroup(QtCore.QObject): """State manager for groups of widgets. WidgetGroup handles common problems that arise when dealing with groups of widgets like a control panel: - Provide a single place for saving / restoring the state of all widgets in the group - Provide a single signal for detecting when any of the widgets have changed """ # List of widget types that can be handled by WidgetGroup. # The value for each type is a tuple (change signal function, get function, set function, [auto-add children]) # The change signal function that takes an object and returns a signal that is emitted any time the state of the widget changes, not just # when it is changed by user interaction. (for example, 'clicked' is not a valid signal here) # If the change signal is None, the value of the widget is not cached. # Custom widgets not in this list can be made to work with WidgetGroup by giving them a 'widgetGroupInterface' method # which returns the tuple. classes = { QtWidgets.QSpinBox: ( lambda w: w.valueChanged, QtWidgets.QSpinBox.value, QtWidgets.QSpinBox.setValue ), QtWidgets.QDoubleSpinBox: ( lambda w: w.valueChanged, QtWidgets.QDoubleSpinBox.value, QtWidgets.QDoubleSpinBox.setValue ), QtWidgets.QSplitter: ( None, splitterState, restoreSplitter, True ), QtWidgets.QCheckBox: ( lambda w: w.stateChanged, QtWidgets.QCheckBox.isChecked, QtWidgets.QCheckBox.setChecked ), QtWidgets.QComboBox: ( lambda w: w.currentIndexChanged, comboState, setComboState ), QtWidgets.QGroupBox: ( lambda w: w.toggled, QtWidgets.QGroupBox.isChecked, QtWidgets.QGroupBox.setChecked, True ), QtWidgets.QLineEdit: ( lambda w: w.editingFinished, lambda w: str(w.text()), QtWidgets.QLineEdit.setText ), QtWidgets.QRadioButton: ( lambda w: w.toggled, QtWidgets.QRadioButton.isChecked, QtWidgets.QRadioButton.setChecked ), QtWidgets.QSlider: ( lambda w: w.valueChanged, QtWidgets.QSlider.value, QtWidgets.QSlider.setValue ), } sigChanged = QtCore.Signal(str, object) def __init__(self, widgetList=None): """Initialize WidgetGroup, adding specified widgets into this group. widgetList can be: - a list of widget specifications (widget, [name], [scale]) - a dict of name: widget pairs - any QObject, and all compatible child widgets will be added recursively. The 'scale' parameter for each widget allows QSpinBox to display a different value than the value recorded in the group state (for example, the program may set a spin box value to 100e-6 and have it displayed as 100 to the user) """ QtCore.QObject.__init__(self) self.widgetList = weakref.WeakKeyDictionary() # Make sure widgets don't stick around just because they are listed here self.scales = weakref.WeakKeyDictionary() self.cache = {} # name:value pairs self.uncachedWidgets = weakref.WeakKeyDictionary() if isinstance(widgetList, QtCore.QObject): self.autoAdd(widgetList) elif isinstance(widgetList, list): for w in widgetList: self.addWidget(*w) elif isinstance(widgetList, dict): for name, w in widgetList.items(): self.addWidget(w, name) elif widgetList is None: return else: raise Exception("Wrong argument type %s" % type(widgetList)) def addWidget(self, w, name=None, scale=None): if not self.acceptsType(w): raise Exception("Widget type %s not supported by WidgetGroup" % type(w)) if name is None: name = str(w.objectName()) if name == '': raise Exception("Cannot add widget '%s' without a name." % str(w)) self.widgetList[w] = name self.scales[w] = scale self.readWidget(w) if type(w) in WidgetGroup.classes: signal = WidgetGroup.classes[type(w)][0] else: signal = w.widgetGroupInterface()[0] if signal is not None: if inspect.isfunction(signal) or inspect.ismethod(signal): signal = signal(w) signal.connect(self.mkChangeCallback(w)) else: self.uncachedWidgets[w] = None def findWidget(self, name): for w in self.widgetList: if self.widgetList[w] == name: return w return None def interface(self, obj): t = type(obj) if t in WidgetGroup.classes: return WidgetGroup.classes[t] else: return obj.widgetGroupInterface() def checkForChildren(self, obj): """Return true if we should automatically search the children of this object for more.""" iface = self.interface(obj) return (len(iface) > 3 and iface[3]) def autoAdd(self, obj): # Find all children of this object and add them if possible. accepted = self.acceptsType(obj) if accepted: self.addWidget(obj) if not accepted or self.checkForChildren(obj): for c in obj.children(): self.autoAdd(c) def acceptsType(self, obj): for c in WidgetGroup.classes: if isinstance(obj, c): return True if hasattr(obj, 'widgetGroupInterface'): return True return False def setScale(self, widget, scale): val = self.readWidget(widget) self.scales[widget] = scale self.setWidget(widget, val) def mkChangeCallback(self, w): return lambda *args: self.widgetChanged(w, *args) def widgetChanged(self, w, *args): n = self.widgetList[w] v1 = self.cache[n] v2 = self.readWidget(w) if v1 != v2: self.sigChanged.emit(self.widgetList[w], v2) def state(self): for w in self.uncachedWidgets: self.readWidget(w) return self.cache.copy() def setState(self, s): for w in self.widgetList: n = self.widgetList[w] if n not in s: continue self.setWidget(w, s[n]) def readWidget(self, w): if type(w) in WidgetGroup.classes: getFunc = WidgetGroup.classes[type(w)][1] else: getFunc = w.widgetGroupInterface()[1] if getFunc is None: return None # if the getter function provided in the interface is a bound method, # then just call the method directly. Otherwise, pass in the widget as the first arg # to the function. if inspect.ismethod(getFunc) and getFunc.__self__ is not None: val = getFunc() else: val = getFunc(w) if self.scales[w] is not None: val /= self.scales[w] n = self.widgetList[w] self.cache[n] = val return val def setWidget(self, w, v): v1 = v if self.scales[w] is not None: v *= self.scales[w] if type(w) in WidgetGroup.classes: setFunc = WidgetGroup.classes[type(w)][2] else: setFunc = w.widgetGroupInterface()[2] # if the setter function provided in the interface is a bound method, # then just call the method directly. Otherwise, pass in the widget as the first arg # to the function. if inspect.ismethod(setFunc) and setFunc.__self__ is not None: setFunc(v) else: setFunc(w, v) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/__init__.py000066400000000000000000000434221421045507400217710ustar00rootroot00000000000000""" PyQtGraph - Scientific Graphics and GUI Library for Python www.pyqtgraph.org """ __version__ = '0.12.4' ### import all the goodies and add some helper functions for easy CLI use import os import sys import numpy # # pyqtgraph requires numpy ## 'Qt' is a local module; it is intended mainly to cover up the differences ## between PyQt and PySide. from .colors import palette from .Qt import QtCore, QtGui, QtWidgets from .Qt import exec_ as exec from .Qt import mkQApp ## not really safe--If we accidentally create another QApplication, the process hangs (and it is very difficult to trace the cause) #if QtWidgets.QApplication.instance() is None: #app = QtWidgets.QApplication([]) ## (import here to avoid massive error dump later on if numpy is not available) ## 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. if 'linux' in sys.platform: ## linux has numerous bugs in opengl implementation useOpenGL = False elif 'darwin' in sys.platform: ## openGL can have a major impact on mac, but also has serious bugs useOpenGL = False else: useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff. CONFIG_OPTIONS = { 'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl. 'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox # foreground/background take any arguments to the 'mkColor' in /pyqtgraph/functions.py 'foreground': 'd', ## default foreground color for axes, labels, etc. 'background': 'k', ## default background for GraphicsWidget 'antialias': False, 'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets 'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide 'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code) 'crashWarning': False, # If True, print warnings about situations that may result in a crash 'mouseRateLimit': 100, # For ignoring frequent mouse events, max number of mouse move events per second, if <= 0, then it is switched off 'imageAxisOrder': 'col-major', # For 'row-major', image data is expected in the standard (row, col) order. # For 'col-major', image data is expected in reversed (col, row) order. # The default is 'col-major' for backward compatibility, but this may # change in the future. 'useCupy': False, # When True, attempt to use cupy ( currently only with ImageItem and related functions ) 'useNumba': False, # When True, use numba } def setConfigOption(opt, value): if opt not in CONFIG_OPTIONS: raise KeyError('Unknown configuration option "%s"' % opt) if opt == 'imageAxisOrder' and value not in ('row-major', 'col-major'): raise ValueError('imageAxisOrder must be either "row-major" or "col-major"') CONFIG_OPTIONS[opt] = value def setConfigOptions(**opts): """Set global configuration options. Each keyword argument sets one global option. """ for k,v in opts.items(): setConfigOption(k, v) def getConfigOption(opt): """Return the value of a single global configuration option. """ return CONFIG_OPTIONS[opt] def systemInfo(): print("sys.platform: %s" % sys.platform) print("sys.version: %s" % sys.version) from .Qt import VERSION_INFO print("qt bindings: %s" % VERSION_INFO) global __version__ rev = None if __version__ is None: ## this code was probably checked out from bzr; look up the last-revision file lastRevFile = os.path.join(os.path.dirname(__file__), '..', '.bzr', 'branch', 'last-revision') if os.path.exists(lastRevFile): with open(lastRevFile, 'r') as fd: rev = fd.read().strip() print("pyqtgraph: %s; %s" % (__version__, rev)) print("config:") import pprint pprint.pprint(CONFIG_OPTIONS) ## Rename orphaned .pyc files. This is *probably* safe :) ## We only do this if __version__ is None, indicating the code was probably pulled ## from the repository. def renamePyc(startDir): ### Used to rename orphaned .pyc files ### When a python file changes its location in the repository, usually the .pyc file ### is left behind, possibly causing mysterious and difficult to track bugs. ### Note that this is no longer necessary for python 3.2; from PEP 3147: ### "If the py source file is missing, the pyc file inside __pycache__ will be ignored. ### This eliminates the problem of accidental stale pyc file imports." printed = False startDir = os.path.abspath(startDir) for path, dirs, files in os.walk(startDir): if '__pycache__' in path: continue for f in files: fileName = os.path.join(path, f) base, ext = os.path.splitext(fileName) py = base + ".py" if ext == '.pyc' and not os.path.isfile(py): if not printed: print("NOTE: Renaming orphaned .pyc files:") printed = True n = 1 while True: name2 = fileName + ".renamed%d" % n if not os.path.exists(name2): break n += 1 print(" " + fileName + " ==>") print(" " + name2) os.rename(fileName, name2) path = os.path.split(__file__)[0] ## Import almost everything to make it available from a single namespace ## don't import the more complex systems--canvas, parametertree, flowchart, dockarea ## these must be imported separately. #from . import frozenSupport #def importModules(path, globals, locals, excludes=()): #"""Import all modules residing within *path*, return a dict of name: module pairs. #Note that *path* MUST be relative to the module doing the import. #""" #d = os.path.join(os.path.split(globals['__file__'])[0], path) #files = set() #for f in frozenSupport.listdir(d): #if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']: #files.add(f) #elif f[-3:] == '.py' and f != '__init__.py': #files.add(f[:-3]) #elif f[-4:] == '.pyc' and f != '__init__.pyc': #files.add(f[:-4]) #mods = {} #path = path.replace(os.sep, '.') #for modName in files: #if modName in excludes: #continue #try: #if len(path) > 0: #modName = path + '.' + modName #print( "from .%s import * " % modName) #mod = __import__(modName, globals, locals, ['*'], 1) #mods[modName] = mod #except: #import traceback #traceback.print_stack() #sys.excepthook(*sys.exc_info()) #print("[Error importing module: %s]" % modName) #return mods #def importAll(path, globals, locals, excludes=()): #"""Given a list of modules, import all names from each module into the global namespace.""" #mods = importModules(path, globals, locals, excludes) #for mod in mods.values(): #if hasattr(mod, '__all__'): #names = mod.__all__ #else: #names = [n for n in dir(mod) if n[0] != '_'] #for k in names: #if hasattr(mod, k): #globals[k] = getattr(mod, k) # Dynamic imports are disabled. This causes too many problems. #importAll('graphicsItems', globals(), locals()) #importAll('widgets', globals(), locals(), #excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView']) ## Attempts to work around exit crashes: import atexit from .colormap import * from .functions import * from .graphicsItems.ArrowItem import * from .graphicsItems.AxisItem import * from .graphicsItems.BarGraphItem import * from .graphicsItems.ButtonItem import * from .graphicsItems.ColorBarItem import * from .graphicsItems.CurvePoint import * from .graphicsItems.DateAxisItem import * from .graphicsItems.ErrorBarItem import * from .graphicsItems.FillBetweenItem import * from .graphicsItems.GradientEditorItem import * from .graphicsItems.GradientLegend import * from .graphicsItems.GraphicsItem import * from .graphicsItems.GraphicsLayout import * from .graphicsItems.GraphicsObject import * from .graphicsItems.GraphicsWidget import * from .graphicsItems.GraphicsWidgetAnchor import * from .graphicsItems.GraphItem import * from .graphicsItems.GridItem import * from .graphicsItems.HistogramLUTItem import * from .graphicsItems.ImageItem import * from .graphicsItems.InfiniteLine import * from .graphicsItems.IsocurveItem import * from .graphicsItems.ItemGroup import * from .graphicsItems.LabelItem import * from .graphicsItems.LegendItem import * from .graphicsItems.LinearRegionItem import * from .graphicsItems.MultiPlotItem import * from .graphicsItems.PColorMeshItem import * from .graphicsItems.PlotCurveItem import * from .graphicsItems.PlotDataItem import * from .graphicsItems.PlotItem import * from .graphicsItems.ROI import * from .graphicsItems.ScaleBar import * from .graphicsItems.ScatterPlotItem import * from .graphicsItems.TargetItem import * from .graphicsItems.TextItem import * from .graphicsItems.UIGraphicsItem import * from .graphicsItems.ViewBox import * from .graphicsItems.VTickGroup import * # indirect imports used within library from .GraphicsScene import GraphicsScene from .graphicsWindows import * from .imageview import * # indirect imports known to be used outside of the library from .metaarray import MetaArray from .ordereddict import OrderedDict from .Point import Point from .ptime import time from .Qt import isQObjectAlive from .SignalProxy import * from .SRTTransform import SRTTransform from .SRTTransform3D import SRTTransform3D from .ThreadsafeTimer import * from .Transform3D import Transform3D from .util.cupy_helper import getCupy from .Vector import Vector from .WidgetGroup import * from .widgets.BusyCursor import * from .widgets.CheckTable import * from .widgets.ColorButton import * from .widgets.ColorMapWidget import * from .widgets.ComboBox import * from .widgets.DataFilterWidget import * from .widgets.DataTreeWidget import * from .widgets.DiffTreeWidget import * from .widgets.FeedbackButton import * from .widgets.FileDialog import * from .widgets.GradientWidget import * from .widgets.GraphicsLayoutWidget import * from .widgets.GraphicsView import * from .widgets.GroupBox import GroupBox from .widgets.HistogramLUTWidget import * from .widgets.JoystickButton import * from .widgets.LayoutWidget import * from .widgets.MultiPlotWidget import * from .widgets.PathButton import * from .widgets.PlotWidget import * from .widgets.ProgressDialog import * from .widgets.RemoteGraphicsView import RemoteGraphicsView from .widgets.ScatterPlotWidget import * from .widgets.SpinBox import * from .widgets.TableWidget import * from .widgets.TreeWidget import * from .widgets.ValueLabel import * from .widgets.VerticalLabel import * ############################################################## ## PyQt and PySide both are prone to crashing on exit. ## There are two general approaches to dealing with this: ## 1. Install atexit handlers that assist in tearing down to avoid crashes. ## This helps, but is never perfect. ## 2. Terminate the process before python starts tearing down ## This is potentially dangerous _cleanupCalled = False def cleanup(): global _cleanupCalled if _cleanupCalled: return if not getConfigOption('exitCleanup'): return ViewBox.quit() ## tell ViewBox that it doesn't need to deregister views anymore. ## Workaround for Qt exit crash: ## ALL QGraphicsItems must have a scene before they are deleted. ## This is potentially very expensive, but preferred over crashing. ## Note: this appears to be fixed in PySide as of 2012.12, but it should be left in for a while longer.. app = QtWidgets.QApplication.instance() if app is None or not isinstance(app, QtWidgets.QApplication): # app was never constructed is already deleted or is an # QCoreApplication/QGuiApplication and not a full QApplication return import gc s = QtWidgets.QGraphicsScene() for o in gc.get_objects(): try: if isinstance(o, QtWidgets.QGraphicsItem) and isQObjectAlive(o) and o.scene() is None: if getConfigOption('crashWarning'): sys.stderr.write('Error: graphics item without scene. ' 'Make sure ViewBox.close() and GraphicsView.close() ' 'are properly called before app shutdown (%s)\n' % (o,)) s.addItem(o) except (RuntimeError, ReferenceError): ## occurs if a python wrapper no longer has its underlying C++ object continue _cleanupCalled = True atexit.register(cleanup) # Call cleanup when QApplication quits. This is necessary because sometimes # the QApplication will quit before the atexit callbacks are invoked. # Note: cannot connect this function until QApplication has been created, so # instead we have GraphicsView.__init__ call this for us. _cleanupConnected = False def _connectCleanup(): global _cleanupConnected if _cleanupConnected: return QtWidgets.QApplication.instance().aboutToQuit.connect(cleanup) _cleanupConnected = True ## Optional function for exiting immediately (with some manual teardown) def exit(): """ Causes python to exit without garbage-collecting any objects, and thus avoids calling object destructor methods. This is a sledgehammer workaround for a variety of bugs in PyQt and Pyside that cause crashes on exit. This function does the following in an attempt to 'safely' terminate the process: * 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, to properly terminate log files, disconnect from devices, etc). Situations like this are probably quite rare, but use at your own risk. """ ## first disable our own cleanup function; won't be needing it. setConfigOptions(exitCleanup=False) ## invoke atexit callbacks atexit._run_exitfuncs() ## close file handles if sys.platform == 'darwin': for fd in range(3, 4096): if fd in [7]: # trying to close 7 produces an illegal instruction on the Mac. continue try: os.close(fd) except OSError: pass else: os.closerange(3, 4096) ## just guessing on the maximum descriptor count.. os._exit(0) ## Convenience functions for command-line use plots = [] images = [] QAPP = None def plot(*args, **kargs): """ Create and return a :class:`PlotWidget ` Accepts a *title* argument to set the title of the window. All other arguments are used to plot data. (see :func:`PlotItem.plot() `) """ mkQApp() pwArgList = ['title', 'labels', 'name', 'left', 'right', 'top', 'bottom', 'background'] pwArgs = {} dataArgs = {} for k in kargs: if k in pwArgList: pwArgs[k] = kargs[k] else: dataArgs[k] = kargs[k] windowTitle = pwArgs.pop("title", "PlotWidget") w = PlotWidget(**pwArgs) w.setWindowTitle(windowTitle) if len(args) > 0 or len(dataArgs) > 0: w.plot(*args, **dataArgs) plots.append(w) w.show() return w def image(*args, **kargs): """ Create and return an :class:`ImageView ` Will show 2D or 3D image data. Accepts a *title* argument to set the title of the window. All other arguments are used to show data. (see :func:`ImageView.setImage() `) """ mkQApp() w = ImageView() windowTitle = kargs.pop("title", "ImageView") w.setWindowTitle(windowTitle) w.setImage(*args, **kargs) images.append(w) w.show() return w show = image ## for backward compatibility def dbg(*args, **kwds): """ Create a console window and begin watching for exceptions. All arguments are passed to :func:`ConsoleWidget.__init__() `. """ mkQApp() from . import console c = console.ConsoleWidget(*args, **kwds) c.catchAllExceptions() c.show() global consoles try: consoles.append(c) except NameError: consoles = [c] return c def stack(*args, **kwds): """ Create a console window and show the current stack trace. All arguments are passed to :func:`ConsoleWidget.__init__() `. """ mkQApp() from . import console c = console.ConsoleWidget(*args, **kwds) c.setStack() c.show() global consoles try: consoles.append(c) except NameError: consoles = [c] return c def setPalette(app, style): if isinstance(style, str): style = style.lower() if style == 'qdarkstylelight': p = palette.getQDarkStyleLightQPalette() elif style in ['qdarkstyle','qdarkstyledark']: p = palette.getQDarkStyleDarkQPalette() else: raise ValueError(f'no palette by the name {style} exists') elif isinstance(style, QtGui.QPalette): p = style else: raise TypeError('style either be a string or QPalette') app.paletteChanged.emit(p) app.setPalette(p) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/000077500000000000000000000000001421045507400211265ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/Canvas.py000066400000000000000000000377101421045507400227230ustar00rootroot00000000000000__all__ = ["Canvas"] import importlib from ..graphicsItems.GridItem import GridItem from ..graphicsItems.ROI import ROI from ..graphicsItems.ViewBox import ViewBox from ..Qt import QT_LIB, QtCore, QtGui, QtWidgets ui_template = importlib.import_module( f'.CanvasTemplate_{QT_LIB.lower()}', package=__package__) import gc import weakref from .CanvasItem import CanvasItem, GroupCanvasItem from .CanvasManager import CanvasManager translate = QtCore.QCoreApplication.translate class Canvas(QtWidgets.QWidget): sigSelectionChanged = QtCore.Signal(object, object) sigItemTransformChanged = QtCore.Signal(object, object) sigItemTransformChangeFinished = QtCore.Signal(object, object) def __init__(self, parent=None, allowTransforms=True, hideCtrl=False, name=None): QtWidgets.QWidget.__init__(self, parent) self.ui = ui_template.Ui_Form() self.ui.setupUi(self) self.view = ViewBox() self.ui.view.setCentralItem(self.view) self.itemList = self.ui.itemList self.itemList.setSelectionMode(self.itemList.SelectionMode.ExtendedSelection) self.allowTransforms = allowTransforms self.multiSelectBox = SelectBox() self.view.addItem(self.multiSelectBox) self.multiSelectBox.hide() self.multiSelectBox.setZValue(1e6) self.ui.mirrorSelectionBtn.hide() self.ui.reflectSelectionBtn.hide() self.ui.resetTransformsBtn.hide() self.redirect = None ## which canvas to redirect items to self.items = [] self.view.setAspectLocked(True) grid = GridItem() self.grid = CanvasItem(grid, name='Grid', movable=False) self.addItem(self.grid) self.hideBtn = QtWidgets.QPushButton('>', self) self.hideBtn.setFixedWidth(20) self.hideBtn.setFixedHeight(20) self.ctrlSize = 200 self.sizeApplied = False self.hideBtn.clicked.connect(self.hideBtnClicked) self.ui.splitter.splitterMoved.connect(self.splitterMoved) self.ui.itemList.itemChanged.connect(self.treeItemChanged) self.ui.itemList.sigItemMoved.connect(self.treeItemMoved) self.ui.itemList.itemSelectionChanged.connect(self.treeItemSelected) self.ui.autoRangeBtn.clicked.connect(self.autoRange) self.ui.redirectCheck.toggled.connect(self.updateRedirect) self.ui.redirectCombo.currentIndexChanged.connect(self.updateRedirect) self.multiSelectBox.sigRegionChanged.connect(self.multiSelectBoxChanged) self.multiSelectBox.sigRegionChangeFinished.connect(self.multiSelectBoxChangeFinished) self.ui.mirrorSelectionBtn.clicked.connect(self.mirrorSelectionClicked) self.ui.reflectSelectionBtn.clicked.connect(self.reflectSelectionClicked) self.ui.resetTransformsBtn.clicked.connect(self.resetTransformsClicked) self.resizeEvent() if hideCtrl: self.hideBtnClicked() if name is not None: self.registeredName = CanvasManager.instance().registerCanvas(self, name) self.ui.redirectCombo.setHostName(self.registeredName) self.menu = QtWidgets.QMenu() remAct = QtGui.QAction(translate("Context Menu", "Remove item"), self.menu) remAct.triggered.connect(self.removeClicked) self.menu.addAction(remAct) self.menu.remAct = remAct self.ui.itemList.contextMenuEvent = self.itemListContextMenuEvent def splitterMoved(self): self.resizeEvent() def hideBtnClicked(self): ctrlSize = self.ui.splitter.sizes()[1] if ctrlSize == 0: cs = self.ctrlSize w = self.ui.splitter.size().width() if cs > w: cs = w - 20 self.ui.splitter.setSizes([w-cs, cs]) self.hideBtn.setText('>') else: self.ctrlSize = ctrlSize self.ui.splitter.setSizes([100, 0]) self.hideBtn.setText('<') self.resizeEvent() def autoRange(self): self.view.autoRange() def resizeEvent(self, ev=None): if ev is not None: super().resizeEvent(ev) self.hideBtn.move(self.ui.view.size().width() - self.hideBtn.width(), 0) if not self.sizeApplied: self.sizeApplied = True s = int( min(self.width(), max(100, min(200, self.width()//4))) ) s2 = self.width()-s self.ui.splitter.setSizes([s2, s]) def updateRedirect(self, *args): ### Decide whether/where to redirect items and make it so cname = str(self.ui.redirectCombo.currentText()) man = CanvasManager.instance() if self.ui.redirectCheck.isChecked() and cname != '': redirect = man.getCanvas(cname) else: redirect = None if self.redirect is redirect: return self.redirect = redirect if redirect is None: self.reclaimItems() else: self.redirectItems(redirect) def redirectItems(self, canvas): for i in self.items: if i is self.grid: continue li = i.listItem parent = li.parent() if parent is None: tree = li.treeWidget() if tree is None: print("Skipping item", i, i.name) continue tree.removeTopLevelItem(li) else: parent.removeChild(li) canvas.addItem(i) def reclaimItems(self): items = self.items self.items = [self.grid] items.remove(self.grid) for i in items: i.canvas.removeItem(i) self.addItem(i) def treeItemChanged(self, item, col): try: citem = item.canvasItem() except AttributeError: return if item.checkState(0) == QtCore.Qt.CheckState.Checked: for i in range(item.childCount()): item.child(i).setCheckState(0, QtCore.Qt.CheckState.Checked) citem.show() else: for i in range(item.childCount()): item.child(i).setCheckState(0, QtCore.Qt.CheckState.Unchecked) citem.hide() def treeItemSelected(self): sel = self.selectedItems() if len(sel) == 0: return multi = len(sel) > 1 for i in self.items: ## updated the selected state of every item i.selectionChanged(i in sel, multi) if len(sel)==1: self.multiSelectBox.hide() self.ui.mirrorSelectionBtn.hide() self.ui.reflectSelectionBtn.hide() self.ui.resetTransformsBtn.hide() elif len(sel) > 1: self.showMultiSelectBox() self.sigSelectionChanged.emit(self, sel) def selectedItems(self): """ Return list of all selected canvasItems """ return [item.canvasItem() for item in self.itemList.selectedItems() if item.canvasItem() is not None] def selectItem(self, item): li = item.listItem self.itemList.setCurrentItem(li) def showMultiSelectBox(self): ## Get list of selected canvas items items = self.selectedItems() rect = self.view.itemBoundingRect(items[0].graphicsItem()) for i in items: if not i.isMovable(): ## all items in selection must be movable return br = self.view.itemBoundingRect(i.graphicsItem()) rect = rect|br self.multiSelectBox.blockSignals(True) self.multiSelectBox.setPos([rect.x(), rect.y()]) self.multiSelectBox.setSize(rect.size()) self.multiSelectBox.setAngle(0) self.multiSelectBox.blockSignals(False) self.multiSelectBox.show() self.ui.mirrorSelectionBtn.show() self.ui.reflectSelectionBtn.show() self.ui.resetTransformsBtn.show() def mirrorSelectionClicked(self): for ci in self.selectedItems(): ci.mirrorY() self.showMultiSelectBox() def reflectSelectionClicked(self): for ci in self.selectedItems(): ci.mirrorXY() self.showMultiSelectBox() def resetTransformsClicked(self): for i in self.selectedItems(): i.resetTransformClicked() self.showMultiSelectBox() def multiSelectBoxChanged(self): self.multiSelectBoxMoved() def multiSelectBoxChangeFinished(self): for ci in self.selectedItems(): ci.applyTemporaryTransform() ci.sigTransformChangeFinished.emit(ci) def multiSelectBoxMoved(self): transform = self.multiSelectBox.getGlobalTransform() for ci in self.selectedItems(): ci.setTemporaryTransform(transform) ci.sigTransformChanged.emit(ci) def addGraphicsItem(self, item, **opts): """Add a new GraphicsItem to the scene at pos. Common options are name, pos, scale, and z """ citem = CanvasItem(item, **opts) item._canvasItem = citem self.addItem(citem) return citem def addGroup(self, name, **kargs): group = GroupCanvasItem(name=name) self.addItem(group, **kargs) return group def addItem(self, citem): """ Add an item to the canvas. """ ## Check for redirections if self.redirect is not None: name = self.redirect.addItem(citem) self.items.append(citem) return name if not self.allowTransforms: citem.setMovable(False) citem.sigTransformChanged.connect(self.itemTransformChanged) citem.sigTransformChangeFinished.connect(self.itemTransformChangeFinished) citem.sigVisibilityChanged.connect(self.itemVisibilityChanged) ## Determine name to use in the item list name = citem.opts['name'] if name is None: name = 'item' newname = name ## If name already exists, append a number to the end ## NAH. Let items have the same name if they really want. #c=0 #while newname in self.items: #c += 1 #newname = name + '_%03d' %c #name = newname ## find parent and add item to tree insertLocation = 0 #print "Inserting node:", name ## determine parent list item where this item should be inserted parent = citem.parentItem() if parent in (None, self.view.childGroup): parent = self.itemList.invisibleRootItem() else: parent = parent.listItem ## set Z value above all other siblings if none was specified siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())] z = citem.zValue() if z is None: zvals = [i.zValue() for i in siblings] if parent is self.itemList.invisibleRootItem(): if len(zvals) == 0: z = 0 else: z = max(zvals)+10 else: if len(zvals) == 0: z = parent.canvasItem().zValue() else: z = max(zvals)+1 citem.setZValue(z) ## determine location to insert item relative to its siblings for i in range(parent.childCount()): ch = parent.child(i) zval = ch.canvasItem().graphicsItem().zValue() ## should we use CanvasItem.zValue here? if zval < z: insertLocation = i break else: insertLocation = i+1 node = QtWidgets.QTreeWidgetItem([name]) flags = node.flags() | QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsDragEnabled if not isinstance(citem, GroupCanvasItem): flags = flags & ~QtCore.Qt.ItemFlag.ItemIsDropEnabled node.setFlags(flags) if citem.opts['visible']: node.setCheckState(0, QtCore.Qt.CheckState.Checked) else: node.setCheckState(0, QtCore.Qt.CheckState.Unchecked) node.name = name parent.insertChild(insertLocation, node) citem.name = name citem.listItem = node node.canvasItem = weakref.ref(citem) self.items.append(citem) ctrl = citem.ctrlWidget() ctrl.hide() self.ui.ctrlLayout.addWidget(ctrl) ## inform the canvasItem that its parent canvas has changed citem.setCanvas(self) ## Autoscale to fit the first item added (not including the grid). if len(self.items) == 2: self.autoRange() return citem def treeItemMoved(self, item, parent, index): ##Item moved in tree; update Z values if parent is self.itemList.invisibleRootItem(): item.canvasItem().setParentItem(self.view.childGroup) else: item.canvasItem().setParentItem(parent.canvasItem()) siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())] zvals = [i.zValue() for i in siblings] zvals.sort(reverse=True) for i in range(len(siblings)): item = siblings[i] item.setZValue(zvals[i]) def itemVisibilityChanged(self, item): listItem = item.listItem checked = listItem.checkState(0) == QtCore.Qt.CheckState.Checked vis = item.isVisible() if vis != checked: if vis: listItem.setCheckState(0, QtCore.Qt.CheckState.Checked) else: listItem.setCheckState(0, QtCore.Qt.CheckState.Unchecked) def removeItem(self, item): if isinstance(item, QtWidgets.QTreeWidgetItem): item = item.canvasItem() if isinstance(item, CanvasItem): item.setCanvas(None) listItem = item.listItem listItem.canvasItem = None item.listItem = None self.itemList.removeTopLevelItem(listItem) self.items.remove(item) ctrl = item.ctrlWidget() ctrl.hide() self.ui.ctrlLayout.removeWidget(ctrl) ctrl.setParent(None) else: if hasattr(item, '_canvasItem'): self.removeItem(item._canvasItem) else: self.view.removeItem(item) gc.collect() def clear(self): while len(self.items) > 0: self.removeItem(self.items[0]) def addToScene(self, item): self.view.addItem(item) def removeFromScene(self, item): self.view.removeItem(item) def listItems(self): """Return a dictionary of name:item pairs""" return self.items def getListItem(self, name): return self.items[name] def itemTransformChanged(self, item): self.sigItemTransformChanged.emit(self, item) def itemTransformChangeFinished(self, item): self.sigItemTransformChangeFinished.emit(self, item) def itemListContextMenuEvent(self, ev): self.menuItem = self.itemList.itemAt(ev.pos()) self.menu.popup(ev.globalPos()) def removeClicked(self): for item in self.selectedItems(): self.removeItem(item) self.menuItem = None import gc gc.collect() class SelectBox(ROI): def __init__(self, scalable=False): #QtWidgets.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) ROI.__init__(self, [0,0], [1,1]) center = [0.5, 0.5] if scalable: self.addScaleHandle([1, 1], center, lockAspect=True) self.addScaleHandle([0, 0], center, lockAspect=True) self.addRotateHandle([0, 1], center) self.addRotateHandle([1, 0], center) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/CanvasItem.py000066400000000000000000000433721421045507400235430ustar00rootroot00000000000000__all__ = ["CanvasItem", "GroupCanvasItem"] import importlib from .. import ItemGroup, SRTTransform from .. import functions as fn from ..graphicsItems.ROI import ROI from ..Qt import QT_LIB, QtCore, QtWidgets ui_template = importlib.import_module( f'.TransformGuiTemplate_{QT_LIB.lower()}', package=__package__) from .. import debug translate = QtCore.QCoreApplication.translate class SelectBox(ROI): def __init__(self, scalable=False, rotatable=True): #QtWidgets.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) ROI.__init__(self, [0,0], [1,1], invertible=True) center = [0.5, 0.5] if scalable: self.addScaleHandle([1, 1], center, lockAspect=True) self.addScaleHandle([0, 0], center, lockAspect=True) if rotatable: self.addRotateHandle([0, 1], center) self.addRotateHandle([1, 0], center) class CanvasItem(QtCore.QObject): sigResetUserTransform = QtCore.Signal(object) sigTransformChangeFinished = QtCore.Signal(object) sigTransformChanged = QtCore.Signal(object) """CanvasItem takes care of managing an item's state--alpha, visibility, z-value, transformations, etc. and provides a control widget""" sigVisibilityChanged = QtCore.Signal(object) transformCopyBuffer = None def __init__(self, item, **opts): defOpts = {'name': None, 'z': None, 'movable': True, 'scalable': False, 'rotatable': True, 'visible': True, 'parent':None} #'pos': [0,0], 'scale': [1,1], 'angle':0, defOpts.update(opts) self.opts = defOpts self.selectedAlone = False ## whether this item is the only one selected QtCore.QObject.__init__(self) self.canvas = None self._graphicsItem = item parent = self.opts['parent'] if parent is not None: self._graphicsItem.setParentItem(parent.graphicsItem()) self._parentItem = parent else: self._parentItem = None z = self.opts['z'] if z is not None: item.setZValue(z) self.ctrl = QtWidgets.QWidget() self.layout = QtWidgets.QGridLayout() self.layout.setSpacing(0) self.layout.setContentsMargins(0,0,0,0) self.ctrl.setLayout(self.layout) self.alphaLabel = QtWidgets.QLabel(translate("CanvasItem", "Alpha")) self.alphaSlider = QtWidgets.QSlider() self.alphaSlider.setMaximum(1023) self.alphaSlider.setOrientation(QtCore.Qt.Orientation.Horizontal) self.alphaSlider.setValue(1023) self.layout.addWidget(self.alphaLabel, 0, 0) self.layout.addWidget(self.alphaSlider, 0, 1) self.resetTransformBtn = QtWidgets.QPushButton('Reset Transform') self.copyBtn = QtWidgets.QPushButton('Copy') self.pasteBtn = QtWidgets.QPushButton('Paste') self.transformWidget = QtWidgets.QWidget() self.transformGui = ui_template.Ui_Form() self.transformGui.setupUi(self.transformWidget) self.layout.addWidget(self.transformWidget, 3, 0, 1, 2) self.transformGui.mirrorImageBtn.clicked.connect(self.mirrorY) self.transformGui.reflectImageBtn.clicked.connect(self.mirrorXY) self.layout.addWidget(self.resetTransformBtn, 1, 0, 1, 2) self.layout.addWidget(self.copyBtn, 2, 0, 1, 1) self.layout.addWidget(self.pasteBtn, 2, 1, 1, 1) self.alphaSlider.valueChanged.connect(self.alphaChanged) self.alphaSlider.sliderPressed.connect(self.alphaPressed) self.alphaSlider.sliderReleased.connect(self.alphaReleased) self.resetTransformBtn.clicked.connect(self.resetTransformClicked) self.copyBtn.clicked.connect(self.copyClicked) self.pasteBtn.clicked.connect(self.pasteClicked) self.setMovable(self.opts['movable']) ## update gui to reflect this option if 'transform' in self.opts: self.baseTransform = self.opts['transform'] else: self.baseTransform = SRTTransform() if 'pos' in self.opts and self.opts['pos'] is not None: self.baseTransform.translate(self.opts['pos']) if 'angle' in self.opts and self.opts['angle'] is not None: self.baseTransform.rotate(self.opts['angle']) if 'scale' in self.opts and self.opts['scale'] is not None: self.baseTransform.scale(self.opts['scale']) ## create selection box (only visible when selected) tr = self.baseTransform.saveState() if 'scalable' not in opts and tr['scale'] == (1,1): self.opts['scalable'] = True ## every CanvasItem implements its own individual selection box ## so that subclasses are free to make their own. self.selectBox = SelectBox(scalable=self.opts['scalable'], rotatable=self.opts['rotatable']) self.selectBox.hide() self.selectBox.setZValue(1e6) self.selectBox.sigRegionChanged.connect(self.selectBoxChanged) ## calls selectBoxMoved self.selectBox.sigRegionChangeFinished.connect(self.selectBoxChangeFinished) ## set up the transformations that will be applied to the item ## (It is not safe to use item.setTransform, since the item might count on that not changing) self.itemRotation = QtWidgets.QGraphicsRotation() self.itemScale = QtWidgets.QGraphicsScale() self._graphicsItem.setTransformations([self.itemRotation, self.itemScale]) self.tempTransform = SRTTransform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done. self.userTransform = SRTTransform() ## stores the total transform of the object self.resetUserTransform() def setMovable(self, m): self.opts['movable'] = m if m: self.resetTransformBtn.show() self.copyBtn.show() self.pasteBtn.show() else: self.resetTransformBtn.hide() self.copyBtn.hide() self.pasteBtn.hide() def setCanvas(self, canvas): ## Called by canvas whenever the item is added. ## It is our responsibility to add all graphicsItems to the canvas's scene ## The canvas will automatically add our graphicsitem, ## so we just need to take care of the selectbox. if canvas is self.canvas: return if canvas is None: self.canvas.removeFromScene(self._graphicsItem) self.canvas.removeFromScene(self.selectBox) else: canvas.addToScene(self._graphicsItem) canvas.addToScene(self.selectBox) self.canvas = canvas def graphicsItem(self): """Return the graphicsItem for this canvasItem.""" return self._graphicsItem def parentItem(self): return self._parentItem def setParentItem(self, parent): self._parentItem = parent if parent is not None: if isinstance(parent, CanvasItem): parent = parent.graphicsItem() self.graphicsItem().setParentItem(parent) #def name(self): #return self.opts['name'] def copyClicked(self): CanvasItem.transformCopyBuffer = self.saveTransform() def pasteClicked(self): t = CanvasItem.transformCopyBuffer if t is None: return else: self.restoreTransform(t) def mirrorY(self): if not self.isMovable(): return #flip = self.transformGui.mirrorImageCheck.isChecked() #tr = self.userTransform.saveState() inv = SRTTransform() inv.scale(-1, 1) self.userTransform = self.userTransform * inv self.updateTransform() self.selectBoxFromUser() self.sigTransformChangeFinished.emit(self) #if flip: #if tr['scale'][0] < 0 xor tr['scale'][1] < 0: #return #else: #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) #self.userTransform.setRotate(-tr['angle']) #self.updateTransform() #self.selectBoxFromUser() #return #elif not flip: #if tr['scale'][0] > 0 and tr['scale'][1] > 0: #return #else: #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) #self.userTransform.setRotate(-tr['angle']) #self.updateTransform() #self.selectBoxFromUser() #return def mirrorXY(self): if not self.isMovable(): return self.rotate(180.) # inv = SRTTransform() # inv.scale(-1, -1) # self.userTransform = self.userTransform * inv #flip lr/ud # s=self.updateTransform() # self.setTranslate(-2*s['pos'][0], -2*s['pos'][1]) # self.selectBoxFromUser() def hasUserTransform(self): #print self.userRotate, self.userTranslate return not self.userTransform.isIdentity() def ctrlWidget(self): return self.ctrl def alphaChanged(self, val): alpha = val / 1023. self._graphicsItem.setOpacity(alpha) def setAlpha(self, alpha): self.alphaSlider.setValue(int(fn.clip_scalar(alpha * 1023, 0, 1023))) def alpha(self): return self.alphaSlider.value() / 1023. def isMovable(self): return self.opts['movable'] def selectBoxMoved(self): """The selection box has moved; get its transformation information and pass to the graphics item""" self.userTransform = self.selectBox.getGlobalTransform(relativeTo=self.selectBoxBase) self.updateTransform() def scale(self, x, y): self.userTransform.scale(x, y) self.selectBoxFromUser() self.updateTransform() def rotate(self, ang): self.userTransform.rotate(ang) self.selectBoxFromUser() self.updateTransform() def translate(self, x, y): self.userTransform.translate(x, y) self.selectBoxFromUser() self.updateTransform() def setTranslate(self, x, y): self.userTransform.setTranslate(x, y) self.selectBoxFromUser() self.updateTransform() def setRotate(self, angle): self.userTransform.setRotate(angle) self.selectBoxFromUser() self.updateTransform() def setScale(self, x, y): self.userTransform.setScale(x, y) self.selectBoxFromUser() self.updateTransform() def setTemporaryTransform(self, transform): self.tempTransform = transform self.updateTransform() def applyTemporaryTransform(self): """Collapses tempTransform into UserTransform, resets tempTransform""" self.userTransform = self.userTransform * self.tempTransform ## order is important! self.resetTemporaryTransform() self.selectBoxFromUser() ## update the selection box to match the new userTransform def resetTemporaryTransform(self): self.tempTransform = SRTTransform() ## don't use Transform.reset()--this transform might be used elsewhere. self.updateTransform() def transform(self): return self._graphicsItem.transform() def updateTransform(self): """Regenerate the item position from the base, user, and temp transforms""" transform = self.baseTransform * self.userTransform * self.tempTransform ## order is important s = transform.saveState() self._graphicsItem.setPos(*s['pos']) self.itemRotation.setAngle(s['angle']) self.itemScale.setXScale(s['scale'][0]) self.itemScale.setYScale(s['scale'][1]) self.displayTransform(transform) return(s) # return the transform state def displayTransform(self, transform): """Updates transform numbers in the ctrl widget.""" tr = transform.saveState() self.transformGui.translateLabel.setText("Translate: (%f, %f)" %(tr['pos'][0], tr['pos'][1])) self.transformGui.rotateLabel.setText("Rotate: %f degrees" %tr['angle']) self.transformGui.scaleLabel.setText("Scale: (%f, %f)" %(tr['scale'][0], tr['scale'][1])) def resetUserTransform(self): self.userTransform.reset() self.updateTransform() self.selectBox.blockSignals(True) self.selectBoxToItem() self.selectBox.blockSignals(False) self.sigTransformChanged.emit(self) self.sigTransformChangeFinished.emit(self) def resetTransformClicked(self): self.resetUserTransform() self.sigResetUserTransform.emit(self) def restoreTransform(self, tr): try: self.userTransform = SRTTransform(tr) self.updateTransform() self.selectBoxFromUser() ## move select box to match self.sigTransformChanged.emit(self) self.sigTransformChangeFinished.emit(self) except: self.userTransform = SRTTransform() debug.printExc("Failed to load transform:") def saveTransform(self): """Return a dict containing the current user transform""" return self.userTransform.saveState() def selectBoxFromUser(self): """Move the selection box to match the current userTransform""" ## user transform #trans = QtGui.QTransform() #trans.translate(*self.userTranslate) #trans.rotate(-self.userRotate) #x2, y2 = trans.map(*self.selectBoxBase['pos']) self.selectBox.blockSignals(True) self.selectBox.setState(self.selectBoxBase) self.selectBox.applyGlobalTransform(self.userTransform) #self.selectBox.setAngle(self.userRotate) #self.selectBox.setPos([x2, y2]) self.selectBox.blockSignals(False) def selectBoxToItem(self): """Move/scale the selection box so it fits the item's bounding rect. (assumes item is not rotated)""" self.itemRect = self._graphicsItem.boundingRect() rect = self._graphicsItem.mapRectToParent(self.itemRect) self.selectBox.blockSignals(True) self.selectBox.setPos([rect.x(), rect.y()]) self.selectBox.setSize(rect.size()) self.selectBox.setAngle(0) self.selectBoxBase = self.selectBox.getState().copy() self.selectBox.blockSignals(False) def zValue(self): return self.opts['z'] def setZValue(self, z): self.opts['z'] = z if z is not None: self._graphicsItem.setZValue(z) def selectionChanged(self, sel, multi): """ Inform the item that its selection state has changed. ============== ========================================================= **Arguments:** sel (bool) whether the item is currently selected multi (bool) whether there are multiple items currently selected ============== ========================================================= """ self.selectedAlone = sel and not multi self.showSelectBox() if self.selectedAlone: self.ctrlWidget().show() else: self.ctrlWidget().hide() def showSelectBox(self): """Display the selection box around this item if it is selected and movable""" if self.selectedAlone and self.isMovable() and self.isVisible(): #and len(self.canvas.itemList.selectedItems())==1: self.selectBox.show() else: self.selectBox.hide() def hideSelectBox(self): self.selectBox.hide() def selectBoxChanged(self): self.selectBoxMoved() self.sigTransformChanged.emit(self) def selectBoxChangeFinished(self): self.sigTransformChangeFinished.emit(self) def alphaPressed(self): """Hide selection box while slider is moving""" self.hideSelectBox() def alphaReleased(self): self.showSelectBox() def show(self): if self.opts['visible']: return self.opts['visible'] = True self._graphicsItem.show() self.showSelectBox() self.sigVisibilityChanged.emit(self) def hide(self): if not self.opts['visible']: return self.opts['visible'] = False self._graphicsItem.hide() self.hideSelectBox() self.sigVisibilityChanged.emit(self) def setVisible(self, vis): if vis: self.show() else: self.hide() def isVisible(self): return self.opts['visible'] def saveState(self): return { 'type': self.__class__.__name__, 'name': self.name, 'visible': self.isVisible(), 'alpha': self.alpha(), 'userTransform': self.saveTransform(), 'z': self.zValue(), 'scalable': self.opts['scalable'], 'rotatable': self.opts['rotatable'], 'movable': self.opts['movable'], } def restoreState(self, state): self.setVisible(state['visible']) self.setAlpha(state['alpha']) self.restoreTransform(state['userTransform']) self.setZValue(state['z']) class GroupCanvasItem(CanvasItem): """ Canvas item used for grouping others """ def __init__(self, **opts): defOpts = {'movable': False, 'scalable': False} defOpts.update(opts) item = ItemGroup() CanvasItem.__init__(self, item, **defOpts) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/CanvasManager.py000066400000000000000000000042231421045507400242070ustar00rootroot00000000000000from ..Qt import QtCore, QtWidgets if not hasattr(QtCore, 'Signal'): QtCore.Signal = QtCore.pyqtSignal import weakref class CanvasManager(QtCore.QObject): SINGLETON = None sigCanvasListChanged = QtCore.Signal() def __init__(self): if CanvasManager.SINGLETON is not None: raise Exception("Can only create one canvas manager.") CanvasManager.SINGLETON = self QtCore.QObject.__init__(self) self.canvases = weakref.WeakValueDictionary() @classmethod def instance(cls): return CanvasManager.SINGLETON def registerCanvas(self, canvas, name): n2 = name i = 0 while n2 in self.canvases: n2 = "%s_%03d" % (name, i) i += 1 self.canvases[n2] = canvas self.sigCanvasListChanged.emit() return n2 def unregisterCanvas(self, name): c = self.canvases[name] del self.canvases[name] self.sigCanvasListChanged.emit() def listCanvases(self): return list(self.canvases.keys()) def getCanvas(self, name): return self.canvases[name] manager = CanvasManager() class CanvasCombo(QtWidgets.QComboBox): def __init__(self, parent=None): QtWidgets.QComboBox.__init__(self, parent) man = CanvasManager.instance() man.sigCanvasListChanged.connect(self.updateCanvasList) self.hostName = None self.updateCanvasList() def updateCanvasList(self): canvases = CanvasManager.instance().listCanvases() canvases.insert(0, "") if self.hostName in canvases: canvases.remove(self.hostName) sel = self.currentText() if sel in canvases: self.blockSignals(True) ## change does not affect current selection; block signals during update self.clear() for i in canvases: self.addItem(i) if i == sel: self.setCurrentIndex(self.count()) self.blockSignals(False) def setHostName(self, name): self.hostName = name self.updateCanvasList() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/CanvasTemplate.ui000066400000000000000000000103541421045507400243770ustar00rootroot00000000000000 Form 0 0 821 578 PyQtGraph 0 0 Qt::Horizontal Qt::Vertical 0 1 Auto Range 0 Check to display all local items in a remote canvas. Redirect 0 100 true 1 Reset Transforms Mirror Selection MirrorXY 0 0 TreeWidget QTreeWidget
      ..widgets.TreeWidget
      GraphicsView QGraphicsView
      ..widgets.GraphicsView
      CanvasCombo QComboBox
      .CanvasManager
      pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/CanvasTemplate_pyqt5.py000066400000000000000000000114411421045507400255520ustar00rootroot00000000000000 # Form implementation generated from reading ui file 'CanvasTemplate.ui' # # Created by: PyQt5 UI code generator 5.7.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(821, 578) self.gridLayout_2 = QtWidgets.QGridLayout(Form) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Horizontal) self.splitter.setObjectName("splitter") self.view = GraphicsView(self.splitter) self.view.setObjectName("view") self.vsplitter = QtWidgets.QSplitter(self.splitter) self.vsplitter.setOrientation(QtCore.Qt.Vertical) self.vsplitter.setObjectName("vsplitter") self.canvasCtrlWidget = QtWidgets.QWidget(self.vsplitter) self.canvasCtrlWidget.setObjectName("canvasCtrlWidget") self.gridLayout = QtWidgets.QGridLayout(self.canvasCtrlWidget) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.autoRangeBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) self.autoRangeBtn.setSizePolicy(sizePolicy) self.autoRangeBtn.setObjectName("autoRangeBtn") self.gridLayout.addWidget(self.autoRangeBtn, 0, 0, 1, 2) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSpacing(0) self.horizontalLayout.setObjectName("horizontalLayout") self.redirectCheck = QtWidgets.QCheckBox(self.canvasCtrlWidget) self.redirectCheck.setObjectName("redirectCheck") self.horizontalLayout.addWidget(self.redirectCheck) self.redirectCombo = CanvasCombo(self.canvasCtrlWidget) self.redirectCombo.setObjectName("redirectCombo") self.horizontalLayout.addWidget(self.redirectCombo) self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2) self.itemList = TreeWidget(self.canvasCtrlWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(100) sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) self.itemList.setSizePolicy(sizePolicy) self.itemList.setHeaderHidden(True) self.itemList.setObjectName("itemList") self.itemList.headerItem().setText(0, "1") self.gridLayout.addWidget(self.itemList, 2, 0, 1, 2) self.resetTransformsBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) self.resetTransformsBtn.setObjectName("resetTransformsBtn") self.gridLayout.addWidget(self.resetTransformsBtn, 3, 0, 1, 2) self.mirrorSelectionBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") self.gridLayout.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) self.reflectSelectionBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") self.gridLayout.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) self.canvasItemCtrl = QtWidgets.QWidget(self.vsplitter) self.canvasItemCtrl.setObjectName("canvasItemCtrl") self.ctrlLayout = QtWidgets.QGridLayout(self.canvasItemCtrl) self.ctrlLayout.setContentsMargins(0, 0, 0, 0) self.ctrlLayout.setSpacing(0) self.ctrlLayout.setObjectName("ctrlLayout") self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.autoRangeBtn.setText(_translate("Form", "Auto Range")) self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas.")) self.redirectCheck.setText(_translate("Form", "Redirect")) self.resetTransformsBtn.setText(_translate("Form", "Reset Transforms")) self.mirrorSelectionBtn.setText(_translate("Form", "Mirror Selection")) self.reflectSelectionBtn.setText(_translate("Form", "MirrorXY")) from ..widgets.GraphicsView import GraphicsView from ..widgets.TreeWidget import TreeWidget from .CanvasManager import CanvasCombo pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/CanvasTemplate_pyqt6.py000066400000000000000000000117061421045507400255570ustar00rootroot00000000000000# Form implementation generated from reading ui file '../pyqtgraph/canvas/CanvasTemplate.ui' # # Created by: PyQt6 UI code generator 6.1.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(821, 578) self.gridLayout_2 = QtWidgets.QGridLayout(Form) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Orientation.Horizontal) self.splitter.setObjectName("splitter") self.view = GraphicsView(self.splitter) self.view.setObjectName("view") self.vsplitter = QtWidgets.QSplitter(self.splitter) self.vsplitter.setOrientation(QtCore.Qt.Orientation.Vertical) self.vsplitter.setObjectName("vsplitter") self.canvasCtrlWidget = QtWidgets.QWidget(self.vsplitter) self.canvasCtrlWidget.setObjectName("canvasCtrlWidget") self.gridLayout = QtWidgets.QGridLayout(self.canvasCtrlWidget) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.autoRangeBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) self.autoRangeBtn.setSizePolicy(sizePolicy) self.autoRangeBtn.setObjectName("autoRangeBtn") self.gridLayout.addWidget(self.autoRangeBtn, 0, 0, 1, 2) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSpacing(0) self.horizontalLayout.setObjectName("horizontalLayout") self.redirectCheck = QtWidgets.QCheckBox(self.canvasCtrlWidget) self.redirectCheck.setObjectName("redirectCheck") self.horizontalLayout.addWidget(self.redirectCheck) self.redirectCombo = CanvasCombo(self.canvasCtrlWidget) self.redirectCombo.setObjectName("redirectCombo") self.horizontalLayout.addWidget(self.redirectCombo) self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2) self.itemList = TreeWidget(self.canvasCtrlWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(100) sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) self.itemList.setSizePolicy(sizePolicy) self.itemList.setHeaderHidden(True) self.itemList.setObjectName("itemList") self.itemList.headerItem().setText(0, "1") self.gridLayout.addWidget(self.itemList, 2, 0, 1, 2) self.resetTransformsBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) self.resetTransformsBtn.setObjectName("resetTransformsBtn") self.gridLayout.addWidget(self.resetTransformsBtn, 3, 0, 1, 2) self.mirrorSelectionBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") self.gridLayout.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) self.reflectSelectionBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") self.gridLayout.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) self.canvasItemCtrl = QtWidgets.QWidget(self.vsplitter) self.canvasItemCtrl.setObjectName("canvasItemCtrl") self.ctrlLayout = QtWidgets.QGridLayout(self.canvasItemCtrl) self.ctrlLayout.setContentsMargins(0, 0, 0, 0) self.ctrlLayout.setSpacing(0) self.ctrlLayout.setObjectName("ctrlLayout") self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.autoRangeBtn.setText(_translate("Form", "Auto Range")) self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas.")) self.redirectCheck.setText(_translate("Form", "Redirect")) self.resetTransformsBtn.setText(_translate("Form", "Reset Transforms")) self.mirrorSelectionBtn.setText(_translate("Form", "Mirror Selection")) self.reflectSelectionBtn.setText(_translate("Form", "MirrorXY")) from ..widgets.GraphicsView import GraphicsView from ..widgets.TreeWidget import TreeWidget from .CanvasManager import CanvasCombo pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/CanvasTemplate_pyside2.py000066400000000000000000000112451421045507400260510ustar00rootroot00000000000000 # Form implementation generated from reading ui file 'CanvasTemplate.ui' # # Created: Sun Sep 18 19:18:22 2016 # by: pyside2-uic running on PySide2 2.0.0~alpha0 # # WARNING! All changes made in this file will be lost! from PySide2 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(490, 414) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Horizontal) self.splitter.setObjectName("splitter") self.view = GraphicsView(self.splitter) self.view.setObjectName("view") self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.gridLayout_2 = QtWidgets.QGridLayout(self.layoutWidget) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setObjectName("gridLayout_2") self.autoRangeBtn = QtWidgets.QPushButton(self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) self.autoRangeBtn.setSizePolicy(sizePolicy) self.autoRangeBtn.setObjectName("autoRangeBtn") self.gridLayout_2.addWidget(self.autoRangeBtn, 2, 0, 1, 2) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSpacing(0) self.horizontalLayout.setObjectName("horizontalLayout") self.redirectCheck = QtWidgets.QCheckBox(self.layoutWidget) self.redirectCheck.setObjectName("redirectCheck") self.horizontalLayout.addWidget(self.redirectCheck) self.redirectCombo = CanvasCombo(self.layoutWidget) self.redirectCombo.setObjectName("redirectCombo") self.horizontalLayout.addWidget(self.redirectCombo) self.gridLayout_2.addLayout(self.horizontalLayout, 5, 0, 1, 2) self.itemList = TreeWidget(self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(100) sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) self.itemList.setSizePolicy(sizePolicy) self.itemList.setHeaderHidden(True) self.itemList.setObjectName("itemList") self.itemList.headerItem().setText(0, "1") self.gridLayout_2.addWidget(self.itemList, 6, 0, 1, 2) self.ctrlLayout = QtWidgets.QGridLayout() self.ctrlLayout.setSpacing(0) self.ctrlLayout.setObjectName("ctrlLayout") self.gridLayout_2.addLayout(self.ctrlLayout, 10, 0, 1, 2) self.resetTransformsBtn = QtWidgets.QPushButton(self.layoutWidget) self.resetTransformsBtn.setObjectName("resetTransformsBtn") self.gridLayout_2.addWidget(self.resetTransformsBtn, 7, 0, 1, 1) self.mirrorSelectionBtn = QtWidgets.QPushButton(self.layoutWidget) self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 3, 0, 1, 1) self.reflectSelectionBtn = QtWidgets.QPushButton(self.layoutWidget) self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") self.gridLayout_2.addWidget(self.reflectSelectionBtn, 3, 1, 1, 1) self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1)) self.autoRangeBtn.setText(QtWidgets.QApplication.translate("Form", "Auto Range", None, -1)) self.redirectCheck.setToolTip(QtWidgets.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, -1)) self.redirectCheck.setText(QtWidgets.QApplication.translate("Form", "Redirect", None, -1)) self.resetTransformsBtn.setText(QtWidgets.QApplication.translate("Form", "Reset Transforms", None, -1)) self.mirrorSelectionBtn.setText(QtWidgets.QApplication.translate("Form", "Mirror Selection", None, -1)) self.reflectSelectionBtn.setText(QtWidgets.QApplication.translate("Form", "MirrorXY", None, -1)) from ..widgets.TreeWidget import TreeWidget from CanvasManager import CanvasCombo from ..widgets.GraphicsView import GraphicsView pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/CanvasTemplate_pyside6.py000066400000000000000000000124701421045507400260560ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'CanvasTemplate.ui' ## ## Created by: Qt User Interface Compiler version 6.1.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * from ..widgets.TreeWidget import TreeWidget from ..widgets.GraphicsView import GraphicsView from .CanvasManager import CanvasCombo class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(821, 578) self.gridLayout_2 = QGridLayout(Form) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setObjectName(u"gridLayout_2") self.splitter = QSplitter(Form) self.splitter.setObjectName(u"splitter") self.splitter.setOrientation(Qt.Horizontal) self.view = GraphicsView(self.splitter) self.view.setObjectName(u"view") self.splitter.addWidget(self.view) self.vsplitter = QSplitter(self.splitter) self.vsplitter.setObjectName(u"vsplitter") self.vsplitter.setOrientation(Qt.Vertical) self.canvasCtrlWidget = QWidget(self.vsplitter) self.canvasCtrlWidget.setObjectName(u"canvasCtrlWidget") self.gridLayout = QGridLayout(self.canvasCtrlWidget) self.gridLayout.setObjectName(u"gridLayout") self.autoRangeBtn = QPushButton(self.canvasCtrlWidget) self.autoRangeBtn.setObjectName(u"autoRangeBtn") sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) self.autoRangeBtn.setSizePolicy(sizePolicy) self.gridLayout.addWidget(self.autoRangeBtn, 0, 0, 1, 2) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setSpacing(0) self.horizontalLayout.setObjectName(u"horizontalLayout") self.redirectCheck = QCheckBox(self.canvasCtrlWidget) self.redirectCheck.setObjectName(u"redirectCheck") self.horizontalLayout.addWidget(self.redirectCheck) self.redirectCombo = CanvasCombo(self.canvasCtrlWidget) self.redirectCombo.setObjectName(u"redirectCombo") self.horizontalLayout.addWidget(self.redirectCombo) self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2) self.itemList = TreeWidget(self.canvasCtrlWidget) __qtreewidgetitem = QTreeWidgetItem() __qtreewidgetitem.setText(0, u"1"); self.itemList.setHeaderItem(__qtreewidgetitem) self.itemList.setObjectName(u"itemList") sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(100) sizePolicy1.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) self.itemList.setSizePolicy(sizePolicy1) self.itemList.setHeaderHidden(True) self.gridLayout.addWidget(self.itemList, 2, 0, 1, 2) self.resetTransformsBtn = QPushButton(self.canvasCtrlWidget) self.resetTransformsBtn.setObjectName(u"resetTransformsBtn") self.gridLayout.addWidget(self.resetTransformsBtn, 3, 0, 1, 2) self.mirrorSelectionBtn = QPushButton(self.canvasCtrlWidget) self.mirrorSelectionBtn.setObjectName(u"mirrorSelectionBtn") self.gridLayout.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) self.reflectSelectionBtn = QPushButton(self.canvasCtrlWidget) self.reflectSelectionBtn.setObjectName(u"reflectSelectionBtn") self.gridLayout.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) self.vsplitter.addWidget(self.canvasCtrlWidget) self.canvasItemCtrl = QWidget(self.vsplitter) self.canvasItemCtrl.setObjectName(u"canvasItemCtrl") self.ctrlLayout = QGridLayout(self.canvasItemCtrl) self.ctrlLayout.setSpacing(0) self.ctrlLayout.setContentsMargins(0, 0, 0, 0) self.ctrlLayout.setObjectName(u"ctrlLayout") self.vsplitter.addWidget(self.canvasItemCtrl) self.splitter.addWidget(self.vsplitter) self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) self.autoRangeBtn.setText(QCoreApplication.translate("Form", u"Auto Range", None)) #if QT_CONFIG(tooltip) self.redirectCheck.setToolTip(QCoreApplication.translate("Form", u"Check to display all local items in a remote canvas.", None)) #endif // QT_CONFIG(tooltip) self.redirectCheck.setText(QCoreApplication.translate("Form", u"Redirect", None)) self.resetTransformsBtn.setText(QCoreApplication.translate("Form", u"Reset Transforms", None)) self.mirrorSelectionBtn.setText(QCoreApplication.translate("Form", u"Mirror Selection", None)) self.reflectSelectionBtn.setText(QCoreApplication.translate("Form", u"MirrorXY", None)) # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/TransformGuiTemplate.ui000066400000000000000000000034301421045507400256010ustar00rootroot00000000000000 Form 0 0 224 117 0 0 PyQtGraph 1 0 Translate: Rotate: Scale: Mirror Reflect pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/TransformGuiTemplate_pyqt5.py000066400000000000000000000047431421045507400267660ustar00rootroot00000000000000 # Form implementation generated from reading ui file 'pyqtgraph/canvas/TransformGuiTemplate.ui' # # Created by: PyQt5 UI code generator 5.5.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(224, 117) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) Form.setSizePolicy(sizePolicy) self.verticalLayout = QtWidgets.QVBoxLayout(Form) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(1) self.verticalLayout.setObjectName("verticalLayout") self.translateLabel = QtWidgets.QLabel(Form) self.translateLabel.setObjectName("translateLabel") self.verticalLayout.addWidget(self.translateLabel) self.rotateLabel = QtWidgets.QLabel(Form) self.rotateLabel.setObjectName("rotateLabel") self.verticalLayout.addWidget(self.rotateLabel) self.scaleLabel = QtWidgets.QLabel(Form) self.scaleLabel.setObjectName("scaleLabel") self.verticalLayout.addWidget(self.scaleLabel) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.mirrorImageBtn = QtWidgets.QPushButton(Form) self.mirrorImageBtn.setToolTip("") self.mirrorImageBtn.setObjectName("mirrorImageBtn") self.horizontalLayout.addWidget(self.mirrorImageBtn) self.reflectImageBtn = QtWidgets.QPushButton(Form) self.reflectImageBtn.setObjectName("reflectImageBtn") self.horizontalLayout.addWidget(self.reflectImageBtn) self.verticalLayout.addLayout(self.horizontalLayout) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.translateLabel.setText(_translate("Form", "Translate:")) self.rotateLabel.setText(_translate("Form", "Rotate:")) self.scaleLabel.setText(_translate("Form", "Scale:")) self.mirrorImageBtn.setText(_translate("Form", "Mirror")) self.reflectImageBtn.setText(_translate("Form", "Reflect")) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/TransformGuiTemplate_pyqt6.py000066400000000000000000000051221421045507400267570ustar00rootroot00000000000000# Form implementation generated from reading ui file '../pyqtgraph/canvas/TransformGuiTemplate.ui' # # Created by: PyQt6 UI code generator 6.1.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(224, 117) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) Form.setSizePolicy(sizePolicy) self.verticalLayout = QtWidgets.QVBoxLayout(Form) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(1) self.verticalLayout.setObjectName("verticalLayout") self.translateLabel = QtWidgets.QLabel(Form) self.translateLabel.setObjectName("translateLabel") self.verticalLayout.addWidget(self.translateLabel) self.rotateLabel = QtWidgets.QLabel(Form) self.rotateLabel.setObjectName("rotateLabel") self.verticalLayout.addWidget(self.rotateLabel) self.scaleLabel = QtWidgets.QLabel(Form) self.scaleLabel.setObjectName("scaleLabel") self.verticalLayout.addWidget(self.scaleLabel) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.mirrorImageBtn = QtWidgets.QPushButton(Form) self.mirrorImageBtn.setToolTip("") self.mirrorImageBtn.setObjectName("mirrorImageBtn") self.horizontalLayout.addWidget(self.mirrorImageBtn) self.reflectImageBtn = QtWidgets.QPushButton(Form) self.reflectImageBtn.setObjectName("reflectImageBtn") self.horizontalLayout.addWidget(self.reflectImageBtn) self.verticalLayout.addLayout(self.horizontalLayout) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.translateLabel.setText(_translate("Form", "Translate:")) self.rotateLabel.setText(_translate("Form", "Rotate:")) self.scaleLabel.setText(_translate("Form", "Scale:")) self.mirrorImageBtn.setText(_translate("Form", "Mirror")) self.reflectImageBtn.setText(_translate("Form", "Reflect")) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/TransformGuiTemplate_pyside2.py000066400000000000000000000052101421045507400272510ustar00rootroot00000000000000 # Form implementation generated from reading ui file 'TransformGuiTemplate.ui' # # Created: Sun Sep 18 19:18:41 2016 # by: pyside2-uic running on PySide2 2.0.0~alpha0 # # WARNING! All changes made in this file will be lost! from PySide2 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(224, 117) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) Form.setSizePolicy(sizePolicy) self.verticalLayout = QtWidgets.QVBoxLayout(Form) self.verticalLayout.setSpacing(1) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") self.translateLabel = QtWidgets.QLabel(Form) self.translateLabel.setObjectName("translateLabel") self.verticalLayout.addWidget(self.translateLabel) self.rotateLabel = QtWidgets.QLabel(Form) self.rotateLabel.setObjectName("rotateLabel") self.verticalLayout.addWidget(self.rotateLabel) self.scaleLabel = QtWidgets.QLabel(Form) self.scaleLabel.setObjectName("scaleLabel") self.verticalLayout.addWidget(self.scaleLabel) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.mirrorImageBtn = QtWidgets.QPushButton(Form) self.mirrorImageBtn.setToolTip("") self.mirrorImageBtn.setObjectName("mirrorImageBtn") self.horizontalLayout.addWidget(self.mirrorImageBtn) self.reflectImageBtn = QtWidgets.QPushButton(Form) self.reflectImageBtn.setObjectName("reflectImageBtn") self.horizontalLayout.addWidget(self.reflectImageBtn) self.verticalLayout.addLayout(self.horizontalLayout) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1)) self.translateLabel.setText(QtWidgets.QApplication.translate("Form", "Translate:", None, -1)) self.rotateLabel.setText(QtWidgets.QApplication.translate("Form", "Rotate:", None, -1)) self.scaleLabel.setText(QtWidgets.QApplication.translate("Form", "Scale:", None, -1)) self.mirrorImageBtn.setText(QtWidgets.QApplication.translate("Form", "Mirror", None, -1)) self.reflectImageBtn.setText(QtWidgets.QApplication.translate("Form", "Reflect", None, -1)) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/TransformGuiTemplate_pyside6.py000066400000000000000000000054661421045507400272720ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'TransformGuiTemplate.ui' ## ## Created by: Qt User Interface Compiler version 6.1.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(224, 117) sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) Form.setSizePolicy(sizePolicy) self.verticalLayout = QVBoxLayout(Form) self.verticalLayout.setSpacing(1) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName(u"verticalLayout") self.translateLabel = QLabel(Form) self.translateLabel.setObjectName(u"translateLabel") self.verticalLayout.addWidget(self.translateLabel) self.rotateLabel = QLabel(Form) self.rotateLabel.setObjectName(u"rotateLabel") self.verticalLayout.addWidget(self.rotateLabel) self.scaleLabel = QLabel(Form) self.scaleLabel.setObjectName(u"scaleLabel") self.verticalLayout.addWidget(self.scaleLabel) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName(u"horizontalLayout") self.mirrorImageBtn = QPushButton(Form) self.mirrorImageBtn.setObjectName(u"mirrorImageBtn") self.horizontalLayout.addWidget(self.mirrorImageBtn) self.reflectImageBtn = QPushButton(Form) self.reflectImageBtn.setObjectName(u"reflectImageBtn") self.horizontalLayout.addWidget(self.reflectImageBtn) self.verticalLayout.addLayout(self.horizontalLayout) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) self.translateLabel.setText(QCoreApplication.translate("Form", u"Translate:", None)) self.rotateLabel.setText(QCoreApplication.translate("Form", u"Rotate:", None)) self.scaleLabel.setText(QCoreApplication.translate("Form", u"Scale:", None)) #if QT_CONFIG(tooltip) self.mirrorImageBtn.setToolTip("") #endif // QT_CONFIG(tooltip) self.mirrorImageBtn.setText(QCoreApplication.translate("Form", u"Mirror", None)) self.reflectImageBtn.setText(QCoreApplication.translate("Form", u"Reflect", None)) # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/canvas/__init__.py000066400000000000000000000000601421045507400232330ustar00rootroot00000000000000from .Canvas import * from .CanvasItem import * pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colormap.py000066400000000000000000001023411421045507400220420ustar00rootroot00000000000000import warnings from collections.abc import Callable, Sequence from os import listdir, path import numpy as np from .functions import clip_array, clip_scalar, colorDistance, eq, mkColor from .Qt import QtCore, QtGui __all__ = ['ColorMap'] _mapCache = {} def listMaps(source=None): """ .. warning:: Experimental, subject to change. List available color maps. Parameters ---------- source: str, optional 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 Returns ------- list of str Known color map names. """ if source is None: pathname = path.join(path.dirname(__file__), 'colors','maps') files = listdir( pathname ) list_of_maps = [] for filename in files: if filename[-4:] == '.csv' or filename[-4:] == '.hex': list_of_maps.append(filename[:-4]) return list_of_maps elif source.lower() == 'matplotlib': try: import matplotlib.pyplot as mpl_plt list_of_maps = mpl_plt.colormaps() return list_of_maps except ModuleNotFoundError: return [] elif source.lower() == 'colorcet': try: import colorcet list_of_maps = list( colorcet.palette.keys() ) list_of_maps.sort() return list_of_maps except ModuleNotFoundError: return [] return [] def get(name, source=None, skipCache=False): """ .. warning:: Experimental, subject to change. Returns a ColorMap object from a local definition or imported from another library. The generated ColorMap objects are cached for fast repeated access. Parameters ---------- name: str Name of color map. In addition to the included maps, this can also 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: - '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 ColorMap object is generated. This can load an unaltered copy when the previous ColorMap object has been modified. """ if not skipCache and name in _mapCache: return _mapCache[name] if source is None: return _getFromFile(name) elif source == 'matplotlib': return getFromMatplotlib(name) elif source == 'colorcet': return getFromColorcet(name) return None def _getFromFile(name): filename = name if filename[0] !='.': # load from built-in directory dirname = path.dirname(__file__) 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+'.hex' ): filename += '.hex' with open(filename,'r') as fh: idx = 0 color_list = [] if filename[-4:].lower() != '.hex': csv_mode = True else: csv_mode = False for line in fh: line = line.strip() if len(line) == 0: continue # empty line if line[0] == ';': continue # comment parts = line.split(sep=';', maxsplit=1) # split into color and names/comments if csv_mode: comp = parts[0].split(',') if len( comp ) < 3: continue # not enough components given color_tuple = tuple( [ int(255*float(c)+0.5) for c in comp ] ) else: hex_str = parts[0] if hex_str[0] == '#': hex_str = hex_str[1:] # strip leading # if len(hex_str) < 3: continue # not enough information if len(hex_str) == 3: # parse as abbreviated RGB hex_str = 2*hex_str[0] + 2*hex_str[1] + 2*hex_str[2] 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 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 cmap = ColorMap( name=name, pos=np.linspace(0.0, 1.0, len(color_list)), color=color_list) #, names=color_names) if cmap is not None: cmap.name = name _mapCache[name] = cmap return cmap def getFromMatplotlib(name): """ Generates a ColorMap object from a Matplotlib definition. Same as ``colormap.get(name, source='matplotlib')``. """ # inspired and informed by "mpl_cmaps_in_ImageItem.py", published by Sebastian Hoefer at # https://github.com/honkomonk/pyqtgraph_sandbox/blob/master/mpl_cmaps_in_ImageItem.py try: import matplotlib.pyplot as mpl_plt except ModuleNotFoundError: return None cmap = None col_map = mpl_plt.get_cmap(name) if hasattr(col_map, '_segmentdata'): # handle LinearSegmentedColormap data = col_map._segmentdata if ('red' in data) and isinstance(data['red'], (Sequence, np.ndarray)): positions = set() # super-set of handle positions in individual channels for key in ['red','green','blue']: for tup in data[key]: positions.add(tup[0]) col_data = np.zeros((len(positions),4 )) col_data[:,-1] = sorted(positions) for idx, key in enumerate(['red','green','blue']): positions = np.zeros( len(data[key] ) ) comp_vals = np.zeros( len(data[key] ) ) for idx2, tup in enumerate( data[key] ): 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) 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) 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) 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')``. """ try: import colorcet except ModuleNotFoundError: return None color_strings = colorcet.palette[name] color_list = [] for hex_str in color_strings: if hex_str[0] != '#': continue if len(hex_str) != 7: 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 cmap = ColorMap( name=name, pos=np.linspace(0.0, 1.0, len(color_list)), color=color_list) #, names=color_names) if cmap is not None: cmap.name = name _mapCache[name] = cmap return cmap 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. In addition to neutral, warm or cold grays, imitations of monochrome computer monitors are also available. The following predefined color ramps are available: `neutral`, `warm`, `cool`, `green`, `amber`, `blue`, `red`, `pink`, `lavender`. The ramp can also be specified by a tuple of float values in the range of 0 to 1. In this case `(h, s, l0, l1)` describe hue, saturation, minimum lightness and maximum lightness within the HSL color space. The values `l0` and `l1` can be omitted. They default to `l0=0.0` and `l1=1.0` in this case. Parameters ---------- color: str or tuple of floats Color description. Can be one of the predefined identifiers, or a tuple `(h, s, l0, l1)`, `(h, s)` or (`h`). 'green', 'amber', 'blue', 'red', 'lavender', 'pink' or a tuple of relative ``(R,G,B)`` contributions in range 0.0 to 1.0 """ name=f'Monochrome {color}' defaults = { 'neutral': (0.00, 0.00, 0.00, 1.00), 'warm' : (0.10, 0.08, 0.00, 0.95), 'cool' : (0.60, 0.08, 0.00, 0.95), 'green' : (0.35, 0.55, 0.02, 0.90), 'amber' : (0.09, 0.80, 0.02, 0.80), 'blue' : (0.58, 0.85, 0.02, 0.95), 'red' : (0.01, 0.60, 0.02, 0.90), 'pink' : (0.93, 0.65, 0.02, 0.95), 'lavender': (0.75, 0.50, 0.02, 0.90) } if isinstance(color, str): if color in defaults: h_val, s_val, l_min, l_max = defaults[color] else: valid = ','.join(defaults.keys()) raise ValueError(f"Undefined color descriptor '{color}', known values are:\n{valid}") else: s_val = 0.70 # set up default values l_min = 0.00 l_max = 1.00 if not hasattr(color,'__len__'): h_val = float(color) elif len(color) == 1: h_val = color[0] elif len(color) == 2: h_val, s_val = color elif len(color) == 4: 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=16) color_list = [] for l_val in l_vals: qcol = QtGui.QColor.fromHslF( h_val, s_val, l_val ) color_list.append( qcol ) return ColorMap( None, color_list, name=name, linearize=True ) def modulatedBarData(length=768, width=32): """ Returns an NumPy array that represents a modulated color bar ranging from 0 to 1. This is used to judge the perceived variation of the color gradient. Parameters ---------- length: int Length of the data set. Values will vary from 0 to 1 over this axis. width: int Width of the data set. The modulation will vary from 0% to 4% over this axis. """ gradient = np.linspace(0.00, 1.00, length) modulation = -0.04 * np.sin( (np.pi/4) * np.arange(length) ) data = np.zeros( (length, width) ) for idx in range(width): data[:,idx] = gradient + (idx/(width-1)) * modulation clip_array(data, 0.0, 1.0, out=data) return data class ColorMap(object): """ ColorMap(pos, color, mapping=ColorMap.CLIP) ColorMap stores a mapping of specific data values to colors, for example: | 0.0 β†’ black | 0.2 β†’ red | 0.6 β†’ yellow | 1.0 β†’ white The colors for intermediate values are determined by interpolating between the two nearest colors in RGB color space. A ColorMap object provides access to the interpolated colors by indexing with a float value: ``cm[0.5]`` returns a QColor corresponding to the center of ColorMap `cm`. """ ## mapping modes CLIP = 1 REPEAT = 2 MIRROR = 3 DIVERGING = 4 ## return types BYTE = 1 FLOAT = 2 QCOLOR = 3 enumMap = { 'clip': CLIP, 'repeat': REPEAT, 'mirror': MIRROR, 'diverging': DIVERGING, 'byte': BYTE, 'float': FLOAT, 'qcolor': QCOLOR, } def __init__(self, pos, color, mapping=CLIP, mode=None, linearize=False, name=''): """ __init__(pos, color, mapping=ColorMap.CLIP) Parameters ---------- pos: array_like of float in range 0 to 1, or None Assigned positions of specified colors. `None` sets equal spacing. color: array_like of colors List of colors, interpreted via :func:`mkColor() `. mapping: str or int, optional Controls how values outside the 0 to 1 range are mapped to colors. See :func:`setMappingMode() ` for details. The default of `ColorMap.CLIP` continues to show the colors assigned to 0 and 1 for all values below or above this range, respectively. """ self.name = name # storing a name helps identify ColorMaps sampled by Palette if mode is not None: warnings.warn( "'mode' argument is deprecated and does nothing.", DeprecationWarning, stacklevel=2 ) if pos is None: order = range(len(color)) self.pos = np.linspace(0.0, 1.0, num=len(color)) else: self.pos = np.array(pos) order = np.argsort(self.pos) self.pos = self.pos[order] self.color = np.zeros( (len(color), 4) ) # stores float rgba values for cnt, idx in enumerate(order): self.color[cnt] = mkColor(color[idx]).getRgbF() # alternative code may be more efficient, but fails to handle lists of QColor. # self.color = np.apply_along_axis( # func1d = lambda x: np.uint8( mkColor(x).getRgb() ), # cast RGB integer values to uint8 # axis = -1, # arr = color, # )[order] self.mapping_mode = self.CLIP # default to CLIP mode if mapping is not None: self.setMappingMode( mapping ) self.stopsCache = {} if linearize: self.linearize() def setMappingMode(self, mapping): """ Sets the way that values outside of the range 0 to 1 are mapped to colors. Parameters ---------- mapping: int or str Sets mapping mode to - `ColorMap.CLIP` or 'clip': Values are clipped to the range 0 to 1. ColorMap defaults to this. - `ColorMap.REPEAT` or 'repeat': Colors repeat cyclically, i.e. range 1 to 2 repeats the colors for 0 to 1. - `ColorMap.MIRROR` or 'mirror': The range 0 to -1 uses same colors (in reverse order) as 0 to 1. - `ColorMap.DIVERGING` or 'diverging': Colors are mapped to -1 to 1 such that the central value appears at 0. """ if isinstance(mapping, str): mapping = self.enumMap[mapping.lower()] if mapping in [self.CLIP, self.REPEAT, self.DIVERGING, self.MIRROR]: self.mapping_mode = mapping # only allow defined values else: raise ValueError(f"Undefined mapping type '{mapping}'") self.stopsCache = {} def __str__(self): """ provide human-readable identifier """ if self.name is None: return 'unnamed ColorMap({:d})'.format(len(self.pos)) return "ColorMap({:d}):'{:s}'".format(len(self.pos),self.name) def __getitem__(self, key): """ Convenient shorthand access to palette colors """ if isinstance(key, int): # access by color index return self.getByIndex(key) # otherwise access by map try: # accept any numerical format that converts to float float_idx = float(key) return self.mapToQColor(float_idx) except ValueError: pass return None def linearize(self): """ Adjusts the positions assigned to color stops to approximately equalize the perceived color difference for a fixed step. """ colors = self.getColors(mode=self.QCOLOR) distances = colorDistance(colors) positions = np.insert( np.cumsum(distances), 0, 0.0 ) self.pos = positions / positions[-1] # normalize last value to 1.0 self.stopsCache = {} def reverse(self): """ Reverses the color map, so that the color assigned to a value of 1 now appears at 0 and vice versa. This is convenient to adjust imported color maps. """ 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): """ map(data, mode=ColorMap.BYTE) Returns an array of colors corresponding to a single value or an array of values. Data must be either a scalar position or an array (any shape) of positions. Parameters ---------- data: float or array_like of float Scalar value(s) to be mapped to colors 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. Returns ------- array of color.dtype for `ColorMap.BYTE` or `ColorMap.FLOAT`: RGB values for each `data` value, arranged in the same shape as `data`. list of QColor objects for `ColorMap.QCOLOR`: Colors for each `data` value as Qcolor objects. """ if isinstance(mode, str): mode = self.enumMap[mode.lower()] if mode == self.QCOLOR: pos, color = self.getStops(self.FLOAT) else: pos, color = self.getStops(mode) if np.isscalar(data): interp = np.empty((color.shape[1],), dtype=color.dtype) else: if not isinstance(data, np.ndarray): data = np.array(data) interp = np.empty(data.shape + (color.shape[1],), dtype=color.dtype) if self.mapping_mode != self.CLIP: if self.mapping_mode == self.REPEAT: data = data % 1.0 elif self.mapping_mode == self.DIVERGING: data = (data/2)+0.5 elif self.mapping_mode == self.MIRROR: data = abs(data) for i in range(color.shape[1]): interp[...,i] = np.interp(data, pos, color[:,i]) # Convert to QColor if requested if mode == self.QCOLOR: if np.isscalar(data): return QtGui.QColor.fromRgbF(*interp) else: return [QtGui.QColor.fromRgbF(*x.tolist()) for x in interp] else: return interp def mapToQColor(self, data): """Convenience function; see :func:`map() `.""" return self.map(data, mode=self.QCOLOR) def mapToByte(self, data): """Convenience function; see :func:`map() `.""" return self.map(data, mode=self.BYTE) def mapToFloat(self, data): """Convenience function; see :func:`map() `.""" return self.map(data, mode=self.FLOAT) def getByIndex(self, idx): """Retrieve a QColor by the index of the stop it is assigned to.""" return QtGui.QColor( *self.color[idx] ) def getGradient(self, p1=None, p2=None): """ Returns a QtGui.QLinearGradient corresponding to this ColorMap. The span and orientiation is given by two points in plot coordinates. 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 ---------- p1: QtCore.QPointF, default (0,0) Starting point (value 0) of the gradient. p2: QtCore.QPointF, default (dy,0) End point (value 1) of the gradient. Default parameter `dy` is the span of ``max(pos) - min(pos)`` over which the color map is defined, typically `dy=1`. """ if p1 is None: p1 = QtCore.QPointF(0,0) if p2 is None: p2 = QtCore.QPointF(self.pos.max()-self.pos.min(),0) grad = QtGui.QLinearGradient(p1, p2) 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 ) pos_p = (1. + pos) / 2 col_p = color pos = np.concatenate( (pos_n, pos_p) ) color = np.concatenate( (col_n, col_p) ) grad.setStops(list(zip(pos, color))) if self.mapping_mode == self.REPEAT: grad.setSpread( QtGui.QGradient.Spread.RepeatSpread ) return grad def getBrush(self, span=(0.,1.), orientation='vertical'): """ 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`. orientation : str, default 'vertical' Orientiation of the gradient: - '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]) ) elif orientation == 'horizontal': grad = self.getGradient( p1=QtCore.QPointF(span[0],0.), p2=QtCore.QPointF(span[1],0.) ) else: raise ValueError("Orientation must be 'vertical' or 'horizontal'") return QtGui.QBrush(grad) 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`. 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. width : int or float Width of the pen in pixels on screen. """ brush = self.getBrush( span=span, orientation=orientation ) pen = QtGui.QPen(brush, width) pen.setCosmetic(True) return pen def getColors(self, mode=BYTE): """ Returns a list of the colors associated with the stops of the color map. The parameter `mode` can be one of - `ColorMap.BYTE` or 'byte' to return colors as RGBA tuples in byte format (0 to 255) - `ColorMap.FLOAT` or 'float' to return colors as RGBA tuples in float format (0.0 to 1.0) - `ColorMap.QCOLOR` or 'qcolor' to return a list of QColors The default is byte format. """ stops, color = self.getStops(mode=mode) return color def getStops(self, mode=BYTE): """ Returns a tuple (stops, colors) containing a list of all stops (ranging 0.0 to 1.0) and a list of the associated colors. The parameter `mode` can be one of - `ColorMap.BYTE` or 'byte' to return colors as RGBA tuples in byte format (0 to 255) - `ColorMap.FLOAT` or 'float' to return colors as RGBA tuples in float format (0.0 to 1.0) - `ColorMap.QCOLOR` or 'qcolor' to return a list of QColors The default is byte format. """ if isinstance(mode, str): mode = self.enumMap[mode.lower()] if mode not in self.stopsCache: color = self.color if mode == self.BYTE and color.dtype.kind == 'f': color = (color*255).astype(np.ubyte) elif mode == self.FLOAT and color.dtype.kind != 'f': color = color.astype(float) / 255. elif mode == self.QCOLOR: if color.dtype.kind == 'f': 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] def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode=BYTE): """ getLookupTable(start=0.0, stop=1.0, nPts=512, alpha=None, mode=ColorMap.BYTE) Returns an equally-spaced lookup table of RGB(A) values created by interpolating the specified color stops. Parameters ---------- start: float, default=0.0 The starting value in the lookup table stop: float, default=1.0 The final value in the lookup table nPts: int, default is 512 The number of points in the returned lookup table. alpha: True, False, or None Specifies whether or not alpha values are included in the table. If alpha is None, it will be automatically determined. mode: int or str, default is `ColorMap.BYTE` Determines return type as described in :func:`map() `, can be either `ColorMap.BYTE` (0 to 255), `ColorMap.FLOAT` (0.0 to 1.0) or `ColorMap.QColor`. Returns ------- array of color.dtype for `ColorMap.BYTE` or `ColorMap.FLOAT`: RGB values for each `data` value, arranged in the same shape as `data`. If alpha values are included the array has shape (`nPts`, 4), otherwise (`nPts`, 3). list of QColor objects for `ColorMap.QCOLOR`: Colors for each `data` value as QColor objects. """ if isinstance(mode, str): mode = self.enumMap[mode.lower()] if alpha is None: alpha = self.usesAlpha() x = np.linspace(start, stop, nPts) table = self.map(x, mode) if not alpha and mode != self.QCOLOR: return table[:,:3] else: return table def usesAlpha(self): """Returns `True` if any stops have assigned colors with alpha < 255.""" max = 1.0 if self.color.dtype.kind == 'f' else 255 return np.any(self.color[:,3] != max) def isMapTrivial(self): """ Returns `True` if the gradient has exactly two stops in it: Black at 0.0 and white at 1.0. """ if len(self.pos) != 2: return False if self.pos[0] != 0.0 or self.pos[1] != 1.0: return False if self.color.dtype.kind == 'f': return np.all(self.color == np.array([[0.,0.,0.,1.], [1.,1.,1.,1.]])) else: return np.all(self.color == np.array([[0,0,0,255], [255,255,255,255]])) def __repr__(self): pos = repr(self.pos).replace('\n', '') color = repr(self.color).replace('\n', '') return "ColorMap(%s, %s)" % (pos, color) def __eq__(self, other): if other is None: return False return eq(self.pos, other.pos) and eq(self.color, other.color) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/000077500000000000000000000000001421045507400211545ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/__init__.py000066400000000000000000000000001421045507400232530ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/000077500000000000000000000000001421045507400221145ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CC-BY license - applies to CET color map data.txt000066400000000000000000000352161421045507400323450ustar00rootroot00000000000000Creative Commons Attribution 4.0 International Public License (CC-BY) By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 - Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section [5]2(b)(1)-(2) are not Copyright and Similar Rights. d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 - Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: A. reproduce and Share the Licensed Material, in whole or in part; and B. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section [6]6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section [7]2(a)(4) never produces Adapted Material. 5. Downstream recipients. A. Offer from the Licensor - Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section [8]3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 - License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: A. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section [9]3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section [10]3(a)(1)(A) to the extent reasonably practicable. 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. Section 4 - Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section [11]2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and c. You must comply with the conditions in Section [12]3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section [13]4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 - Disclaimer of Warranties and Limitation of Liability. a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 - Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section [14]6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section [15]6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections [16]1, [17]5, [18]6, [19]7, and [20]8 survive termination of this Public License. Section 7 - Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 - Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. CC0 legal code - applies to virids, magma, plasma, inferno and cividis.txt000066400000000000000000000156101421045507400370110ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/mapsCreative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C1.csv000066400000000000000000000110001421045507400234750ustar00rootroot000000000000000.976,0.520,0.971 0.980,0.517,0.963 0.984,0.514,0.954 0.987,0.509,0.945 0.990,0.504,0.934 0.991,0.499,0.923 0.992,0.492,0.911 0.992,0.486,0.899 0.992,0.478,0.886 0.992,0.471,0.872 0.991,0.463,0.858 0.989,0.455,0.844 0.988,0.446,0.830 0.986,0.438,0.815 0.984,0.429,0.800 0.981,0.420,0.786 0.979,0.411,0.771 0.976,0.402,0.756 0.973,0.393,0.741 0.971,0.384,0.726 0.968,0.375,0.711 0.964,0.365,0.696 0.961,0.356,0.682 0.958,0.347,0.667 0.954,0.337,0.652 0.951,0.328,0.637 0.947,0.318,0.623 0.943,0.309,0.608 0.938,0.300,0.593 0.934,0.291,0.578 0.929,0.282,0.564 0.924,0.273,0.549 0.919,0.264,0.534 0.914,0.256,0.519 0.908,0.247,0.504 0.903,0.239,0.489 0.897,0.231,0.474 0.890,0.223,0.459 0.884,0.215,0.444 0.877,0.207,0.429 0.871,0.200,0.414 0.864,0.193,0.399 0.857,0.185,0.384 0.850,0.178,0.369 0.842,0.171,0.354 0.835,0.164,0.339 0.828,0.157,0.324 0.820,0.150,0.309 0.812,0.143,0.295 0.805,0.136,0.280 0.797,0.129,0.265 0.790,0.122,0.251 0.782,0.116,0.236 0.775,0.110,0.222 0.767,0.104,0.208 0.760,0.099,0.194 0.753,0.095,0.180 0.747,0.091,0.167 0.740,0.089,0.154 0.734,0.088,0.142 0.729,0.089,0.130 0.724,0.091,0.118 0.720,0.095,0.107 0.716,0.100,0.096 0.713,0.106,0.086 0.711,0.113,0.077 0.710,0.121,0.068 0.709,0.130,0.060 0.709,0.139,0.052 0.710,0.149,0.045 0.711,0.159,0.039 0.713,0.169,0.033 0.715,0.180,0.029 0.717,0.191,0.025 0.720,0.201,0.023 0.723,0.212,0.021 0.727,0.222,0.019 0.730,0.233,0.018 0.734,0.243,0.017 0.738,0.253,0.017 0.742,0.263,0.016 0.746,0.273,0.016 0.750,0.283,0.016 0.754,0.293,0.016 0.758,0.303,0.016 0.762,0.312,0.017 0.765,0.322,0.017 0.769,0.331,0.017 0.773,0.341,0.018 0.777,0.350,0.018 0.780,0.359,0.018 0.784,0.369,0.018 0.787,0.378,0.019 0.791,0.387,0.019 0.794,0.396,0.019 0.797,0.406,0.019 0.800,0.415,0.019 0.803,0.424,0.019 0.806,0.433,0.019 0.809,0.443,0.019 0.811,0.452,0.019 0.814,0.461,0.019 0.816,0.471,0.018 0.819,0.480,0.018 0.821,0.489,0.018 0.823,0.498,0.017 0.825,0.508,0.017 0.827,0.517,0.017 0.829,0.526,0.016 0.831,0.535,0.016 0.833,0.544,0.016 0.834,0.554,0.016 0.836,0.563,0.016 0.837,0.572,0.017 0.839,0.580,0.018 0.840,0.589,0.020 0.841,0.598,0.023 0.841,0.606,0.027 0.842,0.614,0.032 0.842,0.622,0.040 0.841,0.629,0.049 0.840,0.636,0.059 0.839,0.643,0.070 0.836,0.649,0.083 0.834,0.654,0.096 0.830,0.658,0.109 0.825,0.662,0.123 0.820,0.665,0.138 0.814,0.667,0.153 0.807,0.669,0.169 0.799,0.669,0.184 0.791,0.669,0.200 0.782,0.668,0.215 0.772,0.666,0.231 0.761,0.664,0.246 0.750,0.661,0.261 0.739,0.658,0.276 0.727,0.655,0.291 0.714,0.651,0.306 0.701,0.647,0.320 0.688,0.642,0.334 0.675,0.638,0.348 0.661,0.633,0.361 0.647,0.628,0.375 0.632,0.624,0.388 0.618,0.619,0.401 0.602,0.614,0.413 0.587,0.609,0.426 0.571,0.604,0.438 0.555,0.599,0.450 0.539,0.594,0.463 0.522,0.590,0.475 0.504,0.585,0.486 0.486,0.580,0.498 0.468,0.575,0.510 0.449,0.570,0.522 0.430,0.565,0.533 0.410,0.560,0.545 0.390,0.555,0.556 0.369,0.549,0.568 0.348,0.544,0.579 0.327,0.538,0.591 0.306,0.532,0.603 0.284,0.526,0.614 0.263,0.519,0.626 0.242,0.513,0.638 0.223,0.506,0.649 0.204,0.498,0.661 0.186,0.490,0.673 0.171,0.482,0.685 0.157,0.473,0.697 0.146,0.464,0.709 0.139,0.454,0.722 0.134,0.445,0.734 0.132,0.434,0.746 0.133,0.424,0.759 0.135,0.413,0.771 0.139,0.401,0.783 0.144,0.390,0.796 0.150,0.378,0.808 0.156,0.366,0.820 0.162,0.354,0.833 0.169,0.342,0.845 0.175,0.330,0.856 0.181,0.318,0.868 0.187,0.306,0.879 0.194,0.295,0.889 0.201,0.285,0.900 0.208,0.275,0.909 0.216,0.266,0.918 0.225,0.258,0.927 0.234,0.251,0.934 0.244,0.245,0.941 0.254,0.241,0.948 0.265,0.239,0.953 0.277,0.237,0.958 0.288,0.238,0.962 0.300,0.239,0.965 0.311,0.242,0.968 0.323,0.246,0.970 0.335,0.251,0.972 0.346,0.257,0.974 0.357,0.263,0.975 0.368,0.270,0.976 0.379,0.277,0.977 0.390,0.285,0.977 0.400,0.293,0.977 0.410,0.300,0.978 0.420,0.308,0.978 0.430,0.316,0.978 0.440,0.324,0.978 0.449,0.332,0.978 0.459,0.340,0.978 0.469,0.348,0.978 0.478,0.355,0.978 0.488,0.363,0.979 0.498,0.370,0.979 0.508,0.377,0.979 0.518,0.384,0.979 0.529,0.391,0.980 0.540,0.398,0.980 0.551,0.404,0.981 0.563,0.410,0.981 0.575,0.416,0.982 0.588,0.421,0.982 0.600,0.427,0.983 0.613,0.432,0.984 0.627,0.437,0.985 0.640,0.442,0.986 0.654,0.446,0.987 0.668,0.451,0.988 0.682,0.455,0.989 0.696,0.460,0.990 0.710,0.464,0.991 0.724,0.468,0.992 0.738,0.472,0.994 0.752,0.476,0.995 0.766,0.480,0.996 0.780,0.484,0.997 0.794,0.488,0.998 0.807,0.492,0.999 0.821,0.496,1.000 0.834,0.500,1.000 0.847,0.503,1.000 0.860,0.507,1.000 0.873,0.510,1.000 0.885,0.513,1.000 0.897,0.516,1.000 0.909,0.519,1.000 0.919,0.521,1.000 0.930,0.523,0.999 0.939,0.524,0.996 0.948,0.525,0.993 0.956,0.524,0.989 0.964,0.524,0.984 0.970,0.522,0.978 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C1s.csv000066400000000000000000000110001421045507400236600ustar00rootroot000000000000000.244,0.245,0.941 0.254,0.241,0.948 0.265,0.239,0.953 0.277,0.237,0.958 0.288,0.238,0.962 0.300,0.239,0.965 0.311,0.242,0.968 0.323,0.246,0.970 0.335,0.251,0.972 0.346,0.257,0.974 0.357,0.263,0.975 0.368,0.270,0.976 0.379,0.277,0.977 0.390,0.285,0.977 0.400,0.293,0.977 0.410,0.300,0.978 0.420,0.308,0.978 0.430,0.316,0.978 0.440,0.324,0.978 0.449,0.332,0.978 0.459,0.340,0.978 0.469,0.348,0.978 0.478,0.355,0.978 0.488,0.363,0.979 0.498,0.370,0.979 0.508,0.377,0.979 0.518,0.384,0.979 0.529,0.391,0.980 0.540,0.398,0.980 0.551,0.404,0.981 0.563,0.410,0.981 0.575,0.416,0.982 0.588,0.421,0.982 0.600,0.427,0.983 0.613,0.432,0.984 0.627,0.437,0.985 0.640,0.442,0.986 0.654,0.446,0.987 0.668,0.451,0.988 0.682,0.455,0.989 0.696,0.460,0.990 0.710,0.464,0.991 0.724,0.468,0.992 0.738,0.472,0.994 0.752,0.476,0.995 0.766,0.480,0.996 0.780,0.484,0.997 0.794,0.488,0.998 0.807,0.492,0.999 0.821,0.496,1.000 0.834,0.500,1.000 0.847,0.503,1.000 0.860,0.507,1.000 0.873,0.510,1.000 0.885,0.513,1.000 0.897,0.516,1.000 0.909,0.519,1.000 0.919,0.521,1.000 0.930,0.523,0.999 0.939,0.524,0.996 0.948,0.525,0.993 0.956,0.524,0.989 0.964,0.524,0.984 0.970,0.522,0.978 0.976,0.520,0.971 0.980,0.517,0.963 0.984,0.514,0.954 0.987,0.509,0.945 0.990,0.504,0.934 0.991,0.499,0.923 0.992,0.492,0.911 0.992,0.486,0.899 0.992,0.478,0.886 0.992,0.471,0.872 0.991,0.463,0.858 0.989,0.455,0.844 0.988,0.446,0.830 0.986,0.438,0.815 0.984,0.429,0.800 0.981,0.420,0.786 0.979,0.411,0.771 0.976,0.402,0.756 0.973,0.393,0.741 0.971,0.384,0.726 0.968,0.375,0.711 0.964,0.365,0.696 0.961,0.356,0.682 0.958,0.347,0.667 0.954,0.337,0.652 0.951,0.328,0.637 0.947,0.318,0.623 0.943,0.309,0.608 0.938,0.300,0.593 0.934,0.291,0.578 0.929,0.282,0.564 0.924,0.273,0.549 0.919,0.264,0.534 0.914,0.256,0.519 0.908,0.247,0.504 0.903,0.239,0.489 0.897,0.231,0.474 0.890,0.223,0.459 0.884,0.215,0.444 0.877,0.207,0.429 0.871,0.200,0.414 0.864,0.193,0.399 0.857,0.185,0.384 0.850,0.178,0.369 0.842,0.171,0.354 0.835,0.164,0.339 0.828,0.157,0.324 0.820,0.150,0.309 0.812,0.143,0.295 0.805,0.136,0.280 0.797,0.129,0.265 0.790,0.122,0.251 0.782,0.116,0.236 0.775,0.110,0.222 0.767,0.104,0.208 0.760,0.099,0.194 0.753,0.095,0.180 0.747,0.091,0.167 0.740,0.089,0.154 0.734,0.088,0.142 0.729,0.089,0.130 0.724,0.091,0.118 0.720,0.095,0.107 0.716,0.100,0.096 0.713,0.106,0.086 0.711,0.113,0.077 0.710,0.121,0.068 0.709,0.130,0.060 0.709,0.139,0.052 0.710,0.149,0.045 0.711,0.159,0.039 0.713,0.169,0.033 0.715,0.180,0.029 0.717,0.191,0.025 0.720,0.201,0.023 0.723,0.212,0.021 0.727,0.222,0.019 0.730,0.233,0.018 0.734,0.243,0.017 0.738,0.253,0.017 0.742,0.263,0.016 0.746,0.273,0.016 0.750,0.283,0.016 0.754,0.293,0.016 0.758,0.303,0.016 0.762,0.312,0.017 0.765,0.322,0.017 0.769,0.331,0.017 0.773,0.341,0.018 0.777,0.350,0.018 0.780,0.359,0.018 0.784,0.369,0.018 0.787,0.378,0.019 0.791,0.387,0.019 0.794,0.396,0.019 0.797,0.406,0.019 0.800,0.415,0.019 0.803,0.424,0.019 0.806,0.433,0.019 0.809,0.443,0.019 0.811,0.452,0.019 0.814,0.461,0.019 0.816,0.471,0.018 0.819,0.480,0.018 0.821,0.489,0.018 0.823,0.498,0.017 0.825,0.508,0.017 0.827,0.517,0.017 0.829,0.526,0.016 0.831,0.535,0.016 0.833,0.544,0.016 0.834,0.554,0.016 0.836,0.563,0.016 0.837,0.572,0.017 0.839,0.580,0.018 0.840,0.589,0.020 0.841,0.598,0.023 0.841,0.606,0.027 0.842,0.614,0.032 0.842,0.622,0.040 0.841,0.629,0.049 0.840,0.636,0.059 0.839,0.643,0.070 0.836,0.649,0.083 0.834,0.654,0.096 0.830,0.658,0.109 0.825,0.662,0.123 0.820,0.665,0.138 0.814,0.667,0.153 0.807,0.669,0.169 0.799,0.669,0.184 0.791,0.669,0.200 0.782,0.668,0.215 0.772,0.666,0.231 0.761,0.664,0.246 0.750,0.661,0.261 0.739,0.658,0.276 0.727,0.655,0.291 0.714,0.651,0.306 0.701,0.647,0.320 0.688,0.642,0.334 0.675,0.638,0.348 0.661,0.633,0.361 0.647,0.628,0.375 0.632,0.624,0.388 0.618,0.619,0.401 0.602,0.614,0.413 0.587,0.609,0.426 0.571,0.604,0.438 0.555,0.599,0.450 0.539,0.594,0.463 0.522,0.590,0.475 0.504,0.585,0.486 0.486,0.580,0.498 0.468,0.575,0.510 0.449,0.570,0.522 0.430,0.565,0.533 0.410,0.560,0.545 0.390,0.555,0.556 0.369,0.549,0.568 0.348,0.544,0.579 0.327,0.538,0.591 0.306,0.532,0.603 0.284,0.526,0.614 0.263,0.519,0.626 0.242,0.513,0.638 0.223,0.506,0.649 0.204,0.498,0.661 0.186,0.490,0.673 0.171,0.482,0.685 0.157,0.473,0.697 0.146,0.464,0.709 0.139,0.454,0.722 0.134,0.445,0.734 0.132,0.434,0.746 0.133,0.424,0.759 0.135,0.413,0.771 0.139,0.401,0.783 0.144,0.390,0.796 0.150,0.378,0.808 0.156,0.366,0.820 0.162,0.354,0.833 0.169,0.342,0.845 0.175,0.330,0.856 0.181,0.318,0.868 0.187,0.306,0.879 0.194,0.295,0.889 0.201,0.285,0.900 0.208,0.275,0.909 0.216,0.266,0.918 0.225,0.258,0.927 0.234,0.251,0.934 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C2.csv000066400000000000000000000110001421045507400234760ustar00rootroot000000000000000.938,0.334,0.948 0.944,0.343,0.942 0.949,0.353,0.936 0.954,0.363,0.929 0.959,0.374,0.922 0.963,0.386,0.914 0.966,0.397,0.906 0.969,0.409,0.897 0.972,0.421,0.888 0.975,0.433,0.879 0.977,0.446,0.869 0.978,0.458,0.859 0.980,0.470,0.849 0.981,0.482,0.839 0.983,0.494,0.829 0.984,0.506,0.819 0.985,0.518,0.808 0.985,0.530,0.798 0.986,0.541,0.787 0.986,0.553,0.776 0.987,0.564,0.766 0.987,0.575,0.755 0.987,0.586,0.744 0.987,0.597,0.733 0.987,0.608,0.722 0.987,0.618,0.712 0.987,0.629,0.701 0.987,0.639,0.690 0.987,0.649,0.679 0.987,0.659,0.668 0.987,0.670,0.657 0.986,0.679,0.645 0.986,0.689,0.634 0.986,0.699,0.623 0.986,0.708,0.612 0.986,0.718,0.601 0.986,0.727,0.589 0.986,0.736,0.578 0.987,0.745,0.566 0.987,0.754,0.555 0.987,0.763,0.543 0.987,0.772,0.531 0.988,0.781,0.519 0.988,0.789,0.507 0.988,0.798,0.495 0.988,0.807,0.483 0.989,0.815,0.470 0.989,0.824,0.458 0.989,0.832,0.445 0.989,0.840,0.432 0.988,0.849,0.418 0.988,0.857,0.404 0.987,0.865,0.390 0.986,0.873,0.376 0.985,0.880,0.362 0.983,0.888,0.347 0.981,0.895,0.332 0.978,0.901,0.316 0.975,0.907,0.301 0.972,0.913,0.285 0.967,0.918,0.270 0.962,0.923,0.254 0.957,0.927,0.239 0.951,0.930,0.224 0.944,0.932,0.209 0.936,0.934,0.195 0.928,0.935,0.182 0.920,0.935,0.169 0.910,0.935,0.157 0.901,0.934,0.146 0.891,0.933,0.136 0.881,0.931,0.127 0.870,0.928,0.120 0.859,0.925,0.113 0.848,0.922,0.107 0.837,0.919,0.103 0.826,0.915,0.099 0.815,0.911,0.095 0.803,0.907,0.092 0.792,0.903,0.090 0.780,0.899,0.088 0.769,0.895,0.086 0.757,0.891,0.084 0.746,0.887,0.082 0.734,0.882,0.081 0.722,0.878,0.079 0.711,0.874,0.078 0.699,0.869,0.076 0.687,0.865,0.075 0.675,0.861,0.074 0.663,0.856,0.072 0.652,0.852,0.071 0.640,0.848,0.070 0.628,0.843,0.068 0.616,0.839,0.067 0.604,0.835,0.066 0.591,0.830,0.064 0.579,0.826,0.063 0.567,0.822,0.062 0.554,0.817,0.060 0.542,0.813,0.059 0.529,0.808,0.058 0.517,0.804,0.057 0.504,0.800,0.055 0.491,0.795,0.054 0.478,0.791,0.053 0.465,0.786,0.051 0.452,0.782,0.050 0.438,0.777,0.049 0.424,0.773,0.048 0.411,0.768,0.047 0.397,0.764,0.047 0.382,0.759,0.046 0.368,0.755,0.047 0.354,0.750,0.047 0.339,0.745,0.049 0.324,0.741,0.051 0.310,0.736,0.054 0.295,0.731,0.058 0.281,0.726,0.064 0.267,0.721,0.070 0.254,0.716,0.078 0.241,0.711,0.087 0.230,0.706,0.097 0.220,0.700,0.107 0.211,0.695,0.119 0.204,0.689,0.131 0.199,0.683,0.144 0.196,0.677,0.158 0.195,0.671,0.172 0.196,0.664,0.186 0.198,0.658,0.200 0.202,0.651,0.215 0.206,0.644,0.230 0.212,0.638,0.245 0.218,0.631,0.261 0.224,0.623,0.276 0.230,0.616,0.291 0.236,0.609,0.306 0.242,0.602,0.321 0.247,0.595,0.335 0.252,0.587,0.350 0.256,0.580,0.365 0.260,0.572,0.379 0.263,0.565,0.393 0.266,0.558,0.407 0.268,0.550,0.421 0.270,0.543,0.435 0.271,0.535,0.449 0.272,0.528,0.462 0.272,0.521,0.476 0.271,0.513,0.489 0.270,0.506,0.503 0.268,0.498,0.516 0.266,0.491,0.529 0.263,0.484,0.542 0.260,0.476,0.555 0.256,0.469,0.568 0.252,0.461,0.581 0.247,0.454,0.594 0.242,0.446,0.606 0.236,0.438,0.619 0.230,0.431,0.631 0.224,0.423,0.644 0.218,0.415,0.656 0.212,0.407,0.667 0.206,0.399,0.679 0.199,0.391,0.691 0.194,0.382,0.702 0.188,0.374,0.713 0.183,0.365,0.724 0.178,0.356,0.735 0.174,0.347,0.745 0.170,0.337,0.756 0.166,0.327,0.766 0.163,0.317,0.776 0.160,0.307,0.786 0.157,0.297,0.796 0.155,0.286,0.805 0.152,0.275,0.815 0.150,0.264,0.824 0.147,0.253,0.833 0.146,0.241,0.843 0.144,0.230,0.851 0.143,0.218,0.860 0.143,0.206,0.869 0.144,0.195,0.877 0.146,0.183,0.885 0.150,0.172,0.892 0.155,0.161,0.900 0.162,0.151,0.906 0.171,0.141,0.913 0.181,0.132,0.919 0.192,0.124,0.924 0.204,0.118,0.929 0.217,0.112,0.933 0.230,0.108,0.937 0.244,0.105,0.941 0.258,0.104,0.944 0.272,0.104,0.947 0.287,0.106,0.950 0.301,0.108,0.952 0.314,0.112,0.954 0.328,0.116,0.956 0.341,0.120,0.958 0.354,0.125,0.960 0.367,0.131,0.961 0.379,0.136,0.963 0.392,0.142,0.964 0.404,0.148,0.966 0.415,0.154,0.967 0.427,0.159,0.969 0.438,0.165,0.970 0.449,0.171,0.971 0.460,0.176,0.973 0.471,0.182,0.974 0.482,0.188,0.975 0.493,0.193,0.976 0.504,0.198,0.978 0.514,0.203,0.979 0.525,0.208,0.980 0.536,0.213,0.981 0.548,0.217,0.982 0.559,0.221,0.982 0.571,0.225,0.983 0.582,0.229,0.984 0.594,0.233,0.984 0.606,0.236,0.985 0.619,0.239,0.985 0.631,0.242,0.985 0.644,0.245,0.985 0.657,0.247,0.985 0.669,0.250,0.985 0.682,0.252,0.985 0.695,0.254,0.985 0.708,0.256,0.985 0.721,0.258,0.984 0.734,0.260,0.984 0.747,0.261,0.984 0.760,0.263,0.983 0.773,0.265,0.983 0.785,0.267,0.982 0.798,0.269,0.982 0.810,0.271,0.981 0.822,0.273,0.980 0.834,0.275,0.979 0.846,0.278,0.978 0.857,0.281,0.977 0.868,0.284,0.975 0.878,0.288,0.973 0.889,0.293,0.971 0.898,0.298,0.968 0.907,0.303,0.965 0.916,0.310,0.962 0.924,0.317,0.958 0.931,0.325,0.953 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C2s.csv000066400000000000000000000110001421045507400236610ustar00rootroot000000000000000.181,0.132,0.919 0.192,0.124,0.924 0.204,0.118,0.929 0.217,0.112,0.933 0.230,0.108,0.937 0.244,0.105,0.941 0.258,0.104,0.944 0.272,0.104,0.947 0.287,0.106,0.950 0.301,0.108,0.952 0.314,0.112,0.954 0.328,0.116,0.956 0.341,0.120,0.958 0.354,0.125,0.960 0.367,0.131,0.961 0.379,0.136,0.963 0.392,0.142,0.964 0.404,0.148,0.966 0.415,0.154,0.967 0.427,0.159,0.969 0.438,0.165,0.970 0.449,0.171,0.971 0.460,0.176,0.973 0.471,0.182,0.974 0.482,0.188,0.975 0.493,0.193,0.976 0.504,0.198,0.978 0.514,0.203,0.979 0.525,0.208,0.980 0.536,0.213,0.981 0.548,0.217,0.982 0.559,0.221,0.982 0.571,0.225,0.983 0.582,0.229,0.984 0.594,0.233,0.984 0.606,0.236,0.985 0.619,0.239,0.985 0.631,0.242,0.985 0.644,0.245,0.985 0.657,0.247,0.985 0.669,0.250,0.985 0.682,0.252,0.985 0.695,0.254,0.985 0.708,0.256,0.985 0.721,0.258,0.984 0.734,0.260,0.984 0.747,0.261,0.984 0.760,0.263,0.983 0.773,0.265,0.983 0.785,0.267,0.982 0.798,0.269,0.982 0.810,0.271,0.981 0.822,0.273,0.980 0.834,0.275,0.979 0.846,0.278,0.978 0.857,0.281,0.977 0.868,0.284,0.975 0.878,0.288,0.973 0.889,0.293,0.971 0.898,0.298,0.968 0.907,0.303,0.965 0.916,0.310,0.962 0.924,0.317,0.958 0.931,0.325,0.953 0.938,0.334,0.948 0.944,0.343,0.942 0.949,0.353,0.936 0.954,0.363,0.929 0.959,0.374,0.922 0.963,0.386,0.914 0.966,0.397,0.906 0.969,0.409,0.897 0.972,0.421,0.888 0.975,0.433,0.879 0.977,0.446,0.869 0.978,0.458,0.859 0.980,0.470,0.849 0.981,0.482,0.839 0.983,0.494,0.829 0.984,0.506,0.819 0.985,0.518,0.808 0.985,0.530,0.798 0.986,0.541,0.787 0.986,0.553,0.776 0.987,0.564,0.766 0.987,0.575,0.755 0.987,0.586,0.744 0.987,0.597,0.733 0.987,0.608,0.722 0.987,0.618,0.712 0.987,0.629,0.701 0.987,0.639,0.690 0.987,0.649,0.679 0.987,0.659,0.668 0.987,0.670,0.657 0.986,0.679,0.645 0.986,0.689,0.634 0.986,0.699,0.623 0.986,0.708,0.612 0.986,0.718,0.601 0.986,0.727,0.589 0.986,0.736,0.578 0.987,0.745,0.566 0.987,0.754,0.555 0.987,0.763,0.543 0.987,0.772,0.531 0.988,0.781,0.519 0.988,0.789,0.507 0.988,0.798,0.495 0.988,0.807,0.483 0.989,0.815,0.470 0.989,0.824,0.458 0.989,0.832,0.445 0.989,0.840,0.432 0.988,0.849,0.418 0.988,0.857,0.404 0.987,0.865,0.390 0.986,0.873,0.376 0.985,0.880,0.362 0.983,0.888,0.347 0.981,0.895,0.332 0.978,0.901,0.316 0.975,0.907,0.301 0.972,0.913,0.285 0.967,0.918,0.270 0.962,0.923,0.254 0.957,0.927,0.239 0.951,0.930,0.224 0.944,0.932,0.209 0.936,0.934,0.195 0.928,0.935,0.182 0.920,0.935,0.169 0.910,0.935,0.157 0.901,0.934,0.146 0.891,0.933,0.136 0.881,0.931,0.127 0.870,0.928,0.120 0.859,0.925,0.113 0.848,0.922,0.107 0.837,0.919,0.103 0.826,0.915,0.099 0.815,0.911,0.095 0.803,0.907,0.092 0.792,0.903,0.090 0.780,0.899,0.088 0.769,0.895,0.086 0.757,0.891,0.084 0.746,0.887,0.082 0.734,0.882,0.081 0.722,0.878,0.079 0.711,0.874,0.078 0.699,0.869,0.076 0.687,0.865,0.075 0.675,0.861,0.074 0.663,0.856,0.072 0.652,0.852,0.071 0.640,0.848,0.070 0.628,0.843,0.068 0.616,0.839,0.067 0.604,0.835,0.066 0.591,0.830,0.064 0.579,0.826,0.063 0.567,0.822,0.062 0.554,0.817,0.060 0.542,0.813,0.059 0.529,0.808,0.058 0.517,0.804,0.057 0.504,0.800,0.055 0.491,0.795,0.054 0.478,0.791,0.053 0.465,0.786,0.051 0.452,0.782,0.050 0.438,0.777,0.049 0.424,0.773,0.048 0.411,0.768,0.047 0.397,0.764,0.047 0.382,0.759,0.046 0.368,0.755,0.047 0.354,0.750,0.047 0.339,0.745,0.049 0.324,0.741,0.051 0.310,0.736,0.054 0.295,0.731,0.058 0.281,0.726,0.064 0.267,0.721,0.070 0.254,0.716,0.078 0.241,0.711,0.087 0.230,0.706,0.097 0.220,0.700,0.107 0.211,0.695,0.119 0.204,0.689,0.131 0.199,0.683,0.144 0.196,0.677,0.158 0.195,0.671,0.172 0.196,0.664,0.186 0.198,0.658,0.200 0.202,0.651,0.215 0.206,0.644,0.230 0.212,0.638,0.245 0.218,0.631,0.261 0.224,0.623,0.276 0.230,0.616,0.291 0.236,0.609,0.306 0.242,0.602,0.321 0.247,0.595,0.335 0.252,0.587,0.350 0.256,0.580,0.365 0.260,0.572,0.379 0.263,0.565,0.393 0.266,0.558,0.407 0.268,0.550,0.421 0.270,0.543,0.435 0.271,0.535,0.449 0.272,0.528,0.462 0.272,0.521,0.476 0.271,0.513,0.489 0.270,0.506,0.503 0.268,0.498,0.516 0.266,0.491,0.529 0.263,0.484,0.542 0.260,0.476,0.555 0.256,0.469,0.568 0.252,0.461,0.581 0.247,0.454,0.594 0.242,0.446,0.606 0.236,0.438,0.619 0.230,0.431,0.631 0.224,0.423,0.644 0.218,0.415,0.656 0.212,0.407,0.667 0.206,0.399,0.679 0.199,0.391,0.691 0.194,0.382,0.702 0.188,0.374,0.713 0.183,0.365,0.724 0.178,0.356,0.735 0.174,0.347,0.745 0.170,0.337,0.756 0.166,0.327,0.766 0.163,0.317,0.776 0.160,0.307,0.786 0.157,0.297,0.796 0.155,0.286,0.805 0.152,0.275,0.815 0.150,0.264,0.824 0.147,0.253,0.833 0.146,0.241,0.843 0.144,0.230,0.851 0.143,0.218,0.860 0.143,0.206,0.869 0.144,0.195,0.877 0.146,0.183,0.885 0.150,0.172,0.892 0.155,0.161,0.900 0.162,0.151,0.906 0.171,0.141,0.913 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C3.csv000066400000000000000000000110001421045507400234770ustar00rootroot000000000000000.881,0.843,0.856 0.886,0.842,0.849 0.891,0.839,0.840 0.896,0.835,0.831 0.900,0.830,0.821 0.904,0.825,0.811 0.907,0.819,0.800 0.911,0.812,0.789 0.914,0.804,0.778 0.917,0.797,0.766 0.919,0.788,0.754 0.922,0.780,0.742 0.924,0.771,0.730 0.926,0.762,0.717 0.928,0.752,0.705 0.930,0.743,0.692 0.932,0.734,0.679 0.933,0.724,0.667 0.935,0.714,0.654 0.936,0.705,0.641 0.937,0.695,0.629 0.938,0.685,0.616 0.939,0.676,0.604 0.940,0.666,0.591 0.940,0.656,0.579 0.941,0.646,0.566 0.941,0.636,0.554 0.941,0.627,0.541 0.941,0.617,0.529 0.941,0.607,0.517 0.941,0.597,0.504 0.941,0.587,0.492 0.941,0.577,0.480 0.940,0.567,0.468 0.940,0.557,0.456 0.939,0.547,0.444 0.939,0.537,0.431 0.938,0.527,0.419 0.937,0.516,0.407 0.936,0.506,0.395 0.935,0.496,0.383 0.934,0.485,0.371 0.932,0.475,0.359 0.931,0.464,0.348 0.930,0.454,0.336 0.928,0.443,0.324 0.926,0.432,0.312 0.925,0.421,0.300 0.923,0.410,0.288 0.921,0.399,0.277 0.919,0.388,0.265 0.916,0.377,0.254 0.914,0.365,0.242 0.911,0.354,0.231 0.908,0.343,0.220 0.905,0.332,0.209 0.901,0.320,0.198 0.898,0.309,0.188 0.893,0.299,0.178 0.889,0.288,0.168 0.883,0.278,0.159 0.877,0.268,0.151 0.871,0.259,0.143 0.864,0.250,0.136 0.857,0.242,0.130 0.849,0.235,0.124 0.840,0.229,0.119 0.831,0.223,0.115 0.821,0.218,0.111 0.811,0.214,0.109 0.800,0.211,0.107 0.789,0.208,0.105 0.778,0.206,0.104 0.766,0.204,0.103 0.755,0.202,0.103 0.743,0.201,0.103 0.730,0.200,0.103 0.718,0.199,0.103 0.706,0.198,0.104 0.694,0.198,0.104 0.681,0.197,0.105 0.669,0.196,0.105 0.656,0.195,0.106 0.644,0.195,0.106 0.631,0.194,0.107 0.619,0.193,0.107 0.607,0.192,0.108 0.594,0.191,0.108 0.582,0.190,0.109 0.570,0.189,0.109 0.558,0.188,0.110 0.546,0.187,0.110 0.534,0.186,0.110 0.521,0.184,0.111 0.509,0.183,0.111 0.497,0.182,0.111 0.485,0.180,0.112 0.474,0.179,0.112 0.462,0.177,0.112 0.450,0.176,0.112 0.438,0.174,0.112 0.426,0.172,0.112 0.414,0.171,0.112 0.403,0.169,0.113 0.391,0.167,0.113 0.379,0.165,0.113 0.368,0.163,0.113 0.356,0.161,0.113 0.345,0.159,0.113 0.333,0.157,0.113 0.322,0.155,0.113 0.310,0.153,0.113 0.299,0.151,0.113 0.288,0.149,0.113 0.277,0.146,0.113 0.266,0.144,0.113 0.255,0.142,0.114 0.244,0.140,0.114 0.234,0.138,0.115 0.224,0.136,0.116 0.215,0.135,0.118 0.206,0.133,0.120 0.197,0.132,0.122 0.189,0.131,0.125 0.182,0.130,0.129 0.176,0.130,0.133 0.170,0.130,0.138 0.165,0.131,0.144 0.161,0.131,0.150 0.157,0.133,0.157 0.155,0.134,0.164 0.153,0.136,0.173 0.152,0.139,0.182 0.151,0.142,0.191 0.151,0.145,0.201 0.152,0.148,0.212 0.152,0.152,0.223 0.154,0.156,0.235 0.155,0.160,0.246 0.157,0.164,0.258 0.159,0.169,0.271 0.162,0.173,0.283 0.164,0.178,0.296 0.166,0.182,0.309 0.168,0.187,0.322 0.170,0.192,0.335 0.172,0.196,0.348 0.174,0.201,0.361 0.176,0.206,0.375 0.178,0.211,0.388 0.180,0.216,0.402 0.181,0.221,0.415 0.183,0.226,0.429 0.184,0.230,0.443 0.185,0.235,0.457 0.186,0.240,0.470 0.187,0.245,0.484 0.188,0.250,0.499 0.188,0.255,0.513 0.188,0.261,0.527 0.189,0.266,0.541 0.189,0.271,0.555 0.188,0.276,0.570 0.188,0.281,0.584 0.187,0.286,0.599 0.187,0.291,0.614 0.186,0.297,0.628 0.184,0.302,0.643 0.183,0.307,0.658 0.181,0.312,0.673 0.179,0.317,0.687 0.177,0.323,0.702 0.174,0.328,0.717 0.171,0.333,0.732 0.168,0.339,0.747 0.165,0.344,0.762 0.161,0.349,0.777 0.158,0.355,0.792 0.154,0.360,0.807 0.150,0.366,0.822 0.146,0.371,0.837 0.143,0.377,0.851 0.140,0.382,0.865 0.138,0.388,0.878 0.137,0.393,0.892 0.138,0.399,0.904 0.141,0.405,0.916 0.146,0.410,0.927 0.153,0.416,0.937 0.162,0.422,0.947 0.173,0.428,0.955 0.186,0.434,0.963 0.200,0.440,0.969 0.216,0.446,0.975 0.232,0.452,0.979 0.248,0.458,0.983 0.265,0.465,0.986 0.282,0.471,0.988 0.299,0.477,0.989 0.316,0.484,0.990 0.332,0.490,0.990 0.348,0.497,0.990 0.364,0.503,0.989 0.380,0.510,0.989 0.394,0.517,0.988 0.409,0.523,0.986 0.423,0.530,0.985 0.437,0.537,0.983 0.450,0.544,0.982 0.463,0.551,0.980 0.476,0.557,0.979 0.488,0.564,0.977 0.500,0.571,0.975 0.512,0.578,0.973 0.524,0.585,0.972 0.535,0.592,0.970 0.546,0.599,0.968 0.557,0.606,0.966 0.568,0.613,0.964 0.578,0.620,0.963 0.589,0.627,0.961 0.599,0.635,0.959 0.609,0.642,0.957 0.619,0.649,0.955 0.629,0.656,0.953 0.638,0.663,0.951 0.648,0.671,0.950 0.657,0.678,0.948 0.666,0.685,0.946 0.675,0.692,0.944 0.685,0.700,0.942 0.693,0.707,0.940 0.702,0.714,0.938 0.711,0.722,0.936 0.720,0.729,0.934 0.728,0.736,0.932 0.737,0.744,0.930 0.745,0.751,0.928 0.754,0.759,0.925 0.762,0.766,0.923 0.770,0.773,0.921 0.778,0.780,0.919 0.786,0.788,0.917 0.794,0.795,0.914 0.802,0.801,0.912 0.810,0.808,0.909 0.817,0.814,0.906 0.825,0.820,0.903 0.832,0.826,0.900 0.839,0.831,0.896 0.846,0.835,0.892 0.852,0.838,0.887 0.859,0.841,0.882 0.865,0.843,0.877 0.871,0.844,0.870 0.876,0.844,0.864 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C3s.csv000066400000000000000000000110001421045507400236620ustar00rootroot000000000000000.200,0.440,0.969 0.216,0.446,0.975 0.232,0.452,0.979 0.248,0.458,0.983 0.265,0.465,0.986 0.282,0.471,0.988 0.299,0.477,0.989 0.316,0.484,0.990 0.332,0.490,0.990 0.348,0.497,0.990 0.364,0.503,0.989 0.380,0.510,0.989 0.394,0.517,0.988 0.409,0.523,0.986 0.423,0.530,0.985 0.437,0.537,0.983 0.450,0.544,0.982 0.463,0.551,0.980 0.476,0.557,0.979 0.488,0.564,0.977 0.500,0.571,0.975 0.512,0.578,0.973 0.524,0.585,0.972 0.535,0.592,0.970 0.546,0.599,0.968 0.557,0.606,0.966 0.568,0.613,0.964 0.578,0.620,0.963 0.589,0.627,0.961 0.599,0.635,0.959 0.609,0.642,0.957 0.619,0.649,0.955 0.629,0.656,0.953 0.638,0.663,0.951 0.648,0.671,0.950 0.657,0.678,0.948 0.666,0.685,0.946 0.675,0.692,0.944 0.685,0.700,0.942 0.693,0.707,0.940 0.702,0.714,0.938 0.711,0.722,0.936 0.720,0.729,0.934 0.728,0.736,0.932 0.737,0.744,0.930 0.745,0.751,0.928 0.754,0.759,0.925 0.762,0.766,0.923 0.770,0.773,0.921 0.778,0.780,0.919 0.786,0.788,0.917 0.794,0.795,0.914 0.802,0.801,0.912 0.810,0.808,0.909 0.817,0.814,0.906 0.825,0.820,0.903 0.832,0.826,0.900 0.839,0.831,0.896 0.846,0.835,0.892 0.852,0.838,0.887 0.859,0.841,0.882 0.865,0.843,0.877 0.871,0.844,0.870 0.876,0.844,0.864 0.881,0.843,0.856 0.886,0.842,0.849 0.891,0.839,0.840 0.896,0.835,0.831 0.900,0.830,0.821 0.904,0.825,0.811 0.907,0.819,0.800 0.911,0.812,0.789 0.914,0.804,0.778 0.917,0.797,0.766 0.919,0.788,0.754 0.922,0.780,0.742 0.924,0.771,0.730 0.926,0.762,0.717 0.928,0.752,0.705 0.930,0.743,0.692 0.932,0.734,0.679 0.933,0.724,0.667 0.935,0.714,0.654 0.936,0.705,0.641 0.937,0.695,0.629 0.938,0.685,0.616 0.939,0.676,0.604 0.940,0.666,0.591 0.940,0.656,0.579 0.941,0.646,0.566 0.941,0.636,0.554 0.941,0.627,0.541 0.941,0.617,0.529 0.941,0.607,0.517 0.941,0.597,0.504 0.941,0.587,0.492 0.941,0.577,0.480 0.940,0.567,0.468 0.940,0.557,0.456 0.939,0.547,0.444 0.939,0.537,0.431 0.938,0.527,0.419 0.937,0.516,0.407 0.936,0.506,0.395 0.935,0.496,0.383 0.934,0.485,0.371 0.932,0.475,0.359 0.931,0.464,0.348 0.930,0.454,0.336 0.928,0.443,0.324 0.926,0.432,0.312 0.925,0.421,0.300 0.923,0.410,0.288 0.921,0.399,0.277 0.919,0.388,0.265 0.916,0.377,0.254 0.914,0.365,0.242 0.911,0.354,0.231 0.908,0.343,0.220 0.905,0.332,0.209 0.901,0.320,0.198 0.898,0.309,0.188 0.893,0.299,0.178 0.889,0.288,0.168 0.883,0.278,0.159 0.877,0.268,0.151 0.871,0.259,0.143 0.864,0.250,0.136 0.857,0.242,0.130 0.849,0.235,0.124 0.840,0.229,0.119 0.831,0.223,0.115 0.821,0.218,0.111 0.811,0.214,0.109 0.800,0.211,0.107 0.789,0.208,0.105 0.778,0.206,0.104 0.766,0.204,0.103 0.755,0.202,0.103 0.743,0.201,0.103 0.730,0.200,0.103 0.718,0.199,0.103 0.706,0.198,0.104 0.694,0.198,0.104 0.681,0.197,0.105 0.669,0.196,0.105 0.656,0.195,0.106 0.644,0.195,0.106 0.631,0.194,0.107 0.619,0.193,0.107 0.607,0.192,0.108 0.594,0.191,0.108 0.582,0.190,0.109 0.570,0.189,0.109 0.558,0.188,0.110 0.546,0.187,0.110 0.534,0.186,0.110 0.521,0.184,0.111 0.509,0.183,0.111 0.497,0.182,0.111 0.485,0.180,0.112 0.474,0.179,0.112 0.462,0.177,0.112 0.450,0.176,0.112 0.438,0.174,0.112 0.426,0.172,0.112 0.414,0.171,0.112 0.403,0.169,0.113 0.391,0.167,0.113 0.379,0.165,0.113 0.368,0.163,0.113 0.356,0.161,0.113 0.345,0.159,0.113 0.333,0.157,0.113 0.322,0.155,0.113 0.310,0.153,0.113 0.299,0.151,0.113 0.288,0.149,0.113 0.277,0.146,0.113 0.266,0.144,0.113 0.255,0.142,0.114 0.244,0.140,0.114 0.234,0.138,0.115 0.224,0.136,0.116 0.215,0.135,0.118 0.206,0.133,0.120 0.197,0.132,0.122 0.189,0.131,0.125 0.182,0.130,0.129 0.176,0.130,0.133 0.170,0.130,0.138 0.165,0.131,0.144 0.161,0.131,0.150 0.157,0.133,0.157 0.155,0.134,0.164 0.153,0.136,0.173 0.152,0.139,0.182 0.151,0.142,0.191 0.151,0.145,0.201 0.152,0.148,0.212 0.152,0.152,0.223 0.154,0.156,0.235 0.155,0.160,0.246 0.157,0.164,0.258 0.159,0.169,0.271 0.162,0.173,0.283 0.164,0.178,0.296 0.166,0.182,0.309 0.168,0.187,0.322 0.170,0.192,0.335 0.172,0.196,0.348 0.174,0.201,0.361 0.176,0.206,0.375 0.178,0.211,0.388 0.180,0.216,0.402 0.181,0.221,0.415 0.183,0.226,0.429 0.184,0.230,0.443 0.185,0.235,0.457 0.186,0.240,0.470 0.187,0.245,0.484 0.188,0.250,0.499 0.188,0.255,0.513 0.188,0.261,0.527 0.189,0.266,0.541 0.189,0.271,0.555 0.188,0.276,0.570 0.188,0.281,0.584 0.187,0.286,0.599 0.187,0.291,0.614 0.186,0.297,0.628 0.184,0.302,0.643 0.183,0.307,0.658 0.181,0.312,0.673 0.179,0.317,0.687 0.177,0.323,0.702 0.174,0.328,0.717 0.171,0.333,0.732 0.168,0.339,0.747 0.165,0.344,0.762 0.161,0.349,0.777 0.158,0.355,0.792 0.154,0.360,0.807 0.150,0.366,0.822 0.146,0.371,0.837 0.143,0.377,0.851 0.140,0.382,0.865 0.138,0.388,0.878 0.137,0.393,0.892 0.138,0.399,0.904 0.141,0.405,0.916 0.146,0.410,0.927 0.153,0.416,0.937 0.162,0.422,0.947 0.173,0.428,0.955 0.186,0.434,0.963 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C4.csv000066400000000000000000000110001421045507400235000ustar00rootroot000000000000000.873,0.836,0.849 0.878,0.834,0.841 0.882,0.831,0.832 0.886,0.827,0.823 0.889,0.821,0.812 0.892,0.815,0.802 0.895,0.808,0.790 0.897,0.800,0.778 0.898,0.792,0.766 0.900,0.783,0.753 0.901,0.773,0.740 0.902,0.763,0.727 0.903,0.753,0.713 0.903,0.743,0.700 0.904,0.732,0.686 0.904,0.722,0.672 0.904,0.711,0.658 0.903,0.700,0.644 0.903,0.689,0.630 0.902,0.678,0.617 0.902,0.667,0.603 0.901,0.656,0.589 0.900,0.645,0.575 0.899,0.634,0.562 0.898,0.623,0.548 0.896,0.612,0.534 0.895,0.601,0.521 0.893,0.589,0.507 0.892,0.578,0.494 0.890,0.567,0.480 0.888,0.556,0.467 0.886,0.545,0.454 0.884,0.533,0.440 0.881,0.522,0.427 0.879,0.511,0.414 0.876,0.500,0.401 0.874,0.488,0.388 0.871,0.477,0.375 0.868,0.465,0.362 0.866,0.454,0.349 0.863,0.442,0.336 0.859,0.430,0.323 0.856,0.419,0.310 0.853,0.407,0.297 0.850,0.395,0.284 0.846,0.383,0.271 0.843,0.371,0.259 0.839,0.358,0.246 0.835,0.346,0.233 0.832,0.334,0.221 0.828,0.321,0.208 0.824,0.309,0.196 0.820,0.296,0.184 0.816,0.284,0.172 0.813,0.271,0.161 0.809,0.260,0.150 0.805,0.248,0.139 0.802,0.237,0.130 0.799,0.226,0.120 0.796,0.217,0.112 0.794,0.209,0.105 0.792,0.202,0.099 0.791,0.196,0.095 0.790,0.193,0.092 0.789,0.191,0.090 0.789,0.191,0.090 0.790,0.193,0.092 0.791,0.198,0.096 0.793,0.203,0.101 0.795,0.211,0.107 0.797,0.219,0.114 0.800,0.229,0.123 0.803,0.240,0.132 0.806,0.251,0.142 0.810,0.263,0.153 0.814,0.275,0.164 0.817,0.287,0.176 0.821,0.300,0.187 0.825,0.312,0.199 0.829,0.324,0.212 0.833,0.337,0.224 0.836,0.349,0.237 0.840,0.362,0.249 0.844,0.374,0.262 0.847,0.386,0.275 0.851,0.398,0.288 0.854,0.410,0.300 0.857,0.422,0.313 0.860,0.433,0.326 0.863,0.445,0.339 0.866,0.457,0.352 0.869,0.468,0.365 0.872,0.480,0.378 0.875,0.491,0.391 0.877,0.503,0.404 0.880,0.514,0.418 0.882,0.525,0.431 0.884,0.537,0.444 0.886,0.548,0.457 0.888,0.559,0.471 0.890,0.570,0.484 0.892,0.581,0.497 0.894,0.592,0.511 0.895,0.604,0.524 0.897,0.615,0.538 0.898,0.626,0.552 0.899,0.637,0.565 0.900,0.648,0.579 0.901,0.659,0.593 0.902,0.670,0.607 0.902,0.681,0.620 0.903,0.692,0.634 0.903,0.703,0.648 0.903,0.713,0.662 0.903,0.724,0.676 0.903,0.735,0.690 0.902,0.745,0.703 0.901,0.755,0.717 0.900,0.764,0.731 0.899,0.774,0.744 0.897,0.782,0.757 0.894,0.790,0.770 0.891,0.798,0.782 0.888,0.804,0.794 0.884,0.810,0.806 0.880,0.815,0.816 0.875,0.818,0.827 0.870,0.821,0.836 0.864,0.822,0.844 0.857,0.822,0.852 0.851,0.821,0.859 0.843,0.819,0.865 0.836,0.816,0.871 0.828,0.812,0.875 0.819,0.807,0.879 0.811,0.802,0.883 0.802,0.795,0.886 0.793,0.788,0.888 0.784,0.781,0.890 0.775,0.773,0.891 0.765,0.765,0.892 0.756,0.757,0.893 0.746,0.749,0.894 0.737,0.740,0.895 0.727,0.731,0.895 0.717,0.723,0.896 0.707,0.714,0.896 0.697,0.705,0.897 0.687,0.697,0.897 0.677,0.688,0.897 0.667,0.679,0.897 0.657,0.670,0.898 0.646,0.662,0.898 0.636,0.653,0.898 0.625,0.644,0.898 0.615,0.636,0.898 0.604,0.627,0.899 0.593,0.619,0.899 0.582,0.610,0.899 0.571,0.602,0.899 0.559,0.593,0.899 0.548,0.585,0.899 0.536,0.576,0.899 0.524,0.568,0.899 0.512,0.560,0.899 0.500,0.551,0.899 0.487,0.543,0.899 0.475,0.535,0.899 0.462,0.526,0.899 0.448,0.518,0.899 0.435,0.510,0.899 0.421,0.502,0.899 0.406,0.494,0.899 0.392,0.486,0.899 0.376,0.478,0.899 0.361,0.470,0.899 0.344,0.462,0.899 0.328,0.455,0.898 0.311,0.447,0.898 0.293,0.440,0.898 0.274,0.433,0.898 0.255,0.426,0.898 0.236,0.420,0.898 0.216,0.414,0.898 0.196,0.409,0.897 0.177,0.404,0.897 0.157,0.400,0.897 0.139,0.396,0.897 0.123,0.393,0.897 0.110,0.391,0.897 0.101,0.390,0.897 0.099,0.390,0.897 0.102,0.390,0.897 0.112,0.392,0.897 0.126,0.394,0.897 0.143,0.397,0.897 0.161,0.400,0.897 0.181,0.405,0.897 0.201,0.410,0.897 0.221,0.415,0.898 0.240,0.421,0.898 0.260,0.428,0.898 0.278,0.435,0.898 0.297,0.442,0.898 0.314,0.449,0.898 0.331,0.456,0.898 0.348,0.464,0.899 0.364,0.472,0.899 0.380,0.480,0.899 0.395,0.488,0.899 0.409,0.496,0.899 0.424,0.504,0.899 0.438,0.512,0.899 0.451,0.520,0.899 0.465,0.528,0.899 0.477,0.536,0.899 0.490,0.545,0.899 0.503,0.553,0.899 0.515,0.561,0.899 0.527,0.570,0.899 0.539,0.578,0.899 0.550,0.586,0.899 0.562,0.595,0.899 0.573,0.603,0.899 0.584,0.612,0.899 0.595,0.620,0.899 0.606,0.629,0.899 0.617,0.638,0.898 0.628,0.646,0.898 0.638,0.655,0.898 0.648,0.664,0.898 0.659,0.672,0.898 0.669,0.681,0.897 0.679,0.690,0.897 0.689,0.698,0.897 0.699,0.707,0.897 0.709,0.716,0.896 0.719,0.725,0.896 0.729,0.734,0.896 0.739,0.742,0.895 0.748,0.751,0.895 0.758,0.760,0.894 0.767,0.768,0.894 0.776,0.777,0.893 0.785,0.785,0.892 0.794,0.793,0.891 0.803,0.800,0.889 0.811,0.807,0.888 0.820,0.814,0.886 0.828,0.820,0.883 0.835,0.825,0.880 0.843,0.829,0.877 0.850,0.833,0.873 0.856,0.835,0.868 0.862,0.837,0.862 0.868,0.837,0.856 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C4s.csv000066400000000000000000000110001421045507400236630ustar00rootroot000000000000000.102,0.390,0.897 0.112,0.392,0.897 0.126,0.394,0.897 0.143,0.397,0.897 0.161,0.400,0.897 0.181,0.405,0.897 0.201,0.410,0.897 0.221,0.415,0.898 0.240,0.421,0.898 0.260,0.428,0.898 0.278,0.435,0.898 0.297,0.442,0.898 0.314,0.449,0.898 0.331,0.456,0.898 0.348,0.464,0.899 0.364,0.472,0.899 0.380,0.480,0.899 0.395,0.488,0.899 0.409,0.496,0.899 0.424,0.504,0.899 0.438,0.512,0.899 0.451,0.520,0.899 0.465,0.528,0.899 0.477,0.536,0.899 0.490,0.545,0.899 0.503,0.553,0.899 0.515,0.561,0.899 0.527,0.570,0.899 0.539,0.578,0.899 0.550,0.586,0.899 0.562,0.595,0.899 0.573,0.603,0.899 0.584,0.612,0.899 0.595,0.620,0.899 0.606,0.629,0.899 0.617,0.638,0.898 0.628,0.646,0.898 0.638,0.655,0.898 0.648,0.664,0.898 0.659,0.672,0.898 0.669,0.681,0.897 0.679,0.690,0.897 0.689,0.698,0.897 0.699,0.707,0.897 0.709,0.716,0.896 0.719,0.725,0.896 0.729,0.734,0.896 0.739,0.742,0.895 0.748,0.751,0.895 0.758,0.760,0.894 0.767,0.768,0.894 0.776,0.777,0.893 0.785,0.785,0.892 0.794,0.793,0.891 0.803,0.800,0.889 0.811,0.807,0.888 0.820,0.814,0.886 0.828,0.820,0.883 0.835,0.825,0.880 0.843,0.829,0.877 0.850,0.833,0.873 0.856,0.835,0.868 0.862,0.837,0.862 0.868,0.837,0.856 0.873,0.836,0.849 0.878,0.834,0.841 0.882,0.831,0.832 0.886,0.827,0.823 0.889,0.821,0.812 0.892,0.815,0.802 0.895,0.808,0.790 0.897,0.800,0.778 0.898,0.792,0.766 0.900,0.783,0.753 0.901,0.773,0.740 0.902,0.763,0.727 0.903,0.753,0.713 0.903,0.743,0.700 0.904,0.732,0.686 0.904,0.722,0.672 0.904,0.711,0.658 0.903,0.700,0.644 0.903,0.689,0.630 0.902,0.678,0.617 0.902,0.667,0.603 0.901,0.656,0.589 0.900,0.645,0.575 0.899,0.634,0.562 0.898,0.623,0.548 0.896,0.612,0.534 0.895,0.601,0.521 0.893,0.589,0.507 0.892,0.578,0.494 0.890,0.567,0.480 0.888,0.556,0.467 0.886,0.545,0.454 0.884,0.533,0.440 0.881,0.522,0.427 0.879,0.511,0.414 0.876,0.500,0.401 0.874,0.488,0.388 0.871,0.477,0.375 0.868,0.465,0.362 0.866,0.454,0.349 0.863,0.442,0.336 0.859,0.430,0.323 0.856,0.419,0.310 0.853,0.407,0.297 0.850,0.395,0.284 0.846,0.383,0.271 0.843,0.371,0.259 0.839,0.358,0.246 0.835,0.346,0.233 0.832,0.334,0.221 0.828,0.321,0.208 0.824,0.309,0.196 0.820,0.296,0.184 0.816,0.284,0.172 0.813,0.271,0.161 0.809,0.260,0.150 0.805,0.248,0.139 0.802,0.237,0.130 0.799,0.226,0.120 0.796,0.217,0.112 0.794,0.209,0.105 0.792,0.202,0.099 0.791,0.196,0.095 0.790,0.193,0.092 0.789,0.191,0.090 0.789,0.191,0.090 0.790,0.193,0.092 0.791,0.198,0.096 0.793,0.203,0.101 0.795,0.211,0.107 0.797,0.219,0.114 0.800,0.229,0.123 0.803,0.240,0.132 0.806,0.251,0.142 0.810,0.263,0.153 0.814,0.275,0.164 0.817,0.287,0.176 0.821,0.300,0.187 0.825,0.312,0.199 0.829,0.324,0.212 0.833,0.337,0.224 0.836,0.349,0.237 0.840,0.362,0.249 0.844,0.374,0.262 0.847,0.386,0.275 0.851,0.398,0.288 0.854,0.410,0.300 0.857,0.422,0.313 0.860,0.433,0.326 0.863,0.445,0.339 0.866,0.457,0.352 0.869,0.468,0.365 0.872,0.480,0.378 0.875,0.491,0.391 0.877,0.503,0.404 0.880,0.514,0.418 0.882,0.525,0.431 0.884,0.537,0.444 0.886,0.548,0.457 0.888,0.559,0.471 0.890,0.570,0.484 0.892,0.581,0.497 0.894,0.592,0.511 0.895,0.604,0.524 0.897,0.615,0.538 0.898,0.626,0.552 0.899,0.637,0.565 0.900,0.648,0.579 0.901,0.659,0.593 0.902,0.670,0.607 0.902,0.681,0.620 0.903,0.692,0.634 0.903,0.703,0.648 0.903,0.713,0.662 0.903,0.724,0.676 0.903,0.735,0.690 0.902,0.745,0.703 0.901,0.755,0.717 0.900,0.764,0.731 0.899,0.774,0.744 0.897,0.782,0.757 0.894,0.790,0.770 0.891,0.798,0.782 0.888,0.804,0.794 0.884,0.810,0.806 0.880,0.815,0.816 0.875,0.818,0.827 0.870,0.821,0.836 0.864,0.822,0.844 0.857,0.822,0.852 0.851,0.821,0.859 0.843,0.819,0.865 0.836,0.816,0.871 0.828,0.812,0.875 0.819,0.807,0.879 0.811,0.802,0.883 0.802,0.795,0.886 0.793,0.788,0.888 0.784,0.781,0.890 0.775,0.773,0.891 0.765,0.765,0.892 0.756,0.757,0.893 0.746,0.749,0.894 0.737,0.740,0.895 0.727,0.731,0.895 0.717,0.723,0.896 0.707,0.714,0.896 0.697,0.705,0.897 0.687,0.697,0.897 0.677,0.688,0.897 0.667,0.679,0.897 0.657,0.670,0.898 0.646,0.662,0.898 0.636,0.653,0.898 0.625,0.644,0.898 0.615,0.636,0.898 0.604,0.627,0.899 0.593,0.619,0.899 0.582,0.610,0.899 0.571,0.602,0.899 0.559,0.593,0.899 0.548,0.585,0.899 0.536,0.576,0.899 0.524,0.568,0.899 0.512,0.560,0.899 0.500,0.551,0.899 0.487,0.543,0.899 0.475,0.535,0.899 0.462,0.526,0.899 0.448,0.518,0.899 0.435,0.510,0.899 0.421,0.502,0.899 0.406,0.494,0.899 0.392,0.486,0.899 0.376,0.478,0.899 0.361,0.470,0.899 0.344,0.462,0.899 0.328,0.455,0.898 0.311,0.447,0.898 0.293,0.440,0.898 0.274,0.433,0.898 0.255,0.426,0.898 0.236,0.420,0.898 0.216,0.414,0.898 0.196,0.409,0.897 0.177,0.404,0.897 0.157,0.400,0.897 0.139,0.396,0.897 0.123,0.393,0.897 0.110,0.391,0.897 0.101,0.390,0.897 0.099,0.390,0.897 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C5.csv000066400000000000000000000110001421045507400235010ustar00rootroot000000000000000.469,0.469,0.469 0.474,0.474,0.474 0.479,0.479,0.479 0.484,0.484,0.484 0.489,0.489,0.489 0.494,0.494,0.494 0.499,0.499,0.499 0.505,0.505,0.505 0.510,0.510,0.510 0.515,0.515,0.515 0.520,0.521,0.521 0.526,0.526,0.526 0.531,0.531,0.531 0.537,0.537,0.537 0.542,0.542,0.542 0.548,0.548,0.548 0.553,0.553,0.553 0.558,0.559,0.559 0.564,0.564,0.564 0.569,0.570,0.570 0.575,0.575,0.575 0.581,0.581,0.581 0.586,0.586,0.586 0.592,0.592,0.592 0.597,0.597,0.597 0.603,0.603,0.603 0.608,0.609,0.609 0.614,0.614,0.614 0.620,0.620,0.620 0.625,0.625,0.625 0.631,0.631,0.631 0.637,0.637,0.637 0.642,0.642,0.642 0.648,0.648,0.648 0.653,0.654,0.654 0.659,0.659,0.659 0.665,0.665,0.665 0.671,0.671,0.671 0.676,0.676,0.676 0.682,0.682,0.682 0.688,0.688,0.688 0.693,0.694,0.693 0.699,0.699,0.699 0.705,0.705,0.705 0.711,0.711,0.711 0.716,0.717,0.716 0.722,0.722,0.722 0.728,0.728,0.728 0.733,0.734,0.734 0.739,0.739,0.739 0.745,0.745,0.745 0.750,0.750,0.750 0.755,0.756,0.756 0.761,0.761,0.761 0.766,0.766,0.766 0.770,0.771,0.771 0.775,0.775,0.775 0.779,0.779,0.779 0.783,0.783,0.783 0.786,0.786,0.786 0.788,0.788,0.788 0.790,0.790,0.790 0.792,0.792,0.792 0.792,0.793,0.793 0.793,0.793,0.793 0.792,0.792,0.792 0.791,0.791,0.791 0.789,0.789,0.789 0.786,0.786,0.786 0.783,0.783,0.783 0.780,0.780,0.780 0.776,0.776,0.776 0.771,0.772,0.772 0.767,0.767,0.767 0.762,0.762,0.762 0.757,0.757,0.757 0.751,0.752,0.752 0.746,0.746,0.746 0.740,0.741,0.741 0.735,0.735,0.735 0.729,0.729,0.729 0.723,0.724,0.724 0.718,0.718,0.718 0.712,0.712,0.712 0.706,0.706,0.706 0.701,0.701,0.701 0.695,0.695,0.695 0.689,0.689,0.689 0.683,0.683,0.683 0.678,0.678,0.678 0.672,0.672,0.672 0.666,0.666,0.666 0.661,0.661,0.661 0.655,0.655,0.655 0.649,0.649,0.649 0.644,0.644,0.644 0.638,0.638,0.638 0.632,0.632,0.632 0.627,0.627,0.627 0.621,0.621,0.621 0.615,0.615,0.615 0.610,0.610,0.610 0.604,0.604,0.604 0.599,0.599,0.599 0.593,0.593,0.593 0.587,0.588,0.588 0.582,0.582,0.582 0.576,0.576,0.576 0.571,0.571,0.571 0.565,0.565,0.565 0.560,0.560,0.560 0.554,0.554,0.554 0.549,0.549,0.549 0.543,0.543,0.543 0.538,0.538,0.538 0.532,0.532,0.532 0.527,0.527,0.527 0.521,0.522,0.522 0.516,0.516,0.516 0.511,0.511,0.511 0.505,0.505,0.505 0.500,0.500,0.500 0.494,0.494,0.494 0.489,0.489,0.489 0.484,0.484,0.484 0.478,0.478,0.478 0.473,0.473,0.473 0.468,0.468,0.468 0.462,0.462,0.462 0.457,0.457,0.457 0.452,0.452,0.452 0.446,0.446,0.446 0.441,0.441,0.441 0.436,0.436,0.436 0.430,0.431,0.431 0.425,0.425,0.425 0.420,0.420,0.420 0.415,0.415,0.415 0.410,0.410,0.410 0.404,0.404,0.404 0.399,0.399,0.399 0.394,0.394,0.394 0.389,0.389,0.389 0.384,0.384,0.384 0.378,0.379,0.379 0.373,0.373,0.373 0.368,0.368,0.368 0.363,0.363,0.363 0.358,0.358,0.358 0.353,0.353,0.353 0.348,0.348,0.348 0.343,0.343,0.343 0.338,0.338,0.338 0.333,0.333,0.333 0.328,0.328,0.328 0.323,0.323,0.323 0.318,0.318,0.318 0.313,0.313,0.313 0.308,0.308,0.308 0.303,0.303,0.303 0.298,0.298,0.298 0.293,0.293,0.293 0.288,0.288,0.288 0.283,0.283,0.283 0.278,0.278,0.278 0.273,0.273,0.273 0.269,0.269,0.269 0.264,0.264,0.264 0.259,0.259,0.259 0.254,0.254,0.254 0.249,0.249,0.249 0.244,0.245,0.245 0.240,0.240,0.240 0.235,0.235,0.235 0.230,0.230,0.230 0.226,0.226,0.226 0.221,0.221,0.221 0.217,0.217,0.217 0.212,0.212,0.212 0.208,0.208,0.208 0.204,0.204,0.204 0.200,0.200,0.200 0.196,0.196,0.196 0.192,0.192,0.192 0.189,0.189,0.189 0.186,0.186,0.186 0.183,0.183,0.183 0.181,0.181,0.181 0.179,0.179,0.179 0.178,0.178,0.178 0.177,0.177,0.177 0.177,0.177,0.177 0.177,0.177,0.177 0.178,0.178,0.178 0.179,0.179,0.179 0.180,0.180,0.180 0.183,0.183,0.183 0.185,0.185,0.185 0.188,0.188,0.188 0.191,0.191,0.191 0.195,0.195,0.195 0.199,0.199,0.199 0.203,0.203,0.203 0.207,0.207,0.207 0.211,0.211,0.211 0.215,0.216,0.216 0.220,0.220,0.220 0.225,0.225,0.225 0.229,0.229,0.229 0.234,0.234,0.234 0.239,0.239,0.239 0.243,0.243,0.243 0.248,0.248,0.248 0.253,0.253,0.253 0.258,0.258,0.258 0.263,0.263,0.263 0.267,0.267,0.267 0.272,0.272,0.272 0.277,0.277,0.277 0.282,0.282,0.282 0.287,0.287,0.287 0.292,0.292,0.292 0.297,0.297,0.297 0.302,0.302,0.302 0.307,0.307,0.307 0.312,0.312,0.312 0.317,0.317,0.317 0.321,0.322,0.322 0.326,0.327,0.327 0.332,0.332,0.332 0.337,0.337,0.337 0.342,0.342,0.342 0.347,0.347,0.347 0.352,0.352,0.352 0.357,0.357,0.357 0.362,0.362,0.362 0.367,0.367,0.367 0.372,0.372,0.372 0.377,0.377,0.377 0.382,0.382,0.382 0.387,0.388,0.388 0.393,0.393,0.393 0.398,0.398,0.398 0.403,0.403,0.403 0.408,0.408,0.408 0.413,0.413,0.413 0.418,0.418,0.418 0.423,0.423,0.423 0.428,0.429,0.429 0.434,0.434,0.434 0.439,0.439,0.439 0.444,0.444,0.444 0.449,0.449,0.449 0.454,0.454,0.454 0.459,0.459,0.459 0.464,0.464,0.464 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C5s.csv000066400000000000000000000110001421045507400236640ustar00rootroot000000000000000.177,0.177,0.177 0.178,0.178,0.178 0.179,0.179,0.179 0.180,0.180,0.180 0.183,0.183,0.183 0.185,0.185,0.185 0.188,0.188,0.188 0.191,0.191,0.191 0.195,0.195,0.195 0.199,0.199,0.199 0.203,0.203,0.203 0.207,0.207,0.207 0.211,0.211,0.211 0.215,0.216,0.216 0.220,0.220,0.220 0.225,0.225,0.225 0.229,0.229,0.229 0.234,0.234,0.234 0.239,0.239,0.239 0.243,0.243,0.243 0.248,0.248,0.248 0.253,0.253,0.253 0.258,0.258,0.258 0.263,0.263,0.263 0.267,0.267,0.267 0.272,0.272,0.272 0.277,0.277,0.277 0.282,0.282,0.282 0.287,0.287,0.287 0.292,0.292,0.292 0.297,0.297,0.297 0.302,0.302,0.302 0.307,0.307,0.307 0.312,0.312,0.312 0.317,0.317,0.317 0.321,0.322,0.322 0.326,0.327,0.327 0.332,0.332,0.332 0.337,0.337,0.337 0.342,0.342,0.342 0.347,0.347,0.347 0.352,0.352,0.352 0.357,0.357,0.357 0.362,0.362,0.362 0.367,0.367,0.367 0.372,0.372,0.372 0.377,0.377,0.377 0.382,0.382,0.382 0.387,0.388,0.388 0.393,0.393,0.393 0.398,0.398,0.398 0.403,0.403,0.403 0.408,0.408,0.408 0.413,0.413,0.413 0.418,0.418,0.418 0.423,0.423,0.423 0.428,0.429,0.429 0.434,0.434,0.434 0.439,0.439,0.439 0.444,0.444,0.444 0.449,0.449,0.449 0.454,0.454,0.454 0.459,0.459,0.459 0.464,0.464,0.464 0.469,0.469,0.469 0.474,0.474,0.474 0.479,0.479,0.479 0.484,0.484,0.484 0.489,0.489,0.489 0.494,0.494,0.494 0.499,0.499,0.499 0.505,0.505,0.505 0.510,0.510,0.510 0.515,0.515,0.515 0.520,0.521,0.521 0.526,0.526,0.526 0.531,0.531,0.531 0.537,0.537,0.537 0.542,0.542,0.542 0.548,0.548,0.548 0.553,0.553,0.553 0.558,0.559,0.559 0.564,0.564,0.564 0.569,0.570,0.570 0.575,0.575,0.575 0.581,0.581,0.581 0.586,0.586,0.586 0.592,0.592,0.592 0.597,0.597,0.597 0.603,0.603,0.603 0.608,0.609,0.609 0.614,0.614,0.614 0.620,0.620,0.620 0.625,0.625,0.625 0.631,0.631,0.631 0.637,0.637,0.637 0.642,0.642,0.642 0.648,0.648,0.648 0.653,0.654,0.654 0.659,0.659,0.659 0.665,0.665,0.665 0.671,0.671,0.671 0.676,0.676,0.676 0.682,0.682,0.682 0.688,0.688,0.688 0.693,0.694,0.693 0.699,0.699,0.699 0.705,0.705,0.705 0.711,0.711,0.711 0.716,0.717,0.716 0.722,0.722,0.722 0.728,0.728,0.728 0.733,0.734,0.734 0.739,0.739,0.739 0.745,0.745,0.745 0.750,0.750,0.750 0.755,0.756,0.756 0.761,0.761,0.761 0.766,0.766,0.766 0.770,0.771,0.771 0.775,0.775,0.775 0.779,0.779,0.779 0.783,0.783,0.783 0.786,0.786,0.786 0.788,0.788,0.788 0.790,0.790,0.790 0.792,0.792,0.792 0.792,0.793,0.793 0.793,0.793,0.793 0.792,0.792,0.792 0.791,0.791,0.791 0.789,0.789,0.789 0.786,0.786,0.786 0.783,0.783,0.783 0.780,0.780,0.780 0.776,0.776,0.776 0.771,0.772,0.772 0.767,0.767,0.767 0.762,0.762,0.762 0.757,0.757,0.757 0.751,0.752,0.752 0.746,0.746,0.746 0.740,0.741,0.741 0.735,0.735,0.735 0.729,0.729,0.729 0.723,0.724,0.724 0.718,0.718,0.718 0.712,0.712,0.712 0.706,0.706,0.706 0.701,0.701,0.701 0.695,0.695,0.695 0.689,0.689,0.689 0.683,0.683,0.683 0.678,0.678,0.678 0.672,0.672,0.672 0.666,0.666,0.666 0.661,0.661,0.661 0.655,0.655,0.655 0.649,0.649,0.649 0.644,0.644,0.644 0.638,0.638,0.638 0.632,0.632,0.632 0.627,0.627,0.627 0.621,0.621,0.621 0.615,0.615,0.615 0.610,0.610,0.610 0.604,0.604,0.604 0.599,0.599,0.599 0.593,0.593,0.593 0.587,0.588,0.588 0.582,0.582,0.582 0.576,0.576,0.576 0.571,0.571,0.571 0.565,0.565,0.565 0.560,0.560,0.560 0.554,0.554,0.554 0.549,0.549,0.549 0.543,0.543,0.543 0.538,0.538,0.538 0.532,0.532,0.532 0.527,0.527,0.527 0.521,0.522,0.522 0.516,0.516,0.516 0.511,0.511,0.511 0.505,0.505,0.505 0.500,0.500,0.500 0.494,0.494,0.494 0.489,0.489,0.489 0.484,0.484,0.484 0.478,0.478,0.478 0.473,0.473,0.473 0.468,0.468,0.468 0.462,0.462,0.462 0.457,0.457,0.457 0.452,0.452,0.452 0.446,0.446,0.446 0.441,0.441,0.441 0.436,0.436,0.436 0.430,0.431,0.431 0.425,0.425,0.425 0.420,0.420,0.420 0.415,0.415,0.415 0.410,0.410,0.410 0.404,0.404,0.404 0.399,0.399,0.399 0.394,0.394,0.394 0.389,0.389,0.389 0.384,0.384,0.384 0.378,0.379,0.379 0.373,0.373,0.373 0.368,0.368,0.368 0.363,0.363,0.363 0.358,0.358,0.358 0.353,0.353,0.353 0.348,0.348,0.348 0.343,0.343,0.343 0.338,0.338,0.338 0.333,0.333,0.333 0.328,0.328,0.328 0.323,0.323,0.323 0.318,0.318,0.318 0.313,0.313,0.313 0.308,0.308,0.308 0.303,0.303,0.303 0.298,0.298,0.298 0.293,0.293,0.293 0.288,0.288,0.288 0.283,0.283,0.283 0.278,0.278,0.278 0.273,0.273,0.273 0.269,0.269,0.269 0.264,0.264,0.264 0.259,0.259,0.259 0.254,0.254,0.254 0.249,0.249,0.249 0.244,0.245,0.245 0.240,0.240,0.240 0.235,0.235,0.235 0.230,0.230,0.230 0.226,0.226,0.226 0.221,0.221,0.221 0.217,0.217,0.217 0.212,0.212,0.212 0.208,0.208,0.208 0.204,0.204,0.204 0.200,0.200,0.200 0.196,0.196,0.196 0.192,0.192,0.192 0.189,0.189,0.189 0.186,0.186,0.186 0.183,0.183,0.183 0.181,0.181,0.181 0.179,0.179,0.179 0.178,0.178,0.178 0.177,0.177,0.177 0.177,0.177,0.177 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C6.csv000066400000000000000000000110001421045507400235020ustar00rootroot000000000000000.967,0.214,0.103 0.967,0.220,0.092 0.966,0.228,0.082 0.967,0.239,0.072 0.967,0.252,0.063 0.968,0.266,0.055 0.969,0.282,0.047 0.971,0.298,0.040 0.972,0.315,0.033 0.974,0.333,0.027 0.976,0.350,0.023 0.978,0.368,0.019 0.980,0.386,0.015 0.983,0.403,0.012 0.985,0.421,0.010 0.987,0.438,0.007 0.989,0.455,0.005 0.991,0.472,0.003 0.992,0.489,0.001 0.994,0.505,0.000 0.996,0.522,0.000 0.998,0.538,0.000 0.999,0.554,0.000 1.000,0.569,0.000 1.000,0.585,0.000 1.000,0.600,0.000 1.000,0.616,0.000 1.000,0.631,0.000 1.000,0.646,0.000 1.000,0.660,0.000 1.000,0.675,0.000 1.000,0.689,0.000 1.000,0.703,0.000 1.000,0.717,0.000 1.000,0.730,0.000 1.000,0.743,0.000 0.997,0.755,0.000 0.993,0.766,0.000 0.988,0.777,0.000 0.983,0.787,0.000 0.976,0.795,0.000 0.968,0.803,0.000 0.959,0.809,0.000 0.949,0.814,0.000 0.938,0.817,0.000 0.925,0.820,0.000 0.912,0.821,0.000 0.898,0.821,0.000 0.883,0.820,0.000 0.868,0.817,0.000 0.852,0.814,0.000 0.835,0.810,0.000 0.818,0.805,0.000 0.801,0.800,0.000 0.783,0.794,0.000 0.765,0.788,0.000 0.747,0.781,0.000 0.729,0.774,0.000 0.711,0.767,0.000 0.692,0.760,0.000 0.674,0.752,0.000 0.655,0.745,0.000 0.637,0.737,0.000 0.618,0.730,0.000 0.600,0.722,0.000 0.581,0.714,0.000 0.562,0.707,0.001 0.543,0.699,0.003 0.524,0.691,0.004 0.505,0.684,0.006 0.486,0.676,0.008 0.467,0.668,0.010 0.448,0.661,0.014 0.429,0.654,0.017 0.409,0.647,0.022 0.390,0.640,0.027 0.371,0.633,0.034 0.352,0.627,0.042 0.334,0.621,0.051 0.316,0.616,0.061 0.298,0.611,0.071 0.281,0.607,0.082 0.265,0.604,0.094 0.249,0.602,0.106 0.235,0.601,0.119 0.222,0.601,0.133 0.210,0.601,0.147 0.199,0.603,0.162 0.190,0.606,0.177 0.183,0.610,0.193 0.177,0.614,0.210 0.173,0.620,0.227 0.170,0.626,0.244 0.169,0.632,0.262 0.169,0.640,0.281 0.169,0.648,0.299 0.171,0.656,0.318 0.173,0.665,0.337 0.176,0.674,0.356 0.179,0.683,0.375 0.181,0.692,0.395 0.184,0.702,0.414 0.187,0.711,0.434 0.190,0.721,0.454 0.192,0.731,0.473 0.194,0.741,0.493 0.195,0.751,0.513 0.196,0.760,0.533 0.197,0.770,0.552 0.198,0.780,0.572 0.197,0.790,0.592 0.197,0.800,0.612 0.196,0.810,0.632 0.194,0.820,0.652 0.192,0.830,0.672 0.190,0.839,0.692 0.187,0.848,0.712 0.183,0.858,0.732 0.180,0.866,0.752 0.176,0.874,0.771 0.172,0.882,0.790 0.167,0.889,0.809 0.163,0.895,0.827 0.159,0.901,0.845 0.155,0.905,0.862 0.151,0.908,0.878 0.149,0.910,0.893 0.147,0.911,0.907 0.146,0.911,0.920 0.146,0.909,0.932 0.147,0.906,0.943 0.149,0.901,0.953 0.152,0.896,0.961 0.156,0.889,0.969 0.160,0.881,0.975 0.164,0.873,0.980 0.168,0.863,0.985 0.172,0.853,0.989 0.176,0.843,0.992 0.180,0.832,0.995 0.183,0.820,0.997 0.186,0.809,0.999 0.188,0.797,1.000 0.190,0.785,1.000 0.192,0.773,1.000 0.193,0.760,1.000 0.193,0.748,1.000 0.193,0.736,1.000 0.192,0.723,1.000 0.191,0.711,1.000 0.189,0.699,1.000 0.187,0.687,1.000 0.184,0.674,1.000 0.181,0.662,1.000 0.177,0.650,1.000 0.173,0.638,1.000 0.169,0.626,1.000 0.165,0.614,1.000 0.161,0.603,1.000 0.158,0.591,1.000 0.156,0.580,1.000 0.155,0.570,1.000 0.155,0.559,1.000 0.158,0.550,1.000 0.163,0.541,1.000 0.170,0.533,1.000 0.180,0.525,1.000 0.193,0.519,1.000 0.207,0.513,1.000 0.224,0.509,1.000 0.242,0.506,1.000 0.262,0.504,1.000 0.283,0.503,1.000 0.304,0.503,1.000 0.326,0.504,1.000 0.348,0.507,1.000 0.370,0.510,1.000 0.392,0.514,1.000 0.414,0.519,1.000 0.435,0.525,1.000 0.457,0.531,1.000 0.478,0.538,1.000 0.498,0.546,1.000 0.518,0.553,1.000 0.538,0.561,1.000 0.558,0.570,1.000 0.577,0.578,1.000 0.595,0.586,1.000 0.614,0.595,1.000 0.632,0.604,1.000 0.649,0.613,1.000 0.667,0.622,1.000 0.684,0.631,1.000 0.701,0.640,1.000 0.718,0.648,1.000 0.734,0.657,1.000 0.751,0.666,1.000 0.767,0.675,1.000 0.783,0.684,1.000 0.799,0.692,1.000 0.814,0.701,1.000 0.830,0.709,1.000 0.845,0.716,1.000 0.861,0.723,1.000 0.876,0.730,1.000 0.890,0.736,1.000 0.905,0.740,0.994 0.919,0.744,0.987 0.932,0.747,0.979 0.945,0.749,0.970 0.957,0.750,0.959 0.969,0.749,0.947 0.980,0.747,0.934 0.990,0.743,0.920 0.999,0.738,0.904 1.000,0.732,0.887 1.000,0.725,0.870 1.000,0.716,0.851 1.000,0.707,0.832 1.000,0.696,0.812 1.000,0.685,0.792 1.000,0.673,0.771 1.000,0.661,0.750 1.000,0.648,0.729 1.000,0.635,0.707 1.000,0.621,0.686 1.000,0.607,0.664 1.000,0.593,0.643 1.000,0.579,0.621 1.000,0.564,0.600 1.000,0.549,0.578 1.000,0.535,0.557 1.000,0.520,0.536 1.000,0.505,0.515 1.000,0.489,0.494 1.000,0.474,0.473 1.000,0.458,0.452 1.000,0.443,0.431 1.000,0.427,0.411 1.000,0.411,0.390 1.000,0.395,0.370 1.000,0.378,0.350 1.000,0.362,0.330 1.000,0.346,0.310 1.000,0.329,0.291 0.999,0.313,0.272 0.995,0.297,0.254 0.991,0.282,0.235 0.987,0.267,0.218 0.984,0.253,0.201 0.980,0.240,0.185 0.977,0.230,0.169 0.974,0.221,0.154 0.972,0.215,0.140 0.970,0.212,0.127 0.968,0.211,0.115 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C6s.csv000066400000000000000000000110001421045507400236650ustar00rootroot000000000000000.684,0.631,1.000 0.701,0.640,1.000 0.718,0.648,1.000 0.734,0.657,1.000 0.751,0.666,1.000 0.767,0.675,1.000 0.783,0.684,1.000 0.799,0.692,1.000 0.814,0.701,1.000 0.830,0.709,1.000 0.845,0.716,1.000 0.861,0.723,1.000 0.876,0.730,1.000 0.890,0.736,1.000 0.905,0.740,0.994 0.919,0.744,0.987 0.932,0.747,0.979 0.945,0.749,0.970 0.957,0.750,0.959 0.969,0.749,0.947 0.980,0.747,0.934 0.990,0.743,0.920 0.999,0.738,0.904 1.000,0.732,0.887 1.000,0.725,0.870 1.000,0.716,0.851 1.000,0.707,0.832 1.000,0.696,0.812 1.000,0.685,0.792 1.000,0.673,0.771 1.000,0.661,0.750 1.000,0.648,0.729 1.000,0.635,0.707 1.000,0.621,0.686 1.000,0.607,0.664 1.000,0.593,0.643 1.000,0.579,0.621 1.000,0.564,0.600 1.000,0.549,0.578 1.000,0.535,0.557 1.000,0.520,0.536 1.000,0.505,0.515 1.000,0.489,0.494 1.000,0.474,0.473 1.000,0.458,0.452 1.000,0.443,0.431 1.000,0.427,0.411 1.000,0.411,0.390 1.000,0.395,0.370 1.000,0.378,0.350 1.000,0.362,0.330 1.000,0.346,0.310 1.000,0.329,0.291 0.999,0.313,0.272 0.995,0.297,0.254 0.991,0.282,0.235 0.987,0.267,0.218 0.984,0.253,0.201 0.980,0.240,0.185 0.977,0.230,0.169 0.974,0.221,0.154 0.972,0.215,0.140 0.970,0.212,0.127 0.968,0.211,0.115 0.967,0.214,0.103 0.967,0.220,0.092 0.966,0.228,0.082 0.967,0.239,0.072 0.967,0.252,0.063 0.968,0.266,0.055 0.969,0.282,0.047 0.971,0.298,0.040 0.972,0.315,0.033 0.974,0.333,0.027 0.976,0.350,0.023 0.978,0.368,0.019 0.980,0.386,0.015 0.983,0.403,0.012 0.985,0.421,0.010 0.987,0.438,0.007 0.989,0.455,0.005 0.991,0.472,0.003 0.992,0.489,0.001 0.994,0.505,0.000 0.996,0.522,0.000 0.998,0.538,0.000 0.999,0.554,0.000 1.000,0.569,0.000 1.000,0.585,0.000 1.000,0.600,0.000 1.000,0.616,0.000 1.000,0.631,0.000 1.000,0.646,0.000 1.000,0.660,0.000 1.000,0.675,0.000 1.000,0.689,0.000 1.000,0.703,0.000 1.000,0.717,0.000 1.000,0.730,0.000 1.000,0.743,0.000 0.997,0.755,0.000 0.993,0.766,0.000 0.988,0.777,0.000 0.983,0.787,0.000 0.976,0.795,0.000 0.968,0.803,0.000 0.959,0.809,0.000 0.949,0.814,0.000 0.938,0.817,0.000 0.925,0.820,0.000 0.912,0.821,0.000 0.898,0.821,0.000 0.883,0.820,0.000 0.868,0.817,0.000 0.852,0.814,0.000 0.835,0.810,0.000 0.818,0.805,0.000 0.801,0.800,0.000 0.783,0.794,0.000 0.765,0.788,0.000 0.747,0.781,0.000 0.729,0.774,0.000 0.711,0.767,0.000 0.692,0.760,0.000 0.674,0.752,0.000 0.655,0.745,0.000 0.637,0.737,0.000 0.618,0.730,0.000 0.600,0.722,0.000 0.581,0.714,0.000 0.562,0.707,0.001 0.543,0.699,0.003 0.524,0.691,0.004 0.505,0.684,0.006 0.486,0.676,0.008 0.467,0.668,0.010 0.448,0.661,0.014 0.429,0.654,0.017 0.409,0.647,0.022 0.390,0.640,0.027 0.371,0.633,0.034 0.352,0.627,0.042 0.334,0.621,0.051 0.316,0.616,0.061 0.298,0.611,0.071 0.281,0.607,0.082 0.265,0.604,0.094 0.249,0.602,0.106 0.235,0.601,0.119 0.222,0.601,0.133 0.210,0.601,0.147 0.199,0.603,0.162 0.190,0.606,0.177 0.183,0.610,0.193 0.177,0.614,0.210 0.173,0.620,0.227 0.170,0.626,0.244 0.169,0.632,0.262 0.169,0.640,0.281 0.169,0.648,0.299 0.171,0.656,0.318 0.173,0.665,0.337 0.176,0.674,0.356 0.179,0.683,0.375 0.181,0.692,0.395 0.184,0.702,0.414 0.187,0.711,0.434 0.190,0.721,0.454 0.192,0.731,0.473 0.194,0.741,0.493 0.195,0.751,0.513 0.196,0.760,0.533 0.197,0.770,0.552 0.198,0.780,0.572 0.197,0.790,0.592 0.197,0.800,0.612 0.196,0.810,0.632 0.194,0.820,0.652 0.192,0.830,0.672 0.190,0.839,0.692 0.187,0.848,0.712 0.183,0.858,0.732 0.180,0.866,0.752 0.176,0.874,0.771 0.172,0.882,0.790 0.167,0.889,0.809 0.163,0.895,0.827 0.159,0.901,0.845 0.155,0.905,0.862 0.151,0.908,0.878 0.149,0.910,0.893 0.147,0.911,0.907 0.146,0.911,0.920 0.146,0.909,0.932 0.147,0.906,0.943 0.149,0.901,0.953 0.152,0.896,0.961 0.156,0.889,0.969 0.160,0.881,0.975 0.164,0.873,0.980 0.168,0.863,0.985 0.172,0.853,0.989 0.176,0.843,0.992 0.180,0.832,0.995 0.183,0.820,0.997 0.186,0.809,0.999 0.188,0.797,1.000 0.190,0.785,1.000 0.192,0.773,1.000 0.193,0.760,1.000 0.193,0.748,1.000 0.193,0.736,1.000 0.192,0.723,1.000 0.191,0.711,1.000 0.189,0.699,1.000 0.187,0.687,1.000 0.184,0.674,1.000 0.181,0.662,1.000 0.177,0.650,1.000 0.173,0.638,1.000 0.169,0.626,1.000 0.165,0.614,1.000 0.161,0.603,1.000 0.158,0.591,1.000 0.156,0.580,1.000 0.155,0.570,1.000 0.155,0.559,1.000 0.158,0.550,1.000 0.163,0.541,1.000 0.170,0.533,1.000 0.180,0.525,1.000 0.193,0.519,1.000 0.207,0.513,1.000 0.224,0.509,1.000 0.242,0.506,1.000 0.262,0.504,1.000 0.283,0.503,1.000 0.304,0.503,1.000 0.326,0.504,1.000 0.348,0.507,1.000 0.370,0.510,1.000 0.392,0.514,1.000 0.414,0.519,1.000 0.435,0.525,1.000 0.457,0.531,1.000 0.478,0.538,1.000 0.498,0.546,1.000 0.518,0.553,1.000 0.538,0.561,1.000 0.558,0.570,1.000 0.577,0.578,1.000 0.595,0.586,1.000 0.614,0.595,1.000 0.632,0.604,1.000 0.649,0.613,1.000 0.667,0.622,1.000 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C7.csv000066400000000000000000000110001421045507400235030ustar00rootroot000000000000000.914,0.894,0.098 0.920,0.891,0.123 0.926,0.887,0.147 0.932,0.882,0.171 0.937,0.876,0.193 0.942,0.869,0.215 0.947,0.862,0.236 0.951,0.854,0.256 0.955,0.846,0.275 0.959,0.837,0.294 0.963,0.829,0.312 0.966,0.820,0.330 0.970,0.811,0.346 0.973,0.802,0.363 0.976,0.793,0.379 0.980,0.784,0.394 0.983,0.774,0.409 0.985,0.765,0.423 0.988,0.756,0.438 0.991,0.747,0.452 0.993,0.737,0.465 0.996,0.728,0.479 0.998,0.718,0.492 1.000,0.709,0.505 1.000,0.699,0.518 1.000,0.689,0.531 1.000,0.680,0.544 1.000,0.670,0.556 1.000,0.660,0.568 1.000,0.650,0.581 1.000,0.640,0.593 1.000,0.630,0.605 1.000,0.619,0.617 1.000,0.609,0.629 1.000,0.598,0.641 1.000,0.588,0.652 1.000,0.577,0.664 1.000,0.566,0.676 1.000,0.555,0.687 1.000,0.544,0.699 1.000,0.533,0.710 1.000,0.522,0.722 1.000,0.510,0.733 1.000,0.498,0.745 1.000,0.486,0.756 1.000,0.474,0.767 1.000,0.462,0.779 1.000,0.449,0.790 1.000,0.436,0.801 1.000,0.423,0.812 1.000,0.409,0.824 1.000,0.395,0.835 1.000,0.381,0.846 1.000,0.366,0.857 1.000,0.351,0.868 1.000,0.336,0.879 1.000,0.321,0.890 1.000,0.306,0.901 1.000,0.291,0.912 1.000,0.277,0.923 1.000,0.265,0.933 0.999,0.255,0.942 0.996,0.248,0.951 0.992,0.245,0.960 0.987,0.246,0.967 0.983,0.250,0.974 0.978,0.259,0.979 0.973,0.270,0.984 0.967,0.284,0.988 0.962,0.300,0.990 0.956,0.316,0.993 0.950,0.334,0.994 0.944,0.351,0.995 0.938,0.369,0.996 0.932,0.386,0.997 0.926,0.402,0.997 0.920,0.419,0.997 0.913,0.435,0.997 0.907,0.450,0.997 0.900,0.465,0.997 0.893,0.480,0.997 0.886,0.494,0.997 0.879,0.507,0.997 0.872,0.521,0.997 0.865,0.534,0.997 0.858,0.547,0.997 0.850,0.560,0.997 0.842,0.572,0.996 0.835,0.584,0.996 0.827,0.596,0.996 0.818,0.608,0.996 0.810,0.620,0.996 0.802,0.631,0.996 0.793,0.642,0.996 0.784,0.653,0.996 0.775,0.664,0.995 0.766,0.675,0.995 0.756,0.686,0.995 0.747,0.697,0.995 0.737,0.707,0.995 0.726,0.718,0.995 0.716,0.728,0.994 0.705,0.738,0.994 0.694,0.748,0.994 0.683,0.758,0.994 0.671,0.768,0.994 0.659,0.778,0.993 0.647,0.788,0.993 0.634,0.798,0.993 0.620,0.807,0.993 0.607,0.817,0.992 0.592,0.826,0.992 0.578,0.836,0.992 0.562,0.845,0.992 0.546,0.855,0.991 0.529,0.864,0.991 0.511,0.873,0.990 0.493,0.882,0.990 0.473,0.891,0.989 0.453,0.900,0.988 0.431,0.908,0.987 0.408,0.916,0.985 0.385,0.924,0.983 0.360,0.931,0.980 0.334,0.937,0.976 0.308,0.943,0.972 0.282,0.947,0.966 0.257,0.951,0.959 0.232,0.953,0.951 0.209,0.955,0.942 0.189,0.955,0.933 0.173,0.954,0.922 0.160,0.952,0.910 0.152,0.949,0.898 0.148,0.946,0.886 0.147,0.942,0.873 0.149,0.938,0.860 0.153,0.934,0.847 0.157,0.929,0.834 0.162,0.924,0.820 0.167,0.919,0.807 0.172,0.914,0.793 0.177,0.909,0.780 0.181,0.904,0.767 0.185,0.899,0.753 0.188,0.894,0.740 0.191,0.889,0.726 0.194,0.884,0.713 0.196,0.879,0.700 0.198,0.873,0.687 0.199,0.868,0.673 0.201,0.863,0.660 0.202,0.858,0.647 0.202,0.853,0.633 0.203,0.848,0.620 0.203,0.843,0.607 0.203,0.838,0.594 0.203,0.833,0.581 0.202,0.828,0.567 0.201,0.823,0.554 0.200,0.819,0.541 0.199,0.814,0.528 0.198,0.809,0.515 0.196,0.804,0.501 0.194,0.799,0.488 0.192,0.794,0.475 0.189,0.789,0.462 0.187,0.784,0.448 0.184,0.779,0.435 0.181,0.774,0.422 0.177,0.769,0.408 0.174,0.764,0.395 0.170,0.760,0.381 0.166,0.755,0.368 0.161,0.750,0.354 0.156,0.745,0.340 0.151,0.740,0.326 0.146,0.735,0.312 0.140,0.730,0.298 0.134,0.726,0.284 0.128,0.721,0.269 0.122,0.716,0.255 0.116,0.712,0.240 0.111,0.707,0.224 0.107,0.703,0.209 0.104,0.699,0.193 0.103,0.695,0.177 0.105,0.692,0.160 0.109,0.690,0.144 0.116,0.688,0.128 0.126,0.686,0.111 0.138,0.686,0.095 0.152,0.686,0.078 0.167,0.686,0.062 0.183,0.688,0.046 0.199,0.690,0.031 0.216,0.692,0.019 0.232,0.695,0.009 0.249,0.698,0.002 0.265,0.702,0.000 0.281,0.705,0.000 0.296,0.709,0.000 0.311,0.713,0.000 0.326,0.717,0.000 0.340,0.721,0.000 0.354,0.725,0.000 0.368,0.729,0.000 0.382,0.733,0.000 0.395,0.737,0.000 0.408,0.741,0.000 0.421,0.745,0.000 0.433,0.749,0.000 0.446,0.753,0.000 0.458,0.757,0.000 0.470,0.761,0.000 0.482,0.765,0.000 0.494,0.769,0.000 0.506,0.773,0.000 0.518,0.777,0.000 0.529,0.781,0.000 0.541,0.785,0.000 0.552,0.789,0.000 0.564,0.792,0.000 0.575,0.796,0.000 0.586,0.800,0.000 0.597,0.804,0.000 0.609,0.808,0.000 0.620,0.812,0.000 0.631,0.816,0.000 0.642,0.820,0.000 0.653,0.823,0.000 0.664,0.827,0.000 0.675,0.831,0.000 0.686,0.835,0.000 0.696,0.839,0.000 0.707,0.842,0.000 0.718,0.846,0.000 0.729,0.850,0.000 0.740,0.854,0.000 0.750,0.858,0.000 0.761,0.861,0.000 0.772,0.865,0.000 0.782,0.869,0.000 0.793,0.873,0.000 0.803,0.876,0.000 0.814,0.880,0.000 0.824,0.883,0.000 0.835,0.887,0.000 0.845,0.890,0.000 0.855,0.892,0.000 0.864,0.895,0.000 0.874,0.897,0.000 0.883,0.898,0.000 0.891,0.898,0.012 0.899,0.898,0.042 0.907,0.897,0.072 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-C7s.csv000066400000000000000000000110001421045507400236660ustar00rootroot000000000000000.152,0.686,0.078 0.167,0.686,0.062 0.183,0.688,0.046 0.199,0.690,0.031 0.216,0.692,0.019 0.232,0.695,0.009 0.249,0.698,0.002 0.265,0.702,0.000 0.281,0.705,0.000 0.296,0.709,0.000 0.311,0.713,0.000 0.326,0.717,0.000 0.340,0.721,0.000 0.354,0.725,0.000 0.368,0.729,0.000 0.382,0.733,0.000 0.395,0.737,0.000 0.408,0.741,0.000 0.421,0.745,0.000 0.433,0.749,0.000 0.446,0.753,0.000 0.458,0.757,0.000 0.470,0.761,0.000 0.482,0.765,0.000 0.494,0.769,0.000 0.506,0.773,0.000 0.518,0.777,0.000 0.529,0.781,0.000 0.541,0.785,0.000 0.552,0.789,0.000 0.564,0.792,0.000 0.575,0.796,0.000 0.586,0.800,0.000 0.597,0.804,0.000 0.609,0.808,0.000 0.620,0.812,0.000 0.631,0.816,0.000 0.642,0.820,0.000 0.653,0.823,0.000 0.664,0.827,0.000 0.675,0.831,0.000 0.686,0.835,0.000 0.696,0.839,0.000 0.707,0.842,0.000 0.718,0.846,0.000 0.729,0.850,0.000 0.740,0.854,0.000 0.750,0.858,0.000 0.761,0.861,0.000 0.772,0.865,0.000 0.782,0.869,0.000 0.793,0.873,0.000 0.803,0.876,0.000 0.814,0.880,0.000 0.824,0.883,0.000 0.835,0.887,0.000 0.845,0.890,0.000 0.855,0.892,0.000 0.864,0.895,0.000 0.874,0.897,0.000 0.883,0.898,0.000 0.891,0.898,0.012 0.899,0.898,0.042 0.907,0.897,0.072 0.914,0.894,0.098 0.920,0.891,0.123 0.926,0.887,0.147 0.932,0.882,0.171 0.937,0.876,0.193 0.942,0.869,0.215 0.947,0.862,0.236 0.951,0.854,0.256 0.955,0.846,0.275 0.959,0.837,0.294 0.963,0.829,0.312 0.966,0.820,0.330 0.970,0.811,0.346 0.973,0.802,0.363 0.976,0.793,0.379 0.980,0.784,0.394 0.983,0.774,0.409 0.985,0.765,0.423 0.988,0.756,0.438 0.991,0.747,0.452 0.993,0.737,0.465 0.996,0.728,0.479 0.998,0.718,0.492 1.000,0.709,0.505 1.000,0.699,0.518 1.000,0.689,0.531 1.000,0.680,0.544 1.000,0.670,0.556 1.000,0.660,0.568 1.000,0.650,0.581 1.000,0.640,0.593 1.000,0.630,0.605 1.000,0.619,0.617 1.000,0.609,0.629 1.000,0.598,0.641 1.000,0.588,0.652 1.000,0.577,0.664 1.000,0.566,0.676 1.000,0.555,0.687 1.000,0.544,0.699 1.000,0.533,0.710 1.000,0.522,0.722 1.000,0.510,0.733 1.000,0.498,0.745 1.000,0.486,0.756 1.000,0.474,0.767 1.000,0.462,0.779 1.000,0.449,0.790 1.000,0.436,0.801 1.000,0.423,0.812 1.000,0.409,0.824 1.000,0.395,0.835 1.000,0.381,0.846 1.000,0.366,0.857 1.000,0.351,0.868 1.000,0.336,0.879 1.000,0.321,0.890 1.000,0.306,0.901 1.000,0.291,0.912 1.000,0.277,0.923 1.000,0.265,0.933 0.999,0.255,0.942 0.996,0.248,0.951 0.992,0.245,0.960 0.987,0.246,0.967 0.983,0.250,0.974 0.978,0.259,0.979 0.973,0.270,0.984 0.967,0.284,0.988 0.962,0.300,0.990 0.956,0.316,0.993 0.950,0.334,0.994 0.944,0.351,0.995 0.938,0.369,0.996 0.932,0.386,0.997 0.926,0.402,0.997 0.920,0.419,0.997 0.913,0.435,0.997 0.907,0.450,0.997 0.900,0.465,0.997 0.893,0.480,0.997 0.886,0.494,0.997 0.879,0.507,0.997 0.872,0.521,0.997 0.865,0.534,0.997 0.858,0.547,0.997 0.850,0.560,0.997 0.842,0.572,0.996 0.835,0.584,0.996 0.827,0.596,0.996 0.818,0.608,0.996 0.810,0.620,0.996 0.802,0.631,0.996 0.793,0.642,0.996 0.784,0.653,0.996 0.775,0.664,0.995 0.766,0.675,0.995 0.756,0.686,0.995 0.747,0.697,0.995 0.737,0.707,0.995 0.726,0.718,0.995 0.716,0.728,0.994 0.705,0.738,0.994 0.694,0.748,0.994 0.683,0.758,0.994 0.671,0.768,0.994 0.659,0.778,0.993 0.647,0.788,0.993 0.634,0.798,0.993 0.620,0.807,0.993 0.607,0.817,0.992 0.592,0.826,0.992 0.578,0.836,0.992 0.562,0.845,0.992 0.546,0.855,0.991 0.529,0.864,0.991 0.511,0.873,0.990 0.493,0.882,0.990 0.473,0.891,0.989 0.453,0.900,0.988 0.431,0.908,0.987 0.408,0.916,0.985 0.385,0.924,0.983 0.360,0.931,0.980 0.334,0.937,0.976 0.308,0.943,0.972 0.282,0.947,0.966 0.257,0.951,0.959 0.232,0.953,0.951 0.209,0.955,0.942 0.189,0.955,0.933 0.173,0.954,0.922 0.160,0.952,0.910 0.152,0.949,0.898 0.148,0.946,0.886 0.147,0.942,0.873 0.149,0.938,0.860 0.153,0.934,0.847 0.157,0.929,0.834 0.162,0.924,0.820 0.167,0.919,0.807 0.172,0.914,0.793 0.177,0.909,0.780 0.181,0.904,0.767 0.185,0.899,0.753 0.188,0.894,0.740 0.191,0.889,0.726 0.194,0.884,0.713 0.196,0.879,0.700 0.198,0.873,0.687 0.199,0.868,0.673 0.201,0.863,0.660 0.202,0.858,0.647 0.202,0.853,0.633 0.203,0.848,0.620 0.203,0.843,0.607 0.203,0.838,0.594 0.203,0.833,0.581 0.202,0.828,0.567 0.201,0.823,0.554 0.200,0.819,0.541 0.199,0.814,0.528 0.198,0.809,0.515 0.196,0.804,0.501 0.194,0.799,0.488 0.192,0.794,0.475 0.189,0.789,0.462 0.187,0.784,0.448 0.184,0.779,0.435 0.181,0.774,0.422 0.177,0.769,0.408 0.174,0.764,0.395 0.170,0.760,0.381 0.166,0.755,0.368 0.161,0.750,0.354 0.156,0.745,0.340 0.151,0.740,0.326 0.146,0.735,0.312 0.140,0.730,0.298 0.134,0.726,0.284 0.128,0.721,0.269 0.122,0.716,0.255 0.116,0.712,0.240 0.111,0.707,0.224 0.107,0.703,0.209 0.104,0.699,0.193 0.103,0.695,0.177 0.105,0.692,0.160 0.109,0.690,0.144 0.116,0.688,0.128 0.126,0.686,0.111 0.138,0.686,0.095 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-CBC1.csv000066400000000000000000000110001421045507400237020ustar00rootroot000000000000000.244,0.528,0.918 0.254,0.534,0.923 0.267,0.539,0.928 0.280,0.545,0.931 0.294,0.551,0.934 0.309,0.557,0.936 0.324,0.563,0.938 0.340,0.569,0.939 0.355,0.575,0.940 0.370,0.581,0.941 0.384,0.587,0.942 0.399,0.593,0.942 0.413,0.599,0.943 0.426,0.606,0.943 0.440,0.612,0.944 0.453,0.618,0.944 0.466,0.624,0.945 0.478,0.631,0.945 0.491,0.637,0.945 0.503,0.644,0.946 0.515,0.650,0.946 0.526,0.656,0.947 0.538,0.663,0.947 0.549,0.669,0.947 0.561,0.676,0.948 0.572,0.682,0.948 0.583,0.689,0.948 0.593,0.695,0.949 0.604,0.702,0.949 0.615,0.708,0.949 0.625,0.715,0.950 0.636,0.722,0.950 0.646,0.728,0.950 0.656,0.735,0.951 0.666,0.742,0.951 0.676,0.748,0.951 0.686,0.755,0.951 0.696,0.762,0.952 0.706,0.768,0.952 0.716,0.775,0.952 0.726,0.782,0.952 0.735,0.789,0.952 0.745,0.795,0.953 0.754,0.802,0.953 0.764,0.809,0.953 0.773,0.816,0.953 0.783,0.823,0.953 0.792,0.829,0.954 0.801,0.836,0.954 0.811,0.843,0.954 0.820,0.850,0.954 0.829,0.857,0.954 0.838,0.864,0.954 0.847,0.870,0.954 0.856,0.877,0.953 0.865,0.884,0.953 0.873,0.890,0.952 0.882,0.896,0.950 0.890,0.901,0.948 0.897,0.906,0.945 0.904,0.910,0.941 0.910,0.914,0.936 0.915,0.916,0.930 0.919,0.917,0.923 0.922,0.917,0.915 0.924,0.916,0.906 0.925,0.914,0.895 0.925,0.911,0.884 0.924,0.908,0.872 0.922,0.903,0.860 0.920,0.898,0.847 0.917,0.892,0.834 0.913,0.886,0.821 0.909,0.879,0.807 0.905,0.872,0.794 0.901,0.866,0.780 0.897,0.859,0.766 0.892,0.852,0.753 0.888,0.845,0.739 0.883,0.838,0.726 0.878,0.831,0.712 0.873,0.824,0.699 0.869,0.818,0.685 0.864,0.811,0.672 0.859,0.804,0.658 0.854,0.797,0.645 0.849,0.790,0.631 0.844,0.784,0.618 0.839,0.777,0.605 0.834,0.770,0.591 0.829,0.763,0.578 0.824,0.757,0.565 0.819,0.750,0.552 0.814,0.743,0.538 0.808,0.736,0.525 0.803,0.730,0.512 0.798,0.723,0.499 0.792,0.717,0.486 0.787,0.710,0.472 0.782,0.703,0.459 0.776,0.697,0.446 0.771,0.690,0.433 0.765,0.684,0.420 0.759,0.677,0.406 0.754,0.670,0.393 0.748,0.664,0.380 0.742,0.657,0.367 0.737,0.651,0.353 0.731,0.645,0.340 0.725,0.638,0.327 0.719,0.632,0.313 0.713,0.625,0.299 0.707,0.619,0.286 0.701,0.612,0.272 0.695,0.606,0.258 0.689,0.600,0.244 0.683,0.593,0.230 0.677,0.587,0.216 0.671,0.581,0.201 0.665,0.574,0.187 0.658,0.568,0.172 0.652,0.562,0.157 0.645,0.556,0.143 0.639,0.550,0.129 0.632,0.543,0.115 0.626,0.537,0.102 0.619,0.531,0.090 0.612,0.525,0.079 0.605,0.519,0.071 0.598,0.512,0.064 0.591,0.506,0.059 0.584,0.500,0.057 0.577,0.494,0.057 0.570,0.488,0.058 0.563,0.482,0.061 0.556,0.476,0.065 0.549,0.470,0.069 0.542,0.464,0.073 0.534,0.458,0.077 0.527,0.452,0.081 0.520,0.446,0.085 0.513,0.440,0.089 0.506,0.434,0.093 0.499,0.428,0.096 0.492,0.422,0.099 0.485,0.416,0.102 0.478,0.410,0.105 0.470,0.404,0.108 0.463,0.398,0.111 0.456,0.392,0.113 0.449,0.386,0.116 0.442,0.380,0.118 0.435,0.375,0.120 0.428,0.369,0.122 0.421,0.363,0.124 0.414,0.357,0.126 0.407,0.352,0.128 0.400,0.346,0.130 0.393,0.340,0.131 0.387,0.334,0.133 0.380,0.329,0.134 0.373,0.323,0.136 0.366,0.317,0.137 0.359,0.312,0.138 0.352,0.306,0.140 0.345,0.301,0.141 0.338,0.295,0.142 0.331,0.289,0.143 0.325,0.284,0.144 0.318,0.278,0.145 0.311,0.273,0.146 0.304,0.267,0.147 0.297,0.262,0.148 0.290,0.256,0.148 0.283,0.251,0.149 0.277,0.246,0.150 0.270,0.240,0.150 0.263,0.235,0.151 0.256,0.230,0.152 0.249,0.224,0.152 0.242,0.219,0.153 0.236,0.214,0.154 0.229,0.209,0.155 0.223,0.204,0.156 0.216,0.200,0.157 0.210,0.195,0.159 0.205,0.192,0.161 0.199,0.188,0.163 0.195,0.186,0.167 0.190,0.183,0.171 0.187,0.182,0.176 0.184,0.182,0.181 0.183,0.182,0.188 0.181,0.183,0.195 0.181,0.185,0.203 0.181,0.187,0.212 0.182,0.191,0.221 0.184,0.194,0.231 0.186,0.198,0.241 0.188,0.203,0.251 0.190,0.208,0.261 0.192,0.213,0.272 0.195,0.218,0.283 0.198,0.223,0.294 0.200,0.228,0.305 0.203,0.233,0.316 0.205,0.239,0.327 0.207,0.244,0.338 0.210,0.250,0.350 0.212,0.255,0.361 0.214,0.260,0.373 0.216,0.266,0.384 0.218,0.271,0.396 0.220,0.277,0.407 0.222,0.283,0.419 0.224,0.288,0.431 0.225,0.294,0.442 0.227,0.299,0.454 0.228,0.305,0.466 0.229,0.311,0.478 0.231,0.316,0.490 0.232,0.322,0.502 0.233,0.328,0.514 0.234,0.333,0.526 0.235,0.339,0.538 0.235,0.345,0.550 0.236,0.351,0.563 0.236,0.356,0.575 0.237,0.362,0.587 0.237,0.368,0.600 0.237,0.374,0.612 0.237,0.380,0.625 0.237,0.386,0.637 0.237,0.392,0.650 0.237,0.397,0.662 0.236,0.403,0.675 0.236,0.409,0.688 0.235,0.415,0.701 0.234,0.421,0.713 0.233,0.427,0.726 0.232,0.433,0.739 0.230,0.439,0.752 0.229,0.445,0.765 0.227,0.452,0.778 0.225,0.458,0.791 0.223,0.464,0.804 0.221,0.470,0.816 0.220,0.476,0.829 0.218,0.482,0.841 0.217,0.488,0.853 0.216,0.494,0.865 0.217,0.499,0.876 0.219,0.505,0.886 0.222,0.511,0.895 0.228,0.517,0.904 0.235,0.522,0.911 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-CBC2.csv000066400000000000000000000110001421045507400237030ustar00rootroot000000000000000.933,0.930,0.927 0.935,0.929,0.918 0.935,0.926,0.909 0.935,0.923,0.899 0.933,0.919,0.887 0.931,0.914,0.875 0.929,0.909,0.863 0.925,0.903,0.849 0.922,0.896,0.836 0.918,0.890,0.822 0.914,0.883,0.808 0.910,0.876,0.793 0.906,0.869,0.779 0.901,0.862,0.765 0.897,0.855,0.750 0.893,0.848,0.736 0.888,0.841,0.721 0.883,0.834,0.707 0.879,0.827,0.693 0.874,0.820,0.678 0.869,0.813,0.664 0.864,0.806,0.650 0.860,0.799,0.636 0.855,0.792,0.622 0.850,0.785,0.607 0.845,0.779,0.593 0.840,0.772,0.579 0.835,0.765,0.565 0.829,0.758,0.551 0.824,0.751,0.537 0.819,0.744,0.523 0.814,0.738,0.509 0.808,0.731,0.495 0.803,0.724,0.481 0.798,0.717,0.467 0.792,0.711,0.452 0.787,0.704,0.438 0.781,0.697,0.424 0.775,0.691,0.410 0.770,0.684,0.396 0.764,0.677,0.382 0.758,0.671,0.368 0.753,0.664,0.353 0.747,0.658,0.339 0.741,0.651,0.325 0.735,0.644,0.310 0.729,0.638,0.296 0.723,0.631,0.281 0.717,0.625,0.266 0.711,0.618,0.251 0.704,0.612,0.235 0.698,0.605,0.220 0.692,0.599,0.204 0.686,0.593,0.187 0.680,0.587,0.171 0.674,0.580,0.154 0.668,0.575,0.136 0.662,0.569,0.118 0.657,0.564,0.099 0.652,0.559,0.080 0.647,0.555,0.060 0.643,0.551,0.040 0.641,0.548,0.023 0.639,0.546,0.011 0.638,0.546,0.006 0.638,0.546,0.007 0.639,0.547,0.014 0.641,0.549,0.027 0.644,0.552,0.046 0.648,0.556,0.066 0.653,0.560,0.085 0.658,0.565,0.104 0.663,0.570,0.123 0.669,0.576,0.141 0.675,0.582,0.158 0.681,0.588,0.175 0.688,0.594,0.192 0.694,0.601,0.208 0.700,0.607,0.224 0.706,0.614,0.240 0.712,0.620,0.255 0.718,0.627,0.270 0.724,0.633,0.285 0.730,0.640,0.300 0.736,0.646,0.314 0.742,0.653,0.329 0.748,0.659,0.343 0.754,0.666,0.357 0.760,0.672,0.372 0.766,0.679,0.386 0.771,0.686,0.400 0.777,0.692,0.414 0.783,0.699,0.428 0.788,0.706,0.442 0.794,0.712,0.456 0.799,0.719,0.470 0.805,0.726,0.484 0.810,0.733,0.498 0.815,0.739,0.512 0.820,0.746,0.527 0.826,0.753,0.541 0.831,0.760,0.555 0.836,0.767,0.569 0.841,0.773,0.583 0.846,0.780,0.597 0.851,0.787,0.611 0.856,0.794,0.625 0.861,0.801,0.640 0.866,0.808,0.654 0.871,0.815,0.668 0.875,0.822,0.682 0.880,0.829,0.697 0.885,0.836,0.711 0.889,0.843,0.725 0.894,0.850,0.740 0.898,0.857,0.754 0.902,0.864,0.768 0.907,0.871,0.783 0.911,0.877,0.797 0.915,0.884,0.812 0.918,0.890,0.826 0.921,0.897,0.840 0.924,0.902,0.854 0.926,0.907,0.867 0.927,0.912,0.880 0.927,0.915,0.892 0.927,0.918,0.903 0.925,0.919,0.913 0.922,0.919,0.922 0.918,0.919,0.930 0.913,0.917,0.937 0.908,0.914,0.943 0.901,0.910,0.948 0.894,0.905,0.951 0.886,0.900,0.954 0.877,0.894,0.956 0.869,0.888,0.958 0.860,0.881,0.960 0.850,0.874,0.961 0.841,0.868,0.961 0.832,0.861,0.962 0.822,0.854,0.963 0.812,0.847,0.963 0.803,0.840,0.964 0.793,0.833,0.964 0.783,0.826,0.964 0.773,0.819,0.965 0.763,0.812,0.965 0.753,0.805,0.966 0.743,0.798,0.966 0.733,0.791,0.966 0.723,0.785,0.967 0.713,0.778,0.967 0.703,0.771,0.968 0.692,0.764,0.968 0.682,0.757,0.968 0.671,0.750,0.969 0.660,0.744,0.969 0.650,0.737,0.969 0.639,0.730,0.969 0.628,0.724,0.970 0.616,0.717,0.970 0.605,0.710,0.970 0.594,0.704,0.971 0.582,0.697,0.971 0.570,0.690,0.971 0.559,0.684,0.971 0.546,0.677,0.971 0.534,0.671,0.972 0.522,0.664,0.972 0.509,0.657,0.972 0.496,0.651,0.972 0.483,0.644,0.972 0.469,0.638,0.973 0.455,0.632,0.973 0.441,0.625,0.973 0.426,0.619,0.973 0.411,0.612,0.973 0.396,0.606,0.973 0.380,0.600,0.973 0.363,0.594,0.973 0.346,0.588,0.973 0.329,0.582,0.974 0.311,0.576,0.974 0.293,0.571,0.974 0.276,0.566,0.974 0.259,0.561,0.974 0.244,0.557,0.974 0.231,0.554,0.974 0.221,0.552,0.974 0.214,0.551,0.974 0.212,0.550,0.974 0.215,0.551,0.974 0.222,0.552,0.974 0.233,0.555,0.974 0.247,0.558,0.974 0.263,0.562,0.974 0.280,0.567,0.974 0.297,0.572,0.974 0.315,0.577,0.974 0.333,0.583,0.974 0.350,0.589,0.973 0.367,0.595,0.973 0.383,0.601,0.973 0.399,0.607,0.973 0.415,0.614,0.973 0.430,0.620,0.973 0.444,0.627,0.973 0.458,0.633,0.973 0.472,0.639,0.972 0.486,0.646,0.972 0.499,0.652,0.972 0.512,0.659,0.972 0.524,0.665,0.972 0.537,0.672,0.972 0.549,0.679,0.971 0.561,0.685,0.971 0.573,0.692,0.971 0.585,0.698,0.971 0.596,0.705,0.970 0.608,0.712,0.970 0.619,0.718,0.970 0.630,0.725,0.970 0.641,0.732,0.969 0.652,0.738,0.969 0.663,0.745,0.969 0.673,0.752,0.968 0.684,0.759,0.968 0.694,0.766,0.968 0.705,0.772,0.968 0.715,0.779,0.967 0.725,0.786,0.967 0.736,0.793,0.966 0.746,0.800,0.966 0.756,0.807,0.966 0.766,0.814,0.965 0.775,0.820,0.965 0.785,0.827,0.964 0.795,0.834,0.964 0.805,0.841,0.963 0.814,0.848,0.963 0.824,0.855,0.963 0.834,0.862,0.962 0.843,0.869,0.962 0.852,0.876,0.961 0.862,0.883,0.960 0.871,0.890,0.960 0.880,0.896,0.959 0.888,0.903,0.957 0.896,0.909,0.956 0.904,0.914,0.954 0.911,0.919,0.952 0.918,0.923,0.948 0.923,0.926,0.944 0.928,0.929,0.940 0.931,0.930,0.934 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-CBD1.csv000066400000000000000000000110001421045507400237030ustar00rootroot000000000000000.229,0.566,0.997 0.242,0.569,0.996 0.253,0.571,0.996 0.265,0.574,0.996 0.276,0.577,0.996 0.286,0.580,0.995 0.296,0.582,0.995 0.306,0.585,0.995 0.315,0.588,0.994 0.324,0.590,0.994 0.333,0.593,0.994 0.342,0.596,0.993 0.350,0.599,0.993 0.359,0.601,0.993 0.367,0.604,0.993 0.375,0.607,0.992 0.382,0.610,0.992 0.390,0.612,0.992 0.397,0.615,0.991 0.405,0.618,0.991 0.412,0.621,0.991 0.419,0.624,0.990 0.426,0.626,0.990 0.433,0.629,0.990 0.440,0.632,0.989 0.446,0.635,0.989 0.453,0.638,0.989 0.459,0.640,0.988 0.466,0.643,0.988 0.472,0.646,0.988 0.478,0.649,0.987 0.485,0.652,0.987 0.491,0.654,0.987 0.497,0.657,0.986 0.503,0.660,0.986 0.509,0.663,0.986 0.515,0.666,0.985 0.520,0.669,0.985 0.526,0.672,0.985 0.532,0.674,0.984 0.538,0.677,0.984 0.543,0.680,0.983 0.549,0.683,0.983 0.554,0.686,0.983 0.560,0.689,0.982 0.565,0.692,0.982 0.571,0.694,0.982 0.576,0.697,0.981 0.581,0.700,0.981 0.587,0.703,0.980 0.592,0.706,0.980 0.597,0.709,0.980 0.602,0.712,0.979 0.608,0.715,0.979 0.613,0.718,0.979 0.618,0.721,0.978 0.623,0.724,0.978 0.628,0.726,0.977 0.633,0.729,0.977 0.638,0.732,0.977 0.643,0.735,0.976 0.648,0.738,0.976 0.653,0.741,0.975 0.658,0.744,0.975 0.663,0.747,0.975 0.667,0.750,0.974 0.672,0.753,0.974 0.677,0.756,0.973 0.682,0.759,0.973 0.687,0.762,0.973 0.691,0.765,0.972 0.696,0.768,0.972 0.701,0.771,0.971 0.705,0.774,0.971 0.710,0.777,0.970 0.715,0.780,0.970 0.719,0.783,0.970 0.724,0.786,0.969 0.728,0.789,0.969 0.733,0.792,0.968 0.738,0.795,0.968 0.742,0.798,0.967 0.747,0.801,0.967 0.751,0.804,0.966 0.756,0.807,0.966 0.760,0.810,0.966 0.765,0.813,0.965 0.769,0.816,0.965 0.773,0.819,0.964 0.778,0.822,0.964 0.782,0.825,0.963 0.787,0.828,0.963 0.791,0.831,0.962 0.795,0.834,0.962 0.800,0.837,0.961 0.804,0.840,0.961 0.808,0.843,0.960 0.813,0.846,0.960 0.817,0.849,0.959 0.821,0.852,0.959 0.826,0.855,0.958 0.830,0.858,0.958 0.834,0.862,0.957 0.838,0.865,0.957 0.843,0.868,0.956 0.847,0.871,0.956 0.851,0.874,0.955 0.855,0.877,0.955 0.860,0.880,0.954 0.864,0.883,0.954 0.868,0.886,0.953 0.872,0.889,0.953 0.876,0.892,0.952 0.880,0.896,0.952 0.885,0.899,0.951 0.889,0.902,0.951 0.893,0.905,0.950 0.897,0.908,0.949 0.901,0.911,0.949 0.905,0.914,0.948 0.909,0.916,0.947 0.912,0.919,0.946 0.916,0.922,0.944 0.919,0.924,0.942 0.922,0.925,0.940 0.925,0.927,0.937 0.927,0.928,0.934 0.929,0.928,0.930 0.931,0.928,0.926 0.932,0.928,0.922 0.932,0.927,0.917 0.932,0.925,0.911 0.932,0.924,0.905 0.931,0.921,0.899 0.931,0.919,0.893 0.929,0.916,0.887 0.928,0.914,0.880 0.927,0.911,0.874 0.925,0.908,0.867 0.924,0.905,0.860 0.922,0.902,0.854 0.920,0.898,0.847 0.919,0.895,0.840 0.917,0.892,0.834 0.915,0.889,0.827 0.914,0.886,0.820 0.912,0.883,0.814 0.910,0.880,0.807 0.908,0.877,0.801 0.907,0.874,0.794 0.905,0.870,0.787 0.903,0.867,0.781 0.901,0.864,0.774 0.899,0.861,0.768 0.898,0.858,0.761 0.896,0.855,0.754 0.894,0.852,0.748 0.892,0.849,0.741 0.890,0.846,0.735 0.888,0.843,0.728 0.886,0.840,0.722 0.884,0.837,0.715 0.882,0.834,0.709 0.881,0.831,0.702 0.879,0.827,0.695 0.877,0.824,0.689 0.875,0.821,0.682 0.873,0.818,0.676 0.871,0.815,0.669 0.869,0.812,0.663 0.867,0.809,0.656 0.865,0.806,0.650 0.863,0.803,0.643 0.861,0.800,0.637 0.859,0.797,0.630 0.856,0.794,0.624 0.854,0.791,0.617 0.852,0.788,0.611 0.850,0.785,0.604 0.848,0.782,0.598 0.846,0.779,0.592 0.844,0.776,0.585 0.842,0.773,0.579 0.840,0.770,0.572 0.837,0.767,0.566 0.835,0.764,0.559 0.833,0.761,0.553 0.831,0.758,0.546 0.829,0.755,0.540 0.826,0.752,0.533 0.824,0.749,0.527 0.822,0.746,0.521 0.820,0.743,0.514 0.817,0.740,0.508 0.815,0.737,0.501 0.813,0.734,0.495 0.811,0.731,0.488 0.808,0.728,0.482 0.806,0.726,0.476 0.804,0.723,0.469 0.801,0.720,0.463 0.799,0.717,0.456 0.797,0.714,0.450 0.794,0.711,0.443 0.792,0.708,0.437 0.789,0.705,0.430 0.787,0.702,0.424 0.785,0.699,0.418 0.782,0.696,0.411 0.780,0.693,0.405 0.777,0.690,0.398 0.775,0.688,0.392 0.772,0.685,0.385 0.770,0.682,0.379 0.767,0.679,0.372 0.765,0.676,0.366 0.762,0.673,0.359 0.760,0.670,0.352 0.757,0.667,0.346 0.755,0.664,0.339 0.752,0.662,0.333 0.750,0.659,0.326 0.747,0.656,0.319 0.745,0.653,0.313 0.742,0.650,0.306 0.740,0.647,0.299 0.737,0.644,0.292 0.734,0.641,0.286 0.732,0.639,0.279 0.729,0.636,0.272 0.726,0.633,0.265 0.724,0.630,0.258 0.721,0.627,0.251 0.718,0.624,0.244 0.716,0.622,0.237 0.713,0.619,0.230 0.710,0.616,0.222 0.708,0.613,0.215 0.705,0.610,0.207 0.702,0.608,0.200 0.700,0.605,0.192 0.697,0.602,0.184 0.694,0.599,0.176 0.691,0.596,0.168 0.689,0.594,0.160 0.686,0.591,0.151 0.683,0.588,0.142 0.680,0.585,0.133 0.677,0.582,0.124 0.675,0.580,0.114 0.672,0.577,0.103 0.669,0.574,0.092 0.666,0.571,0.080 0.663,0.569,0.067 0.660,0.566,0.051 0.657,0.563,0.033 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-CBL1.csv000066400000000000000000000110001421045507400237130ustar00rootroot000000000000000.066,0.066,0.066 0.068,0.069,0.075 0.070,0.073,0.084 0.072,0.076,0.091 0.073,0.079,0.099 0.075,0.082,0.106 0.076,0.085,0.113 0.077,0.088,0.120 0.077,0.090,0.127 0.077,0.093,0.135 0.078,0.096,0.142 0.078,0.098,0.149 0.078,0.101,0.157 0.078,0.104,0.164 0.078,0.107,0.171 0.078,0.109,0.179 0.077,0.112,0.186 0.077,0.115,0.193 0.076,0.118,0.200 0.075,0.120,0.208 0.075,0.123,0.215 0.074,0.126,0.222 0.072,0.129,0.229 0.071,0.132,0.237 0.070,0.135,0.244 0.068,0.138,0.251 0.067,0.140,0.258 0.065,0.143,0.265 0.063,0.146,0.272 0.061,0.149,0.279 0.059,0.152,0.286 0.056,0.155,0.293 0.054,0.158,0.300 0.051,0.161,0.307 0.048,0.164,0.314 0.045,0.167,0.321 0.042,0.170,0.328 0.039,0.173,0.335 0.036,0.176,0.341 0.032,0.179,0.348 0.029,0.182,0.355 0.025,0.185,0.361 0.022,0.188,0.368 0.019,0.191,0.374 0.015,0.194,0.381 0.012,0.197,0.387 0.009,0.200,0.393 0.006,0.203,0.400 0.004,0.206,0.406 0.001,0.209,0.412 0.000,0.212,0.418 0.000,0.215,0.424 0.000,0.218,0.430 0.000,0.221,0.436 0.000,0.224,0.441 0.000,0.228,0.447 0.000,0.231,0.452 0.000,0.234,0.458 0.000,0.237,0.463 0.000,0.240,0.468 0.000,0.243,0.474 0.000,0.246,0.479 0.000,0.249,0.484 0.002,0.252,0.488 0.005,0.255,0.493 0.010,0.259,0.498 0.015,0.262,0.502 0.021,0.265,0.507 0.028,0.268,0.511 0.035,0.271,0.515 0.043,0.274,0.519 0.052,0.277,0.523 0.060,0.281,0.526 0.068,0.284,0.530 0.076,0.287,0.533 0.084,0.290,0.536 0.092,0.293,0.539 0.100,0.296,0.542 0.108,0.299,0.545 0.117,0.303,0.547 0.125,0.306,0.549 0.133,0.309,0.552 0.141,0.312,0.553 0.149,0.315,0.555 0.158,0.319,0.556 0.166,0.322,0.558 0.175,0.325,0.558 0.183,0.328,0.559 0.191,0.331,0.560 0.200,0.335,0.560 0.209,0.338,0.559 0.217,0.341,0.559 0.226,0.344,0.558 0.235,0.348,0.557 0.243,0.351,0.556 0.252,0.354,0.554 0.261,0.357,0.551 0.270,0.361,0.549 0.279,0.364,0.546 0.287,0.367,0.544 0.295,0.370,0.541 0.303,0.374,0.539 0.311,0.377,0.536 0.318,0.380,0.533 0.326,0.384,0.531 0.333,0.387,0.528 0.340,0.390,0.525 0.347,0.394,0.523 0.354,0.397,0.520 0.361,0.401,0.517 0.368,0.404,0.515 0.374,0.407,0.512 0.381,0.411,0.509 0.387,0.414,0.507 0.394,0.418,0.504 0.400,0.421,0.501 0.406,0.424,0.498 0.412,0.428,0.496 0.419,0.431,0.493 0.425,0.435,0.490 0.431,0.438,0.487 0.437,0.442,0.484 0.442,0.445,0.481 0.448,0.448,0.479 0.454,0.452,0.476 0.460,0.455,0.473 0.466,0.459,0.470 0.471,0.462,0.467 0.477,0.466,0.464 0.483,0.469,0.461 0.488,0.473,0.458 0.494,0.476,0.455 0.499,0.480,0.452 0.505,0.483,0.449 0.510,0.487,0.446 0.516,0.490,0.443 0.521,0.494,0.440 0.527,0.497,0.437 0.532,0.501,0.434 0.537,0.504,0.431 0.543,0.508,0.428 0.548,0.511,0.425 0.553,0.515,0.422 0.559,0.518,0.418 0.564,0.522,0.415 0.569,0.525,0.412 0.574,0.529,0.409 0.580,0.532,0.405 0.585,0.536,0.402 0.590,0.539,0.399 0.595,0.543,0.395 0.600,0.547,0.392 0.606,0.550,0.389 0.611,0.554,0.385 0.616,0.557,0.382 0.621,0.561,0.378 0.626,0.564,0.375 0.631,0.568,0.371 0.637,0.571,0.367 0.642,0.575,0.364 0.647,0.578,0.361 0.652,0.582,0.358 0.657,0.586,0.355 0.661,0.589,0.353 0.666,0.593,0.351 0.671,0.596,0.350 0.675,0.600,0.349 0.680,0.604,0.348 0.684,0.607,0.348 0.689,0.611,0.347 0.693,0.614,0.348 0.698,0.618,0.348 0.702,0.622,0.349 0.706,0.625,0.350 0.710,0.629,0.351 0.715,0.633,0.353 0.719,0.636,0.354 0.723,0.640,0.357 0.727,0.644,0.359 0.731,0.647,0.361 0.735,0.651,0.364 0.739,0.655,0.367 0.743,0.658,0.370 0.747,0.662,0.373 0.751,0.666,0.377 0.755,0.670,0.381 0.759,0.673,0.385 0.763,0.677,0.389 0.766,0.681,0.393 0.770,0.685,0.398 0.774,0.688,0.402 0.778,0.692,0.407 0.781,0.696,0.412 0.785,0.699,0.417 0.789,0.703,0.422 0.792,0.707,0.428 0.796,0.711,0.433 0.800,0.715,0.439 0.803,0.718,0.445 0.807,0.722,0.451 0.810,0.726,0.457 0.814,0.730,0.463 0.817,0.733,0.470 0.820,0.737,0.476 0.824,0.741,0.483 0.827,0.745,0.489 0.830,0.749,0.496 0.834,0.753,0.503 0.837,0.756,0.510 0.840,0.760,0.517 0.843,0.764,0.525 0.847,0.768,0.532 0.850,0.772,0.540 0.853,0.776,0.547 0.856,0.780,0.555 0.859,0.783,0.563 0.862,0.787,0.571 0.865,0.791,0.579 0.868,0.795,0.587 0.871,0.799,0.595 0.873,0.803,0.603 0.876,0.807,0.611 0.879,0.811,0.620 0.882,0.815,0.628 0.884,0.818,0.637 0.887,0.822,0.646 0.890,0.826,0.655 0.892,0.830,0.664 0.895,0.834,0.672 0.897,0.838,0.682 0.900,0.842,0.691 0.902,0.846,0.700 0.904,0.850,0.709 0.907,0.854,0.719 0.909,0.858,0.728 0.911,0.862,0.738 0.913,0.866,0.747 0.915,0.870,0.757 0.917,0.874,0.767 0.919,0.878,0.776 0.921,0.882,0.786 0.923,0.886,0.796 0.925,0.890,0.806 0.927,0.894,0.816 0.928,0.898,0.827 0.930,0.902,0.837 0.932,0.907,0.847 0.933,0.911,0.858 0.935,0.915,0.868 0.936,0.919,0.879 0.937,0.923,0.889 0.939,0.927,0.900 0.940,0.931,0.911 0.941,0.935,0.922 0.942,0.939,0.933 0.943,0.944,0.943 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-CBL2.csv000066400000000000000000000110001421045507400237140ustar00rootroot000000000000000.066,0.066,0.066 0.068,0.070,0.074 0.071,0.073,0.082 0.073,0.076,0.089 0.075,0.079,0.095 0.077,0.082,0.101 0.079,0.085,0.108 0.080,0.088,0.114 0.081,0.091,0.121 0.082,0.094,0.128 0.083,0.097,0.134 0.084,0.099,0.141 0.085,0.102,0.148 0.086,0.105,0.154 0.087,0.108,0.161 0.087,0.111,0.168 0.088,0.114,0.175 0.088,0.116,0.182 0.089,0.119,0.189 0.089,0.122,0.196 0.089,0.125,0.203 0.089,0.128,0.210 0.089,0.131,0.217 0.089,0.134,0.224 0.088,0.137,0.231 0.088,0.140,0.238 0.087,0.143,0.246 0.087,0.146,0.253 0.086,0.149,0.260 0.085,0.152,0.267 0.084,0.155,0.274 0.083,0.158,0.281 0.082,0.161,0.289 0.081,0.164,0.296 0.080,0.167,0.303 0.078,0.170,0.310 0.077,0.173,0.317 0.076,0.176,0.324 0.074,0.179,0.331 0.073,0.182,0.338 0.072,0.185,0.344 0.070,0.189,0.351 0.069,0.192,0.358 0.068,0.195,0.364 0.067,0.198,0.371 0.066,0.201,0.377 0.065,0.204,0.383 0.064,0.207,0.390 0.063,0.210,0.396 0.062,0.214,0.402 0.062,0.217,0.408 0.061,0.220,0.414 0.061,0.223,0.421 0.060,0.226,0.427 0.060,0.230,0.433 0.060,0.233,0.439 0.060,0.236,0.445 0.060,0.239,0.451 0.060,0.242,0.456 0.060,0.246,0.462 0.060,0.249,0.468 0.060,0.252,0.474 0.060,0.255,0.480 0.060,0.259,0.486 0.060,0.262,0.492 0.060,0.265,0.498 0.060,0.268,0.504 0.060,0.272,0.510 0.059,0.275,0.516 0.059,0.278,0.522 0.059,0.282,0.528 0.059,0.285,0.534 0.059,0.288,0.541 0.059,0.292,0.547 0.059,0.295,0.553 0.058,0.298,0.559 0.058,0.302,0.565 0.058,0.305,0.571 0.057,0.308,0.577 0.057,0.312,0.583 0.057,0.315,0.590 0.056,0.318,0.596 0.056,0.322,0.602 0.056,0.325,0.608 0.055,0.329,0.614 0.055,0.332,0.621 0.054,0.335,0.627 0.053,0.339,0.633 0.053,0.342,0.639 0.052,0.346,0.646 0.051,0.349,0.652 0.051,0.352,0.658 0.050,0.356,0.664 0.049,0.359,0.671 0.048,0.363,0.677 0.047,0.366,0.683 0.046,0.370,0.690 0.045,0.373,0.696 0.044,0.377,0.703 0.043,0.380,0.709 0.042,0.384,0.715 0.041,0.387,0.722 0.040,0.391,0.728 0.038,0.394,0.735 0.037,0.398,0.741 0.036,0.401,0.747 0.034,0.405,0.754 0.032,0.408,0.760 0.031,0.412,0.767 0.029,0.415,0.773 0.027,0.419,0.780 0.026,0.422,0.786 0.024,0.426,0.793 0.022,0.429,0.799 0.020,0.433,0.806 0.018,0.436,0.812 0.016,0.440,0.819 0.014,0.444,0.826 0.012,0.447,0.832 0.010,0.451,0.839 0.008,0.454,0.845 0.006,0.458,0.852 0.005,0.462,0.858 0.003,0.465,0.865 0.002,0.469,0.871 0.001,0.472,0.878 0.001,0.476,0.884 0.002,0.480,0.891 0.004,0.483,0.897 0.007,0.487,0.903 0.012,0.490,0.909 0.019,0.494,0.915 0.029,0.498,0.920 0.042,0.501,0.925 0.056,0.505,0.930 0.071,0.509,0.935 0.087,0.512,0.939 0.104,0.516,0.942 0.121,0.519,0.945 0.138,0.523,0.947 0.156,0.526,0.949 0.175,0.530,0.950 0.194,0.534,0.950 0.213,0.537,0.949 0.232,0.541,0.947 0.252,0.544,0.945 0.271,0.548,0.941 0.291,0.551,0.937 0.310,0.555,0.932 0.329,0.558,0.926 0.348,0.562,0.919 0.366,0.565,0.911 0.385,0.569,0.903 0.402,0.573,0.894 0.419,0.576,0.884 0.436,0.580,0.874 0.452,0.584,0.864 0.468,0.587,0.853 0.483,0.591,0.841 0.498,0.595,0.829 0.512,0.598,0.817 0.525,0.602,0.805 0.539,0.606,0.793 0.551,0.610,0.781 0.563,0.614,0.768 0.575,0.617,0.756 0.587,0.621,0.743 0.598,0.625,0.730 0.608,0.629,0.718 0.619,0.633,0.705 0.629,0.637,0.693 0.639,0.640,0.681 0.648,0.644,0.668 0.658,0.648,0.656 0.667,0.652,0.644 0.676,0.656,0.632 0.685,0.660,0.620 0.693,0.663,0.608 0.702,0.667,0.596 0.710,0.671,0.584 0.718,0.675,0.573 0.726,0.678,0.561 0.734,0.682,0.549 0.742,0.686,0.537 0.750,0.690,0.525 0.758,0.693,0.514 0.765,0.697,0.502 0.772,0.701,0.490 0.780,0.705,0.478 0.787,0.708,0.466 0.794,0.712,0.454 0.801,0.716,0.442 0.808,0.720,0.429 0.815,0.723,0.417 0.821,0.727,0.404 0.828,0.731,0.391 0.834,0.735,0.378 0.841,0.739,0.365 0.847,0.743,0.351 0.853,0.746,0.337 0.859,0.750,0.324 0.865,0.754,0.309 0.871,0.758,0.295 0.877,0.762,0.281 0.883,0.766,0.266 0.889,0.770,0.251 0.894,0.774,0.236 0.900,0.778,0.222 0.905,0.782,0.207 0.910,0.786,0.193 0.916,0.790,0.179 0.921,0.794,0.166 0.926,0.798,0.155 0.931,0.802,0.145 0.935,0.806,0.138 0.940,0.809,0.134 0.945,0.813,0.134 0.949,0.817,0.137 0.954,0.821,0.144 0.958,0.825,0.155 0.962,0.829,0.168 0.966,0.833,0.185 0.970,0.837,0.203 0.973,0.841,0.223 0.977,0.845,0.244 0.980,0.849,0.267 0.983,0.853,0.290 0.986,0.857,0.314 0.989,0.861,0.339 0.991,0.865,0.364 0.993,0.869,0.389 0.995,0.873,0.415 0.997,0.877,0.441 0.998,0.882,0.467 0.999,0.886,0.493 1.000,0.890,0.519 1.000,0.894,0.545 1.000,0.898,0.571 1.000,0.903,0.597 1.000,0.907,0.622 1.000,0.911,0.647 1.000,0.915,0.672 0.999,0.920,0.696 0.998,0.924,0.720 0.997,0.928,0.743 0.996,0.933,0.765 0.995,0.937,0.787 0.994,0.942,0.808 0.993,0.946,0.829 0.992,0.950,0.849 0.991,0.955,0.868 0.990,0.959,0.886 0.989,0.964,0.904 0.988,0.968,0.921 0.988,0.972,0.937 0.987,0.977,0.953 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-CBTC1.csv000066400000000000000000000110001421045507400240260ustar00rootroot000000000000000.149,0.735,0.842 0.163,0.740,0.847 0.179,0.744,0.851 0.198,0.748,0.854 0.217,0.752,0.857 0.237,0.757,0.860 0.257,0.761,0.863 0.277,0.765,0.866 0.296,0.769,0.868 0.315,0.773,0.871 0.333,0.777,0.873 0.351,0.781,0.875 0.368,0.785,0.878 0.385,0.789,0.880 0.401,0.793,0.882 0.416,0.797,0.885 0.432,0.801,0.887 0.446,0.806,0.889 0.461,0.810,0.891 0.475,0.814,0.894 0.489,0.818,0.896 0.503,0.822,0.898 0.516,0.826,0.901 0.529,0.830,0.903 0.542,0.834,0.905 0.555,0.838,0.908 0.568,0.842,0.910 0.580,0.846,0.912 0.593,0.850,0.915 0.605,0.854,0.917 0.617,0.858,0.919 0.629,0.862,0.922 0.641,0.867,0.924 0.653,0.871,0.926 0.664,0.875,0.928 0.676,0.879,0.931 0.688,0.883,0.933 0.699,0.887,0.935 0.710,0.891,0.938 0.722,0.895,0.940 0.733,0.899,0.942 0.744,0.903,0.945 0.755,0.907,0.947 0.766,0.911,0.949 0.777,0.915,0.952 0.788,0.919,0.954 0.799,0.923,0.956 0.810,0.927,0.958 0.821,0.931,0.961 0.832,0.935,0.963 0.842,0.939,0.965 0.853,0.943,0.968 0.864,0.947,0.970 0.874,0.951,0.972 0.885,0.955,0.974 0.895,0.959,0.976 0.906,0.962,0.978 0.916,0.965,0.979 0.926,0.968,0.980 0.936,0.970,0.980 0.945,0.972,0.980 0.953,0.973,0.980 0.961,0.973,0.978 0.969,0.973,0.976 0.975,0.972,0.973 0.981,0.969,0.969 0.985,0.966,0.964 0.989,0.963,0.959 0.992,0.958,0.953 0.994,0.953,0.947 0.995,0.948,0.940 0.996,0.942,0.934 0.997,0.936,0.926 0.997,0.930,0.919 0.997,0.923,0.912 0.997,0.917,0.904 0.997,0.911,0.897 0.996,0.904,0.890 0.996,0.898,0.882 0.995,0.891,0.875 0.995,0.885,0.867 0.994,0.878,0.860 0.993,0.871,0.852 0.993,0.865,0.845 0.992,0.858,0.838 0.991,0.852,0.830 0.990,0.845,0.823 0.990,0.839,0.816 0.989,0.832,0.808 0.988,0.826,0.801 0.987,0.819,0.794 0.986,0.813,0.786 0.985,0.806,0.779 0.984,0.800,0.772 0.983,0.793,0.765 0.982,0.787,0.757 0.981,0.780,0.750 0.980,0.774,0.743 0.979,0.767,0.736 0.978,0.761,0.728 0.976,0.754,0.721 0.975,0.748,0.714 0.974,0.741,0.707 0.973,0.735,0.700 0.971,0.728,0.693 0.970,0.722,0.685 0.969,0.715,0.678 0.967,0.709,0.671 0.966,0.702,0.664 0.964,0.696,0.657 0.963,0.689,0.650 0.961,0.683,0.643 0.960,0.676,0.636 0.958,0.670,0.629 0.957,0.663,0.622 0.955,0.657,0.615 0.953,0.650,0.608 0.951,0.644,0.601 0.949,0.638,0.594 0.947,0.631,0.587 0.945,0.625,0.581 0.943,0.619,0.574 0.940,0.612,0.568 0.937,0.607,0.561 0.934,0.601,0.556 0.930,0.595,0.550 0.926,0.590,0.545 0.921,0.585,0.540 0.915,0.580,0.535 0.909,0.575,0.531 0.903,0.571,0.527 0.896,0.567,0.523 0.888,0.563,0.520 0.880,0.560,0.517 0.872,0.556,0.514 0.864,0.553,0.511 0.856,0.550,0.508 0.847,0.546,0.505 0.838,0.543,0.503 0.830,0.540,0.500 0.821,0.537,0.498 0.812,0.534,0.495 0.804,0.531,0.493 0.795,0.527,0.490 0.786,0.524,0.488 0.777,0.521,0.485 0.769,0.518,0.483 0.760,0.515,0.480 0.751,0.512,0.478 0.743,0.509,0.475 0.734,0.505,0.473 0.725,0.502,0.470 0.717,0.499,0.468 0.708,0.496,0.465 0.699,0.493,0.463 0.691,0.490,0.460 0.682,0.486,0.458 0.673,0.483,0.456 0.665,0.480,0.453 0.656,0.477,0.451 0.648,0.474,0.448 0.639,0.471,0.446 0.630,0.467,0.443 0.622,0.464,0.441 0.613,0.461,0.438 0.605,0.458,0.436 0.596,0.455,0.433 0.588,0.451,0.431 0.579,0.448,0.429 0.570,0.445,0.426 0.562,0.442,0.424 0.553,0.439,0.421 0.545,0.435,0.419 0.536,0.432,0.416 0.528,0.429,0.414 0.519,0.426,0.411 0.510,0.423,0.409 0.502,0.419,0.407 0.493,0.416,0.404 0.485,0.413,0.402 0.476,0.410,0.400 0.468,0.407,0.397 0.459,0.404,0.395 0.451,0.401,0.393 0.442,0.398,0.392 0.434,0.396,0.390 0.426,0.394,0.389 0.419,0.392,0.389 0.411,0.391,0.389 0.405,0.391,0.389 0.398,0.391,0.391 0.393,0.392,0.393 0.388,0.393,0.396 0.384,0.395,0.400 0.380,0.398,0.404 0.377,0.402,0.409 0.374,0.405,0.414 0.372,0.410,0.420 0.371,0.414,0.426 0.369,0.419,0.433 0.368,0.424,0.439 0.367,0.430,0.446 0.366,0.435,0.453 0.365,0.440,0.460 0.364,0.446,0.467 0.363,0.451,0.474 0.362,0.457,0.481 0.361,0.462,0.488 0.360,0.468,0.495 0.359,0.473,0.503 0.357,0.479,0.510 0.356,0.484,0.517 0.354,0.490,0.524 0.352,0.495,0.532 0.351,0.501,0.539 0.349,0.507,0.546 0.347,0.512,0.553 0.344,0.518,0.561 0.342,0.523,0.568 0.340,0.529,0.575 0.337,0.535,0.583 0.334,0.540,0.590 0.332,0.546,0.597 0.329,0.551,0.605 0.325,0.557,0.612 0.322,0.563,0.620 0.319,0.568,0.627 0.315,0.574,0.635 0.311,0.580,0.642 0.307,0.585,0.650 0.303,0.591,0.657 0.298,0.597,0.665 0.294,0.603,0.672 0.289,0.608,0.680 0.284,0.614,0.687 0.278,0.620,0.695 0.272,0.626,0.703 0.266,0.631,0.710 0.260,0.637,0.718 0.253,0.643,0.725 0.246,0.649,0.733 0.238,0.654,0.741 0.230,0.660,0.748 0.222,0.666,0.756 0.212,0.672,0.764 0.203,0.677,0.771 0.193,0.683,0.779 0.182,0.689,0.787 0.171,0.694,0.794 0.161,0.700,0.801 0.151,0.705,0.808 0.142,0.711,0.815 0.136,0.716,0.821 0.133,0.721,0.827 0.134,0.726,0.833 0.139,0.731,0.838 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-CBTC2.csv000066400000000000000000000110001421045507400240270ustar00rootroot000000000000000.985,0.981,0.982 0.989,0.979,0.978 0.992,0.976,0.974 0.995,0.973,0.970 0.997,0.968,0.964 0.998,0.964,0.958 0.999,0.958,0.952 1.000,0.953,0.945 1.000,0.947,0.938 1.000,0.940,0.931 1.000,0.934,0.924 1.000,0.928,0.916 1.000,0.921,0.909 1.000,0.915,0.901 1.000,0.908,0.894 1.000,0.902,0.886 1.000,0.895,0.879 0.999,0.889,0.871 0.999,0.882,0.864 0.998,0.876,0.857 0.998,0.869,0.849 0.997,0.863,0.842 0.997,0.856,0.834 0.996,0.850,0.827 0.996,0.843,0.819 0.995,0.836,0.812 0.995,0.830,0.805 0.994,0.823,0.797 0.993,0.817,0.790 0.992,0.810,0.783 0.992,0.804,0.775 0.991,0.797,0.768 0.990,0.791,0.761 0.989,0.784,0.753 0.988,0.778,0.746 0.987,0.771,0.739 0.986,0.765,0.732 0.985,0.758,0.724 0.984,0.752,0.717 0.983,0.745,0.710 0.982,0.739,0.703 0.981,0.732,0.695 0.980,0.726,0.688 0.979,0.719,0.681 0.977,0.713,0.674 0.976,0.706,0.667 0.975,0.700,0.660 0.974,0.693,0.653 0.972,0.687,0.646 0.971,0.680,0.638 0.970,0.673,0.631 0.968,0.667,0.624 0.967,0.660,0.617 0.965,0.654,0.610 0.964,0.648,0.604 0.962,0.641,0.597 0.961,0.635,0.590 0.959,0.629,0.584 0.958,0.624,0.578 0.957,0.619,0.573 0.956,0.614,0.568 0.955,0.611,0.564 0.954,0.608,0.561 0.954,0.606,0.559 0.953,0.605,0.558 0.953,0.605,0.558 0.954,0.606,0.560 0.954,0.609,0.562 0.955,0.612,0.565 0.956,0.616,0.569 0.957,0.620,0.574 0.958,0.625,0.580 0.960,0.631,0.586 0.961,0.637,0.592 0.963,0.643,0.599 0.964,0.649,0.605 0.966,0.656,0.612 0.967,0.662,0.619 0.969,0.669,0.626 0.970,0.675,0.633 0.971,0.682,0.640 0.973,0.688,0.647 0.974,0.695,0.655 0.975,0.701,0.662 0.977,0.708,0.669 0.978,0.714,0.676 0.979,0.721,0.683 0.980,0.727,0.690 0.981,0.734,0.697 0.982,0.740,0.705 0.984,0.747,0.712 0.985,0.754,0.719 0.986,0.760,0.726 0.987,0.767,0.734 0.988,0.773,0.741 0.988,0.780,0.748 0.989,0.786,0.755 0.990,0.793,0.763 0.991,0.799,0.770 0.992,0.806,0.777 0.993,0.812,0.785 0.993,0.819,0.792 0.994,0.825,0.799 0.995,0.832,0.807 0.995,0.838,0.814 0.996,0.845,0.821 0.997,0.851,0.829 0.997,0.858,0.836 0.998,0.864,0.844 0.998,0.871,0.851 0.999,0.877,0.858 0.999,0.884,0.866 0.999,0.890,0.873 1.000,0.897,0.881 1.000,0.903,0.888 1.000,0.910,0.896 1.000,0.916,0.903 1.000,0.923,0.911 1.000,0.929,0.918 1.000,0.936,0.926 0.999,0.942,0.933 0.998,0.948,0.940 0.996,0.953,0.947 0.994,0.958,0.953 0.992,0.963,0.959 0.988,0.967,0.964 0.984,0.970,0.969 0.979,0.973,0.973 0.973,0.975,0.977 0.966,0.976,0.979 0.959,0.976,0.981 0.950,0.975,0.982 0.941,0.973,0.983 0.932,0.971,0.982 0.922,0.969,0.982 0.912,0.966,0.981 0.902,0.963,0.979 0.891,0.959,0.978 0.881,0.956,0.976 0.870,0.952,0.974 0.859,0.948,0.972 0.848,0.944,0.970 0.837,0.940,0.968 0.826,0.936,0.965 0.815,0.932,0.963 0.804,0.928,0.961 0.793,0.924,0.959 0.782,0.920,0.957 0.771,0.916,0.955 0.760,0.912,0.953 0.748,0.908,0.950 0.737,0.905,0.948 0.725,0.901,0.946 0.714,0.897,0.944 0.702,0.893,0.942 0.691,0.889,0.940 0.679,0.885,0.938 0.667,0.881,0.935 0.655,0.877,0.933 0.643,0.873,0.931 0.631,0.869,0.929 0.618,0.865,0.927 0.606,0.861,0.925 0.593,0.857,0.923 0.581,0.853,0.920 0.568,0.849,0.918 0.555,0.845,0.916 0.542,0.841,0.914 0.528,0.837,0.912 0.515,0.833,0.910 0.501,0.829,0.907 0.487,0.825,0.905 0.472,0.821,0.903 0.457,0.817,0.901 0.442,0.813,0.899 0.427,0.809,0.897 0.411,0.805,0.895 0.395,0.801,0.892 0.378,0.797,0.890 0.360,0.793,0.888 0.342,0.789,0.886 0.324,0.785,0.884 0.305,0.782,0.882 0.285,0.778,0.880 0.265,0.774,0.878 0.246,0.771,0.876 0.227,0.768,0.875 0.209,0.766,0.874 0.193,0.764,0.872 0.181,0.762,0.872 0.174,0.762,0.871 0.171,0.761,0.871 0.175,0.762,0.871 0.184,0.763,0.872 0.197,0.764,0.873 0.213,0.766,0.874 0.231,0.769,0.875 0.250,0.772,0.877 0.270,0.775,0.879 0.289,0.779,0.880 0.309,0.782,0.882 0.328,0.786,0.884 0.346,0.790,0.887 0.364,0.794,0.889 0.381,0.798,0.891 0.398,0.802,0.893 0.414,0.806,0.895 0.430,0.810,0.897 0.446,0.814,0.899 0.461,0.818,0.901 0.475,0.822,0.904 0.490,0.826,0.906 0.504,0.830,0.908 0.518,0.834,0.910 0.531,0.838,0.912 0.545,0.842,0.914 0.558,0.846,0.917 0.571,0.850,0.919 0.584,0.854,0.921 0.596,0.858,0.923 0.609,0.862,0.925 0.621,0.866,0.927 0.633,0.870,0.929 0.646,0.874,0.932 0.658,0.878,0.934 0.670,0.882,0.936 0.681,0.886,0.938 0.693,0.890,0.940 0.705,0.893,0.942 0.716,0.897,0.944 0.728,0.901,0.947 0.739,0.905,0.949 0.751,0.909,0.951 0.762,0.913,0.953 0.773,0.917,0.955 0.785,0.921,0.957 0.796,0.925,0.959 0.807,0.929,0.962 0.818,0.933,0.964 0.829,0.937,0.966 0.840,0.941,0.968 0.851,0.945,0.970 0.861,0.949,0.972 0.872,0.953,0.974 0.883,0.957,0.976 0.893,0.960,0.978 0.904,0.964,0.980 0.914,0.968,0.982 0.924,0.971,0.984 0.934,0.974,0.985 0.943,0.977,0.986 0.952,0.979,0.987 0.960,0.981,0.987 0.967,0.982,0.987 0.974,0.983,0.986 0.980,0.982,0.984 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-CBTD1.csv000066400000000000000000000110001421045507400240270ustar00rootroot000000000000000.161,0.791,0.905 0.179,0.792,0.906 0.195,0.794,0.907 0.210,0.795,0.907 0.224,0.797,0.908 0.237,0.798,0.908 0.250,0.799,0.909 0.261,0.801,0.909 0.273,0.802,0.910 0.284,0.804,0.911 0.294,0.805,0.911 0.304,0.807,0.912 0.314,0.808,0.912 0.324,0.810,0.913 0.333,0.811,0.913 0.342,0.813,0.914 0.351,0.814,0.915 0.359,0.816,0.915 0.368,0.817,0.916 0.376,0.819,0.916 0.384,0.820,0.917 0.392,0.822,0.917 0.400,0.823,0.918 0.408,0.825,0.919 0.415,0.826,0.919 0.423,0.828,0.920 0.430,0.829,0.920 0.437,0.831,0.921 0.444,0.832,0.921 0.451,0.834,0.922 0.458,0.835,0.923 0.465,0.837,0.923 0.472,0.838,0.924 0.479,0.840,0.924 0.485,0.841,0.925 0.492,0.843,0.925 0.498,0.844,0.926 0.505,0.845,0.927 0.511,0.847,0.927 0.518,0.848,0.928 0.524,0.850,0.928 0.530,0.851,0.929 0.536,0.853,0.929 0.542,0.854,0.930 0.549,0.856,0.930 0.555,0.857,0.931 0.561,0.859,0.932 0.566,0.860,0.932 0.572,0.862,0.933 0.578,0.863,0.933 0.584,0.865,0.934 0.590,0.866,0.934 0.596,0.867,0.935 0.601,0.869,0.936 0.607,0.870,0.936 0.613,0.872,0.937 0.618,0.873,0.937 0.624,0.875,0.938 0.629,0.876,0.938 0.635,0.878,0.939 0.640,0.879,0.939 0.646,0.881,0.940 0.651,0.882,0.941 0.657,0.884,0.941 0.662,0.885,0.942 0.667,0.886,0.942 0.673,0.888,0.943 0.678,0.889,0.943 0.683,0.891,0.944 0.689,0.892,0.945 0.694,0.894,0.945 0.699,0.895,0.946 0.704,0.897,0.946 0.710,0.898,0.947 0.715,0.899,0.947 0.720,0.901,0.948 0.725,0.902,0.948 0.730,0.904,0.949 0.735,0.905,0.950 0.740,0.907,0.950 0.746,0.908,0.951 0.751,0.910,0.951 0.756,0.911,0.952 0.761,0.912,0.952 0.766,0.914,0.953 0.771,0.915,0.953 0.776,0.917,0.954 0.781,0.918,0.955 0.786,0.920,0.955 0.791,0.921,0.956 0.795,0.923,0.956 0.800,0.924,0.957 0.805,0.925,0.957 0.810,0.927,0.958 0.815,0.928,0.958 0.820,0.930,0.959 0.825,0.931,0.960 0.830,0.933,0.960 0.834,0.934,0.961 0.839,0.936,0.961 0.844,0.937,0.962 0.849,0.938,0.962 0.854,0.940,0.963 0.858,0.941,0.963 0.863,0.943,0.964 0.868,0.944,0.965 0.873,0.946,0.965 0.878,0.947,0.966 0.882,0.948,0.966 0.887,0.950,0.967 0.892,0.951,0.967 0.896,0.953,0.968 0.901,0.954,0.968 0.906,0.956,0.969 0.911,0.957,0.969 0.915,0.958,0.970 0.920,0.960,0.970 0.925,0.961,0.971 0.929,0.962,0.971 0.934,0.964,0.972 0.938,0.965,0.972 0.943,0.966,0.972 0.947,0.967,0.972 0.951,0.968,0.972 0.956,0.968,0.972 0.959,0.968,0.971 0.963,0.968,0.970 0.966,0.968,0.969 0.969,0.967,0.967 0.972,0.966,0.966 0.974,0.965,0.964 0.976,0.963,0.961 0.978,0.961,0.959 0.979,0.959,0.956 0.980,0.957,0.953 0.981,0.954,0.950 0.982,0.952,0.947 0.983,0.949,0.944 0.984,0.947,0.941 0.984,0.944,0.938 0.985,0.941,0.934 0.985,0.939,0.931 0.986,0.936,0.928 0.986,0.933,0.925 0.987,0.931,0.922 0.987,0.928,0.918 0.988,0.925,0.915 0.988,0.922,0.912 0.988,0.920,0.909 0.989,0.917,0.906 0.989,0.914,0.903 0.990,0.912,0.899 0.990,0.909,0.896 0.991,0.906,0.893 0.991,0.904,0.890 0.991,0.901,0.887 0.992,0.898,0.884 0.992,0.895,0.880 0.992,0.893,0.877 0.993,0.890,0.874 0.993,0.887,0.871 0.993,0.885,0.868 0.994,0.882,0.865 0.994,0.879,0.861 0.994,0.877,0.858 0.995,0.874,0.855 0.995,0.871,0.852 0.995,0.869,0.849 0.995,0.866,0.846 0.996,0.863,0.843 0.996,0.860,0.839 0.996,0.858,0.836 0.996,0.855,0.833 0.997,0.852,0.830 0.997,0.850,0.827 0.997,0.847,0.824 0.997,0.844,0.821 0.998,0.842,0.817 0.998,0.839,0.814 0.998,0.836,0.811 0.998,0.833,0.808 0.998,0.831,0.805 0.998,0.828,0.802 0.999,0.825,0.799 0.999,0.823,0.796 0.999,0.820,0.793 0.999,0.817,0.789 0.999,0.815,0.786 0.999,0.812,0.783 0.999,0.809,0.780 0.999,0.806,0.777 1.000,0.804,0.774 1.000,0.801,0.771 1.000,0.798,0.768 1.000,0.796,0.765 1.000,0.793,0.762 1.000,0.790,0.759 1.000,0.787,0.755 1.000,0.785,0.752 1.000,0.782,0.749 1.000,0.779,0.746 1.000,0.777,0.743 1.000,0.774,0.740 1.000,0.771,0.737 1.000,0.769,0.734 1.000,0.766,0.731 1.000,0.763,0.728 1.000,0.760,0.725 1.000,0.758,0.722 1.000,0.755,0.719 1.000,0.752,0.716 1.000,0.750,0.713 1.000,0.747,0.709 1.000,0.744,0.706 1.000,0.741,0.703 1.000,0.739,0.700 1.000,0.736,0.697 1.000,0.733,0.694 0.999,0.731,0.691 0.999,0.728,0.688 0.999,0.725,0.685 0.999,0.722,0.682 0.999,0.720,0.679 0.999,0.717,0.676 0.999,0.714,0.673 0.999,0.711,0.670 0.998,0.709,0.667 0.998,0.706,0.664 0.998,0.703,0.661 0.998,0.701,0.658 0.998,0.698,0.655 0.998,0.695,0.652 0.997,0.692,0.649 0.997,0.690,0.646 0.997,0.687,0.643 0.997,0.684,0.640 0.997,0.681,0.637 0.996,0.679,0.634 0.996,0.676,0.631 0.996,0.673,0.628 0.996,0.670,0.625 0.996,0.668,0.622 0.995,0.665,0.619 0.995,0.662,0.616 0.995,0.659,0.613 0.995,0.657,0.610 0.994,0.654,0.607 0.994,0.651,0.604 0.994,0.648,0.601 0.993,0.646,0.598 0.993,0.643,0.595 0.993,0.640,0.592 0.993,0.637,0.589 0.992,0.635,0.586 0.992,0.632,0.583 0.992,0.629,0.580 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-CBTL1.csv000066400000000000000000000110001421045507400240370ustar00rootroot000000000000000.066,0.066,0.066 0.078,0.067,0.065 0.089,0.069,0.065 0.099,0.070,0.064 0.108,0.071,0.064 0.116,0.072,0.063 0.124,0.073,0.063 0.133,0.074,0.062 0.141,0.075,0.061 0.149,0.075,0.061 0.157,0.075,0.060 0.165,0.075,0.060 0.173,0.075,0.059 0.181,0.075,0.058 0.190,0.075,0.058 0.198,0.075,0.057 0.206,0.074,0.057 0.214,0.074,0.056 0.222,0.073,0.055 0.230,0.072,0.055 0.238,0.071,0.054 0.246,0.070,0.053 0.254,0.069,0.053 0.262,0.068,0.052 0.270,0.066,0.052 0.278,0.065,0.051 0.286,0.063,0.050 0.294,0.061,0.050 0.302,0.059,0.049 0.310,0.057,0.049 0.318,0.054,0.048 0.326,0.052,0.048 0.333,0.049,0.047 0.341,0.046,0.047 0.349,0.043,0.047 0.357,0.040,0.047 0.364,0.036,0.047 0.372,0.033,0.047 0.379,0.030,0.047 0.386,0.027,0.048 0.393,0.024,0.048 0.401,0.021,0.049 0.408,0.018,0.049 0.414,0.016,0.050 0.421,0.014,0.051 0.428,0.012,0.052 0.435,0.010,0.053 0.441,0.009,0.054 0.447,0.007,0.056 0.454,0.006,0.057 0.460,0.005,0.058 0.466,0.005,0.060 0.473,0.004,0.061 0.479,0.004,0.063 0.485,0.004,0.064 0.491,0.004,0.065 0.497,0.004,0.067 0.503,0.004,0.068 0.509,0.004,0.070 0.515,0.005,0.071 0.521,0.005,0.072 0.527,0.006,0.074 0.533,0.007,0.075 0.539,0.007,0.077 0.545,0.008,0.078 0.551,0.009,0.080 0.557,0.009,0.081 0.563,0.010,0.083 0.569,0.011,0.084 0.575,0.012,0.086 0.581,0.013,0.087 0.588,0.014,0.089 0.594,0.015,0.090 0.600,0.016,0.092 0.606,0.017,0.093 0.612,0.018,0.095 0.618,0.019,0.096 0.624,0.020,0.098 0.631,0.021,0.099 0.637,0.022,0.101 0.643,0.023,0.103 0.649,0.025,0.104 0.655,0.026,0.106 0.662,0.027,0.107 0.668,0.028,0.109 0.674,0.030,0.110 0.680,0.031,0.112 0.687,0.032,0.113 0.693,0.034,0.115 0.699,0.035,0.117 0.705,0.037,0.118 0.712,0.038,0.120 0.718,0.040,0.121 0.724,0.041,0.123 0.731,0.043,0.124 0.737,0.044,0.126 0.743,0.046,0.128 0.750,0.047,0.129 0.756,0.049,0.131 0.763,0.050,0.132 0.769,0.052,0.134 0.775,0.053,0.136 0.782,0.055,0.137 0.788,0.056,0.139 0.795,0.057,0.140 0.801,0.059,0.142 0.808,0.060,0.144 0.814,0.062,0.145 0.821,0.063,0.147 0.827,0.065,0.149 0.834,0.066,0.150 0.840,0.068,0.152 0.847,0.069,0.153 0.853,0.071,0.155 0.860,0.072,0.157 0.866,0.074,0.158 0.873,0.075,0.160 0.879,0.077,0.162 0.886,0.079,0.164 0.892,0.081,0.165 0.899,0.083,0.167 0.905,0.085,0.169 0.912,0.087,0.171 0.918,0.090,0.173 0.924,0.093,0.175 0.930,0.097,0.177 0.936,0.101,0.179 0.942,0.105,0.181 0.948,0.110,0.184 0.953,0.116,0.187 0.958,0.122,0.190 0.963,0.130,0.193 0.968,0.137,0.197 0.972,0.146,0.201 0.976,0.155,0.205 0.979,0.165,0.210 0.982,0.176,0.215 0.984,0.187,0.220 0.986,0.199,0.226 0.988,0.211,0.233 0.988,0.224,0.240 0.989,0.236,0.247 0.988,0.250,0.255 0.987,0.263,0.263 0.986,0.277,0.272 0.983,0.290,0.281 0.981,0.304,0.291 0.978,0.318,0.301 0.974,0.331,0.311 0.970,0.345,0.322 0.965,0.358,0.333 0.960,0.371,0.344 0.954,0.384,0.356 0.948,0.397,0.368 0.942,0.410,0.380 0.935,0.422,0.392 0.929,0.435,0.404 0.921,0.447,0.417 0.914,0.458,0.429 0.906,0.470,0.442 0.898,0.481,0.454 0.889,0.493,0.467 0.881,0.504,0.480 0.872,0.514,0.493 0.863,0.525,0.506 0.853,0.535,0.519 0.843,0.546,0.532 0.833,0.556,0.545 0.823,0.566,0.558 0.812,0.576,0.571 0.801,0.586,0.585 0.789,0.595,0.598 0.777,0.605,0.611 0.765,0.614,0.625 0.752,0.624,0.638 0.739,0.633,0.651 0.725,0.642,0.665 0.710,0.651,0.678 0.696,0.660,0.692 0.680,0.669,0.705 0.664,0.677,0.719 0.647,0.686,0.732 0.630,0.695,0.745 0.612,0.703,0.759 0.593,0.711,0.772 0.573,0.720,0.785 0.552,0.728,0.798 0.531,0.736,0.811 0.509,0.744,0.824 0.485,0.751,0.837 0.461,0.759,0.849 0.437,0.766,0.861 0.411,0.773,0.872 0.385,0.780,0.884 0.358,0.787,0.894 0.330,0.794,0.905 0.302,0.800,0.914 0.274,0.806,0.924 0.246,0.812,0.932 0.218,0.818,0.940 0.192,0.823,0.947 0.169,0.829,0.954 0.149,0.834,0.960 0.136,0.838,0.966 0.131,0.843,0.970 0.134,0.847,0.975 0.146,0.851,0.978 0.163,0.855,0.982 0.184,0.859,0.984 0.207,0.862,0.986 0.232,0.866,0.988 0.257,0.869,0.990 0.282,0.872,0.991 0.307,0.875,0.992 0.331,0.878,0.992 0.354,0.881,0.993 0.377,0.884,0.993 0.400,0.886,0.993 0.421,0.889,0.993 0.442,0.892,0.993 0.463,0.894,0.993 0.483,0.897,0.992 0.502,0.899,0.992 0.521,0.902,0.992 0.539,0.905,0.991 0.557,0.907,0.991 0.574,0.909,0.991 0.591,0.912,0.990 0.608,0.914,0.990 0.624,0.917,0.989 0.640,0.919,0.989 0.655,0.922,0.989 0.671,0.924,0.988 0.686,0.927,0.988 0.701,0.929,0.987 0.715,0.931,0.987 0.730,0.934,0.986 0.744,0.936,0.986 0.758,0.938,0.985 0.772,0.941,0.985 0.785,0.943,0.985 0.799,0.945,0.984 0.812,0.948,0.984 0.826,0.950,0.983 0.839,0.952,0.983 0.852,0.955,0.982 0.865,0.957,0.982 0.878,0.959,0.981 0.890,0.962,0.981 0.903,0.964,0.980 0.916,0.966,0.980 0.928,0.968,0.979 0.940,0.971,0.979 0.953,0.973,0.978 0.965,0.975,0.978 0.977,0.977,0.977 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-CBTL2.csv000066400000000000000000000110001421045507400240400ustar00rootroot000000000000000.066,0.066,0.066 0.077,0.068,0.066 0.086,0.069,0.066 0.095,0.071,0.066 0.103,0.072,0.066 0.111,0.074,0.066 0.118,0.075,0.067 0.125,0.076,0.067 0.132,0.078,0.067 0.139,0.078,0.067 0.146,0.079,0.068 0.152,0.080,0.068 0.159,0.081,0.069 0.166,0.082,0.069 0.173,0.083,0.070 0.179,0.084,0.070 0.186,0.084,0.070 0.192,0.085,0.071 0.199,0.086,0.072 0.205,0.087,0.072 0.212,0.087,0.073 0.218,0.088,0.074 0.225,0.089,0.074 0.231,0.090,0.075 0.237,0.090,0.076 0.244,0.091,0.076 0.250,0.092,0.077 0.256,0.093,0.078 0.262,0.094,0.079 0.268,0.094,0.080 0.274,0.095,0.081 0.280,0.096,0.082 0.286,0.097,0.082 0.292,0.098,0.083 0.298,0.099,0.084 0.304,0.100,0.085 0.310,0.101,0.086 0.316,0.102,0.088 0.322,0.103,0.089 0.327,0.105,0.090 0.333,0.106,0.091 0.339,0.107,0.093 0.344,0.109,0.094 0.350,0.110,0.095 0.355,0.112,0.097 0.361,0.113,0.098 0.366,0.115,0.100 0.371,0.117,0.101 0.376,0.118,0.103 0.382,0.120,0.105 0.387,0.122,0.107 0.392,0.124,0.108 0.397,0.126,0.110 0.402,0.128,0.112 0.407,0.131,0.114 0.411,0.133,0.116 0.416,0.135,0.119 0.421,0.138,0.121 0.425,0.140,0.123 0.430,0.143,0.125 0.434,0.146,0.128 0.439,0.149,0.130 0.443,0.152,0.133 0.447,0.155,0.135 0.451,0.158,0.138 0.455,0.161,0.141 0.459,0.164,0.144 0.463,0.168,0.147 0.467,0.171,0.150 0.470,0.175,0.153 0.474,0.178,0.156 0.477,0.182,0.159 0.480,0.186,0.163 0.483,0.190,0.166 0.486,0.194,0.170 0.489,0.198,0.173 0.492,0.202,0.177 0.495,0.207,0.181 0.497,0.211,0.185 0.500,0.215,0.189 0.502,0.220,0.193 0.504,0.224,0.197 0.506,0.229,0.202 0.508,0.234,0.206 0.509,0.239,0.211 0.510,0.244,0.216 0.512,0.249,0.220 0.513,0.254,0.225 0.513,0.259,0.230 0.514,0.264,0.236 0.514,0.270,0.241 0.514,0.275,0.247 0.514,0.281,0.252 0.514,0.286,0.258 0.514,0.292,0.264 0.513,0.297,0.270 0.513,0.303,0.276 0.513,0.308,0.281 0.512,0.314,0.287 0.511,0.319,0.293 0.511,0.324,0.299 0.510,0.330,0.305 0.509,0.335,0.311 0.509,0.340,0.317 0.508,0.345,0.323 0.507,0.351,0.329 0.506,0.356,0.335 0.505,0.361,0.341 0.504,0.366,0.348 0.503,0.371,0.354 0.501,0.377,0.360 0.500,0.382,0.366 0.499,0.387,0.372 0.497,0.392,0.379 0.496,0.397,0.385 0.494,0.402,0.391 0.493,0.407,0.398 0.491,0.412,0.404 0.489,0.417,0.410 0.487,0.422,0.417 0.485,0.427,0.423 0.483,0.433,0.430 0.481,0.438,0.436 0.479,0.443,0.443 0.477,0.448,0.449 0.475,0.453,0.456 0.472,0.458,0.462 0.470,0.463,0.469 0.467,0.468,0.476 0.464,0.473,0.482 0.461,0.478,0.489 0.458,0.483,0.496 0.455,0.488,0.502 0.452,0.493,0.509 0.449,0.498,0.516 0.445,0.503,0.523 0.442,0.508,0.530 0.438,0.513,0.537 0.434,0.517,0.543 0.430,0.522,0.550 0.426,0.527,0.557 0.421,0.532,0.564 0.417,0.537,0.571 0.412,0.542,0.578 0.407,0.547,0.585 0.402,0.552,0.592 0.397,0.557,0.599 0.392,0.562,0.606 0.386,0.567,0.613 0.380,0.572,0.621 0.374,0.577,0.628 0.368,0.582,0.635 0.361,0.587,0.642 0.354,0.592,0.649 0.346,0.597,0.657 0.339,0.602,0.664 0.331,0.607,0.671 0.322,0.612,0.679 0.313,0.617,0.686 0.304,0.622,0.693 0.294,0.627,0.701 0.283,0.631,0.708 0.271,0.636,0.716 0.259,0.641,0.723 0.247,0.646,0.730 0.234,0.651,0.738 0.221,0.656,0.745 0.208,0.661,0.752 0.195,0.665,0.758 0.182,0.670,0.765 0.169,0.675,0.771 0.156,0.679,0.777 0.142,0.684,0.784 0.129,0.688,0.790 0.116,0.693,0.795 0.102,0.697,0.801 0.089,0.701,0.807 0.076,0.706,0.812 0.064,0.710,0.817 0.053,0.714,0.822 0.043,0.718,0.827 0.036,0.722,0.832 0.032,0.726,0.837 0.031,0.730,0.842 0.034,0.734,0.846 0.042,0.738,0.851 0.051,0.742,0.855 0.062,0.746,0.859 0.075,0.750,0.863 0.088,0.754,0.867 0.101,0.758,0.871 0.115,0.762,0.875 0.129,0.765,0.878 0.142,0.769,0.882 0.156,0.773,0.885 0.170,0.776,0.889 0.184,0.780,0.892 0.198,0.784,0.895 0.211,0.787,0.898 0.225,0.791,0.901 0.239,0.794,0.904 0.252,0.798,0.907 0.266,0.801,0.909 0.279,0.805,0.912 0.293,0.808,0.914 0.306,0.811,0.917 0.319,0.815,0.919 0.333,0.818,0.921 0.346,0.821,0.923 0.359,0.825,0.925 0.372,0.828,0.927 0.385,0.831,0.929 0.398,0.834,0.931 0.411,0.837,0.933 0.424,0.840,0.934 0.437,0.843,0.936 0.450,0.846,0.937 0.463,0.849,0.939 0.476,0.852,0.940 0.489,0.855,0.941 0.502,0.858,0.943 0.515,0.861,0.944 0.528,0.864,0.945 0.540,0.867,0.946 0.553,0.870,0.947 0.566,0.873,0.948 0.578,0.875,0.948 0.591,0.878,0.949 0.604,0.881,0.950 0.617,0.884,0.950 0.629,0.886,0.951 0.642,0.889,0.951 0.654,0.891,0.952 0.667,0.894,0.952 0.680,0.897,0.952 0.692,0.899,0.952 0.705,0.902,0.952 0.717,0.904,0.952 0.730,0.907,0.952 0.743,0.909,0.952 0.755,0.911,0.952 0.768,0.914,0.952 0.780,0.916,0.952 0.793,0.918,0.951 0.805,0.921,0.951 0.818,0.923,0.951 0.831,0.925,0.950 0.843,0.927,0.950 0.856,0.929,0.949 0.868,0.931,0.948 0.881,0.934,0.948 0.893,0.936,0.947 0.906,0.938,0.946 0.918,0.940,0.945 0.931,0.942,0.944 0.943,0.944,0.943 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D1.csv000066400000000000000000000110001421045507400234760ustar00rootroot000000000000000.128,0.316,0.858 0.147,0.320,0.859 0.164,0.325,0.860 0.179,0.329,0.861 0.194,0.333,0.862 0.207,0.338,0.863 0.220,0.342,0.864 0.232,0.346,0.864 0.243,0.351,0.865 0.254,0.355,0.866 0.264,0.360,0.867 0.274,0.364,0.868 0.284,0.369,0.869 0.294,0.373,0.869 0.303,0.377,0.870 0.312,0.382,0.871 0.321,0.386,0.872 0.329,0.391,0.873 0.337,0.395,0.874 0.346,0.400,0.874 0.354,0.404,0.875 0.362,0.409,0.876 0.369,0.414,0.877 0.377,0.418,0.878 0.384,0.423,0.878 0.392,0.427,0.879 0.399,0.432,0.880 0.406,0.436,0.881 0.413,0.441,0.882 0.420,0.446,0.882 0.427,0.450,0.883 0.434,0.455,0.884 0.440,0.460,0.885 0.447,0.464,0.886 0.454,0.469,0.886 0.460,0.474,0.887 0.467,0.478,0.888 0.473,0.483,0.889 0.479,0.488,0.889 0.485,0.492,0.890 0.492,0.497,0.891 0.498,0.502,0.892 0.504,0.507,0.892 0.510,0.511,0.893 0.516,0.516,0.894 0.522,0.521,0.895 0.528,0.526,0.895 0.534,0.530,0.896 0.540,0.535,0.897 0.545,0.540,0.898 0.551,0.545,0.898 0.557,0.550,0.899 0.563,0.555,0.900 0.568,0.559,0.900 0.574,0.564,0.901 0.580,0.569,0.902 0.585,0.574,0.903 0.591,0.579,0.903 0.596,0.584,0.904 0.602,0.588,0.905 0.607,0.593,0.905 0.613,0.598,0.906 0.618,0.603,0.907 0.623,0.608,0.907 0.629,0.613,0.908 0.634,0.618,0.909 0.639,0.623,0.909 0.645,0.628,0.910 0.650,0.633,0.911 0.655,0.638,0.911 0.660,0.643,0.912 0.666,0.648,0.913 0.671,0.653,0.913 0.676,0.658,0.914 0.681,0.663,0.915 0.686,0.668,0.915 0.691,0.673,0.916 0.697,0.678,0.917 0.702,0.683,0.917 0.707,0.688,0.918 0.712,0.693,0.918 0.717,0.698,0.919 0.722,0.703,0.920 0.727,0.708,0.920 0.732,0.713,0.921 0.737,0.718,0.921 0.742,0.723,0.922 0.747,0.728,0.923 0.752,0.733,0.923 0.757,0.738,0.924 0.762,0.743,0.924 0.766,0.748,0.925 0.771,0.754,0.926 0.776,0.759,0.926 0.781,0.764,0.927 0.786,0.769,0.927 0.791,0.774,0.928 0.796,0.779,0.928 0.801,0.784,0.929 0.805,0.789,0.929 0.810,0.795,0.930 0.815,0.800,0.930 0.820,0.805,0.931 0.825,0.810,0.932 0.829,0.815,0.932 0.834,0.821,0.933 0.839,0.826,0.933 0.844,0.831,0.934 0.848,0.836,0.934 0.853,0.841,0.935 0.858,0.846,0.935 0.863,0.852,0.935 0.867,0.857,0.936 0.872,0.862,0.936 0.877,0.867,0.936 0.881,0.872,0.937 0.886,0.876,0.937 0.890,0.881,0.936 0.895,0.885,0.936 0.899,0.890,0.936 0.903,0.893,0.935 0.908,0.897,0.934 0.912,0.900,0.932 0.916,0.903,0.931 0.919,0.905,0.928 0.923,0.906,0.926 0.926,0.907,0.923 0.929,0.907,0.919 0.932,0.907,0.915 0.935,0.905,0.911 0.937,0.904,0.906 0.939,0.901,0.900 0.941,0.898,0.895 0.942,0.895,0.888 0.944,0.891,0.882 0.945,0.887,0.875 0.946,0.882,0.869 0.946,0.877,0.861 0.947,0.872,0.854 0.947,0.866,0.847 0.948,0.861,0.840 0.948,0.855,0.832 0.948,0.849,0.825 0.948,0.843,0.817 0.948,0.837,0.810 0.948,0.831,0.802 0.948,0.825,0.794 0.948,0.819,0.787 0.948,0.813,0.779 0.947,0.807,0.772 0.947,0.800,0.764 0.947,0.794,0.757 0.947,0.788,0.749 0.946,0.782,0.742 0.946,0.776,0.734 0.945,0.770,0.727 0.945,0.764,0.719 0.944,0.758,0.712 0.944,0.752,0.704 0.943,0.746,0.697 0.942,0.740,0.689 0.942,0.733,0.682 0.941,0.727,0.675 0.940,0.721,0.667 0.940,0.715,0.660 0.939,0.709,0.653 0.938,0.703,0.645 0.937,0.697,0.638 0.936,0.691,0.631 0.935,0.685,0.623 0.934,0.679,0.616 0.933,0.673,0.609 0.932,0.666,0.601 0.931,0.660,0.594 0.930,0.654,0.587 0.929,0.648,0.580 0.928,0.642,0.573 0.927,0.636,0.565 0.925,0.630,0.558 0.924,0.624,0.551 0.923,0.618,0.544 0.922,0.612,0.537 0.920,0.605,0.530 0.919,0.599,0.522 0.917,0.593,0.515 0.916,0.587,0.508 0.915,0.581,0.501 0.913,0.575,0.494 0.912,0.569,0.487 0.910,0.562,0.480 0.908,0.556,0.473 0.907,0.550,0.466 0.905,0.544,0.459 0.904,0.538,0.452 0.902,0.531,0.445 0.900,0.525,0.438 0.898,0.519,0.431 0.897,0.513,0.424 0.895,0.507,0.417 0.893,0.500,0.410 0.891,0.494,0.403 0.889,0.488,0.396 0.887,0.481,0.390 0.885,0.475,0.383 0.884,0.469,0.376 0.882,0.463,0.369 0.880,0.456,0.362 0.878,0.450,0.355 0.875,0.443,0.349 0.873,0.437,0.342 0.871,0.431,0.335 0.869,0.424,0.328 0.867,0.418,0.322 0.865,0.411,0.315 0.863,0.405,0.308 0.860,0.398,0.301 0.858,0.391,0.295 0.856,0.385,0.288 0.854,0.378,0.281 0.851,0.372,0.275 0.849,0.365,0.268 0.846,0.358,0.261 0.844,0.351,0.255 0.842,0.344,0.248 0.839,0.337,0.241 0.837,0.331,0.235 0.834,0.323,0.228 0.832,0.316,0.222 0.829,0.309,0.215 0.827,0.302,0.208 0.824,0.295,0.202 0.821,0.287,0.195 0.819,0.280,0.188 0.816,0.272,0.182 0.813,0.265,0.175 0.811,0.257,0.169 0.808,0.249,0.162 0.805,0.241,0.155 0.803,0.233,0.148 0.800,0.224,0.142 0.797,0.216,0.135 0.794,0.207,0.128 0.791,0.198,0.121 0.788,0.189,0.115 0.786,0.179,0.108 0.783,0.169,0.101 0.780,0.159,0.093 0.777,0.148,0.086 0.774,0.137,0.079 0.771,0.124,0.071 0.768,0.111,0.064 0.765,0.096,0.056 0.762,0.080,0.047 0.759,0.061,0.039 0.756,0.036,0.030 0.752,0.008,0.022 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D10.csv000066400000000000000000000110001421045507400235560ustar00rootroot000000000000000.000,0.851,1.000 0.000,0.852,1.000 0.056,0.853,1.000 0.100,0.854,1.000 0.132,0.856,1.000 0.157,0.857,1.000 0.179,0.858,1.000 0.198,0.859,1.000 0.216,0.860,1.000 0.232,0.862,1.000 0.248,0.863,1.000 0.262,0.864,1.000 0.275,0.865,1.000 0.288,0.867,1.000 0.300,0.868,1.000 0.312,0.869,1.000 0.323,0.870,1.000 0.334,0.871,1.000 0.345,0.873,1.000 0.355,0.874,1.000 0.365,0.875,1.000 0.375,0.876,1.000 0.384,0.877,1.000 0.394,0.879,1.000 0.403,0.880,1.000 0.411,0.881,1.000 0.420,0.882,1.000 0.429,0.883,1.000 0.437,0.885,1.000 0.445,0.886,1.000 0.453,0.887,1.000 0.461,0.888,1.000 0.469,0.889,1.000 0.477,0.891,1.000 0.484,0.892,1.000 0.492,0.893,1.000 0.499,0.894,1.000 0.506,0.895,1.000 0.513,0.897,1.000 0.520,0.898,1.000 0.527,0.899,1.000 0.534,0.900,1.000 0.541,0.901,1.000 0.548,0.903,1.000 0.555,0.904,1.000 0.561,0.905,1.000 0.568,0.906,1.000 0.574,0.907,1.000 0.581,0.908,1.000 0.587,0.910,1.000 0.594,0.911,1.000 0.600,0.912,1.000 0.606,0.913,1.000 0.612,0.914,1.000 0.618,0.916,1.000 0.625,0.917,1.000 0.631,0.918,1.000 0.637,0.919,1.000 0.643,0.920,1.000 0.649,0.921,1.000 0.654,0.923,1.000 0.660,0.924,1.000 0.666,0.925,1.000 0.672,0.926,1.000 0.678,0.927,1.000 0.683,0.928,1.000 0.689,0.930,1.000 0.695,0.931,1.000 0.700,0.932,1.000 0.706,0.933,1.000 0.711,0.934,1.000 0.717,0.935,1.000 0.722,0.937,1.000 0.728,0.938,1.000 0.733,0.939,1.000 0.739,0.940,1.000 0.744,0.941,1.000 0.749,0.942,1.000 0.755,0.944,1.000 0.760,0.945,1.000 0.765,0.946,1.000 0.771,0.947,1.000 0.776,0.948,1.000 0.781,0.949,1.000 0.786,0.950,1.000 0.792,0.952,1.000 0.797,0.953,1.000 0.802,0.954,1.000 0.807,0.955,1.000 0.812,0.956,1.000 0.817,0.957,1.000 0.822,0.958,1.000 0.827,0.960,1.000 0.833,0.961,1.000 0.838,0.962,1.000 0.843,0.963,1.000 0.848,0.964,1.000 0.853,0.965,1.000 0.858,0.966,1.000 0.863,0.968,1.000 0.867,0.969,1.000 0.872,0.970,1.000 0.877,0.971,1.000 0.882,0.972,1.000 0.887,0.973,1.000 0.892,0.974,1.000 0.897,0.976,1.000 0.902,0.977,1.000 0.907,0.978,1.000 0.911,0.979,1.000 0.916,0.980,1.000 0.921,0.981,1.000 0.926,0.982,1.000 0.931,0.984,1.000 0.935,0.985,1.000 0.940,0.986,1.000 0.945,0.987,1.000 0.950,0.988,1.000 0.954,0.989,1.000 0.959,0.990,1.000 0.964,0.991,1.000 0.968,0.993,1.000 0.973,0.994,1.000 0.978,0.995,1.000 0.982,0.996,1.000 0.987,0.997,1.000 0.991,0.998,1.000 0.997,0.999,1.000 1.000,0.998,1.000 1.000,0.996,0.999 1.000,0.993,0.999 1.000,0.991,0.998 1.000,0.988,0.998 1.000,0.986,0.998 1.000,0.983,0.997 1.000,0.981,0.997 1.000,0.979,0.996 1.000,0.976,0.996 1.000,0.974,0.995 1.000,0.971,0.995 1.000,0.969,0.994 1.000,0.966,0.994 1.000,0.964,0.994 1.000,0.961,0.993 1.000,0.959,0.993 1.000,0.956,0.992 1.000,0.954,0.992 1.000,0.952,0.991 1.000,0.949,0.991 1.000,0.947,0.991 1.000,0.944,0.990 1.000,0.942,0.990 1.000,0.939,0.989 1.000,0.937,0.989 1.000,0.934,0.988 0.999,0.932,0.988 0.999,0.929,0.987 0.999,0.927,0.987 0.999,0.924,0.987 0.999,0.922,0.986 0.999,0.919,0.986 0.999,0.917,0.985 0.999,0.915,0.985 0.999,0.912,0.984 0.999,0.910,0.984 0.999,0.907,0.983 0.999,0.905,0.983 0.999,0.902,0.983 0.998,0.900,0.982 0.998,0.897,0.982 0.998,0.895,0.981 0.998,0.892,0.981 0.998,0.890,0.980 0.998,0.887,0.980 0.998,0.885,0.979 0.998,0.882,0.979 0.998,0.880,0.978 0.997,0.877,0.978 0.997,0.875,0.978 0.997,0.872,0.977 0.997,0.870,0.977 0.997,0.868,0.976 0.997,0.865,0.976 0.997,0.863,0.975 0.997,0.860,0.975 0.996,0.858,0.974 0.996,0.855,0.974 0.996,0.853,0.974 0.996,0.850,0.973 0.996,0.848,0.973 0.996,0.845,0.972 0.996,0.843,0.972 0.995,0.840,0.971 0.995,0.838,0.971 0.995,0.835,0.970 0.995,0.833,0.970 0.995,0.830,0.969 0.995,0.828,0.969 0.994,0.825,0.968 0.994,0.823,0.968 0.994,0.820,0.968 0.994,0.818,0.967 0.994,0.815,0.967 0.993,0.813,0.966 0.993,0.810,0.966 0.993,0.808,0.965 0.993,0.805,0.965 0.993,0.803,0.964 0.992,0.800,0.964 0.992,0.798,0.963 0.992,0.795,0.963 0.992,0.793,0.962 0.992,0.790,0.962 0.991,0.788,0.962 0.991,0.785,0.961 0.991,0.783,0.961 0.991,0.780,0.960 0.990,0.777,0.960 0.990,0.775,0.959 0.990,0.772,0.959 0.990,0.770,0.958 0.990,0.767,0.958 0.989,0.765,0.957 0.989,0.762,0.957 0.989,0.760,0.956 0.989,0.757,0.956 0.988,0.755,0.955 0.988,0.752,0.955 0.988,0.750,0.954 0.988,0.747,0.954 0.987,0.745,0.954 0.987,0.742,0.953 0.987,0.739,0.953 0.987,0.737,0.952 0.986,0.734,0.952 0.986,0.732,0.951 0.986,0.729,0.951 0.985,0.727,0.950 0.985,0.724,0.950 0.985,0.722,0.949 0.985,0.719,0.949 0.984,0.717,0.948 0.984,0.714,0.948 0.984,0.711,0.947 0.983,0.709,0.947 0.983,0.706,0.946 0.983,0.704,0.946 0.982,0.701,0.945 0.982,0.699,0.945 0.982,0.696,0.944 0.982,0.693,0.944 0.981,0.691,0.943 0.981,0.688,0.943 0.981,0.686,0.943 0.980,0.683,0.942 0.980,0.680,0.942 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D11.csv000066400000000000000000000110001421045507400235570ustar00rootroot000000000000000.000,0.715,1.000 0.000,0.715,1.000 0.000,0.715,1.000 0.000,0.714,1.000 0.024,0.714,1.000 0.068,0.713,0.999 0.098,0.713,0.996 0.121,0.713,0.994 0.140,0.712,0.991 0.157,0.712,0.988 0.172,0.712,0.986 0.186,0.711,0.983 0.199,0.711,0.980 0.211,0.711,0.977 0.222,0.710,0.975 0.232,0.710,0.972 0.242,0.709,0.969 0.252,0.709,0.967 0.261,0.709,0.964 0.270,0.708,0.961 0.278,0.708,0.958 0.286,0.708,0.956 0.294,0.707,0.953 0.301,0.707,0.950 0.309,0.707,0.948 0.316,0.706,0.945 0.323,0.706,0.942 0.329,0.705,0.939 0.336,0.705,0.937 0.342,0.705,0.934 0.348,0.704,0.931 0.355,0.704,0.929 0.360,0.704,0.926 0.366,0.703,0.923 0.372,0.703,0.921 0.377,0.703,0.918 0.383,0.702,0.915 0.388,0.702,0.912 0.393,0.702,0.910 0.398,0.701,0.907 0.403,0.701,0.904 0.408,0.701,0.902 0.413,0.700,0.899 0.418,0.700,0.896 0.423,0.699,0.894 0.427,0.699,0.891 0.432,0.699,0.888 0.436,0.698,0.886 0.441,0.698,0.883 0.445,0.698,0.880 0.449,0.697,0.878 0.453,0.697,0.875 0.458,0.697,0.872 0.462,0.696,0.869 0.466,0.696,0.867 0.470,0.696,0.864 0.473,0.695,0.861 0.477,0.695,0.859 0.481,0.695,0.856 0.485,0.694,0.853 0.489,0.694,0.851 0.492,0.693,0.848 0.496,0.693,0.845 0.500,0.693,0.843 0.503,0.692,0.840 0.507,0.692,0.837 0.510,0.692,0.835 0.513,0.691,0.832 0.517,0.691,0.829 0.520,0.691,0.827 0.524,0.690,0.824 0.527,0.690,0.821 0.530,0.690,0.819 0.533,0.689,0.816 0.536,0.689,0.813 0.540,0.689,0.811 0.543,0.688,0.808 0.546,0.688,0.805 0.549,0.688,0.803 0.552,0.687,0.800 0.555,0.687,0.797 0.558,0.687,0.795 0.561,0.686,0.792 0.564,0.686,0.789 0.566,0.686,0.787 0.569,0.685,0.784 0.572,0.685,0.781 0.575,0.685,0.779 0.578,0.684,0.776 0.580,0.684,0.773 0.583,0.683,0.771 0.586,0.683,0.768 0.588,0.683,0.765 0.591,0.682,0.763 0.594,0.682,0.760 0.596,0.682,0.757 0.599,0.681,0.755 0.601,0.681,0.752 0.604,0.681,0.749 0.607,0.680,0.747 0.609,0.680,0.744 0.612,0.680,0.741 0.614,0.679,0.739 0.616,0.679,0.736 0.619,0.679,0.733 0.621,0.678,0.731 0.624,0.678,0.728 0.626,0.678,0.725 0.628,0.677,0.723 0.631,0.677,0.720 0.633,0.677,0.717 0.635,0.676,0.715 0.638,0.676,0.712 0.640,0.676,0.709 0.642,0.675,0.707 0.644,0.675,0.704 0.647,0.675,0.701 0.649,0.674,0.699 0.651,0.674,0.696 0.653,0.674,0.694 0.656,0.673,0.691 0.658,0.673,0.689 0.660,0.672,0.686 0.662,0.672,0.684 0.665,0.671,0.681 0.667,0.671,0.679 0.670,0.671,0.676 0.672,0.670,0.674 0.675,0.670,0.672 0.677,0.669,0.669 0.680,0.668,0.667 0.682,0.668,0.665 0.685,0.667,0.663 0.688,0.667,0.661 0.691,0.666,0.659 0.693,0.665,0.657 0.696,0.665,0.655 0.699,0.664,0.653 0.702,0.663,0.651 0.704,0.663,0.649 0.707,0.662,0.647 0.710,0.661,0.645 0.713,0.660,0.643 0.715,0.660,0.641 0.718,0.659,0.639 0.721,0.658,0.637 0.724,0.658,0.635 0.726,0.657,0.633 0.729,0.656,0.631 0.732,0.655,0.630 0.734,0.655,0.628 0.737,0.654,0.626 0.740,0.653,0.624 0.742,0.652,0.622 0.745,0.652,0.620 0.748,0.651,0.618 0.750,0.650,0.616 0.753,0.649,0.614 0.755,0.649,0.612 0.758,0.648,0.610 0.761,0.647,0.609 0.763,0.646,0.607 0.766,0.646,0.605 0.768,0.645,0.603 0.771,0.644,0.601 0.773,0.643,0.599 0.776,0.643,0.597 0.778,0.642,0.595 0.781,0.641,0.593 0.783,0.640,0.591 0.786,0.639,0.589 0.788,0.639,0.588 0.790,0.638,0.586 0.793,0.637,0.584 0.795,0.636,0.582 0.798,0.636,0.580 0.800,0.635,0.578 0.802,0.634,0.576 0.805,0.633,0.574 0.807,0.632,0.572 0.810,0.632,0.570 0.812,0.631,0.568 0.814,0.630,0.567 0.817,0.629,0.565 0.819,0.628,0.563 0.821,0.628,0.561 0.824,0.627,0.559 0.826,0.626,0.557 0.828,0.625,0.555 0.830,0.624,0.553 0.833,0.623,0.551 0.835,0.623,0.549 0.837,0.622,0.547 0.839,0.621,0.546 0.842,0.620,0.544 0.844,0.619,0.542 0.846,0.619,0.540 0.848,0.618,0.538 0.851,0.617,0.536 0.853,0.616,0.534 0.855,0.615,0.532 0.857,0.614,0.530 0.859,0.613,0.528 0.862,0.613,0.527 0.864,0.612,0.525 0.866,0.611,0.523 0.868,0.610,0.521 0.870,0.609,0.519 0.872,0.608,0.517 0.874,0.608,0.515 0.877,0.607,0.513 0.879,0.606,0.511 0.881,0.605,0.509 0.883,0.604,0.507 0.885,0.603,0.505 0.887,0.602,0.504 0.889,0.601,0.502 0.891,0.601,0.500 0.893,0.600,0.498 0.895,0.599,0.496 0.898,0.598,0.494 0.900,0.597,0.492 0.902,0.596,0.490 0.904,0.595,0.488 0.906,0.594,0.486 0.908,0.593,0.484 0.910,0.593,0.483 0.912,0.592,0.481 0.914,0.591,0.479 0.916,0.590,0.477 0.918,0.589,0.475 0.920,0.588,0.473 0.922,0.587,0.471 0.924,0.586,0.469 0.926,0.585,0.467 0.928,0.584,0.465 0.930,0.583,0.463 0.932,0.582,0.461 0.934,0.581,0.460 0.936,0.581,0.458 0.938,0.580,0.456 0.940,0.579,0.454 0.942,0.578,0.452 0.943,0.577,0.450 0.945,0.576,0.448 0.947,0.575,0.446 0.949,0.574,0.444 0.951,0.573,0.442 0.953,0.572,0.440 0.955,0.571,0.438 0.957,0.570,0.436 0.959,0.569,0.434 0.961,0.568,0.433 0.963,0.567,0.431 0.965,0.566,0.429 0.966,0.565,0.427 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D12.csv000066400000000000000000000110001421045507400235600ustar00rootroot000000000000000.000,0.787,1.000 0.000,0.786,1.000 0.000,0.786,0.998 0.000,0.785,0.995 0.000,0.785,0.993 0.000,0.784,0.991 0.000,0.784,0.989 0.051,0.783,0.987 0.088,0.783,0.984 0.115,0.782,0.982 0.137,0.782,0.980 0.156,0.781,0.978 0.173,0.781,0.976 0.188,0.781,0.973 0.202,0.780,0.971 0.215,0.780,0.969 0.227,0.779,0.967 0.238,0.779,0.965 0.249,0.778,0.962 0.259,0.778,0.960 0.269,0.777,0.958 0.279,0.777,0.956 0.288,0.776,0.954 0.296,0.776,0.951 0.305,0.775,0.949 0.313,0.775,0.947 0.321,0.774,0.945 0.328,0.774,0.943 0.336,0.773,0.940 0.343,0.773,0.938 0.350,0.773,0.936 0.357,0.772,0.934 0.364,0.772,0.932 0.370,0.771,0.930 0.377,0.771,0.927 0.383,0.770,0.925 0.389,0.770,0.923 0.395,0.769,0.921 0.401,0.769,0.919 0.407,0.768,0.916 0.412,0.768,0.914 0.418,0.767,0.912 0.423,0.767,0.910 0.429,0.766,0.908 0.434,0.766,0.905 0.439,0.765,0.903 0.444,0.765,0.901 0.449,0.764,0.899 0.454,0.764,0.897 0.459,0.763,0.895 0.464,0.763,0.892 0.469,0.762,0.890 0.474,0.762,0.888 0.478,0.761,0.886 0.483,0.761,0.884 0.487,0.761,0.881 0.492,0.760,0.879 0.496,0.760,0.877 0.500,0.759,0.875 0.505,0.759,0.873 0.509,0.758,0.871 0.513,0.758,0.868 0.517,0.757,0.866 0.521,0.757,0.864 0.525,0.756,0.862 0.529,0.756,0.860 0.533,0.755,0.858 0.537,0.755,0.855 0.541,0.754,0.853 0.545,0.754,0.851 0.549,0.753,0.849 0.553,0.753,0.847 0.556,0.752,0.845 0.560,0.752,0.842 0.564,0.751,0.840 0.567,0.751,0.838 0.571,0.750,0.836 0.574,0.750,0.834 0.578,0.749,0.831 0.582,0.749,0.829 0.585,0.748,0.827 0.588,0.748,0.825 0.592,0.747,0.823 0.595,0.747,0.821 0.599,0.746,0.818 0.602,0.746,0.816 0.605,0.745,0.814 0.608,0.745,0.812 0.612,0.744,0.810 0.615,0.744,0.808 0.618,0.743,0.805 0.621,0.743,0.803 0.624,0.742,0.801 0.628,0.742,0.799 0.631,0.741,0.797 0.634,0.741,0.795 0.637,0.740,0.792 0.640,0.740,0.790 0.643,0.739,0.788 0.646,0.739,0.786 0.649,0.738,0.784 0.652,0.738,0.782 0.655,0.737,0.780 0.657,0.737,0.777 0.660,0.736,0.775 0.663,0.736,0.773 0.666,0.735,0.771 0.669,0.735,0.769 0.672,0.734,0.767 0.674,0.734,0.764 0.677,0.733,0.762 0.680,0.733,0.760 0.683,0.732,0.758 0.685,0.732,0.756 0.688,0.731,0.754 0.691,0.731,0.752 0.693,0.730,0.750 0.696,0.729,0.748 0.699,0.729,0.746 0.701,0.728,0.744 0.704,0.728,0.742 0.706,0.727,0.741 0.709,0.727,0.739 0.711,0.726,0.738 0.714,0.726,0.737 0.716,0.725,0.736 0.718,0.724,0.735 0.721,0.724,0.734 0.723,0.723,0.734 0.725,0.723,0.734 0.727,0.722,0.734 0.729,0.721,0.734 0.731,0.721,0.734 0.733,0.720,0.735 0.735,0.719,0.735 0.737,0.719,0.736 0.739,0.718,0.737 0.741,0.717,0.738 0.743,0.717,0.739 0.744,0.716,0.740 0.746,0.715,0.741 0.748,0.715,0.742 0.750,0.714,0.744 0.751,0.713,0.745 0.753,0.713,0.746 0.755,0.712,0.747 0.757,0.711,0.749 0.758,0.710,0.750 0.760,0.710,0.751 0.762,0.709,0.753 0.763,0.708,0.754 0.765,0.708,0.755 0.767,0.707,0.757 0.769,0.706,0.758 0.770,0.706,0.759 0.772,0.705,0.761 0.774,0.704,0.762 0.775,0.703,0.763 0.777,0.703,0.765 0.779,0.702,0.766 0.780,0.701,0.767 0.782,0.701,0.769 0.784,0.700,0.770 0.785,0.699,0.771 0.787,0.698,0.773 0.789,0.698,0.774 0.790,0.697,0.775 0.792,0.696,0.777 0.794,0.695,0.778 0.795,0.695,0.779 0.797,0.694,0.781 0.799,0.693,0.782 0.800,0.692,0.783 0.802,0.692,0.785 0.804,0.691,0.786 0.805,0.690,0.787 0.807,0.689,0.788 0.809,0.689,0.790 0.810,0.688,0.791 0.812,0.687,0.792 0.814,0.686,0.794 0.815,0.686,0.795 0.817,0.685,0.796 0.819,0.684,0.798 0.820,0.683,0.799 0.822,0.682,0.800 0.823,0.682,0.802 0.825,0.681,0.803 0.827,0.680,0.804 0.828,0.679,0.806 0.830,0.678,0.807 0.831,0.678,0.808 0.833,0.677,0.810 0.835,0.676,0.811 0.836,0.675,0.812 0.838,0.675,0.814 0.840,0.674,0.815 0.841,0.673,0.816 0.843,0.672,0.818 0.844,0.671,0.819 0.846,0.670,0.820 0.848,0.670,0.822 0.849,0.669,0.823 0.851,0.668,0.824 0.852,0.667,0.825 0.854,0.666,0.827 0.855,0.666,0.828 0.857,0.665,0.829 0.859,0.664,0.831 0.860,0.663,0.832 0.862,0.662,0.833 0.863,0.661,0.835 0.865,0.660,0.836 0.866,0.660,0.837 0.868,0.659,0.839 0.870,0.658,0.840 0.871,0.657,0.841 0.873,0.656,0.843 0.874,0.655,0.844 0.876,0.654,0.845 0.877,0.654,0.847 0.879,0.653,0.848 0.881,0.652,0.849 0.882,0.651,0.851 0.884,0.650,0.852 0.885,0.649,0.853 0.887,0.648,0.855 0.888,0.647,0.856 0.890,0.647,0.857 0.891,0.646,0.859 0.893,0.645,0.860 0.895,0.644,0.861 0.896,0.643,0.862 0.898,0.642,0.864 0.899,0.641,0.865 0.901,0.640,0.866 0.902,0.639,0.868 0.904,0.638,0.869 0.905,0.638,0.870 0.907,0.637,0.872 0.908,0.636,0.873 0.910,0.635,0.874 0.911,0.634,0.876 0.913,0.633,0.877 0.914,0.632,0.878 0.916,0.631,0.880 0.917,0.630,0.881 0.919,0.629,0.882 0.920,0.628,0.884 0.922,0.627,0.885 0.924,0.626,0.886 0.925,0.625,0.888 0.927,0.624,0.889 0.928,0.623,0.890 0.930,0.622,0.892 0.931,0.621,0.893 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D13.csv000066400000000000000000000110001421045507400235610ustar00rootroot000000000000000.066,0.176,0.408 0.070,0.181,0.416 0.074,0.186,0.424 0.078,0.191,0.433 0.083,0.196,0.441 0.087,0.200,0.449 0.091,0.205,0.458 0.095,0.210,0.466 0.099,0.215,0.475 0.103,0.220,0.483 0.107,0.225,0.491 0.111,0.230,0.500 0.114,0.235,0.508 0.118,0.241,0.516 0.122,0.246,0.525 0.126,0.251,0.533 0.129,0.256,0.542 0.133,0.261,0.550 0.136,0.267,0.558 0.140,0.272,0.567 0.143,0.277,0.575 0.147,0.283,0.583 0.150,0.288,0.591 0.154,0.294,0.600 0.157,0.299,0.608 0.160,0.305,0.616 0.163,0.310,0.624 0.166,0.316,0.633 0.169,0.321,0.641 0.172,0.327,0.649 0.175,0.333,0.657 0.178,0.338,0.665 0.181,0.344,0.673 0.184,0.350,0.681 0.186,0.356,0.689 0.189,0.362,0.697 0.191,0.368,0.705 0.194,0.374,0.713 0.196,0.380,0.721 0.198,0.386,0.729 0.200,0.392,0.736 0.202,0.398,0.744 0.204,0.404,0.752 0.206,0.410,0.759 0.207,0.416,0.767 0.209,0.423,0.774 0.210,0.429,0.781 0.211,0.436,0.789 0.212,0.442,0.796 0.213,0.449,0.803 0.213,0.455,0.810 0.214,0.462,0.817 0.214,0.468,0.824 0.214,0.475,0.831 0.214,0.482,0.838 0.213,0.489,0.844 0.212,0.496,0.851 0.211,0.502,0.857 0.210,0.509,0.863 0.208,0.517,0.869 0.206,0.524,0.875 0.205,0.531,0.881 0.203,0.538,0.887 0.200,0.545,0.892 0.199,0.552,0.898 0.197,0.560,0.903 0.196,0.567,0.908 0.195,0.574,0.913 0.194,0.581,0.917 0.195,0.588,0.922 0.196,0.596,0.926 0.198,0.603,0.931 0.201,0.610,0.935 0.206,0.617,0.939 0.211,0.624,0.942 0.217,0.631,0.946 0.224,0.637,0.950 0.231,0.644,0.953 0.240,0.651,0.956 0.249,0.658,0.959 0.259,0.665,0.962 0.270,0.671,0.965 0.281,0.678,0.967 0.293,0.684,0.970 0.305,0.691,0.972 0.317,0.697,0.974 0.330,0.704,0.976 0.343,0.710,0.978 0.356,0.716,0.979 0.370,0.723,0.981 0.384,0.729,0.982 0.398,0.735,0.983 0.412,0.741,0.984 0.427,0.747,0.985 0.441,0.753,0.985 0.456,0.759,0.986 0.471,0.765,0.986 0.486,0.771,0.986 0.502,0.777,0.986 0.517,0.783,0.986 0.532,0.788,0.986 0.548,0.794,0.985 0.563,0.800,0.985 0.579,0.805,0.984 0.595,0.811,0.983 0.610,0.816,0.982 0.626,0.822,0.981 0.641,0.827,0.979 0.657,0.833,0.978 0.672,0.838,0.977 0.687,0.844,0.975 0.702,0.849,0.974 0.717,0.855,0.972 0.732,0.860,0.970 0.747,0.865,0.969 0.761,0.871,0.967 0.776,0.876,0.965 0.789,0.881,0.963 0.803,0.887,0.961 0.816,0.892,0.959 0.828,0.897,0.957 0.840,0.902,0.955 0.850,0.906,0.952 0.859,0.910,0.949 0.866,0.914,0.945 0.872,0.917,0.941 0.875,0.919,0.937 0.876,0.921,0.932 0.875,0.922,0.926 0.871,0.922,0.921 0.865,0.921,0.914 0.857,0.920,0.908 0.848,0.918,0.901 0.836,0.916,0.894 0.824,0.913,0.886 0.810,0.910,0.879 0.796,0.906,0.871 0.781,0.902,0.864 0.766,0.899,0.856 0.750,0.895,0.848 0.734,0.891,0.841 0.718,0.886,0.833 0.702,0.882,0.825 0.685,0.878,0.818 0.669,0.874,0.810 0.653,0.870,0.802 0.636,0.865,0.795 0.620,0.861,0.787 0.603,0.856,0.779 0.587,0.852,0.772 0.570,0.848,0.764 0.554,0.843,0.756 0.537,0.838,0.748 0.521,0.834,0.740 0.505,0.829,0.732 0.489,0.824,0.724 0.473,0.819,0.716 0.458,0.814,0.707 0.442,0.809,0.699 0.427,0.804,0.690 0.412,0.799,0.682 0.397,0.794,0.673 0.383,0.789,0.664 0.368,0.783,0.655 0.354,0.778,0.647 0.341,0.772,0.638 0.327,0.767,0.628 0.314,0.761,0.619 0.301,0.756,0.610 0.288,0.750,0.601 0.276,0.745,0.591 0.264,0.739,0.581 0.252,0.733,0.572 0.241,0.727,0.562 0.230,0.721,0.552 0.220,0.715,0.542 0.211,0.709,0.532 0.201,0.703,0.522 0.193,0.697,0.512 0.185,0.691,0.502 0.178,0.685,0.492 0.171,0.679,0.481 0.165,0.673,0.471 0.160,0.667,0.460 0.155,0.660,0.450 0.152,0.654,0.439 0.149,0.648,0.428 0.146,0.641,0.418 0.145,0.635,0.407 0.143,0.629,0.396 0.142,0.622,0.386 0.142,0.616,0.375 0.141,0.609,0.365 0.141,0.603,0.355 0.140,0.597,0.345 0.140,0.590,0.335 0.139,0.584,0.325 0.139,0.577,0.315 0.138,0.571,0.306 0.137,0.564,0.297 0.136,0.558,0.288 0.135,0.552,0.279 0.134,0.545,0.271 0.132,0.539,0.263 0.131,0.533,0.255 0.129,0.526,0.247 0.127,0.520,0.239 0.125,0.514,0.232 0.123,0.507,0.225 0.121,0.501,0.217 0.119,0.495,0.211 0.116,0.488,0.204 0.114,0.482,0.197 0.112,0.476,0.191 0.109,0.470,0.184 0.107,0.463,0.178 0.104,0.457,0.172 0.101,0.451,0.166 0.099,0.445,0.160 0.096,0.438,0.154 0.093,0.432,0.149 0.090,0.426,0.143 0.087,0.420,0.138 0.084,0.414,0.132 0.082,0.408,0.127 0.078,0.402,0.122 0.075,0.395,0.117 0.072,0.389,0.112 0.069,0.383,0.108 0.066,0.377,0.103 0.063,0.371,0.098 0.060,0.365,0.094 0.057,0.359,0.089 0.053,0.353,0.085 0.050,0.347,0.081 0.047,0.341,0.076 0.043,0.335,0.072 0.040,0.329,0.068 0.037,0.324,0.064 0.033,0.318,0.060 0.030,0.312,0.057 0.027,0.306,0.053 0.024,0.300,0.049 0.021,0.294,0.045 0.019,0.289,0.042 0.016,0.283,0.038 0.013,0.277,0.035 0.011,0.271,0.032 0.009,0.265,0.029 0.007,0.260,0.026 0.005,0.254,0.023 0.004,0.248,0.020 0.002,0.243,0.016 0.001,0.237,0.013 0.001,0.232,0.009 0.000,0.226,0.006 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D1A.csv000066400000000000000000000110001421045507400235770ustar00rootroot000000000000000.092,0.161,0.447 0.093,0.165,0.458 0.095,0.170,0.469 0.096,0.174,0.481 0.098,0.178,0.492 0.099,0.182,0.504 0.101,0.187,0.515 0.102,0.191,0.527 0.103,0.196,0.538 0.104,0.200,0.550 0.106,0.204,0.562 0.107,0.209,0.573 0.108,0.213,0.585 0.109,0.218,0.597 0.111,0.222,0.609 0.112,0.227,0.621 0.113,0.231,0.633 0.114,0.236,0.645 0.115,0.240,0.657 0.116,0.245,0.669 0.118,0.249,0.681 0.119,0.254,0.693 0.121,0.259,0.704 0.122,0.263,0.716 0.124,0.268,0.728 0.127,0.273,0.740 0.129,0.278,0.751 0.133,0.282,0.762 0.137,0.287,0.773 0.141,0.292,0.784 0.147,0.297,0.794 0.153,0.302,0.804 0.159,0.307,0.813 0.167,0.312,0.822 0.175,0.317,0.830 0.184,0.322,0.838 0.194,0.328,0.845 0.203,0.333,0.852 0.214,0.338,0.858 0.224,0.344,0.864 0.235,0.349,0.870 0.246,0.355,0.875 0.257,0.361,0.879 0.268,0.366,0.884 0.279,0.372,0.888 0.290,0.378,0.892 0.300,0.384,0.896 0.311,0.390,0.899 0.321,0.395,0.903 0.332,0.401,0.906 0.342,0.407,0.910 0.352,0.413,0.913 0.361,0.419,0.916 0.371,0.425,0.920 0.380,0.431,0.923 0.390,0.437,0.926 0.399,0.443,0.930 0.408,0.449,0.933 0.417,0.455,0.936 0.426,0.462,0.939 0.434,0.468,0.943 0.443,0.474,0.946 0.452,0.480,0.949 0.460,0.486,0.952 0.469,0.492,0.956 0.477,0.498,0.959 0.485,0.505,0.962 0.494,0.511,0.965 0.502,0.517,0.968 0.510,0.523,0.970 0.518,0.530,0.973 0.526,0.536,0.976 0.535,0.543,0.978 0.543,0.549,0.980 0.551,0.555,0.982 0.559,0.562,0.984 0.567,0.568,0.986 0.576,0.575,0.987 0.584,0.582,0.988 0.592,0.588,0.989 0.600,0.595,0.990 0.608,0.602,0.990 0.617,0.608,0.991 0.625,0.615,0.991 0.633,0.622,0.990 0.641,0.629,0.990 0.649,0.636,0.990 0.657,0.643,0.989 0.665,0.650,0.989 0.672,0.657,0.988 0.680,0.664,0.987 0.688,0.671,0.986 0.696,0.678,0.985 0.703,0.685,0.985 0.711,0.692,0.984 0.718,0.699,0.983 0.726,0.706,0.982 0.733,0.713,0.981 0.740,0.720,0.980 0.747,0.727,0.979 0.755,0.734,0.978 0.762,0.742,0.977 0.769,0.749,0.976 0.776,0.756,0.975 0.783,0.763,0.973 0.790,0.770,0.972 0.797,0.778,0.971 0.804,0.785,0.970 0.810,0.792,0.969 0.817,0.799,0.968 0.824,0.806,0.967 0.831,0.814,0.965 0.837,0.821,0.964 0.844,0.828,0.963 0.851,0.835,0.961 0.857,0.841,0.960 0.864,0.848,0.958 0.871,0.854,0.956 0.877,0.861,0.953 0.883,0.866,0.951 0.890,0.872,0.948 0.896,0.876,0.944 0.902,0.881,0.940 0.909,0.884,0.936 0.914,0.887,0.931 0.920,0.889,0.926 0.926,0.889,0.920 0.931,0.890,0.913 0.936,0.889,0.906 0.941,0.887,0.898 0.946,0.884,0.889 0.950,0.880,0.881 0.954,0.876,0.871 0.958,0.871,0.861 0.961,0.865,0.851 0.965,0.858,0.841 0.968,0.851,0.830 0.970,0.844,0.820 0.973,0.836,0.809 0.975,0.828,0.798 0.977,0.820,0.786 0.979,0.811,0.775 0.980,0.802,0.764 0.982,0.794,0.753 0.983,0.785,0.742 0.984,0.776,0.730 0.986,0.767,0.719 0.987,0.758,0.708 0.988,0.748,0.697 0.988,0.739,0.686 0.989,0.730,0.675 0.990,0.721,0.664 0.990,0.712,0.652 0.991,0.703,0.641 0.991,0.694,0.630 0.991,0.684,0.619 0.991,0.675,0.609 0.991,0.666,0.598 0.991,0.657,0.587 0.991,0.647,0.576 0.991,0.638,0.565 0.991,0.629,0.554 0.990,0.619,0.543 0.990,0.610,0.533 0.989,0.601,0.522 0.989,0.591,0.511 0.988,0.582,0.501 0.987,0.572,0.490 0.986,0.563,0.480 0.985,0.554,0.470 0.983,0.544,0.459 0.982,0.535,0.449 0.980,0.525,0.439 0.978,0.516,0.429 0.976,0.507,0.419 0.974,0.498,0.410 0.971,0.488,0.400 0.969,0.479,0.391 0.966,0.470,0.382 0.963,0.461,0.373 0.959,0.453,0.364 0.956,0.444,0.356 0.952,0.435,0.347 0.948,0.426,0.339 0.944,0.418,0.330 0.940,0.409,0.322 0.936,0.401,0.314 0.931,0.392,0.306 0.927,0.384,0.299 0.922,0.375,0.291 0.917,0.367,0.283 0.912,0.358,0.276 0.908,0.350,0.268 0.903,0.341,0.260 0.898,0.333,0.253 0.893,0.324,0.245 0.888,0.315,0.238 0.883,0.306,0.230 0.878,0.297,0.223 0.873,0.288,0.215 0.868,0.279,0.208 0.863,0.270,0.200 0.858,0.260,0.193 0.853,0.251,0.186 0.848,0.241,0.178 0.843,0.231,0.171 0.837,0.221,0.164 0.832,0.211,0.156 0.827,0.200,0.149 0.821,0.190,0.142 0.816,0.179,0.135 0.810,0.168,0.128 0.804,0.156,0.121 0.798,0.145,0.114 0.792,0.133,0.108 0.786,0.121,0.101 0.779,0.109,0.095 0.772,0.097,0.090 0.765,0.085,0.084 0.757,0.073,0.079 0.750,0.061,0.074 0.742,0.049,0.070 0.733,0.037,0.065 0.724,0.027,0.062 0.715,0.019,0.058 0.706,0.012,0.056 0.697,0.007,0.053 0.687,0.004,0.051 0.677,0.002,0.049 0.667,0.001,0.047 0.657,0.001,0.045 0.646,0.001,0.044 0.636,0.002,0.043 0.625,0.003,0.042 0.615,0.004,0.041 0.604,0.006,0.040 0.594,0.008,0.039 0.583,0.009,0.038 0.573,0.011,0.037 0.562,0.013,0.036 0.552,0.014,0.034 0.542,0.016,0.033 0.531,0.017,0.032 0.521,0.019,0.030 0.511,0.020,0.029 0.500,0.021,0.027 0.490,0.022,0.026 0.480,0.023,0.024 0.470,0.024,0.023 0.460,0.025,0.021 0.450,0.026,0.019 0.440,0.026,0.017 0.430,0.027,0.015 0.420,0.027,0.013 0.410,0.028,0.011 0.400,0.028,0.009 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D2.csv000066400000000000000000000110001421045507400234770ustar00rootroot000000000000000.222,0.591,0.055 0.230,0.594,0.069 0.238,0.597,0.081 0.245,0.600,0.093 0.253,0.602,0.103 0.260,0.605,0.113 0.268,0.608,0.123 0.275,0.611,0.132 0.282,0.614,0.140 0.289,0.616,0.149 0.296,0.619,0.157 0.303,0.622,0.165 0.310,0.625,0.173 0.316,0.628,0.181 0.323,0.630,0.189 0.329,0.633,0.196 0.336,0.636,0.203 0.342,0.639,0.211 0.349,0.642,0.218 0.355,0.644,0.225 0.361,0.647,0.232 0.367,0.650,0.239 0.373,0.653,0.246 0.380,0.655,0.253 0.386,0.658,0.260 0.392,0.661,0.267 0.398,0.664,0.274 0.404,0.667,0.280 0.410,0.669,0.287 0.415,0.672,0.294 0.421,0.675,0.300 0.427,0.678,0.307 0.433,0.681,0.314 0.439,0.683,0.320 0.445,0.686,0.327 0.450,0.689,0.333 0.456,0.692,0.340 0.462,0.694,0.346 0.467,0.697,0.353 0.473,0.700,0.359 0.479,0.703,0.366 0.484,0.705,0.372 0.490,0.708,0.379 0.496,0.711,0.385 0.501,0.714,0.392 0.507,0.716,0.398 0.512,0.719,0.405 0.518,0.722,0.411 0.523,0.725,0.417 0.529,0.728,0.424 0.534,0.730,0.430 0.540,0.733,0.437 0.545,0.736,0.443 0.550,0.739,0.449 0.556,0.741,0.456 0.561,0.744,0.462 0.567,0.747,0.469 0.572,0.750,0.475 0.578,0.752,0.482 0.583,0.755,0.488 0.588,0.758,0.494 0.594,0.760,0.501 0.599,0.763,0.507 0.604,0.766,0.514 0.610,0.769,0.520 0.615,0.771,0.526 0.620,0.774,0.533 0.626,0.777,0.539 0.631,0.780,0.546 0.636,0.782,0.552 0.641,0.785,0.559 0.647,0.788,0.565 0.652,0.791,0.571 0.657,0.793,0.578 0.663,0.796,0.584 0.668,0.799,0.591 0.673,0.802,0.597 0.678,0.804,0.604 0.684,0.807,0.610 0.689,0.810,0.617 0.694,0.812,0.623 0.699,0.815,0.630 0.704,0.818,0.636 0.710,0.821,0.643 0.715,0.823,0.649 0.720,0.826,0.656 0.725,0.829,0.662 0.730,0.832,0.669 0.736,0.834,0.675 0.741,0.837,0.682 0.746,0.840,0.688 0.751,0.842,0.695 0.756,0.845,0.701 0.762,0.848,0.708 0.767,0.851,0.714 0.772,0.853,0.721 0.777,0.856,0.728 0.782,0.859,0.734 0.787,0.861,0.741 0.793,0.864,0.747 0.798,0.867,0.754 0.803,0.870,0.760 0.808,0.872,0.767 0.813,0.875,0.774 0.818,0.878,0.780 0.823,0.880,0.787 0.829,0.883,0.794 0.834,0.886,0.800 0.839,0.888,0.807 0.844,0.891,0.813 0.849,0.894,0.820 0.854,0.897,0.827 0.859,0.899,0.833 0.864,0.902,0.840 0.869,0.904,0.847 0.874,0.907,0.853 0.879,0.909,0.860 0.884,0.911,0.866 0.889,0.914,0.872 0.893,0.915,0.878 0.898,0.917,0.884 0.902,0.919,0.890 0.906,0.920,0.896 0.910,0.921,0.901 0.913,0.922,0.906 0.917,0.922,0.911 0.919,0.922,0.915 0.922,0.921,0.919 0.924,0.920,0.923 0.926,0.919,0.926 0.927,0.917,0.929 0.928,0.915,0.931 0.929,0.912,0.933 0.929,0.909,0.935 0.929,0.906,0.936 0.929,0.903,0.937 0.929,0.899,0.938 0.928,0.895,0.939 0.927,0.891,0.939 0.926,0.887,0.939 0.925,0.883,0.939 0.924,0.879,0.939 0.923,0.874,0.939 0.921,0.870,0.939 0.920,0.865,0.939 0.919,0.861,0.939 0.917,0.856,0.939 0.916,0.852,0.938 0.915,0.847,0.938 0.913,0.843,0.938 0.912,0.838,0.938 0.910,0.834,0.937 0.909,0.830,0.937 0.907,0.825,0.937 0.906,0.821,0.936 0.905,0.816,0.936 0.903,0.812,0.936 0.902,0.807,0.935 0.900,0.803,0.935 0.899,0.798,0.935 0.897,0.794,0.935 0.896,0.789,0.934 0.894,0.785,0.934 0.893,0.780,0.934 0.891,0.776,0.933 0.890,0.771,0.933 0.888,0.767,0.933 0.886,0.762,0.932 0.885,0.758,0.932 0.883,0.753,0.932 0.882,0.749,0.931 0.880,0.744,0.931 0.879,0.740,0.931 0.877,0.735,0.930 0.875,0.731,0.930 0.874,0.726,0.930 0.872,0.722,0.929 0.871,0.717,0.929 0.869,0.713,0.929 0.867,0.708,0.928 0.866,0.704,0.928 0.864,0.699,0.928 0.862,0.695,0.927 0.861,0.690,0.927 0.859,0.686,0.926 0.857,0.681,0.926 0.856,0.677,0.926 0.854,0.672,0.925 0.852,0.668,0.925 0.850,0.663,0.925 0.849,0.659,0.924 0.847,0.654,0.924 0.845,0.650,0.923 0.844,0.645,0.923 0.842,0.640,0.923 0.840,0.636,0.922 0.838,0.631,0.922 0.836,0.627,0.921 0.835,0.622,0.921 0.833,0.618,0.921 0.831,0.613,0.920 0.829,0.609,0.920 0.828,0.604,0.919 0.826,0.599,0.919 0.824,0.595,0.919 0.822,0.590,0.918 0.820,0.586,0.918 0.818,0.581,0.917 0.816,0.577,0.917 0.815,0.572,0.917 0.813,0.567,0.916 0.811,0.563,0.916 0.809,0.558,0.915 0.807,0.554,0.915 0.805,0.549,0.914 0.803,0.544,0.914 0.801,0.540,0.914 0.799,0.535,0.913 0.797,0.530,0.913 0.796,0.526,0.912 0.794,0.521,0.912 0.792,0.516,0.911 0.790,0.512,0.911 0.788,0.507,0.910 0.786,0.502,0.910 0.784,0.498,0.910 0.782,0.493,0.909 0.780,0.488,0.909 0.778,0.483,0.908 0.776,0.479,0.908 0.774,0.474,0.907 0.772,0.469,0.907 0.769,0.464,0.906 0.767,0.460,0.906 0.765,0.455,0.905 0.763,0.450,0.905 0.761,0.445,0.904 0.759,0.440,0.904 0.757,0.435,0.903 0.755,0.431,0.903 0.753,0.426,0.902 0.751,0.421,0.902 0.749,0.416,0.902 0.746,0.411,0.901 0.744,0.406,0.901 0.742,0.401,0.900 0.740,0.396,0.900 0.738,0.391,0.899 0.736,0.386,0.899 0.733,0.381,0.898 0.731,0.376,0.898 0.729,0.371,0.897 0.727,0.365,0.897 0.724,0.360,0.896 0.722,0.355,0.895 0.720,0.350,0.895 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D3.csv000066400000000000000000000110001421045507400235000ustar00rootroot000000000000000.222,0.591,0.055 0.230,0.594,0.069 0.238,0.597,0.081 0.245,0.600,0.093 0.253,0.602,0.103 0.260,0.605,0.113 0.268,0.608,0.123 0.275,0.611,0.132 0.282,0.614,0.140 0.289,0.616,0.149 0.296,0.619,0.157 0.303,0.622,0.165 0.310,0.625,0.173 0.316,0.628,0.181 0.323,0.630,0.189 0.329,0.633,0.196 0.336,0.636,0.203 0.342,0.639,0.211 0.349,0.642,0.218 0.355,0.644,0.225 0.361,0.647,0.232 0.367,0.650,0.239 0.373,0.653,0.246 0.380,0.655,0.253 0.386,0.658,0.260 0.392,0.661,0.267 0.398,0.664,0.274 0.404,0.667,0.280 0.410,0.669,0.287 0.415,0.672,0.294 0.421,0.675,0.300 0.427,0.678,0.307 0.433,0.681,0.314 0.439,0.683,0.320 0.445,0.686,0.327 0.450,0.689,0.333 0.456,0.692,0.340 0.462,0.694,0.346 0.467,0.697,0.353 0.473,0.700,0.359 0.479,0.703,0.366 0.484,0.705,0.372 0.490,0.708,0.379 0.496,0.711,0.385 0.501,0.714,0.392 0.507,0.716,0.398 0.512,0.719,0.405 0.518,0.722,0.411 0.523,0.725,0.417 0.529,0.728,0.424 0.534,0.730,0.430 0.540,0.733,0.437 0.545,0.736,0.443 0.550,0.739,0.449 0.556,0.741,0.456 0.561,0.744,0.462 0.567,0.747,0.469 0.572,0.750,0.475 0.578,0.752,0.482 0.583,0.755,0.488 0.588,0.758,0.494 0.594,0.760,0.501 0.599,0.763,0.507 0.604,0.766,0.514 0.610,0.769,0.520 0.615,0.771,0.526 0.620,0.774,0.533 0.626,0.777,0.539 0.631,0.780,0.546 0.636,0.782,0.552 0.641,0.785,0.559 0.647,0.788,0.565 0.652,0.791,0.571 0.657,0.793,0.578 0.663,0.796,0.584 0.668,0.799,0.591 0.673,0.802,0.597 0.678,0.804,0.604 0.684,0.807,0.610 0.689,0.810,0.617 0.694,0.812,0.623 0.699,0.815,0.630 0.704,0.818,0.636 0.710,0.821,0.643 0.715,0.823,0.649 0.720,0.826,0.656 0.725,0.829,0.662 0.730,0.832,0.669 0.736,0.834,0.675 0.741,0.837,0.682 0.746,0.840,0.688 0.751,0.842,0.695 0.756,0.845,0.701 0.762,0.848,0.708 0.767,0.851,0.714 0.772,0.853,0.721 0.777,0.856,0.728 0.782,0.859,0.734 0.787,0.861,0.741 0.793,0.864,0.747 0.798,0.867,0.754 0.803,0.870,0.760 0.808,0.872,0.767 0.813,0.875,0.774 0.818,0.878,0.780 0.823,0.880,0.787 0.829,0.883,0.794 0.834,0.886,0.800 0.839,0.888,0.807 0.844,0.891,0.813 0.849,0.894,0.820 0.854,0.897,0.827 0.859,0.899,0.833 0.865,0.902,0.840 0.870,0.904,0.846 0.875,0.907,0.852 0.880,0.909,0.859 0.885,0.911,0.865 0.890,0.913,0.870 0.895,0.915,0.876 0.900,0.917,0.881 0.904,0.919,0.886 0.909,0.920,0.890 0.914,0.921,0.894 0.918,0.921,0.897 0.922,0.921,0.900 0.926,0.921,0.902 0.930,0.920,0.903 0.934,0.919,0.904 0.937,0.917,0.904 0.940,0.915,0.903 0.943,0.913,0.901 0.946,0.910,0.899 0.948,0.907,0.897 0.950,0.904,0.893 0.952,0.900,0.890 0.954,0.896,0.886 0.956,0.892,0.881 0.957,0.888,0.876 0.959,0.884,0.872 0.960,0.879,0.866 0.961,0.874,0.861 0.962,0.870,0.856 0.964,0.865,0.850 0.965,0.860,0.845 0.966,0.856,0.839 0.966,0.851,0.834 0.967,0.846,0.828 0.968,0.841,0.823 0.969,0.837,0.817 0.970,0.832,0.812 0.971,0.827,0.806 0.971,0.822,0.800 0.972,0.817,0.795 0.973,0.813,0.789 0.973,0.808,0.784 0.974,0.803,0.778 0.975,0.798,0.773 0.975,0.793,0.767 0.976,0.789,0.762 0.976,0.784,0.756 0.977,0.779,0.751 0.977,0.774,0.745 0.978,0.769,0.740 0.978,0.765,0.734 0.979,0.760,0.729 0.979,0.755,0.723 0.979,0.750,0.718 0.980,0.745,0.712 0.980,0.740,0.707 0.980,0.736,0.701 0.980,0.731,0.696 0.981,0.726,0.691 0.981,0.721,0.685 0.981,0.716,0.680 0.981,0.711,0.674 0.981,0.707,0.669 0.981,0.702,0.664 0.982,0.697,0.658 0.982,0.692,0.653 0.982,0.687,0.647 0.982,0.682,0.642 0.982,0.677,0.637 0.982,0.672,0.631 0.982,0.668,0.626 0.981,0.663,0.621 0.981,0.658,0.615 0.981,0.653,0.610 0.981,0.648,0.605 0.981,0.643,0.600 0.981,0.638,0.594 0.981,0.633,0.589 0.980,0.628,0.584 0.980,0.623,0.578 0.980,0.618,0.573 0.980,0.613,0.568 0.979,0.608,0.563 0.979,0.603,0.557 0.979,0.598,0.552 0.978,0.593,0.547 0.978,0.588,0.542 0.977,0.583,0.536 0.977,0.578,0.531 0.977,0.573,0.526 0.976,0.568,0.521 0.976,0.563,0.516 0.975,0.558,0.511 0.975,0.553,0.505 0.974,0.548,0.500 0.974,0.543,0.495 0.973,0.538,0.490 0.972,0.532,0.485 0.972,0.527,0.480 0.971,0.522,0.475 0.970,0.517,0.469 0.970,0.512,0.464 0.969,0.507,0.459 0.968,0.501,0.454 0.968,0.496,0.449 0.967,0.491,0.444 0.966,0.486,0.439 0.965,0.480,0.434 0.965,0.475,0.429 0.964,0.470,0.424 0.963,0.464,0.419 0.962,0.459,0.414 0.961,0.453,0.409 0.960,0.448,0.404 0.960,0.442,0.399 0.959,0.437,0.394 0.958,0.431,0.389 0.957,0.426,0.384 0.956,0.420,0.379 0.955,0.415,0.374 0.954,0.409,0.369 0.953,0.403,0.364 0.952,0.398,0.359 0.951,0.392,0.354 0.950,0.386,0.349 0.949,0.380,0.344 0.947,0.374,0.340 0.946,0.368,0.335 0.945,0.363,0.330 0.944,0.356,0.325 0.943,0.350,0.320 0.942,0.344,0.315 0.941,0.338,0.310 0.939,0.332,0.306 0.938,0.325,0.301 0.937,0.319,0.296 0.936,0.312,0.291 0.934,0.306,0.286 0.933,0.299,0.281 0.932,0.292,0.277 0.931,0.286,0.272 0.929,0.279,0.267 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D4.csv000066400000000000000000000110001421045507400235010ustar00rootroot000000000000000.097,0.507,0.982 0.103,0.504,0.974 0.109,0.500,0.967 0.114,0.497,0.959 0.118,0.493,0.951 0.123,0.490,0.943 0.127,0.487,0.936 0.131,0.483,0.928 0.135,0.480,0.920 0.138,0.476,0.913 0.141,0.473,0.905 0.145,0.469,0.897 0.148,0.466,0.890 0.150,0.463,0.882 0.153,0.459,0.874 0.156,0.456,0.867 0.158,0.452,0.859 0.161,0.449,0.852 0.163,0.445,0.844 0.165,0.442,0.837 0.167,0.439,0.829 0.169,0.435,0.822 0.171,0.432,0.814 0.172,0.429,0.807 0.174,0.425,0.799 0.176,0.422,0.792 0.177,0.418,0.784 0.179,0.415,0.777 0.180,0.412,0.769 0.181,0.408,0.762 0.182,0.405,0.754 0.184,0.402,0.747 0.185,0.398,0.740 0.186,0.395,0.732 0.187,0.392,0.725 0.188,0.389,0.718 0.188,0.385,0.710 0.189,0.382,0.703 0.190,0.379,0.696 0.191,0.375,0.688 0.191,0.372,0.681 0.192,0.369,0.674 0.192,0.365,0.667 0.193,0.362,0.659 0.193,0.359,0.652 0.194,0.356,0.645 0.194,0.352,0.638 0.194,0.349,0.631 0.194,0.346,0.624 0.195,0.343,0.616 0.195,0.340,0.609 0.195,0.336,0.602 0.195,0.333,0.595 0.195,0.330,0.588 0.195,0.327,0.581 0.195,0.323,0.574 0.195,0.320,0.567 0.195,0.317,0.560 0.195,0.314,0.553 0.194,0.311,0.546 0.194,0.308,0.539 0.194,0.304,0.532 0.194,0.301,0.525 0.193,0.298,0.518 0.193,0.295,0.511 0.192,0.292,0.504 0.192,0.289,0.497 0.192,0.286,0.491 0.191,0.282,0.484 0.190,0.279,0.477 0.190,0.276,0.470 0.189,0.273,0.463 0.189,0.270,0.457 0.188,0.267,0.450 0.187,0.264,0.443 0.187,0.261,0.436 0.186,0.258,0.430 0.185,0.255,0.423 0.184,0.252,0.416 0.183,0.249,0.410 0.182,0.245,0.403 0.182,0.242,0.396 0.181,0.239,0.390 0.180,0.236,0.383 0.179,0.233,0.377 0.178,0.230,0.370 0.177,0.227,0.364 0.176,0.224,0.357 0.174,0.221,0.351 0.173,0.218,0.344 0.172,0.215,0.338 0.171,0.213,0.331 0.170,0.210,0.325 0.168,0.207,0.319 0.167,0.204,0.312 0.166,0.201,0.306 0.165,0.198,0.300 0.163,0.195,0.293 0.162,0.192,0.287 0.160,0.189,0.281 0.159,0.186,0.275 0.158,0.183,0.268 0.156,0.180,0.262 0.155,0.178,0.256 0.153,0.175,0.250 0.151,0.172,0.244 0.150,0.169,0.238 0.148,0.166,0.232 0.147,0.163,0.226 0.145,0.161,0.220 0.143,0.158,0.214 0.142,0.155,0.208 0.140,0.152,0.202 0.139,0.150,0.196 0.137,0.147,0.190 0.135,0.144,0.184 0.134,0.142,0.179 0.133,0.139,0.173 0.132,0.137,0.168 0.131,0.134,0.163 0.130,0.132,0.158 0.129,0.130,0.153 0.129,0.128,0.148 0.129,0.127,0.144 0.129,0.125,0.140 0.130,0.124,0.137 0.132,0.123,0.133 0.133,0.123,0.131 0.135,0.122,0.128 0.138,0.122,0.126 0.141,0.122,0.124 0.144,0.122,0.123 0.148,0.123,0.122 0.152,0.124,0.121 0.156,0.125,0.121 0.161,0.126,0.121 0.165,0.127,0.121 0.170,0.129,0.122 0.176,0.130,0.122 0.181,0.132,0.123 0.186,0.133,0.124 0.192,0.135,0.125 0.197,0.137,0.126 0.203,0.139,0.127 0.209,0.140,0.128 0.214,0.142,0.129 0.220,0.144,0.130 0.226,0.146,0.131 0.232,0.148,0.132 0.237,0.149,0.133 0.243,0.151,0.135 0.249,0.153,0.136 0.255,0.155,0.137 0.260,0.156,0.138 0.266,0.158,0.139 0.272,0.160,0.140 0.278,0.162,0.142 0.284,0.163,0.143 0.289,0.165,0.144 0.295,0.167,0.145 0.301,0.169,0.146 0.307,0.170,0.147 0.313,0.172,0.148 0.319,0.174,0.150 0.325,0.176,0.151 0.330,0.177,0.152 0.336,0.179,0.153 0.342,0.181,0.154 0.348,0.182,0.155 0.354,0.184,0.157 0.360,0.186,0.158 0.366,0.187,0.159 0.372,0.189,0.160 0.378,0.191,0.161 0.384,0.192,0.162 0.390,0.194,0.163 0.396,0.196,0.165 0.402,0.197,0.166 0.408,0.199,0.167 0.414,0.201,0.168 0.420,0.202,0.169 0.426,0.204,0.170 0.432,0.206,0.172 0.438,0.207,0.173 0.444,0.209,0.174 0.450,0.210,0.175 0.456,0.212,0.176 0.463,0.214,0.177 0.469,0.215,0.179 0.475,0.217,0.180 0.481,0.218,0.181 0.487,0.220,0.182 0.493,0.222,0.183 0.499,0.223,0.184 0.506,0.225,0.185 0.512,0.226,0.187 0.518,0.228,0.188 0.524,0.229,0.189 0.530,0.231,0.190 0.537,0.232,0.191 0.543,0.234,0.192 0.549,0.236,0.194 0.556,0.237,0.195 0.562,0.239,0.196 0.568,0.240,0.197 0.574,0.242,0.198 0.581,0.243,0.199 0.587,0.245,0.201 0.593,0.246,0.202 0.600,0.248,0.203 0.606,0.249,0.204 0.612,0.251,0.205 0.619,0.252,0.206 0.625,0.254,0.208 0.632,0.255,0.209 0.638,0.257,0.210 0.644,0.258,0.211 0.651,0.260,0.212 0.657,0.261,0.213 0.664,0.263,0.215 0.670,0.264,0.216 0.677,0.266,0.217 0.683,0.267,0.218 0.690,0.268,0.219 0.696,0.270,0.220 0.703,0.271,0.222 0.709,0.273,0.223 0.716,0.274,0.224 0.722,0.276,0.225 0.729,0.277,0.226 0.735,0.278,0.227 0.742,0.280,0.229 0.748,0.281,0.230 0.755,0.283,0.231 0.762,0.284,0.232 0.768,0.286,0.233 0.775,0.287,0.234 0.782,0.288,0.236 0.788,0.290,0.237 0.795,0.291,0.238 0.801,0.292,0.239 0.808,0.294,0.240 0.815,0.295,0.241 0.821,0.297,0.243 0.828,0.298,0.244 0.835,0.299,0.245 0.842,0.301,0.246 0.848,0.302,0.247 0.855,0.303,0.249 0.862,0.305,0.250 0.868,0.306,0.251 0.875,0.307,0.252 0.882,0.309,0.253 0.889,0.310,0.254 0.896,0.311,0.256 0.902,0.313,0.257 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D6.csv000066400000000000000000000110001421045507400235030ustar00rootroot000000000000000.057,0.580,0.981 0.066,0.575,0.973 0.074,0.571,0.966 0.082,0.567,0.958 0.089,0.563,0.950 0.095,0.559,0.943 0.101,0.555,0.935 0.106,0.551,0.927 0.111,0.547,0.919 0.116,0.543,0.912 0.120,0.539,0.904 0.124,0.535,0.896 0.128,0.531,0.889 0.132,0.526,0.881 0.135,0.522,0.874 0.138,0.518,0.866 0.141,0.514,0.858 0.144,0.510,0.851 0.147,0.506,0.843 0.150,0.502,0.836 0.152,0.498,0.828 0.155,0.494,0.821 0.157,0.490,0.813 0.159,0.486,0.806 0.161,0.482,0.798 0.163,0.478,0.791 0.165,0.474,0.783 0.167,0.470,0.776 0.168,0.466,0.768 0.170,0.462,0.761 0.172,0.458,0.754 0.173,0.455,0.746 0.174,0.451,0.739 0.176,0.447,0.732 0.177,0.443,0.724 0.178,0.439,0.717 0.179,0.435,0.710 0.180,0.431,0.702 0.181,0.427,0.695 0.182,0.423,0.688 0.183,0.419,0.680 0.184,0.415,0.673 0.184,0.412,0.666 0.185,0.408,0.659 0.186,0.404,0.651 0.186,0.400,0.644 0.187,0.396,0.637 0.187,0.392,0.630 0.188,0.388,0.623 0.188,0.385,0.616 0.188,0.381,0.609 0.189,0.377,0.601 0.189,0.373,0.594 0.189,0.369,0.587 0.189,0.366,0.580 0.189,0.362,0.573 0.189,0.358,0.566 0.189,0.354,0.559 0.189,0.351,0.552 0.189,0.347,0.545 0.189,0.343,0.538 0.189,0.339,0.531 0.189,0.336,0.524 0.189,0.332,0.517 0.188,0.328,0.510 0.188,0.324,0.504 0.188,0.321,0.497 0.187,0.317,0.490 0.187,0.313,0.483 0.186,0.310,0.476 0.186,0.306,0.469 0.186,0.302,0.463 0.185,0.299,0.456 0.184,0.295,0.449 0.184,0.291,0.442 0.183,0.288,0.436 0.182,0.284,0.429 0.182,0.281,0.422 0.181,0.277,0.416 0.180,0.273,0.409 0.180,0.270,0.402 0.179,0.266,0.396 0.178,0.263,0.389 0.177,0.259,0.383 0.176,0.255,0.376 0.175,0.252,0.370 0.174,0.248,0.363 0.173,0.245,0.357 0.172,0.241,0.350 0.171,0.238,0.344 0.170,0.234,0.337 0.169,0.231,0.331 0.168,0.227,0.324 0.166,0.224,0.318 0.165,0.220,0.312 0.164,0.217,0.305 0.163,0.214,0.299 0.162,0.210,0.293 0.160,0.207,0.287 0.159,0.203,0.280 0.157,0.200,0.274 0.156,0.196,0.268 0.155,0.193,0.262 0.153,0.190,0.256 0.152,0.186,0.249 0.150,0.183,0.243 0.149,0.180,0.237 0.147,0.176,0.231 0.146,0.173,0.225 0.144,0.170,0.219 0.142,0.166,0.213 0.141,0.163,0.207 0.139,0.160,0.201 0.138,0.157,0.195 0.136,0.154,0.190 0.135,0.151,0.184 0.133,0.148,0.178 0.132,0.145,0.173 0.130,0.142,0.167 0.129,0.139,0.162 0.128,0.137,0.157 0.127,0.135,0.152 0.127,0.133,0.148 0.127,0.131,0.143 0.127,0.129,0.139 0.127,0.128,0.135 0.127,0.128,0.132 0.128,0.127,0.129 0.130,0.127,0.126 0.131,0.127,0.124 0.133,0.128,0.122 0.135,0.129,0.120 0.138,0.130,0.119 0.141,0.131,0.118 0.144,0.133,0.117 0.147,0.135,0.116 0.151,0.137,0.116 0.155,0.140,0.116 0.159,0.142,0.116 0.162,0.145,0.116 0.167,0.148,0.116 0.171,0.151,0.116 0.175,0.154,0.117 0.179,0.157,0.117 0.183,0.160,0.117 0.188,0.163,0.118 0.192,0.166,0.118 0.196,0.169,0.119 0.201,0.172,0.119 0.205,0.175,0.120 0.209,0.178,0.120 0.214,0.181,0.121 0.218,0.184,0.121 0.222,0.187,0.121 0.227,0.191,0.122 0.231,0.194,0.122 0.236,0.197,0.123 0.240,0.200,0.123 0.244,0.203,0.123 0.249,0.207,0.124 0.253,0.210,0.124 0.257,0.213,0.125 0.262,0.216,0.125 0.266,0.219,0.125 0.271,0.223,0.126 0.275,0.226,0.126 0.280,0.229,0.126 0.284,0.232,0.126 0.288,0.236,0.127 0.293,0.239,0.127 0.297,0.242,0.127 0.302,0.245,0.128 0.306,0.249,0.128 0.311,0.252,0.128 0.315,0.255,0.128 0.320,0.259,0.129 0.324,0.262,0.129 0.329,0.265,0.129 0.333,0.269,0.129 0.338,0.272,0.129 0.342,0.275,0.129 0.347,0.279,0.130 0.352,0.282,0.130 0.356,0.286,0.130 0.361,0.289,0.130 0.365,0.292,0.130 0.370,0.296,0.130 0.374,0.299,0.130 0.379,0.303,0.131 0.384,0.306,0.131 0.388,0.309,0.131 0.393,0.313,0.131 0.397,0.316,0.131 0.402,0.320,0.131 0.407,0.323,0.131 0.411,0.327,0.131 0.416,0.330,0.131 0.421,0.334,0.131 0.425,0.337,0.131 0.430,0.341,0.131 0.435,0.344,0.131 0.439,0.348,0.131 0.444,0.351,0.131 0.449,0.355,0.130 0.453,0.358,0.130 0.458,0.362,0.130 0.463,0.365,0.130 0.468,0.369,0.130 0.472,0.372,0.130 0.477,0.376,0.130 0.482,0.379,0.129 0.487,0.383,0.129 0.491,0.386,0.129 0.496,0.390,0.129 0.501,0.394,0.129 0.506,0.397,0.128 0.510,0.401,0.128 0.515,0.404,0.128 0.520,0.408,0.127 0.525,0.412,0.127 0.530,0.415,0.127 0.534,0.419,0.126 0.539,0.422,0.126 0.544,0.426,0.126 0.549,0.430,0.125 0.554,0.433,0.125 0.559,0.437,0.124 0.564,0.441,0.124 0.568,0.444,0.123 0.573,0.448,0.123 0.578,0.452,0.122 0.583,0.455,0.122 0.588,0.459,0.121 0.593,0.463,0.121 0.598,0.466,0.120 0.603,0.470,0.120 0.608,0.474,0.119 0.613,0.477,0.118 0.618,0.481,0.118 0.622,0.485,0.117 0.627,0.489,0.116 0.632,0.492,0.115 0.637,0.496,0.115 0.642,0.500,0.114 0.647,0.503,0.113 0.652,0.507,0.112 0.657,0.511,0.111 0.662,0.515,0.110 0.667,0.518,0.109 0.672,0.522,0.108 0.677,0.526,0.107 0.682,0.530,0.106 0.687,0.534,0.105 0.692,0.537,0.104 0.697,0.541,0.103 0.702,0.545,0.102 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D7.csv000066400000000000000000000110001421045507400235040ustar00rootroot000000000000000.078,0.193,0.758 0.095,0.196,0.757 0.110,0.199,0.756 0.123,0.202,0.754 0.135,0.205,0.753 0.146,0.208,0.751 0.156,0.210,0.750 0.166,0.213,0.749 0.175,0.216,0.747 0.183,0.219,0.746 0.191,0.222,0.745 0.199,0.225,0.743 0.206,0.228,0.742 0.213,0.230,0.740 0.220,0.233,0.739 0.227,0.236,0.738 0.233,0.239,0.736 0.240,0.242,0.735 0.246,0.245,0.733 0.251,0.248,0.732 0.257,0.250,0.731 0.263,0.253,0.729 0.268,0.256,0.728 0.273,0.259,0.727 0.278,0.262,0.725 0.283,0.265,0.724 0.288,0.268,0.722 0.293,0.271,0.721 0.298,0.273,0.719 0.303,0.276,0.718 0.307,0.279,0.717 0.312,0.282,0.715 0.316,0.285,0.714 0.320,0.288,0.712 0.324,0.291,0.711 0.329,0.294,0.710 0.333,0.296,0.708 0.337,0.299,0.707 0.341,0.302,0.705 0.345,0.305,0.704 0.348,0.308,0.703 0.352,0.311,0.701 0.356,0.314,0.700 0.360,0.317,0.698 0.363,0.320,0.697 0.367,0.322,0.695 0.370,0.325,0.694 0.374,0.328,0.692 0.377,0.331,0.691 0.381,0.334,0.690 0.384,0.337,0.688 0.387,0.340,0.687 0.391,0.343,0.685 0.394,0.346,0.684 0.397,0.349,0.682 0.400,0.351,0.681 0.403,0.354,0.679 0.406,0.357,0.678 0.410,0.360,0.676 0.413,0.363,0.675 0.416,0.366,0.674 0.418,0.369,0.672 0.421,0.372,0.671 0.424,0.375,0.669 0.427,0.378,0.668 0.430,0.381,0.666 0.433,0.384,0.665 0.436,0.387,0.663 0.438,0.389,0.662 0.441,0.392,0.660 0.444,0.395,0.659 0.447,0.398,0.657 0.449,0.401,0.656 0.452,0.404,0.654 0.454,0.407,0.653 0.457,0.410,0.651 0.460,0.413,0.650 0.462,0.416,0.648 0.465,0.419,0.647 0.467,0.422,0.645 0.470,0.425,0.644 0.472,0.428,0.642 0.474,0.431,0.641 0.477,0.434,0.639 0.479,0.437,0.637 0.482,0.440,0.636 0.484,0.443,0.634 0.486,0.445,0.633 0.489,0.448,0.631 0.491,0.451,0.630 0.493,0.454,0.628 0.495,0.457,0.627 0.498,0.460,0.625 0.500,0.463,0.624 0.502,0.466,0.622 0.504,0.469,0.620 0.506,0.472,0.619 0.509,0.475,0.617 0.511,0.478,0.616 0.513,0.481,0.614 0.515,0.484,0.612 0.517,0.487,0.611 0.519,0.490,0.609 0.521,0.493,0.608 0.523,0.496,0.606 0.525,0.499,0.604 0.527,0.502,0.603 0.529,0.505,0.601 0.531,0.508,0.600 0.533,0.511,0.598 0.535,0.514,0.596 0.537,0.517,0.595 0.539,0.520,0.593 0.541,0.523,0.591 0.543,0.526,0.590 0.544,0.529,0.588 0.546,0.532,0.586 0.548,0.535,0.585 0.550,0.538,0.583 0.552,0.541,0.581 0.554,0.544,0.580 0.555,0.547,0.578 0.557,0.550,0.576 0.559,0.553,0.575 0.561,0.556,0.573 0.562,0.559,0.571 0.564,0.562,0.569 0.566,0.565,0.568 0.569,0.568,0.566 0.573,0.570,0.563 0.577,0.573,0.561 0.581,0.575,0.559 0.585,0.578,0.556 0.589,0.580,0.554 0.593,0.582,0.552 0.597,0.585,0.549 0.601,0.587,0.547 0.605,0.590,0.544 0.609,0.592,0.542 0.613,0.595,0.539 0.616,0.597,0.537 0.620,0.599,0.535 0.624,0.602,0.532 0.628,0.604,0.530 0.632,0.607,0.527 0.635,0.609,0.525 0.639,0.612,0.522 0.643,0.614,0.520 0.646,0.616,0.517 0.650,0.619,0.515 0.654,0.621,0.512 0.657,0.624,0.510 0.661,0.626,0.507 0.665,0.629,0.505 0.668,0.631,0.502 0.672,0.634,0.500 0.676,0.636,0.497 0.679,0.639,0.495 0.683,0.641,0.492 0.686,0.644,0.489 0.690,0.646,0.487 0.693,0.649,0.484 0.697,0.651,0.482 0.700,0.654,0.479 0.704,0.656,0.476 0.707,0.659,0.474 0.711,0.661,0.471 0.714,0.664,0.468 0.718,0.666,0.466 0.721,0.669,0.463 0.724,0.671,0.460 0.728,0.674,0.457 0.731,0.676,0.455 0.735,0.679,0.452 0.738,0.681,0.449 0.741,0.684,0.446 0.745,0.686,0.443 0.748,0.689,0.441 0.752,0.691,0.438 0.755,0.694,0.435 0.758,0.696,0.432 0.761,0.699,0.429 0.765,0.702,0.426 0.768,0.704,0.423 0.771,0.707,0.420 0.775,0.709,0.417 0.778,0.712,0.414 0.781,0.714,0.411 0.784,0.717,0.408 0.788,0.719,0.405 0.791,0.722,0.402 0.794,0.725,0.399 0.797,0.727,0.396 0.801,0.730,0.393 0.804,0.732,0.390 0.807,0.735,0.387 0.810,0.737,0.383 0.814,0.740,0.380 0.817,0.743,0.377 0.820,0.745,0.374 0.823,0.748,0.370 0.826,0.750,0.367 0.829,0.753,0.364 0.833,0.756,0.360 0.836,0.758,0.357 0.839,0.761,0.353 0.842,0.763,0.350 0.845,0.766,0.346 0.848,0.769,0.343 0.851,0.771,0.339 0.855,0.774,0.336 0.858,0.777,0.332 0.861,0.779,0.328 0.864,0.782,0.324 0.867,0.784,0.320 0.870,0.787,0.317 0.873,0.790,0.313 0.876,0.792,0.309 0.879,0.795,0.305 0.883,0.798,0.301 0.886,0.800,0.297 0.889,0.803,0.292 0.892,0.805,0.288 0.895,0.808,0.284 0.898,0.811,0.279 0.901,0.813,0.275 0.904,0.816,0.270 0.907,0.819,0.266 0.910,0.821,0.261 0.913,0.824,0.256 0.916,0.827,0.251 0.919,0.829,0.246 0.922,0.832,0.241 0.925,0.835,0.236 0.928,0.837,0.231 0.931,0.840,0.225 0.934,0.843,0.220 0.937,0.845,0.214 0.940,0.848,0.208 0.943,0.851,0.202 0.946,0.853,0.195 0.949,0.856,0.189 0.952,0.859,0.182 0.955,0.861,0.175 0.958,0.864,0.168 0.961,0.867,0.160 0.964,0.870,0.152 0.967,0.872,0.143 0.970,0.875,0.134 0.973,0.878,0.124 0.976,0.880,0.114 0.979,0.883,0.102 0.982,0.886,0.090 0.985,0.888,0.075 0.988,0.891,0.058 0.991,0.894,0.036 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D8.csv000066400000000000000000000110001421045507400235050ustar00rootroot000000000000000.000,0.165,0.844 0.000,0.167,0.840 0.000,0.169,0.837 0.000,0.171,0.833 0.000,0.173,0.829 0.000,0.175,0.826 0.000,0.177,0.822 0.000,0.179,0.819 0.026,0.181,0.815 0.053,0.183,0.811 0.073,0.185,0.808 0.090,0.187,0.804 0.104,0.189,0.801 0.116,0.191,0.797 0.128,0.193,0.794 0.138,0.195,0.790 0.147,0.197,0.786 0.156,0.199,0.783 0.164,0.201,0.779 0.172,0.203,0.776 0.179,0.205,0.772 0.186,0.207,0.768 0.192,0.208,0.765 0.199,0.210,0.761 0.205,0.212,0.758 0.210,0.214,0.754 0.216,0.216,0.751 0.221,0.218,0.747 0.226,0.220,0.744 0.231,0.222,0.740 0.236,0.223,0.736 0.240,0.225,0.733 0.245,0.227,0.729 0.249,0.229,0.726 0.253,0.231,0.722 0.257,0.233,0.719 0.261,0.235,0.715 0.265,0.236,0.712 0.269,0.238,0.708 0.272,0.240,0.705 0.276,0.242,0.701 0.279,0.244,0.697 0.283,0.246,0.694 0.286,0.247,0.690 0.289,0.249,0.687 0.292,0.251,0.683 0.295,0.253,0.680 0.298,0.255,0.676 0.301,0.256,0.673 0.304,0.258,0.669 0.306,0.260,0.666 0.309,0.262,0.662 0.311,0.264,0.659 0.314,0.265,0.655 0.316,0.267,0.652 0.319,0.269,0.648 0.321,0.271,0.645 0.323,0.273,0.641 0.326,0.274,0.638 0.328,0.276,0.634 0.330,0.278,0.631 0.332,0.280,0.627 0.334,0.282,0.623 0.336,0.283,0.620 0.338,0.285,0.616 0.340,0.287,0.613 0.342,0.289,0.609 0.343,0.290,0.606 0.345,0.292,0.602 0.347,0.294,0.599 0.349,0.296,0.595 0.350,0.297,0.592 0.352,0.299,0.588 0.353,0.301,0.585 0.355,0.303,0.581 0.356,0.304,0.578 0.358,0.306,0.574 0.359,0.308,0.571 0.361,0.310,0.567 0.362,0.311,0.564 0.363,0.313,0.560 0.365,0.315,0.557 0.366,0.316,0.553 0.367,0.318,0.550 0.368,0.320,0.546 0.369,0.322,0.543 0.371,0.323,0.539 0.372,0.325,0.536 0.373,0.327,0.532 0.374,0.329,0.529 0.375,0.330,0.525 0.376,0.332,0.522 0.377,0.334,0.518 0.378,0.335,0.515 0.378,0.337,0.511 0.379,0.339,0.508 0.380,0.340,0.505 0.381,0.342,0.501 0.382,0.344,0.498 0.382,0.346,0.494 0.383,0.347,0.490 0.384,0.349,0.487 0.384,0.351,0.483 0.385,0.352,0.480 0.386,0.354,0.476 0.386,0.356,0.473 0.387,0.357,0.469 0.387,0.359,0.466 0.388,0.361,0.462 0.388,0.362,0.459 0.389,0.364,0.455 0.389,0.366,0.452 0.390,0.368,0.448 0.390,0.369,0.445 0.390,0.371,0.441 0.391,0.373,0.438 0.391,0.374,0.434 0.391,0.376,0.431 0.392,0.378,0.427 0.392,0.379,0.424 0.392,0.381,0.420 0.392,0.383,0.417 0.393,0.384,0.413 0.393,0.386,0.409 0.393,0.388,0.406 0.393,0.389,0.402 0.393,0.391,0.399 0.393,0.393,0.395 0.397,0.393,0.392 0.403,0.393,0.390 0.410,0.392,0.387 0.417,0.392,0.385 0.423,0.391,0.382 0.429,0.391,0.380 0.436,0.390,0.377 0.442,0.390,0.375 0.448,0.389,0.373 0.454,0.388,0.370 0.460,0.388,0.368 0.466,0.387,0.365 0.472,0.387,0.363 0.478,0.386,0.360 0.484,0.385,0.358 0.490,0.385,0.355 0.496,0.384,0.353 0.501,0.383,0.350 0.507,0.383,0.348 0.513,0.382,0.346 0.518,0.381,0.343 0.524,0.380,0.341 0.529,0.380,0.338 0.535,0.379,0.336 0.540,0.378,0.333 0.545,0.377,0.331 0.551,0.376,0.328 0.556,0.376,0.326 0.561,0.375,0.323 0.567,0.374,0.321 0.572,0.373,0.318 0.577,0.372,0.316 0.582,0.371,0.313 0.587,0.370,0.311 0.593,0.369,0.308 0.598,0.368,0.306 0.603,0.367,0.303 0.608,0.366,0.301 0.613,0.365,0.298 0.618,0.364,0.296 0.623,0.363,0.293 0.628,0.362,0.291 0.633,0.361,0.288 0.638,0.360,0.286 0.643,0.359,0.283 0.648,0.358,0.281 0.653,0.356,0.278 0.657,0.355,0.275 0.662,0.354,0.273 0.667,0.353,0.270 0.672,0.351,0.268 0.677,0.350,0.265 0.682,0.349,0.263 0.686,0.347,0.260 0.691,0.346,0.257 0.696,0.345,0.255 0.701,0.343,0.252 0.705,0.342,0.250 0.710,0.340,0.247 0.715,0.339,0.244 0.720,0.337,0.242 0.724,0.336,0.239 0.729,0.334,0.237 0.734,0.333,0.234 0.738,0.331,0.231 0.743,0.330,0.229 0.748,0.328,0.226 0.752,0.326,0.223 0.757,0.324,0.221 0.761,0.323,0.218 0.766,0.321,0.215 0.771,0.319,0.212 0.775,0.317,0.210 0.780,0.315,0.207 0.784,0.313,0.204 0.789,0.311,0.201 0.794,0.309,0.199 0.798,0.307,0.196 0.803,0.305,0.193 0.807,0.303,0.190 0.812,0.301,0.187 0.816,0.299,0.184 0.821,0.296,0.182 0.825,0.294,0.179 0.830,0.292,0.176 0.834,0.290,0.173 0.839,0.287,0.170 0.843,0.285,0.167 0.848,0.282,0.164 0.852,0.280,0.161 0.857,0.277,0.158 0.861,0.274,0.155 0.866,0.271,0.152 0.870,0.269,0.149 0.875,0.266,0.145 0.879,0.263,0.142 0.884,0.260,0.139 0.888,0.257,0.136 0.892,0.254,0.132 0.897,0.250,0.129 0.901,0.247,0.126 0.906,0.244,0.122 0.910,0.240,0.119 0.915,0.237,0.115 0.919,0.233,0.112 0.923,0.229,0.108 0.928,0.225,0.104 0.932,0.222,0.100 0.937,0.217,0.096 0.941,0.213,0.092 0.945,0.209,0.088 0.950,0.204,0.084 0.954,0.200,0.080 0.959,0.195,0.075 0.963,0.190,0.070 0.967,0.185,0.066 0.972,0.179,0.060 0.976,0.174,0.055 0.981,0.168,0.050 0.985,0.162,0.043 0.989,0.156,0.037 0.994,0.149,0.030 0.998,0.142,0.024 1.000,0.134,0.018 1.000,0.126,0.012 1.000,0.117,0.005 1.000,0.108,0.000 1.000,0.097,0.000 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-D9.csv000066400000000000000000000110001421045507400235060ustar00rootroot000000000000000.141,0.501,0.998 0.162,0.504,0.998 0.181,0.508,0.998 0.198,0.511,0.998 0.214,0.514,0.998 0.228,0.518,0.998 0.242,0.521,0.998 0.255,0.524,0.998 0.267,0.528,0.998 0.279,0.531,0.998 0.290,0.535,0.998 0.301,0.538,0.998 0.311,0.541,0.998 0.321,0.545,0.998 0.331,0.548,0.998 0.340,0.552,0.998 0.350,0.555,0.998 0.359,0.559,0.998 0.367,0.562,0.998 0.376,0.565,0.998 0.385,0.569,0.998 0.393,0.572,0.998 0.401,0.576,0.998 0.409,0.579,0.998 0.417,0.583,0.998 0.424,0.586,0.997 0.432,0.590,0.997 0.439,0.593,0.997 0.447,0.597,0.997 0.454,0.600,0.997 0.461,0.604,0.997 0.468,0.608,0.997 0.475,0.611,0.997 0.482,0.615,0.997 0.489,0.618,0.997 0.495,0.622,0.997 0.502,0.625,0.997 0.509,0.629,0.997 0.515,0.633,0.997 0.521,0.636,0.997 0.528,0.640,0.996 0.534,0.643,0.996 0.540,0.647,0.996 0.547,0.651,0.996 0.553,0.654,0.996 0.559,0.658,0.996 0.565,0.661,0.996 0.571,0.665,0.996 0.577,0.669,0.996 0.583,0.672,0.996 0.589,0.676,0.995 0.594,0.680,0.995 0.600,0.683,0.995 0.606,0.687,0.995 0.612,0.691,0.995 0.617,0.694,0.995 0.623,0.698,0.995 0.629,0.702,0.995 0.634,0.705,0.994 0.640,0.709,0.994 0.645,0.713,0.994 0.651,0.717,0.994 0.656,0.720,0.994 0.661,0.724,0.994 0.667,0.728,0.994 0.672,0.731,0.993 0.678,0.735,0.993 0.683,0.739,0.993 0.688,0.743,0.993 0.693,0.746,0.993 0.699,0.750,0.993 0.704,0.754,0.992 0.709,0.758,0.992 0.714,0.762,0.992 0.719,0.765,0.992 0.724,0.769,0.992 0.730,0.773,0.992 0.735,0.777,0.991 0.740,0.780,0.991 0.745,0.784,0.991 0.750,0.788,0.991 0.755,0.792,0.991 0.760,0.796,0.990 0.765,0.800,0.990 0.770,0.803,0.990 0.775,0.807,0.990 0.780,0.811,0.989 0.784,0.815,0.989 0.789,0.819,0.989 0.794,0.823,0.989 0.799,0.826,0.989 0.804,0.830,0.988 0.809,0.834,0.988 0.814,0.838,0.988 0.818,0.842,0.988 0.823,0.846,0.987 0.828,0.850,0.987 0.833,0.853,0.987 0.837,0.857,0.987 0.842,0.861,0.986 0.847,0.865,0.986 0.852,0.869,0.986 0.856,0.873,0.986 0.861,0.877,0.985 0.866,0.881,0.985 0.870,0.885,0.985 0.875,0.889,0.985 0.880,0.893,0.984 0.884,0.896,0.984 0.889,0.900,0.984 0.894,0.904,0.983 0.898,0.908,0.983 0.903,0.912,0.983 0.908,0.916,0.982 0.912,0.920,0.982 0.917,0.924,0.982 0.921,0.928,0.982 0.926,0.932,0.981 0.930,0.936,0.981 0.935,0.940,0.981 0.939,0.944,0.980 0.944,0.948,0.980 0.949,0.952,0.980 0.953,0.956,0.979 0.958,0.960,0.978 0.962,0.963,0.977 0.967,0.966,0.975 0.972,0.967,0.972 0.975,0.966,0.967 0.978,0.964,0.962 0.980,0.960,0.956 0.981,0.955,0.949 0.982,0.951,0.943 0.983,0.946,0.936 0.984,0.941,0.930 0.985,0.936,0.924 0.986,0.931,0.917 0.986,0.926,0.911 0.987,0.921,0.905 0.988,0.916,0.898 0.988,0.911,0.892 0.989,0.906,0.886 0.990,0.901,0.879 0.990,0.896,0.873 0.991,0.891,0.867 0.991,0.886,0.860 0.992,0.881,0.854 0.992,0.876,0.848 0.992,0.871,0.841 0.993,0.866,0.835 0.993,0.861,0.829 0.993,0.857,0.823 0.994,0.852,0.816 0.994,0.847,0.810 0.994,0.842,0.804 0.994,0.837,0.798 0.995,0.832,0.791 0.995,0.827,0.785 0.995,0.822,0.779 0.995,0.817,0.773 0.995,0.812,0.767 0.995,0.807,0.760 0.995,0.802,0.754 0.995,0.797,0.748 0.995,0.792,0.742 0.995,0.787,0.736 0.995,0.782,0.730 0.995,0.777,0.724 0.995,0.772,0.717 0.995,0.767,0.711 0.994,0.762,0.705 0.994,0.757,0.699 0.994,0.753,0.693 0.994,0.748,0.687 0.994,0.743,0.681 0.993,0.738,0.675 0.993,0.733,0.669 0.993,0.728,0.663 0.992,0.723,0.657 0.992,0.718,0.651 0.991,0.713,0.645 0.991,0.708,0.639 0.990,0.703,0.633 0.990,0.698,0.627 0.989,0.693,0.621 0.989,0.688,0.615 0.988,0.683,0.609 0.988,0.678,0.603 0.987,0.673,0.597 0.987,0.668,0.591 0.986,0.663,0.585 0.985,0.658,0.579 0.985,0.653,0.573 0.984,0.648,0.567 0.983,0.643,0.561 0.982,0.638,0.555 0.982,0.633,0.549 0.981,0.628,0.543 0.980,0.623,0.537 0.979,0.618,0.532 0.978,0.613,0.526 0.978,0.608,0.520 0.977,0.603,0.514 0.976,0.598,0.508 0.975,0.592,0.502 0.974,0.587,0.496 0.973,0.582,0.491 0.972,0.577,0.485 0.971,0.572,0.479 0.970,0.567,0.473 0.969,0.562,0.468 0.968,0.557,0.462 0.967,0.552,0.456 0.966,0.547,0.450 0.964,0.541,0.444 0.963,0.536,0.439 0.962,0.531,0.433 0.961,0.526,0.427 0.960,0.521,0.422 0.958,0.516,0.416 0.957,0.510,0.410 0.956,0.505,0.404 0.955,0.500,0.399 0.953,0.495,0.393 0.952,0.489,0.387 0.951,0.484,0.382 0.949,0.479,0.376 0.948,0.474,0.370 0.947,0.468,0.365 0.945,0.463,0.359 0.944,0.458,0.354 0.942,0.452,0.348 0.941,0.447,0.342 0.940,0.441,0.337 0.938,0.436,0.331 0.937,0.431,0.325 0.935,0.425,0.320 0.933,0.420,0.314 0.932,0.414,0.309 0.930,0.409,0.303 0.929,0.403,0.298 0.927,0.397,0.292 0.926,0.392,0.286 0.924,0.386,0.281 0.922,0.380,0.275 0.921,0.375,0.270 0.919,0.369,0.264 0.917,0.363,0.259 0.915,0.357,0.253 0.914,0.351,0.248 0.912,0.345,0.242 0.910,0.339,0.237 0.908,0.333,0.231 0.907,0.327,0.226 0.905,0.321,0.220 0.903,0.315,0.214 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-I1.csv000066400000000000000000000110001421045507400235030ustar00rootroot000000000000000.216,0.718,0.926 0.218,0.718,0.923 0.220,0.718,0.919 0.223,0.719,0.916 0.225,0.719,0.912 0.227,0.719,0.909 0.230,0.720,0.906 0.232,0.720,0.902 0.234,0.720,0.899 0.236,0.721,0.895 0.238,0.721,0.892 0.240,0.721,0.888 0.242,0.721,0.885 0.245,0.722,0.882 0.247,0.722,0.878 0.249,0.722,0.875 0.251,0.723,0.871 0.253,0.723,0.868 0.255,0.723,0.864 0.257,0.723,0.861 0.259,0.724,0.857 0.261,0.724,0.854 0.263,0.724,0.850 0.265,0.724,0.847 0.267,0.725,0.843 0.269,0.725,0.840 0.271,0.725,0.836 0.273,0.725,0.833 0.274,0.726,0.829 0.276,0.726,0.826 0.278,0.726,0.822 0.280,0.726,0.819 0.282,0.727,0.815 0.284,0.727,0.812 0.286,0.727,0.808 0.288,0.727,0.805 0.290,0.728,0.801 0.291,0.728,0.798 0.293,0.728,0.794 0.295,0.728,0.791 0.297,0.729,0.787 0.299,0.729,0.784 0.301,0.729,0.780 0.303,0.729,0.776 0.304,0.729,0.773 0.306,0.730,0.769 0.308,0.730,0.766 0.310,0.730,0.762 0.312,0.730,0.759 0.314,0.730,0.755 0.316,0.731,0.751 0.318,0.731,0.748 0.319,0.731,0.744 0.321,0.731,0.740 0.323,0.731,0.737 0.325,0.731,0.733 0.327,0.732,0.730 0.329,0.732,0.726 0.331,0.732,0.722 0.333,0.732,0.719 0.335,0.732,0.715 0.337,0.732,0.711 0.339,0.732,0.708 0.341,0.733,0.704 0.343,0.733,0.700 0.345,0.733,0.696 0.347,0.733,0.693 0.349,0.733,0.689 0.351,0.733,0.685 0.353,0.733,0.682 0.355,0.733,0.678 0.357,0.733,0.674 0.359,0.734,0.670 0.361,0.734,0.667 0.363,0.734,0.663 0.366,0.734,0.659 0.368,0.734,0.655 0.370,0.734,0.652 0.372,0.734,0.648 0.375,0.734,0.644 0.377,0.734,0.640 0.379,0.734,0.636 0.381,0.734,0.633 0.384,0.734,0.629 0.386,0.734,0.625 0.389,0.734,0.621 0.391,0.734,0.617 0.394,0.734,0.613 0.396,0.734,0.610 0.399,0.734,0.606 0.401,0.734,0.602 0.404,0.734,0.598 0.406,0.734,0.594 0.409,0.734,0.590 0.412,0.734,0.586 0.415,0.733,0.582 0.417,0.733,0.579 0.420,0.733,0.575 0.423,0.733,0.571 0.426,0.733,0.567 0.429,0.733,0.563 0.432,0.733,0.559 0.435,0.732,0.555 0.438,0.732,0.552 0.441,0.732,0.548 0.444,0.732,0.544 0.448,0.731,0.540 0.451,0.731,0.536 0.454,0.731,0.532 0.458,0.731,0.529 0.461,0.730,0.525 0.465,0.730,0.521 0.468,0.730,0.517 0.472,0.729,0.513 0.475,0.729,0.510 0.479,0.729,0.506 0.483,0.728,0.502 0.486,0.728,0.499 0.490,0.727,0.495 0.494,0.727,0.492 0.498,0.726,0.488 0.502,0.726,0.485 0.506,0.725,0.481 0.510,0.725,0.478 0.514,0.724,0.474 0.518,0.724,0.471 0.522,0.723,0.468 0.526,0.722,0.465 0.530,0.722,0.462 0.534,0.721,0.459 0.538,0.720,0.456 0.542,0.720,0.453 0.547,0.719,0.450 0.551,0.718,0.447 0.555,0.717,0.444 0.559,0.717,0.441 0.563,0.716,0.439 0.568,0.715,0.436 0.572,0.714,0.434 0.576,0.713,0.431 0.580,0.713,0.429 0.585,0.712,0.427 0.589,0.711,0.424 0.593,0.710,0.422 0.597,0.709,0.420 0.601,0.708,0.418 0.606,0.707,0.416 0.610,0.706,0.414 0.614,0.706,0.412 0.618,0.705,0.410 0.622,0.704,0.408 0.626,0.703,0.406 0.630,0.702,0.404 0.634,0.701,0.402 0.638,0.700,0.401 0.642,0.699,0.399 0.646,0.698,0.397 0.650,0.697,0.395 0.654,0.696,0.394 0.658,0.695,0.392 0.662,0.694,0.391 0.666,0.693,0.389 0.670,0.692,0.388 0.674,0.691,0.386 0.678,0.690,0.385 0.681,0.689,0.384 0.685,0.688,0.383 0.689,0.687,0.381 0.693,0.685,0.380 0.697,0.684,0.379 0.700,0.683,0.378 0.704,0.682,0.377 0.708,0.681,0.376 0.712,0.680,0.375 0.715,0.679,0.374 0.719,0.678,0.373 0.723,0.676,0.373 0.726,0.675,0.372 0.730,0.674,0.371 0.734,0.673,0.371 0.737,0.672,0.370 0.741,0.671,0.370 0.745,0.669,0.369 0.748,0.668,0.369 0.752,0.667,0.368 0.755,0.666,0.368 0.759,0.664,0.367 0.762,0.663,0.367 0.766,0.662,0.367 0.769,0.661,0.367 0.773,0.660,0.367 0.776,0.658,0.367 0.780,0.657,0.366 0.783,0.656,0.366 0.787,0.654,0.366 0.790,0.653,0.367 0.793,0.652,0.367 0.797,0.651,0.367 0.800,0.649,0.367 0.803,0.648,0.367 0.807,0.647,0.367 0.810,0.645,0.368 0.813,0.644,0.368 0.816,0.643,0.369 0.820,0.641,0.369 0.823,0.640,0.369 0.826,0.639,0.370 0.829,0.637,0.370 0.833,0.636,0.371 0.836,0.634,0.372 0.839,0.633,0.372 0.842,0.632,0.373 0.845,0.630,0.374 0.848,0.629,0.374 0.851,0.628,0.375 0.854,0.626,0.376 0.857,0.625,0.377 0.861,0.623,0.377 0.864,0.622,0.378 0.867,0.620,0.379 0.870,0.619,0.380 0.873,0.618,0.381 0.876,0.616,0.382 0.878,0.615,0.383 0.881,0.613,0.384 0.884,0.612,0.385 0.887,0.610,0.386 0.890,0.609,0.387 0.893,0.607,0.389 0.896,0.606,0.390 0.899,0.604,0.391 0.902,0.603,0.392 0.905,0.601,0.393 0.907,0.600,0.395 0.910,0.598,0.396 0.913,0.597,0.397 0.916,0.595,0.398 0.919,0.594,0.400 0.921,0.592,0.401 0.924,0.591,0.402 0.927,0.589,0.404 0.930,0.587,0.405 0.932,0.586,0.407 0.935,0.584,0.408 0.938,0.583,0.410 0.940,0.581,0.411 0.943,0.580,0.413 0.946,0.578,0.414 0.948,0.576,0.416 0.951,0.575,0.417 0.954,0.573,0.419 0.956,0.572,0.420 0.959,0.570,0.422 0.961,0.568,0.423 0.964,0.567,0.425 0.966,0.565,0.427 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-I2.csv000066400000000000000000000110001421045507400235040ustar00rootroot000000000000000.438,0.821,1.000 0.438,0.822,1.000 0.438,0.822,1.000 0.439,0.823,1.000 0.439,0.823,1.000 0.439,0.823,0.998 0.439,0.824,0.995 0.440,0.824,0.992 0.440,0.824,0.989 0.440,0.825,0.985 0.441,0.825,0.982 0.441,0.825,0.979 0.441,0.826,0.976 0.442,0.826,0.973 0.442,0.826,0.970 0.442,0.827,0.967 0.443,0.827,0.964 0.443,0.827,0.960 0.443,0.828,0.957 0.444,0.828,0.954 0.444,0.828,0.951 0.445,0.829,0.948 0.445,0.829,0.945 0.445,0.829,0.942 0.446,0.830,0.938 0.446,0.830,0.935 0.447,0.830,0.932 0.447,0.831,0.929 0.448,0.831,0.926 0.448,0.831,0.922 0.449,0.832,0.919 0.449,0.832,0.916 0.450,0.832,0.913 0.450,0.833,0.910 0.451,0.833,0.906 0.451,0.833,0.903 0.452,0.833,0.900 0.452,0.834,0.897 0.453,0.834,0.893 0.454,0.834,0.890 0.454,0.834,0.887 0.455,0.835,0.884 0.455,0.835,0.880 0.456,0.835,0.877 0.457,0.836,0.874 0.457,0.836,0.871 0.458,0.836,0.867 0.459,0.836,0.864 0.460,0.837,0.861 0.460,0.837,0.857 0.461,0.837,0.854 0.462,0.837,0.851 0.463,0.838,0.847 0.463,0.838,0.844 0.464,0.838,0.841 0.465,0.838,0.838 0.466,0.838,0.834 0.467,0.839,0.831 0.468,0.839,0.827 0.469,0.839,0.824 0.470,0.839,0.821 0.470,0.839,0.817 0.471,0.840,0.814 0.472,0.840,0.811 0.473,0.840,0.807 0.474,0.840,0.804 0.476,0.840,0.800 0.477,0.841,0.797 0.478,0.841,0.793 0.479,0.841,0.790 0.480,0.841,0.787 0.481,0.841,0.783 0.482,0.841,0.780 0.484,0.841,0.776 0.485,0.842,0.773 0.486,0.842,0.769 0.488,0.842,0.766 0.489,0.842,0.762 0.490,0.842,0.759 0.492,0.842,0.755 0.493,0.842,0.752 0.495,0.842,0.748 0.496,0.842,0.745 0.498,0.842,0.741 0.499,0.842,0.738 0.501,0.843,0.734 0.502,0.843,0.730 0.504,0.843,0.727 0.506,0.843,0.723 0.508,0.843,0.720 0.509,0.843,0.716 0.511,0.843,0.713 0.513,0.843,0.709 0.515,0.843,0.705 0.517,0.843,0.702 0.519,0.843,0.698 0.521,0.843,0.694 0.523,0.843,0.691 0.525,0.843,0.687 0.527,0.842,0.684 0.530,0.842,0.680 0.532,0.842,0.676 0.534,0.842,0.673 0.537,0.842,0.669 0.539,0.842,0.665 0.541,0.842,0.662 0.544,0.842,0.658 0.547,0.841,0.655 0.549,0.841,0.651 0.552,0.841,0.647 0.555,0.841,0.644 0.557,0.841,0.640 0.560,0.840,0.636 0.563,0.840,0.633 0.566,0.840,0.629 0.569,0.840,0.626 0.572,0.839,0.622 0.575,0.839,0.619 0.578,0.839,0.615 0.582,0.838,0.612 0.585,0.838,0.608 0.588,0.837,0.605 0.592,0.837,0.601 0.595,0.837,0.598 0.598,0.836,0.595 0.602,0.836,0.591 0.606,0.835,0.588 0.609,0.835,0.585 0.613,0.834,0.582 0.616,0.834,0.579 0.620,0.833,0.575 0.624,0.832,0.572 0.628,0.832,0.569 0.631,0.831,0.566 0.635,0.831,0.564 0.639,0.830,0.561 0.643,0.829,0.558 0.647,0.829,0.555 0.651,0.828,0.552 0.655,0.827,0.550 0.659,0.827,0.547 0.663,0.826,0.545 0.667,0.825,0.542 0.671,0.824,0.540 0.674,0.824,0.537 0.678,0.823,0.535 0.682,0.822,0.533 0.686,0.821,0.531 0.690,0.820,0.529 0.694,0.820,0.526 0.698,0.819,0.524 0.702,0.818,0.522 0.706,0.817,0.520 0.710,0.816,0.518 0.714,0.815,0.516 0.718,0.815,0.514 0.722,0.814,0.513 0.726,0.813,0.511 0.729,0.812,0.509 0.733,0.811,0.507 0.737,0.810,0.505 0.741,0.809,0.503 0.745,0.808,0.502 0.748,0.807,0.500 0.752,0.806,0.498 0.756,0.805,0.497 0.760,0.805,0.495 0.763,0.804,0.493 0.767,0.803,0.492 0.771,0.802,0.490 0.774,0.801,0.489 0.778,0.800,0.487 0.782,0.799,0.486 0.785,0.798,0.485 0.789,0.797,0.483 0.793,0.796,0.482 0.796,0.795,0.481 0.800,0.794,0.479 0.804,0.793,0.478 0.807,0.792,0.477 0.811,0.791,0.476 0.814,0.790,0.475 0.818,0.789,0.474 0.821,0.788,0.473 0.825,0.786,0.472 0.829,0.785,0.471 0.832,0.784,0.470 0.836,0.783,0.469 0.839,0.782,0.468 0.843,0.781,0.468 0.846,0.780,0.467 0.849,0.779,0.466 0.853,0.778,0.466 0.856,0.777,0.465 0.860,0.775,0.465 0.863,0.774,0.464 0.866,0.773,0.464 0.870,0.772,0.463 0.873,0.771,0.463 0.877,0.770,0.462 0.880,0.769,0.462 0.883,0.767,0.462 0.887,0.766,0.462 0.890,0.765,0.462 0.893,0.764,0.461 0.896,0.763,0.461 0.900,0.761,0.461 0.903,0.760,0.461 0.906,0.759,0.462 0.909,0.758,0.462 0.912,0.757,0.462 0.916,0.755,0.462 0.919,0.754,0.462 0.922,0.753,0.463 0.925,0.752,0.463 0.928,0.750,0.463 0.931,0.749,0.464 0.934,0.748,0.464 0.937,0.747,0.465 0.940,0.745,0.465 0.943,0.744,0.466 0.946,0.743,0.466 0.949,0.742,0.467 0.952,0.740,0.468 0.955,0.739,0.468 0.958,0.738,0.469 0.961,0.737,0.470 0.964,0.735,0.471 0.967,0.734,0.472 0.970,0.733,0.473 0.973,0.732,0.474 0.975,0.730,0.475 0.978,0.729,0.476 0.981,0.728,0.477 0.984,0.726,0.478 0.986,0.725,0.479 0.989,0.724,0.480 0.992,0.723,0.481 0.995,0.721,0.483 0.997,0.720,0.484 1.000,0.719,0.485 1.000,0.717,0.486 1.000,0.716,0.488 1.000,0.715,0.489 1.000,0.713,0.490 1.000,0.712,0.492 1.000,0.711,0.493 1.000,0.710,0.495 1.000,0.708,0.496 1.000,0.707,0.498 1.000,0.706,0.499 1.000,0.704,0.501 1.000,0.703,0.503 1.000,0.702,0.504 1.000,0.700,0.506 1.000,0.699,0.508 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-I3.csv000066400000000000000000000110001421045507400235050ustar00rootroot000000000000000.078,0.727,0.901 0.097,0.727,0.901 0.112,0.726,0.902 0.126,0.726,0.902 0.139,0.725,0.903 0.150,0.725,0.903 0.161,0.724,0.904 0.171,0.723,0.904 0.181,0.723,0.905 0.190,0.722,0.905 0.198,0.722,0.906 0.207,0.721,0.906 0.215,0.720,0.907 0.222,0.720,0.907 0.230,0.719,0.908 0.237,0.719,0.908 0.244,0.718,0.909 0.251,0.717,0.909 0.257,0.717,0.910 0.264,0.716,0.910 0.270,0.716,0.911 0.276,0.715,0.911 0.282,0.714,0.912 0.288,0.714,0.912 0.294,0.713,0.913 0.300,0.712,0.913 0.305,0.712,0.914 0.311,0.711,0.914 0.316,0.711,0.914 0.322,0.710,0.915 0.327,0.709,0.915 0.332,0.709,0.916 0.337,0.708,0.916 0.342,0.707,0.916 0.347,0.707,0.917 0.352,0.706,0.917 0.357,0.705,0.918 0.362,0.705,0.918 0.366,0.704,0.918 0.371,0.703,0.919 0.376,0.703,0.919 0.380,0.702,0.919 0.385,0.701,0.920 0.389,0.701,0.920 0.394,0.700,0.920 0.398,0.699,0.921 0.403,0.699,0.921 0.407,0.698,0.921 0.411,0.697,0.922 0.416,0.697,0.922 0.420,0.696,0.922 0.424,0.695,0.922 0.428,0.694,0.923 0.432,0.694,0.923 0.436,0.693,0.923 0.441,0.692,0.923 0.445,0.692,0.924 0.449,0.691,0.924 0.453,0.690,0.924 0.457,0.689,0.924 0.461,0.689,0.924 0.465,0.688,0.925 0.468,0.687,0.925 0.472,0.686,0.925 0.476,0.686,0.925 0.480,0.685,0.925 0.484,0.684,0.925 0.488,0.683,0.925 0.492,0.683,0.926 0.495,0.682,0.926 0.499,0.681,0.926 0.503,0.680,0.926 0.507,0.680,0.926 0.510,0.679,0.926 0.514,0.678,0.926 0.518,0.677,0.926 0.521,0.677,0.926 0.525,0.676,0.926 0.529,0.675,0.926 0.532,0.674,0.926 0.536,0.673,0.926 0.540,0.673,0.926 0.543,0.672,0.926 0.547,0.671,0.926 0.550,0.670,0.925 0.554,0.669,0.925 0.558,0.669,0.925 0.561,0.668,0.925 0.565,0.667,0.925 0.568,0.666,0.925 0.572,0.665,0.924 0.575,0.664,0.924 0.579,0.664,0.924 0.582,0.663,0.924 0.586,0.662,0.923 0.589,0.661,0.923 0.593,0.660,0.923 0.596,0.659,0.922 0.600,0.659,0.922 0.603,0.658,0.921 0.607,0.657,0.921 0.610,0.656,0.921 0.614,0.655,0.920 0.617,0.654,0.920 0.621,0.653,0.919 0.624,0.653,0.919 0.627,0.652,0.918 0.631,0.651,0.917 0.634,0.650,0.917 0.638,0.649,0.916 0.641,0.648,0.915 0.644,0.647,0.915 0.648,0.646,0.914 0.651,0.645,0.913 0.655,0.645,0.912 0.658,0.644,0.911 0.661,0.643,0.910 0.665,0.642,0.910 0.668,0.641,0.909 0.671,0.640,0.908 0.675,0.639,0.907 0.678,0.638,0.905 0.681,0.638,0.904 0.684,0.637,0.903 0.688,0.636,0.902 0.691,0.635,0.901 0.694,0.634,0.900 0.697,0.633,0.898 0.700,0.632,0.897 0.704,0.631,0.896 0.707,0.631,0.894 0.710,0.630,0.893 0.713,0.629,0.892 0.716,0.628,0.890 0.719,0.627,0.889 0.722,0.626,0.887 0.725,0.625,0.886 0.728,0.625,0.885 0.731,0.624,0.883 0.734,0.623,0.882 0.737,0.622,0.880 0.740,0.621,0.879 0.743,0.620,0.877 0.746,0.619,0.876 0.749,0.619,0.874 0.752,0.618,0.873 0.754,0.617,0.871 0.757,0.616,0.870 0.760,0.615,0.868 0.763,0.614,0.867 0.766,0.613,0.865 0.768,0.613,0.863 0.771,0.612,0.862 0.774,0.611,0.860 0.777,0.610,0.859 0.779,0.609,0.857 0.782,0.608,0.855 0.785,0.608,0.854 0.787,0.607,0.852 0.790,0.606,0.850 0.793,0.605,0.849 0.795,0.604,0.847 0.798,0.603,0.846 0.800,0.602,0.844 0.803,0.602,0.842 0.806,0.601,0.841 0.808,0.600,0.839 0.811,0.599,0.837 0.813,0.598,0.835 0.816,0.597,0.834 0.818,0.596,0.832 0.821,0.596,0.830 0.823,0.595,0.829 0.826,0.594,0.827 0.828,0.593,0.825 0.830,0.592,0.824 0.833,0.591,0.822 0.835,0.590,0.820 0.838,0.590,0.818 0.840,0.589,0.817 0.842,0.588,0.815 0.845,0.587,0.813 0.847,0.586,0.811 0.849,0.585,0.810 0.852,0.584,0.808 0.854,0.584,0.806 0.856,0.583,0.804 0.859,0.582,0.803 0.861,0.581,0.801 0.863,0.580,0.799 0.866,0.579,0.797 0.868,0.578,0.796 0.870,0.578,0.794 0.872,0.577,0.792 0.874,0.576,0.790 0.877,0.575,0.788 0.879,0.574,0.787 0.881,0.573,0.785 0.883,0.572,0.783 0.885,0.572,0.781 0.888,0.571,0.780 0.890,0.570,0.778 0.892,0.569,0.776 0.894,0.568,0.774 0.896,0.567,0.772 0.898,0.566,0.770 0.900,0.565,0.769 0.903,0.565,0.767 0.905,0.564,0.765 0.907,0.563,0.763 0.909,0.562,0.761 0.911,0.561,0.760 0.913,0.560,0.758 0.915,0.559,0.756 0.917,0.558,0.754 0.919,0.558,0.752 0.921,0.557,0.750 0.923,0.556,0.749 0.925,0.555,0.747 0.927,0.554,0.745 0.929,0.553,0.743 0.931,0.552,0.741 0.933,0.551,0.739 0.935,0.550,0.738 0.937,0.550,0.736 0.939,0.549,0.734 0.941,0.548,0.732 0.943,0.547,0.730 0.945,0.546,0.728 0.947,0.545,0.727 0.949,0.544,0.725 0.950,0.543,0.723 0.952,0.542,0.721 0.954,0.541,0.719 0.956,0.541,0.717 0.958,0.540,0.715 0.960,0.539,0.714 0.962,0.538,0.712 0.964,0.537,0.710 0.965,0.536,0.708 0.967,0.535,0.706 0.969,0.534,0.704 0.971,0.533,0.702 0.973,0.532,0.701 0.975,0.531,0.699 0.976,0.531,0.697 0.978,0.530,0.695 0.980,0.529,0.693 0.982,0.528,0.691 0.984,0.527,0.689 0.985,0.526,0.687 0.987,0.525,0.686 0.989,0.524,0.684 0.991,0.523,0.682 0.992,0.522,0.680 0.994,0.521,0.678 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L1.csv000066400000000000000000000110001421045507400235060ustar00rootroot000000000000000.000,0.000,0.000 0.006,0.006,0.006 0.011,0.011,0.011 0.017,0.017,0.017 0.022,0.022,0.022 0.028,0.028,0.028 0.034,0.034,0.034 0.039,0.039,0.039 0.045,0.045,0.045 0.050,0.050,0.050 0.054,0.054,0.054 0.059,0.059,0.059 0.063,0.063,0.063 0.067,0.067,0.067 0.071,0.071,0.071 0.074,0.075,0.075 0.078,0.078,0.078 0.081,0.081,0.081 0.085,0.085,0.085 0.088,0.088,0.088 0.091,0.091,0.091 0.094,0.094,0.094 0.097,0.097,0.097 0.100,0.100,0.100 0.103,0.103,0.103 0.106,0.106,0.106 0.109,0.109,0.109 0.112,0.112,0.112 0.115,0.115,0.115 0.119,0.119,0.119 0.122,0.122,0.122 0.125,0.125,0.125 0.128,0.128,0.128 0.131,0.131,0.131 0.134,0.134,0.134 0.137,0.137,0.137 0.141,0.141,0.141 0.144,0.144,0.144 0.147,0.147,0.147 0.150,0.150,0.150 0.153,0.153,0.153 0.157,0.157,0.157 0.160,0.160,0.160 0.163,0.163,0.163 0.166,0.166,0.166 0.170,0.170,0.170 0.173,0.173,0.173 0.176,0.176,0.176 0.179,0.179,0.179 0.183,0.183,0.183 0.186,0.186,0.186 0.189,0.189,0.189 0.193,0.193,0.193 0.196,0.196,0.196 0.199,0.199,0.199 0.203,0.203,0.203 0.206,0.206,0.206 0.209,0.210,0.209 0.213,0.213,0.213 0.216,0.216,0.216 0.220,0.220,0.220 0.223,0.223,0.223 0.226,0.227,0.227 0.230,0.230,0.230 0.233,0.233,0.233 0.237,0.237,0.237 0.240,0.240,0.240 0.244,0.244,0.244 0.247,0.247,0.247 0.251,0.251,0.251 0.254,0.254,0.254 0.258,0.258,0.258 0.261,0.261,0.261 0.265,0.265,0.265 0.268,0.268,0.268 0.272,0.272,0.272 0.275,0.275,0.275 0.279,0.279,0.279 0.282,0.282,0.282 0.286,0.286,0.286 0.289,0.289,0.289 0.293,0.293,0.293 0.297,0.297,0.297 0.300,0.300,0.300 0.304,0.304,0.304 0.307,0.307,0.307 0.311,0.311,0.311 0.314,0.315,0.315 0.318,0.318,0.318 0.322,0.322,0.322 0.325,0.325,0.325 0.329,0.329,0.329 0.333,0.333,0.333 0.336,0.336,0.336 0.340,0.340,0.340 0.344,0.344,0.344 0.347,0.347,0.347 0.351,0.351,0.351 0.355,0.355,0.355 0.358,0.358,0.358 0.362,0.362,0.362 0.366,0.366,0.366 0.369,0.370,0.370 0.373,0.373,0.373 0.377,0.377,0.377 0.381,0.381,0.381 0.384,0.384,0.384 0.388,0.388,0.388 0.392,0.392,0.392 0.396,0.396,0.396 0.399,0.399,0.399 0.403,0.403,0.403 0.407,0.407,0.407 0.411,0.411,0.411 0.415,0.415,0.415 0.418,0.418,0.418 0.422,0.422,0.422 0.426,0.426,0.426 0.430,0.430,0.430 0.434,0.434,0.434 0.437,0.437,0.437 0.441,0.441,0.441 0.445,0.445,0.445 0.449,0.449,0.449 0.453,0.453,0.453 0.457,0.457,0.457 0.460,0.461,0.461 0.464,0.464,0.464 0.468,0.468,0.468 0.472,0.472,0.472 0.476,0.476,0.476 0.480,0.480,0.480 0.484,0.484,0.484 0.488,0.488,0.488 0.492,0.492,0.492 0.495,0.496,0.496 0.499,0.499,0.499 0.503,0.503,0.503 0.507,0.507,0.507 0.511,0.511,0.511 0.515,0.515,0.515 0.519,0.519,0.519 0.523,0.523,0.523 0.527,0.527,0.527 0.531,0.531,0.531 0.535,0.535,0.535 0.539,0.539,0.539 0.543,0.543,0.543 0.547,0.547,0.547 0.551,0.551,0.551 0.555,0.555,0.555 0.559,0.559,0.559 0.563,0.563,0.563 0.567,0.567,0.567 0.571,0.571,0.571 0.575,0.575,0.575 0.579,0.579,0.579 0.583,0.583,0.583 0.587,0.587,0.587 0.591,0.591,0.591 0.595,0.595,0.595 0.599,0.599,0.599 0.603,0.603,0.603 0.607,0.607,0.607 0.611,0.611,0.611 0.615,0.615,0.615 0.619,0.619,0.619 0.623,0.624,0.624 0.628,0.628,0.628 0.632,0.632,0.632 0.636,0.636,0.636 0.640,0.640,0.640 0.644,0.644,0.644 0.648,0.648,0.648 0.652,0.652,0.652 0.656,0.656,0.656 0.660,0.660,0.660 0.664,0.665,0.665 0.669,0.669,0.669 0.673,0.673,0.673 0.677,0.677,0.677 0.681,0.681,0.681 0.685,0.685,0.685 0.689,0.689,0.689 0.694,0.694,0.694 0.698,0.698,0.698 0.702,0.702,0.702 0.706,0.706,0.706 0.710,0.710,0.710 0.714,0.715,0.714 0.719,0.719,0.719 0.723,0.723,0.723 0.727,0.727,0.727 0.731,0.731,0.731 0.735,0.735,0.735 0.740,0.740,0.740 0.744,0.744,0.744 0.748,0.748,0.748 0.752,0.752,0.752 0.756,0.757,0.757 0.761,0.761,0.761 0.765,0.765,0.765 0.769,0.769,0.769 0.773,0.774,0.774 0.778,0.778,0.778 0.782,0.782,0.782 0.786,0.786,0.786 0.790,0.791,0.791 0.795,0.795,0.795 0.799,0.799,0.799 0.803,0.803,0.803 0.808,0.808,0.808 0.812,0.812,0.812 0.816,0.816,0.816 0.820,0.821,0.821 0.825,0.825,0.825 0.829,0.829,0.829 0.833,0.833,0.833 0.838,0.838,0.838 0.842,0.842,0.842 0.846,0.846,0.846 0.851,0.851,0.851 0.855,0.855,0.855 0.859,0.859,0.859 0.864,0.864,0.864 0.868,0.868,0.868 0.872,0.872,0.872 0.877,0.877,0.877 0.881,0.881,0.881 0.885,0.885,0.885 0.890,0.890,0.890 0.894,0.894,0.894 0.898,0.899,0.898 0.903,0.903,0.903 0.907,0.907,0.907 0.911,0.912,0.912 0.916,0.916,0.916 0.920,0.920,0.920 0.925,0.925,0.925 0.929,0.929,0.929 0.933,0.934,0.934 0.938,0.938,0.938 0.942,0.942,0.942 0.947,0.947,0.947 0.951,0.951,0.951 0.955,0.956,0.956 0.960,0.960,0.960 0.964,0.965,0.964 0.969,0.969,0.969 0.973,0.973,0.973 0.978,0.978,0.978 0.982,0.982,0.982 0.987,0.987,0.987 0.991,0.991,0.991 0.995,0.996,0.996 1.000,1.000,1.000 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L10.csv000066400000000000000000000110001421045507400235660ustar00rootroot000000000000000.399,0.606,0.565 0.402,0.607,0.563 0.404,0.608,0.561 0.407,0.609,0.559 0.410,0.610,0.557 0.413,0.611,0.555 0.416,0.612,0.553 0.419,0.613,0.552 0.421,0.614,0.550 0.424,0.615,0.548 0.427,0.616,0.546 0.430,0.617,0.544 0.433,0.617,0.543 0.436,0.618,0.541 0.439,0.619,0.539 0.442,0.620,0.537 0.445,0.621,0.535 0.448,0.622,0.534 0.450,0.623,0.532 0.453,0.624,0.530 0.456,0.624,0.528 0.459,0.625,0.527 0.462,0.626,0.525 0.465,0.627,0.523 0.468,0.628,0.521 0.471,0.629,0.520 0.474,0.629,0.518 0.478,0.630,0.516 0.481,0.631,0.515 0.484,0.632,0.513 0.487,0.633,0.511 0.490,0.633,0.509 0.493,0.634,0.508 0.496,0.635,0.506 0.500,0.636,0.505 0.503,0.636,0.503 0.506,0.637,0.501 0.509,0.638,0.500 0.513,0.639,0.498 0.516,0.639,0.496 0.519,0.640,0.495 0.523,0.641,0.493 0.526,0.641,0.492 0.530,0.642,0.490 0.533,0.643,0.489 0.537,0.643,0.487 0.540,0.644,0.486 0.544,0.644,0.484 0.547,0.645,0.483 0.551,0.645,0.481 0.555,0.646,0.480 0.558,0.647,0.478 0.562,0.647,0.477 0.566,0.648,0.475 0.570,0.648,0.474 0.574,0.648,0.473 0.578,0.649,0.471 0.582,0.649,0.470 0.586,0.650,0.469 0.590,0.650,0.467 0.594,0.650,0.466 0.598,0.651,0.465 0.602,0.651,0.463 0.607,0.651,0.462 0.611,0.652,0.461 0.616,0.652,0.460 0.620,0.652,0.459 0.625,0.652,0.458 0.630,0.652,0.457 0.634,0.652,0.456 0.639,0.652,0.455 0.644,0.652,0.454 0.649,0.652,0.453 0.654,0.652,0.452 0.660,0.652,0.451 0.665,0.652,0.450 0.671,0.652,0.449 0.676,0.652,0.449 0.682,0.651,0.448 0.687,0.651,0.447 0.692,0.651,0.447 0.697,0.651,0.446 0.702,0.650,0.445 0.707,0.650,0.445 0.712,0.650,0.444 0.717,0.650,0.443 0.722,0.650,0.443 0.726,0.650,0.442 0.730,0.650,0.441 0.735,0.650,0.440 0.739,0.650,0.440 0.743,0.650,0.439 0.747,0.650,0.438 0.751,0.650,0.438 0.755,0.650,0.437 0.759,0.651,0.436 0.762,0.651,0.436 0.766,0.651,0.435 0.769,0.651,0.434 0.773,0.652,0.434 0.776,0.652,0.433 0.779,0.652,0.432 0.782,0.653,0.432 0.785,0.653,0.431 0.788,0.653,0.430 0.791,0.654,0.430 0.794,0.654,0.429 0.796,0.655,0.428 0.799,0.656,0.428 0.801,0.656,0.427 0.803,0.657,0.426 0.806,0.657,0.426 0.808,0.658,0.425 0.810,0.659,0.424 0.812,0.660,0.424 0.814,0.660,0.423 0.816,0.661,0.422 0.818,0.662,0.422 0.819,0.663,0.421 0.821,0.664,0.420 0.822,0.665,0.420 0.824,0.666,0.419 0.825,0.667,0.418 0.826,0.668,0.418 0.827,0.669,0.417 0.829,0.670,0.416 0.830,0.672,0.415 0.830,0.673,0.415 0.831,0.674,0.414 0.832,0.675,0.414 0.833,0.677,0.413 0.834,0.678,0.413 0.834,0.679,0.412 0.835,0.680,0.412 0.836,0.682,0.412 0.836,0.683,0.412 0.837,0.684,0.412 0.838,0.685,0.412 0.838,0.687,0.413 0.839,0.688,0.413 0.840,0.689,0.414 0.840,0.690,0.414 0.841,0.692,0.415 0.841,0.693,0.416 0.842,0.694,0.417 0.842,0.695,0.418 0.843,0.697,0.419 0.843,0.698,0.420 0.844,0.699,0.421 0.844,0.700,0.423 0.845,0.702,0.424 0.845,0.703,0.426 0.846,0.704,0.427 0.846,0.706,0.429 0.846,0.707,0.431 0.847,0.708,0.433 0.847,0.709,0.435 0.847,0.711,0.437 0.848,0.712,0.440 0.848,0.713,0.442 0.848,0.714,0.444 0.848,0.716,0.447 0.849,0.717,0.450 0.849,0.718,0.453 0.849,0.720,0.455 0.849,0.721,0.458 0.849,0.722,0.461 0.849,0.723,0.465 0.849,0.725,0.468 0.850,0.726,0.471 0.850,0.727,0.475 0.850,0.729,0.478 0.850,0.730,0.482 0.850,0.731,0.486 0.849,0.733,0.490 0.849,0.734,0.494 0.849,0.735,0.498 0.849,0.737,0.502 0.849,0.738,0.506 0.849,0.739,0.510 0.848,0.740,0.515 0.848,0.742,0.519 0.848,0.743,0.523 0.848,0.744,0.527 0.848,0.746,0.532 0.848,0.747,0.536 0.847,0.748,0.540 0.847,0.749,0.545 0.847,0.751,0.549 0.847,0.752,0.553 0.847,0.753,0.557 0.847,0.755,0.562 0.847,0.756,0.566 0.846,0.757,0.570 0.846,0.758,0.574 0.846,0.760,0.579 0.846,0.761,0.583 0.846,0.762,0.587 0.846,0.763,0.591 0.846,0.765,0.596 0.846,0.766,0.600 0.845,0.767,0.604 0.845,0.768,0.608 0.845,0.769,0.613 0.845,0.771,0.617 0.845,0.772,0.621 0.845,0.773,0.625 0.845,0.774,0.630 0.844,0.776,0.634 0.844,0.777,0.638 0.844,0.778,0.642 0.844,0.779,0.646 0.844,0.780,0.651 0.844,0.782,0.655 0.843,0.783,0.659 0.843,0.784,0.663 0.843,0.785,0.668 0.843,0.787,0.672 0.843,0.788,0.676 0.843,0.789,0.680 0.842,0.790,0.684 0.842,0.791,0.689 0.842,0.793,0.693 0.842,0.794,0.697 0.842,0.795,0.701 0.841,0.796,0.706 0.841,0.797,0.710 0.841,0.799,0.714 0.841,0.800,0.718 0.841,0.801,0.722 0.840,0.802,0.727 0.840,0.804,0.731 0.840,0.805,0.735 0.840,0.806,0.739 0.839,0.807,0.744 0.839,0.808,0.748 0.839,0.810,0.752 0.838,0.811,0.756 0.838,0.812,0.760 0.838,0.813,0.765 0.838,0.814,0.769 0.837,0.816,0.773 0.837,0.817,0.777 0.837,0.818,0.782 0.836,0.819,0.786 0.836,0.820,0.790 0.836,0.822,0.794 0.835,0.823,0.798 0.835,0.824,0.803 0.835,0.825,0.807 0.834,0.826,0.811 0.834,0.828,0.815 0.833,0.829,0.820 0.833,0.830,0.824 0.833,0.831,0.828 0.832,0.832,0.832 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L11.csv000066400000000000000000000110001421045507400235670ustar00rootroot000000000000000.438,0.678,0.361 0.443,0.678,0.361 0.449,0.679,0.361 0.454,0.679,0.362 0.459,0.679,0.362 0.465,0.680,0.363 0.470,0.680,0.363 0.475,0.680,0.364 0.480,0.680,0.364 0.485,0.681,0.365 0.491,0.681,0.365 0.496,0.681,0.366 0.501,0.682,0.366 0.506,0.682,0.366 0.511,0.682,0.367 0.515,0.682,0.367 0.520,0.683,0.368 0.525,0.683,0.368 0.530,0.683,0.369 0.535,0.683,0.369 0.539,0.684,0.370 0.544,0.684,0.370 0.549,0.684,0.371 0.554,0.684,0.371 0.558,0.685,0.372 0.563,0.685,0.372 0.567,0.685,0.373 0.572,0.685,0.373 0.577,0.686,0.374 0.581,0.686,0.374 0.585,0.686,0.374 0.590,0.686,0.375 0.594,0.687,0.375 0.599,0.687,0.376 0.603,0.687,0.376 0.608,0.687,0.377 0.612,0.687,0.377 0.616,0.688,0.378 0.621,0.688,0.378 0.625,0.688,0.379 0.629,0.688,0.379 0.633,0.689,0.380 0.638,0.689,0.380 0.642,0.689,0.381 0.646,0.689,0.381 0.650,0.689,0.382 0.654,0.690,0.382 0.658,0.690,0.383 0.662,0.690,0.383 0.666,0.690,0.384 0.671,0.690,0.384 0.675,0.691,0.385 0.679,0.691,0.385 0.683,0.691,0.386 0.687,0.691,0.386 0.691,0.691,0.387 0.694,0.692,0.387 0.698,0.692,0.388 0.702,0.692,0.388 0.706,0.692,0.389 0.710,0.692,0.389 0.714,0.693,0.390 0.718,0.693,0.390 0.722,0.693,0.391 0.725,0.693,0.391 0.729,0.694,0.392 0.733,0.694,0.392 0.737,0.694,0.393 0.740,0.694,0.393 0.744,0.694,0.394 0.748,0.695,0.394 0.751,0.695,0.395 0.755,0.695,0.395 0.759,0.695,0.396 0.762,0.695,0.396 0.766,0.696,0.397 0.769,0.696,0.398 0.773,0.696,0.398 0.776,0.696,0.399 0.780,0.697,0.399 0.783,0.697,0.400 0.787,0.697,0.400 0.790,0.697,0.401 0.794,0.698,0.401 0.797,0.698,0.402 0.800,0.698,0.402 0.804,0.698,0.403 0.807,0.699,0.403 0.810,0.699,0.404 0.814,0.699,0.405 0.817,0.700,0.405 0.820,0.700,0.406 0.823,0.700,0.406 0.826,0.700,0.407 0.830,0.701,0.407 0.833,0.701,0.408 0.836,0.701,0.408 0.839,0.702,0.409 0.842,0.702,0.410 0.845,0.702,0.410 0.848,0.703,0.411 0.851,0.703,0.411 0.853,0.704,0.412 0.856,0.704,0.412 0.859,0.704,0.413 0.862,0.705,0.414 0.864,0.705,0.414 0.867,0.706,0.415 0.870,0.706,0.415 0.872,0.707,0.416 0.875,0.707,0.417 0.877,0.708,0.417 0.879,0.709,0.418 0.882,0.709,0.418 0.884,0.710,0.419 0.886,0.710,0.420 0.888,0.711,0.420 0.890,0.712,0.421 0.892,0.713,0.421 0.894,0.713,0.422 0.896,0.714,0.423 0.897,0.715,0.423 0.899,0.716,0.424 0.900,0.717,0.425 0.901,0.718,0.425 0.902,0.719,0.426 0.903,0.720,0.427 0.903,0.722,0.428 0.904,0.723,0.428 0.904,0.725,0.429 0.904,0.726,0.430 0.904,0.727,0.431 0.904,0.729,0.432 0.905,0.730,0.433 0.905,0.732,0.434 0.905,0.733,0.436 0.905,0.734,0.437 0.905,0.736,0.438 0.906,0.737,0.440 0.906,0.739,0.441 0.906,0.740,0.443 0.906,0.741,0.445 0.906,0.743,0.446 0.906,0.744,0.448 0.906,0.746,0.450 0.907,0.747,0.452 0.907,0.748,0.454 0.907,0.750,0.456 0.907,0.751,0.458 0.907,0.753,0.461 0.907,0.754,0.463 0.907,0.755,0.465 0.907,0.757,0.468 0.907,0.758,0.471 0.907,0.759,0.473 0.908,0.761,0.476 0.908,0.762,0.479 0.908,0.763,0.481 0.908,0.765,0.484 0.908,0.766,0.487 0.908,0.768,0.490 0.908,0.769,0.494 0.908,0.770,0.497 0.908,0.772,0.500 0.908,0.773,0.503 0.908,0.774,0.507 0.907,0.776,0.510 0.907,0.777,0.514 0.907,0.778,0.517 0.907,0.780,0.521 0.907,0.781,0.525 0.907,0.782,0.529 0.907,0.784,0.532 0.907,0.785,0.536 0.907,0.786,0.540 0.906,0.788,0.545 0.906,0.789,0.549 0.906,0.790,0.553 0.906,0.792,0.557 0.905,0.793,0.562 0.905,0.794,0.566 0.905,0.796,0.570 0.905,0.797,0.575 0.905,0.798,0.579 0.904,0.800,0.583 0.904,0.801,0.588 0.904,0.802,0.592 0.904,0.804,0.596 0.904,0.805,0.601 0.903,0.806,0.605 0.903,0.807,0.609 0.903,0.809,0.614 0.903,0.810,0.618 0.903,0.811,0.622 0.903,0.813,0.627 0.902,0.814,0.631 0.902,0.815,0.635 0.902,0.816,0.639 0.902,0.818,0.644 0.902,0.819,0.648 0.902,0.820,0.652 0.901,0.821,0.657 0.901,0.823,0.661 0.901,0.824,0.665 0.901,0.825,0.669 0.901,0.826,0.674 0.901,0.828,0.678 0.900,0.829,0.682 0.900,0.830,0.687 0.900,0.831,0.691 0.900,0.833,0.695 0.900,0.834,0.699 0.900,0.835,0.704 0.899,0.836,0.708 0.899,0.838,0.712 0.899,0.839,0.717 0.899,0.840,0.721 0.899,0.841,0.725 0.898,0.842,0.729 0.898,0.844,0.734 0.898,0.845,0.738 0.898,0.846,0.742 0.898,0.847,0.746 0.897,0.849,0.751 0.897,0.850,0.755 0.897,0.851,0.759 0.897,0.852,0.763 0.897,0.854,0.768 0.896,0.855,0.772 0.896,0.856,0.776 0.896,0.857,0.781 0.896,0.858,0.785 0.895,0.860,0.789 0.895,0.861,0.793 0.895,0.862,0.798 0.894,0.863,0.802 0.894,0.865,0.806 0.894,0.866,0.810 0.894,0.867,0.815 0.893,0.868,0.819 0.893,0.869,0.823 0.893,0.871,0.828 0.892,0.872,0.832 0.892,0.873,0.836 0.892,0.874,0.840 0.891,0.875,0.845 0.891,0.877,0.849 0.891,0.878,0.853 0.890,0.879,0.858 0.890,0.880,0.862 0.889,0.882,0.866 0.889,0.883,0.870 0.889,0.884,0.875 0.888,0.885,0.879 0.888,0.886,0.883 0.887,0.888,0.888 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L12.csv000066400000000000000000000110001421045507400235700ustar00rootroot000000000000000.943,0.944,0.943 0.940,0.942,0.943 0.937,0.940,0.943 0.934,0.938,0.943 0.931,0.936,0.943 0.928,0.934,0.942 0.925,0.932,0.942 0.922,0.930,0.942 0.919,0.929,0.942 0.917,0.927,0.941 0.914,0.925,0.941 0.911,0.923,0.941 0.908,0.921,0.941 0.905,0.919,0.940 0.902,0.917,0.940 0.899,0.916,0.940 0.896,0.914,0.939 0.893,0.912,0.939 0.890,0.910,0.939 0.887,0.908,0.939 0.884,0.906,0.938 0.881,0.904,0.938 0.878,0.903,0.938 0.875,0.901,0.937 0.872,0.899,0.937 0.869,0.897,0.937 0.866,0.895,0.937 0.863,0.893,0.936 0.860,0.892,0.936 0.857,0.890,0.936 0.854,0.888,0.935 0.851,0.886,0.935 0.848,0.884,0.935 0.845,0.882,0.934 0.842,0.880,0.934 0.839,0.879,0.934 0.836,0.877,0.933 0.833,0.875,0.933 0.830,0.873,0.933 0.827,0.871,0.932 0.824,0.869,0.932 0.821,0.868,0.931 0.818,0.866,0.931 0.815,0.864,0.931 0.812,0.862,0.930 0.809,0.860,0.930 0.806,0.858,0.930 0.803,0.857,0.929 0.800,0.855,0.929 0.797,0.853,0.928 0.794,0.851,0.928 0.791,0.849,0.928 0.788,0.847,0.927 0.785,0.846,0.927 0.782,0.844,0.926 0.779,0.842,0.926 0.776,0.840,0.925 0.773,0.838,0.925 0.770,0.836,0.925 0.767,0.835,0.924 0.764,0.833,0.924 0.761,0.831,0.923 0.758,0.829,0.923 0.755,0.827,0.922 0.752,0.825,0.922 0.749,0.824,0.921 0.746,0.822,0.921 0.743,0.820,0.920 0.740,0.818,0.920 0.737,0.816,0.919 0.734,0.815,0.919 0.731,0.813,0.918 0.728,0.811,0.918 0.725,0.809,0.917 0.722,0.807,0.917 0.719,0.805,0.916 0.716,0.804,0.915 0.713,0.802,0.915 0.710,0.800,0.914 0.707,0.798,0.914 0.704,0.796,0.913 0.701,0.794,0.912 0.698,0.793,0.912 0.695,0.791,0.911 0.692,0.789,0.911 0.689,0.787,0.910 0.686,0.785,0.909 0.684,0.784,0.909 0.681,0.782,0.908 0.678,0.780,0.907 0.675,0.778,0.906 0.672,0.776,0.906 0.669,0.774,0.905 0.666,0.773,0.904 0.663,0.771,0.903 0.660,0.769,0.903 0.658,0.767,0.902 0.655,0.765,0.901 0.652,0.764,0.900 0.649,0.762,0.899 0.646,0.760,0.898 0.643,0.758,0.897 0.641,0.756,0.897 0.638,0.754,0.896 0.635,0.753,0.895 0.632,0.751,0.894 0.630,0.749,0.893 0.627,0.747,0.892 0.624,0.745,0.891 0.622,0.743,0.889 0.619,0.742,0.888 0.617,0.740,0.887 0.614,0.738,0.886 0.612,0.736,0.884 0.609,0.734,0.883 0.607,0.732,0.882 0.604,0.731,0.880 0.602,0.729,0.879 0.600,0.727,0.878 0.597,0.725,0.876 0.595,0.723,0.875 0.592,0.721,0.874 0.590,0.720,0.872 0.588,0.718,0.871 0.585,0.716,0.869 0.583,0.714,0.868 0.581,0.712,0.866 0.578,0.710,0.865 0.576,0.708,0.863 0.574,0.707,0.862 0.571,0.705,0.860 0.569,0.703,0.859 0.567,0.701,0.857 0.565,0.699,0.856 0.562,0.697,0.854 0.560,0.696,0.853 0.558,0.694,0.851 0.556,0.692,0.849 0.553,0.690,0.848 0.551,0.688,0.846 0.549,0.686,0.845 0.547,0.685,0.843 0.545,0.683,0.841 0.542,0.681,0.840 0.540,0.679,0.838 0.538,0.677,0.836 0.536,0.675,0.835 0.534,0.673,0.833 0.532,0.672,0.831 0.530,0.670,0.830 0.528,0.668,0.828 0.526,0.666,0.826 0.524,0.664,0.824 0.521,0.662,0.823 0.519,0.661,0.821 0.517,0.659,0.819 0.515,0.657,0.817 0.513,0.655,0.816 0.511,0.653,0.814 0.509,0.651,0.812 0.507,0.650,0.810 0.505,0.648,0.808 0.503,0.646,0.807 0.502,0.644,0.805 0.500,0.642,0.803 0.498,0.640,0.801 0.496,0.639,0.799 0.494,0.637,0.797 0.492,0.635,0.795 0.490,0.633,0.793 0.488,0.631,0.791 0.486,0.629,0.790 0.484,0.628,0.788 0.482,0.626,0.786 0.480,0.624,0.784 0.478,0.622,0.782 0.476,0.620,0.781 0.474,0.618,0.779 0.472,0.617,0.777 0.470,0.615,0.776 0.468,0.613,0.774 0.466,0.611,0.773 0.463,0.609,0.771 0.461,0.608,0.769 0.459,0.606,0.768 0.456,0.604,0.767 0.454,0.602,0.765 0.452,0.600,0.764 0.449,0.599,0.762 0.447,0.597,0.761 0.444,0.595,0.760 0.442,0.593,0.758 0.439,0.592,0.757 0.437,0.590,0.756 0.434,0.588,0.754 0.432,0.586,0.753 0.429,0.585,0.752 0.427,0.583,0.751 0.424,0.581,0.749 0.421,0.579,0.748 0.419,0.578,0.747 0.416,0.576,0.746 0.413,0.574,0.745 0.410,0.572,0.744 0.408,0.571,0.742 0.405,0.569,0.741 0.402,0.567,0.740 0.399,0.565,0.739 0.396,0.564,0.738 0.394,0.562,0.737 0.391,0.560,0.736 0.388,0.558,0.735 0.385,0.557,0.734 0.382,0.555,0.733 0.379,0.553,0.732 0.376,0.552,0.731 0.373,0.550,0.730 0.370,0.548,0.729 0.367,0.546,0.728 0.364,0.545,0.727 0.361,0.543,0.726 0.357,0.541,0.725 0.354,0.540,0.724 0.351,0.538,0.723 0.348,0.536,0.722 0.345,0.534,0.721 0.341,0.533,0.721 0.338,0.531,0.720 0.335,0.529,0.719 0.331,0.528,0.718 0.328,0.526,0.717 0.324,0.524,0.716 0.321,0.523,0.715 0.318,0.521,0.715 0.314,0.519,0.714 0.310,0.518,0.713 0.307,0.516,0.712 0.303,0.514,0.711 0.300,0.513,0.710 0.296,0.511,0.710 0.292,0.509,0.709 0.288,0.508,0.708 0.285,0.506,0.707 0.281,0.504,0.706 0.277,0.503,0.706 0.273,0.501,0.705 0.269,0.499,0.704 0.265,0.498,0.703 0.260,0.496,0.703 0.256,0.494,0.702 0.252,0.493,0.701 0.248,0.491,0.700 0.243,0.489,0.700 0.239,0.488,0.699 0.234,0.486,0.698 0.230,0.484,0.698 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L13.csv000066400000000000000000000110001421045507400235710ustar00rootroot000000000000000.000,0.000,0.000 0.008,0.002,0.000 0.016,0.003,0.000 0.024,0.005,0.000 0.032,0.006,0.000 0.040,0.008,0.000 0.048,0.009,0.000 0.055,0.010,0.000 0.062,0.012,0.000 0.069,0.013,0.000 0.075,0.014,0.000 0.081,0.015,0.000 0.086,0.016,0.000 0.092,0.017,0.000 0.097,0.018,0.000 0.102,0.019,0.000 0.107,0.020,0.000 0.112,0.021,0.000 0.116,0.022,0.000 0.121,0.023,0.000 0.125,0.024,0.000 0.129,0.024,0.000 0.133,0.025,0.000 0.137,0.026,0.000 0.141,0.027,0.000 0.145,0.027,0.000 0.149,0.028,0.000 0.152,0.029,0.000 0.156,0.029,0.000 0.159,0.030,0.000 0.163,0.031,0.000 0.166,0.031,0.000 0.169,0.032,0.000 0.173,0.033,0.000 0.176,0.033,0.000 0.179,0.034,0.000 0.182,0.034,0.000 0.185,0.035,0.000 0.188,0.036,0.000 0.191,0.036,0.000 0.194,0.037,0.000 0.197,0.037,0.000 0.200,0.038,0.000 0.203,0.038,0.000 0.206,0.039,0.000 0.209,0.039,0.000 0.212,0.040,0.000 0.215,0.041,0.000 0.218,0.041,0.000 0.221,0.042,0.000 0.223,0.042,0.000 0.226,0.043,0.000 0.229,0.043,0.000 0.232,0.044,0.000 0.235,0.044,0.000 0.238,0.045,0.000 0.241,0.046,0.000 0.244,0.046,0.000 0.247,0.047,0.000 0.250,0.047,0.000 0.253,0.048,0.000 0.256,0.048,0.000 0.259,0.049,0.000 0.262,0.049,0.000 0.265,0.050,0.000 0.268,0.051,0.000 0.271,0.051,0.000 0.274,0.052,0.000 0.277,0.052,0.000 0.280,0.053,0.000 0.283,0.053,0.000 0.286,0.054,0.000 0.289,0.055,0.000 0.292,0.055,0.000 0.295,0.056,0.000 0.298,0.056,0.000 0.301,0.057,0.000 0.304,0.057,0.000 0.307,0.058,0.000 0.310,0.059,0.000 0.313,0.059,0.000 0.316,0.060,0.000 0.319,0.060,0.000 0.322,0.061,0.000 0.325,0.061,0.000 0.328,0.062,0.000 0.332,0.063,0.000 0.335,0.063,0.000 0.338,0.064,0.000 0.341,0.064,0.000 0.344,0.065,0.000 0.347,0.066,0.000 0.350,0.066,0.000 0.353,0.067,0.000 0.356,0.067,0.000 0.359,0.068,0.000 0.363,0.068,0.000 0.366,0.069,0.000 0.369,0.070,0.000 0.372,0.070,0.000 0.375,0.071,0.000 0.378,0.071,0.000 0.381,0.072,0.000 0.384,0.073,0.000 0.388,0.073,0.000 0.391,0.074,0.000 0.394,0.074,0.000 0.397,0.075,0.000 0.400,0.076,0.000 0.403,0.076,0.000 0.406,0.077,0.000 0.410,0.077,0.000 0.413,0.078,0.000 0.416,0.079,0.000 0.419,0.079,0.000 0.422,0.080,0.000 0.426,0.080,0.000 0.429,0.081,0.000 0.432,0.082,0.000 0.435,0.082,0.000 0.438,0.083,0.000 0.441,0.083,0.000 0.445,0.084,0.000 0.448,0.085,0.000 0.451,0.085,0.000 0.454,0.086,0.000 0.458,0.086,0.000 0.461,0.087,0.000 0.464,0.088,0.000 0.467,0.088,0.000 0.470,0.089,0.000 0.474,0.089,0.000 0.477,0.090,0.000 0.480,0.091,0.000 0.483,0.091,0.000 0.487,0.092,0.000 0.490,0.093,0.000 0.493,0.093,0.000 0.496,0.094,0.000 0.500,0.094,0.000 0.503,0.095,0.000 0.506,0.096,0.000 0.510,0.096,0.000 0.513,0.097,0.000 0.516,0.098,0.000 0.519,0.098,0.000 0.523,0.099,0.000 0.526,0.099,0.000 0.529,0.100,0.000 0.533,0.101,0.000 0.536,0.101,0.000 0.539,0.102,0.000 0.543,0.102,0.000 0.546,0.103,0.000 0.549,0.104,0.000 0.552,0.104,0.000 0.556,0.105,0.000 0.559,0.106,0.000 0.563,0.106,0.000 0.566,0.107,0.000 0.569,0.108,0.000 0.572,0.108,0.000 0.576,0.109,0.000 0.579,0.109,0.000 0.583,0.110,0.000 0.586,0.111,0.000 0.589,0.111,0.000 0.593,0.112,0.000 0.596,0.113,0.000 0.599,0.113,0.000 0.603,0.114,0.000 0.606,0.114,0.000 0.609,0.115,0.000 0.613,0.116,0.000 0.616,0.116,0.000 0.620,0.117,0.000 0.623,0.118,0.000 0.626,0.118,0.000 0.630,0.119,0.000 0.633,0.120,0.000 0.637,0.120,0.000 0.640,0.121,0.000 0.643,0.122,0.000 0.647,0.122,0.000 0.650,0.123,0.000 0.654,0.123,0.000 0.657,0.124,0.000 0.660,0.125,0.000 0.664,0.125,0.000 0.667,0.126,0.000 0.671,0.127,0.000 0.674,0.127,0.000 0.678,0.128,0.000 0.681,0.129,0.000 0.685,0.129,0.000 0.688,0.130,0.000 0.691,0.131,0.000 0.695,0.131,0.000 0.698,0.132,0.000 0.702,0.133,0.000 0.705,0.133,0.000 0.709,0.134,0.000 0.712,0.135,0.000 0.716,0.135,0.000 0.719,0.136,0.000 0.723,0.136,0.000 0.726,0.137,0.000 0.730,0.138,0.000 0.733,0.138,0.000 0.737,0.139,0.000 0.740,0.140,0.000 0.744,0.140,0.000 0.747,0.141,0.000 0.751,0.142,0.000 0.754,0.142,0.000 0.758,0.143,0.000 0.761,0.144,0.000 0.765,0.144,0.000 0.768,0.145,0.000 0.772,0.146,0.000 0.775,0.146,0.000 0.779,0.147,0.000 0.782,0.148,0.000 0.786,0.148,0.000 0.789,0.149,0.000 0.793,0.150,0.000 0.796,0.150,0.000 0.800,0.151,0.000 0.803,0.152,0.000 0.807,0.152,0.000 0.810,0.153,0.000 0.814,0.154,0.000 0.818,0.154,0.000 0.821,0.155,0.000 0.825,0.156,0.000 0.828,0.156,0.000 0.832,0.157,0.000 0.835,0.158,0.000 0.839,0.158,0.000 0.842,0.159,0.000 0.846,0.160,0.000 0.850,0.160,0.000 0.853,0.161,0.000 0.857,0.162,0.000 0.860,0.163,0.000 0.864,0.163,0.000 0.868,0.164,0.000 0.871,0.165,0.000 0.875,0.165,0.000 0.878,0.166,0.000 0.882,0.167,0.000 0.886,0.167,0.000 0.889,0.168,0.000 0.893,0.169,0.000 0.896,0.169,0.000 0.900,0.170,0.000 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L14.csv000066400000000000000000000110001421045507400235720ustar00rootroot000000000000000.000,0.000,0.000 0.000,0.004,0.000 0.000,0.007,0.000 0.000,0.011,0.000 0.000,0.015,0.000 0.000,0.018,0.000 0.000,0.022,0.000 0.000,0.025,0.000 0.000,0.029,0.000 0.000,0.032,0.000 0.000,0.036,0.000 0.000,0.040,0.000 0.000,0.043,0.000 0.000,0.047,0.000 0.000,0.050,0.000 0.000,0.053,0.000 0.000,0.056,0.000 0.000,0.059,0.000 0.000,0.061,0.000 0.000,0.064,0.000 0.000,0.067,0.000 0.000,0.069,0.000 0.000,0.071,0.000 0.000,0.074,0.000 0.000,0.076,0.000 0.000,0.078,0.000 0.000,0.081,0.000 0.000,0.083,0.000 0.000,0.085,0.000 0.000,0.087,0.000 0.000,0.089,0.000 0.000,0.091,0.000 0.000,0.093,0.000 0.000,0.095,0.000 0.000,0.097,0.000 0.000,0.098,0.000 0.000,0.100,0.000 0.000,0.102,0.000 0.000,0.104,0.000 0.000,0.106,0.000 0.000,0.107,0.000 0.000,0.109,0.000 0.000,0.111,0.000 0.000,0.112,0.000 0.000,0.114,0.000 0.000,0.115,0.000 0.000,0.117,0.000 0.000,0.119,0.000 0.000,0.120,0.000 0.000,0.122,0.000 0.000,0.123,0.000 0.000,0.125,0.000 0.000,0.127,0.000 0.000,0.128,0.000 0.000,0.130,0.000 0.000,0.132,0.000 0.000,0.133,0.000 0.000,0.135,0.000 0.000,0.136,0.000 0.000,0.138,0.000 0.000,0.140,0.000 0.000,0.141,0.000 0.000,0.143,0.000 0.000,0.145,0.000 0.000,0.146,0.000 0.000,0.148,0.000 0.000,0.150,0.000 0.000,0.151,0.000 0.000,0.153,0.000 0.000,0.154,0.000 0.000,0.156,0.000 0.000,0.158,0.000 0.000,0.159,0.000 0.000,0.161,0.000 0.000,0.163,0.000 0.000,0.165,0.000 0.000,0.166,0.000 0.000,0.168,0.000 0.000,0.170,0.000 0.000,0.171,0.000 0.000,0.173,0.000 0.000,0.175,0.000 0.000,0.176,0.000 0.000,0.178,0.000 0.000,0.180,0.000 0.000,0.181,0.000 0.000,0.183,0.000 0.000,0.185,0.000 0.000,0.187,0.000 0.000,0.188,0.000 0.000,0.190,0.000 0.000,0.192,0.000 0.000,0.193,0.000 0.000,0.195,0.000 0.000,0.197,0.000 0.000,0.199,0.000 0.000,0.200,0.000 0.000,0.202,0.000 0.000,0.204,0.000 0.000,0.205,0.000 0.000,0.207,0.000 0.000,0.209,0.000 0.000,0.211,0.000 0.000,0.212,0.000 0.000,0.214,0.000 0.000,0.216,0.000 0.000,0.218,0.000 0.000,0.219,0.000 0.000,0.221,0.000 0.000,0.223,0.000 0.000,0.225,0.000 0.000,0.226,0.000 0.000,0.228,0.000 0.000,0.230,0.000 0.000,0.232,0.000 0.000,0.234,0.000 0.000,0.235,0.000 0.000,0.237,0.000 0.000,0.239,0.000 0.000,0.241,0.000 0.000,0.242,0.000 0.000,0.244,0.000 0.000,0.246,0.000 0.000,0.248,0.000 0.000,0.250,0.000 0.000,0.251,0.000 0.000,0.253,0.000 0.000,0.255,0.000 0.000,0.257,0.000 0.000,0.259,0.000 0.000,0.260,0.000 0.000,0.262,0.000 0.000,0.264,0.000 0.000,0.266,0.000 0.000,0.268,0.000 0.000,0.269,0.000 0.000,0.271,0.000 0.000,0.273,0.000 0.000,0.275,0.000 0.000,0.277,0.000 0.000,0.278,0.000 0.000,0.280,0.000 0.000,0.282,0.000 0.000,0.284,0.000 0.000,0.286,0.000 0.000,0.288,0.000 0.000,0.289,0.000 0.000,0.291,0.000 0.000,0.293,0.000 0.000,0.295,0.000 0.000,0.297,0.000 0.000,0.299,0.000 0.000,0.300,0.000 0.000,0.302,0.000 0.000,0.304,0.000 0.000,0.306,0.000 0.000,0.308,0.000 0.000,0.310,0.000 0.000,0.312,0.000 0.000,0.313,0.000 0.000,0.315,0.000 0.000,0.317,0.000 0.000,0.319,0.000 0.000,0.321,0.000 0.000,0.323,0.000 0.000,0.325,0.000 0.000,0.327,0.000 0.000,0.328,0.000 0.000,0.330,0.000 0.000,0.332,0.000 0.000,0.334,0.000 0.000,0.336,0.000 0.000,0.338,0.000 0.000,0.340,0.000 0.000,0.342,0.000 0.000,0.343,0.000 0.000,0.345,0.000 0.000,0.347,0.000 0.000,0.349,0.000 0.000,0.351,0.000 0.000,0.353,0.000 0.000,0.355,0.000 0.000,0.357,0.000 0.000,0.359,0.000 0.000,0.361,0.000 0.000,0.362,0.000 0.000,0.364,0.000 0.000,0.366,0.000 0.000,0.368,0.000 0.000,0.370,0.000 0.000,0.372,0.000 0.000,0.374,0.000 0.000,0.376,0.000 0.000,0.378,0.000 0.000,0.380,0.000 0.000,0.382,0.000 0.000,0.384,0.000 0.000,0.385,0.000 0.000,0.387,0.000 0.000,0.389,0.000 0.000,0.391,0.000 0.000,0.393,0.000 0.000,0.395,0.000 0.000,0.397,0.000 0.000,0.399,0.000 0.000,0.401,0.000 0.000,0.403,0.000 0.000,0.405,0.000 0.000,0.407,0.000 0.000,0.409,0.000 0.000,0.411,0.000 0.000,0.413,0.000 0.000,0.415,0.000 0.000,0.417,0.000 0.000,0.418,0.000 0.000,0.420,0.000 0.000,0.422,0.000 0.000,0.424,0.000 0.000,0.426,0.000 0.000,0.428,0.000 0.000,0.430,0.000 0.000,0.432,0.000 0.000,0.434,0.000 0.000,0.436,0.000 0.000,0.438,0.000 0.000,0.440,0.000 0.000,0.442,0.000 0.000,0.444,0.000 0.000,0.446,0.000 0.000,0.448,0.000 0.000,0.450,0.000 0.000,0.452,0.000 0.000,0.454,0.000 0.000,0.456,0.000 0.000,0.458,0.000 0.000,0.460,0.000 0.000,0.462,0.000 0.000,0.464,0.000 0.000,0.466,0.000 0.000,0.468,0.000 0.000,0.470,0.000 0.000,0.472,0.000 0.000,0.474,0.000 0.000,0.476,0.000 0.000,0.478,0.000 0.000,0.480,0.000 0.000,0.482,0.000 0.000,0.484,0.000 0.000,0.486,0.000 0.000,0.488,0.000 0.000,0.490,0.000 0.000,0.492,0.000 0.000,0.494,0.000 0.000,0.496,0.000 0.000,0.498,0.000 0.000,0.500,0.000 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L15.csv000066400000000000000000000110001421045507400235730ustar00rootroot000000000000000.000,0.000,0.000 0.001,0.002,0.007 0.001,0.005,0.015 0.002,0.007,0.022 0.003,0.010,0.030 0.004,0.012,0.038 0.004,0.015,0.045 0.005,0.017,0.052 0.006,0.020,0.059 0.007,0.022,0.066 0.007,0.024,0.073 0.008,0.026,0.079 0.009,0.028,0.086 0.009,0.030,0.092 0.010,0.032,0.098 0.010,0.034,0.105 0.011,0.037,0.111 0.012,0.038,0.117 0.012,0.040,0.122 0.013,0.042,0.128 0.013,0.044,0.133 0.014,0.046,0.138 0.014,0.047,0.144 0.015,0.049,0.149 0.015,0.051,0.153 0.016,0.052,0.158 0.016,0.054,0.163 0.017,0.055,0.167 0.017,0.057,0.172 0.018,0.058,0.176 0.018,0.059,0.180 0.018,0.061,0.184 0.019,0.062,0.188 0.019,0.063,0.192 0.020,0.065,0.196 0.020,0.066,0.200 0.020,0.067,0.204 0.021,0.069,0.208 0.021,0.070,0.211 0.021,0.071,0.215 0.022,0.072,0.218 0.022,0.073,0.222 0.023,0.074,0.225 0.023,0.075,0.229 0.023,0.077,0.232 0.024,0.078,0.235 0.024,0.079,0.238 0.024,0.080,0.242 0.025,0.081,0.245 0.025,0.082,0.248 0.025,0.083,0.252 0.025,0.084,0.255 0.026,0.085,0.258 0.026,0.086,0.261 0.026,0.087,0.265 0.027,0.088,0.268 0.027,0.089,0.271 0.027,0.091,0.274 0.028,0.092,0.278 0.028,0.093,0.281 0.028,0.094,0.284 0.029,0.095,0.288 0.029,0.096,0.291 0.029,0.097,0.294 0.030,0.098,0.297 0.030,0.099,0.301 0.030,0.100,0.304 0.031,0.101,0.307 0.031,0.103,0.311 0.031,0.104,0.314 0.032,0.105,0.317 0.032,0.106,0.321 0.032,0.107,0.324 0.033,0.108,0.327 0.033,0.109,0.331 0.033,0.110,0.334 0.034,0.111,0.338 0.034,0.113,0.341 0.034,0.114,0.344 0.035,0.115,0.348 0.035,0.116,0.351 0.035,0.117,0.354 0.036,0.118,0.358 0.036,0.119,0.361 0.036,0.120,0.365 0.037,0.121,0.368 0.037,0.123,0.371 0.037,0.124,0.375 0.038,0.125,0.378 0.038,0.126,0.382 0.039,0.127,0.385 0.039,0.128,0.389 0.039,0.129,0.392 0.040,0.131,0.395 0.040,0.132,0.399 0.040,0.133,0.402 0.041,0.134,0.406 0.041,0.135,0.409 0.041,0.136,0.413 0.042,0.137,0.416 0.042,0.138,0.420 0.042,0.140,0.423 0.043,0.141,0.427 0.043,0.142,0.430 0.043,0.143,0.434 0.044,0.144,0.437 0.044,0.145,0.441 0.044,0.147,0.444 0.045,0.148,0.448 0.045,0.149,0.451 0.045,0.150,0.455 0.046,0.151,0.458 0.046,0.152,0.462 0.047,0.153,0.465 0.047,0.155,0.469 0.047,0.156,0.472 0.048,0.157,0.476 0.048,0.158,0.479 0.048,0.159,0.483 0.049,0.160,0.486 0.049,0.162,0.490 0.049,0.163,0.493 0.050,0.164,0.497 0.050,0.165,0.500 0.050,0.166,0.504 0.051,0.168,0.508 0.051,0.169,0.511 0.051,0.170,0.515 0.052,0.171,0.518 0.052,0.172,0.522 0.053,0.173,0.526 0.053,0.175,0.529 0.053,0.176,0.533 0.054,0.177,0.536 0.054,0.178,0.540 0.054,0.179,0.543 0.055,0.181,0.547 0.055,0.182,0.551 0.055,0.183,0.554 0.056,0.184,0.558 0.056,0.185,0.562 0.057,0.187,0.565 0.057,0.188,0.569 0.057,0.189,0.572 0.058,0.190,0.576 0.058,0.191,0.580 0.058,0.193,0.583 0.059,0.194,0.587 0.059,0.195,0.591 0.059,0.196,0.594 0.060,0.197,0.598 0.060,0.199,0.602 0.061,0.200,0.605 0.061,0.201,0.609 0.061,0.202,0.613 0.062,0.203,0.616 0.062,0.205,0.620 0.062,0.206,0.624 0.063,0.207,0.627 0.063,0.208,0.631 0.063,0.209,0.635 0.064,0.211,0.638 0.064,0.212,0.642 0.065,0.213,0.646 0.065,0.214,0.650 0.065,0.216,0.653 0.066,0.217,0.657 0.066,0.218,0.661 0.066,0.219,0.664 0.067,0.220,0.668 0.067,0.222,0.672 0.068,0.223,0.676 0.068,0.224,0.679 0.068,0.225,0.683 0.069,0.227,0.687 0.069,0.228,0.691 0.069,0.229,0.694 0.070,0.230,0.698 0.070,0.232,0.702 0.071,0.233,0.705 0.071,0.234,0.709 0.071,0.235,0.713 0.072,0.237,0.717 0.072,0.238,0.721 0.072,0.239,0.724 0.073,0.240,0.728 0.073,0.242,0.732 0.074,0.243,0.736 0.074,0.244,0.739 0.074,0.245,0.743 0.075,0.247,0.747 0.075,0.248,0.751 0.075,0.249,0.755 0.076,0.250,0.758 0.076,0.252,0.762 0.077,0.253,0.766 0.077,0.254,0.770 0.077,0.255,0.774 0.078,0.257,0.777 0.078,0.258,0.781 0.079,0.259,0.785 0.079,0.260,0.789 0.079,0.262,0.793 0.080,0.263,0.797 0.080,0.264,0.800 0.080,0.265,0.804 0.081,0.267,0.808 0.081,0.268,0.812 0.082,0.269,0.816 0.082,0.270,0.820 0.082,0.272,0.823 0.083,0.273,0.827 0.083,0.274,0.831 0.084,0.276,0.835 0.084,0.277,0.839 0.084,0.278,0.843 0.085,0.279,0.847 0.085,0.281,0.851 0.085,0.282,0.854 0.086,0.283,0.858 0.086,0.285,0.862 0.087,0.286,0.866 0.087,0.287,0.870 0.087,0.288,0.874 0.088,0.290,0.878 0.088,0.291,0.882 0.089,0.292,0.886 0.089,0.294,0.889 0.089,0.295,0.893 0.090,0.296,0.897 0.090,0.297,0.901 0.091,0.299,0.905 0.091,0.300,0.909 0.091,0.301,0.913 0.092,0.303,0.917 0.092,0.304,0.921 0.092,0.305,0.925 0.093,0.306,0.929 0.093,0.308,0.933 0.094,0.309,0.937 0.094,0.310,0.941 0.094,0.312,0.944 0.095,0.313,0.948 0.095,0.314,0.952 0.096,0.316,0.956 0.096,0.317,0.960 0.096,0.318,0.964 0.097,0.320,0.968 0.097,0.321,0.972 0.098,0.322,0.976 0.098,0.323,0.980 0.098,0.325,0.984 0.099,0.326,0.988 0.099,0.327,0.992 0.100,0.329,0.996 0.100,0.330,1.000 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L16.csv000066400000000000000000000110001421045507400235740ustar00rootroot000000000000000.108,0.108,0.108 0.115,0.108,0.125 0.121,0.107,0.142 0.127,0.107,0.159 0.132,0.107,0.176 0.137,0.106,0.193 0.141,0.106,0.210 0.145,0.105,0.226 0.148,0.104,0.243 0.151,0.104,0.260 0.154,0.103,0.276 0.156,0.103,0.292 0.158,0.102,0.308 0.159,0.101,0.324 0.160,0.101,0.340 0.161,0.100,0.356 0.161,0.100,0.371 0.161,0.099,0.387 0.161,0.099,0.402 0.160,0.098,0.416 0.159,0.098,0.431 0.158,0.098,0.445 0.156,0.098,0.460 0.154,0.098,0.473 0.152,0.098,0.487 0.150,0.098,0.500 0.147,0.099,0.513 0.144,0.099,0.526 0.140,0.100,0.539 0.137,0.101,0.551 0.133,0.102,0.562 0.129,0.104,0.574 0.124,0.106,0.585 0.120,0.107,0.596 0.115,0.110,0.606 0.110,0.112,0.616 0.105,0.114,0.625 0.099,0.117,0.634 0.094,0.120,0.643 0.088,0.124,0.651 0.083,0.127,0.659 0.077,0.131,0.666 0.071,0.135,0.673 0.066,0.139,0.679 0.060,0.143,0.685 0.055,0.148,0.690 0.050,0.152,0.695 0.045,0.157,0.699 0.041,0.163,0.702 0.038,0.168,0.705 0.036,0.173,0.708 0.034,0.179,0.709 0.033,0.184,0.711 0.032,0.190,0.712 0.031,0.196,0.714 0.030,0.201,0.715 0.029,0.207,0.716 0.028,0.212,0.717 0.028,0.217,0.718 0.027,0.223,0.719 0.027,0.228,0.720 0.026,0.233,0.720 0.026,0.239,0.721 0.026,0.244,0.721 0.026,0.250,0.721 0.026,0.255,0.721 0.026,0.260,0.721 0.026,0.266,0.721 0.026,0.271,0.720 0.026,0.276,0.720 0.026,0.282,0.719 0.026,0.287,0.718 0.027,0.292,0.717 0.027,0.298,0.716 0.027,0.303,0.714 0.028,0.308,0.713 0.028,0.314,0.711 0.028,0.319,0.709 0.028,0.324,0.707 0.028,0.330,0.704 0.028,0.335,0.701 0.028,0.341,0.698 0.028,0.346,0.695 0.027,0.352,0.692 0.026,0.357,0.688 0.025,0.363,0.684 0.024,0.368,0.680 0.023,0.374,0.675 0.022,0.379,0.670 0.024,0.385,0.665 0.027,0.390,0.659 0.032,0.396,0.654 0.039,0.401,0.648 0.047,0.406,0.642 0.055,0.411,0.635 0.063,0.417,0.629 0.071,0.422,0.622 0.079,0.427,0.615 0.086,0.432,0.608 0.094,0.437,0.601 0.101,0.442,0.594 0.107,0.447,0.586 0.114,0.452,0.578 0.120,0.457,0.570 0.126,0.462,0.562 0.132,0.466,0.554 0.137,0.471,0.546 0.143,0.476,0.538 0.147,0.481,0.529 0.152,0.486,0.520 0.156,0.491,0.511 0.160,0.495,0.503 0.164,0.500,0.493 0.168,0.505,0.484 0.171,0.510,0.475 0.174,0.515,0.465 0.177,0.519,0.456 0.179,0.524,0.446 0.181,0.529,0.436 0.183,0.534,0.426 0.185,0.538,0.415 0.186,0.543,0.405 0.188,0.548,0.394 0.188,0.553,0.384 0.189,0.558,0.373 0.190,0.562,0.363 0.192,0.567,0.352 0.193,0.571,0.343 0.195,0.576,0.333 0.197,0.580,0.324 0.200,0.585,0.315 0.203,0.589,0.306 0.206,0.593,0.297 0.209,0.597,0.289 0.213,0.601,0.281 0.217,0.606,0.273 0.221,0.610,0.265 0.226,0.614,0.257 0.231,0.618,0.250 0.236,0.622,0.242 0.241,0.625,0.235 0.247,0.629,0.228 0.252,0.633,0.221 0.259,0.637,0.214 0.265,0.641,0.207 0.271,0.644,0.200 0.278,0.648,0.194 0.285,0.652,0.187 0.292,0.655,0.180 0.299,0.659,0.174 0.306,0.662,0.168 0.314,0.666,0.161 0.321,0.669,0.155 0.329,0.673,0.149 0.337,0.676,0.143 0.345,0.679,0.136 0.353,0.683,0.130 0.361,0.686,0.124 0.369,0.689,0.118 0.378,0.693,0.112 0.386,0.696,0.106 0.395,0.699,0.100 0.403,0.702,0.095 0.412,0.705,0.089 0.421,0.708,0.083 0.430,0.711,0.077 0.439,0.714,0.071 0.448,0.717,0.065 0.457,0.720,0.059 0.466,0.723,0.053 0.476,0.726,0.048 0.485,0.729,0.042 0.494,0.732,0.036 0.504,0.735,0.031 0.513,0.737,0.027 0.523,0.740,0.023 0.532,0.743,0.019 0.542,0.746,0.017 0.552,0.748,0.014 0.562,0.751,0.012 0.571,0.753,0.011 0.581,0.756,0.010 0.591,0.758,0.010 0.601,0.761,0.009 0.611,0.763,0.009 0.620,0.766,0.010 0.630,0.768,0.010 0.639,0.771,0.010 0.649,0.773,0.011 0.658,0.776,0.012 0.667,0.779,0.014 0.676,0.781,0.015 0.685,0.784,0.017 0.694,0.786,0.019 0.703,0.789,0.021 0.712,0.791,0.024 0.721,0.794,0.026 0.729,0.796,0.030 0.738,0.799,0.033 0.746,0.801,0.037 0.755,0.804,0.041 0.763,0.807,0.046 0.771,0.809,0.050 0.780,0.812,0.055 0.788,0.814,0.060 0.796,0.817,0.064 0.804,0.820,0.069 0.812,0.822,0.074 0.819,0.825,0.079 0.827,0.828,0.084 0.835,0.831,0.090 0.842,0.833,0.095 0.849,0.836,0.100 0.857,0.839,0.106 0.864,0.842,0.112 0.871,0.845,0.118 0.878,0.848,0.124 0.884,0.851,0.130 0.891,0.854,0.136 0.897,0.857,0.142 0.904,0.860,0.149 0.910,0.863,0.156 0.916,0.866,0.163 0.922,0.869,0.170 0.927,0.873,0.177 0.932,0.876,0.185 0.937,0.880,0.193 0.942,0.883,0.201 0.946,0.887,0.210 0.950,0.891,0.219 0.954,0.895,0.228 0.957,0.899,0.238 0.960,0.903,0.248 0.962,0.907,0.260 0.965,0.911,0.273 0.968,0.915,0.288 0.970,0.919,0.304 0.973,0.923,0.322 0.976,0.927,0.341 0.979,0.931,0.362 0.982,0.934,0.385 0.985,0.938,0.408 0.987,0.941,0.434 0.990,0.945,0.461 0.993,0.948,0.490 0.995,0.951,0.521 0.997,0.954,0.554 0.999,0.957,0.589 1.000,0.960,0.626 1.000,0.963,0.666 1.000,0.966,0.709 0.999,0.968,0.755 0.997,0.971,0.804 0.993,0.973,0.858 0.986,0.975,0.915 0.977,0.977,0.977 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L17.csv000066400000000000000000000110001421045507400235750ustar00rootroot000000000000001.000,1.000,1.000 0.998,0.997,0.989 0.996,0.994,0.978 0.995,0.991,0.967 0.993,0.988,0.956 0.991,0.985,0.945 0.989,0.982,0.934 0.987,0.979,0.923 0.985,0.976,0.912 0.983,0.973,0.901 0.981,0.970,0.890 0.979,0.967,0.879 0.977,0.964,0.868 0.974,0.961,0.857 0.972,0.958,0.846 0.970,0.955,0.835 0.968,0.952,0.824 0.965,0.949,0.813 0.963,0.946,0.803 0.961,0.943,0.792 0.958,0.940,0.781 0.956,0.937,0.770 0.953,0.934,0.759 0.951,0.931,0.748 0.950,0.927,0.741 0.951,0.923,0.734 0.951,0.919,0.728 0.951,0.915,0.721 0.951,0.911,0.714 0.951,0.906,0.708 0.951,0.902,0.701 0.951,0.898,0.695 0.951,0.894,0.688 0.951,0.890,0.682 0.951,0.886,0.675 0.951,0.882,0.669 0.951,0.878,0.662 0.951,0.874,0.655 0.951,0.870,0.649 0.951,0.865,0.642 0.950,0.861,0.636 0.950,0.857,0.629 0.950,0.853,0.623 0.950,0.849,0.616 0.950,0.845,0.610 0.949,0.841,0.603 0.949,0.837,0.597 0.949,0.832,0.592 0.950,0.828,0.587 0.950,0.823,0.582 0.951,0.819,0.577 0.951,0.814,0.573 0.952,0.810,0.568 0.952,0.805,0.563 0.953,0.801,0.559 0.953,0.796,0.554 0.954,0.792,0.549 0.954,0.787,0.544 0.955,0.783,0.540 0.955,0.778,0.535 0.955,0.773,0.530 0.956,0.769,0.526 0.956,0.764,0.521 0.956,0.760,0.516 0.956,0.755,0.512 0.957,0.751,0.507 0.957,0.746,0.502 0.957,0.742,0.498 0.957,0.737,0.493 0.957,0.732,0.489 0.958,0.728,0.485 0.958,0.723,0.482 0.958,0.718,0.479 0.959,0.713,0.476 0.959,0.708,0.474 0.959,0.704,0.471 0.960,0.699,0.468 0.960,0.694,0.465 0.960,0.689,0.463 0.960,0.684,0.460 0.961,0.679,0.457 0.961,0.675,0.455 0.961,0.670,0.452 0.961,0.665,0.449 0.961,0.660,0.446 0.961,0.655,0.444 0.962,0.650,0.441 0.962,0.645,0.438 0.962,0.640,0.435 0.962,0.635,0.433 0.962,0.630,0.430 0.962,0.625,0.427 0.962,0.620,0.425 0.962,0.615,0.423 0.962,0.610,0.422 0.961,0.605,0.421 0.961,0.600,0.420 0.961,0.595,0.419 0.961,0.590,0.418 0.960,0.585,0.417 0.960,0.580,0.417 0.960,0.575,0.416 0.959,0.570,0.415 0.959,0.565,0.414 0.959,0.560,0.413 0.958,0.555,0.412 0.958,0.550,0.412 0.957,0.544,0.411 0.957,0.539,0.410 0.957,0.534,0.409 0.956,0.529,0.408 0.956,0.524,0.407 0.955,0.518,0.406 0.955,0.513,0.406 0.954,0.508,0.405 0.954,0.502,0.404 0.953,0.497,0.403 0.952,0.492,0.404 0.951,0.487,0.405 0.950,0.482,0.405 0.948,0.477,0.406 0.947,0.472,0.407 0.946,0.467,0.408 0.945,0.462,0.408 0.943,0.457,0.409 0.942,0.451,0.410 0.941,0.446,0.410 0.940,0.441,0.411 0.938,0.436,0.412 0.937,0.430,0.412 0.936,0.425,0.413 0.934,0.420,0.414 0.933,0.414,0.414 0.932,0.409,0.415 0.930,0.404,0.416 0.929,0.398,0.416 0.928,0.393,0.417 0.926,0.387,0.418 0.925,0.382,0.418 0.924,0.376,0.419 0.921,0.371,0.420 0.919,0.366,0.422 0.917,0.361,0.424 0.914,0.356,0.426 0.912,0.351,0.428 0.909,0.347,0.430 0.907,0.342,0.431 0.904,0.337,0.433 0.902,0.332,0.435 0.899,0.326,0.437 0.897,0.321,0.438 0.894,0.316,0.440 0.892,0.311,0.442 0.889,0.306,0.444 0.887,0.300,0.445 0.884,0.295,0.447 0.882,0.290,0.449 0.879,0.284,0.450 0.876,0.279,0.452 0.874,0.273,0.454 0.871,0.268,0.455 0.869,0.262,0.457 0.866,0.256,0.459 0.863,0.252,0.461 0.859,0.247,0.463 0.855,0.243,0.466 0.851,0.239,0.468 0.847,0.235,0.470 0.843,0.230,0.473 0.839,0.226,0.475 0.835,0.222,0.478 0.831,0.217,0.480 0.827,0.213,0.482 0.823,0.208,0.485 0.819,0.204,0.487 0.815,0.199,0.489 0.811,0.194,0.492 0.807,0.190,0.494 0.803,0.185,0.496 0.799,0.180,0.499 0.795,0.175,0.501 0.791,0.170,0.503 0.787,0.165,0.506 0.783,0.160,0.508 0.778,0.155,0.510 0.774,0.150,0.513 0.769,0.146,0.515 0.764,0.144,0.518 0.758,0.141,0.520 0.753,0.139,0.523 0.747,0.137,0.525 0.741,0.134,0.528 0.736,0.132,0.530 0.730,0.129,0.533 0.724,0.127,0.535 0.718,0.125,0.538 0.712,0.122,0.541 0.707,0.120,0.543 0.701,0.118,0.546 0.695,0.115,0.548 0.689,0.113,0.551 0.683,0.111,0.553 0.677,0.109,0.556 0.671,0.106,0.558 0.665,0.104,0.561 0.659,0.102,0.563 0.653,0.100,0.566 0.647,0.097,0.568 0.641,0.095,0.571 0.634,0.094,0.573 0.626,0.096,0.576 0.619,0.097,0.578 0.611,0.099,0.580 0.603,0.100,0.582 0.596,0.102,0.585 0.588,0.103,0.587 0.580,0.104,0.589 0.572,0.106,0.591 0.564,0.107,0.594 0.555,0.108,0.596 0.547,0.109,0.598 0.539,0.111,0.600 0.531,0.112,0.603 0.522,0.113,0.605 0.514,0.114,0.607 0.505,0.115,0.609 0.496,0.116,0.612 0.487,0.117,0.614 0.478,0.119,0.616 0.469,0.120,0.618 0.460,0.121,0.620 0.450,0.122,0.623 0.441,0.123,0.625 0.430,0.126,0.626 0.419,0.128,0.628 0.408,0.131,0.629 0.396,0.133,0.631 0.384,0.136,0.632 0.372,0.138,0.633 0.360,0.140,0.635 0.347,0.142,0.636 0.334,0.144,0.638 0.321,0.146,0.639 0.307,0.148,0.640 0.292,0.150,0.642 0.277,0.152,0.643 0.261,0.153,0.645 0.244,0.155,0.646 0.226,0.156,0.648 0.206,0.158,0.649 0.184,0.159,0.650 0.159,0.161,0.652 0.131,0.162,0.653 0.094,0.163,0.655 0.038,0.164,0.656 0.000,0.165,0.658 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L18.csv000066400000000000000000000110001421045507400235760ustar00rootroot000000000000001.000,1.000,1.000 0.998,0.998,0.990 0.995,0.997,0.980 0.993,0.995,0.969 0.991,0.993,0.959 0.989,0.991,0.949 0.986,0.990,0.939 0.984,0.988,0.929 0.981,0.986,0.919 0.979,0.984,0.909 0.976,0.983,0.898 0.974,0.981,0.888 0.971,0.979,0.878 0.969,0.977,0.868 0.966,0.976,0.858 0.964,0.974,0.848 0.961,0.972,0.838 0.958,0.971,0.828 0.956,0.969,0.818 0.953,0.967,0.807 0.950,0.965,0.797 0.947,0.964,0.787 0.944,0.962,0.777 0.941,0.960,0.767 0.940,0.958,0.760 0.939,0.955,0.754 0.938,0.953,0.748 0.937,0.950,0.742 0.936,0.947,0.736 0.935,0.945,0.730 0.934,0.942,0.724 0.933,0.940,0.718 0.932,0.937,0.712 0.931,0.934,0.706 0.930,0.932,0.700 0.929,0.929,0.694 0.928,0.927,0.688 0.927,0.924,0.682 0.926,0.922,0.676 0.925,0.919,0.670 0.924,0.916,0.664 0.923,0.914,0.658 0.922,0.911,0.652 0.920,0.909,0.646 0.919,0.906,0.640 0.918,0.904,0.634 0.917,0.901,0.628 0.916,0.898,0.623 0.916,0.895,0.617 0.915,0.893,0.612 0.915,0.890,0.607 0.915,0.887,0.602 0.914,0.884,0.597 0.914,0.881,0.592 0.913,0.879,0.586 0.913,0.876,0.581 0.912,0.873,0.576 0.911,0.870,0.571 0.911,0.867,0.566 0.910,0.864,0.560 0.910,0.862,0.555 0.909,0.859,0.550 0.909,0.856,0.545 0.908,0.853,0.540 0.907,0.850,0.535 0.907,0.848,0.529 0.906,0.845,0.524 0.906,0.842,0.519 0.905,0.839,0.514 0.904,0.836,0.509 0.904,0.833,0.504 0.904,0.830,0.499 0.903,0.827,0.495 0.903,0.824,0.490 0.903,0.821,0.485 0.903,0.818,0.481 0.903,0.815,0.476 0.902,0.812,0.472 0.902,0.809,0.467 0.902,0.806,0.463 0.902,0.803,0.458 0.901,0.800,0.453 0.901,0.797,0.449 0.901,0.794,0.444 0.900,0.791,0.440 0.900,0.788,0.435 0.900,0.785,0.431 0.899,0.782,0.426 0.899,0.779,0.421 0.899,0.776,0.417 0.898,0.773,0.412 0.898,0.771,0.408 0.897,0.768,0.403 0.897,0.764,0.399 0.897,0.761,0.395 0.897,0.758,0.391 0.897,0.755,0.387 0.897,0.752,0.383 0.897,0.749,0.379 0.897,0.746,0.375 0.896,0.742,0.371 0.896,0.739,0.367 0.896,0.736,0.363 0.896,0.733,0.359 0.896,0.730,0.355 0.896,0.727,0.351 0.895,0.724,0.347 0.895,0.720,0.343 0.895,0.717,0.339 0.895,0.714,0.335 0.895,0.711,0.331 0.894,0.708,0.327 0.894,0.705,0.323 0.894,0.702,0.318 0.894,0.698,0.314 0.893,0.695,0.310 0.893,0.692,0.306 0.893,0.689,0.303 0.893,0.686,0.300 0.893,0.682,0.296 0.893,0.679,0.293 0.893,0.676,0.289 0.893,0.672,0.286 0.893,0.669,0.283 0.892,0.666,0.279 0.892,0.663,0.276 0.892,0.659,0.272 0.892,0.656,0.269 0.892,0.653,0.266 0.892,0.649,0.262 0.892,0.646,0.259 0.891,0.643,0.255 0.891,0.639,0.252 0.891,0.636,0.248 0.891,0.633,0.245 0.891,0.629,0.241 0.890,0.626,0.238 0.890,0.623,0.235 0.890,0.620,0.231 0.890,0.616,0.228 0.890,0.613,0.225 0.890,0.609,0.222 0.889,0.606,0.219 0.889,0.602,0.217 0.889,0.599,0.214 0.889,0.596,0.211 0.889,0.592,0.208 0.889,0.589,0.206 0.889,0.585,0.203 0.888,0.582,0.200 0.888,0.578,0.197 0.888,0.575,0.195 0.888,0.571,0.192 0.888,0.568,0.189 0.887,0.564,0.186 0.887,0.561,0.183 0.887,0.557,0.181 0.887,0.554,0.178 0.886,0.550,0.175 0.886,0.547,0.172 0.886,0.543,0.169 0.886,0.540,0.166 0.885,0.536,0.164 0.885,0.533,0.161 0.885,0.529,0.159 0.885,0.525,0.158 0.885,0.522,0.156 0.884,0.518,0.154 0.884,0.515,0.152 0.884,0.511,0.150 0.884,0.507,0.148 0.883,0.504,0.146 0.883,0.500,0.144 0.883,0.496,0.142 0.882,0.493,0.140 0.882,0.489,0.138 0.882,0.485,0.136 0.882,0.481,0.134 0.881,0.478,0.132 0.881,0.474,0.130 0.881,0.470,0.128 0.880,0.466,0.126 0.880,0.463,0.124 0.880,0.459,0.122 0.879,0.455,0.120 0.879,0.451,0.119 0.878,0.447,0.117 0.878,0.443,0.116 0.878,0.440,0.115 0.877,0.436,0.114 0.877,0.432,0.113 0.877,0.428,0.112 0.876,0.424,0.111 0.876,0.420,0.110 0.875,0.416,0.110 0.875,0.412,0.109 0.874,0.408,0.108 0.874,0.404,0.107 0.874,0.400,0.106 0.873,0.396,0.105 0.873,0.392,0.104 0.872,0.387,0.103 0.872,0.383,0.102 0.871,0.379,0.101 0.871,0.375,0.100 0.870,0.371,0.100 0.870,0.367,0.099 0.869,0.362,0.098 0.869,0.358,0.097 0.868,0.354,0.096 0.868,0.349,0.097 0.867,0.345,0.097 0.867,0.341,0.097 0.866,0.336,0.097 0.865,0.332,0.097 0.865,0.327,0.097 0.864,0.323,0.097 0.864,0.318,0.097 0.863,0.313,0.097 0.862,0.309,0.098 0.862,0.304,0.098 0.861,0.299,0.098 0.861,0.295,0.098 0.860,0.290,0.098 0.859,0.285,0.098 0.859,0.280,0.098 0.858,0.275,0.098 0.857,0.270,0.098 0.857,0.265,0.098 0.856,0.259,0.098 0.855,0.254,0.098 0.855,0.249,0.098 0.854,0.243,0.099 0.853,0.238,0.099 0.852,0.232,0.100 0.852,0.226,0.101 0.851,0.221,0.102 0.850,0.215,0.103 0.849,0.209,0.104 0.848,0.203,0.104 0.847,0.196,0.105 0.846,0.190,0.106 0.846,0.183,0.106 0.845,0.177,0.107 0.844,0.169,0.108 0.843,0.162,0.108 0.842,0.155,0.109 0.841,0.147,0.110 0.840,0.138,0.110 0.840,0.130,0.111 0.839,0.120,0.112 0.838,0.110,0.112 0.837,0.099,0.113 0.836,0.088,0.113 0.835,0.074,0.114 0.834,0.059,0.114 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L19.csv000066400000000000000000000110001421045507400235770ustar00rootroot000000000000001.000,1.000,1.000 0.995,0.998,1.000 0.990,0.996,0.999 0.985,0.994,0.999 0.980,0.992,0.998 0.975,0.990,0.998 0.970,0.989,0.998 0.965,0.987,0.997 0.960,0.985,0.997 0.955,0.983,0.996 0.950,0.981,0.996 0.945,0.979,0.996 0.940,0.977,0.995 0.935,0.975,0.995 0.930,0.973,0.994 0.925,0.971,0.994 0.920,0.969,0.994 0.915,0.967,0.993 0.910,0.965,0.993 0.905,0.963,0.992 0.900,0.962,0.992 0.895,0.960,0.992 0.890,0.958,0.991 0.885,0.956,0.991 0.881,0.954,0.991 0.877,0.951,0.991 0.873,0.949,0.991 0.869,0.947,0.991 0.865,0.944,0.992 0.861,0.942,0.992 0.857,0.940,0.992 0.853,0.938,0.992 0.849,0.935,0.993 0.845,0.933,0.993 0.840,0.931,0.993 0.836,0.928,0.993 0.832,0.926,0.993 0.828,0.924,0.994 0.824,0.922,0.994 0.820,0.919,0.994 0.816,0.917,0.994 0.812,0.915,0.994 0.808,0.913,0.994 0.804,0.910,0.995 0.799,0.908,0.995 0.795,0.906,0.995 0.791,0.904,0.995 0.788,0.901,0.995 0.786,0.898,0.996 0.783,0.896,0.996 0.781,0.893,0.996 0.778,0.890,0.996 0.776,0.888,0.997 0.773,0.885,0.997 0.771,0.882,0.997 0.768,0.880,0.997 0.766,0.877,0.998 0.763,0.874,0.998 0.761,0.871,0.998 0.758,0.869,0.998 0.756,0.866,0.998 0.753,0.863,0.999 0.751,0.861,0.999 0.748,0.858,0.999 0.746,0.855,0.999 0.743,0.853,1.000 0.740,0.850,1.000 0.738,0.847,1.000 0.735,0.845,1.000 0.733,0.842,1.000 0.731,0.839,1.000 0.731,0.836,1.000 0.730,0.833,1.000 0.730,0.830,0.999 0.729,0.827,0.999 0.729,0.824,0.999 0.728,0.821,0.998 0.728,0.817,0.998 0.727,0.814,0.998 0.727,0.811,0.997 0.726,0.808,0.997 0.726,0.805,0.996 0.725,0.802,0.996 0.725,0.799,0.996 0.724,0.796,0.995 0.724,0.793,0.995 0.723,0.789,0.995 0.723,0.786,0.994 0.722,0.783,0.994 0.722,0.780,0.994 0.721,0.777,0.993 0.721,0.774,0.993 0.720,0.771,0.992 0.720,0.767,0.992 0.722,0.764,0.990 0.723,0.760,0.989 0.725,0.757,0.987 0.726,0.753,0.986 0.728,0.750,0.985 0.729,0.746,0.983 0.731,0.743,0.982 0.732,0.739,0.980 0.734,0.735,0.979 0.735,0.732,0.977 0.736,0.728,0.976 0.738,0.725,0.974 0.739,0.721,0.973 0.740,0.718,0.971 0.742,0.714,0.970 0.743,0.710,0.968 0.744,0.707,0.967 0.746,0.703,0.966 0.747,0.699,0.964 0.748,0.696,0.963 0.749,0.692,0.961 0.750,0.689,0.960 0.752,0.685,0.958 0.755,0.681,0.955 0.758,0.677,0.952 0.760,0.673,0.949 0.763,0.669,0.946 0.766,0.665,0.943 0.768,0.661,0.940 0.771,0.657,0.937 0.774,0.653,0.934 0.776,0.649,0.931 0.779,0.645,0.928 0.781,0.641,0.926 0.784,0.637,0.923 0.786,0.633,0.920 0.789,0.629,0.917 0.791,0.624,0.914 0.794,0.620,0.911 0.796,0.616,0.908 0.798,0.612,0.905 0.801,0.608,0.902 0.803,0.604,0.899 0.805,0.600,0.896 0.807,0.595,0.893 0.809,0.591,0.890 0.812,0.587,0.886 0.816,0.582,0.881 0.819,0.578,0.876 0.822,0.574,0.872 0.824,0.569,0.867 0.827,0.565,0.863 0.830,0.560,0.858 0.833,0.556,0.853 0.836,0.551,0.849 0.838,0.547,0.844 0.841,0.542,0.839 0.844,0.538,0.835 0.846,0.533,0.830 0.849,0.529,0.826 0.851,0.524,0.821 0.854,0.520,0.817 0.856,0.515,0.812 0.859,0.510,0.807 0.861,0.505,0.803 0.864,0.501,0.798 0.866,0.496,0.794 0.868,0.491,0.789 0.870,0.486,0.785 0.873,0.482,0.779 0.875,0.477,0.773 0.877,0.473,0.766 0.879,0.468,0.760 0.882,0.463,0.754 0.884,0.458,0.748 0.886,0.454,0.741 0.888,0.449,0.735 0.890,0.444,0.729 0.892,0.439,0.723 0.894,0.434,0.717 0.896,0.429,0.711 0.898,0.425,0.704 0.899,0.420,0.698 0.901,0.415,0.692 0.903,0.410,0.686 0.905,0.404,0.680 0.906,0.399,0.674 0.908,0.394,0.668 0.910,0.389,0.662 0.911,0.384,0.655 0.913,0.378,0.649 0.914,0.373,0.643 0.915,0.368,0.636 0.916,0.364,0.629 0.917,0.359,0.621 0.918,0.354,0.614 0.918,0.350,0.606 0.919,0.345,0.599 0.920,0.340,0.592 0.920,0.335,0.584 0.921,0.330,0.577 0.921,0.326,0.569 0.922,0.321,0.562 0.922,0.316,0.555 0.923,0.311,0.547 0.923,0.306,0.540 0.924,0.300,0.532 0.924,0.295,0.525 0.924,0.290,0.518 0.925,0.285,0.510 0.925,0.279,0.503 0.925,0.274,0.496 0.925,0.268,0.489 0.925,0.263,0.481 0.925,0.257,0.474 0.925,0.252,0.467 0.924,0.248,0.458 0.923,0.244,0.450 0.922,0.240,0.442 0.921,0.237,0.434 0.919,0.233,0.425 0.918,0.229,0.417 0.917,0.224,0.409 0.916,0.220,0.401 0.914,0.216,0.393 0.913,0.212,0.385 0.912,0.208,0.376 0.910,0.204,0.368 0.909,0.200,0.360 0.908,0.195,0.352 0.906,0.191,0.344 0.905,0.186,0.336 0.903,0.182,0.328 0.901,0.177,0.320 0.900,0.173,0.312 0.898,0.168,0.304 0.897,0.163,0.295 0.895,0.159,0.287 0.893,0.154,0.279 0.890,0.153,0.271 0.887,0.152,0.262 0.884,0.151,0.253 0.880,0.149,0.245 0.877,0.148,0.236 0.874,0.147,0.227 0.871,0.146,0.218 0.867,0.145,0.210 0.864,0.144,0.201 0.861,0.143,0.192 0.857,0.141,0.183 0.854,0.140,0.174 0.851,0.139,0.164 0.847,0.138,0.155 0.844,0.137,0.145 0.840,0.136,0.136 0.837,0.135,0.126 0.833,0.134,0.115 0.830,0.133,0.105 0.826,0.132,0.094 0.823,0.131,0.082 0.819,0.130,0.069 0.816,0.130,0.056 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L2.csv000066400000000000000000000110001421045507400235070ustar00rootroot000000000000000.108,0.108,0.108 0.110,0.110,0.110 0.113,0.113,0.113 0.116,0.116,0.116 0.118,0.118,0.118 0.121,0.121,0.121 0.123,0.123,0.123 0.126,0.126,0.126 0.129,0.129,0.129 0.131,0.132,0.132 0.134,0.134,0.134 0.137,0.137,0.137 0.140,0.140,0.140 0.142,0.142,0.142 0.145,0.145,0.145 0.148,0.148,0.148 0.150,0.150,0.150 0.153,0.153,0.153 0.156,0.156,0.156 0.159,0.159,0.159 0.161,0.161,0.161 0.164,0.164,0.164 0.167,0.167,0.167 0.170,0.170,0.170 0.172,0.173,0.173 0.175,0.175,0.175 0.178,0.178,0.178 0.181,0.181,0.181 0.184,0.184,0.184 0.187,0.187,0.187 0.189,0.189,0.189 0.192,0.192,0.192 0.195,0.195,0.195 0.198,0.198,0.198 0.201,0.201,0.201 0.204,0.204,0.204 0.206,0.207,0.206 0.209,0.209,0.209 0.212,0.212,0.212 0.215,0.215,0.215 0.218,0.218,0.218 0.221,0.221,0.221 0.224,0.224,0.224 0.227,0.227,0.227 0.230,0.230,0.230 0.232,0.233,0.233 0.235,0.235,0.235 0.238,0.238,0.238 0.241,0.241,0.241 0.244,0.244,0.244 0.247,0.247,0.247 0.250,0.250,0.250 0.253,0.253,0.253 0.256,0.256,0.256 0.259,0.259,0.259 0.262,0.262,0.262 0.265,0.265,0.265 0.268,0.268,0.268 0.271,0.271,0.271 0.274,0.274,0.274 0.277,0.277,0.277 0.280,0.280,0.280 0.283,0.283,0.283 0.286,0.286,0.286 0.289,0.289,0.289 0.292,0.292,0.292 0.295,0.295,0.295 0.298,0.298,0.298 0.301,0.301,0.301 0.304,0.304,0.304 0.307,0.307,0.307 0.310,0.310,0.310 0.313,0.313,0.313 0.316,0.317,0.317 0.320,0.320,0.320 0.323,0.323,0.323 0.326,0.326,0.326 0.329,0.329,0.329 0.332,0.332,0.332 0.335,0.335,0.335 0.338,0.338,0.338 0.341,0.341,0.341 0.344,0.344,0.344 0.347,0.348,0.348 0.351,0.351,0.351 0.354,0.354,0.354 0.357,0.357,0.357 0.360,0.360,0.360 0.363,0.363,0.363 0.366,0.366,0.366 0.369,0.370,0.370 0.373,0.373,0.373 0.376,0.376,0.376 0.379,0.379,0.379 0.382,0.382,0.382 0.385,0.385,0.385 0.389,0.389,0.389 0.392,0.392,0.392 0.395,0.395,0.395 0.398,0.398,0.398 0.401,0.401,0.401 0.404,0.405,0.405 0.408,0.408,0.408 0.411,0.411,0.411 0.414,0.414,0.414 0.417,0.417,0.417 0.421,0.421,0.421 0.424,0.424,0.424 0.427,0.427,0.427 0.430,0.430,0.430 0.434,0.434,0.434 0.437,0.437,0.437 0.440,0.440,0.440 0.443,0.443,0.443 0.447,0.447,0.447 0.450,0.450,0.450 0.453,0.453,0.453 0.456,0.456,0.456 0.460,0.460,0.460 0.463,0.463,0.463 0.466,0.466,0.466 0.470,0.470,0.470 0.473,0.473,0.473 0.476,0.476,0.476 0.479,0.480,0.480 0.483,0.483,0.483 0.486,0.486,0.486 0.489,0.489,0.489 0.493,0.493,0.493 0.496,0.496,0.496 0.499,0.499,0.499 0.503,0.503,0.503 0.506,0.506,0.506 0.509,0.509,0.509 0.513,0.513,0.513 0.516,0.516,0.516 0.519,0.520,0.520 0.523,0.523,0.523 0.526,0.526,0.526 0.530,0.530,0.530 0.533,0.533,0.533 0.536,0.536,0.536 0.540,0.540,0.540 0.543,0.543,0.543 0.546,0.547,0.546 0.550,0.550,0.550 0.553,0.553,0.553 0.557,0.557,0.557 0.560,0.560,0.560 0.563,0.563,0.563 0.567,0.567,0.567 0.570,0.570,0.570 0.574,0.574,0.574 0.577,0.577,0.577 0.580,0.581,0.581 0.584,0.584,0.584 0.587,0.587,0.587 0.591,0.591,0.591 0.594,0.594,0.594 0.598,0.598,0.598 0.601,0.601,0.601 0.605,0.605,0.605 0.608,0.608,0.608 0.611,0.612,0.612 0.615,0.615,0.615 0.618,0.618,0.618 0.622,0.622,0.622 0.625,0.625,0.625 0.629,0.629,0.629 0.632,0.632,0.632 0.636,0.636,0.636 0.639,0.639,0.639 0.643,0.643,0.643 0.646,0.646,0.646 0.650,0.650,0.650 0.653,0.653,0.653 0.657,0.657,0.657 0.660,0.660,0.660 0.664,0.664,0.664 0.667,0.667,0.667 0.671,0.671,0.671 0.674,0.674,0.674 0.678,0.678,0.678 0.681,0.681,0.681 0.685,0.685,0.685 0.688,0.688,0.688 0.692,0.692,0.692 0.695,0.696,0.695 0.699,0.699,0.699 0.702,0.703,0.703 0.706,0.706,0.706 0.710,0.710,0.710 0.713,0.713,0.713 0.717,0.717,0.717 0.720,0.720,0.720 0.724,0.724,0.724 0.727,0.728,0.727 0.731,0.731,0.731 0.735,0.735,0.735 0.738,0.738,0.738 0.742,0.742,0.742 0.745,0.745,0.745 0.749,0.749,0.749 0.752,0.753,0.753 0.756,0.756,0.756 0.760,0.760,0.760 0.763,0.763,0.763 0.767,0.767,0.767 0.770,0.771,0.771 0.774,0.774,0.774 0.778,0.778,0.778 0.781,0.781,0.781 0.785,0.785,0.785 0.789,0.789,0.789 0.792,0.792,0.792 0.796,0.796,0.796 0.799,0.800,0.800 0.803,0.803,0.803 0.807,0.807,0.807 0.810,0.810,0.810 0.814,0.814,0.814 0.818,0.818,0.818 0.821,0.821,0.821 0.825,0.825,0.825 0.829,0.829,0.829 0.832,0.832,0.832 0.836,0.836,0.836 0.840,0.840,0.840 0.843,0.843,0.843 0.847,0.847,0.847 0.851,0.851,0.851 0.854,0.854,0.854 0.858,0.858,0.858 0.862,0.862,0.862 0.865,0.865,0.865 0.869,0.869,0.869 0.873,0.873,0.873 0.876,0.877,0.876 0.880,0.880,0.880 0.884,0.884,0.884 0.887,0.888,0.888 0.891,0.891,0.891 0.895,0.895,0.895 0.899,0.899,0.899 0.902,0.902,0.902 0.906,0.906,0.906 0.910,0.910,0.910 0.913,0.914,0.914 0.917,0.917,0.917 0.921,0.921,0.921 0.925,0.925,0.925 0.928,0.929,0.929 0.932,0.932,0.932 0.936,0.936,0.936 0.940,0.940,0.940 0.943,0.944,0.943 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L3.csv000066400000000000000000000110001421045507400235100ustar00rootroot000000000000000.000,0.000,0.000 0.027,0.000,0.000 0.052,0.000,0.000 0.072,0.000,0.000 0.087,0.000,0.000 0.101,0.000,0.000 0.113,0.000,0.000 0.124,0.000,0.000 0.135,0.001,0.000 0.144,0.001,0.000 0.153,0.001,0.000 0.161,0.001,0.000 0.169,0.001,0.000 0.177,0.001,0.000 0.184,0.002,0.000 0.191,0.002,0.000 0.198,0.002,0.000 0.204,0.002,0.000 0.210,0.002,0.000 0.216,0.002,0.000 0.222,0.002,0.000 0.228,0.002,0.000 0.234,0.002,0.000 0.240,0.002,0.000 0.245,0.002,0.000 0.251,0.002,0.000 0.257,0.002,0.000 0.263,0.002,0.000 0.269,0.002,0.000 0.275,0.002,0.000 0.280,0.002,0.000 0.286,0.003,0.000 0.292,0.003,0.000 0.298,0.003,0.000 0.304,0.003,0.000 0.310,0.003,0.000 0.316,0.003,0.000 0.322,0.003,0.000 0.328,0.003,0.000 0.334,0.004,0.000 0.341,0.004,0.000 0.347,0.004,0.000 0.353,0.004,0.000 0.359,0.004,0.000 0.365,0.004,0.000 0.371,0.005,0.000 0.378,0.005,0.000 0.384,0.005,0.000 0.390,0.005,0.000 0.396,0.005,0.000 0.403,0.005,0.000 0.409,0.006,0.000 0.415,0.006,0.000 0.422,0.006,0.000 0.428,0.006,0.000 0.434,0.007,0.000 0.441,0.007,0.000 0.447,0.007,0.000 0.453,0.007,0.000 0.460,0.007,0.000 0.466,0.008,0.000 0.473,0.008,0.000 0.479,0.008,0.000 0.486,0.009,0.000 0.492,0.009,0.000 0.499,0.009,0.000 0.505,0.009,0.000 0.512,0.010,0.000 0.518,0.010,0.000 0.525,0.010,0.000 0.532,0.011,0.000 0.538,0.011,0.000 0.545,0.011,0.000 0.552,0.012,0.000 0.558,0.012,0.000 0.565,0.013,0.000 0.572,0.013,0.000 0.578,0.013,0.000 0.585,0.014,0.000 0.592,0.014,0.000 0.598,0.015,0.000 0.605,0.015,0.000 0.612,0.015,0.000 0.619,0.016,0.000 0.625,0.016,0.000 0.632,0.017,0.000 0.639,0.017,0.000 0.646,0.018,0.000 0.653,0.018,0.000 0.660,0.019,0.000 0.666,0.020,0.000 0.673,0.020,0.000 0.680,0.021,0.000 0.687,0.021,0.000 0.694,0.022,0.000 0.701,0.023,0.000 0.708,0.023,0.000 0.715,0.024,0.000 0.722,0.025,0.000 0.729,0.026,0.000 0.736,0.026,0.000 0.743,0.027,0.000 0.750,0.028,0.000 0.757,0.029,0.000 0.764,0.030,0.000 0.771,0.031,0.000 0.778,0.032,0.000 0.785,0.033,0.000 0.792,0.034,0.000 0.799,0.035,0.000 0.806,0.036,0.000 0.813,0.037,0.000 0.820,0.038,0.000 0.827,0.040,0.000 0.834,0.041,0.000 0.841,0.043,0.000 0.848,0.044,0.000 0.855,0.046,0.000 0.863,0.048,0.000 0.870,0.050,0.000 0.877,0.052,0.000 0.884,0.054,0.000 0.891,0.056,0.000 0.898,0.059,0.000 0.905,0.062,0.000 0.911,0.065,0.000 0.918,0.069,0.000 0.924,0.075,0.000 0.931,0.081,0.000 0.936,0.089,0.000 0.942,0.097,0.000 0.947,0.107,0.000 0.952,0.117,0.000 0.957,0.127,0.000 0.961,0.138,0.000 0.965,0.150,0.000 0.968,0.161,0.000 0.971,0.173,0.000 0.974,0.185,0.000 0.977,0.197,0.000 0.979,0.208,0.000 0.981,0.220,0.000 0.983,0.232,0.000 0.985,0.243,0.000 0.987,0.254,0.000 0.988,0.265,0.000 0.989,0.276,0.000 0.991,0.287,0.000 0.992,0.297,0.000 0.993,0.308,0.000 0.993,0.318,0.000 0.994,0.328,0.000 0.995,0.338,0.000 0.996,0.347,0.000 0.996,0.357,0.000 0.997,0.366,0.000 0.997,0.376,0.000 0.998,0.385,0.000 0.998,0.394,0.000 0.998,0.403,0.000 0.999,0.411,0.000 0.999,0.420,0.000 0.999,0.429,0.000 0.999,0.437,0.000 1.000,0.446,0.000 1.000,0.454,0.000 1.000,0.462,0.000 1.000,0.470,0.000 1.000,0.478,0.000 1.000,0.486,0.000 1.000,0.494,0.000 1.000,0.502,0.000 1.000,0.510,0.000 1.000,0.518,0.000 1.000,0.525,0.000 1.000,0.533,0.000 1.000,0.540,0.000 1.000,0.548,0.000 1.000,0.555,0.000 1.000,0.562,0.001 1.000,0.570,0.001 1.000,0.577,0.001 1.000,0.584,0.001 1.000,0.591,0.001 1.000,0.598,0.002 1.000,0.605,0.002 1.000,0.612,0.002 1.000,0.619,0.003 1.000,0.626,0.003 1.000,0.633,0.003 1.000,0.640,0.004 1.000,0.647,0.004 1.000,0.653,0.004 1.000,0.660,0.005 1.000,0.667,0.005 1.000,0.673,0.006 1.000,0.680,0.006 1.000,0.687,0.007 1.000,0.693,0.007 1.000,0.700,0.008 1.000,0.706,0.009 1.000,0.713,0.009 1.000,0.719,0.010 1.000,0.726,0.011 1.000,0.732,0.012 1.000,0.739,0.012 1.000,0.745,0.013 1.000,0.751,0.014 1.000,0.758,0.015 1.000,0.764,0.016 1.000,0.770,0.017 1.000,0.777,0.018 1.000,0.783,0.019 1.000,0.789,0.020 1.000,0.796,0.021 1.000,0.802,0.023 1.000,0.808,0.024 1.000,0.814,0.025 1.000,0.820,0.027 1.000,0.827,0.028 1.000,0.833,0.030 1.000,0.839,0.032 1.000,0.845,0.033 1.000,0.851,0.035 1.000,0.857,0.037 1.000,0.863,0.040 1.000,0.869,0.042 1.000,0.876,0.044 1.000,0.882,0.047 1.000,0.888,0.050 1.000,0.894,0.053 1.000,0.900,0.056 1.000,0.906,0.060 1.000,0.912,0.064 1.000,0.918,0.069 1.000,0.924,0.074 1.000,0.930,0.081 1.000,0.936,0.091 1.000,0.942,0.104 1.000,0.948,0.121 1.000,0.953,0.141 1.000,0.959,0.167 1.000,0.965,0.198 1.000,0.970,0.235 1.000,0.975,0.279 1.000,0.980,0.329 1.000,0.984,0.386 1.000,0.988,0.449 1.000,0.991,0.517 1.000,0.994,0.588 1.000,0.996,0.660 1.000,0.998,0.732 1.000,0.999,0.803 1.000,1.000,0.871 1.000,1.000,0.937 1.000,1.000,1.000 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L4.csv000066400000000000000000000110001421045507400235110ustar00rootroot000000000000000.000,0.000,0.000 0.026,0.000,0.000 0.051,0.000,0.000 0.070,0.000,0.000 0.085,0.000,0.000 0.099,0.000,0.000 0.111,0.000,0.000 0.122,0.000,0.000 0.132,0.000,0.000 0.142,0.001,0.000 0.150,0.001,0.000 0.159,0.001,0.000 0.166,0.001,0.000 0.174,0.001,0.000 0.181,0.001,0.000 0.188,0.001,0.000 0.195,0.001,0.000 0.201,0.001,0.000 0.207,0.001,0.000 0.213,0.001,0.000 0.219,0.002,0.000 0.224,0.002,0.000 0.230,0.002,0.000 0.236,0.002,0.000 0.241,0.002,0.000 0.247,0.002,0.000 0.253,0.002,0.000 0.258,0.002,0.000 0.264,0.002,0.000 0.270,0.002,0.000 0.275,0.002,0.000 0.281,0.002,0.000 0.287,0.003,0.000 0.293,0.003,0.000 0.298,0.003,0.000 0.304,0.003,0.000 0.310,0.003,0.000 0.316,0.003,0.000 0.322,0.003,0.000 0.328,0.003,0.000 0.334,0.004,0.000 0.339,0.004,0.000 0.345,0.004,0.000 0.351,0.004,0.000 0.357,0.004,0.000 0.363,0.004,0.000 0.369,0.004,0.000 0.375,0.005,0.000 0.381,0.005,0.000 0.387,0.005,0.000 0.394,0.005,0.000 0.400,0.005,0.000 0.406,0.006,0.000 0.412,0.006,0.000 0.418,0.006,0.000 0.424,0.006,0.000 0.430,0.006,0.000 0.437,0.007,0.000 0.443,0.007,0.000 0.449,0.007,0.000 0.455,0.007,0.000 0.461,0.008,0.000 0.468,0.008,0.000 0.474,0.008,0.000 0.480,0.008,0.000 0.487,0.009,0.000 0.493,0.009,0.000 0.499,0.009,0.000 0.506,0.009,0.000 0.512,0.010,0.000 0.518,0.010,0.000 0.525,0.010,0.000 0.531,0.011,0.000 0.538,0.011,0.000 0.544,0.011,0.000 0.551,0.012,0.000 0.557,0.012,0.000 0.563,0.012,0.000 0.570,0.013,0.000 0.576,0.013,0.000 0.583,0.014,0.000 0.589,0.014,0.000 0.596,0.014,0.000 0.603,0.015,0.000 0.609,0.015,0.000 0.616,0.016,0.000 0.622,0.016,0.000 0.629,0.017,0.000 0.635,0.017,0.000 0.642,0.018,0.000 0.649,0.018,0.000 0.655,0.019,0.000 0.662,0.019,0.000 0.669,0.020,0.000 0.675,0.020,0.000 0.682,0.021,0.000 0.689,0.022,0.000 0.695,0.022,0.000 0.702,0.023,0.000 0.709,0.024,0.000 0.716,0.024,0.000 0.722,0.025,0.000 0.729,0.026,0.000 0.736,0.026,0.000 0.743,0.027,0.000 0.750,0.028,0.000 0.756,0.029,0.000 0.763,0.030,0.000 0.770,0.031,0.000 0.777,0.032,0.000 0.784,0.033,0.000 0.790,0.034,0.000 0.797,0.035,0.000 0.804,0.036,0.000 0.811,0.037,0.000 0.818,0.038,0.000 0.825,0.039,0.000 0.832,0.041,0.000 0.839,0.042,0.000 0.845,0.044,0.000 0.852,0.045,0.000 0.859,0.047,0.000 0.866,0.049,0.000 0.873,0.050,0.000 0.880,0.052,0.000 0.887,0.055,0.000 0.893,0.057,0.000 0.900,0.060,0.000 0.907,0.063,0.000 0.913,0.066,0.000 0.920,0.071,0.000 0.926,0.076,0.000 0.932,0.084,0.000 0.937,0.093,0.000 0.942,0.104,0.000 0.946,0.115,0.000 0.950,0.128,0.000 0.953,0.140,0.000 0.956,0.153,0.000 0.959,0.166,0.000 0.961,0.179,0.000 0.963,0.191,0.000 0.965,0.203,0.000 0.967,0.215,0.000 0.969,0.227,0.000 0.970,0.238,0.000 0.972,0.249,0.000 0.973,0.260,0.000 0.974,0.270,0.000 0.975,0.281,0.000 0.976,0.291,0.000 0.977,0.301,0.000 0.978,0.311,0.000 0.979,0.320,0.000 0.980,0.330,0.000 0.981,0.339,0.000 0.982,0.348,0.000 0.982,0.357,0.000 0.983,0.366,0.000 0.984,0.375,0.000 0.984,0.383,0.000 0.985,0.392,0.000 0.985,0.400,0.000 0.986,0.408,0.000 0.986,0.416,0.000 0.987,0.425,0.000 0.987,0.433,0.000 0.988,0.440,0.000 0.988,0.448,0.000 0.989,0.456,0.000 0.989,0.464,0.000 0.989,0.471,0.000 0.990,0.479,0.000 0.990,0.486,0.000 0.991,0.494,0.000 0.991,0.501,0.000 0.991,0.508,0.000 0.991,0.515,0.000 0.992,0.523,0.000 0.992,0.530,0.000 0.992,0.537,0.000 0.993,0.544,0.000 0.993,0.551,0.000 0.993,0.558,0.000 0.993,0.565,0.000 0.994,0.571,0.000 0.994,0.578,0.000 0.994,0.585,0.000 0.994,0.592,0.000 0.995,0.598,0.000 0.995,0.605,0.000 0.995,0.612,0.000 0.995,0.618,0.000 0.995,0.625,0.000 0.996,0.632,0.000 0.996,0.638,0.000 0.996,0.645,0.000 0.996,0.651,0.000 0.996,0.657,0.000 0.996,0.664,0.000 0.997,0.670,0.000 0.997,0.677,0.000 0.997,0.683,0.000 0.997,0.689,0.000 0.997,0.696,0.000 0.997,0.702,0.000 0.997,0.708,0.000 0.997,0.714,0.000 0.998,0.721,0.000 0.998,0.727,0.000 0.998,0.733,0.000 0.998,0.739,0.000 0.998,0.745,0.000 0.998,0.752,0.000 0.998,0.758,0.000 0.998,0.764,0.000 0.998,0.770,0.000 0.999,0.776,0.000 0.999,0.782,0.000 0.999,0.788,0.000 0.999,0.794,0.000 0.999,0.800,0.000 0.999,0.806,0.000 0.999,0.812,0.000 0.999,0.818,0.000 0.999,0.824,0.000 0.999,0.830,0.000 0.999,0.836,0.000 0.999,0.842,0.000 0.999,0.848,0.000 0.999,0.854,0.000 0.999,0.860,0.000 1.000,0.866,0.000 1.000,0.872,0.000 1.000,0.878,0.000 1.000,0.884,0.000 1.000,0.890,0.000 1.000,0.895,0.000 1.000,0.901,0.000 1.000,0.907,0.000 1.000,0.913,0.000 1.000,0.919,0.000 1.000,0.925,0.000 1.000,0.930,0.000 1.000,0.936,0.000 1.000,0.942,0.000 1.000,0.948,0.000 1.000,0.954,0.000 1.000,0.960,0.000 1.000,0.965,0.000 1.000,0.971,0.000 1.000,0.977,0.000 1.000,0.983,0.000 1.000,0.988,0.000 1.000,0.994,0.000 1.000,1.000,0.000 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L5.csv000066400000000000000000000110001421045507400235120ustar00rootroot000000000000000.002,0.083,0.022 0.004,0.086,0.022 0.007,0.090,0.022 0.009,0.093,0.022 0.012,0.096,0.022 0.014,0.100,0.022 0.017,0.103,0.022 0.019,0.106,0.022 0.022,0.109,0.022 0.024,0.112,0.022 0.026,0.115,0.022 0.028,0.118,0.022 0.030,0.121,0.022 0.032,0.124,0.022 0.033,0.127,0.022 0.034,0.130,0.022 0.036,0.133,0.022 0.036,0.136,0.022 0.037,0.140,0.021 0.037,0.143,0.021 0.037,0.146,0.021 0.037,0.150,0.021 0.037,0.153,0.021 0.036,0.156,0.021 0.036,0.160,0.021 0.036,0.163,0.020 0.035,0.167,0.020 0.035,0.170,0.020 0.035,0.173,0.020 0.034,0.177,0.020 0.034,0.180,0.020 0.034,0.184,0.019 0.033,0.187,0.019 0.033,0.191,0.019 0.033,0.194,0.019 0.033,0.197,0.019 0.033,0.201,0.019 0.033,0.204,0.019 0.033,0.208,0.019 0.034,0.211,0.018 0.034,0.215,0.018 0.035,0.218,0.018 0.036,0.222,0.019 0.037,0.225,0.019 0.038,0.228,0.019 0.039,0.232,0.019 0.040,0.235,0.019 0.041,0.239,0.019 0.042,0.242,0.019 0.043,0.246,0.019 0.044,0.249,0.019 0.045,0.253,0.019 0.046,0.256,0.019 0.047,0.260,0.020 0.048,0.263,0.020 0.049,0.267,0.020 0.050,0.270,0.020 0.051,0.274,0.020 0.052,0.277,0.020 0.053,0.281,0.020 0.054,0.284,0.020 0.055,0.288,0.020 0.057,0.292,0.020 0.058,0.295,0.020 0.059,0.299,0.020 0.060,0.302,0.020 0.061,0.306,0.020 0.062,0.310,0.020 0.063,0.313,0.020 0.064,0.317,0.021 0.065,0.320,0.021 0.066,0.324,0.021 0.067,0.328,0.022 0.068,0.331,0.022 0.069,0.335,0.022 0.070,0.339,0.023 0.070,0.342,0.023 0.071,0.346,0.023 0.072,0.350,0.024 0.073,0.353,0.024 0.074,0.357,0.024 0.075,0.361,0.025 0.076,0.365,0.025 0.077,0.368,0.025 0.078,0.372,0.026 0.079,0.376,0.026 0.080,0.380,0.026 0.081,0.383,0.027 0.082,0.387,0.027 0.083,0.391,0.028 0.084,0.395,0.028 0.085,0.398,0.028 0.086,0.402,0.029 0.087,0.406,0.029 0.088,0.410,0.030 0.089,0.413,0.030 0.090,0.417,0.030 0.091,0.421,0.031 0.092,0.425,0.031 0.093,0.429,0.032 0.094,0.433,0.032 0.095,0.436,0.032 0.096,0.440,0.033 0.097,0.444,0.033 0.098,0.448,0.034 0.099,0.452,0.034 0.100,0.456,0.034 0.101,0.460,0.035 0.102,0.463,0.036 0.103,0.467,0.036 0.104,0.471,0.036 0.105,0.475,0.037 0.106,0.479,0.037 0.107,0.483,0.038 0.108,0.487,0.038 0.109,0.491,0.039 0.110,0.495,0.039 0.111,0.499,0.039 0.112,0.503,0.040 0.113,0.506,0.040 0.114,0.510,0.041 0.115,0.514,0.041 0.116,0.518,0.042 0.117,0.522,0.042 0.118,0.526,0.043 0.119,0.530,0.043 0.120,0.534,0.044 0.121,0.538,0.044 0.122,0.542,0.045 0.123,0.546,0.045 0.124,0.550,0.045 0.125,0.554,0.046 0.126,0.558,0.046 0.127,0.562,0.047 0.128,0.566,0.047 0.129,0.570,0.048 0.130,0.574,0.048 0.131,0.578,0.049 0.132,0.582,0.049 0.133,0.586,0.050 0.134,0.590,0.050 0.135,0.595,0.050 0.136,0.599,0.051 0.137,0.603,0.051 0.138,0.607,0.052 0.139,0.611,0.052 0.140,0.615,0.053 0.141,0.619,0.053 0.142,0.623,0.054 0.143,0.627,0.054 0.144,0.631,0.054 0.145,0.635,0.055 0.146,0.639,0.055 0.147,0.644,0.056 0.148,0.648,0.056 0.149,0.652,0.057 0.150,0.656,0.057 0.151,0.660,0.058 0.152,0.664,0.058 0.153,0.668,0.059 0.155,0.673,0.059 0.156,0.677,0.060 0.157,0.681,0.060 0.158,0.685,0.060 0.159,0.689,0.061 0.160,0.693,0.061 0.161,0.698,0.062 0.162,0.702,0.062 0.163,0.706,0.063 0.164,0.710,0.063 0.165,0.714,0.064 0.166,0.719,0.064 0.167,0.723,0.064 0.168,0.727,0.065 0.169,0.731,0.065 0.170,0.735,0.066 0.171,0.740,0.066 0.172,0.744,0.067 0.173,0.748,0.067 0.174,0.752,0.068 0.175,0.757,0.068 0.177,0.761,0.069 0.178,0.765,0.069 0.179,0.769,0.070 0.180,0.774,0.070 0.181,0.778,0.070 0.182,0.782,0.071 0.183,0.786,0.071 0.184,0.791,0.072 0.185,0.795,0.072 0.186,0.799,0.073 0.187,0.803,0.073 0.188,0.808,0.074 0.189,0.812,0.074 0.190,0.816,0.074 0.191,0.821,0.075 0.193,0.825,0.075 0.194,0.829,0.076 0.195,0.834,0.076 0.196,0.838,0.077 0.197,0.842,0.077 0.198,0.847,0.077 0.199,0.851,0.078 0.200,0.855,0.078 0.201,0.860,0.079 0.202,0.864,0.079 0.203,0.868,0.080 0.204,0.873,0.080 0.205,0.877,0.081 0.206,0.881,0.081 0.208,0.886,0.082 0.209,0.890,0.082 0.210,0.895,0.082 0.212,0.899,0.083 0.216,0.903,0.083 0.222,0.907,0.084 0.231,0.911,0.084 0.242,0.915,0.084 0.254,0.919,0.084 0.267,0.923,0.084 0.281,0.926,0.085 0.296,0.930,0.085 0.311,0.933,0.085 0.327,0.936,0.085 0.343,0.940,0.085 0.359,0.943,0.085 0.375,0.946,0.085 0.392,0.949,0.085 0.408,0.952,0.084 0.425,0.955,0.084 0.441,0.958,0.084 0.458,0.960,0.084 0.474,0.963,0.084 0.491,0.966,0.084 0.507,0.968,0.084 0.524,0.971,0.083 0.540,0.973,0.083 0.557,0.976,0.083 0.573,0.978,0.083 0.589,0.980,0.083 0.606,0.982,0.083 0.622,0.985,0.082 0.638,0.987,0.082 0.655,0.989,0.082 0.671,0.991,0.082 0.687,0.993,0.082 0.703,0.995,0.082 0.719,0.996,0.082 0.735,0.998,0.082 0.751,1.000,0.081 0.767,1.000,0.081 0.783,1.000,0.081 0.800,1.000,0.081 0.816,1.000,0.081 0.832,1.000,0.081 0.848,1.000,0.081 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L6.csv000066400000000000000000000110001421045507400235130ustar00rootroot000000000000000.000,0.002,0.307 0.000,0.004,0.314 0.000,0.006,0.322 0.000,0.008,0.330 0.000,0.009,0.337 0.000,0.010,0.345 0.000,0.011,0.352 0.000,0.011,0.360 0.000,0.011,0.368 0.002,0.011,0.376 0.004,0.011,0.384 0.007,0.010,0.392 0.009,0.010,0.400 0.012,0.010,0.407 0.015,0.010,0.416 0.018,0.009,0.424 0.021,0.009,0.432 0.024,0.009,0.440 0.026,0.009,0.448 0.029,0.008,0.456 0.032,0.008,0.464 0.036,0.008,0.473 0.039,0.007,0.481 0.041,0.007,0.489 0.044,0.007,0.498 0.047,0.007,0.506 0.049,0.006,0.514 0.052,0.006,0.523 0.054,0.006,0.531 0.056,0.006,0.540 0.057,0.006,0.549 0.059,0.005,0.557 0.061,0.005,0.566 0.062,0.005,0.575 0.063,0.005,0.583 0.064,0.005,0.592 0.064,0.006,0.601 0.064,0.006,0.610 0.064,0.006,0.619 0.063,0.007,0.628 0.062,0.007,0.637 0.060,0.008,0.646 0.058,0.009,0.656 0.055,0.010,0.665 0.052,0.011,0.674 0.049,0.013,0.683 0.046,0.014,0.692 0.043,0.016,0.701 0.040,0.017,0.710 0.038,0.019,0.719 0.036,0.021,0.727 0.033,0.024,0.736 0.032,0.026,0.744 0.031,0.029,0.753 0.030,0.031,0.761 0.029,0.034,0.769 0.029,0.038,0.777 0.030,0.041,0.785 0.031,0.044,0.793 0.032,0.048,0.801 0.034,0.051,0.808 0.037,0.055,0.816 0.040,0.058,0.823 0.043,0.062,0.831 0.047,0.065,0.838 0.052,0.069,0.845 0.056,0.072,0.852 0.061,0.076,0.859 0.066,0.080,0.866 0.071,0.084,0.873 0.076,0.087,0.879 0.082,0.091,0.886 0.087,0.095,0.892 0.092,0.099,0.898 0.097,0.104,0.904 0.102,0.108,0.910 0.107,0.113,0.915 0.111,0.118,0.920 0.116,0.123,0.925 0.120,0.128,0.930 0.124,0.134,0.935 0.128,0.139,0.939 0.132,0.145,0.944 0.135,0.151,0.948 0.139,0.157,0.951 0.142,0.163,0.955 0.146,0.169,0.958 0.149,0.175,0.962 0.152,0.181,0.964 0.155,0.187,0.967 0.158,0.194,0.970 0.160,0.200,0.972 0.163,0.206,0.974 0.165,0.213,0.976 0.168,0.220,0.977 0.170,0.226,0.979 0.172,0.233,0.980 0.174,0.239,0.981 0.175,0.246,0.982 0.177,0.253,0.982 0.178,0.260,0.983 0.179,0.266,0.983 0.180,0.273,0.984 0.181,0.280,0.984 0.182,0.286,0.984 0.183,0.292,0.985 0.184,0.299,0.985 0.185,0.305,0.985 0.185,0.311,0.986 0.186,0.317,0.986 0.186,0.323,0.987 0.186,0.329,0.987 0.187,0.335,0.987 0.187,0.341,0.988 0.187,0.347,0.988 0.187,0.353,0.988 0.187,0.359,0.989 0.186,0.365,0.989 0.186,0.371,0.989 0.186,0.376,0.990 0.185,0.382,0.990 0.184,0.388,0.990 0.184,0.393,0.991 0.183,0.399,0.991 0.182,0.405,0.991 0.181,0.410,0.992 0.180,0.416,0.992 0.178,0.421,0.992 0.177,0.427,0.993 0.176,0.432,0.993 0.175,0.438,0.993 0.174,0.443,0.994 0.173,0.449,0.994 0.172,0.454,0.994 0.171,0.459,0.994 0.171,0.465,0.994 0.170,0.470,0.995 0.170,0.475,0.995 0.170,0.480,0.995 0.170,0.486,0.995 0.170,0.491,0.995 0.171,0.496,0.995 0.171,0.501,0.995 0.172,0.506,0.995 0.173,0.511,0.995 0.174,0.516,0.995 0.175,0.521,0.995 0.176,0.526,0.995 0.178,0.531,0.995 0.179,0.536,0.995 0.181,0.541,0.995 0.183,0.546,0.995 0.185,0.551,0.995 0.187,0.556,0.994 0.189,0.561,0.994 0.192,0.566,0.994 0.194,0.571,0.994 0.197,0.575,0.993 0.199,0.580,0.993 0.201,0.585,0.993 0.203,0.590,0.993 0.205,0.595,0.993 0.206,0.600,0.992 0.207,0.605,0.992 0.208,0.610,0.992 0.209,0.615,0.992 0.210,0.620,0.992 0.211,0.624,0.991 0.211,0.629,0.991 0.211,0.634,0.991 0.211,0.639,0.991 0.211,0.644,0.991 0.211,0.649,0.991 0.210,0.654,0.990 0.210,0.659,0.990 0.209,0.664,0.990 0.208,0.669,0.990 0.206,0.674,0.990 0.205,0.679,0.990 0.203,0.684,0.990 0.201,0.689,0.990 0.199,0.694,0.989 0.197,0.699,0.989 0.194,0.704,0.989 0.191,0.709,0.989 0.188,0.714,0.989 0.185,0.719,0.989 0.182,0.724,0.989 0.179,0.729,0.989 0.177,0.734,0.989 0.174,0.739,0.988 0.172,0.744,0.988 0.170,0.749,0.988 0.168,0.754,0.988 0.166,0.759,0.988 0.165,0.764,0.987 0.163,0.769,0.987 0.162,0.774,0.987 0.161,0.778,0.987 0.160,0.783,0.987 0.160,0.788,0.986 0.160,0.793,0.986 0.160,0.798,0.986 0.160,0.803,0.985 0.161,0.807,0.985 0.161,0.812,0.985 0.162,0.817,0.984 0.164,0.822,0.984 0.165,0.827,0.984 0.167,0.831,0.983 0.169,0.836,0.983 0.171,0.841,0.982 0.174,0.845,0.982 0.177,0.850,0.982 0.183,0.855,0.981 0.190,0.859,0.981 0.199,0.864,0.980 0.210,0.868,0.980 0.221,0.872,0.979 0.233,0.876,0.979 0.246,0.880,0.979 0.260,0.884,0.978 0.273,0.888,0.978 0.287,0.892,0.977 0.301,0.896,0.977 0.314,0.900,0.976 0.328,0.904,0.976 0.342,0.907,0.976 0.356,0.911,0.975 0.370,0.915,0.975 0.384,0.918,0.974 0.397,0.922,0.974 0.411,0.925,0.973 0.424,0.929,0.973 0.438,0.932,0.973 0.451,0.936,0.972 0.464,0.939,0.972 0.478,0.943,0.971 0.491,0.946,0.971 0.504,0.949,0.970 0.517,0.953,0.970 0.529,0.956,0.969 0.542,0.959,0.969 0.555,0.962,0.968 0.568,0.966,0.968 0.580,0.969,0.968 0.593,0.972,0.967 0.605,0.975,0.967 0.618,0.978,0.966 0.630,0.981,0.966 0.642,0.984,0.965 0.654,0.987,0.965 0.667,0.990,0.964 0.679,0.993,0.964 0.691,0.996,0.963 0.703,0.999,0.963 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L7.csv000066400000000000000000000110001421045507400235140ustar00rootroot000000000000000.000,0.008,0.296 0.000,0.010,0.303 0.000,0.012,0.311 0.000,0.014,0.319 0.000,0.016,0.327 0.000,0.017,0.335 0.000,0.018,0.343 0.000,0.019,0.351 0.000,0.019,0.359 0.000,0.020,0.368 0.000,0.020,0.376 0.000,0.020,0.384 0.000,0.020,0.392 0.000,0.020,0.401 0.001,0.020,0.409 0.002,0.020,0.417 0.003,0.020,0.426 0.005,0.021,0.434 0.006,0.021,0.442 0.007,0.021,0.451 0.008,0.021,0.459 0.009,0.021,0.468 0.010,0.021,0.476 0.012,0.021,0.485 0.013,0.021,0.493 0.014,0.021,0.502 0.015,0.021,0.511 0.016,0.021,0.519 0.017,0.022,0.528 0.018,0.022,0.537 0.019,0.022,0.546 0.020,0.022,0.554 0.021,0.022,0.563 0.022,0.022,0.572 0.022,0.023,0.581 0.023,0.023,0.590 0.023,0.023,0.599 0.023,0.024,0.607 0.023,0.024,0.616 0.023,0.024,0.625 0.023,0.025,0.634 0.022,0.025,0.643 0.021,0.026,0.652 0.020,0.027,0.661 0.019,0.028,0.670 0.018,0.029,0.679 0.018,0.030,0.688 0.018,0.031,0.697 0.019,0.032,0.706 0.020,0.033,0.714 0.021,0.035,0.723 0.023,0.036,0.731 0.025,0.038,0.740 0.029,0.039,0.748 0.032,0.041,0.756 0.037,0.043,0.764 0.041,0.044,0.772 0.046,0.046,0.780 0.052,0.048,0.788 0.057,0.050,0.796 0.063,0.052,0.804 0.069,0.054,0.811 0.075,0.056,0.819 0.081,0.058,0.826 0.087,0.060,0.833 0.094,0.063,0.841 0.100,0.065,0.848 0.106,0.067,0.855 0.113,0.070,0.862 0.119,0.072,0.869 0.126,0.075,0.876 0.133,0.077,0.882 0.140,0.079,0.889 0.148,0.082,0.895 0.156,0.084,0.901 0.165,0.086,0.907 0.174,0.088,0.913 0.184,0.090,0.919 0.194,0.092,0.924 0.204,0.094,0.929 0.214,0.095,0.934 0.225,0.097,0.939 0.236,0.098,0.943 0.247,0.099,0.947 0.258,0.101,0.951 0.269,0.102,0.955 0.281,0.103,0.959 0.292,0.104,0.962 0.304,0.105,0.966 0.316,0.106,0.969 0.327,0.107,0.971 0.339,0.107,0.974 0.351,0.108,0.976 0.363,0.109,0.979 0.375,0.109,0.980 0.387,0.110,0.982 0.399,0.110,0.984 0.411,0.110,0.985 0.423,0.111,0.986 0.435,0.111,0.987 0.447,0.111,0.988 0.458,0.111,0.988 0.470,0.111,0.989 0.481,0.111,0.989 0.493,0.111,0.990 0.504,0.111,0.991 0.515,0.111,0.991 0.525,0.111,0.992 0.536,0.112,0.992 0.547,0.112,0.993 0.557,0.112,0.993 0.568,0.112,0.993 0.578,0.112,0.994 0.588,0.112,0.994 0.598,0.112,0.994 0.608,0.112,0.995 0.618,0.112,0.995 0.628,0.112,0.995 0.638,0.112,0.995 0.648,0.112,0.996 0.657,0.112,0.996 0.667,0.112,0.996 0.677,0.112,0.996 0.686,0.112,0.996 0.696,0.112,0.996 0.705,0.112,0.996 0.715,0.112,0.996 0.724,0.112,0.996 0.733,0.112,0.996 0.742,0.112,0.996 0.751,0.113,0.996 0.760,0.114,0.996 0.769,0.115,0.996 0.777,0.117,0.996 0.785,0.119,0.996 0.793,0.122,0.996 0.801,0.125,0.996 0.809,0.128,0.996 0.816,0.132,0.996 0.823,0.136,0.996 0.831,0.140,0.996 0.838,0.145,0.996 0.845,0.149,0.996 0.851,0.154,0.996 0.858,0.160,0.996 0.864,0.165,0.996 0.871,0.171,0.996 0.877,0.176,0.996 0.883,0.182,0.996 0.888,0.188,0.996 0.894,0.195,0.996 0.900,0.201,0.996 0.905,0.208,0.996 0.910,0.214,0.996 0.915,0.221,0.996 0.920,0.228,0.996 0.925,0.234,0.996 0.930,0.241,0.996 0.934,0.249,0.996 0.939,0.256,0.996 0.943,0.263,0.997 0.947,0.270,0.997 0.951,0.278,0.997 0.955,0.285,0.997 0.958,0.293,0.996 0.961,0.301,0.996 0.965,0.308,0.996 0.968,0.316,0.996 0.971,0.324,0.996 0.973,0.332,0.996 0.976,0.340,0.996 0.978,0.348,0.996 0.981,0.356,0.996 0.983,0.364,0.996 0.985,0.372,0.995 0.987,0.381,0.995 0.988,0.389,0.995 0.990,0.397,0.995 0.991,0.405,0.995 0.992,0.414,0.994 0.993,0.422,0.994 0.994,0.430,0.994 0.994,0.439,0.994 0.995,0.447,0.993 0.995,0.456,0.993 0.995,0.464,0.993 0.995,0.472,0.992 0.996,0.481,0.992 0.996,0.489,0.992 0.996,0.497,0.992 0.996,0.504,0.991 0.996,0.512,0.991 0.996,0.520,0.991 0.996,0.527,0.991 0.996,0.535,0.991 0.996,0.542,0.991 0.997,0.550,0.991 0.997,0.557,0.991 0.997,0.564,0.991 0.997,0.572,0.991 0.997,0.579,0.991 0.997,0.586,0.991 0.997,0.593,0.991 0.997,0.600,0.991 0.997,0.607,0.991 0.997,0.614,0.991 0.997,0.620,0.991 0.997,0.627,0.991 0.997,0.634,0.992 0.997,0.641,0.992 0.997,0.647,0.992 0.997,0.654,0.992 0.997,0.660,0.993 0.997,0.667,0.993 0.997,0.673,0.993 0.997,0.680,0.994 0.997,0.686,0.994 0.997,0.693,0.994 0.997,0.699,0.995 0.997,0.705,0.995 0.997,0.712,0.995 0.997,0.718,0.995 0.997,0.724,0.995 0.998,0.730,0.996 0.998,0.736,0.996 0.998,0.743,0.996 0.998,0.749,0.996 0.998,0.755,0.996 0.998,0.761,0.996 0.998,0.767,0.997 0.998,0.773,0.997 0.998,0.779,0.997 0.998,0.785,0.997 0.998,0.791,0.997 0.998,0.797,0.997 0.998,0.803,0.997 0.999,0.809,0.997 0.999,0.815,0.997 0.999,0.821,0.997 0.999,0.827,0.997 0.999,0.833,0.997 0.999,0.839,0.998 0.999,0.845,0.998 0.999,0.851,0.998 0.999,0.857,0.998 0.999,0.863,0.998 0.999,0.868,0.998 0.998,0.874,0.998 0.998,0.880,0.998 0.998,0.886,0.998 0.998,0.892,0.998 0.998,0.898,0.998 0.998,0.903,0.997 0.998,0.909,0.997 0.998,0.915,0.997 0.998,0.921,0.997 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L8.csv000066400000000000000000000110001421045507400235150ustar00rootroot000000000000000.002,0.058,0.364 0.002,0.060,0.370 0.003,0.062,0.377 0.004,0.064,0.383 0.006,0.065,0.390 0.008,0.067,0.396 0.010,0.069,0.402 0.013,0.071,0.409 0.016,0.072,0.415 0.019,0.074,0.421 0.023,0.076,0.427 0.028,0.077,0.433 0.033,0.079,0.439 0.039,0.081,0.445 0.045,0.082,0.451 0.052,0.084,0.457 0.058,0.085,0.463 0.065,0.087,0.468 0.071,0.088,0.474 0.078,0.089,0.480 0.085,0.091,0.485 0.092,0.092,0.490 0.099,0.093,0.496 0.107,0.095,0.501 0.114,0.096,0.506 0.122,0.097,0.511 0.129,0.098,0.516 0.137,0.099,0.520 0.145,0.100,0.525 0.153,0.101,0.529 0.162,0.102,0.534 0.170,0.102,0.538 0.178,0.103,0.542 0.187,0.104,0.546 0.196,0.104,0.549 0.205,0.104,0.553 0.214,0.105,0.556 0.224,0.105,0.559 0.233,0.105,0.562 0.243,0.105,0.564 0.253,0.104,0.566 0.263,0.104,0.568 0.273,0.103,0.570 0.284,0.102,0.571 0.295,0.101,0.572 0.306,0.100,0.573 0.317,0.098,0.573 0.328,0.097,0.573 0.339,0.095,0.574 0.350,0.093,0.574 0.360,0.092,0.574 0.370,0.090,0.574 0.380,0.088,0.574 0.390,0.087,0.573 0.399,0.085,0.573 0.409,0.083,0.573 0.418,0.081,0.573 0.428,0.079,0.572 0.437,0.077,0.572 0.446,0.075,0.572 0.455,0.074,0.571 0.464,0.072,0.570 0.473,0.070,0.570 0.481,0.068,0.569 0.490,0.066,0.568 0.499,0.064,0.568 0.507,0.062,0.567 0.515,0.060,0.566 0.524,0.058,0.565 0.532,0.056,0.564 0.540,0.054,0.563 0.548,0.052,0.562 0.556,0.051,0.560 0.564,0.049,0.559 0.572,0.048,0.558 0.580,0.046,0.556 0.588,0.045,0.555 0.596,0.043,0.554 0.603,0.042,0.552 0.611,0.041,0.551 0.619,0.040,0.549 0.626,0.039,0.548 0.634,0.038,0.546 0.641,0.038,0.545 0.648,0.037,0.543 0.656,0.037,0.542 0.663,0.037,0.540 0.670,0.037,0.538 0.677,0.037,0.537 0.684,0.037,0.535 0.691,0.038,0.534 0.698,0.039,0.532 0.705,0.040,0.530 0.712,0.041,0.529 0.719,0.042,0.527 0.726,0.044,0.525 0.733,0.045,0.523 0.739,0.047,0.522 0.746,0.049,0.520 0.753,0.051,0.518 0.759,0.054,0.516 0.766,0.056,0.514 0.772,0.059,0.513 0.779,0.061,0.511 0.785,0.064,0.509 0.792,0.067,0.507 0.798,0.070,0.505 0.804,0.074,0.503 0.811,0.078,0.501 0.817,0.082,0.499 0.823,0.087,0.496 0.828,0.092,0.494 0.834,0.097,0.492 0.840,0.103,0.489 0.845,0.108,0.487 0.850,0.114,0.484 0.856,0.120,0.481 0.861,0.126,0.478 0.866,0.133,0.475 0.871,0.139,0.472 0.876,0.146,0.469 0.880,0.152,0.466 0.885,0.159,0.463 0.889,0.166,0.460 0.894,0.173,0.456 0.898,0.180,0.453 0.902,0.187,0.449 0.906,0.194,0.445 0.910,0.201,0.441 0.914,0.208,0.438 0.918,0.215,0.434 0.921,0.223,0.429 0.924,0.230,0.425 0.928,0.237,0.421 0.931,0.245,0.417 0.934,0.252,0.412 0.937,0.259,0.408 0.940,0.267,0.403 0.943,0.274,0.399 0.945,0.281,0.394 0.948,0.289,0.390 0.950,0.296,0.386 0.952,0.303,0.382 0.955,0.311,0.378 0.957,0.318,0.374 0.959,0.325,0.370 0.961,0.333,0.366 0.962,0.340,0.363 0.964,0.347,0.359 0.966,0.354,0.355 0.967,0.361,0.352 0.968,0.369,0.348 0.970,0.376,0.345 0.971,0.383,0.341 0.972,0.390,0.338 0.973,0.398,0.335 0.974,0.405,0.332 0.974,0.412,0.329 0.975,0.419,0.325 0.975,0.426,0.323 0.976,0.434,0.320 0.976,0.441,0.317 0.976,0.448,0.314 0.976,0.455,0.311 0.976,0.462,0.309 0.976,0.469,0.306 0.976,0.477,0.304 0.976,0.484,0.301 0.976,0.491,0.299 0.976,0.497,0.296 0.976,0.504,0.294 0.976,0.511,0.291 0.976,0.517,0.289 0.976,0.524,0.286 0.976,0.530,0.284 0.976,0.537,0.281 0.976,0.543,0.278 0.976,0.549,0.276 0.976,0.555,0.273 0.976,0.562,0.271 0.977,0.568,0.268 0.977,0.574,0.266 0.977,0.580,0.263 0.978,0.585,0.261 0.978,0.591,0.258 0.979,0.597,0.256 0.979,0.603,0.253 0.980,0.609,0.250 0.980,0.614,0.248 0.981,0.620,0.245 0.981,0.625,0.243 0.982,0.631,0.240 0.983,0.636,0.238 0.983,0.642,0.235 0.984,0.647,0.232 0.985,0.653,0.230 0.986,0.658,0.227 0.987,0.663,0.225 0.987,0.669,0.224 0.988,0.674,0.222 0.988,0.680,0.221 0.989,0.685,0.220 0.989,0.690,0.220 0.989,0.696,0.219 0.990,0.701,0.219 0.990,0.707,0.218 0.990,0.712,0.218 0.990,0.718,0.218 0.990,0.723,0.219 0.990,0.728,0.219 0.991,0.734,0.219 0.991,0.739,0.220 0.991,0.745,0.221 0.990,0.750,0.221 0.990,0.755,0.222 0.990,0.761,0.223 0.990,0.766,0.224 0.990,0.772,0.225 0.990,0.777,0.226 0.990,0.783,0.228 0.989,0.788,0.229 0.989,0.793,0.230 0.989,0.799,0.232 0.988,0.804,0.233 0.988,0.810,0.235 0.987,0.815,0.237 0.987,0.820,0.238 0.987,0.826,0.240 0.986,0.831,0.242 0.986,0.836,0.244 0.985,0.842,0.245 0.984,0.847,0.247 0.984,0.853,0.249 0.983,0.858,0.251 0.983,0.863,0.254 0.982,0.869,0.256 0.981,0.874,0.258 0.980,0.879,0.260 0.980,0.885,0.262 0.979,0.890,0.264 0.978,0.895,0.267 0.977,0.901,0.269 0.976,0.906,0.271 0.975,0.912,0.274 0.974,0.917,0.276 0.973,0.922,0.279 0.972,0.928,0.281 0.971,0.933,0.284 0.970,0.938,0.286 0.969,0.944,0.289 0.968,0.949,0.291 0.967,0.954,0.294 0.966,0.960,0.297 0.964,0.965,0.299 0.963,0.970,0.302 0.962,0.976,0.305 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-L9.csv000066400000000000000000000110001421045507400235160ustar00rootroot000000000000000.020,0.000,0.673 0.022,0.009,0.675 0.024,0.020,0.676 0.026,0.032,0.677 0.028,0.043,0.679 0.029,0.053,0.680 0.031,0.063,0.682 0.033,0.072,0.683 0.035,0.080,0.684 0.036,0.087,0.686 0.038,0.094,0.687 0.039,0.101,0.688 0.041,0.108,0.689 0.042,0.114,0.690 0.043,0.121,0.692 0.045,0.127,0.693 0.046,0.133,0.694 0.047,0.139,0.695 0.048,0.144,0.696 0.048,0.150,0.697 0.049,0.155,0.698 0.050,0.161,0.699 0.051,0.166,0.700 0.051,0.171,0.701 0.052,0.177,0.702 0.052,0.182,0.703 0.053,0.187,0.703 0.053,0.192,0.704 0.053,0.197,0.705 0.054,0.202,0.705 0.054,0.207,0.706 0.054,0.212,0.707 0.054,0.216,0.707 0.054,0.221,0.708 0.054,0.226,0.708 0.054,0.231,0.709 0.054,0.236,0.709 0.054,0.240,0.709 0.053,0.245,0.709 0.053,0.250,0.710 0.053,0.254,0.710 0.052,0.259,0.710 0.052,0.264,0.710 0.051,0.268,0.710 0.051,0.273,0.710 0.050,0.278,0.709 0.049,0.282,0.709 0.048,0.287,0.709 0.047,0.292,0.708 0.046,0.296,0.708 0.045,0.301,0.707 0.044,0.305,0.706 0.043,0.310,0.705 0.042,0.315,0.704 0.041,0.319,0.703 0.039,0.324,0.702 0.038,0.329,0.700 0.036,0.333,0.698 0.035,0.338,0.697 0.033,0.343,0.695 0.031,0.347,0.692 0.030,0.352,0.690 0.028,0.357,0.687 0.026,0.362,0.684 0.025,0.367,0.681 0.023,0.372,0.677 0.022,0.377,0.673 0.022,0.381,0.668 0.024,0.386,0.663 0.028,0.391,0.659 0.032,0.396,0.654 0.039,0.401,0.648 0.045,0.405,0.643 0.052,0.410,0.637 0.059,0.414,0.632 0.067,0.419,0.626 0.074,0.423,0.620 0.080,0.428,0.614 0.087,0.432,0.607 0.094,0.437,0.601 0.100,0.441,0.594 0.106,0.446,0.588 0.112,0.450,0.581 0.117,0.454,0.574 0.123,0.459,0.567 0.128,0.463,0.560 0.133,0.467,0.553 0.138,0.472,0.545 0.142,0.476,0.538 0.147,0.480,0.530 0.151,0.485,0.523 0.155,0.489,0.515 0.158,0.493,0.507 0.162,0.497,0.499 0.165,0.502,0.491 0.168,0.506,0.482 0.171,0.510,0.474 0.174,0.514,0.466 0.176,0.519,0.457 0.179,0.523,0.448 0.181,0.527,0.440 0.182,0.531,0.431 0.184,0.536,0.422 0.185,0.540,0.413 0.187,0.544,0.403 0.188,0.548,0.394 0.188,0.552,0.384 0.189,0.557,0.375 0.190,0.561,0.366 0.191,0.565,0.357 0.192,0.569,0.348 0.194,0.573,0.339 0.196,0.577,0.331 0.198,0.581,0.323 0.200,0.585,0.315 0.202,0.588,0.307 0.205,0.592,0.299 0.208,0.596,0.292 0.211,0.600,0.284 0.215,0.603,0.277 0.218,0.607,0.270 0.222,0.611,0.263 0.226,0.614,0.256 0.230,0.618,0.250 0.235,0.621,0.243 0.240,0.625,0.237 0.245,0.628,0.230 0.250,0.631,0.224 0.255,0.635,0.218 0.260,0.638,0.212 0.266,0.641,0.205 0.272,0.645,0.200 0.278,0.648,0.194 0.284,0.651,0.188 0.290,0.654,0.182 0.296,0.657,0.176 0.303,0.661,0.171 0.309,0.664,0.165 0.316,0.667,0.159 0.323,0.670,0.154 0.329,0.673,0.148 0.336,0.676,0.143 0.343,0.679,0.137 0.351,0.682,0.132 0.358,0.685,0.127 0.365,0.688,0.121 0.372,0.691,0.116 0.380,0.694,0.111 0.387,0.696,0.105 0.395,0.699,0.100 0.403,0.702,0.095 0.411,0.705,0.090 0.418,0.707,0.084 0.426,0.710,0.079 0.434,0.713,0.074 0.442,0.716,0.069 0.450,0.718,0.064 0.458,0.721,0.058 0.466,0.723,0.053 0.475,0.726,0.048 0.483,0.728,0.043 0.491,0.731,0.038 0.500,0.733,0.033 0.508,0.736,0.029 0.516,0.738,0.025 0.525,0.741,0.022 0.533,0.743,0.019 0.542,0.746,0.016 0.551,0.748,0.014 0.559,0.750,0.013 0.568,0.752,0.011 0.577,0.755,0.010 0.586,0.757,0.010 0.594,0.759,0.010 0.603,0.761,0.009 0.611,0.764,0.009 0.620,0.766,0.010 0.628,0.768,0.010 0.637,0.770,0.010 0.645,0.773,0.011 0.653,0.775,0.012 0.662,0.777,0.013 0.670,0.779,0.014 0.678,0.781,0.015 0.686,0.784,0.017 0.694,0.786,0.019 0.702,0.788,0.021 0.709,0.790,0.023 0.717,0.793,0.025 0.725,0.795,0.028 0.733,0.797,0.031 0.740,0.800,0.034 0.748,0.802,0.038 0.755,0.804,0.042 0.763,0.806,0.045 0.770,0.809,0.049 0.777,0.811,0.053 0.784,0.813,0.057 0.792,0.816,0.062 0.799,0.818,0.066 0.806,0.820,0.070 0.813,0.823,0.075 0.820,0.825,0.079 0.826,0.828,0.084 0.833,0.830,0.089 0.840,0.833,0.093 0.846,0.835,0.098 0.853,0.837,0.103 0.859,0.840,0.108 0.865,0.843,0.113 0.872,0.845,0.118 0.878,0.848,0.124 0.884,0.850,0.129 0.890,0.853,0.135 0.895,0.856,0.140 0.901,0.858,0.146 0.906,0.861,0.152 0.912,0.864,0.158 0.917,0.867,0.164 0.922,0.870,0.171 0.927,0.873,0.177 0.932,0.876,0.184 0.936,0.879,0.191 0.940,0.882,0.198 0.944,0.885,0.206 0.948,0.888,0.213 0.952,0.892,0.221 0.955,0.895,0.230 0.957,0.899,0.238 0.960,0.903,0.248 0.962,0.906,0.258 0.964,0.910,0.269 0.967,0.914,0.282 0.969,0.917,0.296 0.971,0.921,0.311 0.974,0.924,0.327 0.976,0.928,0.345 0.979,0.931,0.363 0.981,0.934,0.383 0.984,0.937,0.404 0.987,0.940,0.426 0.989,0.943,0.450 0.991,0.946,0.475 0.994,0.949,0.501 0.996,0.952,0.529 0.997,0.955,0.558 0.999,0.957,0.590 1.000,0.960,0.623 1.000,0.962,0.658 1.000,0.965,0.695 1.000,0.967,0.735 0.998,0.969,0.777 0.996,0.971,0.822 0.991,0.973,0.870 0.985,0.975,0.922 0.977,0.977,0.977 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-R1.csv000066400000000000000000000110001421045507400235140ustar00rootroot000000000000000.000,0.187,0.963 0.000,0.202,0.949 0.000,0.217,0.935 0.000,0.231,0.921 0.011,0.244,0.908 0.036,0.257,0.894 0.056,0.269,0.880 0.069,0.281,0.867 0.079,0.292,0.853 0.087,0.302,0.840 0.092,0.313,0.826 0.096,0.323,0.813 0.098,0.333,0.799 0.100,0.342,0.786 0.100,0.352,0.772 0.099,0.361,0.759 0.098,0.370,0.745 0.097,0.378,0.732 0.095,0.386,0.719 0.093,0.395,0.706 0.092,0.402,0.693 0.092,0.410,0.680 0.092,0.417,0.667 0.093,0.425,0.654 0.095,0.432,0.641 0.099,0.438,0.629 0.104,0.445,0.616 0.110,0.451,0.604 0.117,0.457,0.592 0.125,0.463,0.580 0.134,0.469,0.568 0.142,0.474,0.556 0.151,0.480,0.544 0.160,0.485,0.532 0.169,0.491,0.520 0.177,0.496,0.509 0.185,0.501,0.497 0.193,0.506,0.485 0.200,0.511,0.473 0.206,0.516,0.462 0.213,0.522,0.450 0.218,0.527,0.438 0.223,0.532,0.426 0.228,0.537,0.414 0.232,0.542,0.402 0.236,0.547,0.390 0.239,0.552,0.378 0.242,0.557,0.365 0.245,0.562,0.353 0.247,0.567,0.340 0.249,0.573,0.327 0.250,0.578,0.314 0.252,0.583,0.301 0.253,0.588,0.288 0.254,0.593,0.275 0.255,0.598,0.262 0.256,0.603,0.248 0.257,0.608,0.235 0.258,0.613,0.221 0.260,0.618,0.208 0.263,0.623,0.195 0.266,0.627,0.182 0.270,0.632,0.169 0.274,0.636,0.156 0.280,0.641,0.144 0.286,0.645,0.133 0.293,0.649,0.122 0.302,0.653,0.112 0.311,0.657,0.102 0.320,0.660,0.094 0.331,0.664,0.086 0.341,0.667,0.080 0.353,0.671,0.075 0.364,0.674,0.070 0.376,0.677,0.067 0.388,0.680,0.065 0.400,0.683,0.064 0.412,0.686,0.064 0.425,0.689,0.064 0.437,0.692,0.064 0.449,0.695,0.065 0.461,0.698,0.067 0.473,0.701,0.068 0.485,0.703,0.070 0.496,0.706,0.071 0.508,0.709,0.073 0.520,0.712,0.075 0.531,0.715,0.077 0.542,0.717,0.078 0.554,0.720,0.080 0.565,0.723,0.082 0.576,0.725,0.084 0.587,0.728,0.086 0.598,0.731,0.087 0.609,0.733,0.089 0.620,0.736,0.091 0.631,0.739,0.093 0.642,0.741,0.095 0.653,0.744,0.096 0.664,0.746,0.098 0.674,0.749,0.100 0.685,0.751,0.102 0.696,0.754,0.103 0.707,0.757,0.105 0.717,0.759,0.107 0.728,0.762,0.109 0.738,0.764,0.111 0.749,0.766,0.112 0.759,0.769,0.114 0.770,0.771,0.116 0.780,0.774,0.118 0.791,0.776,0.120 0.801,0.778,0.121 0.812,0.781,0.123 0.822,0.783,0.125 0.832,0.785,0.127 0.843,0.787,0.128 0.853,0.789,0.130 0.863,0.791,0.132 0.873,0.793,0.133 0.882,0.794,0.135 0.892,0.795,0.136 0.901,0.796,0.137 0.910,0.797,0.139 0.918,0.797,0.140 0.926,0.797,0.141 0.933,0.796,0.142 0.940,0.795,0.142 0.946,0.793,0.143 0.952,0.791,0.143 0.957,0.789,0.143 0.962,0.786,0.143 0.966,0.782,0.143 0.969,0.779,0.142 0.972,0.774,0.142 0.974,0.770,0.141 0.976,0.765,0.140 0.977,0.760,0.139 0.978,0.754,0.138 0.979,0.749,0.137 0.979,0.743,0.136 0.980,0.737,0.135 0.980,0.732,0.133 0.980,0.726,0.132 0.979,0.720,0.131 0.979,0.714,0.130 0.979,0.707,0.128 0.979,0.701,0.127 0.978,0.695,0.126 0.978,0.689,0.124 0.977,0.683,0.123 0.977,0.677,0.122 0.976,0.670,0.121 0.976,0.664,0.119 0.975,0.658,0.118 0.975,0.652,0.117 0.974,0.645,0.116 0.974,0.639,0.114 0.973,0.633,0.113 0.973,0.627,0.112 0.972,0.620,0.111 0.971,0.614,0.110 0.971,0.608,0.108 0.970,0.601,0.107 0.969,0.595,0.106 0.969,0.588,0.105 0.968,0.582,0.104 0.967,0.575,0.103 0.966,0.569,0.102 0.966,0.563,0.100 0.965,0.556,0.099 0.964,0.549,0.098 0.963,0.543,0.097 0.962,0.536,0.096 0.961,0.530,0.095 0.961,0.523,0.094 0.960,0.516,0.093 0.959,0.510,0.092 0.958,0.503,0.091 0.957,0.496,0.090 0.956,0.489,0.089 0.955,0.482,0.088 0.954,0.476,0.087 0.953,0.469,0.086 0.952,0.462,0.085 0.951,0.455,0.084 0.950,0.448,0.083 0.949,0.440,0.083 0.948,0.433,0.082 0.947,0.426,0.082 0.946,0.419,0.082 0.945,0.412,0.082 0.944,0.405,0.083 0.943,0.398,0.084 0.942,0.391,0.086 0.941,0.384,0.088 0.941,0.378,0.091 0.940,0.371,0.095 0.940,0.365,0.100 0.940,0.360,0.106 0.940,0.355,0.113 0.940,0.350,0.121 0.941,0.346,0.130 0.942,0.343,0.139 0.943,0.340,0.150 0.944,0.338,0.161 0.946,0.337,0.173 0.948,0.337,0.186 0.950,0.338,0.199 0.952,0.339,0.213 0.954,0.341,0.227 0.957,0.343,0.242 0.959,0.346,0.257 0.962,0.350,0.272 0.965,0.354,0.288 0.967,0.358,0.304 0.970,0.362,0.320 0.973,0.367,0.336 0.975,0.372,0.352 0.978,0.377,0.368 0.980,0.382,0.385 0.983,0.387,0.401 0.985,0.392,0.418 0.987,0.397,0.434 0.990,0.403,0.451 0.992,0.408,0.467 0.993,0.413,0.484 0.995,0.418,0.500 0.997,0.424,0.517 0.999,0.429,0.533 1.000,0.434,0.550 1.000,0.440,0.567 1.000,0.445,0.583 1.000,0.451,0.600 1.000,0.456,0.617 1.000,0.461,0.634 1.000,0.467,0.651 1.000,0.472,0.668 1.000,0.478,0.685 1.000,0.483,0.702 1.000,0.488,0.719 1.000,0.494,0.736 1.000,0.499,0.753 1.000,0.505,0.770 1.000,0.510,0.787 1.000,0.516,0.805 1.000,0.521,0.822 1.000,0.527,0.840 1.000,0.532,0.857 1.000,0.538,0.874 1.000,0.544,0.892 1.000,0.549,0.910 0.998,0.555,0.927 0.997,0.560,0.945 0.995,0.566,0.963 0.992,0.572,0.980 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-R2.csv000066400000000000000000000110001421045507400235150ustar00rootroot000000000000000.000,0.204,0.963 0.000,0.215,0.951 0.000,0.226,0.940 0.000,0.237,0.929 0.000,0.247,0.918 0.000,0.256,0.907 0.000,0.266,0.896 0.000,0.275,0.885 0.000,0.283,0.874 0.000,0.292,0.863 0.000,0.300,0.852 0.000,0.309,0.841 0.000,0.317,0.830 0.000,0.325,0.819 0.000,0.332,0.808 0.000,0.340,0.797 0.000,0.347,0.786 0.000,0.355,0.775 0.000,0.362,0.765 0.000,0.369,0.754 0.000,0.376,0.743 0.000,0.383,0.732 0.000,0.390,0.721 0.000,0.396,0.710 0.000,0.403,0.699 0.000,0.409,0.689 0.000,0.416,0.678 0.000,0.422,0.667 0.000,0.428,0.657 0.000,0.433,0.646 0.000,0.439,0.636 0.000,0.444,0.626 0.000,0.449,0.616 0.000,0.454,0.606 0.000,0.459,0.596 0.004,0.464,0.586 0.030,0.468,0.577 0.056,0.472,0.567 0.077,0.477,0.558 0.095,0.481,0.548 0.111,0.485,0.539 0.125,0.489,0.530 0.138,0.493,0.520 0.149,0.497,0.511 0.160,0.501,0.502 0.169,0.505,0.492 0.177,0.508,0.483 0.185,0.512,0.474 0.192,0.516,0.464 0.199,0.520,0.455 0.204,0.524,0.445 0.210,0.529,0.436 0.215,0.533,0.426 0.219,0.537,0.417 0.223,0.541,0.407 0.227,0.545,0.397 0.230,0.549,0.387 0.233,0.553,0.377 0.236,0.557,0.367 0.238,0.561,0.357 0.240,0.565,0.347 0.242,0.569,0.336 0.243,0.574,0.326 0.245,0.578,0.315 0.246,0.582,0.304 0.246,0.586,0.293 0.247,0.590,0.282 0.247,0.594,0.271 0.247,0.599,0.259 0.247,0.603,0.247 0.247,0.607,0.235 0.246,0.611,0.222 0.246,0.615,0.209 0.246,0.619,0.196 0.246,0.623,0.183 0.246,0.627,0.170 0.247,0.631,0.156 0.249,0.635,0.143 0.252,0.639,0.130 0.256,0.642,0.117 0.261,0.646,0.106 0.267,0.649,0.095 0.274,0.652,0.085 0.282,0.655,0.076 0.291,0.658,0.070 0.300,0.661,0.065 0.311,0.664,0.061 0.321,0.666,0.059 0.332,0.669,0.058 0.343,0.671,0.058 0.354,0.674,0.058 0.364,0.676,0.060 0.375,0.679,0.061 0.386,0.681,0.063 0.396,0.683,0.065 0.407,0.686,0.067 0.417,0.688,0.069 0.427,0.690,0.071 0.437,0.693,0.072 0.447,0.695,0.074 0.457,0.697,0.076 0.467,0.700,0.078 0.476,0.702,0.080 0.486,0.704,0.082 0.495,0.706,0.084 0.505,0.709,0.086 0.514,0.711,0.088 0.523,0.713,0.089 0.533,0.715,0.091 0.542,0.717,0.093 0.551,0.720,0.095 0.560,0.722,0.097 0.569,0.724,0.098 0.578,0.726,0.100 0.587,0.728,0.102 0.596,0.730,0.104 0.605,0.733,0.106 0.614,0.735,0.107 0.622,0.737,0.109 0.631,0.739,0.111 0.640,0.741,0.113 0.649,0.743,0.115 0.657,0.745,0.116 0.666,0.747,0.118 0.675,0.749,0.120 0.683,0.751,0.122 0.692,0.753,0.123 0.701,0.756,0.125 0.709,0.758,0.127 0.718,0.760,0.129 0.726,0.762,0.130 0.735,0.764,0.132 0.743,0.766,0.134 0.752,0.768,0.136 0.760,0.770,0.137 0.769,0.772,0.139 0.777,0.773,0.141 0.786,0.775,0.143 0.794,0.777,0.144 0.803,0.779,0.146 0.811,0.781,0.148 0.819,0.783,0.150 0.828,0.785,0.151 0.836,0.787,0.153 0.845,0.789,0.155 0.853,0.791,0.157 0.861,0.793,0.158 0.870,0.794,0.160 0.878,0.796,0.162 0.886,0.798,0.163 0.895,0.800,0.165 0.903,0.801,0.167 0.911,0.802,0.168 0.919,0.804,0.170 0.927,0.805,0.171 0.935,0.805,0.172 0.942,0.805,0.173 0.949,0.805,0.174 0.955,0.804,0.174 0.961,0.803,0.174 0.966,0.801,0.174 0.971,0.798,0.173 0.975,0.795,0.172 0.978,0.792,0.171 0.981,0.788,0.170 0.983,0.784,0.168 0.985,0.779,0.167 0.987,0.775,0.165 0.988,0.770,0.163 0.989,0.764,0.161 0.990,0.759,0.158 0.991,0.754,0.156 0.991,0.749,0.154 0.992,0.744,0.152 0.993,0.738,0.150 0.993,0.733,0.148 0.994,0.728,0.145 0.995,0.722,0.143 0.995,0.717,0.141 0.996,0.712,0.139 0.996,0.706,0.137 0.997,0.701,0.134 0.997,0.695,0.132 0.998,0.690,0.130 0.998,0.684,0.128 0.998,0.679,0.126 0.999,0.674,0.123 0.999,0.668,0.121 1.000,0.663,0.119 1.000,0.657,0.117 1.000,0.652,0.115 1.000,0.646,0.112 1.000,0.641,0.110 1.000,0.635,0.108 1.000,0.629,0.106 1.000,0.624,0.104 1.000,0.618,0.101 1.000,0.613,0.099 1.000,0.607,0.097 1.000,0.601,0.095 1.000,0.596,0.092 1.000,0.590,0.090 1.000,0.584,0.088 1.000,0.578,0.086 1.000,0.573,0.084 1.000,0.567,0.081 1.000,0.561,0.079 1.000,0.555,0.077 1.000,0.549,0.075 1.000,0.543,0.072 1.000,0.537,0.070 1.000,0.531,0.068 1.000,0.525,0.065 1.000,0.519,0.063 1.000,0.513,0.061 1.000,0.507,0.059 1.000,0.501,0.056 1.000,0.495,0.054 1.000,0.489,0.051 1.000,0.482,0.049 1.000,0.476,0.047 1.000,0.470,0.044 1.000,0.463,0.042 1.000,0.457,0.040 1.000,0.450,0.037 1.000,0.444,0.035 1.000,0.437,0.032 1.000,0.430,0.030 1.000,0.424,0.028 1.000,0.417,0.026 1.000,0.410,0.024 1.000,0.403,0.022 1.000,0.396,0.020 1.000,0.389,0.019 1.000,0.382,0.017 1.000,0.374,0.015 0.999,0.367,0.013 0.999,0.359,0.012 0.999,0.352,0.010 0.998,0.344,0.009 0.998,0.336,0.007 0.997,0.328,0.006 0.997,0.320,0.005 0.997,0.311,0.004 0.996,0.303,0.002 0.996,0.294,0.001 0.995,0.285,0.000 0.995,0.276,0.000 0.994,0.266,0.000 0.994,0.257,0.000 0.993,0.247,0.000 0.993,0.236,0.000 0.992,0.225,0.000 0.992,0.214,0.000 0.991,0.202,0.000 0.990,0.189,0.000 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-R3.csv000066400000000000000000000110001421045507400235160ustar00rootroot000000000000000.032,0.362,0.973 0.058,0.371,0.958 0.076,0.380,0.943 0.089,0.389,0.928 0.098,0.397,0.914 0.105,0.406,0.899 0.110,0.414,0.884 0.114,0.422,0.869 0.116,0.430,0.854 0.116,0.438,0.839 0.115,0.445,0.824 0.113,0.453,0.809 0.109,0.461,0.794 0.105,0.468,0.780 0.099,0.475,0.765 0.093,0.482,0.750 0.086,0.489,0.735 0.080,0.496,0.720 0.075,0.503,0.705 0.071,0.509,0.689 0.071,0.515,0.674 0.074,0.521,0.658 0.080,0.527,0.643 0.089,0.532,0.627 0.100,0.538,0.611 0.113,0.543,0.594 0.126,0.548,0.578 0.139,0.553,0.561 0.152,0.557,0.544 0.164,0.562,0.527 0.175,0.566,0.510 0.185,0.571,0.493 0.194,0.575,0.475 0.202,0.580,0.458 0.209,0.584,0.440 0.215,0.589,0.422 0.220,0.593,0.404 0.224,0.598,0.386 0.227,0.602,0.368 0.230,0.607,0.349 0.232,0.611,0.331 0.233,0.616,0.312 0.234,0.620,0.294 0.236,0.624,0.276 0.237,0.629,0.258 0.239,0.633,0.240 0.241,0.637,0.223 0.244,0.641,0.207 0.249,0.644,0.193 0.254,0.648,0.179 0.261,0.651,0.167 0.268,0.655,0.156 0.277,0.658,0.147 0.286,0.661,0.140 0.297,0.663,0.134 0.307,0.666,0.130 0.318,0.669,0.126 0.329,0.671,0.124 0.340,0.674,0.122 0.351,0.676,0.120 0.362,0.678,0.119 0.373,0.681,0.118 0.384,0.683,0.117 0.394,0.686,0.116 0.405,0.688,0.116 0.415,0.690,0.115 0.425,0.693,0.114 0.435,0.695,0.113 0.445,0.697,0.113 0.455,0.699,0.112 0.465,0.702,0.111 0.474,0.704,0.110 0.484,0.706,0.110 0.493,0.709,0.109 0.503,0.711,0.108 0.512,0.713,0.107 0.521,0.715,0.106 0.531,0.718,0.105 0.540,0.720,0.104 0.549,0.722,0.103 0.558,0.724,0.103 0.567,0.726,0.102 0.576,0.729,0.101 0.585,0.731,0.100 0.594,0.733,0.099 0.603,0.735,0.098 0.611,0.737,0.097 0.620,0.739,0.096 0.629,0.742,0.095 0.638,0.744,0.094 0.646,0.746,0.092 0.655,0.748,0.091 0.663,0.750,0.090 0.672,0.752,0.089 0.681,0.754,0.088 0.689,0.756,0.087 0.698,0.758,0.085 0.706,0.760,0.084 0.715,0.762,0.083 0.723,0.765,0.082 0.732,0.767,0.080 0.740,0.769,0.079 0.748,0.771,0.078 0.757,0.773,0.076 0.765,0.775,0.075 0.774,0.777,0.073 0.782,0.779,0.072 0.790,0.781,0.070 0.799,0.783,0.069 0.807,0.785,0.067 0.815,0.786,0.065 0.824,0.788,0.064 0.832,0.790,0.062 0.840,0.792,0.060 0.849,0.794,0.058 0.857,0.796,0.057 0.865,0.798,0.055 0.874,0.800,0.054 0.882,0.801,0.054 0.890,0.803,0.054 0.898,0.804,0.056 0.907,0.805,0.058 0.915,0.806,0.063 0.923,0.806,0.069 0.930,0.806,0.077 0.938,0.806,0.087 0.945,0.805,0.098 0.951,0.803,0.110 0.957,0.801,0.123 0.963,0.798,0.136 0.968,0.794,0.149 0.972,0.790,0.162 0.975,0.786,0.175 0.978,0.781,0.188 0.980,0.776,0.200 0.982,0.771,0.212 0.984,0.765,0.224 0.985,0.760,0.235 0.986,0.754,0.245 0.987,0.749,0.255 0.988,0.743,0.265 0.989,0.738,0.275 0.990,0.732,0.284 0.991,0.726,0.293 0.991,0.720,0.302 0.992,0.715,0.310 0.993,0.709,0.319 0.993,0.703,0.327 0.994,0.697,0.335 0.994,0.692,0.343 0.995,0.686,0.351 0.995,0.680,0.358 0.996,0.674,0.366 0.996,0.668,0.373 0.996,0.663,0.380 0.997,0.657,0.387 0.997,0.651,0.394 0.997,0.645,0.401 0.998,0.639,0.408 0.998,0.633,0.415 0.998,0.627,0.422 0.998,0.621,0.428 0.998,0.615,0.435 0.998,0.609,0.441 0.999,0.603,0.448 0.999,0.596,0.454 0.999,0.590,0.461 0.999,0.584,0.467 0.999,0.578,0.473 0.998,0.572,0.479 0.998,0.565,0.486 0.998,0.559,0.492 0.998,0.553,0.498 0.998,0.546,0.504 0.998,0.540,0.510 0.997,0.533,0.516 0.997,0.527,0.522 0.997,0.520,0.528 0.997,0.514,0.533 0.996,0.507,0.539 0.996,0.500,0.545 0.995,0.494,0.551 0.995,0.487,0.557 0.995,0.480,0.562 0.994,0.473,0.568 0.993,0.466,0.574 0.993,0.459,0.579 0.992,0.452,0.585 0.992,0.445,0.591 0.991,0.437,0.596 0.990,0.430,0.602 0.990,0.423,0.608 0.989,0.415,0.613 0.988,0.407,0.619 0.987,0.400,0.624 0.986,0.392,0.629 0.986,0.384,0.635 0.985,0.376,0.640 0.984,0.368,0.644 0.983,0.360,0.649 0.982,0.352,0.653 0.981,0.344,0.656 0.980,0.336,0.658 0.979,0.328,0.660 0.978,0.320,0.660 0.976,0.312,0.659 0.975,0.305,0.656 0.974,0.298,0.652 0.973,0.291,0.646 0.972,0.285,0.639 0.970,0.279,0.631 0.969,0.273,0.621 0.968,0.268,0.610 0.966,0.262,0.599 0.965,0.257,0.587 0.963,0.252,0.574 0.961,0.248,0.562 0.959,0.243,0.548 0.958,0.238,0.535 0.956,0.233,0.522 0.954,0.229,0.509 0.951,0.224,0.495 0.949,0.219,0.482 0.947,0.215,0.469 0.945,0.210,0.456 0.942,0.205,0.442 0.940,0.200,0.429 0.937,0.196,0.416 0.935,0.191,0.403 0.932,0.186,0.390 0.929,0.181,0.377 0.926,0.176,0.364 0.923,0.171,0.351 0.921,0.166,0.338 0.918,0.161,0.325 0.915,0.156,0.311 0.911,0.151,0.298 0.908,0.146,0.285 0.905,0.140,0.272 0.902,0.135,0.259 0.898,0.129,0.246 0.895,0.124,0.233 0.892,0.118,0.220 0.888,0.112,0.206 0.885,0.106,0.193 0.881,0.100,0.179 0.877,0.094,0.165 0.874,0.087,0.151 0.870,0.080,0.136 0.866,0.073,0.121 0.862,0.065,0.105 0.859,0.057,0.088 0.855,0.048,0.070 0.851,0.038,0.049 0.847,0.028,0.024 0.843,0.018,0.002 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/CET-R4.csv000066400000000000000000000110001421045507400235170ustar00rootroot000000000000000.015,0.002,0.425 0.015,0.002,0.437 0.016,0.002,0.448 0.016,0.002,0.460 0.016,0.002,0.472 0.017,0.002,0.483 0.017,0.001,0.495 0.017,0.001,0.507 0.017,0.001,0.519 0.018,0.001,0.531 0.018,0.001,0.543 0.018,0.001,0.555 0.018,0.001,0.567 0.019,0.001,0.579 0.019,0.002,0.590 0.020,0.002,0.602 0.020,0.003,0.614 0.021,0.005,0.625 0.021,0.007,0.636 0.022,0.010,0.647 0.023,0.013,0.657 0.024,0.017,0.667 0.025,0.023,0.676 0.027,0.029,0.685 0.028,0.036,0.694 0.030,0.044,0.703 0.032,0.051,0.711 0.034,0.059,0.719 0.036,0.067,0.727 0.038,0.074,0.735 0.040,0.081,0.742 0.042,0.088,0.750 0.044,0.095,0.757 0.045,0.102,0.765 0.047,0.108,0.773 0.049,0.115,0.780 0.050,0.121,0.788 0.052,0.127,0.795 0.053,0.133,0.803 0.055,0.139,0.810 0.056,0.146,0.818 0.057,0.151,0.825 0.059,0.157,0.833 0.060,0.163,0.841 0.061,0.169,0.848 0.062,0.174,0.856 0.063,0.180,0.863 0.064,0.186,0.871 0.065,0.191,0.879 0.066,0.197,0.886 0.067,0.203,0.894 0.068,0.208,0.901 0.069,0.214,0.909 0.071,0.220,0.916 0.072,0.226,0.922 0.074,0.232,0.928 0.077,0.239,0.933 0.080,0.246,0.937 0.084,0.254,0.940 0.089,0.262,0.941 0.094,0.271,0.941 0.099,0.280,0.939 0.104,0.290,0.934 0.110,0.301,0.928 0.114,0.312,0.920 0.119,0.324,0.910 0.122,0.335,0.898 0.123,0.347,0.885 0.124,0.359,0.871 0.123,0.371,0.856 0.120,0.383,0.841 0.115,0.395,0.824 0.109,0.406,0.808 0.101,0.418,0.791 0.091,0.429,0.774 0.079,0.439,0.756 0.066,0.450,0.739 0.051,0.460,0.721 0.037,0.470,0.703 0.026,0.479,0.684 0.021,0.488,0.665 0.022,0.497,0.646 0.030,0.506,0.627 0.045,0.514,0.607 0.063,0.522,0.586 0.081,0.529,0.565 0.099,0.537,0.544 0.115,0.544,0.522 0.129,0.551,0.500 0.142,0.558,0.478 0.153,0.565,0.455 0.163,0.572,0.432 0.171,0.579,0.408 0.177,0.586,0.385 0.181,0.593,0.361 0.184,0.600,0.337 0.187,0.607,0.313 0.188,0.613,0.289 0.189,0.620,0.265 0.191,0.626,0.242 0.193,0.633,0.219 0.195,0.639,0.196 0.200,0.645,0.174 0.206,0.651,0.153 0.214,0.656,0.133 0.224,0.662,0.115 0.235,0.667,0.098 0.248,0.672,0.082 0.262,0.677,0.069 0.276,0.681,0.057 0.291,0.686,0.047 0.306,0.690,0.039 0.321,0.694,0.033 0.337,0.699,0.029 0.351,0.703,0.027 0.366,0.707,0.025 0.381,0.711,0.024 0.395,0.715,0.023 0.409,0.719,0.023 0.423,0.724,0.022 0.437,0.728,0.021 0.450,0.732,0.021 0.464,0.736,0.020 0.477,0.740,0.020 0.490,0.744,0.020 0.503,0.748,0.019 0.515,0.752,0.019 0.528,0.756,0.018 0.541,0.760,0.018 0.553,0.764,0.017 0.566,0.768,0.017 0.578,0.772,0.016 0.590,0.776,0.016 0.602,0.780,0.015 0.615,0.784,0.015 0.627,0.788,0.014 0.639,0.792,0.014 0.651,0.795,0.013 0.663,0.799,0.013 0.674,0.803,0.012 0.686,0.807,0.012 0.698,0.811,0.012 0.710,0.815,0.011 0.722,0.819,0.011 0.733,0.823,0.010 0.745,0.826,0.010 0.757,0.830,0.009 0.769,0.834,0.009 0.780,0.838,0.008 0.792,0.842,0.008 0.803,0.845,0.008 0.815,0.849,0.007 0.827,0.853,0.007 0.838,0.856,0.007 0.850,0.860,0.006 0.861,0.863,0.006 0.873,0.866,0.006 0.884,0.869,0.006 0.895,0.871,0.006 0.906,0.873,0.007 0.916,0.874,0.007 0.926,0.874,0.008 0.935,0.873,0.010 0.943,0.872,0.011 0.950,0.869,0.013 0.957,0.866,0.015 0.962,0.862,0.018 0.967,0.857,0.020 0.971,0.851,0.023 0.974,0.845,0.026 0.977,0.838,0.029 0.979,0.831,0.032 0.980,0.824,0.035 0.981,0.816,0.038 0.982,0.809,0.041 0.983,0.801,0.044 0.984,0.793,0.047 0.984,0.785,0.049 0.985,0.777,0.052 0.985,0.769,0.054 0.985,0.761,0.056 0.986,0.753,0.058 0.986,0.745,0.060 0.986,0.737,0.062 0.986,0.729,0.064 0.987,0.721,0.066 0.987,0.713,0.068 0.987,0.705,0.069 0.987,0.697,0.071 0.987,0.689,0.073 0.987,0.681,0.074 0.987,0.673,0.075 0.987,0.664,0.077 0.987,0.656,0.078 0.987,0.648,0.079 0.987,0.640,0.081 0.987,0.631,0.082 0.987,0.623,0.083 0.987,0.614,0.083 0.987,0.606,0.084 0.987,0.597,0.084 0.987,0.588,0.085 0.987,0.580,0.085 0.987,0.571,0.084 0.987,0.561,0.084 0.988,0.552,0.083 0.988,0.543,0.082 0.989,0.533,0.081 0.990,0.524,0.080 0.990,0.514,0.079 0.991,0.504,0.077 0.992,0.494,0.076 0.992,0.483,0.074 0.993,0.473,0.073 0.994,0.462,0.071 0.994,0.452,0.070 0.995,0.441,0.068 0.995,0.430,0.067 0.996,0.418,0.065 0.996,0.407,0.064 0.997,0.395,0.063 0.997,0.383,0.061 0.997,0.371,0.060 0.998,0.358,0.059 0.998,0.345,0.058 0.998,0.332,0.057 0.998,0.319,0.056 0.998,0.305,0.055 0.997,0.291,0.054 0.996,0.277,0.053 0.995,0.262,0.053 0.993,0.248,0.052 0.991,0.234,0.052 0.989,0.221,0.051 0.985,0.207,0.051 0.981,0.195,0.051 0.977,0.183,0.051 0.972,0.172,0.051 0.966,0.161,0.051 0.960,0.152,0.051 0.953,0.143,0.051 0.947,0.135,0.051 0.939,0.127,0.051 0.932,0.120,0.051 0.925,0.113,0.051 0.917,0.106,0.052 0.910,0.099,0.052 0.902,0.092,0.052 0.895,0.085,0.052 0.887,0.077,0.052 0.880,0.069,0.052 0.872,0.061,0.052 0.864,0.052,0.052 0.857,0.042,0.052 0.849,0.031,0.052 0.842,0.020,0.052 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/PAL-relaxed.hex000066400000000000000000000004341421045507400246610ustar00rootroot00000000000000; 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 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/PAL-relaxed_bright.hex000066400000000000000000000004341421045507400262200ustar00rootroot00000000000000; 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 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/cividis.csv000066400000000000000000000163641421045507400242750ustar00rootroot00000000000000; cividis colormap, optimized with consideration for color vision deficiency ; to enable accurate interpretation of scientific data ; ; Published by Jamie R. NuΓ±ez, Christopher R. Anderton and Ryan S. Renslow ; in PLoS ONE 13(7): e0199239. https://doi.org/10.1371/journal.pone.0199239 ; available under Creative Commons CC0 public domain dedication. ; ; You should have received a copy of the CC0 legalcode along with this ; work. If not, see . ; 0.000000,0.126200,0.301500 0.000000,0.129200,0.307700 0.000000,0.132100,0.314200 0.000000,0.135000,0.320500 0.000000,0.137900,0.326900 0.000000,0.140800,0.333400 0.000000,0.143700,0.340000 0.000000,0.146500,0.346700 0.000000,0.149200,0.353700 0.000000,0.151900,0.360600 0.000000,0.154600,0.367600 0.000000,0.157400,0.374600 0.000000,0.160100,0.381700 0.000000,0.162900,0.388800 0.000000,0.165700,0.396000 0.000000,0.168500,0.403100 0.000000,0.171400,0.410200 0.000000,0.174300,0.417200 0.000000,0.177300,0.424100 0.000000,0.179800,0.430700 0.000000,0.181700,0.434700 0.000000,0.183400,0.436300 0.000000,0.185200,0.436800 0.000000,0.187200,0.436800 0.000000,0.190100,0.436500 0.000000,0.193000,0.436100 0.000000,0.195800,0.435600 0.000000,0.198700,0.434900 0.000000,0.201500,0.434300 0.000000,0.204400,0.433600 0.000000,0.207300,0.432900 0.005500,0.210100,0.432200 0.023600,0.213000,0.431400 0.041600,0.215800,0.430800 0.057600,0.218700,0.430100 0.071000,0.221500,0.429300 0.082700,0.224400,0.428700 0.093200,0.227200,0.428000 0.103000,0.230000,0.427400 0.112000,0.232900,0.426800 0.120400,0.235700,0.426200 0.128300,0.238500,0.425600 0.135900,0.241400,0.425100 0.143100,0.244200,0.424500 0.150000,0.247000,0.424100 0.156600,0.249800,0.423600 0.163000,0.252600,0.423200 0.169200,0.255500,0.422800 0.175200,0.258300,0.422400 0.181100,0.261100,0.422000 0.186800,0.263900,0.421700 0.192300,0.266700,0.421400 0.197700,0.269500,0.421200 0.203000,0.272300,0.420900 0.208200,0.275100,0.420700 0.213300,0.278000,0.420500 0.218300,0.280800,0.420400 0.223200,0.283600,0.420300 0.228100,0.286400,0.420200 0.232800,0.289200,0.420100 0.237500,0.292000,0.420000 0.242100,0.294800,0.420000 0.246600,0.297600,0.420000 0.251100,0.300400,0.420100 0.255600,0.303200,0.420100 0.259900,0.306000,0.420200 0.264300,0.308800,0.420300 0.268600,0.311600,0.420500 0.272800,0.314400,0.420600 0.277000,0.317200,0.420800 0.281100,0.320000,0.421000 0.285300,0.322800,0.421200 0.289400,0.325600,0.421500 0.293400,0.328400,0.421800 0.297400,0.331200,0.422100 0.301400,0.334000,0.422400 0.305400,0.336800,0.422700 0.309300,0.339600,0.423100 0.313200,0.342400,0.423600 0.317000,0.345300,0.424000 0.320900,0.348100,0.424400 0.324700,0.350900,0.424900 0.328500,0.353700,0.425400 0.332300,0.356500,0.425900 0.336100,0.359300,0.426400 0.339800,0.362200,0.427000 0.343500,0.365000,0.427600 0.347200,0.367800,0.428200 0.350900,0.370600,0.428800 0.354600,0.373400,0.429400 0.358200,0.376300,0.430200 0.361900,0.379100,0.430800 0.365500,0.381900,0.431600 0.369100,0.384800,0.432200 0.372700,0.387600,0.433100 0.376300,0.390400,0.433800 0.379800,0.393300,0.434600 0.383400,0.396100,0.435500 0.386900,0.399000,0.436400 0.390500,0.401800,0.437200 0.394000,0.404700,0.438100 0.397500,0.407500,0.439000 0.401000,0.410400,0.440000 0.404500,0.413200,0.440900 0.408000,0.416100,0.441900 0.411400,0.418900,0.443000 0.414900,0.421800,0.444000 0.418300,0.424700,0.445000 0.421800,0.427500,0.446200 0.425200,0.430400,0.447300 0.428600,0.433300,0.448500 0.432000,0.436200,0.449600 0.435400,0.439000,0.450800 0.438800,0.441900,0.452100 0.442200,0.444800,0.453400 0.445600,0.447700,0.454700 0.448900,0.450600,0.456100 0.452300,0.453500,0.457500 0.455600,0.456400,0.458900 0.458900,0.459300,0.460400 0.462200,0.462200,0.462000 0.465600,0.465100,0.463500 0.468900,0.468000,0.465000 0.472200,0.470900,0.466500 0.475600,0.473800,0.467900 0.479000,0.476700,0.469100 0.482500,0.479700,0.470100 0.486100,0.482600,0.470700 0.489700,0.485600,0.471400 0.493400,0.488600,0.471900 0.497100,0.491500,0.472300 0.500800,0.494500,0.472700 0.504500,0.497500,0.473000 0.508300,0.500500,0.473200 0.512100,0.503500,0.473400 0.515800,0.506500,0.473600 0.519600,0.509500,0.473700 0.523400,0.512500,0.473800 0.527200,0.515500,0.473900 0.531000,0.518600,0.473900 0.534900,0.521600,0.473800 0.538700,0.524600,0.473900 0.542500,0.527700,0.473800 0.546400,0.530700,0.473600 0.550200,0.533800,0.473500 0.554100,0.536800,0.473300 0.557900,0.539900,0.473200 0.561800,0.543000,0.472900 0.565700,0.546100,0.472700 0.569600,0.549100,0.472300 0.573500,0.552200,0.472000 0.577400,0.555300,0.471700 0.581300,0.558400,0.471400 0.585200,0.561500,0.470900 0.589200,0.564600,0.470500 0.593100,0.567800,0.470100 0.597000,0.570900,0.469600 0.601000,0.574000,0.469100 0.605000,0.577200,0.468500 0.608900,0.580300,0.468000 0.612900,0.583500,0.467300 0.616800,0.586600,0.466800 0.620800,0.589800,0.466200 0.624800,0.592900,0.465500 0.628800,0.596100,0.464900 0.632800,0.599300,0.464100 0.636800,0.602500,0.463200 0.640800,0.605700,0.462500 0.644900,0.608900,0.461700 0.648900,0.612100,0.460900 0.652900,0.615300,0.460000 0.657000,0.618500,0.459100 0.661000,0.621700,0.458300 0.665100,0.625000,0.457300 0.669100,0.628200,0.456200 0.673200,0.631500,0.455300 0.677300,0.634700,0.454300 0.681300,0.638000,0.453200 0.685400,0.641200,0.452100 0.689500,0.644500,0.451100 0.693600,0.647800,0.449900 0.697700,0.651100,0.448700 0.701800,0.654400,0.447500 0.706000,0.657700,0.446300 0.710100,0.661000,0.445000 0.714200,0.664300,0.443700 0.718400,0.667600,0.442400 0.722500,0.671000,0.440900 0.726700,0.674300,0.439600 0.730800,0.677600,0.438200 0.735000,0.681000,0.436800 0.739200,0.684400,0.435200 0.743400,0.687700,0.433800 0.747600,0.691100,0.432200 0.751800,0.694500,0.430700 0.756000,0.697900,0.429000 0.760200,0.701300,0.427300 0.764400,0.704700,0.425800 0.768600,0.708100,0.424100 0.772900,0.711500,0.422300 0.777100,0.715000,0.420500 0.781400,0.718400,0.418800 0.785600,0.721800,0.416800 0.789900,0.725300,0.415000 0.794200,0.728800,0.412900 0.798500,0.732200,0.411100 0.802700,0.735700,0.409000 0.807000,0.739200,0.407000 0.811400,0.742700,0.404900 0.815700,0.746200,0.402800 0.820000,0.749700,0.400700 0.824300,0.753200,0.398400 0.828700,0.756800,0.396100 0.833000,0.760300,0.393800 0.837400,0.763900,0.391500 0.841700,0.767400,0.389200 0.846100,0.771000,0.386900 0.850500,0.774500,0.384300 0.854800,0.778100,0.381800 0.859200,0.781700,0.379300 0.863600,0.785300,0.376600 0.868100,0.788900,0.373900 0.872500,0.792600,0.371200 0.876900,0.796200,0.368400 0.881300,0.799800,0.365700 0.885800,0.803500,0.362700 0.890200,0.807100,0.359900 0.894700,0.810800,0.356900 0.899200,0.814500,0.353800 0.903700,0.818200,0.350700 0.908200,0.821900,0.347400 0.912700,0.825600,0.344200 0.917200,0.829300,0.340900 0.921700,0.833000,0.337400 0.926200,0.836700,0.334000 0.930800,0.840500,0.330600 0.935300,0.844200,0.326800 0.939900,0.848000,0.323200 0.944400,0.851800,0.319500 0.949000,0.855600,0.315500 0.953600,0.859300,0.311600 0.958200,0.863200,0.307600 0.962800,0.867000,0.303400 0.967400,0.870800,0.299000 0.972100,0.874600,0.294700 0.976700,0.878500,0.290100 0.981400,0.882300,0.285600 0.986000,0.886200,0.280700 0.990700,0.890100,0.275900 0.995400,0.894000,0.270800 1.000000,0.897900,0.265500 1.000000,0.901800,0.260000 1.000000,0.905700,0.259300 1.000000,0.909400,0.263400 1.000000,0.913100,0.268000 1.000000,0.916900,0.273100 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/inferno.csv000066400000000000000000000166261421045507400243040ustar00rootroot00000000000000; New matplotlib colormaps by Nathaniel J. Smith, Stefan van der Walt, ; and (in the case of viridis) Eric Firing. ; ; This file and the colormaps in it are released under the CC0 license / ; public domain dedication. The creators would appreciate credit if you use or ; redistribute these colormaps, but do not impose any legal restrictions. ; ; To the extent possible under law, the persons who associated CC0 with ; mpl-colormaps have waived all copyright and related or neighboring rights ; to mpl-colormaps. ; ; You should have received a copy of the CC0 legalcode along with this ; work. If not, see . ; 0.001462,0.000466,0.013866 0.002267,0.001270,0.018570 0.003299,0.002249,0.024239 0.004547,0.003392,0.030909 0.006006,0.004692,0.038558 0.007676,0.006136,0.046836 0.009561,0.007713,0.055143 0.011663,0.009417,0.063460 0.013995,0.011225,0.071862 0.016561,0.013136,0.080282 0.019373,0.015133,0.088767 0.022447,0.017199,0.097327 0.025793,0.019331,0.105930 0.029432,0.021503,0.114621 0.033385,0.023702,0.123397 0.037668,0.025921,0.132232 0.042253,0.028139,0.141141 0.046915,0.030324,0.150164 0.051644,0.032474,0.159254 0.056449,0.034569,0.168414 0.061340,0.036590,0.177642 0.066331,0.038504,0.186962 0.071429,0.040294,0.196354 0.076637,0.041905,0.205799 0.081962,0.043328,0.215289 0.087411,0.044556,0.224813 0.092990,0.045583,0.234358 0.098702,0.046402,0.243904 0.104551,0.047008,0.253430 0.110536,0.047399,0.262912 0.116656,0.047574,0.272321 0.122908,0.047536,0.281624 0.129285,0.047293,0.290788 0.135778,0.046856,0.299776 0.142378,0.046242,0.308553 0.149073,0.045468,0.317085 0.155850,0.044559,0.325338 0.162689,0.043554,0.333277 0.169575,0.042489,0.340874 0.176493,0.041402,0.348111 0.183429,0.040329,0.354971 0.190367,0.039309,0.361447 0.197297,0.038400,0.367535 0.204209,0.037632,0.373238 0.211095,0.037030,0.378563 0.217949,0.036615,0.383522 0.224763,0.036405,0.388129 0.231538,0.036405,0.392400 0.238273,0.036621,0.396353 0.244967,0.037055,0.400007 0.251620,0.037705,0.403378 0.258234,0.038571,0.406485 0.264810,0.039647,0.409345 0.271347,0.040922,0.411976 0.277850,0.042353,0.414392 0.284321,0.043933,0.416608 0.290763,0.045644,0.418637 0.297178,0.047470,0.420491 0.303568,0.049396,0.422182 0.309935,0.051407,0.423721 0.316282,0.053490,0.425116 0.322610,0.055634,0.426377 0.328921,0.057827,0.427511 0.335217,0.060060,0.428524 0.341500,0.062325,0.429425 0.347771,0.064616,0.430217 0.354032,0.066925,0.430906 0.360284,0.069247,0.431497 0.366529,0.071579,0.431994 0.372768,0.073915,0.432400 0.379001,0.076253,0.432719 0.385228,0.078591,0.432955 0.391453,0.080927,0.433109 0.397674,0.083257,0.433183 0.403894,0.085580,0.433179 0.410113,0.087896,0.433098 0.416331,0.090203,0.432943 0.422549,0.092501,0.432714 0.428768,0.094790,0.432412 0.434987,0.097069,0.432039 0.441207,0.099338,0.431594 0.447428,0.101597,0.431080 0.453651,0.103848,0.430498 0.459875,0.106089,0.429846 0.466100,0.108322,0.429125 0.472328,0.110547,0.428334 0.478558,0.112764,0.427475 0.484789,0.114974,0.426548 0.491022,0.117179,0.425552 0.497257,0.119379,0.424488 0.503493,0.121575,0.423356 0.509730,0.123769,0.422156 0.515967,0.125960,0.420887 0.522206,0.128150,0.419549 0.528444,0.130341,0.418142 0.534683,0.132534,0.416667 0.540920,0.134729,0.415123 0.547157,0.136929,0.413511 0.553392,0.139134,0.411829 0.559624,0.141346,0.410078 0.565854,0.143567,0.408258 0.572081,0.145797,0.406369 0.578304,0.148039,0.404411 0.584521,0.150294,0.402385 0.590734,0.152563,0.400290 0.596940,0.154848,0.398125 0.603139,0.157151,0.395891 0.609330,0.159474,0.393589 0.615513,0.161817,0.391219 0.621685,0.164184,0.388781 0.627847,0.166575,0.386276 0.633998,0.168992,0.383704 0.640135,0.171438,0.381065 0.646260,0.173914,0.378359 0.652369,0.176421,0.375586 0.658463,0.178962,0.372748 0.664540,0.181539,0.369846 0.670599,0.184153,0.366879 0.676638,0.186807,0.363849 0.682656,0.189501,0.360757 0.688653,0.192239,0.357603 0.694627,0.195021,0.354388 0.700576,0.197851,0.351113 0.706500,0.200728,0.347777 0.712396,0.203656,0.344383 0.718264,0.206636,0.340931 0.724103,0.209670,0.337424 0.729909,0.212759,0.333861 0.735683,0.215906,0.330245 0.741423,0.219112,0.326576 0.747127,0.222378,0.322856 0.752794,0.225706,0.319085 0.758422,0.229097,0.315266 0.764010,0.232554,0.311399 0.769556,0.236077,0.307485 0.775059,0.239667,0.303526 0.780517,0.243327,0.299523 0.785929,0.247056,0.295477 0.791293,0.250856,0.291390 0.796607,0.254728,0.287264 0.801871,0.258674,0.283099 0.807082,0.262692,0.278898 0.812239,0.266786,0.274661 0.817341,0.270954,0.270390 0.822386,0.275197,0.266085 0.827372,0.279517,0.261750 0.832299,0.283913,0.257383 0.837165,0.288385,0.252988 0.841969,0.292933,0.248564 0.846709,0.297559,0.244113 0.851384,0.302260,0.239636 0.855992,0.307038,0.235133 0.860533,0.311892,0.230606 0.865006,0.316822,0.226055 0.869409,0.321827,0.221482 0.873741,0.326906,0.216886 0.878001,0.332060,0.212268 0.882188,0.337287,0.207628 0.886302,0.342586,0.202968 0.890341,0.347957,0.198286 0.894305,0.353399,0.193584 0.898192,0.358911,0.188860 0.902003,0.364492,0.184116 0.905735,0.370140,0.179350 0.909390,0.375856,0.174563 0.912966,0.381636,0.169755 0.916462,0.387481,0.164924 0.919879,0.393389,0.160070 0.923215,0.399359,0.155193 0.926470,0.405389,0.150292 0.929644,0.411479,0.145367 0.932737,0.417627,0.140417 0.935747,0.423831,0.135440 0.938675,0.430091,0.130438 0.941521,0.436405,0.125409 0.944285,0.442772,0.120354 0.946965,0.449191,0.115272 0.949562,0.455660,0.110164 0.952075,0.462178,0.105031 0.954506,0.468744,0.099874 0.956852,0.475356,0.094695 0.959114,0.482014,0.089499 0.961293,0.488716,0.084289 0.963387,0.495462,0.079073 0.965397,0.502249,0.073859 0.967322,0.509078,0.068659 0.969163,0.515946,0.063488 0.970919,0.522853,0.058367 0.972590,0.529798,0.053324 0.974176,0.536780,0.048392 0.975677,0.543798,0.043618 0.977092,0.550850,0.039050 0.978422,0.557937,0.034931 0.979666,0.565057,0.031409 0.980824,0.572209,0.028508 0.981895,0.579392,0.026250 0.982881,0.586606,0.024661 0.983779,0.593849,0.023770 0.984591,0.601122,0.023606 0.985315,0.608422,0.024202 0.985952,0.615750,0.025592 0.986502,0.623105,0.027814 0.986964,0.630485,0.030908 0.987337,0.637890,0.034916 0.987622,0.645320,0.039886 0.987819,0.652773,0.045581 0.987926,0.660250,0.051750 0.987945,0.667748,0.058329 0.987874,0.675267,0.065257 0.987714,0.682807,0.072489 0.987464,0.690366,0.079990 0.987124,0.697944,0.087731 0.986694,0.705540,0.095694 0.986175,0.713153,0.103863 0.985566,0.720782,0.112229 0.984865,0.728427,0.120785 0.984075,0.736087,0.129527 0.983196,0.743758,0.138453 0.982228,0.751442,0.147565 0.981173,0.759135,0.156863 0.980032,0.766837,0.166353 0.978806,0.774545,0.176037 0.977497,0.782258,0.185923 0.976108,0.789974,0.196018 0.974638,0.797692,0.206332 0.973088,0.805409,0.216877 0.971468,0.813122,0.227658 0.969783,0.820825,0.238686 0.968041,0.828515,0.249972 0.966243,0.836191,0.261534 0.964394,0.843848,0.273391 0.962517,0.851476,0.285546 0.960626,0.859069,0.298010 0.958720,0.866624,0.310820 0.956834,0.874129,0.323974 0.954997,0.881569,0.337475 0.953215,0.888942,0.351369 0.951546,0.896226,0.365627 0.950018,0.903409,0.380271 0.948683,0.910473,0.395289 0.947594,0.917399,0.410665 0.946809,0.924168,0.426373 0.946392,0.930761,0.442367 0.946403,0.937159,0.458592 0.946903,0.943348,0.474970 0.947937,0.949318,0.491426 0.949545,0.955063,0.507860 0.951740,0.960587,0.524203 0.954529,0.965896,0.540361 0.957896,0.971003,0.556275 0.961812,0.975924,0.571925 0.966249,0.980678,0.587206 0.971162,0.985282,0.602154 0.976511,0.989753,0.616760 0.982257,0.994109,0.631017 0.988362,0.998364,0.644924 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/magma.csv000066400000000000000000000166261421045507400237260ustar00rootroot00000000000000; New matplotlib colormaps by Nathaniel J. Smith, Stefan van der Walt, ; and (in the case of viridis) Eric Firing. ; ; This file and the colormaps in it are released under the CC0 license / ; public domain dedication. The creators would appreciate credit if you use or ; redistribute these colormaps, but do not impose any legal restrictions. ; ; To the extent possible under law, the persons who associated CC0 with ; mpl-colormaps have waived all copyright and related or neighboring rights ; to mpl-colormaps. ; ; You should have received a copy of the CC0 legalcode along with this ; work. If not, see . ; 0.001462,0.000466,0.013866 0.002258,0.001295,0.018331 0.003279,0.002305,0.023708 0.004512,0.003490,0.029965 0.005950,0.004843,0.037130 0.007588,0.006356,0.044973 0.009426,0.008022,0.052844 0.011465,0.009828,0.060750 0.013708,0.011771,0.068667 0.016156,0.013840,0.076603 0.018815,0.016026,0.084584 0.021692,0.018320,0.092610 0.024792,0.020715,0.100676 0.028123,0.023201,0.108787 0.031696,0.025765,0.116965 0.035520,0.028397,0.125209 0.039608,0.031090,0.133515 0.043830,0.033830,0.141886 0.048062,0.036607,0.150327 0.052320,0.039407,0.158841 0.056615,0.042160,0.167446 0.060949,0.044794,0.176129 0.065330,0.047318,0.184892 0.069764,0.049726,0.193735 0.074257,0.052017,0.202660 0.078815,0.054184,0.211667 0.083446,0.056225,0.220755 0.088155,0.058133,0.229922 0.092949,0.059904,0.239164 0.097833,0.061531,0.248477 0.102815,0.063010,0.257854 0.107899,0.064335,0.267289 0.113094,0.065492,0.276784 0.118405,0.066479,0.286321 0.123833,0.067295,0.295879 0.129380,0.067935,0.305443 0.135053,0.068391,0.315000 0.140858,0.068654,0.324538 0.146785,0.068738,0.334011 0.152839,0.068637,0.343404 0.159018,0.068354,0.352688 0.165308,0.067911,0.361816 0.171713,0.067305,0.370771 0.178212,0.066576,0.379497 0.184801,0.065732,0.387973 0.191460,0.064818,0.396152 0.198177,0.063862,0.404009 0.204935,0.062907,0.411514 0.211718,0.061992,0.418647 0.218512,0.061158,0.425392 0.225302,0.060445,0.431742 0.232077,0.059889,0.437695 0.238826,0.059517,0.443256 0.245543,0.059352,0.448436 0.252220,0.059415,0.453248 0.258857,0.059706,0.457710 0.265447,0.060237,0.461840 0.271994,0.060994,0.465660 0.278493,0.061978,0.469190 0.284951,0.063168,0.472451 0.291366,0.064553,0.475462 0.297740,0.066117,0.478243 0.304081,0.067835,0.480812 0.310382,0.069702,0.483186 0.316654,0.071690,0.485380 0.322899,0.073782,0.487408 0.329114,0.075972,0.489287 0.335308,0.078236,0.491024 0.341482,0.080564,0.492631 0.347636,0.082946,0.494121 0.353773,0.085373,0.495501 0.359898,0.087831,0.496778 0.366012,0.090314,0.497960 0.372116,0.092816,0.499053 0.378211,0.095332,0.500067 0.384299,0.097855,0.501002 0.390384,0.100379,0.501864 0.396467,0.102902,0.502658 0.402548,0.105420,0.503386 0.408629,0.107930,0.504052 0.414709,0.110431,0.504662 0.420791,0.112920,0.505215 0.426877,0.115395,0.505714 0.432967,0.117855,0.506160 0.439062,0.120298,0.506555 0.445163,0.122724,0.506901 0.451271,0.125132,0.507198 0.457386,0.127522,0.507448 0.463508,0.129893,0.507652 0.469640,0.132245,0.507809 0.475780,0.134577,0.507921 0.481929,0.136891,0.507989 0.488088,0.139186,0.508011 0.494258,0.141462,0.507988 0.500438,0.143719,0.507920 0.506629,0.145958,0.507806 0.512831,0.148179,0.507648 0.519045,0.150383,0.507443 0.525270,0.152569,0.507192 0.531507,0.154739,0.506895 0.537755,0.156894,0.506551 0.544015,0.159033,0.506159 0.550287,0.161158,0.505719 0.556571,0.163269,0.505230 0.562866,0.165368,0.504692 0.569172,0.167454,0.504105 0.575490,0.169530,0.503466 0.581819,0.171596,0.502777 0.588158,0.173652,0.502035 0.594508,0.175701,0.501241 0.600868,0.177743,0.500394 0.607238,0.179779,0.499492 0.613617,0.181811,0.498536 0.620005,0.183840,0.497524 0.626401,0.185867,0.496456 0.632805,0.187893,0.495332 0.639216,0.189921,0.494150 0.645633,0.191952,0.492910 0.652056,0.193986,0.491611 0.658483,0.196027,0.490253 0.664915,0.198075,0.488836 0.671349,0.200133,0.487358 0.677786,0.202203,0.485819 0.684224,0.204286,0.484219 0.690661,0.206384,0.482558 0.697098,0.208501,0.480835 0.703532,0.210638,0.479049 0.709962,0.212797,0.477201 0.716387,0.214982,0.475290 0.722805,0.217194,0.473316 0.729216,0.219437,0.471279 0.735616,0.221713,0.469180 0.742004,0.224025,0.467018 0.748378,0.226377,0.464794 0.754737,0.228772,0.462509 0.761077,0.231214,0.460162 0.767398,0.233705,0.457755 0.773695,0.236249,0.455289 0.779968,0.238851,0.452765 0.786212,0.241514,0.450184 0.792427,0.244242,0.447543 0.798608,0.247040,0.444848 0.804752,0.249911,0.442102 0.810855,0.252861,0.439305 0.816914,0.255895,0.436461 0.822926,0.259016,0.433573 0.828886,0.262229,0.430644 0.834791,0.265540,0.427671 0.840636,0.268953,0.424666 0.846416,0.272473,0.421631 0.852126,0.276106,0.418573 0.857763,0.279857,0.415496 0.863320,0.283729,0.412403 0.868793,0.287728,0.409303 0.874176,0.291859,0.406205 0.879464,0.296125,0.403118 0.884651,0.300530,0.400047 0.889731,0.305079,0.397002 0.894700,0.309773,0.393995 0.899552,0.314616,0.391037 0.904281,0.319610,0.388137 0.908884,0.324755,0.385308 0.913354,0.330052,0.382563 0.917689,0.335500,0.379915 0.921884,0.341098,0.377376 0.925937,0.346844,0.374959 0.929845,0.352734,0.372677 0.933606,0.358764,0.370541 0.937221,0.364929,0.368567 0.940687,0.371224,0.366762 0.944006,0.377643,0.365136 0.947180,0.384178,0.363701 0.950210,0.390820,0.362468 0.953099,0.397563,0.361438 0.955849,0.404400,0.360619 0.958464,0.411324,0.360014 0.960949,0.418323,0.359630 0.963310,0.425390,0.359469 0.965549,0.432519,0.359529 0.967671,0.439703,0.359810 0.969680,0.446936,0.360311 0.971582,0.454210,0.361030 0.973381,0.461520,0.361965 0.975082,0.468861,0.363111 0.976690,0.476226,0.364466 0.978210,0.483612,0.366025 0.979645,0.491014,0.367783 0.981000,0.498428,0.369734 0.982279,0.505851,0.371874 0.983485,0.513280,0.374198 0.984622,0.520713,0.376698 0.985693,0.528148,0.379371 0.986700,0.535582,0.382210 0.987646,0.543015,0.385210 0.988533,0.550446,0.388365 0.989363,0.557873,0.391671 0.990138,0.565296,0.395122 0.990871,0.572706,0.398714 0.991558,0.580107,0.402441 0.992196,0.587502,0.406299 0.992785,0.594891,0.410283 0.993326,0.602275,0.414390 0.993834,0.609644,0.418613 0.994309,0.616999,0.422950 0.994738,0.624350,0.427397 0.995122,0.631696,0.431951 0.995480,0.639027,0.436607 0.995810,0.646344,0.441361 0.996096,0.653659,0.446213 0.996341,0.660969,0.451160 0.996580,0.668256,0.456192 0.996775,0.675541,0.461314 0.996925,0.682828,0.466526 0.997077,0.690088,0.471811 0.997186,0.697349,0.477182 0.997254,0.704611,0.482635 0.997325,0.711848,0.488154 0.997351,0.719089,0.493755 0.997351,0.726324,0.499428 0.997341,0.733545,0.505167 0.997285,0.740772,0.510983 0.997228,0.747981,0.516859 0.997138,0.755190,0.522806 0.997019,0.762398,0.528821 0.996898,0.769591,0.534892 0.996727,0.776795,0.541039 0.996571,0.783977,0.547233 0.996369,0.791167,0.553499 0.996162,0.798348,0.559820 0.995932,0.805527,0.566202 0.995680,0.812706,0.572645 0.995424,0.819875,0.579140 0.995131,0.827052,0.585701 0.994851,0.834213,0.592307 0.994524,0.841387,0.598983 0.994222,0.848540,0.605696 0.993866,0.855711,0.612482 0.993545,0.862859,0.619299 0.993170,0.870024,0.626189 0.992831,0.877168,0.633109 0.992440,0.884330,0.640099 0.992089,0.891470,0.647116 0.991688,0.898627,0.654202 0.991332,0.905763,0.661309 0.990930,0.912915,0.668481 0.990570,0.920049,0.675675 0.990175,0.927196,0.682926 0.989815,0.934329,0.690198 0.989434,0.941470,0.697519 0.989077,0.948604,0.704863 0.988717,0.955742,0.712242 0.988367,0.962878,0.719649 0.988033,0.970012,0.727077 0.987691,0.977154,0.734536 0.987387,0.984288,0.742002 0.987053,0.991438,0.749504 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/plasma.csv000066400000000000000000000166261421045507400241210ustar00rootroot00000000000000; New matplotlib colormaps by Nathaniel J. Smith, Stefan van der Walt, ; and (in the case of viridis) Eric Firing. ; ; This file and the colormaps in it are released under the CC0 license / ; public domain dedication. The creators would appreciate credit if you use or ; redistribute these colormaps, but do not impose any legal restrictions. ; ; To the extent possible under law, the persons who associated CC0 with ; mpl-colormaps have waived all copyright and related or neighboring rights ; to mpl-colormaps. ; ; You should have received a copy of the CC0 legalcode along with this ; work. If not, see . ; 0.050383,0.029803,0.527975 0.063536,0.028426,0.533124 0.075353,0.027206,0.538007 0.086222,0.026125,0.542658 0.096379,0.025165,0.547103 0.105980,0.024309,0.551368 0.115124,0.023556,0.555468 0.123903,0.022878,0.559423 0.132381,0.022258,0.563250 0.140603,0.021687,0.566959 0.148607,0.021154,0.570562 0.156421,0.020651,0.574065 0.164070,0.020171,0.577478 0.171574,0.019706,0.580806 0.178950,0.019252,0.584054 0.186213,0.018803,0.587228 0.193374,0.018354,0.590330 0.200445,0.017902,0.593364 0.207435,0.017442,0.596333 0.214350,0.016973,0.599239 0.221197,0.016497,0.602083 0.227983,0.016007,0.604867 0.234715,0.015502,0.607592 0.241396,0.014979,0.610259 0.248032,0.014439,0.612868 0.254627,0.013882,0.615419 0.261183,0.013308,0.617911 0.267703,0.012716,0.620346 0.274191,0.012109,0.622722 0.280648,0.011488,0.625038 0.287076,0.010855,0.627295 0.293478,0.010213,0.629490 0.299855,0.009561,0.631624 0.306210,0.008902,0.633694 0.312543,0.008239,0.635700 0.318856,0.007576,0.637640 0.325150,0.006915,0.639512 0.331426,0.006261,0.641316 0.337683,0.005618,0.643049 0.343925,0.004991,0.644710 0.350150,0.004382,0.646298 0.356359,0.003798,0.647810 0.362553,0.003243,0.649245 0.368733,0.002724,0.650601 0.374897,0.002245,0.651876 0.381047,0.001814,0.653068 0.387183,0.001434,0.654177 0.393304,0.001114,0.655199 0.399411,0.000859,0.656133 0.405503,0.000678,0.656977 0.411580,0.000577,0.657730 0.417642,0.000564,0.658390 0.423689,0.000646,0.658956 0.429719,0.000831,0.659425 0.435734,0.001127,0.659797 0.441732,0.001540,0.660069 0.447714,0.002080,0.660240 0.453677,0.002755,0.660310 0.459623,0.003574,0.660277 0.465550,0.004545,0.660139 0.471457,0.005678,0.659897 0.477344,0.006980,0.659549 0.483210,0.008460,0.659095 0.489055,0.010127,0.658534 0.494877,0.011990,0.657865 0.500678,0.014055,0.657088 0.506454,0.016333,0.656202 0.512206,0.018833,0.655209 0.517933,0.021563,0.654109 0.523633,0.024532,0.652901 0.529306,0.027747,0.651586 0.534952,0.031217,0.650165 0.540570,0.034950,0.648640 0.546157,0.038954,0.647010 0.551715,0.043136,0.645277 0.557243,0.047331,0.643443 0.562738,0.051545,0.641509 0.568201,0.055778,0.639477 0.573632,0.060028,0.637349 0.579029,0.064296,0.635126 0.584391,0.068579,0.632812 0.589719,0.072878,0.630408 0.595011,0.077190,0.627917 0.600266,0.081516,0.625342 0.605485,0.085854,0.622686 0.610667,0.090204,0.619951 0.615812,0.094564,0.617140 0.620919,0.098934,0.614257 0.625987,0.103312,0.611305 0.631017,0.107699,0.608287 0.636008,0.112092,0.605205 0.640959,0.116492,0.602065 0.645872,0.120898,0.598867 0.650746,0.125309,0.595617 0.655580,0.129725,0.592317 0.660374,0.134144,0.588971 0.665129,0.138566,0.585582 0.669845,0.142992,0.582154 0.674522,0.147419,0.578688 0.679160,0.151848,0.575189 0.683758,0.156278,0.571660 0.688318,0.160709,0.568103 0.692840,0.165141,0.564522 0.697324,0.169573,0.560919 0.701769,0.174005,0.557296 0.706178,0.178437,0.553657 0.710549,0.182868,0.550004 0.714883,0.187299,0.546338 0.719181,0.191729,0.542663 0.723444,0.196158,0.538981 0.727670,0.200586,0.535293 0.731862,0.205013,0.531601 0.736019,0.209439,0.527908 0.740143,0.213864,0.524216 0.744232,0.218288,0.520524 0.748289,0.222711,0.516834 0.752312,0.227133,0.513149 0.756304,0.231555,0.509468 0.760264,0.235976,0.505794 0.764193,0.240396,0.502126 0.768090,0.244817,0.498465 0.771958,0.249237,0.494813 0.775796,0.253658,0.491171 0.779604,0.258078,0.487539 0.783383,0.262500,0.483918 0.787133,0.266922,0.480307 0.790855,0.271345,0.476706 0.794549,0.275770,0.473117 0.798216,0.280197,0.469538 0.801855,0.284626,0.465971 0.805467,0.289057,0.462415 0.809052,0.293491,0.458870 0.812612,0.297928,0.455338 0.816144,0.302368,0.451816 0.819651,0.306812,0.448306 0.823132,0.311261,0.444806 0.826588,0.315714,0.441316 0.830018,0.320172,0.437836 0.833422,0.324635,0.434366 0.836801,0.329105,0.430905 0.840155,0.333580,0.427455 0.843484,0.338062,0.424013 0.846788,0.342551,0.420579 0.850066,0.347048,0.417153 0.853319,0.351553,0.413734 0.856547,0.356066,0.410322 0.859750,0.360588,0.406917 0.862927,0.365119,0.403519 0.866078,0.369660,0.400126 0.869203,0.374212,0.396738 0.872303,0.378774,0.393355 0.875376,0.383347,0.389976 0.878423,0.387932,0.386600 0.881443,0.392529,0.383229 0.884436,0.397139,0.379860 0.887402,0.401762,0.376494 0.890340,0.406398,0.373130 0.893250,0.411048,0.369768 0.896131,0.415712,0.366407 0.898984,0.420392,0.363047 0.901807,0.425087,0.359688 0.904601,0.429797,0.356329 0.907365,0.434524,0.352970 0.910098,0.439268,0.349610 0.912800,0.444029,0.346251 0.915471,0.448807,0.342890 0.918109,0.453603,0.339529 0.920714,0.458417,0.336166 0.923287,0.463251,0.332801 0.925825,0.468103,0.329435 0.928329,0.472975,0.326067 0.930798,0.477867,0.322697 0.933232,0.482780,0.319325 0.935630,0.487712,0.315952 0.937990,0.492667,0.312575 0.940313,0.497642,0.309197 0.942598,0.502639,0.305816 0.944844,0.507658,0.302433 0.947051,0.512699,0.299049 0.949217,0.517763,0.295662 0.951344,0.522850,0.292275 0.953428,0.527960,0.288883 0.955470,0.533093,0.285490 0.957469,0.538250,0.282096 0.959424,0.543431,0.278701 0.961336,0.548636,0.275305 0.963203,0.553865,0.271909 0.965024,0.559118,0.268513 0.966798,0.564396,0.265118 0.968526,0.569700,0.261721 0.970205,0.575028,0.258325 0.971835,0.580382,0.254931 0.973416,0.585761,0.251540 0.974947,0.591165,0.248151 0.976428,0.596595,0.244767 0.977856,0.602051,0.241387 0.979233,0.607532,0.238013 0.980556,0.613039,0.234646 0.981826,0.618572,0.231287 0.983041,0.624131,0.227937 0.984199,0.629718,0.224595 0.985301,0.635330,0.221265 0.986345,0.640969,0.217948 0.987332,0.646633,0.214648 0.988260,0.652325,0.211364 0.989128,0.658043,0.208100 0.989935,0.663787,0.204859 0.990681,0.669558,0.201642 0.991365,0.675355,0.198453 0.991985,0.681179,0.195295 0.992541,0.687030,0.192170 0.993032,0.692907,0.189084 0.993456,0.698810,0.186041 0.993814,0.704741,0.183043 0.994103,0.710698,0.180097 0.994324,0.716681,0.177208 0.994474,0.722691,0.174381 0.994553,0.728728,0.171622 0.994561,0.734791,0.168938 0.994495,0.740880,0.166335 0.994355,0.746995,0.163821 0.994141,0.753137,0.161404 0.993851,0.759304,0.159092 0.993482,0.765499,0.156891 0.993033,0.771720,0.154808 0.992505,0.777967,0.152855 0.991897,0.784239,0.151042 0.991209,0.790537,0.149377 0.990439,0.796859,0.147870 0.989587,0.803205,0.146529 0.988648,0.809579,0.145357 0.987621,0.815978,0.144363 0.986509,0.822401,0.143557 0.985314,0.828846,0.142945 0.984031,0.835315,0.142528 0.982653,0.841812,0.142303 0.981190,0.848329,0.142279 0.979644,0.854866,0.142453 0.977995,0.861432,0.142808 0.976265,0.868016,0.143351 0.974443,0.874622,0.144061 0.972530,0.881250,0.144923 0.970533,0.887896,0.145919 0.968443,0.894564,0.147014 0.966271,0.901249,0.148180 0.964021,0.907950,0.149370 0.961681,0.914672,0.150520 0.959276,0.921407,0.151566 0.956808,0.928152,0.152409 0.954287,0.934908,0.152921 0.951726,0.941671,0.152925 0.949151,0.948435,0.152178 0.946602,0.955190,0.150328 0.944152,0.961916,0.146861 0.941896,0.968590,0.140956 0.940015,0.975158,0.131326 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/maps/viridis.csv000066400000000000000000000166261421045507400243150ustar00rootroot00000000000000; New matplotlib colormaps by Nathaniel J. Smith, Stefan van der Walt, ; and (in the case of viridis) Eric Firing. ; ; This file and the colormaps in it are released under the CC0 license / ; public domain dedication. The creators would appreciate credit if you use or ; redistribute these colormaps, but do not impose any legal restrictions. ; ; To the extent possible under law, the persons who associated CC0 with ; mpl-colormaps have waived all copyright and related or neighboring rights ; to mpl-colormaps. ; ; You should have received a copy of the CC0 legalcode along with this ; work. If not, see . ; 0.267004,0.004874,0.329415 0.268510,0.009605,0.335427 0.269944,0.014625,0.341379 0.271305,0.019942,0.347269 0.272594,0.025563,0.353093 0.273809,0.031497,0.358853 0.274952,0.037752,0.364543 0.276022,0.044167,0.370164 0.277018,0.050344,0.375715 0.277941,0.056324,0.381191 0.278791,0.062145,0.386592 0.279566,0.067836,0.391917 0.280267,0.073417,0.397163 0.280894,0.078907,0.402329 0.281446,0.084320,0.407414 0.281924,0.089666,0.412415 0.282327,0.094955,0.417331 0.282656,0.100196,0.422160 0.282910,0.105393,0.426902 0.283091,0.110553,0.431554 0.283197,0.115680,0.436115 0.283229,0.120777,0.440584 0.283187,0.125848,0.444960 0.283072,0.130895,0.449241 0.282884,0.135920,0.453427 0.282623,0.140926,0.457517 0.282290,0.145912,0.461510 0.281887,0.150881,0.465405 0.281412,0.155834,0.469201 0.280868,0.160771,0.472899 0.280255,0.165693,0.476498 0.279574,0.170599,0.479997 0.278826,0.175490,0.483397 0.278012,0.180367,0.486697 0.277134,0.185228,0.489898 0.276194,0.190074,0.493001 0.275191,0.194905,0.496005 0.274128,0.199721,0.498911 0.273006,0.204520,0.501721 0.271828,0.209303,0.504434 0.270595,0.214069,0.507052 0.269308,0.218818,0.509577 0.267968,0.223549,0.512008 0.266580,0.228262,0.514349 0.265145,0.232956,0.516599 0.263663,0.237631,0.518762 0.262138,0.242286,0.520837 0.260571,0.246922,0.522828 0.258965,0.251537,0.524736 0.257322,0.256130,0.526563 0.255645,0.260703,0.528312 0.253935,0.265254,0.529983 0.252194,0.269783,0.531579 0.250425,0.274290,0.533103 0.248629,0.278775,0.534556 0.246811,0.283237,0.535941 0.244972,0.287675,0.537260 0.243113,0.292092,0.538516 0.241237,0.296485,0.539709 0.239346,0.300855,0.540844 0.237441,0.305202,0.541921 0.235526,0.309527,0.542944 0.233603,0.313828,0.543914 0.231674,0.318106,0.544834 0.229739,0.322361,0.545706 0.227802,0.326594,0.546532 0.225863,0.330805,0.547314 0.223925,0.334994,0.548053 0.221989,0.339161,0.548752 0.220057,0.343307,0.549413 0.218130,0.347432,0.550038 0.216210,0.351535,0.550627 0.214298,0.355619,0.551184 0.212395,0.359683,0.551710 0.210503,0.363727,0.552206 0.208623,0.367752,0.552675 0.206756,0.371758,0.553117 0.204903,0.375746,0.553533 0.203063,0.379716,0.553925 0.201239,0.383670,0.554294 0.199430,0.387607,0.554642 0.197636,0.391528,0.554969 0.195860,0.395433,0.555276 0.194100,0.399323,0.555565 0.192357,0.403199,0.555836 0.190631,0.407061,0.556089 0.188923,0.410910,0.556326 0.187231,0.414746,0.556547 0.185556,0.418570,0.556753 0.183898,0.422383,0.556944 0.182256,0.426184,0.557120 0.180629,0.429975,0.557282 0.179019,0.433756,0.557430 0.177423,0.437527,0.557565 0.175841,0.441290,0.557685 0.174274,0.445044,0.557792 0.172719,0.448791,0.557885 0.171176,0.452530,0.557965 0.169646,0.456262,0.558030 0.168126,0.459988,0.558082 0.166617,0.463708,0.558119 0.165117,0.467423,0.558141 0.163625,0.471133,0.558148 0.162142,0.474838,0.558140 0.160665,0.478540,0.558115 0.159194,0.482237,0.558073 0.157729,0.485932,0.558013 0.156270,0.489624,0.557936 0.154815,0.493313,0.557840 0.153364,0.497000,0.557724 0.151918,0.500685,0.557587 0.150476,0.504369,0.557430 0.149039,0.508051,0.557250 0.147607,0.511733,0.557049 0.146180,0.515413,0.556823 0.144759,0.519093,0.556572 0.143343,0.522773,0.556295 0.141935,0.526453,0.555991 0.140536,0.530132,0.555659 0.139147,0.533812,0.555298 0.137770,0.537492,0.554906 0.136408,0.541173,0.554483 0.135066,0.544853,0.554029 0.133743,0.548535,0.553541 0.132444,0.552216,0.553018 0.131172,0.555899,0.552459 0.129933,0.559582,0.551864 0.128729,0.563265,0.551229 0.127568,0.566949,0.550556 0.126453,0.570633,0.549841 0.125394,0.574318,0.549086 0.124395,0.578002,0.548287 0.123463,0.581687,0.547445 0.122606,0.585371,0.546557 0.121831,0.589055,0.545623 0.121148,0.592739,0.544641 0.120565,0.596422,0.543611 0.120092,0.600104,0.542530 0.119738,0.603785,0.541400 0.119512,0.607464,0.540218 0.119423,0.611141,0.538982 0.119483,0.614817,0.537692 0.119699,0.618490,0.536347 0.120081,0.622161,0.534946 0.120638,0.625828,0.533488 0.121380,0.629492,0.531973 0.122312,0.633153,0.530398 0.123444,0.636809,0.528763 0.124780,0.640461,0.527068 0.126326,0.644107,0.525311 0.128087,0.647749,0.523491 0.130067,0.651384,0.521608 0.132268,0.655014,0.519661 0.134692,0.658636,0.517649 0.137339,0.662252,0.515571 0.140210,0.665859,0.513427 0.143303,0.669459,0.511215 0.146616,0.673050,0.508936 0.150148,0.676631,0.506589 0.153894,0.680203,0.504172 0.157851,0.683765,0.501686 0.162016,0.687316,0.499129 0.166383,0.690856,0.496502 0.170948,0.694384,0.493803 0.175707,0.697900,0.491033 0.180653,0.701402,0.488189 0.185783,0.704891,0.485273 0.191090,0.708366,0.482284 0.196571,0.711827,0.479221 0.202219,0.715272,0.476084 0.208030,0.718701,0.472873 0.214000,0.722114,0.469588 0.220124,0.725509,0.466226 0.226397,0.728888,0.462789 0.232815,0.732247,0.459277 0.239374,0.735588,0.455688 0.246070,0.738910,0.452024 0.252899,0.742211,0.448284 0.259857,0.745492,0.444467 0.266941,0.748751,0.440573 0.274149,0.751988,0.436601 0.281477,0.755203,0.432552 0.288921,0.758394,0.428426 0.296479,0.761561,0.424223 0.304148,0.764704,0.419943 0.311925,0.767822,0.415586 0.319809,0.770914,0.411152 0.327796,0.773980,0.406640 0.335885,0.777018,0.402049 0.344074,0.780029,0.397381 0.352360,0.783011,0.392636 0.360741,0.785964,0.387814 0.369214,0.788888,0.382914 0.377779,0.791781,0.377939 0.386433,0.794644,0.372886 0.395174,0.797475,0.367757 0.404001,0.800275,0.362552 0.412913,0.803041,0.357269 0.421908,0.805774,0.351910 0.430983,0.808473,0.346476 0.440137,0.811138,0.340967 0.449368,0.813768,0.335384 0.458674,0.816363,0.329727 0.468053,0.818921,0.323998 0.477504,0.821444,0.318195 0.487026,0.823929,0.312321 0.496615,0.826376,0.306377 0.506271,0.828786,0.300362 0.515992,0.831158,0.294279 0.525776,0.833491,0.288127 0.535621,0.835785,0.281908 0.545524,0.838039,0.275626 0.555484,0.840254,0.269281 0.565498,0.842430,0.262877 0.575563,0.844566,0.256415 0.585678,0.846661,0.249897 0.595839,0.848717,0.243329 0.606045,0.850733,0.236712 0.616293,0.852709,0.230052 0.626579,0.854645,0.223353 0.636902,0.856542,0.216620 0.647257,0.858400,0.209861 0.657642,0.860219,0.203082 0.668054,0.861999,0.196293 0.678489,0.863742,0.189503 0.688944,0.865448,0.182725 0.699415,0.867117,0.175971 0.709898,0.868751,0.169257 0.720391,0.870350,0.162603 0.730889,0.871916,0.156029 0.741388,0.873449,0.149561 0.751884,0.874951,0.143228 0.762373,0.876424,0.137064 0.772852,0.877868,0.131109 0.783315,0.879285,0.125405 0.793760,0.880678,0.120005 0.804182,0.882046,0.114965 0.814576,0.883393,0.110347 0.824940,0.884720,0.106217 0.835270,0.886029,0.102646 0.845561,0.887322,0.099702 0.855810,0.888601,0.097452 0.866013,0.889868,0.095953 0.876168,0.891125,0.095250 0.886271,0.892374,0.095374 0.896320,0.893616,0.096335 0.906311,0.894855,0.098125 0.916242,0.896091,0.100717 0.926106,0.897330,0.104071 0.935904,0.898570,0.108131 0.945636,0.899815,0.112838 0.955300,0.901065,0.118128 0.964894,0.902323,0.123941 0.974417,0.903590,0.130215 0.983868,0.904867,0.136897 0.993248,0.906157,0.143936 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/colors/palette.py000066400000000000000000000070131421045507400231650ustar00rootroot00000000000000from ..Qt import QtGui def getQDarkStyleDarkQPalette(): BG_DARK = QtGui.QColor('#19232D') BG_NORMAL = QtGui.QColor('#37414F') BG_LIGHT = QtGui.QColor('#455364') FG_DARK = QtGui.QColor('#9DA9B5') FG_NORMAL = QtGui.QColor('#E0E1E3') FG_LIGHT = QtGui.QColor('#F0F0F0') SEL_DARK = QtGui.QColor('#1A72BB') SEL_NORMAL = QtGui.QColor('#26486B') SEL_LIGHT = QtGui.QColor('#346792') qpal = QtGui.QPalette(QtGui.QColor(BG_DARK)) for ptype in (QtGui.QPalette.Active, QtGui.QPalette.Inactive): qpal.setColor(ptype, QtGui.QPalette.Base, BG_DARK) qpal.setColor(ptype, QtGui.QPalette.Window, BG_DARK) qpal.setColor(ptype, QtGui.QPalette.WindowText, FG_NORMAL) qpal.setColor(ptype, QtGui.QPalette.AlternateBase, BG_LIGHT) qpal.setColor(ptype, QtGui.QPalette.Button, BG_LIGHT) qpal.setColor(ptype, QtGui.QPalette.ButtonText, FG_LIGHT) qpal.setColor(ptype, QtGui.QPalette.Highlight, SEL_LIGHT) qpal.setColor(ptype, QtGui.QPalette.HighlightedText, FG_LIGHT) qpal.setColor(ptype, QtGui.QPalette.Text, FG_LIGHT) qpal.setColor(ptype, QtGui.QPalette.ToolTipBase, BG_LIGHT) qpal.setColor(ptype, QtGui.QPalette.ToolTipText, FG_LIGHT) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Base, BG_NORMAL) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Button, BG_NORMAL) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, FG_DARK) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, FG_DARK) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, FG_DARK) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Highlight, BG_LIGHT) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.HighlightedText, FG_DARK) return qpal def getQDarkStyleLightQPalette(): BG_DARK = QtGui.QColor('#ACB1B6') BG_NORMAL = QtGui.QColor('#E0E1E3') BG_LIGHT = QtGui.QColor('#FAFAFA') FG_DARK = QtGui.QColor('#19223D') FG_NORMAL = QtGui.QColor('#293544') FG_LIGHT = QtGui.QColor('#788D9C') SEL_DARK = QtGui.QColor('#1A72BB') SEL_NORMAL = QtGui.QColor('#26486B') SEL_LIGHT = QtGui.QColor('#9FCBFF') qpal = QtGui.QPalette(QtGui.QColor(BG_LIGHT)) for ptype in (QtGui.QPalette.Active, QtGui.QPalette.Inactive): qpal.setColor(ptype, QtGui.QPalette.Base, BG_LIGHT) qpal.setColor(ptype, QtGui.QPalette.Window, BG_LIGHT) qpal.setColor(ptype, QtGui.QPalette.WindowText, FG_NORMAL) qpal.setColor(ptype, QtGui.QPalette.AlternateBase, BG_NORMAL) qpal.setColor(ptype, QtGui.QPalette.Button, BG_NORMAL) qpal.setColor(ptype, QtGui.QPalette.ButtonText, FG_DARK) qpal.setColor(ptype, QtGui.QPalette.Highlight, SEL_LIGHT) qpal.setColor(ptype, QtGui.QPalette.HighlightedText, FG_DARK) qpal.setColor(ptype, QtGui.QPalette.Text, FG_DARK) qpal.setColor(ptype, QtGui.QPalette.ToolTipBase, BG_DARK) qpal.setColor(ptype, QtGui.QPalette.ToolTipText, FG_DARK) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Base, BG_NORMAL) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Button, BG_NORMAL) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, FG_LIGHT) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, FG_LIGHT) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, FG_LIGHT) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Highlight, SEL_LIGHT) qpal.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.HighlightedText, FG_NORMAL) return qpal pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/configfile.py000066400000000000000000000152001421045507400223300ustar00rootroot00000000000000""" configfile.py - Human-readable text configuration file library Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. Used for reading and writing dictionary objects to a python-like configuration file format. Data structures may be nested and contain any data type as long as it can be converted to/from a string using repr and eval. """ import datetime import os import re import sys import tempfile from collections import OrderedDict import numpy from . import units from .colormap import ColorMap from .Point import Point from .Qt import QtCore GLOBAL_PATH = None # so not thread safe. class ParseError(Exception): def __init__(self, message, lineNum, line, fileName=None): self.lineNum = lineNum self.line = line self.message = message self.fileName = fileName Exception.__init__(self, message) def __str__(self): if self.fileName is None: msg = "Error parsing string at line %d:\n" % self.lineNum else: msg = "Error parsing config file '%s' at line %d:\n" % (self.fileName, self.lineNum) msg += "%s\n%s" % (self.line, Exception.__str__(self)) return msg def writeConfigFile(data, fname): s = genString(data) with open(fname, 'wt') as fd: fd.write(s) def readConfigFile(fname, **scope): #cwd = os.getcwd() global GLOBAL_PATH if GLOBAL_PATH is not None: fname2 = os.path.join(GLOBAL_PATH, fname) if os.path.exists(fname2): 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, "rt") as fd: s = fd.read() s = s.replace("\r\n", "\n") s = s.replace("\r", "\n") data = parseString(s, **local)[1] except ParseError: sys.exc_info()[1].fileName = fname raise except: print("Error while reading config file %s:"% fname) raise #finally: #os.chdir(cwd) return data def appendConfigFile(data, fname): s = genString(data) with open(fname, 'at') as fd: fd.write(s) def genString(data, indent=''): s = '' for k in data: sk = str(k) if len(sk) == 0: print(data) raise Exception('blank dict keys not allowed (see data above)') if sk[0] == ' ' or ':' in sk: print(data) raise Exception('dict keys must not contain ":" or start with spaces [offending key is "%s"]' % sk) if isinstance(data[k], dict): s += indent + sk + ':\n' s += genString(data[k], indent + ' ') else: s += indent + sk + ': ' + repr(data[k]).replace("\n", "\\\n") + '\n' return s def parseString(lines, start=0, **scope): data = OrderedDict() 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 indent = measureIndent(lines[start]) ln = start - 1 try: while True: ln += 1 #print ln if ln >= len(lines): break l = lines[ln] ## Skip blank lines or lines starting with # if re.match(r'\s*#', l) or not re.search(r'\S', l): continue ## Measure line indentation, make sure it is correct for this level lineInd = measureIndent(l) if lineInd < indent: ln -= 1 break if lineInd > indent: #print lineInd, indent raise ParseError('Indentation is incorrect. Expected %d, got %d' % (indent, lineInd), ln+1, l) if ':' not in l: raise ParseError('Missing colon', ln+1, l) (k, p, v) = l.partition(':') k = k.strip() v = v.strip() ## set up local variables to use for eval 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, 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, scope) except: ex = sys.exc_info()[1] raise ParseError("Error evaluating expression '%s': [%s: %s]" % (v, ex.__class__.__name__, str(ex)), (ln+1), l) else: if ln+1 >= len(lines) or measureIndent(lines[ln+1]) <= indent: #print "blank dict" val = {} else: #print "Going deeper..", ln+1 (ln, val) = parseString(lines, start=ln+1, **scope) data[k] = val #print k, repr(val) except ParseError: raise except: ex = sys.exc_info()[1] raise ParseError("%s: %s" % (ex.__class__.__name__, str(ex)), ln+1, l) #print "Returning shallower..", ln+1 return (ln, data) def measureIndent(s): n = 0 while n < len(s) and s[n] == ' ': n += 1 return n if __name__ == '__main__': cf = """ key: 'value' key2: ##comment ##comment key21: 'value' ## comment ##comment key22: [1,2,3] key23: 234 #comment """ with tempfile.NamedTemporaryFile(encoding="utf-8") as tf: tf.write(cf.encode("utf-8")) print("=== Test:===") for num, line in enumerate(cf.split('\n'), start=1): print("%02d %s" % (num, line)) print(cf) print("============") data = readConfigFile(tf.name) print(data) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/console/000077500000000000000000000000001421045507400213155ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/console/CmdInput.py000066400000000000000000000022471421045507400234170ustar00rootroot00000000000000from ..Qt import QtCore, QtWidgets class CmdInput(QtWidgets.QLineEdit): sigExecuteCmd = QtCore.Signal(object) def __init__(self, parent): QtWidgets.QLineEdit.__init__(self, parent) self.history = [""] self.ptr = 0 def keyPressEvent(self, ev): if ev.key() == QtCore.Qt.Key.Key_Up: if self.ptr < len(self.history) - 1: self.setHistory(self.ptr+1) ev.accept() return elif ev.key() == QtCore.Qt.Key.Key_Down: if self.ptr > 0: self.setHistory(self.ptr-1) ev.accept() return elif ev.key() == QtCore.Qt.Key.Key_Return: self.execCmd() else: super().keyPressEvent(ev) self.history[0] = self.text() def execCmd(self): cmd = self.text() if len(self.history) == 1 or cmd != self.history[1]: self.history.insert(1, cmd) self.history[0] = "" self.setHistory(0) self.sigExecuteCmd.emit(cmd) def setHistory(self, num): self.ptr = num self.setText(self.history[self.ptr]) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/console/Console.py000066400000000000000000000462661421045507400233070ustar00rootroot00000000000000import importlib import pickle import re import subprocess import sys import traceback from .. import exceptionHandling as exceptionHandling from .. import getConfigOption from ..functions import SignalBlock from ..Qt import QT_LIB, QtCore, QtGui, QtWidgets ui_template = importlib.import_module( f'.template_{QT_LIB.lower()}', package=__package__) class ConsoleWidget(QtWidgets.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 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 """ _threadException = QtCore.Signal(object) def __init__(self, parent=None, namespace=None, historyFile=None, text=None, editor=None): """ ============== ============================================================================ **Arguments:** namespace dictionary containing the initial variables present in the default namespace historyFile optional file for storing command history text initial text to display in the console window editor optional string for invoking code editor (called when stack trace entries are double-clicked). May contain {fileName} and {lineNum} format keys. Example:: editorCommand --loadfile {fileName} --gotoline {lineNum} ============== ============================================================================= """ QtWidgets.QWidget.__init__(self, parent) if namespace is None: namespace = {} namespace['__console__'] = self self.localNamespace = namespace self.editor = editor self.multiline = None self.inCmd = False self.frames = [] # stack frames to access when an item in the stack list is selected self.ui = ui_template.Ui_Form() self.ui.setupUi(self) self.output = self.ui.output self.input = self.ui.input self.input.setFocus() if text is not None: self.output.setPlainText(text) self.historyFile = historyFile history = self.loadHistory() if history is not None: self.input.history = [""] + history self.ui.historyList.addItems(history[::-1]) self.ui.historyList.hide() self.ui.exceptionGroup.hide() self.input.sigExecuteCmd.connect(self.runCmd) self.ui.historyBtn.toggled.connect(self.ui.historyList.setVisible) self.ui.historyList.itemClicked.connect(self.cmdSelected) self.ui.historyList.itemDoubleClicked.connect(self.cmdDblClicked) self.ui.exceptionBtn.toggled.connect(self.ui.exceptionGroup.setVisible) self.ui.catchAllExceptionsBtn.toggled.connect(self.catchAllExceptions) self.ui.catchNextExceptionBtn.toggled.connect(self.catchNextException) self.ui.clearExceptionBtn.clicked.connect(self.clearExceptionClicked) self.ui.exceptionStackList.itemClicked.connect(self.stackItemClicked) self.ui.exceptionStackList.itemDoubleClicked.connect(self.stackItemDblClicked) self.ui.onlyUncaughtCheck.toggled.connect(self.updateSysTrace) self.currentTraceback = None # send exceptions raised in non-gui threads back to the main thread by signal. self._threadException.connect(self._threadExceptionHandler) def loadHistory(self): """Return the list of previously-invoked command strings (or None).""" if self.historyFile is not None: with open(self.historyFile, 'rb') as pf: return pickle.load(pf) def saveHistory(self, history): """Store the list of previously-invoked command strings.""" if self.historyFile is not None: with open(self.historyFile, 'wb') as pf: pickle.dump(pf, history) def runCmd(self, cmd): #cmd = str(self.input.lastCmd) orig_stdout = sys.stdout orig_stderr = sys.stderr encCmd = re.sub(r'>', '>', re.sub(r'<', '<', cmd)) encCmd = re.sub(r' ', ' ', encCmd) self.ui.historyList.addItem(cmd) self.saveHistory(self.input.history[1:100]) try: sys.stdout = self sys.stderr = self if self.multiline is not None: self.write("
      %s\n"%encCmd, html=True, scrollToBottom=True) self.execMulti(cmd) else: self.write("
      %s\n"%encCmd, html=True, scrollToBottom=True) self.inCmd = True self.execSingle(cmd) if not self.inCmd: self.write("
      \n", html=True, scrollToBottom=True) finally: sys.stdout = orig_stdout sys.stderr = orig_stderr sb = self.ui.historyList.verticalScrollBar() sb.setValue(sb.maximum()) def globals(self): frame = self.currentFrame() if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): return self.currentFrame().f_globals else: return self.localNamespace def locals(self): frame = self.currentFrame() if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): return self.currentFrame().f_locals else: return self.localNamespace def currentFrame(self): ## Return the currently selected exception stack frame (or None if there is no exception) index = self.ui.exceptionStackList.currentRow() if index >= 0 and index < len(self.frames): return self.frames[index] else: return None def execSingle(self, cmd): try: output = eval(cmd, self.globals(), self.locals()) self.write(repr(output) + '\n') return except SyntaxError: pass except: self.displayException() return # eval failed with syntax error; try exec instead try: exec(cmd, self.globals(), self.locals()) except SyntaxError as exc: if 'unexpected EOF' in exc.msg: self.multiline = cmd else: self.displayException() except: self.displayException() def execMulti(self, nextLine): if nextLine.strip() != '': self.multiline += "\n" + nextLine return else: cmd = self.multiline try: output = eval(cmd, self.globals(), self.locals()) self.write(str(output) + '\n') self.multiline = None return except SyntaxError: pass except: self.displayException() self.multiline = None return # eval failed with syntax error; try exec instead try: exec(cmd, self.globals(), self.locals()) self.multiline = None except SyntaxError as exc: if 'unexpected EOF' in exc.msg: self.multiline = cmd else: self.displayException() self.multiline = None except: self.displayException() self.multiline = None def write(self, strn, html=False, scrollToBottom='auto'): """Write a string into the console. If scrollToBottom is 'auto', then the console is automatically scrolled to fit the new text only if it was already at the bottom. """ isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() if not isGuiThread: sys.__stdout__.write(strn) return sb = self.output.verticalScrollBar() scroll = sb.value() if scrollToBottom == 'auto': atBottom = scroll == sb.maximum() scrollToBottom = atBottom self.output.moveCursor(QtGui.QTextCursor.MoveOperation.End) if html: self.output.textCursor().insertHtml(strn) else: if self.inCmd: self.inCmd = False self.output.textCursor().insertHtml("
      ") self.output.insertPlainText(strn) if scrollToBottom: sb.setValue(sb.maximum()) else: sb.setValue(scroll) def fileno(self): # Need to implement this since we temporarily occlude sys.stdout, and someone may be looking for it (faulthandler, for example) return 1 def displayException(self): """ Display the current exception and stack. """ tb = traceback.format_exc() lines = [] indent = 4 prefix = '' for l in tb.split('\n'): lines.append(" "*indent + prefix + l) self.write('\n'.join(lines)) self.exceptionHandler(*sys.exc_info()) def cmdSelected(self, item): index = -(self.ui.historyList.row(item)+1) self.input.setHistory(index) self.input.setFocus() def cmdDblClicked(self, item): index = -(self.ui.historyList.row(item)+1) self.input.setHistory(index) self.input.execCmd() def flush(self): pass def catchAllExceptions(self, catch=True): """ If True, the console will catch all unhandled exceptions and display the stack trace. Each exception caught clears the last. """ with SignalBlock(self.ui.catchAllExceptionsBtn.toggled, self.catchAllExceptions): self.ui.catchAllExceptionsBtn.setChecked(catch) if catch: with SignalBlock(self.ui.catchNextExceptionBtn.toggled, self.catchNextException): self.ui.catchNextExceptionBtn.setChecked(False) self.enableExceptionHandling() self.ui.exceptionBtn.setChecked(True) else: self.disableExceptionHandling() def catchNextException(self, catch=True): """ If True, the console will catch the next unhandled exception and display the stack trace. """ with SignalBlock(self.ui.catchNextExceptionBtn.toggled, self.catchNextException): self.ui.catchNextExceptionBtn.setChecked(catch) if catch: with SignalBlock(self.ui.catchAllExceptionsBtn.toggled, self.catchAllExceptions): self.ui.catchAllExceptionsBtn.setChecked(False) self.enableExceptionHandling() self.ui.exceptionBtn.setChecked(True) else: self.disableExceptionHandling() def enableExceptionHandling(self): exceptionHandling.register(self.exceptionHandler) self.updateSysTrace() def disableExceptionHandling(self): exceptionHandling.unregister(self.exceptionHandler) self.updateSysTrace() def clearExceptionClicked(self): self.currentTraceback = None self.frames = [] self.ui.exceptionInfoLabel.setText("[No current exception]") self.ui.exceptionStackList.clear() self.ui.clearExceptionBtn.setEnabled(False) def stackItemClicked(self, item): pass def stackItemDblClicked(self, item): editor = self.editor if editor is None: editor = getConfigOption('editorCommand') if editor is None: return tb = self.currentFrame() lineNum = tb.f_lineno fileName = tb.f_code.co_filename subprocess.Popen(self.editor.format(fileName=fileName, lineNum=lineNum), shell=True) def updateSysTrace(self): ## Install or uninstall sys.settrace handler if not self.ui.catchNextExceptionBtn.isChecked() and not self.ui.catchAllExceptionsBtn.isChecked(): if sys.gettrace() == self.systrace: sys.settrace(None) return if self.ui.onlyUncaughtCheck.isChecked(): if sys.gettrace() == self.systrace: sys.settrace(None) else: if sys.gettrace() is not None and sys.gettrace() != self.systrace: self.ui.onlyUncaughtCheck.setChecked(False) raise Exception("sys.settrace is in use; cannot monitor for caught exceptions.") else: sys.settrace(self.systrace) def exceptionHandler(self, excType, exc, tb, systrace=False, frame=None): if frame is None: frame = sys._getframe() # exceptions raised in non-gui threads must be handled separately isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() if not isGuiThread: # sending a frame from one thread to another.. probably not safe, but better than just # dropping the exception? self._threadException.emit((excType, exc, tb, systrace, frame.f_back)) return if self.ui.catchNextExceptionBtn.isChecked(): self.ui.catchNextExceptionBtn.setChecked(False) elif not self.ui.catchAllExceptionsBtn.isChecked(): return self.currentTraceback = tb excMessage = ''.join(traceback.format_exception_only(excType, exc)) self.ui.exceptionInfoLabel.setText(excMessage) if systrace: # exceptions caught using systrace don't need the usual # call stack + traceback handling self.setStack(frame.f_back.f_back) else: self.setStack(frame=frame.f_back, tb=tb) def _threadExceptionHandler(self, args): self.exceptionHandler(*args) def setStack(self, frame=None, tb=None): """Display a call stack and exception traceback. This allows the user to probe the contents of any frame in the given stack. *frame* may either be a Frame instance or None, in which case the current frame is retrieved from ``sys._getframe()``. If *tb* is provided then the frames in the traceback will be appended to the end of the stack list. If *tb* is None, then sys.exc_info() will be checked instead. """ self.ui.clearExceptionBtn.setEnabled(True) if frame is None: frame = sys._getframe().f_back if tb is None: tb = sys.exc_info()[2] self.ui.exceptionStackList.clear() self.frames = [] # Build stack up to this point for index, line in enumerate(traceback.extract_stack(frame)): # extract_stack return value changed in python 3.5 if 'FrameSummary' in str(type(line)): line = (line.filename, line.lineno, line.name, line._line) self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line) while frame is not None: self.frames.insert(0, frame) frame = frame.f_back if tb is None: return self.ui.exceptionStackList.addItem('-- exception caught here: --') item = self.ui.exceptionStackList.item(self.ui.exceptionStackList.count()-1) item.setBackground(QtGui.QBrush(QtGui.QColor(200, 200, 200))) item.setForeground(QtGui.QBrush(QtGui.QColor(50, 50, 50))) self.frames.append(None) # And finish the rest of the stack up to the exception for index, line in enumerate(traceback.extract_tb(tb)): # extract_stack return value changed in python 3.5 if 'FrameSummary' in str(type(line)): line = (line.filename, line.lineno, line.name, line._line) self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line) while tb is not None: self.frames.append(tb.tb_frame) tb = tb.tb_next def systrace(self, frame, event, arg): if event == 'exception' and self.checkException(*arg): self.exceptionHandler(*arg, systrace=True) return self.systrace def checkException(self, excType, exc, tb): ## Return True if the exception is interesting; False if it should be ignored. filename = tb.tb_frame.f_code.co_filename function = tb.tb_frame.f_code.co_name filterStr = str(self.ui.filterText.text()) if filterStr != '': if isinstance(exc, Exception): msg = traceback.format_exception_only(type(exc), exc) elif isinstance(exc, str): msg = exc else: msg = repr(exc) match = re.search(filterStr, "%s:%s:%s" % (filename, function, msg)) return match is not None ## Go through a list of common exception points we like to ignore: if excType is GeneratorExit or excType is StopIteration: return False if excType is KeyError: if filename.endswith('python2.7/weakref.py') and function in ('__contains__', 'get'): return False if filename.endswith('python2.7/copy.py') and function == '_keep_alive': return False if excType is AttributeError: if filename.endswith('python2.7/collections.py') and function == '__init__': return False if filename.endswith('numpy/core/fromnumeric.py') and function in ('all', '_wrapit', 'transpose', 'sum'): return False if filename.endswith('numpy/core/arrayprint.py') and function in ('_array2string'): return False if filename.endswith('MetaArray.py') and function == '__getattr__': for name in ('__array_interface__', '__array_struct__', '__array__'): ## numpy looks for these when converting objects to array if name in exc: return False if filename.endswith('flowchart/eq.py'): return False if filename.endswith('pyqtgraph/functions.py') and function == 'makeQImage': return False if excType is TypeError: if filename.endswith('numpy/lib/function_base.py') and function == 'iterable': return False if excType is ZeroDivisionError: if filename.endswith('python2.7/traceback.py'): return False return True pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/console/__init__.py000066400000000000000000000000431421045507400234230ustar00rootroot00000000000000from .Console import ConsoleWidget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/console/template.ui000066400000000000000000000131461421045507400234740ustar00rootroot00000000000000 Form 0 0 739 497 Console 0 0 Qt::Vertical Monospace true History.. true Exceptions.. true Monospace Exception Handling 0 0 2 0 false Clear Stack Show All Exceptions true Show Next Exception true Only Uncaught Exceptions true true Run commands in selected stack frame true Stack Trace true Qt::Horizontal 40 20 Filter (regex): CmdInput QLineEdit
      .CmdInput
      pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/console/template_pyqt5.py000066400000000000000000000140771421045507400246550ustar00rootroot00000000000000 # Form implementation generated from reading ui file 'pyqtgraph/console/template.ui' # # Created by: PyQt5 UI code generator 5.5.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(739, 497) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Vertical) self.splitter.setObjectName("splitter") self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget) self.verticalLayout.setObjectName("verticalLayout") self.output = QtWidgets.QPlainTextEdit(self.layoutWidget) font = QtGui.QFont() font.setFamily("Monospace") self.output.setFont(font) self.output.setReadOnly(True) self.output.setObjectName("output") self.verticalLayout.addWidget(self.output) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.input = CmdInput(self.layoutWidget) self.input.setObjectName("input") self.horizontalLayout.addWidget(self.input) self.historyBtn = QtWidgets.QPushButton(self.layoutWidget) self.historyBtn.setCheckable(True) self.historyBtn.setObjectName("historyBtn") self.horizontalLayout.addWidget(self.historyBtn) self.exceptionBtn = QtWidgets.QPushButton(self.layoutWidget) self.exceptionBtn.setCheckable(True) self.exceptionBtn.setObjectName("exceptionBtn") self.horizontalLayout.addWidget(self.exceptionBtn) self.verticalLayout.addLayout(self.horizontalLayout) self.historyList = QtWidgets.QListWidget(self.splitter) font = QtGui.QFont() font.setFamily("Monospace") self.historyList.setFont(font) self.historyList.setObjectName("historyList") self.exceptionGroup = QtWidgets.QGroupBox(self.splitter) self.exceptionGroup.setObjectName("exceptionGroup") self.gridLayout_2 = QtWidgets.QGridLayout(self.exceptionGroup) self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) self.gridLayout_2.setHorizontalSpacing(2) self.gridLayout_2.setVerticalSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") self.clearExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) self.clearExceptionBtn.setEnabled(False) self.clearExceptionBtn.setObjectName("clearExceptionBtn") self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1) self.catchAllExceptionsBtn = QtWidgets.QPushButton(self.exceptionGroup) self.catchAllExceptionsBtn.setCheckable(True) self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn") self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) self.catchNextExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) self.catchNextExceptionBtn.setCheckable(True) self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn") self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) self.onlyUncaughtCheck = QtWidgets.QCheckBox(self.exceptionGroup) self.onlyUncaughtCheck.setChecked(True) self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck") self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1) self.exceptionStackList = QtWidgets.QListWidget(self.exceptionGroup) self.exceptionStackList.setAlternatingRowColors(True) self.exceptionStackList.setObjectName("exceptionStackList") self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7) self.runSelectedFrameCheck = QtWidgets.QCheckBox(self.exceptionGroup) self.runSelectedFrameCheck.setChecked(True) self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck") self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7) self.exceptionInfoLabel = QtWidgets.QLabel(self.exceptionGroup) self.exceptionInfoLabel.setWordWrap(True) self.exceptionInfoLabel.setObjectName("exceptionInfoLabel") self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.gridLayout_2.addItem(spacerItem, 0, 5, 1, 1) self.label = QtWidgets.QLabel(self.exceptionGroup) self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1) self.filterText = QtWidgets.QLineEdit(self.exceptionGroup) self.filterText.setObjectName("filterText") self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1) self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Console")) self.historyBtn.setText(_translate("Form", "History..")) self.exceptionBtn.setText(_translate("Form", "Exceptions..")) self.exceptionGroup.setTitle(_translate("Form", "Exception Handling")) self.clearExceptionBtn.setText(_translate("Form", "Clear Stack")) self.catchAllExceptionsBtn.setText(_translate("Form", "Show All Exceptions")) self.catchNextExceptionBtn.setText(_translate("Form", "Show Next Exception")) self.onlyUncaughtCheck.setText(_translate("Form", "Only Uncaught Exceptions")) self.runSelectedFrameCheck.setText(_translate("Form", "Run commands in selected stack frame")) self.exceptionInfoLabel.setText(_translate("Form", "Stack Trace")) self.label.setText(_translate("Form", "Filter (regex):")) from .CmdInput import CmdInput pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/console/template_pyqt6.py000066400000000000000000000143631421045507400246540ustar00rootroot00000000000000# Form implementation generated from reading ui file '../pyqtgraph/console/template.ui' # # Created by: PyQt6 UI code generator 6.1.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(739, 497) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Orientation.Vertical) self.splitter.setObjectName("splitter") self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") self.output = QtWidgets.QPlainTextEdit(self.layoutWidget) font = QtGui.QFont() font.setFamily("Monospace") self.output.setFont(font) self.output.setReadOnly(True) self.output.setObjectName("output") self.verticalLayout.addWidget(self.output) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.input = CmdInput(self.layoutWidget) self.input.setObjectName("input") self.horizontalLayout.addWidget(self.input) self.historyBtn = QtWidgets.QPushButton(self.layoutWidget) self.historyBtn.setCheckable(True) self.historyBtn.setObjectName("historyBtn") self.horizontalLayout.addWidget(self.historyBtn) self.exceptionBtn = QtWidgets.QPushButton(self.layoutWidget) self.exceptionBtn.setCheckable(True) self.exceptionBtn.setObjectName("exceptionBtn") self.horizontalLayout.addWidget(self.exceptionBtn) self.verticalLayout.addLayout(self.horizontalLayout) self.historyList = QtWidgets.QListWidget(self.splitter) font = QtGui.QFont() font.setFamily("Monospace") self.historyList.setFont(font) self.historyList.setObjectName("historyList") self.exceptionGroup = QtWidgets.QGroupBox(self.splitter) self.exceptionGroup.setObjectName("exceptionGroup") self.gridLayout_2 = QtWidgets.QGridLayout(self.exceptionGroup) self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) self.gridLayout_2.setHorizontalSpacing(2) self.gridLayout_2.setVerticalSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") self.clearExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) self.clearExceptionBtn.setEnabled(False) self.clearExceptionBtn.setObjectName("clearExceptionBtn") self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1) self.catchAllExceptionsBtn = QtWidgets.QPushButton(self.exceptionGroup) self.catchAllExceptionsBtn.setCheckable(True) self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn") self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) self.catchNextExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) self.catchNextExceptionBtn.setCheckable(True) self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn") self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) self.onlyUncaughtCheck = QtWidgets.QCheckBox(self.exceptionGroup) self.onlyUncaughtCheck.setChecked(True) self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck") self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1) self.exceptionStackList = QtWidgets.QListWidget(self.exceptionGroup) self.exceptionStackList.setAlternatingRowColors(True) self.exceptionStackList.setObjectName("exceptionStackList") self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7) self.runSelectedFrameCheck = QtWidgets.QCheckBox(self.exceptionGroup) self.runSelectedFrameCheck.setChecked(True) self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck") self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7) self.exceptionInfoLabel = QtWidgets.QLabel(self.exceptionGroup) self.exceptionInfoLabel.setWordWrap(True) self.exceptionInfoLabel.setObjectName("exceptionInfoLabel") self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) self.gridLayout_2.addItem(spacerItem, 0, 5, 1, 1) self.label = QtWidgets.QLabel(self.exceptionGroup) self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1) self.filterText = QtWidgets.QLineEdit(self.exceptionGroup) self.filterText.setObjectName("filterText") self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1) self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Console")) self.historyBtn.setText(_translate("Form", "History..")) self.exceptionBtn.setText(_translate("Form", "Exceptions..")) self.exceptionGroup.setTitle(_translate("Form", "Exception Handling")) self.clearExceptionBtn.setText(_translate("Form", "Clear Stack")) self.catchAllExceptionsBtn.setText(_translate("Form", "Show All Exceptions")) self.catchNextExceptionBtn.setText(_translate("Form", "Show Next Exception")) self.onlyUncaughtCheck.setText(_translate("Form", "Only Uncaught Exceptions")) self.runSelectedFrameCheck.setText(_translate("Form", "Run commands in selected stack frame")) self.exceptionInfoLabel.setText(_translate("Form", "Stack Trace")) self.label.setText(_translate("Form", "Filter (regex):")) from .CmdInput import CmdInput pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/console/template_pyside2.py000066400000000000000000000145361421045507400251520ustar00rootroot00000000000000 # Form implementation generated from reading ui file 'template.ui' # # Created: Sun Sep 18 19:19:10 2016 # by: pyside2-uic running on PySide2 2.0.0~alpha0 # # WARNING! All changes made in this file will be lost! from PySide2 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(694, 497) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Vertical) self.splitter.setObjectName("splitter") self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") self.output = QtWidgets.QPlainTextEdit(self.layoutWidget) font = QtGui.QFont() font.setFamily("Monospace") self.output.setFont(font) self.output.setReadOnly(True) self.output.setObjectName("output") self.verticalLayout.addWidget(self.output) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.input = CmdInput(self.layoutWidget) self.input.setObjectName("input") self.horizontalLayout.addWidget(self.input) self.historyBtn = QtWidgets.QPushButton(self.layoutWidget) self.historyBtn.setCheckable(True) self.historyBtn.setObjectName("historyBtn") self.horizontalLayout.addWidget(self.historyBtn) self.exceptionBtn = QtWidgets.QPushButton(self.layoutWidget) self.exceptionBtn.setCheckable(True) self.exceptionBtn.setObjectName("exceptionBtn") self.horizontalLayout.addWidget(self.exceptionBtn) self.verticalLayout.addLayout(self.horizontalLayout) self.historyList = QtWidgets.QListWidget(self.splitter) font = QtGui.QFont() font.setFamily("Monospace") self.historyList.setFont(font) self.historyList.setObjectName("historyList") self.exceptionGroup = QtWidgets.QGroupBox(self.splitter) self.exceptionGroup.setObjectName("exceptionGroup") self.gridLayout_2 = QtWidgets.QGridLayout(self.exceptionGroup) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) self.gridLayout_2.setObjectName("gridLayout_2") self.clearExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) self.clearExceptionBtn.setEnabled(False) self.clearExceptionBtn.setObjectName("clearExceptionBtn") self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1) self.catchAllExceptionsBtn = QtWidgets.QPushButton(self.exceptionGroup) self.catchAllExceptionsBtn.setCheckable(True) self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn") self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) self.catchNextExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) self.catchNextExceptionBtn.setCheckable(True) self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn") self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) self.onlyUncaughtCheck = QtWidgets.QCheckBox(self.exceptionGroup) self.onlyUncaughtCheck.setChecked(True) self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck") self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1) self.exceptionStackList = QtWidgets.QListWidget(self.exceptionGroup) self.exceptionStackList.setAlternatingRowColors(True) self.exceptionStackList.setObjectName("exceptionStackList") self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7) self.runSelectedFrameCheck = QtWidgets.QCheckBox(self.exceptionGroup) self.runSelectedFrameCheck.setChecked(True) self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck") self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7) self.exceptionInfoLabel = QtWidgets.QLabel(self.exceptionGroup) self.exceptionInfoLabel.setObjectName("exceptionInfoLabel") self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.gridLayout_2.addItem(spacerItem, 0, 5, 1, 1) self.label = QtWidgets.QLabel(self.exceptionGroup) self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1) self.filterText = QtWidgets.QLineEdit(self.exceptionGroup) self.filterText.setObjectName("filterText") self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1) self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Console", None, -1)) self.historyBtn.setText(QtWidgets.QApplication.translate("Form", "History..", None, -1)) self.exceptionBtn.setText(QtWidgets.QApplication.translate("Form", "Exceptions..", None, -1)) self.exceptionGroup.setTitle(QtWidgets.QApplication.translate("Form", "Exception Handling", None, -1)) self.clearExceptionBtn.setText(QtWidgets.QApplication.translate("Form", "Clear Exception", None, -1)) self.catchAllExceptionsBtn.setText(QtWidgets.QApplication.translate("Form", "Show All Exceptions", None, -1)) self.catchNextExceptionBtn.setText(QtWidgets.QApplication.translate("Form", "Show Next Exception", None, -1)) self.onlyUncaughtCheck.setText(QtWidgets.QApplication.translate("Form", "Only Uncaught Exceptions", None, -1)) self.runSelectedFrameCheck.setText(QtWidgets.QApplication.translate("Form", "Run commands in selected stack frame", None, -1)) self.exceptionInfoLabel.setText(QtWidgets.QApplication.translate("Form", "Exception Info", None, -1)) self.label.setText(QtWidgets.QApplication.translate("Form", "Filter (regex):", None, -1)) from .CmdInput import CmdInput pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/console/template_pyside6.py000066400000000000000000000150211421045507400251440ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'template.ui' ## ## Created by: Qt User Interface Compiler version 6.1.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * from .CmdInput import CmdInput class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(739, 497) self.gridLayout = QGridLayout(Form) self.gridLayout.setSpacing(0) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName(u"gridLayout") self.splitter = QSplitter(Form) self.splitter.setObjectName(u"splitter") self.splitter.setOrientation(Qt.Vertical) self.layoutWidget = QWidget(self.splitter) self.layoutWidget.setObjectName(u"layoutWidget") self.verticalLayout = QVBoxLayout(self.layoutWidget) self.verticalLayout.setObjectName(u"verticalLayout") self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.output = QPlainTextEdit(self.layoutWidget) self.output.setObjectName(u"output") font = QFont() font.setFamilies([u"Monospace"]) self.output.setFont(font) self.output.setReadOnly(True) self.verticalLayout.addWidget(self.output) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName(u"horizontalLayout") self.input = CmdInput(self.layoutWidget) self.input.setObjectName(u"input") self.horizontalLayout.addWidget(self.input) self.historyBtn = QPushButton(self.layoutWidget) self.historyBtn.setObjectName(u"historyBtn") self.historyBtn.setCheckable(True) self.horizontalLayout.addWidget(self.historyBtn) self.exceptionBtn = QPushButton(self.layoutWidget) self.exceptionBtn.setObjectName(u"exceptionBtn") self.exceptionBtn.setCheckable(True) self.horizontalLayout.addWidget(self.exceptionBtn) self.verticalLayout.addLayout(self.horizontalLayout) self.splitter.addWidget(self.layoutWidget) self.historyList = QListWidget(self.splitter) self.historyList.setObjectName(u"historyList") self.historyList.setFont(font) self.splitter.addWidget(self.historyList) self.exceptionGroup = QGroupBox(self.splitter) self.exceptionGroup.setObjectName(u"exceptionGroup") self.gridLayout_2 = QGridLayout(self.exceptionGroup) self.gridLayout_2.setObjectName(u"gridLayout_2") self.gridLayout_2.setHorizontalSpacing(2) self.gridLayout_2.setVerticalSpacing(0) self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) self.clearExceptionBtn = QPushButton(self.exceptionGroup) self.clearExceptionBtn.setObjectName(u"clearExceptionBtn") self.clearExceptionBtn.setEnabled(False) self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1) self.catchAllExceptionsBtn = QPushButton(self.exceptionGroup) self.catchAllExceptionsBtn.setObjectName(u"catchAllExceptionsBtn") self.catchAllExceptionsBtn.setCheckable(True) self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) self.catchNextExceptionBtn = QPushButton(self.exceptionGroup) self.catchNextExceptionBtn.setObjectName(u"catchNextExceptionBtn") self.catchNextExceptionBtn.setCheckable(True) self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) self.onlyUncaughtCheck = QCheckBox(self.exceptionGroup) self.onlyUncaughtCheck.setObjectName(u"onlyUncaughtCheck") self.onlyUncaughtCheck.setChecked(True) self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1) self.exceptionStackList = QListWidget(self.exceptionGroup) self.exceptionStackList.setObjectName(u"exceptionStackList") self.exceptionStackList.setAlternatingRowColors(True) self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7) self.runSelectedFrameCheck = QCheckBox(self.exceptionGroup) self.runSelectedFrameCheck.setObjectName(u"runSelectedFrameCheck") self.runSelectedFrameCheck.setChecked(True) self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7) self.exceptionInfoLabel = QLabel(self.exceptionGroup) self.exceptionInfoLabel.setObjectName(u"exceptionInfoLabel") self.exceptionInfoLabel.setWordWrap(True) self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7) self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.gridLayout_2.addItem(self.horizontalSpacer, 0, 5, 1, 1) self.label = QLabel(self.exceptionGroup) self.label.setObjectName(u"label") self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1) self.filterText = QLineEdit(self.exceptionGroup) self.filterText.setObjectName(u"filterText") self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1) self.splitter.addWidget(self.exceptionGroup) self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"Console", None)) self.historyBtn.setText(QCoreApplication.translate("Form", u"History..", None)) self.exceptionBtn.setText(QCoreApplication.translate("Form", u"Exceptions..", None)) self.exceptionGroup.setTitle(QCoreApplication.translate("Form", u"Exception Handling", None)) self.clearExceptionBtn.setText(QCoreApplication.translate("Form", u"Clear Stack", None)) self.catchAllExceptionsBtn.setText(QCoreApplication.translate("Form", u"Show All Exceptions", None)) self.catchNextExceptionBtn.setText(QCoreApplication.translate("Form", u"Show Next Exception", None)) self.onlyUncaughtCheck.setText(QCoreApplication.translate("Form", u"Only Uncaught Exceptions", None)) self.runSelectedFrameCheck.setText(QCoreApplication.translate("Form", u"Run commands in selected stack frame", None)) self.exceptionInfoLabel.setText(QCoreApplication.translate("Form", u"Stack Trace", None)) self.label.setText(QCoreApplication.translate("Form", u"Filter (regex):", None)) # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/debug.py000066400000000000000000001226111421045507400213160ustar00rootroot00000000000000""" debug.py - Functions to aid in debugging Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ from __future__ import print_function import contextlib import cProfile import gc import inspect import os import re import sys import threading import time import traceback import types import warnings import weakref from time import perf_counter from numpy import ndarray from .Qt import QT_LIB, QtCore from .util import cprint if sys.version.startswith("3.8") and QT_LIB == "PySide2": from .Qt import PySide2 if tuple(map(int, PySide2.__version__.split("."))) < (5, 14): warnings.warn("Due to PYSIDE-1140, ThreadChase and ThreadColor won't work") 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. Automatically indents nested calls. """ def w(*args, **kargs): global __ftraceDepth pfx = " " * __ftraceDepth print(pfx + func.__name__ + " start") __ftraceDepth += 1 try: rv = func(*args, **kargs) finally: __ftraceDepth -= 1 print(pfx + func.__name__ + " done") return rv return w class Tracer(object): """ Prints every function enter/exit. Useful for debugging crashes / lockups. """ def __init__(self): self.count = 0 self.stack = [] def trace(self, frame, event, arg): self.count += 1 # If it has been a long time since we saw the top of the stack, # print a reminder if self.count % 1000 == 0: print("----- current stack: -----") for line in self.stack: print(line) if event == 'call': line = " " * len(self.stack) + ">> " + self.frameInfo(frame) print(line) self.stack.append(line) elif event == 'return': self.stack.pop() line = " " * len(self.stack) + "<< " + self.frameInfo(frame) print(line) if len(self.stack) == 0: self.count = 0 return self.trace def stop(self): sys.settrace(None) def start(self): sys.settrace(self.trace) def frameInfo(self, fr): filename = fr.f_code.co_filename funcname = fr.f_code.co_name lineno = fr.f_lineno callfr = sys._getframe(3) callline = "%s %d" % (callfr.f_code.co_name, callfr.f_lineno) args, _, _, value_dict = inspect.getargvalues(fr) if len(args) and args[0] == 'self': instance = value_dict.get('self', None) if instance is not None: cls = getattr(instance, '__class__', None) if cls is not None: funcname = cls.__name__ + "." + funcname return "%s: %s %s: %s" % (callline, filename, lineno, funcname) def warnOnException(func): """Decorator that catches/ignores exceptions and prints a stack trace.""" def w(*args, **kwds): try: func(*args, **kwds) except: printExc('Ignored exception:') return w def getExc(indent=4, prefix='| ', skip=1): lines = formatException(*sys.exc_info(), skip=skip) lines2 = [] for l in lines: lines2.extend(l.strip('\n').split('\n')) lines3 = [" "*indent + prefix + l for l in lines2] return '\n'.join(lines3) def printExc(msg='', indent=4, prefix='|'): """Print an error message followed by an indented exception backtrace (This function is intended to be called within except: blocks)""" exc = getExc(indent=0, prefix="", skip=2) # print(" "*indent + prefix + '='*30 + '>>') warnings.warn("\n".join([msg, exc]), RuntimeWarning, stacklevel=2) # print(" "*indent + prefix + '='*30 + '<<') def printTrace(msg='', indent=4, prefix='|'): """Print an error message followed by an indented stack trace""" trace = backtrace(1) #exc = getExc(indent, prefix + ' ') print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) print(" "*indent + prefix + '='*30 + '>>') for line in trace.split('\n'): print(" "*indent + prefix + " " + line) print(" "*indent + prefix + '='*30 + '<<') def backtrace(skip=0): return ''.join(traceback.format_stack()[:-(skip+1)]) def formatException(exctype, value, tb, skip=0): """Return a list of formatted exception strings. Similar to traceback.format_exception, but displays the entire stack trace rather than just the portion downstream of the point where the exception is caught. In particular, unhandled exceptions that occur during Qt signal handling do not usually show the portion of the stack that emitted the signal. """ lines = traceback.format_exception(exctype, value, tb) lines = [lines[0]] + traceback.format_stack()[:-(skip+1)] + [' --- exception caught here ---\n'] + lines[1:] return lines def printException(exctype, value, traceback): """Print an exception with its full traceback. Set `sys.excepthook = printException` to ensure that exceptions caught inside Qt signal handlers are printed with their full stack trace. """ print(''.join(formatException(exctype, value, traceback, skip=1))) def listObjs(regex='Q', typ=None): """List all objects managed by python gc with class name matching regex. Finds 'Q...' classes by default.""" if typ is not None: return [x for x in gc.get_objects() if isinstance(x, typ)] else: return [x for x in gc.get_objects() if re.match(regex, type(x).__name__)] 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 prefix = " "*(8-maxLen) #print prefix + str(map(type, path)) prefix += " " if restart: #gc.collect() seen.clear() gc.collect() newRefs = [r for r in gc.get_referrers(endObj) if id(r) not in ignore] ignore[id(newRefs)] = None #fo = allFrameObjs() #newRefs = [] #for r in gc.get_referrers(endObj): #try: #if r not in fo: #newRefs.append(r) #except: #newRefs.append(r) for r in newRefs: #print prefix+"->"+str(type(r)) if type(r).__name__ in ['frame', 'function', 'listiterator']: #print prefix+" FRAME" continue try: if any(r is x for x in path): #print prefix+" LOOP", objChainString([r]+path) continue except: print(r) print(path) raise if r is startObj: refs.append([r]) print(refPathString([startObj]+path)) continue if maxLen == 0: #print prefix+" END:", objChainString([r]+path) continue ## See if we have already searched this node. ## If not, recurse. tree = None try: cache = seen[id(r)] if cache[0] >= maxLen: tree = cache[1] for p in tree: print(refPathString(p+path)) except KeyError: pass ignore[id(tree)] = None if tree is None: tree = findRefPath(startObj, r, maxLen-1, restart=False, path=[r]+path, ignore=ignore) seen[id(r)] = [maxLen, tree] ## integrate any returned results if len(tree) == 0: #print prefix+" EMPTY TREE" continue else: for p in tree: refs.append(p+[r]) #seen[id(r)] = [maxLen, refs] return refs def objString(obj): """Return a short but descriptive string for any object""" try: if type(obj) in [int, float]: return str(obj) elif isinstance(obj, dict): if len(obj) > 5: return "" % (",".join(list(obj.keys())[:5])) else: return "" % (",".join(list(obj.keys()))) elif isinstance(obj, str): if len(obj) > 50: return '"%s..."' % obj[:50] else: return obj[:] elif isinstance(obj, ndarray): return "" % (str(obj.dtype), str(obj.shape)) elif hasattr(obj, '__len__'): if len(obj) > 5: return "<%s [%s,...]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj[:5]])) else: return "<%s [%s]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj])) else: return "<%s %s>" % (type(obj).__name__, obj.__class__.__name__) except: return str(type(obj)) def refPathString(chain): """Given a list of adjacent objects in a reference path, print the 'natural' path names (ie, attribute names, keys, and indexes) that follow from one object to the next .""" s = objString(chain[0]) i = 0 while i < len(chain)-1: #print " -> ", i i += 1 o1 = chain[i-1] o2 = chain[i] cont = False if isinstance(o1, list) or isinstance(o1, tuple): if any(o2 is x for x in o1): s += "[%d]" % o1.index(o2) continue #print " not list" if isinstance(o2, dict) and hasattr(o1, '__dict__') and o2 == o1.__dict__: i += 1 if i >= len(chain): s += ".__dict__" continue o3 = chain[i] for k in o2: if o2[k] is o3: s += '.%s' % k cont = True continue #print " not __dict__" if isinstance(o1, dict): try: if o2 in o1: s += "[key:%s]" % objString(o2) continue except TypeError: pass for k in o1: if o1[k] is o2: s += "[%s]" % objString(k) cont = True continue #print " not dict" #for k in dir(o1): ## Not safe to request attributes like this. #if getattr(o1, k) is o2: #s += ".%s" % k #cont = True #continue #print " not attr" if cont: continue s += " ? " sys.stdout.flush() return s def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False): """Guess how much memory an object is using""" ignoreTypes = ['MethodType', 'UnboundMethodType', 'BuiltinMethodType', 'FunctionType', 'BuiltinFunctionType'] ignoreTypes = [getattr(types, key) for key in ignoreTypes if hasattr(types, key)] ignoreRegex = re.compile('(method-wrapper|Flag|ItemChange|Option|Mode)') if ignore is None: ignore = {} indent = ' '*depth try: hash(obj) hsh = obj except: hsh = "%s:%d" % (str(type(obj)), id(obj)) if hsh in ignore: return 0 ignore[hsh] = 1 try: size = sys.getsizeof(obj) except TypeError: size = 0 if isinstance(obj, ndarray): try: size += len(obj.data) except: pass if recursive: if type(obj) in [list, tuple]: if verbose: print(indent+"list:") for o in obj: s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) if verbose: print(indent+' +', s) size += s elif isinstance(obj, dict): if verbose: print(indent+"list:") for k in obj: s = objectSize(obj[k], ignore=ignore, verbose=verbose, depth=depth+1) if verbose: print(indent+' +', k, s) size += s #elif isinstance(obj, QtCore.QObject): #try: #childs = obj.children() #if verbose: #print indent+"Qt children:" #for ch in childs: #s = objectSize(obj, ignore=ignore, verbose=verbose, depth=depth+1) #size += s #if verbose: #print indent + ' +', ch.objectName(), s #except: #pass #if isinstance(obj, types.InstanceType): gc.collect() if verbose: print(indent+'attrs:') for k in dir(obj): if k in ['__dict__']: continue o = getattr(obj, k) if type(o) in ignoreTypes: continue strtyp = str(type(o)) if ignoreRegex.search(strtyp): continue #if isinstance(o, types.ObjectType) and strtyp == "": #continue #if verbose: #print indent, k, '?' refs = [r for r in gc.get_referrers(o) if type(r) != types.FrameType] if len(refs) == 1: s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) size += s if verbose: print(indent + " +", k, s) #else: #if verbose: #print indent + ' -', k, len(refs) return size class GarbageWatcher(object): """ Convenient dictionary for holding weak references to objects. Mainly used to check whether the objects have been collect yet or not. Example: gw = GarbageWatcher() gw['objName'] = obj gw['objName2'] = obj2 gw.check() """ def __init__(self): self.objs = weakref.WeakValueDictionary() self.allNames = [] def add(self, obj, name): self.objs[name] = obj self.allNames.append(name) def __setitem__(self, name, obj): self.add(obj, name) def check(self): """Print a list of all watched objects and whether they have been collected.""" gc.collect() dead = self.allNames[:] alive = [] for k in self.objs: dead.remove(k) alive.append(k) print("Deleted objects:", dead) print("Live objects:", alive) def __getitem__(self, item): return self.objs[item] class Profiler(object): """Simple profiler allowing measurement of multiple time intervals. By default, profilers are disabled. To enable profiling, set the environment variable `PYQTGRAPHPROFILE` to a comma-separated list of fully-qualified names of profiled functions. Calling a profiler registers a message (defaulting to an increasing counter) that contains the time elapsed since the last call. When the profiler is about to be garbage-collected, the messages are passed to the outer profiler if one is running, or printed to stdout otherwise. If `delayed` is set to False, messages are immediately printed instead. Example: def function(...): profiler = Profiler() ... do stuff ... profiler('did stuff') ... do other stuff ... profiler('did other stuff') # profiler is garbage-collected and flushed at function end If this function is a method of class C, setting `PYQTGRAPHPROFILE` to "C.function" (without the module name) will enable this profiler. For regular functions, use the qualified name of the function, stripping only the initial "pyqtgraph." prefix from the module. """ _profilers = os.environ.get("PYQTGRAPHPROFILE", None) _profilers = _profilers.split(",") if _profilers is not None else [] _depth = 0 _msgs = [] disable = False # set this flag to disable all or individual profilers at runtime class DisabledProfiler(object): def __init__(self, *args, **kwds): pass def __call__(self, *args): pass def finish(self): pass def mark(self, msg=None): pass _disabledProfiler = DisabledProfiler() def __new__(cls, msg=None, disabled='env', delayed=True): """Optionally create a new profiler based on caller's qualname. """ if disabled is True or (disabled == 'env' and len(cls._profilers) == 0): return cls._disabledProfiler # determine the qualified name of the caller function caller_frame = sys._getframe(1) try: caller_object_type = type(caller_frame.f_locals["self"]) except KeyError: # we are in a regular function qualifier = caller_frame.f_globals["__name__"].split(".", 1)[-1] else: # we are in a method qualifier = caller_object_type.__name__ func_qualname = qualifier + "." + caller_frame.f_code.co_name if disabled == 'env' and func_qualname not in cls._profilers: # don't do anything return cls._disabledProfiler # create an actual profiling object cls._depth += 1 obj = super(Profiler, cls).__new__(cls) obj._name = msg or func_qualname obj._delayed = delayed obj._markCount = 0 obj._finished = False obj._firstTime = obj._lastTime = perf_counter() obj._newMsg("> Entering " + obj._name) return obj def __call__(self, msg=None): """Register or print a new message with timing information. """ if self.disable: return if msg is None: msg = str(self._markCount) self._markCount += 1 newTime = perf_counter() self._newMsg(" %s: %0.4f ms", msg, (newTime - self._lastTime) * 1000) self._lastTime = newTime def mark(self, msg=None): self(msg) def _newMsg(self, msg, *args): msg = " " * (self._depth - 1) + msg if self._delayed: self._msgs.append((msg, args)) else: self.flush() print(msg % args) def __del__(self): self.finish() def finish(self, msg=None): """Add a final message; flush the message list if no parent profiler. """ if self._finished or self.disable: return self._finished = True if msg is not None: self(msg) self._newMsg("< Exiting %s, total time: %0.4f ms", self._name, (perf_counter() - self._firstTime) * 1000) type(self)._depth -= 1 if self._depth < 1: self.flush() def flush(self): if self._msgs: print("\n".join([m[0]%m[1] for m in self._msgs])) type(self)._msgs = [] 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) stats.print_stats(num) return stats #### Code for listing (nearly) all objects in the known universe #### http://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects # Recursively expand slist's objects # into olist, using seen to track # already processed objects. def _getr(slist, olist, first=True): i = 0 for e in slist: oid = id(e) typ = type(e) if oid in olist or typ is int: ## or e in olist: ## since we're excluding all ints, there is no longer a need to check for olist keys continue olist[oid] = e if first and (i%1000) == 0: gc.collect() tl = gc.get_referents(e) if tl: _getr(tl, olist, first=False) i += 1 # The public function. def get_all_objects(): """Return a list of all live Python objects (excluding int and long), not including the list itself.""" gc.collect() gcl = gc.get_objects() olist = {} _getr(gcl, olist) del olist[id(olist)] del olist[id(gcl)] del olist[id(sys._getframe())] return olist def lookup(oid, objects=None): """Return an object given its ID, if it exists.""" if objects is None: objects = get_all_objects() return objects[oid] class ObjTracker(object): """ Tracks all objects under the sun, reporting the changes between snapshots: what objects are created, deleted, and persistent. This class is very useful for tracking memory leaks. The class goes to great (but not heroic) lengths to avoid tracking its own internal objects. Example: ot = ObjTracker() # takes snapshot of currently existing objects ... do stuff ... ot.diff() # prints lists of objects created and deleted since ot was initialized ... do stuff ... ot.diff() # prints lists of objects created and deleted since last call to ot.diff() # also prints list of items that were created since initialization AND have not been deleted yet # (if done correctly, this list can tell you about objects that were leaked) arrays = ot.findPersistent('ndarray') ## returns all objects matching 'ndarray' (string match, not instance checking) ## that were considered persistent when the last diff() was run describeObj(arrays[0]) ## See if we can determine who has references to this array """ allObjs = {} ## keep track of all objects created and stored within class instances allObjs[id(allObjs)] = None def __init__(self): self.startRefs = {} ## list of objects that exist when the tracker is initialized {oid: weakref} ## (If it is not possible to weakref the object, then the value is None) self.startCount = {} self.newRefs = {} ## list of objects that have been created since initialization self.persistentRefs = {} ## list of objects considered 'persistent' when the last diff() was called self.objTypes = {} ObjTracker.allObjs[id(self)] = None self.objs = [self.__dict__, self.startRefs, self.startCount, self.newRefs, self.persistentRefs, self.objTypes] self.objs.append(self.objs) for v in self.objs: ObjTracker.allObjs[id(v)] = None self.start() def findNew(self, regex): """Return all objects matching regex that were considered 'new' when the last diff() was run.""" return self.findTypes(self.newRefs, regex) def findPersistent(self, regex): """Return all objects matching regex that were considered 'persistent' when the last diff() was run.""" return self.findTypes(self.persistentRefs, regex) def start(self): """ Remember the current set of objects as the comparison for all future calls to diff() Called automatically on init, but can be called manually as well. """ refs, count, objs = self.collect() for r in self.startRefs: self.forgetRef(self.startRefs[r]) self.startRefs.clear() self.startRefs.update(refs) for r in refs: self.rememberRef(r) self.startCount.clear() self.startCount.update(count) #self.newRefs.clear() #self.newRefs.update(refs) def diff(self, **kargs): """ Compute all differences between the current object set and the reference set. Print a set of reports for created, deleted, and persistent objects """ refs, count, objs = self.collect() ## refs contains the list of ALL objects ## Which refs have disappeared since call to start() (these are only displayed once, then forgotten.) delRefs = {} for i in list(self.startRefs.keys()): if i not in refs: delRefs[i] = self.startRefs[i] del self.startRefs[i] self.forgetRef(delRefs[i]) for i in list(self.newRefs.keys()): if i not in refs: delRefs[i] = self.newRefs[i] del self.newRefs[i] self.forgetRef(delRefs[i]) #print "deleted:", len(delRefs) ## Which refs have appeared since call to start() or diff() persistentRefs = {} ## created since start(), but before last diff() createRefs = {} ## created since last diff() for o in refs: if o not in self.startRefs: if o not in self.newRefs: createRefs[o] = refs[o] ## object has been created since last diff() else: persistentRefs[o] = refs[o] ## object has been created since start(), but before last diff() (persistent) #print "new:", len(newRefs) ## self.newRefs holds the entire set of objects created since start() for r in self.newRefs: self.forgetRef(self.newRefs[r]) self.newRefs.clear() self.newRefs.update(persistentRefs) self.newRefs.update(createRefs) for r in self.newRefs: self.rememberRef(self.newRefs[r]) #print "created:", len(createRefs) ## self.persistentRefs holds all objects considered persistent. self.persistentRefs.clear() self.persistentRefs.update(persistentRefs) print("----------- Count changes since start: ----------") c1 = count.copy() for k in self.startCount: c1[k] = c1.get(k, 0) - self.startCount[k] typs = list(c1.keys()) typs.sort(key=lambda a: c1[a]) for t in typs: if c1[t] == 0: continue num = "%d" % c1[t] print(" " + num + " "*(10-len(num)) + str(t)) print("----------- %d Deleted since last diff: ------------" % len(delRefs)) self.report(delRefs, objs, **kargs) print("----------- %d Created since last diff: ------------" % len(createRefs)) self.report(createRefs, objs, **kargs) print("----------- %d Created since start (persistent): ------------" % len(persistentRefs)) self.report(persistentRefs, objs, **kargs) def __del__(self): self.startRefs.clear() self.startCount.clear() self.newRefs.clear() self.persistentRefs.clear() del ObjTracker.allObjs[id(self)] for v in self.objs: del ObjTracker.allObjs[id(v)] @classmethod def isObjVar(cls, o): return type(o) is cls or id(o) in cls.allObjs def collect(self): print("Collecting list of all objects...") gc.collect() objs = get_all_objects() frame = sys._getframe() del objs[id(frame)] ## ignore the current frame del objs[id(frame.f_code)] ignoreTypes = [int] refs = {} count = {} for k in objs: o = objs[k] typ = type(o) oid = id(o) if ObjTracker.isObjVar(o) or typ in ignoreTypes: continue try: ref = weakref.ref(o) except: ref = None refs[oid] = ref typ = type(o) typStr = typeStr(o) self.objTypes[oid] = typStr ObjTracker.allObjs[id(typStr)] = None count[typ] = count.get(typ, 0) + 1 print("All objects: %d Tracked objects: %d" % (len(objs), len(refs))) return refs, count, objs def forgetRef(self, ref): if ref is not None: del ObjTracker.allObjs[id(ref)] def rememberRef(self, ref): ## Record the address of the weakref object so it is not included in future object counts. if ref is not None: ObjTracker.allObjs[id(ref)] = None def lookup(self, oid, ref, objs=None): if ref is None or ref() is None: try: obj = lookup(oid, objects=objs) except: obj = None else: obj = ref() return obj def report(self, refs, allobjs=None, showIDs=False): if allobjs is None: allobjs = get_all_objects() count = {} rev = {} for oid in refs: obj = self.lookup(oid, refs[oid], allobjs) if obj is None: typ = "[del] " + self.objTypes[oid] else: typ = typeStr(obj) if typ not in rev: rev[typ] = [] rev[typ].append(oid) c = count.get(typ, [0,0]) count[typ] = [c[0]+1, c[1]+objectSize(obj)] typs = list(count.keys()) typs.sort(key=lambda a: count[a][1]) for t in typs: line = " %d\t%d\t%s" % (count[t][0], count[t][1], t) if showIDs: line += "\t"+",".join(map(str,rev[t])) print(line) def findTypes(self, refs, regex): allObjs = get_all_objects() ids = {} objs = [] r = re.compile(regex) for k in refs: if r.search(self.objTypes[k]): objs.append(self.lookup(k, refs[k], allObjs)) return objs def describeObj(obj, depth=4, path=None, ignore=None): """ Trace all reference paths backward, printing a list of different ways this object can be accessed. Attempts to answer the question "who has a reference to this object" """ if path is None: path = [obj] if ignore is None: ignore = {} ## holds IDs of objects used within the function. ignore[id(sys._getframe())] = None ignore[id(path)] = None gc.collect() refs = gc.get_referrers(obj) ignore[id(refs)] = None printed=False for ref in refs: if id(ref) in ignore: continue if id(ref) in list(map(id, path)): print("Cyclic reference: " + refPathString([ref]+path)) printed = True continue newPath = [ref]+path if len(newPath) >= depth: refStr = refPathString(newPath) if '[_]' not in refStr: ## ignore '_' references generated by the interactive shell print(refStr) printed = True else: describeObj(ref, depth, newPath, ignore) printed = True if not printed: print("Dead end: " + refPathString(path)) def typeStr(obj): """Create a more useful type string by making types report their class.""" typ = type(obj) if typ == getattr(types, 'InstanceType', None): return "" % obj.__class__.__name__ else: return str(typ) def searchRefs(obj, *args): """Pseudo-interactive function for tracing references backward. **Arguments:** obj: The initial object from which to start searching args: A set of string or int arguments. each integer selects one of obj's referrers to be the new 'obj' each string indicates an action to take on the current 'obj': t: print the types of obj's referrers l: print the lengths of obj's referrers (if they have __len__) i: print the IDs of obj's referrers o: print obj ro: return obj rr: return list of obj's referrers Examples:: searchRefs(obj, 't') ## Print types of all objects referring to obj searchRefs(obj, 't', 0, 't') ## ..then select the first referrer and print the types of its referrers searchRefs(obj, 't', 0, 't', 'l') ## ..also print lengths of the last set of referrers searchRefs(obj, 0, 1, 'ro') ## Select index 0 from obj's referrer, then select index 1 from the next set of referrers, then return that object """ ignore = {id(sys._getframe()): None} gc.collect() refs = gc.get_referrers(obj) ignore[id(refs)] = None refs = [r for r in refs if id(r) not in ignore] for a in args: #fo = allFrameObjs() #refs = [r for r in refs if r not in fo] if type(a) is int: obj = refs[a] gc.collect() refs = gc.get_referrers(obj) ignore[id(refs)] = None refs = [r for r in refs if id(r) not in ignore] elif a == 't': print(list(map(typeStr, refs))) elif a == 'i': print(list(map(id, refs))) elif a == 'l': def slen(o): if hasattr(o, '__len__'): return len(o) else: return None print(list(map(slen, refs))) elif a == 'o': print(obj) elif a == 'ro': return obj elif a == 'rr': return refs def allFrameObjs(): """Return list of frame objects in current stack. Useful if you want to ignore these objects in refernece searches""" f = sys._getframe() objs = [] while f is not None: objs.append(f) objs.append(f.f_code) #objs.append(f.f_locals) #objs.append(f.f_globals) #objs.append(f.f_builtins) f = f.f_back return objs def findObj(regex): """Return a list of objects whose typeStr matches regex""" allObjs = get_all_objects() objs = [] r = re.compile(regex) for i in allObjs: obj = allObjs[i] if r.search(typeStr(obj)): objs.append(obj) return objs def listRedundantModules(): """List modules that have been imported more than once via different paths.""" mods = {} for name, mod in sys.modules.items(): if not hasattr(mod, '__file__'): continue mfile = os.path.abspath(mod.__file__) if mfile[-1] == 'c': mfile = mfile[:-1] if mfile in mods: print("module at %s has 2 names: %s, %s" % (mfile, name, mods[mfile])) else: mods[mfile] = name def walkQObjectTree(obj, counts=None, verbose=False, depth=0): """ Walk through a tree of QObjects, doing nothing to them. The purpose of this function is to find dead objects and generate a crash immediately rather than stumbling upon them later. Prints a count of the objects encountered, for fun. (or is it?) """ if verbose: print(" "*depth + typeStr(obj)) report = False if counts is None: counts = {} report = True typ = str(type(obj)) try: counts[typ] += 1 except KeyError: counts[typ] = 1 for child in obj.children(): walkQObjectTree(child, counts, verbose, depth+1) return counts QObjCache = {} def qObjectReport(verbose=False): """Generate a report counting all QObjects and their types""" global qObjCache count = {} for obj in findObj('PyQt'): if isinstance(obj, QtCore.QObject): oid = id(obj) if oid not in QObjCache: QObjCache[oid] = typeStr(obj) + " " + obj.objectName() try: QObjCache[oid] += " " + obj.parent().objectName() QObjCache[oid] += " " + obj.text() except: pass print("check obj", oid, str(QObjCache[oid])) if obj.parent() is None: walkQObjectTree(obj, count, verbose) typs = list(count.keys()) typs.sort() for t in typs: print(count[t], "\t", t) class PrintDetector(object): """Find code locations that print to stdout.""" def __init__(self): self.stdout = sys.stdout sys.stdout = self def remove(self): sys.stdout = self.stdout def __del__(self): self.remove() def write(self, x): self.stdout.write(x) traceback.print_stack() def flush(self): self.stdout.flush() def listQThreads(): """Prints Thread IDs (Qt's, not OS's) for all QThreads.""" thr = findObj('[Tt]hread') thr = [t for t in thr if isinstance(t, QtCore.QThread)] try: from PyQt5 import sip except ImportError: import sip for t in thr: print("--> ", t) print(" Qt ID: 0x%x" % sip.unwrapinstance(t)) def pretty(data, indent=''): """Format nested dict/list/tuple structures into a more human-readable string This function is a bit better than pprint for displaying OrderedDicts. """ ret = "" ind2 = indent + " " if isinstance(data, dict): ret = indent+"{\n" for k, v in data.items(): ret += ind2 + repr(k) + ": " + pretty(v, ind2).strip() + "\n" ret += indent+"}\n" elif isinstance(data, list) or isinstance(data, tuple): s = repr(data) if len(s) < 40: ret += indent + s else: if isinstance(data, list): d = '[]' else: d = '()' ret = indent+d[0]+"\n" for i, v in enumerate(data): ret += ind2 + str(i) + ": " + pretty(v, ind2).strip() + "\n" ret += indent+d[1]+"\n" else: ret += indent + repr(data) return ret 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, logFile=None): self.interval = interval self.lock = Mutex() self._stop = False self.logFile = logFile self.start() def stop(self): with self.lock: self._stop = True def start(self, interval=None): if interval is not None: self.interval = interval self._stop = False self.thread = threading.Thread(target=self.run) self.thread.daemon = True self.thread.start() def run(self): iter = 0 with open_maybe_console(self.logFile) as printFile: while True: with self.lock: if self._stop is True: return 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: name = threading._active.get(id, None) except: name = None 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 = "???" 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): """ Wrapper on stdout/stderr that colors text by the current thread ID. *stream* must be 'stdout' or 'stderr'. """ colors = {} lock = Mutex() def __init__(self, stream): self.stream = getattr(sys, stream) self.err = stream == 'stderr' setattr(sys, stream, self) def write(self, msg): with self.lock: cprint.cprint(self.stream, self.color(), msg, -1, stderr=self.err) def flush(self): with self.lock: self.stream.flush() def color(self): tid = threading.current_thread() if tid not in self.colors: c = (len(self.colors) % 15) + 1 self.colors[tid] = c return self.colors[tid] def enableFaulthandler(): """ Enable faulthandler for all threads. If the faulthandler package is available, this function disables and then re-enables fault handling for all threads (this is necessary to ensure any new threads are handled correctly), and returns True. If faulthandler is not available, then returns False. """ try: import faulthandler # necessary to disable first or else new threads may not be handled. faulthandler.disable() faulthandler.enable(all_threads=True) return True except ImportError: return False pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/dockarea/000077500000000000000000000000001421045507400214245ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/dockarea/Container.py000066400000000000000000000223371421045507400237270ustar00rootroot00000000000000__all__ = ["Container", "HContainer", "VContainer", "TContainer"] import weakref from ..Qt import QtCore, QtWidgets from .Dock import Dock class Container(object): #sigStretchChanged = QtCore.Signal() ## can't do this here; not a QObject. def __init__(self, area): object.__init__(self) self.area = area self._container = None self._stretch = (10, 10) self.stretches = weakref.WeakKeyDictionary() def container(self): return self._container def containerChanged(self, c): self._container = c if c is None: self.area = None else: self.area = c.area def type(self): return None def insert(self, new, pos=None, neighbor=None): if not isinstance(new, list): new = [new] for n in new: # remove from existing parent first n.setParent(None) if neighbor is None: if pos == 'before': index = 0 else: index = self.count() else: index = self.indexOf(neighbor) if index == -1: index = 0 if pos == 'after': index += 1 for n in new: #print "insert", n, " -> ", self, index self._insertItem(n, index) #print "change container", n, " -> ", self n.containerChanged(self) index += 1 n.sigStretchChanged.connect(self.childStretchChanged) #print "child added", self self.updateStretch() def apoptose(self, propagate=True): # if there is only one (or zero) item in this container, disappear. # if propagate is True, then also attempt to apoptose parent containers. cont = self._container c = self.count() if c > 1: return if c == 1: ## if there is one item, give it to the parent container (unless this is the top) ch = self.widget(0) if (self.area is not None and self is self.area.topContainer and not isinstance(ch, Container)) or self.container() is None: return self.container().insert(ch, 'before', self) #print "apoptose:", self self.close() if propagate and cont is not None: cont.apoptose() def close(self): self.setParent(None) if self.area is not None and self.area.topContainer is self: self.area.topContainer = None self.containerChanged(None) def childEvent_(self, ev): # NOTE: this method has been renamed to avoid having the same method name as # QSplitter.childEvent() # this causes problems for PyQt6 since SplitContainer inherits from # Container and QSplitter. ch = ev.child() if ev.removed() and hasattr(ch, 'sigStretchChanged'): #print "Child", ev.child(), "removed, updating", self try: ch.sigStretchChanged.disconnect(self.childStretchChanged) except: pass self.updateStretch() def childStretchChanged(self): #print "child", QtCore.QObject.sender(self), "changed shape, updating", self self.updateStretch() def setStretch(self, x=None, y=None): #print "setStretch", self, x, y self._stretch = (x, y) self.sigStretchChanged.emit() def updateStretch(self): ###Set the stretch values for this container to reflect its contents pass def stretch(self): """Return the stretch factors for this container""" return self._stretch class SplitContainer(Container, QtWidgets.QSplitter): """Horizontal or vertical splitter with some changes: - save/restore works correctly """ sigStretchChanged = QtCore.Signal() def __init__(self, area, orientation): QtWidgets.QSplitter.__init__(self) self.setOrientation(orientation) Container.__init__(self, area) #self.splitterMoved.connect(self.restretchChildren) def _insertItem(self, item, index): self.insertWidget(index, item) item.show() ## need to show since it may have been previously hidden by tab def saveState(self): sizes = self.sizes() if all(x == 0 for x in sizes): sizes = [10] * len(sizes) return {'sizes': sizes} def restoreState(self, state): sizes = state['sizes'] self.setSizes(sizes) for i in range(len(sizes)): self.setStretchFactor(i, sizes[i]) def childEvent(self, ev): super().childEvent(ev) # call QSplitter.childEvent() Container.childEvent_(self, ev) #def restretchChildren(self): #sizes = self.sizes() #tot = sum(sizes) class HContainer(SplitContainer): def __init__(self, area): SplitContainer.__init__(self, area, QtCore.Qt.Orientation.Horizontal) def type(self): return 'horizontal' def updateStretch(self): ##Set the stretch values for this container to reflect its contents #print "updateStretch", self x = 0 y = 0 sizes = [] for i in range(self.count()): wx, wy = self.widget(i).stretch() x += wx y = max(y, wy) sizes.append(wx) #print " child", self.widget(i), wx, wy self.setStretch(x, y) #print sizes tot = float(sum(sizes)) if tot == 0: scale = 1.0 else: scale = self.width() / tot self.setSizes([int(s*scale) for s in sizes]) class VContainer(SplitContainer): def __init__(self, area): SplitContainer.__init__(self, area, QtCore.Qt.Orientation.Vertical) def type(self): return 'vertical' def updateStretch(self): ##Set the stretch values for this container to reflect its contents #print "updateStretch", self x = 0 y = 0 sizes = [] for i in range(self.count()): wx, wy = self.widget(i).stretch() y += wy x = max(x, wx) sizes.append(wy) #print " child", self.widget(i), wx, wy self.setStretch(x, y) #print sizes tot = float(sum(sizes)) if tot == 0: scale = 1.0 else: scale = self.height() / tot self.setSizes([int(s*scale) for s in sizes]) class StackedWidget(QtWidgets.QStackedWidget): def __init__(self, *, container): super().__init__() self.container = container def childEvent(self, ev): super().childEvent(ev) self.container.childEvent_(ev) class TContainer(Container, QtWidgets.QWidget): sigStretchChanged = QtCore.Signal() def __init__(self, area): QtWidgets.QWidget.__init__(self) Container.__init__(self, area) self.layout = QtWidgets.QGridLayout() self.layout.setSpacing(0) self.layout.setContentsMargins(0,0,0,0) self.setLayout(self.layout) self.hTabLayout = QtWidgets.QHBoxLayout() self.hTabBox = QtWidgets.QWidget() self.hTabBox.setLayout(self.hTabLayout) self.hTabLayout.setSpacing(2) self.hTabLayout.setContentsMargins(0,0,0,0) self.layout.addWidget(self.hTabBox, 0, 1) self.stack = StackedWidget(container=self) self.layout.addWidget(self.stack, 1, 1) self.setLayout(self.layout) for n in ['count', 'widget', 'indexOf']: setattr(self, n, getattr(self.stack, n)) def _insertItem(self, item, index): 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) #QtCore.QObject.connect(item.label, QtCore.SIGNAL('clicked'), self.tabClicked) item.label.sigClicked.connect(self.tabClicked) self.tabClicked(item.label) def tabClicked(self, tab, ev=None): if ev is None or ev.button() == QtCore.Qt.MouseButton.LeftButton: for i in range(self.count()): w = self.widget(i) if w is tab.dock: w.label.setDim(False) self.stack.setCurrentIndex(i) else: w.label.setDim(True) def raiseDock(self, dock): """Move *dock* to the top of the stack""" self.stack.currentWidget().label.setDim(True) self.stack.setCurrentWidget(dock) dock.label.setDim(False) def type(self): return 'tab' def saveState(self): return {'index': self.stack.currentIndex()} def restoreState(self, state): self.stack.setCurrentIndex(state['index']) def updateStretch(self): ##Set the stretch values for this container to reflect its contents x = 0 y = 0 for i in range(self.count()): wx, wy = self.widget(i).stretch() x = max(x, wx) y = max(y, wy) self.setStretch(x, y) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/dockarea/Dock.py000066400000000000000000000301571421045507400226640ustar00rootroot00000000000000import warnings from ..Qt import QtCore, QtGui, QtWidgets from ..widgets.VerticalLabel import VerticalLabel from .DockDrop import DockDrop class Dock(QtWidgets.QWidget, DockDrop): sigStretchChanged = QtCore.Signal() sigClosed = QtCore.Signal(object) def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True, closable=False, fontSize="12px"): QtWidgets.QWidget.__init__(self) DockDrop.__init__(self) self._container = None self._name = name self.area = area self.label = DockLabel(name, self, closable, fontSize) if closable: self.label.sigCloseClicked.connect(self.close) self.labelHidden = False self.moveLabel = True ## If false, the dock is no longer allowed to move the label. self.autoOrient = autoOrientation self.orientation = 'horizontal' #self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter) self.topLayout = QtWidgets.QGridLayout() self.topLayout.setContentsMargins(0, 0, 0, 0) self.topLayout.setSpacing(0) self.setLayout(self.topLayout) self.topLayout.addWidget(self.label, 0, 1) self.widgetArea = QtWidgets.QWidget() self.topLayout.addWidget(self.widgetArea, 1, 1) self.layout = QtWidgets.QGridLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.widgetArea.setLayout(self.layout) self.widgetArea.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) self.widgets = [] self.currentRow = 0 #self.titlePos = 'top' self.raiseOverlay() self.hStyle = """ Dock > QWidget { border: 1px solid #000; border-radius: 5px; border-top-left-radius: 0px; border-top-right-radius: 0px; border-top-width: 0px; }""" self.vStyle = """ Dock > QWidget { border: 1px solid #000; border-radius: 5px; border-top-left-radius: 0px; border-bottom-left-radius: 0px; border-left-width: 0px; }""" self.nStyle = """ Dock > QWidget { border: 1px solid #000; border-radius: 5px; }""" self.dragStyle = """ Dock > QWidget { border: 4px solid #00F; border-radius: 5px; }""" self.setAutoFillBackground(False) self.widgetArea.setStyleSheet(self.hStyle) self.setStretch(*size) if widget is not None: self.addWidget(widget) if hideTitle: self.hideTitleBar() def implements(self, name=None): if name is None: return ['dock'] else: return name == 'dock' def setStretch(self, x=None, y=None): """ Set the 'target' size for this Dock. The actual size will be determined by comparing this Dock's stretch value to the rest of the docks it shares space with. """ if x is None: x = 0 if y is None: y = 0 self._stretch = (x, y) self.sigStretchChanged.emit() def stretch(self): return self._stretch def hideTitleBar(self): """ Hide the title bar for this Dock. This will prevent the Dock being moved by the user. """ self.label.hide() self.labelHidden = True if 'center' in self.allowedAreas: self.allowedAreas.remove('center') self.updateStyle() def showTitleBar(self): """ Show the title bar for this Dock. """ self.label.show() self.labelHidden = False self.allowedAreas.add('center') self.updateStyle() def title(self): """ Gets the text displayed in the title bar for this dock. """ return self.label.text() def setTitle(self, text): """ Sets the text displayed in title bar for this Dock. """ self.label.setText(text) def setOrientation(self, o='auto', force=False): """ Sets the orientation of the title bar for this Dock. Must be one of 'auto', 'horizontal', or 'vertical'. By default ('auto'), the orientation is determined based on the aspect ratio of the Dock. """ # setOrientation may be called before the container is set in some cases # (via resizeEvent), so there's no need to do anything here until called # again by containerChanged if self.container() is None: return if o == 'auto' and self.autoOrient: if self.container().type() == 'tab': o = 'horizontal' elif self.width() > self.height()*1.5: o = 'vertical' else: o = 'horizontal' if force or self.orientation != o: self.orientation = o self.label.setOrientation(o) self.updateStyle() def updateStyle(self): ## updates orientation and appearance of title bar if self.labelHidden: self.widgetArea.setStyleSheet(self.nStyle) elif self.orientation == 'vertical': self.label.setOrientation('vertical') if self.moveLabel: self.topLayout.addWidget(self.label, 1, 0) self.widgetArea.setStyleSheet(self.vStyle) else: self.label.setOrientation('horizontal') if self.moveLabel: self.topLayout.addWidget(self.label, 0, 1) self.widgetArea.setStyleSheet(self.hStyle) def resizeEvent(self, ev): self.setOrientation() self.resizeOverlay(self.size()) def name(self): return self._name def addWidget(self, widget, row=None, col=0, rowspan=1, colspan=1): """ Add a new widget to the interior of this Dock. Each Dock uses a QGridLayout to arrange widgets within. """ if row is None: row = self.currentRow self.currentRow = max(row+1, self.currentRow) self.widgets.append(widget) self.layout.addWidget(widget, row, col, rowspan, colspan) self.raiseOverlay() def startDrag(self): self.drag = QtGui.QDrag(self) mime = QtCore.QMimeData() self.drag.setMimeData(mime) self.widgetArea.setStyleSheet(self.dragStyle) self.update() action = self.drag.exec() if hasattr(self.drag, 'exec') else self.drag.exec_() self.updateStyle() def float(self): self.area.floatDock(self) def container(self): return self._container def containerChanged(self, c): if self._container is not None: # ask old container to close itself if it is no longer needed self._container.apoptose() self._container = c if c is None: self.area = None else: self.area = c.area if c.type() != 'tab': self.moveLabel = True self.label.setDim(False) else: self.moveLabel = False self.setOrientation(force=True) def raiseDock(self): """If this Dock is stacked underneath others, raise it to the top.""" self.container().raiseDock(self) def close(self): """Remove this dock from the DockArea it lives inside.""" if self._container is None: warnings.warn(f"Cannot close dock {self} because it is not open.", RuntimeWarning, stacklevel=2) return self.setParent(None) QtWidgets.QLabel.close(self.label) self.label.setParent(None) self._container.apoptose() self._container = None self.sigClosed.emit(self) def __repr__(self): return "" % (self.name(), self.stretch()) ## PySide bug: We need to explicitly redefine these methods ## or else drag/drop events will not be delivered. def dragEnterEvent(self, *args): DockDrop.dragEnterEvent(self, *args) def dragMoveEvent(self, *args): DockDrop.dragMoveEvent(self, *args) def dragLeaveEvent(self, *args): DockDrop.dragLeaveEvent(self, *args) def dropEvent(self, *args): DockDrop.dropEvent(self, *args) class DockLabel(VerticalLabel): sigClicked = QtCore.Signal(object, object) sigCloseClicked = QtCore.Signal() def __init__(self, text, dock, showCloseButton, fontSize): self.dim = False self.fixedWidth = False self.fontSize = fontSize VerticalLabel.__init__(self, text, orientation='horizontal', forceWidth=False) self.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop|QtCore.Qt.AlignmentFlag.AlignHCenter) self.dock = dock self.updateStyle() self.setAutoFillBackground(False) self.mouseMoved = False self.closeButton = None if showCloseButton: self.closeButton = QtWidgets.QToolButton(self) self.closeButton.clicked.connect(self.sigCloseClicked) self.closeButton.setIcon(QtWidgets.QApplication.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_TitleBarCloseButton)) def updateStyle(self): r = '3px' if self.dim: fg = '#aaa' bg = '#44a' border = '#339' else: fg = '#fff' bg = '#66c' border = '#55B' if self.orientation == 'vertical': self.vStyle = """DockLabel { background-color : %s; color : %s; border-top-right-radius: 0px; border-top-left-radius: %s; border-bottom-right-radius: 0px; border-bottom-left-radius: %s; border-width: 0px; border-right: 2px solid %s; padding-top: 3px; padding-bottom: 3px; font-size: %s; }""" % (bg, fg, r, r, border, self.fontSize) self.setStyleSheet(self.vStyle) else: self.hStyle = """DockLabel { background-color : %s; color : %s; border-top-right-radius: %s; border-top-left-radius: %s; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border-width: 0px; border-bottom: 2px solid %s; padding-left: 3px; padding-right: 3px; font-size: %s; }""" % (bg, fg, r, r, border, self.fontSize) self.setStyleSheet(self.hStyle) def setDim(self, d): if self.dim != d: self.dim = d self.updateStyle() def setOrientation(self, o): VerticalLabel.setOrientation(self, o) self.updateStyle() def mousePressEvent(self, ev): lpos = ev.position() if hasattr(ev, 'position') else ev.localPos() self.pressPos = lpos self.mouseMoved = False ev.accept() def mouseMoveEvent(self, ev): if not self.mouseMoved: lpos = ev.position() if hasattr(ev, 'position') else ev.localPos() self.mouseMoved = (lpos - self.pressPos).manhattanLength() > QtWidgets.QApplication.startDragDistance() if self.mouseMoved and ev.buttons() == QtCore.Qt.MouseButton.LeftButton: self.dock.startDrag() ev.accept() def mouseReleaseEvent(self, ev): ev.accept() if not self.mouseMoved: self.sigClicked.emit(self, ev) def mouseDoubleClickEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.LeftButton: self.dock.float() def resizeEvent (self, ev): if self.closeButton: if self.orientation == 'vertical': size = ev.size().width() pos = QtCore.QPoint(0, 0) else: size = ev.size().height() pos = QtCore.QPoint(ev.size().width() - size, 0) self.closeButton.setFixedSize(QtCore.QSize(size, size)) self.closeButton.move(pos) super(DockLabel,self).resizeEvent(ev) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/dockarea/DockArea.py000066400000000000000000000335131421045507400234540ustar00rootroot00000000000000import weakref from ..Qt import QtWidgets from .Container import Container, HContainer, TContainer, VContainer from .Dock import Dock from .DockDrop import DockDrop class DockArea(Container, QtWidgets.QWidget, DockDrop): def __init__(self, parent=None, temporary=False, home=None): Container.__init__(self, self) QtWidgets.QWidget.__init__(self, parent=parent) DockDrop.__init__(self, allowedAreas=['left', 'right', 'top', 'bottom']) self.layout = QtWidgets.QVBoxLayout() self.layout.setContentsMargins(0,0,0,0) self.layout.setSpacing(0) self.setLayout(self.layout) self.docks = weakref.WeakValueDictionary() self.topContainer = None self.raiseOverlay() self.temporary = temporary self.tempAreas = [] self.home = home def type(self): return "top" def addDock(self, dock=None, position='bottom', relativeTo=None, **kwds): """Adds a dock to this area. ============== ================================================================= **Arguments:** dock The new Dock object to add. If None, then a new Dock will be created. position 'bottom', 'top', 'left', 'right', 'above', or 'below' relativeTo If relativeTo is None, then the new Dock is added to fill an entire edge of the window. If relativeTo is another Dock, then the new Dock is placed adjacent to it (or in a tabbed configuration for 'above' and 'below'). ============== ================================================================= All extra keyword arguments are passed to Dock.__init__() if *dock* is None. """ if dock is None: dock = Dock(**kwds) # store original area that the dock will return to when un-floated if not self.temporary: dock.orig_area = self ## Determine the container to insert this dock into. ## If there is no neighbor, then the container is the top. if relativeTo is None or relativeTo is self: if self.topContainer is None: container = self neighbor = None else: container = self.topContainer neighbor = None else: if isinstance(relativeTo, str): relativeTo = self.docks[relativeTo] container = self.getContainer(relativeTo) if container is None: raise TypeError("Dock %s is not contained in a DockArea; cannot add another dock relative to it." % relativeTo) neighbor = relativeTo ## what container type do we need? neededContainer = { 'bottom': 'vertical', 'top': 'vertical', 'left': 'horizontal', 'right': 'horizontal', 'above': 'tab', 'below': 'tab' }[position] ## Can't insert new containers into a tab container; insert outside instead. if neededContainer != container.type() and container.type() == 'tab': neighbor = container container = container.container() ## Decide if the container we have is suitable. ## If not, insert a new container inside. if neededContainer != container.type(): if neighbor is None: container = self.addContainer(neededContainer, self.topContainer) else: container = self.addContainer(neededContainer, neighbor) ## Insert the new dock before/after its neighbor insertPos = { 'bottom': 'after', 'top': 'before', 'left': 'before', 'right': 'after', 'above': 'before', 'below': 'after' }[position] #print "request insert", dock, insertPos, neighbor old = dock.container() container.insert(dock, insertPos, neighbor) self.docks[dock.name()] = dock if old is not None: old.apoptose() return dock def moveDock(self, dock, position, neighbor): """ Move an existing Dock to a new location. """ ## Moving to the edge of a tabbed dock causes a drop outside the tab box if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab': neighbor = neighbor.container() self.addDock(dock, position, neighbor) def getContainer(self, obj): if obj is None: return self return obj.container() def makeContainer(self, typ): if typ == 'vertical': new = VContainer(self) elif typ == 'horizontal': new = HContainer(self) elif typ == 'tab': new = TContainer(self) else: raise ValueError("typ must be one of 'vertical', 'horizontal', or 'tab'") return new def addContainer(self, typ, obj): """Add a new container around obj""" new = self.makeContainer(typ) container = self.getContainer(obj) container.insert(new, 'before', obj) #print "Add container:", new, " -> ", container if obj is not None: new.insert(obj) self.raiseOverlay() return new def insert(self, new, pos=None, neighbor=None): if self.topContainer is not None: # Adding new top-level container; addContainer() should # take care of giving the old top container a new home. self.topContainer.containerChanged(None) self.layout.addWidget(new) new.containerChanged(self) self.topContainer = new self.raiseOverlay() def count(self): if self.topContainer is None: return 0 return 1 def resizeEvent(self, ev): self.resizeOverlay(self.size()) def addTempArea(self): if self.home is None: area = DockArea(temporary=True, home=self) self.tempAreas.append(area) win = TempAreaWindow(area) area.win = win win.show() else: area = self.home.addTempArea() #print "added temp area", area, area.window() return area def floatDock(self, dock): """Removes *dock* from this DockArea and places it in a new window.""" area = self.addTempArea() area.win.resize(dock.size()) area.moveDock(dock, 'top', None) def removeTempArea(self, area): self.tempAreas.remove(area) #print "close window", area.window() area.window().close() def saveState(self): """ Return a serialized (storable) representation of the state of all Docks in this DockArea.""" if self.topContainer is None: main = None else: main = self.childState(self.topContainer) state = {'main': main, 'float': []} for a in self.tempAreas: geo = a.win.geometry() geo = (geo.x(), geo.y(), geo.width(), geo.height()) state['float'].append((a.saveState(), geo)) return state def childState(self, obj): if isinstance(obj, Dock): return ('dock', obj.name(), {}) else: childs = [] for i in range(obj.count()): childs.append(self.childState(obj.widget(i))) return (obj.type(), childs, obj.saveState()) def restoreState(self, state, missing='error', extra='bottom'): """ Restore Dock configuration as generated by saveState. This function does not create any Docks--it will only restore the arrangement of an existing set of Docks. By default, docks that are described in *state* but do not exist in the dock area will cause an exception to be raised. This behavior can be changed by setting *missing* to 'ignore' or 'create'. Extra docks that are in the dockarea but that are not mentioned in *state* will be added to the bottom of the dockarea, unless otherwise specified by the *extra* argument. """ ## 1) make dict of all docks and list of existing containers containers, docks = self.findAll() oldTemps = self.tempAreas[:] #print "found docks:", docks ## 2) create container structure, move docks into new containers if state['main'] is not None: self.buildFromState(state['main'], docks, self, missing=missing) ## 3) create floating areas, populate for s in state['float']: a = self.addTempArea() a.buildFromState(s[0]['main'], docks, a, missing=missing) a.win.setGeometry(*s[1]) a.apoptose() # ask temp area to close itself if it is empty ## 4) Add any remaining docks to a float for d in docks.values(): if extra == 'float': a = self.addTempArea() a.addDock(d, 'below') else: self.moveDock(d, extra, None) #print "\nKill old containers:" ## 5) kill old containers for c in containers: c.close() for a in oldTemps: a.apoptose() def buildFromState(self, state, docks, root, depth=0, missing='error'): typ, contents, state = state pfx = " " * depth if typ == 'dock': try: obj = docks[contents] del docks[contents] except KeyError: if missing == 'error': raise Exception('Cannot restore dock state; no dock with name "%s"' % contents) elif missing == 'create': obj = Dock(name=contents) elif missing == 'ignore': return else: raise ValueError('"missing" argument must be one of "error", "create", or "ignore".') else: obj = self.makeContainer(typ) root.insert(obj, 'after') #print pfx+"Add:", obj, " -> ", root if typ != 'dock': for o in contents: self.buildFromState(o, docks, obj, depth+1, missing=missing) # remove this container if possible. (there are valid situations when a restore will # generate empty containers, such as when using missing='ignore') obj.apoptose(propagate=False) obj.restoreState(state) ## this has to be done later? def findAll(self, obj=None, c=None, d=None): if obj is None: obj = self.topContainer ## check all temp areas first if c is None: c = [] d = {} for a in self.tempAreas: c1, d1 = a.findAll() c.extend(c1) d.update(d1) if isinstance(obj, Dock): d[obj.name()] = obj elif obj is not None: c.append(obj) for i in range(obj.count()): o2 = obj.widget(i) c2, d2 = self.findAll(o2) c.extend(c2) d.update(d2) return (c, d) def apoptose(self, propagate=True): # remove top container if possible, close this area if it is temporary. #print "apoptose area:", self.temporary, self.topContainer, self.topContainer.count() if self.topContainer is None or self.topContainer.count() == 0: self.topContainer = None if self.temporary and self.home is not None: self.home.removeTempArea(self) #self.close() def clear(self): docks = self.findAll()[1] for dock in docks.values(): dock.close() ## PySide bug: We need to explicitly redefine these methods ## or else drag/drop events will not be delivered. def dragEnterEvent(self, *args): DockDrop.dragEnterEvent(self, *args) def dragMoveEvent(self, *args): DockDrop.dragMoveEvent(self, *args) def dragLeaveEvent(self, *args): DockDrop.dragLeaveEvent(self, *args) def dropEvent(self, *args): DockDrop.dropEvent(self, *args) def printState(self, state=None, name='Main'): # for debugging if state is None: state = self.saveState() print("=== %s dock area ===" % name) if state['main'] is None: print(" (empty)") else: self._printAreaState(state['main']) for i, float in enumerate(state['float']): self.printState(float[0], name='float %d' % i) def _printAreaState(self, area, indent=0): if area[0] == 'dock': print(" " * indent + area[0] + " " + str(area[1:])) return else: print(" " * indent + area[0]) for ch in area[1]: self._printAreaState(ch, indent+1) class TempAreaWindow(QtWidgets.QWidget): def __init__(self, area, **kwargs): QtWidgets.QWidget.__init__(self, **kwargs) self.layout = QtWidgets.QGridLayout() self.setLayout(self.layout) self.layout.setContentsMargins(0, 0, 0, 0) self.dockarea = area self.layout.addWidget(area) def closeEvent(self, *args): # restore docks to their original area docks = self.dockarea.findAll()[1] for dock in docks.values(): if hasattr(dock, 'orig_area'): dock.orig_area.addDock(dock, ) # clear dock area, and close remaining docks self.dockarea.clear() super().closeEvent(*args) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/dockarea/DockDrop.py000066400000000000000000000103161421045507400235040ustar00rootroot00000000000000__all__ = ["DockDrop"] from ..Qt import QtCore, QtGui, QtWidgets class DockDrop(object): """Provides dock-dropping methods""" def __init__(self, allowedAreas=None): object.__init__(self) if allowedAreas is None: allowedAreas = ['center', 'right', 'left', 'top', 'bottom'] self.allowedAreas = set(allowedAreas) self.setAcceptDrops(True) self.dropArea = None self.overlay = DropAreaOverlay(self) self.overlay.raise_() def resizeOverlay(self, size): self.overlay.resize(size) def raiseOverlay(self): self.overlay.raise_() def dragEnterEvent(self, ev): src = ev.source() if hasattr(src, 'implements') and src.implements('dock'): #print "drag enter accept" ev.accept() else: #print "drag enter ignore" ev.ignore() def dragMoveEvent(self, ev): #print "drag move" # QDragMoveEvent inherits QDropEvent which provides posF() # PyQt6 provides only position() posF = ev.posF() if hasattr(ev, 'posF') else ev.position() ld = posF.x() rd = self.width() - ld td = posF.y() bd = self.height() - td mn = min(ld, rd, td, bd) if mn > 30: self.dropArea = "center" elif (ld == mn or td == mn) and mn > self.height()/3.: self.dropArea = "center" elif (rd == mn or ld == mn) and mn > self.width()/3.: self.dropArea = "center" elif rd == mn: self.dropArea = "right" elif ld == mn: self.dropArea = "left" elif td == mn: self.dropArea = "top" elif bd == mn: self.dropArea = "bottom" if ev.source() is self and self.dropArea == 'center': #print " no self-center" self.dropArea = None ev.ignore() elif self.dropArea not in self.allowedAreas: #print " not allowed" self.dropArea = None ev.ignore() else: #print " ok" ev.accept() self.overlay.setDropArea(self.dropArea) def dragLeaveEvent(self, ev): self.dropArea = None self.overlay.setDropArea(self.dropArea) def dropEvent(self, ev): area = self.dropArea if area is None: return if area == 'center': area = 'above' self.area.moveDock(ev.source(), area, self) self.dropArea = None self.overlay.setDropArea(self.dropArea) class DropAreaOverlay(QtWidgets.QWidget): """Overlay widget that draws drop areas during a drag-drop operation""" def __init__(self, parent): QtWidgets.QWidget.__init__(self, parent) self.dropArea = None self.hide() self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents) def setDropArea(self, area): self.dropArea = area if area is None: self.hide() else: ## Resize overlay to just the region where drop area should be displayed. ## This works around a Qt bug--can't display transparent widgets over QGLWidget prgn = self.parent().rect() rgn = QtCore.QRect(prgn) w = min(30, int(prgn.width() / 3)) h = min(30, int(prgn.height() / 3)) if self.dropArea == 'left': rgn.setWidth(w) elif self.dropArea == 'right': rgn.setLeft(rgn.left() + prgn.width() - w) elif self.dropArea == 'top': rgn.setHeight(h) elif self.dropArea == 'bottom': rgn.setTop(rgn.top() + prgn.height() - h) elif self.dropArea == 'center': rgn.adjust(w, h, -w, -h) self.setGeometry(rgn) self.show() self.update() def paintEvent(self, ev): if self.dropArea is None: return p = QtGui.QPainter(self) rgn = self.rect() p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 255, 50))) p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 150), 3)) p.drawRect(rgn) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/dockarea/__init__.py000066400000000000000000000000661421045507400235370ustar00rootroot00000000000000from .Dock import Dock from .DockArea import DockArea pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/000077500000000000000000000000001421045507400214715ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/Arrow.py000066400000000000000000000026651421045507400231460ustar00rootroot00000000000000""" Display an animated arrowhead following a curve. This example uses the CurveArrow class, which is a combination of ArrowItem and CurvePoint. To place a static arrow anywhere in a scene, use ArrowItem. To attach other types of item to a curve, use CurvePoint. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtWidgets app = pg.mkQApp("Arrow Example") w = QtWidgets.QMainWindow() cw = pg.GraphicsLayoutWidget() w.show() w.resize(400,600) w.setCentralWidget(cw) w.setWindowTitle('pyqtgraph example: Arrow') p = cw.addPlot(row=0, col=0) p2 = cw.addPlot(row=1, col=0) ## variety of arrow shapes a1 = pg.ArrowItem(angle=-160, tipAngle=60, headLen=40, tailLen=40, tailWidth=20, pen={'color': 'w', 'width': 3}) a2 = pg.ArrowItem(angle=-120, tipAngle=30, baseAngle=20, headLen=40, tailLen=40, tailWidth=8, pen=None, brush='y') a3 = pg.ArrowItem(angle=-60, baseAngle=20, headLen=40, headWidth=20, tailLen=None, brush=None) a4 = pg.ArrowItem(angle=-20, tipAngle=30, baseAngle=-30, headLen=40, tailLen=None) a2.setPos(10,0) a3.setPos(20,0) a4.setPos(30,0) p.addItem(a1) p.addItem(a2) p.addItem(a3) p.addItem(a4) p.setRange(QtCore.QRectF(-20, -10, 60, 20)) ## Animated arrow following curve c = p2.plot(x=np.sin(np.linspace(0, 2*np.pi, 1000)), y=np.cos(np.linspace(0, 6*np.pi, 1000))) a = pg.CurveArrow(c) a.setStyle(headLen=40) p2.addItem(a) anim = a.makeAnimation(loop=-1) anim.start() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/BarGraphItem.py000066400000000000000000000013551421045507400243540ustar00rootroot00000000000000""" Simple example using BarGraphItem """ import numpy as np import pyqtgraph as pg win = pg.plot() win.setWindowTitle('pyqtgraph example: BarGraphItem') x = np.arange(10) y1 = np.sin(x) y2 = 1.1 * np.sin(x+1) y3 = 1.2 * np.sin(x+2) bg1 = pg.BarGraphItem(x=x, height=y1, width=0.3, brush='r') bg2 = pg.BarGraphItem(x=x+0.33, height=y2, width=0.3, brush='g') bg3 = pg.BarGraphItem(x=x+0.66, height=y3, width=0.3, brush='b') win.addItem(bg1) win.addItem(bg2) win.addItem(bg3) # Final example shows how to handle mouse clicks: class BarGraph(pg.BarGraphItem): def mouseClickEvent(self, event): print("clicked") bg = BarGraph(x=x, y=y1*0.3+2, height=0.4+y1*0.2, width=0.8) win.addItem(bg) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/CLIexample.py000066400000000000000000000011751421045507400240320ustar00rootroot00000000000000""" Display a plot and an image with minimal setup. pg.plot() and pg.image() are indended to be used from an interactive prompt to allow easy data inspection (but note that PySide unfortunately does not call the Qt event loop while the interactive prompt is running, in this case it is necessary to call QApplication.exec_() to make the windows appear). """ import numpy as np import pyqtgraph as pg data = np.random.normal(size=1000) pg.plot(data, title="Simplest possible plotting example") data = np.random.normal(size=(500,500)) pg.image(data, title="Simplest possible image example") if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ColorBarItem.py000066400000000000000000000064251421045507400243740ustar00rootroot00000000000000""" This example demonstrates the use of ColorBarItem, which displays a simple interactive color bar. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets, mkQApp class MainWindow(QtWidgets.QMainWindow): """ example application main window """ def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) gr_wid = pg.GraphicsLayoutWidget(show=True) self.setCentralWidget(gr_wid) self.setWindowTitle('pyqtgraph example: Interactive color bar') self.resize(800,700) self.show() ## Create image items data = np.fromfunction(lambda i, j: (1+0.3*np.sin(i)) * (i)**2 + (j)**2, (100, 100)) noisy_data = data * (1 + 0.2 * np.random.random(data.shape) ) noisy_transposed = noisy_data.transpose() #--- add non-interactive image with integrated color ------------------ p1 = gr_wid.addPlot(title="non-interactive") # Basic steps to create a false color image with color bar: i1 = pg.ImageItem(image=data) p1.addItem( i1 ) p1.addColorBar( i1, colorMap='CET-L9', values=(0, 30_000)) # , interactive=False) # p1.setMouseEnabled( x=False, y=False) p1.disableAutoRange() p1.hideButtons() p1.setRange(xRange=(0,100), yRange=(0,100), padding=0) p1.showAxes(True, showValues=(True,False,False,True) ) #--- add interactive image with integrated horizontal color bar ------- i2 = pg.ImageItem(image=noisy_data) p2 = gr_wid.addPlot(1,0, 1,1, title="interactive") p2.addItem( i2, title='' ) # inserted color bar also works with labels on the right. p2.showAxis('right') p2.getAxis('left').setStyle( showValues=False ) p2.getAxis('bottom').setLabel('bottom axis label') p2.getAxis('right').setLabel('right axis label') bar = pg.ColorBarItem( values = (0, 30_000), colorMap='CET-L4', label='horizontal color bar', limits = (0, None), rounding=1000, orientation = 'h', pen='#8888FF', hoverPen='#EEEEFF', hoverBrush='#EEEEFF80' ) bar.setImageItem( i2, insert_in=p2 ) #--- multiple images adjusted by a separate color bar ------------------------ i3 = pg.ImageItem(image=noisy_data) p3 = gr_wid.addPlot(0,1, 1,1, title="shared 1") p3.addItem( i3 ) i4 = pg.ImageItem(image=noisy_transposed) p4 = gr_wid.addPlot(1,1, 1,1, title="shared 2") p4.addItem( i4 ) cmap = pg.colormap.get('CET-L8') bar = pg.ColorBarItem( # values = (-15_000, 15_000), limits = (-30_000, 30_000), # start with full range... rounding=1000, width = 10, colorMap=cmap ) bar.setImageItem( [i3, i4] ) bar.setLevels( low=-5_000, high=15_000) # ... then adjust to retro sunset. # manually adjust reserved space at top and bottom to align with plot bar.getAxis('bottom').setHeight(21) bar.getAxis('top').setHeight(31) gr_wid.addItem(bar, 0,2, 2,1) # large bar spanning both rows mkQApp("ColorBarItem Example") main_window = MainWindow() ## Start Qt event loop if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ColorButton.py000066400000000000000000000011341421045507400243140ustar00rootroot00000000000000""" Simple example demonstrating a button which displays a colored rectangle and allows the user to select a new color by clicking on the button. """ import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets app = pg.mkQApp("ColorButton Example") win = QtWidgets.QMainWindow() btn = pg.ColorButton() win.setCentralWidget(btn) win.show() win.setWindowTitle('pyqtgraph example: ColorButton') def change(btn): print("change", btn.color()) def done(btn): print("done", btn.color()) btn.sigColorChanging.connect(change) btn.sigColorChanged.connect(done) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ColorGradientPlots.py000066400000000000000000000140461421045507400256260ustar00rootroot00000000000000""" This example demonstrates plotting with color gradients. It also shows multiple plots with timed rolling updates """ import time import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, mkQApp class DataSource(object): """ source of buffered demonstration data """ def __init__(self, sample_rate=200., signal_period=0.55, negative_period=None, max_length=300): """ prepare, but don't start yet """ self.rate = sample_rate self.period = signal_period self.neg_period = negative_period self.start_time = 0. self.sample_idx = 0 # number of next sample to be taken def start(self, timestamp): """ start acquiring simulated data """ self.start_time = timestamp self.sample_idx = 0 def get_data(self, timestamp, max_length=6000): """ return all data acquired since last get_data call """ next_idx = int( (timestamp - self.start_time) * self.rate ) if next_idx - self.sample_idx > max_length: self.sample_idx = next_idx - max_length # catch up if needed # create some mildly intersting data: sample_phases = np.arange( self.sample_idx, next_idx, dtype=np.float64 ) self.sample_idx = next_idx sample_phase_pos = sample_phases / (self.period*self.rate) sample_phase_pos %= 1.0 if self.neg_period is None: return sample_phase_pos**4 sample_phase_neg = sample_phases / (self.neg_period*self.rate) sample_phase_neg %= 1.0 return sample_phase_pos**4 - sample_phase_neg**4 class MainWindow(pg.GraphicsLayoutWidget): """ example application main window """ def __init__(self): super().__init__() self.setWindowTitle('pyqtgraph example: gradient plots') self.resize(800,800) self.show() layout = self # we are using a GraphicsLayoutWidget as main window for convenience cm = pg.colormap.get('CET-L17') cm.reverse() pen0 = cm.getPen( span=(0.0,1.0), width=5 ) curve0 = pg.PlotDataItem(pen=pen0 ) comment0 = 'Clipped color map applied to vertical axis' cm = pg.colormap.get('CET-D1') cm.setMappingMode('diverging') brush = cm.getBrush( span=(-1., 1.), orientation='vertical' ) curve1 = pg.PlotDataItem(pen='w', brush=brush, fillLevel=0.0 ) comment1 = 'Diverging vertical color map used as brush' cm = pg.colormap.get('CET-L17') cm.setMappingMode('mirror') pen2 = cm.getPen( span=(400.0,600.0), width=5, orientation='horizontal' ) curve2 = pg.PlotDataItem(pen=pen2 ) comment2 = 'Mirrored color map applied to horizontal axis' cm = pg.colormap.get('CET-C2') cm.setMappingMode('repeat') pen3 = cm.getPen( span=(100, 200), width=5, orientation='horizontal' ) curve3 = pg.PlotDataItem(pen=pen3 ) # vertical diverging fill comment3 = 'Repeated color map applied to horizontal axis' curves = (curve0, curve1, curve2, curve3) comments = (comment0, comment1, comment2, comment3) length = int( 3.0 * 200. ) # length of display in samples self.top_plot = None for idx, (curve, comment) in enumerate( zip(curves,comments) ): plot = layout.addPlot(row=idx+1, col=0) text = pg.TextItem( comment, anchor=(0,1) ) text.setPos(0.,1.) if self.top_plot is None: self.top_plot = plot else: plot.setXLink( self.top_plot ) plot.addItem( curve ) plot.addItem( text ) plot.setXRange( 0, length ) if idx != 1: plot.setYRange( 0. , 1.1 ) else : plot.setYRange( -1. , 1.2 ) # last plot include positive/negative data self.traces = ( {'crv': curve0, 'buf': np.zeros( length ), 'ptr':0, 'ds': DataSource( signal_period=0.55 ) }, {'crv': curve1, 'buf': np.zeros( length ), 'ptr':0, 'ds': DataSource( signal_period=0.61, negative_period=0.55 ) }, {'crv': curve2, 'buf': np.zeros( length ), 'ptr':0, 'ds': DataSource( signal_period=0.65 ) }, {'crv': curve3, 'buf': np.zeros( length ), 'ptr':0, 'ds': DataSource( signal_period=0.52 ) }, ) self.timer = QtCore.QTimer(timerType=QtCore.Qt.TimerType.PreciseTimer) self.timer.timeout.connect(self.update) timestamp = time.perf_counter() for dic in self.traces: dic['ds'].start( timestamp ) self.last_update = time.perf_counter() self.mean_dt = None self.timer.start(33) def update(self): """ called by timer at 30 Hz """ timestamp = time.perf_counter() # measure actual update rate: dt = timestamp - self.last_update if self.mean_dt is None: self.mean_dt = dt else: self.mean_dt = 0.95 * self.mean_dt + 0.05 * dt # average over fluctuating measurements self.top_plot.setTitle( 'refresh: {:0.1f}ms -> {:0.1f} fps'.format( 1000*self.mean_dt, 1/self.mean_dt ) ) # handle rolling buffer: self.last_update = timestamp for dic in self.traces: new_data = dic['ds'].get_data( timestamp ) idx_a = dic['ptr'] idx_b = idx_a + len( new_data ) len_buffer = dic['buf'].shape[0] if idx_b < len_buffer: # data does not cross buffer boundary dic['buf'][idx_a:idx_b] = new_data else: # part of the new data needs to roll over to beginning of buffer len_1 = len_buffer - idx_a # this many elements still fit dic['buf'][idx_a:idx_a+len_1] = new_data[:len_1] # first part of data at end idx_b = len(new_data) - len_1 dic['buf'][0:idx_b] = new_data[len_1:] # second part of data at re-start dic['ptr'] = idx_b dic['crv'].setData( dic['buf'] ) mkQApp("Gradient plotting example") main_window = MainWindow() ## Start Qt event loop if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ConsoleWidget.py000066400000000000000000000014641421045507400246160ustar00rootroot00000000000000""" ConsoleWidget is used to allow execution of user-supplied python commands in an application. It also includes a command history and functionality for trapping and inspecting stack traces. """ import numpy as np import pyqtgraph as pg import pyqtgraph.console app = pg.mkQApp() ## build an initial namespace for console commands to be executed in (this is optional; ## the user can always import these modules manually) namespace = {'pg': pg, 'np': np} ## initial text to display in the console text = """ This is an interactive python console. The numpy and pyqtgraph modules have already been imported as 'np' and 'pg'. Go, play. """ c = pyqtgraph.console.ConsoleWidget(namespace=namespace, text=text) c.show() c.setWindowTitle('pyqtgraph example: ConsoleWidget') if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/CustomGraphItem.py000066400000000000000000000065201421045507400251210ustar00rootroot00000000000000""" Simple example of subclassing GraphItem. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore # Enable antialiasing for prettier plots pg.setConfigOptions(antialias=True) w = pg.GraphicsLayoutWidget(show=True) w.setWindowTitle('pyqtgraph example: CustomGraphItem') v = w.addViewBox() v.setAspectLocked() class Graph(pg.GraphItem): def __init__(self): self.dragPoint = None self.dragOffset = None self.textItems = [] pg.GraphItem.__init__(self) self.scatter.sigClicked.connect(self.clicked) def setData(self, **kwds): self.text = kwds.pop('text', []) self.data = kwds if 'pos' in self.data: npts = self.data['pos'].shape[0] self.data['data'] = np.empty(npts, dtype=[('index', int)]) self.data['data']['index'] = np.arange(npts) self.setTexts(self.text) self.updateGraph() def setTexts(self, text): for i in self.textItems: i.scene().removeItem(i) self.textItems = [] for t in text: item = pg.TextItem(t) self.textItems.append(item) item.setParentItem(self) def updateGraph(self): pg.GraphItem.setData(self, **self.data) for i,item in enumerate(self.textItems): item.setPos(*self.data['pos'][i]) def mouseDragEvent(self, ev): if ev.button() != QtCore.Qt.MouseButton.LeftButton: ev.ignore() return if ev.isStart(): # We are already one step into the drag. # Find the point(s) at the mouse cursor when the button was first # pressed: pos = ev.buttonDownPos() pts = self.scatter.pointsAt(pos) if len(pts) == 0: ev.ignore() return self.dragPoint = pts[0] ind = pts[0].data()[0] self.dragOffset = self.data['pos'][ind] - pos elif ev.isFinish(): self.dragPoint = None return else: if self.dragPoint is None: ev.ignore() return ind = self.dragPoint.data()[0] self.data['pos'][ind] = ev.pos() + self.dragOffset self.updateGraph() ev.accept() def clicked(self, pts): print("clicked: %s" % pts) g = Graph() v.addItem(g) ## Define positions of nodes pos = np.array([ [0,0], [10,0], [0,10], [10,10], [5,5], [15,5] ], dtype=float) ## Define the set of connections in the graph adj = np.array([ [0,1], [1,3], [3,2], [2,0], [1,5], [3,5], ]) ## Define the symbol to use for each node (this is optional) symbols = ['o','o','o','o','t','+'] ## Define the line style for each connection (this is optional) lines = np.array([ (255,0,0,255,1), (255,0,255,255,2), (255,0,255,255,3), (255,255,0,255,2), (255,0,0,255,1), (255,255,255,255,4), ], dtype=[('red',np.ubyte),('green',np.ubyte),('blue',np.ubyte),('alpha',np.ubyte),('width',float)]) ## Define text to show next to each symbol texts = ["Point %d" % i for i in range(6)] ## Update the graph g.setData(pos=pos, adj=adj, pen=lines, size=1, symbol=symbols, pxMode=False, text=texts) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/DataSlicing.py000066400000000000000000000027131421045507400242300ustar00rootroot00000000000000""" Demonstrate a simple data-slicing task: given 3D data (displayed at top), select a 2D plane and interpolate data along that plane to generate a slice image (displayed at bottom). """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets app = pg.mkQApp("Data Slicing Example") ## Create window with two ImageView widgets win = QtWidgets.QMainWindow() win.resize(800,800) win.setWindowTitle('pyqtgraph example: DataSlicing') cw = QtWidgets.QWidget() win.setCentralWidget(cw) l = QtWidgets.QGridLayout() cw.setLayout(l) imv1 = pg.ImageView() imv2 = pg.ImageView() l.addWidget(imv1, 0, 0) l.addWidget(imv2, 1, 0) win.show() roi = pg.LineSegmentROI([[10, 64], [120,64]], pen='r') imv1.addItem(roi) x1 = np.linspace(-30, 10, 128)[:, np.newaxis, np.newaxis] x2 = np.linspace(-20, 20, 128)[:, np.newaxis, np.newaxis] y = np.linspace(-30, 10, 128)[np.newaxis, :, np.newaxis] z = np.linspace(-20, 20, 128)[np.newaxis, np.newaxis, :] d1 = np.sqrt(x1**2 + y**2 + z**2) d2 = 2*np.sqrt(x1[::-1]**2 + y**2 + z**2) d3 = 4*np.sqrt(x2**2 + y[:,::-1]**2 + z**2) data = (np.sin(d1) / d1**2) + (np.sin(d2) / d2**2) + (np.sin(d3) / d3**2) def update(): global data, imv1, imv2 d2 = roi.getArrayRegion(data, imv1.imageItem, axes=(1,2)) imv2.setImage(d2) roi.sigRegionChanged.connect(update) ## Display the data imv1.setImage(data) imv1.setHistogramRange(-0.01, 0.01) imv1.setLevels(-0.003, 0.003) update() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/DataTreeWidget.py000066400000000000000000000015511421045507400247020ustar00rootroot00000000000000 """ Simple use of DataTreeWidget to display a structure of nested dicts, lists, and arrays """ import numpy as np import pyqtgraph as pg # for generating a traceback object to display def some_func1(): return some_func2() def some_func2(): try: raise Exception() except: import sys return sys.exc_info()[2] app = pg.mkQApp("DataTreeWidget Example") d = { 'a list': [1,2,3,4,5,6, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"], 'a dict': { 'x': 1, 'y': 2, 'z': 'three' }, 'an array': np.random.randint(10, size=(40,10)), 'a traceback': some_func1(), 'a function': some_func1, 'a class': pg.DataTreeWidget, } tree = pg.DataTreeWidget(data=d) tree.show() tree.setWindowTitle('pyqtgraph example: DataTreeWidget') tree.resize(600,600) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/DateAxisItem.py000066400000000000000000000012261421045507400243650ustar00rootroot00000000000000""" Demonstrates the usage of DateAxisItem to display properly-formatted timestamps on x-axis which automatically adapt to current zoom level. """ import time import numpy as np import pyqtgraph as pg app = pg.mkQApp("DateAxisItem Example") # Create a plot with a date-time axis w = pg.PlotWidget(axisItems = {'bottom': pg.DateAxisItem()}) w.showGrid(x=True, y=True) # Plot sin(1/x^2) with timestamps in the last 100 years now = time.time() x = np.linspace(2*np.pi, 1000*2*np.pi, 8301) w.plot(now-(2*np.pi/x)**2*100*np.pi*1e7, np.sin(x), symbol='o') w.setWindowTitle('pyqtgraph example: DateAxisItem') w.show() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/DateAxisItem_QtDesigner.py000066400000000000000000000026041421045507400265130ustar00rootroot00000000000000""" Demonstrates the usage of DateAxisItem in a layout created with Qt Designer. The spotlight here is on the 'setAxisItems' method, without which one would have to subclass plotWidget in order to attach a dateaxis to it. """ import os import time import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets, loadUiType pg.setConfigOption('background', 'w') pg.setConfigOption('foreground', 'k') BLUE = pg.mkPen('#1f77b4') path = os.path.dirname(os.path.abspath(__file__)) uiFile = os.path.join(path, 'DateAxisItem_QtDesigner.ui') Design, _ = loadUiType(uiFile) class ExampleApp(QtWidgets.QMainWindow, Design): def __init__(self): super().__init__() self.setupUi(self) now = time.time() # Plot random values with timestamps in the last 6 months timestamps = np.linspace(now - 6*30*24*3600, now, 100) self.curve = self.plotWidget.plot(x=timestamps, y=np.random.rand(100), symbol='o', symbolSize=5, pen=BLUE) # 'o' circle 't' triangle 'd' diamond '+' plus 's' square self.plotWidget.setAxisItems({'bottom': pg.DateAxisItem()}) self.plotWidget.showGrid(x=True, y=True) app = pg.mkQApp("DateAxisItem_QtDesigner Example") window = ExampleApp() window.setWindowTitle('pyqtgraph example: DateAxisItem_QtDesigner') window.show() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/DateAxisItem_QtDesigner.ui000066400000000000000000000017621421045507400265040ustar00rootroot00000000000000 MainWindow 0 0 536 381 MainWindow 0 0 536 18 PlotWidget QGraphicsView
      pyqtgraph
      pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/DiffTreeWidget.py000066400000000000000000000017151421045507400247030ustar00rootroot00000000000000 """ Simple use of DiffTreeWidget to display differences between structures of nested dicts, lists, and arrays. """ import numpy as np import pyqtgraph as pg app = pg.mkQApp("DiffTreeWidget Example") A = { 'a list': [1,2,2,4,5,6, {'nested1': 'aaaa', 'nested2': 'bbbbb'}, "seven"], 'a dict': { 'x': 1, 'y': 2, 'z': 'three' }, 'an array': np.random.randint(10, size=(40,10)), #'a traceback': some_func1(), #'a function': some_func1, #'a class': pg.DataTreeWidget, } B = { 'a list': [1,2,3,4,5,5, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"], 'a dict': { 'x': 2, 'y': 2, 'z': 'three', 'w': 5 }, 'another dict': {1:2, 2:3, 3:4}, 'an array': np.random.randint(10, size=(40,10)), } tree = pg.DiffTreeWidget() tree.setData(A, B) tree.show() tree.setWindowTitle('pyqtgraph example: DiffTreeWidget') tree.resize(1000, 800) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/Draw.py000066400000000000000000000014761421045507400227500ustar00rootroot00000000000000""" Demonstrate ability of ImageItem to be used as a canvas for painting with the mouse. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore app = pg.mkQApp("Draw Example") ## Create window with GraphicsView widget w = pg.GraphicsView() w.show() w.resize(800,800) w.setWindowTitle('pyqtgraph example: Draw') view = pg.ViewBox() w.setCentralItem(view) ## lock the aspect ratio view.setAspectLocked(True) ## Create image item img = pg.ImageItem(np.zeros((200,200))) view.addItem(img) ## Set initial view bounds view.setRange(QtCore.QRectF(0, 0, 200, 200)) ## start drawing with 3x3 brush kern = np.array([ [0.0, 0.5, 0.0], [0.5, 1.0, 0.5], [0.0, 0.5, 0.0] ]) img.setDrawKernel(kern, mask=kern, center=(1,1), mode='add') img.setLevels([0, 10]) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ErrorBarItem.py000066400000000000000000000007501421045507400244020ustar00rootroot00000000000000""" Demonstrates basic use of ErrorBarItem """ import numpy as np import pyqtgraph as pg pg.setConfigOptions(antialias=True) x = np.arange(10) y = np.arange(10) %3 top = np.linspace(1.0, 3.0, 10) bottom = np.linspace(2, 0.5, 10) plt = pg.plot() plt.setWindowTitle('pyqtgraph example: ErrorBarItem') err = pg.ErrorBarItem(x=x, y=y, top=top, bottom=bottom, beam=0.5) plt.addItem(err) plt.plot(x, y, symbol='o', pen={'color': 0.8, 'width': 2}) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ExampleApp.py000066400000000000000000000447311421045507400241100ustar00rootroot00000000000000import keyword import os import re import subprocess import sys from argparse import Namespace from collections import OrderedDict from functools import lru_cache import pyqtgraph as pg from pyqtgraph.Qt import QT_LIB, QtCore, QtGui, QtWidgets app = pg.mkQApp() path = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, path) import importlib import utils ui_template = importlib.import_module( f'exampleLoaderTemplate_{QT_LIB.lower()}') # based on https://github.com/art1415926535/PyQt5-syntax-highlighting QRegularExpression = QtCore.QRegularExpression QFont = QtGui.QFont QColor = QtGui.QColor QTextCharFormat = QtGui.QTextCharFormat QSyntaxHighlighter = QtGui.QSyntaxHighlighter def charFormat(color, style='', background=None): """ Return a QTextCharFormat with the given attributes. """ _color = QColor() if type(color) is not str: _color.setRgb(color[0], color[1], color[2]) else: _color.setNamedColor(color) _format = QTextCharFormat() _format.setForeground(_color) if 'bold' in style: _format.setFontWeight(QFont.Weight.Bold) if 'italic' in style: _format.setFontItalic(True) if background is not None: _format.setBackground(pg.mkColor(background)) return _format class LightThemeColors: Red = "#B71C1C" Pink = "#FCE4EC" Purple = "#4A148C" DeepPurple = "#311B92" Indigo = "#1A237E" Blue = "#0D47A1" LightBlue = "#01579B" Cyan = "#006064" Teal = "#004D40" Green = "#1B5E20" LightGreen = "#33691E" Lime = "#827717" Yellow = "#F57F17" Amber = "#FF6F00" Orange = "#E65100" DeepOrange = "#BF360C" Brown = "#3E2723" Grey = "#212121" BlueGrey = "#263238" class DarkThemeColors: Red = "#F44336" Pink = "#F48FB1" Purple = "#CE93D8" DeepPurple = "#B39DDB" Indigo = "#9FA8DA" Blue = "#90CAF9" LightBlue = "#81D4FA" Cyan = "#80DEEA" Teal = "#80CBC4" Green = "#A5D6A7" LightGreen = "#C5E1A5" Lime = "#E6EE9C" Yellow = "#FFF59D" Amber = "#FFE082" Orange = "#FFCC80" DeepOrange = "#FFAB91" Brown = "#BCAAA4" Grey = "#EEEEEE" BlueGrey = "#B0BEC5" LIGHT_STYLES = { 'keyword': charFormat(LightThemeColors.Blue, 'bold'), 'operator': charFormat(LightThemeColors.Red, 'bold'), 'brace': charFormat(LightThemeColors.Purple), 'defclass': charFormat(LightThemeColors.Indigo, 'bold'), 'string': charFormat(LightThemeColors.Amber), 'string2': charFormat(LightThemeColors.DeepPurple), 'comment': charFormat(LightThemeColors.Green, 'italic'), 'self': charFormat(LightThemeColors.Blue, 'bold'), 'numbers': charFormat(LightThemeColors.Teal), } DARK_STYLES = { 'keyword': charFormat(DarkThemeColors.Blue, 'bold'), 'operator': charFormat(DarkThemeColors.Red, 'bold'), 'brace': charFormat(DarkThemeColors.Purple), 'defclass': charFormat(DarkThemeColors.Indigo, 'bold'), 'string': charFormat(DarkThemeColors.Amber), 'string2': charFormat(DarkThemeColors.DeepPurple), 'comment': charFormat(DarkThemeColors.Green, 'italic'), 'self': charFormat(DarkThemeColors.Blue, 'bold'), 'numbers': charFormat(DarkThemeColors.Teal), } class PythonHighlighter(QSyntaxHighlighter): """Syntax highlighter for the Python language. """ # Python keywords keywords = keyword.kwlist # Python operators operators = [ r'=', # Comparison r'==', r'!=', r'<', r'<=', r'>', r'>=', # Arithmetic r'\+', r"-", r'\*', r'/', r'//', r'%', r'\*\*', # In-place r'\+=', r'-=', r'\*=', r'/=', r'\%=', # Bitwise r'\^', r'\|', r'&', r'~', r'>>', r'<<', ] # Python braces braces = [ r'\{', r'\}', r'\(', r'\)', r'\[', r'\]', ] def __init__(self, document): super().__init__(document) # Multi-line strings (expression, flag, style) self.tri_single = (QRegularExpression("'''"), 1, 'string2') self.tri_double = (QRegularExpression('"""'), 2, 'string2') rules = [] # Keyword, operator, and brace rules rules += [(r'\b%s\b' % w, 0, 'keyword') for w in PythonHighlighter.keywords] rules += [(o, 0, 'operator') for o in PythonHighlighter.operators] rules += [(b, 0, 'brace') for b in PythonHighlighter.braces] # All other rules rules += [ # 'self' (r'\bself\b', 0, 'self'), # 'def' followed by an identifier (r'\bdef\b\s*(\w+)', 1, 'defclass'), # 'class' followed by an identifier (r'\bclass\b\s*(\w+)', 1, 'defclass'), # Numeric literals (r'\b[+-]?[0-9]+[lL]?\b', 0, 'numbers'), (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, 'numbers'), (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, 'numbers'), # Double-quoted string, possibly containing escape sequences (r'"[^"\\]*(\\.[^"\\]*)*"', 0, 'string'), # Single-quoted string, possibly containing escape sequences (r"'[^'\\]*(\\.[^'\\]*)*'", 0, 'string'), # From '#' until a newline (r'#[^\n]*', 0, 'comment'), ] self.rules = rules self.searchText = None @property def styles(self): app = QtWidgets.QApplication.instance() return DARK_STYLES if app.property('darkMode') else LIGHT_STYLES def highlightBlock(self, text): """Apply syntax highlighting to the given block of text. """ # Do other syntax formatting rules = self.rules.copy() for expression, nth, format in rules: format = self.styles[format] for n, match in enumerate(re.finditer(expression, text)): if n < nth: continue start = match.start() length = match.end() - start self.setFormat(start, length, format) self.applySearchHighlight(text) self.setCurrentBlockState(0) # Do multi-line strings in_multiline = self.match_multiline(text, *self.tri_single) if not in_multiline: in_multiline = self.match_multiline(text, *self.tri_double) def match_multiline(self, text, delimiter, in_state, style): """Do highlighting of multi-line strings. =========== ========================================================== delimiter (QRegularExpression) for triple-single-quotes or triple-double-quotes in_state (int) to represent the corresponding state changes when inside those strings. Returns True if we're still inside a multi-line string when this function is finished. style (str) representation of the kind of style to use =========== ========================================================== """ # If inside triple-single quotes, start at 0 if self.previousBlockState() == in_state: start = 0 add = 0 # Otherwise, look for the delimiter on this line else: match = delimiter.match(text) start = match.capturedStart() # Move past this match add = match.capturedLength() # As long as there's a delimiter match on this line... while start >= 0: # Look for the ending delimiter match = delimiter.match(text, start + add) end = match.capturedEnd() # Ending delimiter on this line? if end >= add: length = end - start + add + match.capturedLength() self.setCurrentBlockState(0) # No; multi-line string else: self.setCurrentBlockState(in_state) length = len(text) - start + add # Apply formatting self.setFormat(start, length, self.styles[style]) # Highlighting sits on top of this formatting # Look for the next match match = delimiter.match(text, start + length) start = match.capturedStart() self.applySearchHighlight(text) # Return True if still inside a multi-line string, False otherwise if self.currentBlockState() == in_state: return True else: return False def applySearchHighlight(self, text): if not self.searchText: return expr = f'(?i){self.searchText}' palette: QtGui.QPalette = app.palette() color = palette.highlight().color() fgndColor = palette.color(palette.ColorGroup.Current, palette.ColorRole.Text).name() style = charFormat(fgndColor, background=color.name()) for match in re.finditer(expr, text): start = match.start() length = match.end() - start self.setFormat(start, length, style) def unnestedDict(exDict): """Converts a dict-of-dicts to a singly nested dict for non-recursive parsing""" out = {} for kk, vv in exDict.items(): if isinstance(vv, dict): out.update(unnestedDict(vv)) else: out[kk] = vv return out class ExampleLoader(QtWidgets.QMainWindow): def __init__(self): QtWidgets.QMainWindow.__init__(self) self.ui = ui_template.Ui_Form() self.cw = QtWidgets.QWidget() self.setCentralWidget(self.cw) self.ui.setupUi(self.cw) self.setWindowTitle("PyQtGraph Examples") self.codeBtn = QtWidgets.QPushButton('Run Edited Code') self.codeLayout = QtWidgets.QGridLayout() self.ui.codeView.setLayout(self.codeLayout) self.hl = PythonHighlighter(self.ui.codeView.document()) app = QtWidgets.QApplication.instance() app.paletteChanged.connect(self.updateTheme) policy = QtWidgets.QSizePolicy.Policy.Expanding self.codeLayout.addItem(QtWidgets.QSpacerItem(100,100, policy, policy), 0, 0) self.codeLayout.addWidget(self.codeBtn, 1, 1) self.codeBtn.hide() textFil = self.ui.exampleFilter self.curListener = None self.ui.exampleFilter.setFocus() def onComboChanged(searchType): if self.curListener is not None: self.curListener.disconnect() self.curListener = textFil.textChanged if searchType == 'Content Search': self.curListener.connect(self.filterByContent) else: self.hl.searchText = None self.curListener.connect(self.filterByTitle) # Fire on current text, too self.curListener.emit(textFil.text()) self.ui.searchFiles.currentTextChanged.connect(onComboChanged) onComboChanged(self.ui.searchFiles.currentText()) self.itemCache = [] self.populateTree(self.ui.exampleTree.invisibleRootItem(), utils.examples_) self.ui.exampleTree.expandAll() self.resize(1000,500) self.show() self.ui.splitter.setSizes([250,750]) self.oldText = self.ui.codeView.toPlainText() self.ui.loadBtn.clicked.connect(self.loadFile) self.ui.exampleTree.currentItemChanged.connect(self.showFile) self.ui.exampleTree.itemDoubleClicked.connect(self.loadFile) self.ui.codeView.textChanged.connect(self.onTextChange) self.codeBtn.clicked.connect(self.runEditedCode) def onTextChange(self): """ textChanged fires when the highlighter is reassigned the same document. Prevent this from showing "run edited code" by checking for actual content change """ newText = self.ui.codeView.toPlainText() if newText != self.oldText: self.oldText = newText self.codeEdited() def filterByTitle(self, text): self.showExamplesByTitle(self.getMatchingTitles(text)) self.hl.setDocument(self.ui.codeView.document()) def filterByContent(self, text=None): # Don't filter very short strings checkDict = unnestedDict(utils.examples_) self.hl.searchText = text # Need to reapply to current document self.hl.setDocument(self.ui.codeView.document()) titles = [] text = text.lower() for kk, vv in checkDict.items(): if isinstance(vv, Namespace): vv = vv.filename filename = os.path.join(path, vv) contents = self.getExampleContent(filename).lower() if text in contents: titles.append(kk) self.showExamplesByTitle(titles) def getMatchingTitles(self, text, exDict=None, acceptAll=False): if exDict is None: exDict = utils.examples_ text = text.lower() titles = [] for kk, vv in exDict.items(): matched = acceptAll or text in kk.lower() if isinstance(vv, dict): titles.extend(self.getMatchingTitles(text, vv, acceptAll=matched)) elif matched: titles.append(kk) return titles def showExamplesByTitle(self, titles): QTWI = QtWidgets.QTreeWidgetItemIterator flag = QTWI.IteratorFlag.NoChildren treeIter = QTWI(self.ui.exampleTree, flag) item = treeIter.value() while item is not None: parent = item.parent() show = (item.childCount() or item.text(0) in titles) item.setHidden(not show) # If all children of a parent are gone, hide it if parent: hideParent = True for ii in range(parent.childCount()): if not parent.child(ii).isHidden(): hideParent = False break parent.setHidden(hideParent) treeIter += 1 item = treeIter.value() def simulate_black_mode(self): """ used to simulate MacOS "black mode" on other platforms intended for debug only, as it manage only the QPlainTextEdit """ # first, a dark background c = QtGui.QColor('#171717') p = self.ui.codeView.palette() p.setColor(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Base, c) p.setColor(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Base, c) self.ui.codeView.setPalette(p) # then, a light font f = QtGui.QTextCharFormat() f.setForeground(QtGui.QColor('white')) self.ui.codeView.setCurrentCharFormat(f) # finally, override application automatic detection app = QtWidgets.QApplication.instance() app.setProperty('darkMode', True) def updateTheme(self): self.hl = PythonHighlighter(self.ui.codeView.document()) def populateTree(self, root, examples): bold_font = None for key, val in examples.items(): item = QtWidgets.QTreeWidgetItem([key]) self.itemCache.append(item) # PyQt 4.9.6 no longer keeps references to these wrappers, # so we need to make an explicit reference or else the .file # attribute will disappear. if isinstance(val, OrderedDict): self.populateTree(item, val) elif isinstance(val, Namespace): item.file = val.filename if 'recommended' in val: if bold_font is None: bold_font = item.font(0) bold_font.setBold(True) item.setFont(0, bold_font) else: item.file = val root.addChild(item) def currentFile(self): item = self.ui.exampleTree.currentItem() if hasattr(item, 'file'): return os.path.join(path, item.file) return None def loadFile(self, edited=False): qtLib = str(self.ui.qtLibCombo.currentText()) env = None if qtLib != 'default': env = dict(os.environ, PYQTGRAPH_QT_LIB=qtLib) else: env = dict(os.environ) example_path = os.path.abspath(os.path.dirname(__file__)) path = os.path.dirname(os.path.dirname(example_path)) env['PYTHONPATH'] = f'{path}' if edited: proc = subprocess.Popen([sys.executable, '-'], stdin=subprocess.PIPE, cwd=example_path, env=env) code = self.ui.codeView.toPlainText().encode('UTF-8') proc.stdin.write(code) proc.stdin.close() else: fn = self.currentFile() if fn is None: return subprocess.Popen([sys.executable, fn], cwd=path, env=env) def showFile(self): fn = self.currentFile() text = self.getExampleContent(fn) self.ui.codeView.setPlainText(text) self.ui.loadedFileLabel.setText(fn) self.codeBtn.hide() @lru_cache(100) def getExampleContent(self, filename): if filename is None: self.ui.codeView.clear() return if os.path.isdir(filename): filename = os.path.join(filename, '__main__.py') with open(filename, "r") as currentFile: text = currentFile.read() return text def codeEdited(self): self.codeBtn.show() def runEditedCode(self): self.loadFile(edited=True) def keyPressEvent(self, event): ret = super().keyPressEvent(event) if not QtCore.Qt.KeyboardModifier.ControlModifier & event.modifiers(): return ret key = event.key() Key = QtCore.Qt.Key # Allow quick navigate to search if key == Key.Key_F: self.ui.exampleFilter.setFocus() event.accept() return if key not in [Key.Key_Plus, Key.Key_Minus, Key.Key_Underscore, Key.Key_Equal, Key.Key_0]: return ret font = self.ui.codeView.font() oldSize = font.pointSize() if key == Key.Key_Plus or key == Key.Key_Equal: font.setPointSize(oldSize + max(oldSize*.15, 1)) elif key == Key.Key_Minus or key == Key.Key_Underscore: newSize = oldSize - max(oldSize*.15, 1) font.setPointSize(max(newSize, 1)) elif key == Key.Key_0: # Reset to original size font.setPointSize(10) self.ui.codeView.setFont(font) event.accept() def main(): app = pg.mkQApp() loader = ExampleLoader() loader.ui.exampleTree.setCurrentIndex( loader.ui.exampleTree.model().index(0,0) ) pg.exec() if __name__ == '__main__': main() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/FillBetweenItem.py000066400000000000000000000023161421045507400250640ustar00rootroot00000000000000""" Demonstrates use of FillBetweenItem to fill the space between two plot curves. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore #FIXME: When running on Qt5, not as perfect as on Qt4 win = pg.plot() win.setWindowTitle('pyqtgraph example: FillBetweenItem') win.setXRange(-10, 10) win.setYRange(-10, 10) N = 200 x = np.linspace(-10, 10, N) gauss = np.exp(-x**2 / 20.) mn = mx = np.zeros(len(x)) curves = [win.plot(x=x, y=np.zeros(len(x)), pen='k') for i in range(4)] brushes = [0.5, (100, 100, 255), 0.5] fills = [pg.FillBetweenItem(curves[i], curves[i+1], brushes[i]) for i in range(3)] for f in fills: win.addItem(f) def update(): global mx, mn, curves, gauss, x a = 5 / abs(np.random.normal(loc=1, scale=0.2)) y1 = -np.abs(a*gauss + np.random.normal(size=len(x))) y2 = np.abs(a*gauss + np.random.normal(size=len(x))) s = 0.01 mn = np.where(y1mx, y2, mx) * (1-s) + y2 * s curves[0].setData(x, mn) curves[1].setData(x, y1) curves[2].setData(x, y2) curves[3].setData(x, mx) timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(30) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/Flowchart.py000066400000000000000000000043361421045507400240020ustar00rootroot00000000000000""" This example demonstrates a very basic use of flowcharts: filter data, displaying both the input and output of the filter. The behavior of the filter can be reprogrammed by the user. Basic steps are: - create a flowchart and two plots - input noisy data to the flowchart - flowchart connects data to the first plot, where it is displayed - add a gaussian filter to lowpass the data, then display it in the second plot. """ import numpy as np import pyqtgraph as pg import pyqtgraph.metaarray as metaarray from pyqtgraph.flowchart import Flowchart from pyqtgraph.Qt import QtWidgets app = pg.mkQApp("Flowchart Example") ## Create main window with grid layout win = QtWidgets.QMainWindow() win.setWindowTitle('pyqtgraph example: Flowchart') cw = QtWidgets.QWidget() win.setCentralWidget(cw) layout = QtWidgets.QGridLayout() cw.setLayout(layout) ## Create flowchart, define input/output terminals fc = Flowchart(terminals={ 'dataIn': {'io': 'in'}, 'dataOut': {'io': 'out'} }) w = fc.widget() ## Add flowchart control panel to the main window layout.addWidget(fc.widget(), 0, 0, 2, 1) ## Add two plot widgets pw1 = pg.PlotWidget() pw2 = pg.PlotWidget() layout.addWidget(pw1, 0, 1) layout.addWidget(pw2, 1, 1) win.show() ## generate signal data to pass through the flowchart data = np.random.normal(size=1000) data[200:300] += 1 data += np.sin(np.linspace(0, 100, 1000)) data = metaarray.MetaArray(data, info=[{'name': 'Time', 'values': np.linspace(0, 1.0, len(data))}, {}]) ## Feed data into the input terminal of the flowchart fc.setInput(dataIn=data) ## populate the flowchart with a basic set of processing nodes. ## (usually we let the user do this) plotList = {'Top Plot': pw1, 'Bottom Plot': pw2} pw1Node = fc.createNode('PlotWidget', pos=(0, -150)) pw1Node.setPlotList(plotList) pw1Node.setPlot(pw1) pw2Node = fc.createNode('PlotWidget', pos=(150, -150)) pw2Node.setPlot(pw2) pw2Node.setPlotList(plotList) fNode = fc.createNode('GaussianFilter', pos=(0, 0)) fNode.ctrls['sigma'].setValue(5) fc.connectTerminals(fc['dataIn'], fNode['In']) fc.connectTerminals(fc['dataIn'], pw1Node['In']) fc.connectTerminals(fNode['Out'], pw2Node['In']) fc.connectTerminals(fNode['Out'], fc['dataOut']) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/FlowchartCustomNode.py000066400000000000000000000130551421045507400260010ustar00rootroot00000000000000""" This example demonstrates writing a custom Node subclass for use with flowcharts. We implement a couple of simple image processing nodes. """ import numpy as np import pyqtgraph as pg import pyqtgraph.flowchart.library as fclib from pyqtgraph.flowchart import Flowchart, Node from pyqtgraph.flowchart.library.common import CtrlNode from pyqtgraph.Qt import QtWidgets app = pg.mkQApp("Flowchart Custom Node Example") ## Create main window with a grid layout inside win = QtWidgets.QMainWindow() win.setWindowTitle('pyqtgraph example: FlowchartCustomNode') cw = QtWidgets.QWidget() win.setCentralWidget(cw) layout = QtWidgets.QGridLayout() cw.setLayout(layout) ## Create an empty flowchart with a single input and output fc = Flowchart(terminals={ 'dataIn': {'io': 'in'}, 'dataOut': {'io': 'out'} }) w = fc.widget() layout.addWidget(fc.widget(), 0, 0, 2, 1) ## Create two ImageView widgets to display the raw and processed data with contrast ## and color control. v1 = pg.ImageView() v2 = pg.ImageView() layout.addWidget(v1, 0, 1) layout.addWidget(v2, 1, 1) win.show() ## generate random input data data = np.random.normal(size=(100,100)) data = 25 * pg.gaussianFilter(data, (5,5)) data += np.random.normal(size=(100,100)) data[40:60, 40:60] += 15.0 data[30:50, 30:50] += 15.0 #data += np.sin(np.linspace(0, 100, 1000)) #data = metaarray.MetaArray(data, info=[{'name': 'Time', 'values': np.linspace(0, 1.0, len(data))}, {}]) ## Set the raw data as the input value to the flowchart fc.setInput(dataIn=data) ## At this point, we need some custom Node classes since those provided in the library ## are not sufficient. Each node will define a set of input/output terminals, a ## processing function, and optionally a control widget (to be displayed in the ## flowchart control panel) class ImageViewNode(Node): """Node that displays image data in an ImageView widget""" nodeName = 'ImageView' def __init__(self, name): self.view = None ## Initialize node with only a single input terminal Node.__init__(self, name, terminals={'data': {'io':'in'}}) def setView(self, view): ## setView must be called by the program self.view = view def process(self, data, display=True): ## if process is called with display=False, then the flowchart is being operated ## in batch processing mode, so we should skip displaying to improve performance. if display and self.view is not None: ## the 'data' argument is the value given to the 'data' terminal if data is None: self.view.setImage(np.zeros((1,1))) # give a blank array to clear the view else: self.view.setImage(data) ## We will define an unsharp masking filter node as a subclass of CtrlNode. ## CtrlNode is just a convenience class that automatically creates its ## control widget based on a simple data structure. class UnsharpMaskNode(CtrlNode): """Return the input data passed through an unsharp mask.""" nodeName = "UnsharpMask" uiTemplate = [ ('sigma', 'spin', {'value': 1.0, 'step': 1.0, 'bounds': [0.0, None]}), ('strength', 'spin', {'value': 1.0, 'dec': True, 'step': 0.5, 'minStep': 0.01, 'bounds': [0.0, None]}), ] def __init__(self, name): ## Define the input / output terminals available on this node terminals = { 'dataIn': dict(io='in'), # each terminal needs at least a name and 'dataOut': dict(io='out'), # to specify whether it is input or output } # other more advanced options are available # as well.. CtrlNode.__init__(self, name, terminals=terminals) def process(self, dataIn, display=True): # CtrlNode has created self.ctrls, which is a dict containing {ctrlName: widget} sigma = self.ctrls['sigma'].value() strength = self.ctrls['strength'].value() output = dataIn - (strength * pg.gaussianFilter(dataIn, (sigma,sigma))) return {'dataOut': output} ## To make our custom node classes available in the flowchart context menu, ## we can either register them with the default node library or make a ## new library. ## Method 1: Register to global default library: #fclib.registerNodeType(ImageViewNode, [('Display',)]) #fclib.registerNodeType(UnsharpMaskNode, [('Image',)]) ## Method 2: If we want to make our custom node available only to this flowchart, ## then instead of registering the node type globally, we can create a new ## NodeLibrary: library = fclib.LIBRARY.copy() # start with the default node set library.addNodeType(ImageViewNode, [('Display',)]) # Add the unsharp mask node to two locations in the menu to demonstrate # that we can create arbitrary menu structures library.addNodeType(UnsharpMaskNode, [('Image',), ('Submenu_test','submenu2','submenu3')]) fc.setLibrary(library) ## Now we will programmatically add nodes to define the function of the flowchart. ## Normally, the user will do this manually or by loading a pre-generated ## flowchart file. v1Node = fc.createNode('ImageView', pos=(0, -150)) v1Node.setView(v1) v2Node = fc.createNode('ImageView', pos=(150, -150)) v2Node.setView(v2) fNode = fc.createNode('UnsharpMask', pos=(0, 0)) fc.connectTerminals(fc['dataIn'], fNode['dataIn']) fc.connectTerminals(fc['dataIn'], v1Node['data']) fc.connectTerminals(fNode['dataOut'], v2Node['data']) fc.connectTerminals(fNode['dataOut'], fc['dataOut']) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLBarGraphItem.py000066400000000000000000000015071421045507400245760ustar00rootroot00000000000000""" This example demonstrates the use of GLBarGraphItem. """ import numpy as np import pyqtgraph as pg import pyqtgraph.opengl as gl app = pg.mkQApp("GLBarGraphItem Example") w = gl.GLViewWidget() w.show() w.setWindowTitle('pyqtgraph example: GLBarGraphItem') w.setCameraPosition(distance=40) gx = gl.GLGridItem() gx.rotate(90, 0, 1, 0) gx.translate(-10, 0, 10) w.addItem(gx) gy = gl.GLGridItem() gy.rotate(90, 1, 0, 0) gy.translate(0, -10, 10) w.addItem(gy) gz = gl.GLGridItem() gz.translate(0, 0, 0) w.addItem(gz) # regular grid of starting positions pos = np.mgrid[0:10, 0:10, 0:1].reshape(3,10,10).transpose(1,2,0) # fixed widths, random heights size = np.empty((10,10,3)) size[...,0:2] = 0.4 size[...,2] = np.random.normal(size=(10,10)) bg = gl.GLBarGraphItem(pos, size) w.addItem(bg) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLGradientLegendItem.py000066400000000000000000000020321421045507400257560ustar00rootroot00000000000000import numpy import pyqtgraph as pg import pyqtgraph.opengl as gl 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() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLGraphItem.py000066400000000000000000000011211421045507400241410ustar00rootroot00000000000000""" Demonstrates use of GLGraphItem """ import numpy as np import pyqtgraph as pg import pyqtgraph.opengl as gl 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() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLImageItem.py000066400000000000000000000026401421045507400241310ustar00rootroot00000000000000""" Use GLImageItem to display image data on rectangular planes. In this example, the image data is sampled from a volume and the image planes placed as if they slice through the volume. """ import numpy as np import pyqtgraph as pg import pyqtgraph.opengl as gl app = pg.mkQApp("GLImageItem Example") w = gl.GLViewWidget() w.show() w.setWindowTitle('pyqtgraph example: GLImageItem') w.setCameraPosition(distance=200) ## create volume data set to slice three images from shape = (100,100,70) data = pg.gaussianFilter(np.random.normal(size=shape), (4,4,4)) data += pg.gaussianFilter(np.random.normal(size=shape), (15,15,15))*15 ## slice out three planes, convert to RGBA for OpenGL texture levels = (-0.08, 0.08) tex1 = pg.makeRGBA(data[shape[0]//2], levels=levels)[0] # yz plane tex2 = pg.makeRGBA(data[:,shape[1]//2], levels=levels)[0] # xz plane tex3 = pg.makeRGBA(data[:,:,shape[2]//2], levels=levels)[0] # xy plane #tex1[:,:,3] = 128 #tex2[:,:,3] = 128 #tex3[:,:,3] = 128 ## Create three image items from textures, add to view v1 = gl.GLImageItem(tex1) v1.translate(-shape[1]/2, -shape[2]/2, 0) v1.rotate(90, 0,0,1) v1.rotate(-90, 0,1,0) w.addItem(v1) v2 = gl.GLImageItem(tex2) v2.translate(-shape[0]/2, -shape[2]/2, 0) v2.rotate(-90, 1,0,0) w.addItem(v2) v3 = gl.GLImageItem(tex3) v3.translate(-shape[0]/2, -shape[1]/2, 0) w.addItem(v3) ax = gl.GLAxisItem() w.addItem(ax) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLIsosurface.py000066400000000000000000000026731421045507400244010ustar00rootroot00000000000000""" This example uses the isosurface function to convert a scalar field (a hydrogen orbital) into a mesh for 3D display. """ import numpy as np import pyqtgraph as pg import pyqtgraph.opengl as gl app = pg.mkQApp("GLIsosurface Example") w = gl.GLViewWidget() w.show() w.setWindowTitle('pyqtgraph example: GLIsosurface') w.setCameraPosition(distance=40) g = gl.GLGridItem() g.scale(2,2,1) w.addItem(g) ## Define a scalar field from which we will generate an isosurface def psi(i, j, k, offset=(25, 25, 50)): x = i-offset[0] y = j-offset[1] z = k-offset[2] th = np.arctan2(z, np.hypot(x, y)) r = np.sqrt(x**2 + y**2 + z **2) a0 = 1 ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1) return ps print("Generating scalar field..") data = np.abs(np.fromfunction(psi, (50,50,100))) print("Generating isosurface..") verts, faces = pg.isosurface(data, data.max()/4.) md = gl.MeshData(vertexes=verts, faces=faces) colors = np.ones((md.faceCount(), 4), dtype=float) colors[:,3] = 0.2 colors[:,2] = np.linspace(0, 1, colors.shape[0]) md.setFaceColors(colors) m1 = gl.GLMeshItem(meshdata=md, smooth=False, shader='balloon') m1.setGLOptions('additive') #w.addItem(m1) m1.translate(-25, -25, -20) m2 = gl.GLMeshItem(meshdata=md, smooth=True, shader='balloon') m2.setGLOptions('additive') w.addItem(m2) m2.translate(-25, -25, -50) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLLinePlotItem.py000066400000000000000000000016071421045507400246370ustar00rootroot00000000000000""" Demonstrate use of GLLinePlotItem to draw cross-sections of a surface. """ import numpy as np import pyqtgraph as pg import pyqtgraph.opengl as gl app = pg.mkQApp("GLLinePlotItem Example") w = gl.GLViewWidget() w.show() w.setWindowTitle('pyqtgraph example: GLLinePlotItem') w.setCameraPosition(distance=40) gx = gl.GLGridItem() gx.rotate(90, 0, 1, 0) gx.translate(-10, 0, 0) w.addItem(gx) gy = gl.GLGridItem() gy.rotate(90, 1, 0, 0) gy.translate(0, -10, 0) w.addItem(gy) gz = gl.GLGridItem() gz.translate(0, 0, -10) w.addItem(gz) n = 51 y = np.linspace(-10,10,n) x = np.linspace(-10,10,100) for i in range(n): yi = y[i] 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.mkColor((i,n*1.3)), width=(i+1)/10., antialias=True) w.addItem(plt) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLMeshItem.py000066400000000000000000000056301421045507400240050ustar00rootroot00000000000000""" Simple examples demonstrating the use of GLMeshItem. """ import pyqtgraph as pg import pyqtgraph.opengl as gl app = pg.mkQApp("GLMeshItem Example") w = gl.GLViewWidget() w.show() w.setWindowTitle('pyqtgraph example: GLMeshItem') w.setCameraPosition(distance=40) g = gl.GLGridItem() g.scale(2,2,1) w.addItem(g) import numpy as np ## Example 1: ## Array of vertex positions and array of vertex indexes defining faces ## Colors are specified per-face verts = np.array([ [0, 0, 0], [2, 0, 0], [1, 2, 0], [1, 1, 1], ]) faces = np.array([ [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3] ]) colors = np.array([ [1, 0, 0, 0.3], [0, 1, 0, 0.3], [0, 0, 1, 0.3], [1, 1, 0, 0.3] ]) ## Mesh item will automatically compute face normals. m1 = gl.GLMeshItem(vertexes=verts, faces=faces, faceColors=colors, smooth=False) m1.translate(5, 5, 0) m1.setGLOptions('additive') w.addItem(m1) ## Example 2: ## Array of vertex positions, three per face verts = np.empty((36, 3, 3), dtype=np.float32) theta = np.linspace(0, 2*np.pi, 37)[:-1] verts[:,0] = np.vstack([2*np.cos(theta), 2*np.sin(theta), [0]*36]).T verts[:,1] = np.vstack([4*np.cos(theta+0.2), 4*np.sin(theta+0.2), [-1]*36]).T verts[:,2] = np.vstack([4*np.cos(theta-0.2), 4*np.sin(theta-0.2), [1]*36]).T ## Colors are specified per-vertex colors = np.random.random(size=(verts.shape[0], 3, 4)) m2 = gl.GLMeshItem(vertexes=verts, vertexColors=colors, smooth=False, shader='balloon', drawEdges=True, edgeColor=(1, 1, 0, 1)) m2.translate(-5, 5, 0) w.addItem(m2) ## Example 3: ## sphere md = gl.MeshData.sphere(rows=10, cols=20) #colors = np.random.random(size=(md.faceCount(), 4)) #colors[:,3] = 0.3 #colors[100:] = 0.0 colors = np.ones((md.faceCount(), 4), dtype=float) colors[::2,0] = 0 colors[:,1] = np.linspace(0, 1, colors.shape[0]) md.setFaceColors(colors) m3 = gl.GLMeshItem(meshdata=md, smooth=False)#, shader='balloon') m3.translate(5, -5, 0) w.addItem(m3) # Example 4: # wireframe md = gl.MeshData.sphere(rows=4, cols=8) m4 = gl.GLMeshItem(meshdata=md, smooth=False, drawFaces=False, drawEdges=True, edgeColor=(1,1,1,1)) m4.translate(0,10,0) w.addItem(m4) # Example 5: # cylinder md = gl.MeshData.cylinder(rows=10, cols=20, radius=[1., 2.0], length=5.) md2 = gl.MeshData.cylinder(rows=10, cols=20, radius=[2., 0.5], length=10.) colors = np.ones((md.faceCount(), 4), dtype=float) colors[::2,0] = 0 colors[:,1] = np.linspace(0, 1, colors.shape[0]) md.setFaceColors(colors) m5 = gl.GLMeshItem(meshdata=md, smooth=True, drawEdges=True, edgeColor=(1,0,0,1), shader='balloon') colors = np.ones((md.faceCount(), 4), dtype=float) colors[::2,0] = 0 colors[:,1] = np.linspace(0, 1, colors.shape[0]) md2.setFaceColors(colors) m6 = gl.GLMeshItem(meshdata=md2, smooth=True, drawEdges=False, shader='balloon') m6.translate(0,0,7.5) m6.rotate(0., 0, 1, 1) #m5.translate(-3,3,0) w.addItem(m5) w.addItem(m6) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLPainterItem.py000066400000000000000000000057161421045507400245200ustar00rootroot00000000000000""" Demonstrate using QPainter on a subclass of GLGraphicsItem. """ import OpenGL.GL as GL import pyqtgraph as pg from pyqtgraph.opengl import GLAxisItem, GLGraphicsItem, GLGridItem, GLViewWidget from pyqtgraph.Qt import QtCore, QtGui SIZE = 32 class GLPainterItem(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 = GLViewWidget() glv.show() glv.setWindowTitle('pyqtgraph example: GLPainterItem') glv.setCameraPosition(distance=50, elevation=90, azimuth=0) griditem = GLGridItem() griditem.setSize(SIZE, SIZE) griditem.setSpacing(1, 1) glv.addItem(griditem) axisitem = GLAxisItem() axisitem.setSize(SIZE/2, SIZE/2, 1) glv.addItem(axisitem) paintitem = GLPainterItem() glv.addItem(paintitem) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLScatterPlotItem.py000066400000000000000000000051731421045507400253570ustar00rootroot00000000000000""" Demonstrates use of GLScatterPlotItem with rapidly-updating plots. """ import numpy as np import pyqtgraph as pg import pyqtgraph.opengl as gl from pyqtgraph import functions as fn from pyqtgraph.Qt import QtCore app = pg.mkQApp("GLScatterPlotItem Example") w = gl.GLViewWidget() w.show() w.setWindowTitle('pyqtgraph example: GLScatterPlotItem') w.setCameraPosition(distance=20) g = gl.GLGridItem() w.addItem(g) ## ## First example is a set of points with pxMode=False ## These demonstrate the ability to have points with real size down to a very small scale ## pos = np.empty((53, 3)) size = np.empty((53)) color = np.empty((53, 4)) pos[0] = (1,0,0); size[0] = 0.5; color[0] = (1.0, 0.0, 0.0, 0.5) pos[1] = (0,1,0); size[1] = 0.2; color[1] = (0.0, 0.0, 1.0, 0.5) pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5) z = 0.5 d = 6.0 for i in range(3,53): pos[i] = (0,0,z) size[i] = 2./d color[i] = (0.0, 1.0, 0.0, 0.5) z *= 0.5 d *= 2.0 sp1 = gl.GLScatterPlotItem(pos=pos, size=size, color=color, pxMode=False) sp1.translate(5,5,0) w.addItem(sp1) ## ## Second example shows a volume of points with rapidly updating color ## and pxMode=True ## pos = np.random.random(size=(100000,3)) pos *= [10,-10,10] pos[0] = (0,0,0) color = np.ones((pos.shape[0], 4)) d2 = (pos**2).sum(axis=1)**0.5 size = np.random.random(size=pos.shape[0])*10 sp2 = gl.GLScatterPlotItem(pos=pos, color=(1,1,1,1), size=size) phase = 0. w.addItem(sp2) ## ## Third example shows a grid of points with rapidly updating position ## and pxMode = False ## pos3 = np.zeros((100,100,3)) pos3[:,:,:2] = np.mgrid[:100, :100].transpose(1,2,0) * [-0.1,0.1] pos3 = pos3.reshape(10000,3) d3 = (pos3**2).sum(axis=1)**0.5 sp3 = gl.GLScatterPlotItem(pos=pos3, color=(1,1,1,.3), size=0.1, pxMode=False) w.addItem(sp3) def update(): ## update volume colors global phase, sp2, d2 s = -np.cos(d2*2+phase) color = np.empty((len(d2),4), dtype=np.float32) color[:,3] = fn.clip_array(s * 0.1, 0., 1.) color[:,0] = fn.clip_array(s * 3.0, 0., 1.) color[:,1] = fn.clip_array(s * 1.0, 0., 1.) color[:,2] = fn.clip_array(s ** 3, 0., 1.) sp2.setData(color=color) phase -= 0.1 ## update surface positions and colors global sp3, d3, pos3 z = -np.cos(d3*2+phase) pos3[:,2] = z color = np.empty((len(d3),4), dtype=np.float32) color[:,3] = 0.3 color[:,0] = np.clip(z * 3.0, 0, 1) color[:,1] = np.clip(z * 1.0, 0, 1) color[:,2] = np.clip(z ** 3, 0, 1) sp3.setData(pos=pos3, color=color) t = QtCore.QTimer() t.timeout.connect(update) t.start(50) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLSurfacePlot.py000066400000000000000000000050241421045507400245160ustar00rootroot00000000000000""" This example demonstrates the use of GLSurfacePlotItem. """ import numpy as np import pyqtgraph as pg import pyqtgraph.opengl as gl from pyqtgraph.Qt import QtCore ## Create a GL View widget to display data app = pg.mkQApp("GLSurfacePlot Example") w = gl.GLViewWidget() w.show() w.setWindowTitle('pyqtgraph example: GLSurfacePlot') w.setCameraPosition(distance=50) ## Add a grid to the view g = gl.GLGridItem() g.scale(2,2,1) g.setDepthValue(10) # draw grid after surfaces since they may be translucent w.addItem(g) ## Simple surface plot example ## x, y values are not specified, so assumed to be 0:50 z = pg.gaussianFilter(np.random.normal(size=(50,50)), (1,1)) p1 = gl.GLSurfacePlotItem(z=z, shader='shaded', color=(0.5, 0.5, 1, 1)) p1.scale(16./49., 16./49., 1.0) p1.translate(-18, 2, 0) w.addItem(p1) ## Saddle example with x and y specified x = np.linspace(-8, 8, 50) y = np.linspace(-8, 8, 50) z = 0.1 * ((x.reshape(50,1) ** 2) - (y.reshape(1,50) ** 2)) p2 = gl.GLSurfacePlotItem(x=x, y=y, z=z, shader='normalColor') p2.translate(-10,-10,0) w.addItem(p2) ## Manually specified colors z = pg.gaussianFilter(np.random.normal(size=(50,50)), (1,1)) x = np.linspace(-12, 12, 50) y = np.linspace(-12, 12, 50) colors = np.ones((50,50,4), dtype=float) colors[...,0] = np.clip(np.cos(((x.reshape(50,1) ** 2) + (y.reshape(1,50) ** 2)) ** 0.5), 0, 1) colors[...,1] = colors[...,0] p3 = gl.GLSurfacePlotItem(z=z, colors=colors.reshape(50*50,4), shader='shaded', smooth=False) p3.scale(16./49., 16./49., 1.0) p3.translate(2, -18, 0) w.addItem(p3) ## Animated example ## compute surface vertex data cols = 90 rows = 100 x = np.linspace(-8, 8, cols+1).reshape(cols+1,1) y = np.linspace(-8, 8, rows+1).reshape(1,rows+1) d = (x**2 + y**2) * 0.1 d2 = d ** 0.5 + 0.1 ## precompute height values for all frames phi = np.arange(0, np.pi*2, np.pi/20.) z = np.sin(d[np.newaxis,...] + phi.reshape(phi.shape[0], 1, 1)) / d2[np.newaxis,...] ## create a surface plot, tell it to use the 'heightColor' shader ## since this does not require normal vectors to render (thus we ## can set computeNormals=False to save time when the mesh updates) p4 = gl.GLSurfacePlotItem(x=x[:,0], y = y[0,:], shader='heightColor', computeNormals=False, smooth=False) p4.shader()['colorMap'] = np.array([0.2, 2, 0.5, 0.2, 1, 1, 0.2, 0, 2]) p4.translate(10, 10, 0) w.addItem(p4) index = 0 def update(): global p4, z, index index -= 1 p4.setData(z=z[index%z.shape[0]]) timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(30) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLTextItem.py000066400000000000000000000012451421045507400240330ustar00rootroot00000000000000""" Simple examples demonstrating the use of GLTextItem. """ import pyqtgraph as pg import pyqtgraph.opengl as gl from pyqtgraph.Qt import mkQApp app = mkQApp("GLTextItem Example") gvw = gl.GLViewWidget() gvw.show() gvw.setWindowTitle('pyqtgraph example: GLTextItem') griditem = gl.GLGridItem() griditem.setSize(10, 10) griditem.setSpacing(1, 1) gvw.addItem(griditem) axisitem = gl.GLAxisItem() gvw.addItem(axisitem) txtitem1 = gl.GLTextItem(pos=(0.0, 0.0, 0.0), text='text1') gvw.addItem(txtitem1) txtitem2 = gl.GLTextItem() txtitem2.setData(pos=(1.0, -1.0, 2.0), color=(127, 255, 127, 255), text='text2') gvw.addItem(txtitem2) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLViewWidget.py000066400000000000000000000007411421045507400243460ustar00rootroot00000000000000""" Very basic 3D graphics example; create a view widget and add a few items. """ import pyqtgraph as pg import pyqtgraph.opengl as gl pg.mkQApp("GLViewWidget Example") w = gl.GLViewWidget() w.show() w.setWindowTitle('pyqtgraph example: GLViewWidget') w.setCameraPosition(distance=20) ax = gl.GLAxisItem() ax.setSize(5,5,5) w.addItem(ax) b = gl.GLBoxItem() w.addItem(b) ax2 = gl.GLAxisItem() ax2.setParentItem(b) b.translate(1,1,1) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLVolumeItem.py000066400000000000000000000042771421045507400243660ustar00rootroot00000000000000""" Demonstrates GLVolumeItem for displaying volumetric data. """ import numpy as np import pyqtgraph as pg import pyqtgraph.opengl as gl from pyqtgraph import functions as fn app = pg.mkQApp("GLVolumeItem Example") w = gl.GLViewWidget() w.show() w.setWindowTitle('pyqtgraph example: GLVolumeItem') w.setCameraPosition(distance=200) g = gl.GLGridItem() g.scale(10, 10, 1) w.addItem(g) ## Hydrogen electron probability density def psi(i, j, k, offset=(50,50,100)): x = i-offset[0] y = j-offset[1] z = k-offset[2] th = np.arctan2(z, np.hypot(x, y)) r = np.sqrt(x**2 + y**2 + z **2) a0 = 2 return ( (1.0 / 81.0) * 1.0 / (6.0 * np.pi) ** 0.5 * (1.0 / a0) ** (3 / 2) * (r / a0) ** 2 * np.exp(-r / (3 * a0)) * (3 * np.cos(th) ** 2 - 1) ) data = np.fromfunction(psi, (100,100,200)) with np.errstate(divide = 'ignore'): positive = np.log(fn.clip_array(data, 0, data.max())**2) negative = np.log(fn.clip_array(-data, 0, -data.min())**2) d2 = np.empty(data.shape + (4,), dtype=np.ubyte) # 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 d2[:, 0, 0] = [255,0,0,100] d2[0, :, 0] = [0,255,0,100] d2[0, 0, :] = [0,0,255,100] v = gl.GLVolumeItem(d2) v.translate(-50,-50,-100) w.addItem(v) ax = gl.GLAxisItem() w.addItem(ax) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GLshaders.py000066400000000000000000000053141421045507400237220ustar00rootroot00000000000000""" Demonstration of some of the shader programs included with pyqtgraph that can be used to affect the appearance of a surface. """ import numpy as np import pyqtgraph as pg import pyqtgraph.opengl as gl app = pg.mkQApp("GLShaders Example") w = gl.GLViewWidget() w.show() w.setWindowTitle('pyqtgraph example: GL Shaders') w.setCameraPosition(distance=15, azimuth=-90) g = gl.GLGridItem() g.scale(2,2,1) w.addItem(g) md = gl.MeshData.sphere(rows=10, cols=20) x = np.linspace(-8, 8, 6) m1 = gl.GLMeshItem(meshdata=md, smooth=True, color=(1, 0, 0, 0.2), shader='balloon', glOptions='additive') m1.translate(x[0], 0, 0) m1.scale(1, 1, 2) w.addItem(m1) m2 = gl.GLMeshItem(meshdata=md, smooth=True, shader='normalColor', glOptions='opaque') m2.translate(x[1], 0, 0) m2.scale(1, 1, 2) w.addItem(m2) m3 = gl.GLMeshItem(meshdata=md, smooth=True, shader='viewNormalColor', glOptions='opaque') m3.translate(x[2], 0, 0) m3.scale(1, 1, 2) w.addItem(m3) m4 = gl.GLMeshItem(meshdata=md, smooth=True, shader='shaded', glOptions='opaque') m4.translate(x[3], 0, 0) m4.scale(1, 1, 2) w.addItem(m4) m5 = gl.GLMeshItem(meshdata=md, smooth=True, color=(1, 0, 0, 1), shader='edgeHilight', glOptions='opaque') m5.translate(x[4], 0, 0) m5.scale(1, 1, 2) w.addItem(m5) m6 = gl.GLMeshItem(meshdata=md, smooth=True, color=(1, 0, 0, 1), shader='heightColor', glOptions='opaque') m6.translate(x[5], 0, 0) m6.scale(1, 1, 2) w.addItem(m6) #def psi(i, j, k, offset=(25, 25, 50)): #x = i-offset[0] #y = j-offset[1] #z = k-offset[2] #th = np.arctan2(z, (x**2+y**2)**0.5) #phi = np.arctan2(y, x) #r = (x**2 + y**2 + z **2)**0.5 #a0 = 1 ##ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th) #ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1) #return ps ##return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2 #print("Generating scalar field..") #data = np.abs(np.fromfunction(psi, (50,50,100))) ##data = np.fromfunction(lambda i,j,k: np.sin(0.2*((i-25)**2+(j-15)**2+k**2)**0.5), (50,50,50)); #print("Generating isosurface..") #verts = pg.isosurface(data, data.max()/4.) #md = gl.MeshData.MeshData(vertexes=verts) #colors = np.ones((md.vertexes(indexed='faces').shape[0], 4), dtype=float) #colors[:,3] = 0.3 #colors[:,2] = np.linspace(0, 1, colors.shape[0]) #m1 = gl.GLMeshItem(meshdata=md, color=colors, smooth=False) #w.addItem(m1) #m1.translate(-25, -25, -20) #m2 = gl.GLMeshItem(vertexes=verts, color=colors, smooth=True) #w.addItem(m2) #m2.translate(-25, -25, -50) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GradientEditor.py000066400000000000000000000004641421045507400247530ustar00rootroot00000000000000 import pyqtgraph as pg app = pg.mkQApp("Gradiant Editor Example") mw = pg.GraphicsView() mw.resize(800,800) mw.show() #ts = pg.TickSliderItem() #mw.setCentralItem(ts) #ts.addTick(0.5, 'r') #ts.addTick(0.9, 'b') ge = pg.GradientEditorItem() mw.setCentralItem(ge) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GradientWidget.py000066400000000000000000000024461421045507400247520ustar00rootroot00000000000000""" Demonstrates the appearance / interactivity of GradientWidget (without actually doing anything useful with it) """ import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets app = pg.mkQApp("Gradiant Widget Example") w = QtWidgets.QMainWindow() w.show() w.setWindowTitle('pyqtgraph example: GradientWidget') w.setGeometry(10, 50, 400, 400) cw = QtWidgets.QWidget() w.setCentralWidget(cw) l = QtWidgets.QGridLayout() l.setSpacing(0) cw.setLayout(l) w1 = pg.GradientWidget(orientation='top') w2 = pg.GradientWidget(orientation='right', allowAdd=False) #w2.setTickColor(1, QtGui.QColor(255,255,255)) w3 = pg.GradientWidget(orientation='bottom', allowAdd=False, allowRemove=False) w4 = pg.GradientWidget(orientation='left') w4.loadPreset('spectrum') label = QtWidgets.QLabel(""" - Click a triangle to change its color - Drag triangles to move - Right-click a gradient to load triangle presets - Click in an empty area to add a new color (adding is disabled for the bottom-side and right-side widgets) - Right click a triangle to remove (only possible if more than two triangles are visible) (removing is disabled for the bottom-side widget) """) l.addWidget(w1, 0, 1) l.addWidget(w2, 1, 2) l.addWidget(w3, 2, 1) l.addWidget(w4, 1, 0) l.addWidget(label, 1, 1) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GraphItem.py000066400000000000000000000022041421045507400237210ustar00rootroot00000000000000""" Simple example of GraphItem use. """ import numpy as np import pyqtgraph as pg # Enable antialiasing for prettier plots pg.setConfigOptions(antialias=True) w = pg.GraphicsLayoutWidget(show=True) w.setWindowTitle('pyqtgraph example: GraphItem') v = w.addViewBox() v.setAspectLocked() g = pg.GraphItem() v.addItem(g) ## Define positions of nodes pos = np.array([ [0,0], [10,0], [0,10], [10,10], [5,5], [15,5] ]) ## Define the set of connections in the graph adj = np.array([ [0,1], [1,3], [3,2], [2,0], [1,5], [3,5], ]) ## Define the symbol to use for each node (this is optional) symbols = ['o','o','o','o','t','+'] ## Define the line style for each connection (this is optional) lines = np.array([ (255,0,0,255,1), (255,0,255,255,2), (255,0,255,255,3), (255,255,0,255,2), (255,0,0,255,1), (255,255,255,255,4), ], dtype=[('red',np.ubyte),('green',np.ubyte),('blue',np.ubyte),('alpha',np.ubyte),('width',float)]) ## Update the graph g.setData(pos=pos, adj=adj, pen=lines, size=1, symbol=symbols, pxMode=False) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GraphicsLayout.py000066400000000000000000000037761421045507400250160ustar00rootroot00000000000000""" Demonstrate the use of layouts to control placement of multiple plots / views / labels """ import numpy as np import pyqtgraph as pg app = pg.mkQApp("Gradiant Layout Example") view = pg.GraphicsView() l = pg.GraphicsLayout(border=(100,100,100)) view.setCentralItem(l) view.show() view.setWindowTitle('pyqtgraph example: GraphicsLayout') view.resize(800,600) ## Title at top text = """ This example demonstrates the use of GraphicsLayout to arrange items in a grid.
      The items added to the layout must be subclasses of QGraphicsWidget (this includes
      PlotItem, ViewBox, LabelItem, and GrphicsLayout itself). """ l.addLabel(text, col=1, colspan=4) l.nextRow() ## Put vertical label on left side l.addLabel('Long Vertical Label', angle=-90, rowspan=3) ## Add 3 plots into the first row (automatic position) p1 = l.addPlot(title="Plot 1") p2 = l.addPlot(title="Plot 2") vb = l.addViewBox(lockAspect=True) img = pg.ImageItem(np.random.normal(size=(100,100))) vb.addItem(img) vb.autoRange() ## Add a sub-layout into the second row (automatic position) ## The added item should avoid the first column, which is already filled l.nextRow() l2 = l.addLayout(colspan=3, border=(50,0,0)) l2.setContentsMargins(10, 10, 10, 10) l2.addLabel("Sub-layout: this layout demonstrates the use of shared axes and axis labels", colspan=3) l2.nextRow() l2.addLabel('Vertical Axis Label', angle=-90, rowspan=2) p21 = l2.addPlot() p22 = l2.addPlot() l2.nextRow() p23 = l2.addPlot() p24 = l2.addPlot() l2.nextRow() l2.addLabel("HorizontalAxisLabel", col=1, colspan=2) ## hide axes on some plots p21.hideAxis('bottom') p22.hideAxis('bottom') p22.hideAxis('left') p24.hideAxis('left') p21.hideButtons() p22.hideButtons() p23.hideButtons() p24.hideButtons() ## Add 2 more plots into the third row (manual position) p4 = l.addPlot(row=3, col=1) p5 = l.addPlot(row=3, col=2, colspan=2) ## show some content in the plots p1.plot([1,3,2,4,3,5]) p2.plot([1,3,2,4,3,5]) p4.plot([1,3,2,4,3,5]) p5.plot([1,3,2,4,3,5]) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/GraphicsScene.py000066400000000000000000000023461421045507400245660ustar00rootroot00000000000000import pyqtgraph as pg from pyqtgraph.GraphicsScene import GraphicsScene from pyqtgraph.Qt import QtCore, QtWidgets app = pg.mkQApp("GraphicsScene Example") win = pg.GraphicsView() win.show() class Obj(QtWidgets.QGraphicsObject): def __init__(self): QtWidgets.QGraphicsObject.__init__(self) GraphicsScene.registerObject(self) def paint(self, p, *args): p.setPen(pg.mkPen(200,200,200)) p.drawRect(self.boundingRect()) def boundingRect(self): return QtCore.QRectF(0, 0, 20, 20) def mouseClickEvent(self, ev): if ev.double(): print("double click") else: print("click") ev.accept() #def mouseDragEvent(self, ev): #print "drag" #ev.accept() #self.setPos(self.pos() + ev.pos()-ev.lastPos()) vb = pg.ViewBox() win.setCentralItem(vb) obj = Obj() vb.addItem(obj) obj2 = Obj() win.addItem(obj2) def clicked(): print("button click") btn = QtWidgets.QPushButton("BTN") btn.clicked.connect(clicked) prox = QtWidgets.QGraphicsProxyWidget() prox.setWidget(btn) prox.setPos(100,0) vb.addItem(prox) g = pg.GridItem() vb.addItem(g) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/HistogramLUT.py000066400000000000000000000023631421045507400243710ustar00rootroot00000000000000""" Use a HistogramLUTWidget to control the contrast / coloration of an image. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets app = pg.mkQApp("Histogram Lookup Table Example") win = QtWidgets.QMainWindow() win.resize(880, 600) win.show() win.setWindowTitle('pyqtgraph example: Histogram LUT') cw = QtWidgets.QWidget() win.setCentralWidget(cw) layout = QtWidgets.QGridLayout() cw.setLayout(layout) layout.setSpacing(0) view = pg.GraphicsView() vb = pg.ViewBox() vb.setAspectLocked() view.setCentralItem(vb) layout.addWidget(view, 0, 1, 3, 1) hist = pg.HistogramLUTWidget(gradientPosition="left") layout.addWidget(hist, 0, 2) monoRadio = QtWidgets.QRadioButton('mono') rgbaRadio = QtWidgets.QRadioButton('rgba') layout.addWidget(monoRadio, 1, 2) layout.addWidget(rgbaRadio, 2, 2) monoRadio.setChecked(True) def setLevelMode(): mode = 'mono' if monoRadio.isChecked() else 'rgba' hist.setLevelMode(mode) monoRadio.toggled.connect(setLevelMode) data = pg.gaussianFilter(np.random.normal(size=(256, 256, 3)), (20, 20, 0)) for i in range(32): for j in range(32): data[i*8, j*8] += .1 img = pg.ImageItem(data) vb.addItem(img) vb.autoRange() hist.setImageItem(img) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ImageItem.py000066400000000000000000000025311421045507400237050ustar00rootroot00000000000000""" Demonstrates very basic use of ImageItem to display image data inside a ViewBox. """ from time import perf_counter import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore app = pg.mkQApp("ImageItem Example") ## Create window with GraphicsView widget win = pg.GraphicsLayoutWidget() win.show() ## show widget alone in its own window win.setWindowTitle('pyqtgraph example: ImageItem') view = win.addViewBox() ## lock the aspect ratio so pixels are always square view.setAspectLocked(True) ## Create image item img = pg.ImageItem(border='w') view.addItem(img) ## Set initial view bounds view.setRange(QtCore.QRectF(0, 0, 600, 600)) ## Create random image data = np.random.normal(size=(15, 600, 600), loc=1024, scale=64).astype(np.uint16) i = 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, elapsed ## Display the data img.setImage(data[i]) i = (i+1) % data.shape[0] timer.start(1) now = perf_counter() elapsed_now = now - updateTime updateTime = now elapsed = elapsed * 0.9 + elapsed_now * 0.1 # print(f"{1 / elapsed:.1f} fps") timer.timeout.connect(updateData) updateData() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ImageView.py000066400000000000000000000041021421045507400237150ustar00rootroot00000000000000""" This example demonstrates the use of ImageView with 3-color image stacks. ImageView is a high-level widget for displaying and analyzing 2D and 3D data. ImageView provides: 1. A zoomable region (ViewBox) for displaying the image 2. A combination histogram and gradient editor (HistogramLUTItem) for controlling the visual appearance of the image 3. A timeline for selecting the currently displayed frame (for 3D data only). 4. Tools for very basic analysis of image data (see ROI and Norm buttons) """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets # Interpret image data as row-major instead of col-major pg.setConfigOptions(imageAxisOrder='row-major') app = pg.mkQApp("ImageView Example") ## Create window with ImageView widget win = QtWidgets.QMainWindow() win.resize(800,800) imv = pg.ImageView() win.setCentralWidget(imv) win.show() win.setWindowTitle('pyqtgraph example: ImageView') ## Create random 3D data set with time varying signals dataRed = np.ones((100, 200, 200)) * np.linspace(90, 150, 100)[:, np.newaxis, np.newaxis] dataRed += pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 100 dataGrn = np.ones((100, 200, 200)) * np.linspace(90, 180, 100)[:, np.newaxis, np.newaxis] dataGrn += pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 100 dataBlu = np.ones((100, 200, 200)) * np.linspace(180, 90, 100)[:, np.newaxis, np.newaxis] dataBlu += pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 100 data = np.concatenate( (dataRed[:, :, :, np.newaxis], dataGrn[:, :, :, np.newaxis], dataBlu[:, :, :, np.newaxis]), axis=3 ) ## Display the data and assign each frame a time value from 1.0 to 3.0 imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0])) ## Set a custom color map colors = [ (0, 0, 0), (45, 5, 61), (84, 42, 55), (150, 87, 60), (208, 171, 141), (255, 255, 255) ] cmap = pg.ColorMap(pos=np.linspace(0.0, 1.0, 6), color=colors) imv.setColorMap(cmap) # Start up with an ROI imv.ui.roiBtn.setChecked(True) imv.roiClicked() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/InfiniteLine.py000066400000000000000000000043371421045507400244270ustar00rootroot00000000000000""" This example demonstrates some of the plotting items available in pyqtgraph. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore app = pg.mkQApp("InfiniteLine Example") win = pg.GraphicsLayoutWidget(show=True, title="Plotting items examples") win.resize(1000,600) # Enable antialiasing for prettier plots pg.setConfigOptions(antialias=True) # Create a plot with some random data p1 = win.addPlot(title="Plot Items example", y=np.random.normal(size=100, scale=10), pen=0.5) p1.setYRange(-40, 40) # Add three infinite lines with labels inf1 = pg.InfiniteLine(movable=True, angle=90, label='x={value:0.2f}', labelOpts={'position':0.1, 'color': (200,200,100), 'fill': (200,200,200,50), 'movable': True}) inf2 = pg.InfiniteLine(movable=True, angle=0, pen=(0, 0, 200), bounds = [-20, 20], hoverPen=(0,200,0), label='y={value:0.2f}mm', labelOpts={'color': (200,0,0), 'movable': True, 'fill': (0, 0, 200, 100)}) inf3 = pg.InfiniteLine(movable=True, angle=45, pen='g', label='diagonal', labelOpts={'rotateAxis': [1, 0], 'fill': (0, 200, 0, 100), 'movable': True}) inf1.setPos([2,2]) p1.addItem(inf1) p1.addItem(inf2) p1.addItem(inf3) targetItem1 = pg.TargetItem() targetItem2 = pg.TargetItem( pos=(30, 5), size=20, symbol="star", pen="#F4511E", label="vert={1:0.2f}", labelOpts={ "offset": QtCore.QPoint(15, 15) } ) targetItem2.label().setAngle(45) targetItem3 = pg.TargetItem( pos=(10, 10), size=10, symbol="x", pen="#00ACC1", ) targetItem3.setLabel( "Third Label", { "anchor": QtCore.QPointF(0.5, 0.5), "offset": QtCore.QPointF(30, 0), "color": "#558B2F", "rotateAxis": (0, 1) } ) def callableFunction(x, y): return f"Square Values: ({x**2:.4f}, {y**2:.4f})" targetItem4 = pg.TargetItem( pos=(10, -10), label=callableFunction ) p1.addItem(targetItem1) p1.addItem(targetItem2) p1.addItem(targetItem3) p1.addItem(targetItem4) # Add a linear region with a label lr = pg.LinearRegionItem(values=[70, 80]) p1.addItem(lr) label = pg.InfLineLabel(lr.lines[1], "region 1", position=0.95, rotateAxis=(1,0), anchor=(1, 1)) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/JoystickButton.py000066400000000000000000000021121421045507400250320ustar00rootroot00000000000000""" JoystickButton is a button with x/y values. When the button is depressed and the mouse dragged, the x/y values change to follow the mouse. When the mouse button is released, the x/y values change to 0,0 (rather like letting go of the joystick). """ import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtWidgets app = pg.mkQApp("Joystick Button Example") mw = QtWidgets.QMainWindow() mw.resize(300,50) mw.setWindowTitle('pyqtgraph example: JoystickButton') cw = QtWidgets.QWidget() mw.setCentralWidget(cw) layout = QtWidgets.QGridLayout() cw.setLayout(layout) mw.show() l1 = pg.ValueLabel(siPrefix=True, suffix='m') l2 = pg.ValueLabel(siPrefix=True, suffix='m') jb = pg.JoystickButton() jb.setFixedWidth(30) jb.setFixedHeight(30) layout.addWidget(l1, 0, 0) layout.addWidget(l2, 0, 1) layout.addWidget(jb, 0, 2) x = 0 y = 0 def update(): global x, y, l1, l2, jb dx, dy = jb.getState() x += dx * 1e-3 y += dy * 1e-3 l1.setValue(x) l2.setValue(y) timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(30) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/Legend.py000066400000000000000000000023171421045507400232440ustar00rootroot00000000000000""" Demonstrates basic use of LegendItem """ import numpy as np import pyqtgraph as pg win = pg.plot() win.setWindowTitle('pyqtgraph example: BarGraphItem') # # option1: only for .plot(), following c1,c2 for example----------------------- # win.addLegend(frame=False, colCount=2) # bar graph x = np.arange(10) y = np.sin(x+2) * 3 bg1 = pg.BarGraphItem(x=x, height=y, width=0.3, brush='b', pen='w', name='bar') win.addItem(bg1) # curve c1 = win.plot([np.random.randint(0,8) for i in range(10)], pen='r', symbol='t', symbolPen='r', symbolBrush='g', name='curve1') c2 = win.plot([2,1,4,3,1,3,2,4,3,2], pen='g', fillLevel=0, fillBrush=(255,255,255,30), name='curve2') # scatter plot s1 = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 120), name='scatter') spots = [{'pos': [i, np.random.randint(-3, 3)], 'data': 1} for i in range(10)] s1.addPoints(spots) win.addItem(s1) # # option2: generic method------------------------------------------------ legend = pg.LegendItem((80,60), offset=(70,20)) legend.setParentItem(win.graphicsItem()) legend.addItem(bg1, 'bar') legend.addItem(c1, 'curve1') legend.addItem(c2, 'curve2') legend.addItem(s1, 'scatter') if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/LogPlotTest.py000066400000000000000000000013441421045507400242650ustar00rootroot00000000000000""" Simple logarithmic plotting test """ import numpy as np import pyqtgraph as pg app = pg.mkQApp("Log Plot Example") win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples") win.resize(1000,600) win.setWindowTitle('pyqtgraph example: LogPlotTest') p5 = win.addPlot(title="Scatter plot, axis labels, log scale") x = np.random.normal(size=1000) * 1e-5 y = x*1000 + 0.005 * np.random.normal(size=1000) y -= y.min()-1.0 mask = x > 1e-15 x = x[mask] y = y[mask] p5.plot(x, y, pen=None, symbol='t', symbolPen=None, symbolSize=10, symbolBrush=(100, 100, 255, 50)) p5.setLabel('left', "Y Axis", units='A') p5.setLabel('bottom', "Y Axis", units='s') p5.setLogMode(x=True, y=False) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/MatrixDisplayExample.py000066400000000000000000000050271421045507400261550ustar00rootroot00000000000000""" This example demonstrates ViewBox and AxisItem configuration to plot a correlation matrix. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtWidgets, mkQApp class MainWindow(QtWidgets.QMainWindow): """ example application main window """ def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) gr_wid = pg.GraphicsLayoutWidget(show=True) self.setCentralWidget(gr_wid) self.setWindowTitle('pyqtgraph example: Correlation matrix display') self.resize(600,500) self.show() corrMatrix = np.array([ [ 1. , 0.5184571 , -0.70188642], [ 0.5184571 , 1. , -0.86094096], [-0.70188642, -0.86094096, 1. ] ]) columns = ["A", "B", "C"] pg.setConfigOption('imageAxisOrder', 'row-major') # Switch default order to Row-major correlogram = pg.ImageItem() # create transform to center the corner element on the origin, for any assigned image: tr = QtGui.QTransform().translate(-0.5, -0.5) correlogram.setTransform(tr) correlogram.setImage(corrMatrix) plotItem = gr_wid.addPlot() # add PlotItem to the main GraphicsLayoutWidget plotItem.invertY(True) # orient y axis to run top-to-bottom plotItem.setDefaultPadding(0.0) # plot without padding data range plotItem.addItem(correlogram) # display correlogram # show full frame, label tick marks at top and left sides, with some extra space for labels: plotItem.showAxes( True, showValues=(True, True, False, False), size=20 ) # define major tick marks and labels: ticks = [ (idx, label) for idx, label in enumerate( columns ) ] for side in ('left','top','right','bottom'): plotItem.getAxis(side).setTicks( (ticks, []) ) # add list of major ticks; no minor ticks plotItem.getAxis('bottom').setHeight(10) # include some additional space at bottom of figure colorMap = pg.colormap.get("CET-D1") # choose perceptually uniform, diverging color map # generate an adjustabled color bar, initially spanning -1 to 1: bar = pg.ColorBarItem( values=(-1,1), colorMap=colorMap) # link color bar and color map to correlogram, and show it in plotItem: bar.setImageItem(correlogram, insert_in=plotItem) mkQApp("Correlation matrix display") main_window = MainWindow() ## Start Qt event loop if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/MouseSelection.py000066400000000000000000000014251421045507400250030ustar00rootroot00000000000000""" Demonstrates selecting plot curves by mouse click """ import numpy as np import pyqtgraph as pg win = pg.plot() win.setWindowTitle('pyqtgraph example: Plot data selection') curves = [ pg.PlotCurveItem(y=np.sin(np.linspace(0, 20, 1000)), pen='r', clickable=True), pg.PlotCurveItem(y=np.sin(np.linspace(1, 21, 1000)), pen='g', clickable=True), pg.PlotCurveItem(y=np.sin(np.linspace(2, 22, 1000)), pen='b', clickable=True), ] def plotClicked(curve): global curves for i,c in enumerate(curves): if c is curve: c.setPen('rgb'[i], width=3) else: c.setPen('rgb'[i], width=1) for c in curves: win.addItem(c) c.sigClicked.connect(plotClicked) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/MultiPlotSpeedTest.py000066400000000000000000000027251421045507400256230ustar00rootroot00000000000000#!/usr/bin/python """ Test the speed of rapidly updating multiple plot curves """ from time import perf_counter import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore # pg.setConfigOptions(useOpenGL=True) app = pg.mkQApp("MultiPlot Speed Test") plot = pg.plot() plot.setWindowTitle('pyqtgraph example: MultiPlotSpeedTest') plot.setLabel('bottom', 'Index', units='B') nPlots = 100 nSamples = 500 curves = [] for idx in range(nPlots): curve = pg.PlotCurveItem(pen=({'color': (idx, nPlots*1.3), 'width': 1}), skipFiniteCheck=True) plot.addItem(curve) curve.setPos(0,idx*6) curves.append(curve) plot.setYRange(0, nPlots*6) plot.setXRange(0, nSamples) plot.resize(600,900) rgn = pg.LinearRegionItem([nSamples/5.,nSamples/3.]) plot.addItem(rgn) data = np.random.normal(size=(nPlots*23,nSamples)) ptr = 0 lastTime = perf_counter() fps = None count = 0 def update(): global curve, data, ptr, plot, lastTime, fps, nPlots, count count += 1 for i in range(nPlots): curves[i].setData(data[(ptr+i)%data.shape[0]]) ptr += nPlots now = perf_counter() 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 plot.setTitle('%0.2f fps' % fps) #app.processEvents() ## force complete redraw for every plot timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(0) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/MultiPlotWidget.py000066400000000000000000000015511421045507400251420ustar00rootroot00000000000000#!/usr/bin/python import numpy as np from numpy import linspace import pyqtgraph as pg from pyqtgraph import MultiPlotWidget from pyqtgraph.Qt import QtWidgets try: from pyqtgraph.metaarray import * except: print("MultiPlot is only used with MetaArray for now (and you do not have the metaarray package)") exit() app = pg.mkQApp("MultiPlot Widget Example") mw = QtWidgets.QMainWindow() mw.resize(800,800) pw = MultiPlotWidget() mw.setCentralWidget(pw) mw.show() data = np.random.normal(size=(3, 1000)) * np.array([[0.1], [1e-5], [1]]) ma = MetaArray(data, info=[ {'name': 'Signal', 'cols': [ {'name': 'Col1', 'units': 'V'}, {'name': 'Col2', 'units': 'A'}, {'name': 'Col3'}, ]}, {'name': 'Time', 'values': linspace(0., 1., 1000), 'units': 's'} ]) pw.plot(ma, pen='y') if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/MultiplePlotAxes.py000066400000000000000000000030121421045507400253120ustar00rootroot00000000000000""" Demonstrates a way to put multiple axes around a single plot. (This will eventually become a built-in feature of PlotItem) """ import pyqtgraph as pg pg.mkQApp() pw = pg.PlotWidget() pw.show() pw.setWindowTitle('pyqtgraph example: MultiplePlotAxes') p1 = pw.plotItem p1.setLabels(left='axis 1') ## create a new ViewBox, link the right axis to its coordinate system p2 = pg.ViewBox() p1.showAxis('right') p1.scene().addItem(p2) p1.getAxis('right').linkToView(p2) p2.setXLink(p1) p1.getAxis('right').setLabel('axis2', color='#0000ff') ## create third ViewBox. ## this time we need to create a new axis as well. p3 = pg.ViewBox() ax3 = pg.AxisItem('right') p1.layout.addItem(ax3, 2, 3) p1.scene().addItem(p3) ax3.linkToView(p3) p3.setXLink(p1) ax3.setZValue(-10000) ax3.setLabel('axis 3', color='#ff0000') ## Handle view resizing def updateViews(): ## view has resized; update auxiliary views to match global p1, p2, p3 p2.setGeometry(p1.vb.sceneBoundingRect()) p3.setGeometry(p1.vb.sceneBoundingRect()) ## need to re-update linked axes since this was called ## incorrectly while views had different shapes. ## (probably this should be handled in ViewBox.resizeEvent) p2.linkedViewChanged(p1.vb, p2.XAxis) p3.linkedViewChanged(p1.vb, p3.XAxis) updateViews() p1.vb.sigResized.connect(updateViews) p1.plot([1,2,4,8,16,32]) p2.addItem(pg.PlotCurveItem([10,20,40,80,40,20], pen='b')) p3.addItem(pg.PlotCurveItem([3200,1600,800,400,200,100], pen='r')) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/NonUniformImage.py000066400000000000000000000043531421045507400251050ustar00rootroot00000000000000""" Display a non-uniform image. This example displays 2-d data as an image with non-uniformly distributed sample points. """ import numpy as np import pyqtgraph as pg from pyqtgraph.graphicsItems.GradientEditorItem import Gradients from pyqtgraph.graphicsItems.NonUniformImage import NonUniformImage from pyqtgraph.Qt import QtWidgets RPM2RADS = 2 * np.pi / 60 RADS2RPM = 1 / RPM2RADS kfric = 1 # [Ws/rad] angular damping coefficient [0;100] kfric3 = 1.5e-6 # [Ws3/rad3] angular damping coefficient (3rd order) [0;10-3] psi = 0.2 # [Vs] flux linkage [0.001;10] res = 5e-3 # [Ohm] resistance [0;100] v_ref = 200 # [V] reference DC voltage [0;1000] k_v = 5 # linear voltage coefficient [-100;100] # create the (non-uniform) scales tau = np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200, 220], dtype=np.float32) w = np.array([0, 250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000], dtype=np.float32) * RPM2RADS v = 380 # calculate the power losses TAU, W = np.meshgrid(tau, w, indexing='ij') V = np.ones_like(TAU) * v P_loss = kfric * W + kfric3 * W ** 3 + (res * (TAU / psi) ** 2) + k_v * (V - v_ref) P_mech = TAU * W P_loss[P_mech > 1.5e5] = np.NaN # green - orange - red Gradients['gor'] = {'ticks': [(0.0, (74, 158, 71)), (0.5, (255, 230, 0)), (1, (191, 79, 76))], 'mode': 'rgb'} app = pg.mkQApp("NonUniform Image Example") win = QtWidgets.QMainWindow() cw = pg.GraphicsLayoutWidget() win.show() win.resize(600, 400) win.setCentralWidget(cw) win.setWindowTitle('pyqtgraph example: Non-uniform Image') p = cw.addPlot(title="Power Losses [W]", row=0, col=0) lut = pg.HistogramLUTItem(orientation="horizontal") p.setMouseEnabled(x=False, y=False) cw.nextRow() cw.addItem(lut) # load the gradient lut.gradient.loadPreset('gor') image = NonUniformImage(w * RADS2RPM, tau, P_loss.T) image.setLookupTable(lut, autoLevel=True) image.setZValue(-1) p.addItem(image) h = image.getHistogram() lut.plot.setData(*h) p.showGrid(x=True, y=True) p.setLabel(axis='bottom', text='Speed [rpm]') p.setLabel(axis='left', text='Torque [Nm]') # elevate the grid lines p.axes['bottom']['item'].setZValue(1000) p.axes['left']['item'].setZValue(1000) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/PColorMeshItem.py000066400000000000000000000047421421045507400247040ustar00rootroot00000000000000""" Demonstrates very basic use of PColorMeshItem """ import time import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore app = pg.mkQApp("PColorMesh Example") ## Create window with GraphicsView widget win = pg.GraphicsLayoutWidget() win.show() ## show widget alone in its own window win.setWindowTitle('pyqtgraph example: pColorMeshItem') view = win.addViewBox() ## Create data # To enhance the non-grid meshing, we randomize the polygon vertices per and # certain amount randomness = 5 # x and y being the vertices of the polygons, they share the same shape # However the shape can be different in both dimension xn = 50 # nb points along x yn = 40 # nb points along y x = np.repeat(np.arange(1, xn+1), yn).reshape(xn, yn)\ + np.random.random((xn, yn))*randomness y = np.tile(np.arange(1, yn+1), xn).reshape(xn, yn)\ + np.random.random((xn, yn))*randomness x.sort(axis=0) y.sort(axis=0) # z being the color of the polygons its shape must be decreased by one in each dimension z = np.exp(-(x*xn)**2/1000)[:-1,:-1] ## Create image item edgecolors = None antialiasing = False # edgecolors = {'color':'w', 'width':2} # May be uncommened to see edgecolor effect # antialiasing = True # May be uncommened to see antialiasing effect pcmi = pg.PColorMeshItem(edgecolors=edgecolors, antialiasing=antialiasing) view.addItem(pcmi) textitem = pg.TextItem(anchor=(1, 0)) view.addItem(textitem) ## Set the animation fps = 25 # Frame per second of the animation # Wave parameters wave_amplitude = 3 wave_speed = 0.3 wave_length = 10 color_speed = 0.3 timer = QtCore.QTimer() timer.setSingleShot(True) # not using QTimer.singleShot() because of persistence on PyQt. see PR #1605 textpos = None i=0 def updateData(): global i global textpos ## Display the new data set t0 = time.perf_counter() new_x = x new_y = y+wave_amplitude*np.cos(x/wave_length+i) new_z = np.exp(-(x-np.cos(i*color_speed)*xn)**2/1000)[:-1,:-1] t1 = time.perf_counter() pcmi.setData(new_x, new_y, new_z) t2 = time.perf_counter() i += wave_speed # display info in top-right corner textitem.setText(f'{(t2 - t1)*1000:.1f} ms') if textpos is None: textpos = pcmi.width(), pcmi.height() textitem.setPos(*textpos) # cap update rate at fps delay = max(1000/fps - (t2 - t0), 0) timer.start(int(delay)) timer.timeout.connect(updateData) updateData() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/PanningPlot.py000066400000000000000000000012001421045507400242650ustar00rootroot00000000000000""" Shows use of PlotWidget to display panning data """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore win = pg.GraphicsLayoutWidget(show=True) win.setWindowTitle('pyqtgraph example: PanningPlot') plt = win.addPlot() #plt.setAutoVisibleOnly(y=True) curve = plt.plot() data = [] count = 0 def update(): global data, curve, count data.append(np.random.normal(size=10) + np.sin(count * 0.1) * 5) if len(data) > 100: data.pop(0) curve.setData(np.hstack(data)) count += 1 timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(50) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/PlotAutoRange.py000066400000000000000000000015241421045507400245710ustar00rootroot00000000000000 """ This example demonstrates the different auto-ranging capabilities of ViewBoxes """ import time import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore app = pg.mkQApp("Plot Auto Range Example") win = pg.GraphicsLayoutWidget(show=True, title="Plot auto-range examples") win.resize(800,600) win.setWindowTitle('pyqtgraph example: PlotAutoRange') d = np.random.normal(size=100) d[50:54] += 10 p1 = win.addPlot(title="95th percentile range", y=d) p1.enableAutoRange('y', 0.95) p2 = win.addPlot(title="Auto Pan Only") p2.setAutoPan(y=True) curve = p2.plot() t0 = time.time() def update(): t = time.time() - t0 data = np.ones(100) * np.sin(t) data[50:60] += np.sin(t) curve.setData(data) timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(50) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/PlotSpeedTest.py000066400000000000000000000141741421045507400246110ustar00rootroot00000000000000#!/usr/bin/python """ Update a simple plot as rapidly as possible to measure speed. """ import argparse from collections import deque from time import perf_counter import numpy as np import pyqtgraph as pg import pyqtgraph.functions as fn import pyqtgraph.parametertree as ptree from pyqtgraph.Qt import QtCore, QtGui, QtWidgets # 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 MonkeyCurveItem(pg.PlotCurveItem): def __init__(self, *args, **kwds): super().__init__(*args, **kwds) self.monkey_mode = '' def setMethod(self, param, value): self.monkey_mode = value def paint(self, painter, opt, widget): if self.monkey_mode not in ['drawPolyline']: 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)) app = pg.mkQApp("Plot Speed Test") default_pen = pg.mkPen() 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='fill', type='bool', value=False), dict(name='skipFiniteCheck', type='bool', value=False), dict(name='plotMethod', title='Plot Method', type='list', limits=['pyqtgraph', 'drawPolyline']) ] 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() pw.setWindowTitle('pyqtgraph example: PlotSpeedTest') pw.setLabel('bottom', 'Index', units='B') curve = MonkeyCurveItem(pen=default_pen, brush='b') 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) def onFillChanged(param, enable): curve.setFillLevel(0.0 if enable else None) 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('fill').sigValueChanged.connect(onFillChanged) params.child('plotMethod').sigValueChanged.connect(curve.setMethod) params.sigTreeStateChanged.connect(resetTimings) makeData() fpsLastUpdate = perf_counter() def update(): 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() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/PlotWidget.py000066400000000000000000000042161421045507400241300ustar00rootroot00000000000000""" Demonstrates use of PlotWidget class. This is little more than a GraphicsView with a PlotItem placed in its center. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtWidgets app = pg.mkQApp() mw = QtWidgets.QMainWindow() mw.setWindowTitle('pyqtgraph example: PlotWidget') mw.resize(800,800) cw = QtWidgets.QWidget() mw.setCentralWidget(cw) l = QtWidgets.QVBoxLayout() cw.setLayout(l) pw = pg.PlotWidget(name='Plot1') ## giving the plots names allows us to link their axes together l.addWidget(pw) pw2 = pg.PlotWidget(name='Plot2') l.addWidget(pw2) pw3 = pg.PlotWidget() l.addWidget(pw3) mw.show() ## Create an empty plot curve to be filled later, set its pen p1 = pw.plot() p1.setPen((200,200,100)) ## Add in some extra graphics rect = QtWidgets.QGraphicsRectItem(QtCore.QRectF(0, 0, 1, 5e-11)) rect.setPen(pg.mkPen(100, 200, 100)) pw.addItem(rect) pw.setLabel('left', 'Value', units='V') pw.setLabel('bottom', 'Time', units='s') pw.setXRange(0, 2) pw.setYRange(0, 1e-10) def rand(n): data = np.random.random(n) data[int(n*0.1):int(n*0.13)] += .5 data[int(n*0.18)] += 2 data[int(n*0.1):int(n*0.13)] *= 5 data[int(n*0.18)] *= 20 data *= 1e-12 return data, np.arange(n, n+len(data)) / float(n) def updateData(): yd, xd = rand(10000) p1.setData(y=yd, x=xd) ## Start a timer to rapidly update the plot in pw t = QtCore.QTimer() t.timeout.connect(updateData) t.start(50) #updateData() ## Multiple parameterized plots--we can autogenerate averages for these. for i in range(0, 5): for j in range(0, 3): yd, xd = rand(10000) pw2.plot(y=yd*(j+1), x=xd, params={'iter': i, 'val': j}) ## Test large numbers curve = pw3.plot(np.random.normal(size=100)*1e0, clickable=True) curve.curve.setClickable(True) curve.setPen('w') ## white pen curve.setShadowPen(pg.mkPen((70,70,30), width=6, cosmetic=True)) def clicked(): print("curve clicked") curve.sigClicked.connect(clicked) lr = pg.LinearRegionItem([1, 30], bounds=[0,100], movable=True) pw3.addItem(lr) line = pg.InfiniteLine(angle=90, movable=True) pw3.addItem(line) line.setBounds([0,200]) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/Plotting.py000066400000000000000000000054571421045507400236560ustar00rootroot00000000000000""" This example demonstrates many of the 2D plotting capabilities in pyqtgraph. All of the plots may be panned/scaled by dragging with the left/right mouse buttons. Right click on any plot to show a context menu. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore app = pg.mkQApp("Plotting Example") #mw = QtWidgets.QMainWindow() #mw.resize(800,800) win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples") win.resize(1000,600) win.setWindowTitle('pyqtgraph example: Plotting') # Enable antialiasing for prettier plots pg.setConfigOptions(antialias=True) p1 = win.addPlot(title="Basic array plotting", y=np.random.normal(size=100)) p2 = win.addPlot(title="Multiple curves") p2.plot(np.random.normal(size=100), pen=(255,0,0), name="Red curve") p2.plot(np.random.normal(size=110)+5, pen=(0,255,0), name="Green curve") p2.plot(np.random.normal(size=120)+10, pen=(0,0,255), name="Blue curve") p3 = win.addPlot(title="Drawing with points") p3.plot(np.random.normal(size=100), pen=(200,200,200), symbolBrush=(255,0,0), symbolPen='w') win.nextRow() p4 = win.addPlot(title="Parametric, grid enabled") x = np.cos(np.linspace(0, 2*np.pi, 1000)) y = np.sin(np.linspace(0, 4*np.pi, 1000)) p4.plot(x, y) p4.showGrid(x=True, y=True) p5 = win.addPlot(title="Scatter plot, axis labels, log scale") x = np.random.normal(size=1000) * 1e-5 y = x*1000 + 0.005 * np.random.normal(size=1000) y -= y.min()-1.0 mask = x > 1e-15 x = x[mask] y = y[mask] p5.plot(x, y, pen=None, symbol='t', symbolPen=None, symbolSize=10, symbolBrush=(100, 100, 255, 50)) p5.setLabel('left', "Y Axis", units='A') p5.setLabel('bottom', "Y Axis", units='s') p5.setLogMode(x=True, y=False) p6 = win.addPlot(title="Updating plot") curve = p6.plot(pen='y') data = np.random.normal(size=(10,1000)) ptr = 0 def update(): global curve, data, ptr, p6 curve.setData(data[ptr%10]) if ptr == 0: p6.enableAutoRange('xy', False) ## stop auto-scaling after the first data set is plotted ptr += 1 timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(50) win.nextRow() p7 = win.addPlot(title="Filled plot, axis disabled") y = np.sin(np.linspace(0, 10, 1000)) + np.random.normal(size=1000, scale=0.1) p7.plot(y, fillLevel=-0.3, brush=(50,50,200,100)) p7.showAxis('bottom', False) x2 = np.linspace(-100, 100, 1000) data2 = np.sin(x2) / x2 p8 = win.addPlot(title="Region Selection") p8.plot(data2, pen=(255,255,255,200)) lr = pg.LinearRegionItem([400,700]) lr.setZValue(-10) p8.addItem(lr) p9 = win.addPlot(title="Zoom on selected region") p9.plot(data2) def updatePlot(): p9.setXRange(*lr.getRegion(), padding=0) def updateRegion(): lr.setRegion(p9.getViewBox().viewRange()[0]) lr.sigRegionChanged.connect(updatePlot) p9.sigXRangeChanged.connect(updateRegion) updatePlot() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ProgressDialog.py000066400000000000000000000025071421045507400247730ustar00rootroot00000000000000""" Using ProgressDialog to show progress updates in a nested process. """ import time import pyqtgraph as pg app = pg.mkQApp("Progress Dialog Example") def runStage(i): """Waste time for 2 seconds while incrementing a progress bar. """ with pg.ProgressDialog("Running stage %s.." % i, maximum=100, nested=True) as dlg: for j in range(100): time.sleep(0.02) dlg += 1 if dlg.wasCanceled(): print("Canceled stage %s" % i) break def runManyStages(i): """Iterate over runStage() 3 times while incrementing a progress bar. """ with pg.ProgressDialog("Running stage %s.." % i, maximum=3, nested=True, wait=0) as dlg: for j in range(1,4): runStage('%d.%d' % (i, j)) dlg += 1 if dlg.wasCanceled(): print("Canceled stage %s" % i) break with pg.ProgressDialog("Doing a multi-stage process..", maximum=5, nested=True, wait=0) as dlg1: for i in range(1,6): if i == 3: # this stage will have 3 nested progress bars runManyStages(i) else: # this stage will have 2 nested progress bars runStage(i) dlg1 += 1 if dlg1.wasCanceled(): print("Canceled process") break pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ROIExamples.py000066400000000000000000000107471421045507400242040ustar00rootroot00000000000000""" Demonstrates a variety of uses for ROI. This class provides a user-adjustable region of interest marker. It is possible to customize the layout and function of the scale/rotate handles in very flexible ways. """ import numpy as np import pyqtgraph as pg pg.setConfigOptions(imageAxisOrder='row-major') ## Create image to display arr = np.ones((100, 100), dtype=float) arr[45:55, 45:55] = 0 arr[25, :] = 5 arr[:, 25] = 5 arr[75, :] = 5 arr[:, 75] = 5 arr[50, :] = 10 arr[:, 50] = 10 arr += np.sin(np.linspace(0, 20, 100)).reshape(1, 100) arr += np.random.normal(size=(100,100)) # add an arrow for asymmetry arr[10, :50] = 10 arr[9:12, 44:48] = 10 arr[8:13, 44:46] = 10 ## create GUI app = pg.mkQApp("ROI Examples") w = pg.GraphicsLayoutWidget(show=True, size=(1000,800), border=True) w.setWindowTitle('pyqtgraph example: ROI Examples') text = """Data Selection From Image.
      \n Drag an ROI or its handles to update the selected image.
      Hold CTRL while dragging to snap to pixel boundaries
      and 15-degree rotation angles. """ w1 = w.addLayout(row=0, col=0) label1 = w1.addLabel(text, row=0, col=0) v1a = w1.addViewBox(row=1, col=0, lockAspect=True) v1b = w1.addViewBox(row=2, col=0, lockAspect=True) img1a = pg.ImageItem(arr) v1a.addItem(img1a) img1b = pg.ImageItem() v1b.addItem(img1b) v1a.disableAutoRange('xy') v1b.disableAutoRange('xy') v1a.autoRange() v1b.autoRange() rois = [] rois.append(pg.RectROI([20, 20], [20, 20], pen=(0,9))) rois[-1].addRotateHandle([1,0], [0.5, 0.5]) rois.append(pg.LineROI([0, 60], [20, 80], width=5, pen=(1,9))) rois.append(pg.TriangleROI([80, 75], 20, pen=(5, 9))) rois.append(pg.MultiRectROI([[20, 90], [50, 60], [60, 90]], width=5, pen=(2,9))) rois.append(pg.EllipseROI([60, 10], [30, 20], pen=(3,9))) rois.append(pg.CircleROI([80, 50], [20, 20], pen=(4,9))) #rois.append(pg.LineSegmentROI([[110, 50], [20, 20]], pen=(5,9))) rois.append(pg.PolyLineROI([[80, 60], [90, 30], [60, 40]], pen=(6,9), closed=True)) def update(roi): img1b.setImage(roi.getArrayRegion(arr, img1a), levels=(0, arr.max())) v1b.autoRange() for roi in rois: roi.sigRegionChanged.connect(update) v1a.addItem(roi) update(rois[-1]) text = """User-Modifiable ROIs
      Click on a line segment to add a new handle. Right click on a handle to remove. """ w2 = w.addLayout(row=0, col=1) label2 = w2.addLabel(text, row=0, col=0) v2a = w2.addViewBox(row=1, col=0, lockAspect=True) r2a = pg.PolyLineROI([[0,0], [10,10], [10,30], [30,10]], closed=True) v2a.addItem(r2a) r2b = pg.PolyLineROI([[0,-20], [10,-10], [10,-30]], closed=False) v2a.addItem(r2b) v2a.disableAutoRange('xy') #v2b.disableAutoRange('xy') v2a.autoRange() #v2b.autoRange() text = """Building custom ROI types
      ROIs can be built with a variety of different handle types
      that scale and rotate the roi around an arbitrary center location """ w3 = w.addLayout(row=1, col=0) label3 = w3.addLabel(text, row=0, col=0) v3 = w3.addViewBox(row=1, col=0, lockAspect=True) r3a = pg.ROI([0,0], [10,10]) v3.addItem(r3a) ## handles scaling horizontally around center r3a.addScaleHandle([1, 0.5], [0.5, 0.5]) r3a.addScaleHandle([0, 0.5], [0.5, 0.5]) ## handles scaling vertically from opposite edge r3a.addScaleHandle([0.5, 0], [0.5, 1]) r3a.addScaleHandle([0.5, 1], [0.5, 0]) ## handles scaling both vertically and horizontally r3a.addScaleHandle([1, 1], [0, 0]) r3a.addScaleHandle([0, 0], [1, 1]) r3b = pg.ROI([20,0], [10,10]) v3.addItem(r3b) ## handles rotating around center r3b.addRotateHandle([1, 1], [0.5, 0.5]) r3b.addRotateHandle([0, 0], [0.5, 0.5]) ## handles rotating around opposite corner r3b.addRotateHandle([1, 0], [0, 1]) r3b.addRotateHandle([0, 1], [1, 0]) ## handles rotating/scaling around center r3b.addScaleRotateHandle([0, 0.5], [0.5, 0.5]) # handles rotating/scaling around arbitrary point r3b.addScaleRotateHandle([0.3, 0], [0.9, 0.7]) v3.disableAutoRange('xy') v3.autoRange() text = """Transforming objects with ROI""" w4 = w.addLayout(row=1, col=1) label4 = w4.addLabel(text, row=0, col=0) v4 = w4.addViewBox(row=1, col=0, lockAspect=True) g = pg.GridItem() v4.addItem(g) r4 = pg.ROI([0,0], [100,100], resizable=False, removable=True) r4.addRotateHandle([1,0], [0.5, 0.5]) r4.addRotateHandle([0,1], [0.5, 0.5]) img4 = pg.ImageItem(arr) v4.addItem(r4) img4.setParentItem(r4) v4.disableAutoRange('xy') v4.autoRange() # Provide a callback to remove the ROI (and its children) when # "remove" is selected from the context menu. def remove(): v4.removeItem(r4) r4.sigRemoveRequested.connect(remove) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ROItypes.py000066400000000000000000000057641421045507400235750ustar00rootroot00000000000000#!/usr/bin/python -i import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui pg.setConfigOptions(imageAxisOrder='row-major') ## create GUI app = pg.mkQApp("ROI Types Examples") w = pg.GraphicsLayoutWidget(show=True, size=(800,800), border=True) v = w.addViewBox(colspan=2) v.invertY(True) ## Images usually have their Y-axis pointing downward v.setAspectLocked(True) ## Create image to display arr = np.ones((100, 100), dtype=float) arr[45:55, 45:55] = 0 arr[25, :] = 5 arr[:, 25] = 5 arr[75, :] = 5 arr[:, 75] = 5 arr[50, :] = 10 arr[:, 50] = 10 # add an arrow for asymmetry arr[10, :50] = 10 arr[9:12, 44:48] = 10 arr[8:13, 44:46] = 10 ## Create image items, add to scene and set position im1 = pg.ImageItem(arr) im2 = pg.ImageItem(arr) v.addItem(im1) v.addItem(im2) im2.moveBy(110, 20) v.setRange(QtCore.QRectF(0, 0, 200, 120)) im1.setTransform(QtGui.QTransform.fromScale(0.8, 0.5)) im3 = pg.ImageItem() v2 = w.addViewBox(1,0) v2.addItem(im3) v2.setRange(QtCore.QRectF(0, 0, 60, 60)) v2.invertY(True) v2.setAspectLocked(True) #im3.moveBy(0, 130) im3.setZValue(10) im4 = pg.ImageItem() v3 = w.addViewBox(1,1) v3.addItem(im4) v3.setRange(QtCore.QRectF(0, 0, 60, 60)) v3.invertY(True) v3.setAspectLocked(True) #im4.moveBy(110, 130) im4.setZValue(10) ## create the plot pi1 = w.addPlot(2,0, colspan=2) #pi1 = pg.PlotItem() #s.addItem(pi1) #pi1.scale(0.5, 0.5) #pi1.setGeometry(0, 170, 300, 100) lastRoi = None def updateRoi(roi): global im1, im2, im3, im4, arr, lastRoi if roi is None: return lastRoi = roi arr1 = roi.getArrayRegion(im1.image, img=im1) im3.setImage(arr1) arr2 = roi.getArrayRegion(im2.image, img=im2) im4.setImage(arr2) updateRoiPlot(roi, arr1) def updateRoiPlot(roi, data=None): if data is None: data = roi.getArrayRegion(im1.image, img=im1) if data is not None: roi.curve.setData(data.mean(axis=1)) ## Create a variety of different ROI types rois = [] rois.append(pg.TestROI([0, 0], [20, 20], maxBounds=QtCore.QRectF(-10, -10, 230, 140), pen=(0,9))) rois.append(pg.LineROI([0, 0], [20, 20], width=5, pen=(1,9))) rois.append(pg.MultiRectROI([[0, 50], [50, 60], [60, 30]], width=5, pen=(2,9))) rois.append(pg.EllipseROI([110, 10], [30, 20], pen=(3,9))) rois.append(pg.CircleROI([110, 50], [20, 20], pen=(4,9))) rois.append(pg.PolyLineROI([[2,0], [2.1,0], [2,.1]], pen=(5,9))) #rois.append(SpiralROI([20,30], [1,1], pen=mkPen(0))) ## Add each ROI to the scene and link its data to a plot curve with the same color for r in rois: v.addItem(r) c = pi1.plot(pen=r.pen) r.curve = c r.sigRegionChanged.connect(updateRoi) def updateImage(): global im1, arr, lastRoi r = abs(np.random.normal(loc=0, scale=(arr.max()-arr.min())*0.1, size=arr.shape)) im1.updateImage(arr + r) updateRoi(lastRoi) for r in rois: updateRoiPlot(r) ## Rapidly update one of the images with random noise t = QtCore.QTimer() t.timeout.connect(updateImage) t.start(50) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/RemoteGraphicsView.py000066400000000000000000000016701421045507400256160ustar00rootroot00000000000000""" Very simple example demonstrating RemoteGraphicsView. This allows graphics to be rendered in a child process and displayed in the parent, which can improve CPU usage on multi-core processors. """ import pyqtgraph as pg from pyqtgraph.widgets.RemoteGraphicsView import RemoteGraphicsView app = pg.mkQApp() ## Create the widget v = RemoteGraphicsView(debug=False) # setting debug=True causes both processes to print information # about interprocess communication v.show() v.setWindowTitle('pyqtgraph example: RemoteGraphicsView') ## v.pg is a proxy to the remote process' pyqtgraph module. All attribute ## requests and function calls made with this object are forwarded to the ## remote process and executed there. See pyqtgraph.multiprocess.remoteproxy ## for more inormation. plt = v.pg.PlotItem() v.setCentralItem(plt) plt.plot([1,4,2,3,6,2,3,4,2,3], pen='g') if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/RemoteSpeedTest.py000066400000000000000000000052431421045507400251230ustar00rootroot00000000000000""" This example demonstrates the use of RemoteGraphicsView to improve performance in applications with heavy load. It works by starting a second process to handle all graphics rendering, thus freeing up the main process to do its work. In this example, the update() function is very expensive and is called frequently. After update() generates a new set of data, it can either plot directly to a local plot (bottom) or remotely via a RemoteGraphicsView (top), allowing speed comparison between the two cases. IF you have a multi-core CPU, it should be obvious that the remote case is much faster. """ from time import perf_counter import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtWidgets app = pg.mkQApp() view = pg.widgets.RemoteGraphicsView.RemoteGraphicsView() pg.setConfigOptions(antialias=True) ## this will be expensive for the local plot view.pg.setConfigOptions(antialias=True) ## prettier plots at no cost to the main process! view.setWindowTitle('pyqtgraph example: RemoteSpeedTest') app.aboutToQuit.connect(view.close) label = QtWidgets.QLabel() rcheck = QtWidgets.QCheckBox('plot remote') rcheck.setChecked(True) lcheck = QtWidgets.QCheckBox('plot local') lplt = pg.PlotWidget() layout = pg.LayoutWidget() layout.addWidget(rcheck) layout.addWidget(lcheck) layout.addWidget(label) layout.addWidget(view, row=1, col=0, colspan=3) layout.addWidget(lplt, row=2, col=0, colspan=3) layout.resize(800,800) layout.show() ## Create a PlotItem in the remote process that will be displayed locally rplt = view.pg.PlotItem() rplt._setProxyOptions(deferGetattr=True) ## speeds up access to rplt.plot view.setCentralItem(rplt) lastUpdate = perf_counter() avgFps = 0.0 def update(): global check, label, plt, lastUpdate, avgFps, rpltfunc data = np.random.normal(size=(10000,50)).sum(axis=1) data += 5 * np.sin(np.linspace(0, 10, data.shape[0])) if rcheck.isChecked(): rplt.plot(data, clear=True, _callSync='off') ## We do not expect a return value. ## By turning off callSync, we tell ## the proxy that it does not need to ## wait for a reply from the remote ## process. if lcheck.isChecked(): lplt.plot(data, clear=True) now = perf_counter() fps = 1.0 / (now - lastUpdate) lastUpdate = now avgFps = avgFps * 0.8 + fps * 0.2 label.setText("Generating %0.2f fps" % avgFps) timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(0) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/RunExampleApp.py000066400000000000000000000007571421045507400245750ustar00rootroot00000000000000""" This file is used by test_examples.py for ensuring the Example App works. It is not named test_ExampleApp.py as that way the Example application is not run twice. """ from ExampleApp import ExampleLoader import pyqtgraph as pg from pyqtgraph.Qt import QtTest pg.mkQApp() def test_ExampleLoader(): loader = ExampleLoader() QtTest.QTest.qWaitForWindowExposed(loader) QtTest.QTest.qWait(200) loader.close() if __name__ == "__main__": test_ExampleLoader() pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ScaleBar.py000066400000000000000000000007271421045507400235250ustar00rootroot00000000000000""" Demonstrates ScaleBar """ import numpy as np import pyqtgraph as pg pg.mkQApp() win = pg.GraphicsLayoutWidget(show=True) win.setWindowTitle('pyqtgraph example: ScaleBar') vb = win.addViewBox() vb.setAspectLocked() img = pg.ImageItem() img.setImage(np.random.normal(size=(100,100))) img.setScale(0.01) vb.addItem(img) scale = pg.ScaleBar(size=0.1) scale.setParentItem(vb) scale.anchor((1, 1), (1, 1), offset=(-20, -20)) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ScatterPlot.py000066400000000000000000000102371421045507400243120ustar00rootroot00000000000000""" Example demonstrating a variety of scatter plot features. """ from collections import namedtuple import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtWidgets app = pg.mkQApp("Scatter Plot Item Example") mw = QtWidgets.QMainWindow() mw.resize(800,800) view = pg.GraphicsLayoutWidget() ## GraphicsView with GraphicsLayout inserted by default mw.setCentralWidget(view) mw.show() mw.setWindowTitle('pyqtgraph example: ScatterPlot') ## create four areas to add plots w1 = view.addPlot() w2 = view.addViewBox() w2.setAspectLocked(True) view.nextRow() w3 = view.addPlot() w4 = view.addPlot() print("Generating data, this takes a few seconds...") ## Make all plots clickable clickedPen = pg.mkPen('b', width=2) lastClicked = [] def clicked(plot, points): global lastClicked for p in lastClicked: p.resetPen() print("clicked points", points) for p in points: p.setPen(clickedPen) lastClicked = points ## There are a few different ways we can draw scatter plots; each is optimized for different types of data: ## 1) All spots identical and transform-invariant (top-left plot). ## In this case we can get a huge performance boost by pre-rendering the spot ## image and just drawing that image repeatedly. n = 300 s1 = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 120)) pos = np.random.normal(size=(2,n), scale=1e-5) spots = [{'pos': pos[:,i], 'data': 1} for i in range(n)] + [{'pos': [0,0], 'data': 1}] s1.addPoints(spots) w1.addItem(s1) s1.sigClicked.connect(clicked) ## 2) Spots are transform-invariant, but not identical (top-right plot). ## In this case, drawing is almsot as fast as 1), but there is more startup ## overhead and memory usage since each spot generates its own pre-rendered ## image. TextSymbol = namedtuple("TextSymbol", "label symbol scale") def createLabel(label, angle): symbol = QtGui.QPainterPath() #symbol.addText(0, 0, QFont("San Serif", 10), label) f = QtGui.QFont() f.setPointSize(10) symbol.addText(0, 0, f, label) br = symbol.boundingRect() scale = min(1. / br.width(), 1. / br.height()) tr = QtGui.QTransform() tr.scale(scale, scale) tr.rotate(angle) tr.translate(-br.x() - br.width()/2., -br.y() - br.height()/2.) return TextSymbol(label, tr.map(symbol), 0.1 / scale) random_str = lambda : (''.join([chr(np.random.randint(ord('A'),ord('z'))) for i in range(np.random.randint(1,5))]), np.random.randint(0, 360)) s2 = pg.ScatterPlotItem(size=10, pen=pg.mkPen('w'), pxMode=True) pos = np.random.normal(size=(2,n), scale=1e-5) spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': i%10, 'size': 5+i/10.} for i in range(n)] s2.addPoints(spots) spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': label[1], 'size': label[2]*(5+i/10.)} for (i, label) in [(i, createLabel(*random_str())) for i in range(n)]] s2.addPoints(spots) w2.addItem(s2) s2.sigClicked.connect(clicked) ## 3) Spots are not transform-invariant, not identical (bottom-left). ## This is the slowest case, since all spots must be completely re-drawn ## every time because their apparent transformation may have changed. s3 = pg.ScatterPlotItem( pxMode=False, # Set pxMode=False to allow spots to transform with the view hoverable=True, hoverPen=pg.mkPen('g'), hoverSize=1e-6 ) spots3 = [] for i in range(10): for j in range(10): spots3.append({'pos': (1e-6*i, 1e-6*j), 'size': 1e-6, 'pen': {'color': 'w', 'width': 2}, 'brush':pg.intColor(i*10+j, 100)}) s3.addPoints(spots3) w3.addItem(s3) s3.sigClicked.connect(clicked) ## Test performance of large scatterplots s4 = pg.ScatterPlotItem( size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 20), hoverable=True, hoverSymbol='s', hoverSize=15, hoverPen=pg.mkPen('r', width=2), hoverBrush=pg.mkBrush('g'), ) n = 10000 pos = np.random.normal(size=(2, n), scale=1e-9) s4.addPoints( x=pos[0], y=pos[1], # size=(np.random.random(n) * 20.).astype(int), # brush=[pg.mkBrush(x) for x in np.random.randint(0, 256, (n, 3))], data=np.arange(n) ) w4.addItem(s4) s4.sigClicked.connect(clicked) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ScatterPlotSpeedTest.py000066400000000000000000000077511421045507400261420ustar00rootroot00000000000000#!/usr/bin/python """ For testing rapid updates of ScatterPlotItem under various conditions. """ from time import perf_counter import numpy as np import pyqtgraph as pg import pyqtgraph.parametertree as ptree from pyqtgraph.Qt import QtCore, QtWidgets translate = QtCore.QCoreApplication.translate app = pg.mkQApp() param = ptree.Parameter.create(name=translate('ScatterPlot', 'Parameters'), type='group', children=[ dict(name='paused', title=translate('ScatterPlot', 'Paused: '), type='bool', value=False), dict(name='count', title=translate('ScatterPlot', 'Count: '), type='int', limits=[1, None], value=500, step=100), dict(name='size', title=translate('ScatterPlot', 'Size: '), type='int', limits=[1, None], value=10), 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', 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()) pt = ptree.ParameterTree(showHeader=False) pt.setParameters(param) p = pg.PlotWidget() splitter = QtWidgets.QSplitter() splitter.addWidget(pt) splitter.addWidget(p) splitter.show() data = {} item = pg.ScatterPlotItem() hoverBrush = pg.mkBrush('y') ptr = 0 lastTime = perf_counter() fps = None timer = QtCore.QTimer() def mkDataAndItem(): global data, fps scale = 100 data = { 'pos': np.random.normal(size=(50, param['count']), scale=scale), 'pen': [pg.mkPen(x) for x in np.random.randint(0, 256, (param['count'], 3))], 'brush': [pg.mkBrush(x) for x in np.random.randint(0, 256, (param['count'], 3))], 'size': (np.random.random(param['count']) * param['size']).astype(int) } data['pen'][0] = pg.mkPen('w') data['size'][0] = param['size'] data['brush'][0] = pg.mkBrush('b') bound = 5 * scale p.setRange(xRange=[-bound, bound], yRange=[-bound, bound]) mkItem() def mkItem(): global item item = pg.ScatterPlotItem(pxMode=param['pxMode'], **getData()) item.opts['useCache'] = param['useCache'] p.clear() p.addItem(item) def getData(): pos = data['pos'] pen = data['pen'] size = data['size'] brush = data['brush'] if not param['randomize']: pen = pen[0] size = size[0] brush = brush[0] return dict(x=pos[ptr % 50], y=pos[(ptr + 1) % 50], pen=pen, brush=brush, size=size) def update(): global ptr, lastTime, fps mode = param['mode'] if mode == 'newItem': mkItem() elif mode == 'reuseItem': item.setData(**getData()) elif mode == 'panZoom': item.viewTransformChanged() item.update() elif mode == 'hover': pts = item.points() old = pts[(ptr - 1) % len(pts)] new = pts[ptr % len(pts)] item.pointsAt(new.pos()) old.resetBrush() # reset old's brush before setting new's to better simulate hovering new.setBrush(hoverBrush) ptr += 1 now = perf_counter() 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) p.repaint() # app.processEvents() # force complete redraw for every plot mkDataAndItem() for name in ['count', 'size']: param.child(name).sigValueChanged.connect(mkDataAndItem) for name in ['useCache', 'pxMode', 'randomize']: param.child(name).sigValueChanged.connect(mkItem) param.child('paused').sigValueChanged.connect(lambda _, v: timer.stop() if v else timer.start()) timer.timeout.connect(update) timer.start(0) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ScatterPlotWidget.py000066400000000000000000000045121421045507400254550ustar00rootroot00000000000000""" Demonstration of ScatterPlotWidget for exploring structure in tabular data. The widget consists of four components: 1) A list of column names from which the user may select 1 or 2 columns to plot. If one column is selected, the data for that column will be plotted in a histogram-like manner by using pg.pseudoScatter(). If two columns are selected, then the scatter plot will be generated with x determined by the first column that was selected and y by the second. 2) A DataFilter that allows the user to select a subset of the data by specifying multiple selection criteria. 3) A ColorMap that allows the user to determine how points are colored by specifying multiple criteria. 4) A PlotWidget for displaying the data. """ import numpy as np import pyqtgraph as pg pg.mkQApp() # Make up some tabular data with structure data = np.empty(1000, dtype=[('x_pos', float), ('y_pos', float), ('count', int), ('amplitude', float), ('decay', float), ('type', 'U10')]) strings = ['Type-A', 'Type-B', 'Type-C', 'Type-D', 'Type-E'] typeInds = np.random.randint(5, size=1000) data['type'] = np.array(strings)[typeInds] data['x_pos'] = np.random.normal(size=1000) data['x_pos'][data['type'] == 'Type-A'] -= 1 data['x_pos'][data['type'] == 'Type-B'] -= 1 data['x_pos'][data['type'] == 'Type-C'] += 2 data['x_pos'][data['type'] == 'Type-D'] += 2 data['x_pos'][data['type'] == 'Type-E'] += 2 data['y_pos'] = np.random.normal(size=1000) + data['x_pos']*0.1 data['y_pos'][data['type'] == 'Type-A'] += 3 data['y_pos'][data['type'] == 'Type-B'] += 3 data['amplitude'] = data['x_pos'] * 1.4 + data['y_pos'] + np.random.normal(size=1000, scale=0.4) data['count'] = (np.random.exponential(size=1000, scale=100) * data['x_pos']).astype(int) data['decay'] = np.random.normal(size=1000, scale=1e-3) + data['amplitude'] * 1e-4 data['decay'][data['type'] == 'Type-A'] /= 2 data['decay'][data['type'] == 'Type-E'] *= 3 # Create ScatterPlotWidget and configure its fields spw = pg.ScatterPlotWidget() spw.setFields([ ('x_pos', {'units': 'm'}), ('y_pos', {'units': 'm'}), ('count', {}), ('amplitude', {'units': 'V'}), ('decay', {'units': 's'}), ('type', {'mode': 'enum', 'values': strings}), ]) spw.setData(data) spw.show() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/SimplePlot.py000066400000000000000000000002541421045507400241340ustar00rootroot00000000000000import numpy as np import pyqtgraph as pg plt = pg.plot(np.random.normal(size=100), title="Simplest possible plotting example") if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/SpinBox.py000066400000000000000000000110131421045507400234210ustar00rootroot00000000000000""" This example demonstrates the SpinBox widget, which is an extension of QDoubleSpinBox providing some advanced features: * SI-prefixed units * Non-linear stepping modes * Bounded/unbounded values """ import ast import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets app = pg.mkQApp("SpinBox Example") spins = [ ("Floating-point spin box, min=0, no maximum.
      Non-finite values (nan, inf) are permitted.", pg.SpinBox(value=5.0, bounds=[0, None], finite=False)), ("Integer spin box, dec stepping
      (1-9, 10-90, 100-900, etc), decimals=4", pg.SpinBox(value=10, int=True, dec=True, minStep=1, step=1, decimals=4)), ("Float with SI-prefixed units
      (n, u, m, k, M, etc)", pg.SpinBox(value=0.9, suffix='V', siPrefix=True)), ("Float with SI-prefixed units,
      dec step=0.1, minStep=0.1", pg.SpinBox(value=1.0, suffix='PSI', siPrefix=True, dec=True, step=0.1, minStep=0.1)), ("Float with SI-prefixed units,
      dec step=0.5, minStep=0.01", pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.5, minStep=0.01)), ("Float with SI-prefixed units,
      dec step=1.0, minStep=0.001", pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=1.0, minStep=0.001)), ("Float with SI prefix but no suffix", pg.SpinBox(value=1e9, siPrefix=True)), ("Float with custom formatting", pg.SpinBox(value=23.07, format='${value:0.02f}', regex='\$?(?P(-?\d+(\.\d+)?)|(-?\.\d+))$')), ("Int with suffix", pg.SpinBox(value=999, step=1, int=True, suffix="V")), ("Int with custom formatting", pg.SpinBox(value=4567, step=1, int=True, bounds=[0,None], format='0x{value:X}', regex='(0x)?(?P[0-9a-fA-F]+)$', evalFunc=lambda s: ast.literal_eval('0x'+s))), ("Integer with bounds=[10, 20] and wrapping", pg.SpinBox(value=10, bounds=[10, 20], int=True, minStep=1, step=1, wrapping=True)), ] win = QtWidgets.QMainWindow() win.setWindowTitle('pyqtgraph example: SpinBox') cw = QtWidgets.QWidget() layout = QtWidgets.QGridLayout() cw.setLayout(layout) win.setCentralWidget(cw) win.show() #win.resize(300, 600) changingLabel = QtWidgets.QLabel() ## updated immediately changedLabel = QtWidgets.QLabel() ## updated only when editing is finished or mouse wheel has stopped for 0.3sec changingLabel.setMinimumWidth(200) font = changingLabel.font() font.setBold(True) font.setPointSize(14) changingLabel.setFont(font) changedLabel.setFont(font) labels = [] def valueChanged(sb): changedLabel.setText("Final value: %s" % str(sb.value())) def valueChanging(sb, value): changingLabel.setText("Value changing: %s" % str(sb.value())) for text, spin in spins: label = QtWidgets.QLabel(text) labels.append(label) layout.addWidget(label) layout.addWidget(spin) spin.sigValueChanged.connect(valueChanged) spin.sigValueChanging.connect(valueChanging) layout.addWidget(changingLabel, 0, 1) layout.addWidget(changedLabel, 2, 1) #def mkWin(): #win = QtWidgets.QMainWindow() #g = QtWidgets.QFormLayout() #w = QtWidgets.QWidget() #w.setLayout(g) #win.setCentralWidget(w) #s1 = SpinBox(value=5, step=0.1, bounds=[-1.5, None], suffix='units') #t1 = QtWidgets.QLineEdit() #g.addRow(s1, t1) #s2 = SpinBox(value=10e-6, dec=True, step=0.1, minStep=1e-6, suffix='A', siPrefix=True) #t2 = QtWidgets.QLineEdit() #g.addRow(s2, t2) #s3 = SpinBox(value=1000, dec=True, step=0.5, minStep=1e-6, bounds=[1, 1e9], suffix='Hz', siPrefix=True) #t3 = QtWidgets.QLineEdit() #g.addRow(s3, t3) #s4 = SpinBox(int=True, dec=True, step=1, minStep=1, bounds=[-10, 1000]) #t4 = QtWidgets.QLineEdit() #g.addRow(s4, t4) #win.show() #import sys #for sb in [s1, s2, s3,s4]: ##QtCore.QObject.connect(sb, QtCore.SIGNAL('valueChanged(double)'), lambda v: sys.stdout.write(str(sb) + " valueChanged\n")) ##QtCore.QObject.connect(sb, QtCore.SIGNAL('editingFinished()'), lambda: sys.stdout.write(str(sb) + " editingFinished\n")) #sb.sigValueChanged.connect(valueChanged) #sb.sigValueChanging.connect(valueChanging) #sb.editingFinished.connect(lambda: sys.stdout.write(str(sb) + " editingFinished\n")) #return win, w, [s1, s2, s3, s4] #a = mkWin() #def test(n=100): #for i in range(n): #win, w, sb = mkWin() #for s in sb: #w.setParent(None) #s.setParent(None) #s.valueChanged.disconnect() #s.editingFinished.disconnect() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/Symbols.py000077500000000000000000000051041421045507400234760ustar00rootroot00000000000000""" This example shows all the scatter plot symbols available in pyqtgraph. These symbols are used to mark point locations for scatter plots and some line plots, similar to "markers" in matplotlib and vispy. """ import pyqtgraph as pg app = pg.mkQApp("Symbols Examples") win = pg.GraphicsLayoutWidget(show=True, title="Scatter Plot Symbols") win.resize(1000,600) pg.setConfigOptions(antialias=True) plot = win.addPlot(title="Plotting with symbols") plot.addLegend() plot.plot([0, 1, 2, 3, 4], pen=(0,0,200), symbolBrush=(0,0,200), symbolPen='w', symbol='o', symbolSize=14, name="symbol='o'") plot.plot([1, 2, 3, 4, 5], pen=(0,128,0), symbolBrush=(0,128,0), symbolPen='w', symbol='t', symbolSize=14, name="symbol='t'") plot.plot([2, 3, 4, 5, 6], pen=(19,234,201), symbolBrush=(19,234,201), symbolPen='w', symbol='t1', symbolSize=14, name="symbol='t1'") plot.plot([3, 4, 5, 6, 7], pen=(195,46,212), symbolBrush=(195,46,212), symbolPen='w', symbol='t2', symbolSize=14, name="symbol='t2'") plot.plot([4, 5, 6, 7, 8], pen=(250,194,5), symbolBrush=(250,194,5), symbolPen='w', symbol='t3', symbolSize=14, name="symbol='t3'") plot.plot([5, 6, 7, 8, 9], pen=(54,55,55), symbolBrush=(55,55,55), symbolPen='w', symbol='s', symbolSize=14, name="symbol='s'") plot.plot([6, 7, 8, 9, 10], pen=(0,114,189), symbolBrush=(0,114,189), symbolPen='w', symbol='p', symbolSize=14, name="symbol='p'") plot.plot([7, 8, 9, 10, 11], pen=(217,83,25), symbolBrush=(217,83,25), symbolPen='w', symbol='h', symbolSize=14, name="symbol='h'") plot.plot([8, 9, 10, 11, 12], pen=(237,177,32), symbolBrush=(237,177,32), symbolPen='w', symbol='star', symbolSize=14, name="symbol='star'") plot.plot([9, 10, 11, 12, 13], pen=(126,47,142), symbolBrush=(126,47,142), symbolPen='w', symbol='+', symbolSize=14, name="symbol='+'") plot.plot([10, 11, 12, 13, 14], pen=(119,172,48), symbolBrush=(119,172,48), symbolPen='w', symbol='d', symbolSize=14, name="symbol='d'") plot.plot([11, 12, 13, 14, 15], pen=(253, 216, 53), symbolBrush=(253, 216, 53), symbolPen='w', symbol='arrow_down', symbolSize=22, name="symbol='arrow_down'") plot.plot([12, 13, 14, 15, 16], pen=(189, 189, 189), symbolBrush=(189, 189, 189), symbolPen='w', symbol='arrow_left', symbolSize=22, name="symbol='arrow_left'") plot.plot([13, 14, 15, 16, 17], pen=(187, 26, 95), symbolBrush=(187, 26, 95), symbolPen='w', symbol='arrow_up', symbolSize=22, name="symbol='arrow_up'") plot.plot([14, 15, 16, 17, 18], pen=(248, 187, 208), symbolBrush=(248, 187, 208), symbolPen='w', symbol='arrow_right', symbolSize=22, name="symbol='arrow_right'") plot.setXRange(-2, 4) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/TableWidget.py000066400000000000000000000011131421045507400242320ustar00rootroot00000000000000""" Simple demonstration of TableWidget, which is an extension of QTableWidget that automatically displays a variety of tabluar data formats. """ import numpy as np import pyqtgraph as pg app = pg.mkQApp("Table Widget Example") w = pg.TableWidget() w.show() w.resize(500,500) w.setWindowTitle('pyqtgraph example: TableWidget') data = np.array([ (1, 1.6, 'x'), (3, 5.4, 'y'), (8, 12.5, 'z'), (443, 1e-12, 'w'), ], dtype=[('Column 1', int), ('Column 2', float), ('Column 3', object)]) w.setData(data) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/TreeWidget.py000066400000000000000000000022551421045507400241120ustar00rootroot00000000000000""" Simple demonstration of TreeWidget, which is an extension of QTreeWidget that allows widgets to be added and dragged within the tree more easily. """ import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets app = pg.mkQApp("TreeWidget Example") w = pg.TreeWidget() w.setColumnCount(2) w.show() w.setWindowTitle('pyqtgraph example: TreeWidget') i1 = QtWidgets.QTreeWidgetItem(["Item 1"]) i11 = QtWidgets.QTreeWidgetItem(["Item 1.1"]) i12 = QtWidgets.QTreeWidgetItem(["Item 1.2"]) i2 = QtWidgets.QTreeWidgetItem(["Item 2"]) i21 = QtWidgets.QTreeWidgetItem(["Item 2.1"]) i211 = pg.TreeWidgetItem(["Item 2.1.1"]) i212 = pg.TreeWidgetItem(["Item 2.1.2"]) i22 = pg.TreeWidgetItem(["Item 2.2"]) i3 = pg.TreeWidgetItem(["Item 3"]) i4 = pg.TreeWidgetItem(["Item 4"]) i5 = pg.TreeWidgetItem(["Item 5"]) b5 = QtWidgets.QPushButton('Button') i5.setWidget(1, b5) w.addTopLevelItem(i1) w.addTopLevelItem(i2) w.addTopLevelItem(i3) w.addTopLevelItem(i4) w.addTopLevelItem(i5) i1.addChild(i11) i1.addChild(i12) i2.addChild(i21) i21.addChild(i211) i21.addChild(i212) i2.addChild(i22) b1 = QtWidgets.QPushButton("Button") w.setItemWidget(i1, 1, b1) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/VideoSpeedTest.py000066400000000000000000000225441421045507400247410ustar00rootroot00000000000000""" Tests the speed of image updates for an ImageItem and RawImageWidget. The speed will generally depend on the type of data being shown, whether it is being scaled and/or converted by lookup table, and whether OpenGL is used by the view widget """ import argparse import sys from time import perf_counter import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QT_LIB, QtCore, QtGui, QtWidgets pg.setConfigOption('imageAxisOrder', 'row-major') import importlib ui_template = importlib.import_module(f'VideoTemplate_{QT_LIB.lower()}') try: import cupy as cp pg.setConfigOption("useCupy", True) _has_cupy = True except ImportError: cp = None _has_cupy = False try: import numba _has_numba = True except ImportError: numba = None _has_numba = False try: from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget except ImportError: RawImageGLWidget = None parser = argparse.ArgumentParser(description="Benchmark for testing video performance") parser.add_argument('--cuda', default=False, action='store_true', help="Use CUDA to process on the GPU", dest="cuda") parser.add_argument('--dtype', default='uint8', choices=['uint8', 'uint16', 'float'], help="Image dtype (uint8, uint16, or float)") parser.add_argument('--frames', default=3, type=int, help="Number of image frames to generate (default=3)") parser.add_argument('--image-mode', default='mono', choices=['mono', 'rgb'], help="Image data mode (mono or rgb)", dest='image_mode') parser.add_argument('--levels', default=None, type=lambda s: tuple([float(x) for x in s.split(',')]), help="min,max levels to scale monochromatic image dynamic range, or rmin,rmax,gmin,gmax,bmin,bmax to scale rgb") parser.add_argument('--lut', default=False, action='store_true', help="Use color lookup table") parser.add_argument('--lut-alpha', default=False, action='store_true', help="Use alpha color lookup table", dest='lut_alpha') parser.add_argument('--size', default='512x512', type=lambda s: tuple([int(x) for x in s.split('x')]), help="WxH image dimensions default='512x512'") args = parser.parse_args(sys.argv[1:]) if RawImageGLWidget is not None: # don't limit frame rate to vsync sfmt = QtGui.QSurfaceFormat() sfmt.setSwapInterval(0) QtGui.QSurfaceFormat.setDefaultFormat(sfmt) app = pg.mkQApp("Video Speed Test Example") win = QtWidgets.QMainWindow() win.setWindowTitle('pyqtgraph example: VideoSpeedTest') ui = ui_template.Ui_MainWindow() ui.setupUi(win) win.show() if RawImageGLWidget is None: ui.rawGLRadio.setEnabled(False) ui.rawGLRadio.setText(ui.rawGLRadio.text() + " (OpenGL not available)") else: ui.rawGLImg = RawImageGLWidget() ui.stack.addWidget(ui.rawGLImg) # read in CLI args ui.cudaCheck.setChecked(args.cuda and _has_cupy) ui.cudaCheck.setEnabled(_has_cupy) ui.numbaCheck.setChecked(_has_numba and pg.getConfigOption("useNumba")) ui.numbaCheck.setEnabled(_has_numba) ui.framesSpin.setValue(args.frames) ui.widthSpin.setValue(args.size[0]) ui.heightSpin.setValue(args.size[1]) ui.dtypeCombo.setCurrentText(args.dtype) ui.rgbCheck.setChecked(args.image_mode=='rgb') ui.maxSpin1.setOpts(value=255, step=1) ui.minSpin1.setOpts(value=0, step=1) levelSpins = [ui.minSpin1, ui.maxSpin1, ui.minSpin2, ui.maxSpin2, ui.minSpin3, ui.maxSpin3] if args.cuda and _has_cupy: xp = cp else: xp = np if args.levels is None: ui.scaleCheck.setChecked(False) ui.rgbLevelsCheck.setChecked(False) else: ui.scaleCheck.setChecked(True) if len(args.levels) == 2: ui.rgbLevelsCheck.setChecked(False) ui.minSpin1.setValue(args.levels[0]) ui.maxSpin1.setValue(args.levels[1]) elif len(args.levels) == 6: ui.rgbLevelsCheck.setChecked(True) for spin,val in zip(levelSpins, args.levels): spin.setValue(val) else: raise ValueError("levels argument must be 2 or 6 comma-separated values (got %r)" % (args.levels,)) ui.lutCheck.setChecked(args.lut) ui.alphaCheck.setChecked(args.lut_alpha) #ui.graphicsView.useOpenGL() ## buggy, but you can try it if you need extra speed. vb = pg.ViewBox() ui.graphicsView.setCentralItem(vb) vb.setAspectLocked() img = pg.ImageItem() vb.addItem(img) LUT = None def updateLUT(): global LUT, ui dtype = ui.dtypeCombo.currentText() if dtype == 'uint8': n = 256 else: n = 4096 LUT = ui.gradient.getLookupTable(n, alpha=ui.alphaCheck.isChecked()) if _has_cupy and xp == cp: LUT = cp.asarray(LUT) ui.gradient.sigGradientChanged.connect(updateLUT) updateLUT() ui.alphaCheck.toggled.connect(updateLUT) def updateScale(): global ui, levelSpins if ui.rgbLevelsCheck.isChecked(): for s in levelSpins[2:]: s.setEnabled(True) else: for s in levelSpins[2:]: s.setEnabled(False) updateScale() ui.rgbLevelsCheck.toggled.connect(updateScale) cache = {} def mkData(): with pg.BusyCursor(): global data, cache, ui, xp frames = ui.framesSpin.value() width = ui.widthSpin.value() height = ui.heightSpin.value() cacheKey = (ui.dtypeCombo.currentText(), ui.rgbCheck.isChecked(), frames, width, height) if cacheKey not in cache: if cacheKey[0] == 'uint8': dt = xp.uint8 loc = 128 scale = 64 mx = 255 elif cacheKey[0] == 'uint16': dt = xp.uint16 loc = 4096 scale = 1024 mx = 2**16 - 1 elif cacheKey[0] == 'float': dt = xp.float32 loc = 1.0 scale = 0.1 mx = 1.0 else: raise ValueError(f"unable to handle dtype: {cacheKey[0]}") chan_shape = (height, width) if ui.rgbCheck.isChecked(): frame_shape = chan_shape + (3,) else: frame_shape = chan_shape data = xp.empty((frames,) + frame_shape, dtype=dt) view = data.reshape((-1,) + chan_shape) for idx in range(view.shape[0]): subdata = xp.random.normal(loc=loc, scale=scale, size=chan_shape) # note: gaussian filtering has been removed as it slows down array # creation greatly. if cacheKey[0] != 'float': xp.clip(subdata, 0, mx, out=subdata) view[idx] = subdata data[:, 10:50, 10] = mx data[:, 48, 9:12] = mx data[:, 47, 8:13] = mx cache = {cacheKey: data} # clear to save memory (but keep one to prevent unnecessary regeneration) data = cache[cacheKey] updateLUT() updateSize() def updateSize(): global ui, vb frames = ui.framesSpin.value() width = ui.widthSpin.value() height = ui.heightSpin.value() dtype = xp.dtype(str(ui.dtypeCombo.currentText())) rgb = 3 if ui.rgbCheck.isChecked() else 1 ui.sizeLabel.setText('%d MB' % (frames * width * height * rgb * dtype.itemsize / 1e6)) vb.setRange(QtCore.QRectF(0, 0, width, height)) def noticeCudaCheck(): global xp, cache cache = {} if ui.cudaCheck.isChecked(): if _has_cupy: xp = cp else: xp = np ui.cudaCheck.setChecked(False) else: xp = np mkData() def noticeNumbaCheck(): pg.setConfigOption('useNumba', _has_numba and ui.numbaCheck.isChecked()) mkData() ui.dtypeCombo.currentIndexChanged.connect(mkData) ui.rgbCheck.toggled.connect(mkData) ui.widthSpin.editingFinished.connect(mkData) ui.heightSpin.editingFinished.connect(mkData) ui.framesSpin.editingFinished.connect(mkData) ui.widthSpin.valueChanged.connect(updateSize) ui.heightSpin.valueChanged.connect(updateSize) ui.framesSpin.valueChanged.connect(updateSize) ui.cudaCheck.toggled.connect(noticeCudaCheck) ui.numbaCheck.toggled.connect(noticeNumbaCheck) ptr = 0 lastTime = perf_counter() fps = None def update(): global ui, ptr, lastTime, fps, LUT, img if ui.lutCheck.isChecked(): useLut = LUT else: useLut = None downsample = ui.downsampleCheck.isChecked() if ui.scaleCheck.isChecked(): if ui.rgbLevelsCheck.isChecked(): useScale = [ [ui.minSpin1.value(), ui.maxSpin1.value()], [ui.minSpin2.value(), ui.maxSpin2.value()], [ui.minSpin3.value(), ui.maxSpin3.value()]] else: useScale = [ui.minSpin1.value(), ui.maxSpin1.value()] else: useScale = None if ui.rawRadio.isChecked(): ui.rawImg.setImage(data[ptr%data.shape[0]], lut=useLut, levels=useScale) ui.stack.setCurrentIndex(1) elif ui.rawGLRadio.isChecked(): ui.rawGLImg.setImage(data[ptr%data.shape[0]], lut=useLut, levels=useScale) ui.stack.setCurrentIndex(2) else: img.setImage(data[ptr%data.shape[0]], autoLevels=False, levels=useScale, lut=useLut, autoDownsample=downsample) ui.stack.setCurrentIndex(0) #img.setImage(data[ptr%data.shape[0]], autoRange=False) ptr += 1 now = perf_counter() 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 ui.fpsLabel.setText('%0.2f fps' % fps) app.processEvents() ## force complete redraw for every plot timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(0) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/VideoTemplate.ui000066400000000000000000000230151421045507400245730ustar00rootroot00000000000000 MainWindow 0 0 695 798 MainWindow Use CUDA (GPU) if available Use Numba if available Auto downsample Scale Data RawImageWidget GraphicsView + ImageItem true 1 0 0 RawGLImageWidget uint8 uint16 float Data type RGB false <---> Qt::AlignCenter false <---> Qt::AlignCenter false <---> Qt::AlignCenter false Use Lookup Table alpha 0 0 Qt::Horizontal 40 20 12 FPS Qt::AlignCenter RGB Image size QAbstractSpinBox::NoButtons 10 QAbstractSpinBox::PlusMinus 10000 512 QAbstractSpinBox::NoButtons 10000 512 GraphicsView QGraphicsView
      pyqtgraph
      RawImageWidget QWidget
      pyqtgraph.widgets.RawImageWidget
      1
      GradientWidget QWidget
      pyqtgraph
      1
      SpinBox QDoubleSpinBox
      pyqtgraph
      pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/VideoTemplate_pyqt5.py000066400000000000000000000264031421045507400257540ustar00rootroot00000000000000 # Form implementation generated from reading ui file '.\VideoTemplate.ui' # # Created by: PyQt5 UI code generator 5.15.4 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(695, 798) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout_2.setObjectName("gridLayout_2") self.cudaCheck = QtWidgets.QCheckBox(self.centralwidget) self.cudaCheck.setObjectName("cudaCheck") self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2) self.numbaCheck = QtWidgets.QCheckBox(self.centralwidget) self.numbaCheck.setObjectName("numbaCheck") self.gridLayout_2.addWidget(self.numbaCheck, 9, 2, 1, 2) self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget) self.downsampleCheck.setObjectName("downsampleCheck") self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2) self.scaleCheck = QtWidgets.QCheckBox(self.centralwidget) self.scaleCheck.setObjectName("scaleCheck") self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1) self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setObjectName("gridLayout") self.rawRadio = QtWidgets.QRadioButton(self.centralwidget) self.rawRadio.setObjectName("rawRadio") self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) self.gfxRadio = QtWidgets.QRadioButton(self.centralwidget) self.gfxRadio.setChecked(True) self.gfxRadio.setObjectName("gfxRadio") self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) self.stack = QtWidgets.QStackedWidget(self.centralwidget) self.stack.setObjectName("stack") self.page = QtWidgets.QWidget() self.page.setObjectName("page") self.gridLayout_3 = QtWidgets.QGridLayout(self.page) self.gridLayout_3.setObjectName("gridLayout_3") self.graphicsView = GraphicsView(self.page) self.graphicsView.setObjectName("graphicsView") self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) self.stack.addWidget(self.page) self.page_2 = QtWidgets.QWidget() self.page_2.setObjectName("page_2") self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2) self.gridLayout_4.setObjectName("gridLayout_4") self.rawImg = RawImageWidget(self.page_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) self.rawImg.setSizePolicy(sizePolicy) self.rawImg.setObjectName("rawImg") self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) self.stack.addWidget(self.page_2) self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) self.rawGLRadio = QtWidgets.QRadioButton(self.centralwidget) self.rawGLRadio.setObjectName("rawGLRadio") self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) self.dtypeCombo = QtWidgets.QComboBox(self.centralwidget) self.dtypeCombo.setObjectName("dtypeCombo") self.dtypeCombo.addItem("") self.dtypeCombo.addItem("") self.dtypeCombo.addItem("") self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1) self.label = QtWidgets.QLabel(self.centralwidget) self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) self.rgbLevelsCheck = QtWidgets.QCheckBox(self.centralwidget) self.rgbLevelsCheck.setObjectName("rgbLevelsCheck") self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1) self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.minSpin2 = SpinBox(self.centralwidget) self.minSpin2.setEnabled(False) self.minSpin2.setObjectName("minSpin2") self.horizontalLayout_2.addWidget(self.minSpin2) self.label_3 = QtWidgets.QLabel(self.centralwidget) self.label_3.setAlignment(QtCore.Qt.AlignCenter) self.label_3.setObjectName("label_3") self.horizontalLayout_2.addWidget(self.label_3) self.maxSpin2 = SpinBox(self.centralwidget) self.maxSpin2.setEnabled(False) self.maxSpin2.setObjectName("maxSpin2") self.horizontalLayout_2.addWidget(self.maxSpin2) self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.minSpin1 = SpinBox(self.centralwidget) self.minSpin1.setObjectName("minSpin1") self.horizontalLayout.addWidget(self.minSpin1) self.label_2 = QtWidgets.QLabel(self.centralwidget) self.label_2.setAlignment(QtCore.Qt.AlignCenter) self.label_2.setObjectName("label_2") self.horizontalLayout.addWidget(self.label_2) self.maxSpin1 = SpinBox(self.centralwidget) self.maxSpin1.setObjectName("maxSpin1") self.horizontalLayout.addWidget(self.maxSpin1) self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.minSpin3 = SpinBox(self.centralwidget) self.minSpin3.setEnabled(False) self.minSpin3.setObjectName("minSpin3") self.horizontalLayout_3.addWidget(self.minSpin3) self.label_4 = QtWidgets.QLabel(self.centralwidget) self.label_4.setAlignment(QtCore.Qt.AlignCenter) self.label_4.setObjectName("label_4") self.horizontalLayout_3.addWidget(self.label_4) self.maxSpin3 = SpinBox(self.centralwidget) self.maxSpin3.setEnabled(False) self.maxSpin3.setObjectName("maxSpin3") self.horizontalLayout_3.addWidget(self.maxSpin3) self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1) self.lutCheck = QtWidgets.QCheckBox(self.centralwidget) self.lutCheck.setObjectName("lutCheck") self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1) self.alphaCheck = QtWidgets.QCheckBox(self.centralwidget) self.alphaCheck.setObjectName("alphaCheck") self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1) self.gradient = GradientWidget(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) self.gradient.setSizePolicy(sizePolicy) self.gradient.setObjectName("gradient") self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1) self.fpsLabel = QtWidgets.QLabel(self.centralwidget) font = QtGui.QFont() font.setPointSize(12) self.fpsLabel.setFont(font) self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter) self.fpsLabel.setObjectName("fpsLabel") self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) self.rgbCheck = QtWidgets.QCheckBox(self.centralwidget) self.rgbCheck.setObjectName("rgbCheck") self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) self.label_5 = QtWidgets.QLabel(self.centralwidget) self.label_5.setObjectName("label_5") self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.framesSpin = QtWidgets.QSpinBox(self.centralwidget) self.framesSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) self.framesSpin.setProperty("value", 10) self.framesSpin.setObjectName("framesSpin") self.horizontalLayout_4.addWidget(self.framesSpin) self.widthSpin = QtWidgets.QSpinBox(self.centralwidget) self.widthSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.PlusMinus) self.widthSpin.setMaximum(10000) self.widthSpin.setProperty("value", 512) self.widthSpin.setObjectName("widthSpin") self.horizontalLayout_4.addWidget(self.widthSpin) self.heightSpin = QtWidgets.QSpinBox(self.centralwidget) self.heightSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) self.heightSpin.setMaximum(10000) self.heightSpin.setProperty("value", 512) self.heightSpin.setObjectName("heightSpin") self.horizontalLayout_4.addWidget(self.heightSpin) self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2) self.sizeLabel = QtWidgets.QLabel(self.centralwidget) self.sizeLabel.setText("") self.sizeLabel.setObjectName("sizeLabel") self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) self.stack.setCurrentIndex(1) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) self.cudaCheck.setText(_translate("MainWindow", "Use CUDA (GPU) if available")) self.numbaCheck.setText(_translate("MainWindow", "Use Numba if available")) self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample")) self.scaleCheck.setText(_translate("MainWindow", "Scale Data")) self.rawRadio.setText(_translate("MainWindow", "RawImageWidget")) self.gfxRadio.setText(_translate("MainWindow", "GraphicsView + ImageItem")) self.rawGLRadio.setText(_translate("MainWindow", "RawGLImageWidget")) self.dtypeCombo.setItemText(0, _translate("MainWindow", "uint8")) self.dtypeCombo.setItemText(1, _translate("MainWindow", "uint16")) self.dtypeCombo.setItemText(2, _translate("MainWindow", "float")) self.label.setText(_translate("MainWindow", "Data type")) self.rgbLevelsCheck.setText(_translate("MainWindow", "RGB")) self.label_3.setText(_translate("MainWindow", "<--->")) self.label_2.setText(_translate("MainWindow", "<--->")) self.label_4.setText(_translate("MainWindow", "<--->")) self.lutCheck.setText(_translate("MainWindow", "Use Lookup Table")) self.alphaCheck.setText(_translate("MainWindow", "alpha")) self.fpsLabel.setText(_translate("MainWindow", "FPS")) self.rgbCheck.setText(_translate("MainWindow", "RGB")) self.label_5.setText(_translate("MainWindow", "Image size")) from pyqtgraph import GradientWidget, GraphicsView, SpinBox from pyqtgraph.widgets.RawImageWidget import RawImageWidget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/VideoTemplate_pyqt6.py000066400000000000000000000266241421045507400257620ustar00rootroot00000000000000# Form implementation generated from reading ui file 'examples/VideoTemplate.ui' # # Created by: PyQt6 UI code generator 6.1.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(695, 798) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout_2.setObjectName("gridLayout_2") self.cudaCheck = QtWidgets.QCheckBox(self.centralwidget) self.cudaCheck.setObjectName("cudaCheck") self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2) self.numbaCheck = QtWidgets.QCheckBox(self.centralwidget) self.numbaCheck.setObjectName("numbaCheck") self.gridLayout_2.addWidget(self.numbaCheck, 9, 2, 1, 2) self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget) self.downsampleCheck.setObjectName("downsampleCheck") self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2) self.scaleCheck = QtWidgets.QCheckBox(self.centralwidget) self.scaleCheck.setObjectName("scaleCheck") self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1) self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setObjectName("gridLayout") self.rawRadio = QtWidgets.QRadioButton(self.centralwidget) self.rawRadio.setObjectName("rawRadio") self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) self.gfxRadio = QtWidgets.QRadioButton(self.centralwidget) self.gfxRadio.setChecked(True) self.gfxRadio.setObjectName("gfxRadio") self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) self.stack = QtWidgets.QStackedWidget(self.centralwidget) self.stack.setObjectName("stack") self.page = QtWidgets.QWidget() self.page.setObjectName("page") self.gridLayout_3 = QtWidgets.QGridLayout(self.page) self.gridLayout_3.setObjectName("gridLayout_3") self.graphicsView = GraphicsView(self.page) self.graphicsView.setObjectName("graphicsView") self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) self.stack.addWidget(self.page) self.page_2 = QtWidgets.QWidget() self.page_2.setObjectName("page_2") self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2) self.gridLayout_4.setObjectName("gridLayout_4") self.rawImg = RawImageWidget(self.page_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) self.rawImg.setSizePolicy(sizePolicy) self.rawImg.setObjectName("rawImg") self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) self.stack.addWidget(self.page_2) self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) self.rawGLRadio = QtWidgets.QRadioButton(self.centralwidget) self.rawGLRadio.setObjectName("rawGLRadio") self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) self.dtypeCombo = QtWidgets.QComboBox(self.centralwidget) self.dtypeCombo.setObjectName("dtypeCombo") self.dtypeCombo.addItem("") self.dtypeCombo.addItem("") self.dtypeCombo.addItem("") self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1) self.label = QtWidgets.QLabel(self.centralwidget) self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) self.rgbLevelsCheck = QtWidgets.QCheckBox(self.centralwidget) self.rgbLevelsCheck.setObjectName("rgbLevelsCheck") self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1) self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.minSpin2 = SpinBox(self.centralwidget) self.minSpin2.setEnabled(False) self.minSpin2.setObjectName("minSpin2") self.horizontalLayout_2.addWidget(self.minSpin2) self.label_3 = QtWidgets.QLabel(self.centralwidget) self.label_3.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.label_3.setObjectName("label_3") self.horizontalLayout_2.addWidget(self.label_3) self.maxSpin2 = SpinBox(self.centralwidget) self.maxSpin2.setEnabled(False) self.maxSpin2.setObjectName("maxSpin2") self.horizontalLayout_2.addWidget(self.maxSpin2) self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.minSpin1 = SpinBox(self.centralwidget) self.minSpin1.setObjectName("minSpin1") self.horizontalLayout.addWidget(self.minSpin1) self.label_2 = QtWidgets.QLabel(self.centralwidget) self.label_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.label_2.setObjectName("label_2") self.horizontalLayout.addWidget(self.label_2) self.maxSpin1 = SpinBox(self.centralwidget) self.maxSpin1.setObjectName("maxSpin1") self.horizontalLayout.addWidget(self.maxSpin1) self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.minSpin3 = SpinBox(self.centralwidget) self.minSpin3.setEnabled(False) self.minSpin3.setObjectName("minSpin3") self.horizontalLayout_3.addWidget(self.minSpin3) self.label_4 = QtWidgets.QLabel(self.centralwidget) self.label_4.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.label_4.setObjectName("label_4") self.horizontalLayout_3.addWidget(self.label_4) self.maxSpin3 = SpinBox(self.centralwidget) self.maxSpin3.setEnabled(False) self.maxSpin3.setObjectName("maxSpin3") self.horizontalLayout_3.addWidget(self.maxSpin3) self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1) self.lutCheck = QtWidgets.QCheckBox(self.centralwidget) self.lutCheck.setObjectName("lutCheck") self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1) self.alphaCheck = QtWidgets.QCheckBox(self.centralwidget) self.alphaCheck.setObjectName("alphaCheck") self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1) self.gradient = GradientWidget(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) self.gradient.setSizePolicy(sizePolicy) self.gradient.setObjectName("gradient") self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1) self.fpsLabel = QtWidgets.QLabel(self.centralwidget) font = QtGui.QFont() font.setPointSize(12) self.fpsLabel.setFont(font) self.fpsLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.fpsLabel.setObjectName("fpsLabel") self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) self.rgbCheck = QtWidgets.QCheckBox(self.centralwidget) self.rgbCheck.setObjectName("rgbCheck") self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) self.label_5 = QtWidgets.QLabel(self.centralwidget) self.label_5.setObjectName("label_5") self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.framesSpin = QtWidgets.QSpinBox(self.centralwidget) self.framesSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.NoButtons) self.framesSpin.setProperty("value", 10) self.framesSpin.setObjectName("framesSpin") self.horizontalLayout_4.addWidget(self.framesSpin) self.widthSpin = QtWidgets.QSpinBox(self.centralwidget) self.widthSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.PlusMinus) self.widthSpin.setMaximum(10000) self.widthSpin.setProperty("value", 512) self.widthSpin.setObjectName("widthSpin") self.horizontalLayout_4.addWidget(self.widthSpin) self.heightSpin = QtWidgets.QSpinBox(self.centralwidget) self.heightSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.NoButtons) self.heightSpin.setMaximum(10000) self.heightSpin.setProperty("value", 512) self.heightSpin.setObjectName("heightSpin") self.horizontalLayout_4.addWidget(self.heightSpin) self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2) self.sizeLabel = QtWidgets.QLabel(self.centralwidget) self.sizeLabel.setText("") self.sizeLabel.setObjectName("sizeLabel") self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) self.stack.setCurrentIndex(1) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) self.cudaCheck.setText(_translate("MainWindow", "Use CUDA (GPU) if available")) self.numbaCheck.setText(_translate("MainWindow", "Use Numba if available")) self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample")) self.scaleCheck.setText(_translate("MainWindow", "Scale Data")) self.rawRadio.setText(_translate("MainWindow", "RawImageWidget")) self.gfxRadio.setText(_translate("MainWindow", "GraphicsView + ImageItem")) self.rawGLRadio.setText(_translate("MainWindow", "RawGLImageWidget")) self.dtypeCombo.setItemText(0, _translate("MainWindow", "uint8")) self.dtypeCombo.setItemText(1, _translate("MainWindow", "uint16")) self.dtypeCombo.setItemText(2, _translate("MainWindow", "float")) self.label.setText(_translate("MainWindow", "Data type")) self.rgbLevelsCheck.setText(_translate("MainWindow", "RGB")) self.label_3.setText(_translate("MainWindow", "<--->")) self.label_2.setText(_translate("MainWindow", "<--->")) self.label_4.setText(_translate("MainWindow", "<--->")) self.lutCheck.setText(_translate("MainWindow", "Use Lookup Table")) self.alphaCheck.setText(_translate("MainWindow", "alpha")) self.fpsLabel.setText(_translate("MainWindow", "FPS")) self.rgbCheck.setText(_translate("MainWindow", "RGB")) self.label_5.setText(_translate("MainWindow", "Image size")) from pyqtgraph import GradientWidget, GraphicsView, SpinBox from pyqtgraph.widgets.RawImageWidget import RawImageWidget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/VideoTemplate_pyside2.py000066400000000000000000000266101421045507400262510ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'VideoTemplate.ui' ## ## Created by: Qt User Interface Compiler version 5.15.2 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide2.QtCore import * from PySide2.QtGui import * from PySide2.QtWidgets import * from pyqtgraph import GraphicsView from pyqtgraph.widgets.RawImageWidget import RawImageWidget from pyqtgraph import GradientWidget from pyqtgraph import SpinBox class Ui_MainWindow(object): def setupUi(self, MainWindow): if not MainWindow.objectName(): MainWindow.setObjectName(u"MainWindow") MainWindow.resize(695, 798) self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(u"centralwidget") self.gridLayout_2 = QGridLayout(self.centralwidget) self.gridLayout_2.setObjectName(u"gridLayout_2") self.cudaCheck = QCheckBox(self.centralwidget) self.cudaCheck.setObjectName(u"cudaCheck") self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2) self.numbaCheck = QCheckBox(self.centralwidget) self.numbaCheck.setObjectName(u"numbaCheck") self.gridLayout_2.addWidget(self.numbaCheck, 9, 2, 1, 2) self.downsampleCheck = QCheckBox(self.centralwidget) self.downsampleCheck.setObjectName(u"downsampleCheck") self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2) self.scaleCheck = QCheckBox(self.centralwidget) self.scaleCheck.setObjectName(u"scaleCheck") self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1) self.gridLayout = QGridLayout() self.gridLayout.setObjectName(u"gridLayout") self.rawRadio = QRadioButton(self.centralwidget) self.rawRadio.setObjectName(u"rawRadio") self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) self.gfxRadio = QRadioButton(self.centralwidget) self.gfxRadio.setObjectName(u"gfxRadio") self.gfxRadio.setChecked(True) self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) self.stack = QStackedWidget(self.centralwidget) self.stack.setObjectName(u"stack") self.page = QWidget() self.page.setObjectName(u"page") self.gridLayout_3 = QGridLayout(self.page) self.gridLayout_3.setObjectName(u"gridLayout_3") self.graphicsView = GraphicsView(self.page) self.graphicsView.setObjectName(u"graphicsView") self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) self.stack.addWidget(self.page) self.page_2 = QWidget() self.page_2.setObjectName(u"page_2") self.gridLayout_4 = QGridLayout(self.page_2) self.gridLayout_4.setObjectName(u"gridLayout_4") self.rawImg = RawImageWidget(self.page_2) self.rawImg.setObjectName(u"rawImg") sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) self.rawImg.setSizePolicy(sizePolicy) self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) self.stack.addWidget(self.page_2) self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) self.rawGLRadio = QRadioButton(self.centralwidget) self.rawGLRadio.setObjectName(u"rawGLRadio") self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) self.dtypeCombo = QComboBox(self.centralwidget) self.dtypeCombo.addItem("") self.dtypeCombo.addItem("") self.dtypeCombo.addItem("") self.dtypeCombo.setObjectName(u"dtypeCombo") self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1) self.label = QLabel(self.centralwidget) self.label.setObjectName(u"label") self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) self.rgbLevelsCheck = QCheckBox(self.centralwidget) self.rgbLevelsCheck.setObjectName(u"rgbLevelsCheck") self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1) self.horizontalLayout_2 = QHBoxLayout() self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") self.minSpin2 = SpinBox(self.centralwidget) self.minSpin2.setObjectName(u"minSpin2") self.minSpin2.setEnabled(False) self.horizontalLayout_2.addWidget(self.minSpin2) self.label_3 = QLabel(self.centralwidget) self.label_3.setObjectName(u"label_3") self.label_3.setAlignment(Qt.AlignCenter) self.horizontalLayout_2.addWidget(self.label_3) self.maxSpin2 = SpinBox(self.centralwidget) self.maxSpin2.setObjectName(u"maxSpin2") self.maxSpin2.setEnabled(False) self.horizontalLayout_2.addWidget(self.maxSpin2) self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName(u"horizontalLayout") self.minSpin1 = SpinBox(self.centralwidget) self.minSpin1.setObjectName(u"minSpin1") self.horizontalLayout.addWidget(self.minSpin1) self.label_2 = QLabel(self.centralwidget) self.label_2.setObjectName(u"label_2") self.label_2.setAlignment(Qt.AlignCenter) self.horizontalLayout.addWidget(self.label_2) self.maxSpin1 = SpinBox(self.centralwidget) self.maxSpin1.setObjectName(u"maxSpin1") self.horizontalLayout.addWidget(self.maxSpin1) self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1) self.horizontalLayout_3 = QHBoxLayout() self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") self.minSpin3 = SpinBox(self.centralwidget) self.minSpin3.setObjectName(u"minSpin3") self.minSpin3.setEnabled(False) self.horizontalLayout_3.addWidget(self.minSpin3) self.label_4 = QLabel(self.centralwidget) self.label_4.setObjectName(u"label_4") self.label_4.setAlignment(Qt.AlignCenter) self.horizontalLayout_3.addWidget(self.label_4) self.maxSpin3 = SpinBox(self.centralwidget) self.maxSpin3.setObjectName(u"maxSpin3") self.maxSpin3.setEnabled(False) self.horizontalLayout_3.addWidget(self.maxSpin3) self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1) self.lutCheck = QCheckBox(self.centralwidget) self.lutCheck.setObjectName(u"lutCheck") self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1) self.alphaCheck = QCheckBox(self.centralwidget) self.alphaCheck.setObjectName(u"alphaCheck") self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1) self.gradient = GradientWidget(self.centralwidget) self.gradient.setObjectName(u"gradient") sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) self.gradient.setSizePolicy(sizePolicy) self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2) self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.gridLayout_2.addItem(self.horizontalSpacer, 3, 3, 1, 1) self.fpsLabel = QLabel(self.centralwidget) self.fpsLabel.setObjectName(u"fpsLabel") font = QFont() font.setPointSize(12) self.fpsLabel.setFont(font) self.fpsLabel.setAlignment(Qt.AlignCenter) self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) self.rgbCheck = QCheckBox(self.centralwidget) self.rgbCheck.setObjectName(u"rgbCheck") self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) self.label_5 = QLabel(self.centralwidget) self.label_5.setObjectName(u"label_5") self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1) self.horizontalLayout_4 = QHBoxLayout() self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") self.framesSpin = QSpinBox(self.centralwidget) self.framesSpin.setObjectName(u"framesSpin") self.framesSpin.setButtonSymbols(QAbstractSpinBox.NoButtons) self.framesSpin.setValue(10) self.horizontalLayout_4.addWidget(self.framesSpin) self.widthSpin = QSpinBox(self.centralwidget) self.widthSpin.setObjectName(u"widthSpin") self.widthSpin.setButtonSymbols(QAbstractSpinBox.PlusMinus) self.widthSpin.setMaximum(10000) self.widthSpin.setValue(512) self.horizontalLayout_4.addWidget(self.widthSpin) self.heightSpin = QSpinBox(self.centralwidget) self.heightSpin.setObjectName(u"heightSpin") self.heightSpin.setButtonSymbols(QAbstractSpinBox.NoButtons) self.heightSpin.setMaximum(10000) self.heightSpin.setValue(512) self.horizontalLayout_4.addWidget(self.heightSpin) self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2) self.sizeLabel = QLabel(self.centralwidget) self.sizeLabel.setObjectName(u"sizeLabel") self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) self.stack.setCurrentIndex(1) QMetaObject.connectSlotsByName(MainWindow) # setupUi def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) self.cudaCheck.setText(QCoreApplication.translate("MainWindow", u"Use CUDA (GPU) if available", None)) self.numbaCheck.setText(QCoreApplication.translate("MainWindow", u"Use Numba if available", None)) self.downsampleCheck.setText(QCoreApplication.translate("MainWindow", u"Auto downsample", None)) self.scaleCheck.setText(QCoreApplication.translate("MainWindow", u"Scale Data", None)) self.rawRadio.setText(QCoreApplication.translate("MainWindow", u"RawImageWidget", None)) self.gfxRadio.setText(QCoreApplication.translate("MainWindow", u"GraphicsView + ImageItem", None)) self.rawGLRadio.setText(QCoreApplication.translate("MainWindow", u"RawGLImageWidget", None)) self.dtypeCombo.setItemText(0, QCoreApplication.translate("MainWindow", u"uint8", None)) self.dtypeCombo.setItemText(1, QCoreApplication.translate("MainWindow", u"uint16", None)) self.dtypeCombo.setItemText(2, QCoreApplication.translate("MainWindow", u"float", None)) self.label.setText(QCoreApplication.translate("MainWindow", u"Data type", None)) self.rgbLevelsCheck.setText(QCoreApplication.translate("MainWindow", u"RGB", None)) self.label_3.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) self.label_2.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) self.label_4.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) self.lutCheck.setText(QCoreApplication.translate("MainWindow", u"Use Lookup Table", None)) self.alphaCheck.setText(QCoreApplication.translate("MainWindow", u"alpha", None)) self.fpsLabel.setText(QCoreApplication.translate("MainWindow", u"FPS", None)) self.rgbCheck.setText(QCoreApplication.translate("MainWindow", u"RGB", None)) self.label_5.setText(QCoreApplication.translate("MainWindow", u"Image size", None)) self.sizeLabel.setText("") # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/VideoTemplate_pyside6.py000066400000000000000000000266071421045507400262630ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'VideoTemplate.ui' ## ## Created by: Qt User Interface Compiler version 6.1.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * from pyqtgraph import GraphicsView from pyqtgraph.widgets.RawImageWidget import RawImageWidget from pyqtgraph import GradientWidget from pyqtgraph import SpinBox class Ui_MainWindow(object): def setupUi(self, MainWindow): if not MainWindow.objectName(): MainWindow.setObjectName(u"MainWindow") MainWindow.resize(695, 798) self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(u"centralwidget") self.gridLayout_2 = QGridLayout(self.centralwidget) self.gridLayout_2.setObjectName(u"gridLayout_2") self.cudaCheck = QCheckBox(self.centralwidget) self.cudaCheck.setObjectName(u"cudaCheck") self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2) self.numbaCheck = QCheckBox(self.centralwidget) self.numbaCheck.setObjectName(u"numbaCheck") self.gridLayout_2.addWidget(self.numbaCheck, 9, 2, 1, 2) self.downsampleCheck = QCheckBox(self.centralwidget) self.downsampleCheck.setObjectName(u"downsampleCheck") self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2) self.scaleCheck = QCheckBox(self.centralwidget) self.scaleCheck.setObjectName(u"scaleCheck") self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1) self.gridLayout = QGridLayout() self.gridLayout.setObjectName(u"gridLayout") self.rawRadio = QRadioButton(self.centralwidget) self.rawRadio.setObjectName(u"rawRadio") self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) self.gfxRadio = QRadioButton(self.centralwidget) self.gfxRadio.setObjectName(u"gfxRadio") self.gfxRadio.setChecked(True) self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) self.stack = QStackedWidget(self.centralwidget) self.stack.setObjectName(u"stack") self.page = QWidget() self.page.setObjectName(u"page") self.gridLayout_3 = QGridLayout(self.page) self.gridLayout_3.setObjectName(u"gridLayout_3") self.graphicsView = GraphicsView(self.page) self.graphicsView.setObjectName(u"graphicsView") self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) self.stack.addWidget(self.page) self.page_2 = QWidget() self.page_2.setObjectName(u"page_2") self.gridLayout_4 = QGridLayout(self.page_2) self.gridLayout_4.setObjectName(u"gridLayout_4") self.rawImg = RawImageWidget(self.page_2) self.rawImg.setObjectName(u"rawImg") sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) self.rawImg.setSizePolicy(sizePolicy) self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) self.stack.addWidget(self.page_2) self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) self.rawGLRadio = QRadioButton(self.centralwidget) self.rawGLRadio.setObjectName(u"rawGLRadio") self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) self.dtypeCombo = QComboBox(self.centralwidget) self.dtypeCombo.addItem("") self.dtypeCombo.addItem("") self.dtypeCombo.addItem("") self.dtypeCombo.setObjectName(u"dtypeCombo") self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1) self.label = QLabel(self.centralwidget) self.label.setObjectName(u"label") self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) self.rgbLevelsCheck = QCheckBox(self.centralwidget) self.rgbLevelsCheck.setObjectName(u"rgbLevelsCheck") self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1) self.horizontalLayout_2 = QHBoxLayout() self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") self.minSpin2 = SpinBox(self.centralwidget) self.minSpin2.setObjectName(u"minSpin2") self.minSpin2.setEnabled(False) self.horizontalLayout_2.addWidget(self.minSpin2) self.label_3 = QLabel(self.centralwidget) self.label_3.setObjectName(u"label_3") self.label_3.setAlignment(Qt.AlignCenter) self.horizontalLayout_2.addWidget(self.label_3) self.maxSpin2 = SpinBox(self.centralwidget) self.maxSpin2.setObjectName(u"maxSpin2") self.maxSpin2.setEnabled(False) self.horizontalLayout_2.addWidget(self.maxSpin2) self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName(u"horizontalLayout") self.minSpin1 = SpinBox(self.centralwidget) self.minSpin1.setObjectName(u"minSpin1") self.horizontalLayout.addWidget(self.minSpin1) self.label_2 = QLabel(self.centralwidget) self.label_2.setObjectName(u"label_2") self.label_2.setAlignment(Qt.AlignCenter) self.horizontalLayout.addWidget(self.label_2) self.maxSpin1 = SpinBox(self.centralwidget) self.maxSpin1.setObjectName(u"maxSpin1") self.horizontalLayout.addWidget(self.maxSpin1) self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1) self.horizontalLayout_3 = QHBoxLayout() self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") self.minSpin3 = SpinBox(self.centralwidget) self.minSpin3.setObjectName(u"minSpin3") self.minSpin3.setEnabled(False) self.horizontalLayout_3.addWidget(self.minSpin3) self.label_4 = QLabel(self.centralwidget) self.label_4.setObjectName(u"label_4") self.label_4.setAlignment(Qt.AlignCenter) self.horizontalLayout_3.addWidget(self.label_4) self.maxSpin3 = SpinBox(self.centralwidget) self.maxSpin3.setObjectName(u"maxSpin3") self.maxSpin3.setEnabled(False) self.horizontalLayout_3.addWidget(self.maxSpin3) self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1) self.lutCheck = QCheckBox(self.centralwidget) self.lutCheck.setObjectName(u"lutCheck") self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1) self.alphaCheck = QCheckBox(self.centralwidget) self.alphaCheck.setObjectName(u"alphaCheck") self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1) self.gradient = GradientWidget(self.centralwidget) self.gradient.setObjectName(u"gradient") sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) self.gradient.setSizePolicy(sizePolicy) self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2) self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.gridLayout_2.addItem(self.horizontalSpacer, 3, 3, 1, 1) self.fpsLabel = QLabel(self.centralwidget) self.fpsLabel.setObjectName(u"fpsLabel") font = QFont() font.setPointSize(12) self.fpsLabel.setFont(font) self.fpsLabel.setAlignment(Qt.AlignCenter) self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) self.rgbCheck = QCheckBox(self.centralwidget) self.rgbCheck.setObjectName(u"rgbCheck") self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) self.label_5 = QLabel(self.centralwidget) self.label_5.setObjectName(u"label_5") self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1) self.horizontalLayout_4 = QHBoxLayout() self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") self.framesSpin = QSpinBox(self.centralwidget) self.framesSpin.setObjectName(u"framesSpin") self.framesSpin.setButtonSymbols(QAbstractSpinBox.NoButtons) self.framesSpin.setValue(10) self.horizontalLayout_4.addWidget(self.framesSpin) self.widthSpin = QSpinBox(self.centralwidget) self.widthSpin.setObjectName(u"widthSpin") self.widthSpin.setButtonSymbols(QAbstractSpinBox.PlusMinus) self.widthSpin.setMaximum(10000) self.widthSpin.setValue(512) self.horizontalLayout_4.addWidget(self.widthSpin) self.heightSpin = QSpinBox(self.centralwidget) self.heightSpin.setObjectName(u"heightSpin") self.heightSpin.setButtonSymbols(QAbstractSpinBox.NoButtons) self.heightSpin.setMaximum(10000) self.heightSpin.setValue(512) self.horizontalLayout_4.addWidget(self.heightSpin) self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2) self.sizeLabel = QLabel(self.centralwidget) self.sizeLabel.setObjectName(u"sizeLabel") self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) self.stack.setCurrentIndex(1) QMetaObject.connectSlotsByName(MainWindow) # setupUi def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) self.cudaCheck.setText(QCoreApplication.translate("MainWindow", u"Use CUDA (GPU) if available", None)) self.numbaCheck.setText(QCoreApplication.translate("MainWindow", u"Use Numba if available", None)) self.downsampleCheck.setText(QCoreApplication.translate("MainWindow", u"Auto downsample", None)) self.scaleCheck.setText(QCoreApplication.translate("MainWindow", u"Scale Data", None)) self.rawRadio.setText(QCoreApplication.translate("MainWindow", u"RawImageWidget", None)) self.gfxRadio.setText(QCoreApplication.translate("MainWindow", u"GraphicsView + ImageItem", None)) self.rawGLRadio.setText(QCoreApplication.translate("MainWindow", u"RawGLImageWidget", None)) self.dtypeCombo.setItemText(0, QCoreApplication.translate("MainWindow", u"uint8", None)) self.dtypeCombo.setItemText(1, QCoreApplication.translate("MainWindow", u"uint16", None)) self.dtypeCombo.setItemText(2, QCoreApplication.translate("MainWindow", u"float", None)) self.label.setText(QCoreApplication.translate("MainWindow", u"Data type", None)) self.rgbLevelsCheck.setText(QCoreApplication.translate("MainWindow", u"RGB", None)) self.label_3.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) self.label_2.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) self.label_4.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) self.lutCheck.setText(QCoreApplication.translate("MainWindow", u"Use Lookup Table", None)) self.alphaCheck.setText(QCoreApplication.translate("MainWindow", u"alpha", None)) self.fpsLabel.setText(QCoreApplication.translate("MainWindow", u"FPS", None)) self.rgbCheck.setText(QCoreApplication.translate("MainWindow", u"RGB", None)) self.label_5.setText(QCoreApplication.translate("MainWindow", u"Image size", None)) self.sizeLabel.setText("") # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ViewBox.py000066400000000000000000000047071421045507400234360ustar00rootroot00000000000000#!/usr/bin/python """ 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 construction of a ViewBox-based plot area with axes, very similar to the way PlotItem is built. """ ## This example uses a ViewBox to create a PlotWidget-like interface import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtWidgets app = pg.mkQApp("ViewBox Example") mw = QtWidgets.QMainWindow() mw.setWindowTitle('pyqtgraph example: ViewBox') mw.show() mw.resize(800, 600) gv = pg.GraphicsView() mw.setCentralWidget(gv) l = QtWidgets.QGraphicsGridLayout() l.setHorizontalSpacing(0) l.setVerticalSpacing(0) vb = pg.ViewBox() p1 = pg.PlotDataItem() vb.addItem(p1) ## Just something to play with inside the ViewBox class movableRect(QtWidgets.QGraphicsRectItem): def __init__(self, *args): QtWidgets.QGraphicsRectItem.__init__(self, *args) self.setAcceptHoverEvents(True) def hoverEnterEvent(self, ev): self.savedPen = self.pen() self.setPen(pg.mkPen(255, 255, 255)) ev.ignore() def hoverLeaveEvent(self, ev): self.setPen(self.savedPen) ev.ignore() def mousePressEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.LeftButton: ev.accept() self.pressDelta = self.mapToParent(ev.pos()) - self.pos() else: ev.ignore() def mouseMoveEvent(self, ev): self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) rect = movableRect(QtCore.QRectF(0, 0, 1, 1)) rect.setPen(pg.mkPen(100, 200, 100)) vb.addItem(rect) l.addItem(vb, 0, 1) gv.centralWidget.setLayout(l) xScale = pg.AxisItem(orientation='bottom', linkView=vb) l.addItem(xScale, 1, 1) yScale = pg.AxisItem(orientation='left', linkView=vb) l.addItem(yScale, 0, 0) xScale.setLabel(text="X Axis", units="s") yScale.setLabel('Y Axis', units='V') def rand(n): data = np.random.random(n) data[int(n*0.1):int(n*0.13)] += .5 data[int(n*0.18)] += 2 data[int(n*0.1):int(n*0.13)] *= 5 data[int(n*0.18)] *= 20 return data, np.arange(n, n+len(data)) / float(n) def updateData(): yd, xd = rand(10000) p1.setData(y=yd, x=xd) yd, xd = rand(10000) updateData() vb.autoRange() t = QtCore.QTimer() t.timeout.connect(updateData) t.start(50) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ViewBoxFeatures.py000066400000000000000000000041511421045507400251260ustar00rootroot00000000000000""" ViewBox is the general-purpose graphical container that allows the user to zoom / pan to inspect any area of a 2D coordinate system. This example demonstrates many of the features ViewBox provides. """ import numpy as np import pyqtgraph as pg x = np.arange(1000, dtype=float) y = np.random.normal(size=1000) y += 5 * np.sin(x/100) win = pg.GraphicsLayoutWidget(show=True) win.setWindowTitle('pyqtgraph example: ____') win.resize(1000, 800) win.ci.setBorder((50, 50, 100)) sub1 = win.addLayout() sub1.addLabel("Standard mouse interaction:
      left-drag to pan, right-drag to zoom.") sub1.nextRow() v1 = sub1.addViewBox() l1 = pg.PlotDataItem(y) v1.addItem(l1) sub2 = win.addLayout() sub2.addLabel("One-button mouse interaction:
      left-drag zoom to box, wheel to zoom out.") sub2.nextRow() v2 = sub2.addViewBox() v2.setMouseMode(v2.RectMode) l2 = pg.PlotDataItem(y) v2.addItem(l2) win.nextRow() sub3 = win.addLayout() sub3.addLabel("Locked aspect ratio when zooming.") sub3.nextRow() v3 = sub3.addViewBox() v3.setAspectLocked(1.0) l3 = pg.PlotDataItem(y) v3.addItem(l3) sub4 = win.addLayout() sub4.addLabel("View limits:
      prevent panning or zooming past limits.") sub4.nextRow() v4 = sub4.addViewBox() v4.setLimits(xMin=-100, xMax=1100, minXRange=20, maxXRange=500, yMin=-10, yMax=10, minYRange=1, maxYRange=10) l4 = pg.PlotDataItem(y) v4.addItem(l4) win.nextRow() sub5 = win.addLayout() sub5.addLabel("Linked axes: Data in this plot is always X-aligned to
      the plot above.") sub5.nextRow() v5 = sub5.addViewBox() v5.setXLink(v3) l5 = pg.PlotDataItem(y) v5.addItem(l5) sub6 = win.addLayout() sub6.addLabel("Disable mouse: Per-axis control over mouse input.
      " "Auto-scale-visible: Automatically fit *visible* data within view
      " "(try panning left-right).") sub6.nextRow() v6 = sub6.addViewBox() v6.setMouseEnabled(x=True, y=False) v6.enableAutoRange(x=False, y=True) v6.setXRange(300, 450) v6.setAutoVisible(x=False, y=True) l6 = pg.PlotDataItem(y) v6.addItem(l6) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/ViewLimits.py000066400000000000000000000003531421045507400241400ustar00rootroot00000000000000import numpy as np import pyqtgraph as pg plt = pg.plot(np.random.normal(size=100), title="View limit example") plt.centralWidget.vb.setLimits(xMin=-20, xMax=120, minXRange=5, maxXRange=100) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/__init__.py000066400000000000000000000000441421045507400236000ustar00rootroot00000000000000from .ExampleApp import main as run pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/__main__.py000066400000000000000000000001171421045507400235620ustar00rootroot00000000000000 if __name__ == '__main__': from .ExampleApp import main as run run() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/_buildParamTypes.py000066400000000000000000000063261421045507400253160ustar00rootroot00000000000000from _paramtreecfg import cfg from pyqtgraph.parametertree import Parameter from pyqtgraph.parametertree.Parameter import PARAM_TYPES from pyqtgraph.parametertree.parameterTypes import GroupParameter _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 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/_paramtreecfg.py000066400000000000000000000114171421045507400246460ustar00rootroot00000000000000import numpy as np from pyqtgraph.parametertree.parameterTypes import QtEnumParameter as enum from pyqtgraph.Qt import QtWidgets 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, }, 'delay': { 'type': 'float', 'value': 1.0, 'limits': [0, None] } }, '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', } } pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/beeswarm.py000066400000000000000000000014311421045507400236470ustar00rootroot00000000000000""" Example beeswarm / bar chart """ import numpy as np import pyqtgraph as pg win = pg.plot() win.setWindowTitle('pyqtgraph example: beeswarm') data = np.random.normal(size=(4,20)) data[0] += 5 data[1] += 7 data[2] += 5 data[3] = 10 + data[3] * 2 ## Make bar graph #bar = pg.BarGraphItem(x=range(4), height=data.mean(axis=1), width=0.5, brush=0.4) #win.addItem(bar) ## add scatter plots on top for i in range(4): xvals = pg.pseudoScatter(data[i], spacing=0.4, bidir=True) * 0.2 win.plot(x=xvals+i, y=data[i], pen=None, symbol='o', symbolBrush=pg.intColor(i,6,maxValue=128)) ## Make error bars err = pg.ErrorBarItem(x=np.arange(4), y=data.mean(axis=1), height=data.std(axis=1), beam=0.5, pen={'color':'w', 'width':2}) win.addItem(err) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/colorMaps.py000066400000000000000000000040051421045507400240010ustar00rootroot00000000000000""" This example displays all color maps currently available, either as local data or imported from Matplotlib of ColorCET. """ import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtWidgets app = pg.mkQApp() win = QtWidgets.QMainWindow() win.resize(1000,800) lw = pg.GraphicsLayoutWidget() lw.setFixedWidth(1000) lw.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) scr = QtWidgets.QScrollArea() scr.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn) scr.setWidget(lw) win.setCentralWidget(scr) win.setWindowTitle('pyqtgraph example: Color maps') win.show() bar_width = 32 bar_data = pg.colormap.modulatedBarData(width=bar_width) num_bars = 0 def add_heading(lw, name): global num_bars lw.addLabel('=== '+name+' ===') num_bars += 1 lw.nextRow() def add_bar(lw, name, cm): global num_bars lw.addLabel(name) imi = pg.ImageItem( bar_data ) imi.setLookupTable( cm.getLookupTable(alpha=True) ) vb = lw.addViewBox(lockAspect=True, enableMouse=False) vb.addItem( imi ) num_bars += 1 lw.nextRow() add_heading(lw, 'local color maps') list_of_maps = pg.colormap.listMaps() list_of_maps = sorted( list_of_maps, key=lambda x: x.swapcase() ) for map_name in list_of_maps: cm = pg.colormap.get(map_name) add_bar(lw, map_name, cm) add_heading(lw, 'Matplotlib import') list_of_maps = pg.colormap.listMaps('matplotlib') list_of_maps = sorted( list_of_maps, key=lambda x: x.lower() ) for map_name in list_of_maps: cm = pg.colormap.get(map_name, source='matplotlib', skipCache=True) if cm is not None: add_bar(lw, map_name, cm) add_heading(lw, 'ColorCET import') list_of_maps = pg.colormap.listMaps('colorcet') list_of_maps = sorted( list_of_maps, key=lambda x: x.lower() ) for map_name in list_of_maps: cm = pg.colormap.get(map_name, source='colorcet', skipCache=True) if cm is not None: add_bar(lw, map_name, cm) lw.setFixedHeight(num_bars * (bar_width+5) ) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/colorMapsLinearized.py000066400000000000000000000060741421045507400260200ustar00rootroot00000000000000""" This example demonstrates linearized ColorMap objects using colormap.makeMonochrome() or using the `ColorMap`'s `linearize()` method. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui, QtWidgets name_list = ( 'warm','neutral','cool', 'green','amber','blue','red','pink','lavender', (0.5, 0.2, 0.1, 0.8) ) ramp_list = [ pg.colormap.makeMonochrome(name) for name in name_list ] cm_list = [] # Create a gray ramp for demonstrating the idea: cm = pg.ColorMap( None, [ QtGui.QColor( 0, 0, 0), QtGui.QColor( 10, 10, 10), QtGui.QColor(127, 127, 127), QtGui.QColor(240, 240, 240), QtGui.QColor(255, 255, 255) ]) cm_list.append(('Distorted gray ramp',cm)) # Create a rainbow scale in HSL color space: length = 41 col_list = [] for idx in range(length): frac = idx/(length-1) qcol = QtGui.QColor() qcol.setHslF( (2*frac-0.15)%1.0, 0.8, 0.5-0.5*np.cos(np.pi*frac) ) col_list.append(qcol) cm = pg.ColorMap( None, col_list ) cm_list.append( ('Distorted HSL spiral', cm) ) # Create some random examples: for example_idx in range(3): previous = None col_list = [] for idx in range(8): values = np.random.random((3)) if previous is not None: intermediate = (values + previous) / 2 qcol = QtGui.QColor() qcol.setRgbF( *intermediate ) col_list.append( qcol) qcol1 = QtGui.QColor() qcol1.setRgbF( *values ) col_list.append( qcol1) previous = values cm = pg.ColorMap( None, col_list ) cm_list.append( (f'random {example_idx+1}', cm) ) app = pg.mkQApp() win = QtWidgets.QMainWindow() win.resize(1000,800) lw = pg.GraphicsLayoutWidget() lw.setFixedWidth(1000) lw.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) scr = QtWidgets.QScrollArea() scr.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn) scr.setWidget(lw) win.setCentralWidget(scr) win.setWindowTitle('pyqtgraph example: Linearized color maps') win.show() bar_width = 32 bar_data = pg.colormap.modulatedBarData(width=bar_width) num_bars = 0 def add_heading(lw, name): global num_bars lw.addLabel('=== '+name+' ===') num_bars += 1 lw.nextRow() def add_bar(lw, name, cm): global num_bars lw.addLabel(name) imi = pg.ImageItem( bar_data ) imi.setLookupTable( cm.getLookupTable(alpha=True) ) vb = lw.addViewBox(lockAspect=True, enableMouse=False) vb.addItem( imi ) num_bars += 1 lw.nextRow() add_heading(lw, 'ramp generator') for cm in ramp_list: add_bar(lw, cm.name, cm) add_heading(lw, 'linearizer demonstration') for (name, cm) in cm_list: add_bar(lw, name, cm) cm.linearize() add_bar(lw, '> linearized', cm) add_heading(lw, 'consistency with included maps') for name in ('CET-C3', 'CET-L17', 'CET-L2'): # lw.addLabel(str(name)) cm = pg.colormap.get(name) add_bar(lw, name, cm) cm.linearize() add_bar(lw, '> linearized', cm) lw.setFixedHeight(num_bars * (bar_width+5) ) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/contextMenu.py000066400000000000000000000102111421045507400243470ustar00rootroot00000000000000""" Demonstrates adding a custom context menu to a GraphicsItem and extending the context menu of a ViewBox. PyQtGraph implements a system that allows each item in a scene to implement its own context menu, and for the menus of its parent items to be automatically displayed as well. """ import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui, QtWidgets win = pg.GraphicsLayoutWidget(show=True) win.setWindowTitle('pyqtgraph example: context menu') view = win.addViewBox() # add two new actions to the ViewBox context menu: zoom1 = view.menu.addAction('Zoom to box 1') zoom2 = view.menu.addAction('Zoom to box 2') # define callbacks for these actions def zoomTo1(): # note that box1 is defined below view.autoRange(items=[box1]) zoom1.triggered.connect(zoomTo1) def zoomTo2(): # note that box1 is defined below view.autoRange(items=[box2]) zoom2.triggered.connect(zoomTo2) class MenuBox(pg.GraphicsObject): """ This class draws a rectangular area. Right-clicking inside the area will raise a custom context menu which also includes the context menus of its parents. """ def __init__(self, name): self.name = name self.pen = pg.mkPen('r') # menu creation is deferred because it is expensive and often # the user will never see the menu anyway. self.menu = None # note that the use of super() is often avoided because Qt does not # allow to inherit from multiple QObject subclasses. pg.GraphicsObject.__init__(self) # All graphics items must have paint() and boundingRect() defined. def boundingRect(self): return QtCore.QRectF(0, 0, 10, 10) def paint(self, p, *args): p.setPen(self.pen) p.drawRect(self.boundingRect()) # On right-click, raise the context menu def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.RightButton: if self.raiseContextMenu(ev): ev.accept() def raiseContextMenu(self, ev): menu = self.getContextMenus() # Let the scene add on to the end of our context menu # (this is optional) menu = self.scene().addParentContextMenus(self, menu, ev) pos = ev.screenPos() menu.popup(QtCore.QPoint(pos.x(), pos.y())) return True # This method will be called when this item's _children_ want to raise # a context menu that includes their parents' menus. def getContextMenus(self, event=None): if self.menu is None: self.menu = QtWidgets.QMenu() self.menu.setTitle(self.name+ " options..") green = QtGui.QAction("Turn green", self.menu) green.triggered.connect(self.setGreen) self.menu.addAction(green) self.menu.green = green blue = QtGui.QAction("Turn blue", self.menu) blue.triggered.connect(self.setBlue) self.menu.addAction(blue) self.menu.green = blue alpha = QtWidgets.QWidgetAction(self.menu) alphaSlider = QtWidgets.QSlider() alphaSlider.setOrientation(QtCore.Qt.Orientation.Horizontal) alphaSlider.setMaximum(255) alphaSlider.setValue(255) alphaSlider.valueChanged.connect(self.setAlpha) alpha.setDefaultWidget(alphaSlider) self.menu.addAction(alpha) self.menu.alpha = alpha self.menu.alphaSlider = alphaSlider return self.menu # Define context menu callbacks def setGreen(self): self.pen = pg.mkPen('g') # inform Qt that this item must be redrawn. self.update() def setBlue(self): self.pen = pg.mkPen('b') self.update() def setAlpha(self, a): self.setOpacity(a/255.) # This box's context menu will include the ViewBox's menu box1 = MenuBox("Menu Box #1") view.addItem(box1) # This box's context menu will include both the ViewBox's menu and box1's menu box2 = MenuBox("Menu Box #2") box2.setParentItem(box1) box2.setPos(5, 5) box2.setScale(0.2) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/crosshair.py000066400000000000000000000047671421045507400240560ustar00rootroot00000000000000""" Demonstrates some customized mouse interaction by drawing a crosshair that follows the mouse. """ import numpy as np import pyqtgraph as pg #generate layout app = pg.mkQApp("Crosshair Example") win = pg.GraphicsLayoutWidget(show=True) win.setWindowTitle('pyqtgraph example: crosshair') label = pg.LabelItem(justify='right') win.addItem(label) p1 = win.addPlot(row=1, col=0) # customize the averaged curve that can be activated from the context menu: p1.avgPen = pg.mkPen('#FFFFFF') p1.avgShadowPen = pg.mkPen('#8080DD', width=10) p2 = win.addPlot(row=2, col=0) region = pg.LinearRegionItem() region.setZValue(10) # Add the LinearRegionItem to the ViewBox, but tell the ViewBox to exclude this # item when doing auto-range calculations. p2.addItem(region, ignoreBounds=True) #pg.dbg() p1.setAutoVisible(y=True) #create numpy arrays #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") p2d = p2.plot(data1, pen="w") # bound the LinearRegionItem to the plotted data region.setClipItem(p2d) def update(): region.setZValue(10) minX, maxX = region.getRegion() p1.setXRange(minX, maxX, padding=0) region.sigRegionChanged.connect(update) def updateRegion(window, viewRange): rgn = viewRange[0] region.setRegion(rgn) p1.sigRangeChanged.connect(updateRegion) region.setRegion([1000, 2000]) #cross hair vLine = pg.InfiniteLine(angle=90, movable=False) hLine = pg.InfiniteLine(angle=0, movable=False) p1.addItem(vLine, ignoreBounds=True) p1.addItem(hLine, ignoreBounds=True) vb = p1.vb def mouseMoved(evt): pos = evt[0] ## using signal proxy turns original arguments into a tuple if p1.sceneBoundingRect().contains(pos): mousePoint = vb.mapSceneToView(pos) index = int(mousePoint.x()) if index > 0 and index < len(data1): label.setText("x=%0.1f, y1=%0.1f, y2=%0.1f" % (mousePoint.x(), data1[index], data2[index])) vLine.setPos(mousePoint.x()) hLine.setPos(mousePoint.y()) proxy = pg.SignalProxy(p1.scene().sigMouseMoved, rateLimit=60, slot=mouseMoved) #p1.scene().sigMouseMoved.connect(mouseMoved) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/customGraphicsItem.py000066400000000000000000000036421421045507400256620ustar00rootroot00000000000000""" Demonstrate creation of a custom graphic (a candlestick plot) """ import pyqtgraph as pg from pyqtgraph import QtCore, QtGui ## Create a subclass of GraphicsObject. ## The only required methods are paint() and boundingRect() ## (see QGraphicsItem documentation) class CandlestickItem(pg.GraphicsObject): def __init__(self, data): pg.GraphicsObject.__init__(self) self.data = data ## data must have fields: time, open, close, min, max self.generatePicture() def generatePicture(self): ## pre-computing a QPicture object allows paint() to run much more quickly, ## rather than re-drawing the shapes every time. self.picture = QtGui.QPicture() p = QtGui.QPainter(self.picture) p.setPen(pg.mkPen('w')) w = (self.data[1][0] - self.data[0][0]) / 3. for (t, open, close, min, max) in self.data: p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max)) if open > close: p.setBrush(pg.mkBrush('r')) else: p.setBrush(pg.mkBrush('g')) p.drawRect(QtCore.QRectF(t-w, open, w*2, close-open)) p.end() def paint(self, p, *args): p.drawPicture(0, 0, self.picture) def boundingRect(self): ## boundingRect _must_ indicate the entire area that will be drawn on ## or else we will get artifacts and possibly crashing. ## (in this case, QPicture does all the work of computing the bouning rect for us) return QtCore.QRectF(self.picture.boundingRect()) data = [ ## fields are (time, open, close, min, max). (1., 10, 13, 5, 15), (2., 13, 17, 9, 20), (3., 17, 14, 11, 23), (4., 14, 15, 5, 19), (5., 15, 9, 8, 22), (6., 9, 15, 8, 16), ] item = CandlestickItem(data) plt = pg.plot() plt.addItem(item) plt.setWindowTitle('pyqtgraph example: customGraphicsItem') if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/customPlot.py000066400000000000000000000056311421045507400242210ustar00rootroot00000000000000""" This example demonstrates the creation of a plot with DateAxisItem and a customized ViewBox. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore class CustomViewBox(pg.ViewBox): def __init__(self, *args, **kwds): kwds['enableMenu'] = False pg.ViewBox.__init__(self, *args, **kwds) self.setMouseMode(self.RectMode) ## reimplement right-click to zoom out def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.RightButton: self.autoRange() ## reimplement mouseDragEvent to disable continuous axis zoom def mouseDragEvent(self, ev, axis=None): if axis is not None and ev.button() == QtCore.Qt.MouseButton.RightButton: ev.ignore() else: pg.ViewBox.mouseDragEvent(self, ev, axis=axis) class CustomTickSliderItem(pg.TickSliderItem): def __init__(self, *args, **kwds): pg.TickSliderItem.__init__(self, *args, **kwds) self.all_ticks = {} self._range = [0,1] def setTicks(self, ticks): for tick, pos in self.listTicks(): self.removeTick(tick) for pos in ticks: tickItem = self.addTick(pos, movable=False, color="#333333") self.all_ticks[pos] = tickItem self.updateRange(None, self._range) def updateRange(self, vb, viewRange): origin = self.tickSize/2. length = self.length lengthIncludingPadding = length + self.tickSize + 2 self._range = viewRange for pos in self.all_ticks: tickValueIncludingPadding = (pos - viewRange[0]) / (viewRange[1] - viewRange[0]) tickValue = (tickValueIncludingPadding*lengthIncludingPadding - origin) / length # Convert from np.bool_ to bool for setVisible visible = bool(tickValue >= 0 and tickValue <= 1) tick = self.all_ticks[pos] tick.setVisible(visible) if visible: self.setTickValue(tick, tickValue) app = pg.mkQApp() axis = pg.DateAxisItem(orientation='bottom') vb = CustomViewBox() pw = pg.PlotWidget(viewBox=vb, axisItems={'bottom': axis}, enableMenu=False, title="PlotItem with DateAxisItem, custom ViewBox and markers on x axis
      Menu disabled, mouse behavior changed: left-drag to zoom, right-click to reset zoom") dates = np.arange(8) * (3600*24*356) pw.plot(x=dates, y=[1,6,2,4,3,5,6,8], symbol='o') # Using allowAdd and allowRemove to limit user interaction tickViewer = CustomTickSliderItem(allowAdd=False, allowRemove=False) vb.sigXRangeChanged.connect(tickViewer.updateRange) pw.plotItem.layout.addItem(tickViewer, 4, 1) tickViewer.setTicks( [dates[0], dates[2], dates[-1]] ) pw.show() pw.setWindowTitle('pyqtgraph example: customPlot') r = pg.PolyLineROI([(0,0), (10, 10)]) pw.addItem(r) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/cx_freeze/000077500000000000000000000000001421045507400234435ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/cx_freeze/plotTest.py000066400000000000000000000011451421045507400256340ustar00rootroot00000000000000import sys import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets # For packages that require scipy, these may be needed: # from scipy.stats import futil # from scipy.sparse.csgraph import _validation pg.setConfigOption('background','w') pg.setConfigOption('foreground','k') app = QtWidgets.QApplication(sys.argv) pw = pg.plot(x = [0, 1, 2, 4], y = [4, 5, 9, 6]) pw.showGrid(x=True,y=True) text = pg.TextItem(html='
      %s
      ' % "here",anchor=(0.0, 0.0)) text.setPos(1.0, 5.0) pw.addItem(text) status = app.exec_() sys.exit(status) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/cx_freeze/setup.py000066400000000000000000000027041421045507400251600ustar00rootroot00000000000000# Build with `python setup.py build_exe` import shutil from pathlib import Path from cx_Freeze import Executable, setup # Remove the build folder shutil.rmtree("build", ignore_errors=True) shutil.rmtree("dist", ignore_errors=True) import sys includes = ['pyqtgraph.graphicsItems', 'numpy', 'atexit'] excludes = ['cvxopt','_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger', 'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl','tables', 'Tkconstants', 'Tkinter', 'zmq','PySide','pysideuic','scipy','matplotlib'] # Workaround for making sure the templates are included in the frozen app package include_files = [] import pyqtgraph pg_folder = Path(pyqtgraph.__file__).parent templates = pg_folder.rglob('*template*.py') sources = [str(w) for w in templates] destinations = ['lib' + w.replace(str(pg_folder.parent), '') for w in sources] for a in zip(sources, destinations): include_files.append(a) print(include_files) if sys.version[0] == '2': # causes syntax error on py2 excludes.append('PyQt4.uic.port_v3') base = None if sys.platform == "win32": base = "Win32GUI" build_exe_options = {'excludes': excludes, 'includes':includes, 'include_msvcr':True, 'optimize':1, "include_files": include_files,} setup(name = "cx_freeze plot test", version = "0.2", description = "cx_freeze plot test", options = {"build_exe": build_exe_options}, executables = [Executable("plotTest.py", base=base)]) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/designerExample.py000066400000000000000000000021421421045507400251560ustar00rootroot00000000000000""" Simple example of loading UI template created with Qt Designer. This example uses uic.loadUiType to parse and load the ui at runtime. It is also possible to pre-compile the .ui file using pyuic (see VideoSpeedTest and ScatterPlotSpeedTest examples; these .ui files have been compiled with the tools/rebuildUi.py script). """ import os import numpy as np import pyqtgraph as pg pg.mkQApp() ## Define main window class from template path = os.path.dirname(os.path.abspath(__file__)) uiFile = os.path.join(path, 'designerExample.ui') WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) class MainWindow(TemplateBaseClass): def __init__(self): TemplateBaseClass.__init__(self) self.setWindowTitle('pyqtgraph example: Qt Designer') # Create the main window self.ui = WindowTemplate() self.ui.setupUi(self) self.ui.plotBtn.clicked.connect(self.plot) self.show() def plot(self): self.ui.plot.plot(np.random.normal(size=100), clear=True) win = MainWindow() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/designerExample.ui000066400000000000000000000015411421045507400251450ustar00rootroot00000000000000 Form 0 0 400 300 PyQtGraph Plot! PlotWidget QGraphicsView
      pyqtgraph
      pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/dockarea.py000066400000000000000000000074671421045507400236320ustar00rootroot00000000000000""" This example demonstrates the use of pyqtgraph's dock widget system. The dockarea system allows the design of user interfaces which can be rearranged by the user at runtime. Docks can be moved, resized, stacked, and torn out of the main window. This is similar in principle to the docking system built into Qt, but offers a more deterministic dock placement API (in Qt it is very difficult to programatically generate complex dock arrangements). Additionally, Qt's docks are designed to be used as small panels around the outer edge of a window. Pyqtgraph's docks were created with the notion that the entire window (or any portion of it) would consist of dockable components. """ import numpy as np import pyqtgraph as pg from pyqtgraph.console import ConsoleWidget from pyqtgraph.dockarea.Dock import Dock from pyqtgraph.dockarea.DockArea import DockArea from pyqtgraph.Qt import QtWidgets app = pg.mkQApp("DockArea Example") win = QtWidgets.QMainWindow() area = DockArea() win.setCentralWidget(area) win.resize(1000,500) win.setWindowTitle('pyqtgraph example: dockarea') ## Create docks, place them into the window one at a time. ## Note that size arguments are only a suggestion; docks will still have to ## fill the entire dock area and obey the limits of their internal widgets. d1 = Dock("Dock1", size=(1, 1)) ## give this dock the minimum possible size d2 = Dock("Dock2 - Console", size=(500,300), closable=True) d3 = Dock("Dock3", size=(500,400)) d4 = Dock("Dock4 (tabbed) - Plot", size=(500,200)) d5 = Dock("Dock5 - Image", size=(500,200)) d6 = Dock("Dock6 (tabbed) - Plot", size=(500,200)) area.addDock(d1, 'left') ## place d1 at left edge of dock area (it will fill the whole space since there are no other docks yet) area.addDock(d2, 'right') ## place d2 at right edge of dock area area.addDock(d3, 'bottom', d1)## place d3 at bottom edge of d1 area.addDock(d4, 'right') ## place d4 at right edge of dock area area.addDock(d5, 'left', d1) ## place d5 at left edge of d1 area.addDock(d6, 'top', d4) ## place d5 at top edge of d4 ## Test ability to move docks programatically after they have been placed area.moveDock(d4, 'top', d2) ## move d4 to top edge of d2 area.moveDock(d6, 'above', d4) ## move d6 to stack on top of d4 area.moveDock(d5, 'top', d2) ## move d5 to top edge of d2 ## Add widgets into each dock ## first dock gets save/restore buttons w1 = pg.LayoutWidget() label = QtWidgets.QLabel(""" -- DockArea Example -- This window has 6 Dock widgets in it. Each dock can be dragged by its title bar to occupy a different space within the window but note that one dock has its title bar hidden). Additionally, the borders between docks may be dragged to resize. Docks that are dragged on top of one another are stacked in a tabbed layout. Double-click a dock title bar to place it in its own window. """) saveBtn = QtWidgets.QPushButton('Save dock state') restoreBtn = QtWidgets.QPushButton('Restore dock state') restoreBtn.setEnabled(False) w1.addWidget(label, row=0, col=0) w1.addWidget(saveBtn, row=1, col=0) w1.addWidget(restoreBtn, row=2, col=0) d1.addWidget(w1) state = None def save(): global state state = area.saveState() restoreBtn.setEnabled(True) def load(): global state area.restoreState(state) saveBtn.clicked.connect(save) restoreBtn.clicked.connect(load) w2 = ConsoleWidget() d2.addWidget(w2) ## Hide title bar on dock 3 d3.hideTitleBar() w3 = pg.PlotWidget(title="Plot inside dock with no title bar") w3.plot(np.random.normal(size=100)) d3.addWidget(w3) w4 = pg.PlotWidget(title="Dock 4 plot") w4.plot(np.random.normal(size=100)) d4.addWidget(w4) w5 = pg.ImageView() w5.setImage(np.random.normal(size=(100,100))) d5.addWidget(w5) w6 = pg.PlotWidget(title="Dock 6 plot") w6.plot(np.random.normal(size=100)) d6.addWidget(w6) win.show() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/exampleLoaderTemplate.ui000066400000000000000000000071161421045507400263130ustar00rootroot00000000000000 Form 0 0 846 552 PyQtGraph Qt::Horizontal default PyQt5 PySide2 PySide6 PyQt6 Run Example false 1 Qt Library: Type to filter... Title Search Content Search true Qt::AlignCenter Courier New pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/exampleLoaderTemplate_pyqt5.py000066400000000000000000000105211421045507400274620ustar00rootroot00000000000000 # Form implementation generated from reading ui file 'examples/exampleLoaderTemplate.ui' # # Created by: PyQt5 UI code generator 5.15.4 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(846, 552) self.gridLayout_2 = QtWidgets.QGridLayout(Form) self.gridLayout_2.setObjectName("gridLayout_2") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Horizontal) self.splitter.setObjectName("splitter") self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.qtLibCombo = QtWidgets.QComboBox(self.layoutWidget) self.qtLibCombo.setObjectName("qtLibCombo") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.gridLayout.addWidget(self.qtLibCombo, 4, 1, 1, 1) self.loadBtn = QtWidgets.QPushButton(self.layoutWidget) self.loadBtn.setObjectName("loadBtn") self.gridLayout.addWidget(self.loadBtn, 6, 1, 1, 1) self.exampleTree = QtWidgets.QTreeWidget(self.layoutWidget) self.exampleTree.setObjectName("exampleTree") self.exampleTree.headerItem().setText(0, "1") self.exampleTree.header().setVisible(False) self.gridLayout.addWidget(self.exampleTree, 3, 0, 1, 2) self.label = QtWidgets.QLabel(self.layoutWidget) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 4, 0, 1, 1) self.exampleFilter = QtWidgets.QLineEdit(self.layoutWidget) self.exampleFilter.setObjectName("exampleFilter") self.gridLayout.addWidget(self.exampleFilter, 0, 0, 1, 2) self.searchFiles = QtWidgets.QComboBox(self.layoutWidget) self.searchFiles.setObjectName("searchFiles") self.searchFiles.addItem("") self.searchFiles.addItem("") self.gridLayout.addWidget(self.searchFiles, 1, 0, 1, 2) self.layoutWidget1 = QtWidgets.QWidget(self.splitter) self.layoutWidget1.setObjectName("layoutWidget1") self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget1) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") self.loadedFileLabel = QtWidgets.QLabel(self.layoutWidget1) font = QtGui.QFont() font.setBold(True) self.loadedFileLabel.setFont(font) self.loadedFileLabel.setText("") self.loadedFileLabel.setAlignment(QtCore.Qt.AlignCenter) self.loadedFileLabel.setObjectName("loadedFileLabel") self.verticalLayout.addWidget(self.loadedFileLabel) self.codeView = QtWidgets.QPlainTextEdit(self.layoutWidget1) font = QtGui.QFont() font.setFamily("Courier New") self.codeView.setFont(font) self.codeView.setObjectName("codeView") self.verticalLayout.addWidget(self.codeView) self.gridLayout_2.addWidget(self.splitter, 1, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.qtLibCombo.setItemText(0, _translate("Form", "default")) self.qtLibCombo.setItemText(1, _translate("Form", "PyQt5")) self.qtLibCombo.setItemText(2, _translate("Form", "PySide2")) self.qtLibCombo.setItemText(3, _translate("Form", "PySide6")) self.qtLibCombo.setItemText(4, _translate("Form", "PyQt6")) self.loadBtn.setText(_translate("Form", "Run Example")) self.label.setText(_translate("Form", "Qt Library:")) self.exampleFilter.setPlaceholderText(_translate("Form", "Type to filter...")) self.searchFiles.setItemText(0, _translate("Form", "Title Search")) self.searchFiles.setItemText(1, _translate("Form", "Content Search")) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/exampleLoaderTemplate_pyqt6.py000066400000000000000000000105511421045507400274660ustar00rootroot00000000000000# Form implementation generated from reading ui file 'examples/exampleLoaderTemplate.ui' # # Created by: PyQt6 UI code generator 6.1.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(846, 552) self.gridLayout_2 = QtWidgets.QGridLayout(Form) self.gridLayout_2.setObjectName("gridLayout_2") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Orientation.Horizontal) self.splitter.setObjectName("splitter") self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.qtLibCombo = QtWidgets.QComboBox(self.layoutWidget) self.qtLibCombo.setObjectName("qtLibCombo") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.gridLayout.addWidget(self.qtLibCombo, 4, 1, 1, 1) self.loadBtn = QtWidgets.QPushButton(self.layoutWidget) self.loadBtn.setObjectName("loadBtn") self.gridLayout.addWidget(self.loadBtn, 6, 1, 1, 1) self.exampleTree = QtWidgets.QTreeWidget(self.layoutWidget) self.exampleTree.setObjectName("exampleTree") self.exampleTree.headerItem().setText(0, "1") self.exampleTree.header().setVisible(False) self.gridLayout.addWidget(self.exampleTree, 3, 0, 1, 2) self.label = QtWidgets.QLabel(self.layoutWidget) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 4, 0, 1, 1) self.exampleFilter = QtWidgets.QLineEdit(self.layoutWidget) self.exampleFilter.setObjectName("exampleFilter") self.gridLayout.addWidget(self.exampleFilter, 0, 0, 1, 2) self.searchFiles = QtWidgets.QComboBox(self.layoutWidget) self.searchFiles.setObjectName("searchFiles") self.searchFiles.addItem("") self.searchFiles.addItem("") self.gridLayout.addWidget(self.searchFiles, 1, 0, 1, 2) self.layoutWidget1 = QtWidgets.QWidget(self.splitter) self.layoutWidget1.setObjectName("layoutWidget1") self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget1) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") self.loadedFileLabel = QtWidgets.QLabel(self.layoutWidget1) font = QtGui.QFont() font.setBold(True) self.loadedFileLabel.setFont(font) self.loadedFileLabel.setText("") self.loadedFileLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.loadedFileLabel.setObjectName("loadedFileLabel") self.verticalLayout.addWidget(self.loadedFileLabel) self.codeView = QtWidgets.QPlainTextEdit(self.layoutWidget1) font = QtGui.QFont() font.setFamily("Courier New") self.codeView.setFont(font) self.codeView.setObjectName("codeView") self.verticalLayout.addWidget(self.codeView) self.gridLayout_2.addWidget(self.splitter, 1, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.qtLibCombo.setItemText(0, _translate("Form", "default")) self.qtLibCombo.setItemText(1, _translate("Form", "PyQt5")) self.qtLibCombo.setItemText(2, _translate("Form", "PySide2")) self.qtLibCombo.setItemText(3, _translate("Form", "PySide6")) self.qtLibCombo.setItemText(4, _translate("Form", "PyQt6")) self.loadBtn.setText(_translate("Form", "Run Example")) self.label.setText(_translate("Form", "Qt Library:")) self.exampleFilter.setPlaceholderText(_translate("Form", "Type to filter...")) self.searchFiles.setItemText(0, _translate("Form", "Title Search")) self.searchFiles.setItemText(1, _translate("Form", "Content Search")) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/exampleLoaderTemplate_pyside2.py000066400000000000000000000114421421045507400277620ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'exampleLoaderTemplate.ui' ## ## Created by: Qt User Interface Compiler version 5.15.2 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide2.QtCore import * from PySide2.QtGui import * from PySide2.QtWidgets import * class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(846, 552) self.gridLayout_2 = QGridLayout(Form) self.gridLayout_2.setObjectName(u"gridLayout_2") self.splitter = QSplitter(Form) self.splitter.setObjectName(u"splitter") self.splitter.setOrientation(Qt.Horizontal) self.layoutWidget = QWidget(self.splitter) self.layoutWidget.setObjectName(u"layoutWidget") self.gridLayout = QGridLayout(self.layoutWidget) self.gridLayout.setObjectName(u"gridLayout") self.gridLayout.setContentsMargins(0, 0, 0, 0) self.qtLibCombo = QComboBox(self.layoutWidget) self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.setObjectName(u"qtLibCombo") self.gridLayout.addWidget(self.qtLibCombo, 4, 1, 1, 1) self.loadBtn = QPushButton(self.layoutWidget) self.loadBtn.setObjectName(u"loadBtn") self.gridLayout.addWidget(self.loadBtn, 6, 1, 1, 1) self.exampleTree = QTreeWidget(self.layoutWidget) __qtreewidgetitem = QTreeWidgetItem() __qtreewidgetitem.setText(0, u"1"); self.exampleTree.setHeaderItem(__qtreewidgetitem) self.exampleTree.setObjectName(u"exampleTree") self.exampleTree.header().setVisible(False) self.gridLayout.addWidget(self.exampleTree, 3, 0, 1, 2) self.label = QLabel(self.layoutWidget) self.label.setObjectName(u"label") self.gridLayout.addWidget(self.label, 4, 0, 1, 1) self.exampleFilter = QLineEdit(self.layoutWidget) self.exampleFilter.setObjectName(u"exampleFilter") self.gridLayout.addWidget(self.exampleFilter, 0, 0, 1, 2) self.searchFiles = QComboBox(self.layoutWidget) self.searchFiles.addItem("") self.searchFiles.addItem("") self.searchFiles.setObjectName(u"searchFiles") self.gridLayout.addWidget(self.searchFiles, 1, 0, 1, 2) self.splitter.addWidget(self.layoutWidget) self.layoutWidget1 = QWidget(self.splitter) self.layoutWidget1.setObjectName(u"layoutWidget1") self.verticalLayout = QVBoxLayout(self.layoutWidget1) self.verticalLayout.setObjectName(u"verticalLayout") self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.loadedFileLabel = QLabel(self.layoutWidget1) self.loadedFileLabel.setObjectName(u"loadedFileLabel") font = QFont() font.setBold(True) self.loadedFileLabel.setFont(font) self.loadedFileLabel.setAlignment(Qt.AlignCenter) self.verticalLayout.addWidget(self.loadedFileLabel) self.codeView = QPlainTextEdit(self.layoutWidget1) self.codeView.setObjectName(u"codeView") font1 = QFont() font1.setFamily(u"Courier New") self.codeView.setFont(font1) self.verticalLayout.addWidget(self.codeView) self.splitter.addWidget(self.layoutWidget1) self.gridLayout_2.addWidget(self.splitter, 1, 0, 1, 1) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) self.qtLibCombo.setItemText(0, QCoreApplication.translate("Form", u"default", None)) self.qtLibCombo.setItemText(1, QCoreApplication.translate("Form", u"PyQt5", None)) self.qtLibCombo.setItemText(2, QCoreApplication.translate("Form", u"PySide2", None)) self.qtLibCombo.setItemText(3, QCoreApplication.translate("Form", u"PySide6", None)) self.qtLibCombo.setItemText(4, QCoreApplication.translate("Form", u"PyQt6", None)) self.loadBtn.setText(QCoreApplication.translate("Form", u"Run Example", None)) self.label.setText(QCoreApplication.translate("Form", u"Qt Library:", None)) self.exampleFilter.setPlaceholderText(QCoreApplication.translate("Form", u"Type to filter...", None)) self.searchFiles.setItemText(0, QCoreApplication.translate("Form", u"Title Search", None)) self.searchFiles.setItemText(1, QCoreApplication.translate("Form", u"Content Search", None)) self.loadedFileLabel.setText("") # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/exampleLoaderTemplate_pyside6.py000066400000000000000000000115251421045507400277700ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'exampleLoaderTemplate.ui' ## ## Created by: Qt User Interface Compiler version 6.1.1 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * # type: ignore from PySide6.QtGui import * # type: ignore from PySide6.QtWidgets import * # type: ignore class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(846, 552) self.gridLayout_2 = QGridLayout(Form) self.gridLayout_2.setObjectName(u"gridLayout_2") self.splitter = QSplitter(Form) self.splitter.setObjectName(u"splitter") self.splitter.setOrientation(Qt.Horizontal) self.layoutWidget = QWidget(self.splitter) self.layoutWidget.setObjectName(u"layoutWidget") self.gridLayout = QGridLayout(self.layoutWidget) self.gridLayout.setObjectName(u"gridLayout") self.gridLayout.setContentsMargins(0, 0, 0, 0) self.qtLibCombo = QComboBox(self.layoutWidget) self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.addItem("") self.qtLibCombo.setObjectName(u"qtLibCombo") self.gridLayout.addWidget(self.qtLibCombo, 4, 1, 1, 1) self.loadBtn = QPushButton(self.layoutWidget) self.loadBtn.setObjectName(u"loadBtn") self.gridLayout.addWidget(self.loadBtn, 6, 1, 1, 1) self.exampleTree = QTreeWidget(self.layoutWidget) __qtreewidgetitem = QTreeWidgetItem() __qtreewidgetitem.setText(0, u"1"); self.exampleTree.setHeaderItem(__qtreewidgetitem) self.exampleTree.setObjectName(u"exampleTree") self.exampleTree.header().setVisible(False) self.gridLayout.addWidget(self.exampleTree, 3, 0, 1, 2) self.label = QLabel(self.layoutWidget) self.label.setObjectName(u"label") self.gridLayout.addWidget(self.label, 4, 0, 1, 1) self.exampleFilter = QLineEdit(self.layoutWidget) self.exampleFilter.setObjectName(u"exampleFilter") self.gridLayout.addWidget(self.exampleFilter, 0, 0, 1, 2) self.searchFiles = QComboBox(self.layoutWidget) self.searchFiles.addItem("") self.searchFiles.addItem("") self.searchFiles.setObjectName(u"searchFiles") self.gridLayout.addWidget(self.searchFiles, 1, 0, 1, 2) self.splitter.addWidget(self.layoutWidget) self.layoutWidget1 = QWidget(self.splitter) self.layoutWidget1.setObjectName(u"layoutWidget1") self.verticalLayout = QVBoxLayout(self.layoutWidget1) self.verticalLayout.setObjectName(u"verticalLayout") self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.loadedFileLabel = QLabel(self.layoutWidget1) self.loadedFileLabel.setObjectName(u"loadedFileLabel") font = QFont() font.setBold(True) self.loadedFileLabel.setFont(font) self.loadedFileLabel.setAlignment(Qt.AlignCenter) self.verticalLayout.addWidget(self.loadedFileLabel) self.codeView = QPlainTextEdit(self.layoutWidget1) self.codeView.setObjectName(u"codeView") font1 = QFont() font1.setFamilies([u"Courier New"]) self.codeView.setFont(font1) self.verticalLayout.addWidget(self.codeView) self.splitter.addWidget(self.layoutWidget1) self.gridLayout_2.addWidget(self.splitter, 1, 0, 1, 1) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) self.qtLibCombo.setItemText(0, QCoreApplication.translate("Form", u"default", None)) self.qtLibCombo.setItemText(1, QCoreApplication.translate("Form", u"PyQt5", None)) self.qtLibCombo.setItemText(2, QCoreApplication.translate("Form", u"PySide2", None)) self.qtLibCombo.setItemText(3, QCoreApplication.translate("Form", u"PySide6", None)) self.qtLibCombo.setItemText(4, QCoreApplication.translate("Form", u"PyQt6", None)) self.loadBtn.setText(QCoreApplication.translate("Form", u"Run Example", None)) self.label.setText(QCoreApplication.translate("Form", u"Qt Library:", None)) self.exampleFilter.setPlaceholderText(QCoreApplication.translate("Form", u"Type to filter...", None)) self.searchFiles.setItemText(0, QCoreApplication.translate("Form", u"Title Search", None)) self.searchFiles.setItemText(1, QCoreApplication.translate("Form", u"Content Search", None)) self.loadedFileLabel.setText("") # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/fractal.py000066400000000000000000000062331421045507400234630ustar00rootroot00000000000000""" Displays an interactive Koch fractal """ from functools import reduce import numpy as np import pyqtgraph as pg app = pg.mkQApp("Fractal Example") # Set up UI widgets win = pg.QtWidgets.QWidget() win.setWindowTitle('pyqtgraph example: fractal demo') layout = pg.QtWidgets.QGridLayout() win.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) depthLabel = pg.QtWidgets.QLabel('fractal depth:') layout.addWidget(depthLabel, 0, 0) depthSpin = pg.SpinBox(value=5, step=1, bounds=[1, 10], delay=0, int=True) depthSpin.resize(100, 20) layout.addWidget(depthSpin, 0, 1) w = pg.GraphicsLayoutWidget() layout.addWidget(w, 1, 0, 1, 2) win.show() # Set up graphics v = w.addViewBox() v.setAspectLocked() baseLine = pg.PolyLineROI([[0, 0], [1, 0], [1.5, 1], [2, 0], [3, 0]], pen=(0, 255, 0, 100), movable=False) v.addItem(baseLine) fc = pg.PlotCurveItem(pen=(255, 255, 255, 200), antialias=True) v.addItem(fc) v.autoRange() transformMap = [0, 0, None] def update(): # recalculate and redraw the fractal curve depth = depthSpin.value() pts = baseLine.getState()['points'] nbseg = len(pts) - 1 nseg = nbseg**depth # Get a transformation matrix for each base segment trs = [] v1 = pts[-1] - pts[0] l1 = v1.length() for i in range(len(pts)-1): p1 = pts[i] p2 = pts[i+1] v2 = p2 - p1 t = p1 - pts[0] r = v2.angle(v1) s = v2.length() / l1 trs.append(pg.SRTTransform({'pos': t, 'scale': (s, s), 'angle': r})) basePts = [np.array(list(pt) + [1]) for pt in baseLine.getState()['points']] baseMats = np.dstack([tr.matrix().T for tr in trs]).transpose(2, 0, 1) # Generate an array of matrices to transform base points global transformMap if transformMap[:2] != [depth, nbseg]: # we can cache the transform index to save a little time.. nseg = nbseg**depth matInds = np.empty((depth, nseg), dtype=int) for i in range(depth): matInds[i] = np.tile(np.repeat(np.arange(nbseg), nbseg**(depth-1-i)), nbseg**i) transformMap = [depth, nbseg, matInds] # Each column in matInds contains the indices referring to the base transform # matrices that must be multiplied together to generate the final transform # for each segment of the fractal matInds = transformMap[2] # Collect all matrices needed for generating fractal curve mats = baseMats[matInds] # Magic-multiply stacks of matrices together def matmul(a, b): return np.sum(np.transpose(a,(0,2,1))[..., None] * b[..., None, :], axis=-3) mats = reduce(matmul, mats) # Transform base points through matrix array pts = np.empty((nseg * nbseg + 1, 2)) for l in range(len(trs)): bp = basePts[l] pts[l:-1:len(trs)] = np.dot(mats, bp)[:, :2] # Finish the curve with the last base point pts[-1] = basePts[-1][:2] # update fractal curve with new points fc.setData(pts[:,0], pts[:,1]) # Update the fractal whenever the base shape or depth has changed baseLine.sigRegionChanged.connect(update) depthSpin.valueChanged.connect(update) # Initialize update() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/hdf5.py000066400000000000000000000114121421045507400226700ustar00rootroot00000000000000""" In this example we create a subclass of PlotCurveItem for displaying a very large data set from an HDF5 file that does not fit in memory. The basic approach is to override PlotCurveItem.viewRangeChanged such that it reads only the portion of the HDF5 data that is necessary to display the visible portion of the data. This is further downsampled to reduce the number of samples being displayed. A more clever implementation of this class would employ some kind of caching to avoid re-reading the entire visible waveform at every update. """ import os import sys import h5py import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets pg.mkQApp() plt = pg.plot() plt.setWindowTitle('pyqtgraph example: HDF5 big data') plt.enableAutoRange(False, False) plt.setXRange(0, 500) class HDF5Plot(pg.PlotCurveItem): def __init__(self, *args, **kwds): self.hdf5 = None self.limit = 10000 # maximum number of samples to be plotted pg.PlotCurveItem.__init__(self, *args, **kwds) def setHDF5(self, data): self.hdf5 = data self.updateHDF5Plot() def viewRangeChanged(self): self.updateHDF5Plot() def updateHDF5Plot(self): if self.hdf5 is None: self.setData([]) return vb = self.getViewBox() if vb is None: return # no ViewBox yet # Determine what data range must be read from HDF5 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 if ds == 1: # Small enough to display with no intervention. visible = self.hdf5[start:stop] scale = 1 else: # Here convert data into a down-sampled array suitable for visualizing. # Must do this piecewise to limit memory usage. samples = 1 + ((stop-start) // ds) visible = np.zeros(samples*2, dtype=self.hdf5.dtype) sourcePtr = start targetPtr = 0 # read data in chunks of ~1M samples chunkSize = (1000000//ds) * ds while sourcePtr < stop-1: chunk = self.hdf5[sourcePtr:min(stop,sourcePtr+chunkSize)] sourcePtr += len(chunk) # reshape chunk to be integral multiple of ds chunk = chunk[:(len(chunk)//ds) * ds].reshape(len(chunk)//ds, ds) # compute max and min chunkMax = chunk.max(axis=1) chunkMin = chunk.min(axis=1) # interleave min and max into plot data to preserve envelope shape visible[targetPtr:targetPtr+chunk.shape[0]*2:2] = chunkMin visible[1+targetPtr:1+targetPtr+chunk.shape[0]*2:2] = chunkMax targetPtr += chunk.shape[0]*2 visible = visible[:targetPtr] scale = ds * 0.5 self.setData(visible) # update the plot self.setPos(start, 0) # shift to match starting index self.resetTransform() self.scale(scale, 1) # scale to match downsampling def createFile(finalSize=2000000000): """Create a large HDF5 data file for testing. Data consists of 1M random samples tiled through the end of the array. """ chunk = np.random.normal(size=1000000).astype(np.float32) f = h5py.File('test.hdf5', 'w') f.create_dataset('data', data=chunk, chunks=True, maxshape=(None,)) data = f['data'] nChunks = finalSize // (chunk.size * chunk.itemsize) with pg.ProgressDialog("Generating test.hdf5...", 0, nChunks) as dlg: for i in range(nChunks): newshape = [data.shape[0] + chunk.shape[0]] data.resize(newshape) data[-chunk.shape[0]:] = chunk dlg += 1 if dlg.wasCanceled(): f.close() os.remove('test.hdf5') sys.exit() dlg += 1 f.close() if len(sys.argv) > 1: fileName = sys.argv[1] else: fileName = 'test.hdf5' if not os.path.isfile(fileName): size, ok = QtWidgets.QInputDialog.getDouble(None, "Create HDF5 Dataset?", "This demo requires a large HDF5 array. To generate a file, enter the array size (in GB) and press OK.", 2.0) if not ok: sys.exit(0) else: createFile(int(size*1e9)) #raise Exception("No suitable HDF5 file found. Use createFile() to generate an example file.") f = h5py.File(fileName, 'r') curve = HDF5Plot() curve.setHDF5(f['data']) plt.addItem(curve) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/histogram.py000066400000000000000000000017261421045507400240460ustar00rootroot00000000000000""" In this example we draw two different kinds of histogram. """ import numpy as np import pyqtgraph as pg win = pg.GraphicsLayoutWidget(show=True) win.resize(800,350) win.setWindowTitle('pyqtgraph example: Histogram') plt1 = win.addPlot() plt2 = win.addPlot() ## make interesting distribution of values vals = np.hstack([np.random.normal(size=500), np.random.normal(size=260, loc=4)]) ## compute standard histogram y,x = np.histogram(vals, bins=np.linspace(-3, 8, 40)) ## Using stepMode="center" causes the plot to draw two lines for each sample. ## notice that len(x) == len(y)+1 plt1.plot(x, y, stepMode="center", fillLevel=0, fillOutline=True, brush=(0,0,255,150)) ## Now draw all points as a nicely-spaced scatter plot y = pg.pseudoScatter(vals, spacing=0.15) #plt2.plot(vals, y, pen=None, symbol='o', symbolSize=5) plt2.plot(vals, y, pen=None, symbol='o', symbolSize=5, symbolPen=(255,255,255,200), symbolBrush=(0,0,255,150)) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/imageAnalysis.py000066400000000000000000000060361421045507400246360ustar00rootroot00000000000000""" Demonstrates common image analysis tools. Many of the features demonstrated here are already provided by the ImageView widget, but here we present a lower-level approach that provides finer control over the user interface. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtGui # Interpret image data as row-major instead of col-major pg.setConfigOptions(imageAxisOrder='row-major') pg.mkQApp() win = pg.GraphicsLayoutWidget() win.setWindowTitle('pyqtgraph example: Image Analysis') # A plot area (ViewBox + axes) for displaying the image p1 = win.addPlot(title="") # Item for displaying image data img = pg.ImageItem() p1.addItem(img) # Custom ROI for selecting an image region roi = pg.ROI([-8, 14], [6, 5]) roi.addScaleHandle([0.5, 1], [0.5, 0.5]) roi.addScaleHandle([0, 0.5], [0.5, 0.5]) p1.addItem(roi) roi.setZValue(10) # make sure ROI is drawn above image # Isocurve drawing iso = pg.IsocurveItem(level=0.8, pen='g') iso.setParentItem(img) iso.setZValue(5) # Contrast/color control hist = pg.HistogramLUTItem() hist.setImageItem(img) win.addItem(hist) # Draggable line for setting isocurve level isoLine = pg.InfiniteLine(angle=0, movable=True, pen='g') hist.vb.addItem(isoLine) hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier isoLine.setValue(0.8) isoLine.setZValue(1000) # bring iso line above contrast controls # Another plot area for displaying ROI data win.nextRow() p2 = win.addPlot(colspan=2) p2.setMaximumHeight(250) win.resize(800, 800) win.show() # Generate image data data = np.random.normal(size=(200, 100)) data[20:80, 20:80] += 2. data = pg.gaussianFilter(data, (3, 3)) data += np.random.normal(size=(200, 100)) * 0.1 img.setImage(data) hist.setLevels(data.min(), data.max()) # build isocurves from smoothed data iso.setData(pg.gaussianFilter(data, (2, 2))) # set position and scale of image tr = QtGui.QTransform() img.setTransform(tr.scale(0.2, 0.2).translate(-50, 0)) # zoom to fit imageo p1.autoRange() # Callbacks for handling user interaction def updatePlot(): global img, roi, data, p2 selected = roi.getArrayRegion(data, img) p2.plot(selected.mean(axis=0), clear=True) roi.sigRegionChanged.connect(updatePlot) updatePlot() def updateIsocurve(): global isoLine, iso iso.setLevel(isoLine.value()) isoLine.sigDragged.connect(updateIsocurve) def imageHoverEvent(event): """Show the position, pixel, and value under the mouse cursor. """ if event.isExit(): p1.setTitle("") return pos = event.pos() i, j = pos.y(), pos.x() i = int(np.clip(i, 0, data.shape[0] - 1)) j = int(np.clip(j, 0, data.shape[1] - 1)) 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: %.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), # but it works for a very simple use like this. img.hoverEvent = imageHoverEvent if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/infiniteline_performance.py000066400000000000000000000021301421045507400270750ustar00rootroot00000000000000#!/usr/bin/python from time import perf_counter import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore app = pg.mkQApp("Infinite Line Performance") p = pg.plot() p.setWindowTitle('pyqtgraph performance: InfiniteLine') p.setRange(QtCore.QRectF(0, -10, 5000, 20)) p.setLabel('bottom', 'Index', units='B') curve = p.plot() # Add a large number of horizontal InfiniteLine to plot for i in range(100): line = pg.InfiniteLine(pos=np.random.randint(5000), movable=True) p.addItem(line) data = np.random.normal(size=(50, 5000)) ptr = 0 lastTime = perf_counter() fps = None def update(): global curve, data, ptr, p, lastTime, fps curve.setData(data[ptr % 10]) ptr += 1 now = perf_counter() dt = now - lastTime lastTime = now if fps is None: fps = 1.0/dt else: s = np.clip(dt*3., 0, 1) fps = fps * (1-s) + (1.0/dt) * s p.setTitle('%0.2f fps' % fps) app.processEvents() # force complete redraw for every plot timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(0) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/isocurve.py000066400000000000000000000025221421045507400237030ustar00rootroot00000000000000""" Tests use of IsoCurve item displayed with image """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore app = pg.mkQApp("Isocurve Example") ## make pretty looping data frames = 200 data = np.random.normal(size=(frames,30,30), loc=0, scale=100) data = np.concatenate([data, data], axis=0) data = pg.gaussianFilter(data, (10, 10, 10))[frames//2:frames + frames//2] data[:, 15:16, 15:17] += 1 win = pg.GraphicsLayoutWidget(show=True) win.setWindowTitle('pyqtgraph example: Isocurve') vb = win.addViewBox() img = pg.ImageItem(data[0]) vb.addItem(img) vb.setAspectLocked() ## generate empty curves curves = [] levels = np.linspace(data.min(), data.max(), 10) for i in range(len(levels)): v = levels[i] ## generate isocurve with automatic color selection c = pg.IsocurveItem(level=v, pen=(i, len(levels)*1.5)) c.setParentItem(img) ## make sure isocurve is always correctly displayed over image c.setZValue(10) curves.append(c) ## animate! ptr = 0 imgLevels = (data.min(), data.max() * 2) def update(): global data, curves, img, ptr, imgLevels ptr = (ptr + 1) % data.shape[0] data[ptr] img.setImage(data[ptr], levels=imgLevels) for c in curves: c.setData(data[ptr]) timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(50) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/linkedViews.py000066400000000000000000000023071421045507400243310ustar00rootroot00000000000000""" This example demonstrates the ability to link the axes of views together Views can be linked manually using the context menu, but only if they are given names. """ import numpy as np import pyqtgraph as pg app = pg.mkQApp("Linked Views Example") #mw = QtWidgets.QMainWindow() #mw.resize(800,800) x = np.linspace(-50, 50, 1000) y = np.sin(x) / x win = pg.GraphicsLayoutWidget(show=True, title="pyqtgraph example: Linked Views") win.resize(800,600) win.addLabel("Linked Views", colspan=2) win.nextRow() p1 = win.addPlot(x=x, y=y, name="Plot1", title="Plot1") p2 = win.addPlot(x=x, y=y, name="Plot2", title="Plot2: Y linked with Plot1") p2.setLabel('bottom', "Label to test offset") p2.setYLink('Plot1') ## test linking by name ## create plots 3 and 4 out of order p4 = win.addPlot(x=x, y=y, name="Plot4", title="Plot4: X -> Plot3 (deferred), Y -> Plot1", row=2, col=1) p4.setXLink('Plot3') ## Plot3 has not been created yet, but this should still work anyway. p4.setYLink(p1) p3 = win.addPlot(x=x, y=y, name="Plot3", title="Plot3: X linked with Plot1", row=2, col=0) p3.setXLink(p1) p3.setLabel('left', "Label to test offset") #QtWidgets.QApplication.processEvents() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/logAxis.py000066400000000000000000000027241421045507400234560ustar00rootroot00000000000000""" Demonstrate programmatic setting of log transformation modes. """ import numpy as np import pyqtgraph as pg app = pg.mkQApp("Log Axis Example") w = pg.GraphicsLayoutWidget(show=True) 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(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() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/multiplePlotSpeedTest.py000066400000000000000000000046501421045507400263630ustar00rootroot00000000000000from time import perf_counter import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets app = pg.mkQApp() plt = pg.PlotWidget() app.processEvents() ## Putting this at the beginning or end does not have much effect plt.show() ## The auto-range is recomputed after each item is added, ## so disabling it before plotting helps plt.enableAutoRange(False, False) def plot(): start = perf_counter() n = 15 pts = 100 x = np.linspace(0, 0.8, pts) y = np.random.random(size=pts)*0.8 for i in range(n): for j in range(n): ## calling PlotWidget.plot() generates a PlotDataItem, which ## has a bit more overhead than PlotCurveItem, which is all ## we need here. This overhead adds up quickly and makes a big ## difference in speed. #plt.plot(x=x+i, y=y+j) plt.addItem(pg.PlotCurveItem(x=x+i, y=y+j)) #path = pg.arrayToQPath(x+i, y+j) #item = QtWidgets.QGraphicsPathItem(path) #item.setPen(pg.mkPen('w')) #plt.addItem(item) 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): plt.clear() plot() app.processEvents() plt.autoRange() def fastPlot(): ## Different approach: generate a single item with all data points. ## This runs about 20x faster. start = perf_counter() n = 15 pts = 100 x = np.linspace(0, 0.8, pts) y = np.random.random(size=pts)*0.8 xdata = np.empty((n, n, pts)) xdata[:] = x.reshape(1,1,pts) + np.arange(n).reshape(n,1,1) ydata = np.empty((n, n, pts)) ydata[:] = y.reshape(1,1,pts) + np.arange(n).reshape(1,n,1) conn = np.ones((n*n,pts)) conn[:,-1] = False # make sure plots are disconnected path = pg.arrayToQPath(xdata.flatten(), ydata.flatten(), conn.flatten()) item = QtWidgets.QGraphicsPathItem(path) item.setPen(pg.mkPen('w')) plt.addItem(item) dt = perf_counter() - start print("Create plots took: %0.3fms" % (dt*1000)) ## Plot and clear 5 times, printing the time it took if hasattr(pg, 'arrayToQPath'): for i in range(5): plt.clear() fastPlot() app.processEvents() else: print("Skipping fast tests--arrayToQPath function is missing.") plt.autoRange() if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/multiprocess.py000066400000000000000000000022311421045507400245720ustar00rootroot00000000000000 import numpy as np import pyqtgraph as pg import pyqtgraph.multiprocess as mp print("\n=================\nStart Process") proc = mp.Process() import os print("parent:", os.getpid(), "child:", proc.proc.pid) print("started") rnp = proc._import('numpy') arr = rnp.array([1,2,3,4]) print(repr(arr)) print(str(arr)) print("return value:", repr(arr.mean(_returnType='value'))) print( "return proxy:", repr(arr.mean(_returnType='proxy'))) print( "return auto: ", repr(arr.mean(_returnType='auto'))) proc.join() print( "process finished") print( "\n=================\nStart ForkedProcess") proc = mp.ForkedProcess() rnp = proc._import('numpy') arr = rnp.array([1,2,3,4]) print( repr(arr)) print( str(arr)) print( repr(arr.mean())) proc.join() print( "process finished") import pyqtgraph as pg app = pg.mkQApp("Multiprocess Example") print( "\n=================\nStart QtProcess") import sys if (sys.flags.interactive != 1): print( " (not interactive; remote process will exit immediately.)") proc = mp.QtProcess() d1 = proc.transfer(np.random.normal(size=1000)) d2 = proc.transfer(np.random.normal(size=1000)) rpg = proc._import('pyqtgraph') plt = rpg.plot(d1+d2) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/notebooks/000077500000000000000000000000001421045507400234745ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/notebooks/ImageAnalysis.ipynb000066400000000000000000000116671421045507400273000ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "Demonstrates common image analysis tools.\n", "\n", "Many of the features demonstrated here are already provided by the ImageView\n", "widget, but here we present a lower-level approach that provides finer control\n", "over the user interface.\n", "\"\"\"\n", "from pyqtgraph.jupyter import GraphicsLayoutWidget\n", "import ipywidgets\n", "from IPython.display import display\n", "\n", "import pyqtgraph as pg\n", "from pyqtgraph.Qt import QtCore, QtGui\n", "import numpy as np\n", "\n", "\n", "# Interpret image data as row-major instead of col-major\n", "pg.setConfigOptions(imageAxisOrder='row-major')\n", "\n", "pg.mkQApp()\n", "win = GraphicsLayoutWidget(css_width=\"800px\", css_height=\"480px\")\n", "\n", "# A plot area (ViewBox + axes) for displaying the image\n", "p1 = win.addPlot(title=\"\")\n", "\n", "# Item for displaying image data\n", "img = pg.ImageItem()\n", "p1.addItem(img)\n", "\n", "# Custom ROI for selecting an image region\n", "roi = pg.ROI([-8, 14], [6, 5])\n", "roi.addScaleHandle([0.5, 1], [0.5, 0.5])\n", "roi.addScaleHandle([0, 0.5], [0.5, 0.5])\n", "p1.addItem(roi)\n", "roi.setZValue(10) # make sure ROI is drawn above image\n", "\n", "# Isocurve drawing\n", "iso = pg.IsocurveItem(level=0.8, pen='g')\n", "iso.setParentItem(img)\n", "iso.setZValue(5)\n", "\n", "# Contrast/color control\n", "hist = pg.HistogramLUTItem()\n", "hist.setImageItem(img)\n", "win.addItem(hist)\n", "\n", "# Draggable line for setting isocurve level\n", "isoLine = pg.InfiniteLine(angle=0, movable=True, pen='g')\n", "hist.vb.addItem(isoLine)\n", "hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier\n", "isoLine.setValue(0.8)\n", "isoLine.setZValue(1000) # bring iso line above contrast controls\n", "\n", "# Another plot area for displaying ROI data\n", "win.nextRow()\n", "p2 = win.addPlot(colspan=2)\n", "p2.setMaximumHeight(250)\n", "\n", "\n", "# Generate image data\n", "data = np.random.normal(size=(200, 100))\n", "data[20:80, 20:80] += 2.\n", "data = pg.gaussianFilter(data, (3, 3))\n", "data += np.random.normal(size=(200, 100)) * 0.1\n", "img.setImage(data)\n", "hist.setLevels(data.min(), data.max())\n", "\n", "# build isocurves from smoothed data\n", "iso.setData(pg.gaussianFilter(data, (2, 2)))\n", "\n", "# set position and scale of image\n", "tr = QtGui.QTransform()\n", "img.setTransform(tr.scale(0.2, 0.2).translate(-50, 0))\n", "\n", "# zoom to fit imageo\n", "p1.autoRange() \n", "\n", "\n", "# Callbacks for handling user interaction\n", "def updatePlot():\n", " global img, roi, data, p2\n", " selected = roi.getArrayRegion(data, img)\n", " p2.plot(selected.mean(axis=0), clear=True)\n", "\n", "roi.sigRegionChanged.connect(updatePlot)\n", "updatePlot()\n", "\n", "def updateIsocurve():\n", " global isoLine, iso\n", " iso.setLevel(isoLine.value())\n", "\n", "isoLine.sigDragged.connect(updateIsocurve)\n", "\n", "def imageHoverEvent(event):\n", " \"\"\"Show the position, pixel, and value under the mouse cursor.\n", " \"\"\"\n", " if event.isExit():\n", " p1.setTitle(\"\")\n", " return\n", " pos = event.pos()\n", " i, j = pos.y(), pos.x()\n", " i = int(np.clip(i, 0, data.shape[0] - 1))\n", " j = int(np.clip(j, 0, data.shape[1] - 1))\n", " val = data[i, j]\n", " ppos = img.mapToParent(pos)\n", " x, y = ppos.x(), ppos.y()\n", " p1.setTitle(\"pos: (%0.1f, %0.1f) pixel: (%d, %d) value: %.3g\" % (x, y, i, j, val))\n", "\n", "# Monkey-patch the image to use our custom hover function. \n", "# This is generally discouraged (you should subclass ImageItem instead),\n", "# but it works for a very simple use like this. \n", "img.hoverEvent = imageHoverEvent\n", "\n", "\n", "# disable HistogramLUTItem right-click context menus to prevent crashes\n", "hist.vb.setMenuEnabled(False)\n", "hist.gradient.showMenu = lambda ev : None\n", "display(win)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 2 } pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/notebooks/PColorMeshItem.ipynb000066400000000000000000000076411421045507400274010ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "Demonstrates very basic use of PColorMeshItem\n", "\"\"\"\n", "from pyqtgraph.jupyter import GraphicsLayoutWidget\n", "import ipywidgets\n", "from IPython.display import display\n", "\n", "import time\n", "import numpy as np\n", "import pyqtgraph as pg\n", "\n", "## Create window with GraphicsView widget\n", "pg.mkQApp()\n", "win = GraphicsLayoutWidget(css_width=\"800px\", css_height=\"480px\")\n", "view = win.addViewBox()\n", "\n", "\n", "## Create data\n", "\n", "# To enhance the non-grid meshing, we randomize the polygon vertices per and \n", "# certain amount\n", "randomness = 5\n", "\n", "# x and y being the vertices of the polygons, they share the same shape\n", "# However the shape can be different in both dimension\n", "xn = 50 # nb points along x\n", "yn = 40 # nb points along y\n", "\n", "\n", "x = np.repeat(np.arange(1, xn+1), yn).reshape(xn, yn)\\\n", " + np.random.random((xn, yn))*randomness\n", "y = np.tile(np.arange(1, yn+1), xn).reshape(xn, yn)\\\n", " + np.random.random((xn, yn))*randomness\n", "x.sort(axis=0)\n", "y.sort(axis=0)\n", "\n", "\n", "# z being the color of the polygons its shape must be decreased by one in each dimension\n", "z = np.exp(-(x*xn)**2/1000)[:-1,:-1]\n", "\n", "## Create image item\n", "edgecolors = None\n", "antialiasing = False\n", "# edgecolors = {'color':'w', 'width':2} # May be uncommened to see edgecolor effect\n", "# antialiasing = True # May be uncommened to see antialiasing effect\n", "pcmi = pg.PColorMeshItem(edgecolors=edgecolors, antialiasing=antialiasing)\n", "view.addItem(pcmi)\n", "textitem = pg.TextItem(anchor=(1, 0))\n", "view.addItem(textitem)\n", "\n", "\n", "## Set the animation\n", "fps = 25 # Frame per second of the animation\n", "\n", "# Wave parameters\n", "wave_amplitude = 3\n", "wave_speed = 0.3\n", "wave_length = 10\n", "color_speed = 0.3\n", "\n", "textpos = None\n", "i = 0\n", "def updateData():\n", " global i\n", " global textpos\n", " \n", " ## Display the new data set\n", " t0 = time.perf_counter()\n", " new_x = x\n", " new_y = y+wave_amplitude*np.cos(x/wave_length+i)\n", " new_z = np.exp(-(x-np.cos(i*color_speed)*xn)**2/1000)[:-1,:-1]\n", " t1 = time.perf_counter()\n", " pcmi.setData(new_x,\n", " new_y,\n", " new_z)\n", " t2 = time.perf_counter()\n", "\n", " i += wave_speed\n", " \n", " # display info in top-right corner\n", " textitem.setText(f'{(t2 - t1)*1000:.1f} ms')\n", " if textpos is None:\n", " textpos = pcmi.width(), pcmi.height()\n", " textitem.setPos(*textpos)\n", "\n", "updateData()\n", "\n", "def onPlay(change):\n", " updateData()\n", " win.request_draw()\n", "\n", "play = ipywidgets.Play(\n", " interval = 40,\n", " min = 0,\n", " max = 1000,\n", ")\n", "play.observe(onPlay, names='value')\n", "\n", "display(play)\n", "display(win)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 2 } pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/notebooks/Plotting.ipynb000066400000000000000000000106461421045507400263460ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "This example demonstrates many of the 2D plotting capabilities\n", "in pyqtgraph. All of the plots may be panned/scaled by dragging with \n", "the left/right mouse buttons. Right click on any plot to show a context menu.\n", "\"\"\"\n", "from pyqtgraph.jupyter import GraphicsLayoutWidget\n", "from IPython.display import display\n", "\n", "import numpy as np\n", "import pyqtgraph as pg\n", "\n", "class CustomGLW(GraphicsLayoutWidget):\n", " def get_frame(self):\n", " # rather than eating up cpu cycles by perpetually updating \"Updating plot\",\n", " # we will only update it opportunistically on a redraw.\n", " # self.request_draw()\n", " update()\n", " return super().get_frame()\n", "\n", "pg.mkQApp()\n", "win = CustomGLW(css_width=\"1000px\", css_height=\"600px\")\n", "\n", "# Enable antialiasing for prettier plots\n", "pg.setConfigOptions(antialias=True)\n", "\n", "p1 = win.addPlot(title=\"Basic array plotting\", y=np.random.normal(size=100))\n", "\n", "p2 = win.addPlot(title=\"Multiple curves\")\n", "p2.plot(np.random.normal(size=100), pen=(255,0,0), name=\"Red curve\")\n", "p2.plot(np.random.normal(size=110)+5, pen=(0,255,0), name=\"Green curve\")\n", "p2.plot(np.random.normal(size=120)+10, pen=(0,0,255), name=\"Blue curve\")\n", "\n", "p3 = win.addPlot(title=\"Drawing with points\")\n", "p3.plot(np.random.normal(size=100), pen=(200,200,200), symbolBrush=(255,0,0), symbolPen='w')\n", "\n", "\n", "win.nextRow()\n", "\n", "p4 = win.addPlot(title=\"Parametric, grid enabled\")\n", "x = np.cos(np.linspace(0, 2*np.pi, 1000))\n", "y = np.sin(np.linspace(0, 4*np.pi, 1000))\n", "p4.plot(x, y)\n", "p4.showGrid(x=True, y=True)\n", "\n", "p5 = win.addPlot(title=\"Scatter plot, axis labels, log scale\")\n", "x = np.random.normal(size=1000) * 1e-5\n", "y = x*1000 + 0.005 * np.random.normal(size=1000)\n", "y -= y.min()-1.0\n", "mask = x > 1e-15\n", "x = x[mask]\n", "y = y[mask]\n", "p5.plot(x, y, pen=None, symbol='t', symbolPen=None, symbolSize=10, symbolBrush=(100, 100, 255, 50))\n", "p5.setLabel('left', \"Y Axis\", units='A')\n", "p5.setLabel('bottom', \"Y Axis\", units='s')\n", "p5.setLogMode(x=True, y=False)\n", "\n", "p6 = win.addPlot(title=\"Updating plot\")\n", "curve = p6.plot(pen='y')\n", "data = np.random.normal(size=(10,1000))\n", "ptr = 0\n", "def update():\n", " global curve, data, ptr, p6\n", " curve.setData(data[ptr%10])\n", " if ptr == 0:\n", " p6.enableAutoRange('xy', False) ## stop auto-scaling after the first data set is plotted\n", " ptr += 1\n", "\n", "win.nextRow()\n", "\n", "p7 = win.addPlot(title=\"Filled plot, axis disabled\")\n", "y = np.sin(np.linspace(0, 10, 1000)) + np.random.normal(size=1000, scale=0.1)\n", "p7.plot(y, fillLevel=-0.3, brush=(50,50,200,100))\n", "p7.showAxis('bottom', False)\n", "\n", "\n", "x2 = np.linspace(-100, 100, 1000)\n", "data2 = np.sin(x2) / x2\n", "p8 = win.addPlot(title=\"Region Selection\")\n", "p8.plot(data2, pen=(255,255,255,200))\n", "lr = pg.LinearRegionItem([400,700])\n", "lr.setZValue(-10)\n", "p8.addItem(lr)\n", "\n", "p9 = win.addPlot(title=\"Zoom on selected region\")\n", "p9.plot(data2)\n", "def updatePlot():\n", " p9.setXRange(*lr.getRegion(), padding=0)\n", "def updateRegion():\n", " lr.setRegion(p9.getViewBox().viewRange()[0])\n", "lr.sigRegionChanged.connect(updatePlot)\n", "p9.sigXRangeChanged.connect(updateRegion)\n", "updatePlot()\n", "\n", "display(win)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 2 } pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/notebooks/ROIExamples.ipynb000066400000000000000000000152721421045507400266760ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "Demonstrates a variety of uses for ROI. This class provides a user-adjustable\n", "region of interest marker. It is possible to customize the layout and \n", "function of the scale/rotate handles in very flexible ways. \n", "\"\"\"\n", "from pyqtgraph.jupyter import GraphicsLayoutWidget\n", "from IPython.display import display\n", "\n", "import pyqtgraph as pg\n", "import numpy as np\n", "\n", "pg.setConfigOptions(imageAxisOrder='row-major')\n", "\n", "## Create image to display\n", "arr = np.ones((100, 100), dtype=float)\n", "arr[45:55, 45:55] = 0\n", "arr[25, :] = 5\n", "arr[:, 25] = 5\n", "arr[75, :] = 5\n", "arr[:, 75] = 5\n", "arr[50, :] = 10\n", "arr[:, 50] = 10\n", "arr += np.sin(np.linspace(0, 20, 100)).reshape(1, 100)\n", "arr += np.random.normal(size=(100,100))\n", "\n", "# add an arrow for asymmetry\n", "arr[10, :50] = 10\n", "arr[9:12, 44:48] = 10\n", "arr[8:13, 44:46] = 10\n", "\n", "\n", "## create GUI\n", "app = pg.mkQApp()\n", "font = app.font()\n", "if font.pointSize() > 10:\n", " font.setPointSize(10)\n", " app.setFont(font)\n", "w = GraphicsLayoutWidget(css_width=\"1000px\", css_height=\"800px\")\n", "\n", "text = \"\"\"Data Selection From Image.
      \\n\n", "Drag an ROI or its handles to update the selected image.
      \n", "Hold CTRL while dragging to snap to pixel boundaries
      \n", "and 15-degree rotation angles.\n", "\"\"\"\n", "w1 = w.addLayout(row=0, col=0)\n", "label1 = w1.addLabel(text, row=0, col=0)\n", "v1a = w1.addViewBox(row=1, col=0, lockAspect=True)\n", "v1b = w1.addViewBox(row=2, col=0, lockAspect=True)\n", "img1a = pg.ImageItem(arr)\n", "v1a.addItem(img1a)\n", "img1b = pg.ImageItem()\n", "v1b.addItem(img1b)\n", "v1a.disableAutoRange('xy')\n", "v1b.disableAutoRange('xy')\n", "v1a.autoRange()\n", "v1b.autoRange()\n", "\n", "rois = []\n", "rois.append(pg.RectROI([20, 20], [20, 20], pen=(0,9)))\n", "rois[-1].addRotateHandle([1,0], [0.5, 0.5])\n", "rois.append(pg.LineROI([0, 60], [20, 80], width=5, pen=(1,9)))\n", "rois.append(pg.TriangleROI([80, 75], 20, pen=(5, 9)))\n", "rois.append(pg.MultiRectROI([[20, 90], [50, 60], [60, 90]], width=5, pen=(2,9)))\n", "rois.append(pg.EllipseROI([60, 10], [30, 20], pen=(3,9)))\n", "rois.append(pg.CircleROI([80, 50], [20, 20], pen=(4,9)))\n", "#rois.append(pg.LineSegmentROI([[110, 50], [20, 20]], pen=(5,9)))\n", "rois.append(pg.PolyLineROI([[80, 60], [90, 30], [60, 40]], pen=(6,9), closed=True))\n", "\n", "def update(roi):\n", " img1b.setImage(roi.getArrayRegion(arr, img1a), levels=(0, arr.max()))\n", " v1b.autoRange()\n", " \n", "for roi in rois:\n", " roi.sigRegionChanged.connect(update)\n", " v1a.addItem(roi)\n", "\n", "update(rois[-1])\n", " \n", "\n", "\n", "text = \"\"\"User-Modifiable ROIs
      \n", "Click on a line segment to add a new handle.\n", "Right click on a handle to remove.\n", "\"\"\"\n", "w2 = w.addLayout(row=0, col=1)\n", "label2 = w2.addLabel(text, row=0, col=0)\n", "v2a = w2.addViewBox(row=1, col=0, lockAspect=True)\n", "r2a = pg.PolyLineROI([[0,0], [10,10], [10,30], [30,10]], closed=True)\n", "v2a.addItem(r2a)\n", "r2b = pg.PolyLineROI([[0,-20], [10,-10], [10,-30]], closed=False)\n", "v2a.addItem(r2b)\n", "v2a.disableAutoRange('xy')\n", "#v2b.disableAutoRange('xy')\n", "v2a.autoRange()\n", "#v2b.autoRange()\n", "\n", "text = \"\"\"Building custom ROI types
      \n", "ROIs can be built with a variety of different handle types
      \n", "that scale and rotate the roi around an arbitrary center location\n", "\"\"\"\n", "w3 = w.addLayout(row=1, col=0)\n", "label3 = w3.addLabel(text, row=0, col=0)\n", "v3 = w3.addViewBox(row=1, col=0, lockAspect=True)\n", "\n", "r3a = pg.ROI([0,0], [10,10])\n", "v3.addItem(r3a)\n", "## handles scaling horizontally around center\n", "r3a.addScaleHandle([1, 0.5], [0.5, 0.5])\n", "r3a.addScaleHandle([0, 0.5], [0.5, 0.5])\n", "\n", "## handles scaling vertically from opposite edge\n", "r3a.addScaleHandle([0.5, 0], [0.5, 1])\n", "r3a.addScaleHandle([0.5, 1], [0.5, 0])\n", "\n", "## handles scaling both vertically and horizontally\n", "r3a.addScaleHandle([1, 1], [0, 0])\n", "r3a.addScaleHandle([0, 0], [1, 1])\n", "\n", "r3b = pg.ROI([20,0], [10,10])\n", "v3.addItem(r3b)\n", "## handles rotating around center\n", "r3b.addRotateHandle([1, 1], [0.5, 0.5])\n", "r3b.addRotateHandle([0, 0], [0.5, 0.5])\n", "\n", "## handles rotating around opposite corner\n", "r3b.addRotateHandle([1, 0], [0, 1])\n", "r3b.addRotateHandle([0, 1], [1, 0])\n", "\n", "## handles rotating/scaling around center\n", "r3b.addScaleRotateHandle([0, 0.5], [0.5, 0.5])\n", "\n", "# handles rotating/scaling around arbitrary point\n", "r3b.addScaleRotateHandle([0.3, 0], [0.9, 0.7])\n", "\n", "v3.disableAutoRange('xy')\n", "v3.autoRange()\n", "\n", "\n", "text = \"\"\"Transforming objects with ROI\"\"\"\n", "w4 = w.addLayout(row=1, col=1)\n", "label4 = w4.addLabel(text, row=0, col=0)\n", "v4 = w4.addViewBox(row=1, col=0, lockAspect=True)\n", "g = pg.GridItem()\n", "v4.addItem(g)\n", "r4 = pg.ROI([0,0], [100,100], resizable=False, removable=True)\n", "r4.addRotateHandle([1,0], [0.5, 0.5])\n", "r4.addRotateHandle([0,1], [0.5, 0.5])\n", "img4 = pg.ImageItem(arr)\n", "v4.addItem(r4)\n", "img4.setParentItem(r4)\n", "\n", "v4.disableAutoRange('xy')\n", "v4.autoRange()\n", "\n", "# Provide a callback to remove the ROI (and its children) when\n", "# \"remove\" is selected from the context menu.\n", "def remove():\n", " v4.removeItem(r4)\n", "r4.sigRemoveRequested.connect(remove)\n", "\n", "\n", "display(w)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 2 } pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/notebooks/ScatterPlot.ipynb000066400000000000000000000140031421045507400270010ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "Example demonstrating a variety of scatter plot features.\n", "\"\"\"\n", "from pyqtgraph.jupyter import GraphicsLayoutWidget\n", "import ipywidgets\n", "from IPython.display import display\n", "\n", "from pyqtgraph.Qt import QtGui\n", "import pyqtgraph as pg\n", "import numpy as np\n", "from collections import namedtuple\n", "\n", "pg.mkQApp()\n", "view = GraphicsLayoutWidget(css_width=\"800px\", css_height=\"800px\")\n", "\n", "## create four areas to add plots\n", "w1 = view.addPlot()\n", "w2 = view.addViewBox()\n", "w2.setAspectLocked(True)\n", "view.nextRow()\n", "w3 = view.addPlot()\n", "w4 = view.addPlot()\n", "print(\"Generating data, this takes a few seconds...\")\n", "\n", "## Make all plots clickable\n", "clickedPen = pg.mkPen('b', width=2)\n", "lastClicked = []\n", "def clicked(plot, points):\n", " global lastClicked\n", " for p in lastClicked:\n", " p.resetPen()\n", " with out:\n", " print(\"clicked points\", points)\n", " for p in points:\n", " p.setPen(clickedPen)\n", " lastClicked = points\n", " view.request_draw()\n", "\n", "\n", "## There are a few different ways we can draw scatter plots; each is optimized for different types of data:\n", "\n", "## 1) All spots identical and transform-invariant (top-left plot).\n", "## In this case we can get a huge performance boost by pre-rendering the spot\n", "## image and just drawing that image repeatedly.\n", "\n", "n = 300\n", "s1 = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 120))\n", "pos = np.random.normal(size=(2,n), scale=1e-5)\n", "spots = [{'pos': pos[:,i], 'data': 1} for i in range(n)] + [{'pos': [0,0], 'data': 1}]\n", "s1.addPoints(spots)\n", "w1.addItem(s1)\n", "s1.sigClicked.connect(clicked)\n", "\n", "\n", "## 2) Spots are transform-invariant, but not identical (top-right plot).\n", "## In this case, drawing is almsot as fast as 1), but there is more startup\n", "## overhead and memory usage since each spot generates its own pre-rendered\n", "## image.\n", "\n", "TextSymbol = namedtuple(\"TextSymbol\", \"label symbol scale\")\n", "\n", "def createLabel(label, angle):\n", " symbol = QtGui.QPainterPath()\n", " #symbol.addText(0, 0, QFont(\"San Serif\", 10), label)\n", " f = QtGui.QFont()\n", " f.setPointSize(10)\n", " symbol.addText(0, 0, f, label)\n", " br = symbol.boundingRect()\n", " scale = min(1. / br.width(), 1. / br.height())\n", " tr = QtGui.QTransform()\n", " tr.scale(scale, scale)\n", " tr.rotate(angle)\n", " tr.translate(-br.x() - br.width()/2., -br.y() - br.height()/2.)\n", " return TextSymbol(label, tr.map(symbol), 0.1 / scale)\n", "\n", "random_str = lambda : (''.join([chr(np.random.randint(ord('A'),ord('z'))) for i in range(np.random.randint(1,5))]), np.random.randint(0, 360))\n", "\n", "s2 = pg.ScatterPlotItem(size=10, pen=pg.mkPen('w'), pxMode=True)\n", "pos = np.random.normal(size=(2,n), scale=1e-5)\n", "spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': i%10, 'size': 5+i/10.} for i in range(n)]\n", "s2.addPoints(spots)\n", "spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': label[1], 'size': label[2]*(5+i/10.)} for (i, label) in [(i, createLabel(*random_str())) for i in range(n)]]\n", "s2.addPoints(spots)\n", "w2.addItem(s2)\n", "s2.sigClicked.connect(clicked)\n", "\n", "\n", "## 3) Spots are not transform-invariant, not identical (bottom-left).\n", "## This is the slowest case, since all spots must be completely re-drawn\n", "## every time because their apparent transformation may have changed.\n", "\n", "s3 = pg.ScatterPlotItem(\n", " pxMode=False, # Set pxMode=False to allow spots to transform with the view\n", " hoverable=True,\n", " hoverPen=pg.mkPen('g'),\n", " hoverSize=1e-6\n", ")\n", "spots3 = []\n", "for i in range(10):\n", " for j in range(10):\n", " spots3.append({'pos': (1e-6*i, 1e-6*j), 'size': 1e-6, 'pen': {'color': 'w', 'width': 2}, 'brush':pg.intColor(i*10+j, 100)})\n", "s3.addPoints(spots3)\n", "w3.addItem(s3)\n", "s3.sigClicked.connect(clicked)\n", "\n", "## Test performance of large scatterplots\n", "\n", "s4 = pg.ScatterPlotItem(\n", " size=10,\n", " pen=pg.mkPen(None),\n", " brush=pg.mkBrush(255, 255, 255, 20),\n", " hoverable=True,\n", " hoverSymbol='s',\n", " hoverSize=15,\n", " hoverPen=pg.mkPen('r', width=2),\n", " hoverBrush=pg.mkBrush('g'),\n", ")\n", "n = 10000\n", "pos = np.random.normal(size=(2, n), scale=1e-9)\n", "s4.addPoints(\n", " x=pos[0],\n", " y=pos[1],\n", " # size=(np.random.random(n) * 20.).astype(int),\n", " # brush=[pg.mkBrush(x) for x in np.random.randint(0, 256, (n, 3))],\n", " data=np.arange(n)\n", ")\n", "w4.addItem(s4)\n", "s4.sigClicked.connect(clicked)\n", "\n", "\n", "out = ipywidgets.Output(layout={'border': '1px solid black'})\n", "display(view)\n", "display(out)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 2 } pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/notebooks/simple.ipynb000066400000000000000000000043471421045507400260400ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "fa151629", "metadata": {}, "outputs": [], "source": [ "import pyqtgraph as pg\n", "import numpy as np\n", "from pyqtgraph.jupyter import PlotWidget\n", "\n", "pg.mkQApp()" ] }, { "cell_type": "code", "execution_count": null, "id": "3c28c27d", "metadata": {}, "outputs": [], "source": [ "pw1 = PlotWidget()\n", "pw1.plot(np.arange(10))\n", "pw1" ] }, { "cell_type": "code", "execution_count": null, "id": "9a2fe974", "metadata": {}, "outputs": [], "source": [ "pw2 = PlotWidget()\n", "pditem = pw2.plot()\n", "x = np.linspace(-3, 3, 1000)\n", "data = np.sinc(x)\n", "pditem.setData(data)\n", "pw2" ] }, { "cell_type": "code", "execution_count": null, "id": "8644cd6f", "metadata": {}, "outputs": [], "source": [ "pw3 = PlotWidget()\n", "imgitem = pg.ImageItem(axisOrder='row-major')\n", "pw3.addItem(imgitem)\n", "x = np.linspace(-3, 3, 1000)\n", "y = np.linspace(-3, 3, 1000)\n", "data = np.sinc(y)[:,None] * np.sinc(x)\n", "imgitem.setImage(data)\n", "\n", "limits = data.min(), data.max()\n", "# bar = pw3.addColorBar( imgitem, colorMap='viridis', values=limits, limits=limits, rounding=0.1)\n", "cmap = pg.colormap.get('viridis')\n", "bar = pg.ColorBarItem(values=limits, colorMap=cmap, limits=limits, rounding=0.1)\n", "bar.setImageItem(imgitem, insert_in=pw3.getPlotItem())\n", "bar.sigLevelsChanged.connect(pw3.request_draw)\n", "bar.sigLevelsChangeFinished.connect(pw3.request_draw)\n", "\n", "pw3" ] }, { "cell_type": "code", "execution_count": null, "id": "f695e828", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 5 } pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/optics/000077500000000000000000000000001421045507400227725ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/optics/__init__.py000066400000000000000000000000271421045507400251020ustar00rootroot00000000000000from .pyoptic import * pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/optics/pyoptic.py000066400000000000000000000423411421045507400250370ustar00rootroot00000000000000import csv import gzip import os from math import asin, atan2, cos, degrees, hypot, sin, sqrt import numpy as np import pyqtgraph as pg from pyqtgraph import Point from pyqtgraph.Qt import QtCore, QtGui class GlassDB: """ Database of dispersion coefficients for Schott glasses + Corning 7980 """ def __init__(self, fileName='schott_glasses.csv'): path = os.path.dirname(__file__) fh = gzip.open(os.path.join(path, 'schott_glasses.csv.gz'), 'rb') r = csv.reader(map(str, fh.readlines())) lines = [x for x in r] self.data = {} header = lines[0] for l in lines[1:]: info = {} for i in range(1, len(l)): info[header[i]] = l[i] self.data[l[0]] = info self.data['Corning7980'] = { ## Thorlabs UV fused silica--not in schott catalog. 'B1': 0.68374049400, 'B2': 0.42032361300, 'B3': 0.58502748000, 'C1': 0.00460352869, 'C2': 0.01339688560, 'C3': 64.49327320000, 'TAUI25/250': 0.95, ## transmission data is fabricated, but close. 'TAUI25/1400': 0.98, } for k in self.data: self.data[k]['ior_cache'] = {} def ior(self, glass, wl): """ Return the index of refraction for *glass* at wavelength *wl*. The *glass* argument must be a key in self.data. """ info = self.data[glass] cache = info['ior_cache'] if wl not in cache: B = list(map(float, [info['B1'], info['B2'], info['B3']])) C = list(map(float, [info['C1'], info['C2'], info['C3']])) w2 = (wl/1000.)**2 n = sqrt(1.0 + (B[0]*w2 / (w2-C[0])) + (B[1]*w2 / (w2-C[1])) + (B[2]*w2 / (w2-C[2]))) cache[wl] = n return cache[wl] def transmissionCurve(self, glass): data = self.data[glass] keys = [int(x[7:]) for x in data.keys() if 'TAUI25' in x] keys.sort() curve = np.empty((2,len(keys))) for i in range(len(keys)): curve[0][i] = keys[i] key = 'TAUI25/%d' % keys[i] val = data[key] if val == '': val = 0 else: val = float(val) curve[1][i] = val return curve GLASSDB = GlassDB() def wlPen(wl): """Return a pen representing the given wavelength""" l1 = 400 l2 = 700 hue = np.clip(((l2-l1) - (wl-l1)) * 0.8 / (l2-l1), 0, 0.8) val = 1.0 if wl > 700: val = 1.0 * (((700-wl)/700.) + 1) elif wl < 400: val = wl * 1.0/400. #print hue, val color = pg.hsvColor(hue, 1.0, val) pen = pg.mkPen(color) return pen class ParamObj(object): # Just a helper for tracking parameters and responding to changes def __init__(self): self.__params = {} def __setitem__(self, item, val): self.setParam(item, val) def setParam(self, param, val): self.setParams(**{param:val}) def setParams(self, **params): """Set parameters for this optic. This is a good function to override for subclasses.""" self.__params.update(params) self.paramStateChanged() def paramStateChanged(self): pass def __getitem__(self, item): # bug in pyside 1.2.2 causes getitem to be called inside QGraphicsObject.parentItem: return self.getParam(item) # PySide bug: https://bugreports.qt.io/browse/PYSIDE-671 def __len__(self): # Workaround for PySide bug: https://bugreports.qt.io/browse/PYSIDE-671 return 0 def getParam(self, param): return self.__params[param] class Optic(pg.GraphicsObject, ParamObj): sigStateChanged = QtCore.Signal() def __init__(self, gitem, **params): ParamObj.__init__(self) pg.GraphicsObject.__init__(self) #, [0,0], [1,1]) self.gitem = gitem self.surfaces = gitem.surfaces gitem.setParentItem(self) self.roi = pg.ROI([0,0], [1,1]) self.roi.addRotateHandle([1, 1], [0.5, 0.5]) self.roi.setParentItem(self) defaults = { 'pos': Point(0,0), 'angle': 0, } defaults.update(params) self._ior_cache = {} self.roi.sigRegionChanged.connect(self.roiChanged) self.setParams(**defaults) def updateTransform(self): self.setPos(0, 0) tr = QtGui.QTransform() self.setTransform(tr.translate(Point(self['pos'])).rotate(self['angle'])) def setParam(self, param, val): ParamObj.setParam(self, param, val) def paramStateChanged(self): """Some parameters of the optic have changed.""" # Move graphics item self.gitem.setPos(Point(self['pos'])) self.gitem.resetTransform() self.gitem.setRotation(self['angle']) # Move ROI to match try: self.roi.sigRegionChanged.disconnect(self.roiChanged) br = self.gitem.boundingRect() o = self.gitem.mapToParent(br.topLeft()) self.roi.setAngle(self['angle']) self.roi.setPos(o) self.roi.setSize([br.width(), br.height()]) finally: self.roi.sigRegionChanged.connect(self.roiChanged) self.sigStateChanged.emit() def roiChanged(self, *args): pos = self.roi.pos() # rotate gitem temporarily so we can decide where it will need to move self.gitem.resetTransform() self.gitem.setRotation(self.roi.angle()) br = self.gitem.boundingRect() o1 = self.gitem.mapToParent(br.topLeft()) self.setParams(angle=self.roi.angle(), pos=pos + (self.gitem.pos() - o1)) def boundingRect(self): return QtCore.QRectF() def paint(self, p, *args): pass def ior(self, wavelength): return GLASSDB.ior(self['glass'], wavelength) class Lens(Optic): def __init__(self, **params): defaults = { 'dia': 25.4, ## diameter of lens 'r1': 50., ## positive means convex, use 0 for planar 'r2': 0, ## negative means convex 'd': 4.0, 'glass': 'N-BK7', 'reflect': False, } defaults.update(params) d = defaults.pop('d') defaults['x1'] = -d/2. defaults['x2'] = d/2. gitem = CircularSolid(brush=(100, 100, 130, 100), **defaults) Optic.__init__(self, gitem, **defaults) def propagateRay(self, ray): """Refract, reflect, absorb, and/or scatter ray. This function may create and return new rays""" """ NOTE:: We can probably use this to compute refractions faster: (from GLSL 120 docs) For the incident vector I and surface normal N, and the ratio of indices of refraction eta, return the refraction vector. The result is computed by k = 1.0 - eta * eta * (1.0 - dot(N, I) * dot(N, I)) if (k < 0.0) return genType(0.0) else return eta * I - (eta * dot(N, I) + sqrt(k)) * N The input parameters for the incident vector I and the surface normal N must already be normalized to get the desired results. eta == ratio of IORs For reflection: For the incident vector I and surface orientation N, returns the reflection direction: I – 2 βˆ— dot(N, I) βˆ— N N must already be normalized in order to achieve the desired result. """ iors = [self.ior(ray['wl']), 1.0] for i in [0,1]: surface = self.surfaces[i] ior = iors[i] p1, ai = surface.intersectRay(ray) if p1 is None: ray.setEnd(None) break p1 = surface.mapToItem(ray, p1) rd = ray['dir'] a1 = atan2(rd[1], rd[0]) try: ar = a1 - ai + asin((sin(ai) * ray['ior'] / ior)) except ValueError: ar = np.nan ray.setEnd(p1) dp = Point(cos(ar), sin(ar)) ray = Ray(parent=ray, ior=ior, dir=dp) return [ray] class Mirror(Optic): def __init__(self, **params): defaults = { 'r1': 0, 'r2': 0, 'd': 0.01, } defaults.update(params) d = defaults.pop('d') defaults['x1'] = -d/2. defaults['x2'] = d/2. gitem = CircularSolid(brush=(100,100,100,255), **defaults) Optic.__init__(self, gitem, **defaults) def propagateRay(self, ray): """Refract, reflect, absorb, and/or scatter ray. This function may create and return new rays""" surface = self.surfaces[0] p1, ai = surface.intersectRay(ray) if p1 is not None: p1 = surface.mapToItem(ray, p1) rd = ray['dir'] a1 = atan2(rd[1], rd[0]) ar = a1 + np.pi - 2 * ai ray.setEnd(p1) dp = Point(cos(ar), sin(ar)) ray = Ray(parent=ray, dir=dp) else: ray.setEnd(None) return [ray] class CircularSolid(pg.GraphicsObject, ParamObj): """GraphicsObject with two circular or flat surfaces.""" def __init__(self, pen=None, brush=None, **opts): """ Arguments for each surface are: x1,x2 - position of center of _physical surface_ r1,r2 - radius of curvature d1,d2 - diameter of optic """ defaults = dict(x1=-2, r1=100, d1=25.4, x2=2, r2=100, d2=25.4) defaults.update(opts) ParamObj.__init__(self) self.surfaces = [CircleSurface(defaults['r1'], defaults['d1']), CircleSurface(-defaults['r2'], defaults['d2'])] pg.GraphicsObject.__init__(self) for s in self.surfaces: s.setParentItem(self) if pen is None: self.pen = pg.mkPen((220,220,255,200), width=1, cosmetic=True) else: self.pen = pg.mkPen(pen) if brush is None: self.brush = pg.mkBrush((230, 230, 255, 30)) else: self.brush = pg.mkBrush(brush) self.setParams(**defaults) def paramStateChanged(self): self.updateSurfaces() def updateSurfaces(self): self.surfaces[0].setParams(self['r1'], self['d1']) self.surfaces[1].setParams(-self['r2'], self['d2']) self.surfaces[0].setPos(self['x1'], 0) self.surfaces[1].setPos(self['x2'], 0) self.path = QtGui.QPainterPath() self.path.connectPath(self.surfaces[0].path.translated(self.surfaces[0].pos())) self.path.connectPath(self.surfaces[1].path.translated(self.surfaces[1].pos()).toReversed()) self.path.closeSubpath() def boundingRect(self): return self.path.boundingRect() def shape(self): return self.path def paint(self, p, *args): p.setRenderHints(p.renderHints() | p.RenderHint.Antialiasing) p.setPen(self.pen) p.fillPath(self.path, self.brush) p.drawPath(self.path) class CircleSurface(pg.GraphicsObject): def __init__(self, radius=None, diameter=None): """center of physical surface is at 0,0 radius is the radius of the surface. If radius is None, the surface is flat. diameter is of the optic's edge.""" pg.GraphicsObject.__init__(self) self.r = radius self.d = diameter self.mkPath() def setParams(self, r, d): self.r = r self.d = d self.mkPath() def mkPath(self): self.prepareGeometryChange() r = self.r d = self.d h2 = d/2. self.path = QtGui.QPainterPath() if r == 0: ## flat surface self.path.moveTo(0, h2) self.path.lineTo(0, -h2) else: ## half-height of surface can't be larger than radius h2 = min(h2, abs(r)) arc = QtCore.QRectF(0, -r, r*2, r*2) a1 = degrees(asin(h2/r)) a2 = -2*a1 a1 += 180. self.path.arcMoveTo(arc, a1) self.path.arcTo(arc, a1, a2) self.h2 = h2 def boundingRect(self): return self.path.boundingRect() def paint(self, p, *args): return ## usually we let the optic draw. def intersectRay(self, ray): ## return the point of intersection and the angle of incidence #print "intersect ray" h = self.h2 r = self.r p, dir = ray.currentState(relativeTo=self) # position and angle of ray in local coords. #print " ray: ", p, dir p = p - Point(r, 0) ## move position so center of circle is at 0,0 #print " adj: ", p, r if r == 0: #print " flat" if dir[0] == 0: y = 0 else: y = p[1] - p[0] * dir[1]/dir[0] if abs(y) > h: return None, None else: return (Point(0, y), atan2(dir[1], dir[0])) else: #print " curve" ## find intersection of circle and line (quadratic formula) dx = dir[0] dy = dir[1] dr = hypot(dx, dy) # length D = p[0] * (p[1]+dy) - (p[0]+dx) * p[1] idr2 = 1.0 / dr**2 disc = r**2 * dr**2 - D**2 if disc < 0: return None, None disc2 = disc**0.5 if dy < 0: sgn = -1 else: sgn = 1 br = self.path.boundingRect() x1 = (D*dy + sgn*dx*disc2) * idr2 y1 = (-D*dx + abs(dy)*disc2) * idr2 if br.contains(x1+r, y1): pt = Point(x1, y1) else: x2 = (D*dy - sgn*dx*disc2) * idr2 y2 = (-D*dx - abs(dy)*disc2) * idr2 pt = Point(x2, y2) if not br.contains(x2+r, y2): return None, None norm = atan2(pt[1], pt[0]) if r < 0: norm += np.pi dp = p - pt ang = atan2(dp[1], dp[0]) return pt + Point(r, 0), ang-norm class Ray(pg.GraphicsObject, ParamObj): """Represents a single straight segment of a ray""" sigStateChanged = QtCore.Signal() def __init__(self, **params): ParamObj.__init__(self) defaults = { 'ior': 1.0, 'wl': 500, 'end': None, 'dir': Point(1,0), } self.params = {} pg.GraphicsObject.__init__(self) self.children = [] parent = params.get('parent', None) if parent is not None: defaults['start'] = parent['end'] defaults['wl'] = parent['wl'] self['ior'] = parent['ior'] self['dir'] = parent['dir'] parent.addChild(self) defaults.update(params) defaults['dir'] = Point(defaults['dir']) self.setParams(**defaults) self.mkPath() def clearChildren(self): for c in self.children: c.clearChildren() c.setParentItem(None) self.scene().removeItem(c) self.children = [] def paramStateChanged(self): pass def addChild(self, ch): self.children.append(ch) ch.setParentItem(self) def currentState(self, relativeTo=None): pos = self['start'] dir = self['dir'] if relativeTo is None: return pos, dir else: trans = self.itemTransform(relativeTo)[0] p1 = trans.map(pos) p2 = trans.map(pos + dir) return Point(p1), Point(p2-p1) def setEnd(self, end): self['end'] = end self.mkPath() def boundingRect(self): return self.path.boundingRect() def paint(self, p, *args): #p.setPen(pg.mkPen((255,0,0, 150))) p.setRenderHints(p.renderHints() | p.RenderHint.Antialiasing) p.setCompositionMode(p.CompositionMode.CompositionMode_Plus) p.setPen(wlPen(self['wl'])) p.drawPath(self.path) def mkPath(self): self.prepareGeometryChange() self.path = QtGui.QPainterPath() self.path.moveTo(self['start']) if self['end'] is not None: self.path.lineTo(self['end']) else: self.path.lineTo(self['start']+500*self['dir']) def trace(rays, optics): if len(optics) < 1 or len(rays) < 1: return for r in rays: r.clearChildren() o = optics[0] r2 = o.propagateRay(r) trace(r2, optics[1:]) class Tracer(QtCore.QObject): """ Simple ray tracer. Initialize with a list of rays and optics; calling trace() will cause rays to be extended by propagating them through each optic in sequence. """ def __init__(self, rays, optics): QtCore.QObject.__init__(self) self.optics = optics self.rays = rays for o in self.optics: o.sigStateChanged.connect(self.trace) self.trace() def trace(self): trace(self.rays, self.optics) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/optics/schott_glasses.csv.gz000066400000000000000000001105601421045507400271560ustar00rootroot00000000000000‹|n}Sschott_glasses.csvν½Ϋ²$Χu$ψ―(γKKmyNνϋε‘AjΦ£866ύ% 6 ΐ@Ιτ_3?Π_6Λέ׎Ψq’N‘ VK=-€¬Μξ»ΗΧ_<Ύ~χψ‡/πξρΛoΎϊζίΎων7_Ό{ό">~‘ΏΘ_ΖΗ/Σγ—ωρ«πψU|ό*=ώ<<ώ<>ΎϊόχϋΕηZψϊχwO_}iοσ;]ύ‹ήωζCOύμϋoί}χέ›oώπύ—Ώύό+ϋκοώξοΎόν—οΎώώρ›Ÿ)Υ·©†pάΞ©ΫqφγώXσyΫ>fέξηKΫyoKΗΝ:Ξ›₯7Ο—•σe%O(η;”PΟ›Η½yž7ϏΘη7ΞνxYη½Ή7ϏΘρΌΉ‹σ#ωιόˆt~υδΓy8q{N»}Nά^‡·Χα΄Ϋύ|i;ο]‡ΣnΓ‰›~8qσ|Y9_Ά'nžο°'oχΓ‰›ηGδσ―Γ‰›ηo]‡7ϏX‡7·cq~D:?"‘Ϊ~ ΏψγίώνWοήό |σ§Ώψ³Η/ζρΙί<>ϋ›ΗΟζρΧσψΥ»―ΏϋςϋzόζwίΔόψMnΏ|χωχo~ϋω>­=ςζOϋ‡?σ»Ύωϊ‹?ώφϋ/ΑξώSαςΟŸυ‡ΏόΝSΖaχ?’ΎρυΝΏώέϋΎ{σϋoΎψγW΄σ?{όυ7_~χέ7_ΏωφσοΏόζ͟ώ·χΟŸ~ύΝ7xσχŸϋΕΧΐ؟ώε§φψωί~ϋωw_ΪσΆ»Βžϊxσζk,‡η‚[X Ο‘·jšφΝqΛΒsΐ­οqρ.ΎΕΕ/yρpΩrzΈρ+\|‹wΈψ„|ΚοpρχΈψvŠτA9&}NšνΉσΖϊ6©Œηόψϊ“'ϋ({—'|ΨΟήΌωλ7ί=Ύ™nύςρίϊβρKΏυξρ…ίϊέγΏυεγοuλ?ΰΕoΦm{ΓοΦm{ƒ°nΫ[Ο±79ξ··ΡύΏΒGήόnέόδρnέ\Ÿώ+|όο~φψ [2ά’ζέο?φώξρ«ΟΏχψφέWΟoώϊΫ/ϋξρώΝΣ_ΩB²‹Ÿζρ—ρτWςIzΔgΓY(Ό.a2Ά {$—šsσžS˜‘ηΨ›έžΉηΪcKv;„9{/yτˆΏJ₯†ήϊ#Ζψyέ:žίbJΧ5d].ΪΨjγ5ΞΦΛElΧΆ1λΫ²]Ϋ·ΰλjΰJl­%Ύί(΅?ς!φΐf7ΐJ9ΰ`•1°ΘΣΜƒwφΐ…“xΨbαcIwκ*•’ωLΎ FΜ‡Žͺ_U]ω‘Œ φ[r+ΟΩρWο>ΫψχŸωυ—_ξΝοhΎ§?Ό{€τ6½%`šαψ/σ’{Δ)λZκΔΨZπδ&ΊLdψL.…Ι£3ΉΖ΅RΉB†Ϋ ί"¦cώa?ΙϋWΌ£:o_Ž·žS[ύ ΙBςӟ|Ryΐrα έ>%g;\Ή?›9PC)4(qςΩ\ ͎°ŒAΈ;^‘πλ•Ϊ[Β ΒՎHΆJsΜαVαfΛ‡6δ&>Φοl†α€a8 \pNˆΟ M;ΆΒρ€pε{.ϋƒ@«|ΟΖΛΡh,ΗΨϋΪί/7λ6Γ†‰ΊΑ–ψΠ;g^F\φ¦νθαΏ\³ΔνΓ^ιύ²έΎτζ/ΘΧΰυ=…'vπωMΛμNvK7πCg}4;;u8jνhf`lΧ3sξ…h¬}₯X/άΞGODχ 3σΪπΙλΡ‰vϋςDc0di±¦q[΄@siB}ΊSζΡhq€β¦΅‡•y’ΦΎp΄χ@ ŽC™©΅όΛPKΗΝwz{οŒr_"7«R£^N(njΏΖ˜BGΤ•™¦'^GΏΞΊΆd?ΗB³dύCΐΓΒΨ·φ;l)ɧܝjˆ™ΗՎ‡‚φlkΖ.ν»εό–V-Φ6zΰj΄ ,΅TK)4φ»ZΜ“ί͜ΐ’μŒgƒBΨΗ ψΟ½t[Ε†£ζψνIψΝ„-¬½™$šΰbΗΓρΫd‚¦@¬nd‰_·Ν0μΔ}ΣΘx_Ve―‰ι‚ΫΔo"~‰¬QΓi‘|ωίέe]nFφ‚άqξ£έPΜ{κ8QΜOΟΌωB ’Ή„™ϋφα»IΎΪ}ΛωπƳ㹝?]xζΖάΈSΛθΔβ_-:œXΧ ΘNϋ—‡€cAΩα΅]σΡνxΣG{ό%ωh›@₯ΟUF Ή,£ΠΕΌ<]ka³|“םζΊL@Χ“ Ψ«/δ4o ם€ΆDyL…Elzρο'ιπΜΓ7ΗΣωHςG‚<λζΘςGš/L.vίQi)»c5l ΈCdw¦σ‰πΧ.w?VΑ‰ΜbΡ¦Ο‘τ{ƒθCίφ‚΅§|Ψ4ί½ŠΧ.λΛο―ο©oHO'4σ=-ΆZΰΆ—˜•vkm ύ7ωΡm™‘64Oσ€2[RΨ?³5!\ΡPα*Ϋx4ͺyAS £κο1υ|·ω1†z[ΕΈξBjμ΄ν5ΑΛΕu-enRνΥφ¦~B–ž7“ƒ'©" H3…‘πZN‹…“?#‹ͺ8Ι>- •V4Χ’°|iΟ—„u΅ξ΅_ΥmG7•·γm²Λε“Οψs›Ή}Ċ!±".ΆύΝ. dΩ]όjAΗθςχνΤZΕΣ[l ꍋ+D3)ršl;-φ”>ντ΅i¦Ε?ό-σχέ2g·ΜQHˆ]±ΫvM΄}€V…Ώg€=$.dE„άe”‡κzε°w7Κ΄ΰv|ξ°‘œvROF]™ΆΛx^އεΐΨ(η»ΙφVžϊ¬Τ–0 ζŽμΞ~ϊ΅“»΅Cθ£,oΩ€·eΥr>ήMvv<³uάSΉΈ’m‘Ηv°ν_5_Η•Μ·1GΖ~y±―SΉd+Χφ[ϋΆ ž4D΅0hEG,J£:-`kUΫ¨‘‰p7η™jP <Ί\ηiN&―-2ζu’Kl.MΎ­W\[„λΈ Ψ'L{–+n~‘m‡+=#Ύέ(rž 3°ηψK©‹Γ•ξΚ‰KBκu<›™Ρ‘ Ο4WšhΘAŸaα‡\gGi.±oΨΟΘ-Γ²=~φSϋζΏόκ«7ϋξ·ίόώέ›/Ώώ―όςΫςΗ?ξΝ7χζ―>ϊ  χxσυ7ίΏωOόύ»―ΏxχΕ›ΏϋζΫ7_ΏϋΗ7_ΌϋξΛί}ύ³GšoΛ[ ŸˆύΜsT‹φ;5v²§Ή¨Έ„ΛΫίrs4§:υAŒΫ‘k£)Ϊ³!ηά<Ο½Ϊ9Ν¨j[›$s u}Ψ:¨ˆŠWf¬Œ•K όiξPό98ψ‹GΤν°Ξ8g\t<:οθ‘ΆΏ’Ή«Ξx»^Ί­Y» K”ώhΘJe¨]9θπ¦―žΫ§‹ΉL”ΚͺΫ&Β,Άώ^Ξ>Ξp?Γ7ε₯fP°«/π1aτ;μIφpn"Ύp ΨχΚΠΑΉ tι²Ψ·«8[…ν©L.ςΫ&σDƒͺ#ŠΈ6*+‡ΐn~XŠ³άαVBΧ&ΡJΡf‘ΔH™Ϊ`΄ymηΆPΉ™hΣ°|3ΡγΘπύmIΞτx;έθ<Ύ©ͺΖyΊΣτ š[bν2=Ι‘f¬Y«o]V[gΜ\W<³ρ± =Ÿα χλ.πgƒžύŒ<Ϋ3^ώ/ϊψΓ ·XΌΎeB)όΙ―ώΙ֎¨XΖPkΐΞn•]φtX|;¨‚+`Kάblυ€8—”ΩϋlސV(ν}lΩΞπ@"Μφ ΫΊ­ŽeογŠΔ³"qsί!?uγleNs=ο,Œά—<>Ys¦Ν:@€{ Κ§ηΐΨήd|V~j‰[,ΌΧG³2΅Κ\ΧθrνθγzαRJI)Χ θΛi­yά=ξYω_*²n'ΠλφntΎτΖgV.Ϊh‹υό†w·ψΆ·.Μ# ΄Ck©ζτqoΔ‘δΞ)fΈG„χ˜νξQ›!OΚΐ4fΏ JΜ…χœ˜›ξY­&’ΥάFfΗz©z‹Ζγm‘Ϊ5Ά ^Wξ*½+Xο£sΧ0O’₯υ8”ͺ=+Ζ]TI²dv'½šA›‰“UOΌ3s·ˆUω΅•λ–S Š`μrΔ»›ϊθ¦>yPm‡Ύ[$RνsνεJw··ύ-Θ†ΤOTBˆάλ(Ψ΄†KσPryΛ\šsNλl[¬9>]Y<Νϋ‰^·„ν· j>ό#A3£Β [,¬NβWgΗjnŽΥΉΜsVΓΥ#Š{xΌgΟg>’|„ίι'Gω“ΎΒ?\{­i7ώ˜ΰυ΅BΠ+Όg—•Rέͺ`όΔPΒ†ΩQψήeOΪύΨbΥkώΌ6–-λ=βθΞχΙιϊμ8]ς݈±ώB.„nISα΄FμˆfΘiΎRfφ;ι #pEƒλ©¬v«Š [댌Νzz–\‘―9‡YΧώχ¨ͺEd YςX”5OYΩτ©ZUp+'χ†,σ™ύ.‰‘Œέ(r‹jV:Dζ²Λ&σDβ«ρζ=%žeΰKS$ΝU t—Άͺ'/L½ˆ/ιoƒ;~O€Ιϊ`v,N™\DΩΏώυ'ό‘ Ϋq`ΓD σ~SE… 2"9šK“²άZρ₯˜³-ζΉA;ΎJ{υΚμΙψv1t;’ΩΎΎ­κ5ζ^Vρ*&χ²P£*\Ης²³§Ηκ‘θή`š<ψ<έu λ%gΉŠ.fAπΛπΕ› ΞΫ―¬Φ’v»η5ό@{vlΝ_άκWρΜ‹)#6T Ο{&ώžτΎW¦?ΖΏ—Νυ=yɐ\ΑΎ—Η“ŒHΊdΎ•›¨A›ϋP7„52d‘%hY+;Ρͺfρ #c5”KY―4―sιΚ\™;­Μ•πn‘0qiποJdEν UϋB‘w]ΖL Uν=iΏΘ έk›΄γ΅Oν3Μtβ9κΩrWԜD©‘αdR’3ΙΡ¬Ύ«‚’*Ÿb>9Ρ]Ό„­Ό„κWζρψ±TρٌΰΖ«Ίe[ύσ+ΰ|Π.£ ]˜μώΟOΏψω'ͺΆχ 8€#ώ/d’°›ζιI3Ϋ €ΑΤ6T$ϊ φCbjžŽOΆν™o„ηb›κΐω ΏΒHγ|φι>t+gV’η»Gρ°E,ΩεUΝκGt|‚΅ΥθqDΗαx zΊΜ‹Ψτ΅‹γœΑJ‘'ͺ¨=Ά€ω’λ.Κ·Ϊ•η‡·ψ–{œ‡Κaσ£΅gΘY+gνκ…­MΉ0ίύƒΧbxΑ±Ÿ_Ι=hB\~t”MΦ‡2ώ¬S‘R»EΜΐjqΦ}}ΰ«·š*TΌ¨#Wf«½Ϋ™~ΠΜ'3MΰςVΖΘI±p˜ΊξD[c2…ΧJœ™9UB͌ƒΗΒC±pQπΦƒ·9οk• 7•CFPB͎:οŸσγθΒμ2) 3γ‰KakL’Š 7»"՘ΓΜηD…ΛY†ΪŸSe―‘φs\Œυ NƒυΓΎύp;$ΆΝ™)MHY!z%ψδ-Ty …λ’Έšε:±gΆΡΆ"ΉΕ† qρ™?Dη΅03 UQJPΑ”"ΞΜNSžnM“[Σi/4¨‘ζ;‚#a”œ ’Ž uΌ˜ζαξfGΫω@ή°Έ²WQ9ν Θn?V‘§)μ€ϊžΊ‘=.―²Uρ\ι;ψϊ™«η­’ρwΜ₯ŒΌAΨ?ο5άέύά[1ΛΏΚFŠ,˜«–§W΄ΣKά©6Ή5 P;u¦_;‹-žn§œ­ωΈxΐΙ:’τjΕιmhηΞςjgpއ[»Yͺ¬™{©Ά~ΔυHJT‡$ζVpnΗe’σQœΩ%œ›[+oΧή`ÝY wwƒΥZδI L³Λ‰ε•‰μΈSi*`υb ‹φΒθ†Χ+^+Rxˆ³kxΙvœμVq·,ΘΡ£·έLίΩ~ςόΆαβv]`NΥ2‹rA9CέΧnDΡίπ›m\υηd{PΚη0ά”T˜7oΜ  IR5’@{g;g\λΉ8݁νοbЁ­ͺΖΚτωΓσ@d>lb?hZ«’€πτα)UΡ‰ΈΌΕ‘nξvͺΖξΪξάH!ŽχΧv".žψB΄R|fΎ’4v‡5ΚfΔ^υ˜χ{vΨ₯vΚ Δτ›C€ΞΎ©πx-!ζ3m™;[X('u&†jLΘ g`¦Jk—§Š—ˆ’"a 3•¦¨QΉ)…›»ΜRQ=nj›–bιJ)“έ Τ±–¦’PΧ₯J*UQΚΨ)Y]u’Φ{kμΚΚ―ŠJXCU΄{aH^)κΌ, vςÏμ°όQϋξkΊΉKιZbC³―kŸό ΗyAο°vUΫΙh"†Fπ›X)ΜƒζΞ Ε΄ά4&Μ§νUԚd9z±χ¨έ>—‰Ι’Ώ"ȍ;e`C O:EsUAθ Θ€•lBί+1[ƒΝ„{™³ qσ°vέ£;OεΨ^ۜΝ4nνŠϋŒΆ8pW½RκTδδ8ψΊ‡|Hd…‡π'Uξΰ]Ι‘°Υ.fε^ Ωͺ’w@ΎJeRτΖgζΣ*ϋšψόΠ²β`Žnψ‹φ˜σ΅ΌΝŸi«¦ΚeNj€ m€K“ι™Εΰq²΄‹tMΙ5λh‡dΓί4]t)ϋ'wΝ– :A“NΣ/JΛΨ’ζί΅ŠSh`£γj₯ΘΛEδδΦ»ξŸβΆ9EfΕvΉ/p¦iτΎ"]ΛχSϟ=ΊC+ζS3΄ΆƒθDκ3£σ=°Υ.Fίݜ[':Β…;œ}O¦zt$Μ‰U|™ΌJ#―‹θξ …<νeUOž¬l­›ΤŒ’°ρŒ’#φEQt`σ8eœe¦»`Σ&Y,ζ‘”GNoιΨ§˜ηΚMήήΖ¨Ξ‹EEh*£„μ΅E?τΜG³rΫΓc΄Sνfg’fΩˆlσq§›(ΠηΕvˆ2‘OήΜζΑ©ͺH€ΓαΜζσ‘Σž/?™$½’θό8²±rΉac·―χ θΞ…Κ·ΛόwŽ”Qᬌ\Ε#%{α!—υ‡«*Ν›™‚ͺ₯",ͺΦη5—UpY€Eϋiˆš3”‹6–γ1]ΕΕ|BšARββ }‘&₯kšQΤΒ9” M ͺrU[Yφφ[v|~κ2ΛSH½¬l\7%dιΛbηˆQΧΥ΅Ιw ;3Ϊ Zsμ§ΑύV>#+%ΰU*ύκ\ceMrpb’Syz›"†Ω+YΞH+D6Σh½e―΄ OΙ:Η?”u΅h!ΒΣΖ”PGJθ@Ά TC…¬ŽJ­vVq +Χή2%μ Em^φ΅zRςΡ]UιΘ6ΫΫνΐςλί`ΞJΔρ3'yΆŠNν"1–rπ˜ς" /γμ1=!ΒΚΚ–υHΕ£ω nGΊ6œΖpΎ/MJh?VG’ ς–)ς¦ d‰ΗΔƒj§Ήiςάμ€MpVPWτG\“ίM U2K•{ ²0―›X»*!hψ‘IœZυJͺr>¨€κώΡο ΧUζξm#ϊuSεeΒΤήsπήTσ@δ w…ž9]M―θr;†‚T­ϋ<7½ͺ€45r#C±ΡιPŒωEόΊάq@ν·_œκθ“_’šρ”33h|σ3^d>θ$ՐισˆΤ”²Ά…vUΛώT Ω j@œMg9[πΠS@i:Ls³z†yΝ«―τεSΧανΥκ%ΠHJΓbQ†š5έK.ϊβQ1™šΫA9,^1ρWdVLlΑ©™ž-{Εd__θήΗΊ΅Ξx$)hήj(|Χ<.)$εTE\“έόŒϋόZι΅n¨”δgΡ‰γΆτΟ”87ζΉ"πh\M­ώ8QΘߚκ ¬°τͺ΅ΐjh‚_•kΡaζ”$yδΛnv Κ―μ0sψu{Š pY¬Š’=ZnJηΰžρτfήυγˆΖ^ΕeP|ϊΉ©©›':,ΥΪ©σ₯œτϋm[ΰSͺχπρνŠϊτ˜M›xUMζ-­DÞ5ŸΑczλ:Η¨τ¬ &ΓΩ2ω`ν`§a^d·Π£ύp€qα‹Φ`ί()Ψ`TV%7l§ŒΆΘaΈμΰoΛJ/ΑnY­;!y°;^€vΩ°γ©έz8΅ύ¨ΔŽ/uΞΤTήͺ)¬©άjΓ8έb§χ-ψ»XŸ|ιzΰ<`₯xϊv'ςύ•ίΐ6·η˜˜€[}:2Ε»ΒΕέ½½σd±7Ϋρ9bΣ4¬β.W»Sά[Œ΅Β•‚?Λͺ ›©ΈfΤTΜ=‡m_2—ͺŽwU§\ΟίΌΖ±ΘΊ.ΚΓ—’VΫβΎiι~Ύ2ρ"r‘Ω^™P/Ο4=6π%Ογ5™Nkψ<ΘΙΛ,₯Ϊ/d"»Όv'džΒΌρ ½Ÿ§-Ύ[Gtr '­3φΆΏΚC•ύϋLr2­uφΒβg’l)?J|«γάμƒy<#σΘΜΆ‹¦Σ… ₯Ψ·’Ypβ͚#Uνi"Jƒ/pτΨ΄θυ“ޏou―S₯"Ά™θΦΟ>Σ}O―œ”£{½„Ÿ~ν^_Δ>δo+σΈ*βΙϊνέεcwL·{ό²o&EyΧxΌΦΠpZ9ο=νΩΫΗβ¨oάίμWn>π%δέ &aϋ"jσ©‡mσ:efΞ@ό­ΝΞέ,Ω4Γ·ΉGgیšu :X=(zb₯³’™u ™?Už5£e‡Υ‘,’@Ίj$―Aβ']Ο N'4'Φ7‘οΊ,YQ•₯&B½mκη^€EiN d,A6J={Σ=Ο¦(pͺZ’ΔHUδ'ε}―ΨB―δ []yΙ9c|eΈ˜ρž·ύμρ‚Cρ Ι0‹»7p7b;rh‰>μ›I·„»²²Xv£6od³]vφΖ’hιJv;ΘBZ1`²±@ֲ켁¬ [΅G4{C‹—B†/ινθLG‹y₯5cQΓI9}‹ϋ,―@вΎI(νέ*is"Ηεν₯ˆ|^(I/xΞΗΩ"?αη=ΕΗy^^UϊωZη²KΈετ%EΪσB Σ©GSιοe²”ˆ…λΧƎƒΊKΝvΦγW4tŽ; G΄™¬ F›ή§ΦX)Šθ2^‡²Kψˆ~3‡Ηœή—¦>ΆςΈ/Nδ›ϊΤbχΎ΅¦\|’ ‡£œ€λCΞUώ½ ©pEG1©Έ|°δ@λ¬/ŠNξzΣe½HIΒQ•NυδjN‡iΫ©±vψμW"ΈΩΎα§ή'ίyT 9 A5τ–Ψ₯‘ίjΨŽbVމΎœlc.κgŸh˜ έΏAv4μH)Όm1Ϊ_fΐη΄γb¦63Ş^(=$%^πXνžda%Y ϋαΦΓ—Œδ¦Gpyσ%γQP‰Gΰš§cΈ0UΐχTŒgXΓfhnιŒ‹sΉ+=μ^`Έέ―#ΦΞKΉžEŒοσ;xOx\μKUφ==γίμNšϋ˜δι‡/Λν2nί2ίAd’Βnζ˜Δ{ /Μ¦s>ΊGWΓ레υ[yε΄…L~AΡ™@ψΈΓ–ήŠ -(€ŽτχRxˆϊ{–(YZ»vE )νn+ήPRξΥΰ6sαΨΌTo#χ”ϊQψ@j}5ΧΈr­]ζ7† α š¨8eŠΡ£@RžmW?‹a‘.Hτvρα Ο\Ž|y— LΘꏋς~c#Τ½ΐΛοE—zhΚPΓπ?šα ?ZFεyGC6;™Τ1›£y ™™3Φ6•Έ±ƒ­ΖEm”g&;B†[ίΈΟ:.«{ œ@’»,)΅F¦‚”6)΅tΈ^^=‹¦YΌ ψˆ{~–;ώ1½¨*J;γΒЍksq&χϋ_Γ~ΪΠ½•F½ςHD+fYLθ‰GΙ¨³Ο­σΫ?δ_ͺ~^nT^99»Νœ;¨u8%ε“χ4¬NH½ChΩω―«E]|±H_쑇$Ι ·εt½.άNα{–y•}hΝ•X”Λ ^K‰IΝ)±¨ŠκNkΊΏυ|[Ω€iΚ’}HΚ%5©ψ”cΚMRQ%u…Eζό¦Sώ!TΥ1FtΞ22Tό«Λ^sσb ςσωX κV©Ν-;·σφ{ό@GM]Hΐ^ΘνΞ·³₯šwQ2I,LI#:ž©ŽψοΡEό’κžΙγˆ>_>ϊ±Œ‚½ϋc³έMβV,Aˆ7%’kΚgETt:e“φνδŸ'“ψ1΄ˆr~AQZD+•&‘"3ο?ϋ Ρ :…·P`<žΡbΖΉKκ΄0Χdo ² ±7́Vοj¬Mdl°˜Νι:κ λς/‹±¦.Qœ [‘y½Ψ8s)φε{ηκ!pY!π‘ε²c9ϊ ΅££cμΘζf ©­4οΚ'퍒υ€:œo ±ΠΫSwkΙ^άψ.N v1½{7¨h±[<ά·t“XύtφώН€ΫΟΧ_ΈρnwΫ+±rπFœάxΕΐέλθά6ΤΟ0o=)Λw6Ώ€]Γ°Sξ@c<Τ΄€ο9<‡Λ£ΙΪγ>,«φ¨έ56罋°<@Ž)‰ςW%Ψ«RS±Uοξt’οΎFγ{•Lω>(Ψ"ΰΰ}τ=IΓ1σP"ˆ«Η›ψβΌ§\GWpS.3[‡d«“UΔ(Ίς`·’EB₯¨Ϋ<’T «»ΓŸ|*-*‰ΫΨ†mŽΚgtŸ9xΈ‹4χTλΈΩMΫͺFr© > ξΩ?eεg ’sΔ‚&ŸΩΜY€‘O]](:#«ͺ*g¬FW6lKϋΜs»O΄zOΡ…Νΐ("ЈΓ'ZΦ'ΖΎ‡,ZγύΊΜΌ Ϋ;ΪOK3»Ξ7€‘fΰ²Ρε=Ϋ«‚Θ₯ΈΏϋΒw%΄ϋε=žάcםŽΟυ8xηEίΝU‘tΆQίΚ%―Κ ŒŸό“αΦ*Œ»pƒα σˆλPύη H©SX]!‘’+ΆρA_KΕΖoΐzBQ¦–―–Nα4ΪΧ‡;ν`ιΩ53V₯Oύ=\qπΠLtᡦΕήXύΔβΟ7ΐŽ'¨SsΆιšŠ.ΘVΌλxf•KZ–Ϊω©@Δ표”wt6-€œͺ’\.έ5EΜ§σVEν9ͺcžFΥPμ’/"90>€χƒIKΒ=9AxueŸEΔFΏ·ύΖ5;φίύρΛο?Η0 h­όαΫwΏύ’~ΝW_|ωυοΰe—·ΆsVιΕ0΅eğλψU)!u;£ ά.u$Ήάc²³"ς2a±D…Y₯™WӚoPovZb΄sΤι‹˜Χσάμfb–·bχ`eh² ”}ΣθΕ“dΕΨɟH…x’•fV*ΘΌlΌ,ΌΔΆ@–Υ…VžΊ_>ΪyYy™ψœΑG+/ί’ψ|,Λκ!υ%°ΎΣ_kΏK΅έΓkά³υΕp,T™ε Θ^o[U€Ή΅Šχέ›xΝ«Ύ{αΓN¬ηΦΆ{αά§T$.RUob²q§P½IVΏ¦MΛ%ƒέΙ΄²Τ“’NDb\7Πε£ΪаΥΝt7 5pΩ"λJ§5 ¨φΙ΄΅]O>6mΉλο2|Σ8W‹/ϊx[όTq¬.ΘΚt;$It£&Œ’d„|‰ΞcGa¨y…H…Έ$zS-N΅πP›ƒx½¨8%U³ͺζ΄T«oξ”σ™H’ΑKž(«.D‘ͺ”Rθζ Ul¨=g;ήΉ=Δ¦ϊΔ²ο5 Ϋ>4Ή`,4o@OζeJ}‘»< 6—₯s>«ά)Ϊ|%/ͺ/γNKήρ}η Ζ——v£ό|q T«’WΌΉ-Iη7έ͘ΏκΏά՚ώyi‚;qC>‚Ύ[8πο$ZEα«Ζ|υυmΥ’&ŽTχ”‘"ŸY°rε`7f―.Λ27W/v[?.:]ΝXΒ±ΊΊρ·#:7#y4|‹8ή3Υ³Ύ³2ΰYΉW³!S$Ϊρxc€μ™n»iHΙ₯ϊ9AΨ; θr³€$©<©C‘ϊ˜ƒΕGζ3ΫβS-ι&ΧO:₯UΫΚ~VθΠ9Ϋe|ϊ‰k=λ(&ˆWό,\¦“S§‰K†aV₯΄ΊςΌφE‡ΟM°/~« NSθ Nlε°Ο€1o.ˆ\Κ²οaι*²χI΅ƒπΑhύθ4˜'ˆη)άtΆσρr 5_+ŽW«k:|ŸνΔΗ±sͺξ€₯{,l¨U[©³—ΒkΚ;·±°»δΞ¬b˜εΔΗ‹αώ±tδύ‹©ξΦ˜zγΊΔ{ε:¦;³Κ‘(©Šž― Ÿy£Vλ¦Δ*¬‹2mέ cpΓθΩ2EcχμΨѐݗO-lzω)({f±§°κu«ΛͺeVL{BŽqeΗ” ήk0Σ…Š¬2¨ΘήΫ#^aΩ%TνJΝbT%e_ku€׌ΐۏδΦ¬‡¬BUΧϊt,gΤ&*λ#/° Αc΅ vמ„JUIˆΩ‹²KM6›ΗŸ˜wMΓ‚Θ4G4Η;φμP± \]ζΘ"§Η!.pΚ²Ϊlj/Ω› ΧnI+Ašχ ԝOμŒΕ΄¬ι“W₯ΖβAΏJGν8HΚUcDμ.<΄2’,₯»Ν: ξ >›u*ώΖ8ΡθΣ9xO=ϋΌΓ]ψδIC%Φ—ΆU}Χ]lOΔ8?Ί*uΩ5ΆN†KΓ»Ύ Πt‹%±•Τw_6ΫΙ³‰“„In2•D± °:nqαœ]·­εΩG†4ηΙ΅Δ€ΑhΔΆΌΫt¨ά–Ο²lyέ‰fυθω«»AO‡ΈqΩΊς1σoϞίΑž?a”­eA*ά ώJƒξήCηΚb7σQΊ5ˆk;‘NMXVόt²«$Ξτ ²ΰγγΩ%{ΞKήdϋ–)χY‘g+Ύ71΄­Gχ…έv($ν2ββψyσpήΉ ιαϊ ΡζYM½Ί”_’¬α˜έ»~„σιIX^«°…ι‚Ps¨0$Ψέ’_Φ-p$ΘFχ˜@wGUžrf§§ϋ5ΝΞ²nO˜Φ( ―.3»U]bH©oeŽ«ξ€μYlΦN§ϋδΒ±·ΔΖΎΤOu= .±ψ9―πΓ{εΌ"s␑΅γ’–ί;‰π˜ΠžGΜ¬*EΑτ3v’Ν\G‘HEΓV’άς‰6Ι<–Τq-Ψΐ ŠBxC`bζςpΜΥ†”l©;–Ηt©CPρ˜Cά,ο#ϊ–ηνVQβ΄νΰNΌ»σΓG”x;/’rρTqΞΔ­N'β–7ώτ{2Ϋ»¦[9±§6"e©6f·sΊ‘ΟsχcΗ²Saζ-{¨ό–oα±ϋή{ή}«ϋ|Ιpμβ/CΙσ=Q3[!PΔA9IDΦξι-θαψNΧV,ωu`"n:=B₯θΤiκ4«%Ξw BlYc ΙΥ%kǍχ·U)Ε(ŸΫKβ]‚ζKΰ]u›EŠιm7ΫΥƒ«Ÿ,₯7'‚*%*Μ‹$Z]譈r—](NΫ΅%p½ΰΈ¦|­Y$Κλ4Υs,VgωX$›Yβ)i˜κ’™  H·ν”‹Μκb‰+υωb&DnDh¨œC“&%HšΞόλα­“ΆΣ3Ξ ώ’Λ Μ »Ώ³Lm‘l2©U%iΖ. sζ΅₯ež„y7Ι{ŸSqۚ.™γvx’Ληzž²8μ³ΙyBι]Δ—τχ.ίΌAηήΦΎχχξέNg‚kn›‚αގQΞ”—X©»:ꒈŒΆ·ϊξίε#š|ί£·ΕΪ^ΩP¬Ό—h¨~‹'Pί‹eμ™vrΠβژ‘ΰ2 /₯S8ϋΆ«ˆ>jW‡†„Q» ₯š±RGΖT=‡tjζLKPψwp΄εE»ΗJ‹‚ΙΞO˜ΝΎ.]vbhΌ@nΩ' ©s£v=θmό[΄Ί$ x‘»a•XρtπΖO‹%WΜQˆRΌ1Š1wτΉ0—TΕΛqρ˜αΗ9zύ£"iGss4“¨^ζ_œt²^ΤUΨA €o {*ͺθ›Ν-)‰Nf{S%“  MΨη#ΨWŽ<Π4Ι†xυ»κ A­ύ)KŠ«Ι§ϊ΅ηCh΅,€Η•,s•γ Χ8¬©~"«¬ωΞ/{ŠžΜ^}λ Ɂ.Ž ½q8LdH1gnQ―3O^+=o|ο½M'šςŒͺΨ­ς₯ΚNJ–»ΦρeL‡3C$ΞΈΊο|•Χ)ϋ# kƒ9Υ3Τ©:“’εRA£kΛ`CΨ½¦g5'υ«‘lA†ξ EW ;0U•LŠ5V@SS±4MAΧ GΉžTκ9ψΔQ3;€u­&©(O‡ΑμωΆ`ΥΚdY―ήlKNθ&πn{κAΈΕœ GYΧή›₯jΓμ½V+V½&¨gͺFWšΪ7՝]‡T˜y9Σm5κχkΫ'‹‹ύ7Ή$Ξ>«κΘδ/CΏdF3#†wΏέ\2=ρ›‡ΘR“'ΥϋdA²MΞL« ²f¦Ί=]‘R΄Γ52R8γLΤΙ]ΞG“Υ’¨σ!>m ρ‰ ‹ωUΗ‘­GΩ·μ -—ΣHGΨ»rΫnO_{œδ %`§Q«Ϊϋ0qo΄Έε±|¦’^χEyN»B!FŠΜ x―Κ—™ ^αŠ2ŠΛδG4Tm|±1Ό7Xjj™’O|ͺΨ)φR~ϋVιDF₯΄†0Š&~΅Q)FHή š$ω(Q­ŠΗYsyˆš…¦¦–¦n—¨B―#ΡT4χg=ίך]σ샑Η;SΡΧ΅‹λιΝ°*9™σΗέΤ6΅η5ΑQ:³£Τ HΗ%½ςΌ€΄JMžTΚNeΰ¦Ρ?ͺ<₯&ρ5}p€Μn07τ*Y>t €ΫΟ(½’‘Ίη„)¬5HΥϋ9|››θΚ6ΛiΎ €Π³Ι"wesΡ{Χώ«­χ€‘0™ ΚϊΡωbς—}ό€C]γ+έδΆψΨqι&ΧƒΛ|ΦΦΐ7Ž˜΅uFͺ¦όb0mžΓαNάΉζqqκV ۜMκ¦Γ:HΗ@T;ΒI‰¦ΎΣ&ξ•ΥνΙZŠπN τδY^±@μ†#\£¬$†δ#CΪΠʷӘR΅,sώ1…ΰ»ΉΗ΅žAΣ06΄ςΛ ΪpiJmώœ ₯ ·¨LA7¦œΡ¬ά|ž ήρ  πp‰G5κ8¨IfLv4˜‡ͺΥΥDΑξ­±ζ‹yY|ά»ό °E€ΰδ΅*γΧλϊΕυΠΊž“1&tu¨±o™ͺ.ΊEˆCE&Φ°8IζT"ž©’Á7V“$ŽγCRό™>G@ρFrΎΆOΞήΛΈjΓ}eœ-\Ά[B'ΉύGΌ`άιsβŠΓΉΘkœΈYI΄JB¦³He`¨Ά„tSSΖ [͞rΓ‹Ά_ΦδR΄ΒΫv€2—Μ©Lv Γfν|`_Ο§ŒG8d”ΎAu‘‡tT—+꺞@ή!ψZwςžFΎ₯hο δxΖ­€LŠ€Β5™Ό$Tέ5φ—†γkΠά‹Caϋž½w‚RŸ(Ά―L.Β½r―l¨`ΥWRΝ1•D@»’Έ o’κ=8όZαο“tθαq#hJ}JM˜m΄JΡC)N Z‹Ζ\†θ‹Φ'Tο‹XΞRd…›Tθn;½c ԍrΑ’έΡ#$–žς‹MXΤΞ₯Ρ2cΊ•³œzΊΞ-€ \ύi–τ%PVdš}Λ €Β ΰIαt#»t6C‡Oa'b“ImTW1"E;Όέ RUδ •&|—©<£4ʏυ%+™žθMˆe ,ˆΙ±H< β)“°ŠOνΒaδΓγ˜/έΊ~«²˜•ύ…ƒ]7l,–”ΐ±ζ}²ξUν™{(m4‹ΎA0ž5  ˆεΒ},9r5#ΎΦ΄pJ_Ϋδ:σžxj‰¨|Σš΅±΅SλπsOΐ‘BΣ'₯y$Z$;>±ύ‘ž3«ΛΦώrώ#Ω½V·hI–―›Xe!”RJJ“ο*τy³QŸ…z=xŒξΛΦΫ²…S•Ί|[i6–WΰγJGd Σαγ>ώy'ΑHϋ*HnηeκˆGψƒΌ΅,$oλ<†Ϊ1ˆ@Chή©xΞΛzς»ιλbΠQΐΥ3άޟ}διςΓc€=Υ–Ρ}9Fš•žx4₯©ΧΨ6Ί4T‰HΆ9E1”€{]r[έ\cύ:Ϋφ,@n [t$7v΄­rŽK©?EκΈε­ο‘ΣV©§‰&’ψˆ‡@R’)zZ«_‘ˆi±€]Ϋ!$΅-Œ%QΕvl₯žχŒ΅Ό)£ŽΝ(ofzg.KΕY‚ω˜(ς±Q–VFXŒmcrΌG,ςΦ316ίΫηDŸ€ei7KwΐIΉΡEρmϊήλιθƒπ/£ 9\YΡ¦Δ§σ,μΜς;’£ιΰͺΓ‰t°$%l‰Θ…>·,ͺΆEYΦ€ϋΙ·₯ε”…]‰ͺ VϊWJP;χΕ“©΅œ–5Εκ΅Sﳁμ7T/Μ&o«m©Ϋ ·ΎΚgYΦ₯n7Ό…AY%)8'ϊ›ϋωΐW.˜™tΞͺnV₯¬Υ“Ζψ™ž -Νc—Ε•Θΐ±cNmνƒfœ†«ι_½›E·ΝŒΖ†*ά@°m\ξ%,ΡΕΝ‹ξ4ͺ/Γy½]ιN^V GΚχ1‘ ΰ³½GAΉΑgφ”/—²ΪςΚ{ZϋχPσήIΌg’φκεF΅78ζΣ|_†ί­ξ;υΦΌ―ΏΓέΓ·ωbX·׎IUcԐV.¬ϋ°§IΕΑœο lΌRaά °mA' ©*‘˜4cj0]jΜχζ[ ‚Berε:‘+ ω­Q“΄:*~ΌnΕQτΈύ―+מ.ΞAυΣ¬A†^e₯*”τ‹Z€€…ω“*Ώˆ"^5Ψρr•{«ΚZPl)0j²3λδΕ8εh¨DΌkai(―4―»Ύ˜Ÿk?£š=BαφŽ·Iφ²Ά<~¨YEξ-ξ{,¨j8ΆPΑΆb…Fp…ž‘YνTC^GŽ€~ <šϋNΖν§*ƒ‡vŸ$ήƒ†uši‰›ηCς.- €prτώ·[wYO!DŸ’Λ€ν)BΗVp%±€―4‘’Aq!V,ΊswχfΑbƒΡΨ(‰ž’#Ό‘6ŽpχnοΝ/Έ+”²&‹μ%άέι}%\ΎLDˆηf²;Ζξkƒβ'Κn[š— dΙKΟξgη–PHΉΑ#›>ΤΒ6(pΧVj3Β%ω]κ΅GtΧ$ιZ|πŒ)ˆ­€ItΑΩ%\ =E-cμΊ,gn E˜αj›2ΊΠΗT-w¨¦k>p<ιΖfεΫbWνE mM?ΞΑsfk6 VwΔ¨σί5›€\S­<…MΚ¬ZjΌ9al6τ ΠζσR³ζ‘œ jΓΐ’υ•–γhίΝ["μgΆ¦ώGθτ₯α~"BYα· %r LΕ"n€qH&6³φ$œQΌe / t jΡaudΣCβa-—σ‹ρ_»υ=¦‡ΰΝΈ>V‘DmΟήίφΤΡ‡»nwε!"žοP•ψٌ¬Μχ6^“.½ηΎl©#"¨Ή˜G>?εBΧguw^±²Ίσ|©ΛŠϊα^]΅‘B†»m2ΐω…Μ^€Z+ΪΔ̝Cγ@EhN­κͺΘ†΅' jΔζWg>Wv(ΐ5όΙΜL™Msν¦γPdFΞα*VγΠe5ŸzΞΙ6ΐirΤŝk}©·vT]p6yήV³ΰ˜wf³­WYΥl›ΤΓ%‘ 9huh0_-Χv)¨!9ΝΨ™a‘ΉΛCLKΝ:f~Ν1NΖήOψ&ŸκlM0I=uΒυYφ7%Lθύ€M8²„K0³ W½j`pYτ"ͺΓfΏcͺ©―C‹―]ρΕJ’΄·Μn2‹σ”ΪLχ΄.9o λ Α»qΟ!^«C ^yΘκŠϋΟΰΡΫζϋ=ε΄5Θ]”svzΪπδ’ŠNΌqŸΌ†ίI'ΩΩ/W?VlwσέΏω¨)Ρχž€­'~ο πY#|7iPNUψ΅τψ΅Ixrau“[άL”RΰI,UΉΊΜΘΩϋψZh#"ΈΡΗ‰h³ΨΡω«CΧ]γjӐ@{Ž²ΒΉJ^:woλσΧgEiXΔᢘ#ΗέκώQEAvλv©>lΊϋ΅SΛЌχΥpYΜΈži_Πρ›Οœ7Σ*[>f―ξTχc΄fŒΝÙ葬Έ€γœ5άφ°ΑM6XmŠκ’$ώsŽΦτΜ‚Χuκ"+޳|iTŒQV6›ŠόυK>β§9œΡ{&ά“NN /νk¬¬Α66εH,TanŽΑqΓΌj/šλ±<υE`jξŒ#ɉΝμbW“ln{₯[3ΣΚO-':¬ _cΈ]D<μρΌ‚΄8σπQ·LU?¦ƒτ• μ&†4­ϊυξ!εξLοχΛ1έΙ›}―ΐξjΠͺŒ«‘;ΰ#ΕΗkΤϋρ%Ψ»-ίωήΗΓgͺ9\bδšύJΐ•2λβΩ:/‚ωγa#Έ­θ—ΡZuΎžhl¬UόΗρΉΞŒe8υkΓ‰3ςš£ΠU…-Ρ―5Γ λNŒ‰ΰυ€ ηW'+^Φ1rΕκυιΐόƒͺ!φ("3* ξcUα(‹[1³†•Ν*+›εJS.–νʐ!»A–-šš¨I’’>Ταe^σJ’ΛdΜkΌHŒƒTx²εnΗύ‘`!μz"ωόεN’¦‹Q™ζ’;όifώPΌ:Ηuškΰ ΡnΤdΫ]ψΗr΄D“ΑΞΖ ιμŸwΞ’†‘γ皀Ή˜Ε£ά2ΑέϋŒ°"Ωrτι·Ξ'Ϋ©nΐομΖ;μ„ρΨ`άξ*S·σ}PίΕGέ$bφF['žGΓuUq9½["λtίΔ>E顐ψ56Ε^œν°}Y>gkΜσX³°d…ΈH“ Qq\ڑ͐ψχ`Χ΅sΙρC‘—E!œ}9I=Ά/©σΐ³@–’@œήdF@T g —δΧCΜδœο/Q 0—E“¨λλ&]JDZu‚€†9τ>3O΅ά;’(G !"ΈKίΐ-[.XU ΟkΰΞ·μ΄$Ά_·χβ:#ωEͺzΤ άυw};Τ<‰C£C\!T»ENP”bΤ9οG«pTΥΑ|–§c‚.ŸΌ~D ,zΐΜ}ς·ΓΜ”ΑQlήΨ2r˜D.-u!Ÿ‘–:-Kέ’C<Xu‹Gά†ε†#֎ΞAρH†Νcg˜„9Nˆχβ{Βλ΅€πήΧ7ˆK¦·λήιΓχίηUΘρaήwΏύΔyΪpΎ§Ύ^#7ξχμΙΉ|1ί·fz~{±΅TώYAiά(ڟ§kΚc ΐ_8Ααnԟ({Njy.±HIσ°εšj6άMžM’ΰ0Ω+NEΆcΈ~ΕΝθIŠοvέ΅ΫZίαΎΞΉmo#Jjx^ΜZ[2iιEέGΞI³-Έηά•tR~Ι»κԝs±ε–½] Υ$MWo{9;«P‘9ƒΙη`Ο—•©ι\Œv³ζ©$yjŽ<ͺΖͺ DΕ+˜Ίψ¨‡Ϋ.!EW5ά3Ϊ搒v©ΑDV묛αΤj³NΰnxΚΞ~ΆΕέ‘β$P»]9RNyχΆz§bmκ)ŽτΠ=3–|οBΣu‰Q{΄½_S½zχ’ΧͺΞ¨ϋ),=:dώn|Χw™ͺ‡ζ΅ο‰cοέΉ[Š{―;+φΌr–¬yƒͺ1*»υ5‰’¨’ΘΡ.ewRΥVϋ¨)7£ώͺΪ₯ςC$vΔ:6ί_=1’hrΎ]]3²ςeg• ΚFˆκ!—άωC4ά!y€HάσdΤ…΅ηv΄%’Ήκa+ςžžηJJ‘”ζ2VΓ“»ώϊ²"ιθy°}e3ΒVΎ­N΅ζ75ρΪΆ[i΄G gžΜΒueΉ)^OίF)1Y‰CRRwjxσzν ΅yΒ&)©,w3ΥφΊ¦C―­ψpψdχ,φΕω°UlΟΎ%BξOŠV׏•mY#G±ΞJk)'—Ή€ZΈΊμ1DJ;L΄ƒΦΠ? 5d‘Σ…ώqΤ¨Ϊ’…ΝKQ2½…= Pυθͺ?‡³Ό ‡8/€z@ξQy:d­’wόΡίzτζOΩwΕΊΧڈΆ{.&[ΑυVXVν§Ÿa΅jνŒJ1–kaω 7‡-ώMθϋεΎΕμΆ¨*κ^υ ιŠ.¨.7sׁφπ‚έΣ<› TθWωGͺ¨bg•­»Hξύi5SΤμŒϊνƒ 8œ/2{τœ•ΨTAϊς=hJ‚\ξΌΗ ~{Ÿίr]΄κޏ―>V=U‘ΆYkEζml vYR0ΛβZΝΠΦ€ΔΣΛωvžH‰ΒnU θ΄Ζ¬ΫιΗΆ)«Χ\7¬\ U#»56— ?`p½œOjV]ΞsŒςχζnw^–q ΨΆΎuͺ³Ή Σ'ΠY„>+οQH%υ%Ά™lGͺ³mο(mΰΐdΌ&Θ­ΈΧν’ο0Ψ«{¨¬*sxΫ}Ξ>gή—έχβ^‘v·»sέEΙ§Ε~¨<Ρ=v·ϋn~o pηLmuζ½{θY{ ΝRΎ¨ΤaΦχq₯?Ύί8΄ΕΣμAναWΫ _Ν=ΌΑ½φΨ†έπVΤ–›zνiWšZƒJU™-!’L'Œ:ΕΙΤ±‡Mt· ©Θ¦Κoλ>‘’gΈ˜ £ΜΕψPZ‚,Ύ„$d―+šgΊ0ΏΣ7ΉνΖ³―-"YN¦HΛΞι2’C]χΕ;.„ΞÝVL¬γ74Ιex–ϋ€₯*"mZΓ%—νΝ—6AL*n[ξΟθ2<)˜πgŸdŽnΐμR%•A…+-)Œs¨nσς’RΗX½t4ύ%ιεΫ^’qšζƒ44Sα/ƒ::υ3οT6h’γ8ΣkΊ&Ζ (;n|ΒR_ƒς¨==œΨη#}φ;΄Cϊfέ—¬ZΣΛh‹IΘΗVΒΝ”±Ε“κ΄nφšΟ^eή.ϋVo€;E-AΙψ6χΆΈa0}βΕF|– Σi»Δέέ«Ώ_Ζ §[ l„™άvn%*Ύ¨5ŠΑλΑ5^ 7Ι[ώ ώΙQhπ&΄'orh("χg .yγ§'iΰVqη(‰²%Eς†AC·MΒ£‰ƒ9\Πbt gbΥ:oAχ‘h›― σβmξ˜J^ΫYΥu Μ‚Ο<Σ‘‚„ΗΈ4n¦³²¦«4|αP¬R—}ΡQvTΦ’ή_Νό=ΊΤ0}“'˜φyIt!dΗFΖ3‡Β.w9φ¬΄Px QFƒG–ϊZgAjΎΝ•Xc[U0+‘z¬`ξ yE5­KΟΘ<„ω1‘9{Άυέζ–@§i2 ή ‡·!Ε³ i‘›z“VΧίdt·ΆόŽ;ΗΡ†„ΐ8©)/¬lZ‚ &jζ`晏ς|’ϊXΟ“5Άͺ–»Η¬ΫΖ‘©΅ΛB2Όm.Ύπ F_£q©FΌΕΩu#o —j.'΅Sεˆ%YΆ―±²ιΔ@σΚφ&Mkυ,:•³•)8§ζ#WΆha₯ƒ7Πώx­i(#FΑ76汇K:Zp₯ζ‘4XΌi’Ϋe+kΤΫZμ₯oƒQ‚y’λ=:ŠS'U 9ϊL΄βZ’lέIW€’Ζ?5Τ1 ΑΪaν…:™©ψ`ŒΩΫqWθŠ‡νΛζžΩbτ(oΗ[NθEν^AΧȍs3Z‡\έτω‹rq\σάΝ―e’?Ϋ·,Γ£U[΄Ή$Φ]Ά¨ Ες1ΝfΩY©ΆL²f—H;θ\:λm鬏΄Μδ<£U7u§7{6Δ‡£aaμπW”C»Ήώ/D˜©Π«ή‹«ϋοcjHΛρΥξ?I$l{kΓ}‘N#ΈξTΰΝ½}΅ΟLγ£ϊδފ<4Συ\VC a0ΚkόŠ—b' “Γ|0‡ž—%d3œφa+z”ΫΊe?ώp’υ|›&Š{Β¦η8³ΫŒY%([‚V M* žzS6W«qλ8\˜Ζυ.ψΜζz2©εKiE’•ΎF-™£—PK―o ’<‘ΉvD€²5Ό:\"”ΤNd;QHc€ZΞ”μ„_e f/D_lIVœςΡA™¬μ%:jΌ±,ω(p ©μζπ¬νξŒIwQW»­³™Η‘B ή(4v–½‰χt`Η¦&%.ܚΗ{»ε½΄§Ά’ξαυΉCSΤf±5σY-ž€V rι%z_{ό‡9Ξ{ŒΌEΠ;*Λ, £ͺ/D₯T¬D―OρόρV²mjB€,γ<Υ’` Ι)R%ύΪ€£Š¬ΜTnkή‡λ]D£,MW–Qύ·)3‘I΄uQ§z_x­©Χ… k|¨ύ0yYΣ 1¬°=60Ž5i"Έ&¬D蝸όB\5¨;Ύˆj!ΪBruUΩΤJ Σ΅ βϊ‹&\{Νϊ-F£V4―ΘΫ€W|―οC½§p!Ϋ%ΖΌ±•mwCfi @FRr²ς@ε«%Ρ€ δŽψΐάΔj1‚’¬)#{w<ͺ?N“:4db~―eωξ?UŒ©oεczελ‡ς)Ηx˜D8«f˜šν]ˆ–-ά©ί£*o₯Xρμ.6t–·rΗQ€^σQΊύ7”ηΚRΔMΙ~‡”Q³\†‚8―UŽ ξΓ3:Η~ΧhK:Ζ ¨Y\O)Ε*y %‰dυΓ*]©žr(+ζCΐb(‡+γƒ*g̍Γ#Π9]¦ž@Ϋ‡άΥ³`Ήg@5 ΆmΔη^Η-σx›Sχ6Όo{6h9ΕTΨ•!ˆ^Φΐ”%SdW™cgο\ ΆœIˆν6Ό©=έΗd= £ ΰž$H|ν!ώ Wyνͺ>h83fKx:RΥ…^”jv³£¬ηΝ*ΤLςed‰·…‰@²PΏeškΘϋK$ImVT=O”Q•φœI™i/ζ4]U_“μ¦βΆ€ύ°FοXLB’j€RΚ΅\mΣg™‘X  Ž#»τ–eΧΓ°΅_|gΪ5βŒd;*neΎ9Ί ΄€βŠω ΙM•; gΠ@CuBƒfΟθ΅p"Ξυ^ολ}'Ί˜­w% wuέΝ«‹ηͺ/Ϋ*UnΞ—E?ΆΔΔ₯μžΞΘΫwR ωBΉW—Ž₯―ͺύͺ‘“ΈΛΛﱌΜZϋH5Β‰#Ζ₯@,Aγo2ŽήjbNΖ£SίξYΆ ψκ­EΙ}τvjυ7Υήͺβ’‘ιΙXύυ_fυ«©Β8^VZ«ŸVEΒΧEޓ"Ξsυ+ιωΤͺδtx·cυƒMÜ’lLςc©’+VόZύv @A7 UγžμΛΪϊv|0iκ 7ub£€;υ°BDG€2R-’LΪo3zŸΆ’’šTŽ€œXςΪ0?C² ”$ΪΥs2κ\%(³.Ώ―{£ηpcΣ4a­x ΌόζEϊ1Ν<²Χn”˜’τИ³YIzζΒ­žνΌdrnΝ_—™gJ7ͺ ’}>Ψ…ŠΔ¬Ρ Ξπ² §O*rήΊsWφF―]Šp#’Ίδ₯WΖ·tΏ(€σd« ―ˆ“κβΛΡώMTΡ‘I–dΚιΰCl“’'ΰYθ ;«Γ½2•Ϋά₯€\aςΜDR[εΘ"¬˜—&{U²μ˜ω4Ύ$CΊ-Mζχ½šξŸ7€Δiχ^μP+Ύ 6Ο“Λ—ήΕ©­slϋ‡ο1ά8œv˜DW;ΩλοχdΘk™[${W5Ίτ?* >)9-ŸuωS~,l}˜_ζ¦ΏΖW{­3σΞAν·Λν—Œ3γ$οZ{"«ϋwu-Χ6”dνψβŸ“€ ‚!šΜ›’δ‘DjiMΟΐέɐ΄˜UkY­ΕG[θ₯ΐ ΌE™…$2τw-~νΟWCηuιGŽDΛΊfh\ƒ;˜―Κk(žhwς™ζΆ?ͺέ7•1ΓΪ%ֈŸ9Ό³T‰Νv $±R»ΟkΣφP•BOΥ‰rt―JοΪ%bˆ@Ÿω$±ό ©”!3̎`Κφu„œΉKΞ›Ά-»ΔgŸςˆΤΔ’j­»m“ΈμŽVΞoΕΆ‚Ζ`ŠRCœΣ+8sS’Στ˜½™Ν).mEhζ›sW Ε„š{:™sΗ d²ΙΩ€]»¬Š;ννθφπ θΓΫ/O>@pW=n’G§θο*“x‹Xb£HzΦά¦¬Ό8fίχ‡έΰn\ΥΛ.ρZρdΏG]›[ ‰Š*ή‡*Oλ£]ͺ•k°ΤςvI€;ΰ_ω;ΤξT#ΉJΪ&•λ•dDΦ€u5HVΗ)ηS@NΛΎ†mwζG›ΣQ¬’$7]P~²FZ™ό,π%‰ ˜]₯/n°ί%Eύ](Ž`ΰ‘+P¦\‰Έ)Ψ΅ή―&Jž]—6‡ζ«S)IΠ̘ 6ΝΑ¨=“Žm;@‘Ο`NΑι"ŒξCβ 9$eυ$m₯dVΚ<ρU}™½zK‰Λς‰qPΥ6Άΐ―­Rs…‚λ[œς’υΈφΑ~UΛ!ΗΡ5’ΨΈi€TF2ψω|wΑ ̐›ˆš?β’jF‹vΒ Σέ¬¨™3–ό³z0 ߜ#oaKBΝ³>fGu©γζΣ€‘LζO JυΦ― ° ³CΫՏdϊ—p<Τ —? |tς²π9γΕ£Ωϋ·η& ¬η>Zω φϊmš>OΠyWς{€tCKΪΰΌλ†Ξe+Ϊμb…»ψBήzΑZ(e9 Ά˜ςΆψχ^°~§ΟήΕ EIs%kν8{/‰K§ΓΧNy`|_gη1ύV0’€‘ό¦μCΌό~σˆ66|ΤYΥ_M%ή {½‚zΈ ιΓ^―X₯l†.D]OυukΒκuq³Ο[ͺΒ©Ίώ‘FΗ΅\ŠϊΈ£λΞ)‚YνIΚ‡h_–έΰΥ*εν²Χb½μΛfΒΊKΆ5ΙzΧίΧ—­}΄ϊψδδb Q’½AΞ•Λ&Tx0„±€•΅€˜ήb σlŸ}β’η*#ΫΩ3 Κ‰p #j”ΣζρhYw~OUŠv—νPͺbEΓ<ΚΨόΑvž*₯lγ³]₯& Ή‚|ΦΝP²~~^YΓslω¨¬6XˆG›— ~ˆŽjΤρs8³gKz!(Φύα€"μΰΓκδ ψYRžM²ΫuWLκ»Σ6΄φ—χH¨pLOMW»9/0+άͺ†)Ι±E‘έΝρΎ©άz^ΌΏdWC‘:/]LO vurUΜΆ}ƒλDsΔ‹ΚC…2XQ€@3‹ΣK‹‚;š}α²άšyaηY@ ΧΣaqΘό1BSs₯ω‘F‰>;#ΧHqΝ”}i‚;Sĝ·ZέΤS-- ΦωΜ¬ο·H₯ifͺ¦λ4$ʝέϋCZ Ο©"+ψΐvΝ1W·`EgΙC1=―\.ΫΣk}κ 6ΦέN…}™BTiEΨOΐH[Ϊ₯m ΰͺKuΥπ4ݜ7ΜΑθC+]'.&ϋ±HΥφ0;Θā0TΪ@εΨ¬U[ζ“z D٘žΙ.+“]B™8³δj³,›v~tηX#Η£β!tδΧσ kξΜSt]`ΝI’­ϋšR2-οΙ4έΞηrή]g‘ΡΕIne]©™z«ε²Ž/ŠGb:>gyΧ]؜oχˆoyμ{1W­„¬ηkδΟjžq―ƒΜΰcF―‚Œ.Ε"uΜΰ©ϋM5pέ8Μ–¦Δ F]κΩ‚EHq Ρ)šu}[ ϊ;INΓΰ}؊½―MΐJ_½ψŒγ泓ϋ1ά‚θZDpeiΰT™-‡„Ά,†μ;8K|R’|ˆDR=\Y/Ω²ΰ½”R*Βΰ8~ΥΚ‰Z7˜‰wξΣq&hu‰sCμ²tς`}bG’ελ^§ άΈ(JR„>ΎH)‘%°€" ˆ©΅ 8ƒK,…EΛ‹ΑAΦκΩ2e³œ¦,‰‡Χ/32ψώψšb*©  Σζ₯Q΅ζξ©‚VԘψ!₯ΒλCά ¬§5Ωtϋε&#­Ξm6…rίRύj›:¨gpBz²tš§΄:Κv₯“=½΅‹(”ν'ΚΤ"ύ-εšiq•JB―Π«3Οέ[ Š·‘ίƒβQ][ έΩ%Rtψ(Χ‘—rwν=§κUΝ‰0§J "QkŠΧ>΄M–ZŽπvY–*A©qd§›kͺ`ξ@²?ό±9*―›T¦+MξφpUοΒ4­θΤϊp™Υ0 9Ό‡ϋ‡ eΙδ<²χM.Υ"kπ-ΆΉd| ceρΨ‡©Ρ”5…S6ΩΦΉvΞΓΎTjϊ$ϋi/ʎšŸŽb­mAe‚άΪ#2ΏΝ Y˜Λ…eΘΖΒXžŽ±Ρ—!s}7dyχ ηiΣβiΘΪ€O.Ϋ½Z«2ΙΝLnΘDBι§ΡΉŽGά#¦qήΏnΨΝί₯εCίy-αYέP.`kAζη*έ³ƒm―Kν2$Bύ₯€oγγ‹™_πx„&mΦύ\‡ŽΫΰ‘ -™Mχ±Ίρϋήϋ0Y’­:Œ”Υ‘Fe¬’έI.–—“†~s|δ>.½š]·h΄0ΈœZš΅ί–(π—’7r”(–@¬ύTCΒ€?΅ν¦)ιΊ€1ί₯©V3ΌγΏͺ-©½#¨X―*«ΧsRŽͺ F5\!Ψή\ 8 o ΟΟ9ΎoRt‘Ё,30α/oL=c>FΣ΄ΆK¦κdH Šφ@ξQŒΆA \Ke£ ƒ21±U…ͺpv:&Πjσǁ8i]Ζ#ŸZέ%μn΅’R«k€u³Β‘v[DMοαΥ žxΚ=p”Η jNάΚњ½Έ*ͺl«ŽBZy ϊ,~VφΙ‰2M Λ; pΓω%›{/œΚy•9TR‡Θ[oG9³'а/}»F/Q*Ό°ζ‘x²R#bkάIω$λR=§IKqHΑ©&z™ž£ϊϋPCbγ0Apϊ{ζ€έ•κ¦š‡―K“8Vΐ›‹[Ξ,OxbοžΪς.­Ώτ#•”Θτoή€V=‚©ΰ«λ(7ο=ΪάpφZΩaϋΖ{"΅¨.2΄‰γηe‡μΊ†ο›OeΰΌƒΊL)ˆƒ ©ΘΓ’£ΨΫα‘δΔh½gΟ?6uΔ€Ι Q"pβP‘sU€#™4Ɂa?υ#•N,Rζ°(LfaΊ­Q 8 -Ή“^εΐL’m˜α=ύEτ‚μ•7U §Ό_dΫ ͺΉDE|Δuρόχ5’…ΗP2KσΖΛ£AcpδŒθ΄LBΈIδΊͺ£Ϊφ‘»΄]νSښϊ`sφ4Ζ\3h¦ϋ#λ”%)€ΡGyp*\ΑdOƒ[C6d(DΩc ΦΣ Τ–°MYΌΎ‘–ΉjΩΗ3γiδβ1εΘJ.δ=ΪsεœBhΗ|εUΡΡώ&»΄ΙiΡο‘Yί98Š‘”‚ΤhVψ>φl}'Ύ8ŠΫ·Ι§­M)΅5dβ†8η·g΄oΐ!CvDΪE^t€'Yέ“τ dcέΉιXΚωψ]ΕΡΗν&MόλΆ„šη8\u}Tπ±»>°LyŒU$ΏλBEdWdΩΊ& ZD·H…mύt•8m{ž_eΚ—6ψ+4»1# DUώΰVŠQ³ψˆΌyKΈz’Κ b8/„έάͺXOE©κ «ΪΊφ‰ŽΡDFžε‹Ή3!JIΩ΅€™ƒΚB:ΊvGͺEuƒ@G3qσS€:«duΩ±Π—YΒjΏ|Μ£i±χ#WkTqδzψ‘5¬‡‹“fΓ!X”™‡Ξ—α­ΰ7I‘‹hΛ[ΘΦ7#rπ3ΞUΎS…/oyΘ­2§ΤΛ²]Ζ­:ЊΦv?ς.pΚψEΕΝ‡eσv*Ϊά=Z‹ρ΅,βD]ΫWΠ%“ˆ%nP:‘ΡӏζτΆ€>E9.Ÿb[puΉcω²8CU°¨ADΥΝ_ΛYE±Φ$έθcΕΪ(Ϊ™jΎ-KJ5ͺz0†Ζ‹MΙ!χxQΗ0΄ΘaN1Jε]΄}σηE—¨—ζTμ¦|ΞjI$(‹›>OίΞΊ*w3)β1…ZΟζprόπK'²z›MS“ΊΩμ™v”ν~6C5ŠFΩΞ’΅2r‘("ˆ‘cϊάX8XHΎ«Ε=5Lˆ ڏ4+i»n…]«ρE_pδΨx&AΓ¬q›gη―^μΪR»p»ζhΩ’‘ΙqτδyΙ5j~₯I–>†Jν‘sΉ‹ϋRŽ΅8ζnΦϊiΎΪ6\Δ=H^ žκ~— /]I±m…4]ξώ›zƒ₯/5NΌ^,ZΌ‘|«ευΝ‰Uφ‘‘}ίfαͺ¦iςΞt½ΪΤKZΔΦ7ΖΆ4˜OϊεΣΞ[AφΨΎ‚–₯5+:„”΅+_θ-½<³#F‡Œ‘Βƒ’8Tv zjύΝ48.ύεϊΔb”Η9Ί¬β˜‘š±l“LCM<•Œ8-•Ζ‹ŠΉ›†“Τ˜@nˆεψœ …‹δ‹§Y[šOΛ¬#_²tFΗ™Λ΄ηpζ3ΛίσJ›ϋŠ{ΎO+zλκέY[ΎΟ½GQV|ώ OHΊς²ΛΛσϊZΩُνό`Χ6¬oω―¦ΙgδΧ’p―δd_Ί‹6yΫ@ΡU₯dϊΝΔ|¦ΙΊΞK^•Λ€½jΓΚΏΦΞτ  †ξωOσ+"Rrˊ(WθΩψ>ύ‡—ι Σ\:;½΄a˜Ζ —»³xΟπο:Ότ™kAYβκE“l†RΥου“ ϋΧ³aε„Ϋ‘½ŽΉόοsuΓά&”q/­½ΚΟ"Όΐ&Uw™8ΎΏXu^γI/1Χ6̍ΐܝšΕΫ—‘Œα4vŠ‘ΤΒܿ梍% Œ ~c;Τ"ΥΪΡ;m€ͺ1‹Ά=Ž’γƒcB-šTi-CjDΤ,;©‚nDΒ2ŠΗΕωΖύΰη²D —ΣxVԚWΤ‚»„«5&»#EŚOδ(Έ90ΧHRΧ°Ι«Έ9kœ\$5#)u²™6]zNdsάόr ۜx(GokœAΪ%ρέΠ_š6υΊΔM;χ"DΊs³v†Φ.*or·΄σ4pκ_'ε8ΕWIύ:NhU77―qca™ΨFιΟ ξ–8Π/ B™ΊΠJQ9¬ŸΪUkβ ·&• ϋ"κbλ>mt$•α0*Χώ>ΆI”ΫΪϋΦϋθΒή}.yΩ+ΧžΤƒW¬Η%L¨yΣcΌ¬6]ώ“9£]t^sΑsr–ƒ χΒε%¬²š« i6τTνεΰIΨΟΤ>UknΛ3ˆζŸF+Π¨€wHΣΨΖ“5ˆΝΌXȝj ` ΛT§:xΐ* ’™LΡ"œŽξΤ¨'o<©wœ ^ΏξΆΟz_y]3ΏK\}εΓϋΚ™7\UκπμΙ΄‡‡ZνΪ9Ύ4ŸA<Ξ‡MτNtZ3v°?VLs\.~ά»Βξδ½³3œρδδVχζΣ|bac7χ{ΆδΤ‘Q}L³ή§υΪε½Νν.¬σΪWnΗΧτέ–KI""9X1BΦPϊl‰Δ‰Θς7C:]b’‚’tάΩ9>½s<«CΌM9hΪΰUεUM9vΕ 2IZΆRT„<Π2ξνΆœΙαMκ׌μ|ί1Ku4n θ΅QSΖ¨δ3Ιo SσΘP ©&—mΟηΝ¦QάŸη-Y'7WoK₯Χ_δjtμj— ήΡ%3ΕοΞάΝϊ?τ—’cnGTΐ5+οNˆΖy―hΤυ©Άk΄P2z+!mFΏΗ&βl@ŸSe!dτnˆWΗBρ1<Όf6’—₯/•’7’Χΰ(Ύθ?8Їϋ¦>G):Χ$½©ιΠΚφa„§žDr«fSnΉŽέ±όΪε&΅œ"Ά‘X ¨[‡|WoDMWs 7{χϝ#ϊŠ ΔΐŠk·ƒΝΤW·³memΰ‚1ΐ(Α$Η0J {ΞάDΫΦ$Ε*ΰμΌLσŸ[ΠaJ1Θ#λ“v΄z’N m±kIALΙ΅μΥύ$aךϊ’³ή%υp]Π`{ο6’»Dυ ΅8E1€jjxmε˜ ζτNjWξM*ΙΨk% Qζΐ„₯Ϋ: ž¬bά¦Z6έ8Ονp’Ρ“WφbΔ`Ώ6δ¦χ.•ΰδj†`5ͺΡM™£vΑ“ΕΎ -"₯˜―vθΑr°–G†–ύ‡ œœYΊfϋCεπ—Š‘„¬o”‡K%ξ4] .Lπ‘ΡΓَ“+―VΞkΨΖz/}Ζp̍G†υ΄Υ+₯1v §dUκήϊsιίΆο[ΣέEΉ±lΰmxΗVψp 'Ή§xMφ,E•ξc-ΊέΎή»ί³η{HΰM'xλΦΜΠNbuΡP₯*p ΒEBϊ‡ξŽ oθΆδ‡+£q#q 6sΡΓ₯¦Η©Ώaξ«δΩl›“όƒš~Zθβ©…!>YŒjzˆ ˜=ιoG-ΫG _Φ5*ΐ§!Λ•%7g\²₯ ΰΆœθA|87:© Σqο±ΘjηπuM–GυŠEφYΪKα±_q= mδπ€1nπf΅}9)=`Τ Ύ !(Ψcώ¬„ΆΘέ©ΪO—›hπ’i…oΖ¦vΰbv&sΩŒΎ ΰί*{”θZMiψΓ’dΡ ž“κϋΓY¬vςͺ΄^m0cΧκϊΰΈ@,Š_ϋ1δ{pΠazΞσxΩχ°χυΌΦupkY[κΖ-σ½ατάE•Ή—FΆZ΄"Ϊk/ΔΦΓϋQCξQο~Ή±rφ‘ˆΎ²Άκ‰J !m›M;ύ|:Τ>Ω 4ͺΞ% CŸ‡Η–™]J:ψ°ΓCŽξΘRi“0Γ|Β xK²iŽ"x'‘,w$‹Ί‚ΪΆΣΌ-hΐ\Y*l*­Ήj TλΥmN{CwfuP±65w‘†€™|¨ OτV.\ό$ŽwΥXΌT%ώ¨€Ήψ^ή}(ilNtό‰» AΠ~Omι-Υώώ–i0‹,€pω_Β•§£+ΛκήυΚ>,_Έ©1]ς±»λͺΔ’‚cΥ>UŒQΤΙ½\šΕσμpyu+ή!|·Μ^ΌŠΘ[–MJΟτ°­΄μKT=ώαs§ϊΆIδOυλ’š)c΅―YF Ω%ΰkώt’k{y—Ζ†}ω’ŽήW;~Ιcq¨}Ψs$[‘ Ό6gΈ© έ]έVπj›Λ£vξAyΞz¬’Λ “”ngύeωAήrΌζ½Vg”Ώ.ρt?ς^“MΎ>ΛTšͺΥxLμ³>ςΆΰ_‹‰j§˜X–WΜςΣΎ;h›nΊ‹Ε,‰ /έΤ]"ρξ4ΏζXοήR΅εμϊF ‹A(κLΉΰ°©K΅fq’*jΫ†jφέ-HGe«<ΎM)ωFdoV30]ΩΡ–ϋ[vδΫ―kς’}l κn3Γ=Γ\ς¬ͺJ2'!ηά-€§΄IƒEΉͺ²BD@ηcn\mυǐ­‡nιšUœŽ‘ΰγjK«ΓΨ§ΗΣuτ»Ϋ\];Γ_YΕXKL/ˆƒ'šΠςŽw?χž«zΝ΄n…"/•ΘνCΤϋόΆ£ϋqΧ,φ/πšιkVu·€›–•ΛF1!%υE±ͺ‰Œ“±Ž9μuώ«αΣ-ρjœ3ŒiD$₯$ μiJ…Ά53`Ά!τ8ZΒπΖο©*nLj~ŒMš’qxςIrΘ W-Σ8oΛUvSM—Ρ“O9ފˆƒ ΓN³j> s Ί‹j£«’•.rM(T°"•B―MD‰κ%\"YΒ>“§‹W69-)αşΠqTΌμ‡˜vn»pι½Θ²΅ζΖHžΓ£’o[)›ΗΫ¬("AG]γŒΊGb±B/‡τb hμζ Ο‚Tk"0§™έ§7γ{ϊf Ά†5DΌ5ζXNo:όVo}zœ=ύώhrfΕΙHJ2­'₯Βλ₯ °΄»­ /MΣ₯ρ^ΟT‘uλΨmvΨ Ή ›–ϋμβ₯[(uΔ«]½ΗΚϋε–dΊŒίΫΘΉυœMβm]άKυ‰k€qί€™8p‚ε²δbΫ*Η=Ϊ~ΦαΔA ΡΙθbFzΰ‘Τ°‘F ‰9J!8K‹ίC₯„‹4ϊ[ιBtΒy-n„νQWyΏΩζΫbΕ΅˜M­y 2ΈΆΐŸχ›©'*ΣτΑ 9x‘UΞm²DΨΞ©αŒα£+Τ)Υ(¨T}ΎψšNΦLτH3LGηšό―ΖŠ^σγE.ψ³O=Eξ5τΛDdMΆςΘSSΝ7„ϋ-φƒ'…lGΘ]³rνƒμ4©Θ@‰™¬AEDάΰTIΚI μΥ­e Κ°€ϋSρΪΞ<4ψUΥ‘λxTgΓa$Sξω¦˜u•ͺNL· •—κμžIέ’C—)|Tδ:ΝρQ‡V8 EήN<ζΈ'f*% ,T^Κ±»q|-§΅ τ•υ|Ÿδδc(“τBΔίX„§N–¦bg²…’@ͺcŒ,Xhφ˜ϋ³§₯T½uTω©3J¬lv/Ϋd·˜ΕU¦] J#±Ei.‚?!΄FWνφΘs_«ΈvραΤ•L2?M–γw60fοAL5©Ό$ΎC!EΚκσƒ8 ¬uͺ¦ιͺ₯Ua Τpdˆ.„³'žP°y†ΠΐU8ΰΏ|ͺΩ!JUΔΕ9ν 5œΐƒΫdΖ*ζxγΘ]FCΞ4¦’‘¨φΨڊκ™Eͺ͜ΜHΣ`hK‚I‚Ψ;Xm₯€xG ˆΊR Pκ‡I,2pΣέO>Π•ηž°₯N©?ΚΑσ‹³Ϋ8θΣ–EBή­‘άΤΛ8«ύφ½³³"φΒj?Ώ£r³•ω΄’rY]Ω΄o!#ΟΊYΘΎCŸGΈη—oφΣ“C§λΊ§2V›ΣD‡V¦Σ6]JQuu˜IΜ/1cM=yη΅XTyό‘©2!j₯ΘΘ₯_‹³4ΛΈ†β#pͺ’K‘IΫΜV`τεnΛΧ꽬θ“βυh>¨+kjΗhΞΉ’ςˆυœ–α»₯έΠ))’Vλ:-CΥ«­Ϊζ$†˜Όcβ•ΓCU|r–gm’»ΆΡ­g^2ϋv€ΝΒΕDA£έŠώυΣ―ώ‰ψυUƒlC3]*zbνΞbNQ©‘wSa· P—y€’ΩIΈ“k€³Ζα@ρ-v0^Β“ϊXΌΌΊ“ιχž•WcΒ½Έ'kd²`%N·Μ« ”Β£΄uΖ–ΨtΖΊ–•ΦTχΟ{Ωkqβk ν-Jί2―νδ0©ίΕERΕd₯ΟεݚE XΕ.Šη|Ιϊ`ΒΏ—xOpU3Zm1'VιH©θ4 Φs²ηέ{^΄―XoΊVͺgv†4ΏΫ˜Š!§θͺvέ]₯_ΕΜΛκΖRIRš šRΣ10†JΥΖ‘κτd޳¨™ƒΗΞ9ϊμxW@ .L$μrγEΚE#+‡5U>Ρ_οw™ξ"Ά‘m*ςY«Λ>,Ί―Γ¦»~NN¬/δ#Ϊfμ•ςφ#γ$μ?ϋ„ξy‚Z ζ’b₯PHQ';ƒlΙ:]φ³‡-G†ί‰Cg³k―Θiϊ`;H cΠδcΞϊͺtkΗR σΘ5Χ₯«Γ 3yΠUsαHšΡΥvd]§χYS#„“ccPΆvωΒ;'I²γ:z/6_Ψ1μ7c•vaΊ±Λ άλŸ›™χwηξo}N"Μg*X–0ί¬ dίθυ―Zξ—;/jί‰Άmέ4Y₯ς₯`²Κ1S4μαΣ‰²ZΘW 3+Ά‚y*ν6zlĈ’»²wƒ"vI›Ž—H ­«¦ΧμΚΰ2Γ£z/›θψ «JŒΓeHcε΅ΊqF¨}YΓΈua€¨ϋ=kޱ†N{#·9Φ9θΆZ«yϊHR₯](ϊ―vΘ”%ΪΊb%…4MΪμ°Δ6ΕΨΘ$Ζp@5λg"ezΟ`knΙ=Qδ“€°i ¨ς@t³|μ8Θ'MŒ΅3ήƒƒΫU5 Α" 3ςI\BdyŠέ`‘q •_;§)gΉ¨0=†ΆυgΏ₯+²ΝΠrASαcV$;’ή"χ«’–κdœή#—”RšLψj ΗttΏ• oWZ/πΫΌά΅„γ¬<Ν<Λ4έ^ΕβG€{oRK//ΗΒώkϋ5[ώί n%)ςFnΰB3«rΝ‡N)P―_ΐ$ Œfrhž½5Ι’Ά)—ΊwWϋoš0άυžniηΤ@χΰ}§!kΌ…³²C€½.Y`8ω ψLΕΘYβEΛΣυž:0Ά3$†Š£/λχχΌƒwΘυ.>ξΥαξΦ Σ*λ°n†W†B-2…ΤαΜ#ϋΊ΅ΗR‰.MQc` œiΗi!„$eŒŒwά34grˆr­]ω‰>‚ΐO3»Ί B7³IT>9κtA{RsϋΫBc½Ωίιšζ7ϋΫ ΠrΨ_’v|Πώ‚χ Oθ'ˆήμοKΣ›OΣ;TLFlRgqGΔ1ΘΣ`5μ’3žG -=‹ΐ:“βWΡϊ`—­΅«TέΞθMγσ Υ›Θ§šΖ/K— •žXͺ\j .`΅υ  q=Hk8 οt–³le’ΕPηΗ{ ―R’3H½ϊμ1ρOΓ+]΅ιDD7ΌιjxΥ}0‹Α~Ž οψA·\ΰΣπ"݊`ϊ―?U› ɎL©‘OΆΑΰΆ .˜Ά2̘ΜΘz\As]’+Υ1Ό¨ŠŠ@ Xe―Χ™)΄MζXΠΗSJ#Χ ± \'Ο)GQΚjΤI‹ό$}>ά§βσΥ'7^b—Σ ,R | «Dω˜‹~j>§=MŠΗeΨήΗΎu-˜ ΣιQ£‰Uoœψ‹Γ^ZΩ‘Υ7|m—;f·¬z 4€p«σΐR4ΖΈΛίtίvξqτΗtΝξ—[k―ΖJϋ€—*@ŠlwˆόE6ΛVψ/‘ ˆˆΘ.ΈΘa3™ΐΆ°Μ~φΆΩΐjϊq0 ¬QΚΡΫ2£Θ½(Έ0yœk5ε d°¬1΄Θ₯I^“žΧΖ}e#Yœ8­ΞΥ`kF™›υαϋONΫ ΒΩoΧ§™’P·ΨQ†˜”£³ίwv—Ήq¨‘φψ—ύvŒΈ«“ƒ‘CIXprNβΤ ™ŠV’’ΘΟ~πoώρΛ―Ύzσ·ο~ϋΝοί½ωςλϊΗ/Ώύ§7ΏϋκσοΎ{σωwoΎω»7υωΧoϊκί|ύΝχoΎΕξλ/ή}Α7όϊέ?Ύωβέw_ώξ럽pΪ‘‚={·Νγ³OΈGNtΙγš‚ξIlވξeP -’c­ ΩΎ0ÝΤŌnGaJν=e:jζ£Mω[ՌD‘΄HΡΐ…C»b`όσΤ°gΏKC‹Ϋχκ>»Z=―֏|–žb<8ΗΕCτ₯"Γ-"ζη₯_‘‡‹γ.ΆBΩpm7©Κ‹|±%²·~=ohΡcŸύz΄ϊμαν±!̜Ϸ78_˜”Šϊ·f<)V¨Ÿ–VΖ…ͺΫΩΗγtΕΥΊ½ψ΅Ψ[̊±Š„΄_Sj˜ g2œys ‡šΒd δ'Qbj’ έ*A:šzπFΑb ,Ζ,zώtυΞ©ΑxM+· ]4u˜Yτ©ΩΚ©ξ&*Έ_Ά i₯£ΆΜβΉmψΙJ7$ΰψLF‘£ξ[@Pn|‰Γ/­‡²X`qΪ!ι)šš~TΠ)E`{Ÿπ§Vρͺ)Yƒ8“”X@”}{a„aΞ™έ} $0 yψ“mͺγΖ¦xMD±?zΔgLΎ‰ήrΧiͺ²άιθΘ$‹r¬ρ{I2OTpˆ½HŠq"³"'Η’τΈΣ$0i²SΡ„xήCΚFR?€ΠHŸH|Μ“ο@οŠUΈCEώΗ8SӜ§―/e#cn¦ψT–ρ©ιaCa}ΌΗ©“¦Έk•lΟpί¨Ξz¦7Νφάυα•©½Œ:rΈΥs*:ώ5ό1Ψ.©zοΌd]¦«»λSt$QКύΜΨΙe]O e£mCζΦš.―{`©ΧpΕΤιœ‘₯Ϊ9Ui5/’Σf¬ŒΫ„PΉ4 x ςqœ‡Ο^%RP(CέQνΕηtθnq#ΰ&Ϋ¨ DE ώ$V˜N94 ΣvVgϊΤeuKUΖΎ%3ρ‡λNSziͺ;Αχ2§VˆS²=©Ϊ– Uek8ρ ‘5Sn›s•!6αΣΊΉΓ-)―–"’ϋτ†ΣΓ—―HΤj„βσk%©&‹©ηdχ½ΏΤϊ½³d―ύHT©œ ‘d˜Οε;‹›=7cζ"ιI€vωΗyƒΑkιΦ―sQVΚ§]SW+  ²Mυ°Ϋ£Έн!k8ΒEϊ¨α YΙ‘f»hL~ yV€‘2ΌQltŸ˜άƒΟ―Sρ6䦁γkΉ_c[ƒΘ5MΘYΉΫ2ρΉvAŠhES€zuQλ6vqi %unίS ά8΄πΥ‚& ρcޝOΰŠκ' &΅ΙTŸγΪEΉσ‘ΟzΔώΟkbή]ώ1%YΫ™ŠΑ%oΖΓΝ›a`hνηΐ π,$'“@1;H/Žσž'OgUT(/aŒ΄™R.H™*"6²Θ–ΌYXJ‚aΉˆι˜Y~hβ.ΝΐμCΈΚ‹\Ρluœf/ڊήγdΐυx_£`}ΰr₯?C#R\˜€οX·Ίͺkwx'"Ο&Ζ°S%TΪT'xff ˜ΐbKINςo}Tσdk·―Id©9ΙZgͺΉNqGοvd±κ‡bΔ]λμ\¨:ΣDτκ“ΠE~ƒ"؎ΛD–Hθζ€ρ―—ZgQΈœ@Λ–θΕ‘uζs(—<ƒΓS–Λ~σuφ£œDΨ;(ΐζ§όms•y ͺK Š&8—θϊ& cΐ`Lν²vΞ›΄ˆcΠεΧlˆ”Έcdρ¬ΠΑL:’-,§ψ`πτΞ ήΩ2ώhU G3š·v>œŒ{²tΫΡͺΆf2€ƒ#α―-³Ύ=δ2q“+ϋ4/ο³ώprτž(έY{R§l ‘Q ΆCνXLΝ•¬·˜μ‚Σ_ή)½w\§νλn_Q*ˆ)žϋΘAw…«CR}&»>sηFj2όΕj8ςΆ J…Σ•@Ι"Τ6¨ά ’ΈnΥ…?%ZV%ςΪ«‚ζz·iU₯€:^νΪ»QΥΕr]Γΰ»(Ϊ”ύζΤ8ž78Ί–’w’§ΦΆž΄‘h ΣD†Lδ΅Β‹ΥπRrE$Όαs=Dπ­Εο9gΏ(fόΧ.}ήK ,1h©Md βΑ8ͺ`˜€W„ρρσΗ΅i/“LιC;KPgΘ[—”9τQ£ωΰ#UˆΡϋNζŠvEΎΠ°oΰHAς»²Y)πpc[ΕΣ|t― ΗxQ 7³Š*Œ7Ηx:xΎΒ­?ft©ϊ֐VΦ~‚5€ŠλzG™KWΔέ'σ]† νιΫ=*άRwyυ½Όd4κ³Κα&w”χmoVΤο‚&]n’ή†Κ$1ΣMςšϊ‡Υ)~zΜ]F 2gΩe˜|ηλ¬n/ΠΜfͺΒ3#°‹7%_{wUΒξ@φϋ=ŒDΣ©”škξυΰM·¦S]ŸM§KLν΅¦ΣΊ‰£5οi™έ‡ϋIεΉ2»‘CS6Η™ΏCpu½R(Š€Zτ$χ{ψq•£κεV•5z•5ε£JΓfS8ΥύγIO ΐF•5$Ϋ›‚(1q#=ͺNΚSΥ±θΌ2·L}`ŒL°-’Μš”ηΏ ]1€ma݌NC¨Ό„>‡$πΒxWι6jy=9ΫρPAœωfΌσΥxΗ׌wΎοqtΏ©-ŽέoI#έwγ½ϊlφ8wGJΫξTZ;·ΈκΛ‘Η{¦„άVΎd:ΊŸ»βXΉεÊkϋΏΡ˜w ¦/‚‚CG-?%Š·Ί€)μ°…Š5³ΠGdjUٞ·$˜lΤy Ή–ŽΛP[œ‘Τ%E„’£΄\PΊλ·tΧoQŸ€­j‰Ώ¬n΅μxσ«ΪςRΦϋ%Υ~m/2[94sμ Θ«rΝIE`μ΄Μ»θ‘6φΨK6κCχb¦ΘQ―\%–l˜‘άDzσ΄©7±Υyτς,iSšχŽ>¨Pϋ€Δ鏀>v ‹ώδ³O<$α±5ί;*k™wš€\πμΨιHSL’QRiiΈh“ύ₯!λH9Tΐy\ΜΩ―f΅ΐa‰pάlMΤX/ͺ{’·p/`U=©ΝƒλΊΣW³OτςJrKέ5wžμ§Ιάoφέ€>S4‹ ΪNr’βΫ(7/ΖΝc± ϊeΨmηk6uo>έiΓ„μΞ»Pˆ:˙ޏE€pυβΊ+τ|‡Ο^oέ^Ό-_Ο§δpQV­ΧKΚ+©ΛN¨¦Bν;>”>+;Ό‚jL€Q’4±K&ε¬Ή‘έ>·™ͺ“Υ j[#α°\Aύ(1ŠR“ΨζLΦΫ*Ήpg°{υw™dΩ~Α@έ>~9Lτ5σ!G€™ΒQγνΊλαΝ>y~)χ(›ΔƒZ•ΰJ%θ™kP―: Χ(μψ’οΞǐ(ζ/₯ΐ1 ηρΥ»ΟΏxσΫoΎώώσ/Ώ6„zύτϋϊΓ»GjK\­£žηKκ•Cb>Ρ²ΛθFΉ₯!1,ΞΦ§ΘάΑl±2o4μ@¦η˜ϋ31²4™Ζ 3†ςލsA Ρ΄ šόWpˆ'A‰zœŽ†Ό~¨φ–U’Δ›X=Κ €VˆΉμ3Ȝ*JίD όK1ΛΣf^ΥΨίπz™8xΚ©8"%Ύͺ„\»–dΐ (…υΆΦ‡σΠ{½I­κAΎ³«ϊ·-~™œΆ„<%”f윑νM̘d©5@ΛΖ£yˆ5?€Μ\©` ΅ž’J3»$ΊSΘ\€1cζ™rΗxk^+֝a+2δΫΚ[Q{ΡPwgσv£ŽΦΥ‘U£P«idςΆ4©υ΅Υ«)ςlš‘Αυ]ƒ›MG­χΰ9KιμbΏΒ˜*|فΒθψ ώΠJΗΪ’Α›YιMYΒΊ0{:2-Ν!έbπŽ.6]΅³i[£EΉNZ‚±ΆΘMm_3§Τ°&#W}UΟF\bL½ΎPvC¦IV΄,‰ˆΑϊΜω5XCυΡCχ°Ι{^iκzˆVYΕv ²§"2]\’[‚xΡοfπή{oŠέΊΐ½·fΣnٌ€Θ‰Ή_ΝcΏoΛehσxwyοΪ-›‘ΉˆŸ·Ξψΰ͞ΞΒ6°f±ͺD²R†Γ,!ρˆ²DˆΜ¨^ιγK‘»L–Ρτ~²F^Έ₯‰ Sσ ₯V4JΤUM ]λ΄ήΧ+Υ"ΤιήΤήj?©xηͺGœ4³ -RΊQ<ύA¨–!)–ΉD‰Ε‘$A^cκρσR ”3W±qf*K;Ι[Mωpδ¦Κ>Ψφ ŸE΄±T#cMΚ Av9‘£—f%Q,R80-βHΞ!6(*M в•¨ E}©Z`5‘cΧQe^xαΟV֏4$dωΊeω³­y{<­αp’›Τ§hΓ}ω=1•έΣΥΣ‰πΙΏ‹ΉΙ2Ί+k”Ο/Y–²t·ˆ£ͺF;Uβ·Ψ¨έΦ),di²”JI™™œp¬Ρ9ηΝ₯νΩ‡~u“ζ-ο..ΚwtΗΤ' W… (φΨΜhφ Δb-Š°Χ‘„RϊqόŠή —φρ(­ΪƒXΫΟ- ο$N¬²ύμ°© sίΌΈχ˜5°ΗπjλVΌ)Ϋ'Μκ"WG˜(|°Ϋ9"±€ΞάH*6xAφw%¦£agυΔSt‰Ύ ŒΏ/«χnxlι‘eq›-Š©d2_–)~‰Π°;³pξuΣ9ͺ[Ψ9NΒ…‚ΫΓ«0­{χŸeQ=ŝ_Ω@ΘΊr(Έ4·°ΣgW1'xΪζU–€r#bΞτXψΜω°’­΄¬£k)―Ρ@νAVΠΩ›M}²-+ΙΣΤښΛλ·QΔΩποe…nžσE›SSηbπ{Με8π†7αDΥiS€»Β{XΡΈUvͺϋr±³¨:oτ:··’™NΒ'X…—δπ²’ΊrœΎŽΙ­Θβ~ŸυmŒoC¦2šα³ύœ!tρ%HΓ'h”‘$‡sγγαΡ&œ\ŠΉ ’d ³&ΨΗ7ΫΧ4ώ/h`vΦ#VΤͺšˆ»5c -ο)νθR‚iήϋ]§;ŸQ%έ΄ΜXδχΥ`—Υ"§ηvύ†yΡ!Rβές}WζO»1½{”w¦μ6RcγγλέΔςIVaOnΑ'z >w.ΰΔΧμh8αΘεh$VιΗiW›κ•τ]œπ”B+hά0―σƒΚεn1ΎAb€A6OΖ3Κ&κΜR’6P£*Μ’‡aΘσ­9ΙΪ6ίdƒw©KΆ¦ Eό¨ΛΪTΧ+ι½a¨ ')ν4Κ†nAZπζ?Œ’)δYhžRπ™y€Z˜>Ή‘©μ2}¬£HYνrπ\43¨₯F•`;B˜#C1›šΘ Ι„' HrrqrE8™2Ϊμv#x²τχΑ: ³|Œ™₯=z4χHυ˜XHRΥ΅Ξ„ΞaH‚Nφή„’ϋͺΊŽ±\ΩX‚s=Φ½κ*‰rΫ‚…29E¦SIJNξtο5˜Ζ' ΐ…Όb’r(WK/χΟΗ’τTpθ™Υ‹φ‚ήQ(ψŒγ΅I(ννMΝΡ …ϊ¨Έ•AίƒΒΧ.72Φ§€Iu›ς™€έ―§VΈIU‰EΡ…G[‘€'n’A†0]”V³"N—τyPμARέδ:•τΟei;ΟNΣ Ι§Y…˜‘}œxV 2‡ΎBs»­TΠκ“_βή&yQZΆ}’j•˜†Ή§‘†0ϊXϋ*A‚‘Vž5I΅Ÿ.ω{™Υμέ¬Ϋ$Υ§½z*bUXe {Ψ»‘EΫΗΟ>Θ7©œΝ+‰"‘°Ν―’χ/5uόU£Άώ˜_~H ©Ζ ’ΣNΊ±ͺ”7Ϊ`μΡ¨ό[…λi^¬‘Kv8lψ1Œ¨=,/`쁨fψ(ϋW—= ΐ*ώφέןώέoΎζ qηΣΗίόΑύέ»7ίΎϋ»wίΎxπΝŸωwξέχ?Ϋ¦Ό†Vcώ9 ΊΒωE BEW\`Υ£JR·1W ’뀩€R»ύxwΛθΡ© ¦¦ϊ_Π₯N±vΔ¨cυΏδζπν}w7˜]§μEεΰ3\Q`Ό“WbΤΓ„pχž™A4—€Œxjdͺο³Ύχ–”WJ―$ΫœήΊ ορ1ωEBiœu“χ™ΰΧΘχ*Œκu ‘^v(ν,ͺ tͺ―Ξ>ωΡ“IΆΏŽB3xπU…ϊϋhϊD‰tzΧόUd²ύhFΆdJgM\€£¦‹£D#4MΓΜu­F>†BwD}θχaζΪ€οΉx"Ώτ ΎsɜŽΩW£-xΤY0χU9άt4ͺ 1£«Ά!ΟX{ ^D8»,€O’=œώ1~#L˜ƒyγƒ$θ%ΣgFζΖǜγ§|ΐ4Gτ ΌeSœε&cϋ:XγO`ύ`½”CΔBΪάf5"πΔωΘΈϊ/Φ]bkΪ„υ½·u‰ύΦωΑ m_§ίόεφΩϊυΣnŸT\ΝΌ`€ν£uΞκTΔλoωu[GσdΙHd)q1μψΫχΖy³Ω a‹ts²ƒ /šLΓ–Ug»`σΣWŽ©tŸ€“+CμΓ&bϋH§pΨ=ΉΫ‚ΜT†W’.Ε‹:γHK‘€Gδ0ϋ»‘ 8ֈ*©ΒφΉρ‚?Κ)ύ·ΌμJρΖ=ιυo­>xΉHŸ§0yjpα“7©ιΙόκƒnͺωveŸ5.‘δDθ–<iΫ’„θ5&§Τΐbkn―₯χΒN]³ΌcΧd0c‚ψΈΧb84ΐπΊ±i§Μ|ξχΘή^ΩΊx?vƒ;m‰―jΝΡύu§ϊτΡ\ΘgjŒŸͺΐΙχ˜άΕ>QχF*ί‘ 4*np.pi: m1['angle'] = 315 + 1.5*np.sin(phase) m1a['angle'] = 315 + 1.5*np.sin(phase) else: m2['angle'] = 135 + 1.5*np.sin(phase) m2a['angle'] = 135 + 1.5*np.sin(phase) phase += 0.2 timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(40) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/parallelize.py000066400000000000000000000036341421045507400243550ustar00rootroot00000000000000import time import pyqtgraph as pg import pyqtgraph.multiprocess as mp print( "\n=================\nParallelize") ## Do a simple task: ## for x in range(N): ## sum([x*i for i in range(M)]) ## ## We'll do this three times ## - once without Parallelize ## - once with Parallelize, but forced to use a single worker ## - once with Parallelize automatically determining how many workers to use ## tasks = range(10) results = [None] * len(tasks) results2 = results[:] results3 = results[:] size = 2000000 pg.mkQApp() ### Purely serial processing start = time.time() with pg.ProgressDialog('processing serially..', maximum=len(tasks)) as dlg: for i, x in enumerate(tasks): tot = 0 for j in range(size): tot += j * x results[i] = tot dlg += 1 if dlg.wasCanceled(): raise Exception('processing canceled') print( "Serial time: %0.2f" % (time.time() - start)) ### Use parallelize, but force a single worker ### (this simulates the behavior seen on windows, which lacks os.fork) 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 range(size): tot += j * x tasker.results[i] = tot print( "\nParallel time, 1 worker: %0.2f" % (time.time() - start)) print( "Results match serial: %s" % str(results2 == results)) ### Use parallelize with multiple workers 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 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)) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/parametertree.py000066400000000000000000000130261421045507400247050ustar00rootroot00000000000000""" 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 demonstrates a variety of different parameter types (int, float, list, etc.) as well as some customized parameter types """ # `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 _buildParamTypes import makeAllParamTypes import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets app = pg.mkQApp("Parameter Tree Example") import pyqtgraph.parametertree.parameterTypes as pTypes from pyqtgraph.parametertree import Parameter, ParameterTree ## test subclassing parameters ## This parameter automatically generates two child parameters which are always reciprocals of each other class ComplexParameter(pTypes.GroupParameter): def __init__(self, **opts): opts['type'] = 'bool' opts['value'] = True pTypes.GroupParameter.__init__(self, **opts) self.addChild({'name': 'A = 1/B', 'type': 'float', 'value': 7, 'suffix': 'Hz', 'siPrefix': True}) self.addChild({'name': 'B = 1/A', 'type': 'float', 'value': 1/7., 'suffix': 's', 'siPrefix': True}) self.a = self.param('A = 1/B') self.b = self.param('B = 1/A') self.a.sigValueChanged.connect(self.aChanged) self.b.sigValueChanged.connect(self.bChanged) def aChanged(self): self.b.setValue(1.0 / self.a.value(), blockSignal=self.bChanged) def bChanged(self): self.a.setValue(1.0 / self.b.value(), blockSignal=self.aChanged) ## test add/remove ## this group includes a menu allowing the user to add new parameters into its child list class ScalableGroup(pTypes.GroupParameter): def __init__(self, **opts): opts['type'] = 'group' opts['addText'] = "Add" opts['addList'] = ['str', 'float', 'int'] pTypes.GroupParameter.__init__(self, **opts) def addNew(self, typ): val = { 'str': '', 'float': 0.0, 'int': 0 }[typ] self.addChild(dict(name="ScalableParam %d" % (len(self.childs)+1), type=typ, value=val, removable=True, renamable=True)) params = [ makeAllParamTypes(), {'name': 'Save/Restore functionality', 'type': 'group', 'children': [ {'name': 'Save State', 'type': 'action'}, {'name': 'Restore State', 'type': 'action', 'children': [ {'name': 'Add missing items', 'type': 'bool', 'value': True}, {'name': 'Remove extra items', 'type': 'bool', 'value': True}, ]}, ]}, {'name': 'Custom context menu', 'type': 'group', 'children': [ {'name': 'List contextMenu', 'type': 'float', 'value': 0, 'context': [ 'menu1', 'menu2' ]}, {'name': 'Dict contextMenu', 'type': 'float', 'value': 0, 'context': { 'changeName': 'Title', 'internal': 'What the user sees', }}, ]}, ComplexParameter(name='Custom parameter group (reciprocal values)'), ScalableGroup(name="Expandable Parameter Group", tip='Click to add children', children=[ {'name': 'ScalableParam 1', 'type': 'str', 'value': "default param 1"}, {'name': 'ScalableParam 2', 'type': 'str', 'value': "default param 2"}, ]), ] ## Create tree of Parameter objects p = Parameter.create(name='params', type='group', children=params) ## If anything changes in the tree, print a message def change(param, changes): print("tree changes:") for param, change, data in changes: path = p.childPath(param) if path is not None: childName = '.'.join(path) else: childName = param.name() print(' parameter: %s'% childName) print(' change: %s'% change) print(' data: %s'% str(data)) print(' ----------') p.sigTreeStateChanged.connect(change) def valueChanging(param, value): print("Value changing (not finalized): %s %s" % (param, value)) # Only listen for changes of the 'widget' child: for child in p.child('Example Parameters'): if 'widget' in child.names: child.child('widget').sigValueChanging.connect(valueChanging) def save(): global state state = p.saveState() def restore(): global state add = p['Save/Restore functionality', 'Restore State', 'Add missing items'] rem = p['Save/Restore functionality', 'Restore State', 'Remove extra items'] p.restoreState(state, addChildren=add, removeChildren=rem) p.param('Save/Restore functionality', 'Save State').sigActivated.connect(save) p.param('Save/Restore functionality', 'Restore State').sigActivated.connect(restore) ## Create two ParameterTree widgets, both accessing the same data t = ParameterTree() t.setParameters(p, showTop=False) t.setWindowTitle('pyqtgraph example: Parameter Tree') t2 = ParameterTree() t2.setParameters(p, showTop=False) win = QtWidgets.QWidget() layout = QtWidgets.QGridLayout() win.setLayout(layout) layout.addWidget(QtWidgets.QLabel("These are two views of the same data. They should always display the same values."), 0, 0, 1, 2) layout.addWidget(t, 1, 0, 1, 1) layout.addWidget(t2, 1, 1, 1, 1) win.show() ## test save/restore state = p.saveState() p.restoreState(state) compareState = p.saveState() assert pg.eq(compareState, state) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/py2exe/000077500000000000000000000000001421045507400227055ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/py2exe/plotTest.py000066400000000000000000000011021421045507400250670ustar00rootroot00000000000000import sys import pyqtgraph as pg # For packages that require scipy, these may be needed: # from scipy.stats import futil # from scipy.sparse.csgraph import _validation pg.setConfigOption('background','w') pg.setConfigOption('foreground','k') app = QtWidgets.QApplication(sys.argv) pw = pg.plot(x = [0, 1, 2, 4], y = [4, 5, 9, 6]) pw.showGrid(x=True,y=True) text = pg.TextItem(html='
      %s
      ' % "here",anchor=(0.0, 0.0)) text.setPos(1.0, 5.0) pw.addItem(text) status = app.exec_() sys.exit(status) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/py2exe/setup.py000066400000000000000000000021071421045507400244170ustar00rootroot00000000000000import shutil from distutils.core import setup # Remove the build folder shutil.rmtree("build", ignore_errors=True) shutil.rmtree("dist", ignore_errors=True) import sys includes = ['PyQt4', 'PyQt4.QtGui', 'PyQt4.QtSvg', 'sip', 'pyqtgraph.graphicsItems'] excludes = ['_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger', 'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl', 'Tkconstants', 'Tkinter', 'zmq'] if sys.version[0] == '2': # causes syntax error on py2 excludes.append('PyQt4.uic.port_v3') packages = [] dll_excludes = ['libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll', 'tcl84.dll', 'tk84.dll', 'MSVCP90.dll'] icon_resources = [] bitmap_resources = [] other_resources = [] data_files = [] setup( data_files=data_files, console=['plotTest.py'] , options={"py2exe": {"excludes": excludes, "includes": includes, "dll_excludes": dll_excludes, "optimize": 0, "compressed": 2, "bundle_files": 1}}, zipfile=None, ) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/relativity/000077500000000000000000000000001421045507400236655ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/relativity/__init__.py000066400000000000000000000000321421045507400257710ustar00rootroot00000000000000from .relativity import * pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/relativity/presets/000077500000000000000000000000001421045507400253525ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/relativity/presets/Grid Expansion.cfg000066400000000000000000000422711421045507400306530ustar00rootroot00000000000000name: 'params' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'group' children: Load Preset..: name: 'Load Preset..' limits: ['', 'Twin Paradox (grid)', 'Twin Paradox'] strictNaming: False default: None renamable: False enabled: True value: 'Twin Paradox (grid)' visible: True readonly: False values: [] removable: False type: 'list' children: Duration: name: 'Duration' limits: [0.1, None] strictNaming: False default: 10.0 renamable: False enabled: True readonly: False value: 20.0 visible: True step: 0.1 removable: False type: 'float' children: Reference Frame: name: 'Reference Frame' limits: ['Grid00', 'Grid01', 'Grid02', 'Grid03', 'Grid04'] strictNaming: False default: None renamable: False enabled: True value: 'Grid02' visible: True readonly: False values: [] removable: False type: 'list' children: Animate: name: 'Animate' strictNaming: False default: True renamable: False enabled: True value: True visible: True readonly: False removable: False type: 'bool' children: Animation Speed: name: 'Animation Speed' limits: [0.0001, None] strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 1.0 visible: True step: 0.1 removable: False dec: True type: 'float' children: Recalculate Worldlines: name: 'Recalculate Worldlines' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'action' children: Save: name: 'Save' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'action' children: Load: name: 'Load' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'action' children: Objects: name: 'Objects' strictNaming: False default: None renamable: False addText: 'Add New..' enabled: True value: None visible: True readonly: False removable: False type: None children: Grid: name: 'Grid' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: 'Grid' autoIncrementName: True children: Number of Clocks: name: 'Number of Clocks' limits: [1, None] strictNaming: False default: 5 renamable: False enabled: True value: 5 visible: True readonly: False removable: False type: 'int' children: Spacing: name: 'Spacing' strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 1.0 visible: True step: 0.1 removable: False type: 'float' children: ClockTemplate: name: 'ClockTemplate' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: 'Clock' autoIncrementName: True children: Initial Position: name: 'Initial Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: -2.0 visible: True step: 0.1 removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: None renamable: False addText: 'Add Command..' enabled: True value: None visible: True readonly: False removable: False type: 'AccelerationGroup' children: Command: name: 'Command' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 0.0 renamable: False enabled: True value: 1.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.5 visible: True step: 0.1 removable: False type: 'float' children: Command2: name: 'Command2' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 2.0 renamable: False enabled: True value: 3.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Command3: name: 'Command3' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 4.0 renamable: False enabled: True value: 11.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: -0.5 visible: True step: 0.1 removable: False type: 'float' children: Command4: name: 'Command4' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 8.0 renamable: False enabled: True value: 13.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Rest Mass: name: 'Rest Mass' limits: [1e-09, None] strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 1.0 visible: True step: 0.1 removable: False type: 'float' children: Color: name: 'Color' strictNaming: False default: (100, 100, 150) renamable: False enabled: True value: (100, 100, 150, 255) visible: True readonly: False removable: False type: 'color' children: Size: name: 'Size' strictNaming: False default: 0.5 renamable: False enabled: True value: 0.5 visible: True readonly: False removable: False type: 'float' children: Vertical Position: name: 'Vertical Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: addList: ['Clock', 'Grid'] pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/relativity/presets/Twin Paradox (grid).cfg000066400000000000000000000650731421045507400313750ustar00rootroot00000000000000name: 'params' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'group' children: Load Preset..: name: 'Load Preset..' limits: ['', 'Twin Paradox (grid)', 'Twin Paradox'] strictNaming: False default: None renamable: False enabled: True value: 'Twin Paradox (grid)' visible: True readonly: False values: [] removable: False type: 'list' children: Duration: name: 'Duration' limits: [0.1, None] strictNaming: False default: 10.0 renamable: False enabled: True readonly: False value: 27.0 visible: True step: 0.1 removable: False type: 'float' children: Reference Frame: name: 'Reference Frame' limits: ['Grid00', 'Grid01', 'Grid02', 'Grid03', 'Grid04', 'Grid05', 'Grid06', 'Grid07', 'Grid08', 'Grid09', 'Grid10', 'Alice', 'Bob'] strictNaming: False default: None renamable: False enabled: True value: 'Alice' visible: True readonly: False values: [] removable: False type: 'list' children: Animate: name: 'Animate' strictNaming: False default: True renamable: False enabled: True value: True visible: True readonly: False removable: False type: 'bool' children: Animation Speed: name: 'Animation Speed' limits: [0.0001, None] strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 1.0 visible: True step: 0.1 removable: False dec: True type: 'float' children: Recalculate Worldlines: name: 'Recalculate Worldlines' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'action' children: Save: name: 'Save' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'action' children: Load: name: 'Load' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'action' children: Objects: name: 'Objects' strictNaming: False default: None renamable: False addText: 'Add New..' enabled: True value: None visible: True readonly: False removable: False type: None children: Grid: name: 'Grid' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: 'Grid' autoIncrementName: True children: Number of Clocks: name: 'Number of Clocks' limits: [1, None] strictNaming: False default: 5 renamable: False enabled: True value: 11 visible: True readonly: False removable: False type: 'int' children: Spacing: name: 'Spacing' strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 2.0 visible: True step: 0.1 removable: False type: 'float' children: ClockTemplate: name: 'ClockTemplate' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: 'Clock' autoIncrementName: True children: Initial Position: name: 'Initial Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: -10.0 visible: True step: 0.1 removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: None renamable: False addText: 'Add Command..' enabled: True value: None visible: True readonly: False removable: False type: 'AccelerationGroup' children: Rest Mass: name: 'Rest Mass' limits: [1e-09, None] strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 1.0 visible: True step: 0.1 removable: False type: 'float' children: Color: name: 'Color' strictNaming: False default: (100, 100, 150) renamable: False enabled: True value: (77, 77, 77, 255) visible: True readonly: False removable: False type: 'color' children: Size: name: 'Size' strictNaming: False default: 0.5 renamable: False enabled: True value: 1.0 visible: True readonly: False removable: False type: 'float' children: Vertical Position: name: 'Vertical Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: -2.0 visible: True step: 0.1 removable: False type: 'float' children: Alice: name: 'Alice' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: 'Clock' autoIncrementName: True children: Initial Position: name: 'Initial Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: None renamable: False addText: 'Add Command..' enabled: True value: None visible: True readonly: False removable: False type: 'AccelerationGroup' children: Command: name: 'Command' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 0.0 renamable: False enabled: True value: 1.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.5 visible: True step: 0.1 removable: False type: 'float' children: Command2: name: 'Command2' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 2.0 renamable: False enabled: True value: 3.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Command3: name: 'Command3' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 3.0 renamable: False enabled: True value: 8.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: -0.5 visible: True step: 0.1 removable: False type: 'float' children: Command4: name: 'Command4' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 4.0 renamable: False enabled: True value: 12.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Command5: name: 'Command5' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 6.0 renamable: False enabled: True value: 17.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.5 visible: True step: 0.1 removable: False type: 'float' children: Command6: name: 'Command6' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 7.0 renamable: False enabled: True value: 19.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Rest Mass: name: 'Rest Mass' limits: [1e-09, None] strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 1.0 visible: True step: 0.1 removable: False type: 'float' children: Color: name: 'Color' strictNaming: False default: (100, 100, 150) renamable: False enabled: True value: (82, 123, 44, 255) visible: True readonly: False removable: False type: 'color' children: Size: name: 'Size' strictNaming: False default: 0.5 renamable: False enabled: True value: 1.5 visible: True readonly: False removable: False type: 'float' children: Vertical Position: name: 'Vertical Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 3.0 visible: True step: 0.1 removable: False type: 'float' children: Bob: name: 'Bob' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: 'Clock' autoIncrementName: True children: Initial Position: name: 'Initial Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: None renamable: False addText: 'Add Command..' enabled: True value: None visible: True readonly: False removable: False type: 'AccelerationGroup' children: Rest Mass: name: 'Rest Mass' limits: [1e-09, None] strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 1.0 visible: True step: 0.1 removable: False type: 'float' children: Color: name: 'Color' strictNaming: False default: (100, 100, 150) renamable: False enabled: True value: (69, 69, 126, 255) visible: True readonly: False removable: False type: 'color' children: Size: name: 'Size' strictNaming: False default: 0.5 renamable: False enabled: True value: 1.5 visible: True readonly: False removable: False type: 'float' children: Vertical Position: name: 'Vertical Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: addList: ['Clock', 'Grid'] pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/relativity/presets/Twin Paradox.cfg000066400000000000000000000520311421045507400303340ustar00rootroot00000000000000name: 'params' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'group' children: Load Preset..: name: 'Load Preset..' limits: ['', 'Twin Paradox', 'test'] strictNaming: False default: None renamable: False enabled: True value: 'Twin Paradox' visible: True readonly: False values: [] removable: False type: 'list' children: Duration: name: 'Duration' limits: [0.1, None] strictNaming: False default: 10.0 renamable: False enabled: True readonly: False value: 27.0 visible: True step: 0.1 removable: False type: 'float' children: Reference Frame: name: 'Reference Frame' limits: ['Alice', 'Bob'] strictNaming: False default: None renamable: False enabled: True value: 'Alice' visible: True readonly: False values: [] removable: False type: 'list' children: Animate: name: 'Animate' strictNaming: False default: True renamable: False enabled: True value: True visible: True readonly: False removable: False type: 'bool' children: Animation Speed: name: 'Animation Speed' limits: [0.0001, None] strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 1.0 visible: True step: 0.1 removable: False dec: True type: 'float' children: Recalculate Worldlines: name: 'Recalculate Worldlines' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'action' children: Save: name: 'Save' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'action' children: Load: name: 'Load' strictNaming: False default: None renamable: False enabled: True value: None visible: True readonly: False removable: False type: 'action' children: Objects: name: 'Objects' strictNaming: False default: None renamable: False addText: 'Add New..' enabled: True value: None visible: True readonly: False removable: False type: None children: Alice: name: 'Alice' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: 'Clock' autoIncrementName: True children: Initial Position: name: 'Initial Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: None renamable: False addText: 'Add Command..' enabled: True value: None visible: True readonly: False removable: False type: 'AccelerationGroup' children: Command: name: 'Command' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 0.0 renamable: False enabled: True value: 1.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.5 visible: True step: 0.1 removable: False type: 'float' children: Command2: name: 'Command2' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 2.0 renamable: False enabled: True value: 3.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Command3: name: 'Command3' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 3.0 renamable: False enabled: True value: 8.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: -0.5 visible: True step: 0.1 removable: False type: 'float' children: Command4: name: 'Command4' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 4.0 renamable: False enabled: True value: 12.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Command5: name: 'Command5' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 6.0 renamable: False enabled: True value: 17.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.5 visible: True step: 0.1 removable: False type: 'float' children: Command6: name: 'Command6' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: None autoIncrementName: True children: Proper Time: name: 'Proper Time' strictNaming: False default: 7.0 renamable: False enabled: True value: 19.0 visible: True readonly: False removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Rest Mass: name: 'Rest Mass' limits: [1e-09, None] strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 1.0 visible: True step: 0.1 removable: False type: 'float' children: Color: name: 'Color' strictNaming: False default: (100, 100, 150) renamable: False enabled: True value: (82, 123, 44, 255) visible: True readonly: False removable: False type: 'color' children: Size: name: 'Size' strictNaming: False default: 0.5 renamable: False enabled: True value: 0.5 visible: True readonly: False removable: False type: 'float' children: Vertical Position: name: 'Vertical Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.5 visible: True step: 0.1 removable: False type: 'float' children: Bob: name: 'Bob' strictNaming: False default: None renamable: True enabled: True value: None visible: True readonly: False removable: True type: 'Clock' autoIncrementName: True children: Initial Position: name: 'Initial Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: Acceleration: name: 'Acceleration' strictNaming: False default: None renamable: False addText: 'Add Command..' enabled: True value: None visible: True readonly: False removable: False type: 'AccelerationGroup' children: Rest Mass: name: 'Rest Mass' limits: [1e-09, None] strictNaming: False default: 1.0 renamable: False enabled: True readonly: False value: 1.0 visible: True step: 0.1 removable: False type: 'float' children: Color: name: 'Color' strictNaming: False default: (100, 100, 150) renamable: False enabled: True value: (69, 69, 126, 255) visible: True readonly: False removable: False type: 'color' children: Size: name: 'Size' strictNaming: False default: 0.5 renamable: False enabled: True value: 0.5 visible: True readonly: False removable: False type: 'float' children: Vertical Position: name: 'Vertical Position' strictNaming: False default: 0.0 renamable: False enabled: True readonly: False value: 0.0 visible: True step: 0.1 removable: False type: 'float' children: addList: ['Clock', 'Grid'] pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/relativity/relativity.py000066400000000000000000000671231421045507400264440ustar00rootroot00000000000000import collections import os import sys from time import perf_counter import numpy as np import pyqtgraph as pg from pyqtgraph import configfile from pyqtgraph.parametertree import Parameter, ParameterTree from pyqtgraph.parametertree import types as pTypes from pyqtgraph.Qt import QtCore, QtGui, QtWidgets class RelativityGUI(QtWidgets.QWidget): def __init__(self): QtWidgets.QWidget.__init__(self) self.animations = [] self.animTimer = QtCore.QTimer() self.animTimer.timeout.connect(self.stepAnimation) self.animTime = 0 self.animDt = .016 self.lastAnimTime = 0 self.setupGUI() self.objectGroup = ObjectGroupParam() self.params = Parameter.create(name='params', type='group', children=[ dict(name='Load Preset..', type='list', 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', 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'), dict(name='Save', type='action'), dict(name='Load', type='action'), self.objectGroup, ]) self.tree.setParameters(self.params, showTop=False) self.params.param('Recalculate Worldlines').sigActivated.connect(self.recalculate) self.params.param('Save').sigActivated.connect(self.save) self.params.param('Load').sigActivated.connect(self.load) self.params.param('Load Preset..').sigValueChanged.connect(self.loadPreset) self.params.sigTreeStateChanged.connect(self.treeChanged) ## read list of preset configs presetDir = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'presets') if os.path.exists(presetDir): presets = [os.path.splitext(p)[0] for p in os.listdir(presetDir)] self.params.param('Load Preset..').setLimits(['']+presets) def setupGUI(self): self.layout = QtWidgets.QVBoxLayout() self.layout.setContentsMargins(0,0,0,0) self.setLayout(self.layout) self.splitter = QtWidgets.QSplitter() self.splitter.setOrientation(QtCore.Qt.Orientation.Horizontal) self.layout.addWidget(self.splitter) self.tree = ParameterTree(showHeader=False) self.splitter.addWidget(self.tree) self.splitter2 = QtWidgets.QSplitter() self.splitter2.setOrientation(QtCore.Qt.Orientation.Vertical) self.splitter.addWidget(self.splitter2) self.worldlinePlots = pg.GraphicsLayoutWidget() self.splitter2.addWidget(self.worldlinePlots) self.animationPlots = pg.GraphicsLayoutWidget() self.splitter2.addWidget(self.animationPlots) self.splitter2.setSizes([int(self.height()*0.8), int(self.height()*0.2)]) self.inertWorldlinePlot = self.worldlinePlots.addPlot() self.refWorldlinePlot = self.worldlinePlots.addPlot() self.inertAnimationPlot = self.animationPlots.addPlot() self.inertAnimationPlot.setAspectLocked(1) self.refAnimationPlot = self.animationPlots.addPlot() self.refAnimationPlot.setAspectLocked(1) self.inertAnimationPlot.setXLink(self.inertWorldlinePlot) self.refAnimationPlot.setXLink(self.refWorldlinePlot) def recalculate(self): ## build 2 sets of clocks clocks1 = collections.OrderedDict() clocks2 = collections.OrderedDict() for cl in self.params.param('Objects'): clocks1.update(cl.buildClocks()) clocks2.update(cl.buildClocks()) ## Inertial simulation dt = self.animDt * self.params['Animation Speed'] sim1 = Simulation(clocks1, ref=None, duration=self.params['Duration'], dt=dt) sim1.run() sim1.plot(self.inertWorldlinePlot) self.inertWorldlinePlot.autoRange(padding=0.1) ## reference simulation ref = self.params['Reference Frame'] dur = clocks1[ref].refData['pt'][-1] ## decide how long to run the reference simulation sim2 = Simulation(clocks2, ref=clocks2[ref], duration=dur, dt=dt) sim2.run() sim2.plot(self.refWorldlinePlot) self.refWorldlinePlot.autoRange(padding=0.1) ## create animations self.refAnimationPlot.clear() self.inertAnimationPlot.clear() self.animTime = 0 self.animations = [Animation(sim1), Animation(sim2)] self.inertAnimationPlot.addItem(self.animations[0]) self.refAnimationPlot.addItem(self.animations[1]) ## create lines representing all that is visible to a particular reference #self.inertSpaceline = Spaceline(sim1, ref) #self.refSpaceline = Spaceline(sim2) self.inertWorldlinePlot.addItem(self.animations[0].items[ref].spaceline()) self.refWorldlinePlot.addItem(self.animations[1].items[ref].spaceline()) def setAnimation(self, a): if a: self.lastAnimTime = perf_counter() self.animTimer.start(int(self.animDt*1000)) else: self.animTimer.stop() def stepAnimation(self): now = perf_counter() dt = (now-self.lastAnimTime) * self.params['Animation Speed'] self.lastAnimTime = now self.animTime += dt if self.animTime > self.params['Duration']: self.animTime = 0 for a in self.animations: a.restart() for a in self.animations: a.stepTo(self.animTime) def treeChanged(self, *args): clocks = [] for c in self.params.param('Objects'): clocks.extend(c.clockNames()) #for param, change, data in args[1]: #if change == 'childAdded': self.params.param('Reference Frame').setLimits(clocks) self.setAnimation(self.params['Animate']) def save(self): filename = pg.QtWidgets.QFileDialog.getSaveFileName(self, "Save State..", "untitled.cfg", "Config Files (*.cfg)") if isinstance(filename, tuple): filename = filename[0] # Qt4/5 API difference if filename == '': return state = self.params.saveState() configfile.writeConfigFile(state, str(filename)) def load(self): filename = pg.QtWidgets.QFileDialog.getOpenFileName(self, "Save State..", "", "Config Files (*.cfg)") if isinstance(filename, tuple): filename = filename[0] # Qt4/5 API difference if filename == '': return state = configfile.readConfigFile(str(filename)) self.loadState(state) def loadPreset(self, param, preset): if preset == '': return path = os.path.abspath(os.path.dirname(__file__)) fn = os.path.join(path, 'presets', preset+".cfg") state = configfile.readConfigFile(fn) self.loadState(state) def loadState(self, state): if 'Load Preset..' in state['children']: del state['children']['Load Preset..']['limits'] del state['children']['Load Preset..']['value'] self.params.param('Objects').clearChildren() self.params.restoreState(state, removeChildren=False) self.recalculate() class ObjectGroupParam(pTypes.GroupParameter): def __init__(self): pTypes.GroupParameter.__init__(self, name="Objects", addText="Add New..", addList=['Clock', 'Grid']) def addNew(self, typ): if typ == 'Clock': self.addChild(ClockParam()) elif typ == 'Grid': self.addChild(GridParam()) class ClockParam(pTypes.GroupParameter): def __init__(self, **kwds): defs = dict(name="Clock", autoIncrementName=True, renamable=True, removable=True, children=[ dict(name='Initial Position', type='float', value=0.0, step=0.1), #dict(name='V0', type='float', value=0.0, step=0.1), AccelerationGroup(), dict(name='Rest Mass', type='float', value=1.0, step=0.1, limits=[1e-9, None]), dict(name='Color', type='color', value=(100,100,150)), dict(name='Size', type='float', value=0.5), dict(name='Vertical Position', type='float', value=0.0, step=0.1), ]) #defs.update(kwds) pTypes.GroupParameter.__init__(self, **defs) self.restoreState(kwds, removeChildren=False) def buildClocks(self): x0 = self['Initial Position'] y0 = self['Vertical Position'] color = self['Color'] m = self['Rest Mass'] size = self['Size'] prog = self.param('Acceleration').generate() c = Clock(x0=x0, m0=m, y0=y0, color=color, prog=prog, size=size) return {self.name(): c} def clockNames(self): return [self.name()] pTypes.registerParameterType('Clock', ClockParam) class GridParam(pTypes.GroupParameter): def __init__(self, **kwds): defs = dict(name="Grid", autoIncrementName=True, renamable=True, removable=True, children=[ dict(name='Number of Clocks', type='int', value=5, limits=[1, None]), dict(name='Spacing', type='float', value=1.0, step=0.1), ClockParam(name='ClockTemplate'), ]) #defs.update(kwds) pTypes.GroupParameter.__init__(self, **defs) self.restoreState(kwds, removeChildren=False) def buildClocks(self): clocks = {} template = self.param('ClockTemplate') spacing = self['Spacing'] for i in range(self['Number of Clocks']): c = list(template.buildClocks().values())[0] c.x0 += i * spacing clocks[self.name() + '%02d' % i] = c return clocks def clockNames(self): return [self.name() + '%02d' % i for i in range(self['Number of Clocks'])] pTypes.registerParameterType('Grid', GridParam) class AccelerationGroup(pTypes.GroupParameter): def __init__(self, **kwds): defs = dict(name="Acceleration", addText="Add Command..") pTypes.GroupParameter.__init__(self, **defs) self.restoreState(kwds, removeChildren=False) def addNew(self): nextTime = 0.0 if self.hasChildren(): nextTime = self.children()[-1]['Proper Time'] + 1 self.addChild(Parameter.create(name='Command', autoIncrementName=True, type=None, renamable=True, removable=True, children=[ dict(name='Proper Time', type='float', value=nextTime), dict(name='Acceleration', type='float', value=0.0, step=0.1), ])) def generate(self): prog = [] for cmd in self: prog.append((cmd['Proper Time'], cmd['Acceleration'])) return prog pTypes.registerParameterType('AccelerationGroup', AccelerationGroup) class Clock(object): nClocks = 0 def __init__(self, x0=0.0, y0=0.0, m0=1.0, v0=0.0, t0=0.0, color=None, prog=None, size=0.5): Clock.nClocks += 1 self.pen = pg.mkPen(color) self.brush = pg.mkBrush(color) self.y0 = y0 self.x0 = x0 self.v0 = v0 self.m0 = m0 self.t0 = t0 self.prog = prog self.size = size def init(self, nPts): ## Keep records of object from inertial frame as well as reference frame self.inertData = np.empty(nPts, dtype=[('x', float), ('t', float), ('v', float), ('pt', float), ('m', float), ('f', float)]) self.refData = np.empty(nPts, dtype=[('x', float), ('t', float), ('v', float), ('pt', float), ('m', float), ('f', float)]) ## Inertial frame variables self.x = self.x0 self.v = self.v0 self.m = self.m0 self.t = 0.0 ## reference clock always starts at 0 self.pt = self.t0 ## proper time starts at t0 ## reference frame variables self.refx = None self.refv = None self.refm = None self.reft = None self.recordFrame(0) def recordFrame(self, i): f = self.force() self.inertData[i] = (self.x, self.t, self.v, self.pt, self.m, f) self.refData[i] = (self.refx, self.reft, self.refv, self.pt, self.refm, f) def force(self, t=None): if len(self.prog) == 0: return 0.0 if t is None: t = self.pt ret = 0.0 for t1,f in self.prog: if t >= t1: ret = f return ret def acceleration(self, t=None): return self.force(t) / self.m0 def accelLimits(self): ## return the proper time values which bound the current acceleration command if len(self.prog) == 0: return -np.inf, np.inf t = self.pt ind = -1 for i, v in enumerate(self.prog): t1,f = v if t >= t1: ind = i if ind == -1: return -np.inf, self.prog[0][0] elif ind == len(self.prog)-1: return self.prog[-1][0], np.inf else: return self.prog[ind][0], self.prog[ind+1][0] def getCurve(self, ref=True): if ref is False: data = self.inertData else: data = self.refData[1:] x = data['x'] y = data['t'] curve = pg.PlotCurveItem(x=x, y=y, pen=self.pen) #x = self.data['x'] - ref.data['x'] #y = self.data['t'] step = 1.0 #mod = self.data['pt'] % step #inds = np.argwhere(abs(mod[1:] - mod[:-1]) > step*0.9) inds = [0] pt = data['pt'] for i in range(1,len(pt)): diff = pt[i] - pt[inds[-1]] if abs(diff) >= step: inds.append(i) inds = np.array(inds) #t = self.data['t'][inds] #x = self.data['x'][inds] pts = [] for i in inds: x = data['x'][i] y = data['t'][i] if i+1 < len(data): dpt = data['pt'][i+1]-data['pt'][i] dt = data['t'][i+1]-data['t'][i] else: dpt = 1 if dpt > 0: c = pg.mkBrush((0,0,0)) else: c = pg.mkBrush((200,200,200)) pts.append({'pos': (x, y), 'brush': c}) points = pg.ScatterPlotItem(pts, pen=self.pen, size=7) return curve, points class Simulation: def __init__(self, clocks, ref, duration, dt): self.clocks = clocks self.ref = ref self.duration = duration self.dt = dt @staticmethod def hypTStep(dt, v0, x0, tau0, g): ## Hyperbolic step. ## If an object has proper acceleration g and starts at position x0 with speed v0 and proper time tau0 ## as seen from an inertial frame, then return the new v, x, tau after time dt has elapsed. if g == 0: return v0, x0 + v0*dt, tau0 + dt * (1. - v0**2)**0.5 v02 = v0**2 g2 = g**2 tinit = v0 / (g * (1 - v02)**0.5) B = (1 + (g2 * (dt+tinit)**2))**0.5 v1 = g * (dt+tinit) / B dtau = (np.arcsinh(g * (dt+tinit)) - np.arcsinh(g * tinit)) / g tau1 = tau0 + dtau x1 = x0 + (1.0 / g) * ( B - 1. / (1.-v02)**0.5 ) return v1, x1, tau1 @staticmethod def tStep(dt, v0, x0, tau0, g): ## Linear step. ## Probably not as accurate as hyperbolic step, but certainly much faster. gamma = (1. - v0**2)**-0.5 dtau = dt / gamma return v0 + dtau * g, x0 + v0*dt, tau0 + dtau @staticmethod def tauStep(dtau, v0, x0, t0, g): ## linear step in proper time of clock. ## If an object has proper acceleration g and starts at position x0 with speed v0 at time t0 ## as seen from an inertial frame, then return the new v, x, t after proper time dtau has elapsed. ## Compute how much t will change given a proper-time step of dtau gamma = (1. - v0**2)**-0.5 if g == 0: dt = dtau * gamma else: v0g = v0 * gamma dt = (np.sinh(dtau * g + np.arcsinh(v0g)) - v0g) / g #return v0 + dtau * g, x0 + v0*dt, t0 + dt v1, x1, t1 = Simulation.hypTStep(dt, v0, x0, t0, g) return v1, x1, t0+dt @staticmethod def hypIntersect(x0r, t0r, vr, x0, t0, v0, g): ## given a reference clock (seen from inertial frame) has rx, rt, and rv, ## and another clock starts at x0, t0, and v0, with acceleration g, ## compute the intersection time of the object clock's hyperbolic path with ## the reference plane. ## I'm sure we can simplify this... if g == 0: ## no acceleration, path is linear (and hyperbola is undefined) #(-t0r + t0 v0 vr - vr x0 + vr x0r)/(-1 + v0 vr) t = (-t0r + t0 *v0 *vr - vr *x0 + vr *x0r)/(-1 + v0 *vr) return t gamma = (1.0-v0**2)**-0.5 sel = (1 if g>0 else 0) + (1 if vr<0 else 0) sel = sel%2 if sel == 0: #(1/(g^2 (-1 + vr^2)))(-g^2 t0r + g gamma vr + g^2 t0 vr^2 - #g gamma v0 vr^2 - g^2 vr x0 + #g^2 vr x0r + \[Sqrt](g^2 vr^2 (1 + gamma^2 (v0 - vr)^2 - vr^2 + #2 g gamma (v0 - vr) (-t0 + t0r + vr (x0 - x0r)) + #g^2 (t0 - t0r + vr (-x0 + x0r))^2))) t = (1./(g**2 *(-1. + vr**2)))*(-g**2 *t0r + g *gamma *vr + g**2 *t0 *vr**2 - g *gamma *v0 *vr**2 - g**2 *vr *x0 + g**2 *vr *x0r + np.sqrt(g**2 *vr**2 *(1. + gamma**2 *(v0 - vr)**2 - vr**2 + 2 *g *gamma *(v0 - vr)* (-t0 + t0r + vr *(x0 - x0r)) + g**2 *(t0 - t0r + vr* (-x0 + x0r))**2))) else: #-(1/(g^2 (-1 + vr^2)))(g^2 t0r - g gamma vr - g^2 t0 vr^2 + #g gamma v0 vr^2 + g^2 vr x0 - #g^2 vr x0r + \[Sqrt](g^2 vr^2 (1 + gamma^2 (v0 - vr)^2 - vr^2 + #2 g gamma (v0 - vr) (-t0 + t0r + vr (x0 - x0r)) + #g^2 (t0 - t0r + vr (-x0 + x0r))^2))) t = -(1./(g**2 *(-1. + vr**2)))*(g**2 *t0r - g *gamma* vr - g**2 *t0 *vr**2 + g *gamma *v0 *vr**2 + g**2* vr* x0 - g**2 *vr *x0r + np.sqrt(g**2* vr**2 *(1. + gamma**2 *(v0 - vr)**2 - vr**2 + 2 *g *gamma *(v0 - vr) *(-t0 + t0r + vr *(x0 - x0r)) + g**2 *(t0 - t0r + vr *(-x0 + x0r))**2))) return t def run(self): nPts = int(self.duration/self.dt)+1 for cl in self.clocks.values(): cl.init(nPts) if self.ref is None: self.runInertial(nPts) else: self.runReference(nPts) def runInertial(self, nPts): clocks = self.clocks dt = self.dt tVals = np.linspace(0, dt*(nPts-1), nPts) for cl in self.clocks.values(): for i in range(1,nPts): nextT = tVals[i] while True: tau1, tau2 = cl.accelLimits() x = cl.x v = cl.v tau = cl.pt g = cl.acceleration() v1, x1, tau1 = self.hypTStep(dt, v, x, tau, g) if tau1 > tau2: dtau = tau2-tau cl.v, cl.x, cl.t = self.tauStep(dtau, v, x, cl.t, g) cl.pt = tau2 else: cl.v, cl.x, cl.pt = v1, x1, tau1 cl.t += dt if cl.t >= nextT: cl.refx = cl.x cl.refv = cl.v cl.reft = cl.t cl.recordFrame(i) break def runReference(self, nPts): clocks = self.clocks ref = self.ref dt = self.dt dur = self.duration ## make sure reference clock is not present in the list of clocks--this will be handled separately. clocks = clocks.copy() for k,v in clocks.items(): if v is ref: del clocks[k] break ref.refx = 0 ref.refv = 0 ref.refm = ref.m0 ## These are the set of proper times (in the reference frame) that will be simulated ptVals = np.linspace(ref.pt, ref.pt + dt*(nPts-1), nPts) for i in 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 while True: tau1, tau2 = ref.accelLimits() dtau = min(nextPt-ref.pt, tau2-ref.pt) ## do not step past the next command boundary g = ref.acceleration() v, x, t = Simulation.tauStep(dtau, ref.v, ref.x, ref.t, g) ref.pt += dtau ref.v = v ref.x = x ref.t = t ref.reft = ref.pt if ref.pt >= nextPt: break #else: #print "Stepped to", tau2, "instead of", nextPt ref.recordFrame(i) ## determine plane visible to reference clock ## this plane goes through the point ref.x, ref.t and has slope = ref.v ## update all other clocks for cl in clocks.values(): while True: g = cl.acceleration() tau1, tau2 = cl.accelLimits() ##Given current position / speed of clock, determine where it will intersect reference plane #t1 = (ref.v * (cl.x - cl.v * cl.t) + (ref.t - ref.v * ref.x)) / (1. - cl.v) t1 = Simulation.hypIntersect(ref.x, ref.t, ref.v, cl.x, cl.t, cl.v, g) dt1 = t1 - cl.t ## advance clock by correct time step v, x, tau = Simulation.hypTStep(dt1, cl.v, cl.x, cl.pt, g) ## check to see whether we have gone past an acceleration command boundary. ## if so, we must instead advance the clock to the boundary and start again if tau < tau1: dtau = tau1 - cl.pt cl.v, cl.x, cl.t = Simulation.tauStep(dtau, cl.v, cl.x, cl.t, g) cl.pt = tau1-0.000001 continue if tau > tau2: dtau = tau2 - cl.pt cl.v, cl.x, cl.t = Simulation.tauStep(dtau, cl.v, cl.x, cl.t, g) cl.pt = tau2 continue ## Otherwise, record the new values and exit the loop cl.v = v cl.x = x cl.pt = tau cl.t = t1 cl.m = None break ## transform position into reference frame x = cl.x - ref.x t = cl.t - ref.t gamma = (1.0 - ref.v**2) ** -0.5 vg = -ref.v * gamma cl.refx = gamma * (x - ref.v * t) cl.reft = ref.pt # + gamma * (t - ref.v * x) # this term belongs here, but it should always be equal to 0. cl.refv = (cl.v - ref.v) / (1.0 - cl.v * ref.v) cl.refm = None cl.recordFrame(i) t += dt def plot(self, plot): plot.clear() for cl in self.clocks.values(): c, p = cl.getCurve() plot.addItem(c) plot.addItem(p) class Animation(pg.ItemGroup): def __init__(self, sim): pg.ItemGroup.__init__(self) self.sim = sim self.clocks = sim.clocks self.items = {} for name, cl in self.clocks.items(): item = ClockItem(cl) self.addItem(item) self.items[name] = item def restart(self): for cl in self.items.values(): cl.reset() def stepTo(self, t): for i in self.items.values(): i.stepTo(t) class ClockItem(pg.ItemGroup): def __init__(self, clock): pg.ItemGroup.__init__(self) self.size = clock.size self.item = QtWidgets.QGraphicsEllipseItem(QtCore.QRectF(0, 0, self.size, self.size)) tr = QtGui.QTransform.fromTranslate(-self.size*0.5, -self.size*0.5) self.item.setTransform(tr) self.item.setPen(pg.mkPen(100,100,100)) self.item.setBrush(clock.brush) self.hand = QtWidgets.QGraphicsLineItem(0, 0, 0, self.size*0.5) self.hand.setPen(pg.mkPen('w')) self.hand.setZValue(10) self.flare = QtWidgets.QGraphicsPolygonItem(QtGui.QPolygonF([ QtCore.QPointF(0, -self.size*0.25), QtCore.QPointF(0, self.size*0.25), QtCore.QPointF(self.size*1.5, 0), QtCore.QPointF(0, -self.size*0.25), ])) self.flare.setPen(pg.mkPen('y')) self.flare.setBrush(pg.mkBrush(255,150,0)) self.flare.setZValue(-10) self.addItem(self.hand) self.addItem(self.item) self.addItem(self.flare) self.clock = clock self.i = 1 self._spaceline = None def spaceline(self): if self._spaceline is None: self._spaceline = pg.InfiniteLine() self._spaceline.setPen(self.clock.pen) return self._spaceline def stepTo(self, t): data = self.clock.refData while self.i < len(data)-1 and data['t'][self.i] < t: self.i += 1 while self.i > 1 and data['t'][self.i-1] >= t: self.i -= 1 self.setPos(data['x'][self.i], self.clock.y0) t = data['pt'][self.i] self.hand.setRotation(-0.25 * t * 360.) v = data['v'][self.i] gam = (1.0 - v**2)**0.5 self.setTransform(QtGui.QTransform.fromScale(gam, 1.0)) f = data['f'][self.i] tr = QtGui.QTransform() if f < 0: tr.translate(self.size*0.4, 0) else: tr.translate(-self.size*0.4, 0) tr.scale(-f * (0.5+np.random.random()*0.1), 1.0) self.flare.setTransform(tr) if self._spaceline is not None: self._spaceline.setPos(pg.Point(data['x'][self.i], data['t'][self.i])) self._spaceline.setAngle(data['v'][self.i] * 45.) def reset(self): self.i = 1 #class Spaceline(pg.InfiniteLine): #def __init__(self, sim, frame): #self.sim = sim #self.frame = frame #pg.InfiniteLine.__init__(self) #self.setPen(sim.clocks[frame].pen) #def stepTo(self, t): #self.setAngle(0) #pass if __name__ == '__main__': app = pg.mkQApp() #import pyqtgraph.console #cw = pyqtgraph.console.ConsoleWidget() #cw.show() #cw.catchNextException() win = RelativityGUI() win.setWindowTitle("Relativity!") win.show() win.resize(1100,700) pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/relativity_demo.py000066400000000000000000000004401421045507400252410ustar00rootroot00000000000000""" Special relativity simulation """ from relativity import RelativityGUI import pyqtgraph as pg pg.mkQApp() win = RelativityGUI() win.setWindowTitle("Relativity!") win.resize(1100,700) win.show() win.loadPreset(None, 'Twin Paradox (grid)') if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/scrollingPlots.py000066400000000000000000000051651421045507400250700ustar00rootroot00000000000000""" Various methods of drawing scrolling plots. """ from time import perf_counter import numpy as np import pyqtgraph as pg win = pg.GraphicsLayoutWidget(show=True) win.setWindowTitle('pyqtgraph example: Scrolling Plots') # 1) Simplest approach -- update data in the array such that plot appears to scroll # In these examples, the array size is fixed. p1 = win.addPlot() p2 = win.addPlot() data1 = np.random.normal(size=300) curve1 = p1.plot(data1) curve2 = p2.plot(data1) ptr1 = 0 def update1(): global data1, ptr1 data1[:-1] = data1[1:] # shift data in the array one sample left # (see also: np.roll) data1[-1] = np.random.normal() curve1.setData(data1) ptr1 += 1 curve2.setData(data1) curve2.setPos(ptr1, 0) # 2) Allow data to accumulate. In these examples, the array doubles in length # whenever it is full. win.nextRow() p3 = win.addPlot() p4 = win.addPlot() # Use automatic downsampling and clipping to reduce the drawing load p3.setDownsampling(mode='peak') p4.setDownsampling(mode='peak') p3.setClipToView(True) p4.setClipToView(True) p3.setRange(xRange=[-100, 0]) p3.setLimits(xMax=0) curve3 = p3.plot() curve4 = p4.plot() data3 = np.empty(100) ptr3 = 0 def update2(): global data3, ptr3 data3[ptr3] = np.random.normal() ptr3 += 1 if ptr3 >= data3.shape[0]: tmp = data3 data3 = np.empty(data3.shape[0] * 2) data3[:tmp.shape[0]] = tmp curve3.setData(data3[:ptr3]) curve3.setPos(-ptr3, 0) curve4.setData(data3[:ptr3]) # 3) Plot in chunks, adding one new plot curve for every 100 samples chunkSize = 100 # Remove chunks after we have 10 maxChunks = 10 startTime = perf_counter() win.nextRow() p5 = win.addPlot(colspan=2) p5.setLabel('bottom', 'Time', 's') p5.setXRange(-10, 0) curves = [] data5 = np.empty((chunkSize+1,2)) ptr5 = 0 def update3(): global p5, data5, ptr5, curves now = perf_counter() for c in curves: c.setPos(-(now-startTime), 0) i = ptr5 % chunkSize if i == 0: curve = p5.plot() curves.append(curve) last = data5[-1] data5 = np.empty((chunkSize+1,2)) data5[0] = last while len(curves) > maxChunks: c = curves.pop(0) p5.removeItem(c) else: curve = curves[-1] data5[i+1,0] = now - startTime data5[i+1,1] = np.random.normal() curve.setData(x=data5[:i+2, 0], y=data5[:i+2, 1]) ptr5 += 1 # update all plots def update(): update1() update2() update3() timer = pg.QtCore.QTimer() timer.timeout.connect(update) timer.start(50) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/syntax.py000066400000000000000000000175241421045507400234020ustar00rootroot00000000000000# based on https://github.com/art1415926535/PyQt5-syntax-highlighting from pyqtgraph.Qt import QtCore, QtGui, QtWidgets QRegExp = QtCore.QRegExp QFont = QtGui.QFont QColor = QtGui.QColor QTextCharFormat = QtGui.QTextCharFormat QSyntaxHighlighter = QtGui.QSyntaxHighlighter def format(color, style=''): """ Return a QTextCharFormat with the given attributes. """ _color = QColor() if type(color) is not str: _color.setRgb(color[0], color[1], color[2]) else: _color.setNamedColor(color) _format = QTextCharFormat() _format.setForeground(_color) if 'bold' in style: _format.setFontWeight(QFont.Weight.Bold) if 'italic' in style: _format.setFontItalic(True) return _format class LightThemeColors: Red = "#B71C1C" Pink = "#FCE4EC" Purple = "#4A148C" DeepPurple = "#311B92" Indigo = "#1A237E" Blue = "#0D47A1" LightBlue = "#01579B" Cyan = "#006064" Teal = "#004D40" Green = "#1B5E20" LightGreen = "#33691E" Lime = "#827717" Yellow = "#F57F17" Amber = "#FF6F00" Orange = "#E65100" DeepOrange = "#BF360C" Brown = "#3E2723" Grey = "#212121" BlueGrey = "#263238" class DarkThemeColors: Red = "#F44336" Pink = "#F48FB1" Purple = "#CE93D8" DeepPurple = "#B39DDB" Indigo = "#9FA8DA" Blue = "#90CAF9" LightBlue = "#81D4FA" Cyan = "#80DEEA" Teal = "#80CBC4" Green = "#A5D6A7" LightGreen = "#C5E1A5" Lime = "#E6EE9C" Yellow = "#FFF59D" Amber = "#FFE082" Orange = "#FFCC80" DeepOrange = "#FFAB91" Brown = "#BCAAA4" Grey = "#EEEEEE" BlueGrey = "#B0BEC5" LIGHT_STYLES = { 'keyword': format(LightThemeColors.Blue, 'bold'), 'operator': format(LightThemeColors.Red, 'bold'), 'brace': format(LightThemeColors.Purple), 'defclass': format(LightThemeColors.Indigo, 'bold'), 'string': format(LightThemeColors.Amber), 'string2': format(LightThemeColors.DeepPurple), 'comment': format(LightThemeColors.Green, 'italic'), 'self': format(LightThemeColors.Blue, 'bold'), 'numbers': format(LightThemeColors.Teal), } DARK_STYLES = { 'keyword': format(DarkThemeColors.Blue, 'bold'), 'operator': format(DarkThemeColors.Red, 'bold'), 'brace': format(DarkThemeColors.Purple), 'defclass': format(DarkThemeColors.Indigo, 'bold'), 'string': format(DarkThemeColors.Amber), 'string2': format(DarkThemeColors.DeepPurple), 'comment': format(DarkThemeColors.Green, 'italic'), 'self': format(DarkThemeColors.Blue, 'bold'), 'numbers': format(DarkThemeColors.Teal), } class PythonHighlighter(QSyntaxHighlighter): """Syntax highlighter for the Python language. """ # Python keywords keywords = [ 'and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'yield', 'None', 'True', 'False', 'async', 'await', ] # Python operators operators = [ r'=', # Comparison r'==', r'!=', r'<', r'<=', r'>', r'>=', # Arithmetic r'\+', r'-', r'\*', r'/', r'//', r'\%', r'\*\*', # In-place r'\+=', r'-=', r'\*=', r'/=', r'\%=', # Bitwise r'\^', r'\|', r'\&', r'\~', r'>>', r'<<', ] # Python braces braces = [ r'\{', r'\}', r'\(', r'\)', r'\[', r'\]', ] def __init__(self, document): QSyntaxHighlighter.__init__(self, document) # Multi-line strings (expression, flag, style) # FIXME: The triple-quotes in these two lines will mess up the # syntax highlighting from this point onward self.tri_single = (QRegExp("'''"), 1, 'string2') self.tri_double = (QRegExp('"""'), 2, 'string2') rules = [] # Keyword, operator, and brace rules rules += [(r'\b%s\b' % w, 0, 'keyword') for w in PythonHighlighter.keywords] rules += [(r'%s' % o, 0, 'operator') for o in PythonHighlighter.operators] rules += [(r'%s' % b, 0, 'brace') for b in PythonHighlighter.braces] # All other rules rules += [ # 'self' (r'\bself\b', 0, 'self'), # 'def' followed by an identifier (r'\bdef\b\s*(\w+)', 1, 'defclass'), # 'class' followed by an identifier (r'\bclass\b\s*(\w+)', 1, 'defclass'), # Numeric literals (r'\b[+-]?[0-9]+[lL]?\b', 0, 'numbers'), (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, 'numbers'), (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, 'numbers'), # Double-quoted string, possibly containing escape sequences (r'"[^"\\]*(\\.[^"\\]*)*"', 0, 'string'), # Single-quoted string, possibly containing escape sequences (r"'[^'\\]*(\\.[^'\\]*)*'", 0, 'string'), # From '#' until a newline (r'#[^\n]*', 0, 'comment'), ] # Build a QRegExp for each pattern self.rules = [(QRegExp(pat), index, fmt) for (pat, index, fmt) in rules] @property def styles(self): app = QtWidgets.QApplication.instance() return DARK_STYLES if app.property('darkMode') else LIGHT_STYLES def highlightBlock(self, text): """Apply syntax highlighting to the given block of text. """ # Do other syntax formatting for expression, nth, format in self.rules: index = expression.indexIn(text, 0) format = self.styles[format] while index >= 0: # We actually want the index of the nth match index = expression.pos(nth) length = len(expression.cap(nth)) self.setFormat(index, length, format) index = expression.indexIn(text, index + length) self.setCurrentBlockState(0) # Do multi-line strings in_multiline = self.match_multiline(text, *self.tri_single) if not in_multiline: in_multiline = self.match_multiline(text, *self.tri_double) def match_multiline(self, text, delimiter, in_state, style): """Do highlighting of multi-line strings. ``delimiter`` should be a ``QRegExp`` for triple-single-quotes or triple-double-quotes, and ``in_state`` should be a unique integer to represent the corresponding state changes when inside those strings. Returns True if we're still inside a multi-line string when this function is finished. """ # If inside triple-single quotes, start at 0 if self.previousBlockState() == in_state: start = 0 add = 0 # Otherwise, look for the delimiter on this line else: start = delimiter.indexIn(text) # Move past this match add = delimiter.matchedLength() # As long as there's a delimiter match on this line... while start >= 0: # Look for the ending delimiter end = delimiter.indexIn(text, start + add) # Ending delimiter on this line? if end >= add: length = end - start + add + delimiter.matchedLength() self.setCurrentBlockState(0) # No; multi-line string else: self.setCurrentBlockState(in_state) length = len(text) - start + add # Apply formatting self.setFormat(start, length, self.styles[style]) # Look for the next match start = delimiter.indexIn(text, start + length) # Return True if still inside a multi-line string, False otherwise if self.currentBlockState() == in_state: return True else: return False pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/template.py000066400000000000000000000003601421045507400236550ustar00rootroot00000000000000""" Description of example """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, mkQApp app = mkQApp() # win.setWindowTitle('pyqtgraph example: ____') if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/test_examples.py000066400000000000000000000134511421045507400247240ustar00rootroot00000000000000import contextlib import errno import importlib import itertools import os import platform import subprocess import sys import time from argparse import Namespace from collections import namedtuple import pytest from pyqtgraph import Qt if __name__ == "__main__" and (__package__ is None or __package__==''): parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, parent_dir) import examples __package__ = "examples" from . import utils def buildFileList(examples, files=None): if files is None: files = [] for key, val in examples.items(): if isinstance(val, dict): buildFileList(val, files) elif isinstance(val, Namespace): files.append((key, val.filename)) else: files.append((key, val)) return files path = os.path.abspath(os.path.dirname(__file__)) files = [("Example App", "RunExampleApp.py")] for ex in [utils.examples_, utils.others]: files = buildFileList(ex, files) files = sorted(set(files)) frontends = { Qt.PYQT5: False, Qt.PYQT6: False, Qt.PYSIDE2: False, Qt.PYSIDE6: False, } # sort out which of the front ends are available for frontend in frontends.keys(): with contextlib.suppress(ImportError): importlib.import_module(frontend) frontends[frontend] = True installedFrontends = sorted([ frontend for frontend, isPresent in frontends.items() if isPresent ]) darwin_opengl_broken = (platform.system() == "Darwin" and tuple(map(int, platform.mac_ver()[0].split("."))) >= (10, 16) and (sys.version_info < (3, 8, 10) or sys.version_info == (3, 9, 0))) darwin_opengl_reason = ("pyopenGL cannot find openGL library on big sur: " "https://github.com/python/cpython/pull/21241") exceptionCondition = namedtuple("exceptionCondition", ["condition", "reason"]) conditionalExamples = { "hdf5.py": exceptionCondition( False, reason="Example requires user interaction" ), "RemoteSpeedTest.py": exceptionCondition( False, reason="Test is being problematic on CI machines" ), } 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" and tuple(map(int, Qt.PySide2.__version__.split("."))) >= (5, 14) and tuple(map(int, Qt.PySide2.__version__.split("."))) < (5, 14, 2, 2), reason="new PySide2 doesn't have loadUi functionality" ) @pytest.mark.parametrize( "frontend, f", [ pytest.param( frontend, f, marks=pytest.mark.skipif( conditionalExamples[f[1]].condition is False, reason=conditionalExamples[f[1]].reason ) if f[1] in conditionalExamples.keys() else (), ) for frontend, f, in itertools.product(installedFrontends, files) ], ids = [ " {} - {} ".format(f[1], frontend) for frontend, f in itertools.product( installedFrontends, files ) ] ) def testExamples(frontend, f): name, file = f global path fn = os.path.join(path, file) os.chdir(path) sys.stdout.write(f"{name}") sys.stdout.flush() import1 = "import %s" % frontend if frontend != '' else '' import2 = os.path.splitext(os.path.split(fn)[1])[0] code = """ try: {0} import faulthandler faulthandler.enable() import pyqtgraph as pg import {1} import sys print("test complete") sys.stdout.flush() pg.Qt.QtCore.QTimer.singleShot(1000, pg.Qt.QtWidgets.QApplication.quit) pg.exec() names = [x for x in dir({1}) if not x.startswith('_')] for name in names: delattr({1}, name) except: print("test failed") raise """.format(import1, import2) env = dict(os.environ) example_dir = os.path.abspath(os.path.dirname(__file__)) path = os.path.dirname(os.path.dirname(example_dir)) env['PYTHONPATH'] = f'{path}{os.pathsep}{example_dir}' process = subprocess.Popen([sys.executable], stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, env=env) process.stdin.write(code) process.stdin.close() output = '' fail = False while True: try: c = process.stdout.read(1) except IOError as err: if err.errno == errno.EINTR: # Interrupted system call; just try again. c = '' else: raise output += c if output.endswith('test complete'): break if output.endswith('test failed'): fail = True break start = time.time() killed = False while process.poll() is None: time.sleep(0.1) if time.time() - start > 2.0 and not killed: process.kill() killed = True stdout, stderr = (process.stdout.read(), process.stderr.read()) process.stdout.close() process.stderr.close() if (fail or 'Exception:' in stderr or 'Error:' in stderr): if (not fail and name == "RemoteGraphicsView" and "pyqtgraph.multiprocess.remoteproxy.ClosedError" in stderr): # This test can intermittently fail when the subprocess is killed return None print(stdout) print(stderr) pytest.fail( f"{stdout}\n{stderr}\nFailed {name} Example Test Located in {file}", pytrace=False ) if __name__ == "__main__": pytest.cmdline.main() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/text.py000066400000000000000000000032161421045507400230310ustar00rootroot00000000000000""" This example shows how to insert text into a scene using TextItem. This class is for displaying text that is anchored to a particular location in the data coordinate system, but which is always displayed unscaled. For text that scales with the data, use QTextItem. For text that can be placed in a layout, use LabelItem. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore x = np.linspace(-20, 20, 1000) y = np.sin(x) / x plot = pg.plot() ## create an empty plot widget plot.setYRange(-1, 2) plot.setWindowTitle('pyqtgraph example: text') curve = plot.plot(x,y) ## add a single curve ## Create text object, use HTML tags to specify color/size text = pg.TextItem(html='
      This is the
      PEAK
      ', anchor=(-0.3,0.5), angle=45, border='w', fill=(0, 0, 255, 100)) plot.addItem(text) text.setPos(0, y.max()) ## Draw an arrowhead next to the text box arrow = pg.ArrowItem(pos=(0, y.max()), angle=-45) plot.addItem(arrow) ## Set up an animated arrow and text that track the curve curvePoint = pg.CurvePoint(curve) plot.addItem(curvePoint) text2 = pg.TextItem("test", anchor=(0.5, -1.0)) text2.setParentItem(curvePoint) arrow2 = pg.ArrowItem(angle=90) arrow2.setParentItem(curvePoint) ## update position every 10ms index = 0 def update(): global curvePoint, index index = (index + 1) % len(x) curvePoint.setPos(float(index)/(len(x)-1)) text2.setText('[%0.1f, %0.1f]' % (x[index], y[index])) timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(10) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/utils.py000066400000000000000000000121121421045507400232000ustar00rootroot00000000000000from argparse import Namespace from collections import OrderedDict # Avoid clash with module name examples_ = OrderedDict([ ('Command-line usage', 'CLIexample.py'), ('Basic Plotting', Namespace(filename='Plotting.py', recommended=True)), ('ImageView', 'ImageView.py'), ('ParameterTree', 'parametertree.py'), ('Crosshair / Mouse interaction', 'crosshair.py'), ('Data Slicing', 'DataSlicing.py'), ('Plot Customization', 'customPlot.py'), ('Timestamps on x axis', 'DateAxisItem.py'), ('Image Analysis', 'imageAnalysis.py'), ('Matrix Display', 'MatrixDisplayExample.py'), ('ViewBox Features', Namespace(filename='ViewBoxFeatures.py', recommended=True)), ('Dock widgets', 'dockarea.py'), ('Console', 'ConsoleWidget.py'), ('Histograms', 'histogram.py'), ('Beeswarm plot', 'beeswarm.py'), ('Symbols', 'Symbols.py'), ('Auto-range', 'PlotAutoRange.py'), ('Remote Plotting', 'RemoteSpeedTest.py'), ('Scrolling plots', 'scrollingPlots.py'), ('HDF5 big data', 'hdf5.py'), ('Demos', OrderedDict([ ('Optics', 'optics_demos.py'), ('Special relativity', 'relativity_demo.py'), ('Verlet chain', 'verlet_chain_demo.py'), ('Koch Fractal', 'fractal.py'), ])), ('Colors', OrderedDict([ ('Color Maps', 'colorMaps.py'), ('Color Map Linearization', 'colorMapsLinearized.py'), ('Color Gradient Plots', 'ColorGradientPlots.py') ])), ('GraphicsItems', OrderedDict([ ('Scatter Plot', 'ScatterPlot.py'), #('PlotItem', 'PlotItem.py'), ('InfiniteLine', 'InfiniteLine.py'), ('IsocurveItem', 'isocurve.py'), ('GraphItem', 'GraphItem.py'), ('ErrorBarItem', 'ErrorBarItem.py'), ('FillBetweenItem', 'FillBetweenItem.py'), ('ImageItem - video', 'ImageItem.py'), ('ImageItem - draw', 'Draw.py'), ('ColorBarItem','ColorBarItem.py'), ('Non-uniform Image', 'NonUniformImage.py'), ('Region-of-Interest', 'ROIExamples.py'), ('Bar Graph', 'BarGraphItem.py'), ('GraphicsLayout', 'GraphicsLayout.py'), ('LegendItem', 'Legend.py'), ('Text Item', 'text.py'), ('Linked Views', 'linkedViews.py'), ('Arrow', 'Arrow.py'), ('ViewBox', 'ViewBoxFeatures.py'), ('Custom Graphics', 'customGraphicsItem.py'), ('Labeled Graph', 'CustomGraphItem.py'), ('PColorMeshItem', 'PColorMeshItem.py'), ])), ('Benchmarks', OrderedDict([ ('Video speed test', 'VideoSpeedTest.py'), ('Line Plot update', 'PlotSpeedTest.py'), ('Scatter Plot update', 'ScatterPlotSpeedTest.py'), ('Multiple plots', 'MultiPlotSpeedTest.py'), ])), ('3D Graphics', OrderedDict([ ('Volumetric', 'GLVolumeItem.py'), ('Isosurface', 'GLIsosurface.py'), ('Surface Plot', 'GLSurfacePlot.py'), ('Scatter Plot', 'GLScatterPlotItem.py'), ('Shaders', 'GLshaders.py'), ('Line Plot', 'GLLinePlotItem.py'), ('Mesh', 'GLMeshItem.py'), ('Image', 'GLImageItem.py'), ('Text', 'GLTextItem.py'), ('BarGraph', 'GLBarGraphItem.py'), ('Painter', 'GLPainterItem.py'), ('Gradient Legend', 'GLGradientLegendItem.py') ])), ('Widgets', OrderedDict([ ('PlotWidget', 'PlotWidget.py'), ('SpinBox', 'SpinBox.py'), ('ConsoleWidget', 'ConsoleWidget.py'), ('Histogram / lookup table', 'HistogramLUT.py'), ('TreeWidget', 'TreeWidget.py'), ('ScatterPlotWidget', 'ScatterPlotWidget.py'), ('DataTreeWidget', 'DataTreeWidget.py'), ('GradientWidget', 'GradientWidget.py'), ('TableWidget', 'TableWidget.py'), ('ColorButton', 'ColorButton.py'), #('CheckTable', '../widgets/CheckTable.py'), #('VerticalLabel', '../widgets/VerticalLabel.py'), ('JoystickButton', 'JoystickButton.py'), ])), ('Flowcharts', 'Flowchart.py'), ('Custom Flowchart Nodes', 'FlowchartCustomNode.py'), ]) # don't care about ordering # but actually from Python 3.7, dict is ordered others = dict([ ('logAxis', 'logAxis.py'), ('PanningPlot', 'PanningPlot.py'), ('MultiplePlotAxes', 'MultiplePlotAxes.py'), ('ROItypes', 'ROItypes.py'), ('ScaleBar', 'ScaleBar.py'), ('ViewBox', 'ViewBox.py'), ('GradientEditor', 'GradientEditor.py'), ('GLViewWidget', 'GLViewWidget.py'), ('DiffTreeWidget', 'DiffTreeWidget.py'), ('MultiPlotWidget', 'MultiPlotWidget.py'), ('RemoteGraphicsView', 'RemoteGraphicsView.py'), ('contextMenu', 'contextMenu.py'), ('designerExample', 'designerExample.py'), ('DateAxisItem_QtDesigner', 'DateAxisItem_QtDesigner.py'), ('GraphicsScene', 'GraphicsScene.py'), ('MouseSelection', 'MouseSelection.py'), ]) # examples that are subsumed into other examples trivial = dict([ ('SimplePlot', 'SimplePlot.py'), # Plotting.py ('LogPlotTest', 'LogPlotTest.py'), # Plotting.py ('ViewLimits', 'ViewLimits.py'), # ViewBoxFeatures.py ]) # examples that are not suitable for CI testing skiptest = dict([ ('ProgressDialog', 'ProgressDialog.py'), # modal dialog ]) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/verlet_chain/000077500000000000000000000000001421045507400241345ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/verlet_chain/__init__.py000066400000000000000000000000341421045507400262420ustar00rootroot00000000000000from .chain import ChainSim pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/verlet_chain/chain.py000066400000000000000000000070041421045507400255710ustar00rootroot00000000000000import time import numpy as np import pyqtgraph as pg from . import relax class ChainSim(pg.QtCore.QObject): stepped = pg.QtCore.Signal() relaxed = pg.QtCore.Signal() def __init__(self): pg.QtCore.QObject.__init__(self) self.damping = 0.1 # 0=full damping, 1=no damping self.relaxPerStep = 10 self.maxTimeStep = 0.01 self.pos = None # (Npts, 2) float self.mass = None # (Npts) float self.fixed = None # (Npts) bool self.links = None # (Nlinks, 2), uint self.lengths = None # (Nlinks), float self.push = None # (Nlinks), bool self.pull = None # (Nlinks), bool self.initialized = False self.lasttime = None self.lastpos = None def init(self): if self.initialized: return if self.fixed is None: self.fixed = np.zeros(self.pos.shape[0], dtype=bool) if self.push is None: self.push = np.ones(self.links.shape[0], dtype=bool) if self.pull is None: self.pull = np.ones(self.links.shape[0], dtype=bool) # precompute relative masses across links l1 = self.links[:,0] l2 = self.links[:,1] m1 = self.mass[l1] m2 = self.mass[l2] self.mrel1 = (m1 / (m1+m2))[:,np.newaxis] self.mrel1[self.fixed[l1]] = 1 # fixed point constraint self.mrel1[self.fixed[l2]] = 0 self.mrel2 = 1.0 - self.mrel1 for i in range(10): self.relax(n=10) self.initialized = True def makeGraph(self): #g1 = pg.GraphItem(pos=self.pos, adj=self.links[self.rope], pen=0.2, symbol=None) brushes = np.where(self.fixed, pg.mkBrush(0,0,0,255), pg.mkBrush(50,50,200,255)) g2 = pg.GraphItem(pos=self.pos, adj=self.links[self.push & self.pull], pen=0.5, brush=brushes, symbol='o', size=(self.mass**0.33), pxMode=False) p = pg.ItemGroup() #p.addItem(g1) p.addItem(g2) return p def update(self): # approximate physics with verlet integration now = time.perf_counter() if self.lasttime is None: dt = 0 else: dt = now - self.lasttime self.lasttime = now # limit amount of work to be done between frames if not relax.COMPILED: dt = self.maxTimeStep if self.lastpos is None: self.lastpos = self.pos # remember fixed positions fixedpos = self.pos[self.fixed] while dt > 0: dt1 = min(self.maxTimeStep, dt) dt -= dt1 # compute motion since last timestep dx = self.pos - self.lastpos self.lastpos = self.pos # update positions for gravity and inertia acc = np.array([[0, -5]]) * dt1 inertia = dx * (self.damping**(dt1/self.mass))[:,np.newaxis] # with mass-dependent damping self.pos = self.pos + inertia + acc self.pos[self.fixed] = fixedpos # fixed point constraint # correct for link constraints self.relax(self.relaxPerStep) self.stepped.emit() def relax(self, n=50): # speed up with C magic if possible relax.relax(self.pos, self.links, self.mrel1, self.mrel2, self.lengths, self.push, self.pull, n) self.relaxed.emit() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/verlet_chain/make000077500000000000000000000000651421045507400250000ustar00rootroot00000000000000gcc -fPIC -c relax.c gcc -shared -o maths.so relax.o pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/verlet_chain/relax.c000066400000000000000000000023121421045507400254110ustar00rootroot00000000000000#include #include void relax( double* pos, long* links, double* mrel1, double* mrel2, double* lengths, char* push, char* pull, int nlinks, int iters) { int i, l, p1, p2; double x1, x2, y1, y2, dx, dy, dist, change; // printf("%d, %d\n", iters, nlinks); for( i=0; i lengths[l] ) dist = lengths[l]; change = (lengths[l]-dist) / dist; dx *= change; dy *= change; pos[p1] -= mrel2[l] * dx; pos[p1+1] -= mrel2[l] * dy; pos[p2] += mrel1[l] * dx; pos[p2+1] += mrel1[l] * dy; } } } pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/verlet_chain/relax.py000066400000000000000000000041051421045507400256210ustar00rootroot00000000000000import ctypes import os so = os.path.join(os.path.dirname(__file__), 'maths.so') try: lib = ctypes.CDLL(so) COMPILED = True except OSError: COMPILED = False if COMPILED: lib.relax.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ] def relax(pos, links, mrel1, mrel2, lengths, push, pull, iters): nlinks = links.shape[0] lib.relax(pos.ctypes, links.ctypes, mrel1.ctypes, mrel2.ctypes, lengths.ctypes, push.ctypes, pull.ctypes, nlinks, iters) else: def relax(pos, links, mrel1, mrel2, lengths, push, pull, iters): lengths2 = lengths**2 for i in range(iters): #p1 = links[:, 0] #p2 = links[:, 1] #x1 = pos[p1] #x2 = pos[p2] #dx = x2 - x1 #dist = (dx**2).sum(axis=1)**0.5 #mask = (npush & (dist < lengths)) | (npull & (dist > lengths)) ##dist[mask] = lengths[mask] #change = (lengths-dist) / dist #change[mask] = 0 #dx *= change[:, np.newaxis] #print dx ##pos[p1] -= mrel2 * dx ##pos[p2] += mrel1 * dx #for j in range(links.shape[0]): #pos[links[j,0]] -= mrel2[j] * dx[j] #pos[links[j,1]] += mrel1[j] * dx[j] for l in range(links.shape[0]): p1, p2 = links[l]; x1 = pos[p1] x2 = pos[p2] dx = x2 - x1 dist2 = (dx**2).sum() if (push[l] and dist2 < lengths2[l]) or (pull[l] and dist2 > lengths2[l]): dist = dist2 ** 0.5 change = (lengths[l]-dist) / dist dx *= change pos[p1] -= mrel2[l] * dx pos[p2] += mrel1[l] * dx pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/examples/verlet_chain_demo.py000066400000000000000000000055561421045507400255250ustar00rootroot00000000000000""" Mechanical simulation of a chain using verlet integration. Use the mouse to interact with one of the chains. By default, this uses a slow, pure-python integrator to solve the chain link positions. Unix users may compile a small math library to speed this up by running the `examples/verlet_chain/make` script. """ import numpy as np import verlet_chain import pyqtgraph as pg sim = verlet_chain.ChainSim() if verlet_chain.relax.COMPILED: # Use more complex chain if compiled mad library is available. chlen1 = 80 chlen2 = 60 linklen = 1 else: chlen1 = 10 chlen2 = 8 linklen = 8 npts = chlen1 + chlen2 sim.mass = np.ones(npts) sim.mass[int(chlen1 * 0.8)] = 100 sim.mass[chlen1-1] = 500 sim.mass[npts-1] = 200 sim.fixed = np.zeros(npts, dtype=bool) sim.fixed[0] = True sim.fixed[chlen1] = True sim.pos = np.empty((npts, 2)) sim.pos[:chlen1, 0] = 0 sim.pos[chlen1:, 0] = 10 sim.pos[:chlen1, 1] = np.arange(chlen1) * linklen sim.pos[chlen1:, 1] = np.arange(chlen2) * linklen # to prevent miraculous balancing acts: sim.pos += np.random.normal(size=sim.pos.shape, scale=1e-3) links1 = [(j, i+j+1) for i in range(chlen1) for j in range(chlen1-i-1)] links2 = [(j, i+j+1) for i in range(chlen2) for j in range(chlen2-i-1)] sim.links = np.concatenate([np.array(links1), np.array(links2)+chlen1, np.array([[chlen1-1, npts-1]])]) p1 = sim.pos[sim.links[:,0]] p2 = sim.pos[sim.links[:,1]] dif = p2-p1 sim.lengths = (dif**2).sum(axis=1) ** 0.5 sim.lengths[(chlen1-1):len(links1)] *= 1.05 # let auxiliary links stretch a little sim.lengths[(len(links1)+chlen2-1):] *= 1.05 sim.lengths[-1] = 7 push1 = np.ones(len(links1), dtype=bool) push1[chlen1:] = False push2 = np.ones(len(links2), dtype=bool) push2[chlen2:] = False sim.push = np.concatenate([push1, push2, np.array([True], dtype=bool)]) sim.pull = np.ones(sim.links.shape[0], dtype=bool) sim.pull[-1] = False # move chain initially just to generate some motion if the mouse is not over the window mousepos = np.array([30, 20]) def display(): global view, sim view.clear() view.addItem(sim.makeGraph()) def relaxed(): global app display() app.processEvents() def mouse(pos): global mousepos pos = view.mapSceneToView(pos) mousepos = np.array([pos.x(), pos.y()]) def update(): global mousepos #sim.pos[0] = sim.pos[0] * 0.9 + mousepos * 0.1 s = 0.9 sim.pos[0] = sim.pos[0] * s + mousepos * (1.0-s) sim.update() app = pg.mkQApp() win = pg.GraphicsLayoutWidget() win.show() view = win.addViewBox() view.setAspectLocked(True) view.setXRange(-100, 100) #view.autoRange() view.scene().sigMouseMoved.connect(mouse) #display() #app.processEvents() sim.relaxed.connect(relaxed) sim.init() sim.relaxed.disconnect(relaxed) sim.stepped.connect(display) timer = pg.QtCore.QTimer() timer.timeout.connect(update) timer.start(16) if __name__ == '__main__': pg.exec() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/exceptionHandling.py000066400000000000000000000100421421045507400236650ustar00rootroot00000000000000"""This module installs a wrapper around sys.excepthook which allows multiple new exception handlers to be registered. Optionally, the wrapper also stops exceptions from causing long-term storage of local stack frames. This has two major effects: - Unhandled exceptions will no longer cause memory leaks (If an exception occurs while a lot of data is present on the stack, such as when loading large files, the data would ordinarily be kept until the next exception occurs. We would rather release this memory as soon as possible.) - Some debuggers may have a hard time handling uncaught exceptions The module also provides a callback mechanism allowing others to respond to exceptions. """ import sys import time #from lib.Manager import logMsg import traceback #from log import * #logging = False callbacks = [] clear_tracebacks = False def register(fn): """ Register a callable to be invoked when there is an unhandled exception. The callback will be passed the output of sys.exc_info(): (exception type, exception, traceback) Multiple callbacks will be invoked in the order they were registered. """ callbacks.append(fn) def unregister(fn): """Unregister a previously registered callback.""" callbacks.remove(fn) def setTracebackClearing(clear=True): """ Enable or disable traceback clearing. By default, clearing is disabled and Python will indefinitely store unhandled exception stack traces. This function is provided since Python's default behavior can cause unexpected retention of large memory-consuming objects. """ global clear_tracebacks clear_tracebacks = clear class ExceptionHandler(object): def __call__(self, *args): ## Start by extending recursion depth just a bit. ## If the error we are catching is due to recursion, we don't want to generate another one here. recursionLimit = sys.getrecursionlimit() try: sys.setrecursionlimit(recursionLimit+100) ## call original exception handler first (prints exception) global original_excepthook, callbacks, clear_tracebacks try: print("===== %s =====" % str(time.strftime("%Y.%m.%d %H:%m:%S", time.localtime(time.time())))) except Exception: sys.stderr.write("Warning: stdout is broken! Falling back to stderr.\n") sys.stdout = sys.stderr ret = original_excepthook(*args) for cb in callbacks: try: cb(*args) except Exception: print(" --------------------------------------------------------------") print(" Error occurred during exception callback %s" % str(cb)) print(" --------------------------------------------------------------") traceback.print_exception(*sys.exc_info()) ## Clear long-term storage of last traceback to prevent memory-hogging. ## (If an exception occurs while a lot of data is present on the stack, ## such as when loading large files, the data would ordinarily be kept ## until the next exception occurs. We would rather release this memory ## as soon as possible.) if clear_tracebacks is True: sys.last_traceback = None finally: sys.setrecursionlimit(recursionLimit) def implements(self, interface=None): ## this just makes it easy for us to detect whether an ExceptionHook is already installed. if interface is None: return ['ExceptionHandler'] else: return interface == 'ExceptionHandler' ## replace built-in excepthook only if this has not already been done if not (hasattr(sys.excepthook, 'implements') and sys.excepthook.implements('ExceptionHandler')): original_excepthook = sys.excepthook sys.excepthook = ExceptionHandler() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/exporters/000077500000000000000000000000001421045507400217065ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/exporters/CSVExporter.py000066400000000000000000000056611421045507400244540ustar00rootroot00000000000000from .. import PlotItem from ..parametertree import Parameter from ..Qt import QtCore from .Exporter import Exporter translate = QtCore.QCoreApplication.translate __all__ = ['CSVExporter'] class CSVExporter(Exporter): Name = "CSV from plot data" windows = [] 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', 'limits': ['comma', 'tab']}, {'name': 'precision', 'title': translate("Exporter", 'precision'), 'type': 'int', 'value': 10, 'limits': [0, None]}, {'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list', 'limits': ['(x,y) per plot', '(x,y,y,y) for all plots']} ]) def parameters(self): return self.params def export(self, fileName=None): if not isinstance(self.item, PlotItem): raise Exception("Must have a PlotItem selected for CSV export.") if fileName is None: self.fileSaveDialog(filter=["*.csv", "*.tsv"]) return data = [] header = [] appendAllX = self.params['columnMode'] == '(x,y) per plot' for i, c in enumerate(self.item.curves): cd = c.getData() if cd[0] is None: continue data.append(cd) if hasattr(c, 'implements') and c.implements('plotData') and c.name() is not None: name = c.name().replace('"', '""') + '_' xName, yName = '"'+name+'x"', '"'+name+'y"' else: xName = 'x%04d' % i yName = 'y%04d' % i if appendAllX or i == 0: header.extend([xName, yName]) else: header.extend([yName]) if self.params['separator'] == 'comma': sep = ',' else: sep = '\t' with open(fileName, 'w') as fd: 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]) for i in range(numRows): for j, d in enumerate(data): # write x value if this is the first column, or if we want # x for all rows if appendAllX or j == 0: if d is not None and i < len(d[0]): fd.write(numFormat % d[0][i] + sep) else: fd.write(' %s' % sep) # write y value if d is not None and i < len(d[1]): fd.write(numFormat % d[1][i] + sep) else: fd.write(' %s' % sep) fd.write('\n') CSVExporter.register() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/exporters/Exporter.py000066400000000000000000000123641421045507400240760ustar00rootroot00000000000000import os import re from ..GraphicsScene import GraphicsScene from ..Qt import QtCore, QtWidgets from ..widgets.FileDialog import FileDialog LastExportDirectory = None class Exporter(object): """ Abstract class used for exporting graphics to file / printer / whatever. """ allowCopy = False # subclasses set this to True if they can use the copy buffer Exporters = [] @classmethod def register(cls): """ Used to register Exporter classes to appear in the export dialog. """ Exporter.Exporters.append(cls) def __init__(self, item): """ Initialize with the item to be exported. Can be an individual graphics item or a scene. """ object.__init__(self) self.item = item def parameters(self): """Return the parameters used to configure this exporter.""" raise Exception("Abstract method must be overridden in subclass.") def export(self, fileName=None, toBytes=False, copy=False): """ If *fileName* is None, pop-up a file dialog. If *toBytes* is True, return a bytes object rather than writing to file. If *copy* is True, export to the copy buffer rather than writing to file. """ raise Exception("Abstract method must be overridden in subclass.") def fileSaveDialog(self, filter=None, opts=None): ## Show a file dialog, call self.export(fileName) when finished. if opts is None: opts = {} self.fileDialog = FileDialog() self.fileDialog.setFileMode(QtWidgets.QFileDialog.FileMode.AnyFile) self.fileDialog.setAcceptMode(QtWidgets.QFileDialog.AcceptMode.AcceptSave) if filter is not None: if isinstance(filter, str): self.fileDialog.setNameFilter(filter) elif isinstance(filter, list): self.fileDialog.setNameFilters(filter) global LastExportDirectory exportDir = LastExportDirectory if exportDir is not None: self.fileDialog.setDirectory(exportDir) self.fileDialog.show() self.fileDialog.opts = opts self.fileDialog.fileSelected.connect(self.fileSaveFinished) return def fileSaveFinished(self, 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', self.fileDialog.selectedNameFilter()) if selectedExt is not None: selectedExt = selectedExt.groups()[0].lower() if ext != selectedExt: fileName = fileName + '.' + selectedExt.lstrip('.') self.export(fileName=fileName, **self.fileDialog.opts) def getScene(self): if isinstance(self.item, GraphicsScene): return self.item else: return self.item.scene() def getSourceRect(self): if isinstance(self.item, GraphicsScene): w = self.item.getViewWidget() return w.viewportTransform().inverted()[0].mapRect(w.rect()) else: return self.item.sceneBoundingRect() def getTargetRect(self): if isinstance(self.item, GraphicsScene): return self.item.getViewWidget().rect() else: return self.item.mapRectToDevice(self.item.boundingRect()) def setExportMode(self, export, opts=None): """ Call setExportMode(export, opts) on all items that will be painted during the export. This informs the item that it is about to be painted for export, allowing it to alter its appearance temporarily *export* - bool; must be True before exporting and False afterward *opts* - dict; common parameters are 'antialias' and 'background' """ if opts is None: opts = {} for item in self.getPaintItems(): if hasattr(item, 'setExportMode'): item.setExportMode(export, opts) def getPaintItems(self, root=None): """Return a list of all items that should be painted in the correct order.""" if root is None: root = self.item preItems = [] postItems = [] if isinstance(root, QtWidgets.QGraphicsScene): childs = [i for i in root.items() if i.parentItem() is None] rootItem = [] else: childs = root.childItems() rootItem = [root] childs.sort(key=lambda a: a.zValue()) while len(childs) > 0: ch = childs.pop(0) tree = self.getPaintItems(ch) if (ch.flags() & ch.GraphicsItemFlag.ItemStacksBehindParent) or \ (ch.zValue() < 0 and (ch.flags() & ch.GraphicsItemFlag.ItemNegativeZStacksBehindParent)): preItems.extend(tree) else: postItems.extend(tree) return preItems + rootItem + postItems def render(self, painter, targetRect, sourceRect, item=None): self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/exporters/HDF5Exporter.py000066400000000000000000000047611421045507400245070ustar00rootroot00000000000000import importlib import numpy from .. import PlotItem from ..parametertree import Parameter from ..Qt import QtCore from .Exporter import Exporter HAVE_HDF5 = importlib.util.find_spec("h5py") is not None translate = QtCore.QCoreApplication.translate __all__ = ['HDF5Exporter'] class HDF5Exporter(Exporter): Name = "HDF5 Export: plot (x,y)" windows = [] allowCopy = False def __init__(self, item): Exporter.__init__(self, item) 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', 'limits': ['(x,y) per plot', '(x,y,y,y) for all plots']}, ]) def parameters(self): return self.params def export(self, fileName=None): if not HAVE_HDF5: raise RuntimeError("This exporter requires the h5py package, " "but it was not importable.") import h5py if not isinstance(self.item, PlotItem): raise Exception("Must have a PlotItem selected for HDF5 export.") if fileName is None: self.fileSaveDialog(filter=["*.h5", "*.hdf", "*.hd5"]) return dsname = self.params['Name'] fd = h5py.File(fileName, 'a') # forces append to file... 'w' doesn't seem to "delete/overwrite" data = [] appendAllX = self.params['columnMode'] == '(x,y) per plot' # Check if the arrays are ragged len_first = len(self.item.curves[0].getData()[0]) if self.item.curves[0] else None ragged = any(len(i.getData()[0]) != len_first for i in self.item.curves) if ragged: dgroup = fd.create_group(dsname) for i, c in enumerate(self.item.curves): d = c.getData() fdata = numpy.array([d[0], d[1]]).astype('double') cname = c.name() if c.name() is not None else str(i) dset = dgroup.create_dataset(cname, data=fdata) else: for i, c in enumerate(self.item.curves): d = c.getData() if appendAllX or i == 0: data.append(d[0]) data.append(d[1]) fdata = numpy.array(data).astype('double') dset = fd.create_dataset(dsname, data=fdata) fd.close() if HAVE_HDF5: HDF5Exporter.register() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/exporters/ImageExporter.py000066400000000000000000000112161421045507400250340ustar00rootroot00000000000000import sys import numpy as np from .. import functions as fn from ..parametertree import Parameter from ..Qt import QtCore, QtGui, QtWidgets from .Exporter import Exporter translate = QtCore.QCoreApplication.translate __all__ = ['ImageExporter'] class ImageExporter(Exporter): Name = "Image File (PNG, TIF, JPG, ...)" allowCopy = True def __init__(self, item): Exporter.__init__(self, item) tr = self.getTargetRect() if isinstance(item, QtWidgets.QGraphicsItem): scene = item.scene() else: scene = item bgbrush = scene.views()[0].backgroundBrush() bg = bgbrush.color() if bgbrush.style() == QtCore.Qt.BrushStyle.NoBrush: bg.setAlpha(0) self.params = Parameter(name='params', type='group', children=[ {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'int', 'value': int(tr.width()), 'limits': (0, None)}, {'name': 'height', 'title': translate("Exporter", 'height'), 'type': 'int', 'value': int(tr.height()), 'limits': (0, None)}, {'name': 'antialias', 'title': translate("Exporter", 'antialias'), 'type': 'bool', 'value': True}, {'name': 'background', 'title': translate("Exporter", 'background'), 'type': 'color', 'value': bg}, {'name': 'invertValue', 'title': translate("Exporter", 'invertValue'), 'type': 'bool', 'value': False} ]) self.params.param('width').sigValueChanged.connect(self.widthChanged) self.params.param('height').sigValueChanged.connect(self.heightChanged) def widthChanged(self): sr = self.getSourceRect() ar = float(sr.height()) / sr.width() self.params.param('height').setValue(int(self.params['width'] * ar), blockSignal=self.heightChanged) def heightChanged(self): sr = self.getSourceRect() ar = float(sr.width()) / sr.height() self.params.param('width').setValue(int(self.params['height'] * ar), blockSignal=self.widthChanged) def parameters(self): return self.params @staticmethod def getSupportedImageFormats(): filter = ["*."+f.data().decode('utf-8') for f in QtGui.QImageWriter.supportedImageFormats()] preferred = ['*.png', '*.tif', '*.jpg'] for p in preferred[::-1]: if p in filter: filter.remove(p) filter.insert(0, p) return filter def export(self, fileName=None, toBytes=False, copy=False): if fileName is None and not toBytes and not copy: filter = self.getSupportedImageFormats() self.fileSaveDialog(filter=filter) return w = int(self.params['width']) h = int(self.params['height']) if w == 0 or h == 0: raise Exception("Cannot export image with size=0 (requested " "export size is %dx%d)" % (w, h)) targetRect = QtCore.QRect(0, 0, w, h) sourceRect = self.getSourceRect() self.png = QtGui.QImage(w, h, QtGui.QImage.Format.Format_ARGB32) self.png.fill(self.params['background']) ## set resolution of image: origTargetRect = self.getTargetRect() resolutionScale = targetRect.width() / origTargetRect.width() #self.png.setDotsPerMeterX(self.png.dotsPerMeterX() * resolutionScale) #self.png.setDotsPerMeterY(self.png.dotsPerMeterY() * resolutionScale) painter = QtGui.QPainter(self.png) #dtr = painter.deviceTransform() try: self.setExportMode(True, { 'antialias': self.params['antialias'], 'background': self.params['background'], 'painter': painter, 'resolutionScale': resolutionScale}) painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, self.params['antialias']) self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) finally: self.setExportMode(False) painter.end() if self.params['invertValue']: bg = fn.ndarray_from_qimage(self.png) if sys.byteorder == 'little': cv = slice(0, 3) else: cv = slice(1, 4) mn = bg[...,cv].min(axis=2) mx = bg[...,cv].max(axis=2) d = (255 - mx) - mn bg[...,cv] += d[...,np.newaxis] if copy: QtWidgets.QApplication.clipboard().setImage(self.png) elif toBytes: return self.png else: return self.png.save(fileName) ImageExporter.register() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/exporters/Matplotlib.py000066400000000000000000000114431421045507400243720ustar00rootroot00000000000000from .. import PlotItem from .. import functions as fn from ..Qt import QtCore, QtWidgets from .Exporter import Exporter __all__ = ['MatplotlibExporter'] """ It is helpful when using the matplotlib Exporter if your .matplotlib/matplotlibrc file is configured appropriately. The following are suggested for getting usable PDF output that can be edited in Illustrator, etc. backend : Qt4Agg text.usetex : True # Assumes you have a findable LaTeX installation interactive : False font.family : sans-serif font.sans-serif : 'Arial' # (make first in list) mathtext.default : sf figure.facecolor : white # personal preference # next setting allows pdf font to be readable in Adobe Illustrator pdf.fonttype : 42 # set fonts to TrueType (otherwise it will be 3 # and the text will be vectorized. text.dvipnghack : True # primarily to clean up font appearance on Mac The advantage is that there is less to do to get an exported file cleaned and ready for publication. Fonts are not vectorized (outlined), and window colors are white. """ class MatplotlibExporter(Exporter): Name = "Matplotlib Window" windows = [] def __init__(self, item): Exporter.__init__(self, item) def parameters(self): return None def cleanAxes(self, axl): if type(axl) is not list: axl = [axl] for ax in axl: if ax is None: continue for loc, spine in ax.spines.items(): if loc in ['left', 'bottom']: pass elif loc in ['right', 'top']: spine.set_color('none') # do not draw the spine else: raise ValueError('Unknown spine location: %s' % loc) # turn off ticks when there is no spine ax.xaxis.set_ticks_position('bottom') def export(self, fileName=None): if not isinstance(self.item, PlotItem): raise Exception("MatplotlibExporter currently only works with PlotItem") mpw = MatplotlibWindow() MatplotlibExporter.windows.append(mpw) fig = mpw.getFigure() xax = self.item.getAxis('bottom') yax = self.item.getAxis('left') # get labels from the graphic item xlabel = xax.label.toPlainText() ylabel = yax.label.toPlainText() title = self.item.titleLabel.text # if axes use autoSIPrefix, scale the data so mpl doesn't add its own # scale factor label xscale = yscale = 1.0 if xax.autoSIPrefix: xscale = xax.autoSIPrefixScale if yax.autoSIPrefix: yscale = yax.autoSIPrefixScale ax = fig.add_subplot(111, title=title) ax.clear() self.cleanAxes(ax) for item in self.item.curves: x, y = item.getData() x = x * xscale y = y * yscale opts = item.opts pen = fn.mkPen(opts['pen']) if pen.style() == QtCore.Qt.PenStyle.NoPen: linestyle = '' else: linestyle = '-' color = pen.color().getRgbF() symbol = opts['symbol'] if symbol == 't': symbol = '^' symbolPen = fn.mkPen(opts['symbolPen']) symbolBrush = fn.mkBrush(opts['symbolBrush']) 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 = 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(), linestyle=linestyle, markeredgecolor=markeredgecolor, markerfacecolor=markerfacecolor, markersize=markersize) xr, yr = self.item.viewRange() ax.set_xbound(xr[0]*xscale, xr[1]*xscale) ax.set_ybound(yr[0]*yscale, yr[1]*yscale) ax.set_xlabel(xlabel) # place the labels. ax.set_ylabel(ylabel) mpw.draw() MatplotlibExporter.register() class MatplotlibWindow(QtWidgets.QMainWindow): def __init__(self): from ..widgets import MatplotlibWidget QtWidgets.QMainWindow.__init__(self) self.mpl = MatplotlibWidget.MatplotlibWidget() self.setCentralWidget(self.mpl) self.show() def __getattr__(self, attr): return getattr(self.mpl, attr) def closeEvent(self, ev): MatplotlibExporter.windows.remove(self) self.deleteLater() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/exporters/PrintExporter.py000066400000000000000000000054621421045507400251140ustar00rootroot00000000000000from ..parametertree import Parameter from ..Qt import QtCore, QtGui, QtWidgets from .Exporter import Exporter translate = QtCore.QCoreApplication.translate __all__ = ['PrintExporter'] #__all__ = [] ## Printer is disabled for now--does not work very well. class PrintExporter(Exporter): Name = "Printer" def __init__(self, item): Exporter.__init__(self, item) tr = self.getTargetRect() self.params = Parameter(name='params', type='group', children=[ {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'float', 'value': 0.1, 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, {'name': 'height', 'title': translate("Exporter", 'height'), 'type': 'float', 'value': (0.1 * tr.height()) / tr.width(), 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, ]) self.params.param('width').sigValueChanged.connect(self.widthChanged) self.params.param('height').sigValueChanged.connect(self.heightChanged) def widthChanged(self): sr = self.getSourceRect() ar = sr.height() / sr.width() self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) def heightChanged(self): sr = self.getSourceRect() ar = sr.width() / sr.height() self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) def parameters(self): return self.params def export(self, fileName=None): printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) dialog = QtGui.QPrintDialog(printer) dialog.setWindowTitle(translate('Exporter', "Print Document")) if dialog.exec_() != QtWidgets.QDialog.DialogCode.Accepted: return #dpi = QtGui.QDesktopWidget().physicalDpiX() #self.svg.setSize(QtCore.QSize(100,100)) #self.svg.setResolution(600) #res = printer.resolution() sr = self.getSourceRect() #res = sr.width() * .4 / (self.params['width'] * 100 / 2.54) res = QtGui.QGuiApplication.primaryScreen().physicalDotsPerInchX() printer.setResolution(res) rect = printer.pageRect() center = rect.center() h = self.params['height'] * res * 100. / 2.54 w = self.params['width'] * res * 100. / 2.54 x = center.x() - w/2. y = center.y() - h/2. targetRect = QtCore.QRect(x, y, w, h) sourceRect = self.getSourceRect() painter = QtGui.QPainter(printer) try: self.setExportMode(True, {'painter': painter}) self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) finally: self.setExportMode(False) painter.end() #PrintExporter.register() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/exporters/SVGExporter.py000066400000000000000000000443521421045507400244600ustar00rootroot00000000000000import re import xml.dom.minidom as xml import numpy as np from .. import debug from .. import functions as fn from ..parametertree import Parameter from ..Qt import QtCore, QtGui, QtSvg, QtWidgets from .Exporter import Exporter translate = QtCore.QCoreApplication.translate __all__ = ['SVGExporter'] class SVGExporter(Exporter): Name = "Scalable Vector Graphics (SVG)" allowCopy=True def __init__(self, item): Exporter.__init__(self, item) tr = self.getTargetRect() if isinstance(item, QtWidgets.QGraphicsItem): scene = item.scene() else: scene = item bgbrush = scene.views()[0].backgroundBrush() bg = bgbrush.color() if bgbrush.style() == QtCore.Qt.BrushStyle.NoBrush: bg.setAlpha(0) self.params = Parameter(name='params', type='group', children=[ {'name': 'background', 'title': translate("Exporter", 'background'), 'type': 'color', 'value': bg}, {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'float', 'value': tr.width(), 'limits': (0, None)}, {'name': 'height', 'title': translate("Exporter", 'height'), 'type': 'float', 'value': tr.height(), 'limits': (0, None)}, #{'name': 'viewbox clipping', 'type': 'bool', 'value': True}, #{'name': 'normalize coordinates', 'type': 'bool', 'value': True}, {'name': 'scaling stroke', 'title': translate("Exporter", 'scaling stroke'), 'type': 'bool', 'value': False, 'tip': "If False, strokes are non-scaling, " "which means that they appear the same width on screen regardless of how they are scaled or how the view is zoomed."}, ]) self.params.param('width').sigValueChanged.connect(self.widthChanged) self.params.param('height').sigValueChanged.connect(self.heightChanged) def widthChanged(self): sr = self.getSourceRect() ar = sr.height() / sr.width() self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) def heightChanged(self): sr = self.getSourceRect() ar = sr.width() / sr.height() self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) def parameters(self): return self.params def export(self, fileName=None, toBytes=False, copy=False): if toBytes is False and copy is False and fileName is None: self.fileSaveDialog(filter=f"{translate('Exporter', 'Scalable Vector Graphics')} (*.svg)") return ## Qt's SVG generator is not complete. (notably, it lacks clipping) ## Instead, we will use Qt to generate SVG for each item independently, ## then manually reconstruct the entire document. options = {ch.name():ch.value() for ch in self.params.children()} options['background'] = self.params['background'] options['width'] = self.params['width'] options['height'] = self.params['height'] xml = generateSvg(self.item, options) if toBytes: return xml.encode('UTF-8') elif copy: md = QtCore.QMimeData() md.setData('image/svg+xml', QtCore.QByteArray(xml.encode('UTF-8'))) QtWidgets.QApplication.clipboard().setMimeData(md) else: with open(fileName, 'wb') as fh: fh.write(str(xml).encode('utf-8')) # Includes space for extra attributes xmlHeader = """\ pyqtgraph SVG export Generated with Qt and pyqtgraph """ def generateSvg(item, options={}): global xmlHeader try: node, defs = _generateItemSvg(item, options=options) finally: ## reset export mode for all items in the tree if isinstance(item, QtWidgets.QGraphicsScene): items = item.items() else: items = [item] for i in items: items.extend(i.childItems()) for i in items: if hasattr(i, 'setExportMode'): i.setExportMode(False) cleanXml(node) defsXml = "\n" for d in defs: defsXml += d.toprettyxml(indent=' ') defsXml += "\n" svgAttributes = ' viewBox ="0 0 %f %f"' % (options["width"], options["height"]) c = options['background'] backgroundtag = '\n' % (c.red(), c.green(), c.blue(), c.alphaF()) return (xmlHeader % svgAttributes) + backgroundtag + defsXml + node.toprettyxml(indent=' ') + "\n\n" def _generateItemSvg(item, nodes=None, root=None, options={}): ## This function is intended to work around some issues with Qt's SVG generator ## and SVG in general. ## 1) Qt SVG does not implement clipping paths. This is absurd. ## The solution is to let Qt generate SVG for each item independently, ## then glue them together manually with clipping. ## ## The format Qt generates for all items looks like this: ## ## ## ## one or more of: or or ## ## ## one or more of: or or ## ## . . . ## ## ## 2) There seems to be wide disagreement over whether path strokes ## should be scaled anisotropically. ## see: http://web.mit.edu/jonas/www/anisotropy/ ## Given that both inkscape and illustrator seem to prefer isotropic ## scaling, we will optimize for those cases. ## ## 3) Qt generates paths using non-scaling-stroke from SVG 1.2, but ## inkscape only supports 1.1. ## ## Both 2 and 3 can be addressed by drawing all items in world coordinates. profiler = debug.Profiler() if nodes is None: ## nodes maps all node IDs to their XML element. ## this allows us to ensure all elements receive unique names. nodes = {} if root is None: root = item ## Skip hidden items if hasattr(item, 'isVisible') and not item.isVisible(): return None ## If this item defines its own SVG generator, use that. if hasattr(item, 'generateSvg'): return item.generateSvg(nodes) ## Generate SVG text for just this item (exclude its children; we'll handle them later) if isinstance(item, QtWidgets.QGraphicsScene): xmlStr = "\n\n" doc = xml.parseString(xmlStr) childs = [i for i in item.items() if i.parentItem() is None] elif item.__class__.paint == QtWidgets.QGraphicsItem.paint: xmlStr = "\n\n" doc = xml.parseString(xmlStr) childs = item.childItems() else: childs = item.childItems() tr = itemTransform(item, item.scene()) ## offset to corner of root item if isinstance(root, QtWidgets.QGraphicsScene): rootPos = QtCore.QPoint(0,0) else: rootPos = root.scenePos() tr2 = QtGui.QTransform() tr2.translate(-rootPos.x(), -rootPos.y()) tr = tr * tr2 arr = QtCore.QByteArray() buf = QtCore.QBuffer(arr) svg = QtSvg.QSvgGenerator() svg.setOutputDevice(buf) dpi = QtGui.QGuiApplication.primaryScreen().logicalDotsPerInchX() svg.setResolution(int(dpi)) p = QtGui.QPainter() p.begin(svg) if hasattr(item, 'setExportMode'): item.setExportMode(True, {'painter': p}) try: p.setTransform(tr) opt = QtWidgets.QStyleOptionGraphicsItem() if item.flags() & QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemUsesExtendedStyleOption: opt.exposedRect = item.boundingRect() item.paint(p, opt, None) finally: p.end() ## Can't do this here--we need to wait until all children have painted as well. ## this is taken care of in generateSvg instead. #if hasattr(item, 'setExportMode'): #item.setExportMode(False) doc = xml.parseString(arr.data()) try: ## Get top-level group for this item g1 = doc.getElementsByTagName('g')[0] ## get list of sub-groups g2 = [n for n in g1.childNodes if isinstance(n, xml.Element) and n.tagName == 'g'] defs = doc.getElementsByTagName('defs') if len(defs) > 0: defs = [n for n in defs[0].childNodes if isinstance(n, xml.Element)] except: print(doc.toxml()) raise profiler('render') ## Get rid of group transformation matrices by applying ## transformation to inner coordinates correctCoordinates(g1, defs, item, options) profiler('correct') ## decide on a name for this item baseName = item.__class__.__name__ i = 1 while True: name = baseName + "_%d" % i if name not in nodes: break i += 1 nodes[name] = g1 g1.setAttribute('id', name) ## If this item clips its children, we need to take care of that. childGroup = g1 ## add children directly to this node unless we are clipping if not isinstance(item, QtWidgets.QGraphicsScene): ## See if this item clips its children if item.flags() & item.GraphicsItemFlag.ItemClipsChildrenToShape: ## Generate svg for just the path path = QtWidgets.QGraphicsPathItem(item.mapToScene(item.shape())) item.scene().addItem(path) try: pathNode = _generateItemSvg(path, root=root, options=options)[0].getElementsByTagName('path')[0] # assume for this path is empty.. possibly problematic. finally: item.scene().removeItem(path) ## and for the clipPath element clip = name + '_clip' clipNode = g1.ownerDocument.createElement('clipPath') clipNode.setAttribute('id', clip) clipNode.appendChild(pathNode) g1.appendChild(clipNode) childGroup = g1.ownerDocument.createElement('g') childGroup.setAttribute('clip-path', 'url(#%s)' % clip) g1.appendChild(childGroup) profiler('clipping') ## Add all child items as sub-elements. childs.sort(key=lambda c: c.zValue()) for ch in childs: csvg = _generateItemSvg(ch, nodes, root, options=options) if csvg is None: continue cg, cdefs = csvg childGroup.appendChild(cg) ### this isn't quite right--some items draw below their parent (good enough for now) defs.extend(cdefs) profiler('children') return g1, defs def correctCoordinates(node, defs, item, options): # TODO: correct gradient coordinates inside defs ## Remove transformation matrices from tags by applying matrix to coordinates inside. ## Each item is represented by a single top-level group with one or more groups inside. ## Each inner group contains one or more drawing primitives, possibly of different types. groups = node.getElementsByTagName('g') ## Since we leave text unchanged, groups which combine text and non-text primitives must be split apart. ## (if at some point we start correcting text transforms as well, then it should be safe to remove this) groups2 = [] for grp in groups: subGroups = [grp.cloneNode(deep=False)] textGroup = None for ch in grp.childNodes[:]: if isinstance(ch, xml.Element): if textGroup is None: textGroup = ch.tagName == 'text' if ch.tagName == 'text': if textGroup is False: subGroups.append(grp.cloneNode(deep=False)) textGroup = True else: if textGroup is True: subGroups.append(grp.cloneNode(deep=False)) textGroup = False subGroups[-1].appendChild(ch) groups2.extend(subGroups) for sg in subGroups: node.insertBefore(sg, grp) node.removeChild(grp) groups = groups2 for grp in groups: matrix = grp.getAttribute('transform') match = re.match(r'matrix\((.*)\)', matrix) if match is None: vals = [1,0,0,1,0,0] else: vals = [float(a) for a in match.groups()[0].split(',')] tr = np.array([[vals[0], vals[2], vals[4]], [vals[1], vals[3], vals[5]]]) removeTransform = False for ch in grp.childNodes: if not isinstance(ch, xml.Element): continue if ch.tagName == 'polyline': removeTransform = True coords = np.array([[float(a) for a in c.split(',')] for c in ch.getAttribute('points').strip().split(' ')]) coords = fn.transformCoordinates(tr, coords, transpose=True) ch.setAttribute('points', ' '.join([','.join([str(a) for a in c]) for c in coords])) elif ch.tagName == 'path': removeTransform = True newCoords = '' oldCoords = ch.getAttribute('d').strip() if oldCoords == '': continue for c in oldCoords.split(' '): x,y = c.split(',') if x[0].isalpha(): t = x[0] x = x[1:] else: t = '' nc = fn.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True) newCoords += t+str(nc[0,0])+','+str(nc[0,1])+' ' # If coords start with L instead of M, then the entire path will not be rendered. # (This can happen if the first point had nan values in it--Qt will skip it on export) if newCoords[0] != 'M': newCoords = 'M' + newCoords[1:] ch.setAttribute('d', newCoords) elif ch.tagName == 'text': removeTransform = False ## leave text alone for now. Might need this later to correctly render text with outline. #c = np.array([ #[float(ch.getAttribute('x')), float(ch.getAttribute('y'))], #[float(ch.getAttribute('font-size')), 0], #[0,0]]) #c = fn.transformCoordinates(tr, c, transpose=True) #ch.setAttribute('x', str(c[0,0])) #ch.setAttribute('y', str(c[0,1])) #fs = c[1]-c[2] #fs = (fs**2).sum()**0.5 #ch.setAttribute('font-size', str(fs)) ## Correct some font information families = ch.getAttribute('font-family').split(',') if len(families) == 1: font = QtGui.QFont(families[0].strip('" ')) if font.styleHint() == font.StyleHint.SansSerif: families.append('sans-serif') elif font.styleHint() == font.StyleHint.Serif: families.append('serif') elif font.styleHint() == font.StyleHint.Courier: families.append('monospace') ch.setAttribute('font-family', ', '.join([f if ' ' not in f else '"%s"'%f for f in families])) ## correct line widths if needed if removeTransform and ch.getAttribute('vector-effect') != 'non-scaling-stroke' and grp.getAttribute('stroke-width') != '': w = float(grp.getAttribute('stroke-width')) s = fn.transformCoordinates(tr, np.array([[w,0], [0,0]]), transpose=True) w = ((s[0]-s[1])**2).sum()**0.5 ch.setAttribute('stroke-width', str(w)) # Remove non-scaling-stroke if requested if options.get('scaling stroke') is True and ch.getAttribute('vector-effect') == 'non-scaling-stroke': ch.removeAttribute('vector-effect') if removeTransform: grp.removeAttribute('transform') SVGExporter.register() def itemTransform(item, root): ## Return the transformation mapping item to root ## (actually to parent coordinate system of root) if item is root: tr = QtGui.QTransform() tr.translate(*item.pos()) tr = tr * item.transform() return tr if item.flags() & item.GraphicsItemFlag.ItemIgnoresTransformations: pos = item.pos() parent = item.parentItem() if parent is not None: pos = itemTransform(parent, root).map(pos) tr = QtGui.QTransform() tr.translate(pos.x(), pos.y()) tr = item.transform() * tr else: ## find next parent that is either the root item or ## an item that ignores its transformation nextRoot = item while True: nextRoot = nextRoot.parentItem() if nextRoot is None: nextRoot = root break if nextRoot is root or (nextRoot.flags() & nextRoot.GraphicsItemFlag.ItemIgnoresTransformations): break if isinstance(nextRoot, QtWidgets.QGraphicsScene): tr = item.sceneTransform() else: tr = itemTransform(nextRoot, root) * item.itemTransform(nextRoot)[0] return tr def cleanXml(node): ## remove extraneous text; let the xml library do the formatting. hasElement = False nonElement = [] for ch in node.childNodes: if isinstance(ch, xml.Element): hasElement = True cleanXml(ch) else: nonElement.append(ch) if hasElement: for ch in nonElement: node.removeChild(ch) elif node.tagName == 'g': ## remove childless groups node.parentNode.removeChild(node) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/exporters/__init__.py000066400000000000000000000003751421045507400240240ustar00rootroot00000000000000from .CSVExporter import * from .Exporter import Exporter from .HDF5Exporter import * from .ImageExporter import * from .Matplotlib import * from .PrintExporter import * from .SVGExporter import * def listExporters(): return Exporter.Exporters[:] pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/000077500000000000000000000000001421045507400216445ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/Flowchart.py000066400000000000000000001052601421045507400241530ustar00rootroot00000000000000__init__ = ["Flowchart", "FlowchartGraphicsItem", "FlowchartNode"] import importlib from collections import OrderedDict from .. import DataTreeWidget, FileDialog from ..Qt import QT_LIB, QtCore, QtWidgets from .Node import Node FlowchartCtrlTemplate = importlib.import_module( f'.FlowchartCtrlTemplate_{QT_LIB.lower()}', package=__package__) from numpy import ndarray from .. import configfile as configfile from .. import dockarea as dockarea from .. import functions as fn from ..debug import printExc from ..graphicsItems.GraphicsObject import GraphicsObject from . import FlowchartGraphicsView from .library import LIBRARY from .Terminal import Terminal def strDict(d): return dict([(str(k), v) for k, v in d.items()]) class Flowchart(Node): sigFileLoaded = QtCore.Signal(object) sigFileSaved = QtCore.Signal(object) #sigOutputChanged = QtCore.Signal() ## inherited from Node sigChartLoaded = QtCore.Signal() sigStateChanged = QtCore.Signal() # called when output is expected to have changed sigChartChanged = QtCore.Signal(object, object, object) # called when nodes are added, removed, or renamed. # (self, action, node) def __init__(self, terminals=None, name=None, filePath=None, library=None): self.library = library or LIBRARY if name is None: name = "Flowchart" if terminals is None: terminals = {} self.filePath = filePath Node.__init__(self, name, allowAddInput=True, allowAddOutput=True) ## create node without terminals; we'll add these later self.inputWasSet = False ## flag allows detection of changes in the absence of input change. self._nodes = {} self.nextZVal = 10 #self.connects = [] #self._chartGraphicsItem = FlowchartGraphicsItem(self) self._widget = None self._scene = None self.processing = False ## flag that prevents recursive node updates self.widget() self.inputNode = Node('Input', allowRemove=False, allowAddOutput=True) self.outputNode = Node('Output', allowRemove=False, allowAddInput=True) self.addNode(self.inputNode, 'Input', [-150, 0]) self.addNode(self.outputNode, 'Output', [300, 0]) self.outputNode.sigOutputChanged.connect(self.outputChanged) self.outputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) self.inputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) self.outputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) self.inputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) self.viewBox.autoRange(padding = 0.04) for name, opts in terminals.items(): self.addTerminal(name, **opts) def setLibrary(self, lib): self.library = lib self.widget().chartWidget.buildMenu() def setInput(self, **args): """Set the input values of the flowchart. This will automatically propagate the new values throughout the flowchart, (possibly) causing the output to change. """ #print "setInput", args #Node.setInput(self, **args) #print " ....." self.inputWasSet = True self.inputNode.setOutput(**args) def outputChanged(self): ## called when output of internal node has changed vals = self.outputNode.inputValues() self.widget().outputChanged(vals) self.setOutput(**vals) #self.sigOutputChanged.emit(self) def output(self): """Return a dict of the values on the Flowchart's output terminals. """ return self.outputNode.inputValues() def nodes(self): return self._nodes def addTerminal(self, name, **opts): term = Node.addTerminal(self, name, **opts) name = term.name() if opts['io'] == 'in': ## inputs to the flowchart become outputs on the input node opts['io'] = 'out' opts['multi'] = False self.inputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) try: term2 = self.inputNode.addTerminal(name, **opts) finally: self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) else: opts['io'] = 'in' #opts['multi'] = False self.outputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) try: term2 = self.outputNode.addTerminal(name, **opts) finally: self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) return term def removeTerminal(self, name): #print "remove:", name term = self[name] inTerm = self.internalTerminal(term) Node.removeTerminal(self, name) inTerm.node().removeTerminal(inTerm.name()) def internalTerminalRenamed(self, term, oldName): self[oldName].rename(term.name()) def internalTerminalAdded(self, node, term): if term._io == 'in': io = 'out' else: io = 'in' Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable()) def internalTerminalRemoved(self, node, term): try: Node.removeTerminal(self, term.name()) except KeyError: pass def terminalRenamed(self, term, oldName): newName = term.name() #print "flowchart rename", newName, oldName #print self.terminals Node.terminalRenamed(self, self[oldName], oldName) #print self.terminals for n in [self.inputNode, self.outputNode]: if oldName in n.terminals: n[oldName].rename(newName) def createNode(self, nodeType, name=None, pos=None): """Create a new Node and add it to this flowchart. """ if name is None: n = 0 while True: name = "%s.%d" % (nodeType, n) if name not in self._nodes: break n += 1 node = self.library.getNodeType(nodeType)(name) self.addNode(node, name, pos) return node def addNode(self, node, name, pos=None): """Add an existing Node to this flowchart. See also: createNode() """ if pos is None: pos = [0, 0] if type(pos) in [QtCore.QPoint, QtCore.QPointF]: pos = [pos.x(), pos.y()] item = node.graphicsItem() item.setZValue(self.nextZVal*2) self.nextZVal += 1 self.viewBox.addItem(item) item.moveBy(*pos) self._nodes[name] = node if node is not self.inputNode and node is not self.outputNode: self.widget().addNode(node) node.sigClosed.connect(self.nodeClosed) node.sigRenamed.connect(self.nodeRenamed) node.sigOutputChanged.connect(self.nodeOutputChanged) self.sigChartChanged.emit(self, 'add', node) def removeNode(self, node): """Remove a Node from this flowchart. """ node.close() def nodeClosed(self, node): del self._nodes[node.name()] self.widget().removeNode(node) for signal, slot in [('sigClosed', self.nodeClosed), ('sigRenamed', self.nodeRenamed), ('sigOutputChanged', self.nodeOutputChanged)]: try: getattr(node, signal).disconnect(slot) except (TypeError, RuntimeError): pass self.sigChartChanged.emit(self, 'remove', node) def nodeRenamed(self, node, oldName): del self._nodes[oldName] self._nodes[node.name()] = node self.widget().nodeRenamed(node, oldName) self.sigChartChanged.emit(self, 'rename', node) def arrangeNodes(self): pass def internalTerminal(self, term): """If the terminal belongs to the external Node, return the corresponding internal terminal""" if term.node() is self: if term.isInput(): return self.inputNode[term.name()] else: return self.outputNode[term.name()] else: return term def connectTerminals(self, term1, term2): """Connect two terminals together within this flowchart.""" term1 = self.internalTerminal(term1) term2 = self.internalTerminal(term2) term1.connectTo(term2) def process(self, **args): """ Process data through the flowchart, returning the output. Keyword arguments must be the names of input terminals. The return value is a dict with one key per output terminal. """ data = {} ## Stores terminal:value pairs ## determine order of operations ## order should look like [('p', node1), ('p', node2), ('d', terminal1), ...] ## Each tuple specifies either (p)rocess this node or (d)elete the result from this terminal order = self.processOrder() #print "ORDER:", order ## Record inputs given to process() for n, t in self.inputNode.outputs().items(): # if n not in args: # raise Exception("Parameter %s required to process this chart." % n) if n in args: data[t] = args[n] ret = {} ## process all in order for c, arg in order: if c == 'p': ## Process a single node #print "===> process:", arg node = arg if node is self.inputNode: continue ## input node has already been processed. ## get input and output terminals for this node outs = list(node.outputs().values()) ins = list(node.inputs().values()) ## construct input value dictionary args = {} for inp in ins: inputs = inp.inputTerminals() if len(inputs) == 0: continue if inp.isMultiValue(): ## multi-input terminals require a dict of all inputs args[inp.name()] = dict([(i, data[i]) for i in inputs if i in data]) else: ## single-inputs terminals only need the single input value available args[inp.name()] = data[inputs[0]] if node is self.outputNode: ret = args ## we now have the return value, but must keep processing in case there are other endpoint nodes in the chart else: try: if node.isBypassed(): result = node.processBypassed(args) else: result = node.process(display=False, **args) except: print("Error processing node %s. Args are: %s" % (str(node), str(args))) raise for out in outs: #print " Output:", out, out.name() #print out.name() try: data[out] = result[out.name()] except KeyError: pass elif c == 'd': ## delete a terminal result (no longer needed; may be holding a lot of memory) #print "===> delete", arg if arg in data: del data[arg] return ret def processOrder(self): """Return the order of operations required to process this chart. The order returned should look like [('p', node1), ('p', node2), ('d', terminal1), ...] where each tuple specifies either (p)rocess this node or (d)elete the result from this terminal """ ## first collect list of nodes/terminals and their dependencies deps = {} tdeps = {} ## {terminal: [nodes that depend on terminal]} for name, node in self._nodes.items(): deps[node] = node.dependentNodes() for t in node.outputs().values(): tdeps[t] = t.dependentNodes() #print "DEPS:", deps ## determine correct node-processing order order = fn.toposort(deps) #print "ORDER1:", order ## construct list of operations ops = [('p', n) for n in order] ## determine when it is safe to delete terminal values dels = [] for t, nodes in tdeps.items(): lastInd = 0 lastNode = None for n in nodes: ## determine which node is the last to be processed according to order if n is self: lastInd = None break else: try: ind = order.index(n) except ValueError: continue if lastNode is None or ind > lastInd: lastNode = n lastInd = ind if lastInd is not None: dels.append((lastInd+1, t)) dels.sort(key=lambda a: a[0], reverse=True) for i, t in dels: ops.insert(i, ('d', t)) return ops def nodeOutputChanged(self, startNode): """Triggered when a node's output values have changed. (NOT called during process()) Propagates new data forward through network.""" ## first collect list of nodes/terminals and their dependencies if self.processing: return self.processing = True try: deps = {} for name, node in self._nodes.items(): deps[node] = [] for t in node.outputs().values(): deps[node].extend(t.dependentNodes()) ## determine order of updates order = fn.toposort(deps, nodes=[startNode]) order.reverse() ## keep track of terminals that have been updated terms = set(startNode.outputs().values()) #print "======= Updating", startNode # print("Order:", order) for node in order[1:]: # print("Processing node", node) update = False for term in list(node.inputs().values()): # print(" checking terminal", term) deps = list(term.connections().keys()) for d in deps: if d in terms: # print(" ..input", d, "changed") update |= True term.inputChanged(d, process=False) if update: # print(" processing..") node.update() terms |= set(node.outputs().values()) finally: self.processing = False if self.inputWasSet: self.inputWasSet = False else: self.sigStateChanged.emit() def chartGraphicsItem(self): """Return the graphicsItem that displays the internal nodes and connections of this flowchart. Note that the similar method `graphicsItem()` is inherited from Node and returns the *external* graphical representation of this flowchart.""" return self.viewBox def widget(self): """Return the control widget for this flowchart. This widget provides GUI access to the parameters for each node and a graphical representation of the flowchart. """ if self._widget is None: self._widget = FlowchartCtrlWidget(self) self.scene = self._widget.scene() self.viewBox = self._widget.viewBox() return self._widget def listConnections(self): conn = set() for n in self._nodes.values(): terms = n.outputs() for t in terms.values(): for c in t.connections(): conn.add((t, c)) return conn def saveState(self): """Return a serializable data structure representing the current state of this flowchart. """ state = Node.saveState(self) state['nodes'] = [] state['connects'] = [] for name, node in self._nodes.items(): cls = type(node) if hasattr(cls, 'nodeName'): clsName = cls.nodeName pos = node.graphicsItem().pos() ns = {'class': clsName, 'name': name, 'pos': (pos.x(), pos.y()), 'state': node.saveState()} state['nodes'].append(ns) conn = self.listConnections() for a, b in conn: state['connects'].append((a.node().name(), a.name(), b.node().name(), b.name())) state['inputNode'] = self.inputNode.saveState() state['outputNode'] = self.outputNode.saveState() return state def restoreState(self, state, clear=False): """Restore the state of this flowchart from a previous call to `saveState()`. """ self.blockSignals(True) try: if clear: self.clear() Node.restoreState(self, state) nodes = state['nodes'] nodes.sort(key=lambda a: a['pos'][0]) for n in nodes: if n['name'] in self._nodes: self._nodes[n['name']].restoreState(n['state']) continue try: node = self.createNode(n['class'], name=n['name']) node.restoreState(n['state']) except: printExc("Error creating node %s: (continuing anyway)" % n['name']) self.inputNode.restoreState(state.get('inputNode', {})) self.outputNode.restoreState(state.get('outputNode', {})) #self.restoreTerminals(state['terminals']) for n1, t1, n2, t2 in state['connects']: try: self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2]) except: print(self._nodes[n1].terminals) print(self._nodes[n2].terminals) printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2)) finally: self.blockSignals(False) self.outputChanged() self.sigChartLoaded.emit() self.sigStateChanged.emit() def loadFile(self, fileName=None, startDir=None): """Load a flowchart (``*.fc``) file. """ if fileName is None: if startDir is None: startDir = self.filePath if startDir is None: startDir = '.' self.fileDialog = FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") self.fileDialog.show() 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.. state = configfile.readConfigFile(fileName) self.restoreState(state, clear=True) self.viewBox.autoRange() self.sigFileLoaded.emit(fileName) def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'): """Save this flowchart to a .fc file """ if fileName is None: if startDir is None: startDir = self.filePath if startDir is None: startDir = '.' self.fileDialog = FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") self.fileDialog.setDefaultSuffix("fc") self.fileDialog.setAcceptMode(QtWidgets.QFileDialog.AcceptMode.AcceptSave) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.saveFile) return configfile.writeConfigFile(self.saveState(), fileName) self.sigFileSaved.emit(fileName) def clear(self): """Remove all nodes from this flowchart except the original input/output nodes. """ for n in list(self._nodes.values()): if n is self.inputNode or n is self.outputNode: continue n.close() ## calls self.nodeClosed(n) by signal #self.clearTerminals() self.widget().clear() def clearTerminals(self): Node.clearTerminals(self) self.inputNode.clearTerminals() self.outputNode.clearTerminals() class FlowchartGraphicsItem(GraphicsObject): def __init__(self, chart): GraphicsObject.__init__(self) self.chart = chart ## chart is an instance of Flowchart() self.updateTerminals() def updateTerminals(self): self.terminals = {} bounds = self.boundingRect() inp = self.chart.inputs() dy = bounds.height() / (len(inp)+1) y = dy for n, t in inp.items(): item = t.graphicsItem() self.terminals[n] = item item.setParentItem(self) item.setAnchor(bounds.width(), y) y += dy out = self.chart.outputs() dy = bounds.height() / (len(out)+1) y = dy for n, t in out.items(): item = t.graphicsItem() self.terminals[n] = item item.setParentItem(self) item.setAnchor(0, y) y += dy def boundingRect(self): #print "FlowchartGraphicsItem.boundingRect" return QtCore.QRectF() def paint(self, p, *args): #print "FlowchartGraphicsItem.paint" pass #p.drawRect(self.boundingRect()) class FlowchartCtrlWidget(QtWidgets.QWidget): """The widget that contains the list of all the nodes in a flowchart and their controls, as well as buttons for loading/saving flowcharts.""" def __init__(self, chart): self.items = {} #self.loadDir = loadDir ## where to look initially for chart files self.currentFileName = None QtWidgets.QWidget.__init__(self) self.chart = chart self.ui = FlowchartCtrlTemplate.Ui_Form() self.ui.setupUi(self) self.ui.ctrlList.setColumnCount(2) #self.ui.ctrlList.setColumnWidth(0, 200) self.ui.ctrlList.setColumnWidth(1, 20) self.ui.ctrlList.setVerticalScrollMode(self.ui.ctrlList.ScrollMode.ScrollPerPixel) self.ui.ctrlList.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.chartWidget = FlowchartWidget(chart, self) #self.chartWidget.viewBox().autoRange() self.cwWin = QtWidgets.QMainWindow() self.cwWin.setWindowTitle('Flowchart') self.cwWin.setCentralWidget(self.chartWidget) self.cwWin.resize(1000,800) h = self.ui.ctrlList.header() h.setSectionResizeMode(0, h.ResizeMode.Stretch) self.ui.ctrlList.itemChanged.connect(self.itemChanged) self.ui.loadBtn.clicked.connect(self.loadClicked) self.ui.saveBtn.clicked.connect(self.saveClicked) self.ui.saveAsBtn.clicked.connect(self.saveAsClicked) self.ui.showChartBtn.toggled.connect(self.chartToggled) self.chart.sigFileLoaded.connect(self.setCurrentFile) self.ui.reloadBtn.clicked.connect(self.reloadClicked) self.chart.sigFileSaved.connect(self.fileSaved) #def resizeEvent(self, ev): #QtWidgets.QWidget.resizeEvent(self, ev) #self.ui.ctrlList.setColumnWidth(0, self.ui.ctrlList.viewport().width()-20) def chartToggled(self, b): if b: self.cwWin.show() else: self.cwWin.hide() def reloadClicked(self): try: self.chartWidget.reloadLibrary() self.ui.reloadBtn.success("Reloaded.") except: self.ui.reloadBtn.success("Error.") raise def loadClicked(self): newFile = self.chart.loadFile() #self.setCurrentFile(newFile) def fileSaved(self, fileName): self.setCurrentFile(fileName) self.ui.saveBtn.success("Saved.") def saveClicked(self): if self.currentFileName is None: self.saveAsClicked() else: try: self.chart.saveFile(self.currentFileName) #self.ui.saveBtn.success("Saved.") except: self.ui.saveBtn.failure("Error") raise def saveAsClicked(self): try: if self.currentFileName is None: newFile = self.chart.saveFile() else: newFile = self.chart.saveFile(suggestedFileName=self.currentFileName) #self.ui.saveAsBtn.success("Saved.") #print "Back to saveAsClicked." except: self.ui.saveBtn.failure("Error") raise #self.setCurrentFile(newFile) def setCurrentFile(self, fileName): self.currentFileName = fileName if fileName is None: self.ui.fileNameLabel.setText("[ new ]") else: self.ui.fileNameLabel.setText("%s" % os.path.split(self.currentFileName)[1]) self.resizeEvent(None) def itemChanged(self, *args): pass def scene(self): return self.chartWidget.scene() ## returns the GraphicsScene object def viewBox(self): return self.chartWidget.viewBox() def nodeRenamed(self, node, oldName): self.items[node].setText(0, node.name()) def addNode(self, node): ctrl = node.ctrlWidget() #if ctrl is None: #return item = QtWidgets.QTreeWidgetItem([node.name(), '', '']) self.ui.ctrlList.addTopLevelItem(item) byp = QtWidgets.QPushButton('X') byp.setCheckable(True) byp.setFixedWidth(20) item.bypassBtn = byp self.ui.ctrlList.setItemWidget(item, 1, byp) byp.node = node node.bypassButton = byp byp.setChecked(node.isBypassed()) byp.clicked.connect(self.bypassClicked) if ctrl is not None: item2 = QtWidgets.QTreeWidgetItem() item.addChild(item2) self.ui.ctrlList.setItemWidget(item2, 0, ctrl) self.items[node] = item def removeNode(self, node): if node in self.items: item = self.items[node] #self.disconnect(item.bypassBtn, QtCore.SIGNAL('clicked()'), self.bypassClicked) try: item.bypassBtn.clicked.disconnect(self.bypassClicked) except (TypeError, RuntimeError): pass self.ui.ctrlList.removeTopLevelItem(item) def bypassClicked(self): btn = QtCore.QObject.sender(self) btn.node.bypass(btn.isChecked()) def chartWidget(self): return self.chartWidget def outputChanged(self, data): pass #self.ui.outputTree.setData(data, hideRoot=True) def clear(self): self.chartWidget.clear() def select(self, node): item = self.items[node] self.ui.ctrlList.setCurrentItem(item) def clearSelection(self): self.ui.ctrlList.selectionModel().clearSelection() class FlowchartWidget(dockarea.DockArea): """Includes the actual graphical flowchart and debugging interface""" def __init__(self, chart, ctrl): #QtWidgets.QWidget.__init__(self) dockarea.DockArea.__init__(self) self.chart = chart self.ctrl = ctrl self.hoverItem = None #self.setMinimumWidth(250) #self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding)) #self.ui = FlowchartTemplate.Ui_Form() #self.ui.setupUi(self) ## build user interface (it was easier to do it here than via developer) self.view = FlowchartGraphicsView.FlowchartGraphicsView(self) self.viewDock = dockarea.Dock('view', size=(1000,600)) self.viewDock.addWidget(self.view) self.viewDock.hideTitleBar() self.addDock(self.viewDock) self.hoverText = QtWidgets.QTextEdit() self.hoverText.setReadOnly(True) self.hoverDock = dockarea.Dock('Hover Info', size=(1000,20)) self.hoverDock.addWidget(self.hoverText) self.addDock(self.hoverDock, 'bottom') self.selInfo = QtWidgets.QWidget() self.selInfoLayout = QtWidgets.QGridLayout() self.selInfo.setLayout(self.selInfoLayout) self.selDescLabel = QtWidgets.QLabel() self.selNameLabel = QtWidgets.QLabel() self.selDescLabel.setWordWrap(True) self.selectedTree = DataTreeWidget() #self.selectedTree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded) #self.selInfoLayout.addWidget(self.selNameLabel) self.selInfoLayout.addWidget(self.selDescLabel) self.selInfoLayout.addWidget(self.selectedTree) self.selDock = dockarea.Dock('Selected Node', size=(1000,200)) self.selDock.addWidget(self.selInfo) self.addDock(self.selDock, 'bottom') self._scene = self.view.scene() self._viewBox = self.view.viewBox() #self._scene = QtWidgets.QGraphicsScene() #self._scene = FlowchartGraphicsView.FlowchartGraphicsScene() #self.view.setScene(self._scene) self.buildMenu() #self.ui.addNodeBtn.mouseReleaseEvent = self.addNodeBtnReleased self._scene.selectionChanged.connect(self.selectionChanged) self._scene.sigMouseHover.connect(self.hoverOver) #self.view.sigClicked.connect(self.showViewMenu) #self._scene.sigSceneContextMenu.connect(self.showViewMenu) #self._viewBox.sigActionPositionChanged.connect(self.menuPosChanged) def reloadLibrary(self): #QtCore.QObject.disconnect(self.nodeMenu, QtCore.SIGNAL('triggered(QAction*)'), self.nodeMenuTriggered) self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered) self.nodeMenu = None self.subMenus = [] self.chart.library.reload() self.buildMenu() def buildMenu(self, pos=None): def buildSubMenu(node, rootMenu, subMenus, pos=None): for section, node in node.items(): if isinstance(node, OrderedDict): menu = QtWidgets.QMenu(section) rootMenu.addMenu(menu) buildSubMenu(node, menu, subMenus, pos=pos) subMenus.append(menu) else: act = rootMenu.addAction(section) act.nodeType = section act.pos = pos self.nodeMenu = QtWidgets.QMenu() self.subMenus = [] buildSubMenu(self.chart.library.getNodeTree(), self.nodeMenu, self.subMenus, pos=pos) self.nodeMenu.triggered.connect(self.nodeMenuTriggered) return self.nodeMenu def menuPosChanged(self, pos): self.menuPos = pos def showViewMenu(self, ev): #QtWidgets.QPushButton.mouseReleaseEvent(self.ui.addNodeBtn, ev) #if ev.button() == QtCore.Qt.MouseButton.RightButton: #self.menuPos = self.view.mapToScene(ev.pos()) #self.nodeMenu.popup(ev.globalPos()) #print "Flowchart.showViewMenu called" #self.menuPos = ev.scenePos() self.buildMenu(ev.scenePos()) self.nodeMenu.popup(ev.screenPos()) def scene(self): return self._scene ## the GraphicsScene item def viewBox(self): return self._viewBox ## the viewBox that items should be added to def nodeMenuTriggered(self, action): nodeType = action.nodeType if action.pos is not None: pos = action.pos else: pos = self.menuPos pos = self.viewBox().mapSceneToView(pos) self.chart.createNode(nodeType, pos=pos) def selectionChanged(self): #print "FlowchartWidget.selectionChanged called." items = self._scene.selectedItems() #print " scene.selectedItems: ", items if len(items) == 0: data = None else: item = items[0] if hasattr(item, 'node') and isinstance(item.node, Node): n = item.node if n in self.ctrl.items: self.ctrl.select(n) else: self.ctrl.clearSelection() data = {'outputs': n.outputValues(), 'inputs': n.inputValues()} self.selNameLabel.setText(n.name()) if hasattr(n, 'nodeName'): self.selDescLabel.setText("%s: %s" % (n.nodeName, n.__class__.__doc__)) else: self.selDescLabel.setText("") if n.exception is not None: data['exception'] = n.exception else: data = None self.selectedTree.setData(data, hideRoot=True) def hoverOver(self, items): #print "FlowchartWidget.hoverOver called." term = None for item in items: if item is self.hoverItem: return self.hoverItem = item if hasattr(item, 'term') and isinstance(item.term, Terminal): term = item.term break if term is None: self.hoverText.setPlainText("") else: val = term.value() if isinstance(val, ndarray): val = "%s %s %s" % (type(val).__name__, str(val.shape), str(val.dtype)) else: val = str(val) if len(val) > 400: val = val[:400] + "..." self.hoverText.setPlainText("%s.%s = %s" % (term.node().name(), term.name(), val)) #self.hoverLabel.setCursorPosition(0) def clear(self): #self.outputTree.setData(None) self.selectedTree.setData(None) self.hoverText.setPlainText('') self.selNameLabel.setText('') self.selDescLabel.setText('') class FlowchartNode(Node): pass pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui000066400000000000000000000056451421045507400264670ustar00rootroot00000000000000 Form 0 0 217 499 PyQtGraph 0 0 Load.. Flowchart true false false false false 1 true Qt::AlignCenter TreeWidget QTreeWidget
      ..widgets.TreeWidget
      FeedbackButton QPushButton
      ..widgets.FeedbackButton
      pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt5.py000066400000000000000000000055111421045507400276340ustar00rootroot00000000000000 # Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartCtrlTemplate.ui' # # Created: Wed Mar 26 15:09:28 2014 # by: PyQt5 UI code generator 5.0.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(217, 499) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setVerticalSpacing(0) self.gridLayout.setObjectName("gridLayout") self.loadBtn = QtWidgets.QPushButton(Form) self.loadBtn.setObjectName("loadBtn") self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) self.saveBtn = FeedbackButton(Form) self.saveBtn.setObjectName("saveBtn") self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) self.saveAsBtn = FeedbackButton(Form) self.saveAsBtn.setObjectName("saveAsBtn") self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) self.reloadBtn = FeedbackButton(Form) self.reloadBtn.setCheckable(False) self.reloadBtn.setFlat(False) self.reloadBtn.setObjectName("reloadBtn") self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) self.showChartBtn = QtWidgets.QPushButton(Form) self.showChartBtn.setCheckable(True) self.showChartBtn.setObjectName("showChartBtn") self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) self.ctrlList = TreeWidget(Form) self.ctrlList.setObjectName("ctrlList") self.ctrlList.headerItem().setText(0, "1") self.ctrlList.header().setVisible(False) self.ctrlList.header().setStretchLastSection(False) self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) self.fileNameLabel = QtWidgets.QLabel(Form) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.fileNameLabel.setFont(font) self.fileNameLabel.setText("") self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) self.fileNameLabel.setObjectName("fileNameLabel") self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.loadBtn.setText(_translate("Form", "Load..")) self.saveBtn.setText(_translate("Form", "Save")) self.saveAsBtn.setText(_translate("Form", "As..")) self.reloadBtn.setText(_translate("Form", "Reload Libs")) self.showChartBtn.setText(_translate("Form", "Flowchart")) from ..widgets.FeedbackButton import FeedbackButton from ..widgets.TreeWidget import TreeWidget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt6.py000066400000000000000000000055711421045507400276430ustar00rootroot00000000000000# Form implementation generated from reading ui file '../pyqtgraph/flowchart/FlowchartCtrlTemplate.ui' # # Created by: PyQt6 UI code generator 6.1.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(217, 499) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setVerticalSpacing(0) self.gridLayout.setObjectName("gridLayout") self.loadBtn = QtWidgets.QPushButton(Form) self.loadBtn.setObjectName("loadBtn") self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) self.saveBtn = FeedbackButton(Form) self.saveBtn.setObjectName("saveBtn") self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) self.saveAsBtn = FeedbackButton(Form) self.saveAsBtn.setObjectName("saveAsBtn") self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) self.reloadBtn = FeedbackButton(Form) self.reloadBtn.setCheckable(False) self.reloadBtn.setFlat(False) self.reloadBtn.setObjectName("reloadBtn") self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) self.showChartBtn = QtWidgets.QPushButton(Form) self.showChartBtn.setCheckable(True) self.showChartBtn.setObjectName("showChartBtn") self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) self.ctrlList = TreeWidget(Form) self.ctrlList.setObjectName("ctrlList") self.ctrlList.headerItem().setText(0, "1") self.ctrlList.header().setVisible(False) self.ctrlList.header().setStretchLastSection(False) self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) self.fileNameLabel = QtWidgets.QLabel(Form) font = QtGui.QFont() font.setBold(True) self.fileNameLabel.setFont(font) self.fileNameLabel.setText("") self.fileNameLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.fileNameLabel.setObjectName("fileNameLabel") self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.loadBtn.setText(_translate("Form", "Load..")) self.saveBtn.setText(_translate("Form", "Save")) self.saveAsBtn.setText(_translate("Form", "As..")) self.reloadBtn.setText(_translate("Form", "Reload Libs")) self.showChartBtn.setText(_translate("Form", "Flowchart")) from ..widgets.FeedbackButton import FeedbackButton from ..widgets.TreeWidget import TreeWidget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside2.py000066400000000000000000000057101421045507400301320ustar00rootroot00000000000000 # Form implementation generated from reading ui file 'FlowchartCtrlTemplate.ui' # # Created: Sun Sep 18 19:16:46 2016 # by: pyside2-uic running on PySide2 2.0.0~alpha0 # # WARNING! All changes made in this file will be lost! from PySide2 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(217, 499) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setVerticalSpacing(0) self.gridLayout.setObjectName("gridLayout") self.loadBtn = QtWidgets.QPushButton(Form) self.loadBtn.setObjectName("loadBtn") self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) self.saveBtn = FeedbackButton(Form) self.saveBtn.setObjectName("saveBtn") self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) self.saveAsBtn = FeedbackButton(Form) self.saveAsBtn.setObjectName("saveAsBtn") self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) self.reloadBtn = FeedbackButton(Form) self.reloadBtn.setCheckable(False) self.reloadBtn.setFlat(False) self.reloadBtn.setObjectName("reloadBtn") self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) self.showChartBtn = QtWidgets.QPushButton(Form) self.showChartBtn.setCheckable(True) self.showChartBtn.setObjectName("showChartBtn") self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) self.ctrlList = TreeWidget(Form) self.ctrlList.setObjectName("ctrlList") self.ctrlList.headerItem().setText(0, "1") self.ctrlList.header().setVisible(False) self.ctrlList.header().setStretchLastSection(False) self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) self.fileNameLabel = QtWidgets.QLabel(Form) font = QtGui.QFont() font.setWeight(75) font.setBold(True) self.fileNameLabel.setFont(font) self.fileNameLabel.setText("") self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) self.fileNameLabel.setObjectName("fileNameLabel") self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1)) self.loadBtn.setText(QtWidgets.QApplication.translate("Form", "Load..", None, -1)) self.saveBtn.setText(QtWidgets.QApplication.translate("Form", "Save", None, -1)) self.saveAsBtn.setText(QtWidgets.QApplication.translate("Form", "As..", None, -1)) self.reloadBtn.setText(QtWidgets.QApplication.translate("Form", "Reload Libs", None, -1)) self.showChartBtn.setText(QtWidgets.QApplication.translate("Form", "Flowchart", None, -1)) from ..widgets.FeedbackButton import FeedbackButton from ..widgets.TreeWidget import TreeWidget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside6.py000066400000000000000000000062671421045507400301460ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'FlowchartCtrlTemplate.ui' ## ## Created by: Qt User Interface Compiler version 6.1.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * from ..widgets.TreeWidget import TreeWidget from ..widgets.FeedbackButton import FeedbackButton class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(217, 499) self.gridLayout = QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName(u"gridLayout") self.gridLayout.setVerticalSpacing(0) self.loadBtn = QPushButton(Form) self.loadBtn.setObjectName(u"loadBtn") self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) self.saveBtn = FeedbackButton(Form) self.saveBtn.setObjectName(u"saveBtn") self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) self.saveAsBtn = FeedbackButton(Form) self.saveAsBtn.setObjectName(u"saveAsBtn") self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) self.reloadBtn = FeedbackButton(Form) self.reloadBtn.setObjectName(u"reloadBtn") self.reloadBtn.setCheckable(False) self.reloadBtn.setFlat(False) self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) self.showChartBtn = QPushButton(Form) self.showChartBtn.setObjectName(u"showChartBtn") self.showChartBtn.setCheckable(True) self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) self.ctrlList = TreeWidget(Form) __qtreewidgetitem = QTreeWidgetItem() __qtreewidgetitem.setText(0, u"1"); self.ctrlList.setHeaderItem(__qtreewidgetitem) self.ctrlList.setObjectName(u"ctrlList") self.ctrlList.header().setVisible(False) self.ctrlList.header().setStretchLastSection(False) self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) self.fileNameLabel = QLabel(Form) self.fileNameLabel.setObjectName(u"fileNameLabel") font = QFont() font.setBold(True) self.fileNameLabel.setFont(font) self.fileNameLabel.setAlignment(Qt.AlignCenter) self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) self.loadBtn.setText(QCoreApplication.translate("Form", u"Load..", None)) self.saveBtn.setText(QCoreApplication.translate("Form", u"Save", None)) self.saveAsBtn.setText(QCoreApplication.translate("Form", u"As..", None)) self.reloadBtn.setText(QCoreApplication.translate("Form", u"Reload Libs", None)) self.showChartBtn.setText(QCoreApplication.translate("Form", u"Flowchart", None)) self.fileNameLabel.setText("") # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartGraphicsView.py000066400000000000000000000025651421045507400264730ustar00rootroot00000000000000from ..graphicsItems.ViewBox import ViewBox from ..Qt import QtCore, QtGui, QtWidgets from ..widgets.GraphicsView import GraphicsView translate = QtCore.QCoreApplication.translate class FlowchartGraphicsView(GraphicsView): sigHoverOver = QtCore.Signal(object) sigClicked = QtCore.Signal(object) def __init__(self, widget, *args): GraphicsView.__init__(self, *args, useOpenGL=False) self._vb = FlowchartViewBox(widget, lockAspect=True, invertY=True) self.setCentralItem(self._vb) self.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True) def viewBox(self): return self._vb class FlowchartViewBox(ViewBox): def __init__(self, widget, *args, **kwargs): ViewBox.__init__(self, *args, **kwargs) self.widget = widget def getMenu(self, ev): ## called by ViewBox to create a new context menu self._fc_menu = QtWidgets.QMenu() self._subMenus = self.getContextMenus(ev) for menu in self._subMenus: self._fc_menu.addMenu(menu) return self._fc_menu def getContextMenus(self, ev): ## called by scene to add menus on to someone else's context menu menu = self.widget.buildMenu(ev.scenePos()) menu.setTitle(translate("Context Menu", "Add node")) return [menu, ViewBox.getMenu(self, ev)] pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartTemplate.ui000066400000000000000000000044001421045507400256260ustar00rootroot00000000000000 Form 0 0 529 329 PyQtGraph 260 10 264 222 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true true 1 0 240 521 81 0 0 256 192 DataTreeWidget QTreeWidget
      ..widgets.DataTreeWidget
      FlowchartGraphicsView QGraphicsView
      ..flowchart.FlowchartGraphicsView
      pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartTemplate_pyqt5.py000066400000000000000000000045211421045507400270070ustar00rootroot00000000000000 # Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartTemplate.ui' # # Created: Wed Mar 26 15:09:28 2014 # by: PyQt5 UI code generator 5.0.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(529, 329) self.selInfoWidget = QtWidgets.QWidget(Form) self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) self.selInfoWidget.setObjectName("selInfoWidget") self.gridLayout = QtWidgets.QGridLayout(self.selInfoWidget) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.selDescLabel = QtWidgets.QLabel(self.selInfoWidget) self.selDescLabel.setText("") self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.selDescLabel.setWordWrap(True) self.selDescLabel.setObjectName("selDescLabel") self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) self.selNameLabel = QtWidgets.QLabel(self.selInfoWidget) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.selNameLabel.setFont(font) self.selNameLabel.setText("") self.selNameLabel.setObjectName("selNameLabel") self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) self.selectedTree = DataTreeWidget(self.selInfoWidget) self.selectedTree.setObjectName("selectedTree") self.selectedTree.headerItem().setText(0, "1") self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) self.hoverText = QtWidgets.QTextEdit(Form) self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) self.hoverText.setObjectName("hoverText") self.view = FlowchartGraphicsView(Form) self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) self.view.setObjectName("view") self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) from ..widgets.DataTreeWidget import DataTreeWidget from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartTemplate_pyqt6.py000066400000000000000000000046351421045507400270160ustar00rootroot00000000000000# Form implementation generated from reading ui file '../pyqtgraph/flowchart/FlowchartTemplate.ui' # # Created by: PyQt6 UI code generator 6.1.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(529, 329) self.selInfoWidget = QtWidgets.QWidget(Form) self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) self.selInfoWidget.setObjectName("selInfoWidget") self.gridLayout = QtWidgets.QGridLayout(self.selInfoWidget) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.selDescLabel = QtWidgets.QLabel(self.selInfoWidget) self.selDescLabel.setText("") self.selDescLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) self.selDescLabel.setWordWrap(True) self.selDescLabel.setObjectName("selDescLabel") self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) self.selNameLabel = QtWidgets.QLabel(self.selInfoWidget) font = QtGui.QFont() font.setBold(True) self.selNameLabel.setFont(font) self.selNameLabel.setText("") self.selNameLabel.setObjectName("selNameLabel") self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) self.selectedTree = DataTreeWidget(self.selInfoWidget) self.selectedTree.setObjectName("selectedTree") self.selectedTree.headerItem().setText(0, "1") self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) self.hoverText = QtWidgets.QTextEdit(Form) self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) self.hoverText.setObjectName("hoverText") self.view = FlowchartGraphicsView(Form) self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) self.view.setObjectName("view") self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView from ..widgets.DataTreeWidget import DataTreeWidget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartTemplate_pyside2.py000066400000000000000000000044601421045507400273060ustar00rootroot00000000000000 # Form implementation generated from reading ui file 'FlowchartTemplate.ui' # # Created: Sun Sep 18 19:16:03 2016 # by: pyside2-uic running on PySide2 2.0.0~alpha0 # # WARNING! All changes made in this file will be lost! from PySide2 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(529, 329) self.selInfoWidget = QtWidgets.QWidget(Form) self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) self.selInfoWidget.setObjectName("selInfoWidget") self.gridLayout = QtWidgets.QGridLayout(self.selInfoWidget) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.selDescLabel = QtWidgets.QLabel(self.selInfoWidget) self.selDescLabel.setText("") self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.selDescLabel.setWordWrap(True) self.selDescLabel.setObjectName("selDescLabel") self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) self.selNameLabel = QtWidgets.QLabel(self.selInfoWidget) font = QtGui.QFont() font.setWeight(75) font.setBold(True) self.selNameLabel.setFont(font) self.selNameLabel.setText("") self.selNameLabel.setObjectName("selNameLabel") self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) self.selectedTree = DataTreeWidget(self.selInfoWidget) self.selectedTree.setObjectName("selectedTree") self.selectedTree.headerItem().setText(0, "1") self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) self.hoverText = QtWidgets.QTextEdit(Form) self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) self.hoverText.setObjectName("hoverText") self.view = FlowchartGraphicsView(Form) self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) self.view.setObjectName("view") self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1)) from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView from ..widgets.DataTreeWidget import DataTreeWidget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/FlowchartTemplate_pyside6.py000066400000000000000000000047371421045507400273210ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'FlowchartTemplate.ui' ## ## Created by: Qt User Interface Compiler version 6.1.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * from ..widgets.DataTreeWidget import DataTreeWidget from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(529, 329) self.selInfoWidget = QWidget(Form) self.selInfoWidget.setObjectName(u"selInfoWidget") self.selInfoWidget.setGeometry(QRect(260, 10, 264, 222)) self.gridLayout = QGridLayout(self.selInfoWidget) self.gridLayout.setObjectName(u"gridLayout") self.selDescLabel = QLabel(self.selInfoWidget) self.selDescLabel.setObjectName(u"selDescLabel") self.selDescLabel.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignTop) self.selDescLabel.setWordWrap(True) self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) self.selNameLabel = QLabel(self.selInfoWidget) self.selNameLabel.setObjectName(u"selNameLabel") font = QFont() font.setBold(True) self.selNameLabel.setFont(font) self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) self.selectedTree = DataTreeWidget(self.selInfoWidget) __qtreewidgetitem = QTreeWidgetItem() __qtreewidgetitem.setText(0, u"1"); self.selectedTree.setHeaderItem(__qtreewidgetitem) self.selectedTree.setObjectName(u"selectedTree") self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) self.hoverText = QTextEdit(Form) self.hoverText.setObjectName(u"hoverText") self.hoverText.setGeometry(QRect(0, 240, 521, 81)) self.view = FlowchartGraphicsView(Form) self.view.setObjectName(u"view") self.view.setGeometry(QRect(0, 0, 256, 192)) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) self.selDescLabel.setText("") self.selNameLabel.setText("") # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/Node.py000066400000000000000000000632171421045507400231140ustar00rootroot00000000000000__all__ = ["Node", "NodeGraphicsItem"] import sys import warnings from collections import OrderedDict from .. import functions as fn from ..debug import printExc from ..graphicsItems.GraphicsObject import GraphicsObject from ..Qt import QtCore, QtGui, QtWidgets from .Terminal import Terminal translate = QtCore.QCoreApplication.translate def strDict(d): return dict([(str(k), v) for k, v in d.items()]) class Node(QtCore.QObject): """ Node represents the basic processing unit of a flowchart. A Node subclass implements at least: 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 to other by wires between their terminals. A flowchart is, itself, also a special subclass of Node. This allows Nodes within the flowchart to connect to the input/output nodes of the flowchart itself. Optionally, a node class can implement the ctrlWidget() method, which must return a QWidget (usually containing other widgets) that will be displayed in the flowchart control panel. Some nodes implement fairly complex control widgets, but most nodes follow a simple form-like pattern: a list of parameter names and a single value (represented as spin box, check box, etc..) for each parameter. To make this easier, the CtrlNode subclass allows you to instead define a simple data structure that CtrlNode will use to automatically generate the control widget. """ sigOutputChanged = QtCore.Signal(object) # self sigClosed = QtCore.Signal(object) sigRenamed = QtCore.Signal(object, object) sigTerminalRenamed = QtCore.Signal(object, object) # term, oldName sigTerminalAdded = QtCore.Signal(object, object) # self, term sigTerminalRemoved = QtCore.Signal(object, object) # self, term def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True): """ ============== ============================================================ **Arguments:** name The name of this specific node instance. It can be any string, but must be unique within a flowchart. Usually, we simply let the flowchart decide on a name when calling Flowchart.addNode(...) terminals Dict-of-dicts specifying the terminals present on this Node. Terminal specifications look like:: 'inputTerminalName': {'io': 'in'} 'outputTerminalName': {'io': 'out'} There are a number of optional parameters for terminals: multi, pos, renamable, removable, multiable, bypass. See the Terminal class for more information. allowAddInput bool; whether the user is allowed to add inputs by the context menu. allowAddOutput bool; whether the user is allowed to add outputs by the context menu. allowRemove bool; whether the user is allowed to remove this node by the context menu. ============== ============================================================ """ QtCore.QObject.__init__(self) self._name = name self._bypass = False self.bypassButton = None ## this will be set by the flowchart ctrl widget.. self._graphicsItem = None self.terminals = OrderedDict() self._inputs = OrderedDict() self._outputs = OrderedDict() self._allowAddInput = allowAddInput ## flags to allow the user to add/remove terminals self._allowAddOutput = allowAddOutput self._allowRemove = allowRemove self.exception = None if terminals is None: return for name, opts in terminals.items(): self.addTerminal(name, **opts) def nextTerminalName(self, name): """Return an unused terminal name""" name2 = name i = 1 while name2 in self.terminals: name2 = "%s.%d" % (name, i) i += 1 return name2 def addInput(self, name="Input", **args): """Add a new input terminal to this Node with the given name. Extra keyword arguments are passed to Terminal.__init__. This is a convenience function that just calls addTerminal(io='in', ...)""" #print "Node.addInput called." return self.addTerminal(name, io='in', **args) def addOutput(self, name="Output", **args): """Add a new output terminal to this Node with the given name. Extra keyword arguments are passed to Terminal.__init__. This is a convenience function that just calls addTerminal(io='out', ...)""" return self.addTerminal(name, io='out', **args) def removeTerminal(self, term): """Remove the specified terminal from this Node. May specify either the terminal's name or the terminal itself. Causes sigTerminalRemoved to be emitted.""" if isinstance(term, Terminal): name = term.name() else: name = term term = self.terminals[name] #print "remove", name #term.disconnectAll() term.close() del self.terminals[name] if name in self._inputs: del self._inputs[name] if name in self._outputs: del self._outputs[name] self.graphicsItem().updateTerminals() self.sigTerminalRemoved.emit(self, term) def terminalRenamed(self, term, oldName): """Called after a terminal has been renamed Causes sigTerminalRenamed to be emitted.""" newName = term.name() for d in [self.terminals, self._inputs, self._outputs]: if oldName not in d: continue d[newName] = d[oldName] del d[oldName] self.graphicsItem().updateTerminals() self.sigTerminalRenamed.emit(term, oldName) def addTerminal(self, name, **opts): """Add a new terminal to this Node with the given name. Extra keyword arguments are passed to Terminal.__init__. Causes sigTerminalAdded to be emitted.""" name = self.nextTerminalName(name) term = Terminal(self, name, **opts) self.terminals[name] = term if term.isInput(): self._inputs[name] = term elif term.isOutput(): self._outputs[name] = term self.graphicsItem().updateTerminals() self.sigTerminalAdded.emit(self, term) return term def inputs(self): """Return dict of all input terminals. Warning: do not modify.""" return self._inputs def outputs(self): """Return dict of all output terminals. Warning: do not modify.""" return self._outputs def process(self, **kargs): """Process data through this node. This method is called any time the flowchart wants the node to process data. It will be called with one keyword argument corresponding to each input terminal, and must return a dict mapping the name of each output terminal to its new value. This method is also called with a 'display' keyword argument, which indicates whether the node should update its display (if it implements any) while processing this data. This is primarily used to disable expensive display operations during batch processing. """ return {} def graphicsItem(self): """Return the GraphicsItem for this node. Subclasses may re-implement this method to customize their appearance in the flowchart.""" if self._graphicsItem is None: self._graphicsItem = NodeGraphicsItem(self) return self._graphicsItem ## this is just bad planning. Causes too many bugs. def __getattr__(self, attr): """Return the terminal with the given name""" warnings.warn( "Use of note.terminalName is deprecated, use node['terminalName'] instead" "Will be removed from 0.13.0", DeprecationWarning, stacklevel=2 ) if attr not in self.terminals: raise AttributeError(attr) else: import traceback traceback.print_stack() print("Warning: use of node.terminalName is deprecated; use node['terminalName'] instead.") return self.terminals[attr] def __getitem__(self, item): #return getattr(self, item) """Return the terminal with the given name""" if item not in self.terminals: raise KeyError(item) else: return self.terminals[item] def name(self): """Return the name of this node.""" return self._name def rename(self, name): """Rename this node. This will cause sigRenamed to be emitted.""" oldName = self._name self._name = name #self.emit(QtCore.SIGNAL('renamed'), self, oldName) self.sigRenamed.emit(self, oldName) def dependentNodes(self): """Return the list of nodes which provide direct input to this node""" nodes = set() for t in self.inputs().values(): nodes |= set([i.node() for i in t.inputTerminals()]) return nodes #return set([t.inputTerminals().node() for t in self.listInputs().values()]) def __repr__(self): return "" % (self.name(), id(self)) def ctrlWidget(self): """Return this Node's control widget. By default, Nodes have no control widget. Subclasses may reimplement this method to provide a custom widget. This method is called by Flowcharts when they are constructing their Node list.""" return None def bypass(self, byp): """Set whether this node should be bypassed. When bypassed, a Node's process() method is never called. In some cases, data is automatically copied directly from specific input nodes to output nodes instead (see the bypass argument to Terminal.__init__). This is usually called when the user disables a node from the flowchart control panel. """ self._bypass = byp if self.bypassButton is not None: self.bypassButton.setChecked(byp) self.update() def isBypassed(self): """Return True if this Node is currently bypassed.""" return self._bypass def setInput(self, **args): """Set the values on input terminals. For most nodes, this will happen automatically through Terminal.inputChanged. This is normally only used for nodes with no connected inputs.""" changed = False for k, v in args.items(): term = self._inputs[k] oldVal = term.value() if not fn.eq(oldVal, v): changed = True term.setValue(v, process=False) if changed and '_updatesHandled_' not in args: self.update() def inputValues(self): """Return a dict of all input values currently assigned to this node.""" vals = {} for n, t in self.inputs().items(): vals[n] = t.value() return vals def outputValues(self): """Return a dict of all output values currently generated by this node.""" vals = {} for n, t in self.outputs().items(): vals[n] = t.value() return vals def connected(self, localTerm, remoteTerm): """Called whenever one of this node's terminals is connected elsewhere.""" pass def disconnected(self, localTerm, remoteTerm): """Called whenever one of this node's terminals is disconnected from another.""" pass def update(self, signal=True): """Collect all input values, attempt to process new output values, and propagate downstream. Subclasses should call update() whenever thir internal state has changed (such as when the user interacts with the Node's control widget). Update is automatically called when the inputs to the node are changed. """ vals = self.inputValues() #print " inputs:", vals try: if self.isBypassed(): out = self.processBypassed(vals) else: out = self.process(**strDict(vals)) #print " output:", out if out is not None: if signal: self.setOutput(**out) else: self.setOutputNoSignal(**out) for n,t in self.inputs().items(): t.setValueAcceptable(True) self.clearException() except: #printExc( "Exception while processing %s:" % self.name()) for n,t in self.outputs().items(): t.setValue(None) self.setException(sys.exc_info()) if signal: #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data def processBypassed(self, args): """Called when the flowchart would normally call Node.process, but this node is currently bypassed. The default implementation looks for output terminals with a bypass connection and returns the corresponding values. Most Node subclasses will _not_ need to reimplement this method.""" result = {} for term in list(self.outputs().values()): byp = term.bypassValue() if byp is None: result[term.name()] = None else: result[term.name()] = args.get(byp, None) return result def setOutput(self, **vals): self.setOutputNoSignal(**vals) #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data def setOutputNoSignal(self, **vals): for k, v in vals.items(): term = self.outputs()[k] term.setValue(v) #targets = term.connections() #for t in targets: ## propagate downstream #if t is term: #continue #t.inputChanged(term) term.setValueAcceptable(True) def setException(self, exc): self.exception = exc self.recolor() def clearException(self): self.setException(None) def recolor(self): if self.exception is None: self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(0, 0, 0))) else: self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(150, 0, 0), 3)) def saveState(self): """Return a dictionary representing the current state of this node (excluding input / output values). This is used for saving/reloading flowcharts. The default implementation returns this Node's position, bypass state, and information about each of its terminals. Subclasses may want to extend this method, adding extra keys to the returned dict.""" pos = self.graphicsItem().pos() state = {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()} termsEditable = self._allowAddInput | self._allowAddOutput for term in list(self._inputs.values()) + list(self._outputs.values()): termsEditable |= term._renamable | term._removable | term._multiable if termsEditable: state['terminals'] = self.saveTerminals() return state def restoreState(self, state): """Restore the state of this node from a structure previously generated by saveState(). """ pos = state.get('pos', (0,0)) self.graphicsItem().setPos(*pos) self.bypass(state.get('bypass', False)) if 'terminals' in state: self.restoreTerminals(state['terminals']) def saveTerminals(self): terms = OrderedDict() for n, t in self.terminals.items(): terms[n] = (t.saveState()) return terms def restoreTerminals(self, state): for name in list(self.terminals.keys()): if name not in state: self.removeTerminal(name) for name, opts in state.items(): if name in self.terminals: term = self[name] term.setOpts(**opts) continue try: opts = strDict(opts) self.addTerminal(name, **opts) except: printExc("Error restoring terminal %s (%s):" % (str(name), str(opts))) def clearTerminals(self): for t in self.terminals.values(): t.close() self.terminals = OrderedDict() self._inputs = OrderedDict() self._outputs = OrderedDict() def close(self): """Cleans up after the node--removes terminals, graphicsItem, widget""" self.disconnectAll() self.clearTerminals() item = self.graphicsItem() if item.scene() is not None: item.scene().removeItem(item) self._graphicsItem = None w = self.ctrlWidget() if w is not None: w.setParent(None) #self.emit(QtCore.SIGNAL('closed'), self) self.sigClosed.emit(self) def disconnectAll(self): for t in self.terminals.values(): t.disconnectAll() class TextItem(QtWidgets.QGraphicsTextItem): def __init__(self, text, parent, on_update): super().__init__(text, parent) self.on_update = on_update def focusOutEvent(self, ev): super().focusOutEvent(ev) if self.on_update is not None: self.on_update() def keyPressEvent(self, ev): if ev.key() == QtCore.Qt.Key.Key_Enter or ev.key() == QtCore.Qt.Key.Key_Return: if self.on_update is not None: self.on_update() return super().keyPressEvent(ev) #class NodeGraphicsItem(QtWidgets.QGraphicsItem): class NodeGraphicsItem(GraphicsObject): def __init__(self, node): #QtWidgets.QGraphicsItem.__init__(self) GraphicsObject.__init__(self) #QObjectWorkaround.__init__(self) #self.shadow = QtWidgets.QGraphicsDropShadowEffect() #self.shadow.setOffset(5,5) #self.shadow.setBlurRadius(10) #self.setGraphicsEffect(self.shadow) self.pen = fn.mkPen(0,0,0) self.selectPen = fn.mkPen(200,200,200,width=2) self.brush = fn.mkBrush(200, 200, 200, 150) self.hoverBrush = fn.mkBrush(200, 200, 200, 200) self.selectBrush = fn.mkBrush(200, 200, 255, 200) self.hovered = False self.node = node flags = self.GraphicsItemFlag.ItemIsMovable | self.GraphicsItemFlag.ItemIsSelectable | self.GraphicsItemFlag.ItemIsFocusable | self.GraphicsItemFlag.ItemSendsGeometryChanges #flags = self.ItemIsFocusable |self.ItemSendsGeometryChanges self.setFlags(flags) self.bounds = QtCore.QRectF(0, 0, 100, 100) self.nameItem = TextItem(self.node.name(), self, self.labelChanged) self.nameItem.setDefaultTextColor(QtGui.QColor(50, 50, 50)) self.nameItem.moveBy(self.bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) self.nameItem.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextEditorInteraction) self.updateTerminals() #self.setZValue(10) self.menu = None self.buildMenu() #self.node.sigTerminalRenamed.connect(self.updateActionMenu) #def setZValue(self, z): #for t, item in self.terminals.values(): #item.setZValue(z+1) #GraphicsObject.setZValue(self, z) def labelChanged(self): newName = self.nameItem.toPlainText() if newName != self.node.name(): self.node.rename(newName) ### re-center the label bounds = self.boundingRect() self.nameItem.setPos(bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) def setPen(self, *args, **kwargs): self.pen = fn.mkPen(*args, **kwargs) self.update() def setBrush(self, brush): self.brush = brush self.update() def updateTerminals(self): self.terminals = {} inp = self.node.inputs() out = self.node.outputs() maxNode = max(len(inp), len(out)) titleOffset = 25 nodeOffset = 12 # calculate new height newHeight = titleOffset+maxNode*nodeOffset # if current height is not equal to new height, update if not self.bounds.height() == newHeight: self.bounds.setHeight(newHeight) self.update() # Populate inputs y = titleOffset for i, t in inp.items(): item = t.graphicsItem() item.setParentItem(self) #item.setZValue(self.zValue()+1) item.setAnchor(0, y) self.terminals[i] = (t, item) y += nodeOffset # Populate inputs y = titleOffset for i, t in out.items(): item = t.graphicsItem() item.setParentItem(self) item.setZValue(self.zValue()) item.setAnchor(self.bounds.width(), y) self.terminals[i] = (t, item) y += nodeOffset #self.buildMenu() def boundingRect(self): return self.bounds.adjusted(-5, -5, 5, 5) def paint(self, p, *args): p.setPen(self.pen) if self.isSelected(): p.setPen(self.selectPen) p.setBrush(self.selectBrush) else: p.setPen(self.pen) if self.hovered: p.setBrush(self.hoverBrush) else: p.setBrush(self.brush) p.drawRect(self.bounds) def mousePressEvent(self, ev): ev.ignore() def mouseClickEvent(self, ev): #print "Node.mouseClickEvent called." if ev.button() == QtCore.Qt.MouseButton.LeftButton: ev.accept() #print " ev.button: left" sel = self.isSelected() #ret = QtWidgets.QGraphicsItem.mousePressEvent(self, ev) self.setSelected(True) if not sel and self.isSelected(): #self.setBrush(QtGui.QBrush(QtGui.QColor(200, 200, 255))) #self.emit(QtCore.SIGNAL('selected')) #self.scene().selectionChanged.emit() ## for some reason this doesn't seem to be happening automatically self.update() #return ret elif ev.button() == QtCore.Qt.MouseButton.RightButton: #print " ev.button: right" ev.accept() #pos = ev.screenPos() self.raiseContextMenu(ev) #self.menu.popup(QtCore.QPoint(pos.x(), pos.y())) def mouseDragEvent(self, ev): #print "Node.mouseDrag" if ev.button() == QtCore.Qt.MouseButton.LeftButton: ev.accept() self.setPos(self.pos()+self.mapToParent(ev.pos())-self.mapToParent(ev.lastPos())) def hoverEvent(self, ev): if not ev.isExit() and ev.acceptClicks(QtCore.Qt.MouseButton.LeftButton): ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton) self.hovered = True else: self.hovered = False self.update() def keyPressEvent(self, ev): if ev.key() == QtCore.Qt.Key.Key_Delete or ev.key() == QtCore.Qt.Key.Key_Backspace: ev.accept() if not self.node._allowRemove: return self.node.close() else: ev.ignore() def itemChange(self, change, val): if change == self.GraphicsItemChange.ItemPositionHasChanged: for k, t in self.terminals.items(): t[1].nodeMoved() return GraphicsObject.itemChange(self, change, val) def getMenu(self): return self.menu def raiseContextMenu(self, ev): menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) pos = ev.screenPos() menu.popup(QtCore.QPoint(pos.x(), pos.y())) def buildMenu(self): self.menu = QtWidgets.QMenu() self.menu.setTitle(translate("Context Menu", "Node")) a = self.menu.addAction(translate("Context Menu","Add input"), self.addInputFromMenu) if not self.node._allowAddInput: a.setEnabled(False) a = self.menu.addAction(translate("Context Menu", "Add output"), self.addOutputFromMenu) if not self.node._allowAddOutput: a.setEnabled(False) a = self.menu.addAction(translate("Context Menu", "Remove node"), self.node.close) if not self.node._allowRemove: a.setEnabled(False) def addInputFromMenu(self): ## called when add input is clicked in context menu self.node.addInput(renamable=True, removable=True, multiable=True) def addOutputFromMenu(self): ## called when add output is clicked in context menu self.node.addOutput(renamable=True, removable=True, multiable=False) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/NodeLibrary.py000066400000000000000000000050751421045507400244370ustar00rootroot00000000000000from collections import OrderedDict from .Node import Node def isNodeClass(cls): try: if not issubclass(cls, Node): return False except: return False return hasattr(cls, 'nodeName') class NodeLibrary: """ A library of flowchart Node types. Custom libraries may be built to provide each flowchart with a specific set of allowed Node types. """ def __init__(self): self.nodeList = OrderedDict() self.nodeTree = OrderedDict() def addNodeType(self, nodeClass, paths, override=False): """ Register a new node type. If the type's name is already in use, an exception will be raised (unless override=True). ============== ========================================================= **Arguments:** nodeClass a subclass of Node (must have typ.nodeName) paths list of tuples specifying the location(s) this type will appear in the library tree. override if True, overwrite any class having the same name ============== ========================================================= """ if not isNodeClass(nodeClass): raise Exception("Object %s is not a Node subclass" % str(nodeClass)) name = nodeClass.nodeName if not override and name in self.nodeList: raise Exception("Node type name '%s' is already registered." % name) self.nodeList[name] = nodeClass for path in paths: root = self.nodeTree for n in path: if n not in root: root[n] = OrderedDict() root = root[n] root[name] = nodeClass def getNodeType(self, name): try: return self.nodeList[name] except KeyError: raise Exception("No node type called '%s'" % name) def getNodeTree(self): return self.nodeTree def copy(self): """ Return a copy of this library. """ lib = NodeLibrary() lib.nodeList = self.nodeList.copy() lib.nodeTree = self.treeCopy(self.nodeTree) return lib @staticmethod def treeCopy(tree): copy = OrderedDict() for k,v in tree.items(): if isNodeClass(v): copy[k] = v else: copy[k] = NodeLibrary.treeCopy(v) return copy def reload(self): """ Reload Node classes in this library. """ raise NotImplementedError() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/Terminal.py000066400000000000000000000520441421045507400237760ustar00rootroot00000000000000__all__ = ["Terminal", "TerminalGraphicsItem"] import weakref from .. import functions as fn from ..graphicsItems.GraphicsObject import GraphicsObject from ..Point import Point from ..Qt import QtCore, QtGui, QtWidgets translate = QtCore.QCoreApplication.translate class Terminal(object): def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None): """ Construct a new terminal. ============== ================================================================================= **Arguments:** node the node to which this terminal belongs name string, the name of the terminal io 'in' or 'out' optional bool, whether the node may process without connection to this terminal multi bool, for inputs: whether this terminal may make multiple connections for outputs: whether this terminal creates a different value for each connection pos [x, y], the position of the terminal within its node's boundaries renamable (bool) Whether the terminal can be renamed by the user removable (bool) Whether the terminal can be removed by the user multiable (bool) Whether the user may toggle the *multi* option for this terminal bypass (str) Name of the terminal from which this terminal's value is derived when the Node is in bypass mode. ============== ================================================================================= """ self._io = io self._optional = optional self._multi = multi self._node = weakref.ref(node) self._name = name self._renamable = renamable self._removable = removable self._multiable = multiable self._connections = {} self._graphicsItem = TerminalGraphicsItem(self, parent=self._node().graphicsItem()) self._bypass = bypass if multi: self._value = {} ## dictionary of terminal:value pairs. else: self._value = None self.valueOk = None self.recolor() def value(self, term=None): """Return the value this terminal provides for the connected terminal""" if term is None: return self._value if self.isMultiValue(): return self._value.get(term, None) else: return self._value def bypassValue(self): return self._bypass def setValue(self, val, process=True): """If this is a single-value terminal, val should be a single value. If this is a multi-value terminal, val should be a dict of terminal:value pairs""" if not self.isMultiValue(): if fn.eq(val, self._value): return self._value = val else: if not isinstance(self._value, dict): self._value = {} if val is not None: self._value.update(val) self.setValueAcceptable(None) ## by default, input values are 'unchecked' until Node.update(). if self.isInput() and process: self.node().update() self.recolor() def setOpts(self, **opts): self._renamable = opts.get('renamable', self._renamable) self._removable = opts.get('removable', self._removable) self._multiable = opts.get('multiable', self._multiable) if 'multi' in opts: self.setMultiValue(opts['multi']) def connected(self, term): """Called whenever this terminal has been connected to another. (note--this function is called on both terminals)""" if self.isInput() and term.isOutput(): self.inputChanged(term) if self.isOutput() and self.isMultiValue(): self.node().update() self.node().connected(self, term) def disconnected(self, term): """Called whenever this terminal has been disconnected from another. (note--this function is called on both terminals)""" if self.isMultiValue() and term in self._value: del self._value[term] self.node().update() else: if self.isInput(): self.setValue(None) self.node().disconnected(self, term) def inputChanged(self, term, process=True): """Called whenever there is a change to the input value to this terminal. It may often be useful to override this function.""" if self.isMultiValue(): self.setValue({term: term.value(self)}, process=process) else: self.setValue(term.value(self), process=process) def valueIsAcceptable(self): """Returns True->acceptable None->unknown False->Unacceptable""" return self.valueOk def setValueAcceptable(self, v=True): self.valueOk = v self.recolor() def connections(self): return self._connections def node(self): return self._node() def isInput(self): return self._io == 'in' def isMultiValue(self): return self._multi def setMultiValue(self, multi): """Set whether this is a multi-value terminal.""" self._multi = multi if not multi and len(self.inputTerminals()) > 1: self.disconnectAll() for term in self.inputTerminals(): self.inputChanged(term) def isOutput(self): return self._io == 'out' def isRenamable(self): return self._renamable def isRemovable(self): return self._removable def isMultiable(self): return self._multiable def name(self): return self._name def graphicsItem(self): return self._graphicsItem def isConnected(self): return len(self.connections()) > 0 def connectedTo(self, term): return term in self.connections() def hasInput(self): for t in self.connections(): if t.isOutput(): return True return False def inputTerminals(self): """Return the terminal(s) that give input to this one.""" return [t for t in self.connections() if t.isOutput()] def dependentNodes(self): """Return the list of nodes which receive input from this terminal.""" return set([t.node() for t in self.connections() if t.isInput()]) def connectTo(self, term, connectionItem=None): try: if self.connectedTo(term): raise Exception('Already connected') if term is self: raise Exception('Not connecting terminal to self') if term.node() is self.node(): raise Exception("Can't connect to terminal on same node.") for t in [self, term]: if t.isInput() and not t._multi and len(t.connections()) > 0: raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, list(t.connections().keys()))) except: if connectionItem is not None: connectionItem.close() raise if connectionItem is None: connectionItem = ConnectionItem(self.graphicsItem(), term.graphicsItem()) self.graphicsItem().getViewBox().addItem(connectionItem) self._connections[term] = connectionItem term._connections[self] = connectionItem self.recolor() self.connected(term) term.connected(self) return connectionItem def disconnectFrom(self, term): if not self.connectedTo(term): return item = self._connections[term] item.close() del self._connections[term] del term._connections[self] self.recolor() term.recolor() self.disconnected(term) term.disconnected(self) def disconnectAll(self): for t in list(self._connections.keys()): self.disconnectFrom(t) def recolor(self, color=None, recurse=True): if color is None: if not self.isConnected(): ## disconnected terminals are black color = QtGui.QColor(0,0,0) elif self.isInput() and not self.hasInput(): ## input terminal with no connected output terminals color = QtGui.QColor(200,200,0) elif self._value is None or fn.eq(self._value, {}): ## terminal is connected but has no data (possibly due to processing error) color = QtGui.QColor(255,255,255) elif self.valueIsAcceptable() is None: ## terminal has data, but it is unknown if the data is ok color = QtGui.QColor(200, 200, 0) elif self.valueIsAcceptable() is True: ## terminal has good input, all ok color = QtGui.QColor(0, 200, 0) else: ## terminal has bad input color = QtGui.QColor(200, 0, 0) self.graphicsItem().setBrush(QtGui.QBrush(color)) if recurse: for t in self.connections(): t.recolor(color, recurse=False) def rename(self, name): oldName = self._name self._name = name self.node().terminalRenamed(self, oldName) self.graphicsItem().termRenamed(name) def __repr__(self): return "" % (str(self.node().name()), str(self.name())) def __hash__(self): return id(self) def close(self): self.disconnectAll() item = self.graphicsItem() if item.scene() is not None: item.scene().removeItem(item) def saveState(self): return {'io': self._io, 'multi': self._multi, 'optional': self._optional, 'renamable': self._renamable, 'removable': self._removable, 'multiable': self._multiable} def __lt__(self, other): """When the terminal is multi value, the data passed to the DatTreeWidget for each input or output, is {Terminal: value}. To make this sortable, we provide the < operator. """ return self._name < other._name class TextItem(QtWidgets.QGraphicsTextItem): def __init__(self, text, parent, on_update): super().__init__(text, parent) self.on_update = on_update def focusOutEvent(self, ev): super().focusOutEvent(ev) if self.on_update is not None: self.on_update() def keyPressEvent(self, ev): if ev.key() == QtCore.Qt.Key.Key_Enter or ev.key() == QtCore.Qt.Key.Key_Return: if self.on_update is not None: self.on_update() return super().keyPressEvent(ev) class TerminalGraphicsItem(GraphicsObject): def __init__(self, term, parent=None): self.term = term GraphicsObject.__init__(self, parent) self.brush = fn.mkBrush(0,0,0) self.box = QtWidgets.QGraphicsRectItem(0, 0, 10, 10, self) on_update = self.labelChanged if self.term.isRenamable() else None self.label = TextItem(self.term.name(), self, on_update) self.label.setScale(0.7) self.newConnection = None self.setFiltersChildEvents(True) ## to pick up mouse events on the rectitem if self.term.isRenamable(): self.label.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextEditorInteraction) self.setZValue(1) self.menu = None def labelChanged(self): newName = self.label.toPlainText() if newName != self.term.name(): self.term.rename(newName) def termRenamed(self, name): self.label.setPlainText(name) def setBrush(self, brush): self.brush = brush self.box.setBrush(brush) def disconnect(self, target): self.term.disconnectFrom(target.term) def boundingRect(self): br = self.box.mapRectToParent(self.box.boundingRect()) lr = self.label.mapRectToParent(self.label.boundingRect()) return br | lr def paint(self, p, *args): pass def setAnchor(self, x, y): pos = QtCore.QPointF(x, y) self.anchorPos = pos br = self.box.mapRectToParent(self.box.boundingRect()) lr = self.label.mapRectToParent(self.label.boundingRect()) if self.term.isInput(): self.box.setPos(pos.x(), pos.y()-br.height()/2.) self.label.setPos(pos.x() + br.width(), pos.y() - lr.height()/2.) else: self.box.setPos(pos.x()-br.width(), pos.y()-br.height()/2.) self.label.setPos(pos.x()-br.width()-lr.width(), pos.y()-lr.height()/2.) self.updateConnections() def updateConnections(self): for t, c in self.term.connections().items(): c.updateLine() def mousePressEvent(self, ev): #ev.accept() ev.ignore() ## necessary to allow click/drag events to process correctly def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.LeftButton: ev.accept() self.label.setFocus(QtCore.Qt.FocusReason.MouseFocusReason) elif ev.button() == QtCore.Qt.MouseButton.RightButton: ev.accept() self.raiseContextMenu(ev) def raiseContextMenu(self, ev): ## only raise menu if this terminal is removable menu = self.getMenu() menu = self.scene().addParentContextMenus(self, menu, ev) pos = ev.screenPos() menu.popup(QtCore.QPoint(pos.x(), pos.y())) def getMenu(self): if self.menu is None: self.menu = QtWidgets.QMenu() self.menu.setTitle(translate("Context Menu", "Terminal")) remAct = QtGui.QAction(translate("Context Menu", "Remove terminal"), self.menu) remAct.triggered.connect(self.removeSelf) self.menu.addAction(remAct) self.menu.remAct = remAct if not self.term.isRemovable(): remAct.setEnabled(False) multiAct = QtGui.QAction(translate("Context Menu", "Multi-value"), self.menu) multiAct.setCheckable(True) multiAct.setChecked(self.term.isMultiValue()) multiAct.setEnabled(self.term.isMultiable()) multiAct.triggered.connect(self.toggleMulti) self.menu.addAction(multiAct) self.menu.multiAct = multiAct if self.term.isMultiable(): multiAct.setEnabled = False return self.menu def toggleMulti(self): multi = self.menu.multiAct.isChecked() self.term.setMultiValue(multi) def removeSelf(self): self.term.node().removeTerminal(self.term) def mouseDragEvent(self, ev): if ev.button() != QtCore.Qt.MouseButton.LeftButton: ev.ignore() return ev.accept() if ev.isStart(): if self.newConnection is None: self.newConnection = ConnectionItem(self) #self.scene().addItem(self.newConnection) self.getViewBox().addItem(self.newConnection) #self.newConnection.setParentItem(self.parent().parent()) self.newConnection.setTarget(self.mapToView(ev.pos())) elif ev.isFinish(): if self.newConnection is not None: items = self.scene().items(ev.scenePos()) gotTarget = False for i in items: if isinstance(i, TerminalGraphicsItem): self.newConnection.setTarget(i) try: self.term.connectTo(i.term, self.newConnection) gotTarget = True except: self.scene().removeItem(self.newConnection) self.newConnection = None raise break if not gotTarget: self.newConnection.close() self.newConnection = None else: if self.newConnection is not None: self.newConnection.setTarget(self.mapToView(ev.pos())) def hoverEvent(self, ev): if not ev.isExit() and ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton): ev.acceptClicks(QtCore.Qt.MouseButton.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. ev.acceptClicks(QtCore.Qt.MouseButton.RightButton) self.box.setBrush(fn.mkBrush('w')) else: self.box.setBrush(self.brush) self.update() def connectPoint(self): ## return the connect position of this terminal in view coords return self.mapToView(self.mapFromItem(self.box, self.box.boundingRect().center())) def nodeMoved(self): for t, item in self.term.connections().items(): item.updateLine() class ConnectionItem(GraphicsObject): def __init__(self, source, target=None): GraphicsObject.__init__(self) self.setFlags( self.GraphicsItemFlag.ItemIsSelectable | self.GraphicsItemFlag.ItemIsFocusable ) self.source = source self.target = target self.length = 0 self.hovered = False self.path = None self.shapePath = None self.style = { 'shape': 'line', 'color': (100, 100, 250), 'width': 1.0, 'hoverColor': (150, 150, 250), 'hoverWidth': 1.0, 'selectedColor': (200, 200, 0), 'selectedWidth': 3.0, } self.source.getViewBox().addItem(self) self.updateLine() self.setZValue(0) def close(self): if self.scene() is not None: self.scene().removeItem(self) def setTarget(self, target): self.target = target self.updateLine() def setStyle(self, **kwds): self.style.update(kwds) if 'shape' in kwds: self.updateLine() else: self.update() def updateLine(self): start = Point(self.source.connectPoint()) if isinstance(self.target, TerminalGraphicsItem): stop = Point(self.target.connectPoint()) elif isinstance(self.target, QtCore.QPointF): stop = Point(self.target) else: return self.prepareGeometryChange() self.path = self.generatePath(start, stop) self.shapePath = None self.update() def generatePath(self, start, stop): path = QtGui.QPainterPath() path.moveTo(start) if self.style['shape'] == 'line': path.lineTo(stop) elif self.style['shape'] == 'cubic': path.cubicTo(Point(stop.x(), start.y()), Point(start.x(), stop.y()), Point(stop.x(), stop.y())) else: raise Exception('Invalid shape "%s"; options are "line" or "cubic"' % self.style['shape']) return path def keyPressEvent(self, ev): if not self.isSelected(): ev.ignore() return if ev.key() == QtCore.Qt.Key.Key_Delete or ev.key() == QtCore.Qt.Key.Key_Backspace: self.source.disconnect(self.target) ev.accept() else: ev.ignore() def mousePressEvent(self, ev): ev.ignore() def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.LeftButton: ev.accept() sel = self.isSelected() self.setSelected(True) self.setFocus() if not sel and self.isSelected(): self.update() def hoverEvent(self, ev): if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.MouseButton.LeftButton): self.hovered = True else: self.hovered = False self.update() def boundingRect(self): return self.shape().boundingRect() def viewRangeChanged(self): self.shapePath = None self.prepareGeometryChange() def shape(self): if self.shapePath is None: if self.path is None: return QtGui.QPainterPath() stroker = QtGui.QPainterPathStroker() px = self.pixelWidth() stroker.setWidth(px*8) self.shapePath = stroker.createStroke(self.path) return self.shapePath def paint(self, p, *args): if self.isSelected(): p.setPen(fn.mkPen(self.style['selectedColor'], width=self.style['selectedWidth'])) else: if self.hovered: p.setPen(fn.mkPen(self.style['hoverColor'], width=self.style['hoverWidth'])) else: p.setPen(fn.mkPen(self.style['color'], width=self.style['width'])) p.drawPath(self.path) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/__init__.py000066400000000000000000000001311421045507400237500ustar00rootroot00000000000000from .Flowchart import * from .library import getNodeTree, getNodeType, registerNodeType pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/library/000077500000000000000000000000001421045507400233105ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/library/Data.py000066400000000000000000000372121421045507400245400ustar00rootroot00000000000000 import numpy as np from ...graphicsItems.LinearRegionItem import LinearRegionItem from ...Qt import QtCore, QtWidgets from ...widgets.TreeWidget import TreeWidget from ..Node import Node from . import functions from .common import CtrlNode class ColumnSelectNode(Node): """Select named columns from a record array or MetaArray.""" nodeName = "ColumnSelect" def __init__(self, name): Node.__init__(self, name, terminals={'In': {'io': 'in'}}) self.columns = set() self.columnList = QtWidgets.QListWidget() self.axis = 0 self.columnList.itemChanged.connect(self.itemChanged) def process(self, In, display=True): if display: self.updateList(In) out = {} if hasattr(In, 'implements') and In.implements('MetaArray'): for c in self.columns: out[c] = In[self.axis:c] elif isinstance(In, np.ndarray) and In.dtype.fields is not None: for c in self.columns: out[c] = In[c] else: self.In.setValueAcceptable(False) raise Exception("Input must be MetaArray or ndarray with named fields") return out def ctrlWidget(self): return self.columnList def updateList(self, data): if hasattr(data, 'implements') and data.implements('MetaArray'): cols = data.listColumns() for ax in cols: ## find first axis with columns if len(cols[ax]) > 0: self.axis = ax cols = set(cols[ax]) break else: cols = list(data.dtype.fields.keys()) rem = set() for c in self.columns: if c not in cols: self.removeTerminal(c) rem.add(c) self.columns -= rem self.columnList.blockSignals(True) self.columnList.clear() for c in cols: item = QtWidgets.QListWidgetItem(c) item.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled|QtCore.Qt.ItemFlag.ItemIsUserCheckable) if c in self.columns: item.setCheckState(QtCore.Qt.CheckState.Checked) else: item.setCheckState(QtCore.Qt.CheckState.Unchecked) self.columnList.addItem(item) self.columnList.blockSignals(False) def itemChanged(self, item): col = str(item.text()) if item.checkState() == QtCore.Qt.CheckState.Checked: if col not in self.columns: self.columns.add(col) self.addOutput(col) else: if col in self.columns: self.columns.remove(col) self.removeTerminal(col) self.update() def saveState(self): state = Node.saveState(self) state['columns'] = list(self.columns) return state def restoreState(self, state): Node.restoreState(self, state) self.columns = set(state.get('columns', [])) for c in self.columns: self.addOutput(c) class RegionSelectNode(CtrlNode): """Returns a slice from a 1-D array. Connect the 'widget' output to a plot to display a region-selection widget.""" nodeName = "RegionSelect" uiTemplate = [ ('start', 'spin', {'value': 0, 'step': 0.1}), ('stop', 'spin', {'value': 0.1, 'step': 0.1}), ('display', 'check', {'value': True}), ('movable', 'check', {'value': True}), ] def __init__(self, name): self.items = {} CtrlNode.__init__(self, name, terminals={ 'data': {'io': 'in'}, 'selected': {'io': 'out'}, 'region': {'io': 'out'}, 'widget': {'io': 'out', 'multi': True} }) self.ctrls['display'].toggled.connect(self.displayToggled) self.ctrls['movable'].toggled.connect(self.movableToggled) def displayToggled(self, b): for item in self.items.values(): item.setVisible(b) def movableToggled(self, b): for item in self.items.values(): item.setMovable(b) def process(self, data=None, display=True): #print "process.." s = self.stateGroup.state() region = [s['start'], s['stop']] if display: conn = self['widget'].connections() for c in conn: plot = c.node().getPlot() if plot is None: continue if c in self.items: item = self.items[c] item.setRegion(region) #print " set rgn:", c, region #item.setXVals(events) else: item = LinearRegionItem(values=region) self.items[c] = item #item.connect(item, QtCore.SIGNAL('regionChanged'), self.rgnChanged) item.sigRegionChanged.connect(self.rgnChanged) item.setVisible(s['display']) item.setMovable(s['movable']) #print " new rgn:", c, region #self.items[c].setYRange([0., 0.2], relative=True) if self['selected'].isConnected(): if data is None: sliced = None elif (hasattr(data, 'implements') and data.implements('MetaArray')): sliced = data[0:s['start']:s['stop']] else: mask = (data['time'] >= s['start']) * (data['time'] < s['stop']) sliced = data[mask] else: sliced = None return {'selected': sliced, 'widget': self.items, 'region': region} def rgnChanged(self, item): region = item.getRegion() self.stateGroup.setState({'start': region[0], 'stop': region[1]}) self.update() class TextEdit(QtWidgets.QTextEdit): def __init__(self, on_update): super().__init__() self.on_update = on_update self.lastText = None def focusOutEvent(self, ev): text = self.toPlainText() if text != self.lastText: self.lastText = text self.on_update() super().focusOutEvent(ev) class EvalNode(Node): """Return the output of a string evaluated/executed by the python interpreter. The string may be either an expression or a python script, and inputs are accessed as the name of the terminal. For expressions, a single value may be evaluated for a single output, or a dict for multiple outputs. For a script, the text will be executed as the body of a function.""" nodeName = 'PythonEval' def __init__(self, name): Node.__init__(self, name, terminals = { 'input': {'io': 'in', 'renamable': True, 'multiable': True}, 'output': {'io': 'out', 'renamable': True, 'multiable': True}, }, allowAddInput=True, allowAddOutput=True) self.ui = QtWidgets.QWidget() self.layout = QtWidgets.QGridLayout() self.text = TextEdit(self.update) self.text.setTabStopWidth(30) self.text.setPlainText("# Access inputs as args['input_name']\nreturn {'output': None} ## one key per output terminal") self.layout.addWidget(self.text, 1, 0, 1, 2) self.ui.setLayout(self.layout) def ctrlWidget(self): return self.ui def setCode(self, code): # unindent code; this allows nicer inline code specification when # calling this method. ind = [] lines = code.split('\n') for line in lines: stripped = line.lstrip() if len(stripped) > 0: ind.append(len(line) - len(stripped)) if len(ind) > 0: ind = min(ind) code = '\n'.join([line[ind:] for line in lines]) self.text.clear() self.text.insertPlainText(code) def code(self): return self.text.toPlainText() def process(self, display=True, **args): l = locals() l.update(args) ## try eval first, then exec try: 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 self.text.toPlainText().split('\n')]) + run ldict = locals() exec(text, globals(), ldict) output = ldict['output'] except: print(f"Error processing node: {self.name()}") raise return output def saveState(self): state = Node.saveState(self) state['text'] = self.text.toPlainText() #state['terminals'] = self.saveTerminals() return state def restoreState(self, state): Node.restoreState(self, state) self.setCode(state['text']) self.restoreTerminals(state['terminals']) self.update() class ColumnJoinNode(Node): """Concatenates record arrays and/or adds new columns""" nodeName = 'ColumnJoin' def __init__(self, name): Node.__init__(self, name, terminals = { 'output': {'io': 'out'}, }) #self.items = [] self.ui = QtWidgets.QWidget() self.layout = QtWidgets.QGridLayout() self.ui.setLayout(self.layout) self.tree = TreeWidget() self.addInBtn = QtWidgets.QPushButton('+ Input') self.remInBtn = QtWidgets.QPushButton('- Input') self.layout.addWidget(self.tree, 0, 0, 1, 2) self.layout.addWidget(self.addInBtn, 1, 0) self.layout.addWidget(self.remInBtn, 1, 1) self.addInBtn.clicked.connect(self.addInput) self.remInBtn.clicked.connect(self.remInput) self.tree.sigItemMoved.connect(self.update) def ctrlWidget(self): return self.ui def addInput(self): #print "ColumnJoinNode.addInput called." term = Node.addInput(self, 'input', renamable=True, removable=True, multiable=True) #print "Node.addInput returned. term:", term item = QtWidgets.QTreeWidgetItem([term.name()]) item.term = term term.joinItem = item #self.items.append((term, item)) self.tree.addTopLevelItem(item) def remInput(self): sel = self.tree.currentItem() term = sel.term term.joinItem = None sel.term = None self.tree.removeTopLevelItem(sel) self.removeTerminal(term) self.update() def process(self, display=True, **args): order = self.order() vals = [] for name in order: if name not in args: continue val = args[name] if isinstance(val, np.ndarray) and len(val.dtype) > 0: vals.append(val) else: vals.append((name, None, val)) return {'output': functions.concatenateColumns(vals)} def order(self): return [str(self.tree.topLevelItem(i).text(0)) for i in range(self.tree.topLevelItemCount())] def saveState(self): state = Node.saveState(self) state['order'] = self.order() return state def restoreState(self, state): Node.restoreState(self, state) inputs = self.inputs() ## Node.restoreState should have created all of the terminals we need ## However: to maintain support for some older flowchart files, we need ## to manually add any terminals that were not taken care of. for name in [n for n in state['order'] if n not in inputs]: Node.addInput(self, name, renamable=True, removable=True, multiable=True) inputs = self.inputs() order = [name for name in state['order'] if name in inputs] for name in inputs: if name not in order: order.append(name) self.tree.clear() for name in order: term = self[name] item = QtWidgets.QTreeWidgetItem([name]) item.term = term term.joinItem = item #self.items.append((term, item)) self.tree.addTopLevelItem(item) def terminalRenamed(self, term, oldName): Node.terminalRenamed(self, term, oldName) item = term.joinItem item.setText(0, term.name()) self.update() class Mean(CtrlNode): """Calculate the mean of an array across an axis. """ nodeName = 'Mean' uiTemplate = [ ('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}), ] def processData(self, data): s = self.stateGroup.state() ax = None if s['axis'] == -1 else s['axis'] return data.mean(axis=ax) class Max(CtrlNode): """Calculate the maximum of an array across an axis. """ nodeName = 'Max' uiTemplate = [ ('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}), ] def processData(self, data): s = self.stateGroup.state() ax = None if s['axis'] == -1 else s['axis'] return data.max(axis=ax) class Min(CtrlNode): """Calculate the minimum of an array across an axis. """ nodeName = 'Min' uiTemplate = [ ('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}), ] def processData(self, data): s = self.stateGroup.state() ax = None if s['axis'] == -1 else s['axis'] return data.min(axis=ax) class Stdev(CtrlNode): """Calculate the standard deviation of an array across an axis. """ nodeName = 'Stdev' uiTemplate = [ ('axis', 'intSpin', {'value': -0, 'min': -1, 'max': 1000000}), ] def processData(self, data): s = self.stateGroup.state() ax = None if s['axis'] == -1 else s['axis'] return data.std(axis=ax) class Index(CtrlNode): """Select an index from an array axis. """ nodeName = 'Index' uiTemplate = [ ('axis', 'intSpin', {'value': 0, 'min': 0, 'max': 1000000}), ('index', 'intSpin', {'value': 0, 'min': 0, 'max': 1000000}), ] def processData(self, data): s = self.stateGroup.state() ax = s['axis'] ind = s['index'] if ax == 0: # allow support for non-ndarray sequence types return data[ind] else: return data.take(ind, axis=ax) class Slice(CtrlNode): """Select a slice from an array axis. """ nodeName = 'Slice' uiTemplate = [ ('axis', 'intSpin', {'value': 0, 'min': 0, 'max': 1e6}), ('start', 'intSpin', {'value': 0, 'min': -1e6, 'max': 1e6}), ('stop', 'intSpin', {'value': -1, 'min': -1e6, 'max': 1e6}), ('step', 'intSpin', {'value': 1, 'min': -1e6, 'max': 1e6}), ] def processData(self, data): s = self.stateGroup.state() ax = s['axis'] start = s['start'] stop = s['stop'] step = s['step'] if ax == 0: # allow support for non-ndarray sequence types return data[start:stop:step] else: sl = [slice(None) for i in range(data.ndim)] sl[ax] = slice(start, stop, step) return data[sl] class AsType(CtrlNode): """Convert an array to a different dtype. """ nodeName = 'AsType' uiTemplate = [ ('dtype', 'combo', {'values': ['float', 'int', 'float32', 'float64', 'float128', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64'], 'index': 0}), ] def processData(self, data): s = self.stateGroup.state() return data.astype(s['dtype']) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/library/Display.py000066400000000000000000000240471421045507400252760ustar00rootroot00000000000000import numpy as np from ... import ComboBox, PlotDataItem from ...graphicsItems.ScatterPlotItem import ScatterPlotItem from ...Qt import QtCore, QtGui, QtWidgets from ..Node import Node from .common import * class PlotWidgetNode(Node): """Connection to PlotWidget. Will plot arrays, metaarrays, and display event lists.""" nodeName = 'PlotWidget' sigPlotChanged = QtCore.Signal(object) def __init__(self, name): Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) self.plot = None # currently selected plot self.plots = {} # list of available plots user may select from self.ui = None self.items = {} def disconnected(self, localTerm, remoteTerm): if localTerm is self['In'] and remoteTerm in self.items: self.plot.removeItem(self.items[remoteTerm]) del self.items[remoteTerm] def setPlot(self, plot): #print "======set plot" if plot == self.plot: return # clear data from previous plot if self.plot is not None: for vid in list(self.items.keys()): self.plot.removeItem(self.items[vid]) del self.items[vid] self.plot = plot self.updateUi() self.update() self.sigPlotChanged.emit(self) def getPlot(self): return self.plot def process(self, In, display=True): if display and self.plot is not None: items = set() # Add all new input items to selected plot for name, vals in In.items(): if vals is None: continue if type(vals) is not list: vals = [vals] for val in vals: vid = id(val) if vid in self.items and self.items[vid].scene() is self.plot.scene(): # Item is already added to the correct scene # possible bug: what if two plots occupy the same scene? (should # rarely be a problem because items are removed from a plot before # switching). items.add(vid) else: # Add the item to the plot, or generate a new item if needed. if isinstance(val, QtWidgets.QGraphicsItem): self.plot.addItem(val) item = val else: item = self.plot.plot(val) self.items[vid] = item items.add(vid) # Any left-over items that did not appear in the input must be removed for vid in list(self.items.keys()): if vid not in items: self.plot.removeItem(self.items[vid]) del self.items[vid] def processBypassed(self, args): if self.plot is None: return for item in list(self.items.values()): self.plot.removeItem(item) self.items = {} def ctrlWidget(self): if self.ui is None: self.ui = ComboBox() self.ui.currentIndexChanged.connect(self.plotSelected) self.updateUi() return self.ui def plotSelected(self, index): self.setPlot(self.ui.value()) def setPlotList(self, plots): """ Specify the set of plots (PlotWidget or PlotItem) that the user may select from. *plots* must be a dictionary of {name: plot} pairs. """ self.plots = plots self.updateUi() def updateUi(self): # sets list and automatically preserves previous selection self.ui.setItems(self.plots) try: self.ui.setValue(self.plot) except ValueError: pass class CanvasNode(Node): """Connection to a Canvas widget.""" nodeName = 'CanvasWidget' def __init__(self, name): Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) self.canvas = None self.items = {} def disconnected(self, localTerm, remoteTerm): if localTerm is self.In and remoteTerm in self.items: self.canvas.removeItem(self.items[remoteTerm]) del self.items[remoteTerm] def setCanvas(self, canvas): self.canvas = canvas def getCanvas(self): return self.canvas def process(self, In, display=True): if display: items = set() for name, vals in In.items(): if vals is None: continue if type(vals) is not list: vals = [vals] for val in vals: vid = id(val) if vid in self.items: items.add(vid) else: self.canvas.addItem(val) item = val self.items[vid] = item items.add(vid) for vid in list(self.items.keys()): if vid not in items: #print "remove", self.items[vid] self.canvas.removeItem(self.items[vid]) del self.items[vid] class PlotCurve(CtrlNode): """Generates a plot curve from x/y data""" nodeName = 'PlotCurve' uiTemplate = [ ('color', 'color'), ] def __init__(self, name): CtrlNode.__init__(self, name, terminals={ 'x': {'io': 'in'}, 'y': {'io': 'in'}, 'plot': {'io': 'out'} }) self.item = PlotDataItem() def process(self, x, y, display=True): #print "scatterplot process" if not display: return {'plot': None} self.item.setData(x, y, pen=self.ctrls['color'].color()) return {'plot': self.item} class ScatterPlot(CtrlNode): """Generates a scatter plot from a record array or nested dicts""" nodeName = 'ScatterPlot' uiTemplate = [ ('x', 'combo', {'values': [], 'index': 0}), ('y', 'combo', {'values': [], 'index': 0}), ('sizeEnabled', 'check', {'value': False}), ('size', 'combo', {'values': [], 'index': 0}), ('absoluteSize', 'check', {'value': False}), ('colorEnabled', 'check', {'value': False}), ('color', 'colormap', {}), ('borderEnabled', 'check', {'value': False}), ('border', 'colormap', {}), ] def __init__(self, name): CtrlNode.__init__(self, name, terminals={ 'input': {'io': 'in'}, 'plot': {'io': 'out'} }) self.item = ScatterPlotItem() self.keys = [] #self.ui = QtWidgets.QWidget() #self.layout = QtWidgets.QGridLayout() #self.ui.setLayout(self.layout) #self.xCombo = QtWidgets.QComboBox() #self.yCombo = QtWidgets.QComboBox() def process(self, input, display=True): #print "scatterplot process" if not display: return {'plot': None} self.updateKeys(input[0]) x = str(self.ctrls['x'].currentText()) y = str(self.ctrls['y'].currentText()) size = str(self.ctrls['size'].currentText()) pen = QtGui.QPen(QtGui.QColor(0,0,0,0)) points = [] for i in input: pt = {'pos': (i[x], i[y])} if self.ctrls['sizeEnabled'].isChecked(): pt['size'] = i[size] if self.ctrls['borderEnabled'].isChecked(): pt['pen'] = QtGui.QPen(self.ctrls['border'].getColor(i)) else: pt['pen'] = pen if self.ctrls['colorEnabled'].isChecked(): pt['brush'] = QtGui.QBrush(self.ctrls['color'].getColor(i)) points.append(pt) self.item.setPxMode(not self.ctrls['absoluteSize'].isChecked()) self.item.setPoints(points) return {'plot': self.item} def updateKeys(self, data): if isinstance(data, dict): keys = list(data.keys()) elif isinstance(data, list) or isinstance(data, tuple): keys = data elif isinstance(data, np.ndarray) or isinstance(data, np.void): keys = data.dtype.names else: print("Unknown data type:", type(data), data) return for c in self.ctrls.values(): c.blockSignals(True) for c in [self.ctrls['x'], self.ctrls['y'], self.ctrls['size']]: cur = str(c.currentText()) c.clear() for k in keys: c.addItem(k) if k == cur: c.setCurrentIndex(c.count()-1) for c in [self.ctrls['color'], self.ctrls['border']]: c.setArgList(keys) for c in self.ctrls.values(): c.blockSignals(False) self.keys = keys def saveState(self): state = CtrlNode.saveState(self) return {'keys': self.keys, 'ctrls': state} def restoreState(self, state): self.updateKeys(state['keys']) CtrlNode.restoreState(self, state['ctrls']) #class ImageItem(Node): #"""Creates an ImageItem for display in a canvas from a file handle.""" #nodeName = 'Image' #def __init__(self, name): #Node.__init__(self, name, terminals={ #'file': {'io': 'in'}, #'image': {'io': 'out'} #}) #self.imageItem = graphicsItems.ImageItem() #self.handle = None #def process(self, file, display=True): #if not display: #return {'image': None} #if file != self.handle: #self.handle = file #data = file.read() #self.imageItem.updateImage(data) #pos = file. pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/library/Filters.py000066400000000000000000000315151421045507400252770ustar00rootroot00000000000000import numpy as np from ... import Point, PolyLineROI from ... import functions as pgfn from ... import metaarray as metaarray from . import functions from .common import CtrlNode, PlottingCtrlNode, metaArrayWrapper class Downsample(CtrlNode): """Downsample by averaging samples together.""" nodeName = 'Downsample' uiTemplate = [ ('n', 'intSpin', {'min': 1, 'max': 1000000}) ] def processData(self, data): return functions.downsample(data, self.ctrls['n'].value(), axis=0) class Subsample(CtrlNode): """Downsample by selecting every Nth sample.""" nodeName = 'Subsample' uiTemplate = [ ('n', 'intSpin', {'min': 1, 'max': 1000000}) ] def processData(self, data): return data[::self.ctrls['n'].value()] class Bessel(CtrlNode): """Bessel filter. Input data must have time values.""" nodeName = 'BesselFilter' uiTemplate = [ ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), ('cutoff', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), ('order', 'intSpin', {'value': 4, 'min': 1, 'max': 16}), ('bidir', 'check', {'checked': True}) ] def processData(self, data): s = self.stateGroup.state() if s['band'] == 'lowpass': mode = 'low' else: mode = 'high' return functions.besselFilter(data, bidir=s['bidir'], btype=mode, cutoff=s['cutoff'], order=s['order']) class Butterworth(CtrlNode): """Butterworth filter""" nodeName = 'ButterworthFilter' uiTemplate = [ ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), ('wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), ('wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), ('gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), ('gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), ('bidir', 'check', {'checked': True}) ] def processData(self, data): s = self.stateGroup.state() if s['band'] == 'lowpass': mode = 'low' else: mode = 'high' ret = functions.butterworthFilter(data, bidir=s['bidir'], btype=mode, wPass=s['wPass'], wStop=s['wStop'], gPass=s['gPass'], gStop=s['gStop']) return ret class ButterworthNotch(CtrlNode): """Butterworth notch filter""" nodeName = 'ButterworthNotchFilter' uiTemplate = [ ('low_wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), ('low_wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), ('low_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), ('low_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), ('high_wPass', 'spin', {'value': 3000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), ('high_wStop', 'spin', {'value': 4000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), ('high_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), ('high_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), ('bidir', 'check', {'checked': True}) ] def processData(self, data): s = self.stateGroup.state() low = functions.butterworthFilter(data, bidir=s['bidir'], btype='low', wPass=s['low_wPass'], wStop=s['low_wStop'], gPass=s['low_gPass'], gStop=s['low_gStop']) high = functions.butterworthFilter(data, bidir=s['bidir'], btype='high', wPass=s['high_wPass'], wStop=s['high_wStop'], gPass=s['high_gPass'], gStop=s['high_gStop']) return low + high class Mean(CtrlNode): """Filters data by taking the mean of a sliding window""" nodeName = 'MeanFilter' uiTemplate = [ ('n', 'intSpin', {'min': 1, 'max': 1000000}) ] @metaArrayWrapper def processData(self, data): n = self.ctrls['n'].value() return functions.rollingSum(data, n) / n class Median(CtrlNode): """Filters data by taking the median of a sliding window""" nodeName = 'MedianFilter' uiTemplate = [ ('n', 'intSpin', {'min': 1, 'max': 1000000}) ] @metaArrayWrapper def processData(self, data): try: import scipy.ndimage except ImportError: raise Exception("MedianFilter node requires the package scipy.ndimage.") return scipy.ndimage.median_filter(data, self.ctrls['n'].value()) class Mode(CtrlNode): """Filters data by taking the mode (histogram-based) of a sliding window""" nodeName = 'ModeFilter' uiTemplate = [ ('window', 'intSpin', {'value': 500, 'min': 1, 'max': 1000000}), ] @metaArrayWrapper def processData(self, data): return functions.modeFilter(data, self.ctrls['window'].value()) class Denoise(CtrlNode): """Removes anomalous spikes from data, replacing with nearby values""" nodeName = 'DenoiseFilter' uiTemplate = [ ('radius', 'intSpin', {'value': 2, 'min': 0, 'max': 1000000}), ('threshold', 'doubleSpin', {'value': 4.0, 'min': 0, 'max': 1000}) ] def processData(self, data): #print "DENOISE" s = self.stateGroup.state() return functions.denoise(data, **s) class Gaussian(CtrlNode): """Gaussian smoothing filter.""" nodeName = 'GaussianFilter' uiTemplate = [ ('sigma', 'doubleSpin', {'min': 0, 'max': 1000000}) ] @metaArrayWrapper def processData(self, data): sigma = self.ctrls['sigma'].value() try: import scipy.ndimage return scipy.ndimage.gaussian_filter(data, sigma) except ImportError: return pgfn.gaussianFilter(data, sigma) class Derivative(CtrlNode): """Returns the pointwise derivative of the input""" nodeName = 'DerivativeFilter' def processData(self, data): if hasattr(data, 'implements') and data.implements('MetaArray'): info = data.infoCopy() if 'values' in info[0]: info[0]['values'] = info[0]['values'][:-1] return metaarray.MetaArray(data[1:] - data[:-1], info=info) else: return data[1:] - data[:-1] class Integral(CtrlNode): """Returns the pointwise integral of the input""" nodeName = 'IntegralFilter' @metaArrayWrapper def processData(self, data): data[1:] += data[:-1] return data class Detrend(CtrlNode): """Removes linear trend from the data""" nodeName = 'DetrendFilter' @metaArrayWrapper def processData(self, data): try: from scipy.signal import detrend except ImportError: raise Exception("DetrendFilter node requires the package scipy.signal.") return detrend(data) class RemoveBaseline(PlottingCtrlNode): """Remove an arbitrary, graphically defined baseline from the data.""" nodeName = 'RemoveBaseline' def __init__(self, name): ## define inputs and outputs (one output needs to be a plot) PlottingCtrlNode.__init__(self, name) self.line = PolyLineROI([[0,0],[1,0]]) self.line.sigRegionChanged.connect(self.changed) ## create a PolyLineROI, add it to a plot -- actually, I think we want to do this after the node is connected to a plot (look at EventDetection.ThresholdEvents node for ideas), and possible after there is data. We will need to update the end positions of the line each time the input data changes #self.line = None ## will become a PolyLineROI def connectToPlot(self, node): """Define what happens when the node is connected to a plot""" if node.plot is None: return node.getPlot().addItem(self.line) def disconnectFromPlot(self, plot): """Define what happens when the node is disconnected from a plot""" plot.removeItem(self.line) def processData(self, data): ## get array of baseline (from PolyLineROI) h0 = self.line.getHandles()[0] h1 = self.line.getHandles()[-1] timeVals = data.xvals(0) h0.setPos(timeVals[0], h0.pos()[1]) h1.setPos(timeVals[-1], h1.pos()[1]) pts = self.line.listPoints() ## lists line handles in same coordinates as data pts, indices = self.adjustXPositions(pts, timeVals) ## maxe sure x positions match x positions of data points ## construct an array that represents the baseline arr = np.zeros(len(data), dtype=float) n = 1 arr[0] = pts[0].y() for i in range(len(pts)-1): x1 = pts[i].x() x2 = pts[i+1].x() y1 = pts[i].y() y2 = pts[i+1].y() m = (y2-y1)/(x2-x1) b = y1 times = timeVals[(timeVals > x1)*(timeVals <= x2)] arr[n:n+len(times)] = (m*(times-times[0]))+b n += len(times) return data - arr ## subract baseline from data def adjustXPositions(self, pts, data): """Return a list of Point() where the x position is set to the nearest x value in *data* for each point in *pts*.""" points = [] timeIndices = [] for p in pts: x = np.argwhere(abs(data - p.x()) == abs(data - p.x()).min()) points.append(Point(data[x], p.y())) timeIndices.append(x) return points, timeIndices class AdaptiveDetrend(CtrlNode): """Removes baseline from data, ignoring anomalous events""" nodeName = 'AdaptiveDetrend' uiTemplate = [ ('threshold', 'doubleSpin', {'value': 3.0, 'min': 0, 'max': 1000000}) ] def processData(self, data): return functions.adaptiveDetrend(data, threshold=self.ctrls['threshold'].value()) class HistogramDetrend(CtrlNode): """Removes baseline from data by computing mode (from histogram) of beginning and end of data.""" nodeName = 'HistogramDetrend' uiTemplate = [ ('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), ('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}), ('offsetOnly', 'check', {'checked': False}), ] def processData(self, data): s = self.stateGroup.state() #ws = self.ctrls['windowSize'].value() #bn = self.ctrls['numBins'].value() #offset = self.ctrls['offsetOnly'].checked() return functions.histogramDetrend(data, window=s['windowSize'], bins=s['numBins'], offsetOnly=s['offsetOnly']) class RemovePeriodic(CtrlNode): nodeName = 'RemovePeriodic' uiTemplate = [ #('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), #('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}) ('f0', 'spin', {'value': 60, 'suffix': 'Hz', 'siPrefix': True, 'min': 0, 'max': None}), ('harmonics', 'intSpin', {'value': 30, 'min': 0}), ('samples', 'intSpin', {'value': 1, 'min': 1}), ] def processData(self, data): times = data.xvals('Time') dt = times[1]-times[0] data1 = data.asarray() ft = np.fft.fft(data1) ## determine frequencies in fft data df = 1.0 / (len(data1) * dt) freqs = np.linspace(0.0, (len(ft)-1) * df, len(ft)) ## flatten spikes at f0 and harmonics f0 = self.ctrls['f0'].value() for i in range(1, self.ctrls['harmonics'].value()+2): f = f0 * i # target frequency ## determine index range to check for this frequency ind1 = int(np.floor(f / df)) ind2 = int(np.ceil(f / df)) + (self.ctrls['samples'].value()-1) if ind1 > len(ft)/2.: break mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5 for j in range(ind1, ind2+1): phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts. re = mag * np.cos(phase) im = mag * np.sin(phase) ft[j] = re + im*1j ft[len(ft)-j] = re - im*1j data2 = np.fft.ifft(ft).real ma = metaarray.MetaArray(data2, info=data.infoCopy()) return ma pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/library/Operators.py000066400000000000000000000062571421045507400256520ustar00rootroot00000000000000from ..Node import Node from .common import CtrlNode class UniOpNode(Node): """Generic node for performing any operation like Out = In.fn()""" def __init__(self, name, fn): self.fn = fn Node.__init__(self, name, terminals={ 'In': {'io': 'in'}, 'Out': {'io': 'out', 'bypass': 'In'} }) def process(self, **args): return {'Out': getattr(args['In'], self.fn)()} class BinOpNode(CtrlNode): """Generic node for performing any operation like A.fn(B)""" _dtypes = [ 'float64', 'float32', 'float16', 'int64', 'int32', 'int16', 'int8', 'uint64', 'uint32', 'uint16', 'uint8' ] uiTemplate = [ ('outputType', 'combo', {'values': ['no change', 'input A', 'input B'] + _dtypes , 'index': 0}) ] def __init__(self, name, fn): self.fn = fn CtrlNode.__init__(self, name, terminals={ 'A': {'io': 'in'}, 'B': {'io': 'in'}, 'Out': {'io': 'out', 'bypass': 'A'} }) def process(self, **args): if isinstance(self.fn, tuple): for name in self.fn: try: fn = getattr(args['A'], name) break except AttributeError as e: pass else: raise e else: fn = getattr(args['A'], self.fn) out = fn(args['B']) if out is NotImplemented: raise Exception("Operation %s not implemented between %s and %s" % (fn, str(type(args['A'])), str(type(args['B'])))) # Coerce dtype if requested typ = self.stateGroup.state()['outputType'] if typ == 'no change': pass elif typ == 'input A': out = out.astype(args['A'].dtype) elif typ == 'input B': out = out.astype(args['B'].dtype) else: out = out.astype(typ) #print " ", fn, out return {'Out': out} class AbsNode(UniOpNode): """Returns abs(Inp). Does not check input types.""" nodeName = 'Abs' def __init__(self, name): UniOpNode.__init__(self, name, '__abs__') class AddNode(BinOpNode): """Returns A + B. Does not check input types.""" nodeName = 'Add' def __init__(self, name): BinOpNode.__init__(self, name, '__add__') class SubtractNode(BinOpNode): """Returns A - B. Does not check input types.""" nodeName = 'Subtract' def __init__(self, name): BinOpNode.__init__(self, name, '__sub__') class MultiplyNode(BinOpNode): """Returns A * B. Does not check input types.""" nodeName = 'Multiply' def __init__(self, name): BinOpNode.__init__(self, name, '__mul__') class DivideNode(BinOpNode): """Returns A / B. Does not check input types.""" nodeName = 'Divide' def __init__(self, name): # try truediv first, followed by div BinOpNode.__init__(self, name, ('__truediv__', '__div__')) class FloorDivideNode(BinOpNode): """Returns A // B. Does not check input types.""" nodeName = 'FloorDivide' def __init__(self, name): BinOpNode.__init__(self, name, '__floordiv__') pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/library/__init__.py000066400000000000000000000012271421045507400254230ustar00rootroot00000000000000from ..NodeLibrary import NodeLibrary, isNodeClass from . import Data, Display, Filters, Operators # Build default library LIBRARY = NodeLibrary() # For backward compatibility, expose the default library's properties here: NODE_LIST = LIBRARY.nodeList NODE_TREE = LIBRARY.nodeTree registerNodeType = LIBRARY.addNodeType getNodeTree = LIBRARY.getNodeTree getNodeType = LIBRARY.getNodeType # Add all nodes to the default library for mod in [Data, Display, Filters, Operators]: nodes = [getattr(mod, name) for name in dir(mod) if isNodeClass(getattr(mod, name))] for node in nodes: LIBRARY.addNodeType(node, [(mod.__name__.split('.')[-1],)]) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/library/common.py000066400000000000000000000136451421045507400251630ustar00rootroot00000000000000__all__ = ["CtrlNode", "PlottingCtrlNode", "metaArrayWrapper"] import numpy as np from ...Qt import QtCore, QtWidgets #from ...SignalProxy import SignalProxy from ...WidgetGroup import WidgetGroup from ...widgets.ColorButton import ColorButton from ...widgets.SpinBox import SpinBox #from ColorMapper import ColorMapper from ..Node import Node try: import metaarray HAVE_METAARRAY = True except: HAVE_METAARRAY = False def generateUi(opts): """Convenience function for generating common UI types""" widget = QtWidgets.QWidget() l = QtWidgets.QFormLayout() l.setSpacing(0) widget.setLayout(l) ctrls = {} row = 0 for opt in opts: if len(opt) == 2: k, t = opt o = {} elif len(opt) == 3: k, t, o = opt else: raise Exception("Widget specification must be (name, type) or (name, type, {opts})") ## clean out these options so they don't get sent to SpinBox hidden = o.pop('hidden', False) tip = o.pop('tip', None) if t == 'intSpin': w = QtWidgets.QSpinBox() if 'max' in o: w.setMaximum(o['max']) if 'min' in o: w.setMinimum(o['min']) if 'value' in o: w.setValue(o['value']) elif t == 'doubleSpin': w = QtWidgets.QDoubleSpinBox() if 'max' in o: w.setMaximum(o['max']) if 'min' in o: w.setMinimum(o['min']) if 'value' in o: w.setValue(o['value']) elif t == 'spin': w = SpinBox() w.setOpts(**o) elif t == 'check': w = QtWidgets.QCheckBox() if 'checked' in o: w.setChecked(o['checked']) elif t == 'combo': w = QtWidgets.QComboBox() for i in o['values']: w.addItem(i) #elif t == 'colormap': #w = ColorMapper() elif t == 'color': w = ColorButton() else: raise Exception("Unknown widget type '%s'" % str(t)) if tip is not None: w.setToolTip(tip) w.setObjectName(k) l.addRow(k, w) if hidden: w.hide() label = l.labelForField(w) label.hide() ctrls[k] = w w.rowNum = row row += 1 group = WidgetGroup(widget) return widget, group, ctrls class CtrlNode(Node): """Abstract class for nodes with auto-generated control UI""" sigStateChanged = QtCore.Signal(object) def __init__(self, name, ui=None, terminals=None): if terminals is None: terminals = {'In': {'io': 'in'}, 'Out': {'io': 'out', 'bypass': 'In'}} Node.__init__(self, name=name, terminals=terminals) if ui is None: if hasattr(self, 'uiTemplate'): ui = self.uiTemplate else: ui = [] self.ui, self.stateGroup, self.ctrls = generateUi(ui) self.stateGroup.sigChanged.connect(self.changed) def ctrlWidget(self): return self.ui def changed(self): self.update() self.sigStateChanged.emit(self) def process(self, In, display=True): out = self.processData(In) return {'Out': out} def saveState(self): state = Node.saveState(self) state['ctrl'] = self.stateGroup.state() return state def restoreState(self, state): Node.restoreState(self, state) if self.stateGroup is not None: self.stateGroup.setState(state.get('ctrl', {})) def hideRow(self, name): w = self.ctrls[name] l = self.ui.layout().labelForField(w) w.hide() l.hide() def showRow(self, name): w = self.ctrls[name] l = self.ui.layout().labelForField(w) w.show() l.show() class PlottingCtrlNode(CtrlNode): """Abstract class for CtrlNodes that can connect to plots.""" def __init__(self, name, ui=None, terminals=None): #print "PlottingCtrlNode.__init__ called." CtrlNode.__init__(self, name, ui=ui, terminals=terminals) self.plotTerminal = self.addOutput('plot', optional=True) def connected(self, term, remote): CtrlNode.connected(self, term, remote) if term is not self.plotTerminal: return node = remote.node() node.sigPlotChanged.connect(self.connectToPlot) self.connectToPlot(node) def disconnected(self, term, remote): CtrlNode.disconnected(self, term, remote) if term is not self.plotTerminal: return remote.node().sigPlotChanged.disconnect(self.connectToPlot) self.disconnectFromPlot(remote.node().getPlot()) def connectToPlot(self, node): """Define what happens when the node is connected to a plot""" raise Exception("Must be re-implemented in subclass") def disconnectFromPlot(self, plot): """Define what happens when the node is disconnected from a plot""" raise Exception("Must be re-implemented in subclass") def process(self, In, display=True): out = CtrlNode.process(self, In, display) out['plot'] = None return out def metaArrayWrapper(fn): def newFn(self, data, *args, **kargs): if HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): d1 = fn(self, data.view(np.ndarray), *args, **kargs) info = data.infoCopy() if d1.shape != data.shape: for i in range(data.ndim): if 'values' in info[i]: info[i]['values'] = info[i]['values'][:d1.shape[i]] return metaarray.MetaArray(d1, info=info) else: return fn(self, data, *args, **kargs) return newFn pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/flowchart/library/functions.py000066400000000000000000000267041421045507400257030ustar00rootroot00000000000000import numpy as np from ...metaarray import MetaArray def downsample(data, n, axis=0, xvals='subsample'): """Downsample by averaging points together across axis. If multiple axes are specified, runs once per axis. If a metaArray is given, then the axis values can be either subsampled or downsampled to match. """ ma = None if (hasattr(data, 'implements') and data.implements('MetaArray')): ma = data data = data.view(np.ndarray) if hasattr(axis, '__len__'): if not hasattr(n, '__len__'): n = [n]*len(axis) for i in range(len(axis)): data = downsample(data, n[i], axis[i]) return data nPts = int(data.shape[axis] / n) s = list(data.shape) s[axis] = nPts s.insert(axis+1, n) sl = [slice(None)] * data.ndim sl[axis] = slice(0, nPts*n) d1 = data[tuple(sl)] #print d1.shape, s d1.shape = tuple(s) d2 = d1.mean(axis+1) if ma is None: return d2 else: info = ma.infoCopy() if 'values' in info[axis]: if xvals == 'subsample': info[axis]['values'] = info[axis]['values'][::n][:nPts] elif xvals == 'downsample': info[axis]['values'] = downsample(info[axis]['values'], n) return MetaArray(d2, info=info) def applyFilter(data, b, a, padding=100, bidir=True): """Apply a linear filter with coefficients a, b. Optionally pad the data before filtering and/or run the filter in both directions.""" try: import scipy.signal except ImportError: raise Exception("applyFilter() requires the package scipy.signal.") d1 = data.view(np.ndarray) if padding > 0: d1 = np.hstack([d1[:padding], d1, d1[-padding:]]) if bidir: d1 = scipy.signal.lfilter(b, a, scipy.signal.lfilter(b, a, d1)[::-1])[::-1] else: d1 = scipy.signal.lfilter(b, a, d1) if padding > 0: d1 = d1[padding:-padding] if (hasattr(data, 'implements') and data.implements('MetaArray')): return MetaArray(d1, info=data.infoCopy()) else: return d1 def besselFilter(data, cutoff, order=1, dt=None, btype='low', bidir=True): """return data passed through bessel filter""" try: import scipy.signal except ImportError: raise Exception("besselFilter() requires the package scipy.signal.") if dt is None: try: tvals = data.xvals('Time') dt = (tvals[-1]-tvals[0]) / (len(tvals)-1) except: dt = 1.0 b,a = scipy.signal.bessel(order, cutoff * dt, btype=btype) return applyFilter(data, b, a, bidir=bidir) #base = data.mean() #d1 = scipy.signal.lfilter(b, a, data.view(ndarray)-base) + base #if (hasattr(data, 'implements') and data.implements('MetaArray')): #return MetaArray(d1, info=data.infoCopy()) #return d1 def butterworthFilter(data, wPass, wStop=None, gPass=2.0, gStop=20.0, order=1, dt=None, btype='low', bidir=True): """return data passed through bessel filter""" try: import scipy.signal except ImportError: raise Exception("butterworthFilter() requires the package scipy.signal.") if dt is None: try: tvals = data.xvals('Time') dt = (tvals[-1]-tvals[0]) / (len(tvals)-1) except: dt = 1.0 if wStop is None: wStop = wPass * 2.0 ord, Wn = scipy.signal.buttord(wPass*dt*2., wStop*dt*2., gPass, gStop) #print "butterworth ord %f Wn %f c %f sc %f" % (ord, Wn, cutoff, stopCutoff) b,a = scipy.signal.butter(ord, Wn, btype=btype) return applyFilter(data, b, a, bidir=bidir) def rollingSum(data, n): d1 = data.copy() d1[1:] += d1[:-1] # integrate d2 = np.empty(len(d1) - n + 1, dtype=data.dtype) d2[0] = d1[n-1] # copy first point d2[1:] = d1[n:] - d1[:-n] # subtract return d2 def mode(data, bins=None): """Returns location max value from histogram.""" if bins is None: bins = int(len(data)/10.) if bins < 2: bins = 2 y, x = np.histogram(data, bins=bins) ind = np.argmax(y) mode = 0.5 * (x[ind] + x[ind+1]) return mode def modeFilter(data, window=500, step=None, bins=None): """Filter based on histogram-based mode function""" d1 = data.view(np.ndarray) vals = [] l2 = int(window/2.) if step is None: step = l2 i = 0 while True: if i > len(data)-step: break vals.append(mode(d1[i:i+window], bins)) i += step chunks = [np.linspace(vals[0], vals[0], l2)] for i in range(len(vals)-1): chunks.append(np.linspace(vals[i], vals[i+1], step)) remain = len(data) - step*(len(vals)-1) - l2 chunks.append(np.linspace(vals[-1], vals[-1], remain)) d2 = np.hstack(chunks) if (hasattr(data, 'implements') and data.implements('MetaArray')): return MetaArray(d2, info=data.infoCopy()) return d2 def denoise(data, radius=2, threshold=4): """Very simple noise removal function. Compares a point to surrounding points, replaces with nearby values if the difference is too large.""" r2 = radius * 2 d1 = data.view(np.ndarray) d2 = d1[radius:] - d1[:-radius] #a derivative #d3 = data[r2:] - data[:-r2] #d4 = d2 - d3 stdev = d2.std() #print "denoise: stdev of derivative:", stdev mask1 = d2 > stdev*threshold #where derivative is large and positive mask2 = d2 < -stdev*threshold #where derivative is large and negative maskpos = mask1[:-radius] * mask2[radius:] #both need to be true maskneg = mask1[radius:] * mask2[:-radius] mask = maskpos + maskneg d5 = np.where(mask, d1[:-r2], d1[radius:-radius]) #where both are true replace the value with the value from 2 points before d6 = np.empty(d1.shape, dtype=d1.dtype) #add points back to the ends d6[radius:-radius] = d5 d6[:radius] = d1[:radius] d6[-radius:] = d1[-radius:] if (hasattr(data, 'implements') and data.implements('MetaArray')): return MetaArray(d6, info=data.infoCopy()) return d6 def adaptiveDetrend(data, x=None, threshold=3.0): """Return the signal with baseline removed. Discards outliers from baseline measurement.""" try: import scipy.signal except ImportError: raise Exception("adaptiveDetrend() requires the package scipy.signal.") if x is None: x = data.xvals(0) d = data.view(np.ndarray) d2 = scipy.signal.detrend(d) stdev = d2.std() mask = abs(d2) < stdev*threshold #d3 = where(mask, 0, d2) #d4 = d2 - lowPass(d3, cutoffs[1], dt=dt) lr = scipy.stats.linregress(x[mask], d[mask]) base = lr[1] + lr[0]*x d4 = d - base if (hasattr(data, 'implements') and data.implements('MetaArray')): return MetaArray(d4, info=data.infoCopy()) return d4 def histogramDetrend(data, window=500, bins=50, threshold=3.0, offsetOnly=False): """Linear detrend. Works by finding the most common value at the beginning and end of a trace, excluding outliers. If offsetOnly is True, then only the offset from the beginning of the trace is subtracted. """ d1 = data.view(np.ndarray) d2 = [d1[:window], d1[-window:]] v = [0, 0] for i in [0, 1]: d3 = d2[i] stdev = d3.std() mask = abs(d3-np.median(d3)) < stdev*threshold d4 = d3[mask] y, x = np.histogram(d4, bins=bins) ind = np.argmax(y) v[i] = 0.5 * (x[ind] + x[ind+1]) if offsetOnly: d3 = data.view(np.ndarray) - v[0] else: base = np.linspace(v[0], v[1], len(data)) d3 = data.view(np.ndarray) - base if (hasattr(data, 'implements') and data.implements('MetaArray')): return MetaArray(d3, info=data.infoCopy()) return d3 def concatenateColumns(data): """Returns a single record array with columns taken from the elements in data. data should be a list of elements, which can be either record arrays or tuples (name, type, data) """ ## first determine dtype dtype = [] names = set() maxLen = 0 for element in data: if isinstance(element, np.ndarray): ## use existing columns for i in range(len(element.dtype)): name = element.dtype.names[i] dtype.append((name, element.dtype[i])) maxLen = max(maxLen, len(element)) else: name, type, d = element if type is None: type = suggestDType(d) dtype.append((name, type)) if isinstance(d, list) or isinstance(d, np.ndarray): maxLen = max(maxLen, len(d)) if name in names: raise Exception('Name "%s" repeated' % name) names.add(name) ## create empty array out = np.empty(maxLen, dtype) ## fill columns for element in data: if isinstance(element, np.ndarray): for i in range(len(element.dtype)): name = element.dtype.names[i] try: out[name] = element[name] except: print("Column:", name) print("Input shape:", element.shape, element.dtype) print("Output shape:", out.shape, out.dtype) raise else: name, type, d = element out[name] = d return out def suggestDType(x): """Return a suitable dtype for x""" if isinstance(x, list) or isinstance(x, tuple): if len(x) == 0: raise Exception('can not determine dtype for empty list') x = x[0] if hasattr(x, 'dtype'): return x.dtype elif isinstance(x, float): return float elif isinstance(x, int): return int #elif isinstance(x, str): ## don't try to guess correct string length; use object instead. #return ' len(ft)/2.: break mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5 for j in range(ind1, ind2+1): phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts. re = mag * np.cos(phase) im = mag * np.sin(phase) ft[j] = re + im*1j ft[len(ft)-j] = re - im*1j data2 = np.fft.ifft(ft).real if (hasattr(data, 'implements') and data.implements('MetaArray')): return metaarray.MetaArray(data2, info=data.infoCopy()) else: return data2 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/frozenSupport.py000066400000000000000000000034641421045507400231340ustar00rootroot00000000000000## Definitions helpful in frozen environments (eg py2exe) import os import sys import zipfile def listdir(path): """Replacement for os.listdir that works in frozen environments.""" if not hasattr(sys, 'frozen'): return os.listdir(path) (zipPath, archivePath) = splitZip(path) if archivePath is None: return os.listdir(path) with zipfile.ZipFile(zipPath, "r") as zipobj: contents = zipobj.namelist() results = set() for name in contents: # components in zip archive paths are always separated by forward slash if name.startswith(archivePath) and len(name) > len(archivePath): name = name[len(archivePath):].split('/')[0] results.add(name) return list(results) def isdir(path): """Replacement for os.path.isdir that works in frozen environments.""" if not hasattr(sys, 'frozen'): return os.path.isdir(path) (zipPath, archivePath) = splitZip(path) if archivePath is None: return os.path.isdir(path) with zipfile.ZipFile(zipPath, "r") as zipobj: contents = zipobj.namelist() archivePath = archivePath.rstrip('/') + '/' ## make sure there's exactly one '/' at the end for c in contents: if c.startswith(archivePath): return True return False def splitZip(path): """Splits a path containing a zip file into (zipfile, subpath). If there is no zip file, returns (path, None)""" components = os.path.normpath(path).split(os.sep) for index, component in enumerate(components): if component.endswith('.zip'): zipPath = os.sep.join(components[0:index+1]) archivePath = ''.join([x+'/' for x in components[index+1:]]) return (zipPath, archivePath) else: return (path, None) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/functions.py000066400000000000000000003535531421045507400222530ustar00rootroot00000000000000""" functions.py - Miscellaneous functions with no other home Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ from __future__ import division import decimal import math import re import struct import sys import warnings from collections import OrderedDict import numpy as np from . import Qt, debug, reload from .metaarray import MetaArray from .Qt import QT_LIB, QtCore, QtGui from .util.cupy_helper import getCupy from .util.numba_helper import getNumbaFunctions # in order of appearance in this file. # add new functions to this list only if they are to reside in pg namespace. __all__ = [ 'siScale', 'siFormat', 'siParse', 'siEval', 'siApply', 'Color', 'mkColor', 'mkBrush', 'mkPen', 'hsvColor', 'CIELabColor', 'colorCIELab', 'colorDistance', 'colorTuple', 'colorStr', 'intColor', 'glColor', 'makeArrowPath', 'eq', 'affineSliceCoords', 'affineSlice', 'interweaveArrays', 'interpolateArray', 'subArray', 'transformToArray', 'transformCoordinates', 'solve3DTransform', 'solveBilinearTransform', 'clip_scalar', 'clip_array', 'rescaleData', 'applyLookupTable', 'makeRGBA', 'makeARGB', # 'try_fastpath_argb', 'ndarray_to_qimage', 'makeQImage', # 'ndarray_from_qimage', 'imageToArray', 'colorToAlpha', 'gaussianFilter', 'downsample', 'arrayToQPath', # 'ndarray_from_qpolygonf', 'create_qpolygonf', 'arrayToQPolygonF', 'isocurve', 'traceImage', 'isosurface', 'invertQTransform', 'pseudoScatter', 'toposort', 'disconnect', 'SignalBlock'] Colors = { 'b': QtGui.QColor(0,0,255,255), 'g': QtGui.QColor(0,255,0,255), 'r': QtGui.QColor(255,0,0,255), 'c': QtGui.QColor(0,255,255,255), 'm': QtGui.QColor(255,0,255,255), 'y': QtGui.QColor(255,255,0,255), 'k': QtGui.QColor(0,0,0,255), 'w': QtGui.QColor(255,255,255,255), 'd': QtGui.QColor(150,150,150,255), 'l': QtGui.QColor(200,200,200,255), 's': QtGui.QColor(100,100,150,255), } 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 FLOAT_REGEX = re.compile(r'(?P[+-]?((((\d+(\.\d*)?)|(\d*\.\d+))([eE][+-]?\d+)?)|((?i:nan)|(inf))))\s*((?P[u' + SI_PREFIXES + r']?)(?P\w.*))?$') INT_REGEX = re.compile(r'(?P[+-]?\d+)\s*(?P[u' + SI_PREFIXES + r']?)(?P.*)$') def siScale(x, minVal=1e-25, allowUnicode=True): """ Return the recommended scale factor and SI prefix string for x. Example:: siScale(0.0001) # returns (1e6, 'ΞΌ') # This indicates that the number 0.0001 is best represented as 0.0001 * 1e6 = 100 ΞΌUnits """ if isinstance(x, decimal.Decimal): x = float(x) try: if not math.isfinite(x): return(1, '') except: raise if abs(x) < minVal: m = 0 else: m = int(clip_scalar(math.floor(math.log(abs(x))/math.log(1000)), -9.0, 9.0)) if m == 0: pref = '' elif m < -8 or m > 8: pref = 'e%d' % (m*3) else: if allowUnicode: pref = SI_PREFIXES[m+8] else: pref = SI_PREFIXES_ASCII[m+8] m1 = -3*m p = 10.**m1 return (p, pref) def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, allowUnicode=True): """ Return the number x formatted in engineering notation with SI prefix. Example:: siFormat(0.0001, suffix='V') # returns "100 ΞΌV" """ if space is True: space = ' ' if space is False: space = '' (p, pref) = siScale(x, minVal, allowUnicode) if not (len(pref) > 0 and pref[0] == 'e'): pref = space + pref if error is None: fmt = "%." + str(precision) + "g%s%s" return fmt % (x*p, pref, suffix) else: if allowUnicode: plusminus = space + "Β±" + space else: plusminus = " +/- " fmt = "%." + str(precision) + "g%s%s%s%s" return fmt % (x*p, pref, suffix, plusminus, siFormat(error, precision=precision, suffix=suffix, space=space, minVal=minVal)) def siParse(s, regex=FLOAT_REGEX, suffix=None): """Convert a value written in SI notation to a tuple (number, si_prefix, suffix). Example:: siParse('100 Β΅V") # returns ('100', 'Β΅', 'V') Note that in the above example, the Β΅ symbol is the "micro sign" (UTF-8 0xC2B5), as opposed to the Greek letter mu (UTF-8 0xCEBC). Parameters ---------- s : str The string to parse. regex : re.Pattern, optional Compiled regular expression object for parsing. The default is a general-purpose regex for parsing floating point expressions, potentially containing an SI prefix and a suffix. suffix : str, optional Suffix to check for in ``s``. The default (None) indicates there may or may not be a suffix contained in the string and it is returned if found. An empty string ``""`` is handled differently: if the string contains a suffix, it is discarded. This enables interpreting characters following the numerical value as an SI prefix. """ s = s.strip() if suffix is not None and len(suffix) > 0: if s[-len(suffix):] != suffix: raise ValueError("String '%s' does not have the expected suffix '%s'" % (s, suffix)) s = s[:-len(suffix)] + 'X' # add a fake suffix so the regex still picks up the si prefix # special case: discard any extra characters if suffix is explicitly empty if suffix == "": s += 'X' m = regex.match(s) if m is None: raise ValueError('Cannot parse number "%s"' % s) try: sip = m.group('siPrefix') except IndexError: sip = '' if suffix is None: try: suf = m.group('suffix') except IndexError: suf = '' else: suf = suffix return m.group('number'), '' if sip is None else sip, '' if suf is None else suf def siEval(s, typ=float, regex=FLOAT_REGEX, suffix=None): """ Convert a value written in SI notation to its equivalent prefixless value. Example:: siEval("100 ΞΌV") # returns 0.0001 """ val, siprefix, suffix = siParse(s, regex, suffix=suffix) v = typ(val) return siApply(v, siprefix) def siApply(val, siprefix): """ """ n = SI_PREFIX_EXPONENTS[siprefix] if siprefix != '' else 0 if n > 0: return val * 10**n elif n < 0: # this case makes it possible to use Decimal objects here return val / 10**-n else: return val class Color(QtGui.QColor): def __init__(self, *args): QtGui.QColor.__init__(self, mkColor(*args)) def glColor(self): """Return (r,g,b,a) normalized for use in opengl""" return self.getRgbF() def __getitem__(self, ind): return (self.red, self.green, self.blue, self.alpha)[ind]() def mkColor(*args): """ Convenience function for constructing QColor from a variety of argument types. Accepted arguments are: ================ ================================================ 'c' one of: r, g, b, c, m, y, k, w R, G, B, [A] integers 0-255 (R, G, B, [A]) tuple of integers 0-255 float greyscale, 0.0-1.0 int see :func:`intColor() ` (int, hues) see :func:`intColor() ` "#RGB" hexadecimal strings prefixed with '#' "#RGBA" previously allowed use without prefix is deprecated and "#RRGGBB" will be removed in 0.13 "#RRGGBBAA" QColor QColor instance; makes a copy. ================ ================================================ """ err = 'Not sure how to make a color from "%s"' % str(args) if len(args) == 1: 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: warnings.warn( "Parsing of hex strings that do not start with '#' is" "deprecated and support will be removed in 0.13", DeprecationWarning, stacklevel=2 ) if len(c) == 3: r = int(c[0]*2, 16) g = int(c[1]*2, 16) b = int(c[2]*2, 16) a = 255 elif len(c) == 4: r = int(c[0]*2, 16) g = int(c[1]*2, 16) b = int(c[2]*2, 16) a = int(c[3]*2, 16) elif len(c) == 6: r = int(c[0:2], 16) g = int(c[2:4], 16) b = int(c[4:6], 16) a = 255 elif len(c) == 8: r = int(c[0:2], 16) g = int(c[2:4], 16) b = int(c[4:6], 16) a = int(c[6:8], 16) else: raise ValueError(f"Unknown how to convert string {c} to color") elif isinstance(args[0], QtGui.QColor): return QtGui.QColor(args[0]) elif np.issubdtype(type(args[0]), np.floating): r = g = b = int(args[0] * 255) a = 255 elif hasattr(args[0], '__len__'): if len(args[0]) == 3: r, g, b = args[0] a = 255 elif len(args[0]) == 4: r, g, b, a = args[0] elif len(args[0]) == 2: return intColor(*args[0]) else: raise TypeError(err) elif np.issubdtype(type(args[0]), np.integer): return intColor(args[0]) else: raise TypeError(err) elif len(args) == 3: r, g, b = args a = 255 elif len(args) == 4: r, g, b, a = args else: raise TypeError(err) args = [int(a) if np.isfinite(a) else 0 for a in (r, g, b, a)] return QtGui.QColor(*args) def mkBrush(*args, **kwds): """ | Convenience function for constructing Brush. | This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() ` | Calling mkBrush(None) returns an invisible brush. """ if 'color' in kwds: color = kwds['color'] elif len(args) == 1: arg = args[0] if arg is None: return QtGui.QBrush(QtCore.Qt.BrushStyle.NoBrush) elif isinstance(arg, QtGui.QBrush): return QtGui.QBrush(arg) else: color = arg elif len(args) > 1: color = args return QtGui.QBrush(mkColor(color)) def mkPen(*args, **kargs): """ Convenience function for constructing QPen. Examples:: mkPen(color) mkPen(color, width=2) mkPen(cosmetic=False, width=4.5, color='r') mkPen({'color': "#FF0", width: 2}) mkPen(None) # (no pen) In these examples, *color* may be replaced with any arguments accepted by :func:`mkColor() ` """ color = kargs.get('color', None) width = kargs.get('width', 1) style = kargs.get('style', None) dash = kargs.get('dash', None) cosmetic = kargs.get('cosmetic', True) hsv = kargs.get('hsv', None) if len(args) == 1: arg = args[0] if isinstance(arg, dict): return mkPen(**arg) if isinstance(arg, QtGui.QPen): return QtGui.QPen(arg) ## return a copy of this pen elif arg is None: style = QtCore.Qt.PenStyle.NoPen else: color = arg if len(args) > 1: color = args if color is None: color = mkColor('l') if hsv is not None: color = hsvColor(*hsv) else: color = mkColor(color) pen = QtGui.QPen(QtGui.QBrush(color), width) pen.setCosmetic(cosmetic) if style is not None: pen.setStyle(style) if dash is not None: pen.setDashPattern(dash) # for width > 1.0, we are drawing many short segments to emulate a # single polyline. the default SquareCap style causes artifacts. # these artifacts can be avoided by using RoundCap. # this does have a performance penalty, so enable it only # for thicker line widths where the artifacts are visible. if width > 4.0: pen.setCapStyle(QtCore.Qt.PenCapStyle.RoundCap) return pen 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)""" 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 MATRIX_XYZ_FROM_RGB = np.array( ( ( 0.4124, 0.3576, 0.1805), ( 0.2126, 0.7152, 0.0722), ( 0.0193, 0.1192, 0.9505) ) ) MATRIX_RGB_FROM_XYZ = np.array( ( ( 3.2410,-1.5374,-0.4985), (-0.9692, 1.8760, 0.0416), ( 0.0556,-0.2040, 1.0570) ) ) VECTOR_XYZn = np.array( ( 0.9505, 1.0000, 1.0891) ) # white reference at illuminant D65 def CIELabColor(L, a, b, alpha=1.0): """ Generates as QColor from CIE L*a*b* values. Parameters ---------- L: float Lightness value ranging from 0 to 100 a, b: float (green/red) and (blue/yellow) coordinates, typically -127 to +127. alpha: float, optional Opacity, ranging from 0 to 1 Notes ----- The CIE L*a*b* color space parametrizes color in terms of a luminance `L` and the `a` and `b` coordinates that locate the hue in terms of a "green to red" and a "blue to yellow" axis. These coordinates seek to parametrize human color preception in such a way that the Euclidean distance between the coordinates of two colors represents the visual difference between these colors. In particular, the difference Ξ”E = sqrt( (L1-L2)Β² + (a1-a2)Β² + (b1-b2)Β² ) = 2.3 is considered the smallest "just noticeable difference" between colors. This simple equation represents the CIE76 standard. Later standards CIE94 and CIE2000 refine the difference calculation Ξ”E, while maintaining the L*a*b* coordinates. Alternative (and arguably more accurate) methods exist to quantify color difference, but the CIELab color space remains a convenient approximation. Under a known illumination, assumed to be white standard illuminant D65 here, a CIELab color induces a response in the human eye that is described by the tristimulus value XYZ. Once this is known, an sRGB color can be calculated to induce the same response. More information and underlying mathematics can be found in e.g. "CIELab Color Space" by Gernot Hoffmann, available at http://docs-hoffmann.de/cielab03022003.pdf . Also see :func:`colorDistance() `. """ # convert to tristimulus XYZ values vec_XYZ = np.full(3, ( L +16)/116 ) # Y1 = (L+16)/116 vec_XYZ[0] += a / 500 # X1 = (L+16)/116 + a/500 vec_XYZ[2] -= b / 200 # Z1 = (L+16)/116 - b/200 for idx, val in enumerate(vec_XYZ): if val > 0.20689: vec_XYZ[idx] = vec_XYZ[idx]**3 else: vec_XYZ[idx] = (vec_XYZ[idx] - 16/116) / 7.787 vec_XYZ = VECTOR_XYZn * vec_XYZ # apply white reference # print(f'XYZ: {vec_XYZ}') # convert XYZ to linear RGB vec_RGB = MATRIX_RGB_FROM_XYZ @ vec_XYZ # gamma-encode linear RGB arr_sRGB = np.zeros(3) for idx, val in enumerate( vec_RGB[:3] ): if val > 0.0031308: # (t) RGB value for linear/exponential transition arr_sRGB[idx] = 1.055 * val**(1/2.4) - 0.055 else: arr_sRGB[idx] = 12.92 * val # (s) arr_sRGB = clip_array( arr_sRGB, 0.0, 1.0 ) # avoid QColor errors return QtGui.QColor.fromRgbF( *arr_sRGB, alpha ) def colorCIELab(qcol): """ Describes a QColor by an array of CIE L*a*b* values. Also see :func:`CIELabColor() ` . Parameters ---------- qcol: QColor QColor to be converted Returns ------- NumPy array Color coordinates `[L, a, b]`. """ srgb = qcol.getRgbF()[:3] # get sRGB values from QColor # convert gamma-encoded sRGB to linear: vec_RGB = np.zeros(3) for idx, val in enumerate( srgb ): if val > (12.92 * 0.0031308): # coefficients (s) * (t) vec_RGB[idx] = ((val+0.055)/1.055)**2.4 else: vec_RGB[idx] = val / 12.92 # (s) coefficient # converted linear RGB to tristimulus XYZ: vec_XYZ = MATRIX_XYZ_FROM_RGB @ vec_RGB # normalize with white reference and convert to L*a*b* values vec_XYZ1 = vec_XYZ / VECTOR_XYZn for idx, val in enumerate(vec_XYZ1): if val > 0.008856: vec_XYZ1[idx] = vec_XYZ1[idx]**(1/3) else: vec_XYZ1[idx] = 7.787*vec_XYZ1[idx] + 16/116 vec_Lab = np.array([ 116 * vec_XYZ1[1] - 16, # Y1 500 * (vec_XYZ1[0] - vec_XYZ1[1]), # X1 - Y1 200 * (vec_XYZ1[1] - vec_XYZ1[2])] ) # Y1 - Z1 return vec_Lab def colorDistance(colors, metric='CIE76'): """ Returns the perceptual distances between a sequence of QColors. See :func:`CIELabColor() ` for more information. Parameters ---------- colors: list of QColor Two or more colors to calculate the distances between. metric: string, optional Metric used to determined the difference. Only 'CIE76' is supported at this time, where a distance of 2.3 is considered a "just noticeable difference". The default may change as more metrics become available. Returns ------- List The `N-1` sequential distances between `N` colors. """ metric = metric.upper() if len(colors) < 1: return np.array([], dtype=np.float) if metric == 'CIE76': dist = [] lab1 = None for col in colors: lab2 = colorCIELab(col) if lab1 is None: #initialize on first element lab1 = lab2 continue dE = math.sqrt( np.sum( (lab1-lab2)**2 ) ) dist.append(dE) lab1 = lab2 return np.array(dist) raise ValueError(f'Metric {metric} is not available.') def colorTuple(c): """Return a tuple (R,G,B,A) from a QColor""" return c.getRgb() def colorStr(c): """Generate a hex string code from a QColor""" return ('%02x'*4) % colorTuple(c) def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255): """ Creates a QColor from a single index. Useful for stepping through a predefined list of colors. The argument *index* determines which color from the set will be returned. All other arguments determine what the set of predefined colors will be Colors are chosen by cycling across hues while varying the value (brightness). By default, this selects from a list of 9 hues.""" hues = int(hues) values = int(values) ind = int(index) % (hues * values) indh = ind % hues indv = ind // hues if values > 1: v = minValue + indv * ((maxValue-minValue) // (values-1)) else: v = maxValue h = minHue + (indh * (maxHue-minHue)) // hues return QtGui.QColor.fromHsv(h, sat, v, alpha) def glColor(*args, **kargs): """ Convert a color to OpenGL color format (r,g,b,a) floats 0.0-1.0 Accepts same arguments as :func:`mkColor `. """ c = mkColor(*args, **kargs) return c.getRgbF() def makeArrowPath(headLen=20, headWidth=None, tipAngle=20, tailLen=20, tailWidth=3, baseAngle=0): """ Construct a path outlining an arrow with the given dimensions. The arrow points in the -x direction with tip positioned at 0,0. If *headWidth* is supplied, it overrides *tipAngle* (in degrees). If *tailLen* is None, no tail will be drawn. """ if headWidth is None: headWidth = headLen * math.tan(math.radians(tipAngle * 0.5)) path = QtGui.QPainterPath() path.moveTo(0,0) path.lineTo(headLen, -headWidth) if tailLen is None: innerY = headLen - headWidth * math.tan(math.radians(baseAngle)) path.lineTo(innerY, 0) else: tailWidth *= 0.5 innerY = headLen - (headWidth-tailWidth) * math.tan(math.radians(baseAngle)) path.lineTo(innerY, -tailWidth) path.lineTo(headLen + tailLen, -tailWidth) path.lineTo(headLen + tailLen, tailWidth) path.lineTo(innerY, tailWidth) path.lineTo(headLen, headWidth) path.lineTo(0,0) return path def eq(a, b): """The great missing equivalence function: Guaranteed evaluation to a single bool value. This function has some important differences from the == operator: 1. Returns True if a IS b, even if a==b still evaluates to False. 2. While a is b will catch the case with np.nan values, special handling is done for distinct float('nan') instances using math.isnan. 3. Tests for equivalence using ==, but silently ignores some common exceptions that can occur (AtrtibuteError, ValueError). 4. When comparing arrays, returns False if the array shapes are not the same. 5. When comparing arrays of the same shape, returns True only if all elements are equal (whereas the == operator would return a boolean array). 6. Collections (dict, list, etc.) must have the same type to be considered equal. One consequence is that comparing a dict to an OrderedDict will always return False. """ if a is b: return True # The above catches np.nan, but not float('nan') if isinstance(a, float) and isinstance(b, float): if math.isnan(a) and math.isnan(b): return True # Avoid comparing large arrays against scalars; this is expensive and we know it should return False. aIsArr = isinstance(a, (np.ndarray, MetaArray)) bIsArr = isinstance(b, (np.ndarray, MetaArray)) if (aIsArr or bIsArr) and type(a) != type(b): return False # If both inputs are arrays, we can speeed up comparison if shapes / dtypes don't match # NOTE: arrays of dissimilar type should be considered unequal even if they are numerically # equal because they may behave differently when computed on. if aIsArr and bIsArr and (a.shape != b.shape or a.dtype != b.dtype): return False # Recursively handle common containers if isinstance(a, dict) and isinstance(b, dict): if type(a) != type(b) or len(a) != len(b): return False if set(a.keys()) != set(b.keys()): return False for k, v in a.items(): if not eq(v, b[k]): return False if isinstance(a, OrderedDict) or sys.version_info >= (3, 7): for a_item, b_item in zip(a.items(), b.items()): if not eq(a_item, b_item): return False return True if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)): if type(a) != type(b) or len(a) != len(b): return False for v1,v2 in zip(a, b): if not eq(v1, v2): return False return True # Test for equivalence. # If the test raises a recognized exception, then return Falase try: try: # Sometimes running catch_warnings(module=np) generates AttributeError ??? catcher = warnings.catch_warnings(module=np) # ignore numpy futurewarning (numpy v. 1.10) catcher.__enter__() except Exception: catcher = None e = a==b except (ValueError, AttributeError): return False except: print('failed to evaluate equivalence for:') print(" a:", str(type(a)), str(a)) print(" b:", str(type(b)), str(b)) raise finally: if catcher is not None: catcher.__exit__(None, None, None) t = type(e) if t is bool: return e elif t is np.bool_: return bool(e) elif isinstance(e, np.ndarray) or (hasattr(e, 'implements') and e.implements('MetaArray')): try: ## disaster: if a is an empty array and b is not, then e.all() is True if a.shape != b.shape: return False except: return False if (hasattr(e, 'implements') and e.implements('MetaArray')): return e.asarray().all() else: return e.all() else: raise TypeError("== operator returned type %s" % str(type(e))) def affineSliceCoords(shape, origin, vectors, axes): """Return the array of coordinates used to sample data arrays in affineSlice(). """ # sanity check if len(shape) != len(vectors): raise Exception("shape and vectors must have same length.") if len(origin) != len(axes): raise Exception("origin and axes must have same length.") for v in vectors: if len(v) != len(axes): raise Exception("each vector must be same length as axes.") shape = list(map(np.ceil, shape)) ## make sure vectors are arrays if not isinstance(vectors, np.ndarray): vectors = np.array(vectors) if not isinstance(origin, np.ndarray): origin = np.array(origin) origin.shape = (len(axes),) + (1,)*len(shape) ## Build array of sample locations. grid = np.mgrid[tuple([slice(0,x) for x in shape])] ## mesh grid of indexes x = (grid[np.newaxis,...] * vectors.transpose()[(Ellipsis,) + (np.newaxis,)*len(shape)]).sum(axis=1) ## magic x += origin return x def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs): """ Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data. The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using either interpolateArray if order<2 or scipy.ndimage.map_coordinates otherwise. For a graphical interface to this function, see :func:`ROI.getArrayRegion ` ============== ==================================================================================================== **Arguments:** *data* (ndarray) the original dataset *shape* the shape of the slice to take (Note the return value may have more dimensions than len(shape)) *origin* the location in the original dataset that will become the origin of the sliced data. *vectors* list of unit vectors which point in the direction of the slice axes. Each vector must have the same length as *axes*. If the vectors are not unit length, the result will be scaled relative to the original data. If the vectors are not orthogonal, the result will be sheared relative to the original data. *axes* The axes in the original dataset which correspond to the slice *vectors* *order* The order of spline interpolation. Default is 1 (linear). See scipy.ndimage.map_coordinates for more information. *returnCoords* If True, return a tuple (result, coords) where coords is the array of coordinates used to select values from the original dataset. *All extra keyword arguments are passed to scipy.ndimage.map_coordinates.* -------------------------------------------------------------------------------------------------------------------- ============== ==================================================================================================== Note the following must be true: | len(shape) == len(vectors) | len(origin) == len(axes) == len(vectors[i]) Example: start with a 4D fMRI data set, take a diagonal-planar slice out of the last 3 axes * data = array with dims (time, x, y, z) = (100, 40, 40, 40) * The plane to pull out is perpendicular to the vector (x,y,z) = (1,1,1) * The origin of the slice will be at (x,y,z) = (40, 0, 0) * We will slice a 20x20 plane from each timepoint, giving a final shape (100, 20, 20) The call for this example would look like:: affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3)) """ x = affineSliceCoords(shape, origin, vectors, axes) ## transpose data so slice axes come first trAx = list(range(data.ndim)) for ax in axes: trAx.remove(ax) tr1 = tuple(axes) + tuple(trAx) data = data.transpose(tr1) #print "tr1:", tr1 ## dims are now [(slice axes), (other axes)] if order > 1: try: import scipy.ndimage except ImportError: raise ImportError("Interpolating with order > 1 requires the scipy.ndimage module, but it could not be imported.") # iterate manually over unused axes since map_coordinates won't do it for us extraShape = data.shape[len(axes):] output = np.empty(tuple(shape) + extraShape, dtype=data.dtype) for inds in np.ndindex(*extraShape): ind = (Ellipsis,) + inds output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs) else: # map_coordinates expects the indexes as the first axis, whereas # interpolateArray expects indexes at the last axis. tr = tuple(range(1, x.ndim)) + (0,) output = interpolateArray(data, x.transpose(tr), order=order) tr = list(range(output.ndim)) trb = [] for i in range(min(axes)): ind = tr1.index(i) + (len(shape)-len(axes)) tr.remove(ind) trb.append(ind) tr2 = tuple(trb+tr) ## Untranspose array before returning output = output.transpose(tr2) if returnCoords: return (output, x) else: return output def interweaveArrays(*args): """ Parameters ---------- args : numpy.ndarray series of 1D numpy arrays of the same length and dtype Returns ------- numpy.ndarray A numpy array with all the input numpy arrays interwoven Examples -------- >>> result = interweaveArrays(numpy.ndarray([0, 2, 4]), numpy.ndarray([1, 3, 5])) >>> result array([0, 1, 2, 3, 4, 5]) """ size = sum(x.size for x in args) result = np.empty((size,), dtype=args[0].dtype) n = len(args) for index, array in enumerate(args): result[index::n] = array return result def interpolateArray(data, x, default=0.0, order=1): """ N-dimensional interpolation similar to scipy.ndimage.map_coordinates. This function returns linearly-interpolated values sampled from a regular grid of data. It differs from `ndimage.map_coordinates` by allowing broadcasting within the input array. ============== =========================================================================================== **Arguments:** *data* Array of any shape containing the values to be interpolated. *x* Array with (shape[-1] <= data.ndim) containing the locations within *data* to interpolate. (note: the axes for this argument are transposed relative to the same argument for `ndimage.map_coordinates`). *default* Value to return for locations in *x* that are outside the bounds of *data*. *order* Order of interpolation: 0=nearest, 1=linear. ============== =========================================================================================== Returns array of shape (x.shape[:-1] + data.shape[x.shape[-1]:]) For example, assume we have the following 2D image data:: >>> data = np.array([[1, 2, 4 ], [10, 20, 40 ], [100, 200, 400]]) To compute a single interpolated point from this data:: >>> x = np.array([(0.5, 0.5)]) >>> interpolateArray(data, x) array([ 8.25]) To compute a 1D list of interpolated locations:: >>> x = np.array([(0.5, 0.5), (1.0, 1.0), (1.0, 2.0), (1.5, 0.0)]) >>> interpolateArray(data, x) array([ 8.25, 20. , 40. , 55. ]) To compute a 2D array of interpolated locations:: >>> x = np.array([[(0.5, 0.5), (1.0, 2.0)], [(1.0, 1.0), (1.5, 0.0)]]) >>> interpolateArray(data, x) array([[ 8.25, 40. ], [ 20. , 55. ]]) ..and so on. The *x* argument may have any shape as long as ```x.shape[-1] <= data.ndim```. In the case that ```x.shape[-1] < data.ndim```, then the remaining axes are simply broadcasted as usual. For example, we can interpolate one location from an entire row of the data:: >>> x = np.array([[0.5]]) >>> interpolateArray(data, x) array([[ 5.5, 11. , 22. ]]) This is useful for interpolating from arrays of colors, vertexes, etc. """ if order not in (0, 1): raise ValueError("interpolateArray requires order=0 or 1 (got %s)" % order) prof = debug.Profiler() nd = data.ndim md = x.shape[-1] if md > nd: raise TypeError("x.shape[-1] must be less than or equal to data.ndim") totalMask = np.ones(x.shape[:-1], dtype=bool) # keep track of out-of-bound indexes if order == 0: xinds = np.round(x).astype(int) # NOTE: for 0.5 this rounds to the nearest *even* number for ax in range(md): mask = (xinds[...,ax] >= 0) & (xinds[...,ax] <= data.shape[ax]-1) xinds[...,ax][~mask] = 0 # keep track of points that need to be set to default totalMask &= mask result = data[tuple([xinds[...,i] for i in range(xinds.shape[-1])])] elif order == 1: # First we generate arrays of indexes that are needed to # extract the data surrounding each point fields = np.mgrid[(slice(0,order+1),) * md] xmin = np.floor(x).astype(int) xmax = xmin + 1 indexes = np.concatenate([xmin[np.newaxis, ...], xmax[np.newaxis, ...]]) fieldInds = [] for ax in range(md): mask = (xmin[...,ax] >= 0) & (x[...,ax] <= data.shape[ax]-1) # keep track of points that need to be set to default totalMask &= mask # ..and keep track of indexes that are out of bounds # (note that when x[...,ax] == data.shape[ax], then xmax[...,ax] will be out # of bounds, but the interpolation will work anyway) mask &= (xmax[...,ax] < data.shape[ax]) axisIndex = indexes[...,ax][fields[ax]] axisIndex[axisIndex < 0] = 0 axisIndex[axisIndex >= data.shape[ax]] = 0 fieldInds.append(axisIndex) prof() # Get data values surrounding each requested point fieldData = data[tuple(fieldInds)] prof() ## Interpolate s = np.empty((md,) + fieldData.shape, dtype=float) dx = x - xmin # reshape fields for arithmetic against dx for ax in range(md): f1 = fields[ax].reshape(fields[ax].shape + (1,)*(dx.ndim-1)) sax = f1 * dx[...,ax] + (1-f1) * (1-dx[...,ax]) sax = sax.reshape(sax.shape + (1,) * (s.ndim-1-sax.ndim)) s[ax] = sax s = np.product(s, axis=0) result = fieldData * s for i in range(md): result = result.sum(axis=0) prof() if totalMask.ndim > 0: result[~totalMask] = default else: if totalMask is False: result[:] = default prof() return result def subArray(data, offset, shape, stride): """ Unpack a sub-array from *data* using the specified offset, shape, and stride. Note that *stride* is specified in array elements, not bytes. For example, we have a 2x3 array packed in a 1D array as follows:: data = [_, _, 00, 01, 02, _, 10, 11, 12, _] Then we can unpack the sub-array with this call:: subArray(data, offset=2, shape=(2, 3), stride=(4, 1)) ..which returns:: [[00, 01, 02], [10, 11, 12]] This function operates only on the first axis of *data*. So changing the input in the example above to have shape (10, 7) would cause the output to have shape (2, 3, 7). """ data = np.ascontiguousarray(data)[offset:] shape = tuple(shape) extraShape = data.shape[1:] strides = list(data.strides[::-1]) itemsize = strides[-1] for s in stride[1::-1]: strides.append(itemsize * s) strides = tuple(strides[::-1]) return np.ndarray(buffer=data, shape=shape+extraShape, strides=strides, dtype=data.dtype) def transformToArray(tr): """ Given a QTransform, return a 3x3 numpy array. Given a QMatrix4x4, return a 4x4 numpy array. Example: map an array of x,y coordinates through a transform:: ## coordinates to map are (1,5), (2,6), (3,7), and (4,8) coords = np.array([[1,2,3,4], [5,6,7,8], [1,1,1,1]]) # the extra '1' coordinate is needed for translation to work ## Make an example transform tr = QtGui.QTransform() tr.translate(3,4) tr.scale(2, 0.1) ## convert to array m = pg.transformToArray()[:2] # ignore the perspective portion of the transformation ## map coordinates through transform mapped = np.dot(m, coords) """ #return np.array([[tr.m11(), tr.m12(), tr.m13()],[tr.m21(), tr.m22(), tr.m23()],[tr.m31(), tr.m32(), tr.m33()]]) ## The order of elements given by the method names m11..m33 is misleading-- ## It is most common for x,y translation to occupy the positions 1,3 and 2,3 in ## a transformation matrix. However, with QTransform these values appear at m31 and m32. ## So the correct interpretation is transposed: if isinstance(tr, QtGui.QTransform): return np.array([[tr.m11(), tr.m21(), tr.m31()], [tr.m12(), tr.m22(), tr.m32()], [tr.m13(), tr.m23(), tr.m33()]]) elif isinstance(tr, QtGui.QMatrix4x4): return np.array(tr.copyDataTo()).reshape(4,4) else: raise Exception("Transform argument must be either QTransform or QMatrix4x4.") def transformCoordinates(tr, coords, transpose=False): """ Map a set of 2D or 3D coordinates through a QTransform or QMatrix4x4. The shape of coords must be (2,...) or (3,...) The mapping will _ignore_ any perspective transformations. For coordinate arrays with ndim=2, this is basically equivalent to matrix multiplication. Most arrays, however, prefer to put the coordinate axis at the end (eg. shape=(...,3)). To allow this, use transpose=True. """ if transpose: ## move last axis to beginning. This transposition will be reversed before returning the mapped coordinates. coords = coords.transpose((coords.ndim-1,) + tuple(range(0,coords.ndim-1))) nd = coords.shape[0] if isinstance(tr, np.ndarray): m = tr else: m = transformToArray(tr) m = m[:m.shape[0]-1] # remove perspective ## If coords are 3D and tr is 2D, assume no change for Z axis if m.shape == (2,3) and nd == 3: m2 = np.zeros((3,4)) m2[:2, :2] = m[:2,:2] m2[:2, 3] = m[:2,2] m2[2,2] = 1 m = m2 ## if coords are 2D and tr is 3D, ignore Z axis if m.shape == (3,4) and nd == 2: m2 = np.empty((2,3)) m2[:,:2] = m[:2,:2] m2[:,2] = m[:2,3] m = m2 ## reshape tr and coords to prepare for multiplication m = m.reshape(m.shape + (1,)*(coords.ndim-1)) coords = coords[np.newaxis, ...] # separate scale/rotate and translation translate = m[:,-1] m = m[:, :-1] ## map coordinates and return # 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: ## move first axis to end. mapped = mapped.transpose(tuple(range(1,mapped.ndim)) + (0,)) return mapped def solve3DTransform(points1, points2): """ Find a 3D transformation matrix that maps points1 onto points2. Points must be specified as either lists of 4 Vectors or (4, 3) arrays. """ import numpy.linalg pts = [] for inp in (points1, points2): if isinstance(inp, np.ndarray): A = np.empty((4,4), dtype=float) A[:,:3] = inp[:,:3] A[:,3] = 1.0 else: A = np.array([[inp[i].x(), inp[i].y(), inp[i].z(), 1] for i in range(4)]) pts.append(A) ## solve 3 sets of linear equations to determine transformation matrix elements matrix = np.zeros((4,4)) for i in range(3): ## solve Ax = B; x is one row of the desired transformation matrix matrix[i] = numpy.linalg.solve(pts[0], pts[1][:,i]) return matrix def solveBilinearTransform(points1, points2): """ Find a bilinear transformation matrix (2x4) that maps points1 onto points2. Points must be specified as a list of 4 Vector, Point, QPointF, etc. To use this matrix to map a point [x,y]:: mapped = np.dot(matrix, [x*y, x, y, 1]) """ import numpy.linalg ## A is 4 rows (points) x 4 columns (xy, x, y, 1) ## B is 4 rows (points) x 2 columns (x, y) A = np.array([[points1[i].x()*points1[i].y(), points1[i].x(), points1[i].y(), 1] for i in range(4)]) B = np.array([[points2[i].x(), points2[i].y()] for i in range(4)]) ## solve 2 sets of linear equations to determine transformation matrix elements matrix = np.zeros((2,4)) for i in range(2): matrix[i] = numpy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix return matrix def clip_scalar(val, vmin, vmax): """ convenience function to avoid using np.clip for scalar values """ return vmin if val < vmin else vmax if val > vmax else val # umath.clip was slower than umath.maximum(umath.minimum). # See https://github.com/numpy/numpy/pull/20134 for details. _win32_clip_workaround_needed = ( sys.platform == 'win32' and tuple(map(int, np.__version__.split(".")[:2])) < (1, 22) ) def clip_array(arr, vmin, vmax, out=None): # replacement for np.clip due to regression in # performance since numpy 1.17 # https://github.com/numpy/numpy/issues/14281 if vmin is None and vmax is None: # let np.clip handle the error return np.clip(arr, vmin, vmax, out=out) if vmin is None: return np.core.umath.minimum(arr, vmax, out=out) elif vmax is None: return np.core.umath.maximum(arr, vmin, out=out) elif _win32_clip_workaround_needed: if out is None: out = np.empty_like(arr) out = np.core.umath.minimum(arr, vmax, out=out) return np.core.umath.maximum(out, vmin, out=out) else: return np.core.umath.clip(arr, vmin, vmax, out=out) def _rescaleData_nditer(data_in, scale, offset, work_dtype, out_dtype, clip): """Refer to documentation for rescaleData()""" data_out = np.empty_like(data_in, dtype=out_dtype) # integer clip operations are faster than float clip operations # so test to see if we can perform integer clipping fits_int32 = False if data_in.dtype.kind in 'ui' and out_dtype.kind in 'ui': # estimate whether data range after rescale will fit within an int32. # this means that the input dtype should be an 8-bit or 16-bit integer type. # casting to an int32 will lose the fractional part, therefore the # output dtype must be an integer kind. lim_in = np.iinfo(data_in.dtype) # convert numpy scalar to python scalar to avoid overflow warnings lo = offset.item(0) if isinstance(offset, np.number) else offset dst_bounds = scale * (lim_in.min - lo), scale * (lim_in.max - lo) if dst_bounds[1] < dst_bounds[0]: dst_bounds = dst_bounds[1], dst_bounds[0] lim32 = np.iinfo(np.int32) fits_int32 = lim32.min < dst_bounds[0] and dst_bounds[1] < lim32.max it = np.nditer([data_in, data_out], flags=['external_loop', 'buffered'], op_flags=[['readonly'], ['writeonly', 'no_broadcast']], op_dtypes=[None, work_dtype], casting='unsafe', buffersize=32768) with it: for x, y in it: y[...] = x y -= offset y *= scale # Clip before converting dtype to avoid overflow if clip is not None: if fits_int32: # converts to int32, clips back to float32 np.core.umath.clip(y.astype(np.int32), clip[0], clip[1], out=y) else: clip_array(y, clip[0], clip[1], out=y) return data_out def rescaleData(data, scale, offset, dtype=None, clip=None): """Return data rescaled and optionally cast to a new dtype. The scaling operation is:: data => (data-offset) * scale """ if dtype is None: out_dtype = data.dtype else: out_dtype = np.dtype(dtype) if out_dtype.kind in 'ui': lim = np.iinfo(out_dtype) if clip is None: # don't let rescale cause integer overflow clip = lim.min, lim.max clip = max(clip[0], lim.min), min(clip[1], lim.max) # make clip limits integer-valued (no need to cast to int) # this improves performance, especially on Windows clip = [math.trunc(x) for x in clip] if np.can_cast(data, np.float32): work_dtype = np.float32 else: work_dtype = np.float64 cp = getCupy() if cp and cp.get_array_module(data) == cp: # Cupy does not support nditer # https://github.com/cupy/cupy/issues/5021 data_out = data.astype(work_dtype, copy=True) data_out -= offset data_out *= scale # Clip before converting dtype to avoid overflow if clip is not None: clip_array(data_out, clip[0], clip[1], out=data_out) # don't copy if no change in dtype return data_out.astype(out_dtype, copy=False) numba_fn = getNumbaFunctions() if numba_fn and clip is not None: # if we got here by makeARGB(), clip will not be None at this point return numba_fn.rescaleData(data, scale, offset, out_dtype, clip) return _rescaleData_nditer(data, scale, offset, work_dtype, out_dtype, clip) def applyLookupTable(data, lut): """ Uses values in *data* as indexes to select values from *lut*. The returned data has shape data.shape + lut.shape[1:] Note: color gradient lookup tables can be generated using GradientWidget. Parameters ---------- data : ndarray lut : ndarray Either cupy or numpy arrays are accepted, though this function has only consistently behaved correctly on windows with cuda toolkit version >= 11.1. """ if data.dtype.kind not in ('i', 'u'): data = data.astype(int) cp = getCupy() if cp and cp.get_array_module(data) == cp: # cupy.take only supports "wrap" mode return cp.take(lut, cp.clip(data, 0, lut.shape[0] - 1), axis=0) else: return np.take(lut, data, axis=0, mode='clip') def makeRGBA(*args, **kwds): """Equivalent to makeARGB(..., useRGBA=True)""" kwds['useRGBA'] = True return makeARGB(*args, **kwds) def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False, maskNans=True, output=None): """ Convert an array of values into an ARGB array suitable for building QImages, OpenGL textures, etc. Returns the ARGB array (unsigned byte) and a boolean indicating whether there is alpha channel data. This is a two stage process: 1) Rescale the data based on the values in the *levels* argument (min, max). 2) Determine the final output by passing the rescaled values through a lookup table. Both stages are optional. ============== ================================================================================== **Arguments:** data numpy array of int/float types. If levels List [min, max]; optionally rescale data before converting through the lookup table. The data is rescaled such that min->0 and max->*scale*:: rescaled = (clip(data, min, max) - min) * (*scale* / (max - min)) It is also possible to use a 2D (N,2) array of values for levels. In this case, it is assumed that each pair of min,max values in the levels array should be applied to a different subset of the input data (for example, the input data may already have RGB values and the levels are used to independently scale each channel). The use of this feature requires that levels.shape[0] == data.shape[-1]. scale The maximum value to which data will be rescaled before being passed through the lookup table (or returned if there is no lookup table). By default this will be set to the length of the lookup table, or 255 if no lookup table is provided. lut Optional lookup table (array with dtype=ubyte). Values in data will be converted to color by indexing directly from lut. The output data shape will be input.shape + lut.shape[1:]. Lookup tables can be built using ColorMap or GradientWidget. useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures). The default is False, which returns in ARGB order for use with QImage (Note that 'ARGB' is a term used by the Qt documentation; the *actual* order is BGRA). maskNans Enable or disable masking NaNs as transparent. ============== ================================================================================== """ cp = getCupy() xp = cp.get_array_module(data) if cp else np profile = debug.Profiler() if data.ndim not in (2, 3): raise TypeError("data must be 2D or 3D") if data.ndim == 3 and data.shape[2] > 4: raise TypeError("data.shape[2] must be <= 4") if lut is not None and not isinstance(lut, xp.ndarray): lut = xp.array(lut) if levels is None: # automatically decide levels based on data dtype if data.dtype.kind == 'u': levels = xp.array([0, 2**(data.itemsize*8)-1]) elif data.dtype.kind == 'i': s = 2**(data.itemsize*8 - 1) levels = xp.array([-s, s-1]) elif data.dtype.kind == 'b': levels = xp.array([0,1]) else: raise Exception('levels argument is required for float input types') if not isinstance(levels, xp.ndarray): levels = xp.array(levels) levels = levels.astype(xp.float64) if levels.ndim == 1: if levels.shape[0] != 2: raise Exception('levels argument must have length 2') elif levels.ndim == 2: if lut is not None and lut.ndim > 1: raise Exception('Cannot make ARGB data when both levels and lut have ndim > 2') if levels.shape != (data.shape[-1], 2): raise Exception('levels must have shape (data.shape[-1], 2)') else: raise Exception("levels argument must be 1D or 2D (got shape=%s)." % repr(levels.shape)) profile('check inputs') # Decide on maximum scaled value if scale is None: if lut is not None: scale = lut.shape[0] else: scale = 255. # Decide on the dtype we want after scaling if lut is None: dtype = xp.ubyte else: dtype = xp.min_scalar_type(lut.shape[0]-1) # awkward, but fastest numpy native nan evaluation nanMask = None if maskNans and data.dtype.kind == 'f' and xp.isnan(data.min()): nanMask = xp.isnan(data) if data.ndim > 2: nanMask = xp.any(nanMask, axis=-1) # Apply levels if given if levels is not None: if isinstance(levels, xp.ndarray) and levels.ndim == 2: # we are going to rescale each channel independently if levels.shape[0] != data.shape[-1]: raise Exception("When rescaling multi-channel data, there must be the same number of levels as channels (data.shape[-1] == levels.shape[0])") newData = xp.empty(data.shape, dtype=int) for i in range(data.shape[-1]): minVal, maxVal = levels[i] if minVal == maxVal: maxVal = xp.nextafter(maxVal, 2*maxVal) rng = maxVal-minVal rng = 1 if rng == 0 else rng newData[...,i] = rescaleData(data[...,i], scale / rng, minVal, dtype=dtype) data = newData else: # Apply level scaling unless it would have no effect on the data minVal, maxVal = levels if minVal != 0 or maxVal != scale: if minVal == maxVal: maxVal = xp.nextafter(maxVal, 2*maxVal) rng = maxVal-minVal rng = 1 if rng == 0 else rng data = rescaleData(data, scale/rng, minVal, dtype=dtype) profile('apply levels') # apply LUT if given if lut is not None: data = applyLookupTable(data, lut) else: if data.dtype != xp.ubyte: data = xp.clip(data, 0, 255).astype(xp.ubyte) profile('apply lut') # this will be the final image array if output is None: imgData = xp.empty(data.shape[:2]+(4,), dtype=xp.ubyte) else: imgData = output profile('allocate') # decide channel order if useRGBA: dst_order = [0, 1, 2, 3] # R,G,B,A elif sys.byteorder == 'little': dst_order = [2, 1, 0, 3] # B,G,R,A (ARGB32 little endian) else: dst_order = [1, 2, 3, 0] # A,R,G,B (ARGB32 big endian) # copy data into image array fastpath = try_fastpath_argb(xp, data, imgData, useRGBA) if fastpath: pass elif data.ndim == 2: # This is tempting: # imgData[..., :3] = data[..., xp.newaxis] # ..but it turns out this is faster: for i in range(3): imgData[..., dst_order[i]] = data elif data.shape[2] == 1: for i in range(3): imgData[..., dst_order[i]] = data[..., 0] else: for i in range(0, data.shape[2]): imgData[..., dst_order[i]] = data[..., i] profile('reorder channels') # add opaque alpha channel if needed if data.ndim == 3 and data.shape[2] == 4: alpha = True else: alpha = False if not fastpath: # fastpath has already filled it in imgData[..., dst_order[3]] = 255 # apply nan mask through alpha channel if nanMask is not None: alpha = True # Workaround for https://github.com/cupy/cupy/issues/4693 if xp == cp: imgData[nanMask, :, dst_order[3]] = 0 else: imgData[nanMask, dst_order[3]] = 0 profile('alpha channel') return imgData, alpha def try_fastpath_argb(xp, ain, aout, useRGBA): # we only optimize for certain cases # return False if we did not handle it can_handle = xp is np and ain.dtype == xp.ubyte and ain.flags['C_CONTIGUOUS'] if not can_handle: return False nrows, ncols = ain.shape[:2] nchans = 1 if ain.ndim == 2 else ain.shape[2] Format = QtGui.QImage.Format if nchans == 1: in_fmt = Format.Format_Grayscale8 elif nchans == 3: in_fmt = Format.Format_RGB888 else: in_fmt = Format.Format_RGBA8888 if useRGBA: out_fmt = Format.Format_RGBA8888 else: out_fmt = Format.Format_ARGB32 if in_fmt == out_fmt: aout[:] = ain return True npixels_chunk = 512*1024 batch = int(npixels_chunk / ncols / nchans) batch = max(1, batch) row_beg = 0 while row_beg < nrows: row_end = min(row_beg + batch, nrows) ain_view = ain[row_beg:row_end, ...] aout_view = aout[row_beg:row_end, ...] qimg = QtGui.QImage(ain_view, ncols, ain_view.shape[0], ain.strides[0], in_fmt) qimg = qimg.convertToFormat(out_fmt) aout_view[:] = imageToArray(qimg, copy=False, transpose=False) row_beg = row_end return True def ndarray_to_qimage(arr, fmt): """ Low level function to encapsulate QImage creation differences between bindings. "arr" is assumed to be C-contiguous. """ # C++ QImage has two kind of constructors # - QImage(const uchar*, ...) # - QImage(uchar*, ...) # If the const constructor is used, subsequently calling any non-const method # will trigger the COW mechanism, i.e. a copy is made under the hood. if QT_LIB.startswith('PyQt'): # PyQt5 -> non-const # PyQt6 >= 6.0.1 -> non-const img_ptr = int(Qt.sip.voidptr(arr)) # or arr.ctypes.data else: # bindings that support ndarray # PyQt5 -> const # PyQt6 >= 6.0.1 -> const # PySide2 -> non-const # PySide6 -> non-const img_ptr = arr h, w = arr.shape[:2] bytesPerLine = arr.strides[0] qimg = QtGui.QImage(img_ptr, w, h, bytesPerLine, fmt) qimg.data = arr return qimg def makeQImage(imgData, alpha=None, copy=True, transpose=True): """ Turn an ARGB array into QImage. By default, the data is copied; changes to the array will not be reflected in the image. The image will be given a 'data' attribute pointing to the array which shares its data to prevent python freeing that memory while the image is in use. ============== =================================================================== **Arguments:** imgData Array of data to convert. Must have shape (height, width), (height, width, 3), or (height, width, 4). If transpose is True, then the first two axes are swapped. The array dtype must be ubyte. For 2D arrays, the value is interpreted as greyscale. For 3D arrays, the order of values in the 3rd axis must be (b, g, r, a). alpha If the input array is 3D and *alpha* is True, the QImage returned will have format ARGB32. If False, the format will be RGB32. By default, _alpha_ is True if array.shape[2] == 4. copy If True, the data is copied before converting to QImage. If False, the new QImage points directly to the data in the array. Note that the array must be contiguous for this to work (see numpy.ascontiguousarray). transpose If True (the default), the array x/y axes are transposed before creating the image. Note that Qt expects the axes to be in (height, width) order whereas pyqtgraph usually prefers the opposite. ============== =================================================================== """ ## create QImage from buffer profile = debug.Profiler() copied = False if imgData.ndim == 2: imgFormat = QtGui.QImage.Format.Format_Grayscale8 elif imgData.ndim == 3: # If we didn't explicitly specify alpha, check the array shape. if alpha is None: alpha = (imgData.shape[2] == 4) if imgData.shape[2] == 3: # need to make alpha channel (even if alpha==False; QImage requires 32 bpp) if copy is True: d2 = np.empty(imgData.shape[:2] + (4,), dtype=imgData.dtype) d2[:,:,:3] = imgData d2[:,:,3] = 255 imgData = d2 copied = True else: raise Exception('Array has only 3 channels; cannot make QImage without copying.') profile("add alpha channel") if alpha: imgFormat = QtGui.QImage.Format.Format_ARGB32 else: imgFormat = QtGui.QImage.Format.Format_RGB32 else: raise TypeError("Image array must have ndim = 2 or 3.") if transpose: imgData = imgData.transpose((1, 0, 2)) # QImage expects row-major order if not imgData.flags['C_CONTIGUOUS']: if copy is False: extra = ' (try setting transpose=False)' if transpose else '' raise Exception('Array is not contiguous; cannot make QImage without copying.'+extra) imgData = np.ascontiguousarray(imgData) copied = True profile("ascontiguousarray") if copy is True and copied is False: imgData = imgData.copy() profile("copy") return ndarray_to_qimage(imgData, imgFormat) def ndarray_from_qimage(qimg): img_ptr = qimg.bits() 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 img_ptr.setsize(h * bpl) memory = np.frombuffer(img_ptr, dtype=np.ubyte).reshape((h, bpl)) memory = memory[:, :logical_bpl] if depth in (8, 24, 32): dtype = np.uint8 nchan = depth // 8 elif depth in (16, 64): dtype = np.uint16 nchan = depth // 16 else: raise ValueError("Unsupported Image Type") shape = h, w if nchan != 1: shape = shape + (nchan,) arr = memory.view(dtype).reshape(shape) return arr def imageToArray(img, copy=False, transpose=True): """ Convert a QImage into numpy array. The image must have format RGB32, ARGB32, or ARGB32_Premultiplied. By default, the image is not copied; changes made to the array will appear in the QImage as well (beware: if the QImage is collected before the array, there may be trouble). The array will have shape (width, height, (b,g,r,a)). """ arr = ndarray_from_qimage(img) fmt = img.format() if fmt == img.Format.Format_RGB32: arr[...,3] = 255 if copy: arr = arr.copy() if transpose: return arr.transpose((1,0,2)) else: return arr def colorToAlpha(data, color): """ Given an RGBA image in *data*, convert *color* to be transparent. *data* must be an array (w, h, 3 or 4) of ubyte values and *color* must be an array (3) of ubyte values. This is particularly useful for use with images that have a black or white background. Algorithm is taken from Gimp's color-to-alpha function in plug-ins/common/colortoalpha.c Credit: /* * Color To Alpha plug-in v1.0 by Seth Burgess, sjburges@gimp.org 1999/05/14 * with algorithm by clahey */ """ data = data.astype(float) if data.shape[-1] == 3: ## add alpha channel if needed d2 = np.empty(data.shape[:2]+(4,), dtype=data.dtype) d2[...,:3] = data d2[...,3] = 255 data = d2 color = color.astype(float) alpha = np.zeros(data.shape[:2]+(3,), dtype=float) output = data.copy() for i in [0,1,2]: d = data[...,i] c = color[i] mask = d > c alpha[...,i][mask] = (d[mask] - c) / (255. - c) imask = d < c alpha[...,i][imask] = (c - d[imask]) / c output[...,3] = alpha.max(axis=2) * 255. mask = output[...,3] >= 1.0 ## avoid zero division while processing alpha channel correction = 255. / output[...,3][mask] ## increase value to compensate for decreased alpha for i in [0,1,2]: output[...,i][mask] = ((output[...,i][mask]-color[i]) * correction) + color[i] output[...,3][mask] *= data[...,3][mask] / 255. ## combine computed and previous alpha values #raise Exception() return np.clip(output, 0, 255).astype(np.ubyte) def gaussianFilter(data, sigma): """ Drop-in replacement for scipy.ndimage.gaussian_filter. (note: results are only approximately equal to the output of gaussian_filter) """ cp = getCupy() xp = cp.get_array_module(data) if cp else np if xp.isscalar(sigma): sigma = (sigma,) * data.ndim baseline = data.mean() filtered = data - baseline for ax in range(data.ndim): s = sigma[ax] if s == 0: continue # generate 1D gaussian kernel ksize = int(s * 6) x = xp.arange(-ksize, ksize) kernel = xp.exp(-x**2 / (2*s**2)) kshape = [1,] * data.ndim kshape[ax] = len(kernel) kernel = kernel.reshape(kshape) # convolve as product of FFTs shape = data.shape[ax] + ksize scale = 1.0 / (abs(s) * (2*xp.pi)**0.5) filtered = scale * xp.fft.irfft(xp.fft.rfft(filtered, shape, axis=ax) * xp.fft.rfft(kernel, shape, axis=ax), axis=ax) # clip off extra data sl = [slice(None)] * data.ndim sl[ax] = slice(filtered.shape[ax]-data.shape[ax],None,None) filtered = filtered[tuple(sl)] return filtered + baseline def downsample(data, n, axis=0, xvals='subsample'): """Downsample by averaging points together across axis. If multiple axes are specified, runs once per axis. If a metaArray is given, then the axis values can be either subsampled or downsampled to match. """ ma = None if (hasattr(data, 'implements') and data.implements('MetaArray')): ma = data data = data.view(np.ndarray) if hasattr(axis, '__len__'): if not hasattr(n, '__len__'): n = [n]*len(axis) for i in range(len(axis)): data = downsample(data, n[i], axis[i]) return data if n <= 1: return data nPts = int(data.shape[axis] / n) s = list(data.shape) s[axis] = nPts s.insert(axis+1, n) sl = [slice(None)] * data.ndim sl[axis] = slice(0, nPts*n) d1 = data[tuple(sl)] #print d1.shape, s d1.shape = tuple(s) d2 = d1.mean(axis+1) if ma is None: return d2 else: info = ma.infoCopy() if 'values' in info[axis]: if xvals == 'subsample': info[axis]['values'] = info[axis]['values'][::n][:nPts] elif xvals == 'downsample': info[axis]['values'] = downsample(info[axis]['values'], n) 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() finite_idx = None if finiteCheck: isfinite = np.isfinite(x) & np.isfinite(y) if not isfinite.all(): finite_idx = isfinite.nonzero()[0] n = len(finite_idx) if n < 2: return QtGui.QPainterPath() 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 finite_idx is None: arr[:, 0] = x arr[:, 1] = y else: arr[:, 0] = x[finite_idx] arr[:, 1] = y[finite_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 finite_idx is None: subarr[:, 0] = x[sl] subarr[:, 1] = y[sl] else: fiv = finite_idx[sl] # view subarr[:, 0] = x[fiv] subarr[:, 1] = y[fiv] 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 possible. The *connect* argument may be 'all', indicating that each point should be connected to the next; 'pairs', indicating that each pair of points should be connected, or an array of int32 values (0 or 1) indicating connections. Parameters ---------- x : (N,) ndarray x-values to be plotted y : (N,) ndarray y-values to be plotted, must be same length as `x` connect : {'all', 'pairs', 'finite', (N,) ndarray}, optional Argument detailing how to connect the points in the path. `all` will have sequential points being connected. `pairs` generates lines between every other point. `finite` only connects points that are finite. If an ndarray is passed, containing int32 values of 0 or 1, 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 nonfinite values are present in `x` or `y`, an empty QPainterPath will be generated. Returns ------- QPainterPath QPainterPath object to be drawn Raises ------ ValueError Raised when the connect argument has an invalid value placed within. Notes ----- A QPainterPath is generated through one of two ways. When the connect parameter is 'all', a QPolygonF object is created, and ``QPainterPath.addPolygon()`` is called. For other connect parameters a ``QDataStream`` object is created and the QDataStream >> QPainterPath operator is used to pass the data. The memory format is as follows numVerts(i4) 0(i4) x(f8) y(f8) <-- 0 means this vertex does not connect 1(i4) x(f8) y(f8) <-- 1 means this vertex connects to the previous vertex ... cStart(i4) fillRule(i4) see: https://github.com/qt/qtbase/blob/dev/src/gui/painting/qpainterpath.cpp All values are big endian--pack using struct.pack('>d') or struct.pack('>i') This binary format may change in future versions of Qt """ 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' isfinite = None if connect == 'finite': if not finiteCheck: # 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: # delegate to connect=ndarray # finiteCheck=True, all_isfinite=False connect = 'array' connect_array = isfinite if connect == 'all': return _arrayToQPath_all(x, y, finiteCheck) 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) backfill_idx = None if finiteCheck: if isfinite is None: isfinite = np.isfinite(x) & np.isfinite(y) 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 == 'pairs': arr['c'][0::2] = 0 arr['c'][1::2] = 1 # connect every 2nd point to every 1st one 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. 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') path = QtGui.QPainterPath() if hasattr(path, 'reserve'): # Qt 5.13 path.reserve(n) ds = QtCore.QDataStream(backstore) ds >> path return path def ndarray_from_qpolygonf(polyline): nbytes = 2 * len(polyline) * 8 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): polyline = QtGui.QPolygonF() if QT_LIB.startswith('PyQt'): polyline.fill(QtCore.QPointF(), size) else: polyline.resize(size) return polyline def arrayToQPolygonF(x, y): """ Utility function to convert two 1D-NumPy arrays representing curve data (X-axis, Y-axis data) into a single open polygon (QtGui.PolygonF) object. Thanks to PythonQwt for making this code available License/copyright: MIT License Β© Pierre Raybaut 2020. Parameters ---------- x : np.array x-axis coordinates for data to be plotted, must have have ndim of 1 y : np.array y-axis coordinates for data to be plotted, must have ndim of 1 and be the same length as x Returns ------- QPolygonF Open QPolygonF object that represents the path looking to be plotted Raises ------ ValueError When xdata or ydata does not meet the required criteria """ if not ( x.size == y.size == x.shape[0] == y.shape[0] ): raise ValueError("Arguments must be 1D and the same size") size = x.size polyline = create_qpolygonf(size) memory = ndarray_from_qpolygonf(polyline) memory[:, 0] = x memory[:, 1] = y return polyline #def isosurface(data, level): #""" #Generate isosurface from volumetric data using marching tetrahedra algorithm. #See Paul Bourke, "Polygonising a Scalar Field Using Tetrahedrons" (http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/) #*data* 3D numpy array of scalar values #*level* The level at which to generate an isosurface #""" #facets = [] ### mark everything below the isosurface level #mask = data < level #### make eight sub-fields #fields = np.empty((2,2,2), dtype=object) #slices = [slice(0,-1), slice(1,None)] #for i in [0,1]: #for j in [0,1]: #for k in [0,1]: #fields[i,j,k] = mask[slices[i], slices[j], slices[k]] ### split each cell into 6 tetrahedra ### these all have the same 'orienation'; points 1,2,3 circle ### clockwise around point 0 #tetrahedra = [ #[(0,1,0), (1,1,1), (0,1,1), (1,0,1)], #[(0,1,0), (0,1,1), (0,0,1), (1,0,1)], #[(0,1,0), (0,0,1), (0,0,0), (1,0,1)], #[(0,1,0), (0,0,0), (1,0,0), (1,0,1)], #[(0,1,0), (1,0,0), (1,1,0), (1,0,1)], #[(0,1,0), (1,1,0), (1,1,1), (1,0,1)] #] ### each tetrahedron will be assigned an index ### which determines how to generate its facets. ### this structure is: ### facets[index][facet1, facet2, ...] ### where each facet is triangular and its points are each ### interpolated between two points on the tetrahedron ### facet = [(p1a, p1b), (p2a, p2b), (p3a, p3b)] ### facet points always circle clockwise if you are looking ### at them from below the isosurface. #indexFacets = [ #[], ## all above #[[(0,1), (0,2), (0,3)]], # 0 below #[[(1,0), (1,3), (1,2)]], # 1 below #[[(0,2), (1,3), (1,2)], [(0,2), (0,3), (1,3)]], # 0,1 below #[[(2,0), (2,1), (2,3)]], # 2 below #[[(0,3), (1,2), (2,3)], [(0,3), (0,1), (1,2)]], # 0,2 below #[[(1,0), (2,3), (2,0)], [(1,0), (1,3), (2,3)]], # 1,2 below #[[(3,0), (3,1), (3,2)]], # 3 above #[[(3,0), (3,2), (3,1)]], # 3 below #[[(1,0), (2,0), (2,3)], [(1,0), (2,3), (1,3)]], # 0,3 below #[[(0,3), (2,3), (1,2)], [(0,3), (1,2), (0,1)]], # 1,3 below #[[(2,0), (2,3), (2,1)]], # 0,1,3 below #[[(0,2), (1,2), (1,3)], [(0,2), (1,3), (0,3)]], # 2,3 below #[[(1,0), (1,2), (1,3)]], # 0,2,3 below #[[(0,1), (0,3), (0,2)]], # 1,2,3 below #[] ## all below #] #for tet in tetrahedra: ### get the 4 fields for this tetrahedron #tetFields = [fields[c] for c in tet] ### generate an index for each grid cell #index = tetFields[0] + tetFields[1]*2 + tetFields[2]*4 + tetFields[3]*8 ### add facets #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 #p1 = tet[f[l][0]] # tet corner 1 #p2 = tet[f[l][1]] # tet corner 2 #pts.append([(p1[x]+p2[x])*0.5+[i,j,k][x]+0.5 for x in [0,1,2]]) ## interpolate between tet corners #facets.append(pts) #return facets def isocurve(data, level, connected=False, extendToEdge=False, path=False): """ Generate isocurve from 2D data using marching squares algorithm. ============== ========================================================= **Arguments:** data 2D numpy array of scalar values level The level at which to generate an isosurface connected If False, return a single long list of point pairs If True, return multiple long lists of connected point locations. (This is slower but better for drawing continuous lines) extendToEdge If True, extend the curves to reach the exact edges of the data. path if True, return a QPainterPath rather than a list of vertex coordinates. This forces connected=True. ============== ========================================================= This function is SLOW; plenty of room for optimization here. """ if path is True: connected = True if extendToEdge: d2 = np.empty((data.shape[0]+2, data.shape[1]+2), dtype=data.dtype) d2[1:-1, 1:-1] = data d2[0, 1:-1] = data[0] d2[-1, 1:-1] = data[-1] d2[1:-1, 0] = data[:, 0] d2[1:-1, -1] = data[:, -1] d2[0,0] = d2[0,1] d2[0,-1] = d2[1,-1] d2[-1,0] = d2[-1,1] d2[-1,-1] = d2[-1,-2] data = d2 sideTable = [ [], [0,1], [1,2], [0,2], [0,3], [1,3], [0,1,2,3], [2,3], [2,3], [0,1,2,3], [1,3], [0,3], [0,2], [1,2], [0,1], [] ] edgeKey=[ [(0,1), (0,0)], [(0,0), (1,0)], [(1,0), (1,1)], [(1,1), (0,1)] ] lines = [] ## mark everything below the isosurface level mask = data < level ### make four sub-fields and compute indexes for grid cells index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) fields = np.empty((2,2), dtype=object) slices = [slice(0,-1), slice(1,None)] for i in [0,1]: for j in [0,1]: fields[i,j] = mask[slices[i], slices[j]] #vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme vertIndex = i+2*j #print i,j,k," : ", fields[i,j,k], 2**vertIndex np.add(index, fields[i,j] * 2**vertIndex, out=index, casting='unsafe') #print index #print index ## add lines for i in range(index.shape[0]): # data x-axis for j in range(index.shape[1]): # data y-axis sides = sideTable[index[i,j]] for l in range(0, len(sides), 2): ## faces for this grid cell edges = sides[l:l+2] pts = [] for m in [0,1]: # points in this face p1 = edgeKey[edges[m]][0] # p1, p2 are points at either side of an edge p2 = edgeKey[edges[m]][1] v1 = data[i+p1[0], j+p1[1]] # v1 and v2 are the values at p1 and p2 v2 = data[i+p2[0], j+p2[1]] f = (level-v1) / (v2-v1) fi = 1.0 - f p = ( ## interpolate between corners p1[0]*fi + p2[0]*f + i + 0.5, p1[1]*fi + p2[1]*f + j + 0.5 ) if extendToEdge: ## check bounds p = ( min(data.shape[0]-2, max(0, p[0]-1)), min(data.shape[1]-2, max(0, p[1]-1)), ) if connected: gridKey = i + (1 if edges[m]==2 else 0), j + (1 if edges[m]==3 else 0), edges[m]%2 pts.append((p, gridKey)) ## give the actual position and a key identifying the grid location (for connecting segments) else: pts.append(p) lines.append(pts) if not connected: return lines ## turn disjoint list of segments into continuous lines #lines = [[2,5], [5,4], [3,4], [1,3], [6,7], [7,8], [8,6], [11,12], [12,15], [11,13], [13,14]] #lines = [[(float(a), a), (float(b), b)] for a,b in lines] points = {} ## maps each point to its connections for a,b in lines: if a[1] not in points: points[a[1]] = [] points[a[1]].append([a,b]) if b[1] not in points: points[b[1]] = [] points[b[1]].append([b,a]) ## rearrange into chains for k in list(points.keys()): try: chains = points[k] except KeyError: ## already used this point elsewhere continue #print "===========", k for chain in chains: #print " chain:", chain x = None while True: if x == chain[-1][1]: break ## nothing left to do on this chain x = chain[-1][1] if x == k: break ## chain has looped; we're done and can ignore the opposite chain y = chain[-2][1] connects = points[x] for conn in connects[:]: if conn[1][1] != y: #print " ext:", conn chain.extend(conn[1:]) #print " del:", x del points[x] if chain[0][1] == chain[-1][1]: # looped chain; no need to continue the other direction chains.pop() break ## extract point locations lines = [] for chain in points.values(): if len(chain) == 2: chain = chain[1][1:][::-1] + chain[0] # join together ends of chain else: chain = chain[0] lines.append([p[0] for p in chain]) if not path: return lines ## a list of pairs of points path = QtGui.QPainterPath() for line in lines: path.moveTo(*line[0]) for p in line[1:]: path.lineTo(*p) return path def traceImage(image, values, smooth=0.5): """ Convert an image to a set of QPainterPath curves. One curve will be generated for each item in *values*; each curve outlines the area of the image that is closer to its value than to any others. If image is RGB or RGBA, then the shape of values should be (nvals, 3/4) The parameter *smooth* is expressed in pixels. """ if values.ndim == 2: values = values.T values = values[np.newaxis, np.newaxis, ...].astype(float) image = image[..., np.newaxis].astype(float) diff = np.abs(image-values) if values.ndim == 4: diff = diff.sum(axis=2) labels = np.argmin(diff, axis=2) paths = [] for i in range(diff.shape[-1]): d = (labels==i).astype(float) d = gaussianFilter(d, (smooth, smooth)) lines = isocurve(d, 0.5, connected=True, extendToEdge=True) path = QtGui.QPainterPath() for line in lines: path.moveTo(*line[0]) for p in line[1:]: path.lineTo(*p) paths.append(path) return paths IsosurfaceDataCache = None def isosurface(data, level): """ Generate isosurface from volumetric data using marching cubes algorithm. See Paul Bourke, "Polygonising a Scalar Field" (http://paulbourke.net/geometry/polygonise/) *data* 3D numpy array of scalar values. Must be contiguous. *level* The level at which to generate an isosurface Returns an array of vertex coordinates (Nv, 3) and an array of per-face vertex indexes (Nf, 3) """ ## For improvement, see: ## ## Efficient implementation of Marching Cubes' cases with topological guarantees. ## Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan Tavares. ## Journal of Graphics Tools 8(2): pp. 1-15 (december 2003) ## Precompute lookup tables on the first run global IsosurfaceDataCache if IsosurfaceDataCache is None: ## map from grid cell index to edge index. ## grid cell index tells us which corners are below the isosurface, ## edge index tells us which edges are cut by the isosurface. ## (Data stolen from Bourk; see above.) edgeTable = np.array([ 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ], dtype=np.uint16) ## Table of triangles to use for filling each grid cell. ## Each set of three integers tells us which three edges to ## draw a triangle between. ## (Data stolen from Bourk; see above.) triTable = [ [], [0, 8, 3], [0, 1, 9], [1, 8, 3, 9, 8, 1], [1, 2, 10], [0, 8, 3, 1, 2, 10], [9, 2, 10, 0, 2, 9], [2, 8, 3, 2, 10, 8, 10, 9, 8], [3, 11, 2], [0, 11, 2, 8, 11, 0], [1, 9, 0, 2, 3, 11], [1, 11, 2, 1, 9, 11, 9, 8, 11], [3, 10, 1, 11, 10, 3], [0, 10, 1, 0, 8, 10, 8, 11, 10], [3, 9, 0, 3, 11, 9, 11, 10, 9], [9, 8, 10, 10, 8, 11], [4, 7, 8], [4, 3, 0, 7, 3, 4], [0, 1, 9, 8, 4, 7], [4, 1, 9, 4, 7, 1, 7, 3, 1], [1, 2, 10, 8, 4, 7], [3, 4, 7, 3, 0, 4, 1, 2, 10], [9, 2, 10, 9, 0, 2, 8, 4, 7], [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4], [8, 4, 7, 3, 11, 2], [11, 4, 7, 11, 2, 4, 2, 0, 4], [9, 0, 1, 8, 4, 7, 2, 3, 11], [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1], [3, 10, 1, 3, 11, 10, 7, 8, 4], [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4], [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3], [4, 7, 11, 4, 11, 9, 9, 11, 10], [9, 5, 4], [9, 5, 4, 0, 8, 3], [0, 5, 4, 1, 5, 0], [8, 5, 4, 8, 3, 5, 3, 1, 5], [1, 2, 10, 9, 5, 4], [3, 0, 8, 1, 2, 10, 4, 9, 5], [5, 2, 10, 5, 4, 2, 4, 0, 2], [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8], [9, 5, 4, 2, 3, 11], [0, 11, 2, 0, 8, 11, 4, 9, 5], [0, 5, 4, 0, 1, 5, 2, 3, 11], [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5], [10, 3, 11, 10, 1, 3, 9, 5, 4], [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10], [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3], [5, 4, 8, 5, 8, 10, 10, 8, 11], [9, 7, 8, 5, 7, 9], [9, 3, 0, 9, 5, 3, 5, 7, 3], [0, 7, 8, 0, 1, 7, 1, 5, 7], [1, 5, 3, 3, 5, 7], [9, 7, 8, 9, 5, 7, 10, 1, 2], [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3], [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2], [2, 10, 5, 2, 5, 3, 3, 5, 7], [7, 9, 5, 7, 8, 9, 3, 11, 2], [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11], [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7], [11, 2, 1, 11, 1, 7, 7, 1, 5], [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11], [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0], [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0], [11, 10, 5, 7, 11, 5], [10, 6, 5], [0, 8, 3, 5, 10, 6], [9, 0, 1, 5, 10, 6], [1, 8, 3, 1, 9, 8, 5, 10, 6], [1, 6, 5, 2, 6, 1], [1, 6, 5, 1, 2, 6, 3, 0, 8], [9, 6, 5, 9, 0, 6, 0, 2, 6], [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8], [2, 3, 11, 10, 6, 5], [11, 0, 8, 11, 2, 0, 10, 6, 5], [0, 1, 9, 2, 3, 11, 5, 10, 6], [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11], [6, 3, 11, 6, 5, 3, 5, 1, 3], [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6], [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9], [6, 5, 9, 6, 9, 11, 11, 9, 8], [5, 10, 6, 4, 7, 8], [4, 3, 0, 4, 7, 3, 6, 5, 10], [1, 9, 0, 5, 10, 6, 8, 4, 7], [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4], [6, 1, 2, 6, 5, 1, 4, 7, 8], [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7], [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6], [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9], [3, 11, 2, 7, 8, 4, 10, 6, 5], [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11], [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6], [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6], [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6], [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11], [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7], [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9], [10, 4, 9, 6, 4, 10], [4, 10, 6, 4, 9, 10, 0, 8, 3], [10, 0, 1, 10, 6, 0, 6, 4, 0], [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10], [1, 4, 9, 1, 2, 4, 2, 6, 4], [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4], [0, 2, 4, 4, 2, 6], [8, 3, 2, 8, 2, 4, 4, 2, 6], [10, 4, 9, 10, 6, 4, 11, 2, 3], [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6], [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10], [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1], [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3], [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1], [3, 11, 6, 3, 6, 0, 0, 6, 4], [6, 4, 8, 11, 6, 8], [7, 10, 6, 7, 8, 10, 8, 9, 10], [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10], [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0], [10, 6, 7, 10, 7, 1, 1, 7, 3], [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7], [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9], [7, 8, 0, 7, 0, 6, 6, 0, 2], [7, 3, 2, 6, 7, 2], [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7], [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7], [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11], [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1], [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6], [0, 9, 1, 11, 6, 7], [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0], [7, 11, 6], [7, 6, 11], [3, 0, 8, 11, 7, 6], [0, 1, 9, 11, 7, 6], [8, 1, 9, 8, 3, 1, 11, 7, 6], [10, 1, 2, 6, 11, 7], [1, 2, 10, 3, 0, 8, 6, 11, 7], [2, 9, 0, 2, 10, 9, 6, 11, 7], [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8], [7, 2, 3, 6, 2, 7], [7, 0, 8, 7, 6, 0, 6, 2, 0], [2, 7, 6, 2, 3, 7, 0, 1, 9], [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6], [10, 7, 6, 10, 1, 7, 1, 3, 7], [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8], [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7], [7, 6, 10, 7, 10, 8, 8, 10, 9], [6, 8, 4, 11, 8, 6], [3, 6, 11, 3, 0, 6, 0, 4, 6], [8, 6, 11, 8, 4, 6, 9, 0, 1], [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6], [6, 8, 4, 6, 11, 8, 2, 10, 1], [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6], [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9], [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3], [8, 2, 3, 8, 4, 2, 4, 6, 2], [0, 4, 2, 4, 6, 2], [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8], [1, 9, 4, 1, 4, 2, 2, 4, 6], [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1], [10, 1, 0, 10, 0, 6, 6, 0, 4], [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3], [10, 9, 4, 6, 10, 4], [4, 9, 5, 7, 6, 11], [0, 8, 3, 4, 9, 5, 11, 7, 6], [5, 0, 1, 5, 4, 0, 7, 6, 11], [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5], [9, 5, 4, 10, 1, 2, 7, 6, 11], [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5], [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2], [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6], [7, 2, 3, 7, 6, 2, 5, 4, 9], [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7], [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0], [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8], [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7], [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4], [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10], [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10], [6, 9, 5, 6, 11, 9, 11, 8, 9], [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5], [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11], [6, 11, 3, 6, 3, 5, 5, 3, 1], [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6], [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10], [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5], [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3], [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2], [9, 5, 6, 9, 6, 0, 0, 6, 2], [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8], [1, 5, 6, 2, 1, 6], [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6], [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0], [0, 3, 8, 5, 6, 10], [10, 5, 6], [11, 5, 10, 7, 5, 11], [11, 5, 10, 11, 7, 5, 8, 3, 0], [5, 11, 7, 5, 10, 11, 1, 9, 0], [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1], [11, 1, 2, 11, 7, 1, 7, 5, 1], [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11], [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7], [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2], [2, 5, 10, 2, 3, 5, 3, 7, 5], [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5], [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2], [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2], [1, 3, 5, 3, 7, 5], [0, 8, 7, 0, 7, 1, 1, 7, 5], [9, 0, 3, 9, 3, 5, 5, 3, 7], [9, 8, 7, 5, 9, 7], [5, 8, 4, 5, 10, 8, 10, 11, 8], [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0], [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5], [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4], [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8], [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11], [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5], [9, 4, 5, 2, 11, 3], [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4], [5, 10, 2, 5, 2, 4, 4, 2, 0], [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9], [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2], [8, 4, 5, 8, 5, 3, 3, 5, 1], [0, 4, 5, 1, 0, 5], [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5], [9, 4, 5], [4, 11, 7, 4, 9, 11, 9, 10, 11], [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11], [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11], [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4], [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2], [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3], [11, 7, 4, 11, 4, 2, 2, 4, 0], [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4], [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9], [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7], [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10], [1, 10, 2, 8, 7, 4], [4, 9, 1, 4, 1, 7, 7, 1, 3], [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1], [4, 0, 3, 7, 4, 3], [4, 8, 7], [9, 10, 8, 10, 11, 8], [3, 0, 9, 3, 9, 11, 11, 9, 10], [0, 1, 10, 0, 10, 8, 8, 10, 11], [3, 1, 10, 11, 3, 10], [1, 2, 11, 1, 11, 9, 9, 11, 8], [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9], [0, 2, 11, 8, 0, 11], [3, 2, 11], [2, 3, 8, 2, 8, 10, 10, 8, 9], [9, 10, 2, 0, 9, 2], [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8], [1, 10, 2], [1, 3, 8, 9, 1, 8], [0, 9, 1], [0, 3, 8], [] ] edgeShifts = np.array([ ## maps edge ID (0-11) to (x,y,z) cell offset and edge ID (0-2) [0, 0, 0, 0], [1, 0, 0, 1], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [1, 0, 1, 1], [0, 1, 1, 0], [0, 0, 1, 1], [0, 0, 0, 2], [1, 0, 0, 2], [1, 1, 0, 2], [0, 1, 0, 2], #[9, 9, 9, 9] ## fake ], dtype=np.uint16) # don't use ubyte here! This value gets added to cell index later; will need the extra precision. nTableFaces = np.array([len(f)/3 for f in triTable], dtype=np.ubyte) faceShiftTables = [None] for i in range(1,6): ## compute lookup table of index: vertexes mapping faceTableI = np.zeros((len(triTable), i*3), dtype=np.ubyte) faceTableInds = np.argwhere(nTableFaces == i) faceTableI[faceTableInds[:,0]] = np.array([triTable[j[0]] for j in faceTableInds]) faceTableI = faceTableI.reshape((len(triTable), i, 3)) faceShiftTables.append(edgeShifts[faceTableI]) ## Let's try something different: #faceTable = np.empty((256, 5, 3, 4), dtype=np.ubyte) # (grid cell index, faces, vertexes, edge lookup) #for i,f in enumerate(triTable): #f = np.array(f + [12] * (15-len(f))).reshape(5,3) #faceTable[i] = edgeShifts[f] IsosurfaceDataCache = (faceShiftTables, edgeShifts, edgeTable, nTableFaces) else: faceShiftTables, edgeShifts, edgeTable, nTableFaces = IsosurfaceDataCache # We use strides below, which means we need contiguous array input. # Ideally we can fix this just by removing the dependency on strides. if not data.flags['C_CONTIGUOUS']: raise TypeError("isosurface input data must be c-contiguous.") ## mark everything below the isosurface level mask = data < level ### make eight sub-fields and compute indexes for grid cells index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) fields = np.empty((2,2,2), dtype=object) slices = [slice(0,-1), slice(1,None)] for i in [0,1]: for j in [0,1]: for k in [0,1]: fields[i,j,k] = mask[slices[i], slices[j], slices[k]] vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme np.add(index, fields[i,j,k] * 2**vertIndex, out=index, casting='unsafe') ### Generate table of edges that have been cut cutEdges = np.zeros([x+1 for x in index.shape]+[3], dtype=np.uint32) edges = edgeTable[index] for i, shift in enumerate(edgeShifts[:12]): slices = [slice(shift[j],cutEdges.shape[j]+(shift[j]-1)) for j in range(3)] cutEdges[slices[0], slices[1], slices[2], shift[3]] += edges & 2**i ## for each cut edge, interpolate to see where exactly the edge is cut and generate vertex positions m = cutEdges > 0 vertexInds = np.argwhere(m) ## argwhere is slow! vertexes = vertexInds[:,:3].astype(np.float32) dataFlat = data.reshape(data.shape[0]*data.shape[1]*data.shape[2]) ## re-use the cutEdges array as a lookup table for vertex IDs cutEdges[vertexInds[:,0], vertexInds[:,1], vertexInds[:,2], vertexInds[:,3]] = np.arange(vertexInds.shape[0]) for i in [0,1,2]: vim = vertexInds[:,3] == i vi = vertexInds[vim, :3] viFlat = (vi * (np.array(data.strides[:3]) // data.itemsize)[np.newaxis,:]).sum(axis=1) v1 = dataFlat[viFlat] v2 = dataFlat[viFlat + data.strides[i]//data.itemsize] vertexes[vim,i] += (level-v1) / (v2-v1) ### compute the set of vertex indexes for each face. ## This works, but runs a bit slower. #cells = np.argwhere((index != 0) & (index != 255)) ## all cells with at least one face #cellInds = index[cells[:,0], cells[:,1], cells[:,2]] #verts = faceTable[cellInds] #mask = verts[...,0,0] != 9 #verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges #verts = verts[mask] #faces = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want. ## To allow this to be vectorized efficiently, we count the number of faces in each ## grid cell and handle each group of cells with the same number together. ## determine how many faces to assign to each grid cell nFaces = nTableFaces[index] totFaces = nFaces.sum() faces = np.empty((totFaces, 3), dtype=np.uint32) ptr = 0 #import debug #p = debug.Profiler() ## this helps speed up an indexing operation later on cs = np.array(cutEdges.strides)//cutEdges.itemsize cutEdges = cutEdges.flatten() ## this, strangely, does not seem to help. #ins = np.array(index.strides)/index.itemsize #index = index.flatten() for i in range(1,6): ### expensive: #profiler() cells = np.argwhere(nFaces == i) ## all cells which require i faces (argwhere is expensive) #profiler() if cells.shape[0] == 0: continue cellInds = index[cells[:,0], cells[:,1], cells[:,2]] ## index values of cells to process for this round #profiler() ### expensive: verts = faceShiftTables[i][cellInds] #profiler() np.add(verts[...,:3], cells[:,np.newaxis,np.newaxis,:], out=verts[...,:3], casting='unsafe') ## we now have indexes into cutEdges verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:]) #profiler() ### expensive: verts = (verts * cs[np.newaxis, np.newaxis, :]).sum(axis=2) vertInds = cutEdges[verts] #profiler() nv = vertInds.shape[0] #profiler() faces[ptr:ptr+nv] = vertInds #.reshape((nv, 3)) #profiler() ptr += nv return vertexes, faces def _pinv_fallback(tr): arr = np.array([tr.m11(), tr.m12(), tr.m13(), tr.m21(), tr.m22(), tr.m23(), tr.m31(), tr.m32(), tr.m33()]) arr.shape = (3, 3) pinv = np.linalg.pinv(arr) return QtGui.QTransform(*pinv.ravel().tolist()) def invertQTransform(tr): """Return a QTransform that is the inverse of *tr*. A pseudo-inverse is returned if tr is not invertible. Note that this function is preferred over QTransform.inverted() due to bugs in that method. (specifically, Qt has floating-point precision issues when determining whether a matrix is invertible) """ try: det = tr.determinant() detr = 1.0 / det # let singular matrices raise ZeroDivisionError inv = tr.adjoint() inv *= detr return inv except ZeroDivisionError: return _pinv_fallback(tr) def pseudoScatter(data, spacing=None, shuffle=True, bidir=False, method='exact'): """Return an array of position values needed to make beeswarm or column scatter plots. Used for examining the distribution of values in an array. Given an array of x-values, construct an array of y-values such that an x,y scatter-plot will not have overlapping points (it will look similar to a histogram). """ if method == 'exact': return _pseudoScatterExact(data, spacing=spacing, shuffle=shuffle, bidir=bidir) elif method == 'histogram': return _pseudoScatterHistogram(data, spacing=spacing, shuffle=shuffle, bidir=bidir) def _pseudoScatterHistogram(data, spacing=None, shuffle=True, bidir=False): """Works by binning points into a histogram and spreading them out to fill the bin. Faster method, but can produce blocky results. """ inds = np.arange(len(data)) if shuffle: np.random.shuffle(inds) data = data[inds] if spacing is None: spacing = 2.*np.std(data)/len(data)**0.5 yvals = np.empty(len(data)) dmin = data.min() dmax = data.max() nbins = int((dmax-dmin) / spacing) + 1 bins = np.linspace(dmin, dmax, nbins) dx = bins[1] - bins[0] dbins = ((data - bins[0]) / dx).astype(int) binCounts = {} for i,j in enumerate(dbins): c = binCounts.get(j, -1) + 1 binCounts[j] = c yvals[i] = c if bidir is True: for i in range(nbins): yvals[dbins==i] -= binCounts.get(i, 0) * 0.5 return yvals[np.argsort(inds)] ## un-shuffle values before returning def _pseudoScatterExact(data, spacing=None, shuffle=True, bidir=False): """Works by stacking points up one at a time, searching for the lowest position available at each point. This method produces nice, smooth results but can be prohibitively slow for large datasets. """ inds = np.arange(len(data)) if shuffle: np.random.shuffle(inds) data = data[inds] if spacing is None: spacing = 2.*np.std(data)/len(data)**0.5 s2 = spacing**2 yvals = np.empty(len(data)) if len(data) == 0: return yvals yvals[0] = 0 for i in range(1,len(data)): x = data[i] # current x value to be placed x0 = data[:i] # all x values already placed y0 = yvals[:i] # all y values already placed y = 0 dx = (x0-x)**2 # x-distance to each previous point xmask = dx < s2 # exclude anything too far away if xmask.sum() > 0: if bidir: dirs = [-1, 1] else: dirs = [1] yopts = [] for direction in dirs: y = 0 dx2 = dx[xmask] dy = (s2 - dx2)**0.5 limits = np.empty((2,len(dy))) # ranges of y-values to exclude limits[0] = y0[xmask] - dy limits[1] = y0[xmask] + dy while True: # ignore anything below this y-value if direction > 0: mask = limits[1] >= y else: mask = limits[0] <= y limits2 = limits[:,mask] # are we inside an excluded region? mask = (limits2[0] < y) & (limits2[1] > y) if mask.sum() == 0: break if direction > 0: y = limits2[:,mask].max() else: y = limits2[:,mask].min() yopts.append(y) if bidir: y = yopts[0] if -yopts[0] < yopts[1] else yopts[1] else: y = yopts[0] yvals[i] = y return yvals[np.argsort(inds)] ## un-shuffle values before returning def toposort(deps, nodes=None, seen=None, stack=None, depth=0): """Topological sort. Arguments are: deps dictionary describing dependencies where a:[b,c] means "a depends on b and c" nodes optional, specifies list of starting nodes (these should be the nodes which are not depended on by any other nodes). Other candidate starting nodes will be ignored. Example:: # Sort the following graph: # # B ──┬─────> C <── D # β”‚ β”‚ # E <─┴─> A <β”€β”˜ # deps = {'a': ['b', 'c'], 'c': ['b', 'd'], 'e': ['b']} toposort(deps) => ['b', 'd', 'c', 'a', 'e'] """ # fill in empty dep lists deps = deps.copy() for k,v in list(deps.items()): for k in v: if k not in deps: deps[k] = [] if nodes is None: ## run through deps to find nodes that are not depended upon rem = set() for dep in deps.values(): rem |= set(dep) nodes = set(deps.keys()) - rem if seen is None: seen = set() stack = [] sorted = [] for n in nodes: if n in stack: raise Exception("Cyclic dependency detected", stack + [n]) if n in seen: continue seen.add(n) sorted.extend( toposort(deps, deps[n], seen, stack+[n], depth=depth+1)) sorted.append(n) return sorted def disconnect(signal, slot): """Disconnect a Qt signal from a 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 """ while True: try: signal.disconnect(slot) return True except (TypeError, RuntimeError): slot = reload.getPreviousVersion(slot) if slot is None: return False class SignalBlock(object): """Class used to temporarily block a Qt signal connection:: with SignalBlock(signal, slot): # do something that emits a signal; it will # not be delivered to slot """ def __init__(self, signal, slot): self.signal = signal self.slot = slot def __enter__(self): self.reconnect = disconnect(self.signal, self.slot) return self def __exit__(self, *args): if self.reconnect: self.signal.connect(self.slot) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/functions_numba.py000066400000000000000000000027021421045507400234200ustar00rootroot00000000000000import numba import numpy as np rescale_functions = {} def rescale_clip_source(xx, scale, offset, vmin, vmax, yy): for i in range(xx.size): val = (xx[i] - offset) * scale yy[i] = min(max(val, vmin), vmax) def rescaleData(data, scale, offset, dtype, clip): data_out = np.empty_like(data, dtype=dtype) key = (data.dtype.name, data_out.dtype.name) func = rescale_functions.get(key) if func is None: func = numba.guvectorize( [f'{key[0]}[:],f8,f8,f8,f8,{key[1]}[:]'], '(n),(),(),(),()->(n)', nopython=True)(rescale_clip_source) rescale_functions[key] = func func(data, scale, offset, clip[0], clip[1], out=data_out) return data_out @numba.jit(nopython=True) def _rescale_and_lookup1d_function(data, scale, offset, lut, out): vmin, vmax = 0, lut.shape[0] - 1 for r in range(data.shape[0]): for c in range(data.shape[1]): val = (data[r, c] - offset) * scale val = min(max(val, vmin), vmax) out[r, c] = lut[int(val)] def rescale_and_lookup1d(data, scale, offset, lut): # data should be floating point and 2d # lut is 1d data_out = np.empty_like(data, dtype=lut.dtype) _rescale_and_lookup1d_function(data, float(scale), float(offset), lut, data_out) return data_out @numba.jit(nopython=True) def numba_take(lut, data): # numba supports only the 1st two arguments of np.take return np.take(lut, data) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/000077500000000000000000000000001421045507400224555ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ArrowItem.py000066400000000000000000000124441421045507400247450ustar00rootroot00000000000000from math import hypot from .. import functions as fn from ..Qt import QtGui, QtWidgets __all__ = ['ArrowItem'] class ArrowItem(QtWidgets.QGraphicsPathItem): """ For displaying scale-invariant arrows. For arrows pointing to a location on a curve, see CurveArrow """ def __init__(self, parent=None, **opts): """ Arrows can be initialized with any keyword arguments accepted by the setStyle() method. """ self.opts = {} QtWidgets.QGraphicsPathItem.__init__(self, parent) if 'size' in opts: opts['headLen'] = opts['size'] if 'width' in opts: opts['headWidth'] = opts['width'] pos = opts.pop('pos', (0, 0)) defaultOpts = { 'pxMode': True, 'angle': -150, ## If the angle is 0, the arrow points left 'headLen': 20, 'headWidth': None, 'tipAngle': 25, 'baseAngle': 0, 'tailLen': None, 'tailWidth': 3, 'pen': (200,200,200), 'brush': (50,50,200), } defaultOpts.update(opts) self.setStyle(**defaultOpts) # for backward compatibility self.setPos(*pos) def setStyle(self, **opts): """ Changes the appearance of the arrow. All arguments are optional: ====================== ================================================= **Keyword Arguments:** angle Orientation of the arrow in degrees. Default is 0; arrow pointing to the left. headLen Length of the arrow head, from tip to base. default=20 headWidth Width of the arrow head at its base. If headWidth is specified, it overrides tipAngle. tipAngle Angle of the tip of the arrow in degrees. Smaller values make a 'sharper' arrow. default=25 baseAngle Angle of the base of the arrow head. Default is 0, which means that the base of the arrow head is perpendicular to the arrow tail. tailLen Length of the arrow tail, measured from the base of the arrow head to the end of the tail. If this value is None, no tail will be drawn. default=None tailWidth Width of the tail. default=3 pen The pen used to draw the outline of the arrow. brush The brush used to fill the arrow. pxMode If True, then the arrow is drawn as a fixed size regardless of the scale of its parents (including the ViewBox zoom level). ====================== ================================================= """ arrowOpts = ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth', 'headWidth'] allowedOpts = ['angle', 'pen', 'brush', 'pxMode'] + arrowOpts needUpdate = False for k,v in opts.items(): if k not in allowedOpts: raise KeyError('Invalid arrow style option "%s"' % k) if self.opts.get(k) != v: needUpdate = True self.opts[k] = v if not needUpdate: return opt = dict([(k,self.opts[k]) for k in arrowOpts if k in self.opts]) tr = QtGui.QTransform() tr.rotate(self.opts['angle']) self.path = tr.map(fn.makeArrowPath(**opt)) self.setPath(self.path) self.setPen(fn.mkPen(self.opts['pen'])) self.setBrush(fn.mkBrush(self.opts['brush'])) if self.opts['pxMode']: self.setFlags(self.flags() | self.GraphicsItemFlag.ItemIgnoresTransformations) else: self.setFlags(self.flags() & ~self.GraphicsItemFlag.ItemIgnoresTransformations) def paint(self, p, *args): p.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) super().paint(p, *args) #p.setPen(fn.mkPen('r')) #p.setBrush(fn.mkBrush(None)) #p.drawRect(self.boundingRect()) def shape(self): #if not self.opts['pxMode']: #return QtWidgets.QGraphicsPathItem.shape(self) return self.path ## dataBounds and pixelPadding methods are provided to ensure ViewBox can ## properly auto-range def dataBounds(self, ax, frac, orthoRange=None): pw = 0 pen = self.pen() if not pen.isCosmetic(): pw = pen.width() * 0.7072 if self.opts['pxMode']: return [0,0] else: br = self.boundingRect() if ax == 0: return [br.left()-pw, br.right()+pw] else: return [br.top()-pw, br.bottom()+pw] def pixelPadding(self): pad = 0 if self.opts['pxMode']: br = self.boundingRect() pad += hypot(br.width(), br.height()) pen = self.pen() if pen.isCosmetic(): pad += max(1, pen.width()) * 0.7072 return pad pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/AxisItem.py000066400000000000000000001413501421045507400245560ustar00rootroot00000000000000import warnings import weakref from math import ceil, floor, isfinite, log, log10 import numpy as np from .. import debug as debug from .. import functions as fn from .. import getConfigOption from ..Point import Point from ..Qt import QtCore, QtGui, QtWidgets from .GraphicsWidget import GraphicsWidget __all__ = ['AxisItem'] class AxisItem(GraphicsWidget): """ GraphicsItem showing a single plot axis with ticks, values, and label. Can be configured to fit on any side of a plot, Can automatically synchronize its displayed scale with ViewBox items. Ticks can be extended to draw a grid. If maxTickLength is negative, ticks point into the plot. """ def __init__(self, orientation, pen=None, textPen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True, text='', units='', unitPrefix='', **args): """ =============== =============================================================== **Arguments:** orientation one of 'left', 'right', 'top', or 'bottom' maxTickLength (px) maximum length of ticks to draw. Negative values draw into the plot, positive values draw outward. linkView (ViewBox) causes the range of values displayed in the axis to be linked to the visible range of a ViewBox. showValues (bool) Whether to display values adjacent to ticks pen (QPen) Pen used when drawing ticks. textPen (QPen) Pen used when drawing tick labels. text The text (excluding units) to display on the label for this axis. units The units for this axis. Units should generally be given without any scaling prefix (eg, 'V' instead of 'mV'). The scaling prefix will be automatically prepended based on the range of data displayed. args All extra keyword arguments become CSS style options for the tag which will surround the axis label and units. =============== =============================================================== """ GraphicsWidget.__init__(self, parent) self.label = QtWidgets.QGraphicsTextItem(self) self.picture = None self.orientation = orientation if orientation not in ['left', 'right', 'top', 'bottom']: raise Exception("Orientation argument must be one of 'left', 'right', 'top', or 'bottom'.") if orientation in ['left', 'right']: self.label.setRotation(-90) self.style = { 'tickTextOffset': [5, 2], ## (horizontal, vertical) spacing between text and axis 'tickTextWidth': 30, ## space reserved for tick text 'tickTextHeight': 18, 'autoExpandTextSpace': True, ## automatically expand text space if needed 'autoReduceTextSpace': True, 'tickFont': None, 'stopAxisAtTick': (False, False), ## whether axis is drawn to edge of box or to last tick 'textFillLimits': [ ## how much of the axis to fill up with tick text, maximally. (0, 0.8), ## never fill more than 80% of the axis (2, 0.6), ## If we already have 2 ticks with text, fill no more than 60% of the axis (4, 0.4), ## If we already have 4 ticks with text, fill no more than 40% of the axis (6, 0.2), ## If we already have 6 ticks with text, fill no more than 20% of the axis ], 'showValues': showValues, 'tickLength': maxTickLength, 'maxTickLevel': 2, 'maxTextLevel': 2, 'tickAlpha': None, ## If not none, use this alpha for all ticks. } self.textWidth = 30 ## Keeps track of maximum width / height of tick text self.textHeight = 18 # If the user specifies a width / height, remember that setting # indefinitely. self.fixedWidth = None self.fixedHeight = None self.labelText = text self.labelUnits = units self.labelUnitPrefix = unitPrefix self.labelStyle = args self.logMode = False self._tickLevels = None ## used to override the automatic ticking system with explicit ticks self._tickSpacing = None # used to override default tickSpacing method self.scale = 1.0 self.autoSIPrefix = True self.autoSIPrefixScale = 1.0 self.showLabel(False) self.setRange(0, 1) if pen is None: self.setPen() else: self.setPen(pen) if textPen is None: self.setTextPen() else: self.setTextPen(pen) self._linkedView = None if linkView is not None: self._linkToView_internal(linkView) self.grid = False #self.setCacheMode(self.DeviceCoordinateCache) def setStyle(self, **kwds): """ Set various style options. =================== ======================================================= Keyword Arguments: tickLength (int) The maximum length of ticks in pixels. Positive values point toward the text; negative values point away. tickTextOffset (int) reserved spacing between text and axis in px tickTextWidth (int) Horizontal space reserved for tick text in px tickTextHeight (int) Vertical space reserved for tick text in px autoExpandTextSpace (bool) Automatically expand text space if the tick strings become too long. autoReduceTextSpace (bool) Automatically shrink the axis if necessary tickFont (QFont or None) Determines the font used for tick values. Use None for the default font. stopAxisAtTick (tuple: (bool min, bool max)) If True, the axis line is drawn only as far as the last tick. Otherwise, the line is drawn to the edge of the AxisItem boundary. textFillLimits (list of (tick #, % fill) tuples). This structure determines how the AxisItem decides how many ticks should have text appear next to them. Each tuple in the list specifies what fraction of the axis length may be occupied by text, given the number of ticks that already have text displayed. For example:: [(0, 0.8), # Never fill more than 80% of the axis (2, 0.6), # If we already have 2 ticks with text, # fill no more than 60% of the axis (4, 0.4), # If we already have 4 ticks with text, # fill no more than 40% of the axis (6, 0.2)] # If we already have 6 ticks with text, # fill no more than 20% of the axis showValues (bool) indicates whether text is displayed adjacent to ticks. 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 value of type float, accepted values are from [0..1]. =================== ======================================================= Added in version 0.9.9 """ for kwd,value in kwds.items(): if kwd not in self.style: raise NameError("%s is not a valid style argument." % kwd) if kwd in ('tickLength', 'tickTextOffset', 'tickTextWidth', 'tickTextHeight'): if not isinstance(value, int): raise ValueError("Argument '%s' must be int" % kwd) if kwd == 'tickTextOffset': if self.orientation in ('left', 'right'): self.style['tickTextOffset'][0] = value else: self.style['tickTextOffset'][1] = value elif kwd == 'stopAxisAtTick': try: assert len(value) == 2 and isinstance(value[0], bool) and isinstance(value[1], bool) except: raise ValueError("Argument 'stopAxisAtTick' must have type (bool, bool)") self.style[kwd] = value else: self.style[kwd] = value self.picture = None self._adjustSize() self.update() def close(self): self.scene().removeItem(self.label) self.label = None self.scene().removeItem(self) def setGrid(self, grid): """Set the alpha value (0-255) for the grid, or False to disable. When grid lines are enabled, the axis tick lines are extended to cover the extent of the linked ViewBox, if any. """ self.grid = grid self.picture = None self.prepareGeometryChange() self.update() def setLogMode(self, *args, **kwargs): """ Set log scaling for x and/or y axes. If two positional arguments are provided, the first will set log scaling for the x axis and the second for the y axis. If a single positional argument is provided, it will set the log scaling along the direction of the AxisItem. Alternatively, x and y can be passed as keyword arguments. If an axis is set to log scale, ticks are displayed on a logarithmic scale and values are adjusted accordingly. (This is usually accessed by changing the log mode of a :func:`PlotItem `.) The linked ViewBox will be informed of the change. """ if len(args) == 1: self.logMode = args[0] else: if len(args) == 2: x, y = args else: x = kwargs.get('x') y = kwargs.get('y') if x is not None and self.orientation in ('top', 'bottom'): self.logMode = x if y is not None and self.orientation in ('left', 'right'): self.logMode = y if self._linkedView is not None: if self.orientation in ('top', 'bottom'): self._linkedView().setLogMode('x', self.logMode) elif self.orientation in ('left', 'right'): self._linkedView().setLogMode('y', self.logMode) self.picture = None self.update() def setTickFont(self, font): """ (QFont or None) Determines the font used for tick values. Use None for the default font. """ self.style['tickFont'] = font self.picture = None self.prepareGeometryChange() ## Need to re-allocate space depending on font size? self.update() def resizeEvent(self, ev=None): #s = self.size() ## 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': p.setY(int(self.size().height()/2 + br.width()/2)) p.setX(-nudge) elif self.orientation == 'right': p.setY(int(self.size().height()/2 + br.width()/2)) p.setX(int(self.size().width()-br.height()+nudge)) elif self.orientation == 'top': p.setY(-nudge) p.setX(int(self.size().width()/2. - br.width()/2.)) elif self.orientation == 'bottom': p.setX(int(self.size().width()/2. - br.width()/2.)) p.setY(int(self.size().height()-br.height()+nudge)) self.label.setPos(p) self.picture = None def showLabel(self, show=True): """Show/hide the label text for this axis.""" #self.drawLabel = show self.label.setVisible(show) if self.orientation in ['left', 'right']: self._updateWidth() else: self._updateHeight() if self.autoSIPrefix: self.updateAutoSIPrefix() def setLabel(self, text=None, units=None, unitPrefix=None, **args): """Set the text displayed adjacent to the axis. ============== ============================================================= **Arguments:** text The text (excluding units) to display on the label for this axis. units The units for this axis. Units should generally be given without any scaling prefix (eg, 'V' instead of 'mV'). The scaling prefix will be automatically prepended based on the range of data displayed. args All extra keyword arguments become CSS style options for the tag which will surround the axis label and units. ============== ============================================================= The final text generated for the label will look like:: {text} (prefix{units}) Each extra keyword argument will become a CSS option in the above template. For example, you can set the font size and color of the label:: labelStyle = {'color': '#FFF', 'font-size': '14pt'} axis.setLabel('label text', units='V', **labelStyle) """ # `None` input is kept for backward compatibility! self.labelText = text or "" self.labelUnits = units or "" self.labelUnitPrefix = unitPrefix or "" if len(args) > 0: self.labelStyle = args # Account empty string and `None` for units and text visible = True if (text or units) else False self.showLabel(visible) self._updateLabel() def _updateLabel(self): """Internal method to update the label according to the text""" self.label.setHtml(self.labelString()) self._adjustSize() self.picture = None self.update() def labelString(self): if self.labelUnits == '': if not self.autoSIPrefix or self.autoSIPrefixScale == 1.0: units = '' else: units = '(x%g)' % (1.0/self.autoSIPrefixScale) else: units = '(%s%s)' % (self.labelUnitPrefix, self.labelUnits) s = '%s %s' % (self.labelText, units) style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle]) return "%s" % (style, s) def _updateMaxTextSize(self, x): ## Informs that the maximum tick size orthogonal to the axis has ## changed; we use this to decide whether the item needs to be resized ## to accomodate. if self.orientation in ['left', 'right']: if self.style["autoReduceTextSpace"]: if x > self.textWidth or x < self.textWidth - 10: self.textWidth = x else: mx = max(self.textWidth, x) if mx > self.textWidth or mx < self.textWidth - 10: self.textWidth = mx if self.style['autoExpandTextSpace']: self._updateWidth() else: if self.style['autoReduceTextSpace']: if x > self.textHeight or x < self.textHeight - 10: self.textHeight = x else: mx = max(self.textHeight, x) if mx > self.textHeight or mx < self.textHeight - 10: self.textHeight = mx if self.style['autoExpandTextSpace']: self._updateHeight() def _adjustSize(self): if self.orientation in ['left', 'right']: self._updateWidth() else: self._updateHeight() def setHeight(self, h=None): """Set the height of this axis reserved for ticks and tick labels. The height of the axis label is automatically added. If *height* is None, then the value will be determined automatically based on the size of the tick text.""" self.fixedHeight = h self._updateHeight() def _updateHeight(self): if not self.isVisible(): h = 0 else: if self.fixedHeight is None: if not self.style['showValues']: h = 0 elif self.style['autoExpandTextSpace']: h = self.textHeight else: h = self.style['tickTextHeight'] h += self.style['tickTextOffset'][1] if self.style['showValues'] else 0 h += max(0, self.style['tickLength']) if self.label.isVisible(): h += self.label.boundingRect().height() * 0.8 else: h = self.fixedHeight self.setMaximumHeight(h) self.setMinimumHeight(h) self.picture = None def setWidth(self, w=None): """Set the width of this axis reserved for ticks and tick labels. The width of the axis label is automatically added. If *width* is None, then the value will be determined automatically based on the size of the tick text.""" self.fixedWidth = w self._updateWidth() def _updateWidth(self): if not self.isVisible(): w = 0 else: if self.fixedWidth is None: if not self.style['showValues']: w = 0 elif self.style['autoExpandTextSpace']: w = self.textWidth else: w = self.style['tickTextWidth'] w += self.style['tickTextOffset'][0] if self.style['showValues'] else 0 w += max(0, self.style['tickLength']) if self.label.isVisible(): w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate else: w = self.fixedWidth self.setMaximumWidth(w) self.setMinimumWidth(w) self.picture = None def pen(self): if self._pen is None: return fn.mkPen(getConfigOption('foreground')) return fn.mkPen(self._pen) def setPen(self, *args, **kwargs): """ Set the pen used for drawing text, axes, ticks, and grid lines. If no arguments are given, the default foreground color will be used (see :func:`setConfigOption `). """ self.picture = None if args or kwargs: self._pen = fn.mkPen(*args, **kwargs) else: self._pen = fn.mkPen(getConfigOption('foreground')) self.labelStyle['color'] = self._pen.color().name() # #RRGGBB self._updateLabel() def textPen(self): if self._textPen is None: return fn.mkPen(getConfigOption('foreground')) return fn.mkPen(self._textPen) def setTextPen(self, *args, **kwargs): """ Set the pen used for drawing text. If no arguments are given, the default foreground color will be used. """ self.picture = None if args or kwargs: self._textPen = fn.mkPen(*args, **kwargs) else: self._textPen = fn.mkPen(getConfigOption('foreground')) self.labelStyle['color'] = self._textPen.color().name() # #RRGGBB self._updateLabel() def setScale(self, scale=None): """ Set the value scaling for this axis. Setting this value causes the axis to draw ticks and tick labels as if the view coordinate system were scaled. By default, the axis scaling is 1.0. """ # Deprecated usage, kept for backward compatibility if scale is None: warnings.warn( 'AxisItem.setScale(None) is deprecated, will be removed in 0.13.0' 'instead use AxisItem.enableAutoSIPrefix(bool) to enable/disable' 'SI prefix scaling', DeprecationWarning, stacklevel=2 ) scale = 1.0 self.enableAutoSIPrefix(True) if scale != self.scale: self.scale = scale self._updateLabel() def enableAutoSIPrefix(self, enable=True): """ Enable (or disable) automatic SI prefix scaling on this axis. When enabled, this feature automatically determines the best SI prefix to prepend to the label units, while ensuring that axis values are scaled accordingly. For example, if the axis spans values from -0.1 to 0.1 and has units set to 'V' then the axis would display values -100 to 100 and the units would appear as 'mV' This feature is enabled by default, and is only available when a suffix (unit string) is provided to display on the label. """ self.autoSIPrefix = enable self.updateAutoSIPrefix() def updateAutoSIPrefix(self): if self.label.isVisible(): if self.logMode: _range = 10**np.array(self.range) else: _range = self.range (scale, prefix) = fn.siScale(max(abs(_range[0]*self.scale), abs(_range[1]*self.scale))) if self.labelUnits == '' and prefix in ['k', 'm']: ## If we are not showing units, wait until 1e6 before scaling. scale = 1.0 prefix = '' self.autoSIPrefixScale = scale self.labelUnitPrefix = prefix else: self.autoSIPrefixScale = 1.0 self._updateLabel() def setRange(self, mn, mx): """Set the range of values displayed by the axis. Usually this is handled automatically by linking the axis to a ViewBox with :func:`linkToView `""" if not isfinite(mn) or not isfinite(mx): raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx))) self.range = [mn, mx] if self.autoSIPrefix: # XXX: Will already update once! self.updateAutoSIPrefix() else: self.picture = None self.update() def linkedView(self): """Return the ViewBox this axis is linked to""" if self._linkedView is None: return None else: return self._linkedView() def _linkToView_internal(self, view): # We need this code to be available without override, # even though DateAxisItem overrides the user-side linkToView method self.unlinkFromView() self._linkedView = weakref.ref(view) if self.orientation in ['right', 'left']: view.sigYRangeChanged.connect(self.linkedViewChanged) else: view.sigXRangeChanged.connect(self.linkedViewChanged) view.sigResized.connect(self.linkedViewChanged) def linkToView(self, view): """Link this axis to a ViewBox, causing its displayed range to match the visible range of the view.""" self._linkToView_internal(view) def unlinkFromView(self): """Unlink this axis from a ViewBox.""" oldView = self.linkedView() self._linkedView = None if self.orientation in ['right', 'left']: if oldView is not None: oldView.sigYRangeChanged.disconnect(self.linkedViewChanged) else: if oldView is not None: oldView.sigXRangeChanged.disconnect(self.linkedViewChanged) if oldView is not None: oldView.sigResized.disconnect(self.linkedViewChanged) def linkedViewChanged(self, view, newRange=None): if self.orientation in ['right', 'left']: if newRange is None: newRange = view.viewRange()[1] if view.yInverted(): self.setRange(*newRange[::-1]) else: self.setRange(*newRange) else: if newRange is None: newRange = view.viewRange()[0] if view.xInverted(): self.setRange(*newRange[::-1]) else: self.setRange(*newRange) def boundingRect(self): linkedView = self.linkedView() if linkedView is None or self.grid is False: rect = self.mapRectFromParent(self.geometry()) ## extend rect if ticks go in negative direction ## also extend to account for text that flows past the edges tl = self.style['tickLength'] if self.orientation == 'left': rect = rect.adjusted(0, -15, -min(0,tl), 15) elif self.orientation == 'right': rect = rect.adjusted(min(0,tl), -15, 0, 15) elif self.orientation == 'top': rect = rect.adjusted(-15, 0, 15, -min(0,tl)) elif self.orientation == 'bottom': rect = rect.adjusted(-15, min(0,tl), 15, 0) return rect else: return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect()) def paint(self, p, opt, widget): profiler = debug.Profiler() if self.picture is None: try: picture = QtGui.QPicture() painter = QtGui.QPainter(picture) if self.style["tickFont"]: painter.setFont(self.style["tickFont"]) specs = self.generateDrawSpecs(painter) profiler('generate specs') if specs is not None: self.drawPicture(painter, *specs) profiler('draw picture') finally: painter.end() self.picture = picture #p.setRenderHint(p.RenderHint.Antialiasing, False) ## Sometimes we get a segfault here ??? #p.setRenderHint(p.RenderHint.TextAntialiasing, True) self.picture.play(p) def setTicks(self, ticks): """Explicitly determine which ticks to display. This overrides the behavior specified by tickSpacing(), tickValues(), and tickStrings() The format for *ticks* looks like:: [ [ (majorTickValue1, majorTickString1), (majorTickValue2, majorTickString2), ... ], [ (minorTickValue1, minorTickString1), (minorTickValue2, minorTickString2), ... ], ... ] If *ticks* is None, then the default tick system will be used instead. """ self._tickLevels = ticks self.picture = None self.update() def setTickSpacing(self, major=None, minor=None, levels=None): """ Explicitly determine the spacing of major and minor ticks. This overrides the default behavior of the tickSpacing method, and disables the effect of setTicks(). Arguments may be either *major* and *minor*, or *levels* which is a list of (spacing, offset) tuples for each tick level desired. If no arguments are given, then the default behavior of tickSpacing is enabled. Examples:: # two levels, all offsets = 0 axis.setTickSpacing(5, 1) # three levels, all offsets = 0 axis.setTickSpacing([(3, 0), (1, 0), (0.25, 0)]) # reset to default axis.setTickSpacing() """ if levels is None: if major is None: levels = None else: levels = [(major, 0), (minor, 0)] self._tickSpacing = levels self.picture = None self.update() def tickSpacing(self, minVal, maxVal, size): """Return values describing the desired spacing and offset of ticks. This method is called whenever the axis needs to be redrawn and is a good method to override in subclasses that require control over tick locations. The return value must be a list of tuples, one for each set of ticks:: [ (major tick spacing, offset), (minor tick spacing, offset), (sub-minor tick spacing, offset), ... ] """ # First check for override tick spacing if self._tickSpacing is not None: return self._tickSpacing dif = abs(maxVal - minVal) if dif == 0: return [] ## decide optimal minor tick spacing in pixels (this is just aesthetics) optimalTickCount = max(2., log(size)) ## optimal minor tick spacing optimalSpacing = dif / optimalTickCount ## the largest power-of-10 spacing which is smaller than optimal p10unit = 10 ** floor(log10(optimalSpacing)) ## Determine major/minor tick spacings which flank the optimal spacing. intervals = np.array([1., 2., 10., 20., 100.]) * p10unit minorIndex = 0 while intervals[minorIndex+1] <= optimalSpacing: minorIndex += 1 levels = [ (intervals[minorIndex+2], 0), (intervals[minorIndex+1], 0), #(intervals[minorIndex], 0) ## Pretty, but eats up CPU ] if self.style['maxTickLevel'] >= 2: ## decide whether to include the last level of ticks minSpacing = min(size / 20., 30.) maxTickCount = size / minSpacing if dif / intervals[minorIndex] <= maxTickCount: levels.append((intervals[minorIndex], 0)) return levels ##### This does not work -- switching between 2/5 confuses the automatic text-level-selection ### Determine major/minor tick spacings which flank the optimal spacing. #intervals = np.array([1., 2., 5., 10., 20., 50., 100.]) * p10unit #minorIndex = 0 #while intervals[minorIndex+1] <= optimalSpacing: #minorIndex += 1 ### make sure we never see 5 and 2 at the same time #intIndexes = [ #[0,1,3], #[0,2,3], #[2,3,4], #[3,4,6], #[3,5,6], #][minorIndex] #return [ #(intervals[intIndexes[2]], 0), #(intervals[intIndexes[1]], 0), #(intervals[intIndexes[0]], 0) #] def tickValues(self, minVal, maxVal, size): """ Return the values and spacing of ticks to draw:: [ (spacing, [major ticks]), (spacing, [minor ticks]), ... ] By default, this method calls tickSpacing to determine the correct tick locations. This is a good method to override in subclasses. """ minVal, maxVal = sorted((minVal, maxVal)) minVal *= self.scale maxVal *= self.scale #size *= self.scale ticks = [] tickLevels = self.tickSpacing(minVal, maxVal, size) allValues = np.array([]) for i in range(len(tickLevels)): spacing, offset = tickLevels[i] ## determine starting tick start = (ceil((minVal-offset) / spacing) * spacing) + offset ## determine number of ticks num = int((maxVal-start) / spacing) + 1 values = (np.arange(num) * spacing + start) / self.scale ## remove any ticks that were present in higher levels ## we assume here that if the difference between a tick value and a previously seen tick value ## is less than spacing/100, then they are 'equal' and we can ignore the new tick. values = list(filter(lambda x: np.all(np.abs(allValues-x) > spacing/self.scale*0.01), values)) allValues = np.concatenate([allValues, values]) ticks.append((spacing/self.scale, values)) if self.logMode: return self.logTickValues(minVal, maxVal, size, ticks) #nticks = [] #for t in ticks: #nvals = [] #for v in t[1]: #nvals.append(v/self.scale) #nticks.append((t[0]/self.scale,nvals)) #ticks = nticks return ticks def logTickValues(self, minVal, maxVal, size, stdTicks): ## start with the tick spacing given by tickValues(). ## Any level whose spacing is < 1 needs to be converted to log scale ticks = [] for (spacing, t) in stdTicks: if spacing >= 1.0: ticks.append((spacing, t)) if len(ticks) < 3: v1 = int(floor(minVal)) v2 = int(ceil(maxVal)) #major = list(range(v1+1, v2)) minor = [] for v in range(v1, v2): minor.extend(v + np.log10(np.arange(1, 10))) minor = [x for x in minor if x>minVal and x= 10000: vstr = "%g" % vs else: vstr = ("%%0.%df" % places) % vs strings.append(vstr) return strings def logTickStrings(self, values, scale, spacing): estrings = ["%0.1g"%x for x in 10 ** np.array(values).astype(float) * np.array(scale)] 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) else: dstrings.append(e) return dstrings def generateDrawSpecs(self, p): """ Calls tickValues() and tickStrings() to determine where and how ticks should be drawn, then generates from this a set of drawing commands to be interpreted by drawPicture(). """ profiler = debug.Profiler() if self.style['tickFont'] is not None: p.setFont(self.style['tickFont']) bounds = self.mapRectFromParent(self.geometry()) linkedView = self.linkedView() if linkedView is None or self.grid is False: tickBounds = bounds else: tickBounds = linkedView.mapRectToItem(self, linkedView.boundingRect()) if self.orientation == 'left': span = (bounds.topRight(), bounds.bottomRight()) tickStart = tickBounds.right() tickStop = bounds.right() tickDir = -1 axis = 0 elif self.orientation == 'right': span = (bounds.topLeft(), bounds.bottomLeft()) tickStart = tickBounds.left() tickStop = bounds.left() tickDir = 1 axis = 0 elif self.orientation == 'top': span = (bounds.bottomLeft(), bounds.bottomRight()) tickStart = tickBounds.bottom() tickStop = bounds.bottom() tickDir = -1 axis = 1 elif self.orientation == 'bottom': span = (bounds.topLeft(), bounds.topRight()) tickStart = tickBounds.top() tickStop = bounds.top() tickDir = 1 axis = 1 else: raise ValueError("self.orientation must be in ('left', 'right', 'top', 'bottom')") #print tickStart, tickStop, span ## determine size of this item in pixels points = list(map(self.mapToDevice, span)) if None in points: return lengthInPixels = Point(points[1] - points[0]).length() if lengthInPixels == 0: return # Determine major / minor / subminor axis ticks if self._tickLevels is None: tickLevels = self.tickValues(self.range[0], self.range[1], lengthInPixels) tickStrings = None else: ## parse self.tickLevels into the formats returned by tickLevels() and tickStrings() tickLevels = [] tickStrings = [] for level in self._tickLevels: values = [] strings = [] tickLevels.append((None, values)) tickStrings.append(strings) for val, strn in level: values.append(val) strings.append(strn) ## determine mapping between tick values and local coordinates dif = self.range[1] - self.range[0] if dif == 0: xScale = 1 offset = 0 else: if axis == 0: xScale = -bounds.height() / dif offset = self.range[0] * xScale - bounds.height() else: xScale = bounds.width() / dif offset = self.range[0] * xScale xRange = [x * xScale - offset for x in self.range] xMin = min(xRange) xMax = max(xRange) profiler('init') tickPositions = [] # remembers positions of previously drawn ticks ## compute coordinates to draw ticks ## draw three different intervals, long ticks first tickSpecs = [] for i in range(len(tickLevels)): tickPositions.append([]) ticks = tickLevels[i][1] ## length of tick tickLength = self.style['tickLength'] / ((i*0.5)+1.0) lineAlpha = self.style["tickAlpha"] if lineAlpha is None: lineAlpha = 255 / (i+1) if self.grid is not False: lineAlpha *= self.grid/255. * fn.clip_scalar((0.05 * lengthInPixels / (len(ticks)+1)), 0., 1.) elif isinstance(lineAlpha, float): lineAlpha *= 255 lineAlpha = max(0, int(round(lineAlpha))) lineAlpha = min(255, int(round(lineAlpha))) elif isinstance(lineAlpha, int): if (lineAlpha > 255) or (lineAlpha < 0): raise ValueError("lineAlpha should be [0..255]") else: raise TypeError("Line Alpha should be of type None, float or int") for v in ticks: ## determine actual position to draw this tick x = (v * xScale) - offset if x < xMin or x > xMax: ## last check to make sure no out-of-bounds ticks are drawn tickPositions[i].append(None) continue tickPositions[i].append(x) p1 = [x, x] p2 = [x, x] p1[axis] = tickStart p2[axis] = tickStop if self.grid is False: p2[axis] += tickLength*tickDir tickPen = self.pen() color = tickPen.color() color.setAlpha(int(lineAlpha)) tickPen.setColor(color) tickSpecs.append((tickPen, Point(p1), Point(p2))) profiler('compute ticks') if self.style['stopAxisAtTick'][0] is True: minTickPosition = min(map(min, tickPositions)) if axis == 0: stop = max(span[0].y(), minTickPosition) span[0].setY(stop) else: stop = max(span[0].x(), minTickPosition) span[0].setX(stop) if self.style['stopAxisAtTick'][1] is True: maxTickPosition = max(map(max, tickPositions)) if axis == 0: stop = min(span[1].y(), maxTickPosition) span[1].setY(stop) else: stop = min(span[1].x(), maxTickPosition) span[1].setX(stop) axisSpec = (self.pen(), span[0], span[1]) textOffset = self.style['tickTextOffset'][axis] ## spacing between axis and text #if self.style['autoExpandTextSpace'] is True: #textWidth = self.textWidth #textHeight = self.textHeight #else: #textWidth = self.style['tickTextWidth'] ## space allocated for horizontal text #textHeight = self.style['tickTextHeight'] ## space allocated for horizontal text textSize2 = 0 lastTextSize2 = 0 textRects = [] textSpecs = [] ## list of draw # If values are hidden, return early if not self.style['showValues']: return (axisSpec, tickSpecs, textSpecs) for i in range(min(len(tickLevels), self.style['maxTextLevel']+1)): ## Get the list of strings to display for this level if tickStrings is None: spacing, values = tickLevels[i] strings = self.tickStrings(values, self.autoSIPrefixScale * self.scale, spacing) else: strings = tickStrings[i] if len(strings) == 0: continue ## ignore strings belonging to ticks that were previously ignored for j in range(len(strings)): if tickPositions[i][j] is None: strings[j] = None ## Measure density of text; decide whether to draw this level rects = [] for s in strings: if s is None: rects.append(None) else: 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) rects.append(br) textRects.append(rects[-1]) if len(textRects) > 0: ## measure all text, make sure there's enough room if axis == 0: textSize = np.sum([r.height() for r in textRects]) textSize2 = np.max([r.width() for r in textRects]) else: textSize = np.sum([r.width() for r in textRects]) textSize2 = np.max([r.height() for r in textRects]) else: textSize = 0 textSize2 = 0 if i > 0: ## always draw top level ## If the strings are too crowded, stop drawing text now. ## We use three different crowding limits based on the number ## of texts drawn so far. textFillRatio = float(textSize) / lengthInPixels finished = False for nTexts, limit in self.style['textFillLimits']: if len(textSpecs) >= nTexts and textFillRatio >= limit: finished = True break if finished: break lastTextSize2 = textSize2 #spacing, values = tickLevels[best] #strings = self.tickStrings(values, self.scale, spacing) # Determine exactly where tick text should be drawn for j in range(len(strings)): vstr = strings[j] if vstr is None: ## this tick was ignored because it is out of bounds continue x = tickPositions[i][j] #textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignmentFlag.AlignCenter, vstr) textRect = rects[j] height = textRect.height() width = textRect.width() #self.textHeight = height offset = max(0,self.style['tickLength']) + textOffset if self.orientation == 'left': alignFlags = QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignVCenter rect = QtCore.QRectF(tickStop-offset-width, x-(height/2), width, height) elif self.orientation == 'right': alignFlags = QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignVCenter rect = QtCore.QRectF(tickStop+offset, x-(height/2), width, height) elif self.orientation == 'top': alignFlags = QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignBottom rect = QtCore.QRectF(x-width/2., tickStop-offset-height, width, height) elif self.orientation == 'bottom': alignFlags = QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop rect = QtCore.QRectF(x-width/2., tickStop+offset, width, height) textFlags = alignFlags | QtCore.Qt.TextFlag.TextDontClip #p.setPen(self.pen()) #p.drawText(rect, textFlags, vstr) textSpecs.append((rect, textFlags, vstr)) profiler('compute text') ## update max text size if needed. self._updateMaxTextSize(lastTextSize2) return (axisSpec, tickSpecs, textSpecs) def drawPicture(self, p, axisSpec, tickSpecs, textSpecs): profiler = debug.Profiler() p.setRenderHint(p.RenderHint.Antialiasing, False) p.setRenderHint(p.RenderHint.TextAntialiasing, True) ## draw long line along axis pen, p1, p2 = axisSpec p.setPen(pen) p.drawLine(p1, p2) # p.translate(0.5,0) ## resolves some damn pixel ambiguity ## draw ticks for pen, p1, p2 in tickSpecs: p.setPen(pen) p.drawLine(p1, p2) profiler('draw ticks') # Draw all text if self.style['tickFont'] is not None: p.setFont(self.style['tickFont']) p.setPen(self.textPen()) bounding = self.boundingRect().toAlignedRect() p.setClipRect(bounding) for rect, flags, text in textSpecs: p.drawText(rect, int(flags), text) profiler('draw text') def show(self): GraphicsWidget.show(self) if self.orientation in ['left', 'right']: self._updateWidth() else: self._updateHeight() def hide(self): GraphicsWidget.hide(self) if self.orientation in ['left', 'right']: self._updateWidth() else: self._updateHeight() def wheelEvent(self, event): lv = self.linkedView() if lv is None: return # Did the event occur inside the linked ViewBox (and not over the axis iteself)? if lv.sceneBoundingRect().contains(event.scenePos()): event.ignore() return else: # pass event to linked viewbox with appropriate single axis zoom parameter if self.orientation in ['left', 'right']: lv.wheelEvent(event, axis=1) else: lv.wheelEvent(event, axis=0) event.accept() def mouseDragEvent(self, event): lv = self.linkedView() if lv is None: return # Did the mouse down event occur inside the linked ViewBox (and not the axis)? if lv.sceneBoundingRect().contains(event.buttonDownScenePos()): event.ignore() return # otherwise pass event to linked viewbox with appropriate single axis parameter if self.orientation in ['left', 'right']: return lv.mouseDragEvent(event, axis=1) else: return lv.mouseDragEvent(event, axis=0) def mouseClickEvent(self, event): lv = self.linkedView() if lv is None: return return lv.mouseClickEvent(event) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/BarGraphItem.py000066400000000000000000000121041421045507400253320ustar00rootroot00000000000000import numpy as np from .. import functions as fn from .. import getConfigOption from ..Qt import QtCore, QtGui from .GraphicsObject import GraphicsObject __all__ = ['BarGraphItem'] class BarGraphItem(GraphicsObject): def __init__(self, **opts): """ Valid keyword options are: x, x0, x1, y, y0, y1, width, height, pen, brush x specifies the x-position of the center of the bar. x0, x1 specify left and right edges of the bar, respectively. width specifies distance from x0 to x1. You may specify any combination: x, width x0, width x1, width x0, x1 Likewise y, y0, y1, and height. If only height is specified, then y0 will be set to 0 Example uses: BarGraphItem(x=range(5), height=[1,5,2,4,3], width=0.5) """ GraphicsObject.__init__(self) self.opts = dict( x=None, y=None, x0=None, y0=None, x1=None, y1=None, name=None, height=None, width=None, pen=None, brush=None, pens=None, brushes=None, ) self._shape = None self.picture = None self.setOpts(**opts) def setOpts(self, **opts): self.opts.update(opts) self.picture = None self._shape = None self.update() self.informViewBoundsChanged() def drawPicture(self): self.picture = QtGui.QPicture() self._shape = QtGui.QPainterPath() p = QtGui.QPainter(self.picture) pen = self.opts['pen'] pens = self.opts['pens'] if pen is None and pens is None: pen = getConfigOption('foreground') brush = self.opts['brush'] brushes = self.opts['brushes'] if brush is None and brushes is None: brush = (128, 128, 128) def asarray(x): if x is None or np.isscalar(x) or isinstance(x, np.ndarray): return x return np.array(x) x = asarray(self.opts.get('x')) x0 = asarray(self.opts.get('x0')) x1 = asarray(self.opts.get('x1')) width = asarray(self.opts.get('width')) if x0 is None: if width is None: raise Exception('must specify either x0 or width') if x1 is not None: x0 = x1 - width elif x is not None: x0 = x - width/2. else: raise Exception('must specify at least one of x, x0, or x1') if width is None: if x1 is None: raise Exception('must specify either x1 or width') width = x1 - x0 y = asarray(self.opts.get('y')) y0 = asarray(self.opts.get('y0')) y1 = asarray(self.opts.get('y1')) height = asarray(self.opts.get('height')) if y0 is None: if height is None: y0 = 0 elif y1 is not None: y0 = y1 - height elif y is not None: y0 = y - height/2. else: y0 = 0 if height is None: if y1 is None: raise Exception('must specify either y1 or height') height = y1 - y0 p.setPen(fn.mkPen(pen)) p.setBrush(fn.mkBrush(brush)) for i in range(len(x0 if not np.isscalar(x0) else y0)): if pens is not None: p.setPen(fn.mkPen(pens[i])) if brushes is not None: p.setBrush(fn.mkBrush(brushes[i])) if np.isscalar(x0): x = x0 else: x = x0[i] if np.isscalar(y0): y = y0 else: y = y0[i] if np.isscalar(width): w = width else: w = width[i] if np.isscalar(height): h = height else: h = height[i] rect = QtCore.QRectF(x, y, w, h) p.drawRect(rect) self._shape.addRect(rect) p.end() self.prepareGeometryChange() def paint(self, p, *args): if self.picture is None: self.drawPicture() self.picture.play(p) def boundingRect(self): if self.picture is None: self.drawPicture() return QtCore.QRectF(self.picture.boundingRect()) def shape(self): if self.picture is None: self.drawPicture() return self._shape def implements(self, interface=None): ints = ['plotData'] if interface is None: return ints return interface in ints def name(self): return self.opts.get('name', None) def getData(self): return self.opts.get('x'), self.opts.get('height') pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ButtonItem.py000066400000000000000000000031601421045507400251210ustar00rootroot00000000000000from ..Qt import QtCore, QtGui from .GraphicsObject import GraphicsObject __all__ = ['ButtonItem'] class ButtonItem(GraphicsObject): """Button graphicsItem displaying an image.""" clicked = QtCore.Signal(object) def __init__(self, imageFile=None, width=None, parentItem=None, pixmap=None): self.enabled = True GraphicsObject.__init__(self) if imageFile is not None: self.setImageFile(imageFile) elif pixmap is not None: self.setPixmap(pixmap) if width is not None: s = float(width) / self.pixmap.width() self.setScale(s) if parentItem is not None: self.setParentItem(parentItem) self.setOpacity(0.7) def setImageFile(self, imageFile): self.setPixmap(QtGui.QPixmap(imageFile)) def setPixmap(self, pixmap): self.pixmap = pixmap self.update() def mouseClickEvent(self, ev): if self.enabled: self.clicked.emit(self) def mouseHoverEvent(self, ev): if not self.enabled: return if ev.isEnter(): self.setOpacity(1.0) else: self.setOpacity(0.7) def disable(self): self.enabled = False self.setOpacity(0.4) def enable(self): self.enabled = True self.setOpacity(0.7) def paint(self, p, *args): p.setRenderHint(p.RenderHint.Antialiasing) p.drawPixmap(0, 0, self.pixmap) def boundingRect(self): return QtCore.QRectF(self.pixmap.rect()) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ColorBarItem.py000066400000000000000000000361601421045507400253570ustar00rootroot00000000000000import math import warnings import weakref import numpy as np from .. import functions as fn from ..Qt import QtCore from .ImageItem import ImageItem from .LinearRegionItem import LinearRegionItem from .PlotItem import PlotItem from .. import colormap __all__ = ['ColorBarItem'] class ColorBarItem(PlotItem): """ **Bases:** :class:`PlotItem ` :class:`ColorBarItem` controls the application of a :ref:`color map ` to one (or more) :class:`~pyqtgraph.ImageItem`. It is a simpler, compact alternative to :class:`~pyqtgraph.HistogramLUTItem`, without histogram or the option to adjust the colors of the look-up table. A labeled axis is displayed directly next to the gradient to help identify values. Handles included in the color bar allow for interactive adjustment. A ColorBarItem can be assigned one or more :class:`~pyqtgraph.ImageItem`s that will be displayed according to the selected color map and levels. The ColorBarItem can be used as a separate element in a :class:`~pyqtgraph.GraphicsLayout` or added to the layout of a :class:`~pyqtgraph.PlotItem` used to display image data with coordinate axes. ============================= ============================================= **Signals:** sigLevelsChanged(self) Emitted when the range sliders are moved sigLevelsChangeFinished(self) Emitted when the range sliders are released ============================= ============================================= """ sigLevelsChanged = QtCore.Signal(object) sigLevelsChangeFinished = QtCore.Signal(object) 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', cmap=None ): """ Creates a new ColorBarItem. Parameters ---------- colorMap: `str` or :class:`~pyqtgraph.ColorMap` Determines the color map displayed and applied to assigned ImageItem(s). values: tuple of float The range of image levels covered by the color bar, as ``(min, max)``. width: float, default=25 The width of the displayed color bar. label: str, optional Label applied to the color bar axis. interactive: bool, default=True If `True`, handles are displayed to interactively adjust the level range. limits: `None` or `tuple of float` Limits the adjustment range to `(low, high)`, `None` disables the limit. rounding: float, default=1 Adjusted range values are rounded to multiples of this value. orientation: str, default 'vertical' 'horizontal' or 'h' gives a horizontal color bar instead of the default vertical bar pen: :class:`Qpen` or argument to :func:`~pyqtgraph.mkPen` Sets the color of adjustment handles in interactive mode. hoverPen: :class:`QPen` or argument to :func:`~pyqtgraph.mkPen` Sets the color of adjustement handles when hovered over. hoverBrush: :class:`QBrush` or argument to :func:`~pyqtgraph.mkBrush` Sets the 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._colorMap = None self.rounding = rounding self.horizontal = bool( orientation in ('h', 'horizontal') ) self.lo_prv, self.hi_prv = self.values # remember previous values while adjusting range self.lo_lim = None self.hi_lim = None if limits is not None: self.lo_lim, self.hi_lim = limits # slightly expand the limits to match the rounding steps: if self.lo_lim is not None: self.lo_lim = self.rounding * math.floor( self.lo_lim/self.rounding ) if self.hi_lim is not None: self.hi_lim = self.rounding * math.ceil( self.hi_lim/self.rounding ) self.disableAutoRange() self.hideButtons() self.setMouseEnabled(x=False, y=False) self.setMenuEnabled( False) if self.horizontal: self.setRange( xRange=(0,256), yRange=(0,1), padding=0 ) self.layout.setRowFixedHeight(2, width) else: self.setRange( xRange=(0,1), yRange=(0,256), padding=0 ) self.layout.setColumnFixedWidth(1, width) # width of color bar for key in ['left','right','top','bottom']: self.showAxis(key) axis = self.getAxis(key) axis.setZValue(0.5) # select main axis: if self.horizontal and key == 'bottom': self.axis = axis elif not self.horizontal and key == 'right': self.axis = axis self.axis.setWidth(45) else: # show other axes to create frame axis.setTicks( [] ) axis.setStyle( showValues=False ) self.axis.setStyle( showValues=True ) self.axis.unlinkFromView() self.axis.setRange( self.values[0], self.values[1] ) self.bar = ImageItem(axisOrder='col-major') if self.horizontal: self.bar.setImage( np.linspace(0, 1, 256).reshape( (-1,1) ) ) if label is not None: self.getAxis('bottom').setLabel(label) else: 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 colorMap is not None: self.setColorMap(colorMap) if interactive: if self.horizontal: align = 'vertical' else: align = 'horizontal' self.region = LinearRegionItem( [63, 191], align, swapMode='block', # span=(0.15, 0.85), # limited span looks better, but disables grabbing the region pen=pen, brush=fn.mkBrush(None), hoverPen=hoverPen, hoverBrush=hoverBrush ) self.region.setZValue(1000) self.region.lines[0].addMarker('<|>', size=6) self.region.lines[1].addMarker('<|>', size=6) self.region.sigRegionChanged.connect(self._regionChanging) self.region.sigRegionChangeFinished.connect(self._regionChanged) self.addItem(self.region) self.region_changed_enable = True self.region.setRegion( (63, 191) ) # place handles at 25% and 75% locations else: self.region = None self.region_changed_enable = False def setImageItem(self, img, insert_in=None): """ Assigns an ImageItem or list of ImageItems to be represented and controlled Parameters ---------- image: :class:`~pyqtgraph.ImageItem` or list of `[ImageItem, ImageItem, ...]` Assigns one or more ImageItems to this ColorBarItem. If a :class:`~pyqtgraph.ColorMap` is defined for ColorBarItem, this will be assigned to the ImageItems. Otherwise, the ColorBarItem will attempt to retrieve a color map from the ImageItems. In interactive mode, ColorBarItem will control the levels of the assigned ImageItems, simultaneously if there is more than one. insert_in: :class:`~pyqtgraph.PlotItem`, optional If a PlotItem is given, the color bar is inserted on the right or bottom of the plot, depending on the specified orientation. """ try: self.img_list = [ weakref.ref(item) for item in img ] except TypeError: # failed to iterate, make a single-item list self.img_list = [ weakref.ref( img ) ] if self._colorMap is None: # check if one of the assigned images has a defined color map for img_weakref in self.img_list: img = img_weakref() if img is not None: img_cm = img.getColorMap() if img_cm is not None: self._colorMap = img_cm break if insert_in is not None: if self.horizontal: insert_in.layout.addItem( self, 5, 1 ) # insert in layout below bottom axis insert_in.layout.setRowFixedHeight(4, 10) # enforce some space to axis above else: insert_in.layout.addItem( self, 2, 5 ) # insert in layout after right-hand axis 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 color map to determine the ColorBarItem's look-up table. The same look-up table is applied to any assigned ImageItem. `colorMap` can be a :class:`~pyqtgraph.ColorMap` or a string argument that is passed to :func:`colormap.get() `. """ if isinstance(colorMap, str): colorMap = colormap.get(colorMap) 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 ): """ Sets the displayed range of image levels. Parameters ---------- values: tuple of float Specifies levels as tuple ``(low, high)``. Either value can be `None` to leave the previous value unchanged. Takes precedence over `low` and `high` parameters. low: float Applies a new low level to color bar and assigned images high: float Applies a new high level to color bar and assigned images """ if values is not None: # values setting takes precendence low, high = values lo_new, hi_new = low, high lo_cur, hi_cur = self.values # allow None values to preserve original values: if lo_new is None: lo_new = lo_cur if hi_new is None: hi_new = hi_cur if lo_new > hi_new: # prevent reversal lo_new = hi_new = (lo_new + hi_new) / 2 # clip to limits if set: if self.lo_lim is not None and lo_new < self.lo_lim: lo_new = self.lo_lim if self.hi_lim is not None and hi_new > self.hi_lim: hi_new = self.hi_lim self.values = self.lo_prv, self.hi_prv = (lo_new, hi_new) self._update_items() def levels(self): """ Returns the currently set levels as the tuple ``(low, high)``. """ return self.values def _update_items(self, update_cmap=False): """ 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._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._colorMap is not None: img.setLookupTable( self._colorMap.getLookupTable(nPts=256) ) def _regionChanged(self): """ internal: snap adjusters back to default positions on release """ self.lo_prv, self.hi_prv = self.values self.region_changed_enable = False # otherwise this affects the region again self.region.setRegion( (63, 191) ) self.region_changed_enable = True self.sigLevelsChangeFinished.emit(self) def _regionChanging(self): """ internal: recalculate levels based on new position of adjusters """ if not self.region_changed_enable: return bot, top = self.region.getRegion() bot = ( (bot - 63) / 64 ) # -1 to +1 over half-bar range top = ( (top - 191) / 64 ) # -1 to +1 over half-bar range bot = math.copysign( bot**2, bot ) # quadratic behaviour for sensitivity to small changes top = math.copysign( top**2, top ) # These are the new values if adjuster is released now, rate of change depends on original separation span_prv = self.hi_prv - self.lo_prv # previous span of values hi_new = self.hi_prv + (span_prv + 2*self.rounding) * top # make sure that we can always lo_new = self.lo_prv + (span_prv + 2*self.rounding) * bot # reach 2x the minimal step # Alternative model with speed depending on level magnitude: # mean_val = abs(self.lo_prv) + abs(self.hi_prv) / 2 # hi_new = self.hi_prv + (mean_val + 2*self.rounding) * top # make sure that we can always # lo_new = self.lo_prv + (mean_val + 2*self.rounding) * bot # reach 2x the minimal step if self.hi_lim is not None: if hi_new > self.hi_lim: # limit maximum value hi_new = self.hi_lim if top!=0 and bot!=0: # moving entire region? lo_new = hi_new - span_prv # avoid collapsing the span against top limit if self.lo_lim is not None: if lo_new < self.lo_lim: # limit minimum value lo_new = self.lo_lim if top!=0 and bot!=0: # moving entire region? hi_new = lo_new + span_prv # avoid collapsing the span against bottom limit if hi_new-lo_new < self.rounding: # do not allow less than one "rounding" unit of span if bot == 0: hi_new = lo_new + self.rounding elif top == 0: lo_new = hi_new - self.rounding else: # this should never happen, but let's try to recover if it does: mid = (hi_new + lo_new) / 2 hi_new = mid + self.rounding / 2 lo_new = mid - self.rounding / 2 lo_new = self.rounding * round( lo_new/self.rounding ) hi_new = self.rounding * round( hi_new/self.rounding ) self.values = (lo_new, hi_new) self._update_items() self.sigLevelsChanged.emit(self) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/CurvePoint.py000066400000000000000000000111211421045507400251210ustar00rootroot00000000000000import weakref from math import atan2, degrees from ..functions import clip_scalar from ..Qt import QtCore, QtWidgets from . import ArrowItem from .GraphicsObject import GraphicsObject __all__ = ['CurvePoint', 'CurveArrow'] class CurvePoint(GraphicsObject): """A GraphicsItem that sets its location to a point on a PlotCurveItem. Also rotates to be tangent to the curve. The position along the curve is a Qt property, and thus can be easily animated. Note: This class does not display anything; see CurveArrow for an applied example """ def __init__(self, curve, index=0, pos=None, rotate=True): """Position can be set either as an index referring to the sample number or the position 0.0 - 1.0 If *rotate* is True, then the item rotates to match the tangent of the curve. """ GraphicsObject.__init__(self) #QObjectWorkaround.__init__(self) self._rotate = rotate self.curve = weakref.ref(curve) self.setParentItem(curve) self.setProperty('position', 0.0) self.setProperty('index', 0) self.setFlags(self.flags() | self.GraphicsItemFlag.ItemHasNoContents) if pos is not None: self.setPos(pos) else: self.setIndex(index) def setPos(self, pos): self.setProperty('position', float(pos))## cannot use numpy types here, MUST be python float. def setIndex(self, index): self.setProperty('index', int(index)) ## cannot use numpy types here, MUST be python int. def event(self, ev): if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None: return False if ev.propertyName() == 'index': index = self.property('index') if 'QVariant' in repr(index): index = index.toInt()[0] elif ev.propertyName() == 'position': index = None else: return False (x, y) = self.curve().getData() if index is None: #print ev.propertyName(), self.property('position').toDouble()[0], self.property('position').typeName() pos = self.property('position') if 'QVariant' in repr(pos): ## need to support 2 APIs :( pos = pos.toDouble()[0] index = (len(x)-1) * clip_scalar(pos, 0.0, 1.0) if index != int(index): ## interpolate floating-point values i1 = int(index) i2 = clip_scalar(i1+1, 0, len(x)-1) s2 = index-i1 s1 = 1.0-s2 newPos = (x[i1]*s1+x[i2]*s2, y[i1]*s1+y[i2]*s2) else: index = int(index) i1 = clip_scalar(index-1, 0, len(x)-1) i2 = clip_scalar(index+1, 0, len(x)-1) newPos = (x[index], y[index]) p1 = self.parentItem().mapToScene(QtCore.QPointF(x[i1], y[i1])) p2 = self.parentItem().mapToScene(QtCore.QPointF(x[i2], y[i2])) rads = atan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians self.resetTransform() if self._rotate: self.setRotation(180 + degrees(rads)) QtWidgets.QGraphicsItem.setPos(self, *newPos) return True def boundingRect(self): return QtCore.QRectF() def paint(self, *args): pass def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1): # In Python 3, a bytes object needs to be used as a property name in # QPropertyAnimation. PyQt stopped automatically encoding a str when a # QByteArray was expected in v5.5 (see qbytearray.sip). if not isinstance(prop, bytes): prop = prop.encode('latin-1') anim = QtCore.QPropertyAnimation(self, prop) anim.setDuration(duration) anim.setStartValue(start) anim.setEndValue(end) anim.setLoopCount(loop) return anim class CurveArrow(CurvePoint): """Provides an arrow that points to any specific sample on a PlotCurveItem. Provides properties that can be animated.""" def __init__(self, curve, index=0, pos=None, **opts): CurvePoint.__init__(self, curve, index=index, pos=pos) if opts.get('pxMode', True): opts['pxMode'] = False self.setFlags(self.flags() | self.GraphicsItemFlag.ItemIgnoresTransformations) opts['angle'] = 0 self.arrow = ArrowItem.ArrowItem(**opts) self.arrow.setParentItem(self) def setStyle(self, **opts): return self.arrow.setStyle(**opts) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/DateAxisItem.py000066400000000000000000000306121421045507400253520ustar00rootroot00000000000000import sys import time from collections import OrderedDict from datetime import datetime, timedelta import numpy as np from .AxisItem import AxisItem __all__ = ['DateAxisItem'] MS_SPACING = 1/1000.0 SECOND_SPACING = 1 MINUTE_SPACING = 60 HOUR_SPACING = 3600 DAY_SPACING = 24 * HOUR_SPACING WEEK_SPACING = 7 * DAY_SPACING MONTH_SPACING = 30 * DAY_SPACING YEAR_SPACING = 365 * DAY_SPACING if sys.platform == 'win32': _epoch = datetime.utcfromtimestamp(0) def utcfromtimestamp(timestamp): return _epoch + timedelta(seconds=timestamp) else: utcfromtimestamp = datetime.utcfromtimestamp MIN_REGULAR_TIMESTAMP = (datetime(1, 1, 1) - datetime(1970,1,1)).total_seconds() MAX_REGULAR_TIMESTAMP = (datetime(9999, 1, 1) - datetime(1970,1,1)).total_seconds() SEC_PER_YEAR = 365.25*24*3600 def makeMSStepper(stepSize): def stepper(val, n): if val < MIN_REGULAR_TIMESTAMP or val > MAX_REGULAR_TIMESTAMP: return np.inf val *= 1000 f = stepSize * 1000 return (val // (n*f) + 1) * (n*f) / 1000.0 return stepper def makeSStepper(stepSize): def stepper(val, n): if val < MIN_REGULAR_TIMESTAMP or val > MAX_REGULAR_TIMESTAMP: return np.inf return (val // (n*stepSize) + 1) * (n*stepSize) return stepper def makeMStepper(stepSize): def stepper(val, n): if val < MIN_REGULAR_TIMESTAMP or val > MAX_REGULAR_TIMESTAMP: return np.inf d = utcfromtimestamp(val) base0m = (d.month + n*stepSize - 1) d = datetime(d.year + base0m // 12, base0m % 12 + 1, 1) return (d - datetime(1970, 1, 1)).total_seconds() return stepper def makeYStepper(stepSize): def stepper(val, n): if val < MIN_REGULAR_TIMESTAMP or val > MAX_REGULAR_TIMESTAMP: return np.inf d = utcfromtimestamp(val) next_year = (d.year // (n*stepSize) + 1) * (n*stepSize) if next_year > 9999: return np.inf next_date = datetime(next_year, 1, 1) return (next_date - datetime(1970, 1, 1)).total_seconds() return stepper class TickSpec: """ Specifies the properties for a set of date ticks and computes ticks within a given utc timestamp range """ def __init__(self, spacing, stepper, format, autoSkip=None): """ ============= ========================================================== Arguments spacing approximate (average) tick spacing stepper a stepper function that takes a utc time stamp and a step steps number n to compute the start of the next unit. You can use the make_X_stepper functions to create common steppers. format a strftime compatible format string which will be used to convert tick locations to date/time strings autoSkip list of step size multipliers to be applied when the tick density becomes too high. The tick spec automatically applies additional powers of 10 (10, 100, ...) to the list if necessary. Set to None to switch autoSkip off ============= ========================================================== """ self.spacing = spacing self.step = stepper self.format = format self.autoSkip = autoSkip def makeTicks(self, minVal, maxVal, minSpc): ticks = [] n = self.skipFactor(minSpc) x = self.step(minVal, n) while x <= maxVal: ticks.append(x) x = self.step(x, n) return (np.array(ticks), n) def skipFactor(self, minSpc): if self.autoSkip is None or minSpc < self.spacing: return 1 factors = np.array(self.autoSkip, dtype=np.float64) while True: for f in factors: spc = self.spacing * f if spc > minSpc: return int(f) factors *= 10 class ZoomLevel: """ Generates the ticks which appear in a specific zoom level """ def __init__(self, tickSpecs, exampleText): """ ============= ========================================================== tickSpecs a list of one or more TickSpec objects with decreasing coarseness ============= ========================================================== """ self.tickSpecs = tickSpecs self.utcOffset = 0 self.exampleText = exampleText def tickValues(self, minVal, maxVal, minSpc): # return tick values for this format in the range minVal, maxVal # the return value is a list of tuples (, [tick positions]) # minSpc indicates the minimum spacing (in seconds) between two ticks # to fullfill the maxTicksPerPt constraint of the DateAxisItem at the # current zoom level. This is used for auto skipping ticks. allTicks = [] valueSpecs = [] # back-project (minVal maxVal) to UTC, compute ticks then offset to # back to local time again utcMin = minVal - self.utcOffset utcMax = maxVal - self.utcOffset for spec in self.tickSpecs: ticks, skipFactor = spec.makeTicks(utcMin, utcMax, minSpc) # reposition tick labels to local time coordinates ticks += self.utcOffset # remove any ticks that were present in higher levels tick_list = [x for x in ticks.tolist() if x not in allTicks] allTicks.extend(tick_list) valueSpecs.append((spec.spacing, tick_list)) # if we're skipping ticks on the current level there's no point in # producing lower level ticks if skipFactor > 1: break return valueSpecs YEAR_MONTH_ZOOM_LEVEL = ZoomLevel([ TickSpec(YEAR_SPACING, makeYStepper(1), '%Y', autoSkip=[1, 5, 10, 25]), TickSpec(MONTH_SPACING, makeMStepper(1), '%b') ], "YYYY") MONTH_DAY_ZOOM_LEVEL = ZoomLevel([ TickSpec(MONTH_SPACING, makeMStepper(1), '%b'), TickSpec(DAY_SPACING, makeSStepper(DAY_SPACING), '%d', autoSkip=[1, 5]) ], "MMM") DAY_HOUR_ZOOM_LEVEL = ZoomLevel([ TickSpec(DAY_SPACING, makeSStepper(DAY_SPACING), '%a %d'), TickSpec(HOUR_SPACING, makeSStepper(HOUR_SPACING), '%H:%M', autoSkip=[1, 6]) ], "MMM 00") HOUR_MINUTE_ZOOM_LEVEL = ZoomLevel([ TickSpec(DAY_SPACING, makeSStepper(DAY_SPACING), '%a %d'), TickSpec(MINUTE_SPACING, makeSStepper(MINUTE_SPACING), '%H:%M', autoSkip=[1, 5, 15]) ], "MMM 00") HMS_ZOOM_LEVEL = ZoomLevel([ TickSpec(SECOND_SPACING, makeSStepper(SECOND_SPACING), '%H:%M:%S', autoSkip=[1, 5, 15, 30]) ], "99:99:99") MS_ZOOM_LEVEL = ZoomLevel([ TickSpec(MINUTE_SPACING, makeSStepper(MINUTE_SPACING), '%H:%M:%S'), TickSpec(MS_SPACING, makeMSStepper(MS_SPACING), '%S.%f', autoSkip=[1, 5, 10, 25]) ], "99:99:99") def getOffsetFromUtc(): """Retrieve the utc offset respecting the daylight saving time""" ts = time.localtime() if ts.tm_isdst: utc_offset = time.altzone else: utc_offset = time.timezone return utc_offset class DateAxisItem(AxisItem): """ **Bases:** :class:`AxisItem ` An AxisItem that displays dates from unix timestamps. The display format is adjusted automatically depending on the current time density (seconds/point) on the axis. For more details on changing this behaviour, see :func:`setZoomLevelForDensity() `. Can be added to an existing plot e.g. via :func:`setAxisItems({'bottom':axis}) `. """ def __init__(self, orientation='bottom', utcOffset=None, **kwargs): """ Create a new DateAxisItem. For `orientation` and `**kwargs`, see :func:`AxisItem.__init__ `. """ super(DateAxisItem, self).__init__(orientation, **kwargs) # Set the zoom level to use depending on the time density on the axis if utcOffset is None: utcOffset = getOffsetFromUtc() self.utcOffset = utcOffset self.zoomLevels = OrderedDict([ (np.inf, YEAR_MONTH_ZOOM_LEVEL), (5 * 3600*24, MONTH_DAY_ZOOM_LEVEL), (6 * 3600, DAY_HOUR_ZOOM_LEVEL), (15 * 60, HOUR_MINUTE_ZOOM_LEVEL), (30, HMS_ZOOM_LEVEL), (1, MS_ZOOM_LEVEL), ]) self.autoSIPrefix = False def tickStrings(self, values, scale, spacing): tickSpecs = self.zoomLevel.tickSpecs tickSpec = next((s for s in tickSpecs if s.spacing == spacing), None) try: dates = [utcfromtimestamp(v - self.utcOffset) for v in values] except (OverflowError, ValueError, OSError): # should not normally happen return ['%g' % ((v-self.utcOffset)//SEC_PER_YEAR + 1970) for v in values] formatStrings = [] for x in dates: try: s = x.strftime(tickSpec.format) if '%f' in tickSpec.format: # we only support ms precision s = s[:-3] elif '%Y' in tickSpec.format: s = s.lstrip('0') formatStrings.append(s) except ValueError: # Windows can't handle dates before 1970 formatStrings.append('') return formatStrings def tickValues(self, minVal, maxVal, size): density = (maxVal - minVal) / size self.setZoomLevelForDensity(density) values = self.zoomLevel.tickValues(minVal, maxVal, minSpc=self.minSpacing) return values def setZoomLevelForDensity(self, density): """ Setting `zoomLevel` and `minSpacing` based on given density of seconds per pixel The display format is adjusted automatically depending on the current time density (seconds/point) on the axis. You can customize the behaviour by overriding this function or setting a different set of zoom levels than the default one. The `zoomLevels` variable is a dictionary with the maximal distance of ticks in seconds which are allowed for each zoom level before the axis switches to the next coarser level. To customize the zoom level selection, override this function. """ padding = 10 # Size in pixels a specific tick label will take if self.orientation in ['bottom', 'top']: def sizeOf(text): return self.fontMetrics.boundingRect(text).width() + padding else: def sizeOf(text): return self.fontMetrics.boundingRect(text).height() + padding # Fallback zoom level: Years/Months self.zoomLevel = YEAR_MONTH_ZOOM_LEVEL for maximalSpacing, zoomLevel in self.zoomLevels.items(): size = sizeOf(zoomLevel.exampleText) # Test if zoom level is too fine grained if maximalSpacing/size < density: break self.zoomLevel = zoomLevel # Set up zoomLevel self.zoomLevel.utcOffset = self.utcOffset # Calculate minimal spacing of items on the axis size = sizeOf(self.zoomLevel.exampleText) self.minSpacing = density*size def linkToView(self, view): """Link this axis to a ViewBox, causing its displayed range to match the visible range of the view.""" self._linkToView_internal(view) # calls original linkToView code # Set default limits _min = MIN_REGULAR_TIMESTAMP _max = MAX_REGULAR_TIMESTAMP if self.orientation in ['right', 'left']: view.setLimits(yMin=_min, yMax=_max) else: view.setLimits(xMin=_min, xMax=_max) def generateDrawSpecs(self, p): # Get font metrics from QPainter # Not happening in "paint", as the QPainter p there is a different one from the one here, # so changing that font could cause unwanted side effects if self.style['tickFont'] is not None: p.setFont(self.style['tickFont']) self.fontMetrics = p.fontMetrics() # Get font scale factor by current window resolution return super(DateAxisItem, self).generateDrawSpecs(p) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ErrorBarItem.py000066400000000000000000000125401421045507400253660ustar00rootroot00000000000000from .. import functions as fn from .. import getConfigOption from ..Qt import QtGui from .GraphicsObject import GraphicsObject __all__ = ['ErrorBarItem'] class ErrorBarItem(GraphicsObject): def __init__(self, **opts): """ All keyword arguments are passed to setData(). """ GraphicsObject.__init__(self) self.opts = dict( x=None, y=None, height=None, width=None, top=None, bottom=None, left=None, right=None, beam=None, pen=None ) self.setVisible(False) self.setData(**opts) def setData(self, **opts): """ Update the data in the item. All arguments are optional. 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(). This method was added in version 0.9.9. For prior versions, use setOpts. """ self.opts.update(opts) self.setVisible(all(self.opts[ax] is not None for ax in ['x', 'y'])) self.path = None self.update() self.prepareGeometryChange() self.informViewBoundsChanged() def setOpts(self, **opts): # for backward compatibility self.setData(**opts) def drawPath(self): p = QtGui.QPainterPath() x, y = self.opts['x'], self.opts['y'] if x is None or y is None: self.path = p return beam = self.opts['beam'] height, top, bottom = self.opts['height'], self.opts['top'], self.opts['bottom'] if height is not None or top is not None or bottom is not None: ## draw vertical error bars if height is not None: y1 = y - height/2. y2 = y + height/2. else: if bottom is None: y1 = y else: y1 = y - bottom if top is None: y2 = y else: y2 = y + top xs = fn.interweaveArrays(x, x) y1_y2 = fn.interweaveArrays(y1, y2) verticalLines = fn.arrayToQPath(xs, y1_y2, connect="pairs") p.addPath(verticalLines) if beam is not None and beam > 0: x1 = x - beam/2. x2 = x + beam/2. x1_x2 = fn.interweaveArrays(x1, x2) if height is not None or top is not None: y2s = fn.interweaveArrays(y2, y2) topEnds = fn.arrayToQPath(x1_x2, y2s, connect="pairs") p.addPath(topEnds) if height is not None or bottom is not None: y1s = fn.interweaveArrays(y1, y1) bottomEnds = fn.arrayToQPath(x1_x2, y1s, connect="pairs") p.addPath(bottomEnds) width, right, left = self.opts['width'], self.opts['right'], self.opts['left'] if width is not None or right is not None or left is not None: ## draw vertical error bars if width is not None: x1 = x - width/2. x2 = x + width/2. else: if left is None: x1 = x else: x1 = x - left if right is None: x2 = x else: x2 = x + right ys = fn.interweaveArrays(y, y) x1_x2 = fn.interweaveArrays(x1, x2) ends = fn.arrayToQPath(x1_x2, ys, connect='pairs') p.addPath(ends) if beam is not None and beam > 0: y1 = y - beam/2. y2 = y + beam/2. y1_y2 = fn.interweaveArrays(y1, y2) if width is not None or right is not None: x2s = fn.interweaveArrays(x2, x2) rightEnds = fn.arrayToQPath(x2s, y1_y2, connect="pairs") p.addPath(rightEnds) if width is not None or left is not None: x1s = fn.interweaveArrays(x1, x1) leftEnds = fn.arrayToQPath(x1s, y1_y2, connect="pairs") p.addPath(leftEnds) self.path = p self.prepareGeometryChange() def paint(self, p, *args): if self.path is None: self.drawPath() pen = self.opts['pen'] if pen is None: pen = getConfigOption('foreground') p.setPen(fn.mkPen(pen)) p.drawPath(self.path) def boundingRect(self): if self.path is None: self.drawPath() return self.path.boundingRect() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/FillBetweenItem.py000066400000000000000000000055101421045507400260470ustar00rootroot00000000000000from .. import functions as fn from ..Qt import QtGui, QtWidgets from .PlotCurveItem import PlotCurveItem from .PlotDataItem import PlotDataItem __all__ = ['FillBetweenItem'] class FillBetweenItem(QtWidgets.QGraphicsPathItem): """ GraphicsItem filling the space between two PlotDataItems. """ def __init__(self, curve1=None, curve2=None, brush=None, pen=None): QtWidgets.QGraphicsPathItem.__init__(self) self.curves = None if curve1 is not None and curve2 is not None: self.setCurves(curve1, curve2) elif curve1 is not None or curve2 is not None: raise Exception("Must specify two curves to fill between.") if brush is not None: self.setBrush(brush) self.setPen(pen) self.updatePath() def setBrush(self, *args, **kwds): """Change the fill brush. Acceps the same arguments as pg.mkBrush()""" QtWidgets.QGraphicsPathItem.setBrush(self, fn.mkBrush(*args, **kwds)) def setPen(self, *args, **kwds): QtWidgets.QGraphicsPathItem.setPen(self, fn.mkPen(*args, **kwds)) def setCurves(self, curve1, curve2): """Set the curves to fill between. Arguments must be instances of PlotDataItem or PlotCurveItem. Added in version 0.9.9 """ if self.curves is not None: for c in self.curves: try: c.sigPlotChanged.disconnect(self.curveChanged) except (TypeError, RuntimeError): pass curves = [curve1, curve2] for c in curves: if not isinstance(c, PlotDataItem) and not isinstance(c, PlotCurveItem): raise TypeError("Curves must be PlotDataItem or PlotCurveItem.") self.curves = curves curve1.sigPlotChanged.connect(self.curveChanged) curve2.sigPlotChanged.connect(self.curveChanged) self.setZValue(min(curve1.zValue(), curve2.zValue())-1) self.curveChanged() def curveChanged(self): self.updatePath() def updatePath(self): if self.curves is None: self.setPath(QtGui.QPainterPath()) return paths = [] for c in self.curves: if isinstance(c, PlotDataItem): paths.append(c.curve.getPath()) elif isinstance(c, PlotCurveItem): paths.append(c.getPath()) path = QtGui.QPainterPath() transform = QtGui.QTransform() ps1 = paths[0].toSubpathPolygons(transform) ps2 = paths[1].toReversed().toSubpathPolygons(transform) ps2.reverse() if len(ps1) == 0 or len(ps2) == 0: self.setPath(QtGui.QPainterPath()) return for p1, p2 in zip(ps1, ps2): path.addPolygon(p1 + p2) self.setPath(path) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/GradientEditorItem.py000066400000000000000000001175011421045507400265570ustar00rootroot00000000000000import operator import weakref from collections import OrderedDict import numpy as np from .. import functions as fn from ..colormap import ColorMap from ..Qt import QtCore, QtGui, QtWidgets from ..widgets.SpinBox import SpinBox from .GraphicsWidget import GraphicsWidget translate = QtCore.QCoreApplication.translate __all__ = ['TickSliderItem', 'GradientEditorItem'] Gradients = OrderedDict([ ('thermal', {'ticks': [(0.3333, (185, 0, 0, 255)), (0.6666, (255, 220, 0, 255)), (1, (255, 255, 255, 255)), (0, (0, 0, 0, 255))], 'mode': 'rgb'}), ('flame', {'ticks': [(0.2, (7, 0, 220, 255)), (0.5, (236, 0, 134, 255)), (0.8, (246, 246, 0, 255)), (1.0, (255, 255, 255, 255)), (0.0, (0, 0, 0, 255))], 'mode': 'rgb'}), ('yellowy', {'ticks': [(0.0, (0, 0, 0, 255)), (0.2328863796753704, (32, 0, 129, 255)), (0.8362738179251941, (255, 255, 0, 255)), (0.5257586450247, (115, 15, 255, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'} ), ('bipolar', {'ticks': [(0.0, (0, 255, 255, 255)), (1.0, (255, 255, 0, 255)), (0.5, (0, 0, 0, 255)), (0.25, (0, 0, 255, 255)), (0.75, (255, 0, 0, 255))], 'mode': 'rgb'}), ('spectrum', {'ticks': [(1.0, (255, 0, 255, 255)), (0.0, (255, 0, 0, 255))], 'mode': 'hsv'}), ('cyclic', {'ticks': [(0.0, (255, 0, 4, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'hsv'}), ('greyclip', {'ticks': [(0.0, (0, 0, 0, 255)), (0.99, (255, 255, 255, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'rgb'}), ('grey', {'ticks': [(0.0, (0, 0, 0, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'}), # Perceptually uniform sequential colormaps from Matplotlib 2.0 ('viridis', {'ticks': [(0.0, (68, 1, 84, 255)), (0.25, (58, 82, 139, 255)), (0.5, (32, 144, 140, 255)), (0.75, (94, 201, 97, 255)), (1.0, (253, 231, 36, 255))], 'mode': 'rgb'}), ('inferno', {'ticks': [(0.0, (0, 0, 3, 255)), (0.25, (87, 15, 109, 255)), (0.5, (187, 55, 84, 255)), (0.75, (249, 142, 8, 255)), (1.0, (252, 254, 164, 255))], 'mode': 'rgb'}), ('plasma', {'ticks': [(0.0, (12, 7, 134, 255)), (0.25, (126, 3, 167, 255)), (0.5, (203, 71, 119, 255)), (0.75, (248, 149, 64, 255)), (1.0, (239, 248, 33, 255))], 'mode': 'rgb'}), ('magma', {'ticks': [(0.0, (0, 0, 3, 255)), (0.25, (80, 18, 123, 255)), (0.5, (182, 54, 121, 255)), (0.75, (251, 136, 97, 255)), (1.0, (251, 252, 191, 255))], 'mode': 'rgb'}), ]) def addGradientListToDocstring(): """Decorator to add list of current pre-defined gradients to the end of a function docstring.""" def dec(fn): if fn.__doc__ is not None: fn.__doc__ = fn.__doc__ + str(list(Gradients.keys())).strip('[').strip(']') return fn return dec class TickSliderItem(GraphicsWidget): ## public class """**Bases:** :class:`GraphicsWidget ` A rectangular item with tick marks along its length that can (optionally) be moved by the user.""" sigTicksChanged = QtCore.Signal(object) sigTicksChangeFinished = QtCore.Signal(object) def __init__(self, orientation='bottom', allowAdd=True, allowRemove=True, **kargs): """ ============== ================================================================================= **Arguments:** orientation Set the orientation of the gradient. Options are: 'left', 'right' 'top', and 'bottom'. allowAdd Specifies whether the user can add ticks. allowRemove Specifies whether the user can remove new ticks. tickPen Default is white. Specifies the color of the outline of the ticks. Can be any of the valid arguments for :func:`mkPen ` ============== ================================================================================= """ ## public GraphicsWidget.__init__(self) self.orientation = orientation self.length = 100 self.tickSize = 15 self.ticks = {} self.maxDim = 20 self.allowAdd = allowAdd self.allowRemove = allowRemove if 'tickPen' in kargs: self.tickPen = fn.mkPen(kargs['tickPen']) else: self.tickPen = fn.mkPen('w') self.orientations = { 'left': (90, 1, 1), 'right': (90, 1, 1), 'top': (0, 1, -1), 'bottom': (0, 1, 1) } self.setOrientation(orientation) #self.setFrameStyle(QtWidgets.QFrame.Shape.NoFrame | QtWidgets.QFrame.Shadow.Plain) #self.setBackgroundRole(QtGui.QPalette.ColorRole.NoRole) #self.setMouseTracking(True) #def boundingRect(self): #return self.mapRectFromParent(self.geometry()).normalized() #def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. #p = QtGui.QPainterPath() #p.addRect(self.boundingRect()) #return p def paint(self, p, opt, widget): #p.setPen(fn.mkPen('g', width=3)) #p.drawRect(self.boundingRect()) return def keyPressEvent(self, ev): ev.ignore() def setMaxDim(self, mx=None): if mx is None: mx = self.maxDim else: self.maxDim = mx if self.orientation in ['bottom', 'top']: self.setFixedHeight(mx) self.setMaximumWidth(16777215) else: self.setFixedWidth(mx) self.setMaximumHeight(16777215) def setOrientation(self, orientation): ## public """Set the orientation of the TickSliderItem. ============== =================================================================== **Arguments:** orientation Options are: 'left', 'right', 'top', 'bottom' The orientation option specifies which side of the slider the ticks are on, as well as whether the slider is vertical ('right' and 'left') or horizontal ('top' and 'bottom'). ============== =================================================================== """ self.orientation = orientation self.setMaxDim() self.resetTransform() ort = orientation if ort == 'top': transform = QtGui.QTransform.fromScale(1, -1) transform.translate(0, -self.height()) self.setTransform(transform) elif ort == 'left': transform = QtGui.QTransform() transform.rotate(270) transform.scale(1, -1) transform.translate(-self.height(), -self.maxDim) self.setTransform(transform) elif ort == 'right': transform = QtGui.QTransform() transform.rotate(270) transform.translate(-self.height(), 0) self.setTransform(transform) elif ort != 'bottom': raise Exception("%s is not a valid orientation. Options are 'left', 'right', 'top', and 'bottom'" %str(ort)) tr = QtGui.QTransform.fromTranslate(self.tickSize/2., 0) self.setTransform(tr, True) def addTick(self, x, color=None, movable=True, finish=True): ## public """ Add a tick to the item. ============== ================================================================== **Arguments:** x Position where tick should be added. color Color of added tick. If color is not specified, the color will be white. movable Specifies whether the tick is movable with the mouse. ============== ================================================================== """ if color is None: color = QtGui.QColor(255,255,255) tick = Tick([x*self.length, 0], color, movable, self.tickSize, pen=self.tickPen, removeAllowed=self.allowRemove) self.ticks[tick] = x tick.setParentItem(self) tick.sigMoving.connect(self.tickMoved) tick.sigMoved.connect(self.tickMoveFinished) tick.sigClicked.connect(self.tickClicked) self.sigTicksChanged.emit(self) if finish: self.sigTicksChangeFinished.emit(self) return tick def removeTick(self, tick, finish=True): ## public """ Removes the specified tick. """ del self.ticks[tick] tick.setParentItem(None) if self.scene() is not None: self.scene().removeItem(tick) self.sigTicksChanged.emit(self) if finish: self.sigTicksChangeFinished.emit(self) def tickMoved(self, tick, pos): #print "tick changed" ## Correct position of tick if it has left bounds. newX = min(max(0, pos.x()), self.length) pos.setX(newX) tick.setPos(pos) self.ticks[tick] = float(newX) / self.length self.sigTicksChanged.emit(self) def tickMoveFinished(self, tick): self.sigTicksChangeFinished.emit(self) def tickClicked(self, tick, ev): if ev.button() == QtCore.Qt.MouseButton.RightButton and tick.removeAllowed: self.removeTick(tick) def widgetLength(self): if self.orientation in ['bottom', 'top']: return self.width() else: return self.height() def resizeEvent(self, ev): wlen = max(40, self.widgetLength()) self.setLength(wlen-self.tickSize-2) self.setOrientation(self.orientation) #bounds = self.scene().itemsBoundingRect() #bounds.setLeft(min(-self.tickSize*0.5, bounds.left())) #bounds.setRight(max(self.length + self.tickSize, bounds.right())) #self.setSceneRect(bounds) #self.fitInView(bounds, QtCore.Qt.AspectRatioMode.KeepAspectRatio) def setLength(self, newLen): #private for t, x in list(self.ticks.items()): t.setPos(x * newLen + 1, t.pos().y()) self.length = float(newLen) #def mousePressEvent(self, ev): #QtWidgets.QGraphicsView.mousePressEvent(self, ev) #self.ignoreRelease = False #for i in self.items(ev.pos()): #if isinstance(i, Tick): #self.ignoreRelease = True #break ##if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks ##self.ignoreRelease = True #def mouseReleaseEvent(self, ev): #QtWidgets.QGraphicsView.mouseReleaseEvent(self, ev) #if self.ignoreRelease: #return #pos = self.mapToScene(ev.pos()) #if ev.button() == QtCore.Qt.MouseButton.LeftButton and self.allowAdd: #if pos.x() < 0 or pos.x() > self.length: #return #if pos.y() < 0 or pos.y() > self.tickSize: #return #pos.setX(min(max(pos.x(), 0), self.length)) #self.addTick(pos.x()/self.length) #elif ev.button() == QtCore.Qt.MouseButton.RightButton: #self.showMenu(ev) def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.LeftButton and self.allowAdd: pos = ev.pos() if pos.x() < 0 or pos.x() > self.length: return if pos.y() < 0 or pos.y() > self.tickSize: return pos.setX(min(max(pos.x(), 0), self.length)) self.addTick(pos.x()/self.length) elif ev.button() == QtCore.Qt.MouseButton.RightButton: self.showMenu(ev) #if ev.button() == QtCore.Qt.MouseButton.RightButton: #if self.moving: #ev.accept() #self.setPos(self.startPosition) #self.moving = False #self.sigMoving.emit(self) #self.sigMoved.emit(self) #else: #pass #self.view().tickClicked(self, ev) ###remove def hoverEvent(self, ev): if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.MouseButton.LeftButton): ev.acceptClicks(QtCore.Qt.MouseButton.RightButton) ## show ghost tick #self.currentPen = fn.mkPen(255, 0,0) #else: #self.currentPen = self.pen #self.update() def showMenu(self, ev): pass def setTickColor(self, tick, color): """Set the color of the specified tick. ============== ================================================================== **Arguments:** tick Can be either an integer corresponding to the index of the tick or a Tick object. Ex: if you had a slider with 3 ticks and you wanted to change the middle tick, the index would be 1. color The color to make the tick. Can be any argument that is valid for :func:`mkBrush ` ============== ================================================================== """ tick = self.getTick(tick) tick.color = color tick.update() #tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color))) self.sigTicksChanged.emit(self) self.sigTicksChangeFinished.emit(self) def setTickValue(self, tick, val): ## public """ Set the position (along the slider) of the tick. ============== ================================================================== **Arguments:** tick Can be either an integer corresponding to the index of the tick or a Tick object. Ex: if you had a slider with 3 ticks and you wanted to change the middle tick, the index would be 1. val The desired position of the tick. If val is < 0, position will be set to 0. If val is > 1, position will be set to 1. ============== ================================================================== """ tick = self.getTick(tick) val = min(max(0.0, val), 1.0) x = val * self.length pos = tick.pos() pos.setX(x) tick.setPos(pos) self.ticks[tick] = val self.update() self.sigTicksChanged.emit(self) self.sigTicksChangeFinished.emit(self) def tickValue(self, tick): ## public """Return the value (from 0.0 to 1.0) of the specified tick. ============== ================================================================== **Arguments:** tick Can be either an integer corresponding to the index of the tick or a Tick object. Ex: if you had a slider with 3 ticks and you wanted the value of the middle tick, the index would be 1. ============== ================================================================== """ tick = self.getTick(tick) return self.ticks[tick] def getTick(self, tick): ## public """Return the Tick object at the specified index. ============== ================================================================== **Arguments:** tick An integer corresponding to the index of the desired tick. If the argument is not an integer it will be returned unchanged. ============== ================================================================== """ if type(tick) is int: tick = self.listTicks()[tick][0] return tick #def mouseMoveEvent(self, ev): #QtWidgets.QGraphicsView.mouseMoveEvent(self, ev) def listTicks(self): """Return a sorted list of all the Tick objects on the slider.""" ## public ticks = sorted(self.ticks.items(), key=operator.itemgetter(1)) return ticks class GradientEditorItem(TickSliderItem): """ **Bases:** :class:`TickSliderItem ` An item that can be used to define a color gradient. Implements common pre-defined gradients that are customizable by the user. :class: `GradientWidget ` provides a widget with a GradientEditorItem that can be added to a GUI. ================================ =========================================================== **Signals:** sigGradientChanged(self) Signal is emitted anytime the gradient changes. The signal is emitted in real time while ticks are being dragged or colors are being changed. sigGradientChangeFinished(self) Signal is emitted when the gradient is finished changing. ================================ =========================================================== """ sigGradientChanged = QtCore.Signal(object) sigGradientChangeFinished = QtCore.Signal(object) def __init__(self, *args, **kargs): """ Create a new GradientEditorItem. All arguments are passed to :func:`TickSliderItem.__init__ ` =============== ================================================================================= **Arguments:** orientation Set the orientation of the gradient. Options are: 'left', 'right' 'top', and 'bottom'. allowAdd Default is True. Specifies whether ticks can be added to the item. tickPen Default is white. Specifies the color of the outline of the ticks. Can be any of the valid arguments for :func:`mkPen ` =============== ================================================================================= """ self.currentTick = None self.currentTickColor = None self.rectSize = 15 self.gradRect = QtWidgets.QGraphicsRectItem(QtCore.QRectF(0, self.rectSize, 100, self.rectSize)) self.backgroundRect = QtWidgets.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize)) self.backgroundRect.setBrush(QtGui.QBrush(QtCore.Qt.BrushStyle.DiagCrossPattern)) self.colorMode = 'rgb' TickSliderItem.__init__(self, *args, **kargs) self.colorDialog = QtWidgets.QColorDialog() self.colorDialog.setOption(QtWidgets.QColorDialog.ColorDialogOption.ShowAlphaChannel, True) self.colorDialog.setOption(QtWidgets.QColorDialog.ColorDialogOption.DontUseNativeDialog, True) self.colorDialog.currentColorChanged.connect(self.currentColorChanged) self.colorDialog.rejected.connect(self.currentColorRejected) self.colorDialog.accepted.connect(self.currentColorAccepted) self.backgroundRect.setParentItem(self) self.gradRect.setParentItem(self) self.setMaxDim(self.rectSize + self.tickSize) self.rgbAction = QtGui.QAction(translate("GradiantEditorItem", 'RGB'), self) self.rgbAction.setCheckable(True) self.rgbAction.triggered.connect(self._setColorModeToRGB) self.hsvAction = QtGui.QAction(translate("GradiantEditorItem", 'HSV'), self) self.hsvAction.setCheckable(True) self.hsvAction.triggered.connect(self._setColorModeToHSV) self.menu = QtWidgets.QMenu() ## build context menu of gradients l = self.length self.length = 100 global Gradients for g in Gradients: px = QtGui.QPixmap(100, 15) p = QtGui.QPainter(px) self.restoreState(Gradients[g]) grad = self.getGradient() brush = QtGui.QBrush(grad) p.fillRect(QtCore.QRect(0, 0, 100, 15), brush) p.end() label = QtWidgets.QLabel() label.setPixmap(px) label.setContentsMargins(1, 1, 1, 1) labelName = QtWidgets.QLabel(g) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(labelName) hbox.addWidget(label) widget = QtWidgets.QWidget() widget.setLayout(hbox) act = QtWidgets.QWidgetAction(self) act.setDefaultWidget(widget) act.triggered.connect(self.contextMenuClicked) act.name = g self.menu.addAction(act) self.length = l self.menu.addSeparator() self.menu.addAction(self.rgbAction) self.menu.addAction(self.hsvAction) for t in list(self.ticks.keys()): self.removeTick(t) self.addTick(0, QtGui.QColor(0,0,0), True) self.addTick(1, QtGui.QColor(255,0,0), True) self.setColorMode('rgb') self.updateGradient() self.linkedGradients = {} self.sigTicksChanged.connect(self._updateGradientIgnoreArgs) self.sigTicksChangeFinished.connect(self.sigGradientChangeFinished.emit) def showTicks(self, show=True): for tick in self.ticks.keys(): if show: tick.show() orig = getattr(self, '_allowAdd_backup', None) if orig: self.allowAdd = orig else: self._allowAdd_backup = self.allowAdd self.allowAdd = False #block tick creation tick.hide() def setOrientation(self, orientation): ## public """ Set the orientation of the GradientEditorItem. ============== =================================================================== **Arguments:** orientation Options are: 'left', 'right', 'top', 'bottom' The orientation option specifies which side of the gradient the ticks are on, as well as whether the gradient is vertical ('right' and 'left') or horizontal ('top' and 'bottom'). ============== =================================================================== """ TickSliderItem.setOrientation(self, orientation) tr = QtGui.QTransform.fromTranslate(0, self.rectSize) self.setTransform(tr, True) def showMenu(self, ev): #private self.menu.popup(ev.screenPos().toQPoint()) def contextMenuClicked(self, b=None): #private #global Gradients act = self.sender() self.loadPreset(act.name) @addGradientListToDocstring() def loadPreset(self, name): """ Load a predefined gradient. Currently defined gradients are: """## TODO: provide image with names of defined gradients #global Gradients self.restoreState(Gradients[name]) def setColorMode(self, cm): """ Set the color mode for the gradient. Options are: 'hsv', 'rgb' """ ## public if cm not in ['rgb', 'hsv']: raise Exception("Unknown color mode %s. Options are 'rgb' and 'hsv'." % str(cm)) try: self.rgbAction.blockSignals(True) self.hsvAction.blockSignals(True) self.rgbAction.setChecked(cm == 'rgb') self.hsvAction.setChecked(cm == 'hsv') finally: self.rgbAction.blockSignals(False) self.hsvAction.blockSignals(False) self.colorMode = cm self.sigTicksChanged.emit(self) self.sigGradientChangeFinished.emit(self) def _setColorModeToRGB(self): self.setColorMode("rgb") def _setColorModeToHSV(self): self.setColorMode("hsv") def colorMap(self): """Return a ColorMap object representing the current state of the editor.""" if self.colorMode == 'hsv': raise NotImplementedError('hsv colormaps not yet supported') pos = [] color = [] for t,x in self.listTicks(): pos.append(x) c = t.color color.append(c.getRgb()) return ColorMap(np.array(pos), np.array(color, dtype=np.ubyte)) def updateGradient(self): #private self.gradient = self.getGradient() self.gradRect.setBrush(QtGui.QBrush(self.gradient)) self.sigGradientChanged.emit(self) def _updateGradientIgnoreArgs(self, *args, **kwargs): self.updateGradient() def setLength(self, newLen): #private (but maybe public) TickSliderItem.setLength(self, newLen) self.backgroundRect.setRect(1, -self.rectSize, newLen, self.rectSize) self.gradRect.setRect(1, -self.rectSize, newLen, self.rectSize) self.sigTicksChanged.emit(self) def currentColorChanged(self, color): #private if color.isValid() and self.currentTick is not None: self.setTickColor(self.currentTick, color) def currentColorRejected(self): #private self.setTickColor(self.currentTick, self.currentTickColor) def currentColorAccepted(self): self.sigGradientChangeFinished.emit(self) def tickClicked(self, tick, ev): #private if ev.button() == QtCore.Qt.MouseButton.LeftButton: self.raiseColorDialog(tick) elif ev.button() == QtCore.Qt.MouseButton.RightButton: self.raiseTickContextMenu(tick, ev) def raiseColorDialog(self, tick): if not tick.colorChangeAllowed: return self.currentTick = tick self.currentTickColor = tick.color self.colorDialog.setCurrentColor(tick.color) self.colorDialog.open() def raiseTickContextMenu(self, tick, ev): self.tickMenu = TickMenu(tick, self) self.tickMenu.popup(ev.screenPos().toQPoint()) def tickMoveFinished(self, tick): self.sigGradientChangeFinished.emit(self) def getGradient(self): """Return a QLinearGradient object.""" g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0)) if self.colorMode == 'rgb': ticks = self.listTicks() g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop ticks = self.listTicks() stops = [] stops.append((ticks[0][1], ticks[0][0].color)) for i in range(1,len(ticks)): x1 = ticks[i-1][1] x2 = ticks[i][1] dx = (x2-x1) / 10. for j in range(1,10): x = x1 + dx*j stops.append((x, self.getColor(x))) stops.append((x2, self.getColor(x2))) g.setStops(stops) return g def getColor(self, x, toQColor=True): """ Return a color for a given value. ============== ================================================================== **Arguments:** x Value (position on gradient) of requested color. toQColor If true, returns a QColor object, else returns a (r,g,b,a) tuple. ============== ================================================================== """ ticks = self.listTicks() if x <= ticks[0][1]: c = ticks[0][0].color if toQColor: return QtGui.QColor(c) # always copy colors before handing them out else: 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.getRgb() x2 = ticks[0][1] for i in range(1,len(ticks)): x1 = x2 x2 = ticks[i][1] if x1 <= x and x2 >= x: break dx = (x2-x1) if dx == 0: f = 0. else: f = (x-x1) / dx c1 = ticks[i-1][0].color c2 = ticks[i][0].color if self.colorMode == 'rgb': r = c1.red() * (1.-f) + c2.red() * f g = c1.green() * (1.-f) + c2.green() * f b = c1.blue() * (1.-f) + c2.blue() * f a = c1.alpha() * (1.-f) + c2.alpha() * f if toQColor: return QtGui.QColor(int(r), int(g), int(b), int(a)) else: return (r,g,b,a) elif self.colorMode == 'hsv': h1,s1,v1,_ = c1.getHsv() h2,s2,v2,_ = c2.getHsv() h = h1 * (1.-f) + h2 * f s = s1 * (1.-f) + s2 * f v = v1 * (1.-f) + v2 * f c = QtGui.QColor.fromHsv(int(h), int(s), int(v)) if toQColor: return c else: return c.getRgb() def getLookupTable(self, nPts, alpha=None): """ Return an RGB(A) lookup table (ndarray). ============== ============================================================================ **Arguments:** nPts The number of points in the returned lookup table. alpha True, False, or None - Specifies whether or not alpha values are included in the table.If alpha is None, alpha will be automatically determined. ============== ============================================================================ """ if alpha is None: alpha = self.usesAlpha() if alpha: table = np.empty((nPts,4), dtype=np.ubyte) else: table = np.empty((nPts,3), dtype=np.ubyte) for i in range(nPts): x = float(i)/(nPts-1) color = self.getColor(x, toQColor=False) table[i] = color[:table.shape[1]] return table def usesAlpha(self): """Return True if any ticks have an alpha < 255""" ticks = self.listTicks() for t in ticks: if t[0].color.alpha() < 255: return True return False def isLookupTrivial(self): """Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0""" ticks = self.listTicks() if len(ticks) != 2: return False if ticks[0][1] != 0.0 or ticks[1][1] != 1.0: return False 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 def addTick(self, x, color=None, movable=True, finish=True): """ Add a tick to the gradient. Return the tick. ============== ================================================================== **Arguments:** x Position where tick should be added. color Color of added tick. If color is not specified, the color will be the color of the gradient at the specified position. movable Specifies whether the tick is movable with the mouse. ============== ================================================================== """ if color is None: color = self.getColor(x) t = TickSliderItem.addTick(self, x, color=color, movable=movable, finish=finish) t.colorChangeAllowed = True return t def saveState(self): """ Return a dictionary with parameters for rebuilding the gradient. Keys will include: - 'mode': hsv or rgb - 'ticks': a list of tuples (pos, (r,g,b,a)) """ ## public ticks = [] for t in self.ticks: c = t.color ticks.append((self.ticks[t], c.getRgb())) state = {'mode': self.colorMode, 'ticks': ticks, 'ticksVisible': next(iter(self.ticks)).isVisible()} return state def restoreState(self, state): """ Restore the gradient specified in state. ============== ==================================================================== **Arguments:** state A dictionary with same structure as those returned by :func:`saveState ` Keys must include: - 'mode': hsv or rgb - 'ticks': a list of tuples (pos, (r,g,b,a)) ============== ==================================================================== """ ## public # Mass edit ticks without graphics update signalsBlocked = self.blockSignals(True) self.setColorMode(state['mode']) for t in list(self.ticks.keys()): self.removeTick(t, finish=False) for t in state['ticks']: c = QtGui.QColor(*t[1]) self.addTick(t[0], c, finish=False) self.showTicks( state.get('ticksVisible', next(iter(self.ticks)).isVisible()) ) # Close with graphics update self.blockSignals(signalsBlocked) self.sigTicksChanged.emit(self) self.sigGradientChangeFinished.emit(self) def setColorMap(self, cm): # Mass edit ticks without graphics update signalsBlocked = self.blockSignals(True) self.setColorMode('rgb') for t in list(self.ticks.keys()): self.removeTick(t, finish=False) colors = cm.getColors(mode='qcolor') for i in range(len(cm.pos)): x = cm.pos[i] c = colors[i] self.addTick(x, c, finish=False) # Close with graphics update self.blockSignals(signalsBlocked) self.sigTicksChanged.emit(self) self.sigGradientChangeFinished.emit(self) def linkGradient(self, slaveGradient, connect=True): if connect: fn = lambda g, slave=slaveGradient:slave.restoreState( g.saveState()) self.linkedGradients[id(slaveGradient)] = fn self.sigGradientChanged.connect(fn) self.sigGradientChanged.emit(self) else: fn = self.linkedGradients.get(id(slaveGradient), None) if fn: self.sigGradientChanged.disconnect(fn) class Tick(QtWidgets.QGraphicsWidget): ## NOTE: Making this a subclass of GraphicsObject instead results in ## activating this bug: https://bugreports.qt-project.org/browse/PYSIDE-86 ## private class # When making Tick a subclass of QtWidgets.QGraphicsObject as origin, # ..GraphicsScene.items(self, *args) will get Tick object as a # class of QtGui.QMultimediaWidgets.QGraphicsVideoItem in python2.7-PyQt5(5.4.0) sigMoving = QtCore.Signal(object, object) sigMoved = QtCore.Signal(object) sigClicked = QtCore.Signal(object, object) def __init__(self, pos, color, movable=True, scale=10, pen='w', removeAllowed=True): self.movable = movable self.moving = False self.scale = scale self.color = color self.pen = fn.mkPen(pen) self.hoverPen = fn.mkPen(255,255,0) self.currentPen = self.pen self.removeAllowed = removeAllowed self.pg = QtGui.QPainterPath(QtCore.QPointF(0,0)) self.pg.lineTo(QtCore.QPointF(-scale/3**0.5, scale)) self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale)) self.pg.closeSubpath() QtWidgets.QGraphicsWidget.__init__(self) self.setPos(pos[0], pos[1]) if self.movable: self.setZValue(1) else: self.setZValue(0) def boundingRect(self): return self.pg.boundingRect() def shape(self): return self.pg def paint(self, p, *args): p.setRenderHints(QtGui.QPainter.RenderHint.Antialiasing) p.fillPath(self.pg, fn.mkBrush(self.color)) p.setPen(self.currentPen) p.drawPath(self.pg) def mouseDragEvent(self, ev): if self.movable and ev.button() == QtCore.Qt.MouseButton.LeftButton: if ev.isStart(): self.moving = True self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) self.startPosition = self.pos() ev.accept() if not self.moving: return newPos = self.cursorOffset + self.mapToParent(ev.pos()) newPos.setY(self.pos().y()) self.setPos(newPos) self.sigMoving.emit(self, newPos) if ev.isFinish(): self.moving = False self.sigMoved.emit(self) def mouseClickEvent(self, ev): ev.accept() if ev.button() == QtCore.Qt.MouseButton.RightButton and self.moving: self.setPos(self.startPosition) self.moving = False self.sigMoving.emit(self, self.startPosition) self.sigMoved.emit(self) else: self.sigClicked.emit(self, ev) def hoverEvent(self, ev): if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton): ev.acceptClicks(QtCore.Qt.MouseButton.LeftButton) ev.acceptClicks(QtCore.Qt.MouseButton.RightButton) self.currentPen = self.hoverPen else: self.currentPen = self.pen self.update() class TickMenu(QtWidgets.QMenu): def __init__(self, tick, sliderItem): QtWidgets.QMenu.__init__(self) self.tick = weakref.ref(tick) self.sliderItem = weakref.ref(sliderItem) self.removeAct = self.addAction(translate("GradientEditorItem", "Remove Tick"), lambda: self.sliderItem().removeTick(tick)) if (not self.tick().removeAllowed) or len(self.sliderItem().ticks) < 3: self.removeAct.setEnabled(False) positionMenu = self.addMenu(translate("GradientEditorItem", "Set Position")) w = QtWidgets.QWidget() l = QtWidgets.QGridLayout() w.setLayout(l) value = sliderItem.tickValue(tick) self.fracPosSpin = SpinBox() self.fracPosSpin.setOpts(value=value, bounds=(0.0, 1.0), step=0.01, decimals=2) #self.dataPosSpin = SpinBox(value=dataVal) #self.dataPosSpin.setOpts(decimals=3, siPrefix=True) l.addWidget(QtWidgets.QLabel(f"{translate('GradiantEditorItem', 'Position')}:"), 0,0) l.addWidget(self.fracPosSpin, 0, 1) #l.addWidget(QtWidgets.QLabel("Position (data units):"), 1, 0) #l.addWidget(self.dataPosSpin, 1,1) #if self.sliderItem().dataParent is None: # self.dataPosSpin.setEnabled(False) a = QtWidgets.QWidgetAction(self) a.setDefaultWidget(w) positionMenu.addAction(a) self.fracPosSpin.sigValueChanging.connect(self.fractionalValueChanged) #self.dataPosSpin.valueChanged.connect(self.dataValueChanged) colorAct = self.addAction(translate("Context Menu", "Set Color"), lambda: self.sliderItem().raiseColorDialog(self.tick())) if not self.tick().colorChangeAllowed: colorAct.setEnabled(False) def fractionalValueChanged(self, x): self.sliderItem().setTickValue(self.tick(), self.fracPosSpin.value()) #if self.sliderItem().dataParent is not None: # self.dataPosSpin.blockSignals(True) # self.dataPosSpin.setValue(self.sliderItem().tickDataValue(self.tick())) # self.dataPosSpin.blockSignals(False) #def dataValueChanged(self, val): # self.sliderItem().setTickValue(self.tick(), val, dataUnits=True) # self.fracPosSpin.blockSignals(True) # self.fracPosSpin.setValue(self.sliderItem().tickValue(self.tick())) # self.fracPosSpin.blockSignals(False) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/GradientLegend.py000066400000000000000000000114601421045507400257050ustar00rootroot00000000000000from .. import functions as fn from ..Qt import QtCore, QtGui from .UIGraphicsItem import * __all__ = ['GradientLegend'] class GradientLegend(UIGraphicsItem): """ Draws a color gradient rectangle along with text labels denoting the value at specific points along the gradient. """ def __init__(self, size, offset): self.size = size self.offset = offset UIGraphicsItem.__init__(self) self.setAcceptedMouseButtons(QtCore.Qt.MouseButton.NoButton) self.brush = QtGui.QBrush(QtGui.QColor(255,255,255,100)) # background color self.pen = QtGui.QPen(QtGui.QColor(0,0,0)) self.textPen = QtGui.QPen(QtGui.QColor(0,0,0)) self.labels = {'max': 1, 'min': 0} self.gradient = QtGui.QLinearGradient() self.gradient.setColorAt(0, QtGui.QColor(0,0,0)) self.gradient.setColorAt(1, QtGui.QColor(255,0,0)) self.setZValue(100) # draw on top of ordinary plots def setGradient(self, g): self.gradient = g self.update() def setColorMap(self, colormap): """ Set displayed gradient from a :class:`~pyqtgraph.ColorMap` object. """ self.gradient = colormap.getGradient() def setIntColorScale(self, minVal, maxVal, *args, **kargs): colors = [fn.intColor(i, maxVal-minVal, *args, **kargs) for i in range(minVal, maxVal)] g = QtGui.QLinearGradient() for i in range(len(colors)): x = float(i)/len(colors) g.setColorAt(x, colors[i]) self.setGradient(g) if 'labels' not in kargs: self.setLabels({str(minVal): 0, str(maxVal): 1}) else: self.setLabels({kargs['labels'][0]:0, kargs['labels'][1]:1}) def setLabels(self, l): """Defines labels to appear next to the color scale. Accepts a dict of {text: value} pairs""" self.labels = l self.update() def paint(self, p, opt, widget): UIGraphicsItem.paint(self, p, opt, widget) view = self.getViewBox() if view is None: return p.save() # save painter state before we change transformation trans = view.sceneTransform() p.setTransform( trans ) # draw in ViewBox pixel coordinates rect = view.rect() ## determine max width of all labels labelWidth = 0 labelHeight = 0 for k in self.labels: b = p.boundingRect(QtCore.QRectF(0, 0, 0, 0), QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter, str(k)) labelWidth = max(labelWidth, b.width()) labelHeight = max(labelHeight, b.height()) textPadding = 2 # in px xR = rect.right() xL = rect.left() yT = rect.top() yB = rect.bottom() # coordinates describe edges of text and bar, additional margins will be added for background if self.offset[0] < 0: x3 = xR + self.offset[0] # right edge from right edge of view, offset is negative! x2 = x3 - labelWidth - 2*textPadding # right side of color bar x1 = x2 - self.size[0] # left side of color bar else: x1 = xL + self.offset[0] # left edge from left edge of view x2 = x1 + self.size[0] x3 = x2 + labelWidth + 2*textPadding # leave room for 2x textpadding between bar and text if self.offset[1] < 0: y2 = yB + self.offset[1] # bottom edge from bottom of view, offset is negative! y1 = y2 - self.size[1] else: y1 = yT + self.offset[1] # top edge from top of view y2 = y1 + self.size[1] self.b = [x1,x2,x3,y1,y2,labelWidth] ## Draw background p.setPen(self.pen) p.setBrush(self.brush) # background color rect = QtCore.QRectF( QtCore.QPointF(x1 - textPadding, y1-labelHeight/2 - textPadding), # extra left/top padding QtCore.QPointF(x3 + textPadding, y2+labelHeight/2 + textPadding) # extra bottom/right padding ) p.drawRect(rect) ## Draw color bar self.gradient.setStart(0, y2) self.gradient.setFinalStop(0, y1) p.setBrush(self.gradient) rect = QtCore.QRectF( QtCore.QPointF(x1, y1), QtCore.QPointF(x2, y2) ) p.drawRect(rect) ## draw labels p.setPen(self.textPen) tx = x2 + 2 * textPadding # margin between bar and text lh = labelHeight lw = labelWidth for k in self.labels: y = y2 - self.labels[k] * (y2-y1) p.drawText(QtCore.QRectF(tx, y - lh/2, lw, lh), QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter, str(k)) p.restore() # restore QPainter transform to original state pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/GraphItem.py000066400000000000000000000132101421045507400247040ustar00rootroot00000000000000import numpy as np from .. import functions as fn from .. import getConfigOption from ..Qt import QtCore, QtGui from .GraphicsObject import GraphicsObject from .ScatterPlotItem import ScatterPlotItem __all__ = ['GraphItem'] class GraphItem(GraphicsObject): """A GraphItem displays graph information as a set of nodes connected by lines (as in 'graph theory', not 'graphics'). Useful for drawing networks, trees, etc. """ def __init__(self, **kwds): GraphicsObject.__init__(self) self.scatter = ScatterPlotItem() self.scatter.setParentItem(self) self.adjacency = None self.pos = None self.picture = None self.pen = 'default' self.setData(**kwds) def setData(self, **kwds): """ Change the data displayed by the graph. ============== ======================================================================= **Arguments:** pos (N,2) array of the positions of each node in the graph. adj (M,2) array of connection data. Each row contains indexes of two nodes that are connected or None to hide lines 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. symbolPen The pen(s) used for drawing nodes. symbolBrush The brush(es) used for drawing nodes. ``**opts`` All other keyword arguments are given to :func:`ScatterPlotItem.setData() ` to affect the appearance of nodes (symbol, size, brush, etc.) ============== ======================================================================= """ if 'adj' in kwds: self.adjacency = kwds.pop('adj') if hasattr(self.adjacency, '__len__') and len(self.adjacency) == 0: self.adjacency = None elif self.adjacency is not None and self.adjacency.dtype.kind not in 'iu': raise Exception("adjacency must be None or an array of either int or unsigned type.") self._update() if 'pos' in kwds: self.pos = kwds['pos'] self._update() if 'pen' in kwds: self.setPen(kwds.pop('pen')) self._update() if 'symbolPen' in kwds: kwds['pen'] = kwds.pop('symbolPen') if 'symbolBrush' in kwds: kwds['brush'] = kwds.pop('symbolBrush') self.scatter.setData(**kwds) self.informViewBoundsChanged() def _update(self): self.picture = None self.prepareGeometryChange() self.update() def setPen(self, *args, **kwargs): """ 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 `. * 'default' to use the default foreground color. """ if len(args) == 1 and len(kwargs) == 0: self.pen = args[0] else: self.pen = fn.mkPen(*args, **kwargs) self.picture = None self.update() def generatePicture(self): self.picture = QtGui.QPicture() if self.pen is None or self.pos is None or self.adjacency is None: return p = QtGui.QPainter(self.picture) try: pts = self.pos[self.adjacency] pen = self.pen if isinstance(pen, np.ndarray): lastPen = None for i in range(pts.shape[0]): pen = self.pen[i] if np.any(pen != lastPen): lastPen = pen if pen.dtype.fields is None: p.setPen(fn.mkPen(color=(pen[0], pen[1], pen[2], pen[3]), width=1)) else: p.setPen(fn.mkPen(color=(pen['red'], pen['green'], pen['blue'], pen['alpha']), width=pen['width'])) p.drawLine(QtCore.QPointF(*pts[i][0]), QtCore.QPointF(*pts[i][1])) else: if pen == 'default': pen = getConfigOption('foreground') p.setPen(fn.mkPen(pen)) pts = pts.reshape((pts.shape[0]*pts.shape[1], pts.shape[2])) path = fn.arrayToQPath(x=pts[:,0], y=pts[:,1], connect='pairs') p.drawPath(path) finally: p.end() def paint(self, p, *args): if self.picture is None: self.generatePicture() if getConfigOption('antialias') is True: p.setRenderHint(p.RenderHint.Antialiasing) self.picture.play(p) def boundingRect(self): return self.scatter.boundingRect() def dataBounds(self, *args, **kwds): return self.scatter.dataBounds(*args, **kwds) def pixelPadding(self): return self.scatter.pixelPadding() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/GraphicsItem.py000066400000000000000000000574721421045507400254250ustar00rootroot00000000000000import operator import warnings import weakref from collections import OrderedDict from functools import reduce from math import hypot from .. import functions as fn from ..GraphicsScene import GraphicsScene from ..Point import Point from ..Qt import QtCore, QtWidgets, isQObjectAlive __all__ = ['GraphicsItem'] # Recipe from https://docs.python.org/3.8/library/collections.html#collections.OrderedDict # slightly adapted for Python 3.7 compatibility class LRU(OrderedDict): 'Limit size, evicting the least recently looked-up key when full' def __init__(self, maxsize=128, *args, **kwds): self.maxsize = maxsize super().__init__(*args, **kwds) def __getitem__(self, key): value = super().__getitem__(key) self.move_to_end(key) return value def __setitem__(self, key, value): if key in self: self.move_to_end(key) super().__setitem__(key, value) if len(self) > self.maxsize: oldest = next(iter(self)) del self[oldest] class GraphicsItem(object): """ **Bases:** :class:`object` Abstract class providing useful methods to GraphicsObject and GraphicsWidget. (This is required because we cannot have multiple inheritance with QObject subclasses.) A note about Qt's GraphicsView framework: The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task. """ _pixelVectorGlobalCache = LRU(100) def __init__(self, register=None): if not hasattr(self, '_qtBaseClass'): for b in self.__class__.__bases__: if issubclass(b, QtWidgets.QGraphicsItem): self.__class__._qtBaseClass = b break if not hasattr(self, '_qtBaseClass'): raise Exception('Could not determine Qt base class for GraphicsItem: %s' % str(self)) self._pixelVectorCache = [None, None] self._viewWidget = None self._viewBox = None self._connectedView = None self._exportOpts = False ## If False, not currently exporting. Otherwise, contains dict of export options. self._cachedView = None if register is not None and register: warnings.warn( "'register' argument is deprecated and does nothing, will be removed in 0.13", DeprecationWarning, stacklevel=2 ) def getViewWidget(self): """ Return the view widget for this item. If the scene has multiple views, only the first view is returned. The return value is cached; clear the cached value with forgetViewWidget(). If the view has been deleted by Qt, return None. """ if self._viewWidget is None: scene = self.scene() if scene is None: return None views = scene.views() if len(views) < 1: return None self._viewWidget = weakref.ref(self.scene().views()[0]) v = self._viewWidget() if v is not None and not isQObjectAlive(v): return None return v def forgetViewWidget(self): self._viewWidget = None def getViewBox(self): """ Return the first ViewBox or GraphicsView which bounds this item's visible space. If this item is not contained within a ViewBox, then the GraphicsView is returned. If the item is contained inside nested ViewBoxes, then the inner-most ViewBox is returned. The result is cached; clear the cache with forgetViewBox() """ if self._viewBox is None: p = self while True: try: p = p.parentItem() except RuntimeError: ## sometimes happens as items are being removed from a scene and collected. return None if p is None: vb = self.getViewWidget() if vb is None: return None else: self._viewBox = weakref.ref(vb) break if hasattr(p, 'implements') and p.implements('ViewBox'): self._viewBox = weakref.ref(p) break return self._viewBox() ## If we made it this far, _viewBox is definitely not None def forgetViewBox(self): self._viewBox = None def deviceTransform(self, viewportTransform=None): """ Return the transform that converts local item coordinates to device coordinates (usually pixels). Extends deviceTransform to automatically determine the viewportTransform. """ if viewportTransform is None: view = self.getViewWidget() if view is None: return None viewportTransform = view.viewportTransform() dt = self._qtBaseClass.deviceTransform(self, viewportTransform) #xmag = abs(dt.m11())+abs(dt.m12()) #ymag = abs(dt.m21())+abs(dt.m22()) #if xmag * ymag == 0: if dt.determinant() == 0: ## occurs when deviceTransform is invalid because widget has not been displayed return None else: return dt def viewTransform(self): """Return the transform that maps from local coordinates to the item's ViewBox coordinates If there is no ViewBox, return the scene transform. Returns None if the item does not have a view.""" view = self.getViewBox() if view is None: return None if hasattr(view, 'implements') and view.implements('ViewBox'): tr = self.itemTransform(view.innerSceneItem()) if isinstance(tr, tuple): tr = tr[0] ## difference between pyside and pyqt return tr else: return self.sceneTransform() #return self.deviceTransform(view.viewportTransform()) def getBoundingParents(self): """Return a list of parents to this item that have child clipping enabled.""" p = self parents = [] while True: p = p.parentItem() if p is None: break if p.flags() & self.GraphicsItemFlag.ItemClipsChildrenToShape: parents.append(p) return parents def viewRect(self): """Return the visible bounds of this item's ViewBox or GraphicsWidget, in the local coordinate system of the item.""" if self._cachedView is not None: return self._cachedView # Note that in cases of early returns here, the view cache stays empty (None). view = self.getViewBox() if view is None: return None bounds = self.mapRectFromView(view.viewRect()) if bounds is None: return None bounds = bounds.normalized() self._cachedView = bounds ## nah. #for p in self.getBoundingParents(): #bounds &= self.mapRectFromScene(p.sceneBoundingRect()) return bounds def pixelVectors(self, direction=None): """Return vectors in local coordinates representing the width and height of a view pixel. If direction is specified, then return vectors parallel and orthogonal to it. Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed) or if pixel size is below floating-point precision limit. """ ## This is an expensive function that gets called very frequently. ## We have two levels of cache to try speeding things up. dt = self.deviceTransform() if dt is None: return None, None ## Ignore translation. If the translation is much larger than the scale ## (such as when looking at unix timestamps), we can get floating-point errors. dt.setMatrix(dt.m11(), dt.m12(), 0, dt.m21(), dt.m22(), 0, 0, 0, 1) if direction is None: direction = QtCore.QPointF(1, 0) elif direction.manhattanLength() == 0: raise Exception("Cannot compute pixel length for 0-length vector.") key = (dt.m11(), dt.m21(), dt.m12(), dt.m22(), direction.x(), direction.y()) ## check local cache if key == self._pixelVectorCache[0]: return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy* ## check global cache pv = self._pixelVectorGlobalCache.get(key, None) if pv is not None: self._pixelVectorCache = [key, pv] return tuple(map(Point,pv)) ## return a *copy* ## attempt to re-scale direction vector to fit within the precision of the coordinate system ## Here's the problem: we need to map the vector 'direction' from the item to the device, via transform 'dt'. ## In some extreme cases, this mapping can fail unless the length of 'direction' is cleverly chosen. ## Example: ## dt = [ 1, 0, 2 ## 0, 2, 1e20 ## 0, 0, 1 ] ## Then we map the origin (0,0) and direction (0,1) and get: ## o' = 2,1e20 ## d' = 2,1e20 <-- should be 1e20+2, but this can't be represented with a 32-bit float ## ## |o' - d'| == 0 <-- this is the problem. ## Perhaps the easiest solution is to exclude the transformation column from dt. Does this cause any other problems? #if direction.x() == 0: #r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22())) ##r = 1.0/(abs(dt.m12()) + abs(dt.m22())) #elif direction.y() == 0: #r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21())) ##r = 1.0/(abs(dt.m11()) + abs(dt.m21())) #else: #r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5 #if r == 0: #r = 1. ## shouldn't need to do this; probably means the math above is wrong? #directionr = direction * r directionr = direction ## map direction vector onto device #viewDir = Point(dt.map(directionr) - dt.map(Point(0,0))) #mdirection = dt.map(directionr) dirLine = QtCore.QLineF(QtCore.QPointF(0,0), directionr) viewDir = dt.map(dirLine) if viewDir.length() == 0: return None, None ## pixel size cannot be represented on this scale ## get unit vector and orthogonal vector (length of pixel) #orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space try: normView = viewDir.unitVector() #normView = viewDir.norm() ## direction of one pixel orthogonal to line normOrtho = normView.normalVector() #normOrtho = orthoDir.norm() except: raise Exception("Invalid direction %s" %directionr) ## map back to item dti = fn.invertQTransform(dt) #pv = Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0))) pv = Point(dti.map(normView).p2()), Point(dti.map(normOrtho).p2()) self._pixelVectorCache[1] = pv self._pixelVectorCache[0] = dt self._pixelVectorGlobalCache[key] = pv return self._pixelVectorCache[1] def pixelLength(self, direction, ortho=False): """Return the length of one pixel in the direction indicated (in local coordinates) If ortho=True, then return the length of one pixel orthogonal to the direction indicated. Return None if pixel size is not yet defined (usually because the item has not yet been displayed). """ normV, orthoV = self.pixelVectors(direction) if normV is None or orthoV is None: return None if ortho: return orthoV.length() return normV.length() def pixelSize(self): ## deprecated v = self.pixelVectors() if v == (None, None): return None, None return (hypot(v[0].x(), v[0].y()), hypot(v[1].x(), v[1].y())) # lengths def pixelWidth(self): ## deprecated vt = self.deviceTransform() if vt is None: return 0 vt = fn.invertQTransform(vt) return vt.map(QtCore.QLineF(0, 0, 1, 0)).length() def pixelHeight(self): ## deprecated vt = self.deviceTransform() if vt is None: return 0 vt = fn.invertQTransform(vt) return vt.map(QtCore.QLineF(0, 0, 0, 1)).length() #return Point(vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).length() def mapToDevice(self, obj): """ Return *obj* mapped from local coordinates to device coordinates (pixels). If there is no device mapping available, return None. """ vt = self.deviceTransform() if vt is None: return None return vt.map(obj) def mapFromDevice(self, obj): """ Return *obj* mapped from device coordinates (pixels) to local coordinates. If there is no device mapping available, return None. """ vt = self.deviceTransform() if vt is None: return None if isinstance(obj, QtCore.QPoint): obj = QtCore.QPointF(obj) vt = fn.invertQTransform(vt) return vt.map(obj) def mapRectToDevice(self, rect): """ Return *rect* mapped from local coordinates to device coordinates (pixels). If there is no device mapping available, return None. """ vt = self.deviceTransform() if vt is None: return None return vt.mapRect(rect) def mapRectFromDevice(self, rect): """ Return *rect* mapped from device coordinates (pixels) to local coordinates. If there is no device mapping available, return None. """ vt = self.deviceTransform() if vt is None: return None vt = fn.invertQTransform(vt) return vt.mapRect(rect) def mapToView(self, obj): vt = self.viewTransform() if vt is None: return None return vt.map(obj) def mapRectToView(self, obj): vt = self.viewTransform() if vt is None: return None return vt.mapRect(obj) def mapFromView(self, obj): vt = self.viewTransform() if vt is None: return None vt = fn.invertQTransform(vt) return vt.map(obj) def mapRectFromView(self, obj): vt = self.viewTransform() if vt is None: return None vt = fn.invertQTransform(vt) return vt.mapRect(obj) def pos(self): return Point(self._qtBaseClass.pos(self)) def viewPos(self): return self.mapToView(self.mapFromParent(self.pos())) def parentItem(self): ## PyQt bug -- some items are returned incorrectly. return GraphicsScene.translateGraphicsItem(self._qtBaseClass.parentItem(self)) def setParentItem(self, parent): ## Workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 if parent is not None: pscene = parent.scene() if pscene is not None and self.scene() is not pscene: pscene.addItem(self) return self._qtBaseClass.setParentItem(self, parent) def childItems(self): ## PyQt bug -- some child items are returned incorrectly. return list(map(GraphicsScene.translateGraphicsItem, self._qtBaseClass.childItems(self))) def sceneTransform(self): ## Qt bug: do no allow access to sceneTransform() until ## the item has a scene. if self.scene() is None: return self.transform() else: return self._qtBaseClass.sceneTransform(self) def transformAngle(self, relativeItem=None): """Return the rotation produced by this item's transform (this assumes there is no shear in the transform) If relativeItem is given, then the angle is determined relative to that item. """ if relativeItem is None: relativeItem = self.parentItem() tr = self.itemTransform(relativeItem) if isinstance(tr, tuple): ## difference between pyside and pyqt tr = tr[0] vec = tr.map(QtCore.QLineF(0,0,1,0)) return vec.angleTo(QtCore.QLineF(vec.p1(), vec.p1()+QtCore.QPointF(1,0))) #def itemChange(self, change, value): #ret = self._qtBaseClass.itemChange(self, change, value) #if change == self.GraphicsItemChange.ItemParentHasChanged or change == self.ItemSceneHasChanged: #print "Item scene changed:", self #self.setChildScene(self) ## This is bizarre. #return ret #def setChildScene(self, ch): #scene = self.scene() #for ch2 in ch.childItems(): #if ch2.scene() is not scene: #print "item", ch2, "has different scene:", ch2.scene(), scene #scene.addItem(ch2) #QtWidgets.QApplication.processEvents() #print " --> ", ch2.scene() #self.setChildScene(ch2) def parentChanged(self): """Called when the item's parent has changed. This method handles connecting / disconnecting from ViewBox signals to make sure viewRangeChanged works properly. It should generally be extended, not overridden.""" self._updateView() def _updateView(self): ## called to see whether this item has a new view to connect to ## NOTE: This is called from GraphicsObject.itemChange or GraphicsWidget.itemChange. if not hasattr(self, '_connectedView'): # Happens when Python is shutting down. return ## It is possible this item has moved to a different ViewBox or widget; ## clear out previously determined references to these. self.forgetViewBox() self.forgetViewWidget() ## check for this item's current viewbox or view widget view = self.getViewBox() #if view is None: ##print " no view" #return oldView = None if self._connectedView is not None: oldView = self._connectedView() if view is oldView: #print " already have view", view return ## disconnect from previous view if oldView is not None: for signal, slot in [('sigRangeChanged', self.viewRangeChanged), ('sigDeviceRangeChanged', self.viewRangeChanged), ('sigTransformChanged', self.viewTransformChanged), ('sigDeviceTransformChanged', self.viewTransformChanged)]: try: getattr(oldView, signal).disconnect(slot) except (TypeError, AttributeError, RuntimeError): # TypeError and RuntimeError are from pyqt and pyside, respectively pass self._connectedView = None ## connect to new view if view is not None: #print "connect:", self, view if hasattr(view, 'sigDeviceRangeChanged'): # connect signals from GraphicsView view.sigDeviceRangeChanged.connect(self.viewRangeChanged) view.sigDeviceTransformChanged.connect(self.viewTransformChanged) else: # connect signals from ViewBox view.sigRangeChanged.connect(self.viewRangeChanged) view.sigTransformChanged.connect(self.viewTransformChanged) self._connectedView = weakref.ref(view) self.viewRangeChanged() self.viewTransformChanged() ## inform children that their view might have changed self._replaceView(oldView) self.viewChanged(view, oldView) def viewChanged(self, view, oldView): """Called when this item's view has changed (ie, the item has been added to or removed from a ViewBox)""" def _replaceView(self, oldView, item=None): if item is None: item = self for child in item.childItems(): if isinstance(child, GraphicsItem): if child.getViewBox() is oldView: child._updateView() #self._replaceView(oldView, child) else: self._replaceView(oldView, child) def viewRangeChanged(self): """ Called whenever the view coordinates of the ViewBox containing this item have changed. """ # when this is called, _cachedView is not invalidated. # this means that for functions overriding viewRangeChanged, viewRect() may be stale. def viewTransformChanged(self): """ Called whenever the transformation matrix of the view has changed. (eg, the view range has changed or the view was resized) Invalidates the viewRect cache. """ self._cachedView = None #def prepareGeometryChange(self): #self._qtBaseClass.prepareGeometryChange(self) #self.informViewBoundsChanged() def informViewBoundsChanged(self): """ Inform this item's container ViewBox that the bounds of this item have changed. This is used by ViewBox to react if auto-range is enabled. """ view = self.getViewBox() if view is not None and hasattr(view, 'implements') and view.implements('ViewBox'): view.itemBoundsChanged(self) ## inform view so it can update its range if it wants def childrenShape(self): """Return the union of the shapes of all descendants of this item in local coordinates.""" childs = self.allChildItems() shapes = [self.mapFromItem(c, c.shape()) for c in self.allChildItems()] return reduce(operator.add, shapes) def allChildItems(self, root=None): """Return list of the entire item tree descending from this item.""" if root is None: root = self tree = [] for ch in root.childItems(): tree.append(ch) tree.extend(self.allChildItems(ch)) return tree def setExportMode(self, export, opts=None): """ This method is called by exporters to inform items that they are being drawn for export with a specific set of options. Items access these via self._exportOptions. When exporting is complete, _exportOptions is set to False. """ if opts is None: opts = {} if export: self._exportOpts = opts #if 'antialias' not in opts: #self._exportOpts['antialias'] = True else: self._exportOpts = False #def update(self): #self._qtBaseClass.update(self) #print "Update:", self def getContextMenus(self, event): return [self.getMenu()] if hasattr(self, "getMenu") else [] pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/GraphicsLayout.py000066400000000000000000000162551421045507400257760ustar00rootroot00000000000000from .. import functions as fn from ..Qt import QtWidgets from .GraphicsWidget import GraphicsWidget from .LabelItem import LabelItem from .PlotItem import PlotItem ## Must be imported at the end to avoid cyclic-dependency hell: from .ViewBox import ViewBox __all__ = ['GraphicsLayout'] class GraphicsLayout(GraphicsWidget): """ Used for laying out GraphicsWidgets in a grid. This is usually created automatically as part of a :class:`GraphicsWindow ` or :class:`GraphicsLayoutWidget `. """ def __init__(self, parent=None, border=None): GraphicsWidget.__init__(self, parent) if border is True: border = (100,100,100) elif border is False: border = None self.border = border self.layout = QtWidgets.QGraphicsGridLayout() self.setLayout(self.layout) self.items = {} ## item: [(row, col), (row, col), ...] lists all cells occupied by the item self.rows = {} ## row: {col1: item1, col2: item2, ...} maps cell location to item self.itemBorders = {} ## {item1: QtWidgets.QGraphicsRectItem, ...} border rects self.currentRow = 0 self.currentCol = 0 self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)) #def resizeEvent(self, ev): #ret = GraphicsWidget.resizeEvent(self, ev) #print self.pos(), self.mapToDevice(self.rect().topLeft()) #return ret def setBorder(self, *args, **kwds): """ Set the pen used to draw border between cells. See :func:`mkPen ` for arguments. """ self.border = fn.mkPen(*args, **kwds) for borderRect in self.itemBorders.values(): borderRect.setPen(self.border) def nextRow(self): """Advance to next row for automatic item placement""" self.currentRow += 1 self.currentCol = -1 self.nextColumn() def nextColumn(self): """Advance to next available column (generally only for internal use--called by addItem)""" self.currentCol += 1 while self.getItem(self.currentRow, self.currentCol) is not None: self.currentCol += 1 def nextCol(self, *args, **kargs): """Alias of nextColumn""" return self.nextColumn(*args, **kargs) def addPlot(self, row=None, col=None, rowspan=1, colspan=1, **kargs): """ Create a PlotItem and place it in the next available cell (or in the cell specified) All extra keyword arguments are passed to :func:`PlotItem.__init__ ` Returns the created item. """ plot = PlotItem(**kargs) self.addItem(plot, row, col, rowspan, colspan) return plot def addViewBox(self, row=None, col=None, rowspan=1, colspan=1, **kargs): """ Create a ViewBox and place it in the next available cell (or in the cell specified) All extra keyword arguments are passed to :func:`ViewBox.__init__ ` Returns the created item. """ vb = ViewBox(**kargs) self.addItem(vb, row, col, rowspan, colspan) return vb def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs): """ Create a LabelItem with *text* and place it in the next available cell (or in the cell specified) All extra keyword arguments are passed to :func:`LabelItem.__init__ ` Returns the created item. To create a vertical label, use *angle* = -90. """ text = LabelItem(text, **kargs) self.addItem(text, row, col, rowspan, colspan) return text def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs): """ Create an empty GraphicsLayout and place it in the next available cell (or in the cell specified) All extra keyword arguments are passed to :func:`GraphicsLayout.__init__ ` Returns the created item. """ layout = GraphicsLayout(**kargs) self.addItem(layout, row, col, rowspan, colspan) return layout def addItem(self, item, row=None, col=None, rowspan=1, colspan=1): """ Add an item to the layout and place it in the next available cell (or in the cell specified). The item must be an instance of a QGraphicsWidget subclass. """ if row is None: row = self.currentRow if col is None: col = self.currentCol self.items[item] = [] for i in range(rowspan): for j in range(colspan): row2 = row + i col2 = col + j if row2 not in self.rows: self.rows[row2] = {} self.rows[row2][col2] = item self.items[item].append((row2, col2)) borderRect = QtWidgets.QGraphicsRectItem() borderRect.setParentItem(self) borderRect.setZValue(1e3) borderRect.setPen(fn.mkPen(self.border)) self.itemBorders[item] = borderRect item.geometryChanged.connect(self._updateItemBorder) self.layout.addItem(item, row, col, rowspan, colspan) self.layout.activate() # Update layout, recalculating bounds. # Allows some PyQtGraph features to also work without Qt event loop. self.nextColumn() def getItem(self, row, col): """Return the item in (*row*, *col*). If the cell is empty, return None.""" return self.rows.get(row, {}).get(col, None) def boundingRect(self): return self.rect() def itemIndex(self, item): for i in range(self.layout.count()): if self.layout.itemAt(i).graphicsItem() is item: return i raise Exception("Could not determine index of item " + str(item)) def removeItem(self, item): """Remove *item* from the layout.""" ind = self.itemIndex(item) self.layout.removeAt(ind) self.scene().removeItem(item) for r, c in self.items[item]: del self.rows[r][c] del self.items[item] item.geometryChanged.disconnect(self._updateItemBorder) del self.itemBorders[item] self.update() def clear(self): for i in list(self.items.keys()): self.removeItem(i) self.currentRow = 0 self.currentCol = 0 def setContentsMargins(self, *args): # Wrap calls to layout. This should happen automatically, but there # seems to be a Qt bug: # http://stackoverflow.com/questions/27092164/margins-in-pyqtgraphs-graphicslayout self.layout.setContentsMargins(*args) def setSpacing(self, *args): self.layout.setSpacing(*args) def _updateItemBorder(self): if self.border is None: return item = self.sender() if item is None: return r = item.mapRectToParent(item.boundingRect()) self.itemBorders[item].setRect(r) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/GraphicsObject.py000066400000000000000000000041121421045507400257140ustar00rootroot00000000000000from ..Qt import QT_LIB, QtWidgets if QT_LIB.startswith('PyQt'): from ..Qt import sip from .GraphicsItem import GraphicsItem __all__ = ['GraphicsObject'] class GraphicsObject(GraphicsItem, QtWidgets.QGraphicsObject): """ **Bases:** :class:`GraphicsItem `, :class:`QtWidgets.QGraphicsObject` Extension of QGraphicsObject with some useful methods (provided by :class:`GraphicsItem `) """ _qtBaseClass = QtWidgets.QGraphicsObject def __init__(self, *args): self.__inform_view_on_changes = True QtWidgets.QGraphicsObject.__init__(self, *args) self.setFlag(self.GraphicsItemFlag.ItemSendsGeometryChanges) GraphicsItem.__init__(self) def itemChange(self, change, value): ret = super().itemChange(change, value) if change in [self.GraphicsItemChange.ItemParentHasChanged, self.GraphicsItemChange.ItemSceneHasChanged]: import types if isinstance(self.parentChanged, types.MethodType): self.parentChanged() else: # workaround PySide6 6.2.2 issue https://bugreports.qt.io/browse/PYSIDE-1730 getattr(self.__class__, 'parentChanged')(self) try: inform_view_on_change = self.__inform_view_on_changes except AttributeError: # It's possible that the attribute was already collected when the itemChange happened # (if it was triggered during the gc of the object). pass else: if inform_view_on_change and change in [self.GraphicsItemChange.ItemPositionHasChanged, self.GraphicsItemChange.ItemTransformHasChanged]: self.informViewBoundsChanged() ## workaround for pyqt bug: ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html if QT_LIB == 'PyQt5' and change == self.GraphicsItemChange.ItemParentChange and isinstance(ret, QtWidgets.QGraphicsItem): ret = sip.cast(ret, QtWidgets.QGraphicsItem) return ret pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/GraphicsWidget.py000066400000000000000000000050021421045507400257300ustar00rootroot00000000000000from ..Qt import QtGui, QtWidgets from .GraphicsItem import GraphicsItem __all__ = ['GraphicsWidget'] class GraphicsWidget(GraphicsItem, QtWidgets.QGraphicsWidget): _qtBaseClass = QtWidgets.QGraphicsWidget def __init__(self, *args, **kargs): """ **Bases:** :class:`GraphicsItem `, :class:`QtWidgets.QGraphicsWidget` Extends QGraphicsWidget with several helpful methods and workarounds for PyQt bugs. Most of the extra functionality is inherited from :class:`GraphicsItem `. """ QtWidgets.QGraphicsWidget.__init__(self, *args, **kargs) GraphicsItem.__init__(self) # cache bouding rect and geometry self._boundingRectCache = self._previousGeometry = None self._painterPathCache = None ## done by GraphicsItem init #GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() # Removed due to https://bugreports.qt-project.org/browse/PYSIDE-86 #def itemChange(self, change, value): ## BEWARE: Calling QGraphicsWidget.itemChange can lead to crashing! ##ret = QtWidgets.QGraphicsWidget.itemChange(self, change, value) ## segv occurs here ## The default behavior is just to return the value argument, so we'll do that ## without calling the original method. #ret = value #if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: #self._updateView() #return ret def setFixedHeight(self, h): self.setMaximumHeight(h) self.setMinimumHeight(h) def setFixedWidth(self, h): self.setMaximumWidth(h) self.setMinimumWidth(h) def height(self): return self.geometry().height() def width(self): return self.geometry().width() def boundingRect(self): geometry = self.geometry() if geometry != self._previousGeometry: self._painterPathCache = None br = self.mapRectFromParent(geometry).normalized() self._boundingRectCache = br self._previousGeometry = geometry else: br = self._boundingRectCache return br def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. p = self._painterPathCache if p is None: self._painterPathCache = p = QtGui.QPainterPath() p.addRect(self.boundingRect()) return p pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py000066400000000000000000000077651421045507400271050ustar00rootroot00000000000000from ..Point import Point __all__ = ['GraphicsWidgetAnchor'] class GraphicsWidgetAnchor(object): """ Class used to allow GraphicsWidgets to anchor to a specific position on their parent. The item will be automatically repositioned if the parent is resized. This is used, for example, to anchor a LegendItem to a corner of its parent PlotItem. """ def __init__(self): self.__parent = None self.__parentAnchor = None self.__itemAnchor = None self.__offset = (0,0) if hasattr(self, 'geometryChanged'): self.geometryChanged.connect(self.__geometryChanged) def anchor(self, itemPos, parentPos, offset=(0,0)): """ Anchors the item at its local itemPos to the item's parent at parentPos. Both positions are expressed in values relative to the size of the item or parent; a value of 0 indicates left or top edge, while 1 indicates right or bottom edge. Optionally, offset may be specified to introduce an absolute offset. Example: anchor a box such that its upper-right corner is fixed 10px left and 10px down from its parent's upper-right corner:: box.anchor(itemPos=(1,0), parentPos=(1,0), offset=(-10,10)) """ parent = self.parentItem() if parent is None: raise Exception("Cannot anchor; parent is not set.") if self.__parent is not parent: if self.__parent is not None: self.__parent.geometryChanged.disconnect(self.__geometryChanged) self.__parent = parent parent.geometryChanged.connect(self.__geometryChanged) self.__itemAnchor = itemPos self.__parentAnchor = parentPos self.__offset = offset self.__geometryChanged() def autoAnchor(self, pos, relative=True): """ Set the position of this item relative to its parent by automatically choosing appropriate anchor settings. If relative is True, one corner of the item will be anchored to the appropriate location on the parent with no offset. The anchored corner will be whichever is closest to the parent's boundary. If relative is False, one corner of the item will be anchored to the same corner of the parent, with an absolute offset to achieve the correct position. """ pos = Point(pos) br = self.mapRectToParent(self.boundingRect()).translated(pos - self.pos()) pbr = self.parentItem().boundingRect() anchorPos = [0,0] parentPos = Point() itemPos = Point() if abs(br.left() - pbr.left()) < abs(br.right() - pbr.right()): anchorPos[0] = 0 parentPos[0] = pbr.left() itemPos[0] = br.left() else: anchorPos[0] = 1 parentPos[0] = pbr.right() itemPos[0] = br.right() if abs(br.top() - pbr.top()) < abs(br.bottom() - pbr.bottom()): anchorPos[1] = 0 parentPos[1] = pbr.top() itemPos[1] = br.top() else: anchorPos[1] = 1 parentPos[1] = pbr.bottom() itemPos[1] = br.bottom() if relative: relPos = [(itemPos[0]-pbr.left()) / pbr.width(), (itemPos[1]-pbr.top()) / pbr.height()] self.anchor(anchorPos, relPos) else: offset = itemPos - parentPos self.anchor(anchorPos, anchorPos, offset) def __geometryChanged(self): if self.__parent is None: return if self.__itemAnchor is None: return o = self.mapToParent(Point(0,0)) a = self.boundingRect().bottomRight() * Point(self.__itemAnchor) a = self.mapToParent(a) p = self.__parent.boundingRect().bottomRight() * Point(self.__parentAnchor) off = Point(self.__offset) pos = p + (o-a) + off self.setPos(pos) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/GridItem.py000066400000000000000000000157721421045507400245470ustar00rootroot00000000000000import numpy as np from .. import functions as fn from .. import getConfigOption from ..Point import Point from ..Qt import QtCore, QtGui from .UIGraphicsItem import * __all__ = ['GridItem'] class GridItem(UIGraphicsItem): """ **Bases:** :class:`UIGraphicsItem ` Displays a rectangular grid of lines indicating major divisions within a coordinate system. Automatically determines what divisions to use. """ def __init__(self, pen='default', textPen='default'): UIGraphicsItem.__init__(self) #QtWidgets.QGraphicsItem.__init__(self, *args) #self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemClipsToShape) #self.setCacheMode(QtWidgets.QGraphicsItem.CacheMode.DeviceCoordinateCache) self.opts = {} self.setPen(pen) self.setTextPen(textPen) self.setTickSpacing(x=[None, None, None], y=[None, None, None]) def setPen(self, *args, **kwargs): """Set the pen used to draw the grid.""" if kwargs == {} and (args == () or args == ('default',)): self.opts['pen'] = fn.mkPen(getConfigOption('foreground')) else: self.opts['pen'] = fn.mkPen(*args, **kwargs) self.picture = None self.update() def setTextPen(self, *args, **kwargs): """Set the pen used to draw the texts.""" if kwargs == {} and (args == () or args == ('default',)): self.opts['textPen'] = fn.mkPen(getConfigOption('foreground')) else: if args == (None,): self.opts['textPen'] = None else: self.opts['textPen'] = fn.mkPen(*args, **kwargs) self.picture = None self.update() def setTickSpacing(self, x=None, y=None): """ Set the grid tick spacing to use. Tick spacing for each axis shall be specified as an array of descending values, one for each tick scale. When the value is set to None, grid line distance is chosen automatically for this particular level. Example: Default setting of 3 scales for each axis: setTickSpacing(x=[None, None, None], y=[None, None, None]) Single scale with distance of 1.0 for X axis, Two automatic scales for Y axis: setTickSpacing(x=[1.0], y=[None, None]) Single scale with distance of 1.0 for X axis, Two scales for Y axis, one with spacing of 1.0, other one automatic: setTickSpacing(x=[1.0], y=[1.0, None]) """ self.opts['tickSpacing'] = (x or self.opts['tickSpacing'][0], y or self.opts['tickSpacing'][1]) self.grid_depth = max([len(s) for s in self.opts['tickSpacing']]) self.picture = None self.update() def viewRangeChanged(self): UIGraphicsItem.viewRangeChanged(self) self.picture = None #UIGraphicsItem.viewRangeChanged(self) #self.update() def paint(self, p, opt, widget): #p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) #p.drawRect(self.boundingRect()) #UIGraphicsItem.paint(self, p, opt, widget) ### draw picture if self.picture is None: #print "no pic, draw.." self.generatePicture() p.drawPicture(QtCore.QPointF(0, 0), self.picture) #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) #p.drawLine(0, -100, 0, 100) #p.drawLine(-100, 0, 100, 0) #print "drawing Grid." def generatePicture(self): self.picture = QtGui.QPicture() p = QtGui.QPainter() p.begin(self.picture) vr = self.getViewWidget().rect() unit = self.pixelWidth(), self.pixelHeight() dim = [vr.width(), vr.height()] lvr = self.boundingRect() ul = np.array([lvr.left(), lvr.top()]) br = np.array([lvr.right(), lvr.bottom()]) texts = [] if ul[1] > br[1]: x = ul[1] ul[1] = br[1] br[1] = x lastd = [None, None] for i in range(self.grid_depth - 1, -1, -1): dist = br-ul nlTarget = 10.**i d = 10. ** np.floor(np.log10(np.abs(dist/nlTarget))+0.5) for ax in range(0,2): ts = self.opts['tickSpacing'][ax] try: if ts[i] is not None: d[ax] = ts[i] except IndexError: pass lastd[ax] = d[ax] ul1 = np.floor(ul / d) * d br1 = np.ceil(br / d) * d dist = br1-ul1 nl = (dist / d) + 0.5 for ax in range(0,2): ## Draw grid for both axes if i >= len(self.opts['tickSpacing'][ax]): continue if d[ax] < lastd[ax]: continue ppl = dim[ax] / nl[ax] c = int(fn.clip_scalar(5 * (ppl-3), 0, 50)) linePen = self.opts['pen'] lineColor = self.opts['pen'].color() lineColor.setAlpha(c) linePen.setColor(lineColor) textPen = self.opts['textPen'] if textPen is not None: textColor = self.opts['textPen'].color() textColor.setAlpha(c * 2) textPen.setColor(textColor) bx = (ax+1) % 2 for x in range(0, int(nl[ax])): linePen.setCosmetic(False) if ax == 0: linePen.setWidthF(self.pixelWidth()) else: linePen.setWidthF(self.pixelHeight()) p.setPen(linePen) p1 = np.array([0.,0.]) p2 = np.array([0.,0.]) p1[ax] = ul1[ax] + x * d[ax] p2[ax] = p1[ax] p1[bx] = ul[bx] p2[bx] = br[bx] ## don't draw lines that are out of bounds. if p1[ax] < min(ul[ax], br[ax]) or p1[ax] > max(ul[ax], br[ax]): continue p.drawLine(QtCore.QPointF(p1[0], p1[1]), QtCore.QPointF(p2[0], p2[1])) if i < 2 and textPen is not None: if ax == 0: x = p1[0] + unit[0] y = ul[1] + unit[1] * 8. else: x = ul[0] + unit[0]*3 y = p1[1] + unit[1] texts.append((QtCore.QPointF(x, y), "%g"%p1[ax])) tr = self.deviceTransform() p.setWorldTransform(fn.invertQTransform(tr)) if textPen is not None and len(texts) > 0: # if there is at least one text, then c is set textColor.setAlpha(c * 2) p.setPen(QtGui.QPen(textColor)) for t in texts: x = tr.map(t[0]) + Point(0.5, 0.5) p.drawText(x, t[1]) p.end() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/HistogramLUTItem.py000066400000000000000000000437701421045507400262030ustar00rootroot00000000000000""" GraphicsWidget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. """ import weakref import numpy as np from .. import debug as debug from .. import functions as fn from ..Point import Point from ..Qt import QtCore, QtGui, QtWidgets from .AxisItem import * from .GradientEditorItem import * from .GraphicsWidget import GraphicsWidget from .LinearRegionItem import * from .PlotCurveItem import * from .ViewBox import * __all__ = ['HistogramLUTItem'] class HistogramLUTItem(GraphicsWidget): """ :class:`~pyqtgraph.GraphicsWidget` with controls for adjusting the display of an :class:`~pyqtgraph.ImageItem`. Includes: - Image histogram - Movable region over the histogram to select black/white levels - Gradient editor to define color lookup table for single-channel images Parameters ---------- image : pyqtgraph.ImageItem, optional If provided, control will be automatically linked to the image and changes to the control will be reflected in the image's appearance. This may also be set via :meth:`setImageItem`. fillHistogram : bool, optional By default, the histogram is rendered with a fill. Performance may be improved by disabling the fill. Additional control over the fill is provided by :meth:`fillHistogram`. levelMode : str, optional 'mono' (default) One histogram with a :class:`~pyqtgraph.LinearRegionItem` is displayed to control the black/white levels of the image. This option may be used for color images, in which case the histogram and levels correspond to all channels of the image. 'rgba' A histogram and level control pair is provided for each image channel. The alpha channel histogram and level control are only shown if the image contains an alpha channel. gradientPosition : str, optional Position of the gradient editor relative to the histogram. Must be one of {'right', 'left', 'top', 'bottom'}. 'right' and 'left' options should be used with a 'vertical' orientation; 'top' and 'bottom' options are for 'horizontal' orientation. orientation : str, optional The orientation of the axis along which the histogram is displayed. Either 'vertical' (default) or 'horizontal'. Attributes ---------- sigLookupTableChanged : signal Emits the HistogramLUTItem itself when the gradient changes sigLevelsChanged : signal Emits the HistogramLUTItem itself while the movable region is changing sigLevelChangeFinished : signal Emits the HistogramLUTItem itself when the movable region is finished changing See Also -------- :class:`~pyqtgraph.ImageItem` HistogramLUTItem is most useful when paired with an ImageItem. :class:`~pyqtgraph.ImageView` Widget containing a paired ImageItem and HistogramLUTItem. :class:`~pyqtgraph.HistogramLUTWidget` QWidget containing a HistogramLUTItem for widget-based layouts. """ sigLookupTableChanged = QtCore.Signal(object) sigLevelsChanged = QtCore.Signal(object) sigLevelChangeFinished = QtCore.Signal(object) def __init__(self, image=None, fillHistogram=True, levelMode='mono', gradientPosition='right', orientation='vertical'): GraphicsWidget.__init__(self) self.lut = None self.imageItem = lambda: None # fake a dead weakref self.levelMode = levelMode self.orientation = orientation self.gradientPosition = gradientPosition if orientation == 'vertical' and gradientPosition not in {'right', 'left'}: self.gradientPosition = 'right' elif orientation == 'horizontal' and gradientPosition not in {'top', 'bottom'}: self.gradientPosition = 'bottom' self.layout = QtWidgets.QGraphicsGridLayout() self.setLayout(self.layout) self.layout.setContentsMargins(1, 1, 1, 1) self.layout.setSpacing(0) self.vb = ViewBox(parent=self) if self.orientation == 'vertical': self.vb.setMaximumWidth(152) self.vb.setMinimumWidth(45) self.vb.setMouseEnabled(x=False, y=True) else: self.vb.setMaximumHeight(152) self.vb.setMinimumHeight(45) self.vb.setMouseEnabled(x=True, y=False) self.gradient = GradientEditorItem(orientation=self.gradientPosition) self.gradient.loadPreset('grey') # LinearRegionItem orientation refers to the bounding lines regionOrientation = 'horizontal' if self.orientation == 'vertical' else 'vertical' self.regions = [ # single region for mono levelMode LinearRegionItem([0, 1], regionOrientation, swapMode='block'), # r/g/b/a regions for rgba levelMode LinearRegionItem([0, 1], regionOrientation, swapMode='block', pen='r', brush=fn.mkBrush((255, 50, 50, 50)), span=(0., 1/3.)), LinearRegionItem([0, 1], regionOrientation, swapMode='block', pen='g', brush=fn.mkBrush((50, 255, 50, 50)), span=(1/3., 2/3.)), LinearRegionItem([0, 1], regionOrientation, swapMode='block', pen='b', brush=fn.mkBrush((50, 50, 255, 80)), span=(2/3., 1.)), LinearRegionItem([0, 1], regionOrientation, swapMode='block', pen='w', brush=fn.mkBrush((255, 255, 255, 50)), span=(2/3., 1.)) ] self.region = self.regions[0] # for backward compatibility. for region in self.regions: region.setZValue(1000) self.vb.addItem(region) region.lines[0].addMarker('<|', 0.5) region.lines[1].addMarker('|>', 0.5) region.sigRegionChanged.connect(self.regionChanging) region.sigRegionChangeFinished.connect(self.regionChanged) # gradient position to axis orientation ax = {'left': 'right', 'right': 'left', 'top': 'bottom', 'bottom': 'top'}[self.gradientPosition] self.axis = AxisItem(ax, linkView=self.vb, maxTickLength=-10, parent=self) # axis / viewbox / gradient order in the grid avg = (0, 1, 2) if self.gradientPosition in {'right', 'bottom'} else (2, 1, 0) if self.orientation == 'vertical': self.layout.addItem(self.axis, 0, avg[0]) self.layout.addItem(self.vb, 0, avg[1]) self.layout.addItem(self.gradient, 0, avg[2]) else: self.layout.addItem(self.axis, avg[0], 0) self.layout.addItem(self.vb, avg[1], 0) self.layout.addItem(self.gradient, avg[2], 0) self.gradient.setFlag(self.gradient.GraphicsItemFlag.ItemStacksBehindParent) self.vb.setFlag(self.gradient.GraphicsItemFlag.ItemStacksBehindParent) self.gradient.sigGradientChanged.connect(self.gradientChanged) self.vb.sigRangeChanged.connect(self.viewRangeChanged) comp = QtGui.QPainter.CompositionMode.CompositionMode_Plus self.plots = [ PlotCurveItem(pen=(200, 200, 200, 100)), # mono PlotCurveItem(pen=(255, 0, 0, 100), compositionMode=comp), # r PlotCurveItem(pen=(0, 255, 0, 100), compositionMode=comp), # g PlotCurveItem(pen=(0, 0, 255, 100), compositionMode=comp), # b PlotCurveItem(pen=(200, 200, 200, 100), compositionMode=comp), # a ] self.plot = self.plots[0] # for backward compatibility. for plot in self.plots: if self.orientation == 'vertical': plot.setRotation(90) self.vb.addItem(plot) self.fillHistogram(fillHistogram) self._showRegions() self.autoHistogramRange() if image is not None: self.setImageItem(image) def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)): """Control fill of the histogram curve(s). Parameters ---------- fill : bool, optional Set whether or not the histogram should be filled. level : float, optional Set the fill level. See :meth:`PlotCurveItem.setFillLevel `. Only used if ``fill`` is True. color : color, optional Color to use for the fill when the histogram ``levelMode == "mono"``. See :meth:`PlotCurveItem.setBrush `. """ colors = [color, (255, 0, 0, 50), (0, 255, 0, 50), (0, 0, 255, 50), (255, 255, 255, 50)] for color, plot in zip(colors, self.plots): if fill: plot.setFillLevel(level) plot.setBrush(color) else: plot.setFillLevel(None) def paint(self, p, *args): # paint the bounding edges of the region item and gradient item with lines # connecting them if self.levelMode != 'mono' or not self.region.isVisible(): return pen = self.region.lines[0].pen mn, mx = self.getLevels() vbc = self.vb.viewRect().center() gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect()) if self.orientation == 'vertical': p1mn = self.vb.mapFromViewToItem(self, Point(vbc.x(), mn)) + Point(0, 5) p1mx = self.vb.mapFromViewToItem(self, Point(vbc.x(), mx)) - Point(0, 5) if self.gradientPosition == 'right': p2mn = gradRect.bottomLeft() p2mx = gradRect.topLeft() else: p2mn = gradRect.bottomRight() p2mx = gradRect.topRight() else: p1mn = self.vb.mapFromViewToItem(self, Point(mn, vbc.y())) - Point(5, 0) p1mx = self.vb.mapFromViewToItem(self, Point(mx, vbc.y())) + Point(5, 0) if self.gradientPosition == 'bottom': p2mn = gradRect.topLeft() p2mx = gradRect.topRight() else: p2mn = gradRect.bottomLeft() p2mx = gradRect.bottomRight() p.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) for pen in [fn.mkPen((0, 0, 0, 100), width=3), pen]: p.setPen(pen) # lines from the linear region item bounds to the gradient item bounds p.drawLine(p1mn, p2mn) p.drawLine(p1mx, p2mx) # lines bounding the edges of the gradient item if self.orientation == 'vertical': p.drawLine(gradRect.topLeft(), gradRect.topRight()) p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight()) else: p.drawLine(gradRect.topLeft(), gradRect.bottomLeft()) p.drawLine(gradRect.topRight(), gradRect.bottomRight()) def setHistogramRange(self, mn, mx, padding=0.1): """Set the Y range on the histogram plot. This disables auto-scaling.""" if self.orientation == 'vertical': self.vb.enableAutoRange(self.vb.YAxis, False) self.vb.setYRange(mn, mx, padding) else: self.vb.enableAutoRange(self.vb.XAxis, False) self.vb.setXRange(mn, mx, padding) def autoHistogramRange(self): """Enable auto-scaling on the histogram plot.""" self.vb.enableAutoRange(self.vb.XYAxes) def disableAutoHistogramRange(self): """Disable auto-scaling on the histogram plot.""" self.vb.disableAutoRange(self.vb.XYAxes) def setImageItem(self, img): """Set an ImageItem to have its levels and LUT automatically controlled by this HistogramLUTItem. """ self.imageItem = weakref.ref(img) img.sigImageChanged.connect(self.imageChanged) self._setImageLookupTable() self.regionChanged() self.imageChanged(autoLevel=True) def viewRangeChanged(self): self.update() def gradientChanged(self): if self.imageItem() is not None: self._setImageLookupTable() self.lut = None self.sigLookupTableChanged.emit(self) def _setImageLookupTable(self): if self.gradient.isLookupTrivial(): self.imageItem().setLookupTable(None) else: self.imageItem().setLookupTable(self.getLookupTable) def getLookupTable(self, img=None, n=None, alpha=None): """Return a lookup table from the color gradient defined by this HistogramLUTItem. """ if self.levelMode != 'mono': return None if n is None: if img.dtype == np.uint8: n = 256 else: n = 512 if self.lut is None: self.lut = self.gradient.getLookupTable(n, alpha=alpha) return self.lut def regionChanged(self): if self.imageItem() is not None: self.imageItem().setLevels(self.getLevels()) self.sigLevelChangeFinished.emit(self) def regionChanging(self): if self.imageItem() is not None: self.imageItem().setLevels(self.getLevels()) self.update() self.sigLevelsChanged.emit(self) def imageChanged(self, autoLevel=False, autoRange=False): if self.imageItem() is None: return if self.levelMode == 'mono': for plt in self.plots[1:]: plt.setVisible(False) self.plots[0].setVisible(True) # plot one histogram for all image data profiler = debug.Profiler() h = self.imageItem().getHistogram() profiler('get histogram') if h[0] is None: return self.plot.setData(*h) profiler('set plot') if autoLevel: mn = h[0][0] mx = h[0][-1] self.region.setRegion([mn, mx]) profiler('set region') else: mn, mx = self.imageItem().levels self.region.setRegion([mn, mx]) else: # plot one histogram for each channel self.plots[0].setVisible(False) ch = self.imageItem().getHistogram(perChannel=True) if ch[0] is None: return for i in range(1, 5): if len(ch) >= i: h = ch[i-1] self.plots[i].setVisible(True) self.plots[i].setData(*h) if autoLevel: mn = h[0][0] mx = h[0][-1] self.regions[i].setRegion([mn, mx]) else: # hide channels not present in image data self.plots[i].setVisible(False) # make sure we are displaying the correct number of channels self._showRegions() def getLevels(self): """Return the min and max levels. For rgba mode, this returns a list of the levels for each channel. """ if self.levelMode == 'mono': return self.region.getRegion() else: nch = self.imageItem().channels() if nch is None: nch = 3 return [r.getRegion() for r in self.regions[1:nch+1]] def setLevels(self, min=None, max=None, rgba=None): """Set the min/max (bright and dark) levels. Parameters ---------- min : float, optional Minimum level. max : float, optional Maximum level. rgba : list, optional Sequence of (min, max) pairs for each channel for 'rgba' mode. """ if None in {min, max} and (rgba is None or None in rgba[0]): raise ValueError("Must specify min and max levels") if self.levelMode == 'mono': if min is None: min, max = rgba[0] self.region.setRegion((min, max)) else: if rgba is None: rgba = 4*[(min, max)] for levels, region in zip(rgba, self.regions[1:]): region.setRegion(levels) def setLevelMode(self, mode): """Set the method of controlling the image levels offered to the user. Options are 'mono' or 'rgba'. """ if mode not in {'mono', 'rgba'}: raise ValueError(f"Level mode must be one of {{'mono', 'rgba'}}, got {mode}") if mode == self.levelMode: return oldLevels = self.getLevels() self.levelMode = mode self._showRegions() # do our best to preserve old levels if mode == 'mono': levels = np.array(oldLevels).mean(axis=0) self.setLevels(*levels) else: levels = [oldLevels] * 4 self.setLevels(rgba=levels) # force this because calling self.setLevels might not set the imageItem # levels if there was no change to the region item self.imageItem().setLevels(self.getLevels()) self.imageChanged() self.update() def _showRegions(self): for i in range(len(self.regions)): self.regions[i].setVisible(False) if self.levelMode == 'rgba': nch = 4 if self.imageItem() is not None: # Only show rgb channels if connected image lacks alpha. nch = self.imageItem().channels() if nch is None: nch = 3 xdif = 1.0 / nch for i in range(1, nch+1): self.regions[i].setVisible(True) self.regions[i].setSpan((i-1) * xdif, i * xdif) self.gradient.hide() elif self.levelMode == 'mono': self.regions[0].setVisible(True) self.gradient.show() else: raise ValueError(f"Unknown level mode {self.levelMode}") def saveState(self): return { 'gradient': self.gradient.saveState(), 'levels': self.getLevels(), 'mode': self.levelMode, } def restoreState(self, state): if 'mode' in state: self.setLevelMode(state['mode']) self.gradient.restoreState(state['gradient']) self.setLevels(*state['levels']) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ImageItem.py000066400000000000000000001272061421045507400247000ustar00rootroot00000000000000import warnings from collections.abc import Callable import numpy from .. import debug as debug from .. import functions as fn from .. import getConfigOption from ..Point import Point from ..Qt import QtCore, QtGui, QtWidgets from ..util.cupy_helper import getCupy from .GraphicsObject import GraphicsObject from .. import colormap translate = QtCore.QCoreApplication.translate __all__ = ['ImageItem'] class ImageItem(GraphicsObject): """ **Bases:** :class:`GraphicsObject ` """ # Overall description of ImageItem (including examples) moved to documentation text sigImageChanged = QtCore.Signal() sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu def __init__(self, image=None, **kargs): """ See :func:`~pyqtgraph.ImageItem.setOpts` for further keyword arguments and and :func:`~pyqtgraph.ImageItem.setImage` for information on supported formats. Parameters ---------- image: array Image data """ GraphicsObject.__init__(self) self.menu = None self.image = None ## original image data self.qimage = None ## rendered image for display self.paintMode = None self.levels = None ## [min, max] or [[redMin, redMax], ...] self.lut = None self.autoDownsample = False self._colorMap = None # This is only set if a color map is assigned directly self._lastDownsample = (1, 1) self._processingBuffer = None self._displayBuffer = None self._renderRequired = True self._unrenderable = False self._xp = None # either numpy or cupy, to match the image data self._defferedLevels = None self.axisOrder = getConfigOption('imageAxisOrder') self._dataTransform = self._inverseDataTransform = None self._update_data_transforms( self.axisOrder ) # install initial transforms # In some cases, we use a modified lookup table to handle both rescaling # and LUT more efficiently self._effectiveLut = None self.drawKernel = None self.border = None self.removable = False if image is not None: self.setImage(image, **kargs) else: self.setOpts(**kargs) def setCompositionMode(self, mode): """ Change the composition mode of the item. This is useful when overlaying multiple items. Parameters ---------- mode : ``QtGui.QPainter.CompositionMode`` Composition of the item, often used when overlaying items. Common options include: ``QPainter.CompositionMode.CompositionMode_SourceOver`` (Default) Image replaces the background if it is opaque. Otherwise, it uses the alpha channel to blend the image with the background. ``QPainter.CompositionMode.CompositionMode_Overlay`` Image color is mixed with the background color to reflect the lightness or darkness of the background ``QPainter.CompositionMode.CompositionMode_Plus`` Both the alpha and color of the image and background pixels are added together. ``QPainter.CompositionMode.CompositionMode_Plus`` The output is the image color multiplied by the background. See ``QPainter::CompositionMode`` in the Qt Documentation for more options and details """ self.paintMode = mode self.update() def setBorder(self, b): """ Defines the border drawn around the image. Accepts all arguments supported by :func:`~pyqtgraph.functions.mkPen`. """ self.border = fn.mkPen(b) self.update() def width(self): if self.image is None: return None axis = 0 if self.axisOrder == 'col-major' else 1 return self.image.shape[axis] def height(self): if self.image is None: return None axis = 1 if self.axisOrder == 'col-major' else 0 return self.image.shape[axis] def channels(self): if self.image is None: return None return self.image.shape[2] if self.image.ndim == 3 else 1 def boundingRect(self): if self.image is None: return QtCore.QRectF(0., 0., 0., 0.) return QtCore.QRectF(0., 0., float(self.width()), float(self.height())) def setLevels(self, levels, update=True): """ Sets image scaling levels. See :func:`makeARGB ` for more details on how levels are applied. Parameters ---------- levels: list_like - ``[blackLevel, whiteLevel]`` sets black and white levels for monochrome data and can be used with a lookup table. - ``[[minR, maxR], [minG, maxG], [minB, maxB]]`` sets individual scaling for RGB values. Not compatible with lookup tables. update: bool, optional Controls if image immediately updates to reflect the new levels. """ if self._xp is None: self.levels = levels self._defferedLevels = levels return if levels is not None: levels = self._xp.asarray(levels) self.levels = levels self._effectiveLut = None if update: self.updateImage() def getLevels(self): """ Returns the list representing the current level settings. See :func:`~setLevels`. When ``autoLevels`` is active, the format is ``[blackLevel, whiteLevel]``. """ return self.levels def setColorMap(self, colorMap): """ Sets a color map for false color display of a monochrome image. Parameters ---------- colorMap : :class:`~pyqtgraph.ColorMap` or `str` A string argument will be passed to :func:`colormap.get() ` """ if isinstance(colorMap, colormap.ColorMap): self._colorMap = colorMap elif isinstance(colorMap, str): self._colorMap = colormap.get(colorMap) else: raise TypeError("'colorMap' argument must be ColorMap or string") self.setLookupTable( self._colorMap.getLookupTable(nPts=256) ) def getColorMap(self): """ Returns the assigned :class:`pyqtgraph.ColorMap`, or `None` if not available """ return self._colorMap def setLookupTable(self, lut, update=True): """ Sets lookup table ``lut`` to use for false color display of a monochrome image. See :func:`makeARGB ` for more information on how this is used. Optionally, `lut` can be a callable that accepts the current image as an argument and returns the lookup table to use. Ordinarily, this table is supplied by a :class:`~pyqtgraph.HistogramLUTItem`, :class:`~pyqtgraph.GradientEditorItem` or :class:`~pyqtgraph.ColorBarItem`. Setting ``update = False`` avoids an immediate image update. """ if lut is not self.lut: if self._xp is not None: lut = self._ensure_proper_substrate(lut, self._xp) self.lut = lut self._effectiveLut = None if update: self.updateImage() @staticmethod def _ensure_proper_substrate(data, substrate): if data is None or isinstance(data, Callable) or isinstance(data, substrate.ndarray): return data cupy = getCupy() if substrate == cupy and not isinstance(data, cupy.ndarray): data = cupy.asarray(data) elif substrate == numpy: if cupy is not None and isinstance(data, cupy.ndarray): data = data.get() else: data = numpy.asarray(data) return data def setAutoDownsample(self, active=True): """ Controls automatic downsampling for this ImageItem. If `active` is `True`, the image is automatically downsampled to match the screen resolution. This improves performance for large images and reduces aliasing. If `autoDownsample` is not specified, then ImageItem will choose whether to downsample the image based on its size. `False` disables automatic downsampling. """ self.autoDownsample = active self._renderRequired = True self.update() def setOpts(self, update=True, **kargs): """ Sets display and processing options for this ImageItem. :func:`~pyqtgraph.ImageItem.__init__` and :func:`~pyqtgraph.ImageItem.setImage` support all keyword arguments listed here. Parameters ---------- autoDownsample: bool See :func:`~pyqtgraph.ImageItem.setAutoDownsample`. axisOrder: str | `'col-major'`: The shape of the array represents (width, height) of the image. This is the default. | `'row-major'`: The shape of the array represents (height, width). border: bool Sets a pen to draw to draw an image border. See :func:`~pyqtgraph.ImageItem.setBorder`. compositionMode: See :func:`~pyqtgraph.ImageItem.setCompositionMode` colorMap: :class:`~pyqtgraph.ColorMap` or `str` Sets a color map. A string will be passed to :func:`colormap.get() ` lut: array Sets a color lookup table to use when displaying the image. See :func:`~pyqtgraph.ImageItem.setLookupTable`. levels: list_like, usally [`min`, `max`] Sets minimum and maximum values to use when rescaling the image data. By default, these will be set to the estimated minimum and maximum values in the image. If the image array has dtype uint8, no rescaling is necessary. See :func:`~pyqtgraph.ImageItem.setLevels`. opacity: float, 0.0-1.0 Overall opacity for an RGB image. rect: :class:`QRectF`, :class:`QRect` or array_like of floats (`x`,`y`,`w`,`h`) Displays the current image within the specified rectangle in plot coordinates. See :func:`~pyqtgraph.ImageItem.setRect`. update : bool, optional Controls if image immediately updates to reflect the new options. """ if 'axisOrder' in kargs: val = kargs['axisOrder'] if val not in ('row-major', 'col-major'): raise ValueError("axisOrder must be either 'row-major' or 'col-major'") self.axisOrder = val self._update_data_transforms(self.axisOrder) # update cached transforms if 'colorMap' in kargs: self.setColorMap(kargs['colorMap']) if 'lut' in kargs: self.setLookupTable(kargs['lut'], update=update) if 'levels' in kargs: self.setLevels(kargs['levels'], update=update) #if 'clipLevel' in kargs: #self.setClipLevel(kargs['clipLevel']) if 'opacity' in kargs: self.setOpacity(kargs['opacity']) if 'compositionMode' in kargs: self.setCompositionMode(kargs['compositionMode']) if 'border' in kargs: self.setBorder(kargs['border']) if 'removable' in kargs: self.removable = kargs['removable'] self.menu = None if 'autoDownsample' in kargs: self.setAutoDownsample(kargs['autoDownsample']) if 'rect' in kargs: self.setRect(kargs['rect']) if update: self.update() def setRect(self, *args): """ setRect(rect) or setRect(x,y,w,h) Sets translation and scaling of this ImageItem to display the current image within the rectangle given as ``rect`` (:class:`QtCore.QRect` or :class:`QtCore.QRectF`), or described by parameters `x, y, w, h`, defining starting position, width and height. This method cannot be used before an image is assigned. See the :ref:`examples ` for how to manually set transformations. """ if len(args) == 0: self.resetTransform() # reset scaling and rotation when called without argument return if isinstance(args[0], (QtCore.QRectF, QtCore.QRect)): rect = args[0] # use QRectF or QRect directly else: if hasattr(args[0],'__len__'): args = args[0] # promote tuple or list of values rect = QtCore.QRectF( *args ) # QRectF(x,y,w,h), but also accepts other initializers tr = QtGui.QTransform() tr.translate(rect.left(), rect.top()) tr.scale(rect.width() / self.width(), rect.height() / self.height()) self.setTransform(tr) def clear(self): """ Clears the assigned image. """ self.image = None self.prepareGeometryChange() self.informViewBoundsChanged() self.update() def _buildQImageBuffer(self, shape): self._displayBuffer = numpy.empty(shape[:2] + (4,), dtype=numpy.ubyte) if self._xp == getCupy(): self._processingBuffer = self._xp.empty(shape[:2] + (4,), dtype=self._xp.ubyte) else: self._processingBuffer = self._displayBuffer self.qimage = None def setImage(self, image=None, autoLevels=None, **kargs): """ Updates the image displayed by this ImageItem. For more information on how the image is processed before displaying, see :func:`~pyqtgraph.makeARGB`. For backward compatibility, image data is assumed to be in column-major order (column, row) by default. However, most data is stored in row-major order (row, column). It can either be transposed before assignment:: imageitem.setImage(imagedata.T) or the interpretation of the data can be changed locally through the ``axisOrder`` keyword or by changing the `imageAxisOrder` :ref:`global configuration option `. All keywords supported by :func:`~pyqtgraph.ImageItem.setOpts` are also allowed here. Parameters ---------- image: array Image data given as NumPy array with an integer or floating point dtype of any bit depth. A 2-dimensional array describes single-valued (monochromatic) data. A 3-dimensional array is used to give individual color components. The third dimension must be of length 3 (RGB) or 4 (RGBA). rect: QRectF, QRect or list_like of floats ``[x, y, w, h]``, optional If given, sets translation and scaling to display the image within the specified rectangle. See :func:`~pyqtgraph.ImageItem.setRect`. autoLevels: bool, optional If `True`, ImageItem will automatically select levels based on the maximum and minimum values encountered in the data. For performance reasons, this search subsamples the images and may miss individual bright or or dark points in the data set. If `False`, the search will be omitted. The default is `False` if a ``levels`` keyword argument is given, and `True` otherwise. levelSamples: int, default 65536 When determining minimum and maximum values, ImageItem only inspects a subset of pixels no larger than this number. Setting this larger than the total number of pixels considers all values. """ profile = debug.Profiler() gotNewData = False if image is None: if self.image is None: return else: old_xp = self._xp cp = getCupy() self._xp = cp.get_array_module(image) if cp else numpy gotNewData = True processingSubstrateChanged = old_xp != self._xp if processingSubstrateChanged: self._processingBuffer = None shapeChanged = (processingSubstrateChanged or self.image is None or image.shape != self.image.shape) image = image.view() if self.image is None or image.dtype != self.image.dtype: self._effectiveLut = None self.image = image if self.image.shape[0] > 2**15-1 or self.image.shape[1] > 2**15-1: if 'autoDownsample' not in kargs: kargs['autoDownsample'] = True if shapeChanged: self.prepareGeometryChange() self.informViewBoundsChanged() profile() if autoLevels is None: if 'levels' in kargs: autoLevels = False else: autoLevels = True if autoLevels: level_samples = kargs.pop('levelSamples', 2**16) mn, mx = self.quickMinMax( targetSize=level_samples ) # mn and mx can still be NaN if the data is all-NaN if mn == mx or self._xp.isnan(mn) or self._xp.isnan(mx): mn = 0 mx = 255 kargs['levels'] = [mn,mx] profile() self.setOpts(update=False, **kargs) profile() self._renderRequired = True self.update() profile() if gotNewData: self.sigImageChanged.emit() if self._defferedLevels is not None: levels = self._defferedLevels self._defferedLevels = None self.setLevels((levels)) def _update_data_transforms(self, axisOrder='col-major'): """ Sets up the transforms needed to map between input array and display """ self._dataTransform = QtGui.QTransform() self._inverseDataTransform = QtGui.QTransform() if self.axisOrder == 'row-major': # transpose both self._dataTransform.scale(1, -1) self._dataTransform.rotate(-90) self._inverseDataTransform.scale(1, -1) self._inverseDataTransform.rotate(-90) def dataTransform(self): """ Returns the transform that maps from this image's input array to its local coordinate system. This transform corrects for the transposition that occurs when image data is interpreted in row-major order. :meta private: """ # Might eventually need to account for downsampling / clipping here # transforms are updated in setOpts call. return self._dataTransform def inverseDataTransform(self): """Return the transform that maps from this image's local coordinate system to its input array. See dataTransform() for more information. :meta private: """ # transforms are updated in setOpts call. return self._inverseDataTransform def mapToData(self, obj): return self._inverseDataTransform.map(obj) def mapFromData(self, obj): return self._dataTransform.map(obj) def quickMinMax(self, targetSize=1e6): """ Estimates the min/max values of the image data by subsampling. Subsampling is performed at regular strides chosen to evaluate a number of samples equal to or less than `targetSize`. Returns (`min`, `max`). """ data = self.image if targetSize < 2: # keep at least two pixels targetSize = 2 while True: h, w = data.shape[:2] if h * w <= targetSize: break if h > w: data = data[::2, ::] # downsample first axis else: data = data[::, ::2] # downsample second axis return self._xp.nanmin(data), self._xp.nanmax(data) def updateImage(self, *args, **kargs): ## used for re-rendering qimage from self.image. ## can we make any assumptions here that speed things up? ## dtype, range, size are all the same? defaults = { 'autoLevels': False, } defaults.update(kargs) return self.setImage(*args, **defaults) def render(self): # Convert data to QImage for display. self._unrenderable = True if self.image is None or self.image.size == 0: return # Request a lookup table if this image has only one channel if self.image.ndim == 2 or self.image.shape[2] == 1: self.lut = self._ensure_proper_substrate(self.lut, self._xp) if isinstance(self.lut, Callable): lut = self._ensure_proper_substrate(self.lut(self.image), self._xp) else: lut = self.lut else: lut = None if self.autoDownsample: xds, yds = self._computeDownsampleFactors() if xds is None: return axes = [1, 0] if self.axisOrder == 'row-major' else [0, 1] image = fn.downsample(self.image, xds, axis=axes[0]) image = fn.downsample(image, yds, axis=axes[1]) self._lastDownsample = (xds, yds) # Check if downsampling reduced the image size to zero due to inf values. if image.size == 0: return else: image = self.image # Convert single-channel image to 2D array if image.ndim == 3 and image.shape[-1] == 1: image = image[..., 0] # Assume images are in column-major order for backward compatibility # (most images are in row-major order) if self.axisOrder == 'col-major': image = image.swapaxes(0, 1) levels = self.levels augmented_alpha = False if lut is not None and lut.dtype != self._xp.uint8: # Both _try_rescale_float() and _try_combine_lut() assume that # lut is of type uint8. It is considered a usage error if that # is not the case. # However, the makeARGB() codepath has previously allowed such # a usage to work. Rather than fail outright, we delegate this # case to makeARGB(). warnings.warn( "Using non-uint8 LUTs is an undocumented accidental feature and may " "be removed at some point in the future. Please open an issue if you " "instead believe this to be worthy of protected inclusion in pyqtgraph.", DeprecationWarning, stacklevel=2) elif image.dtype.kind == 'f': image, levels, lut, augmented_alpha = self._try_rescale_float(image, levels, lut) # if we succeeded, we will have an uint8 image with levels None. # lut if not None will have <= 256 entries # if the image data is a small int, then we can combine levels + lut # into a single lut for better performance elif image.dtype in (self._xp.ubyte, self._xp.uint16): image, levels, lut, augmented_alpha = self._try_combine_lut(image, levels, lut) qimage = self._try_make_qimage(image, levels, lut, augmented_alpha) if qimage is not None: self._processingBuffer = None self._displayBuffer = None self.qimage = qimage self._renderRequired = False self._unrenderable = False return if self._processingBuffer is None or self._processingBuffer.shape[:2] != image.shape[:2]: self._buildQImageBuffer(image.shape) fn.makeARGB(image, lut=lut, levels=levels, output=self._processingBuffer) if self._xp == getCupy(): self._processingBuffer.get(out=self._displayBuffer) self.qimage = fn.ndarray_to_qimage(self._displayBuffer, QtGui.QImage.Format.Format_ARGB32) self._renderRequired = False self._unrenderable = False def _try_rescale_float(self, image, levels, lut): xp = self._xp augmented_alpha = False can_handle = False while True: if levels is None or levels.ndim != 1: # float images always need levels # can't handle multi-channel levels break # awkward, but fastest numpy native nan evaluation if xp.isnan(image.min()): # don't handle images with nans # this should be an uncommon case break can_handle = True break if not can_handle: return image, levels, lut, augmented_alpha # Decide on maximum scaled value if lut is not None: scale = lut.shape[0] num_colors = lut.shape[0] else: scale = 255. num_colors = 256 dtype = xp.min_scalar_type(num_colors-1) minVal, maxVal = levels if minVal == maxVal: maxVal = xp.nextafter(maxVal, 2*maxVal) rng = maxVal - minVal rng = 1 if rng == 0 else rng fn_numba = fn.getNumbaFunctions() if xp == numpy and image.flags.c_contiguous and dtype == xp.uint16 and fn_numba is not None: lut, augmented_alpha = self._convert_2dlut_to_1dlut(lut) image = fn_numba.rescale_and_lookup1d(image, scale/rng, minVal, lut) if image.dtype == xp.uint32: image = image[..., xp.newaxis].view(xp.uint8) return image, None, None, augmented_alpha else: image = fn.rescaleData(image, scale/rng, offset=minVal, dtype=dtype, clip=(0, num_colors-1)) levels = None if image.dtype == xp.uint16 and image.ndim == 2: image, augmented_alpha = self._apply_lut_for_uint16_mono(image, lut) lut = None # image is now of type uint8 return image, levels, lut, augmented_alpha def _try_combine_lut(self, image, levels, lut): augmented_alpha = False xp = self._xp can_handle = False while True: if levels is not None and levels.ndim != 1: # can't handle multi-channel levels break if image.dtype == xp.uint16 and levels is None and \ image.ndim == 3 and image.shape[2] == 3: # uint16 rgb can't be directly displayed, so make it # pass through effective lut processing levels = [0, 65535] if levels is None and lut is None: # nothing to combine break can_handle = True break if not can_handle: return image, levels, lut, augmented_alpha # distinguish between lut for levels and colors levels_lut = None colors_lut = lut lut = None eflsize = 2**(image.itemsize*8) if levels is None: info = xp.iinfo(image.dtype) minlev, maxlev = info.min, info.max else: minlev, maxlev = levels levdiff = maxlev - minlev levdiff = 1 if levdiff == 0 else levdiff # don't allow division by 0 if colors_lut is None: if image.dtype == xp.ubyte and image.ndim == 2: # uint8 mono image ind = xp.arange(eflsize) levels_lut = fn.rescaleData(ind, scale=255./levdiff, offset=minlev, dtype=xp.ubyte) # image data is not scaled. instead, levels_lut is used # as (grayscale) Indexed8 ColorTable to get the same effect. # due to the small size of the input to rescaleData(), we # do not bother caching the result return image, None, levels_lut, augmented_alpha else: # uint16 mono, uint8 rgb, uint16 rgb # rescale image data by computation instead of by memory lookup image = fn.rescaleData(image, scale=255./levdiff, offset=minlev, dtype=xp.ubyte) return image, None, colors_lut, augmented_alpha else: num_colors = colors_lut.shape[0] effscale = num_colors / levdiff lutdtype = xp.min_scalar_type(num_colors - 1) if image.dtype == xp.ubyte or lutdtype != xp.ubyte: # combine if either: # 1) uint8 mono image # 2) colors_lut has more entries than will fit within 8-bits if self._effectiveLut is None: ind = xp.arange(eflsize) levels_lut = fn.rescaleData(ind, scale=effscale, offset=minlev, dtype=lutdtype, clip=(0, num_colors-1)) efflut = colors_lut[levels_lut] levels_lut = None colors_lut = None self._effectiveLut = efflut efflut = self._effectiveLut # apply the effective lut early for the following types: if image.dtype == xp.uint16 and image.ndim == 2: image, augmented_alpha = self._apply_lut_for_uint16_mono(image, efflut) efflut = None return image, None, efflut, augmented_alpha else: # uint16 image with colors_lut <= 256 entries # don't combine, we will use QImage ColorTable image = fn.rescaleData(image, scale=effscale, offset=minlev, dtype=lutdtype, clip=(0, num_colors-1)) return image, None, colors_lut, augmented_alpha def _apply_lut_for_uint16_mono(self, image, lut): # Note: compared to makeARGB(), we have already clipped the data to range xp = self._xp augmented_alpha = False # if lut is 1d, then lut[image] is fastest # if lut is 2d, then lut.take(image, axis=0) is faster than lut[image] if not image.flags.c_contiguous: image = lut.take(image, axis=0) # if lut had dimensions (N, 1), then our resultant image would # have dimensions (h, w, 1) if image.ndim == 3 and image.shape[-1] == 1: image = image[..., 0] return image, augmented_alpha # if we are contiguous, we can take a faster codepath where we # ensure that the lut is 1d lut, augmented_alpha = self._convert_2dlut_to_1dlut(lut) fn_numba = fn.getNumbaFunctions() if xp == numpy and fn_numba is not None: image = fn_numba.numba_take(lut, image) else: image = lut[image] if image.dtype == xp.uint32: image = image[..., xp.newaxis].view(xp.uint8) return image, augmented_alpha def _convert_2dlut_to_1dlut(self, lut): # converts: # - uint8 (N, 1) to uint8 (N,) # - uint8 (N, 3) or (N, 4) to uint32 (N,) # this allows faster lookup as 1d lookup is faster xp = self._xp augmented_alpha = False if lut.ndim == 1: return lut, augmented_alpha if lut.shape[1] == 3: # rgb # convert rgb lut to rgba so that it is 32-bits lut = xp.column_stack([lut, xp.full(lut.shape[0], 255, dtype=xp.uint8)]) augmented_alpha = True if lut.shape[1] == 4: # rgba lut = lut.view(xp.uint32) lut = lut.ravel() return lut, augmented_alpha def _try_make_qimage(self, image, levels, lut, augmented_alpha): xp = self._xp ubyte_nolvl = image.dtype == xp.ubyte and levels is None is_passthru8 = ubyte_nolvl and lut is None is_indexed8 = ubyte_nolvl and image.ndim == 2 and \ lut is not None and lut.shape[0] <= 256 is_passthru16 = image.dtype == xp.uint16 and levels is None and lut is None can_grayscale16 = is_passthru16 and image.ndim == 2 and \ hasattr(QtGui.QImage.Format, 'Format_Grayscale16') is_rgba64 = is_passthru16 and image.ndim == 3 and image.shape[2] == 4 # bypass makeARGB for supported combinations supported = is_passthru8 or is_indexed8 or can_grayscale16 or is_rgba64 if not supported: return None if self._xp == getCupy(): image = image.get() # worthwhile supporting non-contiguous arrays image = numpy.ascontiguousarray(image) fmt = None ctbl = None if is_passthru8: # both levels and lut are None # these images are suitable for display directly if image.ndim == 2: fmt = QtGui.QImage.Format.Format_Grayscale8 elif image.shape[2] == 3: fmt = QtGui.QImage.Format.Format_RGB888 elif image.shape[2] == 4: if augmented_alpha: fmt = QtGui.QImage.Format.Format_RGBX8888 else: fmt = QtGui.QImage.Format.Format_RGBA8888 elif is_indexed8: # levels and/or lut --> lut-only fmt = QtGui.QImage.Format.Format_Indexed8 if lut.ndim == 1 or lut.shape[1] == 1: ctbl = [QtGui.qRgb(x,x,x) for x in lut.ravel().tolist()] elif lut.shape[1] == 3: ctbl = [QtGui.qRgb(*rgb) for rgb in lut.tolist()] elif lut.shape[1] == 4: ctbl = [QtGui.qRgba(*rgba) for rgba in lut.tolist()] elif can_grayscale16: # single channel uint16 # both levels and lut are None fmt = QtGui.QImage.Format.Format_Grayscale16 elif is_rgba64: # uint16 rgba # both levels and lut are None fmt = QtGui.QImage.Format.Format_RGBA64 # endian-independent if fmt is None: raise ValueError("unsupported image type") qimage = fn.ndarray_to_qimage(image, fmt) if ctbl is not None: qimage.setColorTable(ctbl) return qimage def paint(self, p, *args): profile = debug.Profiler() if self.image is None: return if self._renderRequired: self.render() if self._unrenderable: return profile('render QImage') if self.paintMode is not None: p.setCompositionMode(self.paintMode) profile('set comp mode') shape = self.image.shape[:2] if self.axisOrder == 'col-major' else self.image.shape[:2][::-1] p.drawImage(QtCore.QRectF(0,0,*shape), self.qimage) profile('p.drawImage') if self.border is not None: p.setPen(self.border) p.drawRect(self.boundingRect()) def save(self, fileName, *args): """ Saves this image to file. Note that this saves the visible image (after scale/color changes), not the original data. """ if self._renderRequired: self.render() self.qimage.save(fileName, *args) def getHistogram(self, bins='auto', step='auto', perChannel=False, targetImageSize=200, targetHistogramSize=500, **kwds): """ Returns `x` and `y` arrays containing the histogram values for the current image. For an explanation of the return format, see :func:`numpy.histogram()`. The `step` argument causes pixels to be skipped when computing the histogram to save time. If `step` is 'auto', then a step is chosen such that the analyzed data has dimensions approximating `targetImageSize` for each axis. The `bins` argument and any extra keyword arguments are passed to :func:`self.xp.histogram()`. If `bins` is `auto`, a bin number is automatically chosen based on the image characteristics: * Integer images will have approximately `targetHistogramSize` bins, with each bin having an integer width. * All other types will have `targetHistogramSize` bins. If `perChannel` is `True`, then a histogram is computed for each channel, and the output is a list of the results. """ # This method is also used when automatically computing levels. if self.image is None or self.image.size == 0: return None, None if step == 'auto': step = (max(1, int(self._xp.ceil(self.image.shape[0] / targetImageSize))), max(1, int(self._xp.ceil(self.image.shape[1] / targetImageSize)))) if self._xp.isscalar(step): step = (step, step) stepData = self.image[::step[0], ::step[1]] if isinstance(bins, str) and bins == 'auto': mn = self._xp.nanmin(stepData).item() mx = self._xp.nanmax(stepData).item() if mx == mn: # degenerate image, arange will fail mx += 1 if self._xp.isnan(mn) or self._xp.isnan(mx): # the data are all-nan return None, None if stepData.dtype.kind in "ui": # For integer data, we select the bins carefully to avoid aliasing step = int(self._xp.ceil((mx - mn) / 500.)) bins = [] if step > 0.0: bins = self._xp.arange(mn, mx + 1.01 * step, step, dtype=int) else: # for float data, let numpy select the bins. bins = self._xp.linspace(mn, mx, 500) if len(bins) == 0: bins = self._xp.asarray((mn, mx)) kwds['bins'] = bins cp = getCupy() if perChannel: hist = [] for i in range(stepData.shape[-1]): stepChan = stepData[..., i] stepChan = stepChan[self._xp.isfinite(stepChan)] h = self._xp.histogram(stepChan, **kwds) if cp: hist.append((cp.asnumpy(h[1][:-1]), cp.asnumpy(h[0]))) else: hist.append((h[1][:-1], h[0])) return hist else: stepData = stepData[self._xp.isfinite(stepData)] hist = self._xp.histogram(stepData, **kwds) if cp: return cp.asnumpy(hist[1][:-1]), cp.asnumpy(hist[0]) else: return hist[1][:-1], hist[0] def setPxMode(self, b): """ Sets whether the item ignores transformations and draws directly to screen pixels. If `True`, the item will not inherit any scale or rotation transformations from its parent items, but its position will be transformed as usual. (see ``GraphicsItem::ItemIgnoresTransformations`` in the Qt documentation) """ self.setFlag(self.GraphicsItemFlag.ItemIgnoresTransformations, b) def setScaledMode(self): self.setPxMode(False) def getPixmap(self): if self._renderRequired: self.render() if self._unrenderable: return None return QtGui.QPixmap.fromImage(self.qimage) def pixelSize(self): """ Returns the scene-size of a single pixel in the image """ br = self.sceneBoundingRect() if self.image is None: return 1,1 return br.width()/self.width(), br.height()/self.height() def viewTransformChanged(self): if self.autoDownsample: xds, yds = self._computeDownsampleFactors() if xds is None: self._renderRequired = True self._unrenderable = True return if (xds, yds) != self._lastDownsample: self._renderRequired = True self.update() def _computeDownsampleFactors(self): # reduce dimensions of image based on screen resolution o = self.mapToDevice(QtCore.QPointF(0, 0)) x = self.mapToDevice(QtCore.QPointF(1, 0)) y = self.mapToDevice(QtCore.QPointF(0, 1)) # scene may not be available yet if o is None: return None, None w = Point(x - o).length() h = Point(y - o).length() if w == 0 or h == 0: return None, None return max(1, int(1.0 / w)), max(1, int(1.0 / h)) def mouseDragEvent(self, ev): if ev.button() != QtCore.Qt.MouseButton.LeftButton: ev.ignore() return elif self.drawKernel is not None: ev.accept() self.drawAt(ev.pos(), ev) def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.RightButton: if self.raiseContextMenu(ev): ev.accept() if self.drawKernel is not None and ev.button() == QtCore.Qt.MouseButton.LeftButton: self.drawAt(ev.pos(), ev) def raiseContextMenu(self, ev): menu = self.getMenu() if menu is None: return False menu = self.scene().addParentContextMenus(self, menu, ev) pos = ev.screenPos() menu.popup(QtCore.QPoint(pos.x(), pos.y())) return True def getMenu(self): if self.menu is None: if not self.removable: return None self.menu = QtWidgets.QMenu() self.menu.setTitle(translate("ImageItem", "Image")) remAct = QtGui.QAction(translate("ImageItem", "Remove image"), self.menu) remAct.triggered.connect(self.removeClicked) self.menu.addAction(remAct) self.menu.remAct = remAct return self.menu def hoverEvent(self, ev): if not ev.isExit() and self.drawKernel is not None and ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton): ev.acceptClicks(QtCore.Qt.MouseButton.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. ev.acceptClicks(QtCore.Qt.MouseButton.RightButton) elif not ev.isExit() and self.removable: ev.acceptClicks(QtCore.Qt.MouseButton.RightButton) ## accept context menu clicks def tabletEvent(self, ev): pass #print(ev.device()) #print(ev.pointerType()) #print(ev.pressure()) def drawAt(self, pos, ev=None): if self.axisOrder == "col-major": pos = [int(pos.x()), int(pos.y())] else: pos = [int(pos.y()), int(pos.x())] dk = self.drawKernel kc = self.drawKernelCenter sx = [0,dk.shape[0]] sy = [0,dk.shape[1]] tx = [pos[0] - kc[0], pos[0] - kc[0]+ dk.shape[0]] ty = [pos[1] - kc[1], pos[1] - kc[1]+ dk.shape[1]] for i in [0,1]: dx1 = -min(0, tx[i]) dx2 = min(0, self.image.shape[0]-tx[i]) tx[i] += dx1+dx2 sx[i] += dx1+dx2 dy1 = -min(0, ty[i]) dy2 = min(0, self.image.shape[1]-ty[i]) ty[i] += dy1+dy2 sy[i] += dy1+dy2 ts = (slice(tx[0],tx[1]), slice(ty[0],ty[1])) ss = (slice(sx[0],sx[1]), slice(sy[0],sy[1])) mask = self.drawMask src = dk if isinstance(self.drawMode, Callable): self.drawMode(dk, self.image, mask, ss, ts, ev) else: src = src[ss] if self.drawMode == 'set': if mask is not None: mask = mask[ss] self.image[ts] = self.image[ts] * (1-mask) + src * mask else: self.image[ts] = src elif self.drawMode == 'add': self.image[ts] += src else: raise Exception("Unknown draw mode '%s'" % self.drawMode) self.updateImage() def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'): self.drawKernel = kernel self.drawKernelCenter = center self.drawMode = mode self.drawMask = mask def removeClicked(self): ## Send remove event only after we have exited the menu event handler self.removeTimer = QtCore.QTimer() self.removeTimer.timeout.connect(self.emitRemoveRequested) self.removeTimer.start(0) def emitRemoveRequested(self): self.removeTimer.timeout.disconnect(self.emitRemoveRequested) self.sigRemoveRequested.emit(self) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/InfiniteLine.py000066400000000000000000000556351421045507400254220ustar00rootroot00000000000000from math import atan2, degrees import numpy as np from .. import functions as fn from ..Point import Point from ..Qt import QtCore, QtGui from .GraphicsItem import GraphicsItem from .GraphicsObject import GraphicsObject from .TextItem import TextItem from .ViewBox import ViewBox __all__ = ['InfiniteLine', 'InfLineLabel'] class InfiniteLine(GraphicsObject): """ **Bases:** :class:`GraphicsObject ` Displays a line of infinite length. This line may be dragged to indicate a position in data coordinates. =============================== =================================================== **Signals:** sigDragged(self) sigPositionChangeFinished(self) sigPositionChanged(self) sigClicked(self, ev) =============================== =================================================== """ sigDragged = QtCore.Signal(object) sigPositionChangeFinished = QtCore.Signal(object) sigPositionChanged = QtCore.Signal(object) sigClicked = QtCore.Signal(object, object) def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None, hoverPen=None, label=None, labelOpts=None, span=(0, 1), markers=None, name=None): """ =============== ================================================================== **Arguments:** pos Position of the line. This can be a QPointF or a single value for vertical/horizontal lines. angle Angle of line in degrees. 0 is horizontal, 90 is vertical. pen Pen to use when drawing line. Can be any arguments that are valid for :func:`mkPen `. Default pen is transparent yellow. hoverPen Pen to use when the mouse cursor hovers over the line. Only used when movable=True. movable If True, the line can be dragged to a new position by the user. bounds Optional [min, max] bounding values. Bounds are only valid if the line is vertical or horizontal. hoverPen Pen to use when drawing line when hovering over it. Can be any arguments that are valid for :func:`mkPen `. Default pen is red. label Text to be displayed in a label attached to the line, or None to show no label (default is None). May optionally include formatting strings to display the line value. labelOpts A dict of keyword arguments to use when constructing the text label. See :class:`InfLineLabel`. span Optional tuple (min, max) giving the range over the view to draw the line. For example, with a vertical line, use span=(0.5, 1) to draw only on the top half of the view. markers List of (marker, position, size) tuples, one per marker to display on the line. See the addMarker method. name Name of the item =============== ================================================================== """ self._boundingRect = None self._name = name GraphicsObject.__init__(self) if bounds is None: ## allowed value boundaries for orthogonal lines self.maxRange = [None, None] else: self.maxRange = bounds self.moving = False self.setMovable(movable) self.mouseHovering = False self.p = [0, 0] self.setAngle(angle) if pos is None: pos = Point(0,0) self.setPos(pos) if pen is None: pen = (200, 200, 100) self.setPen(pen) if hoverPen is None: self.setHoverPen(color=(255,0,0), width=self.pen.width()) else: self.setHoverPen(hoverPen) self.span = span self.currentPen = self.pen self.markers = [] self._maxMarkerSize = 0 if markers is not None: for m in markers: self.addMarker(*m) # Cache variables for managing bounds self._endPoints = [0, 1] # self._bounds = None self._lastViewSize = None if label is not None: labelOpts = {} if labelOpts is None else labelOpts self.label = InfLineLabel(self, text=label, **labelOpts) def setMovable(self, m): """Set whether the line is movable by the user.""" self.movable = m self.setAcceptHoverEvents(m) def setBounds(self, bounds): """Set the (minimum, maximum) allowable values when dragging.""" self.maxRange = bounds self.setValue(self.value()) def bounds(self): """Return the (minimum, maximum) values allowed when dragging. """ return self.maxRange[:] def setPen(self, *args, **kwargs): """Set the pen for drawing the line. Allowable arguments are any that are valid for :func:`mkPen `.""" self.pen = fn.mkPen(*args, **kwargs) if not self.mouseHovering: self.currentPen = self.pen self.update() def setHoverPen(self, *args, **kwargs): """Set the pen for drawing the line while the mouse hovers over it. Allowable arguments are any that are valid for :func:`mkPen `. If the line is not movable, then hovering is also disabled. Added in version 0.9.9.""" # If user did not supply a width, then copy it from pen widthSpecified = ((len(args) == 1 and (isinstance(args[0], QtGui.QPen) or (isinstance(args[0], dict) and 'width' in args[0])) ) or 'width' in kwargs) self.hoverPen = fn.mkPen(*args, **kwargs) if not widthSpecified: self.hoverPen.setWidth(self.pen.width()) if self.mouseHovering: self.currentPen = self.hoverPen self.update() def addMarker(self, marker, position=0.5, size=10.0): """Add a marker to be displayed on the line. ============= ========================================================= **Arguments** marker String indicating the style of marker to add: ``'<|'``, ``'|>'``, ``'>|'``, ``'|<'``, ``'<|>'``, ``'>|<'``, ``'^'``, ``'v'``, ``'o'`` position Position (0.0-1.0) along the visible extent of the line to place the marker. Default is 0.5. size Size of the marker in pixels. Default is 10.0. ============= ========================================================= """ path = QtGui.QPainterPath() if marker == 'o': path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) if '<|' in marker: p = QtGui.QPolygonF([Point(0.5, 0), Point(0, -0.5), Point(-0.5, 0)]) path.addPolygon(p) path.closeSubpath() if '|>' in marker: p = QtGui.QPolygonF([Point(0.5, 0), Point(0, 0.5), Point(-0.5, 0)]) path.addPolygon(p) path.closeSubpath() if '>|' in marker: p = QtGui.QPolygonF([Point(0.5, -0.5), Point(0, 0), Point(-0.5, -0.5)]) path.addPolygon(p) path.closeSubpath() if '|<' in marker: p = QtGui.QPolygonF([Point(0.5, 0.5), Point(0, 0), Point(-0.5, 0.5)]) path.addPolygon(p) path.closeSubpath() if '^' in marker: p = QtGui.QPolygonF([Point(0, -0.5), Point(0.5, 0), Point(0, 0.5)]) path.addPolygon(p) path.closeSubpath() if 'v' in marker: p = QtGui.QPolygonF([Point(0, -0.5), Point(-0.5, 0), Point(0, 0.5)]) path.addPolygon(p) path.closeSubpath() self.markers.append((path, position, size)) self._maxMarkerSize = max([m[2] / 2. for m in self.markers]) self.update() def clearMarkers(self): """ Remove all markers from this line. """ self.markers = [] self._maxMarkerSize = 0 self.update() def setAngle(self, angle): """ Takes angle argument in degrees. 0 is horizontal; 90 is vertical. Note that the use of value() and setValue() changes if the line is not vertical or horizontal. """ self.angle = angle #((angle+45) % 180) - 45 ## -45 <= angle < 135 self.resetTransform() self.setRotation(self.angle) self.update() def setPos(self, pos): if isinstance(pos, (list, tuple, np.ndarray)) and not np.ndim(pos) == 0: newPos = list(pos) elif isinstance(pos, QtCore.QPointF): newPos = [pos.x(), pos.y()] else: if self.angle == 90: newPos = [pos, 0] elif self.angle == 0: newPos = [0, pos] else: raise Exception("Must specify 2D coordinate for non-orthogonal lines.") ## check bounds (only works for orthogonal lines) if self.angle == 90: if self.maxRange[0] is not None: newPos[0] = max(newPos[0], self.maxRange[0]) if self.maxRange[1] is not None: newPos[0] = min(newPos[0], self.maxRange[1]) elif self.angle == 0: if self.maxRange[0] is not None: newPos[1] = max(newPos[1], self.maxRange[0]) if self.maxRange[1] is not None: newPos[1] = min(newPos[1], self.maxRange[1]) if self.p != newPos: self.p = newPos self.viewTransformChanged() GraphicsObject.setPos(self, Point(self.p)) self.sigPositionChanged.emit(self) def getXPos(self): return self.p[0] def getYPos(self): return self.p[1] def getPos(self): return self.p def value(self): """Return the value of the line. Will be a single number for horizontal and vertical lines, and a list of [x,y] values for diagonal lines.""" if self.angle%180 == 0: return self.getYPos() elif self.angle%180 == 90: return self.getXPos() else: return self.getPos() def setValue(self, v): """Set the position of the line. If line is horizontal or vertical, v can be a single value. Otherwise, a 2D coordinate must be specified (list, tuple and QPointF are all acceptable).""" self.setPos(v) ## broken in 4.7 #def itemChange(self, change, val): #if change in [self.GraphicsItemChange.ItemScenePositionHasChanged, self.GraphicsItemChange.ItemSceneHasChanged]: #self.updateLine() #print "update", change #print self.getBoundingParents() #else: #print "ignore", change #return GraphicsObject.itemChange(self, change, val) def setSpan(self, mn, mx): if self.span != (mn, mx): self.span = (mn, mx) self.update() def _computeBoundingRect(self): #br = UIGraphicsItem.boundingRect(self) vr = self.viewRect() # bounds of containing ViewBox mapped to local coords. if vr is None: return QtCore.QRectF() ## add a 4-pixel radius around the line for mouse interaction. px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line if px is None: px = 0 pw = max(self.pen.width() / 2, self.hoverPen.width() / 2) w = max(4, self._maxMarkerSize + pw) + 1 w = w * px br = QtCore.QRectF(vr) br.setBottom(-w) br.setTop(w) length = br.width() left = br.left() + length * self.span[0] right = br.left() + length * self.span[1] br.setLeft(left) br.setRight(right) br = br.normalized() vs = self.getViewBox().size() if self._bounds != br or self._lastViewSize != vs: self._bounds = br self._lastViewSize = vs self.prepareGeometryChange() self._endPoints = (left, right) self._lastViewRect = vr return self._bounds def boundingRect(self): if self._boundingRect is None: self._boundingRect = self._computeBoundingRect() return self._boundingRect def paint(self, p, *args): p.setRenderHint(p.RenderHint.Antialiasing) left, right = self._endPoints pen = self.currentPen pen.setJoinStyle(QtCore.Qt.PenJoinStyle.MiterJoin) p.setPen(pen) p.drawLine(Point(left, 0), Point(right, 0)) if len(self.markers) == 0: return # paint markers in native coordinate system tr = p.transform() p.resetTransform() start = tr.map(Point(left, 0)) end = tr.map(Point(right, 0)) up = tr.map(Point(left, 1)) dif = end - start length = Point(dif).length() angle = degrees(atan2(dif.y(), dif.x())) p.translate(start) p.rotate(angle) up = up - start det = up.x() * dif.y() - dif.x() * up.y() p.scale(1, 1 if det > 0 else -1) p.setBrush(fn.mkBrush(self.currentPen.color())) #p.setPen(fn.mkPen(None)) tr = p.transform() for path, pos, size in self.markers: p.setTransform(tr) x = length * pos p.translate(x, 0) p.scale(size, size) p.drawPath(path) def dataBounds(self, axis, frac=1.0, orthoRange=None): if axis == 0: return None ## x axis should never be auto-scaled else: return (0,0) def mouseDragEvent(self, ev): if self.movable and ev.button() == QtCore.Qt.MouseButton.LeftButton: if ev.isStart(): self.moving = True self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) self.startPosition = self.pos() ev.accept() if not self.moving: return self.setPos(self.cursorOffset + self.mapToParent(ev.pos())) self.sigDragged.emit(self) if ev.isFinish(): self.moving = False self.sigPositionChangeFinished.emit(self) def mouseClickEvent(self, ev): self.sigClicked.emit(self, ev) if self.moving and ev.button() == QtCore.Qt.MouseButton.RightButton: ev.accept() self.setPos(self.startPosition) self.moving = False self.sigDragged.emit(self) self.sigPositionChangeFinished.emit(self) def hoverEvent(self, ev): if (not ev.isExit()) and self.movable and ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton): self.setMouseHover(True) else: self.setMouseHover(False) def setMouseHover(self, hover): ## Inform the item that the mouse is (not) hovering over it if self.mouseHovering == hover: return self.mouseHovering = hover if hover: self.currentPen = self.hoverPen else: self.currentPen = self.pen self.update() def viewTransformChanged(self): """ Called whenever the transformation matrix of the view has changed. (eg, the view range has changed or the view was resized) """ self._boundingRect = None GraphicsItem.viewTransformChanged(self) def setName(self, name): self._name = name def name(self): return self._name class InfLineLabel(TextItem): """ A TextItem that attaches itself to an InfiniteLine. This class extends TextItem with the following features: * Automatically positions adjacent to the line at a fixed position along the line and within the view box. * Automatically reformats text when the line value has changed. * Can optionally be dragged to change its location along the line. * Optionally aligns to its parent line. =============== ================================================================== **Arguments:** line The InfiniteLine to which this label will be attached. text String to display in the label. May contain a {value} formatting string to display the current value of the line. movable Bool; if True, then the label can be dragged along the line. position Relative position (0.0-1.0) within the view to position the label along the line. anchors List of (x,y) pairs giving the text anchor positions that should be used when the line is moved to one side of the view or the other. This allows text to switch to the opposite side of the line as it approaches the edge of the view. These are automatically selected for some common cases, but may be specified if the default values give unexpected results. =============== ================================================================== All extra keyword arguments are passed to TextItem. A particularly useful option here is to use `rotateAxis=(1, 0)`, which will cause the text to be automatically rotated parallel to the line. """ def __init__(self, line, text="", movable=False, position=0.5, anchors=None, **kwds): self.line = line self.movable = movable self.moving = False self.orthoPos = position # text will always be placed on the line at a position relative to view bounds self.format = text self.line.sigPositionChanged.connect(self.valueChanged) self._endpoints = (None, None) if anchors is None: # automatically pick sensible anchors rax = kwds.get('rotateAxis', None) if rax is not None: if tuple(rax) == (1,0): anchors = [(0.5, 0), (0.5, 1)] else: anchors = [(0, 0.5), (1, 0.5)] else: if line.angle % 180 == 0: anchors = [(0.5, 0), (0.5, 1)] else: anchors = [(0, 0.5), (1, 0.5)] self.anchors = anchors TextItem.__init__(self, **kwds) self.setParentItem(line) self.valueChanged() def valueChanged(self): if not self.isVisible(): return value = self.line.value() self.setText(self.format.format(value=value)) self.updatePosition() def getEndpoints(self): # calculate points where line intersects view box # (in line coordinates) if self._endpoints[0] is None: lr = self.line.boundingRect() pt1 = Point(lr.left(), 0) pt2 = Point(lr.right(), 0) if self.line.angle % 90 != 0: # more expensive to find text position for oblique lines. view = self.getViewBox() if not self.isVisible() or not isinstance(view, ViewBox): # not in a viewbox, skip update return (None, None) p = QtGui.QPainterPath() p.moveTo(pt1) p.lineTo(pt2) p = self.line.itemTransform(view)[0].map(p) vr = QtGui.QPainterPath() vr.addRect(view.boundingRect()) paths = vr.intersected(p).toSubpathPolygons(QtGui.QTransform()) if len(paths) > 0: l = list(paths[0]) pt1 = self.line.mapFromItem(view, l[0]) pt2 = self.line.mapFromItem(view, l[1]) self._endpoints = (pt1, pt2) return self._endpoints def updatePosition(self): # update text position to relative view location along line self._endpoints = (None, None) pt1, pt2 = self.getEndpoints() if pt1 is None: return pt = pt2 * self.orthoPos + pt1 * (1-self.orthoPos) self.setPos(pt) # update anchor to keep text visible as it nears the view box edge vr = self.line.viewRect() if vr is not None: self.setAnchor(self.anchors[0 if vr.center().y() < 0 else 1]) def setVisible(self, v): TextItem.setVisible(self, v) if v: self.valueChanged() def setMovable(self, m): """Set whether this label is movable by dragging along the line. """ self.movable = m self.setAcceptHoverEvents(m) def setPosition(self, p): """Set the relative position (0.0-1.0) of this label within the view box and along the line. For horizontal (angle=0) and vertical (angle=90) lines, a value of 0.0 places the text at the bottom or left of the view, respectively. """ self.orthoPos = p self.updatePosition() def setFormat(self, text): """Set the text format string for this label. May optionally contain "{value}" to include the lines current value (the text will be reformatted whenever the line is moved). """ self.format = text self.valueChanged() def mouseDragEvent(self, ev): if self.movable and ev.button() == QtCore.Qt.MouseButton.LeftButton: if ev.isStart(): self._moving = True self._cursorOffset = self._posToRel(ev.buttonDownPos()) self._startPosition = self.orthoPos ev.accept() if not self._moving: return rel = self._posToRel(ev.pos()) self.orthoPos = fn.clip_scalar(self._startPosition + rel - self._cursorOffset, 0., 1.) self.updatePosition() if ev.isFinish(): self._moving = False def mouseClickEvent(self, ev): if self.moving and ev.button() == QtCore.Qt.MouseButton.RightButton: ev.accept() self.orthoPos = self._startPosition self.moving = False def hoverEvent(self, ev): if not ev.isExit() and self.movable: ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton) def viewTransformChanged(self): GraphicsItem.viewTransformChanged(self) self.updatePosition() TextItem.viewTransformChanged(self) def _posToRel(self, pos): # convert local position to relative position along line between view bounds pt1, pt2 = self.getEndpoints() if pt1 is None: return 0 view = self.getViewBox() pos = self.mapToParent(pos) return (pos.x() - pt1.x()) / (pt2.x()-pt1.x()) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/IsocurveItem.py000066400000000000000000000077061421045507400254570ustar00rootroot00000000000000from .. import functions as fn from .. import getConfigOption from ..Qt import QtCore, QtGui from .GraphicsObject import * __all__ = ['IsocurveItem'] class IsocurveItem(GraphicsObject): """ **Bases:** :class:`GraphicsObject ` Item displaying an isocurve of a 2D array. To align this item correctly with an ImageItem, call ``isocurve.setParentItem(image)``. """ def __init__(self, data=None, level=0, pen='w', axisOrder=None): """ Create a new isocurve item. ============== =============================================================== **Arguments:** data A 2-dimensional ndarray. Can be initialized as None, and set later using :func:`setData ` level The cutoff value at which to draw the isocurve. pen The color of the curve item. Can be anything valid for :func:`mkPen ` axisOrder May be either 'row-major' or 'col-major'. By default this uses the ``imageAxisOrder`` :ref:`global configuration option `. ============== =============================================================== """ GraphicsObject.__init__(self) self.level = level self.data = None self.path = None self.axisOrder = getConfigOption('imageAxisOrder') if axisOrder is None else axisOrder self.setPen(pen) self.setData(data, level) def setData(self, data, level=None): """ Set the data/image to draw isocurves for. ============== ======================================================================== **Arguments:** data A 2-dimensional ndarray. level The cutoff value at which to draw the curve. If level is not specified, the previously set level is used. ============== ======================================================================== """ if level is None: level = self.level self.level = level self.data = data self.path = None self.prepareGeometryChange() self.update() def setLevel(self, level): """Set the level at which the isocurve is drawn.""" self.level = level self.path = None self.prepareGeometryChange() self.update() def setPen(self, *args, **kwargs): """Set the pen used to draw the isocurve. Arguments can be any that are valid for :func:`mkPen `""" self.pen = fn.mkPen(*args, **kwargs) self.update() def setBrush(self, *args, **kwargs): """Set the brush used to draw the isocurve. Arguments can be any that are valid for :func:`mkBrush `""" self.brush = fn.mkBrush(*args, **kwargs) self.update() def updateLines(self, data, level): self.setData(data, level) def boundingRect(self): if self.data is None: return QtCore.QRectF() if self.path is None: self.generatePath() return self.path.boundingRect() def generatePath(self): if self.data is None: self.path = None return if self.axisOrder == 'row-major': data = self.data.T else: data = self.data lines = fn.isocurve(data, self.level, connected=True, extendToEdge=True) self.path = QtGui.QPainterPath() for line in lines: self.path.moveTo(*line[0]) for p in line[1:]: self.path.lineTo(*p) def paint(self, p, *args): if self.data is None: return if self.path is None: self.generatePath() p.setPen(self.pen) p.drawPath(self.path) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ItemGroup.py000066400000000000000000000007701421045507400247460ustar00rootroot00000000000000from ..Qt import QtCore from .GraphicsObject import GraphicsObject __all__ = ['ItemGroup'] class ItemGroup(GraphicsObject): """ Replacement for QGraphicsItemGroup """ def __init__(self, *args): GraphicsObject.__init__(self, *args) self.setFlag(self.GraphicsItemFlag.ItemHasNoContents) def boundingRect(self): return QtCore.QRectF() def paint(self, *args): pass def addItem(self, item): item.setParentItem(self) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/LabelItem.py000066400000000000000000000121001421045507400246570ustar00rootroot00000000000000from .. import functions as fn from .. import getConfigOption from ..Qt import QtCore, QtWidgets from .GraphicsWidget import GraphicsWidget from .GraphicsWidgetAnchor import GraphicsWidgetAnchor __all__ = ['LabelItem'] class LabelItem(GraphicsWidget, GraphicsWidgetAnchor): """ GraphicsWidget displaying text. Used mainly as axis labels, titles, etc. Note: To display text inside a scaled view (ViewBox, PlotWidget, etc) use TextItem """ def __init__(self, text=' ', parent=None, angle=0, **args): GraphicsWidget.__init__(self, parent) GraphicsWidgetAnchor.__init__(self) self.item = QtWidgets.QGraphicsTextItem(self) self.opts = { 'color': None, 'justify': 'center' } self.opts.update(args) self._sizeHint = {} self.setText(text) self.setAngle(angle) def setAttr(self, attr, value): """Set default text properties. See setText() for accepted parameters.""" self.opts[attr] = value def setText(self, text, **args): """Set the text and text properties in the label. Accepts optional arguments for auto-generating a CSS style string: ==================== ============================== **Style Arguments:** color (str) example: '#CCFF00' size (str) example: '8pt' bold (bool) italic (bool) ==================== ============================== """ self.text = text opts = self.opts for k in args: opts[k] = args[k] optlist = [] color = self.opts['color'] if color is None: color = getConfigOption('foreground') color = fn.mkColor(color) optlist.append('color: ' + color.name()) if 'size' in opts: optlist.append('font-size: ' + opts['size']) if 'bold' in opts and opts['bold'] in [True, False]: optlist.append('font-weight: ' + {True:'bold', False:'normal'}[opts['bold']]) if 'italic' in opts and opts['italic'] in [True, False]: optlist.append('font-style: ' + {True:'italic', False:'normal'}[opts['italic']]) full = "%s" % ('; '.join(optlist), text) #print full self.item.setHtml(full) self.updateMin() self.resizeEvent(None) self.updateGeometry() def resizeEvent(self, ev): #c1 = self.boundingRect().center() #c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos() #dif = c1 - c2 #self.item.moveBy(dif.x(), dif.y()) #print c1, c2, dif, self.item.pos() self.item.setPos(0,0) bounds = self.itemRect() left = self.mapFromItem(self.item, QtCore.QPointF(0,0)) - self.mapFromItem(self.item, QtCore.QPointF(1,0)) rect = self.rect() if self.opts['justify'] == 'left': if left.x() != 0: bounds.moveLeft(rect.left()) if left.y() < 0: bounds.moveTop(rect.top()) elif left.y() > 0: bounds.moveBottom(rect.bottom()) elif self.opts['justify'] == 'center': bounds.moveCenter(rect.center()) #bounds = self.itemRect() #self.item.setPos(self.width()/2. - bounds.width()/2., 0) elif self.opts['justify'] == 'right': if left.x() != 0: bounds.moveRight(rect.right()) if left.y() < 0: bounds.moveBottom(rect.bottom()) elif left.y() > 0: bounds.moveTop(rect.top()) #bounds = self.itemRect() #self.item.setPos(self.width() - bounds.width(), 0) self.item.setPos(bounds.topLeft() - self.itemRect().topLeft()) self.updateMin() def setAngle(self, angle): self.angle = angle self.item.resetTransform() self.item.setRotation(angle) self.updateMin() def updateMin(self): bounds = self.itemRect() self.setMinimumWidth(bounds.width()) self.setMinimumHeight(bounds.height()) self._sizeHint = { QtCore.Qt.SizeHint.MinimumSize: (bounds.width(), bounds.height()), QtCore.Qt.SizeHint.PreferredSize: (bounds.width(), bounds.height()), QtCore.Qt.SizeHint.MaximumSize: (-1, -1), #bounds.width()*2, bounds.height()*2), QtCore.Qt.SizeHint.MinimumDescent: (0, 0) ##?? what is this? } self.updateGeometry() def sizeHint(self, hint, constraint): if hint not in self._sizeHint: return QtCore.QSizeF(0, 0) return QtCore.QSizeF(*self._sizeHint[hint]) def itemRect(self): return self.item.mapRectToParent(self.item.boundingRect()) #def paint(self, p, *args): #p.setPen(fn.mkPen('r')) #p.drawRect(self.rect()) #p.setPen(fn.mkPen('g')) #p.drawRect(self.itemRect()) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/LegendItem.py000066400000000000000000000345541421045507400250570ustar00rootroot00000000000000import math from .. import functions as fn from ..icons import invisibleEye from ..Point import Point from ..Qt import QtCore, QtGui, QtWidgets from .BarGraphItem import BarGraphItem from .GraphicsWidget import GraphicsWidget from .GraphicsWidgetAnchor import GraphicsWidgetAnchor from .LabelItem import LabelItem from .PlotDataItem import PlotDataItem from .ScatterPlotItem import ScatterPlotItem, drawSymbol __all__ = ['LegendItem', 'ItemSample'] class LegendItem(GraphicsWidget, GraphicsWidgetAnchor): """ Displays a legend used for describing the contents of a plot. LegendItems are most commonly created by calling :meth:`PlotItem.addLegend `. Note that this item should *not* be added directly to a PlotItem (via :meth:`PlotItem.addItem `). Instead, make it a direct descendant of the PlotItem:: legend.setParentItem(plotItem) """ def __init__(self, size=None, offset=None, horSpacing=25, verSpacing=0, pen=None, brush=None, labelTextColor=None, frame=True, labelTextSize='9pt', colCount=1, sampleType=None, **kwargs): """ ============== =============================================================== **Arguments:** size Specifies the fixed size (width, height) of the legend. If this argument is omitted, the legend will automatically resize to fit its contents. offset Specifies the offset position relative to the legend's parent. Positive values offset from the left or top; negative values offset from the right or bottom. If offset is None, the legend must be anchored manually by calling anchor() or positioned by calling setPos(). horSpacing Specifies the spacing between the line symbol and the label. verSpacing Specifies the spacing between individual entries of the legend vertically. (Can also be negative to have them really close) pen Pen to use when drawing legend border. Any single argument accepted by :func:`mkPen ` is allowed. brush QBrush to use as legend background filling. Any single argument accepted by :func:`mkBrush ` is allowed. labelTextColor Pen to use when drawing legend text. Any single argument accepted by :func:`mkPen ` is allowed. labelTextSize Size to use when drawing legend text. Accepts CSS style string arguments, e.g. '9pt'. colCount Specifies the integer number of columns that the legend should be divided into. The number of rows will be calculated based on this argument. This is useful for plots with many curves displayed simultaneously. Default: 1 column. sampleType Customizes the item sample class of the `LegendItem`. ============== =============================================================== """ GraphicsWidget.__init__(self) GraphicsWidgetAnchor.__init__(self) self.setFlag(self.GraphicsItemFlag.ItemIgnoresTransformations) self.layout = QtWidgets.QGraphicsGridLayout() self.layout.setVerticalSpacing(verSpacing) self.layout.setHorizontalSpacing(horSpacing) self.setLayout(self.layout) self.items = [] self.size = size self.offset = offset self.frame = frame self.columnCount = colCount self.rowCount = 1 if size is not None: self.setGeometry(QtCore.QRectF(0, 0, self.size[0], self.size[1])) if sampleType is not None: if not issubclass(sampleType, GraphicsWidget): raise RuntimeError("Only classes of type `GraphicsWidgets` " "are allowed as `sampleType`") self.sampleType = sampleType else: self.sampleType = ItemSample self.opts = { 'pen': fn.mkPen(pen), 'brush': fn.mkBrush(brush), 'labelTextColor': labelTextColor, 'labelTextSize': labelTextSize, 'offset': offset, } self.opts.update(kwargs) def setSampleType(self, sample): """Set the new sample item claspes""" if sample is self.sampleType: return # Clear the legend, but before create a list of items items = list(self.items) self.sampleType = sample self.clear() # Refill the legend with the item list and new sample item for sample, label in items: plot_item = sample.item plot_name = label.text self.addItem(plot_item, plot_name) self.updateSize() def offset(self): """Get the offset position relative to the parent.""" return self.opts['offset'] def setOffset(self, offset): """Set the offset position relative to the parent.""" self.opts['offset'] = offset offset = Point(self.opts['offset']) anchorx = 1 if offset[0] <= 0 else 0 anchory = 1 if offset[1] <= 0 else 0 anchor = (anchorx, anchory) self.anchor(itemPos=anchor, parentPos=anchor, offset=offset) def pen(self): """Get the QPen used to draw the border around the legend.""" return self.opts['pen'] def setPen(self, *args, **kargs): """Set the pen used to draw a border around the legend. Accepts the same arguments as :func:`~pyqtgraph.mkPen`. """ pen = fn.mkPen(*args, **kargs) self.opts['pen'] = pen self.update() def brush(self): """Get the QBrush used to draw the legend background.""" return self.opts['brush'] def setBrush(self, *args, **kargs): """Set the brush used to draw the legend background. Accepts the same arguments as :func:`~pyqtgraph.mkBrush`. """ brush = fn.mkBrush(*args, **kargs) if self.opts['brush'] == brush: return self.opts['brush'] = brush self.update() def labelTextColor(self): """Get the QColor used for the item labels.""" return self.opts['labelTextColor'] def setLabelTextColor(self, *args, **kargs): """Set the color of the item labels. Accepts the same arguments as :func:`~pyqtgraph.mkColor`. """ self.opts['labelTextColor'] = fn.mkColor(*args, **kargs) for sample, label in self.items: label.setAttr('color', self.opts['labelTextColor']) self.update() def labelTextSize(self): """Get the `labelTextSize` used for the item labels.""" return self.opts['labelTextSize'] def setLabelTextSize(self, size): """Set the `size` of the item labels. Accepts the CSS style string arguments, e.g. '8pt'. """ self.opts['labelTextSize'] = size for _, label in self.items: label.setAttr('size', self.opts['labelTextSize']) self.update() def setParentItem(self, p): """Set the parent.""" ret = GraphicsWidget.setParentItem(self, p) if self.opts['offset'] is not None: offset = Point(self.opts['offset']) anchorx = 1 if offset[0] <= 0 else 0 anchory = 1 if offset[1] <= 0 else 0 anchor = (anchorx, anchory) self.anchor(itemPos=anchor, parentPos=anchor, offset=offset) return ret def addItem(self, item, name): """ Add a new entry to the legend. ============== ======================================================== **Arguments:** item A :class:`~pyqtgraph.PlotDataItem` from which the line and point style of the item will be determined or an instance of ItemSample (or a subclass), allowing the item display to be customized. title The title to display for this item. Simple HTML allowed. ============== ======================================================== """ label = LabelItem(name, color=self.opts['labelTextColor'], justify='left', size=self.opts['labelTextSize']) if isinstance(item, self.sampleType): sample = item else: sample = self.sampleType(item) self.items.append((sample, label)) self._addItemToLayout(sample, label) self.updateSize() def _addItemToLayout(self, sample, label): col = self.layout.columnCount() row = self.layout.rowCount() if row: row -= 1 nCol = self.columnCount * 2 # FIRST ROW FULL if col == nCol: for col in range(0, nCol, 2): # FIND RIGHT COLUMN if not self.layout.itemAt(row, col): break else: if col + 2 == nCol: # MAKE NEW ROW col = 0 row += 1 self.layout.addItem(sample, row, col) self.layout.addItem(label, row, col + 1) # Keep rowCount in sync with the number of rows if items are added self.rowCount = max(self.rowCount, row + 1) def setColumnCount(self, columnCount): """change the orientation of all items of the legend """ if columnCount != self.columnCount: self.columnCount = columnCount self.rowCount = math.ceil(len(self.items) / columnCount) for i in range(self.layout.count() - 1, -1, -1): self.layout.removeAt(i) # clear layout for sample, label in self.items: self._addItemToLayout(sample, label) self.updateSize() def getLabel(self, plotItem): """Return the labelItem inside the legend for a given plotItem The label-text can be changed via labelItem.setText """ out = [(it, lab) for it, lab in self.items if it.item == plotItem] try: return out[0][1] except IndexError: return None def removeItem(self, item): """Removes one item from the legend. ============== ======================================================== **Arguments:** item The item to remove or its name. ============== ======================================================== """ for sample, label in self.items: if sample.item is item or label.text == item: self.items.remove((sample, label)) # remove from itemlist self.layout.removeItem(sample) # remove from layout sample.close() # remove from drawing self.layout.removeItem(label) label.close() self.updateSize() # redraq box return # return after first match def clear(self): """Remove all items from the legend.""" for sample, label in self.items: self.layout.removeItem(sample) sample.close() self.layout.removeItem(label) label.close() self.items = [] self.updateSize() def updateSize(self): if self.size is not None: return height = 0 width = 0 for row in range(self.layout.rowCount()): row_height = 0 col_width = 0 for col in range(self.layout.columnCount()): item = self.layout.itemAt(row, col) if item: col_width += item.width() + 3 row_height = max(row_height, item.height()) width = max(width, col_width) height += row_height self.setGeometry(0, 0, width, height) return def boundingRect(self): return QtCore.QRectF(0, 0, self.width(), self.height()) def paint(self, p, *args): if self.frame: p.setPen(self.opts['pen']) p.setBrush(self.opts['brush']) p.drawRect(self.boundingRect()) def hoverEvent(self, ev): ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton) def mouseDragEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.LeftButton: ev.accept() dpos = ev.pos() - ev.lastPos() self.autoAnchor(self.pos() + dpos) class ItemSample(GraphicsWidget): """Class responsible for drawing a single item in a LegendItem (sans label) """ def __init__(self, item): GraphicsWidget.__init__(self) self.item = item def boundingRect(self): return QtCore.QRectF(0, 0, 20, 20) def paint(self, p, *args): opts = self.item.opts if opts.get('antialias'): p.setRenderHint(p.RenderHint.Antialiasing) visible = self.item.isVisible() if not visible: icon = invisibleEye.qicon p.drawPixmap(QtCore.QPoint(1, 1), icon.pixmap(18, 18)) return if not isinstance(self.item, ScatterPlotItem): p.setPen(fn.mkPen(opts['pen'])) p.drawLine(0, 11, 20, 11) if (opts.get('fillLevel', None) is not None and opts.get('fillBrush', None) is not None): p.setBrush(fn.mkBrush(opts['fillBrush'])) p.setPen(fn.mkPen(opts['pen'])) p.drawPolygon(QtGui.QPolygonF( [QtCore.QPointF(2, 18), QtCore.QPointF(18, 2), QtCore.QPointF(18, 18)])) symbol = opts.get('symbol', None) if symbol is not None: if isinstance(self.item, PlotDataItem): opts = self.item.scatter.opts p.translate(10, 10) drawSymbol(p, symbol, opts['size'], fn.mkPen(opts['pen']), fn.mkBrush(opts['brush'])) if isinstance(self.item, BarGraphItem): p.setBrush(fn.mkBrush(opts['brush'])) p.drawRect(QtCore.QRectF(2, 2, 18, 18)) def mouseClickEvent(self, event): """Use the mouseClick event to toggle the visibility of the plotItem """ if event.button() == QtCore.Qt.MouseButton.LeftButton: visible = self.item.isVisible() self.item.setVisible(not visible) event.accept() self.update() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/LinearRegionItem.py000066400000000000000000000333351421045507400262330ustar00rootroot00000000000000from .. import debug as debug from .. import functions as fn from ..Qt import QtCore, QtGui from .GraphicsObject import GraphicsObject from .InfiniteLine import InfiniteLine __all__ = ['LinearRegionItem'] class LinearRegionItem(GraphicsObject): """ **Bases:** :class:`GraphicsObject ` Used for marking a horizontal or vertical region in plots. The region can be dragged and is bounded by lines which can be dragged individually. =============================== ============================================================================= **Signals:** sigRegionChangeFinished(self) Emitted when the user has finished dragging the region (or one of its lines) and when the region is changed programatically. sigRegionChanged(self) Emitted while the user is dragging the region (or one of its lines) and when the region is changed programatically. =============================== ============================================================================= """ sigRegionChangeFinished = QtCore.Signal(object) sigRegionChanged = QtCore.Signal(object) Vertical = 0 Horizontal = 1 _orientation_axis = { Vertical: 0, Horizontal: 1, 'vertical': 0, 'horizontal': 1, } def __init__(self, values=(0, 1), orientation='vertical', brush=None, pen=None, hoverBrush=None, hoverPen=None, movable=True, bounds=None, span=(0, 1), swapMode='sort', clipItem=None): """Create a new LinearRegionItem. ============== ===================================================================== **Arguments:** values A list of the positions of the lines in the region. These are not limits; limits can be set by specifying bounds. orientation Options are 'vertical' or 'horizontal' The default is 'vertical', indicating that the region is bounded by vertical lines. brush Defines the brush that fills the region. Can be any arguments that are valid for :func:`mkBrush `. Default is transparent blue. pen The pen to use when drawing the lines that bound the region. hoverBrush The brush to use when the mouse is hovering over the region. hoverPen The pen to use when the mouse is hovering over the region. movable If True, the region and individual lines are movable by the user; if False, they are static. bounds Optional [min, max] bounding values for the region span Optional [min, max] giving the range over the view to draw the region. For example, with a vertical line, use ``span=(0.5, 1)`` to draw only on the top half of the view. swapMode Sets the behavior of the region when the lines are moved such that their order reverses: * "block" means the user cannot drag one line past the other * "push" causes both lines to be moved if one would cross the other * "sort" means that lines may trade places, but the output of getRegion always gives the line positions in ascending order. * None means that no attempt is made to handle swapped line positions. The default is "sort". clipItem An item whose bounds will be used to limit the region bounds. This is useful when a LinearRegionItem is added on top of an :class:`~pyqtgraph.ImageItem` or :class:`~pyqtgraph.PlotDataItem` and the visual region should not extend beyond its range. This overrides ``bounds``. ============== ===================================================================== """ GraphicsObject.__init__(self) self.orientation = orientation self.blockLineSignal = False self.moving = False self.mouseHovering = False self.span = span self.swapMode = swapMode self.clipItem = clipItem self._boundingRectCache = None self._clipItemBoundsCache = None # note LinearRegionItem.Horizontal and LinearRegionItem.Vertical # are kept for backward compatibility. lineKwds = dict( movable=movable, bounds=bounds, span=span, pen=pen, hoverPen=hoverPen, ) if orientation in ('horizontal', LinearRegionItem.Horizontal): self.lines = [ # rotate lines to 180 to preserve expected line orientation # with respect to region. This ensures that placing a '<|' # marker on lines[0] causes it to point left in vertical mode # and down in horizontal mode. InfiniteLine(QtCore.QPointF(0, values[0]), angle=0, **lineKwds), InfiniteLine(QtCore.QPointF(0, values[1]), angle=0, **lineKwds)] tr = QtGui.QTransform.fromScale(1, -1) self.lines[0].setTransform(tr, True) self.lines[1].setTransform(tr, True) elif orientation in ('vertical', LinearRegionItem.Vertical): self.lines = [ InfiniteLine(QtCore.QPointF(values[0], 0), angle=90, **lineKwds), InfiniteLine(QtCore.QPointF(values[1], 0), angle=90, **lineKwds)] else: raise Exception("Orientation must be 'vertical' or 'horizontal'.") for l in self.lines: l.setParentItem(self) l.sigPositionChangeFinished.connect(self.lineMoveFinished) self.lines[0].sigPositionChanged.connect(self._line0Moved) self.lines[1].sigPositionChanged.connect(self._line1Moved) if brush is None: brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50)) self.setBrush(brush) if hoverBrush is None: c = self.brush.color() c.setAlpha(min(c.alpha() * 2, 255)) hoverBrush = fn.mkBrush(c) self.setHoverBrush(hoverBrush) self.setMovable(movable) def getRegion(self): """Return the values at the edges of the region.""" r = (self.lines[0].value(), self.lines[1].value()) if self.swapMode == 'sort': return (min(r), max(r)) else: return r def setRegion(self, rgn): """Set the values for the edges of the region. ============== ============================================== **Arguments:** rgn A list or tuple of the lower and upper values. ============== ============================================== """ if self.lines[0].value() == rgn[0] and self.lines[1].value() == rgn[1]: return self.blockLineSignal = True self.lines[0].setValue(rgn[0]) self.blockLineSignal = False self.lines[1].setValue(rgn[1]) #self.blockLineSignal = False self.lineMoved(0) self.lineMoved(1) self.lineMoveFinished() def setBrush(self, *br, **kargs): """Set the brush that fills the region. Can have any arguments that are valid for :func:`mkBrush `. """ self.brush = fn.mkBrush(*br, **kargs) self.currentBrush = self.brush def setHoverBrush(self, *br, **kargs): """Set the brush that fills the region when the mouse is hovering over. Can have any arguments that are valid for :func:`mkBrush `. """ self.hoverBrush = fn.mkBrush(*br, **kargs) def setBounds(self, bounds): """Set ``(min, max)`` bounding values for the region. The current position is only affected it is outside the new bounds. See :func:`~pyqtgraph.LinearRegionItem.setRegion` to set the position of the region. Use ``(None, None)`` to disable bounds. """ if self.clipItem is not None: self.setClipItem(None) self._setBounds(bounds) def _setBounds(self, bounds): # internal impl so user-facing setBounds can clear clipItem and clipItem can # set bounds without clearing itself for line in self.lines: line.setBounds(bounds) def setMovable(self, m=True): """Set lines to be movable by the user, or not. If lines are movable, they will also accept HoverEvents.""" for line in self.lines: line.setMovable(m) self.movable = m self.setAcceptHoverEvents(m) def setSpan(self, mn, mx): if self.span == (mn, mx): return self.span = (mn, mx) for line in self.lines: line.setSpan(mn, mx) self.update() def setClipItem(self, item=None): """Set an item to which the region is bounded. If ``None``, bounds are disabled. """ self.clipItem = item self._clipItemBoundsCache = None if item is None: self._setBounds((None, None)) if item is not None: self._updateClipItemBounds() def _updateClipItemBounds(self): # set region bounds corresponding to clipItem item_vb = self.clipItem.getViewBox() if item_vb is None: return item_bounds = item_vb.childrenBounds(items=(self.clipItem,)) if item_bounds == self._clipItemBoundsCache or None in item_bounds: return self._clipItemBoundsCache = item_bounds if self.orientation in ('horizontal', LinearRegionItem.Horizontal): self._setBounds(item_bounds[1]) else: self._setBounds(item_bounds[0]) def boundingRect(self): br = QtCore.QRectF(self.viewRect()) # bounds of containing ViewBox mapped to local coords. if self.clipItem is not None: self._updateClipItemBounds() rng = self.getRegion() if self.orientation in ('vertical', LinearRegionItem.Vertical): br.setLeft(rng[0]) br.setRight(rng[1]) length = br.height() br.setBottom(br.top() + length * self.span[1]) br.setTop(br.top() + length * self.span[0]) else: br.setTop(rng[0]) br.setBottom(rng[1]) length = br.width() br.setRight(br.left() + length * self.span[1]) br.setLeft(br.left() + length * self.span[0]) br = br.normalized() if self._boundingRectCache != br: self._boundingRectCache = br self.prepareGeometryChange() return br def paint(self, p, *args): profiler = debug.Profiler() p.setBrush(self.currentBrush) p.setPen(fn.mkPen(None)) p.drawRect(self.boundingRect()) def dataBounds(self, axis, frac=1.0, orthoRange=None): if axis == self._orientation_axis[self.orientation]: return self.getRegion() else: return None def lineMoved(self, i): if self.blockLineSignal: return # lines swapped if self.lines[0].value() > self.lines[1].value(): if self.swapMode == 'block': self.lines[i].setValue(self.lines[1-i].value()) elif self.swapMode == 'push': self.lines[1-i].setValue(self.lines[i].value()) self.prepareGeometryChange() self.sigRegionChanged.emit(self) def _line0Moved(self): self.lineMoved(0) def _line1Moved(self): self.lineMoved(1) def lineMoveFinished(self): self.sigRegionChangeFinished.emit(self) def mouseDragEvent(self, ev): if not self.movable or ev.button() != QtCore.Qt.MouseButton.LeftButton: return ev.accept() if ev.isStart(): bdp = ev.buttonDownPos() self.cursorOffsets = [l.pos() - bdp for l in self.lines] self.startPositions = [l.pos() for l in self.lines] self.moving = True if not self.moving: return self.lines[0].blockSignals(True) # only want to update once for i, l in enumerate(self.lines): l.setPos(self.cursorOffsets[i] + ev.pos()) self.lines[0].blockSignals(False) self.prepareGeometryChange() if ev.isFinish(): self.moving = False self.sigRegionChangeFinished.emit(self) else: self.sigRegionChanged.emit(self) def mouseClickEvent(self, ev): if self.moving and ev.button() == QtCore.Qt.MouseButton.RightButton: ev.accept() for i, l in enumerate(self.lines): l.setPos(self.startPositions[i]) self.moving = False self.sigRegionChanged.emit(self) self.sigRegionChangeFinished.emit(self) def hoverEvent(self, ev): if self.movable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton): self.setMouseHover(True) else: self.setMouseHover(False) def setMouseHover(self, hover): ## Inform the item that the mouse is(not) hovering over it if self.mouseHovering == hover: return self.mouseHovering = hover if hover: self.currentBrush = self.hoverBrush else: self.currentBrush = self.brush self.update() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/MultiPlotItem.py000066400000000000000000000045541421045507400256070ustar00rootroot00000000000000""" MultiPlotItem.py - Graphics item used for displaying an array of PlotItems Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ from . import GraphicsLayout __all__ = ['MultiPlotItem'] class MultiPlotItem(GraphicsLayout.GraphicsLayout): """ :class:`~pyqtgraph.GraphicsLayout` that automatically generates a grid of plots from a MetaArray. .. seealso:: :class:`~pyqtgraph.MultiPlotWidget`: Widget containing a MultiPlotItem """ def __init__(self, *args, **kwds): GraphicsLayout.GraphicsLayout.__init__(self, *args, **kwds) self.plots = [] def plot(self, data, **plotArgs): """Plot the data from a MetaArray with each array column as a separate :class:`~pyqtgraph.PlotItem`. Axis labels are automatically extracted from the array info. ``plotArgs`` are passed to :meth:`PlotItem.plot `. """ #self.layout.clear() if hasattr(data, 'implements') and data.implements('MetaArray'): if data.ndim != 2: raise Exception("MultiPlot currently only accepts 2D MetaArray.") ic = data.infoCopy() ax = 0 for i in [0, 1]: if 'cols' in ic[i]: ax = i break #print "Plotting using axis %d as columns (%d plots)" % (ax, data.shape[ax]) for i in range(data.shape[ax]): pi = self.addPlot() self.nextRow() sl = [slice(None)] * 2 sl[ax] = i pi.plot(data[tuple(sl)], **plotArgs) #self.layout.addItem(pi, i, 0) self.plots.append((pi, i, 0)) info = ic[ax]['cols'][i] title = info.get('title', info.get('name', None)) units = info.get('units', None) pi.setLabel('left', text=title, units=units) info = ic[1-ax] title = info.get('title', info.get('name', None)) units = info.get('units', None) pi.setLabel('bottom', text=title, units=units) else: raise Exception("Data type %s not (yet?) supported for MultiPlot." % type(data)) def close(self): for p in self.plots: p[0].close() self.plots = None self.clear() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/NonUniformImage.py000066400000000000000000000077131421045507400260740ustar00rootroot00000000000000import math import numpy as np from .. import functions as fn from .. import mkBrush, mkPen from ..colormap import ColorMap from ..Qt import QtCore, QtGui from .GraphicsObject import GraphicsObject __all__ = ['NonUniformImage'] class NonUniformImage(GraphicsObject): """ **Bases:** :class:`GraphicsObject ` GraphicsObject displaying an image with non-uniform sample points. It's commonly used to display 2-d or slices of higher dimensional data that have a regular but non-uniform grid e.g. measurements or simulation results. """ def __init__(self, x, y, z, border=None): GraphicsObject.__init__(self) # convert to numpy arrays x = np.asarray(x, dtype=np.float64) y = np.asarray(y, dtype=np.float64) z = np.asarray(z, dtype=np.float64) if x.ndim != 1 or y.ndim != 1: raise Exception("x and y must be 1-d arrays.") if np.any(np.diff(x) < 0) or np.any(np.diff(y) < 0): raise Exception("The values in x and y must be monotonically increasing.") if len(z.shape) != 2 or z.shape != (x.size, y.size): raise Exception("The length of x and y must match the shape of z.") # default colormap (black - white) self.cmap = ColorMap(pos=[0.0, 1.0], color=[(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)]) self.data = (x, y, z) self.lut = None self.border = border self.generatePicture() def setLookupTable(self, lut, autoLevel=False): lut.sigLevelsChanged.connect(self.generatePicture) lut.gradient.sigGradientChanged.connect(self.generatePicture) self.lut = lut if autoLevel: _, _, z = self.data f = z[np.isfinite(z)] lut.setLevels(f.min(), f.max()) self.generatePicture() def setColorMap(self, cmap): self.cmap = cmap self.generatePicture() def getHistogram(self, **kwds): """Returns x and y arrays containing the histogram values for the current image. For an explanation of the return format, see numpy.histogram(). """ z = self.data[2] z = z[np.isfinite(z)] hist = np.histogram(z, **kwds) return hist[1][:-1], hist[0] def generatePicture(self): x, y, z = self.data self.picture = QtGui.QPicture() p = QtGui.QPainter(self.picture) p.setPen(mkPen(None)) # normalize if self.lut is not None: mn, mx = self.lut.getLevels() else: f = z[np.isfinite(z)] mn = f.min() mx = f.max() # draw the tiles for i in range(x.size): for j in range(y.size): value = z[i, j] if np.isneginf(value): value = 0.0 elif np.isposinf(value): value = 1.0 elif math.isnan(value): continue # ignore NaN else: value = (value - mn) / (mx - mn) # normalize if self.lut: color = self.lut.gradient.getColor(value) else: color = self.cmap.mapToQColor(value) p.setBrush(mkBrush(color)) # left, right, bottom, top l = x[0] if i == 0 else (x[i - 1] + x[i]) / 2 r = (x[i] + x[i + 1]) / 2 if i < x.size - 1 else x[-1] b = y[0] if j == 0 else (y[j - 1] + y[j]) / 2 t = (y[j] + y[j + 1]) / 2 if j < y.size - 1 else y[-1] p.drawRect(QtCore.QRectF(l, t, r - l, b - t)) if self.border is not None: p.setPen(self.border) p.setBrush(fn.mkBrush(None)) p.drawRect(self.boundingRect()) p.end() self.update() def paint(self, p, *args): p.drawPicture(0, 0, self.picture) def boundingRect(self): return QtCore.QRectF(self.picture.boundingRect()) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PColorMeshItem.py000066400000000000000000000235071421045507400256700ustar00rootroot00000000000000import itertools import warnings import numpy as np from .. import Qt, colormap from .. import functions as fn from ..Qt import QtCore, QtGui from .GradientEditorItem import Gradients # List of colormaps from .GraphicsObject import GraphicsObject __all__ = ['PColorMeshItem'] if Qt.QT_LIB.startswith('PyQt'): wrapinstance = Qt.sip.wrapinstance else: wrapinstance = Qt.shiboken.wrapInstance class QuadInstances: def __init__(self): self.polys = [] def alloc(self, size): self.polys.clear() # 2 * (size + 1) vertices, (x, y) arr = np.empty((2 * (size + 1), 2), dtype=np.float64) ptrs = list(map(wrapinstance, itertools.count(arr.ctypes.data, arr.strides[0]), itertools.repeat(QtCore.QPointF, arr.shape[0]))) # arrange into 2 rows, (size + 1) vertices points = [ptrs[:len(ptrs)//2], ptrs[len(ptrs)//2:]] self.arr = arr.reshape((2, -1, 2)) # pre-create quads from those 2 rows of QPointF(s) for j in range(size): bl, tl = points[0][j:j+2] br, tr = points[1][j:j+2] poly = (bl, br, tr, tl) self.polys.append(poly) def array(self, size): if size != len(self.polys): self.alloc(size) return self.arr def instances(self): return self.polys class PColorMeshItem(GraphicsObject): """ **Bases:** :class:`GraphicsObject ` """ def __init__(self, *args, **kwargs): """ Create a pseudocolor plot with convex polygons. Call signature: ``PColorMeshItem([x, y,] z, **kwargs)`` x and y can be used to specify the corners of the quadrilaterals. z must be used to specified to color of the quadrilaterals. Parameters ---------- x, y : np.ndarray, optional, default None 2D array containing the coordinates of the polygons z : np.ndarray 2D array containing the value which will be mapped into the polygons colors. If x and y is None, the polygons will be displaced on a grid otherwise x and y will be used as polygons vertices coordinates as:: (x[i+1, j], y[i+1, j]) (x[i+1, j+1], y[i+1, j+1]) +---------+ | z[i, j] | +---------+ (x[i, j], y[i, j]) (x[i, j+1], y[i, j+1]) "ASCII from: ". colorMap : pg.ColorMap, default pg.colormap.get('viridis') Colormap used to map the z value to colors. edgecolors : dict, default None The color of the edges of the polygons. Default None means no edges. The dict may contains any arguments accepted by :func:`mkColor() `. Example: ``mkPen(color='w', width=2)`` antialiasing : bool, default False Whether to draw edgelines with antialiasing. Note that if edgecolors is None, antialiasing is always False. """ GraphicsObject.__init__(self) self.qpicture = None ## rendered picture for display self.x = None self.y = None self.z = None self.edgecolors = kwargs.get('edgecolors', None) self.antialiasing = kwargs.get('antialiasing', False) if 'colorMap' in kwargs: cmap = kwargs.get('colorMap') if not isinstance(cmap, colormap.ColorMap): raise ValueError('colorMap argument must be a ColorMap instance') self.cmap = cmap elif 'cmap' in kwargs: # legacy unadvertised argument for backwards compatibility. # this will only use colormaps from Gradients. # Note that the colors will be wrong for the hsv colormaps. warnings.warn( "The parameter 'cmap' will be removed in a version of PyQtGraph released after Nov 2022.", DeprecationWarning, stacklevel=2 ) cmap = kwargs.get('cmap') if not isinstance(cmap, str) or cmap not in Gradients: raise NameError('Undefined colormap, should be one of the following: '+', '.join(['"'+i+'"' for i in Gradients.keys()])+'.') pos, color = zip(*Gradients[cmap]['ticks']) self.cmap = colormap.ColorMap(pos, color) else: self.cmap = colormap.get('viridis') lut_qcolor = self.cmap.getLookupTable(nPts=256, mode=self.cmap.QCOLOR) self.lut_qbrush = [QtGui.QBrush(x) for x in lut_qcolor] self.quads = QuadInstances() # If some data have been sent we directly display it if len(args)>0: self.setData(*args) def _prepareData(self, args): """ Check the shape of the data. Return a set of 2d array x, y, z ready to be used to draw the picture. """ # User didn't specified data if len(args)==0: self.x = None self.y = None self.z = None # User only specified z elif len(args)==1: # If x and y is None, the polygons will be displaced on a grid x = np.arange(0, args[0].shape[0]+1, 1) y = np.arange(0, args[0].shape[1]+1, 1) self.x, self.y = np.meshgrid(x, y, indexing='ij') self.z = args[0] # User specified x, y, z elif len(args)==3: # Shape checking if args[0].shape[0] != args[2].shape[0]+1 or args[0].shape[1] != args[2].shape[1]+1: raise ValueError('The dimension of x should be one greater than the one of z') if args[1].shape[0] != args[2].shape[0]+1 or args[1].shape[1] != args[2].shape[1]+1: raise ValueError('The dimension of y should be one greater than the one of z') self.x = args[0] self.y = args[1] self.z = args[2] else: ValueError('Data must been sent as (z) or (x, y, z)') def setData(self, *args): """ Set the data to be drawn. Parameters ---------- x, y : np.ndarray, optional, default None 2D array containing the coordinates of the polygons z : np.ndarray 2D array containing the value which will be mapped into the polygons colors. If x and y is None, the polygons will be displaced on a grid otherwise x and y will be used as polygons vertices coordinates as:: (x[i+1, j], y[i+1, j]) (x[i+1, j+1], y[i+1, j+1]) +---------+ | z[i, j] | +---------+ (x[i, j], y[i, j]) (x[i, j+1], y[i, j+1]) "ASCII from: ". """ # Prepare data self._prepareData(args) # Has the view bounds changed shapeChanged = False if self.qpicture is None: shapeChanged = True elif len(args)==1: if args[0].shape[0] != self.x[:,1][-1] or args[0].shape[1] != self.y[0][-1]: shapeChanged = True elif len(args)==3: if np.any(self.x != args[0]) or np.any(self.y != args[1]): shapeChanged = True self.qpicture = QtGui.QPicture() painter = QtGui.QPainter(self.qpicture) # We set the pen of all polygons once if self.edgecolors is None: painter.setPen(QtCore.Qt.PenStyle.NoPen) else: painter.setPen(fn.mkPen(self.edgecolors)) if self.antialiasing: painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) ## Prepare colormap # First we get the LookupTable lut = self.lut_qbrush # Second we associate each z value, that we normalize, to the lut scale = len(lut) - 1 z_min = self.z.min() z_max = self.z.max() rng = z_max - z_min if rng == 0: rng = 1 norm = fn.rescaleData(self.z, scale / rng, z_min, dtype=int, clip=(0, len(lut)-1)) if Qt.QT_LIB.startswith('PyQt'): drawConvexPolygon = lambda x : painter.drawConvexPolygon(*x) else: drawConvexPolygon = painter.drawConvexPolygon memory = self.quads.array(self.z.shape[1]) polys = self.quads.instances() # Go through all the data and draw the polygons accordingly for i in range(self.z.shape[0]): # populate 2 rows of values into points memory[..., 0] = self.x[i:i+2, :] memory[..., 1] = self.y[i:i+2, :] brushes = [lut[z] for z in norm[i].tolist()] for brush, poly in zip(brushes, polys): painter.setBrush(brush) drawConvexPolygon(poly) painter.end() self.update() self.prepareGeometryChange() if shapeChanged: self.informViewBoundsChanged() def paint(self, p, *args): if self.z is None: return p.drawPicture(0, 0, self.qpicture) def setBorder(self, b): self.border = fn.mkPen(b) self.update() def width(self): if self.x is None: return None return np.max(self.x) def height(self): if self.y is None: return None return np.max(self.y) def boundingRect(self): if self.qpicture is None: return QtCore.QRectF(0., 0., 0., 0.) return QtCore.QRectF(self.qpicture.boundingRect()) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PlotCurveItem.py000066400000000000000000001067671421045507400256120ustar00rootroot00000000000000from ..Qt import QtCore, QtGui, QtWidgets HAVE_OPENGL = hasattr(QtWidgets, 'QOpenGLWidget') import itertools import math import sys import warnings import numpy as np from .. import Qt, debug from .. import functions as fn from .. import getConfigOption from .GraphicsObject import GraphicsObject __all__ = ['PlotCurveItem'] if Qt.QT_LIB.startswith('PyQt'): wrapinstance = Qt.sip.wrapinstance else: wrapinstance = Qt.shiboken.wrapInstance class LineSegments: 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] def arrayToLineSegments(self, x, y, connect, finiteCheck): # analogue of arrayToQPath taking the same parameters if len(x) < 2: return [] connect_array = None if isinstance(connect, np.ndarray): connect_array, connect = connect, 'array' all_finite = True if finiteCheck or connect == 'finite': mask = np.isfinite(x) & np.isfinite(y) all_finite = np.all(mask) if connect == 'all': if not all_finite: # remove non-finite points, if any x = x.compress(mask) y = y.compress(mask) elif connect == 'finite': if not all_finite: # each non-finite point affects the segment before and after connect_array = mask[:-1] & mask[1:] elif connect in ['pairs', 'array']: if not all_finite: # replicate the behavior of arrayToQPath backfill_idx = fn._compute_backfill_indices(mask) x = x[backfill_idx] y = y[backfill_idx] npts = len(x) if npts < 2: return [] segs = [] if connect in ['all', 'finite', 'array']: memory = self.array(npts - 1) memory[:, 0] = x[:-1] memory[:, 1] = y[:-1] memory[:, 2] = x[1:] memory[:, 3] = y[1:] segs = self.instances(npts - 1) if connect_array is not None: segs = list(itertools.compress(segs, connect_array.tolist())) elif connect in ['pairs']: npairs = npts // 2 memory = self.array(npairs).reshape((-1, 2)) memory[:, 0] = x[:npairs * 2] memory[:, 1] = y[:npairs * 2] segs = self.instances(npairs) return segs class PlotCurveItem(GraphicsObject): """ Class representing a single plot curve. Instances of this class are created automatically as part of :class:`PlotDataItem `; these rarely need to be instantiated directly. Features: - Fast data update - Fill under curve - Mouse interaction ===================== =============================================== **Signals:** sigPlotChanged(self) Emitted when the data being plotted has changed sigClicked(self, ev) Emitted when the curve is clicked ===================== =============================================== """ sigPlotChanged = QtCore.Signal(object) sigClicked = QtCore.Signal(object, object) def __init__(self, *args, **kargs): """ Forwards all arguments to :func:`setData `. Some extra arguments are accepted as well: ============== ======================================================= **Arguments:** parent The parent GraphicsObject (optional) clickable If `True`, the item will emit ``sigClicked`` when it is clicked on. Defaults to `False`. ============== ======================================================= """ GraphicsObject.__init__(self, kargs.get('parent', None)) self.clear() ## this is disastrous for performance. #self.setCacheMode(QtWidgets.QGraphicsItem.CacheMode.DeviceCoordinateCache) self.metaData = {} self.opts = { 'shadowPen': None, 'fillLevel': None, 'fillOutline': False, 'brush': None, 'stepMode': None, 'name': None, 'antialias': getConfigOption('antialias'), 'connect': 'all', 'mouseWidth': 8, # width of shape responding to mouse click 'compositionMode': None, 'skipFiniteCheck': False } if 'pen' not in kargs: self.opts['pen'] = fn.mkPen('w') self.setClickable(kargs.get('clickable', False)) self.setData(*args, **kargs) def implements(self, interface=None): ints = ['plotData'] if interface is None: return ints return interface in ints def name(self): return self.opts.get('name', None) def setClickable(self, s, width=None): """Sets whether the item responds to mouse clicks. The `width` argument specifies the width in pixels orthogonal to the curve that will respond to a mouse click. """ self.clickable = s if width is not None: self.opts['mouseWidth'] = width self._mouseShape = None self._boundingRect = None def setCompositionMode(self, mode): """ Change the composition mode of the item. This is useful when overlaying multiple items. Parameters ---------- mode : ``QtGui.QPainter.CompositionMode`` Composition of the item, often used when overlaying items. Common options include: ``QPainter.CompositionMode.CompositionMode_SourceOver`` (Default) Image replaces the background if it is opaque. Otherwise, it uses the alpha channel to blend the image with the background. ``QPainter.CompositionMode.CompositionMode_Overlay`` Image color is mixed with the background color to reflect the lightness or darkness of the background ``QPainter.CompositionMode.CompositionMode_Plus`` Both the alpha and color of the image and background pixels are added together. ``QPainter.CompositionMode.CompositionMode_Plus`` The output is the image color multiplied by the background. See ``QPainter::CompositionMode`` in the Qt Documentation for more options and details """ self.opts['compositionMode'] = mode self.update() def getData(self): return self.xData, self.yData def dataBounds(self, ax, frac=1.0, orthoRange=None): ## Need this to run as fast as possible. ## check cache first: cache = self._boundsCache[ax] if cache is not None and cache[0] == (frac, orthoRange): return cache[1] (x, y) = self.getData() if x is None or len(x) == 0: return (None, None) if ax == 0: d = x d2 = y elif ax == 1: d = y d2 = x else: raise ValueError("Invalid axis value") ## If an orthogonal range is specified, mask the data now if orthoRange is not None: mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) d = d[mask] #d2 = d2[mask] if len(d) == 0: return (None, None) ## Get min/max (or percentiles) of the requested data range if frac >= 1.0: # include complete data range # first try faster nanmin/max function, then cut out infs if needed. with warnings.catch_warnings(): # All-NaN data is acceptable; Explicit numpy warning is not needed. warnings.simplefilter("ignore") b = (np.nanmin(d), np.nanmax(d)) if math.isinf(b[0]) or math.isinf(b[1]): mask = np.isfinite(d) d = d[mask] if len(d) == 0: return (None, None) b = (d.min(), d.max()) elif frac <= 0.0: raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) else: # include a percentile of data range mask = np.isfinite(d) d = d[mask] if len(d) == 0: return (None, None) b = np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) ## adjust for fill level if ax == 1 and self.opts['fillLevel'] not in [None, 'enclosed']: b = (min(b[0], self.opts['fillLevel']), max(b[1], self.opts['fillLevel'])) ## Add pen width only if it is non-cosmetic. pen = self.opts['pen'] spen = self.opts['shadowPen'] if not pen.isCosmetic(): b = (b[0] - pen.widthF()*0.7072, b[1] + pen.widthF()*0.7072) if spen is not None and not spen.isCosmetic() and spen.style() != QtCore.Qt.PenStyle.NoPen: b = (b[0] - spen.widthF()*0.7072, b[1] + spen.widthF()*0.7072) self._boundsCache[ax] = [(frac, orthoRange), b] return b def pixelPadding(self): pen = self.opts['pen'] spen = self.opts['shadowPen'] w = 0 if pen.isCosmetic(): w += pen.widthF()*0.7072 if spen is not None and spen.isCosmetic() and spen.style() != QtCore.Qt.PenStyle.NoPen: w = max(w, spen.widthF()*0.7072) if self.clickable: w = max(w, self.opts['mouseWidth']//2 + 1) return w def boundingRect(self): if self._boundingRect is None: (xmn, xmx) = self.dataBounds(ax=0) if xmn is None or xmx is None: return QtCore.QRectF() (ymn, ymx) = self.dataBounds(ax=1) if ymn is None or ymx is None: return QtCore.QRectF() px = py = 0.0 pxPad = self.pixelPadding() if pxPad > 0: # determine length of pixel in local x, y directions px, py = self.pixelVectors() try: px = 0 if px is None else px.length() except OverflowError: px = 0 try: py = 0 if py is None else py.length() except OverflowError: py = 0 # return bounds expanded by pixel size px *= pxPad py *= pxPad #px += self._maxSpotWidth * 0.5 #py += self._maxSpotWidth * 0.5 self._boundingRect = QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) return self._boundingRect def viewTransformChanged(self): self.invalidateBounds() self.prepareGeometryChange() #def boundingRect(self): #if self._boundingRect is None: #(x, y) = self.getData() #if x is None or y is None or len(x) == 0 or len(y) == 0: #return QtCore.QRectF() #if self.opts['shadowPen'] is not None: #lineWidth = (max(self.opts['pen'].width(), self.opts['shadowPen'].width()) + 1) #else: #lineWidth = (self.opts['pen'].width()+1) #pixels = self.pixelVectors() #if pixels == (None, None): #pixels = [Point(0,0), Point(0,0)] #xmin = x.min() #xmax = x.max() #ymin = y.min() #ymax = y.max() #if self.opts['fillLevel'] is not None: #ymin = min(ymin, self.opts['fillLevel']) #ymax = max(ymax, self.opts['fillLevel']) #xmin -= pixels[0].x() * lineWidth #xmax += pixels[0].x() * lineWidth #ymin -= abs(pixels[1].y()) * lineWidth #ymax += abs(pixels[1].y()) * lineWidth #self._boundingRect = QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin) #return self._boundingRect def invalidateBounds(self): self._boundingRect = None self._boundsCache = [None, None] def setPen(self, *args, **kargs): """Set the pen used to draw the curve.""" if args[0] is None: self.opts['pen'] = None else: self.opts['pen'] = fn.mkPen(*args, **kargs) self.invalidateBounds() self.update() def setShadowPen(self, *args, **kargs): """ Set the shadow pen used to draw behind the primary pen. This pen must have a larger width than the primary pen to be visible. Arguments are passed to :func:`mkPen ` """ if args[0] is None: self.opts['shadowPen'] = None else: self.opts['shadowPen'] = fn.mkPen(*args, **kargs) self.invalidateBounds() self.update() def setBrush(self, *args, **kargs): """ Sets the brush used when filling the area under the curve. All arguments are passed to :func:`mkBrush `. """ if args[0] is None: self.opts['brush'] = None else: self.opts['brush'] = fn.mkBrush(*args, **kargs) self.invalidateBounds() self.update() def setFillLevel(self, level): """Sets the level filled to when filling under the curve""" self.opts['fillLevel'] = level self.fillPath = None self._fillPathList = None self.invalidateBounds() self.update() def setSkipFiniteCheck(self, skipFiniteCheck): """ When it is known that the plot data passed to ``PlotCurveItem`` contains only finite numerical values, the `skipFiniteCheck` property can help speed up plotting. If this flag is set and the data contains any non-finite values (such as `NaN` or `Inf`), unpredictable behavior will occur. The data might not be plotted, or there migth be significant performance impact. """ self.opts['skipFiniteCheck'] = bool(skipFiniteCheck) def setData(self, *args, **kargs): """ =============== ================================================================= **Arguments:** x, y (numpy arrays) Data to display pen Pen to use when drawing. Any single argument accepted by :func:`mkPen ` is allowed. shadowPen Pen for drawing behind the primary pen. Usually this is used to emphasize the curve by providing a high-contrast border. Any single argument accepted by :func:`mkPen ` is allowed. fillLevel (float or None) Fill the area under the curve to the specified value. fillOutline (bool) If True, an outline surrounding the `fillLevel` area is drawn. brush Brush to use when filling. Any single argument accepted by :func:`mkBrush ` is allowed. antialias (bool) Whether to use antialiasing when drawing. This is disabled by default because it decreases performance. stepMode (str or None) If 'center', a step is drawn using the `x` values as boundaries and the given `y` values are associated to the mid-points between the boundaries of each step. This is commonly used when drawing histograms. Note that in this case, ``len(x) == len(y) + 1`` If 'left' or 'right', the step is drawn assuming that the `y` value is associated to the left or right boundary, respectively. In this case ``len(x) == len(y)`` If not passed or an empty string or `None` is passed, the step mode is not enabled. connect Argument specifying how vertexes should be connected by line segments. | 'all' (default) indicates full connection. | 'pairs' draws one separate line segment for each two points given. | 'finite' omits segments attached to `NaN` or `Inf` values. | For any other connectivity, specify an array of boolean values. compositionMode See :func:`setCompositionMode `. skipFiniteCheck (bool, defaults to `False`) Optimization flag that can speed up plotting by not checking and compensating for `NaN` values. If set to `True`, and `NaN` values exist, the data may not be displayed or the plot may take a significant performance hit. =============== ================================================================= If non-keyword arguments are used, they will be interpreted as ``setData(y)`` for a single argument and ``setData(x, y)`` for two arguments. **Notes on performance:** Line widths greater than 1 pixel affect the performance as discussed in the documentation of :class:`PlotDataItem `. """ self.updateData(*args, **kargs) def updateData(self, *args, **kargs): profiler = debug.Profiler() if 'compositionMode' in kargs: self.setCompositionMode(kargs['compositionMode']) if len(args) == 1: kargs['y'] = args[0] elif len(args) == 2: kargs['x'] = args[0] kargs['y'] = args[1] if 'y' not in kargs or kargs['y'] is None: kargs['y'] = np.array([]) if 'x' not in kargs or kargs['x'] is None: kargs['x'] = np.arange(len(kargs['y'])) for k in ['x', 'y']: data = kargs[k] if isinstance(data, list): data = np.array(data) kargs[k] = data if not isinstance(data, np.ndarray) or data.ndim > 1: raise Exception("Plot data must be 1D ndarray.") if data.dtype.kind == 'c': raise Exception("Can not plot complex data types.") profiler("data checks") #self.setCacheMode(QtWidgets.QGraphicsItem.CacheMode.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly ## Test this bug with test_PlotWidget and zoom in on the animated plot self.yData = kargs['y'].view(np.ndarray) self.xData = kargs['x'].view(np.ndarray) self.invalidateBounds() self.prepareGeometryChange() self.informViewBoundsChanged() profiler('copy') if 'stepMode' in kargs: self.opts['stepMode'] = kargs['stepMode'] if self.opts['stepMode'] in ("center", True): ## check against True for backwards compatibility if self.opts['stepMode'] is True: warnings.warn( 'stepMode=True is deprecated and will result in an error after October 2022. Use stepMode="center" instead.', DeprecationWarning, stacklevel=3 ) if len(self.xData) != len(self.yData)+1: ## allow difference of 1 for step mode plots raise Exception("len(X) must be len(Y)+1 since stepMode=True (got %s and %s)" % (self.xData.shape, self.yData.shape)) else: if self.xData.shape != self.yData.shape: ## allow difference of 1 for step mode plots raise Exception("X and Y arrays must be the same shape--got %s and %s." % (self.xData.shape, self.yData.shape)) self.path = None self.fillPath = None self._fillPathList = None self._mouseShape = None self._renderSegmentList = None if 'name' in kargs: self.opts['name'] = kargs['name'] if 'connect' in kargs: self.opts['connect'] = kargs['connect'] if 'pen' in kargs: self.setPen(kargs['pen']) if 'shadowPen' in kargs: self.setShadowPen(kargs['shadowPen']) if 'fillLevel' in kargs: self.setFillLevel(kargs['fillLevel']) if 'fillOutline' in kargs: self.opts['fillOutline'] = kargs['fillOutline'] if 'brush' in kargs: self.setBrush(kargs['brush']) if 'antialias' in kargs: self.opts['antialias'] = kargs['antialias'] if 'skipFiniteCheck' in kargs: self.opts['skipFiniteCheck'] = kargs['skipFiniteCheck'] profiler('set') self.update() profiler('update') self.sigPlotChanged.emit(self) profiler('emit') @staticmethod def _generateStepModeData(stepMode, x, y, baseline): ## each value in the x/y arrays generates 2 points. if stepMode == "right": x2 = np.empty((len(x) + 1, 2), dtype=x.dtype) x2[:-1] = x[:, np.newaxis] x2[-1] = x2[-2] elif stepMode == "left": x2 = np.empty((len(x) + 1, 2), dtype=x.dtype) x2[1:] = x[:, np.newaxis] x2[0] = x2[1] elif stepMode in ("center", True): ## support True for back-compat x2 = np.empty((len(x),2), dtype=x.dtype) x2[:] = x[:, np.newaxis] else: raise ValueError("Unsupported stepMode %s" % stepMode) if baseline is None: x = x2.reshape(x2.size)[1:-1] y2 = np.empty((len(y),2), dtype=y.dtype) y2[:] = y[:,np.newaxis] y = y2.reshape(y2.size) else: # if baseline is provided, add vertical lines to left/right ends x = x2.reshape(x2.size) y2 = np.empty((len(y)+2,2), dtype=y.dtype) y2[1:-1] = y[:,np.newaxis] y = y2.reshape(y2.size)[1:-1] y[[0, -1]] = baseline return x, y def generatePath(self, x, y): if self.opts['stepMode']: x, y = self._generateStepModeData( self.opts['stepMode'], x, y, baseline=self.opts['fillLevel'] ) return fn.arrayToQPath( x, y, connect=self.opts['connect'], finiteCheck=not self.opts['skipFiniteCheck'] ) def getPath(self): if self.path is None: x,y = self.getData() if x is None or len(x) == 0 or y is None or len(y) == 0: self.path = QtGui.QPainterPath() else: self.path = self.generatePath(*self.getData()) self.fillPath = None self._fillPathList = None self._mouseShape = None return self.path def _shouldUseDrawLineSegments(self, pen): return ( pen.widthF() > 1.0 # non-solid pen styles need single polyline to be effective and pen.style() == QtCore.Qt.PenStyle.SolidLine # segmenting the curve slows gradient brushes, and is expected # to do the same for other patterns and pen.isSolid() # pen.brush().style() == Qt.BrushStyle.SolidPattern # ends of adjacent line segments overlapping is visible when not opaque and pen.color().alphaF() == 1.0 ) def _getLineSegments(self): if not hasattr(self, '_lineSegments'): self._lineSegments = LineSegments() if self._renderSegmentList is None: x, y = self.getData() if self.opts['stepMode']: x, y = self._generateStepModeData( self.opts['stepMode'], x, y, baseline=self.opts['fillLevel'] ) self._renderSegmentList = self._lineSegments.arrayToLineSegments( x, y, connect=self.opts['connect'], finiteCheck=not self.opts['skipFiniteCheck'] ) return self._renderSegmentList def _getClosingSegments(self): # this is only used for fillOutline # no point caching with so few elements generated segments = [] if self.opts['fillLevel'] == 'enclosed': return segments baseline = self.opts['fillLevel'] x, y = self.getData() lx, rx = x[[0, -1]] ly, ry = y[[0, -1]] if ry != baseline: segments.append(QtCore.QLineF(rx, ry, rx, baseline)) segments.append(QtCore.QLineF(rx, baseline, lx, baseline)) if ly != baseline: segments.append(QtCore.QLineF(lx, baseline, lx, ly)) return segments def _getFillPath(self): if self.fillPath is not None: return self.fillPath path = QtGui.QPainterPath(self.getPath()) self.fillPath = path if self.opts['fillLevel'] == 'enclosed': return path baseline = self.opts['fillLevel'] x, y = self.getData() lx, rx = x[[0, -1]] ly, ry = y[[0, -1]] if ry != baseline: path.lineTo(rx, baseline) path.lineTo(lx, baseline) if ly != baseline: path.lineTo(lx, ly) return path def _shouldUseFillPathList(self): connect = self.opts['connect'] return ( # not meaningful to fill disjoint lines isinstance(connect, str) and connect == 'all' # guard against odd-ball argument 'enclosed' and isinstance(self.opts['fillLevel'], (int, float)) ) def _getFillPathList(self): if self._fillPathList is not None: return self._fillPathList x, y = self.getData() if self.opts['stepMode']: x, y = self._generateStepModeData( self.opts['stepMode'], x, y, # note that left/right vertical lines can be omitted here baseline=None ) if not self.opts['skipFiniteCheck']: mask = np.isfinite(x) & np.isfinite(y) if not mask.all(): # we are only supporting connect='all', # so remove non-finite values x = x.compress(mask) y = y.compress(mask) if len(x) < 2: return [] paths = self._fillPathList = [] offset = 0 chunksize = 50 # determined empirically xybuf = np.empty((chunksize+3, 2)) baseline = self.opts['fillLevel'] while offset < len(x) - 1: subx = x[offset:offset + chunksize] suby = y[offset:offset + chunksize] size = len(subx) xyview = xybuf[:size+3] xyview[:-3, 0] = subx xyview[:-3, 1] = suby xyview[-3:, 0] = subx[[-1, 0, 0]] xyview[-3:, 1] = [baseline, baseline, suby[0]] offset += size - 1 # last point is re-used for next chunk # data was either declared to be all-finite OR was sanitized path = fn._arrayToQPath_all(xyview[:, 0], xyview[:, 1], finiteCheck=False) paths.append(path) return paths @debug.warnOnException ## raising an exception here causes crash def paint(self, p, opt, widget): profiler = debug.Profiler() if self.xData is None or len(self.xData) == 0: return if getConfigOption('enableExperimental'): if HAVE_OPENGL and isinstance(widget, QtWidgets.QOpenGLWidget): self.paintGL(p, opt, widget) return if self._exportOpts is not False: aa = self._exportOpts.get('antialias', True) else: aa = self.opts['antialias'] p.setRenderHint(p.RenderHint.Antialiasing, aa) cmode = self.opts['compositionMode'] if cmode is not None: p.setCompositionMode(cmode) do_fill = self.opts['brush'] is not None and self.opts['fillLevel'] is not None do_fill_outline = do_fill and self.opts['fillOutline'] if do_fill: if self._shouldUseFillPathList(): paths = self._getFillPathList() else: paths = [self._getFillPath()] profiler('generate fill path') for path in paths: p.fillPath(path, self.opts['brush']) profiler('draw fill path') # Avoid constructing a shadow pen if it's not used. if self.opts.get('shadowPen') is not None: if isinstance(self.opts.get('shadowPen'), QtGui.QPen): sp = self.opts['shadowPen'] else: sp = fn.mkPen(self.opts['shadowPen']) if sp.style() != QtCore.Qt.PenStyle.NoPen: p.setPen(sp) if self._shouldUseDrawLineSegments(sp): p.drawLines(self._getLineSegments()) if do_fill_outline: p.drawLines(self._getClosingSegments()) else: if do_fill_outline: p.drawPath(self._getFillPath()) else: p.drawPath(self.getPath()) cp = self.opts['pen'] if not isinstance(cp, QtGui.QPen): cp = fn.mkPen(cp) p.setPen(cp) if self._shouldUseDrawLineSegments(cp): p.drawLines(self._getLineSegments()) if do_fill_outline: p.drawLines(self._getClosingSegments()) else: if do_fill_outline: p.drawPath(self._getFillPath()) else: p.drawPath(self.getPath()) profiler('drawPath') def paintGL(self, p, opt, widget): p.beginNativePainting() import OpenGL.GL as gl if sys.platform == 'win32': # If Qt is built to dynamically load OpenGL, then the projection and # modelview matrices are not setup. # https://doc.qt.io/qt-6/windows-graphics.html # https://code.woboq.org/qt6/qtbase/src/opengl/qopenglpaintengine.cpp.html # Technically, we could enable it for all platforms, but for now, just # enable it where it is required, i.e. Windows gl.glMatrixMode(gl.GL_PROJECTION) gl.glLoadIdentity() gl.glOrtho(0, widget.width(), widget.height(), 0, -999999, 999999) gl.glMatrixMode(gl.GL_MODELVIEW) mat = QtGui.QMatrix4x4(self.sceneTransform()) gl.glLoadMatrixf(np.array(mat.data(), dtype=np.float32)) ## set clipping viewport view = self.getViewBox() if view is not None: rect = view.mapRectToItem(self, view.boundingRect()) #gl.glViewport(int(rect.x()), int(rect.y()), int(rect.width()), int(rect.height())) #gl.glTranslate(-rect.x(), -rect.y(), 0) gl.glEnable(gl.GL_STENCIL_TEST) gl.glColorMask(gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE) # disable drawing to frame buffer gl.glDepthMask(gl.GL_FALSE) # disable drawing to depth buffer gl.glStencilFunc(gl.GL_NEVER, 1, 0xFF) gl.glStencilOp(gl.GL_REPLACE, gl.GL_KEEP, gl.GL_KEEP) ## draw stencil pattern gl.glStencilMask(0xFF) gl.glClear(gl.GL_STENCIL_BUFFER_BIT) gl.glBegin(gl.GL_TRIANGLES) gl.glVertex2f(rect.x(), rect.y()) gl.glVertex2f(rect.x()+rect.width(), rect.y()) gl.glVertex2f(rect.x(), rect.y()+rect.height()) gl.glVertex2f(rect.x()+rect.width(), rect.y()+rect.height()) gl.glVertex2f(rect.x()+rect.width(), rect.y()) gl.glVertex2f(rect.x(), rect.y()+rect.height()) gl.glEnd() gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE) gl.glDepthMask(gl.GL_TRUE) gl.glStencilMask(0x00) gl.glStencilFunc(gl.GL_EQUAL, 1, 0xFF) try: x, y = self.getData() pos = np.empty((len(x), 2), dtype=np.float32) pos[:,0] = x pos[:,1] = y gl.glEnableClientState(gl.GL_VERTEX_ARRAY) try: gl.glVertexPointerf(pos) pen = fn.mkPen(self.opts['pen']) gl.glColor4f(*pen.color().getRgbF()) width = pen.width() if pen.isCosmetic() and width < 1: width = 1 gl.glPointSize(width) gl.glLineWidth(width) # enable antialiasing if requested if self._exportOpts is not False: aa = self._exportOpts.get('antialias', True) else: aa = self.opts['antialias'] if aa: gl.glEnable(gl.GL_LINE_SMOOTH) gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST) else: gl.glDisable(gl.GL_LINE_SMOOTH) gl.glDrawArrays(gl.GL_LINE_STRIP, 0, pos.shape[0]) finally: gl.glDisableClientState(gl.GL_VERTEX_ARRAY) finally: p.endNativePainting() def clear(self): self.xData = None ## raw values self.yData = None self._renderSegmentList = None self.path = None self.fillPath = None self._fillPathList = None self._mouseShape = None self._mouseBounds = None self._boundsCache = [None, None] #del self.xData, self.yData, self.xDisp, self.yDisp, self.path def mouseShape(self): """ Return a QPainterPath representing the clickable shape of the curve """ if self._mouseShape is None: view = self.getViewBox() if view is None: return QtGui.QPainterPath() stroker = QtGui.QPainterPathStroker() path = self.getPath() path = self.mapToItem(view, path) stroker.setWidth(self.opts['mouseWidth']) mousePath = stroker.createStroke(path) self._mouseShape = self.mapFromItem(view, mousePath) return self._mouseShape def mouseClickEvent(self, ev): if not self.clickable or ev.button() != QtCore.Qt.MouseButton.LeftButton: return if self.mouseShape().contains(ev.pos()): ev.accept() self.sigClicked.emit(self, ev) class ROIPlotItem(PlotCurveItem): """Plot curve that monitors an ROI and image for changes to automatically replot.""" def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None): self.roi = roi self.roiData = data self.roiImg = img self.axes = axes self.xVals = xVals PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color) #roi.connect(roi, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent) roi.sigRegionChanged.connect(self.roiChangedEvent) #self.roiChangedEvent() def getRoiData(self): d = self.roi.getArrayRegion(self.roiData, self.roiImg, axes=self.axes) if d is None: return while d.ndim > 1: d = d.mean(axis=1) return d def roiChangedEvent(self): d = self.getRoiData() self.updateData(d, self.xVals) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PlotDataItem.py000066400000000000000000001561431421045507400253700ustar00rootroot00000000000000import warnings import numpy as np from .. import debug as debug from .. import functions as fn from .. import getConfigOption from ..Qt import QtCore from .GraphicsObject import GraphicsObject from .PlotCurveItem import PlotCurveItem from .ScatterPlotItem import ScatterPlotItem __all__ = ['PlotDataItem'] class PlotDataset(object): """ :orphan: .. warning:: This class is intended for internal use. The interface may change without warning. Holds collected information for a plotable dataset. Numpy arrays containing x and y coordinates are available as ``dataset.x`` and ``dataset.y``. After a search has been performed, typically during a call to :func:`dataRect() `, ``dataset.containsNonfinite`` is `True` if any coordinate values are nonfinite (e.g. NaN or inf) or `False` if all values are finite. If no search has been performed yet, ``dataset.containsNonfinite`` is `None`. For internal use in :class:`PlotDataItem `, this class should not be instantiated when no data is available. """ def __init__(self, x, y): """ Parameters ---------- x: array x coordinates of data points. y: array y coordinates of data points. """ super().__init__() self.x = x self.y = y self._dataRect = None self.containsNonfinite = None def _updateDataRect(self): """ Finds bounds of plotable data and stores them as ``dataset._dataRect``, stores information about the presence of nonfinite data points. """ if self.y is None or self.x is None: return None if self.containsNonfinite is False: # all points are directly usable. ymin = np.min( self.y ) # find minimum of all values ymax = np.max( self.y ) # find maximum of all values xmin = np.min( self.x ) # find minimum of all values xmax = np.max( self.x ) # find maximum of all values else: # This may contain NaN values and infinites. selection = np.isfinite(self.y) # We are looking for the bounds of the plottable data set. Infinite and Nan are ignored. all_y_are_finite = selection.all() # False if all values are finite, True if there are any non-finites try: ymin = np.min( self.y[selection] ) # find minimum of all finite values ymax = np.max( self.y[selection] ) # find maximum of all finite values except ValueError: # is raised when there are no finite values ymin = np.nan ymax = np.nan selection = np.isfinite(self.x) # We are looking for the bounds of the plottable data set. Infinite and Nan are ignored. all_x_are_finite = selection.all() # False if all values are finite, True if there are any non-finites try: xmin = np.min( self.x[selection] ) # find minimum of all finite values xmax = np.max( self.x[selection] ) # find maximum of all finite values except ValueError: # is raised when there are no finite values xmin = np.nan xmax = np.nan self.containsNonfinite = not (all_x_are_finite and all_y_are_finite) # This always yields a definite True/False answer self._dataRect = QtCore.QRectF( QtCore.QPointF(xmin,ymin), QtCore.QPointF(xmax,ymax) ) def dataRect(self): """ Returns a bounding rectangle (as :class:`QtCore.QRectF`) for the finite subset of data. If there is an active mapping function, such as logarithmic scaling, then bounds represent the mapped data. Will return `None` if there is no data or if all values (`x` or `y`) are NaN. """ if self._dataRect is None: self._updateDataRect() return self._dataRect def applyLogMapping(self, logMode): """ Applies a logarithmic mapping transformation (base 10) if requested for the respective axis. This replaces the internal data. Values of ``-inf`` resulting from zeros in the original dataset are replaced by ``np.NaN``. Parameters ---------- logmode: tuple or list of two bool A `True` value requests log-scale mapping for the x and y axis (in this order). """ all_x_finite = False if logMode[0]: with warnings.catch_warnings(): warnings.simplefilter("ignore", RuntimeWarning) self.x = np.log10(self.x) nonfinites = ~np.isfinite( self.x ) if nonfinites.any(): self.x[nonfinites] = np.nan # set all non-finite values to NaN self.containsNonfinite = True else: all_x_finite = True all_y_finite = False if logMode[1]: with warnings.catch_warnings(): warnings.simplefilter("ignore", RuntimeWarning) self.y = np.log10(self.y) nonfinites = ~np.isfinite( self.y ) if nonfinites.any(): self.y[nonfinites] = np.nan # set all non-finite values to NaN self.containsNonfinite = True else: all_y_finite = True if all_x_finite and all_y_finite: self.containsNonfinite = False # mark as False only if both axes were checked. class PlotDataItem(GraphicsObject): """ **Bases:** :class:`GraphicsObject ` :class:`PlotDataItem` provides a unified interface for displaying plot curves, scatter plots, or both. It also contains methods to transform or decimate the original data before it is displayed. As pyqtgraph's standard plotting object, ``plot()`` methods such as :func:`pyqtgraph.plot` and :func:`PlotItem.plot() ` create instances of :class:`PlotDataItem`. While it is possible to use :class:`PlotCurveItem ` or :class:`ScatterPlotItem ` individually, this is recommended only where performance is critical and the limited functionality of these classes is sufficient. ================================== ============================================== **Signals:** sigPlotChanged(self) Emitted when the data in this item is updated. sigClicked(self, ev) Emitted when the item is clicked. sigPointsClicked(self, points, ev) Emitted when a plot point is clicked Sends the list of points under the mouse. sigPointsHovered(self, points, ev) Emitted when a plot point is hovered over. Sends the list of points under the mouse. ================================== ============================================== """ sigPlotChanged = QtCore.Signal(object) sigClicked = QtCore.Signal(object, object) sigPointsClicked = QtCore.Signal(object, object, object) sigPointsHovered = QtCore.Signal(object, object, object) # **(x,y data only)** def __init__(self, *args, **kargs): """ There are many different ways to create a PlotDataItem. **Data initialization arguments:** (x,y data only) ========================== ========================================= PlotDataItem(x, y) x, y: array_like coordinate values PlotDataItem(y) y values only -- x will be automatically set to ``range(len(y))`` PlotDataItem(x=x, y=y) x and y given by keyword arguments PlotDataItem(ndarray(N,2)) single numpy array with shape (N, 2), where ``x=data[:,0]`` and ``y=data[:,1]`` ========================== ========================================= **Data initialization arguments:** (x,y data AND may include spot style) ============================ =============================================== PlotDataItem(recarray) numpy record array with ``dtype=[('x', float), ('y', float), ...]`` PlotDataItem(list-of-dicts) ``[{'x': x, 'y': y, ...}, ...]`` PlotDataItem(dict-of-lists) ``{'x': [...], 'y': [...], ...}`` ============================ =============================================== **Line style keyword arguments:** ============ ============================================================================== connect Specifies how / whether vertexes should be connected. See below for details. pen Pen to use for drawing the lines between points. Default is solid grey, 1px width. Use None to disable line drawing. May be a ``QPen`` or any single argument accepted by :func:`mkPen() ` shadowPen Pen for secondary line to draw behind the primary line. Disabled by default. May be a ``QPen`` or any single argument accepted by :func:`mkPen() ` fillLevel If specified, the area between the curve and fillLevel is filled. fillOutline (bool) If True, an outline surrounding the *fillLevel* area is drawn. fillBrush Fill to use in the *fillLevel* area. May be any single argument accepted by :func:`mkBrush() ` stepMode (str or None) If specified and not None, a stepped curve is drawn. For 'left' the specified points each describe the left edge of a step. For 'right', they describe the right edge. For 'center', the x coordinates specify the location of the step boundaries. This mode is commonly used for histograms. Note that it requires an additional x value, such that len(x) = len(y) + 1 . ============ ============================================================================== ``connect`` supports the following arguments: - 'all' connects all points. - 'pairs' generates lines between every other point. - 'finite' creates a break when a nonfinite points is encountered. - If an ndarray is passed, it should contain `N` int32 values of 0 or 1. Values of 1 indicate that the respective point will be connected to the next. - In the default 'auto' mode, PlotDataItem will normally use 'all', but if any nonfinite data points are detected, it will automatically switch to 'finite'. See :func:`arrayToQPath() ` for more details. **Point style keyword arguments:** (see :func:`ScatterPlotItem.setData() ` for more information) ============ ====================================================== symbol Symbol to use for drawing points, or a list of symbols for each. The default is no symbol. symbolPen Outline pen for drawing points, or a list of pens, one per point. May be any single argument accepted by :func:`mkPen() `. symbolBrush Brush for filling points, or a list of brushes, one per point. May be any single argument accepted by :func:`mkBrush() `. symbolSize Diameter of symbols, or list of diameters. pxMode (bool) If True, then symbolSize is specified in pixels. If False, then symbolSize is specified in data coordinates. ============ ====================================================== Any symbol recognized by :class:`ScatterPlotItem ` can be specified, including 'o' (circle), 's' (square), 't', 't1', 't2', 't3' (triangles of different orientation), 'd' (diamond), '+' (plus sign), 'x' (x mark), 'p' (pentagon), 'h' (hexagon) and 'star'. Symbols can also be directly given in the form of a :class:`QtGui.QPainterPath` instance. **Optimization keyword arguments:** ================= ======================================================================= antialias (bool) By default, antialiasing is disabled to improve performance. Note that in some cases (in particular, when ``pxMode=True``), points will be rendered antialiased even if this is set to `False`. downsample (int) Reduce the number of samples displayed by the given factor. downsampleMethod 'subsample': Downsample by taking the first of N samples. This method is fastest and least accurate. 'mean': Downsample by taking the mean of N samples. 'peak': Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower. autoDownsample (bool) If `True`, resample the data before plotting to avoid plotting multiple line segments per pixel. This can improve performance when viewing very high-density data, but increases the initial overhead and memory usage. clipToView (bool) If `True`, only data visible within the X range of the containing :class:`ViewBox` is plotted. This can improve performance when plotting very large data sets where only a fraction of the data is visible at any time. dynamicRangeLimit (float or `None`) Limit off-screen y positions of data points. `None` disables the limiting. This can increase performance but may cause plots to disappear at high levels of magnification. The default of 1e6 limits data to approximately 1,000,000 times the :class:`ViewBox` height. dynamicRangeHyst (float) Permits changes in vertical zoom up to the given hysteresis factor (the default is 3.0) before the limit calculation is repeated. skipFiniteCheck (bool, default `False`) Optimization flag that can speed up plotting by not checking and compensating for NaN values. If set to `True`, and NaN values exist, unpredictable behavior will occur. The data may not be displayed or the plot may take a significant performance hit. In the default 'auto' connect mode, `PlotDataItem` will apply this setting automatically. ================= ======================================================================= **Meta-info keyword arguments:** ========== ================================================ name (string) Name of item for use in the plot legend ========== ================================================ **Notes on performance:** Plotting lines with the default single-pixel width is the fastest available option. For such lines, translucent colors (`alpha` < 1) do not result in a significant slowdown. Wider lines increase the complexity due to the overlap of individual line segments. Translucent colors require merging the entire plot into a single entity before the alpha value can be applied. For plots with more than a few hundred points, this can result in excessive slowdown. Since version 0.12.4, this slowdown is automatically avoided by an algorithm that draws line segments separately for fully opaque lines. Setting `alpha` < 1 reverts to the previous, slower drawing method. For lines with a width of more than 4 pixels, :func:`pyqtgraph.mkPen() ` will automatically create a ``QPen`` with `Qt.PenCapStyle.RoundCap` to ensure a smooth connection of line segments. This incurs a small performance penalty. """ GraphicsObject.__init__(self) self.setFlag(self.GraphicsItemFlag.ItemHasNoContents) # Original data, mapped data, and data processed for display is now all held in PlotDataset objects. # The convention throughout PlotDataItem is that a PlotDataset is only instantiated if valid data is available. self._dataset = None # will hold a PlotDataset for the original data self._datasetMapped = None # will hold a PlotDataset for data after mapping transforms (e.g. log scale) self._datasetDisplay = None # will hold a PlotDataset for data downsampled and limited for display self.curve = PlotCurveItem() self.scatter = ScatterPlotItem() self.curve.setParentItem(self) self.scatter.setParentItem(self) self.curve.sigClicked.connect(self.curveClicked) self.scatter.sigClicked.connect(self.scatterClicked) self.scatter.sigHovered.connect(self.scatterHovered) # self._xViewRangeWasChanged = False # self._yViewRangeWasChanged = False # self._styleWasChanged = True # force initial update # update-required notifications are handled through properties to allow future management through # the QDynamicPropertyChangeEvent sent on any change. self.setProperty('xViewRangeWasChanged', False) self.setProperty('yViewRangeWasChanged', False) self.setProperty('styleWasChanged', True) # force initial update self._drlLastClip = (0.0, 0.0) # holds last clipping points of dynamic range limiter #self.clear() self.opts = { 'connect': 'auto', # defaults to 'all', unless overridden to 'finite' for log-scaling 'skipFiniteCheck': False, 'fftMode': False, 'logMode': [False, False], 'derivativeMode': False, 'phasemapMode': False, 'alphaHint': 1.0, 'alphaMode': False, 'pen': (200,200,200), 'shadowPen': None, 'fillLevel': None, 'fillOutline': False, 'fillBrush': None, 'stepMode': None, 'symbol': None, 'symbolSize': 10, 'symbolPen': (200,200,200), 'symbolBrush': (50, 50, 150), 'pxMode': True, 'antialias': getConfigOption('antialias'), 'pointMode': None, 'downsample': 1, 'autoDownsample': False, 'downsampleMethod': 'peak', 'autoDownsampleFactor': 5., # draw ~5 samples per pixel 'clipToView': False, 'dynamicRangeLimit': 1e6, 'dynamicRangeHyst': 3.0, 'data': None, } self.setCurveClickable(kargs.get('clickable', False)) self.setData(*args, **kargs) # Compatibility with direct property access to previous xData and yData structures: @property def xData(self): if self._dataset is None: return None return self._dataset.x @property def yData(self): if self._dataset is None: return None return self._dataset.y def implements(self, interface=None): ints = ['plotData'] if interface is None: return ints return interface in ints def name(self): """ Returns the name that represents this item in the legend. """ return self.opts.get('name', None) def setCurveClickable(self, state, width=None): """ ``state=True`` sets the curve to be clickable, with a tolerance margin represented by `width`. """ self.curve.setClickable(state, width) def curveClickable(self): """ Returns `True` if the curve is set to be clickable. """ return self.curve.clickable def boundingRect(self): return QtCore.QRectF() ## let child items handle this def setPos(self, x, y): GraphicsObject.setPos(self, x, y) # to update viewRect: self.viewTransformChanged() # to update displayed point sets, e.g. when clipping (which uses viewRect): self.viewRangeChanged() def setAlpha(self, alpha, auto): if self.opts['alphaHint'] == alpha and self.opts['alphaMode'] == auto: return self.opts['alphaHint'] = alpha self.opts['alphaMode'] = auto self.setOpacity(alpha) #self.update() def setFftMode(self, state): """ ``state = True`` enables mapping the data by a fast Fourier transform. If the `x` values are not equidistant, the data set is resampled at equal intervals. """ if self.opts['fftMode'] == state: return self.opts['fftMode'] = state self._datasetMapped = None self._datasetDisplay = None self.updateItems(styleUpdate=False) self.informViewBoundsChanged() def setLogMode(self, xState, yState): """ When log mode is enabled for the respective axis by setting ``xState`` or ``yState`` to `True`, a mapping according to ``mapped = np.log10( value )`` is applied to the data. For negative or zero values, this results in a `NaN` value. """ if self.opts['logMode'] == [xState, yState]: return self.opts['logMode'] = [xState, yState] self._datasetMapped = None # invalidate mapped data self._datasetDisplay = None # invalidate display data self.updateItems(styleUpdate=False) self.informViewBoundsChanged() def setDerivativeMode(self, state): """ ``state = True`` enables derivative mode, where a mapping according to ``y_mapped = dy / dx`` is applied, with `dx` and `dy` representing the differences between adjacent `x` and `y` values. """ if self.opts['derivativeMode'] == state: return self.opts['derivativeMode'] = state self._datasetMapped = None # invalidate mapped data self._datasetDisplay = None # invalidate display data self.updateItems(styleUpdate=False) self.informViewBoundsChanged() def setPhasemapMode(self, state): """ ``state = True`` enables phase map mode, where a mapping according to ``x_mappped = y`` and ``y_mapped = dy / dx`` is applied, plotting the numerical derivative of the data over the original `y` values. """ if self.opts['phasemapMode'] == state: return self.opts['phasemapMode'] = state self._datasetMapped = None # invalidate mapped data self._datasetDisplay = None # invalidate display data self.updateItems(styleUpdate=False) self.informViewBoundsChanged() def setPointMode(self, state): # This does not seem to do anything, but PlotItem still seems to call it. # warnings.warn( # 'setPointMode has been deprecated, and has no effect. It will be removed from the library in the first release following April, 2022.', # DeprecationWarning, stacklevel=2 # ) if self.opts['pointMode'] == state: return self.opts['pointMode'] = state self.update() def setPen(self, *args, **kargs): """ Sets the pen used to draw lines between points. The argument can be a :class:`QtGui.QPen` or any combination of arguments accepted by :func:`pyqtgraph.mkPen() `. """ pen = fn.mkPen(*args, **kargs) self.opts['pen'] = pen #self.curve.setPen(pen) #for c in self.curves: #c.setPen(pen) #self.update() self.updateItems(styleUpdate=True) def setShadowPen(self, *args, **kargs): """ Sets the shadow pen used to draw lines between points (this is for enhancing contrast or emphasizing data). This line is drawn behind the primary pen and should generally be assigned greater width than the primary pen. The argument can be a :class:`QtGui.QPen` or any combination of arguments accepted by :func:`pyqtgraph.mkPen() `. """ if args[0] is None: pen = None else: pen = fn.mkPen(*args, **kargs) self.opts['shadowPen'] = pen #for c in self.curves: #c.setPen(pen) #self.update() self.updateItems(styleUpdate=True) def setFillBrush(self, *args, **kargs): """ Sets the :class:`QtGui.QBrush` used to fill the area under the curve. See :func:`mkBrush() `) for arguments. """ if args[0] is None: brush = None else: brush = fn.mkBrush(*args, **kargs) if self.opts['fillBrush'] == brush: return self.opts['fillBrush'] = brush self.updateItems(styleUpdate=True) def setBrush(self, *args, **kargs): """ See :func:`setFillBrush() ` or a list that specifies a symbol for each point. """ if self.opts['symbol'] == symbol: return self.opts['symbol'] = symbol #self.scatter.setSymbol(symbol) self.updateItems(styleUpdate=True) def setSymbolPen(self, *args, **kargs): """ Sets the :class:`QtGui.QPen` used to draw symbol outlines. See :func:`mkPen() `) for arguments. """ pen = fn.mkPen(*args, **kargs) if self.opts['symbolPen'] == pen: return self.opts['symbolPen'] = pen #self.scatter.setSymbolPen(pen) self.updateItems(styleUpdate=True) def setSymbolBrush(self, *args, **kargs): """ Sets the :class:`QtGui.QBrush` used to fill symbols. See :func:`mkBrush() `) for arguments. """ brush = fn.mkBrush(*args, **kargs) if self.opts['symbolBrush'] == brush: return self.opts['symbolBrush'] = brush #self.scatter.setSymbolBrush(brush) self.updateItems(styleUpdate=True) def setSymbolSize(self, size): """ Sets the symbol size. """ if self.opts['symbolSize'] == size: return self.opts['symbolSize'] = size #self.scatter.setSymbolSize(symbolSize) self.updateItems(styleUpdate=True) def setDownsampling(self, ds=None, auto=None, method=None): """ Sets the downsampling mode of this item. Downsampling reduces the number of samples drawn to increase performance. ============== ================================================================= **Arguments:** ds (int) Reduce visible plot samples by this factor. To disable, set ds=1. auto (bool) If True, automatically pick *ds* based on visible range mode 'subsample': Downsample by taking the first of N samples. This method is fastest and least accurate. 'mean': Downsample by taking the mean of N samples. 'peak': Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower. ============== ================================================================= """ changed = False if ds is not None: if self.opts['downsample'] != ds: changed = True self.opts['downsample'] = ds if auto is not None and self.opts['autoDownsample'] != auto: self.opts['autoDownsample'] = auto changed = True if method is not None: if self.opts['downsampleMethod'] != method: changed = True self.opts['downsampleMethod'] = method if changed: self._datasetMapped = None # invalidata mapped data self._datasetDisplay = None # invalidate display data self.updateItems(styleUpdate=False) def setClipToView(self, state): """ ``state=True`` enables clipping the displayed data set to the visible x-axis range. """ if self.opts['clipToView'] == state: return self.opts['clipToView'] = state self._datasetDisplay = None # invalidate display data self.updateItems(styleUpdate=False) def setDynamicRangeLimit(self, limit=1e06, hysteresis=3.): """ Limit the off-screen positions of data points at large magnification This avoids errors with plots not displaying because their visibility is incorrectly determined. The default setting repositions far-off points to be within Β±10^6 times the viewport height. =============== ================================================================ **Arguments:** limit (float or None) Any data outside the range of limit * hysteresis will be constrained to the limit value limit. All values are relative to the viewport height. 'None' disables the check for a minimal increase in performance. Default is 1E+06. hysteresis (float) Hysteresis factor that controls how much change in zoom level (vertical height) is allowed before recalculating Default is 3.0 =============== ================================================================ """ if hysteresis < 1.0: hysteresis = 1.0 self.opts['dynamicRangeHyst'] = hysteresis if limit == self.opts['dynamicRangeLimit']: return # avoid update if there is no change self.opts['dynamicRangeLimit'] = limit # can be None self._datasetDisplay = None # invalidate display data self.updateItems(styleUpdate=False) def setSkipFiniteCheck(self, skipFiniteCheck): """ When it is known that the plot data passed to ``PlotDataItem`` contains only finite numerical values, the ``skipFiniteCheck`` property can help speed up plotting. If this flag is set and the data contains any non-finite values (such as `NaN` or `Inf`), unpredictable behavior will occur. The data might not be plotted, or there migth be significant performance impact. In the default 'auto' connect mode, ``PlotDataItem`` will apply this setting automatically. """ self.opts['skipFiniteCheck'] = bool(skipFiniteCheck) def setData(self, *args, **kargs): """ Clear any data displayed by this item and display new data. See :func:`__init__() ` for details; it accepts the same arguments. """ #self.clear() if kargs.get("stepMode", None) is True: warnings.warn( 'stepMode=True is deprecated and will result in an error after October 2022. Use stepMode="center" instead.', DeprecationWarning, stacklevel=3 ) if 'decimate' in kargs.keys(): warnings.warn( 'The decimate keyword has been deprecated. It has no effect and may result in an error in releases after October 2022. ', DeprecationWarning, stacklevel=2 ) if 'identical' in kargs.keys(): warnings.warn( 'The identical keyword has been deprecated. It has no effect may result in an error in releases after October 2022. ', DeprecationWarning, stacklevel=2 ) profiler = debug.Profiler() y = None x = None if len(args) == 1: data = args[0] dt = dataType(data) if dt == 'empty': pass elif dt == 'listOfValues': y = np.array(data) elif dt == 'Nx2array': x = data[:,0] y = data[:,1] elif dt == 'recarray' or dt == 'dictOfLists': if 'x' in data: x = np.array(data['x']) if 'y' in data: y = np.array(data['y']) elif dt == 'listOfDicts': if 'x' in data[0]: x = np.array([d.get('x',None) for d in data]) if 'y' in data[0]: y = np.array([d.get('y',None) for d in data]) for k in ['data', 'symbolSize', 'symbolPen', 'symbolBrush', 'symbolShape']: if k in data: kargs[k] = [d.get(k, None) for d in data] elif dt == 'MetaArray': y = data.view(np.ndarray) x = data.xvals(0).view(np.ndarray) else: raise Exception('Invalid data type %s' % type(data)) elif len(args) == 2: seq = ('listOfValues', 'MetaArray', 'empty') dtyp = dataType(args[0]), dataType(args[1]) if dtyp[0] not in seq or dtyp[1] not in seq: raise Exception('When passing two unnamed arguments, both must be a list or array of values. (got %s, %s)' % (str(type(args[0])), str(type(args[1])))) if not isinstance(args[0], np.ndarray): #x = np.array(args[0]) if dtyp[0] == 'MetaArray': x = args[0].asarray() else: x = np.array(args[0]) else: x = args[0].view(np.ndarray) if not isinstance(args[1], np.ndarray): #y = np.array(args[1]) if dtyp[1] == 'MetaArray': y = args[1].asarray() else: y = np.array(args[1]) else: y = args[1].view(np.ndarray) if 'x' in kargs: x = kargs['x'] if dataType(x) == 'MetaArray': x = x.asarray() if 'y' in kargs: y = kargs['y'] if dataType(y) == 'MetaArray': y = y.asarray() profiler('interpret data') ## pull in all style arguments. ## Use self.opts to fill in anything not present in kargs. if 'name' in kargs: self.opts['name'] = kargs['name'] self.setProperty('styleWasChanged', True) if 'connect' in kargs: self.opts['connect'] = kargs['connect'] self.setProperty('styleWasChanged', True) if 'skipFiniteCheck' in kargs: self.opts['skipFiniteCheck'] = kargs['skipFiniteCheck'] ## if symbol pen/brush are given with no previously set symbol, then assume symbol is 'o' if 'symbol' not in kargs and ('symbolPen' in kargs or 'symbolBrush' in kargs or 'symbolSize' in kargs): if self.opts['symbol'] is None: kargs['symbol'] = 'o' if 'brush' in kargs: kargs['fillBrush'] = kargs['brush'] for k in list(self.opts.keys()): if k in kargs: self.opts[k] = kargs[k] self.setProperty('styleWasChanged', True) #curveArgs = {} #for k in ['pen', 'shadowPen', 'fillLevel', 'brush']: #if k in kargs: #self.opts[k] = kargs[k] #curveArgs[k] = self.opts[k] #scatterArgs = {} #for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol')]: #if k in kargs: #self.opts[k] = kargs[k] #scatterArgs[v] = self.opts[k] if y is None or len(y) == 0: # empty data is represented as None yData = None else: # actual data is represented by ndarray if not isinstance(y, np.ndarray): y = np.array(y) yData = y.view(np.ndarray) if x is None: x = np.arange(len(y)) if x is None or len(x)==0: # empty data is represented as None xData = None else: # actual data is represented by ndarray if not isinstance(x, np.ndarray): x = np.array(x) xData = x.view(np.ndarray) # one last check to make sure there are no MetaArrays getting by if xData is None or yData is None: self._dataset = None else: self._dataset = PlotDataset( xData, yData ) self._datasetMapped = None # invalidata mapped data , will be generated in getData() / getDisplayDataset() self._datasetDisplay = None # invalidate display data, will be generated in getData() / getDisplayDataset() profiler('set data') self.updateItems( styleUpdate = self.property('styleWasChanged') ) self.setProperty('styleWasChanged', False) # items have been updated profiler('update items') self.informViewBoundsChanged() self.sigPlotChanged.emit(self) profiler('emit') def updateItems(self, styleUpdate=True): # override styleUpdate request and always enforce update until we have a better solution for # - ScatterPlotItem losing per-point style information # - PlotDataItem performing multiple unnecessary setData calls on initialization styleUpdate=True curveArgs = {} scatterArgs = {} if styleUpdate: # repeat style arguments only when changed for k, v in [ ('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillOutline', 'fillOutline'), ('fillBrush', 'brush'), ('antialias', 'antialias'), ('connect', 'connect'), ('stepMode', 'stepMode'), ('skipFiniteCheck', 'skipFiniteCheck') ]: if k in self.opts: curveArgs[v] = self.opts[k] for k, v in [ ('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol'), ('symbolSize', 'size'), ('data', 'data'), ('pxMode', 'pxMode'), ('antialias', 'antialias') ]: if k in self.opts: scatterArgs[v] = self.opts[k] dataset = self.getDisplayDataset() if dataset is None: # then we have nothing to show self.curve.hide() self.scatter.hide() return x = dataset.x y = dataset.y #scatterArgs['mask'] = self.dataMask if( self.opts['pen'] is not None or (self.opts['fillBrush'] is not None and self.opts['fillLevel'] is not None) ): # draw if visible... # auto-switch to indicate non-finite values as interruptions in the curve: if isinstance(curveArgs['connect'], str) and curveArgs['connect'] == 'auto': # connect can also take a boolean array if dataset.containsNonfinite is None: curveArgs['connect'] = 'all' # this is faster, but silently connects the curve across any non-finite values else: if dataset.containsNonfinite: curveArgs['connect'] = 'finite' else: curveArgs['connect'] = 'all' # all points can be connected, and no further check is needed. curveArgs['skipFiniteCheck'] = True self.curve.setData(x=x, y=y, **curveArgs) self.curve.show() else: # ...hide if not. self.curve.hide() if self.opts['symbol'] is not None: # draw if visible... ## check against `True` too for backwards compatibility if self.opts.get('stepMode', False) in ("center", True): x = 0.5 * (x[:-1] + x[1:]) self.scatter.setData(x=x, y=y, **scatterArgs) self.scatter.show() else: # ...hide if not. self.scatter.hide() def getDisplayDataset(self): """ Returns a :class:`PlotDataset ` object that contains data suitable for display (after mapping and data reduction) as ``dataset.x`` and ``dataset.y``. Intended for internal use. """ if self._dataset is None: return None # Return cached processed dataset if available and still valid: if( self._datasetDisplay is not None and not (self.property('xViewRangeWasChanged') and self.opts['clipToView']) and not (self.property('xViewRangeWasChanged') and self.opts['autoDownsample']) and not (self.property('yViewRangeWasChanged') and self.opts['dynamicRangeLimit'] is not None) ): return self._datasetDisplay # Apply data mapping functions if mapped dataset is not yet available: if self._datasetMapped is None: x = self._dataset.x y = self._dataset.y if y.dtype == bool: y = y.astype(np.uint8) if x.dtype == bool: x = x.astype(np.uint8) if self.opts['fftMode']: x,y = self._fourierTransform(x, y) # Ignore the first bin for fft data if we have a logx scale if self.opts['logMode'][0]: x=x[1:] y=y[1:] if self.opts['derivativeMode']: # plot dV/dt y = np.diff(self._dataset.y)/np.diff(self._dataset.x) x = x[:-1] if self.opts['phasemapMode']: # plot dV/dt vs V x = self._dataset.y[:-1] y = np.diff(self._dataset.y)/np.diff(self._dataset.x) dataset = PlotDataset(x,y) dataset.containsNonfinite = self._dataset.containsNonfinite if True in self.opts['logMode']: dataset.applyLogMapping( self.opts['logMode'] ) # Apply log scaling for x and/or y axis self._datasetMapped = dataset # apply processing that affects the on-screen display of data: x = self._datasetMapped.x y = self._datasetMapped.y containsNonfinite = self._datasetMapped.containsNonfinite view = self.getViewBox() if view is None: view_range = None else: view_range = view.viewRect() # this is always up-to-date if view_range is None: view_range = self.viewRect() ds = self.opts['downsample'] if not isinstance(ds, int): ds = 1 if self.opts['autoDownsample']: # this option presumes that x-values have uniform spacing if view_range is not None and len(x) > 1: dx = float(x[-1]-x[0]) / (len(x)-1) if dx != 0.0: x0 = (view_range.left()-x[0]) / dx x1 = (view_range.right()-x[0]) / dx width = self.getViewBox().width() if width != 0.0: ds = int(max(1, int((x1-x0) / (width*self.opts['autoDownsampleFactor'])))) ## downsampling is expensive; delay until after clipping. if self.opts['clipToView']: if view is None or view.autoRangeEnabled()[0]: pass # no ViewBox to clip to, or view will autoscale to data range. else: # clip-to-view always presumes that x-values are in increasing order if view_range is not None and len(x) > 1: # find first in-view value (left edge) and first out-of-view value (right edge) # since we want the curve to go to the edge of the screen, we need to preserve # one down-sampled point on the left and one of the right, so we extend the interval x0 = np.searchsorted(x, view_range.left()) - ds x0 = fn.clip_scalar(x0, 0, len(x)) # workaround # x0 = np.clip(x0, 0, len(x)) x1 = np.searchsorted(x, view_range.right()) + ds x1 = fn.clip_scalar(x1, x0, len(x)) # x1 = np.clip(x1, 0, len(x)) x = x[x0:x1] y = y[x0:x1] if ds > 1: if self.opts['downsampleMethod'] == 'subsample': x = x[::ds] y = y[::ds] elif self.opts['downsampleMethod'] == 'mean': n = len(x) // ds stx = ds//2 # start of x-values; try to select a somewhat centered point x = x[stx:stx+n*ds:ds] y = y[:n*ds].reshape(n,ds).mean(axis=1) elif self.opts['downsampleMethod'] == 'peak': n = len(x) // ds x1 = np.empty((n,2)) stx = ds//2 # start of x-values; try to select a somewhat centered point x1[:] = x[stx:stx+n*ds:ds,np.newaxis] x = x1.reshape(n*2) y1 = np.empty((n,2)) y2 = y[:n*ds].reshape((n, ds)) y1[:,0] = y2.max(axis=1) y1[:,1] = y2.min(axis=1) y = y1.reshape(n*2) if self.opts['dynamicRangeLimit'] is not None: if view_range is not None: data_range = self._datasetMapped.dataRect() if data_range is not None: view_height = view_range.height() limit = self.opts['dynamicRangeLimit'] hyst = self.opts['dynamicRangeHyst'] # never clip data if it fits into +/- (extended) limit * view height if ( # note that "bottom" is the larger number, and "top" is the smaller one. view_height > 0 # never clip if the view does not show anything and would cause division by zero and not data_range.bottom() < view_range.top() # never clip if all data is too small to see and not data_range.top() > view_range.bottom() # never clip if all data is too large to see and data_range.height() > 2 * hyst * limit * view_height ): cache_is_good = False # check if cached display data can be reused: if self._datasetDisplay is not None: # top is minimum value, bottom is maximum value # how many multiples of the current view height does the clipped plot extend to the top and bottom? top_exc =-(self._drlLastClip[0]-view_range.bottom()) / view_height bot_exc = (self._drlLastClip[1]-view_range.top() ) / view_height # print(top_exc, bot_exc, hyst) if ( top_exc >= limit / hyst and top_exc <= limit * hyst and bot_exc >= limit / hyst and bot_exc <= limit * hyst ): # restore cached values x = self._datasetDisplay.x y = self._datasetDisplay.y cache_is_good = True if not cache_is_good: min_val = view_range.bottom() - limit * view_height max_val = view_range.top() + limit * view_height # print('alloc:', end='') # workaround for slowdown from numpy deprecation issues in 1.17 to 1.20+ : # y = np.clip(y, a_min=min_val, a_max=max_val) y = fn.clip_array(y, min_val, max_val) self._drlLastClip = (min_val, max_val) self._datasetDisplay = PlotDataset( x,y ) self._datasetDisplay.containsNonfinite = containsNonfinite self.setProperty('xViewRangeWasChanged', False) self.setProperty('yViewRangeWasChanged', False) return self._datasetDisplay def getData(self): """ Returns the displayed data as the tuple (`xData`, `yData`) after mapping and data reduction. """ dataset = self.getDisplayDataset() if dataset is None: return (None, None) return dataset.x, dataset.y # compatbility method for access to dataRect for full dataset: def dataRect(self): """ Returns a bounding rectangle (as :class:`QtGui.QRectF`) for the full set of data. Will return `None` if there is no data or if all values (x or y) are NaN. """ if self._dataset is None: return None return self._dataset.dataRect() def dataBounds(self, ax, frac=1.0, orthoRange=None): """ Returns the range occupied by the data (along a specific axis) in this item. This method is called by :class:`ViewBox` when auto-scaling. =============== ==================================================================== **Arguments:** ax (0 or 1) the axis for which to return this item's data range frac (float 0.0-1.0) Specifies what fraction of the total data range to return. By default, the entire range is returned. This allows the :class:`ViewBox` to ignore large spikes in the data when auto-scaling. orthoRange ([min,max] or None) Specifies that only the data within the given range (orthogonal to *ax*) should me measured when returning the data range. (For example, a ViewBox might ask what is the y-range of all data with x-values between min and max) =============== ==================================================================== """ range = [None, None] if self.curve.isVisible(): range = self.curve.dataBounds(ax, frac, orthoRange) elif self.scatter.isVisible(): r2 = self.scatter.dataBounds(ax, frac, orthoRange) range = [ r2[0] if range[0] is None else (range[0] if r2[0] is None else min(r2[0], range[0])), r2[1] if range[1] is None else (range[1] if r2[1] is None else min(r2[1], range[1])) ] return range def pixelPadding(self): """ Returns the size in pixels that this item may draw beyond the values returned by dataBounds(). This method is called by :class:`ViewBox` when auto-scaling. """ pad = 0 if self.curve.isVisible(): pad = max(pad, self.curve.pixelPadding()) elif self.scatter.isVisible(): pad = max(pad, self.scatter.pixelPadding()) return pad def clear(self): self._dataset = None self._datasetMapped = None self._datasetDisplay = None self.curve.clear() self.scatter.clear() def appendData(self, *args, **kargs): pass def curveClicked(self, curve, ev): self.sigClicked.emit(self, ev) def scatterClicked(self, plt, points, ev): self.sigClicked.emit(self, ev) self.sigPointsClicked.emit(self, points, ev) def scatterHovered(self, plt, points, ev): self.sigPointsHovered.emit(self, points, ev) # def viewTransformChanged(self): # """ view transform (and thus range) has changed, replot if needed """ # viewTransformChanged is only called when the cached viewRect of GraphicsItem # has already been invalidated. However, responding here will make PlotDataItem # update curve and scatter later than intended. # super().viewTransformChanged() # this invalidates the viewRect() cache! def viewRangeChanged(self, vb=None, ranges=None, changed=None): # view range has changed; re-plot if needed update_needed = False if changed is None or changed[0]: # if ranges is not None: # print('hor:', ranges[0]) self.setProperty('xViewRangeWasChanged', True) if( self.opts['clipToView'] or self.opts['autoDownsample'] ): self._datasetDisplay = None update_needed = True if changed is None or changed[1]: # if ranges is not None: # print('ver:', ranges[1]) self.setProperty('yViewRangeWasChanged', True) if self.opts['dynamicRangeLimit'] is not None: # update, but do not discard cached display data update_needed = True if update_needed: self.updateItems(styleUpdate=False) def _fourierTransform(self, x, y): ## Perform Fourier transform. If x values are not sampled uniformly, ## then use np.interp to resample before taking fft. dx = np.diff(x) uniform = not np.any(np.abs(dx-dx[0]) > (abs(dx[0]) / 1000.)) if not uniform: x2 = np.linspace(x[0], x[-1], len(x)) y = np.interp(x2, x, y) x = x2 n = y.size f = np.fft.rfft(y) / n d = float(x[-1]-x[0]) / (len(x)-1) x = np.fft.rfftfreq(n, d) y = np.abs(f) return x, y # helper functions: def dataType(obj): if hasattr(obj, '__len__') and len(obj) == 0: return 'empty' if isinstance(obj, dict): return 'dictOfLists' elif isSequence(obj): first = obj[0] if (hasattr(obj, 'implements') and obj.implements('MetaArray')): return 'MetaArray' elif isinstance(obj, np.ndarray): if obj.ndim == 1: if obj.dtype.names is None: return 'listOfValues' else: return 'recarray' elif obj.ndim == 2 and obj.dtype.names is None and obj.shape[1] == 2: return 'Nx2array' else: raise Exception('array shape must be (N,) or (N,2); got %s instead' % str(obj.shape)) elif isinstance(first, dict): return 'listOfDicts' else: return 'listOfValues' def isSequence(obj): return hasattr(obj, '__iter__') or isinstance(obj, np.ndarray) or (hasattr(obj, 'implements') and obj.implements('MetaArray')) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PlotItem/000077500000000000000000000000001421045507400242125ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PlotItem/PlotItem.py000066400000000000000000001530231421045507400263250ustar00rootroot00000000000000import collections.abc import importlib import os import warnings import weakref import numpy as np from ... import functions as fn from ... import icons from ...Qt import QT_LIB, QtCore, QtGui, QtWidgets from ...WidgetGroup import WidgetGroup from ...widgets.FileDialog import FileDialog from ..AxisItem import AxisItem from ..ButtonItem import ButtonItem from ..GraphicsWidget import GraphicsWidget from ..InfiniteLine import InfiniteLine from ..LabelItem import LabelItem from ..LegendItem import LegendItem from ..PlotCurveItem import PlotCurveItem from ..PlotDataItem import PlotDataItem from ..ScatterPlotItem import ScatterPlotItem from ..ViewBox import ViewBox translate = QtCore.QCoreApplication.translate ui_template = importlib.import_module( f'.plotConfigTemplate_{QT_LIB.lower()}', package=__package__) __all__ = ['PlotItem'] class PlotItem(GraphicsWidget): """GraphicsWidget implementing a standard 2D plotting area with axes. **Bases:** :class:`GraphicsWidget ` This class provides the ViewBox-plus-axes that appear when using :func:`pg.plot() `, :class:`PlotWidget `, and :func:`GraphicsLayoutWidget.addPlot() `. It's main functionality is: - Manage placement of ViewBox, AxisItems, and LabelItems - Create and manage a list of PlotDataItems displayed inside the ViewBox - Implement a context menu with commonly used display and analysis options Use :func:`plot() ` to create a new PlotDataItem and add it to the view. Use :func:`addItem() ` to add any QGraphicsItem to the view. This class wraps several methods from its internal ViewBox: - :func:`setXRange ` - :func:`setYRange ` - :func:`setRange ` - :func:`autoRange ` - :func:`setDefaultPadding ` - :func:`setXLink ` - :func:`setYLink ` - :func:`setAutoPan ` - :func:`setAutoVisible ` - :func:`setLimits ` - :func:`viewRect ` - :func:`viewRange ` - :func:`setMouseEnabled ` - :func:`enableAutoRange ` - :func:`disableAutoRange ` - :func:`setAspectLocked ` - :func:`invertY ` - :func:`invertX ` - :func:`register ` - :func:`unregister ` The ViewBox itself can be accessed by calling :func:`getViewBox() ` ==================== ======================================================================= **Signals:** sigYRangeChanged wrapped from :class:`ViewBox ` sigXRangeChanged wrapped from :class:`ViewBox ` sigRangeChanged wrapped from :class:`ViewBox ` ==================== ======================================================================= """ sigRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox range has changed sigYRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox Y range has changed sigXRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox X range has changed lastFileDir = None def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs): """ Create a new PlotItem. All arguments are optional. Any extra keyword arguments are passed to :func:`PlotItem.plot() `. ============== ========================================================================================== **Arguments:** *title* Title to display at the top of the item. Html is allowed. *labels* A dictionary specifying the axis labels to display:: {'left': (args), 'bottom': (args), ...} The name of each axis and the corresponding arguments are passed to :func:`PlotItem.setLabel() ` Optionally, PlotItem my also be initialized with the keyword arguments left, right, top, or bottom to achieve the same effect. *name* Registers a name for this view so that others may link to it *viewBox* If specified, the PlotItem will be constructed with this as its ViewBox. *axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top') and the values must be instances of AxisItem (or at least compatible with AxisItem). ============== ========================================================================================== """ GraphicsWidget.__init__(self, parent) self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) ## Set up control buttons path = os.path.dirname(__file__) self.autoBtn = ButtonItem(icons.getGraphPixmap('auto'), 14, self) self.autoBtn.mode = 'auto' self.autoBtn.clicked.connect(self.autoBtnClicked) self.buttonsHidden = False ## whether the user has requested buttons to be hidden self.mouseHovering = False self.layout = QtWidgets.QGraphicsGridLayout() self.layout.setContentsMargins(1,1,1,1) self.setLayout(self.layout) self.layout.setHorizontalSpacing(0) self.layout.setVerticalSpacing(0) if viewBox is None: viewBox = ViewBox(parent=self, enableMenu=enableMenu) self.vb = viewBox self.vb.sigStateChanged.connect(self.viewStateChanged) # Enable or disable plotItem menu self.setMenuEnabled(enableMenu, None) if name is not None: self.vb.register(name) self.vb.sigRangeChanged.connect(self.sigRangeChanged) self.vb.sigXRangeChanged.connect(self.sigXRangeChanged) self.vb.sigYRangeChanged.connect(self.sigYRangeChanged) self.layout.addItem(self.vb, 2, 1) self.alpha = 1.0 self.autoAlpha = True self.spectrumMode = False self.legend = None # Initialize axis items self.axes = {} self.setAxisItems(axisItems) self.titleLabel = LabelItem('', size='11pt', parent=self) self.layout.addItem(self.titleLabel, 0, 1) self.setTitle(None) ## hide for i in range(4): self.layout.setRowPreferredHeight(i, 0) self.layout.setRowMinimumHeight(i, 0) self.layout.setRowSpacing(i, 0) self.layout.setRowStretchFactor(i, 1) for i in range(3): self.layout.setColumnPreferredWidth(i, 0) self.layout.setColumnMinimumWidth(i, 0) self.layout.setColumnSpacing(i, 0) self.layout.setColumnStretchFactor(i, 1) self.layout.setRowStretchFactor(2, 100) self.layout.setColumnStretchFactor(1, 100) self.items = [] self.curves = [] self.itemMeta = weakref.WeakKeyDictionary() self.dataItems = [] self.paramList = {} self.avgCurves = {} # Change these properties to adjust the appearance of the averged curve: self.avgPen = fn.mkPen([0, 200, 0]) self.avgShadowPen = fn.mkPen([0, 0, 0], width=4) # the previous default of [0,0,0,100] prevent fast drawing of the wide shadow line ### Set up context menu w = QtWidgets.QWidget() self.ctrl = c = ui_template.Ui_Form() c.setupUi(w) dv = QtGui.QDoubleValidator(self) menuItems = [ (translate("PlotItem", 'Transforms'), c.transformGroup), (translate("PlotItem", 'Downsample'), c.decimateGroup), (translate("PlotItem", 'Average'), c.averageGroup), (translate("PlotItem", 'Alpha'), c.alphaGroup), (translate("PlotItem", 'Grid'), c.gridGroup), (translate("PlotItem", 'Points'), c.pointsGroup), ] self.ctrlMenu = QtWidgets.QMenu() self.ctrlMenu.setTitle(translate("PlotItem", 'Plot Options')) self.subMenus = [] for name, grp in menuItems: sm = QtWidgets.QMenu(name) act = QtWidgets.QWidgetAction(self) act.setDefaultWidget(grp) sm.addAction(act) self.subMenus.append(sm) self.ctrlMenu.addMenu(sm) self.stateGroup = WidgetGroup() for name, w in menuItems: self.stateGroup.autoAdd(w) self.fileDialog = None c.alphaGroup.toggled.connect(self.updateAlpha) c.alphaSlider.valueChanged.connect(self.updateAlpha) c.autoAlphaCheck.toggled.connect(self.updateAlpha) c.xGridCheck.toggled.connect(self.updateGrid) c.yGridCheck.toggled.connect(self.updateGrid) c.gridAlphaSlider.valueChanged.connect(self.updateGrid) c.fftCheck.toggled.connect(self.updateSpectrumMode) c.logXCheck.toggled.connect(self.updateLogMode) c.logYCheck.toggled.connect(self.updateLogMode) c.derivativeCheck.toggled.connect(self.updateDerivativeMode) c.phasemapCheck.toggled.connect(self.updatePhasemapMode) c.downsampleSpin.valueChanged.connect(self.updateDownsampling) c.downsampleCheck.toggled.connect(self.updateDownsampling) c.autoDownsampleCheck.toggled.connect(self.updateDownsampling) c.subsampleRadio.toggled.connect(self.updateDownsampling) c.meanRadio.toggled.connect(self.updateDownsampling) c.clipToViewCheck.toggled.connect(self.updateDownsampling) self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked) self.ctrl.averageGroup.toggled.connect(self.avgToggled) self.ctrl.maxTracesCheck.toggled.connect(self._handle_max_traces_toggle) self.ctrl.forgetTracesCheck.toggled.connect(self.updateDecimation) self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation) if labels is None: labels = {} for label in list(self.axes.keys()): if label in kargs: labels[label] = kargs[label] del kargs[label] for k in labels: if isinstance(labels[k], str): labels[k] = (labels[k],) self.setLabel(k, *labels[k]) if title is not None: self.setTitle(title) if len(kargs) > 0: self.plot(**kargs) def implements(self, interface=None): return interface in ['ViewBoxWrapper'] def getViewBox(self): """Return the :class:`ViewBox ` contained within.""" return self.vb ## Wrap a few methods from viewBox. #Important: don't use a settattr(m, getattr(self.vb, m)) as we'd be leaving the viebox alive #because we had a reference to an instance method (creating wrapper methods at runtime instead). for m in ['setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', # NOTE: 'setAutoVisible', 'setDefaultPadding', 'setRange', 'autoRange', 'viewRect', 'viewRange', # If you update this list, please 'setMouseEnabled', 'setLimits', 'enableAutoRange', 'disableAutoRange', # update the class docstring 'setAspectLocked', 'invertY', 'invertX', 'register', 'unregister']: # as well. def _create_method(name): def method(self, *args, **kwargs): return getattr(self.vb, name)(*args, **kwargs) method.__name__ = name return method locals()[m] = _create_method(m) del _create_method def setAxisItems(self, axisItems=None): """ Place axis items as given by `axisItems`. Initializes non-existing axis items. ============== ========================================================================================== **Arguments:** *axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top') and the values must be instances of AxisItem (or at least compatible with AxisItem). ============== ========================================================================================== """ if axisItems is None: axisItems = {} # Array containing visible axis items # Also containing potentially hidden axes, but they are not touched so it does not matter visibleAxes = ['left', 'bottom'] visibleAxes.extend(axisItems.keys()) # Note that it does not matter that this adds # some values to visibleAxes a second time for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))): if k in self.axes: if k not in axisItems: continue # Nothing to do here # Remove old axis oldAxis = self.axes[k]['item'] self.layout.removeItem(oldAxis) oldAxis.scene().removeItem(oldAxis) oldAxis.unlinkFromView() # Create new axis if k in axisItems: axis = axisItems[k] if axis.scene() is not None: if k not in self.axes or axis != self.axes[k]["item"]: raise RuntimeError( "Can't add an axis to multiple plots. Shared axes" " can be achieved with multiple AxisItem instances" " and set[X/Y]Link.") else: axis = AxisItem(orientation=k, parent=self) # Set up new axis axis.linkToView(self.vb) self.axes[k] = {'item': axis, 'pos': pos} self.layout.addItem(axis, *pos) # place axis above images at z=0, items that want to draw over the axes should be placed at z>=1: axis.setZValue(0.5) axis.setFlag(axis.GraphicsItemFlag.ItemNegativeZStacksBehindParent) axisVisible = k in visibleAxes self.showAxis(k, axisVisible) def setLogMode(self, x=None, y=None): """ Set log scaling for `x` and/or `y` axes. This informs PlotDataItems to transform logarithmically and switches the axes to use log ticking. Note that *no other items* in the scene will be affected by this; there is (currently) no generic way to redisplay a GraphicsItem with log coordinates. """ if x is not None: self.ctrl.logXCheck.setChecked(x) if y is not None: self.ctrl.logYCheck.setChecked(y) def showGrid(self, x=None, y=None, alpha=None): """ Show or hide the grid for either axis. ============== ===================================== **Arguments:** x (bool) Whether to show the X grid y (bool) Whether to show the Y grid alpha (0.0-1.0) Opacity of the grid ============== ===================================== """ if x is None and y is None and alpha is None: raise Exception("Must specify at least one of x, y, or alpha.") ## prevent people getting confused if they just call showGrid() if x is not None: self.ctrl.xGridCheck.setChecked(x) if y is not None: self.ctrl.yGridCheck.setChecked(y) if alpha is not None: v = fn.clip_scalar(alpha, 0, 1) * self.ctrl.gridAlphaSlider.maximum() # slider range 0 to 255 self.ctrl.gridAlphaSlider.setValue( int(v) ) def close(self): ## Most of this crap is needed to avoid PySide trouble. ## The problem seems to be whenever scene.clear() leads to deletion of widgets (either through proxies or qgraphicswidgets) ## the solution is to manually remove all widgets before scene.clear() is called if self.ctrlMenu is None: ## already shut down return self.ctrlMenu.setParent(None) self.ctrlMenu = None self.autoBtn.setParent(None) self.autoBtn = None for k in self.axes: i = self.axes[k]['item'] i.close() self.axes = None self.scene().removeItem(self.vb) self.vb = None def registerPlot(self, name): ## for backward compatibility self.vb.register(name) def updateGrid(self, *args): alpha = self.ctrl.gridAlphaSlider.value() x = alpha if self.ctrl.xGridCheck.isChecked() else False y = alpha if self.ctrl.yGridCheck.isChecked() else False self.getAxis('top').setGrid(x) self.getAxis('bottom').setGrid(x) self.getAxis('left').setGrid(y) self.getAxis('right').setGrid(y) def viewGeometry(self): """Return the screen geometry of the viewbox""" v = self.scene().views()[0] b = self.vb.mapRectToScene(self.vb.boundingRect()) wr = v.mapFromScene(b).boundingRect() pos = v.mapToGlobal(v.pos()) wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) return wr def avgToggled(self, b): if b: self.recomputeAverages() for k in self.avgCurves: self.avgCurves[k][1].setVisible(b) def avgParamListClicked(self, item): name = str(item.text()) self.paramList[name] = (item.checkState() == QtCore.Qt.CheckState.Checked) self.recomputeAverages() def recomputeAverages(self): if not self.ctrl.averageGroup.isChecked(): return for k in self.avgCurves: self.removeItem(self.avgCurves[k][1]) self.avgCurves = {} for c in self.curves: self.addAvgCurve(c) self.replot() def addAvgCurve(self, curve): ## Add a single curve into the pool of curves averaged together ## If there are plot parameters, then we need to determine which to average together. remKeys = [] addKeys = [] if self.ctrl.avgParamList.count() > 0: ### First determine the key of the curve to which this new data should be averaged for i in range(self.ctrl.avgParamList.count()): item = self.ctrl.avgParamList.item(i) if item.checkState() == QtCore.Qt.CheckState.Checked: remKeys.append(str(item.text())) else: addKeys.append(str(item.text())) if len(remKeys) < 1: ## In this case, there would be 1 average plot for each data plot; not useful. return p = self.itemMeta.get(curve,{}).copy() for k in p: if type(k) is tuple: p['.'.join(k)] = p[k] del p[k] for rk in remKeys: if rk in p: del p[rk] for ak in addKeys: if ak not in p: p[ak] = None key = tuple(p.items()) ### Create a new curve if needed if key not in self.avgCurves: plot = PlotDataItem() plot.setPen( self.avgPen ) plot.setShadowPen( self.avgShadowPen ) plot.setAlpha(1.0, False) plot.setZValue(100) self.addItem(plot, skipAverage=True) self.avgCurves[key] = [0, plot] self.avgCurves[key][0] += 1 (n, plot) = self.avgCurves[key] ### Average data together (x, y) = curve.getData() stepMode = curve.opts['stepMode'] if plot.yData is not None and y.shape == plot.yData.shape: # note that if shapes do not match, then the average resets. newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n) plot.setData(plot.xData, newData, stepMode=stepMode) else: plot.setData(x, y, stepMode=stepMode) def autoBtnClicked(self): if self.autoBtn.mode == 'auto': self.enableAutoRange() self.autoBtn.hide() else: self.disableAutoRange() def viewStateChanged(self): self.updateButtons() def enableAutoScale(self): """ Enable auto-scaling. The plot will continuously scale to fit the boundaries of its data. """ warnings.warn( 'PlotItem.enableAutoScale is deprecated, and will be removed in 0.13' 'Use PlotItem.enableAutoRange(axis, enable) instead', DeprecationWarning, stacklevel=2 ) self.vb.enableAutoRange(self.vb.XYAxes) def addItem(self, item, *args, **kargs): """ Add a graphics item to the view box. If the item has plot data (:class:`~pyqtgrpah.PlotDataItem`, :class:`~pyqtgraph.PlotCurveItem`, :class:`~pyqtgraph.ScatterPlotItem`), it may be included in analysis performed by the PlotItem. """ if item in self.items: warnings.warn('Item already added to PlotItem, ignoring.') return self.items.append(item) vbargs = {} if 'ignoreBounds' in kargs: vbargs['ignoreBounds'] = kargs['ignoreBounds'] self.vb.addItem(item, *args, **vbargs) name = None if hasattr(item, 'implements') and item.implements('plotData'): name = item.name() self.dataItems.append(item) #self.plotChanged() params = kargs.get('params', {}) self.itemMeta[item] = params #item.setMeta(params) self.curves.append(item) #self.addItem(c) if hasattr(item, 'setLogMode'): item.setLogMode(self.ctrl.logXCheck.isChecked(), self.ctrl.logYCheck.isChecked()) if isinstance(item, PlotDataItem): ## configure curve for this plot (alpha, auto) = self.alphaState() item.setAlpha(alpha, auto) item.setFftMode(self.ctrl.fftCheck.isChecked()) item.setDownsampling(*self.downsampleMode()) item.setClipToView(self.clipToViewMode()) item.setPointMode(self.pointMode()) ## Hide older plots if needed self.updateDecimation() ## Add to average if needed self.updateParamList() if self.ctrl.averageGroup.isChecked() and 'skipAverage' not in kargs: self.addAvgCurve(item) #c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged) #item.sigPlotChanged.connect(self.plotChanged) #self.plotChanged() #name = kargs.get('name', getattr(item, 'opts', {}).get('name', None)) if name is not None and hasattr(self, 'legend') and self.legend is not None: self.legend.addItem(item, name=name) def addDataItem(self, item, *args): warnings.warn( 'PlotItem.addDataItem is deprecated and will be removed in 0.13. ' 'Use PlotItem.addItem instead', DeprecationWarning, stacklevel=2 ) self.addItem(item, *args) def listDataItems(self): """Return a list of all data items (:class:`~pyqtgrpah.PlotDataItem`, :class:`~pyqtgraph.PlotCurveItem`, :class:`~pyqtgraph.ScatterPlotItem`, etc) contained in this PlotItem.""" return self.dataItems[:] def addCurve(self, c, params=None): warnings.warn( 'PlotItem.addCurve is deprecated and will be removed in 0.13. ' 'Use PlotItem.addItem instead.', DeprecationWarning, stacklevel=2 ) self.addItem(c, params) def addLine(self, x=None, y=None, z=None, **kwds): """ Create an :class:`~pyqtgraph.InfiniteLine` and add to the plot. If `x` is specified, the line will be vertical. If `y` is specified, the line will be horizontal. All extra keyword arguments are passed to :func:`InfiniteLine.__init__() `. Returns the item created. """ kwds['pos'] = kwds.get('pos', x if x is not None else y) kwds['angle'] = kwds.get('angle', 0 if x is None else 90) line = InfiniteLine(**kwds) self.addItem(line) if z is not None: line.setZValue(z) return line def removeItem(self, item): """ Remove an item from the PlotItem's :class:`~pyqtgraph.ViewBox`. """ if not item in self.items: return self.items.remove(item) if item in self.dataItems: self.dataItems.remove(item) self.vb.removeItem(item) if item in self.curves: self.curves.remove(item) self.updateDecimation() self.updateParamList() if self.legend is not None: self.legend.removeItem(item) def clear(self): """ Remove all items from the PlotItem's :class:`~pyqtgraph.ViewBox`. """ for i in self.items[:]: self.removeItem(i) self.avgCurves = {} def clearPlots(self): for i in self.curves[:]: self.removeItem(i) self.avgCurves = {} def plot(self, *args, **kargs): # **Additional arguments:** """ Add and return a new plot. See :func:`PlotDataItem.__init__ ` for data arguments **Additional allowed arguments** ========= ================================================================= `clear` clears all plots before displaying new data `params` sets meta-parameters to associate with this data ========= ================================================================= """ clear = kargs.get('clear', False) params = kargs.get('params', None) if clear: self.clear() item = PlotDataItem(*args, **kargs) if params is None: params = {} self.addItem(item, params=params) return item def addLegend(self, offset=(30, 30), **kwargs): """ Create a new :class:`~pyqtgraph.LegendItem` and anchor it over the internal :class:`~pyqtgraph.ViewBox`. Plots added after this will be automatically displayed in the legend if they are created with a 'name' argument. If a :class:`~pyqtGraph.LegendItem` has already been created using this method, that item will be returned rather than creating a new one. Accepts the same arguments as :func:`~pyqtgraph.LegendItem.__init__`. """ if self.legend is None: self.legend = LegendItem(offset=offset, **kwargs) self.legend.setParentItem(self.vb) return self.legend def addColorBar(self, image, **kargs): """ Adds a color bar linked to the ImageItem specified by `image`. AAdditional parameters will be passed to the `pyqtgraph.ColorBarItem`. A call like `plot.addColorBar(img, colorMap='viridis')` is a convenient method to assign and show a color map. """ from ..ColorBarItem import ColorBarItem # avoid circular import bar = ColorBarItem(**kargs) bar.setImageItem( image, insert_in=self ) return bar def scatterPlot(self, *args, **kargs): if 'pen' in kargs: kargs['symbolPen'] = kargs['pen'] kargs['pen'] = None if 'brush' in kargs: kargs['symbolBrush'] = kargs['brush'] del kargs['brush'] if 'size' in kargs: kargs['symbolSize'] = kargs['size'] del kargs['size'] return self.plot(*args, **kargs) def replot(self): self.update() def updateParamList(self): self.ctrl.avgParamList.clear() ## Check to see that each parameter for each curve is present in the list for c in self.curves: for p in list(self.itemMeta.get(c, {}).keys()): if type(p) is tuple: p = '.'.join(p) ## If the parameter is not in the list, add it. matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchFlag.MatchExactly) if len(matches) == 0: i = QtWidgets.QListWidgetItem(p) if p in self.paramList and self.paramList[p] is True: i.setCheckState(QtCore.Qt.CheckState.Checked) else: i.setCheckState(QtCore.Qt.CheckState.Unchecked) self.ctrl.avgParamList.addItem(i) else: i = matches[0] self.paramList[p] = (i.checkState() == QtCore.Qt.CheckState.Checked) def writeSvgCurves(self, fileName=None): if fileName is None: self._chooseFilenameDialog(handler=self.writeSvg) return if isinstance(fileName, tuple): raise Exception("Not implemented yet..") fileName = str(fileName) PlotItem.lastFileDir = os.path.dirname(fileName) rect = self.vb.viewRect() xRange = rect.left(), rect.right() svg = "" dx = max(rect.right(),0) - min(rect.left(),0) ymn = min(rect.top(), rect.bottom()) ymx = max(rect.top(), rect.bottom()) dy = max(ymx,0) - min(ymn,0) sx = 1. sy = 1. while dx*sx < 10: sx *= 1000 while dy*sy < 10: sy *= 1000 sy *= -1 with open(fileName, 'w') as fh: # fh.write('\n' % (rect.left() * sx, # rect.top() * sx, # rect.width() * sy, # rect.height()*sy)) fh.write('\n') fh.write('\n' % ( rect.left() * sx, rect.right() * sx)) fh.write('\n' % ( rect.top() * sy, rect.bottom() * sy)) for item in self.curves: if isinstance(item, PlotCurveItem): color = item.pen.color() hrrggbb, opacity = color.name(), color.alphaF() x, y = item.getData() mask = (x > xRange[0]) * (x < xRange[1]) mask[:-1] += mask[1:] m2 = mask.copy() mask[1:] += m2[:-1] x = x[mask] y = y[mask] x *= sx y *= sy # fh.write('\n' % ( # color, )) fh.write('') # fh.write("") for item in self.dataItems: if isinstance(item, ScatterPlotItem): pRect = item.boundingRect() vRect = pRect.intersected(rect) for point in item.points(): pos = point.pos() if not rect.contains(pos): continue color = point.brush.color() hrrggbb, opacity = color.name(), color.alphaF() x = pos.x() * sx y = pos.y() * sy fh.write('\n' % ( x, y, hrrggbb, opacity)) fh.write("\n") def writeSvg(self, fileName=None): if fileName is None: self._chooseFilenameDialog(handler=self.writeSvg) return fileName = str(fileName) PlotItem.lastFileDir = os.path.dirname(fileName) from ...exporters import SVGExporter ex = SVGExporter(self) ex.export(fileName) def writeImage(self, fileName=None): if fileName is None: self._chooseFilenameDialog(handler=self.writeImage) return from ...exporters import ImageExporter ex = ImageExporter(self) ex.export(fileName) def writeCsv(self, fileName=None): if fileName is None: self._chooseFilenameDialog(handler=self.writeCsv) return fileName = str(fileName) PlotItem.lastFileDir = os.path.dirname(fileName) data = [c.getData() for c in self.curves] with open(fileName, 'w') as fd: i = 0 while True: done = True for d in data: if i < len(d[0]): fd.write('%g,%g,' % (d[0][i], d[1][i])) done = False else: fd.write(' , ,') fd.write('\n') if done: break i += 1 def saveState(self): state = self.stateGroup.state() state['paramList'] = self.paramList.copy() state['view'] = self.vb.getState() return state def restoreState(self, state): if 'paramList' in state: self.paramList = state['paramList'].copy() self.stateGroup.setState(state) self.updateSpectrumMode() self.updateDownsampling() self.updateAlpha() self.updateDecimation() if 'powerSpectrumGroup' in state: state['fftCheck'] = state['powerSpectrumGroup'] if 'gridGroup' in state: state['xGridCheck'] = state['gridGroup'] state['yGridCheck'] = state['gridGroup'] self.stateGroup.setState(state) self.updateParamList() if 'view' not in state: r = [[float(state['xMinText']), float(state['xMaxText'])], [float(state['yMinText']), float(state['yMaxText'])]] state['view'] = { 'autoRange': [state['xAutoRadio'], state['yAutoRadio']], 'linkedViews': [state['xLinkCombo'], state['yLinkCombo']], 'targetRange': r, 'viewRange': r, } self.vb.setState(state['view']) def widgetGroupInterface(self): return (None, PlotItem.saveState, PlotItem.restoreState) def updateSpectrumMode(self, b=None): if b is None: b = self.ctrl.fftCheck.isChecked() for c in self.curves: c.setFftMode(b) self.enableAutoRange() self.recomputeAverages() def updateLogMode(self): x = self.ctrl.logXCheck.isChecked() y = self.ctrl.logYCheck.isChecked() for i in self.items: if hasattr(i, 'setLogMode'): i.setLogMode(x,y) self.getAxis('bottom').setLogMode(x, y) self.getAxis('top').setLogMode(x, y) self.getAxis('left').setLogMode(x, y) self.getAxis('right').setLogMode(x, y) self.enableAutoRange() self.recomputeAverages() def updateDerivativeMode(self): d = self.ctrl.derivativeCheck.isChecked() for i in self.items: if hasattr(i, 'setDerivativeMode'): i.setDerivativeMode(d) self.enableAutoRange() self.recomputeAverages() def updatePhasemapMode(self): d = self.ctrl.phasemapCheck.isChecked() for i in self.items: if hasattr(i, 'setPhasemapMode'): i.setPhasemapMode(d) self.enableAutoRange() self.recomputeAverages() def setDownsampling(self, ds=None, auto=None, mode=None): """ Changes the default downsampling mode for all :class:`~pyqtgraph.PlotDataItem` managed by this plot. =============== ==================================================================== **Arguments:** ds (int) Reduce visible plot samples by this factor, or (bool) To enable/disable downsampling without changing the value. auto (bool) If `True`, automatically pick ``ds`` based on visible range mode 'subsample': Downsample by taking the first of N samples. This method is fastest but least accurate. 'mean': Downsample by taking the mean of N samples. 'peak': Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower. =============== ==================================================================== """ if ds is not None: if ds is False: self.ctrl.downsampleCheck.setChecked(False) elif ds is True: self.ctrl.downsampleCheck.setChecked(True) else: self.ctrl.downsampleCheck.setChecked(True) self.ctrl.downsampleSpin.setValue(ds) if auto is not None: if auto and ds is not False: self.ctrl.downsampleCheck.setChecked(True) self.ctrl.autoDownsampleCheck.setChecked(auto) if mode is not None: if mode == 'subsample': self.ctrl.subsampleRadio.setChecked(True) elif mode == 'mean': self.ctrl.meanRadio.setChecked(True) elif mode == 'peak': self.ctrl.peakRadio.setChecked(True) else: raise ValueError("mode argument must be 'subsample', 'mean', or 'peak'.") def updateDownsampling(self): ds, auto, method = self.downsampleMode() clip = self.ctrl.clipToViewCheck.isChecked() for c in self.curves: c.setDownsampling(ds, auto, method) c.setClipToView(clip) self.recomputeAverages() def downsampleMode(self): if self.ctrl.downsampleCheck.isChecked(): ds = self.ctrl.downsampleSpin.value() else: ds = 1 auto = self.ctrl.downsampleCheck.isChecked() and self.ctrl.autoDownsampleCheck.isChecked() if self.ctrl.subsampleRadio.isChecked(): method = 'subsample' elif self.ctrl.meanRadio.isChecked(): method = 'mean' elif self.ctrl.peakRadio.isChecked(): method = 'peak' else: raise ValueError("one of the method radios must be selected for: 'subsample', 'mean', or 'peak'.") return ds, auto, method def setClipToView(self, clip): """Set the default clip-to-view mode for all :class:`~pyqtgraph.PlotDataItem`s managed by this plot. If *clip* is `True`, then PlotDataItems will attempt to draw only points within the visible range of the ViewBox.""" self.ctrl.clipToViewCheck.setChecked(clip) def clipToViewMode(self): return self.ctrl.clipToViewCheck.isChecked() def _handle_max_traces_toggle(self, check_state): if check_state: self.updateDecimation() else: for curve in self.curves: curve.show() def updateDecimation(self): """ Reduce or increase number of visible curves according to value set by the `Max Traces` spinner, if `Max Traces` is checked in the context menu. Destroy curves that are not visible if `forget traces` is checked. In most cases, this function is called automaticaly when the `Max Traces` GUI elements are triggered. It is also alled when the state of PlotItem is updated, its state is restored, or new items added added/removed. This can cause an unexpected or conflicting state of curve visibility (or destruction) if curve visibilities are controlled externally. In the case of external control it is advised to disable the `Max Traces` checkbox (or context menu) to prevent unexpected curve state changes. """ if not self.ctrl.maxTracesCheck.isChecked(): return else: numCurves = self.ctrl.maxTracesSpin.value() if self.ctrl.forgetTracesCheck.isChecked(): for curve in self.curves[:-numCurves]: curve.clear() self.removeItem(curve) for i, curve in enumerate(reversed(self.curves)): if i < numCurves: curve.show() else: curve.hide() def updateAlpha(self, *args): (alpha, auto) = self.alphaState() for c in self.curves: c.setAlpha(alpha**2, auto) def alphaState(self): enabled = self.ctrl.alphaGroup.isChecked() auto = self.ctrl.autoAlphaCheck.isChecked() alpha = float(self.ctrl.alphaSlider.value()) / self.ctrl.alphaSlider.maximum() if auto: alpha = 1.0 ## should be 1/number of overlapping plots if not enabled: auto = False alpha = 1.0 return (alpha, auto) def pointMode(self): if self.ctrl.pointsGroup.isChecked(): if self.ctrl.autoPointsCheck.isChecked(): mode = None else: mode = True else: mode = False return mode def resizeEvent(self, ev): if self.autoBtn is None: ## already closed down return btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect()) y = self.size().height() - btnRect.height() self.autoBtn.setPos(0, y) def getMenu(self): return self.ctrlMenu def getContextMenus(self, event): ## called when another item is displaying its context menu; we get to add extras to the end of the menu. if self.menuEnabled(): return self.ctrlMenu else: return None def setMenuEnabled(self, enableMenu=True, enableViewBoxMenu='same'): """ Enable or disable the context menu for this PlotItem. By default, the ViewBox's context menu will also be affected. (use ``enableViewBoxMenu=None`` to leave the ViewBox unchanged) """ self._menuEnabled = enableMenu if enableViewBoxMenu is None: return if enableViewBoxMenu == 'same': enableViewBoxMenu = enableMenu self.vb.setMenuEnabled(enableViewBoxMenu) def menuEnabled(self): return self._menuEnabled def hoverEvent(self, ev): if ev.enter: self.mouseHovering = True if ev.exit: self.mouseHovering = False self.updateButtons() def getLabel(self, key): pass def _checkScaleKey(self, key): if key not in self.axes: raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.axes.keys())))) def getScale(self, key): return self.getAxis(key) def getAxis(self, name): """Return the specified AxisItem. *name* should be 'left', 'bottom', 'top', or 'right'.""" self._checkScaleKey(name) return self.axes[name]['item'] def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args): """ Sets the label for an axis. Basic HTML formatting is allowed. ============== ================================================================= **Arguments:** axis must be one of 'left', 'bottom', 'right', or 'top' text text to display along the axis. HTML allowed. units units to display after the title. If units are given, then an SI prefix will be automatically appended and the axis values will be scaled accordingly. (ie, use 'V' instead of 'mV'; 'm' will be added automatically) ============== ================================================================= """ self.getAxis(axis).setLabel(text=text, units=units, **args) self.showAxis(axis) def setLabels(self, **kwds): """ Convenience function allowing multiple labels and/or title to be set in one call. Keyword arguments can be 'title', 'left', 'bottom', 'right', or 'top'. Values may be strings or a tuple of arguments to pass to :func:`setLabel`. """ for k,v in kwds.items(): if k == 'title': self.setTitle(v) else: if isinstance(v, str): v = (v,) self.setLabel(k, *v) def showLabel(self, axis, show=True): """ Show or hide one of the plot's axis labels (the axis itself will be unaffected). axis must be one of 'left', 'bottom', 'right', or 'top' """ self.getScale(axis).showLabel(show) def setTitle(self, title=None, **args): """ Set the title of the plot. Basic HTML formatting is allowed. If title is None, then the title will be hidden. """ if title is None: self.titleLabel.setVisible(False) self.layout.setRowFixedHeight(0, 0) self.titleLabel.setMaximumHeight(0) else: self.titleLabel.setMaximumHeight(30) self.layout.setRowFixedHeight(0, 30) self.titleLabel.setVisible(True) self.titleLabel.setText(title, **args) def showAxis(self, axis, show=True): """ Show or hide one of the plot's axes. axis must be one of 'left', 'bottom', 'right', or 'top' """ s = self.getScale(axis) p = self.axes[axis]['pos'] if show: s.show() else: s.hide() def hideAxis(self, axis): """Hide one of the PlotItem's axes. ('left', 'bottom', 'right', or 'top')""" self.showAxis(axis, False) def showAxes(self, selection, showValues=True, size=False): """ Convenience method for quickly configuring axis settings. Parameters ---------- selection: boolean or tuple of booleans (left, top, right, bottom) Determines which AxisItems will be displayed. A single boolean value will set all axes, so that ``showAxes(True)`` configures the axes to draw a frame. showValues: optional, boolean or tuple of booleans (left, top, right, bottom) Determines if values will be displayed for the ticks of each axis. True value shows values for left and bottom axis (default). False shows no values. None leaves settings unchanged. If not specified, left and bottom axes will be drawn with values. size: optional, float or tuple of floats (width, height) Reserves as fixed amount of space (width for vertical axis, height for horizontal axis) for each axis where tick values are enabled. If only a single float value is given, it will be applied for both width and height. If `None` is given instead of a float value, the axis reverts to automatic allocation of space. """ if selection is True: # shortcut: enable all axes, creating a frame selection = (True, True, True, True) elif selection is False: # shortcut: disable all axes selection = (False, False, False, False) if showValues is True: # shortcut: defaults arrangement with labels at left and bottom showValues = (True, False, False, True) elif showValues is False: # shortcut: disable all labels showValues = (False, False, False, False) elif showValues is None: # leave labelling untouched showValues = (None, None, None, None) if size is not False and not isinstance(size, collections.abc.Sized): size = (size, size) # make sure that size is either False or a full set of (width, height) all_axes = ('left','top','right','bottom') for show_axis, show_value, axis_key in zip(selection, showValues, all_axes): if show_axis is None: pass # leave axis display as it is. else: if show_axis: self.showAxis(axis_key) else : self.hideAxis(axis_key) if show_value is None: pass # leave value display as it is. else: ax = self.getAxis(axis_key) ax.setStyle(showValues=show_value) if size is not False: # size adjustment is requested if axis_key in ('left','right'): if show_value: ax.setWidth(size[0]) else : ax.setWidth( None ) elif axis_key in ('top', 'bottom'): if show_value: ax.setHeight(size[1]) else : ax.setHeight( None ) def showScale(self, *args, **kargs): warnings.warn( 'PlotItem.showScale has been deprecated and will be removed in 0.13. ' 'Use PlotItem.showAxis() instead', DeprecationWarning, stacklevel=2 ) return self.showAxis(*args, **kargs) def hideButtons(self): """Causes auto-scale button ('A' in lower-left corner) to be hidden for this PlotItem""" #self.ctrlBtn.hide() self.buttonsHidden = True self.updateButtons() def showButtons(self): """Causes auto-scale button ('A' in lower-left corner) to be visible for this PlotItem""" #self.ctrlBtn.hide() self.buttonsHidden = False self.updateButtons() def updateButtons(self): try: if self._exportOpts is False and self.mouseHovering and not self.buttonsHidden and not all(self.vb.autoRangeEnabled()): self.autoBtn.show() else: self.autoBtn.hide() except RuntimeError: pass # this can happen if the plot has been deleted. def _plotArray(self, arr, x=None, **kargs): if arr.ndim != 1: raise Exception("Array must be 1D to plot (shape is %s)" % arr.shape) if x is None: x = np.arange(arr.shape[0]) if x.ndim != 1: raise Exception("X array must be 1D to plot (shape is %s)" % x.shape) c = PlotCurveItem(arr, x=x, **kargs) return c def _plotMetaArray(self, arr, x=None, autoLabel=True, **kargs): inf = arr.infoCopy() if arr.ndim != 1: raise Exception('can only automatically plot 1 dimensional arrays.') ## create curve try: xv = arr.xvals(0) except: if x is None: xv = np.arange(arr.shape[0]) else: xv = x c = PlotCurveItem(**kargs) c.setData(x=xv, y=arr.view(np.ndarray)) if autoLabel: name = arr._info[0].get('name', None) units = arr._info[0].get('units', None) self.setLabel('bottom', text=name, units=units) name = arr._info[1].get('name', None) units = arr._info[1].get('units', None) self.setLabel('left', text=name, units=units) return c def setExportMode(self, export, opts=None): GraphicsWidget.setExportMode(self, export, opts) self.updateButtons() def _chooseFilenameDialog(self, handler): self.fileDialog = FileDialog() if PlotItem.lastFileDir is not None: self.fileDialog.setDirectory(PlotItem.lastFileDir) self.fileDialog.setFileMode(QtWidgets.QFileDialog.FileMode.AnyFile) self.fileDialog.setAcceptMode(QtWidgets.QFileDialog.AcceptMode.AcceptSave) self.fileDialog.show() self.fileDialog.fileSelected.connect(handler) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PlotItem/__init__.py�����������������������������0000664�0000000�0000000�00000000067�14210455074�0026326�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .PlotItem import PlotItem __all__ = ['PlotItem'] �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui�������������������0000664�0000000�0000000�00000024003�14210455074�0030350�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Form 0 0 481 840 PyQtGraph 0 640 242 182 Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available). Average true false 0 0 10 140 191 171 0 0 Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced. Clip to View If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed. Max Traces: Downsample Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower. Peak true If multiple curves are displayed in this plot, check "Max Traces" and set this value to limit the number of traces that are displayed. If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden). Forget hidden traces Downsample by taking the mean of N samples. Mean Downsample by taking the first of N samples. This method is fastest and least accurate. Subsample Automatically downsample data based on the visible range. This assumes X values are uniformly spaced. Auto true Qt::Horizontal QSizePolicy::Maximum 30 20 Downsample data before plotting. (plot every Nth sample) x 1 100000 1 10 10 171 101 0 0 Log Y Log X Power Spectrum (FFT) dy/dx Y vs. Y' 10 550 234 58 Points true Auto true 10 460 221 81 Show X Grid Show Y Grid 255 128 Qt::Horizontal Opacity 10 390 234 60 Alpha true Auto false 1000 1000 Qt::Horizontal �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt5.py�������������0000664�0000000�0000000�00000025707�14210455074�0031541�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ # Form implementation generated from reading ui file 'plotConfigTemplate.ui' # # Created by: PyQt5 UI code generator 5.12.3 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(481, 840) self.averageGroup = QtWidgets.QGroupBox(Form) self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) self.averageGroup.setCheckable(True) self.averageGroup.setChecked(False) self.averageGroup.setObjectName("averageGroup") self.gridLayout_5 = QtWidgets.QGridLayout(self.averageGroup) self.gridLayout_5.setContentsMargins(0, 0, 0, 0) self.gridLayout_5.setSpacing(0) self.gridLayout_5.setObjectName("gridLayout_5") self.avgParamList = QtWidgets.QListWidget(self.averageGroup) self.avgParamList.setObjectName("avgParamList") self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) self.decimateGroup = QtWidgets.QFrame(Form) self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) self.decimateGroup.setObjectName("decimateGroup") self.gridLayout_4 = QtWidgets.QGridLayout(self.decimateGroup) self.gridLayout_4.setContentsMargins(0, 0, 0, 0) self.gridLayout_4.setSpacing(0) self.gridLayout_4.setObjectName("gridLayout_4") self.clipToViewCheck = QtWidgets.QCheckBox(self.decimateGroup) self.clipToViewCheck.setObjectName("clipToViewCheck") self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) self.maxTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) self.maxTracesCheck.setObjectName("maxTracesCheck") self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) self.downsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) self.downsampleCheck.setObjectName("downsampleCheck") self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) self.peakRadio = QtWidgets.QRadioButton(self.decimateGroup) self.peakRadio.setChecked(True) self.peakRadio.setObjectName("peakRadio") self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) self.maxTracesSpin = QtWidgets.QSpinBox(self.decimateGroup) self.maxTracesSpin.setObjectName("maxTracesSpin") self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) self.forgetTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) self.forgetTracesCheck.setObjectName("forgetTracesCheck") self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) self.meanRadio = QtWidgets.QRadioButton(self.decimateGroup) self.meanRadio.setObjectName("meanRadio") self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) self.subsampleRadio = QtWidgets.QRadioButton(self.decimateGroup) self.subsampleRadio.setObjectName("subsampleRadio") self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) self.autoDownsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) self.autoDownsampleCheck.setChecked(True) self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) spacerItem = QtWidgets.QSpacerItem(30, 20, QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Minimum) self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) self.downsampleSpin = QtWidgets.QSpinBox(self.decimateGroup) self.downsampleSpin.setMinimum(1) self.downsampleSpin.setMaximum(100000) self.downsampleSpin.setProperty("value", 1) self.downsampleSpin.setObjectName("downsampleSpin") self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) self.transformGroup = QtWidgets.QFrame(Form) self.transformGroup.setGeometry(QtCore.QRect(10, 10, 171, 101)) self.transformGroup.setObjectName("transformGroup") self.gridLayout = QtWidgets.QGridLayout(self.transformGroup) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.logYCheck = QtWidgets.QCheckBox(self.transformGroup) self.logYCheck.setObjectName("logYCheck") self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) self.logXCheck = QtWidgets.QCheckBox(self.transformGroup) self.logXCheck.setObjectName("logXCheck") self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) self.fftCheck = QtWidgets.QCheckBox(self.transformGroup) self.fftCheck.setObjectName("fftCheck") self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) self.derivativeCheck = QtWidgets.QCheckBox(self.transformGroup) self.derivativeCheck.setObjectName("derivativeCheck") self.gridLayout.addWidget(self.derivativeCheck, 3, 0, 1, 1) self.phasemapCheck = QtWidgets.QCheckBox(self.transformGroup) self.phasemapCheck.setObjectName("phasemapCheck") self.gridLayout.addWidget(self.phasemapCheck, 4, 0, 1, 1) self.pointsGroup = QtWidgets.QGroupBox(Form) self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) self.pointsGroup.setCheckable(True) self.pointsGroup.setObjectName("pointsGroup") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.pointsGroup) self.verticalLayout_5.setObjectName("verticalLayout_5") self.autoPointsCheck = QtWidgets.QCheckBox(self.pointsGroup) self.autoPointsCheck.setChecked(True) self.autoPointsCheck.setObjectName("autoPointsCheck") self.verticalLayout_5.addWidget(self.autoPointsCheck) self.gridGroup = QtWidgets.QFrame(Form) self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) self.gridGroup.setObjectName("gridGroup") self.gridLayout_2 = QtWidgets.QGridLayout(self.gridGroup) self.gridLayout_2.setObjectName("gridLayout_2") self.xGridCheck = QtWidgets.QCheckBox(self.gridGroup) self.xGridCheck.setObjectName("xGridCheck") self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) self.yGridCheck = QtWidgets.QCheckBox(self.gridGroup) self.yGridCheck.setObjectName("yGridCheck") self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) self.gridAlphaSlider = QtWidgets.QSlider(self.gridGroup) self.gridAlphaSlider.setMaximum(255) self.gridAlphaSlider.setProperty("value", 128) self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) self.gridAlphaSlider.setObjectName("gridAlphaSlider") self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) self.label = QtWidgets.QLabel(self.gridGroup) self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) self.alphaGroup = QtWidgets.QGroupBox(Form) self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) self.alphaGroup.setCheckable(True) self.alphaGroup.setObjectName("alphaGroup") self.horizontalLayout = QtWidgets.QHBoxLayout(self.alphaGroup) self.horizontalLayout.setObjectName("horizontalLayout") self.autoAlphaCheck = QtWidgets.QCheckBox(self.alphaGroup) self.autoAlphaCheck.setChecked(False) self.autoAlphaCheck.setObjectName("autoAlphaCheck") self.horizontalLayout.addWidget(self.autoAlphaCheck) self.alphaSlider = QtWidgets.QSlider(self.alphaGroup) self.alphaSlider.setMaximum(1000) self.alphaSlider.setProperty("value", 1000) self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) self.alphaSlider.setObjectName("alphaSlider") self.horizontalLayout.addWidget(self.alphaSlider) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).")) self.averageGroup.setTitle(_translate("Form", "Average")) self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.")) self.clipToViewCheck.setText(_translate("Form", "Clip to View")) self.maxTracesCheck.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.")) self.maxTracesCheck.setText(_translate("Form", "Max Traces:")) self.downsampleCheck.setText(_translate("Form", "Downsample")) self.peakRadio.setToolTip(_translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.")) self.peakRadio.setText(_translate("Form", "Peak")) self.maxTracesSpin.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.")) self.forgetTracesCheck.setToolTip(_translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).")) self.forgetTracesCheck.setText(_translate("Form", "Forget hidden traces")) self.meanRadio.setToolTip(_translate("Form", "Downsample by taking the mean of N samples.")) self.meanRadio.setText(_translate("Form", "Mean")) self.subsampleRadio.setToolTip(_translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.")) self.subsampleRadio.setText(_translate("Form", "Subsample")) self.autoDownsampleCheck.setToolTip(_translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.")) self.autoDownsampleCheck.setText(_translate("Form", "Auto")) self.downsampleSpin.setToolTip(_translate("Form", "Downsample data before plotting. (plot every Nth sample)")) self.downsampleSpin.setSuffix(_translate("Form", "x")) self.logYCheck.setText(_translate("Form", "Log Y")) self.logXCheck.setText(_translate("Form", "Log X")) self.fftCheck.setText(_translate("Form", "Power Spectrum (FFT)")) self.derivativeCheck.setText(_translate("Form", "dy/dx")) self.phasemapCheck.setText(_translate("Form", "Y vs. Y\'")) self.pointsGroup.setTitle(_translate("Form", "Points")) self.autoPointsCheck.setText(_translate("Form", "Auto")) self.xGridCheck.setText(_translate("Form", "Show X Grid")) self.yGridCheck.setText(_translate("Form", "Show Y Grid")) self.label.setText(_translate("Form", "Opacity")) self.alphaGroup.setTitle(_translate("Form", "Alpha")) self.autoAlphaCheck.setText(_translate("Form", "Auto")) ���������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt6.py�������������0000664�0000000�0000000�00000026154�14210455074�0031537�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Form implementation generated from reading ui file '../pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui' # # Created by: PyQt6 UI code generator 6.1.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(481, 840) self.averageGroup = QtWidgets.QGroupBox(Form) self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) self.averageGroup.setCheckable(True) self.averageGroup.setChecked(False) self.averageGroup.setObjectName("averageGroup") self.gridLayout_5 = QtWidgets.QGridLayout(self.averageGroup) self.gridLayout_5.setContentsMargins(0, 0, 0, 0) self.gridLayout_5.setSpacing(0) self.gridLayout_5.setObjectName("gridLayout_5") self.avgParamList = QtWidgets.QListWidget(self.averageGroup) self.avgParamList.setObjectName("avgParamList") self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) self.decimateGroup = QtWidgets.QFrame(Form) self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) self.decimateGroup.setObjectName("decimateGroup") self.gridLayout_4 = QtWidgets.QGridLayout(self.decimateGroup) self.gridLayout_4.setContentsMargins(0, 0, 0, 0) self.gridLayout_4.setSpacing(0) self.gridLayout_4.setObjectName("gridLayout_4") self.clipToViewCheck = QtWidgets.QCheckBox(self.decimateGroup) self.clipToViewCheck.setObjectName("clipToViewCheck") self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) self.maxTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) self.maxTracesCheck.setObjectName("maxTracesCheck") self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) self.downsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) self.downsampleCheck.setObjectName("downsampleCheck") self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) self.peakRadio = QtWidgets.QRadioButton(self.decimateGroup) self.peakRadio.setChecked(True) self.peakRadio.setObjectName("peakRadio") self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) self.maxTracesSpin = QtWidgets.QSpinBox(self.decimateGroup) self.maxTracesSpin.setObjectName("maxTracesSpin") self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) self.forgetTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) self.forgetTracesCheck.setObjectName("forgetTracesCheck") self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) self.meanRadio = QtWidgets.QRadioButton(self.decimateGroup) self.meanRadio.setObjectName("meanRadio") self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) self.subsampleRadio = QtWidgets.QRadioButton(self.decimateGroup) self.subsampleRadio.setObjectName("subsampleRadio") self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) self.autoDownsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) self.autoDownsampleCheck.setChecked(True) self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) spacerItem = QtWidgets.QSpacerItem(30, 20, QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Minimum) self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) self.downsampleSpin = QtWidgets.QSpinBox(self.decimateGroup) self.downsampleSpin.setMinimum(1) self.downsampleSpin.setMaximum(100000) self.downsampleSpin.setProperty("value", 1) self.downsampleSpin.setObjectName("downsampleSpin") self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) self.transformGroup = QtWidgets.QFrame(Form) self.transformGroup.setGeometry(QtCore.QRect(10, 10, 171, 101)) self.transformGroup.setObjectName("transformGroup") self.gridLayout = QtWidgets.QGridLayout(self.transformGroup) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.logYCheck = QtWidgets.QCheckBox(self.transformGroup) self.logYCheck.setObjectName("logYCheck") self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) self.logXCheck = QtWidgets.QCheckBox(self.transformGroup) self.logXCheck.setObjectName("logXCheck") self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) self.fftCheck = QtWidgets.QCheckBox(self.transformGroup) self.fftCheck.setObjectName("fftCheck") self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) self.derivativeCheck = QtWidgets.QCheckBox(self.transformGroup) self.derivativeCheck.setObjectName("derivativeCheck") self.gridLayout.addWidget(self.derivativeCheck, 3, 0, 1, 1) self.phasemapCheck = QtWidgets.QCheckBox(self.transformGroup) self.phasemapCheck.setObjectName("phasemapCheck") self.gridLayout.addWidget(self.phasemapCheck, 4, 0, 1, 1) self.pointsGroup = QtWidgets.QGroupBox(Form) self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) self.pointsGroup.setCheckable(True) self.pointsGroup.setObjectName("pointsGroup") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.pointsGroup) self.verticalLayout_5.setObjectName("verticalLayout_5") self.autoPointsCheck = QtWidgets.QCheckBox(self.pointsGroup) self.autoPointsCheck.setChecked(True) self.autoPointsCheck.setObjectName("autoPointsCheck") self.verticalLayout_5.addWidget(self.autoPointsCheck) self.gridGroup = QtWidgets.QFrame(Form) self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) self.gridGroup.setObjectName("gridGroup") self.gridLayout_2 = QtWidgets.QGridLayout(self.gridGroup) self.gridLayout_2.setObjectName("gridLayout_2") self.xGridCheck = QtWidgets.QCheckBox(self.gridGroup) self.xGridCheck.setObjectName("xGridCheck") self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) self.yGridCheck = QtWidgets.QCheckBox(self.gridGroup) self.yGridCheck.setObjectName("yGridCheck") self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) self.gridAlphaSlider = QtWidgets.QSlider(self.gridGroup) self.gridAlphaSlider.setMaximum(255) self.gridAlphaSlider.setProperty("value", 128) self.gridAlphaSlider.setOrientation(QtCore.Qt.Orientation.Horizontal) self.gridAlphaSlider.setObjectName("gridAlphaSlider") self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) self.label = QtWidgets.QLabel(self.gridGroup) self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) self.alphaGroup = QtWidgets.QGroupBox(Form) self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) self.alphaGroup.setCheckable(True) self.alphaGroup.setObjectName("alphaGroup") self.horizontalLayout = QtWidgets.QHBoxLayout(self.alphaGroup) self.horizontalLayout.setObjectName("horizontalLayout") self.autoAlphaCheck = QtWidgets.QCheckBox(self.alphaGroup) self.autoAlphaCheck.setChecked(False) self.autoAlphaCheck.setObjectName("autoAlphaCheck") self.horizontalLayout.addWidget(self.autoAlphaCheck) self.alphaSlider = QtWidgets.QSlider(self.alphaGroup) self.alphaSlider.setMaximum(1000) self.alphaSlider.setProperty("value", 1000) self.alphaSlider.setOrientation(QtCore.Qt.Orientation.Horizontal) self.alphaSlider.setObjectName("alphaSlider") self.horizontalLayout.addWidget(self.alphaSlider) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).")) self.averageGroup.setTitle(_translate("Form", "Average")) self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.")) self.clipToViewCheck.setText(_translate("Form", "Clip to View")) self.maxTracesCheck.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.")) self.maxTracesCheck.setText(_translate("Form", "Max Traces:")) self.downsampleCheck.setText(_translate("Form", "Downsample")) self.peakRadio.setToolTip(_translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.")) self.peakRadio.setText(_translate("Form", "Peak")) self.maxTracesSpin.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.")) self.forgetTracesCheck.setToolTip(_translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).")) self.forgetTracesCheck.setText(_translate("Form", "Forget hidden traces")) self.meanRadio.setToolTip(_translate("Form", "Downsample by taking the mean of N samples.")) self.meanRadio.setText(_translate("Form", "Mean")) self.subsampleRadio.setToolTip(_translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.")) self.subsampleRadio.setText(_translate("Form", "Subsample")) self.autoDownsampleCheck.setToolTip(_translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.")) self.autoDownsampleCheck.setText(_translate("Form", "Auto")) self.downsampleSpin.setToolTip(_translate("Form", "Downsample data before plotting. (plot every Nth sample)")) self.downsampleSpin.setSuffix(_translate("Form", "x")) self.logYCheck.setText(_translate("Form", "Log Y")) self.logXCheck.setText(_translate("Form", "Log X")) self.fftCheck.setText(_translate("Form", "Power Spectrum (FFT)")) self.derivativeCheck.setText(_translate("Form", "dy/dx")) self.phasemapCheck.setText(_translate("Form", "Y vs. Y\'")) self.pointsGroup.setTitle(_translate("Form", "Points")) self.autoPointsCheck.setText(_translate("Form", "Auto")) self.xGridCheck.setText(_translate("Form", "Show X Grid")) self.yGridCheck.setText(_translate("Form", "Show Y Grid")) self.label.setText(_translate("Form", "Opacity")) self.alphaGroup.setTitle(_translate("Form", "Alpha")) self.autoAlphaCheck.setText(_translate("Form", "Auto")) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside2.py�����������0000664�0000000�0000000�00000030012�14210455074�0032017�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ # Form implementation generated from reading ui file 'plotConfigTemplate.ui', # licensing of 'plotConfigTemplate.ui' applies. # # Created: Sun Jan 31 22:10:16 2021 # by: pyside2-uic running on PySide2 5.12.6 # # WARNING! All changes made in this file will be lost! from PySide2 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(481, 840) self.averageGroup = QtWidgets.QGroupBox(Form) self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) self.averageGroup.setCheckable(True) self.averageGroup.setChecked(False) self.averageGroup.setObjectName("averageGroup") self.gridLayout_5 = QtWidgets.QGridLayout(self.averageGroup) self.gridLayout_5.setContentsMargins(0, 0, 0, 0) self.gridLayout_5.setSpacing(0) self.gridLayout_5.setObjectName("gridLayout_5") self.avgParamList = QtWidgets.QListWidget(self.averageGroup) self.avgParamList.setObjectName("avgParamList") self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) self.decimateGroup = QtWidgets.QFrame(Form) self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) self.decimateGroup.setObjectName("decimateGroup") self.gridLayout_4 = QtWidgets.QGridLayout(self.decimateGroup) self.gridLayout_4.setContentsMargins(0, 0, 0, 0) self.gridLayout_4.setSpacing(0) self.gridLayout_4.setObjectName("gridLayout_4") self.clipToViewCheck = QtWidgets.QCheckBox(self.decimateGroup) self.clipToViewCheck.setObjectName("clipToViewCheck") self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) self.maxTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) self.maxTracesCheck.setObjectName("maxTracesCheck") self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) self.downsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) self.downsampleCheck.setObjectName("downsampleCheck") self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) self.peakRadio = QtWidgets.QRadioButton(self.decimateGroup) self.peakRadio.setChecked(True) self.peakRadio.setObjectName("peakRadio") self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) self.maxTracesSpin = QtWidgets.QSpinBox(self.decimateGroup) self.maxTracesSpin.setObjectName("maxTracesSpin") self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) self.forgetTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) self.forgetTracesCheck.setObjectName("forgetTracesCheck") self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) self.meanRadio = QtWidgets.QRadioButton(self.decimateGroup) self.meanRadio.setObjectName("meanRadio") self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) self.subsampleRadio = QtWidgets.QRadioButton(self.decimateGroup) self.subsampleRadio.setObjectName("subsampleRadio") self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) self.autoDownsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) self.autoDownsampleCheck.setChecked(True) self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) spacerItem = QtWidgets.QSpacerItem(30, 20, QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Minimum) self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) self.downsampleSpin = QtWidgets.QSpinBox(self.decimateGroup) self.downsampleSpin.setMinimum(1) self.downsampleSpin.setMaximum(100000) self.downsampleSpin.setProperty("value", 1) self.downsampleSpin.setObjectName("downsampleSpin") self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) self.transformGroup = QtWidgets.QFrame(Form) self.transformGroup.setGeometry(QtCore.QRect(10, 10, 171, 101)) self.transformGroup.setObjectName("transformGroup") self.gridLayout = QtWidgets.QGridLayout(self.transformGroup) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.logYCheck = QtWidgets.QCheckBox(self.transformGroup) self.logYCheck.setObjectName("logYCheck") self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) self.logXCheck = QtWidgets.QCheckBox(self.transformGroup) self.logXCheck.setObjectName("logXCheck") self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) self.fftCheck = QtWidgets.QCheckBox(self.transformGroup) self.fftCheck.setObjectName("fftCheck") self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) self.derivativeCheck = QtWidgets.QCheckBox(self.transformGroup) self.derivativeCheck.setObjectName("derivativeCheck") self.gridLayout.addWidget(self.derivativeCheck, 3, 0, 1, 1) self.phasemapCheck = QtWidgets.QCheckBox(self.transformGroup) self.phasemapCheck.setObjectName("phasemapCheck") self.gridLayout.addWidget(self.phasemapCheck, 4, 0, 1, 1) self.pointsGroup = QtWidgets.QGroupBox(Form) self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) self.pointsGroup.setCheckable(True) self.pointsGroup.setObjectName("pointsGroup") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.pointsGroup) self.verticalLayout_5.setObjectName("verticalLayout_5") self.autoPointsCheck = QtWidgets.QCheckBox(self.pointsGroup) self.autoPointsCheck.setChecked(True) self.autoPointsCheck.setObjectName("autoPointsCheck") self.verticalLayout_5.addWidget(self.autoPointsCheck) self.gridGroup = QtWidgets.QFrame(Form) self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) self.gridGroup.setObjectName("gridGroup") self.gridLayout_2 = QtWidgets.QGridLayout(self.gridGroup) self.gridLayout_2.setObjectName("gridLayout_2") self.xGridCheck = QtWidgets.QCheckBox(self.gridGroup) self.xGridCheck.setObjectName("xGridCheck") self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) self.yGridCheck = QtWidgets.QCheckBox(self.gridGroup) self.yGridCheck.setObjectName("yGridCheck") self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) self.gridAlphaSlider = QtWidgets.QSlider(self.gridGroup) self.gridAlphaSlider.setMaximum(255) self.gridAlphaSlider.setProperty("value", 128) self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) self.gridAlphaSlider.setObjectName("gridAlphaSlider") self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) self.label = QtWidgets.QLabel(self.gridGroup) self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) self.alphaGroup = QtWidgets.QGroupBox(Form) self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) self.alphaGroup.setCheckable(True) self.alphaGroup.setObjectName("alphaGroup") self.horizontalLayout = QtWidgets.QHBoxLayout(self.alphaGroup) self.horizontalLayout.setObjectName("horizontalLayout") self.autoAlphaCheck = QtWidgets.QCheckBox(self.alphaGroup) self.autoAlphaCheck.setChecked(False) self.autoAlphaCheck.setObjectName("autoAlphaCheck") self.horizontalLayout.addWidget(self.autoAlphaCheck) self.alphaSlider = QtWidgets.QSlider(self.alphaGroup) self.alphaSlider.setMaximum(1000) self.alphaSlider.setProperty("value", 1000) self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) self.alphaSlider.setObjectName("alphaSlider") self.horizontalLayout.addWidget(self.alphaSlider) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "PyQtGraph", None, -1)) self.averageGroup.setToolTip(QtWidgets.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, -1)) self.averageGroup.setTitle(QtWidgets.QApplication.translate("Form", "Average", None, -1)) self.clipToViewCheck.setToolTip(QtWidgets.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, -1)) self.clipToViewCheck.setText(QtWidgets.QApplication.translate("Form", "Clip to View", None, -1)) self.maxTracesCheck.setToolTip(QtWidgets.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, -1)) self.maxTracesCheck.setText(QtWidgets.QApplication.translate("Form", "Max Traces:", None, -1)) self.downsampleCheck.setText(QtWidgets.QApplication.translate("Form", "Downsample", None, -1)) self.peakRadio.setToolTip(QtWidgets.QApplication.translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None, -1)) self.peakRadio.setText(QtWidgets.QApplication.translate("Form", "Peak", None, -1)) self.maxTracesSpin.setToolTip(QtWidgets.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, -1)) self.forgetTracesCheck.setToolTip(QtWidgets.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, -1)) self.forgetTracesCheck.setText(QtWidgets.QApplication.translate("Form", "Forget hidden traces", None, -1)) self.meanRadio.setToolTip(QtWidgets.QApplication.translate("Form", "Downsample by taking the mean of N samples.", None, -1)) self.meanRadio.setText(QtWidgets.QApplication.translate("Form", "Mean", None, -1)) self.subsampleRadio.setToolTip(QtWidgets.QApplication.translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None, -1)) self.subsampleRadio.setText(QtWidgets.QApplication.translate("Form", "Subsample", None, -1)) self.autoDownsampleCheck.setToolTip(QtWidgets.QApplication.translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None, -1)) self.autoDownsampleCheck.setText(QtWidgets.QApplication.translate("Form", "Auto", None, -1)) self.downsampleSpin.setToolTip(QtWidgets.QApplication.translate("Form", "Downsample data before plotting. (plot every Nth sample)", None, -1)) self.downsampleSpin.setSuffix(QtWidgets.QApplication.translate("Form", "x", None, -1)) self.logYCheck.setText(QtWidgets.QApplication.translate("Form", "Log Y", None, -1)) self.logXCheck.setText(QtWidgets.QApplication.translate("Form", "Log X", None, -1)) self.fftCheck.setText(QtWidgets.QApplication.translate("Form", "Power Spectrum (FFT)", None, -1)) self.derivativeCheck.setText(QtWidgets.QApplication.translate("Form", "dy/dx", None, -1)) self.phasemapCheck.setText(QtWidgets.QApplication.translate("Form", "Y vs. Y\'", None, -1)) self.pointsGroup.setTitle(QtWidgets.QApplication.translate("Form", "Points", None, -1)) self.autoPointsCheck.setText(QtWidgets.QApplication.translate("Form", "Auto", None, -1)) self.xGridCheck.setText(QtWidgets.QApplication.translate("Form", "Show X Grid", None, -1)) self.yGridCheck.setText(QtWidgets.QApplication.translate("Form", "Show Y Grid", None, -1)) self.label.setText(QtWidgets.QApplication.translate("Form", "Opacity", None, -1)) self.alphaGroup.setTitle(QtWidgets.QApplication.translate("Form", "Alpha", None, -1)) self.autoAlphaCheck.setText(QtWidgets.QApplication.translate("Form", "Auto", None, -1)) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside6.py�����������0000664�0000000�0000000�00000030112�14210455074�0032024�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ################################################################################ ## Form generated from reading UI file 'plotConfigTemplate.ui' ## ## Created by: Qt User Interface Compiler version 6.1.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(481, 840) self.averageGroup = QGroupBox(Form) self.averageGroup.setObjectName(u"averageGroup") self.averageGroup.setGeometry(QRect(0, 640, 242, 182)) self.averageGroup.setCheckable(True) self.averageGroup.setChecked(False) self.gridLayout_5 = QGridLayout(self.averageGroup) self.gridLayout_5.setSpacing(0) self.gridLayout_5.setContentsMargins(0, 0, 0, 0) self.gridLayout_5.setObjectName(u"gridLayout_5") self.avgParamList = QListWidget(self.averageGroup) self.avgParamList.setObjectName(u"avgParamList") self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) self.decimateGroup = QFrame(Form) self.decimateGroup.setObjectName(u"decimateGroup") self.decimateGroup.setGeometry(QRect(10, 140, 191, 171)) self.gridLayout_4 = QGridLayout(self.decimateGroup) self.gridLayout_4.setSpacing(0) self.gridLayout_4.setContentsMargins(0, 0, 0, 0) self.gridLayout_4.setObjectName(u"gridLayout_4") self.clipToViewCheck = QCheckBox(self.decimateGroup) self.clipToViewCheck.setObjectName(u"clipToViewCheck") self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) self.maxTracesCheck = QCheckBox(self.decimateGroup) self.maxTracesCheck.setObjectName(u"maxTracesCheck") self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) self.downsampleCheck = QCheckBox(self.decimateGroup) self.downsampleCheck.setObjectName(u"downsampleCheck") self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) self.peakRadio = QRadioButton(self.decimateGroup) self.peakRadio.setObjectName(u"peakRadio") self.peakRadio.setChecked(True) self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) self.maxTracesSpin = QSpinBox(self.decimateGroup) self.maxTracesSpin.setObjectName(u"maxTracesSpin") self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) self.forgetTracesCheck = QCheckBox(self.decimateGroup) self.forgetTracesCheck.setObjectName(u"forgetTracesCheck") self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) self.meanRadio = QRadioButton(self.decimateGroup) self.meanRadio.setObjectName(u"meanRadio") self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) self.subsampleRadio = QRadioButton(self.decimateGroup) self.subsampleRadio.setObjectName(u"subsampleRadio") self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) self.autoDownsampleCheck = QCheckBox(self.decimateGroup) self.autoDownsampleCheck.setObjectName(u"autoDownsampleCheck") self.autoDownsampleCheck.setChecked(True) self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) self.horizontalSpacer = QSpacerItem(30, 20, QSizePolicy.Maximum, QSizePolicy.Minimum) self.gridLayout_4.addItem(self.horizontalSpacer, 2, 0, 1, 1) self.downsampleSpin = QSpinBox(self.decimateGroup) self.downsampleSpin.setObjectName(u"downsampleSpin") self.downsampleSpin.setMinimum(1) self.downsampleSpin.setMaximum(100000) self.downsampleSpin.setValue(1) self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) self.transformGroup = QFrame(Form) self.transformGroup.setObjectName(u"transformGroup") self.transformGroup.setGeometry(QRect(10, 10, 171, 101)) self.gridLayout = QGridLayout(self.transformGroup) self.gridLayout.setSpacing(0) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName(u"gridLayout") self.logYCheck = QCheckBox(self.transformGroup) self.logYCheck.setObjectName(u"logYCheck") self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) self.logXCheck = QCheckBox(self.transformGroup) self.logXCheck.setObjectName(u"logXCheck") self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) self.fftCheck = QCheckBox(self.transformGroup) self.fftCheck.setObjectName(u"fftCheck") self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) self.derivativeCheck = QCheckBox(self.transformGroup) self.derivativeCheck.setObjectName(u"derivativeCheck") self.gridLayout.addWidget(self.derivativeCheck, 3, 0, 1, 1) self.phasemapCheck = QCheckBox(self.transformGroup) self.phasemapCheck.setObjectName(u"phasemapCheck") self.gridLayout.addWidget(self.phasemapCheck, 4, 0, 1, 1) self.pointsGroup = QGroupBox(Form) self.pointsGroup.setObjectName(u"pointsGroup") self.pointsGroup.setGeometry(QRect(10, 550, 234, 58)) self.pointsGroup.setCheckable(True) self.verticalLayout_5 = QVBoxLayout(self.pointsGroup) self.verticalLayout_5.setObjectName(u"verticalLayout_5") self.autoPointsCheck = QCheckBox(self.pointsGroup) self.autoPointsCheck.setObjectName(u"autoPointsCheck") self.autoPointsCheck.setChecked(True) self.verticalLayout_5.addWidget(self.autoPointsCheck) self.gridGroup = QFrame(Form) self.gridGroup.setObjectName(u"gridGroup") self.gridGroup.setGeometry(QRect(10, 460, 221, 81)) self.gridLayout_2 = QGridLayout(self.gridGroup) self.gridLayout_2.setObjectName(u"gridLayout_2") self.xGridCheck = QCheckBox(self.gridGroup) self.xGridCheck.setObjectName(u"xGridCheck") self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) self.yGridCheck = QCheckBox(self.gridGroup) self.yGridCheck.setObjectName(u"yGridCheck") self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) self.gridAlphaSlider = QSlider(self.gridGroup) self.gridAlphaSlider.setObjectName(u"gridAlphaSlider") self.gridAlphaSlider.setMaximum(255) self.gridAlphaSlider.setValue(128) self.gridAlphaSlider.setOrientation(Qt.Horizontal) self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) self.label = QLabel(self.gridGroup) self.label.setObjectName(u"label") self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) self.alphaGroup = QGroupBox(Form) self.alphaGroup.setObjectName(u"alphaGroup") self.alphaGroup.setGeometry(QRect(10, 390, 234, 60)) self.alphaGroup.setCheckable(True) self.horizontalLayout = QHBoxLayout(self.alphaGroup) self.horizontalLayout.setObjectName(u"horizontalLayout") self.autoAlphaCheck = QCheckBox(self.alphaGroup) self.autoAlphaCheck.setObjectName(u"autoAlphaCheck") self.autoAlphaCheck.setChecked(False) self.horizontalLayout.addWidget(self.autoAlphaCheck) self.alphaSlider = QSlider(self.alphaGroup) self.alphaSlider.setObjectName(u"alphaSlider") self.alphaSlider.setMaximum(1000) self.alphaSlider.setValue(1000) self.alphaSlider.setOrientation(Qt.Horizontal) self.horizontalLayout.addWidget(self.alphaSlider) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) #if QT_CONFIG(tooltip) self.averageGroup.setToolTip(QCoreApplication.translate("Form", u"Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None)) #endif // QT_CONFIG(tooltip) self.averageGroup.setTitle(QCoreApplication.translate("Form", u"Average", None)) #if QT_CONFIG(tooltip) self.clipToViewCheck.setToolTip(QCoreApplication.translate("Form", u"Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None)) #endif // QT_CONFIG(tooltip) self.clipToViewCheck.setText(QCoreApplication.translate("Form", u"Clip to View", None)) #if QT_CONFIG(tooltip) self.maxTracesCheck.setToolTip(QCoreApplication.translate("Form", u"If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None)) #endif // QT_CONFIG(tooltip) self.maxTracesCheck.setText(QCoreApplication.translate("Form", u"Max Traces:", None)) self.downsampleCheck.setText(QCoreApplication.translate("Form", u"Downsample", None)) #if QT_CONFIG(tooltip) self.peakRadio.setToolTip(QCoreApplication.translate("Form", u"Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None)) #endif // QT_CONFIG(tooltip) self.peakRadio.setText(QCoreApplication.translate("Form", u"Peak", None)) #if QT_CONFIG(tooltip) self.maxTracesSpin.setToolTip(QCoreApplication.translate("Form", u"If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None)) #endif // QT_CONFIG(tooltip) #if QT_CONFIG(tooltip) self.forgetTracesCheck.setToolTip(QCoreApplication.translate("Form", u"If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None)) #endif // QT_CONFIG(tooltip) self.forgetTracesCheck.setText(QCoreApplication.translate("Form", u"Forget hidden traces", None)) #if QT_CONFIG(tooltip) self.meanRadio.setToolTip(QCoreApplication.translate("Form", u"Downsample by taking the mean of N samples.", None)) #endif // QT_CONFIG(tooltip) self.meanRadio.setText(QCoreApplication.translate("Form", u"Mean", None)) #if QT_CONFIG(tooltip) self.subsampleRadio.setToolTip(QCoreApplication.translate("Form", u"Downsample by taking the first of N samples. This method is fastest and least accurate.", None)) #endif // QT_CONFIG(tooltip) self.subsampleRadio.setText(QCoreApplication.translate("Form", u"Subsample", None)) #if QT_CONFIG(tooltip) self.autoDownsampleCheck.setToolTip(QCoreApplication.translate("Form", u"Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None)) #endif // QT_CONFIG(tooltip) self.autoDownsampleCheck.setText(QCoreApplication.translate("Form", u"Auto", None)) #if QT_CONFIG(tooltip) self.downsampleSpin.setToolTip(QCoreApplication.translate("Form", u"Downsample data before plotting. (plot every Nth sample)", None)) #endif // QT_CONFIG(tooltip) self.downsampleSpin.setSuffix(QCoreApplication.translate("Form", u"x", None)) self.logYCheck.setText(QCoreApplication.translate("Form", u"Log Y", None)) self.logXCheck.setText(QCoreApplication.translate("Form", u"Log X", None)) self.fftCheck.setText(QCoreApplication.translate("Form", u"Power Spectrum (FFT)", None)) self.derivativeCheck.setText(QCoreApplication.translate("Form", u"dy/dx", None)) self.phasemapCheck.setText(QCoreApplication.translate("Form", u"Y vs. Y'", None)) self.pointsGroup.setTitle(QCoreApplication.translate("Form", u"Points", None)) self.autoPointsCheck.setText(QCoreApplication.translate("Form", u"Auto", None)) self.xGridCheck.setText(QCoreApplication.translate("Form", u"Show X Grid", None)) self.yGridCheck.setText(QCoreApplication.translate("Form", u"Show Y Grid", None)) self.label.setText(QCoreApplication.translate("Form", u"Opacity", None)) self.alphaGroup.setTitle(QCoreApplication.translate("Form", u"Alpha", None)) self.autoAlphaCheck.setText(QCoreApplication.translate("Form", u"Auto", None)) # retranslateUi ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ROI.py�������������������������������������������0000664�0000000�0000000�00000303477�14210455074�0023476�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" ROI.py - Interactive graphics items for GraphicsView (ROI widgets) Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. Implements a series of graphics items which display movable/scalable/rotatable shapes for use as region-of-interest markers. ROI class automatically handles extraction of array data from ImageItems. The ROI class is meant to serve as the base for more specific types; see several examples of how to build an ROI at the bottom of the file. """ import sys import warnings from math import atan2, cos, degrees, hypot, sin import numpy as np from .. import functions as fn #from numpy.linalg import norm from ..Point import Point from ..Qt import QtCore, QtGui, QtWidgets from ..SRTTransform import SRTTransform from .GraphicsObject import GraphicsObject from .UIGraphicsItem import UIGraphicsItem translate = QtCore.QCoreApplication.translate __all__ = [ 'ROI', 'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI', 'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', 'CrosshairROI','TriangleROI' ] def rectStr(r): return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height()) class ROI(GraphicsObject): """ Generic region-of-interest widget. Can be used for implementing many types of selection box with rotate/translate/scale handles. ROIs can be customized to have a variety of shapes (by subclassing or using any of the built-in subclasses) and any combination of draggable handles that allow the user to manipulate the ROI. Default mouse interaction: * Left drag moves the ROI * Left drag + Ctrl moves the ROI with position snapping * Left drag + Alt rotates the ROI * Left drag + Alt + Ctrl rotates the ROI with angle snapping * Left drag + Shift scales the ROI * Left drag + Shift + Ctrl scales the ROI with size snapping In addition to the above interaction modes, it is possible to attach any number of handles to the ROI that can be dragged to change the ROI in various ways (see the ROI.add____Handle methods). ================ =========================================================== **Arguments** pos (length-2 sequence) Indicates the position of the ROI's origin. For most ROIs, this is the lower-left corner of its bounding rectangle. size (length-2 sequence) Indicates the width and height of the ROI. angle (float) The rotation of the ROI in degrees. Default is 0. invertible (bool) If True, the user may resize the ROI to have negative width or height (assuming the ROI has scale handles). Default is False. maxBounds (QRect, QRectF, or None) Specifies boundaries that the ROI cannot be dragged outside of by the user. Default is None. snapSize (float) The spacing of snap positions used when *scaleSnap* or *translateSnap* are enabled. Default is 1.0. scaleSnap (bool) If True, the width and height of the ROI are forced to be integer multiples of *snapSize* when being resized by the user. Default is False. translateSnap (bool) If True, the x and y positions of the ROI are forced to be integer multiples of *snapSize* when being resized by the user. Default is False. rotateSnap (bool) If True, the ROI angle is forced to a multiple of the ROI's snap angle (default is 15 degrees) when rotated by the user. Default is False. parent (QGraphicsItem) The graphics item parent of this ROI. It is generally not necessary to specify the parent. pen (QPen or argument to pg.mkPen) The pen to use when drawing the shape of the ROI. hoverPen (QPen or argument to mkPen) The pen to use while the mouse is hovering over the ROI shape. handlePen (QPen or argument to mkPen) The pen to use when drawing the ROI handles. handleHoverPen (QPen or argument to mkPen) The pen to use while the mouse is hovering over an ROI handle. movable (bool) If True, the ROI can be moved by dragging anywhere inside the ROI. Default is True. rotatable (bool) If True, the ROI can be rotated by mouse drag + ALT resizable (bool) If True, the ROI can be resized by mouse drag + SHIFT removable (bool) If True, the ROI will be given a context menu with an option to remove the ROI. The ROI emits sigRemoveRequested when this menu action is selected. Default is False. ================ =========================================================== ======================= ==================================================== **Signals** sigRegionChangeFinished Emitted when the user stops dragging the ROI (or one of its handles) or if the ROI is changed programatically. sigRegionChangeStarted Emitted when the user starts dragging the ROI (or one of its handles). sigRegionChanged Emitted any time the position of the ROI changes, including while it is being dragged by the user. sigHoverEvent Emitted when the mouse hovers over the ROI. sigClicked Emitted when the user clicks on the ROI. Note that clicking is disabled by default to prevent stealing clicks from objects behind the ROI. To enable clicking, call roi.setAcceptedMouseButtons(QtCore.Qt.MouseButton.LeftButton). See QtWidgets.QGraphicsItem documentation for more details. sigRemoveRequested Emitted when the user selects 'remove' from the ROI's context menu (if available). ======================= ==================================================== """ sigRegionChangeFinished = QtCore.Signal(object) sigRegionChangeStarted = QtCore.Signal(object) sigRegionChanged = QtCore.Signal(object) sigHoverEvent = QtCore.Signal(object) sigClicked = QtCore.Signal(object, object) sigRemoveRequested = QtCore.Signal(object) def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None, hoverPen=None, handlePen=None, handleHoverPen=None, movable=True, rotatable=True, resizable=True, removable=False, aspectLocked=False): GraphicsObject.__init__(self, parent) self.setAcceptedMouseButtons(QtCore.Qt.MouseButton.NoButton) pos = Point(pos) size = Point(size) self.aspectLocked = aspectLocked self.translatable = movable self.rotatable = rotatable self.resizable = resizable self.removable = removable self.menu = None self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted. self.mouseHovering = False if pen is None: pen = (255, 255, 255) self.setPen(pen) if hoverPen is None: hoverPen = (255, 255, 0) self.hoverPen = fn.mkPen(hoverPen) if handlePen is None: handlePen = (150, 255, 255) self.handlePen = fn.mkPen(handlePen) if handleHoverPen is None: handleHoverPen = (255, 255, 0) self.handleHoverPen = handleHoverPen self.handles = [] self.state = {'pos': Point(0,0), 'size': Point(1,1), 'angle': 0} ## angle is in degrees for ease of Qt integration self.lastState = None self.setPos(pos) self.setAngle(angle) self.setSize(size) self.setZValue(10) self.isMoving = False self.handleSize = 5 self.invertible = invertible self.maxBounds = maxBounds self.snapSize = snapSize self.translateSnap = translateSnap self.rotateSnap = rotateSnap self.rotateSnapAngle = 15.0 self.scaleSnap = scaleSnap self.scaleSnapSize = snapSize # Implement mouse handling in a separate class to allow easier customization self.mouseDragHandler = MouseDragHandler(self) def getState(self): return self.stateCopy() def stateCopy(self): sc = {} sc['pos'] = Point(self.state['pos']) sc['size'] = Point(self.state['size']) sc['angle'] = self.state['angle'] return sc def saveState(self): """Return the state of the widget in a format suitable for storing to disk. (Points are converted to tuple) Combined with setState(), this allows ROIs to be easily saved and restored.""" state = {} state['pos'] = tuple(self.state['pos']) state['size'] = tuple(self.state['size']) state['angle'] = self.state['angle'] return state def setState(self, state, update=True): """ Set the state of the ROI from a structure generated by saveState() or getState(). """ self.setPos(state['pos'], update=False) self.setSize(state['size'], update=False) self.setAngle(state['angle'], update=update) def setZValue(self, z): QtWidgets.QGraphicsItem.setZValue(self, z) for h in self.handles: h['item'].setZValue(z+1) def parentBounds(self): """ Return the bounding rectangle of this ROI in the coordinate system of its parent. """ return self.mapToParent(self.boundingRect()).boundingRect() def setPen(self, *args, **kwargs): """ Set the pen to use when drawing the ROI shape. For arguments, see :func:`mkPen `. """ self.pen = fn.mkPen(*args, **kwargs) self.currentPen = self.pen self.update() def size(self): """Return the size (w,h) of the ROI.""" return self.getState()['size'] def pos(self): """Return the position (x,y) of the ROI's origin. For most ROIs, this will be the lower-left corner.""" return self.getState()['pos'] def angle(self): """Return the angle of the ROI in degrees.""" return self.getState()['angle'] def setPos(self, pos, y=None, update=True, finish=True): """Set the position of the ROI (in the parent's coordinate system). Accepts either separate (x, y) arguments or a single :class:`Point` or ``QPointF`` argument. By default, this method causes both ``sigRegionChanged`` and ``sigRegionChangeFinished`` to be emitted. If *finish* is False, then ``sigRegionChangeFinished`` will not be emitted. You can then use stateChangeFinished() to cause the signal to be emitted after a series of state changes. If *update* is False, the state change will be remembered but not processed and no signals will be emitted. You can then use stateChanged() to complete the state change. This allows multiple change functions to be called sequentially while minimizing processing overhead and repeated signals. Setting ``update=False`` also forces ``finish=False``. """ if update not in (True, False): raise TypeError("update argument must be bool") if y is None: pos = Point(pos) else: # avoid ambiguity where update is provided as a positional argument if isinstance(y, bool): raise TypeError("Positional arguments to setPos() must be numerical.") pos = Point(pos, y) self.state['pos'] = pos QtWidgets.QGraphicsItem.setPos(self, pos) if update: self.stateChanged(finish=finish) def setSize(self, size, center=None, centerLocal=None, snap=False, update=True, finish=True): """ Set the ROI's size. =============== ========================================================================== **Arguments** size (Point | QPointF | sequence) The final size of the ROI center (None | Point) Optional center point around which the ROI is scaled, expressed as [0-1, 0-1] over the size of the ROI. centerLocal (None | Point) Same as *center*, but the position is expressed in the local coordinate system of the ROI snap (bool) If True, the final size is snapped to the nearest increment (see ROI.scaleSnapSize) update (bool) See setPos() finish (bool) See setPos() =============== ========================================================================== """ if update not in (True, False): raise TypeError("update argument must be bool") size = Point(size) if snap: size[0] = round(size[0] / self.scaleSnapSize) * self.scaleSnapSize size[1] = round(size[1] / self.scaleSnapSize) * self.scaleSnapSize if centerLocal is not None: oldSize = Point(self.state['size']) oldSize[0] = 1 if oldSize[0] == 0 else oldSize[0] oldSize[1] = 1 if oldSize[1] == 0 else oldSize[1] center = Point(centerLocal) / oldSize if center is not None: center = Point(center) c = self.mapToParent(Point(center) * self.state['size']) c1 = self.mapToParent(Point(center) * size) newPos = self.state['pos'] + c - c1 self.setPos(newPos, update=False, finish=False) self.prepareGeometryChange() self.state['size'] = size if update: self.stateChanged(finish=finish) def setAngle(self, angle, center=None, centerLocal=None, snap=False, update=True, finish=True): """ Set the ROI's rotation angle. =============== ========================================================================== **Arguments** angle (float) The final ROI angle in degrees center (None | Point) Optional center point around which the ROI is rotated, expressed as [0-1, 0-1] over the size of the ROI. centerLocal (None | Point) Same as *center*, but the position is expressed in the local coordinate system of the ROI snap (bool) If True, the final ROI angle is snapped to the nearest increment (default is 15 degrees; see ROI.rotateSnapAngle) update (bool) See setPos() finish (bool) See setPos() =============== ========================================================================== """ if update not in (True, False): raise TypeError("update argument must be bool") if snap is True: angle = round(angle / self.rotateSnapAngle) * self.rotateSnapAngle self.state['angle'] = angle tr = QtGui.QTransform() # note: only rotation is contained in the transform tr.rotate(angle) if center is not None: centerLocal = Point(center) * self.state['size'] if centerLocal is not None: centerLocal = Point(centerLocal) # rotate to new angle, keeping a specific point anchored as the center of rotation cc = self.mapToParent(centerLocal) - (tr.map(centerLocal) + self.state['pos']) self.translate(cc, update=False) self.setTransform(tr) if update: self.stateChanged(finish=finish) def scale(self, s, center=None, centerLocal=None, snap=False, update=True, finish=True): """ Resize the ROI by scaling relative to *center*. See setPos() for an explanation of the *update* and *finish* arguments. """ newSize = self.state['size'] * s self.setSize(newSize, center=center, centerLocal=centerLocal, snap=snap, update=update, finish=finish) def translate(self, *args, **kargs): """ Move the ROI to a new position. Accepts either (x, y, snap) or ([x,y], snap) as arguments If the ROI is bounded and the move would exceed boundaries, then the ROI is moved to the nearest acceptable position instead. *snap* can be: =============== ========================================================================== None (default) use self.translateSnap and self.snapSize to determine whether/how to snap False do not snap Point(w,h) snap to rectangular grid with spacing (w,h) True snap using self.snapSize (and ignoring self.translateSnap) =============== ========================================================================== Also accepts *update* and *finish* arguments (see setPos() for a description of these). """ if len(args) == 1: pt = args[0] else: pt = args newState = self.stateCopy() newState['pos'] = newState['pos'] + pt snap = kargs.get('snap', None) if snap is None: snap = self.translateSnap if snap is not False: newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap) if self.maxBounds is not None: r = self.stateRect(newState) d = Point(0,0) if self.maxBounds.left() > r.left(): d[0] = self.maxBounds.left() - r.left() elif self.maxBounds.right() < r.right(): d[0] = self.maxBounds.right() - r.right() if self.maxBounds.top() > r.top(): d[1] = self.maxBounds.top() - r.top() elif self.maxBounds.bottom() < r.bottom(): d[1] = self.maxBounds.bottom() - r.bottom() newState['pos'] += d update = kargs.get('update', True) finish = kargs.get('finish', True) self.setPos(newState['pos'], update=update, finish=finish) def rotate(self, angle, center=None, snap=False, update=True, finish=True): """ Rotate the ROI by *angle* degrees. =============== ========================================================================== **Arguments** angle (float) The angle in degrees to rotate center (None | Point) Optional center point around which the ROI is rotated, in the local coordinate system of the ROI snap (bool) If True, the final ROI angle is snapped to the nearest increment (default is 15 degrees; see ROI.rotateSnapAngle) update (bool) See setPos() finish (bool) See setPos() =============== ========================================================================== """ self.setAngle(self.angle()+angle, center=center, snap=snap, update=update, finish=finish) def handleMoveStarted(self): self.preMoveState = self.getState() self.sigRegionChangeStarted.emit(self) def addTranslateHandle(self, pos, axes=None, item=None, name=None, index=None): """ Add a new translation handle to the ROI. Dragging the handle will move the entire ROI without changing its angle or shape. Note that, by default, ROIs may be moved by dragging anywhere inside the ROI. However, for larger ROIs it may be desirable to disable this and instead provide one or more translation handles. =================== ==================================================== **Arguments** pos (length-2 sequence) The position of the handle relative to the shape of the ROI. A value of (0,0) indicates the origin, whereas (1, 1) indicates the upper-right corner, regardless of the ROI's size. item The Handle instance to add. If None, a new handle will be created. name The name of this handle (optional). Handles are identified by name when calling getLocalHandlePositions and getSceneHandlePositions. =================== ==================================================== """ pos = Point(pos) return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item}, index=index) def addFreeHandle(self, pos=None, axes=None, item=None, name=None, index=None): """ Add a new free handle to the ROI. Dragging free handles has no effect on the position or shape of the ROI. =================== ==================================================== **Arguments** pos (length-2 sequence) The position of the handle relative to the shape of the ROI. A value of (0,0) indicates the origin, whereas (1, 1) indicates the upper-right corner, regardless of the ROI's size. item The Handle instance to add. If None, a new handle will be created. name The name of this handle (optional). Handles are identified by name when calling getLocalHandlePositions and getSceneHandlePositions. =================== ==================================================== """ if pos is not None: pos = Point(pos) return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item}, index=index) def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspect=False, index=None): """ Add a new scale handle to the ROI. Dragging a scale handle allows the user to change the height and/or width of the ROI. =================== ==================================================== **Arguments** pos (length-2 sequence) The position of the handle relative to the shape of the ROI. A value of (0,0) indicates the origin, whereas (1, 1) indicates the upper-right corner, regardless of the ROI's size. center (length-2 sequence) The center point around which scaling takes place. If the center point has the same x or y value as the handle position, then scaling will be disabled for that axis. item The Handle instance to add. If None, a new handle will be created. name The name of this handle (optional). Handles are identified by name when calling getLocalHandlePositions and getSceneHandlePositions. =================== ==================================================== """ pos = Point(pos) center = Point(center) info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item, 'lockAspect': lockAspect} if pos.x() == center.x(): info['xoff'] = True if pos.y() == center.y(): info['yoff'] = True return self.addHandle(info, index=index) def addRotateHandle(self, pos, center, item=None, name=None, index=None): """ Add a new rotation handle to the ROI. Dragging a rotation handle allows the user to change the angle of the ROI. =================== ==================================================== **Arguments** pos (length-2 sequence) The position of the handle relative to the shape of the ROI. A value of (0,0) indicates the origin, whereas (1, 1) indicates the upper-right corner, regardless of the ROI's size. center (length-2 sequence) The center point around which rotation takes place. item The Handle instance to add. If None, a new handle will be created. name The name of this handle (optional). Handles are identified by name when calling getLocalHandlePositions and getSceneHandlePositions. =================== ==================================================== """ pos = Point(pos) center = Point(center) return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item}, index=index) def addScaleRotateHandle(self, pos, center, item=None, name=None, index=None): """ Add a new scale+rotation handle to the ROI. When dragging a handle of this type, the user can simultaneously rotate the ROI around an arbitrary center point as well as scale the ROI by dragging the handle toward or away from the center point. =================== ==================================================== **Arguments** pos (length-2 sequence) The position of the handle relative to the shape of the ROI. A value of (0,0) indicates the origin, whereas (1, 1) indicates the upper-right corner, regardless of the ROI's size. center (length-2 sequence) The center point around which scaling and rotation take place. item The Handle instance to add. If None, a new handle will be created. name The name of this handle (optional). Handles are identified by name when calling getLocalHandlePositions and getSceneHandlePositions. =================== ==================================================== """ pos = Point(pos) center = Point(center) if pos[0] == center[0] and pos[1] == center[1]: raise Exception("Scale/rotate handles cannot be at their center point.") return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item}, index=index) def addRotateFreeHandle(self, pos, center, axes=None, item=None, name=None, index=None): """ Add a new rotation+free handle to the ROI. When dragging a handle of this type, the user can rotate the ROI around an arbitrary center point, while moving toward or away from the center point has no effect on the shape of the ROI. =================== ==================================================== **Arguments** pos (length-2 sequence) The position of the handle relative to the shape of the ROI. A value of (0,0) indicates the origin, whereas (1, 1) indicates the upper-right corner, regardless of the ROI's size. center (length-2 sequence) The center point around which rotation takes place. item The Handle instance to add. If None, a new handle will be created. name The name of this handle (optional). Handles are identified by name when calling getLocalHandlePositions and getSceneHandlePositions. =================== ==================================================== """ pos = Point(pos) center = Point(center) return self.addHandle({'name': name, 'type': 'rf', 'center': center, 'pos': pos, 'item': item}, index=index) def addHandle(self, info, index=None): ## If a Handle was not supplied, create it now if 'item' not in info or info['item'] is None: h = Handle(self.handleSize, typ=info['type'], pen=self.handlePen, hoverPen=self.handleHoverPen, parent=self) info['item'] = h else: h = info['item'] if info['pos'] is None: info['pos'] = h.pos() h.setPos(info['pos'] * self.state['size']) ## connect the handle to this ROI #iid = len(self.handles) h.connectROI(self) if index is None: self.handles.append(info) else: self.handles.insert(index, info) h.setZValue(self.zValue()+1) self.stateChanged() return h def indexOfHandle(self, handle): """ Return the index of *handle* in the list of this ROI's handles. """ if isinstance(handle, Handle): index = [i for i, info in enumerate(self.handles) if info['item'] is handle] if len(index) == 0: raise Exception("Cannot return handle index; not attached to this ROI") return index[0] else: return handle def removeHandle(self, handle): """Remove a handle from this ROI. Argument may be either a Handle instance or the integer index of the handle.""" index = self.indexOfHandle(handle) handle = self.handles[index]['item'] self.handles.pop(index) handle.disconnectROI(self) if len(handle.rois) == 0 and self.scene() is not None: self.scene().removeItem(handle) self.stateChanged() def replaceHandle(self, oldHandle, newHandle): """Replace one handle in the ROI for another. This is useful when connecting multiple ROIs together. *oldHandle* may be a Handle instance or the index of a handle to be replaced.""" index = self.indexOfHandle(oldHandle) info = self.handles[index] self.removeHandle(index) info['item'] = newHandle info['pos'] = newHandle.pos() self.addHandle(info, index=index) def checkRemoveHandle(self, handle): ## This is used when displaying a Handle's context menu to determine ## whether removing is allowed. ## Subclasses may wish to override this to disable the menu entry. ## Note: by default, handles are not user-removable even if this method returns True. return True def getLocalHandlePositions(self, index=None): """Returns the position of handles in the ROI's coordinate system. The format returned is a list of (name, pos) tuples. """ if index is None: positions = [] for h in self.handles: positions.append((h['name'], h['pos'])) return positions else: return (self.handles[index]['name'], self.handles[index]['pos']) def getSceneHandlePositions(self, index=None): """Returns the position of handles in the scene coordinate system. The format returned is a list of (name, pos) tuples. """ if index is None: positions = [] for h in self.handles: positions.append((h['name'], h['item'].scenePos())) return positions else: return (self.handles[index]['name'], self.handles[index]['item'].scenePos()) def getHandles(self): """ Return a list of this ROI's Handles. """ return [h['item'] for h in self.handles] def mapSceneToParent(self, pt): return self.mapToParent(self.mapFromScene(pt)) def setSelected(self, s): QtWidgets.QGraphicsItem.setSelected(self, s) #print "select", self, s if s: for h in self.handles: h['item'].show() else: for h in self.handles: h['item'].hide() def hoverEvent(self, ev): hover = False if not ev.isExit(): if self.translatable and ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton): hover=True for btn in [QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.MouseButton.RightButton, QtCore.Qt.MouseButton.MiddleButton]: if (self.acceptedMouseButtons() & btn) and ev.acceptClicks(btn): hover=True if self.contextMenuEnabled(): ev.acceptClicks(QtCore.Qt.MouseButton.RightButton) if hover: self.setMouseHover(True) ev.acceptClicks(QtCore.Qt.MouseButton.LeftButton) ## If the ROI is hilighted, we should accept all clicks to avoid confusion. ev.acceptClicks(QtCore.Qt.MouseButton.RightButton) ev.acceptClicks(QtCore.Qt.MouseButton.MiddleButton) self.sigHoverEvent.emit(self) else: self.setMouseHover(False) def setMouseHover(self, hover): ## Inform the ROI that the mouse is(not) hovering over it if self.mouseHovering == hover: return self.mouseHovering = hover self._updateHoverColor() def _updateHoverColor(self): pen = self._makePen() if self.currentPen != pen: self.currentPen = pen self.update() def _makePen(self): # Generate the pen color for this ROI based on its current state. if self.mouseHovering: return self.hoverPen else: return self.pen def contextMenuEnabled(self): return self.removable def raiseContextMenu(self, ev): if not self.contextMenuEnabled(): return menu = self.getMenu() menu = self.scene().addParentContextMenus(self, menu, ev) pos = ev.screenPos() menu.popup(QtCore.QPoint(pos.x(), pos.y())) def getMenu(self): if self.menu is None: self.menu = QtWidgets.QMenu() self.menu.setTitle(translate("ROI", "ROI")) remAct = QtGui.QAction(translate("ROI", "Remove ROI"), self.menu) remAct.triggered.connect(self.removeClicked) self.menu.addAction(remAct) self.menu.remAct = remAct # ROI menu may be requested when showing the handle context menu, so # return the menu but disable it if the ROI isn't removable self.menu.setEnabled(self.contextMenuEnabled()) return self.menu def removeClicked(self): ## Send remove event only after we have exited the menu event handler QtCore.QTimer.singleShot(0, self._emitRemoveRequest) def _emitRemoveRequest(self): self.sigRemoveRequested.emit(self) def mouseDragEvent(self, ev): self.mouseDragHandler.mouseDragEvent(ev) def mouseClickEvent(self, ev): with warnings.catch_warnings(): # warning present on pyqt5 5.12 + python 3.8 warnings.filterwarnings( "ignore", message=( ".*Implicit conversion to integers using __int__ is " "deprecated, and may be removed in a future version of " "Python." ), category=DeprecationWarning ) if ev.button() == QtCore.Qt.MouseButton.RightButton and self.isMoving: ev.accept() self.cancelMove() if ev.button() == QtCore.Qt.MouseButton.RightButton and self.contextMenuEnabled(): self.raiseContextMenu(ev) ev.accept() elif ev.button() & self.acceptedMouseButtons(): ev.accept() self.sigClicked.emit(self, ev) else: ev.ignore() def _moveStarted(self): self.isMoving = True self.preMoveState = self.getState() self.sigRegionChangeStarted.emit(self) def _moveFinished(self): if self.isMoving: self.stateChangeFinished() self.isMoving = False def cancelMove(self): self.isMoving = False self.setState(self.preMoveState) def checkPointMove(self, handle, pos, modifiers): """When handles move, they must ask the ROI if the move is acceptable. By default, this always returns True. Subclasses may wish override. """ return True def movePoint(self, handle, pos, modifiers=None, finish=True, coords='parent'): ## called by Handles when they are moved. ## pos is the new position of the handle in scene coords, as requested by the handle. if modifiers is None: modifiers = QtCore.Qt.KeyboardModifier.NoModifier newState = self.stateCopy() index = self.indexOfHandle(handle) h = self.handles[index] p0 = self.mapToParent(h['pos'] * self.state['size']) p1 = Point(pos) if coords == 'parent': pass elif coords == 'scene': p1 = self.mapSceneToParent(p1) else: raise Exception("New point location must be given in either 'parent' or 'scene' coordinates.") ## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1) if 'center' in h: c = h['center'] cs = c * self.state['size'] lp0 = self.mapFromParent(p0) - cs lp1 = self.mapFromParent(p1) - cs if h['type'] == 't': snap = True if (modifiers & QtCore.Qt.KeyboardModifier.ControlModifier) else None self.translate(p1-p0, snap=snap, update=False) elif h['type'] == 'f': newPos = self.mapFromParent(p1) h['item'].setPos(newPos) h['pos'] = newPos self.freeHandleMoved = True elif h['type'] == 's': ## If a handle and its center have the same x or y value, we can't scale across that axis. if h['center'][0] == h['pos'][0]: lp1[0] = 0 if h['center'][1] == h['pos'][1]: lp1[1] = 0 ## snap if self.scaleSnap or (modifiers & QtCore.Qt.KeyboardModifier.ControlModifier): lp1[0] = round(lp1[0] / self.scaleSnapSize) * self.scaleSnapSize lp1[1] = round(lp1[1] / self.scaleSnapSize) * self.scaleSnapSize ## preserve aspect ratio (this can override snapping) if h['lockAspect'] or (modifiers & QtCore.Qt.KeyboardModifier.AltModifier): #arv = Point(self.preMoveState['size']) - lp1 = lp1.proj(lp0) ## determine scale factors and new size of ROI hs = h['pos'] - c if hs[0] == 0: hs[0] = 1 if hs[1] == 0: hs[1] = 1 newSize = lp1 / hs ## Perform some corrections and limit checks if newSize[0] == 0: newSize[0] = newState['size'][0] if newSize[1] == 0: newSize[1] = newState['size'][1] if not self.invertible: if newSize[0] < 0: newSize[0] = newState['size'][0] if newSize[1] < 0: newSize[1] = newState['size'][1] if self.aspectLocked: newSize[0] = newSize[1] ## Move ROI so the center point occupies the same scene location after the scale s0 = c * self.state['size'] s1 = c * newSize cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0)) ## update state, do more boundary checks newState['size'] = newSize newState['pos'] = newState['pos'] + cc if self.maxBounds is not None: r = self.stateRect(newState) if not self.maxBounds.contains(r): return self.setPos(newState['pos'], update=False) self.setSize(newState['size'], update=False) elif h['type'] in ['r', 'rf']: if h['type'] == 'rf': self.freeHandleMoved = True if not self.rotatable: return ## If the handle is directly over its center point, we can't compute an angle. try: if lp1.length() == 0 or lp0.length() == 0: return except OverflowError: return ## determine new rotation angle, constrained if necessary ang = newState['angle'] - lp0.angle(lp1) if ang is None: ## this should never happen.. return if self.rotateSnap or (modifiers & QtCore.Qt.KeyboardModifier.ControlModifier): ang = round(ang / self.rotateSnapAngle) * self.rotateSnapAngle ## create rotation transform tr = QtGui.QTransform() tr.rotate(ang) ## move ROI so that center point remains stationary after rotate cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos']) newState['angle'] = ang newState['pos'] = newState['pos'] + cc ## check boundaries, update if self.maxBounds is not None: r = self.stateRect(newState) if not self.maxBounds.contains(r): return self.setPos(newState['pos'], update=False) self.setAngle(ang, update=False) ## If this is a free-rotate handle, its distance from the center may change. if h['type'] == 'rf': h['item'].setPos(self.mapFromScene(p1)) ## changes ROI coordinates of handle h['pos'] = self.mapFromParent(p1) elif h['type'] == 'sr': try: if lp1.length() == 0 or lp0.length() == 0: return except OverflowError: return ang = newState['angle'] - lp0.angle(lp1) if ang is None: return if self.rotateSnap or (modifiers & QtCore.Qt.KeyboardModifier.ControlModifier): ang = round(ang / self.rotateSnapAngle) * self.rotateSnapAngle if self.aspectLocked or h['center'][0] != h['pos'][0]: newState['size'][0] = self.state['size'][0] * lp1.length() / lp0.length() if self.scaleSnap: # use CTRL only for angular snap here. newState['size'][0] = round(newState['size'][0] / self.snapSize) * self.snapSize if self.aspectLocked or h['center'][1] != h['pos'][1]: newState['size'][1] = self.state['size'][1] * lp1.length() / lp0.length() if self.scaleSnap: # use CTRL only for angular snap here. newState['size'][1] = round(newState['size'][1] / self.snapSize) * self.snapSize if newState['size'][0] == 0: newState['size'][0] = 1 if newState['size'][1] == 0: newState['size'][1] = 1 c1 = c * newState['size'] tr = QtGui.QTransform() tr.rotate(ang) cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos']) newState['angle'] = ang newState['pos'] = newState['pos'] + cc if self.maxBounds is not None: r = self.stateRect(newState) if not self.maxBounds.contains(r): return self.setState(newState, update=False) self.stateChanged(finish=finish) def stateChanged(self, finish=True): """Process changes to the state of the ROI. If there are any changes, then the positions of handles are updated accordingly and sigRegionChanged is emitted. If finish is True, then sigRegionChangeFinished will also be emitted.""" changed = False if self.lastState is None: changed = True else: state = self.getState() for k in list(state.keys()): if state[k] != self.lastState[k]: changed = True self.prepareGeometryChange() if changed: ## Move all handles to match the current configuration of the ROI for h in self.handles: if h['item'] in self.childItems(): p = h['pos'] h['item'].setPos(h['pos'] * self.state['size']) self.update() self.sigRegionChanged.emit(self) elif self.freeHandleMoved: self.sigRegionChanged.emit(self) self.freeHandleMoved = False self.lastState = self.getState() if finish: self.stateChangeFinished() self.informViewBoundsChanged() def stateChangeFinished(self): self.sigRegionChangeFinished.emit(self) def stateRect(self, state): r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1]) tr = QtGui.QTransform() tr.rotate(-state['angle']) r = tr.mapRect(r) return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1]) def getSnapPosition(self, pos, snap=None): ## Given that pos has been requested, return the nearest snap-to position ## optionally, snap may be passed in to specify a rectangular snap grid. ## override this function for more interesting snap functionality.. if snap is None or snap is True: if self.snapSize is None: return pos snap = Point(self.snapSize, self.snapSize) return Point( round(pos[0] / snap[0]) * snap[0], round(pos[1] / snap[1]) * snap[1] ) def boundingRect(self): return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() def paint(self, p, opt, widget): # Note: don't use self.boundingRect here, because subclasses may need to redefine it. r = QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() p.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) p.setPen(self.currentPen) p.translate(r.left(), r.top()) p.scale(r.width(), r.height()) p.drawRect(0, 0, 1, 1) def getArraySlice(self, data, img, axes=(0,1), returnSlice=True): """Return a tuple of slice objects that can be used to slice the region from *data* that is covered by the bounding rectangle of this ROI. Also returns the transform that maps the ROI into data coordinates. If returnSlice is set to False, the function returns a pair of tuples with the values that would have been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop)) If the slice cannot be computed (usually because the scene/transforms are not properly constructed yet), then the method returns None. """ ## Determine shape of array along ROI axes dShape = (data.shape[axes[0]], data.shape[axes[1]]) ## Determine transform that maps ROI bounding box to image coordinates try: tr = self.sceneTransform() * fn.invertQTransform(img.sceneTransform()) except np.linalg.linalg.LinAlgError: return None ## Modify transform to scale from image coords to data coords axisOrder = img.axisOrder if axisOrder == 'row-major': tr.scale(float(dShape[1]) / img.width(), float(dShape[0]) / img.height()) else: tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height()) ## Transform ROI bounds into data bounds dataBounds = tr.mapRect(self.boundingRect()) ## Intersect transformed ROI bounds with data bounds if axisOrder == 'row-major': intBounds = dataBounds.intersected(QtCore.QRectF(0, 0, dShape[1], dShape[0])) else: intBounds = dataBounds.intersected(QtCore.QRectF(0, 0, dShape[0], dShape[1])) ## Determine index values to use when referencing the array. bounds = ( (int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))), (int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top()))) ) if axisOrder == 'row-major': bounds = bounds[::-1] if returnSlice: ## Create slice objects sl = [slice(None)] * data.ndim sl[axes[0]] = slice(*bounds[0]) sl[axes[1]] = slice(*bounds[1]) return tuple(sl), tr else: return bounds, tr def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds): r"""Use the position and orientation of this ROI relative to an imageItem to pull a slice from an array. =================== ==================================================== **Arguments** data The array to slice from. Note that this array does *not* have to be the same data that is represented in *img*. img (ImageItem or other suitable QGraphicsItem) Used to determine the relationship between the ROI and the boundaries of *data*. axes (length-2 tuple) Specifies the axes in *data* that correspond to the (x, y) axes of *img*. If the image's axis order is set to 'row-major', then the axes are instead specified in (y, x) order. returnMappedCoords (bool) If True, the array slice is returned along with a corresponding array of coordinates that were used to extract data from the original array. \**kwds All keyword arguments are passed to :func:`affineSlice `. =================== ==================================================== This method uses :func:`affineSlice ` to generate the slice from *data* and uses :func:`getAffineSliceParams ` to determine the parameters to pass to :func:`affineSlice `. If *returnMappedCoords* is True, then the method returns a tuple (result, coords) such that coords is the set of coordinates used to interpolate values from the original data, mapped into the parent coordinate system of the image. This is useful, when slicing data from images that have been transformed, for determining the location of each value in the sliced data. All extra keyword arguments are passed to :func:`affineSlice `. """ # this is a hidden argument for internal use fromBR = kwds.pop('fromBoundingRect', False) # Automaticaly compute missing parameters _shape, _vectors, _origin = self.getAffineSliceParams(data, img, axes, fromBoundingRect=fromBR) # Replace them with user defined parameters if defined shape = kwds.pop('shape', _shape) vectors = kwds.pop('vectors', _vectors) origin = kwds.pop('origin', _origin) if not returnMappedCoords: rgn = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) return rgn else: kwds['returnCoords'] = True result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) ### map coordinates and return mapped = fn.transformCoordinates(img.transform(), coords) return result, mapped def _getArrayRegionForArbitraryShape(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds): """ Return the result of :meth:`~pyqtgraph.ROI.getArrayRegion`, masked by the shape of the ROI. Values outside the ROI shape are set to 0. See :meth:`~pyqtgraph.ROI.getArrayRegion` for a description of the arguments. """ br = self.boundingRect() if br.width() > 1000: raise Exception() if returnMappedCoords: sliced, mappedCoords = ROI.getArrayRegion( self, data, img, axes, returnMappedCoords, fromBoundingRect=True, **kwds) else: sliced = ROI.getArrayRegion( self, data, img, axes, returnMappedCoords, fromBoundingRect=True, **kwds) if img.axisOrder == 'col-major': mask = self.renderShapeMask(sliced.shape[axes[0]], sliced.shape[axes[1]]) else: mask = self.renderShapeMask(sliced.shape[axes[1]], sliced.shape[axes[0]]) mask = mask.T # reshape mask to ensure it is applied to the correct data axes shape = [1] * data.ndim shape[axes[0]] = sliced.shape[axes[0]] shape[axes[1]] = sliced.shape[axes[1]] mask = mask.reshape(shape) if returnMappedCoords: return sliced * mask, mappedCoords else: return sliced * mask def getAffineSliceParams(self, data, img, axes=(0,1), fromBoundingRect=False): """ Returns the parameters needed to use :func:`affineSlice ` (shape, vectors, origin) to extract a subset of *data* using this ROI and *img* to specify the subset. If *fromBoundingRect* is True, then the ROI's bounding rectangle is used rather than the shape of the ROI. See :func:`getArrayRegion ` for more information. """ if self.scene() is not img.scene(): raise Exception("ROI and target item must be members of the same scene.") origin = img.mapToData(self.mapToItem(img, QtCore.QPointF(0, 0))) ## vx and vy point in the directions of the slice axes, but must be scaled properly vx = img.mapToData(self.mapToItem(img, QtCore.QPointF(1, 0))) - origin vy = img.mapToData(self.mapToItem(img, QtCore.QPointF(0, 1))) - origin lvx = hypot(vx.x(), vx.y()) # length lvy = hypot(vy.x(), vy.y()) # length ##img.width is number of pixels, not width of item. ##need pxWidth and pxHeight instead of pxLen ? sx = 1.0 / lvx sy = 1.0 / lvy vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) if fromBoundingRect is True: shape = self.boundingRect().width(), self.boundingRect().height() origin = img.mapToData(self.mapToItem(img, self.boundingRect().topLeft())) origin = (origin.x(), origin.y()) else: shape = self.state['size'] origin = (origin.x(), origin.y()) shape = [abs(shape[0]/sx), abs(shape[1]/sy)] if img.axisOrder == 'row-major': # transpose output vectors = vectors[::-1] shape = shape[::-1] return shape, vectors, origin def renderShapeMask(self, width, height): """Return an array of 0.0-1.0 into which the shape of the item has been drawn. This can be used to mask array selections. """ if width == 0 or height == 0: return np.empty((width, height), dtype=float) im = QtGui.QImage(width, height, QtGui.QImage.Format.Format_ARGB32) im.fill(QtCore.Qt.GlobalColor.transparent) p = QtGui.QPainter(im) p.setPen(fn.mkPen(None)) p.setBrush(fn.mkBrush('w')) shape = self.shape() bounds = shape.boundingRect() p.scale(im.width() / bounds.width(), im.height() / bounds.height()) p.translate(-bounds.topLeft()) p.drawPath(shape) p.end() cidx = 0 if sys.byteorder == 'little' else 3 mask = fn.ndarray_from_qimage(im)[...,cidx].T return mask.astype(float) / 255 def getGlobalTransform(self, relativeTo=None): """Return global transformation (rotation angle+translation) required to move from relative state to current state. If relative state isn't specified, then we use the state of the ROI when mouse is pressed.""" if relativeTo is None: relativeTo = self.preMoveState st = self.getState() ## this is only allowed because we will be comparing the two relativeTo['scale'] = relativeTo['size'] st['scale'] = st['size'] t1 = SRTTransform(relativeTo) t2 = SRTTransform(st) return t2/t1 def applyGlobalTransform(self, tr): st = self.getState() st['scale'] = st['size'] st = SRTTransform(st) st = (st * tr).saveState() st['size'] = st['scale'] self.setState(st) class Handle(UIGraphicsItem): """ Handle represents a single user-interactable point attached to an ROI. They are usually created by a call to one of the ROI.add___Handle() methods. Handles are represented as a square, diamond, or circle, and are drawn with fixed pixel size regardless of the scaling of the view they are displayed in. Handles may be dragged to change the position, size, orientation, or other properties of the ROI they are attached to. """ types = { ## defines number of sides, start angle for each handle type 't': (4, np.pi/4), 'f': (4, np.pi/4), 's': (4, 0), 'r': (12, 0), 'sr': (12, 0), 'rf': (12, 0), } sigClicked = QtCore.Signal(object, object) # self, event sigRemoveRequested = QtCore.Signal(object) # self def __init__(self, radius, typ=None, pen=(200, 200, 220), hoverPen=(255, 255, 0), parent=None, deletable=False): self.rois = [] self.radius = radius self.typ = typ self.pen = fn.mkPen(pen) self.hoverPen = fn.mkPen(hoverPen) self.currentPen = self.pen self.pen.setWidth(0) self.pen.setCosmetic(True) self.isMoving = False self.sides, self.startAng = self.types[typ] self.buildPath() self._shape = None self.menu = self.buildMenu() UIGraphicsItem.__init__(self, parent=parent) self.setAcceptedMouseButtons(QtCore.Qt.MouseButton.NoButton) self.deletable = deletable if deletable: self.setAcceptedMouseButtons(QtCore.Qt.MouseButton.RightButton) self.setZValue(11) def connectROI(self, roi): ### roi is the "parent" roi, i is the index of the handle in roi.handles self.rois.append(roi) def disconnectROI(self, roi): self.rois.remove(roi) def setDeletable(self, b): self.deletable = b if b: self.setAcceptedMouseButtons(self.acceptedMouseButtons() | QtCore.Qt.MouseButton.RightButton) else: self.setAcceptedMouseButtons(self.acceptedMouseButtons() & ~QtCore.Qt.MouseButton.RightButton) def removeClicked(self): self.sigRemoveRequested.emit(self) def hoverEvent(self, ev): hover = False if not ev.isExit(): if ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton): hover=True for btn in [QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.MouseButton.RightButton, QtCore.Qt.MouseButton.MiddleButton]: if (self.acceptedMouseButtons() & btn) and ev.acceptClicks(btn): hover=True if hover: self.currentPen = self.hoverPen else: self.currentPen = self.pen self.update() def mouseClickEvent(self, ev): with warnings.catch_warnings(): # warning present on pyqt5 5.12 + python 3.8 warnings.filterwarnings( "ignore", message=( ".*Implicit conversion to integers using __int__ is " "deprecated, and may be removed in a future version of " "Python." ), category=DeprecationWarning ) ## right-click cancels drag if ev.button() == QtCore.Qt.MouseButton.RightButton and self.isMoving: self.isMoving = False ## prevents any further motion self.movePoint(self.startPos, finish=True) ev.accept() elif ev.button() & self.acceptedMouseButtons(): ev.accept() if ev.button() == QtCore.Qt.MouseButton.RightButton and self.deletable: self.raiseContextMenu(ev) self.sigClicked.emit(self, ev) else: ev.ignore() def buildMenu(self): menu = QtWidgets.QMenu() menu.setTitle(translate("ROI", "Handle")) self.removeAction = menu.addAction(translate("ROI", "Remove handle"), self.removeClicked) return menu def getMenu(self): return self.menu def raiseContextMenu(self, ev): menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) ## Make sure it is still ok to remove this handle removeAllowed = all(r.checkRemoveHandle(self) for r in self.rois) self.removeAction.setEnabled(removeAllowed) pos = ev.screenPos() menu.popup(QtCore.QPoint(pos.x(), pos.y())) def mouseDragEvent(self, ev): if ev.button() != QtCore.Qt.MouseButton.LeftButton: return ev.accept() ## Inform ROIs that a drag is happening ## note: the ROI is informed that the handle has moved using ROI.movePoint ## this is for other (more nefarious) purposes. #for r in self.roi: #r[0].pointDragEvent(r[1], ev) if ev.isFinish(): if self.isMoving: for r in self.rois: r.stateChangeFinished() self.isMoving = False self.currentPen = self.pen self.update() elif ev.isStart(): for r in self.rois: r.handleMoveStarted() self.isMoving = True self.startPos = self.scenePos() self.cursorOffset = self.scenePos() - ev.buttonDownScenePos() self.currentPen = self.hoverPen if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click. pos = ev.scenePos() + self.cursorOffset self.currentPen = self.hoverPen self.movePoint(pos, ev.modifiers(), finish=False) def movePoint(self, pos, modifiers=None, finish=True): if modifiers is None: modifiers = QtCore.Qt.KeyboardModifier.NoModifier for r in self.rois: if not r.checkPointMove(self, pos, modifiers): return #print "point moved; inform %d ROIs" % len(self.roi) # A handle can be used by multiple ROIs; tell each to update its handle position for r in self.rois: r.movePoint(self, pos, modifiers, finish=finish, coords='scene') def buildPath(self): size = self.radius self.path = QtGui.QPainterPath() ang = self.startAng dt = 2 * np.pi / self.sides for i in range(0, self.sides+1): x = size * cos(ang) y = size * sin(ang) ang += dt if i == 0: self.path.moveTo(x, y) else: self.path.lineTo(x, y) def paint(self, p, opt, widget): p.setRenderHints(p.RenderHint.Antialiasing, True) p.setPen(self.currentPen) p.drawPath(self.shape()) def shape(self): if self._shape is None: s = self.generateShape() if s is None: return self.path self._shape = s self.prepareGeometryChange() ## beware--this can cause the view to adjust, which would immediately invalidate the shape. return self._shape def boundingRect(self): s1 = self.shape() return self.shape().boundingRect() def generateShape(self): dt = self.deviceTransform() if dt is None: self._shape = self.path return None v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) va = atan2(v.y(), v.x()) dti = fn.invertQTransform(dt) devPos = dt.map(QtCore.QPointF(0,0)) tr = QtGui.QTransform() tr.translate(devPos.x(), devPos.y()) tr.rotateRadians(va) return dti.map(tr.map(self.path)) def viewTransformChanged(self): GraphicsObject.viewTransformChanged(self) self._shape = None ## invalidate shape, recompute later if requested. self.update() class MouseDragHandler(object): """Implements default mouse drag behavior for ROI (not for ROI handles). """ def __init__(self, roi): self.roi = roi self.dragMode = None self.startState = None self.snapModifier = QtCore.Qt.KeyboardModifier.ControlModifier self.translateModifier = QtCore.Qt.KeyboardModifier.NoModifier self.rotateModifier = QtCore.Qt.KeyboardModifier.AltModifier self.scaleModifier = QtCore.Qt.KeyboardModifier.ShiftModifier self.rotateSpeed = 0.5 self.scaleSpeed = 1.01 def mouseDragEvent(self, ev): roi = self.roi if ev.isStart(): if ev.button() == QtCore.Qt.MouseButton.LeftButton: roi.setSelected(True) mods = ev.modifiers() & ~self.snapModifier if roi.translatable and mods == self.translateModifier: self.dragMode = 'translate' elif roi.rotatable and mods == self.rotateModifier: self.dragMode = 'rotate' elif roi.resizable and mods == self.scaleModifier: self.dragMode = 'scale' else: self.dragMode = None if self.dragMode is not None: roi._moveStarted() self.startPos = roi.mapToParent(ev.buttonDownPos()) self.startState = roi.saveState() self.cursorOffset = roi.pos() - self.startPos ev.accept() else: ev.ignore() else: self.dragMode = None ev.ignore() if ev.isFinish() and self.dragMode is not None: roi._moveFinished() return # roi.isMoving becomes False if the move was cancelled by right-click if not roi.isMoving or self.dragMode is None: return snap = True if (ev.modifiers() & self.snapModifier) else None pos = roi.mapToParent(ev.pos()) if self.dragMode == 'translate': newPos = pos + self.cursorOffset roi.translate(newPos - roi.pos(), snap=snap, finish=False) elif self.dragMode == 'rotate': diff = self.rotateSpeed * (ev.scenePos() - ev.buttonDownScenePos()).x() angle = self.startState['angle'] - diff roi.setAngle(angle, centerLocal=ev.buttonDownPos(), snap=snap, finish=False) elif self.dragMode == 'scale': diff = self.scaleSpeed ** -(ev.scenePos() - ev.buttonDownScenePos()).y() roi.setSize(Point(self.startState['size']) * diff, centerLocal=ev.buttonDownPos(), snap=snap, finish=False) class TestROI(ROI): def __init__(self, pos, size, **args): ROI.__init__(self, pos, size, **args) self.addTranslateHandle([0.5, 0.5]) self.addScaleHandle([1, 1], [0, 0]) self.addScaleHandle([0, 0], [1, 1]) self.addScaleRotateHandle([1, 0.5], [0.5, 0.5]) self.addScaleHandle([0.5, 1], [0.5, 0.5]) self.addRotateHandle([1, 0], [0, 0]) self.addRotateHandle([0, 1], [1, 1]) class RectROI(ROI): r""" Rectangular ROI subclass with a single scale handle at the top-right corner. ============== ============================================================= **Arguments** pos (length-2 sequence) The position of the ROI origin. See ROI(). size (length-2 sequence) The size of the ROI. See ROI(). centered (bool) If True, scale handles affect the ROI relative to its center, rather than its origin. sideScalers (bool) If True, extra scale handles are added at the top and right edges. \**args All extra keyword arguments are passed to ROI() ============== ============================================================= """ def __init__(self, pos, size, centered=False, sideScalers=False, **args): ROI.__init__(self, pos, size, **args) if centered: center = [0.5, 0.5] else: center = [0, 0] self.addScaleHandle([1, 1], center) if sideScalers: self.addScaleHandle([1, 0.5], [center[0], 0.5]) self.addScaleHandle([0.5, 1], [0.5, center[1]]) class LineROI(ROI): r""" Rectangular ROI subclass with scale-rotate handles on either side. This allows the ROI to be positioned as if moving the ends of a line segment. A third handle controls the width of the ROI orthogonal to its "line" axis. ============== ============================================================= **Arguments** pos1 (length-2 sequence) The position of the center of the ROI's left edge. pos2 (length-2 sequence) The position of the center of the ROI's right edge. width (float) The width of the ROI. \**args All extra keyword arguments are passed to ROI() ============== ============================================================= """ def __init__(self, pos1, pos2, width, **args): pos1 = Point(pos1) pos2 = Point(pos2) d = pos2-pos1 l = d.length() ra = d.angle(Point(1, 0), units="radians") c = Point(width/2. * sin(ra), -width/2. * cos(ra)) pos1 = pos1 + c ROI.__init__(self, pos1, size=Point(l, width), angle=degrees(ra), **args) self.addScaleRotateHandle([0, 0.5], [1, 0.5]) self.addScaleRotateHandle([1, 0.5], [0, 0.5]) self.addScaleHandle([0.5, 1], [0.5, 0.5]) class MultiRectROI(QtWidgets.QGraphicsObject): r""" Chain of rectangular ROIs connected by handles. This is generally used to mark a curved path through an image similarly to PolyLineROI. It differs in that each segment of the chain is rectangular instead of linear and thus has width. ============== ============================================================= **Arguments** points (list of length-2 sequences) The list of points in the path. width (float) The width of the ROIs orthogonal to the path. \**args All extra keyword arguments are passed to ROI() ============== ============================================================= """ sigRegionChangeFinished = QtCore.Signal(object) sigRegionChangeStarted = QtCore.Signal(object) sigRegionChanged = QtCore.Signal(object) def __init__(self, points, width, pen=None, **args): QtWidgets.QGraphicsObject.__init__(self) self.pen = pen self.roiArgs = args self.lines = [] if len(points) < 2: raise Exception("Must start with at least 2 points") ## create first segment self.addSegment(points[1], connectTo=points[0], scaleHandle=True) ## create remaining segments for p in points[2:]: self.addSegment(p) def paint(self, *args): pass def boundingRect(self): return QtCore.QRectF() def roiChangedEvent(self): w = self.lines[0].state['size'][1] for l in self.lines[1:]: w0 = l.state['size'][1] if w == w0: continue l.scale([1.0, w/w0], center=[0.5,0.5]) self.sigRegionChanged.emit(self) def roiChangeStartedEvent(self): self.sigRegionChangeStarted.emit(self) def roiChangeFinishedEvent(self): self.sigRegionChangeFinished.emit(self) def getHandlePositions(self): """Return the positions of all handles in local coordinates.""" pos = [self.mapFromScene(self.lines[0].getHandles()[0].scenePos())] for l in self.lines: pos.append(self.mapFromScene(l.getHandles()[1].scenePos())) return pos def getArrayRegion(self, arr, img=None, axes=(0,1), **kwds): """ Return the result of :meth:`~pyqtgraph.ROI.getArrayRegion` for each rect in the chain concatenated into a single ndarray. See :meth:`~pyqtgraph.ROI.getArrayRegion` for a description of the arguments. Note: ``returnMappedCoords`` is not yet supported for this ROI type. """ rgns = [] for l in self.lines: rgn = l.getArrayRegion(arr, img, axes=axes, **kwds) if rgn is None: continue rgns.append(rgn) #print l.state['size'] ## make sure orthogonal axis is the same size ## (sometimes fp errors cause differences) if img.axisOrder == 'row-major': axes = axes[::-1] ms = min([r.shape[axes[1]] for r in rgns]) sl = [slice(None)] * rgns[0].ndim sl[axes[1]] = slice(0,ms) rgns = [r[tuple(sl)] for r in rgns] #print [r.shape for r in rgns], axes return np.concatenate(rgns, axis=axes[0]) def addSegment(self, pos=(0,0), scaleHandle=False, connectTo=None): """ Add a new segment to the ROI connecting from the previous endpoint to *pos*. (pos is specified in the parent coordinate system of the MultiRectROI) """ ## by default, connect to the previous endpoint if connectTo is None: connectTo = self.lines[-1].getHandles()[1] ## create new ROI newRoi = ROI((0,0), [1, 5], parent=self, pen=self.pen, **self.roiArgs) self.lines.append(newRoi) ## Add first SR handle if isinstance(connectTo, Handle): self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=connectTo) newRoi.movePoint(connectTo, connectTo.scenePos(), coords='scene') else: h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5]) newRoi.movePoint(h, connectTo, coords='scene') ## add second SR handle h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5]) newRoi.movePoint(h, pos) ## optionally add scale handle (this MUST come after the two SR handles) if scaleHandle: newRoi.addScaleHandle([0.5, 1], [0.5, 0.5]) newRoi.translatable = False newRoi.sigRegionChanged.connect(self.roiChangedEvent) newRoi.sigRegionChangeStarted.connect(self.roiChangeStartedEvent) newRoi.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent) self.sigRegionChanged.emit(self) def removeSegment(self, index=-1): """Remove a segment from the ROI.""" roi = self.lines[index] self.lines.pop(index) self.scene().removeItem(roi) roi.sigRegionChanged.disconnect(self.roiChangedEvent) roi.sigRegionChangeStarted.disconnect(self.roiChangeStartedEvent) roi.sigRegionChangeFinished.disconnect(self.roiChangeFinishedEvent) self.sigRegionChanged.emit(self) class MultiLineROI(MultiRectROI): def __init__(self, *args, **kwds): MultiRectROI.__init__(self, *args, **kwds) print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)") class EllipseROI(ROI): r""" Elliptical ROI subclass with one scale handle and one rotation handle. ============== ============================================================= **Arguments** pos (length-2 sequence) The position of the ROI's origin. size (length-2 sequence) The size of the ROI's bounding rectangle. \**args All extra keyword arguments are passed to ROI() ============== ============================================================= """ def __init__(self, pos, size, **args): self.path = None ROI.__init__(self, pos, size, **args) self.sigRegionChanged.connect(self._clearPath) self._addHandles() def _addHandles(self): self.addRotateHandle([1.0, 0.5], [0.5, 0.5]) self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) def _clearPath(self): self.path = None def paint(self, p, opt, widget): r = self.boundingRect() p.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) p.setPen(self.currentPen) p.scale(r.width(), r.height())## workaround for GL bug r = QtCore.QRectF(r.x()/r.width(), r.y()/r.height(), 1,1) p.drawEllipse(r) def getArrayRegion(self, arr, img=None, axes=(0, 1), returnMappedCoords=False, **kwds): """ Return the result of :meth:`~pyqtgraph.ROI.getArrayRegion` masked by the elliptical shape of the ROI. Regions outside the ellipse are set to 0. See :meth:`~pyqtgraph.ROI.getArrayRegion` for a description of the arguments. Note: ``returnMappedCoords`` is not yet supported for this ROI type. """ # Note: we could use the same method as used by PolyLineROI, but this # implementation produces a nicer mask. if returnMappedCoords: arr, mappedCoords = ROI.getArrayRegion(self, arr, img, axes, returnMappedCoords, **kwds) else: arr = ROI.getArrayRegion(self, arr, img, axes, returnMappedCoords, **kwds) if arr is None or arr.shape[axes[0]] == 0 or arr.shape[axes[1]] == 0: if returnMappedCoords: return arr, mappedCoords else: return arr w = arr.shape[axes[0]] h = arr.shape[axes[1]] ## generate an ellipsoidal mask mask = np.fromfunction(lambda x,y: np.hypot(((x+0.5)/(w/2.)-1), ((y+0.5)/(h/2.)-1)) < 1, (w, h)) # reshape to match array axes if axes[0] > axes[1]: mask = mask.T shape = [(n if i in axes else 1) for i,n in enumerate(arr.shape)] mask = mask.reshape(shape) if returnMappedCoords: return arr * mask, mappedCoords else: return arr * mask def shape(self): if self.path is None: path = QtGui.QPainterPath() # Note: Qt has a bug where very small ellipses (radius <0.001) do # not correctly intersect with mouse position (upper-left and # lower-right quadrants are not clickable). #path.addEllipse(self.boundingRect()) # Workaround: manually draw the path. br = self.boundingRect() center = br.center() r1 = br.width() / 2. r2 = br.height() / 2. theta = np.linspace(0, 2 * np.pi, 24) x = center.x() + r1 * np.cos(theta) y = center.y() + r2 * np.sin(theta) path.moveTo(x[0], y[0]) for i in range(1, len(x)): path.lineTo(x[i], y[i]) self.path = path return self.path class CircleROI(EllipseROI): r""" Circular ROI subclass. Behaves exactly as EllipseROI, but may only be scaled proportionally to maintain its aspect ratio. ============== ============================================================= **Arguments** pos (length-2 sequence) The position of the ROI's origin. size (length-2 sequence) The size of the ROI's bounding rectangle. \**args All extra keyword arguments are passed to ROI() ============== ============================================================= """ def __init__(self, pos, size=None, radius=None, **args): if size is None: if radius is None: raise TypeError("Must provide either size or radius.") size = (radius*2, radius*2) EllipseROI.__init__(self, pos, size, aspectLocked=True, **args) def _addHandles(self): self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) class PolygonROI(ROI): def __init__(self, positions, pos=None, **args): warnings.warn( 'PolygonROI has been deprecated, will be removed in 0.13' 'use PolyLineROI instead', DeprecationWarning, stacklevel=2 ) if pos is None: pos = [0,0] ROI.__init__(self, pos, [1,1], **args) for p in positions: self.addFreeHandle(p) self.setZValue(1000) def listPoints(self): return [p['item'].pos() for p in self.handles] def paint(self, p, *args): p.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) p.setPen(self.currentPen) for i in range(len(self.handles)): h1 = self.handles[i]['item'].pos() h2 = self.handles[i-1]['item'].pos() p.drawLine(h1, h2) def boundingRect(self): r = QtCore.QRectF() for h in self.handles: r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs return r def shape(self): p = QtGui.QPainterPath() p.moveTo(self.handles[0]['item'].pos()) for i in range(len(self.handles)): p.lineTo(self.handles[i]['item'].pos()) return p def stateCopy(self): sc = {} sc['pos'] = Point(self.state['pos']) sc['size'] = Point(self.state['size']) sc['angle'] = self.state['angle'] return sc class PolyLineROI(ROI): r""" Container class for multiple connected LineSegmentROIs. This class allows the user to draw paths of multiple line segments. ============== ============================================================= **Arguments** positions (list of length-2 sequences) The list of points in the path. Note that, unlike the handle positions specified in other ROIs, these positions must be expressed in the normal coordinate system of the ROI, rather than (0 to 1) relative to the size of the ROI. closed (bool) if True, an extra LineSegmentROI is added connecting the beginning and end points. \**args All extra keyword arguments are passed to ROI() ============== ============================================================= """ def __init__(self, positions, closed=False, pos=None, **args): if pos is None: pos = [0,0] self.closed = closed self.segments = [] ROI.__init__(self, pos, size=[1,1], **args) self.setPoints(positions) def setPoints(self, points, closed=None): """ Set the complete sequence of points displayed by this ROI. ============= ========================================================= **Arguments** points List of (x,y) tuples specifying handle locations to set. closed If bool, then this will set whether the ROI is closed (the last point is connected to the first point). If None, then the closed mode is left unchanged. ============= ========================================================= """ if closed is not None: self.closed = closed self.clearPoints() for p in points: self.addFreeHandle(p) start = -1 if self.closed else 0 for i in range(start, len(self.handles)-1): self.addSegment(self.handles[i]['item'], self.handles[i+1]['item']) def clearPoints(self): """ Remove all handles and segments. """ while len(self.handles) > 0: self.removeHandle(self.handles[0]['item']) def getState(self): state = ROI.getState(self) state['closed'] = self.closed state['points'] = [Point(h.pos()) for h in self.getHandles()] return state def saveState(self): state = ROI.saveState(self) state['closed'] = self.closed state['points'] = [tuple(h.pos()) for h in self.getHandles()] return state def setState(self, state): ROI.setState(self, state) self.setPoints(state['points'], closed=state['closed']) def addSegment(self, h1, h2, index=None): seg = _PolyLineSegment(handles=(h1, h2), pen=self.pen, hoverPen=self.hoverPen, parent=self, movable=False) if index is None: self.segments.append(seg) else: self.segments.insert(index, seg) seg.sigClicked.connect(self.segmentClicked) seg.setAcceptedMouseButtons(QtCore.Qt.MouseButton.LeftButton) seg.setZValue(self.zValue()+1) for h in seg.handles: h['item'].setDeletable(True) h['item'].setAcceptedMouseButtons(h['item'].acceptedMouseButtons() | QtCore.Qt.MouseButton.LeftButton) ## have these handles take left clicks too, so that handles cannot be added on top of other handles def setMouseHover(self, hover): ## Inform all the ROI's segments that the mouse is(not) hovering over it ROI.setMouseHover(self, hover) for s in self.segments: s.setParentHover(hover) def addHandle(self, info, index=None): h = ROI.addHandle(self, info, index=index) h.sigRemoveRequested.connect(self.removeHandle) self.stateChanged(finish=True) return h def segmentClicked(self, segment, ev=None, pos=None): ## pos should be in this item's coordinate system if ev is not None: pos = segment.mapToParent(ev.pos()) elif pos is None: raise Exception("Either an event or a position must be given.") h1 = segment.handles[0]['item'] h2 = segment.handles[1]['item'] i = self.segments.index(segment) h3 = self.addFreeHandle(pos, index=self.indexOfHandle(h2)) self.addSegment(h3, h2, index=i+1) segment.replaceHandle(h2, h3) def removeHandle(self, handle, updateSegments=True): ROI.removeHandle(self, handle) handle.sigRemoveRequested.disconnect(self.removeHandle) if not updateSegments: return segments = handle.rois[:] if len(segments) == 1: self.removeSegment(segments[0]) elif len(segments) > 1: handles = [h['item'] for h in segments[1].handles] handles.remove(handle) segments[0].replaceHandle(handle, handles[0]) self.removeSegment(segments[1]) self.stateChanged(finish=True) def removeSegment(self, seg): for handle in seg.handles[:]: seg.removeHandle(handle['item']) self.segments.remove(seg) seg.sigClicked.disconnect(self.segmentClicked) self.scene().removeItem(seg) def checkRemoveHandle(self, h): ## called when a handle is about to display its context menu if self.closed: return len(self.handles) > 3 else: return len(self.handles) > 2 def paint(self, p, *args): pass def boundingRect(self): return self.shape().boundingRect() def shape(self): p = QtGui.QPainterPath() if len(self.handles) == 0: return p p.moveTo(self.handles[0]['item'].pos()) for i in range(len(self.handles)): p.lineTo(self.handles[i]['item'].pos()) p.lineTo(self.handles[0]['item'].pos()) return p def getArrayRegion(self, *args, **kwds): return self._getArrayRegionForArbitraryShape(*args, **kwds) def setPen(self, *args, **kwds): ROI.setPen(self, *args, **kwds) for seg in self.segments: seg.setPen(*args, **kwds) class LineSegmentROI(ROI): r""" ROI subclass with two freely-moving handles defining a line. ============== ============================================================= **Arguments** positions (list of two length-2 sequences) The endpoints of the line segment. Note that, unlike the handle positions specified in other ROIs, these positions must be expressed in the normal coordinate system of the ROI, rather than (0 to 1) relative to the size of the ROI. \**args All extra keyword arguments are passed to ROI() ============== ============================================================= """ def __init__(self, positions=(None, None), pos=None, handles=(None,None), **args): if pos is None: pos = [0,0] ROI.__init__(self, pos, [1,1], **args) if len(positions) > 2: raise Exception("LineSegmentROI must be defined by exactly 2 positions. For more points, use PolyLineROI.") for i, p in enumerate(positions): self.addFreeHandle(p, item=handles[i]) @property def endpoints(self): # must not be cached because self.handles may change. return [h['item'] for h in self.handles] def listPoints(self): return [p['item'].pos() for p in self.handles] def getState(self): state = ROI.getState(self) state['points'] = [Point(h.pos()) for h in self.getHandles()] return state def saveState(self): state = ROI.saveState(self) state['points'] = [tuple(h.pos()) for h in self.getHandles()] return state def setState(self, state): ROI.setState(self, state) p1 = [state['points'][0][0]+state['pos'][0], state['points'][0][1]+state['pos'][1]] p2 = [state['points'][1][0]+state['pos'][0], state['points'][1][1]+state['pos'][1]] self.movePoint(self.getHandles()[0], p1, finish=False) self.movePoint(self.getHandles()[1], p2) def paint(self, p, *args): p.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) p.setPen(self.currentPen) h1 = self.endpoints[0].pos() h2 = self.endpoints[1].pos() p.drawLine(h1, h2) def boundingRect(self): return self.shape().boundingRect() def shape(self): p = QtGui.QPainterPath() h1 = self.endpoints[0].pos() h2 = self.endpoints[1].pos() dh = h2-h1 if dh.length() == 0: return p pxv = self.pixelVectors(dh)[1] if pxv is None: return p pxv *= 4 p.moveTo(h1+pxv) p.lineTo(h2+pxv) p.lineTo(h2-pxv) p.lineTo(h1-pxv) p.lineTo(h1+pxv) return p def getArrayRegion(self, data, img, axes=(0,1), order=1, returnMappedCoords=False, **kwds): """ Use the position of this ROI relative to an imageItem to pull a slice from an array. Since this pulls 1D data from a 2D coordinate system, the return value will have ndim = data.ndim-1 See :meth:`~pyqtgraph.ROI.getArrayRegion` for a description of the arguments. """ imgPts = [self.mapToItem(img, h.pos()) for h in self.endpoints] rgns = [] coords = [] d = Point(imgPts[1] - imgPts[0]) o = Point(imgPts[0]) rgn = fn.affineSlice(data, shape=(int(d.length()),), vectors=[Point(d.norm())], origin=o, axes=axes, order=order, returnCoords=returnMappedCoords, **kwds) return rgn class _PolyLineSegment(LineSegmentROI): # Used internally by PolyLineROI def __init__(self, *args, **kwds): self._parentHovering = False LineSegmentROI.__init__(self, *args, **kwds) def setParentHover(self, hover): # set independently of own hover state if self._parentHovering != hover: self._parentHovering = hover self._updateHoverColor() def _makePen(self): if self.mouseHovering or self._parentHovering: return self.hoverPen else: return self.pen def hoverEvent(self, ev): # accept drags even though we discard them to prevent competition with parent ROI # (unless parent ROI is not movable) if self.parentItem().translatable: ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton) return LineSegmentROI.hoverEvent(self, ev) class CrosshairROI(ROI): """A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable.""" def __init__(self, pos=None, size=None, **kargs): if size is None: size=[1,1] if pos is None: pos = [0,0] self._shape = None ROI.__init__(self, pos, size, aspectLocked=True, **kargs) self.sigRegionChanged.connect(self.invalidate) self.addScaleRotateHandle(Point(1, 0), Point(0, 0)) def invalidate(self): self._shape = None self.prepareGeometryChange() def boundingRect(self): return self.shape().boundingRect() def shape(self): if self._shape is None: radius = self.getState()['size'][1] p = QtGui.QPainterPath() p.moveTo(Point(0, -radius)) p.lineTo(Point(0, radius)) p.moveTo(Point(-radius, 0)) p.lineTo(Point(radius, 0)) p = self.mapToDevice(p) stroker = QtGui.QPainterPathStroker() stroker.setWidth(10) outline = stroker.createStroke(p) self._shape = self.mapFromDevice(outline) return self._shape def paint(self, p, *args): radius = self.getState()['size'][1] p.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) p.setPen(self.currentPen) p.drawLine(Point(0, -radius), Point(0, radius)) p.drawLine(Point(-radius, 0), Point(radius, 0)) class RulerROI(LineSegmentROI): def paint(self, p, *args): LineSegmentROI.paint(self, p, *args) h1 = self.handles[0]['item'].pos() h2 = self.handles[1]['item'].pos() p1 = p.transform().map(h1) p2 = p.transform().map(h2) vec = Point(h2) - Point(h1) length = vec.length() angle = vec.angle(Point(1, 0)) pvec = p2 - p1 pvecT = Point(pvec.y(), -pvec.x()) pos = 0.5 * (p1 + p2) + pvecT * 40 / pvecT.length() p.resetTransform() txt = fn.siFormat(length, suffix='m') + '\n%0.1f deg' % angle p.drawText(QtCore.QRectF(pos.x()-50, pos.y()-50, 100, 100), QtCore.Qt.AlignmentFlag.AlignCenter, txt) def boundingRect(self): r = LineSegmentROI.boundingRect(self) pxl = self.pixelLength(Point([1, 0])) if pxl is None: return r pxw = 50 * pxl return r.adjusted(-50, -50, 50, 50) class TriangleROI(ROI): r""" Equilateral triangle ROI subclass with one scale handle and one rotation handle. Arguments pos (length-2 sequence) The position of the ROI's origin. size (float) The length of an edge of the triangle. \**args All extra keyword arguments are passed to ROI() ============== ============================================================= """ def __init__(self, pos, size, **args): ROI.__init__(self, pos, [size, size], aspectLocked=True, **args) angles = np.linspace(0, np.pi * 4 / 3, 3) verticies = (np.array((np.sin(angles), np.cos(angles))).T + 1.0) / 2.0 self.poly = QtGui.QPolygonF() for pt in verticies: self.poly.append(QtCore.QPointF(*pt)) self.addRotateHandle(verticies[0], [0.5, 0.5]) self.addScaleHandle(verticies[1], [0.5, 0.5]) def paint(self, p, *args): r = self.boundingRect() p.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) p.scale(r.width(), r.height()) p.setPen(self.currentPen) p.drawPolygon(self.poly) def shape(self): self.path = QtGui.QPainterPath() r = self.boundingRect() # scale the path to match whats on the screen t = QtGui.QTransform() t.scale(r.width(), r.height()) self.path.addPolygon(self.poly) return t.map(self.path) def getArrayRegion(self, *args, **kwds): return self._getArrayRegionForArbitraryShape(*args, **kwds) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ScaleBar.py��������������������������������������0000664�0000000�0000000�00000004377�14210455074�0024516�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .. import functions as fn from .. import getConfigOption from ..Point import Point from ..Qt import QtCore, QtWidgets from .GraphicsObject import * from .GraphicsWidgetAnchor import * from .TextItem import TextItem __all__ = ['ScaleBar'] class ScaleBar(GraphicsObject, GraphicsWidgetAnchor): """ Displays a rectangular bar to indicate the relative scale of objects on the view. """ def __init__(self, size, width=5, brush=None, pen=None, suffix='m', offset=None): GraphicsObject.__init__(self) GraphicsWidgetAnchor.__init__(self) self.setFlag(self.GraphicsItemFlag.ItemHasNoContents) self.setAcceptedMouseButtons(QtCore.Qt.MouseButton.NoButton) if brush is None: brush = getConfigOption('foreground') self.brush = fn.mkBrush(brush) self.pen = fn.mkPen(pen) self._width = width self.size = size if offset is None: offset = (0,0) self.offset = offset self.bar = QtWidgets.QGraphicsRectItem() self.bar.setPen(self.pen) self.bar.setBrush(self.brush) self.bar.setParentItem(self) self.text = TextItem(text=fn.siFormat(size, suffix=suffix), anchor=(0.5,1)) self.text.setParentItem(self) def parentChanged(self): view = self.parentItem() if view is None: return view.sigRangeChanged.connect(self.updateBar) self.updateBar() def updateBar(self): view = self.parentItem() if view is None: return p1 = view.mapFromViewToItem(self, QtCore.QPointF(0,0)) p2 = view.mapFromViewToItem(self, QtCore.QPointF(self.size,0)) w = (p2-p1).x() self.bar.setRect(QtCore.QRectF(-w, 0, w, self._width)) self.text.setPos(-w/2., 0) def boundingRect(self): return QtCore.QRectF() def setParentItem(self, p): ret = GraphicsObject.setParentItem(self, p) if self.offset is not None: offset = Point(self.offset) anchorx = 1 if offset[0] <= 0 else 0 anchory = 1 if offset[1] <= 0 else 0 anchor = (anchorx, anchory) self.anchor(itemPos=anchor, parentPos=anchor, offset=offset) return ret �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ScatterPlotItem.py�������������������������������0000664�0000000�0000000�00000142014�14210455074�0026114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import itertools import math import warnings import weakref from collections import OrderedDict import numpy as np from .. import Qt, debug from .. import functions as fn from .. import getConfigOption from ..Point import Point from ..Qt import QT_LIB, QtCore, QtGui from .GraphicsObject import GraphicsObject __all__ = ['ScatterPlotItem', 'SpotItem'] ## Build all symbol paths name_list = ['o', 's', 't', 't1', 't2', 't3', 'd', '+', 'x', 'p', 'h', 'star', 'arrow_up', 'arrow_right', 'arrow_down', 'arrow_left', 'crosshair'] Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in name_list]) Symbols['o'].addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) Symbols['s'].addRect(QtCore.QRectF(-0.5, -0.5, 1, 1)) def makeCrosshair(r=0.5, w=1, h=1): path = QtGui.QPainterPath() rect = QtCore.QRectF(-r, -r, r * 2, r * 2) path.addEllipse(rect) path.moveTo(-w, 0) path.lineTo(w, 0) path.moveTo(0, -h) path.lineTo(0, h) return path Symbols['crosshair'] = makeCrosshair() coords = { 't': [(-0.5, -0.5), (0, 0.5), (0.5, -0.5)], 't1': [(-0.5, 0.5), (0, -0.5), (0.5, 0.5)], 't2': [(-0.5, -0.5), (-0.5, 0.5), (0.5, 0)], 't3': [(0.5, 0.5), (0.5, -0.5), (-0.5, 0)], 'd': [(0., -0.5), (-0.4, 0.), (0, 0.5), (0.4, 0)], '+': [ (-0.5, -0.1), (-0.5, 0.1), (-0.1, 0.1), (-0.1, 0.5), (0.1, 0.5), (0.1, 0.1), (0.5, 0.1), (0.5, -0.1), (0.1, -0.1), (0.1, -0.5), (-0.1, -0.5), (-0.1, -0.1) ], 'p': [(0, -0.5), (-0.4755, -0.1545), (-0.2939, 0.4045), (0.2939, 0.4045), (0.4755, -0.1545)], 'h': [(0.433, 0.25), (0., 0.5), (-0.433, 0.25), (-0.433, -0.25), (0, -0.5), (0.433, -0.25)], 'star': [(0, -0.5), (-0.1123, -0.1545), (-0.4755, -0.1545), (-0.1816, 0.059), (-0.2939, 0.4045), (0, 0.1910), (0.2939, 0.4045), (0.1816, 0.059), (0.4755, -0.1545), (0.1123, -0.1545)], 'arrow_up': [ (-0.125, 0.125), (0, 0), (0.125, 0.125), (0.05, 0.125), (0.05, 0.5), (-0.05, 0.5), (-0.05, 0.125) ] } for k, c in coords.items(): Symbols[k].moveTo(*c[0]) for x,y in c[1:]: Symbols[k].lineTo(x, y) Symbols[k].closeSubpath() tr = QtGui.QTransform() tr.rotate(45) Symbols['x'] = tr.map(Symbols['+']) tr.rotate(45) Symbols['arrow_right'] = tr.map(Symbols['arrow_up']) Symbols['arrow_down'] = tr.map(Symbols['arrow_right']) Symbols['arrow_left'] = tr.map(Symbols['arrow_down']) _DEFAULT_STYLE = {'symbol': None, 'size': -1, 'pen': None, 'brush': None, 'visible': True} def drawSymbol(painter, symbol, size, pen, brush): if symbol is None: return painter.scale(size, size) painter.setPen(pen) painter.setBrush(brush) if isinstance(symbol, str): symbol = Symbols[symbol] if np.isscalar(symbol): symbol = list(Symbols.values())[symbol % len(Symbols)] painter.drawPath(symbol) def renderSymbol(symbol, size, pen, brush, device=None): """ Render a symbol specification to QImage. Symbol may be either a QPainterPath or one of the keys in the Symbols dict. If *device* is None, a new QPixmap will be returned. Otherwise, the symbol will be rendered into the device specified (See QPainter documentation for more information). """ ## Render a spot with the given parameters to a pixmap penPxWidth = max(math.ceil(pen.widthF()), 1) if device is None: device = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format.Format_ARGB32) device.fill(QtCore.Qt.GlobalColor.transparent) p = QtGui.QPainter(device) try: p.setRenderHint(p.RenderHint.Antialiasing) p.translate(device.width()*0.5, device.height()*0.5) drawSymbol(p, symbol, size, pen, brush) finally: p.end() return device def makeSymbolPixmap(size, pen, brush, symbol): warnings.warn( "This is an internal function that is no longer being used. " "Will be removed in 0.13", DeprecationWarning, stacklevel=2 ) img = renderSymbol(symbol, size, pen, brush) return QtGui.QPixmap(img) def _mkPen(*args, **kwargs): """ Wrapper for fn.mkPen which avoids creating a new QPen object if passed one as its sole argument. This is used to avoid unnecessary cache misses in SymbolAtlas which uses the QPen object id in its key. """ if len(args) == 1 and isinstance(args[0], QtGui.QPen): return args[0] else: return fn.mkPen(*args, **kwargs) def _mkBrush(*args, **kwargs): """ Wrapper for fn.mkBrush which avoids creating a new QBrush object if passed one as its sole argument. This is used to avoid unnecessary cache misses in SymbolAtlas which uses the QBrush object id in its key. """ if len(args) == 1 and isinstance(args[0], QtGui.QBrush): return args[0] else: return fn.mkBrush(*args, **kwargs) class PixmapFragments: def __init__(self): self.alloc(0) def alloc(self, size): # The C++ native API is: # drawPixmapFragments(const PixmapFragment *fragments, int fragmentCount, # const QPixmap &pixmap) # # PySide exposes this API whereas PyQt wraps it to be more Pythonic. # In PyQt, a Python list of PixmapFragment instances needs to be provided. # This is inefficient because: # 1) constructing the Python list involves calling sip.wrapinstance multiple times. # - this is mitigated here by reusing the instance pointers # 2) PyQt will anyway deconstruct the Python list and repack the PixmapFragment # instances into a contiguous array, in order to call the underlying C++ native API. self.arr = np.empty((size, 10), dtype=np.float64) if QT_LIB.startswith('PyQt'): self.ptrs = list(map(Qt.sip.wrapinstance, itertools.count(self.arr.ctypes.data, self.arr.strides[0]), itertools.repeat(QtGui.QPainter.PixmapFragment, self.arr.shape[0]))) else: self.ptrs = Qt.shiboken.wrapInstance(self.arr.ctypes.data, QtGui.QPainter.PixmapFragment) def array(self, size): if size > self.arr.shape[0]: self.alloc(size + 16) return self.arr[:size] def draw(self, painter, size, pixmap): if QT_LIB.startswith('PyQt'): painter.drawPixmapFragments(self.ptrs[:size], pixmap) else: painter.drawPixmapFragments(self.ptrs, size, pixmap) class SymbolAtlas(object): """ Used to efficiently construct a single QPixmap containing all rendered symbols for a ScatterPlotItem. This is required for fragment rendering. Use example: atlas = SymbolAtlas() sc1 = atlas[[('o', 5, QPen(..), QBrush(..))]] sc2 = atlas[[('t', 10, QPen(..), QBrush(..))]] pm = atlas.pixmap """ _idGenerator = itertools.count() def __init__(self): self._data = np.zeros((0, 0, 4), dtype=np.ubyte) # numpy array of atlas image self._coords = {} self._pixmap = None self._maxWidth = 0 self._totalWidth = 0 self._totalArea = 0 self._pos = (0, 0) self._rowShape = (0, 0) def __getitem__(self, styles): """ Given a list of tuples, (symbol, size, pen, brush), return a list of coordinates of corresponding symbols within the atlas. Note that these coordinates may change if the atlas is rebuilt. """ keys = self._keys(styles) new = {key: style for key, style in zip(keys, styles) if key not in self._coords} if new: self._extend(new) return list(map(self._coords.__getitem__, keys)) def __len__(self): return len(self._coords) @property def pixmap(self): if self._pixmap is None: self._pixmap = self._createPixmap() return self._pixmap @property def maxWidth(self): return self._maxWidth def rebuild(self, styles=None): profiler = debug.Profiler() if styles is None: data = [] else: keys = set(self._keys(styles)) data = list(self._itemData(keys)) self.clear() if data: self._extendFromData(data) def clear(self): self.__init__() def diagnostics(self): n = len(self) w, h, _ = self._data.shape a = self._totalArea return dict(count=n, width=w, height=h, area=w * h, area_used=1.0 if n == 0 else a / (w * h), squareness=1.0 if n == 0 else 2 * w * h / (w**2 + h**2)) def _keys(self, styles): def getId(obj): try: return obj._id except AttributeError: obj._id = next(SymbolAtlas._idGenerator) return obj._id return [ (symbol if isinstance(symbol, (str, int)) else getId(symbol), size, getId(pen), getId(brush)) for symbol, size, pen, brush in styles ] def _itemData(self, keys): for key in keys: y, x, h, w = self._coords[key] yield key, self._data[x:x + w, y:y + h] def _extend(self, styles): profiler = debug.Profiler() images = [] data = [] for key, style in styles.items(): img = renderSymbol(*style) arr = fn.imageToArray(img, copy=False, transpose=False) images.append(img) # keep these to delay garbage collection data.append((key, arr)) profiler('render') self._extendFromData(data) profiler('insert') def _extendFromData(self, data): self._pack(data) # expand array if necessary wNew, hNew = self._minDataShape() wOld, hOld, _ = self._data.shape if (wNew > wOld) or (hNew > hOld): arr = np.zeros((wNew, hNew, 4), dtype=np.ubyte) arr[:wOld, :hOld] = self._data self._data = arr # insert data into array for key, arr in data: y, x, h, w = self._coords[key] self._data[x:x+w, y:y+h] = arr self._pixmap = None def _pack(self, data): # pack each item rectangle as efficiently as possible into a larger, expanding, approximate square n = len(self) wMax = self._maxWidth wSum = self._totalWidth aSum = self._totalArea x, y = self._pos wRow, hRow = self._rowShape # update packing statistics for _, arr in data: w, h, _ = arr.shape wMax = max(w, wMax) wSum += w aSum += w * h n += len(data) # maybe expand row width for squareness and to accommodate largest width wRowEst = int(wSum / (n ** 0.5)) if wRowEst > 2 * wRow: wRow = wRowEst wRow = max(wMax, wRow) # set coordinates by packing along rows # sort by rectangle height first to improve packing density for key, arr in sorted(data, key=lambda data: data[1].shape[1]): w, h, _ = arr.shape if x + w > wRow: # move up a row x = 0 y += hRow hRow = h hRow = max(h, hRow) self._coords[key] = (y, x, h, w) x += w self._maxWidth = wMax self._totalWidth = wSum self._totalArea = aSum self._pos = (x, y) self._rowShape = (wRow, hRow) def _minDataShape(self): x, y = self._pos w, h = self._rowShape return int(w), int(y + h) def _createPixmap(self): profiler = debug.Profiler() if self._data.size == 0: pm = QtGui.QPixmap(0, 0) else: img = fn.makeQImage(self._data, copy=False, transpose=False) pm = QtGui.QPixmap(img) return pm class ScatterPlotItem(GraphicsObject): """ Displays a set of x/y points. Instances of this class are created automatically as part of PlotDataItem; these rarely need to be instantiated directly. The size, shape, pen, and fill brush may be set for each point individually or for all points. ============================ =============================================== **Signals:** sigPlotChanged(self) Emitted when the data being plotted has changed sigClicked(self, points, ev) Emitted when points are clicked. Sends a list of all the points under the mouse pointer. sigHovered(self, points, ev) Emitted when the item is hovered. Sends a list of all the points under the mouse pointer. ============================ =============================================== """ #sigPointClicked = QtCore.Signal(object, object) sigClicked = QtCore.Signal(object, object, object) sigHovered = QtCore.Signal(object, object, object) sigPlotChanged = QtCore.Signal(object) def __init__(self, *args, **kargs): """ Accepts the same arguments as setData() """ profiler = debug.Profiler() GraphicsObject.__init__(self) self.picture = None # QPicture used for rendering when pxmode==False self.fragmentAtlas = SymbolAtlas() dtype = [ ('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('visible', bool), ('data', object), ('hovered', bool), ('item', object), ('sourceRect', [ ('x', int), ('y', int), ('w', int), ('h', int) ]) ] self.data = np.empty(0, dtype=dtype) self.bounds = [None, None] ## caches data bounds self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots self._pixmapFragments = PixmapFragments() self.opts = { 'pxMode': True, 'useCache': True, ## If useCache is False, symbols are re-drawn on every paint. 'antialias': getConfigOption('antialias'), 'compositionMode': None, 'name': None, 'symbol': 'o', 'size': 7, 'pen': fn.mkPen(getConfigOption('foreground')), 'brush': fn.mkBrush(100, 100, 150), 'hoverable': False, 'tip': 'x: {x:.3g}\ny: {y:.3g}\ndata={data}'.format, } self.opts.update( {'hover' + opt.title(): _DEFAULT_STYLE[opt] for opt in ['symbol', 'size', 'pen', 'brush']} ) profiler() self.setData(*args, **kargs) profiler('setData') #self.setCacheMode(self.DeviceCoordinateCache) def setData(self, *args, **kargs): """ **Ordered Arguments:** * If there is only one unnamed argument, it will be interpreted like the 'spots' argument. * If there are two unnamed arguments, they will be interpreted as sequences of x and y values. ====================== =============================================================================================== **Keyword Arguments:** *spots* Optional list of dicts. Each dict specifies parameters for a single spot: {'pos': (x,y), 'size', 'pen', 'brush', 'symbol'}. This is just an alternate method of passing in data for the corresponding arguments. *x*,*y* 1D arrays of x,y values. *pos* 2D structure of x,y pairs (such as Nx2 array or list of tuples) *pxMode* If True, spots are always the same size regardless of scaling, and size is given in px. Otherwise, size is in scene coordinates and the spots scale with the view. To ensure effective caching, QPen and QBrush objects should be reused as much as possible. Default is True *symbol* can be one (or a list) of symbols. For a list of supported symbols, see :func:`~ScatterPlotItem.setSymbol`. QPainterPath is also supported to specify custom symbol shapes. To properly obey the position and size, custom symbols should be centered at (0,0) and width and height of 1.0. Note that it is also possible to 'install' custom shapes by setting ScatterPlotItem.Symbols[key] = shape. *pen* The pen (or list of pens) to use for drawing spot outlines. *brush* The brush (or list of brushes) to use for filling spots. *size* The size (or list of sizes) of spots. If *pxMode* is True, this value is in pixels. Otherwise, it is in the item's local coordinate system. *data* a list of python objects used to uniquely identify each spot. *hoverable* If True, sigHovered is emitted with a list of hovered points, a tool tip is shown containing information about them, and an optional separate style for them is used. Default is False. *tip* A string-valued function of a spot's (x, y, data) values. Set to None to prevent a tool tip from being shown. *hoverSymbol* A single symbol to use for hovered spots. Set to None to keep symbol unchanged. Default is None. *hoverSize* A single size to use for hovered spots. Set to -1 to keep size unchanged. Default is -1. *hoverPen* A single pen to use for hovered spots. Set to None to keep pen unchanged. Default is None. *hoverBrush* A single brush to use for hovered spots. Set to None to keep brush unchanged. Default is None. *antialias* Whether to draw symbols with antialiasing. Note that if pxMode is True, symbols are always rendered with antialiasing (since the rendered symbols can be cached, this incurs very little performance cost) *compositionMode* If specified, this sets the composition mode used when drawing the scatter plot (see QPainter::CompositionMode in the Qt documentation). *name* The name of this item. Names are used for automatically generating LegendItem entries and by some exporters. ====================== =============================================================================================== """ if 'identical' in kargs: warnings.warn( "The *identical* functionality is handled automatically now. " "Will be removed in 0.13.", DeprecationWarning, stacklevel=2 ) oldData = self.data ## this causes cached pixmaps to be preserved while new data is registered. self.clear() ## clear out all old data self.addPoints(*args, **kargs) def addPoints(self, *args, **kargs): """ Add new points to the scatter plot. Arguments are the same as setData() """ ## deal with non-keyword arguments if len(args) == 1: kargs['spots'] = args[0] elif len(args) == 2: kargs['x'] = args[0] kargs['y'] = args[1] elif len(args) > 2: raise Exception('Only accepts up to two non-keyword arguments.') ## convert 'pos' argument to 'x' and 'y' if 'pos' in kargs: pos = kargs['pos'] if isinstance(pos, np.ndarray): kargs['x'] = pos[:,0] kargs['y'] = pos[:,1] else: x = [] y = [] for p in pos: if isinstance(p, QtCore.QPointF): x.append(p.x()) y.append(p.y()) else: x.append(p[0]) y.append(p[1]) kargs['x'] = x kargs['y'] = y ## determine how many spots we have if 'spots' in kargs: numPts = len(kargs['spots']) elif 'y' in kargs and kargs['y'] is not None: numPts = len(kargs['y']) else: kargs['x'] = [] kargs['y'] = [] numPts = 0 ## Clear current SpotItems since the data references they contain will no longer be current self.data['item'][...] = None ## Extend record array oldData = self.data self.data = np.empty(len(oldData)+numPts, dtype=self.data.dtype) ## note that np.empty initializes object fields to None and string fields to '' self.data[:len(oldData)] = oldData #for i in range(len(oldData)): #oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array newData = self.data[len(oldData):] newData['size'] = -1 ## indicates to use default size newData['visible'] = True if 'spots' in kargs: spots = kargs['spots'] for i in range(len(spots)): spot = spots[i] for k in spot: if k == 'pos': pos = spot[k] if isinstance(pos, QtCore.QPointF): x,y = pos.x(), pos.y() else: x,y = pos[0], pos[1] newData[i]['x'] = x newData[i]['y'] = y elif k == 'pen': newData[i][k] = _mkPen(spot[k]) elif k == 'brush': newData[i][k] = _mkBrush(spot[k]) elif k in ['x', 'y', 'size', 'symbol', 'data']: newData[i][k] = spot[k] else: raise Exception("Unknown spot parameter: %s" % k) elif 'y' in kargs: newData['x'] = kargs['x'] newData['y'] = kargs['y'] if 'name' in kargs: self.opts['name'] = kargs['name'] if 'pxMode' in kargs: self.setPxMode(kargs['pxMode']) if 'antialias' in kargs: self.opts['antialias'] = kargs['antialias'] if 'hoverable' in kargs: self.opts['hoverable'] = bool(kargs['hoverable']) if 'tip' in kargs: self.opts['tip'] = kargs['tip'] ## Set any extra parameters provided in keyword arguments for k in ['pen', 'brush', 'symbol', 'size']: if k in kargs: setMethod = getattr(self, 'set' + k[0].upper() + k[1:]) setMethod(kargs[k], update=False, dataSet=newData, mask=kargs.get('mask', None)) kh = 'hover' + k.title() if kh in kargs: vh = kargs[kh] if k == 'pen': vh = _mkPen(vh) elif k == 'brush': vh = _mkBrush(vh) self.opts[kh] = vh if 'data' in kargs: self.setPointData(kargs['data'], dataSet=newData) self.prepareGeometryChange() self.informViewBoundsChanged() self.bounds = [None, None] self.invalidate() self.updateSpots(newData) self.sigPlotChanged.emit(self) def invalidate(self): ## clear any cached drawing state self.picture = None self.update() def getData(self): return self.data['x'], self.data['y'] def setPoints(self, *args, **kargs): warnings.warn( "ScatterPlotItem.setPoints is deprecated, use ScatterPlotItem.setData " "instead. Will be removed in 0.13", DeprecationWarning, stacklevel=2 ) return self.setData(*args, **kargs) def implements(self, interface=None): ints = ['plotData'] if interface is None: return ints return interface in ints def name(self): return self.opts.get('name', None) def setPen(self, *args, **kargs): """Set the pen(s) used to draw the outline around each spot. If a list or array is provided, then the pen for each spot will be set separately. Otherwise, the arguments are passed to pg.mkPen and used as the default pen for all spots which do not have a pen explicitly set.""" update = kargs.pop('update', True) dataSet = kargs.pop('dataSet', self.data) if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)): pens = args[0] if 'mask' in kargs and kargs['mask'] is not None: pens = pens[kargs['mask']] if len(pens) != len(dataSet): raise Exception("Number of pens does not match number of points (%d != %d)" % (len(pens), len(dataSet))) dataSet['pen'] = list(map(_mkPen, pens)) else: self.opts['pen'] = _mkPen(*args, **kargs) dataSet['sourceRect'] = 0 if update: self.updateSpots(dataSet) def setBrush(self, *args, **kargs): """Set the brush(es) used to fill the interior of each spot. If a list or array is provided, then the brush for each spot will be set separately. Otherwise, the arguments are passed to pg.mkBrush and used as the default brush for all spots which do not have a brush explicitly set.""" update = kargs.pop('update', True) dataSet = kargs.pop('dataSet', self.data) if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)): brushes = args[0] if 'mask' in kargs and kargs['mask'] is not None: brushes = brushes[kargs['mask']] if len(brushes) != len(dataSet): raise Exception("Number of brushes does not match number of points (%d != %d)" % (len(brushes), len(dataSet))) dataSet['brush'] = list(map(_mkBrush, brushes)) else: self.opts['brush'] = _mkBrush(*args, **kargs) dataSet['sourceRect'] = 0 if update: self.updateSpots(dataSet) def setSymbol(self, symbol, update=True, dataSet=None, mask=None): """Set the symbol(s) used to draw each spot. If a list or array is provided, then the symbol for each spot will be set separately. Otherwise, the argument will be used as the default symbol for all spots which do not have a symbol explicitly set. **Supported symbols:** * 'o' circle (default) * 's' square * 't' triangle * 'd' diamond * '+' plus * 't1' triangle pointing upwards * 't2' triangle pointing right side * 't3' triangle pointing left side * 'p' pentagon * 'h' hexagon * 'star' * 'x' cross * 'arrow_up' * 'arrow_right' * 'arrow_down' * 'arrow_left' * 'crosshair' * any QPainterPath to specify custom symbol shapes. """ if dataSet is None: dataSet = self.data if isinstance(symbol, np.ndarray) or isinstance(symbol, list): symbols = symbol if mask is not None: symbols = symbols[mask] if len(symbols) != len(dataSet): raise Exception("Number of symbols does not match number of points (%d != %d)" % (len(symbols), len(dataSet))) dataSet['symbol'] = symbols else: self.opts['symbol'] = symbol self._spotPixmap = None dataSet['sourceRect'] = 0 if update: self.updateSpots(dataSet) def setSize(self, size, update=True, dataSet=None, mask=None): """Set the size(s) used to draw each spot. If a list or array is provided, then the size for each spot will be set separately. Otherwise, the argument will be used as the default size for all spots which do not have a size explicitly set.""" if dataSet is None: dataSet = self.data if isinstance(size, np.ndarray) or isinstance(size, list): sizes = size if mask is not None: sizes = sizes[mask] if len(sizes) != len(dataSet): raise Exception("Number of sizes does not match number of points (%d != %d)" % (len(sizes), len(dataSet))) dataSet['size'] = sizes else: self.opts['size'] = size self._spotPixmap = None dataSet['sourceRect'] = 0 if update: self.updateSpots(dataSet) def setPointsVisible(self, visible, update=True, dataSet=None, mask=None): """Set whether or not each spot is visible. If a list or array is provided, then the visibility for each spot will be set separately. Otherwise, the argument will be used for all spots.""" if dataSet is None: dataSet = self.data if isinstance(visible, np.ndarray) or isinstance(visible, list): visibilities = visible if mask is not None: visibilities = visibilities[mask] if len(visibilities) != len(dataSet): raise Exception("Number of visibilities does not match number of points (%d != %d)" % (len(visibilities), len(dataSet))) dataSet['visible'] = visibilities else: dataSet['visible'] = visible dataSet['sourceRect'] = 0 if update: self.updateSpots(dataSet) def setPointData(self, data, dataSet=None, mask=None): if dataSet is None: dataSet = self.data if isinstance(data, np.ndarray) or isinstance(data, list): if mask is not None: data = data[mask] if len(data) != len(dataSet): raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(dataSet))) ## Bug: If data is a numpy record array, then items from that array must be copied to dataSet one at a time. ## (otherwise they are converted to tuples and thus lose their field names. if isinstance(data, np.ndarray) and (data.dtype.fields is not None)and len(data.dtype.fields) > 1: for i, rec in enumerate(data): dataSet['data'][i] = rec else: dataSet['data'] = data def setPxMode(self, mode): if self.opts['pxMode'] == mode: return self.opts['pxMode'] = mode self.invalidate() def updateSpots(self, dataSet=None): profiler = debug.Profiler() if dataSet is None: dataSet = self.data invalidate = False if self.opts['pxMode'] and self.opts['useCache']: mask = dataSet['sourceRect']['w'] == 0 if np.any(mask): invalidate = True coords = self.fragmentAtlas[ list(zip(*self._style(['symbol', 'size', 'pen', 'brush'], data=dataSet, idx=mask))) ] dataSet['sourceRect'][mask] = coords self._maybeRebuildAtlas() else: invalidate = True self._updateMaxSpotSizes(data=dataSet) if invalidate: self.invalidate() def _maybeRebuildAtlas(self, threshold=4, minlen=1000): n = len(self.fragmentAtlas) if (n > minlen) and (n > threshold * len(self.data)): self.fragmentAtlas.rebuild( list(zip(*self._style(['symbol', 'size', 'pen', 'brush']))) ) self.data['sourceRect'] = 0 self.updateSpots() def _style(self, opts, data=None, idx=None, scale=None): if data is None: data = self.data if idx is None: idx = np.s_[:] for opt in opts: col = data[opt][idx] if col.base is not None: col = col.copy() if self.opts['hoverable']: val = self.opts['hover' + opt.title()] if val != _DEFAULT_STYLE[opt]: col[data['hovered'][idx]] = val col[np.equal(col, _DEFAULT_STYLE[opt])] = self.opts[opt] if opt == 'size' and scale is not None: col *= scale yield col def _updateMaxSpotSizes(self, **kwargs): if self.opts['pxMode'] and self.opts['useCache']: w, pw = 0, self.fragmentAtlas.maxWidth else: w, pw = max(itertools.chain([(self._maxSpotWidth, self._maxSpotPxWidth)], self._measureSpotSizes(**kwargs))) self._maxSpotWidth = w self._maxSpotPxWidth = pw self.bounds = [None, None] def _measureSpotSizes(self, **kwargs): """Generate pairs (width, pxWidth) for spots in data""" styles = zip(*self._style(['size', 'pen'], **kwargs)) if self.opts['pxMode']: for size, pen in styles: yield 0, size + pen.widthF() else: for size, pen in styles: if pen.isCosmetic(): yield size, pen.widthF() else: yield size + pen.widthF(), 0 def getSpotOpts(self, recs, scale=1.0): warnings.warn( "This is an internal method that is no longer being used. Will be " "removed in 0.13", DeprecationWarning, stacklevel=2 ) if recs.ndim == 0: rec = recs symbol = rec['symbol'] if symbol is None: symbol = self.opts['symbol'] size = rec['size'] if size < 0: size = self.opts['size'] pen = rec['pen'] if pen is None: pen = self.opts['pen'] brush = rec['brush'] if brush is None: brush = self.opts['brush'] return (symbol, size*scale, fn.mkPen(pen), fn.mkBrush(brush)) else: recs = recs.copy() recs['symbol'][np.equal(recs['symbol'], None)] = self.opts['symbol'] recs['size'][np.equal(recs['size'], -1)] = self.opts['size'] recs['size'] *= scale recs['pen'][np.equal(recs['pen'], None)] = fn.mkPen(self.opts['pen']) recs['brush'][np.equal(recs['brush'], None)] = fn.mkBrush(self.opts['brush']) return recs def measureSpotSizes(self, dataSet): warnings.warn( "This is an internal method that is no longer being used. " "Will be removed in 0.13.", DeprecationWarning, stacklevel=2 ) for size, pen in zip(*self._style(['size', 'pen'], data=dataSet)): ## keep track of the maximum spot size and pixel size width = 0 pxWidth = 0 if self.opts['pxMode']: pxWidth = size + pen.widthF() else: width = size if pen.isCosmetic(): pxWidth += pen.widthF() else: width += pen.widthF() self._maxSpotWidth = max(self._maxSpotWidth, width) self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth) self.bounds = [None, None] def clear(self): """Remove all spots from the scatter plot""" #self.clearItems() self._maxSpotWidth = 0 self._maxSpotPxWidth = 0 self.data = np.empty(0, dtype=self.data.dtype) self.bounds = [None, None] self.invalidate() def dataBounds(self, ax, frac=1.0, orthoRange=None): if frac >= 1.0 and orthoRange is None and self.bounds[ax] is not None: return self.bounds[ax] #self.prepareGeometryChange() if self.data is None or len(self.data) == 0: return (None, None) if ax == 0: d = self.data['x'] d2 = self.data['y'] elif ax == 1: d = self.data['y'] d2 = self.data['x'] else: raise ValueError("Invalid axis value") if orthoRange is not None: mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) d = d[mask] d2 = d2[mask] if d.size == 0: return (None, None) if frac >= 1.0: self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072) return self.bounds[ax] elif frac <= 0.0: raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) else: mask = np.isfinite(d) d = d[mask] return np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) def pixelPadding(self): return self._maxSpotPxWidth*0.7072 def boundingRect(self): (xmn, xmx) = self.dataBounds(ax=0) (ymn, ymx) = self.dataBounds(ax=1) if xmn is None or xmx is None: xmn = 0 xmx = 0 if ymn is None or ymx is None: ymn = 0 ymx = 0 px = py = 0.0 pxPad = self.pixelPadding() if pxPad > 0: # determine length of pixel in local x, y directions px, py = self.pixelVectors() try: px = 0 if px is None else px.length() except OverflowError: px = 0 try: py = 0 if py is None else py.length() except OverflowError: py = 0 # return bounds expanded by pixel size px *= pxPad py *= pxPad return QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) def viewTransformChanged(self): self.prepareGeometryChange() GraphicsObject.viewTransformChanged(self) self.bounds = [None, None] def setExportMode(self, *args, **kwds): GraphicsObject.setExportMode(self, *args, **kwds) self.invalidate() def mapPointsToDevice(self, pts): warnings.warn( "This is an internal method that is no longer being used. " "Will be removed in 0.13", DeprecationWarning, stacklevel=2 ) # Map point locations to device tr = self.deviceTransform() if tr is None: return None pts = fn.transformCoordinates(tr, pts) pts -= self.data['sourceRect']['w'] / 2 pts = np.clip(pts, -2**30, 2**30) ## prevent Qt segmentation fault. return pts def getViewMask(self, pts): warnings.warn( "This is an internal method that is no longer being used. " "Will be removed in 0.13", DeprecationWarning, stacklevel=2 ) # Return bool mask indicating all points that are within viewbox # pts is expressed in *device coordiantes* vb = self.getViewBox() if vb is None: return None viewBounds = vb.mapRectToDevice(vb.boundingRect()) w = self.data['sourceRect']['w'] / 2 mask = ((pts[0] + w > viewBounds.left()) & (pts[0] - w < viewBounds.right()) & (pts[1] + w > viewBounds.top()) & (pts[1] - w < viewBounds.bottom())) ## remove out of view points mask &= self.data['visible'] return mask @debug.warnOnException ## raising an exception here causes crash def paint(self, p, *args): profiler = debug.Profiler() cmode = self.opts.get('compositionMode', None) if cmode is not None: p.setCompositionMode(cmode) #p.setPen(fn.mkPen('r')) #p.drawRect(self.boundingRect()) if self._exportOpts is not False: aa = self._exportOpts.get('antialias', True) scale = self._exportOpts.get('resolutionScale', 1.0) ## exporting to image; pixel resolution may have changed else: aa = self.opts['antialias'] scale = 1.0 if self.opts['pxMode'] is True: # Cull points that are outside view viewMask = self._maskAt(self.getViewBox().viewRect()) # Map points using painter's world transform so they are drawn with pixel-valued sizes pts = np.vstack([self.data['x'], self.data['y']]) pts = fn.transformCoordinates(p.transform(), pts) pts = fn.clip_array(pts, -2 ** 30, 2 ** 30) # prevent Qt segmentation fault. p.resetTransform() if self.opts['useCache'] and self._exportOpts is False: # Draw symbols from pre-rendered atlas # x, y is the center of the target rect xy = pts[:, viewMask].T sr = self.data['sourceRect'][viewMask] frags = self._pixmapFragments.array(sr.size) frags[:, 0:2] = xy frags[:, 2:6] = np.frombuffer(sr, dtype=int).reshape((-1, 4)) # sx, sy, sw, sh frags[:, 6:10] = [1.0, 1.0, 0.0, 1.0] # scaleX, scaleY, rotation, opacity profiler('prep') self._pixmapFragments.draw(p, len(frags), self.fragmentAtlas.pixmap) profiler('draw') else: # render each symbol individually p.setRenderHint(p.RenderHint.Antialiasing, aa) for pt, style in zip( pts[:, viewMask].T, zip(*(self._style(['symbol', 'size', 'pen', 'brush'], idx=viewMask, scale=scale))) ): p.resetTransform() p.translate(*pt) drawSymbol(p, *style) else: if self.picture is None: self.picture = QtGui.QPicture() p2 = QtGui.QPainter(self.picture) for x, y, style in zip( self.data['x'], self.data['y'], zip(*self._style(['symbol', 'size', 'pen', 'brush'], scale=scale)) ): p2.resetTransform() p2.translate(x, y) drawSymbol(p2, *style) p2.end() p.setRenderHint(p.RenderHint.Antialiasing, aa) self.picture.play(p) def points(self): m = np.equal(self.data['item'], None) for i in np.argwhere(m)[:, 0]: rec = self.data[i] if rec['item'] is None: rec['item'] = SpotItem(rec, self, i) return self.data['item'] def pointsAt(self, pos): return self.points()[self._maskAt(pos)][::-1] def _maskAt(self, obj): """ Return a boolean mask indicating all points that overlap obj, a QPointF or QRectF. """ if isinstance(obj, QtCore.QPointF): l = r = obj.x() t = b = obj.y() elif isinstance(obj, QtCore.QRectF): l = obj.left() r = obj.right() t = obj.top() b = obj.bottom() else: raise TypeError if self.opts['pxMode'] and self.opts['useCache']: w = self.data['sourceRect']['w'] h = self.data['sourceRect']['h'] else: s, = self._style(['size']) w = h = s w = w / 2 h = h / 2 if self.opts['pxMode']: # determine length of pixel in local x, y directions px, py = self.pixelVectors() try: px = 0 if px is None else px.length() except OverflowError: px = 0 try: py = 0 if py is None else py.length() except OverflowError: py = 0 w *= px h *= py return (self.data['visible'] & (self.data['x'] + w > l) & (self.data['x'] - w < r) & (self.data['y'] + h > t) & (self.data['y'] - h < b)) def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.LeftButton: pts = self.pointsAt(ev.pos()) if len(pts) > 0: self.ptsClicked = pts ev.accept() self.sigClicked.emit(self, self.ptsClicked, ev) else: #print "no spots" ev.ignore() else: ev.ignore() def hoverEvent(self, ev): if self.opts['hoverable']: old = self.data['hovered'] if ev.exit: new = np.zeros_like(self.data['hovered']) else: new = self._maskAt(ev.pos()) if self._hasHoverStyle(): self.data['sourceRect'][old ^ new] = 0 self.data['hovered'] = new self.updateSpots() points = self.points()[new][::-1] # Show information about hovered points in a tool tip vb = self.getViewBox() if vb is not None and self.opts['tip'] is not None: cutoff = 3 tip = [self.opts['tip'](x=pt.pos().x(), y=pt.pos().y(), data=pt.data()) for pt in points[:cutoff]] if len(points) > cutoff: tip.append('({} others...)'.format(len(points) - cutoff)) vb.setToolTip('\n\n'.join(tip)) self.sigHovered.emit(self, points, ev) def _hasHoverStyle(self): return any(self.opts['hover' + opt.title()] != _DEFAULT_STYLE[opt] for opt in ['symbol', 'size', 'pen', 'brush']) class SpotItem(object): """ Class referring to individual spots in a scatter plot. These can be retrieved by calling ScatterPlotItem.points() or by connecting to the ScatterPlotItem's click signals. """ def __init__(self, data, plot, index): self._data = data self._index = index # SpotItems are kept in plot.data["items"] numpy object array which # does not support cyclic garbage collection (numpy issue 6581). # Keeping a strong ref to plot here would leak the cycle self.__plot_ref = weakref.ref(plot) @property def _plot(self): return self.__plot_ref() def data(self): """Return the user data associated with this spot.""" return self._data['data'] def index(self): """Return the index of this point as given in the scatter plot data.""" return self._index def size(self): """Return the size of this spot. If the spot has no explicit size set, then return the ScatterPlotItem's default size instead.""" if self._data['size'] == -1: return self._plot.opts['size'] else: return self._data['size'] def pos(self): return Point(self._data['x'], self._data['y']) def viewPos(self): return self._plot.mapToView(self.pos()) def setSize(self, size): """Set the size of this spot. If the size is set to -1, then the ScatterPlotItem's default size will be used instead.""" self._data['size'] = size self.updateItem() def symbol(self): """Return the symbol of this spot. If the spot has no explicit symbol set, then return the ScatterPlotItem's default symbol instead. """ symbol = self._data['symbol'] if symbol is None: symbol = self._plot.opts['symbol'] try: n = int(symbol) symbol = list(Symbols.keys())[n % len(Symbols)] except: pass return symbol def setSymbol(self, symbol): """Set the symbol for this spot. If the symbol is set to '', then the ScatterPlotItem's default symbol will be used instead.""" self._data['symbol'] = symbol self.updateItem() def pen(self): pen = self._data['pen'] if pen is None: pen = self._plot.opts['pen'] return fn.mkPen(pen) def setPen(self, *args, **kargs): """Set the outline pen for this spot""" self._data['pen'] = _mkPen(*args, **kargs) self.updateItem() def resetPen(self): """Remove the pen set for this spot; the scatter plot's default pen will be used instead.""" self._data['pen'] = None ## Note this is NOT the same as calling setPen(None) self.updateItem() def brush(self): brush = self._data['brush'] if brush is None: brush = self._plot.opts['brush'] return fn.mkBrush(brush) def setBrush(self, *args, **kargs): """Set the fill brush for this spot""" self._data['brush'] = _mkBrush(*args, **kargs) self.updateItem() def resetBrush(self): """Remove the brush set for this spot; the scatter plot's default brush will be used instead.""" self._data['brush'] = None ## Note this is NOT the same as calling setBrush(None) self.updateItem() def isVisible(self): return self._data['visible'] def setVisible(self, visible): """Set whether or not this spot is visible.""" self._data['visible'] = visible self.updateItem() def setData(self, data): """Set the user-data associated with this spot""" self._data['data'] = data def updateItem(self): self._data['sourceRect'] = (0, 0, 0, 0) # numpy <=1.13.1 won't let us set this with a single zero self._plot.updateSpots(self._data.reshape(1)) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/TargetItem.py������������������������������������0000664�0000000�0000000�00000041030�14210455074�0025072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import string import warnings from math import atan2 from .. import functions as fn from ..Point import Point from ..Qt import QtCore, QtGui from .GraphicsObject import GraphicsObject from .ScatterPlotItem import Symbols, makeCrosshair from .TextItem import TextItem from .UIGraphicsItem import UIGraphicsItem from .ViewBox import ViewBox __all__ = ['TargetItem', 'TargetLabel'] class TargetItem(UIGraphicsItem): """Draws a draggable target symbol (circle plus crosshair). The size of TargetItem will remain fixed on screen even as the view is zoomed. Includes an optional text label. """ sigPositionChanged = QtCore.Signal(object) sigPositionChangeFinished = QtCore.Signal(object) def __init__( self, pos=None, size=10, radii=None, symbol="crosshair", pen=None, hoverPen=None, brush=None, hoverBrush=None, movable=True, label=None, labelOpts=None, ): r""" Parameters ---------- pos : list, tuple, QPointF, QPoint, Optional Initial position of the symbol. Default is (0, 0) size : int Size of the symbol in pixels. Default is 10. radii : tuple of int Deprecated. Gives size of crosshair in screen pixels. pen : QPen, tuple, list or str Pen to use when drawing line. Can be any arguments that are valid for :func:`~pyqtgraph.mkPen`. Default pen is transparent yellow. brush : QBrush, tuple, list, or str Defines the brush that fill the symbol. Can be any arguments that is valid for :func:`~pyqtgraph.mkBrush`. Default is transparent blue. movable : bool If True, the symbol can be dragged to a new position by the user. hoverPen : QPen, tuple, list, or str Pen to use when drawing symbol when hovering over it. Can be any arguments that are valid for :func:`~pyqtgraph.mkPen`. Default pen is red. hoverBrush : QBrush, tuple, list or str Brush to use to fill the symbol when hovering over it. Can be any arguments that is valid for :func:`~pyqtgraph.mkBrush`. Default is transparent blue. symbol : QPainterPath or str QPainterPath to use for drawing the target, should be centered at ``(0, 0)`` with ``max(width, height) == 1.0``. Alternatively a string which can be any symbol accepted by :func:`~pyqtgraph.ScatterPlotItem.setSymbol` label : bool, str or callable, optional Text to be displayed in a label attached to the symbol, or None to show no label (default is None). May optionally include formatting strings to display the symbol value, or a callable that accepts x and y as inputs. If True, the label is ``x = {: >.3n}\ny = {: >.3n}`` False or None will result in no text being displayed labelOpts : dict A dict of keyword arguments to use when constructing the text label. See :class:`TargetLabel` and :class:`~pyqtgraph.TextItem` """ super().__init__(self) self.movable = movable self.moving = False self._label = None self.mouseHovering = False if radii is not None: warnings.warn( "'radii' is now deprecated, and will be removed in 0.13.0. Use 'size' " "parameter instead", DeprecationWarning, stacklevel=2, ) symbol = makeCrosshair(*radii) size = 1 if pen is None: pen = (255, 255, 0) self.setPen(pen) if hoverPen is None: hoverPen = (255, 0, 255) self.setHoverPen(hoverPen) if brush is None: brush = (0, 0, 255, 50) self.setBrush(brush) if hoverBrush is None: hoverBrush = (0, 255, 255, 100) self.setHoverBrush(hoverBrush) self.currentPen = self.pen self.currentBrush = self.brush self._shape = None self._pos = Point(0, 0) if pos is None: pos = Point(0, 0) self.setPos(pos) if isinstance(symbol, str): try: self._path = Symbols[symbol] except KeyError: raise KeyError("symbol name found in available Symbols") elif isinstance(symbol, QtGui.QPainterPath): self._path = symbol else: raise TypeError("Unknown type provided as symbol") self.scale = size self.setPath(self._path) self.setLabel(label, labelOpts) @property def sigDragged(self): warnings.warn( "'sigDragged' has been deprecated and will be removed in 0.13.0. Use " "`sigPositionChangeFinished` instead", DeprecationWarning, stacklevel=2, ) return self.sigPositionChangeFinished def setPos(self, *args): """Method to set the position to ``(x, y)`` within the plot view Parameters ---------- args : tuple, list, QPointF, QPoint, pg.Point, or two floats Two float values or a container that specifies ``(x, y)`` position where the TargetItem should be placed Raises ------ TypeError If args cannot be used to instantiate a pg.Point """ try: newPos = Point(*args) except TypeError: raise except Exception: raise TypeError(f"Could not make Point from arguments: {args!r}") if self._pos != newPos: self._pos = newPos super().setPos(self._pos) self.sigPositionChanged.emit(self) def setBrush(self, *args, **kwargs): """Set the brush that fills the symbol. Allowable arguments are any that are valid for :func:`~pyqtgraph.mkBrush`. """ self.brush = fn.mkBrush(*args, **kwargs) if not self.mouseHovering: self.currentBrush = self.brush self.update() def setHoverBrush(self, *args, **kwargs): """Set the brush that fills the symbol when hovering over it. Allowable arguments are any that are valid for :func:`~pyqtgraph.mkBrush`. """ self.hoverBrush = fn.mkBrush(*args, **kwargs) if self.mouseHovering: self.currentBrush = self.hoverBrush self.update() def setPen(self, *args, **kwargs): """Set the pen for drawing the symbol. Allowable arguments are any that are valid for :func:`~pyqtgraph.mkPen`.""" self.pen = fn.mkPen(*args, **kwargs) if not self.mouseHovering: self.currentPen = self.pen self.update() def setHoverPen(self, *args, **kwargs): """Set the pen for drawing the symbol when hovering over it. Allowable arguments are any that are valid for :func:`~pyqtgraph.mkPen`.""" self.hoverPen = fn.mkPen(*args, **kwargs) if self.mouseHovering: self.currentPen = self.hoverPen self.update() def boundingRect(self): return self.shape().boundingRect() def paint(self, p, *_): p.setPen(self.currentPen) p.setBrush(self.currentBrush) p.drawPath(self.shape()) def setPath(self, path): if path != self._path: self._path = path self._shape = None return None def shape(self): if self._shape is None: s = self.generateShape() if s is None: return self._path self._shape = s # beware--this can cause the view to adjust # which would immediately invalidate the shape. self.prepareGeometryChange() return self._shape def generateShape(self): dt = self.deviceTransform() if dt is None: self._shape = self._path return None v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) dti = fn.invertQTransform(dt) devPos = dt.map(QtCore.QPointF(0, 0)) tr = QtGui.QTransform() tr.translate(devPos.x(), devPos.y()) va = atan2(v.y(), v.x()) tr.rotateRadians(va) tr.scale(self.scale, self.scale) return dti.map(tr.map(self._path)) def mouseDragEvent(self, ev): if not self.movable or ev.button() != QtCore.Qt.MouseButton.LeftButton: return ev.accept() if ev.isStart(): self.symbolOffset = self.pos() - self.mapToView(ev.buttonDownPos()) self.moving = True if not self.moving: return self.setPos(self.symbolOffset + self.mapToView(ev.pos())) if ev.isFinish(): self.moving = False self.sigPositionChangeFinished.emit(self) def mouseClickEvent(self, ev): if self.moving and ev.button() == QtCore.Qt.MouseButton.RightButton: ev.accept() self.moving = False self.sigPositionChanged.emit(self) self.sigPositionChangeFinished.emit(self) def setMouseHover(self, hover): # Inform the item that the mouse is(not) hovering over it if self.mouseHovering is hover: return self.mouseHovering = hover if hover: self.currentBrush = self.hoverBrush self.currentPen = self.hoverPen else: self.currentBrush = self.brush self.currentPen = self.pen self.update() def hoverEvent(self, ev): if self.movable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton): self.setMouseHover(True) else: self.setMouseHover(False) def viewTransformChanged(self): GraphicsObject.viewTransformChanged(self) self._shape = None # invalidate shape, recompute later if requested. self.update() def pos(self): """Provides the current position of the TargetItem Returns ------- Point pg.Point of the current position of the TargetItem """ return self._pos def label(self): """Provides the TargetLabel if it exists Returns ------- TargetLabel or None If a TargetLabel exists for this TargetItem, return that, otherwise return None """ return self._label def setLabel(self, text=None, labelOpts=None): """Method to call to enable or disable the TargetLabel for displaying text Parameters ---------- text : Callable or str, optional Details how to format the text, by default None If None, do not show any text next to the TargetItem If Callable, then the label will display the result of ``text(x, y)`` If a fromatted string, then the output of ``text.format(x, y)`` will be displayed If a non-formatted string, then the text label will display ``text``, by default None labelOpts : dictionary, optional These arguments are passed on to :class:`~pyqtgraph.TextItem` """ if not text: if self._label is not None and self._label.scene() is not None: # remove the label if it's already added self._label.scene().removeItem(self._label) self._label = None else: # provide default text if text is True if text is True: # convert to default value or empty string text = "x = {: .3n}\ny = {: .3n}" labelOpts = {} if labelOpts is None else labelOpts if self._label is not None: self._label.scene().removeItem(self._label) self._label = TargetLabel(self, text=text, **labelOpts) def setLabelAngle(self, angle): warnings.warn( "TargetItem.setLabelAngle is deprecated and will be removed in 0.13.0." "Use TargetItem.label().setAngle() instead", DeprecationWarning, stacklevel=2, ) if self.label() is not None and angle != self.label().angle: self.label().setAngle(angle) return None class TargetLabel(TextItem): """A TextItem that attaches itself to a TargetItem. This class extends TextItem with the following features : * Automatically positions adjacent to the symbol at a fixed position. * Automatically reformats text when the symbol location has changed. Parameters ---------- target : TargetItem The TargetItem to which this label will be attached to. text : str or callable, Optional Governs the text displayed, can be a fixed string or a format string that accepts the x, and y position of the target item; or be a callable method that accepts a tuple (x, y) and returns a string to be displayed. If None, an empty string is used. Default is None offset : tuple or list or QPointF or QPoint Position to set the anchor of the TargetLabel away from the center of the target in pixels, by default it is (20, 0). anchor : tuple, list, QPointF or QPoint Position to rotate the TargetLabel about, and position to set the offset value to see :class:`~pyqtgraph.TextItem` for more information. kwargs : dict of arguments that are passed on to :class:`~pyqtgraph.TextItem` constructor, excluding text parameter """ def __init__( self, target, text="", offset=(20, 0), anchor=(0, 0.5), **kwargs, ): if isinstance(offset, Point): self.offset = offset elif isinstance(offset, (tuple, list)): self.offset = Point(*offset) elif isinstance(offset, (QtCore.QPoint, QtCore.QPointF)): self.offset = Point(offset.x(), offset.y()) else: raise TypeError("Offset parameter is the wrong data type") super().__init__(anchor=anchor, **kwargs) self.setParentItem(target) self.target = target self.setFormat(text) self.target.sigPositionChanged.connect(self.valueChanged) self.valueChanged() def format(self): return self._format def setFormat(self, text): """Method to set how the TargetLabel should display the text. This method should be called from TargetItem.setLabel directly. Parameters ---------- text : Callable or str Details how to format the text. If Callable, then the label will display the result of ``text(x, y)`` If a fromatted string, then the output of ``text.format(x, y)`` will be displayed If a non-formatted string, then the text label will display ``text`` """ if not callable(text): parsed = list(string.Formatter().parse(text)) if parsed and parsed[0][1] is not None: self.setProperty("formattableText", True) else: self.setText(text) self.setProperty("formattableText", False) else: self.setProperty("formattableText", False) self._format = text self.valueChanged() def valueChanged(self): x, y = self.target.pos() if self.property("formattableText"): self.setText(self._format.format(float(x), float(y))) elif callable(self._format): self.setText(self._format(x, y)) def viewTransformChanged(self): viewbox = self.getViewBox() if isinstance(viewbox, ViewBox): viewPixelSize = viewbox.viewPixelSize() scaledOffset = QtCore.QPointF( self.offset.x() * viewPixelSize[0], self.offset.y() * viewPixelSize[1] ) self.setPos(scaledOffset) return super().viewTransformChanged() def mouseClickEvent(self, ev): return self.parentItem().mouseClickEvent(ev) def mouseDragEvent(self, ev): targetItem = self.parentItem() if not targetItem.movable or ev.button() != QtCore.Qt.MouseButton.LeftButton: return ev.accept() if ev.isStart(): targetItem.symbolOffset = targetItem.pos() - self.mapToView( ev.buttonDownPos() ) targetItem.moving = True if not targetItem.moving: return targetItem.setPos(targetItem.symbolOffset + self.mapToView(ev.pos())) if ev.isFinish(): targetItem.moving = False targetItem.sigPositionChangeFinished.emit(self) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/TextItem.py��������������������������������������0000664�0000000�0000000�00000020175�14210455074�0024577�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from math import atan2, degrees from .. import functions as fn from ..Point import Point from ..Qt import QtCore, QtGui, QtWidgets from .GraphicsObject import GraphicsObject __all__ = ['TextItem'] class TextItem(GraphicsObject): """ GraphicsItem displaying unscaled text (the text will always appear normal even inside a scaled ViewBox). """ def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0), border=None, fill=None, angle=0, rotateAxis=None): """ ============== ================================================================================= **Arguments:** *text* The text to display *color* The color of the text (any format accepted by pg.mkColor) *html* If specified, this overrides both *text* and *color* *anchor* A QPointF or (x,y) sequence indicating what region of the text box will be anchored to the item's position. A value of (0,0) sets the upper-left corner of the text box to be at the position specified by setPos(), while a value of (1,1) sets the lower-right corner. *border* A pen to use when drawing the border *fill* A brush to use when filling within the border *angle* Angle in degrees to rotate text. Default is 0; text will be displayed upright. *rotateAxis* If None, then a text angle of 0 always points along the +x axis of the scene. If a QPointF or (x,y) sequence is given, then it represents a vector direction in the parent's coordinate system that the 0-degree line will be aligned to. This Allows text to follow both the position and orientation of its parent while still discarding any scale and shear factors. ============== ================================================================================= The effects of the `rotateAxis` and `angle` arguments are added independently. So for example: * rotateAxis=None, angle=0 -> normal horizontal text * rotateAxis=None, angle=90 -> normal vertical text * rotateAxis=(1, 0), angle=0 -> text aligned with x axis of its parent * rotateAxis=(0, 1), angle=0 -> text aligned with y axis of its parent * rotateAxis=(1, 0), angle=90 -> text orthogonal to x axis of its parent """ self.anchor = Point(anchor) self.rotateAxis = None if rotateAxis is None else Point(rotateAxis) #self.angle = 0 GraphicsObject.__init__(self) self.textItem = QtWidgets.QGraphicsTextItem() self.textItem.setParentItem(self) self._lastTransform = None self._lastScene = None self._bounds = QtCore.QRectF() if html is None: self.setColor(color) self.setText(text) else: self.setHtml(html) self.fill = fn.mkBrush(fill) self.border = fn.mkPen(border) self.setAngle(angle) def setText(self, text, color=None): """ Set the text of this item. This method sets the plain text of the item; see also setHtml(). """ if color is not None: self.setColor(color) self.setPlainText(text) def setPlainText(self, text): """ Set the plain text to be rendered by this item. See QtWidgets.QGraphicsTextItem.setPlainText(). """ if text != self.toPlainText(): self.textItem.setPlainText(text) self.updateTextPos() def toPlainText(self): return self.textItem.toPlainText() def setHtml(self, html): """ Set the HTML code to be rendered by this item. See QtWidgets.QGraphicsTextItem.setHtml(). """ if self.toHtml() != html: self.textItem.setHtml(html) self.updateTextPos() def toHtml(self): return self.textItem.toHtml() def setTextWidth(self, *args): """ Set the width of the text. If the text requires more space than the width limit, then it will be wrapped into multiple lines. See QtWidgets.QGraphicsTextItem.setTextWidth(). """ self.textItem.setTextWidth(*args) self.updateTextPos() def setFont(self, *args): """ Set the font for this text. See QtWidgets.QGraphicsTextItem.setFont(). """ self.textItem.setFont(*args) self.updateTextPos() def setAngle(self, angle): """ Set the angle of the text in degrees. This sets the rotation angle of the text as a whole, measured counter-clockwise from the x axis of the parent. Note that this rotation angle does not depend on horizontal/vertical scaling of the parent. """ self.angle = angle self.updateTransform(force=True) def setAnchor(self, anchor): self.anchor = Point(anchor) self.updateTextPos() def setColor(self, color): """ Set the color for this text. See QtWidgets.QGraphicsItem.setDefaultTextColor(). """ self.color = fn.mkColor(color) self.textItem.setDefaultTextColor(self.color) def updateTextPos(self): # update text position to obey anchor r = self.textItem.boundingRect() tl = self.textItem.mapToParent(r.topLeft()) br = self.textItem.mapToParent(r.bottomRight()) offset = (br - tl) * self.anchor self.textItem.setPos(-offset) def boundingRect(self): return self.textItem.mapRectToParent(self.textItem.boundingRect()) def viewTransformChanged(self): # called whenever view transform has changed. # Do this here to avoid double-updates when view changes. self.updateTransform() def paint(self, p, *args): # this is not ideal because it requires the transform to be updated at every draw. # ideally, we would have a sceneTransformChanged event to react to.. s = self.scene() ls = self._lastScene if s is not ls: if ls is not None: ls.sigPrepareForPaint.disconnect(self.updateTransform) self._lastScene = s if s is not None: s.sigPrepareForPaint.connect(self.updateTransform) self.updateTransform() p.setTransform(self.sceneTransform()) if self.border.style() != QtCore.Qt.PenStyle.NoPen or self.fill.style() != QtCore.Qt.BrushStyle.NoBrush: p.setPen(self.border) p.setBrush(self.fill) p.setRenderHint(p.RenderHint.Antialiasing, True) p.drawPolygon(self.textItem.mapToParent(self.textItem.boundingRect())) def setVisible(self, v): GraphicsObject.setVisible(self, v) if v: self.updateTransform() def updateTransform(self, force=False): if not self.isVisible(): return # update transform such that this item has the correct orientation # and scaling relative to the scene, but inherits its position from its # parent. # This is similar to setting ItemIgnoresTransformations = True, but # does not break mouse interaction and collision detection. p = self.parentItem() if p is None: pt = QtGui.QTransform() else: pt = p.sceneTransform() if not force and pt == self._lastTransform: return t = pt.inverted()[0] # reset translation t.setMatrix(t.m11(), t.m12(), t.m13(), t.m21(), t.m22(), t.m23(), 0, 0, t.m33()) # apply rotation angle = -self.angle if self.rotateAxis is not None: d = pt.map(self.rotateAxis) - pt.map(Point(0, 0)) a = degrees(atan2(d.y(), d.x())) angle += a t.rotate(angle) self.setTransform(t) self._lastTransform = pt self.updateTextPos() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/UIGraphicsItem.py��������������������������������0000664�0000000�0000000�00000011214�14210455074�0025643�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ..Qt import QT_LIB, QtCore, QtGui, QtWidgets from .GraphicsObject import GraphicsObject if QT_LIB.startswith('PyQt'): from ..Qt import sip __all__ = ['UIGraphicsItem'] class UIGraphicsItem(GraphicsObject): """ Base class for graphics items with boundaries relative to a GraphicsView or ViewBox. The purpose of this class is to allow the creation of GraphicsItems which live inside a scalable view, but whose boundaries will always stay fixed relative to the view's boundaries. For example: GridItem, InfiniteLine The view can be specified on initialization or it can be automatically detected when the item is painted. NOTE: Only the item's boundingRect is affected; the item is not transformed in any way. Use viewRangeChanged to respond to changes in the view. """ #sigViewChanged = QtCore.Signal(object) ## emitted whenever the viewport coords have changed def __init__(self, bounds=None, parent=None): """ ============== ============================================================================= **Arguments:** bounds QRectF with coordinates relative to view box. The default is QRectF(0,0,1,1), which means the item will have the same bounds as the view. ============== ============================================================================= """ GraphicsObject.__init__(self, parent) self.setFlag(self.GraphicsItemFlag.ItemSendsScenePositionChanges) if bounds is None: self._bounds = QtCore.QRectF(0, 0, 1, 1) else: self._bounds = bounds self._boundingRect = None self._updateView() def paint(self, *args): ## check for a new view object every time we paint. #self.updateView() pass def itemChange(self, change, value): ret = GraphicsObject.itemChange(self, change, value) ## workaround for pyqt bug: ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html if QT_LIB == 'PyQt5' and change == self.GraphicsItemChange.ItemParentChange and isinstance(ret, QtWidgets.QGraphicsItem): ret = sip.cast(ret, QtWidgets.QGraphicsItem) if change == self.GraphicsItemChange.ItemScenePositionHasChanged: self.setNewBounds() return ret #def updateView(self): ### called to see whether this item has a new view to connect to ### check for this item's current viewbox or view widget #view = self.getViewBox() #if view is None: ##print " no view" #return #if self._connectedView is not None and view is self._connectedView(): ##print " already have view", view #return ### disconnect from previous view #if self._connectedView is not None: #cv = self._connectedView() #if cv is not None: ##print "disconnect:", self #cv.sigRangeChanged.disconnect(self.viewRangeChanged) ### connect to new view ##print "connect:", self #view.sigRangeChanged.connect(self.viewRangeChanged) #self._connectedView = weakref.ref(view) #self.setNewBounds() def boundingRect(self): if self._boundingRect is None: br = self.viewRect() if br is None: return QtCore.QRectF() else: self._boundingRect = br return QtCore.QRectF(self._boundingRect) def dataBounds(self, axis, frac=1.0, orthoRange=None): """Called by ViewBox for determining the auto-range bounds. By default, UIGraphicsItems are excluded from autoRange.""" return None def viewRangeChanged(self): """Called when the view widget/viewbox is resized/rescaled""" self.setNewBounds() self.update() def setNewBounds(self): """Update the item's bounding rect to match the viewport""" self._boundingRect = None ## invalidate bounding rect, regenerate later if needed. self.prepareGeometryChange() def setPos(self, *args): GraphicsObject.setPos(self, *args) self.setNewBounds() def mouseShape(self): """Return the shape of this item after expanding by 2 pixels""" shape = self.shape() ds = self.mapToDevice(shape) stroker = QtGui.QPainterPathStroker() stroker.setWidh(2) ds2 = stroker.createStroke(ds).united(ds) return self.mapFromDevice(ds2) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/VTickGroup.py������������������������������������0000664�0000000�0000000�00000006700�14210455074�0025067�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������if __name__ == '__main__': import os import sys path = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, os.path.join(path, '..', '..')) from .. import functions as fn from ..Qt import QtGui, QtWidgets from .UIGraphicsItem import UIGraphicsItem __all__ = ['VTickGroup'] class VTickGroup(UIGraphicsItem): """ **Bases:** :class:`UIGraphicsItem ` Draws a set of tick marks which always occupy the same vertical range of the view, but have x coordinates relative to the data within the view. """ def __init__(self, xvals=None, yrange=None, pen=None): """ ============== =================================================================== **Arguments:** xvals A list of x values (in data coordinates) at which to draw ticks. yrange A list of [low, high] limits for the tick. 0 is the bottom of the view, 1 is the top. [0.8, 1] would draw ticks in the top fifth of the view. pen The pen to use for drawing ticks. Default is grey. Can be specified as any argument valid for :func:`mkPen` ============== =================================================================== """ if yrange is None: yrange = [0, 1] if xvals is None: xvals = [] UIGraphicsItem.__init__(self) if pen is None: pen = (200, 200, 200) self.path = QtWidgets.QGraphicsPathItem() self.ticks = [] self.xvals = [] self.yrange = [0,1] self.setPen(pen) self.setYRange(yrange) self.setXVals(xvals) def setPen(self, *args, **kwargs): """Set the pen to use for drawing ticks. Can be specified as any arguments valid for :func:`mkPen`""" self.pen = fn.mkPen(*args, **kwargs) def setXVals(self, vals): """Set the x values for the ticks. ============== ===================================================================== **Arguments:** vals A list of x values (in data/plot coordinates) at which to draw ticks. ============== ===================================================================== """ self.xvals = vals self.rebuildTicks() #self.valid = False def setYRange(self, vals): """Set the y range [low, high] that the ticks are drawn on. 0 is the bottom of the view, 1 is the top.""" self.yrange = vals self.rebuildTicks() def dataBounds(self, *args, **kargs): return None ## item should never affect view autoscaling def yRange(self): return self.yrange def rebuildTicks(self): self.path = QtGui.QPainterPath() yrange = self.yRange() for x in self.xvals: self.path.moveTo(x, 0.) self.path.lineTo(x, 1.) def paint(self, p, *args): UIGraphicsItem.paint(self, p, *args) br = self.boundingRect() h = br.height() br.setY(br.y() + self.yrange[0] * h) br.setHeight((self.yrange[1] - self.yrange[0]) * h) p.translate(0, br.y()) p.scale(1.0, br.height()) p.setPen(self.pen) p.drawPath(self.path) ����������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ViewBox/�����������������������������������������0000775�0000000�0000000�00000000000�14210455074�0024040�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ViewBox/ViewBox.py�������������������������������0000664�0000000�0000000�00000215643�14210455074�0026010�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import math import sys import weakref from copy import deepcopy import numpy as np from ... import debug as debug from ... import functions as fn from ... import getConfigOption from ...Point import Point from ...Qt import QtCore, QtGui, QtWidgets, isQObjectAlive from ..GraphicsWidget import GraphicsWidget from ..ItemGroup import ItemGroup __all__ = ['ViewBox'] class WeakList(object): def __init__(self): self._items = [] def append(self, obj): #Add backwards to iterate backwards (to make iterating more efficient on removal). self._items.insert(0, weakref.ref(obj)) def __iter__(self): i = len(self._items)-1 while i >= 0: ref = self._items[i] d = ref() if d is None: del self._items[i] else: yield d i -= 1 class ChildGroup(ItemGroup): def __init__(self, parent): ItemGroup.__init__(self, parent) # Used as callback to inform ViewBox when items are added/removed from # the group. # Note 1: We would prefer to override itemChange directly on the # ViewBox, but this causes crashes on PySide. # Note 2: We might also like to use a signal rather than this callback # mechanism, but this causes a different PySide crash. self.itemsChangedListeners = WeakList() # exempt from telling view when transform changes self._GraphicsObject__inform_view_on_change = False def itemChange(self, change, value): ret = ItemGroup.itemChange(self, change, value) if change in [ self.GraphicsItemChange.ItemChildAddedChange, self.GraphicsItemChange.ItemChildRemovedChange, ]: try: itemsChangedListeners = self.itemsChangedListeners except AttributeError: # It's possible that the attribute was already collected when the itemChange happened # (if it was triggered during the gc of the object). pass else: for listener in itemsChangedListeners: listener.itemsChanged() return ret class ViewBox(GraphicsWidget): """ **Bases:** :class:`GraphicsWidget ` Box that allows internal scaling/panning of children by mouse drag. This class is usually created automatically as part of a :class:`PlotItem ` or :class:`Canvas ` or with :func:`GraphicsLayout.addViewBox() `. Features: * Scaling contents by mouse or auto-scale when contents change * View linking--multiple views display the same data ranges * Configurable by context menu * Item coordinate mapping methods """ sigYRangeChanged = QtCore.Signal(object, object) sigXRangeChanged = QtCore.Signal(object, object) sigRangeChangedManually = QtCore.Signal(object) sigRangeChanged = QtCore.Signal(object, object, object) sigStateChanged = QtCore.Signal(object) sigTransformChanged = QtCore.Signal(object) sigResized = QtCore.Signal(object) ## mouse modes PanMode = 3 RectMode = 1 ## axes XAxis = 0 YAxis = 1 XYAxes = 2 ## for linking views together NamedViews = weakref.WeakValueDictionary() # name: ViewBox AllViews = weakref.WeakKeyDictionary() # ViewBox: None def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu=True, name=None, invertX=False, defaultPadding=0.02): """ ================= ============================================================= **Arguments:** *parent* (QGraphicsWidget) Optional parent widget *border* (QPen) Do draw a border around the view, give any single argument accepted by :func:`mkPen ` *lockAspect* (False or float) The aspect ratio to lock the view coorinates to. (or False to allow the ratio to change) *enableMouse* (bool) Whether mouse can be used to scale/pan the view *invertY* (bool) See :func:`invertY ` *invertX* (bool) See :func:`invertX ` *enableMenu* (bool) Whether to display a context menu when right-clicking on the ViewBox background. *name* (str) Used to register this ViewBox so that it appears in the "Link axis" dropdown inside other ViewBox context menus. This allows the user to manually link the axes of any other view to this one. *defaultPadding* (float) fraction of the data range that will be added as padding by default ================= ============================================================= """ GraphicsWidget.__init__(self, parent) self.name = None self.linksBlocked = False self.addedItems = [] self._matrixNeedsUpdate = True ## indicates that range has changed, but matrix update was deferred self._autoRangeNeedsUpdate = True ## indicates auto-range needs to be recomputed. self._lastScene = None ## stores reference to the last known scene this view was a part of. self.state = { ## separating targetRange and viewRange allows the view to be resized ## while keeping all previously viewed contents visible 'targetRange': [[0,1], [0,1]], ## child coord. range visible [[xmin, xmax], [ymin, ymax]] 'viewRange': [[0,1], [0,1]], ## actual range viewed 'yInverted': invertY, 'xInverted': invertX, 'aspectLocked': False, ## False if aspect is unlocked, otherwise float specifies the locked ratio. 'autoRange': [True, True], ## False if auto range is disabled, ## otherwise float gives the fraction of data that is visible 'autoPan': [False, False], ## whether to only pan (do not change scaling) when auto-range is enabled 'autoVisibleOnly': [False, False], ## whether to auto-range only to the visible portion of a plot 'linkedViews': [None, None], ## may be None, "viewName", or weakref.ref(view) ## a name string indicates that the view *should* link to another, but no view with that name exists yet. 'defaultPadding': defaultPadding, 'mouseEnabled': [enableMouse, enableMouse], 'mouseMode': ViewBox.PanMode if getConfigOption('leftButtonPan') else ViewBox.RectMode, 'enableMenu': enableMenu, 'wheelScaleFactor': -1.0 / 8.0, 'background': None, 'logMode': [False, False], # Limits # maximum value of double float is 1.7E+308, but internal caluclations exceed this limit before the range reaches it. 'limits': { 'xLimits': [-1E307, +1E307], # Maximum and minimum visible X values 'yLimits': [-1E307, +1E307], # Maximum and minimum visible Y values 'xRange': [None, None], # Maximum and minimum X range 'yRange': [None, None], # Maximum and minimum Y range } } self._updatingRange = False ## Used to break recursive loops. See updateAutoRange. self._itemBoundsCache = weakref.WeakKeyDictionary() self.locateGroup = None ## items displayed when using ViewBox.locate(item) self.setFlag(self.GraphicsItemFlag.ItemClipsChildrenToShape) self.setFlag(self.GraphicsItemFlag.ItemIsFocusable, True) ## so we can receive key presses ## childGroup is required so that ViewBox has local coordinates similar to device coordinates. ## this is a workaround for a Qt + OpenGL bug that causes improper clipping ## https://bugreports.qt.nokia.com/browse/QTBUG-23723 self.childGroup = ChildGroup(self) self.childGroup.itemsChangedListeners.append(self) self.background = QtWidgets.QGraphicsRectItem(self.rect()) self.background.setParentItem(self) self.background.setZValue(-1e6) self.background.setPen(fn.mkPen(None)) self.updateBackground() self.border = fn.mkPen(border) self.borderRect = QtWidgets.QGraphicsRectItem(self.rect()) self.borderRect.setParentItem(self) self.borderRect.setZValue(1e3) self.borderRect.setPen(self.border) ## Make scale box that is shown when dragging on the view self.rbScaleBox = QtWidgets.QGraphicsRectItem(0, 0, 1, 1) self.rbScaleBox.setPen(fn.mkPen((255,255,100), width=1)) self.rbScaleBox.setBrush(fn.mkBrush(255,255,0,100)) self.rbScaleBox.setZValue(1e9) self.rbScaleBox.hide() self.addItem(self.rbScaleBox, ignoreBounds=True) ## show target rect for debugging self.target = QtWidgets.QGraphicsRectItem(0, 0, 1, 1) self.target.setPen(fn.mkPen('r')) self.target.setParentItem(self) self.target.hide() self.axHistory = [] # maintain a history of zoom locations self.axHistoryPointer = -1 # pointer into the history. Allows forward/backward movement, not just "undo" self.setZValue(-100) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)) self.setAspectLocked(lockAspect) if enableMenu: self.menu = ViewBoxMenu(self) else: self.menu = None self.register(name) if name is None: self.updateViewLists() self._viewPixelSizeCache = None def getAspectRatio(self): '''return the current aspect ratio''' rect = self.rect() vr = self.viewRect() if rect.height() == 0 or vr.width() == 0 or vr.height() == 0: currentRatio = 1.0 else: currentRatio = (rect.width()/float(rect.height())) / ( vr.width()/vr.height()) return currentRatio def register(self, name): """ Add this ViewBox to the registered list of views. This allows users to manually link the axes of any other ViewBox to this one. The specified *name* will appear in the drop-down lists for axis linking in the context menus of all other views. The same can be accomplished by initializing the ViewBox with the *name* attribute. """ ViewBox.AllViews[self] = None if self.name is not None: del ViewBox.NamedViews[self.name] self.name = name if name is not None: ViewBox.NamedViews[name] = self ViewBox.updateAllViewLists() sid = id(self) self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if (ViewBox is not None and 'sid' in locals() and 'name' in locals()) else None) def unregister(self): """ Remove this ViewBox from the list of linkable views. (see :func:`register() `) """ del ViewBox.AllViews[self] if self.name is not None: del ViewBox.NamedViews[self.name] def close(self): self.clear() self.unregister() def implements(self, interface): return interface == 'ViewBox' def itemChange(self, change, value): ret = super().itemChange(change, value) if change == self.GraphicsItemChange.ItemSceneChange: scene = self.scene() if scene is not None and hasattr(scene, 'sigPrepareForPaint'): scene.sigPrepareForPaint.disconnect(self.prepareForPaint) elif change == self.GraphicsItemChange.ItemSceneHasChanged: scene = self.scene() if scene is not None and hasattr(scene, 'sigPrepareForPaint'): scene.sigPrepareForPaint.connect(self.prepareForPaint) return ret def prepareForPaint(self): #autoRangeEnabled = (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False) # don't check whether auto range is enabled here--only check when setting dirty flag. if self._autoRangeNeedsUpdate: # and autoRangeEnabled: self.updateAutoRange() self.updateMatrix() def getState(self, copy=True): """Return the current state of the ViewBox. Linked views are always converted to view names in the returned state.""" state = self.state.copy() views = [] for v in state['linkedViews']: if isinstance(v, weakref.ref): v = v() if v is None or isinstance(v, str): views.append(v) else: views.append(v.name) state['linkedViews'] = views if copy: return deepcopy(state) else: return state def setState(self, state): """Restore the state of this ViewBox. (see also getState)""" state = state.copy() self.setXLink(state['linkedViews'][0]) self.setYLink(state['linkedViews'][1]) del state['linkedViews'] self.state.update(state) self._applyMenuEnabled() self.updateViewRange() self.sigStateChanged.emit(self) def setBackgroundColor(self, color): """ Set the background color of the ViewBox. If color is None, then no background will be drawn. Added in version 0.9.9 """ self.background.setVisible(color is not None) self.state['background'] = color self.updateBackground() def setMouseMode(self, mode): """ Set the mouse interaction mode. *mode* must be either ViewBox.PanMode or ViewBox.RectMode. In PanMode, the left mouse button pans the view and the right button scales. In RectMode, the left button draws a rectangle which updates the visible region (this mode is more suitable for single-button mice) """ if mode not in [ViewBox.PanMode, ViewBox.RectMode]: raise Exception("Mode must be ViewBox.PanMode or ViewBox.RectMode") self.state['mouseMode'] = mode self.sigStateChanged.emit(self) def setLeftButtonAction(self, mode='rect'): ## for backward compatibility if mode.lower() == 'rect': self.setMouseMode(ViewBox.RectMode) elif mode.lower() == 'pan': self.setMouseMode(ViewBox.PanMode) else: raise Exception('graphicsItems:ViewBox:setLeftButtonAction: unknown mode = %s (Options are "pan" and "rect")' % mode) def innerSceneItem(self): return self.childGroup def setMouseEnabled(self, x=None, y=None): """ Set whether each axis is enabled for mouse interaction. *x*, *y* arguments must be True or False. This allows the user to pan/scale one axis of the view while leaving the other axis unchanged. """ if x is not None: self.state['mouseEnabled'][0] = x if y is not None: self.state['mouseEnabled'][1] = y self.sigStateChanged.emit(self) def mouseEnabled(self): return self.state['mouseEnabled'][:] def setMenuEnabled(self, enableMenu=True): self.state['enableMenu'] = enableMenu self._applyMenuEnabled() self.sigStateChanged.emit(self) def menuEnabled(self): return self.state.get('enableMenu', True) def _applyMenuEnabled(self): enableMenu = self.state.get("enableMenu", True) if enableMenu and self.menu is None: self.menu = ViewBoxMenu(self) self.updateViewLists() elif not enableMenu and self.menu is not None: self.menu.setParent(None) self.menu = None def addItem(self, item, ignoreBounds=False): """ Add a QGraphicsItem to this view. The view will include this item when determining how to set its range automatically unless *ignoreBounds* is True. """ if item.zValue() < self.zValue(): item.setZValue(self.zValue()+1) scene = self.scene() if scene is not None and scene is not item.scene(): scene.addItem(item) ## Necessary due to Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 item.setParentItem(self.childGroup) if not ignoreBounds: self.addedItems.append(item) self.updateAutoRange() def removeItem(self, item): """Remove an item from this view.""" try: self.addedItems.remove(item) except: pass scene = self.scene() if scene is not None: scene.removeItem(item) item.setParentItem(None) self.updateAutoRange() def clear(self): for i in self.addedItems[:]: self.removeItem(i) for ch in self.childGroup.childItems(): ch.setParentItem(None) def resizeEvent(self, ev): if ev.oldSize() != ev.newSize(): self._viewPixelSizeCache = None self._matrixNeedsUpdate = True self.linkedXChanged() self.linkedYChanged() self.updateAutoRange() self.updateViewRange() # self._matrixNeedsUpdate = True self.background.setRect(self.rect()) self.borderRect.setRect(self.rect()) self.sigStateChanged.emit(self) self.sigResized.emit(self) def viewRange(self): """Return a the view's visible range as a list: [[xmin, xmax], [ymin, ymax]]""" return [x[:] for x in self.state['viewRange']] ## return copy def viewRect(self): """Return a QRectF bounding the region visible within the ViewBox""" try: vr0 = self.state['viewRange'][0] vr1 = self.state['viewRange'][1] return QtCore.QRectF(vr0[0], vr1[0], vr0[1]-vr0[0], vr1[1] - vr1[0]) except: print("make qrectf failed:", self.state['viewRange']) raise def targetRange(self): return [x[:] for x in self.state['targetRange']] ## return copy def targetRect(self): """ Return the region which has been requested to be visible. (this is not necessarily the same as the region that is *actually* visible-- resizing and aspect ratio constraints can cause targetRect() and viewRect() to differ) """ try: tr0 = self.state['targetRange'][0] tr1 = self.state['targetRange'][1] return QtCore.QRectF(tr0[0], tr1[0], tr0[1]-tr0[0], tr1[1] - tr1[0]) except: print("make qrectf failed:", self.state['targetRange']) raise def _resetTarget(self): # Reset target range to exactly match current view range. # This is used during mouse interaction to prevent unpredictable # behavior (because the user is unaware of targetRange). if self.state['aspectLocked'] is False: # (interferes with aspect locking) self.state['targetRange'] = [self.state['viewRange'][0][:], self.state['viewRange'][1][:]] def _effectiveLimits(self): # Determines restricted effective scaling range when in log mapping mode if self.state['logMode'][0]: xlimits = (# constrain to the +1.7E308 to 2.2E-308 range of double float values max( self.state['limits']['xLimits'][0], -307.6 ), min( self.state['limits']['xLimits'][1], +308.2 ) ) else: xlimits = self.state['limits']['xLimits'] if self.state['logMode'][1]: ylimits = (# constrain to the +1.7E308 to 2.2E-308 range of double float values max( self.state['limits']['yLimits'][0], -307.6 ), min( self.state['limits']['yLimits'][1], +308.2 ) ) else: ylimits = self.state['limits']['yLimits'] # print('limits ', xlimits, ylimits) # diagnostic output should reflect additional limit in log mode return (xlimits, ylimits) def setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=True, disableAutoRange=True): """ Set the visible range of the ViewBox. Must specify at least one of *rect*, *xRange*, or *yRange*. ================== ===================================================================== **Arguments:** *rect* (QRectF) The full range that should be visible in the view box. *xRange* (min,max) The range that should be visible along the x-axis. *yRange* (min,max) The range that should be visible along the y-axis. *padding* (float) Expand the view by a fraction of the requested range. By default, this value is set between the default padding value and 0.1 depending on the size of the ViewBox. *update* (bool) If True, update the range of the ViewBox immediately. Otherwise, the update is deferred until before the next render. *disableAutoRange* (bool) If True, auto-ranging is diabled. Otherwise, it is left unchanged. ================== ===================================================================== """ self._viewPixelSizeCache = None changes = {} # axes setRequested = [False, False] if rect is not None: changes = {0: [rect.left(), rect.right()], 1: [rect.top(), rect.bottom()]} setRequested = [True, True] if xRange is not None: changes[0] = xRange setRequested[0] = True if yRange is not None: changes[1] = yRange setRequested[1] = True if len(changes) == 0: raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect))) # Update axes one at a time changed = [False, False] # Disable auto-range for each axis that was requested to be set if disableAutoRange: xOff = False if setRequested[0] else None yOff = False if setRequested[1] else None self.enableAutoRange(x=xOff, y=yOff) changed.append(True) limits = self._effectiveLimits() # print('rng:limits ', limits) # diagnostic output should reflect additional limit in log mode # limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits']) minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]] maxRng = [self.state['limits']['xRange'][1], self.state['limits']['yRange'][1]] for ax, range in changes.items(): mn = min(range) mx = max(range) # If we requested 0 range, try to preserve previous scale. # Otherwise just pick an arbitrary scale. if mn == mx: dy = self.state['viewRange'][ax][1] - self.state['viewRange'][ax][0] if dy == 0: dy = 1 mn -= dy*0.5 mx += dy*0.5 # Make sure that the range includes a usable number of quantization steps: # approx. eps : 3e-16 # * min. steps : 10 # * mean value : (mn+mx)*0.5 quantization_limit = (mn+mx) * 1.5e-15 # +/-10 discrete steps of double resolution if mx-mn < 2*quantization_limit: mn -= quantization_limit mx += quantization_limit # Make sure no nan/inf get through if not math.isfinite(mn) or not math.isfinite(mx): raise Exception("Cannot set range [%s, %s]" % (str(mn), str(mx))) # Apply padding if padding is None: xpad = self.suggestPadding(ax) else: xpad = padding p = (mx-mn) * xpad mn -= p mx += p # max range cannot be larger than bounds, if they are given if limits[ax][0] is not None and limits[ax][1] is not None: if maxRng[ax] is not None: maxRng[ax] = min(maxRng[ax], limits[ax][1] - limits[ax][0]) else: maxRng[ax] = limits[ax][1] - limits[ax][0] # If we have limits, we will have at least a max range as well if maxRng[ax] is not None or minRng[ax] is not None: diff = mx - mn if maxRng[ax] is not None and diff > maxRng[ax]: delta = maxRng[ax] - diff elif minRng[ax] is not None and diff < minRng[ax]: delta = minRng[ax] - diff else: delta = 0 mn -= delta / 2. mx += delta / 2. # Make sure our requested area is within limits, if any if limits[ax][0] is not None or limits[ax][1] is not None: lmn, lmx = limits[ax] if lmn is not None and mn < lmn: delta = lmn - mn # Shift the requested view to match our lower limit mn = lmn mx += delta elif lmx is not None and mx > lmx: delta = lmx - mx mx = lmx mn += delta # Set target range if self.state['targetRange'][ax] != [mn, mx]: self.state['targetRange'][ax] = [mn, mx] changed[ax] = True # Update viewRange to match targetRange as closely as possible while # accounting for aspect ratio constraint lockX, lockY = setRequested if lockX and lockY: lockX = False lockY = False self.updateViewRange(lockX, lockY) # If nothing has changed, we are done. if any(changed): # Update target rect for debugging if self.target.isVisible(): self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect())) # If ortho axes have auto-visible-only, update them now # Note that aspect ratio constraints and auto-visible probably do not work together.. if changed[0] and self.state['autoVisibleOnly'][1] and (self.state['autoRange'][0] is not False): self._autoRangeNeedsUpdate = True elif changed[1] and self.state['autoVisibleOnly'][0] and (self.state['autoRange'][1] is not False): self._autoRangeNeedsUpdate = True self.sigStateChanged.emit(self) def setYRange(self, min, max, padding=None, update=True): """ Set the visible Y range of the view to [*min*, *max*]. The *padding* argument causes the range to be set larger by the fraction specified. (by default, this value is between the default padding and 0.1 depending on the size of the ViewBox) """ self.setRange(yRange=[min, max], update=update, padding=padding) def setXRange(self, min, max, padding=None, update=True): """ Set the visible X range of the view to [*min*, *max*]. The *padding* argument causes the range to be set larger by the fraction specified. (by default, this value is between the default padding and 0.1 depending on the size of the ViewBox) """ self.setRange(xRange=[min, max], update=update, padding=padding) def autoRange(self, padding=None, items=None, item=None): """ Set the range of the view box to make all children visible. Note that this is not the same as enableAutoRange, which causes the view to automatically auto-range whenever its contents are changed. ============== ============================================================= **Arguments:** padding The fraction of the total data range to add on to the final visible range. By default, this value is set between the default padding and 0.1 depending on the size of the ViewBox. items If specified, this is a list of items to consider when determining the visible range. ============== ============================================================= """ if item is None: bounds = self.childrenBoundingRect(items=items) else: bounds = self.mapFromItemToView(item, item.boundingRect()).boundingRect() if bounds is not None: self.setRange(bounds, padding=padding) def suggestPadding(self, axis): l = self.width() if axis==0 else self.height() def_pad = self.state['defaultPadding'] if def_pad == 0.: return def_pad # respect requested zero padding max_pad = max(0.1, def_pad) # don't shrink a large default padding if l > 0: padding = fn.clip_scalar( 50*def_pad / (l**0.5), def_pad, max_pad) else: padding = def_pad return padding def setLimits(self, **kwds): """ Set limits that constrain the possible view ranges. **Panning limits**. The following arguments define the region within the viewbox coordinate system that may be accessed by panning the view. =========== ============================================================ xMin Minimum allowed x-axis value xMax Maximum allowed x-axis value yMin Minimum allowed y-axis value yMax Maximum allowed y-axis value =========== ============================================================ **Scaling limits**. These arguments prevent the view being zoomed in or out too far. =========== ============================================================ minXRange Minimum allowed left-to-right span across the view. maxXRange Maximum allowed left-to-right span across the view. minYRange Minimum allowed top-to-bottom span across the view. maxYRange Maximum allowed top-to-bottom span across the view. =========== ============================================================ Added in version 0.9.9 """ update = False allowed = ['xMin', 'xMax', 'yMin', 'yMax', 'minXRange', 'maxXRange', 'minYRange', 'maxYRange'] for kwd in kwds: if kwd not in allowed: raise ValueError("Invalid keyword argument '%s'." % kwd) for axis in [0,1]: for mnmx in [0,1]: kwd = [['xMin', 'xMax'], ['yMin', 'yMax']][axis][mnmx] lname = ['xLimits', 'yLimits'][axis] if kwd in kwds and self.state['limits'][lname][mnmx] != kwds[kwd]: self.state['limits'][lname][mnmx] = kwds[kwd] update = True kwd = [['minXRange', 'maxXRange'], ['minYRange', 'maxYRange']][axis][mnmx] lname = ['xRange', 'yRange'][axis] if kwd in kwds and self.state['limits'][lname][mnmx] != kwds[kwd]: self.state['limits'][lname][mnmx] = kwds[kwd] update = True if update: self.updateViewRange() def scaleBy(self, s=None, center=None, x=None, y=None): """ Scale by *s* around given center point (or center of view). *s* may be a Point or tuple (x, y). Optionally, x or y may be specified individually. This allows the other axis to be left unaffected (note that using a scale factor of 1.0 may cause slight changes due to floating-point error). """ if s is not None: x, y = s[0], s[1] affect = [x is not None, y is not None] if not any(affect): return scale = Point([1.0 if x is None else x, 1.0 if y is None else y]) if self.state['aspectLocked'] is not False: scale[0] = scale[1] vr = self.targetRect() if center is None: center = Point(vr.center()) else: center = Point(center) tl = center + (vr.topLeft()-center) * scale br = center + (vr.bottomRight()-center) * scale if not affect[0]: self.setYRange(tl.y(), br.y(), padding=0) elif not affect[1]: self.setXRange(tl.x(), br.x(), padding=0) else: self.setRange(QtCore.QRectF(tl, br), padding=0) def translateBy(self, t=None, x=None, y=None): """ Translate the view by *t*, which may be a Point or tuple (x, y). Alternately, x or y may be specified independently, leaving the other axis unchanged (note that using a translation of 0 may still cause small changes due to floating-point error). """ vr = self.targetRect() if t is not None: t = Point(t) self.setRange(vr.translated(t), padding=0) else: if x is not None: x = vr.left()+x, vr.right()+x if y is not None: y = vr.top()+y, vr.bottom()+y if x is not None or y is not None: self.setRange(xRange=x, yRange=y, padding=0) def enableAutoRange(self, axis=None, enable=True, x=None, y=None): """ Enable (or disable) auto-range for *axis*, which may be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes for both (if *axis* is omitted, both axes will be changed). When enabled, the axis will automatically rescale when items are added/removed or change their shape. The argument *enable* may optionally be a float (0.0-1.0) which indicates the fraction of the data that should be visible (this only works with items implementing a dataRange method, such as PlotDataItem). """ # support simpler interface: if x is not None or y is not None: if x is not None: self.enableAutoRange(ViewBox.XAxis, x) if y is not None: self.enableAutoRange(ViewBox.YAxis, y) return if enable is True: enable = 1.0 if axis is None: axis = ViewBox.XYAxes needAutoRangeUpdate = False if axis == ViewBox.XYAxes or axis == 'xy': axes = [0, 1] elif axis == ViewBox.XAxis or axis == 'x': axes = [0] elif axis == ViewBox.YAxis or axis == 'y': axes = [1] else: raise Exception('axis argument must be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes.') for ax in axes: if self.state['autoRange'][ax] != enable: # If we are disabling, do one last auto-range to make sure that # previously scheduled auto-range changes are enacted if enable is False and self._autoRangeNeedsUpdate: self.updateAutoRange() self.state['autoRange'][ax] = enable self._autoRangeNeedsUpdate |= (enable is not False) self.update() self.sigStateChanged.emit(self) def disableAutoRange(self, axis=None): """Disables auto-range. (See enableAutoRange)""" self.enableAutoRange(axis, enable=False) def autoRangeEnabled(self): return self.state['autoRange'][:] def setAutoPan(self, x=None, y=None): """Set whether automatic range will only pan (not scale) the view. """ if x is not None: self.state['autoPan'][0] = x if y is not None: self.state['autoPan'][1] = y if None not in [x,y]: self.updateAutoRange() def setAutoVisible(self, x=None, y=None): """Set whether automatic range uses only visible data when determining the range to show. """ if x is not None: self.state['autoVisibleOnly'][0] = x if x is True: self.state['autoVisibleOnly'][1] = False if y is not None: self.state['autoVisibleOnly'][1] = y if y is True: self.state['autoVisibleOnly'][0] = False if x is not None or y is not None: self.updateAutoRange() def updateAutoRange(self): ## Break recursive loops when auto-ranging. ## This is needed because some items change their size in response ## to a view change. if self._updatingRange: return self._updatingRange = True try: if not any(self.state['autoRange']): return targetRect = self.viewRange() fractionVisible = self.state['autoRange'][:] for i in [0,1]: if type(fractionVisible[i]) is bool: fractionVisible[i] = 1.0 childRange = None order = [0,1] if self.state['autoVisibleOnly'][0] is True: order = [1,0] args = {} for ax in order: if self.state['autoRange'][ax] is False: continue if self.state['autoVisibleOnly'][ax]: oRange = [None, None] oRange[ax] = targetRect[1-ax] childRange = self.childrenBounds(frac=fractionVisible, orthoRange=oRange) else: if childRange is None: childRange = self.childrenBounds(frac=fractionVisible) ## Make corrections to range xr = childRange[ax] if xr is not None: if self.state['autoPan'][ax]: x = sum(xr) * 0.5 w2 = (targetRect[ax][1]-targetRect[ax][0]) / 2. childRange[ax] = [x-w2, x+w2] else: padding = self.suggestPadding(ax) wp = (xr[1] - xr[0]) * padding childRange[ax][0] -= wp childRange[ax][1] += wp targetRect[ax] = childRange[ax] args['xRange' if ax == 0 else 'yRange'] = targetRect[ax] # check for and ignore bad ranges for k in ['xRange', 'yRange']: if k in args: if not math.isfinite(args[k][0]) or not math.isfinite(args[k][1]): _ = args.pop(k) #print("Warning: %s is invalid: %s" % (k, str(r)) if len(args) == 0: return args['padding'] = 0.0 args['disableAutoRange'] = False self.setRange(**args) finally: self._autoRangeNeedsUpdate = False self._updatingRange = False def setXLink(self, view): """Link this view's X axis to another view. (see LinkView)""" self.linkView(self.XAxis, view) def setYLink(self, view): """Link this view's Y axis to another view. (see LinkView)""" self.linkView(self.YAxis, view) def setLogMode(self, axis, logMode): """Informs ViewBox that log mode is active for the specified axis, so that the view range cen be restricted""" if axis == 'x': self.state['logMode'][0] = bool(logMode) # print('x log mode', self.state['logMode'][0] ) elif axis == 'y': self.state['logMode'][1] = bool(logMode) # print('x log mode', self.state['logMode'][0] ) def linkView(self, axis, view): """ Link X or Y axes of two views and unlink any previously connected axes. *axis* must be ViewBox.XAxis or ViewBox.YAxis. If view is None, the axis is left unlinked. """ if isinstance(view, str): if view == '': view = None else: view = ViewBox.NamedViews.get(view, view) ## convert view name to ViewBox if possible if hasattr(view, 'implements') and view.implements('ViewBoxWrapper'): view = view.getViewBox() ## used to connect/disconnect signals between a pair of views if axis == ViewBox.XAxis: signal = 'sigXRangeChanged' slot = self.linkedXChanged else: signal = 'sigYRangeChanged' slot = self.linkedYChanged oldLink = self.linkedView(axis) if oldLink is not None: try: getattr(oldLink, signal).disconnect(slot) oldLink.sigResized.disconnect(slot) except (TypeError, RuntimeError): ## This can occur if the view has been deleted already pass if view is None or isinstance(view, str): self.state['linkedViews'][axis] = view else: self.state['linkedViews'][axis] = weakref.ref(view) getattr(view, signal).connect(slot) view.sigResized.connect(slot) if view.autoRangeEnabled()[axis] is not False: self.enableAutoRange(axis, False) slot() else: if self.autoRangeEnabled()[axis] is False: slot() self.sigStateChanged.emit(self) def blockLink(self, b): self.linksBlocked = b ## prevents recursive plot-change propagation def linkedXChanged(self): ## called when x range of linked view has changed view = self.linkedView(0) self.linkedViewChanged(view, ViewBox.XAxis) def linkedYChanged(self): ## called when y range of linked view has changed view = self.linkedView(1) self.linkedViewChanged(view, ViewBox.YAxis) def linkedView(self, ax): ## Return the linked view for axis *ax*. ## this method _always_ returns either a ViewBox or None. v = self.state['linkedViews'][ax] if v is None or isinstance(v, str): return None else: return v() ## dereference weakref pointer. If the reference is dead, this returns None def linkedViewChanged(self, view, axis): if self.linksBlocked or view is None: return #print self.name, "ViewBox.linkedViewChanged", axis, view.viewRange()[axis] vr = view.viewRect() vg = view.screenGeometry() sg = self.screenGeometry() if vg is None or sg is None: return view.blockLink(True) try: if axis == ViewBox.XAxis: overlap = min(sg.right(), vg.right()) - max(sg.left(), vg.left()) if overlap < min(vg.width()/3, sg.width()/3): ## if less than 1/3 of views overlap, ## then just replicate the view x1 = vr.left() x2 = vr.right() else: ## views overlap; line them up upp = float(vr.width()) / vg.width() if self.xInverted(): x1 = vr.left() + (sg.right()-vg.right()) * upp else: x1 = vr.left() + (sg.x()-vg.x()) * upp x2 = x1 + sg.width() * upp self.enableAutoRange(ViewBox.XAxis, False) self.setXRange(x1, x2, padding=0) else: overlap = min(sg.bottom(), vg.bottom()) - max(sg.top(), vg.top()) if overlap < min(vg.height()/3, sg.height()/3): ## if less than 1/3 of views overlap, ## then just replicate the view y1 = vr.top() y2 = vr.bottom() else: ## views overlap; line them up upp = float(vr.height()) / vg.height() if self.yInverted(): y2 = vr.bottom() + (sg.bottom()-vg.bottom()) * upp else: y2 = vr.bottom() + (sg.top()-vg.top()) * upp y1 = y2 - sg.height() * upp self.enableAutoRange(ViewBox.YAxis, False) self.setYRange(y1, y2, padding=0) finally: view.blockLink(False) def screenGeometry(self): """return the screen geometry of the viewbox""" v = self.getViewWidget() if v is None: return None b = self.sceneBoundingRect() wr = v.mapFromScene(b).boundingRect() pos = v.mapToGlobal(v.pos()) wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) return wr def itemsChanged(self): ## called when items are added/removed from self.childGroup self.updateAutoRange() def itemBoundsChanged(self, item): self._itemBoundsCache.pop(item, None) if (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False): self._autoRangeNeedsUpdate = True self.update() def _invertAxis(self, ax, inv): key = 'xy'[ax] + 'Inverted' if self.state[key] == inv: return self.state[key] = inv self._matrixNeedsUpdate = True # updateViewRange won't detect this for us self.updateViewRange() self.update() self.sigStateChanged.emit(self) if ax: self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][ax])) else: self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][ax])) def invertY(self, b=True): """ By default, the positive y-axis points upward on the screen. Use invertY(True) to reverse the y-axis. """ self._invertAxis(1, b) def yInverted(self): return self.state['yInverted'] def invertX(self, b=True): """ By default, the positive x-axis points rightward on the screen. Use invertX(True) to reverse the x-axis. """ self._invertAxis(0, b) def xInverted(self): return self.state['xInverted'] def setBorder(self, *args, **kwds): """ Set the pen used to draw border around the view If border is None, then no border will be drawn. Added in version 0.9.10 See :func:`mkPen ` for arguments. """ self.border = fn.mkPen(*args, **kwds) self.borderRect.setPen(self.border) def setDefaultPadding(self, padding=0.02): """ Sets the fraction of the data range that is used to pad the view range in when auto-ranging. By default, this fraction is 0.02. """ self.state['defaultPadding'] = padding def setAspectLocked(self, lock=True, ratio=1): """ If the aspect ratio is locked, view scaling must always preserve the aspect ratio. By default, the ratio is set to 1; x and y both have the same scaling. This ratio can be overridden (xScale/yScale), or use None to lock in the current ratio. """ if not lock: if self.state['aspectLocked'] == False: return self.state['aspectLocked'] = False else: currentRatio = self.getAspectRatio() if ratio is None: ratio = currentRatio if self.state['aspectLocked'] == ratio: # nothing to change return self.state['aspectLocked'] = ratio if ratio != currentRatio: ## If this would change the current range, do that now self.updateViewRange() self.updateAutoRange() self.updateViewRange() self.sigStateChanged.emit(self) def childTransform(self): """ Return the transform that maps from child(item in the childGroup) coordinates to local coordinates. (This maps from inside the viewbox to outside) """ self.updateMatrix() m = self.childGroup.transform() return m def mapToView(self, obj): """Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox""" self.updateMatrix() m = fn.invertQTransform(self.childTransform()) return m.map(obj) def mapFromView(self, obj): """Maps from the coordinate system displayed inside the ViewBox to the local coordinates of the ViewBox""" self.updateMatrix() m = self.childTransform() return m.map(obj) def mapSceneToView(self, obj): """Maps from scene coordinates to the coordinate system displayed inside the ViewBox""" self.updateMatrix() return self.mapToView(self.mapFromScene(obj)) def mapViewToScene(self, obj): """Maps from the coordinate system displayed inside the ViewBox to scene coordinates""" self.updateMatrix() return self.mapToScene(self.mapFromView(obj)) def mapFromItemToView(self, item, obj): """Maps *obj* from the local coordinate system of *item* to the view coordinates""" self.updateMatrix() return self.childGroup.mapFromItem(item, obj) def mapFromViewToItem(self, item, obj): """Maps *obj* from view coordinates to the local coordinate system of *item*.""" self.updateMatrix() return self.childGroup.mapToItem(item, obj) def mapViewToDevice(self, obj): self.updateMatrix() return self.mapToDevice(self.mapFromView(obj)) def mapDeviceToView(self, obj): self.updateMatrix() return self.mapToView(self.mapFromDevice(obj)) def viewPixelSize(self): """Return the (width, height) of a screen pixel in view coordinates.""" if self._viewPixelSizeCache is None: o = self.mapToView(Point(0, 0)) px, py = [Point(self.mapToView(v) - o) for v in self.pixelVectors()] self._viewPixelSizeCache = (px.length(), py.length()) return self._viewPixelSizeCache def itemBoundingRect(self, item): """Return the bounding rect of the item in view coordinates""" return self.mapSceneToView(item.sceneBoundingRect()).boundingRect() def wheelEvent(self, ev, axis=None): if axis in (0, 1): mask = [False, False] mask[axis] = self.state['mouseEnabled'][axis] else: mask = self.state['mouseEnabled'][:] s = 1.02 ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor s = [(None if m is False else s) for m in mask] center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) self._resetTarget() self.scaleBy(s, center) ev.accept() self.sigRangeChangedManually.emit(mask) def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.MouseButton.RightButton and self.menuEnabled(): ev.accept() self.raiseContextMenu(ev) def raiseContextMenu(self, ev): menu = self.getMenu(ev) if menu is not None: self.scene().addParentContextMenus(self, menu, ev) menu.popup(ev.screenPos().toPoint()) def getMenu(self, ev): return self.menu def getContextMenus(self, event): return self.menu.actions() if self.menuEnabled() else [] def mouseDragEvent(self, ev, axis=None): ## if axis is specified, event will only affect that axis. ev.accept() ## we accept all buttons pos = ev.scenePos() dif = pos - ev.lastScenePos() dif = dif * -1 ## Ignore axes if mouse is disabled mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float64) mask = mouseEnabled.copy() if axis is not None: mask[1-axis] = 0.0 ## Scale or translate based on mouse button if ev.button() in [QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.MouseButton.MiddleButton]: if self.state['mouseMode'] == ViewBox.RectMode and axis is None: if ev.isFinish(): ## This is the final move in the drag; change the view scale now #print "finish" self.rbScaleBox.hide() ax = QtCore.QRectF(Point(ev.buttonDownScenePos(ev.button())), Point(pos)) ax = self.childGroup.mapRectFromScene(ax) self.showAxRect(ax) self.axHistoryPointer += 1 self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax] else: ## update shape of scale box self.updateScaleBox(ev.buttonDownScenePos(), ev.scenePos()) else: tr = self.childGroup.transform() tr = fn.invertQTransform(tr) tr = tr.map(dif*mask) - tr.map(Point(0,0)) x = tr.x() if mask[0] == 1 else None y = tr.y() if mask[1] == 1 else None self._resetTarget() if x is not None or y is not None: self.translateBy(x=x, y=y) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) elif ev.button() & QtCore.Qt.MouseButton.RightButton: #print "vb.rightDrag" if self.state['aspectLocked'] is not False: mask[0] = 0 dif = ev.screenPos() - ev.lastScreenPos() dif = np.array([dif.x(), dif.y()]) dif[0] *= -1 s = ((mask * 0.02) + 1) ** dif tr = self.childGroup.transform() tr = fn.invertQTransform(tr) x = s[0] if mouseEnabled[0] == 1 else None y = s[1] if mouseEnabled[1] == 1 else None center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.MouseButton.RightButton))) self._resetTarget() self.scaleBy(x=x, y=y, center=center) self.sigRangeChangedManually.emit(self.state['mouseEnabled']) def keyPressEvent(self, ev): """ This routine should capture key presses in the current view box. Key presses are used only when mouse mode is RectMode The following events are implemented: ctrl-A : zooms out to the default "full" view of the plot ctrl-+ : moves forward in the zooming stack (if it exists) ctrl-- : moves backward in the zooming stack (if it exists) """ ev.accept() if ev.text() == '-': self.scaleHistory(-1) elif ev.text() in ['+', '=']: self.scaleHistory(1) elif ev.key() == QtCore.Qt.Key.Key_Backspace: self.scaleHistory(len(self.axHistory)) else: ev.ignore() def scaleHistory(self, d): if len(self.axHistory) == 0: return ptr = max(0, min(len(self.axHistory)-1, self.axHistoryPointer+d)) if ptr != self.axHistoryPointer: self.axHistoryPointer = ptr self.showAxRect(self.axHistory[ptr]) def updateScaleBox(self, p1, p2): r = QtCore.QRectF(p1, p2) r = self.childGroup.mapRectFromScene(r) self.rbScaleBox.setPos(r.topLeft()) tr = QtGui.QTransform.fromScale(r.width(), r.height()) self.rbScaleBox.setTransform(tr) self.rbScaleBox.show() def showAxRect(self, ax, **kwargs): """Set the visible range to the given rectangle Passes keyword arguments to setRange """ self.setRange(ax.normalized(), **kwargs) # be sure w, h are correct coordinates self.sigRangeChangedManually.emit(self.state['mouseEnabled']) def allChildren(self, item=None): """Return a list of all children and grandchildren of this ViewBox""" if item is None: item = self.childGroup children = [item] for ch in item.childItems(): children.extend(self.allChildren(ch)) return children def childrenBounds(self, frac=None, orthoRange=(None,None), items=None): """Return the bounding range of all children. [[xmin, xmax], [ymin, ymax]] Values may be None if there are no specific bounds for an axis. """ profiler = debug.Profiler() if items is None: items = self.addedItems ## measure pixel dimensions in view box px, py = [v.length() if v is not None else 0 for v in self.childGroup.pixelVectors()] ## First collect all boundary information itemBounds = [] for item in items: if not item.isVisible() or not item.scene() is self.scene(): continue useX = True useY = True if hasattr(item, 'dataBounds'): if frac is None: frac = (1.0, 1.0) xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() if ( xr is None or (xr[0] is None and xr[1] is None) or not math.isfinite(xr[0]) or not math.isfinite(xr[1]) ): useX = False xr = (0,0) if ( yr is None or (yr[0] is None and yr[1] is None) or not math.isfinite(yr[0]) or not math.isfinite(yr[1]) ): useY = False yr = (0,0) bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0]) bounds = self.mapFromItemToView(item, bounds).boundingRect() if not any([useX, useY]): continue ## If we are ignoring only one axis, we need to check for rotations if useX != useY: ## != means xor ang = round(item.transformAngle()) if ang == 0 or ang == 180: pass elif ang == 90 or ang == 270: useX, useY = useY, useX else: ## Item is rotated at non-orthogonal angle, ignore bounds entirely. ## Not really sure what is the expected behavior in this case. continue ## need to check for item rotations and decide how best to apply this boundary. itemBounds.append((bounds, useX, useY, pxPad)) else: if item.flags() & item.GraphicsItemFlag.ItemHasNoContents: continue bounds = self.mapFromItemToView(item, item.boundingRect()).boundingRect() itemBounds.append((bounds, True, True, 0)) ## determine tentative new range range = [None, None] for bounds, useX, useY, px in itemBounds: if useY: if range[1] is not None: range[1] = [min(bounds.top(), range[1][0]), max(bounds.bottom(), range[1][1])] else: range[1] = [bounds.top(), bounds.bottom()] if useX: if range[0] is not None: range[0] = [min(bounds.left(), range[0][0]), max(bounds.right(), range[0][1])] else: range[0] = [bounds.left(), bounds.right()] profiler() ## Now expand any bounds that have a pixel margin ## This must be done _after_ we have a good estimate of the new range ## to ensure that the pixel size is roughly accurate. w = self.width() h = self.height() if w > 0 and range[0] is not None: pxSize = (range[0][1] - range[0][0]) / w for bounds, useX, useY, px in itemBounds: if px == 0 or not useX: continue range[0][0] = min(range[0][0], bounds.left() - px*pxSize) range[0][1] = max(range[0][1], bounds.right() + px*pxSize) if h > 0 and range[1] is not None: pxSize = (range[1][1] - range[1][0]) / h for bounds, useX, useY, px in itemBounds: if px == 0 or not useY: continue range[1][0] = min(range[1][0], bounds.top() - px*pxSize) range[1][1] = max(range[1][1], bounds.bottom() + px*pxSize) return range def childrenBoundingRect(self, *args, **kwds): range = self.childrenBounds(*args, **kwds) tr = self.targetRange() if range[0] is None: range[0] = tr[0] if range[1] is None: range[1] = tr[1] bounds = QtCore.QRectF(range[0][0], range[1][0], range[0][1]-range[0][0], range[1][1]-range[1][0]) return bounds # Including a prepareForPaint call is part of the Qt strategy to # defer expensive redraw opertions until requested by a 'sigPrepareForPaint' signal # # However, as currently implemented, a call to prepareForPaint as part of the regular # 'update' call results in an undesired reset of pan/zoom: # https://github.com/pyqtgraph/pyqtgraph/issues/2029 # # def update(self, *args, **kwargs): # self.prepareForPaint() # GraphicsWidget.update(self, *args, **kwargs) def updateViewRange(self, forceX=False, forceY=False): ## Update viewRange to match targetRange as closely as possible, given ## aspect ratio constraints. The *force* arguments are used to indicate ## which axis (if any) should be unchanged when applying constraints. viewRange = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]] changed = [False, False] #-------- Make correction for aspect ratio constraint ---------- # aspect is (widget w/h) / (view range w/h) aspect = self.state['aspectLocked'] # size ratio / view ratio tr = self.targetRect() bounds = self.rect() limits = self._effectiveLimits() # print('upd:limits ', limits) # diagnostic output should reflect additional limit in log mode minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]] maxRng = [self.state['limits']['xRange'][1], self.state['limits']['yRange'][1]] for axis in [0, 1]: if limits[axis][0] is None and limits[axis][1] is None and minRng[axis] is None and maxRng[axis] is None: continue # max range cannot be larger than bounds, if they are given if limits[axis][0] is not None and limits[axis][1] is not None: if maxRng[axis] is not None: maxRng[axis] = min(maxRng[axis], limits[axis][1] - limits[axis][0]) else: maxRng[axis] = limits[axis][1] - limits[axis][0] if aspect is not False and 0 not in [aspect, tr.height(), bounds.height(), bounds.width()]: ## This is the view range aspect ratio we have requested targetRatio = tr.width() / tr.height() if tr.height() != 0 else 1 ## This is the view range aspect ratio we need to obey aspect constraint viewRatio = (bounds.width() / bounds.height() if bounds.height() != 0 else 1) / aspect viewRatio = 1 if viewRatio == 0 else viewRatio # Calculate both the x and y ranges that would be needed to obtain the desired aspect ratio dy = 0.5 * (tr.width() / viewRatio - tr.height()) dx = 0.5 * (tr.height() * viewRatio - tr.width()) rangeY = [self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy] rangeX = [self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx] canidateRange = [rangeX, rangeY] # Decide which range to try to keep unchanged #print self.name, "aspect:", aspect, "changed:", changed, "auto:", self.state['autoRange'] if forceX: ax = 0 elif forceY: ax = 1 else: # if we are not required to keep a particular axis unchanged, # then try to make the entire target range visible ax = 0 if targetRatio > viewRatio else 1 target = 0 if ax == 1 else 1 # See if this choice would cause out-of-range issues if maxRng is not None or minRng is not None: diff = canidateRange[target][1] - canidateRange[target][0] if maxRng[target] is not None and diff > maxRng[target] or \ minRng[target] is not None and diff < minRng[target]: # tweak the target range down so we can still pan properly self.state['targetRange'][ax] = canidateRange[ax] ax = target # Switch the "fixed" axes if ax == 0: ## view range needs to be taller than target if dy != 0: changed[1] = True viewRange[1] = rangeY else: ## view range needs to be wider than target if dx != 0: changed[0] = True viewRange[0] = rangeX changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) or (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)] self.state['viewRange'] = viewRange if any(changed): self._matrixNeedsUpdate = True self.update() # Inform linked views that the range has changed for ax in [0, 1]: if not changed[ax]: continue link = self.linkedView(ax) if link is not None: link.linkedViewChanged(self, ax) # emit range change signals # print('announcing view range changes:',self.state['viewRange'] ) if changed[0]: self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0])) if changed[1]: self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1])) self.sigRangeChanged.emit(self, self.state['viewRange'], changed) def updateMatrix(self, changed=None): if not self._matrixNeedsUpdate: return ## Make the childGroup's transform match the requested viewRange. bounds = self.rect() vr = self.viewRect() if vr.height() == 0 or vr.width() == 0: return scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height()) if not self.state['yInverted']: scale = scale * Point(1, -1) if self.state['xInverted']: scale = scale * Point(-1, 1) m = QtGui.QTransform() ## First center the viewport at 0 center = bounds.center() m.translate(center.x(), center.y()) ## Now scale and translate properly m.scale(scale[0], scale[1]) st = Point(vr.center()) m.translate(-st[0], -st[1]) self.childGroup.setTransform(m) self._matrixNeedsUpdate = False self.sigTransformChanged.emit(self) ## segfaults here: 1 def paint(self, p, opt, widget): if self.border is not None: bounds = self.shape() p.setPen(self.border) #p.fillRect(bounds, QtGui.QColor(0, 0, 0)) p.drawPath(bounds) #p.setPen(fn.mkPen('r')) #path = QtGui.QPainterPath() #path.addRect(self.targetRect()) #tr = self.mapFromView(path) #p.drawPath(tr) def updateBackground(self): bg = self.state['background'] if bg is None: self.background.hide() else: self.background.show() self.background.setBrush(fn.mkBrush(bg)) def updateViewLists(self): try: self.window() except RuntimeError: ## this view has already been deleted; it will probably be collected shortly. return def view_key(view): return (view.window() is self.window(), view.name) ## make a sorted list of all named views nv = sorted(ViewBox.NamedViews.values(), key=view_key) if self in nv: nv.remove(self) if self.menu is not None: self.menu.setViewList(nv) for ax in [0,1]: link = self.state['linkedViews'][ax] if isinstance(link, str): ## axis has not been linked yet; see if it's possible now for v in nv: if link == v.name: self.linkView(ax, v) @staticmethod def updateAllViewLists(): for v in ViewBox.AllViews: v.updateViewLists() @staticmethod def forgetView(vid, name): if ViewBox is None: ## can happen as python is shutting down return if QtWidgets.QApplication.instance() is None: return ## Called with ID and name of view (the view itself is no longer available) for v in list(ViewBox.AllViews.keys()): if id(v) == vid: ViewBox.AllViews.pop(v) break ViewBox.NamedViews.pop(name, None) ViewBox.updateAllViewLists() @staticmethod def quit(): ## called when the application is about to exit. ## this disables all callbacks, which might otherwise generate errors if invoked during exit. for k in ViewBox.AllViews: if isQObjectAlive(k) and getConfigOption('crashWarning'): sys.stderr.write('Warning: ViewBox should be closed before application exit.\n') try: k.destroyed.disconnect() except RuntimeError: ## signal is already disconnected. pass except TypeError: ## view has already been deleted (?) pass except AttributeError: # PySide has deleted signal pass def locate(self, item, timeout=3.0, children=False): """ Temporarily display the bounding rect of an item and lines connecting to the center of the view. This is useful for determining the location of items that may be out of the range of the ViewBox. if allChildren is True, then the bounding rect of all item's children will be shown instead. """ self.clearLocate() if item.scene() is not self.scene(): raise Exception("Item does not share a scene with this ViewBox.") c = self.viewRect().center() if children: br = self.mapFromItemToView(item, item.childrenBoundingRect()).boundingRect() else: br = self.mapFromItemToView(item, item.boundingRect()).boundingRect() g = ItemGroup() g.setParentItem(self.childGroup) self.locateGroup = g g.box = QtWidgets.QGraphicsRectItem(br) g.box.setParentItem(g) g.lines = [] for p in (br.topLeft(), br.bottomLeft(), br.bottomRight(), br.topRight()): line = QtWidgets.QGraphicsLineItem(c.x(), c.y(), p.x(), p.y()) line.setParentItem(g) g.lines.append(line) for item in g.childItems(): item.setPen(fn.mkPen(color='y', width=3)) g.setZValue(1000000) if children: g.path = QtWidgets.QGraphicsPathItem(g.childrenShape()) else: g.path = QtWidgets.QGraphicsPathItem(g.shape()) g.path.setParentItem(g) g.path.setPen(fn.mkPen('g')) g.path.setZValue(100) QtCore.QTimer.singleShot(timeout*1000, self.clearLocate) def clearLocate(self): if self.locateGroup is None: return self.scene().removeItem(self.locateGroup) self.locateGroup = None from .ViewBoxMenu import ViewBoxMenu ���������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py���������������������������0000664�0000000�0000000�00000023314�14210455074�0026625�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import importlib from ...Qt import QT_LIB, QtCore, QtGui, QtWidgets from ...WidgetGroup import WidgetGroup ui_template = importlib.import_module( f'.axisCtrlTemplate_{QT_LIB.lower()}', package=__package__) import weakref translate = QtCore.QCoreApplication.translate class ViewBoxMenu(QtWidgets.QMenu): def __init__(self, view): QtWidgets.QMenu.__init__(self) self.view = weakref.ref(view) ## keep weakref to view to avoid circular reference (don't know why, but this prevents the ViewBox from being collected) self.valid = False ## tells us whether the ui needs to be updated self.viewMap = weakref.WeakValueDictionary() ## weakrefs to all views listed in the link combos self.setTitle(translate("ViewBox", "ViewBox options")) self.viewAll = QtGui.QAction(translate("ViewBox", "View All"), self) self.viewAll.triggered.connect(self.autoRange) self.addAction(self.viewAll) self.axes = [] self.ctrl = [] self.widgetGroups = [] self.dv = QtGui.QDoubleValidator(self) for axis in 'XY': m = QtWidgets.QMenu() m.setTitle(f"{axis} {translate('ViewBox', 'axis')}") w = QtWidgets.QWidget() ui = ui_template.Ui_Form() ui.setupUi(w) a = QtWidgets.QWidgetAction(self) a.setDefaultWidget(w) m.addAction(a) self.addMenu(m) self.axes.append(m) self.ctrl.append(ui) wg = WidgetGroup(w) self.widgetGroups.append(w) connects = [ (ui.mouseCheck.toggled, 'MouseToggled'), (ui.manualRadio.clicked, 'ManualClicked'), (ui.minText.editingFinished, 'RangeTextChanged'), (ui.maxText.editingFinished, 'RangeTextChanged'), (ui.autoRadio.clicked, 'AutoClicked'), (ui.autoPercentSpin.valueChanged, 'AutoSpinChanged'), (ui.linkCombo.currentIndexChanged, 'LinkComboChanged'), (ui.autoPanCheck.toggled, 'AutoPanToggled'), (ui.visibleOnlyCheck.toggled, 'VisibleOnlyToggled') ] for sig, fn in connects: sig.connect(getattr(self, axis.lower()+fn)) self.ctrl[0].invertCheck.toggled.connect(self.xInvertToggled) self.ctrl[1].invertCheck.toggled.connect(self.yInvertToggled) ## exporting is handled by GraphicsScene now #self.export = QtWidgets.QMenu("Export") #self.setExportMethods(view.exportMethods) #self.addMenu(self.export) self.leftMenu = QtWidgets.QMenu(translate("ViewBox", "Mouse Mode")) group = QtGui.QActionGroup(self) # This does not work! QAction _must_ be initialized with a permanent # object as the parent or else it may be collected prematurely. #pan = self.leftMenu.addAction("3 button", self.set3ButtonMode) #zoom = self.leftMenu.addAction("1 button", self.set1ButtonMode) pan = QtGui.QAction(translate("ViewBox", "3 button"), self.leftMenu) zoom = QtGui.QAction(translate("ViewBox", "1 button"), self.leftMenu) self.leftMenu.addAction(pan) self.leftMenu.addAction(zoom) pan.triggered.connect(self.set3ButtonMode) zoom.triggered.connect(self.set1ButtonMode) pan.setCheckable(True) zoom.setCheckable(True) pan.setActionGroup(group) zoom.setActionGroup(group) self.mouseModes = [pan, zoom] self.addMenu(self.leftMenu) self.view().sigStateChanged.connect(self.viewStateChanged) self.updateState() def setExportMethods(self, methods): self.exportMethods = methods self.export.clear() for opt, fn in methods.items(): self.export.addAction(opt, self.exportMethod) def viewStateChanged(self): self.valid = False if self.ctrl[0].minText.isVisible() or self.ctrl[1].minText.isVisible(): self.updateState() def updateState(self): ## Something about the viewbox has changed; update the menu GUI state = self.view().getState(copy=False) if state['mouseMode'] == ViewBox.PanMode: self.mouseModes[0].setChecked(True) else: self.mouseModes[1].setChecked(True) for i in [0,1]: # x, y tr = state['targetRange'][i] self.ctrl[i].minText.setText("%0.5g" % tr[0]) self.ctrl[i].maxText.setText("%0.5g" % tr[1]) if state['autoRange'][i] is not False: self.ctrl[i].autoRadio.setChecked(True) if state['autoRange'][i] is not True: self.ctrl[i].autoPercentSpin.setValue(int(state['autoRange'][i] * 100)) else: self.ctrl[i].manualRadio.setChecked(True) self.ctrl[i].mouseCheck.setChecked(state['mouseEnabled'][i]) ## Update combo to show currently linked view c = self.ctrl[i].linkCombo c.blockSignals(True) try: view = state['linkedViews'][i] ## will always be string or None if view is None: view = '' ind = c.findText(view) if ind == -1: ind = 0 c.setCurrentIndex(ind) finally: c.blockSignals(False) self.ctrl[i].autoPanCheck.setChecked(state['autoPan'][i]) self.ctrl[i].visibleOnlyCheck.setChecked(state['autoVisibleOnly'][i]) xy = ['x', 'y'][i] self.ctrl[i].invertCheck.setChecked(state.get(xy+'Inverted', False)) self.valid = True def popup(self, *args): if not self.valid: self.updateState() QtWidgets.QMenu.popup(self, *args) def autoRange(self): self.view().autoRange() ## don't let signal call this directly--it'll add an unwanted argument def xMouseToggled(self, b): self.view().setMouseEnabled(x=b) def xManualClicked(self): self.view().enableAutoRange(ViewBox.XAxis, False) def xRangeTextChanged(self): self.ctrl[0].manualRadio.setChecked(True) self.view().setXRange(*self._validateRangeText(0), padding=0) def xAutoClicked(self): val = self.ctrl[0].autoPercentSpin.value() * 0.01 self.view().enableAutoRange(ViewBox.XAxis, val) def xAutoSpinChanged(self, val): self.ctrl[0].autoRadio.setChecked(True) self.view().enableAutoRange(ViewBox.XAxis, val*0.01) def xLinkComboChanged(self, ind): self.view().setXLink(str(self.ctrl[0].linkCombo.currentText())) def xAutoPanToggled(self, b): self.view().setAutoPan(x=b) def xVisibleOnlyToggled(self, b): self.view().setAutoVisible(x=b) def yMouseToggled(self, b): self.view().setMouseEnabled(y=b) def yManualClicked(self): self.view().enableAutoRange(ViewBox.YAxis, False) def yRangeTextChanged(self): self.ctrl[1].manualRadio.setChecked(True) self.view().setYRange(*self._validateRangeText(1), padding=0) def yAutoClicked(self): val = self.ctrl[1].autoPercentSpin.value() * 0.01 self.view().enableAutoRange(ViewBox.YAxis, val) def yAutoSpinChanged(self, val): self.ctrl[1].autoRadio.setChecked(True) self.view().enableAutoRange(ViewBox.YAxis, val*0.01) def yLinkComboChanged(self, ind): self.view().setYLink(str(self.ctrl[1].linkCombo.currentText())) def yAutoPanToggled(self, b): self.view().setAutoPan(y=b) def yVisibleOnlyToggled(self, b): self.view().setAutoVisible(y=b) def yInvertToggled(self, b): self.view().invertY(b) def xInvertToggled(self, b): self.view().invertX(b) def exportMethod(self): act = self.sender() self.exportMethods[str(act.text())]() def set3ButtonMode(self): self.view().setLeftButtonAction('pan') def set1ButtonMode(self): self.view().setLeftButtonAction('rect') def setViewList(self, views): names = [''] self.viewMap.clear() ## generate list of views to show in the link combo for v in views: name = v.name if name is None: ## unnamed views do not show up in the view list (although they are linkable) continue names.append(name) self.viewMap[name] = v for i in [0,1]: c = self.ctrl[i].linkCombo current = c.currentText() c.blockSignals(True) changed = True try: c.clear() for name in names: c.addItem(name) if name == current: changed = False c.setCurrentIndex(c.count()-1) finally: c.blockSignals(False) if changed: c.setCurrentIndex(0) c.currentIndexChanged.emit(c.currentIndex()) def _validateRangeText(self, axis): """Validate range text inputs. Return current value(s) if invalid.""" inputs = (self.ctrl[axis].minText.text(), self.ctrl[axis].maxText.text()) vals = self.view().viewRange()[axis] for i, text in enumerate(inputs): try: vals[i] = float(text) except ValueError: # could not convert string to float pass return vals from .ViewBox import ViewBox ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ViewBox/__init__.py������������������������������0000664�0000000�0000000�00000000064�14210455074�0026151�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ViewBox import ViewBox __all__ = ['ViewBox'] ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui����������������������0000664�0000000�0000000�00000013005�14210455074�0027663�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Form 0 0 186 154 200 16777215 PyQtGraph 0 0 Link Axis: <html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html> QComboBox::AdjustToContents true <html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html> % 1 100 1 100 <html><head/><body><p>Automatically resize this axis whenever the displayed data is changed.</p></body></html> Auto true <html><head/><body><p>Set the range for this axis manually. This disables automatic scaling. </p></body></html> Manual <html><head/><body><p>Minimum value to display for this axis.</p></body></html> 0 <html><head/><body><p>Maximum value to display for this axis.</p></body></html> 0 <html><head/><body><p>Inverts the display of this axis. (+y points downward instead of upward)</p></body></html> Invert Axis <html><head/><body><p>Enables mouse interaction (panning, scaling) for this axis.</p></body></html> Mouse Enabled true <html><head/><body><p>When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.</p></body></html> Visible Data Only <html><head/><body><p>When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.</p></body></html> Auto Pan Only ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt5.py����������������0000664�0000000�0000000�00000012667�14210455074�0031055�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ # Form implementation generated from reading ui file './pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui' # # Created: Wed Mar 26 15:09:28 2014 # by: PyQt5 UI code generator 5.0.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(186, 154) Form.setMaximumSize(QtCore.QSize(200, 16777215)) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.label = QtWidgets.QLabel(Form) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 7, 0, 1, 2) self.linkCombo = QtWidgets.QComboBox(Form) self.linkCombo.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) self.linkCombo.setObjectName("linkCombo") self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) self.autoPercentSpin = QtWidgets.QSpinBox(Form) self.autoPercentSpin.setEnabled(True) self.autoPercentSpin.setMinimum(1) self.autoPercentSpin.setMaximum(100) self.autoPercentSpin.setSingleStep(1) self.autoPercentSpin.setProperty("value", 100) self.autoPercentSpin.setObjectName("autoPercentSpin") self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) self.autoRadio = QtWidgets.QRadioButton(Form) self.autoRadio.setChecked(True) self.autoRadio.setObjectName("autoRadio") self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) self.manualRadio = QtWidgets.QRadioButton(Form) self.manualRadio.setObjectName("manualRadio") self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) self.minText = QtWidgets.QLineEdit(Form) self.minText.setObjectName("minText") self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) self.maxText = QtWidgets.QLineEdit(Form) self.maxText.setObjectName("maxText") self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) self.invertCheck = QtWidgets.QCheckBox(Form) self.invertCheck.setObjectName("invertCheck") self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) self.mouseCheck = QtWidgets.QCheckBox(Form) self.mouseCheck.setChecked(True) self.mouseCheck.setObjectName("mouseCheck") self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) self.visibleOnlyCheck = QtWidgets.QCheckBox(Form) self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) self.autoPanCheck = QtWidgets.QCheckBox(Form) self.autoPanCheck.setObjectName("autoPanCheck") self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.label.setText(_translate("Form", "Link Axis:")) self.linkCombo.setToolTip(_translate("Form", "

      Links this axis with another view. When linked, both views will display the same data range.

      ")) self.autoPercentSpin.setToolTip(_translate("Form", "

      Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

      ")) self.autoPercentSpin.setSuffix(_translate("Form", "%")) self.autoRadio.setToolTip(_translate("Form", "

      Automatically resize this axis whenever the displayed data is changed.

      ")) self.autoRadio.setText(_translate("Form", "Auto")) self.manualRadio.setToolTip(_translate("Form", "

      Set the range for this axis manually. This disables automatic scaling.

      ")) self.manualRadio.setText(_translate("Form", "Manual")) self.minText.setToolTip(_translate("Form", "

      Minimum value to display for this axis.

      ")) self.minText.setText(_translate("Form", "0")) self.maxText.setToolTip(_translate("Form", "

      Maximum value to display for this axis.

      ")) self.maxText.setText(_translate("Form", "0")) self.invertCheck.setToolTip(_translate("Form", "

      Inverts the display of this axis. (+y points downward instead of upward)

      ")) self.invertCheck.setText(_translate("Form", "Invert Axis")) self.mouseCheck.setToolTip(_translate("Form", "

      Enables mouse interaction (panning, scaling) for this axis.

      ")) self.mouseCheck.setText(_translate("Form", "Mouse Enabled")) self.visibleOnlyCheck.setToolTip(_translate("Form", "

      When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

      ")) self.visibleOnlyCheck.setText(_translate("Form", "Visible Data Only")) self.autoPanCheck.setToolTip(_translate("Form", "

      When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

      ")) self.autoPanCheck.setText(_translate("Form", "Auto Pan Only")) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt6.py000066400000000000000000000130061421045507400310420ustar00rootroot00000000000000# Form implementation generated from reading ui file '../pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui' # # Created by: PyQt6 UI code generator 6.1.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(186, 154) Form.setMaximumSize(QtCore.QSize(200, 16777215)) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.label = QtWidgets.QLabel(Form) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 7, 0, 1, 2) self.linkCombo = QtWidgets.QComboBox(Form) self.linkCombo.setSizeAdjustPolicy(QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents) self.linkCombo.setObjectName("linkCombo") self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) self.autoPercentSpin = QtWidgets.QSpinBox(Form) self.autoPercentSpin.setEnabled(True) self.autoPercentSpin.setMinimum(1) self.autoPercentSpin.setMaximum(100) self.autoPercentSpin.setSingleStep(1) self.autoPercentSpin.setProperty("value", 100) self.autoPercentSpin.setObjectName("autoPercentSpin") self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) self.autoRadio = QtWidgets.QRadioButton(Form) self.autoRadio.setChecked(True) self.autoRadio.setObjectName("autoRadio") self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) self.manualRadio = QtWidgets.QRadioButton(Form) self.manualRadio.setObjectName("manualRadio") self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) self.minText = QtWidgets.QLineEdit(Form) self.minText.setObjectName("minText") self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) self.maxText = QtWidgets.QLineEdit(Form) self.maxText.setObjectName("maxText") self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) self.invertCheck = QtWidgets.QCheckBox(Form) self.invertCheck.setObjectName("invertCheck") self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) self.mouseCheck = QtWidgets.QCheckBox(Form) self.mouseCheck.setChecked(True) self.mouseCheck.setObjectName("mouseCheck") self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) self.visibleOnlyCheck = QtWidgets.QCheckBox(Form) self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) self.autoPanCheck = QtWidgets.QCheckBox(Form) self.autoPanCheck.setObjectName("autoPanCheck") self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.label.setText(_translate("Form", "Link Axis:")) self.linkCombo.setToolTip(_translate("Form", "

      Links this axis with another view. When linked, both views will display the same data range.

      ")) self.autoPercentSpin.setToolTip(_translate("Form", "

      Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

      ")) self.autoPercentSpin.setSuffix(_translate("Form", "%")) self.autoRadio.setToolTip(_translate("Form", "

      Automatically resize this axis whenever the displayed data is changed.

      ")) self.autoRadio.setText(_translate("Form", "Auto")) self.manualRadio.setToolTip(_translate("Form", "

      Set the range for this axis manually. This disables automatic scaling.

      ")) self.manualRadio.setText(_translate("Form", "Manual")) self.minText.setToolTip(_translate("Form", "

      Minimum value to display for this axis.

      ")) self.minText.setText(_translate("Form", "0")) self.maxText.setToolTip(_translate("Form", "

      Maximum value to display for this axis.

      ")) self.maxText.setText(_translate("Form", "0")) self.invertCheck.setToolTip(_translate("Form", "

      Inverts the display of this axis. (+y points downward instead of upward)

      ")) self.invertCheck.setText(_translate("Form", "Invert Axis")) self.mouseCheck.setToolTip(_translate("Form", "

      Enables mouse interaction (panning, scaling) for this axis.

      ")) self.mouseCheck.setText(_translate("Form", "Mouse Enabled")) self.visibleOnlyCheck.setToolTip(_translate("Form", "

      When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

      ")) self.visibleOnlyCheck.setText(_translate("Form", "Visible Data Only")) self.autoPanCheck.setToolTip(_translate("Form", "

      When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

      ")) self.autoPanCheck.setText(_translate("Form", "Auto Pan Only")) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside2.py000066400000000000000000000126641421045507400313470ustar00rootroot00000000000000 # Form implementation generated from reading ui file './pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui' # # Created: Wed Mar 26 15:09:28 2014 # by: PyQt5 UI code generator 5.0.1 # # WARNING! All changes made in this file will be lost! from PySide2 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(186, 154) Form.setMaximumSize(QtCore.QSize(200, 16777215)) self.gridLayout = QtWidgets.QGridLayout(Form) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.label = QtWidgets.QLabel(Form) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 7, 0, 1, 2) self.linkCombo = QtWidgets.QComboBox(Form) self.linkCombo.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) self.linkCombo.setObjectName("linkCombo") self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) self.autoPercentSpin = QtWidgets.QSpinBox(Form) self.autoPercentSpin.setEnabled(True) self.autoPercentSpin.setMinimum(1) self.autoPercentSpin.setMaximum(100) self.autoPercentSpin.setSingleStep(1) self.autoPercentSpin.setProperty("value", 100) self.autoPercentSpin.setObjectName("autoPercentSpin") self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) self.autoRadio = QtWidgets.QRadioButton(Form) self.autoRadio.setChecked(True) self.autoRadio.setObjectName("autoRadio") self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) self.manualRadio = QtWidgets.QRadioButton(Form) self.manualRadio.setObjectName("manualRadio") self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) self.minText = QtWidgets.QLineEdit(Form) self.minText.setObjectName("minText") self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) self.maxText = QtWidgets.QLineEdit(Form) self.maxText.setObjectName("maxText") self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) self.invertCheck = QtWidgets.QCheckBox(Form) self.invertCheck.setObjectName("invertCheck") self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) self.mouseCheck = QtWidgets.QCheckBox(Form) self.mouseCheck.setChecked(True) self.mouseCheck.setObjectName("mouseCheck") self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) self.visibleOnlyCheck = QtWidgets.QCheckBox(Form) self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) self.autoPanCheck = QtWidgets.QCheckBox(Form) self.autoPanCheck.setObjectName("autoPanCheck") self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Form")) self.label.setText(_translate("Form", "Link Axis:")) self.linkCombo.setToolTip(_translate("Form", "

      Links this axis with another view. When linked, both views will display the same data range.

      ")) self.autoPercentSpin.setToolTip(_translate("Form", "

      Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

      ")) self.autoPercentSpin.setSuffix(_translate("Form", "%")) self.autoRadio.setToolTip(_translate("Form", "

      Automatically resize this axis whenever the displayed data is changed.

      ")) self.autoRadio.setText(_translate("Form", "Auto")) self.manualRadio.setToolTip(_translate("Form", "

      Set the range for this axis manually. This disables automatic scaling.

      ")) self.manualRadio.setText(_translate("Form", "Manual")) self.minText.setToolTip(_translate("Form", "

      Minimum value to display for this axis.

      ")) self.minText.setText(_translate("Form", "0")) self.maxText.setToolTip(_translate("Form", "

      Maximum value to display for this axis.

      ")) self.maxText.setText(_translate("Form", "0")) self.invertCheck.setToolTip(_translate("Form", "

      Inverts the display of this axis. (+y points downward instead of upward)

      ")) self.invertCheck.setText(_translate("Form", "Invert Axis")) self.mouseCheck.setToolTip(_translate("Form", "

      Enables mouse interaction (panning, scaling) for this axis.

      ")) self.mouseCheck.setText(_translate("Form", "Mouse Enabled")) self.visibleOnlyCheck.setToolTip(_translate("Form", "

      When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

      ")) self.visibleOnlyCheck.setText(_translate("Form", "Visible Data Only")) self.autoPanCheck.setToolTip(_translate("Form", "

      When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

      ")) self.autoPanCheck.setText(_translate("Form", "Auto Pan Only")) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside6.py000066400000000000000000000147431421045507400313530ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'axisCtrlTemplate.ui' ## ## Created by: Qt User Interface Compiler version 6.1.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(186, 154) Form.setMaximumSize(QSize(200, 16777215)) self.gridLayout = QGridLayout(Form) self.gridLayout.setSpacing(0) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName(u"gridLayout") self.label = QLabel(Form) self.label.setObjectName(u"label") self.gridLayout.addWidget(self.label, 7, 0, 1, 2) self.linkCombo = QComboBox(Form) self.linkCombo.setObjectName(u"linkCombo") self.linkCombo.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) self.autoPercentSpin = QSpinBox(Form) self.autoPercentSpin.setObjectName(u"autoPercentSpin") self.autoPercentSpin.setEnabled(True) self.autoPercentSpin.setMinimum(1) self.autoPercentSpin.setMaximum(100) self.autoPercentSpin.setSingleStep(1) self.autoPercentSpin.setValue(100) self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) self.autoRadio = QRadioButton(Form) self.autoRadio.setObjectName(u"autoRadio") self.autoRadio.setChecked(True) self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) self.manualRadio = QRadioButton(Form) self.manualRadio.setObjectName(u"manualRadio") self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) self.minText = QLineEdit(Form) self.minText.setObjectName(u"minText") self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) self.maxText = QLineEdit(Form) self.maxText.setObjectName(u"maxText") self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) self.invertCheck = QCheckBox(Form) self.invertCheck.setObjectName(u"invertCheck") self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) self.mouseCheck = QCheckBox(Form) self.mouseCheck.setObjectName(u"mouseCheck") self.mouseCheck.setChecked(True) self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) self.visibleOnlyCheck = QCheckBox(Form) self.visibleOnlyCheck.setObjectName(u"visibleOnlyCheck") self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) self.autoPanCheck = QCheckBox(Form) self.autoPanCheck.setObjectName(u"autoPanCheck") self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) self.label.setText(QCoreApplication.translate("Form", u"Link Axis:", None)) #if QT_CONFIG(tooltip) self.linkCombo.setToolTip(QCoreApplication.translate("Form", u"

      Links this axis with another view. When linked, both views will display the same data range.

      ", None)) #endif // QT_CONFIG(tooltip) #if QT_CONFIG(tooltip) self.autoPercentSpin.setToolTip(QCoreApplication.translate("Form", u"

      Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

      ", None)) #endif // QT_CONFIG(tooltip) self.autoPercentSpin.setSuffix(QCoreApplication.translate("Form", u"%", None)) #if QT_CONFIG(tooltip) self.autoRadio.setToolTip(QCoreApplication.translate("Form", u"

      Automatically resize this axis whenever the displayed data is changed.

      ", None)) #endif // QT_CONFIG(tooltip) self.autoRadio.setText(QCoreApplication.translate("Form", u"Auto", None)) #if QT_CONFIG(tooltip) self.manualRadio.setToolTip(QCoreApplication.translate("Form", u"

      Set the range for this axis manually. This disables automatic scaling.

      ", None)) #endif // QT_CONFIG(tooltip) self.manualRadio.setText(QCoreApplication.translate("Form", u"Manual", None)) #if QT_CONFIG(tooltip) self.minText.setToolTip(QCoreApplication.translate("Form", u"

      Minimum value to display for this axis.

      ", None)) #endif // QT_CONFIG(tooltip) self.minText.setText(QCoreApplication.translate("Form", u"0", None)) #if QT_CONFIG(tooltip) self.maxText.setToolTip(QCoreApplication.translate("Form", u"

      Maximum value to display for this axis.

      ", None)) #endif // QT_CONFIG(tooltip) self.maxText.setText(QCoreApplication.translate("Form", u"0", None)) #if QT_CONFIG(tooltip) self.invertCheck.setToolTip(QCoreApplication.translate("Form", u"

      Inverts the display of this axis. (+y points downward instead of upward)

      ", None)) #endif // QT_CONFIG(tooltip) self.invertCheck.setText(QCoreApplication.translate("Form", u"Invert Axis", None)) #if QT_CONFIG(tooltip) self.mouseCheck.setToolTip(QCoreApplication.translate("Form", u"

      Enables mouse interaction (panning, scaling) for this axis.

      ", None)) #endif // QT_CONFIG(tooltip) self.mouseCheck.setText(QCoreApplication.translate("Form", u"Mouse Enabled", None)) #if QT_CONFIG(tooltip) self.visibleOnlyCheck.setToolTip(QCoreApplication.translate("Form", u"

      When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

      ", None)) #endif // QT_CONFIG(tooltip) self.visibleOnlyCheck.setText(QCoreApplication.translate("Form", u"Visible Data Only", None)) #if QT_CONFIG(tooltip) self.autoPanCheck.setToolTip(QCoreApplication.translate("Form", u"

      When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

      ", None)) #endif // QT_CONFIG(tooltip) self.autoPanCheck.setText(QCoreApplication.translate("Form", u"Auto Pan Only", None)) # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsItems/__init__.py000066400000000000000000000011111421045507400245600ustar00rootroot00000000000000### just import everything from sub-modules #import os #d = os.path.split(__file__)[0] #files = [] #for f in os.listdir(d): #if os.path.isdir(os.path.join(d, f)): #files.append(f) #elif f[-3:] == '.py' and f != '__init__.py': #files.append(f[:-3]) #for modName in files: #mod = __import__(modName, globals(), locals(), fromlist=['*']) #if hasattr(mod, '__all__'): #names = mod.__all__ #else: #names = [n for n in dir(mod) if n[0] != '_'] #for k in names: ##print modName, k #globals()[k] = getattr(mod, k) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/graphicsWindows.py000066400000000000000000000070611421045507400234040ustar00rootroot00000000000000""" DEPRECATED: The classes below are convenience classes that create a new window containting a single, specific widget. These classes are now unnecessary because it is possible to place any widget into its own window by simply calling its show() method. """ __all__ = ['GraphicsWindow', 'TabWindow', 'PlotWindow', 'ImageWindow'] import warnings from .imageview import * from .Qt import QtCore, QtWidgets, mkQApp from .widgets.GraphicsLayoutWidget import GraphicsLayoutWidget from .widgets.PlotWidget import * class GraphicsWindow(GraphicsLayoutWidget): """ (deprecated; use :class:`~pyqtgraph.GraphicsLayoutWidget` instead) Convenience subclass of :class:`~pyqtgraph.GraphicsLayoutWidget`. This class is intended for use from the interactive python prompt. """ def __init__(self, title=None, size=(800,600), **kargs): warnings.warn( 'GraphicsWindow is deprecated, use GraphicsLayoutWidget instead,' 'will be removed in 0.13', DeprecationWarning, stacklevel=2 ) mkQApp() GraphicsLayoutWidget.__init__(self, **kargs) self.resize(*size) if title is not None: self.setWindowTitle(title) self.show() class TabWindow(QtWidgets.QMainWindow): """ (deprecated) """ def __init__(self, title=None, size=(800,600)): warnings.warn( 'TabWindow is deprecated, will be removed in 0.13', DeprecationWarning, stacklevel=2 ) mkQApp() QtWidgets.QMainWindow.__init__(self) self.resize(*size) self.cw = QtWidgets.QTabWidget() self.setCentralWidget(self.cw) if title is not None: self.setWindowTitle(title) self.show() def __getattr__(self, attr): return getattr(self.cw, attr) class PlotWindow(PlotWidget): sigClosed = QtCore.Signal(object) """ (deprecated; use :class:`~pyqtgraph.PlotWidget` instead) """ def __init__(self, title=None, **kargs): warnings.warn( 'PlotWindow is deprecated, use PlotWidget instead,' 'will be removed in 0.13', DeprecationWarning, stacklevel=2 ) mkQApp() self.win = QtWidgets.QMainWindow() PlotWidget.__init__(self, **kargs) self.win.setCentralWidget(self) for m in ['resize']: setattr(self, m, getattr(self.win, m)) if title is not None: self.win.setWindowTitle(title) self.win.show() def closeEvent(self, event): PlotWidget.closeEvent(self, event) self.sigClosed.emit(self) class ImageWindow(ImageView): sigClosed = QtCore.Signal(object) """ (deprecated; use :class:`~pyqtgraph.ImageView` instead) """ def __init__(self, *args, **kargs): warnings.warn( 'ImageWindow is deprecated, use ImageView instead' 'will be removed in 0.13', DeprecationWarning, stacklevel=2 ) mkQApp() self.win = QtWidgets.QMainWindow() self.win.resize(800,600) if 'title' in kargs: self.win.setWindowTitle(kargs['title']) del kargs['title'] ImageView.__init__(self, self.win) if len(args) > 0 or len(kargs) > 0: self.setImage(*args, **kargs) self.win.setCentralWidget(self) for m in ['resize']: setattr(self, m, getattr(self.win, m)) self.win.show() def closeEvent(self, event): ImageView.closeEvent(self, event) self.sigClosed.emit(self) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/icons/000077500000000000000000000000001421045507400207665ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/icons/__init__.py000066400000000000000000000036561421045507400231110ustar00rootroot00000000000000import os.path as op import warnings from ..Qt import QtGui, QtWidgets __all__ = ['getGraphIcon', 'getGraphPixmap'] _ICON_REGISTRY = {} class GraphIcon: """An icon place holder for lazy loading of QIcons The icon must reside in the icons folder and the path refers to the full name including suffix of the icon file, e.g.: tiny = GraphIcon("tiny.png") Icons can be later retrieved via the function `getGraphIcon` and providing the name: tiny = getGraphIcon("tiny") """ def __init__(self, path): self._path = path name = path.split('.')[0] _ICON_REGISTRY[name] = self self._icon = None def _build_qicon(self): icon = QtGui.QIcon(op.join(op.dirname(__file__), self._path)) name = self._path.split('.')[0] _ICON_REGISTRY[name] = icon self._icon = icon @property def qicon(self): if self._icon is None: self._build_qicon() return self._icon def getGraphIcon(name): """Return a `PyQtGraph` icon from the registry by `name`""" icon = _ICON_REGISTRY[name] if isinstance(icon, GraphIcon): icon = icon.qicon _ICON_REGISTRY[name] = icon return icon def getGraphPixmap(name, size=(20, 20)): """Return a `QPixmap` from the registry by `name`""" icon = getGraphIcon(name) return icon.pixmap(*size) def getPixmap(name, size=(20, 20)): """Historic `getPixmap` function (eg. getPixmap('auto') loads pyqtgraph/icons/auto.png) """ warnings.warn( "'getPixmap' is deprecated and will be removed soon, " "please use `getGraphPixmap` in the future", DeprecationWarning, stacklevel=2) return getGraphPixmap(name, size=size) # Note: List all graph icons here ... auto = GraphIcon("auto.png") ctrl = GraphIcon("ctrl.png") default = GraphIcon("default.png") invisibleEye = GraphIcon("invisibleEye.svg") lock = GraphIcon("lock.png") pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/icons/auto.png000066400000000000000000000017761421045507400224570ustar00rootroot00000000000000‰PNG  IHDR szzτsBIT|dˆ pHYsKg<tEXtSoftwarewww.inkscape.org›ξ<{IDATX…Ν—ΏOγHΗΏCbσc«]$+HV€― Ί"Ё˜ βGώ­ξΚΠ€΄ΕvΫRΈ!D6:θψ!$t,‡ΆB–¨ˆ"νV!Θ±½kδ‰qlV9q―ς|ίΌyŸρ›ΡΜΓ0`š$Io ΓψΐFΌBs¬ΰ+€3BΘgAΎ›bH’τΑ0Œ/ή4)©“ύ „|‘ψP,³„‚΅—¦i¨V«MΙ …ΰσω(’‘HΆΆΆΊj΅Ϊ7―ΰϊϊ’(BQ¨ͺΪ†aΐqrΉϊϊϊLω!δΩάάόLωΛLžΟη‘iZΓγρ8†‡‡)νττggg γ|>VWW­Ÿό„Q³%Š’krX[[C$‘΄σσs$“Ι†qš¦AE¬¬¬˜ίΰ½ιTΕ59ΟσΆδ022‚h4κ―(Šu’£-ψ΅ΥͺΥͺ§šΟΟΟ;ϊ]γUU΅.ξW- ‡ΓH₯RŽώιιiη ‰g€Σi°,ϋΨΥj6ΐt:ύί,,,PνννmΫΊρR†ίˆΕbˆΕb”&IŠΕ"₯ app°ωυ³/•J899,ΛΆ­»΄΄Τ\–em΅•e†a \.γθθˆςMMM! 5 •J!Sš$Iίυeƒ˜™™i@ύήΏΈΈ ίήή*• ΥΗλbtˆD"ΰyž¬³€ϋϋ{μξξRZ4΅ΏPΏψTUΕΞΎ­_}oΑίΘΩΪڊL&Ci„μοο» “““X^^Ά•Η3ΐΔΔ:::θΏ===žX–… X__wμΣ°―ζVΗ?ΠΫΫ‹x!~ …B[ ΒPU5lšζoΐw6}IY!ΔΟ‘P(YH&“…Ÿͺ½ Γ ŸΟΫQ’$œNη›βc(JŠƒƒƒοΣ@ΐΥΥŠ’ iΕbѷۍΟηC–ez{{-sVρƒsjjjI1a_XXΰφφ–R©dKp€R©D6›εδδ„@ @{{;@Pr!‚–£’(†Qs¦§§ικκͺΔ0 E©6υ;€­FMΣ^μΆτχχsxxHggη³ΆΡΡΡΚsSSΣΫtwwΣΧΧG"‘xαrΉ{βΧ¨\΅ŒgggάίίγχϋI$μοο322Βππ0ΟΏ‡₯₯%2™ §§§uΤ\»»;VVVπϋύ•¬―ε-ΨΪΪb||ά^€έέ]’Ρ(ΧΧΧ―Π(Δ«Ηπψψ˜‰‰ Βα0³³³Δb1l…¨™Υ* \\\TήonnΨΩΩ‘΅΅΅&D4ύ¦œψκ‘₯T*E$±m%ΎΐnˆΊμ„¨ΐ.ˆ†μ€hΰ%ˆL&C,css“xΖχΰυ•™8XϋόgVf90ΫyΖX“Na Γ*0Lp` m<¦bΑ0ϊΑH˜o?9K²PœΗbbpra ±*ρ-‚ε₯±hΠ+£@?Ί‘0˜ρ•ΐd%N&iΎ«i(‹…>°€ˆΨλΏ εI»Wρ[œ š°@χζ θF"`a8&[ΨΠ[Ζ2'Ω―Vή§5=O&«2γ˜kOcθΖYΑΑυŞπΝŞ0hΘς7‰Rˆb’1ί~yλ,|‘]€―#ηαƒά4/YPΛwXΖ'\όυgφŠRE©ž«φŸ›o 6τ7 ξγ„$“-`θ«pήTΤxoŽ65©:=LcΝ.’4r—}Ν1 €Ω6Šι·†Oβ“RuYΐΜ£xrέlΒ΄b60€?†°°`pQEO£c*›βkσl+˜ΉšqΫ₯ΰ‘HžAΡsn³Ιߟ›/[}&₯Νe&Nς‰λ˜daY[‘„· &s(4ρx£!Γ‰^β†p‘…)­ζ"]g³π]‰mΘD= "Ÿ$Pbœnΰ2‘yΉ£ŽύTSή=ŠJwΆϊ…³―¦œ€%A}lBΚοtXΰo)[Q ΩΘ2+ێ e›75ή|x§ͺξ€›Zδμ‘yΖrέ£ίΒε«ζσβ_λφCo»|)e‹—¦ΦΓV^μbWyN3gΑ?ΡΌςCͺjWšVθŒT\kΗ*ψ/~žjΊW ΘIENDB`‚pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/icons/icons.svg000066400000000000000000000143501421045507400226250ustar00rootroot00000000000000 image/svg+xml A pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/icons/invisibleEye.svg000066400000000000000000000044021421045507400241360ustar00rootroot00000000000000 pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/icons/lock.png000066400000000000000000000016211421045507400224240ustar00rootroot00000000000000‰PNG  IHDR szzτsBIT|dˆ pHYsKg<tEXtSoftwarewww.inkscape.org›ξ<IDATX…ν—½k#GΖ£έ#V²”Β „Œ· *‚V‰]šό q‡1u*ζ8T-&Ζ X\₯NU‹αJ…­ώξŠ@‹68*¬σ’F»RŠάξΙ­|>%N᧚χcžy–™Ωy_1™Lπ`šζΧ“Ιδ5π= Qƒΐ;ΰ­β—\.χ§žΣ4_L&“7@|A‹ΞB_ρc.—kψΖK!ΔoΣYλbΫφBVTUI’ξω„/sΉ\Cœœœ¬:Žσπΐεε%υzΛ²F  ( š¦‘ΟηY[[σά}!Δ7φφφOBˆΌΕ Γΰζζ†ρx<—xii Y–q'0o<Σοχ9??'“Ι‹ΕΒΐ8$„Θz‰υzΧuΙ‰΄Z-:N‡V«E₯Rauu5pλΊΤλυiΧw!ΰ[/hYV Αξξ.gggδσyι4²,#Λ2ιtšB‘@»έfgg'Γ²¬ιΜ†ψxΥlΫάσB‘ΐαα!ͺͺϊΎn·K·ΫυνH$ΒΡΡ…Ba&Οh4š>άΡP άH&“†αΫΝf]ΧΙf³d³Yt]§ΩlϊqΓ0H&“‘ζAJ₯αpΣ4)‹τz=?ήλυ(‹˜¦ @8¦T*-N€λ8ŽCΉ\ž™W.—ύαΝωbΡh”T*ΐΕΕƒΑ`fξ`0 ΣιJ₯ˆFηΙη H$ώψϊϊz.αΥΥΥ?Ξ}΄!Δ\’/™ϋ 3πoB nmm±··ηΫ›››œžž――ϋγύύ}Ži·ΫŸ/@QͺΥ*ρψ§Χyyy™L&(`T«Ut]Ÿω“›Ή±XμήβE<χŸΟπ_αYΐ³€g!ώnPUEQόΐp8\HU<‡Ύ­(ΚtUυ!$„ψ@’$4MσmΫ¦V«Ν­xƒΰ8΅Zν^‘iΪtπ^4_Wπ©,ŸŒWVVˆD"pwwΗνν­oK’D₯R™ξ ~~ςΖδι[³Esκα)ΪσΏZMMδ»R0ΩIENDB`‚pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/imageview/000077500000000000000000000000001421045507400216305ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/imageview/ImageView.py000066400000000000000000000775731421045507400241020ustar00rootroot00000000000000""" ImageView.py - Widget for basic image dispay and analysis Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. Widget used for displaying 2D or 3D data. Features: - float or int (including 16-bit int) image display via ImageItem - zoom/pan via GraphicsView - black/white level controls - time slider for 3D data sets - ROI plotting - Image normalization through a variety of methods """ import importlib import os from math import log10 from time import perf_counter import numpy as np from .. import functions as fn from ..Qt import QT_LIB, QtCore, QtGui, QtWidgets ui_template = importlib.import_module( f'.ImageViewTemplate_{QT_LIB.lower()}', package=__package__) from .. import debug as debug from .. import getConfigOption from ..graphicsItems.GradientEditorItem import addGradientListToDocstring from ..graphicsItems.ImageItem import * from ..graphicsItems.InfiniteLine import * from ..graphicsItems.LinearRegionItem import * from ..graphicsItems.ROI import * from ..graphicsItems.ViewBox import * from ..graphicsItems.VTickGroup import VTickGroup from ..SignalProxy import SignalProxy try: from bottleneck import nanmax, nanmin except ImportError: from numpy import nanmax, nanmin translate = QtCore.QCoreApplication.translate class PlotROI(ROI): def __init__(self, size): ROI.__init__(self, pos=[0,0], size=size) #, scaleSnap=True, translateSnap=True) self.addScaleHandle([1, 1], [0, 0]) self.addRotateHandle([0, 0], [0.5, 0.5]) class ImageView(QtWidgets.QWidget): """ Widget used for display and analysis of image data. Implements many features: * Displays 2D and 3D image data. For 3D data, a z-axis slider is displayed allowing the user to select which frame is displayed. * Displays histogram of image data with movable region defining the dark/light levels * Editable gradient provides a color lookup table * Frame slider may also be moved using left/right arrow keys as well as pgup, pgdn, home, and end. * Basic analysis features including: * ROI and embedded plot for measuring image values across frames * Image normalization / background subtraction Basic Usage:: imv = pg.ImageView() imv.show() imv.setImage(data) **Keyboard interaction** * left/right arrows step forward/backward 1 frame when pressed, seek at 20fps when held. * up/down arrows seek at 100fps * pgup/pgdn seek at 1000fps * home/end seek immediately to the first/last frame * space begins playing frames. If time values (in seconds) are given for each frame, then playback is in realtime. """ sigTimeChanged = QtCore.Signal(object, object) sigProcessingChanged = QtCore.Signal(object) def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, levelMode='mono', *args): """ By default, this class creates an :class:`ImageItem ` to display image data and a :class:`ViewBox ` to contain the ImageItem. ============= ========================================================= **Arguments** parent (QWidget) Specifies the parent widget to which this ImageView will belong. If None, then the ImageView is created with no parent. name (str) The name used to register both the internal ViewBox and the PlotItem used to display ROI data. See the *name* argument to :func:`ViewBox.__init__() `. view (ViewBox or PlotItem) If specified, this will be used as the display area that contains the displayed image. Any :class:`ViewBox `, :class:`PlotItem `, or other compatible object is acceptable. imageItem (ImageItem) If specified, this object will be used to display the image. Must be an instance of ImageItem or other compatible object. levelMode See the *levelMode* argument to :func:`HistogramLUTItem.__init__() ` ============= ========================================================= Note: to display axis ticks inside the ImageView, instantiate it with a PlotItem instance as its view:: pg.ImageView(view=pg.PlotItem()) """ QtWidgets.QWidget.__init__(self, parent, *args) self._imageLevels = None # [(min, max), ...] per channel image metrics self.levelMin = None # min / max levels across all channels self.levelMax = None self.name = name self.image = None self.axes = {} self.imageDisp = None self.ui = ui_template.Ui_Form() self.ui.setupUi(self) self.scene = self.ui.graphicsView.scene() self.ignorePlaying = False if view is None: self.view = ViewBox() else: self.view = view self.ui.graphicsView.setCentralItem(self.view) self.view.setAspectLocked(True) self.view.invertY() self.menu = None self.ui.normGroup.hide() self.roi = PlotROI(10) self.roi.setZValue(20) self.view.addItem(self.roi) self.roi.hide() self.normRoi = PlotROI(10) self.normRoi.setPen('y') self.normRoi.setZValue(20) self.view.addItem(self.normRoi) self.normRoi.hide() self.roiCurves = [] self.timeLine = InfiniteLine(0, movable=True) if getConfigOption('background')=='w': self.timeLine.setPen((20, 80,80, 200)) else: self.timeLine.setPen((255, 255, 0, 200)) self.timeLine.setZValue(1) self.ui.roiPlot.addItem(self.timeLine) self.ui.splitter.setSizes([self.height()-35, 35]) # init imageItem and histogram if imageItem is None: self.imageItem = ImageItem() else: self.imageItem = imageItem self.setImage(imageItem.image, autoRange=False, autoLevels=False, transform=imageItem.transform()) self.view.addItem(self.imageItem) self.currentIndex = 0 self.ui.histogram.setImageItem(self.imageItem) self.ui.histogram.setLevelMode(levelMode) # make splitter an unchangeable small grey line: s = self.ui.splitter s.handle(1).setEnabled(False) s.setStyleSheet("QSplitter::handle{background-color: grey}") s.setHandleWidth(2) self.ui.roiPlot.hideAxis('left') self.frameTicks = VTickGroup(yrange=[0.8, 1], pen=0.4) self.ui.roiPlot.addItem(self.frameTicks, ignoreBounds=True) self.keysPressed = {} self.playTimer = QtCore.QTimer() self.playRate = 0 self.fps = 1 # 1 Hz by default self.lastPlayTime = 0 self.normRgn = LinearRegionItem() self.normRgn.setZValue(0) self.ui.roiPlot.addItem(self.normRgn) self.normRgn.hide() ## wrap functions from view box for fn in ['addItem', 'removeItem']: setattr(self, fn, getattr(self.view, fn)) ## wrap functions from histogram for fn in ['setHistogramRange', 'autoHistogramRange', 'getLookupTable', 'getLevels']: setattr(self, fn, getattr(self.ui.histogram, fn)) self.timeLine.sigPositionChanged.connect(self.timeLineChanged) self.ui.roiBtn.clicked.connect(self.roiClicked) self.roi.sigRegionChanged.connect(self.roiChanged) #self.ui.normBtn.toggled.connect(self.normToggled) self.ui.menuBtn.clicked.connect(self.menuClicked) self.ui.normDivideRadio.clicked.connect(self.normRadioChanged) self.ui.normSubtractRadio.clicked.connect(self.normRadioChanged) self.ui.normOffRadio.clicked.connect(self.normRadioChanged) self.ui.normROICheck.clicked.connect(self.updateNorm) self.ui.normFrameCheck.clicked.connect(self.updateNorm) self.ui.normTimeRangeCheck.clicked.connect(self.updateNorm) self.playTimer.timeout.connect(self.timeout) self.normProxy = SignalProxy(self.normRgn.sigRegionChanged, slot=self.updateNorm) self.normRoi.sigRegionChangeFinished.connect(self.updateNorm) self.ui.roiPlot.registerPlot(self.name + '_ROI') self.view.register(self.name) self.noRepeatKeys = [QtCore.Qt.Key.Key_Right, QtCore.Qt.Key.Key_Left, QtCore.Qt.Key.Key_Up, QtCore.Qt.Key.Key_Down, QtCore.Qt.Key.Key_PageUp, QtCore.Qt.Key.Key_PageDown] self.roiClicked() ## initialize roi plot to correct shape / visibility def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None, transform=None, autoHistogramRange=True, levelMode=None): """ Set the image to be displayed in the widget. ================== =========================================================================== **Arguments:** img (numpy array) the image to be displayed. See :func:`ImageItem.setImage` and *notes* below. xvals (numpy array) 1D array of z-axis values corresponding to the first axis in a 3D image. For video, this array should contain the time of each frame. autoRange (bool) whether to scale/pan the view to fit the image. autoLevels (bool) whether to update the white/black levels to fit the image. levels (min, max); the white and black level values to use. axes Dictionary indicating the interpretation for each axis. This is only needed to override the default guess. Format is:: {'t':0, 'x':1, 'y':2, 'c':3}; pos Change the position of the displayed image scale Change the scale of the displayed image transform Set the transform of the displayed image. This option overrides *pos* and *scale*. autoHistogramRange If True, the histogram y-range is automatically scaled to fit the image data. levelMode If specified, this sets the user interaction mode for setting image levels. Options are 'mono', which provides a single level control for all image channels, and 'rgb' or 'rgba', which provide individual controls for each channel. ================== =========================================================================== **Notes:** For backward compatibility, image data is assumed to be in column-major order (column, row). However, most image data is stored in row-major order (row, column) and will need to be transposed before calling setImage():: imageview.setImage(imagedata.T) This requirement can be changed by the ``imageAxisOrder`` :ref:`global configuration option `. """ profiler = debug.Profiler() if hasattr(img, 'implements') and img.implements('MetaArray'): img = img.asarray() if not isinstance(img, np.ndarray): required = ['dtype', 'max', 'min', 'ndim', 'shape', 'size'] if not all(hasattr(img, attr) for attr in required): raise TypeError("Image must be NumPy array or any object " "that provides compatible attributes/methods:\n" " %s" % str(required)) self.image = img self.imageDisp = None if levelMode is not None: self.ui.histogram.setLevelMode(levelMode) profiler() if axes is None: x,y = (0, 1) if self.imageItem.axisOrder == 'col-major' else (1, 0) if img.ndim == 2: self.axes = {'t': None, 'x': x, 'y': y, 'c': None} elif img.ndim == 3: # Ambiguous case; make a guess if img.shape[2] <= 4: self.axes = {'t': None, 'x': x, 'y': y, 'c': 2} else: self.axes = {'t': 0, 'x': x+1, 'y': y+1, 'c': None} elif img.ndim == 4: # Even more ambiguous; just assume the default self.axes = {'t': 0, 'x': x+1, 'y': y+1, 'c': 3} else: raise Exception("Can not interpret image with dimensions %s" % (str(img.shape))) elif isinstance(axes, dict): self.axes = axes.copy() elif isinstance(axes, list) or isinstance(axes, tuple): self.axes = {} for i in range(len(axes)): self.axes[axes[i]] = i else: raise Exception("Can not interpret axis specification %s. Must be like {'t': 2, 'x': 0, 'y': 1} or ('t', 'x', 'y', 'c')" % (str(axes))) for x in ['t', 'x', 'y', 'c']: self.axes[x] = self.axes.get(x, None) axes = self.axes if xvals is not None: self.tVals = xvals elif axes['t'] is not None: if hasattr(img, 'xvals'): try: self.tVals = img.xvals(axes['t']) except: self.tVals = np.arange(img.shape[axes['t']]) else: self.tVals = np.arange(img.shape[axes['t']]) profiler() self.currentIndex = 0 self.updateImage(autoHistogramRange=autoHistogramRange) if levels is None and autoLevels: self.autoLevels() if levels is not None: ## this does nothing since getProcessedImage sets these values again. self.setLevels(*levels) if self.ui.roiBtn.isChecked(): self.roiChanged() profiler() if self.axes['t'] is not None: self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max()) self.frameTicks.setXVals(self.tVals) self.timeLine.setValue(0) if len(self.tVals) > 1: start = self.tVals.min() stop = self.tVals.max() + abs(self.tVals[-1] - self.tVals[0]) * 0.02 elif len(self.tVals) == 1: start = self.tVals[0] - 0.5 stop = self.tVals[0] + 0.5 else: start = 0 stop = 1 for s in [self.timeLine, self.normRgn]: s.setBounds([start, stop]) profiler() if transform is None: transform = QtGui.QTransform() # note that the order of transform is # scale followed by translate if pos is not None: transform.translate(*pos) if scale is not None: transform.scale(*scale) self.imageItem.setTransform(transform) profiler() if autoRange: self.autoRange() self.roiClicked() profiler() def clear(self): self.image = None self.imageItem.clear() def play(self, rate=None): """Begin automatically stepping frames forward at the given rate (in fps). This can also be accessed by pressing the spacebar.""" #print "play:", rate if rate is None: rate = self.fps self.playRate = rate if rate == 0: self.playTimer.stop() return self.lastPlayTime = perf_counter() if not self.playTimer.isActive(): self.playTimer.start(16) def autoLevels(self): """Set the min/max intensity levels automatically to match the image data.""" self.setLevels(rgba=self._imageLevels) def setLevels(self, *args, **kwds): """Set the min/max (bright and dark) levels. See :func:`HistogramLUTItem.setLevels `. """ self.ui.histogram.setLevels(*args, **kwds) def autoRange(self): """Auto scale and pan the view around the image such that the image fills the view.""" image = self.getProcessedImage() self.view.autoRange() def getProcessedImage(self): """Returns the image data after it has been processed by any normalization options in use. """ if self.imageDisp is None: image = self.normalize(self.image) self.imageDisp = image self._imageLevels = self.quickMinMax(self.imageDisp) self.levelMin = min([level[0] for level in self._imageLevels]) self.levelMax = max([level[1] for level in self._imageLevels]) return self.imageDisp def close(self): """Closes the widget nicely, making sure to clear the graphics scene and release memory.""" self.clear() self.imageDisp = None self.imageItem.setParent(None) super(ImageView, self).close() self.setParent(None) def keyPressEvent(self, ev): if not self.hasTimeAxis(): super().keyPressEvent(ev) return if ev.key() == QtCore.Qt.Key.Key_Space: if self.playRate == 0: self.play() else: self.play(0) ev.accept() elif ev.key() == QtCore.Qt.Key.Key_Home: self.setCurrentIndex(0) self.play(0) ev.accept() elif ev.key() == QtCore.Qt.Key.Key_End: self.setCurrentIndex(self.getProcessedImage().shape[0]-1) self.play(0) ev.accept() elif ev.key() in self.noRepeatKeys: ev.accept() if ev.isAutoRepeat(): return self.keysPressed[ev.key()] = 1 self.evalKeyState() else: super().keyPressEvent(ev) def keyReleaseEvent(self, ev): if not self.hasTimeAxis(): super().keyReleaseEvent(ev) return if ev.key() in [QtCore.Qt.Key.Key_Space, QtCore.Qt.Key.Key_Home, QtCore.Qt.Key.Key_End]: ev.accept() elif ev.key() in self.noRepeatKeys: ev.accept() if ev.isAutoRepeat(): return try: del self.keysPressed[ev.key()] except: self.keysPressed = {} self.evalKeyState() else: super().keyReleaseEvent(ev) def evalKeyState(self): if len(self.keysPressed) == 1: key = list(self.keysPressed.keys())[0] if key == QtCore.Qt.Key.Key_Right: self.play(20) self.jumpFrames(1) # effectively pause playback for 0.2 s self.lastPlayTime = perf_counter() + 0.2 elif key == QtCore.Qt.Key.Key_Left: self.play(-20) self.jumpFrames(-1) self.lastPlayTime = perf_counter() + 0.2 elif key == QtCore.Qt.Key.Key_Up: self.play(-100) elif key == QtCore.Qt.Key.Key_Down: self.play(100) elif key == QtCore.Qt.Key.Key_PageUp: self.play(-1000) elif key == QtCore.Qt.Key.Key_PageDown: self.play(1000) else: self.play(0) def timeout(self): now = perf_counter() dt = now - self.lastPlayTime if dt < 0: return n = int(self.playRate * dt) if n != 0: self.lastPlayTime += (float(n)/self.playRate) if self.currentIndex+n > self.image.shape[self.axes['t']]: self.play(0) self.jumpFrames(n) def setCurrentIndex(self, ind): """Set the currently displayed frame index.""" index = fn.clip_scalar(ind, 0, self.getProcessedImage().shape[self.axes['t']]-1) self.ignorePlaying = True # Implicitly call timeLineChanged self.timeLine.setValue(self.tVals[index]) self.ignorePlaying = False def jumpFrames(self, n): """Move video frame ahead n frames (may be negative)""" if self.axes['t'] is not None: self.setCurrentIndex(self.currentIndex + n) def normRadioChanged(self): self.imageDisp = None self.updateImage() self.autoLevels() self.roiChanged() self.sigProcessingChanged.emit(self) def updateNorm(self): if self.ui.normTimeRangeCheck.isChecked(): self.normRgn.show() else: self.normRgn.hide() if self.ui.normROICheck.isChecked(): self.normRoi.show() else: self.normRoi.hide() if not self.ui.normOffRadio.isChecked(): self.imageDisp = None self.updateImage() self.autoLevels() self.roiChanged() self.sigProcessingChanged.emit(self) def normToggled(self, b): self.ui.normGroup.setVisible(b) self.normRoi.setVisible(b and self.ui.normROICheck.isChecked()) self.normRgn.setVisible(b and self.ui.normTimeRangeCheck.isChecked()) def hasTimeAxis(self): return 't' in self.axes and self.axes['t'] is not None def roiClicked(self): showRoiPlot = False if self.ui.roiBtn.isChecked(): showRoiPlot = True self.roi.show() self.ui.roiPlot.setMouseEnabled(True, True) self.ui.splitter.setSizes([int(self.height()*0.6), int(self.height()*0.4)]) self.ui.splitter.handle(1).setEnabled(True) self.roiChanged() for c in self.roiCurves: c.show() self.ui.roiPlot.showAxis('left') else: self.roi.hide() self.ui.roiPlot.setMouseEnabled(False, False) for c in self.roiCurves: c.hide() self.ui.roiPlot.hideAxis('left') if self.hasTimeAxis(): showRoiPlot = True mn = self.tVals.min() mx = self.tVals.max() self.ui.roiPlot.setXRange(mn, mx, padding=0.01) self.timeLine.show() self.timeLine.setBounds([mn, mx]) if not self.ui.roiBtn.isChecked(): self.ui.splitter.setSizes([self.height()-35, 35]) self.ui.splitter.handle(1).setEnabled(False) else: self.timeLine.hide() self.ui.roiPlot.setVisible(showRoiPlot) def roiChanged(self): # Extract image data from ROI if self.image is None: return image = self.getProcessedImage() # getArrayRegion axes should be (x, y) of data array for col-major, # (y, x) for row-major # can't just transpose input because ROI is axisOrder aware colmaj = self.imageItem.axisOrder == 'col-major' if colmaj: axes = (self.axes['x'], self.axes['y']) else: axes = (self.axes['y'], self.axes['x']) data, coords = self.roi.getArrayRegion( image.view(np.ndarray), img=self.imageItem, axes=axes, returnMappedCoords=True) if data is None: return # Convert extracted data into 1D plot data if self.axes['t'] is None: # Average across y-axis of ROI data = data.mean(axis=self.axes['y']) # get coordinates along x axis of ROI mapped to range (0, roiwidth) if colmaj: coords = coords[:, :, 0] - coords[:, 0:1, 0] else: coords = coords[:, 0, :] - coords[:, 0, 0:1] xvals = (coords**2).sum(axis=0) ** 0.5 else: # Average data within entire ROI for each frame data = data.mean(axis=axes) xvals = self.tVals # Handle multi-channel data if data.ndim == 1: plots = [(xvals, data, 'w')] if data.ndim == 2: if data.shape[1] == 1: colors = 'w' else: colors = 'rgbw' plots = [] for i in range(data.shape[1]): d = data[:,i] plots.append((xvals, d, colors[i])) # Update plot line(s) while len(plots) < len(self.roiCurves): c = self.roiCurves.pop() c.scene().removeItem(c) while len(plots) > len(self.roiCurves): self.roiCurves.append(self.ui.roiPlot.plot()) for i in range(len(plots)): x, y, p = plots[i] self.roiCurves[i].setData(x, y, pen=p) def quickMinMax(self, data): """ Estimate the min/max values of *data* by subsampling. Returns [(min, max), ...] with one item per channel """ while data.size > 1e6: ax = np.argmax(data.shape) sl = [slice(None)] * data.ndim sl[ax] = slice(None, None, 2) data = data[tuple(sl)] cax = self.axes['c'] if cax is None: if data.size == 0: return [(0, 0)] return [(float(nanmin(data)), float(nanmax(data)))] else: if data.size == 0: return [(0, 0)] * data.shape[-1] return [(float(nanmin(data.take(i, axis=cax))), float(nanmax(data.take(i, axis=cax)))) for i in range(data.shape[-1])] def normalize(self, image): """ Process *image* using the normalization options configured in the control panel. This can be repurposed to process any data through the same filter. """ if self.ui.normOffRadio.isChecked(): return image div = self.ui.normDivideRadio.isChecked() norm = image.view(np.ndarray).copy() #if div: #norm = ones(image.shape) #else: #norm = zeros(image.shape) if div: norm = norm.astype(np.float32) if self.ui.normTimeRangeCheck.isChecked() and image.ndim == 3: (sind, start) = self.timeIndex(self.normRgn.lines[0]) (eind, end) = self.timeIndex(self.normRgn.lines[1]) #print start, end, sind, eind n = image[sind:eind+1].mean(axis=0) n.shape = (1,) + n.shape if div: norm /= n else: norm -= n if self.ui.normFrameCheck.isChecked() and image.ndim == 3: n = image.mean(axis=1).mean(axis=1) n.shape = n.shape + (1, 1) if div: norm /= n else: norm -= n if self.ui.normROICheck.isChecked() and image.ndim == 3: n = self.normRoi.getArrayRegion(norm, self.imageItem, (1, 2)).mean(axis=1).mean(axis=1) n = n[:,np.newaxis,np.newaxis] #print start, end, sind, eind if div: norm /= n else: norm -= n return norm def timeLineChanged(self): if not self.ignorePlaying: self.play(0) (ind, time) = self.timeIndex(self.timeLine) if ind != self.currentIndex: self.currentIndex = ind self.updateImage() self.sigTimeChanged.emit(ind, time) def updateImage(self, autoHistogramRange=True): ## Redraw image on screen if self.image is None: return image = self.getProcessedImage() if autoHistogramRange: self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax) # Transpose image into order expected by ImageItem if self.imageItem.axisOrder == 'col-major': axorder = ['t', 'x', 'y', 'c'] else: axorder = ['t', 'y', 'x', 'c'] axorder = [self.axes[ax] for ax in axorder if self.axes[ax] is not None] image = image.transpose(axorder) # Select time index if self.axes['t'] is not None: self.ui.roiPlot.show() image = image[self.currentIndex] self.imageItem.updateImage(image) def timeIndex(self, slider): ## Return the time and frame index indicated by a slider if not self.hasTimeAxis(): return (0,0) t = slider.value() xv = self.tVals if xv is None: ind = int(t) else: if len(xv) < 2: return (0,0) totTime = xv[-1] + (xv[-1]-xv[-2]) inds = np.argwhere(xv <= t) if len(inds) < 1: return (0,t) ind = inds[-1,0] return ind, t def getView(self): """Return the ViewBox (or other compatible object) which displays the ImageItem""" return self.view def getImageItem(self): """Return the ImageItem for this ImageView.""" return self.imageItem def getRoiPlot(self): """Return the ROI PlotWidget for this ImageView""" return self.ui.roiPlot def getHistogramWidget(self): """Return the HistogramLUTWidget for this ImageView""" return self.ui.histogram def export(self, fileName): """ Export data from the ImageView to a file, or to a stack of files if the data is 3D. Saving an image stack will result in index numbers being added to the file name. Images are saved as they would appear onscreen, with levels and lookup table applied. """ img = self.getProcessedImage() if self.hasTimeAxis(): base, ext = os.path.splitext(fileName) fmt = "%%s%%0%dd%%s" % int(log10(img.shape[0])+1) for i in range(img.shape[0]): self.imageItem.setImage(img[i], autoLevels=False) self.imageItem.save(fmt % (base, i, ext)) self.updateImage() else: self.imageItem.save(fileName) def exportClicked(self): fileName = QtWidgets.QFileDialog.getSaveFileName() if isinstance(fileName, tuple): fileName = fileName[0] # Qt4/5 API difference if fileName == '': return self.export(str(fileName)) def buildMenu(self): self.menu = QtWidgets.QMenu() self.normAction = QtGui.QAction(translate("ImageView", "Normalization"), self.menu) self.normAction.setCheckable(True) self.normAction.toggled.connect(self.normToggled) self.menu.addAction(self.normAction) self.exportAction = QtGui.QAction(translate("ImageView", "Export"), self.menu) self.exportAction.triggered.connect(self.exportClicked) self.menu.addAction(self.exportAction) def menuClicked(self): if self.menu is None: self.buildMenu() self.menu.popup(QtGui.QCursor.pos()) def setColorMap(self, colormap): """Set the color map. ============= ========================================================= **Arguments** colormap (A ColorMap() instance) The ColorMap to use for coloring images. ============= ========================================================= """ self.ui.histogram.gradient.setColorMap(colormap) @addGradientListToDocstring() def setPredefinedGradient(self, name): """Set one of the gradients defined in :class:`GradientEditorItem `. Currently available gradients are: """ self.ui.histogram.gradient.loadPreset(name) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/imageview/ImageViewTemplate.ui000066400000000000000000000156031421045507400255450ustar00rootroot00000000000000 Form 0 0 726 588 PyQtGraph 0 0 Qt::Vertical 0 0 1 ROI true 0 1 Menu 0 0 0 40 Normalization 0 0 Subtract Divide false true Operation: true Mean: true Blur: ROI X Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Y Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter T Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Off true Time range Frame PlotWidget QWidget
      ..widgets.PlotWidget
      1
      GraphicsView QGraphicsView
      ..widgets.GraphicsView
      HistogramLUTWidget QGraphicsView
      ..widgets.HistogramLUTWidget
      pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/imageview/ImageViewTemplate_pyqt5.py000066400000000000000000000201001421045507400267060ustar00rootroot00000000000000 # Form implementation generated from reading ui file './pyqtgraph/imageview/ImageViewTemplate.ui' # # Created: Wed Mar 26 15:09:28 2014 # by: PyQt5 UI code generator 5.0.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(726, 588) self.gridLayout_3 = QtWidgets.QGridLayout(Form) self.gridLayout_3.setContentsMargins(0, 0, 0, 0) self.gridLayout_3.setSpacing(0) self.gridLayout_3.setObjectName("gridLayout_3") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Vertical) self.splitter.setObjectName("splitter") self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) self.gridLayout.setSpacing(0) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.graphicsView = GraphicsView(self.layoutWidget) self.graphicsView.setObjectName("graphicsView") self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) self.histogram = HistogramLUTWidget(self.layoutWidget) self.histogram.setObjectName("histogram") self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) self.roiBtn = QtWidgets.QPushButton(self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) self.roiBtn.setSizePolicy(sizePolicy) self.roiBtn.setCheckable(True) self.roiBtn.setObjectName("roiBtn") self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) self.menuBtn = QtWidgets.QPushButton(self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) self.menuBtn.setSizePolicy(sizePolicy) self.menuBtn.setCheckable(True) self.menuBtn.setObjectName("menuBtn") self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) self.roiPlot = PlotWidget(self.splitter) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) self.roiPlot.setSizePolicy(sizePolicy) self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) self.roiPlot.setObjectName("roiPlot") self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) self.normGroup = QtWidgets.QGroupBox(Form) self.normGroup.setObjectName("normGroup") self.gridLayout_2 = QtWidgets.QGridLayout(self.normGroup) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") self.normSubtractRadio = QtWidgets.QRadioButton(self.normGroup) self.normSubtractRadio.setObjectName("normSubtractRadio") self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) self.normDivideRadio = QtWidgets.QRadioButton(self.normGroup) self.normDivideRadio.setChecked(False) self.normDivideRadio.setObjectName("normDivideRadio") self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) self.label_5 = QtWidgets.QLabel(self.normGroup) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.label_5.setFont(font) self.label_5.setObjectName("label_5") self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) self.label_3 = QtWidgets.QLabel(self.normGroup) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.label_3.setFont(font) self.label_3.setObjectName("label_3") self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) self.label_4 = QtWidgets.QLabel(self.normGroup) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.label_4.setFont(font) self.label_4.setObjectName("label_4") self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) self.normROICheck = QtWidgets.QCheckBox(self.normGroup) self.normROICheck.setObjectName("normROICheck") self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) self.normXBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) self.normXBlurSpin.setObjectName("normXBlurSpin") self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) self.label_8 = QtWidgets.QLabel(self.normGroup) self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_8.setObjectName("label_8") self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) self.label_9 = QtWidgets.QLabel(self.normGroup) self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_9.setObjectName("label_9") self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) self.normYBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) self.normYBlurSpin.setObjectName("normYBlurSpin") self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) self.label_10 = QtWidgets.QLabel(self.normGroup) self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_10.setObjectName("label_10") self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) self.normOffRadio = QtWidgets.QRadioButton(self.normGroup) self.normOffRadio.setChecked(True) self.normOffRadio.setObjectName("normOffRadio") self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) self.normTimeRangeCheck = QtWidgets.QCheckBox(self.normGroup) self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) self.normFrameCheck = QtWidgets.QCheckBox(self.normGroup) self.normFrameCheck.setObjectName("normFrameCheck") self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) self.normTBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) self.normTBlurSpin.setObjectName("normTBlurSpin") self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.roiBtn.setText(_translate("Form", "ROI")) self.menuBtn.setText(_translate("Form", "Norm")) self.normGroup.setTitle(_translate("Form", "Normalization")) self.normSubtractRadio.setText(_translate("Form", "Subtract")) self.normDivideRadio.setText(_translate("Form", "Divide")) self.label_5.setText(_translate("Form", "Operation:")) self.label_3.setText(_translate("Form", "Mean:")) self.label_4.setText(_translate("Form", "Blur:")) self.normROICheck.setText(_translate("Form", "ROI")) self.label_8.setText(_translate("Form", "X")) self.label_9.setText(_translate("Form", "Y")) self.label_10.setText(_translate("Form", "T")) self.normOffRadio.setText(_translate("Form", "Off")) self.normTimeRangeCheck.setText(_translate("Form", "Time range")) self.normFrameCheck.setText(_translate("Form", "Frame")) from ..widgets.HistogramLUTWidget import HistogramLUTWidget from ..widgets.PlotWidget import PlotWidget from ..widgets.GraphicsView import GraphicsView pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/imageview/ImageViewTemplate_pyqt6.py000066400000000000000000000202701421045507400267170ustar00rootroot00000000000000# Form implementation generated from reading ui file '../pyqtgraph/imageview/ImageViewTemplate.ui' # # Created by: PyQt6 UI code generator 6.1.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(726, 588) self.gridLayout_3 = QtWidgets.QGridLayout(Form) self.gridLayout_3.setContentsMargins(0, 0, 0, 0) self.gridLayout_3.setSpacing(0) self.gridLayout_3.setObjectName("gridLayout_3") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Orientation.Vertical) self.splitter.setObjectName("splitter") self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName("gridLayout") self.graphicsView = GraphicsView(self.layoutWidget) self.graphicsView.setObjectName("graphicsView") self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) self.histogram = HistogramLUTWidget(self.layoutWidget) self.histogram.setObjectName("histogram") self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) self.roiBtn = QtWidgets.QPushButton(self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) self.roiBtn.setSizePolicy(sizePolicy) self.roiBtn.setCheckable(True) self.roiBtn.setObjectName("roiBtn") self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) self.menuBtn = QtWidgets.QPushButton(self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) self.menuBtn.setSizePolicy(sizePolicy) self.menuBtn.setObjectName("menuBtn") self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) self.roiPlot = PlotWidget(self.splitter) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) self.roiPlot.setSizePolicy(sizePolicy) self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) self.roiPlot.setObjectName("roiPlot") self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) self.normGroup = QtWidgets.QGroupBox(Form) self.normGroup.setObjectName("normGroup") self.gridLayout_2 = QtWidgets.QGridLayout(self.normGroup) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") self.normSubtractRadio = QtWidgets.QRadioButton(self.normGroup) self.normSubtractRadio.setObjectName("normSubtractRadio") self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) self.normDivideRadio = QtWidgets.QRadioButton(self.normGroup) self.normDivideRadio.setChecked(False) self.normDivideRadio.setObjectName("normDivideRadio") self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) self.label_5 = QtWidgets.QLabel(self.normGroup) font = QtGui.QFont() font.setBold(True) self.label_5.setFont(font) self.label_5.setObjectName("label_5") self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) self.label_3 = QtWidgets.QLabel(self.normGroup) font = QtGui.QFont() font.setBold(True) self.label_3.setFont(font) self.label_3.setObjectName("label_3") self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) self.label_4 = QtWidgets.QLabel(self.normGroup) font = QtGui.QFont() font.setBold(True) self.label_4.setFont(font) self.label_4.setObjectName("label_4") self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) self.normROICheck = QtWidgets.QCheckBox(self.normGroup) self.normROICheck.setObjectName("normROICheck") self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) self.normXBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) self.normXBlurSpin.setObjectName("normXBlurSpin") self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) self.label_8 = QtWidgets.QLabel(self.normGroup) self.label_8.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) self.label_8.setObjectName("label_8") self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) self.label_9 = QtWidgets.QLabel(self.normGroup) self.label_9.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) self.label_9.setObjectName("label_9") self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) self.normYBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) self.normYBlurSpin.setObjectName("normYBlurSpin") self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) self.label_10 = QtWidgets.QLabel(self.normGroup) self.label_10.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) self.label_10.setObjectName("label_10") self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) self.normOffRadio = QtWidgets.QRadioButton(self.normGroup) self.normOffRadio.setChecked(True) self.normOffRadio.setObjectName("normOffRadio") self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) self.normTimeRangeCheck = QtWidgets.QCheckBox(self.normGroup) self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) self.normFrameCheck = QtWidgets.QCheckBox(self.normGroup) self.normFrameCheck.setObjectName("normFrameCheck") self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) self.normTBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) self.normTBlurSpin.setObjectName("normTBlurSpin") self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "PyQtGraph")) self.roiBtn.setText(_translate("Form", "ROI")) self.menuBtn.setText(_translate("Form", "Menu")) self.normGroup.setTitle(_translate("Form", "Normalization")) self.normSubtractRadio.setText(_translate("Form", "Subtract")) self.normDivideRadio.setText(_translate("Form", "Divide")) self.label_5.setText(_translate("Form", "Operation:")) self.label_3.setText(_translate("Form", "Mean:")) self.label_4.setText(_translate("Form", "Blur:")) self.normROICheck.setText(_translate("Form", "ROI")) self.label_8.setText(_translate("Form", "X")) self.label_9.setText(_translate("Form", "Y")) self.label_10.setText(_translate("Form", "T")) self.normOffRadio.setText(_translate("Form", "Off")) self.normTimeRangeCheck.setText(_translate("Form", "Time range")) self.normFrameCheck.setText(_translate("Form", "Frame")) from ..widgets.GraphicsView import GraphicsView from ..widgets.HistogramLUTWidget import HistogramLUTWidget from ..widgets.PlotWidget import PlotWidget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/imageview/ImageViewTemplate_pyside2.py000066400000000000000000000207271421045507400272220ustar00rootroot00000000000000 # Form implementation generated from reading ui file 'ImageViewTemplate.ui' # # Created: Sun Sep 18 19:17:41 2016 # by: pyside2-uic running on PySide2 2.0.0~alpha0 # # WARNING! All changes made in this file will be lost! from PySide2 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(726, 588) self.gridLayout_3 = QtWidgets.QGridLayout(Form) self.gridLayout_3.setContentsMargins(0, 0, 0, 0) self.gridLayout_3.setSpacing(0) self.gridLayout_3.setObjectName("gridLayout_3") self.splitter = QtWidgets.QSplitter(Form) self.splitter.setOrientation(QtCore.Qt.Vertical) self.splitter.setObjectName("splitter") self.layoutWidget = QtWidgets.QWidget(self.splitter) self.layoutWidget.setObjectName("layoutWidget") self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) self.gridLayout.setSpacing(0) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.graphicsView = GraphicsView(self.layoutWidget) self.graphicsView.setObjectName("graphicsView") self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) self.histogram = HistogramLUTWidget(self.layoutWidget) self.histogram.setObjectName("histogram") self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) self.roiBtn = QtWidgets.QPushButton(self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) self.roiBtn.setSizePolicy(sizePolicy) self.roiBtn.setCheckable(True) self.roiBtn.setObjectName("roiBtn") self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) self.menuBtn = QtWidgets.QPushButton(self.layoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) self.menuBtn.setSizePolicy(sizePolicy) self.menuBtn.setObjectName("menuBtn") self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) self.roiPlot = PlotWidget(self.splitter) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) self.roiPlot.setSizePolicy(sizePolicy) self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) self.roiPlot.setObjectName("roiPlot") self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) self.normGroup = QtWidgets.QGroupBox(Form) self.normGroup.setObjectName("normGroup") self.gridLayout_2 = QtWidgets.QGridLayout(self.normGroup) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") self.normSubtractRadio = QtWidgets.QRadioButton(self.normGroup) self.normSubtractRadio.setObjectName("normSubtractRadio") self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) self.normDivideRadio = QtWidgets.QRadioButton(self.normGroup) self.normDivideRadio.setChecked(False) self.normDivideRadio.setObjectName("normDivideRadio") self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) self.label_5 = QtWidgets.QLabel(self.normGroup) font = QtGui.QFont() font.setWeight(75) font.setBold(True) self.label_5.setFont(font) self.label_5.setObjectName("label_5") self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) self.label_3 = QtWidgets.QLabel(self.normGroup) font = QtGui.QFont() font.setWeight(75) font.setBold(True) self.label_3.setFont(font) self.label_3.setObjectName("label_3") self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) self.label_4 = QtWidgets.QLabel(self.normGroup) font = QtGui.QFont() font.setWeight(75) font.setBold(True) self.label_4.setFont(font) self.label_4.setObjectName("label_4") self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) self.normROICheck = QtWidgets.QCheckBox(self.normGroup) self.normROICheck.setObjectName("normROICheck") self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) self.normXBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) self.normXBlurSpin.setObjectName("normXBlurSpin") self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) self.label_8 = QtWidgets.QLabel(self.normGroup) self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_8.setObjectName("label_8") self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) self.label_9 = QtWidgets.QLabel(self.normGroup) self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_9.setObjectName("label_9") self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) self.normYBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) self.normYBlurSpin.setObjectName("normYBlurSpin") self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) self.label_10 = QtWidgets.QLabel(self.normGroup) self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_10.setObjectName("label_10") self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) self.normOffRadio = QtWidgets.QRadioButton(self.normGroup) self.normOffRadio.setChecked(True) self.normOffRadio.setObjectName("normOffRadio") self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) self.normTimeRangeCheck = QtWidgets.QCheckBox(self.normGroup) self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) self.normFrameCheck = QtWidgets.QCheckBox(self.normGroup) self.normFrameCheck.setObjectName("normFrameCheck") self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) self.normTBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) self.normTBlurSpin.setObjectName("normTBlurSpin") self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1)) self.roiBtn.setText(QtWidgets.QApplication.translate("Form", "ROI", None, -1)) self.menuBtn.setText(QtWidgets.QApplication.translate("Form", "Menu", None, -1)) self.normGroup.setTitle(QtWidgets.QApplication.translate("Form", "Normalization", None, -1)) self.normSubtractRadio.setText(QtWidgets.QApplication.translate("Form", "Subtract", None, -1)) self.normDivideRadio.setText(QtWidgets.QApplication.translate("Form", "Divide", None, -1)) self.label_5.setText(QtWidgets.QApplication.translate("Form", "Operation:", None, -1)) self.label_3.setText(QtWidgets.QApplication.translate("Form", "Mean:", None, -1)) self.label_4.setText(QtWidgets.QApplication.translate("Form", "Blur:", None, -1)) self.normROICheck.setText(QtWidgets.QApplication.translate("Form", "ROI", None, -1)) self.label_8.setText(QtWidgets.QApplication.translate("Form", "X", None, -1)) self.label_9.setText(QtWidgets.QApplication.translate("Form", "Y", None, -1)) self.label_10.setText(QtWidgets.QApplication.translate("Form", "T", None, -1)) self.normOffRadio.setText(QtWidgets.QApplication.translate("Form", "Off", None, -1)) self.normTimeRangeCheck.setText(QtWidgets.QApplication.translate("Form", "Time range", None, -1)) self.normFrameCheck.setText(QtWidgets.QApplication.translate("Form", "Frame", None, -1)) from ..widgets.HistogramLUTWidget import HistogramLUTWidget from ..widgets.PlotWidget import PlotWidget from ..widgets.GraphicsView import GraphicsView pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/imageview/ImageViewTemplate_pyside6.py000066400000000000000000000200011421045507400272070ustar00rootroot00000000000000 ################################################################################ ## Form generated from reading UI file 'ImageViewTemplate.ui' ## ## Created by: Qt User Interface Compiler version 6.1.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * from ..widgets.PlotWidget import PlotWidget from ..widgets.GraphicsView import GraphicsView from ..widgets.HistogramLUTWidget import HistogramLUTWidget class Ui_Form(object): def setupUi(self, Form): if not Form.objectName(): Form.setObjectName(u"Form") Form.resize(726, 588) self.gridLayout_3 = QGridLayout(Form) self.gridLayout_3.setSpacing(0) self.gridLayout_3.setContentsMargins(0, 0, 0, 0) self.gridLayout_3.setObjectName(u"gridLayout_3") self.splitter = QSplitter(Form) self.splitter.setObjectName(u"splitter") self.splitter.setOrientation(Qt.Vertical) self.layoutWidget = QWidget(self.splitter) self.layoutWidget.setObjectName(u"layoutWidget") self.gridLayout = QGridLayout(self.layoutWidget) self.gridLayout.setSpacing(0) self.gridLayout.setObjectName(u"gridLayout") self.gridLayout.setContentsMargins(0, 0, 0, 0) self.graphicsView = GraphicsView(self.layoutWidget) self.graphicsView.setObjectName(u"graphicsView") self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) self.histogram = HistogramLUTWidget(self.layoutWidget) self.histogram.setObjectName(u"histogram") self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) self.roiBtn = QPushButton(self.layoutWidget) self.roiBtn.setObjectName(u"roiBtn") sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) self.roiBtn.setSizePolicy(sizePolicy) self.roiBtn.setCheckable(True) self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) self.menuBtn = QPushButton(self.layoutWidget) self.menuBtn.setObjectName(u"menuBtn") sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) self.menuBtn.setSizePolicy(sizePolicy) self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) self.splitter.addWidget(self.layoutWidget) self.roiPlot = PlotWidget(self.splitter) self.roiPlot.setObjectName(u"roiPlot") sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) self.roiPlot.setSizePolicy(sizePolicy1) self.roiPlot.setMinimumSize(QSize(0, 40)) self.splitter.addWidget(self.roiPlot) self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) self.normGroup = QGroupBox(Form) self.normGroup.setObjectName(u"normGroup") self.gridLayout_2 = QGridLayout(self.normGroup) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setObjectName(u"gridLayout_2") self.normSubtractRadio = QRadioButton(self.normGroup) self.normSubtractRadio.setObjectName(u"normSubtractRadio") self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) self.normDivideRadio = QRadioButton(self.normGroup) self.normDivideRadio.setObjectName(u"normDivideRadio") self.normDivideRadio.setChecked(False) self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) self.label_5 = QLabel(self.normGroup) self.label_5.setObjectName(u"label_5") font = QFont() font.setBold(True) self.label_5.setFont(font) self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) self.label_3 = QLabel(self.normGroup) self.label_3.setObjectName(u"label_3") self.label_3.setFont(font) self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) self.label_4 = QLabel(self.normGroup) self.label_4.setObjectName(u"label_4") self.label_4.setFont(font) self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) self.normROICheck = QCheckBox(self.normGroup) self.normROICheck.setObjectName(u"normROICheck") self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) self.normXBlurSpin = QDoubleSpinBox(self.normGroup) self.normXBlurSpin.setObjectName(u"normXBlurSpin") self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) self.label_8 = QLabel(self.normGroup) self.label_8.setObjectName(u"label_8") self.label_8.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) self.label_9 = QLabel(self.normGroup) self.label_9.setObjectName(u"label_9") self.label_9.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) self.normYBlurSpin = QDoubleSpinBox(self.normGroup) self.normYBlurSpin.setObjectName(u"normYBlurSpin") self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) self.label_10 = QLabel(self.normGroup) self.label_10.setObjectName(u"label_10") self.label_10.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) self.normOffRadio = QRadioButton(self.normGroup) self.normOffRadio.setObjectName(u"normOffRadio") self.normOffRadio.setChecked(True) self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) self.normTimeRangeCheck = QCheckBox(self.normGroup) self.normTimeRangeCheck.setObjectName(u"normTimeRangeCheck") self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) self.normFrameCheck = QCheckBox(self.normGroup) self.normFrameCheck.setObjectName(u"normFrameCheck") self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) self.normTBlurSpin = QDoubleSpinBox(self.normGroup) self.normTBlurSpin.setObjectName(u"normTBlurSpin") self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) # setupUi def retranslateUi(self, Form): Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) self.roiBtn.setText(QCoreApplication.translate("Form", u"ROI", None)) self.menuBtn.setText(QCoreApplication.translate("Form", u"Menu", None)) self.normGroup.setTitle(QCoreApplication.translate("Form", u"Normalization", None)) self.normSubtractRadio.setText(QCoreApplication.translate("Form", u"Subtract", None)) self.normDivideRadio.setText(QCoreApplication.translate("Form", u"Divide", None)) self.label_5.setText(QCoreApplication.translate("Form", u"Operation:", None)) self.label_3.setText(QCoreApplication.translate("Form", u"Mean:", None)) self.label_4.setText(QCoreApplication.translate("Form", u"Blur:", None)) self.normROICheck.setText(QCoreApplication.translate("Form", u"ROI", None)) self.label_8.setText(QCoreApplication.translate("Form", u"X", None)) self.label_9.setText(QCoreApplication.translate("Form", u"Y", None)) self.label_10.setText(QCoreApplication.translate("Form", u"T", None)) self.normOffRadio.setText(QCoreApplication.translate("Form", u"Off", None)) self.normTimeRangeCheck.setText(QCoreApplication.translate("Form", u"Time range", None)) self.normFrameCheck.setText(QCoreApplication.translate("Form", u"Frame", None)) # retranslateUi pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/imageview/__init__.py000066400000000000000000000002721421045507400237420ustar00rootroot00000000000000""" Widget used for display and analysis of 2D and 3D image data. Includes ROI plotting over time and image normalization. """ from .ImageView import ImageView __all__ = ['ImageView'] pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/jupyter/000077500000000000000000000000001421045507400213555ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/jupyter/GraphicsView.py000066400000000000000000000151021421045507400243210ustar00rootroot00000000000000import jupyter_rfb import numpy as np from .. import functions as fn from .. import graphicsItems, widgets from ..Qt import QtCore, QtGui __all__ = ['GraphicsLayoutWidget', 'PlotWidget'] KMLUT = { x : getattr(QtCore.Qt.KeyboardModifier, x + "Modifier") for x in ["Shift", "Control", "Alt", "Meta"] } MBLUT = { k : getattr(QtCore.Qt.MouseButton, v + "Button") for (k, v) in zip( range(6), ["No", "Left", "Right", "Middle", "Back", "Forward"] ) } TYPLUT = { "pointer_down" : QtCore.QEvent.Type.MouseButtonPress, "pointer_up" : QtCore.QEvent.Type.MouseButtonRelease, "pointer_move" : QtCore.QEvent.Type.MouseMove, } def get_buttons(evt_buttons): NoButton = QtCore.Qt.MouseButton.NoButton btns = NoButton for x in evt_buttons: btns |= MBLUT.get(x, NoButton) return btns def get_modifiers(evt_modifiers): NoModifier = QtCore.Qt.KeyboardModifier.NoModifier mods = NoModifier for x in evt_modifiers: mods |= KMLUT.get(x, NoModifier) return mods class GraphicsView(jupyter_rfb.RemoteFrameBuffer): """jupyter_rfb.RemoteFrameBuffer sub-class that wraps around :class:`GraphicsView `. Generally speaking, there is no Qt event loop running. The implementation works by requesting a render() of the scene. Thus things that would work for exporting purposes would be expected to work here. Things that are not part of the scene would not work, e.g. context menus, tooltips. This class should not be used directly. Its corresponding sub-classes :class:`GraphicsLayoutWidget ` and :class:`PlotWidget ` should be used instead.""" def __init__(self, **kwds): super().__init__(**kwds) self.gfxView = widgets.GraphicsView.GraphicsView() self.logical_size = int(self.css_width[:-2]), int(self.css_height[:-2]) self.pixel_ratio = 1.0 # self.gfxView.resize(*self.logical_size) # self.gfxView.show() # self.gfxView.resizeEvent(None) def get_frame(self): w, h = self.logical_size dpr = self.pixel_ratio buf = np.empty((int(h * dpr), int(w * dpr), 4), dtype=np.uint8) qimg = fn.ndarray_to_qimage(buf, QtGui.QImage.Format.Format_RGBX8888) qimg.fill(QtCore.Qt.GlobalColor.transparent) qimg.setDevicePixelRatio(dpr) painter = QtGui.QPainter(qimg) self.gfxView.render(painter, self.gfxView.viewRect(), self.gfxView.rect()) painter.end() return buf def handle_event(self, event): event_type = event["event_type"] if event_type == "resize": oldSize = QtCore.QSize(*self.logical_size) self.logical_size = int(event["width"]), int(event["height"]) self.pixel_ratio = event["pixel_ratio"] self.gfxView.resize(*self.logical_size) newSize = QtCore.QSize(*self.logical_size) self.gfxView.resizeEvent(QtGui.QResizeEvent(newSize, oldSize)) elif event_type in ["pointer_down", "pointer_up", "pointer_move"]: btn = MBLUT.get(event["button"], None) if btn is None: # ignore unsupported buttons return pos = QtCore.QPointF(event["x"], event["y"]) btns = get_buttons(event["buttons"]) mods = get_modifiers(event["modifiers"]) typ = TYPLUT[event_type] evt = QtGui.QMouseEvent(typ, pos, pos, btn, btns, mods) QtCore.QCoreApplication.sendEvent(self.gfxView.viewport(), evt) self.request_draw() elif event_type == "wheel": pos = QtCore.QPointF(event["x"], event["y"]) pixdel = QtCore.QPoint() scale = -1.0 # map JavaScript wheel to Qt wheel angdel = QtCore.QPoint(int(event["dx"] * scale), int(event["dy"] * scale)) btns = get_buttons([]) mods = get_modifiers(event["modifiers"]) phase = QtCore.Qt.ScrollPhase.NoScrollPhase inverted = False evt = QtGui.QWheelEvent(pos, pos, pixdel, angdel, btns, mods, phase, inverted) QtCore.QCoreApplication.sendEvent(self.gfxView.viewport(), evt) def connect_viewbox_redraw(vb, request_draw): # connecting these signals is enough to support zoom/pan # but not enough to support the various graphicsItems # that react to mouse events vb.sigRangeChanged.connect(request_draw) # zoom / pan vb.sigRangeChangedManually.connect(request_draw) # needed for "auto" button vb.sigStateChanged.connect(request_draw) # note that all cases of sig{X,Y}RangeChanged being emitted # are also followed by sigRangeChanged or sigStateChanged vb.sigTransformChanged.connect(request_draw) class GraphicsLayoutWidget(GraphicsView): """jupyter_rfb analogue of :class:`GraphicsLayoutWidget `.""" def __init__(self, **kwds): super().__init__(**kwds) self.gfxLayout = graphicsItems.GraphicsLayout.GraphicsLayout() for n in [ 'nextRow', 'nextCol', 'nextColumn', 'addItem', 'getItem', 'addLayout', 'addLabel', 'removeItem', 'itemIndex', 'clear' ]: setattr(self, n, getattr(self.gfxLayout, n)) self.gfxView.setCentralItem(self.gfxLayout) def addPlot(self, *args, **kwds): kwds["enableMenu"] = False plotItem = self.gfxLayout.addPlot(*args, **kwds) connect_viewbox_redraw(plotItem.getViewBox(), self.request_draw) return plotItem def addViewBox(self, *args, **kwds): kwds["enableMenu"] = False vb = self.gfxLayout.addViewBox(*args, **kwds) connect_viewbox_redraw(vb, self.request_draw) return vb class PlotWidget(GraphicsView): """jupyter_rfb analogue of :class:`PlotWidget `.""" def __init__(self, **kwds): super().__init__(**kwds) plotItem = graphicsItems.PlotItem.PlotItem(enableMenu=False) self.gfxView.setCentralItem(plotItem) connect_viewbox_redraw(plotItem.getViewBox(), self.request_draw) self.plotItem = plotItem def getPlotItem(self): return self.plotItem def __getattr__(self, attr): # kernel crashes if we don't skip attributes starting with '_' if attr.startswith('_'): return super().__getattr__(attr) # implicitly wrap methods from plotItem if hasattr(self.plotItem, attr): m = getattr(self.plotItem, attr) if hasattr(m, '__call__'): return m raise AttributeError(attr) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/jupyter/__init__.py000066400000000000000000000001251421045507400234640ustar00rootroot00000000000000from .GraphicsView import GraphicsLayoutWidget from .GraphicsView import PlotWidget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/metaarray/000077500000000000000000000000001421045507400216405ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/metaarray/MetaArray.py000066400000000000000000001457221421045507400241120ustar00rootroot00000000000000""" MetaArray.py - Class encapsulating ndarray with meta data Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. MetaArray is an array class based on numpy.ndarray that allows storage of per-axis meta data such as axis values, names, units, column names, etc. It also enables several new methods for slicing and indexing the array based on this meta data. More info at http://www.scipy.org/Cookbook/MetaArray """ import copy import os import pickle import warnings import numpy as np ## By default, the library will use HDF5 when writing files. ## This can be overridden by setting USE_HDF5 = False USE_HDF5 = True try: import h5py # Older h5py versions tucked Group and Dataset deeper inside the library: if not hasattr(h5py, 'Group'): import h5py.highlevel h5py.Group = h5py.highlevel.Group h5py.Dataset = h5py.highlevel.Dataset HAVE_HDF5 = True except: USE_HDF5 = False HAVE_HDF5 = False def axis(name=None, cols=None, values=None, units=None): """Convenience function for generating axis descriptions when defining MetaArrays""" ax = {} cNameOrder = ['name', 'units', 'title'] if name is not None: ax['name'] = name if values is not None: ax['values'] = values if units is not None: ax['units'] = units if cols is not None: ax['cols'] = [] for c in cols: if type(c) != list and type(c) != tuple: c = [c] col = {} for i in range(0,len(c)): col[cNameOrder[i]] = c[i] ax['cols'].append(col) return ax class sliceGenerator(object): """Just a compact way to generate tuples of slice objects.""" def __getitem__(self, arg): return arg def __getslice__(self, arg): return arg SLICER = sliceGenerator() class MetaArray(object): """N-dimensional array with meta data such as axis titles, units, and column names. May be initialized with a file name, a tuple representing the dimensions of the array, or any arguments that could be passed on to numpy.array() The info argument sets the metadata for the entire array. It is composed of a list of axis descriptions where each axis may have a name, title, units, and a list of column descriptions. An additional dict at the end of the axis list may specify parameters that apply to values in the entire array. For example: A 2D array of altitude values for a topographical map might look like info=[ {'name': 'lat', 'title': 'Lattitude'}, {'name': 'lon', 'title': 'Longitude'}, {'title': 'Altitude', 'units': 'm'} ] In this case, every value in the array represents the altitude in feet at the lat, lon position represented by the array index. All of the following return the value at lat=10, lon=5: array[10, 5] array['lon':5, 'lat':10] array['lat':10][5] Now suppose we want to combine this data with another array of equal dimensions that represents the average rainfall for each location. We could easily store these as two separate arrays or combine them into a 3D array with this description: info=[ {'name': 'vals', 'cols': [ {'name': 'altitude', 'units': 'm'}, {'name': 'rainfall', 'units': 'cm/year'} ]}, {'name': 'lat', 'title': 'Lattitude'}, {'name': 'lon', 'title': 'Longitude'} ] We can now access the altitude values with array[0] or array['altitude'], and the rainfall values with array[1] or array['rainfall']. All of the following return the rainfall value at lat=10, lon=5: array[1, 10, 5] array['lon':5, 'lat':10, 'val': 'rainfall'] array['rainfall', 'lon':5, 'lat':10] Notice that in the second example, there is no need for an extra (4th) axis description since the actual values are described (name and units) in the column info for the first axis. """ version = u'2' # Default hdf5 compression to use when writing # 'gzip' is widely available and somewhat slow # 'lzf' is faster, but generally not available outside h5py # 'szip' is also faster, but lacks write support on windows # (so by default, we use no compression) # May also be a tuple (filter, opts), such as ('gzip', 3) defaultCompression = None ## Types allowed as axis or column names nameTypes = [str, tuple] @staticmethod def isNameType(var): return any(isinstance(var, t) for t in MetaArray.nameTypes) ## methods to wrap from embedded ndarray / HDF5 wrapMethods = set(['__eq__', '__ne__', '__le__', '__lt__', '__ge__', '__gt__']) def __init__(self, data=None, info=None, dtype=None, file=None, copy=False, **kwargs): object.__init__(self) warnings.warn( 'MetaArray is deprecated and will be removed in 0.14. ' 'Available though https://pypi.org/project/MetaArray/ as its own package.', DeprecationWarning, stacklevel=2 ) self._isHDF = False if file is not None: self._data = None self.readFile(file, **kwargs) if kwargs.get("readAllData", True) and self._data is None: raise Exception("File read failed: %s" % file) else: self._info = info if (hasattr(data, 'implements') and data.implements('MetaArray')): self._info = data._info self._data = data.asarray() elif isinstance(data, tuple): ## create empty array with specified shape self._data = np.empty(data, dtype=dtype) else: self._data = np.array(data, dtype=dtype, copy=copy) ## run sanity checks on info structure self.checkInfo() def checkInfo(self): info = self._info if info is None: if self._data is None: return else: self._info = [{} for i in range(self.ndim + 1)] return else: try: info = list(info) except: raise Exception("Info must be a list of axis specifications") if len(info) < self.ndim+1: info.extend([{}]*(self.ndim+1-len(info))) elif len(info) > self.ndim+1: raise Exception("Info parameter must be list of length ndim+1 or less.") for i in range(len(info)): if not isinstance(info[i], dict): if info[i] is None: info[i] = {} else: raise Exception("Axis specification must be Dict or None") if i < self.ndim and 'values' in info[i]: if type(info[i]['values']) is list: info[i]['values'] = np.array(info[i]['values']) elif type(info[i]['values']) is not np.ndarray: raise Exception("Axis values must be specified as list or ndarray") if info[i]['values'].ndim != 1 or info[i]['values'].shape[0] != self.shape[i]: raise Exception("Values array for axis %d has incorrect shape. (given %s, but should be %s)" % (i, str(info[i]['values'].shape), str((self.shape[i],)))) if i < self.ndim and 'cols' in info[i]: if not isinstance(info[i]['cols'], list): info[i]['cols'] = list(info[i]['cols']) if len(info[i]['cols']) != self.shape[i]: raise Exception('Length of column list for axis %d does not match data. (given %d, but should be %d)' % (i, len(info[i]['cols']), self.shape[i])) self._info = info def implements(self, name=None): ## Rather than isinstance(obj, MetaArray) use object.implements('MetaArray') if name is None: return ['MetaArray'] else: return name == 'MetaArray' def __getitem__(self, ind): nInd = self._interpretIndexes(ind) a = self._data[nInd] if len(nInd) == self.ndim: if np.all([not isinstance(ind, (slice, np.ndarray)) for ind in nInd]): ## no slices; we have requested a single value from the array return a ## indexing returned a sub-array; generate new info array to go with it info = [] extraInfo = self._info[-1].copy() for i in range(0, len(nInd)): ## iterate over all axes if type(nInd[i]) in [slice, list] or isinstance(nInd[i], np.ndarray): ## If the axis is sliced, keep the info but chop if necessary info.append(self._axisSlice(i, nInd[i])) else: ## If the axis is indexed, then move the information from that single index to the last info dictionary newInfo = self._axisSlice(i, nInd[i]) name = None colName = None for k in newInfo: if k == 'cols': if 'cols' not in extraInfo: extraInfo['cols'] = [] extraInfo['cols'].append(newInfo[k]) if 'units' in newInfo[k]: extraInfo['units'] = newInfo[k]['units'] if 'name' in newInfo[k]: colName = newInfo[k]['name'] elif k == 'name': name = newInfo[k] else: if k not in extraInfo: extraInfo[k] = newInfo[k] extraInfo[k] = newInfo[k] if 'name' not in extraInfo: if name is None: if colName is not None: extraInfo['name'] = colName else: if colName is not None: extraInfo['name'] = str(name) + ': ' + str(colName) else: extraInfo['name'] = name info.append(extraInfo) return MetaArray(a, info=info) @property def ndim(self): return len(self.shape) ## hdf5 objects do not have ndim property. @property def shape(self): return self._data.shape @property def dtype(self): return self._data.dtype def __len__(self): return len(self._data) def __getslice__(self, *args): return self.__getitem__(slice(*args)) def __setitem__(self, ind, val): nInd = self._interpretIndexes(ind) try: self._data[nInd] = val except: print(self, nInd, val) raise def __getattr__(self, attr): if attr in self.wrapMethods: return getattr(self._data, attr) else: raise AttributeError(attr) def __eq__(self, b): return self._binop('__eq__', b) def __ne__(self, b): return self._binop('__ne__', b) def __sub__(self, b): return self._binop('__sub__', b) def __add__(self, b): return self._binop('__add__', b) def __mul__(self, b): return self._binop('__mul__', b) def __div__(self, b): return self._binop('__div__', b) def __truediv__(self, b): return self._binop('__truediv__', b) def _binop(self, op, b): if isinstance(b, MetaArray): b = b.asarray() a = self.asarray() c = getattr(a, op)(b) if c.shape != a.shape: raise Exception("Binary operators with MetaArray must return an array of the same shape (this shape is %s, result shape was %s)" % (a.shape, c.shape)) return MetaArray(c, info=self.infoCopy()) def asarray(self): if isinstance(self._data, np.ndarray): return self._data else: return np.array(self._data) def __array__(self, dtype=None): ## supports np.array(metaarray_instance) if dtype is None: return self.asarray() else: return self.asarray().astype(dtype) def view(self, typ): warnings.warn( 'MetaArray.view is deprecated and will be removed in 0.13. ' 'Use MetaArray.asarray() instead.', DeprecationWarning, stacklevel=2 ) if typ is np.ndarray: return self.asarray() else: raise Exception('invalid view type: %s' % str(typ)) def axisValues(self, axis): """Return the list of values for an axis""" ax = self._interpretAxis(axis) if 'values' in self._info[ax]: return self._info[ax]['values'] else: raise Exception('Array axis %s (%d) has no associated values.' % (str(axis), ax)) def xvals(self, axis): """Synonym for axisValues()""" return self.axisValues(axis) def axisHasValues(self, axis): ax = self._interpretAxis(axis) return 'values' in self._info[ax] def axisHasColumns(self, axis): ax = self._interpretAxis(axis) return 'cols' in self._info[ax] def axisUnits(self, axis): """Return the units for axis""" ax = self._info[self._interpretAxis(axis)] if 'units' in ax: return ax['units'] def hasColumn(self, axis, col): ax = self._info[self._interpretAxis(axis)] if 'cols' in ax: for c in ax['cols']: if c['name'] == col: return True return False def listColumns(self, axis=None): """Return a list of column names for axis. If axis is not specified, then return a dict of {axisName: (column names), ...}.""" if axis is None: ret = {} for i in range(self.ndim): if 'cols' in self._info[i]: cols = [c['name'] for c in self._info[i]['cols']] else: cols = [] ret[self.axisName(i)] = cols return ret else: axis = self._interpretAxis(axis) return [c['name'] for c in self._info[axis]['cols']] def columnName(self, axis, col): ax = self._info[self._interpretAxis(axis)] return ax['cols'][col]['name'] def axisName(self, n): return self._info[n].get('name', n) def columnUnits(self, axis, column): """Return the units for column in axis""" ax = self._info[self._interpretAxis(axis)] if 'cols' in ax: for c in ax['cols']: if c['name'] == column: return c['units'] raise Exception("Axis %s has no column named %s" % (str(axis), str(column))) else: raise Exception("Axis %s has no column definitions" % str(axis)) def rowsort(self, axis, key=0): """Return this object with all records sorted along axis using key as the index to the values to compare. Does not yet modify meta info.""" ## make sure _info is copied locally before modifying it! keyList = self[key] order = keyList.argsort() if type(axis) == int: ind = [slice(None)]*axis ind.append(order) elif isinstance(axis, str): ind = (slice(axis, order),) else: raise TypeError("axis must be type (int, str)") return self[tuple(ind)] def append(self, val, axis): """Return this object with val appended along axis. Does not yet combine meta info.""" ## make sure _info is copied locally before modifying it! s = list(self.shape) axis = self._interpretAxis(axis) s[axis] += 1 n = MetaArray(tuple(s), info=self._info, dtype=self.dtype) ind = [slice(None)]*self.ndim ind[axis] = slice(None,-1) n[tuple(ind)] = self ind[axis] = -1 n[tuple(ind)] = val return n def extend(self, val, axis): """Return the concatenation along axis of this object and val. Does not yet combine meta info.""" ## make sure _info is copied locally before modifying it! axis = self._interpretAxis(axis) return MetaArray(np.concatenate(self, val, axis), info=self._info) def infoCopy(self, axis=None): """Return a deep copy of the axis meta info for this object""" if axis is None: return copy.deepcopy(self._info) else: return copy.deepcopy(self._info[self._interpretAxis(axis)]) def copy(self): return MetaArray(self._data.copy(), info=self.infoCopy()) def _interpretIndexes(self, ind): #print "interpret", ind if not isinstance(ind, tuple): ## a list of slices should be interpreted as a tuple of slices. if isinstance(ind, list) and len(ind) > 0 and isinstance(ind[0], slice): ind = tuple(ind) ## everything else can just be converted to a length-1 tuple else: ind = (ind,) nInd = [slice(None)]*self.ndim numOk = True ## Named indices not started yet; numbered sill ok for i in range(0,len(ind)): (axis, index, isNamed) = self._interpretIndex(ind[i], i, numOk) nInd[axis] = index if isNamed: numOk = False return tuple(nInd) def _interpretAxis(self, axis): if isinstance(axis, (str, tuple)): return self._getAxis(axis) else: return axis def _interpretIndex(self, ind, pos, numOk): #print "Interpreting index", ind, pos, numOk ## should probably check for int first to speed things up.. if type(ind) is int: if not numOk: raise Exception("string and integer indexes may not follow named indexes") #print " normal numerical index" return (pos, ind, False) if MetaArray.isNameType(ind): if not numOk: raise Exception("string and integer indexes may not follow named indexes") #print " String index, column is ", self._getIndex(pos, ind) return (pos, self._getIndex(pos, ind), False) elif type(ind) is slice: #print " Slice index" if MetaArray.isNameType(ind.start) or MetaArray.isNameType(ind.stop): ## Not an actual slice! #print " ..not a real slice" axis = self._interpretAxis(ind.start) #print " axis is", axis ## x[Axis:Column] if MetaArray.isNameType(ind.stop): #print " column name, column is ", self._getIndex(axis, ind.stop) index = self._getIndex(axis, ind.stop) ## x[Axis:min:max] elif (isinstance(ind.stop, float) or isinstance(ind.step, float)) and ('values' in self._info[axis]): #print " axis value range" if ind.stop is None: mask = self.xvals(axis) < ind.step elif ind.step is None: mask = self.xvals(axis) >= ind.stop else: mask = (self.xvals(axis) >= ind.stop) * (self.xvals(axis) < ind.step) ##print "mask:", mask index = mask ## x[Axis:columnIndex] elif isinstance(ind.stop, int) or isinstance(ind.step, int): #print " normal slice after named axis" if ind.step is None: index = ind.stop else: index = slice(ind.stop, ind.step) ## x[Axis: [list]] elif type(ind.stop) is list: #print " list of indexes from named axis" index = [] for i in ind.stop: if type(i) is int: index.append(i) elif MetaArray.isNameType(i): index.append(self._getIndex(axis, i)) else: ## unrecognized type, try just passing on to array index = ind.stop break else: #print " other type.. forward on to array for handling", type(ind.stop) index = ind.stop #print "Axis %s (%s) : %s" % (ind.start, str(axis), str(type(index))) #if type(index) is np.ndarray: #print " ", index.shape return (axis, index, True) else: #print " Looks like a real slice, passing on to array" return (pos, ind, False) elif type(ind) is list: #print " List index., interpreting each element individually" indList = [self._interpretIndex(i, pos, numOk)[1] for i in ind] return (pos, indList, False) else: if not numOk: raise Exception("string and integer indexes may not follow named indexes") #print " normal numerical index" return (pos, ind, False) def _getAxis(self, name): for i in range(0, len(self._info)): axis = self._info[i] if 'name' in axis and axis['name'] == name: return i raise Exception("No axis named %s.\n info=%s" % (name, self._info)) def _getIndex(self, axis, name): ax = self._info[axis] if ax is not None and 'cols' in ax: for i in range(0, len(ax['cols'])): if 'name' in ax['cols'][i] and ax['cols'][i]['name'] == name: return i raise Exception("Axis %d has no column named %s.\n info=%s" % (axis, name, self._info)) def _axisCopy(self, i): return copy.deepcopy(self._info[i]) def _axisSlice(self, i, cols): #print "axisSlice", i, cols if 'cols' in self._info[i] or 'values' in self._info[i]: ax = self._axisCopy(i) if 'cols' in ax: #print " slicing columns..", array(ax['cols']), cols sl = np.array(ax['cols'])[cols] if isinstance(sl, np.ndarray): sl = list(sl) ax['cols'] = sl #print " result:", ax['cols'] if 'values' in ax: ax['values'] = np.array(ax['values'])[cols] else: ax = self._info[i] #print " ", ax return ax def prettyInfo(self): s = '' titles = [] maxl = 0 for i in range(len(self._info)-1): ax = self._info[i] axs = '' if 'name' in ax: axs += '"%s"' % str(ax['name']) else: axs += "%d" % i if 'units' in ax: axs += " (%s)" % str(ax['units']) titles.append(axs) if len(axs) > maxl: maxl = len(axs) for i in range(min(self.ndim, len(self._info) - 1)): ax = self._info[i] axs = titles[i] axs += '%s[%d] :' % (' ' * (maxl - len(axs) + 5 - len(str(self.shape[i]))), self.shape[i]) if 'values' in ax: if self.shape[i] > 0: v0 = ax['values'][0] axs += " values: [%g" % (v0) if self.shape[i] > 1: v1 = ax['values'][-1] axs += " ... %g] (step %g)" % (v1, (v1 - v0) / (self.shape[i] - 1)) else: axs += "]" else: axs += " values: []" if 'cols' in ax: axs += " columns: " colstrs = [] for c in range(len(ax['cols'])): col = ax['cols'][c] cs = str(col.get('name', c)) if 'units' in col: cs += " (%s)" % col['units'] colstrs.append(cs) axs += '[' + ', '.join(colstrs) + ']' s += axs + "\n" s += str(self._info[-1]) return s def __repr__(self): return "%s\n-----------------------------------------------\n%s" % (self.view(np.ndarray).__repr__(), self.prettyInfo()) def __str__(self): return self.__repr__() def axisCollapsingFn(self, fn, axis=None, *args, **kargs): fn = getattr(self._data, fn) if axis is None: return fn(axis, *args, **kargs) else: info = self.infoCopy() axis = self._interpretAxis(axis) info.pop(axis) return MetaArray(fn(axis, *args, **kargs), info=info) def mean(self, axis=None, *args, **kargs): return self.axisCollapsingFn('mean', axis, *args, **kargs) def min(self, axis=None, *args, **kargs): return self.axisCollapsingFn('min', axis, *args, **kargs) def max(self, axis=None, *args, **kargs): return self.axisCollapsingFn('max', axis, *args, **kargs) def transpose(self, *args): if len(args) == 1 and hasattr(args[0], '__iter__'): order = args[0] else: order = args order = [self._interpretAxis(ax) for ax in order] infoOrder = order + list(range(len(order), len(self._info))) info = [self._info[i] for i in infoOrder] order = order + list(range(len(order), self.ndim)) try: if self._isHDF: return MetaArray(np.array(self._data).transpose(order), info=info) else: return MetaArray(self._data.transpose(order), info=info) except: print(order) raise #### File I/O Routines def readFile(self, filename, **kwargs): """Load the data and meta info stored in *filename* Different arguments are allowed depending on the type of file. For HDF5 files: *writable* (bool) if True, then any modifications to data in the array will be stored to disk. *readAllData* (bool) if True, then all data in the array is immediately read from disk and the file is closed (this is the default for files < 500MB). Otherwise, the file will be left open and data will be read only as requested (this is the default for files >= 500MB). """ ## decide which read function to use with open(filename, 'rb') as fd: magic = fd.read(8) if magic == b'\x89HDF\r\n\x1a\n': fd.close() self._readHDF5(filename, **kwargs) self._isHDF = True else: fd.seek(0) meta = MetaArray._readMeta(fd) if not kwargs.get("readAllData", True): self._data = np.empty(meta['shape'], dtype=meta['type']) if 'version' in meta: ver = meta['version'] else: ver = 1 rFuncName = '_readData%s' % str(ver) if not hasattr(MetaArray, rFuncName): raise Exception("This MetaArray library does not support array version '%s'" % ver) rFunc = getattr(self, rFuncName) rFunc(fd, meta, **kwargs) self._isHDF = False @staticmethod def _readMeta(fd): """Read meta array from the top of a file. Read lines until a blank line is reached. This function should ideally work for ALL versions of MetaArray. """ meta = u'' ## Read meta information until the first blank line while True: line = fd.readline().strip() if line == '': break meta += line ret = eval(meta) #print ret return ret def _readData1(self, fd, meta, mmap=False, **kwds): ## Read array data from the file descriptor for MetaArray v1 files ## read in axis values for any axis that specifies a length frameSize = 1 for ax in meta['info']: if 'values_len' in ax: ax['values'] = np.frombuffer(fd.read(ax['values_len']), dtype=ax['values_type']) frameSize *= ax['values_len'] del ax['values_len'] del ax['values_type'] self._info = meta['info'] if not kwds.get("readAllData", True): return ## the remaining data is the actual array if mmap: subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape']) else: subarr = np.frombuffer(fd.read(), dtype=meta['type']) subarr.shape = meta['shape'] self._data = subarr def _readData2(self, fd, meta, mmap=False, subset=None, **kwds): ## read in axis values dynAxis = None frameSize = 1 ## read in axis values for any axis that specifies a length for i in range(len(meta['info'])): ax = meta['info'][i] if 'values_len' in ax: if ax['values_len'] == 'dynamic': if dynAxis is not None: raise Exception("MetaArray has more than one dynamic axis! (this is not allowed)") dynAxis = i else: ax['values'] = np.frombuffer(fd.read(ax['values_len']), dtype=ax['values_type']) frameSize *= ax['values_len'] del ax['values_len'] del ax['values_type'] self._info = meta['info'] if not kwds.get("readAllData", True): return ## No axes are dynamic, just read the entire array in at once if dynAxis is None: if meta['type'] == 'object': if mmap: raise Exception('memmap not supported for arrays with dtype=object') subarr = pickle.loads(fd.read()) else: if mmap: subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape']) else: subarr = np.frombuffer(fd.read(), dtype=meta['type']) subarr.shape = meta['shape'] ## One axis is dynamic, read in a frame at a time else: if mmap: raise Exception('memmap not supported for non-contiguous arrays. Use rewriteContiguous() to convert.') ax = meta['info'][dynAxis] xVals = [] frames = [] frameShape = list(meta['shape']) frameShape[dynAxis] = 1 frameSize = np.prod(frameShape) n = 0 while True: ## Extract one non-blank line while True: line = fd.readline() if line != '\n': break if line == '': break ## evaluate line inf = eval(line) ## read data block #print "read %d bytes as %s" % (inf['len'], meta['type']) if meta['type'] == 'object': data = pickle.loads(fd.read(inf['len'])) else: data = np.frombuffer(fd.read(inf['len']), dtype=meta['type']) if data.size != frameSize * inf['numFrames']: #print data.size, frameSize, inf['numFrames'] raise Exception("Wrong frame size in MetaArray file! (frame %d)" % n) ## read in data block shape = list(frameShape) shape[dynAxis] = inf['numFrames'] data.shape = shape if subset is not None: dSlice = subset[dynAxis] if dSlice.start is None: dStart = 0 else: dStart = max(0, dSlice.start - n) if dSlice.stop is None: dStop = data.shape[dynAxis] else: dStop = min(data.shape[dynAxis], dSlice.stop - n) newSubset = list(subset[:]) newSubset[dynAxis] = slice(dStart, dStop) if dStop > dStart: frames.append(data[tuple(newSubset)].copy()) else: frames.append(data) n += inf['numFrames'] if 'xVals' in inf: xVals.extend(inf['xVals']) subarr = np.concatenate(frames, axis=dynAxis) if len(xVals)> 0: ax['values'] = np.array(xVals, dtype=ax['values_type']) del ax['values_len'] del ax['values_type'] self._info = meta['info'] self._data = subarr def _readHDF5(self, fileName, readAllData=None, writable=False, **kargs): if 'close' in kargs and readAllData is None: ## for backward compatibility readAllData = kargs['close'] if readAllData is True and writable is True: raise Exception("Incompatible arguments: readAllData=True and writable=True") if not HAVE_HDF5: try: assert writable==False assert readAllData != False self._readHDF5Remote(fileName) return except: raise Exception("The file '%s' is HDF5-formatted, but the HDF5 library (h5py) was not found." % fileName) ## by default, readAllData=True for files < 500MB if readAllData is None: size = os.stat(fileName).st_size readAllData = (size < 500e6) if writable is True: mode = 'r+' else: mode = 'r' f = h5py.File(fileName, mode) ver = f.attrs['MetaArray'] try: ver = ver.decode('utf-8') except: pass if ver > MetaArray.version: print("Warning: This file was written with MetaArray version %s, but you are using version %s. (Will attempt to read anyway)" % (str(ver), str(MetaArray.version))) meta = MetaArray.readHDF5Meta(f['info']) self._info = meta if writable or not readAllData: ## read all data, convert to ndarray, close file self._data = f['data'] self._openFile = f else: self._data = f['data'][:] f.close() def _readHDF5Remote(self, fileName): ## Used to read HDF5 files via remote process. ## This is needed in the case that HDF5 is not importable due to the use of python-dbg. proc = getattr(MetaArray, '_hdf5Process', None) if proc == False: raise Exception('remote read failed') if proc is None: from .. import multiprocess as mp #print "new process" proc = mp.Process(executable='/usr/bin/python') proc.setProxyOptions(deferGetattr=True) MetaArray._hdf5Process = proc MetaArray._h5py_metaarray = proc._import('pyqtgraph.metaarray') ma = MetaArray._h5py_metaarray.MetaArray(file=fileName) self._data = ma.asarray()._getValue() self._info = ma._info._getValue() @staticmethod def mapHDF5Array(data, writable=False): off = data.id.get_offset() if writable: mode = 'r+' else: mode = 'r' if off is None: raise Exception("This dataset uses chunked storage; it can not be memory-mapped. (store using mappable=True)") return np.memmap(filename=data.file.filename, offset=off, dtype=data.dtype, shape=data.shape, mode=mode) @staticmethod def readHDF5Meta(root, mmap=False): data = {} ## Pull list of values from attributes and child objects for k in root.attrs: val = root.attrs[k] if isinstance(val, bytes): val = val.decode() if isinstance(val, str): ## strings need to be re-evaluated to their original types try: val = eval(val) except: raise Exception('Can not evaluate string: "%s"' % val) data[k] = val for k in root: obj = root[k] if isinstance(obj, h5py.Group): val = MetaArray.readHDF5Meta(obj) elif isinstance(obj, h5py.Dataset): if mmap: val = MetaArray.mapHDF5Array(obj) else: val = obj[:] else: raise Exception("Don't know what to do with type '%s'" % str(type(obj))) data[k] = val typ = root.attrs['_metaType_'] try: typ = typ.decode('utf-8') except: pass del data['_metaType_'] if typ == 'dict': return data elif typ == 'list' or typ == 'tuple': d2 = [None]*len(data) for k in data: d2[int(k)] = data[k] if typ == 'tuple': d2 = tuple(d2) return d2 else: raise Exception("Don't understand metaType '%s'" % typ) def write(self, fileName, **opts): """Write this object to a file. The object can be restored by calling MetaArray(file=fileName) opts: appendAxis: the name (or index) of the appendable axis. Allows the array to grow. appendKeys: a list of keys (other than "values") for metadata to append to on the appendable axis. compression: None, 'gzip' (good compression), 'lzf' (fast compression), etc. chunks: bool or tuple specifying chunk shape """ if USE_HDF5 is False: return self.writeMa(fileName, **opts) elif HAVE_HDF5 is True: return self.writeHDF5(fileName, **opts) else: raise Exception("h5py is required for writing .ma hdf5 files, but it could not be imported.") def writeMeta(self, fileName): """Used to re-write meta info to the given file. This feature is only available for HDF5 files.""" f = h5py.File(fileName, 'r+') if f.attrs['MetaArray'] != MetaArray.version: raise Exception("The file %s was created with a different version of MetaArray. Will not modify." % fileName) del f['info'] self.writeHDF5Meta(f, 'info', self._info) f.close() def writeHDF5(self, fileName, **opts): ## default options for writing datasets comp = self.defaultCompression if isinstance(comp, tuple): comp, copts = comp else: copts = None dsOpts = { 'compression': comp, 'chunks': True, } if copts is not None: dsOpts['compression_opts'] = copts ## if there is an appendable axis, then we can guess the desired chunk shape (optimized for appending) appAxis = opts.get('appendAxis', None) if appAxis is not None: appAxis = self._interpretAxis(appAxis) cs = [min(100000, x) for x in self.shape] cs[appAxis] = 1 dsOpts['chunks'] = tuple(cs) ## if there are columns, then we can guess a different chunk shape ## (read one column at a time) else: cs = [min(100000, x) for x in self.shape] for i in range(self.ndim): if 'cols' in self._info[i]: cs[i] = 1 dsOpts['chunks'] = tuple(cs) ## update options if they were passed in for k in dsOpts: if k in opts: dsOpts[k] = opts[k] ## If mappable is in options, it disables chunking/compression if opts.get('mappable', False): dsOpts = { 'chunks': None, 'compression': None } ## set maximum shape to allow expansion along appendAxis append = False if appAxis is not None: maxShape = list(self.shape) ax = self._interpretAxis(appAxis) maxShape[ax] = None if os.path.exists(fileName): append = True dsOpts['maxshape'] = tuple(maxShape) else: dsOpts['maxshape'] = None if append: f = h5py.File(fileName, 'r+') if f.attrs['MetaArray'] != MetaArray.version: raise Exception("The file %s was created with a different version of MetaArray. Will not modify." % fileName) ## resize data and write in new values data = f['data'] shape = list(data.shape) shape[ax] += self.shape[ax] data.resize(tuple(shape)) sl = [slice(None)] * len(data.shape) sl[ax] = slice(-self.shape[ax], None) data[tuple(sl)] = self.view(np.ndarray) ## add axis values if they are present. axKeys = ["values"] axKeys.extend(opts.get("appendKeys", [])) axInfo = f['info'][str(ax)] for key in axKeys: if key in axInfo: v = axInfo[key] v2 = self._info[ax][key] shape = list(v.shape) shape[0] += v2.shape[0] v.resize(shape) v[-v2.shape[0]:] = v2 else: raise TypeError('Cannot append to axis info key "%s"; this key is not present in the target file.' % key) f.close() else: f = h5py.File(fileName, 'w') f.attrs['MetaArray'] = MetaArray.version #print dsOpts f.create_dataset('data', data=self.view(np.ndarray), **dsOpts) ## dsOpts is used when storing meta data whenever an array is encountered ## however, 'chunks' will no longer be valid for these arrays if it specifies a chunk shape. ## 'maxshape' is right-out. if isinstance(dsOpts['chunks'], tuple): dsOpts['chunks'] = True if 'maxshape' in dsOpts: del dsOpts['maxshape'] self.writeHDF5Meta(f, 'info', self._info, **dsOpts) f.close() def writeHDF5Meta(self, root, name, data, **dsOpts): if isinstance(data, np.ndarray): dsOpts['maxshape'] = (None,) + data.shape[1:] root.create_dataset(name, data=data, **dsOpts) elif isinstance(data, list) or isinstance(data, tuple): gr = root.create_group(name) if isinstance(data, list): gr.attrs['_metaType_'] = 'list' else: gr.attrs['_metaType_'] = 'tuple' #n = int(np.log10(len(data))) + 1 for i in range(len(data)): self.writeHDF5Meta(gr, str(i), data[i], **dsOpts) elif isinstance(data, dict): gr = root.create_group(name) gr.attrs['_metaType_'] = 'dict' for k, v in data.items(): self.writeHDF5Meta(gr, k, v, **dsOpts) elif isinstance(data, int) or isinstance(data, float) or isinstance(data, np.integer) or isinstance(data, np.floating): root.attrs[name] = data else: try: ## strings, bools, None are stored as repr() strings root.attrs[name] = repr(data) except: print("Can not store meta data of type '%s' in HDF5. (key is '%s')" % (str(type(data)), str(name))) raise def writeMa(self, fileName, appendAxis=None, newFile=False): """Write an old-style .ma file""" meta = {'shape':self.shape, 'type':str(self.dtype), 'info':self.infoCopy(), 'version':MetaArray.version} axstrs = [] ## copy out axis values for dynamic axis if requested if appendAxis is not None: if MetaArray.isNameType(appendAxis): appendAxis = self._interpretAxis(appendAxis) ax = meta['info'][appendAxis] ax['values_len'] = 'dynamic' if 'values' in ax: ax['values_type'] = str(ax['values'].dtype) dynXVals = ax['values'] del ax['values'] else: dynXVals = None ## Generate axis data string, modify axis info so we know how to read it back in later for ax in meta['info']: if 'values' in ax: axstrs.append(ax['values'].tostring()) ax['values_len'] = len(axstrs[-1]) ax['values_type'] = str(ax['values'].dtype) del ax['values'] ## Decide whether to output the meta block for a new file if not newFile: ## If the file does not exist or its size is 0, then we must write the header newFile = (not os.path.exists(fileName)) or (os.stat(fileName).st_size == 0) ## write data to file if appendAxis is None or newFile: fd = open(fileName, 'wb') fd.write(str(meta) + '\n\n') for ax in axstrs: fd.write(ax) else: fd = open(fileName, 'ab') if self.dtype != object: dataStr = self.view(np.ndarray).tostring() else: dataStr = pickle.dumps(self.view(np.ndarray)) #print self.size, len(dataStr), self.dtype if appendAxis is not None: frameInfo = {'len':len(dataStr), 'numFrames':self.shape[appendAxis]} if dynXVals is not None: frameInfo['xVals'] = list(dynXVals) fd.write('\n'+str(frameInfo)+'\n') fd.write(dataStr) fd.close() def writeCsv(self, fileName=None): """Write 2D array to CSV file or return the string if no filename is given""" if self.ndim > 2: raise Exception("CSV Export is only for 2D arrays") if fileName is not None: file = open(fileName, 'w') ret = '' if 'cols' in self._info[0]: s = ','.join([x['name'] for x in self._info[0]['cols']]) + '\n' if fileName is not None: file.write(s) else: ret += s for row in range(0, self.shape[1]): s = ','.join(["%g" % x for x in self[:, row]]) + '\n' if fileName is not None: file.write(s) else: ret += s if fileName is not None: file.close() else: return ret if __name__ == '__main__': ## Create an array with every option possible arr = np.zeros((2, 5, 3, 5), dtype=int) for i in range(arr.shape[0]): for j in range(arr.shape[1]): for k in range(arr.shape[2]): for l in range(arr.shape[3]): arr[i,j,k,l] = (i+1)*1000 + (j+1)*100 + (k+1)*10 + (l+1) info = [ axis('Axis1'), axis('Axis2', values=[1,2,3,4,5]), axis('Axis3', cols=[ ('Ax3Col1'), ('Ax3Col2', 'mV', 'Axis3 Column2'), (('Ax3','Col3'), 'A', 'Axis3 Column3')]), {'name': 'Axis4', 'values': np.array([1.1, 1.2, 1.3, 1.4, 1.5]), 'units': 's'}, {'extra': 'info'} ] ma = MetaArray(arr, info=info) print("==== Original Array =======") print(ma) print("\n\n") #### Tests follow: #### Index/slice tests: check that all values and meta info are correct after slice print("\n -- normal integer indexing\n") print("\n ma[1]") print(ma[1]) print("\n ma[1, 2:4]") print(ma[1, 2:4]) print("\n ma[1, 1:5:2]") print(ma[1, 1:5:2]) print("\n -- named axis indexing\n") print("\n ma['Axis2':3]") print(ma['Axis2':3]) print("\n ma['Axis2':3:5]") print(ma['Axis2':3:5]) print("\n ma[1, 'Axis2':3]") print(ma[1, 'Axis2':3]) print("\n ma[:, 'Axis2':3]") print(ma[:, 'Axis2':3]) print("\n ma['Axis2':3, 'Axis4':0:2]") print(ma['Axis2':3, 'Axis4':0:2]) print("\n -- column name indexing\n") print("\n ma['Axis3':'Ax3Col1']") print(ma['Axis3':'Ax3Col1']) print("\n ma['Axis3':('Ax3','Col3')]") print(ma['Axis3':('Ax3','Col3')]) print("\n ma[:, :, 'Ax3Col2']") print(ma[:, :, 'Ax3Col2']) print("\n ma[:, :, ('Ax3','Col3')]") print(ma[:, :, ('Ax3','Col3')]) print("\n -- axis value range indexing\n") print("\n ma['Axis2':1.5:4.5]") print(ma['Axis2':1.5:4.5]) print("\n ma['Axis4':1.15:1.45]") print(ma['Axis4':1.15:1.45]) print("\n ma['Axis4':1.15:1.25]") print(ma['Axis4':1.15:1.25]) print("\n -- list indexing\n") print("\n ma[:, [0,2,4]]") print(ma[:, [0,2,4]]) print("\n ma['Axis4':[0,2,4]]") print(ma['Axis4':[0,2,4]]) print("\n ma['Axis3':[0, ('Ax3','Col3')]]") print(ma['Axis3':[0, ('Ax3','Col3')]]) print("\n -- boolean indexing\n") print("\n ma[:, array([True, True, False, True, False])]") print(ma[:, np.array([True, True, False, True, False])]) print("\n ma['Axis4':array([True, False, False, False])]") print(ma['Axis4':np.array([True, False, False, False])]) #### Array operations # - Concatenate # - Append # - Extend # - Rowsort #### File I/O tests print("\n================ File I/O Tests ===================\n") tf = 'test.ma' # write whole array print("\n -- write/read test") ma.write(tf) ma2 = MetaArray(file=tf) #print ma2 print("\nArrays are equivalent:", (ma == ma2).all()) #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() os.remove(tf) # CSV write # append mode print("\n================append test (%s)===============" % tf) ma['Axis2':0:2].write(tf, appendAxis='Axis2') for i in range(2,ma.shape[1]): ma['Axis2':[i]].write(tf, appendAxis='Axis2') ma2 = MetaArray(file=tf) #print ma2 print("\nArrays are equivalent:", (ma == ma2).all()) #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() os.remove(tf) ## memmap test print("\n==========Memmap test============") ma.write(tf, mappable=True) ma2 = MetaArray(file=tf, mmap=True) print("\nArrays are equivalent:", (ma == ma2).all()) os.remove(tf) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/metaarray/__init__.py000066400000000000000000000000311421045507400237430ustar00rootroot00000000000000from .MetaArray import * pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/metaarray/license.txt000066400000000000000000000021331421045507400240220ustar00rootroot00000000000000Copyright (c) 2010 Luke Campagnola ('luke.campagnola@%s.com' % 'gmail') The MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/metaarray/readMeta.m000066400000000000000000000031771421045507400235500ustar00rootroot00000000000000function f = readMeta(file) info = hdf5info(file); f = readMetaRecursive(info.GroupHierarchy.Groups(1)); end function f = readMetaRecursive(root) typ = 0; for i = 1:length(root.Attributes) if strcmp(root.Attributes(i).Shortname, '_metaType_') typ = root.Attributes(i).Value.Data; break end end if typ == 0 printf('group has no _metaType_') typ = 'dict'; end list = 0; if strcmp(typ, 'list') || strcmp(typ, 'tuple') data = {}; list = 1; elseif strcmp(typ, 'dict') data = struct(); else printf('Unrecognized meta type %s', typ); data = struct(); end for i = 1:length(root.Attributes) name = root.Attributes(i).Shortname; if strcmp(name, '_metaType_') continue end val = root.Attributes(i).Value; if isa(val, 'hdf5.h5string') val = val.Data; end if list ind = str2num(name)+1; data{ind} = val; else data.(name) = val; end end for i = 1:length(root.Datasets) fullName = root.Datasets(i).Name; name = stripName(fullName); file = root.Datasets(i).Filename; data2 = hdf5read(file, fullName); if list ind = str2num(name)+1; data{ind} = data2; else data.(name) = data2; end end for i = 1:length(root.Groups) name = stripName(root.Groups(i).Name); data2 = readMetaRecursive(root.Groups(i)); if list ind = str2num(name)+1; data{ind} = data2; else data.(name) = data2; end end f = data; return; end function f = stripName(str) inds = strfind(str, '/'); if isempty(inds) f = str; else f = str(inds(length(inds))+1:length(str)); end end pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/multiprocess/000077500000000000000000000000001421045507400224045ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/multiprocess/__init__.py000066400000000000000000000016311421045507400245160ustar00rootroot00000000000000""" Multiprocessing utility library (parallelization done the way I like it) Luke Campagnola 2012.06.10 This library provides: - simple mechanism for starting a new python interpreter process that can be controlled from the original process (this allows, for example, displaying and manipulating plots in a remote process while the parent process is free to do other work) - proxy system that allows objects hosted in the remote process to be used as if they were local - Qt signal connection between processes - very simple in-line parallelization (fork only; does not work on windows) for number-crunching TODO: allow remote processes to serve as rendering engines that pass pixmaps back to the parent process for display (RemoteGraphicsView class) """ from .parallelizer import CanceledError, Parallelize from .processes import * from .remoteproxy import ClosedError, NoResultError, proxy pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/multiprocess/bootstrap.py000066400000000000000000000033511421045507400247750ustar00rootroot00000000000000"""For starting up remote processes""" import importlib import os import pickle import sys if __name__ == '__main__': if hasattr(os, 'setpgrp'): os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process if sys.version[0] == '3': #name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin.buffer) opts = pickle.load(sys.stdin.buffer) else: #name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin) opts = pickle.load(sys.stdin) #print "key:", ' '.join([str(ord(x)) for x in authkey]) path = opts.pop('path', None) if path is not None: if isinstance(path, str): # if string, just insert this into the path sys.path.insert(0, path) else: # if list, then replace the entire sys.path ## modify sys.path in place--no idea who already has a reference to the existing list. while len(sys.path) > 0: sys.path.pop() sys.path.extend(path) pyqtapis = opts.pop('pyqtapis', None) if pyqtapis is not None: try: from PyQt5 import sip except ImportError: import sip for k,v in pyqtapis.items(): sip.setapi(k, v) qt_lib = opts.pop('qt_lib', None) if qt_lib is not None: globals()[qt_lib] = importlib.import_module(qt_lib) targetStr = opts.pop('targetStr') try: target = pickle.loads(targetStr) ## unpickling the target should import everything we need except: print("Current sys.path:", sys.path) raise target(**opts) ## Send all other options to the target function sys.exit(0) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/multiprocess/parallelizer.py000066400000000000000000000302341421045507400254460ustar00rootroot00000000000000import multiprocessing import os import re import sys import time from .processes import ForkedProcess from .remoteproxy import ClosedError class CanceledError(Exception): """Raised when the progress dialog is canceled during a processing operation.""" pass class Parallelize(object): """ Class for ultra-simple inline parallelization on multi-core CPUs Example:: ## Here is the serial (single-process) task: tasks = [1, 2, 4, 8] results = [] for task in tasks: result = processTask(task) results.append(result) print(results) ## Here is the parallelized version: tasks = [1, 2, 4, 8] results = [] with Parallelize(tasks, workers=4, results=results) as tasker: for task in tasker: result = processTask(task) tasker.results.append(result) print(results) The only major caveat is that *result* in the example above must be picklable, since it is automatically sent via pipe back to the parent process. """ def __init__(self, tasks=None, workers=None, block=True, progressDialog=None, randomReseed=True, **kwds): """ =============== =================================================================== **Arguments:** tasks list of objects to be processed (Parallelize will determine how to distribute the tasks). If unspecified, then each worker will receive a single task with a unique id number. workers number of worker processes or None to use number of CPUs in the system progressDialog optional dict of arguments for ProgressDialog to update while tasks are processed randomReseed If True, each forked process will reseed its random number generator to ensure independent results. Works with the built-in random and numpy.random. kwds objects to be shared by proxy with child processes (they will appear as attributes of the tasker) =============== =================================================================== """ ## Generate progress dialog. ## Note that we want to avoid letting forked child processes play with progress dialogs.. self.showProgress = False if progressDialog is not None: self.showProgress = True if isinstance(progressDialog, str): progressDialog = {'labelText': progressDialog} from ..widgets.ProgressDialog import ProgressDialog self.progressDlg = ProgressDialog(**progressDialog) if workers is None: workers = self.suggestedWorkerCount() if not hasattr(os, 'fork'): workers = 1 self.workers = workers if tasks is None: tasks = range(workers) self.tasks = list(tasks) self.reseed = randomReseed self.kwds = kwds.copy() self.kwds['_taskStarted'] = self._taskStarted def __enter__(self): self.proc = None if self.workers == 1: return self.runSerial() else: return self.runParallel() def __exit__(self, *exc_info): if self.proc is not None: ## worker exceptOccurred = exc_info[0] is not None ## hit an exception during processing. try: if exceptOccurred: sys.excepthook(*exc_info) finally: #print os.getpid(), 'exit' os._exit(1 if exceptOccurred else 0) else: ## parent if self.showProgress: try: self.progressDlg.__exit__(None, None, None) except Exception: pass def runSerial(self): if self.showProgress: self.progressDlg.__enter__() self.progressDlg.setMaximum(len(self.tasks)) self.progress = {os.getpid(): []} return Tasker(self, None, self.tasks, self.kwds) def runParallel(self): self.childs = [] ## break up tasks into one set per worker workers = self.workers chunks = [[] for i in range(workers)] i = 0 for i in range(len(self.tasks)): chunks[i%workers].append(self.tasks[i]) ## fork and assign tasks to each worker for i in range(workers): proc = ForkedProcess(target=None, preProxy=self.kwds, randomReseed=self.reseed) if not proc.isParent: self.proc = proc return Tasker(self, proc, chunks[i], proc.forkedProxies) else: self.childs.append(proc) ## Keep track of the progress of each worker independently. self.progress = dict([(ch.childPid, []) for ch in self.childs]) ## for each child process, self.progress[pid] is a list ## of task indexes. The last index is the task currently being ## processed; all others are finished. try: if self.showProgress: self.progressDlg.__enter__() self.progressDlg.setMaximum(len(self.tasks)) ## process events from workers until all have exited. activeChilds = self.childs[:] self.exitCodes = [] pollInterval = 0.01 while len(activeChilds) > 0: waitingChildren = 0 rem = [] for ch in activeChilds: try: n = ch.processRequests() if n > 0: waitingChildren += 1 except ClosedError: #print ch.childPid, 'process finished' rem.append(ch) if self.showProgress: self.progressDlg += 1 #print "remove:", [ch.childPid for ch in rem] for ch in rem: activeChilds.remove(ch) while True: try: pid, exitcode = os.waitpid(ch.childPid, 0) self.exitCodes.append(exitcode) break except OSError as ex: if ex.errno == 4: ## If we get this error, just try again continue #print "Ignored system call interruption" else: raise #print [ch.childPid for ch in activeChilds] if self.showProgress and self.progressDlg.wasCanceled(): for ch in activeChilds: ch.kill() raise CanceledError() ## adjust polling interval--prefer to get exactly 1 event per poll cycle. if waitingChildren > 1: pollInterval *= 0.7 elif waitingChildren == 0: pollInterval /= 0.7 pollInterval = max(min(pollInterval, 0.5), 0.0005) ## but keep it within reasonable limits time.sleep(pollInterval) finally: if self.showProgress: self.progressDlg.__exit__(None, None, None) for ch in self.childs: ch.join() if len(self.exitCodes) < len(self.childs): raise Exception("Parallelizer started %d processes but only received exit codes from %d." % (len(self.childs), len(self.exitCodes))) for code in self.exitCodes: if code != 0: raise Exception("Error occurred in parallel-executed subprocess (console output may have more information).") return [] ## no tasks for parent process. @staticmethod def suggestedWorkerCount(): if 'linux' in sys.platform: ## I think we can do a little better here.. ## cpu_count does not consider that there is little extra benefit to using hyperthreaded cores. try: cores = {} pid = None with open('/proc/cpuinfo') as fd: for line in fd: m = re.match(r'physical id\s+:\s+(\d+)', line) if m is not None: pid = m.groups()[0] m = re.match(r'cpu cores\s+:\s+(\d+)', line) if m is not None: cores[pid] = int(m.groups()[0]) return sum(cores.values()) except: return multiprocessing.cpu_count() else: return multiprocessing.cpu_count() def _taskStarted(self, pid, i, **kwds): ## called remotely by tasker to indicate it has started working on task i #print pid, 'reported starting task', i if self.showProgress: if len(self.progress[pid]) > 0: self.progressDlg += 1 if pid == os.getpid(): ## single-worker process if self.progressDlg.wasCanceled(): raise CanceledError() self.progress[pid].append(i) class Tasker(object): def __init__(self, parallelizer, process, tasks, kwds): self.proc = process self.par = parallelizer self.tasks = tasks for k, v in kwds.items(): setattr(self, k, v) def __iter__(self): ## we could fix this up such that tasks are retrieved from the parent process one at a time.. for i, task in enumerate(self.tasks): self.index = i #print os.getpid(), 'starting task', i self._taskStarted(os.getpid(), i, _callSync='off') yield task if self.proc is not None: #print os.getpid(), 'no more tasks' self.proc.close() def process(self): """ Process requests from parent. Usually it is not necessary to call this unless you would like to receive messages (such as exit requests) during an iteration. """ if self.proc is not None: self.proc.processRequests() def numWorkers(self): """ Return the number of parallel workers """ return self.par.workers #class Parallelizer: #""" #Use:: #p = Parallelizer() #with p(4) as i: #p.finish(do_work(i)) #print p.results() #""" #def __init__(self): #pass #def __call__(self, n): #self.replies = [] #self.conn = None ## indicates this is the parent process #return Session(self, n) #def finish(self, data): #if self.conn is None: #self.replies.append((self.i, data)) #else: ##print "send", self.i, data #self.conn.send((self.i, data)) #os._exit(0) #def result(self): #print self.replies #class Session: #def __init__(self, par, n): #self.par = par #self.n = n #def __enter__(self): #self.childs = [] #for i in range(1, self.n): #c1, c2 = multiprocessing.Pipe() #pid = os.fork() #if pid == 0: ## child #self.par.i = i #self.par.conn = c2 #self.childs = None #c1.close() #return i #else: #self.childs.append(c1) #c2.close() #self.par.i = 0 #return 0 #def __exit__(self, *exc_info): #if exc_info[0] is not None: #sys.excepthook(*exc_info) #if self.childs is not None: #self.par.replies.extend([conn.recv() for conn in self.childs]) #else: #self.par.finish(None) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/multiprocess/processes.py000066400000000000000000000553121421045507400247720ustar00rootroot00000000000000import atexit import inspect import multiprocessing.connection import os import signal import subprocess import sys import time try: import cPickle as pickle except ImportError: import pickle from ..Qt import QT_LIB, mkQApp from ..util import cprint # color printing for debugging from .remoteproxy import ( ClosedError, LocalObjectProxy, NoResultError, ObjectProxy, RemoteEventHandler, ) __all__ = ['Process', 'QtProcess', 'ForkedProcess', 'ClosedError', 'NoResultError'] class Process(RemoteEventHandler): """ Bases: RemoteEventHandler This class is used to spawn and control a new python interpreter. It uses subprocess.Popen to start the new process and communicates with it using multiprocessing.Connection objects over a network socket. By default, the remote process will immediately enter an event-processing loop that carries out requests send from the parent process. Remote control works mainly through proxy objects:: proc = Process() ## starts process, returns handle rsys = proc._import('sys') ## asks remote process to import 'sys', returns ## a proxy which references the imported module rsys.stdout.write('hello\n') ## This message will be printed from the remote ## process. Proxy objects can usually be used ## exactly as regular objects are. proc.close() ## Request the remote process shut down Requests made via proxy objects may be synchronous or asynchronous and may return objects either by proxy or by value (if they are picklable). See ProxyObject for more information. """ _process_count = 1 # just used for assigning colors to each process for debugging def __init__(self, name=None, target=None, executable=None, copySysPath=True, debug=False, timeout=20, wrapStdout=None, pyqtapis=None): """ ============== ============================================================= **Arguments:** name Optional name for this process used when printing messages from the remote process. target Optional function to call after starting remote process. By default, this is startEventLoop(), which causes the remote process to handle requests from the parent process until it is asked to quit. If you wish to specify a different target, it must be picklable (bound methods are not). copySysPath If True, copy the contents of sys.path to the remote process. If False, then only the path required to import pyqtgraph is added. debug If True, print detailed information about communication with the child process. wrapStdout If True (default on windows) then stdout and stderr from the child process will be caught by the parent process and forwarded to its stdout/stderr. This provides a workaround for a python bug: http://bugs.python.org/issue3905 but has the side effect that child output is significantly delayed relative to the parent output. pyqtapis Optional dictionary of PyQt API version numbers to set before importing pyqtgraph in the remote process. ============== ============================================================= """ if target is None: target = startEventLoop if name is None: name = str(self) if executable is None: executable = sys.executable self.debug = 7 if debug is True else False # 7 causes printing in white ## random authentication key authkey = os.urandom(20) ## Windows seems to have a hard time with hmac if sys.platform.startswith('win'): authkey = None #print "key:", ' '.join([str(ord(x)) for x in authkey]) ## Listen for connection from remote process (and find free port number) l = multiprocessing.connection.Listener(('localhost', 0), authkey=authkey) port = l.address[1] ## start remote process, instruct it to run target function if copySysPath: sysPath = sys.path else: # what path do we need to make target importable? mod = inspect.getmodule(target) modroot = sys.modules[mod.__name__.split('.')[0]] sysPath = os.path.abspath(os.path.join(os.path.dirname(modroot.__file__), '..')) bootstrap = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bootstrap.py')) self.debugMsg('Starting child process (%s %s)' % (executable, bootstrap)) # Decide on printing color for this process if debug: procDebug = (Process._process_count%6) + 1 # pick a color for this process to print in Process._process_count += 1 else: procDebug = False if wrapStdout is None: wrapStdout = sys.platform.startswith('win') if wrapStdout: ## note: we need all three streams to have their own PIPE due to this bug: ## http://bugs.python.org/issue3905 stdout = subprocess.PIPE stderr = subprocess.PIPE self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE, stdout=stdout, stderr=stderr) ## to circumvent the bug and still make the output visible, we use ## background threads to pass data from pipes to stdout/stderr self._stdoutForwarder = FileForwarder(self.proc.stdout, "stdout", procDebug) self._stderrForwarder = FileForwarder(self.proc.stderr, "stderr", procDebug) else: self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE) targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to ## set its sys.path properly before unpickling the target pid = os.getpid() # we must send pid to child because windows does not have getppid # When running in a venv on Windows platform, since Python >= 3.7.3, the launched # subprocess is a grandchild instead of a child, leading to self.proc.pid not being # the pid of the launched subprocess. # https://bugs.python.org/issue38905 # # As a workaround, when we detect such a situation, we perform exchange of pids via # the multiprocessing connection. Technically, only the launched subprocess needs to # send its pid back. Practically, we hijack the ppid parameter to indicate to the # subprocess that pid exchange is needed. xchg_pids = sys.platform == 'win32' and os.getenv('VIRTUAL_ENV') is not None ## Send everything the remote process needs to start correctly data = dict( name=name+'_child', port=port, authkey=authkey, ppid=pid if not xchg_pids else None, targetStr=targetStr, path=sysPath, qt_lib=QT_LIB, debug=procDebug, pyqtapis=pyqtapis, ) pickle.dump(data, self.proc.stdin) self.proc.stdin.close() ## open connection for remote process self.debugMsg('Listening for child process on port %d, authkey=%s..' % (port, repr(authkey))) while True: try: conn = l.accept() break except IOError as err: if err.errno == 4: # interrupted; try again continue else: raise child_pid = self.proc.pid if xchg_pids: # corresponding code is in: # remoteproxy.py::RemoteEventHandler.__init__() conn.send(pid) child_pid = conn.recv() RemoteEventHandler.__init__(self, conn, name+'_parent', pid=child_pid, debug=self.debug) self.debugMsg('Connected to child process.') atexit.register(self.join) def join(self, timeout=10): self.debugMsg('Joining child process..') if self.proc.poll() is None: self.close() start = time.time() while self.proc.poll() is None: if timeout is not None and time.time() - start > timeout: raise Exception('Timed out waiting for remote process to end.') time.sleep(0.05) self.conn.close() # Close remote polling threads, otherwise they will spin continuously if hasattr(self, "_stdoutForwarder"): self._stdoutForwarder.finish.set() self._stderrForwarder.finish.set() self._stdoutForwarder.join() self._stderrForwarder.join() self.debugMsg('Child process exited. (%d)' % self.proc.returncode) def debugMsg(self, msg, *args): if hasattr(self, '_stdoutForwarder'): ## Lock output from subprocess to make sure we do not get line collisions with self._stdoutForwarder.lock: with self._stderrForwarder.lock: RemoteEventHandler.debugMsg(self, msg, *args) else: RemoteEventHandler.debugMsg(self, msg, *args) def startEventLoop(name, port, authkey, ppid, debug=False): if debug: import os cprint.cout(debug, '[%d] connecting to server at port localhost:%d, authkey=%s..\n' % (os.getpid(), port, repr(authkey)), -1) conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) if debug: cprint.cout(debug, '[%d] connected; starting remote proxy.\n' % os.getpid(), -1) global HANDLER #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() HANDLER = RemoteEventHandler(conn, name, ppid, debug=debug) while True: try: HANDLER.processRequests() # exception raised when the loop should exit time.sleep(0.01) except ClosedError: HANDLER.debugMsg('Exiting server loop.') sys.exit(0) class ForkedProcess(RemoteEventHandler): """ ForkedProcess is a substitute for Process that uses os.fork() to generate a new process. This is much faster than starting a completely new interpreter and child processes automatically have a copy of the entire program state from before the fork. This makes it an appealing approach when parallelizing expensive computations. (see also Parallelizer) However, fork() comes with some caveats and limitations: - fork() is not available on Windows. - It is not possible to have a QApplication in both parent and child process (unless both QApplications are created _after_ the call to fork()) Attempts by the forked process to access Qt GUI elements created by the parent will most likely cause the child to crash. - Likewise, database connections are unlikely to function correctly in a forked child. - Threads are not copied by fork(); the new process will have only one thread that starts wherever fork() was called in the parent process. - Forked processes are unceremoniously terminated when join() is called; they are not given any opportunity to clean up. (This prevents them calling any cleanup code that was only intended to be used by the parent process) - Normally when fork()ing, open file handles are shared with the parent process, which is potentially dangerous. ForkedProcess is careful to close all file handles that are not explicitly needed--stdout, stderr, and a single pipe to the parent process. """ def __init__(self, name=None, target=0, preProxy=None, randomReseed=True): """ When initializing, an optional target may be given. If no target is specified, self.eventLoop will be used. If None is given, no target will be called (and it will be up to the caller to properly shut down the forked process) preProxy may be a dict of values that will appear as ObjectProxy in the remote process (but do not need to be sent explicitly since they are available immediately before the call to fork(). Proxies will be availabe as self.proxies[name]. If randomReseed is True, the built-in random and numpy.random generators will be reseeded in the child process. """ self.hasJoined = False if target == 0: target = self.eventLoop if name is None: name = str(self) conn, remoteConn = multiprocessing.Pipe() proxyIDs = {} if preProxy is not None: for k, v in preProxy.items(): proxyId = LocalObjectProxy.registerObject(v) proxyIDs[k] = proxyId ppid = os.getpid() # write this down now; windows doesn't have getppid pid = os.fork() if pid == 0: self.isParent = False ## We are now in the forked process; need to be extra careful what we touch while here. ## - no reading/writing file handles/sockets owned by parent process (stdout is ok) ## - don't touch QtGui or QApplication at all; these are landmines. ## - don't let the process call exit handlers os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process ## close all file handles we do not want shared with parent conn.close() sys.stdin.close() ## otherwise we screw with interactive prompts. fid = remoteConn.fileno() os.closerange(3, fid) os.closerange(fid+1, 4096) ## just guessing on the maximum descriptor count.. ## Override any custom exception hooks def excepthook(*args): import traceback traceback.print_exception(*args) sys.excepthook = excepthook ## Make it harder to access QApplication instance for qtlib in ('PyQt4', 'PySide', 'PyQt5'): if qtlib in sys.modules: sys.modules[qtlib+'.QtGui'].QApplication = None sys.modules.pop(qtlib+'.QtGui', None) sys.modules.pop(qtlib+'.QtCore', None) ## sabotage atexit callbacks atexit._exithandlers = [] atexit.register(lambda: os._exit(0)) if randomReseed: if 'numpy.random' in sys.modules: sys.modules['numpy.random'].seed(os.getpid() ^ int(time.time()*10000%10000)) if 'random' in sys.modules: sys.modules['random'].seed(os.getpid() ^ int(time.time()*10000%10000)) #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() RemoteEventHandler.__init__(self, remoteConn, name+'_child', pid=ppid) self.forkedProxies = {} for name, proxyId in proxyIDs.items(): self.forkedProxies[name] = ObjectProxy(ppid, proxyId=proxyId, typeStr=repr(preProxy[name])) if target is not None: target() else: self.isParent = True self.childPid = pid remoteConn.close() RemoteEventHandler.handlers = {} ## don't want to inherit any of this from the parent. RemoteEventHandler.__init__(self, conn, name+'_parent', pid=pid) atexit.register(self.join) def eventLoop(self): while True: try: self.processRequests() # exception raised when the loop should exit time.sleep(0.01) except ClosedError: break except: print("Error occurred in forked event loop:") sys.excepthook(*sys.exc_info()) sys.exit(0) def join(self, timeout=10): if self.hasJoined: return #os.kill(pid, 9) try: self.close(callSync='sync', timeout=timeout, noCleanup=True) ## ask the child process to exit and require that it return a confirmation. except IOError: ## probably remote process has already quit pass try: os.waitpid(self.childPid, 0) except OSError: ## probably remote process has already quit pass self.conn.close() # don't leak file handles! self.hasJoined = True def kill(self): """Immediately kill the forked remote process. This is generally safe because forked processes are already expected to _avoid_ any cleanup at exit.""" os.kill(self.childPid, signal.SIGKILL) self.hasJoined = True ##Special set of subclasses that implement a Qt event loop instead. class RemoteQtEventHandler(RemoteEventHandler): def __init__(self, *args, **kwds): RemoteEventHandler.__init__(self, *args, **kwds) def startEventTimer(self): from ..Qt import QtCore self.timer = QtCore.QTimer() self.timer.timeout.connect(self.processRequests) self.timer.start(10) def processRequests(self): try: RemoteEventHandler.processRequests(self) except ClosedError: from ..Qt import QtWidgets QtWidgets.QApplication.instance().quit() self.timer.stop() #raise SystemExit class QtProcess(Process): """ QtProcess is essentially the same as Process, with two major differences: - The remote process starts by running startQtEventLoop() which creates a QApplication in the remote process and uses a QTimer to trigger remote event processing. This allows the remote process to have its own GUI. - A QTimer is also started on the parent process which polls for requests from the child process. This allows Qt signals emitted within the child process to invoke slots on the parent process and vice-versa. This can be disabled using processRequests=False in the constructor. Example:: proc = QtProcess() rQtGui = proc._import('PyQt4.QtGui') btn = rQtWidgets.QPushButton('button on child process') btn.show() def slot(): print('slot invoked on parent process') btn.clicked.connect(proxy(slot)) # be sure to send a proxy of the slot """ def __init__(self, **kwds): if 'target' not in kwds: kwds['target'] = startQtEventLoop from ..Qt import ( # # avoid module-level import to keep bootstrap snappy. QtWidgets, ) self._processRequests = kwds.pop('processRequests', True) if self._processRequests and QtWidgets.QApplication.instance() is None: raise Exception("Must create QApplication before starting QtProcess, or use QtProcess(processRequests=False)") Process.__init__(self, **kwds) self.startEventTimer() def startEventTimer(self): from ..Qt import QtCore # # avoid module-level import to keep bootstrap snappy. self.timer = QtCore.QTimer() if self._processRequests: self.startRequestProcessing() def startRequestProcessing(self, interval=0.01): """Start listening for requests coming from the child process. This allows signals to be connected from the child process to the parent. """ self.timer.timeout.connect(self.processRequests) self.timer.start(int(interval*1000)) def stopRequestProcessing(self): self.timer.stop() def processRequests(self): try: Process.processRequests(self) except ClosedError: self.timer.stop() def startQtEventLoop(name, port, authkey, ppid, debug=False): if debug: import os cprint.cout(debug, '[%d] connecting to server at port localhost:%d, authkey=%s..\n' % (os.getpid(), port, repr(authkey)), -1) conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) if debug: cprint.cout(debug, '[%d] connected; starting remote proxy.\n' % os.getpid(), -1) from ..Qt import QtWidgets app = QtWidgets.QApplication.instance() #print app if app is None: app = mkQApp() app.setQuitOnLastWindowClosed(False) ## generally we want the event loop to stay open ## until it is explicitly closed by the parent process. global HANDLER HANDLER = RemoteQtEventHandler(conn, name, ppid, debug=debug) HANDLER.startEventTimer() app.exec() if hasattr(app, 'exec') else app.exec_() import threading class FileForwarder(threading.Thread): """ Background thread that forwards data from one pipe to another. This is used to catch data from stdout/stderr of the child process and print it back out to stdout/stderr. We need this because this bug: http://bugs.python.org/issue3905 _requires_ us to catch stdout/stderr. *output* may be a file or 'stdout' or 'stderr'. In the latter cases, sys.stdout/stderr are retrieved once for every line that is output, which ensures that the correct behavior is achieved even if sys.stdout/stderr are replaced at runtime. """ def __init__(self, input, output, color): threading.Thread.__init__(self) self.input = input self.output = output self.lock = threading.Lock() self.daemon = True self.color = color self.finish = threading.Event() self.start() def run(self): if self.output == 'stdout' and self.color is not False: while not self.finish.is_set(): line = self.input.readline() with self.lock: cprint.cout(self.color, line.decode('utf8'), -1) elif self.output == 'stderr' and self.color is not False: while not self.finish.is_set(): line = self.input.readline() with self.lock: cprint.cerr(self.color, line.decode('utf8'), -1) else: if isinstance(self.output, str): self.output = getattr(sys, self.output) while not self.finish.is_set(): line = self.input.readline() with self.lock: self.output.write(line.decode('utf8')) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/multiprocess/remoteproxy.py000066400000000000000000001377571421045507400253770ustar00rootroot00000000000000import os import sys import threading import time import traceback import warnings import weakref import numpy as np try: import __builtin__ as builtins import cPickle as pickle except ImportError: import builtins import pickle # color printing for debugging from ..util import cprint class ClosedError(Exception): """Raised when an event handler receives a request to close the connection or discovers that the connection has been closed.""" pass class NoResultError(Exception): """Raised when a request for the return value of a remote call fails because the call has not yet returned.""" pass class RemoteExceptionWarning(UserWarning): """Emitted when a request to a remote object results in an Exception """ pass class RemoteEventHandler(object): """ This class handles communication between two processes. One instance is present on each process and listens for communication from the other process. This enables (amongst other things) ObjectProxy instances to look up their attributes and call their methods. This class is responsible for carrying out actions on behalf of the remote process. Each instance holds one end of a Connection which allows python objects to be passed between processes. For the most common operations, see _import(), close(), and transfer() To handle and respond to incoming requests, RemoteEventHandler requires that its processRequests method is called repeatedly (this is usually handled by the Process classes defined in multiprocess.processes). """ handlers = {} ## maps {process ID : handler}. This allows unpickler to determine which process ## an object proxy belongs to def __init__(self, connection, name, pid, debug=False): self.debug = debug self.conn = connection self.name = name self.results = {} ## reqId: (status, result); cache of request results received from the remote process ## status is either 'result' or 'error' ## if 'error', then result will be (exception, formatted exceprion) ## where exception may be None if it could not be passed through the Connection. self.resultLock = threading.RLock() self.proxies = {} ## maps {weakref(proxy): proxyId}; used to inform the remote process when a proxy has been deleted. self.proxyLock = threading.RLock() ## attributes that affect the behavior of the proxy. ## See ObjectProxy._setProxyOptions for description self.proxyOptions = { 'callSync': 'sync', ## 'sync', 'async', 'off' 'timeout': 10, ## float 'returnType': 'auto', ## 'proxy', 'value', 'auto' 'autoProxy': False, ## bool 'deferGetattr': False, ## True, False 'noProxyTypes': [ type(None), str, int, float, tuple, list, dict, LocalObjectProxy, ObjectProxy ], } if int(sys.version[0]) < 3: self.proxyOptions['noProxyTypes'].append(unicode) else: self.proxyOptions['noProxyTypes'].append(bytes) self.optsLock = threading.RLock() self.nextRequestId = 0 self.exited = False # Mutexes to help prevent issues when multiple threads access the same RemoteEventHandler self.processLock = threading.RLock() self.sendLock = threading.RLock() # parent sent us None as its pid, wants us to exchange pids # corresponding code is in: # processes.py::Process.__init__() if pid is None: connection.send(os.getpid()) pid = connection.recv() RemoteEventHandler.handlers[pid] = self ## register this handler as the one communicating with pid @classmethod def getHandler(cls, pid): try: return cls.handlers[pid] except: print(pid, cls.handlers) raise def debugMsg(self, msg, *args): if not self.debug: return cprint.cout(self.debug, "[%d] %s\n" % (os.getpid(), str(msg)%args), -1) def getProxyOption(self, opt): with self.optsLock: return self.proxyOptions[opt] def setProxyOptions(self, **kwds): """ Set the default behavior options for object proxies. See ObjectProxy._setProxyOptions for more info. """ with self.optsLock: self.proxyOptions.update(kwds) def processRequests(self): """Process all pending requests from the pipe, return after no more events are immediately available. (non-blocking) Returns the number of events processed. """ with self.processLock: if self.exited: self.debugMsg(' processRequests: exited already; raise ClosedError.') raise ClosedError() numProcessed = 0 while self.conn.poll(): #try: #poll = self.conn.poll() #if not poll: #break #except IOError: # this can happen if the remote process dies. ## might it also happen in other circumstances? #raise ClosedError() try: self.handleRequest() numProcessed += 1 except ClosedError: self.debugMsg('processRequests: got ClosedError from handleRequest; setting exited=True.') self.exited = True raise #except IOError as err: ## let handleRequest take care of this. #self.debugMsg(' got IOError from handleRequest; try again.') #if err.errno == 4: ## interrupted system call; try again #continue #else: #raise except: print("Error in process %s" % self.name) sys.excepthook(*sys.exc_info()) if numProcessed > 0: self.debugMsg('processRequests: finished %d requests', numProcessed) return numProcessed def handleRequest(self): """Handle a single request from the remote process. Blocks until a request is available.""" result = None while True: try: ## args, kwds are double-pickled to ensure this recv() call never fails cmd, reqId, nByteMsgs, optStr = self.conn.recv() break except EOFError: self.debugMsg(' handleRequest: got EOFError from recv; raise ClosedError.') ## remote process has shut down; end event loop raise ClosedError() except IOError as err: if err.errno == 4: ## interrupted system call; try again self.debugMsg(' handleRequest: got IOError 4 from recv; try again.') continue else: self.debugMsg(' handleRequest: got IOError %d from recv (%s); raise ClosedError.', err.errno, err.strerror) raise ClosedError() self.debugMsg(" handleRequest: received %s %s", cmd, reqId) ## read byte messages following the main request byteData = [] if nByteMsgs > 0: self.debugMsg(" handleRequest: reading %d byte messages", nByteMsgs) for i in range(nByteMsgs): while True: try: byteData.append(self.conn.recv_bytes()) break except EOFError: self.debugMsg(" handleRequest: got EOF while reading byte messages; raise ClosedError.") raise ClosedError() except IOError as err: if err.errno == 4: self.debugMsg(" handleRequest: got IOError 4 while reading byte messages; try again.") continue else: self.debugMsg(" handleRequest: got IOError while reading byte messages; raise ClosedError.") raise ClosedError() try: if cmd == 'result' or cmd == 'error': resultId = reqId reqId = None ## prevents attempt to return information from this request ## (this is already a return from a previous request) opts = pickle.loads(optStr) self.debugMsg(" handleRequest: id=%s opts=%s", reqId, opts) #print os.getpid(), "received request:", cmd, reqId, opts returnType = opts.get('returnType', 'auto') if cmd == 'result': with self.resultLock: self.results[resultId] = ('result', opts['result']) elif cmd == 'error': with self.resultLock: self.results[resultId] = ('error', (opts['exception'], opts['excString'])) elif cmd == 'getObjAttr': result = getattr(opts['obj'], opts['attr']) elif cmd == 'callObj': obj = opts['obj'] fnargs = opts['args'] fnkwds = opts['kwds'] ## If arrays were sent as byte messages, they must be re-inserted into the ## arguments if len(byteData) > 0: for i,arg in enumerate(fnargs): if isinstance(arg, tuple) and len(arg) > 0 and arg[0] == '__byte_message__': ind = arg[1] dtype, shape = arg[2] fnargs[i] = np.frombuffer(byteData[ind], dtype=dtype).reshape(shape) for k,arg in fnkwds.items(): if isinstance(arg, tuple) and len(arg) > 0 and arg[0] == '__byte_message__': ind = arg[1] dtype, shape = arg[2] fnkwds[k] = np.frombuffer(byteData[ind], dtype=dtype).reshape(shape) if len(fnkwds) == 0: ## need to do this because some functions do not allow keyword arguments. try: result = obj(*fnargs) except: print("Failed to call object %s: %d, %s" % (obj, len(fnargs), fnargs[1:])) raise else: result = obj(*fnargs, **fnkwds) elif cmd == 'getObjValue': result = opts['obj'] ## has already been unpickled into its local value returnType = 'value' elif cmd == 'transfer': result = opts['obj'] returnType = 'proxy' elif cmd == 'transferArray': ## read array data from next message: result = np.frombuffer(byteData[0], dtype=opts['dtype']).reshape(opts['shape']) returnType = 'proxy' elif cmd == 'import': name = opts['module'] fromlist = opts.get('fromlist', []) mod = builtins.__import__(name, fromlist=fromlist) if len(fromlist) == 0: parts = name.lstrip('.').split('.') result = mod for part in parts[1:]: result = getattr(result, part) else: result = map(mod.__getattr__, fromlist) elif cmd == 'del': LocalObjectProxy.releaseProxyId(opts['proxyId']) #del self.proxiedObjects[opts['objId']] elif cmd == 'close': if reqId is not None: result = True returnType = 'value' exc = None except: exc = sys.exc_info() if reqId is not None: if exc is None: self.debugMsg(" handleRequest: sending return value for %d: %s", reqId, result) #print "returnValue:", returnValue, result if returnType == 'auto': with self.optsLock: noProxyTypes = self.proxyOptions['noProxyTypes'] result = self.autoProxy(result, noProxyTypes) elif returnType == 'proxy': result = LocalObjectProxy(result) try: self.replyResult(reqId, result) except: sys.excepthook(*sys.exc_info()) self.replyError(reqId, *sys.exc_info()) else: self.debugMsg(" handleRequest: returning exception for %d", reqId) self.replyError(reqId, *exc) elif exc is not None: sys.excepthook(*exc) if cmd == 'close': if opts.get('noCleanup', False) is True: os._exit(0) ## exit immediately, do not pass GO, do not collect $200. ## (more importantly, do not call any code that would ## normally be invoked at exit) else: raise ClosedError() def replyResult(self, reqId, result): self.send(request='result', reqId=reqId, callSync='off', opts=dict(result=result)) def replyError(self, reqId, *exc): # print("error: %s %s %s" % (self.name, str(reqId), str(exc[1]))) excStr = traceback.format_exception(*exc) try: self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=exc[1], excString=excStr)) except: self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=None, excString=excStr)) def send(self, request, opts=None, reqId=None, callSync='sync', timeout=10, returnType=None, byteData=None, **kwds): """Send a request or return packet to the remote process. Generally it is not necessary to call this method directly; it is for internal use. (The docstring has information that is nevertheless useful to the programmer as it describes the internal protocol used to communicate between processes) ============== ==================================================================== **Arguments:** request String describing the type of request being sent (see below) reqId Integer uniquely linking a result back to the request that generated it. (most requests leave this blank) callSync 'sync': return the actual result of the request 'async': return a Request object which can be used to look up the result later 'off': return no result timeout Time in seconds to wait for a response when callSync=='sync' opts Extra arguments sent to the remote process that determine the way the request will be handled (see below) returnType 'proxy', 'value', or 'auto' byteData If specified, this is a list of objects to be sent as byte messages to the remote process. This is used to send large arrays without the cost of pickling. ============== ==================================================================== Description of request strings and options allowed for each: ============= ============= ======================================================== request option description ------------- ------------- -------------------------------------------------------- getObjAttr Request the remote process return (proxy to) an attribute of an object. obj reference to object whose attribute should be returned attr string name of attribute to return returnValue bool or 'auto' indicating whether to return a proxy or the actual value. callObj Request the remote process call a function or method. If a request ID is given, then the call's return value will be sent back (or information about the error that occurred while running the function) obj the (reference to) object to call args tuple of arguments to pass to callable kwds dict of keyword arguments to pass to callable returnValue bool or 'auto' indicating whether to return a proxy or the actual value. getObjValue Request the remote process return the value of a proxied object (must be picklable) obj reference to object whose value should be returned transfer Copy an object to the remote process and request it return a proxy for the new object. obj The object to transfer. import Request the remote process import new symbols and return proxy(ies) to the imported objects module the string name of the module to import fromlist optional list of string names to import from module del Inform the remote process that a proxy has been released (thus the remote process may be able to release the original object) proxyId id of proxy which is no longer referenced by remote host close Instruct the remote process to stop its event loop and exit. Optionally, this request may return a confirmation. result Inform the remote process that its request has been processed result return value of a request error Inform the remote process that its request failed exception the Exception that was raised (or None if the exception could not be pickled) excString string-formatted version of the exception and traceback ============= ===================================================================== """ if self.exited: self.debugMsg(' send: exited already; raise ClosedError.') raise ClosedError() with self.sendLock: #if len(kwds) > 0: #print "Warning: send() ignored args:", kwds if opts is None: opts = {} assert callSync in ['off', 'sync', 'async'], 'callSync must be one of "off", "sync", or "async" (got %r)' % callSync if reqId is None: if callSync != 'off': ## requested return value; use the next available request ID reqId = self.nextRequestId self.nextRequestId += 1 else: ## If requestId is provided, this _must_ be a response to a previously received request. assert request in ['result', 'error'] if returnType is not None: opts['returnType'] = returnType #print os.getpid(), "send request:", request, reqId, opts ## double-pickle args to ensure that at least status and request ID get through try: optStr = pickle.dumps(opts) except: print("==== Error pickling this object: ====") print(opts) print("=======================================") raise nByteMsgs = 0 if byteData is not None: nByteMsgs = len(byteData) ## Send primary request request = (request, reqId, nByteMsgs, optStr) self.debugMsg('send request: cmd=%s nByteMsgs=%d id=%s opts=%s', request[0], nByteMsgs, reqId, opts) self.conn.send(request) ## follow up by sending byte messages if byteData is not None: for obj in byteData: ## Remote process _must_ be prepared to read the same number of byte messages! self.conn.send_bytes(bytes(obj)) self.debugMsg(' sent %d byte messages', len(byteData)) self.debugMsg(' call sync: %s', callSync) if callSync == 'off': return req = Request(self, reqId, description=str(request), timeout=timeout) if callSync == 'async': return req if callSync == 'sync': return req.result() def close(self, callSync='off', noCleanup=False, **kwds): try: self.send(request='close', opts=dict(noCleanup=noCleanup), callSync=callSync, **kwds) self.exited = True except ClosedError: pass def getResult(self, reqId): ## raises NoResultError if the result is not available yet #print self.results.keys(), os.getpid() with self.resultLock: haveResult = reqId in self.results if not haveResult: try: self.processRequests() except ClosedError: ## even if remote connection has closed, we may have ## received new data during this call to processRequests() pass with self.resultLock: if reqId not in self.results: raise NoResultError() status, result = self.results.pop(reqId) if status == 'result': return result elif status == 'error': #print ''.join(result) exc, excStr = result if exc is not None: # PySide6 6.1.0 does an attribute lookup for feature testing # in such a case, failure is normal normal = ["AttributeError"] if not any(excStr[-1].startswith(x) for x in normal): warnings.warn("===== Remote process raised exception on request: =====", RemoteExceptionWarning) warnings.warn(''.join(excStr), RemoteExceptionWarning) warnings.warn("===== Local Traceback to request follows: =====", RemoteExceptionWarning) raise exc else: print(''.join(excStr)) raise Exception("Error getting result. See above for exception from remote process.") else: raise Exception("Internal error.") def _import(self, mod, **kwds): """ Request the remote process import a module (or symbols from a module) and return the proxied results. Uses built-in __import__() function, but adds a bit more processing: _import('module') => returns module _import('module.submodule') => returns submodule (note this differs from behavior of __import__) _import('module', fromlist=[name1, name2, ...]) => returns [module.name1, module.name2, ...] (this also differs from behavior of __import__) """ return self.send(request='import', callSync='sync', opts=dict(module=mod), **kwds) def getObjAttr(self, obj, attr, **kwds): return self.send(request='getObjAttr', opts=dict(obj=obj, attr=attr), **kwds) def getObjValue(self, obj, **kwds): return self.send(request='getObjValue', opts=dict(obj=obj), **kwds) def callObj(self, obj, args, kwds, **opts): opts = opts.copy() args = list(args) ## Decide whether to send arguments by value or by proxy with self.optsLock: noProxyTypes = opts.pop('noProxyTypes', None) if noProxyTypes is None: noProxyTypes = self.proxyOptions['noProxyTypes'] autoProxy = opts.pop('autoProxy', self.proxyOptions['autoProxy']) if autoProxy is True: args = [self.autoProxy(v, noProxyTypes) for v in args] for k, v in kwds.items(): opts[k] = self.autoProxy(v, noProxyTypes) byteMsgs = [] ## If there are arrays in the arguments, send those as byte messages. ## We do this because pickling arrays is too expensive. for i,arg in enumerate(args): if arg.__class__ == np.ndarray: args[i] = ("__byte_message__", len(byteMsgs), (arg.dtype, arg.shape)) byteMsgs.append(arg) for k,v in kwds.items(): if v.__class__ == np.ndarray: kwds[k] = ("__byte_message__", len(byteMsgs), (v.dtype, v.shape)) byteMsgs.append(v) return self.send(request='callObj', opts=dict(obj=obj, args=args, kwds=kwds), byteData=byteMsgs, **opts) def registerProxy(self, proxy): with self.proxyLock: ref = weakref.ref(proxy, self.deleteProxy) self.proxies[ref] = proxy._proxyId def deleteProxy(self, ref): if self.send is None: # this can happen during shutdown return with self.proxyLock: proxyId = self.proxies.pop(ref) try: self.send(request='del', opts=dict(proxyId=proxyId), callSync='off') except ClosedError: ## if remote process has closed down, there is no need to send delete requests anymore pass def transfer(self, obj, **kwds): """ Transfer an object by value to the remote host (the object must be picklable) and return a proxy for the new remote object. """ if obj.__class__ is np.ndarray: opts = {'dtype': obj.dtype, 'shape': obj.shape} return self.send(request='transferArray', opts=opts, byteData=[obj], **kwds) else: return self.send(request='transfer', opts=dict(obj=obj), **kwds) def autoProxy(self, obj, noProxyTypes): ## Return object wrapped in LocalObjectProxy _unless_ its type is in noProxyTypes. for typ in noProxyTypes: if isinstance(obj, typ): return obj return LocalObjectProxy(obj) class Request(object): """ Request objects are returned when calling an ObjectProxy in asynchronous mode or if a synchronous call has timed out. Use hasResult() to ask whether the result of the call has been returned yet. Use result() to get the returned value. """ def __init__(self, process, reqId, description=None, timeout=10): self.proc = process self.description = description self.reqId = reqId self.gotResult = False self._result = None self.timeout = timeout def result(self, block=True, timeout=None): """ Return the result for this request. If block is True, wait until the result has arrived or *timeout* seconds passes. If the timeout is reached, raise NoResultError. (use timeout=None to disable) If block is False, raise NoResultError immediately if the result has not arrived yet. If the process's connection has closed before the result arrives, raise ClosedError. """ if self.gotResult: return self._result if timeout is None: timeout = self.timeout if block: start = time.time() while not self.hasResult(): if self.proc.exited: raise ClosedError() time.sleep(0.005) if timeout >= 0 and time.time() - start > timeout: print("Request timed out: %s" % self.description) import traceback traceback.print_stack() raise NoResultError() return self._result else: self._result = self.proc.getResult(self.reqId) ## raises NoResultError if result is not available yet self.gotResult = True return self._result def hasResult(self): """Returns True if the result for this request has arrived.""" try: self.result(block=False) except NoResultError: pass return self.gotResult class LocalObjectProxy(object): """ Used for wrapping local objects to ensure that they are send by proxy to a remote host. Note that 'proxy' is just a shorter alias for LocalObjectProxy. For example:: data = [1,2,3,4,5] remotePlot.plot(data) ## by default, lists are pickled and sent by value remotePlot.plot(proxy(data)) ## force the object to be sent by proxy """ nextProxyId = 0 proxiedObjects = {} ## maps {proxyId: object} @classmethod def registerObject(cls, obj): ## assign it a unique ID so we can keep a reference to the local object pid = cls.nextProxyId cls.nextProxyId += 1 cls.proxiedObjects[pid] = obj #print "register:", cls.proxiedObjects return pid @classmethod def lookupProxyId(cls, pid): return cls.proxiedObjects[pid] @classmethod def releaseProxyId(cls, pid): del cls.proxiedObjects[pid] #print "release:", cls.proxiedObjects def __init__(self, obj, **opts): """ Create a 'local' proxy object that, when sent to a remote host, will appear as a normal ObjectProxy to *obj*. Any extra keyword arguments are passed to proxy._setProxyOptions() on the remote side. """ self.processId = os.getpid() #self.objectId = id(obj) self.typeStr = repr(obj) #self.handler = handler self.obj = obj self.opts = opts def __reduce__(self): ## a proxy is being pickled and sent to a remote process. ## every time this happens, a new proxy will be generated in the remote process, ## so we keep a new ID so we can track when each is released. pid = LocalObjectProxy.registerObject(self.obj) return (unpickleObjectProxy, (self.processId, pid, self.typeStr, None, self.opts)) ## alias proxy = LocalObjectProxy def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None, opts=None): if processId == os.getpid(): obj = LocalObjectProxy.lookupProxyId(proxyId) if attributes is not None: for attr in attributes: obj = getattr(obj, attr) return obj else: proxy = ObjectProxy(processId, proxyId=proxyId, typeStr=typeStr) if opts is not None: proxy._setProxyOptions(**opts) return proxy class ObjectProxy(object): """ Proxy to an object stored by the remote process. Proxies are created by calling Process._import(), Process.transfer(), or by requesting/calling attributes on existing proxy objects. For the most part, this object can be used exactly as if it were a local object:: rsys = proc._import('sys') # returns proxy to sys module on remote process rsys.stdout # proxy to remote sys.stdout rsys.stdout.write # proxy to remote sys.stdout.write rsys.stdout.write('hello') # calls sys.stdout.write('hello') on remote machine # and returns the result (None) When calling a proxy to a remote function, the call can be made synchronous (result of call is returned immediately), asynchronous (result is returned later), or return can be disabled entirely:: ros = proc._import('os') ## synchronous call; result is returned immediately pid = ros.getpid() ## asynchronous call request = ros.getpid(_callSync='async') while not request.hasResult(): time.sleep(0.01) pid = request.result() ## disable return when we know it isn't needed rsys.stdout.write('hello', _callSync='off') Additionally, values returned from a remote function call are automatically returned either by value (must be picklable) or by proxy. This behavior can be forced:: rnp = proc._import('numpy') arrProxy = rnp.array([1,2,3,4], _returnType='proxy') arrValue = rnp.array([1,2,3,4], _returnType='value') The default callSync and returnType behaviors (as well as others) can be set for each proxy individually using ObjectProxy._setProxyOptions() or globally using proc.setProxyOptions(). """ def __init__(self, processId, proxyId, typeStr='', parent=None): object.__init__(self) ## can't set attributes directly because setattr is overridden. self.__dict__['_processId'] = processId self.__dict__['_typeStr'] = typeStr self.__dict__['_proxyId'] = proxyId self.__dict__['_attributes'] = () ## attributes that affect the behavior of the proxy. ## in all cases, a value of None causes the proxy to ask ## its parent event handler to make the decision self.__dict__['_proxyOptions'] = { 'callSync': None, ## 'sync', 'async', None 'timeout': None, ## float, None 'returnType': None, ## 'proxy', 'value', 'auto', None 'deferGetattr': None, ## True, False, None 'noProxyTypes': None, ## list of types to send by value instead of by proxy 'autoProxy': None, } self.__dict__['_handler'] = RemoteEventHandler.getHandler(processId) self.__dict__['_handler'].registerProxy(self) ## handler will watch proxy; inform remote process when the proxy is deleted. def _setProxyOptions(self, **kwds): """ Change the behavior of this proxy. For all options, a value of None will cause the proxy to instead use the default behavior defined by its parent Process. Options are: ============= ============================================================= callSync 'sync', 'async', 'off', or None. If 'async', then calling methods will return a Request object which can be used to inquire later about the result of the method call. If 'sync', then calling a method will block until the remote process has returned its result or the timeout has elapsed (in this case, a Request object is returned instead). If 'off', then the remote process is instructed _not_ to reply and the method call will return None immediately. returnType 'auto', 'proxy', 'value', or None. If 'proxy', then the value returned when calling a method will be a proxy to the object on the remote process. If 'value', then attempt to pickle the returned object and send it back. If 'auto', then the decision is made by consulting the 'noProxyTypes' option. autoProxy bool or None. If True, arguments to __call__ are automatically converted to proxy unless their type is listed in noProxyTypes (see below). If False, arguments are left untouched. Use proxy(obj) to manually convert arguments before sending. timeout float or None. Length of time to wait during synchronous requests before returning a Request object instead. deferGetattr True, False, or None. If False, all attribute requests will be sent to the remote process immediately and will block until a response is received (or timeout has elapsed). If True, requesting an attribute from the proxy returns a new proxy immediately. The remote process is _not_ contacted to make this request. This is faster, but it is possible to request an attribute that does not exist on the proxied object. In this case, AttributeError will not be raised until an attempt is made to look up the attribute on the remote process. noProxyTypes List of object types that should _not_ be proxied when sent to the remote process. ============= ============================================================= """ for k in kwds: if k not in self._proxyOptions: raise KeyError("Unrecognized proxy option '%s'" % k) self._proxyOptions.update(kwds) def _getValue(self): """ Return the value of the proxied object (the remote object must be picklable) """ return self._handler.getObjValue(self) def _getProxyOption(self, opt): val = self._proxyOptions[opt] if val is None: return self._handler.getProxyOption(opt) return val def _getProxyOptions(self): return dict([(k, self._getProxyOption(k)) for k in self._proxyOptions]) def __reduce__(self): return (unpickleObjectProxy, (self._processId, self._proxyId, self._typeStr, self._attributes)) def __repr__(self): #objRepr = self.__getattr__('__repr__')(callSync='value') return "" % (self._processId, self._proxyId, self._typeStr) def __getattr__(self, attr, **kwds): """ Calls __getattr__ on the remote object and returns the attribute by value or by proxy depending on the options set (see ObjectProxy._setProxyOptions and RemoteEventHandler.setProxyOptions) If the option 'deferGetattr' is True for this proxy, then a new proxy object is returned _without_ asking the remote object whether the named attribute exists. This can save time when making multiple chained attribute requests, but may also defer a possible AttributeError until later, making them more difficult to debug. """ opts = self._getProxyOptions() for k in opts: if '_'+k in kwds: opts[k] = kwds.pop('_'+k) if opts['deferGetattr'] is True: return self._deferredAttr(attr) else: #opts = self._getProxyOptions() return self._handler.getObjAttr(self, attr, **opts) def _deferredAttr(self, attr): return DeferredObjectProxy(self, attr) def __call__(self, *args, **kwds): """ Attempts to call the proxied object from the remote process. Accepts extra keyword arguments: _callSync 'off', 'sync', or 'async' _returnType 'value', 'proxy', or 'auto' If the remote call raises an exception on the remote process, it will be re-raised on the local process. """ opts = self._getProxyOptions() for k in opts: if '_'+k in kwds: opts[k] = kwds.pop('_'+k) return self._handler.callObj(obj=self, args=args, kwds=kwds, **opts) ## Explicitly proxy special methods. Is there a better way to do this?? def _getSpecialAttr(self, attr): ## this just gives us an easy way to change the behavior of the special methods return self._deferredAttr(attr) def __getitem__(self, *args): return self._getSpecialAttr('__getitem__')(*args) def __setitem__(self, *args): return self._getSpecialAttr('__setitem__')(*args, _callSync='off') def __setattr__(self, *args): return self._getSpecialAttr('__setattr__')(*args, _callSync='off') def __str__(self, *args): return self._getSpecialAttr('__str__')(*args, _returnType='value') def __len__(self, *args): return self._getSpecialAttr('__len__')(*args) def __add__(self, *args): return self._getSpecialAttr('__add__')(*args) def __sub__(self, *args): return self._getSpecialAttr('__sub__')(*args) def __div__(self, *args): return self._getSpecialAttr('__div__')(*args) def __truediv__(self, *args): return self._getSpecialAttr('__truediv__')(*args) def __floordiv__(self, *args): return self._getSpecialAttr('__floordiv__')(*args) def __mul__(self, *args): return self._getSpecialAttr('__mul__')(*args) def __pow__(self, *args): return self._getSpecialAttr('__pow__')(*args) def __iadd__(self, *args): return self._getSpecialAttr('__iadd__')(*args, _callSync='off') def __isub__(self, *args): return self._getSpecialAttr('__isub__')(*args, _callSync='off') def __idiv__(self, *args): return self._getSpecialAttr('__idiv__')(*args, _callSync='off') def __itruediv__(self, *args): return self._getSpecialAttr('__itruediv__')(*args, _callSync='off') def __ifloordiv__(self, *args): return self._getSpecialAttr('__ifloordiv__')(*args, _callSync='off') def __imul__(self, *args): return self._getSpecialAttr('__imul__')(*args, _callSync='off') def __ipow__(self, *args): return self._getSpecialAttr('__ipow__')(*args, _callSync='off') def __rshift__(self, *args): return self._getSpecialAttr('__rshift__')(*args) def __lshift__(self, *args): return self._getSpecialAttr('__lshift__')(*args) def __irshift__(self, *args): return self._getSpecialAttr('__irshift__')(*args, _callSync='off') def __ilshift__(self, *args): return self._getSpecialAttr('__ilshift__')(*args, _callSync='off') def __eq__(self, *args): return self._getSpecialAttr('__eq__')(*args) def __ne__(self, *args): return self._getSpecialAttr('__ne__')(*args) def __lt__(self, *args): return self._getSpecialAttr('__lt__')(*args) def __gt__(self, *args): return self._getSpecialAttr('__gt__')(*args) def __le__(self, *args): return self._getSpecialAttr('__le__')(*args) def __ge__(self, *args): return self._getSpecialAttr('__ge__')(*args) def __and__(self, *args): return self._getSpecialAttr('__and__')(*args) def __or__(self, *args): return self._getSpecialAttr('__or__')(*args) def __xor__(self, *args): return self._getSpecialAttr('__xor__')(*args) def __iand__(self, *args): return self._getSpecialAttr('__iand__')(*args, _callSync='off') def __ior__(self, *args): return self._getSpecialAttr('__ior__')(*args, _callSync='off') def __ixor__(self, *args): return self._getSpecialAttr('__ixor__')(*args, _callSync='off') def __mod__(self, *args): return self._getSpecialAttr('__mod__')(*args) def __radd__(self, *args): return self._getSpecialAttr('__radd__')(*args) def __rsub__(self, *args): return self._getSpecialAttr('__rsub__')(*args) def __rdiv__(self, *args): return self._getSpecialAttr('__rdiv__')(*args) def __rfloordiv__(self, *args): return self._getSpecialAttr('__rfloordiv__')(*args) def __rtruediv__(self, *args): return self._getSpecialAttr('__rtruediv__')(*args) def __rmul__(self, *args): return self._getSpecialAttr('__rmul__')(*args) def __rpow__(self, *args): return self._getSpecialAttr('__rpow__')(*args) def __rrshift__(self, *args): return self._getSpecialAttr('__rrshift__')(*args) def __rlshift__(self, *args): return self._getSpecialAttr('__rlshift__')(*args) def __rand__(self, *args): return self._getSpecialAttr('__rand__')(*args) def __ror__(self, *args): return self._getSpecialAttr('__ror__')(*args) def __rxor__(self, *args): return self._getSpecialAttr('__ror__')(*args) def __rmod__(self, *args): return self._getSpecialAttr('__rmod__')(*args) def __hash__(self): ## Required for python3 since __eq__ is defined. return id(self) class DeferredObjectProxy(ObjectProxy): """ This class represents an attribute (or sub-attribute) of a proxied object. It is used to speed up attribute requests. Take the following scenario:: rsys = proc._import('sys') rsys.stdout.write('hello') For this simple example, a total of 4 synchronous requests are made to the remote process: 1) import sys 2) getattr(sys, 'stdout') 3) getattr(stdout, 'write') 4) write('hello') This takes a lot longer than running the equivalent code locally. To speed things up, we can 'defer' the two attribute lookups so they are only carried out when neccessary:: rsys = proc._import('sys') rsys._setProxyOptions(deferGetattr=True) rsys.stdout.write('hello') This example only makes two requests to the remote process; the two attribute lookups immediately return DeferredObjectProxy instances immediately without contacting the remote process. When the call to write() is made, all attribute requests are processed at the same time. Note that if the attributes requested do not exist on the remote object, making the call to write() will raise an AttributeError. """ def __init__(self, parentProxy, attribute): ## can't set attributes directly because setattr is overridden. for k in ['_processId', '_typeStr', '_proxyId', '_handler']: self.__dict__[k] = getattr(parentProxy, k) self.__dict__['_parent'] = parentProxy ## make sure parent stays alive self.__dict__['_attributes'] = parentProxy._attributes + (attribute,) self.__dict__['_proxyOptions'] = parentProxy._proxyOptions.copy() def __repr__(self): return ObjectProxy.__repr__(self) + '.' + '.'.join(self._attributes) def _undefer(self): """ Return a non-deferred ObjectProxy referencing the same object """ return self._parent.__getattr__(self._attributes[-1], _deferGetattr=False) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/000077500000000000000000000000001421045507400211375ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/GLGraphicsItem.py000066400000000000000000000237531421045507400243250ustar00rootroot00000000000000from OpenGL.GL import * # noqa from OpenGL import GL from .. import Transform3D from ..Qt import QtCore GLOptions = { 'opaque': { GL_DEPTH_TEST: True, GL_BLEND: False, GL_ALPHA_TEST: False, GL_CULL_FACE: False, }, 'translucent': { GL_DEPTH_TEST: True, GL_BLEND: True, GL_ALPHA_TEST: False, GL_CULL_FACE: False, 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), }, 'additive': { GL_DEPTH_TEST: False, GL_BLEND: True, GL_ALPHA_TEST: False, GL_CULL_FACE: False, 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE), }, } class GLGraphicsItem(QtCore.QObject): _nextId = 0 def __init__(self, parentItem=None): super().__init__() self._id = GLGraphicsItem._nextId GLGraphicsItem._nextId += 1 self.__parent = None self.__view = None self.__children = set() self.__transform = Transform3D() self.__visible = True self.__initialized = False self.setParentItem(parentItem) self.setDepthValue(0) self.__glOpts = {} def setParentItem(self, item): """Set this item's parent in the scenegraph hierarchy.""" if self.__parent is not None: self.__parent.__children.remove(self) if item is not None: item.__children.add(self) self.__parent = item if self.__parent is not None and self.view() is not self.__parent.view(): if self.view() is not None: self.view().removeItem(self) self.__parent.view().addItem(self) def setGLOptions(self, opts): """ Set the OpenGL state options to use immediately before drawing this item. (Note that subclasses must call setupGLState before painting for this to work) The simplest way to invoke this method is to pass in the name of a predefined set of options (see the GLOptions variable): ============= ====================================================== opaque Enables depth testing and disables blending translucent Enables depth testing and blending Elements must be drawn sorted back-to-front for translucency to work correctly. additive Disables depth testing, enables blending. Colors are added together, so sorting is not required. ============= ====================================================== It is also possible to specify any arbitrary settings as a dictionary. This may consist of {'functionName': (args...)} pairs where functionName must be a callable attribute of OpenGL.GL, or {GL_STATE_VAR: bool} pairs which will be interpreted as calls to glEnable or glDisable(GL_STATE_VAR). For example:: { GL_ALPHA_TEST: True, GL_CULL_FACE: False, 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), } """ if isinstance(opts, str): opts = GLOptions[opts] self.__glOpts = opts.copy() self.update() def updateGLOptions(self, opts): """ Modify the OpenGL state options to use immediately before drawing this item. *opts* must be a dictionary as specified by setGLOptions. Values may also be None, in which case the key will be ignored. """ self.__glOpts.update(opts) def parentItem(self): """Return a this item's parent in the scenegraph hierarchy.""" return self.__parent def childItems(self): """Return a list of this item's children in the scenegraph hierarchy.""" return list(self.__children) def _setView(self, v): self.__view = v def view(self): return self.__view def setDepthValue(self, value): """ Sets the depth value of this item. Default is 0. This controls the order in which items are drawn--those with a greater depth value will be drawn later. Items with negative depth values are drawn before their parent. (This is analogous to QGraphicsItem.zValue) The depthValue does NOT affect the position of the item or the values it imparts to the GL depth buffer. """ self.__depthValue = value def depthValue(self): """Return the depth value of this item. See setDepthValue for more information.""" return self.__depthValue def setTransform(self, tr): """Set the local transform for this object. Must be a :class:`Transform3D ` instance. This transform determines how the local coordinate system of the item is mapped to the coordinate system of its parent.""" self.__transform = Transform3D(tr) self.update() def resetTransform(self): """Reset this item's transform to an identity transformation.""" self.__transform.setToIdentity() self.update() def applyTransform(self, tr, local): """ Multiply this object's transform by *tr*. If local is True, then *tr* is multiplied on the right of the current transform:: newTransform = transform * tr If local is False, then *tr* is instead multiplied on the left:: newTransform = tr * transform """ if local: self.setTransform(self.transform() * tr) else: self.setTransform(tr * self.transform()) def transform(self): """Return this item's transform object.""" return self.__transform def viewTransform(self): """Return the transform mapping this item's local coordinate system to the view coordinate system.""" tr = self.__transform p = self while True: p = p.parentItem() if p is None: break tr = p.transform() * tr return Transform3D(tr) def translate(self, dx, dy, dz, local=False): """ Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system. If *local* is True, then translation takes place in local coordinates. """ tr = Transform3D() tr.translate(dx, dy, dz) self.applyTransform(tr, local=local) def rotate(self, angle, x, y, z, local=False): """ Rotate the object around the axis specified by (x,y,z). *angle* is in degrees. """ tr = Transform3D() tr.rotate(angle, x, y, z) self.applyTransform(tr, local=local) def scale(self, x, y, z, local=True): """ Scale the object by (*dx*, *dy*, *dz*) in its local coordinate system. If *local* is False, then scale takes place in the parent's coordinates. """ tr = Transform3D() tr.scale(x, y, z) self.applyTransform(tr, local=local) def hide(self): """Hide this item. This is equivalent to setVisible(False).""" self.setVisible(False) def show(self): """Make this item visible if it was previously hidden. This is equivalent to setVisible(True).""" self.setVisible(True) def setVisible(self, vis): """Set the visibility of this item.""" self.__visible = vis self.update() def visible(self): """Return True if the item is currently set to be visible. Note that this does not guarantee that the item actually appears in the view, as it may be obscured or outside of the current view area.""" return self.__visible def initialize(self): self.initializeGL() self.__initialized = True def isInitialized(self): return self.__initialized def initializeGL(self): """ Called after an item is added to a GLViewWidget. The widget's GL context is made current before this method is called. (So this would be an appropriate time to generate lists, upload textures, etc.) """ pass def setupGLState(self): """ This method is responsible for preparing the GL state options needed to render this item (blending, depth testing, etc). The method is called immediately before painting the item. """ for k,v in self.__glOpts.items(): if v is None: continue if isinstance(k, str): func = getattr(GL, k) func(*v) else: if v is True: glEnable(k) else: glDisable(k) def paint(self): """ Called by the GLViewWidget to draw this item. It is the responsibility of the item to set up its own modelview matrix, but the caller will take care of pushing/popping. """ self.setupGLState() def update(self): """ Indicates that this item needs to be redrawn, and schedules an update with the view it is displayed in. """ v = self.view() if v is None: return v.update() def mapToParent(self, point): tr = self.transform() if tr is None: return point return tr.map(point) def mapFromParent(self, point): tr = self.transform() if tr is None: return point return tr.inverted()[0].map(point) def mapToView(self, point): tr = self.viewTransform() if tr is None: return point return tr.map(point) def mapFromView(self, point): tr = self.viewTransform() if tr is None: return point return tr.inverted()[0].map(point) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/GLViewWidget.py000066400000000000000000000610551421045507400240210ustar00rootroot00000000000000from OpenGL.GL import * # noqa import OpenGL.GL.framebufferobjects as glfbo # noqa import warnings from math import cos, radians, sin, tan import numpy as np from .. import Vector from .. import functions as fn from .. import getConfigOption from ..Qt import QtCore, QtGui, QtWidgets ##Vector = QtGui.QVector3D class GLViewWidget(QtWidgets.QOpenGLWidget): def __init__(self, parent=None, devicePixelRatio=None, rotationMethod='euler'): """ Basic widget for displaying 3D data - Rotation/scale controls - Axis/grid display - Export options ================ ============================================================== **Arguments:** parent (QObject, optional): Parent QObject. Defaults to None. devicePixelRatio No longer in use. High-DPI displays should automatically detect the correct resolution. rotationMethod (str): Mechanimsm to drive the rotation method, options are 'euler' and 'quaternion'. Defaults to 'euler'. ================ ============================================================== """ QtWidgets.QOpenGLWidget.__init__(self, parent) self.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus) if rotationMethod not in ["euler", "quaternion"]: raise ValueError("Rotation method should be either 'euler' or 'quaternion'") self.opts = { 'center': Vector(0,0,0), ## will always appear at the center of the widget 'rotation' : QtGui.QQuaternion(1,0,0,0), ## camera rotation (quaternion:wxyz) 'distance': 10.0, ## distance of camera from center 'fov': 60, ## horizontal field of view in degrees 'elevation': 30, ## camera's angle of elevation in degrees 'azimuth': 45, ## camera's azimuthal angle in degrees ## (rotation around z-axis 0 points along x-axis) 'viewport': None, ## glViewport params; None == whole widget ## note that 'viewport' is in device pixels 'rotationMethod': rotationMethod } self.reset() self.items = [] self.noRepeatKeys = [QtCore.Qt.Key.Key_Right, QtCore.Qt.Key.Key_Left, QtCore.Qt.Key.Key_Up, QtCore.Qt.Key.Key_Down, QtCore.Qt.Key.Key_PageUp, QtCore.Qt.Key.Key_PageDown] self.keysPressed = {} self.keyTimer = QtCore.QTimer() self.keyTimer.timeout.connect(self.evalKeyState) def _updateScreen(self, screen): self._updatePixelRatio() if screen is not None: screen.physicalDotsPerInchChanged.connect(self._updatePixelRatio) screen.logicalDotsPerInchChanged.connect(self._updatePixelRatio) def _updatePixelRatio(self): event = QtGui.QResizeEvent(self.size(), self.size()) self.resizeEvent(event) def showEvent(self, event): window = self.window().windowHandle() window.screenChanged.connect(self._updateScreen) self._updateScreen(window.screen()) def deviceWidth(self): dpr = self.devicePixelRatioF() return int(self.width() * dpr) def deviceHeight(self): dpr = self.devicePixelRatioF() return int(self.height() * dpr) def reset(self): """ Initialize the widget state or reset the current state to the original state. """ self.opts['center'] = Vector(0,0,0) ## will always appear at the center of the widget self.opts['distance'] = 10.0 ## distance of camera from center self.opts['fov'] = 60 ## horizontal field of view in degrees self.opts['elevation'] = 30 ## camera's angle of elevation in degrees self.opts['azimuth'] = 45 ## camera's azimuthal angle in degrees ## (rotation around z-axis 0 points along x-axis) self.opts['viewport'] = None ## glViewport params; None == whole widget self.setBackgroundColor(getConfigOption('background')) def addItem(self, item): self.items.append(item) if self.isValid(): item.initialize() item._setView(self) self.update() def removeItem(self, item): """ Remove the item from the scene. """ self.items.remove(item) item._setView(None) self.update() def clear(self): """ Remove all items from the scene. """ for item in self.items: item._setView(None) self.items = [] self.update() def initializeGL(self): """ Initialize items that were not initialized during addItem(). """ ctx = self.context() fmt = ctx.format() if ctx.isOpenGLES() or fmt.version() < (2, 0): verString = glGetString(GL_VERSION) raise RuntimeError( "pyqtgraph.opengl: Requires >= OpenGL 2.0 (not ES); Found %s" % verString ) for item in self.items: if not item.isInitialized(): item.initialize() def setBackgroundColor(self, *args, **kwds): """ Set the background color of the widget. Accepts the same arguments as :func:`~pyqtgraph.mkColor`. """ self.opts['bgcolor'] = fn.mkColor(*args, **kwds).getRgbF() self.update() def getViewport(self): vp = self.opts['viewport'] if vp is None: return (0, 0, self.deviceWidth(), self.deviceHeight()) else: return vp def setProjection(self, region=None): m = self.projectionMatrix(region) glMatrixMode(GL_PROJECTION) glLoadMatrixf(np.array(m.data(), dtype=np.float32)) def projectionMatrix(self, region=None): if region is None: region = (0, 0, self.deviceWidth(), self.deviceHeight()) x0, y0, w, h = self.getViewport() dist = self.opts['distance'] fov = self.opts['fov'] nearClip = dist * 0.001 farClip = dist * 1000. r = nearClip * tan(0.5 * radians(fov)) t = r * h / w ## Note that X0 and width in these equations must be the values used in viewport left = r * ((region[0]-x0) * (2.0/w) - 1) right = r * ((region[0]+region[2]-x0) * (2.0/w) - 1) bottom = t * ((region[1]-y0) * (2.0/h) - 1) top = t * ((region[1]+region[3]-y0) * (2.0/h) - 1) tr = QtGui.QMatrix4x4() tr.frustum(left, right, bottom, top, nearClip, farClip) return tr def setModelview(self): m = self.viewMatrix() glMatrixMode(GL_MODELVIEW) glLoadMatrixf(np.array(m.data(), dtype=np.float32)) def viewMatrix(self): tr = QtGui.QMatrix4x4() tr.translate( 0.0, 0.0, -self.opts['distance']) if self.opts['rotationMethod'] == 'quaternion': tr.rotate(self.opts['rotation']) else: # default rotation method tr.rotate(self.opts['elevation']-90, 1, 0, 0) tr.rotate(self.opts['azimuth']+90, 0, 0, -1) center = self.opts['center'] tr.translate(-center.x(), -center.y(), -center.z()) return tr def itemsAt(self, region=None): """ Return a list of the items displayed in the region (x, y, w, h) relative to the widget. """ region = (region[0], self.deviceHeight()-(region[1]+region[3]), region[2], region[3]) #buf = np.zeros(100000, dtype=np.uint) buf = glSelectBuffer(100000) try: glRenderMode(GL_SELECT) glInitNames() glPushName(0) self._itemNames = {} self.paintGL(region=region, useItemNames=True) finally: hits = glRenderMode(GL_RENDER) items = [(h.near, h.names[0]) for h in hits] items.sort(key=lambda i: i[0]) return [self._itemNames[i[1]] for i in items] def paintGL(self, region=None, viewport=None, useItemNames=False): """ viewport specifies the arguments to glViewport. If None, then we use self.opts['viewport'] region specifies the sub-region of self.opts['viewport'] that should be rendered. Note that we may use viewport != self.opts['viewport'] when exporting. """ if viewport is None: glViewport(*self.getViewport()) else: glViewport(*viewport) self.setProjection(region=region) self.setModelview() bgcolor = self.opts['bgcolor'] glClearColor(*bgcolor) glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT ) self.drawItemTree(useItemNames=useItemNames) def drawItemTree(self, item=None, useItemNames=False): if item is None: items = [x for x in self.items if x.parentItem() is None] else: items = item.childItems() items.append(item) items.sort(key=lambda a: a.depthValue()) for i in items: if not i.visible(): continue if i is item: try: glPushAttrib(GL_ALL_ATTRIB_BITS) if useItemNames: glLoadName(i._id) self._itemNames[i._id] = i i.paint() except: from .. import debug debug.printExc() print("Error while drawing item %s." % str(item)) finally: glPopAttrib() else: glMatrixMode(GL_MODELVIEW) glPushMatrix() try: tr = i.transform() glMultMatrixf(np.array(tr.data(), dtype=np.float32)) self.drawItemTree(i, useItemNames=useItemNames) finally: glMatrixMode(GL_MODELVIEW) glPopMatrix() def setCameraPosition(self, pos=None, distance=None, elevation=None, azimuth=None, rotation=None): if rotation is not None: # Alternatively, we could define that rotation overrides elevation and azimuth if elevation is not None: raise ValueError("cannot set both rotation and elevation") if azimuth is not None: raise ValueError("cannot set both rotation and azimuth") if pos is not None: self.opts['center'] = pos if distance is not None: self.opts['distance'] = distance if self.opts['rotationMethod'] == "quaternion": # note that "quaternion" mode modifies only opts['rotation'] if elevation is not None or azimuth is not None: eu = self.opts['rotation'].toEulerAngles() if azimuth is not None: eu.setZ(-azimuth-90) if elevation is not None: eu.setX(elevation-90) self.opts['rotation'] = QtGui.QQuaternion.fromEulerAngles(eu) if rotation is not None: self.opts['rotation'] = rotation else: # note that "euler" mode modifies only opts['elevation'] and opts['azimuth'] if elevation is not None: self.opts['elevation'] = elevation if azimuth is not None: self.opts['azimuth'] = azimuth if rotation is not None: eu = rotation.toEulerAngles() self.opts['elevation'] = eu.x() + 90 self.opts['azimuth'] = -eu.z() - 90 self.update() def cameraPosition(self): """Return current position of camera based on center, dist, elevation, and azimuth""" center = self.opts['center'] dist = self.opts['distance'] if self.opts['rotationMethod'] == "quaternion": pos = Vector(center - self.opts['rotation'].rotatedVector(Vector(0,0,dist) )) else: # using 'euler' rotation method elev = radians(self.opts['elevation']) azim = radians(self.opts['azimuth']) pos = Vector( center.x() + dist * cos(elev) * cos(azim), center.y() + dist * cos(elev) * sin(azim), center.z() + dist * sin(elev) ) return pos def setCameraParams(self, **kwds): valid_keys = {'center', 'rotation', 'distance', 'fov', 'elevation', 'azimuth'} if not valid_keys.issuperset(kwds): raise ValueError(f'valid keywords are {valid_keys}') self.setCameraPosition(pos=kwds.get('center'), distance=kwds.get('distance'), elevation=kwds.get('elevation'), azimuth=kwds.get('azimuth'), rotation=kwds.get('rotation')) if 'fov' in kwds: self.opts['fov'] = kwds['fov'] def cameraParams(self): valid_keys = {'center', 'rotation', 'distance', 'fov', 'elevation', 'azimuth'} return { k : self.opts[k] for k in valid_keys } def orbit(self, azim, elev): """Orbits the camera around the center position. *azim* and *elev* are given in degrees.""" if self.opts['rotationMethod'] == 'quaternion': q = QtGui.QQuaternion.fromEulerAngles( elev, -azim, 0 ) # rx-ry-rz q *= self.opts['rotation'] self.opts['rotation'] = q else: # default euler rotation method self.opts['azimuth'] += azim self.opts['elevation'] = fn.clip_scalar(self.opts['elevation'] + elev, -90., 90.) self.update() def pan(self, dx, dy, dz, relative='global'): """ Moves the center (look-at) position while holding the camera in place. ============== ======================================================= **Arguments:** *dx* Distance to pan in x direction *dy* Distance to pan in y direction *dz* Distance to pan in z direction *relative* String that determines the direction of dx,dy,dz. If "global", then the global coordinate system is used. If "view", then the z axis is aligned with the view direction, and x and y axes are in the plane of the view: +x points right, +y points up. If "view-upright", then x is in the global xy plane and points to the right side of the view, y is in the global xy plane and orthogonal to x, and z points in the global z direction. ============== ======================================================= Distances are scaled roughly such that a value of 1.0 moves by one pixel on screen. Prior to version 0.11, *relative* was expected to be either True (x-aligned) or False (global). These values are deprecated but still recognized. """ # for backward compatibility: if isinstance(relative, bool): warnings.warn( "'relative' as a boolean is deprecated, and will not be recognized in 0.13. " "Acceptable values are 'global', 'view', or 'view-upright'", DeprecationWarning, stacklevel=2 ) relative = {True: "view-upright", False: "global"}.get(relative, relative) if relative == 'global': self.opts['center'] += QtGui.QVector3D(dx, dy, dz) elif relative == 'view-upright': cPos = self.cameraPosition() cVec = self.opts['center'] - cPos dist = cVec.length() ## distance from camera to center xDist = dist * 2. * tan(0.5 * radians(self.opts['fov'])) ## approx. width of view at distance of center point xScale = xDist / self.width() zVec = QtGui.QVector3D(0,0,1) xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized() yVec = QtGui.QVector3D.crossProduct(xVec, zVec).normalized() self.opts['center'] = self.opts['center'] + xVec * xScale * dx + yVec * xScale * dy + zVec * xScale * dz elif relative == 'view': # pan in plane of camera if self.opts['rotationMethod'] == 'quaternion': # obtain basis vectors qc = self.opts['rotation'].conjugated() xv = qc.rotatedVector( Vector(1,0,0) ) yv = qc.rotatedVector( Vector(0,1,0) ) zv = qc.rotatedVector( Vector(0,0,1) ) scale_factor = self.pixelSize( self.opts['center'] ) # apply translation self.opts['center'] += scale_factor * (xv*-dx + yv*dy + zv*dz) else: # use default euler rotation method elev = radians(self.opts['elevation']) azim = radians(self.opts['azimuth']) fov = radians(self.opts['fov']) dist = (self.opts['center'] - self.cameraPosition()).length() fov_factor = tan(fov / 2) * 2 scale_factor = dist * fov_factor / self.width() z = scale_factor * cos(elev) * dy x = scale_factor * (sin(azim) * dx - sin(elev) * cos(azim) * dy) y = scale_factor * (cos(azim) * dx + sin(elev) * sin(azim) * dy) self.opts['center'] += QtGui.QVector3D(x, -y, z) else: raise ValueError("relative argument must be global, view, or view-upright") self.update() def pixelSize(self, pos): """ Return the approximate size of a screen pixel at the location pos Pos may be a Vector or an (N,3) array of locations """ cam = self.cameraPosition() if isinstance(pos, np.ndarray): cam = np.array(cam).reshape((1,)*(pos.ndim-1)+(3,)) dist = ((pos-cam)**2).sum(axis=-1)**0.5 else: dist = (pos-cam).length() xDist = dist * 2. * tan(0.5 * radians(self.opts['fov'])) return xDist / self.width() def mousePressEvent(self, ev): lpos = ev.position() if hasattr(ev, 'position') else ev.localPos() self.mousePos = lpos def mouseMoveEvent(self, ev): lpos = ev.position() if hasattr(ev, 'position') else ev.localPos() diff = lpos - self.mousePos self.mousePos = lpos if ev.buttons() == QtCore.Qt.MouseButton.LeftButton: if (ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier): self.pan(diff.x(), diff.y(), 0, relative='view') else: self.orbit(-diff.x(), diff.y()) elif ev.buttons() == QtCore.Qt.MouseButton.MiddleButton: if (ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier): self.pan(diff.x(), 0, diff.y(), relative='view-upright') else: self.pan(diff.x(), diff.y(), 0, relative='view-upright') def mouseReleaseEvent(self, ev): pass # Example item selection code: #region = (ev.pos().x()-5, ev.pos().y()-5, 10, 10) #print(self.itemsAt(region)) ## debugging code: draw the picking region #glViewport(*self.getViewport()) #glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT ) #region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3]) #self.paintGL(region=region) #self.swapBuffers() def wheelEvent(self, ev): delta = ev.angleDelta().x() if delta == 0: delta = ev.angleDelta().y() if (ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier): self.opts['fov'] *= 0.999**delta else: self.opts['distance'] *= 0.999**delta self.update() def keyPressEvent(self, ev): if ev.key() in self.noRepeatKeys: ev.accept() if ev.isAutoRepeat(): return self.keysPressed[ev.key()] = 1 self.evalKeyState() def keyReleaseEvent(self, ev): if ev.key() in self.noRepeatKeys: ev.accept() if ev.isAutoRepeat(): return try: del self.keysPressed[ev.key()] except KeyError: self.keysPressed = {} self.evalKeyState() def evalKeyState(self): speed = 2.0 if len(self.keysPressed) > 0: for key in self.keysPressed: if key == QtCore.Qt.Key.Key_Right: self.orbit(azim=-speed, elev=0) elif key == QtCore.Qt.Key.Key_Left: self.orbit(azim=speed, elev=0) elif key == QtCore.Qt.Key.Key_Up: self.orbit(azim=0, elev=-speed) elif key == QtCore.Qt.Key.Key_Down: self.orbit(azim=0, elev=speed) elif key == QtCore.Qt.Key.Key_PageUp: pass elif key == QtCore.Qt.Key.Key_PageDown: pass self.keyTimer.start(16) else: self.keyTimer.stop() def readQImage(self): """ Read the current buffer pixels out as a QImage. """ return self.grabFramebuffer() def renderToArray(self, size, format=GL_BGRA, type=GL_UNSIGNED_BYTE, textureSize=1024, padding=256): w,h = map(int, size) self.makeCurrent() tex = None fb = None depth_buf = None try: output = np.empty((h, w, 4), dtype=np.ubyte) fb = glfbo.glGenFramebuffers(1) glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, fb ) glEnable(GL_TEXTURE_2D) tex = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, tex) texwidth = textureSize data = np.zeros((texwidth,texwidth,4), dtype=np.ubyte) ## Test texture dimensions first glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, None) if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: raise RuntimeError("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % data.shape[:2]) ## create texture glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) # Create depth buffer depth_buf = glGenRenderbuffers(1) glBindRenderbuffer(GL_RENDERBUFFER, depth_buf) glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, texwidth, texwidth) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buf) self.opts['viewport'] = (0, 0, w, h) # viewport is the complete image; this ensures that paintGL(region=...) # is interpreted correctly. p2 = 2 * padding for x in range(-padding, w-padding, texwidth-p2): for y in range(-padding, h-padding, texwidth-p2): x2 = min(x+texwidth, w+padding) y2 = min(y+texwidth, h+padding) w2 = x2-x h2 = y2-y ## render to texture glfbo.glFramebufferTexture2D(glfbo.GL_FRAMEBUFFER, glfbo.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0) self.paintGL(region=(x, h-y-h2, w2, h2), viewport=(0, 0, w2, h2)) # only render sub-region glBindTexture(GL_TEXTURE_2D, tex) # fixes issue #366 ## read texture back to array data = glGetTexImage(GL_TEXTURE_2D, 0, format, type) data = np.frombuffer(data, dtype=np.ubyte).reshape(texwidth,texwidth,4)[::-1, ...] output[y+padding:y2-padding, x+padding:x2-padding] = data[-(h2-padding):-padding, padding:w2-padding] finally: self.opts['viewport'] = None glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, 0) glBindTexture(GL_TEXTURE_2D, 0) if tex is not None: glDeleteTextures([tex]) if fb is not None: glfbo.glDeleteFramebuffers([fb]) if depth_buf is not None: glDeleteRenderbuffers(1, [depth_buf]) return output pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/MeshData.py000066400000000000000000000536211421045507400232060ustar00rootroot00000000000000import numpy as np from ..Qt import QtGui class MeshData(object): """ Class for storing and operating on 3D mesh data. May contain: - list of vertex locations - list of edges - list of triangles - colors per vertex, edge, or tri - normals per vertex or tri This class handles conversion between the standard [list of vertexes, list of faces] format (suitable for use with glDrawElements) and 'indexed' [list of vertexes] format (suitable for use with glDrawArrays). It will automatically compute face normal vectors as well as averaged vertex normal vectors. The class attempts to be as efficient as possible in caching conversion results and avoiding unnecessary conversions. """ def __init__(self, vertexes=None, faces=None, edges=None, vertexColors=None, faceColors=None): """ ============== ===================================================== **Arguments:** vertexes (Nv, 3) array of vertex coordinates. If faces is not specified, then this will instead be interpreted as (Nf, 3, 3) array of coordinates. faces (Nf, 3) array of indexes into the vertex array. edges [not available yet] vertexColors (Nv, 4) array of vertex colors. If faces is not specified, then this will instead be interpreted as (Nf, 3, 4) array of colors. faceColors (Nf, 4) array of face colors. ============== ===================================================== All arguments are optional. """ self._vertexes = None # (Nv,3) array of vertex coordinates self._vertexesIndexedByFaces = None # (Nf, 3, 3) array of vertex coordinates self._vertexesIndexedByEdges = None # (Ne, 2, 3) array of vertex coordinates ## mappings between vertexes, faces, and edges self._faces = None # Nx3 array of indexes into self._vertexes specifying three vertexes for each face self._edges = None # Nx2 array of indexes into self._vertexes specifying two vertexes per edge self._vertexFaces = None ## maps vertex ID to a list of face IDs (inverse mapping of _faces) self._vertexEdges = None ## maps vertex ID to a list of edge IDs (inverse mapping of _edges) ## Per-vertex data self._vertexNormals = None # (Nv, 3) array of normals, one per vertex self._vertexNormalsIndexedByFaces = None # (Nf, 3, 3) array of normals self._vertexColors = None # (Nv, 3) array of colors self._vertexColorsIndexedByFaces = None # (Nf, 3, 4) array of colors self._vertexColorsIndexedByEdges = None # (Nf, 2, 4) array of colors ## Per-face data self._faceNormals = None # (Nf, 3) array of face normals self._faceNormalsIndexedByFaces = None # (Nf, 3, 3) array of face normals self._faceColors = None # (Nf, 4) array of face colors self._faceColorsIndexedByFaces = None # (Nf, 3, 4) array of face colors self._faceColorsIndexedByEdges = None # (Ne, 2, 4) array of face colors ## Per-edge data self._edgeColors = None # (Ne, 4) array of edge colors self._edgeColorsIndexedByEdges = None # (Ne, 2, 4) array of edge colors #self._meshColor = (1, 1, 1, 0.1) # default color to use if no face/edge/vertex colors are given if vertexes is not None: if faces is None: self.setVertexes(vertexes, indexed='faces') if vertexColors is not None: self.setVertexColors(vertexColors, indexed='faces') if faceColors is not None: self.setFaceColors(faceColors, indexed='faces') else: self.setVertexes(vertexes) self.setFaces(faces) if vertexColors is not None: self.setVertexColors(vertexColors) if faceColors is not None: self.setFaceColors(faceColors) def faces(self): """Return an array (Nf, 3) of vertex indexes, three per triangular face in the mesh. If faces have not been computed for this mesh, the function returns None. """ return self._faces def edges(self): """Return an array (Nf, 3) of vertex indexes, two per edge in the mesh.""" if self._edges is None: self._computeEdges() return self._edges def setFaces(self, faces): """Set the (Nf, 3) array of faces. Each rown in the array contains three indexes into the vertex array, specifying the three corners of a triangular face.""" self._faces = faces self._edges = None self._vertexFaces = None self._vertexesIndexedByFaces = None self.resetNormals() self._vertexColorsIndexedByFaces = None self._faceColorsIndexedByFaces = None def vertexes(self, indexed=None): """Return an array (N,3) of the positions of vertexes in the mesh. By default, each unique vertex appears only once in the array. If indexed is 'faces', then the array will instead contain three vertexes per face in the mesh (and a single vertex may appear more than once in the array).""" if indexed is None: if self._vertexes is None and self._vertexesIndexedByFaces is not None: self._computeUnindexedVertexes() return self._vertexes elif indexed == 'faces': if self._vertexesIndexedByFaces is None and self._vertexes is not None: self._vertexesIndexedByFaces = self._vertexes[self.faces()] return self._vertexesIndexedByFaces else: raise Exception("Invalid indexing mode. Accepts: None, 'faces'") def setVertexes(self, verts=None, indexed=None, resetNormals=True): """ Set the array (Nv, 3) of vertex coordinates. If indexed=='faces', then the data must have shape (Nf, 3, 3) and is assumed to be already indexed as a list of faces. This will cause any pre-existing normal vectors to be cleared unless resetNormals=False. """ if indexed is None: if verts is not None: self._vertexes = np.ascontiguousarray(verts, dtype=np.float32) self._vertexesIndexedByFaces = None elif indexed=='faces': self._vertexes = None if verts is not None: self._vertexesIndexedByFaces = np.ascontiguousarray(verts, dtype=np.float32) else: raise Exception("Invalid indexing mode. Accepts: None, 'faces'") if resetNormals: self.resetNormals() def resetNormals(self): self._vertexNormals = None self._vertexNormalsIndexedByFaces = None self._faceNormals = None self._faceNormalsIndexedByFaces = None def hasFaceIndexedData(self): """Return True if this object already has vertex positions indexed by face""" return self._vertexesIndexedByFaces is not None def hasEdgeIndexedData(self): return self._vertexesIndexedByEdges is not None def hasVertexColor(self): """Return True if this data set has vertex color information""" for v in (self._vertexColors, self._vertexColorsIndexedByFaces, self._vertexColorsIndexedByEdges): if v is not None: return True return False def hasFaceColor(self): """Return True if this data set has face color information""" for v in (self._faceColors, self._faceColorsIndexedByFaces, self._faceColorsIndexedByEdges): if v is not None: return True return False def faceNormals(self, indexed=None): """ Return an array (Nf, 3) of normal vectors for each face. If indexed='faces', then instead return an indexed array (Nf, 3, 3) (this is just the same array with each vector copied three times). """ if self._faceNormals is None: v = self.vertexes(indexed='faces') self._faceNormals = np.cross(v[:,1]-v[:,0], v[:,2]-v[:,0]) if indexed is None: return self._faceNormals elif indexed == 'faces': if self._faceNormalsIndexedByFaces is None: norms = np.empty((self._faceNormals.shape[0], 3, 3), dtype=np.float32) norms[:] = self._faceNormals[:,np.newaxis,:] self._faceNormalsIndexedByFaces = norms return self._faceNormalsIndexedByFaces else: raise Exception("Invalid indexing mode. Accepts: None, 'faces'") def vertexNormals(self, indexed=None): """ Return an array of normal vectors. By default, the array will be (N, 3) with one entry per unique vertex in the mesh. If indexed is 'faces', then the array will contain three normal vectors per face (and some vertexes may be repeated). """ if self._vertexNormals is None: faceNorms = self.faceNormals() vertFaces = self.vertexFaces() self._vertexNormals = np.empty(self._vertexes.shape, dtype=np.float32) for vindex in range(self._vertexes.shape[0]): faces = vertFaces[vindex] if len(faces) == 0: self._vertexNormals[vindex] = (0,0,0) continue norms = faceNorms[faces] ## get all face normals norm = norms.sum(axis=0) ## sum normals norm /= (norm**2).sum()**0.5 ## and re-normalize self._vertexNormals[vindex] = norm if indexed is None: return self._vertexNormals elif indexed == 'faces': return self._vertexNormals[self.faces()] else: raise Exception("Invalid indexing mode. Accepts: None, 'faces'") def vertexColors(self, indexed=None): """ Return an array (Nv, 4) of vertex colors. If indexed=='faces', then instead return an indexed array (Nf, 3, 4). """ if indexed is None: return self._vertexColors elif indexed == 'faces': if self._vertexColorsIndexedByFaces is None: self._vertexColorsIndexedByFaces = self._vertexColors[self.faces()] return self._vertexColorsIndexedByFaces else: raise Exception("Invalid indexing mode. Accepts: None, 'faces'") def setVertexColors(self, colors, indexed=None): """ Set the vertex color array (Nv, 4). If indexed=='faces', then the array will be interpreted as indexed and should have shape (Nf, 3, 4) """ if indexed is None: self._vertexColors = np.ascontiguousarray(colors, dtype=np.float32) self._vertexColorsIndexedByFaces = None elif indexed == 'faces': self._vertexColors = None self._vertexColorsIndexedByFaces = np.ascontiguousarray(colors, dtype=np.float32) else: raise Exception("Invalid indexing mode. Accepts: None, 'faces'") def faceColors(self, indexed=None): """ Return an array (Nf, 4) of face colors. If indexed=='faces', then instead return an indexed array (Nf, 3, 4) (note this is just the same array with each color repeated three times). """ if indexed is None: return self._faceColors elif indexed == 'faces': if self._faceColorsIndexedByFaces is None and self._faceColors is not None: Nf = self._faceColors.shape[0] self._faceColorsIndexedByFaces = np.empty((Nf, 3, 4), dtype=self._faceColors.dtype) self._faceColorsIndexedByFaces[:] = self._faceColors.reshape(Nf, 1, 4) return self._faceColorsIndexedByFaces else: raise Exception("Invalid indexing mode. Accepts: None, 'faces'") def setFaceColors(self, colors, indexed=None): """ Set the face color array (Nf, 4). If indexed=='faces', then the array will be interpreted as indexed and should have shape (Nf, 3, 4) """ if indexed is None: self._faceColors = np.ascontiguousarray(colors, dtype=np.float32) self._faceColorsIndexedByFaces = None elif indexed == 'faces': self._faceColors = None self._faceColorsIndexedByFaces = np.ascontiguousarray(colors, dtype=np.float32) else: raise Exception("Invalid indexing mode. Accepts: None, 'faces'") def faceCount(self): """ Return the number of faces in the mesh. """ if self._faces is not None: return self._faces.shape[0] elif self._vertexesIndexedByFaces is not None: return self._vertexesIndexedByFaces.shape[0] def edgeColors(self): return self._edgeColors #def _setIndexedFaces(self, faces, vertexColors=None, faceColors=None): #self._vertexesIndexedByFaces = faces #self._vertexColorsIndexedByFaces = vertexColors #self._faceColorsIndexedByFaces = faceColors def _computeUnindexedVertexes(self): ## Given (Nv, 3, 3) array of vertexes-indexed-by-face, convert backward to unindexed vertexes ## This is done by collapsing into a list of 'unique' vertexes (difference < 1e-14) ## I think generally this should be discouraged.. faces = self._vertexesIndexedByFaces verts = {} ## used to remember the index of each vertex position self._faces = np.empty(faces.shape[:2], dtype=np.uint) self._vertexes = [] self._vertexFaces = [] self._faceNormals = None self._vertexNormals = None for i in range(faces.shape[0]): face = faces[i] inds = [] for j in range(face.shape[0]): pt = face[j] pt2 = tuple([round(x*1e14) for x in pt]) ## quantize to be sure that nearly-identical points will be merged index = verts.get(pt2, None) if index is None: #self._vertexes.append(QtGui.QVector3D(*pt)) self._vertexes.append(pt) self._vertexFaces.append([]) index = len(self._vertexes)-1 verts[pt2] = index self._vertexFaces[index].append(i) # keep track of which vertexes belong to which faces self._faces[i,j] = index self._vertexes = np.array(self._vertexes, dtype=np.float32) #def _setUnindexedFaces(self, faces, vertexes, vertexColors=None, faceColors=None): #self._vertexes = vertexes #[QtGui.QVector3D(*v) for v in vertexes] #self._faces = faces.astype(np.uint) #self._edges = None #self._vertexFaces = None #self._faceNormals = None #self._vertexNormals = None #self._vertexColors = vertexColors #self._faceColors = faceColors def vertexFaces(self): """ Return list mapping each vertex index to a list of face indexes that use the vertex. """ if self._vertexFaces is None: self._vertexFaces = [[] for i in range(len(self.vertexes()))] for i in range(self._faces.shape[0]): face = self._faces[i] for ind in face: self._vertexFaces[ind].append(i) return self._vertexFaces #def reverseNormals(self): #""" #Reverses the direction of all normal vectors. #""" #pass #def generateEdgesFromFaces(self): #""" #Generate a set of edges by listing all the edges of faces and removing any duplicates. #Useful for displaying wireframe meshes. #""" #pass def _computeEdges(self): if not self.hasFaceIndexedData(): ## generate self._edges from self._faces nf = len(self._faces) edges = np.empty(nf*3, dtype=[('i', np.uint, 2)]) edges['i'][0:nf] = self._faces[:,:2] edges['i'][nf:2*nf] = self._faces[:,1:3] edges['i'][-nf:,0] = self._faces[:,2] edges['i'][-nf:,1] = self._faces[:,0] # sort per-edge mask = edges['i'][:,0] > edges['i'][:,1] edges['i'][mask] = edges['i'][mask][:,::-1] # remove duplicate entries self._edges = np.unique(edges)['i'] #print self._edges elif self._vertexesIndexedByFaces is not None: verts = self._vertexesIndexedByFaces edges = np.empty((verts.shape[0], 3, 2), dtype=np.uint) nf = verts.shape[0] edges[:,0,0] = np.arange(nf) * 3 edges[:,0,1] = edges[:,0,0] + 1 edges[:,1,0] = edges[:,0,1] edges[:,1,1] = edges[:,1,0] + 1 edges[:,2,0] = edges[:,1,1] edges[:,2,1] = edges[:,0,0] self._edges = edges else: raise Exception("MeshData cannot generate edges--no faces in this data.") def save(self): """Serialize this mesh to a string appropriate for disk storage""" import pickle if self._faces is not None: names = ['_vertexes', '_faces'] else: names = ['_vertexesIndexedByFaces'] if self._vertexColors is not None: names.append('_vertexColors') elif self._vertexColorsIndexedByFaces is not None: names.append('_vertexColorsIndexedByFaces') if self._faceColors is not None: names.append('_faceColors') elif self._faceColorsIndexedByFaces is not None: names.append('_faceColorsIndexedByFaces') state = dict([(n,getattr(self, n)) for n in names]) return pickle.dumps(state) def restore(self, state): """Restore the state of a mesh previously saved using save()""" import pickle state = pickle.loads(state) for k in state: if isinstance(state[k], list): if isinstance(state[k][0], QtGui.QVector3D): state[k] = [[v.x(), v.y(), v.z()] for v in state[k]] state[k] = np.array(state[k]) setattr(self, k, state[k]) @staticmethod def sphere(rows, cols, radius=1.0, offset=True): """ Return a MeshData instance with vertexes and faces computed for a spherical surface. """ verts = np.empty((rows+1, cols, 3), dtype=float) ## compute vertexes phi = (np.arange(rows+1) * np.pi / rows).reshape(rows+1, 1) s = radius * np.sin(phi) verts[...,2] = radius * np.cos(phi) th = ((np.arange(cols) * 2 * np.pi / cols).reshape(1, cols)) if offset: th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1,1)) ## rotate each row by 1/2 column verts[...,0] = s * np.cos(th) verts[...,1] = s * np.sin(th) verts = verts.reshape((rows+1)*cols, 3)[cols-1:-(cols-1)] ## remove redundant vertexes from top and bottom ## compute faces faces = np.empty((rows*cols*2, 3), dtype=np.uint) rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]]) rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]]) for row in range(rows): start = row * cols * 2 faces[start:start+cols] = rowtemplate1 + row * cols faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols faces = faces[cols:-cols] ## cut off zero-area triangles at top and bottom ## adjust for redundant vertexes that were removed from top and bottom vmin = cols-1 faces[facesvmax] = vmax return MeshData(vertexes=verts, faces=faces) @staticmethod def cylinder(rows, cols, radius=[1.0, 1.0], length=1.0, offset=False): """ Return a MeshData instance with vertexes and faces computed for a cylindrical surface. The cylinder may be tapered with different radii at each end (truncated cone) """ verts = np.empty((rows+1, cols, 3), dtype=float) if isinstance(radius, int): radius = [radius, radius] # convert to list ## compute vertexes th = np.linspace(2 * np.pi, (2 * np.pi)/cols, cols).reshape(1, cols) r = np.linspace(radius[0],radius[1],num=rows+1, endpoint=True).reshape(rows+1, 1) # radius as a function of z verts[...,2] = np.linspace(0, length, num=rows+1, endpoint=True).reshape(rows+1, 1) # z if offset: th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1,1)) ## rotate each row by 1/2 column verts[...,0] = r * np.cos(th) # x = r cos(th) verts[...,1] = r * np.sin(th) # y = r sin(th) verts = verts.reshape((rows+1)*cols, 3) # just reshape: no redundant vertices... ## compute faces faces = np.empty((rows*cols*2, 3), dtype=np.uint) rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]]) rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]]) for row in range(rows): start = row * cols * 2 faces[start:start+cols] = rowtemplate1 + row * cols faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols return MeshData(vertexes=verts, faces=faces) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/__init__.py000066400000000000000000000013421421045507400232500ustar00rootroot00000000000000from . import shaders from .GLViewWidget import GLViewWidget from .items.GLAxisItem import * from .items.GLBarGraphItem import * from .items.GLBoxItem import * from .items.GLGradientLegendItem import * from .items.GLGraphItem import * from .items.GLGridItem import * from .items.GLImageItem import * from .items.GLLinePlotItem import * from .items.GLMeshItem import * from .items.GLScatterPlotItem import * from .items.GLSurfacePlotItem import * from .items.GLTextItem import * from .items.GLVolumeItem import * from .MeshData import MeshData ## dynamic imports cause too many problems. #from .. import importAll #importAll('items', globals(), locals()) ## for backward compatibility: #MeshData.MeshData = MeshData ## breaks autodoc. pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/000077500000000000000000000000001421045507400222605ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLAxisItem.py000066400000000000000000000034071421045507400246040ustar00rootroot00000000000000from OpenGL.GL import * # noqa from ... import QtGui from ..GLGraphicsItem import GLGraphicsItem __all__ = ['GLAxisItem'] class GLAxisItem(GLGraphicsItem): """ **Bases:** :class:`GLGraphicsItem ` Displays three lines indicating origin and orientation of local coordinate system. """ def __init__(self, size=None, antialias=True, glOptions='translucent'): GLGraphicsItem.__init__(self) if size is None: size = QtGui.QVector3D(1,1,1) self.antialias = antialias self.setSize(size=size) self.setGLOptions(glOptions) def setSize(self, x=None, y=None, z=None, size=None): """ Set the size of the axes (in its local coordinate system; this does not affect the transform) Arguments can be x,y,z or size=QVector3D(). """ if size is not None: x = size.x() y = size.y() z = size.z() self.__size = [x,y,z] self.update() def size(self): return self.__size[:] def paint(self): #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) #glEnable( GL_BLEND ) #glEnable( GL_ALPHA_TEST ) self.setupGLState() if self.antialias: glEnable(GL_LINE_SMOOTH) glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) glBegin( GL_LINES ) x,y,z = self.size() glColor4f(0, 1, 0, .6) # z is green glVertex3f(0, 0, 0) glVertex3f(0, 0, z) glColor4f(1, 1, 0, .6) # y is yellow glVertex3f(0, 0, 0) glVertex3f(0, y, 0) glColor4f(0, 0, 1, .6) # x is blue glVertex3f(0, 0, 0) glVertex3f(x, 0, 0) glEnd() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLBarGraphItem.py000066400000000000000000000020261421045507400253620ustar00rootroot00000000000000__all__ = ["GLBarGraphItem"] import numpy as np from ..MeshData import MeshData from .GLMeshItem import GLMeshItem class GLBarGraphItem(GLMeshItem): def __init__(self, pos, size): """ pos is (...,3) array of the bar positions (the corner of each bar) size is (...,3) array of the sizes of each bar """ nCubes = np.prod(pos.shape[:-1]) cubeVerts = np.mgrid[0:2,0:2,0:2].reshape(3,8).transpose().reshape(1,8,3) cubeFaces = np.array([ [0,1,2], [3,2,1], [4,5,6], [7,6,5], [0,1,4], [5,4,1], [2,3,6], [7,6,3], [0,2,4], [6,4,2], [1,3,5], [7,5,3]]).reshape(1,12,3) size = size.reshape((nCubes, 1, 3)) pos = pos.reshape((nCubes, 1, 3)) verts = cubeVerts * size + pos faces = cubeFaces + (np.arange(nCubes) * 8).reshape(nCubes,1,1) md = MeshData(verts.reshape(nCubes*8,3), faces.reshape(nCubes*12,3)) GLMeshItem.__init__(self, meshdata=md, shader='shaded', smooth=False) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLBoxItem.py000066400000000000000000000046361421045507400244350ustar00rootroot00000000000000from OpenGL.GL import * # noqa from ... import functions as fn from ...Qt import QtGui from ..GLGraphicsItem import GLGraphicsItem __all__ = ['GLBoxItem'] class GLBoxItem(GLGraphicsItem): """ **Bases:** :class:`GLGraphicsItem ` Displays a wire-frame box. """ def __init__(self, size=None, color=None, glOptions='translucent'): GLGraphicsItem.__init__(self) if size is None: size = QtGui.QVector3D(1,1,1) self.setSize(size=size) if color is None: color = (255,255,255,80) self.setColor(color) self.setGLOptions(glOptions) def setSize(self, x=None, y=None, z=None, size=None): """ Set the size of the box (in its local coordinate system; this does not affect the transform) Arguments can be x,y,z or size=QVector3D(). """ if size is not None: x = size.x() y = size.y() z = size.z() self.__size = [x,y,z] self.update() def size(self): return self.__size[:] def setColor(self, *args): """Set the color of the box. Arguments are the same as those accepted by functions.mkColor()""" self.__color = fn.mkColor(*args) def color(self): return self.__color def paint(self): #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) #glEnable( GL_BLEND ) #glEnable( GL_ALPHA_TEST ) ##glAlphaFunc( GL_ALWAYS,0.5 ) #glEnable( GL_POINT_SMOOTH ) #glDisable( GL_DEPTH_TEST ) self.setupGLState() glBegin( GL_LINES ) glColor4f(*self.color().getRgbF()) x,y,z = self.size() glVertex3f(0, 0, 0) glVertex3f(0, 0, z) glVertex3f(x, 0, 0) glVertex3f(x, 0, z) glVertex3f(0, y, 0) glVertex3f(0, y, z) glVertex3f(x, y, 0) glVertex3f(x, y, z) glVertex3f(0, 0, 0) glVertex3f(0, y, 0) glVertex3f(x, 0, 0) glVertex3f(x, y, 0) glVertex3f(0, 0, z) glVertex3f(0, y, z) glVertex3f(x, 0, z) glVertex3f(x, y, z) glVertex3f(0, 0, 0) glVertex3f(x, 0, 0) glVertex3f(0, y, 0) glVertex3f(x, y, 0) glVertex3f(0, 0, z) glVertex3f(x, 0, z) glVertex3f(0, y, z) glVertex3f(x, y, z) glEnd() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLGradientLegendItem.py000066400000000000000000000056251421045507400265600ustar00rootroot00000000000000from ... import functions as fn from ...colormap import ColorMap from ...Qt import QtCore, QtGui from ..GLGraphicsItem import GLGraphicsItem __all__ = ['GLGradientLegendItem'] class GLGradientLegendItem(GLGraphicsItem): """ Displays legend colorbar on the screen. """ def __init__(self, **kwds): """ Arguments: pos: position of the colorbar on the screen, from the top left corner, in pixels size: size of the colorbar without the text, in pixels gradient: a pg.ColorMap used to color the colorbar labels: a dict of "text":value to display next to the colorbar. The value corresponds to a position in the gradient from 0 to 1. fontColor: sets the color of the texts. Accepts any single argument accepted by :func:`~pyqtgraph.mkColor` #Todo: size as percentage legend title """ GLGraphicsItem.__init__(self) glopts = kwds.pop("glOptions", "additive") self.setGLOptions(glopts) self.pos = (10, 10) self.size = (10, 100) self.fontColor = QtGui.QColor(QtCore.Qt.GlobalColor.white) # setup a default trivial gradient stops = (0.0, 1.0) self.gradient = ColorMap(pos=stops, color=(0.0, 1.0)) self._gradient = None self.labels = {str(x) : x for x in stops} self.setData(**kwds) def setData(self, **kwds): args = ["size", "pos", "gradient", "labels", "fontColor"] for k in kwds.keys(): if k not in args: raise Exception( "Invalid keyword argument: %s (allowed arguments are %s)" % (k, str(args)) ) self.antialias = False for key in kwds: value = kwds[key] if key == 'fontColor': value = fn.mkColor(value) elif key == 'gradient': self._gradient = None setattr(self, key, value) ##todo: add title ##todo: take more gradient types self.update() def paint(self): self.setupGLState() if self._gradient is None: self._gradient = self.gradient.getGradient() barRect = QtCore.QRectF(self.pos[0], self.pos[1], self.size[0], self.size[1]) self._gradient.setStart(barRect.bottomLeft()) self._gradient.setFinalStop(barRect.topLeft()) painter = QtGui.QPainter(self.view()) painter.fillRect(barRect, self._gradient) painter.setPen(self.fontColor) for labelText, labelPosition in self.labels.items(): ## todo: draw ticks, position text properly x = 1.1 * self.size[0] + self.pos[0] y = self.size[1] - labelPosition * self.size[1] + self.pos[1] + 8 ##todo: fonts painter.drawText(QtCore.QPointF(x, y), labelText) painter.end() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLGraphItem.py000066400000000000000000000075011421045507400247400ustar00rootroot00000000000000from OpenGL.GL import * # noqa import numpy as np from ... import functions as fn from ...Qt import QtCore, QtGui from ..GLGraphicsItem import GLGraphicsItem from .GLScatterPlotItem import GLScatterPlotItem __all__ = ['GLGraphItem'] class GLGraphItem(GLGraphicsItem): """A GLGraphItem displays graph information as a set of nodes connected by lines (as in 'graph theory', not 'graphics'). Useful for drawing networks, trees, etc. """ def __init__(self, **kwds): GLGraphicsItem.__init__(self) self.edges = None self.edgeColor = QtGui.QColor(QtCore.Qt.GlobalColor.white) self.edgeWidth = 1.0 self.scatter = GLScatterPlotItem() self.scatter.setParentItem(self) self.setData(**kwds) def setData(self, **kwds): """ Change the data displayed by the graph. Parameters ---------- edges : np.ndarray 2D array of shape (M, 2) of connection data, each row contains indexes of two nodes that are connected. Dtype must be integer or unsigned. edgeColor: QColor, array-like, optional. The color to draw edges. Accepts the same arguments as :func:`~pyqtgraph.mkColor()`. If None, no edges will be drawn. Default is (1.0, 1.0, 1.0, 0.5). edgeWidth: float, optional. Value specifying edge width. Default is 1.0 nodePositions : np.ndarray 2D array of shape (N, 3), where each row represents the x, y, z coordinates for each node nodeColor : np.ndarray, QColor, str or array like 2D array of shape (N, 4) of dtype float32, where each row represents the R, G, B, A vakues in range of 0-1, or for the same color for all nodes, provide either QColor type or input for :func:`~pyqtgraph.mkColor()` nodeSize : np.ndarray, float or int Either 2D numpy array of shape (N, 1) where each row represents the size of each node, or if a scalar, apply the same size to all nodes **kwds All other keyword arguments are given to :meth:`GLScatterPlotItem.setData() ` to affect the appearance of nodes (pos, color, size, pxMode, etc.) Raises ------ TypeError When dtype of edges dtype is not unisnged or integer dtype """ if 'edges' in kwds: self.edges = kwds.pop('edges') if self.edges.dtype.kind not in 'iu': raise TypeError("edges array must have int or unsigned dtype.") if 'edgeColor' in kwds: edgeColor = kwds.pop('edgeColor') self.edgeColor = fn.mkColor(edgeColor) if edgeColor is not None else None if 'edgeWidth' in kwds: self.edgeWidth = kwds.pop('edgeWidth') if 'nodePositions' in kwds: kwds['pos'] = kwds.pop('nodePositions') if 'nodeColor' in kwds: kwds['color'] = kwds.pop('nodeColor') if 'nodeSize' in kwds: kwds['size'] = kwds.pop('nodeSize') self.scatter.setData(**kwds) self.update() def initializeGL(self): self.scatter.initializeGL() def paint(self): if self.scatter.pos is None \ or self.edges is None \ or self.edgeColor is None: return None verts = self.scatter.pos edges = self.edges.astype(np.uint32).flatten() glEnableClientState(GL_VERTEX_ARRAY) try: glColor4f(*self.edgeColor.getRgbF()) glLineWidth(self.edgeWidth) glVertexPointerf(verts) glDrawElements(GL_LINES, edges.shape[0], GL_UNSIGNED_INT, edges) finally: glDisableClientState(GL_VERTEX_ARRAY) return None pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLGridItem.py000066400000000000000000000050771421045507400245720ustar00rootroot00000000000000from OpenGL.GL import * # noqa import numpy as np from ... import QtGui from ... import functions as fn from ..GLGraphicsItem import GLGraphicsItem __all__ = ['GLGridItem'] class GLGridItem(GLGraphicsItem): """ **Bases:** :class:`GLGraphicsItem ` Displays a wire-frame grid. """ def __init__(self, size=None, color=(255, 255, 255, 76.5), antialias=True, glOptions='translucent'): GLGraphicsItem.__init__(self) self.setGLOptions(glOptions) self.antialias = antialias if size is None: size = QtGui.QVector3D(20,20,1) self.setSize(size=size) self.setSpacing(1, 1, 1) self.setColor(color) def setSize(self, x=None, y=None, z=None, size=None): """ Set the size of the axes (in its local coordinate system; this does not affect the transform) Arguments can be x,y,z or size=QVector3D(). """ if size is not None: x = size.x() y = size.y() z = size.z() self.__size = [x,y,z] self.update() def size(self): return self.__size[:] def setSpacing(self, x=None, y=None, z=None, spacing=None): """ Set the spacing between grid lines. Arguments can be x,y,z or spacing=QVector3D(). """ if spacing is not None: x = spacing.x() y = spacing.y() z = spacing.z() self.__spacing = [x,y,z] self.update() def spacing(self): return self.__spacing[:] def setColor(self, color): """Set the color of the grid. Arguments are the same as those accepted by functions.mkColor()""" self.__color = fn.mkColor(color) self.update() def color(self): return self.__color def paint(self): self.setupGLState() if self.antialias: glEnable(GL_LINE_SMOOTH) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) glBegin( GL_LINES ) x,y,z = self.size() xs,ys,zs = self.spacing() xvals = np.arange(-x/2., x/2. + xs*0.001, xs) yvals = np.arange(-y/2., y/2. + ys*0.001, ys) glColor4f(*self.color().getRgbF()) for x in xvals: glVertex3f(x, yvals[0], 0) glVertex3f(x, yvals[-1], 0) for y in yvals: glVertex3f(xvals[0], y, 0) glVertex3f(xvals[-1], y, 0) glEnd() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLImageItem.py000066400000000000000000000071331421045507400247220ustar00rootroot00000000000000from OpenGL.GL import * # noqa from ..GLGraphicsItem import GLGraphicsItem __all__ = ['GLImageItem'] class GLImageItem(GLGraphicsItem): """ **Bases:** :class:`GLGraphicsItem ` Displays image data as a textured quad. """ def __init__(self, data, smooth=False, glOptions='translucent'): """ ============== ======================================================================================= **Arguments:** data Volume data to be rendered. *Must* be 3D numpy array (x, y, RGBA) with dtype=ubyte. (See functions.makeRGBA) smooth (bool) If True, the volume slices are rendered with linear interpolation ============== ======================================================================================= """ self.smooth = smooth self._needUpdate = False GLGraphicsItem.__init__(self) self.setData(data) self.setGLOptions(glOptions) self.texture = None def initializeGL(self): if self.texture is not None: return glEnable(GL_TEXTURE_2D) self.texture = glGenTextures(1) def setData(self, data): self.data = data self._needUpdate = True self.update() def _updateTexture(self): glBindTexture(GL_TEXTURE_2D, self.texture) if self.smooth: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) else: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) #glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) h, w = self.data.shape[:2] ## Test texture dimensions first glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, None) if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % (h, w)) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data) glDisable(GL_TEXTURE_2D) #self.lists = {} #for ax in [0,1,2]: #for d in [-1, 1]: #l = glGenLists(1) #self.lists[(ax,d)] = l #glNewList(l, GL_COMPILE) #self.drawVolume(ax, d) #glEndList() def paint(self): if self._needUpdate: self._updateTexture() self._needUpdate = False glEnable(GL_TEXTURE_2D) glBindTexture(GL_TEXTURE_2D, self.texture) self.setupGLState() #glEnable(GL_DEPTH_TEST) ##glDisable(GL_CULL_FACE) #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) #glEnable( GL_BLEND ) #glEnable( GL_ALPHA_TEST ) glColor4f(1,1,1,1) h, w = self.data.shape[:2] glBegin(GL_QUADS) glTexCoord2f(1, 0) glVertex3f(0, 0, 0) glTexCoord2f(1, 1) glVertex3f(h, 0, 0) glTexCoord2f(0, 1) glVertex3f(h, w, 0) glTexCoord2f(0, 0) glVertex3f(0, w, 0) glEnd() glDisable(GL_TEXTURE_2D) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLLinePlotItem.py000066400000000000000000000074751421045507400254370ustar00rootroot00000000000000from OpenGL.GL import * # noqa import numpy as np from ... import QtGui from ... import functions as fn from ..GLGraphicsItem import GLGraphicsItem __all__ = ['GLLinePlotItem'] class GLLinePlotItem(GLGraphicsItem): """Draws line plots in 3D.""" def __init__(self, **kwds): """All keyword arguments are passed to setData()""" GLGraphicsItem.__init__(self) glopts = kwds.pop('glOptions', 'additive') self.setGLOptions(glopts) self.pos = None self.mode = 'line_strip' self.width = 1. self.color = (1.0,1.0,1.0,1.0) self.setData(**kwds) def setData(self, **kwds): """ Update the data displayed by this item. All arguments are optional; for example it is allowed to update vertex positions while leaving colors unchanged, etc. ==================== ================================================== **Arguments:** ------------------------------------------------------------------------ pos (N,3) array of floats specifying point locations. color (N,4) array of floats (0.0-1.0) or tuple of floats specifying a single color for the entire item. width float specifying line width antialias enables smooth line drawing mode 'lines': Each pair of vertexes draws a single line segment. 'line_strip': All vertexes are drawn as a continuous set of line segments. ==================== ================================================== """ args = ['pos', 'color', 'width', 'mode', 'antialias'] for k in kwds.keys(): if k not in args: raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) self.antialias = False if 'pos' in kwds: pos = kwds.pop('pos') self.pos = np.ascontiguousarray(pos, dtype=np.float32) if 'color' in kwds: color = kwds.pop('color') if isinstance(color, np.ndarray): color = np.ascontiguousarray(color, dtype=np.float32) self.color = color for k, v in kwds.items(): setattr(self, k, v) self.update() def paint(self): if self.pos is None: return self.setupGLState() glEnableClientState(GL_VERTEX_ARRAY) try: glVertexPointerf(self.pos) if isinstance(self.color, np.ndarray): glEnableClientState(GL_COLOR_ARRAY) glColorPointerf(self.color) else: color = self.color if isinstance(color, str): color = fn.mkColor(color) if isinstance(color, QtGui.QColor): color = color.getRgbF() glColor4f(*color) glLineWidth(self.width) if self.antialias: glEnable(GL_LINE_SMOOTH) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) if self.mode == 'line_strip': glDrawArrays(GL_LINE_STRIP, 0, self.pos.shape[0]) elif self.mode == 'lines': glDrawArrays(GL_LINES, 0, self.pos.shape[0]) else: raise Exception("Unknown line mode '%s'. (must be 'lines' or 'line_strip')" % self.mode) finally: glDisableClientState(GL_COLOR_ARRAY) glDisableClientState(GL_VERTEX_ARRAY) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLMeshItem.py000066400000000000000000000206141421045507400245730ustar00rootroot00000000000000from OpenGL.GL import * # noqa import numpy as np from ...Qt import QtGui from .. import shaders from ..GLGraphicsItem import GLGraphicsItem from ..MeshData import MeshData __all__ = ['GLMeshItem'] class GLMeshItem(GLGraphicsItem): """ **Bases:** :class:`GLGraphicsItem ` Displays a 3D triangle mesh. """ def __init__(self, **kwds): """ ============== ===================================================== **Arguments:** meshdata MeshData object from which to determine geometry for this item. color Default face color used if no vertex or face colors are specified. edgeColor Default edge color to use if no edge colors are specified in the mesh data. drawEdges If True, a wireframe mesh will be drawn. (default=False) drawFaces If True, mesh faces are drawn. (default=True) shader Name of shader program to use when drawing faces. (None for no shader) smooth If True, normal vectors are computed for each vertex and interpolated within each face. computeNormals If False, then computation of normal vectors is disabled. This can provide a performance boost for meshes that do not make use of normals. ============== ===================================================== """ self.opts = { 'meshdata': None, 'color': (1., 1., 1., 1.), 'drawEdges': False, 'drawFaces': True, 'edgeColor': (0.5, 0.5, 0.5, 1.0), 'shader': None, 'smooth': True, 'computeNormals': True, } GLGraphicsItem.__init__(self) glopts = kwds.pop('glOptions', 'opaque') self.setGLOptions(glopts) shader = kwds.pop('shader', None) self.setShader(shader) self.setMeshData(**kwds) ## storage for data compiled from MeshData object self.vertexes = None self.normals = None self.colors = None self.faces = None def setShader(self, shader): """Set the shader used when rendering faces in the mesh. (see the GL shaders example)""" self.opts['shader'] = shader self.update() def shader(self): shader = self.opts['shader'] if isinstance(shader, shaders.ShaderProgram): return shader else: return shaders.getShaderProgram(shader) def setColor(self, c): """Set the default color to use when no vertex or face colors are specified.""" self.opts['color'] = c self.update() def setMeshData(self, **kwds): """ Set mesh data for this item. This can be invoked two ways: 1. Specify *meshdata* argument with a new MeshData object 2. Specify keyword arguments to be passed to MeshData(..) to create a new instance. """ md = kwds.get('meshdata', None) if md is None: opts = {} for k in ['vertexes', 'faces', 'edges', 'vertexColors', 'faceColors']: try: opts[k] = kwds.pop(k) except KeyError: pass md = MeshData(**opts) self.opts['meshdata'] = md self.opts.update(kwds) self.meshDataChanged() self.update() def meshDataChanged(self): """ This method must be called to inform the item that the MeshData object has been altered. """ self.vertexes = None self.faces = None self.normals = None self.colors = None self.edges = None self.edgeColors = None self.update() def parseMeshData(self): ## interpret vertex / normal data before drawing ## This can: ## - automatically generate normals if they were not specified ## - pull vertexes/noormals/faces from MeshData if that was specified if self.vertexes is not None and self.normals is not None: return #if self.opts['normals'] is None: #if self.opts['meshdata'] is None: #self.opts['meshdata'] = MeshData(vertexes=self.opts['vertexes'], faces=self.opts['faces']) if self.opts['meshdata'] is not None: md = self.opts['meshdata'] if self.opts['smooth'] and not md.hasFaceIndexedData(): self.vertexes = md.vertexes() if self.opts['computeNormals']: self.normals = md.vertexNormals() self.faces = md.faces() if md.hasVertexColor(): self.colors = md.vertexColors() if md.hasFaceColor(): self.colors = md.faceColors() else: self.vertexes = md.vertexes(indexed='faces') if self.opts['computeNormals']: if self.opts['smooth']: self.normals = md.vertexNormals(indexed='faces') else: self.normals = md.faceNormals(indexed='faces') self.faces = None if md.hasVertexColor(): self.colors = md.vertexColors(indexed='faces') elif md.hasFaceColor(): self.colors = md.faceColors(indexed='faces') if self.opts['drawEdges']: if not md.hasFaceIndexedData(): self.edges = md.edges() self.edgeVerts = md.vertexes() else: self.edges = md.edges() self.edgeVerts = md.vertexes(indexed='faces') return def paint(self): self.setupGLState() self.parseMeshData() if self.opts['drawFaces']: with self.shader(): verts = self.vertexes norms = self.normals color = self.colors faces = self.faces if verts is None: return glEnableClientState(GL_VERTEX_ARRAY) try: glVertexPointerf(verts) if self.colors is None: color = self.opts['color'] if isinstance(color, QtGui.QColor): glColor4f(*color.getRgbF()) else: glColor4f(*color) else: glEnableClientState(GL_COLOR_ARRAY) glColorPointerf(color) if norms is not None: glEnableClientState(GL_NORMAL_ARRAY) glNormalPointerf(norms) if faces is None: glDrawArrays(GL_TRIANGLES, 0, np.product(verts.shape[:-1])) else: faces = faces.astype(np.uint32).flatten() glDrawElements(GL_TRIANGLES, faces.shape[0], GL_UNSIGNED_INT, faces) finally: glDisableClientState(GL_NORMAL_ARRAY) glDisableClientState(GL_VERTEX_ARRAY) glDisableClientState(GL_COLOR_ARRAY) if self.opts['drawEdges']: verts = self.edgeVerts edges = self.edges glEnableClientState(GL_VERTEX_ARRAY) try: glVertexPointerf(verts) if self.edgeColors is None: color = self.opts['edgeColor'] if isinstance(color, QtGui.QColor): glColor4f(*color.getRgbF()) else: glColor4f(*color) else: glEnableClientState(GL_COLOR_ARRAY) glColorPointerf(color) edges = edges.flatten() glDrawElements(GL_LINES, edges.shape[0], GL_UNSIGNED_INT, edges) finally: glDisableClientState(GL_VERTEX_ARRAY) glDisableClientState(GL_COLOR_ARRAY) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLScatterPlotItem.py000066400000000000000000000172311421045507400261440ustar00rootroot00000000000000from OpenGL.GL import * # noqa import numpy as np from ... import functions as fn from ...Qt import QtGui from .. import shaders from ..GLGraphicsItem import GLGraphicsItem __all__ = ['GLScatterPlotItem'] class GLScatterPlotItem(GLGraphicsItem): """Draws points at a list of 3D positions.""" def __init__(self, **kwds): super().__init__() glopts = kwds.pop('glOptions', 'additive') self.setGLOptions(glopts) self.pos = None self.size = 10 self.color = [1.0,1.0,1.0,0.5] self.pxMode = True self.setData(**kwds) self.shader = None def setData(self, **kwds): """ Update the data displayed by this item. All arguments are optional; for example it is allowed to update spot positions while leaving colors unchanged, etc. ==================== ================================================== **Arguments:** pos (N,3) array of floats specifying point locations. color (N,4) array of floats (0.0-1.0) specifying spot colors OR a tuple of floats specifying a single color for all spots. size (N,) array of floats specifying spot sizes or a single value to apply to all spots. pxMode If True, spot sizes are expressed in pixels. Otherwise, they are expressed in item coordinates. ==================== ================================================== """ args = ['pos', 'color', 'size', 'pxMode'] for k in kwds.keys(): if k not in args: raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) if 'pos' in kwds: pos = kwds.pop('pos') self.pos = np.ascontiguousarray(pos, dtype=np.float32) if 'color' in kwds: color = kwds.pop('color') if isinstance(color, np.ndarray): color = np.ascontiguousarray(color, dtype=np.float32) self.color = color if 'size' in kwds: size = kwds.pop('size') if isinstance(size, np.ndarray): size = np.ascontiguousarray(size, dtype=np.float32) self.size = size self.pxMode = kwds.get('pxMode', self.pxMode) self.update() def initializeGL(self): if self.shader is not None: return ## Generate texture for rendering points w = 64 def genTexture(x,y): r = np.hypot((x-(w-1)/2.), (y-(w-1)/2.)) return 255 * (w / 2 - fn.clip_array(r, w / 2 - 1, w / 2)) pData = np.empty((w, w, 4)) pData[:] = 255 pData[:,:,3] = np.fromfunction(genTexture, pData.shape[:2]) pData = pData.astype(np.ubyte) if getattr(self, "pointTexture", None) is None: self.pointTexture = glGenTextures(1) glActiveTexture(GL_TEXTURE0) glEnable(GL_TEXTURE_2D) glBindTexture(GL_TEXTURE_2D, self.pointTexture) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pData.shape[0], pData.shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, pData) self.shader = shaders.getShaderProgram('pointSprite') #def setupGLState(self): #"""Prepare OpenGL state for drawing. This function is called immediately before painting.""" ##glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly. #glBlendFunc(GL_SRC_ALPHA, GL_ONE) #glEnable( GL_BLEND ) #glEnable( GL_ALPHA_TEST ) #glDisable( GL_DEPTH_TEST ) ##glEnable( GL_POINT_SMOOTH ) ##glHint(GL_POINT_SMOOTH_HINT, GL_NICEST) ##glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3)) ##glPointParameterfv(GL_POINT_SIZE_MAX, (65500,)) ##glPointParameterfv(GL_POINT_SIZE_MIN, (0,)) def paint(self): if self.pos is None: return self.setupGLState() glEnable(GL_POINT_SPRITE) glActiveTexture(GL_TEXTURE0) glEnable( GL_TEXTURE_2D ) glBindTexture(GL_TEXTURE_2D, self.pointTexture) glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE) #glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) ## use texture color exactly #glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ) ## texture modulates current color glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) glEnable(GL_PROGRAM_POINT_SIZE) with self.shader: #glUniform1i(self.shader.uniform('texture'), 0) ## inform the shader which texture to use glEnableClientState(GL_VERTEX_ARRAY) try: pos = self.pos #if pos.ndim > 2: #pos = pos.reshape((-1, pos.shape[-1])) glVertexPointerf(pos) if isinstance(self.color, np.ndarray): glEnableClientState(GL_COLOR_ARRAY) glColorPointerf(self.color) else: color = self.color if isinstance(color, QtGui.QColor): color = color.getRgbF() glColor4f(*color) if not self.pxMode or isinstance(self.size, np.ndarray): glEnableClientState(GL_NORMAL_ARRAY) norm = np.zeros(pos.shape, dtype=np.float32) if self.pxMode: norm[...,0] = self.size else: gpos = self.mapToView(pos.transpose()).transpose() if self.view(): pxSize = self.view().pixelSize(gpos) else: pxSize = self.parentItem().view().pixelSize(gpos) norm[...,0] = self.size / pxSize glNormalPointerf(norm) else: glNormal3f(self.size, 0, 0) ## vertex shader uses norm.x to determine point size #glPointSize(self.size) glDrawArrays(GL_POINTS, 0, pos.shape[0]) finally: glDisableClientState(GL_NORMAL_ARRAY) glDisableClientState(GL_VERTEX_ARRAY) glDisableClientState(GL_COLOR_ARRAY) #posVBO.unbind() ##fixes #145 glDisable( GL_TEXTURE_2D ) #for i in range(len(self.pos)): #pos = self.pos[i] #if isinstance(self.color, np.ndarray): #color = self.color[i] #else: #color = self.color #if isinstance(self.color, QtGui.QColor): #color = fn.glColor(self.color) #if isinstance(self.size, np.ndarray): #size = self.size[i] #else: #size = self.size #pxSize = self.view().pixelSize(QtGui.QVector3D(*pos)) #glPointSize(size / pxSize) #glBegin( GL_POINTS ) #glColor4f(*color) # x is blue ##glNormal3f(size, 0, 0) #glVertex3f(*pos) #glEnd() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLSurfacePlotItem.py000066400000000000000000000121021421045507400261170ustar00rootroot00000000000000from OpenGL.GL import * # noqa import numpy as np from ..MeshData import MeshData from .GLMeshItem import GLMeshItem __all__ = ['GLSurfacePlotItem'] class GLSurfacePlotItem(GLMeshItem): """ **Bases:** :class:`GLMeshItem ` Displays a surface plot on a regular x,y grid """ def __init__(self, x=None, y=None, z=None, colors=None, **kwds): """ The x, y, z, and colors arguments are passed to setData(). All other keyword arguments are passed to GLMeshItem.__init__(). """ self._x = None self._y = None self._z = None self._color = None self._vertexes = None self._meshdata = MeshData() GLMeshItem.__init__(self, meshdata=self._meshdata, **kwds) self.setData(x, y, z, colors) def setData(self, x=None, y=None, z=None, colors=None): """ Update the data in this surface plot. ============== ===================================================================== **Arguments:** x,y 1D arrays of values specifying the x,y positions of vertexes in the grid. If these are omitted, then the values will be assumed to be integers. z 2D array of height values for each grid vertex. colors (width, height, 4) array of vertex colors. ============== ===================================================================== All arguments are optional. Note that if vertex positions are updated, the normal vectors for each triangle must be recomputed. This is somewhat expensive if the surface was initialized with smooth=False and very expensive if smooth=True. For faster performance, initialize with computeNormals=False and use per-vertex colors or a normal-independent shader program. """ if x is not None: if self._x is None or len(x) != len(self._x): self._vertexes = None self._x = x if y is not None: if self._y is None or len(y) != len(self._y): self._vertexes = None self._y = y if z is not None: #if self._x is None: #self._x = np.arange(z.shape[0]) #self._vertexes = None #if self._y is None: #self._y = np.arange(z.shape[1]) #self._vertexes = None if self._x is not None and z.shape[0] != len(self._x): raise Exception('Z values must have shape (len(x), len(y))') if self._y is not None and z.shape[1] != len(self._y): raise Exception('Z values must have shape (len(x), len(y))') self._z = z if self._vertexes is not None and self._z.shape != self._vertexes.shape[:2]: self._vertexes = None if colors is not None: self._colors = colors self._meshdata.setVertexColors(colors) if self._z is None: return updateMesh = False newVertexes = False ## Generate vertex and face array if self._vertexes is None: newVertexes = True self._vertexes = np.empty((self._z.shape[0], self._z.shape[1], 3), dtype=float) self.generateFaces() self._meshdata.setFaces(self._faces) updateMesh = True ## Copy x, y, z data into vertex array if newVertexes or x is not None: if x is None: if self._x is None: x = np.arange(self._z.shape[0]) else: x = self._x self._vertexes[:, :, 0] = x.reshape(len(x), 1) updateMesh = True if newVertexes or y is not None: if y is None: if self._y is None: y = np.arange(self._z.shape[1]) else: y = self._y self._vertexes[:, :, 1] = y.reshape(1, len(y)) updateMesh = True if newVertexes or z is not None: self._vertexes[...,2] = self._z updateMesh = True ## Update MeshData if updateMesh: self._meshdata.setVertexes(self._vertexes.reshape(self._vertexes.shape[0]*self._vertexes.shape[1], 3)) self.meshDataChanged() def generateFaces(self): cols = self._z.shape[1]-1 rows = self._z.shape[0]-1 faces = np.empty((cols*rows*2, 3), dtype=np.uint) rowtemplate1 = np.arange(cols).reshape(cols, 1) + np.array([[0, 1, cols+1]]) rowtemplate2 = np.arange(cols).reshape(cols, 1) + np.array([[cols+1, 1, cols+2]]) for row in range(rows): start = row * cols * 2 faces[start:start+cols] = rowtemplate1 + row * (cols+1) faces[start+cols:start+(cols*2)] = rowtemplate2 + row * (cols+1) self._faces = faces pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLTextItem.py000066400000000000000000000071071421045507400246250ustar00rootroot00000000000000from OpenGL.GL import * # noqa import numpy as np from ... import functions as fn from ...Qt import QtCore, QtGui from ..GLGraphicsItem import GLGraphicsItem __all__ = ['GLTextItem'] class GLTextItem(GLGraphicsItem): """Draws text in 3D.""" def __init__(self, **kwds): """All keyword arguments are passed to setData()""" GLGraphicsItem.__init__(self) glopts = kwds.pop('glOptions', 'additive') self.setGLOptions(glopts) self.pos = np.array([0.0, 0.0, 0.0]) self.color = QtCore.Qt.GlobalColor.white self.text = '' self.font = QtGui.QFont('Helvetica', 16) self.setData(**kwds) def setData(self, **kwds): """ Update the data displayed by this item. All arguments are optional; for example it is allowed to update text while leaving colors unchanged, etc. ==================== ================================================== **Arguments:** ------------------------------------------------------------------------ pos (3,) array of floats specifying text location. color QColor or array of ints [R,G,B] or [R,G,B,A]. (Default: Qt.white) text String to display. font QFont (Default: QFont('Helvetica', 16)) ==================== ================================================== """ args = ['pos', 'color', 'text', 'font'] for k in kwds.keys(): if k not in args: raise ValueError('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) for arg in args: if arg in kwds: value = kwds[arg] if arg == 'pos': if isinstance(value, np.ndarray): if value.shape != (3,): raise ValueError('"pos.shape" must be (3,).') elif isinstance(value, (tuple, list)): if len(value) != 3: raise ValueError('"len(pos)" must be 3.') elif arg == 'color': value = fn.mkColor(value) elif arg == 'font': if isinstance(value, QtGui.QFont) is False: raise TypeError('"font" must be QFont.') setattr(self, arg, value) self.update() def paint(self): if len(self.text) < 1: return self.setupGLState() modelview = glGetDoublev(GL_MODELVIEW_MATRIX) projection = glGetDoublev(GL_PROJECTION_MATRIX) viewport = [0, 0, self.view().width(), self.view().height()] text_pos = self.__project(self.pos, modelview, projection, viewport) text_pos.setY(viewport[3] - text_pos.y()) painter = QtGui.QPainter(self.view()) painter.setPen(self.color) painter.setFont(self.font) painter.setRenderHints(QtGui.QPainter.RenderHint.Antialiasing | QtGui.QPainter.RenderHint.TextAntialiasing) painter.drawText(text_pos, self.text) painter.end() def __project(self, obj_pos, modelview, projection, viewport): obj_vec = np.append(np.array(obj_pos), [1.0]) view_vec = np.matmul(modelview.T, obj_vec) proj_vec = np.matmul(projection.T, view_vec) if proj_vec[3] == 0.0: return QtCore.QPointF(0, 0) proj_vec[0:3] /= proj_vec[3] return QtCore.QPointF( viewport[0] + (1.0 + proj_vec[0]) * viewport[2] / 2, viewport[1] + (1.0 + proj_vec[1]) * viewport[3] / 2 ) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/GLVolumeItem.py000066400000000000000000000170051421045507400251460ustar00rootroot00000000000000from OpenGL.GL import * # noqa import numpy as np from ...Qt import QtGui from ..GLGraphicsItem import GLGraphicsItem __all__ = ['GLVolumeItem'] class GLVolumeItem(GLGraphicsItem): """ **Bases:** :class:`GLGraphicsItem ` Displays volumetric data. """ def __init__(self, data, sliceDensity=1, smooth=True, glOptions='translucent'): """ ============== ======================================================================================= **Arguments:** data Volume data to be rendered. *Must* be 4D numpy array (x, y, z, RGBA) with dtype=ubyte. sliceDensity Density of slices to render through the volume. A value of 1 means one slice per voxel. smooth (bool) If True, the volume slices are rendered with linear interpolation ============== ======================================================================================= """ self.sliceDensity = sliceDensity self.smooth = smooth self.data = None self._needUpload = False self.texture = None GLGraphicsItem.__init__(self) self.setGLOptions(glOptions) self.setData(data) def setData(self, data): self.data = data self._needUpload = True self.update() def _uploadData(self): glEnable(GL_TEXTURE_3D) if self.texture is None: self.texture = glGenTextures(1) glBindTexture(GL_TEXTURE_3D, self.texture) if self.smooth: glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) else: glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) shape = self.data.shape ## Test texture dimensions first glTexImage3D(GL_PROXY_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_WIDTH) == 0: raise Exception("OpenGL failed to create 3D texture (%dx%dx%d); too large for this hardware." % shape[:3]) data = np.ascontiguousarray(self.data.transpose((2,1,0,3))) glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, data) glDisable(GL_TEXTURE_3D) self.lists = {} for ax in [0,1,2]: for d in [-1, 1]: l = glGenLists(1) self.lists[(ax,d)] = l glNewList(l, GL_COMPILE) self.drawVolume(ax, d) glEndList() self._needUpload = False def paint(self): if self.data is None: return if self._needUpload: self._uploadData() self.setupGLState() glEnable(GL_TEXTURE_3D) glBindTexture(GL_TEXTURE_3D, self.texture) #glEnable(GL_DEPTH_TEST) #glDisable(GL_CULL_FACE) #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) #glEnable( GL_BLEND ) #glEnable( GL_ALPHA_TEST ) glColor4f(1,1,1,1) view = self.view() center = QtGui.QVector3D(*[x/2. for x in self.data.shape[:3]]) cam = self.mapFromParent(view.cameraPosition()) - center #print "center", center, "cam", view.cameraPosition(), self.mapFromParent(view.cameraPosition()), "diff", cam cam = np.array([cam.x(), cam.y(), cam.z()]) ax = np.argmax(abs(cam)) d = 1 if cam[ax] > 0 else -1 glCallList(self.lists[(ax,d)]) ## draw axes glDisable(GL_TEXTURE_3D) def drawVolume(self, ax, d): N = 5 imax = [0,1,2] imax.remove(ax) tp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]] vp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]] nudge = [0.5/x for x in self.data.shape] tp[0][imax[0]] = 0+nudge[imax[0]] tp[0][imax[1]] = 0+nudge[imax[1]] tp[1][imax[0]] = 1-nudge[imax[0]] tp[1][imax[1]] = 0+nudge[imax[1]] tp[2][imax[0]] = 1-nudge[imax[0]] tp[2][imax[1]] = 1-nudge[imax[1]] tp[3][imax[0]] = 0+nudge[imax[0]] tp[3][imax[1]] = 1-nudge[imax[1]] vp[0][imax[0]] = 0 vp[0][imax[1]] = 0 vp[1][imax[0]] = self.data.shape[imax[0]] vp[1][imax[1]] = 0 vp[2][imax[0]] = self.data.shape[imax[0]] vp[2][imax[1]] = self.data.shape[imax[1]] vp[3][imax[0]] = 0 vp[3][imax[1]] = self.data.shape[imax[1]] slices = self.data.shape[ax] * self.sliceDensity r = list(range(slices)) if d == -1: r = r[::-1] glBegin(GL_QUADS) tzVals = np.linspace(nudge[ax], 1.0-nudge[ax], slices) vzVals = np.linspace(0, self.data.shape[ax], slices) for i in r: z = tzVals[i] w = vzVals[i] tp[0][ax] = z tp[1][ax] = z tp[2][ax] = z tp[3][ax] = z vp[0][ax] = w vp[1][ax] = w vp[2][ax] = w vp[3][ax] = w glTexCoord3f(*tp[0]) glVertex3f(*vp[0]) glTexCoord3f(*tp[1]) glVertex3f(*vp[1]) glTexCoord3f(*tp[2]) glVertex3f(*vp[2]) glTexCoord3f(*tp[3]) glVertex3f(*vp[3]) glEnd() ## Interesting idea: ## remove projection/modelview matrixes, recreate in texture coords. ## it _sorta_ works, but needs tweaking. #mvm = glGetDoublev(GL_MODELVIEW_MATRIX) #pm = glGetDoublev(GL_PROJECTION_MATRIX) #m = QtGui.QMatrix4x4(mvm.flatten()).inverted()[0] #p = QtGui.QMatrix4x4(pm.flatten()).inverted()[0] #glMatrixMode(GL_PROJECTION) #glPushMatrix() #glLoadIdentity() #N=1 #glOrtho(-N,N,-N,N,-100,100) #glMatrixMode(GL_MODELVIEW) #glLoadIdentity() #glMatrixMode(GL_TEXTURE) #glLoadIdentity() #glMultMatrixf(m.copyDataTo()) #view = self.view() #w = view.width() #h = view.height() #dist = view.opts['distance'] #fov = view.opts['fov'] #nearClip = dist * .1 #farClip = dist * 5. #r = nearClip * np.tan(fov) #t = r * h / w #p = QtGui.QMatrix4x4() #p.frustum( -r, r, -t, t, nearClip, farClip) #glMultMatrixf(p.inverted()[0].copyDataTo()) #glBegin(GL_QUADS) #M=1 #for i in range(500): #z = i/500. #w = -i/500. #glTexCoord3f(-M, -M, z) #glVertex3f(-N, -N, w) #glTexCoord3f(M, -M, z) #glVertex3f(N, -N, w) #glTexCoord3f(M, M, z) #glVertex3f(N, N, w) #glTexCoord3f(-M, M, z) #glVertex3f(-N, N, w) #glEnd() #glDisable(GL_TEXTURE_3D) #glMatrixMode(GL_PROJECTION) #glPopMatrix() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/items/__init__.py000066400000000000000000000000001421045507400243570ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/opengl/shaders.py000066400000000000000000000373101421045507400231460ustar00rootroot00000000000000from OpenGL.GL import * # noqa from OpenGL.GL import shaders # noqa try: from OpenGL import NullFunctionError except ImportError: from OpenGL.error import NullFunctionError import numpy as np import re ## For centralizing and managing vertex/fragment shader programs. def initShaders(): global Shaders Shaders = [ ShaderProgram(None, []), ## increases fragment alpha as the normal turns orthogonal to the view ## this is useful for viewing shells that enclose a volume (such as isosurfaces) ShaderProgram('balloon', [ VertexShader(""" varying vec3 normal; void main() { // compute here for use in fragment shader normal = normalize(gl_NormalMatrix * gl_Normal); gl_FrontColor = gl_Color; gl_BackColor = gl_Color; gl_Position = ftransform(); } """), FragmentShader(""" varying vec3 normal; void main() { vec4 color = gl_Color; color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0); gl_FragColor = color; } """) ]), ## colors fragments based on face normals relative to view ## This means that the colors will change depending on how the view is rotated ShaderProgram('viewNormalColor', [ VertexShader(""" varying vec3 normal; void main() { // compute here for use in fragment shader normal = normalize(gl_NormalMatrix * gl_Normal); gl_FrontColor = gl_Color; gl_BackColor = gl_Color; gl_Position = ftransform(); } """), FragmentShader(""" varying vec3 normal; void main() { vec4 color = gl_Color; color.x = (normal.x + 1.0) * 0.5; color.y = (normal.y + 1.0) * 0.5; color.z = (normal.z + 1.0) * 0.5; gl_FragColor = color; } """) ]), ## colors fragments based on absolute face normals. ShaderProgram('normalColor', [ VertexShader(""" varying vec3 normal; void main() { // compute here for use in fragment shader normal = normalize(gl_Normal); gl_FrontColor = gl_Color; gl_BackColor = gl_Color; gl_Position = ftransform(); } """), FragmentShader(""" varying vec3 normal; void main() { vec4 color = gl_Color; color.x = (normal.x + 1.0) * 0.5; color.y = (normal.y + 1.0) * 0.5; color.z = (normal.z + 1.0) * 0.5; gl_FragColor = color; } """) ]), ## very simple simulation of lighting. ## The light source position is always relative to the camera. ShaderProgram('shaded', [ VertexShader(""" varying vec3 normal; void main() { // compute here for use in fragment shader normal = normalize(gl_NormalMatrix * gl_Normal); gl_FrontColor = gl_Color; gl_BackColor = gl_Color; gl_Position = ftransform(); } """), FragmentShader(""" varying vec3 normal; void main() { float p = dot(normal, normalize(vec3(1.0, -1.0, -1.0))); p = p < 0. ? 0. : p * 0.8; vec4 color = gl_Color; color.x = color.x * (0.2 + p); color.y = color.y * (0.2 + p); color.z = color.z * (0.2 + p); gl_FragColor = color; } """) ]), ## colors get brighter near edges of object ShaderProgram('edgeHilight', [ VertexShader(""" varying vec3 normal; void main() { // compute here for use in fragment shader normal = normalize(gl_NormalMatrix * gl_Normal); gl_FrontColor = gl_Color; gl_BackColor = gl_Color; gl_Position = ftransform(); } """), FragmentShader(""" varying vec3 normal; void main() { vec4 color = gl_Color; float s = pow(normal.x*normal.x + normal.y*normal.y, 2.0); color.x = color.x + s * (1.0-color.x); color.y = color.y + s * (1.0-color.y); color.z = color.z + s * (1.0-color.z); gl_FragColor = color; } """) ]), ## colors fragments by z-value. ## This is useful for coloring surface plots by height. ## This shader uses a uniform called "colorMap" to determine how to map the colors: ## red = pow(colorMap[0]*(z + colorMap[1]), colorMap[2]) ## green = pow(colorMap[3]*(z + colorMap[4]), colorMap[5]) ## blue = pow(colorMap[6]*(z + colorMap[7]), colorMap[8]) ## (set the values like this: shader['uniformMap'] = array([...]) ShaderProgram('heightColor', [ VertexShader(""" varying vec4 pos; void main() { gl_FrontColor = gl_Color; gl_BackColor = gl_Color; pos = gl_Vertex; gl_Position = ftransform(); } """), FragmentShader(""" uniform float colorMap[9]; varying vec4 pos; //out vec4 gl_FragColor; // only needed for later glsl versions //in vec4 gl_Color; void main() { vec4 color = gl_Color; color.x = colorMap[0] * (pos.z + colorMap[1]); if (colorMap[2] != 1.0) color.x = pow(color.x, colorMap[2]); color.x = color.x < 0. ? 0. : (color.x > 1. ? 1. : color.x); color.y = colorMap[3] * (pos.z + colorMap[4]); if (colorMap[5] != 1.0) color.y = pow(color.y, colorMap[5]); color.y = color.y < 0. ? 0. : (color.y > 1. ? 1. : color.y); color.z = colorMap[6] * (pos.z + colorMap[7]); if (colorMap[8] != 1.0) color.z = pow(color.z, colorMap[8]); color.z = color.z < 0. ? 0. : (color.z > 1. ? 1. : color.z); color.w = 1.0; gl_FragColor = color; } """), ], uniforms={'colorMap': [1, 1, 1, 1, 0.5, 1, 1, 0, 1]}), ShaderProgram('pointSprite', [ ## allows specifying point size using normal.x ## See: ## ## http://stackoverflow.com/questions/9609423/applying-part-of-a-texture-sprite-sheet-texture-map-to-a-point-sprite-in-ios ## http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 ## ## VertexShader(""" void main() { gl_FrontColor=gl_Color; gl_PointSize = gl_Normal.x; gl_Position = ftransform(); } """), #FragmentShader(""" ##version 120 #uniform sampler2D texture; #void main ( ) #{ #gl_FragColor = texture2D(texture, gl_PointCoord) * gl_Color; #} #""") ]), ] CompiledShaderPrograms = {} def getShaderProgram(name): return ShaderProgram.names[name] class Shader(object): def __init__(self, shaderType, code): self.shaderType = shaderType self.code = code self.compiled = None def shader(self): if self.compiled is None: try: self.compiled = shaders.compileShader(self.code, self.shaderType) except NullFunctionError: raise Exception("This OpenGL implementation does not support shader programs; many OpenGL features in pyqtgraph will not work.") except RuntimeError as exc: ## Format compile errors a bit more nicely if len(exc.args) == 3: err, code, typ = exc.args if not err.startswith('Shader compile failure'): raise code = code[0].decode('utf_8').split('\n') err, c, msgs = err.partition(':') err = err + '\n' msgs = re.sub('b\'','',msgs) msgs = re.sub('\'$','',msgs) msgs = re.sub('\\\\n','\n',msgs) msgs = msgs.split('\n') errNums = [()] * len(code) for i, msg in enumerate(msgs): msg = msg.strip() if msg == '': continue m = re.match(r'(\d+\:)?\d+\((\d+)\)', msg) if m is not None: line = int(m.groups()[1]) errNums[line-1] = errNums[line-1] + (str(i+1),) #code[line-1] = '%d\t%s' % (i+1, code[line-1]) err = err + "%d %s\n" % (i+1, msg) errNums = [','.join(n) for n in errNums] maxlen = max(map(len, errNums)) code = [errNums[i] + " "*(maxlen-len(errNums[i])) + line for i, line in enumerate(code)] err = err + '\n'.join(code) raise Exception(err) else: raise return self.compiled class VertexShader(Shader): def __init__(self, code): Shader.__init__(self, GL_VERTEX_SHADER, code) class FragmentShader(Shader): def __init__(self, code): Shader.__init__(self, GL_FRAGMENT_SHADER, code) class ShaderProgram(object): names = {} def __init__(self, name, shaders, uniforms=None): self.name = name ShaderProgram.names[name] = self self.shaders = shaders self.prog = None self.blockData = {} self.uniformData = {} ## parse extra options from the shader definition if uniforms is not None: for k,v in uniforms.items(): self[k] = v def setBlockData(self, blockName, data): if data is None: del self.blockData[blockName] else: self.blockData[blockName] = data def setUniformData(self, uniformName, data): if data is None: del self.uniformData[uniformName] else: self.uniformData[uniformName] = data def __setitem__(self, item, val): self.setUniformData(item, val) def __delitem__(self, item): self.setUniformData(item, None) def program(self): if self.prog is None: try: compiled = [s.shader() for s in self.shaders] ## compile all shaders self.prog = shaders.compileProgram(*compiled) ## compile program except: self.prog = -1 raise return self.prog def __enter__(self): if len(self.shaders) > 0 and self.program() != -1: glUseProgram(self.program()) try: ## load uniform values into program for uniformName, data in self.uniformData.items(): loc = self.uniform(uniformName) if loc == -1: raise Exception('Could not find uniform variable "%s"' % uniformName) glUniform1fv(loc, len(data), np.array(data, dtype=np.float32)) ### bind buffer data to program blocks #if len(self.blockData) > 0: #bindPoint = 1 #for blockName, data in self.blockData.items(): ### Program should have a uniform block declared: ### ### layout (std140) uniform blockName { ### vec4 diffuse; ### }; ### pick any-old binding point. (there are a limited number of these per-program #bindPoint = 1 ### get the block index for a uniform variable in the shader #blockIndex = glGetUniformBlockIndex(self.program(), blockName) ### give the shader block a binding point #glUniformBlockBinding(self.program(), blockIndex, bindPoint) ### create a buffer #buf = glGenBuffers(1) #glBindBuffer(GL_UNIFORM_BUFFER, buf) #glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW) ### also possible to use glBufferSubData to fill parts of the buffer ### bind buffer to the same binding point #glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf) except: glUseProgram(0) raise def __exit__(self, *args): if len(self.shaders) > 0: glUseProgram(0) def uniform(self, name): """Return the location integer for a uniform variable in this program""" return glGetUniformLocation(self.program(), name.encode('utf_8')) #def uniformBlockInfo(self, blockName): #blockIndex = glGetUniformBlockIndex(self.program(), blockName) #count = glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS) #indices = [] #for i in range(count): #indices.append(glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES)) class HeightColorShader(ShaderProgram): def __enter__(self): ## Program should have a uniform block declared: ## ## layout (std140) uniform blockName { ## vec4 diffuse; ## vec4 ambient; ## }; ## pick any-old binding point. (there are a limited number of these per-program bindPoint = 1 ## get the block index for a uniform variable in the shader blockIndex = glGetUniformBlockIndex(self.program(), "blockName") ## give the shader block a binding point glUniformBlockBinding(self.program(), blockIndex, bindPoint) ## create a buffer buf = glGenBuffers(1) glBindBuffer(GL_UNIFORM_BUFFER, buf) glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW) ## also possible to use glBufferSubData to fill parts of the buffer ## bind buffer to the same binding point glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf) initShaders() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/ordereddict.py000066400000000000000000000005531421045507400225200ustar00rootroot00000000000000import collections import warnings class OrderedDict(collections.OrderedDict): def __init__(self, *args, **kwds): warnings.warn( "OrderedDict is in the standard library for supported versions of Python. Will be removed in 0.13", DeprecationWarning, stacklevel=2, ) super().__init__(*args, **kwds) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/000077500000000000000000000000001421045507400225135ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/Parameter.py000066400000000000000000001053001421045507400250040ustar00rootroot00000000000000import re import warnings import weakref from collections import OrderedDict from .. import functions as fn from ..Qt import QtCore from .ParameterItem import ParameterItem PARAM_TYPES = {} PARAM_NAMES = {} _PARAM_ITEM_TYPES = {} def registerParameterItemType(name, itemCls, parameterCls=None, override=False): """ Similar to :func:`registerParameterType`, but works on ParameterItems. This is useful for Parameters where the `itemClass` does all the heavy lifting, and a redundant Parameter class must be defined just to house `itemClass`. Instead, use `registerParameterItemType`. If this should belong to a subclass of `Parameter`, specify which one in `parameterCls`. """ global _PARAM_ITEM_TYPES if name in _PARAM_ITEM_TYPES and not override: raise Exception("Parameter item type '%s' already exists (use override=True to replace)" % name) parameterCls = parameterCls or Parameter _PARAM_ITEM_TYPES[name] = itemCls registerParameterType(name, parameterCls, override) def registerParameterType(name, cls, override=False): """Register a parameter type in the parametertree system. This enables construction of custom Parameter classes by name in :meth:`~pyqtgraph.parametertree.Parameter.create`. """ global PARAM_TYPES if name in PARAM_TYPES and not override: raise Exception("Parameter type '%s' already exists (use override=True to replace)" % name) PARAM_TYPES[name] = cls PARAM_NAMES[cls] = name def __reload__(old): PARAM_TYPES.update(old.get('PARAM_TYPES', {})) PARAM_NAMES.update(old.get('PARAM_NAMES', {})) class Parameter(QtCore.QObject): """ A Parameter is the basic unit of data in a parameter tree. Each parameter has a name, a type, a value, and several other properties that modify the behavior of the Parameter. Parameters may have parent / child / sibling relationships to construct organized hierarchies. Parameters generally do not have any inherent GUI or visual interpretation; instead they manage ParameterItem instances which take care of display and user interaction. Note: It is fairly uncommon to use the Parameter class directly; mostly you will use subclasses which provide specialized type and data handling. The static pethod Parameter.create(...) is an easy way to generate instances of these subclasses. For more Parameter types, see ParameterTree.parameterTypes module. =================================== ========================================================= **Signals:** sigStateChanged(self, change, info) Emitted when anything changes about this parameter at all. The second argument is a string indicating what changed ('value', 'childAdded', etc..) The third argument can be any extra information about the change sigTreeStateChanged(self, changes) Emitted when any child in the tree changes state (but only if monitorChildren() is called) the format of *changes* is [(param, change, info), ...] sigValueChanged(self, value) Emitted when value is finished changing sigValueChanging(self, value) Emitted immediately for all value changes, including during editing. sigChildAdded(self, child, index) Emitted when a child is added sigChildRemoved(self, child) Emitted when a child is removed sigRemoved(self) Emitted when this parameter is removed sigParentChanged(self, parent) Emitted when this parameter's parent has changed sigLimitsChanged(self, limits) Emitted when this parameter's limits have changed sigDefaultChanged(self, default) Emitted when this parameter's default value has changed sigNameChanged(self, name) Emitted when this parameter's name has changed sigOptionsChanged(self, opts) Emitted when any of this parameter's options have changed sigContextMenu(self, name) Emitted when a context menu was clicked =================================== ========================================================= """ ## name, type, limits, etc. ## can also carry UI hints (slider vs spinbox, etc.) itemClass = None sigValueChanged = QtCore.Signal(object, object) ## self, value emitted when value is finished being edited sigValueChanging = QtCore.Signal(object, object) ## self, value emitted as value is being edited sigChildAdded = QtCore.Signal(object, object, object) ## self, child, index sigChildRemoved = QtCore.Signal(object, object) ## self, child sigRemoved = QtCore.Signal(object) ## self sigParentChanged = QtCore.Signal(object, object) ## self, parent sigLimitsChanged = QtCore.Signal(object, object) ## self, limits sigDefaultChanged = QtCore.Signal(object, object) ## self, default sigNameChanged = QtCore.Signal(object, object) ## self, name sigOptionsChanged = QtCore.Signal(object, object) ## self, {opt:val, ...} ## Emitted when anything changes about this parameter at all. ## The second argument is a string indicating what changed ('value', 'childAdded', etc..) ## The third argument can be any extra information about the change sigStateChanged = QtCore.Signal(object, object, object) ## self, change, info ## emitted when any child in the tree changes state ## (but only if monitorChildren() is called) sigTreeStateChanged = QtCore.Signal(object, object) # self, changes # changes = [(param, change, info), ...] sigContextMenu = QtCore.Signal(object, object) # self, name # bad planning. #def __new__(cls, *args, **opts): #try: #cls = PARAM_TYPES[opts['type']] #except KeyError: #pass #return QtCore.QObject.__new__(cls, *args, **opts) @staticmethod def create(**opts): """ Static method that creates a new Parameter (or subclass) instance using opts['type'] to select the appropriate class. All options are passed directly to the new Parameter's __init__ method. Use registerParameterType() to add new class types. """ typ = opts.get('type', None) if typ is None: cls = Parameter else: cls = PARAM_TYPES[opts['type']] return cls(**opts) def __init__(self, **opts): """ Initialize a Parameter object. Although it is rare to directly create a Parameter instance, the options available to this method are also allowed by most Parameter subclasses. ======================= ========================================================= **Keyword Arguments:** name The name to give this Parameter. This is the name that will appear in the left-most column of a ParameterTree for this Parameter. value The value to initially assign to this Parameter. default The default value for this Parameter (most Parameters provide an option to 'reset to default'). children A list of children for this Parameter. Children may be given either as a Parameter instance or as a dictionary to pass to Parameter.create(). In this way, it is possible to specify complex hierarchies of Parameters from a single nested data structure. readonly If True, the user will not be allowed to edit this Parameter. (default=False) enabled If False, any widget(s) for this parameter will appear disabled. (default=True) visible If False, the Parameter will not appear when displayed in a ParameterTree. (default=True) renamable If True, the user may rename this Parameter. (default=False) removable If True, the user may remove this Parameter. (default=False) expanded If True, the Parameter will initially be expanded in ParameterTrees: Its children will be visible. (default=True) syncExpanded If True, the `expanded` state of this Parameter is synchronized with all ParameterTrees it is displayed in. (default=False) title (str or None) If specified, then the parameter will be displayed to the user using this string as its name. However, the parameter will still be referred to internally using the *name* specified above. Note that this option is not compatible with renamable=True. (default=None; added in version 0.9.9) ======================= ========================================================= """ QtCore.QObject.__init__(self) self.opts = { 'type': None, 'readonly': False, 'visible': True, 'enabled': True, 'renamable': False, 'removable': False, 'strictNaming': False, # forces name to be usable as a python variable 'expanded': True, 'syncExpanded': False, 'title': None, #'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits. } value = opts.get('value', None) name = opts.get('name', None) self.opts.update(opts) self.opts['value'] = None # will be set later. self.opts['name'] = None self.childs = [] self.names = {} ## map name:child self.items = weakref.WeakKeyDictionary() ## keeps track of tree items representing this parameter self._parent = None self.treeStateChanges = [] ## cache of tree state changes to be delivered on next emit self.blockTreeChangeEmit = 0 #self.monitoringChildren = False ## prevent calling monitorChildren more than once if not isinstance(name, str): raise Exception("Parameter must have a string name specified in opts.") self.setName(name) self.addChildren(self.opts.pop('children', [])) if value is not None: self.setValue(value) if 'default' not in self.opts: self.opts['default'] = None self.setDefault(self.opts['value']) ## Connect all state changed signals to the general sigStateChanged self.sigValueChanged.connect(self._emitValueChanged) self.sigChildAdded.connect(self._emitChildAddedChanged) self.sigChildRemoved.connect(self._emitChildRemovedChanged) self.sigParentChanged.connect(self._emitParentChanged) self.sigLimitsChanged.connect(self._emitLimitsChanged) self.sigDefaultChanged.connect(self._emitDefaultChanged) self.sigNameChanged.connect(self._emitNameChanged) self.sigOptionsChanged.connect(self._emitOptionsChanged) self.sigContextMenu.connect(self._emitContextMenuChanged) #self.watchParam(self) ## emit treechange signals if our own state changes def name(self): """Return the name of this Parameter.""" return self.opts['name'] def title(self): """Return the title of this Parameter. By default, the title is the same as the name unless it has been explicitly specified otherwise.""" title = self.opts.get('title', None) if title is None: title = self.name() return title def contextMenu(self, name): """"A context menu entry was clicked""" self.sigContextMenu.emit(self, name) def setName(self, name): """Attempt to change the name of this parameter; return the actual name. (The parameter may reject the name change or automatically pick a different name)""" if self.opts['strictNaming']: if len(name) < 1 or re.search(r'\W', name) or re.match(r'\d', name[0]): raise Exception("Parameter name '%s' is invalid. (Must contain only alphanumeric and underscore characters and may not start with a number)" % name) parent = self.parent() if parent is not None: name = parent._renameChild(self, name) ## first ask parent if it's ok to rename if self.opts['name'] != name: self.opts['name'] = name self.sigNameChanged.emit(self, name) return name def type(self): """Return the type string for this Parameter.""" return self.opts['type'] def isType(self, typ): """ Return True if this parameter type matches the name *typ*. This can occur either of two ways: - If self.type() == *typ* - If this parameter's class is registered with the name *typ* """ if self.type() == typ: return True global PARAM_TYPES cls = PARAM_TYPES.get(typ, None) if cls is None: raise Exception("Type name '%s' is not registered." % str(typ)) return self.__class__ is cls def childPath(self, child): """ Return the path of parameter names from self to child. If child is not a (grand)child of self, return None. """ path = [] while child is not self: path.insert(0, child.name()) child = child.parent() if child is None: return None return path def setValue(self, value, blockSignal=None): """ Set the value of this Parameter; return the actual value that was set. (this may be different from the value that was requested) """ try: if blockSignal is not None: self.sigValueChanged.disconnect(blockSignal) value = self._interpretValue(value) if fn.eq(self.opts['value'], value): return value self.opts['value'] = value self.sigValueChanged.emit(self, value) # value might change after signal is received by tree item finally: if blockSignal is not None: self.sigValueChanged.connect(blockSignal) return self.opts['value'] def _interpretValue(self, v): return v def value(self): """ Return the value of this Parameter. """ return self.opts['value'] def getValues(self): """Return a tree of all values that are children of this parameter""" vals = OrderedDict() for ch in self: vals[ch.name()] = (ch.value(), ch.getValues()) return vals def saveState(self, filter=None): """ Return a structure representing the entire state of the parameter tree. The tree state may be restored from this structure using restoreState(). If *filter* is set to 'user', then only user-settable data will be included in the returned state. """ if filter is None: state = self.opts.copy() if state['type'] is None: global PARAM_NAMES state['type'] = PARAM_NAMES.get(type(self), None) elif filter == 'user': state = {'value': self.value()} else: raise ValueError("Unrecognized filter argument: '%s'" % filter) ch = OrderedDict([(ch.name(), ch.saveState(filter=filter)) for ch in self]) if len(ch) > 0: state['children'] = ch return state def restoreState(self, state, recursive=True, addChildren=True, removeChildren=True, blockSignals=True): """ Restore the state of this parameter and its children from a structure generated using saveState() If recursive is True, then attempt to restore the state of child parameters as well. If addChildren is True, then any children which are referenced in the state object will be created if they do not already exist. If removeChildren is True, then any children which are not referenced in the state object will be removed. If blockSignals is True, no signals will be emitted until the tree has been completely restored. This prevents signal handlers from responding to a partially-rebuilt network. """ state = state.copy() childState = state.pop('children', []) ## list of children may be stored either as list or dict. if isinstance(childState, dict): cs = [] for k,v in childState.items(): cs.append(v.copy()) cs[-1].setdefault('name', k) childState = cs if blockSignals: self.blockTreeChangeSignal() try: self.setOpts(**state) if not recursive: return ptr = 0 ## pointer to first child that has not been restored yet foundChilds = set() #print "==============", self.name() for ch in childState: name = ch['name'] #typ = ch.get('type', None) #print('child: %s, %s' % (self.name()+'.'+name, typ)) ## First, see if there is already a child with this name gotChild = False for i, ch2 in enumerate(self.childs[ptr:]): #print " ", ch2.name(), ch2.type() if ch2.name() != name: # or not ch2.isType(typ): continue gotChild = True #print " found it" if i != 0: ## move parameter to next position #self.removeChild(ch2) self.insertChild(ptr, ch2) #print " moved to position", ptr ch2.restoreState(ch, recursive=recursive, addChildren=addChildren, removeChildren=removeChildren) foundChilds.add(ch2) break if not gotChild: if not addChildren: #print " ignored child" continue #print " created new" ch2 = Parameter.create(**ch) self.insertChild(ptr, ch2) foundChilds.add(ch2) ptr += 1 if removeChildren: for ch in self.childs[:]: if ch not in foundChilds: #print " remove:", ch self.removeChild(ch) finally: if blockSignals: self.unblockTreeChangeSignal() def defaultValue(self): """Return the default value for this parameter.""" return self.opts['default'] def setDefault(self, val): """Set the default value for this parameter.""" if self.opts['default'] == val: return self.opts['default'] = val self.sigDefaultChanged.emit(self, val) def setToDefault(self): """Set this parameter's value to the default.""" if self.hasDefault(): self.setValue(self.defaultValue()) def hasDefault(self): """Returns True if this parameter has a default value.""" return self.opts['default'] is not None def valueIsDefault(self): """Returns True if this parameter's value is equal to the default value.""" return fn.eq(self.value(), self.defaultValue()) def setLimits(self, limits): """Set limits on the acceptable values for this parameter. The format of limits depends on the type of the parameter and some parameters do not make use of limits at all.""" if 'limits' in self.opts and fn.eq(self.opts['limits'], limits): return self.opts['limits'] = limits self.sigLimitsChanged.emit(self, limits) return limits def writable(self): """ Returns True if this parameter's value can be changed by the user. Note that the value of the parameter can *always* be changed by calling setValue(). """ return not self.readonly() def setWritable(self, writable=True): """Set whether this Parameter should be editable by the user. (This is exactly the opposite of setReadonly).""" self.setOpts(readonly=not writable) def readonly(self): """ Return True if this parameter is read-only. (this is the opposite of writable()) """ return self.opts.get('readonly', False) def setReadonly(self, readonly=True): """Set whether this Parameter's value may be edited by the user (this is the opposite of setWritable()).""" self.setOpts(readonly=readonly) def setOpts(self, **opts): """ Set any arbitrary options on this parameter. The exact behavior of this function will depend on the parameter type, but most parameters will accept a common set of options: value, name, limits, default, readonly, removable, renamable, visible, enabled, expanded and syncExpanded. See :func:`Parameter.__init__ ` for more information on default options. """ changed = OrderedDict() for k in opts: if k == 'value': self.setValue(opts[k]) elif k == 'name': self.setName(opts[k]) elif k == 'limits': self.setLimits(opts[k]) elif k == 'default': self.setDefault(opts[k]) elif k not in self.opts or not fn.eq(self.opts[k], opts[k]): self.opts[k] = opts[k] changed[k] = opts[k] if len(changed) > 0: self.sigOptionsChanged.emit(self, changed) def emitStateChanged(self, changeDesc, data): ## Emits stateChanged signal and ## requests emission of new treeStateChanged signal self.sigStateChanged.emit(self, changeDesc, data) #self.treeStateChanged(self, changeDesc, data) self.treeStateChanges.append((self, changeDesc, data)) self.emitTreeChanges() def _emitValueChanged(self, param, data): self.emitStateChanged("value", data) def _emitChildAddedChanged(self, param, *data): self.emitStateChanged("childAdded", data) def _emitChildRemovedChanged(self, param, data): self.emitStateChanged("childRemoved", data) def _emitParentChanged(self, param, data): self.emitStateChanged("parent", data) def _emitLimitsChanged(self, param, data): self.emitStateChanged("limits", data) def _emitDefaultChanged(self, param, data): self.emitStateChanged("default", data) def _emitNameChanged(self, param, data): self.emitStateChanged("name", data) def _emitOptionsChanged(self, param, data): self.emitStateChanged("options", data) def _emitContextMenuChanged(self, param, data): self.emitStateChanged("contextMenu", data) def makeTreeItem(self, depth): """ Return a TreeWidgetItem suitable for displaying/controlling the content of this parameter. This is called automatically when a ParameterTree attempts to display this Parameter. Most subclasses will want to override this function. """ # Default to user-specified itemClass. If not present, check for a registered item class. Finally, # revert to ParameterItem if both fail itemClass = self.itemClass or _PARAM_ITEM_TYPES.get(self.opts['type'], ParameterItem) return itemClass(self, depth) def addChild(self, child, autoIncrementName=None): """ Add another parameter to the end of this parameter's child list. See insertChild() for a description of the *autoIncrementName* argument. """ return self.insertChild(len(self.childs), child, autoIncrementName=autoIncrementName) def addChildren(self, children): """ Add a list or dict of children to this parameter. This method calls addChild once for each value in *children*. """ ## If children was specified as dict, then assume keys are the names. if isinstance(children, dict): ch2 = [] for name, opts in children.items(): if isinstance(opts, dict) and 'name' not in opts: opts = opts.copy() opts['name'] = name ch2.append(opts) children = ch2 for chOpts in children: #print self, "Add child:", type(chOpts), id(chOpts) self.addChild(chOpts) def insertChild(self, pos, child, autoIncrementName=None): """ Insert a new child at pos. If pos is a Parameter, then insert at the position of that Parameter. If child is a dict, then a parameter is constructed using :func:`Parameter.create `. By default, the child's 'autoIncrementName' option determines whether the name will be adjusted to avoid prior name collisions. This behavior may be overridden by specifying the *autoIncrementName* argument. This argument was added in version 0.9.9. """ if isinstance(child, dict): child = Parameter.create(**child) name = child.name() if name in self.names and child is not self.names[name]: if autoIncrementName is True or (autoIncrementName is None and child.opts.get('autoIncrementName', False)): name = self.incrementName(name) child.setName(name) else: raise Exception("Already have child named %s" % str(name)) if isinstance(pos, Parameter): pos = self.childs.index(pos) with self.treeChangeBlocker(): if child.parent() is not None: child.remove() self.names[name] = child self.childs.insert(pos, child) child.parentChanged(self) child.sigTreeStateChanged.connect(self.treeStateChanged) self.sigChildAdded.emit(self, child, pos) return child def removeChild(self, child): """Remove a child parameter.""" name = child.name() if name not in self.names or self.names[name] is not child: raise Exception("Parameter %s is not my child; can't remove." % str(child)) del self.names[name] self.childs.pop(self.childs.index(child)) child.parentChanged(None) try: child.sigTreeStateChanged.disconnect(self.treeStateChanged) except (TypeError, RuntimeError): ## already disconnected pass self.sigChildRemoved.emit(self, child) def clearChildren(self): """Remove all child parameters.""" for ch in self.childs[:]: self.removeChild(ch) def children(self): """Return a list of this parameter's children. Warning: this overrides QObject.children """ return self.childs[:] def hasChildren(self): """Return True if this Parameter has children.""" return len(self.childs) > 0 def parentChanged(self, parent): """This method is called when the parameter's parent has changed. It may be useful to extend this method in subclasses.""" self._parent = parent self.sigParentChanged.emit(self, parent) def parent(self): """Return the parent of this parameter.""" return self._parent def remove(self): """Remove this parameter from its parent's child list""" parent = self.parent() if parent is None: raise Exception("Cannot remove; no parent.") parent.removeChild(self) self.sigRemoved.emit(self) def incrementName(self, name): ## return an unused name by adding a number to the name given base, num = re.match(r'(.*)(\d*)', name).groups() numLen = len(num) if numLen == 0: num = 2 numLen = 1 else: num = int(num) while True: newName = base + ("%%0%dd"%numLen) % num if newName not in self.names: return newName num += 1 def __iter__(self): for ch in self.childs: yield ch def __getitem__(self, names): """Get the value of a child parameter. The name may also be a tuple giving the path to a sub-parameter:: value = param[('child', 'grandchild')] """ if not isinstance(names, tuple): names = (names,) return self.param(*names).value() def __setitem__(self, names, value): """Set the value of a child parameter. The name may also be a tuple giving the path to a sub-parameter:: param[('child', 'grandchild')] = value """ if isinstance(names, str): names = (names,) return self.param(*names).setValue(value) def keys(self): return self.names def child(self, *names): """Return a child parameter. Accepts the name of the child or a tuple (path, to, child) Added in version 0.9.9. Earlier versions used the 'param' method, which is still implemented for backward compatibility. """ try: param = self.names[names[0]] except KeyError: raise KeyError("Parameter %s has no child named %s" % (self.name(), names[0])) if len(names) > 1: return param.child(*names[1:]) else: return param def param(self, *names): # for backward compatibility. return self.child(*names) def __repr__(self): return "<%s '%s' at 0x%x>" % (self.__class__.__name__, self.name(), id(self)) def __getattr__(self, attr): ## Leaving this undocumented because I might like to remove it in the future.. #print type(self), attr warnings.warn( 'Use of Parameter.subParam is deprecated and will be removed in 0.13 ' 'Use Parameter.param(name) instead.', DeprecationWarning, stacklevel=2 ) if 'names' not in self.__dict__: raise AttributeError(attr) if attr in self.names: import traceback traceback.print_stack() print("Warning: Use of Parameter.subParam is deprecated. Use Parameter.param(name) instead.") return self.param(attr) else: raise AttributeError(attr) def _renameChild(self, child, name): ## Only to be called from Parameter.rename if name in self.names: return child.name() self.names[name] = child del self.names[child.name()] return name def registerItem(self, item): self.items[item] = None def hide(self): """Hide this parameter. It and its children will no longer be visible in any ParameterTree widgets it is connected to.""" self.show(False) def show(self, s=True): """Show this parameter. """ self.opts['visible'] = s self.sigOptionsChanged.emit(self, {'visible': s}) def treeChangeBlocker(self): """ Return an object that can be used to temporarily block and accumulate sigTreeStateChanged signals. This is meant to be used when numerous changes are about to be made to the tree and only one change signal should be emitted at the end. Example:: with param.treeChangeBlocker(): param.addChild(...) param.removeChild(...) param.setValue(...) """ return SignalBlocker(self.blockTreeChangeSignal, self.unblockTreeChangeSignal) def blockTreeChangeSignal(self): """ Used to temporarily block and accumulate tree change signals. *You must remember to unblock*, so it is advisable to use treeChangeBlocker() instead. """ self.blockTreeChangeEmit += 1 def unblockTreeChangeSignal(self): """Unblocks enission of sigTreeStateChanged and flushes the changes out through a single signal.""" self.blockTreeChangeEmit -= 1 self.emitTreeChanges() def treeStateChanged(self, param, changes): """ Called when the state of any sub-parameter has changed. ============== ================================================================ **Arguments:** param The immediate child whose tree state has changed. note that the change may have originated from a grandchild. changes List of tuples describing all changes that have been made in this event: (param, changeDescr, data) ============== ================================================================ This function can be extended to react to tree state changes. """ self.treeStateChanges.extend(changes) self.emitTreeChanges() def emitTreeChanges(self): if self.blockTreeChangeEmit == 0: changes = self.treeStateChanges self.treeStateChanges = [] if len(changes) > 0: self.sigTreeStateChanged.emit(self, changes) class SignalBlocker(object): def __init__(self, enterFn, exitFn): self.enterFn = enterFn self.exitFn = exitFn def __enter__(self): self.enterFn() def __exit__(self, exc_type, exc_value, tb): self.exitFn() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/ParameterItem.py000066400000000000000000000202731421045507400256300ustar00rootroot00000000000000from ..Qt import QtCore, QtGui, QtWidgets translate = QtCore.QCoreApplication.translate class ParameterItem(QtWidgets.QTreeWidgetItem): """ Abstract ParameterTree item. Used to represent the state of a Parameter from within a ParameterTree. - Sets first column of item to name - generates context menu if item is renamable or removable - handles child added / removed events - provides virtual functions for handling changes from parameter For more ParameterItem types, see ParameterTree.parameterTypes module. """ def __init__(self, param, depth=0): QtWidgets.QTreeWidgetItem.__init__(self, [param.title(), '']) self.param = param self.param.registerItem(self) ## let parameter know this item is connected to it (for debugging) self.depth = depth param.sigValueChanged.connect(self.valueChanged) param.sigChildAdded.connect(self.childAdded) param.sigChildRemoved.connect(self.childRemoved) param.sigNameChanged.connect(self.nameChanged) param.sigLimitsChanged.connect(self.limitsChanged) param.sigDefaultChanged.connect(self.defaultChanged) param.sigOptionsChanged.connect(self.optsChanged) param.sigParentChanged.connect(self.parentChanged) self.updateFlags() ## flag used internally during name editing self.ignoreNameColumnChange = False def updateFlags(self): ## called when Parameter opts changed opts = self.param.opts flags = QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled if opts.get('renamable', False): if opts.get('title', None) is not None: raise Exception("Cannot make parameter with both title != None and renamable == True.") flags |= QtCore.Qt.ItemFlag.ItemIsEditable ## handle movable / dropEnabled options if opts.get('movable', False): flags |= QtCore.Qt.ItemFlag.ItemIsDragEnabled if opts.get('dropEnabled', False): flags |= QtCore.Qt.ItemFlag.ItemIsDropEnabled self.setFlags(flags) def valueChanged(self, param, val): ## called when the parameter's value has changed pass def isFocusable(self): """Return True if this item should be included in the tab-focus order""" return False def setFocus(self): """Give input focus to this item. Can be reimplemented to display editor widgets, etc. """ pass def focusNext(self, forward=True): """Give focus to the next (or previous) focusable item in the parameter tree""" self.treeWidget().focusNext(self, forward=forward) def treeWidgetChanged(self): """Called when this item is added or removed from a tree. Expansion, visibility, and column widgets must all be configured AFTER the item is added to a tree, not during __init__. """ self.setHidden(not self.param.opts.get('visible', True)) self.setExpanded(self.param.opts.get('expanded', True)) def childAdded(self, param, child, pos): item = child.makeTreeItem(depth=self.depth+1) self.insertChild(pos, item) item.treeWidgetChanged() for i, ch in enumerate(child): item.childAdded(child, ch, i) def childRemoved(self, param, child): for i in range(self.childCount()): item = self.child(i) if item.param is child: self.takeChild(i) break def parentChanged(self, param, parent): ## called when the parameter's parent has changed. pass def contextMenuEvent(self, ev): opts = self.param.opts if not opts.get('removable', False) and not opts.get('renamable', False)\ and "context" not in opts: return ## Generate context menu for renaming/removing parameter self.contextMenu = QtWidgets.QMenu() # Put in global name space to prevent garbage collection self.contextMenu.addSeparator() if opts.get('renamable', False): self.contextMenu.addAction(translate("ParameterItem", 'Rename')).triggered.connect(self.editName) if opts.get('removable', False): self.contextMenu.addAction(translate("ParameterItem", "Remove")).triggered.connect(self.requestRemove) # context menu context = opts.get('context', None) if isinstance(context, list): for name in context: self.contextMenu.addAction(name).triggered.connect( self.contextMenuTriggered(name)) elif isinstance(context, dict): for name, title in context.items(): self.contextMenu.addAction(title).triggered.connect( self.contextMenuTriggered(name)) self.contextMenu.popup(ev.globalPos()) def columnChangedEvent(self, col): """Called when the text in a column has been edited (or otherwise changed). By default, we only use changes to column 0 to rename the parameter. """ if col == 0 and (self.param.opts.get('title', None) is None): if self.ignoreNameColumnChange: return try: newName = self.param.setName(self.text(col)) except Exception: self.setText(0, self.param.name()) raise try: self.ignoreNameColumnChange = True self.nameChanged(self, newName) ## If the parameter rejects the name change, we need to set it back. finally: self.ignoreNameColumnChange = False def expandedChangedEvent(self, expanded): if self.param.opts['syncExpanded']: self.param.setOpts(expanded=expanded) def nameChanged(self, param, name): ## called when the parameter's name has changed. if self.param.opts.get('title', None) is None: self.titleChanged() def titleChanged(self): # called when the user-visble title has changed (either opts['title'], or name if title is None) title = self.param.title() # This makes sure that items without a title or the title 'params' remain invisible if not title or title == 'params': return self.setText(0, title) fm = QtGui.QFontMetrics(self.font(0)) textFlags = QtCore.Qt.TextFlag.TextSingleLine size = fm.size(textFlags, self.text(0)) size.setHeight(int(size.height() * 1.35)) size.setWidth(int(size.width() * 1.15)) self.setSizeHint(0, size) def limitsChanged(self, param, limits): """Called when the parameter's limits have changed""" pass def defaultChanged(self, param, default): """Called when the parameter's default value has changed""" pass def optsChanged(self, param, opts): """Called when any options are changed that are not name, value, default, or limits""" if 'visible' in opts: self.setHidden(not opts['visible']) if 'expanded' in opts: if self.isExpanded() != opts['expanded']: self.setExpanded(opts['expanded']) if 'title' in opts: self.titleChanged() self.updateFlags() def contextMenuTriggered(self, name): def trigger(): self.param.contextMenu(name) return trigger def editName(self): self.treeWidget().editItem(self, 0) def selected(self, sel): """Called when this item has been selected (sel=True) OR deselected (sel=False)""" pass def requestRemove(self): ## called when remove is selected from the context menu. ## we need to delay removal until the action is complete ## since destroying the menu in mid-action will cause a crash. QtCore.QTimer.singleShot(0, self.param.remove) ## for python 3 support, we need to redefine hash and eq methods. def __hash__(self): return id(self) def __eq__(self, x): return x is self pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/ParameterSystem.py000066400000000000000000000112141421045507400262110ustar00rootroot00000000000000__all__ = ["ParameterSystem", "SystemSolver"] from .. import functions as fn from .parameterTypes import GroupParameter from .SystemSolver import SystemSolver class ParameterSystem(GroupParameter): """ ParameterSystem is a subclass of GroupParameter that manages a tree of sub-parameters with a set of interdependencies--changing any one parameter may affect other parameters in the system. See parametertree/SystemSolver for more information. NOTE: This API is experimental and may change substantially across minor version numbers. """ def __init__(self, *args, **kwds): GroupParameter.__init__(self, *args, **kwds) self._system = None self._fixParams = [] # all auto-generated 'fixed' params sys = kwds.pop('system', None) if sys is not None: self.setSystem(sys) self._ignoreChange = [] # params whose changes should be ignored temporarily self.sigTreeStateChanged.connect(self.updateSystem) def setSystem(self, sys): self._system = sys # auto-generate defaults to match child parameters defaults = {} vals = {} for param in self: name = param.name() constraints = '' if hasattr(sys, '_' + name): constraints += 'n' if not param.readonly(): constraints += 'f' if 'n' in constraints: ch = param.addChild(dict(name='fixed', type='bool', value=False)) self._fixParams.append(ch) param.setReadonly(True) param.setOpts(expanded=False) else: vals[name] = param.value() ch = param.addChild(dict(name='fixed', type='bool', value=True, readonly=True)) #self._fixParams.append(ch) defaults[name] = [None, param.type(), None, constraints] sys.defaultState.update(defaults) sys.reset() for name, value in vals.items(): setattr(sys, name, value) self.updateAllParams() def updateSystem(self, param, changes): changes = [ch for ch in changes if ch[0] not in self._ignoreChange] #resets = [ch[0] for ch in changes if ch[1] == 'setToDefault'] sets = [ch[0] for ch in changes if ch[1] == 'value'] #for param in resets: #setattr(self._system, param.name(), None) for param in sets: #if param in resets: #continue #if param in self._fixParams: #param.parent().setWritable(param.value()) #else: if param in self._fixParams: parent = param.parent() if param.value(): setattr(self._system, parent.name(), parent.value()) else: setattr(self._system, parent.name(), None) else: setattr(self._system, param.name(), param.value()) self.updateAllParams() def updateAllParams(self): try: self.sigTreeStateChanged.disconnect(self.updateSystem) for name, state in self._system._vars.items(): param = self.child(name) try: v = getattr(self._system, name) if self._system._vars[name][2] is None: self.updateParamState(self.child(name), 'autoSet') param.setValue(v) else: self.updateParamState(self.child(name), 'fixed') except RuntimeError: self.updateParamState(param, 'autoUnset') finally: self.sigTreeStateChanged.connect(self.updateSystem) def updateParamState(self, param, state): if state == 'autoSet': bg = fn.mkBrush((200, 255, 200, 255)) bold = False readonly = True elif state == 'autoUnset': bg = fn.mkBrush(None) bold = False readonly = False elif state == 'fixed': bg = fn.mkBrush('y') bold = True readonly = False else: raise ValueError("'state' must be one of 'autoSet', 'autoUnset', or 'fixed'") param.setReadonly(readonly) #for item in param.items: #item.setBackground(0, bg) #f = item.font(0) #f.setWeight(f.Bold if bold else f.Normal) #item.setFont(0, f) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/ParameterTree.py000066400000000000000000000167711421045507400256410ustar00rootroot00000000000000from .parameterTypes import GroupParameterItem from ..Qt import QtCore, QtWidgets, mkQApp from ..widgets.TreeWidget import TreeWidget from .ParameterItem import ParameterItem class ParameterTree(TreeWidget): """Widget used to display or control data from a hierarchy of Parameters""" def __init__(self, parent=None, showHeader=True): """ ============== ======================================================== **Arguments:** parent (QWidget) An optional parent widget showHeader (bool) If True, then the QTreeView header is displayed. ============== ======================================================== """ TreeWidget.__init__(self, parent) self.setVerticalScrollMode(self.ScrollMode.ScrollPerPixel) self.setHorizontalScrollMode(self.ScrollMode.ScrollPerPixel) self.setAnimated(False) self.setColumnCount(2) self.setHeaderLabels(["Parameter", "Value"]) self.setAlternatingRowColors(True) self.paramSet = None self.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.ResizeToContents) self.setHeaderHidden(not showHeader) self.itemChanged.connect(self.itemChangedEvent) self.itemExpanded.connect(self.itemExpandedEvent) self.itemCollapsed.connect(self.itemCollapsedEvent) self.lastSel = None self.setRootIsDecorated(False) app = mkQApp() app.paletteChanged.connect(self.updatePalette) def setParameters(self, param, showTop=True): """ Set the top-level :class:`Parameter ` to be displayed in this ParameterTree. If *showTop* is False, then the top-level parameter is hidden and only its children will be visible. This is a convenience method equivalent to:: tree.clear() tree.addParameters(param, showTop) """ self.clear() self.addParameters(param, showTop=showTop) def addParameters(self, param, root=None, depth=0, showTop=True): """ Adds one top-level :class:`Parameter ` to the view. ============== ========================================================== **Arguments:** param The :class:`Parameter ` to add. root The item within the tree to which *param* should be added. By default, *param* is added as a top-level item. showTop If False, then *param* will be hidden, and only its children will be visible in the tree. ============== ========================================================== """ item = param.makeTreeItem(depth=depth) if root is None: root = self.invisibleRootItem() ## Hide top-level item if not showTop: item.setText(0, '') item.setSizeHint(0, QtCore.QSize(1,1)) item.setSizeHint(1, QtCore.QSize(1,1)) depth -= 1 root.addChild(item) item.treeWidgetChanged() for ch in param: self.addParameters(ch, root=item, depth=depth+1) def clear(self): """ Remove all parameters from the tree. """ self.invisibleRootItem().takeChildren() def focusNext(self, item, forward=True): """Give input focus to the next (or previous) item after *item* """ while True: parent = item.parent() if parent is None: return nextItem = self.nextFocusableChild(parent, item, forward=forward) if nextItem is not None: nextItem.setFocus() self.setCurrentItem(nextItem) return item = parent def focusPrevious(self, item): self.focusNext(item, forward=False) def nextFocusableChild(self, root, startItem=None, forward=True): if startItem is None: if forward: index = 0 else: index = root.childCount()-1 else: if forward: index = root.indexOfChild(startItem) + 1 else: index = root.indexOfChild(startItem) - 1 if forward: inds = list(range(index, root.childCount())) else: inds = list(range(index, -1, -1)) for i in inds: item = root.child(i) if hasattr(item, 'isFocusable') and item.isFocusable(): return item else: item = self.nextFocusableChild(item, forward=forward) if item is not None: return item return None def contextMenuEvent(self, ev): item = self.currentItem() if hasattr(item, 'contextMenuEvent'): item.contextMenuEvent(ev) def updatePalette(self): """ called when application palette changes This should ensure that the color theme of the OS is applied to the GroupParameterItems should the theme chang while the application is running. """ for item in self.listAllItems(): if isinstance(item, GroupParameterItem): item.updateDepth(item.depth) def itemChangedEvent(self, item, col): if hasattr(item, 'columnChangedEvent'): item.columnChangedEvent(col) def itemExpandedEvent(self, item): if hasattr(item, 'expandedChangedEvent'): item.expandedChangedEvent(True) def itemCollapsedEvent(self, item): if hasattr(item, 'expandedChangedEvent'): item.expandedChangedEvent(False) def selectionChanged(self, *args): sel = self.selectedItems() if len(sel) != 1: sel = None if self.lastSel is not None and isinstance(self.lastSel, ParameterItem): self.lastSel.selected(False) if sel is None: self.lastSel = None return self.lastSel = sel[0] if hasattr(sel[0], 'selected'): sel[0].selected(True) return super().selectionChanged(*args) # commented out due to being unreliable # def wheelEvent(self, ev): # self.clearSelection() # return super().wheelEvent(ev) def sizeHint(self): w, h = 0, 0 ind = self.indentation() for x in self.listAllItems(): if x.isHidden(): continue try: depth = x.depth except AttributeError: depth = 0 s0 = x.sizeHint(0) s1 = x.sizeHint(1) w = max(w, depth * ind + max(0, s0.width()) + max(0, s1.width())) h += max(0, s0.height(), s1.height()) # typ = x.param.opts['type'] if isinstance(x, ParameterItem) else x # print(typ, depth * ind, (s0.width(), s0.height()), (s1.width(), s1.height()), (w, h)) # todo: find out if this alternative can be made to work (currently fails when color or colormap are present) # print('custom', (w, h)) # w = self.sizeHintForColumn(0) + self.sizeHintForColumn(1) # h = self.viewportSizeHint().height() # print('alternative', (w, h)) if not self.header().isHidden(): h += self.header().height() return QtCore.QSize(w, h) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/SystemSolver.py000066400000000000000000000426131421045507400255520ustar00rootroot00000000000000import copy from collections import OrderedDict from math import log2 import numpy as np from .. import functions as fn class SystemSolver(object): """ This abstract class is used to formalize and manage user interaction with a complex system of equations (related to "constraint satisfaction problems"). It is often the case that devices must be controlled through a large number of free variables, and interactions between these variables make the system difficult to manage and conceptualize as a user interface. This class does _not_ attempt to numerically solve the system of equations. Rather, it provides a framework for subdividing the system into manageable pieces and specifying closed-form solutions to these small pieces. For an example, see the simple Camera class below. Theory of operation: Conceptualize the system as 1) a set of variables whose values may be either user-specified or automatically generated, and 2) a set of functions that define *how* each variable should be generated. When a variable is accessed (as an instance attribute), the solver first checks to see if it already has a value (either user-supplied, or cached from a previous calculation). If it does not, then the solver calls a method on itself (the method must be named `_variableName`) that will either return the calculated value (which usually involves acccessing other variables in the system), or raise RuntimeError if it is unable to calculate the value (usually because the user has not provided sufficient input to fully constrain the system). Each method that calculates a variable value may include multiple try/except blocks, so that if one method generates a RuntimeError, it may fall back on others. In this way, the system may be solved by recursively searching the tree of possible relationships between variables. This allows the user flexibility in deciding which variables are the most important to specify, while avoiding the apparent combinatorial explosion of calculation pathways that must be considered by the developer. Solved values are cached for efficiency, and automatically cleared when a state change invalidates the cache. The rules for this are simple: any time a value is set, it invalidates the cache *unless* the previous value was None (which indicates that no other variable has yet requested that value). More complex cache management may be defined in subclasses. Subclasses must define: 1) The *defaultState* class attribute: This is a dict containing a description of the variables in the system--their default values, data types, and the ways they can be constrained. The format is:: { name: [value, type, constraint, allowed_constraints], ...} Where: * *value* is the default value. May be None if it has not been specified yet. * *type* may be float, int, bool, np.ndarray, ... * *constraint* may be None, single value, or (min, max) * None indicates that the value is not constrained--it may be automatically generated if the value is requested. * *allowed_constraints* is a string composed of (n)one, (f)ixed, and (r)ange. Note: do not put mutable objects inside defaultState! 2) For each variable that may be automatically determined, a method must be defined with the name `_variableName`. This method may either return the """ defaultState = OrderedDict() def __init__(self): self.__dict__['_vars'] = OrderedDict() self.__dict__['_currentGets'] = set() self.reset() def copy(self): sys = type(self)() sys.__dict__['_vars'] = copy.deepcopy(self.__dict__['_vars']) sys.__dict__['_currentGets'] = copy.deepcopy(self.__dict__['_currentGets']) return sys def reset(self): """ Reset all variables in the solver to their default state. """ self._currentGets.clear() for k in self.defaultState: self._vars[k] = self.defaultState[k][:] def __getattr__(self, name): if name in self._vars: return self.get(name) raise AttributeError(name) def __setattr__(self, name, value): """ Set the value of a state variable. If None is given for the value, then the constraint will also be set to None. If a tuple is given for a scalar variable, then the tuple is used as a range constraint instead of a value. Otherwise, the constraint is set to 'fixed'. """ # First check this is a valid attribute if name in self._vars: if value is None: self.set(name, value, None) elif isinstance(value, tuple) and self._vars[name][1] is not np.ndarray: self.set(name, None, value) else: self.set(name, value, 'fixed') else: # also allow setting any other pre-existing attribute if hasattr(self, name): object.__setattr__(self, name, value) else: raise AttributeError(name) def get(self, name): """ Return the value for parameter *name*. If the value has not been specified, then attempt to compute it from other interacting parameters. If no value can be determined, then raise RuntimeError. """ if name in self._currentGets: raise RuntimeError("Cyclic dependency while calculating '%s'." % name) self._currentGets.add(name) try: v = self._vars[name][0] if v is None: cfunc = getattr(self, '_' + name, None) if cfunc is None: v = None else: v = cfunc() if v is None: raise RuntimeError("Parameter '%s' is not specified." % name) v = self.set(name, v) finally: self._currentGets.remove(name) return v def set(self, name, value=None, constraint=True): """ Set a variable *name* to *value*. The actual set value is returned (in some cases, the value may be cast into another type). If *value* is None, then the value is left to be determined in the future. At any time, the value may be re-assigned arbitrarily unless a constraint is given. If *constraint* is True (the default), then supplying a value that violates a previously specified constraint will raise an exception. If *constraint* is 'fixed', then the value is set (if provided) and the variable will not be updated automatically in the future. If *constraint* is a tuple, then the value is constrained to be within the given (min, max). Either constraint may be None to disable it. In some cases, a constraint cannot be satisfied automatically, and the user will be forced to resolve the constraint manually. If *constraint* is None, then any constraints are removed for the variable. """ var = self._vars[name] if constraint is None: if 'n' not in var[3]: raise TypeError("Empty constraints not allowed for '%s'" % name) var[2] = constraint elif constraint == 'fixed': if 'f' not in var[3]: raise TypeError("Fixed constraints not allowed for '%s'" % name) # This is nice, but not reliable because sometimes there is 1 DOF but we set 2 # values simultaneously. # if var[2] is None: # try: # self.get(name) # # has already been computed by the system; adding a fixed constraint # # would overspecify the system. # raise ValueError("Cannot fix parameter '%s'; system would become overconstrained." % name) # except RuntimeError: # pass var[2] = constraint elif isinstance(constraint, tuple): if 'r' not in var[3]: raise TypeError("Range constraints not allowed for '%s'" % name) assert len(constraint) == 2 var[2] = constraint elif constraint is not True: raise TypeError("constraint must be None, True, 'fixed', or tuple. (got %s)" % constraint) # type checking / massaging if var[1] is np.ndarray and value is not None: value = np.array(value, dtype=float) elif var[1] in (int, float, tuple) and value is not None: value = var[1](value) # constraint checks if constraint is True and not self.check_constraint(name, value): raise ValueError("Setting %s = %s violates constraint %s" % (name, value, var[2])) # invalidate other dependent values if var[0] is not None or value is None: # todo: we can make this more clever..(and might need to) # we just know that a value of None cannot have dependencies # (because if anyone else had asked for this value, it wouldn't be # None anymore) self.resetUnfixed() var[0] = value return value def check_constraint(self, name, value): c = self._vars[name][2] if c is None or value is None: return True if isinstance(c, tuple): return ((c[0] is None or c[0] <= value) and (c[1] is None or c[1] >= value)) else: return value == c def saveState(self): """ Return a serializable description of the solver's current state. """ state = OrderedDict() for name, var in self._vars.items(): state[name] = (var[0], var[2]) return state def restoreState(self, state): """ Restore the state of all values and constraints in the solver. """ self.reset() for name, var in state.items(): self.set(name, var[0], var[1]) def resetUnfixed(self): """ For any variable that does not have a fixed value, reset its value to None. """ for var in self._vars.values(): if var[2] != 'fixed': var[0] = None def solve(self): for k in self._vars: getattr(self, k) def checkOverconstraint(self): """Check whether the system is overconstrained. If so, return the name of the first overconstrained parameter. Overconstraints occur when any fixed parameter can be successfully computed by the system. (Ideally, all parameters are either fixed by the user or constrained by the system, but never both). """ for k,v in self._vars.items(): if v[2] == 'fixed' and 'n' in v[3]: oldval = v[:] self.set(k, None, None) try: self.get(k) return k except RuntimeError: pass finally: self._vars[k] = oldval return False def __repr__(self): state = OrderedDict() for name, var in self._vars.items(): if var[2] == 'fixed': state[name] = var[0] state = ', '.join(["%s=%s" % (n, v) for n,v in state.items()]) return "<%s %s>" % (self.__class__.__name__, state) if __name__ == '__main__': class Camera(SystemSolver): """ Consider a simple SLR camera. The variables we will consider that affect the camera's behavior while acquiring a photo are aperture, shutter speed, ISO, and flash (of course there are many more, but let's keep the example simple). In rare cases, the user wants to manually specify each of these variables and no more work needs to be done to take the photo. More often, the user wants to specify more interesting constraints like depth of field, overall exposure, or maximum allowed ISO value. If we add a simple light meter measurement into this system and an 'exposure' variable that indicates the desired exposure (0 is "perfect", -1 is one stop darker, etc), then the system of equations governing the camera behavior would have the following variables: aperture, shutter, iso, flash, exposure, light meter The first four variables are the "outputs" of the system (they directly drive the camera), the last is a constant (the camera itself cannot affect the reading on the light meter), and 'exposure' specifies a desired relationship between other variables in the system. So the question is: how can I formalize a system like this as a user interface? Typical cameras have a fairly limited approach: provide the user with a list of modes, each of which defines a particular set of constraints. For example: manual: user provides aperture, shutter, iso, and flash aperture priority: user provides aperture and exposure, camera selects iso, shutter, and flash automatically shutter priority: user provides shutter and exposure, camera selects iso, aperture, and flash program: user specifies exposure, camera selects all other variables automatically action: camera selects all variables while attempting to maximize shutter speed portrait: camera selects all variables while attempting to minimize aperture A more general approach might allow the user to provide more explicit constraints on each variable (for example: I want a shutter speed of 1/30 or slower, an ISO no greater than 400, an exposure between -1 and 1, and the smallest aperture possible given all other constraints) and have the camera solve the system of equations, with a warning if no solution is found. This is exactly what we will implement in this example class. """ defaultState = OrderedDict([ # Field stop aperture ('aperture', [None, float, None, 'nf']), # Duration that shutter is held open. ('shutter', [None, float, None, 'nf']), # ISO (sensitivity) value. 100, 200, 400, 800, 1600.. ('iso', [None, int, None, 'nf']), # Flash is a value indicating the brightness of the flash. A table # is used to decide on "balanced" settings for each flash level: # 0: no flash # 1: s=1/60, a=2.0, iso=100 # 2: s=1/60, a=4.0, iso=100 ..and so on.. ('flash', [None, float, None, 'nf']), # exposure is a value indicating how many stops brighter (+1) or # darker (-1) the photographer would like the photo to appear from # the 'balanced' settings indicated by the light meter (see below). ('exposure', [None, float, None, 'f']), # Let's define this as an external light meter (not affected by # aperture) with logarithmic output. We arbitrarily choose the # following settings as "well balanced" for each light meter value: # -1: s=1/60, a=2.0, iso=100 # 0: s=1/60, a=4.0, iso=100 # 1: s=1/120, a=4.0, iso=100 ..and so on.. # Note that the only allowed constraint mode is (f)ixed, since the # camera never _computes_ the light meter value, it only reads it. ('lightMeter', [None, float, None, 'f']), # Indicates the camera's final decision on how it thinks the photo will # look, given the chosen settings. This value is _only_ determined # automatically. ('balance', [None, float, None, 'n']), ]) def _aperture(self): """ Determine aperture automatically under a variety of conditions. """ iso = self.iso exp = self.exposure light = self.lightMeter try: # shutter-priority mode sh = self.shutter # this raises RuntimeError if shutter has not # been specified ap = 4.0 * (sh / (1./60.)) * (iso / 100.) * (2 ** exp) * (2 ** light) ap = fn.clip_scalar(ap, 2.0, 16.0) except RuntimeError: # program mode; we can select a suitable shutter # value at the same time. sh = (1./60.) raise return ap def _balance(self): iso = self.iso light = self.lightMeter sh = self.shutter ap = self.aperture bal = (4.0 / ap) * (sh / (1./60.)) * (iso / 100.) * (2 ** light) return log2(bal) camera = Camera() camera.iso = 100 camera.exposure = 0 camera.lightMeter = 2 camera.shutter = 1./60. camera.flash = 0 camera.solve() print(camera.saveState()) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/__init__.py000066400000000000000000000004061421045507400246240ustar00rootroot00000000000000from . import parameterTypes as types from .Parameter import Parameter, registerParameterItemType, registerParameterType from .ParameterItem import ParameterItem from .ParameterSystem import ParameterSystem, SystemSolver from .ParameterTree import ParameterTree pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/000077500000000000000000000000001421045507400255205ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/__init__.py000066400000000000000000000044741421045507400276420ustar00rootroot00000000000000from ..Parameter import registerParameterItemType, registerParameterType from .action import ActionParameter, ActionParameterItem from .basetypes import ( GroupParameter, GroupParameterItem, SimpleParameter, WidgetParameterItem, ) from .bool import BoolParameterItem from .calendar import CalendarParameter, CalendarParameterItem from .checklist import ChecklistParameter, ChecklistParameterItem from .color import ColorParameter, ColorParameterItem from .colormap import ColorMapParameter, ColorMapParameterItem from .file import FileParameter, FileParameterItem from .font import FontParameter, FontParameterItem from .list import ListParameter, ListParameterItem from .numeric import NumericParameterItem from .pen import PenParameter, PenParameterItem from .progress import ProgressBarParameter, ProgressBarParameterItem from .qtenum import QtEnumParameter from .slider import SliderParameter, SliderParameterItem from .str import StrParameterItem from .text import TextParameter, TextParameterItem registerParameterItemType('bool', BoolParameterItem, SimpleParameter, override=True) registerParameterItemType('float', NumericParameterItem, SimpleParameter, override=True) registerParameterItemType('int', NumericParameterItem, SimpleParameter, override=True) registerParameterItemType('str', StrParameterItem, SimpleParameter, override=True) registerParameterType('group', GroupParameter, override=True) registerParameterType('action', ActionParameter, override=True) registerParameterType('calendar', CalendarParameter, override=True) registerParameterType('checklist', ChecklistParameter, override=True) registerParameterType('color', ColorParameter, override=True) registerParameterType('colormap', ColorMapParameter, override=True) registerParameterType('file', FileParameter, override=True) registerParameterType('font', FontParameter, override=True) registerParameterType('list', ListParameter, override=True) registerParameterType('pen', PenParameter, override=True) registerParameterType('progress', ProgressBarParameter, override=True) # qtenum is a bit specific, hold off on registering for now registerParameterType('slider', SliderParameter, override=True) registerParameterType('text', TextParameter, override=True) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/action.py000066400000000000000000000034561421045507400273570ustar00rootroot00000000000000from ...Qt import QtCore, QtWidgets from ..Parameter import Parameter from ..ParameterItem import ParameterItem class ActionParameterItem(ParameterItem): """ParameterItem displaying a clickable button.""" def __init__(self, param, depth): ParameterItem.__init__(self, param, depth) self.layoutWidget = QtWidgets.QWidget() self.layout = QtWidgets.QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layoutWidget.setLayout(self.layout) self.button = QtWidgets.QPushButton() #self.layout.addSpacing(100) self.layout.addWidget(self.button) self.layout.addStretch() self.button.clicked.connect(self.buttonClicked) self.titleChanged() self.optsChanged(self.param, self.param.opts) def treeWidgetChanged(self): ParameterItem.treeWidgetChanged(self) tree = self.treeWidget() if tree is None: return self.setFirstColumnSpanned(True) tree.setItemWidget(self, 0, self.layoutWidget) def titleChanged(self): self.button.setText(self.param.title()) self.setSizeHint(0, self.button.sizeHint()) def optsChanged(self, param, opts): ParameterItem.optsChanged(self, param, opts) if 'enabled' in opts: self.button.setEnabled(opts['enabled']) if 'tip' in opts: self.button.setToolTip(opts['tip']) def buttonClicked(self): self.param.activate() class ActionParameter(Parameter): """Used for displaying a button within the tree. ``sigActivated(self)`` is emitted when the button is clicked. """ itemClass = ActionParameterItem sigActivated = QtCore.Signal(object) def activate(self): self.sigActivated.emit(self) self.emitStateChanged('activated', None) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/basetypes.py000066400000000000000000000370121421045507400300740ustar00rootroot00000000000000import builtins from ..Parameter import Parameter from ..ParameterItem import ParameterItem from ... import functions as fn from ... import icons from ...Qt import QtCore, QtGui, QtWidgets, mkQApp class WidgetParameterItem(ParameterItem): """ ParameterTree item with: * label in second column for displaying value * simple widget for editing value (displayed instead of label when item is selected) * button that resets value to default This class can be subclassed by overriding makeWidget() to provide a custom widget. """ def __init__(self, param, depth): ParameterItem.__init__(self, param, depth) self.asSubItem = False # place in a child item's column 0 instead of column 1 self.hideWidget = True ## hide edit widget, replace with label when not selected ## set this to False to keep the editor widget always visible # build widget with a display label and default button w = self.makeWidget() self.widget = w self.eventProxy = EventProxy(w, self.widgetEventFilter) if self.asSubItem: self.subItem = QtWidgets.QTreeWidgetItem() self.subItem.depth = self.depth + 1 self.subItem.setFlags(QtCore.Qt.ItemFlag.NoItemFlags) self.addChild(self.subItem) self.defaultBtn = self.makeDefaultButton() self.displayLabel = QtWidgets.QLabel() layout = QtWidgets.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) if not self.asSubItem: layout.addWidget(w, 1) layout.addWidget(self.displayLabel, 1) layout.addStretch(0) layout.addWidget(self.defaultBtn) self.layoutWidget = QtWidgets.QWidget() self.layoutWidget.setLayout(layout) if w.sigChanged is not None: w.sigChanged.connect(self.widgetValueChanged) if hasattr(w, 'sigChanging'): w.sigChanging.connect(self.widgetValueChanging) ## update value shown in widget. opts = self.param.opts if opts.get('value', None) is not None: self.valueChanged(self, opts['value'], force=True) else: ## no starting value was given; use whatever the widget has self.widgetValueChanged() self.updateDefaultBtn() self.optsChanged(self.param, self.param.opts) # set size hints sw = self.widget.sizeHint() sb = self.defaultBtn.sizeHint() # shrink row heights a bit for more compact look sw.setHeight(int(sw.height() * 0.9)) sb.setHeight(int(sb.height() * 0.9)) if self.asSubItem: self.setSizeHint(1, sb) self.subItem.setSizeHint(0, sw) else: w = sw.width() + sb.width() h = max(sw.height(), sb.height()) self.setSizeHint(1, QtCore.QSize(w, h)) def makeWidget(self): """ Return a single widget whose position in the tree is determined by the value of self.asSubItem. If True, it will be placed in the second tree column, and if False, the first tree column of a child item. The widget must be given three attributes: ========== ============================================================ sigChanged a signal that is emitted when the widget's value is changed value a function that returns the value setValue a function that sets the value ========== ============================================================ This function must be overridden by a subclass. """ raise NotImplementedError def widgetEventFilter(self, obj, ev): ## filter widget's events ## catch TAB to change focus ## catch focusOut to hide editor if ev.type() == ev.Type.KeyPress: if ev.key() == QtCore.Qt.Key.Key_Tab: self.focusNext(forward=True) return True ## don't let anyone else see this event elif ev.key() == QtCore.Qt.Key.Key_Backtab: self.focusNext(forward=False) return True ## don't let anyone else see this event return False def makeDefaultButton(self): defaultBtn = QtWidgets.QPushButton() defaultBtn.setAutoDefault(False) defaultBtn.setFixedWidth(20) defaultBtn.setFixedHeight(20) defaultBtn.setIcon(icons.getGraphIcon('default')) defaultBtn.clicked.connect(self.defaultClicked) return defaultBtn def setFocus(self): self.showEditor() def isFocusable(self): return self.param.opts['visible'] and self.param.opts['enabled'] and self.param.writable() def valueChanged(self, param, val, force=False): ## called when the parameter's value has changed ParameterItem.valueChanged(self, param, val) if force or not fn.eq(val, self.widget.value()): try: if self.widget.sigChanged is not None: self.widget.sigChanged.disconnect(self.widgetValueChanged) self.param.sigValueChanged.disconnect(self.valueChanged) self.widget.setValue(val) self.param.setValue(self.widget.value()) finally: if self.widget.sigChanged is not None: self.widget.sigChanged.connect(self.widgetValueChanged) self.param.sigValueChanged.connect(self.valueChanged) self.updateDisplayLabel() ## always make sure label is updated, even if values match! self.updateDefaultBtn() def updateDefaultBtn(self): ## enable/disable default btn self.defaultBtn.setEnabled( not self.param.valueIsDefault() and self.param.opts['enabled'] and self.param.writable()) # hide / show self.defaultBtn.setVisible(self.param.hasDefault() and not self.param.readonly()) def updateDisplayLabel(self, value=None): """Update the display label to reflect the value of the parameter.""" if value is None: value = self.param.value() self.displayLabel.setText(str(value)) def widgetValueChanged(self): ## called when the widget's value has been changed by the user val = self.widget.value() self.param.setValue(val) def widgetValueChanging(self, *args): """ Called when the widget's value is changing, but not finalized. For example: editing text before pressing enter or changing focus. """ self.param.sigValueChanging.emit(self.param, self.widget.value()) def selected(self, sel): """Called when this item has been selected (sel=True) OR deselected (sel=False)""" ParameterItem.selected(self, sel) if self.widget is None: return if sel and self.param.writable(): self.showEditor() elif self.hideWidget: self.hideEditor() def showEditor(self): self.widget.show() self.displayLabel.hide() self.widget.setFocus(QtCore.Qt.FocusReason.OtherFocusReason) def hideEditor(self): self.widget.hide() self.displayLabel.show() def limitsChanged(self, param, limits): """Called when the parameter's limits have changed""" ParameterItem.limitsChanged(self, param, limits) def defaultChanged(self, param, value): self.updateDefaultBtn() def treeWidgetChanged(self): """Called when this item is added or removed from a tree.""" ParameterItem.treeWidgetChanged(self) ## add all widgets for this item into the tree if self.widget is not None: tree = self.treeWidget() if tree is None: return if self.asSubItem: self.subItem.setFirstColumnSpanned(True) tree.setItemWidget(self.subItem, 0, self.widget) tree.setItemWidget(self, 1, self.layoutWidget) self.displayLabel.hide() self.selected(False) def defaultClicked(self): self.param.setToDefault() def optsChanged(self, param, opts): """Called when any options are changed that are not name, value, default, or limits""" ParameterItem.optsChanged(self, param, opts) if 'enabled' in opts: self.updateDefaultBtn() self.widget.setEnabled(opts['enabled']) if 'readonly' in opts: self.updateDefaultBtn() if hasattr(self.widget, 'setReadOnly'): self.widget.setReadOnly(opts['readonly']) else: self.widget.setEnabled(self.param.opts['enabled'] and not opts['readonly']) if 'tip' in opts: self.widget.setToolTip(opts['tip']) class EventProxy(QtCore.QObject): def __init__(self, qobj, callback): QtCore.QObject.__init__(self) self.callback = callback qobj.installEventFilter(self) def eventFilter(self, obj, ev): return self.callback(obj, ev) class SimpleParameter(Parameter): """ Parameter representing a single value. This parameter is backed by :class:`WidgetParameterItem` to represent the following parameter names through various subclasses: - 'int' - 'float' - 'bool' - 'str' - 'color' - 'colormap' """ def __init__(self, *args, **kargs): """ Initialize the parameter. This is normally called implicitly through :meth:`Parameter.create`. The keyword arguments avaialble to :meth:`Parameter.__init__` are applicable. """ Parameter.__init__(self, *args, **kargs) def _interpretValue(self, v): typ = self.opts['type'] def _missing_interp(v): # Assume raw interpretation return v # Or: # raise TypeError(f'No interpreter found for type {typ}') interpreter = getattr(builtins, typ, _missing_interp) return interpreter(v) class GroupParameterItem(ParameterItem): """ Group parameters are used mainly as a generic parent item that holds (and groups!) a set of child parameters. It also provides a simple mechanism for displaying a button or combo that can be used to add new parameters to the group. """ def __init__(self, param, depth): ParameterItem.__init__(self, param, depth) self._initialFontPointSize = self.font(0).pointSize() self.updateDepth(depth) self.addItem = None if 'addText' in param.opts: addText = param.opts['addText'] if 'addList' in param.opts: self.addWidget = QtWidgets.QComboBox() self.addWidget.setSizeAdjustPolicy(QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents) self.updateAddList() self.addWidget.currentIndexChanged.connect(self.addChanged) else: self.addWidget = QtWidgets.QPushButton(addText) self.addWidget.clicked.connect(self.addClicked) w = QtWidgets.QWidget() l = QtWidgets.QHBoxLayout() l.setContentsMargins(0, 0, 0, 0) w.setLayout(l) l.addWidget(self.addWidget) l.addStretch() self.addWidgetBox = w self.addItem = QtWidgets.QTreeWidgetItem([]) self.addItem.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled) self.addItem.depth = self.depth + 1 ParameterItem.addChild(self, self.addItem) self.addItem.setSizeHint(0, self.addWidgetBox.sizeHint()) self.optsChanged(self.param, self.param.opts) def pointSize(self): return self._initialFontPointSize def updateDepth(self, depth): """ Change set the item font to bold and increase the font size on outermost groups. """ app = mkQApp() palette = app.palette() background = palette.base().color() h, s, l, a = background.getHslF() lightness = 0.5 + (l - 0.5) * .8 altBackground = QtGui.QColor.fromHslF(h, s, lightness, a) for c in [0, 1]: font = self.font(c) font.setBold(True) if depth == 0: font.setPointSize(self.pointSize() + 1) self.setBackground(c, background) else: self.setBackground(c, altBackground) self.setForeground(c, palette.text().color()) self.setFont(c, font) self.titleChanged() # sets the size hint for column 0 which is based on the new font def addClicked(self): """Called when "add new" button is clicked The parameter MUST have an 'addNew' method defined. """ self.param.addNew() def addChanged(self): """Called when "add new" combo is changed The parameter MUST have an 'addNew' method defined. """ if self.addWidget.currentIndex() == 0: return typ = self.addWidget.currentText() self.param.addNew(typ) self.addWidget.setCurrentIndex(0) def treeWidgetChanged(self): ParameterItem.treeWidgetChanged(self) tw = self.treeWidget() if tw is None: return self.setFirstColumnSpanned(True) if self.addItem is not None: tw.setItemWidget(self.addItem, 0, self.addWidgetBox) self.addItem.setFirstColumnSpanned(True) def addChild(self, child): ## make sure added childs are actually inserted before add btn if self.addItem is not None: ParameterItem.insertChild(self, self.childCount() - 1, child) else: ParameterItem.addChild(self, child) def optsChanged(self, param, opts): ParameterItem.optsChanged(self, param, opts) if 'addList' in opts: self.updateAddList() if hasattr(self, 'addWidget'): if 'enabled' in opts: self.addWidget.setEnabled(opts['enabled']) if 'tip' in opts: self.addWidget.setToolTip(opts['tip']) def updateAddList(self): self.addWidget.blockSignals(True) try: self.addWidget.clear() self.addWidget.addItem(self.param.opts['addText']) for t in self.param.opts['addList']: self.addWidget.addItem(t) finally: self.addWidget.blockSignals(False) class GroupParameter(Parameter): """ Group parameters are used mainly as a generic parent item that holds (and groups!) a set of child parameters. It also provides a simple mechanism for displaying a button or combo that can be used to add new parameters to the group. To enable this, the group must be initialized with the 'addText' option (the text will be displayed on a button which, when clicked, will cause addNew() to be called). If the 'addList' option is specified as well, then a dropdown-list of addable items will be displayed instead of a button. """ itemClass = GroupParameterItem sigAddNew = QtCore.Signal(object, object) # self, type def addNew(self, typ=None): """ This method is called when the user has requested to add a new item to the group. By default, it emits ``sigAddNew(self, typ)``. """ self.sigAddNew.emit(self, typ) def setAddList(self, vals): """Change the list of options available for the user to add to the group.""" self.setOpts(addList=vals) class Emitter(QtCore.QObject): """ WidgetParameterItem is not a QObject, so create a QObject wrapper that items can use for emitting """ sigChanging = QtCore.Signal(object, object) sigChanged = QtCore.Signal(object, object) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/bool.py000066400000000000000000000006161421045507400270300ustar00rootroot00000000000000from ...Qt import QtWidgets from .basetypes import WidgetParameterItem class BoolParameterItem(WidgetParameterItem): """ Registered parameter type which displays a QCheckBox """ def makeWidget(self): w = QtWidgets.QCheckBox() w.sigChanged = w.toggled w.value = w.isChecked w.setValue = w.setChecked self.hideWidget = False return w pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/calendar.py000066400000000000000000000036171421045507400276520ustar00rootroot00000000000000from ...Qt import QtCore, QtWidgets from ..Parameter import Parameter from .basetypes import WidgetParameterItem class CalendarParameterItem(WidgetParameterItem): def makeWidget(self): self.asSubItem = True w = QtWidgets.QCalendarWidget() w.setMaximumHeight(200) w.sigChanged = w.selectionChanged w.value = w.selectedDate w.setValue = w.setSelectedDate self.hideWidget = False self.param.opts.setdefault('default', QtCore.QDate.currentDate()) return w class CalendarParameter(Parameter): """ Displays a Qt calendar whose date is specified by a 'format' option. ============== ======================================================== **Options:** format Format for displaying the date and converting from a string. Can be any value accepted by `QDate.toString` and `fromString`, or a stringified version of a QDateFormat enum, i.e. 'ISODate', 'TextDate' (default), etc. ============== ======================================================== """ itemClass = CalendarParameterItem def __init__(self, **opts): opts.setdefault('format', 'TextDate') super().__init__(**opts) def _interpretFormat(self, fmt=None): fmt = fmt or self.opts.get('format') if hasattr(QtCore.Qt.DateFormat, fmt): fmt = getattr(QtCore.Qt.DateFormat, fmt) return fmt def _interpretValue(self, v): if isinstance(v, str): fmt = self._interpretFormat() if fmt is None: raise ValueError('Cannot parse date string without a set format') v = QtCore.QDate.fromString(v, fmt) return v def saveState(self, filter=None): state = super().saveState(filter) fmt = self._interpretFormat() state['value'] = state['value'].toString(fmt) return state pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/checklist.py000066400000000000000000000220711421045507400300450ustar00rootroot00000000000000from ... import functions as fn from ...Qt import QtWidgets from ...SignalProxy import SignalProxy from ..ParameterItem import ParameterItem from . import BoolParameterItem, SimpleParameter from .basetypes import GroupParameter, GroupParameterItem, WidgetParameterItem from .list import ListParameter from .slider import Emitter class ChecklistParameterItem(GroupParameterItem): """ Wraps a :class:`GroupParameterItem` to manage ``bool`` parameter children. Also provides convenience buttons to select or clear all values at once. Note these conveniences are disabled when ``exclusive`` is *True*. """ def __init__(self, param, depth): self.btnGrp = QtWidgets.QButtonGroup() self.btnGrp.setExclusive(False) self._constructMetaBtns() super().__init__(param, depth) def _constructMetaBtns(self): self.metaBtnWidget = QtWidgets.QWidget() self.metaBtnLayout = lay = QtWidgets.QHBoxLayout(self.metaBtnWidget) lay.setContentsMargins(0, 0, 0, 0) lay.setSpacing(2) self.metaBtns = {} lay.addStretch(0) for title in 'Clear', 'Select': self.metaBtns[title] = btn = QtWidgets.QPushButton(f'{title} All') self.metaBtnLayout.addWidget(btn) btn.clicked.connect(getattr(self, f'{title.lower()}AllClicked')) self.metaBtns['default'] = WidgetParameterItem.makeDefaultButton(self) self.metaBtnLayout.addWidget(self.metaBtns['default']) def defaultClicked(self): self.param.setToDefault() def treeWidgetChanged(self): ParameterItem.treeWidgetChanged(self) tw = self.treeWidget() if tw is None: return tw.setItemWidget(self, 1, self.metaBtnWidget) def selectAllClicked(self): self.param.setValue(self.param.reverse[0]) def clearAllClicked(self): self.param.setValue([]) def insertChild(self, pos, item): ret = super().insertChild(pos, item) self.btnGrp.addButton(item.widget) return ret def addChild(self, item): ret = super().addChild(item) self.btnGrp.addButton(item.widget) return ret def takeChild(self, i): child = super().takeChild(i) self.btnGrp.removeButton(child.widget) def optsChanged(self, param, opts): if 'expanded' in opts: for btn in self.metaBtns.values(): btn.setVisible(opts['expanded']) exclusive = opts.get('exclusive', param.opts['exclusive']) enabled = opts.get('enabled', param.opts['enabled']) for btn in self.metaBtns.values(): btn.setDisabled(exclusive or (not enabled)) self.btnGrp.setExclusive(exclusive) def expandedChangedEvent(self, expanded): for btn in self.metaBtns.values(): btn.setVisible(expanded) class RadioParameterItem(BoolParameterItem): """ Allows radio buttons to function as booleans when `exclusive` is *True* """ def __init__(self, param, depth): self.emitter = Emitter() super().__init__(param, depth) def makeWidget(self): w = QtWidgets.QRadioButton() w.value = w.isChecked # Since these are only used during exclusive operations, only fire a signal when "True" # to avoid a double-fire w.setValue = w.setChecked w.sigChanged = self.emitter.sigChanged w.toggled.connect(self.maybeSigChanged) self.hideWidget = False return w def maybeSigChanged(self, val): """ Make sure to only activate on a "true" value, since an exclusive button group fires once to deactivate the old option and once to activate the new selection """ if not val: return self.emitter.sigChanged.emit(self, val) # Proxy around radio/bool type so the correct item class gets instantiated class BoolOrRadioParameter(SimpleParameter): def __init__(self, **kargs): if kargs.get('type') == 'bool': self.itemClass = BoolParameterItem else: self.itemClass = RadioParameterItem super().__init__(**kargs) class ChecklistParameter(GroupParameter): """ Can be set just like a :class:`ListParameter`, but allows for multiple values to be selected simultaneously. ============== ======================================================== **Options** exclusive When *False*, any number of options can be selected. The resulting ``value()`` is a list of all checked values. When *True*, it behaves like a ``list`` type -- only one value can be selected. If no values are selected and ``exclusive`` is set to *True*, the first available limit is selected. The return value of an ``exclusive`` checklist is a single value rather than a list with one element. delay Controls the wait time between editing the checkboxes/radio button children and firing a "value changed" signal. This allows users to edit multiple boxes at once for a single value update. ============== ======================================================== """ itemClass = ChecklistParameterItem def __init__(self, **opts): self.targetValue = None limits = opts.setdefault('limits', []) self.forward, self.reverse = ListParameter.mapping(limits) value = opts.setdefault('value', limits) opts.setdefault('exclusive', False) super().__init__(**opts) # Force 'exclusive' to trigger by making sure value is not the same self.sigLimitsChanged.connect(self.updateLimits) self.sigOptionsChanged.connect(self.optsChanged) if len(limits): # Since update signal wasn't hooked up until after parameter construction, need to fire manually self.updateLimits(self, limits) # Also, value calculation will be incorrect until children are added, so make sure to recompute self.setValue(value) self.valChangingProxy = SignalProxy(self.sigValueChanging, delay=opts.get('delay', 1.0), slot=self._finishChildChanges) def childrenValue(self): vals = [self.forward[p.name()] for p in self.children() if p.value()] exclusive = self.opts['exclusive'] if not vals and exclusive: return None elif exclusive: return vals[0] else: return vals def _onChildChanging(self, _ch, _val): self.sigValueChanging.emit(self, self.childrenValue()) def updateLimits(self, _param, limits): oldOpts = self.names val = self.opts['value'] # Make sure adding and removing children don't cause tree state changes self.blockTreeChangeSignal() self.clearChildren() self.forward, self.reverse = ListParameter.mapping(limits) if self.opts.get('exclusive'): typ = 'radio' else: typ = 'bool' for chName in self.forward: # Recycle old values if they match the new limits newVal = bool(oldOpts.get(chName, False)) child = BoolOrRadioParameter(type=typ, name=chName, value=newVal, default=None) self.addChild(child) # Prevent child from broadcasting tree state changes, since this is handled by self child.blockTreeChangeSignal() child.sigValueChanged.connect(self._onChildChanging) # Purge child changes before unblocking self.treeStateChanges.clear() self.unblockTreeChangeSignal() self.setValue(val) def _finishChildChanges(self, paramAndValue): param, value = paramAndValue if self.opts['exclusive']: val = self.reverse[0][self.reverse[1].index(param.name())] return self.setValue(val) # Interpret value, fire sigValueChanged return self.setValue(self.childrenValue()) def optsChanged(self, param, opts): if 'exclusive' in opts: # Force set value to ensure updates # self.opts['value'] = self._VALUE_UNSET self.updateLimits(None, self.opts.get('limits', [])) if 'delay' in opts: self.valChangingProxy.setDelay(opts['delay']) def setValue(self, value, blockSignal=None): self.targetValue = value exclusive = self.opts['exclusive'] # Will emit at the end, so no problem discarding existing changes cmpVals = value if isinstance(value, list) else [value] for ii in range(len(cmpVals)-1, -1, -1): exists = any(fn.eq(cmpVals[ii], lim) for lim in self.reverse[0]) if not exists: del cmpVals[ii] names = [self.reverse[1][self.reverse[0].index(val)] for val in cmpVals] if exclusive and len(names) > 1: names = [names[0]] elif exclusive and not len(names) and len(self.forward): # An option is required during exclusivity names = [self.reverse[1][0]] for chParam in self: checked = chParam.name() in names chParam.setValue(checked, self._onChildChanging) super().setValue(self.childrenValue(), blockSignal) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/color.py000066400000000000000000000016141421045507400272120ustar00rootroot00000000000000from ... import functions as fn from ...widgets.ColorButton import ColorButton from .basetypes import SimpleParameter, WidgetParameterItem class ColorParameterItem(WidgetParameterItem): """Registered parameter type which displays a :class:`ColorButton ` """ def makeWidget(self): w = ColorButton() w.sigChanged = w.sigColorChanged w.sigChanging = w.sigColorChanging w.value = w.color w.setValue = w.setColor self.hideWidget = False w.setFlat(True) return w class ColorParameter(SimpleParameter): itemClass = ColorParameterItem def _interpretValue(self, v): return fn.mkColor(v) def value(self): return fn.mkColor(super().value()) def saveState(self, filter=None): state = super().saveState(filter) state['value'] = self.value().getRgb() return state pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/colormap.py000066400000000000000000000017041421045507400277100ustar00rootroot00000000000000from ...colormap import ColorMap from ...Qt import QtCore from ...widgets.GradientWidget import GradientWidget from .basetypes import SimpleParameter, WidgetParameterItem class ColorMapParameterItem(WidgetParameterItem): """Registered parameter type which displays a :class:`GradientWidget `""" def makeWidget(self): w = GradientWidget(orientation='bottom') w.sizeHint = lambda: QtCore.QSize(300, 35) w.sigChanged = w.sigGradientChangeFinished w.sigChanging = w.sigGradientChanged w.value = w.colorMap w.setValue = w.setColorMap self.hideWidget = False self.asSubItem = True return w class ColorMapParameter(SimpleParameter): itemClass = ColorMapParameterItem def _interpretValue(self, v): if v is not None and not isinstance(v, ColorMap): raise TypeError("Cannot set colormap parameter from object %r" % v) return v pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/file.py000066400000000000000000000176221421045507400270210ustar00rootroot00000000000000import os import re from ...Qt import QtCore, QtGui, QtWidgets from ..Parameter import Parameter from .str import StrParameterItem def _set_filepicker_kwargs(fileDlg, **kwargs): """Applies a dict of enum/flag kwarg opts to a file dialog""" NO_MATCH = object() for kk, vv in kwargs.items(): # Convert string or list representations into true flags # 'fileMode' -> 'FileMode' formattedName = kk[0].upper() + kk[1:] # Edge case: "Options" has enum "Option" if formattedName == 'Options': enumCls = fileDlg.Option else: enumCls = getattr(fileDlg, formattedName, NO_MATCH) setFunc = getattr(fileDlg, f'set{formattedName}', NO_MATCH) if enumCls is NO_MATCH or setFunc is NO_MATCH: continue if enumCls is fileDlg.Option: builder = fileDlg.Option(0) # This is the only flag enum, all others can only take one value if isinstance(vv, str): vv = [vv] for flag in vv: curVal = getattr(enumCls, flag) builder |= curVal # Some Qt implementations turn into ints by this point outEnum = enumCls(builder) else: outEnum = getattr(enumCls, vv) setFunc(outEnum) def popupFilePicker(parent=None, windowTitle='', nameFilter='', directory=None, selectFile=None, relativeTo=None, **kwargs): """ Thin wrapper around Qt file picker dialog. Used internally so all options are consistent among all requests for external file information ============== ======================================================== **Arguments:** parent Dialog parent windowTitle Title of dialog window nameFilter File filter as required by the Qt dialog directory Where in the file system to open this dialog selectFile File to preselect relativeTo Parent directory that, if provided, will be removed from the prefix of all returned paths. So, if '/my/text/file.txt' was selected, and `relativeTo='/my/text/'`, the return value would be 'file.txt'. This uses os.path.relpath under the hood, so expect that behavior. kwargs Any enum value accepted by a QFileDialog and its value. Values can be a string or list of strings, i.e. fileMode='AnyFile', options=['ShowDirsOnly', 'DontResolveSymlinks'], acceptMode='AcceptSave' ============== ======================================================== """ fileDlg = QtWidgets.QFileDialog(parent) _set_filepicker_kwargs(fileDlg, **kwargs) fileDlg.setModal(True) if directory is not None: fileDlg.setDirectory(directory) fileDlg.setNameFilter(nameFilter) if selectFile is not None: fileDlg.selectFile(selectFile) fileDlg.setWindowTitle(windowTitle) if fileDlg.exec(): # Append filter type singleExtReg = r'(\.\w+)' # Extensions of type 'myfile.ext.is.multi.part' need to capture repeating pattern of singleExt suffMatch = re.search(rf'({singleExtReg}+)', fileDlg.selectedNameFilter()) if suffMatch: # Strip leading '.' if it exists ext = suffMatch.group(1) if ext.startswith('.'): ext = ext[1:] fileDlg.setDefaultSuffix(ext) fList = fileDlg.selectedFiles() else: fList = [] if relativeTo is not None: fList = [os.path.relpath(file, relativeTo) for file in fList] # Make consistent to os flavor fList = [os.path.normpath(file) for file in fList] if fileDlg.fileMode() == fileDlg.FileMode.ExistingFiles: return fList elif len(fList) > 0: return fList[0] else: return None class FileParameterItem(StrParameterItem): def __init__(self, param, depth): self._value = None super().__init__(param, depth) button = QtWidgets.QPushButton('...') button.setFixedWidth(25) button.setContentsMargins(0, 0, 0, 0) button.clicked.connect(self._retrieveFileSelection_gui) self.layoutWidget.layout().insertWidget(2, button) self.displayLabel.resizeEvent = self._newResizeEvent # self.layoutWidget.layout().insertWidget(3, self.defaultBtn) def makeWidget(self): w = super().makeWidget() w.setValue = self.setValue w.value = self.value # Doesn't make much sense to have a 'changing' signal since filepaths should be complete before value # is emitted delattr(w, 'sigChanging') return w def _newResizeEvent(self, ev): ret = type(self.displayLabel).resizeEvent(self.displayLabel, ev) self.updateDisplayLabel() return ret def setValue(self, value): self._value = value self.widget.setText(str(value)) def value(self): return self._value def _retrieveFileSelection_gui(self): curVal = self.param.value() if isinstance(curVal, list) and len(curVal): # All files should be from the same directory, in principle # Since no mechanism exists for preselecting multiple, the most sensible # thing is to select nothing in the preview dialog curVal = curVal[0] if os.path.isfile(curVal): curVal = os.path.dirname(curVal) opts = self.param.opts.copy() useDir = curVal or opts.get('directory') or os.getcwd() startDir = os.path.abspath(useDir) if os.path.isfile(startDir): opts['selectFile'] = os.path.basename(startDir) startDir = os.path.dirname(startDir) if os.path.exists(startDir): opts['directory'] = startDir if opts.get('windowTitle') is None: opts['windowTitle'] = self.param.title() fname = popupFilePicker(None, **opts) if not fname: return self.param.setValue(fname) def updateDefaultBtn(self): # Override since a readonly label should still allow reverting to default ## enable/disable default btn self.defaultBtn.setEnabled( not self.param.valueIsDefault() and self.param.opts['enabled']) # hide / show self.defaultBtn.setVisible(self.param.hasDefault()) def updateDisplayLabel(self, value=None): lbl = self.displayLabel if value is None: value = self.param.value() value = str(value) font = lbl.font() metrics = QtGui.QFontMetricsF(font) value = metrics.elidedText(value, QtCore.Qt.TextElideMode.ElideLeft, lbl.width()-5) return super().updateDisplayLabel(value) class FileParameter(Parameter): """ Interfaces with the myriad of file options available from a QFileDialog. Note that the output can either be a single file string or list of files, depending on whether `fileMode='ExistingFiles'` is specified. Note that in all cases, absolute file paths are returned unless `relativeTo` is specified as elaborated below. ============== ======================================================== **Options:** parent Dialog parent winTitle Title of dialog window nameFilter File filter as required by the Qt dialog directory Where in the file system to open this dialog selectFile File to preselect relativeTo Parent directory that, if provided, will be removed from the prefix of all returned paths. So, if '/my/text/file.txt' was selected, and `relativeTo='my/text/'`, the return value would be 'file.txt'. This uses os.path.relpath under the hood, so expect that behavior. kwargs Any enum value accepted by a QFileDialog and its value. Values can be a string or list of strings, i.e. fileMode='AnyFile', options=['ShowDirsOnly', 'DontResolveSymlinks'] ============== ======================================================== """ itemClass = FileParameterItem def __init__(self, **opts): opts.setdefault('readonly', True) super().__init__(**opts) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/font.py000066400000000000000000000022731421045507400270440ustar00rootroot00000000000000from ...Qt import QtGui, QtWidgets from ..Parameter import Parameter from .basetypes import WidgetParameterItem class FontParameterItem(WidgetParameterItem): def makeWidget(self): w = QtWidgets.QFontComboBox() w.setMaximumHeight(20) w.sigChanged = w.currentFontChanged w.value = w.currentFont w.setValue = w.setCurrentFont self.hideWidget = False return w def updateDisplayLabel(self, value=None): if value is None: value = self.widget.currentText() super().updateDisplayLabel(value) class FontParameter(Parameter): """ Creates and controls a QFont value. Be careful when selecting options from the font dropdown. since not all fonts are available on all systems """ itemClass = FontParameterItem def _interpretValue(self, v): if isinstance(v, str): newVal = QtGui.QFont() if not newVal.fromString(v): raise ValueError(f'Error parsing font "{v}"') v = newVal return v def saveState(self, filter=None): state = super().saveState(filter) state['value'] = state['value'].toString() return state pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/list.py000066400000000000000000000107721421045507400270540ustar00rootroot00000000000000import warnings from collections import OrderedDict from ... import functions as fn from ...Qt import QtWidgets from ..Parameter import Parameter from .basetypes import WidgetParameterItem class ListParameterItem(WidgetParameterItem): """ WidgetParameterItem subclass providing comboBox that lets the user select from a list of options. """ def __init__(self, param, depth): self.targetValue = None WidgetParameterItem.__init__(self, param, depth) def makeWidget(self): w = QtWidgets.QComboBox() w.setMaximumHeight(20) ## set to match height of spin box and line edit w.sigChanged = w.currentIndexChanged w.value = self.value w.setValue = self.setValue self.widget = w ## needs to be set before limits are changed self.limitsChanged(self.param, self.param.opts['limits']) if len(self.forward) > 0: self.setValue(self.param.value()) return w def value(self): key = self.widget.currentText() return self.forward.get(key, None) def setValue(self, val): self.targetValue = val match = [fn.eq(val, limVal) for limVal in self.reverse[0]] if not any(match): self.widget.setCurrentIndex(0) else: idx = match.index(True) key = self.reverse[1][idx] ind = self.widget.findText(key) self.widget.setCurrentIndex(ind) def limitsChanged(self, param, limits): # set up forward / reverse mappings for name:value if len(limits) == 0: limits = [''] ## Can never have an empty list--there is always at least a singhe blank item. self.forward, self.reverse = ListParameter.mapping(limits) try: self.widget.blockSignals(True) val = self.targetValue self.widget.clear() for k in self.forward: self.widget.addItem(k) if k == val: self.widget.setCurrentIndex(self.widget.count()-1) self.updateDisplayLabel() finally: self.widget.blockSignals(False) def updateDisplayLabel(self, value=None): if value is None: value = self.widget.currentText() super().updateDisplayLabel(value) class ListParameter(Parameter): """Parameter with a list of acceptable values. By default, this parameter is represtented by a :class:`ListParameterItem`, displaying a combo box to select a value from the list. In addition to the generic :class:`~pyqtgraph.parametertree.Parameter` options, this parameter type accepts a ``limits`` argument specifying the list of allowed values. The values may generally be of any data type, as long as they can be represented as a string. If the string representation provided is undesirable, the values may be given as a dictionary mapping the desired string representation to the value. """ itemClass = ListParameterItem def __init__(self, **opts): self.forward = OrderedDict() ## {name: value, ...} self.reverse = ([], []) ## ([value, ...], [name, ...]) # Parameter uses 'limits' option to define the set of allowed values if 'values' in opts: warnings.warn('Using "values" to set limits is deprecated. Use "limits" instead.', DeprecationWarning, stacklevel=2) opts['limits'] = opts['values'] if opts.get('limits', None) is None: opts['limits'] = [] Parameter.__init__(self, **opts) self.setLimits(opts['limits']) def setLimits(self, limits): """Change the list of allowed values.""" self.forward, self.reverse = self.mapping(limits) Parameter.setLimits(self, limits) # 'value in limits' expression will break when reverse contains numpy array curVal = self.value() if len(self.reverse[0]) > 0 and not any(fn.eq(curVal, limVal) for limVal in self.reverse[0]): self.setValue(self.reverse[0][0]) @staticmethod def mapping(limits): # Return forward and reverse mapping objects given a limit specification forward = OrderedDict() ## {name: value, ...} reverse = ([], []) ## ([value, ...], [name, ...]) if not isinstance(limits, dict): limits = {str(l): l for l in limits} for k, v in limits.items(): forward[k] = v reverse[0].append(v) reverse[1].append(k) return forward, reverse pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/numeric.py000066400000000000000000000040441421045507400275360ustar00rootroot00000000000000from ...widgets.SpinBox import SpinBox from .basetypes import WidgetParameterItem class NumericParameterItem(WidgetParameterItem): """ Subclasses `WidgetParameterItem` to provide the following types: ========================== ============================================================= **Registered Types:** int Displays a :class:`SpinBox ` in integer mode. float Displays a :class:`SpinBox `. ========================== ============================================================= """ def makeWidget(self): opts = self.param.opts t = opts['type'] defs = { 'value': 0, 'min': None, 'max': None, 'step': 1.0, 'dec': False, 'siPrefix': False, 'suffix': '', 'decimals': 3, } if t == 'int': defs['int'] = True defs['minStep'] = 1.0 for k in defs: if k in opts: defs[k] = opts[k] if 'limits' in opts: defs['min'], defs['max'] = opts['limits'] w = SpinBox() w.setOpts(**defs) w.sigChanged = w.sigValueChanged w.sigChanging = w.sigValueChanging return w def updateDisplayLabel(self, value=None): if value is None: value = self.widget.lineEdit().text() super().updateDisplayLabel(value) def showEditor(self): super().showEditor() self.widget.selectNumber() # select the numerical portion of the text for quick editing def limitsChanged(self, param, limits): self.widget.setOpts(bounds=limits) def optsChanged(self, param, opts): super().optsChanged(param, opts) sbOpts = {} if 'units' in opts and 'suffix' not in opts: sbOpts['suffix'] = opts['units'] for k, v in opts.items(): if k in self.widget.opts: sbOpts[k] = v self.widget.setOpts(**sbOpts) self.updateDisplayLabel() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/pen.py000066400000000000000000000165761421045507400266730ustar00rootroot00000000000000import re from contextlib import ExitStack from . import GroupParameterItem from .basetypes import GroupParameter, Parameter, ParameterItem from .qtenum import QtEnumParameter from ... import functions as fn from ...Qt import QtCore from ...SignalProxy import SignalProxy from ...widgets.PenPreviewLabel import PenPreviewLabel class PenParameterItem(GroupParameterItem): def __init__(self, param, depth): super().__init__(param, depth) self.penLabel = PenPreviewLabel(param) def treeWidgetChanged(self): ParameterItem.treeWidgetChanged(self) tw = self.treeWidget() if tw is None: return tw.setItemWidget(self, 1, self.penLabel ) class PenParameter(GroupParameter): """ Controls the appearance of a QPen value. When `saveState` is called, the value is encoded as (color, width, style, capStyle, joinStyle, cosmetic) ============== ======================================================== **Options:** color pen color, can be any argument accepted by :func:`~pyqtgraph.mkColor` (defaults to black) width integer width >= 0 (defaults to 1) style String version of QPenStyle enum, i.e. 'SolidLine' (default), 'DashLine', etc. capStyle String version of QPenCapStyle enum, i.e. 'SquareCap' (default), 'RoundCap', etc. joinStyle String version of QPenJoinStyle enum, i.e. 'BevelJoin' (default), 'RoundJoin', etc. cosmetic Boolean, whether or not the pen is cosmetic (defaults to True) ============== ======================================================== """ itemClass = PenParameterItem def __init__(self, **opts): self.pen = fn.mkPen() children = self._makeChildren(self.pen) if 'children' in opts: raise KeyError('Cannot set "children" argument in Pen Parameter opts') super().__init__(**opts, children=list(children)) self.valChangingProxy = SignalProxy(self.sigValueChanging, delay=1.0, slot=self._childrenFinishedChanging) def _childrenFinishedChanging(self, paramAndValue): self.sigValueChanged.emit(*paramAndValue) def saveState(self, filter=None): state = super().saveState(filter) opts = state.pop('children') state['value'] = tuple(o['value'] for o in opts.values()) return state def restoreState(self, state, recursive=True, addChildren=True, removeChildren=True, blockSignals=True): return super().restoreState(state, recursive=False, addChildren=False, removeChildren=False, blockSignals=blockSignals) def _interpretValue(self, v): return self.mkPen(v) def setValue(self, value, blockSignal=None): if not fn.eq(value, self.pen): value = self.mkPen(value) self.updateFromPen(self, value) return super().setValue(self.pen, blockSignal) def applyOptsToPen(self, **opts): # Transform opts into a value for the current pen paramNames = set(opts).intersection(self.names) # Value should be overridden by opts with self.treeChangeBlocker(): if 'value' in opts: pen = self.mkPen(opts.pop('value')) if not fn.eq(pen, self.pen): self.updateFromPen(self, pen) penOpts = {} for kk in paramNames: penOpts[kk] = opts[kk] self[kk] = opts[kk] return penOpts def setOpts(self, **opts): # Transform opts into a value penOpts = self.applyOptsToPen(**opts) if penOpts: self.setValue(self.pen) return super().setOpts(**opts) def mkPen(self, *args, **kwargs): """Thin wrapper around fn.mkPen which accepts the serialized state from saveState""" if len(args) == 1 and isinstance(args[0], tuple) and len(args[0]) == len(self.childs): opts = dict(zip(self.names, args[0])) self.applyOptsToPen(**opts) args = (self.pen,) kwargs = {} return fn.mkPen(*args, **kwargs) def _makeChildren(self, boundPen=None): cs = QtCore.Qt.PenCapStyle js = QtCore.Qt.PenJoinStyle ps = QtCore.Qt.PenStyle param = Parameter.create( name='Params', type='group', children=[ dict(name='color', type='color', value='k'), dict(name='width', value=1, type='int', limits=[0, None]), QtEnumParameter(ps, name='style', value='SolidLine'), QtEnumParameter(cs, name='capStyle'), QtEnumParameter(js, name='joinStyle'), dict(name='cosmetic', type='bool', value=True) ] ) optsPen = boundPen or fn.mkPen() for p in param: name = p.name() # Qt naming scheme uses isXXX for booleans if isinstance(p.value(), bool): attrName = f'is{name.title()}' else: attrName = name default = getattr(optsPen, attrName)() replace = r'\1 \2' name = re.sub(r'(\w)([A-Z])', replace, name) name = name.title().strip() p.setOpts(title=name, default=default) def penPropertyWrapper(propertySetter): def tiePenPropToParam(_, value): propertySetter(value) self.sigValueChanging.emit(self, self.pen) return tiePenPropToParam if boundPen is not None: self.updateFromPen(param, boundPen) for p in param: setter, setName = self._setterForParam(p.name(), boundPen, returnName=True) # Instead, set the parameter which will signal the old setter setattr(boundPen, setName, p.setValue) newSetter = penPropertyWrapper(setter) # Edge case: color picker uses a dialog with user interaction, so wait until full change there if p.type() != 'color': p.sigValueChanging.connect(newSetter) # Force children to emulate self's value instead of being part of a tree like normal p.sigValueChanged.disconnect(p._emitValueChanged) # Some widgets (e.g. checkbox, combobox) don't emit 'changing' signals, so tie to 'changed' as well p.sigValueChanged.connect(newSetter) return param @staticmethod def _setterForParam(paramName, obj, returnName=False): formatted = paramName[0].upper() + paramName[1:] setter = getattr(obj, f'set{formatted}') if returnName: return setter, formatted return setter @staticmethod def updateFromPen(param, pen): """ Applies settings from a pen to either a Parameter or dict. The Parameter or dict must already be populated with the relevant keys that can be found in `PenSelectorDialog.mkParam`. """ stack = ExitStack() if isinstance(param, Parameter): names = param.names # Block changes until all are finalized stack.enter_context(param.treeChangeBlocker()) else: names = param for opt in names: # Booleans have different naming convention if isinstance(param[opt], bool): attrName = f'is{opt.title()}' else: attrName = opt param[opt] = getattr(pen, attrName)() stack.close() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/progress.py000066400000000000000000000007741421045507400277460ustar00rootroot00000000000000from ...Qt import QtWidgets from ..Parameter import Parameter from .basetypes import WidgetParameterItem class ProgressBarParameterItem(WidgetParameterItem): def makeWidget(self): w = QtWidgets.QProgressBar() w.setMaximumHeight(20) w.sigChanged = w.valueChanged self.hideWidget = False return w class ProgressBarParameter(Parameter): """ Displays a progress bar whose value can be set between 0 and 100 """ itemClass = ProgressBarParameterItem pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/qtenum.py000066400000000000000000000047631421045507400274150ustar00rootroot00000000000000from ...Qt import QT_LIB, QtCore from .list import ListParameter class QtEnumParameter(ListParameter): def __init__(self, enum, searchObj=QtCore.Qt, **opts): """ Constructs a list of allowed enum values from the enum class provided `searchObj` is only needed for PyQt5 compatibility, where it must be the module holding the enum. For instance, if making a QtEnumParameter out of QtWidgets.QFileDialog.Option, `searchObj` would be QtWidgets.QFileDialog """ self.enum = enum self.searchObj = searchObj opts.setdefault('name', enum.__name__) self.enumMap = self._getAllowedEnums(enum) opts.update(limits=self.formattedLimits()) super().__init__(**opts) def setValue(self, value, blockSignal=None): if isinstance(value, str): value = self.enumMap[value] super().setValue(value, blockSignal) def formattedLimits(self): # Title-cased words without the ending substring for brevity substringEnd = None mapping = self.enumMap shortestName = min(len(name) for name in mapping) names = list(mapping) cmpName, *names = names for ii in range(-1, -shortestName-1, -1): if any(cmpName[ii] != curName[ii] for curName in names): substringEnd = ii+1 break # Special case of 0: Set to none to avoid null string if substringEnd == 0: substringEnd = None limits = {} for kk, vv in self.enumMap.items(): limits[kk[:substringEnd]] = vv return limits def saveState(self, filter=None): state = super().saveState(filter) reverseMap = dict(zip(self.enumMap.values(), self.enumMap)) state['value'] = reverseMap[state['value']] return state def _getAllowedEnums(self, enum): """Pyside provides a dict for easy evaluation""" if 'PySide' in QT_LIB: vals = enum.values elif 'PyQt5' in QT_LIB: vals = {} for key in dir(self.searchObj): value = getattr(self.searchObj, key) if isinstance(value, enum): vals[key] = value elif 'PyQt6' in QT_LIB: vals = {e.name: e for e in enum} else: raise RuntimeError(f'Cannot find associated enum values for qt lib {QT_LIB}') # Remove "M" since it's not a real option vals.pop(f'M{enum.__name__}', None) return vals pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/slider.py000066400000000000000000000106341421045507400273600ustar00rootroot00000000000000import numpy as np from ...Qt import QtCore, QtWidgets from ..Parameter import Parameter from .basetypes import Emitter, WidgetParameterItem class SliderParameterItem(WidgetParameterItem): slider: QtWidgets.QSlider span: np.ndarray charSpan: np.ndarray def __init__(self, param, depth): # Bind emitter to self to avoid garbage collection self.emitter = Emitter() self.sigChanging = self.emitter.sigChanging self._suffix = None super().__init__(param, depth) def updateDisplayLabel(self, value=None): if value is None: value = self.param.value() value = str(value) if self._suffix is None: suffixTxt = '' else: suffixTxt = f' {self._suffix}' self.displayLabel.setText(value + suffixTxt) def setSuffix(self, suffix): self._suffix = suffix self._updateLabel(self.slider.value()) def makeWidget(self): param = self.param opts = param.opts opts.setdefault('limits', [0, 0]) self._suffix = opts.get('suffix') self.slider = QtWidgets.QSlider() self.slider.setOrientation(QtCore.Qt.Orientation.Horizontal) lbl = QtWidgets.QLabel() lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeft) w = QtWidgets.QWidget() layout = QtWidgets.QHBoxLayout() w.setLayout(layout) layout.addWidget(lbl) layout.addWidget(self.slider) def setValue(v): self.slider.setValue(self.spanToSliderValue(v)) def getValue(): return self.span[self.slider.value()].item() def vChanged(v): lbl.setText(self.prettyTextValue(v)) self.slider.valueChanged.connect(vChanged) def onMove(pos): self.sigChanging.emit(self, self.span[pos].item()) self.slider.sliderMoved.connect(onMove) w.setValue = setValue w.value = getValue w.sigChanged = self.slider.valueChanged w.sigChanging = self.sigChanging self.optsChanged(param, opts) return w def spanToSliderValue(self, v): return int(np.argmin(np.abs(self.span - v))) def prettyTextValue(self, v): if self._suffix is None: suffixTxt = '' else: suffixTxt = f' {self._suffix}' format_ = self.param.opts.get('format', None) cspan = self.charSpan if format_ is None: format_ = f'{{0:>{cspan.dtype.itemsize}}}{suffixTxt}' return format_.format(cspan[v].decode()) def optsChanged(self, param, opts): try: super().optsChanged(param, opts) except AttributeError: # This may trigger while building the parameter before the widget is fully constructed. # This is fine, since errors are from the parent scope which will stabilize after the widget is # constructed anyway pass span = opts.get('span', None) if span is None: step = opts.get('step', 1) start, stop = opts.get('limits', param.opts['limits']) # Add a bit to 'stop' since python slicing excludes the last value span = np.arange(start, stop + step, step) precision = opts.get('precision', 2) if precision is not None: span = span.round(precision) self.span = span self.charSpan = np.char.array(span) w = self.slider w.setMinimum(0) w.setMaximum(len(span) - 1) if 'suffix' in opts: self.setSuffix(opts['suffix']) self.slider.valueChanged.emit(self.slider.value()) def limitsChanged(self, param, limits): self.optsChanged(param, dict(limits=limits)) class SliderParameter(Parameter): """ ============== ======================================================== **Options** limits [start, stop] numbers step: Defaults to 1, the spacing between each slider tick span: Instead of limits + step, span can be set to specify the range of slider options (e.g. np.linspace(-pi, pi, 100)) format: Format string to determine number of decimals to show, etc. Defaults to display based on span dtype precision: int number of decimals to keep for float tick spaces ============== ======================================================== """ itemClass = SliderParameterItem pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/str.py000066400000000000000000000006611421045507400267050ustar00rootroot00000000000000from ...Qt import QtWidgets from .basetypes import WidgetParameterItem class StrParameterItem(WidgetParameterItem): """Registered parameter type which displays a QLineEdit""" def makeWidget(self): w = QtWidgets.QLineEdit() w.setStyleSheet('border: 0px') w.sigChanged = w.editingFinished w.value = w.text w.setValue = w.setText w.sigChanging = w.textChanged return w pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/parametertree/parameterTypes/text.py000066400000000000000000000012301421045507400270520ustar00rootroot00000000000000from ...Qt import QtCore, QtWidgets from ..Parameter import Parameter from .basetypes import WidgetParameterItem class TextParameterItem(WidgetParameterItem): """ParameterItem displaying a QTextEdit widget.""" def makeWidget(self): self.hideWidget = False self.asSubItem = True self.textBox = w = QtWidgets.QTextEdit() w.sizeHint = lambda: QtCore.QSize(300, 100) w.value = w.toPlainText w.setValue = w.setPlainText w.sigChanged = w.textChanged return w class TextParameter(Parameter): """Editable string, displayed as large text box in the tree.""" itemClass = TextParameterItem pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/pgcollections.py000066400000000000000000000364301421045507400231000ustar00rootroot00000000000000""" advancedTypes.py - Basic data structures not included with python Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. Includes: - OrderedDict - Dictionary which preserves the order of its elements - BiDict, ReverseDict - Bi-directional dictionaries - ThreadsafeDict, ThreadsafeList - Self-mutexed data structures """ import warnings warnings.warn( "None of these are used in pyqtgraph. Will be removed in 0.13", DeprecationWarning, stacklevel=2 ) import copy import threading from collections import OrderedDict try: from collections.abc import Sequence except ImportError: # fallback for python < 3.3 from collections import Sequence class ReverseDict(dict): """extends dict so that reverse lookups are possible by requesting the key as a list of length 1: d = BiDict({'x': 1, 'y': 2}) d['x'] 1 d[[2]] 'y' """ def __init__(self, data=None): if data is None: data = {} self.reverse = {} for k in data: self.reverse[data[k]] = k dict.__init__(self, data) def __getitem__(self, item): if type(item) is list: return self.reverse[item[0]] else: return dict.__getitem__(self, item) def __setitem__(self, item, value): self.reverse[value] = item dict.__setitem__(self, item, value) def __deepcopy__(self, memo): raise Exception("deepcopy not implemented") class BiDict(dict): """extends dict so that reverse lookups are possible by adding each reverse combination to the dict. This only works if all values and keys are unique.""" def __init__(self, data=None): if data is None: data = {} dict.__init__(self) for k in data: self[data[k]] = k def __setitem__(self, item, value): dict.__setitem__(self, item, value) dict.__setitem__(self, value, item) def __deepcopy__(self, memo): raise Exception("deepcopy not implemented") class ThreadsafeDict(dict): """Extends dict so that getitem, setitem, and contains are all thread-safe. Also adds lock/unlock functions for extended exclusive operations Converts all sub-dicts and lists to threadsafe as well. """ def __init__(self, *args, **kwargs): self.mutex = threading.RLock() dict.__init__(self, *args, **kwargs) for k in self: if type(self[k]) is dict: self[k] = ThreadsafeDict(self[k]) def __getitem__(self, attr): self.lock() try: val = dict.__getitem__(self, attr) finally: self.unlock() return val def __setitem__(self, attr, val): if type(val) is dict: val = ThreadsafeDict(val) self.lock() try: dict.__setitem__(self, attr, val) finally: self.unlock() def __contains__(self, attr): self.lock() try: val = dict.__contains__(self, attr) finally: self.unlock() return val def __len__(self): self.lock() try: val = dict.__len__(self) finally: self.unlock() return val def clear(self): self.lock() try: dict.clear(self) finally: self.unlock() def lock(self): self.mutex.acquire() def unlock(self): self.mutex.release() def __deepcopy__(self, memo): raise Exception("deepcopy not implemented") class ThreadsafeList(list): """Extends list so that getitem, setitem, and contains are all thread-safe. Also adds lock/unlock functions for extended exclusive operations Converts all sub-lists and dicts to threadsafe as well. """ def __init__(self, *args, **kwargs): self.mutex = threading.RLock() list.__init__(self, *args, **kwargs) for k in self: self[k] = mkThreadsafe(self[k]) def __getitem__(self, attr): self.lock() try: val = list.__getitem__(self, attr) finally: self.unlock() return val def __setitem__(self, attr, val): val = makeThreadsafe(val) self.lock() try: list.__setitem__(self, attr, val) finally: self.unlock() def __contains__(self, attr): self.lock() try: val = list.__contains__(self, attr) finally: self.unlock() return val def __len__(self): self.lock() try: val = list.__len__(self) finally: self.unlock() return val def lock(self): self.mutex.acquire() def unlock(self): self.mutex.release() def __deepcopy__(self, memo): raise Exception("deepcopy not implemented") def makeThreadsafe(obj): if type(obj) is dict: return ThreadsafeDict(obj) elif type(obj) is list: return ThreadsafeList(obj) elif type(obj) in [str, int, float, bool, tuple]: return obj else: raise Exception("Not sure how to make object of type %s thread-safe" % str(type(obj))) class Locker(object): def __init__(self, lock): self.lock = lock self.lock.acquire() def __del__(self): try: self.lock.release() except: pass class CaselessDict(OrderedDict): """Case-insensitive dict. Values can be set and retrieved using keys of any case. Note that when iterating, the original case is returned for each key.""" def __init__(self, *args): OrderedDict.__init__(self, {}) ## requirement for the empty {} here seems to be a python bug? self.keyMap = OrderedDict([(k.lower(), k) for k in OrderedDict.keys(self)]) if len(args) == 0: return elif len(args) == 1 and isinstance(args[0], dict): for k in args[0]: self[k] = args[0][k] else: raise Exception("CaselessDict may only be instantiated with a single dict.") #def keys(self): #return self.keyMap.values() def __setitem__(self, key, val): kl = key.lower() if kl in self.keyMap: OrderedDict.__setitem__(self, self.keyMap[kl], val) else: OrderedDict.__setitem__(self, key, val) self.keyMap[kl] = key def __getitem__(self, key): kl = key.lower() if kl not in self.keyMap: raise KeyError(key) return OrderedDict.__getitem__(self, self.keyMap[kl]) def __contains__(self, key): return key.lower() in self.keyMap def update(self, d): for k, v in d.items(): self[k] = v def copy(self): return CaselessDict(OrderedDict.copy(self)) def __delitem__(self, key): kl = key.lower() if kl not in self.keyMap: raise KeyError(key) OrderedDict.__delitem__(self, self.keyMap[kl]) del self.keyMap[kl] def __deepcopy__(self, memo): raise Exception("deepcopy not implemented") def clear(self): OrderedDict.clear(self) self.keyMap.clear() class ProtectedDict(dict): """ A class allowing read-only 'view' of a dict. The object can be treated like a normal dict, but will never modify the original dict it points to. Any values accessed from the dict will also be read-only. """ def __init__(self, data): self._data_ = data ## List of methods to directly wrap from _data_ wrapMethods = ['_cmp_', '__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'has_key', 'iterkeys', 'keys', ] ## List of methods which wrap from _data_ but return protected results protectMethods = ['__getitem__', '__iter__', 'get', 'items', 'values'] ## List of methods to disable disableMethods = ['__delitem__', '__setitem__', 'clear', 'pop', 'popitem', 'setdefault', 'update'] ## Template methods def wrapMethod(methodName): return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) def protectMethod(methodName): return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) def error(self, *args, **kargs): raise Exception("Can not modify read-only list.") ## Directly (and explicitly) wrap some methods from _data_ ## Many of these methods can not be intercepted using __getattribute__, so they ## must be implemented explicitly for methodName in wrapMethods: locals()[methodName] = wrapMethod(methodName) ## Wrap some methods from _data_ with the results converted to protected objects for methodName in protectMethods: locals()[methodName] = protectMethod(methodName) ## Disable any methods that could change data in the list for methodName in disableMethods: locals()[methodName] = error ## Add a few extra methods. def copy(self): raise Exception("It is not safe to copy protected dicts! (instead try deepcopy, but be careful.)") def itervalues(self): for v in self._data_.values(): yield protect(v) def iteritems(self): for k, v in self._data_.items(): yield (k, protect(v)) def deepcopy(self): return copy.deepcopy(self._data_) def __deepcopy__(self, memo): return copy.deepcopy(self._data_, memo) class ProtectedList(Sequence): """ A class allowing read-only 'view' of a list or dict. The object can be treated like a normal list, but will never modify the original list it points to. Any values accessed from the list will also be read-only. Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. However, doing this causes tuple(obj) to return unprotected results (importantly, this means unpacking into function arguments will also fail) """ def __init__(self, data): self._data_ = data #self.__mro__ = (ProtectedList, object) ## List of methods to directly wrap from _data_ wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] ## List of methods which wrap from _data_ but return protected results protectMethods = ['__getitem__', '__getslice__', '__mul__', '__reversed__', '__rmul__'] ## List of methods to disable disableMethods = ['__delitem__', '__delslice__', '__iadd__', '__imul__', '__setitem__', '__setslice__', 'append', 'extend', 'insert', 'pop', 'remove', 'reverse', 'sort'] ## Template methods def wrapMethod(methodName): return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) def protectMethod(methodName): return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) def error(self, *args, **kargs): raise Exception("Can not modify read-only list.") ## Directly (and explicitly) wrap some methods from _data_ ## Many of these methods can not be intercepted using __getattribute__, so they ## must be implemented explicitly for methodName in wrapMethods: locals()[methodName] = wrapMethod(methodName) ## Wrap some methods from _data_ with the results converted to protected objects for methodName in protectMethods: locals()[methodName] = protectMethod(methodName) ## Disable any methods that could change data in the list for methodName in disableMethods: locals()[methodName] = error ## Add a few extra methods. def __iter__(self): for item in self._data_: yield protect(item) def __add__(self, op): if isinstance(op, ProtectedList): return protect(self._data_.__add__(op._data_)) elif isinstance(op, list): return protect(self._data_.__add__(op)) else: raise TypeError("Argument must be a list.") def __radd__(self, op): if isinstance(op, ProtectedList): return protect(op._data_.__add__(self._data_)) elif isinstance(op, list): return protect(op.__add__(self._data_)) else: raise TypeError("Argument must be a list.") def deepcopy(self): return copy.deepcopy(self._data_) def __deepcopy__(self, memo): return copy.deepcopy(self._data_, memo) def poop(self): raise Exception("This is a list. It does not poop.") class ProtectedTuple(Sequence): """ A class allowing read-only 'view' of a tuple. The object can be treated like a normal tuple, but its contents will be returned as protected objects. Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. However, doing this causes tuple(obj) to return unprotected results (importantly, this means unpacking into function arguments will also fail) """ def __init__(self, data): self._data_ = data ## List of methods to directly wrap from _data_ wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__getnewargs__', '__gt__', '__hash__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] ## List of methods which wrap from _data_ but return protected results protectMethods = ['__getitem__', '__getslice__', '__iter__', '__add__', '__mul__', '__reversed__', '__rmul__'] ## Template methods def wrapMethod(methodName): return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) def protectMethod(methodName): return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) ## Directly (and explicitly) wrap some methods from _data_ ## Many of these methods can not be intercepted using __getattribute__, so they ## must be implemented explicitly for methodName in wrapMethods: locals()[methodName] = wrapMethod(methodName) ## Wrap some methods from _data_ with the results converted to protected objects for methodName in protectMethods: locals()[methodName] = protectMethod(methodName) ## Add a few extra methods. def deepcopy(self): return copy.deepcopy(self._data_) def __deepcopy__(self, memo): return copy.deepcopy(self._data_, memo) def protect(obj): if isinstance(obj, dict): return ProtectedDict(obj) elif isinstance(obj, list): return ProtectedList(obj) elif isinstance(obj, tuple): return ProtectedTuple(obj) else: return obj if __name__ == '__main__': d = {'x': 1, 'y': [1,2], 'z': ({'a': 2, 'b': [3,4], 'c': (5,6)}, 1, 2)} dp = protect(d) l = [1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}] lp = protect(l) t = (1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}) tp = protect(t) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/ptime.py000066400000000000000000000023111421045507400213400ustar00rootroot00000000000000""" ptime.py - Precision time function made os-independent (should have been taken care of by python) Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ import sys import warnings from time import perf_counter as clock from time import time as system_time START_TIME = None time = None def winTime(): """Return the current time in seconds with high precision (windows version, use Manager.time() to stay platform independent).""" warnings.warn( "'pg.time' will be removed from the library in the first release following January, 2022.", DeprecationWarning, stacklevel=2 ) return clock() + START_TIME def unixTime(): """Return the current time in seconds with high precision (unix version, use Manager.time() to stay platform independent).""" warnings.warn( "'pg.time' will be removed from the library in the first release following January, 2022.", DeprecationWarning, stacklevel=2 ) return system_time() if sys.platform.startswith('win'): cstart = clock() ### Required to start the clock in windows START_TIME = system_time() - cstart time = winTime else: time = unixTime pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/reload.py000066400000000000000000000277701421045507400215100ustar00rootroot00000000000000""" Magic Reload Library Luke Campagnola 2010 Python reload function that actually works (the way you expect it to) - No re-importing necessary - Modules can be reloaded in any order - Replaces functions and methods with their updated code - Changes instances to use updated classes - Automatically decides which modules to update by comparing file modification times Does NOT: - re-initialize exting instances, even if __init__ changes - update references to any module-level objects ie, this does not reload correctly: from module import someObject print someObject ..but you can use this instead: (this works even for the builtin reload) import module print module.someObject """ from __future__ import print_function import gc import inspect import os import sys import traceback import types from .debug import printExc try: from importlib import reload as orig_reload except ImportError: orig_reload = reload def reloadAll(prefix=None, debug=False): """Automatically reload all modules whose __file__ begins with *prefix*. Skips reload if the file has not been updated (if .pyc is newer than .py) If *prefix* is None, then all loaded modules are checked. Returns a dictionary {moduleName: (reloaded, reason)} describing actions taken for each module. """ failed = [] changed = [] ret = {} for modName, mod in list(sys.modules.items()): if not inspect.ismodule(mod): ret[modName] = (False, 'not a module') continue if modName == '__main__': ret[modName] = (False, 'ignored __main__') continue # Ignore modules without a __file__ that is .py or .pyc if getattr(mod, '__file__', None) is None: ret[modName] = (False, 'module has no __file__') continue if os.path.splitext(mod.__file__)[1] not in ['.py', '.pyc']: ret[modName] = (False, '%s not a .py/pyc file' % str(mod.__file__)) continue # Ignore if the file name does not start with prefix if prefix is not None and mod.__file__[:len(prefix)] != prefix: ret[modName] = (False, 'file %s not in prefix %s' % (mod.__file__, prefix)) continue py = os.path.splitext(mod.__file__)[0] + '.py' if py in changed: # already processed this module continue if not os.path.isfile(py): # skip modules that lie about their __file__ ret[modName] = (False, '.py does not exist: %s' % py) continue # if source file is newer than cache file, then it needs to be reloaded. pyc = getattr(mod, '__cached__', py + 'c') if not os.path.isfile(pyc): ret[modName] = (False, 'code has no pyc file to compare') continue if os.stat(pyc).st_mtime > os.stat(py).st_mtime: ret[modName] = (False, 'code has not changed since compile') continue # keep track of which modules have changed to ensure that duplicate-import modules get reloaded. changed.append(py) try: reload(mod, debug=debug) ret[modName] = (True, None) except Exception as exc: printExc("Error while reloading module %s, skipping\n" % mod) failed.append(mod.__name__) ret[modName] = (False, 'reload failed: %s' % traceback.format_exception_only(type(exc), exc)) if len(failed) > 0: raise Exception("Some modules failed to reload: %s" % ', '.join(failed)) return ret def reload(module, debug=False, lists=False, dicts=False): """Replacement for the builtin reload function: - Reloads the module as usual - Updates all old functions and class methods to use the new code - Updates all instances of each modified class to use the new class - Can update lists and dicts, but this is disabled by default - Requires that class and function names have not changed """ if debug: print("Reloading %s" % str(module)) ## make a copy of the old module dictionary, reload, then grab the new module dictionary for comparison oldDict = module.__dict__.copy() orig_reload(module) newDict = module.__dict__ ## Allow modules access to the old dictionary after they reload if hasattr(module, '__reload__'): module.__reload__(oldDict) ## compare old and new elements from each dict; update where appropriate for k in oldDict: old = oldDict[k] new = newDict.get(k, None) if old is new or new is None: continue if inspect.isclass(old): if debug: print(" Updating class %s.%s (0x%x -> 0x%x)" % (module.__name__, k, id(old), id(new))) updateClass(old, new, debug) # don't put this inside updateClass because it is reentrant. new.__previous_reload_version__ = old elif inspect.isfunction(old): depth = updateFunction(old, new, debug) if debug: extra = "" if depth > 0: extra = " (and %d previous versions)" % depth print(" Updating function %s.%s%s" % (module.__name__, k, extra)) elif lists and isinstance(old, list): l = old.len() old.extend(new) for i in range(l): old.pop(0) elif dicts and isinstance(old, dict): old.update(new) for j in old: if j not in new: del old[j] ## For functions: ## 1) update the code and defaults to new versions. ## 2) keep a reference to the previous version so ALL versions get updated for every reload def updateFunction(old, new, debug, depth=0, visited=None): #if debug and depth > 0: #print " -> also updating previous version", old, " -> ", new old.__code__ = new.__code__ old.__defaults__ = new.__defaults__ if hasattr(old, '__kwdefaults'): old.__kwdefaults__ = new.__kwdefaults__ old.__doc__ = new.__doc__ if visited is None: visited = [] if old in visited: return visited.append(old) ## finally, update any previous versions still hanging around.. if hasattr(old, '__previous_reload_version__'): maxDepth = updateFunction(old.__previous_reload_version__, new, debug, depth=depth+1, visited=visited) else: maxDepth = depth ## We need to keep a pointer to the previous version so we remember to update BOTH ## when the next reload comes around. if depth == 0: new.__previous_reload_version__ = old return maxDepth ## For classes: ## 1) find all instances of the old class and set instance.__class__ to the new class ## 2) update all old class methods to use code from the new class methods def updateClass(old, new, debug): ## Track town all instances and subclasses of old refs = gc.get_referrers(old) for ref in refs: try: if isinstance(ref, old) and ref.__class__ is old: ref.__class__ = new if debug: print(" Changed class for %s" % safeStr(ref)) elif inspect.isclass(ref) and issubclass(ref, old) and old in ref.__bases__: ind = ref.__bases__.index(old) ## Does not work: #ref.__bases__ = ref.__bases__[:ind] + (new,) + ref.__bases__[ind+1:] ## reason: Even though we change the code on methods, they remain bound ## to their old classes (changing im_class is not allowed). Instead, ## we have to update the __bases__ such that this class will be allowed ## as an argument to older methods. ## This seems to work. Is there any reason not to? ## Note that every time we reload, the class hierarchy becomes more complex. ## (and I presume this may slow things down?) newBases = ref.__bases__[:ind] + (new,old) + ref.__bases__[ind+1:] try: ref.__bases__ = newBases except TypeError: print(" Error setting bases for class %s" % ref) print(" old bases: %s" % repr(ref.__bases__)) print(" new bases: %s" % repr(newBases)) raise if debug: print(" Changed superclass for %s" % safeStr(ref)) #else: #if debug: #print " Ignoring reference", type(ref) except Exception: print("Error updating reference (%s) for class change (%s -> %s)" % (safeStr(ref), safeStr(old), safeStr(new))) raise ## update all class methods to use new code. ## Generally this is not needed since instances already know about the new class, ## but it fixes a few specific cases (pyqt signals, for one) for attr in dir(old): oa = getattr(old, attr) if inspect.isfunction(oa) or inspect.ismethod(oa): # note python2 has unbound methods, whereas python3 just uses plain functions try: na = getattr(new, attr) except AttributeError: if debug: print(" Skipping method update for %s; new class does not have this attribute" % attr) continue ofunc = getattr(oa, '__func__', oa) # in py2 we have to get the __func__ from unbound method, nfunc = getattr(na, '__func__', na) # in py3 the attribute IS the function if ofunc is not nfunc: depth = updateFunction(ofunc, nfunc, debug) if not hasattr(nfunc, '__previous_reload_method__'): nfunc.__previous_reload_method__ = oa # important for managing signal connection #oa.__class__ = new ## bind old method to new class ## not allowed if debug: extra = "" if depth > 0: extra = " (and %d previous versions)" % depth print(" Updating method %s%s" % (attr, extra)) ## And copy in new functions that didn't exist previously for attr in dir(new): if attr == '__previous_reload_version__': continue if not hasattr(old, attr): if debug: print(" Adding missing attribute %s" % attr) setattr(old, attr, getattr(new, attr)) ## finally, update any previous versions still hanging around.. if hasattr(old, '__previous_reload_version__'): updateClass(old.__previous_reload_version__, new, debug) ## It is possible to build classes for which str(obj) just causes an exception. ## Avoid thusly: def safeStr(obj): try: s = str(obj) except Exception: try: s = repr(obj) except Exception: s = "" % (safeStr(type(obj)), id(obj)) return s def getPreviousVersion(obj): """Return the previous version of *obj*, or None if this object has not been reloaded. """ if isinstance(obj, type) or inspect.isfunction(obj): return getattr(obj, '__previous_reload_version__', None) elif inspect.ismethod(obj): if obj.__self__ is None: # unbound method return getattr(obj.__func__, '__previous_reload_method__', None) else: oldmethod = getattr(obj.__func__, '__previous_reload_method__', None) if oldmethod is None: return None self = obj.__self__ oldfunc = getattr(oldmethod, '__func__', oldmethod) if hasattr(oldmethod, 'im_class'): # python 2 cls = oldmethod.im_class return types.MethodType(oldfunc, self, cls) else: # python 3 return types.MethodType(oldfunc, self) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/units.py000066400000000000000000000031401421045507400213650ustar00rootroot00000000000000# Very simple unit support: # - creates variable names like 'mV' and 'kHz' # - the value assigned to the variable corresponds to the scale prefix # (mV = 0.001) # - the actual units are purely cosmetic for making code clearer: # # x = 20*pA is identical to x = 20*1e-12 # # No unicode variable names (ΞΌ,Ξ©) allowed until python 3, but just assigning # them to the globals dict doesn't error in python 2. import unicodedata # All unicode identifiers get normalized automatically SI_PREFIXES = unicodedata.normalize("NFKC", u"yzafpnΒ΅m kMGTPEZY") UNITS = unicodedata.normalize("NFKC", u"m,s,g,W,J,V,A,F,T,Hz,Ohm,Ξ©,S,N,C,px,b,B,Pa").split(",") allUnits = {} def addUnit(prefix, val): g = globals() for u in UNITS: g[prefix + u] = val allUnits[prefix + u] = val for pre in SI_PREFIXES: v = SI_PREFIXES.index(pre) - 8 if pre == " ": pre = "" addUnit(pre, 1000 ** v) addUnit("c", 0.01) addUnit("d", 0.1) addUnit("da", 10) addUnit("h", 100) # py2 compatibility addUnit("u", 1e-6) def evalUnits(unitStr): """ Evaluate a unit string into ([numerators,...], [denominators,...]) Examples: N m/s^2 => ([N, m], [s, s]) A*s / V => ([A, s], [V,]) """ pass def formatUnits(units): """ Format a unit specification ([numerators,...], [denominators,...]) into a string (this is the inverse of evalUnits) """ pass def simplify(units): """ Cancel units that appear in both numerator and denominator, then attempt to replace groups of units with single units where possible (ie, J/s => W) """ pass pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/000077500000000000000000000000001421045507400206305ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/__init__.py000066400000000000000000000000001421045507400227270ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/colorama/000077500000000000000000000000001421045507400224255ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/colorama/LICENSE.txt000066400000000000000000000027231421045507400242540ustar00rootroot00000000000000Copyright (c) 2010 Jonathan Hartley All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holders, nor those of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/colorama/README.txt000066400000000000000000000242341421045507400241300ustar00rootroot00000000000000Download and docs: http://pypi.python.org/pypi/colorama Development: http://code.google.com/p/colorama Discussion group: https://groups.google.com/forum/#!forum/python-colorama Description =========== Makes ANSI escape character sequences for producing colored terminal text and cursor positioning work under MS Windows. ANSI escape character sequences have long been used to produce colored terminal text and cursor positioning on Unix and Macs. Colorama makes this work on Windows, too, by wrapping stdout, stripping ANSI sequences it finds (which otherwise show up as gobbledygook in your output), and converting them into the appropriate win32 calls to modify the state of the terminal. On other platforms, Colorama does nothing. Colorama also provides some shortcuts to help generate ANSI sequences but works fine in conjunction with any other ANSI sequence generation library, such as Termcolor (http://pypi.python.org/pypi/termcolor.) This has the upshot of providing a simple cross-platform API for printing colored terminal text from Python, and has the happy side-effect that existing applications or libraries which use ANSI sequences to produce colored output on Linux or Macs can now also work on Windows, simply by calling ``colorama.init()``. An alternative approach is to install 'ansi.sys' on Windows machines, which provides the same behaviour for all applications running in terminals. Colorama is intended for situations where that isn't easy (e.g. maybe your app doesn't have an installer.) Demo scripts in the source code repository prints some colored text using ANSI sequences. Compare their output under Gnome-terminal's built in ANSI handling, versus on Windows Command-Prompt using Colorama: .. image:: http://colorama.googlecode.com/hg/screenshots/ubuntu-demo.png :width: 661 :height: 357 :alt: ANSI sequences on Ubuntu under gnome-terminal. .. image:: http://colorama.googlecode.com/hg/screenshots/windows-demo.png :width: 668 :height: 325 :alt: Same ANSI sequences on Windows, using Colorama. These screengrabs show that Colorama on Windows does not support ANSI 'dim text': it looks the same as 'normal text'. License ======= Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. Dependencies ============ None, other than Python. Tested on Python 2.5.5, 2.6.5, 2.7, 3.1.2, and 3.2 Usage ===== Initialisation -------------- Applications should initialise Colorama using:: from colorama import init init() If you are on Windows, the call to ``init()`` will start filtering ANSI escape sequences out of any text sent to stdout or stderr, and will replace them with equivalent Win32 calls. Calling ``init()`` has no effect on other platforms (unless you request other optional functionality, see keyword args below.) The intention is that applications can call ``init()`` unconditionally on all platforms, after which ANSI output should just work. To stop using colorama before your program exits, simply call ``deinit()``. This will restore stdout and stderr to their original values, so that Colorama is disabled. To start using Colorama again, call ``reinit()``, which wraps stdout and stderr again, but is cheaper to call than doing ``init()`` all over again. Colored Output -------------- Cross-platform printing of colored text can then be done using Colorama's constant shorthand for ANSI escape sequences:: from colorama import Fore, Back, Style print(Fore.RED + 'some red text') print(Back.GREEN + 'and with a green background') print(Style.DIM + 'and in dim text') print(Fore.RESET + Back.RESET + Style.RESET_ALL) print('back to normal now') or simply by manually printing ANSI sequences from your own code:: print('/033[31m' + 'some red text') print('/033[30m' # and reset to default color) or Colorama can be used happily in conjunction with existing ANSI libraries such as Termcolor:: from colorama import init from termcolor import colored # use Colorama to make Termcolor work on Windows too init() # then use Termcolor for all colored text output print(colored('Hello, World!', 'green', 'on_red')) Available formatting constants are:: Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. Style: DIM, NORMAL, BRIGHT, RESET_ALL Style.RESET_ALL resets foreground, background and brightness. Colorama will perform this reset automatically on program exit. Cursor Positioning ------------------ ANSI codes to reposition the cursor are supported. See demos/demo06.py for an example of how to generate them. Init Keyword Args ----------------- ``init()`` accepts some kwargs to override default behaviour. init(autoreset=False): If you find yourself repeatedly sending reset sequences to turn off color changes at the end of every print, then ``init(autoreset=True)`` will automate that:: from colorama import init init(autoreset=True) print(Fore.RED + 'some red text') print('automatically back to default color again') init(strip=None): Pass ``True`` or ``False`` to override whether ansi codes should be stripped from the output. The default behaviour is to strip if on Windows. init(convert=None): Pass ``True`` or ``False`` to override whether to convert ansi codes in the output into win32 calls. The default behaviour is to convert if on Windows and output is to a tty (terminal). init(wrap=True): On Windows, colorama works by replacing ``sys.stdout`` and ``sys.stderr`` with proxy objects, which override the .write() method to do their work. If this wrapping causes you problems, then this can be disabled by passing ``init(wrap=False)``. The default behaviour is to wrap if autoreset or strip or convert are True. When wrapping is disabled, colored printing on non-Windows platforms will continue to work as normal. To do cross-platform colored output, you can use Colorama's ``AnsiToWin32`` proxy directly:: import sys from colorama import init, AnsiToWin32 init(wrap=False) stream = AnsiToWin32(sys.stderr).stream # Python 2 print >>stream, Fore.BLUE + 'blue text on stderr' # Python 3 print(Fore.BLUE + 'blue text on stderr', file=stream) Status & Known Problems ======================= I've personally only tested it on WinXP (CMD, Console2), Ubuntu (gnome-terminal, xterm), and OSX. Some presumably valid ANSI sequences aren't recognised (see details below) but to my knowledge nobody has yet complained about this. Puzzling. See outstanding issues and wishlist at: http://code.google.com/p/colorama/issues/list If anything doesn't work for you, or doesn't do what you expected or hoped for, I'd love to hear about it on that issues list, would be delighted by patches, and would be happy to grant commit access to anyone who submits a working patch or two. Recognised ANSI Sequences ========================= ANSI sequences generally take the form: ESC [ ; ... Where is an integer, and is a single letter. Zero or more params are passed to a . If no params are passed, it is generally synonymous with passing a single zero. No spaces exist in the sequence, they have just been inserted here to make it easy to read. The only ANSI sequences that colorama converts into win32 calls are:: ESC [ 0 m # reset all (colors and brightness) ESC [ 1 m # bright ESC [ 2 m # dim (looks same as normal brightness) ESC [ 22 m # normal brightness # FOREGROUND: ESC [ 30 m # black ESC [ 31 m # red ESC [ 32 m # green ESC [ 33 m # yellow ESC [ 34 m # blue ESC [ 35 m # magenta ESC [ 36 m # cyan ESC [ 37 m # white ESC [ 39 m # reset # BACKGROUND ESC [ 40 m # black ESC [ 41 m # red ESC [ 42 m # green ESC [ 43 m # yellow ESC [ 44 m # blue ESC [ 45 m # magenta ESC [ 46 m # cyan ESC [ 47 m # white ESC [ 49 m # reset # cursor positioning ESC [ y;x H # position cursor at x across, y down # clear the screen ESC [ mode J # clear the screen. Only mode 2 (clear entire screen) # is supported. It should be easy to add other modes, # let me know if that would be useful. Multiple numeric params to the 'm' command can be combined into a single sequence, eg:: ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background All other ANSI sequences of the form ``ESC [ ; ... `` are silently stripped from the output on Windows. Any other form of ANSI sequence, such as single-character codes or alternative initial characters, are not recognised nor stripped. It would be cool to add them though. Let me know if it would be useful for you, via the issues on google code. Development =========== Help and fixes welcome! Ask Jonathan for commit rights, you'll get them. Running tests requires: - Michael Foord's 'mock' module to be installed. - Tests are written using the 2010 era updates to 'unittest', and require to be run either using Python2.7 or greater, or else to have Michael Foord's 'unittest2' module installed. unittest2 test discovery doesn't work for colorama, so I use 'nose':: nosetests -s The -s is required because 'nosetests' otherwise applies a proxy of its own to stdout, which confuses the unit tests. Contact ======= Created by Jonathan Hartley, tartley@tartley.com Thanks ====== | Ben Hoyt, for a magnificent fix under 64-bit Windows. | Jesse@EmptySquare for submitting a fix for examples in the README. | User 'jamessp', an observant documentation fix for cursor positioning. | User 'vaal1239', Dave Mckee & Lackner Kristof for a tiny but much-needed Win7 fix. | Julien Stuyck, for wisely suggesting Python3 compatible updates to README. | Daniel Griffith for multiple fabulous patches. | Oscar Lesta for valuable fix to stop ANSI chars being sent to non-tty output. | Roger Binns, for many suggestions, valuable feedback, & bug reports. | Tim Golden for thought and much appreciated feedback on the initial idea. pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/colorama/__init__.py000066400000000000000000000000001421045507400245240ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/colorama/win32.py000066400000000000000000000114451421045507400237460ustar00rootroot00000000000000# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. # from winbase.h STDOUT = -11 STDERR = -12 try: from ctypes import windll from ctypes import wintypes except ImportError: windll = None SetConsoleTextAttribute = lambda *_: None else: from ctypes import ( byref, Structure, c_char, c_short, c_int, c_uint32, c_ushort, c_void_p, POINTER ) class CONSOLE_SCREEN_BUFFER_INFO(Structure): """struct in wincon.h.""" _fields_ = [ ("dwSize", wintypes._COORD), ("dwCursorPosition", wintypes._COORD), ("wAttributes", wintypes.WORD), ("srWindow", wintypes.SMALL_RECT), ("dwMaximumWindowSize", wintypes._COORD), ] def __str__(self): return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( self.dwSize.Y, self.dwSize.X , self.dwCursorPosition.Y, self.dwCursorPosition.X , self.wAttributes , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X ) _GetStdHandle = windll.kernel32.GetStdHandle _GetStdHandle.argtypes = [ wintypes.DWORD, ] _GetStdHandle.restype = wintypes.HANDLE _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo _GetConsoleScreenBufferInfo.argtypes = [ wintypes.HANDLE, c_void_p, #POINTER(CONSOLE_SCREEN_BUFFER_INFO), ] _GetConsoleScreenBufferInfo.restype = wintypes.BOOL _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute _SetConsoleTextAttribute.argtypes = [ wintypes.HANDLE, wintypes.WORD, ] _SetConsoleTextAttribute.restype = wintypes.BOOL _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition _SetConsoleCursorPosition.argtypes = [ wintypes.HANDLE, c_int, #wintypes._COORD, ] _SetConsoleCursorPosition.restype = wintypes.BOOL _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA _FillConsoleOutputCharacterA.argtypes = [ wintypes.HANDLE, c_char, wintypes.DWORD, wintypes._COORD, POINTER(wintypes.DWORD), ] _FillConsoleOutputCharacterA.restype = wintypes.BOOL _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute _FillConsoleOutputAttribute.argtypes = [ wintypes.HANDLE, wintypes.WORD, wintypes.DWORD, c_int, #wintypes._COORD, POINTER(wintypes.DWORD), ] _FillConsoleOutputAttribute.restype = wintypes.BOOL handles = { STDOUT: _GetStdHandle(STDOUT), STDERR: _GetStdHandle(STDERR), } def GetConsoleScreenBufferInfo(stream_id=STDOUT): handle = handles[stream_id] csbi = CONSOLE_SCREEN_BUFFER_INFO() success = _GetConsoleScreenBufferInfo( handle, byref(csbi)) return csbi def SetConsoleTextAttribute(stream_id, attrs): handle = handles[stream_id] return _SetConsoleTextAttribute(handle, attrs) def SetConsoleCursorPosition(stream_id, position): position = wintypes._COORD(*position) # If the position is out of range, do nothing. if position.Y <= 0 or position.X <= 0: return # Adjust for Windows' SetConsoleCursorPosition: # 1. being 0-based, while ANSI is 1-based. # 2. expecting (x,y), while ANSI uses (y,x). adjusted_position = wintypes._COORD(position.Y - 1, position.X - 1) # Adjust for viewport's scroll position sr = GetConsoleScreenBufferInfo(STDOUT).srWindow adjusted_position.Y += sr.Top adjusted_position.X += sr.Left # Resume normal processing handle = handles[stream_id] return _SetConsoleCursorPosition(handle, adjusted_position) def FillConsoleOutputCharacter(stream_id, char, length, start): handle = handles[stream_id] char = c_char(char) length = wintypes.DWORD(length) num_written = wintypes.DWORD(0) # Note that this is hard-coded for ANSI (vs wide) bytes. success = _FillConsoleOutputCharacterA( handle, char, length, start, byref(num_written)) return num_written.value def FillConsoleOutputAttribute(stream_id, attr, length, start): ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' handle = handles[stream_id] attribute = wintypes.WORD(attr) length = wintypes.DWORD(length) num_written = wintypes.DWORD(0) # Note that this is hard-coded for ANSI (vs wide) bytes. return _FillConsoleOutputAttribute( handle, attribute, length, start, byref(num_written)) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/colorama/winterm.py000066400000000000000000000101551421045507400244660ustar00rootroot00000000000000# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. from . import win32 # from wincon.h class WinColor(object): BLACK = 0 BLUE = 1 GREEN = 2 CYAN = 3 RED = 4 MAGENTA = 5 YELLOW = 6 GREY = 7 # from wincon.h class WinStyle(object): NORMAL = 0x00 # dim text, dim background BRIGHT = 0x08 # bright text, dim background class WinTerm(object): def __init__(self): self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes self.set_attrs(self._default) self._default_fore = self._fore self._default_back = self._back self._default_style = self._style def get_attrs(self): return self._fore + self._back * 16 + self._style def set_attrs(self, value): self._fore = value & 7 self._back = (value >> 4) & 7 self._style = value & WinStyle.BRIGHT def reset_all(self, on_stderr=None): self.set_attrs(self._default) self.set_console(attrs=self._default) def fore(self, fore=None, on_stderr=False): if fore is None: fore = self._default_fore self._fore = fore self.set_console(on_stderr=on_stderr) def back(self, back=None, on_stderr=False): if back is None: back = self._default_back self._back = back self.set_console(on_stderr=on_stderr) def style(self, style=None, on_stderr=False): if style is None: style = self._default_style self._style = style self.set_console(on_stderr=on_stderr) def set_console(self, attrs=None, on_stderr=False): if attrs is None: attrs = self.get_attrs() handle = win32.STDOUT if on_stderr: handle = win32.STDERR win32.SetConsoleTextAttribute(handle, attrs) def get_position(self, handle): position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition # Because Windows coordinates are 0-based, # and win32.SetConsoleCursorPosition expects 1-based. position.X += 1 position.Y += 1 return position def set_cursor_position(self, position=None, on_stderr=False): if position is None: #I'm not currently tracking the position, so there is no default. #position = self.get_position() return handle = win32.STDOUT if on_stderr: handle = win32.STDERR win32.SetConsoleCursorPosition(handle, position) def cursor_up(self, num_rows=0, on_stderr=False): if num_rows == 0: return handle = win32.STDOUT if on_stderr: handle = win32.STDERR position = self.get_position(handle) adjusted_position = (position.Y - num_rows, position.X) self.set_cursor_position(adjusted_position, on_stderr) def erase_data(self, mode=0, on_stderr=False): # 0 (or None) should clear from the cursor to the end of the screen. # 1 should clear from the cursor to the beginning of the screen. # 2 should clear the entire screen. (And maybe move cursor to (1,1)?) # # At the moment, I only support mode 2. From looking at the API, it # should be possible to calculate a different number of bytes to clear, # and to do so relative to the cursor position. if mode[0] not in (2,): return handle = win32.STDOUT if on_stderr: handle = win32.STDERR # here's where we'll home the cursor coord_screen = win32.COORD(0,0) csbi = win32.GetConsoleScreenBufferInfo(handle) # get the number of character cells in the current buffer dw_con_size = csbi.dwSize.X * csbi.dwSize.Y # fill the entire screen with blanks win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen) # now set the buffer's attributes accordingly win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen ) # put the cursor at (0, 0) win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y)) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/cprint.py000066400000000000000000000056171421045507400225120ustar00rootroot00000000000000""" Cross-platform color text printing Based on colorama (see pyqtgraph/util/colorama/README.txt) """ import sys from .colorama.win32 import windll from .colorama.winterm import WinColor, WinStyle, WinTerm _WIN = sys.platform.startswith('win') if windll is not None: winterm = WinTerm() else: _WIN = False def winset(reset=False, fore=None, back=None, style=None, stderr=False): if reset: winterm.reset_all() if fore is not None: winterm.fore(fore, stderr) if back is not None: winterm.back(back, stderr) if style is not None: winterm.style(style, stderr) ANSI = {} WIN = {} for i,color in enumerate(['BLACK', 'RED', 'GREEN', 'YELLOW', 'BLUE', 'MAGENTA', 'CYAN', 'WHITE']): globals()[color] = i globals()['BR_' + color] = i + 8 globals()['BACK_' + color] = i + 40 ANSI[i] = "\033[%dm" % (30+i) ANSI[i+8] = "\033[2;%dm" % (30+i) ANSI[i+40] = "\033[%dm" % (40+i) color = 'GREY' if color == 'WHITE' else color WIN[i] = {'fore': getattr(WinColor, color), 'style': WinStyle.NORMAL} WIN[i+8] = {'fore': getattr(WinColor, color), 'style': WinStyle.BRIGHT} WIN[i+40] = {'back': getattr(WinColor, color)} RESET = -1 ANSI[RESET] = "\033[0m" WIN[RESET] = {'reset': True} def cprint(stream, *args, **kwds): """ Print with color. Examples:: # colors are BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE cprint('stdout', RED, 'This is in red. ', RESET, 'and this is normal\n') # Adding BR_ before the color manes it bright cprint('stdout', BR_GREEN, 'This is bright green.\n', RESET) # Adding BACK_ changes background color cprint('stderr', BACK_BLUE, WHITE, 'This is white-on-blue.', -1) # Integers 0-7 for normal, 8-15 for bright, and 40-47 for background. # -1 to reset. cprint('stderr', 1, 'This is in red.', -1) """ if isinstance(stream, str): stream = kwds.get('stream', 'stdout') err = stream == 'stderr' stream = getattr(sys, stream) else: err = kwds.get('stderr', False) if hasattr(stream, 'isatty') and stream.isatty(): if _WIN: # convert to win32 calls for arg in args: if isinstance(arg, str): stream.write(arg) else: kwds = WIN[arg] winset(stderr=err, **kwds) else: # convert to ANSI for arg in args: if isinstance(arg, str): stream.write(arg) else: stream.write(ANSI[arg]) else: # ignore colors for arg in args: if isinstance(arg, str): stream.write(arg) def cout(*args): """Shorthand for cprint('stdout', ...)""" cprint('stdout', *args) def cerr(*args): """Shorthand for cprint('stderr', ...)""" cprint('stderr', *args) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/cupy_helper.py000066400000000000000000000010131421045507400235140ustar00rootroot00000000000000import os from warnings import warn from .. import getConfigOption def getCupy(): if getConfigOption("useCupy"): try: import cupy except ImportError: warn("cupy library could not be loaded, but 'useCupy' is set.") return None if os.name == "nt" and cupy.cuda.runtime.runtimeGetVersion() < 11000: warn("In Windows, CUDA toolkit should be version 11 or higher, or some functions may misbehave.") return cupy else: return None pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/garbage_collector.py000066400000000000000000000031001421045507400246320ustar00rootroot00000000000000import gc from ..Qt import QtCore class GarbageCollector(object): ''' Disable automatic garbage collection and instead collect manually on a timer. This is done to ensure that garbage collection only happens in the GUI thread, as otherwise Qt can crash. Credit: Erik Janssens Source: http://pydev.blogspot.com/2014/03/should-python-garbage-collector-be.html ''' def __init__(self, interval=1.0, debug=False): self.debug = debug if debug: gc.set_debug(gc.DEBUG_LEAK) self.timer = QtCore.QTimer() self.timer.timeout.connect(self.check) self.threshold = gc.get_threshold() gc.disable() self.timer.start(interval * 1000) def check(self): #return self.debug_cycles() # uncomment to just debug cycles l0, l1, l2 = gc.get_count() if self.debug: print('gc_check called:', l0, l1, l2) if l0 > self.threshold[0]: num = gc.collect(0) if self.debug: print('collecting gen 0, found: %d unreachable' % num) if l1 > self.threshold[1]: num = gc.collect(1) if self.debug: print('collecting gen 1, found: %d unreachable' % num) if l2 > self.threshold[2]: num = gc.collect(2) if self.debug: print('collecting gen 2, found: %d unreachable' % num) def debug_cycles(self): gc.collect() for obj in gc.garbage: print(obj, repr(obj), type(obj)) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/get_resolution.py000066400000000000000000000012121421045507400242400ustar00rootroot00000000000000from .. import mkQApp from ..Qt import QtGui def test_screenInformation(): # a qApp is still needed, otherwise screen is None qApp = mkQApp() screen = QtGui.QGuiApplication.primaryScreen() screens = QtGui.QGuiApplication.screens() resolution = screen.size() availableResolution = screen.availableSize() print("Screen resolution: {}x{}".format(resolution.width(), resolution.height())) print("Available geometry: {}x{}".format(availableResolution.width(), availableResolution.height())) print("Number of Screens: {}".format(len(screens))) return None if __name__ == "__main__": test_screenInformation() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/glinfo.py000066400000000000000000000022501421045507400224570ustar00rootroot00000000000000import importlib from ..Qt import QT_LIB, QtGui GL_VENDOR = 7936 GL_RENDERER = 7937 GL_VERSION = 7938 def print_version(funcs): glGetString = funcs.glGetString print('VENDOR:', glGetString(GL_VENDOR)) print('RENDERER:', glGetString(GL_RENDERER)) print('VERSION:', glGetString(GL_VERSION)) def print_extensions(ctx): extensions = sorted([ext.data().decode() for ext in ctx.extensions()]) print("Extensions:") for ext in extensions: print(f" {ext}") app = QtGui.QGuiApplication([]) surf = QtGui.QOffscreenSurface() surf.create() ctx = QtGui.QOpenGLContext() ctx.create() ctx.makeCurrent(surf) if QT_LIB == 'PySide2': funcs = ctx.functions() elif QT_LIB == 'PyQt5': profile = QtGui.QOpenGLVersionProfile() profile.setVersion(2, 0) funcs = ctx.versionFunctions(profile) elif QT_LIB in ['PyQt6', 'PySide6']: QtOpenGL = importlib.import_module(f'{QT_LIB}.QtOpenGL') profile = QtOpenGL.QOpenGLVersionProfile() profile.setVersion(2, 0) funcs_factory = QtOpenGL.QOpenGLVersionFunctionsFactory() funcs = funcs_factory.get(profile, ctx) print('isOpenGLES:', ctx.isOpenGLES()) print_version(funcs) print_extensions(ctx) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/lru_cache.py000066400000000000000000000057461421045507400231430ustar00rootroot00000000000000import warnings warnings.warn( "No longer used in pyqtgraph. Will be removed in 0.13", DeprecationWarning, stacklevel=2 ) import itertools import operator class LRUCache(object): ''' This LRU cache should be reasonable for short collections (until around 100 items), as it does a sort on the items if the collection would become too big (so, it is very fast for getting and setting but when its size would become higher than the max size it does one sort based on the internal time to decide which items should be removed -- which should be Ok if the resizeTo isn't too close to the maxSize so that it becomes an operation that doesn't happen all the time). ''' def __init__(self, maxSize=100, resizeTo=70): ''' ============== ========================================================= **Arguments:** maxSize (int) This is the maximum size of the cache. When some item is added and the cache would become bigger than this, it's resized to the value passed on resizeTo. resizeTo (int) When a resize operation happens, this is the size of the final cache. ============== ========================================================= ''' assert resizeTo < maxSize self.maxSize = maxSize self.resizeTo = resizeTo self._counter = 0 self._dict = {} self._nextTime = itertools.count(0).__next__ def __getitem__(self, key): item = self._dict[key] item[2] = self._nextTime() return item[1] def __len__(self): return len(self._dict) def __setitem__(self, key, value): item = self._dict.get(key) if item is None: if len(self._dict) + 1 > self.maxSize: self._resizeTo() item = [key, value, self._nextTime()] self._dict[key] = item else: item[1] = value item[2] = self._nextTime() def __delitem__(self, key): del self._dict[key] def get(self, key, default=None): try: return self[key] except KeyError: return default def clear(self): self._dict.clear() def values(self): return [i[1] for i in self._dict.values()] def keys(self): return [x[0] for x in self._dict.values()] def _resizeTo(self): ordered = sorted(self._dict.values(), key=operator.itemgetter(2))[:self.resizeTo] for i in ordered: del self._dict[i[0]] def items(self, accessTime=False): ''' :param bool accessTime: If True sorts the returned items by the internal access time. ''' if accessTime: for x in sorted(self._dict.values(), key=operator.itemgetter(2)): yield x[0], x[1] else: for x in self._dict.items(): yield x[0], x[1] pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/mutex.py000066400000000000000000000065621421045507400223550ustar00rootroot00000000000000import traceback from ..Qt import QtCore class Mutex(QtCore.QMutex): """ Subclass of QMutex that provides useful debugging information during deadlocks--tracebacks are printed for both the code location that is attempting to lock the mutex as well as the location that has already acquired the lock. Also provides __enter__ and __exit__ methods for use in "with" statements. """ def __init__(self, *args, **kargs): if kargs.get('recursive', False): args = (QtCore.QMutex.Recursive,) QtCore.QMutex.__init__(self, *args) self.l = QtCore.QMutex() ## for serializing access to self.tb self.tb = [] self.debug = kargs.pop('debug', False) ## True to enable debugging functions def tryLock(self, timeout=None, id=None): if timeout is None: locked = QtCore.QMutex.tryLock(self) else: locked = QtCore.QMutex.tryLock(self, timeout) if self.debug and locked: self.l.lock() try: if id is None: self.tb.append(''.join(traceback.format_stack()[:-1])) else: self.tb.append(" " + str(id)) #print 'trylock', self, len(self.tb) finally: self.l.unlock() return locked def lock(self, id=None): c = 0 waitTime = 5000 # in ms while True: if self.tryLock(waitTime, id): break c += 1 if self.debug: self.l.lock() try: print("Waiting for mutex lock (%0.1f sec). Traceback follows:" % (c*waitTime/1000.)) traceback.print_stack() if len(self.tb) > 0: print("Mutex is currently locked from:\n") print(self.tb[-1]) else: print("Mutex is currently locked from [???]") finally: self.l.unlock() #print 'lock', self, len(self.tb) def unlock(self): QtCore.QMutex.unlock(self) if self.debug: self.l.lock() try: #print 'unlock', self, len(self.tb) if len(self.tb) > 0: self.tb.pop() else: raise Exception("Attempt to unlock mutex before it has been locked") finally: self.l.unlock() def acquire(self, blocking=True): """Mimics threading.Lock.acquire() to allow this class as a drop-in replacement. """ return self.tryLock() def release(self): """Mimics threading.Lock.release() to allow this class as a drop-in replacement. """ self.unlock() def depth(self): self.l.lock() n = len(self.tb) self.l.unlock() return n def traceback(self): self.l.lock() try: ret = self.tb[:] finally: self.l.unlock() return ret def __exit__(self, *args): self.unlock() def __enter__(self): self.lock() return self class RecursiveMutex(Mutex): """Mimics threading.RLock class. """ def __init__(self, **kwds): kwds['recursive'] = True Mutex.__init__(self, **kwds) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/numba_helper.py000066400000000000000000000006161421045507400236460ustar00rootroot00000000000000from warnings import warn from .. import getConfigOption def getNumbaFunctions(): if getConfigOption("useNumba"): try: import numba # noqa except ImportError: warn("numba library could not be loaded, but 'useNumba' is set.") return None from .. import functions_numba return functions_numba else: return None pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/util/pil_fix.py000066400000000000000000000041541421045507400226400ustar00rootroot00000000000000""" Importing this module installs support for 16-bit images in PIL. This works by patching objects in the PIL namespace; no files are modified. """ import warnings warnings.warn( "Not used in pyqtgraph. Will be removed in 0.13", DeprecationWarning, stacklevel=2 ) from PIL import Image if Image.VERSION == '1.1.7': Image._MODE_CONV["I;16"] = ('%su2' % Image._ENDIAN, None) Image._fromarray_typemap[((1, 1), " ndmax: raise ValueError("Too many dimensions.") size = shape[:2][::-1] if strides is not None: obj = obj.tostring() return frombuffer(mode, size, obj, "raw", mode, 0, 1) Image.fromarray=fromarray pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/000077500000000000000000000000001421045507400213215ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/BusyCursor.py000066400000000000000000000013711421045507400240150ustar00rootroot00000000000000from contextlib import contextmanager from ..Qt import QtCore, QtGui, QtWidgets __all__ = ["BusyCursor"] @contextmanager def BusyCursor(): """ Display a busy mouse cursor during long operations. Usage:: with BusyCursor(): doLongOperation() May be nested. If called from a non-gui thread, then the cursor will not be affected. """ app = QtCore.QCoreApplication.instance() in_gui_thread = (app is not None) and (QtCore.QThread.currentThread() == app.thread()) try: if in_gui_thread: QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor)) yield finally: if in_gui_thread: QtWidgets.QApplication.restoreOverrideCursor() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/CheckTable.py000066400000000000000000000064451421045507400236710ustar00rootroot00000000000000from ..Qt import QtCore, QtWidgets from . import VerticalLabel __all__ = ['CheckTable'] class CheckTable(QtWidgets.QWidget): sigStateChanged = QtCore.Signal(object, object, object) # (row, col, state) def __init__(self, columns): QtWidgets.QWidget.__init__(self) self.layout = QtWidgets.QGridLayout() self.layout.setSpacing(0) self.setLayout(self.layout) self.headers = [] self.columns = columns col = 1 for c in columns: label = VerticalLabel.VerticalLabel(c, orientation='vertical') self.headers.append(label) self.layout.addWidget(label, 0, col) col += 1 self.rowNames = [] self.rowWidgets = [] self.oldRows = {} ## remember settings from removed rows; reapply if they reappear. def updateRows(self, rows): for r in self.rowNames[:]: if r not in rows: self.removeRow(r) for r in rows: if r not in self.rowNames: self.addRow(r) def addRow(self, name): label = QtWidgets.QLabel(name) row = len(self.rowNames)+1 self.layout.addWidget(label, row, 0) checks = [] col = 1 for c in self.columns: check = QtWidgets.QCheckBox('') check.col = c check.row = name self.layout.addWidget(check, row, col) checks.append(check) if name in self.oldRows: check.setChecked(self.oldRows[name][col]) col += 1 #QtCore.QObject.connect(check, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) check.stateChanged.connect(self.checkChanged) self.rowNames.append(name) self.rowWidgets.append([label] + checks) def removeRow(self, name): row = self.rowNames.index(name) self.oldRows[name] = self.saveState()['rows'][row] ## save for later self.rowNames.pop(row) for w in self.rowWidgets[row]: w.setParent(None) #QtCore.QObject.disconnect(w, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) if isinstance(w, QtWidgets.QCheckBox): w.stateChanged.disconnect(self.checkChanged) self.rowWidgets.pop(row) for i in range(row, len(self.rowNames)): widgets = self.rowWidgets[i] for j in range(len(widgets)): widgets[j].setParent(None) self.layout.addWidget(widgets[j], i+1, j) def checkChanged(self, state): check = QtCore.QObject.sender(self) #self.emit(QtCore.SIGNAL('stateChanged'), check.row, check.col, state) self.sigStateChanged.emit(check.row, check.col, state) def saveState(self): rows = [] for i in range(len(self.rowNames)): row = [self.rowNames[i]] + [c.isChecked() for c in self.rowWidgets[i][1:]] rows.append(row) return {'cols': self.columns, 'rows': rows} def restoreState(self, state): rows = [r[0] for r in state['rows']] self.updateRows(rows) for r in state['rows']: rowNum = self.rowNames.index(r[0]) for i in range(1, len(r)): self.rowWidgets[rowNum][i].setChecked(r[i]) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/ColorButton.py000066400000000000000000000073331421045507400241530ustar00rootroot00000000000000from .. import functions as functions from ..Qt import QtCore, QtGui, QtWidgets __all__ = ['ColorButton'] class ColorButton(QtWidgets.QPushButton): """ **Bases:** QtWidgets.QPushButton Button displaying a color and allowing the user to select a new color. ====================== ============================================================ **Signals:** sigColorChanging(self) emitted whenever a new color is picked in the color dialog sigColorChanged(self) emitted when the selected color is accepted (user clicks OK) ====================== ============================================================ """ sigColorChanging = QtCore.Signal(object) ## emitted whenever a new color is picked in the color dialog sigColorChanged = QtCore.Signal(object) ## emitted when the selected color is accepted (user clicks OK) def __init__(self, parent=None, color=(128,128,128), padding=6): QtWidgets.QPushButton.__init__(self, parent) self.padding = (padding, padding, -padding, -padding) if isinstance(padding, (int, float)) else padding self.setColor(color) self.colorDialog = QtWidgets.QColorDialog() self.colorDialog.setOption(QtWidgets.QColorDialog.ColorDialogOption.ShowAlphaChannel, True) self.colorDialog.setOption(QtWidgets.QColorDialog.ColorDialogOption.DontUseNativeDialog, True) self.colorDialog.currentColorChanged.connect(self.dialogColorChanged) self.colorDialog.rejected.connect(self.colorRejected) self.colorDialog.colorSelected.connect(self.colorSelected) #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged) #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected) self.clicked.connect(self.selectColor) self.setMinimumHeight(15) self.setMinimumWidth(15) def paintEvent(self, ev): super().paintEvent(ev) p = QtGui.QPainter(self) rect = self.rect().adjusted(*self.padding) ## draw white base, then texture for indicating transparency, then actual color p.setBrush(functions.mkBrush('w')) p.drawRect(rect) p.setBrush(QtGui.QBrush(QtCore.Qt.BrushStyle.DiagCrossPattern)) p.drawRect(rect) p.setBrush(functions.mkBrush(self._color)) p.drawRect(rect) p.end() def setColor(self, color, finished=True): """Sets the button's color and emits both sigColorChanged and sigColorChanging.""" self._color = functions.mkColor(color) self.update() if finished: self.sigColorChanged.emit(self) else: self.sigColorChanging.emit(self) def selectColor(self): self.origColor = self.color() self.colorDialog.setCurrentColor(self.color()) self.colorDialog.open() def dialogColorChanged(self, color): if color.isValid(): self.setColor(color, finished=False) def colorRejected(self): self.setColor(self.origColor, finished=False) def colorSelected(self, color): self.setColor(self._color, finished=True) def saveState(self): return self._color.getRgb() def restoreState(self, state): self.setColor(state) def color(self, mode='qcolor'): color = functions.mkColor(self._color) if mode == 'qcolor': return color elif mode == 'byte': return color.getRgb() elif mode == 'float': return color.getRgbF() def widgetGroupInterface(self): return (self.sigColorChanged, ColorButton.saveState, ColorButton.restoreState) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/ColorMapWidget.py000066400000000000000000000251341421045507400245600ustar00rootroot00000000000000from collections import OrderedDict import numpy as np from .. import functions as fn from .. import parametertree as ptree from ..Qt import QtCore __all__ = ['ColorMapWidget'] class ColorMapWidget(ptree.ParameterTree): """ This class provides a widget allowing the user to customize color mapping for multi-column data. Given a list of field names, the user may specify multiple criteria for assigning colors to each record in a numpy record array. Multiple criteria are evaluated and combined into a single color for each record by user-defined compositing methods. For simpler color mapping using a single gradient editor, see :class:`GradientWidget ` """ sigColorMapChanged = QtCore.Signal(object) def __init__(self, parent=None): ptree.ParameterTree.__init__(self, parent=parent, showHeader=False) self.params = ColorMapParameter() self.setParameters(self.params) self.params.sigTreeStateChanged.connect(self.mapChanged) ## wrap a couple methods self.setFields = self.params.setFields self.map = self.params.map def mapChanged(self): self.sigColorMapChanged.emit(self) def widgetGroupInterface(self): return (self.sigColorMapChanged, self.saveState, self.restoreState) def saveState(self): return self.params.saveState() def restoreState(self, state): self.params.restoreState(state) def addColorMap(self, name): """Add a new color mapping and return the created parameter. """ return self.params.addNew(name) class ColorMapParameter(ptree.types.GroupParameter): sigColorMapChanged = QtCore.Signal(object) def __init__(self): self.fields = {} ptree.types.GroupParameter.__init__(self, name='Color Map', addText='Add Mapping..', addList=[]) self.sigTreeStateChanged.connect(self.mapChanged) def mapChanged(self): self.sigColorMapChanged.emit(self) def addNew(self, name): fieldSpec = self.fields[name] mode = fieldSpec.get('mode', 'range') if mode == 'range': item = RangeColorMapItem(name, self.fields[name]) elif mode == 'enum': item = EnumColorMapItem(name, self.fields[name]) defaults = fieldSpec.get('defaults', {}) for k, v in defaults.items(): if k == 'colormap': if mode == 'range': item.setValue(v) elif mode == 'enum': children = item.param('Values').children() for i, child in enumerate(children): try: child.setValue(v[i]) except IndexError: continue else: item[k] = v self.addChild(item) return item def fieldNames(self): return list(self.fields.keys()) def setFields(self, fields): """ Set the list of fields to be used by the mapper. The format of *fields* is:: [ (fieldName, {options}), ... ] ============== ============================================================ Field Options: mode Either 'range' or 'enum' (default is range). For 'range', The user may specify a gradient of colors to be applied linearly across a specific range of values. For 'enum', the user specifies a single color for each unique value (see *values* option). units String indicating the units of the data for this field. values List of unique values for which the user may assign a color when mode=='enum'. Optionally may specify a dict instead {value: name}. defaults Dict of default values to apply to color map items when they are created. Valid keys are 'colormap' to provide a default color map, or otherwise they a string or tuple indicating the parameter to be set, such as 'Operation' or ('Channels..', 'Red'). ============== ============================================================ """ self.fields = OrderedDict(fields) #self.fields = fields #self.fields.sort() names = self.fieldNames() self.setAddList(names) def map(self, data, mode='byte'): """ Return an array of colors corresponding to *data*. ============== ================================================================= **Arguments:** data A numpy record array where the fields in data.dtype match those defined by a prior call to setFields(). mode Either 'byte' or 'float'. For 'byte', the method returns an array of dtype ubyte with values scaled 0-255. For 'float', colors are returned as 0.0-1.0 float values. ============== ================================================================= """ if isinstance(data, dict): data = np.array([tuple(data.values())], dtype=[(k, float) for k in data.keys()]) colors = np.zeros((len(data),4)) for item in self.children(): if not item['Enabled']: continue chans = item.param('Channels..') mask = np.empty((len(data), 4), dtype=bool) for i,f in enumerate(['Red', 'Green', 'Blue', 'Alpha']): mask[:,i] = chans[f] colors2 = item.map(data) op = item['Operation'] if op == 'Add': colors[mask] = colors[mask] + colors2[mask] elif op == 'Multiply': colors[mask] *= colors2[mask] elif op == 'Overlay': a = colors2[:,3:4] c3 = colors * (1-a) + colors2 * a c3[:,3:4] = colors[:,3:4] + (1-colors[:,3:4]) * a colors = c3 elif op == 'Set': colors[mask] = colors2[mask] colors = fn.clip_array(colors, 0., 1.) if mode == 'byte': colors = (colors * 255).astype(np.ubyte) return colors def saveState(self): items = OrderedDict() for item in self: itemState = item.saveState(filter='user') itemState['field'] = item.fieldName items[item.name()] = itemState state = {'fields': self.fields, 'items': items} return state def restoreState(self, state): if 'fields' in state: self.setFields(state['fields']) for name, itemState in state['items'].items(): item = self.addNew(itemState['field']) item.restoreState(itemState) class RangeColorMapItem(ptree.types.ColorMapParameter): mapType = 'range' def __init__(self, name, opts): self.fieldName = name units = opts.get('units', '') ptree.types.ColorMapParameter.__init__(self, name=name, autoIncrementName=True, type='colormap', removable=True, renamable=True, children=[ #dict(name="Field", type='list', value=name, limits=fields), dict(name='Min', type='float', value=0.0, suffix=units, siPrefix=True), dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True), dict(name='Operation', type='list', value='Overlay', limits=['Overlay', 'Add', 'Multiply', 'Set']), dict(name='Channels..', type='group', expanded=False, children=[ dict(name='Red', type='bool', value=True), dict(name='Green', type='bool', value=True), dict(name='Blue', type='bool', value=True), dict(name='Alpha', type='bool', value=True), ]), dict(name='Enabled', type='bool', value=True), dict(name='NaN', type='color'), ]) def map(self, data): data = data[self.fieldName] scaled = fn.clip_array((data-self['Min']) / (self['Max']-self['Min']), 0, 1) cmap = self.value() colors = cmap.map(scaled, mode='float') mask = np.invert(np.isfinite(data)) nanColor = self['NaN'] nanColor = nanColor.getRgbF() colors[mask] = nanColor return colors class EnumColorMapItem(ptree.types.GroupParameter): mapType = 'enum' def __init__(self, name, opts): self.fieldName = name vals = opts.get('values', []) if isinstance(vals, list): vals = OrderedDict([(v,str(v)) for v in vals]) childs = [] for val,vname in vals.items(): ch = ptree.Parameter.create(name=vname, type='color') ch.maskValue = val childs.append(ch) ptree.types.GroupParameter.__init__(self, name=name, autoIncrementName=True, removable=True, renamable=True, children=[ dict(name='Values', type='group', children=childs), dict(name='Operation', type='list', value='Overlay', limits=['Overlay', 'Add', 'Multiply', 'Set']), dict(name='Channels..', type='group', expanded=False, children=[ dict(name='Red', type='bool', value=True), dict(name='Green', type='bool', value=True), dict(name='Blue', type='bool', value=True), dict(name='Alpha', type='bool', value=True), ]), dict(name='Enabled', type='bool', value=True), dict(name='Default', type='color'), ]) def map(self, data): data = data[self.fieldName] colors = np.empty((len(data), 4)) default = np.array(self['Default'].getRgbF()) colors[:] = default for v in self.param('Values'): mask = data == v.maskValue c = np.array(v.value().getRgbF()) colors[mask] = c #scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) #cmap = self.value() #colors = cmap.map(scaled, mode='float') #mask = np.isnan(data) | np.isinf(data) #nanColor = self['NaN'] #nanColor = nanColor.getRgbF() #colors[mask] = nanColor return colors pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/ComboBox.py000066400000000000000000000176161421045507400234160ustar00rootroot00000000000000import sys from collections import OrderedDict from ..Qt import QtWidgets __all__ = ['ComboBox'] class ComboBox(QtWidgets.QComboBox): """Extends QComboBox to add extra functionality. * Handles dict mappings -- user selects a text key, and the ComboBox indicates the selected value. * Requires item strings to be unique * Remembers selected value if list is cleared and subsequently repopulated * setItems() replaces the items in the ComboBox and blocks signals if the value ultimately does not change. """ def __init__(self, parent=None, items=None, default=None): QtWidgets.QComboBox.__init__(self, parent) self.currentIndexChanged.connect(self.indexChanged) self._ignoreIndexChange = False #self.value = default if 'darwin' in sys.platform: ## because MacOSX can show names that are wider than the comboBox self.setSizeAdjustPolicy(QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents) #self.setMinimumContentsLength(10) self._chosenText = None self._items = OrderedDict() if items is not None: self.setItems(items) if default is not None: self.setValue(default) def setValue(self, value): """Set the selected item to the first one having the given value.""" text = None for k,v in self._items.items(): if v == value: text = k break if text is None: raise ValueError(value) self.setText(text) def setText(self, text): """Set the selected item to the first one having the given text.""" ind = self.findText(text) if ind == -1: raise ValueError(text) #self.value = value self.setCurrentIndex(ind) def value(self): """ If items were given as a list of strings, then return the currently selected text. If items were given as a dict, then return the value corresponding to the currently selected key. If the combo list is empty, return None. """ if self.count() == 0: return None text = self.currentText() return self._items[text] def ignoreIndexChange(func): # Decorator that prevents updates to self._chosenText def fn(self, *args, **kwds): prev = self._ignoreIndexChange self._ignoreIndexChange = True try: ret = func(self, *args, **kwds) finally: self._ignoreIndexChange = prev return ret return fn def blockIfUnchanged(func): # decorator that blocks signal emission during complex operations # and emits currentIndexChanged only if the value has actually # changed at the end. def fn(self, *args, **kwds): prevVal = self.value() blocked = self.signalsBlocked() self.blockSignals(True) try: ret = func(self, *args, **kwds) finally: self.blockSignals(blocked) # only emit if the value has changed if self.value() != prevVal: self.currentIndexChanged.emit(self.currentIndex()) return ret return fn @ignoreIndexChange @blockIfUnchanged def setItems(self, items): """ *items* may be a list, a tuple, or a dict. If a dict is given, then the keys are used to populate the combo box and the values will be used for both value() and setValue(). """ prevVal = self.value() self.blockSignals(True) try: self.clear() self.addItems(items) finally: self.blockSignals(False) # only emit if we were not able to re-set the original value if self.value() != prevVal: self.currentIndexChanged.emit(self.currentIndex()) def items(self): return self.items.copy() def updateList(self, items): # for backward compatibility return self.setItems(items) def indexChanged(self, index): # current index has changed; need to remember new 'chosen text' if self._ignoreIndexChange: return self._chosenText = self.currentText() def setCurrentIndex(self, index): QtWidgets.QComboBox.setCurrentIndex(self, index) def itemsChanged(self): # try to set the value to the last one selected, if it is available. if self._chosenText is not None: try: self.setText(self._chosenText) except ValueError: pass @ignoreIndexChange def insertItem(self, *args): raise NotImplementedError() #QtWidgets.QComboBox.insertItem(self, *args) #self.itemsChanged() @ignoreIndexChange def insertItems(self, *args): raise NotImplementedError() #QtWidgets.QComboBox.insertItems(self, *args) #self.itemsChanged() @ignoreIndexChange def addItem(self, *args, **kwds): # Need to handle two different function signatures for QComboBox.addItem try: if isinstance(args[0], str): text = args[0] if len(args) == 2: value = args[1] else: value = kwds.get('value', text) else: text = args[1] if len(args) == 3: value = args[2] else: value = kwds.get('value', text) except IndexError: raise TypeError("First or second argument of addItem must be a string.") if text in self._items: raise Exception('ComboBox already has item named "%s".' % text) self._items[text] = value QtWidgets.QComboBox.addItem(self, *args) self.itemsChanged() def setItemValue(self, name, value): if name not in self._items: self.addItem(name, value) else: self._items[name] = value @ignoreIndexChange @blockIfUnchanged def addItems(self, items): if isinstance(items, list) or isinstance(items, tuple): texts = items items = dict([(x, x) for x in items]) elif isinstance(items, dict): texts = list(items.keys()) else: raise TypeError("items argument must be list or dict or tuple (got %s)." % type(items)) for t in texts: if t in self._items: raise Exception('ComboBox already has item named "%s".' % t) for k,v in items.items(): self._items[k] = v QtWidgets.QComboBox.addItems(self, list(texts)) self.itemsChanged() @ignoreIndexChange def clear(self): self._items = OrderedDict() QtWidgets.QComboBox.clear(self) self.itemsChanged() def saveState(self): ind = self.currentIndex() data = self.itemData(ind) #if not data.isValid(): if data is not None: try: if not data.isValid(): data = None else: data = data.toInt()[0] except AttributeError: pass if data is None: return self.itemText(ind) else: return data def restoreState(self, v): if type(v) is int: ind = self.findData(v) if ind > -1: self.setCurrentIndex(ind) return self.setCurrentIndex(self.findText(str(v))) def widgetGroupInterface(self): return (self.currentIndexChanged, self.saveState, self.restoreState) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/DataFilterWidget.py000066400000000000000000000167651421045507400250750ustar00rootroot00000000000000from collections import OrderedDict import numpy as np from .. import functions as fn from .. import parametertree as ptree from ..Qt import QtCore __all__ = ['DataFilterWidget'] class DataFilterWidget(ptree.ParameterTree): """ This class allows the user to filter multi-column data sets by specifying multiple criteria Wraps methods from DataFilterParameter: setFields, generateMask, filterData, and describe. """ sigFilterChanged = QtCore.Signal(object) def __init__(self): ptree.ParameterTree.__init__(self, showHeader=False) self.params = DataFilterParameter() self.setParameters(self.params) self.params.sigFilterChanged.connect(self.sigFilterChanged) self.setFields = self.params.setFields self.generateMask = self.params.generateMask self.filterData = self.params.filterData self.describe = self.params.describe def parameters(self): return self.params def addFilter(self, name): """Add a new filter and return the created parameter item. """ return self.params.addNew(name) class DataFilterParameter(ptree.types.GroupParameter): """A parameter group that specifies a set of filters to apply to tabular data. """ sigFilterChanged = QtCore.Signal(object) def __init__(self): self.fields = {} ptree.types.GroupParameter.__init__(self, name='Data Filter', addText='Add filter..', addList=[]) self.sigTreeStateChanged.connect(self.filterChanged) def filterChanged(self): self.sigFilterChanged.emit(self) def addNew(self, name): mode = self.fields[name].get('mode', 'range') if mode == 'range': child = self.addChild(RangeFilterItem(name, self.fields[name])) elif mode == 'enum': child = self.addChild(EnumFilterItem(name, self.fields[name])) else: raise ValueError("field mode must be 'range' or 'enum'") return child def fieldNames(self): return self.fields.keys() def setFields(self, fields): """Set the list of fields that are available to be filtered. *fields* must be a dict or list of tuples that maps field names to a specification describing the field. Each specification is itself a dict with either ``'mode':'range'`` or ``'mode':'enum'``:: filter.setFields([ ('field1', {'mode': 'range'}), ('field2', {'mode': 'enum', 'values': ['val1', 'val2', 'val3']}), ('field3', {'mode': 'enum', 'values': {'val1':True, 'val2':False, 'val3':True}}), ]) """ with fn.SignalBlock(self.sigTreeStateChanged, self.filterChanged): self.fields = OrderedDict(fields) names = self.fieldNames() self.setAddList(names) # update any existing filters for ch in self.children(): name = ch.fieldName if name in fields: ch.updateFilter(fields[name]) self.sigFilterChanged.emit(self) def filterData(self, data): if len(data) == 0: return data return data[self.generateMask(data)] def generateMask(self, data): """Return a boolean mask indicating whether each item in *data* passes the filter critera. """ mask = np.ones(len(data), dtype=bool) if len(data) == 0: return mask for fp in self: if fp.value() is False: continue mask &= fp.generateMask(data, mask.copy()) #key, mn, mx = fp.fieldName, fp['Min'], fp['Max'] #vals = data[key] #mask &= (vals >= mn) #mask &= (vals < mx) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections return mask def describe(self): """Return a list of strings describing the currently enabled filters.""" desc = [] for fp in self: if fp.value() is False: continue desc.append(fp.describe()) return desc class RangeFilterItem(ptree.types.SimpleParameter): def __init__(self, name, opts): self.fieldName = name units = opts.get('units', '') self.units = units ptree.types.SimpleParameter.__init__(self, name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True, children=[ #dict(name="Field", type='list', value=name, limits=fields), dict(name='Min', type='float', value=0.0, suffix=units, siPrefix=True), dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True), ]) def generateMask(self, data, mask): vals = data[self.fieldName][mask] mask[mask] = (vals >= self['Min']) & (vals < self['Max']) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections return mask def describe(self): return "%s < %s < %s" % (fn.siFormat(self['Min'], suffix=self.units), self.fieldName, fn.siFormat(self['Max'], suffix=self.units)) def updateFilter(self, opts): pass class EnumFilterItem(ptree.types.SimpleParameter): def __init__(self, name, opts): self.fieldName = name ptree.types.SimpleParameter.__init__(self, name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True) self.setEnumVals(opts) def generateMask(self, data, startMask): vals = data[self.fieldName][startMask] mask = np.ones(len(vals), dtype=bool) otherMask = np.ones(len(vals), dtype=bool) for c in self: key = c.maskValue if key == '__other__': m = ~otherMask else: m = vals != key otherMask &= m if c.value() is False: mask &= m startMask[startMask] = mask return startMask def describe(self): vals = [ch.name() for ch in self if ch.value() is True] return "%s: %s" % (self.fieldName, ', '.join(vals)) def updateFilter(self, opts): self.setEnumVals(opts) def setEnumVals(self, opts): vals = opts.get('values', {}) prevState = {} for ch in self.children(): prevState[ch.name()] = ch.value() self.removeChild(ch) if not isinstance(vals, dict): vals = OrderedDict([(v,(str(v), True)) for v in vals]) # Each filterable value can come with either (1) a string name, (2) a bool # indicating whether the value is enabled by default, or (3) a tuple providing # both. for val,valopts in vals.items(): if isinstance(valopts, bool): enabled = valopts vname = str(val) elif isinstance(valopts, str): enabled = True vname = valopts elif isinstance(valopts, tuple): vname, enabled = valopts ch = ptree.Parameter.create(name=vname, type='bool', value=prevState.get(vname, enabled)) ch.maskValue = val self.addChild(ch) ch = ptree.Parameter.create(name='(other)', type='bool', value=prevState.get('(other)', True)) ch.maskValue = '__other__' self.addChild(ch) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/DataTreeWidget.py000066400000000000000000000107271421045507400245370ustar00rootroot00000000000000import traceback import types from collections import OrderedDict import numpy as np from ..Qt import QtWidgets from .TableWidget import TableWidget try: import metaarray # noqa HAVE_METAARRAY = True except: HAVE_METAARRAY = False __all__ = ['DataTreeWidget'] class DataTreeWidget(QtWidgets.QTreeWidget): """ Widget for displaying hierarchical python data structures (eg, nested dicts, lists, and arrays) """ def __init__(self, parent=None, data=None): QtWidgets.QTreeWidget.__init__(self, parent) self.setVerticalScrollMode(self.ScrollMode.ScrollPerPixel) self.setData(data) self.setColumnCount(3) self.setHeaderLabels(['key / index', 'type', 'value']) self.setAlternatingRowColors(True) def setData(self, data, hideRoot=False): """data should be a dictionary.""" self.clear() self.widgets = [] self.nodes = {} self.buildTree(data, self.invisibleRootItem(), hideRoot=hideRoot) self.expandToDepth(3) self.resizeColumnToContents(0) def buildTree(self, data, parent, name='', hideRoot=False, path=()): if hideRoot: node = parent else: node = QtWidgets.QTreeWidgetItem([name, "", ""]) parent.addChild(node) # record the path to the node so it can be retrieved later # (this is used by DiffTreeWidget) self.nodes[path] = node typeStr, desc, childs, widget = self.parse(data) node.setText(1, typeStr) node.setText(2, desc) # Truncate description and add text box if needed if len(desc) > 100: desc = desc[:97] + '...' if widget is None: widget = QtWidgets.QPlainTextEdit(str(data)) widget.setMaximumHeight(200) widget.setReadOnly(True) # Add widget to new subnode if widget is not None: self.widgets.append(widget) subnode = QtWidgets.QTreeWidgetItem(["", "", ""]) node.addChild(subnode) self.setItemWidget(subnode, 0, widget) subnode.setFirstColumnSpanned(True) # recurse to children for key, data in childs.items(): self.buildTree(data, node, str(key), path=path+(key,)) def parse(self, data): """ Given any python object, return: * type * a short string representation * a dict of sub-objects to be parsed * optional widget to display as sub-node """ # defaults for all objects typeStr = type(data).__name__ if typeStr == 'instance': typeStr += ": " + data.__class__.__name__ widget = None desc = "" childs = {} # type-specific changes if isinstance(data, dict): desc = "length=%d" % len(data) if isinstance(data, OrderedDict): childs = data else: try: childs = OrderedDict(sorted(data.items())) except TypeError: # if sorting falls childs = OrderedDict(data.items()) elif isinstance(data, (list, tuple)): desc = "length=%d" % len(data) childs = OrderedDict(enumerate(data)) elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): childs = OrderedDict([ ('data', data.view(np.ndarray)), ('meta', data.infoCopy()) ]) elif isinstance(data, np.ndarray): desc = "shape=%s dtype=%s" % (data.shape, data.dtype) table = TableWidget() table.setData(data) table.setMaximumHeight(200) widget = table elif isinstance(data, types.TracebackType): ## convert traceback to a list of strings frames = list(map(str.strip, traceback.format_list(traceback.extract_tb(data)))) #childs = OrderedDict([ #(i, {'file': child[0], 'line': child[1], 'function': child[2], 'code': child[3]}) #for i, child in enumerate(frames)]) #childs = OrderedDict([(i, ch) for i,ch in enumerate(frames)]) widget = QtWidgets.QPlainTextEdit('\n'.join(frames)) widget.setMaximumHeight(200) widget.setReadOnly(True) else: desc = str(data) return typeStr, desc, childs, widget pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/DiffTreeWidget.py000066400000000000000000000133321421045507400245310ustar00rootroot00000000000000import numpy as np from .. import functions as fn from ..Qt import QtWidgets from .DataTreeWidget import DataTreeWidget __all__ = ['DiffTreeWidget'] class DiffTreeWidget(QtWidgets.QWidget): """ Widget for displaying differences between hierarchical python data structures (eg, nested dicts, lists, and arrays) """ def __init__(self, parent=None, a=None, b=None): QtWidgets.QWidget.__init__(self, parent) self.layout = QtWidgets.QHBoxLayout() self.setLayout(self.layout) self.trees = [DataTreeWidget(self), DataTreeWidget(self)] for t in self.trees: self.layout.addWidget(t) if a is not None: self.setData(a, b) def setData(self, a, b): """ Set the data to be compared in this widget. """ self.data = (a, b) self.trees[0].setData(a) self.trees[1].setData(b) return self.compare(a, b) def compare(self, a, b, path=()): """ Compare data structure *a* to structure *b*. Return True if the objects match completely. Otherwise, return a structure that describes the differences: { 'type': bool 'len': bool, 'str': bool, 'shape': bool, 'dtype': bool, 'mask': array, } """ bad = (255, 200, 200) diff = [] # generate typestr, desc, childs for each object typeA, descA, childsA, _ = self.trees[0].parse(a) typeB, descB, childsB, _ = self.trees[1].parse(b) if typeA != typeB: self.setColor(path, 1, bad) if descA != descB: self.setColor(path, 2, bad) if isinstance(a, dict) and isinstance(b, dict): keysA = set(a.keys()) keysB = set(b.keys()) for key in keysA - keysB: self.setColor(path+(key,), 0, bad, tree=0) for key in keysB - keysA: self.setColor(path+(key,), 0, bad, tree=1) for key in keysA & keysB: self.compare(a[key], b[key], path+(key,)) elif isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)): for i in range(max(len(a), len(b))): if len(a) <= i: self.setColor(path+(i,), 0, bad, tree=1) elif len(b) <= i: self.setColor(path+(i,), 0, bad, tree=0) else: self.compare(a[i], b[i], path+(i,)) elif isinstance(a, np.ndarray) and isinstance(b, np.ndarray) and a.shape == b.shape: tableNodes = [tree.nodes[path].child(0) for tree in self.trees] if a.dtype.fields is None and b.dtype.fields is None: eq = self.compareArrays(a, b) if not np.all(eq): for n in tableNodes: n.setBackground(0, fn.mkBrush(bad)) #for i in np.argwhere(~eq): else: if a.dtype == b.dtype: for i,k in enumerate(a.dtype.fields.keys()): eq = self.compareArrays(a[k], b[k]) if not np.all(eq): for n in tableNodes: n.setBackground(0, fn.mkBrush(bad)) #for j in np.argwhere(~eq): # dict: compare keys, then values where keys match # list: # array: compare elementwise for same shape def compareArrays(self, a, b): intnan = -9223372036854775808 # happens when np.nan is cast to int anans = np.isnan(a) | (a == intnan) bnans = np.isnan(b) | (b == intnan) eq = anans == bnans mask = ~anans eq[mask] = np.allclose(a[mask], b[mask]) return eq def setColor(self, path, column, color, tree=None): brush = fn.mkBrush(color) # Color only one tree if specified. if tree is None: trees = self.trees else: trees = [self.trees[tree]] for tree in trees: item = tree.nodes[path] item.setBackground(column, brush) def _compare(self, a, b): """ Compare data structure *a* to structure *b*. """ # Check test structures are the same assert type(info) is type(expect) if hasattr(info, '__len__'): assert len(info) == len(expect) if isinstance(info, dict): for k in info: assert k in expect for k in expect: assert k in info self.compare_results(info[k], expect[k]) elif isinstance(info, list): for i in range(len(info)): self.compare_results(info[i], expect[i]) elif isinstance(info, np.ndarray): assert info.shape == expect.shape assert info.dtype == expect.dtype if info.dtype.fields is None: intnan = -9223372036854775808 # happens when np.nan is cast to int inans = np.isnan(info) | (info == intnan) enans = np.isnan(expect) | (expect == intnan) assert np.all(inans == enans) mask = ~inans assert np.allclose(info[mask], expect[mask]) else: for k in info.dtype.fields.keys(): self.compare_results(info[k], expect[k]) else: try: assert info == expect except Exception: raise NotImplementedError("Cannot compare objects of type %s" % type(info)) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/FeedbackButton.py000066400000000000000000000144521421045507400245610ustar00rootroot00000000000000from ..Qt import QtCore, QtWidgets __all__ = ['FeedbackButton'] class FeedbackButton(QtWidgets.QPushButton): """ QPushButton which flashes success/failure indication for slow or asynchronous procedures. """ ### For thread-safetyness sigCallSuccess = QtCore.Signal(object, object, object) sigCallFailure = QtCore.Signal(object, object, object) sigCallProcess = QtCore.Signal(object, object, object) sigReset = QtCore.Signal() def __init__(self, *args): QtWidgets.QPushButton.__init__(self, *args) self.origStyle = None self.origText = self.text() self.origStyle = self.styleSheet() self.origTip = self.toolTip() self.limitedTime = True #self.textTimer = QtCore.QTimer() #self.tipTimer = QtCore.QTimer() #self.textTimer.timeout.connect(self.setText) #self.tipTimer.timeout.connect(self.setToolTip) self.sigCallSuccess.connect(self.success) self.sigCallFailure.connect(self.failure) self.sigCallProcess.connect(self.processing) self.sigReset.connect(self.reset) def feedback(self, success, message=None, tip="", limitedTime=True): """Calls success() or failure(). If you want the message to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action.Threadsafe.""" if success: self.success(message, tip, limitedTime=limitedTime) else: self.failure(message, tip, limitedTime=limitedTime) def success(self, message=None, tip="", limitedTime=True): """Displays specified message on button and flashes button green to let user know action was successful. If you want the success to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action. Threadsafe.""" isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() if isGuiThread: self.setEnabled(True) #print "success" self.startBlink("#0F0", message, tip, limitedTime=limitedTime) else: self.sigCallSuccess.emit(message, tip, limitedTime) def failure(self, message=None, tip="", limitedTime=True): """Displays specified message on button and flashes button red to let user know there was an error. If you want the error to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action. Threadsafe. """ isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() if isGuiThread: self.setEnabled(True) #print "fail" self.startBlink("#F00", message, tip, limitedTime=limitedTime) else: self.sigCallFailure.emit(message, tip, limitedTime) def processing(self, message="Processing..", tip="", processEvents=True): """Displays specified message on button to let user know the action is in progress. Threadsafe. """ isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() if isGuiThread: self.setEnabled(False) self.setText(message, temporary=True) self.setToolTip(tip, temporary=True) if processEvents: QtWidgets.QApplication.processEvents() else: self.sigCallProcess.emit(message, tip, processEvents) def reset(self): """Resets the button to its original text and style. Threadsafe.""" isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() if isGuiThread: self.limitedTime = True self.setText() self.setToolTip() self.setStyleSheet() else: self.sigReset.emit() def startBlink(self, color, message=None, tip="", limitedTime=True): #if self.origStyle is None: #self.origStyle = self.styleSheet() #self.origText = self.text() self.setFixedHeight(self.height()) if message is not None: self.setText(message, temporary=True) self.setToolTip(tip, temporary=True) self.count = 0 #self.indStyle = "QPushButton {border: 2px solid %s; border-radius: 5px}" % color self.indStyle = "QPushButton {background-color: %s}" % color self.limitedTime = limitedTime self.borderOn() if limitedTime: QtCore.QTimer.singleShot(2000, self.setText) QtCore.QTimer.singleShot(10000, self.setToolTip) def borderOn(self): self.setStyleSheet(self.indStyle, temporary=True) if self.limitedTime or self.count <=2: QtCore.QTimer.singleShot(100, self.borderOff) def borderOff(self): self.setStyleSheet() self.count += 1 if self.count >= 2: if self.limitedTime: return QtCore.QTimer.singleShot(30, self.borderOn) def setText(self, text=None, temporary=False): if text is None: text = self.origText #print text QtWidgets.QPushButton.setText(self, text) if not temporary: self.origText = text def setToolTip(self, text=None, temporary=False): if text is None: text = self.origTip QtWidgets.QPushButton.setToolTip(self, text) if not temporary: self.origTip = text def setStyleSheet(self, style=None, temporary=False): if style is None: style = self.origStyle QtWidgets.QPushButton.setStyleSheet(self, style) if not temporary: self.origStyle = style if __name__ == '__main__': import time app = QtWidgets.QApplication([]) win = QtWidgets.QMainWindow() btn = FeedbackButton("Button") fail = True def click(): btn.processing("Hold on..") time.sleep(2.0) global fail fail = not fail if fail: btn.failure(message="FAIL.", tip="There was a failure. Get over it.") else: btn.success(message="Bueno!") btn.clicked.connect(click) win.setCentralWidget(btn) win.show() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/FileDialog.py000066400000000000000000000007571421045507400237030ustar00rootroot00000000000000import sys from ..Qt import QtWidgets __all__ = ['FileDialog'] class FileDialog(QtWidgets.QFileDialog): ## Compatibility fix for OSX: ## For some reason the native dialog doesn't show up when you set AcceptMode to AcceptSave on OS X, so we don't use the native dialog def __init__(self, *args): QtWidgets.QFileDialog.__init__(self, *args) if sys.platform == 'darwin': self.setOption(QtWidgets.QFileDialog.Option.DontUseNativeDialog) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/GradientWidget.py000066400000000000000000000061221421045507400245750ustar00rootroot00000000000000from ..graphicsItems.GradientEditorItem import GradientEditorItem from ..Qt import QtCore, QtGui, QtWidgets from .GraphicsView import GraphicsView __all__ = ['GradientWidget'] class GradientWidget(GraphicsView): """ Widget displaying an editable color gradient. The user may add, move, recolor, or remove colors from the gradient. Additionally, a context menu allows the user to select from pre-defined gradients. """ sigGradientChanged = QtCore.Signal(object) sigGradientChangeFinished = QtCore.Signal(object) def __init__(self, parent=None, orientation='bottom', *args, **kargs): """ The *orientation* argument may be 'bottom', 'top', 'left', or 'right' indicating whether the gradient is displayed horizontally (top, bottom) or vertically (left, right) and on what side of the gradient the editable ticks will appear. All other arguments are passed to :func:`GradientEditorItem.__init__ `. Note: For convenience, this class wraps methods from :class:`GradientEditorItem `. """ GraphicsView.__init__(self, parent, useOpenGL=False, background=None) self.maxDim = 31 kargs['tickPen'] = 'k' self.item = GradientEditorItem(*args, **kargs) self.item.sigGradientChanged.connect(self.sigGradientChanged) self.item.sigGradientChangeFinished.connect(self.sigGradientChangeFinished) self.setCentralItem(self.item) self.setOrientation(orientation) self.setCacheMode(self.CacheModeFlag.CacheNone) self.setRenderHints(QtGui.QPainter.RenderHint.Antialiasing | QtGui.QPainter.RenderHint.TextAntialiasing) frame_style = QtWidgets.QFrame.Shape.NoFrame | QtWidgets.QFrame.Shadow.Plain self.setFrameStyle(frame_style) #self.setBackgroundRole(QtGui.QPalette.ColorRole.NoRole) #self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.BrushStyle.NoBrush)) #self.setAutoFillBackground(False) #self.setAttribute(QtCore.Qt.WindowType.WindowType.WidgetAttribute.WA_PaintOnScreen, False) #self.setAttribute(QtCore.Qt.WindowType.WindowType.WidgetAttribute.WA_OpaquePaintEvent, True) def setOrientation(self, ort): """Set the orientation of the widget. May be one of 'bottom', 'top', 'left', or 'right'.""" self.item.setOrientation(ort) self.orientation = ort self.setMaxDim() def setMaxDim(self, mx=None): if mx is None: mx = self.maxDim else: self.maxDim = mx if self.orientation in ['bottom', 'top']: self.setFixedHeight(mx) self.setMaximumWidth(16777215) else: self.setFixedWidth(mx) self.setMaximumHeight(16777215) def __getattr__(self, attr): ### wrap methods from GradientEditorItem return getattr(self.item, attr) def widgetGroupInterface(self): return (self.sigGradientChanged, self.saveState, self.restoreState) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/GraphicsLayoutWidget.py000066400000000000000000000054371421045507400260060ustar00rootroot00000000000000from ..graphicsItems.GraphicsLayout import GraphicsLayout from ..Qt import mkQApp from .GraphicsView import GraphicsView __all__ = ['GraphicsLayoutWidget'] class GraphicsLayoutWidget(GraphicsView): """ Convenience class consisting of a :class:`GraphicsView ` with a single :class:`GraphicsLayout ` as its central item. This widget is an easy starting point for generating multi-panel figures. Example:: w = pg.GraphicsLayoutWidget() p1 = w.addPlot(row=0, col=0) p2 = w.addPlot(row=0, col=1) v = w.addViewBox(row=1, col=0, colspan=2) ========= ================================================================= parent (QWidget or None) The parent widget. show (bool) If True, then immediately show the widget after it is created. If the widget has no parent, then it will be shown inside a new window. size (width, height) tuple. Optionally resize the widget. Note: if this widget is placed inside a layout, then this argument has no effect. title (str or None) If specified, then set the window title for this widget. kargs All extra arguments are passed to :meth:`GraphicsLayout.__init__ ` ========= ================================================================= This class wraps several methods from its internal GraphicsLayout: :func:`nextRow ` :func:`nextColumn ` :func:`addPlot ` :func:`addViewBox ` :func:`addItem ` :func:`getItem ` :func:`addLabel ` :func:`addLayout ` :func:`removeItem ` :func:`itemIndex ` :func:`clear ` """ def __init__(self, parent=None, show=False, size=None, title=None, **kargs): mkQApp() GraphicsView.__init__(self, parent) self.ci = GraphicsLayout(**kargs) for n in ['nextRow', 'nextCol', 'nextColumn', 'addPlot', 'addViewBox', 'addItem', 'getItem', 'addLayout', 'addLabel', 'removeItem', 'itemIndex', 'clear']: setattr(self, n, getattr(self.ci, n)) self.setCentralItem(self.ci) if size is not None: self.resize(*size) if title is not None: self.setWindowTitle(title) if show is True: self.show() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/GraphicsView.py000066400000000000000000000363341421045507400242770ustar00rootroot00000000000000""" GraphicsView.py - Extension of QGraphicsView Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ from .. import functions as fn from .. import getConfigOption from ..GraphicsScene import GraphicsScene from ..Point import Point from ..Qt import QT_LIB, QtCore, QtGui, QtWidgets __all__ = ['GraphicsView'] class GraphicsView(QtWidgets.QGraphicsView): """Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the viewed coordinate range. Also automatically creates a GraphicsScene and a central QGraphicsWidget that is automatically scaled to the full view geometry. This widget is the basis for :class:`PlotWidget `, :class:`GraphicsLayoutWidget `, and the view widget in :class:`ImageView `. By default, the view coordinate system matches the widget's pixel coordinates and automatically updates when the view is resized. This can be overridden by setting autoPixelRange=False. The exact visible range can be set with setRange(). The view can be panned using the middle mouse button and scaled using the right mouse button if enabled via enableMouse() (but ordinarily, we use ViewBox for this functionality).""" sigDeviceRangeChanged = QtCore.Signal(object, object) sigDeviceTransformChanged = QtCore.Signal(object) sigMouseReleased = QtCore.Signal(object) sigSceneMouseMoved = QtCore.Signal(object) #sigRegionChanged = QtCore.Signal(object) sigScaleChanged = QtCore.Signal(object) lastFileDir = None def __init__(self, parent=None, useOpenGL=None, background='default'): """ ============== ============================================================ **Arguments:** parent Optional parent widget useOpenGL If True, the GraphicsView will use OpenGL to do all of its rendering. This can improve performance on some systems, but may also introduce bugs (the combination of QGraphicsView and QOpenGLWidget is still an 'experimental' feature of Qt) background Set the background color of the GraphicsView. Accepts any single argument accepted by :func:`mkColor `. By default, the background color is determined using the 'backgroundColor' configuration option (see :func:`setConfigOptions `). ============== ============================================================ """ self.closed = False QtWidgets.QGraphicsView.__init__(self, parent) # This connects a cleanup function to QApplication.aboutToQuit. It is # called from here because we have no good way to react when the # QApplication is created by the user. # See pyqtgraph.__init__.py from .. import _connectCleanup _connectCleanup() if useOpenGL is None: useOpenGL = getConfigOption('useOpenGL') self.useOpenGL(useOpenGL) self.setCacheMode(self.CacheModeFlag.CacheBackground) ## This might help, but it's probably dangerous in the general case.. #self.setOptimizationFlag(self.DontSavePainterState, True) self.setBackgroundRole(QtGui.QPalette.ColorRole.NoRole) self.setBackground(background) self.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) self.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setTransformationAnchor(QtWidgets.QGraphicsView.ViewportAnchor.NoAnchor) self.setResizeAnchor(QtWidgets.QGraphicsView.ViewportAnchor.AnchorViewCenter) self.setViewportUpdateMode(QtWidgets.QGraphicsView.ViewportUpdateMode.MinimalViewportUpdate) self.lockedViewports = [] self.lastMousePos = None self.setMouseTracking(True) self.aspectLocked = False self.range = QtCore.QRectF(0, 0, 1, 1) self.autoPixelRange = True self.currentItem = None self.clearMouse() self.updateMatrix() # GraphicsScene must have parent or expect crashes! self.sceneObj = GraphicsScene(parent=self) self.setScene(self.sceneObj) ## Workaround for PySide crash ## This ensures that the scene will outlive the view. if QT_LIB == 'PySide': self.sceneObj._view_ref_workaround = self ## by default we set up a central widget with a grid layout. ## this can be replaced if needed. self.centralWidget = None self.setCentralItem(QtWidgets.QGraphicsWidget()) self.centralLayout = QtWidgets.QGraphicsGridLayout() self.centralWidget.setLayout(self.centralLayout) self.mouseEnabled = False self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False) self.clickAccepted = False def setAntialiasing(self, aa): """Enable or disable default antialiasing. Note that this will only affect items that do not specify their own antialiasing options.""" if aa: self.setRenderHints(self.renderHints() | QtGui.QPainter.RenderHint.Antialiasing) else: self.setRenderHints(self.renderHints() & ~QtGui.QPainter.RenderHint.Antialiasing) def setBackground(self, background): """ Set the background color of the GraphicsView. To use the defaults specified py pyqtgraph.setConfigOption, use background='default'. To make the background transparent, use background=None. """ self._background = background if background == 'default': background = getConfigOption('background') brush = fn.mkBrush(background) self.setBackgroundBrush(brush) def paintEvent(self, ev): self.scene().prepareForPaint() return super().paintEvent(ev) def render(self, *args, **kwds): self.scene().prepareForPaint() return super().render(*args, **kwds) def close(self): self.centralWidget = None self.scene().clear() self.currentItem = None self.sceneObj = None self.closed = True self.setViewport(None) super(GraphicsView, self).close() def useOpenGL(self, b=True): if b: HAVE_OPENGL = hasattr(QtWidgets, 'QOpenGLWidget') if not HAVE_OPENGL: raise Exception("Requested to use OpenGL with QGraphicsView, but QOpenGLWidget is not available.") v = QtWidgets.QOpenGLWidget() else: v = QtWidgets.QWidget() self.setViewport(v) def keyPressEvent(self, ev): self.scene().keyPressEvent(ev) ## bypass view, hand event directly to scene ## (view likes to eat arrow key events) def setCentralItem(self, item): return self.setCentralWidget(item) def setCentralWidget(self, item): """Sets a QGraphicsWidget to automatically fill the entire view (the item will be automatically resize whenever the GraphicsView is resized).""" if self.centralWidget is not None: self.scene().removeItem(self.centralWidget) self.centralWidget = item if item is not None: self.sceneObj.addItem(item) self.resizeEvent(None) def addItem(self, *args): return self.scene().addItem(*args) def removeItem(self, *args): return self.scene().removeItem(*args) def enableMouse(self, b=True): self.mouseEnabled = b self.autoPixelRange = (not b) def clearMouse(self): self.mouseTrail = [] self.lastButtonReleased = None def resizeEvent(self, ev): if self.closed: return if self.autoPixelRange: self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height()) GraphicsView.setRange(self, self.range, padding=0, disableAutoPixel=False) ## we do this because some subclasses like to redefine setRange in an incompatible way. self.updateMatrix() def updateMatrix(self, propagate=True): self.setSceneRect(self.range) if self.autoPixelRange: self.resetTransform() else: if self.aspectLocked: self.fitInView(self.range, QtCore.Qt.AspectRatioMode.KeepAspectRatio) else: self.fitInView(self.range, QtCore.Qt.AspectRatioMode.IgnoreAspectRatio) if propagate: for v in self.lockedViewports: v.setXRange(self.range, padding=0) self.sigDeviceRangeChanged.emit(self, self.range) self.sigDeviceTransformChanged.emit(self) def viewRect(self): """Return the boundaries of the view in scene coordinates""" ## easier to just return self.range ? r = QtCore.QRectF(self.rect()) return self.viewportTransform().inverted()[0].mapRect(r) def visibleRange(self): ## for backward compatibility return self.viewRect() def translate(self, dx, dy): self.range.adjust(dx, dy, dx, dy) self.updateMatrix() def scale(self, sx, sy, center=None): scale = [sx, sy] if self.aspectLocked: scale[0] = scale[1] if self.scaleCenter: center = None if center is None: center = self.range.center() w = self.range.width() / scale[0] h = self.range.height() / scale[1] self.range = QtCore.QRectF(center.x() - (center.x()-self.range.left()) / scale[0], center.y() - (center.y()-self.range.top()) /scale[1], w, h) self.updateMatrix() self.sigScaleChanged.emit(self) def setRange(self, newRect=None, padding=0.05, lockAspect=None, propagate=True, disableAutoPixel=True): if disableAutoPixel: self.autoPixelRange=False if newRect is None: newRect = self.visibleRange() padding = 0 padding = Point(padding) newRect = QtCore.QRectF(newRect) pw = newRect.width() * padding[0] ph = newRect.height() * padding[1] newRect = newRect.adjusted(-pw, -ph, pw, ph) scaleChanged = False if self.range.width() != newRect.width() or self.range.height() != newRect.height(): scaleChanged = True self.range = newRect #print "New Range:", self.range if self.centralWidget is not None: self.centralWidget.setGeometry(self.range) self.updateMatrix(propagate) if scaleChanged: self.sigScaleChanged.emit(self) def scaleToImage(self, image): """Scales such that pixels in image are the same size as screen pixels. This may result in a significant performance increase.""" pxSize = image.pixelSize() image.setPxMode(True) try: self.sigScaleChanged.disconnect(image.setScaledMode) except (TypeError, RuntimeError): pass tl = image.sceneBoundingRect().topLeft() w = self.size().width() * pxSize[0] h = self.size().height() * pxSize[1] range = QtCore.QRectF(tl.x(), tl.y(), w, h) GraphicsView.setRange(self, range, padding=0) self.sigScaleChanged.connect(image.setScaledMode) def lockXRange(self, v1): if not v1 in self.lockedViewports: self.lockedViewports.append(v1) def setXRange(self, r, padding=0.05): r1 = QtCore.QRectF(self.range) r1.setLeft(r.left()) r1.setRight(r.right()) GraphicsView.setRange(self, r1, padding=[padding, 0], propagate=False) def setYRange(self, r, padding=0.05): r1 = QtCore.QRectF(self.range) r1.setTop(r.top()) r1.setBottom(r.bottom()) GraphicsView.setRange(self, r1, padding=[0, padding], propagate=False) def wheelEvent(self, ev): super().wheelEvent(ev) if not self.mouseEnabled: return delta = ev.angleDelta().x() if delta == 0: delta = ev.angleDelta().y() sc = 1.001 ** delta #self.scale *= sc #self.updateMatrix() self.scale(sc, sc) def setAspectLocked(self, s): self.aspectLocked = s def leaveEvent(self, ev): self.scene().leaveEvent(ev) ## inform scene when mouse leaves def mousePressEvent(self, ev): super().mousePressEvent(ev) if not self.mouseEnabled: return lpos = ev.position() if hasattr(ev, 'position') else ev.localPos() self.lastMousePos = lpos self.mousePressPos = lpos self.clickAccepted = ev.isAccepted() if not self.clickAccepted: self.scene().clearSelection() return ## Everything below disabled for now.. def mouseReleaseEvent(self, ev): super().mouseReleaseEvent(ev) if not self.mouseEnabled: return self.sigMouseReleased.emit(ev) self.lastButtonReleased = ev.button() return ## Everything below disabled for now.. def mouseMoveEvent(self, ev): lpos = ev.position() if hasattr(ev, 'position') else ev.localPos() if self.lastMousePos is None: self.lastMousePos = lpos delta = Point(lpos - self.lastMousePos) self.lastMousePos = lpos super().mouseMoveEvent(ev) if not self.mouseEnabled: return self.sigSceneMouseMoved.emit(self.mapToScene(lpos)) if self.clickAccepted: ## Ignore event if an item in the scene has already claimed it. return if ev.buttons() == QtCore.Qt.MouseButton.RightButton: delta = Point(fn.clip_scalar(delta[0], -50, 50), fn.clip_scalar(-delta[1], -50, 50)) scale = 1.01 ** delta self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos)) self.sigDeviceRangeChanged.emit(self, self.range) elif ev.buttons() in [QtCore.Qt.MouseButton.MiddleButton, QtCore.Qt.MouseButton.LeftButton]: ## Allow panning by left or mid button. px = self.pixelSize() tr = -delta * px self.translate(tr[0], tr[1]) self.sigDeviceRangeChanged.emit(self, self.range) def pixelSize(self): """Return vector with the length and width of one view pixel in scene coordinates""" p0 = Point(0,0) p1 = Point(1,1) tr = self.transform().inverted()[0] p01 = tr.map(p0) p11 = tr.map(p1) return Point(p11 - p01) def dragEnterEvent(self, ev): ev.ignore() ## not sure why, but for some reason this class likes to consume drag events pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/GroupBox.py000066400000000000000000000061611421045507400234440ustar00rootroot00000000000000from ..Qt import QtCore, QtGui, QtWidgets from .PathButton import PathButton __all__ = ['GroupBox'] class GroupBox(QtWidgets.QGroupBox): """Subclass of QGroupBox that implements collapse handle. """ sigCollapseChanged = QtCore.Signal(object) def __init__(self, *args): QtWidgets.QGroupBox.__init__(self, *args) self._collapsed = False # We modify the size policy when the group box is collapsed, so # keep track of the last requested policy: self._lastSizePlocy = self.sizePolicy() self.closePath = QtGui.QPainterPath() self.closePath.moveTo(0, -1) self.closePath.lineTo(0, 1) self.closePath.lineTo(1, 0) self.closePath.lineTo(0, -1) self.openPath = QtGui.QPainterPath() self.openPath.moveTo(-1, 0) self.openPath.lineTo(1, 0) self.openPath.lineTo(0, 1) self.openPath.lineTo(-1, 0) self.collapseBtn = PathButton(path=self.openPath, size=(12, 12), margin=0) self.collapseBtn.setStyleSheet(""" border: none; """) self.collapseBtn.setPen('k') self.collapseBtn.setBrush('w') self.collapseBtn.setParent(self) self.collapseBtn.move(3, 3) self.collapseBtn.setFlat(True) self.collapseBtn.clicked.connect(self.toggleCollapsed) if len(args) > 0 and isinstance(args[0], str): self.setTitle(args[0]) def toggleCollapsed(self): self.setCollapsed(not self._collapsed) def collapsed(self): return self._collapsed def setCollapsed(self, c): if c == self._collapsed: return if c is True: self.collapseBtn.setPath(self.closePath) self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred, closing=True) elif c is False: self.collapseBtn.setPath(self.openPath) self.setSizePolicy(self._lastSizePolicy) else: raise TypeError("Invalid argument %r; must be bool." % c) for ch in self.children(): if isinstance(ch, QtWidgets.QWidget) and ch is not self.collapseBtn: ch.setVisible(not c) self._collapsed = c self.sigCollapseChanged.emit(c) def setSizePolicy(self, *args, **kwds): QtWidgets.QGroupBox.setSizePolicy(self, *args) if kwds.pop('closing', False) is True: self._lastSizePolicy = self.sizePolicy() def setHorizontalPolicy(self, *args): QtWidgets.QGroupBox.setHorizontalPolicy(self, *args) self._lastSizePolicy = self.sizePolicy() def setVerticalPolicy(self, *args): QtWidgets.QGroupBox.setVerticalPolicy(self, *args) self._lastSizePolicy = self.sizePolicy() def setTitle(self, title): # Leave room for button QtWidgets.QGroupBox.setTitle(self, " " + title) def widgetGroupInterface(self): return (self.sigCollapseChanged, GroupBox.collapsed, GroupBox.setCollapsed, True) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/HistogramLUTWidget.py000066400000000000000000000026711421045507400253670ustar00rootroot00000000000000""" Widget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. This is a wrapper around HistogramLUTItem """ from ..graphicsItems.HistogramLUTItem import HistogramLUTItem from ..Qt import QtCore, QtWidgets from .GraphicsView import GraphicsView __all__ = ['HistogramLUTWidget'] class HistogramLUTWidget(GraphicsView): """QWidget wrapper for :class:`~pyqtgraph.HistogramLUTItem`. All parameters are passed along in creating the HistogramLUTItem. """ def __init__(self, parent=None, *args, **kargs): background = kargs.pop('background', 'default') GraphicsView.__init__(self, parent, useOpenGL=False, background=background) self.item = HistogramLUTItem(*args, **kargs) self.setCentralItem(self.item) self.orientation = kargs.get('orientation', 'vertical') if self.orientation == 'vertical': self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding) self.setMinimumWidth(95) else: self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) self.setMinimumHeight(95) def sizeHint(self): if self.orientation == 'vertical': return QtCore.QSize(115, 200) else: return QtCore.QSize(200, 115) def __getattr__(self, attr): return getattr(self.item, attr) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/JoystickButton.py000066400000000000000000000051461421045507400246740ustar00rootroot00000000000000from math import hypot from ..Qt import QtCore, QtGui, QtWidgets, mkQApp __all__ = ['JoystickButton'] class JoystickButton(QtWidgets.QPushButton): sigStateChanged = QtCore.Signal(object, object) ## self, state def __init__(self, parent=None): QtWidgets.QPushButton.__init__(self, parent) self.radius = 200 self.setCheckable(True) self.state = None self.setState(0, 0) self.setFixedWidth(50) self.setFixedHeight(50) def mousePressEvent(self, ev): self.setChecked(True) lpos = ev.position() if hasattr(ev, 'position') else ev.localPos() self.pressPos = lpos ev.accept() def mouseMoveEvent(self, ev): lpos = ev.position() if hasattr(ev, 'position') else ev.localPos() dif = lpos - self.pressPos self.setState(dif.x(), -dif.y()) def mouseReleaseEvent(self, ev): self.setChecked(False) self.setState(0,0) def wheelEvent(self, ev): ev.accept() def doubleClickEvent(self, ev): ev.accept() def getState(self): return self.state def setState(self, *xy): xy = list(xy) d = hypot(xy[0], xy[1]) # length nxy = [0, 0] for i in [0,1]: if xy[i] == 0: nxy[i] = 0 else: nxy[i] = xy[i] / d if d > self.radius: d = self.radius d = (d / self.radius) ** 2 xy = [nxy[0] * d, nxy[1] * d] w2 = self.width() / 2 h2 = self.height() / 2 self.spotPos = QtCore.QPoint( int(w2 * (1 + xy[0])), int(h2 * (1 - xy[1])) ) self.update() if self.state == xy: return self.state = xy self.sigStateChanged.emit(self, self.state) def paintEvent(self, ev): super().paintEvent(ev) p = QtGui.QPainter(self) p.setBrush(QtGui.QBrush(QtGui.QColor(0,0,0))) p.drawEllipse( self.spotPos.x() - 3, self.spotPos.y() - 3, 6, 6 ) def resizeEvent(self, ev): self.setState(*self.state) super().resizeEvent(ev) if __name__ == '__main__': app = mkQApp() w = QtWidgets.QMainWindow() b = JoystickButton() w.setCentralWidget(b) w.show() w.resize(100, 100) def fn(b, s): print("state changed:", s) b.sigStateChanged.connect(fn) app.exec() if hasattr(app, 'exec') else app.exec_() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/LayoutWidget.py000066400000000000000000000065601421045507400243230ustar00rootroot00000000000000from ..Qt import QtWidgets __all__ = ['LayoutWidget'] class LayoutWidget(QtWidgets.QWidget): """ Convenience class used for laying out QWidgets in a grid. (It's just a little less effort to use than QGridLayout) """ def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.layout = QtWidgets.QGridLayout() self.setLayout(self.layout) self.items = {} self.rows = {} self.currentRow = 0 self.currentCol = 0 def nextRow(self): """Advance to next row for automatic widget placement""" self.currentRow += 1 self.currentCol = 0 def nextColumn(self, colspan=1): """Advance to next column, while returning the current column number (generally only for internal use--called by addWidget)""" self.currentCol += colspan return self.currentCol-colspan def nextCol(self, *args, **kargs): """Alias of nextColumn""" return self.nextColumn(*args, **kargs) def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs): """ Create a QLabel with *text* and place it in the next available cell (or in the cell specified) All extra keyword arguments are passed to QLabel(). Returns the created widget. """ text = QtWidgets.QLabel(text, **kargs) self.addWidget(text, row, col, rowspan, colspan) return text def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs): """ Create an empty LayoutWidget and place it in the next available cell (or in the cell specified) All extra keyword arguments are passed to :func:`LayoutWidget.__init__ ` Returns the created widget. """ layout = LayoutWidget(**kargs) self.addWidget(layout, row, col, rowspan, colspan) return layout def addWidget(self, item, row=None, col=None, rowspan=1, colspan=1): """ Add a widget to the layout and place it in the next available cell (or in the cell specified). """ if row == 'next': self.nextRow() row = self.currentRow elif row is None: row = self.currentRow if col is None: col = self.nextCol(colspan) if row not in self.rows: self.rows[row] = {} self.rows[row][col] = item self.items[item] = (row, col) self.layout.addWidget(item, row, col, rowspan, colspan) def getWidget(self, row, col): """Return the widget in (*row*, *col*)""" return self.rows[row][col] #def itemIndex(self, item): #for i in range(self.layout.count()): #if self.layout.itemAt(i).graphicsItem() is item: #return i #raise Exception("Could not determine index of item " + str(item)) #def removeItem(self, item): #"""Remove *item* from the layout.""" #ind = self.itemIndex(item) #self.layout.removeAt(ind) #self.scene().removeItem(item) #r,c = self.items[item] #del self.items[item] #del self.rows[r][c] #self.update() #def clear(self): #items = [] #for i in list(self.items.keys()): #self.removeItem(i) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/MatplotlibWidget.py000066400000000000000000000022031421045507400251430ustar00rootroot00000000000000from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure from ..Qt import QtWidgets __all__ = ['MatplotlibWidget'] class MatplotlibWidget(QtWidgets.QWidget): """ Implements a Matplotlib figure inside a QWidget. Use getFigure() and redraw() to interact with matplotlib. Example:: mw = MatplotlibWidget() subplot = mw.getFigure().add_subplot(111) subplot.plot(x,y) mw.draw() """ def __init__(self, size=(5.0, 4.0), dpi=100): QtWidgets.QWidget.__init__(self) self.fig = Figure(size, dpi=dpi) self.canvas = FigureCanvas(self.fig) self.canvas.setParent(self) self.toolbar = NavigationToolbar(self.canvas, self) self.vbox = QtWidgets.QVBoxLayout() self.vbox.addWidget(self.toolbar) self.vbox.addWidget(self.canvas) self.setLayout(self.vbox) def getFigure(self): return self.fig def draw(self): self.canvas.draw() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/MultiPlotWidget.py000066400000000000000000000057361421045507400250030ustar00rootroot00000000000000""" MultiPlotWidget.py - Convenience class--GraphicsView widget displaying a MultiPlotItem Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ from ..graphicsItems import MultiPlotItem as MultiPlotItem from ..Qt import QtCore from .GraphicsView import GraphicsView __all__ = ['MultiPlotWidget'] class MultiPlotWidget(GraphicsView): """Widget implementing a :class:`~pyqtgraph.GraphicsView` with a single :class:`~pyqtgraph.MultiPlotItem` inside.""" def __init__(self, parent=None): self.minPlotHeight = 50 self.mPlotItem = MultiPlotItem.MultiPlotItem() GraphicsView.__init__(self, parent) self.enableMouse(False) self.setCentralItem(self.mPlotItem) ## Explicitly wrap methods from mPlotItem #for m in ['setData']: #setattr(self, m, getattr(self.mPlotItem, m)) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded) def __getattr__(self, attr): ## implicitly wrap methods from plotItem if hasattr(self.mPlotItem, attr): m = getattr(self.mPlotItem, attr) if hasattr(m, '__call__'): return m raise AttributeError(attr) def setMinimumPlotHeight(self, min): """Set the minimum height for each sub-plot displayed. If the total height of all plots is greater than the height of the widget, then a scroll bar will appear to provide access to the entire set of plots. Added in version 0.9.9 """ self.minPlotHeight = min self.resizeEvent(None) def widgetGroupInterface(self): return (None, MultiPlotWidget.saveState, MultiPlotWidget.restoreState) def saveState(self): return {} #return self.plotItem.saveState() def restoreState(self, state): pass #return self.plotItem.restoreState(state) def close(self): self.mPlotItem.close() self.mPlotItem = None self.setParent(None) GraphicsView.close(self) def setRange(self, *args, **kwds): GraphicsView.setRange(self, *args, **kwds) if self.centralWidget is not None: r = self.range minHeight = len(self.mPlotItem.plots) * self.minPlotHeight if r.height() < minHeight: r.setHeight(minHeight) r.setWidth(r.width() - self.verticalScrollBar().width()) self.centralWidget.setGeometry(r) def resizeEvent(self, ev): if self.closed: return if self.autoPixelRange: self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height()) MultiPlotWidget.setRange(self, self.range, padding=0, disableAutoPixel=False) ## we do this because some subclasses like to redefine setRange in an incompatible way. self.updateMatrix() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/PathButton.py000066400000000000000000000031241421045507400237630ustar00rootroot00000000000000from .. import functions as fn from ..Qt import QtCore, QtGui, QtWidgets __all__ = ['PathButton'] class PathButton(QtWidgets.QPushButton): """Simple PushButton extension that paints a QPainterPath centered on its face. """ def __init__(self, parent=None, path=None, pen='default', brush=None, size=(30,30), margin=7): QtWidgets.QPushButton.__init__(self, parent) self.margin = margin self.path = None if pen == 'default': pen = 'k' self.setPen(pen) self.setBrush(brush) if path is not None: self.setPath(path) if size is not None: self.setFixedWidth(size[0]) self.setFixedHeight(size[1]) def setBrush(self, brush): self.brush = fn.mkBrush(brush) def setPen(self, *args, **kwargs): self.pen = fn.mkPen(*args, **kwargs) def setPath(self, path): self.path = path self.update() def paintEvent(self, ev): super().paintEvent(ev) margin = self.margin geom = QtCore.QRectF(0, 0, self.width(), self.height()).adjusted(margin, margin, -margin, -margin) rect = self.path.boundingRect() scale = min(geom.width() / float(rect.width()), geom.height() / float(rect.height())) p = QtGui.QPainter(self) p.setRenderHint(p.RenderHint.Antialiasing) p.translate(geom.center()) p.scale(scale, scale) p.translate(-rect.center()) p.setPen(self.pen) p.setBrush(self.brush) p.drawPath(self.path) p.end() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/PenPreviewLabel.py000066400000000000000000000021221421045507400247140ustar00rootroot00000000000000from ..Qt import QtWidgets, QtGui, QtCore from ..functions import mkPen class PenPreviewLabel(QtWidgets.QLabel): def __init__(self, param): super().__init__() self.param = param self.pen = QtGui.QPen(self.param.pen) param.sigValueChanging.connect(self.onPenChanging) def onPenChanging(self, param, val): self.pen = QtGui.QPen(val) self.update() def paintEvent(self, ev): path = QtGui.QPainterPath() displaySize = self.size() w, h = displaySize.width(), displaySize.height() # draw a squiggle with the pen path.moveTo(w * .2, h * .2) path.lineTo(w * .4, h * .8) path.cubicTo(w * .5, h * .1, w * .7, h * .1, w * .8, h * .8) painter = QtGui.QPainter(self) painter.setPen(self.pen) painter.drawPath(path) # No indication of "cosmetic" from just the paint path, so add something extra in that case if self.pen.isCosmetic(): painter.setPen(mkPen('k')) painter.drawText(QtCore.QPointF(w * 0.81, 12), 'C') painter.end() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/PlotWidget.py000066400000000000000000000102541421045507400237570ustar00rootroot00000000000000""" PlotWidget.py - Convenience class--GraphicsView widget displaying a single PlotItem Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ from ..graphicsItems.PlotItem import * from ..Qt import QtCore, QtWidgets from .GraphicsView import * __all__ = ['PlotWidget'] class PlotWidget(GraphicsView): # signals wrapped from PlotItem / ViewBox sigRangeChanged = QtCore.Signal(object, object) sigTransformChanged = QtCore.Signal(object) """ :class:`GraphicsView ` widget with a single :class:`PlotItem ` inside. The following methods are wrapped directly from PlotItem: :func:`addItem `, :func:`removeItem `, :func:`clear `, :func:`setAxisItems `, :func:`setXRange `, :func:`setYRange `, :func:`setRange `, :func:`autoRange `, :func:`setXLink `, :func:`setYLink `, :func:`viewRect `, :func:`setMouseEnabled `, :func:`enableAutoRange `, :func:`disableAutoRange `, :func:`setAspectLocked `, :func:`setLimits `, :func:`register `, :func:`unregister ` For all other methods, use :func:`getPlotItem `. """ def __init__(self, parent=None, background='default', plotItem=None, **kargs): """When initializing PlotWidget, *parent* and *background* are passed to :func:`GraphicsWidget.__init__() ` and all others are passed to :func:`PlotItem.__init__() `.""" GraphicsView.__init__(self, parent, background=background) self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) self.enableMouse(False) if plotItem is None: self.plotItem = PlotItem(**kargs) else: self.plotItem = plotItem self.setCentralItem(self.plotItem) ## Explicitly wrap methods from plotItem ## NOTE: If you change this list, update the documentation above as well. for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setAxisItems', 'setXRange', 'setYRange', 'setRange', 'setAspectLocked', 'setMouseEnabled', 'setXLink', 'setYLink', 'enableAutoRange', 'disableAutoRange', 'setLimits', 'register', 'unregister', 'viewRect']: setattr(self, m, getattr(self.plotItem, m)) #QtCore.QObject.connect(self.plotItem, QtCore.SIGNAL('viewChanged'), self.viewChanged) self.plotItem.sigRangeChanged.connect(self.viewRangeChanged) def close(self): self.plotItem.close() self.plotItem = None #self.scene().clear() #self.mPlotItem.close() self.setParent(None) super(PlotWidget, self).close() def __getattr__(self, attr): ## implicitly wrap methods from plotItem if hasattr(self.plotItem, attr): m = getattr(self.plotItem, attr) if hasattr(m, '__call__'): return m raise AttributeError(attr) def viewRangeChanged(self, view, range): #self.emit(QtCore.SIGNAL('viewChanged'), *args) self.sigRangeChanged.emit(self, range) def widgetGroupInterface(self): return (None, PlotWidget.saveState, PlotWidget.restoreState) def saveState(self): return self.plotItem.saveState() def restoreState(self, state): return self.plotItem.restoreState(state) def getPlotItem(self): """Return the PlotItem contained within.""" return self.plotItem pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/ProgressDialog.py000066400000000000000000000227651421045507400246330ustar00rootroot00000000000000from time import perf_counter from ..Qt import QtCore, QtGui, QtWidgets __all__ = ['ProgressDialog'] class ProgressDialog(QtWidgets.QProgressDialog): """ Extends QProgressDialog: * Adds context management so the dialog may be used in `with` statements * Allows nesting multiple progress dialogs Example:: with ProgressDialog("Processing..", minVal, maxVal) as dlg: # do stuff dlg.setValue(i) ## could also use dlg += 1 if dlg.wasCanceled(): raise Exception("Processing canceled by user") """ allDialogs = [] def __init__(self, labelText, minimum=0, maximum=100, cancelText='Cancel', parent=None, wait=250, busyCursor=False, disable=False, nested=False): """ ============== ================================================================ **Arguments:** labelText (required) cancelText Text to display on cancel button, or None to disable it. minimum maximum parent wait Length of time (im ms) to wait before displaying dialog busyCursor If True, show busy cursor until dialog finishes disable If True, the progress dialog will not be displayed and calls to wasCanceled() will always return False. If ProgressDialog is entered from a non-gui thread, it will always be disabled. nested (bool) If True, then this progress bar will be displayed inside any pre-existing progress dialogs that also allow nesting. ============== ================================================================ """ # attributes used for nesting dialogs self.nestedLayout = None self._nestableWidgets = None self._nestingReady = False self._topDialog = None self._subBars = [] self.nested = nested # for rate-limiting Qt event processing during progress bar update self._lastProcessEvents = None isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() self.disabled = disable or (not isGuiThread) if self.disabled: return noCancel = False if cancelText is None: cancelText = '' noCancel = True self.busyCursor = busyCursor QtWidgets.QProgressDialog.__init__(self, labelText, cancelText, minimum, maximum, parent) # If this will be a nested dialog, then we ignore the wait time if nested is True and len(ProgressDialog.allDialogs) > 0: self.setMinimumDuration(2**30) else: self.setMinimumDuration(wait) self.setWindowModality(QtCore.Qt.WindowModality.WindowModal) self.setValue(self.minimum()) if noCancel: self.setCancelButton(None) def __enter__(self): if self.disabled: return self if self.busyCursor: QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor)) if self.nested and len(ProgressDialog.allDialogs) > 0: topDialog = ProgressDialog.allDialogs[0] topDialog._addSubDialog(self) self._topDialog = topDialog topDialog.canceled.connect(self.cancel) ProgressDialog.allDialogs.append(self) return self def __exit__(self, exType, exValue, exTrace): if self.disabled: return if self.busyCursor: QtWidgets.QApplication.restoreOverrideCursor() if self._topDialog is not None: self._topDialog._removeSubDialog(self) ProgressDialog.allDialogs.pop(-1) self.setValue(self.maximum()) def __iadd__(self, val): """Use inplace-addition operator for easy incrementing.""" if self.disabled: return self self.setValue(self.value()+val) return self def _addSubDialog(self, dlg): # insert widgets from another dialog into this one. # set a new layout and arrange children into it (if needed). self._prepareNesting() bar, btn = dlg._extractWidgets() # where should we insert this widget? Find the first slot with a # "removed" widget (that was left as a placeholder) inserted = False for i,bar2 in enumerate(self._subBars): if bar2.hidden: self._subBars.pop(i) bar2.hide() bar2.setParent(None) self._subBars.insert(i, bar) inserted = True break if not inserted: self._subBars.append(bar) # reset the layout while self.nestedLayout.count() > 0: self.nestedLayout.takeAt(0) for b in self._subBars: self.nestedLayout.addWidget(b) def _removeSubDialog(self, dlg): # don't remove the widget just yet; instead we hide it and leave it in # as a placeholder. bar, btn = dlg._extractWidgets() bar.hide() def _prepareNesting(self): # extract all child widgets and place into a new layout that we can add to if self._nestingReady is False: # top layout contains progress bars + cancel button at the bottom self._topLayout = QtWidgets.QGridLayout() self.setLayout(self._topLayout) self._topLayout.setContentsMargins(0, 0, 0, 0) # A vbox to contain all progress bars self.nestedVBox = QtWidgets.QWidget() self._topLayout.addWidget(self.nestedVBox, 0, 0, 1, 2) self.nestedLayout = QtWidgets.QVBoxLayout() self.nestedVBox.setLayout(self.nestedLayout) # re-insert all widgets bar, btn = self._extractWidgets() self.nestedLayout.addWidget(bar) self._subBars.append(bar) self._topLayout.addWidget(btn, 1, 1, 1, 1) self._topLayout.setColumnStretch(0, 100) self._topLayout.setColumnStretch(1, 1) self._topLayout.setRowStretch(0, 100) self._topLayout.setRowStretch(1, 1) self._nestingReady = True def _extractWidgets(self): # return: # 1. a single widget containing the label and progress bar # 2. the cancel button if self._nestableWidgets is None: widgets = [ch for ch in self.children() if isinstance(ch, QtWidgets.QWidget)] label = [ch for ch in self.children() if isinstance(ch, QtWidgets.QLabel)][0] bar = [ch for ch in self.children() if isinstance(ch, QtWidgets.QProgressBar)][0] btn = [ch for ch in self.children() if isinstance(ch, QtWidgets.QPushButton)][0] sw = ProgressWidget(label, bar) self._nestableWidgets = (sw, btn) return self._nestableWidgets def resizeEvent(self, ev): if self._nestingReady: # don't let progress dialog manage widgets anymore. return return super().resizeEvent(ev) ## wrap all other functions to make sure they aren't being called from non-gui threads def setValue(self, val): if self.disabled: return QtWidgets.QProgressDialog.setValue(self, val) # Qt docs say this should happen automatically, but that doesn't seem # to be the case. if self.windowModality() == QtCore.Qt.WindowModality.WindowModal: now = perf_counter() if self._lastProcessEvents is None or (now - self._lastProcessEvents) > 0.2: QtWidgets.QApplication.processEvents() self._lastProcessEvents = now def setLabelText(self, val): if self.disabled: return QtWidgets.QProgressDialog.setLabelText(self, val) def setMaximum(self, val): if self.disabled: return QtWidgets.QProgressDialog.setMaximum(self, val) def setMinimum(self, val): if self.disabled: return QtWidgets.QProgressDialog.setMinimum(self, val) def wasCanceled(self): if self.disabled: return False return QtWidgets.QProgressDialog.wasCanceled(self) def maximum(self): if self.disabled: return 0 return QtWidgets.QProgressDialog.maximum(self) def minimum(self): if self.disabled: return 0 return QtWidgets.QProgressDialog.minimum(self) class ProgressWidget(QtWidgets.QWidget): """Container for a label + progress bar that also allows its child widgets to be hidden without changing size. """ def __init__(self, label, bar): QtWidgets.QWidget.__init__(self) self.hidden = False self.layout = QtWidgets.QVBoxLayout() self.setLayout(self.layout) self.label = label self.bar = bar self.layout.addWidget(label) self.layout.addWidget(bar) def eventFilter(self, obj, ev): return ev.type() == QtCore.QEvent.Type.Paint def hide(self): # hide label and bar, but continue occupying the same space in the layout for widget in (self.label, self.bar): widget.installEventFilter(self) widget.update() self.hidden = True pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/RawImageWidget.py000066400000000000000000000141511421045507400245350ustar00rootroot00000000000000""" RawImageWidget.py Copyright 2010-2016 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ from .. import functions as fn from .. import getConfigOption, getCupy from ..Qt import QtCore, QtGui, QtWidgets try: QOpenGLWidget = QtWidgets.QOpenGLWidget from OpenGL.GL import * # noqa HAVE_OPENGL = True except (ImportError, AttributeError): # Would prefer `except ImportError` here, but some versions of pyopengl generate # AttributeError upon import HAVE_OPENGL = False __all__ = ['RawImageWidget'] class RawImageWidget(QtWidgets.QWidget): """ Widget optimized for very fast video display. Generally using an ImageItem inside GraphicsView is fast enough. On some systems this may provide faster video. See the VideoSpeedTest example for benchmarking. """ def __init__(self, parent=None, scaled=False): """ Setting scaled=True will cause the entire image to be displayed within the boundaries of the widget. This also greatly reduces the speed at which it will draw frames. """ QtWidgets.QWidget.__init__(self, parent) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)) self.scaled = scaled self.opts = None self.image = None self._cp = getCupy() def setImage(self, img, *args, **kargs): """ img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). Extra arguments are sent to functions.makeARGB """ if getConfigOption('imageAxisOrder') == 'col-major': img = img.swapaxes(0, 1) self.opts = (img, args, kargs) self.image = None self.update() def paintEvent(self, ev): if self.opts is None: return if self.image is None: argb, alpha = fn.makeARGB(self.opts[0], *self.opts[1], **self.opts[2]) if self._cp and self._cp.get_array_module(argb) == self._cp: argb = argb.get() # transfer GPU data back to the CPU self.image = fn.makeQImage(argb, alpha, copy=False, transpose=False) self.opts = () # if self.pixmap is None: # self.pixmap = QtGui.QPixmap.fromImage(self.image) p = QtGui.QPainter(self) if self.scaled: rect = self.rect() ar = rect.width() / float(rect.height()) imar = self.image.width() / float(self.image.height()) if ar > imar: rect.setWidth(int(rect.width() * imar / ar)) else: rect.setHeight(int(rect.height() * ar / imar)) p.drawImage(rect, self.image) else: p.drawImage(QtCore.QPointF(), self.image) # p.drawPixmap(self.rect(), self.pixmap) p.end() if HAVE_OPENGL: __all__.append('RawImageGLWidget') class RawImageGLWidget(QOpenGLWidget): """ Similar to RawImageWidget, but uses a GL widget to do all drawing. Performance varies between platforms; see examples/VideoSpeedTest for benchmarking. Checks if setConfigOptions(imageAxisOrder='row-major') was set. """ def __init__(self, parent=None, scaled=False): QOpenGLWidget.__init__(self, parent) self.scaled = scaled self.image = None self.uploaded = False self.smooth = False self.opts = None def setImage(self, img, *args, **kargs): """ img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). Extra arguments are sent to functions.makeARGB """ if getConfigOption('imageAxisOrder') == 'col-major': img = img.swapaxes(0, 1) self.opts = (img, args, kargs) self.image = None self.uploaded = False self.update() def initializeGL(self): self.texture = glGenTextures(1) def uploadTexture(self): glEnable(GL_TEXTURE_2D) glBindTexture(GL_TEXTURE_2D, self.texture) if self.smooth: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) else: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) # glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) ## Test texture dimensions first # shape = self.image.shape # glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) # if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: # raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) h, w = self.image.shape[:2] glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, self.image) glDisable(GL_TEXTURE_2D) self.uploaded = True def paintGL(self): glClear(GL_COLOR_BUFFER_BIT) if self.image is None: if self.opts is None: return img, args, kwds = self.opts kwds['useRGBA'] = True self.image, alpha = fn.makeARGB(img, *args, **kwds) if not self.uploaded: self.uploadTexture() glEnable(GL_TEXTURE_2D) glBindTexture(GL_TEXTURE_2D, self.texture) glColor4f(1, 1, 1, 1) glBegin(GL_QUADS) glTexCoord2f(0, 1) glVertex3f(-1, -1, 0) glTexCoord2f(1, 1) glVertex3f(1, -1, 0) glTexCoord2f(1, 0) glVertex3f(1, 1, 0) glTexCoord2f(0, 0) glVertex3f(-1, 1, 0) glEnd() glDisable(GL_TEXTURE_2D) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/RemoteGraphicsView.py000066400000000000000000000316441421045507400254520ustar00rootroot00000000000000from ..Qt import QT_LIB, QtCore, QtGui, QtWidgets if QT_LIB.startswith('PyQt'): from ..Qt import sip import atexit import mmap import os import random import sys import tempfile from .. import CONFIG_OPTIONS from .. import multiprocess as mp from .GraphicsView import GraphicsView __all__ = ['RemoteGraphicsView'] def serialize_mouse_enum(*args): # PyQt6 can pickle enums and flags but cannot cast to int # PyQt5 5.12, PyQt5 5.15, PySide2 5.15, PySide6 can pickle enums but not flags # PySide2 5.12 cannot pickle enums nor flags # MouseButtons and KeyboardModifiers are flags if QT_LIB != 'PyQt6': args = [int(x) for x in args] return args class MouseEvent(QtGui.QMouseEvent): @staticmethod def get_state(obj, picklable=False): typ = obj.type() if isinstance(typ, int): # PyQt6 returns an int here instead of QEvent.Type, # but its QtGui.QMouseEvent constructor takes only QEvent.Type. # Note however that its QtCore.QEvent constructor accepts both # QEvent.Type and int. typ = QtCore.QEvent.Type(typ) lpos = obj.position() if hasattr(obj, 'position') else obj.localPos() gpos = obj.globalPosition() if hasattr(obj, 'globalPosition') else obj.screenPos() btn, btns, mods = obj.button(), obj.buttons(), obj.modifiers() if picklable: typ, btn, btns, mods = serialize_mouse_enum(typ, btn, btns, mods) return typ, lpos, gpos, btn, btns, mods def __init__(self, rhs): super().__init__(*self.get_state(rhs)) def __getstate__(self): return self.get_state(self, picklable=True) def __setstate__(self, state): typ, lpos, gpos, btn, btns, mods = state typ = QtCore.QEvent.Type(typ) if QT_LIB != 'PyQt6': btn = QtCore.Qt.MouseButton(btn) btns = QtCore.Qt.MouseButtons(btns) mods = QtCore.Qt.KeyboardModifiers(mods) super().__init__(typ, lpos, gpos, btn, btns, mods) class WheelEvent(QtGui.QWheelEvent): @staticmethod def get_state(obj, picklable=False): # {PyQt6, PySide6} have position() # {PyQt5, PySide2} 5.15 have position() # {PyQt5, PySide2} 5.15 have posF() (contrary to C++ docs) # {PyQt5, PySide2} 5.12 have posF() lpos = obj.position() if hasattr(obj, 'position') else obj.posF() gpos = obj.globalPosition() if hasattr(obj, 'globalPosition') else obj.globalPosF() pixdel, angdel, btns = obj.pixelDelta(), obj.angleDelta(), obj.buttons() mods, phase, inverted = obj.modifiers(), obj.phase(), obj.inverted() if picklable: btns, mods, phase = serialize_mouse_enum(btns, mods, phase) return lpos, gpos, pixdel, angdel, btns, mods, phase, inverted def __init__(self, rhs): items = list(self.get_state(rhs)) items[1] = items[0] # gpos = lpos super().__init__(*items) def __getstate__(self): return self.get_state(self, picklable=True) def __setstate__(self, state): pos, gpos, pixdel, angdel, btns, mods, phase, inverted = state if QT_LIB != 'PyQt6': btns = QtCore.Qt.MouseButtons(btns) mods = QtCore.Qt.KeyboardModifiers(mods) phase = QtCore.Qt.ScrollPhase(phase) super().__init__(pos, gpos, pixdel, angdel, btns, mods, phase, inverted) class EnterEvent(QtGui.QEnterEvent): @staticmethod def get_state(obj): lpos = obj.position() if hasattr(obj, 'position') else obj.localPos() wpos = obj.scenePosition() if hasattr(obj, 'scenePosition') else obj.windowPos() gpos = obj.globalPosition() if hasattr(obj, 'globalPosition') else obj.screenPos() return lpos, wpos, gpos def __init__(self, rhs): super().__init__(*self.get_state(rhs)) def __getstate__(self): return self.get_state(self) def __setstate__(self, state): super().__init__(*state) class LeaveEvent(QtCore.QEvent): @staticmethod def get_state(obj, picklable=False): typ = obj.type() if picklable: typ, = serialize_mouse_enum(typ) return typ, def __init__(self, rhs): super().__init__(*self.get_state(rhs)) def __getstate__(self): return self.get_state(self, picklable=True) def __setstate__(self, state): typ, = state typ = QtCore.QEvent.Type(typ) super().__init__(typ) class RemoteGraphicsView(QtWidgets.QWidget): """ Replacement for GraphicsView that does all scene management and rendering on a remote process, while displaying on the local widget. GraphicsItems must be created by proxy to the remote process. """ def __init__(self, parent=None, *args, **kwds): """ The keyword arguments 'useOpenGL' and 'backgound', if specified, are passed to the remote GraphicsView.__init__(). All other keyword arguments are passed to multiprocess.QtProcess.__init__(). """ self._img = None self._imgReq = None self._sizeHint = (640,480) ## no clue why this is needed, but it seems to be the default sizeHint for GraphicsView. ## without it, the widget will not compete for space against another GraphicsView. QtWidgets.QWidget.__init__(self) # separate local keyword arguments from remote. remoteKwds = {} for kwd in ['useOpenGL', 'background']: if kwd in kwds: remoteKwds[kwd] = kwds.pop(kwd) self._proc = mp.QtProcess(**kwds) self.pg = self._proc._import('pyqtgraph') self.pg.setConfigOptions(**CONFIG_OPTIONS) rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView') self._view = rpgRemote.Renderer(*args, **remoteKwds) self._view._setProxyOptions(deferGetattr=True) self.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) self.setMouseTracking(True) self.shm = None shmFileName = self._view.shmFileName() if sys.platform.startswith('win'): self.shmtag = shmFileName else: self.shmFile = open(shmFileName, 'r') self._view.sceneRendered.connect(mp.proxy(self.remoteSceneChanged)) #, callSync='off')) ## Note: we need synchronous signals ## even though there is no return value-- ## this informs the renderer that it is ## safe to begin rendering again. for method in ['scene', 'setCentralItem']: setattr(self, method, getattr(self._view, method)) def resizeEvent(self, ev): ret = super().resizeEvent(ev) self._view.resize(self.size(), _callSync='off') return ret def sizeHint(self): return QtCore.QSize(*self._sizeHint) def remoteSceneChanged(self, data): w, h, size, newfile = data #self._sizeHint = (whint, hhint) if self.shm is None or self.shm.size != size: if self.shm is not None: self.shm.close() if sys.platform.startswith('win'): self.shmtag = newfile ## on windows, we create a new tag for every resize self.shm = mmap.mmap(-1, size, self.shmtag) ## can't use tmpfile on windows because the file can only be opened once. else: self.shm = mmap.mmap(self.shmFile.fileno(), size, mmap.MAP_SHARED, mmap.PROT_READ) self._img = QtGui.QImage(self.shm, w, h, QtGui.QImage.Format.Format_RGB32).copy() self.update() def paintEvent(self, ev): if self._img is None: return p = QtGui.QPainter(self) p.drawImage(self.rect(), self._img, self._img.rect()) p.end() def mousePressEvent(self, ev): self._view.mousePressEvent(MouseEvent(ev), _callSync='off') ev.accept() return super().mousePressEvent(ev) def mouseReleaseEvent(self, ev): self._view.mouseReleaseEvent(MouseEvent(ev), _callSync='off') ev.accept() return super().mouseReleaseEvent(ev) def mouseMoveEvent(self, ev): self._view.mouseMoveEvent(MouseEvent(ev), _callSync='off') ev.accept() return super().mouseMoveEvent(ev) def wheelEvent(self, ev): self._view.wheelEvent(WheelEvent(ev), _callSync='off') ev.accept() return super().wheelEvent(ev) def enterEvent(self, ev): self._view.enterEvent(EnterEvent(ev), _callSync='off') return super().enterEvent(ev) def leaveEvent(self, ev): self._view.leaveEvent(LeaveEvent(ev), _callSync='off') return super().leaveEvent(ev) def remoteProcess(self): """Return the remote process handle. (see multiprocess.remoteproxy.RemoteEventHandler)""" return self._proc def close(self): """Close the remote process. After this call, the widget will no longer be updated.""" self._view.sceneRendered.disconnect() self._proc.close() class Renderer(GraphicsView): ## Created by the remote process to handle render requests sceneRendered = QtCore.Signal(object) def __init__(self, *args, **kwds): ## Create shared memory for rendered image #pg.dbg(namespace={'r': self}) if sys.platform.startswith('win'): self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)]) self.shm = mmap.mmap(-1, mmap.PAGESIZE, self.shmtag) # use anonymous mmap on windows else: self.shmFile = tempfile.NamedTemporaryFile(prefix='pyqtgraph_shmem_') self.shmFile.write(b'\x00' * (mmap.PAGESIZE+1)) self.shmFile.flush() fd = self.shmFile.fileno() self.shm = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE) atexit.register(self.close) GraphicsView.__init__(self, *args, **kwds) self.scene().changed.connect(self.update) self.img = None self.renderTimer = QtCore.QTimer() self.renderTimer.timeout.connect(self.renderView) self.renderTimer.start(16) def close(self): self.shm.close() if not sys.platform.startswith('win'): self.shmFile.close() def shmFileName(self): if sys.platform.startswith('win'): return self.shmtag else: return self.shmFile.name def update(self): self.img = None return super().update() def resize(self, size): oldSize = self.size() super().resize(size) self.resizeEvent(QtGui.QResizeEvent(size, oldSize)) self.update() def renderView(self): if self.img is None: ## make sure shm is large enough and get its address if self.width() == 0 or self.height() == 0: return dpr = self.devicePixelRatioF() iwidth = int(self.width() * dpr) iheight = int(self.height() * dpr) size = iwidth * iheight * 4 if size > self.shm.size(): if sys.platform.startswith('win'): ## windows says "WindowsError: [Error 87] the parameter is incorrect" if we try to resize the mmap self.shm.close() ## it also says (sometimes) 'access is denied' if we try to reuse the tag. self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)]) self.shm = mmap.mmap(-1, size, self.shmtag) elif sys.platform == 'darwin': self.shm.close() fd = self.shmFile.fileno() os.ftruncate(fd, size + 1) self.shm = mmap.mmap(fd, size, mmap.MAP_SHARED, mmap.PROT_WRITE) else: self.shm.resize(size) ## render the scene directly to shared memory # see functions.py::makeQImage() for rationale if QT_LIB.startswith('PyQt'): # PyQt5, PyQt6 >= 6.0.1 img_ptr = int(sip.voidptr(self.shm)) else: # PySide2, PySide6 img_ptr = self.shm self.img = QtGui.QImage(img_ptr, iwidth, iheight, QtGui.QImage.Format.Format_RGB32) self.img.setDevicePixelRatio(dpr) self.img.fill(0xffffffff) p = QtGui.QPainter(self.img) self.render(p, self.viewRect(), self.rect()) p.end() self.sceneRendered.emit((iwidth, iheight, self.shm.size(), self.shmFileName())) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/ScatterPlotWidget.py000066400000000000000000000263651421045507400253170ustar00rootroot00000000000000from collections import OrderedDict import numpy as np from .. import functions as fn from .. import getConfigOption from .. import parametertree as ptree from ..graphicsItems.TextItem import TextItem from ..Qt import QtCore, QtWidgets from .ColorMapWidget import ColorMapParameter from .DataFilterWidget import DataFilterParameter from .PlotWidget import PlotWidget __all__ = ['ScatterPlotWidget'] class ScatterPlotWidget(QtWidgets.QSplitter): """ This is a high-level widget for exploring relationships in tabular data. Given a multi-column record array, the widget displays a scatter plot of a specific subset of the data. Includes controls for selecting the columns to plot, filtering data, and determining symbol color and shape. The widget consists of four components: 1) A list of column names from which the user may select 1 or 2 columns to plot. If one column is selected, the data for that column will be plotted in a histogram-like manner by using :func:`pseudoScatter() `. If two columns are selected, then the scatter plot will be generated with x determined by the first column that was selected and y by the second. 2) A DataFilter that allows the user to select a subset of the data by specifying multiple selection criteria. 3) A ColorMap that allows the user to determine how points are colored by specifying multiple criteria. 4) A PlotWidget for displaying the data. """ sigScatterPlotClicked = QtCore.Signal(object, object, object) sigScatterPlotHovered = QtCore.Signal(object, object, object) def __init__(self, parent=None): QtWidgets.QSplitter.__init__(self, QtCore.Qt.Orientation.Horizontal) self.ctrlPanel = QtWidgets.QSplitter(QtCore.Qt.Orientation.Vertical) self.addWidget(self.ctrlPanel) self.fieldList = QtWidgets.QListWidget() self.fieldList.setSelectionMode(self.fieldList.SelectionMode.ExtendedSelection) self.ptree = ptree.ParameterTree(showHeader=False) self.filter = DataFilterParameter() self.colorMap = ColorMapParameter() self.params = ptree.Parameter.create(name='params', type='group', children=[self.filter, self.colorMap]) self.ptree.setParameters(self.params, showTop=False) self.plot = PlotWidget() self.ctrlPanel.addWidget(self.fieldList) self.ctrlPanel.addWidget(self.ptree) self.addWidget(self.plot) fg = fn.mkColor(getConfigOption('foreground')) fg.setAlpha(150) self.filterText = TextItem(border=getConfigOption('foreground'), color=fg) self.filterText.setPos(60,20) self.filterText.setParentItem(self.plot.plotItem) self.data = None self.indices = None self.mouseOverField = None self.scatterPlot = None self.selectionScatter = None self.selectedIndices = [] self.style = dict(pen=None, symbol='o') self._visibleXY = None # currently plotted points self._visibleData = None # currently plotted records self._visibleIndices = None self._indexMap = None self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged) self.filter.sigFilterChanged.connect(self.filterChanged) self.colorMap.sigColorMapChanged.connect(self.updatePlot) def setFields(self, fields, mouseOverField=None): """ Set the list of field names/units to be processed. The format of *fields* is the same as used by :func:`ColorMapWidget.setFields ` """ self.fields = OrderedDict(fields) self.mouseOverField = mouseOverField self.fieldList.clear() for f,opts in fields: item = QtWidgets.QListWidgetItem(f) item.opts = opts item = self.fieldList.addItem(item) self.filter.setFields(fields) self.colorMap.setFields(fields) def setSelectedFields(self, *fields): self.fieldList.itemSelectionChanged.disconnect(self.fieldSelectionChanged) try: self.fieldList.clearSelection() for f in fields: i = list(self.fields.keys()).index(f) item = self.fieldList.item(i) item.setSelected(True) finally: self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged) self.fieldSelectionChanged() def setData(self, data): """ Set the data to be processed and displayed. Argument must be a numpy record array. """ self.data = data self.indices = np.arange(len(data)) self.filtered = None self.filteredIndices = None self.updatePlot() def setSelectedIndices(self, inds): """Mark the specified indices as selected. Must be a sequence of integers that index into the array given in setData(). """ self.selectedIndices = inds self.updateSelected() def setSelectedPoints(self, points): """Mark the specified points as selected. Must be a list of points as generated by the sigScatterPlotClicked signal. """ self.setSelectedIndices([pt.originalIndex for pt in points]) def fieldSelectionChanged(self): sel = self.fieldList.selectedItems() if len(sel) > 2: self.fieldList.blockSignals(True) try: for item in sel[1:-1]: item.setSelected(False) finally: self.fieldList.blockSignals(False) self.updatePlot() def filterChanged(self, f): self.filtered = None self.updatePlot() desc = self.filter.describe() if len(desc) == 0: self.filterText.setVisible(False) else: self.filterText.setText('\n'.join(desc)) self.filterText.setVisible(True) def updatePlot(self): self.plot.clear() if self.data is None or len(self.data) == 0: return if self.filtered is None: mask = self.filter.generateMask(self.data) self.filtered = self.data[mask] self.filteredIndices = self.indices[mask] data = self.filtered if len(data) == 0: return colors = np.array([fn.mkBrush(*x) for x in self.colorMap.map(data)]) style = self.style.copy() ## Look up selected columns and units sel = list([str(item.text()) for item in self.fieldList.selectedItems()]) units = list([item.opts.get('units', '') for item in self.fieldList.selectedItems()]) if len(sel) == 0: self.plot.setTitle('') return if len(sel) == 1: self.plot.setLabels(left=('N', ''), bottom=(sel[0], units[0]), title='') if len(data) == 0: return #x = data[sel[0]] #y = None xy = [data[sel[0]], None] elif len(sel) == 2: self.plot.setLabels(left=(sel[1],units[1]), bottom=(sel[0],units[0])) if len(data) == 0: return xy = [data[sel[0]], data[sel[1]]] #xydata = [] #for ax in [0,1]: #d = data[sel[ax]] ### scatter catecorical values just a bit so they show up better in the scatter plot. ##if sel[ax] in ['MorphologyBSMean', 'MorphologyTDMean', 'FIType']: ##d += np.random.normal(size=len(cells), scale=0.1) #xydata.append(d) #x,y = xydata ## convert enum-type fields to float, set axis labels enum = [False, False] for i in [0,1]: axis = self.plot.getAxis(['bottom', 'left'][i]) if xy[i] is not None and (self.fields[sel[i]].get('mode', None) == 'enum' or xy[i].dtype.kind in ('S', 'O')): vals = self.fields[sel[i]].get('values', list(set(xy[i]))) xy[i] = np.array([vals.index(x) if x in vals else len(vals) for x in xy[i]], dtype=float) axis.setTicks([list(enumerate(vals))]) enum[i] = True else: axis.setTicks(None) # reset to automatic ticking ## mask out any nan values mask = np.ones(len(xy[0]), dtype=bool) if xy[0].dtype.kind == 'f': mask &= np.isfinite(xy[0]) if xy[1] is not None and xy[1].dtype.kind == 'f': mask &= np.isfinite(xy[1]) xy[0] = xy[0][mask] style['symbolBrush'] = colors[mask] data = data[mask] indices = self.filteredIndices[mask] ## Scatter y-values for a histogram-like appearance if xy[1] is None: ## column scatter plot xy[1] = fn.pseudoScatter(xy[0]) else: ## beeswarm plots xy[1] = xy[1][mask] for ax in [0,1]: if not enum[ax]: continue imax = int(xy[ax].max()) if len(xy[ax]) > 0 else 0 for i in range(imax+1): keymask = xy[ax] == i scatter = fn.pseudoScatter(xy[1-ax][keymask], bidir=True) if len(scatter) == 0: continue smax = np.abs(scatter).max() if smax != 0: scatter *= 0.2 / smax xy[ax][keymask] += scatter if self.scatterPlot is not None: try: self.scatterPlot.sigPointsClicked.disconnect(self.plotClicked) except: pass self._visibleXY = xy self._visibleData = data self._visibleIndices = indices self._indexMap = None self.scatterPlot = self.plot.plot(xy[0], xy[1], data=data, **style) self.scatterPlot.sigPointsClicked.connect(self.plotClicked) self.scatterPlot.sigPointsHovered.connect(self.plotHovered) self.updateSelected() def updateSelected(self): if self._visibleXY is None: return # map from global index to visible index indMap = self._getIndexMap() inds = [indMap[i] for i in self.selectedIndices if i in indMap] x,y = self._visibleXY[0][inds], self._visibleXY[1][inds] if self.selectionScatter is not None: self.plot.plotItem.removeItem(self.selectionScatter) if len(x) == 0: return self.selectionScatter = self.plot.plot(x, y, pen=None, symbol='s', symbolSize=12, symbolBrush=None, symbolPen='y') def _getIndexMap(self): # mapping from original data index to visible point index if self._indexMap is None: self._indexMap = {j:i for i,j in enumerate(self._visibleIndices)} return self._indexMap def plotClicked(self, plot, points, ev): # Tag each point with its index into the original dataset for pt in points: pt.originalIndex = self._visibleIndices[pt.index()] self.sigScatterPlotClicked.emit(self, points, ev) def plotHovered(self, plot, points, ev): self.sigScatterPlotHovered.emit(self, points, ev) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/SpinBox.py000066400000000000000000000607311421045507400232640ustar00rootroot00000000000000import decimal import re from math import isinf, isnan from .. import functions as fn from ..Qt import QtCore, QtGui, QtWidgets from ..SignalProxy import SignalProxy __all__ = ['SpinBox'] class SpinBox(QtWidgets.QAbstractSpinBox): """ **Bases:** QtWidgets.QAbstractSpinBox Extension of QSpinBox widget for selection of a numerical value. Adds many extra features: * SI prefix notation (eg, automatically display "300 mV" instead of "0.003 V") * Float values with linear and decimal stepping (1-9, 10-90, 100-900, etc.) * Option for unbounded values * Delayed signals (allows multiple rapid changes with only one change signal) * Customizable text formatting ============================= ============================================== **Signals:** valueChanged(value) Same as QSpinBox; emitted every time the value has changed. sigValueChanged(self) Emitted when value has changed, but also combines multiple rapid changes into one signal (eg, when rolling the mouse wheel). sigValueChanging(self, value) Emitted immediately for all value changes. ============================= ============================================== """ ## There's a PyQt bug that leaks a reference to the ## QLineEdit returned from QAbstractSpinBox.lineEdit() ## This makes it possible to crash the entire program ## by making accesses to the LineEdit after the spinBox has been deleted. ## I have no idea how to get around this.. valueChanged = QtCore.Signal(object) # (value) for compatibility with QSpinBox sigValueChanged = QtCore.Signal(object) # (self) sigValueChanging = QtCore.Signal(object, object) # (self, value) sent immediately; no delay. def __init__(self, parent=None, value=0.0, **kwargs): """ ============== ======================================================================== **Arguments:** parent Sets the parent widget for this SpinBox (optional). Default is None. value (float/int) initial value. Default is 0.0. ============== ======================================================================== All keyword arguments are passed to :func:`setOpts`. """ QtWidgets.QAbstractSpinBox.__init__(self, parent) self.lastValEmitted = None self.lastText = '' self.textValid = True ## If false, we draw a red border self.setMinimumWidth(0) self._lastFontHeight = None self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) self.errorBox = ErrorBox(self.lineEdit()) self.opts = { 'bounds': [None, None], 'wrapping': False, ## normal arithmetic step 'step': decimal.Decimal('0.01'), ## if 'dec' is false, the spinBox steps by 'step' every time ## if 'dec' is True, the step size is relative to the value ## 'step' needs to be an integral divisor of ten, ie 'step'*n=10 for some integer value of n (but only if dec is True) 'dec': False, ## if true, does decimal stepping. ie from 1-10 it steps by 'step', from 10 to 100 it steps by 10*'step', etc. ## if true, minStep must be set in order to cross zero. 'int': False, ## Set True to force value to be integer 'finite': True, 'suffix': '', 'siPrefix': False, ## Set to True to display numbers with SI prefix (ie, 100pA instead of 1e-10A) 'delay': 0.3, ## delay sending wheel update signals for 300ms 'delayUntilEditFinished': True, ## do not send signals until text editing has finished 'decimals': 6, 'format': "{scaledValue:.{decimals}g}{suffixGap}{siPrefix}{suffix}", 'regex': fn.FLOAT_REGEX, 'evalFunc': decimal.Decimal, 'compactHeight': True, # manually remove extra margin outside of text } self.decOpts = ['step', 'minStep'] self.val = decimal.Decimal(str(value)) ## Value is precise decimal. Ordinary math not allowed. self.updateText() self.skipValidate = False self.setCorrectionMode(self.CorrectionMode.CorrectToPreviousValue) self.setKeyboardTracking(False) self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange, delay=self.opts['delay']) self.setOpts(**kwargs) self._updateHeight() self.editingFinished.connect(self.editingFinishedEvent) def setOpts(self, **opts): """Set options affecting the behavior of the SpinBox. ============== ======================================================================== **Arguments:** bounds (min,max) Minimum and maximum values allowed in the SpinBox. Either may be None to leave the value unbounded. By default, values are unbounded. suffix (str) suffix (units) to display after the numerical value. By default, suffix is an empty str. siPrefix (bool) If True, then an SI prefix is automatically prepended to the units and the value is scaled accordingly. For example, if value=0.003 and suffix='V', then the SpinBox will display "300 mV" (but a call to SpinBox.value will still return 0.003). In case the value represents a dimensionless quantity that might span many orders of magnitude, such as a Reynolds number, an SI prefix is allowed with no suffix. Default is False. step (float) The size of a single step. This is used when clicking the up/ down arrows, when rolling the mouse wheel, or when pressing keyboard arrows while the widget has keyboard focus. Note that the interpretation of this value is different when specifying the 'dec' argument. Default is 0.01. dec (bool) If True, then the step value will be adjusted to match the current size of the variable (for example, a value of 15 might step in increments of 1 whereas a value of 1500 would step in increments of 100). In this case, the 'step' argument is interpreted *relative* to the current value. The most common 'step' values when dec=True are 0.1, 0.2, 0.5, and 1.0. Default is False. minStep (float) When dec=True, this specifies the minimum allowable step size. int (bool) If True, the value is forced to integer type. Default is False finite (bool) When False and int=False, infinite values (nan, inf, -inf) are permitted. Default is True. wrapping (bool) If True and both bounds are not None, spin box has circular behavior. decimals (int) Number of decimal values to display. Default is 6. format (str) Formatting string used to generate the text shown. Formatting is done with ``str.format()`` and makes use of several arguments: * *value* - the unscaled value of the spin box * *suffix* - the suffix string * *scaledValue* - the scaled value to use when an SI prefix is present * *siPrefix* - the SI prefix string (if any), or an empty string if this feature has been disabled * *suffixGap* - a single space if a suffix is present, or an empty string otherwise. regex (str or RegexObject) Regular expression used to parse the spinbox text. May contain the following group names: * *number* - matches the numerical portion of the string (mandatory) * *siPrefix* - matches the SI prefix string * *suffix* - matches the suffix string Default is defined in ``pyqtgraph.functions.FLOAT_REGEX``. evalFunc (callable) Fucntion that converts a numerical string to a number, preferrably a Decimal instance. This function handles only the numerical of the text; it does not have access to the suffix or SI prefix. compactHeight (bool) if True, then set the maximum height of the spinbox based on the height of its font. This allows more compact packing on platforms with excessive widget decoration. Default is True. ============== ======================================================================== """ #print opts for k,v in opts.items(): if k == 'bounds': self.setMinimum(v[0], update=False) self.setMaximum(v[1], update=False) elif k == 'min': self.setMinimum(v, update=False) elif k == 'max': self.setMaximum(v, update=False) elif k in ['step', 'minStep']: self.opts[k] = decimal.Decimal(str(v)) elif k == 'value': pass ## don't set value until bounds have been set elif k == 'format': self.opts[k] = str(v) elif k == 'regex' and isinstance(v, str): self.opts[k] = re.compile(v) elif k in self.opts: self.opts[k] = v else: raise TypeError("Invalid keyword argument '%s'." % k) if 'value' in opts: self.setValue(opts['value']) ## If bounds have changed, update value to match if 'bounds' in opts and 'value' not in opts: self.setValue() ## sanity checks: if self.opts['int']: if 'step' in opts: step = opts['step'] ## not necessary.. #if int(step) != step: #raise Exception('Integer SpinBox must have integer step size.') else: self.opts['step'] = int(self.opts['step']) if 'minStep' in opts: step = opts['minStep'] if int(step) != step: raise Exception('Integer SpinBox must have integer minStep size.') else: ms = int(self.opts.get('minStep', 1)) if ms < 1: ms = 1 self.opts['minStep'] = ms if 'format' not in opts: self.opts['format'] = "{value:d}{suffixGap}{suffix}" if self.opts['dec']: if self.opts.get('minStep') is None: self.opts['minStep'] = self.opts['step'] if 'delay' in opts: self.proxy.setDelay(opts['delay']) self.updateText() def setMaximum(self, m, update=True): """Set the maximum allowed value (or None for no limit)""" if m is not None: m = decimal.Decimal(str(m)) self.opts['bounds'][1] = m if update: self.setValue() def setMinimum(self, m, update=True): """Set the minimum allowed value (or None for no limit)""" if m is not None: m = decimal.Decimal(str(m)) self.opts['bounds'][0] = m if update: self.setValue() def wrapping(self): """Return whether or not the spin box is circular.""" return self.opts['wrapping'] def setWrapping(self, s): """Set whether spin box is circular. Both bounds must be set for this to have an effect.""" self.opts['wrapping'] = s def setPrefix(self, p): """Set a string prefix. """ self.setOpts(prefix=p) def setRange(self, r0, r1): """Set the upper and lower limits for values in the spinbox. """ self.setOpts(bounds = [r0,r1]) def setProperty(self, prop, val): ## for QSpinBox compatibility if prop == 'value': #if type(val) is QtCore.QVariant: #val = val.toDouble()[0] self.setValue(val) else: print("Warning: SpinBox.setProperty('%s', ..) not supported." % prop) def setSuffix(self, suf): """Set the string suffix appended to the spinbox text. """ self.setOpts(suffix=suf) def setSingleStep(self, step): """Set the step size used when responding to the mouse wheel, arrow buttons, or arrow keys. """ self.setOpts(step=step) def setDecimals(self, decimals): """Set the number of decimals to be displayed when formatting numeric values. """ self.setOpts(decimals=decimals) def selectNumber(self): """ Select the numerical portion of the text to allow quick editing by the user. """ le = self.lineEdit() text = le.text() m = self.opts['regex'].match(text) if m is None: return s,e = m.start('number'), m.end('number') le.setSelection(s, e-s) def focusInEvent(self, ev): super(SpinBox, self).focusInEvent(ev) self.selectNumber() def value(self): """ Return the value of this SpinBox. """ if self.opts['int']: return int(self.val) else: return float(self.val) def setValue(self, value=None, update=True, delaySignal=False): """Set the value of this SpinBox. If the value is out of bounds, it will be clipped to the nearest boundary or wrapped if wrapping is enabled. If the spin is integer type, the value will be coerced to int. Returns the actual value set. If value is None, then the current value is used (this is for resetting the value after bounds, etc. have changed) """ if value is None: value = self.value() bounded = True if not isnan(value): bounds = self.opts['bounds'] if None not in bounds and self.opts['wrapping'] is True: bounded = False if isinf(value): value = self.val else: # Casting of Decimals to floats required to avoid unexpected behavior of remainder operator value = float(value) l, u = float(bounds[0]), float(bounds[1]) value = (value - l) % (u - l) + l else: if bounds[0] is not None and value < bounds[0]: bounded = False value = bounds[0] if bounds[1] is not None and value > bounds[1]: bounded = False value = bounds[1] if self.opts['int']: value = int(value) if not isinstance(value, decimal.Decimal): value = decimal.Decimal(str(value)) prev, self.val = self.val, value changed = not fn.eq(value, prev) # use fn.eq to handle nan if update and (changed or not bounded): self.updateText(prev=prev) if changed: self.sigValueChanging.emit(self, float(self.val)) ## change will be emitted in 300ms if there are no subsequent changes. if not delaySignal: self.emitChanged() return value def emitChanged(self): self.lastValEmitted = self.val self.valueChanged.emit(float(self.val)) self.sigValueChanged.emit(self) def delayedChange(self): try: if not fn.eq(self.val, self.lastValEmitted): # use fn.eq to handle nan self.emitChanged() except RuntimeError: pass ## This can happen if we try to handle a delayed signal after someone else has already deleted the underlying C++ object. def widgetGroupInterface(self): return (self.valueChanged, SpinBox.value, SpinBox.setValue) def sizeHint(self): return QtCore.QSize(120, 0) def stepEnabled(self): return self.StepEnabledFlag.StepUpEnabled | self.StepEnabledFlag.StepDownEnabled def stepBy(self, n): if isinf(self.val) or isnan(self.val): return n = decimal.Decimal(int(n)) ## n must be integral number of steps. s = [decimal.Decimal(-1), decimal.Decimal(1)][n >= 0] ## determine sign of step val = self.val for i in range(int(abs(n))): if self.opts['dec']: if val == 0: step = self.opts['minStep'] exp = None else: vs = [decimal.Decimal(-1), decimal.Decimal(1)][val >= 0] #exp = decimal.Decimal(int(abs(val*(decimal.Decimal('1.01')**(s*vs))).log10())) fudge = decimal.Decimal('1.01')**(s*vs) ## fudge factor. at some places, the step size depends on the step sign. exp = abs(val * fudge).log10().quantize(1, decimal.ROUND_FLOOR) step = self.opts['step'] * decimal.Decimal(10)**exp if 'minStep' in self.opts: step = max(step, self.opts['minStep']) val += s * step #print "Exp:", exp, "step", step, "val", val else: val += s*self.opts['step'] if 'minStep' in self.opts and abs(val) < self.opts['minStep']: val = decimal.Decimal(0) self.setValue(val, delaySignal=True) ## note all steps (arrow buttons, wheel, up/down keys..) emit delayed signals only. def valueInRange(self, value): if not isnan(value): bounds = self.opts['bounds'] if bounds[0] is not None and value < bounds[0]: return False if bounds[1] is not None and value > bounds[1]: return False if self.opts.get('int', False): if int(value) != value: return False return True def updateText(self, prev=None): # temporarily disable validation self.skipValidate = True txt = self.formatText(prev=prev) # actually set the text self.lineEdit().setText(txt) self.lastText = txt # re-enable the validation self.skipValidate = False def formatText(self, prev=None): # get the number of decimal places to print decimals = self.opts['decimals'] suffix = self.opts['suffix'] # format the string val = self.value() if self.opts['siPrefix'] is True: # SI prefix was requested, so scale the value accordingly if self.val == 0 and prev is not None: # special case: if it's zero use the previous prefix (s, p) = fn.siScale(prev) else: (s, p) = fn.siScale(val) parts = {'value': val, 'suffix': suffix, 'decimals': decimals, 'siPrefix': p, 'scaledValue': s*val} else: # no SI prefix /suffix requested; scale is 1 parts = {'value': val, 'suffix': suffix, 'decimals': decimals, 'siPrefix': '', 'scaledValue': val} parts['suffixGap'] = '' if (parts['suffix'] == '' and parts['siPrefix'] == '') else ' ' return self.opts['format'].format(**parts) def validate(self, strn, pos): if self.skipValidate: ret = QtGui.QValidator.State.Acceptable else: try: val = self.interpret() if val is False: ret = QtGui.QValidator.State.Intermediate else: if self.valueInRange(val): if not self.opts['delayUntilEditFinished']: self.setValue(val, update=False) ret = QtGui.QValidator.State.Acceptable else: ret = QtGui.QValidator.State.Intermediate except: import sys sys.excepthook(*sys.exc_info()) ret = QtGui.QValidator.State.Intermediate ## draw / clear border if ret == QtGui.QValidator.State.Intermediate: self.textValid = False elif ret == QtGui.QValidator.State.Acceptable: self.textValid = True ## note: if text is invalid, we don't change the textValid flag ## since the text will be forced to its previous state anyway self.update() self.errorBox.setVisible(not self.textValid) ## support 2 different pyqt APIs. Bleh. if hasattr(QtCore, 'QString'): return (ret, pos) else: return (ret, strn, pos) def fixup(self, strn): # fixup is called when the spinbox loses focus with an invalid or intermediate string self.updateText() # support both PyQt APIs (for Python 2 and 3 respectively) # http://pyqt.sourceforge.net/Docs/PyQt4/python_v3.html#qvalidator try: strn.clear() strn.append(self.lineEdit().text()) except AttributeError: return self.lineEdit().text() def interpret(self): """Return value of text or False if text is invalid.""" strn = self.lineEdit().text() # tokenize into numerical value, si prefix, and suffix try: val, siprefix, suffix = fn.siParse(strn, self.opts['regex'], suffix=self.opts['suffix']) except Exception: return False # check suffix if suffix != self.opts['suffix']: return False # generate value val = self.opts['evalFunc'](val) if (self.opts['int'] or self.opts['finite']) and (isinf(val) or isnan(val)): return False if self.opts['int']: val = int(fn.siApply(val, siprefix)) else: try: val = fn.siApply(val, siprefix) except Exception: import sys sys.excepthook(*sys.exc_info()) return False return val def editingFinishedEvent(self): """Edit has finished; set value.""" if self.lineEdit().text() == self.lastText: return try: val = self.interpret() except Exception: return if val is False: return if val == self.val: return self.setValue(val, delaySignal=False) ## allow text update so that values are reformatted pretty-like def _updateHeight(self): # SpinBox has very large margins on some platforms; this is a hack to remove those # margins and allow more compact packing of controls. if not self.opts['compactHeight']: self.setMaximumHeight(1e6) return h = QtGui.QFontMetrics(self.font()).height() if self._lastFontHeight != h: self._lastFontHeight = h self.setMaximumHeight(h) def paintEvent(self, ev): self._updateHeight() super().paintEvent(ev) class ErrorBox(QtWidgets.QWidget): """Red outline to draw around lineedit when value is invalid. (for some reason, setting border from stylesheet does not work) """ def __init__(self, parent): QtWidgets.QWidget.__init__(self, parent) parent.installEventFilter(self) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents) self._resize() self.setVisible(False) def eventFilter(self, obj, ev): if ev.type() == QtCore.QEvent.Type.Resize: self._resize() return False def _resize(self): self.setGeometry(0, 0, self.parent().width(), self.parent().height()) def paintEvent(self, ev): p = QtGui.QPainter(self) p.setPen(fn.mkPen(color='r', width=2)) p.drawRect(self.rect()) p.end() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/TableWidget.py000066400000000000000000000444201421045507400240720ustar00rootroot00000000000000import numpy as np from .. import metaarray from ..Qt import QtCore, QtGui, QtWidgets translate = QtCore.QCoreApplication.translate __all__ = ['TableWidget'] def _defersort(fn): def defersort(self, *args, **kwds): # may be called recursively; only the first call needs to block sorting setSorting = False if self._sorting is None: self._sorting = self.isSortingEnabled() setSorting = True self.setSortingEnabled(False) try: return fn(self, *args, **kwds) finally: if setSorting: self.setSortingEnabled(self._sorting) self._sorting = None return defersort class TableWidget(QtWidgets.QTableWidget): """Extends QTableWidget with some useful functions for automatic data handling and copy / export context menu. Can automatically format and display a variety of data types (see :func:`setData() ` for more information. """ def __init__(self, *args, **kwds): """ All positional arguments are passed to QTableWidget.__init__(). ===================== ================================================= **Keyword Arguments** editable (bool) If True, cells in the table can be edited by the user. Default is False. sortable (bool) If True, the table may be soted by clicking on column headers. Note that this also causes rows to appear initially shuffled until a sort column is selected. Default is True. *(added in version 0.9.9)* ===================== ================================================= """ QtWidgets.QTableWidget.__init__(self, *args) self.itemClass = TableWidgetItem self.setVerticalScrollMode(self.ScrollMode.ScrollPerPixel) self.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ContiguousSelection) self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) self.clear() kwds.setdefault('sortable', True) kwds.setdefault('editable', False) self.setEditable(kwds.pop('editable')) self.setSortingEnabled(kwds.pop('sortable')) if len(kwds) > 0: raise TypeError("Invalid keyword arguments '%s'" % list(kwds.keys())) self._sorting = None # used when temporarily disabling sorting self._formats = {None: None} # stores per-column formats and entire table format self.sortModes = {} # stores per-column sort mode self.itemChanged.connect(self.handleItemChanged) self.contextMenu = QtWidgets.QMenu() self.contextMenu.addAction(translate("TableWidget", 'Copy Selection')).triggered.connect(self.copySel) self.contextMenu.addAction(translate("TableWidget", 'Copy All')).triggered.connect(self.copyAll) self.contextMenu.addAction(translate("TableWidget", 'Save Selection')).triggered.connect(self.saveSel) self.contextMenu.addAction(translate("TableWidget", 'Save All')).triggered.connect(self.saveAll) def clear(self): """Clear all contents from the table.""" QtWidgets.QTableWidget.clear(self) self.verticalHeadersSet = False self.horizontalHeadersSet = False self.items = [] self.setRowCount(0) self.setColumnCount(0) self.sortModes = {} def setData(self, data): """Set the data displayed in the table. Allowed formats are: * numpy arrays * numpy record arrays * metaarrays * list-of-lists [[1,2,3], [4,5,6]] * dict-of-lists {'x': [1,2,3], 'y': [4,5,6]} * list-of-dicts [{'x': 1, 'y': 4}, {'x': 2, 'y': 5}, ...] """ self.clear() self.appendData(data) self.resizeColumnsToContents() @_defersort def appendData(self, data): """ Add new rows to the table. See :func:`setData() ` for accepted data types. """ startRow = self.rowCount() fn0, header0 = self.iteratorFn(data) if fn0 is None: self.clear() return it0 = fn0(data) try: first = next(it0) except StopIteration: return fn1, header1 = self.iteratorFn(first) if fn1 is None: self.clear() return firstVals = [x for x in fn1(first)] self.setColumnCount(len(firstVals)) if not self.verticalHeadersSet and header0 is not None: labels = [self.verticalHeaderItem(i).text() for i in range(self.rowCount())] self.setRowCount(startRow + len(header0)) self.setVerticalHeaderLabels(labels + header0) self.verticalHeadersSet = True if not self.horizontalHeadersSet and header1 is not None: self.setHorizontalHeaderLabels(header1) self.horizontalHeadersSet = True i = startRow self.setRow(i, firstVals) for row in it0: i += 1 self.setRow(i, [x for x in fn1(row)]) if (self._sorting and self.horizontalHeadersSet and self.horizontalHeader().sortIndicatorSection() >= self.columnCount()): self.sortByColumn(0, QtCore.Qt.SortOrder.AscendingOrder) def setEditable(self, editable=True): self.editable = editable for item in self.items: item.setEditable(editable) def setFormat(self, format, column=None): """ Specify the default text formatting for the entire table, or for a single column if *column* is specified. If a string is specified, it is used as a format string for converting float values (and all other types are converted using str). If a function is specified, it will be called with the item as its only argument and must return a string. Setting format = None causes the default formatter to be used instead. Added in version 0.9.9. """ if format is not None and not isinstance(format, str) and not callable(format): raise ValueError("Format argument must string, callable, or None. (got %s)" % format) self._formats[column] = format if column is None: # update format of all items that do not have a column format # specified for c in range(self.columnCount()): if self._formats.get(c, None) is None: for r in range(self.rowCount()): item = self.item(r, c) if item is None: continue item.setFormat(format) else: # set all items in the column to use this format, or the default # table format if None was specified. if format is None: format = self._formats[None] for r in range(self.rowCount()): item = self.item(r, column) if item is None: continue item.setFormat(format) def iteratorFn(self, data): ## Return 1) a function that will provide an iterator for data and 2) a list of header strings if isinstance(data, list) or isinstance(data, tuple): return lambda d: d.__iter__(), None elif isinstance(data, dict): return lambda d: iter(d.values()), list(map(str, data.keys())) elif (hasattr(data, 'implements') and data.implements('MetaArray')): if data.axisHasColumns(0): header = [str(data.columnName(0, i)) for i in range(data.shape[0])] elif data.axisHasValues(0): header = list(map(str, data.xvals(0))) else: header = None return self.iterFirstAxis, header elif isinstance(data, np.ndarray): return self.iterFirstAxis, None elif isinstance(data, np.void): return self.iterate, list(map(str, data.dtype.names)) elif data is None: return (None,None) elif np.isscalar(data): return self.iterateScalar, None else: msg = "Don't know how to iterate over data type: {!s}".format(type(data)) raise TypeError(msg) def iterFirstAxis(self, data): for i in range(data.shape[0]): yield data[i] def iterate(self, data): # for numpy.void, which can be iterated but mysteriously # has no __iter__ (??) for x in data: yield x def iterateScalar(self, data): yield data def appendRow(self, data): self.appendData([data]) @_defersort def addRow(self, vals): row = self.rowCount() self.setRowCount(row + 1) self.setRow(row, vals) @_defersort def setRow(self, row, vals): if row > self.rowCount() - 1: self.setRowCount(row + 1) for col in range(len(vals)): val = vals[col] item = self.itemClass(val, row) item.setEditable(self.editable) sortMode = self.sortModes.get(col, None) if sortMode is not None: item.setSortMode(sortMode) format = self._formats.get(col, self._formats[None]) item.setFormat(format) self.items.append(item) self.setItem(row, col, item) item.setValue(val) # Required--the text-change callback is invoked # when we call setItem. def setSortMode(self, column, mode): """ Set the mode used to sort *column*. ============== ======================================================== **Sort Modes** value Compares item.value if available; falls back to text comparison. text Compares item.text() index Compares by the order in which items were inserted. ============== ======================================================== Added in version 0.9.9 """ for r in range(self.rowCount()): item = self.item(r, column) if hasattr(item, 'setSortMode'): item.setSortMode(mode) self.sortModes[column] = mode def sizeHint(self): # based on http://stackoverflow.com/a/7195443/54056 width = sum(self.columnWidth(i) for i in range(self.columnCount())) width += self.verticalHeader().sizeHint().width() width += self.verticalScrollBar().sizeHint().width() width += self.frameWidth() * 2 height = sum(self.rowHeight(i) for i in range(self.rowCount())) height += self.verticalHeader().sizeHint().height() height += self.horizontalScrollBar().sizeHint().height() return QtCore.QSize(width, height) def serialize(self, useSelection=False): """Convert entire table (or just selected area) into tab-separated text values""" if useSelection: selection = self.selectedRanges()[0] rows = list(range(selection.topRow(), selection.bottomRow() + 1)) columns = list(range(selection.leftColumn(), selection.rightColumn() + 1)) else: rows = list(range(self.rowCount())) columns = list(range(self.columnCount())) data = [] if self.horizontalHeadersSet: row = [] if self.verticalHeadersSet: row.append('') for c in columns: row.append(self.horizontalHeaderItem(c).text()) data.append(row) for r in rows: row = [] if self.verticalHeadersSet: row.append(self.verticalHeaderItem(r).text()) for c in columns: item = self.item(r, c) if item is not None: row.append(str(item.value)) else: row.append('') data.append(row) s = '' for row in data: s += ('\t'.join(row) + '\n') return s def copySel(self): """Copy selected data to clipboard.""" QtWidgets.QApplication.clipboard().setText(self.serialize(useSelection=True)) def copyAll(self): """Copy all data to clipboard.""" QtWidgets.QApplication.clipboard().setText(self.serialize(useSelection=False)) def saveSel(self): """Save selected data to file.""" self.save(self.serialize(useSelection=True)) def saveAll(self): """Save all data to file.""" self.save(self.serialize(useSelection=False)) def save(self, data): fileName = QtWidgets.QFileDialog.getSaveFileName( self, f"{translate('TableWidget', 'Save As')}...", "", f"{translate('TableWidget', 'Tab-separated values')} (*.tsv)" ) if isinstance(fileName, tuple): fileName = fileName[0] # Qt4/5 API difference if fileName == '': return with open(fileName, 'w') as fd: fd.write(data) def contextMenuEvent(self, ev): self.contextMenu.popup(ev.globalPos()) def keyPressEvent(self, ev): if ev.matches(QtGui.QKeySequence.StandardKey.Copy): ev.accept() self.copySel() else: super().keyPressEvent(ev) def handleItemChanged(self, item): item.itemChanged() class TableWidgetItem(QtWidgets.QTableWidgetItem): def __init__(self, val, index, format=None): QtWidgets.QTableWidgetItem.__init__(self, '') self._blockValueChange = False self._format = None self._defaultFormat = '%0.3g' self.sortMode = 'value' self.index = index flags = QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled self.setFlags(flags) self.setValue(val) self.setFormat(format) def setEditable(self, editable): """ Set whether this item is user-editable. """ if editable: self.setFlags(self.flags() | QtCore.Qt.ItemFlag.ItemIsEditable) else: self.setFlags(self.flags() & ~QtCore.Qt.ItemFlag.ItemIsEditable) def setSortMode(self, mode): """ Set the mode used to sort this item against others in its column. ============== ======================================================== **Sort Modes** value Compares item.value if available; falls back to text comparison. text Compares item.text() index Compares by the order in which items were inserted. ============== ======================================================== """ modes = ('value', 'text', 'index', None) if mode not in modes: raise ValueError('Sort mode must be one of %s' % str(modes)) self.sortMode = mode def setFormat(self, fmt): """Define the conversion from item value to displayed text. If a string is specified, it is used as a format string for converting float values (and all other types are converted using str). If a function is specified, it will be called with the item as its only argument and must return a string. Added in version 0.9.9. """ if fmt is not None and not isinstance(fmt, str) and not callable(fmt): raise ValueError("Format argument must string, callable, or None. (got %s)" % fmt) self._format = fmt self._updateText() def _updateText(self): self._blockValueChange = True try: self._text = self.format() self.setText(self._text) finally: self._blockValueChange = False def setValue(self, value): self.value = value self._updateText() def itemChanged(self): """Called when the data of this item has changed.""" if self.text() != self._text: self.textChanged() def textChanged(self): """Called when this item's text has changed for any reason.""" self._text = self.text() if self._blockValueChange: # text change was result of value or format change; do not # propagate. return try: self.value = type(self.value)(self.text()) except ValueError: self.value = str(self.text()) def format(self): if callable(self._format): return self._format(self) if isinstance(self.value, (float, np.floating)): if self._format is None: return self._defaultFormat % self.value else: return self._format % self.value else: return str(self.value) def __lt__(self, other): if self.sortMode == 'index' and hasattr(other, 'index'): return self.index < other.index if self.sortMode == 'value' and hasattr(other, 'value'): return self.value < other.value else: return self.text() < other.text() if __name__ == '__main__': app = QtWidgets.QApplication([]) win = QtWidgets.QMainWindow() t = TableWidget() win.setCentralWidget(t) win.resize(800,600) win.show() ll = [[1,2,3,4,5]] * 20 ld = [{'x': 1, 'y': 2, 'z': 3}] * 20 dl = {'x': list(range(20)), 'y': list(range(20)), 'z': list(range(20))} a = np.ones((20, 5)) ra = np.ones((20,), dtype=[('x', int), ('y', int), ('z', int)]) t.setData(ll) ma = metaarray.MetaArray(np.ones((20, 3)), info=[ {'values': np.linspace(1, 5, 20)}, {'cols': [ {'name': 'x'}, {'name': 'y'}, {'name': 'z'}, ]} ]) t.setData(ma) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/TreeWidget.py000066400000000000000000000343261421045507400237460ustar00rootroot00000000000000from ..Qt import QtCore, QtWidgets __all__ = ['TreeWidget', 'TreeWidgetItem'] class TreeWidget(QtWidgets.QTreeWidget): """Extends QTreeWidget to allow internal drag/drop with widgets in the tree. Also maintains the expanded state of subtrees as they are moved. This class demonstrates the absurd lengths one must go to to make drag/drop work.""" sigItemMoved = QtCore.Signal(object, object, object) # (item, parent, index) sigItemCheckStateChanged = QtCore.Signal(object, object) sigItemTextChanged = QtCore.Signal(object, object) sigColumnCountChanged = QtCore.Signal(object, object) # self, count def __init__(self, parent=None): QtWidgets.QTreeWidget.__init__(self, parent) # wrap this item so that we can propagate tree change information # to children. self._invRootItem = InvisibleRootItem(QtWidgets.QTreeWidget.invisibleRootItem(self)) self.setAcceptDrops(True) self.setDragEnabled(True) self.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.EditKeyPressed|QtWidgets.QAbstractItemView.EditTrigger.SelectedClicked) self.placeholders = [] self.childNestingLimit = None self.itemClicked.connect(self._itemClicked) def setItemWidget(self, item, col, wid): """ Overrides QTreeWidget.setItemWidget such that widgets are added inside an invisible wrapper widget. This makes it possible to move the item in and out of the tree without its widgets being automatically deleted. """ w = QtWidgets.QWidget() ## foster parent / surrogate child widget l = QtWidgets.QVBoxLayout() l.setContentsMargins(0,0,0,0) w.setLayout(l) w.setSizePolicy(wid.sizePolicy()) w.setMinimumHeight(wid.minimumHeight()) w.setMinimumWidth(wid.minimumWidth()) l.addWidget(wid) w.realChild = wid self.placeholders.append(w) QtWidgets.QTreeWidget.setItemWidget(self, item, col, w) def itemWidget(self, item, col): w = QtWidgets.QTreeWidget.itemWidget(self, item, col) if w is not None and hasattr(w, 'realChild'): w = w.realChild return w def dropMimeData(self, parent, index, data, action): item = self.currentItem() p = parent #print "drop", item, "->", parent, index while True: if p is None: break if p is item: return False #raise Exception("Can not move item into itself.") p = p.parent() if not self.itemMoving(item, parent, index): return False currentParent = item.parent() if currentParent is None: currentParent = self.invisibleRootItem() if parent is None: parent = self.invisibleRootItem() if currentParent is parent and index > parent.indexOfChild(item): index -= 1 self.prepareMove(item) currentParent.removeChild(item) #print " insert child to index", index parent.insertChild(index, item) ## index will not be correct self.setCurrentItem(item) self.recoverMove(item) #self.emit(QtCore.SIGNAL('itemMoved'), item, parent, index) self.sigItemMoved.emit(item, parent, index) return True def itemMoving(self, item, parent, index): """Called when item has been dropped elsewhere in the tree. Return True to accept the move, False to reject.""" return True def prepareMove(self, item): item.__widgets = [] item.__expanded = item.isExpanded() for i in range(self.columnCount()): w = self.itemWidget(item, i) item.__widgets.append(w) if w is None: continue w.setParent(None) for i in range(item.childCount()): self.prepareMove(item.child(i)) def recoverMove(self, item): for i in range(self.columnCount()): w = item.__widgets[i] if w is None: continue self.setItemWidget(item, i, w) for i in range(item.childCount()): self.recoverMove(item.child(i)) item.setExpanded(False) ## Items do not re-expand correctly unless they are collapsed first. QtWidgets.QApplication.instance().processEvents() item.setExpanded(item.__expanded) def collapseTree(self, item): item.setExpanded(False) for i in range(item.childCount()): self.collapseTree(item.child(i)) def removeTopLevelItem(self, item): for i in range(self.topLevelItemCount()): if self.topLevelItem(i) is item: self.takeTopLevelItem(i) return raise Exception("Item '%s' not in top-level items." % str(item)) def listAllItems(self, item=None): items = [] if item is not None: items.append(item) else: item = self.invisibleRootItem() for cindex in range(item.childCount()): foundItems = self.listAllItems(item=item.child(cindex)) for f in foundItems: items.append(f) return items def dropEvent(self, ev): super().dropEvent(ev) self.updateDropFlags() def updateDropFlags(self): ### intended to put a limit on how deep nests of children can go. ### self.childNestingLimit is upheld when moving items without children, but if the item being moved has children/grandchildren, the children/grandchildren ### can end up over the childNestingLimit. if self.childNestingLimit is None: pass # enable drops in all items (but only if there are drops that aren't enabled? for performance...) else: items = self.listAllItems() for item in items: parentCount = 0 p = item.parent() while p is not None: parentCount += 1 p = p.parent() if parentCount >= self.childNestingLimit: item.setFlags(item.flags() & (~QtCore.Qt.ItemFlag.ItemIsDropEnabled)) else: item.setFlags(item.flags() | QtCore.Qt.ItemFlag.ItemIsDropEnabled) @staticmethod def informTreeWidgetChange(item): if hasattr(item, 'treeWidgetChanged'): item.treeWidgetChanged() for i in range(item.childCount()): TreeWidget.informTreeWidgetChange(item.child(i)) def addTopLevelItem(self, item): QtWidgets.QTreeWidget.addTopLevelItem(self, item) self.informTreeWidgetChange(item) def addTopLevelItems(self, items): QtWidgets.QTreeWidget.addTopLevelItems(self, items) for item in items: self.informTreeWidgetChange(item) def insertTopLevelItem(self, index, item): QtWidgets.QTreeWidget.insertTopLevelItem(self, index, item) self.informTreeWidgetChange(item) def insertTopLevelItems(self, index, items): QtWidgets.QTreeWidget.insertTopLevelItems(self, index, items) for item in items: self.informTreeWidgetChange(item) def takeTopLevelItem(self, index): item = self.topLevelItem(index) if item is not None: self.prepareMove(item) item = QtWidgets.QTreeWidget.takeTopLevelItem(self, index) self.prepareMove(item) self.informTreeWidgetChange(item) return item def topLevelItems(self): return [self.topLevelItem(i) for i in range(self.topLevelItemCount())] def clear(self): items = self.topLevelItems() for item in items: self.prepareMove(item) QtWidgets.QTreeWidget.clear(self) ## Why do we want to do this? It causes RuntimeErrors. #for item in items: #self.informTreeWidgetChange(item) def invisibleRootItem(self): return self._invRootItem def itemFromIndex(self, index): """Return the item and column corresponding to a QModelIndex. """ col = index.column() rows = [] while index.row() >= 0: rows.insert(0, index.row()) index = index.parent() item = self.topLevelItem(rows[0]) for row in rows[1:]: item = item.child(row) return item, col def setColumnCount(self, c): QtWidgets.QTreeWidget.setColumnCount(self, c) self.sigColumnCountChanged.emit(self, c) def _itemClicked(self, item, col): if hasattr(item, 'itemClicked'): item.itemClicked(col) class TreeWidgetItem(QtWidgets.QTreeWidgetItem): """ TreeWidgetItem that keeps track of its own widgets and expansion state. * Widgets may be added to columns before the item is added to a tree. * Expanded state may be set before item is added to a tree. * Adds setCheked and isChecked methods. * Adds addChildren, insertChildren, and takeChildren methods. """ def __init__(self, *args): QtWidgets.QTreeWidgetItem.__init__(self, *args) self._widgets = {} # col: widget self._tree = None self._expanded = False def setChecked(self, column, checked): self.setCheckState(column, QtCore.Qt.CheckState.Checked if checked else QtCore.Qt.CheckState.Unchecked) def isChecked(self, col): return self.checkState(col) == QtCore.Qt.CheckState.Checked def setExpanded(self, exp): self._expanded = exp QtWidgets.QTreeWidgetItem.setExpanded(self, exp) def isExpanded(self): return self._expanded def setWidget(self, column, widget): if column in self._widgets: self.removeWidget(column) self._widgets[column] = widget tree = self.treeWidget() if tree is None: return else: tree.setItemWidget(self, column, widget) def removeWidget(self, column): del self._widgets[column] tree = self.treeWidget() if tree is None: return tree.removeItemWidget(self, column) def treeWidgetChanged(self): tree = self.treeWidget() if self._tree is tree: return self._tree = self.treeWidget() if tree is None: return for col, widget in self._widgets.items(): tree.setItemWidget(self, col, widget) QtWidgets.QTreeWidgetItem.setExpanded(self, self._expanded) def childItems(self): return [self.child(i) for i in range(self.childCount())] def addChild(self, child): QtWidgets.QTreeWidgetItem.addChild(self, child) TreeWidget.informTreeWidgetChange(child) def addChildren(self, childs): QtWidgets.QTreeWidgetItem.addChildren(self, childs) for child in childs: TreeWidget.informTreeWidgetChange(child) def insertChild(self, index, child): QtWidgets.QTreeWidgetItem.insertChild(self, index, child) TreeWidget.informTreeWidgetChange(child) def insertChildren(self, index, childs): QtWidgets.QTreeWidgetItem.addChildren(self, index, childs) for child in childs: TreeWidget.informTreeWidgetChange(child) def removeChild(self, child): QtWidgets.QTreeWidgetItem.removeChild(self, child) TreeWidget.informTreeWidgetChange(child) def takeChild(self, index): child = QtWidgets.QTreeWidgetItem.takeChild(self, index) TreeWidget.informTreeWidgetChange(child) return child def takeChildren(self): childs = QtWidgets.QTreeWidgetItem.takeChildren(self) for child in childs: TreeWidget.informTreeWidgetChange(child) return childs def setData(self, column, role, value): # credit: ekhumoro # http://stackoverflow.com/questions/13662020/how-to-implement-itemchecked-and-itemunchecked-signals-for-qtreewidget-in-pyqt4 checkstate = self.checkState(column) text = self.text(column) QtWidgets.QTreeWidgetItem.setData(self, column, role, value) treewidget = self.treeWidget() if treewidget is None: return if (role == QtCore.Qt.ItemDataRole.CheckStateRole and checkstate != self.checkState(column)): treewidget.sigItemCheckStateChanged.emit(self, column) elif (role in (QtCore.Qt.ItemDataRole.DisplayRole, QtCore.Qt.ItemDataRole.EditRole) and text != self.text(column)): treewidget.sigItemTextChanged.emit(self, column) def itemClicked(self, col): """Called when this item is clicked on. Override this method to react to user clicks. """ class InvisibleRootItem(object): """Wrapper around a TreeWidget's invisible root item that calls TreeWidget.informTreeWidgetChange when child items are added/removed. """ def __init__(self, item): self._real_item = item def addChild(self, child): self._real_item.addChild(child) TreeWidget.informTreeWidgetChange(child) def addChildren(self, childs): self._real_item.addChildren(childs) for child in childs: TreeWidget.informTreeWidgetChange(child) def insertChild(self, index, child): self._real_item.insertChild(index, child) TreeWidget.informTreeWidgetChange(child) def insertChildren(self, index, childs): self._real_item.addChildren(index, childs) for child in childs: TreeWidget.informTreeWidgetChange(child) def removeChild(self, child): self._real_item.removeChild(child) TreeWidget.informTreeWidgetChange(child) def takeChild(self, index): child = self._real_item.takeChild(index) TreeWidget.informTreeWidgetChange(child) return child def takeChildren(self): childs = self._real_item.takeChildren() for child in childs: TreeWidget.informTreeWidgetChange(child) return childs def __getattr__(self, attr): return getattr(self._real_item, attr) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/ValueLabel.py000066400000000000000000000055021421045507400237110ustar00rootroot00000000000000from time import perf_counter from .. import functions as fn from ..Qt import QtWidgets __all__ = ['ValueLabel'] class ValueLabel(QtWidgets.QLabel): """ QLabel specifically for displaying numerical values. Extends QLabel adding some extra functionality: - displaying units with si prefix - built-in exponential averaging """ def __init__(self, parent=None, suffix='', siPrefix=False, averageTime=0, formatStr=None): """ ============== ================================================================================== **Arguments:** suffix (str or None) The suffix to place after the value siPrefix (bool) Whether to add an SI prefix to the units and display a scaled value averageTime (float) The length of time in seconds to average values. If this value is 0, then no averaging is performed. As this value increases the display value will appear to change more slowly and smoothly. formatStr (str) Optionally, provide a format string to use when displaying text. The text will be generated by calling formatStr.format(value=, avgValue=, suffix=) (see Python documentation on str.format) This option is not compatible with siPrefix ============== ================================================================================== """ QtWidgets.QLabel.__init__(self, parent) self.values = [] self.averageTime = averageTime ## no averaging by default self.suffix = suffix self.siPrefix = siPrefix if formatStr is None: formatStr = '{avgValue:0.2g} {suffix}' self.formatStr = formatStr def setValue(self, value): now = perf_counter() self.values.append((now, value)) cutoff = now - self.averageTime while len(self.values) > 0 and self.values[0][0] < cutoff: self.values.pop(0) self.update() def setFormatStr(self, text): self.formatStr = text self.update() def setAverageTime(self, t): self.averageTime = t def averageValue(self): return sum(v[1] for v in self.values) / float(len(self.values)) def paintEvent(self, ev): self.setText(self.generateText()) return super().paintEvent(ev) def generateText(self): if len(self.values) == 0: return '' avg = self.averageValue() val = self.values[-1][1] if self.siPrefix: return fn.siFormat(avg, suffix=self.suffix) else: return self.formatStr.format(value=val, avgValue=avg, suffix=self.suffix) pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/VerticalLabel.py000066400000000000000000000067041421045507400244130ustar00rootroot00000000000000import warnings from ..Qt import QtCore, QtGui, QtWidgets __all__ = ['VerticalLabel'] #class VerticalLabel(QtWidgets.QLabel): #def paintEvent(self, ev): #p = QtGui.QPainter(self) #p.rotate(-90) #self.hint = p.drawText(QtCore.QRect(-self.height(), 0, self.height(), self.width()), QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignVCenter, self.text()) #p.end() #self.setMinimumWidth(self.hint.height()) #self.setMinimumHeight(self.hint.width()) #def sizeHint(self): #if hasattr(self, 'hint'): #return QtCore.QSize(self.hint.height(), self.hint.width()) #else: #return QtCore.QSize(16, 50) class VerticalLabel(QtWidgets.QLabel): def __init__(self, text, orientation='vertical', forceWidth=True): QtWidgets.QLabel.__init__(self, text) self.forceWidth = forceWidth self.orientation = None self.setOrientation(orientation) def setOrientation(self, o): if self.orientation == o: return self.orientation = o self.update() self.updateGeometry() def paintEvent(self, ev): p = QtGui.QPainter(self) #p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200))) #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100))) #p.drawRect(self.rect().adjusted(0, 0, -1, -1)) #p.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255))) if self.orientation == 'vertical': p.rotate(-90) rgn = QtCore.QRect(-self.height(), 0, self.height(), self.width()) else: rgn = self.contentsRect() align = self.alignment() #align = QtCore.Qt.AlignmentFlag.AlignTop|QtCore.Qt.AlignmentFlag.AlignHCenter with warnings.catch_warnings(): warnings.simplefilter("ignore") self.hint = p.drawText(rgn, align, self.text()) p.end() if self.orientation == 'vertical': self.setMaximumWidth(self.hint.height()) self.setMinimumWidth(0) self.setMaximumHeight(16777215) if self.forceWidth: self.setMinimumHeight(self.hint.width()) else: self.setMinimumHeight(0) else: self.setMaximumHeight(self.hint.height()) self.setMinimumHeight(0) self.setMaximumWidth(16777215) if self.forceWidth: self.setMinimumWidth(self.hint.width()) else: self.setMinimumWidth(0) def sizeHint(self): if self.orientation == 'vertical': if hasattr(self, 'hint'): return QtCore.QSize(self.hint.height(), self.hint.width()) else: return QtCore.QSize(19, 50) else: if hasattr(self, 'hint'): return QtCore.QSize(self.hint.width(), self.hint.height()) else: return QtCore.QSize(50, 19) if __name__ == '__main__': app = QtWidgets.QApplication([]) win = QtWidgets.QMainWindow() w = QtWidgets.QWidget() l = QtWidgets.QGridLayout() w.setLayout(l) l1 = VerticalLabel("text 1", orientation='horizontal') l2 = VerticalLabel("text 2") l3 = VerticalLabel("text 3") l4 = VerticalLabel("text 4", orientation='horizontal') l.addWidget(l1, 0, 0) l.addWidget(l2, 1, 1) l.addWidget(l3, 2, 2) l.addWidget(l4, 3, 3) win.setCentralWidget(w) win.show() pyqtgraph-pyqtgraph-0.12.4/pyqtgraph/widgets/__init__.py000066400000000000000000000000001421045507400234200ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/pytest.ini000066400000000000000000000023651421045507400176730ustar00rootroot00000000000000[pytest] xvfb_width = 1920 xvfb_height = 1080 # use this due to some issues with ndarray reshape errors on CI systems xvfb_colordepth = 24 xvfb_args=-ac +extension GLX +render faulthandler_timeout = 60 filterwarnings = error # re-enable standard library warnings once::DeprecationWarning once::PendingDeprecationWarning # comfortable skipping these warnings runtime warnings # https://stackoverflow.com/questions/40845304/runtimewarning-numpy-dtype-size-changed-may-indicate-binary-incompatibility ignore:numpy.ufunc size changed, may indicate binary incompatibility.*:RuntimeWarning # pyside2_512 specific issue ignore:This method will be removed in future versions. Use 'tree.iter\(\)' or 'list\(tree.iter\(\)\)' instead.:PendingDeprecationWarning # pyqtgraph specific warning we want to ignore during testing ignore:Visible window deleted. To prevent this, store a reference to the window object. # xvfb warnings on non-linux systems ignore:Unknown config option:pytest.PytestConfigWarning # pyreadline windows warning ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working:DeprecationWarning:pyreadline:8 pyqtgraph-pyqtgraph-0.12.4/setup.py000066400000000000000000000110021421045507400173400ustar00rootroot00000000000000DESCRIPTION = """\ PyQtGraph is a pure-python graphics and GUI library built on PyQt5/PySide2 and numpy. It is intended for use in mathematics / scientific / engineering applications. Despite being written entirely in python, the library is very fast due to its heavy leverage of numpy for number crunching, Qt's GraphicsView framework for 2D display, and OpenGL for 3D display. """ setupOpts = dict( name='pyqtgraph', description='Scientific Graphics and GUI Library for Python', long_description=DESCRIPTION, license = 'MIT', url='http://www.pyqtgraph.org', author='Luke Campagnola', author_email='luke.campagnola@gmail.com', classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Development Status :: 4 - Beta", "Environment :: Other Environment", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Scientific/Engineering :: Visualization", "Topic :: Software Development :: User Interfaces", ], ) import distutils.dir_util from distutils.command import build import os, sys, re from setuptools import setup, find_namespace_packages from setuptools.command import install path = os.path.split(__file__)[0] import tools.setupHelpers as helpers ## Decide what version string to use in the build version, forcedVersion, gitVersion, initVersion = helpers.getVersionStrings(pkg='pyqtgraph') class Build(build.build): """ * Clear build path before building """ def run(self): global path ## Make sure build directory is clean buildPath = os.path.join(path, self.build_lib) if os.path.isdir(buildPath): distutils.dir_util.remove_tree(buildPath) build.build.run(self) class Install(install.install): """ * Check for previously-installed version before installing * Set version string in __init__ after building. This helps to ensure that we know when an installation came from a non-release code base. """ def run(self): global path, version, initVersion, forcedVersion, installVersion name = self.config_vars['dist_name'] path = os.path.join(self.install_libbase, 'pyqtgraph') if os.path.exists(path): raise Exception("It appears another version of %s is already " "installed at %s; remove this before installing." % (name, path)) print("Installing to %s" % path) rval = install.install.run(self) # If the version in __init__ is different from the automatically-generated # version string, then we will update __init__ in the install directory if initVersion == version: return rval try: initfile = os.path.join(path, '__init__.py') with open(initfile, "r") as file_: data = file_.read() with open(initfile, "w") as file_: file_.write(re.sub(r"__version__ = .*", "__version__ = '%s'" % version, data)) installVersion = version except: sys.stderr.write("Warning: Error occurred while setting version string in build path. " "Installation will use the original version string " "%s instead.\n" % (initVersion) ) if forcedVersion: raise installVersion = initVersion sys.excepthook(*sys.exc_info()) return rval setup( version=version, cmdclass={ 'build': Build, 'install': Install, 'deb': helpers.DebCommand, 'test': helpers.TestCommand, 'debug': helpers.DebugCommand, 'mergetest': helpers.MergeTestCommand, 'asv_config': helpers.ASVConfigCommand, 'style': helpers.StyleCommand }, packages=find_namespace_packages(include=['pyqtgraph', 'pyqtgraph.*']), python_requires=">=3.7", package_dir={"pyqtgraph": "pyqtgraph"}, package_data={ 'pyqtgraph.examples': ['optics/*.gz', 'relativity/presets/*.cfg'], "pyqtgraph.icons": ["*.svg", "*.png"], "pyqtgraph": [ "colors/maps/*.csv", "colors/maps/*.txt", "colors/maps/*.hex", ], }, install_requires = [ 'numpy>=1.17.0', ], **setupOpts ) pyqtgraph-pyqtgraph-0.12.4/test.py000066400000000000000000000007101421045507400171630ustar00rootroot00000000000000""" Script for invoking pytest with options to select Qt library """ import sys import pytest args = sys.argv[1:] if '--pyqt5' in args: args.remove('--pyqt5') import PyQt5 elif '--pyside2' in args: args.remove('--pyside2') import PySide2 elif '--pyside6' in args: args.remove('--pyside6') import PySide6 elif '--pyqt6' in args: args.remove('--pyqt6') import PyQt6 import pyqtgraph as pg pg.systemInfo() pytest.main(args) pyqtgraph-pyqtgraph-0.12.4/tests/000077500000000000000000000000001421045507400167765ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/__init__.py000066400000000000000000000003021421045507400211020ustar00rootroot00000000000000from .image_testing import TransposedImageItem, assertImageApproved from .ui_testing import ( mouseClick, mouseDrag, mouseMove, mousePress, mouseRelease, resizeWindow, ) pyqtgraph-pyqtgraph-0.12.4/tests/conftest.py000066400000000000000000000003231421045507400211730ustar00rootroot00000000000000import os import sys import pytest @pytest.fixture def tmp_module(tmp_path): module_path = os.fsdecode(tmp_path) sys.path.insert(0, module_path) yield module_path sys.path.remove(module_path) pyqtgraph-pyqtgraph-0.12.4/tests/dockarea/000077500000000000000000000000001421045507400205475ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/dockarea/test_dock.py000066400000000000000000000013701421045507400231010ustar00rootroot00000000000000import pytest import pyqtgraph as pg pg.mkQApp() import pyqtgraph.dockarea as da def test_dock(): name = "Γ©vΓ¨nts_zΓ héér" dock = da.Dock(name=name) # make sure unicode names work correctly assert dock.name() == name # no surprises in return type. assert type(dock.name()) == type(name) def test_closable_dock(): name = "Test close dock" dock = da.Dock(name=name, closable=True) assert dock.label.closeButton is not None def test_hide_title_dock(): name = "Test hide title dock" dock = da.Dock(name=name, hideTitle=True) assert dock.labelHidden == True def test_close(): name = "Test close dock" dock = da.Dock(name=name, hideTitle=True) with pytest.warns(Warning): dock.close() pyqtgraph-pyqtgraph-0.12.4/tests/dockarea/test_dockarea.py000066400000000000000000000141061421045507400237330ustar00rootroot00000000000000import pytest import pyqtgraph as pg import pyqtgraph.dockarea as da pg.mkQApp() def test_dockarea(): a = da.DockArea() d1 = da.Dock("dock 1") a.addDock(d1, 'left') assert a.topContainer is d1.container() assert d1.container().container() is a assert d1.area is a assert a.topContainer.widget(0) is d1 d2 = da.Dock("dock 2") a.addDock(d2, 'right') assert a.topContainer is d1.container() assert a.topContainer is d2.container() assert d1.container().container() is a assert d2.container().container() is a assert d2.area is a assert a.topContainer.widget(0) is d1 assert a.topContainer.widget(1) is d2 d3 = da.Dock("dock 3") a.addDock(d3, 'bottom') assert a.topContainer is d3.container() assert d2.container().container() is d3.container() assert d1.container().container() is d3.container() assert d1.container().container().container() is a assert d2.container().container().container() is a assert d3.container().container() is a assert d3.area is a assert d2.area is a assert a.topContainer.widget(0) is d1.container() assert a.topContainer.widget(1) is d3 d4 = da.Dock("dock 4") a.addDock(d4, 'below', d3) assert d4.container().type() == 'tab' assert d4.container() is d3.container() assert d3.container().container() is d2.container().container() assert d4.area is a a.printState() # layout now looks like: # vcontainer # hcontainer # dock 1 # dock 2 # tcontainer # dock 3 # dock 4 # test save/restore state state = a.saveState() a2 = da.DockArea() # default behavior is to raise exception if docks are missing with pytest.raises(Exception): a2.restoreState(state) # test restore with ignore missing a2.restoreState(state, missing='ignore') assert a2.topContainer is None # test restore with auto-create a2.restoreState(state, missing='create') assert a2.saveState() == state a2.printState() # double-check that state actually matches the output of saveState() c1 = a2.topContainer assert c1.type() == 'vertical' c2 = c1.widget(0) c3 = c1.widget(1) assert c2.type() == 'horizontal' assert c2.widget(0).name() == 'dock 1' assert c2.widget(1).name() == 'dock 2' assert c3.type() == 'tab' assert c3.widget(0).name() == 'dock 3' assert c3.widget(1).name() == 'dock 4' # test restore with docks already present a3 = da.DockArea() a3docks = [] for i in range(1, 5): dock = da.Dock('dock %d' % i) a3docks.append(dock) a3.addDock(dock, 'right') a3.restoreState(state) assert a3.saveState() == state # test restore with extra docks present a3 = da.DockArea() a3docks = [] for i in [1, 2, 5, 4, 3]: dock = da.Dock('dock %d' % i) a3docks.append(dock) a3.addDock(dock, 'left') a3.restoreState(state) a3.printState() # test a more complex restore a4 = da.DockArea() state1 = {'float': [], 'main': ('horizontal', [ ('vertical', [ ('horizontal', [ ('tab', [ ('dock', 'dock1', {}), ('dock', 'dock2', {}), ('dock', 'dock3', {}), ('dock', 'dock4', {}) ], {'index': 1}), ('vertical', [ ('dock', 'dock5', {}), ('horizontal', [ ('dock', 'dock6', {}), ('dock', 'dock7', {}) ], {'sizes': [184, 363]}) ], {'sizes': [355, 120]}) ], {'sizes': [9, 552]}) ], {'sizes': [480]}), ('dock', 'dock8', {}) ], {'sizes': [566, 69]}) } state2 = {'float': [], 'main': ('horizontal', [ ('vertical', [ ('horizontal', [ ('dock', 'dock2', {}), ('vertical', [ ('dock', 'dock5', {}), ('horizontal', [ ('dock', 'dock6', {}), ('dock', 'dock7', {}) ], {'sizes': [492, 485]}) ], {'sizes': [936, 0]}) ], {'sizes': [172, 982]}) ], {'sizes': [941]}), ('vertical', [ ('dock', 'dock8', {}), ('dock', 'dock4', {}), ('dock', 'dock1', {}) ], {'sizes': [681, 225, 25]}) ], {'sizes': [1159, 116]})} a4.restoreState(state1, missing='create') # dock3 not mentioned in restored state; stays in dockarea by default c, d = a4.findAll() assert d['dock3'].area is a4 a4.restoreState(state2, missing='ignore', extra='float') a4.printState() c, d = a4.findAll() # dock3 not mentioned in restored state; goes to float due to `extra` argument assert d['dock3'].area is not a4 assert d['dock1'].container() is d['dock4'].container() is d['dock8'].container() assert d['dock6'].container() is d['dock7'].container() assert a4 is d['dock2'].area is d['dock2'].container().container().container() assert a4 is d['dock5'].area is d['dock5'].container().container().container().container() # States should be the same with two exceptions: # dock3 is in a float because it does not appear in state2 # a superfluous vertical splitter in state2 has been removed state4 = a4.saveState() state4['main'][1][0] = state4['main'][1][0][1][0] with pytest.raises(AssertionError): # this test doesn't work, likely due to clean_state not working as intended assert clean_state(state4['main']) == clean_state(state2['main']) def clean_state(state): # return state dict with sizes removed ch = [clean_state(x) for x in state[1]] if isinstance(state[1], list) else state[1] state = (state[0], ch, {}) return state pyqtgraph-pyqtgraph-0.12.4/tests/exporters/000077500000000000000000000000001421045507400210315ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/exporters/__init__.py000066400000000000000000000000001421045507400231300ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/exporters/test_csv.py000066400000000000000000000030201421045507400232300ustar00rootroot00000000000000""" CSV export test """ from __future__ import absolute_import, division, print_function import csv import tempfile import numpy as np import pyqtgraph as pg app = pg.mkQApp() def approxeq(a, b): return (a-b) <= ((a + b) * 1e-6) def test_CSVExporter(): plt = pg.plot() y1 = [1,3,2,3,1,6,9,8,4,2] plt.plot(y=y1, name='myPlot') y2 = [3,4,6,1,2,4,2,3,5,3,5,1,3] x2 = np.linspace(0, 1.0, len(y2)) plt.plot(x=x2, y=y2) y3 = [1,5,2,3,4,6,1,2,4,2,3,5,3] x3 = np.linspace(0, 1.0, len(y3)+1) plt.plot(x=x3, y=y3, stepMode="center") ex = pg.exporters.CSVExporter(plt.plotItem) with tempfile.NamedTemporaryFile(mode="w+t", suffix='.csv', encoding="utf-8", delete=False) as tf: print("using %s as a temporary file" % tf.name) ex.export(fileName=tf.name) lines = [line for line in csv.reader(tf)] header = lines.pop(0) assert header == ['myPlot_x', 'myPlot_y', 'x0001', 'y0001', 'x0002', 'y0002'] for i, vals in enumerate(lines): vals = list(map(str.strip, vals)) assert (i >= len(y1) and vals[0] == '') or approxeq(float(vals[0]), i) assert (i >= len(y1) and vals[1] == '') or approxeq(float(vals[1]), y1[i]) assert (i >= len(x2) and vals[2] == '') or approxeq(float(vals[2]), x2[i]) assert (i >= len(y2) and vals[3] == '') or approxeq(float(vals[3]), y2[i]) assert (i >= len(x3) and vals[4] == '') or approxeq(float(vals[4]), x3[i]) assert (i >= len(y3) and vals[5] == '') or approxeq(float(vals[5]), y3[i]) pyqtgraph-pyqtgraph-0.12.4/tests/exporters/test_hdf5.py000066400000000000000000000040011421045507400232630ustar00rootroot00000000000000import numpy as np import pytest from numpy.testing import assert_equal import pyqtgraph as pg from pyqtgraph.exporters import HDF5Exporter h5py = pytest.importorskip("h5py") @pytest.fixture def tmp_h5(tmp_path): yield tmp_path / "data.h5" @pytest.mark.parametrize("combine", [False, True]) def test_HDF5Exporter(tmp_h5, combine): # Basic test of functionality: multiple curves with shared x array. Tests # both options for stacking the data (columnMode). x = np.linspace(0, 1, 100) y1 = np.sin(x) y2 = np.cos(x) plt = pg.plot() plt.plot(x=x, y=y1) plt.plot(x=x, y=y2) ex = HDF5Exporter(plt.plotItem) if combine: ex.parameters()['columnMode'] = '(x,y,y,y) for all plots' ex.export(fileName=tmp_h5) with h5py.File(tmp_h5, 'r') as f: # should be a single dataset with the name of the exporter dset = f[ex.parameters()['Name']] assert isinstance(dset, h5py.Dataset) if combine: assert_equal(np.array([x, y1, y2]), dset) else: assert_equal(np.array([x, y1, x, y2]), dset) def test_HDF5Exporter_unequal_lengths(tmp_h5): # Test export with multiple curves of different size. The exporter should # detect this and create multiple hdf5 datasets under a group. x1 = np.linspace(0, 1, 10) y1 = np.sin(x1) x2 = np.linspace(0, 1, 100) y2 = np.cos(x2) plt = pg.plot() plt.plot(x=x1, y=y1, name='plot0') plt.plot(x=x2, y=y2) ex = HDF5Exporter(plt.plotItem) ex.export(fileName=tmp_h5) with h5py.File(tmp_h5, 'r') as f: # should be a group with the name of the exporter group = f[ex.parameters()['Name']] assert isinstance(group, h5py.Group) # should be a dataset under the group with the name of the PlotItem assert_equal(np.array([x1, y1]), group['plot0']) # should be a dataset under the group with a default name that's the # index of the curve in the PlotItem assert_equal(np.array([x2, y2]), group['1']) pyqtgraph-pyqtgraph-0.12.4/tests/exporters/test_image.py000066400000000000000000000014311421045507400235230ustar00rootroot00000000000000import numpy as np import pyqtgraph as pg import pyqtgraph.functions as fn from pyqtgraph.exporters import ImageExporter from pyqtgraph.Qt import QtGui app = pg.mkQApp() def test_ImageExporter_filename_dialog(): """Tests ImageExporter code path that opens a file dialog. Regression test for pull request 1133.""" p = pg.plot() exp = ImageExporter(p.getPlotItem()) exp.export() def test_ImageExporter_toBytes(): p = pg.plot() p.hideAxis('bottom') p.hideAxis('left') exp = ImageExporter(p.getPlotItem()) qimg = exp.export(toBytes=True) qimg = qimg.convertToFormat(QtGui.QImage.Format.Format_RGBA8888) data = fn.ndarray_from_qimage(qimg) black = (0, 0, 0, 255) assert np.all(data == black), "Exported image should be entirely black." pyqtgraph-pyqtgraph-0.12.4/tests/exporters/test_matplotlib.py000066400000000000000000000034031421045507400246110ustar00rootroot00000000000000import pytest import pyqtgraph as pg from pyqtgraph.exporters import MatplotlibExporter pytest.importorskip("matplotlib") import matplotlib app = pg.mkQApp() skip_qt6 = pytest.mark.skipif( # availability of QtAgg signifies Qt6 support pg.Qt.QT_LIB in ["PySide6", "PyQt6"] and "QtAgg" not in matplotlib.rcsetup.interactive_bk, reason= ( "installed version of Matplotlib does not support Qt6, " "see https://github.com/matplotlib/matplotlib/pull/19255" ) ) @skip_qt6 def test_MatplotlibExporter(): plt = pg.plot() # curve item plt.plot([0, 1, 2], [0, 1, 2]) # scatter item plt.plot([0, 1, 2], [1, 2, 3], pen=None, symbolBrush='r') # curve + scatter plt.plot([0, 1, 2], [2, 3, 4], pen='k', symbolBrush='r') exp = MatplotlibExporter(plt.getPlotItem()) exp.export() @skip_qt6 def test_MatplotlibExporter_nonplotitem(): # attempting to export something other than a PlotItem raises an exception plt = pg.plot() plt.plot([0, 1, 2], [2, 3, 4]) exp = MatplotlibExporter(plt.getPlotItem().getViewBox()) with pytest.raises(Exception): exp.export() @skip_qt6 @pytest.mark.parametrize('scale', [1e10, 1e-9]) def test_MatplotlibExporter_siscale(scale): # coarse test to verify that plot data is scaled before export when # autoSIPrefix is in effect (so mpl doesn't add its own multiplier label) plt = pg.plot([0, 1, 2], [(i+1)*scale for i in range(3)]) # set the label so autoSIPrefix works plt.setLabel('left', 'magnitude') exp = MatplotlibExporter(plt.getPlotItem()) exp.export() mpw = MatplotlibExporter.windows[-1] fig = mpw.getFigure() ymin, ymax = fig.axes[0].get_ylim() if scale < 1: assert ymax > scale else: assert ymax < scale pyqtgraph-pyqtgraph-0.12.4/tests/exporters/test_svg.py000066400000000000000000000037711421045507400232510ustar00rootroot00000000000000import pyqtgraph as pg app = pg.mkQApp() def test_plotscene(tmpdir): pg.setConfigOption('foreground', (0,0,0)) w = pg.GraphicsLayoutWidget() w.show() p1 = w.addPlot() p2 = w.addPlot() p1.plot([1,3,2,3,1,6,9,8,4,2,3,5,3], pen={'color':'k'}) p1.setXRange(0,5) p2.plot([1,5,2,3,4,6,1,2,4,2,3,5,3], pen={'color':'k', 'cosmetic':False, 'width': 0.3}) app.processEvents() app.processEvents() ex = pg.exporters.SVGExporter(w.scene()) tf = tmpdir.join("expot.svg") ex.export(fileName=tf) # clean up after the test is done w.close() def test_simple(tmpdir): view = pg.GraphicsView() view.show() scene = view.sceneObj rect = pg.QtWidgets.QGraphicsRectItem(0, 0, 100, 100) scene.addItem(rect) rect.setPos(20,20) tr = pg.QtGui.QTransform() rect.setTransform(tr.translate(50, 50).rotate(30).scale(0.5, 0.5)) rect1 = pg.QtWidgets.QGraphicsRectItem(0, 0, 100, 100) rect1.setParentItem(rect) rect1.setFlag(rect1.GraphicsItemFlag.ItemIgnoresTransformations) rect1.setPos(20, 20) rect1.setScale(2) el1 = pg.QtWidgets.QGraphicsEllipseItem(0, 0, 100, 100) el1.setParentItem(rect1) grp = pg.ItemGroup() grp.setParentItem(rect) tr = pg.QtGui.QTransform() grp.setTransform(tr.translate(200, 0).rotate(30)) rect2 = pg.QtWidgets.QGraphicsRectItem(0, 0, 100, 25) rect2.setFlag(rect2.GraphicsItemFlag.ItemClipsChildrenToShape) rect2.setParentItem(grp) rect2.setPos(0,25) rect2.setRotation(30) el = pg.QtWidgets.QGraphicsEllipseItem(0, 0, 100, 50) tr = pg.QtGui.QTransform() el.setTransform(tr.translate(10, -5).scale(0.5, 2)) el.setParentItem(rect2) grp2 = pg.ItemGroup() scene.addItem(grp2) grp2.setScale(100) rect3 = pg.QtWidgets.QGraphicsRectItem(0,0,2,2) rect3.setPen(pg.mkPen(width=1, cosmetic=False)) grp2.addItem(rect3) ex = pg.exporters.SVGExporter(scene) tf = tmpdir.join("expot.svg") ex.export(fileName=tf) pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/000077500000000000000000000000001421045507400216005ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/PlotItem/000077500000000000000000000000001421045507400233355ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/PlotItem/test_PlotItem.py000066400000000000000000000060211421045507400265020ustar00rootroot00000000000000import numpy as np import pytest import pyqtgraph as pg app = pg.mkQApp() @pytest.mark.parametrize('orientation', ['left', 'right', 'top', 'bottom']) def test_PlotItem_shared_axis_items(orientation): """Adding an AxisItem to multiple plots raises RuntimeError""" ax1 = pg.AxisItem(orientation) ax2 = pg.AxisItem(orientation) layout = pg.GraphicsLayoutWidget() _ = layout.addPlot(axisItems={orientation: ax1}) pi2 = layout.addPlot() # left or bottom replaces, right or top adds new pi2.setAxisItems({orientation: ax2}) with pytest.raises(RuntimeError): pi2.setAxisItems({orientation: ax1}) def test_PlotItem_maxTraces(): item = pg.PlotItem() curve1 = pg.PlotDataItem(np.random.normal(size=10)) item.addItem(curve1) assert curve1.isVisible(), "curve1 should be visible" item.ctrl.maxTracesCheck.setChecked(True) item.ctrl.maxTracesSpin.setValue(0) assert not curve1.isVisible(), "curve1 should not be visible" item.ctrl.maxTracesCheck.setChecked(False) assert curve1.isVisible(), "curve1 should be visible" curve2 = pg.PlotDataItem(np.random.normal(size=10)) item.addItem(curve2) assert curve2.isVisible(), "curve2 should be visible" item.ctrl.maxTracesCheck.setChecked(True) item.ctrl.maxTracesSpin.setValue(1) assert curve2.isVisible(), "curve2 should be visible" assert not curve1.isVisible(), "curve1 should not be visible" assert curve1 in item.curves, "curve1 should be in the item's curves" item.ctrl.forgetTracesCheck.setChecked(True) assert curve2 in item.curves, "curve2 should be in the item's curves" assert curve1 not in item.curves, "curve1 should not be in the item's curves" def test_PlotItem_preserve_external_visibility_control(): item = pg.PlotItem() curve1 = pg.PlotDataItem(np.random.normal(size=10)) curve2 = pg.PlotDataItem(np.random.normal(size=10)) item.addItem(curve1) curve1.hide() item.addItem(curve2) assert not curve1.isVisible() item.removeItem(curve2) assert not curve1.isVisible() def test_plotitem_menu_initialize(): """Test the menu initialization of the plotitem""" item = pg.PlotItem() assert item.menuEnabled() is True viewbox = item.vb assert viewbox is not None assert viewbox.menu is not None assert viewbox.menuEnabled() is True item = pg.PlotItem(enableMenu=False) assert item.menuEnabled() is False viewbox = item.vb assert viewbox is not None assert viewbox.menu is None assert viewbox.menuEnabled() is False viewbox = pg.ViewBox() item = pg.PlotItem(viewBox=viewbox, enableMenu=False) assert item.menuEnabled() is False viewbox = item.vb assert viewbox is not None assert viewbox.menu is not None assert viewbox.menuEnabled() is True viewbox = pg.ViewBox(enableMenu=False) item = pg.PlotItem(viewBox=viewbox) assert item.menuEnabled() is True viewbox = item.vb assert viewbox is not None assert viewbox.menu is None assert viewbox.menuEnabled() is False pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/ViewBox/000077500000000000000000000000001421045507400231635ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/ViewBox/test_ViewBox.py000066400000000000000000000051211421045507400261560ustar00rootroot00000000000000#import PySide import pytest import pyqtgraph as pg app = pg.mkQApp() qtest = pg.Qt.QtTest.QTest QRectF = pg.QtCore.QRectF def assertMapping(vb, r1, r2): assert vb.mapFromView(r1.topLeft()) == r2.topLeft() assert vb.mapFromView(r1.bottomLeft()) == r2.bottomLeft() assert vb.mapFromView(r1.topRight()) == r2.topRight() assert vb.mapFromView(r1.bottomRight()) == r2.bottomRight() def init_viewbox(): """Helper function to init the ViewBox """ global win, vb win = pg.GraphicsLayoutWidget() win.ci.layout.setContentsMargins(0,0,0,0) win.resize(200, 200) win.show() vb = win.addViewBox() # set range before viewbox is shown vb.setRange(xRange=[0, 10], yRange=[0, 10], padding=0) # required to make mapFromView work properly. qtest.qWaitForWindowExposed(win) g = pg.GridItem() vb.addItem(g) app.processEvents() def test_ViewBox(): init_viewbox() w = vb.geometry().width() h = vb.geometry().height() view1 = QRectF(0, 0, 10, 10) size1 = QRectF(0, h, w, -h) assertMapping(vb, view1, size1) # test resize win.resize(400, 400) app.processEvents() w = vb.geometry().width() h = vb.geometry().height() size1 = QRectF(0, h, w, -h) assertMapping(vb, view1, size1) # now lock aspect vb.setAspectLocked() # test wide resize win.resize(800, 400) app.processEvents() w = vb.geometry().width() h = vb.geometry().height() view1 = QRectF(-5, 0, 20, 10) size1 = QRectF(0, h, w, -h) assertMapping(vb, view1, size1) # test tall resize win.resize(200, 400) app.processEvents() w = vb.geometry().width() h = vb.geometry().height() view1 = QRectF(0, -5, 10, 20) size1 = QRectF(0, h, w, -h) assertMapping(vb, view1, size1) win.close() def test_ViewBox_setMenuEnabled(): init_viewbox() vb.setMenuEnabled(True) assert vb.menu is not None vb.setMenuEnabled(False) assert vb.menu is None skipreason = "Skipping this test until someone has time to fix it." @pytest.mark.skipif(True, reason=skipreason) def test_limits_and_resize(): init_viewbox() # now lock aspect vb.setAspectLocked() # test limits + resize (aspect ratio constraint has priority over limits win.resize(400, 400) app.processEvents() vb.setLimits(xMin=0, xMax=10, yMin=0, yMax=10) win.resize(800, 400) app.processEvents() w = vb.geometry().width() h = vb.geometry().height() view1 = QRectF(-5, 0, 20, 10) size1 = QRectF(0, h, w, -h) assertMapping(vb, view1, size1) pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/ViewBox/test_ViewBoxZoom.py000066400000000000000000000143221421045507400270260ustar00rootroot00000000000000import pyqtgraph as pg app = pg.mkQApp() def test_zoom_normal(): vb = pg.ViewBox() testRange = pg.QtCore.QRect(0, 0, 10, 20) vb.setRange(testRange, padding=0) vbViewRange = vb.getState()['viewRange'] assert vbViewRange == [[testRange.left(), testRange.right()], [testRange.top(), testRange.bottom()]] def test_zoom_limit(): """Test zooming with X and Y limits set""" vb = pg.ViewBox() vb.setLimits(xMin=0, xMax=10, yMin=0, yMax=10) # Try zooming within limits. Should return unmodified testRange = pg.QtCore.QRect(0, 0, 9, 9) vb.setRange(testRange, padding=0) vbViewRange = vb.getState()['viewRange'] assert vbViewRange == [[testRange.left(), testRange.right()], [testRange.top(), testRange.bottom()]] # And outside limits. both view range and targetRange should be set to limits testRange = pg.QtCore.QRect(-5, -5, 16, 20) vb.setRange(testRange, padding=0) expected = [[0, 10], [0, 10]] vbState = vb.getState() assert vbState['targetRange'] == expected assert vbState['viewRange'] == expected def test_zoom_range_limit(): """Test zooming with XRange and YRange limits set, but no X and Y limits""" vb = pg.ViewBox() vb.setLimits(minXRange=5, maxXRange=10, minYRange=5, maxYRange=10) # Try something within limits testRange = pg.QtCore.QRect(-15, -15, 7, 7) vb.setRange(testRange, padding=0) expected = [[testRange.left(), testRange.right()], [testRange.top(), testRange.bottom()]] vbViewRange = vb.getState()['viewRange'] assert vbViewRange == expected # and outside limits testRange = pg.QtCore.QRect(-15, -15, 17, 17) # Code should center the required width reduction, so move each side by 3 expected = [[testRange.left() + 3, testRange.right() - 3], [testRange.top() + 3, testRange.bottom() - 3]] vb.setRange(testRange, padding=0) vbViewRange = vb.getState()['viewRange'] vbTargetRange = vb.getState()['targetRange'] assert vbViewRange == expected assert vbTargetRange == expected def test_zoom_ratio(): """Test zooming with a fixed aspect ratio set""" vb = pg.ViewBox(lockAspect=1) # Give the viewbox a size of the proper aspect ratio to keep things easy vb.setFixedHeight(10) vb.setFixedWidth(10) # request a range with a good ratio testRange = pg.QtCore.QRect(0, 0, 10, 10) vb.setRange(testRange, padding=0) expected = [[testRange.left(), testRange.right()], [testRange.top(), testRange.bottom()]] viewRange = vb.getState()['viewRange'] viewWidth = viewRange[0][1] - viewRange[0][0] viewHeight = viewRange[1][1] - viewRange[1][0] # Assert that the width and height are equal, since we locked the aspect ratio at 1 assert viewWidth == viewHeight # and for good measure, that it is the same as the test range assert viewRange == expected # Now try to set to something with a different aspect ratio testRange = pg.QtCore.QRect(0, 0, 10, 20) vb.setRange(testRange, padding=0) viewRange = vb.getState()['viewRange'] viewWidth = viewRange[0][1] - viewRange[0][0] viewHeight = viewRange[1][1] - viewRange[1][0] # Don't really care what we got here, as long as the width and height are the same assert viewWidth == viewHeight def test_zoom_ratio2(): """Slightly more complicated zoom ratio test, where the view box shape does not match the ratio""" vb = pg.ViewBox(lockAspect=1) # twice as wide as tall vb.setFixedHeight(10) vb.setFixedWidth(20) # more or less random requested range testRange = pg.QtCore.QRect(0, 0, 10, 15) vb.setRange(testRange, padding=0) viewRange = vb.getState()['viewRange'] viewWidth = viewRange[0][1] - viewRange[0][0] viewHeight = viewRange[1][1] - viewRange[1][0] # View width should be twice as wide as the height, # since the viewbox is twice as wide as it is tall. assert viewWidth == 2 * viewHeight def test_zoom_ratio_with_limits1(): """Test zoom with both ratio and limits set""" vb = pg.ViewBox(lockAspect=1) # twice as wide as tall vb.setFixedHeight(10) vb.setFixedWidth(20) # set some limits vb.setLimits(xMin=-5, xMax=5, yMin=-5, yMax=5) # Try to zoom too tall testRange = pg.QtCore.QRect(0, 0, 6, 10) vb.setRange(testRange, padding=0) viewRange = vb.getState()['viewRange'] viewWidth = viewRange[0][1] - viewRange[0][0] viewHeight = viewRange[1][1] - viewRange[1][0] # Make sure our view is within limits and the proper aspect ratio assert viewRange[0][0] >= -5 assert viewRange[0][1] <= 5 assert viewRange[1][0] >= -5 assert viewRange[1][1] <= 5 assert viewWidth == 2 * viewHeight def test_zoom_ratio_with_limits2(): vb = pg.ViewBox(lockAspect=1) # twice as wide as tall vb.setFixedHeight(10) vb.setFixedWidth(20) # set some limits vb.setLimits(xMin=-5, xMax=5, yMin=-5, yMax=5) # Same thing, but out-of-range the other way testRange = pg.QtCore.QRect(0, 0, 16, 6) vb.setRange(testRange, padding=0) viewRange = vb.getState()['viewRange'] viewWidth = viewRange[0][1] - viewRange[0][0] viewHeight = viewRange[1][1] - viewRange[1][0] # Make sure our view is within limits and the proper aspect ratio assert viewRange[0][0] >= -5 assert viewRange[0][1] <= 5 assert viewRange[1][0] >= -5 assert viewRange[1][1] <= 5 assert viewWidth == 2 * viewHeight def test_zoom_ratio_with_limits_out_of_range(): vb = pg.ViewBox(lockAspect=1) # twice as wide as tall vb.setFixedHeight(10) vb.setFixedWidth(20) # set some limits vb.setLimits(xMin=-5, xMax=5, yMin=-5, yMax=5) # Request something completely out-of-range and out-of-aspect testRange = pg.QtCore.QRect(10, 10, 25, 100) vb.setRange(testRange, padding=0) viewRange = vb.getState()['viewRange'] viewWidth = viewRange[0][1] - viewRange[0][0] viewHeight = viewRange[1][1] - viewRange[1][0] # Make sure our view is within limits and the proper aspect ratio assert viewRange[0][0] >= -5 assert viewRange[0][1] <= 5 assert viewRange[1][0] >= -5 assert viewRange[1][1] <= 5 assert viewWidth == 2 * viewHeight pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_ArrowItem.py000066400000000000000000000003471421045507400251260ustar00rootroot00000000000000import pyqtgraph as pg app = pg.mkQApp() def test_ArrowItem_parent(): parent = pg.GraphicsObject() a = pg.ArrowItem(parent=parent, pos=(10, 10)) assert a.parentItem() is parent assert a.pos() == pg.Point(10, 10) pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_AxisItem.py000066400000000000000000000127301421045507400247370ustar00rootroot00000000000000from math import isclose import pytest import pyqtgraph as pg app = pg.mkQApp() def test_AxisItem_stopAxisAtTick(monkeypatch): def test_bottom(p, axisSpec, tickSpecs, textSpecs): viewPixelSize = view.viewPixelSize() assert isclose(view.mapToView(axisSpec[1]).x(), 0.25, abs_tol=viewPixelSize[0]) assert isclose(view.mapToView(axisSpec[2]).x(), 0.75, abs_tol=viewPixelSize[0]) def test_left(p, axisSpec, tickSpecs, textSpecs): viewPixelSize = view.viewPixelSize() assert isclose(view.mapToView(axisSpec[1]).y(), 0.875, abs_tol=viewPixelSize[1]) assert isclose(view.mapToView(axisSpec[2]).y(), 0.125, abs_tol=viewPixelSize[1]) plot = pg.PlotWidget() view = plot.plotItem.getViewBox() bottom = plot.getAxis("bottom") bottom.setRange(0, 1) bticks = [(0.25, "a"), (0.6, "b"), (0.75, "c")] bottom.setTicks([bticks, bticks]) bottom.setStyle(stopAxisAtTick=(True, True)) monkeypatch.setattr(bottom, "drawPicture", test_bottom) left = plot.getAxis("left") lticks = [(0.125, "a"), (0.55, "b"), (0.875, "c")] left.setTicks([lticks, lticks]) left.setRange(0, 1) left.setStyle(stopAxisAtTick=(True, True)) monkeypatch.setattr(left, "drawPicture", test_left) plot.show() app.processEvents() plot.close() def test_AxisItem_viewUnlink(): plot = pg.PlotWidget() view = plot.plotItem.getViewBox() axis = plot.getAxis("bottom") assert axis.linkedView() == view axis.unlinkFromView() assert axis.linkedView() is None class FakeSignal: def __init__(self): self.calls = [] def connect(self, *args, **kwargs): self.calls.append('connect') def disconnect(self, *args, **kwargs): self.calls.append('disconnect') class FakeView: def __init__(self): self.sigYRangeChanged = FakeSignal() self.sigXRangeChanged = FakeSignal() self.sigResized = FakeSignal() def test_AxisItem_bottomRelink(): axis = pg.AxisItem('bottom') fake_view = FakeView() axis.linkToView(fake_view) assert axis.linkedView() == fake_view assert fake_view.sigYRangeChanged.calls == [] assert fake_view.sigXRangeChanged.calls == ['connect'] assert fake_view.sigResized.calls == ['connect'] axis.unlinkFromView() assert fake_view.sigYRangeChanged.calls == [] assert fake_view.sigXRangeChanged.calls == ['connect', 'disconnect'] assert fake_view.sigResized.calls == ['connect', 'disconnect'] def test_AxisItem_leftRelink(): axis = pg.AxisItem('left') fake_view = FakeView() axis.linkToView(fake_view) assert axis.linkedView() == fake_view assert fake_view.sigYRangeChanged.calls == ['connect'] assert fake_view.sigXRangeChanged.calls == [] assert fake_view.sigResized.calls == ['connect'] axis.unlinkFromView() assert fake_view.sigYRangeChanged.calls == ['connect', 'disconnect'] assert fake_view.sigXRangeChanged.calls == [] assert fake_view.sigResized.calls == ['connect', 'disconnect'] def test_AxisItem_tickFont(monkeypatch): def collides(textSpecs): fontMetrics = pg.Qt.QtGui.QFontMetrics(font) for rect, _, text in textSpecs: br = fontMetrics.tightBoundingRect(text) if rect.height() < br.height() or rect.width() < br.width(): return True return False def test_collision(p, axisSpec, tickSpecs, textSpecs): assert not collides(textSpecs) plot = pg.PlotWidget() bottom = plot.getAxis("bottom") left = plot.getAxis("left") font = bottom.linkedView().font() font.setPointSize(25) bottom.setStyle(tickFont=font) left.setStyle(tickFont=font) monkeypatch.setattr(bottom, "drawPicture", test_collision) monkeypatch.setattr(left, "drawPicture", test_collision) plot.show() app.processEvents() plot.close() def test_AxisItem_label_visibility(): """Test the visibility of the axis item using `setLabel`""" axis = pg.AxisItem('left') assert axis.labelText == '' assert axis.labelUnits == '' assert not axis.label.isVisible() axis.setLabel(text='Position', units='mm') assert axis.labelText == 'Position' assert axis.labelUnits == 'mm' assert axis.label.isVisible() # XXX: `None` is converted to empty strings. axis.setLabel(text=None, units=None) assert axis.labelText == '' assert axis.labelUnits == '' assert not axis.label.isVisible() axis.setLabel(text='Current', units=None) assert axis.labelText == 'Current' assert axis.labelUnits == '' assert axis.label.isVisible() axis.setLabel(text=None, units=None) assert not axis.label.isVisible() axis.setLabel(text='', units='V') assert axis.labelText == '' assert axis.labelUnits == 'V' assert axis.label.isVisible() @pytest.mark.parametrize( "orientation,x,y,expected", [ ('top', False, True, False), ('top', True, False, True), ('left', False, True, True), ('left', True, False, False), ], ) def test_AxisItem_setLogMode_two_args(orientation, x, y, expected): axis = pg.AxisItem(orientation) axis.setLogMode(x, y) assert axis.logMode == expected @pytest.mark.parametrize( "orientation,log,expected", [ ('top', True, True), ('left', True, True), ('top', False, False), ('left', False, False), ], ) def test_AxisItem_setLogMode_one_arg(orientation, log, expected): axis = pg.AxisItem(orientation) axis.setLogMode(log) assert axis.logMode == expected pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_ErrorBarItem.py000066400000000000000000000020471421045507400255510ustar00rootroot00000000000000import numpy as np import pyqtgraph as pg app = pg.mkQApp() def test_ErrorBarItem_defer_data(): plot = pg.PlotWidget() plot.show() # plot some data away from the origin to set the view rect x = np.arange(5) + 10 curve = pg.PlotCurveItem(x=x, y=x) plot.addItem(curve) app.processEvents() app.processEvents() r_no_ebi = plot.viewRect() # ErrorBarItem with no data shouldn't affect the view rect err = pg.ErrorBarItem() plot.addItem(err) app.processEvents() app.processEvents() r_empty_ebi = plot.viewRect() assert r_no_ebi.height() == r_empty_ebi.height() err.setData(x=x, y=x, bottom=x, top=x) app.processEvents() app.processEvents() r_ebi = plot.viewRect() assert r_ebi.height() > r_empty_ebi.height() # unset data, ErrorBarItem disappears and view rect goes back to original err.setData(x=None, y=None) app.processEvents() app.processEvents() r_clear_ebi = plot.viewRect() assert r_clear_ebi.height() == r_empty_ebi.height() plot.close() pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_GraphicsItem.py000066400000000000000000000013221421045507400255660ustar00rootroot00000000000000import faulthandler import weakref import pyqtgraph as pg faulthandler.enable() pg.mkQApp() def test_getViewWidget(): view = pg.PlotWidget() vref = weakref.ref(view) item = pg.InfiniteLine() view.addItem(item) assert item.getViewWidget() is view del view assert vref() is None assert item.getViewWidget() is None def test_getViewWidget_deleted(): view = pg.PlotWidget() item = pg.InfiniteLine() view.addItem(item) assert item.getViewWidget() is view # Arrange to have Qt automatically delete the view widget obj = pg.QtWidgets.QWidget() view.setParent(obj) del obj assert not pg.Qt.isQObjectAlive(view) assert item.getViewWidget() is None pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_ImageItem.py000066400000000000000000000220441421045507400250540ustar00rootroot00000000000000import time import numpy as np import pytest import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui, QtTest from tests.image_testing import TransposedImageItem, assertImageApproved try: import cupy except ImportError: cupy = None app = pg.mkQApp() @pytest.mark.skipif(cupy is None, reason="CuPy unavailable to test") def test_useCupy_can_be_set_after_init(): prev_setting = pg.getConfigOption("useCupy") try: pg.setConfigOption("useCupy", False) w = pg.GraphicsLayoutWidget() w.show() view = pg.ViewBox() w.setCentralWidget(view) w.resize(200, 200) img = cupy.random.randint(0, 255, size=(32, 32)).astype(cupy.uint8) ii = pg.ImageItem() view.addItem(ii) pg.setConfigOption("useCupy", True) ii.setImage(img) w.hide() finally: pg.setConfigOption("useCupy", prev_setting) @pytest.mark.skipif(cupy is None, reason="CuPy unavailable to test") def test_ensuring_substrate(): prev_setting = pg.getConfigOption("useCupy") try: pg.setConfigOption("useCupy", True) ii = pg.ImageItem() data = cupy.random.randint(0, 255, size=(32, 32)).astype(cupy.uint8) assert data is ii._ensure_proper_substrate(data, cupy) assert isinstance(ii._ensure_proper_substrate(data, cupy), cupy.ndarray) assert data is not ii._ensure_proper_substrate(data, np) assert isinstance(ii._ensure_proper_substrate(data, np), np.ndarray) data = np.random.randint(0, 255, size=(32, 32)).astype(np.uint8) assert data is ii._ensure_proper_substrate(data, np) assert isinstance(ii._ensure_proper_substrate(data, np), np.ndarray) assert data is not ii._ensure_proper_substrate(data, cupy) assert isinstance(ii._ensure_proper_substrate(data, cupy), cupy.ndarray) data = range(0, 255) assert data is not ii._ensure_proper_substrate(data, np) assert isinstance(ii._ensure_proper_substrate(data, np), np.ndarray) assert data is not ii._ensure_proper_substrate(data, cupy) assert isinstance(ii._ensure_proper_substrate(data, cupy), cupy.ndarray) finally: pg.setConfigOption("useCupy", prev_setting) def test_ImageItem(transpose=False): w = pg.GraphicsLayoutWidget() w.show() view = pg.ViewBox() w.setCentralWidget(view) w.resize(200, 200) img = TransposedImageItem(border=0.5, transpose=transpose) view.addItem(img) # test mono float np.random.seed(0) data = np.random.normal(size=(20, 20)) dmax = data.max() data[:10, 1] = dmax + 10 data[1, :10] = dmax + 12 data[3, :10] = dmax + 13 img.setImage(data) QtTest.QTest.qWaitForWindowExposed(w) time.sleep(0.1) app.processEvents() assertImageApproved(w, 'imageitem/init', 'Init image item. View is auto-scaled, image axis 0 marked by 1 line, axis 1 is marked by 2 lines. Origin in bottom-left.') # ..with colormap cmap = pg.ColorMap([0, 0.25, 0.75, 1], [[0, 0, 0, 255], [255, 0, 0, 255], [255, 255, 0, 255], [255, 255, 255, 255]]) img.setLookupTable(cmap.getLookupTable()) assertImageApproved(w, 'imageitem/lut', 'Set image LUT.') # ..and different levels img.setLevels([dmax+9, dmax+13]) assertImageApproved(w, 'imageitem/levels1', 'Levels show only axis lines.') img.setLookupTable(None) # test mono int data = np.fromfunction(lambda x,y: x+y*10, (129, 128)).astype(np.int16) img.setImage(data) assertImageApproved(w, 'imageitem/gradient_mono_int', 'Mono int gradient.') img.setLevels([640, 641]) assertImageApproved(w, 'imageitem/gradient_mono_int_levels', 'Mono int gradient w/ levels to isolate diagonal.') # test mono byte data = np.fromfunction(lambda x,y: x+y, (129, 128)).astype(np.ubyte) img.setImage(data) assertImageApproved(w, 'imageitem/gradient_mono_byte', 'Mono byte gradient.') img.setLevels([127, 128]) assertImageApproved(w, 'imageitem/gradient_mono_byte_levels', 'Mono byte gradient w/ levels to isolate diagonal.') # test monochrome image data = np.zeros((10, 10), dtype='uint8') data[:5,:5] = 1 data[5:,5:] = 1 img.setImage(data) assertImageApproved(w, 'imageitem/monochrome', 'Ubyte image with only 0,1 values.') # test bool data = data.astype(bool) img.setImage(data) assertImageApproved(w, 'imageitem/bool', 'Boolean mask.') # test RGBA byte data = np.zeros((100, 100, 4), dtype='ubyte') data[..., 0] = np.linspace(0, 255, 100).reshape(100, 1) data[..., 1] = np.linspace(0, 255, 100).reshape(1, 100) data[..., 3] = 255 img.setImage(data) assertImageApproved(w, 'imageitem/gradient_rgba_byte', 'RGBA byte gradient.') img.setLevels([[128, 129], [128, 255], [0, 1], [0, 255]]) assertImageApproved(w, 'imageitem/gradient_rgba_byte_levels', 'RGBA byte gradient. Levels set to show x=128 and y>128.') # test RGBA float data = data.astype(float) img.setImage(data / 1e9) assertImageApproved(w, 'imageitem/gradient_rgba_float', 'RGBA float gradient.') # checkerboard to test alpha img2 = TransposedImageItem(transpose=transpose) img2.setImage(np.fromfunction(lambda x,y: (x+y)%2, (10, 10)), levels=[-1,2]) view.addItem(img2) img2.setScale(10) img2.setZValue(-10) data[..., 0] *= 1e-9 data[..., 1] *= 1e9 data[..., 3] = np.fromfunction(lambda x,y: np.sin(0.1 * (x+y)), (100, 100)) img.setImage(data, levels=[[0, 128e-9],[0, 128e9],[0, 1],[-1, 1]]) assertImageApproved(w, 'imageitem/gradient_rgba_float_alpha', 'RGBA float gradient with alpha.') # test composition mode img.setCompositionMode(QtGui.QPainter.CompositionMode.CompositionMode_Plus) assertImageApproved(w, 'imageitem/gradient_rgba_float_additive', 'RGBA float gradient with alpha and additive composition mode.') img2.hide() img.setCompositionMode(QtGui.QPainter.CompositionMode.CompositionMode_SourceOver) # test downsampling data = np.fromfunction(lambda x,y: np.cos(0.002 * x**2), (800, 100)) img.setImage(data, levels=[-1, 1]) assertImageApproved(w, 'imageitem/resolution_without_downsampling', 'Resolution test without downsampling.') img.setAutoDownsample(True) assertImageApproved(w, 'imageitem/resolution_with_downsampling_x', 'Resolution test with downsampling axross x axis.') assert img._lastDownsample == (4, 1) img.setImage(data.T, levels=[-1, 1]) assertImageApproved(w, 'imageitem/resolution_with_downsampling_y', 'Resolution test with downsampling across y axis.') assert img._lastDownsample == (1, 4) w.hide() def test_ImageItem_axisorder(): # All image tests pass again using the opposite axis order origMode = pg.getConfigOption('imageAxisOrder') altMode = 'row-major' if origMode == 'col-major' else 'col-major' pg.setConfigOptions(imageAxisOrder=altMode) try: test_ImageItem(transpose=True) finally: pg.setConfigOptions(imageAxisOrder=origMode) def test_setRect(): def assert_equal_transforms(tr1, tr2): dic = { # there seems to be no easy way to get the matrix in one call: 'tr11': ( tr1.m11(), tr2.m11() ), 'tr12': ( tr1.m12(), tr2.m12() ), 'tr13': ( tr1.m13(), tr2.m13() ), 'tr21': ( tr1.m21(), tr2.m21() ), 'tr22': ( tr1.m22(), tr2.m22() ), 'tr23': ( tr1.m23(), tr2.m23() ), 'tr31': ( tr1.m31(), tr2.m31() ), 'tr32': ( tr1.m32(), tr2.m32() ), 'tr33': ( tr1.m33(), tr2.m33() ) } log_string = 'Matrix element mismatch\n' good = True for key, values in dic.items(): val1, val2 = values if val1 != val2: good = False log_string += f'{key}: {val1} != {val2}\n' assert good, log_string tr = QtGui.QTransform() # construct a reference transform tr.scale(2, 4) # scale 2x2 image to 4x8 tr.translate(-1, -1) # after shifting by -1, -1 # the transformed 2x2 image would cover (-2,-4) to (2,4). # Now have setRect construct the same transform: imgitem = pg.ImageItem(np.eye(2), rect=(-2,-4, 4,8) ) # test tuple of floats assert_equal_transforms(tr, imgitem.transform()) imgitem = pg.ImageItem(np.eye(2), rect=QtCore.QRectF(-2,-4, 4,8) ) # test QRectF assert_equal_transforms(tr, imgitem.transform()) imgitem = pg.ImageItem(np.eye(2)) imgitem.setRect(-2,-4, 4,8) # test individual parameters assert_equal_transforms(tr, imgitem.transform()) imgitem = pg.ImageItem(np.eye(2)) imgitem.setRect(QtCore.QRect(-2,-4, 4,8)) # test QRect argument assert_equal_transforms(tr, imgitem.transform()) def test_dividebyzero(): im = pg.image(np.random.normal(size=(100,100))) im.imageItem.setAutoDownsample(True) im.view.setRange(xRange=[-5+25, 5e+25],yRange=[-5e+25, 5e+25]) app.processEvents() QtTest.QTest.qWait(1000) # must manually call im.imageItem.render here or the exception # will only exist on the Qt event loop im.imageItem.render() pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_ImageItemFormat.py000066400000000000000000000172341421045507400262320ustar00rootroot00000000000000import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtGui def check_format(shape, dtype, levels, lut, expected_format): data = np.zeros(shape, dtype=dtype) item = pg.ImageItem(axisOrder='row-major') item.setImage(data, autoLevels=False, lut=lut, levels=levels) item.render() assert item.qimage.format() == expected_format def test_uint8(): Format = QtGui.QImage.Format dtype = np.uint8 w, h = 192, 108 lo, hi = 50, 200 lut_none = None lut_mono1 = np.random.randint(256, size=256, dtype=np.uint8) lut_mono2 = np.random.randint(256, size=(256, 1), dtype=np.uint8) lut_rgb = np.random.randint(256, size=(256, 3), dtype=np.uint8) lut_rgba = np.random.randint(256, size=(256, 4), dtype=np.uint8) # lut with less than 256 entries lut_mono1_s = np.random.randint(256, size=255, dtype=np.uint8) lut_mono2_s = np.random.randint(256, size=(255, 1), dtype=np.uint8) lut_rgb_s = np.random.randint(256, size=(255, 3), dtype=np.uint8) lut_rgba_s = np.random.randint(256, size=(255, 4), dtype=np.uint8) # lut with more than 256 entries lut_mono1_l = np.random.randint(256, size=257, dtype=np.uint8) lut_mono2_l = np.random.randint(256, size=(257, 1), dtype=np.uint8) lut_rgb_l = np.random.randint(256, size=(257, 3), dtype=np.uint8) lut_rgba_l = np.random.randint(256, size=(257, 4), dtype=np.uint8) levels = None check_format((h, w), dtype, levels, lut_none, Format.Format_Grayscale8) check_format((h, w, 3), dtype, levels, lut_none, Format.Format_RGB888) check_format((h, w, 4), dtype, levels, lut_none, Format.Format_RGBA8888) levels = [lo, hi] check_format((h, w), dtype, levels, lut_none, Format.Format_Indexed8) levels = None check_format((h, w), dtype, levels, lut_mono1, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono2, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgb, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgba, Format.Format_Indexed8) levels = [lo, hi] check_format((h, w), dtype, levels, lut_mono1, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono2, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgb, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgba, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono1_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono2_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgb_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgba_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono1_l, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono2_l, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgb_l, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgba_l, Format.Format_Indexed8) levels = [lo, hi] check_format((h, w, 3), dtype, levels, lut_none, Format.Format_RGB888) def test_uint16(): Format = QtGui.QImage.Format dtype = np.uint16 w, h = 192, 108 lo, hi = 100, 10000 lut_none = None lut_mono1 = np.random.randint(256, size=256, dtype=np.uint8) lut_mono2 = np.random.randint(256, size=(256, 1), dtype=np.uint8) lut_rgb = np.random.randint(256, size=(256, 3), dtype=np.uint8) lut_rgba = np.random.randint(256, size=(256, 4), dtype=np.uint8) # lut with less than 256 entries lut_mono1_s = np.random.randint(256, size=255, dtype=np.uint8) lut_mono2_s = np.random.randint(256, size=(255, 1), dtype=np.uint8) lut_rgb_s = np.random.randint(256, size=(255, 3), dtype=np.uint8) lut_rgba_s = np.random.randint(256, size=(255, 4), dtype=np.uint8) # lut with more than 256 entries lut_mono1_l = np.random.randint(256, size=257, dtype=np.uint8) lut_mono2_l = np.random.randint(256, size=(257, 1), dtype=np.uint8) lut_rgb_l = np.random.randint(256, size=(257, 3), dtype=np.uint8) lut_rgba_l = np.random.randint(256, size=(257, 4), dtype=np.uint8) levels = None try: fmt_gray16 = Format.Format_Grayscale16 except AttributeError: fmt_gray16 = Format.Format_ARGB32 check_format((h, w), dtype, levels, lut_none, fmt_gray16) check_format((h, w, 3), dtype, levels, lut_none, Format.Format_RGB888) check_format((h, w, 4), dtype, levels, lut_none, Format.Format_RGBA64) levels = [lo, hi] check_format((h, w), dtype, levels, lut_none, Format.Format_Grayscale8) levels = None check_format((h, w), dtype, levels, lut_mono1, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono2, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgb, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgba, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono1_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono2_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgb_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgba_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono1_l, Format.Format_Grayscale8) check_format((h, w), dtype, levels, lut_mono2_l, Format.Format_Grayscale8) check_format((h, w), dtype, levels, lut_rgb_l, Format.Format_RGBX8888) check_format((h, w), dtype, levels, lut_rgba_l, Format.Format_RGBA8888) levels = [lo, hi] check_format((h, w, 3), dtype, levels, lut_none, Format.Format_RGB888) def test_float32(): Format = QtGui.QImage.Format dtype = np.float32 w, h = 192, 108 lo, hi = -1, 1 lut_none = None lut_mono1 = np.random.randint(256, size=256, dtype=np.uint8) lut_mono2 = np.random.randint(256, size=(256, 1), dtype=np.uint8) lut_rgb = np.random.randint(256, size=(256, 3), dtype=np.uint8) lut_rgba = np.random.randint(256, size=(256, 4), dtype=np.uint8) # lut with less than 256 entries lut_mono1_s = np.random.randint(256, size=255, dtype=np.uint8) lut_mono2_s = np.random.randint(256, size=(255, 1), dtype=np.uint8) lut_rgb_s = np.random.randint(256, size=(255, 3), dtype=np.uint8) lut_rgba_s = np.random.randint(256, size=(255, 4), dtype=np.uint8) # lut with more than 256 entries lut_mono1_l = np.random.randint(256, size=257, dtype=np.uint8) lut_mono2_l = np.random.randint(256, size=(257, 1), dtype=np.uint8) lut_rgb_l = np.random.randint(256, size=(257, 3), dtype=np.uint8) lut_rgba_l = np.random.randint(256, size=(257, 4), dtype=np.uint8) levels = [lo, hi] check_format((h, w), dtype, levels, lut_none, Format.Format_Grayscale8) check_format((h, w, 3), dtype, levels, lut_none, Format.Format_RGB888) check_format((h, w, 4), dtype, levels, lut_none, Format.Format_RGBA8888) check_format((h, w), dtype, levels, lut_mono1, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono2, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgb, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgba, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono1_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono2_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgb_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_rgba_s, Format.Format_Indexed8) check_format((h, w), dtype, levels, lut_mono1_l, Format.Format_Grayscale8) check_format((h, w), dtype, levels, lut_mono2_l, Format.Format_Grayscale8) check_format((h, w), dtype, levels, lut_rgb_l, Format.Format_RGBX8888) check_format((h, w), dtype, levels, lut_rgba_l, Format.Format_RGBA8888) pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_InfiniteLine.py000066400000000000000000000071711421045507400255740ustar00rootroot00000000000000import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui, QtTest from tests.ui_testing import mouseDrag, mouseMove pg.mkQApp() def test_InfiniteLine(): # disable delay of mouse move events because events is called immediately in test pg.setConfigOption('mouseRateLimit', -1) # Test basic InfiniteLine API plt = pg.plot() plt.setXRange(-10, 10) plt.setYRange(-10, 10) plt.resize(600, 600) # seemingly arbitrary requirements; might need longer wait time for some platforms.. QtTest.QTest.qWaitForWindowExposed(plt) QtTest.QTest.qWait(100) vline = plt.addLine(x=1) assert vline.angle == 90 br = vline.mapToView(QtGui.QPolygonF(vline.boundingRect())) assert br.containsPoint(pg.Point(1, 5), QtCore.Qt.FillRule.OddEvenFill) assert not br.containsPoint(pg.Point(5, 0), QtCore.Qt.FillRule.OddEvenFill) hline = plt.addLine(y=0) assert hline.angle == 0 assert hline.boundingRect().contains(pg.Point(5, 0)) assert not hline.boundingRect().contains(pg.Point(0, 5)) vline.setValue(2) assert vline.value() == 2 vline.setPos(pg.Point(4, -5)) assert vline.value() == 4 oline = pg.InfiniteLine(angle=30) plt.addItem(oline) oline.setPos(pg.Point(1, -1)) assert oline.angle == 30 assert oline.pos() == pg.Point(1, -1) assert oline.value() == [1, -1] # test bounding rect for oblique line br = oline.mapToScene(oline.boundingRect()) pos = oline.mapToScene(pg.Point(2, 0)) assert br.containsPoint(pos, QtCore.Qt.FillRule.OddEvenFill) px = pg.Point(-0.5, -1.0 / 3**0.5) assert br.containsPoint(pos + 5 * px, QtCore.Qt.FillRule.OddEvenFill) assert not br.containsPoint(pos + 7 * px, QtCore.Qt.FillRule.OddEvenFill) plt.close() def test_mouseInteraction(): # disable delay of mouse move events because events is called immediately in test pg.setConfigOption('mouseRateLimit', -1) plt = pg.plot() plt.scene().minDragTime = 0 # let us simulate mouse drags very quickly. vline = plt.addLine(x=0, movable=True) hline = plt.addLine(y=0, movable=True) hline2 = plt.addLine(y=-1, movable=False) plt.setXRange(-10, 10) plt.setYRange(-10, 10) # test horizontal drag pos = plt.plotItem.vb.mapViewToScene(pg.Point(0,5)) pos2 = pos - QtCore.QPointF(200, 200) mouseMove(plt, pos) assert vline.mouseHovering is True and hline.mouseHovering is False mouseDrag(plt, pos, pos2, QtCore.Qt.MouseButton.LeftButton) px = vline.pixelLength(pg.Point(1, 0), ortho=True) assert abs(vline.value() - plt.plotItem.vb.mapSceneToView(pos2).x()) <= px # test missed drag pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,0)) pos = pos + QtCore.QPointF(0, 6) pos2 = pos + QtCore.QPointF(-20, -20) mouseMove(plt, pos) assert vline.mouseHovering is False and hline.mouseHovering is False mouseDrag(plt, pos, pos2, QtCore.Qt.MouseButton.LeftButton) assert hline.value() == 0 # test vertical drag pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,0)) pos2 = pos - QtCore.QPointF(50, 50) mouseMove(plt, pos) assert vline.mouseHovering is False and hline.mouseHovering is True mouseDrag(plt, pos, pos2, QtCore.Qt.MouseButton.LeftButton) px = hline.pixelLength(pg.Point(1, 0), ortho=True) assert abs(hline.value() - plt.plotItem.vb.mapSceneToView(pos2).y()) <= px # test non-interactive line pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,-1)) pos2 = pos - QtCore.QPointF(50, 50) mouseMove(plt, pos) assert hline2.mouseHovering == False mouseDrag(plt, pos, pos2, QtCore.Qt.MouseButton.LeftButton) assert hline2.value() == -1 plt.close() pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_LegendItem.py000066400000000000000000000056351421045507400252370ustar00rootroot00000000000000import pyqtgraph as pg def test_legend_item_basics(): pg.mkQApp() legend = pg.LegendItem() assert legend.opts['pen'] == pg.mkPen(None) assert legend.opts['brush'] == pg.mkBrush(None) assert legend.opts['labelTextColor'] is None assert legend.opts['labelTextSize'] == '9pt' assert legend.opts['offset'] is None assert legend.columnCount == 1 assert legend.rowCount == 1 assert legend.labelTextColor() is None assert legend.labelTextSize() == '9pt' assert legend.brush() == pg.mkBrush(None) assert legend.pen() == pg.mkPen(None) assert legend.sampleType is pg.ItemSample # Set brush # ---------------------------------------------------- brush = pg.mkBrush('b') legend.setBrush(brush) assert legend.brush() == brush assert legend.opts['brush'] == brush # Set pen # ---------------------------------------------------- pen = pg.mkPen('b') legend.setPen(pen) assert legend.pen() == pen assert legend.opts['pen'] == pen # Set labelTextColor # ---------------------------------------------------- text_color = pg.mkColor('b') legend.setLabelTextColor(text_color) assert legend.labelTextColor() == text_color assert legend.opts['labelTextColor'] == text_color # Set labelTextSize # ---------------------------------------------------- text_size = '12pt' legend.setLabelTextSize(text_size) assert legend.labelTextSize() == text_size assert legend.opts['labelTextSize'] == text_size # Add items # ---------------------------------------------------- assert len(legend.items) == 0 plot = pg.PlotDataItem(name="Plot") legend.addItem(plot, name="Plot") assert len(legend.items) == 1 scatter = pg.PlotDataItem(name="Scatter") legend.addItem(scatter, name="Scatter") assert len(legend.items) == 2 assert legend.columnCount == 1 assert legend.rowCount == 2 curve = pg.PlotDataItem(name="Curve") legend.addItem(curve, name="Curve") assert len(legend.items) == 3 assert legend.rowCount == 3 scrabble = pg.PlotDataItem(name="Scrabble") legend.addItem(scrabble, name="Scrabble") assert len(legend.items) == 4 assert legend.layout.rowCount() == 4 assert legend.rowCount == 4 legend.setColumnCount(2) assert legend.columnCount == 2 assert legend.rowCount == 2 assert legend.layout.rowCount() == 2 # Remove items # ---------------------------------------------------- legend.removeItem(scrabble) assert legend.rowCount == 2 assert legend.layout.rowCount() == 2 assert scrabble not in legend.items assert len(legend.items) == 3 legend.removeItem(curve) assert legend.rowCount == 2 # rowCount will never decrease when removing assert legend.layout.rowCount() == 1 assert curve not in legend.items assert len(legend.items) == 2 legend.clear() assert legend.items == [] pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_LinearRegionItem.py000066400000000000000000000056531421045507400264170ustar00rootroot00000000000000import math import numpy as np import pytest import pyqtgraph as pg app = pg.mkQApp() def check_region(lr, bounds, exact=False, rtol=0.005): """Optionally tolerant LinearRegionItem region check""" reg = lr.getRegion() if exact: assert reg[0] == bounds[0] assert reg[1] == bounds[1] else: assert math.isclose(reg[0], bounds[0], rel_tol=rtol) assert math.isclose(reg[1], bounds[1], rel_tol=rtol) @pytest.mark.parametrize("orientation", ["vertical", "horizontal"]) def test_clip_to_plot_data_item(orientation): """Vertical and horizontal LRIs clipping both bounds to a PlotDataItem""" # initial bounds for the LRI init_vals = (-1.5, 1.5) # data for a PlotDataItem to clip to, both inside the inial bounds x = np.linspace(-1, 1, 10) y = np.linspace(1, 1.2, 10) p = pg.PlotWidget() pdi = p.plot(x=x, y=y) lr = pg.LinearRegionItem(init_vals, clipItem=pdi, orientation=orientation) p.addItem(lr) app.processEvents() if orientation == "vertical": check_region(lr, x[[0, -1]]) else: check_region(lr, y[[0, -1]]) def test_disable_clip_item(): """LRI clipItem (ImageItem) disabled by explicit call to setBounds""" init_vals = (5, 40) p = pg.PlotWidget() img = pg.ImageItem(image=np.eye(20, 20)) p.addItem(img) lr = pg.LinearRegionItem(init_vals, clipItem=img) p.addItem(lr) app.processEvents() # initial bound that was out of range snaps to the imageitem bound check_region(lr, (init_vals[0], img.height()), exact=True) # disable clipItem, move the right InfiniteLine beyond the new bound lr.setBounds(init_vals) lr.lines[1].setPos(init_vals[1] + 10) app.processEvents() check_region(lr, init_vals, exact=True) def test_clip_to_item_in_other_vb(): """LRI clip to item in a different ViewBox""" init_vals = (10, 50) img_shape = (20, 20) win = pg.GraphicsLayoutWidget() # "remote" PlotDataItem to bound to p1 = win.addPlot() img = pg.ImageItem(image=np.eye(*img_shape)) p1.addItem(img) # plot the LRI lives in p2 = win.addPlot() x2 = np.linspace(-200, 200, 100) p2.plot(x=x2, y=x2) lr = pg.LinearRegionItem(init_vals) p2.addItem(lr) app.processEvents() # no clip item set yet, should be initial bounds check_region(lr, init_vals, exact=True) lr.setClipItem(img) app.processEvents() # snap to image width check_region(lr, (init_vals[0], img_shape[1]), exact=True) def test_clip_item_override_init_bounds(): """clipItem overrides bounds provided in the constructor""" init_vals = (-10, 10) init_bounds = (-5, 5) img_shape = (5, 5) p = pg.PlotWidget() img = pg.ImageItem(image=np.eye(*img_shape)) p.addItem(img) lr = pg.LinearRegionItem(init_vals, clipItem=img, bounds=init_bounds) p.addItem(lr) app.processEvents() check_region(lr, (0, img_shape[1]), exact=True) pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_NonUniformImage.py000066400000000000000000000050301421045507400262440ustar00rootroot00000000000000import numpy as np import pytest import pyqtgraph as pg import pyqtgraph.functions as fn from pyqtgraph.colormap import ColorMap from pyqtgraph.graphicsItems.NonUniformImage import NonUniformImage from pyqtgraph.Qt import QtTest from tests.image_testing import assertImageApproved app = pg.mkQApp() def test_NonUniformImage_scale_dimensions(): x = [1.0, 3.0, 10.0] y = [1.0, 2.0, 4.0] X, Y = np.meshgrid(x, y, indexing='ij') Z = X * Y for args in [(Z, y, Z), (x, Z, Z)]: with pytest.raises(Exception) as ex: NonUniformImage(*args) assert "x and y must be 1-d arrays." in str(ex) def test_NonUniformImage_scale_monotonicity(): x = [1.0, 0.0, 10.0] y = [1.0, 2.0, 4.0] X, Y = np.meshgrid(x, y, indexing='ij') Z = X * Y for args in [(x, y, Z), (y, x, Z)]: with pytest.raises(Exception) as ex: NonUniformImage(*args) assert "The values in x and y must be monotonically increasing." in str(ex) def test_NonUniformImage_data_dimensions(): x = [1.0, 3.0, 10.0] y = [1.0, 2.0, 4.0] with pytest.raises(Exception) as ex: NonUniformImage(x, y, x) assert "The length of x and y must match the shape of z." in str(ex) def test_NonUniformImage_lut(): window = pg.GraphicsLayoutWidget() viewbox = pg.ViewBox() window.setCentralWidget(viewbox) window.resize(200, 200) window.show() x = [1.0, 3.0, 10.0] y = [1.0, 2.0, 4.0] X, Y = np.meshgrid(x, y, indexing='ij') Z = X * Y image = NonUniformImage(x, y, Z, border=fn.mkPen('g')) viewbox.addItem(image) lut = pg.HistogramLUTItem() window.addItem(lut) image.setLookupTable(lut, autoLevel=True) h = image.getHistogram() lut.plot.setData(*h) QtTest.QTest.qWaitForWindowExposed(window) QtTest.QTest.qWait(100) assertImageApproved(window, 'nonuniform_image/lut-3x3') def test_NonUniformImage_colormap(): window = pg.GraphicsLayoutWidget() viewbox = pg.ViewBox() window.setCentralWidget(viewbox) window.resize(200, 200) window.show() x = [1.0, 3.0, 10.0] y = [1.0, 2.0, 4.0] X, Y = np.meshgrid(x, y, indexing='ij') Z = X * Y Z[:, 0] = [np.NINF, np.NAN, np.PINF] image = NonUniformImage(x, y, Z, border=fn.mkPen('g')) cmap = ColorMap(pos=[0.0, 1.0], color=[(0, 0, 0), (255, 255, 255)]) image.setColorMap(cmap) viewbox.addItem(image) QtTest.QTest.qWaitForWindowExposed(window) QtTest.QTest.qWait(100) assertImageApproved(window, 'nonuniform_image/colormap-3x3') pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_PlotCurveItem.py000066400000000000000000000026151421045507400257570ustar00rootroot00000000000000import numpy as np import pyqtgraph as pg from tests.image_testing import assertImageApproved def test_PlotCurveItem(): p = pg.GraphicsLayoutWidget() p.resize(200, 150) p.ci.setContentsMargins(4, 4, 4, 4) # default margins vary by platform p.show() v = p.addViewBox() data = np.array([1,4,2,3,np.inf,5,7,6,-np.inf,8,10,9,np.nan,-1,-2,0]) c = pg.PlotCurveItem(data) v.addItem(c) v.autoRange() # Check auto-range works. Some platform differences may be expected.. checkRange = np.array([[-1.1457564053237301, 16.145756405323731], [-3.076811473165955, 11.076811473165955]]) assert np.allclose(v.viewRange(), checkRange) assertImageApproved(p, 'plotcurveitem/connectall', "Plot curve with all points connected.") c.setData(data, connect='pairs') assertImageApproved(p, 'plotcurveitem/connectpairs', "Plot curve with pairs connected.") c.setData(data, connect='finite') assertImageApproved(p, 'plotcurveitem/connectfinite', "Plot curve with finite points connected.") c.setData(data, connect='finite', skipFiniteCheck=True) assertImageApproved(p, 'plotcurveitem/connectfinite', "Plot curve with finite points connected using QPolygonF.") c.setData(data, connect=np.array([1,1,1,0,1,1,0,0,1,0,0,0,1,1,0,0])) assertImageApproved(p, 'plotcurveitem/connectarray', "Plot curve with connection array.") p.close() pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_PlotDataItem.py000066400000000000000000000136361421045507400255510ustar00rootroot00000000000000import warnings import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtGui pg.mkQApp() def test_bool(): truths = np.random.randint(0, 2, size=(100,)).astype(bool) pdi = pg.PlotDataItem(truths) bounds = pdi.dataBounds(1) assert isinstance(bounds[0], np.uint8) assert isinstance(bounds[1], np.uint8) xdata, ydata = pdi.getData() assert ydata.dtype == np.uint8 def test_fft(): f = 20. x = np.linspace(0, 1, 1000) y = np.sin(2 * np.pi * f * x) pd = pg.PlotDataItem(x, y) pd.setFftMode(True) x, y = pd.getData() assert abs(x[np.argmax(y)] - f) < 0.03 x = np.linspace(0, 1, 1001) y = np.sin(2 * np.pi * f * x) pd.setData(x, y) x, y = pd.getData() assert abs(x[np.argmax(y)]- f) < 0.03 pd.setLogMode(True, False) x, y = pd.getData() assert abs(x[np.argmax(y)] - np.log10(f)) < 0.01 def test_setData(): pdi = pg.PlotDataItem() #test empty data pdi.setData([]) #test y data y = list(np.random.normal(size=100)) pdi.setData(y) assert len(pdi.xData) == 100 assert len(pdi.yData) == 100 #test x, y data y += list(np.random.normal(size=50)) x = np.linspace(5, 10, 150) pdi.setData(x, y) assert len(pdi.xData) == 150 assert len(pdi.yData) == 150 #test clear by empty call pdi.setData() assert pdi.xData is None assert pdi.yData is None #test dict of x, y list y += list(np.random.normal(size=50)) x = list(np.linspace(5, 10, 200)) pdi.setData({'x': x, 'y': y}) assert len(pdi.xData) == 200 assert len(pdi.yData) == 200 #test clear by zero length arrays call pdi.setData([],[]) assert pdi.xData is None assert pdi.yData is None def test_nonfinite(): def _assert_equal_arrays(a1, a2): assert a1.shape == a2.shape for ( xtest, xgood ) in zip( a1, a2 ): assert( (xtest == xgood) or (np.isnan(xtest) and np.isnan(xgood) ) ) x = np.array([-np.inf, 0.0, 1.0, 2.0 , np.nan, 4.0 , np.inf]) y = np.array([ 1.0, 0.0,-1.0, np.inf, 2.0 , np.nan, 0.0 ]) pdi = pg.PlotDataItem(x, y) dataset = pdi.getDisplayDataset() _assert_equal_arrays( dataset.x, x ) _assert_equal_arrays( dataset.y, y ) with warnings.catch_warnings(): warnings.simplefilter("ignore") x_log = np.log10(x) y_log = np.log10(y) x_log[ ~np.isfinite(x_log) ] = np.nan y_log[ ~np.isfinite(y_log) ] = np.nan pdi.setLogMode(True, True) dataset = pdi.getDisplayDataset() _assert_equal_arrays( dataset.x, x_log ) _assert_equal_arrays( dataset.y, y_log ) def test_opts(): # test that curve and scatter plot properties get updated from PlotDataItem methods y = list(np.random.normal(size=100)) x = np.linspace(5, 10, 100) pdi = pg.PlotDataItem(x, y) pen = QtGui.QPen( QtGui.QColor('#FF0000') ) pen2 = QtGui.QPen( QtGui.QColor('#FFFF00') ) brush = QtGui.QBrush( QtGui.QColor('#00FF00')) brush2 = QtGui.QBrush( QtGui.QColor('#00FFFF')) float_value = 1.0 + 20*np.random.random() pen2.setWidth( int(float_value) ) pdi.setPen(pen) assert pdi.curve.opts['pen'] == pen pdi.setShadowPen(pen2) assert pdi.curve.opts['shadowPen'] == pen2 pdi.setFillLevel( float_value ) assert pdi.curve.opts['fillLevel'] == float_value pdi.setFillBrush(brush2) assert pdi.curve.opts['brush'] == brush2 pdi.setSymbol('t') assert pdi.scatter.opts['symbol'] == 't' pdi.setSymbolPen(pen) assert pdi.scatter.opts['pen'] == pen pdi.setSymbolBrush(brush) assert pdi.scatter.opts['brush'] == brush pdi.setSymbolSize( float_value ) assert pdi.scatter.opts['size'] == float_value def test_clear(): y = list(np.random.normal(size=100)) x = np.linspace(5, 10, 100) pdi = pg.PlotDataItem(x, y) pdi.clear() assert pdi.xData is None assert pdi.yData is None def test_clear_in_step_mode(): w = pg.PlotWidget() c = pg.PlotDataItem([1,4,2,3], [5,7,6], stepMode="center") w.addItem(c) c.clear() def test_clipping(): y = np.random.normal(size=150) x = np.exp2(np.linspace(5, 10, 150)) # non-uniform spacing w = pg.PlotWidget(autoRange=True, downsample=5) c = pg.PlotDataItem(x, y) w.addItem(c) c.setClipToView(True) for x_min in range(-200, 2**10 - 100, 100): x_max = x_min + 100 w.setXRange(x_min, x_max, padding=0) xDisp, _ = c.getData() # vr = c.viewRect() if len(xDisp) > 3: # check that all points except the first and last are on screen assert( xDisp[ 1] >= x_min and xDisp[ 1] <= x_max ) assert( xDisp[-2] >= x_min and xDisp[-2] <= x_max ) c.setDownsampling(ds=1) # disable downsampling for x_min in range(-200, 2**10 - 100, 100): x_max = x_min + 100 w.setXRange(x_min, x_max, padding=0) xDisp, _ = c.getData() # vr = c.viewRect() # this tends to be out of data, so we check against the range that we set if len(xDisp) > 3: # check that all points except the first and last are on screen assert( xDisp[ 0] == x[ 0] or xDisp[ 0] < x_min ) # first point should be unchanged, or off-screen assert( xDisp[ 1] >= x_min and xDisp[ 1] <= x_max ) assert( xDisp[-2] >= x_min and xDisp[-2] <= x_max ) assert( xDisp[-1] == x[-1] or xDisp[-1] > x_max ) # last point should be unchanged, or off-screen c.setData(x=np.zeros_like(y), y=y) # test zero width data set: # test center and expected number of remaining data points for center, num in ((-100.,1), (100.,1), (0.,len(y)) ): # when all elements are off-screen, only one will be kept # when all elelemts are on-screen, all should be kept # and the code should not crash for zero separation w.setXRange( center-50, center+50, padding=0 ) xDisp, yDisp = c.getData() assert len(xDisp) == num assert len(yDisp) == num w.close() pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_ROI.py000066400000000000000000000230701421045507400236440ustar00rootroot00000000000000import math import platform import numpy as np import pytest import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui, QtTest from tests.image_testing import assertImageApproved from tests.ui_testing import mouseClick, mouseDrag, mouseMove, resizeWindow app = pg.mkQApp() pg.setConfigOption("mouseRateLimit", 0) def test_getArrayRegion(transpose=False): pr = pg.PolyLineROI([[0, 0], [27, 0], [0, 28]], closed=True) pr.setPos(1, 1) rois = [ (pg.ROI([1, 1], [27, 28], pen='y'), 'baseroi'), (pg.RectROI([1, 1], [27, 28], pen='y'), 'rectroi'), (pg.EllipseROI([1, 1], [27, 28], pen='y'), 'ellipseroi'), (pr, 'polylineroi'), ] for roi, name in rois: # For some ROIs, resize should not be used. testResize = not isinstance(roi, pg.PolyLineROI) origMode = pg.getConfigOption('imageAxisOrder') try: if transpose: pg.setConfigOptions(imageAxisOrder='row-major') check_getArrayRegion(roi, 'roi/'+name, testResize, transpose=True) else: pg.setConfigOptions(imageAxisOrder='col-major') check_getArrayRegion(roi, 'roi/'+name, testResize) finally: pg.setConfigOptions(imageAxisOrder=origMode) def test_getArrayRegion_axisorder(): test_getArrayRegion(transpose=True) def check_getArrayRegion(roi, name, testResize=True, transpose=False): # on windows, edges corner pixels seem to be slightly different from other platforms # giving a pxCount=2 for a fudge factor if isinstance(roi, (pg.ROI, pg.RectROI)) and platform.system() == "Windows": pxCount = 2 else: pxCount=-1 initState = roi.getState() win = pg.GraphicsView() win.show() resizeWindow(win, 200, 400) # Don't use Qt's layouts for testing--these generate unpredictable results. # Instead, place the viewboxes manually vb1 = pg.ViewBox() win.scene().addItem(vb1) vb1.setPos(6, 6) vb1.resize(188, 191) vb2 = pg.ViewBox() win.scene().addItem(vb2) vb2.setPos(6, 203) vb2.resize(188, 191) img1 = pg.ImageItem(border='w') img2 = pg.ImageItem(border='w') vb1.addItem(img1) vb2.addItem(img2) np.random.seed(0) data = np.random.normal(size=(7, 30, 31, 5)) data[0, :, :, :] += 10 data[:, 1, :, :] += 10 data[:, :, 2, :] += 10 data[:, :, :, 3] += 10 if transpose: data = data.transpose(0, 2, 1, 3) img1.setImage(data[0, ..., 0]) vb1.setAspectLocked() vb1.enableAutoRange(True, True) roi.setZValue(10) vb1.addItem(roi) if isinstance(roi, pg.RectROI): if transpose: assert roi.getAffineSliceParams(data, img1, axes=(1, 2)) == ([28.0, 27.0], ((1.0, 0.0), (0.0, 1.0)), (1.0, 1.0)) else: assert roi.getAffineSliceParams(data, img1, axes=(1, 2)) == ([27.0, 28.0], ((1.0, 0.0), (0.0, 1.0)), (1.0, 1.0)) rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) #assert np.all((rgn == data[:, 1:-2, 1:-2, :]) | (rgn == 0)) img2.setImage(rgn[0, ..., 0]) vb2.setAspectLocked() vb2.enableAutoRange(True, True) app.processEvents() assertImageApproved(win, name+'/roi_getarrayregion', 'Simple ROI region selection.', pxCount=pxCount) with pytest.raises(TypeError): roi.setPos(0, False) roi.setPos([0.5, 1.5]) rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) img2.setImage(rgn[0, ..., 0]) app.processEvents() assertImageApproved(win, name+'/roi_getarrayregion_halfpx', 'Simple ROI region selection, 0.5 pixel shift.', pxCount=pxCount) roi.setAngle(45) roi.setPos([3, 0]) rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) img2.setImage(rgn[0, ..., 0]) app.processEvents() assertImageApproved(win, name+'/roi_getarrayregion_rotate', 'Simple ROI region selection, rotation.', pxCount=pxCount) if testResize: roi.setSize([60, 60]) rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) img2.setImage(rgn[0, ..., 0]) app.processEvents() assertImageApproved(win, name+'/roi_getarrayregion_resize', 'Simple ROI region selection, resized.', pxCount=pxCount) img1.setPos(0, img1.height()) img1.setTransform(QtGui.QTransform().scale(1, -1).rotate(20), True) rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) img2.setImage(rgn[0, ..., 0]) app.processEvents() assertImageApproved(win, name+'/roi_getarrayregion_img_trans', 'Simple ROI region selection, image transformed.', pxCount=pxCount) vb1.invertY() rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) img2.setImage(rgn[0, ..., 0]) app.processEvents() assertImageApproved(win, name+'/roi_getarrayregion_inverty', 'Simple ROI region selection, view inverted.', pxCount=pxCount) roi.setState(initState) img1.setPos(0, 0) img1.setTransform(QtGui.QTransform.fromScale(1, 0.5)) rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) img2.setImage(rgn[0, ..., 0]) app.processEvents() assertImageApproved(win, name+'/roi_getarrayregion_anisotropic', 'Simple ROI region selection, image scaled anisotropically.', pxCount=pxCount) # allow the roi to be re-used roi.scene().removeItem(roi) win.hide() def test_mouseClickEvent(): plt = pg.GraphicsView() plt.show() resizeWindow(plt, 200, 200) vb = pg.ViewBox() plt.scene().addItem(vb) vb.resize(200, 200) QtTest.QTest.qWaitForWindowExposed(plt) QtTest.QTest.qWait(100) roi = pg.RectROI((0, 0), (10, 20), removable=True) vb.addItem(roi) app.processEvents() mouseClick(plt, roi.mapToScene(pg.Point(2, 2)), QtCore.Qt.MouseButton.LeftButton) def test_PolyLineROI(): rois = [ (pg.PolyLineROI([[0, 0], [10, 0], [0, 15]], closed=True, pen=0.3), 'closed'), (pg.PolyLineROI([[0, 0], [10, 0], [0, 15]], closed=False, pen=0.3), 'open') ] #plt = pg.plot() plt = pg.GraphicsView() plt.show() resizeWindow(plt, 200, 200) vb = pg.ViewBox() plt.scene().addItem(vb) vb.resize(200, 200) #plt.plotItem = pg.PlotItem() #plt.scene().addItem(plt.plotItem) #plt.plotItem.resize(200, 200) plt.scene().minDragTime = 0 # let us simulate mouse drags very quickly. # seemingly arbitrary requirements; might need longer wait time for some platforms.. QtTest.QTest.qWaitForWindowExposed(plt) QtTest.QTest.qWait(100) for r, name in rois: vb.clear() vb.addItem(r) vb.autoRange() app.processEvents() assertImageApproved(plt, 'roi/polylineroi/'+name+'_init', 'Init %s polyline.' % name) initState = r.getState() assert len(r.getState()['points']) == 3 # hover over center center = r.mapToScene(pg.Point(3, 3)) mouseMove(plt, center) assertImageApproved(plt, 'roi/polylineroi/'+name+'_hover_roi', 'Hover mouse over center of ROI.') # drag ROI mouseDrag(plt, center, center + pg.Point(10, -10), QtCore.Qt.MouseButton.LeftButton) assertImageApproved(plt, 'roi/polylineroi/'+name+'_drag_roi', 'Drag mouse over center of ROI.') # hover over handle pt = r.mapToScene(pg.Point(r.getState()['points'][2])) mouseMove(plt, pt) assertImageApproved(plt, 'roi/polylineroi/'+name+'_hover_handle', 'Hover mouse over handle.') # drag handle mouseDrag(plt, pt, pt + pg.Point(5, 20), QtCore.Qt.MouseButton.LeftButton) assertImageApproved(plt, 'roi/polylineroi/'+name+'_drag_handle', 'Drag mouse over handle.') # hover over segment pt = r.mapToScene((pg.Point(r.getState()['points'][2]) + pg.Point(r.getState()['points'][1])) * 0.5) mouseMove(plt, pt+pg.Point(0, 2)) assertImageApproved(plt, 'roi/polylineroi/'+name+'_hover_segment', 'Hover mouse over diagonal segment.') # click segment mouseClick(plt, pt, QtCore.Qt.MouseButton.LeftButton) assertImageApproved(plt, 'roi/polylineroi/'+name+'_click_segment', 'Click mouse over segment.') # drag new handle mouseMove(plt, pt+pg.Point(10, -10)) # pg bug: have to move the mouse off/on again to register hover mouseDrag(plt, pt, pt + pg.Point(10, -10), QtCore.Qt.MouseButton.LeftButton) assertImageApproved(plt, 'roi/polylineroi/'+name+'_drag_new_handle', 'Drag mouse over created handle.') # clear all points r.clearPoints() assertImageApproved(plt, 'roi/polylineroi/'+name+'_clear', 'All points cleared.') assert len(r.getState()['points']) == 0 # call setPoints r.setPoints(initState['points']) assertImageApproved(plt, 'roi/polylineroi/'+name+'_setpoints', 'Reset points to initial state.') assert len(r.getState()['points']) == 3 # call setState r.setState(initState) assertImageApproved(plt, 'roi/polylineroi/'+name+'_setstate', 'Reset ROI to initial state.') assert len(r.getState()['points']) == 3 plt.hide() @pytest.mark.parametrize("p1,p2", [ ((1, 1), (2, 5)), ((0.1, 0.1), (-1, 5)), ((3, -1), (5, -6)), ((-2, 1), (-4, -8)), ]) def test_LineROI_coords(p1, p2): pw = pg.plot() lineroi = pg.LineROI(p1, p2, width=0.5, pen="r") pw.addItem(lineroi) # first two handles are the scale-rotate handles positioned by pos1, pos2 for expected, (name, scenepos) in zip([p1, p2], lineroi.getSceneHandlePositions()): got = lineroi.mapSceneToParent(scenepos) assert math.isclose(got.x(), expected[0]) assert math.isclose(got.y(), expected[1]) pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_ScatterPlotItem.py000066400000000000000000000061011421045507400262720ustar00rootroot00000000000000import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui def test_scatterplotitem(): app = pg.mkQApp() plot = pg.PlotWidget() # set view range equal to its bounding rect. # This causes plots to look the same regardless of pxMode. plot.setRange(rect=plot.boundingRect()) # test SymbolAtlas accepts custom symbol s = pg.ScatterPlotItem(name="Scatter") symbol = QtGui.QPainterPath() symbol.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) s.addPoints([{'pos': [0, 0], 'data': 1, 'symbol': symbol}]) assert s.name() == "Scatter" for i, pxMode in enumerate([True, False]): for j, useCache in enumerate([True, False]): s = pg.ScatterPlotItem() s.opts['useCache'] = useCache plot.addItem(s) s.setData(x=np.array([10, 40, 20, 30]) + i * 100, y=np.array([40, 60, 10, 30]) + j * 100, pxMode=pxMode, name="MoreScatter") s.addPoints(x=np.array([60, 70]) + i * 100, y=np.array([60, 70]) + j * 100, size=[20, 30]) assert s.name() == "MoreScatter" # Test uniform spot updates s.setSize(10) s.setBrush('r') s.setPen('g') s.setSymbol('+') app.processEvents() # Test list spot updates s.setSize([10] * 6) s.setBrush([pg.mkBrush('r')] * 6) s.setPen([pg.mkPen('g')] * 6) s.setSymbol(['+'] * 6) s.setPointData([s] * 6) app.processEvents() # Test array spot updates s.setSize(np.array([10] * 6)) s.setBrush(np.array([pg.mkBrush('r')] * 6)) s.setPen(np.array([pg.mkPen('g')] * 6)) s.setSymbol(np.array(['+'] * 6)) s.setPointData(np.array([s] * 6)) app.processEvents() # Test per-spot updates spot = s.points()[0] spot.setSize(20) spot.setBrush('b') spot.setPen('g') spot.setSymbol('o') spot.setData(None) app.processEvents() plot.close() def test_init_spots(): app = pg.mkQApp() plot = pg.PlotWidget() # set view range equal to its bounding rect. # This causes plots to look the same regardless of pxMode. plot.setRange(rect=plot.boundingRect()) spots = [ {'x': 0, 'y': 1}, {'pos': (1, 2), 'pen': None, 'brush': None, 'data': 'zzz'}, ] s = pg.ScatterPlotItem(spots=spots) # Check we can display without errors plot.addItem(s) app.processEvents() plot.clear() # check data is correct spots = s.points() defPen = pg.mkPen(pg.getConfigOption('foreground')) assert spots[0].pos().x() == 0 assert spots[0].pos().y() == 1 assert spots[0].pen() == defPen assert spots[0].data() is None assert spots[1].pos().x() == 1 assert spots[1].pos().y() == 2 assert spots[1].pen() == pg.mkPen(None) assert spots[1].brush() == pg.mkBrush(None) assert spots[1].data() == 'zzz' plot.close() pyqtgraph-pyqtgraph-0.12.4/tests/graphicsItems/test_TextItem.py000066400000000000000000000006021421045507400247520ustar00rootroot00000000000000import pyqtgraph as pg app = pg.mkQApp() def test_TextItem_setAngle(): plt = pg.plot() plt.setXRange(-10, 10) plt.setYRange(-20, 20) item = pg.TextItem(text="test") plt.addItem(item) t1 = item.transform() item.setAngle(30) app.processEvents() t2 = item.transform() assert t1 != t2 assert not t1.isRotating() assert t2.isRotating() pyqtgraph-pyqtgraph-0.12.4/tests/image_testing.py000066400000000000000000000373571421045507400222060ustar00rootroot00000000000000# Image-based testing borrowed from vispy """ Procedure for unit-testing with images: Run individual test scripts with the PYQTGRAPH_AUDIT environment variable set: $ PYQTGRAPH_AUDIT=1 python pyqtgraph/graphicsItems/tests/test_PlotCurveItem.py Any failing tests will display the test results, standard image, and the differences between the two. If the test result is bad, then press (f)ail. If the test result is good, then press (p)ass and the new image will be saved to the test-data directory. To check all test results regardless of whether the test failed, set the environment variable PYQTGRAPH_AUDIT_ALL=1. """ import inspect import os import sys import time import warnings from pathlib import Path import numpy as np from pyqtgraph import GraphicsLayoutWidget, ImageItem, TextItem from pyqtgraph import functions as fn from pyqtgraph.Qt import QtCore, QtGui, QtWidgets tester = None # Convenient stamp used for ensuring image orientation is correct axisImg = [ " 1 1 1 ", " 1 1 1 1 1 1 ", " 1 1 1 1 1 1 1 1 1 1", " 1 1 1 1 1 ", " 1 1 1 1 1 1 ", " 1 1 ", " 1 1 ", " 1 ", " ", " 1 ", " 1 ", " 1 ", "1 1 1 1 1 ", "1 1 1 1 1 ", " 1 1 1 ", " 1 1 1 ", " 1 ", " 1 ", ] axisImg = np.array([map(int, row[::2].replace(' ', '0')) for row in axisImg]) def getTester(): global tester if tester is None: tester = ImageTester() return tester def getImageFromWidget(widget): # just to be sure the widget size is correct (new window may be resized): QtWidgets.QApplication.processEvents() qimg = QtGui.QImage(widget.size(), QtGui.QImage.Format.Format_ARGB32) qimg.fill(QtCore.Qt.GlobalColor.transparent) painter = QtGui.QPainter(qimg) widget.render(painter) painter.end() qimg = qimg.convertToFormat(QtGui.QImage.Format.Format_RGBA8888) return fn.ndarray_from_qimage(qimg).copy() def assertImageApproved(image, standardFile, message=None, **kwargs): """Check that an image test result matches a pre-approved standard. If the result does not match, then the user can optionally invoke a GUI to compare the images and decide whether to fail the test or save the new image as the standard. Run the test with the environment variable PYQTGRAPH_AUDIT=1 to bring up the auditing GUI. Parameters ---------- image : (h, w, 4) ndarray standardFile : str The name of the approved test image to check against. This file name is relative to the root of the pyqtgraph test-data repository and will be automatically fetched. message : str A string description of the image. It is recommended to describe specific features that an auditor should look for when deciding whether to fail a test. Extra keyword arguments are used to set the thresholds for automatic image comparison (see ``assertImageMatch()``). """ if isinstance(image, QtWidgets.QWidget): # just to be sure the widget size is correct (new window may be resized): QtWidgets.QApplication.processEvents() graphstate = scenegraphState(image, standardFile) image = getImageFromWidget(image) if message is None: code = inspect.currentframe().f_back.f_code message = "%s::%s" % (code.co_filename, code.co_name) # Make sure we have a test data repo available dataPath = getTestDataDirectory() # Read the standard image if it exists stdFileName = os.path.join(dataPath, standardFile + '.png') if not os.path.isfile(stdFileName): stdImage = None else: qimg = QtGui.QImage(stdFileName) qimg = qimg.convertToFormat(QtGui.QImage.Format.Format_RGBA8888) stdImage = fn.ndarray_from_qimage(qimg).copy() del qimg # If the test image does not match, then we go to audit if requested. try: if stdImage is None: raise Exception("No reference image saved for this test.") if image.shape[2] != stdImage.shape[2]: raise Exception("Test result has different channel count than standard image" "(%d vs %d)" % (image.shape[2], stdImage.shape[2])) if image.shape != stdImage.shape: # Allow im1 to be an integer multiple larger than im2 to account # for high-resolution displays ims1 = np.array(image.shape).astype(float) ims2 = np.array(stdImage.shape).astype(float) sr = ims1 / ims2 if ims1[0] > ims2[0] else ims2 / ims1 if (sr[0] != sr[1] or not np.allclose(sr, np.round(sr)) or sr[0] < 1): raise TypeError("Test result shape %s is not an integer factor" " different than standard image shape %s." % (ims1, ims2)) sr = np.round(sr).astype(int) image = fn.downsample(image, sr[0], axis=(0, 1)).astype(image.dtype) assertImageMatch(image, stdImage, **kwargs) if bool(os.getenv('PYQTGRAPH_PRINT_TEST_STATE', False)): print(graphstate) if os.getenv('PYQTGRAPH_AUDIT_ALL') == '1': raise Exception("Image test passed, but auditing due to PYQTGRAPH_AUDIT_ALL evnironment variable.") except Exception: if os.getenv('PYQTGRAPH_AUDIT') == '1' or os.getenv('PYQTGRAPH_AUDIT_ALL') == '1': sys.excepthook(*sys.exc_info()) getTester().test(image, stdImage, message) stdPath = os.path.dirname(stdFileName) print('Saving new standard image to "%s"' % stdFileName) if not os.path.isdir(stdPath): os.makedirs(stdPath) qimg = fn.ndarray_to_qimage(image, QtGui.QImage.Format.Format_RGBA8888) qimg.save(stdFileName) del qimg else: if stdImage is None: raise Exception("Test standard %s does not exist. Set " "PYQTGRAPH_AUDIT=1 to add this image." % stdFileName) if os.getenv('CI') is not None: standardFile = os.path.join(os.getenv("SCREENSHOT_DIR", "screenshots"), standardFile) saveFailedTest(image, stdImage, standardFile) print(graphstate) raise def assertImageMatch(im1, im2, minCorr=None, pxThreshold=50., pxCount=-1, maxPxDiff=None, avgPxDiff=None, imgDiff=None): """Check that two images match. Images that differ in shape or dtype will fail unconditionally. Further tests for similarity depend on the arguments supplied. By default, images may have no pixels that gave a value difference greater than 50. Parameters ---------- im1 : (h, w, 4) ndarray Test output image im2 : (h, w, 4) ndarray Test standard image minCorr : float or None Minimum allowed correlation coefficient between corresponding image values (see numpy.corrcoef) pxThreshold : float Minimum value difference at which two pixels are considered different pxCount : int or None Maximum number of pixels that may differ. Default is 0, on Windows some tests have a value of 2. maxPxDiff : float or None Maximum allowed difference between pixels avgPxDiff : float or None Average allowed difference between pixels imgDiff : float or None Maximum allowed summed difference between images """ assert im1.ndim == 3 assert im1.shape[2] == 4 assert im1.dtype == im2.dtype if pxCount == -1: pxCount = 0 diff = im1.astype(float) - im2.astype(float) if imgDiff is not None: assert np.abs(diff).sum() <= imgDiff pxdiff = diff.max(axis=2) # largest value difference per pixel mask = np.abs(pxdiff) >= pxThreshold if pxCount is not None: assert mask.sum() <= pxCount maskedDiff = diff[mask] if maxPxDiff is not None and maskedDiff.size > 0: assert maskedDiff.max() <= maxPxDiff if avgPxDiff is not None and maskedDiff.size > 0: assert maskedDiff.mean() <= avgPxDiff if minCorr is not None: with np.errstate(invalid='ignore'): corr = np.corrcoef(im1.ravel(), im2.ravel())[0, 1] assert corr >= minCorr def saveFailedTest(data, expect, filename): # concatenate data, expect, and diff into a single image ds = data.shape es = expect.shape shape = (max(ds[0], es[0]) + 4, ds[1] + es[1] + 8 + max(ds[1], es[1]), 4) img = np.empty(shape, dtype=np.ubyte) img[..., :3] = 100 img[..., 3] = 255 img[2:2+ds[0], 2:2+ds[1], :ds[2]] = data img[2:2+es[0], ds[1]+4:ds[1]+4+es[1], :es[2]] = expect diff = makeDiffImage(data, expect) img[2:2+diff.shape[0], -diff.shape[1]-2:-2] = diff png = makePng(data) # change `img` to `data` to save just the failed image directory = os.path.dirname(filename) if not os.path.isdir(directory): os.makedirs(directory) with open(filename + ".png", "wb") as png_file: png_file.write(png) print("\nImage comparison failed. Test result: %s %s Expected result: " "%s %s" % (data.shape, data.dtype, expect.shape, expect.dtype)) def makePng(img): """Given an array like (H, W, 4), return a PNG-encoded byte string. """ io = QtCore.QBuffer() qim = fn.ndarray_to_qimage(img, QtGui.QImage.Format.Format_RGBX8888) qim.save(io, 'PNG') return bytes(io.data().data()) def makeDiffImage(im1, im2): """Return image array showing the differences between im1 and im2. Handles images of different shape. Alpha channels are not compared. """ ds = im1.shape es = im2.shape diff = np.empty((max(ds[0], es[0]), max(ds[1], es[1]), 4), dtype=int) diff[..., :3] = 128 diff[..., 3] = 255 diff[:ds[0], :ds[1], :min(ds[2], 3)] += im1[..., :3] diff[:es[0], :es[1], :min(es[2], 3)] -= im2[..., :3] diff = np.clip(diff, 0, 255).astype(np.ubyte) return diff class ImageTester(QtWidgets.QWidget): """Graphical interface for auditing image comparison tests. """ def __init__(self): self.lastKey = None QtWidgets.QWidget.__init__(self) self.resize(1200, 800) #self.showFullScreen() self.layout = QtWidgets.QGridLayout() self.setLayout(self.layout) self.view = GraphicsLayoutWidget() self.layout.addWidget(self.view, 0, 0, 1, 2) self.label = QtWidgets.QLabel() self.layout.addWidget(self.label, 1, 0, 1, 2) self.label.setWordWrap(True) font = QtGui.QFont("monospace", 14, QtGui.QFont.Weight.Bold) self.label.setFont(font) self.passBtn = QtWidgets.QPushButton('Pass') self.failBtn = QtWidgets.QPushButton('Fail') self.layout.addWidget(self.passBtn, 2, 0) self.layout.addWidget(self.failBtn, 2, 1) self.passBtn.clicked.connect(self.passTest) self.failBtn.clicked.connect(self.failTest) self.views = (self.view.addViewBox(row=0, col=0), self.view.addViewBox(row=0, col=1), self.view.addViewBox(row=0, col=2)) labelText = ['test output', 'standard', 'diff'] for i, v in enumerate(self.views): v.setAspectLocked(1) v.invertY() v.image = ImageItem(axisOrder='row-major') v.image.setAutoDownsample(True) v.addItem(v.image) v.label = TextItem(labelText[i]) v.setBackgroundColor(0.5) self.views[1].setXLink(self.views[0]) self.views[1].setYLink(self.views[0]) self.views[2].setXLink(self.views[0]) self.views[2].setYLink(self.views[0]) def test(self, im1, im2, message): """Ask the user to decide whether an image test passes or fails. This method displays the test image, reference image, and the difference between the two. It then blocks until the user selects the test output by clicking a pass/fail button or typing p/f. If the user fails the test, then an exception is raised. """ self.show() if im2 is None: message += '\nImage1: %s %s Image2: [no standard]' % (im1.shape, im1.dtype) im2 = np.zeros((1, 1, 3), dtype=np.ubyte) else: message += '\nImage1: %s %s Image2: %s %s' % (im1.shape, im1.dtype, im2.shape, im2.dtype) self.label.setText(message) self.views[0].image.setImage(im1) self.views[1].image.setImage(im2) diff = makeDiffImage(im1, im2) self.views[2].image.setImage(diff) self.views[0].autoRange() while True: QtWidgets.QApplication.processEvents() lastKey = self.lastKey self.lastKey = None if lastKey in ('f', 'esc') or not self.isVisible(): raise Exception("User rejected test result.") elif lastKey == 'p': break time.sleep(0.03) for v in self.views: v.image.setImage(np.zeros((1, 1, 3), dtype=np.ubyte)) def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key.Key_Escape: self.lastKey = 'esc' else: self.lastKey = str(event.text()).lower() def passTest(self): self.lastKey = 'p' def failTest(self): self.lastKey = 'f' def getTestDataRepo(): warnings.warn( "Test data data repo has been merged with the main repo" "use getTestDataDirectory() instead, this method will be removed" "in a future version of pyqtgraph", DeprecationWarning, stacklevel=2 ) return getTestDataDirectory() def getTestDataDirectory(): dataPath = Path(__file__).absolute().parent / "images" return dataPath.as_posix() def scenegraphState(view, name): """Return information about the scenegraph for debugging test failures. """ state = "====== Scenegraph state for %s ======\n" % name state += "view size: %dx%d\n" % (view.width(), view.height()) state += "view transform:\n" + indent(transformStr(view.transform()), " ") for item in view.scene().items(): if item.parentItem() is None: state += itemState(item) + '\n' return state def itemState(root): state = str(root) + '\n' from pyqtgraph import ViewBox state += 'bounding rect: ' + str(root.boundingRect()) + '\n' if isinstance(root, ViewBox): state += "view range: " + str(root.viewRange()) + '\n' state += "transform:\n" + indent(transformStr(root.transform()).strip(), " ") + '\n' for item in root.childItems(): state += indent(itemState(item).strip(), " ") + '\n' return state def transformStr(t): return ("[%0.2f %0.2f %0.2f]\n"*3) % (t.m11(), t.m12(), t.m13(), t.m21(), t.m22(), t.m23(), t.m31(), t.m32(), t.m33()) def indent(s, pfx): return '\n'.join(pfx+line for line in s.split('\n')) class TransposedImageItem(ImageItem): # used for testing image axis order; we can test row-major and col-major using # the same test images def __init__(self, *args, **kwds): self.__transpose = kwds.pop('transpose', False) ImageItem.__init__(self, *args, **kwds) def setImage(self, image=None, **kwds): if image is not None and self.__transpose is True: image = np.swapaxes(image, 0, 1) return ImageItem.setImage(self, image, **kwds) pyqtgraph-pyqtgraph-0.12.4/tests/images/000077500000000000000000000000001421045507400202435ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/000077500000000000000000000000001421045507400222045ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/bool.png000066400000000000000000000011561421045507400236500ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ IDATxœνΤAn!Aωή ''G‹΄&W­ΏΠ[΄x½€O4NŽζœυd­υγΝϋίϊχφήwOΈίu]'g_ρ >”°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°HΌοφήιŽGcά=α~sΞ“3? a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘'GsΞz²ΦΊ{πG}Zͺΰ€μ*™IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/gradient_mono_byte.png000066400000000000000000000012241421045507400265610ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœFIDATxœνΦAŽβ@AƒΈwΩ'7+X!¨M†ˆU«Ε΄)υκ/ πN3cΤίƒΩΆνεg.σξtsωα/g~~Ο?ός―wΠΧΊ3΅Μ†΅οϋδ'aa‘8ϊ π›, a‘π’°X$„EBX$άX$, a‘ 7 ‹EBX$<…$, a‘ 7 ‹EBX$„EEΒb‘ O! ‹EBX$„EEΒb‘ a‘pc‘°X$„EBX$άX$, a‘π’°X$„EBX$άX$, a‘ 7 ‹EBX$<…$, a‘ 7 ‹EBX$„EEΒb‘ a‘pc‘°X$„EΒSHΒb‘ a‘pc‘°X$„EBX$άX$, a‘π’°X$„EBX$άX$, a‘ 7 ‹EBX$„EEΒb‘ O! ‹EBX$„EEΒb‘ a‘pc‘°X$„EΒSHΒb‘ a‘pc‘°X$„EBX$άX$, a‘ 7 ‹EBX$<…$, a‘ 7 ‹EBX$„EEΒb‘ O! ‹EBX$„EEΒb‘ a‘pc‘°X$„EBX$άX$, a‘π’°X$„EBX$άX$, a‘ 7 ‹EBX$<…$, a‘ 7 ‹EBX$„EEΒb‘ O! ‹EBX$„Eb6¬1Fϊ=8ϋΗ ±ζ+§(ΘXηΛ<φ{ŽΣ‰γ2qž7ΏΐΟ͌UTƒeχάζV©F|Iρ‡ΥπΝ™χS$Φ¬€‘b”B‘,cΚη μnή‘Δ{XͺεYayχΣΏΔ›lΪΗΊΌ‹T±<„@ƒ4ͺΆŠˆγrž–}¬MœΛ*5©€mϊX˜\Κ•+(μgx,‘AKa{Ν(Ξb3"R(<Τφ±³J"oζ5wήH)(…ΒύιΞ|F±g…’ܟ§ύ±‘η(b•†‹oTmΟ —Œ<6#λ~ŠΔ{fβKπXΒΗf4+ΰςβ{π`©c3sY₯yKΌ‡²ΛΌ’μ£)“‰ο‰Sΰ± „¨>Φ9(»ηΦΗ•ψ<ƒϋXžϊ»Ÿ"ρž%ώJ!„0MƒΤ βΑ‡ˆ=¬ΦΗͺ_aχ]β=μΫΗ2# ΈŸώϋُ!l:WX΄ΛΟA‘€« NΑψΧβ­“Έ-š‡ρ/ΡOβ?Z/ΑcAΜ֊ΗΦΦΈν^<`όk˜ΈaΥ6#έ~͘0ώ•‹―—‰ΟΑʍ”!0ώ•Œο,vρ/!«4W‰ΟC)„¦96#+ή³Δ_²Ϊ±Δ­Δχ*ΰAρ‡εωŸRAe1™8?ΕπXB`ƒΤŒΜΈA)ρωΈ‰Dλ”}ŽΣ‰γJ|ž™ϊXž°Jω»h%Ύ!¬ΩΗr€UΊ'φ°u«σε†Wν"qf?{ BϋΞνr3’i«ϋ ΖΏμ»Έ->57R+q ΖΏtνx7$ B`P7%4it€ŒՊ'ͺ­E—»W&•‰ΝHΝ<„ΐ\a.Ύs‰7ƒώΚ(qlFA¬Y.υΕ)f:6£)ή6ηΑcAχ3ΦY3ݞΫΑ*u€=ϊXfpψ£Aœί«€”BA¨AZΏΒ\|ήοaΚ>–™Ξ*iVΐRqŠΕϋXη μš.ηΑcA4Hk['σZ₯ρ%kŽΕ‰/ο"\Iμaλρ/Ν("Ξμ:x,ρ―οΘcDJ0ƒ Ώ©ύΣ„ρ/ϋ.:ˆ›ΧΦ†—«όͺ”B‚ΉΒΥΔf$"χηaόk©nHQΥ>λ+ΰγ_΅b‘DΥ³uβ!›ΩMeσΔΐBρφζJFŒΥΪ»y­=qΤ‡…ΙΝίΕOρ9(²7‚|ΥOπXs…γŞ€ …ΖΏΎ#™7—;W輟M²ο’ƒxl luΉx,ρ―ΕΕωͺmFό+d`όkίnHMλδJ!„ wKΑV‰5+`¨ΨΓ¬ΗfΟ[β=¬lfyχ3<›ΰ± „ R3’°ηŠΔšεR_œ’_Λ *<Jό=qΕ>–ώu’k‘Έs’:ΐcAsχ±ΞAΝr9£ψ^<Ψ’εYa%χΣy?›ΠΗϊ+nηr?Ε)πXγ_Ιψφ..χηaό+φΝ™«T#Ύ„R!0W¨’ΞAΝ ν„ρ―ΙĞ’χ³ΖΏΎ#΅2±s…ϊ‡i‚Η‚˜+΄ο’ƒX$sW‚ρ―ΕΕ Kό9˜AρΨΜpχS$ή3_‚Η‚ζ>6£ Φ¬€‘b-ϋXžIδΡ(ˆη-ρ(…Β€©g© J‰Ο‹M€ϋXΞ°JΕ)VθcuΎάπδZ$ŽΛΔyπXΒ^ R3‚UΊ!Ύ„>–-> Θ~φ°ΕψΧX{§σ;΄HμΏe<„ΐ\a.Ϊ:IΊ•βŒ%㲡΅θrq[4γ_ΊoΙ/ά -z  B`όkΚnΘΨΦ‰ΖΏ7#ζσ@)„˜+όŽ<(ρι»ψ)φ }lFδ9Rβσq“ŽΝ ΞΔγ2q<„°Χ±¬R+ρ%ϋXžIδΡ`•n‹= λcƒ²Ο‘Lό+θ!¨χ±<+Lj•–)ρ&λτ±œ+ ―€‹‰SP !„ν€Ν/7――瑏%z’NΌr γ_ Δη HzΪΟπXs…ύΔfdΈUͺΟΠ&Œεβ"oNgoψ+#γ_ΙΈΎΨ³BάΝƒΗ‚˜+œ΅RT@Μ`MξΏ„ρ―Ε»!Νk«J!„ΐ\!b;~―¨›QxŽ”ψ_bλ›™Ξ*in°Rq ο‡υρραTΐΆό FΓuήr°nIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/gradient_mono_int_levels.png000066400000000000000000000012361421045507400277650ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœPIDATxœνΦ1rΒ@Aδβί‚—γœΐ^ƒ‘Τ_]m0u{§pDΛδΠΊυlΘυzύυΜyxΧεryjφbXΒW<%,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"!,Β"1 kY–tvζΜ*$αΕz »υްήκ8»UXjλ ϊc‘πbmήg~ο„uoή­V!χ– / a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘8Ο­λšΞΐ|Dͺ->3ζPIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/gradient_rgba_byte.png000066400000000000000000000013161421045507400265260ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ€IDATxœνΧΫNADΡψίΓό··'FƒM⎒k=)Hk:UΕ πVΪΆ­>7dίχ/ŸΉ[ύ¬σ~œ™™ΣΜΜίόπϊβιύ―ΗΟ^Όπφγ‡g.|fχφ•ΏθΆNϋΣΜωΌ”2«kV„yΉŽπΝ$ ‰Eb9±ΛSπη¨Bͺ„*$!±HΨX$$ ‰EΒx'‘ I¨B‹„EBb‘°±H¨Bͺ„*$!±HH,‹„o…$T! UHB’X$l,‹„EB’P…$$ ‰EΒx'!±HΨX$T! UHB’X$l,‹„EΒΕ"‘ Iο$$ ‹„Δ"ac‘P…$T! UHBb‘°±HH,‹„ρNB’P…$$ ‹„Δ"ac‘p±H¨BΖ; ‰EΒΖ"!±HΨX$T! UHB’X$l,‹„Δ"aΌ“P…$T! ‰EΒΖβJ‡₯§$ ‹+­]‰EΒΖ"‘ I¨B‹„Δ"aΌ“X$l,ͺ„*$‘ IH,6 ‰EΒΖ"αb‘P…$Œw‹„EBb‘°±H¨Bͺ„*$!±HΨX$$ ‰EΒx'‘ I¨B‹„EBb‘°±HΈX$T! 㝄Δ"ac‘X$l,ͺ„*$‘ IH,6 ‰EBb‘0ήI¨Bͺ„Δ"ac‘X$l,ͺ„*$‘ IH,‹„Δ"α[! UHB’P…$$ ‹„Δ"ac‘P…$T! ‰EBb‘0ήIH,6 UHB’P…$$ ‹ΔκΕΪΆ-=ΗMxš™™ϋ>ΐου υΙn¨s&ιBIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/gradient_rgba_byte_levels.png000066400000000000000000000012371421045507400301020ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœQIDATxœνΧAjΓ@A%1δΩ+=Ϋ&<Α+C£Xͺ: ³‡ffΌ,ΐ}Μ|4Ζ¨ίΑΩΆνι7·ΩίZŸΦEόύ€c­λΤ”™ kΉΏώ.θσθpNΣλQΎ‚Σ1±HLO¬ŸςœŽUHΒ*$a’0±HΈ±H˜X$L,ŽwV! «„‰EEΒΔ"αΖ"a’° IX…$L,& ‹„…$¬BV! «„‰EEΒΔ"αΖ"a’° I˜X$L,Žw& 7 «„UHΒ*d§―©―L,n,v2±8‹Ύ§Ύ «„㝄‰EEΒΔ"αΖ"a’° IX…$L,n,& ‹„㝄UHΒ*$ab‘pc‘0±HΈ±H‹„UHΒρNΒΔ"αΖ"ab‘pc‘° IX…$¬B& 7 ‹„‰EΒρNΒ*$a’0±H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹Δmς»1FϊŽ7²ύ€λΏ[:ΡΧ52ΚIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/gradient_rgba_float.png000066400000000000000000000013551421045507400266730ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœŸIDATxœνΪέJQ…Ρ“γ{Ύ·ύ\„a’u„>J[λ"Β&™β°χΦψžf.ZΧ΅ΎξΘ~Ώφšνμsνφ›1ΖΛcŒΝΙ'·ΗONΏ΄οφϊ·Ÿ=xφ₯‹ΟΩ}ϋιƒΛηpΉtύΌΫ/žσΦίΩ•Ϋ1v»©”™=Xγ0{!ŒγA„6X/ε]πp$‰ιΔz.£ I¨B‹„Δ"aΌ“X$$ 㝄*$‘ IH,6 ‰EΒΖ"‘ I¨Bͺ„Δ"!±HH,^’P…$T! UHBb‘°±HH,6 UHB’X$$ 㝄Δ"ac‘P…$T! UHBb‘°±HH,6 ‹„*$aΌ“X$l,‹„EB’P…$T! ‰EΒΖ"!±HH,Ζ; UHB’X$l,‹„EΒΑ"‘ Iο$$ ‹„Δ"ac‘P…$T! UHBb‘°±HH,‹„ρNB’P…$$ ‰Εζ²Hb‘πͺI,~‘Δ"aΌ“P…$$ ‰EΒx'!±HΨX$T! UHB’X$l,‹„EΒΑ"‘ Iο$$ ‹„Δ"ac‘P…$T! UHBb‘°±HH,‹„ρNB’P…$$ ‹„Δ"ac‘p°H¨BΖ; ‰EΒΖ"!±HΨX$T! UHB’X$l,‹„Δ"aΌ“P…$T! ‰EΒΖ"!±HΨX$T! UHB’X$¦λPήGb‘˜N¬Χς.x8Ζ; UHΒ€$$ ‹„Δ"ac‘πΞ; UHB’X$$ ‰EΒ€$T! UHB’X$l,³k]Χτ>ξΒϋύϋ«)ΐ5o`n§ ήŸIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/gradient_rgba_float_additive.png000066400000000000000000000233031421045507400305410ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ IDATxœνΝŽ3Λ‘žŸϊ!ΩlvkΖ«ΐ€ηΜ^χ Ω΄ζλψ›γ+Ρ½°δ#Ϊ€½φΦ›ρΖ}0ΰt’ΨΏ$«Κ‹ΘȊΜJ‹μζΧ­£|ΥYΕϊοΜ7"#ߌ‚ŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ?ESvκΊξŒSώό9ΈR @PΞ(gε Ύ¨—όΛϊ«3.Ÿογ‹]θœkύφ·Ÿέ miΫx‡ͺ’ͺf³~ωqΖCσDEρ7Gχ©'žkύ»uρ Kžΰ €gΆέφ]°ώ~άΝ„ŠυΝ§;`Ύ˜­+5ΐ€+\yeξώtkΰΊΰП΅,ΞγΊ›ψΰ!Φ‡~Ψο6ξο]Α/ςw­—€‡·||άςωΩ-·[€_ώrΝιλΫoο–K€λk·ΌΉX­Κ28Ο?ύΣΈ½E–²Ο1όbΚNS+ƒKZλtKg’gΠ—΅ύOΡ‹j΄°κχ)τ…Ό‘ΊίXΉϊιjΟΛ=@uΓ¬θIv˜ικ b½1κτ?t‹ΕΤ³IEHυ‚ z5 CHΕGT½€$χy%Κγ»ddœŽΙŒ5 H+rΝ’žZQp˜ijž·„Ϋbκ0Vα©Λπ–g,Y>o\ΉΎ¨‹ώ§™)πExkœ΄˜Ζ[σπF=iaxkHZv·q¬VŽ΄ΔXΪη5Ȍ•qLf,\«gJg²Πs».ε€·ϊUύ½yξO3–,•·J³ρEKΘIx«*ά-[Ζ²,€nκ)Kυ*Λ“}ωΛU¬l 3.‚3‹T ˜Ρ_^<λ."'ΰ€±KςV/|/=ΒX‘)”‚ 7Τ7uh³L΅Χ,Lη­§§“―ςγO»™“Oœ‘1“λ3ŸΧ¬]ΣoΚ •ΈΜ³Ό"ΈψϋοξhΊ'€V–Οt[€V‚*όυχ±’(J’šυΛzΑμ `ΆψφΣ°X±\Ί¬V !ΐo»5pSόP α1Ό₯<Πk‘—Κκίύ]‘——^½<Πk‘—zm Δς@zy Χ’#HΛe― „^XΖδ3I§š+γ"8έΗ*ŠΫxŒ…ίR†\ac§Eθfω²Θ—“±ΣWa«*.{E¨ƒX†‡<ͺθœξ΄ϊβQΣ7ΡAXxέιτΨι’Σ‰83ŽEIqΫ―ΪκεWΕluͺƒqΫџ £hΧAΨ ν΄bθ δΔ~τςqΣo”κUΞ&~a·ώ 5Ν‡δ#ΎόIΪ@N©^Ωf\ηH½M$ŒI˜Ζ "ShyΕ%μŒ-†ΌΥšΡΔξ9Ψxt.Ζ Tα-±‰_@­5Δ›πΦΈ<0Ι['iIϋςidΖΚΈ^‘n₯α­"εrΠͺ³Ε(c΅PΞƒUΑΠ—<­ύKβœΙΉ’–Ύ…'bFνxβdω-ρΚΉ‡t§#Ύό£w(Ȍ•qΌVέ`y« cUxHς֐]𐱇xΛ’V: ΙαjGŸ+€ͺNωX±Ό}„/†)’SΝΎ‰~‹ΙŒυ6c…Ξycήc…3€vεjFcNTWρ™™ΰΛϋŠ•μή P\Έ!AΨAF1‹?ϊΐCŠΓΊu’<π•šζ£Θ¦0γ"xEΈΑό²T›ˆ@ΜX²qΜOτ«z ‚μcΌuΈΑΕ¦PˏχΑ*f.†UCΤ7\`"δ­/&œ‚ΜXΑΉC:‡’‘R(έ*‘ΗγΡj ’]ΉU cE½…‰A€Γ"α–§‡~γcωΉW Όu’ξΤ‹Ny/™±2.‚7UϊY`,Ώ§ε­κ0ˆ:D’1ˆ€Φ4Ίη£Œεcv.ΖGΛs–ξԎϼ-oMͺX_ρΥwV P‡ΩfΊΡΪ‘ΒΩ"©‹Œ°έΠmZ]š{ΎωξhφΝ“Λm΄6[Ϊ=¨ΫώλTκΐ²¨gΤ’σn°ΈX,ωτι•tyctI΄έ(O^gΘΟIθεMψWυΧS ›ΒŒ‹ΰ|S(HšΒΘΔΨρDt£•gyη]ςŽF'±‘žΚƒΨiκng3‰ΐ_N" ·u°§…Δν @ρNfρέsRNDdΖΚΈ^λΌΫr1ΰ*ŒΛRފf±vΊΏψC₯C6ΈΔβN%c‡άΠ—TNJ›Ϋν&υb6›~››w#-ή5'ef¬ŒχΔΫψXΎ<5•βik³ΌUj—³1φύα»πΜΑ%τ!’1Ϋήό©Ά‡sRZύΦuψz,c‘έϋμlqœ”ƒ―­X‚SX¦6R‚ΪDΏlτn\T³hίIηξpAT½’6q›š’Πk¬Έ2oθΑŒ0z|όκυΆ9)'V¬l 3.‚·a,‘pΌkΪDοΌKη·5{ξCDlΥ©,ΊIής‘ωύ΄œ”>!Ÿ]Iϋxko˜“r 2ce\oΙXIαCy(ϊ@'nZuήν_‰~Ξιπ\ŒZ…!_D1ίμ†Œ­Ζ=LH^ύΎ1ˆ)x«œ”G‘+γ"ΈˆeΛIΙQVKξ¦3;4ώπ0a‡}ζFΕ5ΌιΔDΌ%£Χϋ‡0 FΉά”T°ολlMΔkrRNœ°zrΕς‚‘ocŽόo|…(ϋwe` £ΜiQ Β>ӌЄ―:Qυ²”Ύ7ΗΊyYNϊώρ«ΧΩ9)σLθŒχΔkM‘pFg¬*άGN24…έ ά ”³ΐ—_„s£{°ζΔ)ή;•±Ÿ·ΞΠΞ¦e{ʌ•qœœ*+H}Αoœ‡ί›ό†;)G*SDΘ„dΦ©θTΠHHsCsΠlv€ύ=ϋ ΐφΰηίέ»Ά ι²άς™ύpΛFu§ίrR–₯SIΨ―,/Ψ―,Γ§;ŒβT Gƒ“]·>΄>nNΚΏό˜*2γύπΪ^a—ϊ5Bbp7Œψ„½›Rc‘kΜαUθ’E>ֈr+:y›Š&-9[2yυQήΊΏ>o˜“rEΟ>r8ΏΉKύa<oν£ϋG—4·p bΙκBC\Ά²&γ₯™2‘τε£ΧHΕŒT/―ψΥλ λV6…ΑdΖj‚‰Q9%£¦o%%Q Β2–?\ΜVmςA4‘)”‘BƒςV„ͺΑs'y˟Aδ[Οόc΅JV4Βψ'Β[™±2.‚ΙŒ΅QΝ§ςV—Z&7Zψ†šδλ9ωdΈ²±3ylšΘΗςPQπθgI WΚ—·˜žέςVr„ρ‡1c™±2.‚ΙŒε›ςΦc΅©–Ί"ή"\­Β=να.Lpt―΄<ΤA…ZΖ’Ύ8Β[§¦Ϋ―Gu§ΑΩ:гyλSψϊqλlβS˜τθ“οΣ~"5ι};‘‚Ζ "Sκ ’Š57“ό%lˆ!=žxzVtŽΙ?ώxβyu+›ΒŒ‹ΰdΖt~DΘ[‘δ€)<Δ[ΓόGΡLθΚ_¨¨Œ:K^u`.ΖΥ Γ›Ηo—ίη‡!<•·2ce\'8οε8’y>{`—C#$0ΛRž’"ΝζδUΨ.¬ΛΥžš‹±ˆ>37@?ύυX β(N~dβήʌ•qœΫ+$Xυώ–εͺ>­Ωθ²–Τ›{‡)rΆμuG|žytΈα­Rsω%‡«“x o€;ύψΞΦDLz‚λΦΫu1ςΆί…ŸιR6 _ώ7ΦRž™²œkΑώ4ƒΏ=Kΰφ¬‘ωΝ-h·^ΜκσΰωžϋΣ;ΰεΰωΰεΙε6zyΨmέRTrβΆGς@ ͺE™ΕXXyΰ§Ow^Θ™ ΠukΞ©^_"'εO~ς‹’ψ›£ηΝ¦0γ"˜l •Ο»9 l™7sC«ηΗυΪԞ#£ŠΣα­^]μMμ΄:ΒXtL`οH8Ρ&’Žkyo³8Qx™±2.‚“Λažl Kωe;ΰ*Δ ^ΙXž³6±Σ:ŒšzφΊ>œ‚Υ1έi‡t§SD§Όw bŠ"‰ΜXΑdΖztϋšyΏκι' ₯ ­b“εpγ++’ΖJu§Γμ\₯~QΌ8¦ƒπΥˆ]&Ζ Ζu§Iήϊ£&ŽšΊγc°Φ;οaυ *–|oβΦΝ\°5©9`Οƒ?νΟ#5i_:›ΈŒ)”‘T―jTaWη_JxHΘOυΚ¦0γ"˜ΜXOq”<7ΜaΐXn³ϊΡmΘ[ρ ƚ™;ρpW26mFΡ3»±ΕYμΛ3±#ΎόIςΐqm ƒ·¦ 3VΖE0Ήͺ£phβΠΞƒpƒψX½Ÿ.σ ”·†\uφΗΜ| ll³…ΞΕΨ¦N2δvΓΜΕŒ$―Ό^w:EtΚ{Η ¦ 3VΖEp2cy$ΗdΌ³…ζ‡ρΉE™)ώVUιͺύι€7πΓ5Ά[ηC~Ž+‘Ώ5„Ό‹U;΅>Φς˜Š‹T'ρ έι@t:Ήb…|ή…Κ;y™ώίΰͺ—:ο>›¨_ξo©+Π*e:ΎbΩΑ.όΥΖ κQš–W©οbL©X‚WΚ9)³)ΜΈ&3–Ξ1πA‡dΆ™ ά°3έ/`,Υ T†·ͺξo"Ό00ϊψ…ό„γS2μ,g/j*ΨSu?€œ”™±2.‚i©"Οηυ^Ϋφ^Μ(€.―Κkͺk€jπυwwΐl§“Δ(Hey₯ε_±–ζ§₯)ψΓzˆœωΗ§+'Ǎ;lwOχΌάΈrΣμ―ΑK-cν΄Ό—=‹~γ,Œpϊ₯Ÿλ’9‚\R·eΈrΥ:φޚ]¬¦g“3kδwŒ·ΒΡix!Ήzβ€Νοέ ΊiŠ _φΊNOn«oΔ…Μι:”šŠΥtnUςΆο₯χ^τ6cwΏ¨W 2©]Κξ}$ύδ»T,΅2Υ φ¦P ςιžU4uδπDο:ΌΛ)1ˆ@Ψ%.΄j©υΖW,W½φ CŸ―|Mg!›ΒŒ‹`cI”ΣΆ0Ο[)ςw¦π θWbh”·DV™ΒέοηDKΜ’^Ίφ&ϋxΖڟpχ“0‡Y P-Μ=μ_ήbΆd5œχΚ­3χa³1Ν[ΊC :MΪάΦ]]Ψ]bΔχ»H{€›½‹ρς¦i܏"3VΖE0­ΝWΖN·ƒqψ„kTΟ #ΣΖ£o4QŠ//]}άR>•S›~έΈ λt7Μ΄Mž˜aο\rο…»»zΫίR€R­ˆNλNup,νΛοv6IΧ‘^‚:[(om6ι©7 ¨ΞΜš/?ψ“+γ"˜ΖXυ`ǐ·\qv_^Zί+4±Σ’u <€K\.@λ l!ΞM½wέ΄έΤΪ….Χ£šΦ‹ώ6¨ΑΗ ηΈš§Β£Ό5&:¨Qb§ΐ惨Œ,ΥΩ 9σ΅ΞΈN©XΡΫψςζΠmέFηψJ•Δšz&εςΚ™ΒΚΈωΫΦjΣνί-ΩΣοώ,θτκ₯ kμ9σ†iΰΌ/ζLšwη€”Υ]|ͺ`uPƒοοS¦0”;³ΨͺMΌXŒ>›ΒŒ‹`Z›Ÿk "δ-ΧœΔ—ί΄JN‘σ^ΖΪ+E9ΖS(΅ŒQ₯eqœψΏΪMH~Ι"ΐ`Φ |ω!cvσΣ1OlΙ¬1ˆζμγM£613VΖNa,γ$΅·”/ί5ρώπ†€±όrwP©ƒTήΗ c3α³ ; ΤκδNδ/³…zΦW)Ζ:€t "Ωu8sιPjρrΞΦΏ»™Χ 3VΖE09ι?EpY8Ωe0Γ¦KM’_Χ5P”2zSƒˆΞ݈―ŒKyΆδηŸξΠ\Ω‹k€«Ϊρ‘ ΉJ9rxώχf ,―άΤ»ηa‘ϊY_σώη5ΐ@ϋΔζΰώ `σ°yΤUωιΰ§ζξα ΰαΰιΕ-Ÿ_Ά{PλΎqύΑ΄θ΄wšΥξ»²Ws7>}{'Š1·”w{­o$Ε!έ]ƒΜ±4Z^Fβ“rNξ°ϋ‘¦”>€Ÿq•ŠA˜΅97…ϋΠ@ƒSΨ]S†Iuε¦ν[’τ΅ΥΞ…”fRe+€έΫΖkδmIΫΨs+\˜ΌŸΖtςQSΈH) Ί1ϋ8–“2ςε ž^ύͺu‘W½ΒΧqΏβΪk€cgžhqf΄0›ΒŒ‹ΰΖ*,ΗθζΞό)Tθ4¦6P@ΈΆΤ…y=c=aμ΄uΡωJζkPixO Y΅«Ζ5Ν™&ΔΌΞ©‹ΰ†_ΓX¨Tλ6r’‡ΩB}ω$c₯|ωΚ| ΄ΥΪ6ά@xOΟιΈ†ΣΚͺπ–:›{χ,H "Έ_‡LDf¬Œ‹ΰKFχΒη˜@E Ξي:ηrˆdŽ μ‚qžFO΅ξ/!ΛmΫΗ€ς  *ξπ>V½˜νά*2ωl^υJβ²ίF―άΛ“NIοoygΛ/ΜΤέρœf,-Išž΄π– <ϋ4:vΘΆν-όΨΤqΎKk£ύ°ήΩΒH¬Λ5›J]™±2.‚S+tŠTS fηO«Pή’š¨«8d¬’S驉š–WTΒβ­vΓΡγΆϋ₯c¬4@Zj€Τ¦`˜ν4ξ°”ΌžΓŸO}ξ)ΘΑG"Μΐθmτv­P’ cώΜΐ2V²cΤ&bM«Ύ]δi¦FηΌϋ쐜φ©ΔdΖΚΈ&3VνΟc©γe‡tϊ\MΆšςV;0α‘ΦΤϋ[6ϊπ,3c՝’ώ`©με¨ΛPg»sQρ!—”AW±¬ΏbxK‚#oYΖJF Ή[ΆN!hηbDΞ»/ΏͺbEo›ώΦηgIJ)ΧG]υ—΅²Ξ>©μ·φεB_%ΟH`"ͺm7ΝΤΑ‹l 3.‚i©"ωΌώολς ’ΰψ ²ŠΕΦΝ%IΰΟΎΎΨ;—\«Sa`'j­°-ώϊ{§ D5IeεΎί"KI¬8Ÿ»P§,ύ·wΐrΞ΅Lρ“΄‹> eJNτόΏΦ¨JΞQHŠΌ3n›ήΟ’ώO§‘l_6Οχ/lμκΦ•ο_ΎϋwwΐΓ ΐΓ–'IfΉxήl§”y(NψλPXT%3ωz°¨jgm?ύό%ΘΥά½Ί:JγiWKrͺȌχΔ)Ξ»‰žyηέ»νЏσt‘Uš¨ilύCή²Ύ|5άKϊςΫg¨ιΐǚ·*· ½*ΙlVύ³0ΰ³fyηιlTa "εcυoG|¬θƒfΓx Η«,R:ˆθ‡'yΪ†εκVEM―NHP”‘ρφ8εϋs%ψ ‚Fϊγ₯i…₯6»Φ0–WnΩΆ5…·BQ»0ρ’CΧ6— Φ*Šκ—%O":•¨©>xgΛ.νwTΞCrIb°Ϋ*μ9†}{™1±Š8γcUeάIdƒJ2VRΡία΄Aθ“+–«^• ¬K=+Υ,v¦b‰²₯SzοLUhυξƒzV’>lu­bϋβ>ΤA” Χ&F`ζU#ί΅·’š¨b½Iϊ˜(Knν™}%Σ1GlˆkZΕκ偓cΟ6=„―XζŸθu§V¬l 3.‚SΛ΄­’Žο°½uήΥl΅†·ϊΖ‘k@‹ϋL;5νΟeDςŒΰ>QAt€γ-ߍσξσΨ,,«yGώΤοο$a{ϊ/oωΐ“SέίνΈΒ.η₯Ýζ-έψύ₯.ψςΓPνdΖΚΈNn‰…~m²PgΛ/K_ΰ|mv–l<فoύέΙ“ΎΌό’rvͺⲌ‘¨ψXWΚIβcEyl$ϊZFž–υεΟC;pά킆1{Λ€ςλΔΉσΘτ#°ΓŽ#w²ΧDDΓ>τ£ŠoM@f¬Œ‹`rK,zB(ͺΡ‚v‘K ’a―ΠrU«ν 6βΞ γ8g Ε[Α΄ύ!ΐ^C¨.aΚ^a—σ–G©b§΅ΙyΧ{Z'*'θRI(™—;‰caΈ‘¨ž³ΈJv]Gyk7ς\bkΜδŠ₯|ξ'Š£ν–~t―4«ϊŽά° ±ώΏƒΔ€•ζ,θτlΠΆ DKΠyή)“νFX„Aˆ\€4Ξ{Ήw-‰A,ύ¬:}8ρΝόΓ Κ‚[ϋ]k_σLm‹cfΉτ7ΩΌ«Θ—wšΚvΙ²αΉΨΆΫ`―_'M‹N €Yι–‹τΥ-k~ρ―ώϊψγœyχ%4’Ϋςzbc ‹2pE;΅€.ά`Ƌ΍ΟGσ ƒP‹ji1³»ΫΠυ™―D\₯:π±ΜνΕ”u˜!,ύbšΡLA«ƒπ1{‘6tπ%Θ”4…ΡͺΫ@k$_^ρ4mja6…Αιαυ@κ†H_ Λ"Ρ Ϋ’Ξ„R[v”Œ΄6Ud]W9 ςε±1³‡ŸWc¬’eaΉΚ ”½p7s€WUžžν#w!uEžς^ZmdΒ80+Y έόξ€˜Lb+#:Ώ[xσΟ™±2ή§„’¦iί>θ4˜BΛ&”Š:aφi•αΔγnΒ Ωl”mτΧ!vΆŒDαΚμ"¦œ%}¬pι»j/&φ^<Ζ²žΦ.εr%y«ΈGώ޽ŸjHN₯σΈWΦλ8^.σ―G:aο^¦ 2ce\“+Jœͺ…@2ΪΊ£υ±*Ξp^GA'Ξ–Y₯k@n€¨u ӝρ'ilΧ/ԝZή*:φvΜ8kΡ₯K3ΨH’ΔΚ;=Fέ “K$ί$χξWδi ‰d; LσƒrΤ X³λOy CΟΥӘγ-ήw牆Δ ¬…έ4ƚ\±v1“[ ε7QκΔΛ4™Φ]%P^άvg;χFœδAΓρ‰Jθ‹Ϊ€bM‘γ‰%˜—ςΦ3Œ)”UQ u` %t$5ώΆqΊ IΛZFΒrT½FΒρe1πε1uHͺ—Δ Ζ+–>”Ύ9ΒΠ—WMζĊ•MaΖEpΊ) ›ZΰY§Ρ–M˜φ8l7}ͺs:3^j©fΡ1kπmiIDATJ(*Μρ₯ή’£‘·+Φ=·ΖκG?rλ&THΆ…BΙIςAVΦ[―xΰε@Τ4ιΛKaoξ9ΉU~½%s‘YΑΚ²šOOή’‘¨!oM”-Ρύœ0Λ_ϊ’Β1{ε-+όκΩΛ܌Έό{m[‘9[φv[7pάΌ/¨Γ'7οζU 9a’Ίs’ιΥ+›ΒŒ‹`ZͺΘζσϊ—k‰CJH­VG[̐€\œ{y\πΝέPwŽ«δCŒ•ΧAΨ‘F•ς­Ώ_£š>™-ΨΆ.’,wͺS“ΒN.τ³;ΩA œ]6]l^θ‘Ζ)ΪζΖ«½.άΈΫM πώώΞΏ(ArœZnόOλ΄)‰šήm`DmŸΕ–»ΡΛΉ‚Δ!Η πΞ–Ώϋ’a…€ϋ•Bf¬Œ‹`cνaŸξ:Γό€pω#uُIKΉ2ΞA‘ξΧLΓ§Iwΰ΄ξpΧμΕΓ+τŽlŸ4jvf€ΒwmήφQΫΚΦΨD βκ;Ό„E6…ο‹+°}Zs“«ύQ@Τ&ΥάD– sσ£Τ’vt±ŒeoeλΰϋKΫ«οuλ0Βά2πεΓσV"Ρ§žϋŸδ΄Ω9νT•ς%Ɯχqzzτ)ΖκuΡOƒ=γ|Fh,…ΜXΑωΞ»«ά¦UΕΌ•jϋή‘·ήm₯LS›θC§KηΕΖͺ”±δπ…žyΔi‰œϋς€W±†ύSDƒ+Ν‹πBΐ½Ž1ΕΌί’ΩΈ7ŽΐήR”ΦΖο`^έmΈ1b R{Νg=2ce\Σk«’ :XλΒ(_Τ2­wεω£6ο”*llΣη¬Μκ•τCΏ­E\O¦½&yΛο ½΄ΫΚ‹€–όƒΝ£η„Ν>XuΌ580(lS?Ε‘°ͺy̞·dcξ/Ξ`cƒ“LΑ΄Š%“U|•Β˜BY†ΛνΉOάOτΒμ4£P΄%-)ΌrP±ti; ώΙAΏη¦ITŽΫB›SτΣ©¦ΠΒΧ’cu’μΤ&¦2‚>qb•r—8砌Œc8…± RέU:₯1„Ϋ&φƒ‚@ΎšT…{Z-¬Ÿogg^-4МšœvΜ·Βήr™ ’|P„«€ς–wή£§½5cς§—lG oΫ1Jrj„ε­s}ωΜXΑ4ΖjϋΖ{8ΐF©g›`O{8<‡ψXή ³‡ϋD;Ž2Wη}$Z€}Z„«ΓϋIϊςcyVSΰ&εD{€yλΌ΄ςg5Az^ b’gψ›ίό†p0Έ 3έEB?ω·UΣa>>ωΰτ ύΏΟŸQΣ!Λ½Φ;»έCžύ_ŸρDœυPϋBE1½C‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘ρ§… ͺΪ―°ΰjIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/gradient_rgba_float_alpha.png000066400000000000000000000277131421045507400300460ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ IDATxœνO―τ8vήό#Uug“¬³π6π&@`8‹ΫN;mLOgaώ.Yώώ3γEΎ‰#ιΨ7œx'<]ͺ["™Ε!©CJUWχzκνDΟB/₯«’(½Τs8pΰΐ8pΰΐ8pΰΐώ„ΩsύΡ=»~…πΗόΗ―žγw^+|Ξσ 8‡3p γF`ΰ£\τ69ΐ& όψ‡?n.a€M>α"ΐ3ΐŒαχΎψˆώ $7Ι_“Ÿό`˜γ'λ/€ρΰύμK`<ΟηΣ œOΗ8α4Fΰ£!£ΐΙ₯<ΐ@F“_Ę\²ΐΰ£³ωΡΰŸώρΈ½8ΰzuΧ«d;MΈ\|-Σ4ΏώλΏ9Οσ|ζω„pžηAv Ζ1FόψΗΑ1’Rύ[›¬M€sπ>π_Xϋ8χx/ε«χΰύπώx?IαΛ/ 8ŸoΐGΝησ Θφt’m8εχ€aς'ωaKΙΨΫ°ώ*§·ˆκ―’κK‘B`lχΣ6Œυη±L«›˜Άaς@«–­“?AXLD¬’T!E}QC\ΣζZΚεεξ"yω}€ΉΨ4Ν+ΖZ– F „½›ς<ύμm°“Ÿ:ΖZXύˆLH6ε²fςΚ[Β!X 'pa€p•ό¦2VΗ[ϊŽσΕΛ…δa4E͊Ίτ6yKΉΣΒ[ΝcDΉ¦±₯–0 ™νΗ0Ό™±Όν±‰ o)Κ'€δCxۍΔd?cx φ6Ψ‹ΏθοΩ–&ΉΝXςK@*ΦσΦβlQΫ· w|¬MΖ„Λ†%Wμ|,_Λ££rUε­΄xˆ¦πB~L ΕG~»uyγ/ΗΈuψފΡY °Ÿ·žηc½ΩΪ²}d H^ ‡Ν–k^½/OΒΪ8ΠΆ‘aU£©!Ξ{΅zΊaue ž—IσJqεΛη桘?η6ξώ*ήαΌΓƒ¦‘›ΧΪ—OΙΖ°Ώy¦πΐ―ήl …ΜS˜Šjtσ}Θ[&Ίzb¦-ΖͺΘΞϋ4ΘωBH7υσΫc͐βΨ_ΣfͺΚVxΛ¨„u¦–χγ¦PBG±ƒH™rΩΟ[„{Ζ:πμφ±†I‡ * ΨKSWr8±ΠŒ­βN Β&OqrR9Q£Ζ ΦΞϋLΜ¬DuSΟYΛ),Œ΅τгU)•„π–»Λ­χςΦ;|,6x Ύ|%-o= ­#@zΰW oξ Ψ=Œ5Vμ²4εΝDVσυ»»WhZΖͺe«ž³Xš—Hlε­“(WIͺ“(§XE μ€ ½ ησώ@ηΒ[o=pΆW·qτ—7;οςƒΝ†eUˆχGΦ̝ζe’ΎήΌ#\sΓ‚Υy_7,1œhMalΫΩΌ΄/ŸρΆζυކ%ΐoŒIoυε_^SxΰW o0…ϊΣ·+‹Γ*jšΞˆΚPV€1SΉF1Buή5ΒδP¦PH;SXΛi>ΡQԊΐ€d_Ύ'΅ζ«ΌΛ[Ξ½ΓΎ•±rŒ‘AΓ½Όυςς,Sx0ց§`/c}υ_Wœ'Œš<0LƒμΓδύΕ~ςΐψμSΰ4ŸΖp|Ž€ ƒ‹I0Ιώθ‡?κol’‘NΎŒΣ9F7ΐŽψό‹ΟόΥΈ °ώ ?Ξ_μ 2·Ό ώς7?ύ’B yΰΗγ |4 ŒY‡˜Όθιœ_Λ`b‰A8gΌ·ΐ8z࣏ΰρ^Š6“Ό^;I`Υ ΐχΎχ›@•Vm PεUyΰͺ6zy Τ³Κ‹6πχ*¬ΪΐϊΖΌΏVm ΰύεΟόA3Q·ΫuցoΔniς9tRLΩ­"9¨}›ΏrgΈšΦ岡Gί‰Nd–’ρh”ξ΄ΓζˆP„—Ι³κ0C©·Eγh ΰJ?Ρ΄:ˆ\ΏΥ§*ΊΣ7α1UέιύΦ;E§;±χΊσ8λ†Ήu‡»»l_όΔ}ΔF;ˆEΏεΛwΝK7,FΤΕ7Ρ5―—ΛFΓ!‰P KvEX½`‰AΌΪΌDψ&ΌCΘ}yΰMsJž·Θlφγ0…ž‚έ¦p Ί·ΣΣΙVΨ‹Ώn§- Ž˜’ \r—·”¦9τ*ŠMSΈŸ±†Φ2ΖΡQD5’½β­…6¬’Ύ οDp_ψ`.FŒΦσ—Λ[cx φ2VcΗX±έ’ςβ)>Φ&c™ΆE/ξWΦ?₯-D*s1’œAσ=tΑOνΌW5Δ&cιέp^ndmr¬ΌΥ9[”PΕ›π>ηύΎξτ//αΣ=:ˆ·β`¬OΑnˇΝδ΄ίΗͺΌ΅f,ȌUυ[iKΡtγ'TΔΆUD§(ΖƒTΎl5cI4²~ΥΆ8[(ήZw3ϋΕ[‚χωX―ιN_ΧoνsΆvΡΪnSθ£%Δ•dn²)\«Yξ)›ΝŠΜ“ΜY^7/εΛ›"\[ΓΤ–8Φ₯7…V΅0T”Aο†­€YΧΌΦ1kk-ž¨ δςΐ†¦ω›=•9Lၧ`/c%Ÿ’DŸ•σώ @κ·Λήa,”„KΩ²EΣΌΙ[‹<π5FP¦Π³²Ρk½Fg Γ|7Δ_ykƒΨ―ƒ¨x‡„‹7Κ;m oΰ­ƒ±|{Ψνc٘4 oirͺώ–SΕyχ+™c-Κf5¬ΦCΟ[λΉ5{Γ#t“`oΧ Ζέ‹ΞyΟYα΄qi…ΝD½~ήzc½QwšE§ό3ζbΌrυ~ιΨ=Έm WyKι;‰¬hμζ'ΰe«·eξ1Φλ^Bώ6c›byΝU¦TοVD§άa¬.‘{…σ‰;>ΦRΏU BOΤ€]bωwˆNy§ξT|ΑFwϊDέ «ό―™Ό5npΐ=p:{ΰγ8žΓ|<ΐί~ύ0^½θώ₯tΣΰ€Γ/π‹Μδϋ³Oa>.œFΙΖ&ωB²00zŠ6θδBο"΄>Y7Dΐπcψβ‹Ο)’@ηͺ’m0ϊiτΰμ/ΐ_τKΰ£’πξ€RŠ6f£hΌ’zo;m `mͺΪΐrγθ«6’ޟ:π{ί{GκΐρΖԁΏφkφaC)?άsoΕ[t^ ¦Kˆ3 *­ͺ¬ϊ8 žσΌΒΝΨχζεCΡ–»-Κ­ξ‡FΟ°θ=P©(@άΚI Δ"αͺ7 [#]€I)φ!֚:ˆŠwhω€9)χΰ`¬OΑnΖJθhfJ1d/uα­.Ug:ΗυκGŠθ”cmNόΚίOΙIΩSdފ­¦Y.΅ΞέPΗσ:Ι΅ž+Ν[ ΐ{»SQyλ’S>`NΚ=8λΐS°—±lΚ]›J ©8[ύ­`€ζnhfyͺmΑ_XE"4cΡΜ?lsRφΌ΅8[ωΪ’VήZ3Φ\n$Œ5½‹±ΖΡνΤAΤΏΎCtΚΜIΉ«2;Ο³Ι– —ζUl"δL!Ξ‘ΨD ‘•σ^±ωζfe «1κΗΥ5sέΌά¨¨Ϊζ΅ςε›βΐΚ κ΅y5 λ]IΞη!ίυ5D­Ρ;΄|ΐœ”{p˜ΒOΑήOΠ'Ÿšυ$bηΛΡ.60ώnΈ‘C½’˜BmŒ¬rυvΝXqωQ χy+)m`­˜6…]ΧA˜ξ2 [Υ"\j{_Qc5ύςΐχε€άƒƒ±<»+ψδμΒ[/€£ŠAX―ζr<=ΜEtŠb,·¬μ₯ ΜG‘Έα omΖ HΛxb—“RϋXFyρΌΧya\|<C‰NΩΟ[2'εί8πKΗ^ΖbŽΩ™œD^œ-ϊDΜ>V>αΥ)ωΧ9@ͺ},·εϊt~›- Qύ0T°γ­2€Ώ‘ƒΠ.]Χ'}_―PΖ€ΧxƒΨ―ƒΠψπ9)UfηyCτONΫD6cς·δm"Ηx͝χnζOm:ϊ:šΧοέζ%s1Z™ώαŸP•™ή•=<”nΖ Ύ£9)™η8π&μύΗΠ Έ'°Ε&²ŠA¨)$"^“Ξͺ•š}οΌεΕ£xΛ–jΆπω:Fo ܏AτΌ—[t>ͺμ^•ϊŽBXM|°ƒ,s9Ψς–ATKD§]―°νx67Κ•Ήοc±r³δˆSӞκ@υz šƒ˜%—ξVNΚW1½]wzOt b,'ε+ο=ο7,½»Ω°άθλςxbΧΌͺMDΕ άΚύ4+k‹Κkš+3Lάwή7ΓZ~θfάi^Ϊ——uβ;υοpήοiyƒψP9)wYΉΓx ήΐX:φέ%έd,sfb£t o΅Ύ<Β[ΎόU]jΝXΆϋ ήθΌ^©UΈZIŸΆxKΛχσΦ;œχ{Ϊ@Ζ φλ *ή•αν£='Œuΰ)xs€΄›z°ΙXω»y‡|°ε­Ν„ΙΏ”˜dΤ?ΧWξ<ϊΞyΧ#†έΟ)e?'eΗ[s1Κσν$­λΫu§E§ά‰AHλΑ‚G΅<λΐ―ή νT罏զΫΟdSxk;aΥΑ’IκΈJΆΊί/>Ζ€ η>±ϊΙ£D9a—³5MoRΌIt ZwϊrRώ«='νmXŸό»O’St˜€›ŸnώBIœgΧ “h‹“›€ΏωΧ€;™μ =όͺhωDη©ςΐάo–?xkDxŠŠ<πFΐΟ#πw_pυˆ›¬4€i‚ڝ/“έΟ>˜O@ g …1η6’|!" Œ>m πΓ­œ”Ι/Ή!sͺΘAΙΏψβsίζ€ΌŸΌΚIYεUHΡ χrRώώγ¦’ο»η€ފ½ŒuηœDΉΟ²•ܐέT-£r©³α#/:ˆ ۜΊθ·βΜ25#o›ι_νd TθͺT+―œΦΝk.?W³₯7sRκΠόζ‡ϊΧΫ#YψarRξΑΑXž‚½_Ζ©Έ“›δš!R―2g'Η*˜ω˜·ͺθ΄nSŠIι *o΅Vg}q£ώ”ZΖ²ε–Vωϋυόυdښ‚&η²±‰‡c>σV.έw0Φ;Fψ€9)χΰ`¬OΑnΖ §Mι•f,Χ94%yυ:DΉBα­όq-1Κ\ oa7r7(5Ψς§ZΝXŒΏtUκΞWυXώκάBgχτ[kgλΊΣχ1ΦΛIΉ»gB—Ρ«Mύέ  V^ΰ~j"οέvέΞΑm π(DœΥͺΨ"ΤS¨»@|,5D™ΒφmaΎ™©τΊΜ†χΚΏγ9)χΰ0…ž‚7δnθ(g-MΎ)½”urΆ«Ce ΡjDΝΡσ–ςε­¨p|χ­h*ͺλX[%pλœwmI;ΟΦ―–¬ζu_ώ=ςΐwhΉ/ό₯η€άƒƒ±<oHΌΆι$mвuΉλΌίsδΝθΩΠo5Ύ<*ΚIYοωΑ΄ŒUϋVMΤμœ}έ-θ*ΏJ^ύŠ~«κN?€θ”˜“rΖ:𼁱Ί#kΟ©Σcm2ΦΓ ΆˆNQΌΥwe«Δς¦θNuGUΧ€’Jʌš•_Λ΄η¬+gδύV*Ψ§ŠNω€9)χ`wξ†βnšΒΝ†΅ΗzΠ°e΅K}‘v.Fƒζ•Σ$y+©F\[%]ν%―βX]•Ί3w4,τ­ξΘlή¦i~‡6˜“rSxΰ)ΨΝXΙmΆγG¦°Hυ™λ2Κqv²JowεΜ•wcUh½¬š‹αΆh)•iχ§Ξ‹GΚφ΄{ΙκM_~?o½CΘΜIΉcx φ2֟ώπO% d’5ά\LC’¬Ό%+ȝζZ~λΣO€ΩgY©¨LcVœ^Rޝ(trΣύΩΧΐ0ζ%ΑΣΌδ€des²•“2§΅©IjUNΚΑy?xΐŸG #ϊ0ώύΧ_ρΊˆKo“—ΒΛ4P΄¦·ΙΏδƒψ½ψ)joιήη­,'}T9)ΣCέ©W9)εΑ%3ε―ζ€τ[9)g›“R΄¦§’“2K[³ξτQNΚ”#ΦΒ?X`tψhΨΉ=λΐS°—±Lσ–|B™=’τH `Έs‚F‘bΖ`C.€ΘΉq“'ύ“’Έ 7€4’uΞ€δr ‘qΗȝΔRžεξΉΪνvpž\Ωz‚€[p!W[ΆΞ^—›ήp-!]JŽίτH‘₯±₯$pPΗ•τSM¨³!ΞΣ/|ŽMΟΛ \–€—XΔPΆ.v4€LW8HCqy«MaΆ’7 @"Υ,&κρΕ>7A…X*,Κ§HMδ@‚Ε;ηΥ6`₯0 ζBur²‰uk[.)ό@‘ΟΝΝ+θqŽΧΡ³aiƒxj-c5ˆƒμ©œ”f±‰θζ₯>T»»a¦πΐS°—±’IF1ΉI&)_Ύk r0Ο„NeΘED³γl W!τ/*WΖ5rRŒΛrΒ[f(—QF0#–}FD/CR[ΰl6¬₯0‡ΜU.k§§ερ6ΗͺLϋ&Δ€ΊΈS1—7EΝΌ91­c,΅Ύi«–5cUήΞςxςd›$υSζ{ŒuΰΫΕnΖΚ«RyΛ¨ε» šf:TΖWΆ.—ƒ‡|βκFΕΧέTΒΩ—σ.βζ/ δ”ϋšΉΠFΗ[‘ΒU•·œ ?Fω@cQ ·\Ω¬Έφ#€7—ϊ«ήͺht§―θ bΜδ’ηυΧί­Λ΅Ξ»0ΦeΨ^π£‹SΘVΤ­ηQ?B~-o΅_{γ cx ήΒX‚–·€g₯8b χΉά‘k»ϊ²΅₯“¨΄1Ύρ”ώυ΄ΔΗjΆCξ$fΖ2q•LΝ ―π– ©ž’cΌ€S_mt™be‘bŸΤܞϊƒMٍΪ.΄Ιλ“|r²ΣZρn]Ά:Dœ@t§W‘Β s»ΒΗ cUg«^ÚΕΩZ?λμmXΡE«ι-’}ωΝζ% «Ά‘\³Ό€Υ6ΨΖζ8VμΓZ@œKA$~2€Ug£wΌΣΌ²]N‘ϊlr'#GΒ]ΎΔfqΓQ‹φάsήWGHψzNΚ¦ξ:\Χo§Ιo˜ΒΈ^±himσ­mXΚ¬W³˜›j^{p˜ΒOΑ^Ζ .v ou6HΖ¨±B—BJθTΓΦFEΔ ‹$ΈJΕC—mTΡ‡Ε–ΐ)3–iΨ’πΦV ’Œ ³F9ΛͺΚ—Κ–ΡgSxa±§[‘χŠŽ±Φήw*ά¨μΞΒ[r΄π–yά–%έι¦σΎyŸΒυjΘ΅—Β©D~!‡σ~ΰ[ΕnΛGm_+oεάωςω‰~6Χ1ΕQˆŠΐbqΌ$Ωτ 0G,£Š2ψcK9υ`n#ΩyΟWΫmή2ΚΗrΡ&eUΏ<Š©ΟP|(Ξ»y@T΄DΐΩ‡»'Λ=(c‹/ΏπVλl‘ϋΑg#0€’χΙNF1ŝj; ?ϊс΄τΑ”xΐGd4Ζϐ—Π±Γό[Ώσ ΜD+γ'S²2τ{°`ΝδΜX;ηg_£'†€ΡEΐΈ”Iњ\Δ14”―*γ&3X8γη<ΰΗ‘46-ΙN―7L7yΙ`z€Λ‹—]ύ§Ο>ύtN' Δ3(ې %$\ώhKtjΰm֝"=uρόβσΟ½½ςrΌΉήNΧ¬”νΕΫ πΣΏϊ83pfyγ0ΧkŽ.ŠΊΥ»D‰ΕμΑή†u₯I―kh‘πdrMΫ*f±*/š_¬H;%\8SX7χŒ"κz#ΣδN ίx WTP>:’]ty1ΞΆφKBω΄ΜΧΘίΝ°ΑIΕλW·iž­θ@>’t}δΰ«?Uc·“(ΊLN€4Y½φŸ)]½ΐϊ7}γ‘HζΔ}πe«™vΰ0…ž‚½ŒυΒKοΨ­>8ŠΓήρVήQΌ΅ό¨ε-Oͺμ’τ cεΟOλκ€˜E5 8ϋΑ•±?¨ΑO³!,*έκΜ[•ŒLœd Z§ncVΖςK³&β1Κ3–qΊΔ:ΡσVO !Ž1zΣώO΅Œu‘™±501¨g©ŒεΥcξΦοŒuΰ)ΨΫ'&ZuοzπȘόYΨ&γ‹ FPΓͺΕEP'¦„UŒU …ΊΚœ`κA–a˜•4ψ,ό’x„y£%mε…jWΛ:ΝJCΪn½ JqΆέK.šfΉYφϋDα­yεΉJo ΏZη·ρ՜֦šfήκ|,Ν[;p0ց§`7c•ύΪ·0«·οŠΚmμtA\Ϊt*§»4Ά¦Δj°C#ψŸ―`s#{Ws)­²’­ †εΓΎΛ[6Λ²K6¦v v+œΨP\ξλ>Euπω ν4ψN!Οx+2l_Ά#0U˜^”•³…ς·:'rv7¬8uΦβκ.΅ν–$|€6'©ζEΚα’,|(°Όkο.Y'gΞΞ»6…³+6Q5―`«|`ζNσ’4/ΛβΛϋ¨1όψp”P•}ΌμUτ–Σ|τȍΆ›Χ:‘ελk=ϊ‚ι*κςV»ν5θΰΥΑΓψvρSΈΝXκœMSΈ|0;xΛ2P?ͺ’ΏΫ4 š/η«lΜgΏ  ό9’σκj•·*υ«Βάπ’Τ`s¬°νΖkη}SsΞ EKRj‘4Νrσ-ήJψŽu3ξσεε)ά΄ώ6vG«β©wΆΈƒHlι ΊσΩyߜ3])κψξ`·5O¦ §°κπΧ!aνx%²΅˜ΈjΎ›Ό•rd΅κˆIVΕΩBυ΅£^Š₯ vπ>³ΤMK"¨ŽtZ¨qᚴtKŠΕΩ’L*3@tE«_²ςΐόν²νή[ŽεΥεK5jχ<]%χš{ύ–Λ?ˆΗ–]Qjτξ”~’ЎIΟμΔL‘4)WGλT;ΛZΣ΄Άd‹ΙPoD½†π”QέΩϊVΧΪ΅ΞβΜ·‰’G€›tΥ}.η|Jσy•ήΆΙ–[,ΉΊζeΥAŸl™υ±_¦G «Ϋ&€³UΞ{wΝάΘςΠEΧΌ’ξ;˜Άy™zN³kκOΕ—²ŠΙα†ί2φ2ΦOώΗOΖ0ηΰ!ψ1Ω|p.xΐEόΰσ/€Α ތT°8cξjΕΞ™ΌΖFΐζmœ €w3πΫΏυ @šlš—&ΐΖ ΰEv½ΪŽiϊίχ5…>²AΚ™bν’hσΖ€4I΅ΆKŒέ`ΐΡyΰμ|4#ΜΜfΎώΫ―ΡSπΐ%δςΰ2{`Š^ΚrΞgΏσ)0›ΜY+@4^‚Ιˆσ°%$Φ$oΰlΑι|ρύΟΕΈ4I!Ώ«x†4•WwΙΫ―^o-ŒuΰIΨΛX³™Εš‹k΄%[Ϋθ-ηtεŽ/AMNΫσVΗ)H9»vqvŠΒB~ς™gτπ₯ςœHΕ‘’mϊΰεEτΗƒ˜ΛΈ <`°ˆ‹Jk©RρUb™e ΦλέzΪ3w&SθPj*e‘pΕks‘Ν!3ΤkΙOI$嘊Γ—Ωf­Ε,6½ώϋNδ&Ζ:π잰ÜyΐΦH)Pσ”Έƒ|ε·πΫΝ#Ν4Ί“²Ε[5"šΤ©>ΓM>'§Τ#A{qIDAT«Ž^χΪj›ΫΥQSaYg u 1Λΰ Q=Ρ2¬ž+©SΡe¬Ί‹Kμ Μ}•Uw·…Ÿ'X Ÿm‰ΦUw3[7Ό%›€%,3ΦΊ2]=χJήχ›Β8‹k;cηJ £.[_Γεΰ%δYx2O¦Y:Ά\šή«ιϊ²Ήy•Έhšcy/F5¬εu€ΆIΙφ&¦ΠάDŽgsAΕξϋb$S}Šx¦ΪDT󲫯h αΑΜΎMCγg™’Dήꏰ›΅¬Pε]σͺ6‘uNJ£2ΌΡήθA܁Γx ήΒXβW …¦a₯5•Oρ&ΐΦ4FŠ·¨)h„Γ—ϋ4γŠRΦ£„©δ;Π6ј’2E:― Fˆp½ SIj\g›άĚΫlε%.‚ζ-hάϊΚ[M”ίΖψ€œ”ηnLau;τmϋ ŽKh^η­’ Tχ]ΏΐύΥα`¬OΑnη=ιΫ‹ζΙ₯’·]m]I)­υe–ΙΚ%Ή­d³--y™DU  ~%Ύ|;ZT)+3–ΩόΘT£ˆ™Έέ<5e­•))e WsY‡Sμ4ˆŸWΖ|”Ry‰Adg_ιNow²Ν¬=ϊβ;ϊΫTH§tΎΏςθ}ΝO‘ήzƒˆ_kVεΆ—°cx ήn6ΚUΘ)oˆ©ω…±œΝ½½, ήͺ]E@;^%~@₯¨{Ό•,‹U…U¬ΞwΥsΘ’αeφσr->V)Τƒƒi\ΰK“B‚[jWΉ’°”Φ––ΘυΙRZl"€iSEΆh~mσΚ KZ’t jΓ*M Υ°dΖJtε[rτšχc>R’pͺzέn«τ/¨€ k}AT-)™τ«‚dψς9eJ•Zu£^Γ:Lၧ`wβ΅sd‘ 7(ƒ ΙWτr››ΔΌ-£Šš·L‚ζ›ΛΌ΅²‰ΤDΩsωτ*¬΄•Κnf~€hy %Ρr‘(3ceš)[M`2ΥΈNΚ™WZήڈAˆxK~ΓΖκ€/PЍu|΅3…4cυ˜Υ\Œ–·J—ͺΜΕθϊUΪͺ$vŠύΖ:πμf¬[^»zλ9“Άό΅0–ή­>VΞΈ¦¦3Χ¦}―ύj,*•XcωόZ_^J΅ΟΌvή‹^ iήJΌ„BN’ψ˚E[ίΛΠ2VΜkυ’ŸΌY€’‚7Ώ=·Žt6Ό…f”žΖόu5ωx=ΑoΠΨΉ2ΦvτuaςΈςεsMΕ—ο|ΪΎλp0ցo{Λc­Όρΐh‡άœνό w>vγGώ |μΞΐ˟ƒ$•τxΐ%QR‘»Ιeύο8;Ύ€΅ήXρ‡\έ.λbπcѝ’SΨX#žMQVΔΜ `4σ§ίϋpσD?ρaη 0Μ` pš§Σ<§0χσ―)Δ0š W“υk< πιƒι|{cΥΘU֝Z­ΞΦю@pγμFΰλ―Ύx`Β_R.S€ ~J^ €”?ϋδΣٝ€ΰΞr5 Ψ1ΈΫEγdέ#Ρ©< p&Κ–WώεΏωχ―5x“)e_(²ΈΌj²x)“r,[¦~Δ±*œΎ΄’λκIΖωJλΡSIΥκσn[Βδ¦φά“UgfAb“c"νΉή¨Ϊqy4 nεp]‰Ό‹/—ΏG>τ ντ’Q=z3[«NͺΡ) pŽχσ%₯»»ώvmή²ςλ—έj˜ε΅X'ΥΰΥ—ωΖ‘ΓΓx v3ΦKvήηžž•˜I¦U½Έμ‘η„s@ juͺŽbRΚαφBηΡ/!x`ωςD±o-§¨Ρ@²'J W$»Α62λPΆYχ—³λκΌ…@ϋZ­Iš/+oE«t-c·έσχόΛΤΜάκFΫQΑΌ^uνn<β-ΏΩƒ±<»ηβ߈&Rn0™±ΔǚΛtP™κσ28ybq3¨²yρBαΆ,ύ°lKψ³ΔUΒk>Γ΄b“Κ[ς‡Ή\$ΊEΝ\ΧΘΘI%UΘwŽ™Qδa§πΪλZoα-cMπiy+;[Β[U‰+Œ6 SΨ’MrΩπ±2SތAΔ•θt'Ζ:πμf¬—ό―πV¬ιbεƒ.«Eζ9μβcέr―PsΥRVί\˜^6Λ”¨&dR±4k nvm*„΄‚ϊp $;R'D•N¨^M8’²E­\B«/χΫ.ƒπ–΅u$Όα­¦“XyKλEŠΝͺ°ή•λuΪό©#Bγ%Ά%/Ÿω₯sΆ€άtw3Ρξ†š·–LŒΪΙ•„ŸΆΨΔΌDjR‰¦§nx|=Zn²)Τ‰˜–ΈmX]#؊&gDΣx Ιu/ε§’ΡhηŒdac„2_ΉΆί&Ս0x«βςKσj|y‰μ—„Ÿ•)ΌχΖΦ «šΒNBΈΩ{²šΉφ€Ά›ΧF bSxΰ)ΨΛXφV’«Τ^l’β­Ω6Ύ|Ί•pƒ˜Ώn»Χ3†yš+ς*!ιvIι:Ψ·ΠF•:”/x“·ŠiΙR²Υm/aΖΖό ’χEœχKΤBΏ-y‹ΖFγsζΗ–·6b¦hu "³AN›2©α2eJΥ3oΞ3r!g_ 4Νωτ ήΪƒƒ±<{ΛΝ΄α»ΌM]2ΆuΉΚΑ0]([֝ΖL•ΐ`Ιe•£€Iι°ΚΥ6}ωΚ[Ι ύΑv5αΊΐ½°―Υ>Vύτοk‹υ§ύuYΒΦΞΕθbβ-%U΅{ŒeΫ]πΣ΄½ψΪ£OY;F΅žΩ₯ί2<Χ8λΐS°›±bf,[y«ν$Β’›ExΛ„ύtjY}p9ά`‹₯Θ©Ξw­ΛϋΚ6)ž°)ScΣυΫδ-“/•μYΝΣ:sUΕo|E_Π““¦¨΄Ϊ…PŒH υ[Κτ1ˆΰσ„“Β~Ηλ± a¬Λ₯Ο€ uJΩ&³fΖκGΛ₯œ_flίδNμmXC’^†9φ6‘U Β₯ΔΚOO›L^FψΓe1…ΆάNξ[ƒhš†•CkE8Π4― &7yjGmR€uK܁χͺ7’θΓ%©8VΧ†ΊEέ°Ψ¬―Q|ωΆyιχ!Oλ—iγ4ΫΦ‹|uή΅ Ώ™ ΉxτŒΒnΖ€Ϊœ”‡)<πνb/cέΎΉ™όU%2Ÿ -εΨ7’₯@’σήJr3;Xœά|δΰ4Œg’pΟΐ8žΏόΙO(B?IOk­ΟyjΥΑd]’‚ρΐ~π`ό`ό§½[Wζ5Ÿšδ€ΜΟBlŠCq€ΨˆSšO1η8Ώϋ½O(s³†—I 2ρΑ_/€ΏMΓuͺηΈΫόόg_ŸR¨Χ”ώ’¬s!~z–¦LƒyΙjƒl­5€,YεήG?Αΐ<ŒΐΧσΥΥz`²Έ”ςd½+εΙ¨œ”γ ΓYΉςDη% ¦8ΙΨφ±§ΑŒuΰ)ΨνΌ‡βΌ/ΞΗβu.c*jΨN,DΫΔ D0ߐa·y^ΆnΖΌΜ―1Οι*Γg²›Š7–s­ΛξΛUέΌ QV΄ΌΥωςrF3^aςcκξΕμΟ° <κEfκ"lΚƒΌFΏέαώW`‰/ —ρ(žtΙ!γ‰έ,Φ<λΰό=jέV±Χκ Ώ–WΗ+n~Τ?߁ƒ±<ο 7ΨT©«γ-U29ά»δακ9‡ σΆ(1*cIOΘ5»©~―zœη₯θNW\U«ΒJΌ₯f;υŸ™)“`³¬tP9)·" =IX5Ή &θGZ oe—«ψ°ƒΠω *oιυ)ΐψ2o“Σz†~e¬ijͺτ€ςΥ5φ`oΓς!+,lιφηΠKξ—ζ΅ΨΔͺ‰uώ1…Q£Λ61άΤvΔ\·L‘τΫ‰E0#φqš€[»άώχλt=:ˆΕΓζeR“αrφ']‡Ζ΄Uƒ(1«$^ΏjmΫσ›S›hr.׈ ,tΝΛ&ŽΧπ¨aΉφO"›Ή\š:܁ZΫ&Sxΰ)xƒ)̌U>;Ϋ`ʈ}ϋϚζ­ΰ_^ωpΫ`,γ uuŸp•|j…±’²[ί™‘ΧJΉ™,™#Š)$›ΒeeαήVAA· 2LyκαŠαΦcφε­NΉοδ€tJΣμΖ[lάφϊΊ]–θ/jΚuG’cψb·•Χ­]ΎW§|¬:Ί'‹D–‘/εp΅Ό•ΚΜ…<\S·/‹σn Q­Λ”ΜyTι2嚨o«ΦS+·κWgc’StΎΌμEεΘe’Δ·?εΞ%WΫ Ώ1TBKx­γ5Θύκ˜OqΆPΌ%Ρ‘Μ[2O8΄α†.υƒ‹”ωo”΅ωDcψNa7c%―υΔό•₯2±ŒΔητvΚB·-oEΣvΕΣς˜‘εcYΕXω`Θ—θCϊ&ϋXλώΏi?ΈJMVΝ‚Ν§Wή2†55,ιxEYU‘V\Ε6c]Lϋ’7Ή­υrF³δήeYΨ’α­¦“θΚZθz»νζm]ΥR¦½όBωXέpu}ΪεΪέ ΛϊB2šΑυbk £x &.mKU/'XΘdJYβXΊ Υ¦^,7JΌ Ÿ§u`)©¦Ί˜«X+UΫB7―2†uΓ…MΓQ–Uλδ ε1š%ύΊK­b3!¦0Υ0# Φ Y‡Κ—ιΏQΙNϋΠΪύƒ;p˜ΒOΑœwMX)5zΠ–Ρ% ΨPvΌ‘ ωέ’+ΎΏVS8Ev7ηBa¬ ‰]―␦|pν˜&.ͺI…·:›(εΕ&Bt:ΜΪκveΰ&λβΞYθ!ΠΏrNʹ᭍x«†)H}Ά‰ώŸ.ΐ’βΪTn­λΉcx v+Hέ ³ (F}…5±XΤOPdU64;_^¦.dή Ytͺ]„ 5jRƒυΉΓ ΏΈ χvΫΠDωΰΌ<ŒώώξπVvΆ€μ\-χΧμL•'W€Ϊ§ξβ«υa,7Φ7P….³S™‹±ŠAXςϋμyKy“©φ±Ί*έ‹ͺhΪΎ½ λπwžωαρΧ{NoΎωΧΏροΈΡύΛΏά{ͺόΧ~ό1ΐχΏŽ{8pΰΐ8pΰΐ8pΰΐ8pΰΐ―"ώnjΞ„ϋ!πξIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/init.png000066400000000000000000000040461421045507400236610ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœΨIDATxœνέ»NU[Ερ%Šΰ/HTP/&φ[γƒψ„η°±³φ¬Œ• bTxα’ €β…- v'kŒ3ΧNΖ9ǜ―œŸs³Α‘U|ωζšUΰhGΙ?KόAΖΗΗφίμ*ό¬Ϋ·oΧwww«-Ώ~ύR₯έ»wΧwvvͺ-]]]ͺ4;;«J{φμiϊi‡R₯?ͺή½{UIύϊϋϋΥ–χοί«R«ΥR₯γǏΧϊτImΩ΅K@}ν7n¨-Ϋu”ό# )‚…‚…‚…‚…‚…‚…ˆ>V___νϊ·oίΤ–­­-UΪΨΨ¨]ί±C6lwξά©Jͺ΄ΈΈ¨JΚώύϋU©§§G•ζηηUI΅Ω&''Υ–£Gͺ’iυΝΜΜΤ›?ιc™οP‚'""""""""J€jΎμΘ‘#jΛ—/_Timm­vύδΙ“j‹™ζ3TΗΥ4oΝ4ߏ?TijjJ•Τ ΰ‰'šn©τŸͺͺƒ6έςαΓU2%xb!‚`!‚`!‚`!‚`!‚`!‚`!‚`!’΄Aͺζ M§qss³iiaaAmQέΏJΟ£VΊΛ777§Ά˜ά¦A:::ͺJ―_Ώ]7‡ΘΝqg3σ©~ωFFFTΙόχ•ΰ‰…‚…‚…‚…‚…‚…‚…ˆ>Φκκjνϊϟ?ΥsfWΝU/ΰσ?ȜŸV1sxΪŒΕ™¦9ήάg^΄gΎήηϟUixxΈv]ύίUφ―jKπΔBΑBΑBΑBΑBΑBΑBΑBDiƒ΄££>‚ίΏ—­G8P»nFφΦΧΧUΙμRΓtοή½S[ΜΩξ₯₯%U2“ƒƒƒƒ΅λf’neeE•Μ/^Τ ©-ftQ]KSˆ'""""""""J€ͺ in/ή·oŸ*©³Ή§NR[Lίάc£^Wi¦[MοΤΌΑΡάΐ£f>U—Έ²op4mg3wͺ˜vλςςrΣOێ'""""""""J€κπ»ις™&€zρ€9ήnΊ ζ2muϊήτΝ@¦εkΪ‰κOgZΎζš§OŸ6έe^\`ζrΝιϋ<±A°A°A°A°A°A°QΪΗR3nνέ¬fίΜ½&fΘΜ„ξλλ«]oo’Ξœ6―«To‘ΌΎΪrϊτiU2³―^½ͺ]7Σ|ζΈ³iΪ•ΰ‰…‚…‚…‚…‚…‚…‚…‚…ˆ©ΊYΕ4Ν]1jΠϜ«6=ΓΙΙIURo²μννU[ΤΝΚ•mNšΫ¬Υ›Νw0昷HͺA?σ†KσL£ΈO,D,D,D,D,D,D,D,D”6ΑT»μΑƒjΛεΛ—UιΙ“'΅λW\Q[Μτ¦™;UΗšΝ ΰσηΟ«’ιίͺQΥͺͺfggkΧΫ; mΊκDx{ο€lo6ψ/<±A°A°A°A°A°A°A°QΪ m΅ZM?Z oήΌQ₯‹/ͺ’™uT R5ΒZιƒκ•·4/˜Toj4-_s…»Ή΄GυoM«Σ|Z7σlΗ        ₯ Ru=Έ1<<άtKOO*™sτgϞU%υŽΝ—/_ͺ-f°Σœ|7½SΥ„4ŸΦΥΥ₯JSSSͺ4::Z»ώφν[΅Ε\˜311Q»~υκU΅e;žXˆ Xˆ Xˆ Xˆ Xˆ Xˆ Xˆ(νc™ΣƊ:l>|X•UɌζ©a:Σ(Z]]U%s€ΨτωΤΫ4Ν=6ζu•ΖττtνϊΦΦ–ΪbΣ6’<±A°A°A°A°A°A°A°QΪ ››kϊΡmάVmn7cƒζόtνΊpSmΖΚ6iΝΡκηϟΧΆρiζ†"υKutΘΗGgg§*©; ρΔBΑBΑBΑBΑBΑBΑBΑBDiƒΤά0£΄qxΪΌ=ќ„6³ŽͺAϊψρc΅Εά(n˜&­‹5[Μw0£Όσσσ΅λ¦νl†K;¦J%xb!‚`!‚`!‚`!‚`!‚`!‚`!‚`!’΄AjF •3gΞ4έςμΩ³6>Ν|·………Ϊus–_ |VUuαΒUR₯―_ΏΦ›ΕΝν7ζτ½ΊSH}ΚNωΆqv<±A°A°A°A°A°A°QΪΗZ^^nϊя=jΊΕtƒLΖ\R’›»Ν§™—;šCͺΝΆΎΎΆ˜_Ά»»»ιZ[[S[Τ}ΥUU­¬¬¨R žXˆ Xˆ Xˆ Xˆ Xˆ Xˆ Xˆ Xˆ(mήΉs§ιG?|ψ°ι–»wο6έςOΊyσ¦*™Χ1nnnΦ«;v*ϋͺHΣTΧE›AHs/υΉsηT©O,D,D,D,D,D,D,D,D”6HoέΊU»~οή=΅εϊυλͺ€(_»vMmΉtι’*---©’Ί¨··Wm1­ÑͺΚ­VKm1žΟΜΜ¨’.P[666ΪψA%xb!‚`!‚`!‚`!‚`!‚`!‚`!BΚm766–ώψƒŒΫ_ΐΤo±«/±• IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/levels1.png000066400000000000000000000012071421045507400242650ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ9IDATxœνΨ±nΒ0@Q\ρί–Ώ<έ:”ͺ\ εœ5±υ†+οr>Ρ8ςӜ³žƒYk=ζ"aργ` _υ|&a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ΈώρόΆ½Oclϋ§ncχ6ΞΕ‹EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EBX$„EβzπΏνχW?ηΘ3Wp"^,Β"!,Β"!,Β"!,Β"qtAΊ·Ό³Ό³Nά;eωox±H‹„°H‹„°H‹„°HΪΝ9λ98‘΅Φ«GήΤ7Q\Μεύ€IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/lut.png000066400000000000000000000036531421045507400235250ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ]IDATxœνέAˆVeΖρw*E²ΒDB$DDBDΔE„…„HHˆ b""C„ˆHˆ ‰Ζ‰"!"""C !!"""-DΜ$²œvρ-ζΊŸτ`·<Η3σΝΜγ]ξ{ok’$I’$I’$I’ώΊό£ααατηΠ=dddδΝΏΦψZ³yδnΝ„ϊ )ΎΡ>nΝƒϊ,YΙ­Ή΅€[τ{X>GFΓ֏ό^‚ϊi™Ζ­ΫPλv•Ή―Λ?’ϊe°a°a°a°a°a°Ρu΅κίςΘ/ύΧβΣ<Δ­5ά:Α-2k*Ά6σΓ±λΨ’5Ϋ^VmΐN΅κΫ υβWWμ±6r« ―XŠ0XŠ0XŠ0XŠ0XŠ0XŠ0XŠ0XŠθΊ ½υυζV±€έυmυΑρ/hδΚΐΣΤZ:>>a}ϊΐ\ε-(έ&ΪZ;uZœΆς5;ΕώΆψ oA}ΠN΅•O‘€γΣΛxδ}n=Ο­.Όb)Β`)Β`)Β`)Β`)Β`)Β`)Β`)’λ‚τR_z”[τϊλ7yδ(iόIZ-¬ͺ·ΦžαVρ€Iz8ΐe)φ·ΕK{h[¬:‹―ΆκΕί¨—W,E,E,E,E,E,E,E,Et]ςuά€υz’[Ϋωμγ3αΎΣMόΦs«8ωώ·h I/!o­Νζν–[k£O\?vGθ…9­΅ΥP/ž ΠΛ+–" –" –" –" –" –" –"Ίξ±Š·ά½ά’Έ«xδ8·vσ!izϋM±(:Ι­βΑŠΕž~Ε3yd· ϋa_uƒGψέΣνρ;ϊ σŠ₯ƒ₯ƒ₯ƒ₯ƒ₯ƒ₯ƒ₯ƒ₯ˆ P?Δ#‹ϊύ,­Ν½[;ρ!’m?o'‘^άΰφ*·Š%νFn A}Λ}΅βU6τπΙ<2ƒ[ ήqqκKKKKKKKK]€‡ϊOΰžΎφ'ΆŠuβ,n-~tβϊڟp€x£xρ;ΨΓ-Z'Ε_ηVq+ο;PŸΟ#7Ή5υ‹<Λ+–" –" –" –" –" –" –" –"Ί.HΫΤΎΏτξI}Πϋ[ZkϋΈUάy‘+ydˆ[GΉ΅[_C½XωΰV±‰Φηh­-δΦΤ‹§τςŠ₯ƒ₯ƒ₯ƒ₯ƒ₯ƒ₯ƒ₯ˆ{¬―ΰ1„Εqη₯·°EΗy‹mΠnn­γ.ή…\Ός€Έ£π·¦C½Έcψaηpλ1¨Ÿα‘Ήάϊκ/σH/―XŠ0XŠ0XŠ0XŠ0XŠ0XŠ0XŠ0XŠθΊ ]4ώbΏ_ϊT#+y€Έ5οίUμNχ ΌK­βqŒΧ ^όκ‹%ν”°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°H‹„°HΌοφήιŽGcά=α~sΞ“3? a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘ a‘'GsΞz²ΦΊ{πG}Zͺΰ€μ*™IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/resolution_with_downsampling_x.png000066400000000000000000000022461421045507400312650ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœXIDATxœν±kΣνΗαώΪ$U[k 1©V2Έιξδ&"us±€ΤU' έΤIܝΕI‘.Ž".κδ$Ε MΥ’©i›P“wπx{†/ηΘΉιξηαζα36ό?*2Τj΅{πY^^ώΧ™ρΎΥj΅ξέ»wχξέ₯₯₯………f³ωςεΛάΏΝ›7sss·nέΊsηΞσηΟ>|ψύϋχ›7o...ΎώΡ£G·oίήΨΨxόψρ… Ÿ={ΆΊΊϊξέ»?.--­¬¬τϋύ………'Ož΄ΫνΝΝΝ‹/ΎxρβƍOŸ>½~ύϊΥ«WWVV\ΉςϊυλkΧ½zυκςεΛ§OŸžœœόόωσ₯K—ήΎ}{ςδΙΉΉΉσηΟW*•>œ;wZ­~ϊτιΠ‘CέnχΛ—/Υj΅ΩlξξξφϋύF£±ΏΏνΫ·n·;Ž;V­VΗΗΗƒΑΞΞΞΞΞΞήήή`0ΨίߍF~ (Š’(&&&J₯R₯R9|ψπδδδŸ+ΫΫΫ?ώμχϋΥjufff0όψρ£ΧλΝΞΞΦλυN§S*•ζηηƒΑΪΪΪμμl­VFgΜιt:£Ρh~~ώλׯǏοt:Νf³έnΧλυέέέαp8==έh4ΦΧΧO:΅ΎΎ^―Χ·ΆΆŽ9ςλΧ―F£Ρn·Οž=»±±QE­V«Υj•Jemm­^―9LOOw»έννν'NLMMmnnnoo=zt8nmmυz½rΉ<33355UE―ΧΫΫΫϋύϋwQ₯R©\.—Λ剉‰ρρρ’(ΖΖΖF£Ρp8<`0 ώ#Β"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹a!,"„E„°ˆΒ"BXD‹aQd¨Υj₯χΰ/²ΌΌόί^ψυs}sœ'IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/resolution_with_downsampling_y.png000066400000000000000000000035541421045507400312710ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœIDATxœνέΉkkΖqM\c4FχˆΡΖ R©ˆ…`!Φ ’F’ΖΒF‚…H@ΠF--\ Ε],QDΕ "jaαΤ¨Έ“ΈΕ$·˜?ΰ>sΟ{ΈΜ™ο§>„§x˜ωεyί1@T†φοίοΡΡρ―3£ΔΏ544d ƒ ͺͺͺ”1΅X#GJΧ6 C±ΰ‚b!ŸααaeŒb!Š…“Z,ρ„788¨ŒQΈP―XβΘ¨Ε/€@F-ΦίΏ]s (ͺ««•1΅X†0ˆ#q±Έ"΅X΅΅΅9P‰Hkjj aGΏ2¦KΌ³΅X?ώt́`ΤbυττΈζ@QL˜0AS‹υηΟCΔ‘ΈXuuu†0(Š…|z{{•1΅Xοή½3„A'NTΖx€j±ζΜ™γšE‘x”•wd«R©Β ŽΔΏ±XΗB.j±ζΝ›ηšEΡΧΧ§Œ©Εzρβ…! β˜5k–2¦«©©Ιqˆ?ŠΤbMž<Ωq|όψQS‹υψρcCΔΑ­.ί 'Mšdƒ8>ώ¬Œ±Ε.Τ+Φ—/_\s ΅Xβ―‘‘AS‹΅jΥ*CΔρκΥ+eL-֝;w a‡ψpO-ΦκΥ« aΗ‡”1΅X/_Ύ4„Aβa j±¦OŸnƒ8~όψ‘Œ©Εzϋφ­! β¨――WΖΤb577Β ŽΔ‘Ÿ‰?6ΞΎBd‹Ož ΅XΣ¦Mś’·.«Ε5JFπ’ς;v¬2¦λΧ―_†0ˆ#q±Δ£ €ŒZ,±§@F-ΦΈqγ\s (†††”1΅X£G6„AβVόΖ‚ ΅Xƒƒƒ9ŒZ,ρΞ dΤb »ζ@0\±ΰ‚ŸδpΑ­.ΈbΑW,δ“ψPΦ±ίΜγΏBΈ XpA±ΰ‚ α‚ΝpΑ‹~Θ'ρ‹~lXEζλΧ―ΚλXp‘«§§Η5Š"ρΚ»xDxS§NUΖΤb Β t8.Τb‰§{#<ρ˜c·E>‰Οnΰ¨Hd,X Œ©Εjll4„Aιπ±qδS©T”1HαB-ΦΓ‡]s (.\¨Œ©Εͺ――7„A鰎…|ΔCCΥb=xπΐqˆ_UR‹΅aΓCΔ‘ψ},ž"“ΈXgϞ5„A+V¬PΖΤb­]»ΦqˆT‹Υάάlƒ8ž>}ͺŒ©Εϊτι“! JG-–ψ¦3Q‹uξά9Χ(Š––eL-ΦΎ}ϋ aΗνΫ·•1΅Xǎ3„AK–,QΖΤb-_Ύάq$ώδICCƒ! βθξξVΖΤb‰@„—ΈX{φμ1„A[ΆlQΖΤb8qΒq\ΉrES‹ΥΪΪjƒ86oή¬Œ©Ε:zτ¨! βΈqγ†2¦«­­ΝqlΪ΄IγŠ…|_ΏŒ©Εͺ©©1„Aι¨ΕΪΆm›kŎ;”1΅X—/_6„AgΜQΖΤbmίΎέqlέΊUS‹uθΠ!CΔqώ}eL-Φ΅kΧ aΗ”)S”1΅X½½½†0ˆ#q±nήΌiƒ8vξά©Œ©ΕΊpα‚! βΈzυͺ2¦λόωσ†0ˆ#ραΆ·nέ2„A‰Ÿ^ΌxΡq?~\S‹΅{χnCΔ!n«Q‹%žγdΤb͘1Γ5‚Q‹ΥΥΥεšE‘ψΛ—.]2„A§OŸVΖΤb={φΜ₯£kΧ]9PβS΅Xβ{ƒ@F-ΦΑƒ]s (>¬Œ©Εš;w! β7ͺΕZ·n! JG-Φ‘#G\s (/7ΤΦΦΒ tΤb8pΐ5ŠB|Έ§λδΙ“†0ˆcο޽ʘZ¬E‹Β tΤbέ»wΟ5Š’³³SS‹ΥΧΧgƒa—\¨Ε:uκ”kΕƍ•1^τC>‰‹ΕΧΏ‹Z¬Ε‹»ζ@0j±ΪΫΫ]s (*•Š2Ζ'OOβb͞=Ϋ₯£«Ξ5‚Q‹5fΜΧF-VUU•k£«££Γ5 €-N±sΨ–½IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/imageitem/resolution_without_downsampling.png000066400000000000000000000022731421045507400314660ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœmIDATxœνΤ=h“kΖαχM-΄₯hkύ\œά‚ ΑΕ'38(€‹ΰβ 8 YΤ©DΑ­EΝθ€›uPph’ƒƒ"b‘KA*(΄I8σΩΌ9ηpkώσp?ž’ώΚί9šMοΰ?€Ωlώ™‡fggϋύ~―Χλt:+++/_Ύœ››«Χλ###υzύώύϋ›››ΛΛΛ333eYΞΜΜt:v»½cǎj΅ΊΎΎώδΙ“’(–––ήΏ_ΕγǏ[­Φΰΰ`―Χk΅ZW―^­ΥjKKKΓΓΓ«««eY~ϋφmjjκΓ‡FγζΝ›νv{ώύ›››ηΝk4έnwppπΒ… Ÿ>}:tθΠΆmΫ~ύϊυξέ»’(j΅Z·Ϋ]YYΉ}ϋvQ»wοn·ΫίΏύϊυΓ‡'&&ΖΗΗwνΪ5>>>88xζΜ™j΅:??μΨ±‘‘‘ΙΙΙ’(vξάΉuλΦ²,/^ΌΨιtΦΦΦ^½zυβΕ‹‘‘‘J₯2??Ώ°°°ΈΈXΕΨΨΨΖΖΖϊϊz₯RΩ³gO·Ϋ-Λ²V«}όψρδΙ“OŸ>νv»###_Ύ|©V«?ώl΅Z Ÿ?n6›?~όh4Ϋ·oŸ››+ΛςνΫ·ύ~ϊυλ½^οξέ»7nάX[[žž~ώόωεΛ—ΟŸ?ηΝ{χξ]»vmjjκθΡ£‡ξυzύ~rrς₯K{χξ-ŠbίΎ}'NœΨ²eΛ£G<ΈΌΌ|κΤ©²,§§§‡††ͺΥκ*•Κ­[·VWW766NŸ>ύΰΑƒΡΡΡ’(Ύ~ύϊμΩ³+Wœ={φψργoήΌY\\1²ΕY₯Y‘-N©ŠlρF]D‹#Υω˜4XΏF¨Υ²λLΊcq‡ΖpΘO΄ΗbΖ`ΉΗ‚ ]²ΩΨ±€ώ.άifά΄θβΪ(ΫΦ!‚E—Vj]Z—­€z/Ό`etΓͺ V:χ,Ή`εrΫz V"7/ΆlM ύ/Έϋ—Y°’ϋ^‘Ήž¬θ;½ŒZ`Α ­₯ΣΛπΎ²Χ«N/uΏx³cΰ‰ƒ…™’ LaπCu›—8Λg$όUχ~j-C †m«mσp!)΅’υˆάΟ8ζ¨’šθ°κ­°{δS:/}||Œ±Mq‰‰?#;T‰qWXrR3YuβΟΛ[!ŠY·>;;”~A· >A;œ3ό!ΟΫ$™f©jj²#e+tΆωŽ‘°Κ §|·œ%Ξ9λϋdoζΉχ•Ό²Ι§ί‹²ͺΐυΤtKU„{Ξ'τ˜₯ψ=zΕ>λθτtΊ~p*ζΥ9VŠš΄χ:σβΓ?δΩM‘2S(ΥλRŎT­ΡΥκ£`— –ά[ΝΧ¬ΊŸΚύ­\{¬β-^~mՁχΣΣ[©&τy>Y¨‘―δlΕ|ΏbΡGH/ηcγƒŽQΞΣ9V‰ž|ι]ηέ”hΛχ"ύ͏>ΦοΜϊEέ,ޟΗςf|μŒΆ(ϋύΌ=‘Ίg‡ζβ{OΫRVnΤFmΛΌ?(³lνΚψ€Τ#P>½<.ϋΟSΆ“7‹l}ΛuywUη‡=ΗεSG vogIΫ_Ά`_Ί[­‡ΧοO«ud₯Ϊx¨˜_Ώx©v ΅T'χσD–jζ•·/Ոς΄τΣO<]ή€'οςTA€θΫ9_VΨ”jXΡάϋ2χκ—–dkά±!X€‘dΨςL† 2±!XδaΜ²ΝB°ˆ2<[ξX„,BpΔΨk–‰EΑ"„`B°€ƒήίM,B! 8nΤ5ΛΔ"„`B°!Xΐ)Γ?MΚ²d‹(²EΩ"Šl₯΅&^D‘-’ΘQd‹(²EΩκωD6–·œ΄ΩIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/plotcurveitem/connectfinite.png000066400000000000000000000010431421045507400265010ustar00rootroot00000000000000‰PNG  IHDRΘ–ΎPN pHYs  šœΥIDATxœνέΝv‚0€QμϋΏsΊ°υX ˜ŸIˆpοΪ‹οLPt\€RJ)₯£―‚ΣΉW₯-"=χ€-bό/I[XΝΘ-MφλΡ•ή¦£-ŠeF£-Κδ£-r•Ά’-²…xͺ"ήΥͺϊ:ϊ€ZWW‹‰Ÿκ‚γj1±ΰ#]s\-&σ³cβ=’‘lz!žM/t±΅ιeό•p;i‹JΦΌΠEN7Ϊ’Œ5/tQ”‹ΆΘ"”ΑόJjWγ™XPΕΈ"žͺŽβ(„BΖՁL,(a\l³Ε…ΕCh>Ζ}\ZD²wŠ.μ"ήKIΒ"†…fwζSυ [Γ"%=σ²ΏEJ&§ͺξΌ»όu0ΰŒ%U<“3uy³BSVΨÊζξΛά«_ZHk"ξΌ³Α"FΘ°ε+ 2±(!XdH³<§G”,ͺ Ο–3%‹‚1φ˜ebQB°(!X”,ώoψ]1² |ϊ‰4π&pά¨g ˜Xΐqgž―qνJXΚkω8Ÿ*—Bΰ%GǏ‹ %‹λ]•*g, Ψ…A ¨δΜΞυ.O•K!P£β"hbΠΠ/δyκΆ[]š¦IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/000077500000000000000000000000001421045507400210345ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/baseroi/000077500000000000000000000000001421045507400224605ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/baseroi/roi_getarrayregion.png000066400000000000000000000145351421045507400270710ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœIDATxœνKsTΧΉ†W7’ΊpθΒEDqŠbΰͺΜsΖ©œΚ gς°=8ΐCΞΨWŠςάƒT™δ7 *q’Z ΤRwŸλΠ«ίGξέξ­/1•χ±V­ή7}ήώήύ]VJΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ζί‹JίΏl΅Z‡xζηI₯§… ”9λ… ς᧟~šΏϊκ«|822"?ϋφm—£₯”žάΨؐǏΟ‡»»»ωπΘ‘#²^N±³³# δ³³³ω°^―ΛϊŽ?Π«W―ΊœŽ,0₯tζΜ™|ψϊυλ|(£ϋχο§~©φύKcΊ`Γ2!Ψ°L₯|¬ΡΡΡ|(>“8wοή•Ÿ=z4...Κ‚sηΞεΓ'NδΓjU«h4]~NζΝ›7έ8>>žΕΕ9yς€¬ωςe>€Ύ‘Κ%=}ϊTΦONNζCρκe½«ΦjύG_ΊP*€“Λ΄ΏύνΝομ΄Ώό~ϋν·ωz~§Cα§yΡ,ς‘™"NΎk‹έˆ†M)moo§ˆξ[[[Λ‡ς;AfΚws.έJΣ—oύ…iI쁏(Χ‘Ώώυ›ΏύΨοκΗΦί»wο_ΙΩίOKK±±Ρ³όιOΜPΚΚEŸ81. VWίεΓ‰‰Ž΄Τ—/;ΎwΘϋγΤ)5άΧ―;^H|eNMuœqi©γsΓ… zΑλλg”?sJi}½γ ηη;"04,1}Š A*χύHoάΠδ!bΛ„`Γ2!Ψ°L₯|¬³gΟΎχ‘#o_Ώ~½ΎήΆTqΖ%C#₯4==k΅š,N‘ζ’βzΣΙ;uκT>δζI‚zOBF"<)₯Σ§OηCρει)KήΛΤΤT>€S(Ξ>δy5Υj΅^―ηn\ί:ρΛ„`Γ2!Ψ°L6,‚ Λ„PJζ!”f³9::Ί·Χώ-Φ>ˆf9ώΌ,X__ﲞ\Ώ~=Š(c­„DT¨ ε$DΓοΪ+++ωPD\B΄qff&.--Ιϊ›7oζΓεεŽπ+e²D™x…yAΚήήήφφΞκj; :½ ~c™lX&– Α†eB(εΌηi!•J₯^―η!)Β‘#)Y"<‘˜ŒdkρŒSbRŠ8ΛRBΓ3гψρcY/1(ρ΅‰<VNKΤθƍωOLτˆ‰Τ™kΤh4ΖΗΗ§¦ΪA&υίX&– Α†eB°a™Σyo4ω§jIf"²`aaAˆsέ=έ*!½IΪ±Δ~xx8²hG|αηϟwΏzίBα—qAΎέK½41H(χF©σ₯d(€‡ϊΖo,‚ Λ„`Γ2!Ψ°L6,B)U˜G<*•ΚΡ£Gs%uΐ /ˆ c'V‰±HB³‘€FET%¨BΚXQ…R₯#ζ„§\―AT!Β€ŽHBFΌΰΒ¦yΤ«R©4›ΝΌ››τίX&– Α†eB°a™­kςž`žσ$-uΔMpιŒ‹σ.j`uu΅sΉzλ"θκJE][[[mΡ!’DDbBˆFj`B"aλX‰¨¦έI™ΝΕ‹eœBΘγKY·²'PSQaCF’:™Ϋ˜Η|vww#ΉπδθΏ±L6,‚ Λ„`Γ2!”rήΕU<}ϊt΅ΪNθ‘ψ€8Ά 1 ²χ„¬gΙΌDTdιΆ•ΰKNB–ψΒΜΗ’°ƒT’&·Μt(9…δo±ε˜Λ~δ€²³&«tD4ρ€"$₯І!Ι—b‘Œ<}Ty²@ΪB³Ν³Δd€ΚUa«Υj΅:N!Η/ƒίX&– Α†eB°a™J9οyαΘ‘ΪΘΘΘ»wν(‡xΎ¬Τ}@Δ[—μ":ϋβ{Š·ΞΕβŒ3aKN!₯,ΙwžΞΈμ)"δŽ#δ‘‚vό’[¦Ιk##­­wωcαί¨oόΖ2!Ψ°L6,‚ Λ„PΚyώϋοί{oooll¬Ρhw_•ϊζ‹‹‹ωιMςέYΌoξ=.«8ΧtήΕΉ–nX `…»Λ‡oϊΞaKšΥr‡ΡβΛ3K ΘχDΩΩΩ9vl4ΏHΖϊΖo,‚ Λ„`Γ2!Ψ°L6,Βan„)ωX²‡;λΗE4έ»wOHٌh֏‹Κ“ώ[μ§%ΩQσσσ²@va‘ό'ΚLΡ‘ :I Gž·<‘”2IPcŽ\Rχ΄F£±±±ρδI{ eiίψeB°a™lX&– ‘”σžG$ͺΥυγǏ7ΊiΗ{θHЧ9==­Χ‘ί™”˜ŒD`Ψ]WŽΐ=T€B|mξΐ811‘YΤ/)h’ΔφΎέ»λ:οΜ―ΚΨj΅ΖΖΖ&'Ϋ7ΕPίψeB°a™lX&– Α†eB8ΜϋννvW`Ι’γ&υσ™››“’ͺ$DΓ9‚τa£Š”ΤBΡt *IbJ €Θ1θ$3’θǐdJDˆ1₯Λ—/ηC α|——z½ήΘ₯₯\όΖ2!Ψ°L6,‚ Λ„PΚyΟ#­Vkpp0ˆλMWW\cζKIΘB, ˜H Œτi.,`gΡ‹όDάξ8"Πw–R%©ρψ𑬗/‰)ρψβ}σŽς¬ΈcΗφΆΆvσ¦b³³³ιπΛ„`Γ2!Ψ°L6,B)η=Ž\­7›νOΙ’%[υ%xλά,›™ηCvΛ•SˆοΟύ%Ηgθό’vvvκυŽ(·νΏ±L6,‚ Λ„`Γ2!”rήsW±R©Œνν΅“$ώ ΉG ΅ ,gVΦ³τ@b2’Δυβ}‹kœRΊ}ϋv>ΌvνZ>dhQβμ'ΤƒΘ-σ %ΘσςεΛ|Θ&Η’6˜‚–?cǚ―^mηΧΐQίψeB°a™lX&– ‘”σžg8΅Z­j΅š'χΘwgvnߜ…Θ²WΆx²Μ―’oρβκr½€+±V[ @=zΤεzΌin‘";*ΚσK½4Έ5ΐύ»?±Τ™CΆ»»;66vφl{ K«ϋΖo,‚ Λ„`Γ2!Ψ°L6,B)U˜η3U«›ΫΫΫoή΄UΔD%αH;«„Œ.A<`χ&Η…²”ωXSSSωPJΰ0)l Ί―PEJΜGZŽ1έJ”5SΦrŽ­οοΙ/’AͺΎρΛ„`Γ2!Ψ°L6,Β‘mq‘Rj4::₯Jv““ΔΥe1…δKI/Ϊ;wξΘz©…l7’RZ\\Μ‡μw%)e ωιS…MδΠK|σT$(_Dδ½―~ WHϋ)Θ2ο¨oόΖ2!Ψ°L6,‚ Λ„`Γ2!”R…yH‘ΡhH•Žh"* 8°K˜„8$δ233#λExJšή’¬&%4 !Β2!Ή`&ξε;Ž ηΌΙqBŒ[ͺHΡ=χςΎΘγγ΅fs4’YΏ±L6,‚ Λ„`Γ2!”rήστf³922R―·£.βi2yH6Ψ`>–  v“ηWΤƒtΓJHπO9‘HF2ΜXφ#Ξ2‹dΊ·afFnAτΝΚʊ¬½ΒGš ¦V«΅ΎΎώ΄ ;~υίX&– Α†eB°a™J9οy†Σΰΰ“j΅š»Γ’™υκUωΉT Πu•,ω2ΞοΪβۊ+ΝJ‘μΰ%ώΎ\!wχη—ΫŠ4vλΎa"w(‘ f‚—(ž‘‘‘ό'L‰λΏ±L6,‚ Λ„`Γ2!Ψ°L₯TαgŸ}φώί'Nόο'Ÿ|’R»ρ°Δp˜n% Έ'»„\$ΎΑψƒ¬M$%1 !Š,9…$‰hMΘΗ’•‹h΄Z­&λEωJαU§ΐ(V~ ΏψΕ77+•ΚΎŸαŸ oόΖ2!Ψ°L6,‚ Λ„Pΰύu‘ΥjεyψωΛ«?ω^½jχgΊuλVΎž‰ϊβ*ž?^tΟΠβ¦σβ,ONNζCn"λ™1677—₯Γ#0β›σŒ™ŸŸΟ‡χοί—υ’Δ[g‰½D±XγŸ'„}ρΕN³9~λVϋO&Ηξ»ο υΑα7– Α†eB°a™lX&„CsήΏώzλ7ΏιΆu6;·φνρOΈB9…ψΪΜ0 }DϋϋισΟ/όυ―ν¬CtήKΦΗό~X­¦ίώφΏςί|σM>œžž–#ˆ £Ρ$ňdKhS& Ζ”dσ€‹n-,’TDΆU–*y&’ $ΔΔ½tΔpY&”Ϋn«•j΅Ž3J&γζζfί†uhύšΝ΄Ώ/ΕLΧT―λŸM°τHόΤ ύ΄υΥͺ>DΜt  ]/,\Π}˜RͺΧ»=’]/ο\°Ωό'ύ_Β>– Α†eB(εcβu˜Ÿ'?7eŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ζg…Ϋ™nόkZE~ύυΧ2σΕ_δΓΑΑAYΐ ε«««2311!3lΏΙ/Έ/{rΚfι `¦¦¦dfiiIf.\Έ 3άηbhh¨p΄]MuΌεnuμžJΈeœόŠΫτ=xπ π°?†;ϊ™lX&– Α†eB(εΌsγ‘‘‘|HטΫπ tηι†9yς€ΜPLτβΞΣ«“ξ'-›Ϊ₯ƒδχ4ΰ”;²οA:H^P‚Θζ\#{β•Δo,‚ Λ„`Γ2!Ψ°L6,B)U(ϋΆ%μ΄–ohψT+άcœŽ^β’Χ―_—ͺ0Ωφ-₯tξά9™‘*δφqά/ŽA•••™‘v“]ζRJ3332ΓπΡΝ›7efyyYf¨‘Οœ9#3rΝ[[[ιππΛ„`Γ2!Ψ°L6,‚ Λ„PJ2ό$ΫqS+1mΩdŒ 1Ζ·ΉΉΩύΤ ΛΟNΝE©Λ³SK>~όXfρ€v#|bTŽŒKήΈqCfψT©¬E·2w² ~c™lX&– Α†eB°a™J©BјlYΈ`aaAf¨ΤzΙeξ%ƒ_υz]f†‡‡e†7E=υόωσΒ둚#…ρ»aΜρξέ»23;;+3kkk2#)£Όρ2ψeB°a™lX&– Α†eB(₯ )©€ο³()=X%Ηh3?©Έ€Rc„‘·@έJUΘΊΒ·oίΚ (―ΧCUȌVΦB2.Ι»`+ΑΦ2ψeB°a™lX&– Α†eB(₯ ™)ύ©_¨°¨ο¨ Ωd‘ν$©{ e²' σE™±ωμΩ3™Ήtι’Μ°yΛ‹/d†OƒΧΜϋβγ53NʊE9RΏ±L6,‚ Λ„`Γ2!Ψ°L₯T!Cx"4Ψ΄Ύ—~/½ΔΤ¨›λ%‡“υ€/^,<ΜVGr“j7vefl/U–…GžXa0ρ'α7– Α†eB°a™lX&– ‘”*dYœ΄ύ€2’ΒbmOψ+†)›•v7₯ΒbΝ σE©@™AΚ8)c|Μ_εΣΰC湘wΚξ―μΘ*χEiYΏ±L6,‚ Λ„`Γ2!Ψ°L‡œA*†ͺΪδμΩ³2ΓμPaGκ)fuς‚™SʈƒhT©T Œς8ΤΏŒœ2zΘ¨Υn/GήίίΟ‡ή―Π|Ψ°L6,‚ Λ„`Γ2!”R…”x"…Έ73©žXλΗ5ŒΔρΘ΅ZMf¨ExdjIΦϊ1~ΗΠkω|¨ψ¨οΈ†bX›ΙH₯DE­ Ν€ Λ„`Γ2!Ψ°L6,B)UΘ™(ΦΔ±j4j@ζpRKRQ2Σ’ϊŽq7ž‹υ€ΜD₯N€ΎΠηΟ#s £‡¬ί€&εΣύΛΐnόΖ2!Ψ°L6,‚ Λ„`Γ2!”R…άAβMT|‹‹‹2CiΙXΥΧPυP©ρ8Tjμ8ΚόUž06Η(${™>tn₯Nδ ½Ι Λΰ7– Α†eB°a™lX&– ‘”*\^^ξ>ΓN)TFχξέ“Vφ1ξΖθ!υϋ²9œσσσ2ΣˎŠΤ›Τ€½Δω|Έ_!3c™sΛΜX^‘θhώ€ ~c™lX&– Α†eB(εΌ3KNθ%ΥnzzZ― ©mt–yd†bxaBΓ–$,Ϋ’‹ΝδΔ‰‰ ™aΣ™Ιf½lhΠ‹σ.ηβaΛΰ7– Α†eB°a™lX&– ‘”*dŸδΔ1Β(ΠάܜΜP=18Γς&‡Ν#©™fH5G•ΚΣρx§ Cq†‰~T|L!dhˆπςεΛ2#™;μ•Αo,‚ Λ„`Γ2!Ψ°L6,B)UXΈΫ΅Β8£uTŽ,₯b—‹^lp†Ώ’Ίδ6n„‘JΚj†_>|(3L!dμ’η’J•;eVfόΖ2!Ψ°L6,‚ Λ„`Γ2!rSIΪδfΪΤ€Œ|Q—1Ζ²-ž‹κ’›„Soφ­£Nδ]^ŠΟΨ™ƒŠͺ‘S‘ŽΥμς/σ`Γ2!Ψ°L6,‚ Λ„PJ>}ϊTf kΣ¨°zΩZu…Τ€l K„‘G¦.cN)U*£‡Ό/žΪΧΜj+WΘ #ƒ<—΄œδε•Αo,‚ Λ„`Γ2!Ψ°L6,B)UΘQ Zυ²)w/ω’όΥ%c|Μ’δ―¨ζ¨°nίΎ-3Χ]“ζ‚RΫRK²^’OƒΧΜθ!w}η+¬—δc/ƒίX&– Α†eB°a™lX&„Rͺ-M$ήΔζχ”T+ϟ?—κ ζ‚2zH­Δ_±φr(~=zTx…ΤeΤ‰ά wΑϋ”Ro2rZψT{I‚νΏ±L6,‚ Λ„`Γ2!Ψ°L₯T!剀)Rυ°fB™†JΓ#χ²D/*•€SSS2Γ0Μκμ₯―_/Z’ρDn‡Ξ Ι©Ύ©@Ώ±L6,‚ Λ„`Γ2!Ψ°L₯T!剀q2†E­Δϊ»^φΈsηŽΜp‹r†2Ή?ΰββ’Μ°›({Ή,,,Θ σ<{ι«Γs±“*_/š”ͺ™ tww7Rž—Αo,‚ Λ„`Γ2!Ψ°L6,Β!ΧŠπ‘Vb|Š9œŒŽ1~733#3T ΌΌ₯₯%™‘.c­c…½Τ9ς.¨ΛzΩ"χΕ-$εNˆμ?#ͺyvvΆπbzΗo,‚ Λ„`Γ2!Ψ°L6,B)UΘžΘζ1Rzτ’κ%_”ꉱ9ven*5Wa’l:¨b‘‘Jf΄φ²η…DτAχEe½²²"3ΤΘςδ7‘Τo,‚ Λ„`Γ2!Ψ°L6,B)UHI%­8―^½* ˜tJΥC±Ιψ]αξ{ι ]ΖN‘Œ²±o*΅$―™j—y°<υ&σiωΔ¨yW―^Ι οBrSέƒΤ|Ψ°L6,‚ Λ„`Γ2!”R…ŸώΉΜHpJΡCξA±ΙθuŊζ=]7(ί…š΅|§εςl‰_tjζtUuχξ½ΞήgŸSM“―iΫφΐοžf&“ΪN‡]ιΗ?ώqπ'?ω‰uxλ­·ϊ‡—.]²wοήνήΈqΓ:ΌόςΛϋwΈrεJπΣO?΅Ǐοž={Φ:|ρΕύΓιtj–——ϋ‡‡ξξξξZoΏύΆΈΈθοΪΪZπβΕ‹ΦαργΗύΓ­­-λ°ΎΎΎ%μgϊτiλ0›Νϊ‡=²›››ύΓ‡6<ώQ=Φ—_~Ω?LWiΏώwήyΗ:˜3ΈzυͺuHe˜Ο[ZZ²'Nœθζ/Ο^rοή=λpζΜ™ώαƒϊ‡;;;ϋ_ΡΌKξδΘ‘#ΦΑ>ΨcǎY‡£GφΝ‰6ρΙ§Ο3―ωΜ3ΟX‡Β—ΰ± „ƒχΥΥU:ΤόιO/\ΈΩο3χΗm?¬+δKŒ………ώ‘ ²CΖφ’3:thŸyΒΉ·d±σ7α`rΨg π.ύ{XZj~υ«KύλΏhη|ο½χΎΏΑϋ/~ρ ΅——Χ^ύ?Ϋφ/ύΏύoϊ‡'Ož΄3Ψ87]Ί}Iig6ΖΜοΰάΉsύΓoΎωΖ:ΜΥJ»„έ€©y¦–RΈ²²?LSΆdڝaξWžbΪWόχΟ~λ΅ΧήμwΘχ5€†L›ζ΅ώρ;{ΎΤΩμΜήώΝ½{{Ζ7ωfΜ°Ά··­ΓΖΖΖ>ύ›¦Y\<Ώχ–ό[άέέ1έΏοώΓ.aC’όΞΜžϋμ³ύ;䬑ΙqNHτƒΩϋχw76ŽΪΫ$*„§—ƒš+t4wŽΩ6ι±Μηe‡Λ—/οKτso6‡c?ά&ΜG}Τ?Μά9›±ώy“™½37™‡yΝόδ_|ρE΅Οž}°°°eι·™„ΖcA ”0P ϋ“KKνd2±œ[sŽέςR©kVš<7£“V»œ9${IΖ‘Ψΐ9SD6”Ξ+š0ε=[ FΞωX’)ΗζφS―ϋ_Νϊϊϊl6±ϋΜ€γπXP†% ”ΒΎ0--͚fΓB0+²ΛΊϋχοχ́7‘ΦΏ‰4UŠ‚I[ϊYΤ–«όώφ·Ώυ_}υΥώaͺVI&ϋ”R+-‡”©5{›ω¦l’'Ε΄?,Y^nΧΦξ™Όζ—5<”€aA ₯°Yμμ΄mΫZe³τ9K` τΨωΓz)…&4YΈmgψδ“O¬Γ+―Ό?4ΙΘ@ΥR‹6Z˜₯‚Φ!ΕΤτέjšθœVκΛρ“'ΣιΤ>κ|ΙπXPΒ@Υ₯?Ύ;nX©±y‹¬¦Κ\ΓLξ]aΉ±\n?Δ\xhηΜ‘² ν yE럫)Ν€—΅w^Φάj本,;τΟΉ²rqρ‘ΉIκ±ΰιΓ‚Jaίί.-ΝΪΆ΅rσα9x7qLΥ°96!Λu>¦s—Δd^Κf`μ=fρΒά)ΛΖeΖΘ °ςφ’|S¦ΧΉΠ·―ψΫG—³@ΐcA ”0P ϋαΜΒΒ€mΫύχίɍ\¬—™Nee—Θέ(­ͺ.ƒ8‹†rΑͺΙ±!ί”MLYΕGώ%GVZ˜)%ΣT:ΫA3?Ψ½‹ŸL§SSόΜ % τXύ_ή±c»“ΙΓύs!™M±²”ιTNιΨX;+m6#g?μΑ»9Ίοο0Φό#1»'βώeΎ ˜ ύ£G,νΡλάmΖ^’36v†ώ›εΎ}1όϋ\bx,(Γ‚D wšP +FHQ°00IΪdEv°*RoTΦ_XΠgΪΪ„¬ΨlI†l&Ύsw Θ‡ΒYΕDΦ?šΪfε±-ΊΟ―Ÿ*ΫΨΨήέυ«dςlx,(Γ‚Ja?Ž˜L&³ΩΜ΄Ο‚Δ”BsΏω,ΣΚ\c±Lv0)LY±/ΌπΒώχ`WΜέf¬CjŠq™ή΄w‘+ZηVΪ'Ÿ‘oΏŽοΘ‘'‹‹;Βη bx,(a Ηκ§pΪΆL&V/eCοΉΟΘ}žl‚%λeνǚσάvΉΗ\TΖmvΦ<œ Υ›ΘεΨ|ξήό–ΛI‘Lψsέͺ=Ήm:}lίEΦ₯ %`XPΒ@)μKΫξd2±<–ΙJξylγβ¬…²qn%·i’μ`κœΣJ6ΞqiΔάj*“Β,Ω0Κji‹ςc±$•Ρž(–³FύΙ΄ΝΝΝέέ]ϋ²²L|x,(Γ‚`ΧδννέΆmMGΜcg>Ζϊη³tl‚%eΕt*“F6‡3χyΜ)Lφ‹ Seζ.γ±Ψ6―hbšΥ ¦ψYœhw•ΫύOr6›M&›zΚ΅ΑΐcA ”p VgMd#ηξξoΫ^Ϊ|‡D­pouuΥ:Ψ=d‘ŸΙJFX&―6Χ‘‰D‹CS|Meζ>΅&S¬oΌρF0z`χiαώw‘kxφn€§™πX[[³ΙdbƒD„Ξέέε—_ΆΆ(/'7쇕³%VΒ•36Vξl[Α4QΜd)’œ-1hϋ 41όΟ©w{›YXό駟φΣεΨxί;5{Σo ΫkkoήάσeεE€Η‚0,(αž₯sβΔnΫΩό†Φ³lΘόmʐ ₯禈>ψΰϋ‹ sΏšœ“1μ]ΜM2™ξδΠΫ Y mη|χέw­ƒ©ηάεηΦB>Ό³ΌΌΌΊΊη“ω‡#ϊ<”€aA ₯°pmo·“ΙΔrοG?ϊQ0„ύUόGŽμ:΄en%φπτβ—οBŒEξ6Ν«Mse‡π½σ_Mσ?Mσ›ύ;εχϋΰμΩζ jYαΰg?k~3Η¨‚B ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”€aA ”°ψ―Ÿbg§Ή}»ωΓώυ3ΑχΝ… ΝXrζɁœευΧ›—^:3Αχ͟άlnώΠ70BfGΏο“Άmθ[ψ˜LΖχMŒCΓγ[fΧd(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(Γ‚0,(aρ‡Ύ!όξwΏλoΏύvΧ8rδHΧΨΨΨθ.\θί|σMΧX[[λ§OŸξ‹‹ϋ·oίξGν‡κOž<ιk{{»;\XXθΛΛΛvιǏwgžy¦kδ«=zdWTγΦ­[]γδΙ“ϋ~ O5x,(a”kss³k¬vkΧu₯₯%ϋΛΩ³g»ΖΚΚJטL&]c6›YΉ–υυuλάyωΏγǏw{χξuΆm»Ζ±cΗμlrBgΜιr]jθ†ΟŸ?ί5δbΗ Jΐ° „QJαΉsηΊΖ7Ί†FΠR. ίƒΊ†ΖΒΣιίNχοίο;;;]C'΅mmmu‡ψλŠολ΄—.]κΟ>ϋlאΒJu!) nO²>FπXP†%ŒR >|Ψ5”ΎRκH*ων·ίvŒΡ$1R@ι¦ξΔ‰vζN³€ΉJD)+&Τk3΄Tx¨8T±€nOQfkŒΰ±  J₯J#”αΩέέέΡI՝;wμ$’9‘UZ¦jAEŽŸώyΧPτͺ9͌yΗ Jΐ° „QJ‘tJ³~ΚRJ>4+'e‘Iέ€›ͺ–Q]B}Ί3kP*,υΤdŸN")Tͺ iSηOIΥ=Œ<”€aA £”BιέαΓ‡»†$FiF靀PΊ©ψK‘N(aΚ%b]nSQ‘–—) ͺιtZ]Hw₯›‘nͺΰG―₯Η Jΐ° „Q:[ΙGj’ώ₯|¬Ibτ―ŒΘΎώϊλ!ΙλΔK=ΥΠμ‘zζz@)`ΞWκ„ΒΜΌ«Η‚0,(a”R¨Ι8ΥafmŒb4­ΰS &U–Re3*kΡ«tž.5*R‚T©ώ₯ ©F7,uSξ4ίKftΗ Jΐ° „QJ‘΄L $…E…*ΘTΞS…4’*tΚm*Sͺ•οŠeΡ©41—Ο«HFIT_K#ΥY7¬RJ=FπXP†%ŒR ₯w ²$LΚ7ͺF ¨)Bι¦τNΒ€Ώδώ3ώjΦO ¨t¨$L7#αS ¨Ώ(rTŽ(f‰=€ƒaA £”BΙ‡”E™β;©€4Q―Rη,Uψ&”Jv'”π)<”&Jοθεμ€Ξ/ΉΤ 3γͺΖΑcA ”0J)TζP ί_|Ρ5R.3K)υ‘TIΞΤΠμa—ΐΜ-k2~Τγς>%¬Bw%ti₯vΗ Jΐ° „QJ‘€Y—’¦4Q“}>©€Β7Υs*o)qΤ–ΪέBE½V‚•α‘=‰£τ4'.•VΥ ’κ_c%`XPΒ(₯Pz'$C ²΄LOz'ΉTηܞT‹2€Gšaμ„Iκ–›oλ$J*«’I­ΒΨ”B½Š'S8”0J)”ŽHb΄·ŒF=I ₯ΠOSx/^μŠ.₯€šΛ³eΊ΄jZtEŒjdώVΒ§ΘQ ¨›‘kmγΑcA ”0J)TN2WζΓώ€>κ,qΤ€žτN!€’K5Ί3ηv4κ SX§R{ƒλΞυ/½J’ͺς°qΓ‚F)…ΉΰNΒ—Ο"”²(SΔ—{nk-FŠl—V͝j„ώ₯L¬fSj³UW”J2Wΰ`XPΒ(₯PΡ\ξ©y@E[ω$ά}픝%g]Œ& “ζ…tWκŠͺΖQnΦ6;mx2@‚aA £”BEj¦\ωž2€iζ.7^ΣΏΤYšυώϋο7MσΒ /t‡ω\]݌=ν·Ϊ¬ΊQΖ5#G€ΐΑ° „QJ‘rž ERR@UjQΙPtωLyuVAN§›7oή΄ΉωΆβ;…ŸΊΟ|B’βD…‡Ή©ιΑcA ”0J)” Ht4E(•”²θ_T(©OtJvŠήΈq£;T즆nF™|΄„”Z΅―Zt―ΐV·'₯#x,(Γ‚F)…YΣ’[aηvΩZγO—€j=ώ—_~Ω5TΣ-ΑΈ|ωrw(™ΛB½DsšΎTR _ξXuŒΰ±  J₯ͺ¦%ν'eΡΤ[Ζwʝ*K)IΥC+$gŠ»¨P:%…•„©‘λu!­ψκ½(ŒΥΏVWWΏOΰι%`XPΒ(₯PŸTCiFΙ‡”E %•Τ«Kζ#ο•M•xus1…Ξ&ρΝ‡ͺ³΄;Ÿ*%9֝λ†Η Jΐ° „QJ‘$F’£ΌeT^T›ΜH5E˜›žIΰ4…ΧI•’Bin.0ΜŒΊ+εEuόH…΅xŸ RΓ‚F)…oΎωfאŽ(ΘRx¨šɜ"2ɐ„Iκ“ϋΟθ]FT‘Z>ω"χdΛά¬:+Μ'Sθε”Ν8ώsyϊΙ§Ωό;Ž ΰ©β&?ί»βYktIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/baseroi/roi_getarrayregion_halfpx.png000066400000000000000000000147541421045507400304360ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœžIDATxœνΝoUšΖο—?c;±ρGβ$C$Ψ΄z9½ξEkΤκm/[Δzκ¬Kϊ˜Ετ’gzΡ+ΓiDœ8!_vœΨΎώΎΧžE1¨ξσ3·ͺoωM¦ŸίqΚ§ͺN•ίΏO½οyOJΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ζ‹‘Ο<999Γq˜› h!*w½rεJΎωΦ[oε›όqΎ966&§οννυΉZJιξέ»ωζφφvΎωΒ /HV«•oώπΓωζΔΔ„τΝ7777₯ΓδδdΎyppoΦλuι/·Ψέέ•r‹₯₯₯|σππPϊ7=Ώ §OŸφΉ,/0₯4;;›o>{φ,ί”ίΡ­[· Τ>Ә>Ψ°L6,B%k||<ίŸIˆ―ΏώZNΞ7oήΌ)ςΝ©©©|³VΣέn·Οιτ`Ϊνv ž;w.ίgzzZϊ?zτ(ί€Ύ‘ ʐξέ»'ύηζζςMρκšΝ¦τ——L·ukk«O“ΟX&„J3VJ©^?ωυ―koΌρ_½?|’oάΈΡ‘s›Ν){||,¦§·ΟΓ F ΝNGΐ9 θMλλ=ύΗΗw€ή^OΞX##=:±Ω<Κ776t„SS=·)ytT―ΏΆΦs…ΩY}ϋϋϋ)₯Ο?^[SI{ΆT5¬?όaγWΏj―6SJW|›ΡΔDΟSmnφΌ””R­Vπ%ld€ηOUα7ωEJ“†+Ώ'ΪΑθhΟ#μξφόښMύšptt”ϊhœτ6{ϊοξꇇ{n!#l6υ•ξμτ\al¬-:Ξττρo»ϋϋίΟ¦HͺΦΉsέΏώυܟώt!₯τ»ίύKώGŸ}φYΎYθcρ/N’V‘%MΞOβ°σΝΥΥΥ|“>–Έ,Ό |y’i§ωX=ίκd•―V)₯Ϋ·oη›W\”νvϋΏ8|ύg)ϋX&„J3ΦύϋχΫνύ­­Ϊύϋϋ)₯‹{ώ}¬¬¬δ›"ˆf })₯΅΅΅|sff¦x?~œoΚ¦σηΟKΜαψ N0Ašς‘?aey’εεε|σκΥ«g§ΗΗ’ρό²/Wΰ€><<άlžΤj΅μΟ…LΙμ?0ž±L6,‚ Λ„`Γ2!TrήηηηΗΗΧχφκσσηSJOžτ|• ΙΠHpφετwΎπ+‘tΟŒWˆ;ΟŠ·. $Β“‘OJιΒ… ω¦Δ”ψ‘Nς^ΔצڐΔv˜œœ¬ΥjCCCYt¨pγΛ„`Γ2!Ψ°L6,‚ Λ„PInnnμοΧ²5΄‘0Χ>ˆfΉ|ω²tΨΨΨθӟΌφΪkω¦ˆ2• 7U‘<‚DT‘˜’ˆΈ„ Μ₯K—ςM r§”nάΈ‘o>|ψ0ί€L–΅αφφφώώa·ΫΝ‚Eb^ΟX&– Α†eB°a™*9ο­V«^―Χλ΅,ρHβ ’ Z˜’ών·ίJ €HLF5yG‰)1CUœefŒΙΕΩΏsηŽτ—”ψΪDή σ«$jτϊλ―η›|c’oDL€”κυϊΡΡΡΙΙI–O+ςBΤI'ϋ― XDk>>~ϊτ¨ΣιdK’δŽά t` ‚(aŽ”³JΘθΔ φ/r\(K™΅ΈΈ˜oΚxL ‹ˆξ+T‘σΗt+QΦLYK)MLμΥλ{YψK^)ƒTγΛ„`Γ2!Ψ°L6,Β™mq‘ΰiJv““ΔΥεb Ι—’Z΄_}υ•τ—΅ νFRJ7oήΜ7YοJRΚ_Ώžo2}ͺ°h€άB z‰ožŠδε‹ˆƒƒƒιπΰΑƒΕΕッnžΊtιRώ§|’ρŒeB°a™lX&– Α†eB¨€ 766φφφχφjέ„΄8ΡDTp`•0 qHΘEM‚π”ρ¬JΡh²„&!€SΈLHΜΔ½;w‡Βyvv6ί” ·T‘EχLά[ZZš›Ϋo΅ž,-]βυ%5² ž±L6,‚ Λ„`Γ2!TrήwvvŽŽŽ‡vvŽB.βi2yH6Ψ`>–  V“ηWΤƒTΓJΘFO6e˜qُ8Λ\$ΣΏ 3#0ς’o?~,ύE―π•ŽŒŒŸœœdoο»οΎΛ”ΏΖ3– Α†eB°a™lX&„JΞϋΥ«W''Ÿ5^-ΞΜε•WδtΙί’λ*j@ΎŒσ»ΆψΆβJs₯€Θ Vπ_FΘέύΔωεv’$Βϊo˜ΘJdΐLπΚφ;I$ώL‰ΟX&– Α†eB°a™lX&„Jͺπν·ί~σΝΨέ=ΧhόsJιwήΙTb8L·’ά“]d¦Δ7ώ’‰dILBΘ…"Kn! d"Zς±¨C%δ"Q)ΩS>AωΚΒ'ͺNQ¬‘‘‘ωωo/_ώχ?ώρ_ΚπW00ž±L6,‚ Λ„`Γ2!Trή?όπΓΡѝ΅΅Ϊ'ŸόgBΔγΣO?Ν7™¨/βεΛ—₯C -n:/Ξςάά\ΎΙ B€?3Ζ]»–oŠ«ΛŒψζΌ£„t–——σΝ[·nIώ)n\b/Q,ρίέέύε/;σσ;οΏ~Β+’λΏϋξ»iPQ£ΡsG™QΈΊK:0Nίνv§§OΆΆΒ'”ͺ†υΙ'c««?}=ϊ§όώφ·žbφ››ϊkk΅z2}gf&₯Γ³gϊ^ςV§‘Ώ•΄ωΫέn«ΜΝυ IώtndHΌ£Œp~Ύ'ŸβαCύ2Η?vyΈY—G˜=ς矟Ω._?GUΓκtŸόγΓ_½z#£Ώό₯gOΆGzά‹”θhOΡŸΛ—5kεΑƒ~3ΦΤ”.ΧάΩιy­ss=<ΝΗκω‡»±‘]λ1,1τΣ|¬ž!ζcυό¦——{ΎΠήΊ₯EPΗΗ{>ɞ…•ώw°eB¨4c‰ŽλΏΐόΝ7ί”Σ%‚!+Fv¦”bΈχލ6)›V؟2S¦œώ™„TD–U–)Gώ2rUό1•E>όS+Ώ^P†”νeΜ΅ΟX&– Α†eB(Θ»θΏβ˜fζcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒωGΖeŒL?.cT©ι| G€ŽοG}$€.h:ms6Ω½νΤ>,™¬{ΈI‰ΡtΪkbΥΝννm9²°° GX˜ΧaIc^™pσ½V«%GXŒ™uΉyΗ,gρ"_~ωeίΑφΓύL6,‚ Λ„`Γ2!TrήΉIψ°t¨ΛΈαe\c:§RΌ?ΆΗστ΄ξͺB›cζΒνOΈ3PΚπΩeΧ‚tšΰωVΉ›θzξ‡PΟX&– Α†eB°a™lX&„Jͺ°0$Ϋ‰§ΣT!PQ°°CCά‡}Έέ-C(Τ€υz]Ž”Ή5)5 ₯7οΕ#Τ‰…»fςΑ«ΰΛ„`Γ2!Ψ°L6,‚ Λ„PIRGH²£cTFŒ²ρε'5· ηέ>|(GΪνΆ‘₯žβ½8jI^™YΈ<Β³ψ6ζζζδˆlK~κuDςUTΑ3– Α†eB°a™lX&– αŒc…Β+“ ΚXX™EZμΓλπΘώώΎ‘b܍ΡLΒθ!υu"‘Ίδ˜y€Μ‚9ΎCQŽΞ 5Ο6,‚ Λ„`Γ2!Ψ°L•T!ƒh"XΈnŽ…:fggεsAΉ’ŽŠ―LlŽš‹«νx€Œ&ε«`Δ“ρ;fΨκΦΒ‚ι΄'εxδ—UFό–Η3– Α†eB°a™lX&– ‘’*€Ž@cXT4T=\[ΗbT—ŒKrxTOT—Tj¬cS&Ω’Χ‘*€’εs12Θ+S“R2έWήΟΨΨX:;φμ», &ώ]xΖ2!Ψ°L6,‚ Λ„`Γ2!TR… cΙR>†Ÿ¨•˜{YF…q…`™ψ]™•†ΜD₯&₯€βέ©.ω€Τ­OŸ>•#T»|.jΙ2ŠXγ€ζ9ΐ†eB°a™lX&– ‘’*dJ€Θ.j²ΎΎ.G¨Sc†$£ueR"yV™έΉjΧ)_ Υ%c©TΦΤΡμ|cς\ά\² ž±L6,‚ Λ„`Γ2!Trήι±JD‚nζββ’)S’₯ΦΰY ΕΠ1§KK ‘σN‘ΐρπu1ΘCβέω6(8BιSFi•Η3– Α†eB°a™lX&– ‘’*€Θ’Ί† †¬¬¬Θ†k.\Έ GΚGδ½8eφͺ(SηGκ¦V”ύ»πŒeB°a™lX&– Α†eB¨€ ™ *α9*>jœ2»Ο3ςEDΚTwa<‘χ’–€dΖ «€¦ΣΒ―Τ‰¬Γ7ΖχΓ·qΆ ΟX&– Α†eB°a™Τu-O™»Νσō1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1§ςίt?λ·ΣU ‡IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/baseroi/roi_getarrayregion_img_trans.png000066400000000000000000000206701421045507400311310ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ IDATxœν{l\ε™Ώsσ\<νρxμΔ8q0MCΊ„~ΠΔVZ"mW•ͺ-RK-- 取VT*­ΚO jUΆξ₯ Tma[ °ZΆ,ͺ v» ₯@C.ŽνΨc{μ™ρx<·³|™§§γΗ―=—ησ‡υžρ™3ΗφΗΟσΎΟϋžsEQEQEQEQEQEQEQEQEQEQEQŽΗάΪθ“PšŒT θθ@*΅Ρ§’4 ΄Š¨[ΚΪ`·Š¨[Κ©²ά*’n)«ηύ¬"κ–²NlQ·”“c%VuKY)+·Š¨[Κs²VuK9«³Š¨[Κρ9«ˆΊ₯ΤrκVuKω keQ·`­­"κV«cΒ*’n΅.ζ¬"κV+bΪ*’n΅λc‰F1;»NŸ₯l$λiQ·šŸυ·Š¨[ΝΜFYEΤ­ζdc­"κV³QVu«y¨«ˆΊΥ Τ›UDέjlκΣ*’n5*υlQ·ϊ·Š¨[D£XEΤ­Ζ ±¬"κV½ΣˆVu«~i\«ˆΊU4ΊUDέͺ/šΓ*’nΥ ΝdQ·6žζ³Š¨[I³ZE:;13³Ρ'Ρ‚4·UDέZoZΑ*’n­­cQ·ΦƒV³Š¨[fiM«ˆΊeŠVΆŠ¨[kZEΤ­΅D­²£n­ jΥrΤ­SE­z?Τ­Υ£Vuk5¨U+Aέ:9Τͺ•£n­΅κdQ·>΅ju¨['B­:Τ­γ£V:κV-jΥZΡΥ…ιι>‰:A­Z[Τ-@­2C«»₯V™£uέR«Lӊn©UλCkΉ₯V­'­β–Z΅ώ4Ώ[jΥFΡΜn©UKsΊ₯VΥΝζ–ZU?4[jU½Ρ n©UυIc»₯VΥ3κ–ZU4ž[jU£ΠHn©UEcΈ₯V5"έέH&7ϊ$N€ZΥΈΤ©[.FFΤͺΖ¦« ““kv4ηš₯\F(―wM¦l >ΖΖ6ϊ$ŽΛ±cˆΗ7ϊ$”UΡߏ£G7ϊ$N€ΊΥˆΤ»UDέj,Γ*’n5 dQ·κŸΖ³Š¨[υL£ZEΤ­ϊ€±­"κV½Ρ Vu«~h«ˆΊU4›UDέΪXšΣ*’nmΝlQ·ΦŸζ·Š¨[λI«XEΤ­υ‘΅¬"κ–iZΡ*’n™£u­"““θιΩθ“h:Zέ*’n­-jΥ_¨O·,Λ²,k£ΟβδP«j©C·,ΛΪ΅k—Ue£O烩+«ά}οcr;wbjj£Oε―ωξwΏ ΰΎϋξ·Η†žΡριοΗώύΨ΄i£Ο£J}ύŽκΗ-Λ²b±Ψ 7άΰώϋο―T*ύύύfffήzλ-Τ™^υfΦκΊΒ΅"Η«―ΦKNόΪΧΎ&m§Σyΰΐƒυ“"λΠ*ΤO*κ!'Z–uΗwψΖ7Ύ R©<ψΰƒό~τττζΝ›œuΦYΖΗΗ76EΦ§U¨·T(l¬[–e }υ«_0;;  ³³“νόΰkbbBφίΊu+€ίύξwά\7ΓκΦ*ΤaΔ"υ·Θ½χή ΰζ›oζζ7ήθχϋ].Χm·έΖW"‘ΘΜΜΜάά\, …BΓLλUΟV‘nΕΒΖΉeY–ΫνP.—ωJ{{ϋwήY*•|ιK_β‹7ί|s±XdΖΰρx„Γαt:}Ξ9η ͺΜVηV‘nS‘°ώnY–‰DΎσοΈλ»ΌύφΫΆlΩ ›ΝhooΏξΊλŠΕ"€|>οrΉξΊλžž‹‹‹ζζζ8NΨ’ζZVV‘ž#Ω¨Έ΅°°ΐ²,±‘X,...:ΞφφvTλ[•JεΖopν΅ΧΖγq·ί~;ͺlvv6‰°ζυzΧ$†5„U¨ˆEΦΝ-Λ²™.»μ²Ÿόη¨vή}>ŸΣιd4"•JEΎ†B‘«―ΎšbE£Q_ϊΧQssssαp˜Ήu~~~rr««Q¬B£ˆ…υr‹b]vΩe~ωΛ_ >ŸΥ±!S›ΧλeWŒn1λεrΉk―½–b₯R)?ϋΩΟP͌μ₯·Ϋ=V½Υ k «P©PXϜHcH ΰf0ΝfC‘P©T’O°…4ξpΧ]wQ”kΉΐ'>ρ Jyχέw‡Γ-Λ’}}}+I‘e(b£nY–ΥέέΝqߏόc^―]>Θησ^―—±‡) i¬5δr9²Γ₯—^ €=3Λ²ξ»ο>¦TŽ8S”Νf …ΒϋuσΞ*4PΔ"λ·xŠΕ"΅@5ΝI\‘4―Ώώz.— …BŒC…B€”²DŽG}ΐΥW_ΝΝΟ|ζ3Βαπ­·ή:44„ꨳ½½=‘HΠ9»ΚhN,sΛ²¬ΎΎΎΆΆΆJ₯B“Κεr.—+•J‘H„ƒDΗγrΉδ-ccc™L†›Œ@Α`ϋδσω|>ορx‡Ηγy葇ςω<€Ο}ξsά[ίϊVΉ\~όρΗν§ …Bwwwu"r ψG}ήτD4X*¦¦°cΗZήAšb]wέuξΌσNyYΣ8ΣΣΣό~?€J₯2:::00ΐέB‘Π‘#GνζηηQ ] {SSSΊ»»΄΅΅˜œœΌε–[ΚεςΜΜ φΘ#0D1¦ΣιD’ςŸ™ύΫΏ=mdd„ŸRWK*NLÜθrΦΦ-“Ήκͺ«<όπΓ¨¦ΌT*U.—7oή<>>Žj@bϝfF8jτζ›o’ͺN"‘ΐΧΤΤ»πΡhΤαp°[”J₯+―Ό’ŒD"xΰ‰DεΙ'ΗΟ9'Α‘γb2™DƒθΥ§xΦΚ-Λ²ΆmΫ6??O±žxβ v˜Ψγa)—Λ₯R‰1‰ί₯+tbnnξ΄ΣN{σΝ7–Ψ…ηΞιt€ίο_ZZβHίεέn7G‘”&J_qΕΎ;"²sOOO2™\ZZJ§Σ*Φz°&nQ,ΦΠοΈγŽΆΆ6ͺΓ^#Λ›T­½½£<ζ8ώ₯ιY*•ŠΕb΄u―š ΐιt2qRˆ]u~ΚΔΔΔν·ίpΕϋώε_ΎΖ(ψνoη‹Ηk―½Φb5^罆žž΅q+›ΝΞΟΟ3 )f2ap»έ¬sZ–•Νf].M°΄΄ ½½ς%“IζAΉ\.PJIeQg¨­p8œΟηϋϊ¬ϊ§ή½{[Ήόΰ 7άΰσωn»ν6~.t£Πξ―„SqΛ²¬ήή^Χ_=ϋ7 $΄Αςω|œD5ŽŽ28q<ΘAε»οΎ €Ή_yΨX,vψπa0[–΅°°ΐ·ϋ|3Ο>;{ΡE[Q όzν΅Χς³Ύόε/7DΈB½-M^5==xν5Δb«|»ΛεϊβΏ(±„•¦3βp8μω.‹΅΅΅Q£\.ΧΥΥΥΦΦ&"¦Σιt:ΝDF2™ {ί±X €Ϋνv»έ,hoo/ ΡhξΩggw튱Π0i{ΝΎ}ϋ~τ£­ςgΫ šD,œ²[Διt:Ξt:Ν>Ηγa! @2™Μd2 ¬i‘B‘`!›7oήΌys:fl…Br¨rΉ<11a_z …Ψ‹ΗKO>9ΎwοŽJ₯’N§ΉO₯Rq8ςq\7Ρ(4|ΛΞ*ϊ[–eqΖΌηΦ[oΗγ£££Ž;†κψŸ0171ΆΉέnvψ-NξίΏΐΰΰ ͺ-Β·°¬Eν>Μ£E£Ή'ž˜ή΅«Gώ+μΡ@&“Ιησ™L¦Qς š)b‘UΔ­ωωyφŽLNNvvv²Η P( Λ²8<Μd2Ή\Ž#;Ζ!F8)ΎŒŒ s“)/‰pάηυzΉ?ͺ#AΏίΏu«χWΏšΩ½; ™Lz½^oυ)j.—+ ρΣ‹¦ŠXdεqKΊν¨lιξξΞησ@`λΦ­…B‘X,²η$½rZ•Λε(ŸtΛ2™ ηaP­Aπμ“Ρ™,joog.‘¨<ϋμμlΝηgQ¨vΉ\.—kbb"•J±*Ζ―EŠ…“q«P(°PΔ+½ψ|>ŸΟ'A‚*N§³ΏΏŸ} $¦'~eAAόΓ²*y₯R) Ή\Ξησ zžyfzxΈ˜dŠδgy½^ζJTKν<Ψ@yΝ*VμWx`θZXX`tρω|‡βT ίοχϋύω|>‹IρinnNΦ`‘Ί”Ωλυ2†9NŸΟGΟ\.W(b4:tθχομ\όΥ―gŸ2¨NgffΊΊΊxœΑΑA—ΛU38m š­eηΔύ-Λ²vοή `~~ž Ψ)ΗμμμΡ£GΉώ@©Tšžžž>θ}|||||\R6›­T*b&3?rΉœτ«P rΫΆω_|1ΡE[].WGG“&Η•JΕn’ΟηλκꚘ˜h¬p…ζ δΦ±cΗ.ΉδΆΣ鴽㠏K―œ°§ΕŽTGG‡Οη“΅¦–e•J%©Χ³Nao±Xdνι)>χ\κΌσ6-..Ζ«w4tΉ\}}}}}}ܜŸŸŸŸŸ—\#δbaγΔ……{P9γŒ36mڍF%©ωύ~ωn4eηZήΎ΄΄Δ₯/Δησy½^ΦHψύώB‘ ρ―Ώώοs’x<Ξς=·Ϋ …@$aΜ ƒΜ‘ Gσ‹…γΉΕςU0ŒΗγœƒλξξƒΛ«ν’υrΉœΠ,..ΊέnŸΟΗώυΤΤ”=E˜™™± —L&‡‡ΫŸyfϊμ³{XtΝd2###,˜ω|Ύ……IΈ&''Y|ίΏΓεA4q罆ε}ω`0xωε—£:Ν,#A©[ʟ“εuf@ϋπ°ϋ?::Κ‘œΧ띲­m›› ƒ^―wpΠσΨcﺁ”Ηγαμ!ͺU‰l6Λ#OOO‹gœq†έΤΖ’UΔΠΣƒdΫ·CβBM)a~~ήλυ†B‘ΙΙI.SΙf³r‘=+R\mΜW8©,έ¬ξξξR©Δϊ;w’Eooω±ΗF?ϊсDΒbO+•Jq!!ͺkšYmgΠ*—Λn·›ΗoPZH,±’Itw'χμΩσρΐ-·ά ³³Σž†βρx2™”²$CΪbί‹/rYi<F£μΉσ]KKK2'έΣS|≉~τ4Ζ<ξΐz,{WvΏ9KΝIIhΔ<ˆιcΩ‰Εlοθ(˜žžŽΗγ24+—ΛεrY–žžζG¦ΉrΉlλ•Λεh4*Izρ€Χ–J‡νί&Ξ<³c~~>Θκ.©TŠ,Β‘ Ϋνf7ΞλυΪ84-'–eY»wόλΏΎΦΫϋ^΄T*.—‹ωˆMOOΛΚt©T*›Νrξ_©Tdz1—ΛΝΝΝq H²Ω,³Xooy~$eYq €EP(Δ@z{{ΩQc0γρ" ΣrbXXXxπΑε•όόσ»\.Ή#C‘PH$²ψΐδδ€t‘»ΊΊΌ^o$aΦ ƒ²‚kd.hΛ–Ά§žšΪΎύ½u –e₯ΣιwήyΥeμ4LVΡ Ϊ’Ρh΄λ’B+ŠΐιtήsΟwžyζέhτ½•μR,—Λ]]]Η²,φάγρx"‘°W"μ@$αJ,ͺT*υφ–όΰι§ϋlΪ΄©R©HW}ll¬¦|:11ρκ«―rY)mΛd2GληΞΪ«’΅Δβ= ™§~ψΓξΪ΅ι©§FœΞYv«‹Εβ±cΗμΕw–²$Ν1λ1 ²kΕβ*₯Y\\ŒD"@ ‘¨<χ\κoώ¦«ΏΏΏΏΏί~ uWWσ]©Tb5‹K“Q]Ά*²QΖy?ZkTHΊκͺ|>ΟΑέζΝΎΡΡόG>23=h4ΚευzyΥ<{Σ¨ϋcΑkA9‚jY ϊ,Λςz½½½ε§žΚœuVŒC=φΔ].W:– φꀐΑ"»άhΔνvojΈ+κ—ΡZ @‘P°OΘtttμάΩϋΚ+…‘‘€H$"i(™L ϋbφΕΕΕΕΕEϋΊΠ₯₯%φχ–:;Ÿ~:Ιή”& Κ„΄§ 7yα«\Ά  \.OMMΩοuΣΈ4p°=Y,ˊΗγ7έt€{ξΉ€ΛεZXX`wϋέw3—^zζΑƒT#kρLsŒ.333θ΄ΣNγ‹ rik"Qω―ZΌπΒ-Lm.—ΛλυRb·ΫmΏΫ‡άcΥpΕƒπαpψΘ‘# Ρ‚©Πή…‚­ώχ?τqΰόσ;fgεr™/:ΉΨγ5*e; θθXψυ―φξέΐνv³6ΑΨcW„zΩcR©T’M^ΘoΏX£qi­T(₯¦0)MMM½υΦ[»vmzι₯Ωpψ½«<d1^ž*W¨2'NNNr¨—^x!χπTc3 Τ?y½‡ύ~4εΓαpwww£‡¨šκ‡9–e σBχέwͺΣΙr·Ύ‘‘.cίέ»wΐ6ΑƒL&Γb¦L’Ϊ+Κεr}}ΦΛ/—Ο=·o§¬Lv2r‘²ςPœ ‡Γ±XŒ1›ΝΞΝΝ5d-±&''9ˆ#Ιd2i[ΆΌmΫ6–e{ξ–ηž;μχ/0κpX—―"ϋ3€υυYΏωMαβ‹·₯₯%_–]uξΜ% .Zΐοχσt+8)•Y–U,ee󯬧ΰμ5ίΨΠ+ά—ΣόbɌŠίοΰŽ=ϊΖo( KKKbΜόό|ΝήoΌqΦYύΟ??ΦΩY000ΰp8²ΩμζΝΞ'ŸΗ<«――OŒikk«ΉΓεrΙ .fΰυziU$qΉ\ `<ΗΣ4α -’ aΣ+‘H0ρ―ΞXΕ‹ ΩvΉ\\0ΓRV[[ΫόΟ‘Ώϋ»ΎR)2::šHT^x!·gO/ίΒ™cφΐ˜+»»»™νOΰ‘7 “œd€Πτ’α­X,Ž7X­R •? ςz½Ό•B0Ω/b–Ιζ={ϋΏ~μcH$*Ώώυž= cccN§³­­=*ϋ<aΧJΛ‹EΉΥGΉ\nkkct€mφαjsΠ$'‹°σΞ;·—Ιd2›6m’qΰ#I’Ρ( ΫΏ?βάs7‘Ί ŠsˆμυφφJ9”JΝΟΟ³ά`Ώe²=ς»B¦R©™™™¦ WhˆUCM‡Ω‰–;ŽφφφΪ;ΤηŸί^.#‘ψΛmj.v`‡‰wv`γ">f:VDΫΫΫe!SŠrΥa3Ρ’b Λ cΕλΨ±cr#:aYVΉœG5 π>Ά&'''&&€Φ•H$ŠΕ’, δGFFh•ύ>[2Hl&Z],‘Ζ°p8Μς»P|Qξ B’Ρ¨ύBF2Σΐλε™ωy˜ wΰ5^lΏώϊλΝ”Ρ ε†“EVςώ‚Α ύΞFK–d‘Až‰"GψγΘήyηϋmX‹Οησrη#ϋ•gMCSύ—˜@R$”j˜nY–ίοηŒΊDΙX‰H$ŒdμΌspΐωfΖBΗ“N§§¦¦š,b5Υc1¬ΏΏŸ½%yΌ/€`0θχϋ=ΏΕ(%o”KΎd½2Η322dVAϋX+§¦φαXn""Y²X,=zTʚfΖΉL&cYμœυυυΩ»ΪL¨X' “8488Θ‰DΨρ:|ψpOOO©T’e ςΌ'nΆ··ΗͺχΎ9tθPσ…+h*άόώχΏΏNηΪ\¬ξΟ­K1‚Š₯‘~;οίϋήχΨ8σΜ3,--qσπαΓlΌτK2™ 7].o½υ»vνπ‡?όaΞX±‘K1‚Š₯aγG…ĝwήyάμννe£X,²ρ§?ύ ΐθθ(7½^/¬W …šΧ9“ΰ“Ÿό$€_όβܜeγώϋο_«“otT¨Τ*–b„υήxγlΘ ΜφνΫΜΝΝqσ•W^aγΨ±cl”Λeϋ$,3χI”œ855ΕǏ›6mβζΜΜ̚ώ(Κ‰Πˆ₯Αxηύ¦›n‚m†ρ ΐ;οΌco$“I98Nη_IŸΛεΨφΧΫΪΪؐB—„.ŽφξέΛ͟ώτ§lHοώŽ;ξXαOΡΚhη]©#T,Εkάyζ7ΏΙΖΦ­[ΩψΠ‡>[βϋΙO~Β†€6"ΑΆ½½J₯bo„B!nΚΤΝββ"l«―$uΚG066Ζ͝;w²ρη?ωT~Fe%hΔRŒ b)F8ΥQ!V2Φ“ΧΛ/ΏΜΖλ―Ώ[ž’'―,,,ΐ–Ή|ΛF…2Γ#e-ŸΟ[κ”ernΜ’RΗΊΰ‚ Ψxϊι§ν‡βz@εύΠQ‘RG¨XŠV9*|φΩgΩ`=σ7ήΰ&W"ƒlψύ~₯R‰›ΚˆD"φΚώ‡ IšLX5;B:fƒ)ΆI¦W™’Rκππ0GŽYΡO«œ<±#¬2bI)θΰΑƒ°υ¬%ZHx˜ŸŸFΉ){J a¬’‰‰L23Γ '!-›ΝΪ?kω'֌dσΕ_dcχξέlΘ…=ʚ£K1‚Š₯a•©πχΏύ{οw»ε+–υΈQ­*Ιr+Ars‹R°uΖe΅.5λη‘e4 ©P¬lI=tθRΠκμμpωε—sσ‘GYρ/@ω4b)FP±#¬2ΚΰŽ Ιk’ΡdІ™NvLΗϊͺΩSΖ€²ΠO†{άAf€ΑAŸ %WΚg±$&”œ(Ε6wυΚ’K1Β*#–όχ³‹-QD"–D ~KϊςΛ λ5Θ벫fO92 -ηPӝ—A€„@ΉD‘%x‰Χ\s wί}χ‰~xehΔRŒ b)FXe*”τΑ"SMOΆΔΔΎ³€'Ι•2-Ν†Œ€―-“Κ<ΈτΝε#x(ɘ²KŽΐ†τεευšΧΘ’ε^xα$ Κϋ’K1‚Š₯a•©P² ‹@’ι$οΤ,1‘œ$΅1i./_Iƒ;Θ¬‘μΙRΩςΚ™ <™d—εx‹ΆOϊΣά”ϊΦη?y6zθ‘ώB”4b)FP±#¬2JΊaŽ“Ό&uQفߒ)Kδ6hLš’ΡjŽŒeΓFفo‘O”‘)—Κ[j¦†`ΛͺLŽ’·lΩΒ―tUNXŠV±δΏŸhωόŒ\ΘYg 'RΎzΏ»ΚΤ,ΓB΅@%W,ΚΈ§6™Ϋ‘'0 -ΤΜ/ΙΉΎπ…/°!Ο»ώϊλάyη'ϊ](ΗC#–bK1Β*S‘$&Φ„R©Τ{‡ϋλ~1ͺ=hΙG‚τ♏€ %yJ^aχ_R€€Baωr‰šΩ€ε9Wϊϋόy£δD™δωνoϋΑΏ εxhΔRŒ b)FXe*œžžf£fL' …₯.Ε»ΗΘΙh²T©jy‘IŽΐoΙ<Œδ>¦<ΙkR*“WjΞMŽ, ¦i9 Yθ'ϋ/ύy€€\ςΚcΚ)IΎgƒO΄“Ρ’²r4b)F8Υ)ώχΛϊς™d±‰X5IRΗb’·Lέε Ό$P±QΣ…Η²eΠ²ƒ|„|@žWύΨcΩ?ΊEž< PκˆUF¬5?₯žYEΔREQEQEQEQEQEQEQEQEQEQEYώ9¬–^› DΌIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/baseroi/roi_getarrayregion_inverty.png000066400000000000000000000206201421045507400306410ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ IDATxœνmp[g™χ/ιθΥΦ«-Ω²ΨMμ–¦₯i²i¦0€™n™ΆΜ²»,έ†…i ¦₯€tςa†>3ΙPš)l‘-Πτh>u†.3μΞ²°Ϋέl’f›4δΥ‰;Φ›mΙ’¬·£σ|ψG§r›:Šo[GΊ~2·lιθΨώεΊξϋΊ―sD$‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ tΆΥ>NΔ0 l6ωύ WQgpppllŒυjK«}ΊρΖ‰hnnŽˆ*•Κ*Ÿ“b$+„c-·άrώόy"rΉ\D477ΧΫΫKDgϞmΧl(kωaŸ»ξ:" ǎ&’\.GDDd·ΫWο•#b-μRήμ쬦i@`aaŸγv»Ν/Y³fam΄D¬eJέtΣMεr™ˆG‘PΠ4Ÿ022BDv»]Σ΄b±h³ΩΊΪR) b5‡¨p8LDkΦ¬!’ .Q__Υ #"―ΧKDιtšˆΑαhΫίΫώ`κ`ŸnΈαxγχϋ‰ኈFFFςωΌy …• ΝfΓkσω|2™$’ΑΑΑkΉ¦-³‘ˆ΅TΨ§‘‘!€Ή|>Ο_χω|Dδt:‘υTγϊύ~Ά­――OΣ4Γ0œNηΚύ +ˆˆυΑΐŒυλΧQ©T""σό‰ˆΌ^/"Πθθ¨έnGΈ:wξ ΰ%‘H„ŸαΫ•v^ρ^%\(D"ΩlΦοχG"‘B‘€Τ‰Dμv;ϋΡΣΣ“Λε0+7nΤu1)•J†‘λz"‘ΠuŸ“ΝfϋϊϊΪ― /«ώ‘ΧλΝεrζ S±X€z©“ˆΚε²aέέέDT©TœN'ްqγF£'"¬{zz Γ€|Ραp βαόό<aOϊΰΑƒm΄:%b±O]]]DT*•Nž6T­ϊΰζI˜MNwίέχΦ[ρM›ϊΰΫν.•J…BΑλυr Τf³A>„4,<9^†B!δbΓ0\.W;UΪ?b1O=υΤΰΰΰž={π0ŸΟξ5¨ΥΑΓύϋO}β" Q}ˆ<?αβEγSŸ>|8‡Ρh4ςόΜγρx½^θ‹”κt:ΝUΦX,ζρxPϊ’z~lθΦ²4±Š‚Α ₯ΣiLή‰hjjj`` C™™"r:ϋχŸΪΊ5βvkT_άq,ρx<Εb±V«ΩlΆρρ…­[Lέ~ϋ‡ΈCaxxY@\ž%“IŸΟ‡·s8τξFšP(ΤΝ±ϊϋϋΡcψ!v]l6ہgώς/―©TΊ±ρ‡¨γ©ΓΟΗ„,·έz«λ?ώγT‘Pp»έΧ\s ωύ~Ηγv»a*Ή\.—Λen%­V«ΥjΥ|2mΦ©άV?ΜςK/ωύώ]»v麎Š%οε½υΦΉ›oŒΉ\™ΛεB‘‚J±X }}}~Ώu„ώώ~’Ψ'>Ρ?3ID†a4¬‡ΣιD5Ώ§§ΗνvW*žα…Γαp8Μ~£±Ώ‡ #R!Υ³!JTίN1wG…B‘ϋ·C[ΆD βΥχdˆˆEXKB—B‘J9ΆmλώΓί}χFΜΑ±-‹Επ|»έήPb z-ήγρd³Yσ΄½··· ²a§ˆΕ˜+D”Οηρ>xpόSŸΪX«eύ~Κd2ΕbΡγρ†Α%ƒήήήt:.+ξζΛησpbnΞΆ}»?σΘΗ>Άϋ6š¦U«Utσ±Α0²!€ρ—ˆ™§\Φ₯³R‘ΛεϊώχΏOD<π@±X„ηΜ™Ω[nι}η)~f45‡¨ωωyLΙHζζ榦¦P5pΉ\>Ÿ/‰θzτοώnδυΧΟ‘λζ%^.—Σu½V«™/ηZ±X,—Λ΄|>ί 7ά`υ‚V‰e³ΩΟ?οrΉΜ}vsssǎMθC³gηˆ(“Ιψύ~t"D£Qδ5ώ“{½^―Χ‹Υ%p»έ¨.œυ-"šŸŸw8ζΦΏJ<Δn#jξˆ[š¦υυυ™'υΦ₯ƒΔbžώyΗƒ•ΪΕ‹Ελ―wkZooo/ W*•²Ω,fBψ3Ηb±h4ΪΣΣCD•JEΣ΄ααaμaφ aX2©mίξίM’β…yΊλ@/'"»έn–ςq…¬Z­βRKΣYbΩlΆΣ§O£ώΘ#Όύφ…;ο³Ϋ{oœNg,3OΒP·δIOWWW(Β¬ .έέέ΅Z %P―Χ›Ιd …B"aΏγŽπ‘#ιΙΙΙΙΙIž†ΟΝΝ₯ΣιT*ED‡£R©`’Ž]”38\KgΛΌƒZ­φπΓχΆΫΦΟΟkTwί4 T.—KΣ4]Χγρx­VC `rƝ ™L30§Σιυz+•J2Iχά³ξΜ™sλΧ{/\Έ0<< ωˆhhh¨aΛy``σ­sηΞAkΏίoNΈVΔΪkΪ&0 cΛ–-ώο‡_|ρ%ε}ϋφ!e2—ΛeξωtΉ\ύύύ(Ž›σ&υΥj5 •ΛeXb·ΫΉ:Ιάnw4ͺΏφΪΤΐ!iš_^(`'.ώρz½gΟžΕ•<¨βΦj΅‹/Z·θΠqΛf³FψŽ;nΪΆν”άn·W*ŸΟW.—±‘‰D’Ι$½Βα°9?’vΐeˆ.΄8sω|>Kυ€Δuφ‘‘‘ώώ~srΉ\œΰ*•J©TZXXΰBΤψψψψψ8OΒJ₯’ΉΝ+ 9ŽR©tκTξο~ύωσUl<σΑQ€eG#‘Hoo/­8~όΈu[¬zήWΔb«¨^–ŒΗγχέw₯R©\.Η­ZT/bρŸΆ««ΛοχσΟλυV«Υb±ˆή„ΎΎΎΑΑA³μˆF£§OηοΌ3ςΦ[qT°ό~ΨΨ¦YΕb±»»ΫœYξ[o½ΥŠ­φO…οi²!΄A‘“Ώuόψq|ϋ9΅ZΝΌ$œmψ3»έn³@ˆ|°-›Ν.,,ƒAžrMOΣw„ίz+~Λ-—.fŒΗγά§U­V1ζ4šΛ嬘©ν#Φ{ZΕΔb±ίώφ·†«!βρΈΉ5žκ οΌ],ΉPn³ΩOπΊ²ˆΘιtβ‘tΪyΗαύϋ/x½^^κΊ>5555ui+Dσ ΐr΄³X—·Κf³8p€ˆ‚Αΰc=Fυ .zzzΦ¬YΓΣ ‡Γ‰D8I šSžΟη³Ϋνx!€ ƒΨ{ΖΊΊΊΜ‹>4ŽŽ/|τ£žΧ_?£λϊάάί`’ˆμv»9‹Εt:‚ΦrώjΤΣΆ©πςV1Έh‡ˆ¦§§Ιt«b±888X­V..%’l6›L&Χ­[GυY9nq™ͺV«α^£D€λz‘P˜ŸŸ―Υj|)N&γ½σΞΰ‘CΣccέT/ΊrJ-•J/^$’‘‘!¨Ή,Ώ“•€=#Φ­""—ΛυΝo~szzzηΝψJ±X4wb™γ NNNβ+Υj;3Ωl[θ«1?ΏαςT»έξρxΠζ01QώψΗ{NΚσ οeNΗ ω’ #Φ­Βλ2lΡ€R)ŸΟW(ΐΦ­[‡2fWlLWWWCΧ”ίοGΫBooo__bU>Ÿ―Υjv»pλ_>Ÿ‡m‰„γγοyγρM›ϊ Γ@€Τu½R©„ΓaΎ2•Jωύ~k΄Ϊ-b-έ*& ςΖ_ΜΜLΓΥ56› ;~ΏΏ«« i b!ΖπllμΔ‰'NœΐCΜ¨ΈΞ^*•8ώqεύάΉ]wEqΝ~45/ ΠXqϊτiσΥAV‘­ΔjΒ*π½ο}/ŸΟοΪ΅‹ˆΦ]KD±XΜΌ‘G¦ξdͺ gϊ ζηη‹Ε"&έ·ήz+={φμΩ³ζ—γ%£οtdd­ ™Œχov-_ϋΪπ^DδχϋΉIΠ*΄XΝYe³ΩPΈˆ(@ U€­h4κχϋ»»»Νwv4ε‰θόωσηϟΘzσσσ|(MΣΜχœŸŸΗr!™ΤώζoΦόΛΏ±Ϋν@€?…oBID?ψΑ,΄6l±šŽU@Χυώπ‡QΠΜΉΈ Oυϊg2™δ»Hvuu₯Σisk<ΆtB%«FŠΔέˆ(ŸΟ»\LΖ{睑Ç6›Νf³™χ”|πΑ‡~ΈΙŸm•°Μdπ2\₯U<ίΉsg0ά»w/Υ§PΥjΥ¬Ί@aX0τx<θ5…R===Ί£Ν!‡?™Κ’ͺnΎETόΫΣΣS,k΅©ώοΚm·ιΊώΥ―~³+Όο /Ό@DG΅Δής«Β«΄ŠκkΓ±±1ξζΠ…‚ί•Τλυβή2έέέζ 5oKG£Qξ\θκκJ₯RΈ :\.—έn7Χΐ0F,ŒΗmϋφύγ‘COΣ?ύ|ρίψΥ?CΕJ˜θ{rυVˆ•ΙdξΏ~"zυΥWWcΨ›j΅Šϋyΰ»p «ΌΉΉΉ‘‘‘γǏcΞ„@…'C2―Χ[*•PΠΗwΉFD‡γ‘‡’Ί:έέσχίτ† !~r___2™ΔεC–ΠΛΒ+¦±±e°ŠκA‹K‘P! y<žΩΩΩB‘°vνZήΘΣ4­α~Cv»―ζ.?dΓ………P(”H$xˆ?ϊ‹“μŽ;j΅Z&“ ƒ†a<όσDτΚ+=\Ψ²e8˜œρΪZ«Š•NΣ΅ΧR½ή΄<8Nσεν˜Ωp‚L₯Rεr™“ΪΔΔΔππ03115Q…‡=άhU,qJ\£ΗqoΎΞΛ/ΏΜ7p#’“'η·oχ80υя^S«Υ –%b°Μ‰šQa>œˆΎς•―Ψνφ'Ÿ|’ ‘Α`½Β°Ιξwή‘ϊέ° #Κͺ>ŸOΣ4Ά;NΎ©fQŸόηΙτωϋφν;yς$Υwm6›ω£ι£D›‰¬wΉŽυ"– «¨ž ω"zwΚλξξ6—ΐΠАΉ¦€•ω¦~π©T*U*•/}ιKDdχ³οή½{tt΄α40£β‹m6[,F‡YΜ*²œXЬ2Ӑ¨>Λ6_Π\­V±LλκκŠΗγσσσ•*¨Ζ{DDtο½χšσŸœ;ϋNŸ>=44΄vνΪ\.W.—ΡΡ@οNyΣΣ΄y3]ΌH¦Βͺ°ΥV†–¨πκ«―RύήTHΠ—ΦΰλΨ{φx<Ψ½¦ΊXεαvHDτΜ3ϐιΞ‘†aΰΙΘΏG₯ΛN‘b1:tΘJnY&b­@¬B6\·nΉs~Η333ƒ+jW8Kbθ—ΏόeΤP)ύΥ―~EDsss‡£Z­ς²RςEc8…²\ά²†X+`•™}ϋφ}φ³Ÿmθ-ΆΫνœͺ¨^ββKοxΰlΒπFΝ·Ώύm"B$’@ €f&“αŽδ+š’[Λ- ˆ΅ΒVaqvμΨρӟώ΄··«6ά4‹‘ω&">ϊ¨ωΖ‘ΏψΕ/ˆ(ŸΟχττ Ηλυ"Όq;MΣK< ΉΥκb­°U|γn^όσ·œN§ΣιΜεrψ\ΈΗ{ ! λΔ§Ÿ~Σy€oρΡ™3gh™ͺPVq«₯'ο+l0  'Ÿ|’ˆΎσο@2LΌ|πAͺΧ*•Κή½{aŠ}}}Ωl­λΝ₯Ό₯Πϊsω֍X«bΥƒΦθθ('Έ|>΅―}ˆfffp/«o}λ[ζW!DanΎ~>”’“lύΈΥ’b­–U‹Aa“ωξwΏ‹S&“Ι¬_ΏΎ··χΝ7ίΔWV¦Jήϊn΅ι4­n#aO<ρΔO<†έ»wŒŒx<άvaγƍέέέ7ί|σΝ7ߌ«h.ΕYIb12-U[ˆ–›c΅H¬2 cοή½Ψο{ξΉη¨ώ‘]Wθtΰ«RWw#―5η[­Υšά"V’@­VΫ΄iΣ¦M›ˆθΔ‰Έ‘ˆ­Ξκ#‘)'Ά-±ZΚ*΄g­]»φž{ξ!’ψΗΈ)­v|z?Z3n­>«>―ZŒa›7oή³gΟ*N‘ˆ–o­©™nΤ*uVϋDqλΟ΄¦UΦEά"«ΤΠιn‰Uκθ\·Δ*Υt’[bΥΚΠYn‰U+I§Έ%V­<νο–X΅Z΄³[bΥκžn‰U­@»Ή%V΅νγ–XΥj΄ƒ[bUkbm·ΔͺVΖͺn‰U­υά«¬‚•ά«¬…5άJ&Ιτ)‘‚5hu·Δ*λΊn₯Rb•΅‰Εθπαe;Ϊ²]W˜N“₯.;1ŒV½zL¦νΦ₯ΏŸ¦§Wϋ$.ƒΈeEZέ* nY kXΔ-«`%«€ΈΥϊXΟ* n΅2V΅ ˆ[­‰΅­βV«ΡVq«uh«€ΈΥ ΄›U@άZ]ΪΣ* n­νl·Vžφ· ˆ[+I§XΔ­•‘³¬β–j:Ρ* n©£s­β– ϊϊ¨ώvŒΈ΅ΌˆUFάZ.ΔͺFΔ­«G¬zoΔ­«A¬ΊβVsˆUŒΈu₯ˆUKEάZ:bΥ•!n-±ͺΔ­Λ#V5Έυ~ˆUW‹Έ΅±jy·ΜˆUΛ‰ΈΔͺεGά«TΡΙn‰UjιL·Δͺ• Σά«VŽΞqK¬Zi:Α­h”‰Υ>‰€½έ«V“vuK¬Z}ΪΟ-±ͺUh'·ΔͺΦ’=ά«Z«»%V΅.ΦuK¬ju¬θ–Xe ¬ε–Xe%¬β–Xe=Zί-±Κͺ΄²[b•΅iM·Δͺv Υά«Ϊ‡ΦqK¬j7ZΑ-±ͺ=Y]·Δͺvf΅άŠD(™\…χVŽ•wK¬κV-±ͺ³X·ΔͺND΅[bUη’Ξ-±ͺΣQα–X%-·[b•πg–Λ-±Jhδκέ«„χζjά«„Λќ[b•πΑ\©[b•°T–ξ–X%\KqK¬šαςn‰UBσΌŸ[b•p΅,vK¬–³[½½”J­κΩνά«„ε'¦γΗWϋ$AAAAAAAAAAAAAAAAAAΪ[―1 cΩΟChelΆf<ΉbD¬Ž’Ή?·}ΩΟCHΔ!b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(Α±Ϊ'°lœ^―—ˆͺΥ*b)GDΑ`Π|@~ΎΣιΔ€“&VΓΖ“Νf1@J%Σ&+o q)υΊλΓ`bbbI?­pεHΔ”ΠdΔβRΠψψ8™fΦ-8χ9<|ωε——ό >‰X‚D,A M¦B^άaΐy3oΡ Σρ8Σ‘ΎEυμΙk@nτγεžΐ» <ΐ’‰œ+ω½PγrNδbϊΈ?BXF$b Jh2bρ~L±9ŠpΔβ„oρ\~qa½ώ:7`5<“…oBσ94Lηyΐ!/QD žcηC=„Α3ΟPdj˜‰“)1aξΜι‰s%oKcΐ«žkσ¦2Ξss~ Š3&χcρ0ΰΉ<½αΖ5ά²ό»ίύξ  Βϋ"KP‚ˆ%(‘ΙTΘΩE ΞtœwZ x)ΗE#NmHš‹ΛW<ΐx׈Ÿ‰RΩβΚ/<‘d―ωp‹ΆΟ|ζ3xΘυ­/|α όδ'?Yβ/Dh@"– KPB“©Σ rη5‹ςπ-N‘άzΐ·ACδŒΦpdZ΄lδ'ΰ%όŽΌ2Ek!Ώ€akˆLYΙ‘3ΰΊuλ0ΐ•ΒΥ KPB“‹χ#-ޟα«±λΜα„ΛWοwW™†6,ͺ¨ψŠE>žΙχvψ/ φ—ψŽ\_όβ1ΰΟΫΉs'=υΤS—ϋ]ο…D,A "– „&S!'&Τ„fgg/ξέσbͺΟ 91<‹G>β*η)ώ ¦œ"9β‹Ϋ%v“η\žογ-ψ…œy“ηόγ.„χB"– KPB“©0•JaΠ°¦γFaKαξ1άzΐ[‘ͺ™ψψοΓpξCΚγΌΖ₯2ώJΓΉρ‘y€4ΝδOέΉλ»0@^ζΟ©{α…ήϋΧ!,B"– KPΒΥήƒ΄aaΕ{; M 0?€Eι|sήκiHy|dΞΆΘ}άθΗΟ_Όϊό|Ι+ŽΙ§Δωzjj Ÿ>{F6IDAT|’―…₯#KPΒΥnιΰ?__όD&ξ3ζΓ«! ρ€λXˆ@<γζ­°ΈΑ‹ SxZΤΝOΰ·ΰΟΪΌύφΫΙτ±Σχή{/?ϋΩΟHΈ,±%ˆX‚šL…<ƒΖ€““8έΰ[œ+9O5ά—†λ[Ό₯Σpε*WΞΈ†άΗoΝΩΆαPΌΓΓ›HΌ)ΤπΡόΦό©7ήx#Υ?L[Β"KP‚ˆ%(‘ΙΨ±cΖ¨-ξΆγžΞ‰€7j8…!Uρ]h8W6|^―"ΉgoΝk:.€5ά™ϋϋΈeΉa³hρνO|ψ^υ+―Όb~λωδω‘…h2b-ϋy­LKAAAAAAAAAAaψΡ WοτdIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/baseroi/roi_getarrayregion_resize.png000066400000000000000000000145271421045507400304530ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ IDATxœνYlUΊ…—ηγγ1vl‡’0ίH1‰I pρΚ oœ8±š¦E3„ΔΞ @‹~!Z nρΠκ+‘n@jέζ>€@  ΔΔ‰“ΰ1ާΊ+ηOqNΗφ>΅«Ξϊ’]ημͺΪNVΦοο*B!„B!„B!„B!„B!„B1kJ’@lψλ_qς$vνŠz1‘(κΔƒξnτφ@K ΪΫ£HέέX·ξT{έ:twG:‘ 6o>­*"m‰Ω²y3~ϋΫ3|.m‰™s6UiKΜ„s«ŠH[β˜Žͺˆ΄%¦ΛτUE€-q~.TUDΪηbfͺ"–83³Q‘ΆD6³W‘ΆΔiζJUDΪΐ\«ŠH[…Ž Ui«pq§*"m"UE€­Β"?ͺ"V‘OUi+ωδ_U€½]]άW䃨TE€­d­ͺˆ΄•4Άl‰^UDΪJ[Ά`νΪ¨BΪJΎ©ŠH[ρΖOUi+ψ¬*"mΕUE€­8Ui+ΔKUDΪς8ͺŠH[ώ_UiΛGβ*"mωE2TE€-_H’ͺˆ΄=ΙS‘Ά’$©ͺ"V4$[UDΪΚ7… *"mεΒQιθΐ¦MQ"ρšͺˆ΄ε–ΒT‘Ά\±ukαͺŠH[sΟΦ­x≨αΦ\"U…‘Άζ©*ikΆHUgCښ9RΥΉ‘Άf‚T5€­ Cͺš>Φt‘ͺ.iλόHU3CΪ:RΥlΆΞŒT5{€­l€ͺΉBΪ:T5·H[€Tε†BΧ–TεŽΒΥ–TεšBΤ–T•:;±qcΤƒΘRU>)mmΫ&Uε›δkkΫ6¬Yυ ’$kKͺŠ–djKͺς€iKͺς‡δhKͺς$hKͺς“xkKͺς™ΈjKͺςŸψiKͺŠ q–T/β‘-©*Žψ-©*Ύψ«-©*ξψ¨-©*ψ₯-©*Iψ’-©*yD―-©*©¬__ŒθήRU²‰F[RU!@m•”δλ~χέ‡—^ΚΧΝD€όωΟΈχή™œX<ƒs>ό##θμœΙύDŒθμD?ώωΟόήuγFi+ΙD97”Ά’ΚϊυQW€­δe­!Œ΄•$|Q‘Ά’_ͺ"VάρQUDڊ/ώͺŠH[qΔwUi+^ΔCUDڊ qR‘Άό'~ͺ"–ΟΔUUDΪς“x«ŠH[Ύ‘UiΛ’£*"mω@TE€­hI¦ͺˆ΄IVΩ΄ Q’ΐHΎͺˆ΄•O’RUQχ6mΒΡ£θκŠδζΣ%‚¨‡paek_Ί:<ωdƒ‰ΰžβ ­ /^ΌbΕ /Ώό2€t: ΰΔ‰/^ ΰΐ455(//pπΰΑκκj•••ϊϋϋΤΦΦ8yς$€’’μ322b}.ΎψbcccJKK?~άzΦΦΦςŽσηΟπσΟ?Ϋ¨vοޝ%¬'ŸDmm4ͺΒΜš:;ΡΨ¨˜θŠ'ŸDMMdͺB„ŽE|φ­ –-[φΐψΰƒμΨ±@EE€©©) ,@&˜œœδŸtΪ£κE]ΰ‡~P__```ΐΎ ;܏?ώ ΉΉΐΔΔ2n·~:εΠΠ2ŽΥΨΨΘ―Μ±¨ͺίύ.Ig₯4Κ›§ry?΅ΥΣΣ³pαBϋφνPSSƒŒŒκκκ9r™ZrψπaγγγσζΝ0::ŠŒtΨ柌˜Τ"ΫΌΤε—_`Ι’%†‡‡‘ ˆ —K–,α)7•ΚO T…C‘‘˜8‡x’*DξXΔ[ίjii9zτ(2 2C=ŒŸΣ·ΖΗΗνΆλλλΛΚΚΠ·x"½Š!)9ƒ]oo/€††dbM‘y:m‰žwςδI6Ýα“ͺΰƒcωΦ¬yΡUΑΗ"ϊV?σ*¦δ,(Π3.ΉδǎΓ/+^Λ–-ΠΫΫΛς³{:/œ‰‰2z³¨E‹!“ΰ_{ν΅:„ŒΟ?Ÿ'²ΐΑ΄lαΒ…­­ύΐ ?ͺ‚?ŽEδ[3 ΅΅ΏΊ:6D=_ΰ‘c―|«ΌΌœυLΞΒθ,4|χέwΘ$FL§X;`Οt:ΝnτΪ»ΡΓΎώ{dR4zα-θ^ΜΓΉζ»]49ΦW/Ίθ’•+jk‹6onlmΝΗ_ΘτρΛ±ˆ|kš¬\9PU5΅ysCΤ9ή9ρΔ·ΚΛΛ™qBGΨΎκͺ«1$› "3Sd4•J!“]1λιι±Ξt&ΒUšpQŠΉ«²\κιλλγ|prrrεʁͺͺ ««ώΔ‰‡3ΕGΗ"ς­s°rε@uuΠΥUυ@Ί§ŽE"χ­²²2Z‹γΜoh?,21yβΎΕœ¬ΎΎž)OΧίYΗβR]§°?‹³HΦΐ˜‡ρ ¬Ε·΅ ΥΤww7LLŒ!cuΎα―cωVmmCΥΥAwχΌ¨rΌv,‘oUUUqrΗ΅aZ‡ώDΗ /φ1yšœœ€Η°ΆΞω gy°tιRd θ?ύτ“]–vΘ‹π<—…«?ώ1UY9±uλόͺͺ2dζ†,άϋ†οŽEδ[V―N§'·nυ1πεΗ"θκB{;Ί»σwΣ’’"ζO4’π=–£Βι}‹i“MχX―βnvΰWό“e0ξ„‘'qαSΛpy¬΅΅?• Άm[46v:Ω’cρβΎΗ"hjB{{ΤγΘ;«WWWΟ=Wυ@.€Ψ8a¦•7ί ‚€sπ#z?a6Ζ΄©ͺͺŠΛˆtΦ8 odΖ\ŠσG^–7eŸίόf€ͺ*ψӟ75‘··— WωΓ{+ό!NŽE Κ·V­¨ͺšΪΈ±6κ\01s,’7ί’ΡBθXt―––df‚l³ OΛYΊt)OδܐY#Ϊ[UU2™V8a οu^±βX:]όΤSΕΐ ϋ6ΌS™IZ oΔΟ±Hβ}λ±Ηϊι`ΛΧ§C,‹δΑ·†††θO΄ξ#εLŸ°LŊ<&~{τθQz †_ΡΓX£bώΔT‰SN^œ&΄bΕ±t7Ξ&R,˜3]£cvπΈ:I€oύϊΧGΣι`ϋφζ¨2+bμXΔ©o-X°€ΙΝ†ε₯π#4τͺπs§τΡΡQϊџ8CdΦ«8[€o1βΞ‡κ)- ^x‘b¦Εβ½mtt”ζΗlϋξ9*߈·c‘Δψ½jΣ¦ψΝs‰½cGΎ΅oί>n.`NC—ϊφΫo‘™Φ1%’9…,++γ‡t#rςή ΟMθΑχML[Ά4Ί}ŽΎeβΠΨϊϊϊρ-ڞo$Α±H¬}‹^υμ³>¦α3#!ŽEζά·ΚΛΛΉ!²šEƒΩΉs'2*z3-ZT?λUœλρωζπ.ϊ3­‡>TV¬];Œ…ŸCd ~ΠΎΈΈ8<ΩδΓ3DHŽc‘ΨωV[Ϋ`:tuΥE=9&QŽEζΠ·κκκ8­#t š Ÿ‡fΖCβ·L§‚ ΰl޲@Οz=‰ΣΙ§Ÿ.I§‹»ΊκŠ‹OM69Εcύ=ΌΒ‹ψ͌cπˆί6γŽ.92+mA°hΡ’G}ΐ›oΎ‰LθαΚ₯—^ŠLNΝuVψωO?ύΔEeζψŒ•T€½¦­m~ζ™2ΖPώΙ †χ2,RΝ—]vυΚέ8,m0‘Θ}ρZ„$-žΗΔΆΆΑΚΚ©gž)‹z πΡEηŠΩΗΔΙΙΙά-14~N―bŒcξ‡β₯8`vΟ'Γ|#±ŽE<τ­5kF°bs^’μXd6ΎU]]Ν)“žπ£`L€θ\₯ae}&&&˜ŠΡΜx‘‘‘‘Η?^ZZ΄qcmuυιέyαΒ&ΊTxk2ΫSSS,ŠZ.ΜtΑ7ξX€£ΝΝX·.βa<ώψρΚΚ©k"G^HΎc:ΦΊuΨΌωΞκοοηλ@i*$Ό…†NΖr½ŠΙΙI¦\όͺ――oυκ‘ΪΪΤK/-œš:ŒL‚aOz’½Β™œμŠ+@¦B[VVΖužΒn,ϊFA8ioGKK4Ύ΅zυ`eεΤK/΅Dpοˆ(Η"κ[eeeœ 2Ρ οΡ Οω ŒιTee%GGG[[ΟŸ?οΥW—ξΪυΎϊjdζ€4<¦h|ͺŒ—εΉΜ΄ΈΣ·hll€ρΎάτG«σr,’gίjm=^Q1υκ«Kσt?o(,Η"Σχ­±±±πΛΦΒo|δ+Έι΄–π{‹z{{ΗΖΖΪΫΗJKS›6Υ—”œ^-ήΏΏ~ <Λθ<=Ό―~ΖΤ­§§'όΔχαd½ŽΫ|SΘƒo΅·UVbσfί4δ”Bt,2ίͺ©© ?νNSa튏yqΆH;α·τ³§žP²aC:•šD¦:Ε©±ͺΞT)\Δ§K…=Œωm‰“ΑΖΖFZα}™ωF:qδ[?~,• 6lHΟρucEα:9―oΡ68ΧcΊC›a Šε(naψζ›oΌψbu*lΫ6Ι’dφ1σ=ޜZς“œχ…‹ψμΓ₯FΊTΨΫμψπρ2ϊ%ίέ~ί€?΄c‘9τ­υλ'S©`ΫΆΖσwM:…ξXδlΎuμΨ1Nεh*τf9\³c2ΔjΓ†ͺŠŠ©gŸ-kkkωΔ=}ˆ5*ζXα’OgvΕ-V„ΦΘ- LιΈ[λΠ‘CœrGCψ…%Ύ!Η:Ε,}«­m ’bjλΦΈ>?ηΘ±N“λ[ΓΓΓ̟hœšΡ-˜εdN«¨Ί»€IšΝψψ8W 9q£΅„'˜,zΡ~Xš’‡1£b"E›€5r+smm-οKΧά³g~ωͺpcύ‚ψΦOŒ€RAW—ΚDˆ+σ-K–, ׊ΌςJdζ‰4•ŽŽρTͺό(N0U’ΑσZιa<‘ΆG³ Ώ’“MήΒvœ"³ιͺ¦¦†FVΛxAžβr¬3@ίΞ³β³fΝp*lΩβϋ›±#Α£η:|#Φ}όρΞ;ξψ;€ηŸ7b¦uχέ[Z:ώŸάL2Dk‘Γ΅΄΄°"Εό‰~Γα§w8+€·Ωο2Af3;-%4{'Όχ‹k—άΏjΥ*=₯ΊΣιΑ3ϊΦν·\Z:ώο*cŠ iά7‚ hjjϊΗ?ώkp°zωςΘTŒ~ϋ’T*ψΛ_Β/熴ΪOss3λU<€Ιρ‘CΪLψμΙ‹γοή½W£Ωο΅ WΤψΐ*;|ύυΧr¬8ρα‡Ώͺ©zτΡSΏa«³s"• ^x!NoƎΝ ΟΕ%—\ΑΏώυίΝΝ_,_ήίΤΤTQ1΅aCzώόS5$nHη,ξΠ’! „ift¦p.Ŋ|ψ Dζaα ^4'Άƒ ΰW<…Oξsšι™§o„_dψΰΰhF3 Ό …bΊxΉOS!„B!„B!„B!„B!„B!„B!„B!„B!„"qΜδU]9o$ 'O―t“° Š™ύsλ5uΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ ₯Q`ξimmeγ•W^‰v$…ŒK8AΒN(šΑ9AΝδD§,_ΎœΫn»½{χxξΉη"S"˜Ω?·K8!φŽ΅jΥ*6ξΌσN6Ύϊκ+68`xx˜‡oΏύvήG—δXΒ#$,ᄇΒΗ{ ΐ=χάΓΓ/Ύψ‚;v°155`rr’‡555lΌυΦ[ωgάQ(!a 'Δ/\Ή’ΫoΏΐΝ;yh'N°QYY>ρδΙ“lTUU±‘˜8 …GHXΒ ± …kΧeγζ›ofƒΣΐύϋχσ°€€„ ηƒAπp||œκκj6Όϋξ»n‡s …GΔΐ±:::άtΣM<΄2Υ—_~‰?•••±Q\|κK*•022ΒC³.~n=­Πυϊλ―»ϋ)β‹Kx„„%œΰo(dpγ7ψζ›oxΈoί>6Ž?ŽP ›7occcl”——‡/h!‚&+^ϋϋϋΩxύχητG‰7 …Β#$,αοBαŠ+ΨΈχή{ΩψτΣOμΩ³‡‡₯₯§ž,β-ΤΨζ…‰‰ 6XΩ²h'ΪΦ?MλN§Ω°πϊ·Ώύmξ~²Έ’P(PhcD³z•U›Έ›ΚΦm,γΆ%¦νvά₯žbΑ”kD-ςπ CCCY·.¨ϊ–B‘π K8!ί‘Πή sχέw³ρωηŸΨ΅k-0Yͺ‘‘‘8•΅‘ΟfpY+9U°ˆΝ+­η»…?a΄˜MLί|σΝιόΘqG‘PxDžΛ^ίxλ­·²aυυΧ_#΄lύ17GΖ’Μf¬μDg²β“eΦ-ΚlΖN΄4Ÿ¦hώdΡΡΡp;1λsσΞχή{oz ±DŽ% 9–p‚„%œΰjVhΡα»ξπν·ίςОˆ·ΞΪ,ΚXΐ²y’…ͺpd‚ EF X66†<»r]]ΆDΓKYθ΄³ ]ς²vzΩsAGeΓήOR‰K³Bα–pΒ‡B{Ζ†oECζAS‹€ΰ,’qΊg“; CY/eΘ]Ίaˆ΄@i11λDΫa!Οz2„YD³!Ω5ω‰Y›6ςsϋYli°C26A( ˜Η²₯ε[nΉ… {‚υ*Λs )kΧ”Ω†υ€ΑΨz‹΄²lΓ°+0[7;±=ΝfHΌ”9™%υΆtΓdάϊ[ϊΟ™‡νΆ;Ϊ²GkCυ{lδXΒ#$,α„Ω†Β|ΐύχίΟC[¨±Ζ d–A[DΛΪyle­¬ϊ2Θ2n»sκά%;1+YlΝΪa•³ΎΎΎ¬K±ƒΕJΫpΑΑ[.o1Ρ">o}μΨ1ςA#Δσ9…Bα–pΒ CαΣO?ΝφυΧ_Π{Aνυhw°rwd­«δξΆS²ζ}Yϋςrί!“5―΄2•Σά=ƒΔ‚¬Ν μΠb"oaο·±7ΆYΨeΟάΖoΌ˜ P(bγπαΓl0^Xfm‘ΛXΨδnjΘέΐυyΦ―|Ά†•xΣ¬­ΜΉ—ΚJφ‘σΞΘάlXθ΄ΐ—υxΔ’σ;οΌƒΈ‘δ]x„„%œ0ΓY!'ƒΘlg°(cοωψγΩ8rδBΣ₯σΞ›μI« 1’YXΙΪlύswg­YΟ¬_)υ«£ΓW°­YƒgΛ‚¬‘mνΚqŒ€³DŽ%œ0ΫΚ»(ς›š…B!„B!„B!„B!„B!„ώ=nR‘FΩφIIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/baseroi/roi_getarrayregion_rotate.png000066400000000000000000000152661421045507400304510ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœhIDATxœνkŒUΥΩΗΧ Γ\.’€ˆάt(b  P^‘^šψ‘ΡΎD?4ϊΑΔ ρEԚψ["*JΡJo±¦dΠΔΆΦΌ1­š&MJτ› Ze€†™9ΚUδ2gf€™χΓ‚Ώλ9ΜΩμsΦ9{οσ}Ϊ<ξΫ™ώ»Φs[kC!„B!„B!„B!„B!„B!„B!„B!„B!„B! g@±_ ˜|ς‰1Ζ|φY±ί#‰”ϋŠΖ¦MfαBSUe^}΅Ψ―BΓ¦MζΖΟ?φ΅EςT•…Ϊ"Ή’Ue‘ΆHx.¦* ΅EΒΠΏͺ,ΤΉ4‚¨ΚBm‘ W•…Ϊ"ΩΉTUY¨-αTe‘ΆHfrQ•…Ϊ".Ή«ΚBm‘oΙ—ͺ,Τ1&ίͺ²P[₯ŽUY¨­ΕŸͺ,ΤV)β[U–Η7Ώ½χ§¨PUY¨­R‘ͺ²P[Ι§πͺ²P[I¦Xͺ²P[Ι€Έͺ²P[I# ͺ²P[Ι!:ͺ²P[I jͺ²P[ρ&šͺ²P[’²bΏΐ%°i“yδ³yσ%_ΨΧΧηαu2ςͺ1ΝΖ$M_ee—¬“Ψ+΄ͺŒ1}}}W_}΅=^΄h쯞―ό 4Ζt:mp‰1fοή½φΰΔ‰0^~ωεφ ²²Ζ;οl»ζšΎ%Kͺ cMM=ψζ›o`:t¨=θξξ†qΐ€s»iΘΛ;;;υεW]u•=θι遱’’Β|ύυΧ0Κ[α‘ψEƘ‘#GΪƒcǎΑˆΏIsssaΕcο†\TU`Φ¬©ά½»μWΏκΞ~j’‰°b€*‹ΥΦ/~qΌΨ/RL*ŠύYΘ—ͺjkkνf:#f“¦¦&«ͺͺμΑΝ;a=z΄=6lŒεεηώoyφμYηΜΏέΤΦyρΕ“Λ—0ƜT΅~{pε•WΒΨΦΦf€ο‚ΠZΊS‡²—]v™ΎωΑƒq|ϊτi{0|ψp{°nέΰ{ξι|ώωc?ϋY΅Μ¨uuu9Fd.€7#^Γ3yςd{0~όxO:ew0"[!O–χ‡[9fΜε !ˆάˆ•˜±JΠPΩΦVώςΛ]ΩOM ΡV"Ue±ΪZ΅ͺTς[V‚Ueih¨lm-+mEΕΗς­ͺQ£FΩƒ#GŽΐˆͺ…,eΐ “gΒί‚ %‘F$’dώΦk―[΄¨gΥͺ•,Ή §…Œ”9­#Fΐˆά˜¬· P#½%ιΨ‘|$¨σΰž&T}P‰+ρc•€‘‘2•πΛ_¦³ŸgŠ/¬’R•Εjkως―³Ÿ[Š<LUθ ™”bΠ§`Δ1nά8=κόWΙΤ©SqŒiKv" "„9·‘‘ς'?1Ο=wτΩg‡ΙΤ‚|™ΒΐΌ&cǎ΅{φμqϊτιφΰ«―Ύ‚QΞΤhdΩ΄lΘt†ΜΛ„ ˜#V ŽU’΅k§R^xα›μ§Ζ’ «ΔUe±ΪZ±"}ΕUΦάή^‘’ταΠc#“σ A‘G,ŽUωλ_―θθ¨ϊιOχf?5&TXTU?Xm½ψβ©bΏH~(άTX\Ua*”έž²­@λλλaΔ\–1Ι.Φέε‡κκjύtΜJθΌψνoΝ#Τώζ7gV¬)'8€dΉHχζ2Ω‹FYcΜαΓ‡ν²νΞ[ΩM‚X«²~}]{{Ε3Ο.φ‹δJ!„EU]V[qΟoyU‚υλλφμ©xώωkΛ―U!Ά‡»c„γ%co8rI'²PK¦0ΰxI•^|,Ωέ€ED«W|ψα3Ο=χυΣOΧβώςžπ±dkΪ(dΆB>-¦€K€_(ύΉx±’£ͺ˜ςΪk5ννε+WΖ2Nτ%,ͺ*/Xm½όrόz½L…T¬Κ„8ΆTΐ bΔ %3 ˜ ε6 θΐτgΔL*' tνΙΔ=²δϋφνƒqΒ„ φι€wή1 €V­κYΊ΄FφVΰAςιxgω Ήίz"dŠ'ΛerΕlς?bEPUq§‘‘ͺ½½ό₯—βΤ˜gaQUž°Ϊzα…ΨΤͺσ),ͺΚ+ U{φ ˆ‹ΆςζcE\UX σπ3ΰlΙΠφiDBAόπ·d”ž±ƒžΉΈ—ΛKp,ΧM` N<πΐρ•+O­Xq~‘,(Α[’ο)³!πδ;#!},ωͺ!ΘψqU%‰uλ†vt |ζ™#ΩO-*yUU`b‘­\§ΒΈ¨ S€L(g\―‡κΛ/Ώ„s„L7 s!;Χΐϊ #f(42‘p—³2ο2‚$2ψωKυ‚©%Kφ-]Z#.—½r©~²LχgμXΜΈ‚289XqQU"‰x"Ό°¨ͺ’c΅΅lY{lΒ«ΊšͺŠ U{χVDP[a|¬Š “NΗLUˆΙeIή‰τ±ΰ…`»#ͺ7ˆΆR鍑&#„ςŽάωAΎτΫΰ’ΙDΖt²!ώsΩ}χ}φΩ£/½4 €τπ2^uζΜα#Κ’‘τόBfΔκμ4«V™wίΝεΉ$ŸΌρƈŽŽK—ζTέΛ/a„ΥΫkή{Ο¬YCmEˆ7ή±yσ eΛηΆILήŸnxο=cŒyχ]σ£ενmό©Pν1έΘ/S O-'#΄'Θ–@ddj›ΙξΜ;2‰€ΉRΆ' _ Sη¨ΘχΔ΄…™nΥͺ}3gNθνν3&δbF$dβC¦K€ΌCrΚcΕK[ΙζΣO·ίtΣΤξξ¨μF™k‚”ΪŠηUYΠ“—"4΅U\"¨*“―ξ†θk a6βy#ΌΩ €ͺΎ\wΧ*γGΉ€;ΧJUΰEΙ,.—ž(ώΘO³ΐ‹’ϋ)Ψ«ZZφOŸ>»ϋά«’εAφkHw ―'½=d[δξ˜ςo‚Ό΅ΝD_[Ι£₯eu׍9{6Zc•%Ÿ=οΤV!±ͺκξ.ΛmW_δω₯"«-|„B.ΈC˜-g=|τKNšˆνε£Μ,`.“gb.“ϊB?γφ“²ΉΣ–]ΩeΔή²δΟL8΅‰‰ζ掺:ΣΥuΒyOΉwςςΓ²‘χΟXq’ΫUd¬σΗoD)mŚζζŽλ―ΫΥΥ‘ύΤhΰ=T₯ΆrΗͺ*jΉυώ)D$ ΪB–AΆ9/`²Ÿf@p.§-\%“ΘΘά7Ξ”Ÿ~@Ο‚L‚#ŏ/n65₯¦OwϊtYyωkύ—λ3UΜ8ΚΔ:’hŽ»&(ΉmΕ‘¦¦ΤŒγ»»3$Ί"Nα²ΆΤΦ₯²}{jζΜρέέeΖPXύBmgϋφΤ¬Yγ»ΊβδWI ]g*–ΆΰRΘκ Š*2ΜFbβΔ‰0’Bnύ€y&VHΏ Υ!ω­r812…aƒόζζ}cΗV§ΣξβRΩ[―QΊS(Ɏ Y\š4i’=. Ά–ήXΖΟa§LŽ[ύΣάΌο»ί½2>šύΤSœΚ8΅u1¬ͺβ;‚’΅\X[ˆν³nΎ€Jž‰9BΛε^hα:ϊ d£Nΐ€ΉgΟαyσΎSYY&žlŒ˜Άδ<ŽΌIkk+ŒΘΧΛ†œυ0ΑΙ7AαA6ϊΙοY„ ˜½<·${φ―ΏlȐ؏U–"7‰Q[«ͺ2‘7Εο>€Ά ͺbΏH>)Ύ°LA΄…@]v’ό’ρs²‡‰ γΡ³ /‡7&χ>€g&ŸnӍ-Χ]7κΜ™sΖw₯Β+Ι«hϋ”ξTΖ­"e£όBΩ,Šw–‰ Y§ A$„eJuάjll™={RWW†mKγNT„eJO[ηU•¨DHXΖ§Ά1οθψΆ₯© λ1ΩΠ“¦μnΐ (ύ0λΙmŒpζ―––/ΏύλΚΚΚkj.˜^‘zOǭ䀆Ι RΧ\s Œ2‡Ž;ΘŽΈΏΌ•άq)Ρ–)q«₯εΛiΣFΧΦϊƒά…$rΒ2IΧ–UUWW™X]–@’(,“\mAUΕ~οDTX&ίΪ‚o!«7ˆΙ3ξP%Kύp>d'Ό1™D@ύD6XΟiΛ–– ζ]uΥΉπγ?ΆS¦LΑ™(ΪΘU¬pΒdo^O>©Ή ˜|g΄WȊ~ά4Kϊs!ˆ°L²Ζ­-[ZΎχ½IΓ‡'Ω―’DZX&)Ϊ²ͺJ§ΛD.3αD]X&OΪB\&—fΛ₯y˜dSζ™ζΖ=eζsώλ'gΞ—NŸ6YΈ—J₯`Δύεό…iQξ—„‡ΚΌϊe CfC2ώ€ϋεžΠς‚ΛΔyά:pΰδΔ‰ƒ««“ο­;ΔCX&žΪ²ͺκμ4bD+b#,7mAU₯Iœ„erΠŠ*?)}42ΘοM ψ#ύ\%+!Φwill­―qφl™=' €cŒ3fŒ=- ΘΘtž.έ)8a21„τ–dυŽcu¦QTώ:Ω­‚˜ ΛΔaάjll3gb:ύMφS“Kό„e’­­σͺ*9oέ!–Β29hKζΣ‘1—Α9ζ Ω_€¦ΉδφνΫνΎψbχάΉ“»»έ}°ύ€ό&%ςώυυυ0".;/—c‡œυ0«Κ™TNτ˜LεΧ40ι;VΏI8β*,½qΛͺŠc•%ΖΒ2QUεoa™hh‹ͺΔ^X&˜ΆrΛξψ.Ÿ@.— €—ωλ‘lέΪ6mΪιτIηώψL¦Ύ‘lO@Ί!cΗ„L"ΐ1Β& ι bι©\*w|@7©¬Ψ`mjΖ/±‡# Β2Ε·Άnm›={BŽ[’'’„ΛC[msζLΰ ˜‘δΛτ«-δ‘eΎσ…ΜMc^™w€Π}Χύ7ή8³³Χ\8az•Λ.Ρ–s j²₯.c:䔍„‚Lΰιr—οΔL-f––»rΫυ;QΒ2…·vνΪ?cƘžŽU%iΒ2ώ΅eUΥΩΡ―#E„dώmόi ͺΚσ}G2…e”ΆP`ΙXΐΏφΪkaDΝGϊ.Φ3ΫΊ55eJ]gη9η#γ>πdwœ9ΉjN˜|<<ιβΰrٚŠ“,Rι=·Μ…»0ΰ‘²„ζ ιʊV+,#΄•ΆnMΝ™3Ύ³σxφSI²…eΞkλΞ;sΟ«Š3`P.,sN[σ―=υΞ;c/^Œ„,ƒœΛ`Δ Α'žxz͚—z¨\ΈΝ¦9½bŠ‘ϋG"_ g%\.{ξ ‘™wΜͺ²l€ωKξοˆΩY.uΜψ!̌›PΘE%rƒΙ”Θ2·;·lω―»ξZβΚ'žxzυκηNŸΞ°}ι‡δX–––©Ζ˜»ξZkΜΏ ͺC D©˜ΧΦδΙέΧχ ηs¬Κ…’πFϋϊϊΠ_π‡?όpζΜOώφ·ϋŒ1―ΏώΊ5ΚJΎυ-::ŽύΰΣιsBΖςό™Yΐ΄de'ΘΪΎg!=δ€;…[ΙtΎ©Ωάά #\4ιWΙRdC€³ˆJ‘όϊξ°mΫΆŒ^Z”ˆυ-ΝΝSώσŸ9?ώρύœΣΡq¬Ύ~TEBPŠ»ώ΅eUΕΜBŽ”Š5nά8{`Γψ]»κϋϊϊώψΗ{ΰ‘Ζ˜3fΨΊqγ'sηN4¨|Π  JύΨyQnτƒ5zr.Γ ΉυNYLp“²%ωt9%![‘q; Ω!χoFC^…ϋΛοtΚz@JqΔ²47OΩ°‘vέΊo;B7nάtΗ7pΜ %ύGόχΏk ­7έqΗ¬tšy…όPž„Μbgβ=c^1ζ]cJoοŽ`„ˆ ‰1Ζδφ‘+B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„Dπ_uΚφI-’Šπέ/ +ρδς?qI“ψƒΒ"^ °ˆ(,β ‹xΒ"^ °ˆ(,β ‹xΒ"^ °ˆ(,β ‹xΒ"^ °ˆ(,β ‹xΒ"^ °ˆ(,β ‹xΒ"^¨(φ  XΌx±cY½zuQή$G8b/PXΔ ρ…EΌ@a/δ΄ΫLΆ"I .t,σζΝs,­­­ŽeŊίIΛΔ±ˆ(,β ‹xΒ"^ °ˆŽΗ{Μ±άrΛ-ŽeλΦ­Žeοή½ŽεΤ©SŽε­·ήΚων2ΐ¨D ‹xΒ"^ °ˆ(,βF…Ύxψα‡Λν·ίξXΎψβ ΗΤΤδXz{{ΛΩ³gː!CΛ›oΎπ=ϋQ!‰ρ…EΌ@a/PXΔ Œ σΓ£>κXζϟοXvμΨ‘Υ’N§KMMMΦ§www;–ΪΪZΗ"NdTH"…EΌ@a/PXΔ ρ£Β0<ωδ“ŽeξάΉŽEΧS©”c0`€cΡR]Τί?}ϊ΄c<λUz]‘FΧ5z'rΌϋξ»³ή$8±ˆ(,β ‹xΒ"^ °ˆJ=*\΄h‘cΡ½ Ÿ~ϊ©ciiiq,ξ_RWλτκ?θι: >GG…Ί_΄§§Η±8θ Aƒ²Ύ^p8b/PXΔ ρ…EΌ@a/”VTΈdΙΗ2uκTΗ²yσfΗ²~ΗRUUεXτ.1:RΣq’Ž%uμ¦Χ ‰@‡κXtί©C 58±ˆ(,β ‹xΒ"^H²σώΰƒ:–9sζ8–Ο>ϋΜ±θ–½ΊΊ:Ηd–vήυFzq•nτΣΊΎ³n!Τ–3gΞ8'яΞŽXΔ ρ…EΌ@a/PXΔ Ι‰ όqΗ2kΦ,Η’·oloow,:ζ §‚lΨ―[ντB ›>κ¨PίGΏaee₯cΡ¨σKu ™ ±ˆ(,β ‹xΒ"^ °ˆβ.\ΈΠ±θe[ŸώΉcΩ΅k—cΡmΣ1׈#‹nΗΣ•8'κst ¦[uT¨ΡwRψsžχ­ΜŽXΔ ρ…EΌ@a/PXΔ ρˆ /^μXnΊι&Η²sηNΗΪΪκXtM/œ5Ύcǎ9Ν‰έt…1Θ>Ί[5H,©+ž:Ϊuή9ΘOG,β ‹xΒ"^ °ˆ(,β…(F…χίΏcΉωζ›‹ϊ577;OιXIΘt7¦^¨ο£{A΅E?KGj:JΥο£―uI}•ΖΉ.›ζG,β ‹xΒ"^ °ˆ(,β…βG…=τcΉυΦ[‹ώŒ[*•r,Ί‚¦+qΊΣR3rδHΗδΓnϊιAΆωΧ΅Λ ±d€‡ˆRυn—Ήΐ‹xΒ"^ °ˆ(,β ‹x‘ΠQ‘ώŒΫόωσΛφνΫ‹ŽΓΕ\:Βq’ŽΉtf«tœ¨£K½·ŒΎO £ΎώνGŽq,Ξο 2‡#ρ…EΌ@a/PXΔ ρ‚ί¨Pο 3{φlΗ²mΫ6Η’{Au\$žΩοEŸ£‹hAΎ‘ ²pOΏξΥŸ~C9>ά±8έͺ'OžΜϊzΑαˆEΌ@a/PXΔ ρ…EΌη¨πή{ο•Τ½ :βλθθp,zmή§EGXA’B}½›¨ŽΛ† ζXt7¦ήσSΗnAΎ1‘οδ3ζϊΞz]‘9:~™‚Δ ‹xΒ"^ °ˆ(,β…œ’ΒeΛ–9–™3gΚ6559'θ¨PΧζ‚t~κ­Qt©KG|:z¨ŽΤ‚΄Vκϋθg‰έth¦γM]υΣ½CŽ·:ΏT%s#ρ…EΌ@a/PXΔ ρBφϊΪΕΠU-cΜ+―Ό"ωΡG9'θΥmuuuŽEGXAΦθ镆:šΣ=ιί₯ﬣ0ύ†ϊ>Ǐw,:ώΥΟ5Pέ=«ί'Θn3Nœ¨ΒϊυλƒT`3Β‹xΒ"^ °ˆ(,β ‹x!§Zα† Λ¨Q£δ?Gυ&Y£“©¦¦Ο B΅λ8(Θξ.ϊΞA*•AΎ`¨γDmΡŸ {ο8Ώ‚{’@a/PXΔ ρ…EΌSTψώϋο;– &ΘΝ;Χ9αƒ>p,t,ϊ«ρAΎΰ +qz= .‡‰έ‚μ$£ίG_₯k :– ²>1Θχ 5ϊw9ΡeΈ@ϋbpΔ"^ °ˆ(,β ‹xΒ"^Θ)*t*ƒF-ΤQΟm·έζX>όπCΗrθΠ!Η’γ m Τθ/έλ‹:.Σ±›ίιz’~–Žƒτ―jτυU:*ΜοgλέπwkRΚPXΔ ρ…EΌηε_$a„^ώE!„B!„B!„B!„B!„BJ7‘mή`ρΎIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/ellipseroi/000077500000000000000000000000001421045507400232035ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/ellipseroi/roi_getarrayregion.png000066400000000000000000000240751421045507400276140ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ IDATxœνytTUžΗoUR©$•}ί„„ME°iP*j(ΣΨ"x„VΠQl7΄±YŽzpE–6€]i:i2(› ²@φͺΚRIͺ揲Γγχ…TH½[Kςϋœ>sΈwn½χκεη«ίχύ–+Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Σ·Πτψ“6›MΕλ`<¦‡βλΜYϋυλ§>ώψγΚαͺU«”Γΐΐ@ςρζζζ.Ž&„(--UF£rMΦϋωω)‡εεεΚaPPY 644!!!Κakk«rθγγCΦ“S455‘δIIIΚ‘Εb!λ}}/ϊΥΥΥuq:Ό`r…QQQΚa}}½rHώF’§h{όI†ι6,F lXŒœς± ƒrH|&β@δη瓏λυzεπΔ‰dA\\œrͺj΅τΏŠŽŽŽ.>ŽŒΙdκϊ€ΑΑΑΚ!qqΒΒΒΘϊͺͺ*ευ 9 Ή€²²2²>&&F9$^N§#λΙMF·΅±±±‹!°Ηπ‹‘#6,F lXŒœrή{‘‘ΆΤΤΆ˜kP-8ΨfΏ11εAAV£ΡΗίίj6k+*ZL&Ι€il ΪϊϊΆΒBΥΌέ^†S†UQQ‘&$$(‡§OŸV‰ . ’Oqώόyε022²λλ9wξœrΨΦΦ¦†‡‡wώ;&¦cΠ Ά„„ΖŒ kzΊ5-­Γί_μΫηc±»έ˜LšϊzMc£Ι€3΅ƒ΅££=*J$'ΫBB„Ι$Ǝ΅€€XŠŠόŠŠό ύŠŠό΄>|α–’H~£ΤΤTε°ώd½ΩlVΙ‹x|³OŽ€:—(ρψψψΧχ˜>τΔλm7ίάrσΝ-cΖ΄”•ωϊψΨςς¬Ώό’ΝΜτ-(π©ͺΰہψψ‹^(œ9sQLΙ.ζSR,))–ΤTΛM75MœΨ|γm»vιvνςΫ΅Kwς$5¬ΎCο7¬λλΈσΎqγΪGŒ0εδψηδψϞUP )λφ'֎BQ[[«ΥбcΫƎ΅¬ZeJLlά³GŸ­ίΆ-ΐhμyΌί顆n»~Λ΄imǎωh-?εψcNc΅Šμl]vΆNλ;fLλ 7X/nΨ³GΏysΰ–-.Έ ͺΒ»ξ²lΨ`ϊι§ϊΈ8ΫΓ̜ιΏl™>7—f"Έ€Ί:ν—_Όόrθ AρYYΣ§7•–ΆώωΟν½?γΘ©'Vll¬rXSS£’ όέ!Ξ>ωΈwž8γΘ3Ϙά’›λσα‡Ί)SτφKgάγJwώ’WΨ’’4αQFx²²4YY†„„°ϋοoϊόσ¦C‡t_~°gΟE1%L~"y/ΔΧF§$φΰ’WC‚Z=ΞΎBzΙk̚όό“ΎΎΆk―5Lπν·ϊ_S£]³&hμؘυλƒfΜhϊβ‹³·άμψc^ˆΧ–έ€t:1thϊŸ¬olτωΘέτι D>τρΛ/Οήzko3//6¬iΣκ;MjεΚH«Υέtεόό³ίŒ1/Ώ9mšqΛ–Κ#ZΖKπПŒΉζšΦύίσYYaC‡¦{£=Žρ›93fΨ0ΛβΕ΅ωω~/Ώμΰ=°Wΰ}O¬W_­YΌΈfΜΨχίοVΥΙΡ£~“'Ηηηϋεε•L˜@_©{NUιUψK/)‡ωΛ_”Cr ΐ€ΪΪZεπφΫΝ+VΤΌρFΨΗ ˆNΘΌ#iw$<" KΘ%‘DB €Δ=r|A˜ΔΔDεπΜ™3Bˆ€ΫŠΥΎΎΆ?ύ)J«P.¨¬¬TQ&“Z ΌBRB‚`D§»§JΗ•,\Xί~Γ ‰f³χ=e―ˆζfΝμΩΡγΗ7σΝΩ5k΄ΫΆΡK<πθψ©1?._.„(;pΐ3mΛƒ kΐλΎ}¦˜˜GΩ1}šτI“JšFžxwι›oώZτμ™Ά₯¦σN^L“d&DΉΐί_μΪΥ6aΒΘττ ˆsM^4c…;Ι."o™±ΔžΘυ π…Iρ^zί‡oΖ ΤΉ4HDG4s¦βδΙΆ+|ήx#^ΡVVφγ[oMψγΟfΜP'ΩWβRίQ κ‘γO¬ΨXλαΓ '^νξ ρtŒ••ρΓ†ΩύέwΊΒBύκΥΕφaTFFma‘Ϋ πΓϊωη†k‘΅_ Rœ›ϋΝK/=Ύ}»}ψωη»wΟT ύύο# ΪΏn{/O‰ϋ 맟F…Ÿ)ζ”ξί―΄­Ν›#WΝ51yχ’%ξ½0‚› ++Λτ쳁……ξ·o/Bi[CϋΓε7€U)’'[]$''“υ$„rφμY²€|er…#"χ„\!‰Y‰WgΌφZγƍΖgŸMΔ9IΟ"ε'Ξΰ'ΦΫoΧ͚αxγί~’Պ[n19^* ΧΦψρ-Ψ΅‹Φl12xύυΨ… ισ•ΈΞ°–-k˜?Ÿ‚.βά9ί¬¬°Ω³k/•ƒ‹ λ±Ηj><ΰμYφΩ]ΗΚ•Ρ3gΦ…„ΈG!ΊΒ°‚ƒ­Ν†εΡ”–ϊh΅BvܐŸX}‘½{ύF’ϋΠRΣ°΄ZλUWU””τιJΌό:弓!ηJJt:ˆη‹•$u Δ['ΩQθμί“xλΨ’˜8㘰ENAJ!°$ŸΈσθŒ“=EΘqΗδ!)Ψρ‹|e”#φΐΪΙ“–y󊒣£‰ΎΑΏQQσ‰WQY™ΰxγnJKύ"": ‰n–š†YSSΣφ3ξ ϋ%'Kό5TΣ°""jjkΩ°Όƒ’}ώ_“²aυQd–SΞϋιΣ§;m0Ψτz‹ORgURΪ€Ύω‰'”CLo"χmƒόTβΊηwβ\“nX2ΐπŒςβ}ga‹μoˆ;”=A|y‡;£¨ΆγθQΛψρ-αα©Κ/ΖzŒjO¬ΤTk}=-a<–’"ί”‰Ω€*VG}}ŒγuŒgΰ5†e«ͺR­₯#“I“›«Œ”e[ͺVB‚U«u³^¦ϋ€₯΅ ΄@-T3¬  ›ΕΒU„ήDUϋ΅1ƒR―λͺm„ά\]έ’ά·μαŽυγD4?~œ, e3D³•Gϊoa?-’•šJο2Ω……δ?‘Μ$:ƒN$†CξnyBB.$Aδ“α%].νΞΕ‹‡O™’―?=ψΏ ƒž ?°aΓφ%KΔ₯diQσ‰ΥΪΚυ^ΐΦ―/ΙΝ}{τθ€½Ήί¬7ςΨ±ϋή~[έ³¨fXƒΝbaΓςtn|τΡγί}—Ώm[SMΩ¬1l••GΆn=—Ÿ?βT<V" <όΦηŸ?Όy³}h7,ϋΏs֝όΞ;ZHΗθ1όSΨ‡H;Ά@±£‘Ι€ ΊK8••ιΦφφΧ~xλΦ‹₯5ε›Π±Qχ=8N«Σ­Vm­ΖΗGV¨œ‘Ε’1™dνΜ­’aω„‡»mw<¦ ΪVY©š/DPΝ°ŠŠ|##k―c<ƒΪOŸ–eUBUΓς‰Œδ'–א’^T$Ρ°œ:΄2βQQ!όύ[ ΡΦφk‚ΈήθκΧσ₯HΘ‚X0`Bj`HŸf‡μXτB>Bάάq„€Ύ3)U"°’’"²ž$x‘˜Ÿxίψ:³β† «>{VK:Š%%©ΦwCΝΊΒΪΪȈώ5τ’“-ΕΕ“Ψ°ϊ(ύϋ·–”HL%WΧ°’""ͺU< #o2¬ͺͺx½ž_Ύ{z½υΜ}e₯Δ=iœrήΙ{d£qθ°aΫ˜b’t(²UŸo7Λ#Ύ'yοŒέrΙ)ˆοϋy5@ήƒ xN|y‡»ϋ9,ίθ,x·ƒ%ω]o6Ž Ίˆ ΑmfμχdτhSs³hoo'οξ±Ι@Qσ‰e2…utψ††ς―‘§σ›ί4ύτύΟR]TξAZ^ž–˜X ξ1Υρ>Γͺ¨HKH`Γςhτz[zzΛ/ΏΠw`κΒO¬>‡ WBuΓ2™Β­VŸv³<—λ―7οίO[‘©ŽSͺ$H••• !Lσυ=TV65Ααž(DW’*”™€Δήaˆh4ZD–b‚ω xF’γΘβ)D‡8P9Δτ)r|μm΅ZCBΜ™™ώφ’$rFά ΄Η¨ΏISQQΏλ―?’ϊaUHJ²ή|³εΰA‰αg;κVAΑ€ΔΔΚΐ@.σD&OnΝΜtES );¬>_›Νvύυ­?ώ¨—κάJ|s,D&%ρd1ΏŠΌ‹'.'ιJX«M @Š‹‹»Έή4n‘BvT$ŒoκIƒ+’p?ΔΛέ±‡ͺY½:Θh4’2R­Ž₯Υ=FβK±jUΰ“OR₯ΓΈ˜Ψ؎ѣ[·n•xV"Χ°ΆoΧ''w€§³οNζΜi\³ΖEb°Ή†%ψ‘εnόύm<`ώΰΧyWv€Φ–-ώιινύϋσn(ξαΙ'_ζς»ι†%„XΊ4hωrϊšqύϊ΅OœΨ΄~½λή2tβ”*$ωLDΒtΖφξΥ͜Ω2q’νϋο/Κ/#E8€•€*"‚ˆΒŽš;”₯˜―’x¬ΩwΨ4€θ>‡*’Δ|ˆˆΓt+’¬_½zρβ8Ltλ„άR RυW<±„/½Άx1§•Ί”ργ›-MNŽτdΡKβ"Γ:wΞηo ™;W΅²5Ζ!K–ΤΌϊj¬γurp‘a !V¬Ÿ9³!,Œ»ώΉ‚Ήs23ƒͺͺ\D\gXBˆηž‹yκ)ξ‘%ππŽQ£Z—/Ώμ.!.@M‹&ž&Ɏ2ί}§7Ξ:eJέ† A\]τ1IΎιE›—G“TI- PνF„'NœP±ί)iOOOW1}ΚaΣr Π‹ψζΒ‘\@ωbŸ~zξΩgΓ­V+i%@!%&&*‡ψzŒKŸXBˆωσΓηΞ5&%ρ»xYΌψbΓW_;ζζ ³\mXBˆiΣ’?ύ”n‡Δ¨Β7ΆŽΡκϊβΓ**ςύθ£ΰW_e…¨>Ÿ|r~ϊt ,]ƒ KρΑAύϋwάr ΗΥdύϊΊ'žˆlm•ΥΊύŠpa !~8κ₯—jCBψνƒ:<ϊ¨9?ίχ»οδΆϊθ>N©BR iqD‘βΈλθƒΟddόΊ%v #!r!ŠF@Ήž3gΐυD£‘!‡eBδ‚1q―sΗ‘K‚U=MŽν Xgγ»;ξ¨5JϋόσΙ­­έcβι‹LŽOR#ΑmO,!„Ω¬ωό޹“ξSΚ\W_έ4}zυσΟ«ΦvAάiXBˆ'tΛ–…|ψ!χZξ!‘‘ν+WO›F+"ݎ› K±c‡]ώK—Τ¦;lΫ–?a‚Κ[ ͺ‚ϋ KρΑ†;ό_y₯άρRFΑϋοέ~ϋ‘§œw’ΎCB.ΔΣΔδ!₯#ωÁÇΧ̚ujΡ’Λ¦yv“"Ξ/Q${L@6ρdΕε3Μμ`Ωq–±Œ§λ6́!_A©oώυ―‚[o¬ͺΊΘ‹ zSάH]Paa‘rˆΏzŒG<±μ,[xώΌvΕ “γ₯}Ϋ‘#'~χ»”ͺ*ϊσ<λΚV―8tΘχ½χ8ω²DE΅ηζž92½ΆΦMczŒg–β“OόΏωΖoσfΊ-/#„HNΆde_w]F[›'ϊUJά–Φ»Ύ±Q³}{ύ”)‘υυž~]ΖΤ©-wάΡ2fŒΗ½YΈ$NΙpκ:3?--|œδo)]Χ~π/-Υνί_7o^τ?όš§Eތγ{mςX)@δvπ"ώΎΓέύˆσ‹Ϋ5@RΠ°CX§wΏ|y½V+fώβΒ)p‡rΑ˜ΰEYίEΩΕ•βq?…ϊ\w]‡2>σLŸΞƒ ²ύσŸηςϋΣŸά™z₯xaΩ™93ΦbΡ|ϊ)ν;έG;ΆυΐΚΩ³#>ϋΜ₯œΗ},Β;ο„<ΨΌiΣΩ•+}ρ7'FΊ’W^1_}u{FFΌγ₯ž‡§?±μμέ0cFμτ閏?6ΡͺΞήΗν··ž8qΎΌ\;u*mνδ-xΑΛNs³φΑƒξΎΫrμXύ’%οΏοŠž'0ΠΆjU£ŸŸψˆͺ―Χ@­΅ΧΰΤ…Οž=[9œ3gŽrHb8˜nEΰžμDfΪγkֈϋοbήΌ’ΜΜΙ55‘]¬'šˆ”ΔΉ Θ"*ΔCHK@>κPr!-=}Η„ Ω[Άά}τhΖ½χ Κ—>‘κ$`‹|v=Ζ+‹Ψ±cb\\ωm·}ίήϋα‡q΅΅ήϊ{ΡΙ A‡oΈawQQτ‚ΟΈϋZΤΑ+ KQY™ΈeΛ† ;2uκ†ŠŠDο5―ΑƒυύΉs‰_=΅  χΌ φVΓ²sτθ5G^Σi^{φάζEζ5hΠ‘›nϊ²21+λαΪZ{’tοIxτnΓ²Σi^χίqMMτ‘##Žβξ‹Ί,ƒqΨ°cbΚ::|23«σˆj-Υιω³Χf³‘b„§Ÿ~Z9|ο½χ”CLΤ'βUW]E`:‘άtήl6ίv[λ4ΣΊiSΰφνI|Uά „8ט16`ΐ吸Ί!Ύ99γ=χ4Χ5^}uΗ§Ÿϊ}ς‰_q±655UΉ  €nNΫuŠ–Ψ“(Φψ“„°˜˜˜.ŽμΨ1‡ϊΰrτ†'–’;υ;wꃃmψCΣ₯%mmš£0TTΈνΝκΘ‘–I“š§L1ggϋ―[§ί±£·έσK;Ώ€Ρ¨yο=Γ_ 2€ω–[fΟ΄ZΕΏώ°wo`nn`CƒτL¦Œ Λ¨QM7ήhΊωζ֜Ώμl#βΝf Bχbz§au’——°zu\R’eΔˆΪ L‹Ÿ+/χέΆ-ΈͺΚχΨ±ŽS§tf³ Z,!‘#-­cȐ†λ―oΉι¦¦κjŸ½{?όΠπΘ#KοΡzέ§—V'ee~‘›7‡ !† i½φΪ–Ρ£›|°eΰΐ6£Q{κ”oAοώύuuΒd46 £Qšΰΰ³ΩΗjz½Ν`°††ΆΩ‚‚lAAV__[Zš%-­#-­=-­£Ύ^[PΰsκTΰ́―½USγ#.εΥυϊŠa)ΙΛΣηει7n ΅;οqqΆ§₯΅Ψ:`€-(Θ"‚ƒEp°-4τ”ΑΠqδHΐΰΑ-f³ΦhԘLφiKJ|EvΆί»οψ45i8ο}§ ‹θΈ Μ‡N>^SsΡkR1"`gJ²Q ξ½CTi›vΉυ₯₯’΄T›ν§Υ*»nΥι„]]]LH§v…JR±­2Y$d„U=€ˆh:,"< Ή$ϋ^φ`™PρŽμχšq #6,F N½yWρ:Ο€Ηoή†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a˜Ύ·1bΊΒ=x]σ€ηž{ŽΜΰVrψ n*^TTDfΘBˆκκj2³f͚n^§‡ΰΜ³ƒ;ϊ1R`Γb€ΐ†ΕH ‹‘‚SͺЍΞϋΒ… ΙLYY™Α]¨ύόθ`Έ5n.FfH#KGfp—<ά―π7ήpx.I8σ'ζ'#6,F lXŒΨ°)°a1RπUψΒ /2ƒ[¨‘ 3d&11‘Μ“™ΰΰ`2ΣΉ‘\'>>t;`²eœ"!!Μ ΊμΧ―™9}ϊ4™q™NdUΘxlXŒΨ°)°a1R`Γb€ΰ‰ͺπΕ_$3Έk7κ»ΨΨX2ƒq@²ύΈ€ύΙΕ₯v’Ηψ]Tέ–΅F1©o j[ΌBΤ›οΏΎ«BΖγ`Γb€ΐ†ΕH ‹‘#χ«Β7ί|“Μ`΄Ξb±ΜΕZ%Τ€xŒ9bΤO«₯ωαΉ0žh2™ž=::ΪαΩ«ͺͺΘLdd€Γ5λΦ­NΟΫΛγIDATΓͺρ8Ψ°)°a1R`Γb€ΐ†ΕHΑΥͺπΑ$3¨qPaaΌ Υ\kk+™Αh' €Μ477“T…x…ΏλΞLhh¨ΓsUTT™’όξί\²d‰ΈBX2#6,F lXŒΨ°)ΘU…σζΝΓO‘™ΆΆ62ƒ΅u¨z0:†Ή—Xύ‡ν“““Ι Φϊa³ΩLfRSSΙ ήμ‡(y°¨Iu:™ihh 3]Ŏ=V…ŒΗΑ†ΕH ‹‘#6,F Nm ΰT=­Γ,¨ρSΨ½σ31‡5)n’’βπ\¨ ρ\˜ŠGFν†* +(1؝ΪCο$ΒO,F lXŒΨ°)°a1R`Γb€ r¬πΡGU1;κμ-ƒΊ #h¨ΒpMMM ™Α(Ζ ƒ‚‚Θ *PόTwς[θNCGl΄κ©ŽΜ`0ƒηΒƐXΑEf08ƒ*Œd8ŠKΕ»0„3˜ΦΧΕ‡ HP}c¨ŠΜα{ΞΐO,F lXŒΨ°)°a1R`Γb€ΰ”*Δθt¨q0ˆmQυ`΄γ€₯₯₯d“ζ° ‹’POα5c„Ϋ["˜Tˆ‘AŒBžϋάμ{Ξ>χ»{­½φ>‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ |7ΨΘΘHR!""ΒxH:Q`Ÿ<οσH―™’’B*x |ι±-xΨcυ2’’:²³ΟΖΗ·₯₯AttgLLΧΙΙ%‘‘νgΞΗΖΆ;A55ν  MM••§OCaaΠ7ί³±–ΰ©aΝ™3ΗxψΠC‘ d„H4˜ξe€   γαΩ³gI—ΛΥC}HMM5žψ ©³xρbγ!„’‘/q³ω[ˆ'μΧΜš‰‰ν“&5Oœθ;Φ΅{wπΆmŽ­[Cφμ鳓Ÿ{\\9έ?œ» ΚγγΫǎm;Φ5vlsKKΰŽ›6EνΨ εεεΖϊd$μcαΡ‡Γa<,++#ΣӍ‡ΌS$―π ©PRRβ½8–ο3y²σ曝Æ΅\½jUτ]w%ΧΧSM55AλΧG­_εp8 h3ΖyΓ O=uϊwbώλΏ:ΎωΖ/¦ήfXΓ†΅ώβMΣ¦5mΩΎjUτ'Ÿ˜0\ψ.;Š‹˜Ψφ³ŸΥ―][[]ΈfγΏΫΑ‚½ ₯tΡΟ>ϋ,©σςΛ/9B*τλΧΟxΘC>°!m3¦ό¦›Ϊrr:V―^½:€©‰‰ΈV’ρ>…ž΅―’’‚Τ'WδQo§Σ9rd—ι―^υΒ 55έ.J|ξ²τZ€¨¨(γ!ΧΚδδdγ!„‘eCCƒŸJαυΧ;νίκ ۟xΒq萯 ΝηŸ;>ά1gNόw4nή\Ί~}ΤsΟΕΥΦϊz³/{ίΟuΧ9ϊΧ²©S›x ρWΏ σ}«2²jUԈιGoΩRΊ`Au\,Ψ{,β²ρ€ ™»ΰq,βΣυιΣ‡T ]4q—FjyτΡͺΚΚ GI)(p@t4ΝΉ A#>cCTƒW ­"zM4`zΝU†O°,[ΈlYΒ]w5όρ‰?ό!ό΅Χ.ιωδ |BζτιΣΖC·>™F\WΟ°ΣOYΊ΄ϊ7Ώ©›7/iζΜTeUvη΅ΧΒ J<}:πΛ/‹ΗwΊƒΟc3ΓΊρFgQQι_„ώόη)‡υ“2²fMΨΈqι7ί\ςΛεφNzσP ‰΄ΉMΰ‘@βΣq1%Y~~βO”ΤՍ󽢢€€$ψϊ믍H`˜σ ‡6&%%‘ gΜ1’»ζ7E”‹ϋtD[q–ΙΠζΨ‡Š0‘nχξ’ηžλ³d ύ`‰΄qošΘ1Ÿι*--ν~EjΑΔ+τ {τX·άβόŸ)X½:ωί}@[› I>Ξ¦M±£GΛΞnyυU3 {`NΈΟ<…"‘ήcaŸχΤSΥcΖ¨|ΡsΥϊχοίs«ˆPRRB*9?λ` ‡7ŠΰBFξšλ5ωjψwΗƒŽΰ‹=ΦόωΗ]ΐ[o₯I,~Ξ‡†]sMΚͺUΝΩΩ6ˆΡϋœa=ωδ±"Χ­Kv_Υ¨ͺ œ4)β·yΔ_Ο†φP ‰0ρ Z’)ΐsθHΊ¦κΐί|³bέΊ¨Ώόͺˆ§ΙΣ;IΖmj2Q`^_ε·kΧ.γαΰΑƒ‡ά“%yΓ<ΘD>(•$†ΔCkAAAΧ^·~}ω’%‘Ϋ·‡ρ›"“<άa'ΓΎŠYΰC=Φ»ο–―Xσ—ΏΠ¨£ΐωΙORο»―a$/₯.z€―Φϊυ§ϋΈ­[ι:;αΫψε/“§NmΚΟχQMτP ‰gΑ§tHfŸ%0HΧ­«~ρΕ„}ϋ"Œέ6οΓ $«ŽK!ž­FΞπΝ7ߐ ƒ 2’iξ¨’Π"w‰HfWx.¦F}Ÿ??υ駬\΅yσΉ!Ν§•ˆσΟ™ΏΕ¬Oτ[Ή²vεʈmΫD=αΆΫ’ήΏ’’"p~ߚ’χΠ°Θ(•δλ-ψjt551o^ΥηŸGnΫ Απ½+Θ ”/W'ΏEžyLΞΙGΚdhLΞΐ―Hκ“O Xgΐ{YrΧΌ—%έjBBΒM7υΫ³§dόψ~΅΅Aΐ’ΐxPΚνkϋ|¬ιΣλ`εΚσX•pQ\uUϊ§Ÿw_Ο‹XfX'6]~ΉkαBΊ΅ΰNgΐΤ©i|@Glβ‘’ώ–―&%ύ-Όηη·]w]Λν·ΗtiW ’ž| LϊpK#*ΐG©n3,Θ ΉMžΌΰvJ‡DγxΔ¨¨¨¨ηK·ΰM;ΌhQΚOT?όp7ζ_ Q|ώΉρY °¦Ηϊγλ{ŒŠπΩ±#’½ξΊΛ'ς ,0¬eΛζϏ¬ͺς•Zoβ?ώ#εŽ;œYYΦ―…υP ‰;Γϋ|β@a λWΏκ8{ΆcŊΞΦΦnBΖ“ΛˆNqŠ\‚οFI²κΈG‚FΊ'?5>™κv_F’Lϊ`‘²K.ιΆθ/±'ψΈ˜ΌΒ’3π.81<@XVVφKπϊλΓ‡Ώ~(χHΘ„ξxTΌΧc͜ٴvmψώύΤΝtπΟ7}Ί“ύ”Ό‡— +5΅cζΜ¦•+eήΖ{όρa‹{²“Ά)x(…dΜΘχ‘Kd%33“T {έαS@D[ω$9!_κHΎpˆ€[ρ9lδš5aΣ§;GŽ *Aξ‚ :I π oτXΧ\ΣΥωα‡]y›9s’ω’Wοΰ ΓzφΩ–ωσMΘΟ.–/Ύ)* ώωΟ-ΘτP ‰G|γ+ΏώuνφνΑ‡ut“ ²’―!36ά+δYΉβ6ra"Q"Ύ’•΄ΚνC„Θ Ή:ρu;βΦ―t›…φψγɟ}VΊaCό·UΰߝΫG7\z{¬˜2₯qαB:Ό†ΣΈdIόΏώ«·Χ$κ5¬ϋξ«ήΌY†VσΦ[Ρ³gΧ:^έΎΖC)$ŸXPή͌Υ™™)gΟΦσYHŽΗρˆ·ΒE8D|j‚DΉqδΚEΌ]RŸ§‡‹o¬@ζx’ρ ω%Θ]»M#VMZΆ,ϊξ»kώσ?c€9³nΓž‘±ΗΊηηλ―G˜‘?-|W–-‹Ύχ^jΔZρ°Η"ϋςώ&00pΦ,ηψρIκWΞ§cΙH™JHΐ†Ο~^οάB~ξ|z„tH|*ƒœδBρΐ˜Ϋξ„tiόc!IcόΔeα™ΗdΦYΔkjΰΝ7ƒ§M«xυΥ·o•θ걦Mkή²%τΜɍρž}6δΑ½'ΊΎψϋξkzι%ί%Ζ§8y2`ϋφ iΣΌ”ͺeNv‰χddή΅+βδΙ+žY@ΖΪ<σ˜ΜfπΩςΰ]>Ί';ŒΉέ)ŸCFλΔGαή‘!ή$’Χ<³€Ό…Οؐ3πα?ωjŒϋΌρFΛΏόKΝGu»)~|PαZz¬ΌΌέ{φȚfŸγΘ‘ΠΌΌ–œotZZ +7χ‹χή3a(˜ΞϊυΡ7έ䍀xs€Π(ƒο9|ψϋ••έžΓEψ&|‘$™¬ΰˆΕRO(ž‚Aœ>’­ΐn“L†p—ˆ―Ϋ]ψƒnH‰η?΅ε™ΗΔa'·πφێ5kjζΟ?χ ρΰ™˜ίc)Γ2ύ΄‚)œ8\QπƒhίJΔdΓr8šϋφ-*.bξiY·.ψζ›΅³<”Bβ­`ή¨Q{φξΝ―¬¬$~"—BΙσgι­δKbHx"…\VH…ŒŒŒžΫ@Θw›!Έ¦'އ7Ι]π­n3Ι'Ο=ί>H˜7―ό‰'ΊφLδω|Pα&χX}ϋžϊκ«ο™{NΑ\jkί{/όΚ+Mˆ)τ€‡= ~¨d©€€ΞαΓΏΪ²εΆΨXϊ;θαω 2δ6ΑΒσeɏ•Οs“6πΩVEρΑ8YΔGz82Tβcs·{σ“–O ρ€Αm·YSΣ6aBϋΑƒqpΎ.η₯y€™=VώGOœ Ο(|έ»#FtΓςGΎϊ*"?Ώ9$Dc†–‡RH€MΕ±ϊχ/ΪΎ}’*Yα{“q1Ο…"γ\žςE¦‰x’ΝΜ§•Θ@˜‹Ρk·ΩTD yΡ)ž-M#ρ…Έ \ΙΕψ¬‘ \νΪ6thΥίώΞ}ž&ξ&φXiiE'OJevξ »β χm˜fX’ƒφB·a™³k²ΛεJL,*,„jB:X!ΊΓŸG&XΈ¬βA#2‡Γϋ|&ς ρ ΉΚΈ]ΖC|[~E"¦<»(>ON$­β36κ“ά½;82²#0°Ο;ρ΅Α`Z•–v’ΆΦOώfSΪΪvσcσΣ +>Ύ²¦F–yΩ‰cΗ‚33uΝ혳`5(((!‘².c’nwχ'=6yx)\ΐ’$q/9™>Χ‰΄'ϊYα‘W2ΧΑ‰ΔεβKTΖνSkxˆuθΠ‘ΖCώΠΖο’°Σ]TΠ}hο†˜˜Ϊ¦¦θφv{>ΎΨ_)* 8PWšƒ9=VJJ]]]ŠqœH‘nwχΟΞΞ&Θ’<>ΉA~X|Ά„€pρ’ξLΆ‚–ΜDBD|Ά„t|υ:ώσ©wr›όdG5ςΧΘxŸ,vCψνΔ ΘΚjα"Ώ¨˜ΣcΕǟ©©‘' ڌ’’ΰu±Μ1¬ΈΈ3΅΅bX6£Ά6ΜxNοy0ηY:ννΝ₯₯Ζ)2ZηiC€Ώε2D†nCDδ24ζϋΥπ9Ή ·A&’;\eHT‰'C“sξΫ·T κιφAόs3!Άm‹Ίμ²Πžwfσ sz¬€€ŠŽΉΫ””Ά˜-O˜6Η°ŽΦΦVίz¬™p!Τ΅χOκώLF³πP ‰Γžb σΈύ ™<–¨'―@tΚν”ŽΫΝdΈjg–l€ΓΧΠ’ |:…ΈΊYYtv•δpσ™.}βσϊj2-γͺ«Ζ?ώxIjHNZΩƒχgμ|ε•―V―ΎΐK\ζτX!!³ge…ͺmσΐYΧ^ϋ§»οŽz7ΏvεδΥ?ύiphθψƒ‰—Γς;.ΏχήζΚʏχ»κ’’¦¦ΐΘȎ¦ŠŠ=+V|±jΥu‹›u₯ψt!!‡KJͺ:;Οy4dg6ξ²™ΰ9ο$ΊΘ]0cσH Iˆpϋπ ·RHω ‰ρ,™wβQ_ς~SΔeγΣ/dcsΧ?˜1γ₯Λ/Wn}}GGG}Y™ Κή}wόΒ…@Ά@ΑLθ±ΒΒ:ΪΪ‚V%ψ,©Γ†U΅ό#$ΡΤuξηtδγs½Φ” ™šάQ^ή‡ŒΙ/g»’_3a‘ȏ9‘_?Ÿ`ιy±9…'7“‹’…@²ΔΎ±10-Ν‡+τ@ΡΦ­yS¦ΰabb›q@‘χ“Ÿί΅Λ” y’_η ‚‡ΤΘψέ|π€LδΜx:ή˜vΎ·π—¨¨– %ψ./Έ\ΑΑπόσ0{vΧ‹„»Λ»pΜy&eCDG›|”ΩaaχoήάΨ‘²Ρα¨Όcυ a11ΛϊΣfwΫς6{χΒχdΗ[tι”)―yqήΖε£n½5ތ‡_šΟ§ŸΒΨ±V7BΈx>ψΏ^Λ™Ν‰Ό76‚{ƒ ή&* ΨJs0Η°Ž—Α»-‰Š–3fζVI δζšr&Α{ΐ₯—BQ‘–“›cX…… '«GΠΘ Aΐέ™†9†uδδδ˜r&Α{δδΐ‘#ξ«y†–bΓr:‘¦Ίο%ψ:6BNΛ†Ψ Η€νΫΑŒ΄‚χpΉμΠcκŠα :θί.½ΨjNί#=ΜΨΕYπτOπζ›ΟoZuό8΄·CχLwΑwωαα“O4žίΜνΈ·n…qγL<Ÿ ‘qγ`λVη7Σ°>ω~ψCΟ'θ’_? …ξ"ω0Π}ι‘ΰ£άvΌυ–ήK˜Ωc•”@g'°Μ>‡ξ–ωΌπ̚eu#w”—[ λΫ\qόύοV7Bθ‘ργaΣ&«αGΒΐV7Bψv^¦O·ΊπϋίΓΌyV7Bψv\.`Ϋ+فά\`;€ΎΒΟ~χV7ΒcΎψ†·ΊΒωxη˜:ΥκFxΜΓΓSOY݁ μ‘ŒΆ"2Μx€”`2³fΑ3ΟX݈οΘΛ/Γ½χZέ‘;§N{†έ,_CwžŒχX»nΉΕκFΰΐ2ΔκF˜Βe—Α—_Zέ&O† ¬n„‰|ψ!\wΥΆmƒ«²Ί&2n¬\iu#όž‘#aνZ«a:λΧΓ 7XέfηNΈόr«a:ύϋ{Δ•ΰ=nΏήxΓκFhbΙ˜3ΗκFψ+ ½zλ²ϊz`Oς΄σδ“π›ίXέ­x!ΙZ ψKŒϊο‡+°ΊώΔ† 0y²Υπΐ‘CV7ΒoΈε–ή;fη̜ ―Όbu#ό€ΔDΛ.ωӟΰΖ­nDoη³Ο`δH«α}š›!LΊ£… αΡG­n„%\}΅ή]ό™«‚mΫ¬n„…,X?lu#z‰έχχ)-…ξΟTτ?ή~τ#«asϊ 2γwβώ±χλφν0fŒ΅-ς „ό|«a[ϊ 2ΐXTZ—–Άf ός—V·ΙwhjςϋΫ#Πͺ/Υ—Ξ[,{VHN†Š «a7ˆUέ}7ΌϊjWΏea«|Ž‘#α³Ο¬n„}HΚΚ2ZΥ/~|ΠU^TZš‘aM³|“τt @\(χnΨ€=ΣΔ‰πΒ ηώΤgȐ;V―Ά¦Y>ΛΔ‰°}»Υ° Jυ&Mκφ‰}Ξ1f μίou#lΒsU₯ΆœQ‰UΉaθPH!ϊn̘λ֝­‹U]ιιP[ ΑΑV·ΓWΉηXΎΌ«Ό¨΄413S¬κB‰…;ΰšk¬n‡ορΖ°`A·WfΌύΆ5M±/μ―³τη#) ށΫo·Ί½ƒ… {ΧΒpO™2́μl«ΫΡ›˜<ͺ«aπ`«ΫaK–ΐŸlu#z%ρρpΰ€?¦Ωdfв$S3O=ϋχÈV·Γ[,XEE0z΄Υνπ†…={ΰωη­n‡fΎŽƒΗ·ΊώΖμΩPSS¦Xέ=Όφlέ*Ο#²ˆΈ8ψσŸα½χΰΚ+­nŠy̝ ›7ÝwZέaόxψΫί`γFΫηγΝ --°h‘Μ7ψ&ΐφνπΡGΆ4―ίώ\.X΄«›"œ•uσΚ+φΨNwΠ XΌ\.XΌΨž΅ρ7ƍƒ΅kΑε‚W^Q£¬n #8fΜ€;ΰπaψνoΕ€μFh(̜ »vΑΑƒπΘ#šjuƒΎήz Ξ…εΛ%4eςσaιRψθ#8xžnΈΑ«Kϋσς`φlxο=p:aυjΈν6ο]Ϊ°Ίή ?&L€‰aΒΨ³6n„>‚£GM^ yy0lόθG0q"ΤΥΑƍ°ilΪΝΝf^Θψ…aΉςJ˜822ΰΗ?†((€C‡Ίώ?pκκ ΎΪΫݜ$<"#!+ ςς 7rs!/rr  Άmƒ;aγF8uΚ+χγ«ψa‰ο² υ?\y%ΔΔ@K ΤΧC}=44@}=;ΉΉ 11]Z[aί>θ²Heš……ΦލoαΧ†υmDDt2¦ΰ`hjκ22υΟm—&‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ τzμ·£_gg§ΥM°€€ϋ}S6Γ ˎ·hu„ή‰– 1,A bX‚Δ°-ˆa ZΓ΄ †%hA KΠ‚– 1,A bX‚Δ°-ˆa ZΓ΄ †%hA KΠ‚– 1,A bX‚Δ°-ˆa ZΓ΄ †%hA KΠ‚– 1,A bX‚Δ°-ˆa ZΓ΄ †%hA KΠ‚– 1,A bX‚Δ°-ˆa ZΓ΄ †%hA KΠ‚– 1,A bX‚Δ°-ˆa ZΓ΄ †%hA KΠ‚– 1,A bX‚Δ°-ˆa ZΓ΄ †%hA KΠ‚– 1,A bX‚Δ°-ˆa ZΓ΄ †%hA KΠ‚– 1,A bX‚Δ°-ˆa ZΓ΄ †%hA KΠ‚– 1,A bX‚Δ°-ˆa ZΓ΄lu<αΜ™3ͺ°xρbUˆŒŒT…¦¦&U8p *;vLκλλUα’K.Q…ΠΠPU())Q…˜˜UˆˆˆP…ššUˆ—Λ₯ƒƒ»>ΊθθhUp:ͺP]]­  P…––ς.¬ƒoWη€ββbUHIIιαCπq€Η΄`Λ ϋ†ŒŒ UΨ·oŸ*„……©ΒT!--MT! @ΪΫΫIμZT!0°λ·§zμiπleeeͺΠΩΩi¬i<vB}ϊτQ…   Up8ͺ°~UΘΜΜT…ΊΊΊž>ίFz,A bX‚l)…ιιιͺPXX¨ 8θF劏W…Σ§O«Brr²*ΰUμμΩ³ͺ˜˜¨ ¨Ά(pΝΝΝΖΧΡΐ+’+€WΜΝΝU…¬¬,Uhll$mΰήͺ$ΚΊ‘KΠ‚– [JaEE…*DEE©:kύϋχ'uP[[[ΙyPΡΕC 5Ο¬€5·ͺͺJPQX“’’T#gθ‡bP­_Ώ~ͺ€R‹2ξ]Ϊι±-ˆa Z°₯’F΄΅΅©κκN§ futtσ\zι₯ͺpκΤ)Uΐ  ™’ͺhjHHˆ:Dg}@| :zΏ-**R…‘#GͺΒΙ“'U:55•œ•ΤŽH%hA KΠ‚-₯P9h (¨w‡VτοΠγ«­­U ibrΎΕ eW‰`ίΎ}Iτ:QέΠ\ TE΅#₯vDz,A bX‚l)…˜ξ‚šΘÌyyyͺ€1IτΏ0ŠΉ1θaYt<Υ%JKKΙIpϊτΈ–‘ηˆy>θ9–——«Š/ή¦‘KΠ‚– [J!&€££‡ž*&mβμ:ƒ8EˆΊ‰Ί†uP Qm<ΓΥΟ†™Ÿθ«’3ˆ.j\\9?J*"i3‚@Γ΄`K)Dw 5%#œ¨wθd‘² n’β QΞpς'•œ?~\fgg«Š†C± θ«β…°U(⨛˜‹ΑKΫι±-ˆa Z°ΊMgg'†",X  ¨><ο•=>T(Ύ’Wb\‘όM¬‰p^ˆ§»`$%Ϋ‰WΔ%‡θ'.Z΄ˆί²]KΠ‚– [z…_Ώ€ήϊh¨w˜$ƒΊ‰.Κ¨¬¬όΆσ¨Π(†CQQέπOθ~βΔ"f‡b§ρO˜ΕŠ][#=– 1,A φ–BτΘP Q1ΥAkπŠ@IDAT’Ϋ…‰4(UνD•DΧ WΎ+Ο%Υ5‘Λz¦<Α5—WΖιN[#=– 1,A φ–B”!&Œ7’&β¬N’‡3ƒ(—¨€8©‡"«T —N`Ϊ Nφ‘„α ’’β+θ9βT&:•θ‡Ϊι±-ˆa Z°·’“…ΣsθΔ‘†šˆΙŸψ'ž&Š>F)q‡f%»(|θ’&β{1έg'ωΒ|”K¬ƒηA₯Ά5c ZΓ΄`³d ψ–’‡zHP渟ˆ3ƒθ "¨k|CQLUWtλ°€*Œ†nΊψ'Œ‹β-`₯πΕ_Ό[φq€Η΄ †%hΑή^!‚ρF\Ά€O¦@?]?tяCΧ 5ύΈΑƒ«‚Z¨ΘχύΖ·`΄=G βΊB>q‰‘X[ο-Γ‘KΠ‚– …^"…θR‘χ„ϋrc)nƒ•q{RtρψΚwάFM’‡3d2 I8θ0b«’’ΆφzΥΝΎƒ– ›…έΐ]΄pΪ΄iͺ““£ (1(:θbL%Ε5ϋQ-ηΗ)B”0tλπlψ^άO‹—ΖΩΓϋοΏίν½H€TΊΓ΄ΠΫΌBΥ ˜©‚σ}˜$ƒ~Εΐ@(f~ϊι§0tθPR“―ͺΐΉHΌ4ί₯ ·Yλ­H%hA KΠ‚Ν| ΈxιPΜfAχ£©θλαŸ0€‰ΎžšΔ ΈjA‡Ϋ‰Rˆ^αΣO?}α· ^‘ t!†%h‘Χz…NᑨaF Ζ-ΡYΓΔNόΞχυλΧ 0ΈF 8ˆ~"!fφz€Η΄ †%hΑfΎ|ι™gžQ|‚|ff&©ƒžγΑƒUW#*Ο½EtτPj1‘“KKJJTgyδZ.^‘ t!†%hΑf,˜‘ ˜QΓ' qΗQΤD|° ς q'1mIž\ΚχR»(D ‘ 1,A 6λ`ΑT]ΈσΞ;U§ππ9MΕu…jηRœΔ]Ύρ½θ0b\tι₯¦΄S€PΊΓ΄`³τθΒάΉsUύDT@\W¨D·ςΖ™A|Uς±Η3·y"…‚Π…– ›u°ΰ-]ΐLQ\W¨2EωΞl^xθΌH‘ ta³ίθό ΫυX‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚Ώρ»s’4‡o °IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/ellipseroi/roi_getarrayregion_halfpx.png000066400000000000000000000236011421045507400311500ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ IDATxœνy|UΆΗoz˝„„„ν λ ‹#gžŽbŸΞήLt\pCYFGΡ|Μ( ’ΘADTDAvb@ΐ²‘…l$MHΊ“NΛϋ£1ηΔnwurΎψ™[sλΦ’Γνσ«sΞ½ŒAAAAAAAAAA]‹Οt82ή‘L<΄”«φιΣGΨ|πΑ…ΝuλΦ ›!!!ΰτζζf£1ΖΚΚΚ„ΝΖΖFa3&&τΧιtΒζεΛ—…MƒΑϊ ›Χ]ΒΒΒ„Ν––aS­VƒώΰMMM ΈD||Ό°ΩΪΪ ϊk47ό^½κβrψ†Α dŒuλΦMΨ¬――6Αί(??ŸyŠΚγ3 ΒdXΘ°.Hς±τz½° |&ΰ@δζζ‚Σ…ΝΌΌ<Π!..NΨ 6U*ψ―Βf³Ή8{0&“Ιυ€‘‘‘Β&pq"""@κκjaλ0 Έ₯ςςrΠ?66VΨ^V«ύΑKΖnkCCƒ‹&ΠchΖ"Έ iΖκL9 »Αΰ u8«Υ:ΒΓL&•Ω¬2›Υf³ͺΞa6{ώ¦KΡ +<άή―_[rr[ίΎΝ))Ά”kk+λΣΗήΨ`2΄· @3p Ρ`°λυv½ή¦ΧΫCB¬z½£ΈXUV¦ΊxQ]V¦*,Τh««αw’«VJJλθэcΖXFŽlΡhΪΒBmnκδIm~Ύ¦΄Tm·wΰυθΡCΨ,--eŒΕΖΪ °§¦Ϊ~υ+λ€IMύϊ΅ Ž―Ώ.(ΠξίoφΫςJ]Σ™ίBl¬uάΈ¦qγšnΎΉ©Ύ^}μXΰ-Šͺ«»>Α΅!’šUMκθQM»σΧΫSRΪ&Nl^±’iΘΫαΓΪΓ‡5GŽhσςΊ +)€dΪ‹/Ύ(l.]ΊTΨĊ„ ΐhŒ±+W›ΡΡΡΒ&ώπνeZ-›>½eϊτ–^½l‡kR:€©ˆŒŒύΑwg‘3V;XsΖ  Η„ miimγΗ·VWkNžΤmίώ[i4…ύ“““…M»έ4›ΝΒ&Φ­Kΐύ-‹‹ yαΒί„t”Ζ-·΄Ν˜Ρ’žήΊukΰβΕ!™™>ˆfZ,{χκφξΥ1¦OJRϘΡτε—΅.hΆoωτΣ`χηw:Γ\­Υ:22κ,XΈΠrπ ...κρΗυ§NωώίLyΉϊ₯—BGŒθΎaƒaςdKQQε‹/Ά%&v‰ΰ½–VλxτњS§ςlΆ€™3ΣCwμΠΉ?Νλ98~δΐqyyͺ={Zί~»σ›—Ώ–Nηx챚S§ςššΤC‡Ψ²%ͺ²RΆ―Ɯhi xηυ Aͺφμiέ΄©­wo θ4Hϊ½θή½»°YWW'l‚ ΰ)3Ζzφμιβt†άωΆΆ6ηxκ©«χίmέΊθψΖc6ΠΑ Ή`υάy|‡ΐΥi* ΒΓPDxcQQQŒ±½{Ωή½lΪ΄ζυλ‹Οœ \½:²²RΝ:J~y/@L`΅{pW‚Z»κ?›±ώπsvvICCΐ€}7nŒςυνHbηΞΰ΄΄^Gγ΅σηΓT0Ηo +"ΒώφΫΥωŸζ_ΊΟϊυ0ϊλΏμΪ₯Ÿ>½»Αΰψϊ늑C[ܟΰ'ψ‡aέ΅ƒKοB~8Ά­­FλV―Ž˜3'fΕ γŠΠπS”nXέ»[wο.οΡΓ:rdίC‡`rsg’¨H›žή£°Pwξ\ιΝ7{PŠ6¬Ϋo7οΨQρτΣ±Ο?νΎw§ΰ½χBGŽΏλ.σcA%α_HR… 4d±΄;\ϋ4Kοή½…Ν… Λ#"LΏω3[Ўϋc(lQ†k%@& V…ΰ@i˜ΤΤΤ›@Δ1T^Ρ«W/aΣ22™ž|2ϊ‘GmΩRυΠCC„ͺͺͺ„M ‚ͺ•ΐw R@Π θt)(qΖR©ο½—WY©[°ΪbΧα΅ΧΒΧ―ζ›³©©~ω³¨8Γ:Ԝ•υύK/ΕoέλΎw§ζψρ  νoeS§ήΰΡ«Pτ](Λ°&N¬ΏλΪQ£†Ÿ?―wί» `±¨ξ½75!‘υ‘‡*ΫΞώβ‹Ω{φψπΔ  ú㎺?όΑΈre‚―oDq¬]ΫΓbQ/Zt™1φΐ»|ί²e ·-IΞ;¨<ρP„ƒIaϊџώTŸœ|-==˜±‚φƒΞH; &*LπAL g;ό* >π³_RRϊƒπ΅1ΰΰΚιφ¨ΡβΕlξάΦςύρΓ―θ-f±œΫ°αΑύϋίΊύva o€˜`(‰ Θ  N€ ˆkΑcB‚uήΌ.”δ!ώΆκƒΕ‹ζξv6kϟ߻tιœ/Ύπν]ύΎ7¬™3―i΅lΥͺnξ»vUTjυ#Ǐ8oޞ͡Ÿ~ϋΏ{ήyΌότι½K—ήΩgΎ½½ρ±a͘q-5΅uνZ'{•ͺςμYΖΨ‰Q_}ΥνΙ'‹œ‡ΛOŸB)ΧJΐ—†υΗ?6 Π²lYΧύX%»ΝΆλ‘Gζ8ΰlξήέύ‡ Ο>[ΐ{πΰΑo\δG!ΘιΌƒοΤ ™ –Φr뭍Ӧ»ώ‘&55τΞ΅λt+†²‹ΐWf\bδβώς…+**\ίφΎnΏŒڝλΆςςΜW^I{ν΅υ“&1ΖrsΩ3Ο4/?οσW:jkΫγjkk…§ƒμ+ΦΡ3 ΑzΘc|3c%%YŸΎaΪ4ΈΌα‚ͺμμ=Ο>ϋΰώύΞ¦ιΆocΟ/›’ιΫ»ϊ9|Pq Υ:ͺMHˆcΜΥ”F`ΚNΪσμ³ω⠝^Ώσ‘‡>ΉzυwVTθNŸVάχdΦ[o]5ͺ«‡k<¦μΤ©½Λ—·57Wž=?kVΪ΅%—/λͺͺ”•ςονŸΒΧ^«όσΰšίζπ_JΏύΦ©¬Yχζ›Ε>ΌŸρκxκΤf†νΨABε€€$πƒ’Ÿy¦Β}W/"ι§D<€ΘšΡj­^΅ͺ%>>ˆ±λiO@‘ΰ•XAŒ$Tαl$Sͺ KpΓXΖUͺtπ@x‚ϋΑχT!NuD dΤ~Γ›6ι7nΌ6fLέφν7”δc@Τ ά0­Rπތ΅{wλw*±š΄sπ—Ώτ|γJωb}Rρ’a=ωdύ§ŸͺΏϋŽ\+ŽΜšΥsΧ.7Ό†7ώC‡Άτθa[½Z1š:)ί|rψ°:#CΥΥή0¬^0Ύύ6ΜH!xπΎό²E§CΞU“AΒ“s­¦{ξ©ΟΝ )* Œ„υ€ΐ5ΖΞ8pށ… yλnCL "Χΰƒ"z°ΥEίΎ}AB©¬¬ΐ#ƒ;Δ1"πNΐ‚˜c¬W―^«VιΧ―o^Ί΄³πšΓ}ΖzξΉκ+Ί»οGΘΔΝZ ςqTƒ―a-ZTύςΛ±Vk'¬]V2+WΖ-[ζ&y•7 «oίΦργΝο½§Δl‘ΞMNNpq±ξ»|ΉΠGΓΊ~γ’%qξϋX±’Η”)Ρ°ββΚ °œ>έ™W[P2K@QQΰόΟκτ%©B°N—0δrΫmϋ^=R¨Jΐ‚Ύ …h@ C!Ρ!U nΣξ@™MB¬<—βρAY^Vθ8πA’"sW˜„CFBΥΉqcΤ‡Ώύφ ΆΑ2Μ@β7ΰ1\f¬¨(c\\Νώύ”ΗηKjj4™™ΑιιΠX½ΓΊε–“ΗŽγ12ρ‹x㍰yσdΛ6ώEΘoXΑΑΝƒ]8}zΈμ#Ώ”‹u—/«'NτΑ²"ςMWŠbύϊπτ<”δΌƒ…3›gΠ Β>xͺ[7 ˆΗ–!Χ@€k ϊγ’yQ'°έC1(Βa( ˆœ§p d€GΖιPΰ  /9ήΉΑ`ΘΜΤΆ΄°”Λ… Z†άž…oΨcdž±’“s^νn³Q"ƒ‚8~TΉœ>ό›ίψαŒ5jTSyy²ϋ~„ΈtI«Χ;bc]-c$#ςV||›έΠΨHΙ’Š&++xά8/‡IϊDή³†m:qB T ` c†‰pz¨’kc;kβ*vΡτs™ ͺhpHLp‘ x @”©ΡΞ ˆΐ` b2 H΅ΏuΜΫΗ²ŸŒ/yf¬±c[33PΜFΈδΨ1ΥΨ±^š±δ2¬–ΜLٌΰD~Ύ*4Τ ƒχ<Α°ββlšςr?Ψΰ…Ψ½;hΨ0oLZ2Vb’UΎŸf‚/ΝΝͺΤT«ϋ~’‘δΌ;Λ†―­¬ ˆ‹‹s]q#* u@€·βΨΩΎ'πΦρΕΐΗ [ΰ —δw;γ`O0 ήqDT@A ^ρΛmκΚ•€αΓMν‰\@ίΰΏ‘ΗΘ0cυιΣRZJS–PR’KHπ“ŸΒή½-eeAξϋ  ΄4°OoδΟΘ`X -%%4cωuuš  ‡^Ούϋ;ύv9Όσk(Ιy/..ξΣΗ~όΈΊ ΰCK―‚μ›ηεε ›ΐχgθ»3πΎq:p]swΰ\ƒΥ°cν[‰όάΰΓ7φΑ [ίCzψςn“₯°¨­­ύζUXX]mmCIr8–ΰ1Rg¬θhGX˜›ΧM(Š€£ψŸBƒΑa2ΡςWώ„Ι`εΎΰžΤ:-½ήa6“aωaa·<π@ΐέγΒμΝχόκjρ±cşΞιZ4cuΓΒώ{ΫΆ¦«W» <~nΧ.}tτ„εΛ9]Nͺaьε…‡?pτ軷ߞωϊλυωΩ†@Σ…/Ύ8ΊfMΝΩ³ύ§LαqE©aFDX›šΏƒMΫA׏ΡtαΒΠ”εΝ‚kT€Κλoαυ΄@vTr2Μ€»°€ό',3ΕA'ΓooyB. A δ“α[j—Ιι6|ςΧΏZ,«ΥZ_o ±9 rφ­]ϋΧγΗ ΞœΉ”™Ι:’₯C3V— )-­θΘη6›τϊŸ„όŸžœ–&ϋ₯VK «©‘„EΣkψπσŸ|bύ1½Ά‘AUUυΣ\χΥW‘½{Λ~Q©†ΘΌ–ŸOxΖεοΎœžω1υ#,ΜχӌΥχΏΏŠφ]“ŽTΓσ*‘L.8‘τγοπ^έqG᏿’2"Ιy·ΩXT”Εšΐ;vΑ΅ ΐΣμΩ³'ΌΉ#ΐwΖ‚˜ ˆΐΰΥuΑxP |m.Ζ‹ŽŽ6ρ ^  δ?αε}]―λΦyoΝττeeΛβγu:^oolΌ~oΏ}ψαμχήk*,tΚς©55©BBΌ‘CMHΑαpΌ0xπΌ}ϋςŒŠάojΙ<εΞΈΑƒ»υξ}`ιRW”ώS¨λΙ°όΛ΅kοgd…†v›φZΫδΟ†LΪXQΑΙͺ˜τ‹|,?’‘²rͺU£Ys½YσΑ«Z†~»e„f¬.‡Αΰ05(>»‘Ύ^m6Σ†ΌώD@«©QvvƒΙdΊpΩd±˜¬V˜Κ²δπ&υ ζ“˜˜:UB4ΈFŒΦ$Ζ*€βί ’@L @ΐΉ] $ϊαΘ!SJJJ6±.))5ͺρ£Bƒ‚΄ ιJpRΑr‹Š4IIή(U#d!)ΙZTΔ}Yk «ΈX“˜H†εΔΔΨ››ΌιD3VΧΒ;Σ£««‘˜h-.φ†aIΊ†3β‘Ÿ0uͺΥf³Ήή’»Ίΐ5ΖωRΐ΅050 Εm;.z§χο8ΐΎ3Π7 VTTϊƒ/SΒγο?ќ9Ϊͺͺ v™ς½βγγ™LΘςS¨–―jˆΰKP½°ΠE 2Vu΅:%ΕΦ»7%Οψ“'_;wΞϋ)Λσ‘μδIΧ·$<¦_?kCƒΊΞOœwΖΨΙ“ΪqγdΫ‘ŒΰΔΨ±­§NΑrsNH2ήφοΘΩΩAO>yΉΆφ†οΘ  lΥǐ·Ž7ΛΎ'ψξŒWΛ—Ύ?ΨΟƒ!5Ύƒ3τ)ψςnwχs[ΎQ[[+lβ’|Χ›γΊ€ ς%-ΝόυΧΑΒΧΎέγEπό6Cπ榛lIIŽmΫΌ”ΣΧ!|—³YΆΜ°d‰Y§£•ΩΌΚš5-=ζγ$Ή―“ττΣ‘/½?@όΘΘh;sF“γγEΛ$Ν– Ÿ Hg|`Ην¬Y5#G²Ό<˜nŠpΐrV ets·Θ±[YŠσ±zτθ!l‚x\³οvΡ ϋάͺHσ"l“Ι‹ŽŽ^³¦dΐ€§ΒΖ)kπJqΚcΌaΧ‹EΞmrߏΜΓ_[²$]}€7 +;[[V¦~θ!*γΛ„ m7έdΩΆΝgŸ„xι—ψ•WΒn½΅iθΠο\kςΡGϊSwχύΌ‚χ\ΌŒŒοΎ ½(B.>ό°ρΎϋBέν†ξ=δόΤͺ­«SέzklNŽ_ϊιβ7†εδσΟu))‘¬¦ΖψΔώ½°Ϋτι­YYΥcǢΟωƝd’jG?‘yσζ ›σηΟ6A §[xOv 3Ϋγ6{υUϋψρ{Ÿ}φθ‘#Mλ°?ΠD $†‘ Y@…xa1”…u¨3δ2xπ·γΖν/-ΌgΟο"§OΏώ‚=εRΎ π «NŽbGΛΰ?Ηψλάλp¨Ίυπα‰ii‡–.]~ΰΐ€ 64ΐ`Ÿ’ΠιZ‡ Ι=ϊ`iiΚΆm #qΈ³Σΰ―†εΔα8|ψΦ#G&ήtSΦΜ™›Γ³³Gζδ χυ}ARRς‡Ύ_Ώ‚œœ›&ελ;βŽ–‡# +kLV֘ΔΔ²‘COίyηΗΩΩ#²³G΄Άϊ8Γ$*Κ8bΔ™αΓΏ―¨θωέwΓ>όπΏ@φA'¦3V;₯₯}KKϋξή}χΠ‘g&Nάί­ΫΆŠŠA—/Ό|yΩμ₯IB§kιΫ7?!!?!!ίh /.N|ύυyf³ντ7ޟ (FxτΡG…Ν7ί|SΨΔ‰ϊΐUμέ»7θ€Σ‰„ΰM灳<`@ΤθэcƘƌilnV8|β„ώμΩΐšM‡ύqΖXbb’° \έφTD„#5Υϊ»ί΅NœhKNn=q"δΔ‰ΰ“'CrsαzB €“œœ,lζηηƒώSάπ a Š…kόABXll¬‹ρϟ?οVόjƍš/ΏŒόςΛHΖXίΎ-#F§LiXΎΌY―·ꊊtΉΉ¬°P›Ÿ―))Q[­’ή FΓbbl©©Φ”kRRkJŠ55Υ¦Ρ8 ΜΛS―XΡ-'G(»°P•θΜ†%δ₯ΐά܈χߏ`ŒιυφδδΦ€€Φ>}ΜχάcNI±Z­ΙΙΦ†ΦΨΘœΝΝ 6¬L―·ιυvƒΑΧΫυz›Zν()Ρ”—«σσ5ηΟk>ω$θβEumνυy",Μς[8ΡU KˆΩ¬ΚΙ ΚΙ 2›ϊξ₯Ρ8¬Vch( u8«Σ±ΌΌ³Ym6«L&•Ω¬Ί|Ή±₯姉 oWF΄Σ «C¬Φ£1ΐhdBΏ31€Νxξ’v5ό,€Cψ ’f, γ\˜6 œ" b„‘)ΑΦ:xο Ϊΐ²inϋc™ jκΑW(όέ€"βe•Θ!#\Υ €¦ΓeBΰO€·tεΚa— y ΝXΘ°.a\τε]Ζϋ ”‰Η_ή ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ˆ-cDΈΒ7ψέβI›6mGάξέΐ:Ϊ/čΗΡj΅ΰΘμΩ³Eή§B2wЊ~Θ°.a\ Γ"Έ IϊΠyίΉs'8οghΦΡΙψπY`εΑ› ΰkaoχ™φ‘Λ/εOL3Α2,‚ dXΘ°.a\πUψώϋοƒ#xΗ6°Ο6λh“4ŠΡιtΰιΰqpΈ«B<2>‚c&ƒΑ/yκΤ©Μ+*$Α2,‚ dXΘ°.(Qb ˆ/$FOaεΆ7gΕο°ΎΓWΗΧΒγ`-)Fβ#ξΣΨΨŽΜš5‹q€T!‘8Θ°.a\ Γ"Έ@†EpΑχͺpσζΝΰk.ά'88Ψm“ΙŽΰΘ Z­GpiHH8‚΅›ˆ GZ[[ΑwΜ3η'IDAT|ΟXKΘ1Γν΅άBͺPdXΘ°.a\ Γ"ΈΰmUΈzυjp$<<ΑΧϊαK‹‰Δa]f·ΫΑaΔγΰkαœR¬ ±&Εz«B\/‰se±"Ζ}¦OŸΞ~!€ ΕA†Ep ‹ΰΑ2,‚ |Uα[o½Ž`ƒcsέΊuGΑœE‰υΦ\‹Ακ _ «9\Ÿˆu?;G!qdαvd¬R322˜KHŠƒ ‹ΰΑ2,‚ dX$m ΰυΓ`]†ΧαΔΪŸ…ϋΰ Αζζf·γ`}‡£™8v‰ΑOGΖzk[|–˜΅nΔhIЌEp ‹ΰΑ2,‚ dXdŽFasχξέ V=bς3±κΑ±0¬zp«9œSŠU^[FŒ ΓZGτpμί!+τμύ`ύ ήϟόgЁb…„β Γ"Έ@†Ep ‹ΰΑ™c… Œ…£ZX©αψ>‚VFX=EFFΊGλ°zFΔϊοNˆοYΜ*:ψβ|ZόψoΑš±.a\ Γ"Έ@†Ep ‹ΰ‚$UxυκUpΘ\Ω'&VˆΟΒjN̚ŸX]ŠΙ½Δ•}ψ~ΔDβΔΔ18ο?;Ύ:Φ’ψ~.π~6nάθφφΔC3Α2,‚ dXΘ°.a\9V„ŽΔ‰©Ά³§V=b¨˜5jπΥρ|Φ›8§ΑΉ©Xνbm‹Α‘Sό~π8 v)o0‘f,‚ dXΘ°.a\ Γ"Έ Iβ0H€Δκ +#œ{‰σ*ΕΤzPIΗ:Š aŠ`%fΝOό€8R‰u+V»ψΉπϋΑoΗ.rΔ’ΝXΘ°.a\ Γ"Έ@†EpA’*ΔΘ.¬•°~Αϋ5`Eƒcj¬Τπ8¬Τ°rΔχŒϋΰqΔ€sJΕμy•5ΦΡb$x.\φ(š±.a\ Γ"Έ@†EpA’σŽ=VΰΞγδ²θθhp‡5π1[ `W‡b°Σ-f1›Ρ‰YΎΛ 1.Ώ˜P>‚ΟΒΟϊΰ–ΝXΘ°.a\ Γ"Έ@†EpA’*t›7‡•HEE8‚Γ5aaaΰV=Xρ‰ Όˆ'θy¦±"Ζ#γp V©ψ)p~%ήα-ώ’’’„M)’ΝXΘ°.a\ Γ"Έ@†EpA’*Δ ¬_ΔHAΓΒι8‡u˜eυ± ΓIsnK©˜ΈΕNπYb6F{χuˆ˜ε6Α΅hQΒ Γ"Έ@†Ep ‹ΰΑIͺ'vI…•HLL 8‚—εΐ`e„csXραH₯˜ΕψΕ,%‚GΖ}Δ,ί(fiόμbͺĚŸφΐ»£Kf,‚ dXΘ°.a\ Γ"Έ IβδOPW(Fυ`΅"f1 ΓUΜ"”b26ΕΤρy–wκφ²Žή†˜Mπsαψ&`Ϊ΄iΰNΚΝXΘ°.a\ Γ"Έ@†EpAζmε€τΐΚ#f»1[”γθ!Vax¬žΔd½βkα³ΔΔ.ρR‘Έ«91Ω‘b6βΓοGFhΖ"Έ@†Ep ‹ΰΑ2,‚ 2«B ΊiΣ&Π«9œύ(fΫ4¬qpΏΓkδγk‰QXx-ΌBVŽbrAΕ„ηπYΈΚRΜ“Ξš5Λν΅<†f,‚ dXΘ°.a\ Γ"Έ ³*`#¦jŸ…3$qv¨˜έ°ΎΓgUWW3wΰqπY8Ο?~ 1ΡL¬7ΕTGΚ»ρ„[hΖ"Έ@†Ep ‹ΰΑ2,‚ PqˆΗαp`Αβ–­[·‚#XαVXΈVFx¬ΛpΞΕ™–bςWqΜQΜκ¦8ž(&ΓVΜsζΜaΏΟώΔNhΖ"Έ@†Ep ‹ΰΑ2,‚ ήV…˜νΫ·‹Ή8‚£uX=α΅\plη‚β:>¬οΔμ:U*Vjψί³˜•d<Π€R…„β Γ"Έ@†Ep ‹ΰΑί«BΜζΝ›Α¬°p5"VFžΕοπC‰ΩεWφa݊U*«B|-|‡nGφR…„β Γ"Έ@†Ep ‹ΰΑ%ͺBΜ† ΐΟΦiρlΟz1€X“βΨ%Ž0zVω8{φlζHŠƒ ‹ΰΑ2,‚ dXόCb***ΐ¬žπΆμ8‡΅›˜ύ,0ψ,¬7ρ΅πύ`½νφκœ UH(2,‚ dXΘ°.a\πWUθXsαπΡ³ΣŽ9†††ΊΏE%AͺPdXΘ°.a\δΌΛx„2ρ;}FAAAAAAAAAAψŠιYAΕI§ΡIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/ellipseroi/roi_getarrayregion_img_trans.png000066400000000000000000000232001421045507400316440ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ IDATxœνk”TΥ•Ηυ~wυϋMΣ@Σ€ Kƒ“‘Ρ`c&θZAAqPεs™™¨γ“₯Q£Œˆ &Ÿ,WfΔG 3bƒH·Π4ύ¨nκΩΥυΎσa['—F°Ύά{«φολά’κΦş½χΩgŸs†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†aςƒΪhƒΡ8둇¦MςzϋΦ­oί{―$IͺX₯ XXΗΒκr=ξχ―ψΩΟ†ΌΎdύϊΫ‹‹SρΈ*Vι³Ϊh)›ύrΓ†‘/²―ϊ.XXίMy9<Έέ"QΫ =ΐΒϊ“&aςd45aόxŒ‡SΞΔZvοF$‚hάnxΜ;45αŠ+0s&Ί»ρΨcψμ³α[Νh˜όGΌ>ΆnΕμΩ*[rωεψΏΓ{οaζL•-aNЉρα‡XΏ?ώ±Ϊ¦Θ8|ΌσήΝΝj›ΒœO=…έ»ρΓͺmΗQψρ±gž|Rm;˜α3w.’Iά|³Ϊv ƒ[oΕΰ .Ώ\m;˜οδ•Wπΰƒ°XΤΆcΨΨνxθ!όξwjΫΑ©SαχλυW §“'ν•qηœ3uΦ¬©³f՟q†zv8yRnXΊ‹aζLτυ©mΚ‰RU…χίΗ³ΟbΥoμWZ•I₯\%%τWΑW–,QΧΌBdεJόΗ¨mΔρΠγηΊ6~Κ+ε/ΞΈα†[>ψ@-“ ”wήΑuΧ©mΔΘqνώpΧ ?㍑―θζ›g?υ”$[·β§?Uۈ‘£ωG?ZπςΛ.ΉG,#αϊ·ί2E³N/B·ΆbΞόοͺmΗΘQΪΠ@-„oΎ‰ξn΄΄–Ξ›-–βϊϊ/ΎPΝΎγΑ¨Ά'Hk+fΟΞ+U αΣOqν΅Ψ³Gm;N] kλVΜ™ƒmΫΤΆc€ uw§ qΉe ,ΐ'Ÿ|sD">Ÿ:–οΌ“WyΥσΝ©³fΙ_ΉδΌρ~°xράόO΅¬ΚVΜ«9ΰ‘8Š‹oΫΈqˆΆξY=Ή£žΒ‹ž €K—’Ίχή«Ά γπzΌόr¨»Ϋζv0šL™tzrο’}mΙ+Τ6.8ύt »΅.˜tΑߟ7οϋσζ5s½²k&MRΧ¨|ΔηCe₯ΪF¨Jm-:;Υ6"ΟX»‡/r( `υj΅&΅ ψnfΟ†Σ‰_ύJm;4ΐŽ˜>EEΨ½[mSς€X ‡ΪFhα°ΪF ­{¬'žΐo`σf΅νΠ Ι$i\r ή{OmSτKs³ŽΧ4₯­ cΗͺm„~y}mm°Ρ\€ϊ/΅Π)矏?Ym#4Μ† Ϊέƒ€i6lΰΗβΒ ω?ήρsΦYz–Ζq±mτΉΣB=^}—]¦ΆšgΞ¬[§Ά:bμXžK—>@CƒΪF|ZμΔXΈ}€Ά:aγF,\¨Άz‹4ΓG³₯>Νy¬Ώ{ψύψϊk΅νΠ _}…HGD―>šΦάΉœλΦaξ\΅Π>6 ¬Lm#tEUώϋΏΥ6BγZ›θH‘ΑζRm…Β3;„˜&›6aΖ ΅8V> Aai‹ξnTW«m„©―GG‡ΪFކ<Φψρψμ3ττ¨m‡9x--3Fm;dhHX“'γ˜'ς3Η"•:μΥѐ°ššΠΪͺΆΊ₯΅MMj!CCΒ?{χͺm„nΩ»γΗ«m„ VžΐΒ:*N§FΧSuΑž=pύYeM<›Mm#t‹ΛυΝ£:™Γ0›‘J©m„ΞΡΤƒͺ΅ =~ΦςΙ‰ΐγQۈZ–Ϋ͞όd‰FqΔC†UC+ΒrΉ ©m„Ξ 5”ΏkEXLZ?GBλ˜Ν0hζ„F­KSn\§h*Πа4•xκž} \n8yΈάπ-€ΣH§a·«m‡nΡT„v„ ₯…·Qœ8eeΨ΅Km#dhHX‘&LPΫέ2a‚A΅‘!aim}^_h­7„…•'°°ŽŠΦz υ…Φϊo5$¬;ωΨνΗfΓΝj‘Y:;Q[«Ά:€‘ννjq8ςXΰ—'Š7ϊ²°ς K[Lž αύxαΣή‡ΛW_q ~ΈLš€­΅gہΛΛ1y26nTΫ=pσΝΨ±ƒ§„Γ£‘Aͺ’$ITοά·ρρΗ¨«SۈoCsΙ;€ΠΥ…9sΤΆψήχΎ'εPΫ–oaή<|ύ5?όx8γ lΫ¦² ’$yζ™<πΐ<0jΤ( *lηNL™’ΆGΑ¬ΆίΞφνθνΕ¨vΠ΄$I·ήz+€^xΐτιΣτχχ“Ά jο΄ϊΙOpΰ€vλΙZ …ΔΓγΦ[Υ4ΰΞ;οc£ΡΈ}ϋφνΫ·hnnv»έͺ;°[nΑ#¨υεߍF=€>Βέw«γ΄$IZΎ|9€{ξΉ@6›}ρΕ8ŽΎΎΎQ£F8γŒ3tuu mJφ“Ÿ Φτ3Ψ5³sφΫ3οΏ―Β›$I7nάm·έΰΠ‘CJKKiόδ“O’°Ί»»Εϋǎ `Λ–-ty vΰΞ9Gs'%ΛΡΗ°o^}w܁ΗSΝ†ηŸΐέwίM—Λ–-s8&“ιᇦWΌ^o0¬¨¨πx<”NΒξΉkΦhZUΠΈΗ"ΒaΤ՝Ί=Ύ’$™ΝfO<ρ€Η{ΜεrH§ΣnΈα&“ @*•ZΎ|ΉΧλ000 ²²2—••ؚ[™Y…•” ­ ₯₯#xKEЁ°.½Σ§γŽ;NΡΧI’δυzxΰΟ>ϋ,€½{χ3f €h4 ΐεr-]Ί4•JˆΗγ&“ιΩgŸ­¬¬088  0ΝQΨςεΨ΄ oΏ}ςwRM‡Bβ7π³Ÿακ«±zυ©ϋRς@’$ 5€R©ΑΑA£ΡHμΑΝf—-[ΰΖo¬ͺͺπψ㏰X,:δυz)³Ωl'Ÿζ/Z―Wͺ‚.<ΡсιΣqπ ²ί"Iy¦9sζόιOB.y·ΫνF£‘Ό‘ΝfşgρβΕ$¬ΞΞΞ’’wέur~. e2‘PΘησαψεΥ؈?ΤΦγ'ށn„5aήz '*ϋ-$¬9sζxϋν·μv;rsC m6›R1E½X,vγ7’°€?ώρΘEFΚ³Ωά™[‹¦ΒZ[qα…hk©ίͺ,Ί€ωŒ‹»ξRπ+HXσζΝπϊλ―ˆD"$ ·Ϋ\Ž•N§³Ω,½ήΧΧΐn·»έξX,†œP–,Y “(Ÿ{ξ9EEE%I’7ΧΦΦΆ΄΄Π·Ca?Žέ»ρKΚύτFOΒπΜ3Ψ³Ώώ΅"7—$©ΌΌœζ}ΏωΝoΨl6^>ˆΗγ6›|%RΒ₯Q­δR\\,ή0{φl”™I’΄rεJ ©€ΡΊΊ:'“Ι£₯ω·ήŠΡ£±l™"ΏZ!tΌΛYΊ―½†ξnόιOJ}y¦T*E²@.Μ‰Τ›D³kΧΊΊΊX,ζρxΘ%“I’”%Δ±vνZ‹/¦Λ+―Ό@QQΡύχί?nά8δf.—«¦¦†4'—ςε—γμ³5Ρλq\θLX.» ω zzπρΗ#y[I’jkk­Vk6›%%e2™X,–N§½^/M-‹IvRjgggCCC$Wa#δv»ι=ρx<[,ƒΑ`±X^zι₯x<ΰͺ«’χβΏΘd2―ΌςŠά §Σ™L&ΛΛΛs ‘34Оvœθ, ΆmΓ₯ψΛ_Fμ†$¬₯K—xϊι§Ελυh‡)‡Γ ›Νvtt444ΠΫ<ρΘΫ…B!δ\Ή½ήή^εεε¬V+ŸΟwί}χe2™ώώ~ΊΫš5kΘEQό ‡Γ3f€ξΏ?yι₯υ­ΉSTo©>Ί1τHZZ°xρˆi‹ϊd-Z`υκΥΘ…Ό@ ΙdFΥΥΥ…œC’™ ύ3“‡#νΩ³9ιΤΤΤ Woo/₯π%%%ƒ2§Σ N_sΝ5tC*β―Z΅ ΐτιΙ{ονΏθ’Jš"_τϋύΠ‰Όt`β1ΨΌO=…Χ^;ΩϋH’ΤΤΤ …HX―Ώώ:%L”ρ Ιd2ιtš|ύ-i…4 G½gΟrK”ΒΣ›Γα0‡Γ‘H$¨AK$h6›iI’9ν΄ΟΏχ½Ώž{nZΌΉ²²οχ'‰p8ΜΒ:Όϊ*6mΒ3ϜΤMHXTC_Ύ|ΉΥj%ιPVDˆΚ›$5—ΛE³<Šqτ/M: €ͺ{  ŒF#ω!Z’TΎ₯»»{ύϊσkk;ή|σrς‚Ώόε/Πz‘Α`hiiΡ…°΄Ϋθ7LfΟΖψρXΎόdοFCΉgc$“Ιt:MΎΔιtšΝf³Ωl³ΩΔ”-’ζˆD"‘H$D^οχϋ‹ŠŠhͺ‹ΕœNgqq±Νf#U%“Ιx<•DKίEΊ|τΡτξέΎδ’Aκ³Ϋν?όπwήyΝ5ΧθBO=Ωz »ϊ―˜9σDΦ|$IͺpσΝ7S~CŽ„\ˆœD"ΐn·Σ" r~¨££ƒœi‹&•_ύ5Š}τ'έΆ’’’=wζPQQ‘$I₯₯₯΅΅©_άτΣξυλ+‘sτη7ήHίuΣM7ιE^ΊχXΔ /ΰŸŸ|‚ Nδγ&“ιϊλ―OζžuN• g„Α`Η»ŠŠ «ΥJ2ŠΕbeeeV«U1‡Γaμ‘H„²οŠŠ δλrϋΆ\.Χ¬YύΏύmϋάΉ₯λΦ9¨Π@ >±bŊ_+TVŒ<€―ΎΒ¨Qψяπθ£'ώ £Ρh4Γα0ε@‹… Qό~$ š‘L&ε.dΤ¨Q£F ‡ΓδΫ<ΈU&“ιξξ–·žz<žTΚχέ‡¦M‹Ο˜Ρ‹Υd³Ωp8LοΙf³ƒA|υMθ-ξ„>ή|EEΨΌ™Μ°*¨’$M˜0aΙ’%V«υ‘Gq»έB‘P4₯)εF&“I΄Ύ †L&c2™(T‘zΒαpii駟~  ©©©ΈΈ˜δEΉZ&“‘ˆΗγ™L& ^}uϚ5ρU«œΟ>λJ₯R.—‹r/»έ.Ή“Ιd2™ŒD"½½½z‰ƒΘ'%xύu‘¬ ϋφαŸώι»ί …‚Ή#}>_iiiiA“ώQ%I’T=‰Δb1ύ«“‡ΕχΦΦΦζζζζζfΊ€ηυzIŽ6›ήΰόσ“;w†KKMΝΝ›7σu~Ώίf³Ωr06™LUUUγƍ“Oτ‚ώ–t†ΙwbΕ ¬XΫoΗ“OβέwΏε="mG³₯ΌΌ<;Ξ±cΗ&“ΙT*E™“ΘΚIU±XŒΔ'²H$Bλ0ΘΥ θƒ”“‘V¨”zώωρ… γρΜΕυτ|“–‘t¨„a2™L&Swww  —Iκ‹Ό€ύϋqΡE8ο<άq}=†5k†Ύ'™LR‘ˆvz°Ϋνv»]8 r0£ΡXWWG99$ Oτ'Υ#„ώpx•|φμΨ’%Ρύϋχίoόμ3έn7›‰Diε‡ΎΛf³Q EΤ`ίΎ}:ŠƒΘoa7bγFL™‚;ξΐυΧγΓ±nrPίtx Χ500@ήΕn·οίΏŸ–‡ΓαˆΗγξάSƒΑ υ`Ρ%₯Ψ6›|˜Ρh΄Ϋν€³‰Σ_<8}zΌ½έΎp‘gΟS,βεε咚EBμο留6›­±±Ρd2 ™œκ=ύ'8yjj°p!ζΞE&ƒ©SΉθ’ίυφZ.½τgžy¦¨¨ˆΔAΊ\.ŸΟGΒ"!_Ÿ‘$‰€Cο4 €Hς1ΕΕΕΥΥι‹/>4kVΜd2Ύϋnρ»οV:d9xπ €L&300P__OΚ‹Εb΄^„\Ε‹–)ώ †/ΎψB_ξ …&,ΑΤ©Ψ±γΏ>ϊΥ”)ŽŽ1«VνύŸ±ƒδšά)έI&“GΛd2J€(ΗJ§Σ’"o6›kkΣ§ζ‡H{nφΰAΛΆmΖ·ήrvw—ΣίφυυΡάΉά«ͺͺΚησ‘O"_E·’RͺΫνΆZ­,,!IR]]έm·ύt̘Iϊhϊτdk«56ψ|žΆ6c{»υ― ‹₯@"‘p:δBh姦ΖS\œmhˆͺͺ Oœh²Z1fLrΛΫ'ŸX?ύΤΆoŸΉόΐΐ@EE-Κ'•΄’§§gpp°¬¬Œ^”ΧάIΎŸ~ϊ) KPω ΐ 7άπ裏(//―«K–—ϋ¦MσVWGFNZ,‰Ι“3N§40`0„B³.—δre].i`ΐΠΪκ0ξΪ•lk3ξέ‹/Ώ4%5‰D‚\5ZΥΧΧ#^E₯κU)ΒGR©Tyy9IŠΊt&Mš€GU‘’χ£αv»ηϟ\Bc·Ϋ;;±m›ν½χβ™Œ° z›έžr:³GΦ`@2iF ρΈY|άLGGG"‘¨†Νf£r έn·Νf#ΧE ˆ‹…r)δͺΡh”ΤΦΧΧ'f”&LcέQΈΒΒ₯„P(d³Ω<Οη£6•h4šΙd’QC4j²Ωœ¨Ϋ˜>G™ @yyy:¦&Ή,¨~QSSC₯Š@ @ ΉžfZ[€p™ΙdΜfsJΟOΙΞ·%α IYguρΕ;Ξϋξ»/‹ΩνφX,&κγn·»――ΪΟ8N‹Ε’J₯ŒFc:¦œΥj5]]]@ΐj΅:’‘Λε’šL&Ί!-*FΊu2ͺbFZpsL—Λ%IR*•ςz½Ϋ·oΧcD^.ι ŸΎΎΎͺͺ*Κ ‘[ΞmN§³²²’< M 3™ŒΨΊC—%%%"¨QΕ\θ€φR‡ 5{9NŠžD θιι—‘P( ™ΝfZ ²ΩlςέQpΒ’$iΪ΄itς½’ΝfM&Ε#P__ŸθL’Ρ¨Ό˜™ΝfΕςb, ƒ”‰ΡhTΕΪΫΫEΗ)Z τx<ˆNguu5ΥiŚ·~S+AΑ ΐΐΐΐW\ΰΥW_5™LβD†d2YSSC› ŸΟ'R¨ΖΖΖ²²2›Νζυz©˜ιv»KKK…@#‘5rΡ%ω'Ρ•%IR8nkkC&Ίhϋϋϋi.  €€€»»[§q›ΌIΫΕj€L&SVV‡%I"ΟA±’šŒι=Βύ^―—2qͺH0™L‡"‘ΥΧΧg³Ω’’rr43G9tww P+3•οϋϊϊ"§μ@0e(,aѐ4~κ©§λD Y€R©žžžϊϊz±L#‹v‡έn§Τj``ΐh4ŠΌ^―Α`«{Τ#‰Dθ>ΕΕΕΤ» N[,–t:-q $8½μΖ9…%,bΡ’Eρx\tΌ WΜ,))‘β€Νf#­P6 €²lͺΌS/聐++P-^’$ς7N§SδXδxL&S8.в:ϊ.δj€”μ0›ΝTVΥ5—cQ!@\“»"δυz#‘-ϋύώd2)of›yΫŸYι\Τ<νC–tI_ΕΆU™L¦··WduΊ¦€κX’$UUUΡ3V\I;ΊR©T<·Z­^―W¨Έ%veY,Zl&UWW{<ςLΤe‡c±ε^Tρ’sŽ(™³Ϋν™LF„6RžΨ΄HJ2™Lδη Ckk«γ 0ŠΖ_B^@.re2zΡ`0„B!šχQΈ=zτΛ[όΔ"Υ&H1r‰ˆ>ρJ:—΄‘_ΎYCΏ–°Di@μ$ΫΫΫΫΫK­δ6(`Y,’ˆΙd"ΧE>€8΄Θλυ–––J’D©…Ώξξnκ9;QTTDA“ξCωœ(4δ…",I’š››―ΊκͺH$²rεJ“Ι$οnjj’]hί3r]+άnw0-S£¬ˆŠ¨β,?Ί­Hήιm@iΡνvG"±ŸΎ‹φ$Α`Pοq•Όϋ|>šΔ~Ώ_t”hjjBNΡhtppςn Žρβύς4€ΣιL$ϋχοGRšH$†μφ‘‚ϊεεΖδA΅]NAx,I’&MšDγ²²²X,Fš¨­­pθΠ‘@ 2­T*UTT$–hΘ—ˆ“βρ8mαΚd2€Όx<‰D(œ8pΰ€Γαq“ξS\\LQξζχϋέnw:­­•Ÿ<ƒ\ Νtοr‡ λ²Λ.°bΕ δR(Q«„μˆΗl6K ~qq±Οηs:”f‘°(=—IcJΪθhα™ι΄Α` r)ŒτGž’4D’’[%“IΏίŸq… ΕTk̘1cƌ‘/˜”••ΙuIUb /‹QωŠ.©…A~^ˆΓα ’ΆœN§Α`₯2j€)Ε_ΡOA«ΧτAyΑ6Θa‰CiΗͺU«<Έ{χnΙd2‘HΕ„B‘!iΣξέ»εyOCCƒΑ`­ž555΅΅΅B1V«UΎ‰€Ιdf³™<’Νf#Uy½^“Ι$h2™΄X,yγP ‘2yΥΤΤP8£u’Ž( 0™L4Χ£¬‹œS:φxQθΒ QXQQ•―(…’ΗώL·’’ωB’υi!W}₯ψHλEβa*τΪγEγ]»vεSD!”Ž:n4wuu‰‡Θ78Πώ-%©Π ž‰"ξπωηŸΣΪΪΪΔΜȝPΗE[ν%Μ3ςκ‰ˆIJ•ηaβδ‡ΓA ΜΤχL"£JDMM y2Jήir@ δ -K8ΦΧ1Γ!―~Œ’…ΥΥΥQΆ$ο ΐνv;‹ΕBŽώJήΪ@lωύΚ,K΄υ ηXΓeHvΪi§‰'_Š(™J₯<(šΕΚωΉH$"IM)9«­­•?v5Ÿ`a7€0α‡Ιy½^JΌΪΫΫ+++Σι΄Ψd&Ξ™‘K—ΛEΣIϋχοΟ?w…'PΨ駟NΒ2›Ντ CδŠ[ς>ˆx<.6^ †ƒζ₯°Ψc,GVΒ&Ožμt:€θqhooohhΘd2β©L.—Λh4ʟ¨“g°°FŒ! «­­u8Τ6(ήCΙ–hΨΚWw…Κ!ΨYg€V$)ƒ΄κœ― Ψc)Η·†HΑ`PξΓζd‘rΤΦΦ655 Αε%ωι‡5ސTΎΖA†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a˜ηDΞgΚοØ#9Eηx±° Šϋηζ‡41ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒά‹¬Ι&IDATb…Ε( ‹Q£,,FXXŒ"°°E`a1Š`VΫ€cοή½4hii‘AYY€όΰͺΩTΐ°Ηb…Ε(‚ŽCα-·ά`ƌt9nά8Δγq<όσͺΖ€=£†ψŒ$IΓ‰|πdΈώϋi0~όxTVVB–³οάΉ“W_}5 V¬X ΈΈ˜.Ÿxβ‰Sdk~qbάμ±E`a1Š έδύ‘‡’Α”)S$ Ίloo§ΑΗ ‰Π₯Ιd’ΑW_}Eƒ3Ο<ΐΆmΫN‘ΕŒ φXŒ"°°EPVH“Έ³Ο>›.«««iJ₯hπΕ_θθθ K›ΝFͺW%“Ι!―ΣJ€K/½ΐ[o½E—‡’Α /Ό0RΖ<+d4 ‹Q„S=+\Άl Δ Μ€I“ƒAΊά±c zzzhΙdδwn™bŸˆ€"&φφφ€ζυυυtΩίί?’?…9μ±EPπϋύβζ40},£Σι”Ώn΅Zi ]ΒuΡ<ΰΒ /€Λίώφ·4ΩύςεΛ‡ω+ Nή ΑΒba„“χ{ο½—cǎ₯ΑΔ‰! |/Ώό2 Dh#„³uΉ\4Θf³ςΗγ‘K±t388Yχ•β ΠΩΩI—§Ÿ~: ΎόςΛ“ωΜp`Ε( ‹Q₯f…’«nΧ]Ε)ΰΔ+EFjίΓ³B±Β#ΚZv»²Π)Ϊ„m%EλάsΟ₯Αϊυλε·’~@ζhπ¬Ρ,,F”ZΩ·o €t:M—4•ΰυzεοw»έ4°X,4A“֐…A8¦…TΘy(ΌŠ₯!QJmnn¦ΑŽ—1Γ‚=£Š/ι,Z΄@II ]ˆUgςUb‘F,ιˆ•rrΒ₯E£QAζαΔ…«£;ˆΛQ£FΡ`Ϊ΄i4xηwΐνYί'οŒ†`a1ŠpŠZ“―»ξ:ρYˆHGΩ½(G‰d|ΘZxƒ€lχΉΉP–ˆ‘’T6oή<lΨ°@WW]Y³ζΈ~Tΐ‘Ρ,,FNQkς‘‘Ž" r³6ΡD¬ε(zƒπΖb@!RLESƒ˜`„QάPΔDΪφƒ\ΏƒˆΉΜΒ‹Q„S½―pρβΕ4ε%Bψ'Q@ XCή)JπδŠΔβτ΅gδ²ψ#] pi3gΞ°yσfΊήτΉηž;±_——pςΞh£ͺm±Ώώϊλi@λΣ"υ›mΔ+”ζ‹Kϋ(΄™ΝίΜ?D?–P(Α‘Ρ,,FΤ?m†V{Ž,S‰I½"Bž…4©A²Ύ ΡΣLσGιΔ’Ž˜6RτΟώsΊ€f>Ÿ/½τˆόF]Γ‘Ρ,,FΤ…Δ’%Kh χD!”¦"ΐ‰7b-HΤQC‘ (hŠCΡ-ήIσMΡχ'%Sξw‡BFShΕc D}Kdλ”•‹#b„'£δ]\Šuξ!7Δ}Δ`ΘΞDQίΊφΪki Ξ Ώzϊι§GμηιφXŒ†`a1Š ΉPx$W]udύ["‰dB˜$B5Jˆ8δΈeρ‘ΛŸwήy4±ς“O>πβ‹/ŽδοΡ  ΑΒbA‘X°` D‹B‘¨c‰.ρ …]~ρLƒΒ\ααPΘh{¬!άvΫm4ž‰όHή…'¬,ή εεε4ψαY /v­]»V™‘EΨc1‚…Ε(Bώ„Β£1ώ|ˆ}>‚!&+9β9aτ(MΡ©,φζΤ"‡BFC°°EΘPψάtΣM48ς`fzψ€x^υΊuλh@5³yς‡BFCœ Ηq;-“OŠa†a†a†a†a†a†a†a†a†a†a=ραΑQz$IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/ellipseroi/roi_getarrayregion_inverty.png000066400000000000000000000227351421045507400313750ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ IDATxœνy”TΥ΅Ώ5ΟSΥs7Ѝ -Θ OM"Hβ3fιzΡεΑYp™6Q³Œ!YΏ¬Θ[€ £Ζ9ΰ{}1Q—€ΡxΟ|¨ 4 ‘ι†nzξšηΊΏ?Άuri‘‘ιΫχVυώόΑ:·κή[§¨oο½Ο>ϋœ 0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0㠝ڏH’D Ž™ΣFΚQYYΩΠΠ δUΥξΐΈ€4ΤΨΨ`hh@*•RΉO Γ¦XA„M:ϋμ³;::˜ΝfCCCΕΕΕΪΪΪ Υ²Ε}„žΞ8γ n·{χξέ΅΅΅Βα0€ŠŠ z½^½>* kΤz"—788h0άnw,ηX,ω%ΥΥΥ’$€Ρba$©3f$“IF£1 qB]]½^o0βρΈN§#ΣU’"XX#G˜(ŸΟ ΊΊΐ‘C‡ψύ~δΐf³θοοΰv»ΕŒΖ‚ύ/Ψ/¦BOΣ§O'έΈ\.dΤΥΥE"yE#ANGΧF"‘ήή^•••&L(HoΘΒ:Y„žͺͺͺΘΝE"ρΊΣι`2™Θλ$5q‘ΛεjσϋύƒA’$“Ι4vίa aaRΖ€I“$ ςψ €Νf# T__―ΧλΙ\\^^žL&ΙΊPά}ΰΐΚmƒƒƒ4ϊλξξ`΅ZTUUhooομμ$' ‘‘aίΎ}dΜD*Λ`0ˆxΏπ(΄ΑΘ IΉέξRδLι‰~ψΑΑAqZEEE<§Α εθυςςrz±ͺͺͺ««+ΰθ€Œ’}}}‘H„L)Μ`0Πύϋϋϋ il8~-–|”G ‹ΕB./“Ι †Γ‡#—£Bn†ό AΑ“°:Α`ώυz½'yλιι@’ yΕγqŸΟGΪ*$ ηOδ$‘‡P4GΦ…¬…^―Οf³δΪ¨Αησ™L¦t:\:Š2 ™L†€FΑ;έΉˆ‹ŠŠ$I"ρ‘F#™C: …BhNzǎc΄Ζ‹Εzͺ¨¨°Ϋν‰Dkkk6›P^^.IR6›%[Egz½^δ~~.—‹„E;xπ`2™τxŸΟ`0Δb±d2IΪ5 ύύύVHSψιJΤΦΦ>όπΓδ›Ί»»Ι&εεε½½½d`d2™’’’T*EΚH§Σ@@ξΒL&“<«ιυzc±˜˜iƒCCCΑ`°¬¬ ΐΰΰ ‡υGƒΑh4JρY!QψΒΞ%‹-Y²€ΊΊšΖ}f³Ωb±ˆ,”ΗγI₯Rr³1mΪ4y–‘½½]’$§ΣIηtuuuvv Ε$“ΙD"!†2™ Ω<ιtšΜU"‘ ML&Cau&•J•––LBkΌΈB‘+:pΰr³{D? ˆr ‰DΒb±ˆ΄‚έn'wIq…GςŸ?‹‘v-K"‘ˆF£6›M€@u:‰L <…½τz½tsI’Μfs!e ίb Φ¬YSYYΉlΩ2:ŒD"n·[ΤdsΠa0ŒΕbτ.Eλ¬V«8!“ΙΨνv Χ”–––––ŠψΜj΅Ϊl6’/ΉT“Ι$·εεεV«•R_ΘωΗaΥZyΝΈ°XdŠ<€ώώ~‘†ΞΞΊŠ ŸΟG`2™„‡Bnp'l‰ΥjΗγΩl–jͺ(`—W(ΤΦΦ’Χ#Hš"λννu:τqF£±½½Gx½ήΒ(vG«¬¬Œ*’²1βf]θu:4“‡ΙκXsˆσ‡dΡhΤb±L˜0€Λε²Z­‹…” ΐl6›Νfy)i:¦†θLU*Τ—9!/½τ’ΛεΊώϋ3™ e,Ιύ‰ΉΌh4Ϊίί/Χ_8φz½dTβρΈΫνφϋύ.—‹ςeeeeeebL'I0ΑF“ΙDΩό’’"‹Ε’J₯D„ησω|>ŸΠ7e"”ώOΖ…+DΞR ΉιyuΰΪΫΫM&“‡ε !4–$ΉD£QN744&L˜@18M –——Σωz½~XŠΉ\ΌΥj ƒς°½ΈΈΈΌαx–@ž‰Dθ&a‘- ρxάj΅J’$RΕΕΕύύύTe%ͺω"‘ˆ\dΊ(ιe0ι4Uσ “"‡™4Ρ¦%bς+Ιο?‹SB’€ššwέuW&“yβ‰'Δ[ύύύδΪ(¬₯₯₯Ά“°hd'*B=iˆΞ7›ΝdH@‘H$“ɐFΙ΅‘vΙιtΊT*EνX,ζt:γρ8Ε[$8*ΧΩ΅kW^­qcιtΊŽŽ³Ω,―³"ΡΠ4p pΉ\T‰PZZJ~MΨ$›Νf³ΩhtIX,ςͺ$“Ι‹ΕH^‘PΘh4ΚKR9θfI d· ƒίο—υωΛ8–ΰΩgŸ΅Z­4R£WŠ‹‹‹‹‹…ΰ‰D0€Hˆ~ζςςς’’"©TΚ`0ΤΦΦ6š+Y¨h4*Rbd·2™ŒΫν¦ΛθυzΉΈI|"C–N§i©O^3Ύ„₯ΣιφοίO©π{ξΉΗf³ΥΥΥω|>aoΚΛΛεAε-EΠc·Ϋ½^/9G²p‡#›Ν’Ν³Ωl@@>λwψπαΓ‡ 644ΤίίίΧΧΐh4¦R) iV€Β\ΉέήwΑ;!ΜAš ΆΑ`  *³Ωl02™Lwww6›₯AαΉ¨\”5™L6›MΤΫθμμŸOξ)6i»έN±šπ\N§S~~]]ΕοoΡ[$8 ή)˜Σιt}}}’N0›ΝVTTδoBkά KNII PXM©‘§h4‡Ι‘ΰhηqΉ(W§K()+£Ρ˜Νf)γεt: Γ0θσωδK Ιχ₯Σi—Λ …‰„δ#γѐ$ιν·ί°lΩ22ς₯έέέςCrj"“i6›E™Ceee}}½Σιž«―――――OŒ7θυzQ–C'‘ΗJ§Σςͺ *9”Χζ)γΡb‘7œ;wξ°ήγρτττ«κιι‘ίήγqX­)‡C²ΩΒ: S$’Εt™ŒnΨκψΎΎΎςςrΏίίΣΣ#JE-YΎόγt²Οη#XTTδt:Γαp(J₯RTto0‰„Ιd:ηœsςΤŽGaαpxέΊuMMMδ‘μv{mmbξάĜ9p]]`H̘‘1ϊΓa]$’ υƒδpHN§d·g3]k«5Ροޝڿ_ίڊ={ ‘‰₯'h&›B±H$’H$Ό^ο€I“εŠ”―³PRRB.΅³³³₯₯…Š]σ‘q-,’ΤΌnέYΐΦσΟοΩ΅Λ’JιzzRΝΝΦwήqςIp`@g³y$ »έNAEύ~ΏΫγΙΤΥΕκκR~`ώ|£Ε‚)SΪΆm3oίnΩΎέ Λ?΄΄”2XR©”ΡhtΉ\œ9r$Λη E¨wξΉηζ£ΡΚ³ξŽsζΰοΏ£žœ2ΕΥΡ1ρι§[Άm3‡B,χ£€e2™tΉ\δ)€ΡΕϋιtΪb±P‘•ΡhτϋӍ}矟Όΰ©»ΫΈs§ώO²wt”Π»}}}™L† yΖ²²²ξξn2™”h [ 7j6›u:έ_|ΑΒ4ΥΥXΌ "Αœ9+υ__0]qΕO<ρ„Ϋν¦™ώu8έέέ4.ΒBβ… H‡ƒΚήiJGL]—”€/½tΰϋߏ:ϊ·ίφΌσNEo―‰ζj2™L$©&E£ΡL&C 3J«R’‹ $ KΣ̚…@UΆlΑ† hi αoΎωfkΦ¬‰D, ύ{φμ©­­5” DΙ‹’¨V«ΥιtONVΗb±$“I’¦ΣιŒΕbv»}ςδΜ₯—ΎρxW—ε‘G2»v}ΉΝ_uu5ΕμΘi(N‹+e7θδόVαΗX ΰΑαχcωrlΨ0ό]Z΄ΰΘ‘#νbΗ+++Σι΄Ρh€Ε₯‚Α`ooοΔ‰x½^yHDSdͺ²Ω,ν5  ΅ΥππΓVΐzωε‘§žŠtuι–/Χς‰]Ύαε#DΪ=‘Hv«ͺͺΔ2‘ό"ŸώN•†¬[‡t«WcγΖcœ Iy.^Όΐš5kJJJβρ8Y,e€ΘOƒΑl6K…b,ωš0ΗCα—˜jD.–"——Νfυzύ…Ζ/KRζ'?q:dΰv»εY/Φ¬Y³tww8p€-–&xόqόΫΏ‘© ›7ν9”Π"mQ^ ――ΟιtF£Q2`'N€IGX»έ>¬jΚεrΡ ―ΈΈΨοχ“"‘‰‰’4 Β>ψΐϊφΫ™ Rόcπw¬ΏϊΥ—Y.I’Θ@f2™T*εσωΔZΘΎΎ>—Λ•_cΓΜΌ_}5’Q>ŒΙ“§*ΗγeeeΓVΧθt:2<.—Λn·“Ϋ"aQ\,ŽhhhΨ»wοή½{鐖βˆ<{"‘Iφ-[L³g»;;3ψGο…~Ήv£΄΄TΎœš +φοί/_”/š°^y³g£Έ«VΒUΏώυ―#‘Θύχί€Κ—ΛΛΛΕR" ‰Rœ$IδΆB‘p ηž{.€ΆΆΆΆΆ6ωεςΊBͺ;­««ϋΛ_¦648ζΞ•ž|2$Ÿ7”—Λ%Šσ…ΒΦτιθκΒ»οbιRΘπNΧBCD9 δv»E:•γ(--uΉ\‡CΎ³£¨v'::::::άn7y½φφφP($ne0***δ{†B!›­tωrίΗ[φ·v—«S―Χ»έnρL± %€'Ÿ|2J DXMMxν5̞?όa$—g2™§žzJX*« ¦!Ϋ"†’½½½bI»έήίί//§•ϋΓLΉKΚr‘‹€έD"‘·ί.ώΑ&ΌψβΠυΧGu:N§™wMMMwήyηHΎ˜zδM0xΦE(„Ÿώt„—‹ψύξ»οφx<+WD.„J§ΣryQ()ΜγρX­V’€ŠŠŠ2™ •9ΙO¦ ΙΨPθOl\CΕγρ»ξκ°Ω°zυ€L&σγ˜’+ϊά^x@sss^„πy?*|σMl܈uλF~644ˆj`aΊιt4»’Ϊl6J²;y@MŽO¬(--• v»½――Ά ‘Κ³Ω¬Χλε90j“-\ΊΤψΦ[Ώυց7ήXH/ώκWΏBξ*y!)"o:zLΆmΓcαΏώλtοCΒ K–,πΖo]!#t“N§)σNο’Άh”744TWWΧB)VωͺC™ΝfK$4cCο…dFγw܁œt¦Nύβ_ώεoίψFVœμχϋ{{{iωP^Θ+-VK nΊ Ϋ·Β­Θh‰=H ^―Χj΅F£ΡšššΞΞNzΧ`0 ΫoH―Χ‹΅Ρ’ʏΌa,σz½===’²T<ϊK8Ω[nΉ%›ΝJ±>ϋμ³Ξ9'΅qcίΕ—Qgςmp4NΎ «ΉyΤT%0™LςενΩWYYΩΧΧ—L&…Skoo―­­Šioo'iR] ©GZΕγqځl6SQνM"6 yωε—Εnή?‹Y7nμΎμ²šl6KΒΚ [EδMGεlߎ{ξΑόΟhή“žΰ»ξλυ«V­‰PΗCσΚ€rv»vνBn7,’#₯U©ΊFdΫM&“Ψԏ’¨oΌ²ηSΌϊκ«­­­ΘΝηθt:ω£ϊϋί~ ό5$EδŸΕzσM<ϊθ(« 9o(Ιΰh—ηp8δι’ͺͺJžS ˆJΎ©ι)‘H€R©ΫoΏ€$I’žύ‘‡’Υ`r(’‹u:έUWα?Θ3U!ο„΅nή{o’υγ0Μ…!eΛ4§Σi¦ΩνφξξξP(d΅Z)SERsDΏώzωύπ‡?ˆΨφοί_UUUSS‡“Ι€X(·O―ΏŽςr<ρξΊkτΏ¬rδӟBSjj°t©Rχ—$‰JbH oΌρr{{ gHv΄΄†^§ΉgͺΝ’? …Fy΄€΅kΧBΆs€$It2ωίζζf7„ZΎϋφαι§•ϊξ£NήX¬ιΣqηhlTπ#ΘNœ8Q^Ή@?ΏΥj 5dW„—€Y<α@τ£QN2₯o½υ€‘‘!£Ρ˜N§Ε°ŽD)0„zΰ΄΄ΰƒ›ΰΦ:y#¬Ν›1wξ}Φ«―Ύzν΅Χ{T„^―—―Ύ%V\.Χm·έF“0b’ζ±Η@&€Ϋν¦f ΫqŸRH~ΡEψψcδΛ"Φόp…/½„M›πςˊΨ\ωΑπόσΟλt:΅Q‰Ί^―§αε²Ωμ½χή+_B½bΕ δ η<•-X,–έ»wΣ§ŒxˆwΛ-Έΰάzλι~Ν1 ,Φ•W’³s,TΩΖέbπ/ή’'„Γaz.ά/~ρ 2i4NόνoKα„lΠ©Q΄n±VΔ/9vͺBΞhΥΧΧ‹ŒT$ωΩΟ~``` ‹‘k{δ‘GδW‘‰’Ψό“O>·υξxδ<ϊ(|pΤο=šhΪbΥΧγ½χΠΠ0֟KΒΊοΎϋΛ§ΣΠo```υκΥTb*διχΫsLc%okΓόω8xPιΟ)P6mΒE©πΉ’$­X±bŊTPϊΠCΥΥΥY­VΪοζΜ™‡γ¬³Ξ:묳θρÞ7\r ή}w,?π”ΡΕZ°<€ο}OO—$iεΚ•4ίχΜ3Ο χΘ.2WTι ͺ2‘·iώύί±uλΨςI‘έδ₯Kρψγjv€QD6›5k­ςΫ»w/νs¬Λ‘Jχ\λa–™3»š  ιΩ³g/[ΆlΩ²e555R5»u4;wbζL΅;‘_όΗΰškTξƒ$I$,­ιI°hяŒS&Lΐ·ΪΘ­ΜΡ¦€[·’ΊZνN -ΖX·ήŠ-[Τξ nu’|πA~Μπh‚}ϋπ•8ζΨL™‚={ΤξıМΕ:η `~΅ϋ‘'΄΄ ΕμΩjχγ+hNX‹aύz΅;‘W¬_E‹Τξ„φy}ΘΆXgNLyω±wbώΙ™gβσΟΥξD²{7¦NU»G£-W8o>όPνNδ!~ˆyσΤξΔΡ°°  K[tvBΆ{s²ΤΤ ½]νN†,VCv̉9Y::°gdOκT «±²§ό1§F2©μΪΈSECš<­­jw"oimΕδΙjwB «@`a}- Ψ·OνNδ-ϋφ©°8ΰ8hHX.—FηSσ‚–δv›gŽ&Cn”―œNΘΆTbr8z«ζ”ΡTI’V\‘Σ ΩΓ°˜‘ΓιT»9΄",—‹-ωι i(̊°Ψb>l±˜ΒG+ΒΤ_[ž’)«―ai*>ΘS8N=œn8}8έp i€Σœ 9šςƒΠް|ώ9ςν1’’ΈX[Λ4$¬PHs+ςˆ)S΄`iHXZ›ŸΟ/΄V’!ai­’(ΏΠZ5 «@`a}-ΝΝ8ϊω’Μ)`6£ΉYνNh^ώ52xωΧ ΰ…—#Cƒ }YX…€…₯-xS‘Α›‚œ€/Ύ@Ooctj”—ΉZShKX>ϋ G?κ–9Χ_;Υξ„φ9ηόοͺέ‰ΌbΗ-n©ExsΫ“G³›ΫΤξΐ1()Ac#sNŠ»οΖgŸiχ‰:ΪB#Θ ψ§@[z{Υδ‰φY΄‡αΠ!΅ϋ‘G¨ώ¦Ό@Λi’Ε°cΤy fΎpΙ%θμΔgŸ©έΌcΑ­?DT]6mΒόωjwβλΡ¨Ε°e ŒF6ZΗζ’KNσ`p€ΤΧk«άV;΄΅‘NνN-汃ƒ¨­ΕŒΨΆMνh‰ŸώϋχγT»ωN8 ‡CνNhCCjw’0ΈςJ<ϊ¨ΪΠ Λ—γςΛΥξDΑπKΈα΅;‘nΉΟ=§v' .‡Χ`aϋq0ͺ݁“ε’‹°y³ΆΎ (ίϋžΛοΠΰ@λ‡ΨΌ9Ÿ’/š~’φ0ššPSƒ₯KΥξ‡ΒΨ}Ύ›_~yΰΰA³Γ@o4κυϊiGnmέϊi΅;W¨¬[‡ώPνN(‰έη»oλΦ3/½Tώβηώφπ‡zcήΈ—ΌδΝ7 yXΤτη?ΟΈμ2ω+W]…Χ^Γ7—,Ήξ™gΤκΥxaϋvœ{ڝP€ΖK.YΈnό•o~ύλ—ν[7l¨3G…nνΞ‡σΞΓsΟαΌσΤξΗhγ*+3Ωlβpή<¬]‹o}λΛC»ΧλτϋΥιΩ©“—ΒΠ؈_,@m ΎωM<ρΞ:Kν~Œ”|€)S°zuAΕ[mm£ΐUWα±Η†«*›ΝζQ"+ίyσM45©έ‰ΡγΦ ~ώό΅―½6όυoψΗW­^­FΖ1kΧβ±ΗΤξΔ(±|•ν·‡?<鍊δ/.Έϋξ»7oV«K##Ÿ€Η‘© wή‰ο|GŽ¨έ•‘RU…χίΗͺUxξ󒧟ΦιυvŸ$©Ώ­ν?οΎ[νŽW¦OGW»Nν~ŒˆoΔ‘C˜2埯L<ό—]6γ²Λ*gΜP―_LŽW^ΑΓC6fΧ:N'}/Ύ¨v?˜rυՈFqί}jχγ$ψΙO αΚ+Υξsς<ώ8Z[΅[pρΕΨΏ+V¨έf44`Σ&Όϋ..ΎXνΘΈδΌϋ.ή{“&©έζtX°ο½‡O?ΕΒ…*χδΊλ°s'ή}WΣλG‹I7œY³πΐ¨ͺΒ–-Ψ°--'{aΝμΩΣ§{±³ΉωΠύίIήaκT,Z„ommXΎ|Ό¬]/Β"ͺ«±x1.D$‚υλρΚ+θξ>ήω&›ν—{φμϋθ£a―Ÿ1ώ«―O'“ΗΉΆΌ7ά€… a³aύz<χ>ύoΐh›9s°r%6nDs3Φ­Γ΅Χ» ήμpόϊXkϋM,f:Φ#π*+±p!žz »vaγF,_>~χΪ§E‰;v`Η˜>σζαϋίΗͺUψτS$“Ψ·­­hmEK Bρ―½ƒΥŠ"LŠ†LžŒΙ“a±`ζLl݊­[ρ›ί`χξ1ϋ6Zd|ΉΒγ3y2)»³―ZΊ―r†B_>fι„Λ…τΖ‹ϋv}…ώ)Δζfή€99Θz½¨©Α΄i˜6 55πzΏΦ2‚qκ O‰‘!^Υ~Κ°°N€N―ŸώέοQΗ!Δ `aT,φώͺUίΎχήa―oZΎ<H¨%†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†a†)(FςZI’F½Œ–£G³°Ζ#ϋΉυ£ή† ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε( ‹Q£,,FXXŒ"°°E`a1ŠΐΒb…Ε(‚QνŒ­­­Τhnn¦Fqq1€ .Έ@΅>cΨb1ŠΐΒb!]α=χά`ήΌytX__Oxα…Oμι鑍«««ι°ΏΏTΏ s<Ψb1Š xπ~ύχC6Cφ ΐώύϋεήή^qsjθυG‰>RΓn·Λ_7›ΝΤ‰.aΊhπέο~—_zι%jˆθ~εΚ•'ω-Ζ3Ό3‚…Ε(Β(ο?ωΟ©1i$jL:2Ηχβ‹/RCΈ6B[‡ΓAl6+oΈ\.:S7±X ²κ+α:Ε ‡pψπa:œ1c5φμΩs:ί‘9Ψb1ŠΐΒbA©Q‘¨ͺΫ΅kd~J88ρJ$Μ3RωΎ2*3<"­e΅Z!s’ΜAτΌ€Θc}λ[ί’Ζ_ώςω­¨ω:xTΘh£JMι8p€6› @:¦CΚπx<ςσN'5L&5„Σ$‡5lbG ©A.²Ir―bjH€RΟ8γ j΄··Ÿϊ7cN ΆXŒ"(>₯³dΙ>Ÿ©!fΙV‰‰1₯#ffΘΘ “‡©αv»!³pβBaκθβ°¦¦†sηΞ₯ƟόgpyΦ‰ΰΰΡ,,FΖ¨4ωΆΫnΧRCx:ŠξE:JγΓζjΔ κƒΈˆΝEƒ °„©²λ―Ώž›7oΠΩΩI‡/Ώός)}©q»BFC°°E£δ―z:ς€ΘΪ„GΎR€£θaEƒ\€$Š’1ΐ€£Έ‘π‰΄μΉzαs™Q„-£c½πφΫo§†H/Β>‰Ί(ΐv¦HΑ“)“ΣΓζž‘‹βΏj…IϋΞwΎ࣏>’CaMΧ];²oWpπΞh£ͺ-±αH šŸ‘·Xl#^‘0_ ίGΝhόrό!κ±Dƒ\αW'‹Dη!›ί²e 5ž}φΩΣόv…»BFC°°EP·šνωjšJ βθας„+$§IʐΥMˆšf? O'¦tΔ°‘Κ ―Ήζ:€bέέέΤxξΉηFε;ζ5μ  ΑΒbA}WHάqΗΤ…{"JΓFαΰΔ „˜ yΤ@ @ rš’ΖP”G‹3iΌ)κώ"eΚυ`WΘh ­X,Θo‰h’r±EŒ°dΌ‹C1Ο=lα†Έh [™(ς[‹/¦†Ψ€ήZ³fΝ¨}½<„-£!XXŒ"hΞ~•oΌ²ϊ-αιD 2!Ί$\J8l»eq‰ˆεηϟO α+·mΫΰχΏύh~Ÿ|ƒ]!£!XXŒ"δ+$nΊι&jˆ<ΉB‘ΗU βry"&†ΒR’KάPxΫ‹/Ύ˜―ΏώΊΌ/ΌπΒh}<‚]!£!XXŒ"δ+ό*·ήz+d₯ρΒ£‰a£¨a'„υΓͺζΕππόσΟ—ίA<Σ`|Ξπ°+d4D[¬aάwί}Τ–‰μή…%V¬,N’’j\xᅐ…πbΡ+―Ό’Μ—Π"l± ΑΒb‘p\αΧqΓ 7PC¬σ {4˜ΙΟ £GiŠJe±6\Mς°+d4 ‹Q„Βw…'δΞ;ο€ΖW7f¦‡ˆηUoΨ°”3'O`WΘhˆZ¬Qο£e ΙA1 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 “Oότς€²o›₯`IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/ellipseroi/roi_getarrayregion_resize.png000066400000000000000000000171041421045507400311700ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœφIDATxœν{t•Υ™ΖŸ\άC’‚ΠΚ”α¦X—΅€ΆK.vpt΄Khuμ%΄]UK—·ŽX(©XΔΫTYΪ΅Δ§ŠUQ**•› A¨ Ϊ @„@Ή'$$gώx8Ϋ―A½ΟΙσ[¬¬ύσ]ή“Ό<οeοο;€B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„QKŒk|gτ•W~ύλ^όhύϊmώ³{"…XΧxMbrςu‹ΧUVvϊχ£gžIθΩΣ΅u"bILN~ ‘αψΧln–c)–°BΌk<’W/ ϊιΏP:[{aί>€€ %ΠΠ€e=°~=Άm€Rμά‰R”–’ΉΩν'πˆξžΌ€ργ1nFŽΔπαΗόƒvμ@G\ςχ^«ΌgH ύ 8ζa·}άότE™ρ1-Γ†aθP rΜ·oΗ–-X³«Wcο^ןMt='’Έ»vaΟ<υ~όcœuΦgμvͺ9ΦΠ‘(*ΒβΕ(+ΓΗ£Έ—\rΖm ΊW(ΌΰLŸŽιΣρ·Ώα‰'πψγΨ½ϋ ‰‹›t睝^Œ‰ύμά”R·hbμXόβX²%%X²χ_ώD έ%Κ…Λ/€%K°d >Ω‡O™2ψ‚ :½ΈsέΊν―Ύz’gΘΚΒ΄i˜>Gβ…°pαΙ^ZψKFŠ‹Ρގίύύϊ96¦ χέ‡Ά6Μ›‡΄4ΗƈΣ$3>ŠΓ‡qϋνψœΐε†ψxάqjkρߍτtΧֈSβŽ;P]’"Χvœ’"ΤΤΰΆΫ\Ϋ!N†K/EYζΝsmΗIσΫίbχnLžμΪρyτθ?ύ /ΏŒ‚Χ¦œ"bωr”” Ύ{Υθ‘ΐ₯—’₯%²ίO™‚Ά6LšδΪaΈ~Όό²k#Ξω ,pmΔ™ΐ§zιΤΙΝΕ»οbΟ|η;M9CLžŒŠ l݊œΘ;ϋμλ/ώuiι―KK―{ϊιόωΧvΞ;Λ—cψpΧvX`δH,_Ž+oϋξ];vž~ΐ!9C† ;φΏήμτι­‹j.» 7Ί6Β&c§OcνŠγ•ψΖW^9?Σ…Eέ€™3ρΒ °Μ‚κκ€ΜΜeΛ:wγz¦¦.¬«sdΤ)ηΪ€SfΞβšk\Ϋa“ΡW]ΥΦΠπΞ₯%%˜1£GcυκcommMΛΛKΞΜ,ο=§6~–ΌOŠό|άpƒk;lΣΡ‘’›ΛαO~‚ΒBL™ςι›©}ϊ„B!7†4‘δXΏό%ΎωMί'jΞ 11΅εεfλ?ΐ·Ώ›o>ΆYWQαΖͺ¨δϋίΗβōθBHΛΛ ΎRR‚«―FJvφ½Ί²*ΪΈψb¬\ιڈeτ•WήΌjU§W―ΖμΝ+Ο½ϊjEΓ†}šΊv+†O™RΌwοΩίϊV‘#ϋω• ~³gΟςν6t¨kΛN‚XAΪ؈œ45ΉΆΓύϋ_:{φΐ±c”½ύφς9sŽΦ”νέ«…\_š_ΔΤ©πŒ+ΐ₯ψ"Όξcύμgˆ‹ΓΈΆΓ3vμΐ„ 0oΏνΪ”HdΨ0lίξڏωΗ?Ι–w,[†pP|ύϊαΕ]ρωxΪ =›7£²΅³oή}Ηές(>Ÿά\μίοڈ‘ͺ YYˆžώΨΝ₯β Ήκ*όιOˆ.Ή+VΈ6"’XΉ_μΪYΊΊ6"’8λ,EΛ―δύΚ+Ρс={\ΫQ|τποξΪŸΩ΄ ηžλڈδόσ½{”GŠuρΕ¨«S7ωtΨ°­­7Ξ΅~ς꫘8Ρ΅Λ₯—ϊus₯/Š5bjkUž>―Ό‚– ζڎ0Ύ8ΦuΧaΓΧFD87β?pm„oTTΰŸβŠS¦ž¨λ…b]tvμΠ4Ξ—ε“O°kΎωMΧvπΔ±¦MCI‰k#’‚’L›ζΪ¨©AR’k#’‚΄4TUΉ6€Š5aήy§›.i?γΤΥaΗ\x‘k;|p¬ργ»ιM8–X½γΗ»6ΒΗ7kΦΈ6"ŠX³F-xΐΡ£ˆσϊ–Ž£G΄΄Έ6ΒΉb}γΨ°ννn­ˆ*ŽΑΦ­8ξKa»ǎ5r€ΧwD(/Ύˆ‘#ΫΰرƌAu΅[’κjŒγΨǎ5t(JKݚ…”–vϋ[ΛΛ‘Ÿοڈ¨£ eepHR]₯9‚ΔD—Έ …C†`Ϋ6‡ΧfΆnu ]:V^κλ^?šihpό€—Ž•š*Η²E}=RS]ΰ±RRπYίδ-Ξ HIqi€+:‘c9Ό~4γά±\~υbk«Gk΄£ ηΏX—Š•˜yί†)8ώΎV—Žε\£ηi†+:ι֎εΌΧΕ8οJ±’“n­Xϋχλl‘žήoΦκ{tλΥ MM¨©AίΎMˆN ΐώύhmuiƒγ€ZλhΦεΚ±’9ΆlAοήnMˆB23±e‹k#άrΑxλ-ΧFD7βΌσ\ᜢ6Η³ZQFϞhnvm„σPob5xςχŽ₯‡XœYτ•cŒγΎ­]œ>λΦαίpm„'ΤΧkπ̐‘Γ‡]ΐ“ο„4ΩΩΨΌΩ΅‘ΟˆΚJ,_ξΪO˜0oΎιڈ¨`νZ/žιzˆΓ—Η«G6x δη£°Π»―°Š,fΞΔΝxγ ΧvxΕΧΎ†gŸumΔ?Š4ώόgœ}ΆλίZ_zήᅬ”Lš„W_umJ€̘1ΐΓ? )) @ss3ί°wο^υυυrrr$&&Ψ·o_JJ €^½z¨­­––ΰΘ‘#βββpŸ¦¦&³Oώύ΄ΆΆˆpψπa³gZZ―˜  ¦¦ΖXUZz|<>ψ ‹~3_ˆϋ©aώ|άz«k#"˜ωση»6!€/Š`Υ*̟σΞΓίώζΪ”0ΙΙΙΤ' Ɏ;τθΡΐ‡~ //@zz:€ΨΨXννν|ͺΣΠΠ`ήJMMEX222TVV…Bζ]υΙ'ŸθΣ§ΒΪ–@¨”uuuζgBBΒ9η:Φ­λͺίΛIΰ‘c!,ZρνS^^ή·o_»vνBψoƒ°3Or㍇€[ΛΊΰwr’x <`vOΕβI˜31UbBFMbΥ―_?eeeFŒ`ώύ avv6dƒγΰΑƒΏύmΫΈqρρή „w¨ͺΒCaΞΧvxΟάΉνχέWWηγΡGΕ0w.^z }ϋ’Όά₯‰‰‰μg² £ZtttΨΉs'Β‰Σ)ά3))‰»Q{({܍ΆgΟ„S΄ύ{–y ͺσ°αΓ‡›Λ…B!Š\\\\Ώ~ν£Gχ,*ΚNHπρθ£³“Y³΄δαDΌπBνάΉ­ψ\|tvςΰΑρϋίγƝِ˜˜ΘΔ¨%πMmε+_AXL%ˆp₯V__ΟVgϞ=Ȋ•——››_,ΛY*aξΕ,;΄UUU¬ηΝ«zτΡ΄²²x„•7όU,<‚ό|\~Ήk;Q±‚“}LžΪΫΫ©1μ­³d•·oί>ƒ BΈ^QQaNK9δIx ΛΖΥ›oΆΝŸŸwψp―δδ„η§ΩΈχP,«Wγ±ΗPRβΪ§,ZTσΤS½·lιεڐ“"‹<σ ϊφΕΒ…Έε–»hLL σ' IpΥΫQΑτˆΊΕΌΗ”{μWq΅wΰ[όΙ6WΒP“8KΘ2Ψϋυ―ΏυV܊©­­Η’-„‹'χΘP,²p!ήxO>ιڎ.ηα‡k^{-αώΗ» Α1ŠE–/ΗΉηβρΗρ“ŸtΕεB‘kΰ #j_a6Ζ΄)99™ΣˆTφXRX2ic.Εϊ‘§εEΉΟΤ}τQβ»οζεδ ²²’' vωƒk+ό!’‹Μ™ƒM›πςΛνθžzκΰƍ‰=δuΛκ3‰0Ε"‹‘Ό›7γœsμ^¨‘‘:D ‘bQ½rss9fž’3hΠ ΘڐY#Κ[rr2Β™V0abκΖLkΩ²²{οΝzα… ΩΌ{τθQ„u‘…d«Ϋ‡~‘§Xδ•WPT„Ώό£FΉ6Ε_ϋZΛ’E{gΝΚ]³&Ι΅-§ID*Ω²Χ\ƒΧ_Ηˆ ¬\’‘‘ϊDiα:RV‚|…m*vδ™0ρέC‡Q«(0|‹Ζσ'¦J,9yςP(TTT=yrÏ~4πΐV ΔDŠ ³ΆΆ6¦kT,Β|#R‹TUaτhδζϊu7β—αΙ'χ¦₯΅χ»ƒλλ}ΉIύτˆ`Ε2ός—˜8GβςΛ±lΩ™±BδμW±Z€n1Ίδ’ζί~ί΄iΩkΧφˆtε;›gΤΆ––г=»oπςΫD#[± +V GL›†W_Ea‘kkN‘AƒΪŸ}Άv€†aΓΞ^»ΦΗΈvDƒb‘φvLŸŽ‰±r%ž}φΜάT½kΧ...`NC•β­ͺT2Φe§ΰΝƒ |‘jΔYBΑςmmm³g7|η;mwߝϋάs΅ΐΎΰε¨sΤ-ώDxΙ|UUΒΊΥμΓ³l#JΛ°bΖ‘C¨«ΓΜ™­9!ίϋ^έ]Œ½δ’ΑλΧ'»6η =Šδή{ρθ£(.F}=ξ½σηŸζΛ$&&rA:²›Ε,ηƒ>@ΈAEΝ`¦E‰ͺ­­eΏŠ΅οo?Ο#εϊλkgΜ¨ύίM5*Ώ©)&6φΣά+x"S(ž¬.cccƒΕ&O¬ύ!ΪΛP_Ÿύ ωωHH@}=ξΏƒ96©  £ΈΈqΫΆ²Έ8Œ;hξά¬¦¦Η6Y#:ΛΠΠ€»ξΒ]wᦛπΤSHLDI –,ΑΑƒ'uxzz:Λ:B΅`ςΔϋ‘™ρ°ΙΔw™N…B!Vs---ΩΩν&T\uU[c#–-‹>ό,„ΛI>τ%'‹M–xμΏWCΕβ|‹bοεχ/Dν˜Οδλ_Η΄i˜>;žx›6aηΞΟέ9 υλΧ―¨¨ΐΣO?pθαJaa!Β95κμπυŠŠŠ³ΟŽ1’uϊτ¦!CŽΌτRςK/₯lߞŒpάd Ο`ΗʟΎcπΰ–AƒΪϊχo),ά@LLLlμ1bΜbΔδέϊ„"Δ^#οΗŒp•ι<χΩΏ?OΕ2‚‚Η;Γ|£›:–‘Ό%%Η='$`θΠcΞ9ΐβββϊ‚‚’ΔΔΦοΏgΟPll¨±1¦©)Ά±1¦ΉΉαƒztt„vοNXΏ>yΧψΏ½Ή­νΨ²ͺή½ύΜ|Ί’²o„B‘!C†ά|σΝ,X€p.•ψ²eκΟ°³@NJgJΞ fΆ=ƒ‹d‚7ρέ`η“Η—&›‡qΐλ²CA«***ΌΚ±’Άέ ά½υϊ‹¨­­εγ@ƒ*\B||(΅Š φφvͺί’±Ή@™a¦Ε“υ‰ |ΰѐ!CξΠ&$$pž'(‡lϊ†KXAŠu"8aΒD'ΈF™ 5ƒ―Pɘ0υκΥ‹›T&ͺΞƍ|υ«_E8Σ’ΰQΓxWOΛcYr₯!/‘••EγuΉθRηR,a)Φ‰hmm >l-ψΔG&LΤ JKπΉE•••άdœ-ή½{·9$Ψ…g‡ΧRϘΊ•——ογ:œΰ“όΑG›D Ε:©©©Α»έ)*lρ6/V‹”ΎK=ΛΜΜ Κ«B>H]u¦JόΌί5¨aΜ·(K,³²²(i„ΧeΊζR,a)Φ@Ω`­Ηt‡2Γ‹>.SήΎ};Β]₯ΨΨX.ƒα:fΞ ²΄δ3&YχQ«˜0q>δˆ*Τ6ΚaGGo/£^ςΩέΑηMϊƒKXAŠu"ͺ««YΚQT¨ αiζή'CL‘¨T΅΄΄4ήqObŠ9V°ιΕΓ™]q‰αIΈ„)ηχοίϐ+‚,ρ )–°‚λD4662’l°4£Z0Λ!ΑzbΣΦΦΖYBn”–`Ι.ε‡­)j3*&R”IJ#—2§₯₯ρΊT͏>ϊό¨pb +H±NΔΐƒ½’‘C‡"\'RT¨gL‘˜*Q`bccy₯Ž“ŒΤ0HΩ£ΨΙb“—8ε=©©©,©ŽΙΑ5Χ\ΓΑͺU«8xο½χΤΦΦrσξ»οξbΫ’ƒΣϋsK±„δXΒ  yδšU―Ώώ:kΧε ££@[[7³²²8ΈσΞ;»ΞHG‘Px„KX!ςBαO<ΑΑΥW_ `ύϊυά|λ­·8¨««γ ---x`SSάqΗ– …GΘ±„"&>σΜ3\qΕΌφΪkΆmΫΖΝψψcΣS±±Ηώ·=zαΪ@kk+™™™:tΐάΉs-ΫΩ( ˆΕZΊt)€©S§rsέΊuόυ―`,ιΡ£qqq€€€ ΛιJNNζ€"g]·άr‹½OΉH±„GΘ±„ό …Œ€¦L™‚@άΊu+εεε²όό|š››9θΥ«Wπ„&Dš ΩΠΠ€@d́555Α3˜Xzz:l€Έλ»Ξά'‹T …GΘ±„| …Ο=χ^x!›6mβΰΓ?δEΉΙRαgΒVjj*f‘{{{;0;;›Σ)eοԜωΘ‘#vΰ[έΌHT(α^±ξΏ~?ύιOΉΙT~Unn.:d«gϞ•™{ͺͺ Ύ’άΦΛτ±>άiφƌ(vΟυ[R,αr,ag‘πψ“'O°yσfnΎσΞ;Η, _‚}¦ΖΖFnšΗ\M•””d¬βΐLι0šσ΄΄΄p`¦zxˆ ¦pY„9Cuu57M£kφμΩ§ϋΉ#…Bαr,a……?ό0Χ^{-ΌΡtΓ† ά4ΙΤbύϋχG N™ΎC€™ΐι4“ƒπ*"M]iΞΜ·Μ ¦δ䏙,22˜ή~ϋν§φα#…Bα]­XF6ŒDρα0FfΜM¦Εώ“ι-™lΊbšO&©7ΆρZΗh4Œ­y³<ΛΨ` ξ`]¦…ΦιΐyσζΪo!’b c +tυ£"M+hΜ9p^ΕδΛ&Ύ˜ Ι™§Μ 1fγoΒa”4―›Ϋ Νz,F4ώΠ»woLΐK›Ή N³Τ&Ά>ψΰƒόόη??…ίET#ΕVc +Έ_έΐ%Ηίn–³{Τ©”CΈd^75f™Cž9sNN¦δ™ΝŽωPζbbk§NX^^7χνΫΧΙψhjq©*!ΗVp ΙΒ… 90ύLs †<σΊtͺιLυgd5gnγ1ΝDO†0ΡΜ™Ν©ςLUh¦›Έ§ Φ¦Z4πΐθX‘P(<ΒΕ2όζ7ΏαΐdΠΣθ2 -ͺ‚‘ ƒ™*ξΣ§Λ°:MȘS™δέ¬»2{²³eφη"i„UΠL:™+ši%ͺ¬15’Ÿc#Ε!ΗVπ.OqqqpΣ$γΜ©Ÿ11”o™xdrsΣΗβό’YOQVVƁΙξ™­›œ‹‘ &π™˜hziΌ΄ιo™‰ί] P(MJ!„B!„B!„B!„B!„BIό?2―rq~αIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/ellipseroi/roi_getarrayregion_rotate.png000066400000000000000000000173611421045507400311720ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ£IDATxœν{tTΥυΗwBή!<‚Lx‰΄HdH—@„JPkE±Ά…e!’ΈψZ1ΤjͺΠ"fΥϊ •Φ€@E*P( ‚@δH $ 3“ΧόώΨlΟΘ0Ι™;3ω~ώΊlξγάdηξη9‡ˆ°zpΣ œ0‘¦F +JJ>˜8±ςτi«FfDY= ψι”)_δζνέ+…?{ξΉnμ^±ΒͺQ…MQ±jkjΎκ«‚Ν›₯Πa·[5ž°€Ι)VRΕΔΠUWQ|oŠ‹#"*/§²2«‡v„Ήb΅jEΓ‡Σ 7PΤ₯ uνJΫΆΡ©λθξΉg'§“Ž‘Ύ}©ukϊ’5έ=%Μ’}ϋhο^Ϊ»—vο¦C‡¬~%<kΐΚΘ αΓ©gOZ½š6m’Ν›ιΘ*( ςršφΝxŒ ~` iβΗ΄γ]ͺ:@½zQοήτΠCT]MΧ^KΛ–Q^}ω₯Eo²„bΩl4l N΄g}ς M™B[Ά\Α*+iχWτΥW%½{Sf&͟O6Ϋy [½ΊΡž„CΊ‘gOš=›:v€}ϋhυjϊδ:{φrηOZΉ2*:ZρΦSΊuϋπ±ΗnΨΰυ’ΤTΚΜ€Ρ£iπ`ΚΛ£> εΛρ ΠV¬.]hφlκߟžzŠ–.υυͺ–νΫw4Hž+-Ν_»Άήkγβhτhκߟ¦iΣhύϊ+1rRSiρb*( 1c¬@ώ΄n­ZEΧ^kΝ@#“œLoΌA……τλ_[=’;ξ ={θ―₯φν­ hΏϋ>M“&]ΩUnγόΥνnοv?ξvט–ψρ› ₯¨πo£‚jΣƟkΣ<ΏύνoY8ώ|ΟABB ‡r =zΤspVΔmΫΆυΔΔΔ΅~ψαΏ=ςΘλ'Ζ¬ZED‰‰‰|f||Όηΐ."†–-[z\. ›5kζ9—Ÿ;wNΏΌS§Nžƒͺͺ*FE…–‰œ―Ό?”ίˆˆRRR<εεε,δŸΙθΚ‰τγšΐE»wΣκΥτΜ3Vεδζ&§§χ;ΆzΪ΄*)ΏfθΠnC†tϋιO­˜%„€bέx#9tί}τξ»VΕ²²βγβάΉΉ"κ<`ΐΔ Ίw̘ήwή9ωλ―’•eυD°›Βργiά8ŠŽnθ}š7oξ9`KGšμΫ·…±±±žƒόό|vθΠΑs””ΔΒΘΘσ–΅΅΅ςΜE‹hĈ³ΛΏμ±¬xFή„ §ς<461ρξ7ߌŠŽήώή{DΤ’E Ο%l΅jΥΚspςδI²—Γ—°€ΗŽca»vν<lR‰(ZόψψM₯‘?sζŒr \εAύΕZ°€ϊυ£›n²zWΞ'ŸΆήα³g―οί!ξ’ΚΊ**ήπΑώ=tΥO~bαΨCπ*Φ«―ή½4a‚Υγπ‹?Ύ";»{χΞ/Ώ|ϊž{*ε}4yςΐΙ“­XΐRΕZ²„¦ A[θΡΆW/Gy9™šžξ9ςbάwφδΙιιΦ -@£5}:Πo4ζ= =;vdaAAη@ϊ.ηΛ\*))ρ΄ρ–ν(..ζγκκj"rΉ\œš:΅ΝK/ΩlΧ_q»έ‘.—Ϋνv:N§Σs ΠωΜ‘pΰ€‡ADέ»wχtξά™…••ηΏŽœw ‘­'Λϋ³[™ššΚBy‚έkψpΊν6ϊΏ³z γδή½ΡΒ"š0!ώ–[jξΈ£†ˆZ·.ψόsλ† ‚λ‹Υ½σN8”GVϞύτΑƒ―υξΝ’¬¬„έ»+φμ‰Ή`ΑΏ{ Η‚λ‹΅c…GΐTUYω―ργο_²$Qό• Πό坋wηεωβ Η‚θ‹΅bO|‘F¦ύ…_πi1Α‹«²”ΑN˜<“ύ- ₯ …œˆ:ΎeΛφ„„‡W¬ψ棏š·kWUYΩw̘ίœφϋ»^Ύ0‘„k%K.œΣβŒ‰œVrr2 +**<μ’(ΤHoIϋΈ|$…\ηα{*·υƒ`Q¬œΪΌ™V΄zΚαu릧χ5ͺx~"ZστΣeeeρΏŽyν5ΗΤ©ρVΞ,A‘X·ήJ]»X=3μ[ΎΌFLŽ}ϋν˜W^qŒWυα‡Κ8A‘X³gΣΜ™fΑ}2³ΐ₯ξS a#l6 KKK••τκΥ‹ΩlΙNylξOΔ/Xΰ8|Ψρε—1τΓΤω2…ΑvM&Ίκ*ΟΑχίΟΒ>}ϊxNœ8ΑBi©Ή‘AfΈeC¦3d^Ζ¬wή‡%§“6n΄zeξάΨ_ ηٌΦ+VN=ύ΄Υƒ8ϋχGξΪsο½ηκ?54±X±~φ3*)Ή²IZaΓώ4sfΨΞλ·ΨΗΚΙ‘‡ΔƒΈ*"Ϋ5Ή”!ŽΊΊ:ΟΑΑƒYΘqΎl&α&Ύ‰ί‡„$=<»=ϊ£'Mrόλ_]YψέwίydAI:L Y:^œ­ΈξΊλX(_„}DιΓqLRHΟΟ¬όbέs:D;wZ8‹yρΕδιΣK­…¬T¬¦ι]Iκκθ•WZ?ϊθq«ψXf οΊ‹>ώ˜Dη¦YΨΚnOΩV  ―Ήζ²-σšd— kέε‡8ΟΊ6?|ΊΗ*͝Ώwο‰—^rΫν‘ςžΐ1œ, Kt°-“=±ά(KD§NςpΆ]#»Iύΐ²/Φ=χΠΧ_[υπΰ"'§ΕΣO_vQ€Δ2Ε> lœηύχ~όγκδδΊϊO ¬Q¬~ύθ»οθΒWΠ7ίDζΕ.‡.ΦψXτΙ'}"§ Ψέ!αxΙΨ›9₯“Σ²PΓώ–La°“$}~¨τκ8ςOLL\»Φ•uvωςžD$]4ΎΏΌ'ϋX²5£uλ֞™­εS §KδC₯?ηΦ|±`Φm>xp₯˜²ςX X Τ―_“+Φ˚5Ν‡υς9 Q,0…·ƒ$&¬Κ„8/©ΐ„„…’™6…l=Iτ°ω#aI₯β=™Έη,ωργΗ‰θ¨:΄δ³Οzx„2PTT€ŒM>H>Η,$Χ›ΰž™bΰ“ε4W9cΦ,ψbΑzeωςf£FyΙ'…((V»v|±‚ŸΚJΪ²%rΐ€0±†(Vf&‰x \δ㏛έ~{Eύ煁φ±:w¦οΏ'Ώ–ςj<5@ζ ΨΟ`gKžΐmŸt‰€Ÿύ-₯{-ΕpΟ‚œ\Κ—σ%;wΦός—§<”σ&xι-ρΙ‚{Krœ2Β^š3η €%‡κώbuιBGŽψ™!CAAΤ-·Έκ?/€bDχθαe†YΘhS˜’bM%‡M€L({―ΗŠƒ|6B¦8s!SœΧΰω$,T‘˜6ΙΣ ₯UΪ·:v΄oΫ-3 œDež|#NάΛή9U„_YNfτΪ±θu₯οϊ‹GήzUΐyΆnmf³…C5Š\8]Ί@±(Φε±Ϋ#’’3 Π>VE…5>Ηδ²€Γή‰τ±Ψ i/Φσΰκr[©τΖΊtι’?ˆΛ;rεOςθ·E΄kgOII‘i ―ιΞ†ΘEΨ•”žΧ«δδlφeΙHz~~θ/Vt4]¨V/œ9Ω²%Lα•SVF"ά*v{δΙ“A±ξA τ;”–Z³«›BY΄gs#w¦ΰ<΅4Fܞ [9# SΫΌψ‘μn`»#“l+e{BRRYZZMll¬Ls…@Ž“Ν–΄t,”‰9™‘“2ρ!Σ%ŒΌƒΰ‹\DGΧUW[ΏξAΓ τ;QÜΒ0'&Ζ]UΪ›Hz΄bεηΣ°a~f(εv:ΓA±νc•—“ΣI:·ε Βavs±˜1{9²A€«ϊrή»V^7ε’ξ»V²¨Β^”Μπε²g!=½ϊΤ©:»έ.·fa/J§ΐWI!§dΏ†tΧxxΫγl‹\SώLόΐsΎo‰…Κΐ°Ωj ‡9(Φφνt!‰TlΆš£G‘nπ‹Ν›ιΎϋ(77 εM(δ„;³₯ΥγMΏ€Ρδ0^8ΚΜ72Θ3ٖɍΎ8‰/σݝ;Ÿ]Ή2>..N6°ΩςL»P^D& ΨΤΚtΌΧυ₯εdBΪb?°ΰ‹•—G£Gώ±‘Α™3‘G6x½ ΐΕͺ­₯U«θΞ;δ`'-­ΊW―ͺcΗΒΑZ“‹ΓGΛ+ƒΫΈ1‘ώσBkώ8ςςhΞϊΝoχDξ Λ.ς±lΒd‡fώύ,διypκAUx.†μYΰR oΩEbm.ΉάzλΉΏ½₯Ηε’3N½&&xœrνΞȍ-d#{~^+NrΉ ―uί±ζ‹UZJ{φΠ­·Zςπΰeπ`Η&;VXV–‚5T0ΐΉuklMM8€έΙΒ₯"σςθ³ΟhκΤ=ŽƒsΩJΐH»ΐΖBnΝΐΑΉμΰ«ds2χΝgΚ­ΈgΑcΤ† qmά؜#Ήγ&Wδ\?ΌΛωϊ\EΊ„)”‰uΎJΠP]5Ή €ςσ‘‚ΏΘ A•76―ΌΑʍwήiκ«&3mΫΦΆiS»Lύ§†V*ΦP―^Τ·―…CnΎωά₯IυŸ:Xœ‹{ςIzωε@4°K!«7\T‘a6η Ίv½Έa{9rιξgςΔ ι·quHzNμΔ8Ž3Ned΄wΉ\δΛ΄η5do{βBLLΘβR·nέ<δ₯%€7ζu; ί±ΈYqΝŠˆ Ϋo·v3vlΕΏ_RM ŒυΥƒ'Ÿ€Ε‹ι†¬‡uL›f80΅ώσB λkΗΚΟ§¬,Z²ΔΰS8Ά―wρΆPςLΆr…EΎ\6pƒ€Χώ;Ωhΰ9aμΨΣ|{ςδω~mΉΞΓfKΪqΞ›>|˜…œ―—}|걁“#αΒƒlτ“ϋYψυŠEDO>I›6™U¬ eΚ”’ž=“λ?/ΤŠ !G²eτθ£V#ΰŒwκέw“Ž0ΙΆK‚B±ˆhΚ3†–μ =&O.™7Ο‹ν ‚ε7YWGΏϊνΩC?ϊ‘‘ϋs .;HΉόβu» ΩΓΙ‰ γΉgA^Ξޘ\ϋ€=3ωτ_ό’π΅Χ’Žjιy]•Ї$'¬rρGΊS^—Š”μΚfQ³LLΘ:•Λ‹ˆςσι©§θΓ­G@θΫ·2#£β/ ΫΙ»A€XD΄t)νΫώužψψΊ… ²²v9Αb ™gž‘>’Ρ£)/―1oΛσcǎ±P&― :°Ρ”έ le£[=ΉŒ_ε±_›6ί~{ g€yε|½|:ίJ5~Μ€°U½ϊκ«Y(sθ|9Γ‘ο/o%W\ςƒ S,"Ίλ.:xvο¦C‡¬Šώόη²^hqδH”¨ξ„!Α¨XDΤ§•”Pσπι"9ΟδΙ‡7[±"LΪD/CpωXΜΉs4x0mίnυ8•AƒΞήxcΥK/5¨—α”Ψ–#5υ|;²lε,€LπΣ₯;ΕN˜LLpBzK²zΣτ ωv²[ΥBμ‹ΕΌυ%'ΣΩ³T]M=fυhˆΣΟ}ψαΡϋο·OŸήφωηΓΊXγ‘ͺX^xβγΙf£ΒB3ƚ1άx£γ½χŽ_6{vΫργ;ξέΫ]u0Ι¬€¦œ9””Dω-[¦Ίφn·›§ώeee±|ωςεžœ³ ’Σ Έι@š˜ΈΈorrjbciΑ‚Ž[·ͺε?ΩΑΛOΚυ’8c.{ξX(;/8 §Zπ 9$ΆͺςιVςΫI«ΗF*±Ο€¨¨Θλb“—'τ|,―ΡΠυΧΣΓΣΊud·S^-[F;v4ώ³ 8“ž~6=ύΜwίΥΜ™Σμ³Ο"ΣΒΏ¨|₯„‰byΨ΅‹²³);›ΏžF¦Ε‹©MO_ΧΪήωκ«k† q μ2€π‹/Z~ώy‹'žθΊrεαϊ―lͺ„•b1»vΡ]4k₯₯yVαϊC^^aeeδΑƒΡII_§·q:/ι %$ΤuκTc³ΥvκTέΎ}νwV8΄n]μβΕ‰?ή;€οΒ„‰uyάn·ΝΦ²oίڞ=λξΏΏoJJquutηΞ‡jkkνφfv{€ΛU뎍uΗΕΡώύΡύϊU;uβDάργ1ϋχΗωeβ·ίžίίKΆp7©¬Ψπ^wb/((hΊ>V½œ9±aCΤ† Τ¦Ν(ΎώΫII΅IIu••ε.W„ΛαtRDDœέI?τ©‰\Ϊ-Αεh*Šε—+’Έ8ͺΈ˜JK/ώββB;$4Εβ<²άo‚Ώπlρš2σΞΘ=ηΣeΆ‚S2ΰηΨ^ΪΘ–:―]ά‰ m.'δ6œόtωΉ•›w²έ”oΗYψCb–³aέ•ψλF€b#@±€šŠΕ Y{-ΰχθΡƒ…œPΎ {f2_ΰuφdw;srΦ;aςAμαI‡/—­<£Uf@Ψρ’—ΛUψ‘²ΔΞ’τ εŒY?ΐ ŠŒΠTLαΔ‰=“&Mb!g€-c‘Μ}³•Λ ±Ή‘ζ•MŒ\?’σ*ρε²ηŽmlI`«*“μlΏδϊŽlεTG―ys―‹PΘI%ςυύ_,`(0 ‘©t7pοevv6Λ-Zδ9+°oa³ΩXθ΅ΌΓ~ŒΜ,πZ²gO΅#nj•ž η€;Ε·’ιήSσΐ,dMϊU² ΕΩι,r₯HξώΕwψζ›oόθnΐ ŠŒΠT lΧdΟΑy_±Gο²ΤΟ+/Κ…~xŽž΄eά©'—~ΰd€ œΧ$‚Lρ_~~‡Χε$d„lδΧ—Wρύε>²ΰψb#@±€šJThυB?’B€°Δ]°₯VSΐ‚}Ώ XaOC~ΕΨŠŒΕF€b#@±€ XΐP,`(0 ŠŒΕF€b#@±€ XΐP,`(0 ŠŒΕF€b#@±€ XΐQVό€E‹)’GyΔ’‘4|±€ XΐP,`(0 Q‘•Μ›7O‘dff*›Ν¦H222 Ž©‘ΐ ŠŒΕF€b#@±€΄© T†2oΏύΆ"yπΑΙϊυλɞ={‰έnW$Ο>ϋlΗ敆όŠρΕF€b#@±€ XΐP,`D…¦xσΝ7‰ή ΊfΝE²qγFERWW§Hͺ««I›6mΙΜ™3}ηe@T‚(0 ŠŒΕF@TΨ8,^ΌX‘dee)’Ο?\‘lΪ΄I‘œ9sF‘΄lΩ²ή§Ÿ;wN‘΄jΥJ‘Μ˜1£ήϋ( *A ŠŒΕF€b# *τ‡%K–(’»οΎ[‘|ϊ駊dΧ]Š$*JΧ©ώ©ΧΤΤ(½zXUU₯HZ·n­HNŸ>­Hrrrθ² *A ŠŒΕF€b# *¬ŸώσŸŠdΤ¨QŠδϋŸ"Y»v­"Ρ\±±±Š€Y³fŠ$11Q‘θυD=NlήΌΉ"Ρ#P₯uκΤ©Κ ˆ AΠΕF€b#@±€ XΐXƒTEGŒ‘Hτ5avξά©H***‰>055΅ή«jkk/9Φ θŸNyyΉ"Q"ΗιΣ§Χ{ίΑ ŠŒΕF€b#@±€šzTΈpαBE’χ‚Z΅J‘lΫΆM‘ΔΔΔ(½Ζ§ΟώΣ+qz|ηv»‰^OŒŽŽV$₯₯₯υ>]‰@“’’¨ρΐ ŠŒΕF€b#@±€šVTΈtιRE2pΰ@E²bΕ E’ŸŸ―H‰^γΣ#5½«S?Gέ\.—"ρ%MIIQ$zί©oVVVRγ/0 ŠŒΕFgηύό£"Ρ·ςΦΛ5zΛ^ϋφν‰Ύ±›>Kw±υευ%@tZΏξςλ ‡θM…z zόΡπΕF€b#@±€ XΐP,`„π‰ sssΙwά‘Hτεwοή­HτV;=Τc7½KN_šQυ&>=vΣc@}B˜>΅+>>^‘ΤΫθΛ<3ίΑ ŠŒΕF€b#@±€B5*œ?Ύ"Ρ§mι[yoήΌY‘΄hΡB‘θu·N:)‡Γ‘H|YΒ_?G―θιќΎœ€KκwΦγVύ*ειϊƒΎXΐP,`(0 ŠŒQα[o½₯Hξ½χ^E’G|Ϋ·oW$zΜ₯Oœ:uκ”"9qβ„"Ρ#G½ΟS_πCŸΪ₯o$_₯W*υXRŸŽζK…Q }YowπΕF€b#@±€ XΐP,`„`άVξΥW_U$'NT$z ¨oμvςδIE’ΗJϊβ………ŠD―ϊι”~g]βt:λ=G_8€¨¨H‘θQ‘ΝfS$zO©~•ς³fΝRNΐΆr θ€b#@±€ XΐP,`λk…sηΞU$cǎU$Ύlγ¦ΧοτΉ~z§₯υ€₯₯Υ{g½S?G_IF%γββκ½³~Ž~gύ*_ζ06nΛ¨ΎXΐP,`(0 ŠŒθZ‘ΎΫ}χέ§H6lΨ HτP°τΈL?Gƒτ šώRzw¨~•.Ρ;6u‰»ιOΧ£B½U―0κ1ΰργΗɟώτ'Ί,¨‚ ŠŒΕF€b#@±€ΜΦ υ5aF₯Hτ:ΰΦ­[‰>P_«S_t‰/ќ^OΤ#,½σΣ—MΒυ«tτΨVίΟBυM%τ΄C‡υ>½Α ŠŒΕF€b#@±€]+Τχ†Ψ±c‡"Ρ+hΎμβ§χUκ{1θ3ϋτΥDυ8±mΫΆTzύNέτJ₯ώ¦zLͺ_₯G ϊ˜ΛΚΚΙ‹/ΎHWj… θ€b#@±€ XΐP,`λW›yζ™g‰^Τ+_ϊŽz€ΗJz,©£GjzΌ©ΏΈ^ԟ₯Kτ5aτZ‘^MMMU$z]2;;› ’Bt@±€ XΐP,`(0‚υQ‘Ž^Υc%=NΤ+ƒzέM—$''+=Βο¬Χο|‘Ύ―½^Oԟ₯υ8ρΉηž# *A ŠŒΕF€b#cT¨“““£HτxJŒή/κKO }‰}YF—θ#ΤΧMΥ+•Σ§O§€€¨P,`(0 ŠŒQ‘Μ9sIII‰"Ρηκ=½ΛΤ—}ΩεA–/OΧgP>όσdˆ AΠΕF€b#@±€ Xΐ‘ϊΗ /Ό HτXRŸ±¨οb――ω™ HτnΥ€Υψ D… θ€b#@±€ Xΐ rήq 8 Ήψ XΕοΩ1Οχ˜³IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/000077500000000000000000000000001421045507400234015ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_clear.png000066400000000000000000000011631421045507400265270ustar00rootroot00000000000000‰PNG  IHDRΘΘ­XžsBIT|dˆ pHYsttήfxIDATxœνΣ±ΐ ΐ°‡ύwNΰάB!MΰΖkfΎŽφνx™A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ΒιŠ‘ΰφ IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_click_segment.png000066400000000000000000000057251421045507400302600ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ ‡IDATxœνέΟS“Χπσ&$„ˆ€A E΄€”@mD₯Ϊ‰όpΣ.:ΣΫkwwΫιΪ}w]΅]u¦».œΫ?@¬-?4FΤ4C#ŠΙ@†I# 4”δ.ή{Ήˆ¨$9yΟϋγϋ™,τ5œχ™αρ9‡χηΚΒQ₯¦½½Κ•Τφφξ‹©dςί_}•X]₯r :‰υ―_~‰G"A·{χEϋεˏ]ύα*·i)’2Jj{;θv{^έ}±ξά9*ƒƒ©θ§Ρht:έ1AŠ('–^―Ώpαέ1AŠ('V:N§Σνννt‡Ι‘œXj΅:‘HΔb±3gΞΠ€…β=™<}ωrέΉs:Ξh4Z"NΧpφμΠΔ•ρArΤTF ŒŒhυϊgΛΛ©gΟΤ[[ 33ρΏώ ί»§Ÿ …B™L†Κ]@Ή*++?ψΰƒΏj4š’’†!X,–ŽŽŽ={zz “x€Κ‹w•J•N§χ\όύχί[ZZ*++ιή ΔŒώO…©Tκελ.—«ΆΆΆΆΆ–ξν@΄„¨XΌϋχο †ΖΖFΊwq.±!>ŸΪΪJχ¦ BM…;ΐΪΪΪ©S§θήΔFθΔ"„ΜΞΞΞΟϟΓο>Ț SαŽh4ϊθΡ£ξξnΊwρ`“X„§OŸŽχχχΡΩV9³Ωl555ΏZ­ξλλ;tθPαB&˜U,^*•²Ϋν&“‰n$ΐƒΕϋΛFFFššš, έ`€!Ζk‡ΫνͺͺzηwθΖ¬ˆ’bρΌ^―^―ojj’0!–ŠΕ{ψπαφφv[[Հ U,ήτττΚʊέn§0!ŠΕ››› ‡Γ~ψ!•€ 1&!$‹ω|>‡Γ‘P N§S«Υ­ΈΈψ₯KΕΕΕ΄©κλλ£ΎEγp8ΚΚΚθŽ …&ΊΕϋΛnάΈa³Ω***θ E3±8Ž#„βΓ^7oή¬««ΛjΨ’™X΄VξϋΊ{χΩl――/Πψ@ΝΔ*Δ<ΈΫΔΔ„V«΅Z­…»Π"™ŠΕ›ššΪάάDΣρ“RΕβƒΑX,ΦΩΩYθA>$V±x ΣΣΣhΔ%f«XΌ₯₯%―Χ{ρβEώGQ3“ΙtφμY!ο¨ΡhΡtDζ***NŸ>-ό}ΡtD„$ΉΖΪMGDHͺk¬=ΠtDlδP±x|Σ‘'N° v“ObB|>ŸJ₯jiiaπd2ξπϋύh:œ¬*/£ιsr«X<4aN†‹‡¦#ςΡάά,Ά2£ι+²­X<4aEžk¬=ψ¦#ΥΥΥ¬Q™W¬n·Ϋb±?~œu J‘”Δ"„όρΗ₯₯₯h:" EL…;&''ΡtDΚJ,‚¦#BQΠTΈMG ΔΔ"k:ΫΫΛ:ΩRάTΈ#»\ΑΑAŠ]L  zzz$χληΗ9ΞΓ‡³Dn”[±x™LζΧ_νθθ@ΣΊΊΖΪcll MGθRzΕځ¦#tQK¬Βυ0 šŽPD-±€;#΄PK,IΟƒ»‘ι¨Xϋ@Σ‘ό‘bνo:βt:Ρt$7¨X―΄ΎΎ>:::00 ΣιXΗ’`εεεrέΦννν-//g…Δ`*|³ί~ϋνδΙ“h:’$ցܺu MG²‚5ΦA‘ιHVXY@Σ‘ƒΓT˜ΎιΘϋοΏΟ:±CΕΚZ8^\\ΈΫͺδ bε"‰<~όMG^+G+++h:ςjZUTTd2™εεeZŠ_2™ …B‡#‰p\R§#Zν /Bˆ’ώ―FSSSCCλ(Ψp8.ά½[΄±AφΌ\.‚r–/φ0Μ‘C$‘PΏάt$‘ ŠέfΔ‹ŽT*…¦#»!±¨α›ŽΨl6ցˆ7Π499™N§™œϋ"6¨X”ω|ΎΥΥUτGΕ’ΟοχΟΟΟ+όΡ<΅Ÿ†•\±ΆΆΘΔI$v_‹rά_@ωφvœUT2ΡΥΥ₯δ³έŠŠˆN·χe0”β¨Ξ|?Ύ¬¬Œu’ΓΥY\\Μ:ΙϊψγΡNύU‡Ϊΰ§B!άΈq£££Γl6³D8H,Œ544X,ց„3>>^UU₯m$– Ό^oiiicc#λ@ ŽNbΙ ‡‘`&'' !­­­¬),:‰…VVΐΪښΌ?‘A'±0fkvvvqqρΜ™3¬)T,f"‘H ψ裏XRH,––——=ΣιdˆX>|mΚr¦Υj/]Ί„s φ!γF‚‘Ω9XΌ‹ŽΑ[o½Ε::°Ζ‘±±±ΖΖFy1ŒŠ%.wξά‘Η§}P±D‡΄Τ·}P±Δˆίφ‘t#.T,‘ ¬ΙKΌΒαp4•θΆ¦BQ[\\ R|ψŒΔ»εεe―Χλp8X’L…°ΎΎ~σζM%_m΅ZλκκXG!sόρΥ₯₯₯¬9L…’Α_}κΤ)“ΙΔ:–7ΓT(1£££MMMUUU¬yT,ιq»έ555ǎcΘλ bI’Ηγ)++σω+¨XRυΰΑ•JΥάάΜ:ύ‘bI˜ίοώόΉ8·}P±€- E£QŽŠ%y‹‹‹ΣΣΣηϟgΘ Xr°΄΄411!ͺmL…2±ΆΆΖoϋh4Φ±Πγp8Π΄N 8Ž»xρ’:ΰ‘bΙJ&“Ή~ύΊέn7l#ΑK†FFF¬V+Ϋm ‰ΕqΗqH,QΉ}ϋvMMMmm-«($Κ•8y<ƒΑΐκ¬?$–œω|Ύ’’"&Ϋ> +w1{όψρσηΟΫΫΫΎ/*–ό…B‘X,&π™d¨Xа°° …μ„Š₯Ož<ρω|½½½ΒάKAβρΈΛε(’Δθf³Y„ΏΆ―’R©ϊϊϊτz}aο’¨X’N§‡††:;; Ίνƒ5–B [­ΦΚΚʍΔRΫ·oΧΦΦ=z΄ƒc*T΄ϋχο›L¦ϊϊzκ#£b)ݟώ©Υj­V+έaQ±€LMMmnnΪl6Šc’b!„ƒΑ₯₯%ŠΫ>¨Xπ_σσσ·}P±ΰψmŸžžžό‡BΕ‚Δγq·Ϋέίί―V«‡ΦΦV g!ΐŠZ­ξοοΟgΫ φ‘J₯]»ΦΩΩi0rk,x₯ααα–––#GŽδπ΅¨Xπ:.—λψργ9,uP±ΰ ξέ»—ΓΆ*ΌΏνσή{οόKP±ΰ@¦¦¦ΆΆΆΪΪΪψ~$ΤΜΜΜΚʊέn?Θ›1BζζζΒαpWWΧί‰ŠΩ‰Εb“““έέέ―*dmuuu||ΌΰΫ>*vH !όΆOIIΙΎΚε6¨ήh<ύε—όŸ›››>|HI&Μm@¨žžΗsςσΟ‹^lι˜c)ϋηO?U[­$“9d2™ί~{‹γτFcλΰ §RΝy½4i…Bψζλ'Ÿl―λΖWŽŸˆεTͺ{?μΉzU£Ρtww_Ώ~ςΩχίkΠ‰TyΒss₯33wΎύv~~~ηbΎk#¬άFΝfσξ3+σύ IIIqq1ίjβθΡ£FBŠ„j;"QίΨXZ]ύΤl~χέw‡‡‡ώϋο|+ίΊukkk‹Rρι§KΑ Λε’-H†Κf3₯ΣόχύΨ±c™L†δ_±ιτΪΪηT*•L&777σ€%™L¦R)ώϋξχϋω‡[9&–Z£©οκR½ψ|¬’Ύ>ζχη(HΞ‘'μ_|±ϋJŽΟ±jNžt^Ή’ήήή}1LώςυΧ‰x<χA‚JΚΛ?ϋξ;Žω‡/€©Τwέλ6EIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_drag_handle.png000066400000000000000000000051621421045507400276740ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ $IDATxœνέΛSTΗπžŒ#"ƒΘ[@@D@."* ŠAy›d‘EΌζOHeνMvIV©Κ. «\[‚RΌAG Χ€ŒΘεθ€FyέEίΜ‚ΒΜτ9}ϊœο§\θ)νώUΡυν£ΒB@[tLV9wŽάΉCΌή=ςΓΔνf²†ΝΑΊw,,»}ΗΓΫ·Ιƒδ·ί˜μ‚12YΕλ%v;Ή{wΗÊ &kƒτl—3™Lf³™νš "ΖΛb±TUU±]DΔψ`ωύ~Ώίξά9ΆΛ‚p,ƒΑ°±±±ΌΌ\^^Ξve ›—w‡άΎM**ˆΩΌi΅N-,,˜Ν«—/Η3Y„c`²J{;±XΘκ*ωλ/σΦVμΤ”{iΙλpθ]―\.“-@Σ’’’.\ΈόetttSSSTTΗ‘@ RSSKKKw=¬­­=rδ—y€Ζ/οz½ήοχοzψπαΓ„„Ά{’±[‘ΟηϋησΞΞΞάάά΄΄4ΆΫbΙ‘XTooobbβΙ“'ΩξΚ$ίΑ"„ X,–όό|Ά›‚ΙtŒŒx½ήββbΆϋ‚Θ}°!/_Ύt»έΫUΤGΦ«0hfffff¦²²’νξ |!diiixxψƍl…ΰpΉέn»έήΨΨh4²ω/KP­ΤΤԐώˆ^――――?|ψ°D#<‹ςϋύΝΝΝ/^΄Z­l‡ŽΈ½cνήή^PPœœΜvΰ…b=~ό8===33“νHΐ…R‹r8±±±yyy G.”XΤ‹/!EEEŒ&>”•XΤΔΔΔ‡Ο?Οd$ΰB‰‹2===??εΚ•Θ—.w-,,Œ_Ώ~Ιj 3…&υφν[‡ΓQWW§Σ±ι˜QΩl6ζ‡ΐd2Ωl6|εΎXX&=R@€αš„ΗsώύΚΚΚ£G²]€Γς`±½wimm-..NLL”h}`‹εΑbψζΎ§ξξ¬ττtιΆV„I,κιΣ§ 999’ξ‘)±¨ΑΑΑ¨¨¨‚‚©7‚H–XΤΨΨΨζζ&Κ’”LΌΔ’\.Χςςς₯K—δΩB%dbQsss.—λΪ΅k²ν'jbQ+++ƒƒƒ555rn !pbQλλλ]]]6› eIͺ΅g‡‘Ÿ―ΉΉΉΌΌeIςPUΈ][[Κ’δ‘­ƒEώ.KΚΘΘΰ=ˆΚiε*άΞαpΔΕŝ:uŠχ j¦ΕƒEλυ(K’ŽζΒ §Σ‰²$ιh4±(”%IG»‰E‘,I"šN, eIJ'E‡‘lP–Δ³Δ’¨ΓH6(Kb‹ΩΑτά…–%?~œχ Βcv°D|sίSwwwvv6Κ’"„ΔΪC__Κ’"„ΔΪΚ’"„Δϊ,Z–TRRΒ{!α`}‰Λεzσζ Κ’Β€«p³³³(K ΦώP–\…‚²$nRRRT)(΄,)&&†χ ΐUZ–TVV†²€}α* YGGΚ’φ…Δ -KΚΚΚβ=ˆr!±ΒΤίίsϊτiήƒ(+|ΓΓΓ~ΏeI{BbEdrreI{BbEŠ–%UTTπDYX ,--ŽŽ’,i;,6ή½{GΛ’ οYΤEΙF²1 (K"H,Ά|>ίƒP–Dπς.”%$–DP–„Δ’ŠΖΛ’XrYKZ΄,‰ΧwΫγ‰%ΉιιιΕΕΕΛ—/σDV8Xr˜ŸŸŸ˜˜ΠTYB™¬’,)BwΙF;eIlKτ#ΩΛ’bccyΟ"-6 /X!imm-))QwY›ƒ…¬Puuu©», ‰ΕΊΛ’p°x’eIgΜα={Έ 9ΫΪΪR_Y‹Ώ©©)υ•%!±–%]½z•χ Μ ±”beeehhθζΝ›Όa‰₯ kkk===6›Νd2ρžE΄Πa$NWWW'zYKq@KK‹θeIxΗR(ΡΛ’p°”Kθ²$\…Š&nY– Z–„«P΄,©¬¬Œχ !ΐΑΓΜΜΜμμ¬@eIΈ …±ΈΈ(PYK$š+KB‡‘œ„(KBb‰'X–Η{–ΟΒ;–¨ΪΪڊŠŠ’’’x²7$–ΐzzz222”Y–„ΔΫ³gΟ”Y–„Δ-K*,,δ=Θ8Xjΰt:?~ό¨¨²$\…*ρκΥ+E•%!±Τƒ–%UUUρ„$–Κ¬φχχΧΦΦroώAb©Νϋχο;::£££yϝNg³ΩxO»ΥΤΤp,KbXˆ+ezτθΗ²$,5£eIiiiςoΝΰ`αΝ]Ιϊϊϊ³³³eή‰₯~f³Yζ²$$–&ŒŽŽΚ\–„Δ ™Λ’X"gYK[d+KBbiŽ0eIθ0‘ΤeIΈ 5*X–/ΕϊΈ 5­££#???%%…ωΚH,­³Ϋν©©©ΜΛ’X@ž?ΞΌ, ‰„ό]–tφμYV "±ΰ&''ΧΧΧY•%!±ΰ^Ώ~Νͺ, ‰;°*KBbΑn΄,©ΎΎžsY:ŒTΙh4FR–„Δ‚½y½ήHΚ’πŽ_vY φ^Y φGΛ’rssώGXp CCCF£ρΰeI8XpPγγγ/KΒU!8xY Bsΐ²$$„,X–τ…ίƒΔ‚pΠ²€¦¦¦Ο•%…YΟe±Z/}=ύyaaαΘΘ!Δ³±Ρσϋοα-‚ͺ©©ιλλϋΧwίwž°0—ρίό‘RP@Γρρ ii[:Εj=k³ιτϊτχ³ΔΰrΉnύτSΑΧ_oΎo±Zƒ?Œα-§ΣλŸώω§γξ]“ΙT]]έBωφΧ_M’·ΘAθ^ΝΜžš²όσάά\πa€οXxsBΘβββ²€0+θΠ‘CΡΡΡτKΆOœ8a%Δ(ύ—oƒ’δδεΕ€€Ό;v,)))//―½½ύΣ§O‘¬΅΅΅ξξξ­­-BΘρoΎyγrυττ°˜„‘/)‰χϋιΗ=333ΘΛοχ―――ӟϋ|>Η³ΉΉᚠΗγσωθΗέιtΟ; σ`L¦œΚJύΞO]=ž“³μtF>('ρΤ©‹·nmζΏc₯ΧήΉγχz·?τ{<χ~όqcm-όA@‡Žύφ—_tωnΒπφα€πΫ]·IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_drag_new_handle.png000066400000000000000000000064621421045507400305510ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ δIDATxœνέΛWSΧ𓁐HHJx …‚RD!$°ΤjΫQν ΅³»:λκΨ?£uΤN;pέ»V}QADžQDƒ‚ΔH ς~?Οδ.JQ $;9η$ίΟrΰ:&ϋό–nΏϋd“½7E@aαi₯³σςΥ«ρXlοΕx4ϊί Ί\DnάB¦cύη?ά››―&'χ^T]Ήςμƍ{ΧΉp‹H+ρXμΥδδμυλ{/6i΅D.β“m¨¨¨€€„l›ΐE„;–D""Ϋ&pαŽΗ#‘ΘηŸ^QQAΆeΰΒK „Γα›7ož:uJ‘Pm8„ΠΓ{4ͺΎr₯I«-))‘Λε΅ί|CQTEkkΣΖƌDβχϋ‰ά8„ΜtƒΈΌ\ύέwEI₯ŠŠ “ΙDQT$||ύΊJ₯2™LkkkDnͺͺͺͺ§§gίΕΆΆΆήή^>Ÿπ° l& ۜL&“H$›››{/ΪlΆP(400ΰυz}>Ω;;N>ŸŸH$ήΎΎ³³σΧ_ΥΥ՝={–쁝Θ*ŒΗγύιμμ¬ΛεϊτΣOe2ΩϋΫ  …P(΄Ωl½ΐνv[,–ββb‡ΓAφξΐ9 χ ‡Γγγγ|>_§Σ‰D"²KN,₯RIΣ΄έn?ς•v»έγρh΅Ϊp8μρxΘ–ŒΛι3Φ>.—λφνΫ]]]dΛΖN¬ͺͺͺH$β:Ξ—ϋ¬V+ΗΣιtN§3 ’­˜BζG:»RyΖzΫΖΖΖΦΦ–J₯rΉ\ϟ?'[0‚Ι‘p―x<ώπαΓH$200PZZJΆ*Θ=ΒCamm­Χλυz½ι½έιt:΅ZMΣτ±ΖS`Ά$Φ.―Χ;::*•JU*©ͺ χ'V}}½ΓαΆ³½½F‡††άn7Ύu”F£!ϋύΎžžžφφv‚ Bn00σ~,333η“O>9qβΑf!ΫO7dώŒυΆΧ―_ooo«T*‹Ε²²²BΆqΘΆ'VR0 …F($όŸ²+Ιh4Ζααα“'Ofι@ λ¦ηp8nήΌYYYΩΩΩ™½»@ζΈ”X»Ά··/]Ί„f¬Ε‡χw²X,Ι/:£Ρ˜ƒ;Β±L,GQMΣΫί… €R)³Ε2’_›)--U*•f³™`›ιqΉ\[[[===ΐιt2]N!Κ·ΔΪξέ»'‰ϊϊϊΒ_;ƒ#‘ό—J₯εεε‹…`›ΪΩΩ ƒz½ήοχc?’\ΚΫΔΪe·ΫoάΈQ[[ΫΡΡΑt-$ζ±R177·³³sρβΕςςr¦k)$‡B™L&‹­V+Α6 ςx}>Ÿouu΅½½½¬¬Œύƒ8‡ΚtΓ!‰ΔΤΤT(’H$L—“'H&Vuuu(r»έΫΜ§Σi³ΩT*ΗΓ~$™CbύΓοχί½{·΄΄T­V'Ώf i#™XξaΔ6›-‰ βΈΆθξξfΊ bΊ»»qάAΪ0Ηd‚δΊΒ<λXE™ΝζδκE«ΥϊςεK¦ΛᒝyOέξqZ­Ά¨¨ˆιr8ƒδΓ{ccγζζf8&Ψ&KΨνvŸΟ§ΣιpάAŠπŒ•*§ΣyλΦ-w" …Ηc0¬V+φ#9ޏmcc#ΉΓ ΣιΔqAb₯#ΉIςΈ±XΜt9l„g¬τ­ΞΟΟk4šΖΖF¦kab+Η{±„ΧλύϋοΏqάΑۈu¬ΒίφτιS“Ιτε—_VVV2]Kή‰D/^dΊ †©T*w„Δ"izzΗ$›n(΄'χƒμw°ΎΎΎΊΊΚt9ŒAb‘—<ξ@$νw QΙώ_ψΑcJd2Y?ΣU°‹B‘ΈtιRCCΝƒ”Ο·ΧΔ…C6ަP(4 ΣU°‘VΫΌcd©<ήλ™ΨPˆg¬ƒ š¦?ϋμ3Ή\Ξt-Ήƒg¬\ iztt΄½½½p^£cεH4½ΎP(,9z …9555‹Εκκκ˜.$λXΉ6??ίΨؘχϋΏa‚4λ"Κ` ‚Α½Χ&ψ|ώΜ /Λ۟ΩλXH¬ƒD£Τΰΰώ)+‘H¨ΥΔbw*Š;ZZZZZZ˜‚KδrΉV«eΊŠlΑ3cœN§Ωl>wξΣ…d>2ιΝ›7αpψΤ©SLB‹aF£Q&“ΥΤΤ0]aθXΜ›››kiiΙ³―pa(d…±±±ώώ~>ŸπΩο Bb±Εέ»w?ν‘ $[ƒΑǏχφφ2]H,ΩΩΩ±Z­ωq<,‹]L&MΣ|πΣ…d ‰Ε:KKKJ₯’λK‘Xl4==}ζΜ™R¦ I‹₯Έώ!‹₯hšdΊ4a(d/ŸΟg49ϊ5yt,V³Z­N§³΅΅•ιBŽLΗ*Μ=Œrcuu΅¨¨θύχίgΊγ!Σ±π€•UOž<©««γΦ§d:ΖΑl›œœ<ώ<‡Ξ±Fbq·& Π±8#‹=|ψ+;―`(δ·ΫύκΥ«ΞΞN¦ 9‹c,‹ίοg‚($χΌxρB"‘œ![7Dbq›' X‡ηζζΨΉC'‹ΫΗϊϊ: EGΗβ<³ΩF›››™.δ_0ζƒηϟΛεςκκj¦ ω:Vž˜ύπΓΩ³NCaώΣλυΙοΖ1+―°gCa^ ‹‹‹j΅šιBXyΗf³mmm1ΎN‰•‡L&EQ̞TΔΚOOŸ>­ͺͺR*•L€ΔΚ[=joogj>+Ÿ1ψ!‰•ΟhšΎώΐΐ@ξoΔΚs^―wyy9χλτΡ±ς#λτ1„ΥΥU‘HT__Ÿ³;"± ΕγǏλλλsΆN‰U@rΉN‰UXr6A cρx<‡ŽΕ ±XljjJ§ΣeϋF:βŠ[\.ΧΪΪZΆΧι£c"‹ΕNŸ>½[θXxrη’εεε'NΤΦΦf©}$V᚟ŸonnΞ:}$VAΧh4€xΛH¬B—₯ $V‘ …B }}}d›Ebe·Ϋ- ΩuϊH, (Š2›Ν±X¬©©‰TƒH,ψΏgϞ½χή{UUUDZCΗ‚ΜΜΜ΄ΆΆJ₯Μ›ΒPrοή="GŽ!±`?"H,ΨΟοχ/--©TͺLAbΑ;looΫlΆΆΆΆ΄[@bΑ»­­­ρωό†††τήŽΔ‚-..ΦΤΤTTT€ρ^$fjjκάΉsb±ψΈoDbΑϋˆΔ‚#$‰‰‰ ½^¬w!±ΰhηεΛ—ηϟOύ-H,HΙζζ¦Ϋνώ裏R|= R΅²²RRRRWW—Κ‹‘Xp ƒ‘‘‘A.—ωJ$ΟƒzzzD"Ρα/CΗ‚cKeC![4žžΦj΅‡Ό‰ιp:―_Ώξθθ8θH,HΣϊϊz0 ·ΌΌ\VVVSSσφ₯yRT©\ώώϋδοΫΪΪ–––(ŠŠƒΏώšv•ΐQz½ΎΈ«+FΣ{/¦yϊ7Χ‰e2ΫΚ ΗWVVE5ͺT4M?ψν7ΕwΠ<πΓOnί¦χτ­4;ΟŸώύχΩλΧ‹ŠŠ.\ΈpλΦ-Š’Ύώε—’œlC¬",.~:2βψσΟ‘‘‘έ‹™>cαΙ(ŠŠΗパ··wχJš‰΅K,SU__/§(απp†m·4·΄Hkj:::ΑεΛ—GGGΓαp¦ΛνvG"Š’”_}΅σκΥΔΔ‰j3ψŠD"ωο. “S™v¬D"αρx’ΏΗγΡh4 eΨ&pK4ΗγϋώέΣμX‚’’fŽο »”ΝΝΫΛΛιœUyϊ΄κΫoχ^Is«ξμΩ‹W―&b±½Ρθ?ύt»Σ/8H,“}ύσΟΌ,l ς?wΪўΠ0$IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_drag_roi.png000066400000000000000000000031061421045507400272260ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœψIDATxœνΨΝk\UΗρίdƒΑ€h„ΨhŠ!"ιΚ€`‹‘ λΦlj+見.Β€]Ψiύš]!ΰ’Πbέ BΑΆΰ’†’Š©B J E«e4- )Ml\ςf’™Ή―η9ηϋe3ξαωpηΜ-*R]ΊTliων͍‹£γγ]}}³““Ρφ$ŸjŠy}g§zz™„Ό*.¬}ϋ4=Θ$δUqa=z€BAgΞ$2 ωS\X’ζη±E[kŽvΩ?KKύΠ‘φvύΤ©Ÿ_ΤȈήzεΝ/+?$;­ν²»Χ=ΧΦΆP­žTί~ύUυξΥύm“―-LάΈ‘μ„d½½š]X©π›HR"g¬U*œ·HJ–°E’€%lQJ°„­ΰK –°v)ΒΆ.]XΒV¨₯KΨ ²,` [α•,a+°²ƒ%l…T¦°„­`Κ–°F9ΐΆ(XΒ–οεKΨςΊIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_hover_handle.png000066400000000000000000000031141421045507400300750ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœώIDATxœνΨMkTWΗρ_ζΜΑLŠΥΠFH‚³+nΪBt‘jθƝ 7F_ ΧΎ .w‚Bί€ΙΒ¬B+H@7‹–yXΤ$3™t1“43χρόΟω~™Έ{ζχΓ½w¦¨H=rYοίο[œ™ΡψΈζη£mI^Uˆy|Ή\T*‰ŒB>V₯R™œœLbςͺΈ°Z­ΦΞΞN½^Odς¦Έ°$΅Z-IΨ’½•’Φlκξ]]½ͺR©yξάβςςςΰΰΰ₯K•·o7’ŒVŒvΨμ¬Τhhu΅X,ž]\l|ϊ΄ραΓ7/_~Χh4’‘B¬Z­ήΈqcχc½^ηžHJδkoKKKβy‹‡%l‘€4` [”,a+ψ‚%l…]а„­€K–°j©ΓΆ‚, XΒVxeKΨ ¬μ` [!•),a+˜²†%l…Q°„­Κ–°ε{ΉΑΆΌ.OXΒ–Ώε KΨς΄όa [>ζ,aΛ»\%lω•C°„-r –°εKΞΑΆΌΘEXΒ–ύ…%lΟ]XΒ–εœ†%l™ΝuXΒ–Ν ΐΆ f–°e-3°„-SY‚%lΩΙ,aΛHφ` [2 KΨr>«°„-·3 KΨr8Ϋ°„-W3KΨr2` [ξε ,aΛ±ό%lΉ”W°„-gς –°εFΒΆΘOXΒVήy KΨΚ5Ÿa [ωε9,a+§ό‡%lεQ°„­Μ –°•mΑΆ2,,XΒVVKΨΚ€a [ι(,a+εΒ…%l₯YΠ°„­Τ –°•Nΐ’°•BΐϊΆ’ X_ΓV‚k_ΨJ*` [‰¬CΒVό€ux؊°Ž [qΦqa+rΐ:!lE X'‡­««°Υkΐκ6lυ°z[έ¬ήΒV—«η°ΥMΐŠΆN XΓΦρ+zΨ:&`Ε [G¬ΈaλΠ€•@ΨϊΐJ&lX‰…­½+Ι°΅°[€•|Ψ°R [ΐJ«ΐm+ΕBΆ¬t Φ°R/L[ΐΚ’m+£B³¬μ Κ°2-[ΐΚΊ@l+‡B°¬|ςή°rΛo[ΐΚ3m+η|΅¬όσ°œΘ?[ΐr%ΟlΛ‘|²,·ςΖ°œΛ[ΐr1lΛΡ¬Ϋ–»™Ά,§³k XgΤ° dΡ°ldΞ°ΜdΛ°,eΘ°ŒeΕ°μeΒ°LζΎ-`YΝq[ΐ2œΛΆ€e;gmΛ|nΪ–9h Xžδš-`ω“SΆ€εUξΨ–o9b Xζ‚-`ωYξΆ€εmωΪ–Οεh Xž——-`ω_.Ά€DΩΫV(el X•₯-`…UfΆ€\ΩΨVˆe` X–Ά-`…[ͺΆ€tιΩVθ₯d X”Š-`‘”‚-`Ρ—’΅,ϊZ‚Ά€EϋJΚ°θ`‰ΨR|[ΐ’Γ‹i Xtdql‹Ž+²-`Ρ E³,:ΉΆ€E]Υ«-`Q·υd XΤCέΫυV—Ά€E=׍-`Q”N΄,ŠΨρΆϊ’m:P«ύ|rΉ<::ΪωŽζηΟsOžD“LցuφϊυRήυb΄ν¦Ÿ>ΎxQ;;§††††‡[…Β@­φΓΝ›}…ΒŸoή$0/©ΡhόrώΣΣ­ Τj»―R΄νϊ …ωgΟ~ρ’Z­NLLΌzυJν™™ς~ΆBΥΥΏή½ϋγργ΍«ΟX”@+++ΪΌρŠ΅[₯R™šš’422R“JSS1χ$[Χ맆‡WΟ/•Jccc³³³›››qa­――Ώ~ύzkkK™[·ώωψqnn.‰iΙL…+W†ΪνΞyk·ΫŠΕj·ΫλλλχΫΫΫΝfscc#ζžd«f³Ή½½έ9ο ₯RI‘aΛερkΧ Ε}?*ό½ΈP2Χ·.όtηΞή•ˆc}ως―Ά[­½‹νfσ·>―­E V=}ϊφ£G}ň]‘ύ…¦œ(‚j›IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_hover_roi.png000066400000000000000000000032651421045507400274420ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœgIDATxœνΨΟkΣwΗρW8Y™ΥtsΰμV±‘CAX#¬2ΑΉλΌtu‡1π R cΠÌο@= ‚ΕξΰeΈ!L…QœHέ¦&‚έ$.ŠR1Υξ°Ξ¦5ί~σγϋγσγω$‡φσι'Ό‘’O"™_&h£kγΖm££Ο¦§«ŸU*'φμ™*—γŒμ.ΦγγξάΉ11Q½XΊzκΤΩΓ‡γŒμ.΄ρlzϊΖΔΔΕ±±κΕ΅ρD.” ύ‹ΞNuu%0 9U8¬Υ«uωr“S…ΓzψP™ŒφξM`r§pX’ΚelQc_ή+•MCCk–-ΣϊΊ ½Φ{'ŠΏ$9YZ[ΠΖυ3g^io\*ež”>θ/}χmιϊo₯5νήy|τάΉ$'$GλξΦΝ›sΏ‹Ό'Rxuέ±ͺ+ΉoQx ΓΆ¨Žš%lQXMΒΆhњ‡%lQp-ΑΆ( Va [T«` [τRΡΐΆh~‘ΑΆ¨ͺ(a [τΓΆHR°„-Š –°ε}qΑΆό.FXΒ–ΗΕ KΨς΅Ψa [^–,aΛΏ‚%lyVr°„-ŸJ–°εMIΓΆό(XΒ–₯KΨr½Τ` [N—&,aΛέR†%l9Zϊ°„-3–°ε\¦ΐΆάΚ XΒ–C™KΨr%γ` [Nd",aΛώ …%lYžΉ°„-›3–°em¦ΓΆμΜXΒ–…ΩKΨ²-k` [Ve,a˞,ƒ%lY’}°„-²–°e|ΆΒΆΜΞbXΒ–ΑΩ KΨ25λa [Fζ,aΛΌ%l–;°„-“r –°eLΑΆΜΘAXΒ–Ή KΨJ;ga [©ζ2,a+½‡%l₯”ϋ°„­4ς–°•xΎΐΆ’Ν#XΒV‚ωKΨJ*ο` [‰δ#,a+ώ<…%lŜΏ°„­8σ–°[ΎΓΆβ XΆbX³a+Ϊ€5Ά" XσΒVTka؊$`Υ[­¬Ϊa«Ε€ΆZ X‹…­¦VHΨj.`…‡­&V]a«Ρ€UoΨj(`5ΆκX…­:VΓa«ž€ΥLΨ XM†­ΕVσak‘€ΥRΨ X­†­š+‚°υrΐŠ&l-X‘…­κ€eΨz°"[¬θΓ–€SΨV\yn X1ζ³-`Ε›·Ά€{~ΪVyh X ε›-`%—WΆ€•hώΨVyb X)δƒ-`₯“󢀕ZnΫVš9l X)ηͺ-`₯Ÿ“Ά€eDξΩ–)9f Xε’-`™•3Ά€e\nΨ–‰9` X†f»-`™›ΥΆ€etφΪ–ιYj Xd£-`Ω‘uΆ€eMvΩ–MYd X–e‹-`Ω—Ά€eeζΫ–­n Xg²-`ٝ±Ά€e}fΪ– h XŽdš-`Ή“QΆ€εTζΨ–kb Xf‚-`ΉYκΆ€εlιΪ–Λ₯h XŽ—–-`Ή_*Ά€εEΙΫ–/%l X•€-`ωUbΆ€ε]ΙΨ–%` Xž·-`ω[¬Ά€εuρΩ–οΕd X‹-`‘ƒ-`ΡlΡΪΝ‘-`ΡΌ’²,ZX$Ά€E5jέ°¨v-Ϊ֊­LΠF{>ΏiηNIΪ½[ϋχKRejκ§#Gš“μ¬XΤ̌~όkWnι[珛zπ ζ©Ά §ϋτθΡU}}š™ιX™_·>νfΎ=Ÿ_Ώ}{&›½59εΰdvgΟκ£/w­δ³Ηνωό‹ΗšB‘wΛ–_Ož¬y*τt™lφΒργΗΖΊ»υυ‡–€‡-y‰-9ίχ§—~όΖΟ} οΫ7·XμΫΆ-θw,ͺ«ση»oΎb½hΥ*uuιΦ-IΊΌBΛ¦΅ζ«$ »ρš¦rz·¬ŽŒhέ:έ½r$Φδ€ϊϋuοž$mύFχΠΕcΜJΥΉVΌ­ΣEIΡ“'αGΒa=}ͺK—f~τHε²nίnaF²°ή²rωΩϋȈ::Βj[²€gσζlΫΌ+{zώΎv­Υ1ΙΒήμν- V―τ dsΑώ‚6Ί6lΨ::ϊ|zΊzρy₯2><τΥΉΪ«Λ—ο8x03U&›ΛύpΰΐŸW€5QΛύ g1Vfy6IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_hover_segment.png000066400000000000000000000061371421045507400303140ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ IDATxœνέ{P”εποŠ„+tˆ”EgD$E,ARTšΡ€ζ(byι‚vρv&ocj3ޚςLj₯8gRs q(9ˆ6!:J)δρ™9 ΪLHοš₯˜". ηνЊ`χΩ}ήwίοη/}gyžοŒΏω=Ώ]δ """"""""""""""""""""}1Y%$::yιRkCƒύC«Ε²{ήΌΊkΧ„lAΪ"¦°fδδ\―©©*.Ά8$-νl~ώ7›7 Ω‚΄₯£U¬ UΕΕΞΞΆΨ;.NΘβ€EΔ.ηοoθΤ©“Ψ5I‹–ŸŸ133P욀E‚ «ΆΦKQΩΉSμͺ€=‚ ΛΛΛ+;;8?‡‰]˜4FΠπn±Δ¦₯υŽ‹λΤ©S@@@MΝδ7ρχ7žώtή!λ“ζˆωΈΑψΨc±―Ύ ΐΟΟ―kΧ—.]`κR·ιΫ»u²ι[PPΠΰΑƒνώŠΖFršΧρ3–Υjmώ«’ΐΛ ee0@μ>€v‚ «C‡φOšš`2!+ £F‰έŠTMΜπή¬EΗjφδ“8|AAΘΚ»!©”Λ;V³„Œ‹ωσΕnH*εΎΒπςΛ ΕΪ΅bχ$5rνπ~― pγ>ϋLμΆ€:ξ.,|€’μί/vgRΑΓϋÏΒfΫ·CQPZŠΨX±ϋ“ZΘ),_}³—.‘W/±H$…ΝΎ#F >*6ΙηΦw…χϊι' Ί}ϊˆ B’ΙμX6·oγoC~>†›…d’ά±š…‡cέ:€¦ŠCΘοXΝβ␖†Y³Δ&"9Τ±l&L@t4V˜ˆδPQΗ²™=^^ΰ#jΊ:–Ν»ο’Ό99B‘GHHH0B–zρE=*d%’ΐMΛ_~ EΑΉsθΧOΘzδVj< ›;†”όώ;Ό½E-In’Ία½…Š „†βζM„„\•\Nda MMMΧpνy%%4HμΒδB" Kμ9ΨBϞآ ΙΙ.Zž9Ό ?[ˆ‰ΑΧ_ΓdBF†λ6!14Σ±lžρρX²Δ₯›" ΛΥΛζ΅ΧΠ₯ 6lpυ>δu,›wήAu523έ°9H{Λ棏PP€ƒέ³΅›ΘαέmΛfηN( ΜAT”Ϋφ$cέώc7’¦ΖΝ{Rλ49cΩϋαDGΓjEΧnή™F«3–½_~·7ΚΛΡΏΏϋ7§ϋΣ|Η²±]ο–“ƒψx)ϋSKήο‰#G„»“Iΰ G‘½Q£’‚yσδ¦ O9 ν½τΒΒ°fμϊζiΛfώ|άΌ‰mΫdηΠ1μX6kΧ’΄ϋφΙΞAΞ‹ŒŒ •β.cΗβψqΩ!tΙ£ήήk~˜ΝΈx*+xΟη™3–½RΔΗγΦ-ψωɎ’';cΩ»t ]Ί ¦aa²£θ†. ψσz·‚ &;Š>xώQh―OlΨ€ργeηΠ}€αΓ1mfΞ”ΓΣιε(΄—šŠ˜¬X!;‡GΣca˜5 ήήHO—Γsιξ(lΆr%**°{·μΤ*wΉΝΔ‰ψφ[Ω!<‘–~Ϊrr ((/GD„μ(žE§3–½£G‘šŠί~CGΑ—…ιš~g,{ηΞ!, uuθΡCvO!¬°\t‡‘ΫΨw+-Et΄μ(AXaiτl!$Ϋ·#)Ivν6VhχlaΠ δηΓd޲£h;Φ}$'#!‹ΛΞ‘eΒ Λc:–Ν΄i Δϊυ²sh;Φ-Y‚šˆba=Μ† 8t²sh‡χVdfBQpϊ4?†hVλ„ΩŒΛ—Ρ½»μ(ΪΑ£°MΜAL Π₯‹μ(ΑΒj«šψψ ’‚ίn~άΠΆλέφμΑΘ‘²£¨ž°Λγ;V³ώύQXˆ  μΪ%;ŠŠ±c9">©©˜;WvγŒε )SŽΥ«eηP+v,ΗΝ›‡Ί:lέ*;‡*±c9eΝœ8Ό<Ω9<˜ ο0r›qγPR";„Κπ]‘ϋφΑlFUzχ–E5xŠqό8pσ&|}eGQοΒ\Όˆΐ@( ϋΐŽ%–νz·C‡0t¨μ(²±c‰†?FJŠμR±cΉΔ°axύu̘!;‡<μX2~<Ζςε²sHŽεB3gΒΗ›6ΙΞ!;–k­Xσηυψ Ψ±\.={φΰ›odηp/~ςξ»wCQpφ,Ugϋ%%%uδ=@«Wαε%;‡[pΖrŸςr„‡£Ύ^?ν#¦°΄~‡‘ΫΨw;qQQ²£Έ˜˜Ββ€Υ.=zΰσΟ‘˜(;‡+‰™ŠxΆWt4€Ι„ΜLΩQ\ƒKš€$ŒƒE‹dηp –LS§"(λΦΙΞαb ‹G‘Γ/†’ #CvΡΨ±δ[Ώ‡γΐΩ9„βπ ;v@Qpκ ’E1…ΕŽεΌ‚˜Ν¨φ»ζ9c©ΘιΣ< \v§qΖR—Λ—a4βόyτλ';ŠsΨ±TΗv½[n.FŒΕ μX*χήΓ€I²s8Š…₯^#Gβ…0gŽμαQ¨j“'£o_¬Z%;Gϋ±°Τnξ\ΤΧcΛΩ9Ϊ‰G‘¬^“'±w―μνΑH΅aλV( Š‹1|Έμ(mΓoιhF^ΜfTV",Lv”6ΰQ¨%%%3όΞeGi ‡w©ͺBP\Κ―OdΗΫυn‡#6Vv”cΗͺή½±q#ƍ“γΨ±4lθPΌω&¦O—γ~Ψ±΄-%±±XΆLvŽ{°ciތ0±q£μwcay‚εΛQY‰μlΩ9μπ(τ›6!7……²sόΏ₯γ9vν‚’ ¬ ‘‘²£ˆΒ;ŒΤ£όϊ+:»FΘAœ±<ΝΩ³θΫ ‚ƒeΖPXƒΑ`0°°ΤΓv½ΫΙ“8PZ…Εv₯Nέ»##Ο='gwƒ K΅’’PP“ ;wΊ{k‹Ÿ5¨Yb"ρφΫξή—G‘ηKKCp0>όΠ­›²cιΒ’EΈr_|αΎΩ±τbέ:"?ίMΫ ήΩ±΄"#Š‚“'γς½ψP_€ΩŒŸFHˆk7βŒ₯;§N!6wξ ΐ…»pΖ£κjψϊ’²}ϋΊj –NΩwΛΛΓ³ΟΊd}…ΊΦ―V­ΒΔ‰βWfΗ»#0q"ήzKπ²μX„I“χίΉ&;ΐœ9°Xπι§ΒdΗ’?­Z…Σ§‘›+f5~@JΩ²Š‚οΎC\œ³Kρ[:t—½{a6γΒτιγΤ:œ±¨₯βb$&’ΆF£γ‹pΖ’ϋ¨¬DΈz½z9Έ;έߍθά……2Δ‘/gΗ’‡ Ez:Ǝmχς]!΅"6ϋφΑdΒφννψ*v,jέΈq6 K—ΆγK8cQ›LŸ__|ςI[_ΟΒ’ΆZΆ /"+«M/ζQHν°q#ςςpδHλ―δπN퓝 EΑ?bΐοΔ;Œt(2Wΐ`xΰ 8c‘#ΚΚ«έΊέ.Ή‡κ;uͺνΟ‘‘‘eee,uuE[·:Ά iTM α?|vGϋηa“7o6ϊϋ_ΉpΑ`0M¦·nθ5dHSSΣwΫΆ ΘKŒ‚Ÿg_;ν?K,χFVΦΣS¦πφφNLL΄=œ”ž>R£Ώh–œ?ώΏ~ϊδ•WξzθμŒΕΟ@n.’“±pα_Oœ}7g4}||F  gϞ@ΗΡ£\“΄%μ‰'ό‚ƒΏψηθY³ώ›m˜6ν—ϊϊzg λϊυλǎ»sη€ΐ ~­ͺ***‘–4£CTΤ㍍EEEEEXΈ°ΑΧΧZ_οtΗjll¬­­΅ύΩj΅Z,–Ϋ·o;•΄Δb±X­VΫΏϋΪ΅ώ’N ΛΛΫ;μ™g:xyΩ? 3WT8“΄Η>δξιέΑΟ±BžzκΉ₯Kμ6Z,9 Τ]Ώξx@ £Ώ€M› ww""""""""""""""""""""›βSyέeIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_init.png000066400000000000000000000033131421045507400264030ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ}IDATxœνΨΟk“wΐρOΣ'kRM‡Σ‚ΚL1q’‡U°‚NΛ@ΌyπRλm7Ο=ϋ¨xΚΌxΣ€KCDE -XΨ¦ΘΖBŒ­ΥΆ€e[šΨ–…ι` Xv–Ί-`Y[ΊΆ€es)Ϊ–ε₯e Xφ—Š-`9QςΆ€εJ Ϋ–C%i Xn•˜-`9W2Ά€εb Ψ–£©Ά,wSj XN§Ξ°\O‘-`‘[ΐ"Ά€E‹Χ°θs1Ϊ­+.[ΐ’Εb XΤ€θΆ€EΝ‹h XX[]Az …c—.‰H6›έΏύT–—'oίξtN2²:¬]§O{==.ύrχξςβbΣ]έAOwρΝώƒeuu[___ΏŸΙτ ߜ;Χ•ΙόρμYŒs“ζ•Λεο._ώφβΕ{ …΅ΫΧƒƒNž|ρΰAΣ]^ΠΣue2Ώή»χt|<ŸΟ?~όργΗ"rαƍμ&Άd}ε……?_Ύ|qλVύƒ«ήΰΘΘΑ³gƒΆpΖ’–šŸŸ—vΞ[οXkυττδrΉααaΩ»woAΔŽ2"Χ@©΄­Ώ~ί>ΟσŠΕβΔΔΔϋχο·ήkiiιΙ“'>|‘ηΟ=7799ΟΌdH™#Gϊj΅ϊλ^,k΅Zθ–pX΅Zmii©~ΏZ­V*••••ˆƒ’YU*•j΅Zέ§§§=/œMΰ#Ί³Ω'2έλΎ6ξψkf&ϊ d\_8082Έ204” ψ;ΦžΓ‡Ώ«ω~γb­RΉυjΠOdkωνΫ/\Ώή΅ώ]&γy?]»φφΥ«΄¦"""""""""ŠάτΘ9?€SΑIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_setpoints.png000066400000000000000000000031321421045507400274670ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ IDATxœνΨ»k[WΐρŸ₯«Z2†X.nl¨–ˆ††,mΐΙΰΖ¦KΆ ^βdθœ!sώŒ2²θ?Εx°'SB‚Α†„>œBK…₯υCwPpdΧιάΧωσύ’A:p~p?ά{₯¬υύσηΩ\ξχ·oΫ§gg‡ΚεŸ—–Μφ$—Κ„<>—ΛεσωHF!— +ŸΟOLLD1 9UXXυz}ΏR©D2 9SXX"R―ΧE[Τ^`vX£V»vχnιϊυ †‡‡?_[8ωςΪλΧΡΞGJ˚Ά:?Y_ί?ΥκΞΖF6ϋλΚJυΓ‡Ώί½ϋεεΛj΅νˆδc…Barrςΰc₯RαžHΙ3V{«««ΒσEK°E",ΑΕK°ε}qΑlω]Œ°[/,Α–―ΕK°εeIΐlωWB°[ž•,Α–O% K°εMIΓlωQ °[”,Α–λ₯K°εtiΒlΉ[Κ°[Ž–>,Α–‹YK°ε\ΆΐlΉ•E°[e,Α–+YK°εD6ΒlιΟRX‚-εΩ K°₯9«a ΆΤf;,Α–Ξΐl)L,Α–ΆΤΐl©J,Α–ž”Αl)I,Α–†TΒlYŸVX‚-»S K°eqΊa ΆlM=,Α–•ΉK°e_ŽΐlY–;°[6ε,Α–5ΉK°eGΒlY›°[iη,,ΑVͺΉ K°•^ŽΓl₯”ϋ°[iδ,ΑVβωK°•lΑl%˜_°[Iε,ΑV"ωK°žΒlŜΏ°[qζ5,ΑVlωK°OΐΑV λc؊6`} [¬Ca+ͺ€u4lE°Ž [αΦρa+dΐ:1l… X§…-γ€uFΨ2 Xg‡-ƒ€ΥQΨκ6`uΆΊ X]„­ΞVwa«Γ€ΥuΨκ$`™„­3–aΨ:=`™‡­SV¨°uRΐ ΆŽ X„­¬hΒΦ‘€YΨjXQ†­ƒ€qΨj¬θΓ–+¦°¬Έςά°bΜg[ΐŠ7om+φό΄¬$ςΠ°Κ7[ΐJ.―l+Ρό±¬€σΔ°RΘ[ΐJ'ηm+΅άΆ¬4sΨ°RΞU[ΐJ?'mˊܳ,[rΜ°,Κ%[ΐ²+glΛΊά°,sΐ°,M»-`Ω›j[ΐ²:½Ά€e{JmKAmKGκlKMΊlKSŠlKYZlK_*lKeφΫ–Φ,·,ΕΩl XΊ³Φ°Τg§-`Ή…Ά€εHΆΩ–;Ye XNe-`Ή–%Ά€ε`6Ψ–›₯n XΞ–-`Ή\ŠΆ€εxiΩ–ϋ₯b X^”Ό-`ωRΒΆ€εQIΪ–_%f Xή•Œ-`ωXΆ€εiqΫ–ΏΕj X^Ÿ-`ω^LΆ€E±Ψ‰Δ` Xτ±hm‹>‘-`Ρ‘’²,:Z$Ά€EΗή°θψBڝX[ΐ’Σ2Ά,:#3[ΐ’³3°,κ¨nm‹:­+[ΐ’.κά°¨»:΄,κΊNl‹L:Σ°Θ°Σmυ˜mΪW,^»wODrΉάΨΨXλ;jΫΫ‹Ož˜ΞI*kΑ:σfΠΫΫΎž5ΫnζιΣ‘K—dΏpppd€žΙτ‹_έΊΥ“ΙόφκUσ’’ͺΥκ·χο=3σοζf_±xπ ΜΆλΙd–ž=ϋρΕ‹B‘0>>>77'"Σ³³ΉΓlΙ‡ͺΌyσΣγΗ­W+ž±(‚ΦΧΧεπσ–αλ ήήή|>?55%"£££E‘`j*䞀«r₯?2²~αB₯Ri~~~ww7,¬­­­………½½=Ί}ϋ―χο£˜–Τ”Ήzu°ΩlχR©Τl6%ό«Ωlnmm΅ή7Z­Ά³³rOU­Vk4­σΎΌΌΓΚζrε72ΩC?*‡Κε?WVΒJκϊββΕoξάi_1όλΛ+WΎ{ψ°Y―·/6k΅<Ψήά4V8wnϊΡ£ž¬α_WDDDDDDDDDDDDDDDDDDDDδF1Έ‘ΥGO]sIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/closed_setstate.png000066400000000000000000000033131421045507400272740ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ}IDATxœνΨΟk“wΐρOΣ'kRM‡Σ‚ΚL1q’‡U°‚NΛ@ΌyπRλm7Ο=ϋ¨xΚΌxΣ€KCDE -XΨ¦ΘΖBŒ­ΥΆ€e[šΨ–…ι` Xv–Ί-`Y[ΊΆ€es)Ϊ–ε₯e Xφ—Š-`9QςΆ€εJ Ϋ–C%i Xn•˜-`9W2Ά€εb Ψ–£©Ά,wSj XN§Ξ°\O‘-`‘[ΐ"Ά€E‹Χ°θs1Ϊ­+.[ΐ’Εb XΤ€θΆ€EΝ‹h XX[]Az …c—.‰H6›έΏύT–—'oίξtN2²:¬]§O{==.ύrχξςβbΣ]έAOwρΝώƒeuu[___ΏŸΙτ ߜ;Χ•ΙόρμYŒs“ζ•Λεο._ώφβΕ{ …΅ΫΧƒƒNž|ρΰAΣ]^ΠΣue2Ώή»χt|<ŸΟ?~όργΗ"rαƍμ&Άd}ε……?_Ύ|qλVύƒ«ήΰΘΘΑ³gƒΆpΖ’–šŸŸ—vΞ[οXkυττδrΉααaΩ»woAΔŽ2"Χ@©΄­Ώ~ί>ΟσŠΕβΔΔΔϋχο·ήkiiιΙ“'>|‘ηΟ=7799ΟΌdH™#Gϊj΅ϊλ^,k΅Zθ–pX΅Zmii©~ΏZ­V*••••ˆƒ’YU*•j΅Zέ§§§=/œMΰ#Ί³Ω'2έλΎ6ξψkf&ϊ d\_8082Έ204” ψ;ΦžΓ‡Ώ«ω~γb­RΉυjΠOdkωνΫ/\Ώή΅ώ]&γy?]»φφΥ«΄¦"""""""""ŠάτΘ9?€SΑIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_clear.png000066400000000000000000000011631421045507400262170ustar00rootroot00000000000000‰PNG  IHDRΘΘ­XžsBIT|dˆ pHYsttήfxIDATxœνΣ±ΐ ΐ°‡ύwNΰάB!MΰΖkfΎŽφνx™A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ‚A ΒιŠ‘ΰφ IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_click_segment.png000066400000000000000000000037251421045507400277460ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ‡IDATxœνέKO›Wΐq_πέΖŒΑccƒΑ\ŒΉ0ƒΑάW]tT΅©4‹ΩV]η3dΥfΥn»ˆ& #E‰fA2κˆHQ”*A#5R3™‘†H‘ 56³`D:+ς=γΓ'/ΰDzύ—x…ό“ ׋ِ«τNNnίΌY;=m\¬U«όβ‹ΚΡ‘!Oζb̍υ‡»wίώόσα£G‹Ή7ώφύχΎ}ې§@si1δ*΅ΣΣΓGξάi\L,.rq4#‹±—ki1ζNE³3ψΖ2›Ν###Ζ^ΝΘΰ«Z­ONN{Y4ƒo,“ΙtxxψζΝ›……Γ―Œ&bΠ‹χjuξƍΖWλN§s ŸΣ“'†\Mǘ·\ΐάgŸ]Ύt½nωςή½{υzݐgώΛf³νμμΈ\.ιθhmm-HW@G…B‘««KΊ:šνλλ“€Ž2™L*•’€ŽR©ΤψψΈtt‹Εfff€+ £ξξξEώφ*΄΅΅•J%ι θΘνvoooσ—60žΥjέΪΪςxŸossΣl6f π+›ΝΆ»»ΛΠ(ΑΠ¨ΒΠ¨ΒΠ¨’Ιd₯+ £‘‘‘±±1ι θ(3tJ0tͺ0tͺ0tͺ0t 1tͺδσωp8,]MOOΗγqι θhllŒ‘#P‚‘#P…‘#P…‘#PΕοχ―――KW@GN§swwΧn·K‡@;f³yccΓησI‡@GΕb‘‘#P‚‘#P…‘#P…‘#P…‘#P…‘#P…‘#PΕησmll0tΖ³Ϋν»»»N§S::Z__χϋύΠΡCG CG  CG  CG J<Ÿžž–€ŽΒαp>Ÿ—€ŽΪΫΫ:%:U.†ŽΨν&ηςΓf“ξCS+—‹?όΠςξιcίΔ―3\ΗcͺT¬Ώ:R©˜ν6£E:@΅Z-‰0tδ7–a?~μυz³Ω¬tΘn,#={φ¬^―ΟΝΝI‡ΘγΖ2ΨΣ§OŽŽ˜x<¦wοή[ιξξΞησΧωΕ;6ΐ/Ώ˜ž<1U*k―Νζόψ£ττ­TtΠbr:/?/GuB‰σ£:‡ttT.—h%ŠΕb0”€Žζηη#‘ˆtt455ΕΆ”K₯RΠQ*•—€Žb±ŸΘ€αpxaaAΊ:κθθXYY‘€ŽΌ^οΖΖ†ttd·Ϋχφφ8ΗJpŽT)‹ΠΡΒΒG C ކ*lϋ@•T*Ε .(Η§¦¦€+ £H$ΒΆ”θθθΰŒ (ασωΚε²ttδp88ΎJœ_νυz₯C £•••φφvι θ(ŸΟwwwKW@G333±XLΊ:ηό(1444::*]υχχ³ν%"‘G£C‰`0ΈΌΌ,]΅ΆΆ²ν%Ξ·}l΄Γ™ΝζΝΝMΗ#­Ά΅΅IW@G‹‹‹lϋ@‰™™™ΎΎ>ι θ(“Ι HW@GΓΓΓlϋ@‰ώώώΙΙIι 訧§‡3Ι Dggη’ttδχϋΧΧΧ₯+ #§ΣΉ³³ΣΒθ0œΕbΩΪΪr»έ!ΠQ©TbΫJ,..vuuIW@G³³³ΡhTΊ:š˜˜H&“ΠQ:‘€Ž‰D6›•€Žz{{Ωφlϋ@ΏίΏΆΆ&]Ή\ννm«Υ*νX­ΦννmΆ} D©T ΠQ‘P…BΠQ.—cΫJ°νUιt:–€Ž’ΙδΔΔ„ttFsΉœtt … …‚ttJ₯’ttδv»ΩφηΫ>.—λώ«ωju·΅Ν}ώω₯Εj₯²Ν7W» šΤΪΪΪΑΑAζ“OZŽΖυ+~pρw·o»όώΎxΡΈΟεΞΞΞ~ϋνΥ3ΡlξίΏϋ[·Ί‹Ε6_ρΖ2[,ύ;w?ώϊkΫϋ·-ƒΏτ“χεΛΏάΊυκΥ«‹E‹`΄ρϊυλ`0˜H$.V>τ3όN§σβ]h4Ϊf2΅0vβšI¦RήpψίΑ`8zπΰΑΙΙΙ‡ήX'''ϋϋϋη_w~τΡΏ/ΎΕ5aΙfΫλυσŸ{,;;;3}ψo¬³³³γγγσ―k΅Z΅Z½ψΧD΅Z­Υjη?χηϟŸΏΉuΕΛj³% Λϋοu&“ož?πP4Πΰ`ξΣOWψ>Vo&³qσfύτ΄q±^­ήύςΛΚΫ·WDrωύυ•™wα·Κγsξž>uώIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_drag_handle.png000066400000000000000000000033111421045507400273560ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ{IDATxœνάΟOΪwΗρ/Ώ‘ͺ€Q±€ ώ`‚(ώ‘‹§Άd[{ΨΠτά‘Ωeλi½φΠ€ΐziMΊμ°«Iλ’&͚nΛ~\ͺAΛ$nΩ…~ίω„ΟΗ ?‡―ΔoŒ1ψ4 \,SnΙεŒ;wŒ““s‡νΆqλ–ρΗ¦ΌϊŒ9Φ£GΖΟ?ϋϋηoά0ΎϋΞΈwΟ”w@Ÿ±›rΛΙ‰±Ώo<|xξpcΓ”»Ρ—¬ζ^g·›σ€’ί™ό`Y,–ΩΩYsοD?2ωΑj·ΫGGGΉ\ΞάkΡwL~° Γ8<<|σζΝκκͺι7£˜σ+Q»mάΈqφ·υWnχokkΓ?ώψ—)χ£ο˜σηŸΟψβ‹”φφφLy ΰ.—kwwΧιtͺmmm ©^U*•Λ—/«^­†ΓaΥ+ £\.755₯zt477733£zt4==ΝfU―€Ž"‘Θςς²κΠQ(*•JͺW@G>ŸοΪ΅kͺW@GΧ―_ηS\0ŸΥjm6›—.]R=:ΊzυͺίοW½:ΪΨΨS½:* ΡhTυ θhaa!•J©^₯R©ωωyΥ+ £X,Ά΄΄€zt4>>ΎΎΎzt422R­VU―€Ž···-sώιψ‡Γαh΅Zn·[υθ¨^―{½^Υ+ £rΉ<::ͺzt΄²²299©zt΄ΈΈ˜H$T―€Ž2™ ±$ˆˆΗγΔ’ "‹EΥ+ £`0ΈΉΉ©zt4<<άh4T―€Ž\.W«Υ"–σY,bIR©T€κΠΡΪΪΪΔΔ„κΠQ>Ÿ'–Δ’ …X€K‚bIβσωj΅šκΠ‘ΗγΩΩΩ!–σΩlΆf³ιρxTŽj΅±$ˆ –)…B!‰¨^e³Ωd2©zt”N§‰%A±$H!–)Δ’ …X€K‚ bIR.—ƒΑ κΠQ±X$–Δ’ …X€ΔγρΕΕEΥ+ £ΙΙIbIA, Rˆ%A ±$HιΖ’UŽͺΥ*±$ˆ –)ω|>‹©^ΝΟΟ§ΣiΥ+ £d2I, "ˆ%AJ(ΪΨΨP½:ςϋύΔ’ ’K²Ωlͺ‡@;6›mgg‡XDK‚bIB, Rˆ%A ±$H‰Εbω|^υ θhbbbmmMυ θ(K‚bIB, ‚κυϊππ°κΠΡζζ&±$ˆ –)Δ’ %“Ιd2Υ+ £D"A, "ˆ%AJ0,—ΛͺW@G^―·^―«^ΉέξV«εp8Tv,Λφφ6±$ˆ –)Δ’ …X€K‚bI‰D …‚κΠΡΨΨ±$ˆ –)Δ’ …XΥj5ŸΟ§ztT*•B‘κΠΡςς2±$ˆ –)ιtznnNυ θhjjŠXDK‚”@ P©TT―€Ž†††ΆΆΆˆ%Α|N§³ΥjΉ\.ΥC £F£A, "ˆ%AJ±X ‡ΓͺW@GΉ\.«^ΝΞΞK‚bIB, Rˆ%A ±$H!–)Δ’ ¨Z­ŽŒŒ¨^­――«^---K‚bI’L&T―€Ž’Ρ(±$ˆ –)Δ’ Εγρ4›MbI0Ÿέn'–)Δ’ …X€K‚”l6;==­zt433C, "ˆ%A ±$H!–)έX’κΠ‘ΣιάέέύΏXRy.ί_Όyσ_‡ν·oŸ|ϋmo’O5gϞ}τΩgφσO˜½·λ>½woΐλύεω󳇱••N§στώύήg’ίμνν}ωΥW‘ΝΝηOŸž=ορΑ²X­ί?xπΓΓ‡g?ωζΉ‹η§—//½x±χξ«W―ήZ‚6^Ώ~=::z6–ΤγO¬χάnw©TκΎΎrεŠί0μόϋφ“H₯ΗΗB‘P*•zόψρρρρ‡>XΗΗΗOž<ιΎ~όρ―‡‡οΏΔa]\yχϋ}F£NΗψπŸXNηθθ¨ϋϊττ΄έnΏD»έ>==ν~ίΊŸ;νρΑ²9‰RΙzώ£«ΑDβΝΑΑ‡EίM&W>όμIǚΜf·ξάywrrφπ]»ύθφν·ώΩϋ@τ‘―χ“―ΏΆπy€‹νo’Δ3ό£°±IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_drag_new_handle.png000066400000000000000000000047641421045507400302440ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœ ¦IDATxœνέΛOΧπ™ρΨΖ06A Α`‡G€Δπϋ !Ό²κ’wΡ¦»«ξͺσg΄Y΅Ϋ.ΠΝ²‹FU]E$Š’*Κ½ΊΊR₯*Ι"J{ό6Ά?ξ‚ΚušKΝsόύˆ‘™―ΰ§3γs~ν…'r•αΉΉυ[·ͺ•Jσ`U–ρΥWΕtšΘ-@]ΘΦίοάΙΌ}ϋβΡ£ζAχΝ›ύι§ήΎMδ ."‘«T+•ύ²΅Υ<8Ή8¨‘@φr’H¦RAν–  d― jDΈ°^Ώ~½±±ΡΫΫKφΚ .„ ‹γΈ»wο:N›ΝFόβ „ο²μΉyσ½ΥzοΤΤΨ›7OM¦B‘@δ. "d^n0tw{>ό½Αƒbρ_[[n·ϋΥ«W/_Ύ$r#€?LOO{½^A Ψ…3Ks χˆΗγ₯R)‰δrΉ|> w„φ²°°pωςeΪ)ΰ4œΖŒΥπζΝ½^οv»%I*—Λ§yk`Ÿ^―…B/^€X411 u:ν  ˆS}6“$)›ΝrΉœΝfiΕfΉ\«W―N„Q›±vvvxžƒ©TͺX,ŽlΡh4>ŸojjŠv`‘ΓαˆD"F£‘v8)ϊΒf©T*™Lz<žz½žΖ›εΈ™™·ΫM;΄ξlΝX {{{²,Ηb±L&ƒwέy‹‹‹³³³΄S‹FFF–——;;;iζ †H$βt:iMNNϊύ~μ0S…3Ίx?R"‘( ‘P¨X,ζr9Ϊq€9ssssss΄SΐΗ¨iΖjΨΩΩΡh4@ ™Lβί‹@˜(Š~Ώrr’v`‘Σι ‡Γ΄ƒΐ;Tω(l–L&S©”Οη«V«™L†v`Ξμμμββ"νπ;ΥΟX {{{Υj5¦Σιύύ}Ϊq€-<Ο{<ž™™ΪA€Ev»}iiΙl6ΣΜ1ΡhΤαpΠ,šššςω| ;KI΅`ό'žH$ŠΕb8. 8Θ›ŸŸwΉ\΄S΄Ζg¬†·oߊ’θυz%I*•J΄γ[t:]0§X4>> …τz=ν ,k—Ga3I’2™Œίο—eη‘yW\A»…΄γŒΥ°»»[«ΥΒαp*•ΒΏ0AΌ^ο₯K—hΕb1“ΙD;0Ηd2Εb±ΡΡQΪA€EΣΣӏ‡ηΙτμh[m½x?R<?88ˆF£hwŠ@»ƒ“ΐŒυ§Πξ„v  ‰‰‰@  ΥjiQ < E’€|> ΡξvΗ„λ―9lw€σH@‡η‘ έ(β°έΑ` δ,Β£°u‡νΌ^/Ϊ€"ΠξΰC˜±h΄;H§ΣhwδΉέn΄;E έ(ε°έA›ŸG‚·³)ejjΚj΅>yς€R©θtά‡f<8ΰd™F2P;›ΝΆΆΆ622ψπ!—ΟΏ±½Ν‘Ι΄.pνο Ž‹Ξz†Φ™L\‘ΐ―Z­ΦζqœˆΙΔεσœV« ‡ΓΝ―Ω.¬#¦hP‚,Λ<E±M^£GaͺǏW*•ααaΪA‡Β:mϞ=³Ϋνέέέ΄ƒ( ο*ξΰ€{ώœ{χMΫ‚ <}ΚW*uZ©€’ΘutΌσΡΥ₯[__‘ Xd΅ZνJΑΫf¨9…₯>’$]ΈpA£Ρδr9ΪY€9‘PΘb±ΠN,Z[[q( §ΧλWV°O`³Ωό~?νGΐβ]݊Ε"Οσ###gmŸ> Kυ2™ŒΝf³X,©TŠv–? °XH$G­VΓ>} ϋτA)›››<n#@šΡh\ZZ’XΤΧΧηρxh§ΐβ9ϋϋϋZ­–ϊ>}œ›Γ W―^qg·Ϋ)η&y<žΎΎ>Ϊ)€EΨ§Šΐ>}PJggg$‘XDeŸ>^n`•}ϊ(¬ΆJ₯†††t:]6›₯˜ƒ}ϊ μΣE`Ÿ>(₯»»; *},ήΫN©T:…}ϊ(¬v”Λε,‹ΝfS «MI’d·ΫyžΗ>} ϋτA)λλλ \@ZGGΗ΅kΧh§υττψ|>²ΧΔ\±Xμ>}pΗe2™žžžΞΞNRϋτQXπ»x<ξt:«Υ*ΪΩyΡhΤl6ΣN,Ίqγνΐ"“Ι‹Εh§υχχ»έξ“\‹w8B‘PΠιtCCCρxΌ΅+ °ΰhιtz``ΐ`0 ηυz{{{i§-// Ϊ)€9‚ lllΠN,κκκ ‡Γ΄S‹ηηηύψ«Ž%ŸΟ›L¦ώώώcξΣGaΑq%“ΙααaQ±OΘ V«•v `ΡυλΧu:νΐ­V»ΊΊJ;°Θj΅|οЊR©T―ΧΗΖΖώlŸ> Z”Νf?²O…­“$itt”γΈΫΩ΅Ψ)Κh΅zΎψβ½AΉXάώξ»Φ.κ‡υW―VκυζΑ{ νφmƒΕν·ζA»Ϋ]―Χ~}λA…κ—.EΎόςί?\oͺ­ ‹„'?όπΛΦVσΰ§ί~«Ε1„νGΤλsο^ςΗοέ»ΧD“&  Z­>ώάλυ6FZœ±:::ηž?ήΚq"N˜h3Žρqσΰ Λεh4λλλχοί/—Λ'-¬rΉΌ½½}ψyί'Ÿ$^Όh| mBpΉl΅Ϊαο]ΕZ­Ζ|ΖͺΧλ₯Rιπσj΅*ΛrγKh²,W«Υχ~ο-–F«uƒΒ»vυ9{ΏώΪz@P­ώ‹ݟ}Φ<βλXΓ—/_Ώu«V©4ΦdωΞΧ_±[¨Ν,–OΏω†Η±€νν>$ύ2εœύIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_drag_roi.png000066400000000000000000000020531421045507400267160ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœέIDATxœνΨ?hœuΗρίεBDs X‰P ‘ PM¨νjqpΞ?’.Γ-B—^prmΆ©CK‹βζ œ¬mCΑ"ͺ8[τ …+\:μ%ˆΓσδCΉδυšžη Ϗ/ά›ηŽk–JήΏx±96φλΝ›ƒΓΕ••=³³Ώ¬V;“d€ζσ­ΦΆ¬ΑNS7¬F£œ:΅-›°£Τ «ΫΥ‘nX₯”NG[l5Zν±υ~αΨ±—όηφΗRζηΛ›/ΎώYηϋνۍ!Φ¨φΨΔΤΤΒΡ£[†oΏΥλ}χΙςrν₯`‹NΗw"Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘ς?m5ͺψΜττΒργ[†ύ^ο›3gͺȐκtΚγΗελίNŒŽΞ+†υή… ­Φ·oχΞΟ{ξά•³g«―ΙϊψΛγ―½{ε‹λƒΓΡjg5FFVϟΏqιΰpqeelsΆμ_]ηωλ―ό΅΄Όόd8ςτφaηΈv­4›~oU|cύkf¦\½ΊqύΓTyn­μύ°ζ‘ ™ŸŸ-½Ρςj·LN–v»μΫWξέ«Φέ»εΐλΓ•?*7>­y$CζΚΤKεr§”RΪνςπa)υίXλλε͍λJ·ϋδ–]b[F§7>χv»LN–R9¬ζΨΨμ‘C#ΝζΰpΟμμο·nΥ]“!τΒάάό‘#ƒ“Š7ΜμίψδΙGkkƒΓGύώηKK½ϋχ«/Θšh΅OŸnl~Λ°Ϋό οΓ/έΪEΨIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_hover_handle.png000066400000000000000000000020371421045507400275700ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœΡIDATxœνΨΟkgΗρΩΝc)nR°"4Rvq‘ β)=˜“HΟ"Εόρ7xφβ`Δƒ^…ϊ?!^BV$ΤEΌχΡS²Œ»ΫΓ°Ι‘‡Ω|(›Ό^§™/ΜΓσζ™eηŠZž=+ζη‹·oχ Χ֊N§Ψά¬·$GJsΚηΛ²<”}pΔLV£Ρθv»‡²Ž’iΓͺͺͺ( mqΐ΄aEΡλυ m±_Ν_HUUάΎ]\ΎΌ7θ-..^Έ°πϊυξ!mŒΩΦ¨χΨβbqλΦΑα©Sg^Όh08LέnΧ7‘m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHωΆυVόbiιϋ;w «Ηλ-Ȍ‡uζΚ•ςΔ‰ΙyYoΉŸ><ΩjύωώύδπΫ••ΡhτκΙ“Ϊ»dζτz½οίξΪ΅ί^ΎœœΧ «Ρln>}ϊΛση“Γkkσϋ³ε8ψϋγΗίίΌωυΡ£^―·7lώβΘΨήή.ώύ,ŽΥ<±φ,,,¬Ž―————Š’ΌzuΚ5™-nχΛ³g·Ο+Λ²έn―――χϋύiΓκχϋγλΣΧ―υαΓή-ΗDσ₯―†Γρ{o·ΫΓᰘώčF»»»γλΑ`PUΥή-ΗDUUƒΑ`όή·ΆΆΚ²,j‡57?ίY]mΞΝMOw:Ό{7ύF™9_Ÿ?Ώrσζδ€ζXί\ΌψΓ½{Γϟ'‡ΓͺϊωξݝOŸκot²ΥΊρΰAc)ΐqσ…C Υb8·(IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_hover_roi.png000066400000000000000000000022231421045507400271230ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœEIDATxœνΨAhΦuΗρίσψΘ0j›Pb4†``υˆΝk’ΠΩ˜xΩ%ΨΕgδΕ«zRB…‘t ,„θSC"HΑ<³’zαΆi—‘OζΓƒŽΟκ™―ΧιΏοΏg_Ψ›g=₯ΐ_₯Ӎ‘Ν›wLN.ΜΟ·ζζΞμΫΧj6σ‹ΡΫ:†υΙΩ³wggoΞΜ΄λγγΧΝϋξθΡόbτΆZ§ σσ7gfLO·ίΝ―ΔJPνϊΛ°+Mχ°*•rΰΐ2lŠ=¬fS[<³ξa•R mρl:?ΌΟΝmτ΄~­”z½Όχϊ;g?-Χnτ°Ž7¬ά²kΧΓήo΅~ό|j*Ό/ FΓίD2΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"εΉΫͺtΊρΪ΅[vο~b8Χj}μΨσόzV£Q>,ίώΆ·ΦΧχΔ­K'OΆξή}꩎a}tϊτš?nάh―ΧgNœΈpόψΧ₯‡ϊjoίΫ{.|yΉ}Έndδޝ;_μΩσΤ#΅N―U©V8uκΚτtϋpη‘#«•-+ήΧηϋ>|υςΖΏφOM=ΦΗΖ6ξΨΡιHu9φ’χ]ΊT*•gxήκψŽυΘΠPΉxqρϊηΑςΚ|YισnGoΊωriΥΚ[Νί_&&Κ† εφν.GΊ‡5;[Άn]ΌήώYωσ—rεδR₯·Όϋq|£œo”RΚΔDΉΏϋ‘ξa-,”[·―οέ+Νζγ/yAŒ4Kmνβο}b’τχw?1¬U«WoΫV]΅ͺ}ψΪπποΧ―/uMzΠΊ‘‘ϊΨXϋdxt΄ZλόΟ_§C›6mŸœ|0?ί>|07wvώN]°R­ΨyψpεŸο2ΥZ훃½zυΏΪ –μoϋ«κΎƒ’±†IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_hover_segment.png000066400000000000000000000041131421045507400277740ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœύIDATxœνέ{LΥυΗρ7 )π²*/΅Χ@‰Ό4ΩΌΥl§ύδβoΒΡ‘ΆE—?ΊΈίΒJ5ΫπR"l‘ΆΨRl€RyΩ―Dϊ₯Ψ&JΤ$ίsΰχ‡ΏΩ±ίoσΧαλޞ7―Η_η|`οσάΞgggŸsˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆhdρ2eΚ„ΈΈΤ’"§ΓαΊθ4Œ›mΰ·ίLΉ ς,ζl¬œΪΪ«έݝ­­‹ρ™™νυυMee¦άyS¦8ŽΞΦΦο««]£Ÿ~Ϊ”αδ‰ΌΝχΐζΞ#OeςΖ²Xπα‡ζŽ$dςΖΊrηΞaηNs§’η1ycψθ#ΤΧγ«―LLžΔ€'™yϋΩΊ4τcαߞ*·0e>ysŽƌIΘΘψΣβƒa%$"Β”[ rρΠCDxΈtιγε…‹ρΔ€?ΰΩg₯#H₯―ΏΖK/IGJŸ}†Υ«₯#H₯M›°aƒt©τΪkΨΊU:‚TZ±_|!A*-\ˆΆ6ιR)>Ώό"A*=ό00z΄tιγ><ςˆt©τσΟHL”Ž •ZZπόσ€ηŸ#/O:‚T*/Η[oIGΠ°™ΦδaZΉ ψaDΊ'V­Bm­t©τβ‹ψζιRι™gΠΡ!A*MžŒ+Wΰλ+έAϊŒƒ›71a‚t©tξf̐Ž •ŽEjͺt©τε—°Z₯#H₯ͺ*ΌϊͺtέΝ}wς~W―Ό‚°0|πt©TXˆ;€#H₯Œ μί/A*=χNπ‹’θ^˜>έέ€RDœNŒ+έAϊx{£§Σ¦IwJ'Obή<ιRιΐ,_.A*νΪ›M:‚TΪΌο½'A*‘²R:‚TΚΞΖΎ}€’E8rD:‚TJHΐΩ³€RT_Η¨Q€O@~11€ιӘ5K:‚TjmΕ₯€R]rs₯#H₯-[πζ›ͺyή‡)L‘—__”–JwJωω¨©‘Ž •–-Γ‘C€μΩ8uJ:‚Tš2—/Γǜ_^'rΓΐψρ€RWββ€#H₯cΗ°`t©T_¬,ιRiΫ6¬_/αΙFθΙϋ]Y­Ηƍ€Ϊ΅ΨΎ]:‚TΚΜDc£t©”œŒγΗ₯#H₯ΨX\Έ A*EFΒα@X˜tιc± ·S§JwJνν˜;W:‚T:xιι€Ru5 €#H₯’ΌϋtΔύŠ/ιΈΟfΓΐ**€;H₯œμέ+A*-^ŒΓ‡₯#H₯™3ΡΩ)A*Mš„ώ~Iw>Έv ΡΡ€™3˜9S:‚T:|K–HGJ{φ 'G:‚Tͺ¨ΐoHGαΙϋ=”› ??””HwJΨ½[:‚TJKCS“t©4gΪΫ₯#H₯©Sqι,ι'4ƍ“ξ •ΞŸGl¬t©tό8RR€#H₯†dfJGJΫ·cέ:ιR©ΈΕΕ€ΊuΨΆM:‚TΚΚBCƒt©”’‚cΗ€#H₯Έ8œ?/A*Γ@h¨tιγγƒK—0eŠt©tκζΜ‘Ž •šš–&A*νލό|ιR©΄οΌ#ρΧρΓχ»‚ΨνΨ²EΊƒTΚΝŞ=€’%hm•Ž •fΝΒ™3€Rt4]C` tι„ώ~Lš$έA*uv"!A:‚T:r‹KGJ{χ";[:‚Tͺ¬Δλ―KGόžΌ{Όœ`σfιRΙfCu΅t©”žŽƒ₯#H₯Ήsqς€t©4mz{αΝ'ΟdΊ°08ˆŒ”ξ •.\ΐτ选HN–Ž •‘‘!A*ν؁΅k₯#H₯ρώϋ€ϊυ¨ͺ’Ž •¬VΤΧKGJ ΰθQιRiΖ tuIGJγΗγζM„„Hw>ΎΎΈ|“'KwJ˜=[:‚T:tΛ–IGJ55X΅J:‚T*+ΓΫo›9ο#ΘΟ‡a Ό\ΊƒTΚΛC]t©΄t)ZZ€#H₯ΔDœ>-A*ΕΔ ―€ΟθΡΈ~QQ€Ω³ˆ—Ž •ΪΪ°h‘t©΄oV¬Ž •ΆnEQΡ_ψžΌΣ%;AAΨ΄IΊƒTZ½»vIGJΛ—γΐιRiή<όψ£t©τψγθ遗—tι3v,ρΏκζ– IΘΚϊΣ’10Π\Qαή@ςPέέHIApβJ??ΧuχΖ₯—•χάω"xT|όΠΠPKe₯ϋ™δi"#ΡΨ΅ςΫ>λ?χ·Ή»Ή±ΌΌ½ΏέΉσϋ;Ώ§7­΄ΤχΞmK#Α?Šύr Ϋ:Ύ[σι§,ς€”LPW‡ΤTώ±βζ#ΦmώώώIII·.Oœ81π™?˜3Ι³Δ<φΨ¨ΘΘͺηηεύ«ΊΪΛjύΥn·wcΩνφζζζ[—Γ_x‘·³σφU!ΌccC›››››QXθ rΪνΓ~ΔΊqγΖ­ΛN§Σ0ŒΫWi„0 ΓιtήΊί7lψϏtΊΉ±,ΎΎ1IIή‹λbxLΜş~v'yž}4ώε—]Wά<ǚπδ“ΙEEƒ‡λβ aΤY3pυͺϋδ‚ƒΣJJΌξ|”!"""""""""""""""""""’‘ζίγ'π%-ξIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_init.png000066400000000000000000000022271421045507400260760ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœIIDATxœνΨΏk”Ηρ»σD-Ε$A₯\ΘA‹ˆ:Δ!™D n"Ε1:uη,.ώt±ΰͺ»t²BJq°bC=$S‹τ‰S O.ιΦ§W΅Ϋ‹―ΧτάχΉ'ωΒ½yξx*ψ«–ΨsθΠρ‰‰φςrη°]ŸŸ;·Έ°_ŒήVΦ§7o>}ςdnf¦s8<>>{ϋφ—““ωΕθmυ²νεεΉ™™{SSΓΖΘH~%6‚ZΧwΤλ₯ρA™ξaU«Υf³ωVa#ιVQ•JE[Ό”ξaU*•V«UΡ/£όΗ{Qοό΅ήίίΏkώξί#‹ΡΫJ7lλο?rϊτsÁνΫΏ»ukν¦f³ι;‘m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHyεΆͺe'ή8rζΜsΓbqqϊκΥWψ7τ΅°v=Zί²εΉS__ΏΎψτι ―ͺ—ύΉO&'·υυύςψqηπύααΥΥΥ―]{νmι­Vλγ >8qβϋ»w;η;‡†φ>όΩΩ³/Όͺ4¬j­φ͍χ¦¦:‡§._ήό—lΩπ~[XψιΑƒo―\i΅ZλΓα±±/»€φF£ηΝΟΟWώψZό'JοXλΆnέ:::ΊvΌwοށJ₯~μΨ+οG/l6ίέ½{~ίΎz½ήh4ξάΉ³΄΄τχ—tkiiizzzνxΗΙ“ΏΞΝ­Ώδ-Q;x𽕕΅Ο½Ρh¬¬¬t½€{X«««Οž=[;n·ΫEQ¬Ώδ-QE»έ^ϋάgggλυξΩ”ΎcΣζΝƒ££΅M›:‡;~τθυ₯ημλœ ŽŒΤΚ +}Ž΅ηΐ&&V–—;‡+Eqσόω²GlTΫϊϊN]ΊTύσ]¦V―qρβώW[ΐkϋ&J'„ΆQIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_setpoints.png000066400000000000000000000020521421045507400271570ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœάIDATxœνΨΟkgΗρΩΝ“RLRHEh€μβBAΔSr0'‘žEJzπΗ‘g/ώF<θUP¨Cβ%τ`Ε†ΊˆτP ύEτ”,γξφ°4l<τ0›e“Χλ4σ…yx`ή<³μTQΙwOMOφκΥθpm}}±Υϊek«Ϊš%υ1Ÿo4‡²Ž˜qΓͺΥjνvϋPΆΒQ2nXeYE‘->2nXEQt:B[TρR―,Wnάh^ΌΈ?™ŸŸ?uξά―/^Ƙl΅jΝΞΟ―\ΏώΡpαδɟž>`p˜ΪνΆo"Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘ςmΥͺ­ψΙΒΒΚΝ› ΛέέΝͺ-Θ„†uκ₯Ζ‰£σF΅εΎ½wovnξΟ7oF‡_./ƒηVή%§Σι|sηΞWWόόμΩθΌbX΅z}λΡ£ž<­―OΜ–γΰοwο~ωςΗϋχ;Ξώ°ώ?nˆ#cgg§ψχ³8TρΔΪ733³ΊΊ:Ό^ZZZ(ŠΖεΛcΙdi΅ΫŸž>½sζL£Ρh6›έnwά°Ίέξζζζπzρκտ޾ݿ嘨_ΈπYΏ?|οΝf³ίογŸXƒΑ`oooxέλυΚ²άΏε˜(Λ²Χλ ίϋφφv£Ρ(*‡55=έZ]­OM[­?^Ώ£LœΟϞ]ΎvmtRρ¬/Οϊφνώ‡£Γ~Y~λΦξϋχΥ7Θš›[»{·vπ”ΰΈωK‘$βήΛ‰šIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/open_setstate.png000066400000000000000000000022271421045507400267670ustar00rootroot00000000000000‰PNG  IHDRΘΘ":9Ι pHYs  šœIIDATxœνΨΏk”Ηρ»σD-Ε$A₯\ΘA‹ˆ:Δ!™D n"Ε1:uη,.ώt±ΰͺ»t²BJq°bC=$S‹τ‰S O.ιΦ§W΅Ϋ‹―ΧτάχΉ'ωΒ½yξx*ψ«–ΨsθΠρ‰‰φςrη°]ŸŸ;·Έ°_ŒήVΦ§7o>}ςdnf¦s8<>>{ϋφ—““ωΕθmυ²νεεΉ™™{SSΓΖΘH~%6‚ZΧwΤλ₯ρA™ξaU«Υf³ωVa#ιVQ•JE[Ό”ξaU*•V«UΡ/£όΗ{Qοό΅ήίίΏkώξί#‹ΡΫJ7lλο?rϊτsÁνΫΏ»ukν¦f³ι;‘m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHΡ)Ϊ"E[€h‹m‘’-R΄EŠΆHyεΆͺe'ή8rζΜsΓbqqϊκΥWψ7τ΅°v=Zί²εΉS__ΏΎψτι ―ͺ—ύΉO&'·υυύςψqηπύααΥΥΥ―]{νmι­Vλγ >8qβϋ»w;η;‡†φ>όΩΩ³/Όͺ4¬j­φ͍χ¦¦:‡§._ήό—lΩπ~[XψιΑƒo―\i΅ZλΓα±±/»€φF£ηΝΟΟWώψZό'JοXλΆnέ:::ΊvΌwοށJ₯~μΨ+οG/l6ίέ½{~ίΎz½ήh4ξάΉ³΄΄τχ—tkiiizzzνxΗΙ“ΏΞΝ­Ώδ-Q;x𽕕΅Ο½Ρh¬¬¬t½€{X«««Οž=[;n·ΫEQ¬Ώδ-QE»έ^ϋάgggλυξΩ”ΎcΣζΝƒ££΅M›:‡;~τθυ₯ημλœ ŽŒΤΚ +}Ž΅ηΐ&&V–—;‡+Eqσόω²GlTΫϊϊN]ΊTύσ]¦V―qρβώW[ΐkϋ&J'„ΆQIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/roi_getarrayregion.png000066400000000000000000000173651421045507400300160ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ§IDATxœνytUΥΩΖwfF™6§.tBW© "[μBŠ«v «JK‹Z…6₯aR––Ι bV Ά‘ΑA"(C˜F3Ι½ίχ3=>OΌ7ζܝsοΝσ[ώ‘½έχœχžΌΩΌοy‡mŒB!„B!„B!„B!„B!„¨_ΔΥϊ“~Ώ?ŒrˆΘ$.–ΐΝ]»vνκ>ϊθ£ΞαΌyσœΓf͚ΑΗΛΚΚ‚\ΝsψπaηπΒ… Ξa»vν`}£FœΓ£G:‡‰‰‰°ΎiΣ¦ΞαΉsη`A‹-œΓ‹/:‡ °nQZZ ΰ;wv/]Ίλ4ψΪ/θΜ™3AnΗΓ4Ζ\qΕΞαΩ³gCψνίΏίΤ–ψZRˆ H±„€XΒ l¬ζΝ›;‡`3ρΙ'ŸΐΗ7nμξΫ·tμΨΡ9lΩ²₯s•••A>ΞΜ—_~ό‚IIIΞ!˜8­Z΅‚υ'NœpΩΏ ‚HGށυνΫ·wΑͺkΨ°!¬‡‡Μfλωσηƒ ω‚΅Ζ•bcΪ¦¦&|%MΓδδΐ'Oϊ+*\^YD5ξΌΒ>}ϊύϊΧ•_ωJ­Z·6ΖΔ5jtqοήs6„A:΅ΈR¬ΔNNμήύΞτιαΘ‘#1MΣΣ›τμΡD4#γ]XΑ­`μΨ±ίύξwwξά ζ°¨·ΈR¬sηΞ΅,)),,|ξΉηŽ;Φ€I“?ώΨ΄nmš6mšœ|πΰAηbpˆ … ΐι3Ɯ:uΚ9lΫΆmpyNž<ι^Ύ|Ω9lέΊ5¬///wΩ‰ƒ0„ύ†όJ^ί¨GΞa·nέ`}II‰s/βωΝ>\ύ\πΔ;uκ|}­ ώeŒ9~όxjjκΉsηΠcυ·ŠΥ81±mχξƘ„† 7εε}ΏwοΚΒΒcαLD5ΆΎ[Ά΄KMύΙκΥ?Y½:©cG3`ΐ›χνΩ³]QQΈδQŠ««ψΰΑ¬Ύ}??°s§ωΟΜΉsΩƌ=zÁαOD+«C‡U?'$$œ={φτιΣƘ <φΨc©©©Ξdhc’ΏzS ΈΈ€9Ζ8 δΒρ 0ηYB°Φ!M"<†"0α1Ζ΄iΣΖ9'š“Ÿ οlmφ6 ±‡@^MHj­χXσηΟ_³f dˆϊƒΕ€·άrΛζΝ›9MΤμΎyOKKΫ³gΌ}υλ!kΉΖM†«ˆR¬+VeeευΧ_ΟΙX"Άq©ϊΩησ5oήάι—U₯ݝ?ΎOŸ>ΌυΦ[Ÿ₯K—.pύ€ωMλ™ο|η;Ξ!8e\+™€μBζ ό›Ξˆ)g(“’’β:tΦίpΓ ΞαργǝCv“Α[b ‘ BLΰ§»‘޲ŠŠŠ†ΎyσζΊΉπœΊK›9zτθόγ·ί~»Ξξ(<€Nσ±>ϋμ³_όβΉΉΉuySα uθ—ŸŸ?cƌΧ_½Žο+κWΖ»3ί(..ξ₯Kΐ€ώT’»vνΚΚΚZΎ|ωΈq㜠ΰϊ˜ T˜π‘ΧησΑz0–9c ξΖώ_|λ!Ά6Ζ5ηWAΤθϊλ―wω‰Ξ„‘:p/ΒψΖΡ›ΤδχίΙ’%ϋΫί<Ή»¨<ΛyχέwW―^=wξ\―Vρ²˜bύϊυόη?gΝšε‘ ΒWιΌρΖ»vνš9s¦·bˆ°Nγ½²²ωͺ’™˜ΐ‚ωσηOœ8ρ—Ώόε‹/Ύ ΐΈžne(»ή2s‰}“&MœCxΟnΘ.,, .[ί@Θ7γΧƒ&Ƙ’―ηξBφ•©ξ;:a¨ΦDD]αΒ… ‹‹‹φ³Ÿy-ˆ‘XƘ?ωΟ>Ÿoόψρ^ "ΒC€(–1ζ―ύkbbβƒ>θ΅ " Dbc²²²RRR= DTYŠeŒ™5kVzzϊΰΑƒ½DΈΒ•WθŒxΔΕΕ5nάΨιgA9‡ΐC©ͺHyδ‘G-ZTXX H¨βl$¨Q―]π ٍ―ςχ‘ќ!Ηδaΐ+δ„0¨#‚ %ω D½@ΰ0ΏD܎`Β„ #FŒψΑ~ΰ΅ ’–Dn™ΓΈqγ²³³/^ΌΈuλV―eߚΘU,cΜΓ?όκ«―–——πΑ^Λ"ΎϊOa£Gž>}:€‹ˆΘ'l]“γββ|>Ÿ3η Ξ}ΰφT`³1Hz葇V­Zυ«_ύ P`ΘZη€M]¨ˆη|π਋+―ΌΦCεΨ1μ»_$δ<bV†ͺ38€W€τ,ξ Pk"}Η pο½χΝ;—Cc"b‰Ε2Ζ <ψ•W^ ΩΤODQ£XƘ~ύϊ­_Ώ^Ν ’‚hR,cΜ­·ήϊώϋο«DδeŠeŒΉωζ›wξάι΅"ώτ‘OΧ… œQpIΐI4’C!‘ͺxHZZΪ‡~˜šš λ!@2νΚlΈi1D₯ΰ‚|}(+β£lΐƒΘΝΜƒ&qΘΌNΞm„˜x…όjMτνXƘ’’’>}ϊμΩ³ΗkAΔ7•ŠeŒ)..8p`^^žΧ‚ˆκ‰VΕ2Ζ=zτxο½χΌDTC+–1¦  `€ItXδαΚxS±M›6Ξ#ˆ€akΘ4ζ˜–°>>ϊ裏žxβ‰œœœ‘C‡BDΞι„n[†Lc(Β1”€Ά0ηcA؊ƒT_™Σ‘ΰΏΕΗ­Γ3ηw~`ώCzVΘΎΤ5'Ίw¬|πΑμΩ³_{ν5―#Λ³uλΦΕ‹ϟ?ίkAΔ#ŠeŒΩΈqγš5k233½DKŠeŒyϋν·ί{ο½gžyΖkA„;γέω¦ΨχU3`вρ–¦σ•q Έμ±cΗ-ZT^^>eΚ”Ι“'CΎχΗ‚ -~Qο©Α{ηΐΠ»x.mw‚ |βΈGΰΐΥψ‚tδ;ψ7!ίΤך˜Ϊ±,]Ί΄  ΰ©§žςZzM *–1fαΒ…§OŸV3‰MΕ2ΖΜ™3Ηοχ«„WΔ¬bc.\˜””€fžˊeŒωΛ_ώ’’’rί}χy-H½#l^‘ίο/**rf .τ06”NΔιMPEEϊ\φ 3uκΤΩ³g0`ωςε°œ&Ύ 8’PEΓ!˜p‘ <pΚΨΛƒα6Ο“α ·b r}7ΔψŽ`κΤ©ύϊυ6l˜Χ‚Τ#κ…bc~ώσŸ6μξ»οφZϊB=ͺJ¨*ΨW WPΛ3zτ蜜œ²²2₯žΪΖ•b9 ͚5sIΐςεJHέηXλ]ΔΖ>؞± $3 >|ύϊυΣ¦MΫ³g㜰·€R.Ιsžq(\ƒ rYy  …;~ΑWfwkNgΛTχ;ͺ5υΕΖr2pΰΐ¬¬,.ςa€>*–1ζϋίώ₯KΥ ΒυT±Œ1½{χ~ν΅Χ8»W„…ϊ«XƘ>}ϊlΨ°_3 χΈ2ήzτhγ8…%Ό˜`7όP:A žy!HPγ"‰EΓGΰ ΟΦϊVhΗBφνΫχ‡?όaι₯^ έH±ͺaϞ= ,X΄h‘Χ‚D1R¬κΙΛΛ{ι₯—^~ωe―‰V€XίΘζΝ›W\ωΒ /x-HTβΚxwF$βγγ[΄hδt6$ΑLNNFαΎ‘Ϋ™/1ˆΐpw]ΈŸ‘RPPΠ AƒΉsηN˜0Α­Ν'0Βω\Τ)hΔν}ƒwΧ iΌs~\Ό0φ9׎‚—_~yΫΆmYYY^ eH±Bσβ‹/~ϊι§Ο=χœΧ‚DR¬±`Α‚S§NMž<ΩkA’)VMΙΜΜτω|j4RC€Xί‚9sζ΄hΡBFjBˆL± ψύ~籋ϊΧΏΦ¬YγLL[±b…s=D ΅ωκή½; χυD6p‚ΈF=‰‘c±‘ΤBNs/)°ΰ駟ώτΣO—-[Ζyα Μΐδ Bζ Δ|8¦tΥUW9‡μΓ)/PΧi€ηϟ™KψMhΗϊΦLŸ>ύ†n5j”Χ‚D4R¬Ϊ0eΚ”ώύϋ:ΤkA")V-yμ±Η222ξΊλ.―‰P€X΅gόψργƍλίΏΏΧ‚D"^α;8~ΏΏaΓ†Ξˆ48ΧLcΞ—kβE0(A YΐΞ†*|B@νΪ΅›8qβ²eΛͺMdΫJ• ΖŸs£!Α bJ|}ȁγoYqΰm„±‚W;–[Ǝ;mΪ΄ττt―‰,€Xa`Τ¨QΟ>ϋl=Ό$‚b…‡‘C‡>όσ]ΊtρZHAŠ6ξΎϋ5ƒΰΚxwΎGŽOJJrΤπ™_|ƒ΅Ξ‡ερaζΞ!wΛ…[€νΟηy€7ο†ςŸΐ–goΉε–όόόΫn»- jΘς xΣΝ%ωΑη]ΰΠπ13πLΰέ=7¨5Ϊ±ΒLZZΪξέ»ωW^ߐb…Ÿ«―ΎΊ  ΐk)_ZZ—IΦ+€XV())ιΫ·ο–-[ΌΔ3€XΆ(..5jΤƍ½Δ\y…ΞvŸΟwόψqgˆ€}" δ™(ΰWB•»™Pb2ΝPΥ Έ₯\&_αθΡ£<ςHnnξψΓΐ υ !Χ8zυΥW;‡ΐαλshxpG> ΄ΦhΗ²ΛΑƒ'OžόΖox-H]#Ε²Ξή½{gΜYߊͺ₯XuΑ]»²²²ώώχΏ{-Hέ!Εͺ#ΆmΫΆdΙ’9sζx-HαΚxwšŠqqq‰‰‰Ξ(Δ χΘPρŒƒi λΉϋΔd ]‰Χƒυ ¦±1fΫΆmΞα΅Χ^λrhπ6ΐΨ_»v­Οη›1cF `ίΠWf !ΘsβΔ η«?ΐΫΰ4x€p aΪ±κ”΅kΧnάΈ±>μK±κšμμμν۷Ϛ5ΛkAμ"Ες€+VόξwΏσZ‹H±ΌaρβΕΕΕΕO>ω€Χ‚ΨΒ•ρξΜpςϋύρρρΞόxΙ˝[ΑΓӐi –,ηWΑ»x0uy=€+q­6€|ώωηAδ1dMse³³ςxΑ‚S¦L™6mZ•ΙΕ±hpޟ‡ό‰Κ!ƒ3]Έ΄ΊΦhΗς’?ύιOIIIUNb,!Ες˜§Ÿ~ΊkΧcǎυZ0#Εςž'Ÿ|ςΖoŒ±fR¬ˆ`ςδΙίϋήχFŒα΅ aCŠ)Lš4iĈτZπΰΚ+tζ3ΕΗΗ—””8½ˆ@+&CE8ΠΞΚPF8A|Ap3C(λ9«S§NΞ!”ΐsΐ$dΣπϋΐ‹|ΰV­ZUVVΆiΣ¦ΐ Δ|ΐ‰γt+π¬9e €GΚAͺZΆφΛ",ά{ο½λΦ­+++ϋχΏν΅,Π?…Η Aƒžyζ™›oΎΩkA\!ŊD πόσΟ_wέu^ R{€XJοή½_}υΥ0&‘Χ1α΄±*++Ζ&dGA8©ΛŐ/½hω4Tψ5@€Ž1ΖμΫ·Ο9δΆ R–ššκrϊTΘ¦p hθΆyFFΖ† nΏύφͺΨ<1v_ΐ9Έxρ",ΙٞΨTχjv¬ˆζškΩ±cGΟ§¬3€X‘NηΝ8²–.bEνΫ·η“Ζ")Vΰσω’““Ή<5’‘bE₯₯₯ιιι}τ‘Χ‚ΤW^‘3€PYY U:ΰ±Ηξ‰rrΖγ 6ο‘C‡`=ψhp꽑NΘ2!˜χΰΔ€ghrœ‘‘±}ϋφ~ύϊ†|€ μjœΈ}‘αϊινXΡDaaαΈq㒒ш+Κ8pΰΐγ?ž››λ΅ !bEωωωΏύνo³³³½$R¬¨dηΝ™™™‹/φZoΔ•ρξLίρω|͚5sVΒ@Ί'!ΙωXD0Έ›Ώΰ=@7,CΩH`Ι*’ 3.ϋc™‹d‚·aζ |§³cǎyσζeff>τΠCU“ΰ―π#‡ι³Ο>sΉγW­ΡŽΕΌσΞ;+WŒΜ}KŠέ¬]»vΓ† σζΝσZDŠυdggηεε͞=ΫkAΎ†+XΎ|ωώύϋgΞœι΅ ΓΥaγΞσ6mΪ΄eΛηΛλgŸ}ΦΉ²© U €₯́7γ!›Υ‚)Ν/ΚΑ½ΰ^`­ƒqΝέ€ΐψε”x΅ )hά!,xuΨζS§NMIIΙΜΜ¬šafΰΑύβ‹/tΨΈ0³gΟ¨¨˜4i’Χ‚#Ŋ1²²²Ǐο΅ R¬˜cΦ¬Y;w3fŒ·bH±bίώχιιι#GŽτP)VlςΔOτνΫwπΰΑ^ ΰ*€3qβΔͺŸ[Άl Ϋ/Δp8έ π™μrψΗ`=½@IŒ‘  ·€xϋΉΕλr/ Ξ”7δωBαSp—mχξݏ?ώψ]wέεμ _Ϊπ― ΦhNJeώρτμΩΣ“βD)VŒ“““Σ«W―δδδ:Ύ―+φY΅jUΏ~ύΪ·o_—7•bΥ V\yηw²™kW!g1B^^ήϊυλφ/€sp’>˜Š]ΊtΑ3΄ψΠy0–αo”υœ1Φ½{wηL]Žΐ€mΞw„°•3&fŒΩΏ?¬¬u.±‡ Έ;[·n>|Έ³ \γ?uHΗmο†ή>Ϊ$)ΙΣ΄U«&½z5-+3Ζ”οΨα_.nΏύφνΫ·?2cFΣ« ΜT)ξ‘ΌΌ£Ϋ·‡ρ^ώ)Όe̘žΓ‡›Έ8ΠλΈ8—Π]β!α‘N„›^ύϊe/\Ψ΄qcΏ1ΞF,]ήΉέ±N~ςΙ»³gcΚFŽ,ΟΛ+;s¦ΡuΧ5JO‡lΒ σϜY 8MpP Έl†ΊCή;Ϋpx€ƒί²LRΉ­2$Β3αTG(‚Ÿ₯ŠΛeB[΄xxύϊ…½{†%%%Uλ©S§ΈL¨Φ„mΗ*-- Έίρ•• ~EEHΙΕR°€c°ΰΫ^ς-CgΕ ~h+\0δ‚ΰCJ±XqA±ͺQ”K—*όώͺy~!.ά*VΛ]ΣΎ:«£ΙM7ctθ`θ/ID άόU)b·(ΐ•b¬[Χ²[·Φ_½I|ϊ|₯ΡΠ΅’~R~φ즧žκΠ³g`X΅#Ύώӟ†χFήΌ‡Q™Dšƒ%„B!„B!„B!„B!„B!„B!„B!„B!„"’P# ΪI±b7Ώb΅γVb +H±„€XΒ Ί&/Y²f~ψa71ƒv,a)–°‚KXAŠ%¬ ΕVpε6kΦ f222œΓœœ7ΧΡ‹v,a)–°‚KXAŠ%¬ ΕVpες)Λp σύχί V\ιζŽ"ZЎ%¬ ΕVb +H±„€XΒ ΌBŸΟ3/^ ώ‘{ξΉfήzλ-72ˆΘD;–°‚KXAŠ%¬ ΕVb +Ές !2hŒiΨ°‘sxζΜXΐŽδ!C`&77׍T"Ў%¬ ΕVb +H±„€XΒ aŽ6nάΨ9LJJ‚ά}°²²fΈe ·΅Žv,a)–°‚KXAŠ%¬ ΕVpε–””ΐ ψ‰ΰ$Vϋ‘6mΪΐΜεΛ—afΨ°a0σζ›oΦ\NQχhΗVb +H±„€XΒ R,aW^a«V­`ζτιΣΞa‹-π~ πŽEEE0“3˜jŒ:t(Μ¬Y³&¨°’Nю%¬ ΕVb +H±„€XΒ aΞ mΩ²₯sΘ^!Έ¦ΊXαΉsη`¦uλΦ0sβΔ ˜4hΜ¬[·%u…v,a)–°‚KXAŠ%¬ ΕVp岋W^^ξ6ipv(w€aπμΩ³0Γώ&“1rδH˜ΙΞΞ6’NЎ%¬ ΕVb +H±„€XΒ ΌΒ¦M›Β 4“)++ƒ;v„p$Mu~"߈―Μ QΩOΜΘΘ€™œœ#, KXAŠ%¬ ΕVb +H±„\y…Ν›7>Γί‘C‡`&€kiŒ‰‹‹ Ή†JδΖψxόCϊя~3―ΌςŠю%¬ ΕVb +H±„€XΒ aΞ -..vΉMbb"Μ°ŸΨ‘C˜a}IξQσε—_Β w­aΟQηb„νXΒ R,a)–°‚KXΑ•ρΞ–8ΐF7gΈβ ˜a3œΎς… `†Γ>| 'r©Ωwά3οΌσŽAю%¬ ΕVb +H±„€XΒ ΌBΞγƒΎ'Ož„κΤ©Μ°7ΗΑ>ž.99f ΎdͺσΉmIM=>|8Μ¬^½ΪΪ±„€XΒ R,a)–°‚KXΑ•WΘξ)ΐυa\€ΕΙw•••Α/kͺσO:3μίq’c—<Γ2³wωΰƒΒ̊+L=F;–°‚KXAŠ%¬ ΕVb +„Ή)ΐΟTTTΐ ϋe₯₯₯0Γ1>nΙή%_ΐώ&©š΄α+Χσx’v,a)–°‚KXAŠ%¬ ΕVpεrxŽέ%€=,υγ8`³fΝ`†}@ŽθΥ$ΒΘWζ£ΞαuS—Κ^!―1cΖΐΜK/½dbνXΒ R,a)–°‚KXAŠ%¬ζX!ΈK܎†½Βšδ‹ς§Έ€‘…αx"Š―œ’’3ωωω0Σ­[7˜αcξφ%cψ˜;νXΒ R,a)–°‚KXAŠ%¬ΰΚ+δTOpθΈ/(ϋ€άƒ΄¨¨f’’’`¦&±9ξlΓΩͺ|eξΓώζ±cΗB^‡}@&†ϋΨhΗVb +H±„€XΒ R,aW^!ΘΐΝα*Bv$ΉS(гC9ƒ”#Œμ©q/SώgΆmΫf a†ϋΨπ ϋ€œΡΚΡΜ{ξΉfήzλ-ρhΗVb +H±„€XΒ R,aW^aΘV-μ6²―Δyž59ZόσΟ?‡™φνΫΓ { |ŒωαΓ‡a†ΟFδS0Ίtι3μΝΥ€―ί‹K5α°c̝wή 37n4†v,a)–°‚KXAŠ%¬ ΕVpεr«p|ΨWβŸQ“ψηrΖ&g~ž8qfΨ/γx"Η ωd φd9Θ3Ǐ7‘ΰοΕέoξ»ο>˜yύυΧC^Ω*Ϊ±„€XΒ R,a)–°‚KXΑ•WΘa>ϝ?΄kΧf8ƒ”½'φ%9ΘqIad7–sSΩη ™(kͺ«sd Ωe―™εα+sΜ‘―?χί?Μ¬\Ή2€„΅F;–°‚KXAŠ%¬ ΕVb + OTsΨ/3Ζ,Y²Δ9δGΉw({sc?ˆ}₯Φ­[Γ Wν±„μOqv(Η.Ω“­I'Ž]Φ${–―Μ~+{» ܝΣPύ~MS-Ϊ±„€XΒ R,a)–°‚KXΑU¬pΩ²e0³vνZηπΜ™3°€ύ)ξΓ9₯ Gτ8Θ^aMΞ=dΏ΅S§N0Γ΅‡ΡcOοΞ2'''ΓΜ‘#G`&dβ©YΞ-ψΪƒ 2αC;–°‚KXAŠ%¬ ΕVsHGΔ΅ι!„B!„B!„B!„B!„BˆϊΖL€ ƒσΜΒIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/roi_getarrayregion_anisotropic.png000066400000000000000000000166621421045507400324270ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœdIDATxœν}pUΥΉΖχIrBHΘδCΎB `ƒE€ΰEiΑZˆ·V“‘–6z-)΅SμP₯L±-ƒmmι0:u¦S?(ƒΕ•Βm£ΥΡhΥ±΅*-䖏* _‚„€B€ΞΎ,ϋήέηεf'›³²Ο>ηωύ΅φ9λμ½φ9οYΟzίυ΅‡B!„B!„B!„B!„B!„B!„rqΔ|Ζuέ€·ƒ€2±XŸν$'Ψ•¦L™’“—W={Ά9ΌςΚ+?~ΓuΟΆ΄8Žσ«_ύΚ[Ĉp†cǎy:ƍΧs…κκjοαξέ»‘Baa‘χ°΄΄*μίΏί{˜••θ=0`€χπόωσPΔ‰ήÜόzϜ9γ=>|8T8uκ”χπάΉsP‘£££ηKΐί~πΰΑP!‘H”^zι°+0‡¦plϞύ7:ŽsφμYoύ“'O:}' a9ŽσwέU6~|ϋ‘CŽγΔ«ͺΜ‹Yee±άάΞΝ›Ÿ–τ_zόρƒ›6%Ί»Ηικκ2/ΞΌηžΗoΎω€ϊ# a8p`Šλn\»vσΪ΅Žγά}χέζυŸύlV^^,ƒίώw8tcǎ… Ί‹ Ο‹ΗγP‘¨¨Θ{¨yπ‘γǏC…!C†x?ϊθ#οawwwΟW„ήΕQέI^^T8pΰ€χ°  *δηη{‘uTΏ«ϋΌ3gΞδ•”<΅pαΉΣ§Η©¨¨0―}ι₯ςŠŠ]]„Oƒ?!I!Έ •••K–,1Γ‹mωωΉ;ώŠ+ΌuτŸώXz¬ ?dgg{‰DΟ΄ΟΡc¦άάά*θϊ6 ^σ;ͺƒΡΓ> @ο’H$^8π•W^ΙώWc–,Y²sηΞΎž§gΦ’%KLštyyω¬#ςσσsrrŒj ŒΕr]wΘΐ6lπΦ/..†3ΐ8Wwιπ#i;ƒ1¦ώ ΚΚΚΌ‡Gށ ΎZ —€F‚š;ΚΤ΄4Θ{¨MΤVΫ.œΑΧ°΄˜0 1fΜΆmΫbέέ3gΞ|λ­·¦M›6mΪ΄όAƒζΝ›8qBίW’Πc]0ϊΠΦΦζ=Τ]Πθ›Γ’1¦ ξΜλ;ͺ„&9κwύπΓ{Ύ ‰τo_ΕιΣ§‘tHΪ°`ΰ¨Ώ7°u_Γc¬ΌΌ<ΧuOœ8qύuΧύνoΫ΅kWΟgF@ΓzτΡGsτθs§OοέΉ³   φάΉζζfΗqbγΗ;GΎΉeΛΚ•+½υυo=ΈΩϊ#ΠΓ9κK‡ΰ‚£ϊ0pυυ«”””@έÏͺέ0D­tΠkΒHάQ_‹!ΐ9> Fε=ΤΆ{ώόωE·έV6qβ#Ο=χΞ;οΘ—p}gη“kמ8x>ς­o}Λι;Α{¬ΝkΦ\{ο½%£FΕγρ]99Y':ŽγΖbn²Υš$ΑοΎ»??Ώ»¦frMΦφ¦¦Sj΄˜ΰ†utǎnlt§€€dΩo¬ώγ“Υ&b•εΛ—Οο~χδ“OšC_  kίΎ}R6*ύ'Ω»οΎ g9r€χP&ΪΫΫ½‡zζ;„‚!‘ΦJ_·ΪΪwπΰΑž―¨£ή0τΦJκ¬G–ΰpθKΐ]€ψX±bχξέλΦ­“ Α=:JΠƒ‘„Α;‰ χέw_[[ۚ5kϊαZ f K–,I$<π@\.`εUΊόό|ΧuΑ΅†ζF₯}Γ† ƒ ΰ'κ8™V:P =cΒ€+@« .₯>tiWW;΄Ω7‚*2Β|ΰ=4_˝wήY^^ώ£ύ¨°°Ύ7=™¦~ ¦?_ϋΪΧͺͺͺΎχ½ουηEiXiNCCΓ€I“ξΉηž~Ύn@ΓςJΫΰΑƒ]Χ…ωp t$"€†hδ%—\Άoίξ=„ΐ £δXW€ 3L9ŽsτθQο!ΊΎ)P.νӁΆΎώϋP}Ϟ==WΠ³F^9««»ξΊλ.\θuWΑ™ΥΎ0½B΅΅΅uuuwάqG(WOšaAδ;Η έcAŸ§+TVVφά$πΌ±7Μα@δ¨ζŸό§χPǐ|η|`κλFκθt“Ϊα0½ζg>σ™ΖΖΖ―~υ«:εkτθΡήC=ǚ*“Π$՘:uκΒ… ηΝbhXιΖΔ‰—-[vΣM7…ΫŒ€†εˆΗγ±X bB0nΥsμ—Ί©ΙΎ@)(:†Ρώx$0pΦ‘3Jλ+@·R0τœ™@Θjjjxΰ©S§Κ+Z―ᧁΞΉPΠ1챇ͺͺͺυλΧ_~ωεa7ΔqhXiCEEEsssMMMΨ ω˜€†ε&γϋ€ Ι:‡5AΆ₯:½0Ϊ»iΣ‰~ΰ΅ιU~oΏύΆχ~6ν¨BΆ2Α·€΅bH:΄·inͺ¨¨θwή©ΞΞΞ†I-¦0,Ρ«‘τφX‘'οΪ΅KOΆ† +ςα=L;ͺZΤn#Έ:U*h1}ν΅Χ&L˜ΰm ΄žV9Φί³ώH˜aš››§OŸ‡z©@ΐΛ;J-,,ΜΚΚ‚Tcθ-t6•žΑ ƒΡ΄0Υsπ_ΤΙέpN=R†ρ>œA_κλΥ”Πθ^ξZχ²­Ά΄΄Μ˜1γΜ™3ΠqB˜Jω~±IΙΗbI6nά8gΞ­€© +zΌώϊλ·ί~»^Ÿ’R”BoΗΝΚZoθoυΰΔQ«TΠaθΓυ:Pί%1z°30p:yΑwJ’q:b Xϊ―Ύϊκ²eΛφξέkΎ}S Χz‘/(Ύώήτ,PΨcE‰gžyζΑά…νΗNJ 6lxδ‘Gήzλ­°+–םΙΞΞv]·ηύwτF.P_'—Ni .‘w£„¬:νΔAΠH'ι‚ΓτMΑΔ”N€WτθβE>ϊθ£MMMoΎω&lG£•vΠΤ_,δκΜ€ψ”ΒπΠCύιOϊc€61Ψcyy±X ώύ ΡΡˆc]p·ο‘έΓ_MO¦ϊξΛ™Αz"BeC‡υΆΆΆB}¨ΰ»ρš^xgθμμΌώϋ·nέϊμ³Οšϋ'F‘ŸΦ Δ½tN„$ƒΑ+₯ωΑ~°gϞuλօݐ>CΓJ]Ύύνo?~όΧΏώuΨ B@)τ޳²²\Χ…A% e^Ω»w/T€e€z3λθ lυ„.Θʘ1c μa Ί£§€|w―„κ₯Žβ,\Έ°££cι₯P­τœ4R;PA :$ƒ=V*ςΝo~³¨¨θΑ »!Α‘a₯σζΝ5jΤO~ς“°rQ”B―ΧΥΥ•••ξ jίVZκ20c£½Bj €Ϋ¨… r τŠVh•οC„ΰ„ZA|΅Ϋ8ώό)S¦,[ΆΜΜφψϊ•ΎYh:‹*ΐ/ετβΡ ½‘χ’ξškωξwΏvC’ +U¨­­­――_ΌxqΨ I Λλ@λμ˜ξΠ2Dα΄V‚·’E"=5&ZΘ@΅r« υuκ8\zc˜wΉŸ1cFccγ­·ή Ω ϊΎ›”φά$G9³Ύaα`°Η Ÿ«Ίκξ»οž3gNΨ I& Λ»αΩ³gυV‘πηΦΡ)λ lτμτ‚zηψ»λ_θτTœ§t`Μ·;ΡA¦ &όψΗ?ώβΏhΎ"θ±τ%ΐeΡse0λ¬GβΎ’ώ±ΐ+Lƍ·bŊoΌ1μ†$VhŒ9ς±Η›5kVΨ ±Br²²²² δ:₯§t`¬­3a4=ϋήΥ£{ΨaL‹©οžˆ=§ωjod¨‡G,•——oΨ°aƌ žπ=cŠ―ΧΨ€ι΅¬ ¦ϊ.τ "ŒΌ‡@aaαk―½φNG(…ύMNNΞΦ­[!Ο3ύHŽ:J5|ŸWΎ‰^$ “Ί8Pϊ!υΠηλό pϊ@[%+0’]6ί ξΠς©O}ʜY?‚L:ΤVgΓ΄‹!FΣΑ³P ϋ•M›6]}υΥI‰@¦84¬ώγ/ωKmm­οάyzP ½ήJ,K$ }ΰ$j)„N^oοk½$<&]~B-+P‘ͺͺͺη6ΐυn3PΑ«)/ΌπΒ­·ή αJή„»Π+Z‘‚ήBΎyνωBŸΞΠƒŠ°ΗκšššξΌσN=’JcφXήΰ‡λΊ±X ς₯ΰΰϋ|r:j‚EηΛŸUΟsCτl+tQz0+⑇ƒ‘Ί£"@Ζ’^ύυyσζ΅΄΄8½Ψ›’JzRHόίn–εθ.Mη₯€=–]^|ρΕΕ‹«Κ(hXωΓώ°|ως7†έ(…^i3€8ȊήσΖΕ: &+τZr˜&@υ΄ „΅(€^ΩT‚W ϋΫί>όπΓ―ΌςŠ·θ”Ξ–H-ΰ‚he„'Šιυ¨Έ>N{,+¬^½zύϊυ/ΌπBΨ Vςωε/ΩάάόΜ3τݐ0IΒΙ]]]λ‚Ž@«γ1P_?K&Xt΄tJODΐŽοσ˜΅0Α+ΰj•q]wŊ[Άlωύο―έ1GωΆϊŠΰWκμP|œ­36πMκy'½68μ±’ΙψΓχή{oνΪ΅a7$|hXIcΡ’EǏ_΅jUΨ I ’³`ΥQΡHίέύ‘Η†‡—:½ΨAχΚΛΛ‘΄A'ϊ¬h δζ:ΌΔE‹utt¬Z΅ΚΥβ *γϋΤ¬‡‡{釀/¬ΓΒπ[θ˜-χnH,XP\\ό³Ÿύ,솀Iθ±Ξ;‹Ε`ƒPίέύǍ`Qžžά€?–ώηA —ž±tgΨ ΖQΙL"2ΓσΖΖΖκκκοϋΉΉΉΠκΥλ0όΧSοp›:±xχξέήC½lΖϋ°ΨΙQα7έ)&εΙ<Μ ½(Ύς•―Lš4iΡ’Ea7$ε a§ΎΎ~ζΜ™ ,»!©Hž₯STTδΊ.t°0BΤiCΠίj‚‘΄οN/;vμ€W`h¬χ«Ρs2ά…Wί?ωΟΧΥΥΝ›7Ο[tG« t24ΩΆmΫ ¨§ο‚τχCντhy {¬ \{ν΅_ϊΧΏόε/‡έΤ…†ΥgΎϊκΕ‹ίxγ:,N„€†εuΈΊΊΊb±Δx|g? ³@§Γ‚zκ  SΎS:Ύ›ΙhΥgΆ΅΅uςδΙχί5Χ\γ\h -쐣§SΐΥ;v,T€n=Σ₯΅οXΧ‡Ι4ύΣψ^’7°Ηκ—]vΩκΥ«§L™vC"€½eδΘ‘O?ύ4­ͺ—μ±Ό>]III"‘€υ'°΅΅vΩ@&tΞ;Dυ€zl „i|ί‡_τ …eeeλΧ―ΏτK½Ρ'!Σ!YpΑtΤžc£o \6=ύ›k½†Ψ΅^Η›”U:”B ΤΤΤ4}ϊτ°%’šœ››‹Ε`Μ<ν fύΗ‚Θ–Ž9ΑΏ_η?ω.6‡Wtrs{{{NNΞζΝ›Νγμa!NƒΫΤkSα:΄ωUzκ’ΌuŸwΩe—yu€ΠχA˜IYbΟΛ‡;vŒ?>μVD ‘ί 8I3Β‰Ψ•––&%™5ΥhmmΥΣ€HKΓΪ·oŸφ‘~%ύ kηΝ555a·"γI3ΓΪ²eΛδΙ“ΓnI/Γzγ7Μ< Ÿ΄1¬—_~ωsŸϋ\Ψ­ "= λΩgŸMˍό#LΦ† ζΝv+ΘΏuΓzβ‰' Ø€‘6¬Η{μŽ;ξ»iHFηcύόη?ί΅kΧΚ•+ΓnH’Ή†υӟώ΄­­ν‘‡ »!ιI†Φ₯K‰ΔςεΛΓnHΪ’‰i3ίωΞwJJJζιΛ©IΖΦ‚ ͺ««οΊλ°BόˆW8ώόΗ<μVήΓjhhX·n]Ψ­ ½&†UWWΧΤΤv+H_H}Ú={vsssΨ­ }$Ε λӟώτŸόη°[AϊN*Φ•W^ΉiΣ¦°[A‘²†υΙO~ςψGΨ­ AIMΓ3f̞={ΒnΉRΠ°†šQOLORΝ°ŠŠŠτbyΟ€Ϋ”N<okk»ΰslH’n†ΥΡΡ‘χΡ#ύOZ₯Ν΄··—””θM`H“>†ΥΪΪZ]]­˜C’J* ήΉΥBΊaq«…τ$\ΓβV iKˆ†Ε­™° ‹[-€9‘·ZHϊί°ΈΥBFΠΟ†Ε­2…ώ4,n΅’yηV "2†Ε­’E4 ‹[-DŽ€Νp«…(’κ†Ε­2{^!·ZΘh,·ZΘtl·Z Ι7,n΅@'Ω†Ε­ΘΗ$Ρ°ΈΥω?’eXάjόI1,n΅@‹7,n΅@.ΐE·ZHKBžαV ιJΘ†Ε­•0ΣfΈΥBšaq«βC€Α;·Z ώτΥ°ΈΥι}2,n΅@zKο ‹[->ΠKΓβV €oτΖ°ΈΥι3Ύ†Ε­Hz6,n΅@ƒaq«…ŒΕbδ[-d2Ά ‹[-d8V ‹[-ΰi39ΉΉc¦Ms§¨¨θh<~ιΜ™ŽγΈ‰DύUWq« όΙξ»oΚ—ΎtϊΨ±x<>uκΤΏώυ―ŽγTΦΤ hiΉ«Ά6y-$Ζ­Z5ύίp<^αόωσWnέzύ½χ†έ4>Ic544ΤΦΦ>όσΙ:!‰4Ι1¬άάά[nΉε–[nIΚΩHΓΚΝΝ]Ίti}}ύşŠ€ I0¬Γ‡βΏΈψσt"xΈ!–•U<|ψ° Ό/”–vr‘ Ή˜pΓψY³~ψ|WΧΏ.+kεΝ7Ω΅λ’F!„B!„B!„B!„B!„B!„B!„B!„B!„T'ψ6FaαΊnΨMX,zΏTΔΘ@Êβ-‡φ{’ήΠ°ˆhXΔ 4,b± ‹X†E¬@Γ"V a+Π°ˆhXΔ 4,b± ‹X†E¬@Γ"V a+Π°ˆhXΔ 4,b± ‹X†E¬@Γ"V a+Π°ˆhXΔ 4,b± ‹X†E¬@Γ"V a+Π°ˆhXΔ 4,b± ‹X†E¬@Γ"V a+Π°ˆhXΔ 4,b± ‹X†E¬@Γ"V a+Π°ˆhXΔ 4,b± ‹X†E¬@Γ"V a+Π°ˆhXΔ 4,b± ‹X†E¬@Γ"V a+Π°ˆhXΔ 4,b± ‹X†E¬@Γ"V a+Π°ˆhXΔ 4,b± ‹X†E¬v‚πΔO˜ΒSO=e yyy¦ΠΩΩi C‡5…#GŽ˜Β™3gLaπΰΑ¦“σρν=zΤςσσM!77ΧNŸ>m Ο?|rο"½aE¬ »}ΖuέU«V™ςΫoΏm ϋφν3…xώ;}ψᇦΠέέm ………¦ R(ˆJ’ήΐ‹X†E¬I) «Κ+ζZ’•€gΨc+Π°ˆ"Ω±Ÿ;wΞ$Abž"ŽοΏΎ)ˆ'*&κ&―ˆw)—œ‘]SYΞφ…/|Αž{ξΉ€άTšΑ‹X†E¬I)Α3z’’•••¦ ’¦Γ‘¦ 9€RGDφόωσ¦`œΚΆΆ68 Ή 챈hXΔ ‘”BΡ)™υ“QΙΦΦVS·N²ΧEέD7%[FκΘ%€Ž9³δηˆ Σ=Ό 챈hXΔ ‘”BΡ»˜‚h™LŠή‰ŠnJN©xvrB>½DΜΜ9ŠW(ΛΛ"·Μ‘`E¬@Γ"Vˆ€Šο¦5Qή2dˆ)Hβ¨Ό%κ&oΙϊiαƒ>0‘<£€RS 2{ΨΨΨh ²P;“aE¬@Γ"Vˆ€JΎ¨d~κάqί²³³MA―Ύ—u’6#ι¦ς)9 Κd’HΕ!•·ˆΓ‹X‚†E¬I)-“Uσ"…"ŽβΚC‰yJ"Ν±cΗLA\?‰―J€τ’K.1γκ€SΡDIΒ™3gŽ)<ύτӁο1κ°Η"V a+DR Eο$J)Β$1OI€”)BΡMΡ;Y³/―θύgŒώΚτ’( dŸΚμ€4&“aE¬@Γ"Vˆ€J§8q⑉'*)š(Ÿ’Κ:MTr`DE%Ν EψΔ=M”΅\­――7…¦¦¦@7aΨc+Π°ˆ")…2Η'Ύύϋχ›‚–K‘9yETLtMδL 2{h"zΛν?Κγ€y{,b±B$₯P€2Ω'‰4ΐM”Ι>>QIqίdI­q1b„)˜…ŠςYΡJνŠη(☁°Η"V a+DR υN/"CβϊΙΎά’w"—RYoO*‹2$d*3Œf*PΤMβ2σ('‘ ­Dbo»ν6SX»vm_n4Β°Η"V a+DR %*i’²·Œ8ŒΓ† 3Q(qύdαπαΓMAΌKQ@I}eriYΉ/W‡Q :~›9°Η"V a+DR %&©WJάRB’Ψ)•EeROτN\Hύœ_sf½TRœAq!eoπΩ³g›ΒK/½Τ§[Ž챈hXΔ ‘”BŽ"Β§ŸE(R%Sxβρι=·e-†YVΥ;Υς–DbeI£–Ϊ΄‡=± ‹X!’R(ޜ~z ΜŠ&> „4{x΄“,rf–σ‹žŠκ I«΄C*WΌύφΫMaυκΥ½ΉεΘΑ‹X†E¬I)OM„I―|Χ2$oΙ̝ήxMή’Κ’AΊ}ϋvΗqͺͺͺΜ‘DAi <νΧ{Zu“°Η"V a+DR %ζ)Z#Ή ’€’A*›ˆŠ i‡NR_τ"AIΘ1Ίyψπa¨ (ˆW(ξ§΄S?!ρϊλ―7…_|ΡοΎ£{,b±B$₯PEDG¦E%%)Tή’ΰ€V(Ρ5νΠI€΄΄΄ΤqœC‡™C™υ“‚4F"₯:qT”Zr_e7›4ƒ=± ‹X!’R¨sZDE˜τvΩ²Ζaοή½¦PQQa " ²ΐ¦ 90f Fee₯9™Σ‰:ς™ΣαΣJ*Ίœf›x³Η"V a+DR %§EΤG„I”E’6΅'±S‰RŠ€ΚC+DΞΔO4^‘,¦…P ς¬CA.$ρ•{76=`E¬@Γ"Vˆ€ŠΗ'‚"aΖςςrSe…•”O‰/©y/ΡT/SY/¦³‰ψŠ.ΛI€²h·~ͺ”Θρ¬Y³Lαε—_ξρkHiΨc+Π°ˆ")…"1":·Τ+%.*›Μˆ8Κ‘œPΔQNrST‰W(š«κŒ*‰‹Κy$αGTXοGφXΔ 4,b…XΨ θ3ήgC¬Y³ΖΔΙχP²CEζΔ#aυΡϋψT™ˆ¨Έ–ϊΙzO6›•ΚβŠζ ςqΉ…›nΊIWKqΨc+Dμΰ\θi6™@δz,B!„B!„B!„B!„B!„’iό/©ΠΌΝ9¦ IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/roi_getarrayregion_halfpx.png000066400000000000000000000173701421045507400313540ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœͺIDATxœνmtTΥΥΗO2δ„j) H”΄ •b]-±VPΤ‚`ECΠ*P° B‘5΄@ ! )Z›²¨ +ε‘’U0P ˆ Š‘XAAΚ[ΑπVJ3IΘΜσaϊ€—Ž3ΧΉsrηεϋΔ9œΉwΟesfο»_ŽR„B!„B!„B!„B!„B!$Ίˆ ψ“'ˆrΠ$&&@ icε=zτ0³²²ŒΓ%K–‡νΪ΅ƒ;NWSJύλ_2/_ΎlvξάΦΗΗΗ‡§N2Ϋ·oλΫΆmkΦΦΦΒ‚Ž;‡υυυΖ‘Γα€υp‹«W―ΒΈEχξݍƆXί¦Νu@.\πq;)0<@₯ΤWΏϊUγπβΕ‹Ζ!όUVVͺ@‰ ψ“„ψ€ŠE΄ΘOa\bβΣkΦΌ¦ΤΤ;Œσ 55uλΧI0ή’Xm““oMOL©ϋžzͺy2&6φΆlq^έ<Δ§Ÿ~ ΧIHH0> nΊι&γ099Ω8ŒΕνΆ©©ΙΗΗ₯sεΚίμΠ‘ƒq&N§N`ύΩ³gCιίΐA€ͺͺ*X΅―}Ν8«...ΦΓC–fλ₯K—| ε&Pγέγi―Τ•3gš'bE?‘ό?Vm¬‘#G0 (’HΒͺbυμΩ3//οž{ξ Š4$b°τΛΛΪ΅k'NœX/,ΝA±”Rcǎ---=€Τι \Ž„?(V£Λu±ͺjM—.―[Χ»K—ΊkΧ>ΌT©gάξ¦Ύ}kjjΌΛŽ?nό8DJ„ ΐιSJ;wΞ8ΌρΖ} φοϋ:9Γ―|ε+°ήεr‡‰ƒ0„ύJψ•r|£ΤΤTγ0%%ΦΧΥΥ‡π"^ΎΩ‡+H?<ρ›oΎΩχϊ€ D±œ΅΅/?ςΘ±S§ζ>ύt©SΟ_Έ°Ί€D)uΣ€I£326nάN,‰BΤΠΪΟ?ΏA©3֝?ωμΩ3ž9xP9%%%>ϊhRRRp₯$aGπC:όγŸxβ ΨrI΄‘%Vψ‡?όaάΈqΤ­hΖ’WΨ₯K—€€$§ΣΩ₯K₯Τωση›jαΒ…UUUF[24”R]»v5χζ<γX!―s^JΦ:€©@„G‰ Dx”R7άpƒq1%™όy/`kKo{δΘ«ρ+@ΐhΜnΈύφΫeψD Λιt¦§§οΫ·Oί-HΘ’7λΒ… C‡έq}v ‰΄'ϊ>}ϊG?ϊQEE…’52H?ž••΅}ϋφVΈ ,y…΅΅΅υυυ.—Λ[#AcΪ݁ζΝ›WQQ‘™™Ω< >Λ-·άΧoŽ΅Έ^§Oγœ2Y+™€+„ΜAˆ¨ΘΔ”ΐ‰S"Σ­[7γπδΙ“°Ύ_Ώ~ΖαCœjΙM†Z )!€@ˆ όt+΄^Ξϋž={^xα…’’’V»#±‘V-¦ψ裏–/_^\\ܚ7%ΆΠΪU:[·n---]Ίti+ί—΄26”•••½σΞ;/Ύψbλߚ΄–Œχψψx‡Γαp8Ό‰GO€X‘ѐܰaCΫΆmσσσsss›'9Χ‡Δddrάκzέn7¬cYfŒΑΑΨη? λ!ΆΆŒk™_Q£Ύ}ϋ‡ς‰Ξ„u>ΰ^€wbΫ VΧ¬YSYYiT,IΨY ½rεΚsηΞMŸ>έFˆ&l.±υΥW§Nj―$θΨί»‘¨¨())ι'?ω‰έ‚`bΥxmΣ¦Χx‡χԐΜ$i^0sζΜΒΒΒάάάΧ_έΈŒkίιVJdΑ[fYbŸ˜˜h‚όJΨŸώΉo€υ ψ}3€q τΠΔ@)U]]mBφ•jι; b±‚ύ;–—œœœ^½z1ΒnAHpΕRJ|η;ί2dˆέ‚ BŠ₯”ΚΝΝ}ΰξ½χ^»!V -ΕRJ͘1###γ»ξ²[b‰ ½i "?ωΟ—-[ζrΉd ,)V\\œΓልυ†>ΐΙ‚s^ΕX‘ςΠC•••UWWοίΏΏyͺd6Δ”ΐk“.,έXπ ‘Jšσ*αx‚„­d 2ΐΰ+Λt(ΈδoΙγΦα™ΛΣΘΑό‡τ,Ώ}©Νφ;–—΅kΧξάΉ3//ΟnAΘˆΕRJ­Z΅κθΡ£999v B”Š$ΕRJ­Z΅ͺΊΊ:;;ΫnAHd)–RͺΈΈΈ±±1++ΛnA’KΖϋ•+WΌ―Œ! LQiΌƒ₯ι=7Εd\ΑYΓγ΅…sssηϟŸ‘‘±iΣ&γίΚώX‘%_”Γ{jπδ!gπ.^–6€;ΑyβΌI&/xνΪ5Xc~ίΤL€νX^ςςςzτθρψγΫ-Hτ™Š₯”ΚΙΙΉύφΫy仉R"V±”Rωωω `3[ˆdΕRJ=ϋμ³>ψ ›A΄>XJ©ιΣ§9’Ν Z«^accc}}½Χ+/\$θa¬D:‘Lo‚*(—e?pΑζ“5Ÿzκ©5kΦTUU}τΡGΖΰ4Ι ‚# U42€Y$œ2ιεΑˆΐΘ6Ο“‘A*ίUtA<7ςw,/O<ρΔΌyσξΌσN»‰’E±”RC‡-**κέ»·έ‚DQ€XJ©{ο½wεΚ•ALΐ%_Dt)–R*==}ύϊυς-? .–Œχ›nΊ)))Ιεry# βΛWV ΐΞ! `­Cv‘4φΑφ„ˆMs2Sώύ?ύτΣ€ρ.ΆΰP !KςΑœ—Ζ8œ)”'Ž@ RdΗ/ψʁΐZ³γEώLΤνX^ϊτι³oί>ˆύ‘ ₯Š₯”JMMύΗ?ώa·Kτ*–ΫνNKK3Ά’$A$zK)εt: ΄sηN»‰@,οǏ―­­­©©9~όΈ­Wα%―΄Ν>lΚτ&xο ₯ςμq0]ΑΈ–η‹8ŽK—.eddΌώϋχέwtΓR"Μοiηπβ[ΪΞΠaΛχSŸ[^ζo€3Qΐ–—±„€‰κΛΛιӧǏ_VVf· K)₯Nœ8‘½mΫ6»‰¨XαΠ‘CΣ¦MΫ²e‹έ‚DT¬²sηΞ °ΡHPοή A§’’"11±΄΄tΤ¨QvΛήσ L8΄†²~œ¦C‡Α(›ŸE֏CϊoΙχ쐕ššͺ”:rδΘ–-[ώόη?Ϙ1Γx ‹ωOΝ?T †O@y!HP“E8 ’l /Ğηό)l-[ΆlέΊ΅  ΐnAΒ*VΛlΪ΄iϞ=ΏωΝoμ$\‘b}!λΦ­ϋμ³ΟΈoΛ―ΌςJuuunnέ‚„–Œχδδδ„„„ΔΔDohBžΨaD’`ivνΪ…»>"Ά³Ό Δd #»λΒδ*MMM………ΉΉΉΩΩΩ………`kΛoΌρFγPυC δ?ΙφΎΎ»λϊ5ήe~\Ό ξXώ)((θΠ‘Γ3Ο|ψ<`· ‘N0Kμ!•²δ z DΜηΆΫnƒΰUAˆFΦ¨ΐ '±τ"!΅|:%Ό$oLiΒ„ «V­Ί|ωrEE¬‡o$ƒN0‰~2™ƒ’1₯ž={‡†S^ΐ―ω­ΐ t Œ3fνΪ΅N§“iΝ_ $333//―_Ώ~v ’P±gψπα/½τR―^½μ$‘bYβώϋο_±b…μŠK,ΩXMMMn·ΫγρxA}Ρ‘$^d˜Ζ2_ LKh7%&P%(~ ΨeΡ |Μο‰#=τPEEΕΘ‘#‘F΅d;ƒA°cǎΑzHπ‚˜’Ό>XίςAVδ{uοή] ξXA`Π AoΎω¦lƒΝP±‚Czzϊίώφ7Ω&$j‘bώύϋ9rΔn)B*VΠp»έ}ϊτςξ¨Ε’ρήΎ}ϋψψψψψxο ex ιPpTŸΦΊ<,lOxο,»εΒ-ΐφ—ηy€7ς+ρ*ly)°ΣιΌλ»φνΫηmvκ·|μ}Y’οϋ°qωΛ >~ι₯“'OV ε)ΰ‡~ύλ_7eϊ\_φ†‡w bΠ“;–v***ώς—ΏΌτKv ͺP±ZƒΝ›7oέΊuαΒ…v zP±Z‰υλΧοΩ³gΦ¬Yv JP±ZΧ_ύΔ‰ΏψΕ/μ€5°dΌΗΕΕΕΖΖ:―ΝWψΠοJŒƒi λeιΔd ]IλLc₯Ԏ;ŒC89LφoŒ}₯Τ‹/Ύψ³ŸύμΗ?ώρ’E‹”ψΚRBςΐ‰²Ι1x2 )hΔμ ξX­Ν’%Kššš²³³νD/T,XΌxqǎ'Mšd· ‘bΩC~~~JJΚ„ μDT,ۘ={vZZΪ“O>i· Z°dΌ744ΈέξkΧyS Ή^ςΚΞ­`HΚBd0-Α’•ωUπ.L]Ή•d­6Έ#'Nœπ!Φ΄<"NTœ9sζ₯K›šš6lΨ ZŠ @ƒ+πδyˆΎŸ˜9dP­.K«†;–ΝL™2eĈ?ψΑμ$ΘP±μη駟7nάΐν$˜°wCH0zτθuλΦΥΦΦ~ψα‡vΛΈc… #GŽ|ξΉηΌΙς+„©’DHΗo™,χ|Ώ>Ž349>pΰ@QQQiiιΨ±cUKGͺ@Ρ½LάƒΎΘp}H΄w¬0cϋφν+V¬XΎ|Ήέ‚ψŠ~”——ΏρΖK–,±[_P±Β’·ήz«ΌΌΌ  ΐnAΎ*VΈςΖoμέ»wΜ9v 2–ŒχΊΊΊΖΖΖ††o„\ έJ&!)σ±ˆ`ΘnR`ό‚χ OŽ€l$°d•Ώ 3YφΖ²,’ρέ†YF`ΰ+€σϋί~βΔ‰S¦LΙΛΛσΞ€Ώ")8LG5eΗ―€αŽή,[Ά¬¦¦ζWΏϊ•έ‚ T¬°§¨¨Θγρ„Z3*V$PXX˜œœœ••e· …Š!̟??%%eτθΡv ς,ο)));vlllτ¦:ωΞΜΖ7Ύ‡ό-iΊ‚7oΖε{m°mΑ”–•ΰ^Θ^`ςt?0~εqΰ @ šμζϋΐD8‘$++«€€$33sέΊuή™ΰ|A™0ά±"ŠY³f}ϋί>|Έέ‚P±"ŽiΣ¦=όπΓχίΏ½bP±"Ι“'3ζξ»οΆQφnˆLƏ§?ύικΥ«»vν²EξXΛ“O>9gΜΎ}ϋΪrwK;ΦδΙ“Σ^½κu^~ϊӟb82έ Θ3ΩΑΝ„ψ†Œ?ΐzπ‰ $F‰‹<nρpZ•ΘΗ’~(„\ΐGƒ3ε•π|‘πIzΐ{ο½·bŊΏώυ―͏Ύ΄Γ+Β)))ωαΨϊEΥT¬ΘgεΚ•£F β;*3ψSΨ©[·σJΕvξΣ]LLLlηΞJ)§RAΫIIP)..ž4inΨ iγ ή9ΠFQ¬ΆΙΙ“7lxW©„aΓIIm<ž„”₯Τ¦˜˜LΡΞ•„ˏœ™Ήόόy£ΥΦΦνvmήμφ—°(V\bbr·n+Υω›ίœ7oήΉsη–.]λpΜ;}ϊωηŸo^@ΚD}0oΉεXΰ;CK:Ζ2t’„ΐz™1vΫm·‡`κΚ ˜2ςŽIMM5+++a½ο7YbQ,£»36=}ΰ¨Q«W}šρkΦ|ΊcΗ±wίmρϊS§NUB+Šp»έ={φlF#V_φκΥkΠ AwήygLLLߘ˜†Ηkώ«~ύϊWϊ}; ­Kω?ވlρ޻ߌSΨrdF(lπΎCΆq‡˜±ί;Bύ–ίΎ)°£Θκ.X`|[qkjκ}Σ¦]>w’’βδΙ“A`Q,gmνΡmΫΦ?ϊθΔM›ŒσυΝG€₯₯Αu ‚#JœL Εψύι„Άi~ΧΛ-Άί%~ξe[eΨrΰ—QVυΐ)ωȟZ0ϋŒ<ςΞ;ζεyGΉtφμα>Έxξœw(s-&ΕjtΉ–=φΨ«OλΚ222‚$ >»Š‹wΓvι. ώ~-P±ˆόδ]ψ@ΎΕ!‘‡ίΜB!„B!„B!„B!„B!„B!„B!„B!„B!Ρ Ϋ_ΨΠƈŠρXω'fG?’*Ρ‹hŠE΄`ιΘyː!C¬\D ά±ˆ¨XD T,’*Ρ‹hΑκA˜@YY™q8tθΠΰ^Ÿ„ ά±ˆ¨XD T,’*Ρ‹hΑ’W(Α‡q8wξ\XŸŸoεŽ$\ΰŽE΄@Ε"Z b-P±ˆ¨XD –ΌBYtΦΤΤδc¨”š={6Μ<όσVd ‘ w,’*Ρ‹hŠE΄@Ε"Z°δΆiƒoll4].,Έzυ*Μ̘1f/^lE* pΗ"Z b-P±ˆ¨XD T,’K^aBBΜΔΖ^§©qqq°RL•Rυυυ03gΞ˜Y°`A€"›ΰŽE΄@Ε"Z b-P±ˆ¨XD –ΌB·Ϋ 3Χ]3ΫΆm d)"8’J©ΔΔD˜‘Y¦2•„ά±ˆ¨XD T,’*Ρ‹hΑy…ΫΆmƒΙK—.‡rτ ₯k)#Œr¬X\΄h‘OyΙ—Ζγρ|^!w,’*Ρ‹hŠE΄@Ε"Z°δnίΎ&λκκŒCιΚΘ Μ •Ÿτ₯·aJ₯ΤΒ… Qhςe WHB*Ρ‹hŠE΄@Ε"Z°δΒι„Jυφξέλ{j©Œ¬=Œ—w‡§Σ 3²μqώόωŠ˜†^! 9¨XD T,’*Ρ‹h!Θ^!Έf»wfPΖψδŒDΖ₯"=Ph‘ͺ˜wκz…$δ b-P±ˆ¨XD T,’Kέfdb'8k/“ω’22(½B”‡b˜ιc#ο5oή<Ώ3$Έc-P±ˆ¨XD T,’*Ρ‚%―P&‚#&+%W\ιίΙ^¦2†%=>ι“šωT~~>Μ̝;W‘/ w,’*Ρ‹hŠE΄`Ιx—–/˜σ2€“œœ 3ΐ—F·L ,­ΟLz t¦OŸ3/Όπ‚">αŽE΄@Ε"Z b-P±ˆ¨XD Aφ Α}“Vuu5ΜΘpMRRΜHo‘‘Α―xςSfi†ς:ςP;yπ]”Γ‹hŠE΄@Ε"Z b-P±ˆ,y…5ƒžŽ²>Μεrω½¬t6εΰ€ΥRAX»vν`FFε§€(UJyž}φY˜yξΉηTΓ‹hŠE΄@Ε"Z b-P±ˆ,y…2±\*ιvκΤΙοE$2~'σEΝΜΘ#dώͺ™13 H’Όέw,’*Ρ‹hŠE΄@Ε"Z°δJg Bo+4SEh¦i½ŒίIοRzs²PήKϊwfͺΝδΐLnnŠPΈc-P±ˆ¨XD T,’*Ρ‚%―P6“όgζ9ι9Κh\#}R3¨τ7eΦ«™{I―PΚΑ~"w,’*Ρ‹hŠE΄@Ε"Z°δΚ’<@Φί™9 ΞL_P‰\#ύM8 ]΅δ»INFεΙ ²CŽ™ž¨Rζ™3gΒΜ―ύk†pΗ"Z b-P±ˆ¨XD T,’K^‘tj`F.0“±)?%λΝψ’2[ΥΜY.\π+ΌNMM ΜΘήͺς:f*³³³aζ·Ώύ­ yΈc-P±ˆ¨XD T,’*Ρ‚ Ο/ΒγρlάΈ/w½kΆkΧ.ΏΧ‘15ιΝɚA4σ)ιsΙ¨ŸΜ•ΡΓΐςW₯(ΧΘοe¦ΣΞβΕ‹•<™ΰαŽE΄@Ε"Z b-P±ˆ¨XD –b…ƒΓ ΜdQʘšŒšρε•ΝΤϊ™ι8*ε‘±K3^ͺΌ—™zIYη(ΙΙɁ™ΒΒBΏŸ w,’*Ρ‹hŠE΄@Ε"Zr)Έ0²ŽOϊnη’—•–ί’F₯ΤεΛ—aΖLόNz©›“3²‚RΞΘσΝDΝdαJ/5??fζΝ«ZξXD T,’*Ρ‹hŠE΄`Ι+τ{LΌτΉΜœa&Κf&―LΧω)›3“A*ύ23}lδ7•3fΊθΘo*σ`[9žΘ‹hŠE΄@Ε"Z b-P±ˆ,ΥΚIpρή}X }%yΌ…τ₯οf¦›hϋφνaFϊSfςNύ6ΥiρSRžΐ"ƒrŒBJŸT>1ΏΟgπΰΑRΦ’Π‚ŠE΄@Ε"Z b-P±ˆ,Ε ₯kN„ŒXIGflš‰'J?H"―#γ€ς+H™eόΞLΤΟΜi‰ςiΘπ«DvΘ1Sω(½Bωέwίυ{kσpΗ"Z b-P±ˆ¨XD Aι#ΰ!„B!„B!„B!„B!„B‰6ώΔ\uς@މΎIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/roi_getarrayregion_img_trans.png000066400000000000000000000316521421045507400320540ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ IDATxœν{TΥυοWΏζΩ=o’(&5‰IΕTβ―R‰ρeύJZ!ζε+1bP4! Qo"ρ ‚ of`€A/H@A4ρWχŠZ©RQD†ζΡ=3ύξsX·W-φή§g˜™3έΜ¬ΟΤaχ9§Οτωž΅ΧZ{ν}AAAAAAAAAAAAAAAAAAAAAAAAAAAθl_ΐ°bYm»\£λofΌΩΎ€αζ›ίό&nΘDaN0κ„Eόυ―Ε T˜ΘkhEΒ²,‹ΜΥuΧ]Gν“'O1`CΝ(;vΜξ£Ι“'O˜0DaCΔθA’YΆl™ςΡwΏϋ]8uκ”(l0ŒaY–5fΜܞ={6΅»έnάH₯RΚ!Σ¦M€γǏ‹6άΩΎ€αγΎϋξ»οΎϋμ>mmm}χέwqϋΤ©Sό#Ώίοχϋ­4Ξ^εHaT<…–eύύο§ώώχΏ€G}΄ΆΆ–[ZZh»°°°ͺͺ ·?ŽηŸ>nώΔ†e`΄t…ͺ ζΝ›‡έίΚ•+ω………ΠΦΦ“&MΟπυ―Υ&NXF‹°–.]zΗw?ΊυΦ[+**ώπ‡??:uͺB6lμΨ±ηwH&ΜΔΘ-,Λ"qά{ο½0sζL` R¨¨¨ ν|7ΈΉjnnƍšš °ΰπαΓ­­­Έ- ƒΡ),δ±Η»ύφΫq›„ΥήήN;4666 ‹Cͺ€Γ‡ΣΆ( FOW¨πΨcA:‰5wξ\γ>ΏψΕ/pγ‰'ž0ξ€Αcee₯N‚ΝNΨƒ-ΛςzΓστΣO{<άFa!ΕΕΕΈqΧ]w‘ΕBΑ£ψ!Hii)nτττ@<€@ @;ζήyηeς]+™LBZ(© ž~ϊiܸ馛τcΡ°ΕγqΜ\ͺŸΟgχ½ΥΥΥ0Κ Ψ ‹…Ο=χnτττpa% Ϊξξξ€9sζp’MB"‘Θϊυλι<Έ1vμXځλμ‹/Ύ m·ΫMΫV؈ύΓΐ²,²+σηΟ§v|ϊι§pΑͺ )..ž5kn+ΒΒ ΗC'δΒ ‡ΓΈQRRBΒ’$(,,όμ³Οp{δ)l΄t…hTΈ}‚΄ͺpcβΔ‰`ӝ-Z΄ΊΊΊhDˆT…άyηpΊ^IUΠά܌§εΊDΠΝΟΟΟy™°‘σ—(psχί?n¬^½·„(,$βbWWmί}χέ΄M}%:R°xρb.,κ( ½½―ŠωωωΈρΡGαΖPΨYΨaYΦΉηž ννν€*H iooΗ0«Šk‚‡x$,ͺƒΈηž{ΈFΒ€/Ώό6lΨ ¨ŠΆΟ=χ\ϊ"ΖπΙ'ŸΰΖΩ«°‘ίVTTƒΑ’’₯o3¦FS©χ~ˆP(„zQ <σΜ3¨ άHˆtr~§7lΨ€7ήx£ρπ{ξΉž|ςIγ§x*Λ²pƒTηœsnδZ&,Wc±, ‹Y,žeΐ ½xή΅Ρ]A£‚»ρЧO.y]===ό—••)gΈζškψ\Ν–e-_ΎNO|p ŠΣΤ”φX,Μ5ΜΊΒFš°HU0gΞάθξξΦ……Έ\.μγψΰ½Uuu5υz$ ή+ρ|:wη1Ε …HUpΊ4£Ρ(ιž„Ε'kτττ¬X±‚ώj§ς2ή£°p8L1AΆ6*ΊBΏί·ίησ)ͺ‚tηΥΣΣSTT€ˆ9OT*Τ8TΓo-%N€±“ΒΌF]]^Υ-·άb<ωm·έ%%%?ό0ΆπRirαyψ‰ š½^oΆ2a#JX–eΥΤΤδεερ{ ¬!'“I]@Ϊ!»κ υyOΔm4*δΕλ׍F#‘·jΔͺU«pγζ›oΖ ž²€‡zH^ ŠΉβ‘ύf'lDu…(,άΖ’΄F$,`Ξu"‘P*υV2™€QšΠΈΦ„B!.,ΚΈ’ΆΠhρ@g_σςς”―ƒΣ»ΰ_ώς—΄ΝΏ£‡††>ΠΙ]4—ΛE=5yd·hΚ€Σ QKύ'·ΫMόS%»­ΰχϋ3Η‰Ό›γy|ΠήήN6ƒπω|}N|mhh€3fθͺΒv”Θ’%KψQ(4Ιωωω<«‚TVVbΏιt9¨“–•…ΩˆDvΑIτh¨‚ ž}φYγώξρΩ8άζ¨Λcǎ‘ΉRTtE½!wΛƏO8·R'OžΔ‰'’Ε"{ιπηρdΩΌ6¬:δ*αή$ζZαto t¨&lPλ– ^˜3§9£ΛΠI7Ž: ϋΉ€8θ¬πϋ ,5oœΏJ ζ>ώψc;7 Ϋy|Gͺβ—d<|ώόωh‡pα eHOύΐhNW•Ϋνζ‘©Βy睇†mh° λ™PhNUΥά{ΕO~’SΪBπ]³f ΅π—’6nhΒ`,σϋύΙd’WΕd@ρΗiϊ—"PϊjcΏLtww———γ ζ‘ @"‘ 7Ÿ(--Ε1J <…=9z½.š2T]δ~&Ίόψhw7<ψΑYΧ_LCt„†q‚Α κτdWVVς™¨άlΔb1܍ΦόΖπŠΒ?ώ7ΈkΕ½4΅‘ϊc±O–ςήͺ··—,³Μ»Œd2IχŒ„Ε#ωββb²p\X”δaήE”> ‹όHϋsx_aα†Χλ₯+αΒ’°γg?ϋ Λ;~|αχΏο™8±kΑΪsεΚ•Ί° ]§V6kΒ*,+{τΨ1RΥͺU«fϞΗγρψάwί}φͺ«ΊΨC?œpaαΈ[SSuζϊΐۏ6_―ή€p8LwWnπ;Δ ͺΘπtvv’°xšΗΉρœΊ° •JΗ³yάͺg€u»Ώ}πΑβ«―Zε.* όζ7€-μ―q8’Ÿ™OqΉ\Έθα Ug*¬ž}φνυ돾σώχΘ‘#555‰DΒησy½ή”eE#‘D"RΐΖΐŽέ½{χŒ30ω9cƌT*eYV2™\Ίti"‘pΉ\‰D"γΐμJQQ=ύаh›ΔΔ»3rΊΛΛˍΒB£ˆ&JΫΫ‹ια'b,'MΗγq]Xγ.ΎψͺGωΫ·Ώ =τΧw hώ7 ‹+ιΓ?VaΉ½ή?½ϋ.ουΏώϊŸόδ'7άpΓ3‘Πάqγ\©”Χλυω|(΅3έΨ±?ώρίzλ-ŸΟ———7~όx·ΫνrΉ<OOO—ασω<O"‘@EbΗGΔb±d2Ηc±νC{β]€C‘~FQάψkНxT</))ικκJ$¨c‰lΩ²%+Ϊςx<Ζϊ;|μB‘F3ΓΓ"Ԋ>VƒTWW£ςτ\%œ> !U!™‹(urτθQήΞ³Ύ–eαIHΣ»_yεg;v\ςο?ρΔ{φμΑΎ―zϊτ=όπϊϊ/ά'™L>ςΘ#ΈMI/ΐ˜+L‚tώτι·Ώψ"EˆΈšΤ0kΛ²,¬5πx<Ώώυ―©]I‡Jϊγw˜&EΐmΛf|>ξΐsͺέ}ω===(ε@  Ο“€sΞ9ηΛ/Ώδ]9ΏTΤe,ΓΏ+άΡρύϋ».Ώ|Ο/‰ϋ(ͺR@γd·Πα ԐNν 7€Ψߜmq”j―Χ‹6ΖlΖεraͺ0θFoέEŽύa»]ν2ωΞ<”£νP(„'QrύX«ƒ»UWWλƒΚώα«W―^±bΕ™9σ~ψόwΎS=}ϊ•σηΧ_{-υΛ܎RΘΉtιRΌfͺ‹%¬”φ$e][PWWGΏš±:―··}¦x<ΞΝ> Ε„B‘d2iΧOα]΄'ς·`πf'‘d2iYVss3wΞ”cεΐθϊkkk·lΩςή{οω½ή%W\ρσ^pΉ\υΧ^ΛO₯RT>―ŸόΙ'Ÿ{Ζ? #ίP(4„%YV:ͺ-Κ‹–––ςr’₯₯₯’’BOΰ\VΌ‹.—ΛψSRΛξQΖ¦TžΠw:tίΓC‹ΖpP n·ΫΞ ι'ηR£°4“ͺθΣΦΦV―Χ›ŸŸo·…btν¬μΰqΆ4yμVWW¦•Z6τ½PEEEJ‰ΐ{Ζoο5HaΌs€[މVrVt¦M›ΖKω”ωδκQϊ[©ΞC£ElέΊuσζΝ¨*έΘα©<.ͺͺ* §‰ΤCk`jޝЖ2ŒƒtvvΪU!C_KdΫΉηeŒ j±ͺͺ SωΚένKCΓα°ΎŒ§ΈΈυJ₯Θ«««Ϋ²eΛλ―ΏŽŠoooΧO‚^Ή]Ε’^?T Ηd '΄EήޚyOnlx’˘,νι鑞Ș―Η‘ΌΣ\…h~π>υφφκyT>)Νn$ΐXYJVΣνvc7Ίf͚΅kΧr7F7—ςjjjΒ ΎΔœ^ž:„ Σ,‡΄γƍC—œΣŸ‘d4Bn·sϊ|±+H{H(V—ΛΕkͺ8θ?Ω%«ŒΉ~D*•Β?Ν¨?τ«ΆoίNΡ‰βGκΟ<γ`Y¦λΈ›ωηŸω\α›ώ5TΪ²,‹|κΏόε/N"Ψυƒt{Έ¨Ρνv£±QR©΄^Ν„ Œ© sT|ΈšogxΛ&βυz)Η‘ƒξQ$Α«-((ΰή:Ωγ Ϋ>—’kmmΕσχ υŒΦy…Nϋς¨0nΐx?HKžΖ$JKK#‘Θ˜1c(Ξ!šl‘ςˆ“‹m|τ)G‰Dτ^}ββbΌBή_£LQkΦ¬α1 °©`z§δηηO™2Ž9ΒηpΣΰP`8άV‡D[|©E„›«@ €©SŒOΨ©ι·ϋˆγǏ£ώΈΉ*(( qe»H†Ρ\AAqΌ@όΖ¦¦&½wnllά΄iΣΎ}ϋό~Ώ₯: ύυ>0eΚtα%577;1g5 3‘£-Λ².»μ2άΎφΪk±2>©FiQd,yγΰΝkkkΣ2μ4Ρ~Ψύθd'ψ}")ηεεα΅)s¬)w…γ•ΊH`ΑAccγΆmΫ^ύuό/%Vx™Χb*•κνν΅k/((pΉ\………Ζ‚Ÿ‘";Sμ‡ΆOμκκ’1An'h»©© ]]c„9»Y¦xzΖΑhΨ†Š¬­έ0`m‘ ΕσXψζƒψ˜Ϊy¬ΥΥΥάƒ±ΫΰQΩ £S‚/ν‘VwΤΠΕ&‹Ε­¬Χλ5ΚκκκΦ­[Gγ€X­οζρxƎ‹&–GβiΡvϊύ~=ΓςΑ8΄vC6ίb~Γ–-[ϊΉ?•υΐUW]e—’€όόόόό|€S `BiŒΗz<žςςr»Ρb(((ˆF£(Jύ6cAΊ1Τ²ΛM(ߎ$“ITΥξt­:~]uu΅> ŠΆ§¦¦¦¦¦ΖΞω€X,†“ΌϋΌŒΑ“εΥfΩ'’Άh1ξU`„ΪRϊ„Ϋ cbΊΌΌŸυd2Ι Ι›‰F£§N;TjΛnU2œόšy ~}}}]]W°‘υκκκ'NΨ0–²²OΪ[o½εάbFΩ_Ζ¨ŸΪ’κ+₯ήη– ³’Аω±Λ5Η)³ερxπώι!$€c±d2iΜb€©&ŒŸ‡:Mž±\³fΝϊυλχμΩƒsxΐ^ Κ%ρX!™LΓγPΡU²/,8C»εχϋοΈγ;‰ΰ£i7τkLysO fΧ›„Γaœγ…n–βHQžŒtΜΏεσΟ?Η ;—Ή΅΅έΈ… ’ͺψ§xε‡’ ίΝͺϋκκj'J―Œδ„°ΰΜϋDέΟP²XΐD«U+ИZ†WθPρ‰ξσfΘVσ¨ΒθζŸ:u Υ_VVΖEͺZ²dΙ¦M›6oή¬;yhω°δΛNΤ™ςΓIρ---ψ-n·ΫΡ~rGXQ[Ψβ6ŸU²hΡ"»l BΥ2ΥΥΥ܌Xw4Ψa^ Λc”Ό^/εiρfλ £ΙέιμμΔm2™¨ͺύλ_ž€οσωμΒΚΔR -3'KνςgCH 2j«ΟX†¬O.ΡΒ—U!ΠΗg7??ίn|½{;S^”χͺ€γ`0XYYi,§Γ].©J)£΅q(ΒΰζŠ„e·&οΙ“'1¬–uΘ-aA?ϊDκΌ.\Hό·ΖΫ¬δ¨hι‹1cΖtwwΫ-ͺ†κδςβ}%™»»XUU…·ΠΈ¨)φ’˜‡Σ•ΪΪΪΊΊΊΧ^{ΝxZΠΚuβρ8Ν°Υ ^-οΌσΞh\άVΡΖαλŒ‘?a,’ΙΟΟGάΞΝWz7e`‘4dp€ τ©S§ςi^D"‘0Φύρ΄½$qŊ7nDUY–EΏμΦ³D•$ Σ£UQQ’Ql3di—‰rrQX i+Γl8H₯R4δ§ΛέόΦΦVάί «ΫδT[b΄R‡ΖσσΕά mηπ£Ρ¨±¨―|εΚ•€* ρxI]³Χλ5–ήcƌ …BȎ’£Β¦-jqΉ\ψWUUα*ΒόΘΨΗ᳋;Lž<ΩθΕSΪ“ζ’”#‘ήEεδœ–——γ’\ΖΏ=!·Ϋ­‘+Wά°aΓΆmΫπΏνφέwί†Χδ° ±±qΛ–-{χξΕ^}υΥΈΡΦΦ–aPοn4΅KG‘ΏΕεΕ2²LΖ^#™LΖb±ςςrΕD!$;R€βκρ!mTύuΐ¬―$π»ΊΊΠWΣˆΠ°‘ŸΗI$Ž"λds¬°Ÿμή½ϋoϋ›ρ£κκjž°VΦnhIC-|•φH$ΡΡaΤ’L&Iz<ˆγ‰TΈ’tΣ4Ζ3;vlρβΕ ,Ψ»w―q¨ ¨¨‡;ατΪ4x'Nœ8qβD†Ι·X˜o u¨ϊΚp Γπ†»ν?ώψ{g%Ύ΅ΘΌhΆnE:::P@~Ώ_Ρρ£ΞΞN=.C»‚7ή.εm ^zι₯eΛ–νΨ±ZWΠO·«Αυz½hŸPΚCO½Ξ'ε…B!γτ"‰P²­ΕσΟ?ίΠΠπςΛ/Czœ›RYŒaqΪ™™‘F1]tΑΖ,ρprvt…ŽΥ?ωδ“]»v-`λβ#½½½Έb§ρXμΰΘΗ“γǏ7G‘H€8q‡I“&α²XόHτ‰D’½½UυΟώ“©T*•JΩΉhΎ4άΈr-ξίΏ_―Z&Γ†/3–£9MξZ,#X…‹Ε,X`|+©Οη£Ϊe. μb¨>]wδ“Ι$υ;g4FKNύ€I“PLΊ «««[±bΕξέ»3”™γyx Θ—φϜΩΏ?vτveMMMƒ'ΐ‘£Λ²¬©S§βzΤ·ί~»ςιΝ;·mΫΆxρb=Y@Ώϋ‰'2x$˜ °³pix#ο•h&Ύ‘R—ΛEŠ7ς γ iŒgN₯RΖ‚_ΉeYψ`θYυH$‚Λ‰γν…†,₯q͚5υυυmmmv^eƒΕVƒΑ ₯θ===Έp·ρSμΙ>ιUΒEEE›6mRκΦ‘P(d₯1žΌ²²{I° 2,Λ2†¨(JLeq§p˜9kΊΒH$‚ύ w/Ύψ"ΤΧΧϘ1.6?)Ώ)΅}m*h3fŒqύνd2ωΕ_θ9'”žΣ²,EΔ«W―^»vν«―ΎŠζΝΞ§²-ώΥΤm₯R) }S56$ »ξ‡ΘΛΖΑ²,τ…σςςτEΥ0½^VVΦΩΩ‰ΪΪΈq#AڟTqΡΪΪj\vέ/Μ~ωύ~»N£0Όε«W―――ηΖdxŒ 'ƒAμšνr(XzoΧλνƝa#…EΔb1»—@ΪΫέ΅kW*•"m”œδ•Ό’D―ΐδ ‡ΓhŒρ ˆO₯Rz΄uλΦ544πuxz“z1γxŽΟηCλ‚ S,Mζ|z<§1Pšά1όζ rPXdμžώy»C^yε•όόό7Μ9[ΈσaœCΰKP2 ›`7GnΎβ֐2x2bΩ²e ―ΏώΊ]/Fψύ~―Χ‹'Ρο=0ύ#~v―)ΐφα™E¨“‹Ξ;™ ώΞp(HC-δΤoίΎύν·ί――ΟπΪfώξ}ΰO3αθœsΞ©Icω€­­mοή½ΫΆm[±b…’$wγίEΫΑ`PqΰΒα0Uέ(ž«V­ΪΌyσ›oΎ™Α6 #eχ)kGKJJŒu/$¦ͺͺ*cΕβ©S§²b §,–eYΈξ>̚5‹^Δeά_‰ ΣkΨχξέ›——·aΓ†oΌQ?6s TPP@KήCѝkiš»ΠYPIDATiΑۏ½φͺU«κλλχξέ‹·o€τ)ΛΗΌυ••μ¦ •——?~œΏ ƒWΨέݝΉςqxΘ!a …B±ssBοw€σΟ?_Ÿ‹ΌgϏdzaΓ†Y³fΩEϋ4ΘƒDΎ.ŠNKKΛ]»Φ­[ΗλΦ }ؘ‡ež’Fύ~†W †’Ι$ζP”­α_D'·„Υ’,Κ°|ωrάπx<­­­Ζ\ΰRΚh V>-Z΄θζ›oΖξ'ρΔΪ<»~ίΪ€/uΦnέΊuΣ¦MoΎω¦’Δη抾Εξδ”ΘΛΛΣG“πpΏίY{Z… ΜSΩκ!w|,Λ²ΠγiiiΉηž{2,ΙΪΪʟςΜύΪΛ/Ώόό£ΆΆ–vΞΰλDΠ•Έ‹ΆuλΦ•+WR}•Λ劦±;yEEv—ΖΆ±XΜΰΥβ ²]"-•&[Y+ά²XΤ-ͺ±d~-ΛBλβσω#‘Hmm-·―ίοGBƒƒŸbOλΉqP—Λ—/ohh8pΰ€ρΫ© ž{BtΑΖΕB°Bmωύ~»t(j‹›.εςp£©©)‹ζ rDX–e}υ«_ΥΫqVͺ1P"h†»±Β3—””ΌρΖP[[KΉ1Ζ₯μΚ‹±‹QV›AU½φΪkdΈ'Δ©Μ+τƒΑ²²2»UωtλΕ‹gΘ)†ucFN Αgρϊλ―7~JΊα9ξ”η›SxυΖoTUU-X°`φμΩϊΙυκuΕk‘BW°z:£ H{’VτήΚN57v n·GŸ,ΛΚΚ4/;rHX„nηΉSEuyΖWββmΜ=βϊ ,˜;w.ωΤΚν$‹jΦ―_ΏvνΪ={φΨ­gΤΪΪJsόu“ƒ– /Oωˆ.`φ›pώ23JUd½„\αύ 7WΣΐηŸΞ…E?ŸEc7~LRίρΓώπŠ+ :?²XΖ³α{l¨Ύ -h8ζήΊςRqcu(υh~ΏŸ. ‹NΒίΏΒt7Qό+ΘYlmmΝΊ°rΒxφ€1~zξΉη ²ρχͺ¬¬4:I\Ž}τnΌφΪk;vμX²d ώ7œΖΞΝΒ£ͺ ύ`»Εd0™ΞΣ•J‘1υσ.M·Ϋm\| ƒΡh”Δd7fŸ-r«+Δξ#“Ή¦‰P(„>“’˜ΖΗ•QYYi7&ˆη©¨¨ΨΉs',Y²„―ΆEγƒϊ‚€+W¬――πΓkjjμΒ1ΤΙBIl’Άμͺ׍―'ͺ««#‘Jt iIQš-ΜdέbY–Ewτ·Ώύ-n655e˜ ‡v+/κc‚|ψ°1Γ5₯2Lev»έ΄‚ώΈc΄_tΞ9ΎόΟΕsηb’‘‘α‘Ϋn›ψ›ίTυ«\4|Υ%φ€ΖTE[[›±rŸΚΆpαPγΙΏLΓ5ͻ˜ςӍδ’ΕRΠ»ΘΛ/Ώ\Ω' βCρΚ°±eY‹―Έβwσ?›ύλυ―[O__„ΐτ/SVεΧ+ϋψˆΣK19---₯₯₯τ’X}˜AIζ²Ή‚³έΗΚ ο"©‘ιxyνωίK—ώί‡¦q,œ>…• ‹W­Θ»Ÿ $ν¦Α ΪŽΎΧεr½ώϋpΙ%—θΗbž¬ͺͺΚnΩH½‡΅ΤΚF…°ˆ>‹ £q?γΊgdΓΪΫΫρœvϊ{ύχΉQTtΓΓv£YΚMs£MX„’§ΐ¬7wŌ‘'™L†Γa»L,zr©T M ½8!Ή•iΚψϊ;/΅Ÿ™Ψ¬‹b~Haτ’Κ‘<Ώ‘ΌΫςϋύXΕΚM ξιι‘ijάbΡIxΛm!šOeYTξΓ:t(7-V.^SΡ‹xg„vS ΄'―-,,4VP9Ώρ-›œ@ @Β@QWδ¬ͺ`Τv…v(NO “€ͺ««{{{νΊ!κέH‘|’’vU7xZ»W‹c&Ά¦¦&»+"χ‡\ΏΎl‘8aϊ䐑eβώμΫΥΒŒk=Π/%wΆ0ž΅{oYξ ΒΚ„ž§ψΪΧΎΖw0ΎœsτθQτč/ϊ€όό|ϋμ3j€ΜͺΫν¦·Μρ£HŽeeen·Ϋξέ-8ΈžΛJRaŠΒ¨8Χ½ΰΆέτ΅D"ΦȘȠvςΥ””˜Q9ΘYσδ&dΐ.Ύψbj$Ηo? ˆ{E΄°°KiΥ…h4ΪΩΩ‰ΩφœZT­?δΊE1Βh-{ξΒγHbEEyf«²²Λf<˜ϋύ ˆΕ6t'μ[ίϊnΠψt{{;ΦYΗ‹μf†ε&"¬αFQΙ‹SRR’˜«αΉΆ!D„•5'¬ΊΊZYΎV_ +Η‡q8"¬,£w‘J i·nŽ#ΒΚ2, p6rvΨΥΡ *μ‚ .ΐ~ϊι§gK?"¬ά‡šžEΒAAAAAAAAAAAAAAAAAF_ΗξΝΨΒH" '‰°F<ƒΉΕgΩ²τΒΩ‚Kp–ΰ",ΑDX‚#ˆ°Ga Ž ΒA„%8‚Kp–ΰ",ΑDX‚#ˆ°Ga Ž ΒA„%8‚Kp–ΰ",ΑDX‚#ˆ°Ga Ž ΒA„%8‚Kp–ΰ",ΑDX‚#ˆ°Ga Ž ΒA„%8‚Kp–ΰ",ΑDX‚#ˆ°Ga Ž ΒA„%8‚Kp–ΰ",ΑDX‚#ˆ°Ga Ž ΒA„%8‚Kp–ΰ",ΑΌΩΎ€²pαB₯ε’‹.RZΌςΚαΊAE,–ΰ",ΑDX‚#ˆ°Ga Žΰπ‘–eΉ\?<‹-RZ.½τR₯εΫίώΆ²~₯εύχίWZn»νΆA_έ(b0·X,–ΰ",ΑDX‚#ˆ°Ga Ž0άQαΚ•+•}Œο²Λ.SZ>ψΰ₯eλΦ­JKwwwŸί_π3Ο<ΣηQ£‰ …œC„%8‚Kp–ΰ",Α†8*άΎ};―>’7nά8₯eηΝJΛ;οΌ£΄ΈέκL&•Η£΄ƒA₯% )-“&MRZxΰ@’B!a Ž ΒA„%8‚Kp„AE…­­­z#―ίνΫ·Oi)--νσ»βρΈΥΥ₯΄TVV*-‘HDiΡcœόό|₯%•J)-χίŸW8"‘¨PΘ9DX‚#ˆ°Ga Ž0(η}Χ]Jγψc±˜~”RRR’΄œ:uJi3fŒrςδI₯₯­­Mi)++SZό~Ώ’»κΡhTiιιιQZžzκ)ˆσ.δ",ΑDX‚#ˆ°Ga Ž0¨¨πό£˜——Ηλσω”ϊS §£Η&zt©ΰ+-zΪήή΄τφφφy…ϊyζΝ›#‰ …œC„%8‚Kp–ΰ",Αή}χέκιN"€²CQQ‘’OΙ*,,TZτ1ΎŽŽ₯E HΑ4¨ŸΉ?K‰θ‘¬~=ϊ΅ϋξ»―Ο3η8 9‡Kp–ΰ",ΑDX‚# κ΅rεεεJ‹2Π¦ΗzT¨ρυg }ΪV"‘PZϊΡθ‘£^wͺ»~fύΫyδ₯εOϊSŸΧ3b‹%8‚Kp–ΰ",ΑDX‚# **ΤK4½^oζτ(L°τ}τ™}zt©ŸGŸΥ¨·θί₯]φωg‚)rΤ―πΉηžSZξΌσN‘ˆΕA„%8‚Kp–ΰ",Α†Γa₯E ίτ9zύ‰υy|ϊHœ'κ‘š>ζ¨ΟFΤkJυύ<ϊ|ΙώD—zœ¨/BωψγΓˆ@,–ΰ",ΑDX‚#ˆ°Ga Ž0¨y…z£„ύ‰οτZP=Ύ+((θσ(}UR}όNIυوϊΛ τkΦγDύzτT_έTΏB}ύΥ @–y…BΞ!ΒA„%8‚Kp–ΰƒ+Τƒ%|λό<=rΤΧΥWwΡγΔώŒ9κ‘š>š©Η‰ϊΚ6ϊ+ΣυΪT} U5τ΅Lυwp̝;Wiyμ±Η η‹%8‚Kp–ΰ",ΑDX‚# j¬pΥͺUJ£dιo#ΧΗυ4}]P½bSυhNoΡ«:υHMΤΡΗυF½UIϋσƍ`0¨΄θΏΖόωσν/vΰΘX‘sˆ°Ga Ž ΒA„%8Β Ζ ›››•½΄R‘³³SiΡ#¬‰'*-ϊhΎr©Ύ ͺ>z¨Η€z„ΥŸhNŸΥ¨G :ϊx’ΫκWήηψδ“O*-sζΜισ(G‹%8‚Kp–ΰ",ΑDX‚# **ΤQ†ωτ(L+ΤΡ£§ŠŠŠ>χΡGυΉ~cǎUZτوϊQύ‰%»ΊΊ”=–,--νσ»τPKΥΗοτπόΡGUZζΝ›ÈX,ΑDX‚#ˆ°Ga Ž ΒaˆηVVVςκc|z¬€ŸDοτ*J}dP―Υ.υ˜ToιΟyτ¨Pψtτ9Œϊyτ±B=*μs@LQσC=€΄<όπΓ}žgΐˆΕA„%8‚Kp–ΰ",Α5―πή{οΝΌ^W©ΧLκρ‹+ιΡ₯…ιρ”ώ&ύzτTŸW¨„Ί`›Σ―G7υΏKψτ5jτύ―θO•©^O[VVΖ«Ώ&Cζ 9‡Kp–ΰ",ΑDX‚# j¬P»”ΐGDϊσŽ =Vγ2ύ«υ}τˆ¦?c—ϊ™υ5QυΘQIυΨMΥλWυZY=ήΤCύ}ϊlΝ>ί―ρΘ#ΐΠ!Kp–ΰ",ΑDX‚# jHg―CΘM<€#‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ Œ6ώάΑ ‘ϋHFtIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/roi_getarrayregion_inverty.png000066400000000000000000000311301421045507400315600ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ IDATxœν{|TΥΥχχΙΜ$3ΙΜδžP)h[kΛ§Φ^i MEQ”›αΠpΡXTΌ`λ΄ή/΅­P}½TνΗλ£V¬Ά}>ΟS/΅ΆΎήPΉ˜„ά&·™Μυύc=³ήΕήϋœΔ$'9„υύƒΟ™=gΜp~³φZk―½· Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0Μ±…1Ψ7ΐtO*•‚Γ8jž—{°o€1υ4nά8!ΔgŸ}6¨·σΥ`a9ΤΣqΗ'„ΘΞΞΤΫι%,,’:ωδ“…---»γƍK₯RGKoΘΒ|ΠD€$:;;=ΟΐήQ?ΐΒ4PO₯₯₯ΨXTT„Η­­­pPXX87Φ/°°ΤΣwΎσ³sZ[[322πecc#hλψγ?ZzCΦΐ’:逓ΰe0„ƒ‘#GξίΏ_QPP€VJΕλυڏύΖQ ύ£4Q#FŒBψύ~x‰ΒΪ±cΗ¨Q£πόφφv|Έ‚°O>ωΔωF‹-–]P—ΌΉΉY=!οέ»Χβ YYY *!D2™€Ϊr>,¬~υ4~όx·[ί«Fy_|ρ­¬¬,‹λS{ζdXXύHjόψρκ[ωωωΝΝΝ.—K‡΅ιx"‘SC…’*++sΎ ΟΒκ+h’Ύώυ― !233£Ρ(=‘³³SέP Κ³Ύ~GG;\LV/AAδηη !ΚΚΚ€8 „())—n·;Σ@p>Ÿ^Rγδυzc±˜]·> 5Ώ‡€z:ώψγ…‘PίBm}ώωη؈ΒB€°$Η …%„ΘΘΘhll„cŒι Γΐοέ»ΧΙŒ-VOIMœ8Q€φ€eee`„υυυ ­d2©υεΓα0hK€>| gα|XXέ€& $%‘›››L&->>zτht’΄)ƒ.υƒ­­­Α`>‹ͺ:Šp-\PO˜ΐΔ¦’³³S䴝φkθ­SF΅n;4ζεε !ΪΪΪ°½ΎΎGŒΖoΧ]Žν ΩbT %ˆ>ΪΫΫACuuuWΐTZ²œœΤ–0ͺƒA‹D(δKΝcŽΒ‘zx@Rcƌ—Ψ=ΡτΘ£φΖοχCo5vμXl€]dGGŒŠ΄8€‹l€Β£rDsHΣ`Ž5ZGΣ(€‘ͺ(mmm.— δE#5ΐησω|>τvοލo>Βαp8FUIt₯ihhPί­――F£ͺ‘ΓL„Εˆυ sU;@S1vμXL€Ώ ΗΪΈβrΉ233Νή…Ϊ’Ρ£GοΫ·ίM$WN&“ZQœ,)ΰΨ-4 Ž6z₯@ πΕ_@KKK xΣβΘ~ )((hjj‚ƒζζf‹‚Οo|γBˆΪΪZxιρxh"Tuη‰φΕΪaœ’’gο+Β‚g¦Mεζζ†B!P υu(hœPCΪ/…GžJ₯@RZΐoiiΙΝΝ՞ Vbω|Ύp8lvA'0Δ},t‘‚Α`0TKΪΫΫΫΫΫΡ}Ώΐ·FI£½x4ν"¨'Œ5 Ύ]™6l˜bμΨ±cΗŽΥ†xP/ χœ““£ύφ’4¦ό 24……zš0aΒ„ T§;333‘HH:[υχΏνφΫg=υ”Bv‹@Iν·»\Q£FΡΪ=‰={φ`όxβ‰'Jο‚ΦΥ{†Ώ+ž'NœΨνHφΐ3€ΊBκ’ Q΄†aTΟ<Ήxρ>xεΚ+/xβ‰ΗΞ;O= κυ¨CSZZJΣZjŸ5jΤ(ε‘C‡΄† /%„@·½««K*Μg P(dΦu:!",T0,..Vί=ξΈγh ¦π—όχŸ /¬ίΉSρώ_ώ"Φ―ŸχάswΚΒοχk«@LMicImγ‡~xΚ)§ΐ1₯£Z4«ώ³ΞΦ"Ž‹&Ύh’hǁڒΖΙLXΝΝΝ5οΌσθΌyuŸ~j\pψπαΕ&Lέ΄ikE•Ϊ*ψ f‡  'Σψ3ΤbΡ -ZuF«££ƒzWTXΡhCZ”{cc££bΓ£ΗBκΈ4 ·€ONͺ³Γ+Μ{ψαGηΝ«ίΉΣ0 Γ0nΌρF!D]]έώϋ?kjΞΩΌYύvΗΣ‚ƒw˜;@Z[[ΝςLPΖhOˆD"0D‘œœ³D‰£8šΊB΄O§Ÿ~ΊτV €Ρ²²2‹±6Tη !^Ϋ΄ιΒmΫξώΑ„ηŸώ¬Y³ή~ϋν—_~Yqξ½χ>SU•vΘzF²ͺ0kJJJP°Žο233ρΫ½^―¨Ž=ϊ΄ΣNsTBλθ–”…R`0ˆž,­NqΉ\‰DžŸΆ&³«Άφ©‹/yσΝΓχί_QQ1qβΔΗ\1ρΖŸ©ͺjά½[A_ze,AΞΝΝΥJ Σͺ™™™ΕΕΕf₯/ͺN³Ό‹Εό~Ώ3ηI;ZX4ΚΣζ;;;©‹£’L&Ρ*Hin‘Ά@‘’/ΌpρƍӾρ !DeeεΫάύΛ_‚/oF4EM@ŠU:‘¨¨Hk;›šš ΰO3 λhηŽήa,C£εόΒe' υ4|ψpν">Ÿ~ϊ©φSΨΔb1νδ ///•J‘kξΉη~ο”S–̝»δε—ο:γŒΛώρs~όγΝΏψEWSΣφνΫEΪμΑΙΙdR*]WΑNM­μƒ4)vΔtζθ:t$»Q[h***^yε8ž0aŽ;„@@kη!˜wΗΒ³ž0aLš4©’’bΥͺU΄]f¦,_ΎόˆΗγxK*’ΤΦΦF3Φ•}†aXξΥΥΥ‘Φσςς€‰b…t[M?ΐ8Βy7« φμΩƒIυ466jhŸ£ͺΚΛΛO?ύτU«VΑKΜbϋ|>5e lι₯ΫΆmK₯RΰΛγxK8Άx’Π+QUIσ‘ՌkVVΌd2i1ρ F±°γΓ³ΈΈΨ ±‘#=¨oγ™3gbϋ―ύk8 Λ‚Α ­Ή£βiοžϊϋyyyεεερqν΅Χ’›L»$*,ΪΗaΚ`ΫΆmO<ρΔφνΫ³³³©CMϋΎ.ˆΜ^βρ8ώ Ψ₯R‹EΗ=©°ΐhy½^:"I=*VVVց]XްXj'΅=ΪB%τ€u`AU—_~9mτϋύT[Φ«ζ-Z΄θΡGΝΚΚzν΅Χ΄'ωε—pŒSœx©υh"Ξ3“ΐ{££L8pžJ₯πWδ„εh$¬C‡ !ΆlΩrΛ-·¨ο’3ΤΡсʣ…ΒΪψ «š&OžPΟ‘99όμ›ššš΄… 0pJ ‡ΓΪήP€νΊh aΨj{CGX,Jiiι]wέES”βββββbš­±^:qΪ΄iεεε¨ͺφ4Ϊ“½iπ₯tM͞={Α‚?ωΙOΰ%Έ8€ΩΕ›šš γΦNΜΜΜΤZ/Έ[¨s7SUF39qd. Λ JΣΐΛͺͺͺY³fM:μ%Œρ™=Ξai¨Hkσ΅)ͺ°ΉΉΩησ™Υδηηηηη9Rϋξγ a†±sηΞP($…iW\q§R)p&΄W8逓ŠŠŠΚΛΛ§M›Ά|ωrΙ*¨cˆτ‹‚Α ΕάT!„ΟηC=IQjUUՌ3Ύύο[„R€_³:κŒ]CCƒ™Ϊ  ΛkΏΈ±±ρ€“N‚­DPUτ—ΛE+R$Τ":n¨υΟ(Ρh4 UVVΟ?_]υ~'():?$fΏ“¦¦&œ—‘υάnwCC„ΤΏW,''¦b›έΉ­8KX†aΰ¨ώƍ±X@J ΰ[ΐϊAεεε“'OΎι¦›ΰέ@ €+Q@ ΉΉΉΦezϋ` ½”ZΘΪ:σΜ3…ρxά,C†φ [Τ½…š#‘ˆΫν6[~mώύϋχο·~œ%,%•D"Ρ‘†ΆƒͺΎϊκύϋχ£¦Jϊ;­€h ΥΏZ{{kΦ¬9ϋμ³Ο8γ υžα ++ΛΒ„@Ξ“§nw*€·Q[[«.Δ5XFΛ‰ΒΒ,¬#Τ%:π̘1γ[ίϊΦ‚ Μ–8Bό¬ΊDq[[ν.©χ½oίΎCi΄Wφω|Έ>ΗΚ•+Ο?ό)S¦Π‰vε΄Ο…<Έ_it˜σhfΐ’ih£v‰ΤΔqΒ2 C8@Ϊ’<ଳΞͺ¨¨˜?>ΆP7Ή8φ³Ρh4‘HXx`R)9ΰώ4΄ρ’K.meddX”°Š#=zΥ@¨‘ 0ΫΫΫρgfΦ'ͺ768:k˜½ VgζΜ™ηœsU@Sθ Ρω}Ϊ:*ήσ`ցν‘ύVK–,ΩΊukWWΧK/½-΄τΟ0 )C܊z’’ΦΓ̏G a9šNh9Ξbθc=ψΰƒΪ·PUΣ§OGU577[ΧΉγΗ333΅ΕΕΕΕZ―²P›o±;τw ΗeΛ–Ν™3§’’ίu§‘ΑΫƒΦ΅Ε‰DΒbqΚdυΚƒ‚…e]%πz½λΦ­£³ ͺ… ΒΛΒΒBκ}Σ™9‚ΜnΠ~γ°aΓ°»Τζ»aΙ+8¦˜θ.UΑ-[Ά¬²²rκΤ©πΘΝ@1KΏνŸY ‚ΞΤ"ύvΰΐιΏb`ptWhS8K—.=σΜ3.\H§I©¨σ€>ΛΊl0''Ηβ™Α£Εq!uωΖΞΞΞ /Όπ±ΗBHυς@ΐzψΌ±±Ρbƒ'ψC΄W€ €α|œ} Έ7t’Ερ`ν•aŸ&3Ϋ&.HDMVΑ€7ͺp’t¨Ε»ς΅―}Mj_½zυΑƒΛΛΛkjj‚Α Ϊ΅Εb1œF§ΖΓΙπ<΄špΉ\΄/λωF¦ΈψΗώύϋ͜ύ… ώα―ΏώΊΕu€άΰIt·Xl•h–{Ί]ομΖ‘ΒR5Œ?~βΔ‰555»ΩΩΩ… `Π?£‚ θvŠ“πz½h?΄γq‘PCϊ¨bόψργƍϋψγρνΠ]T¨ΎΎή’[‰ΗγuuuXΚ"mύJƒ-Τ κx9Ελυb ]uuυωηŸ?mΪ4‘ΞΗjsCh”Cpυ6mm`aa‘żفᨱX ͺ_|^‚Ι1[΅B‰F£`΄JKK-¦§Š#§κ«¨λΌK›£Z|vΙ’%>ψ`8~ώωη΅'@0Ζ•NΘq»έΪ5ώT*…N•T‘ >|ΐŒΦΡ!¬©S§fffΎψβ‹`!΄i*Ί>‡ΠΕƒ©T ‹¨θbΖ¨NΏί―•tCSγ…Ι iΈΞ  Λ“^tΡE0TυόσΟγζυ ³Eπ"(ίΟ?œξό#ΩZυγŸΣr΄°ΐ…_Ώ~=Œ.ŸuΦYΠ>wξά'žxO³žΐžH$¬³Υωωω¨Bυ ŸΥ֚‚9·Μςt—9ΌσΞ;οΉηžBΌχή{Z₯βb]]]9zμτρžΠh ξT0ηϊX¨JϋV]]+,%¦₯ROqd‘•Χλ…’K³οuΉ\ψlΤ%΅‘ς'ΙQ-Œ‘Œ9rνΪ΅ΥΥΥεεεΪαΌΞΞNt’¨¦Α²B ͺΕ’§0σBkD‘74ϋ`?βh‹5gΞ!ͺκε—_£UTTd±ˆTMYLE€»•tΕλΐήεrω|>³2΄¨Hzeq€©ͺͺϊέο~'„xφΩg‘%[$Ύ4»ω††‹jτΪΪΪΪΪZŒh<ΈbŊ9sζœuΦYfi°–––Ά4Ϊ<Ξ΄VγApηLUΒiΛΒVu;x‡‚ ;> ₯₯₯Zο rTκ²`ω€uϊmŽo ­,’πρΥ«Wc)φηι₯PGBW“"33ΣΪO())Aψ駟Ž?^1i$»{CY, U†k»γjlBˆΥ«Ww¦Ρ^s̘1˜qΠn/˜‘‘‘}0Zτ§ρx|O3ϋ‡…VΊn‡d\W―^=cƌψΗ"=ϋΤ’ΆX­s§©0φ³βrŠΕ²P•5έ@sƒ"½Ά±mmmsΏΐ,‘οŒcAˆ…m«­­΅ž;ZXXˆ)Ί*TI¬^½ϊΞ;οlooΗυεΘ/ΰΖ™Ϊ<ύΐίÈ#ΐh•––’€ΎϋέοΪj΄a±Ύ’ͺΪΫΫo»ν6‹wΫΫΫi‹-iσ–ΤgΟΞΞΆ˜Χοσωβρ8ͺJš 1" ΆΠΗvΒ 'X˜ΈŒj/[ΆlξάΉ?ύιOι»pΫκv¬π-9iΜ.Žf[»‘΅ ΎΕκ‘ͺ`xGπ_ΌxρC=„uΊΪΟb%] ‹’Ν¦β9‰DΒb&t·‰G΅7€«Hj§-[ΆlλΦ­Bˆ—^z)[ lΓπ(ή?¬•Η.— e4πΣuΩb}ΥP~¦ΈQO ²8Ÿ~ϊ©vγ!D(ΒUσ΄ŠΔ©βΘ:>Ή¬¬,­ͺ@4`šΥhηtЎlΑ‚\ A€ΊΊ:Γ0ΜVTγGι„”ι)§œbίπΞ` «/ͺzυΥW-,?8Ε())ο¬]βI$Ϊ8H$‚³OUKι­f]_ίΈ\… šfψΊΊΊ:ΥΏ„¦ΤZ Zƒ”{^ΫMX½P•a”Ά9B`B+ ZΈΥΥΥQΕXLδΒoΔγ––‹εh ΓH$hΜΤΞb‘Θ0i3d ­ο}ο{XϨ픉ζθ„ρ"‹©cv08Βκc(‘››‹=5cx\VV¦ύ‘0†hζΗh„‘&§6hq»έΝΝΝfΫκd@©8½££γ‚ . kRR`·Ωθ, ŒΑ―zZcc£}Εƒ ¬ώRΥ³Ο>k½bLVVξΙ£ΞΛ€>K;.•B< uόΡXF£Qm νMvvΆE$Κρ‘CR΅Υ’Fϊsθ½YwD"‘p8l]%Ϋw:*μ£ͺ 6Tη”Y³fΛ–-pάΦΦf=ŒHγ,u™πϊaΫ7qδϊŽ‘H€Ϋ Uu‰D΄φPξL[χ2gΜ^x!‰lίΎ¦R¬γΣ½{χZΡχ;*¬ώνUԜagg'>H σ#NΩTi΅‚TΚ.b·«hmmEG•2¨½.š"Uψ,\ΈpΫΆm‚μΝ)„H$θ™iGœΊΊΊπ‡qπΰAΤVӟpΒ vdJ+μ/U†ΑυΧ_/ƒ»³gΟ֞«²Pο}πd2©l³gΟ88xπ Eu LΫ§6†$Υ…’ˆΗγAœ΄ŸυΎ¬¨uΤΝ’E‹Ξ=χά)S¦@e‡6!Έf₯Ι i΄οφ{»B»½u!DnnvnlΞ}„ΕΘ+Ψœn%Žœ ¨νF±ΟJ&“†aX ΓνάΉSΓ Ε‰ͺcG«X-φofϞύΗ?ώ±««kϋφν:"ψΠ–Η444¨_ϊ·7΄ΡbΩ­*Lb©‹³ !JKK΅z―Ί$³Ημ4fyv­KŽIOηέrœ°U©T κ!…]ttttΐBBZŠΖ‚hN•φn‘Hl5TτΏž:UψxΠbE"šΡxϋν·ρξsοή½΄T‹€T*…^#ΪzέQŒvθ@`ΫΆmΟ<σΜ[o½…ΤO£4ΡαΒH$?‰}ϋφυ£Ε²₯+UIh}œζ™t³Rt„N‡—Ά§‡­ΦΦΦf]K q |ss³Ά P;ησωΊέ‘Πλυ^rΙ%0Ο‡j«dβΔϊ?ƒΪe,`$½π/=zt?φ†ύί€ͺpW˜ω©’-FΓzn*ΤδP+%• «5ƒt‚Νˆ#΄ίHλ)hwt’{jΞ1cΖ₯–+V̚5kςδΙπ2XZzώζΝΥϋ›8rcNzη˜J5««ξ#}–Kω₯€ͺ ΨAmŸ?>f&΅΅10NΟ’*{Š@ `Q“ώ™Yιίώ4΄‘Fm3m„.—ΛνvkΥ t…Vx+V¬8χάsO;ν΄ΞTjωλ―?>sζ+W^Ήΰ…θΗ‘ΐίlΤyέΊufwΥ ϊdχV>χάΓ‹…Σ`ΰ{@ZSZUU… uηιcΠΪΊπ5u{Ρ»‚ί4]¦ 2Η'P@]Σή:θ@ @­Ί9xπ ]Yo•ήίrαώϋΫΎύνKƏBψύώβ *n½υα³ΟΖ“ioξΧΕ_LU«VυKoΨ{‹΅aǎΗWΌiί>_n U b΄‰μ /Hc#Φ x455>|ƒ85K†β τΙωύώ`νΕsrr°δPλ¨νΫ·O›<ƒg/mΎ’]XψοSNρ½ρΤ†ΡπΙ'Ϋ―ΊjQzQ1‰‹/ΎTe½tή7μΨρΫ3Zkk/ΟΛ»£₯ε½Λ.›φӟ°ͺ³Δ&hΕοχχ|u(Ί†‡υΌpzΜ²θa§6fΜΝYt΅PΒ•L&=Oqq1”ίx<·Ϋ——Ϋz<žT*C.—+???P\|Ζ₯—ώ}ӦףњššύθGO=υΤξέ»=}ΊϊώIDATοΨ±ύͺ«.ΨΆνι₯K….—λͺ«ΒοRΧ›8ωδ“ϋŅ@U΅Ÿ|/±bΕ/yδ[Ψ@vΛ–-kΦ¬l‚Φ%“IŸΟ‹Εΰix0πΨΰ ‰ΐΌ…Ο5 ΒR3°Λ|Δ0 8€«Ήέ Η“L&αΏίΛΐBπEπψΥ”xšX,&'“I(ξƒ—ΰ)& Γ02²²J‰SO=΅΅΅υ7ή(//ζ™gΰ―ξ<|Ψ_Rrι₯—ZόΧ…Γα‡z¨ŸΕW–ΛγΉζύχ©ͺ όZfζ--ןp‚ˆΗα?ώΏzxΠ‹ΰΑ_ϊΧΪΪΪwί}aκ\FFΖ’E‹P"ψΜ‰}`π„ΰ_l„ͺ΄>B?ήΦΦοvuuΑhβΛ/ΏΔ«…B!|π00‹eggγχΒΤ/BHm!MξSW ŽΑKΛΜΜ,™8ρ¬ΫnΫtΪiπξψC!ΔκκΐE…ξΎ[€γΊ²βχΏ=το τW»Φμ-[ώρΘ#{ίy^~ρΕ#FŒΛœH$R†ξθΐОτβ#τΰ/ωΛΜ™3αUVVbHxο½χΒ―FޱCΔ|:κ£ΞΎšΕϊΟλ»iίΎkG†Hπ•W^ΉτKαιnΨ±cσδΙ­ζΛΜΩΚ{ο½Χ]wέe—]&„8pΰ­ΪSέ¬T*ΥΡΡa6Ÿ7΄Pδ)½k†Ά:k’ΰ€?΄OښAΗcΆž€’ώ㏳ήxγφ}ϋZ·n₯ͺB`―φ ΰZYμΛχ•θ*οhiAm’Χ5πΠYΒ³fΝΒvτ3P^―Χl’τtpŒO—vI999hΨ¨iA£EΗ©°ΰA‚CsHuΒ‚Έͺ–.™ŒwB$h΄ζΝ›‡CUξaΓ|?ϊ‘kΔͺͺ­[·n―  ²1pΑΝzMoœwˆQ[ƒ*ΐbΙu!D$Α"ˆόό|uΪ`aa‘vH(Vχ°€Πu"΄σc΅Σq­Ηh4j±J&Γ΄₯σζΝSγ΅΅]όglΟΡ]½l±&lϋΎχN/Σ  ­«†_―9AU”|σXf5μHggηΘ‘#Νή…l'Z5ϊέν‚- ˜ajmmU“ιh„¨C γ‰πEΪΙ₯9.Y²^jΗο'Λγ­z<0ZΪu+E:Ϋiƒ`±€Λσςͺ_zΙ!ͺ‚‚$j 'Igοΰ¦7ΒΔφΰͺ,`₯,Φkf1ΰmh(ώ ΕοχγCUWΎ„¬vςV(ΒΚ3ί΅ξ½€KKφKxΨ§κ†m••aΛhΰgf–ιFgH[η.Lͺ πμίΏŸ–―HΐZίrTΝ]#¨¨Θbu€λ»NίƒΊρlΡƒšjΡr«a¦m3έΰpR©VDΡ…$·lΩ‚σ°Qλ 2΄BW±’ψ¨Ϊ’‡Δ‘i§P(„^<ΊΖΤbiηaϋάΉs鏄ZΦύχί/H*„Š#++ 'š.FmνΪ΅Kτ·žΑ_ΖΘV@Ihσιφ‚TόiΛΉΪΫΫ­§΄΅΅αS—¦Κˆ΄¦M:Δb±nWRΐρ±ΦΦVόά·μ‰'žψμ³ΟΤO_gg§Φ‘= Ϋ$ MaE£Ρ;ξΈΓ¬D ―€jtΥ›8q"­D"a‘Is…5’”E:QIχ+@ζυzΡ%Χjύ†n;v,7N«-ν½†εΌIΣW\xΥHΤΤΤάyηBΗc±Β NλΪkη§Ssb½Έ™a^―ΧΜm_Ίt)`Ÿ%iύ†n°Έ8<-ν <ΐzB†”°ΜhooΗ'J½Zθ8 £Τf‰κκκJKKρΩheAhŒ a+!D[[›vφσηΟ·ΎsužΘξέ»ΡhY@€2ΐzB†²°xΰ‹'GGp…nί/ПvΞ]vv6f΄e^x5šζ@Ξ9ηνΑθΒ~fΣΏΠΗ§ t³φγΓP<\+Y²d γχ€Τ˜”˜P{IšΫ4Ϋ4™5kΚBͺ2ι<μ[i§²ΠŠCτ„ 5aY@g}aŽ«ΑF£Quu$J2™μκκ2›κ£ŽSXYYi}owέu—E5"8^x΅C‡aV7wˆž!.¬G}΄ΫΊVt{ιŽH[[›Φί/**jhh°Φβ5Χ\ƒΗ8:Ig]C—'d5Ν€όΡG ηι ‚Β2λ §OŸώBzΦJ °v’θΈ¬΄“HΥhUλNUWW[ά!ήF^^žZŒΛ€k―Œu¬€€!(,kT‹ϊ:Ϊ靔ΆΆ6‹I€Χ^{­'₯Ρι©ωωωXlŠ^αv»1j ―ιp=!GΗ]φj΄θΐΊπ4QξυzΡιΦΖbTLτς +V¬dρ:Ϋ„υ«_ύJϊFΌ·––Ό7€°Π¬-zB†ΎΕjjjŽIΓ |8”j©ΨT ε†Έ°°Βqhι]”•¬[·Ξb1¨­3CλΨa±„ŸCOOΘPτ†j‚q͚5›6m²ψΰβΕ‹±ŸΒαd‘ΏύφΫΕ‘y|€ΎΎžfΕΤύή(ο«2”…₯’šκEΥΤΤX¬Χ i1 IΪ‚ŽR›n=vτ„ ρΏ,NΟͺ©©ͺœ,ŽL5‘Ρ‚,”  Μξέ»±“CΨ…κ–!n± 7Δ&dύϊυ›6m‚Δ¦vˆPŠ.ΐO±Ψ»$u κ βΒrΙ%—“UŠ `>ŒB;ί§ˆ !>όσ1cΖΠwϋwΥQΝ1ρχƒΡ‚EΖ.ΏόrlΏι¦›ΰ   ΰη?9ΣτΥ­ZΑF*ΛzBŽ‹e±tυ$ttžpm4Z8%υ€r¬ ΉγŽ;ΐhmάΈQ+)Θ‰ΓŒ ³β`λΙ‚cBXΰΒCώιΦ[oΕ›ͺͺ*iοSO=U˜L`ί΅kΦ+³€Ίε˜pλ­·ZΌ[\\ŒΌ€ 쬧^0h»Ψ"›7oΖc‹½*a?˜Β`½£8£r¬Λ0 Μ²χςεΛΥ“ί|σ͝;w²žϊΒ1Τ “Τςζ›oΒ‹©ο+K—B|‘†MT?rlύ'¦R©Σ«Uλ_‚V’[]!’b=1ύ‰Ωž9 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 Γ0 £Εθυ'S©T?ήγL £χ ι%,¬!O_qF?ήΓ ,,ΖXXŒ-°°[`a1ΆΐΒbl…ΕΨ ‹±c ,,ΖXXŒ-°°[`a1ΆΐΒbl…ΕΨ ‹±c ,,ΖXXŒ-°°[`a1ΆΐΒbl…ΕΨ ‹±c ,,ΖXXŒ-°°[`a1ΆΐΒbl…ΕΨ ‹±c ,,ΖXXŒ-°°[`a1ΆΐΒbl…ΕΨ ‹±c ,,ΖXXŒ-°°[`a1ΆΐΒbl…ΕΨ ‹±c ,,ΖXXŒ-°°[pφ τ’»οΎ[j9ωδ“₯–)S¦ Τν02l±[`a1ΆΐΒbl…ΕΨ ‹±£ΧŸL₯R†Ρϋ[pΟ=χH-ίόζ7₯–I“&I-|πΤςώϋοK-]tQŸοξ’/˜-c ,,ΖXXŒ-°°[`a1Ά0ΠQαΦ­[₯uŒοΫίώΆΤςα‡J-O?ύ΄Τήήήν·«7|Χ]wuϋ©cŽ ΗΑΒbl…ΕΨ ‹±c ύΎπΒ τ₯:’7lΨ0©εε—_–Zήyη©%#Cώ$ ©ΕεrI-­­­RK[[›Τ2rδH©εΪk―Œ‚£BƁ°°[`a1ΆΐΒbl…ΕΨBŸ’ΒΓ‡«τ₯ί½υΦ[RKnnn·ί‹Ε€–P($΅J-‘HDjQcœ¬¬,©%™LJ-W]uU·w8$ᨐq,,ΖXXŒ-°°[θ“σώ§?ύIjόψγιΛh4ͺ~Jj ƒRKcc£ΤR\\,΅ΤΧΧK- RK^^žΤβχϋ₯ΥUοκκ’Z:::€–;ξΈC°σΞ8c ,,ΖXXŒ-°°[θSTxΕWH™™™τ₯Ηγ‘NθIžŠ›¨Ρ₯:€“““#΅¨hSS“ΤΩΩΩνͺΧΉϊκ«Εƒ£BΖq°°[`a1ΆΐΒbl…ΕΨBŸ’Β΅kΧΚ—;2ˆ ΩΩΩR‹:%ΛησI-κ_ss³Τ"€B7¨^Ή'K‰¨‘¬z?κ΅+―Ό²Ϋ+;Ž ΗΑΒbl…ΕΨ ‹±c }ΪV.??_j‘ΪԘB Υ1Ύž,’NۊΗγRKO"5rTλNΥoW―¬~ϋΝ7ί,΅\sΝ5έήϐ-c ,,ΖXXŒ-°°[`a1ΆΠ§¨P-Ρt»έΦ'¨Q˜a©η¨3ϋΤθR½Ž:«QmQΏK»μφΟΊΘQ½Γϋξ»Oj©CΆXŒ-°°[`a1ΆΐΒbl…ΕΨBŸ’Βp8,΅Hα›:G―'q’:O‰SγD5RSΗΥوjM©Ϊ’^G/Ω“θRΥE(oΉε1$`‹ΕΨ ‹±c ,,ΖXXŒ-τi^‘ΒHa`Oβ;΅TοΌ^o·ŸRW%UΗοԘT¨nV ή³'ͺχ£F κκ¦κͺλ―nήΌY <―q,,ΖXXŒ-°°[`a1ΆΠ§±B5¨‘Β·žΜΘS#Gu]Puu5Nμɘ£©©£™jœ¨l£n™Φ¦ͺ©κ†Ί–©ΊΗϊυλ₯–M›6 ΗΓ‹±c ,,ΖXXŒ-°°[θΣXαΆmΫ€F)ΘRw#WΗΥ4u]P΅bSΥhNmQ«:ΥHMTQΗΥF΅UI{²γFkk«Τ’ώoάxγζ7Ϋ{x¬q,,ΖXXŒ-°°[`a1ΆΠ§±Β/ΏόRjQK+%ZZZ€5Β*++“ZΤΡ:uεRuATuτPΥ«'ќ:«Q@UΤρD5ΆUžμηxΫm·I-λΦ­λφSΆΒ‹±c ,,ΖXXŒ-°°[θST¨" σ©Q˜:V¨’FOݞ£Ž0ͺsύJJJ€u6’ϊ©žΔ’‘PHjQcΙάάάnΏKΥ±TuόN Ο7nά(΅\}υΥba‹ΕΨ ‹±c ,,ΖXXŒ-τσΌΒΒΒBϊRγSc%υ"j|§VQͺ#ƒju¨:p©Ζ€jKO£F…jΔ§’ΞaT―£ŽͺQa·²B5oΨ°AjΉα†Ί½N―a‹ΕΨ ‹±c ,,ΖXXŒ-τi^aMMυ9j]₯Z3©Ζ/j¬€F—j¦ΖSκNκύ¨¨:―P u…nlN½5ήT.5βSΧ¨Q[ΤΏ’'U¦j=m^^}©n“Ασ ΗΑΒbl…ΕΨ ‹±c }+TΓ.)πQ#‘žμ1‘ΖJj\¦~΅zŽΡτdμR½²Ί&ͺ9ͺ1©»©£’jύͺZ+«Ζ›κ‘ΊŸ…:[³Ϋύ5nΎωfΡ°Εbl…ΕΨ ‹±c }ιΗϋ`œI―‡t†a†a†a†a†a†a†a†a†a†a˜cγaτ$lOIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/polylineroi/roi_getarrayregion_rotate.png000066400000000000000000000144721421045507400313700ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœμIDATxœν}lΥUšΗOΫΫh)}DD„‘$Ύ%Ι¬eb0™Έϊ—B+½ ˆΈq4+oρ]%ΞNΡ :b@MŒΙΨ¬‘Ιn„ΩΙθŠβ¬RDA‘w‹PJ­½mχ#ίύξ9z{ιι}ϋ~ώ ‡§χχϋ[Ύόžη<η9η#„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„ΘςR݁‘ ――/Υ]Θlςς¬“Hˆ~€!γƍ³ΖΖFŸώyΫ>|8ŒΞ%ƘoΏύΦ6ΪΫΫa¬­­΅’’"Ώϋξ;Ϋ(++ƒqΨ°aΆqςδIΛΛΛm£«« Ζ‚‚ς3gΞψ—;Φ6~ϊι'#‘ŸAOœ8#ί Ε72ΖΤΤΤΨΖ?ό#~'»wο6'?‰k„θ K!W\aii©mΐΣς&;wξ„±ΈΈΨ6vνΪγΕ_l#GŽ„1?η–===ώ'ΩC>}ΪΉΔ3bΔΫ`·UQQaGށ1".αϋ8pΖ‹.ΊΘ6ΰR1………hγ›²£?uκ”ΣpJ½±D$, K!Wb¬ƒΪΖ%—\γΧ_m» gΓαΤ±cΗl£ΊΊΪΏωΡ£GΡξξΚΚJόρGΫΰŒŒhΚ\p4#ΊaŒΉβŠ+lγ²Λ.ƒ±££Γ6w0”­ΰσύVŽ=Fώ@θ%‚ a‰ HX"Ήc5Κ6Ύώ{1kΑSΒψ“ˆ·B1lD"Šσ@ˆ·ψA­xΚ9-d€ ε΄ͺͺͺ`DnŒ'ς0QΓΡv˜>b#ζypO“Τό £7–‚„%‚+uœYΐT κ ωˆK/½ΖΆΆ6η§Μ”)SΠ†ΫβJΜσ°+ΔΣ95€A>§0ΰΧ8q0fΜΫψζ›o`Όκͺ«lγπαΓ0²§F!gP²Αι ΞΛ$ήX"–‚„%‚+1fEΈ\SpτφφΪƞ={`Δ8Ÿ“(2Α} ₯0pCaGxΈ"0cΜώύϋmƒ'”8`θ3^ΘVL:Fώ"ˆ9†C ')8ςK½±D$,„œs…\νΙeΎq€I0Β—ΕM²sΒCw -))ρŸ―„Κ Ύ';8€d9Gυ|ΧΔ’PΦsόψqΫ@Άέιΰj$ΠKAΒA°Dr%ΖΒΨ᎑ΐ‹Ηή8xI'ΤΪϊρΗτΑC Σϋ;΄iΣΜ’’Q·έ6ωψρΞΞΞ+VΨΛΎ}k&OŽ‘β‡˜'H?kiΙΛΟ?yπ byi+΅ό}Γ†εΏϋ]εŒ|ϋνΦr_kkjUe’›ω¬₯ͺ²ΤΥΥ­[·nζΜ™ƒΤ+1.\Ψ±gΟ«--7<ψ 1ζΎΦΦΎ:΅ͺ2ΙU7τΡ@€ω{ Γl ¦ eΈ@³ϊΌξWάCΉ8AhΕ“*ˆ’xž—sΝ&ψh y?{USSSIIΙ£>jŒ©[°ΰ7όγΏώβ±.Χΰp έγ)#Μhρξ˜ό;I‚Α¬Η{kˆijjͺ¨¨xϊι§ν_·­_ΏyŊ”Ώ«,ƒ\θ'm VUO<ρOμΫ—’ξΈ ~‘_zϊDBΑ ξ0Μf―‡CΏΨi"ΑΓFΞ,ΐ—ρ'αΛψ /$ργn?ΙΕp[Xv±lΩ²ŠŠŠ'Ÿ|2//“p΅œyŠ›…b―Š ΈΚƒ}q)MΦ{+(PUͺ;r>BΥΌK[°ͺZ•% η!ΰb ikΠΙU™Π‹)'ήBo»ˆ6a" ωβ‹/`DUGH=πά0ΦbpΝφΔ‘]†φ†ΰ)DfΌβΤ&&’Ρh~~ώύχίοτ“χn@Ύ€ΆΰiAά?ξŒoWwž'q‚/{kPˆF£=τPͺ;’(C±PΪΊ@¬ͺ›κŽ ©ίΡOΪ2gUυΘ#€Ί#ƒFκ…er^[Ω§*“>[E†Ž·0Pη RLΏΔ=‚k8‘π05 |9’1ήϋ‘?έ¦ ΚΚΚ{μ1rΕέ• ]Β •}r8w«H.”@\ΘΕ’θ3'&xž* βeΙΑχVCCCeeεc=–κŽ >i$,“cΪ²ͺΚΠ›~IWωDdΜ8#'Ωμwΰ4ΉΊ ύΰυx#\ΥΤΤT[[»vνΪaΓ†±{EꁟŽ[±SΓƒ8‚ΖΔ‰aδ:ξΐ+qΎοΈ”ιυΖ²dύ{«©©©²²rνΪ΅©ξH@QX&«΅eUυψ㏧Ί#aISa™,ΥVŽ¨Κ€aŒΕ bΌ…Ψ‚gK0&»COυ#ψΰJDcœDΐό ΨΘ©ΎΎ~άΈqΝΝΝ6ϊπΓνO±)œ‘I^Ŋ Œk+Π=~RΌ χε<γ„oΗ›fq<—ιϋΖ²dΝ{«ΎΎΎΊΊΊΉΉ9Υ"]X&+΄eU•ΉU{IΦ\ΈODœ“Λfσ<ψ.Κƒΰ47ξΙ™wψ όtι₯v§‘Έ χφΡNΈ?ϋ/ΈEή/ εΌκ9…ΑِΈ_ι~ήšQIo,K†Ύ·–.]Z[[›υU%c„e2P[VU™^ š™$,“QΪΚeU™L‰±˜δβ-LͺΔ=6’c2πy˜όαxWρLˆ]JKKW―^m?ƒ`JΗ3zτhΫΰPd8€§s8… ŒΘAp΄Δ³7Kβ:Ιߎ«U“ ΓήX–4o544TUU­N›ƒΈRBF Λ€±Ά¬ͺr*³—Μs… 9ŸΘωtdΜypΔυ(:ΰύ#[[[m…υυυUUUkΦ¬qφYΐφ“|&%ςώ“&M‚itΌ@:€/Η φzπͺμIΩΡΓ™ςipϊφΐ@§'Ι‘©o,KZ½· ͺTw$-Θla™΄Ρ–TερΒ2i -©Κ'ƒc,¦ίx Cnn@μΒρΖαΌάCzΞΨˆΔ†ΐιHΈ?ŽΙ4qy q+&8‰€ΐ›,0 bι)―GεPMΚ36X›χ$φδΘ†7–%%ο­†††κκj;(˜μ–rmYU=υΤSCσΈΜ"K\!8—OD™σπœ›†_ΰΜ;ΐΠ½±±±ͺͺΚΦ‚²3‚{εeΘh³―Α—ΤΕ­Β@%»l$8q€§³Ηφ“†<5;x―Ύϊ Fή†) ²κe‚χVcccMMMΦ,$N ΛΦ–UUV2D²SX&˜Ά€ͺΙΆ‹AΌeθxΛΈψW^y%Œ˜σαΨΕFf #FŒxψα‡­1ξ>ˆΈ:Α―Ϊ@ΖB„Η!.η Μ8ρ$•Ώη–ω»0ΰ‘<„β Ž yF+ ²YXζ¬ΆεV 555t6DjΙZWκκκŒ1S§N½›XUeΦΩ©%ΛίXΰΕ_άΌyσ‘#Gššš`D–}ŒX!8kΦ¬²²²­[·.Y²„·‚»aχ ΓϋG"_ΐ^ —sΝ²!œy‡Wεiψ/ήίή™—:Ζ=3ξ&Ό¨„7˜L‚μcY6lΨpγ7r`‘ ³fΝ>|ψΦ­[Cτ*‹Ιa™³ΪΠ%VU[Άl Τ₯,&‡„eŒΩ°aC\Ώ©κBHτ·œΡτυυ‘ΎΰΎϋξ[°`Α»οΎ{θΠ‘—^zΙy&ίΖ‹/ΎβŠ+°‡qάιΔ1œYΐZ\³€πάΞ³ΰΘΉ§p+N7ΰLΝέ»wȍp)²!,b¦ˆOΒvμΨ‘ψΖ{Π@/ΘΦ―_?wξ\T­cξόΓψ‹/Ύθ’‹²fg씐‹Β2g΅5mΪ4cΜ²;ΆΎπΒΒ·ή²?²ͺZΉreJ;˜ρδŠ+œ1c†mίvΫm°ίz뭟OœΈbƌڊбΧ_?iώόΡΫ·6Μ±α=}°σ"qΰ`Ο[? θk0žgW…$—"ŸΞΥ 8§“ύ#Š#ΨσΒ d1xο$)ΈLσ§N’+9aΒ΄½{'O˜`Œ9πΧΏNnk›>ΎVn Ή+¬ͺίώφωΊΊ›ηΞ}κ©§&L˜pηwFΪ۟[»φΦuλRέ΅l W\‘cyۘyΖΔέ²nΏ1aΜ‚!θV搄+ΜQ.›5+ϊΞ;Ά]]]Νηu―mo‡ΉsSΤ/‘ωL›7ΪkΫΫ‹iM‹ΙΰhKͺƒ΄%U‰AfΪΌyΡ?ύIͺƒOΝΩT§B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„BτKς§:ωGj‰μ#η~IXYΟ…όηξA˜"(–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–‚„%‚ a‰ HX"–B$ΥH1Ž₯ΉΉ9%=Ι2τΖA°D$, KAΒAΘ­Qα<ΰX¦L™βXͺͺͺΛO<°OYŠήX"–‚„%‚ a‰ δ%}e___^^ς— ,p,sηΞu,Ÿ|ς‰cωϊλ―KEE…cΙ‘iŸ ω'ΦKAΒA°D$, K!{F…K–,q,3fΜp,_}υ•cΩ»w―cρΏT~Ύϋί―««Λ±Όωζ› φ3ƒΠ¨P€–‚„%‚ a‰ HX"™:*¬――w,sζΜq,Ÿ~ϊ©cωςΛ/Λ°aΓKoo―cρKNœ8αX Λk―½f2 EΪ!a‰ HX"–‚„%‚£Β¦¦&ΗrέuΧ9Δ·cΗΗβΟϊ•––:–φφvΗβψb±˜c)..v,ŽεΥW_5…F…"퐰D$, KAΒAHΗQα]wέεXnΊι&ΗβwξάιX~ψαΗβΤΚΛΛK[[›c)++λχ>‰XΊ»»Λλ―Ώn EΪ!a‰ HX"–‚„%‚ϊQαΒ… ΛμΩ³Λ]»ΛΎ}ϋΛO?ύδXzzz‹?ΗηΟϊσ‰~M©,†±――Ο±9–—_~Ήίg Š΄CΒA°D$, Ka¨G…ώ1n7άpƒcimmu,ώΠ»ωγ2Ά.‘ZPK%r•oρΗ’ΎΕ'ϊOγ7LŠΠ¨P€–‚„%‚ a‰ HX"aG…ώž0Χ^{­cΩ³gcΩ½{·cρWν%2š‹DάCσόΟ$2Ÿθέό;ϋ#PΞώHΦΗΏjδΘ‘ŽeέΊuύήgPΠ¨P€–‚„%‚ a‰ HX"ƒ<*ΌγŽ;ψ―·άr‹σΔηŸ αΒό“ ρω£0ΏΚ4‘ΊS\–HύκPΞџ=τŸžΘ4Pέ©F…"퐰D$, KAΒAΈ QαŠ+γΥW_Νυw€ρ«CύΉ9<叞ό%NŸ>νXaωψΟJdŽΟΏs"–ŽŽΗ’Θάeee₯cρG©λΧ―?wgE£B‘vHX"–‚„%‚ a‰ \Π¨Π7>χάsόΧ>ψΐωΐχίοX***KII‰cρΗJ‰ŒέYύηο/κ/Ξώ¬ŸίC>§Nr,ώψΧ–?.σχ2Mdg›7š’Q‘H;$, KAΒA°D.hTθΤξL‡}ψα‡Ξ>μίΗ±Œ1Β±ψsjώxΚΗ=ωcΏΣ'Ήq’_›ϊγ?φΫΓαΓ‡χϋ,*Ξώχςc›6m2ηE£B‘vHX"–‚„%‚ a‰ \Π¨ΠYEhŒ?~(qτΖA°D$, Ka—‰,cPΞ“B!„B!„B!„B!„B!„Ήΐ(«N=Š©ΧIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/rectroi/000077500000000000000000000000001421045507400225035ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/rectroi/roi_getarrayregion.png000066400000000000000000000151021421045507400271030ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœτIDATxœνKlœΧy†Ο EJ’Hέ(KβEвš ΠMΧι*AŠ$ΝΞ› νΒ6oΊθΚΐ ―½0ΥEΠEΰMR$@‘U΄;ΕΛΐ’)²DΙE‘”HŠ”x™™.δpΞΌ=3žΎΔ‚ήg₯srζΏρσŸούΏΛIΙcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖΌX”:ώe­Vλβu˜―&₯R‡²£ΘY?žίxγ|ψώϋοηΓώώ~ωω“'Oš-₯tϋφν|ΈΌΌœ_zι%YίΧΧ—?ϋμ³|800 λwοޝeΑή½{σαΪΪZ>μιι‘υrŠΥΥUY §Λ‡λλλ²~ǎ†?ΠΓ‡›œŽ,0₯tθΠ‘|ψθΡ£|(£kΧ₯N)wόKcš`Γ2!Ψ°^\:φŸΪ‘΅gϞ|(>“8W\‘ŸοάΉ3NMMΙ‚£GζΓ}ϋφεΓrY«¨T*M~NζργΗΝ888˜ΕΕΩΏΏ¬Ώ~>€Ύ‘Κ%έΉsGΦ>|8ŠWΧΫΫ+λε!Σm]ZZΪϊχθΧΎφΈ\žτΣ&μΏ±^PŽΎός7ς“ωυ―‡ΖΗ#ŽoΓz9ϊςΛίzο½όήχή»p!ΘΆlX/Ο¬κ?Ύυ­gΓ Ϋ²a½@”J%±ͺg<³­'Ntσ\²V«;wnkψνo/ύθG{k΅ϊgffςυςy3ΑY¦«»kΧ†km₯bδ2¬V«²^œ}" <ε{lJicc£ω僧8γό * ·@_{ee%κGιςΞΫχΏΓχ·ŸΚιΝ7ΛσσŸ?ΥϋΓώύΥWσε7oήόΛ|yΏ{χξΦΏί~ϋρττ7VWλ_~?ϊθ£|qO~&–‹ζ§ω\Β€”D(Eœ|Χ» ›πg ϋχ7\ά\ΓώΠ‘½ΛUfŠqΑΡ£Cω°ZΥΚ·ώ–ζΎΎΥ)¨ΤΣ{όoϊηγ λs7SJίωΞγxχΟ~v ₯tδόω΅…±γ"2¬œΝΝ4=ύW‹‹υ0Λo~σ«|σ’Η΄oί ,xπΰi>jXΐˆΚύϋ ί;δύqΰ€ξ£G jŸ―Μαα†3NO7|n8~\/xa‘αŒ|₯-,4\αΔDƒ%ρ*¦Ο– RqύΣϊ‡ώέο~ωΓ>šž>ώs«=rώό7ϊΣ_|ϋ-ί>φ±^8>|ν΅ϊωΟχιΧ–Uuχ,6¬‘gΆUςΧV•Ίψ…ζωβΓΧ^ϋ׏»oύι/ΎΡ}«J λΘ‘#[ξιyςθΡ£……ϊ+PœqΙΠH)ŒŒδΓωωyY 1œ–šKHΘ…Nށš_αΣ§ Nž€©H„'!#ž”Αƒσ‘ψςτΝ%οexx8)gŸ ςΌšž_ΎϊθΑκΚJύ¦Ί=τΎΐT5xΪElX&– Α†eB°a™ ©Β<„R­VχμΩ³±Q-Φ>ˆf9vμ˜,XXXh²žδ±ΛQΖZ Ι€*”[ ΏSΟΞΞζCq ΡΐΡΡΡ|8==-λ/\Έ%όJ™,΅ΌΒ–„­€’ 0Ήe¦CΙ)$‹-Ηδ™s7ςάύ_[[ΫΨθΝ%HΛΎΤνγ7– Α†eB°a™lX&„BΞ{ώ₯Έϊ'ΆfΔ₯σ.žfΎΚ3$γJ°¬ψΒ’/Eg_2΄ψ‘\jD=pDωΟ‘#lΰŽ#"Dpc=9`^(ρŒ\ίμάΉ΄ΆVΚoŠ_κ;Ζo,‚ Λ„`Γ2!Ψ°L6,BΧTa­V››››Ÿ―η`‰Dβ&υ’NΔτ&©’‘"}–ύΘegMVιˆhβEHJ C:’/Ε"y"ϊ¨ςd΄…f›g‰Ι0H•«ΒZ­V«5œBŽ_Ώ±L6,‚ Λ„`Γ2!rήσBOΟ|Σ§υ(‡xΎ¬Τ}@Δ[—μ":ϋβ{Š·ΞΕβŒ3aKN!₯,ΙwžΞΈμ)"δŽ#δ‘‚vό’[¦Ιkύύ•εε§ωcαί¨cόΖ2!Ψ°L6,‚ Λ„PΘyτΣO·ώ½±±100P©Τ»―Ji}σ©©©|Θτ&ωξ,ή7χΧUœk:οβ\K7¬„ °–»Λ‡oϊΞaKšΥr‡ΡβΛ3K ΘχDY]]έ½{O~‘Œ%tŒίX&– Α†eB°a™lX&„nn„)ωX²‡;λΗE4]½zUHٌh֏‹Κ“ώ[μ§%ΩQ²@va‘ό'ΚLΡ‘ :I Gž·<‘”2IPcŽ\Rσ΄J₯²ΈΈxϋv} eiΗψeB°a™lX&– ‘σžG$Κε…½{χV*ΊiΗt$ΕΣΡ‹kŒHˆοΜJLF"0μ+Gΰ*R !Ύ6w`Κ‡,κ—4Ιb{ίζέu[:οΜ―ΚX«ΥίC@γ7– Α†eB°a™lX&– ‘›%φ«««++υΐ’%ΗMκ%ζ3>>. DUIˆ†5*rιΓF)©…’ιT’Δ”‘;bΠIf$я! Ι”ˆcJ§NΚ‡Βω./;v¬――Wri)Χ_Ώ±L6,‚ Λ„`Γ2!rήσˆG­VλννΝ#βzΣΥטωR² &R#}š[°³θE~"ξ?wθ;K©’ΤψίΈqCΦK‚—Δ”x|ρΎyGyVάξέΛΛkyS±±±±Τ%όΖ2!Ψ°L6,‚ Λ„PΘyΟΏ#—Λ ƒƒƒΥjύS²€CΙV} ή:7Λγfζωέrεβϋs?Qς<αSΈψς-wχkYΎ‘Ό§νJς›o6Ξ]"hΈΝLώLͺΥj©TΚ?ί³Ι@ΗψeB°a™lX&– Α†eB(€ σ©j΅:333?_P -χD])U:”™Rbί2$–‘%²” ^r <£θ8ΉBn‘":ττιΣωιSr|φΞ/iuuu}½!ΚΔ­@;Ζo,‚ Λ„`Γ2!Ψ°L…œχάU,•Jυ€+‰?HξQBm ΖΕ™•υ,=˜Œ€+q½xίβ§”.]Ί”Ϟ=›ΩZΤ†8ϋ υ rΛΌB ςάΏ?²Ι±¨ ¦ εtχξκΓ‡+ω50FΤ1~c™lX&– Α†eB(δΌηN΅Z­\.ηΙ=ςέ™[Ε7g!²μ•-ž,σ«δ[ΌΈΊ\/ιJ¬Υ–›7o6Ήžoš[€ΘŽŠrΑόR/ D p?ΔζO,5搭­­ 9R_ΓκŽρΛ„`Γ2!Ψ°L6,‚ Λ„PHζωLεςΚΚΚγΗuΥ#ρD E8Ξ*!£KDΨΌΙqKYΚ|¬ααα|(%𠘴l Ί―₯Š”˜΄cΊ•(k¦¬εμάΉΎΉΩ“_$ƒTγ7– Α†eB°a™lX&„mq‘RͺT:₯Jv““ΔΥe1…δKI/ΪΛ—/Λz©…l7’RšššΚ‡μw%)e“““ωιS-›Θ)€‘—ψζ©•\ |qχΎzF7SΪ—?dήQΗψeB°a™lX&– Α†eB(€ σB₯R‘*ΡDTp`—0 qHΘettTΦ‹π”4½ιιiY/MJhB:-Λ„δ‚™Έ—ο8B(œσ&Η A0n©"EχLάΛϋ"ΞW«{ς‡ ©‘EπΛ„`Γ2!Ψ°L6,B!η=Oί©V«ύύύλλυ¨‹xšL’ 6˜%Hƒέ€Δωυ έ°ΌΔSN(’‘ 3–ύˆ³Μ"™ζm˜‘[}3;;+λE―π‘ζ‚©V«-,,όρuEŽ_γ7– Α†eB°a™lX&„BΞ{žαΤΫ{»\.ηξ°dζŸ9sF~.•t]%KΎŒσ»ΆψΆβJ³R@δ;x‰Ώ/WΘέύΔωεv’$šo˜ΘJδ‚™ΰ%Ч――/ Sβ:Ζo,‚ Λ„`Γ2!Ψ°L6,B!Uψζ›onύ{ίΎχ^ύυ”ꍇ%†Γt+Yΐ=Ω%δ"ρ Ζd½h")‰IΉPdΙ)$LDkB>u¨„\D£ΝΟΟΛzQΎRψDΥ)0Š•ίΒΧΏώ«₯₯R©τχ[3όtŒίX&– Α†eB°a™ZxM¨Υjyώοπ·Ώ}ϋαΓz¦‹/ζ뙨/β±cΗdAσ -n:/ΞςαΓ‡σ!7‘υΜΟ‡a‹ρΝyF ιLLLδΓkΧΙzΡβ­³Δ^’X¬ρΟΒήyg΅ZΌx±ώ'“γςΙ'-υΑα7– Α†eB°a™lX&„9ο|°όέο6Ϋ:›[;v ƒψ3\‘œB|mf˜…>’ΝΝτΦ[Η?ώΈžƒΥE罐a½ςΚ+[Γr9ύΰ˜/ψπΓσαΘȈAT5 ˆ&Ω(F$[B›2 h0¦$›η° ]tkΛ2!IEd[e©‘g"š1‘HBLάKG —eBΉνΦji~ΎαŒ’ΙΈ΄΄Τ±au­£_΅š67₯˜©αšΦΧυΟ& Xz$ ΎμϋϊΎάϊrY"f†•Š—Ά\Π|˜RZ_oφˆvμΠυςΞε«Υ?ΣKΨΗ2!Ψ°L…|¬.^‡ωjςUXΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcΎRΈ‘iΖ_¦Uδ| 3οΌσN>μνν•ΌPn0ρΰΑ™’ΆίδΞά—=9e³‰΄έ0ΓΓΓ23==-3Ǐ—ξsΡΧΧΧr΄]MΫuΌεnuμžJΈeœόŠΫτ]Ώ~½εaΏwτ3!Ψ°L6,‚ Λ„PΘyηΖύύύω1·ΰAθΞΣ '\³~™‘˜hǝ§W;00 3άOZ6΅KΫΙ ξiΐ=(wdίƒ΄Ό ‘ΝΈFφΔ+ˆίX&– Α†eB°a™lX&„BͺPφmKΨi-ίΠπT+άcœŽvβ’ηΝ“ͺ0Ωφ-₯tτθQ™‘*δφqά/ŽA•ΩΩY™‘v“]ζRJ£££2ΓπΡ… dfffFf¨‘:$3rΝΛΛΛ©{ψeB°a™lX&– Α†eB(€ ~’νΈ©•˜ΆΖl2FΠγ[ZZj~κ„ΐεΆg§ζ’ΤεΩ©%oέΊ%3ŒxR»>1*GΖ%ϟ?/3|ͺTΦ’[™;YΏ±L6,‚ Λ„`Γ2!Ψ°L…T!ƒhLΆlΉ`rrRf¨ΤΪΙeξ%ƒ_λλλ2³kΧ.™αMQOέ½{·ευPΝ‘–ρ»maΜρΚ•+2366&3sss2#)£Όρ"ψeB°a™lX&– Α†eB(€ )©€ο³()=X%Ηh3?©Έ€Rc„‘·@έJUΘΊΒ'OžΘ (―ΧCUȌVΦB2.Ι»`+ΑΦ"ψeB°a™lX&– Α†eB(€ ™)ύ©_¨°¨ο¨ Ωd‘ν$©Ϋ e²' σE™±ωΩgŸΙΜΙ“'e†Ν[ξέ»'3|ΌfήŸ―™qRV,ΚΉx"ψeB°a™lX&– Α†eB(€ Β‘Α¦υντ{i'¦FΘΨ\;9œ¬ξWΘΜXζά23–W(:š?)‚ίX&– Α†eB°a™ 9οΜ’ΪI΅ΡkBje™‘^˜Ηγ°% ΛΆθb39qhhHfXFΖ΄G¦F²™G;΄γΌΛΉxΨ"ψeB°a™lX&– Α†eB(€ ™Η'9q †0 4>>.3TO Ξ°Ό‰ΗaσHj@¦RΝQ₯2ΔΔt<ή)ΓPœa’S’fQςWTsTX—.]’™³gΟΚ sA©m©%Y/Ι§Αkfτ»Ύσ‰΅¬—δc/‚ίX&– Α†eB°a™lX&„Bͺ-M$ήΔζχ”T+wοή•κ ζ‚2zH­Δ_±φr(~oήΌΩς ©Λ¨ΉA:ο‚!<φ)₯ήdδ΄εSm' Ά}όΖ2!Ψ°L6,‚ Λ„`Γ2!R…”'’¦HΥÚAv e*5άΞών¨TfΛ ;ΐ0«³Ύ:T|νhIΖΉ:7$§ϊ¦ν"~c™lX&– Α†eB°a™ ©BΚIγd ‹Z‰υwνμqωςe™αε erΐ©©)™a7Qφr™œœ”ζyΆΣW‡ηb'U*Ύv4)U3θΪΪZ>€|(3Ό ΙMuRσ`Γ2!Ψ°L6,‚ Λ„PHΎυΦ[2#ΑA*5FΉΕ&£cΤAό+ςX΅ΗψυΟΕ°•,3H©IΏ£4›ŸŸ—*bξΚAJD':VhžlX&– Α†eB°a™Zk‡/’V«½ϋξ»2yρβΕ|Θ½!¨;&3νδ”RQs>|XfXΘ_Q·ŽΛ {™2’GΕΗ³3V811!3Χ]“™–‰»i»œ[FN[ΞΝΝ΅£.·Εo,‚ Λ„`Γ2!Ψ°L…œχ.^‡ωj±σnŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1Ζc^4ώΣψ§$ιvOAIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/rectroi/roi_getarrayregion_anisotropic.png000066400000000000000000000141651421045507400315250ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ'IDATxœνΛoΗ•Ζ»/"%Ύ$Š%JΦƒŠˆ€Aό 0kg1cg•M–Ζx‘ΩΕA ‹cΜnV,Ό ΰ’I€Y±`lcΆ%Cο'%κΑ7yogQšΕοσ°νΔ}ΏUυeέξΎ—ηž―Ξ©SΥEaŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒy2Κο©ͺjΫοΓμdΚς;ΫI³+½ϊκ«ωαλ―ΏNή{ο½όpff†:ά½{7?Ό~ύ:u8uκΤΦNž<™^Έp:ŒŽŽζ‡“““ΤαςεΛωa§Σ‘ΓΓΓωα]»ςΓn·Kύ|˜:,,,δ‡kkkΤayyyλKΠΟ~οή½Τ‘Χλ减=’«««ωαΓ‡‹ο•Ζl =Φ•+WςCu•τλδ“O¨9ƒΩΩYκ .Š Ÿ700@ΖΖΖςCύεΡ[ξέ»GφνΫ—ήΏ??άΨΨΨϊŠδ] q'CCCԁΎΨ={φP‡έ»wη‡δD ωζΥη‘Χ?$^ˆ PBT* $mZθGQ›ς;{φl~ψK/凨RΆ&™θ[R­€’¦Φθcκ‡’IΣ|X2<\--έ#yΥVμ±L6,BC)Μ#‹ͺͺ*  h–^g (Aͺ[_!(­§RHB£…Ϋt†σηΟS‡_|1?$ΙΠ@•R‹6R¨₯‚ΤAΕ”τj hVΚεxqq₯ΣιΠW­oi€=– ‘‘ΗΚG©££έNg…JΙ[h5•Ξΰδ`tο ʍιruϊ!κΒC:§Ž”ihLgΠ+R]MIΞ@½,}jυ²δV5ηDE`Ϊ!?ηΘΘ|#r“Η2;– ‘‘ζώv` WU•3ΧΑ;‰£ͺuЁ0 ™σ!¨]£y)š‘Ο¨Ε ΅S:”ΣŒ`ι%θ-ϊ‘H―u‘oψI‹ι«ΣY Ψc™lX&„†R˜‡3}}eUU[οΏ£ΉP-.#Š.‘»QRUq ι‚U’c:ƒ~(š˜’Š}EGTZ¨)%SU:ΪASΏΨΝ‹;)ΎfΧ`eBhθ±ς_ޞ=έ²|Έu.D³)”Ηq.ΉέΣοL·–¬έ—‘*ƒu"œReΣΣΣωανΫ·©?u¨έxMΤ₯Q£ BςΣ‘δN?E”’l†=– Α†eBh(…ω˜±ΣιUUE{*CτΚΕ‹©-ΤΝdh ¬ΩΨκ„.ΙΚ‰'¨νaLΊ£S@΅»W u©#ΊpˆΚ­tΞ‡nRˆΌCY–ΛΛΛ€ιT@Π {,‚ Λ„ΠP σ n}½κt:ΞΠ‘Ζ&΄RWΘЌF…Z•KPΨ¨ΒDY"]ΡJwUϋ!:‘ͺ3‰―†τ–ΪΈ²Ά M«Hς}}λ###GnͺΙ}tΓ·ΑΛ„`Γ2!4”Β<€ί¨ͺσ‚Qt£™²pͺ•ͺ¨(P@€›PvQ…ŒΔQ•‹B]κ―₯‘ιΖ 4ο€Υ‹[oΪS|‹MJ·Ύ₯bs0;2Rή§i"]]ά{,BC•Χπ―φͺͺ"—C?nΝ¦ΠHY%”°ΡΩς‚Ίs ύάuz„’NeΠ¨Jc΅ξ€6ΙD³+z YtŒfu$žΥ KUU‘θ]5ΐΛ„`Γ2!lOuC§σˆR>€S:₯Ccm­<¦Ω ύ οκθ>ίa¬ψ&1­έqλ2_H†ΎιK›τZw›‘·θŒ A‡$dΊo@.¦ε²Ε%šaeB°a™ΆE 7 Q *FPQ 0PId…v JRO”Φ_PΠGΪZˆ¬Πl‰†l$Ύ΅»θCα¨bBλIm΅ς˜έkˆ—§ΚVVΦ»]ΎŠ&Ο`eB°a™JaG”eΩλυHϋ(HT)$χ«Ο!­Τ%1Λh’B•κpμΨ±­ο¨»ΝPΥ β4½IŸBW΄ΦV7―‘o^Η74΄ΨίΏA!Ό*`eBhθ±ςNUUeYR½ ½kŸ ϋ<Ρ‹ΦˏUηΉιtΉ(ŒΣμ,y8ͺ’±yνήό”ΣI!Mψ΅n•žάΦι,ΠBλ`eB°a™Ja.mέ²,)E²’{ΣΈXk‘hœ«kΙišH;:λ΄ „uœKQ[MER¨%€SZ-M1~-‚¨2ΕtΦ(ŸL[]]νv»τΟ2ρΨc™lX&„mΨ5y}½[UιylΝΗP}–M°¨¬NiˆζpjŸΗ¬ΒD―PT¨*S»Œ‡b[½"‰©V7βkq"έ•Vlηίd―Χ+Λ’¦žtmpμ±L6,ΒΆ,X풍¬έݟ<6=Ό΄ψ;ˆRαήΤΤu {ΠB?’°H^iC‰‡ͺψ’ΚΤ>΅FS¬§OŸΞυ‘tšΞΊ†§πή f'³ km­W–% iZ»»©S§¨-ΚΣΙ ϊaιl •pιŒ •;ΣV0…3QŠHgKΘ>… uκ>¦_Έp!?T—Cγ}ZμTlNΏυυ­/-=ΌqcΣ?K/Ϊ{,‚ Λ„° ΟλVΥΝoΠh]Λ†Θίͺ ΡPΊ6EτωηŸΣ+44ΦύjtN† OQ›d"έΡ‘7 ΄šΞω駟RRΟΪιχ–!vνΪžšΪτΝ|γˆώ»beB°a™Jap­―WeYRާvφƒ* ΄–ΤS;NΥNιΤn&£ͺAΑ,m€£kh©ƒN§P¨;;;K¨†[gΊTϋŠŽ΅>™ΦλυϊϋϋιΏS{‰oƒ=– Α†eBh(…yL71Ρνυv9΄΅΅†l$ZσNΩE ΑΘck& "j~Q+…t¨'$!Σ”,Ν;iΦ—žc£ŠB6~‘ΝU―σΨvcccii‰’W―1;—m(Mμ•eIcFϊεi΅+ύšυ‡E™-Ν9Ρ―_'Xj›Σ+ZάL₯…@Z"FSχu¦Khjκ«tꝊΌΥη½ςΚ+ω‘&σUόCCέΑΑ5r{^bov.13sπε—WŸΩ™Ά£#*ŒΊŒy"rΫ²U™ν$ΩΦΨτ΄­Κl3o9σ‹?~Φwaώ?2<1ρ¬oΑcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖlfΆŠ|ΚTUυ¬oαP–νϋO΅ŒηΠ°Ϊψ‘ύ‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!Ψ°L6,‚ Λ„`Γ2!τ?λhΒϋ￟|πAj ₯ΖΚΚJjLOO§Ζ­[·Rcii)5φξέ›ύύ?ώ;wRcχξέ©188˜‹‹‹ωŸΦΧΧΣa___j Σ₯Rγΐ©‘οzτθ]Ϋ·o§Ζψψψ–_ÎΖΛ„ΠJ΅ΊΊšSSS©qι₯Τ W&''Scdd$5Κ²L^―G}ΰZ–——©sς(π£££©qοή½Τ¨ͺ*5φμΩCgƒΪ·o_jΐu‘>xπ`jΐΕΆ{,‚ Λ„ΠJ)άΏj\Ώ~=50‚†rAψξίΏŸ w:Nσσσ©±±±‘8¨-HΊΆΆΆ–1πΗ1ήΗigffRγΠ‘C©……βBP@άd½Ψc™lX&„VJαΓ‡Sι+€Ž ’Ωn·›Iͺζζζθ$9€Z†jADŽ/^L D―˜ΣԘ·Ψc™lX&„VJ!t ³~ΘRB>0+eAέ ›¨–A\}™1W†zb²'"Ε…0ƒ‰σ«€βڈ=– Α†eBh₯BοvνΪ•€‘wBθ&β/Dv8!„I—ˆ₯ά&’B,/CΒ=ΰ΄Έξ 7έDΑή…K·{,‚ Λ„ΠJg ωPMğ°‚Oƒ5H ώ€ΩΝ›7S’—Δ =Ρΐμ!zκz@( ΞWβΒL½«aeB°a™Z)…˜ŒC¦ΦΖ FΓ >bPd)Q6ƒ²Ό ηI©Qθ€Hρ'\…4Έa¨r§ϊY4£ΫFμ±L6,B+₯Z†B ’Bd"η‰BH:δ6‘)ΕΚχ€bZt MΤεσ(’AηΗHtΖ #„„R·{,‚ Λ„ΠJ)„ή!Θ‚0!߈B( ¦‘›Π;^Ρύg’ώbΦ ˆt($ 7αC ˆW9’W„@{‰½1Œ Λ„ΠJ)„|@Y‘!ΎƒJBρ.tΦ2Q„oP@¨d:!„α!4z‡@Og'q~Θ%N¨W4ڈ=– Α†eBh₯"sˆ„οςεΛ©‘r©YJ¨€ r†fSS·¬Ρψ#Πϋ„°άΐ₯‘Ϊm#φX&– ‘•Rˆ©Φ₯  MΔd„*‰π υœΘ[B±₯vZ¨ˆχB°4θ qΔ€τ!$’K4™u;t€„!¬C‰½ΑqηψήIΕ ύ°qc– ‘•R¨ ξ |ϊ,B( 1D|Ίη6Φb¨Θ¦΄ͺξTπ'db13¨R«•¨Έ"Ts…Ζ06,B+₯ќξ‰y@D[ϊ$ά-퀝!g)Fƒ„A υBΈ+ HqETγ 7K›~2…1Š Λ„ΠJ)D€a•ο*Cψfξtγ5ό ‘YηΝ+ŠβΨ±cιPŸ«‹›‘§ύζ§Υͺd\5r΄ΓΨ°L­”BδURŸξŽX΅Ψc™lX&„VJ!jZτΡ~PL½i|‡ά)²”T<΄r†81E…Π)(,$ ]ˆ a}ΔŸa,ώ455υ};{,‚ Λ„ΠJ)DΔΥ@šςeBA%ρ.Δ’ϊΘ{dS!^©³.¦ΐΩ ΎϊPBt†vλS₯ ΗΈsάp±Η2!Ψ°L­”BH DyK]!ˆΌ(6™8bŠP7=ƒΐa /I’Bh.0ΤŒΈ+δEqό@…±xί€Ζ06,B+₯π7ήH θ‚,„‡¨9Μ!"ƒ A˜ >Ί .‘2’ˆΤτΙΊ'›ζfΡ1 >™owٌ1 \v>ϊ4›ηulΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1Ζμ(ώN%-€3>qIENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/rectroi/roi_getarrayregion_halfpx.png000066400000000000000000000154141421045507400304530ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœΎIDATxœνMlfΥyǏί/{ΖΫ36φx>†’ ,ͺ HI•JQK‘²bΡΤμŠP$Ψ%° ŠDι¦"EbQUͺ @%κ ²i:‚ΞØaΎμρŒgόmΏvοέχ3ο½y―Ÿ*Σωˆs}ξ½η^?sόόοσœη€dŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1Ζcn,zΊ>ssssΗaώ0ιιιBjeξzθΠ‘lσΡGΝ6_xα…lsηΝrϊR‡«₯”>ύτΣlσκΥ«ΩζM7έ$ύFΆωΩgŸe›ǎΩζάܜtΜ6WVV²Νj΅*ύε‹‹‹AnqΰΐlsuuUϊΧjmΏ K—.uΈ,/0₯4::šm^Ύ|9Ϋ”ίΡ‰'R·TΊ>Σ\ΏL|ωΛΡ·(5c™λ‘ž~zΗΘΘόωσWϜ‰»‹g¬‹ž~zfrςυο~χΑ—_ή΅o_܍JΝXύύύΩ¦ψLβ@ΌώϋrzoooΆ999)φξέ›m e›•Šώ«h6›N§3??ίω‚»vνΚ6ΕΕ–ώηϟΟ6©oδ‚2€Σ§OK±±±lSΌΊz½.ύε%‹ΫϊΥgž9}όψ½ςJJι…ϋξϋΞ[oύγ7ΎqijͺΓ»Ζ3֍ΒWŸyffr²eU-^όΚW}ύυέΠLΫBY«Zέόζ7―‰΅»ο~·ύ‡³cΗΦεάz½MΚnllH‡αα+nΝ F Νυuη0  M4ΝΜ΄υοο_ώKKm8cυυ΅ιΔz}-ۜΥ ΅έB¦δ;τϊΣΣmW½φ‡μo›‹=°πΚήΏZN)½ωfοτt5₯τƒ/}ι©ίώφεoϋΜ»ς»+KYΓzδ‘Ω˜Ÿšͺ§”ϊ0ϋ£6³˜›k{))₯J%ηKX__۟ͺάo*ς‹”& W~O΄ƒ;Ϊaq±νΧV―λΧ„΅΅΅Τ‘Zm³½ΩΦqQGΨΫΫv a½―ta‘ν ;w^{ΣχώΙξί|Xϊ/,΄ωXς!ž_φε 2žωϊΧώΩΟήϋΑ_W*ΗβΉη¦?ψΰΤOš•ωG kότΖβŸξΏ;oύηΉ•˜ώŽΏϊκ6Z’ΰ?…7o?ρΗ›©zόΥWCοbΓΊ™8χΡ·°a™JωXγγγύύ3KKΥρρέ)₯‹ΫΎˆJ†dh$8ϋrz‚;Ÿϋ•H:ΘηΖ+ĝηΕ[—αIˆΐH„'₯΄gϞlSbJόP'y/ςωƒjC{Ψapp°R©τττ΄’CΉθΟX&– Α†eB°a™lX&„RͺpnnneeeyΉZ# A‰sνƒh–ƒJ‡ΩΩΩύɝwή™mŠ(γZ rSΚ#HD…Ÿ­%¦$".!³ώlS‚ά)₯cǎe›ηΝΛ6)“e­GxυκΥεεΥf³Ω Iˆiίφε”zΖ2!Ψ°L6,‚ Λ„PΚyo4Υj΅Z­΄$ž ’Ή)κ~ψ‘tˆΔd$]“w”˜3TΕYfƘάQœύS§NI‰A‰―Mδ0ΏJ’FwέuWΆΙ7&ϊFΔDJ©Z­­­mnnΆςiE^ˆ:)ƒg,‚ Λ„`Γ2!Ψ°LeχJ₯R«ΥZΞ»|§–d&"nΏύvι Ξuηt«„μ"){Δε_}}}Ω¦Œ?Α>Σ^Eƒ χ-δ~ΔΉ–%tRΔ ₯433“mr}X³Ωl4z*•%yφΤC]γΛ„`Γ2!Ψ°L6,‚ Λ„PJΦλυj΅Z©TZ‘²ΐœαQa¬Δ*1I¨b6’Δ”D΅QςΘ€)cEΚ*©)’ |Σw– [ίX‚ž_žω[ΥΐΜΜΜάάκΪΪZksI’c,‘kψ@:Θ²Ρ,\?.*Oκo±ž–dG=zT:Θ.,’D™):”A'‰αΘΰ–'r‘5.Β‘!m™‚–έΕ^²΄kώψcι/ ^SβυΕϋζŽŽ -Φj‹-₯"ω^ΐCt‰g,‚ Λ„`Γ2!Ψ°L₯œχFc₯Ρ¨Ά>(ΛwdI‡’­ϊΌun–'Ύ§|wf΅\Ή…ψώάΟCΤ€Œ?αSΈψςΉ»ϋε.ίh­s.ΙοΌΩ8 t‰ α63λλλΝfsss³υrδΫ=‹ tg,‚ Λ„`Γ2!Ψ°L6,B)U877·²²²Ό\ieJ>}:ϋSj"!wOΡ•²J‡2S–Ψ熀䂒Ρ2΄D–2ΑKw'#δ)’Co½υΦl“ιSr}ցήΨΨΈtim}}½΅$IξΘ­@»Ζ3– Α†eB°a™lX&„RΞ{½^―T*Υj΅ε3JΖ•ΔΈ_Φ&pΑΈΈ–ŸK$&#ιJμ/ή·ΈΖ)₯_ϊΧΩζwά‘m²΄¨ qφΦƒΘ#s„δ‘!YδXΤSКΝζΠΠR­ΆΪκ)2FΤ5ž±L6,‚ Λ„`Γ2!”rήWWW766ΦΧ―UJ•δωξΜΚ­β›s!²Έ–βΙ2ΏJΎΕ‹«Λώ’Δ΅Ϊ"G>ωδ“γIπ¦ΉEŠμ¨(ζ—z)p%j€ϋ!v~c)₯΅΅΅ωωΥf³Ω’²ZK«»Ζ3– Α†eB°a™lX&– ‘”*ά΅kW£±ήΫ[i%6‰„‘ψ€’„E8RΞ*!£KD/ΨΉΘq,e>ΦΔΔDΆ)Kΰ0Ι- Ί/WEJΜGDΣ­DY3e-₯40°T­.΅Β_ςJ€κΟX&– Α†eB°a™Άm‹‹OS²£˜œ$.SHΎ”Τ’}ο½χ€Ώ¬…l7’RšœœΜ6YοJRΚnΏύφl“ιSΉEδRΠK|σ”'(_D¬¬¬H‡³gΟNLl¬¬4[α©ύϋχgΚ'κΟX&– Α†eB°a™lX&„Rͺpvvviiyi©2;ΫLH‹MDΕ!V “‡„\DΡ$OΟΤΤ”τ&KhB:ΉΛ„dΐLά;uκTϊb(œGGG³M ‚qKYtΟΔ½Œ-7ΨΟλKjdϊ(ϋSVόκΟX&– Α†eB°a™J9ο‡ΌΈΆV;|8?3ΆΫn“Σ%‹«¨ω2ΞοΪβۊ+Ν•"/XΑKό}!wχη—ΫŠ4VλΌa"w(‘3Α«΅ίIϊί τgJ\ΧxΖ2!Ψ°L6,‚ Λ„`Γ2!”R…=φΨ=χΌΆΈΈ«V»?₯τψγg*1¦[IξΙ.2SβŒ?HΡD²$&!δB‘%·2­ ωXΤ‘r‘¨”μ)Ÿ |eαU§ΐ(V__ίψψ‡ώϋχΎχw eψ+θΟX&– Α†eB°a™J9οΟ=χ܎ ΣΣ•_|;!βρK/e›LΤWρΰΑƒ‘s†7gyll,Ϋδ!ŸcGŽΙ6ΕΥeF|sήQB:GΝ6Oœ8!ύ;§Έq‰½D±ΈΖqqρΎϋΦΗΗžzκ©„W$Χβ‰'R·xΖ2!”- rιRΟ·Ύ΅rχέλ)₯;ξψΧ쏎k[­΅Ό¬zͺΥ6-½s§.ΟβΏψ,υΊώTΤ{oοR‡Ÿ&L9ΜνοoΫ3V&ΰ«ΥΪDsο80p2ΫΌzUŸ¨Vk»£Μ(\έ%§o6›ΓΓ›W„O(e λΕwNM}ώυ菲?ϊկڊΩΟΝι―­ΡhΛτ”—/λ{Ι’[FώVnδoχόΌΪΑΨXېδO'νF†Δ;ΚΗΗΫς)ΝΣ/sόc—…›u‰aq„­G~σΝmΫελ‹(kXλλιΗ?Ύφπ‡Λώθ'?iΫ“νόω6χ"₯΄cG[џƒ5kεμΩN3ΦА.Χ\Xh{­ccmάΚΗjϋ‡;;«Ži3,1τ­|¬Ά!mεc΅ύ¦mϋB{β„Aνοoϋ$»>VϊΏΑ>– ‘Ԍ%:σσ{ξΉGN—†¬IΨ™R6Šαή;’Ϊ€lZnΚL™r:dRYVY¦ωΛΘU=ςΗTωπO­ό xARk/ϋΟaeΧxΖ2!Ψ°L6,BNήEψΗό#73ΗcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1ΖcŒ1Ζc̍ŒΛ™Nt]Ζ¨T gŸ}VŽHίηŸ^:H]Π΄Υζl²{Ϋ–}X2Yχp“£i«ΧΔͺ›W―^•#{χξ•#¬FΜλ°€1―LΈω^£Ρ#,ΖΜΊά<‹c–³x‘γǏwl'\ΡΟ„`Γ2!Ψ°L6,B)睛‰K‡Ίˆ^Δ5¦s*ΕϋΣV{<λ*t±9f!άώ„ϋ1σ₯ Ÿ]v-H[‰Žo•»ˆη~eπŒeB°a™lX&– Α†eB(₯ sI²xΪJς• ϋPρ14Δ}ΨΨ‡Ϋέ2„BMZ­VεH‘{Q“RRzσ^ieΝmξ1Χ6,‚ Λ„`Γ2!Ψ°L₯T!—Ξ‰„‘ζ"Œ…δ*Bͺ§"…φ γ‰Μz₯nεYTŽ| –Šd’jŽ[ P#σ¬άH₯77Χ6,‚ Λ„`Γ2!Ψ°L₯TaξJ΄"›{Ω(›gφ‘ήd8ŒΪϊŽq@FτFFFδ•#―SDνRΝQΎMOOΛ>)c—’Ϋέ_„g,‚ Λ„`Γ2!Ψ°L6,B)U˜+j؁*Œj…g1:V€J ΧθΙ ύτΣOε•―355%G˜ηΙλpΕ"Ÿͺz³HŽ+Ο’χS€dkqEφͺ(RηGκ¦ζV”ύ½πŒeB°a™lX&– Α†eB(₯ )Ν$FΟu―o’OΞyžσ<χ^@QEQEQEQEQEQEQEQ₯™q4ϊš—ΗσΕgžΩrήyu―Ώυ_υƒΏύΫj₯³RlOG0ψs9ΟWχοϋKKŸ―ΡgΧΤΈ}6 T(4ϊ쇳Ρ' ΄&*ΦjI§qθP£OΒ>h*\©†‡ίiD£ >[ λύI₯02‚tι4FFJ5ϊ„μ€–އ»£γŸσΟšVτ’Σ‰αξƒ—ψ/¨–ˍ;΅fGΕ:©>°3΄TΦ½ξsε^CNs’²R)tu½ηW»Ί4'*'Ξρ­"κ–rb¬Ζ*’n)«eυVuKyNΤ*’n)ΗcmVuKywNΖ*’n)υœΌUDέRώΔzYEΤ-Xo«ˆΊΥξXaQ·Ϊλ¬"κV;b΅UDέj/6Ζ*b~~ƒ>Ki$iQ·ZŸ·Š¨[­L£¬"κVkX«ˆΊΥj4ƒUDέjšΗ*’n΅ΝfQ·μMsZEΤ-»ΜVuΛ~4ΏUDέ²v±Š¨[φΐ^Vu«Ω±£UDέj^μkQ·š»[EΤ­ζ’5¬"κV³ΠJVu«ρ΄žUDέj$­jιξΖά\£O’ im«ˆΊ΅Ρ΄ƒUDέΪ8ΪΗ*’nmνfQ·¬₯=­"κ–U΄³UDέZΤ*’n­'j•uk}P«V’n,jΥ{‘n­΅κψ¨[kA­Z κΦ‰‘V­uk΅¨U'ŠΊυώ¨UkCέ:jΥΙ n½;jΥΙ£nΥ£V­==˜mτI4 jΥϊ’nj•5΄»[j•u΄―[j•Υ΄£[jΥΖΠ^n©UI»Έ₯Vm<­ο–ZΥ(ZΩ-΅ͺ±΄¦[jU3Πjn©UΝCλΈ₯V5­ΰ–Z՜ΨΫ-΅ͺ™±«[jUσc?·Τ*»`'·Τ*{a·Τ*;Ϋ‹d²Ρ'qΤ*ϋ€nΉ\S«μMO¦§ΧνhΞu9J₯‚P^οΊLi >&&}οΚ±cˆΗ}ʚΔΡ£>‰γ nΩ‘f·Š¨[φΒVuΛ.ΨΙ*’n5?φ³Š¨[͌]­"κVsbo«ˆΊΥl΄‚UDέjZΗ*’n5­fQ·KkZEΤ­FΡΚVukγi}«ˆΊ΅‘΄‹UDέΪΪΛ*’nYM;ZEΤ-λh_«Θτ4ϊϊ}-G»[EΤ­υE­ϊΝι–a†a4ϊ,N ΅ͺž&tΛ0Œ]»v5}:οOSYεnτ ΌC<Žιiμά‰™™FŸΚŸσνoΐ=χά#n9ކžΡ»38ˆύϋ±iS£Ο£FsύŒšΗ-Γ0b±ΨuΧ]ΰή{ο­V«ƒƒƒζζζήxγ 4™^ΝfΦλΊΒυ"ΗΛ/7KNόΚWΎ"m§ΣyΰΐΨΆm[gggσ€Θ&΄ Ν“ …fΘ‰†aάvΫmΎφ΅―¨V«χί?Ώί?;;»yσfgœq€ΙΙΙΖ¦Θζ΄ Ν– …ΖΊeΖθθθ—ΏόeσσσΊ»»ΩώήχΎG±¦¦¦d-[ΆψΝo~ΓΝ 3¬i­BF, q‹ά}χέnΌρFn^ύυ~ΏίεrέrΛ-|%‰ΜΝΝ₯ΣιX, …0†Y­W3[…¦ sΛ0 ·Ϋ  R©π•`0xϋν·—Λe_ψΒψβ7ήX*•˜1x<αp8“ΙœuΦY¨ιk kr«Π΄©PΨx· ÈD"ίϊΦ·άqΗή|σM###rΉ€`0xΝ5Χ”J%…BΑεrέqΗ}}}–––€ΣiN§¦€Ή^†5ΏUhζˆE·†!6”J₯₯₯%§Σ Q«oU«Υλ―ΏΐΥW_Ηάzλ­¨°ωωωH$Β˜Χλ]—f «Πό‹l˜[†a02]rΙ%?ύιOQλΌϋ|>§ΣΙhDͺΥͺό …ΌςJŠ511F|υ«_E-Ξ₯Σιp8ΜάΊ°°0==5ιe«`±°QnQ¬K.ΉΐΟώs”ΐησ‘66djσz½μŠΡ-f½|>υΥWS¬T*ΰ'?ω j™‘½4βv»'jw£Z₯a6² ͟ …Μ‰4†nvvvΘεr‘P¨\.Σ'˜BwΈγŽ;(ΚUW]ΰcϋ₯ΌσΞ;„ΓaKKK†aΠΏΥ€H{YE,b©[†aτφφrάχΓώ€Χλş— …‚ΧλeμaGJBk ω|@WW—μpρΕ`ΟΜ0Œ{ξΉ‡)•γΞεrΉb±ψ^έ|ΫYE,²q‹§T*Q ԜΔJσκ«―ζσωP(Δ8T,H)Kδxψα‡\yε•άόΤ§> ί|σΝ£££¨:ƒΑ`"‘ sf•νhl',sΛ0ŒŽŽŽj΅J“*•J>Ÿ/—Λ‘H„ƒDΗγrΉδ-CCCΩl–›Œ@ά§P( Ηγp8<Οj>@ P,{{{k‘ΐ_:ΝyoΠγa³T(ΜΜ`ǎυΌƒ4ΕΊζškά~ϋνς:³§qfggψύ~Υju|||hhˆ»…B‘#GŽ0Ϊ-,, Ίφfffτφφθθθ0==}ΣM7U*•ΉΉ9푇bˆbώΝd2‰Dυ?ώ#χWuΚΨΨ?₯©–TۜθJΦΧ-“ΉβŠ+<ψΰƒ¨₯ΌT*U©T6oή<99‰Z@bϝΏfF8jτϊλ―£¦N"‘ΐΧΜΜ »πΡhΤαp°[”ΛεΛ/ΏœŒD"ξ»ο>‰DυρΗ'Ο:+Α‘γb2™„Mτ²Α)‡υrΛ0Œ­[·.,,P¬Η{Œ&φxB*•JΉ\fLβWι H§Σ§œrΚλ―ΏΞ°Δ.>>ΰΨ±c¨ #sc›Ϋνfχˆ_β€αώύϋ £VΠ"| ΛZΤξπαßχω|ΓÞ§žšέΆ-L3Eς³Ό^/s%j₯v΄QD«Š…U»ΕžΊ]|>ί‘C‡8θχϋύ~‘PˆΕbR|J§Σ² ΅₯Μ^―—1Μιtϊ|>zζrΉB‘£Ρ‘C‡Έwχ/~‘9σΜ8E­p:77ΧΣΣΓγ »\ΊΑ©h΅>–™γχ· ΓΨ½{7€…….`§σσσGεϊOεryvvvΆφ χΙΙΙΙΙI HΉ\Z­ŠU<šΜόΘησ―B-ΘmέκώωΒlqΉ\]]]LšT«U³I>Ÿ―§§gjjΚ^α ­-ήΟ­cǎ]tΡElg2sΗ@<—^9aO‹©.ŸΟ'kM Γ(—ΛR―gΒόήR©ΔΪΧWzζ™Τ9ηlZZZŠΧξhθrΉΈΉ°°°°° 8;βbaγΔΕΕEsP9ν΄Σ6mڍF%©ωύ~ωj4eηZήΎΌΌΜ₯/Δησy½^ΦHψύώb±(ρopφoiŽI<gω€Ϋν…B@ ‰0ζuvv2‡ΪŽΦ οζΛWρxœsp½½½+«ν’υςωΌΠ,--ΉέnŸΟΗώυΜ̌9E˜››3 —L&·m >υΤμ™gφ±θšΝfΗΖΖX0σω|‹‹‹’pLOO³ψΎ~ΫεA΄p罎•}ωΞΞΞK/½΅if JέR~,―3š‡o„έρρqεΌ^οŒimk:ξμμτz½ΓÞGy{xΨ €<gQ«Jδr9yvvVμ<ν΄ΣΜ¦Ϊ‹v @_’Ilί‰ u₯„……―Χ …¦§§ΉL%—ΛΙ…φ¬Hq΅1_α€²t³z{{Λε2λοά‰ύύ•G?οΌ‘DΒ`O+•Jq!!jkšYmgΠͺT*n·›Η·)m$€X Ι$z{“{φμωθG? ছnΠέέmNCρx<™LJY’!myy™±οΕΉ¬4G£Qφάωεεe™“ξλ+=φΨΤyηΒ˜ΗXeοΚμ7g©9) ΰΐvΜƒh“>–™X ΐφ2€ΩΩΩx<.C³J₯R©TdYA θλλc~dš«T*ζ±^₯R‰F£’ΤΨ©xνaΉ|ψ_uκτΣ»€¬ξJ₯ΨΑ" ΊέnvγΌ^―yƒνh;± ΓΨ½{θ_ώε•ώώw’u΅ZuΉ\ΜGhvvVV¦H₯RΉ\Ž;pj΅*Σ‹ω|>Ns HrΉ³Xe~$Yq €EP(Δ@ϊϋϋΩQc0γρ-AXLΫ‰`qqρώϋίε—γ³Ο>κrΉδŽ Εb1‘HΘβOΣΣΣ…ξιιρz½‘H„Y―³³³»»[V@p ‚̍Œt<ρΔΜφνο¬S0 #“ΙΌυΦ[¨-c§a²ŠfxxΨ<ŒF£v¬‹ ν(§ΣyΧ]ίzκ©·£ΡwV²KI³R©τττx<Γ0ΨsΗγ‰DΒ\‰0G ‘H„+±x¨rΉάί_yτΡƒ§žκ°iΣ¦j΅*]υ‰‰‰ΊςιΤΤΤΛ/ΏΜe₯΄-›Νmž;k―‰φ‹χ€džϊώχΏΏkΧ¦'žs:ηΩ­.•Jǎ3ίYΚ’4Η¬Η$Θ‹«”fii)‰D’ϊΜ3©Ώψ‹žΑΑΑΑΑAσ%Τ===Μwεr™Υ,.MFmΩͺH»\σ^΄Χ¨\qΕ…Bƒ»Ν›}γγ…}hnvΡh”!Κλυςͺyφ¦Q[χΗ‚;Χ‚9r΅²τ†αυzϋϋ+O<‘=γŒ‡z쉻\L&#>μΥI!ƒEvΉΡˆΫνήd»+κWΠ^ @±X4OΘtuuνάΩKΕΡΡ€H$"i(™L‹Eσbφ₯₯₯₯₯%σΊΠεεeφχ–Ί»—ž|2Ιή”¦³³S&€=5ΈΙ _ε²U•JeffΖ|―ϋbγ`{’†ΗoΈαwέu—Λ΅ΈΈΘξφΫog/Ύψτƒ³¨E Φβ™ζ]ζζζ(Π)§œΒεΦD’ϊŸΉτα0΅Ή\.―ΧK‰έn·ωnrIΤΒΒ7†Γα#GŽΨ:’ S‘Ή S7όoώfτίύΐΉηvΝΟ;+• _t8r±!ΗkTΚόvΠΥ΅ψΛ_.ξέ»3€Ϋνfm‚±Η¬υ2Η€rΉ,›Όί|±†}i―T(₯¦0)ΝΜΜΌρΖ»vmzα…ωpψ«<d1^ž*W¨2'NOOs¨—Ÿ{.wχ!Τb3 Τ?y½‡ύ~4εΓαpoo―έCT-υΝΓ0ΆmΫΖ[έsΟ=¨M'ΛέϊΖΖΖΈŒύw|οή!Σ²Ω,‹™2]ˆZ―(ŸΟ /ΎX9ϋ쾝²2ΩMMMq Θ ΘiDΚΚCqF(Ηb1FΔ\.—N§[@²6ŠXΣΣΣΔ‘d2™4-[ήΊu+Γ0Ξ>{δ™gϋύ‹Œ:ΦjΘώ iΖ―~UΌπΒ­@`yy™+Y)]^^»ΪGΦ]‘Ά”Τ|2-Pm7Σb†±}ϋvΆ{zzςω<#Κΐΐ€ΓαH₯Rγγγ 'J₯ΉηnύΥ―f9Ÿ‹Εb±˜δΠB‘ ·τυ1V™―ψ;rδˆΩ?ψ₯b±˜ίοg¬\. tuu™'‘%ωΪΫ‡άΥ@±>ρ‰OΨ·ojy6˜/:5 £Z­²ƒΏΫωH’Pθ€*²:ΰt:ϋϋ+\΅G-XtX^^vΉ\’QΉ\v8W²ΚΚς#%b b,‹Ιd²ς Ϊ$b‘6’022222b^ΜήΣΣcώνͺ©©©½{Ο|φΩ Ÿ/Η:w憑!­ΰχϋωFΊ‡”Κ Γ(•J²²ŠωWΦSpφšo΄υ χ•΄ΎX2£βχϋο»οΎ£GΎφΪkŠΕβςς²³°°P7…χΪk―qΖΰ³ΟNtwW 9Ž\.·y³σρΗ'ώοΟc:::κΕpΉ\²‚‹Ωx½^Z‰D\.OΖγρ΄LΈB›€B˜τJ$Lgό­3Vρ’BΆ].Μ°”ΥΡΡρ?sθ―z \ŽŒ'ΥηžΛοΩΣΟ·pζ˜=0ζΚήή^&Gσxθ Γ$')4½dx+•J“““-#V»HεFΓFGG½^/―G₯Μ_ζ‹˜e²yϞαώοƒω‰κ/ΉΈgOΐΔΔ„Σιμθθ`Κ<ΟCΨ΅’Ες₯RInυQ©T:::i›yΈΪ΄ΘίΗ‰"μœsΞανe²Ωμ¦M›δFάψH’h4JΓφοΟE£8ϋμM¨-¨β"ϋFύύύR₯R ,7˜o™lŽ…ό*‡©TjnneΒΪ'bΥQΐ„Γav’厣ύύύζυΉη+$Ί­CέΕμ0ρΞ `\ΔΗLNJh0”a„L)ΚU‡­D›Š%¬4Œ―cǎɍhθ„a•J΅444ΔϋΨ˜žžžšš’ZW"‘(•J²0£a TζϋlΙ ±•hw±„:ΓΒα0ΛWμBρEΉƒ‰F£ζKΙΈN ―—g~δδa*܁Χx±ύκ«―ΆRD;”NY5Κϋθμμ4_ΰΐΩhΙ’,4Θ3QδΏύοΉΓ[o½eΎ­kρ…BAξ|dΎς¬eh©Ώ+Ι₯šϋa,¦†αχϋ9#Δ‡.Q2V"‰#;οpΎ™±Πγρd2™™™™‹X-υΝXŠ688Θή’<ή@gg§ίοχx< lό£”ΌQ.ω’υΚ<ΟΨΨX‹Yνc­žΊNΨ?ψAΉ‰ˆdΙR©tτθQY (kšη²Ω¬a<²s600`~μj+‘b04LβΠππ0#P$aΗλπαΓ}}}εrY–-ΘσžΈ c΅{ί:t¨υΒ4ž˜ϊμ/Ώό2—]vϋφνΠΥΥΕΝο~χ»t­ΕΪ~έ±KP±KhήΞϋwΎσ6N?ύtΛΛΛά<|ψ0/Όπ€l6ΛM—ΛΕΖoΌΑΖ]»όξwΏΫ 3VLhΔR,AΕR,‘ρ£BβΞ9ηnφχχ³Q*•ΨψΓώ`||œ›^―— Φ«ŠΕbέλœΙπρΐΟ~φ3nΞΟΟ³qο½χΧΙ·:*TšK±„^ύυlΘ ΜφνΫ€ΣinΎτKl;vŒJ₯b>‚„eζ>Ι€’gffΨΰψqΣ¦Mܜ››[ΧoE9±K°Όσ~Γ 7ΐ4Γψΰ­·ή27’Ι€œ §σΟ€ΟησlσλlH‘KBΗ{χξεζόc6€wΫm·­ς»hg΄σ4*–b λάyϊΧΏΞΖ–-[Ψψΐ>Sβϋя~Δ†€6"Α6 ²Q­V͍P(ΔM™ΊYZZ‚iυ•€NΩαθΡ£&&&ΈΉsηN6ώψΗ?žΜχ¨¬XŠ%¨XŠ%œμ¨+λΙ€λΕ_dγΥW_…)OI‚“Waʌ\Ύ‡£B™α‘²–Οηƒ)uΚ297fI©cώωl<ωδ“ζCq= ς^θ¨Pi"T,ΕΦ8*|ϊι§Ω`=σ΅Χ^γ&W"θμμdΓοχ(—ΛάδP@$1PφχxΡNF‹Κκш₯XΒΙNιπ―_ώΦWξΐΘ$λŒ%ΐHΔͺ H:#τΈeꆬ\ΰ%ŠΊ.Y²\7Y΄ςφ§> Ο«~δ‘GΜέ&OΠ(MΔ#ΦΊŸ‡Μ¬!b)Š’(Š’(Š’(Š’(Š’(Š’(Š’(Š’(Š’(Š’(Š’¬]·.1ΜΪ©IENDB`‚pyqtgraph-pyqtgraph-0.12.4/tests/images/roi/rectroi/roi_getarrayregion_inverty.png000066400000000000000000000210141421045507400306620ustar00rootroot00000000000000‰PNG  IHDRȐdΈη pHYs  šœ IDATxœνmp[ε•ǏtυjλΥ–lΩNμ$6”@ Ι†v2, ³έέnٝN?Πia:PΚπa·Svf S:-ΪmaZ^Ϊ’OΜ΄SΆ3ݝν](Ϋlή–„4―NœΨ±,ΙΆd½KWw?ό£ΓE†ΰ8~,]ιό>dž+_]]ΛΏœσ<ηyξ½D‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ Bgakφ t"†a a³Ιχ/\6FΑΑΑ±±1Φ«-q4ϋ:8tΝ5ΧΡόό<U*•&Ÿ“b$+„cυΧ_φμY"rΉ\D4??ίΫΫKD§OŸnΧl(kεaŸΌςJ" GŽ&’l6KDDd·Ϋ›wŽΚ±V φ )onnNΣ΄@ P(x·Ϋm~˚5k ΓhΛ %b­PκΪk―-—ΛDδp8ςωΌ¦iΌΓΘΘΩνvMӊŒΝfCθjK₯€ˆ΅|8D…Γa"Z³f ;wŽˆϊϊϊ¨ny½^"J₯RDψGΫ~mϋ‹©ƒ}Ίϊκ«αίο'"„+"Ιεrζ.F‚6› οΝεr‰D‚ˆΧ­[Χ–ΩPΔZ*μΣΠΠ\.—γΧ}>9Nd=Υψ~ΏŸmλλλΣ4Ν0 §ΣΉzΏΓ*"b}00cΓ† DT*•ˆΘά""―Χ‹4::j·ΫΜ9CDxK$αύ!ςc»Ξ#ήΛ„ ε£££‘H$“ΙψύώH$’Οη‘Ϊ"‘ˆέng?zzz²Ω,zε`Σ¦MΊ#&%“IΓ0t]Ÿ™™ΡuχΙd2}}}νW…—ˆΥ‰Θλυf³Ys‡©X,R½ΤIDεrΩ0Œξξn"ͺT*N§GΨ΄iv=q⿝w]ΧΡ΅ςz½xo›!b]€}ΪΈq#MNNΖb±rΉŒθ‚~χψψ8j›`nn£Ώxϊθ£ΨΜεr@€ΧΤκ`sϞŸψΔ‡ˆBTŸ$"ΗΓ;œ?o|κSΓΞ`3F£QξŸy<―Χ }‘RN§ΉΚ‹Ε<J_TϏ «΅,MGD,„’`0HD©T w"ššš‡ΓPgvv–ˆœNηž='nΈ!βvkTάq,ρx<Εb±V«ΩlΆρρΒ 78φωζρ …ααad=qΉ–H$|>>ΞαpLLLΠ»„B‘φXμΠA«ΏΏ+’b obΦΕf³νέ{κ/r]₯‰?DOή²xάvγϊ―ω|ήνv―[·Žˆό~ΏΗγq»έ0•ˆ\.—Λε2/%­V«ΥjΥ|2mΆRΉ­~™δΕ_τϋύ=τλ¨X"ύρ\ށg».0>ώNΉ2›Ν†B!•b±ϊϊϊό~?κύύύD±O|’w’ˆ Γh8§Σ‰j~OOΫνT*άΓ ‡Γαp˜ύF%bΎ‡U #R!Υ³!JTŸN1―Ž …Bρϋ·nδσ6Δ#ͺΟΙ‹0–„.ω|>™tlΫΦύ‡?ΌύφMθƒcZ0‹a»έήPb z-ήγρd2s·½··· ²a§ˆΕ˜+D”Λεπή·oόSŸΪT«eό~J§ΣΕbΡγρ†Α%ƒήήήT*…UVΌš/—ΛΑ‰ωyΫφνώώοCϋΨΜΫhšV­V±š †‘ !ΫΈDΜάε².• ].Χ~π"Ίηž{ŠΕ" πx<§NΝ]}οΫoOρžΡhΤ’Π%G ™ŸŸŸššBΥΐεrω|ΎH$’λΡΏϋ»‘Χ_?EDΊ›‡xΩlVΧυZ­fΎΈžKhΕb±\.sΠςω|W_}΅Υ Z$–Νf;{φ¬Λε2―³›ŸŸ?rdϊCr>=ODιtΪοχc%B4E^γ?ΉΧλυz½]·Ϋ κΒyPί"’……‡Γa^ϊW©ƒMΜ6’掸₯iZ__ŸΉSo]:H,ζΉηžσx<©?_Όκ*·¦υφφφ²p₯R)“Ι '„?s,‹F£===DT©T4MΖ6a†%ΪφνώϋΏ*^θ§λΊπv"²ΫνfΉ!WΘͺΥ*.υ±4%–Νf;yς$Jα<πΐ[o»υΦ1»½ρΖιtΖb1s' uKξτtuu…B!τΊ°Π₯»»»V«‘κυzΣιt>ŸŸ™±ίrKψΠ‘Τδδδδδ$wΓηηηS©T2™$"‡ΓQ©TΠIǬʁ€₯³aΗuήA­V»ώΎι¦ ՝ΐ4MΓ *—Λ₯išλρxΌV«‘FΠ9γ• ιt=0§Σιυz+•J"Awά±ώΤ©36xϝ;7<< ωˆhhh¨aΚy``ύ­3gΞ@kΏίoNΈVΔΪcΪe`ΖΦ­[σ?ΎπΒΏΜΜ”wοލ”N§].—yΝ§ΛεκοοGqܜΧΠ©―V«‘P¨\.Γ»έΞΥ)Hζv»£QύΥW§IΣόφ|>;qρΧλ=}ϊ4δA·V«?ήΊE‡Ž‹X6›Ν0Β·άrνΆmΊδv»½R©ψ|ΎrΉŒ‰δH$’H$Έθ‡Νω΅.Ctuu‘ Ε™ΛησaDBΫ²…ff΄sT ?‚|θΌ£3g³Ω’Ι$―¬ΥjΦ-huV‹ˆfg‰θd&£Q$1―Ν4MΣ4φ)ŸΟΟΜΜ`‡Χ§›KPXΞ«Šu]Η΅7Ψt8v»ένω«Ώ8rd> 6¬» ‡Γ\A₯ϊ Z­²|&V€³Δš₯ΡQ" †ρκ«―Ρ£>Š aΎ 4›7!W2]./sυω|¬W2™L&“|q₯RΞΏώλ‘7ί<ƒΨ“L&Σι4Ϊ…B‘Z­šWU`Ι‘yiΌEι T«ζζ.LοlέΊ•œ  ΞΜΜ UΝΜΜΰoοσω0RCίΘΌ΄ακψd2‹Εϊϊϊfffx©1Φ2$Ϊ?όΓθόΟ©΅k/|ααp°§§Ηησe³Ω………J₯‚Eχš¦•J%§ΣyΓ 7X4vJΔb«˜l6ϋΜ3ΟP= q}dd€ΏΏίœ†\.'ΈJ₯R*• …’ΖΗΗΗΗΗΉV*•ΜΛΌB‘Γα(•J'Ndώο7œ=[ΕΔ3Xv4‰τφφβڊ£GZwΙƒUΟϋ’XlΥΛ’ρxό»ξ"’d2™Νfy©Υ‹Xό§νκκςϋύ<τσz½Υj΅X,bmB__ίΰΰ ΩφD£Ρ“'s·ή9p Ž –ίοC7«X,vww›σ/Λ}γ7Z± Υώ©π=­B6Δ•6(rςŽ=Š1ŸS«ΥΜCΒΉΉΉ†?³Ϋν6 „ΘΫ2™L‘Pƒά嚞¦[n 8Ώώϊ 3Ζγq^§U­VΡζ4šΝf­˜©ν#Φ{ZΕΔb±ίύξwh†«!βρΈyi<ΥΌσŸέnΗ!e0Δά3vθκκ2ϊ°pt|Όπяz^ύ”λσσσ|ƒI"²Ϋνζt\,S© Z+ωΥ¨§mSαΕ­bpΡMOO“ι.VΕbqpp°Z­:\\JD™L&‘H¬_Ώžκ½r>άβ2U­VΓ½F‰HΧυ|>Ώ°°P«ΥψRœtΪ{λ­Αύϋ§ΗΖΊ©^tε”Z*•ΞŸ?ODCCCPsEΎ“Υ€=#Φ­""—ΛυΝo~szzzηΝx₯X,šWb™γ 6'''ρJ΅ZΕΜL&“ΑT#Φ՘χoΈ<Υn·{<,s˜˜(όγ='NδxŠΟ2§γ†…ω’ #Φ­Bγ2LΡ$“IŸΟ—ΟηΐΦ―_2zWlLWWWΓͺ)Ώίe ½½½}}}ˆUΉ\V«ΩνvΈΒKrΉl›™q|όγ=oΌ1ΎysΏaΊW*•p8ΜΧB&“IΏίo­‚V»E¬₯[ΕƒAžψλοmΈΊΖf³abΗοχwuu!mA,Δξ㏍;vμΨ±cΨDŠλμ₯R‰γWήϜ)έv[ΧμG£Qσ +Nž‹ˆό~?/΄ ν#Φς¬²Ωl(\D”@ €€„ͺV4υϋύέέέζ;;š‹ςDtφμΩ³gΟd½‰‰‰……>”¦iζ{.,,`ΈHhσ7kώνίΩνφ@ ΐΟDα›PΡψC  ΫD¬eΗ* λϊ~τ#Ž(XΜΉΈ Oυϊg"‘ΰ»Hvuu₯R)σxLι4„€K\W‰»Q.—sΉ\ι΄χΦ[#ΞΨl6›ΝfžSΊχή{οΏώeώnMΒ2Α‹p™Vq}ηΝΑ`pΧ]TοBU«U³^X Γ‚Α ΗγΑZS(ΥΣΣ£λ:–9 δπ“) ,ͺκζ«qPDΕΏ===Εb±V›ϊӟ*7έ4¦λϊWΏϊUτπΉΟ?<>|Ψ]xˏ /Σ*ͺ ΗΖΖx50‡.ψ€^―χ–ιξξ6w¨‘ψxZ:ςΚ…d2‰[Π`ε‚Λε²Ϋνζڈ…ρΈmχξάΏ©οŸπβ7Ύρ ͺ?CΕJ˜θ{rωVˆ•N§οΎϋn"zε•WWcΨ›j΅Šϋyΰ§p £Όωωω‘‘‘£G’Ο„@…!™Χλ-•J(θγ§\ #"‡Γqί}χQ]ξξ…»ο~jγΖοάΧΧ—H$pω%τ²pΔJ₯hll¬’zΠβR$T…Bgnn.ŸΟ―]»–'ς4MkΈίέnηk£y•²a‘P…B333< ďώβ$»cǎZ­–N§ƒΑ aΟ=χ½όrΟή½ηΆnΔ tΞψFm­UΕJ₯θŠ+¨^oZœN§ωςvτl8Α &“ΙrΉΜImbbbxx˜™˜˜€š¨ΒΓ^hU,qJ\£ΗqoΎΞK/½Δ7p#’γΗΆoχοέ;υя«ΥjΛ± XζDΝ¨° Ο'’―|ε+v»ύ‰'žΰBh0ΔZaX‚dχφΫoSύnXΠeUŸΟ§iW۝N'ίΤ½¨Οώσdz>Εξέ»?NυΩF›Νf~΄S*u˜h ‘υ.Χ±^ΔRaΥ³!_$CοNyέέέζr2ΧΠ£2ίΤ>•J₯J₯ς₯/}‰ˆ Γΰυμ<ςΘθθhΓi GΕ!ΪlΆXŒφ﷘Ud9±Ye¦!…Q½—mΎ ΉZ­b˜ΦΥՏΗ<*UPηˆˆθΞ;ο4Ώψ―μ;yςδΠΠΠΪ΅k³ΩlΉ\ƊzwΚ›ž¦-[θόy2V-€•ώ+¨ΆΚ0 ,‰ ―Ός ΥονAυ€νpi ^Ηά³ΗγΑμ5Υ΅ΐ(·C"’§Ÿ~šLwŽ4 ;#>|˜.Ϊ…ŠΕh~+Ήe™ˆ΅ ± Ωpύϊυζ• ψσ{<žΩΩY\QƒΈΒY³xœ@Ώόε/£¦€Jι―ύk"šŸŸw8Υj•‡u’/ϋΐ.”εβ–5ΔZ«Μμή½ϋ³ŸύlΓΪb»έΞ©Šκ%.Ύτώž{ξΑ$ OΤ|ϋΫί&"„@" `¦Σi^‘|I]rkΉe±VΩ* ξΐŽ;~φ³ŸυφφbΤ†›fρBdΎ‰Θƒ>hΎqθ/ωK"Κεr===Xγυzήx9Ν²‡xr«ΥΕZe«ψΖέ<ψη9N§Σ™Νfρ\Έ‡~! γΔ§žz έy€ρΡ©S§h…ͺPVq«₯;ο«l0  'žx‚ˆΎσο@2tΌξ½χ^ͺΧ*•Κ]»`Š}}}™LKΧ'&&–—ς–Bλχε[7b5Ε*ͺ­ΡΡQNpΉ\ξk_ϋΝΞΞ άΛκ[ίϊ–ω]Qθ›οΩ³‡₯θ$[?n΅¨XΝ²j1(l2ίύξw±Δ”I§Σ6lθνν}σΝ7ρΚκTΙ[ί­–#•’ζ.Δ5 γρΗόρΗ± τ‘Gρx<ΈνΒ¦M›Ί»»―»ξΊλ»WΡ4\гšΔbdͺΆ-ΧΗj‘XeΖ]»0ίχμ³ΟRύ‘]WXιΐW₯6w"―5ϋ[­΅4ΉE¬(DZ­ΆyσζΝ›7Ρ±cΗpC[ζ#‘)'Ά-±ZΚ*,ΟZ»vνwάAD?ωΙOp3Rjv|z?Z3n5Ÿ¦χ«cΖ–-[}τΡ&v‘.‰–νo5d’L7jŒ:Ν>‘K@άz‡Φ΄ΚΊˆ[Db•:έ-±Jλ–X₯šNtK¬Z:Λ-±j5ι·ΔͺΥ§ύέ«šE;»%V5—φtK¬jΪΝ-±ͺuh·ΔͺV£ά«Zk»%V΅2VuK¬j}¬η–Xe¬δ–Xe-¬αV"A¦§„ Φ Υέ«¬KλΊ•LŠUΦ&£ƒWμh+v]a*E–Ίμ@hΔ0Zυκ1ιΆ[—ώ~šžnφI\qˊ΄ΊU@ά²Φ° ˆ[VΑJVq«υ±žU@άje¬j·Zk[Δ­V£¬βVλΠ>Vq«h7«€ΈΥ\ΪΣ* n5‹vΆ ˆ[«Oϋ[Δ­Υ€S¬βΦκΠYVqK5h·ΤΡΉVqK}}T ]#n­,bΥ;ˆ[+…XՈΈuωˆUu9ˆUCάZbΥ#n]*bΥR·–ŽXuiˆ[KA¬ZβΦΕ«–Έυ~ˆU—‹Έ΅±je·ΜˆU+‰ΈΔͺ•Gά«TΡΙn‰UjιL·ΔͺΥ Σά«VΞqK¬Zm:Α­h”ffš}H{»%V5“vuK¬j>νη–XΥ*΄“[bUkΡn‰U­ˆΥέ«ZλΊ%V΅:VtK¬²ΦrK¬²VqK¬²­ο–XeUZΩ-±ΚΪ΄¦[bU;Πjn‰UνCλΈ%V΅­ΰ–X՞4Χ-±ͺi–[‘%Mψ\aυX}·ΔͺNa5έ«:‹ΥqK¬κDT»%Vu.κά«:n‰UΡJ»%V ο°Rn‰UB#—ο–X%Ό7—γ–X%\ŒεΉ%V Μ₯Ί%V Keιn‰UΒ₯±·Δ*a9\ά-±κβؚ}-M2I›δ+ώ†Χ»] ώ”‰F›rRΦΐΡμhibƒ_δ~7žXS«½σ’έNλ"ηϊ»ΆU›wj‚•quw/›5ηΔή^J&ιϋ…‚Σγiκ©΅:φfŸ€ˆDθθQκν₯ή^:z”"‘fŸTΈ$"J₯(™«–Šˆ΅Tš~υ˜΅T((A"Φγκξnφ)XλbTK₯“oΌρEO9ώΪkz₯”SAAAAAAAAAAAAAAA‘₯XΞ=H ΓXρσZ›mUξU+buΛϋsΛύ±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%ˆX‚D,A "– KP‚ˆ%(AΔ” b J±%8š}+ΖργΗΡ8|ψ0½½½DtΣM75νœ:‰X‚D,A N…<πmίΎ›£££h‹E4~όγ7εΔ’ˆ%(ΒΆŒχ†a³-獗Γ#<‚ΖW\F__™ϊμo½υwέuΟ<σ …B!l>ρΔ«tνΕςώά±%ˆX‚Z·σώΨc‘ρα˜ˆJ₯6Ϝ9ƒΖλ―ΏND ΨΤ4 cǎ‘±eΛ"ΪΏ*±`B"– KPBσG…Δ}δ#Αf,C£R© qθΠ!":{φ,6έn7¨W•Λε†Χ1“CDŸώτ§‰θWΏϊ6gggΡxφΩgWκδ; -„ˆ%(a΅G…>ψ <³qγF"šŸŸΗζΑƒјžžFCΧuσ8,#χqδœ833ƒƏkΦ¬Αf*•ZΡ_EΈ±%(οΌ?τΠCdš‡A|"’“'Oš‰D‚ކέώ.ισω<]]]ζΧ].\θβΠ…qΐν·ίŽΝ_| ξέοΪ΅k‰ΏE'#w‘…±%¬pηύλ_:6l@γͺ«"Sβ{α…ΠΰΤ8Ψvww£Q«ΥΜ ΏίMžΊ) dZ}Ε©“w8wξMNNbσΪk―EγΟώσεόŽΒRˆ%(AΔ”pΉ£B ¬x¬Η7ήx·ί~›LyŠΏ’ΛεΘ”±| y†‡ΛZ‡L©“—9πΉ!KrkΫΆmhόζ7Ώ1 λ…χCF…B !b JXζ¨π·Ώύ-¨g9r›X‰@D>Ÿ ―ΧKDΥj›ΚQ04χw:hpDΒj˜Ψa2™ H©dšδAzε©!.₯^yε•hLLL,ι·.‰X‚–±Έ4>>N¦ž5G ιtšˆΒα06yO$ˆUδ2₯ΘΤηΥ 88OΤp―GζΡ§Bn ²Ε9ττιΣhpA«§§‡ˆ>χΉΟaσ₯—^Zς |±%ˆX‚–™ yp‡η5Ξh4ž~ϊι‹ύςˆ%(AΔ”°ΜTΘιE¦†ž8™ϊ̞8Wς΄4<ΰΎ6O*γΰά7ηΐ‘8cςz,>ά—ηΧn\ΓK–ϋί_βΧ Ό/±%ˆX‚–™ 9› Δ™ŽσNΓΚqшS’ζβς7°Ορž(•-œρΐIvρh‘Ο·hϋΜg>ƒMo}α _@γ§?ύιΏ‘‰X‚D,A ΛL…œnγ8―q]”wΐ8EςΎ ’&g΄†#Σ’a#ο€·π'ςΘK ω- SCdΚͺHŽœΧ―_t.‰X‚–±ψ?"ΠβωΎ*³ΞNΈ|υ~w•iX†Eυ_±ΘGΐžΨxn‡Ÿ€€΄xXΠ0ΏΔwδϊβΏˆ??lηΝDτδ“O^μ»ή ‰X‚D,A ΛL…œ˜Pš››»pΈwχ‹©ήƒζ|Δp/ωˆ«Pœ§ψt9Er*Δ/—h˜MZœsΉΏΰ7rNδIž?ώρό]ο…D,A "– „e¦Βd2‰F؎ s] wα₯œ$Ό$²IDATœΡx© RΥβ"?βyΞ}HyœΧΈTΖ―4œ™HΣ|@~κΞm·έ†ς2?§ξωηŸο―CX„D,A "– „Λ½iΓΐŠηv€›@ `ή€E+Σωζ<ΥӐςψȜm‘ϋx‘οΏxτψ|Ι+ŽΙ§Δωzjj <юG‹Β‘ˆ%(αr§t𿟯/ή‘‰Χs€αˆΥΈΑu,D ξqσΤ XΌΐ‹ ]xZ΄ šwΰΰgmή|σΝdzμτwή‰ΖΟώs.ŠD,A "– „e¦BξA£ΑI„‹Iœnπ#Ξ•œ§ξKΓυ-žiΈr•+g\CξγζlΫp(žααI$žjx44?Uϊšk‘ϊ3Θ΄rKψ@$b J±%,σ;vμ@Υ£Ε«νxMηDΐ5œΒͺψ.4œ+žΗ£H^3ˆζ1ΐξΜΖλϋxΙrΓdΡβ۟2xψ?―ϊε—_6t‡|ZwγΎ&οσ+—ΏR?όbABˆ9ΠΧ‡Ÿύμ ΏULσαάͺ"–Έ0ζ’*"m‰Ή2wUiKœŸ U‘ΆΔΉ˜Ÿͺˆ΄%ΞΞBTE€-‘ΟΒUE€-ρ‹₯*"m `±UE€­RΗ…ͺˆ΄UΊΈS‘ΆJΧͺ"ViQUi«T(¦ͺˆ΄•|Š―*Ω‰žžξ+ŠATͺ"V2‰VUDΪJ»vE―*"m%‡]»°eKΤƒ!m%ίTE€­xγ§ͺˆ΄W|V‘Άβ‡ͺ"Vœˆ‹ͺˆ΄β₯*"mωNUE€-‰―ͺˆ΄ε#qW‘Άό"ͺ"–/$IUDڊžδ©ŠH[Q’TUi+’­*"m›RP‘ΆŠG騊tuaηΞ¨‘xJMUDΪrKiͺŠH[Ψ½»tUE€­ΕgχnάuWΤƒπik1‘ͺΒH[‹ƒTUˆ΄΅P€ͺ/Bښ?RΥΉ‘ΆζƒT5€­ Cͺš;Φ\‘ͺ.iλόHUσCΪ:RΥBΆΞŽT΅p€­|€ͺΕBΪϊ ©jq‘Ά©Κ ₯-©Κ₯«-©Κ5₯¨-©ͺ8twcǎ¨Q4€ͺbR*ΪΪ³Gͺ*6ΙΧ֞=ΨΌ9κA”$IΦ–T-ΙΤ–TεIΣ–TεΙΡ–TεIΠ–Tε'ρΦ–Tε3qΥ–Tε?ρΣ–Tβ€-©*^ΔC[RUρ][RU|ρW[RUάρQ[RU2πK[RU’πE[RUςˆ^[RURΩΆ =Ρ½₯ͺdΆ€ͺR€Ϊͺ¨(ΦύnΊ ?\¬›‰Hωύοqγσ9±|ηΌψ"ΖΗΡέ=Ÿϋ‰Ρݍ‘!όε/Ε½λŽV’‰rn(m%•mΫ’8H[Ι#ΚZCi+Iψ’*"m%ΏTE€­Έγ£ͺˆ΄_όU‘Άβˆοͺ"VΌˆ‡ͺˆ΄β€*"mωOόTE€-Ÿ‰«ͺˆ΄ε'ρV‘Ά|# ͺ"–?$GUDΪς€©ŠH[Ρ’LUi+*’¬*²s'ΊΊ’D‰‘|Ui«˜D₯ͺ²ξ μά‰γΗΡΣΙΝηJQαΒ(+ΛΧάΆ Έϋξ(Α=ΔA[A¬Z΅jύϊυ~ϋΫίΘd2N:`ΥͺU>ώψc###ZZZTUUψδ“OjkkΤΤΤP__ΰτιΣ***°ΟψψΈυΉθ’‹LNN¨¬¬pςδIλY__Ο;.]Ίΐ§Ÿ~j£:pΰ@ž°ξΎυυΡ¨ σ{όkQθξFs³b’+ξΎuu‘© :ρΩ·‚ X»vνψC/Όπ€}ϋφ¨0;; `Ω²eΘΕ ςςr333ό“C{cT]Ύ|9€ώσŸ Ϋ·a‡ϋΧΏώ ΅΅ΐττ4rnwθΠ!:εθθ(rŽΥάάΜ―Μ±¨ͺŸΌI_He”7Ί»Οδς~j«ΏΏŊ< 95448vμr΄δθΡ£¦¦¦–,Y`bb9ι°Ν?1©EΆy©K.ΉΐκΥ«Œ!.W―^ΝS(n*•Ÿ>¨ †BC1qρDUˆά±ˆ·ΎΥΦΦvόψqδd†z?§oMMMΩ)l766¦R)τ-žH―bΘcJΞ`700 ©© ΉGSdžN[’η>}š FΖpgψ€*ψΰXDΎ΅`ςGUπΔ±ˆ‡Ύ544ΔΌŠ)9 τŒ‹/Ύΐ‰'πωŠΧΪ΅k °|ΐμžŽΕ‹0gbbΔ„ŒžΔ,jεΚ•Θ%ψW\q€#GŽ g„K—.ε‰,p0-[±bE{ϋ0βͺΰcωΦίήΒE?c¦uΫmGR©`Λ–I`2ό"S¨πƒφεεεαΙ&/ž!ϊCr‹ΔΞ·::F2™ §§!κ,2‰r,²ˆΎΥΠΠΐi‘[Πlψ<43šΏe:gsόzΦ«θIœNή{oE&SήΣΣP^~f²Ι)λοαέζXμΐ―hfƒoDόΆwττΰΨ±i+‚•+Wή~ϋνž|ςIδB—PΎτ₯/!—Sσ_~~ψπa.*3Ηg¬€μύ3#ΐΨ}χ₯Cω'/ήKΘ°H5ωΛ_¦^Ή‡₯ &ςΓΓΓ…/^‹€…BΓσ˜ΨΡ1RS3{ί}©¨β ]t±XxLœ™™)άCγαητ*Ζ8φαΞ㦦&v  ψΙΔΔΔζΝ㍍5{χΆ67#We-ƒ‘χ£>B.΅gθdŸ#GŽπRœF0»η“aΎ‘XΗ"ϊΦζΝγ X±9/Iv,²ίͺ­­e”IOψQ0&@τ°²ΐ>ΣΣΣLΕhfΌΘψψψwž¬¬,Ϋ±£ΎΆφ³έyαΒ&ΊTxk2Ϋ³³³,ŠZ.άtΑ7ξX€« ­­ΨΊ5βaάyηɚšΩ;κ"GQHΎc:ΦΦ­θ뻀³†††ψ:Pš o‘‘“±ά@―btff†)Ώά΄i΄Ύ>ύπΓ+fg"W†ΰEΨ“žd―0E.'ϋΚWΎ‚\…6•Jq‡§°‹΄ΎQŽE:;Ρ֍omΪ4RS3ϋπΓmά;"JΕ±Θ…ϊV*•βL‰Nx^x>ΘOθdL§jjjx811Ρή~rι%>Ίζύχΐε—_Žά†ΗO•ρ²<—™wςΝΝΝ΄1ή—›ώhuΎQBŽEŠμ[νν'««g}tM‘ξη ₯εXdξΎ599~ΩZψ|7=ƒΦ~oΡΐΐΐδδdgηdeezηΞƊŠΟV‹:d§„ίΟ2:Oο+€Ÿ1uλοο?1Ζ}8y―γφΗTŠΰ[“55θλσχMCN)EΗ"sρ­ΊΊΊπΣξ4Φψ˜g‹΄~K?»ηΫ·gιδͺS|‘«κL•ΒE|ΊTΨØoΡ–8lnn¦₯ή—ιšo”¨cGΎuη'ι`ϋφΜ"_7V”c‘σϊmƒs=¦;΄– XŽβ†wί}ΐCΥ¦ΣΑž=KW―Cn3ίγΝ©%ί1Ιy_ΈˆΟ>\j€K…½Ν~/£_ςέέαχMϊCI;YDίΪΆm&φμi>Χ€SκŽEΎΘ·Nœ8Α©M…ήΐ,‡kvL†˜Bmߞ­ž½ώ*`€ΎΎžOάΣ‡X£bŽ.zρtfWάbEhάΒΐ”Ž»΅Ž9Β9 w4„_Xβr¬3,Π·::†««gwοŽλρ‹Žλ3 }kllŒωmƒS3Ί³œά‰“ΥΥAoo0C³™ššβ*!'n΄–π“E/ΪKSτ0fTL€h“΄Fne――η}ιš~ψ!>ͺpc}ŽyψΦ]w§ΣAOΚDˆ+σ-«W―׊.½τRδζ‰4•©tΊκ—Ώ,N1U’Α”——σZιa<‘ΆG³ Ώ’“MήΒvœ"·ιͺŽFVΛxAžβr¬³@ίΞ³β³yσX:μΪεϋ›±#Α£η:|#ΆΎόςώλϋ/ΏωΝos#fZ7άπ·ΚΚ©Ώύfδ’!Z ­­)ζOτv?½ΓY!½Ν~— r›Ωi,‘Ω8α½_\»δvψ7κ)ΈΠ›ΙŒœΥ·½φεΚΚ©W^ω~ρΗ<ΈoAΠςη?ud€vέΊ}ΘUŒ~ρ‹²t:ψΓ.Γη熴ΪOkk+λU<€Ιρ‘CΪLψμΙ‹γ8p9W£Ωο΅ WΤψΐ*;ΌσΞ;r¬8ρβ‹ί―«½ύφ3Ώa«»{:|0NoƎΝ ΟΕΕ_Α_ϊο­­o[7ΤR]=»}{fι35$nHη,ξΠ’! ‡ift¦p.Ŋ|ψ Dζaα ^4'Άƒ ΰW<…Oξsšι™§o„_dψoΰYΰ‰hF3Ό …bxΉOS!„B!„B!„B!„B!„B!„B!„B!„B!„"qΜηU]o$ §H―t“°JŠωύsλ5uΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ –p‚„%œ a 'HXΒ •Q`ρioogγ‘G‰v$₯ŒK8AΒN(›Η9A”•ΝηD§¬[·Žο|η;l|τΡGxΰΘΖ”ζχΟ-ΗNˆ½cmάΈ‘ο~χ»lΌύφΫl|όρΗΖΖΖxψΜ3Ο}tI@Ž%§¦¦Ψ¨­­ecddΐsΟ=ηvθ1G‘PxD ««« ΐUW]ΕC+S½υΦ[ωS*•b£ΌόΜ–t: `||œ‡f]όάzZ‘λρΗwχSΔ9–π K8ΑίPΘΰ›ίό&€wί}—‡dγδΙ“²%K–°199ΙFUUUψ‚"-h²βe‘qhhˆηŸ~Q”x£P(`£ΏΏ‘ς¦m^`γn>„φ2XE”Ν"`}}½?―'±5λΐF‰O …GDοX·έv€›nΊ‰‡―Ώώ:V―jllDΘ]Μ±X¦2£²! ³AK³Εi+_YnΞKUWWσΠ’zKY³ώ₯ΉKŽ%ωδ\~δΈ£P(<’HŽe―oΌζškΨ0‹zηwZ Ά‡ώ˜›#gQf3Vv’3YρΙ2kƒe6c'ZšOS4²ΖΔΔDΈƒ˜χΉyηŸώτ§Ήύ5Δ9–π K8Αω[“o½υVΧ]w-8p€ ¬Β…‹q| Π:δ­[Kφ­ Ε+ΨηdνΚy'ΪΌΑV£¬ξeα˜,DώψΗ?fγ±Η;ίίG© ΗN°„\Ν -:\ύυή{ο=ΪρΆΐΒY›E X6O΄Pξ\΄ΘhΛΖƐgWnhh`Γ–hx) vb^‘ΛB^ήN/{.θψργlΨΓϋI*qiV(o}βΔ ςA#Δσ9…Bα–pΒv 6 Ν’Ξ”χ› r¦/ϊ1Νσ’΄aλ¬Θ±„$,α„y&ο\Ι°fΝ_ϊΧyψK/±qτθQ6/,³ΆΘei,Wl 75ngΘϋ<οW>[ΓΚNΌiήVζΒKε%ϋ(xgdᎠ6,tZΰΛ{ΌΗFbΡωΩgŸEάPς.}ϊΐ˜H$μ.1ΖμίΏίœ:u Ζ«ΊΚΓxη{―ΏΎkρβ’Ύ}ϋΒXVVfΎϊκ+ϋχοoΪΫΫaμΥ«—=—·ΆΆκΛ―½φZ{ΠΡΡcQΡ…Θ_~ω%ŒςVx(~‘1fπΰΑφΰΔ‰0βoR__Ÿ†°’1Ε>Uυ0kΧοΩS𳟡§>5ΦD@XR•Εjλ§?=™λΙ%a_»![ͺ*//·ιŒMκκκ`,))±»vν‚qθΠ‘φ`ΐ€0^ψΏεΉsηœ3όgS^~μΉηN/_>ΠsϊτiηcLΏ~ύ쁢***μΑ‘C‡`„ˆKŒ[ZZ`Όϊκ«ν†TcLοή½qŒ_*ϊ“'O:ΞUiκ/VδΎU’ίΎ_SSοεˏηϊErCx…iUYςY[!V Te±ΪZΉςTκSγE},ͺ:xπ =Έζšk`ά»w―=Ύ BkιN9rΔ 4HίόπαΓ8ξμμ΄•••φ`ύϊΎχάΣϊΜ3'~όγR™QkkksŒΘ\Ho FΌ†1fμΨ±φ`δΘ‘0ž9sΖ ο`DΆBž,ο·rΨ°a0Κ t_¬Ψ|«$ΥΥΕ{χΎπB[κSγBΈ„KUY¬ΆV―Ξ—όVˆ„cUYͺ«‹ ςD[aρ±|«jȐ!φΰΨ±c0’j!KpΒδ™π·ΰBI€‰(™‚ΏυΚ+',θX½Ίύ'?)ƒk%K.Θi!#eDNk Ψ;Ή1YoA‘FzK±CωHQηΑ=MZυAI(ΎX±VIͺ«‹›šz­Z•H}j”Ι½°ςJU«­εΛΏL}jdΙρPΨcͺB_€Μ, ƒ>#ƈ#Fΐxόψqη_%γǏΗ1†-Ω‰€ŠΖάκκβόΐ,[vόι§ΘΤ‚|™ΒΐΈ&Γ‡·ϋφνƒqςδΙφΰ‹/Ύ€QŽΤhdΩ΄lΘt†ΜΛ€A.ΏXyψ­’¬[Χ·©©Χ³Ο~•ϊΤ’3aεΉͺ,V[+VΔ°"7Β’ͺΐΊu}››‹β§­ψX9Qͺ"²]₯ ιp`γΒ={φΐˆ8_&Πd‚ϋ‘ Β ’nυ—ΏάpΟ=‡_|±ύη?Ράάl² $&€w–޲“&M‚Qώψˆ‡CLRHΟ/ zϊ‹ΕoUR^ύκ––’ύhκS#B ‹ͺκ«­ηž;“λΙ=7ζVU e·§l+ΠΖͺͺ*1–%M²Λ„5Bw9Η‘΄΄T?£:/~ωKσπΓεΏψΕΩ+Λ Y`.Σ}€±LφΔ’QΦsτθ…Mΐmwή Θn4θ‘/ΏUΩ°‘’ΉΉθ©§"ΏΗ]O‹ͺΊ"¬Ά’žίς.,ͺ* 6l¨Ψ·―θ™g"¬-Ώ>VxT…ΨξŽŽ—Œ½αpΘ)HΘB ό-™Β€γ%}0uκ¨ση»Μ₯‰9™I™ψι οε±’₯­xσρΗ΅³fooΛj”™&H©­0pQUα1Y)BS[Ή%„ͺ2ΩκnΏΆf#ž7ΒΛ‘ ¨κΛyp­’nΚ%έΈV²¨/JfpΉμπDρGnΝ/J§`―jh88yςˆφφ ―Š–Ω―!έ5Όžτφm‘«cΚΏId­m&όڊ 'Lvξ\ΈΎU–lφΌS[=‰UU{{Af«Βψ"Λ/Zma 9αaΆυ°ι—4ΫΛF™YΐX&ΟΔX&7ϊB?ις“²ΉΓ¦]>|jβΔα—$ 0ΤΚ5“,GU,θ +r,Nƒμ«=΄ΪŠ ‡ŸΊξΊ~%%a—Ο(΅ε«ͺΆ6“Yώ;ΎΖgjΛPUψρθψ…J[θ Λ.βX6aΒ‘ΩΉs'ŒθJžR²}s1dΟJ1زˈ΅!dΙž™œqjυυ-¦­ν”σžrνδ δΖ²‘χOZq’ΛU$­σΗoD*mEšϊϊ–I“†·΅΅€>5xU©­Μ±ͺ [n½{z"m!Λ [ €0XΘ­œΛa WΙ$r2χ3εΦθYIp€ψ±γf]]ΣδΙ#:; /™λ‡ΜΈœ˜tb‘P&Φq•@3\5Ή‡’kaΠV©«kš2ed{{’DWΘιΉ¬-΅u₯ΤΦ6M:²½½ΐ «[¨­ΰΤΦ6M›6²­-J~•€§λLΉ\ Y½AQE†ΩΘAŒ=FτAΘ₯Π5 ΟΔΔ ι·‘:$χ*‡#S6Θ――?0|xi"αN.•½π₯;…Bμ˜Ε₯1cΖΨιbi ι%έ#89(`ς»Υ=υυΎρk‰γ©O 1Ή©ŒS[—Γͺ*Ί# ΘYΛEk ±}ΚΕ0BΙ31FΘqΉ\λ 2\Gl4ΐ 4χν;:{φΕΕβΙƈaKŽγΘ›466ˆ|½LaΘQœ|d£ŸάΟ" rΩΛΓο–dίΎ£UUƒϊυ‹ό·Κ’γ&1jΛbUΥΦV ς―Ρ&χέ‡ΤT•λΙ&Ή–ιm!P—€(Ώ$έBφpΒ#‘a5ΎDOX&άΪΊ¨ͺΌσΦ"),“Άd>sœc ’ύh:λGΦΦΦΪ4|φٞ™3ΗΆ·»λ,`ωIΉ'%ςώUUU0".;/—c‡υ0ͺΚ‘TτLεnτ‡ί$=’*,Ύο–UΏU– Λ„I[T•C΄…eΒ‘-ͺJya™`ΪBΘ-»ΰ»HqΈœn€^ζ ¬G²}ϋή‰―N$N;χΗ6™FψF²=醀2‰Η‹,H€ƒˆ©§r>ͺ\ρέ€²bƒΉ©IwbO8Λδξ»΅}ϋήιΣGeΈ$z,‰‰°L.΄US³wƌQ“a™n΅…<²Μ`ΌΉiŒ 2σΊοή}πζ›G΄Άž7—F^ε΄ d΄εXƒ€l©KΪ…N9d#‘ xΊΗε杩ε―ΓθίΠΠc[f«~ΗJX¦§Ύ[»wœ2eXGΏU—%nΒ2ώ΅eUΥΪέ‘BB<6ώ΄UeωΎ±#žΒ2J[(°$-ΰίpΓ 0’ζ#}λ™mίή4n\Ekλη#ι:πdwœ99kN˜|<<ιβΰrٚŠ“,Rι5·Μ₯«0ΰ‘²„ζ ιʊVΔVXFh++lίή4cΖΘΦΦ“©O%ρ–Ή¨­;οΜtDΌ¨*Ž€A‰Ή°ΜmύχίώΔ[o=hŒYΈp!ώ Y9–Αˆ‚=φδΪ΅Ο?τP±Ήt™! 7rxΕ#׏DΎ@ŽJΈ\φά!"3οUeΩγ—\ί£³œκ˜t#Μ€‹PΘI%rΙ4Θ“inwnΫφowέ΅.+{μΙ5k–uv&Y>„tCόΏX–††ρƘ»ξZgΜƒ_U‰O DΎΛ\ΤΦΨ±ΡΥυAΞη·*ςΒνκκBΑo~σοS§~τ§?έgŒyυΥW­QVς­oΡrβ;ί™˜H\p’–wΰΗΘΜ6Π’•iœ kGΨΟBz6ΘHw ·’ιμ©Y__#\4ιWΙRdC€³ˆJ‘άύ wΨ±cGR/­{ςΔΗϊšϊϊqϊ׌ο}oc7η΄΄œ¨ͺU‘4ΘΗΏ]χΪ²ͺbf!CςΕΗ1b„=°aόξέU]]]Ώϋέ>πΐ`cΜ”)SμΏnήόΡΜ™cϊτ)μΣη’R?V^” ύ`ŽžΛ0EB.ύ€d\$‚l D>]IΘV$]NBφAΘυ›‘ŐWαώrŸNYHƒ|όbYκλΗmΪTΎ~ύΧ‘›7oΉγŽ›8f…Όώ#ώγeΠΦζΝ[ξΈcZ"ΑΌBvΘ OBf±“ρŽ1/σΆ1ω·vG0ˆ ‰1ΖdΆΡ!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„BBHϊ»:₯ΪR‹ΔμϋEaŞLώ'Ξλ0‰?(,β ‹xΒ"^ °ˆ(,β ‹xΒ"^ °ˆ(,β ‹xΒ"^ °ˆ(,β ‹xΒ"^ °ˆ(,β ‹xΒ"^ °ˆ(,β…’\ΏΉ„… :–5kΦδδM2„_,β ‹xΒ"^ °ˆ(,β…ŒV›ΙΑR$ρbήΌyŽeφμَ₯±±Ρ±¬X±Βγ; 2ωŸ˜_,β ‹xΒ"^ °ˆ(,βF…=Η£>κXΎύνo;–νΫ·;–ύϋχ;–3gΞ8–7ήx#γ·K£B:(,β ‹xΒ"^ °ˆϊbώόωŽεφΫow,Ÿ}φ™c©««s,ηϟw,ηΝs,ύϊυs,―½φZΐχμF…$tPXΔ ρ…EΌ@a/0*̏<ςˆc™3gŽcΩΉsgJK"‘p,eee)ŸήήήξXΚΛΛKq"£B:(,β ‹xΒ"^ °ˆ¦Γγ?ξXfΜιXt°©©Ι±τκΥΛ±θ?© κ=ΐ;;;KίΎ}Λ©S§Λ›oΎiΊ…Q! ρ…EΌ@a/PXΔ Œ SσΔO8–ιΣ§;έωYSSγXτŸ«wοގ₯°ΠύΏzii©cimmu,:NΤWι;;ρζϊυλυm’pAa/PXΔ ρ…EΌΐ)\t xΣM79½&Μή½{K[[›cΡUΏΚΚΚ”Wιy…]sΤθ5jœΘρξ»οNy“ΰπ‹EΌ@a/PXΔ ρ…EΌοQα‚ ‹ξύψγKCCƒc)*r’ΊZ§gitEOΧυ9:*Τύ’ŽΕ‰@ϋτι“ςυ‚Γ/ρ…EΌ@a/PXΔ ρB~E…‹/v,γǏw,[·nu,t,%%%ŽE―£#5'κXRΗnzΞ`΄ώŽEχ: ZƒΓ/ρ…EΌ@a/PXΔ qvή|πAΗ2cΖ ΗςΙ'Ÿ8έ²WQQαX‚LΐΞ»^¨CO~ΪUΧwΦ-„ΪrφμYΗβ„ ϊΡ™ΐ/ρ…EΌ@a/PXΔ ρB|’ΒE‹9–iΣ¦9½|css³cΡ1—ž8dΑ~έj§'rYτQG…ϊ>ϊ ‹‹‹‹Ž@_ͺΙLΰ‹xΒ"^ °ˆ(,β ‹x!ͺQαΌyσ‹žΆυ駟:–έ»w;½i›ŽΉθXt;žΔι8QŸ£1έB¨£BΎsΒŸσt½ne&π‹EΌ@a/PXΔ ρ…EΌ¨pαΒ…ŽeΦ¬YŽeΧ]Ž₯±±Ρ±θ šž8₯k|'Nœp,:š »ι cu8t·jXRW ΎξΥύ,©ι(UΏΎJΧ%υUη>Ίlš όb/PXΔ ρ…EΌ@a/δ>*|葇Λ­·ήκXτ6nMMMŽEWΠt%NwZjμX‚l즟d™]» KΩ<(U―v™ όb/PXΔ ρ…EΌ@a/τtT¨·q›3gŽc©­­u,:L/ζ–ŽuΜ₯ϋ3ƒ\₯γD]κ΅eτ}‚Tυ}τo?vμ˜cq~W98όb/PXΔ ρ…EΌ@a/ψ υš0Σ§Ow,;vμp,ΊTΗeAβ)Mυ^τ9Ίˆd·M‰{ϊ}tw¨ŽψτκΘ±²²±8έͺ§OŸNωzΑα‹xΒ"^ °ˆ(,β ‹x!ΛQα½χή+Sχ‚κˆ―₯₯Ε±θΉuza‰ υ}τj’:.0`€cΡݘzΝO»ΩcBί9Θ6ζϊΞz^‘9:ξLA"…EΌ@a/PXΔ ρBFQα₯KΛΤ©SεΦΥΥ9'θ¨PΧζ‚t~κ₯Qt©KG|:z¨ŽΤ‚΄Vκϋθg‰έth¦γM]υΣ½BŽ·:ΏT%3_,β ‹xΒ"^ °ˆ(,β…Τυ΅Λ‘«ZƘ—^zIώη|ΰœ g·UTT8a™£§gκhN[tτ€—Ύ³ŽΒτκϋœ= '3' else (int, long) check_param_types(param.child('int'), inttyps, int, 0, all_objs, types) # str (should be able to make a string out of any type) types = all_objs.keys() strtyp = str if sys.version[0] >= '3' else unicode check_param_types(param.child('str'), strtyp, str, '', all_objs, types) # bool (should be able to make a boolean out of any type?) types = all_objs.keys() check_param_types(param.child('bool'), bool, bool, False, all_objs, types) # color types = ['color', 'int0', 'int', 'float', 'npfloat', 'npint', 'list'] init = QtGui.QColor(128, 128, 128, 255) check_param_types(param.child('color'), QtGui.QColor, pg.mkColor, init, all_objs, types) def check_param_types(param, types, map_func, init, objs, keys): """Check that parameter setValue() accepts or rejects the correct types and that value() returns the correct type. Parameters ---------- param : Parameter instance types : type or tuple of types The allowed types for this parameter to return from value(). map_func : function Converts an input value to the expected output value. init : object The expected initial value of the parameter objs : dict Contains a variety of objects that will be tested as arguments to param.setValue(). keys : list The list of keys indicating the valid objects in *objs*. When param.setValue() is teasted with each value from *objs*, we expect an exception to be raised if the associated key is not in *keys*. """ val = param.value() if not isinstance(types, tuple): types = (types,) assert val == init and type(val) in types # test valid input types good_inputs = [objs[k] for k in keys if k in objs] good_outputs = map(map_func, good_inputs) for x,y in zip(good_inputs, good_outputs): param.setValue(x) val = param.value() if not (eq(val, y) and type(val) in types): raise Exception("Setting parameter %s with value %r should have resulted in %r (types: %r), " "but resulted in %r (type: %r) instead." % (param, x, y, types, val, type(val))) # test invalid input types for k,v in objs.items(): if k in keys: continue try: param.setValue(v) except (TypeError, ValueError, OverflowError): continue except Exception as exc: raise Exception("Setting %s parameter value to %r raised %r." % (param, v, exc)) raise Exception("Setting %s parameter value to %r should have raised an exception." % (param, v)) def test_limits_enforcement(): p = pt.Parameter.create(name='params', type='group', children=[ dict(name='float', type='float', limits=[0, 1]), dict(name='int', type='int', bounds=[0, 1]), dict(name='list', type='list', limits=['x', 'y']), dict(name='dict', type='list', limits={'x': 1, 'y': 2}), ]) t = pt.ParameterTree() t.setParameters(p) for k, vin, vout in [('float', -1, 0), ('float', 2, 1), ('int', -1, 0), ('int', 2, 1), ('list', 'w', 'x'), ('dict', 'w', 1)]: p[k] = vin assert p[k] == vout def test_data_race(): # Ensure widgets don't override user setting of param values whether # they connect the signal before or after it's added to a tree p = pt.Parameter.create(name='int', type='int', value=0) t = pt.ParameterTree() def override(): p.setValue(1) p.sigValueChanged.connect(override) t.setParameters(p) pi = next(iter(p.items)) assert pi.param is p pi.widget.setValue(2) assert p.value() == pi.widget.value() == 1 p.sigValueChanged.disconnect(override) p.sigValueChanged.connect(override) pi.widget.setValue(2) assert p.value() == pi.widget.value() == 1 pyqtgraph-pyqtgraph-0.12.4/tests/test_Point.py000066400000000000000000000027331421045507400215050ustar00rootroot00000000000000import math import pytest import pyqtgraph as pg from pyqtgraph.Qt import QtCore angles = [ ((1, 0), (0, 1), 90), ((0, 1), (1, 0), -90), ((-1, 0), (-1, 0), 0), ((0, -1), (0, 1), 180), ] @pytest.mark.parametrize("p1, p2, angle", angles) def test_Point_angle(p1, p2, angle): p1 = pg.Point(*p1) p2 = pg.Point(*p2) assert p2.angle(p1) == angle inits = [ (QtCore.QSizeF(1, 0), (1.0, 0.0)), ((0, -1), (0.0, -1.0)), ([1, 1], (1.0, 1.0)), ] @pytest.mark.parametrize("initArgs, positions", inits) def test_Point_init(initArgs, positions): if isinstance(initArgs, QtCore.QSizeF): point = pg.Point(initArgs) else: point = pg.Point(*initArgs) assert (point.x(), point.y()) == positions lengths = [ ((0, 1), 1), ((1, 0), 1), ((0, 0), 0), ((1, 1), math.sqrt(2)), ((-1, -1), math.sqrt(2)) ] @pytest.mark.parametrize("initArgs, length", lengths) def test_Point_length(initArgs, length): point = pg.Point(initArgs) assert point.length() == length min_max = [ ((0, 1), 0, 1), ((1, 0), 0, 1), ((-math.inf, 0), -math.inf, 0), ((0, math.inf), 0, math.inf) ] @pytest.mark.parametrize("initArgs, min_, max_", min_max) def test_Point_min_max(initArgs, min_, max_): point = pg.Point(initArgs) assert min(point) == min_ assert max(point) == max_ projections = [ ((0, 1), (1, 0), (1, 1)) ] @pytest.mark.parametrize("p1_arg, p2_arg, projection", projections) def test_Point_projection(p1_arg, p2_arg, projection): p1 = pg.Point(p1_arg) p2 = pg.Point(p2_arg) p1.proj(p2) == projection pyqtgraph-pyqtgraph-0.12.4/tests/test_Vector.py000066400000000000000000000030211421045507400216450ustar00rootroot00000000000000import pytest import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui def test_Vector_init(): """Test construction of Vector objects from a variety of source types.""" # separate values without z v = pg.Vector(0, 1) assert v.z() == 0 v = pg.Vector(0.0, 1.0) assert v.z() == 0 # separate values with 3 args v = pg.Vector(0, 1, 2) assert v.x() == 0 assert v.y() == 1 assert v.z() == 2 v = pg.Vector(0.0, 1.0, 2.0) assert v.x() == 0 assert v.y() == 1 assert v.z() == 2 # all in a list v = pg.Vector([0, 1]) assert v.z() == 0 v = pg.Vector([0, 1, 2]) assert v.z() == 2 # QSizeF v = pg.Vector(QtCore.QSizeF(1, 2)) assert v.x() == 1 assert v.z() == 0 # QPoint v = pg.Vector(QtCore.QPoint(0, 1)) assert v.z() == 0 v = pg.Vector(QtCore.QPointF(0, 1)) assert v.z() == 0 # QVector3D qv = QtGui.QVector3D(1, 2, 3) v = pg.Vector(qv) assert v == qv with pytest.raises(Exception): _ = pg.Vector(1, 2, 3, 4) def test_Vector_interface(): """Test various aspects of the Vector API.""" v = pg.Vector(-1, 2) # len assert len(v) == 3 # indexing assert v[0] == -1 assert v[2] == 0 with pytest.raises(IndexError): _ = v[4] assert v[1] == 2 v[1] = 5 assert v[1] == 5 # iteration v2 = pg.Vector(*v) assert v2 == v assert abs(v).x() == 1 # angle v1 = pg.Vector(1, 0) v2 = pg.Vector(1, 1) assert abs(v1.angle(v2) - 45) < 0.001 pyqtgraph-pyqtgraph-0.12.4/tests/test_colormap.py000066400000000000000000000040421421045507400222230ustar00rootroot00000000000000import pytest import pyqtgraph as pg from pyqtgraph.Qt import QtGui pos = [0.0, 0.5, 1.0] qcols = [ QtGui.QColor('#FF0000'), QtGui.QColor('#00FF00'), QtGui.QColor('#0000FF') ] float_tuples = [ (1.0, 0.0, 0.0, 1.0), (0.0, 1.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0) ] int_tuples = [ (255, 0, 0,255), ( 0,255, 0,255), ( 0, 0,255,255) ] @pytest.mark.parametrize("color_list", (qcols, int_tuples)) def test_ColorMap_getStops(color_list): cm = pg.ColorMap(pos, color_list, name='test') # default is byte format: stops, colors = cm.getStops() assert (stops == pos).all() assert (colors == int_tuples).all() # manual byte format: stops, colors = cm.getStops(pg.ColorMap.BYTE) assert (stops == pos).all() assert (colors == int_tuples).all() stops, colors = cm.getStops('bYTe') assert (stops == pos).all() assert (colors == int_tuples).all() # manual float format: stops, colors = cm.getStops(pg.ColorMap.FLOAT) assert (stops == pos).all() assert (colors == float_tuples).all() stops, colors = cm.getStops('floaT') assert (stops == pos).all() assert (colors == float_tuples).all() # manual QColor format: stops, colors = cm.getStops(pg.ColorMap.QCOLOR) assert (stops == pos).all() for actual, good in zip(colors, qcols): assert actual.getRgbF() == good.getRgbF() stops, colors = cm.getStops('qColor') assert (stops == pos).all() for actual, good in zip(colors, qcols): assert actual.getRgbF() == good.getRgbF() @pytest.mark.parametrize("color_list", (qcols, int_tuples)) def test_ColorMap_getColors(color_list): cm = pg.ColorMap(pos, color_list, name='from QColors') colors = cm.getColors() assert (colors == int_tuples).all() colors = cm.getColors('byte') assert (colors == int_tuples).all() colors = cm.getColors('float') assert (colors == float_tuples).all() colors = cm.getColors('qcolor') for actual, good in zip(colors, qcols): assert actual.getRgbF() == good.getRgbF() pyqtgraph-pyqtgraph-0.12.4/tests/test_configparser.py000066400000000000000000000014101421045507400230650ustar00rootroot00000000000000import numpy as np from pyqtgraph import configfile def test_longArrays(tmpdir): """ Test config saving and loading of long arrays. """ arr = np.arange(20) tf = tmpdir.join("config.cfg") configfile.writeConfigFile({'arr': arr}, tf) config = configfile.readConfigFile(tf) assert all(config['arr'] == arr) def test_multipleParameters(tmpdir): """ Test config saving and loading of multiple parameters. """ par1 = [1,2,3] par2 = "Test" par3 = {'a':3,'b':'c'} tf = tmpdir.join("config.cfg") configfile.writeConfigFile({'par1':par1, 'par2':par2, 'par3':par3}, tf) config = configfile.readConfigFile(tf) assert config['par1'] == par1 assert config['par2'] == par2 assert config['par3'] == par3 pyqtgraph-pyqtgraph-0.12.4/tests/test_functions.py000066400000000000000000000340361421045507400224250ustar00rootroot00000000000000from collections import OrderedDict from copy import deepcopy import numpy as np import pytest from numpy.testing import assert_array_almost_equal import pyqtgraph as pg from pyqtgraph.functions import arrayToQPath, eq from pyqtgraph.Qt import QtGui np.random.seed(12345) def testSolve3D(): p1 = np.array([[0,0,0,1], [1,0,0,1], [0,1,0,1], [0,0,1,1]], dtype=float) # transform points through random matrix tr = np.random.normal(size=(4, 4)) tr[3] = (0,0,0,1) p2 = np.dot(tr, p1.T).T[:,:3] # solve to see if we can recover the transformation matrix. tr2 = pg.solve3DTransform(p1, p2) assert_array_almost_equal(tr[:3], tr2[:3]) def test_interpolateArray_order0(): check_interpolateArray(order=0) def test_interpolateArray_order1(): check_interpolateArray(order=1) def check_interpolateArray(order): pytest.importorskip("scipy") def interpolateArray(data, x): result = pg.interpolateArray(data, x, order=order) assert result.shape == x.shape[:-1] + data.shape[x.shape[-1]:] return result data = np.array([[ 1., 2., 4. ], [ 10., 20., 40. ], [ 100., 200., 400.]]) # test various x shapes interpolateArray(data, np.ones((1,))) interpolateArray(data, np.ones((2,))) interpolateArray(data, np.ones((1, 1))) interpolateArray(data, np.ones((1, 2))) interpolateArray(data, np.ones((5, 1))) interpolateArray(data, np.ones((5, 2))) interpolateArray(data, np.ones((5, 5, 1))) interpolateArray(data, np.ones((5, 5, 2))) with pytest.raises(TypeError): interpolateArray(data, np.ones((3,))) with pytest.raises(TypeError): interpolateArray(data, np.ones((1, 3,))) with pytest.raises(TypeError): interpolateArray(data, np.ones((5, 5, 3,))) x = np.array([[ 0.3, 0.6], [ 1. , 1. ], [ 0.501, 1. ], # NOTE: testing at exactly 0.5 can yield different results from map_coordinates [ 0.501, 2.501], # due to differences in rounding [ 10. , 10. ]]) result = interpolateArray(data, x) # make sure results match ndimage.map_coordinates import scipy.ndimage spresult = scipy.ndimage.map_coordinates(data, x.T, order=order) #spresult = np.array([ 5.92, 20. , 11. , 0. , 0. ]) # generated with the above line assert_array_almost_equal(result, spresult) # test mapping when x.shape[-1] < data.ndim x = np.array([[ 0.3, 0], [ 0.3, 1], [ 0.3, 2]]) r1 = interpolateArray(data, x) x = np.array([0.3]) # should broadcast across axis 1 r2 = interpolateArray(data, x) assert_array_almost_equal(r1, r2) # test mapping 2D array of locations x = np.array([[[0.501, 0.501], [0.501, 1.0], [0.501, 1.501]], [[1.501, 0.501], [1.501, 1.0], [1.501, 1.501]]]) r1 = interpolateArray(data, x) r2 = scipy.ndimage.map_coordinates(data, x.transpose(2,0,1), order=order) #r2 = np.array([[ 8.25, 11. , 16.5 ], # generated with the above line #[ 82.5 , 110. , 165. ]]) assert_array_almost_equal(r1, r2) def test_subArray(): a = np.array([0, 0, 111, 112, 113, 0, 121, 122, 123, 0, 0, 0, 211, 212, 213, 0, 221, 222, 223, 0, 0, 0, 0]) b = pg.subArray(a, offset=2, shape=(2,2,3), stride=(10,4,1)) c = np.array([[[111,112,113], [121,122,123]], [[211,212,213], [221,222,223]]]) assert np.all(b == c) # operate over first axis; broadcast over the rest aa = np.vstack([a, a/100.]).T cc = np.empty(c.shape + (2,)) cc[..., 0] = c cc[..., 1] = c / 100. bb = pg.subArray(aa, offset=2, shape=(2,2,3), stride=(10,4,1)) assert np.all(bb == cc) def test_rescaleData(): dtypes = map(np.dtype, ('ubyte', 'uint16', 'byte', 'int16', 'int', 'float')) for dtype1 in dtypes: for dtype2 in dtypes: data = (np.random.random(size=10) * 2**32 - 2**31).astype(dtype1) for scale, offset in [(10, 0), (10., 0.), (1, -50), (0.2, 0.5), (0.001, 0)]: if dtype2.kind in 'iu': lim = np.iinfo(dtype2) lim = lim.min, lim.max else: lim = (-np.inf, np.inf) s1 = np.clip(float(scale) * (data-float(offset)), *lim).astype(dtype2) s2 = pg.rescaleData(data, scale, offset, dtype2) assert s1.dtype == s2.dtype if dtype2.kind in 'iu': assert np.all(s1 == s2) else: assert np.allclose(s1, s2) def test_eq(): eq = pg.functions.eq zeros = [0, 0.0, np.float64(0), np.float32(0), np.int32(0), np.int64(0)] for i,x in enumerate(zeros): for y in zeros[i:]: assert eq(x, y) assert eq(y, x) assert eq(np.nan, np.nan) # test class NotEq(object): def __eq__(self, x): return False noteq = NotEq() assert eq(noteq, noteq) # passes because they are the same object assert not eq(noteq, NotEq()) # Should be able to test for equivalence even if the test raises certain # exceptions class NoEq(object): def __init__(self, err): self.err = err def __eq__(self, x): raise self.err noeq1 = NoEq(AttributeError()) noeq2 = NoEq(ValueError()) noeq3 = NoEq(Exception()) assert eq(noeq1, noeq1) assert not eq(noeq1, noeq2) assert not eq(noeq2, noeq1) with pytest.raises(Exception): eq(noeq3, noeq2) # test array equivalence # note that numpy has a weird behavior here--np.all() always returns True # if one of the arrays has size=0; eq() will only return True if both arrays # have the same shape. a1 = np.zeros((10, 20)).astype('float') a2 = a1 + 1 a3 = a2.astype('int') a4 = np.empty((0, 20)) assert not eq(a1, a2) # same shape/dtype, different values assert not eq(a1, a3) # same shape, different dtype and values assert not eq(a1, a4) # different shape (note: np.all gives True if one array has size 0) assert not eq(a2, a3) # same values, but different dtype assert not eq(a2, a4) # different shape assert not eq(a3, a4) # different shape and dtype assert eq(a4, a4.copy()) assert not eq(a4, a4.T) # test containers assert not eq({'a': 1}, {'a': 1, 'b': 2}) assert not eq({'a': 1}, {'a': 2}) d1 = {'x': 1, 'y': np.nan, 3: ['a', np.nan, a3, 7, 2.3], 4: a4} d2 = deepcopy(d1) assert eq(d1, d2) d1_ordered = OrderedDict(d1) d2_ordered = deepcopy(d1_ordered) assert eq(d1_ordered, d2_ordered) assert not eq(d1_ordered, d2) items = list(d1.items()) assert not eq(OrderedDict(items), OrderedDict(reversed(items))) assert not eq([1,2,3], [1,2,3,4]) l1 = [d1, np.inf, -np.inf, np.nan] l2 = deepcopy(l1) t1 = tuple(l1) t2 = tuple(l2) assert eq(l1, l2) assert eq(t1, t2) assert eq(set(range(10)), set(range(10))) assert not eq(set(range(10)), set(range(9))) @pytest.mark.parametrize("s,suffix,expected", [ # usual cases ("100 uV", "V", ("100", "u", "V")), ("100 Β΅V", "V", ("100", "Β΅", "V")), ("4.2 nV", None, ("4.2", "n", "V")), ("1.2 m", "m", ("1.2", "", "m")), ("1.2 m", None, ("1.2", "", "m")), ("5.0e9", None, ("5.0e9", "", "")), ("2 units", "units", ("2", "", "units")), # siPrefix with explicit empty suffix ("1.2 m", "", ("1.2", "m", "")), ("5.0e-9 M", "", ("5.0e-9", "M", "")), # weirder cases that should return the reasonable thing ("4.2 nV", "nV", ("4.2", "", "nV")), ("4.2 nV", "", ("4.2", "n", "")), ("1.2 j", "", ("1.2", "", "")), ("1.2 j", None, ("1.2", "", "j")), # expected error cases ("100 uV", "v", ValueError), ]) def test_siParse(s, suffix, expected): if isinstance(expected, tuple): assert pg.siParse(s, suffix=suffix) == expected else: with pytest.raises(expected): pg.siParse(s, suffix=suffix) def test_CIELab_reconversion(): color_list = [ pg.Qt.QtGui.QColor('#100235') ] # known problematic values for _ in range(20): qcol = pg.Qt.QtGui.QColor() qcol.setRgbF( *np.random.random((3)) ) color_list.append(qcol) for qcol1 in color_list: vec_Lab = pg.functions.colorCIELab( qcol1 ) qcol2 = pg.functions.CIELabColor(*vec_Lab) for val1, val2 in zip( qcol1.getRgb(), qcol2.getRgb() ): assert abs(val1-val2)<=1, f'Excess CIELab reconversion error ({qcol1.name() } > {vec_Lab } > {qcol2.name()})' MoveToElement = pg.QtGui.QPainterPath.ElementType.MoveToElement LineToElement = pg.QtGui.QPainterPath.ElementType.LineToElement _dtypes = [] for bits in 32, 64: for base in 'int', 'float', 'uint': _dtypes.append(f'{base}{bits}') _dtypes.extend(['uint8', 'uint16']) def _handle_underflow(dtype, *elements): """Wrapper around path description which converts underflow into proper points""" out = [] for el in elements: newElement = [el[0]] for ii in range(1, 3): coord = el[ii] if coord < 0: coord = np.array(coord, dtype=dtype) newElement.append(float(coord)) out.append(tuple(newElement)) return out @pytest.mark.parametrize( "xs, ys, connect, expected", [ *( ( np.arange(6, dtype=dtype), np.arange(0, -6, step=-1, dtype=dtype), 'all', _handle_underflow(dtype, (MoveToElement, 0.0, 0.0), (LineToElement, 1.0, -1.0), (LineToElement, 2.0, -2.0), (LineToElement, 3.0, -3.0), (LineToElement, 4.0, -4.0), (LineToElement, 5.0, -5.0) ) ) for dtype in _dtypes ), *( ( np.arange(6, dtype=dtype), np.arange(0, -6, step=-1, dtype=dtype), 'pairs', _handle_underflow(dtype, (MoveToElement, 0.0, 0.0), (LineToElement, 1.0, -1.0), (MoveToElement, 2.0, -2.0), (LineToElement, 3.0, -3.0), (MoveToElement, 4.0, -4.0), (LineToElement, 5.0, -5.0), ) ) for dtype in _dtypes ), *( ( np.arange(5, dtype=dtype), np.arange(0, -5, step=-1, dtype=dtype), 'pairs', _handle_underflow(dtype, (MoveToElement, 0.0, 0.0), (LineToElement, 1.0, -1.0), (MoveToElement, 2.0, -2.0), (LineToElement, 3.0, -3.0), (MoveToElement, 4.0, -4.0) ) ) for dtype in _dtypes ), # NaN types don't coerce to integers, don't test for all types since that doesn't make sense ( np.arange(5), np.array([0, -1, np.NaN, -3, -4]), 'finite', ( (MoveToElement, 0.0, 0.0), (LineToElement, 1.0, -1.0), (LineToElement, 1.0, -1.0), (MoveToElement, 3.0, -3.0), (LineToElement, 4.0, -4.0) ) ), ( np.array([0, 1, np.NaN, 3, 4]), np.arange(0, -5, step=-1), 'finite', ( (MoveToElement, 0.0, 0.0), (LineToElement, 1.0, -1.0), (LineToElement, 1.0, -1.0), (MoveToElement, 3.0, -3.0), (LineToElement, 4.0, -4.0) ) ), *( ( np.arange(5, dtype=dtype), np.arange(0, -5, step=-1, dtype=dtype), np.array([0, 1, 0, 1, 0]), _handle_underflow(dtype, (MoveToElement, 0.0, 0.0), (MoveToElement, 1.0, -1.0), (LineToElement, 2.0, -2.0), (MoveToElement, 3.0, -3.0), (LineToElement, 4.0, -4.0) ) ) for dtype in _dtypes ), # Empty path with all types of connection *( ( np.arange(0), np.arange(0, dtype=dtype), conn, () ) for conn in ['all', 'pairs', 'finite', np.array([])] for dtype in _dtypes ), ] ) def test_arrayToQPath(xs, ys, connect, expected): path = arrayToQPath(xs, ys, connect=connect) element = None for i in range(path.elementCount()): # nan elements add two line-segments, for simplicity of test config # we can ignore the second segment if element is not None and (eq(element.x, np.nan) or eq(element.y, np.nan)): continue element = path.elementAt(i) assert eq(expected[i], (element.type, element.x, element.y)) def test_ndarray_from_qpolygonf(): # test that we get an empty ndarray from an empty QPolygonF poly = pg.functions.create_qpolygonf(0) arr = pg.functions.ndarray_from_qpolygonf(poly) assert isinstance(arr, np.ndarray) def test_ndarray_from_qimage(): # for QImages created w/o specifying bytesPerLine, Qt will pad # each line to a multiple of 4-bytes. # test that we can handle such QImages. h = 10 fmt = QtGui.QImage.Format.Format_RGB888 for w in [5, 6, 7, 8]: qimg = QtGui.QImage(w, h, fmt) qimg.fill(0) arr = pg.functions.ndarray_from_qimage(qimg) assert arr.shape == (h, w, 3) fmt = QtGui.QImage.Format.Format_Grayscale8 for w in [5, 6, 7, 8]: qimg = QtGui.QImage(w, h, fmt) qimg.fill(0) arr = pg.functions.ndarray_from_qimage(qimg) assert arr.shape == (h, w) pyqtgraph-pyqtgraph-0.12.4/tests/test_makeARGB.py000066400000000000000000007220341421045507400217700ustar00rootroot00000000000000import sys from typing import Any, Dict, Type, Union import numpy as np import pytest from pyqtgraph import getConfigOption, getCupy, setConfigOption from pyqtgraph.functions import makeARGB as real_makeARGB try: import cupy except ImportError: cupy = None IN_2D_INT8 = np.array([[173, 48, 122, 41], [210, 192, 0, 5], [104, 56, 102, 115], [78, 19, 255, 6]], dtype=np.uint8) IN_RGB_INT8 = np.array( [ [[16, 69, 62], [66, 132, 135], [220, 80, 36], [53, 34, 68]], [[140, 23, 113], [0, 63, 206], [96, 255, 100], [226, 182, 155]], [[28, 237, 223], [215, 232, 209], [17, 16, 50], [96, 187, 93]], [[220, 193, 232], [134, 168, 150], [55, 64, 221], [96, 108, 227]], ], dtype=np.uint8, ) IN_RGBA_INT8 = np.array( [ [[151, 252, 73, 107], [28, 221, 35, 0], [87, 122, 126, 114], [47, 59, 24, 200]], [[189, 246, 242, 255], [123, 255, 29, 14], [201, 208, 133, 32], [118, 203, 141, 245]], [[133, 131, 248, 81], [4, 84, 99, 40], [40, 167, 119, 150], [13, 158, 108, 21]], [[156, 221, 166, 250], [77, 188, 13, 166], [0, 1, 185, 25], [83, 35, 103, 120]], ], dtype=np.uint8, ) IN_2D_INT16 = np.array( [ [13364, 55041, 40746, 40937], [57612, 34247, 34132, 0], [10109, 56950, 41856, 21479], [14881, 65535, 48079, 11372], ], dtype=np.uint16, ) IN_RGB_INT16 = np.array( [ [[55626, 45263, 0], [19468, 39208, 36391], [33255, 8664, 56991], [37588, 31212, 38295]], [[58933, 16402, 36905], [9928, 23928, 12418], [16461, 47738, 18189], [17004, 39307, 59941]], [[43717, 49573, 9843], [35967, 3891, 39618], [53542, 58151, 29112], [53667, 4092, 35267]], [[15957, 21648, 45238], [65535, 47107, 52049], [6342, 34547, 19902], [43386, 37301, 35095]], ], dtype=np.uint16, ) IN_RGBA_INT16 = np.array( [ [ [13060, 40847, 29621, 46719], [0, 36509, 33525, 56649], [48328, 23093, 47186, 26801], [57336, 12247, 30996, 11691], ], [ [4863, 41121, 32045, 25250], [27779, 65098, 59921, 47771], [8906, 18280, 5066, 48587], [65535, 25758, 27250, 17284], ], [ [52005, 65535, 40746, 65535], [33, 57630, 27750, 42371], [50176, 35079, 19220, 63662], [17702, 5506, 36216, 48303], ], [ [61592, 27692, 37436, 7249], [54653, 39986, 58441, 12819], [20887, 56588, 32440, 85], [13457, 14661, 58972, 48779], ], ], dtype=np.uint16, ) IN_2D_FLOAT = np.array( [ [np.inf, 0.53662884, np.nan, 0.8853132], [0.8496698, 0.88006145, 1.0, 0.06621328], [0.99158293, 0.8476984, 0.16672458, 0.9887786], [0.07076367, 0.66354364, 0.8781082, 0.988832], ], dtype=np.float32, ) IN_RGB_FLOAT = np.array( [ [ [0.23317624, 0.39086635, 0.12795302], [0.40659714, 0.9079258, 0.28431135], [0.91651599, 0.46082205, 0.16928465], [0.29368765, 0.97987488, 0.72257988], ], [ [np.nan, 0.72908475, 0.54018012], [0.91277435, 0.2842577, 0.73481915], [0.33844504, 0.22060913, 0.9802894], [0.13995676, 0.34752838, 0.39652277], ], [ [0.85315026, 0.19330797, 0.0], [0.48584232, 0.04943356, 0.59906953], [np.inf, 0.32614581, 0.1483722], [0.37340863, 0.35432855, 0.08973532], ], [ [0.69666134, 0.52481322, 0.49057305], [0.93366339, 0.1428689, 0.6845513], [0.27681383, 0.69472673, 0.06750892], [0.26349886, 0.25841691, 0.86171104], ], ] ) IN_RGBA_FLOAT = np.array( [ [ [0.97383172, 0.62680971, 0.02285016, np.nan], [0.85295433, 0.93014834, 0.59482999, np.inf], [0.4017482, 0.79809183, 0.22407464, 0.17327807], [0.95953263, 0.69535086, 0.28846483, 0.76970823], ], [ [0.11487603, 0.7447609, 0.06767498, 0.98054729], [0.66071068, 0.73931366, 0.33155686, 0.81827122], [0.78035892, 0.52920802, 0.5671388, 0.31783899], [0.81709002, 0.82204682, 0.82584029, 0.49434749], ], [ [0.03142089, 0.8322058, 0.31646922, 0.94636969], [0.62381573, 0.60052138, 0.50244611, 0.92886007], [np.nan, np.nan, 0.02940048, 0.52529675], [0.9786162, 0.54928697, 0.2409731, 0.34705397], ], [ [0.68922233, np.inf, 0.85027734, 0.35388624], [0.16489042, 0.29860162, 0.09349833, 0.67714667], [0.25351483, 0.25596098, 0.80461891, 0.99952403], [0.0, 1.0, 0.58084746, 0.46211944], ], ] ) INPUTS: Dict[Any, np.ndarray] = { (np.uint8, "2D"): IN_2D_INT8, (np.uint8, "RGB"): IN_RGB_INT8, (np.uint8, "RGBA"): IN_RGBA_INT8, (np.uint16, "2D"): IN_2D_INT16, (np.uint16, "RGB"): IN_RGB_INT16, (np.uint16, "RGBA"): IN_RGBA_INT16, (np.float32, "2D"): IN_2D_FLOAT, (np.float32, "RGB"): IN_RGB_FLOAT, (np.float32, "RGBA"): IN_RGBA_FLOAT, } LUT8 = np.zeros((255,), dtype=np.uint8) LUT8[::2] = 255 LUT16 = np.zeros((65535,), dtype=np.uint8) LUT16[::3] = 255 LUTS: Dict[Any, np.ndarray] = { np.uint8: LUT8, np.uint16: LUT16, } LEVELS = { "SIMPLE": (0, 1), "RGB": ((0, 255), (1, 250), (100, 160)), "RGBA": ((255, 11111), (100, 10000), (0, 255), (127, 255)), } EXPECTED_OUTPUTS: Dict[tuple, Union[Type[Exception], np.ndarray]] = { (np.uint8, "2D", None, None, None, True): np.array( [ [[173, 173, 173, 255], [48, 48, 48, 255], [122, 122, 122, 255], [41, 41, 41, 255]], [[210, 210, 210, 255], [192, 192, 192, 255], [0, 0, 0, 255], [5, 5, 5, 255]], [[104, 104, 104, 255], [56, 56, 56, 255], [102, 102, 102, 255], [115, 115, 115, 255]], [[78, 78, 78, 255], [19, 19, 19, 255], [255, 255, 255, 255], [6, 6, 6, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, None, None, False): np.array( [ [[173, 173, 173, 255], [48, 48, 48, 255], [122, 122, 122, 255], [41, 41, 41, 255]], [[210, 210, 210, 255], [192, 192, 192, 255], [0, 0, 0, 255], [5, 5, 5, 255]], [[104, 104, 104, 255], [56, 56, 56, 255], [102, 102, 102, 255], [115, 115, 115, 255]], [[78, 78, 78, 255], [19, 19, 19, 255], [255, 255, 255, 255], [6, 6, 6, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, None, 232, True): np.array( [ [[157, 157, 157, 255], [43, 43, 43, 255], [110, 110, 110, 255], [37, 37, 37, 255]], [[191, 191, 191, 255], [174, 174, 174, 255], [0, 0, 0, 255], [4, 4, 4, 255]], [[94, 94, 94, 255], [50, 50, 50, 255], [92, 92, 92, 255], [104, 104, 104, 255]], [[70, 70, 70, 255], [17, 17, 17, 255], [232, 232, 232, 255], [5, 5, 5, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, None, 232, False): np.array( [ [[157, 157, 157, 255], [43, 43, 43, 255], [110, 110, 110, 255], [37, 37, 37, 255]], [[191, 191, 191, 255], [174, 174, 174, 255], [0, 0, 0, 255], [4, 4, 4, 255]], [[94, 94, 94, 255], [50, 50, 50, 255], [92, 92, 92, 255], [104, 104, 104, 255]], [[70, 70, 70, 255], [17, 17, 17, 255], [232, 232, 232, 255], [5, 5, 5, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint8, None, True): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint8, None, False): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint8, 232, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint8, 232, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint16, None, True): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint16, None, False): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint16, 232, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint16, 232, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint16, 13333, True): np.array( [ [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", None, np.uint16, 13333, False): np.array( [ [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", None, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", None, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", None, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", None, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint16, None, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint16, None, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint16, 232, True): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint16, 232, False): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint16, 13333, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "SIMPLE", np.uint16, 13333, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGB", None, None, True): Exception, (np.uint8, "2D", "RGB", None, None, False): Exception, (np.uint8, "2D", "RGB", None, 232, True): Exception, (np.uint8, "2D", "RGB", None, 232, False): Exception, (np.uint8, "2D", "RGB", None, 13333, True): Exception, (np.uint8, "2D", "RGB", None, 13333, False): Exception, (np.uint8, "2D", "RGB", np.uint8, None, True): Exception, (np.uint8, "2D", "RGB", np.uint8, None, False): Exception, (np.uint8, "2D", "RGB", np.uint8, 232, True): Exception, (np.uint8, "2D", "RGB", np.uint8, 232, False): Exception, (np.uint8, "2D", "RGB", np.uint8, 13333, True): Exception, (np.uint8, "2D", "RGB", np.uint8, 13333, False): Exception, (np.uint8, "2D", "RGB", np.uint16, None, True): Exception, (np.uint8, "2D", "RGB", np.uint16, None, False): Exception, (np.uint8, "2D", "RGB", np.uint16, 232, True): Exception, (np.uint8, "2D", "RGB", np.uint16, 232, False): Exception, (np.uint8, "2D", "RGB", np.uint16, 13333, True): Exception, (np.uint8, "2D", "RGB", np.uint16, 13333, False): Exception, (np.uint8, "2D", "RGBA", None, None, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [122, 122, 122, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [2, 2, 2, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [102, 102, 102, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", None, None, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [122, 122, 122, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [2, 2, 2, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [102, 102, 102, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", None, 232, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [110, 110, 110, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [2, 2, 2, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [92, 92, 92, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [232, 232, 232, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", None, 232, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [110, 110, 110, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [2, 2, 2, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [92, 92, 92, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [232, 232, 232, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", None, 13333, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [123, 123, 123, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", None, 13333, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [123, 123, 123, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint16, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint16, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint16, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint16, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint16, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "2D", "RGBA", np.uint16, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, None, None, True): np.array( [ [[16, 69, 62, 255], [66, 132, 135, 255], [220, 80, 36, 255], [53, 34, 68, 255]], [[140, 23, 113, 255], [0, 63, 206, 255], [96, 255, 100, 255], [226, 182, 155, 255]], [[28, 237, 223, 255], [215, 232, 209, 255], [17, 16, 50, 255], [96, 187, 93, 255]], [[220, 193, 232, 255], [134, 168, 150, 255], [55, 64, 221, 255], [96, 108, 227, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, None, None, False): np.array( [ [[62, 69, 16, 255], [135, 132, 66, 255], [36, 80, 220, 255], [68, 34, 53, 255]], [[113, 23, 140, 255], [206, 63, 0, 255], [100, 255, 96, 255], [155, 182, 226, 255]], [[223, 237, 28, 255], [209, 232, 215, 255], [50, 16, 17, 255], [93, 187, 96, 255]], [[232, 193, 220, 255], [150, 168, 134, 255], [221, 64, 55, 255], [227, 108, 96, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, None, 232, True): np.array( [ [[14, 62, 56, 255], [60, 120, 122, 255], [200, 72, 32, 255], [48, 30, 61, 255]], [[127, 20, 102, 255], [0, 57, 187, 255], [87, 232, 90, 255], [205, 165, 141, 255]], [[25, 215, 202, 255], [195, 211, 190, 255], [15, 14, 45, 255], [87, 170, 84, 255]], [[200, 175, 211, 255], [121, 152, 136, 255], [50, 58, 201, 255], [87, 98, 206, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, None, 232, False): np.array( [ [[56, 62, 14, 255], [122, 120, 60, 255], [32, 72, 200, 255], [61, 30, 48, 255]], [[102, 20, 127, 255], [187, 57, 0, 255], [90, 232, 87, 255], [141, 165, 205, 255]], [[202, 215, 25, 255], [190, 211, 195, 255], [45, 14, 15, 255], [84, 170, 87, 255]], [[211, 175, 200, 255], [136, 152, 121, 255], [201, 58, 50, 255], [206, 98, 87, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint8, None, True): np.array( [ [[255, 0, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [0, 255, 255, 255]], [[255, 0, 0, 255], [255, 0, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255]], [[255, 0, 0, 255], [0, 255, 0, 255], [0, 255, 255, 255], [255, 0, 0, 255]], [[255, 0, 255, 255], [255, 255, 255, 255], [0, 255, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint8, None, False): np.array( [ [[255, 0, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255]], [[0, 0, 255, 255], [255, 0, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255]], [[0, 0, 255, 255], [0, 255, 0, 255], [255, 255, 0, 255], [0, 0, 255, 255]], [[255, 0, 255, 255], [255, 255, 255, 255], [0, 255, 0, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255]], [[0, 255, 255, 255], [255, 0, 0, 255], [0, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 255, 255], [0, 0, 255, 255], [0, 255, 0, 255], [0, 255, 255, 255]], [[255, 0, 0, 255], [0, 255, 255, 255], [255, 255, 0, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255]], [[255, 255, 0, 255], [0, 0, 255, 255], [255, 255, 0, 255], [0, 0, 0, 255]], [[255, 0, 0, 255], [255, 0, 0, 255], [0, 255, 0, 255], [255, 255, 0, 255]], [[0, 0, 255, 255], [255, 255, 0, 255], [0, 255, 255, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint16, None, True): np.array( [ [[0, 255, 0, 255], [255, 255, 255, 255], [0, 0, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 0, 255], [255, 0, 0, 255], [0, 0, 0, 255]], [[0, 255, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 255, 255]], [[0, 0, 0, 255], [0, 255, 255, 255], [0, 0, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint16, None, False): np.array( [ [[0, 255, 0, 255], [255, 255, 255, 255], [255, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 255, 255, 255], [0, 0, 255, 255], [0, 0, 0, 255]], [[0, 255, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 255, 255]], [[0, 0, 0, 255], [255, 255, 0, 255], [0, 0, 0, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint16, 232, True): np.array( [ [[0, 0, 0, 255], [255, 255, 0, 255], [0, 255, 0, 255], [255, 255, 0, 255]], [[0, 0, 255, 255], [255, 255, 0, 255], [255, 0, 255, 255], [0, 255, 255, 255]], [[0, 0, 0, 255], [255, 0, 0, 255], [255, 0, 255, 255], [255, 0, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 255, 255], [255, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint16, 232, False): np.array( [ [[0, 0, 0, 255], [0, 255, 255, 255], [0, 255, 0, 255], [0, 255, 255, 255]], [[255, 0, 0, 255], [0, 255, 255, 255], [255, 0, 255, 255], [255, 255, 0, 255]], [[0, 0, 0, 255], [0, 0, 255, 255], [255, 0, 255, 255], [255, 0, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 0, 255], [0, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint16, 13333, True): np.array( [ [[0, 0, 0, 255], [255, 0, 0, 255], [255, 255, 0, 255], [0, 0, 255, 255]], [[255, 0, 0, 255], [255, 255, 255, 255], [255, 0, 0, 255], [0, 255, 0, 255]], [[255, 0, 0, 255], [255, 0, 0, 255], [255, 0, 0, 255], [255, 255, 0, 255]], [[255, 0, 0, 255], [0, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", None, np.uint16, 13333, False): np.array( [ [[0, 0, 0, 255], [0, 0, 255, 255], [0, 255, 255, 255], [255, 0, 0, 255]], [[0, 0, 255, 255], [255, 255, 255, 255], [0, 0, 255, 255], [0, 255, 0, 255]], [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 255, 255, 255]], [[0, 0, 255, 255], [255, 255, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", None, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", None, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", None, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", None, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint16, None, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint16, None, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint16, 232, True): np.array( [ [[0, 255, 0, 255], [255, 255, 255, 255], [0, 0, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255], [0, 0, 0, 255]], [[0, 255, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 255, 255]], [[0, 0, 0, 255], [0, 255, 255, 255], [0, 0, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint16, 232, False): np.array( [ [[0, 255, 0, 255], [255, 255, 255, 255], [255, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 255, 255, 255], [0, 255, 255, 255], [0, 0, 0, 255]], [[0, 255, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 255, 255]], [[0, 0, 0, 255], [255, 255, 0, 255], [0, 0, 0, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint16, 13333, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "SIMPLE", np.uint16, 13333, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", None, None, True): np.array( [ [[16, 69, 0, 255], [66, 134, 148, 255], [220, 80, 0, 255], [53, 33, 0, 255]], [[140, 22, 55, 255], [0, 63, 255, 255], [96, 255, 0, 255], [226, 185, 233, 255]], [[28, 241, 255, 255], [215, 236, 255, 255], [17, 15, 0, 255], [96, 190, 0, 255]], [[220, 196, 255, 255], [134, 171, 212, 255], [55, 64, 255, 255], [96, 109, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", None, None, False): np.array( [ [[0, 69, 16, 255], [148, 134, 66, 255], [0, 80, 220, 255], [0, 33, 53, 255]], [[55, 22, 140, 255], [255, 63, 0, 255], [0, 255, 96, 255], [233, 185, 226, 255]], [[255, 241, 28, 255], [255, 236, 215, 255], [0, 15, 17, 255], [0, 190, 96, 255]], [[255, 196, 220, 255], [212, 171, 134, 255], [255, 64, 55, 255], [255, 109, 96, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", None, 232, True): np.array( [ [[14, 63, 0, 255], [60, 122, 135, 255], [200, 73, 0, 255], [48, 30, 0, 255]], [[127, 20, 50, 255], [0, 57, 255, 255], [87, 236, 0, 255], [205, 168, 212, 255]], [[25, 219, 255, 255], [195, 215, 255, 255], [15, 13, 0, 255], [87, 173, 0, 255]], [[200, 178, 255, 255], [121, 155, 193, 255], [50, 58, 255, 255], [87, 99, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", None, 232, False): np.array( [ [[0, 63, 14, 255], [135, 122, 60, 255], [0, 73, 200, 255], [0, 30, 48, 255]], [[50, 20, 127, 255], [255, 57, 0, 255], [0, 236, 87, 255], [212, 168, 205, 255]], [[255, 219, 25, 255], [255, 215, 195, 255], [0, 13, 15, 255], [0, 173, 87, 255]], [[255, 178, 200, 255], [193, 155, 121, 255], [255, 58, 50, 255], [255, 99, 87, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", None, 13333, True): np.array( [ [[255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", None, 13333, False): np.array( [ [[0, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [0, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint8, None, True): np.array( [ [[255, 0, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 255, 255]], [[255, 255, 0, 255], [255, 0, 255, 255], [255, 255, 255, 255], [255, 0, 0, 255]], [[255, 0, 255, 255], [0, 255, 255, 255], [0, 0, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 0, 255, 255], [0, 255, 255, 255], [255, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint8, None, False): np.array( [ [[255, 0, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 0, 255]], [[0, 255, 255, 255], [255, 0, 255, 255], [255, 255, 255, 255], [0, 0, 255, 255]], [[255, 0, 255, 255], [255, 255, 0, 255], [255, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 0, 255, 255], [255, 255, 0, 255], [255, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint8, 232, True): np.array( [ [[255, 0, 255, 255], [255, 255, 0, 255], [255, 0, 255, 255], [255, 255, 255, 255]], [[0, 255, 255, 255], [255, 0, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint8, 232, False): np.array( [ [[255, 0, 255, 255], [0, 255, 255, 255], [255, 0, 255, 255], [255, 255, 255, 255]], [[255, 255, 0, 255], [255, 0, 255, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 0, 0, 255], [255, 0, 0, 255], [255, 0, 0, 255], [255, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint16, None, True): np.array( [ [[0, 0, 255, 255], [255, 0, 0, 255], [0, 0, 255, 255], [0, 255, 255, 255]], [[0, 255, 255, 255], [255, 255, 0, 255], [255, 0, 255, 255], [0, 255, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 255, 255], [255, 0, 255, 255]], [[0, 0, 0, 255], [0, 255, 255, 255], [0, 255, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint16, None, False): np.array( [ [[255, 0, 0, 255], [0, 0, 255, 255], [255, 0, 0, 255], [255, 255, 0, 255]], [[255, 255, 0, 255], [0, 255, 255, 255], [255, 0, 255, 255], [0, 255, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 0, 255], [255, 0, 255, 255]], [[0, 0, 0, 255], [255, 255, 0, 255], [0, 255, 0, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint16, 232, True): np.array( [ [[0, 255, 255, 255], [255, 0, 255, 255], [0, 0, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 0, 255], [255, 0, 255, 255], [0, 255, 0, 255]], [[0, 255, 0, 255], [255, 0, 0, 255], [255, 0, 255, 255], [255, 0, 255, 255]], [[0, 0, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint16, 232, False): np.array( [ [[255, 255, 0, 255], [255, 0, 255, 255], [255, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 255, 255, 255], [255, 0, 255, 255], [0, 255, 0, 255]], [[0, 255, 0, 255], [0, 0, 255, 255], [255, 0, 255, 255], [255, 0, 255, 255]], [[255, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint16, 13333, True): np.array( [ [[0, 0, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [0, 255, 255, 255]], [[255, 0, 0, 255], [255, 0, 0, 255], [255, 0, 255, 255], [0, 0, 0, 255]], [[255, 255, 0, 255], [255, 255, 0, 255], [255, 0, 255, 255], [255, 0, 255, 255]], [[255, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGB", np.uint16, 13333, False): np.array( [ [[255, 0, 0, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255]], [[0, 0, 255, 255], [0, 0, 255, 255], [255, 0, 255, 255], [0, 0, 0, 255]], [[0, 255, 255, 255], [0, 255, 255, 255], [255, 0, 255, 255], [255, 0, 255, 255]], [[0, 0, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGB", "RGBA", None, None, True): Exception, (np.uint8, "RGB", "RGBA", None, None, False): Exception, (np.uint8, "RGB", "RGBA", None, 232, True): Exception, (np.uint8, "RGB", "RGBA", None, 232, False): Exception, (np.uint8, "RGB", "RGBA", None, 13333, True): Exception, (np.uint8, "RGB", "RGBA", None, 13333, False): Exception, (np.uint8, "RGB", "RGBA", np.uint8, None, True): Exception, (np.uint8, "RGB", "RGBA", np.uint8, None, False): Exception, (np.uint8, "RGB", "RGBA", np.uint8, 232, True): Exception, (np.uint8, "RGB", "RGBA", np.uint8, 232, False): Exception, (np.uint8, "RGB", "RGBA", np.uint8, 13333, True): Exception, (np.uint8, "RGB", "RGBA", np.uint8, 13333, False): Exception, (np.uint8, "RGB", "RGBA", np.uint16, None, True): Exception, (np.uint8, "RGB", "RGBA", np.uint16, None, False): Exception, (np.uint8, "RGB", "RGBA", np.uint16, 232, True): Exception, (np.uint8, "RGB", "RGBA", np.uint16, 232, False): Exception, (np.uint8, "RGB", "RGBA", np.uint16, 13333, True): Exception, (np.uint8, "RGB", "RGBA", np.uint16, 13333, False): Exception, (np.uint8, "RGBA", None, None, None, True): np.array( [ [[151, 252, 73, 107], [28, 221, 35, 0], [87, 122, 126, 114], [47, 59, 24, 200]], [[189, 246, 242, 255], [123, 255, 29, 14], [201, 208, 133, 32], [118, 203, 141, 245]], [[133, 131, 248, 81], [4, 84, 99, 40], [40, 167, 119, 150], [13, 158, 108, 21]], [[156, 221, 166, 250], [77, 188, 13, 166], [0, 1, 185, 25], [83, 35, 103, 120]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, None, None, False): np.array( [ [[73, 252, 151, 107], [35, 221, 28, 0], [126, 122, 87, 114], [24, 59, 47, 200]], [[242, 246, 189, 255], [29, 255, 123, 14], [133, 208, 201, 32], [141, 203, 118, 245]], [[248, 131, 133, 81], [99, 84, 4, 40], [119, 167, 40, 150], [108, 158, 13, 21]], [[166, 221, 156, 250], [13, 188, 77, 166], [185, 1, 0, 25], [103, 35, 83, 120]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, None, 232, True): np.array( [ [[137, 229, 66, 97], [25, 201, 31, 0], [79, 110, 114, 103], [42, 53, 21, 181]], [[171, 223, 220, 232], [111, 232, 26, 12], [182, 189, 121, 29], [107, 184, 128, 222]], [[121, 119, 225, 73], [3, 76, 90, 36], [36, 151, 108, 136], [11, 143, 98, 19]], [[141, 201, 151, 227], [70, 171, 11, 151], [0, 0, 168, 22], [75, 31, 93, 109]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, None, 232, False): np.array( [ [[66, 229, 137, 97], [31, 201, 25, 0], [114, 110, 79, 103], [21, 53, 42, 181]], [[220, 223, 171, 232], [26, 232, 111, 12], [121, 189, 182, 29], [128, 184, 107, 222]], [[225, 119, 121, 73], [90, 76, 3, 36], [108, 151, 36, 136], [98, 143, 11, 19]], [[151, 201, 141, 227], [11, 171, 70, 151], [168, 0, 0, 22], [93, 31, 75, 109]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [209, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 52, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 209, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 52, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint8, None, True): np.array( [ [[0, 255, 0, 0], [255, 0, 0, 255], [0, 255, 255, 255], [0, 0, 255, 255]], [[0, 255, 255, 255], [0, 255, 0, 255], [0, 255, 0, 255], [255, 0, 0, 0]], [[0, 0, 255, 0], [255, 255, 0, 255], [255, 0, 0, 255], [0, 255, 255, 0]], [[255, 0, 255, 255], [0, 255, 0, 255], [255, 0, 0, 0], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint8, None, False): np.array( [ [[0, 255, 0, 0], [0, 0, 255, 255], [255, 255, 0, 255], [255, 0, 0, 255]], [[255, 255, 0, 255], [0, 255, 0, 255], [0, 255, 0, 255], [0, 0, 255, 0]], [[255, 0, 0, 0], [0, 255, 255, 255], [0, 0, 255, 255], [255, 255, 0, 0]], [[255, 0, 255, 255], [0, 255, 0, 255], [0, 0, 255, 0], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint8, 232, True): np.array( [ [[0, 0, 255, 0], [0, 0, 0, 255], [0, 255, 255, 0], [255, 0, 0, 0]], [[0, 0, 255, 255], [0, 255, 255, 255], [255, 0, 0, 0], [0, 255, 255, 255]], [[0, 0, 0, 0], [0, 255, 255, 255], [255, 0, 255, 255], [0, 0, 255, 0]], [[0, 0, 0, 0], [255, 0, 0, 0], [255, 255, 255, 255], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint8, 232, False): np.array( [ [[255, 0, 0, 0], [0, 0, 0, 255], [255, 255, 0, 0], [0, 0, 255, 0]], [[255, 0, 0, 255], [255, 255, 0, 255], [0, 0, 255, 0], [255, 255, 0, 255]], [[0, 0, 0, 0], [255, 255, 0, 255], [255, 0, 255, 255], [255, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 255, 0], [255, 255, 255, 255], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint16, None, True): np.array( [ [[0, 255, 0, 0], [0, 0, 0, 255], [255, 0, 255, 255], [0, 0, 255, 0]], [[255, 255, 0, 0], [255, 0, 0, 0], [255, 0, 0, 0], [0, 0, 255, 0]], [[0, 0, 0, 255], [0, 255, 255, 0], [0, 0, 0, 255], [0, 0, 255, 255]], [[255, 0, 0, 0], [0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint16, None, False): np.array( [ [[0, 255, 0, 0], [0, 0, 0, 255], [255, 0, 255, 255], [255, 0, 0, 0]], [[0, 255, 255, 0], [0, 0, 255, 0], [0, 0, 255, 0], [255, 0, 0, 0]], [[0, 0, 0, 255], [255, 255, 0, 0], [0, 0, 0, 255], [255, 0, 0, 255]], [[0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint16, 232, True): np.array( [ [[0, 0, 255, 0], [0, 255, 0, 255], [0, 0, 255, 0], [255, 0, 255, 0]], [[255, 0, 0, 0], [255, 0, 0, 255], [0, 255, 0, 0], [0, 0, 0, 255]], [[0, 0, 255, 0], [255, 0, 255, 255], [255, 0, 255, 0], [0, 0, 0, 0]], [[255, 255, 0, 0], [0, 255, 0, 0], [255, 255, 255, 0], [255, 0, 255, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint16, 232, False): np.array( [ [[255, 0, 0, 0], [0, 255, 0, 255], [255, 0, 0, 0], [255, 0, 255, 0]], [[0, 0, 255, 0], [0, 0, 255, 255], [0, 255, 0, 0], [0, 0, 0, 255]], [[255, 0, 0, 0], [255, 0, 255, 255], [255, 0, 255, 0], [0, 0, 0, 0]], [[0, 255, 255, 0], [0, 255, 0, 0], [255, 255, 255, 0], [255, 0, 255, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint16, 13333, True): np.array( [ [[0, 255, 255, 0], [255, 0, 255, 255], [255, 255, 255, 0], [255, 255, 255, 0]], [[255, 0, 0, 0], [0, 0, 0, 255], [255, 255, 255, 0], [0, 255, 0, 255]], [[255, 255, 255, 0], [0, 255, 0, 255], [255, 0, 255, 255], [0, 0, 255, 255]], [[0, 0, 255, 255], [255, 0, 0, 255], [255, 0, 255, 0], [0, 255, 255, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", None, np.uint16, 13333, False): np.array( [ [[255, 255, 0, 0], [255, 0, 255, 255], [255, 255, 255, 0], [255, 255, 255, 0]], [[0, 0, 255, 0], [0, 0, 0, 255], [255, 255, 255, 0], [0, 255, 0, 255]], [[255, 255, 255, 0], [0, 255, 0, 255], [255, 0, 255, 255], [255, 0, 0, 255]], [[255, 0, 0, 255], [0, 0, 255, 255], [255, 0, 255, 0], [255, 255, 0, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", None, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", None, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", None, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 232, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", None, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 232, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint16, None, True): np.array( [ [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint16, None, False): np.array( [ [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint16, 232, True): np.array( [ [[0, 255, 0, 0], [0, 0, 0, 255], [255, 0, 255, 255], [0, 0, 255, 0]], [[255, 255, 0, 255], [255, 255, 0, 0], [255, 0, 0, 0], [0, 0, 255, 0]], [[0, 0, 0, 255], [0, 255, 255, 0], [0, 0, 0, 255], [0, 0, 255, 255]], [[255, 0, 0, 0], [0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint16, 232, False): np.array( [ [[0, 255, 0, 0], [0, 0, 0, 255], [255, 0, 255, 255], [255, 0, 0, 0]], [[0, 255, 255, 255], [0, 255, 255, 0], [0, 0, 255, 0], [255, 0, 0, 0]], [[0, 0, 0, 255], [255, 255, 0, 0], [0, 0, 0, 255], [255, 0, 0, 255]], [[0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint16, 13333, True): np.array( [ [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "SIMPLE", np.uint16, 13333, False): np.array( [ [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGB", None, None, True): Exception, (np.uint8, "RGBA", "RGB", None, None, False): Exception, (np.uint8, "RGBA", "RGB", None, 232, True): Exception, (np.uint8, "RGBA", "RGB", None, 232, False): Exception, (np.uint8, "RGBA", "RGB", None, 13333, True): Exception, (np.uint8, "RGBA", "RGB", None, 13333, False): Exception, (np.uint8, "RGBA", "RGB", np.uint8, None, True): Exception, (np.uint8, "RGBA", "RGB", np.uint8, None, False): Exception, (np.uint8, "RGBA", "RGB", np.uint8, 232, True): Exception, (np.uint8, "RGBA", "RGB", np.uint8, 232, False): Exception, (np.uint8, "RGBA", "RGB", np.uint8, 13333, True): Exception, (np.uint8, "RGBA", "RGB", np.uint8, 13333, False): Exception, (np.uint8, "RGBA", "RGB", np.uint16, None, True): Exception, (np.uint8, "RGBA", "RGB", np.uint16, None, False): Exception, (np.uint8, "RGBA", "RGB", np.uint16, 232, True): Exception, (np.uint8, "RGBA", "RGB", np.uint16, 232, False): Exception, (np.uint8, "RGBA", "RGB", np.uint16, 13333, True): Exception, (np.uint8, "RGBA", "RGB", np.uint16, 13333, False): Exception, (np.uint8, "RGBA", "RGBA", None, None, True): np.array( [ [[0, 3, 73, 0], [0, 3, 35, 0], [0, 0, 126, 0], [0, 0, 24, 145]], [[0, 3, 242, 255], [0, 3, 29, 0], [0, 2, 133, 0], [0, 2, 141, 235]], [[0, 0, 248, 0], [0, 0, 99, 0], [0, 1, 119, 45], [0, 1, 108, 0]], [[0, 3, 166, 245], [0, 2, 13, 77], [0, 0, 185, 0], [0, 0, 103, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", None, None, False): np.array( [ [[73, 3, 0, 0], [35, 3, 0, 0], [126, 0, 0, 0], [24, 0, 0, 145]], [[242, 3, 0, 255], [29, 3, 0, 0], [133, 2, 0, 0], [141, 2, 0, 235]], [[248, 0, 0, 0], [99, 0, 0, 0], [119, 1, 0, 45], [108, 1, 0, 0]], [[166, 3, 0, 245], [13, 2, 0, 77], [185, 0, 0, 0], [103, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", None, 232, True): np.array( [ [[0, 3, 66, 0], [0, 2, 31, 0], [0, 0, 114, 0], [0, 0, 21, 132]], [[0, 3, 220, 232], [0, 3, 26, 0], [0, 2, 121, 0], [0, 2, 128, 213]], [[0, 0, 225, 0], [0, 0, 90, 0], [0, 1, 108, 41], [0, 1, 98, 0]], [[0, 2, 151, 222], [0, 2, 11, 70], [0, 0, 168, 0], [0, 0, 93, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", None, 232, False): np.array( [ [[66, 3, 0, 0], [31, 2, 0, 0], [114, 0, 0, 0], [21, 0, 0, 132]], [[220, 3, 0, 232], [26, 3, 0, 0], [121, 2, 0, 0], [128, 2, 0, 213]], [[225, 0, 0, 0], [90, 0, 0, 0], [108, 1, 0, 41], [98, 1, 0, 0]], [[151, 2, 0, 222], [11, 2, 0, 70], [168, 0, 0, 0], [93, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", None, 13333, True): np.array( [ [[0, 204, 255, 0], [0, 162, 255, 0], [0, 29, 255, 0], [0, 0, 255, 255]], [[0, 196, 255, 255], [0, 208, 255, 0], [0, 145, 255, 0], [0, 138, 255, 255]], [[0, 41, 255, 0], [0, 0, 255, 0], [0, 90, 255, 255], [0, 78, 255, 0]], [[0, 162, 255, 255], [0, 118, 255, 255], [0, 0, 255, 0], [0, 0, 255, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", None, 13333, False): np.array( [ [[255, 204, 0, 0], [255, 162, 0, 0], [255, 29, 0, 0], [255, 0, 0, 255]], [[255, 196, 0, 255], [255, 208, 0, 0], [255, 145, 0, 0], [255, 138, 0, 255]], [[255, 41, 0, 0], [255, 0, 0, 0], [255, 90, 0, 255], [255, 78, 0, 0]], [[255, 162, 0, 255], [255, 118, 0, 255], [255, 0, 0, 0], [255, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint8, None, True): np.array( [ [[255, 0, 0, 255], [255, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 0]], [[255, 0, 255, 255], [255, 0, 0, 255], [255, 255, 0, 255], [255, 255, 0, 0]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 0, 0, 0], [255, 0, 255, 255]], [[255, 0, 255, 0], [255, 255, 0, 0], [255, 255, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint8, None, False): np.array( [ [[0, 0, 255, 255], [0, 0, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0]], [[255, 0, 255, 255], [0, 0, 255, 255], [0, 255, 255, 255], [0, 255, 255, 0]], [[255, 255, 255, 255], [0, 255, 255, 255], [0, 0, 255, 0], [255, 0, 255, 255]], [[255, 0, 255, 0], [0, 255, 255, 0], [0, 255, 255, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint8, 232, True): np.array( [ [[255, 0, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 0, 255]], [[255, 0, 255, 255], [255, 0, 255, 255], [255, 255, 0, 255], [255, 255, 255, 0]], [[255, 255, 0, 255], [255, 255, 255, 255], [255, 0, 255, 0], [255, 0, 255, 255]], [[255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint8, 232, False): np.array( [ [[255, 0, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255]], [[255, 0, 255, 255], [255, 0, 255, 255], [0, 255, 255, 255], [255, 255, 255, 0]], [[0, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 0], [255, 0, 255, 255]], [[0, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 255], [255, 255, 255, 255]], [[255, 0, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 255], [255, 255, 255, 255]], [[255, 0, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint16, None, True): np.array( [ [[255, 0, 0, 255], [255, 0, 0, 255], [255, 0, 255, 255], [255, 255, 255, 0]], [[255, 255, 0, 0], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 255, 0]], [[255, 0, 0, 255], [255, 255, 255, 255], [255, 0, 0, 255], [255, 0, 255, 255]], [[255, 0, 0, 0], [255, 255, 0, 0], [255, 255, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint16, None, False): np.array( [ [[0, 0, 255, 255], [0, 0, 255, 255], [255, 0, 255, 255], [255, 255, 255, 0]], [[0, 255, 255, 0], [0, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 0]], [[0, 0, 255, 255], [255, 255, 255, 255], [0, 0, 255, 255], [255, 0, 255, 255]], [[0, 0, 255, 0], [0, 255, 255, 0], [0, 255, 255, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint16, 232, True): np.array( [ [[255, 255, 255, 255], [255, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 0, 0], [255, 255, 0, 255], [255, 0, 0, 255], [255, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 0], [255, 0, 0, 255]], [[255, 0, 0, 255], [255, 0, 0, 0], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint16, 232, False): np.array( [ [[255, 255, 255, 255], [0, 0, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 255, 255, 0], [0, 255, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 0], [0, 0, 255, 255]], [[0, 0, 255, 255], [0, 0, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint16, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 255], [255, 255, 255, 0]], [[255, 0, 0, 0], [255, 0, 0, 255], [255, 0, 255, 255], [255, 255, 0, 255]], [[255, 0, 255, 255], [255, 255, 0, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint8, "RGBA", "RGBA", np.uint16, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 255], [255, 255, 255, 0]], [[0, 0, 255, 0], [0, 0, 255, 255], [255, 0, 255, 255], [0, 255, 255, 255]], [[255, 0, 255, 255], [0, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 0], [0, 0, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, None, None, True): np.array( [ [[52, 52, 52, 255], [214, 214, 214, 255], [158, 158, 158, 255], [159, 159, 159, 255]], [[224, 224, 224, 255], [133, 133, 133, 255], [132, 132, 132, 255], [0, 0, 0, 255]], [[39, 39, 39, 255], [221, 221, 221, 255], [162, 162, 162, 255], [83, 83, 83, 255]], [[57, 57, 57, 255], [255, 255, 255, 255], [187, 187, 187, 255], [44, 44, 44, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, None, None, False): np.array( [ [[52, 52, 52, 255], [214, 214, 214, 255], [158, 158, 158, 255], [159, 159, 159, 255]], [[224, 224, 224, 255], [133, 133, 133, 255], [132, 132, 132, 255], [0, 0, 0, 255]], [[39, 39, 39, 255], [221, 221, 221, 255], [162, 162, 162, 255], [83, 83, 83, 255]], [[57, 57, 57, 255], [255, 255, 255, 255], [187, 187, 187, 255], [44, 44, 44, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, None, 232, True): np.array( [ [[47, 47, 47, 255], [194, 194, 194, 255], [144, 144, 144, 255], [144, 144, 144, 255]], [[203, 203, 203, 255], [121, 121, 121, 255], [120, 120, 120, 255], [0, 0, 0, 255]], [[35, 35, 35, 255], [201, 201, 201, 255], [148, 148, 148, 255], [76, 76, 76, 255]], [[52, 52, 52, 255], [232, 232, 232, 255], [170, 170, 170, 255], [40, 40, 40, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, None, 232, False): np.array( [ [[47, 47, 47, 255], [194, 194, 194, 255], [144, 144, 144, 255], [144, 144, 144, 255]], [[203, 203, 203, 255], [121, 121, 121, 255], [120, 120, 120, 255], [0, 0, 0, 255]], [[35, 35, 35, 255], [201, 201, 201, 255], [148, 148, 148, 255], [76, 76, 76, 255]], [[52, 52, 52, 255], [232, 232, 232, 255], [170, 170, 170, 255], [40, 40, 40, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint8, 232, True): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint8, 232, False): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint16, None, True): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint16, None, False): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint16, 232, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint16, 232, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint16, 13333, True): np.array( [ [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", None, np.uint16, 13333, False): np.array( [ [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", None, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", None, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", None, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", None, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint16, None, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint16, None, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint16, 232, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint16, 232, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint16, 13333, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "SIMPLE", np.uint16, 13333, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGB", None, None, True): Exception, (np.uint16, "2D", "RGB", None, None, False): Exception, (np.uint16, "2D", "RGB", None, 232, True): Exception, (np.uint16, "2D", "RGB", None, 232, False): Exception, (np.uint16, "2D", "RGB", None, 13333, True): Exception, (np.uint16, "2D", "RGB", None, 13333, False): Exception, (np.uint16, "2D", "RGB", np.uint8, None, True): Exception, (np.uint16, "2D", "RGB", np.uint8, None, False): Exception, (np.uint16, "2D", "RGB", np.uint8, 232, True): Exception, (np.uint16, "2D", "RGB", np.uint8, 232, False): Exception, (np.uint16, "2D", "RGB", np.uint8, 13333, True): Exception, (np.uint16, "2D", "RGB", np.uint8, 13333, False): Exception, (np.uint16, "2D", "RGB", np.uint16, None, True): Exception, (np.uint16, "2D", "RGB", np.uint16, None, False): Exception, (np.uint16, "2D", "RGB", np.uint16, 232, True): Exception, (np.uint16, "2D", "RGB", np.uint16, 232, False): Exception, (np.uint16, "2D", "RGB", np.uint16, 13333, True): Exception, (np.uint16, "2D", "RGB", np.uint16, 13333, False): Exception, (np.uint16, "2D", "RGBA", None, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[231, 231, 231, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", None, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[231, 231, 231, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", None, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[210, 210, 210, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", None, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[210, 210, 210, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint16, None, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint16, None, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint16, 232, True): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint16, 232, False): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint16, 13333, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "2D", "RGBA", np.uint16, 13333, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, None, None, True): np.array( [ [[216, 176, 0, 255], [75, 152, 141, 255], [129, 33, 221, 255], [146, 121, 149, 255]], [[229, 63, 143, 255], [38, 93, 48, 255], [64, 185, 70, 255], [66, 152, 233, 255]], [[170, 192, 38, 255], [139, 15, 154, 255], [208, 226, 113, 255], [208, 15, 137, 255]], [[62, 84, 176, 255], [255, 183, 202, 255], [24, 134, 77, 255], [168, 145, 136, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, None, None, False): np.array( [ [[0, 176, 216, 255], [141, 152, 75, 255], [221, 33, 129, 255], [149, 121, 146, 255]], [[143, 63, 229, 255], [48, 93, 38, 255], [70, 185, 64, 255], [233, 152, 66, 255]], [[38, 192, 170, 255], [154, 15, 139, 255], [113, 226, 208, 255], [137, 15, 208, 255]], [[176, 84, 62, 255], [202, 183, 255, 255], [77, 134, 24, 255], [136, 145, 168, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, None, 232, True): np.array( [ [[196, 160, 0, 255], [68, 138, 128, 255], [117, 30, 201, 255], [133, 110, 135, 255]], [[208, 58, 130, 255], [35, 84, 43, 255], [58, 168, 64, 255], [60, 139, 212, 255]], [[154, 175, 34, 255], [127, 13, 140, 255], [189, 205, 103, 255], [189, 14, 124, 255]], [[56, 76, 160, 255], [232, 166, 184, 255], [22, 122, 70, 255], [153, 132, 124, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, None, 232, False): np.array( [ [[0, 160, 196, 255], [128, 138, 68, 255], [201, 30, 117, 255], [135, 110, 133, 255]], [[130, 58, 208, 255], [43, 84, 35, 255], [64, 168, 58, 255], [212, 139, 60, 255]], [[34, 175, 154, 255], [140, 13, 127, 255], [103, 205, 189, 255], [124, 14, 189, 255]], [[160, 76, 56, 255], [184, 166, 232, 255], [70, 122, 22, 255], [124, 132, 153, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, None, 13333, True): np.array( [ [[255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, None, 13333, False): np.array( [ [[0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [0, 255, 0, 255], [0, 0, 0, 255], [255, 0, 0, 255]], [[0, 0, 0, 255], [255, 0, 255, 255], [255, 0, 255, 255], [255, 255, 0, 255]], [[255, 255, 255, 255], [0, 0, 255, 255], [255, 255, 0, 255], [255, 0, 0, 255]], [[255, 255, 255, 255], [255, 0, 255, 255], [255, 255, 0, 255], [255, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [0, 255, 0, 255], [0, 0, 0, 255], [0, 0, 255, 255]], [[0, 0, 0, 255], [255, 0, 255, 255], [255, 0, 255, 255], [0, 255, 255, 255]], [[255, 255, 255, 255], [255, 0, 0, 255], [0, 255, 255, 255], [0, 0, 255, 255]], [[255, 255, 255, 255], [255, 0, 255, 255], [0, 255, 255, 255], [255, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 0, 255], [0, 255, 0, 255]], [[255, 255, 255, 255], [0, 255, 0, 255], [255, 255, 255, 255], [255, 0, 255, 255]], [[255, 0, 255, 255], [0, 0, 255, 255], [0, 0, 0, 255], [0, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 0, 255], [0, 255, 0, 255]], [[255, 255, 255, 255], [0, 255, 0, 255], [255, 255, 255, 255], [255, 0, 255, 255]], [[255, 0, 255, 255], [255, 0, 0, 255], [0, 0, 0, 255], [255, 255, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint16, None, True): np.array( [ [[255, 0, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 255, 255, 255]], [[0, 0, 0, 255], [0, 255, 0, 255], [255, 0, 255, 255], [255, 0, 0, 255]], [[0, 0, 255, 255], [255, 255, 255, 255], [0, 0, 255, 255], [255, 255, 0, 255]], [[255, 255, 0, 255], [0, 0, 0, 255], [255, 0, 255, 255], [255, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint16, None, False): np.array( [ [[255, 0, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 0, 255]], [[0, 0, 0, 255], [0, 255, 0, 255], [255, 0, 255, 255], [0, 0, 255, 255]], [[255, 0, 0, 255], [255, 255, 255, 255], [255, 0, 0, 255], [0, 255, 255, 255]], [[0, 255, 255, 255], [0, 0, 0, 255], [255, 0, 255, 255], [0, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint16, 232, True): np.array( [ [[0, 0, 255, 255], [0, 255, 0, 255], [255, 255, 255, 255], [0, 0, 255, 255]], [[0, 0, 0, 255], [0, 255, 0, 255], [0, 255, 0, 255], [255, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 0, 255], [255, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint16, 232, False): np.array( [ [[255, 0, 0, 255], [0, 255, 0, 255], [255, 255, 255, 255], [255, 0, 0, 255]], [[0, 0, 0, 255], [0, 255, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 255, 255], [0, 0, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint16, 13333, True): np.array( [ [[0, 0, 255, 255], [255, 0, 0, 255], [255, 0, 0, 255], [255, 0, 255, 255]], [[0, 255, 0, 255], [255, 0, 255, 255], [255, 0, 0, 255], [255, 0, 0, 255]], [[0, 0, 0, 255], [255, 0, 0, 255], [255, 0, 255, 255], [0, 0, 0, 255]], [[255, 255, 0, 255], [0, 0, 0, 255], [255, 0, 0, 255], [255, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", None, np.uint16, 13333, False): np.array( [ [[255, 0, 0, 255], [0, 0, 255, 255], [0, 0, 255, 255], [255, 0, 255, 255]], [[0, 255, 0, 255], [255, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]], [[0, 0, 0, 255], [0, 0, 255, 255], [255, 0, 255, 255], [0, 0, 0, 255]], [[0, 255, 255, 255], [0, 0, 0, 255], [0, 0, 255, 255], [255, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", None, None, True): np.array( [ [[255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", None, None, False): np.array( [ [[0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", None, 232, True): np.array( [ [[255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", None, 232, False): np.array( [ [[0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", None, 13333, True): np.array( [ [[255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", None, 13333, False): np.array( [ [[0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint16, None, True): np.array( [ [[0, 0, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint16, None, False): np.array( [ [[255, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint16, 232, True): np.array( [ [[0, 0, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint16, 232, False): np.array( [ [[255, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint16, 13333, True): np.array( [ [[0, 0, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "SIMPLE", np.uint16, 13333, False): np.array( [ [[255, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", None, None, True): np.array( [ [[255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", None, None, False): np.array( [ [[0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", None, 232, True): np.array( [ [[255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", None, 232, False): np.array( [ [[0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", None, 13333, True): np.array( [ [[255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", None, 13333, False): np.array( [ [[0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint16, None, True): np.array( [ [[0, 0, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint16, None, False): np.array( [ [[255, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint16, 232, True): np.array( [ [[0, 255, 255, 255], [255, 0, 0, 255], [255, 0, 0, 255], [255, 0, 0, 255]], [[0, 0, 0, 255], [0, 255, 0, 255], [255, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 255, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 255, 0, 255], [0, 0, 0, 255], [255, 255, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint16, 232, False): np.array( [ [[255, 255, 0, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]], [[0, 0, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 255, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 255, 255, 255], [0, 0, 0, 255], [0, 255, 255, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint16, 13333, True): np.array( [ [[0, 0, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGB", np.uint16, 13333, False): np.array( [ [[255, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGB", "RGBA", None, None, True): Exception, (np.uint16, "RGB", "RGBA", None, None, False): Exception, (np.uint16, "RGB", "RGBA", None, 232, True): Exception, (np.uint16, "RGB", "RGBA", None, 232, False): Exception, (np.uint16, "RGB", "RGBA", None, 13333, True): Exception, (np.uint16, "RGB", "RGBA", None, 13333, False): Exception, (np.uint16, "RGB", "RGBA", np.uint8, None, True): Exception, (np.uint16, "RGB", "RGBA", np.uint8, None, False): Exception, (np.uint16, "RGB", "RGBA", np.uint8, 232, True): Exception, (np.uint16, "RGB", "RGBA", np.uint8, 232, False): Exception, (np.uint16, "RGB", "RGBA", np.uint8, 13333, True): Exception, (np.uint16, "RGB", "RGBA", np.uint8, 13333, False): Exception, (np.uint16, "RGB", "RGBA", np.uint16, None, True): Exception, (np.uint16, "RGB", "RGBA", np.uint16, None, False): Exception, (np.uint16, "RGB", "RGBA", np.uint16, 232, True): Exception, (np.uint16, "RGB", "RGBA", np.uint16, 232, False): Exception, (np.uint16, "RGB", "RGBA", np.uint16, 13333, True): Exception, (np.uint16, "RGB", "RGBA", np.uint16, 13333, False): Exception, (np.uint16, "RGBA", None, None, None, True): np.array( [ [[50, 158, 115, 181], [0, 142, 130, 220], [188, 89, 183, 104], [223, 47, 120, 45]], [[18, 160, 124, 98], [108, 253, 233, 185], [34, 71, 19, 189], [255, 100, 106, 67]], [[202, 255, 158, 255], [0, 224, 107, 164], [195, 136, 74, 247], [68, 21, 140, 187]], [[239, 107, 145, 28], [212, 155, 227, 49], [81, 220, 126, 0], [52, 57, 229, 189]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, None, None, False): np.array( [ [[115, 158, 50, 181], [130, 142, 0, 220], [183, 89, 188, 104], [120, 47, 223, 45]], [[124, 160, 18, 98], [233, 253, 108, 185], [19, 71, 34, 189], [106, 100, 255, 67]], [[158, 255, 202, 255], [107, 224, 0, 164], [74, 136, 195, 247], [140, 21, 68, 187]], [[145, 107, 239, 28], [227, 155, 212, 49], [126, 220, 81, 0], [229, 57, 52, 189]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, None, 232, True): np.array( [ [[46, 144, 104, 165], [0, 129, 118, 200], [171, 81, 167, 94], [202, 43, 109, 41]], [[17, 145, 113, 89], [98, 230, 212, 169], [31, 64, 17, 172], [232, 91, 96, 61]], [[184, 232, 144, 232], [0, 204, 98, 149], [177, 124, 68, 225], [62, 19, 128, 170]], [[218, 98, 132, 25], [193, 141, 206, 45], [73, 200, 114, 0], [47, 51, 208, 172]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, None, 232, False): np.array( [ [[104, 144, 46, 165], [118, 129, 0, 200], [167, 81, 171, 94], [109, 43, 202, 41]], [[113, 145, 17, 89], [212, 230, 98, 169], [17, 64, 31, 172], [96, 91, 232, 61]], [[144, 232, 184, 232], [98, 204, 0, 149], [68, 124, 177, 225], [128, 19, 62, 170]], [[132, 98, 218, 25], [206, 141, 193, 45], [114, 200, 73, 0], [208, 51, 47, 172]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, None, 13333, True): np.array( [ [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [6, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 17], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 6, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 17], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint8, None, True): np.array( [ [[255, 255, 0, 0], [255, 255, 255, 255], [255, 0, 0, 255], [0, 0, 255, 0]], [[255, 255, 255, 255], [255, 0, 0, 0], [255, 0, 0, 0], [255, 255, 255, 0]], [[255, 255, 255, 255], [255, 255, 0, 255], [0, 255, 255, 0], [255, 0, 255, 0]], [[0, 0, 0, 255], [255, 0, 0, 0], [0, 255, 255, 255], [255, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint8, None, False): np.array( [ [[0, 255, 255, 0], [255, 255, 255, 255], [0, 0, 255, 255], [255, 0, 0, 0]], [[255, 255, 255, 255], [0, 0, 255, 0], [0, 0, 255, 0], [255, 255, 255, 0]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 0, 0], [255, 0, 255, 0]], [[0, 0, 0, 255], [0, 0, 255, 0], [255, 255, 0, 255], [0, 0, 255, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint8, 232, True): np.array( [ [[255, 255, 255, 0], [255, 0, 255, 255], [0, 0, 0, 255], [255, 0, 0, 0]], [[0, 0, 0, 0], [255, 255, 255, 0], [0, 255, 0, 255], [255, 0, 255, 0]], [[255, 255, 255, 255], [255, 255, 255, 0], [0, 255, 255, 0], [255, 0, 255, 255]], [[255, 255, 255, 0], [0, 0, 255, 0], [0, 255, 255, 255], [0, 0, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint8, 232, False): np.array( [ [[255, 255, 255, 0], [255, 0, 255, 255], [0, 0, 0, 255], [0, 0, 255, 0]], [[0, 0, 0, 0], [255, 255, 255, 0], [0, 255, 0, 255], [255, 0, 255, 0]], [[255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 0, 0], [255, 0, 255, 255]], [[255, 255, 255, 0], [255, 0, 0, 0], [255, 255, 0, 255], [255, 0, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint16, None, True): np.array( [ [[0, 0, 0, 255], [255, 0, 255, 255], [0, 0, 0, 0], [255, 0, 255, 255]], [[255, 255, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 255, 0, 0]], [[255, 0, 255, 0], [255, 255, 255, 0], [0, 255, 0, 0], [0, 0, 255, 255]], [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0], [0, 255, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint16, None, False): np.array( [ [[0, 0, 0, 255], [255, 0, 255, 255], [0, 0, 0, 0], [255, 0, 255, 255]], [[0, 255, 255, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 255, 0, 0]], [[255, 0, 255, 0], [255, 255, 255, 0], [0, 255, 0, 0], [255, 0, 0, 255]], [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0], [0, 255, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint16, 232, True): np.array( [ [[0, 255, 0, 255], [255, 255, 0, 0], [255, 255, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 255, 0]], [[0, 0, 255, 0], [255, 255, 0, 0], [255, 0, 0, 255], [0, 0, 0, 0]], [[0, 0, 255, 0], [0, 255, 0, 255], [0, 0, 255, 255], [0, 255, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint16, 232, False): np.array( [ [[0, 255, 0, 255], [0, 255, 255, 0], [0, 255, 255, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [255, 0, 0, 0]], [[255, 0, 0, 0], [0, 255, 255, 0], [0, 0, 255, 255], [0, 0, 0, 0]], [[255, 0, 0, 0], [0, 255, 0, 255], [255, 0, 0, 255], [0, 255, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint16, 13333, True): np.array( [ [[0, 255, 0, 255], [255, 0, 0, 0], [0, 255, 0, 0], [255, 0, 255, 0]], [[0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 255]], [[0, 0, 255, 0], [255, 255, 0, 0], [0, 0, 0, 255], [0, 0, 255, 0]], [[0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 0], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", None, np.uint16, 13333, False): np.array( [ [[0, 255, 0, 255], [0, 0, 255, 0], [0, 255, 0, 0], [255, 0, 255, 0]], [[255, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 255]], [[255, 0, 0, 0], [0, 255, 255, 0], [0, 0, 0, 255], [255, 0, 0, 0]], [[0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", None, None, True): np.array( [ [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", None, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", None, 232, True): np.array( [ [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", None, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", None, 13333, True): np.array( [ [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint16, None, True): np.array( [ [[0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint16, None, False): np.array( [ [[0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint16, 232, True): np.array( [ [[0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint16, 232, False): np.array( [ [[0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint16, 13333, True): np.array( [ [[0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "SIMPLE", np.uint16, 13333, False): np.array( [ [[0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGB", None, None, True): Exception, (np.uint16, "RGBA", "RGB", None, None, False): Exception, (np.uint16, "RGBA", "RGB", None, 232, True): Exception, (np.uint16, "RGBA", "RGB", None, 232, False): Exception, (np.uint16, "RGBA", "RGB", None, 13333, True): Exception, (np.uint16, "RGBA", "RGB", None, 13333, False): Exception, (np.uint16, "RGBA", "RGB", np.uint8, None, True): Exception, (np.uint16, "RGBA", "RGB", np.uint8, None, False): Exception, (np.uint16, "RGBA", "RGB", np.uint8, 232, True): Exception, (np.uint16, "RGBA", "RGB", np.uint8, 232, False): Exception, (np.uint16, "RGBA", "RGB", np.uint8, 13333, True): Exception, (np.uint16, "RGBA", "RGB", np.uint8, 13333, False): Exception, (np.uint16, "RGBA", "RGB", np.uint16, None, True): Exception, (np.uint16, "RGBA", "RGB", np.uint16, None, False): Exception, (np.uint16, "RGBA", "RGB", np.uint16, 232, True): Exception, (np.uint16, "RGBA", "RGB", np.uint16, 232, False): Exception, (np.uint16, "RGBA", "RGB", np.uint16, 13333, True): Exception, (np.uint16, "RGBA", "RGB", np.uint16, 13333, False): Exception, (np.uint16, "RGBA", "RGBA", None, None, True): np.array( [ [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[108, 255, 255, 255], [255, 255, 255, 255], [203, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 139, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", None, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 108, 255], [255, 255, 255, 255], [255, 255, 203, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 139, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", None, 232, True): np.array( [ [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[98, 255, 255, 255], [255, 255, 255, 255], [184, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 126, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", None, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 98, 255], [255, 255, 255, 255], [255, 255, 184, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 126, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", None, 13333, True): np.array( [ [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint16, None, True): np.array( [ [[0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint16, None, False): np.array( [ [[0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint16, 232, True): np.array( [ [[255, 255, 255, 0], [255, 0, 255, 0], [0, 0, 255, 0], [0, 0, 255, 0]], [[0, 0, 255, 0], [255, 0, 255, 0], [0, 255, 0, 0], [255, 0, 255, 0]], [[0, 255, 0, 0], [255, 0, 0, 0], [0, 255, 0, 0], [255, 255, 255, 0]], [[0, 0, 255, 0], [0, 0, 255, 255], [0, 255, 255, 255], [255, 0, 255, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint16, 232, False): np.array( [ [[255, 255, 255, 0], [255, 0, 255, 0], [255, 0, 0, 0], [255, 0, 0, 0]], [[255, 0, 0, 0], [255, 0, 255, 0], [0, 255, 0, 0], [255, 0, 255, 0]], [[0, 255, 0, 0], [0, 0, 255, 0], [0, 255, 0, 0], [255, 255, 255, 0]], [[255, 0, 0, 0], [255, 0, 0, 255], [255, 255, 0, 255], [255, 0, 255, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint16, 13333, True): np.array( [ [[255, 255, 0, 0], [255, 0, 0, 0], [0, 255, 0, 0], [0, 255, 0, 0]], [[0, 255, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [255, 0, 0, 0], [255, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.uint16, "RGBA", "RGBA", np.uint16, 13333, False): np.array( [ [[0, 255, 255, 0], [0, 0, 255, 0], [0, 255, 0, 0], [0, 255, 0, 0]], [[0, 255, 0, 0], [0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 255, 0], [0, 0, 255, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", None, None, True): np.array( [ [[255, 255, 255, 255], [136, 136, 136, 255], [0, 0, 0, 0], [225, 225, 225, 255]], [[216, 216, 216, 255], [224, 224, 224, 255], [255, 255, 255, 255], [16, 16, 16, 255]], [[252, 252, 252, 255], [216, 216, 216, 255], [42, 42, 42, 255], [252, 252, 252, 255]], [[18, 18, 18, 255], [169, 169, 169, 255], [223, 223, 223, 255], [252, 252, 252, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", None, None, False): np.array( [ [[255, 255, 255, 255], [136, 136, 136, 255], [0, 0, 0, 0], [225, 225, 225, 255]], [[216, 216, 216, 255], [224, 224, 224, 255], [255, 255, 255, 255], [16, 16, 16, 255]], [[252, 252, 252, 255], [216, 216, 216, 255], [42, 42, 42, 255], [252, 252, 252, 255]], [[18, 18, 18, 255], [169, 169, 169, 255], [223, 223, 223, 255], [252, 252, 252, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", None, 232, True): np.array( [ [[255, 255, 255, 255], [124, 124, 124, 255], [0, 0, 0, 0], [205, 205, 205, 255]], [[197, 197, 197, 255], [204, 204, 204, 255], [232, 232, 232, 255], [15, 15, 15, 255]], [[230, 230, 230, 255], [196, 196, 196, 255], [38, 38, 38, 255], [229, 229, 229, 255]], [[16, 16, 16, 255], [153, 153, 153, 255], [203, 203, 203, 255], [229, 229, 229, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", None, 232, False): np.array( [ [[255, 255, 255, 255], [124, 124, 124, 255], [0, 0, 0, 0], [205, 205, 205, 255]], [[197, 197, 197, 255], [204, 204, 204, 255], [232, 232, 232, 255], [15, 15, 15, 255]], [[230, 230, 230, 255], [196, 196, 196, 255], [38, 38, 38, 255], [229, 229, 229, 255]], [[16, 16, 16, 255], [153, 153, 153, 255], [203, 203, 203, 255], [229, 229, 229, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint16, None, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 0], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint16, None, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 0], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint16, 232, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 0], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint16, 232, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 0], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint16, 13333, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 0], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "SIMPLE", np.uint16, 13333, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [255, 255, 255, 0], [0, 0, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGB", None, None, True): Exception, (np.float32, "2D", "RGB", None, None, False): Exception, (np.float32, "2D", "RGB", None, 232, True): Exception, (np.float32, "2D", "RGB", None, 232, False): Exception, (np.float32, "2D", "RGB", None, 13333, True): Exception, (np.float32, "2D", "RGB", None, 13333, False): Exception, (np.float32, "2D", "RGB", np.uint8, None, True): Exception, (np.float32, "2D", "RGB", np.uint8, None, False): Exception, (np.float32, "2D", "RGB", np.uint8, 232, True): Exception, (np.float32, "2D", "RGB", np.uint8, 232, False): Exception, (np.float32, "2D", "RGB", np.uint8, 13333, True): Exception, (np.float32, "2D", "RGB", np.uint8, 13333, False): Exception, (np.float32, "2D", "RGB", np.uint16, None, True): Exception, (np.float32, "2D", "RGB", np.uint16, None, False): Exception, (np.float32, "2D", "RGB", np.uint16, 232, True): Exception, (np.float32, "2D", "RGB", np.uint16, 232, False): Exception, (np.float32, "2D", "RGB", np.uint16, 13333, True): Exception, (np.float32, "2D", "RGB", np.uint16, 13333, False): Exception, (np.float32, "2D", "RGBA", None, None, True): np.array( [ [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [1, 1, 1, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", None, None, False): np.array( [ [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [1, 1, 1, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", None, 232, True): np.array( [ [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", None, 232, False): np.array( [ [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", None, 13333, True): np.array( [ [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [52, 52, 52, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [8, 8, 8, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [45, 45, 45, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", None, 13333, False): np.array( [ [[255, 255, 255, 255], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [52, 52, 52, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [8, 8, 8, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [45, 45, 45, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint16, None, True): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint16, None, False): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint16, 232, True): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint16, 232, False): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint16, 13333, True): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "2D", "RGBA", np.uint16, 13333, False): np.array( [ [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", None, None, True): np.array( [ [[59, 99, 32, 255], [103, 231, 72, 255], [233, 117, 43, 255], [74, 249, 184, 255]], [[0, 185, 137, 0], [232, 72, 187, 255], [86, 56, 249, 255], [35, 88, 101, 255]], [[217, 49, 0, 255], [123, 12, 152, 255], [255, 83, 37, 255], [95, 90, 22, 255]], [[177, 133, 125, 255], [238, 36, 174, 255], [70, 177, 17, 255], [67, 65, 219, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", None, None, False): np.array( [ [[32, 99, 59, 255], [72, 231, 103, 255], [43, 117, 233, 255], [184, 249, 74, 255]], [[137, 185, 0, 0], [187, 72, 232, 255], [249, 56, 86, 255], [101, 88, 35, 255]], [[0, 49, 217, 255], [152, 12, 123, 255], [37, 83, 255, 255], [22, 90, 95, 255]], [[125, 133, 177, 255], [174, 36, 238, 255], [17, 177, 70, 255], [219, 65, 67, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", None, 232, True): np.array( [ [[54, 90, 29, 255], [94, 210, 65, 255], [212, 106, 39, 255], [68, 227, 167, 255]], [[0, 169, 125, 0], [211, 65, 170, 255], [78, 51, 227, 255], [32, 80, 91, 255]], [[197, 44, 0, 255], [112, 11, 138, 255], [255, 75, 34, 255], [86, 82, 20, 255]], [[161, 121, 113, 255], [216, 33, 158, 255], [64, 161, 15, 255], [61, 59, 199, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", None, 232, False): np.array( [ [[29, 90, 54, 255], [65, 210, 94, 255], [39, 106, 212, 255], [167, 227, 68, 255]], [[125, 169, 0, 0], [170, 65, 211, 255], [227, 51, 78, 255], [91, 80, 32, 255]], [[0, 44, 197, 255], [138, 11, 112, 255], [34, 75, 255, 255], [20, 82, 86, 255]], [[113, 121, 161, 255], [158, 33, 216, 255], [15, 161, 64, 255], [199, 59, 61, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", None, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", None, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 0, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint8, None, True): np.array( [ [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 0, 255], [255, 0, 255, 255]], [[255, 0, 0, 0], [255, 255, 0, 255], [255, 255, 0, 255], [0, 255, 0, 255]], [[0, 0, 255, 255], [0, 255, 255, 255], [255, 0, 0, 255], [0, 255, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint8, None, False): np.array( [ [[255, 0, 0, 255], [255, 0, 0, 255], [0, 0, 0, 255], [255, 0, 255, 255]], [[0, 0, 255, 0], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 0, 255]], [[255, 0, 0, 255], [255, 255, 0, 255], [0, 0, 255, 255], [255, 255, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [0, 0, 255, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint8, 232, True): np.array( [ [[255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255], [255, 0, 0, 255]], [[255, 0, 0, 0], [0, 0, 255, 255], [255, 0, 0, 255], [255, 255, 0, 255]], [[0, 255, 255, 255], [255, 0, 255, 255], [255, 0, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 0, 255, 255], [255, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint8, 232, False): np.array( [ [[0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255], [0, 0, 255, 255]], [[0, 0, 255, 0], [255, 0, 0, 255], [0, 0, 255, 255], [0, 255, 255, 255]], [[255, 255, 0, 255], [255, 0, 255, 255], [255, 0, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 0, 255, 255], [0, 0, 255, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint16, None, True): np.array( [ [[0, 0, 255, 255], [255, 0, 0, 255], [255, 0, 255, 255], [0, 0, 0, 255]], [[255, 0, 255, 0], [0, 0, 255, 255], [255, 255, 0, 255], [0, 0, 255, 255]], [[255, 0, 255, 255], [255, 0, 0, 255], [0, 0, 255, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [0, 0, 255, 255], [0, 255, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint16, None, False): np.array( [ [[255, 0, 0, 255], [0, 0, 255, 255], [255, 0, 255, 255], [0, 0, 0, 255]], [[255, 0, 255, 0], [255, 0, 0, 255], [0, 255, 255, 255], [255, 0, 0, 255]], [[255, 0, 255, 255], [0, 0, 255, 255], [255, 0, 0, 255], [255, 255, 255, 255]], [[0, 0, 0, 255], [255, 0, 0, 255], [0, 255, 0, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint16, 232, True): np.array( [ [[255, 255, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255], [0, 0, 0, 255]], [[255, 0, 0, 0], [0, 0, 0, 255], [255, 255, 0, 255], [0, 0, 0, 255]], [[0, 0, 255, 255], [0, 0, 255, 255], [0, 255, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 0, 255], [0, 0, 255, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint16, 232, False): np.array( [ [[0, 255, 255, 255], [0, 255, 0, 255], [255, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 255, 0], [0, 0, 0, 255], [0, 255, 255, 255], [0, 0, 0, 255]], [[255, 0, 0, 255], [255, 0, 0, 255], [0, 255, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 255, 255, 255], [255, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint16, 13333, True): np.array( [ [[255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255], [255, 0, 0, 255]], [[255, 255, 0, 0], [0, 0, 0, 255], [255, 0, 0, 255], [255, 0, 255, 255]], [[0, 255, 255, 255], [255, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 0, 255, 255], [0, 0, 0, 255], [255, 0, 255, 255], [255, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "SIMPLE", np.uint16, 13333, False): np.array( [ [[0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255], [0, 0, 255, 255]], [[0, 255, 255, 0], [0, 0, 0, 255], [0, 0, 255, 255], [255, 0, 255, 255]], [[255, 255, 0, 255], [0, 0, 255, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[255, 0, 255, 255], [0, 0, 0, 255], [255, 0, 255, 255], [0, 0, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", None, None, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", None, None, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", None, 232, True): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [255, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", None, 232, False): np.array( [ [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 255, 255], [0, 0, 0, 255]], [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", None, 13333, True): np.array( [ [[12, 0, 0, 255], [21, 0, 0, 255], [47, 0, 0, 255], [15, 0, 0, 255]], [[0, 0, 0, 0], [47, 0, 0, 255], [17, 0, 0, 255], [7, 0, 0, 255]], [[44, 0, 0, 255], [25, 0, 0, 255], [255, 0, 0, 255], [19, 0, 0, 255]], [[36, 0, 0, 255], [48, 0, 0, 255], [14, 0, 0, 255], [13, 0, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", None, 13333, False): np.array( [ [[0, 0, 12, 255], [0, 0, 21, 255], [0, 0, 47, 255], [0, 0, 15, 255]], [[0, 0, 0, 0], [0, 0, 47, 255], [0, 0, 17, 255], [0, 0, 7, 255]], [[0, 0, 44, 255], [0, 0, 25, 255], [0, 0, 255, 255], [0, 0, 19, 255]], [[0, 0, 36, 255], [0, 0, 48, 255], [0, 0, 14, 255], [0, 0, 13, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint8, None, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint8, None, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint8, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint8, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[255, 255, 255, 0], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 255, 0], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint16, None, True): np.array( [ [[0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint16, None, False): np.array( [ [[255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint16, 232, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint16, 232, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint16, 13333, True): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGB", np.uint16, 13333, False): np.array( [ [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 0], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGB", "RGBA", None, None, True): Exception, (np.float32, "RGB", "RGBA", None, None, False): Exception, (np.float32, "RGB", "RGBA", None, 232, True): Exception, (np.float32, "RGB", "RGBA", None, 232, False): Exception, (np.float32, "RGB", "RGBA", None, 13333, True): Exception, (np.float32, "RGB", "RGBA", None, 13333, False): Exception, (np.float32, "RGB", "RGBA", np.uint8, None, True): Exception, (np.float32, "RGB", "RGBA", np.uint8, None, False): Exception, (np.float32, "RGB", "RGBA", np.uint8, 232, True): Exception, (np.float32, "RGB", "RGBA", np.uint8, 232, False): Exception, (np.float32, "RGB", "RGBA", np.uint8, 13333, True): Exception, (np.float32, "RGB", "RGBA", np.uint8, 13333, False): Exception, (np.float32, "RGB", "RGBA", np.uint16, None, True): Exception, (np.float32, "RGB", "RGBA", np.uint16, None, False): Exception, (np.float32, "RGB", "RGBA", np.uint16, 232, True): Exception, (np.float32, "RGB", "RGBA", np.uint16, 232, False): Exception, (np.float32, "RGB", "RGBA", np.uint16, 13333, True): Exception, (np.float32, "RGB", "RGBA", np.uint16, 13333, False): Exception, (np.float32, "RGBA", "SIMPLE", None, None, True): np.array( [ [[248, 159, 5, 0], [217, 237, 151, 255], [102, 203, 57, 44], [244, 177, 73, 196]], [[29, 189, 17, 250], [168, 188, 84, 208], [198, 134, 144, 81], [208, 209, 210, 126]], [[8, 212, 80, 241], [159, 153, 128, 236], [0, 0, 7, 0], [249, 140, 61, 88]], [[175, 255, 216, 90], [42, 76, 23, 172], [64, 65, 205, 254], [0, 255, 148, 117]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", None, None, False): np.array( [ [[5, 159, 248, 0], [151, 237, 217, 255], [57, 203, 102, 44], [73, 177, 244, 196]], [[17, 189, 29, 250], [84, 188, 168, 208], [144, 134, 198, 81], [210, 209, 208, 126]], [[80, 212, 8, 241], [128, 153, 159, 236], [7, 0, 0, 0], [61, 140, 249, 88]], [[216, 255, 175, 90], [23, 76, 42, 172], [205, 65, 64, 254], [148, 255, 0, 117]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", None, 232, True): np.array( [ [[225, 145, 5, 0], [197, 215, 138, 255], [93, 185, 51, 40], [222, 161, 66, 178]], [[26, 172, 15, 227], [153, 171, 76, 189], [181, 122, 131, 73], [189, 190, 191, 114]], [[7, 193, 73, 219], [144, 139, 116, 215], [0, 0, 6, 0], [227, 127, 55, 80]], [[159, 255, 197, 82], [38, 69, 21, 157], [58, 59, 186, 231], [0, 232, 134, 107]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", None, 232, False): np.array( [ [[5, 145, 225, 0], [138, 215, 197, 255], [51, 185, 93, 40], [66, 161, 222, 178]], [[15, 172, 26, 227], [76, 171, 153, 189], [131, 122, 181, 73], [191, 190, 189, 114]], [[73, 193, 7, 219], [116, 139, 144, 215], [6, 0, 0, 0], [55, 127, 227, 80]], [[197, 255, 159, 82], [21, 69, 38, 157], [186, 59, 58, 231], [134, 232, 0, 107]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", None, 13333, True): np.array( [ [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 0, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", None, 13333, False): np.array( [ [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 0, 0, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint8, None, True): np.array( [ [[255, 0, 0, 0], [0, 0, 0, 255], [255, 0, 0, 255], [255, 0, 0, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 0, 255, 255]], [[255, 255, 255, 0], [0, 0, 255, 255], [255, 255, 0, 0], [0, 255, 0, 255]], [[0, 255, 255, 255], [255, 255, 0, 255], [255, 0, 0, 255], [255, 255, 255, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint8, None, False): np.array( [ [[0, 0, 255, 0], [0, 0, 0, 255], [0, 0, 255, 255], [0, 0, 255, 255]], [[0, 0, 0, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 0, 255, 255]], [[255, 255, 255, 0], [255, 0, 0, 255], [0, 255, 255, 0], [0, 255, 0, 255]], [[255, 255, 0, 255], [0, 255, 255, 255], [0, 0, 255, 255], [255, 255, 255, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint8, 232, True): np.array( [ [[0, 0, 0, 0], [0, 0, 255, 255], [0, 0, 0, 255], [255, 0, 255, 255]], [[255, 255, 0, 0], [0, 0, 255, 0], [0, 255, 0, 0], [0, 255, 0, 255]], [[0, 0, 0, 0], [255, 0, 255, 0], [255, 255, 255, 0], [0, 0, 0, 255]], [[0, 255, 0, 255], [255, 0, 0, 0], [255, 0, 255, 0], [255, 255, 255, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint8, 232, False): np.array( [ [[0, 0, 0, 0], [255, 0, 0, 255], [0, 0, 0, 255], [255, 0, 255, 255]], [[0, 255, 255, 0], [255, 0, 0, 0], [0, 255, 0, 0], [0, 255, 0, 255]], [[0, 0, 0, 0], [255, 0, 255, 0], [255, 255, 255, 0], [0, 0, 0, 255]], [[0, 255, 0, 255], [0, 0, 255, 0], [255, 0, 255, 0], [255, 255, 255, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint8, 13333, True): np.array( [ [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint8, 13333, False): np.array( [ [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint16, None, True): np.array( [ [[0, 0, 255, 0], [0, 255, 255, 0], [255, 255, 0, 255], [0, 0, 0, 255]], [[0, 255, 0, 255], [255, 255, 0, 255], [0, 0, 255, 255], [255, 0, 0, 255]], [[0, 0, 255, 0], [255, 0, 0, 0], [255, 255, 255, 0], [0, 255, 255, 0]], [[255, 0, 255, 0], [255, 0, 0, 255], [255, 0, 0, 0], [255, 0, 0, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint16, None, False): np.array( [ [[255, 0, 0, 0], [255, 255, 0, 0], [0, 255, 255, 255], [0, 0, 0, 255]], [[0, 255, 0, 255], [0, 255, 255, 255], [255, 0, 0, 255], [0, 0, 255, 255]], [[255, 0, 0, 0], [0, 0, 255, 0], [255, 255, 255, 0], [255, 255, 0, 0]], [[255, 0, 255, 0], [0, 0, 255, 255], [0, 0, 255, 0], [0, 0, 255, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint16, 232, True): np.array( [ [[255, 0, 0, 0], [0, 0, 255, 0], [255, 0, 255, 0], [255, 0, 255, 0]], [[0, 0, 255, 0], [255, 255, 0, 255], [0, 0, 0, 0], [255, 0, 0, 255]], [[0, 0, 0, 255], [255, 0, 0, 0], [255, 255, 255, 0], [0, 0, 0, 0]], [[255, 0, 0, 0], [0, 255, 255, 0], [0, 0, 255, 255], [255, 0, 0, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint16, 232, False): np.array( [ [[0, 0, 255, 0], [255, 0, 0, 0], [255, 0, 255, 0], [255, 0, 255, 0]], [[255, 0, 0, 0], [0, 255, 255, 255], [0, 0, 0, 0], [0, 0, 255, 255]], [[0, 0, 0, 255], [0, 0, 255, 0], [255, 255, 255, 0], [0, 0, 0, 0]], [[0, 0, 255, 0], [255, 255, 0, 0], [255, 0, 0, 255], [0, 0, 255, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint16, 13333, True): np.array( [ [[255, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 255, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [255, 0, 0, 0], [0, 0, 255, 255]], [[0, 0, 0, 0], [0, 0, 255, 255], [255, 255, 0, 0], [255, 255, 0, 0]], [[255, 0, 0, 0], [0, 255, 0, 0], [0, 0, 0, 255], [255, 0, 0, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "SIMPLE", np.uint16, 13333, False): np.array( [ [[0, 0, 255, 0], [0, 0, 0, 0], [0, 0, 0, 255], [255, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 255, 0], [255, 0, 0, 255]], [[0, 0, 0, 0], [255, 0, 0, 255], [0, 255, 255, 0], [0, 255, 255, 0]], [[0, 0, 255, 0], [0, 255, 0, 0], [0, 0, 0, 255], [0, 0, 255, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGB", None, None, True): Exception, (np.float32, "RGBA", "RGB", None, None, False): Exception, (np.float32, "RGBA", "RGB", None, 232, True): Exception, (np.float32, "RGBA", "RGB", None, 232, False): Exception, (np.float32, "RGBA", "RGB", None, 13333, True): Exception, (np.float32, "RGBA", "RGB", None, 13333, False): Exception, (np.float32, "RGBA", "RGB", np.uint8, None, True): Exception, (np.float32, "RGBA", "RGB", np.uint8, None, False): Exception, (np.float32, "RGBA", "RGB", np.uint8, 232, True): Exception, (np.float32, "RGBA", "RGB", np.uint8, 232, False): Exception, (np.float32, "RGBA", "RGB", np.uint8, 13333, True): Exception, (np.float32, "RGBA", "RGB", np.uint8, 13333, False): Exception, (np.float32, "RGBA", "RGB", np.uint16, None, True): Exception, (np.float32, "RGBA", "RGB", np.uint16, None, False): Exception, (np.float32, "RGBA", "RGB", np.uint16, 232, True): Exception, (np.float32, "RGBA", "RGB", np.uint16, 232, False): Exception, (np.float32, "RGBA", "RGB", np.uint16, 13333, True): Exception, (np.float32, "RGBA", "RGB", np.uint16, 13333, False): Exception, (np.float32, "RGBA", "RGBA", None, None, True): np.array( [ [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 255, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", None, None, False): np.array( [ [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 255, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", None, 232, True): np.array( [ [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 255, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", None, 232, False): np.array( [ [[0, 0, 0, 0], [0, 0, 0, 255], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 255, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", None, 13333, True): np.array( [ [[0, 0, 1, 0], [0, 0, 31, 255], [0, 0, 11, 0], [0, 0, 15, 0]], [[0, 0, 3, 0], [0, 0, 17, 0], [0, 0, 29, 0], [0, 0, 43, 0]], [[0, 0, 16, 0], [0, 0, 26, 0], [0, 0, 1, 0], [0, 0, 12, 0]], [[0, 255, 44, 0], [0, 0, 4, 0], [0, 0, 42, 0], [0, 0, 30, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", None, 13333, False): np.array( [ [[1, 0, 0, 0], [31, 0, 0, 255], [11, 0, 0, 0], [15, 0, 0, 0]], [[3, 0, 0, 0], [17, 0, 0, 0], [29, 0, 0, 0], [43, 0, 0, 0]], [[16, 0, 0, 0], [26, 0, 0, 0], [1, 0, 0, 0], [12, 0, 0, 0]], [[44, 255, 0, 0], [4, 0, 0, 0], [42, 0, 0, 0], [30, 0, 0, 0]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint8, None, True): np.array( [ [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint8, None, False): np.array( [ [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint8, 232, True): np.array( [ [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint8, 232, False): np.array( [ [[255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint8, 13333, True): np.array( [ [[255, 255, 0, 0], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint8, 13333, False): np.array( [ [[0, 255, 255, 0], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 0], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint16, None, True): np.array( [ [[255, 255, 0, 0], [255, 255, 0, 0], [255, 255, 255, 255], [255, 255, 0, 255]], [[255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 0, 0], [255, 255, 0, 255]], [[255, 0, 0, 255], [255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 0, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint16, None, False): np.array( [ [[0, 255, 255, 0], [0, 255, 255, 0], [255, 255, 255, 255], [0, 255, 255, 255]], [[0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [0, 255, 255, 0], [0, 255, 255, 255]], [[0, 0, 255, 255], [255, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint16, 232, True): np.array( [ [[255, 255, 255, 0], [255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 0, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint16, 232, False): np.array( [ [[255, 255, 255, 0], [255, 255, 255, 0], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 0], [255, 255, 255, 255]], [[255, 0, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint16, 13333, True): np.array( [ [[255, 255, 0, 0], [255, 255, 0, 0], [255, 255, 0, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 255]], [[255, 255, 0, 255], [255, 255, 0, 255], [255, 255, 0, 0], [255, 255, 255, 255]], [[255, 0, 0, 255], [255, 255, 0, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), (np.float32, "RGBA", "RGBA", np.uint16, 13333, False): np.array( [ [[0, 255, 255, 0], [0, 255, 255, 0], [0, 255, 255, 255], [255, 255, 255, 255]], [[255, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 255]], [[0, 255, 255, 255], [0, 255, 255, 255], [0, 255, 255, 0], [255, 255, 255, 255]], [[0, 0, 255, 255], [0, 255, 255, 255], [255, 255, 255, 255], [255, 255, 255, 255]], ], dtype=np.uint8, ), } def _error_description(output, test_case): return ( f"'{test_case['name']}' output does not match expectations\n" + f"\tExpected:\n{test_case['expected_output']!r}\n\n" + f"\tReceived:\n{output!r}\n" ) def _do_something_for_every_combo(func): for dtype in [np.uint8, np.uint16, np.float32]: for in_fmt in ["2D", "RGB", "RGBA"]: data = INPUTS[(dtype, in_fmt)] for levels_name in [None, "SIMPLE", "RGB", "RGBA"]: levels = LEVELS.get(levels_name, None) if dtype == np.float32 and levels_name is None: continue for lut_type in [None, np.uint8, np.uint16]: lut = LUTS.get(lut_type, None) for scale in [None, 232, 13333]: for use_rgba in [True, False]: key = (dtype, in_fmt, levels_name, lut_type, scale, use_rgba) func(data, key, levels, lut, scale, use_rgba) def _makeARGB(*args, **kwds): img, alpha = real_makeARGB(*args, **kwds) if kwds.get('useRGBA'): # endian independent out = img elif sys.byteorder == 'little': # little-endian ARGB32 to B,G,R,A out = img else: # big-endian ARGB32 to B,G,R,A out = img[..., [3, 2, 1, 0]] return out, alpha def save_reference(): """ This saves the output (or exception type) of running makeARGB for every combo of arguments. The output isn't fit for immediate inclusion in this file as EXPECTED_OUTPUTS, and needs some replace-all work. """ with open("_unformatted_expected_outputs_", "w") as tmp_file: def write_expectation_to_file(data, key, levels, lut, scale, use_rgba): try: output, alpha = _makeARGB(data, lut=lut, levels=levels, scale=scale, useRGBA=use_rgba) except Exception as e: tmp_file.write(f"{key!r}: {type(e)}\n") else: tmp_file.write(f"{key!r}: {output!r},\n") _do_something_for_every_combo(write_expectation_to_file) def test_makeARGB_against_generated_references(): def assert_correct(data, key, levels, lut, scale, use_rgba): expectation = EXPECTED_OUTPUTS[key] if isinstance(expectation, type) and issubclass(expectation, Exception): try: _makeARGB(data, lut=lut, levels=levels, scale=scale, useRGBA=use_rgba) except Exception as e: assert expectation == type(e) else: assert False, f"makeARGB({key!r}) was supposed to raise {expectation} but didn't raise anything." else: output, alpha = _makeARGB(data, lut=lut, levels=levels, scale=scale, useRGBA=use_rgba) assert ( output == expectation ).all(), f"Incorrect _makeARGB({key!r}) output! Expected:\n{expectation!r}\n Got:\n{output!r}" _do_something_for_every_combo(assert_correct) @pytest.mark.skipif(cupy is None, reason="CuPy unavailable to test") def test_cupy_makeARGB_against_generated_references(): prev_setting = getConfigOption("useCupy") try: setConfigOption("useCupy", True) cupy = getCupy() def assert_cupy_correct(data, key, levels, lut, scale, use_rgba): data = cupy.asarray(data) if lut is not None: lut = cupy.asarray(lut) expectation = EXPECTED_OUTPUTS[key] if isinstance(expectation, type) and issubclass(expectation, Exception): try: _makeARGB(data, lut=lut, levels=levels, scale=scale, useRGBA=use_rgba) except Exception as e: assert expectation == type(e) else: assert False, f"makeARGB({key!r}) was supposed to raise {expectation} but didn't raise anything." else: expectation = cupy.asarray(expectation) output, alpha = _makeARGB(data, lut=lut, levels=levels, scale=scale, useRGBA=use_rgba) assert ( output == expectation ).all(), f"Incorrect _makeARGB({key!r}) output! Expected:\n{expectation!r}\n Got:\n{output!r}" _do_something_for_every_combo(assert_cupy_correct) finally: setConfigOption("useCupy", prev_setting) @pytest.mark.filterwarnings("ignore:invalid value encountered") def test_numba_makeARGB_against_generated_references(): oldcfg_numba = getConfigOption("useNumba") if not oldcfg_numba: try: import numba except ImportError: pytest.skip("Numba unavailable to test") # useCupy needs to be set to False because it takes # precedence over useNumba in rescaleData oldcfg_cupy = getConfigOption("useCupy") setConfigOption("useCupy", False) setConfigOption("useNumba", not oldcfg_numba) test_makeARGB_against_generated_references() setConfigOption("useNumba", oldcfg_numba) setConfigOption("useCupy", oldcfg_cupy) def test_makeARGB_with_human_readable_code(): # Many parameters to test here: # * data dtype (ubyte, uint16, float, others) # * data ndim (2 or 3) # * levels (None, 1D, or 2D) # * lut dtype # * lut size # * lut ndim (1 or 2) # * useRGBA argument # Need to check that all input values map to the correct output values, especially # at and beyond the edges of the level range. def checkArrays(a, b): # because py.test output is difficult to read for arrays if not np.all(a == b): comp = [] for i in range(a.shape[0]): if a.shape[1] > 1: comp.append('[') for j in range(a.shape[1]): m = a[i, j] == b[i, j] comp.append('%d,%d %s %s %s%s' % (i, j, str(a[i, j]).ljust(15), str(b[i, j]).ljust(15), m, ' ********' if not np.all(m) else '')) if a.shape[1] > 1: comp.append(']') raise Exception("arrays do not match:\n%s" % '\n'.join(comp)) def checkImage(img, check, alpha, alphaCheck): assert img.dtype == np.ubyte assert alpha is alphaCheck if alpha is False: checkArrays(img[..., 3], 255) if np.isscalar(check) or check.ndim == 3: checkArrays(img[..., :3], check) elif check.ndim == 2: checkArrays(img[..., :3], check[..., np.newaxis]) elif check.ndim == 1: checkArrays(img[..., :3], check[..., np.newaxis, np.newaxis]) else: raise Exception('invalid check array ndim') # uint8 data tests im1 = np.arange(256).astype('ubyte').reshape(256, 1) im2, alpha = _makeARGB(im1, levels=(0, 255)) checkImage(im2, im1, alpha, False) im3, alpha = _makeARGB(im1, levels=(0.0, 255.0)) checkImage(im3, im1, alpha, False) im4, alpha = _makeARGB(im1, levels=(255, 0)) checkImage(im4, 255 - im1, alpha, False) im5, alpha = _makeARGB(np.concatenate([im1] * 3, axis=1), levels=[(0, 255), (0.0, 255.0), (255, 0)]) checkImage(im5, np.concatenate([im1, im1, 255 - im1], axis=1), alpha, False) im2, alpha = _makeARGB(im1, levels=(128, 383)) checkImage(im2[:128], 0, alpha, False) checkImage(im2[128:], im1[:128], alpha, False) # uint8 data + uint8 LUT lut = np.arange(256)[::-1].astype(np.uint8) im2, alpha = _makeARGB(im1, lut=lut) checkImage(im2, lut, alpha, False) # lut larger than maxint lut = np.arange(511).astype(np.uint8) im2, alpha = _makeARGB(im1, lut=lut) checkImage(im2, lut[::2], alpha, False) # lut smaller than maxint lut = np.arange(128).astype(np.uint8) im2, alpha = _makeARGB(im1, lut=lut) checkImage(im2, np.linspace(0, 127.5, 256, dtype='ubyte'), alpha, False) # lut + levels lut = np.arange(256)[::-1].astype(np.uint8) im2, alpha = _makeARGB(im1, lut=lut, levels=[-128, 384]) checkImage(im2, np.linspace(191.5, 64.5, 256, dtype='ubyte'), alpha, False) im2, alpha = _makeARGB(im1, lut=lut, levels=[64, 192]) checkImage(im2, np.clip(np.linspace(384.5, -127.5, 256), 0, 255).astype('ubyte'), alpha, False) # uint8 data + uint16 LUT lut = np.arange(4096)[::-1].astype(np.uint16) // 16 im2, alpha = _makeARGB(im1, lut=lut) checkImage(im2, np.arange(256)[::-1].astype('ubyte'), alpha, False) # uint8 data + float LUT lut = np.linspace(10., 137., 256) im2, alpha = _makeARGB(im1, lut=lut) checkImage(im2, lut.astype('ubyte'), alpha, False) # uint8 data + 2D LUT lut = np.zeros((256, 3), dtype='ubyte') lut[:, 0] = np.arange(256) lut[:, 1] = np.arange(256)[::-1] lut[:, 2] = 7 im2, alpha = _makeARGB(im1, lut=lut) checkImage(im2, lut[:, None, ::-1], alpha, False) # check useRGBA im2, alpha = _makeARGB(im1, lut=lut, useRGBA=True) checkImage(im2, lut[:, None, :], alpha, False) # uint16 data tests im1 = np.arange(0, 2 ** 16, 256).astype('uint16')[:, None] im2, alpha = _makeARGB(im1, levels=(512, 2 ** 16)) checkImage(im2, np.clip(np.linspace(-2, 253, 256), 0, 255).astype('ubyte'), alpha, False) lut = (np.arange(512, 2 ** 16)[::-1] // 256).astype('ubyte') im2, alpha = _makeARGB(im1, lut=lut, levels=(512, 2 ** 16 - 256)) checkImage(im2, np.clip(np.linspace(257, 2, 256), 0, 255).astype('ubyte'), alpha, False) lut = np.zeros(2 ** 16, dtype='ubyte') lut[1000:1256] = np.arange(256) lut[1256:] = 255 im1 = np.arange(1000, 1256).astype('uint16')[:, None] im2, alpha = _makeARGB(im1, lut=lut) checkImage(im2, np.arange(256).astype('ubyte'), alpha, False) # float data tests im1 = np.linspace(1.0, 17.0, 256)[:, None] im2, alpha = _makeARGB(im1, levels=(5.0, 13.0)) checkImage(im2, np.clip(np.linspace(-128, 383, 256), 0, 255).astype('ubyte'), alpha, False) lut = (np.arange(1280)[::-1] // 10).astype('ubyte') im2, alpha = _makeARGB(im1, lut=lut, levels=(1, 17)) checkImage(im2, np.linspace(127.5, 0, 256).astype('ubyte'), alpha, False) # nans in image # 2d input image, one pixel is nan im1 = np.ones((10, 12)) im1[3, 5] = np.nan im2, alpha = _makeARGB(im1, levels=(0, 1)) assert alpha assert im2[3, 5, 3] == 0 # nan pixel is transparent assert im2[0, 0, 3] == 255 # doesn't affect other pixels # With masking nans disabled, the nan pixel shouldn't be transparent im2, alpha = _makeARGB(im1, levels=(0, 1), maskNans=False) assert im2[3, 5, 3] == 255 # 3d RGB input image, any color channel of a pixel is nan im1 = np.ones((10, 12, 3)) im1[3, 5, 1] = np.nan im2, alpha = _makeARGB(im1, levels=(0, 1)) assert alpha assert im2[3, 5, 3] == 0 # nan pixel is transparent assert im2[0, 0, 3] == 255 # doesn't affect other pixels # 3d RGBA input image, any color channel of a pixel is nan im1 = np.ones((10, 12, 4)) im1[3, 5, 1] = np.nan im2, alpha = _makeARGB(im1, levels=(0, 1), useRGBA=True) assert alpha assert im2[3, 5, 3] == 0 # nan pixel is transparent # test sanity checks class AssertExc(object): def __init__(self, exc=Exception): self.exc = exc def __enter__(self): return self def __exit__(self, *args): assert args[0] is self.exc, "Should have raised %s (got %s)" % (self.exc, args[0]) return True with AssertExc(TypeError): # invalid image shape _makeARGB(np.zeros((2,), dtype='float')) with AssertExc(TypeError): # invalid image shape _makeARGB(np.zeros((2, 2, 7), dtype='float')) with AssertExc(): # float images require levels arg _makeARGB(np.zeros((2, 2), dtype='float')) with AssertExc(): # bad levels arg _makeARGB(np.zeros((2, 2), dtype='float'), levels=[1]) with AssertExc(): # bad levels arg _makeARGB(np.zeros((2, 2), dtype='float'), levels=[1, 2, 3]) with AssertExc(): # can't mix 3-channel levels and LUT _makeARGB(np.zeros((2, 2)), lut=np.zeros((10, 3), dtype='ubyte'), levels=[(0, 1)] * 3) with AssertExc(): # multichannel levels must have same number of channels as image _makeARGB(np.zeros((2, 2, 3), dtype='float'), levels=[(1, 2)] * 4) with AssertExc(): # 3d levels not allowed _makeARGB(np.zeros((2, 2, 3), dtype='float'), levels=np.zeros([3, 2, 2])) pyqtgraph-pyqtgraph-0.12.4/tests/test_qimage_writethru.py000066400000000000000000000015511421045507400237710ustar00rootroot00000000000000import numpy as np import pyqtgraph as pg def test_qimage_writethrough(): w, h = 256, 256 backstore = np.ones((h, w), dtype=np.uint8) ptr0 = backstore.ctypes.data fmt = pg.Qt.QtGui.QImage.Format.Format_Grayscale8 qimg = pg.functions.ndarray_to_qimage(backstore, fmt) def get_pointer(obj): if hasattr(obj, 'setsize'): return int(obj) else: return np.frombuffer(obj, dtype=np.uint8).ctypes.data # test that QImage is using the provided buffer (i.e. zero-copy) ptr1 = get_pointer(qimg.constBits()) assert ptr0 == ptr1 # test that QImage is not const (i.e. no COW) # if QImage is const, then bits() returns a copy ptr2 = get_pointer(qimg.bits()) assert ptr1 == ptr2 # test that data gets written through to provided buffer qimg.fill(0) assert np.all(backstore == 0) pyqtgraph-pyqtgraph-0.12.4/tests/test_qt.py000066400000000000000000000015731421045507400210410ustar00rootroot00000000000000import os import pytest import pyqtgraph as pg app = pg.mkQApp() def test_isQObjectAlive(): o1 = pg.QtCore.QObject() o2 = pg.QtCore.QObject() o2.setParent(o1) del o1 assert not pg.Qt.isQObjectAlive(o2) @pytest.mark.skipif(pg.Qt.QT_LIB == 'PySide', reason='pysideuic does not appear to be ' 'packaged with conda') @pytest.mark.skipif( pg.Qt.QT_LIB == "PySide2" and tuple(map(int, pg.Qt.PySide2.__version__.split("."))) >= (5, 14) and tuple(map(int, pg.Qt.PySide2.__version__.split("."))) < (5, 14, 2, 2), reason="new PySide2 doesn't have loadUi functionality" ) def test_loadUiType(): path = os.path.dirname(__file__) formClass, baseClass = pg.Qt.loadUiType(os.path.join(path, 'uictest.ui')) w = baseClass() ui = formClass() ui.setupUi(w) w.show() app.processEvents() pyqtgraph-pyqtgraph-0.12.4/tests/test_ref_cycles.py000066400000000000000000000040751421045507400225330ustar00rootroot00000000000000""" Test for unwanted reference cycles """ import warnings import weakref import numpy as np import pyqtgraph as pg app = pg.mkQApp() def assert_alldead(refs): for ref in refs: assert ref() is None def qObjectTree(root): """Return root and its entire tree of qobject children""" childs = [root] for ch in pg.QtCore.QObject.children(root): childs += qObjectTree(ch) return childs def mkrefs(*objs): """Return a list of weakrefs to each object in *objs. QObject instances are expanded to include all child objects. """ allObjs = {} for obj in objs: obj = qObjectTree(obj) if isinstance(obj, pg.QtCore.QObject) else [obj] for o in obj: allObjs[id(o)] = o return [weakref.ref(obj) for obj in allObjs.values()] def test_PlotWidget(): def mkobjs(*args, **kwds): with warnings.catch_warnings(): warnings.simplefilter("ignore") w = pg.PlotWidget(*args, **kwds) data = np.array([1,5,2,4,3]) c = w.plot(data, name='stuff') w.addLegend() # test that connections do not keep objects alive w.plotItem.vb.sigRangeChanged.connect(mkrefs) app.focusChanged.connect(w.plotItem.vb.invertY) # return weakrefs to a bunch of objects that should die when the scope exits. return mkrefs(w, c, data, w.plotItem, w.plotItem.vb, w.plotItem.getMenu(), w.plotItem.getAxis('left')) for _ in range(5): assert_alldead(mkobjs()) def test_GraphicsWindow(): def mkobjs(): with warnings.catch_warnings(): warnings.simplefilter("ignore") w = pg.GraphicsWindow() p1 = w.addPlot() v1 = w.addViewBox() return mkrefs(w, p1, v1) for _ in range(5): assert_alldead(mkobjs()) def test_ImageView(): def mkobjs(): iv = pg.ImageView() data = np.zeros((10,10,5)) iv.setImage(data) return mkrefs(iv, iv.imageItem, iv.view, iv.ui.histogram, data) for _ in range(5): assert_alldead(mkobjs()) pyqtgraph-pyqtgraph-0.12.4/tests/test_reload.py000066400000000000000000000051371421045507400216630ustar00rootroot00000000000000import os import shutil import sys import time import pytest import pyqtgraph as pg pgpath = os.path.join(os.path.dirname(pg.__file__), '..') pgpath_repr = repr(pgpath) code = """ import sys sys.path.append({path_repr}) import pyqtgraph as pg class C(pg.QtCore.QObject): sig = pg.QtCore.Signal() def fn(self): print("{msg}") """ def remove_cache(mod): if os.path.isfile(mod+'c'): os.remove(mod+'c') cachedir = os.path.join(os.path.dirname(mod), '__pycache__') if os.path.isdir(cachedir): shutil.rmtree(cachedir) @pytest.mark.skipif( ( (pg.Qt.QT_LIB == "PySide2" and pg.Qt.QtVersion.startswith("5.15")) or (pg.Qt.QT_LIB == "PySide6") ) and (sys.version_info >= (3, 9)), reason="Unknown Issue" ) @pytest.mark.usefixtures("tmp_module") def test_reload(tmp_module): # write a module mod = os.path.join(tmp_module, 'reload_test_mod.py') print("\nRELOAD FILE:", mod) with open(mod, "w") as file_: file_.write(code.format(path_repr=pgpath_repr, msg="C.fn() Version1")) # import the new module import reload_test_mod print("RELOAD MOD:", reload_test_mod.__file__) c = reload_test_mod.C() c.sig.connect(c.fn) v1 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, c.sig, c.fn, c.fn.__func__) # write again and reload with open(mod, "w") as file_: file_.write(code.format(path_repr=pgpath_repr, msg="C.fn() Version 2")) time.sleep(1.1) #remove_cache(mod) _ = pg.reload.reloadAll(tmp_module, debug=True) v2 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, c.sig, c.fn, c.fn.__func__) oldcfn = pg.reload.getPreviousVersion(c.fn) if oldcfn is None: # Function did not reload; are we using pytest's assertion rewriting? raise Exception("Function did not reload. (This can happen when using py.test" " with assertion rewriting; use --assert=plain for this test.)") assert oldcfn.__func__ is v1[2] assert oldcfn.__self__ is c # write again and reload with open(mod, "w") as file_: file_.write(code.format(path_repr=pgpath_repr, msg="C.fn() Version2")) time.sleep(1.1) # remove_cache(mod) _ = pg.reload.reloadAll(tmp_module, debug=True) _ = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, c.sig, c.fn, c.fn.__func__) cfn1 = pg.reload.getPreviousVersion(c.fn) cfn2 = pg.reload.getPreviousVersion(cfn1) assert cfn1.__func__ is v2[2] assert cfn2.__func__ is v1[2] assert cfn1.__self__ is c assert cfn2.__self__ is c pg.functions.disconnect(c.sig, c.fn) pyqtgraph-pyqtgraph-0.12.4/tests/test_signalproxy.py000066400000000000000000000072011421045507400227660ustar00rootroot00000000000000import pytest from pyqtgraph import SignalProxy from pyqtgraph.Qt import QtCore, mkQApp class Sender(QtCore.QObject): signalSend = QtCore.Signal() def __init__(self, parent=None): super(Sender, self).__init__(parent) class Receiver(QtCore.QObject): def __init__(self, parent=None): super(Receiver, self).__init__(parent) self.counter = 0 def slotReceive(self): self.counter += 1 @pytest.fixture def qapp(): app = mkQApp() if app is None: app = mkQApp() yield app app.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 100) def test_signal_proxy_slot(qapp): """Test the normal work mode of SignalProxy with `signal` and `slot`""" sender = Sender(parent=qapp) receiver = Receiver(parent=qapp) proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6, slot=receiver.slotReceive) assert proxy.blockSignal is False assert proxy is not None assert sender is not None assert receiver is not None sender.signalSend.emit() proxy.flush() qapp.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 10) assert receiver.counter > 0 def test_signal_proxy_disconnect_slot(qapp): """Test the disconnect of SignalProxy with `signal` and `slot`""" sender = Sender(parent=qapp) receiver = Receiver(parent=qapp) proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6, slot=receiver.slotReceive) assert proxy.blockSignal is False assert proxy is not None assert sender is not None assert receiver is not None assert proxy.slot is not None proxy.disconnect() assert proxy.slot is None assert proxy.blockSignal is True sender.signalSend.emit() proxy.flush() qapp.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 10) assert receiver.counter == 0 def test_signal_proxy_no_slot_start(qapp): """Test the connect mode of SignalProxy without slot at start`""" sender = Sender(parent=qapp) receiver = Receiver(parent=qapp) proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6) assert proxy.blockSignal is True assert proxy is not None assert sender is not None assert receiver is not None sender.signalSend.emit() proxy.flush() qapp.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 10) assert receiver.counter == 0 # Start a connect proxy.connectSlot(receiver.slotReceive) assert proxy.blockSignal is False sender.signalSend.emit() proxy.flush() qapp.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 10) assert receiver.counter > 0 # An additional connect should raise an AssertionError with pytest.raises(AssertionError): proxy.connectSlot(receiver.slotReceive) def test_signal_proxy_slot_block(qapp): """Test the block mode of SignalProxy with `signal` and `slot`""" sender = Sender(parent=qapp) receiver = Receiver(parent=qapp) proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6, slot=receiver.slotReceive) assert proxy.blockSignal is False assert proxy is not None assert sender is not None assert receiver is not None with proxy.block(): sender.signalSend.emit() sender.signalSend.emit() sender.signalSend.emit() proxy.flush() qapp.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 10) assert receiver.counter == 0 sender.signalSend.emit() proxy.flush() qapp.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 10) assert receiver.counter > 0 pyqtgraph-pyqtgraph-0.12.4/tests/test_srttransform3d.py000066400000000000000000000024621421045507400234060ustar00rootroot00000000000000import numpy as np from numpy.testing import assert_almost_equal, assert_array_almost_equal import pyqtgraph as pg from pyqtgraph.Qt import QtGui testPoints = np.array([ [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [-1, -1, 0], [0, -1, -1]]) def testMatrix(): """ SRTTransform3D => Transform3D => SRTTransform3D """ tr = pg.SRTTransform3D() tr.setRotate(45, (0, 0, 1)) tr.setScale(0.2, 0.4, 1) tr.setTranslate(10, 20, 40) assert tr.getRotation() == (45, QtGui.QVector3D(0, 0, 1)) assert tr.getScale() == QtGui.QVector3D(0.2, 0.4, 1) assert tr.getTranslation() == QtGui.QVector3D(10, 20, 40) tr2 = pg.Transform3D(tr) assert np.all(tr.matrix() == tr2.matrix()) # This is the most important test: # The transition from Transform3D to SRTTransform3D is a tricky one. tr3 = pg.SRTTransform3D(tr2) assert_array_almost_equal(tr.matrix(), tr3.matrix()) assert_almost_equal(tr3.getRotation()[0], tr.getRotation()[0]) assert_array_almost_equal(tr3.getRotation()[1], tr.getRotation()[1]) assert_array_almost_equal(tr3.getScale(), tr.getScale()) assert_array_almost_equal(tr3.getTranslation(), tr.getTranslation()) pyqtgraph-pyqtgraph-0.12.4/tests/test_stability.py000066400000000000000000000067221421045507400224220ustar00rootroot00000000000000""" PyQt/PySide stress test: Create lots of random widgets and graphics items, connect them together randomly, the tear them down repeatedly. The purpose of this is to attempt to generate segmentation faults. """ import gc import sys import weakref from random import randint, seed import pyqtgraph as pg from pyqtgraph.Qt import QtTest app = pg.mkQApp() seed(12345) widgetTypes = [ pg.PlotWidget, pg.ImageView, pg.GraphicsView, pg.QtWidgets.QWidget, pg.QtWidgets.QTreeWidget, pg.QtWidgets.QPushButton, ] itemTypes = [ pg.PlotCurveItem, pg.ImageItem, pg.PlotDataItem, pg.ViewBox, pg.QtWidgets.QGraphicsRectItem ] widgets = [] items = [] allWidgets = weakref.WeakKeyDictionary() def crashtest(): global allWidgets try: gc.disable() actions = [ createWidget, #setParent, forgetWidget, showWidget, processEvents, #raiseException, #addReference, ] thread = WorkThread() thread.start() while True: try: action = randItem(actions) action() print('[%d widgets alive, %d zombie]' % (len(allWidgets), len(allWidgets) - len(widgets))) except KeyboardInterrupt: print("Caught interrupt; send another to exit.") try: for _ in range(100): QtTest.QTest.qWait(100) except KeyboardInterrupt: thread.terminate() break except: sys.excepthook(*sys.exc_info()) finally: gc.enable() class WorkThread(pg.QtCore.QThread): '''Intended to give the gc an opportunity to run from a non-gui thread.''' def run(self): i = 0 while True: i += 1 if (i % 1000000) == 0: print('--worker--') def randItem(items): return items[randint(0, len(items)-1)] def p(msg): print(msg) sys.stdout.flush() def createWidget(): p('create widget') global widgets, allWidgets if len(widgets) > 50: return None widget = randItem(widgetTypes)() widget.setWindowTitle(widget.__class__.__name__) widgets.append(widget) allWidgets[widget] = 1 p(" %s" % widget) return widget def setParent(): p('set parent') global widgets if len(widgets) < 2: return child = parent = None while child is parent: child = randItem(widgets) parent = randItem(widgets) p(" %s parent of %s" % (parent, child)) child.setParent(parent) def forgetWidget(): p('forget widget') global widgets if len(widgets) < 1: return widget = randItem(widgets) p(' %s' % widget) widgets.remove(widget) def showWidget(): p('show widget') global widgets if len(widgets) < 1: return widget = randItem(widgets) p(' %s' % widget) widget.show() def processEvents(): p('process events') QtTest.QTest.qWait(25) class TstException(Exception): pass def raiseException(): p('raise exception') raise TstException("A test exception") def addReference(): p('add reference') global widgets if len(widgets) < 1: return obj1 = randItem(widgets) obj2 = randItem(widgets) p(' %s -> %s' % (obj1, obj2)) obj1._testref = obj2 pyqtgraph-pyqtgraph-0.12.4/tests/ui_testing.py000066400000000000000000000053701421045507400215270ustar00rootroot00000000000000import time from pyqtgraph.Qt import QtCore, QtGui, QtTest, QtWidgets def resizeWindow(win, w, h, timeout=2.0): """Resize a window and wait until it has the correct size. This is required for unit testing on some platforms that do not guarantee immediate response from the windowing system. """ QtWidgets.QApplication.processEvents() # Sometimes the window size will switch multiple times before settling # on its final size. Adding qWaitForWindowExposed seems to help with this. QtTest.QTest.qWaitForWindowExposed(win) win.resize(w, h) start = time.time() while True: w1, h1 = win.width(), win.height() if (w,h) == (w1,h1): return QtTest.QTest.qWait(10) if time.time()-start > timeout: raise TimeoutError("Window resize failed (requested %dx%d, got %dx%d)" % (w, h, w1, h1)) # Functions for generating user input events. # We would like to use QTest for this purpose, but it seems to be broken. # See: http://stackoverflow.com/questions/16299779/qt-qgraphicsview-unit-testing-how-to-keep-the-mouse-in-a-pressed-state def mousePress(widget, pos, button, modifier=None): if isinstance(widget, QtWidgets.QGraphicsView): widget = widget.viewport() if modifier is None: modifier = QtCore.Qt.KeyboardModifier.NoModifier event = QtGui.QMouseEvent(QtCore.QEvent.Type.MouseButtonPress, pos, button, QtCore.Qt.MouseButton.NoButton, modifier) QtWidgets.QApplication.sendEvent(widget, event) def mouseRelease(widget, pos, button, modifier=None): if isinstance(widget, QtWidgets.QGraphicsView): widget = widget.viewport() if modifier is None: modifier = QtCore.Qt.KeyboardModifier.NoModifier event = QtGui.QMouseEvent(QtCore.QEvent.Type.MouseButtonRelease, pos, button, QtCore.Qt.MouseButton.NoButton, modifier) QtWidgets.QApplication.sendEvent(widget, event) def mouseMove(widget, pos, buttons=None, modifier=None): if isinstance(widget, QtWidgets.QGraphicsView): widget = widget.viewport() if modifier is None: modifier = QtCore.Qt.KeyboardModifier.NoModifier if buttons is None: buttons = QtCore.Qt.MouseButton.NoButton event = QtGui.QMouseEvent(QtCore.QEvent.Type.MouseMove, pos, QtCore.Qt.MouseButton.NoButton, buttons, modifier) QtWidgets.QApplication.sendEvent(widget, event) def mouseDrag(widget, pos1, pos2, button, modifier=None): mouseMove(widget, pos1) mousePress(widget, pos1, button, modifier) mouseMove(widget, pos2, button, modifier) mouseRelease(widget, pos2, button, modifier) def mouseClick(widget, pos, button, modifier=None): mouseMove(widget, pos) mousePress(widget, pos, button, modifier) mouseRelease(widget, pos, button, modifier) pyqtgraph-pyqtgraph-0.12.4/tests/uictest.ui000066400000000000000000000022001421045507400210070ustar00rootroot00000000000000 Form 0 0 400 300 PyQtGraph 10 10 120 80 10 110 120 80 PlotWidget QWidget
      pyqtgraph
      1
      ImageView QWidget
      pyqtgraph
      1
      pyqtgraph-pyqtgraph-0.12.4/tests/util/000077500000000000000000000000001421045507400177535ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/util/test_lru_cache.py000066400000000000000000000023201421045507400233060ustar00rootroot00000000000000import warnings with warnings.catch_warnings(): warnings.simplefilter('ignore') from pyqtgraph.util.lru_cache import LRUCache def testLRU(): lru = LRUCache(2, 1) # check twice checkLru(lru) checkLru(lru) def checkLru(lru): lru[1] = 1 lru[2] = 2 lru[3] = 3 assert len(lru) == 2 assert set([2, 3]) == set(lru.keys()) assert set([2, 3]) == set(lru.values()) lru[2] = 2 assert set([2, 3]) == set(lru.values()) lru[1] = 1 set([2, 1]) == set(lru.values()) #Iterates from the used in the last access to others based on access time. assert [(2, 2), (1, 1)] == list(lru.items(accessTime=True)) lru[2] = 2 assert [(1, 1), (2, 2)] == list(lru.items(accessTime=True)) del lru[2] assert [(1, 1), ] == list(lru.items(accessTime=True)) lru[2] = 2 assert [(1, 1), (2, 2)] == list(lru.items(accessTime=True)) _ = lru[1] assert [(2, 2), (1, 1)] == list(lru.items(accessTime=True)) _ = lru[2] assert [(1, 1), (2, 2)] == list(lru.items(accessTime=True)) assert lru.get(2) == 2 assert lru.get(3) is None assert [(1, 1), (2, 2)] == list(lru.items(accessTime=True)) lru.clear() assert [] == list(lru.items()) pyqtgraph-pyqtgraph-0.12.4/tests/widgets/000077500000000000000000000000001421045507400204445ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tests/widgets/test_busycursor.py000066400000000000000000000010311421045507400242700ustar00rootroot00000000000000import pyqtgraph as pg pg.mkQApp() def test_nested_busy_cursors_clear_after_all_exit(): with pg.BusyCursor(): wait_cursor = pg.Qt.QtCore.Qt.CursorShape.WaitCursor with pg.BusyCursor(): assert pg.Qt.QtWidgets.QApplication.overrideCursor().shape() == wait_cursor, "Cursor should be waiting" assert pg.Qt.QtWidgets.QApplication.overrideCursor().shape() == wait_cursor, "Cursor should be waiting" assert pg.Qt.QtWidgets.QApplication.overrideCursor() is None, "No override cursor should be set" pyqtgraph-pyqtgraph-0.12.4/tests/widgets/test_combobox.py000066400000000000000000000015531421045507400236710ustar00rootroot00000000000000import pyqtgraph as pg pg.mkQApp() def test_combobox(): cb = pg.ComboBox() items = {'a': 1, 'b': 2, 'c': 3} cb.setItems(items) cb.setValue(2) assert str(cb.currentText()) == 'b' assert cb.value() == 2 # Clear item list; value should be None cb.clear() assert cb.value() is None # Reset item list; value should be set automatically cb.setItems(items) assert cb.value() == 2 # Clear item list; repopulate with same names and new values items = {'a': 4, 'b': 5, 'c': 6} cb.clear() cb.setItems(items) assert cb.value() == 5 # Set list instead of dict cb.setItems(list(items.keys())) assert str(cb.currentText()) == 'b' cb.setValue('c') assert cb.value() == str(cb.currentText()) assert cb.value() == 'c' cb.setItemValue('c', 7) assert cb.value() == 7 pyqtgraph-pyqtgraph-0.12.4/tests/widgets/test_graphics_view.py000066400000000000000000000052731421045507400247160ustar00rootroot00000000000000import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui, QtWidgets app = pg.mkQApp() def test_basics_graphics_view(): view = pg.GraphicsView() background_role = view.backgroundRole() assert background_role == QtGui.QPalette.ColorRole.Window assert view.backgroundBrush().color() == QtGui.QColor(0, 0, 0, 255) assert view.focusPolicy() == QtCore.Qt.FocusPolicy.StrongFocus assert view.transformationAnchor() == QtWidgets.QGraphicsView.ViewportAnchor.NoAnchor minimal_update = QtWidgets.QGraphicsView.ViewportUpdateMode.MinimalViewportUpdate assert view.viewportUpdateMode() == minimal_update assert view.frameShape() == QtWidgets.QFrame.Shape.NoFrame assert view.hasMouseTracking() is True # Default properties # -------------------------------------- assert view.mouseEnabled is False assert view.aspectLocked is False assert view.autoPixelRange is True assert view.scaleCenter is False assert view.clickAccepted is False assert view.centralWidget is not None assert view._background == "default" # Set background color # -------------------------------------- view.setBackground("w") assert view._background == "w" assert view.backgroundBrush().color() == QtCore.Qt.GlobalColor.white # Set anti aliasing # -------------------------------------- aliasing = QtGui.QPainter.RenderHint.Antialiasing # Default is set to `False` assert view.renderHints() & aliasing != aliasing view.setAntialiasing(True) assert view.renderHints() & aliasing == aliasing view.setAntialiasing(False) assert view.renderHints() & aliasing != aliasing # Enable mouse # -------------------------------------- view.enableMouse(True) assert view.mouseEnabled is True assert view.autoPixelRange is False view.enableMouse(False) assert view.mouseEnabled is False assert view.autoPixelRange is True # Add and remove item # -------------------------------------- central_item = QtWidgets.QGraphicsWidget() view.setCentralItem(central_item) assert view.centralWidget is central_item # XXX: Removal of central item is not clear in code scene = view.sceneObj assert isinstance(scene, pg.GraphicsScene) assert central_item in scene.items() item = QtWidgets.QGraphicsWidget() assert item not in scene.items() view.addItem(item) assert item in scene.items() view.removeItem(item) assert item not in scene.items() # Close the graphics view # -------------------------------------- view.close() assert view.centralWidget is None assert view.currentItem is None assert view.sceneObj is None assert view.closed is True pyqtgraph-pyqtgraph-0.12.4/tests/widgets/test_histogramlutwidget.py000066400000000000000000000016241421045507400260060ustar00rootroot00000000000000""" HistogramLUTWidget test: Tests the creation of a HistogramLUTWidget. """ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtWidgets def testHistogramLUTWidget(): pg.mkQApp() win = QtWidgets.QMainWindow() win.show() cw = QtWidgets.QWidget() win.setCentralWidget(cw) l = QtWidgets.QGridLayout() cw.setLayout(l) l.setSpacing(0) v = pg.GraphicsView() vb = pg.ViewBox() vb.setAspectLocked() v.setCentralItem(vb) l.addWidget(v, 0, 0, 3, 1) w = pg.HistogramLUTWidget(background='w') l.addWidget(w, 0, 1) data = pg.gaussianFilter(np.random.normal(size=(256, 256, 3)), (20, 20, 0)) for i in range(32): for j in range(32): data[i*8, j*8] += .1 img = pg.ImageItem(data) vb.addItem(img) vb.autoRange() w.setImageItem(img) QtWidgets.QApplication.processEvents() win.close() pyqtgraph-pyqtgraph-0.12.4/tests/widgets/test_progressdialog.py000066400000000000000000000002241421045507400250770ustar00rootroot00000000000000from pyqtgraph import ProgressDialog, mkQApp mkQApp() def test_progress_dialog(): with ProgressDialog("test", 0, 1) as dlg: dlg += 1 pyqtgraph-pyqtgraph-0.12.4/tests/widgets/test_spinbox.py000066400000000000000000000027231421045507400235430ustar00rootroot00000000000000import pytest import pyqtgraph as pg pg.mkQApp() def test_SpinBox_defaults(): sb = pg.SpinBox() assert sb.opts['decimals'] == 6 assert sb.opts['int'] is False @pytest.mark.parametrize("value,expected_text,opts", [ (0, '0', dict(suffix='', siPrefix=False, dec=False, int=False)), (100, '100', dict()), (1000000, '1e+06', dict()), (1000, '1e+03', dict(decimals=2)), (1000000, '1000000 V', dict(int=True, suffix='V')), (12345678955, '12345678955', dict(int=True, decimals=100)), (1.45e-9, '1.45e-09 A', dict(int=False, decimals=6, suffix='A', siPrefix=False)), (1.45e-9, '1.45 nA', dict(int=False, decimals=6, suffix='A', siPrefix=True)), (1.45, '1.45 PSI', dict(int=False, decimals=6, suffix='PSI', siPrefix=True)), (1.45e-3, '1.45 mPSI', dict(int=False, decimals=6, suffix='PSI', siPrefix=True)), (-2500.3427, '$-2500.34', dict(int=False, format='${value:0.02f}')), (1000, '1 k', dict(siPrefix=True, suffix="")), ]) def test_SpinBox_formatting(value, expected_text, opts): sb = pg.SpinBox(**opts) sb.setValue(value) assert sb.value() == value assert sb.text() == expected_text @pytest.mark.parametrize("suffix", ["", "V"]) def test_SpinBox_gui_set_value(suffix): sb = pg.SpinBox(suffix=suffix) sb.lineEdit().setText('0.1' + suffix) sb.editingFinishedEvent() assert sb.value() == 0.1 sb.lineEdit().setText('0.1 m' + suffix) sb.editingFinishedEvent() assert sb.value() == 0.1e-3 pyqtgraph-pyqtgraph-0.12.4/tests/widgets/test_tablewidget.py000066400000000000000000000074251421045507400243600ustar00rootroot00000000000000from collections import OrderedDict import numpy as np import pyqtgraph as pg app = pg.mkQApp() listOfTuples = [('text_%d' % i, i, i/9.) for i in range(12)] listOfLists = [list(row) for row in listOfTuples] plainArray = np.array(listOfLists, dtype=object) recordArray = np.array(listOfTuples, dtype=[('string', object), ('integer', int), ('floating', float)]) dictOfLists = OrderedDict([(name, list(recordArray[name])) for name in recordArray.dtype.names]) listOfDicts = [OrderedDict([(name, rec[name]) for name in recordArray.dtype.names]) for rec in recordArray] transposed = [[row[col] for row in listOfTuples] for col in range(len(listOfTuples[0]))] def assertTableData(table, data): assert len(data) == table.rowCount() rows = list(range(table.rowCount())) columns = list(range(table.columnCount())) for r in rows: assert len(data[r]) == table.columnCount() row = [] for c in columns: item = table.item(r, c) if item is not None: row.append(item.value) else: row.append(None) assert row == list(data[r]) def test_TableWidget(): w = pg.TableWidget(sortable=False) # Test all input data types w.setData(listOfTuples) assertTableData(w, listOfTuples) w.setData(listOfLists) assertTableData(w, listOfTuples) w.setData(plainArray) assertTableData(w, listOfTuples) w.setData(recordArray) assertTableData(w, listOfTuples) w.setData(dictOfLists) assertTableData(w, transposed) w.appendData(dictOfLists) assertTableData(w, transposed * 2) w.setData(listOfDicts) assertTableData(w, listOfTuples) w.appendData(listOfDicts) assertTableData(w, listOfTuples * 2) # Test sorting w.setData(listOfTuples) w.sortByColumn(0, pg.QtCore.Qt.SortOrder.AscendingOrder) assertTableData(w, sorted(listOfTuples, key=lambda a: a[0])) w.sortByColumn(1, pg.QtCore.Qt.SortOrder.AscendingOrder) assertTableData(w, sorted(listOfTuples, key=lambda a: a[1])) w.sortByColumn(2, pg.QtCore.Qt.SortOrder.AscendingOrder) assertTableData(w, sorted(listOfTuples, key=lambda a: a[2])) w.setSortMode(1, 'text') w.sortByColumn(1, pg.QtCore.Qt.SortOrder.AscendingOrder) assertTableData(w, sorted(listOfTuples, key=lambda a: str(a[1]))) w.setSortMode(1, 'index') w.sortByColumn(1, pg.QtCore.Qt.SortOrder.AscendingOrder) assertTableData(w, listOfTuples) # Test formatting item = w.item(0, 2) assert item.text() == ('%0.3g' % item.value) w.setFormat('%0.6f') assert item.text() == ('%0.6f' % item.value) w.setFormat('X%0.7f', column=2) assert isinstance(item.value, float) assert item.text() == ('X%0.7f' % item.value) # test setting items that do not exist yet w.setFormat('X%0.7f', column=3) # test append uses correct formatting w.appendRow(('x', 10, 7.3)) item = w.item(w.rowCount()-1, 2) assert isinstance(item.value, float) assert item.text() == ('X%0.7f' % item.value) # test reset back to defaults w.setFormat(None, column=2) assert isinstance(item.value, float) assert item.text() == ('%0.6f' % item.value) w.setFormat(None) assert isinstance(item.value, float) assert item.text() == ('%0.3g' % item.value) # test function formatter def fmt(item): if isinstance(item.value, float): return "%d %f" % (item.index, item.value) else: return str(item.value) w.setFormat(fmt) assert isinstance(item.value, float) assert isinstance(item.index, int) assert item.text() == ("%d %f" % (item.index, item.value)) pyqtgraph-pyqtgraph-0.12.4/tools/000077500000000000000000000000001421045507400167745ustar00rootroot00000000000000pyqtgraph-pyqtgraph-0.12.4/tools/generateChangelog.py000066400000000000000000000053161421045507400227550ustar00rootroot00000000000000import re, time, sys def generateDebianChangelog(package, logFile, version, maintainer): """ ------- Convert CHANGELOG format like: pyqtgraph-0.9.1 2012-12-29 - change - change -------- to debian changelog format: python-pyqtgraph (0.9.1-1) UNRELEASED; urgency=low * Initial release. -- Luke Sat, 29 Dec 2012 01:07:23 -0500 *package* is the name of the python package. *logFile* is the CHANGELOG file to read; must have the format described above. *version* will be used to check that the most recent log entry corresponds to the current package version. *maintainer* should be string like "Luke ". """ releases = [] current_version = None current_log = None current_date = None with open(logFile) as file_: for line in file_.readlines(): match = re.match(package+r'-(\d+\.\d+\.\d+(\.\d+)?)\s*(\d+-\d+-\d+)\s*$', line) if match is None: if current_log is not None: current_log.append(line) else: if current_log is not None: releases.append((current_version, current_log, current_date)) current_version, current_date = match.groups()[0], match.groups()[2] #sys.stderr.write("Found release %s\n" % current_version) current_log = [] if releases[0][0] != version: raise Exception("Latest release in changelog (%s) does not match current release (%s)\n" % (releases[0][0], version)) output = [] for release, changes, date in releases: date = time.strptime(date, '%Y-%m-%d') changeset = [ "python-%s (%s-1) UNRELEASED; urgency=low\n" % (package, release), "\n"] + changes + [ " -- %s %s -0%d00\n" % (maintainer, time.strftime('%a, %d %b %Y %H:%M:%S', date), time.timezone/3600), "\n" ] # remove consecutive blank lines except between releases clean = "" lastBlank = True for line in changeset: if line.strip() == '': if lastBlank: continue else: clean += line lastBlank = True else: clean += line lastBlank = False output.append(clean) output.append("") return "\n".join(output) + "\n" if __name__ == '__main__': if len(sys.argv) < 5: sys.stderr.write('Usage: generateChangelog.py package_name log_file version "Maintainer "\n') sys.exit(-1) print(generateDebianChangelog(*sys.argv[1:])) pyqtgraph-pyqtgraph-0.12.4/tools/pg-release.py000066400000000000000000000213731421045507400214000ustar00rootroot00000000000000#!/usr/bin/python import os, sys, argparse, random from shell import shell, ssh description="Build release packages for pyqtgraph." epilog = """ Package build is done in several steps: * Attempt to clone branch release-x.y.z from source-repo * Merge release branch into master * Write new version numbers into the source * Roll over unreleased CHANGELOG entries * Commit and tag new release * Build HTML documentation * Build source package * Build deb packages (if running on Linux) * Build Windows exe installers Release packages may be published by using the --publish flag: * Uploads release files to website * Pushes tagged git commit to github * Uploads source package to pypi Building source packages requires: * * * python-sphinx Building deb packages requires several dependencies: * build-essential * python-all, python3-all * python-stdeb, python3-stdeb Note: building windows .exe files should be possible on any OS. However, Debian/Ubuntu systems do not include the necessary wininst*.exe files; these must be manually copied from the Python source to the distutils/command submodule path (/usr/lib/pythonX.X/distutils/command). Additionally, it may be necessary to rename (or copy / link) wininst-9.0-amd64.exe to wininst-6.0-amd64.exe. """ path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) build_dir = os.path.join(path, 'release-build') pkg_dir = os.path.join(path, 'release-packages') ap = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument('version', help='The x.y.z version to generate release packages for. ' 'There must be a corresponding pyqtgraph-x.y.z branch in the source repository.') ap.add_argument('--publish', metavar='', help='Publish previously built package files (must be stored in pkg-dir/version) and tagged release commit (from build-dir).', action='store_const', const=True, default=False) ap.add_argument('--source-repo', metavar='', help='Repository from which release and master branches will be cloned. Default is the repo containing this script.', default=path) ap.add_argument('--build-dir', metavar='', help='Directory where packages will be staged and built. Default is source_root/release-build.', default=build_dir) ap.add_argument('--pkg-dir', metavar='', help='Directory where packages will be stored. Default is source_root/release-packages.', default=pkg_dir) ap.add_argument('--skip-pip-test', metavar='', help='Skip testing pip install.', action='store_const', const=True, default=False) ap.add_argument('--no-deb', metavar='', help='Skip building Debian packages.', action='store_const', const=True, default=False) ap.add_argument('--no-exe', metavar='', help='Skip building Windows exe installers.', action='store_const', const=True, default=False) def build(args): if os.path.exists(args.build_dir): sys.stderr.write("Please remove the build directory %s before proceeding, or specify a different path with --build-dir.\n" % args.build_dir) sys.exit(-1) if os.path.exists(args.pkg_dir): sys.stderr.write("Please remove the package directory %s before proceeding, or specify a different path with --pkg-dir.\n" % args.pkg_dir) sys.exit(-1) # Clone source repository and tag the release branch shell(''' # Clone and merge release branch into previous master mkdir -p {build_dir} cd {build_dir} rm -rf pyqtgraph git clone --depth 1 --branch master --single-branch {source_repo} pyqtgraph cd pyqtgraph git checkout -b release-{version} git pull {source_repo} release-{version} git checkout master git merge --no-ff --no-commit release-{version} # Write new version number into the source sed -i "s/__version__ = .*/__version__ = '{version}'/" pyqtgraph/__init__.py sed -i "s/version = .*/version = '{version}'/" doc/source/conf.py sed -i "s/release = .*/release = '{version}'/" doc/source/conf.py # make sure changelog mentions unreleased changes grep "pyqtgraph-{version}.*unreleased.*" CHANGELOG sed -i "s/pyqtgraph-{version}.*unreleased.*/pyqtgraph-{version}/" CHANGELOG # Commit and tag new release git commit -a -m "PyQtGraph release {version}" git tag pyqtgraph-{version} # Build HTML documentation cd doc make clean make html cd .. find ./ -name "*.pyc" -delete # package source distribution python setup.py sdist mkdir -p {pkg_dir} cp dist/*.tar.gz {pkg_dir} # source package build complete. '''.format(**args.__dict__)) if args.skip_pip_test: args.pip_test = 'skipped' else: shell(''' # test pip install source distribution rm -rf release-{version}-virtenv virtualenv --system-site-packages release-{version}-virtenv . release-{version}-virtenv/bin/activate echo "PATH: $PATH" echo "ENV: $VIRTUAL_ENV" pip install --no-index --no-deps dist/pyqtgraph-{version}.tar.gz deactivate # pip install test passed '''.format(**args.__dict__)) args.pip_test = 'passed' if 'linux' in sys.platform and not args.no_deb: shell(''' # build deb packages cd {build_dir}/pyqtgraph python setup.py --command-packages=stdeb.command sdist_dsc cd deb_dist/pyqtgraph-{version} sed -i "s/^Depends:.*/Depends: python (>= 2.6), python-qt4 | python-pyside, python-numpy/" debian/control dpkg-buildpackage cd ../../ mv deb_dist {pkg_dir}/pyqtgraph-{version}-deb # deb package build complete. '''.format(**args.__dict__)) args.deb_status = 'built' else: args.deb_status = 'skipped' if not args.no_exe: shell(""" # Build windows executables cd {build_dir}/pyqtgraph python setup.py build bdist_wininst --plat-name=win32 python setup.py build bdist_wininst --plat-name=win-amd64 cp dist/*.exe {pkg_dir} """.format(**args.__dict__)) args.exe_status = 'built' else: args.exe_status = 'skipped' print(unindent(""" ======== Build complete. ========= * Source package: built * Pip install test: {pip_test} * Debian packages: {deb_status} * Windows installers: {exe_status} * Package files in {pkg_dir} Next steps to publish: * Test all packages * Run script again with --publish """).format(**args.__dict__)) def publish(args): if not os.path.isfile(os.path.expanduser('~/.pypirc')): print(unindent(""" Missing ~/.pypirc file. Should look like: ----------------------------------------- [distutils] index-servers = pypi [pypi] username:your_username password:your_password """)) sys.exit(-1) ### Upload everything to server shell(""" cd {build_dir}/pyqtgraph # Uploading documentation.. (disabled; now hosted by readthedocs.io) #rsync -rv doc/build/* pyqtgraph.org:/www/code/pyqtgraph/pyqtgraph/documentation/build/ # Uploading release packages to website rsync -v {pkg_dir} pyqtgraph.org:/www/code/pyqtgraph/downloads/ # Push master to github git push https://github.com/pyqtgraph/pyqtgraph master:master # Push tag to github git push https://github.com/pyqtgraph/pyqtgraph pyqtgraph-{version} # Upload to pypi.. python setup.py sdist upload """.format(**args.__dict__)) print(unindent(""" ======== Upload complete. ========= Next steps to publish: - update website - mailing list announcement - new conda recipe (http://conda.pydata.org/docs/build.html) - contact deb maintainer (gianfranco costamagna) - other package maintainers? """).format(**args.__dict__)) def unindent(msg): ind = 1e6 lines = msg.split('\n') for line in lines: if len(line.strip()) == 0: continue ind = min(ind, len(line) - len(line.lstrip())) return '\n'.join([line[ind:] for line in lines]) if __name__ == '__main__': args = ap.parse_args() args.build_dir = os.path.abspath(args.build_dir) args.pkg_dir = os.path.join(os.path.abspath(args.pkg_dir), args.version) if args.publish: publish(args) else: build(args) pyqtgraph-pyqtgraph-0.12.4/tools/py2exe.bat000066400000000000000000000005121421045507400206760ustar00rootroot00000000000000rem rem This is a simple windows batch file containing the commands needed to package rem a program with pyqtgraph and py2exe. See the packaging tutorial at rem http://luke.campagnola.me/code/pyqtgraph for more information. rem rmdir /S /Q dist rmdir /S /Q build python .\py2exeSetupWindows.py py2exe --includes sip pause pyqtgraph-pyqtgraph-0.12.4/tools/py2exeSetupWindows.py000066400000000000000000000014101421045507400231520ustar00rootroot00000000000000""" Example distutils setup script for packaging a program with pyqtgraph and py2exe. See the packaging tutorial at http://luke.campagnola.me/code/pyqtgraph for more information. """ from distutils.core import setup from glob import glob import py2exe import sys ## This path must contain msvcm90.dll, msvcp90.dll, msvcr90.dll, and Microsoft.VC90.CRT.manifest ## (see http://www.py2exe.org/index.cgi/Tutorial) dllpath = r'C:\Windows\WinSxS\x86_Microsoft.VC90.CRT...' sys.path.append(dllpath) data_files = [ ## Instruct setup to copy the needed DLL files into the build directory ("Microsoft.VC90.CRT", glob(dllpath + r'\*.*')), ] setup( data_files=data_files, windows=['main.py'] , options={"py2exe": {"excludes":["Tkconstants", "Tkinter", "tcl"]}} ) pyqtgraph-pyqtgraph-0.12.4/tools/pyuic5000077500000000000000000000000521421045507400201350ustar00rootroot00000000000000#!/usr/bin/python3 import PyQt5.uic.pyuic pyqtgraph-pyqtgraph-0.12.4/tools/rebuildPtreeRst.py000066400000000000000000000023051421045507400224650ustar00rootroot00000000000000import os.path import textwrap from pyqtgraph.parametertree.Parameter import PARAM_TYPES, _PARAM_ITEM_TYPES def mkDocs(typeList): typeNames = sorted([typ.__name__ for typ in typeList]) typDocs = [ f"""\ .. autoclass:: {name} :members: """ for name in typeNames] indented = '\n'.join(typDocs) # There will be two newlines at the end, so remove one return textwrap.dedent(indented)[:-1] types = set(PARAM_TYPES.values()) items = [typ.itemClass for typ in PARAM_TYPES.values() if typ.itemClass is not None] \ + [item for item in _PARAM_ITEM_TYPES.values()] items = set(items) doc = f"""\ .. 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 ======================== .. currentmodule:: pyqtgraph.parametertree.parameterTypes Parameters ---------- {mkDocs(types)} ParameterItems -------------- {mkDocs(items)} """ here = os.path.dirname(__file__) rstFilename = os.path.join(here, '..', 'doc', 'source', 'parametertree', 'parametertypes.rst') with open(rstFilename, 'w') as ofile: ofile.write(doc) pyqtgraph-pyqtgraph-0.12.4/tools/rebuildUi.py000066400000000000000000000033661421045507400213020ustar00rootroot00000000000000#!/usr/bin/python """ Script for compiling Qt Designer .ui files to .py """ import os, sys, subprocess, tempfile pyqt5uic = 'pyuic5' pyqt6uic = 'pyuic6' pyside2uic = 'pyside2-uic' pyside6uic = 'pyside6-uic' usage = """Compile .ui files to .py for all supported pyqt/pyside versions. Usage: python rebuildUi.py [--force] [.ui files|search paths] May specify a list of .ui files and/or directories to search recursively for .ui files. """ args = sys.argv[1:] if '--force' in args: force = True args.remove('--force') else: force = False if len(args) == 0: print(usage) sys.exit(-1) uifiles = [] for arg in args: if os.path.isfile(arg) and arg.endswith('.ui'): uifiles.append(arg) elif os.path.isdir(arg): # recursively search for ui files in this directory for path, sd, files in os.walk(arg): for f in files: if not f.endswith('.ui'): continue uifiles.append(os.path.join(path, f)) else: print('Argument "%s" is not a directory or .ui file.' % arg) sys.exit(-1) # rebuild all requested ui files for ui in uifiles: base, _ = os.path.splitext(ui) for compiler, ext in [(pyqt5uic, '_pyqt5.py'), (pyside2uic, '_pyside2.py'), (pyqt6uic, '_pyqt6.py'), (pyside6uic, '_pyside6.py')]: py = base + ext if not force and os.path.exists(py) and os.stat(ui).st_mtime <= os.stat(py).st_mtime: print("Skipping %s; already compiled." % py) else: cmd = '%s %s > %s' % (compiler, ui, py) print(cmd) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: os.remove(py) pyqtgraph-pyqtgraph-0.12.4/tools/release_instructions.md000066400000000000000000000021711421045507400235630ustar00rootroot00000000000000PyQtGraph Release Procedure --------------------------- 1. Create a release-x.x.x branch 2. Run pyqtgraph/tools/pg-release.py script (this has only been tested on linux) - creates clone of master - merges release branch into master - updates version numbers in code - creates pyqtgraph-x.x.x tag - creates release commit - builds documentation - builds source package - tests pip install - builds windows .exe installers (note: it may be necessary to manually copy wininst*.exe files from the python source packages) - builds deb package (note: official debian packages are built elsewhere; these locally-built deb packages may be phased out) 3. test build files - test setup.py, pip on OSX - test setup.py, pip, 32/64 exe on windows - test setup.py, pip, deb on linux (py2, py3) 4. Run pg-release.py script again with --publish flag - website upload - github push + release - pip upload 5. publish - update website - mailing list announcement - new conda recipe (http://conda.pydata.org/docs/build.html) - contact various package maintainers pyqtgraph-pyqtgraph-0.12.4/tools/setupHelpers.py000066400000000000000000000500741421045507400220370ustar00rootroot00000000000000from contextlib import suppress import json import os import re import shutil import subprocess import sys from distutils import core from typing import Dict, Any from .generateChangelog import generateDebianChangelog # Maximum allowed repository size difference (in kB) following merge. # This is used to prevent large files from being inappropriately added to # the repository history. MERGE_SIZE_LIMIT = 100 # Paths that are checked for style by flake and flake_diff FLAKE_CHECK_PATHS = ['pyqtgraph', 'examples', 'tools'] # Flake style checks -- mandatory, recommended, optional # See: http://pep8.readthedocs.org/en/1.4.6/intro.html # and https://flake8.readthedocs.org/en/2.0/warnings.html FLAKE_MANDATORY = set([ 'E101', # indentation contains mixed spaces and tabs 'E112', # expected an indented block 'E122', # continuation line missing indentation or outdented 'E125', # continuation line does not distinguish itself from next line 'E133', # closing bracket is missing indentation 'E223', # tab before operator 'E224', # tab after operator 'E242', # tab after β€˜,’ 'E273', # tab after keyword 'E274', # tab before keyword 'E901', # SyntaxError or IndentationError 'E902', # IOError 'W191', # indentation contains tabs 'W601', # .has_key() is deprecated, use β€˜in’ 'W602', # deprecated form of raising exception 'W603', # β€˜<>’ is deprecated, use β€˜!=’ 'W604', # backticks are deprecated, use β€˜repr()’ ]) FLAKE_RECOMMENDED = set([ 'E124', # closing bracket does not match visual indentation 'E231', # missing whitespace after β€˜,’ 'E211', # whitespace before β€˜(β€˜ 'E261', # at least two spaces before inline comment 'E271', # multiple spaces after keyword 'E272', # multiple spaces before keyword 'E304', # blank lines found after function decorator 'F401', # module imported but unused 'F402', # import module from line N shadowed by loop variable 'F403', # β€˜from module import *’ used; unable to detect undefined names 'F404', # future import(s) name after other statements 'E501', # line too long (82 > 79 characters) 'E502', # the backslash is redundant between brackets 'E702', # multiple statements on one line (semicolon) 'E703', # statement ends with a semicolon 'E711', # comparison to None should be β€˜if cond is None:’ 'E712', # comparison to True should be β€˜if cond is True:’ or β€˜if cond:’ 'E721', # do not compare types, use β€˜isinstance()’ 'F811', # redefinition of unused name from line N 'F812', # list comprehension redefines name from line N 'F821', # undefined name name 'F822', # undefined name name in __all__ 'F823', # local variable name ... referenced before assignment 'F831', # duplicate argument name in function definition 'F841', # local variable name is assigned to but never used 'W292', # no newline at end of file ]) FLAKE_OPTIONAL = set([ 'E121', # continuation line indentation is not a multiple of four 'E123', # closing bracket does not match indentation of opening bracket 'E126', # continuation line over-indented for hanging indent 'E127', # continuation line over-indented for visual indent 'E128', # continuation line under-indented for visual indent 'E201', # whitespace after β€˜(β€˜ 'E202', # whitespace before β€˜)’ 'E203', # whitespace before β€˜:’ 'E221', # multiple spaces before operator 'E222', # multiple spaces after operator 'E225', # missing whitespace around operator 'E227', # missing whitespace around bitwise or shift operator 'E226', # missing whitespace around arithmetic operator 'E228', # missing whitespace around modulo operator 'E241', # multiple spaces after β€˜,’ 'E251', # unexpected spaces around keyword / parameter equals 'E262', # inline comment should start with β€˜# β€˜ 'E301', # expected 1 blank line, found 0 'E302', # expected 2 blank lines, found 0 'E303', # too many blank lines (3) 'E401', # multiple imports on one line 'E701', # multiple statements on one line (colon) 'W291', # trailing whitespace 'W293', # blank line contains whitespace 'W391', # blank line at end of file ]) FLAKE_IGNORE = set([ # 111 and 113 are ignored because they appear to be broken. 'E111', # indentation is not a multiple of four 'E113', # unexpected indentation ]) def checkStyle(): """ Run flake8, checking only lines that are modified since the last git commit. """ # First check _all_ code against mandatory error codes print('flake8: check all code against mandatory error set...') errors = ','.join(FLAKE_MANDATORY) cmd = ['flake8', '--select=' + errors] + FLAKE_CHECK_PATHS proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) #ret = proc.wait() output = proc.stdout.read().decode('utf-8') ret = proc.wait() printFlakeOutput(output) # Check for DOS newlines print('check line endings in all files...') count = 0 allowedEndings = set([None, '\n']) for path, dirs, files in os.walk('.'): if path.startswith("." + os.path.sep + ".tox"): continue for f in files: if os.path.splitext(f)[1] not in ('.py', '.rst'): continue filename = os.path.join(path, f) with open(filename, 'U') as fh: _ = fh.readlines() endings = set( fh.newlines if isinstance(fh.newlines, tuple) else (fh.newlines,) ) endings -= allowedEndings if len(endings) > 0: print("\033[0;31m" + "File has invalid line endings: " + "%s" % filename + "\033[0m") ret = ret | 2 count += 1 print('checked line endings in %d files' % count) # Next check new code with optional error codes print('flake8: check new code against recommended error set...') diff = subprocess.check_output(['git', 'diff']) proc = subprocess.Popen(['flake8', '--diff', # '--show-source', '--ignore=' + errors], stdin=subprocess.PIPE, stdout=subprocess.PIPE) proc.stdin.write(diff) proc.stdin.close() output = proc.stdout.read().decode('utf-8') ret |= printFlakeOutput(output) if ret == 0: print('style test passed.') else: print('style test failed: %d' % ret) return ret def printFlakeOutput(text): """ Print flake output, colored by error category. Return 2 if there were any mandatory errors, 1 if only recommended / optional errors, and 0 if only optional errors. """ ret = 0 gotError = False for line in text.split('\n'): m = re.match(r'[^\:]+\:\d+\:\d+\: (\w+) .*', line) if m is None: print(line) else: gotError = True error = m.group(1) if error in FLAKE_MANDATORY: print("\033[0;31m" + line + "\033[0m") ret |= 2 elif error in FLAKE_RECOMMENDED: print("\033[0;33m" + line + "\033[0m") #ret |= 1 elif error in FLAKE_OPTIONAL: print("\033[0;32m" + line + "\033[0m") elif error in FLAKE_IGNORE: continue else: print("\033[0;36m" + line + "\033[0m") if not gotError: print(" [ no errors ]\n") return ret def unitTests(): """ Run all unit tests (using py.test) Return the exit code. """ try: if sys.version[0] == '3': out = subprocess.check_output('PYTHONPATH=. py.test-3', shell=True) else: out = subprocess.check_output('PYTHONPATH=. py.test', shell=True) ret = 0 except Exception as e: out = e.output ret = e.returncode print(out.decode('utf-8')) return ret def checkMergeSize( sourceBranch=None, targetBranch=None, sourceRepo=None, targetRepo=None ): """ Check that a git merge would not increase the repository size by MERGE_SIZE_LIMIT. """ if sourceBranch is None: sourceBranch = getGitBranch() sourceRepo = '..' if targetBranch is None: if sourceBranch == 'master': targetBranch = 'master' targetRepo = 'https://github.com/pyqtgraph/pyqtgraph.git' else: targetBranch = 'master' targetRepo = '..' workingDir = '__merge-test-clone' env = dict(TARGET_BRANCH=targetBranch, SOURCE_BRANCH=sourceBranch, TARGET_REPO=targetRepo, SOURCE_REPO=sourceRepo, WORKING_DIR=workingDir, ) print("Testing merge size difference:\n" " SOURCE: {SOURCE_REPO} {SOURCE_BRANCH}\n" " TARGET: {TARGET_BRANCH} {TARGET_REPO}".format(**env)) setup = """ mkdir {WORKING_DIR} && cd {WORKING_DIR} && git init && git remote add -t {TARGET_BRANCH} target {TARGET_REPO} && git fetch target {TARGET_BRANCH} && git checkout -qf target/{TARGET_BRANCH} && git gc -q --aggressive """.format(**env) checkSize = """ cd {WORKING_DIR} && du -s . | sed -e "s/\t.*//" """.format(**env) merge = """ cd {WORKING_DIR} && git pull -q {SOURCE_REPO} {SOURCE_BRANCH} && git gc -q --aggressive """.format(**env) try: print("Check out target branch:\n" + setup) subprocess.check_call(setup, shell=True) targetSize = int(subprocess.check_output(checkSize, shell=True)) print("TARGET SIZE: %d kB" % targetSize) print("Merge source branch:\n" + merge) subprocess.check_call(merge, shell=True) mergeSize = int(subprocess.check_output(checkSize, shell=True)) print("MERGE SIZE: %d kB" % mergeSize) diff = mergeSize - targetSize if diff <= MERGE_SIZE_LIMIT: print("DIFFERENCE: %d kB [OK]" % diff) return 0 else: print("\033[0;31m" + "DIFFERENCE: %d kB [exceeds %d kB]" % ( diff, MERGE_SIZE_LIMIT) + "\033[0m") return 2 finally: if os.path.isdir(workingDir): shutil.rmtree(workingDir) def mergeTests(): ret = checkMergeSize() ret |= unitTests() ret |= checkStyle() if ret == 0: print("\033[0;32m" + "\nAll merge tests passed." + "\033[0m") else: print("\033[0;31m" + "\nMerge tests failed." + "\033[0m") return ret def getInitVersion(pkgroot): """Return the version string defined in __init__.py""" path = os.getcwd() initfile = os.path.join(path, pkgroot, '__init__.py') init = open(initfile).read() m = re.search(r'__version__ = (\S+)\n', init) if m is None or len(m.groups()) != 1: raise Exception("Cannot determine __version__ from init file: " + "'%s'!" % initfile) version = m.group(1).strip('\'\"') return version def gitCommit(name): """Return the commit ID for the given name.""" commit = subprocess.check_output( ['git', 'show', name], universal_newlines=True).split('\n')[0] assert commit[:7] == 'commit ' return commit[7:] def getGitVersion(tagPrefix): """Return a version string with information about this git checkout. If the checkout is an unmodified, tagged commit, then return the tag version If this is not a tagged commit, return the output of ``git describe --tags`` If this checkout has been modified, append "+" to the version. """ path = os.getcwd() if not os.path.isdir(os.path.join(path, '.git')): return None try: v = ( subprocess.check_output( ["git", "describe", "--tags", "--dirty", '--match="%s*"' % tagPrefix], stderr=subprocess.DEVNULL) .strip() .decode("utf-8") ) except (FileNotFoundError, subprocess.CalledProcessError): return None # chop off prefix assert v.startswith(tagPrefix) v = v[len(tagPrefix):] # split up version parts parts = v.split('-') # has working tree been modified? modified = False if parts[-1] == 'dirty': modified = True parts = parts[:-1] # have commits been added on top of last tagged version? # (git describe adds -NNN-gXXXXXXX if this is the case) local = None if (len(parts) > 2 and re.match(r'\d+', parts[-2]) and re.match(r'g[0-9a-f]{7}', parts[-1])): local = parts[-1] parts = parts[:-2] gitVersion = '-'.join(parts) if local is not None: gitVersion += '+' + local if modified: gitVersion += 'm' return gitVersion def getGitBranch(): m = re.search( r'\* (.*)', subprocess.check_output(['git', 'branch'], universal_newlines=True)) if m is None: return '' else: return m.group(1) def getVersionStrings(pkg): """ Returns 4 version strings: * the version string to use for this build, * version string requested with --force-version (or None) * version string that describes the current git checkout (or None). * version string in the pkg/__init__.py, The first return value is (forceVersion or gitVersion or initVersion). """ ## Determine current version string from __init__.py initVersion = getInitVersion(pkgroot=pkg) # If this is a git checkout # try to generate a more descriptive version string try: gitVersion = getGitVersion(tagPrefix=pkg+'-') except: gitVersion = None sys.stderr.write("This appears to be a git checkout, but an error " "occurred while attempting to determine a version " "string for the current commit.\n") sys.excepthook(*sys.exc_info()) # See whether a --force-version flag was given forcedVersion = None for i, arg in enumerate(sys.argv): if arg.startswith('--force-version'): if arg == '--force-version': forcedVersion = sys.argv[i+1] sys.argv.pop(i) sys.argv.pop(i) elif arg.startswith('--force-version='): forcedVersion = sys.argv[i].replace('--force-version=', '') sys.argv.pop(i) ## Finally decide on a version string to use: if forcedVersion is not None: version = forcedVersion else: version = initVersion # if git says this is a modified branch, add local version information if gitVersion is not None: _, _, local = gitVersion.partition('+') if local != '': version = version + '+' + local sys.stderr.write("Detected git commit; " + "will use version string: '%s'\n" % version) return version, forcedVersion, gitVersion, initVersion DEFAULT_ASV: Dict[str, Any] = { "version": 1, "project": "pyqtgraph", "project_url": "http://pyqtgraph.org/", "repo": ".", "branches": ["master"], "environment_type": "virtualenv", "show_commit_url": "http://github.com/pyqtgraph/pyqtgraph/commit/", # "pythons": ["3.7", "3.8", "3.9"], "matrix": { # "numpy": ["1.17", "1.18", "1.19", ""], "numpy": "", "pyqt5": ["", None], "pyside2": ["", None], }, "exclude": [ {"pyqt5": "", "pyside2": ""}, {"pyqt5": None, "pyside2": None} ], "benchmark_dir": "benchmarks", "env_dir": ".asv/env", "results_dir": ".asv/results", "html_dir": ".asv/html", "build_cache_size": 5 } class ASVConfigCommand(core.Command): description = "Setup the ASV benchmarking config for this system" user_options = [] def initialize_options(self) -> None: pass def finalize_options(self) -> None: pass def run(self) -> None: config = DEFAULT_ASV with suppress(FileNotFoundError, subprocess.CalledProcessError): cuda_check = subprocess.check_output(["nvcc", "--version"]) match = re.search(r"release (\d{1,2}\.\d)", cuda_check.decode("utf-8")) ver = match.groups()[0] # e.g. 11.0 ver_str = ver.replace(".", "") # e.g. 110 config["matrix"][f"cupy-cuda{ver_str}"] = "" with open("asv.conf.json", "w") as conf_file: conf_file.write(json.dumps(config, indent=2)) class DebCommand(core.Command): description = "build .deb package using `debuild -us -uc`" maintainer = "Luke Campagnola " debTemplate = "debian" debDir = "deb_build" user_options = [] def initialize_options(self): self.cwd = None def finalize_options(self): self.cwd = os.getcwd() def run(self): version = self.distribution.get_version() pkgName = self.distribution.get_name() debName = "python-" + pkgName debDir = self.debDir assert os.getcwd() == self.cwd, 'Must be in package root: %s' % self.cwd if os.path.isdir(debDir): raise Exception('DEB build dir already exists: "%s"' % debDir) sdist = "dist/%s-%s.tar.gz" % (pkgName, version) if not os.path.isfile(sdist): raise Exception("No source distribution; " + "run `setup.py sdist` first.") # copy sdist to build directory and extract os.mkdir(debDir) renamedSdist = '%s_%s.orig.tar.gz' % (debName, version) print("copy %s => %s" % (sdist, os.path.join(debDir, renamedSdist))) shutil.copy(sdist, os.path.join(debDir, renamedSdist)) print("cd %s; tar -xzf %s" % (debDir, renamedSdist)) if os.system("cd %s; tar -xzf %s" % (debDir, renamedSdist)) != 0: raise Exception("Error extracting source distribution.") buildDir = '%s/%s-%s' % (debDir, pkgName, version) # copy debian control structure print("copytree %s => %s" % (self.debTemplate, buildDir+'/debian')) shutil.copytree(self.debTemplate, buildDir+'/debian') # Write new changelog chlog = generateDebianChangelog( pkgName, 'CHANGELOG', version, self.maintainer) print("write changelog %s" % buildDir+'/debian/changelog') open(buildDir+'/debian/changelog', 'w').write(chlog) # build package print('cd %s; debuild -us -uc' % buildDir) if os.system('cd %s; debuild -us -uc' % buildDir) != 0: raise Exception("Error during debuild.") class DebugCommand(core.Command): """Just for learning about distutils.""" description = "" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): global cmd cmd = self print(self.distribution.name) print(self.distribution.version) class TestCommand(core.Command): description = "Run all package tests and exit immediately with ", \ "informative return code." user_options = [] def run(self): sys.exit(unitTests()) def initialize_options(self): pass def finalize_options(self): pass class StyleCommand(core.Command): description = "Check all code for style, exit immediately with ", \ "informative return code." user_options = [] def run(self): sys.exit(checkStyle()) def initialize_options(self): pass def finalize_options(self): pass class MergeTestCommand(core.Command): description = "Run all tests needed to determine whether the current ",\ "code is suitable for merge." user_options = [] def run(self): sys.exit(mergeTests()) def initialize_options(self): pass def finalize_options(self): pass pyqtgraph-pyqtgraph-0.12.4/tools/shell.py000066400000000000000000000021061421045507400204540ustar00rootroot00000000000000import os, sys import subprocess as sp def shell(cmd): """Run each line of a shell script; raise an exception if any line returns a nonzero value. """ pin, pout = os.pipe() proc = sp.Popen('/bin/bash', stdin=sp.PIPE) for line in cmd.split('\n'): line = line.strip() if line.startswith('#'): print('\033[33m> ' + line + '\033[0m') else: print('\033[32m> ' + line + '\033[0m') if line.startswith('cd '): os.chdir(line[3:]) proc.stdin.write((line + '\n').encode('utf-8')) proc.stdin.write(('echo $? 1>&%d\n' % pout).encode('utf-8')) ret = "" while not ret.endswith('\n'): ret += os.read(pin, 1) ret = int(ret.strip()) if ret != 0: print("\033[31mLast command returned %d; bailing out.\033[0m" % ret) sys.exit(-1) proc.stdin.close() proc.wait() def ssh(host, cmd): """Run commands on a remote host by ssh. """ proc = sp.Popen(['ssh', host], stdin=sp.PIPE) proc.stdin.write(cmd) proc.wait() pyqtgraph-pyqtgraph-0.12.4/tox.ini000066400000000000000000000013211421045507400171440ustar00rootroot00000000000000[tox] envlist = ; qt 5.15.x py{37,38,39}-{pyqt5,pyside2}_515 ; qt 5.12.x py{37}-{pyqt5,pyside2}_512 ; py38-pyside2_512 doesn't work due to PYSIDE-1140 py38-pyqt5_512 ; qt 6 py{37,38,39}-{pyqt6,pyside6} [base] deps = pytest pytest-xdist numpy scipy pyopengl h5py [testenv] passenv = DISPLAY XAUTHORITY, PYTHON_VERSION setenv = PYTHONWARNINGS=ignore:DEPRECATION::pip._internal.cli.base_command deps= {[base]deps} pyside2_512: pyside2==5.12.6 pyqt5_512: pyqt5==5.12.3 pyside2_515: pyside2 pyqt5_515: pyqt5 pyqt6: pyqt6 pyside6: pyside6 commands= python -c "import pyqtgraph as pg; pg.systemInfo()" pytest -n auto {posargs:}