Compare commits

...

153 Commits

Author SHA1 Message Date
Kyle Sunden 6fa4a0a3eb
Merge pull request #2012 from j9ac9k/prep-0.12.3-release
Prep for 0.12.3 Release
2021-10-10 22:01:10 -05:00
Kyle Sunden 0f3804e32c
Merge pull request #2013 from ksunden/rel_action
Add python publishing workflow
2021-10-10 22:00:18 -05:00
Ogi Moore af8c766de3 Update changelog 2021-10-09 15:28:28 -07:00
Ogi Moore 58325ee91b
Merge pull request #2014 from j9ac9k/fix-some-imports
Fix Some Import Issues
2021-10-09 15:26:44 -07:00
Ogi Moore 936108ec60 Import Dock class not module 2021-10-09 15:06:51 -07:00
Ogi Moore 9b67e51230 Switch to relative imports for some parameterTypes 2021-10-09 15:06:51 -07:00
Ogi Moore db74522003 Have parameter types imports use non __init__
When using isort, the order of parametertree/__init__.py imports broke many
of the parameter-types imports.  This commit modifies the import statement to
not use the imports from __init__.py but import the respective classes from the
the non-__init__ modules
2021-10-09 15:06:51 -07:00
Kyle Sunden 9ae38e6d21
Update .github/workflows/python-publish.yml 2021-10-09 13:04:01 -05:00
Kyle Sunden 34c6981b0f Update version number in __init__ 2021-10-09 00:37:49 -05:00
Ogi Moore b8ca314597 Update changelog for 0.12.3 2021-10-08 22:27:35 -07:00
Kyle Sunden ae484ba411 Add python publishing workflow 2021-10-09 00:26:29 -05:00
Ogi Moore fd6985565c
Merge pull request #2009 from pijyoi/fix_imageview_roi
unhide ImageView roi curves
2021-10-08 21:05:08 -07:00
KIU Shueng Chuan 2da769d6a5 unhide ImageView roi curves 2021-10-08 22:01:41 +08:00
Ogi Moore fb809e5260
Merge pull request #2007 from pijyoi/skip_missing_scipy
skip tests using scipy if not installed
2021-10-07 21:22:24 -07:00
KIU Shueng Chuan 4aeb0ce1dd skip tests using scipy if not installed 2021-10-08 11:52:52 +08:00
Martin Chase a6bb2c6edd
Allow ThreadTrace to save to a file (#1998)
* allow ThreadTrace to save to a file

* make sure we close our output file

* fix the egregious errors in the file
 - no mutable default args
 - import things before using them
 - variable named obj did not exist, but try/except ignored it

* if it's dumb, and it works, it's not dumb

* linux requires a flush; remove commented code

* always close the file handle, even if its stdout

* it may work for us, but just in case, protect with finally

* turns out: lots of things like to use stdout!

Co-authored-by: Luke Campagnola <lukec@alleninstitute.org>
Co-authored-by: Ogi Moore <ognyan.moore@gmail.com>
2021-10-07 12:25:38 -07:00
Ogi Moore e8b77a11e5
Merge pull request #2006 from j9ac9k/revise-reload-skip-condition
Remove python 3.10 skip condition for test_reload
2021-10-06 23:43:05 -07:00
Ogi Moore e04ecdf554 Remove python 3.10 skip condition for test_reload 2021-10-06 23:01:40 -07:00
Nils Nemitz 0cc3580687
Traditional log10 mode for PlotDataItem (by adding "mapped data" stage) (#1992)
* make PlotDataItem aware of mapped data

* inf suppression, metadata storage and refactor of data structures

* cleanup, test, and documentation pass

* re-added prepareForPaint, added PlotDataset to sphinx index

* strip more print statements

* indicate (internal) PlotDataset documentation as orphaned to avoid sphinx error

* Do not export PlotDataset

* replacement example

* example comments
2021-10-06 22:40:38 -07:00
Ogi Moore bc542ae1c4
Merge pull request #2003 from j9ac9k/fix-py310-drawText-call
Fix Python 3.10 GLGradientLegendItem call to QPainter.drawText
2021-10-06 06:35:12 -07:00
Ogi Moore 5917e5e666 QPainter.drawText needs QPointF not x, y 2021-10-05 20:47:15 -07:00
Ogi Moore 95c302fd0d
Merge pull request #1999 from pijyoi/mpl_qt6
Re-enable Matplotlib Exporter Test for Qt6
2021-09-29 09:54:37 -07:00
KIU Shueng Chuan 4b59734629 enable matplotlib exporter test for qt6 2021-09-24 19:47:20 +08:00
ntjess 8d3e6cbd22
Better parameter tree testing (#1953)
* Allows values to be numpy arrays

* Bugfix: Slider now works when limits didn't change during `optsChanged`

* Improved testing + layout of param tree example

* Also fix numpy-like values in list `setValue`

* use proper hex formatting for  value

* Fix code warnings

* Avoids use of configfile in parametertree

* Avoid shadowing variable names

* Add explanatory comment to `makeAllParamTypes`

* Allow string options to be 'unset' in file, etc. parameters example

* Bugfix: unintunitive option unsetting for file window title

* don't use lambda in signal connect

* Remove unused import
2021-09-23 21:06:00 -07:00
Ogi Moore e9d3b6ddd2
Merge pull request #1995 from Achilles1515/colorbutton-padding
Change hardcoded padding into initialization parameter
2021-09-23 17:19:08 -07:00
Dane Owens 1132c138a4 Change hardcoded padding into initialization parameter 2021-09-17 09:51:26 -04:00
Ogi Moore b075035b0d
Merge pull request #1987 from jkotan/regiontypo
Issue 1986: fix for autoLevel of RGB histograms
2021-09-08 11:22:37 -07:00
Jan Kotanski 4334cd14de fix for autoLevel of RGB histograms 2021-09-08 09:20:12 +02:00
Ogi Moore 126b7e0925
Merge pull request #1985 from pijyoi/workaround_float_lut
delegate float luts to makeARGB()
2021-09-07 21:48:15 -07:00
magnium 6f4b6b1b88
Fix ImageView levelMode (#1974)
* fixes #1769

* Automatically set image to ImageView if imageItem is provided.
Added a test for correct initialization of ImageView with imageItem and levelMode.
2021-09-07 21:27:26 -07:00
ntjess babd037cf7
Adds checklist parameter (#1952)
* Alphabetize parameter registration

* Implements a checklist parameter

* Several checklist updates
- Correct indentation
- Allow dict-like setting + non-string values
- Fix several bugs associated with 'exclusive' behavior

* Add 'checklist' to param tree example

* Add documentation

* Removes unneeded code

* Forgot to rebuilt RST doc

* `exclusive` checklist uses radio buttons

* better checklist change logic

Co-authored-by: Ogi Moore <ognyan.moore@gmail.com>
2021-09-07 21:25:17 -07:00
Petras Jokubauskas 745b04baa5
Fix: updateDecimate should not unhide intentionaly hidden curves (#1973)
* Update PlotItem.py

make update decimate to not unhide curves when items added/removed,
while preserving the Max Traces well behaviour

* Update PlotItem.py

fix typo

* Update PlotItem.py

fix: typo with self as argument

* give better name for the function which handles MaxTraces checkstate change

rename it to _handle_max_traces_toggle

* add doc string to updateDecimation

* add test for PlotItem for external curve visibility control

check if hidden curve would stay hidden when adding or removing other items.

* remove additional empty line between methods
2021-09-07 21:19:22 -07:00
Ogi Moore 2a66460afc
Merge pull request #1954 from ntjess/deprecate-values-opt
Deprecate `values` opt for list parameter
2021-09-07 20:57:27 -07:00
Ogi Moore 446a2c324c
Merge pull request #1721 from j9ac9k/test-pr807
Implement a GLGraphItem according to #807
2021-09-07 20:47:19 -07:00
Ogi Moore d54da71138 Add docs page 2021-09-07 20:28:57 -07:00
Ogi Moore 13ab4de209 Add really basic GLGraphItem example 2021-09-07 20:28:57 -07:00
Ogi Moore c3dba791d1 Make equivalent to #807 2021-09-07 20:28:57 -07:00
KIU Shueng Chuan b5475cad6b warn about usage of non-uint8 LUTs 2021-09-08 09:52:46 +08:00
KIU Shueng Chuan 6763b14fb9 delegate float luts to makeARGB() 2021-09-07 00:04:42 +08:00
Ogi Moore 98d7bcf560
Merge pull request #1969 from pijyoi/fix_clip_nop 2021-08-17 07:29:10 -07:00
KIU Shueng Chuan 6f1ea29fe4 bugfix: result of clip_array() not assigned 2021-08-17 22:14:13 +08:00
Ogi Moore 175f84ecbc
Merge pull request #1968 from pijyoi/imageview_scale 2021-08-14 07:37:51 -07:00
KIU Shueng Chuan 11b4a1bb47 fix: ImageView calling deprecated QGraphicsItem.scale() 2021-08-14 14:05:18 +08:00
Ogi Moore e752336b55
Merge pull request #1965 from pijyoi/qpath_chunks
perform arrayToQPath in chunks
2021-08-13 10:37:14 -07:00
KIU Shueng Chuan 0cd926ff8e merge finiteCheck branches 2021-08-14 00:00:59 +08:00
KIU Shueng Chuan e541c9ba1e hoist duplicate code 2021-08-13 18:53:58 +08:00
KIU Shueng Chuan 44e32f00c7 _fill_nonfinite() -> _compute_backfill_indices()
this allows us to defer the backfilling
2021-08-11 21:55:54 +08:00
KIU Shueng Chuan 679c1002c1 break up arrayToQPath
this restructuring removes inter-dependencies between the various modes.
this makes it easier to reason about and modify the various codepaths.
2021-08-11 06:55:21 +08:00
KIU Shueng Chuan 7e31a5e4ee setup codepath for connect=='all' and all-finite
"all-finite" includes the case where user requests to skip finiteCheck.
2021-08-10 11:57:19 +08:00
KIU Shueng Chuan fa826bd4cb add benchmarks/arrayToQPath.py 2021-08-10 11:57:19 +08:00
Ogi Moore 0d8eba36a6
Merge pull request #1957 from j9ac9k/bump-numpy-per-nep29
Bump minimum numpy up per NEP-29 schedule
2021-08-07 10:13:49 -07:00
Ogi Moore 690a416d5d Bump minimum numpy up per NEP-29 schedule 2021-08-07 09:37:19 -07:00
Ogi Moore a537bc2b23
Merge pull request #1956 from pijyoi/zerocopy_qba
arrayToQPath: use QByteArray as backing store
2021-08-06 22:22:09 -07:00
KIU Shueng Chuan 287ec9fbea arrayToQPath: use QByteArray as backing store
this avoids:
1) allocation and zero-ing of bytearray
2) copy of bytearray to QByteArray
2021-08-07 08:56:11 +08:00
Nathan Jessurun 503507ba50 Don't specify a version for deprecation 2021-08-06 08:04:51 -04:00
Nathan Jessurun 3d15ca1038 Catch remaining 'values' usages 2021-08-06 08:04:51 -04:00
Nathan Jessurun 6f44d27f2d Deprecate `values` opt for list parameter 2021-08-05 15:48:09 -04:00
Ogi Moore f16125c378
Merge pull request #1951 from timgates42/bugfix_typos
docs: Fix a few typos
2021-08-04 22:01:50 -07:00
Tim Gates a2078f8a87
docs: Fix a few typos
There are small typos in:
- doc/source/how_to_use.rst
- doc/source/region_of_interest.rst
- examples/ViewBox.py
- pyqtgraph/flowchart/Node.py
- pyqtgraph/graphicsItems/AxisItem.py
- pyqtgraph/graphicsItems/PColorMeshItem.py
- pyqtgraph/graphicsItems/PlotDataItem.py
- pyqtgraph/graphicsItems/TargetItem.py
- pyqtgraph/graphicsItems/ViewBox/ViewBox.py
- pyqtgraph/widgets/RawImageWidget.py

Fixes:
- Should read `mapped` rather than `maped`.
- Should read `vector` rather than `vetctor`.
- Should read `value` rather than `vaule`.
- Should read `preferable` rather than `preferrable`.
- Should read `output` rather than `ouptut`.
- Should read `information` rather than `inforation`.
- Should read `information` rather than `infomation`.
- Should read `exempt` rather than `excempt`.
- Should read `emphasizing` rather than `emphacizing`.
- Should read `construction` rather than `constrution`.
2021-08-05 06:47:08 +10:00
Ogi Moore 239e15a6f8
Merge pull request #1944 from pijyoi/getset_color_nih
use more of QColor functions / methods
2021-08-03 22:53:53 -07:00
KIU Shueng Chuan 178e693a4d add SVG named color support 2021-08-04 09:19:46 +08:00
KIU Shueng Chuan 371facb17f SVGExporter.py : fix background color
1) blue and green were swapped
2) %f used for integer rgb components
3) %d used for float opacity component
2021-08-04 09:19:46 +08:00
KIU Shueng Chuan 482b92d700 GLGradientLegendItem: remove opengl code
change fontColor to take a mkColor() argument

use hyperlink when referring to other functions
2021-08-04 09:19:46 +08:00
KIU Shueng Chuan efa662415e use QColor methods and functions 2021-08-04 09:19:34 +08:00
ntjess 4bf1866c2a
Organize paramtypes (#1919)
* Registered parameter types go in their own files

* Moves [int, float] item definitions outside `WidgetParameterItem`

* Moves [int, float] parameter definitions outside `WidgetParameterItem`

* Allow registering ParameterItems for easy parameter defs

* Finalizes moving simple parameters to their own files

* removes accidentally committed file

* Provides class qualnames in rst

* Address docstring build issues

* Address recent review comments
- `registerParameterItemType`:
  * added to docs and parametertree.__init__
  * Remove unsed PARAM_TYPES global
  * Hyperlink to `registerParameterType`
- parameter tree rst:
  * Alphabetize entries
  * Rebuild RST without fully qualified class name
  * Add note at file header that it is auto generated

* Remove spurious space during rst doc creation

* Ensure created/modified files end with newline

* Address CodeQL warnings

* toPlainText also returns str

* `QTreeWidgetItem.text` returns str
2021-08-02 10:47:55 -07:00
Ogi Moore fb2e684f45
Merge pull request #1943 from ntjess/rm-py2-codepaths
Remove python 2 code paths
2021-08-02 10:19:29 -07:00
ntjess e18d1fb1f2
arrayToQPath can handle empty paths (#1920)
* Fixes #1888

* Improve test coverage of arrayToQPath

* Use early exit to solve empty path instead of slice manipulation

* address codeql qualms: Unused import, uneven tuple
2021-08-02 10:18:25 -07:00
Ogi Moore d0961cc320
Merge pull request #1942 from ntjess/add-newlines
Adds EOF newline to files missing it
2021-08-02 09:09:27 -07:00
njessurun c0eb3267d2 Remove python 2 code paths 2021-08-02 12:07:25 -04:00
Ogi Moore b0bcec1ff2
Merge pull request #1941 from ntjess/more-rm-py2_3
Remove unnecessary casting of `toPlainText` to str
2021-08-02 09:01:22 -07:00
njessurun ef99d3fbf6 Adds EOF newline to files missing it.
Note: intentionally avoids files moved / already adjusted from #1919
2021-08-02 11:51:28 -04:00
Nathan Jessurun 84b491fb9b toPlainText also returns str
Note: intentionally leaves out instance in parametertypes.py to avoid merge conflict with #1919
2021-08-02 11:23:04 -04:00
Kyle Sunden a472f8c5de
Remove all usage of python2_3.py (#1939)
* Remove all usage of python2_3.py

Technically these functions were exported at the top level of the library, this removes them without warning... If we want to we can bring them back for there, but I honestly don't think its needed, as we are py3 only now and have been for multiple releases.

This may introduce a number of 'useless cast' or similar but those were always happening anyway

This PR brought to you by sed

* Update varname in hdf example to avoid collision with builtin

* Clean up some leftover comments surrounding imports of compat code

* Unnecessary string casts

* Additional unnecessary casts

* syntax error fix

* more unnecessary casts

* Yet more unnecessary casts
2021-08-01 21:43:32 -07:00
Ogi Moore 1ddbfc8321
Merge pull request #1940 from pijyoi/fix_logmode
fix log mode by reverting to previous formulation
2021-07-31 20:50:36 -07:00
KIU Shueng Chuan aebc66dcaa fix log mode by reverting to previous formulation
this formulation is in the documentation for setLogMode()
2021-08-01 11:11:41 +08:00
Kyle Sunden 6a59b7e5b5
Many unused import cleanups (#1935)
* Many unused import cleanups

Ignored some star imports, some vendored code in colorama, only looked within pyqtgraph the library, not e.g. examples

* SpinBox decimal imported with both import and from import
2021-07-31 07:35:23 -07:00
Ogi Moore 2d90e54441
Merge pull request #1936 from pijyoi/empty_qpolygonf
Handle empty QPolygonF
2021-07-31 07:30:20 -07:00
KIU Shueng Chuan 04673ac98b rename: qimage_to_ndarray -> ndarray_from_qimage 2021-07-31 21:10:34 +08:00
KIU Shueng Chuan f64290be9e test that binding provides a write-thru QImage 2021-07-31 21:00:54 +08:00
KIU Shueng Chuan 9355ecf469 support padded QImage 2021-07-31 20:58:51 +08:00
KIU Shueng Chuan 75654b8495 handle zero-sized QPolygonF
depending on the implementation, a zero-sized QPolygonF may not
have any underlying buffer allocated and may return a null pointer when
queried for its "data()"

this null pointer is returned to Python as a "None" which breaks code
not expecting it.
2021-07-31 17:25:03 +08:00
KIU Shueng Chuan 9913f7c1e7 import shiboken{2,6} as shiboken 2021-07-31 15:58:56 +08:00
Ogi Moore 7aa5d0cbf4
Merge pull request #1910 from pijyoi/paramtree_plotspeed
PlotSpeedTest: add param tree control panel
2021-07-30 22:51:36 -07:00
KIU Shueng Chuan 643dd87800 add option to allow useOpenGL toggle 2021-07-31 13:01:40 +08:00
Ogi Moore fe10b5e8dc
Merge pull request #1711 from j9ac9k/test-feature-merge
Add GLGradientLegendItem (Reimplement #663)
2021-07-30 20:37:29 -07:00
Ogi Moore 0cfc9cd440 Add GLGradientLegendItem to pyqtgraph
Huge thank you to @feketeimre for the initial PR for this feature
Thank you to @pijyoi for re-implementing with no changes needed to
GLViewWidget and supporting QOpenGLWidget vs. QGLWidget.
2021-07-30 20:19:27 -07:00
Ogi Moore d3755520d0
Merge pull request #1892 from pijyoi/glpainteritem 2021-07-30 18:23:51 -07:00
KIU Shueng Chuan 0afd8adcd8 add Big Sur conditional examples from utils.py 2021-07-31 08:28:45 +08:00
Martin Schulz 96cccd96ec
make antialiasing optional for paintGL in PlotCurveItem (#1932)
* make antialiasing optional for paintGL in PlotCurveItem

* reversion to standard values if aa is disabled

* remove unnecessary lines
2021-07-30 12:19:02 -07:00
Nils Nemitz 5084d9b537
Reduce ColorMap inefficiencies (#1927)
* Avoid regenerating QColor lists, speed up generation where unavoidable

* whitespace and typo reduction
2021-07-29 20:50:10 -07:00
Martin Chase eb8965c2f4
Restore previous signature on TargetItem.setPos() (#1928)
* Restore previous signature on TargetItem.setPos()

* star means something to sphinx

Co-authored-by: Luke Campagnola <luke.campagnola@gmail.com>
2021-07-27 18:04:24 -07:00
KIU Shueng Chuan 1a34c8857f PlotSpeedTest: add param tree control panel
inspired by ScatterPlotSpeedTest.
2021-07-24 07:41:25 +08:00
Nils Nemitz 7009084e4c
more readable names for color map references in ColorBarItem API (#1908)
* more readable names for color map references

* changed example code

* activated deprecation warnings

* added accessor methods for cmap/colorMap
2021-07-23 14:45:57 -07:00
ntjess 81823768c0
feature More parameter item types (#1844)
* feature More parameter item types

Pen: Pops up a dialouge that allows the user to customize a pen. Setting pen value is not working yet.
Progress bar: For indication things.
Slider: Easier way to set values that dont require precision.
Fonts: Picking font types. Next thing could be a Font dialog.
Calendar: For picking dates or intervals
Open/save file/files/directory: Pops up an open/save file/directory dialog to select a file/directory. Filter string and caption can be defined too.

A PenSelectorDialog widget was created for the pen parameter item too.

Also added these parameter items to the example.

* PyQt/Side6 compatibility fixup

* Revisions from intial PR

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/widgets/PenSelectorDialog.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/widgets/PenSelectorDialogbox.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/widgets/PenSelectorDialogbox.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/widgets/PenSelectorDialogbox.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/parametertree/parameterTypes.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/parametertree/parameterTypes.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/widgets/PenSelectorDialog.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/widgets/PenSelectorDialogbox.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/widgets/PenSelectorDialogbox.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/widgets/PenSelectorDialogbox.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Update pyqtgraph/widgets/PenSelectorDialogbox.py

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

Apply suggestions from code review

Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>

* Bugfix: module instead of class import on param tree example

* Enrich the slider parameter

* Address pijyoi comments on pen style parameter

* Different file picker for easier porting

* Better organization and formatting, minor refactoring

* PyQt6/PySide6 fixup for file dialog

* Minor adjustment to file picker

* Bugfix: for 'None' sigChanged

'None' is explicitly allowed for a WidgetParameterItem's `sigChanged` value. However, this raises an error on a changed value unless the commit's fix is applied

* Calendar works better as sub item

* Fixes bugs in pen parameter's dialog + makes it resizable

* more bugfixes and recommended changes, lets pen serialize its options

* better pen save state

* Fixes file parameter qualms

* Fixes font parameter qualms

* Fixes calendar parameter qualms

* Fixes multiply-defined slider optsChanged

* Fixes pen parameter qualms

* ptree example minor bugfix

* Pen dialog bugfixes

* File dialog bugfixes / mild improvements

* unto ptree save state regression

* file fixup

* Adds parameter descriptions to docstrings

* Improved parameter documentation

* adds 'relativeTo' option for file parameter

* Less abuse of Qt enums during or-operations

* More uniform handling of relative paths

* More cleanup of enum setting

* better name for window title (matches qt name)

* Favor os.path over pathlib

* Exposes 'directory', 'windowTitle' to file parameter

* Fixup and add comparison to parameter tree state restoration

* Exposes "cosmetic" in pen parameter

* Indicate defaults in parameter documentation

* QtEnumParameter works for enums outside QtCore.Qt

* see if altering pytest report fixes ci bug

* Cleanup unused import and redundant `self.widget` assignments

Co-authored-by: Fekete Imre <feketeimre87@gmail.com>
Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>
2021-07-23 14:40:49 -07:00
Nils Nemitz 8f96c78715
Extend ColorMap with HSL cycles and subset generation (#1911)
* Extend ColorMap with HSL cycles and subset generation

* relaxed color palette data

* added hex to be installed in colors/maps/
2021-07-23 14:38:17 -07:00
Ogi Moore d396d33799
Remove the use of pyqtgraph.ptime (#1914)
* Remove the use of pyqtgraph.ptime

With us supporting python3.7+, we have no more need for the ptime module
and can instead safely use perf_counter for everything.

* Address small issues PR turned up

* Reword comment in ImageView
2021-07-22 20:57:50 -07:00
Martin Chase 1d40d50b89
push bullet lists over 2 spaces to get them to show up as such in the… (#1912)
* push bullet lists over 2 spaces to get them to show up as such in the docs

* separate the literal block from the bullet list
2021-07-21 07:12:35 -07:00
KIU Shueng Chuan 1184e3fcce avoid converting dict to set 2021-07-19 08:31:54 +08:00
KIU Shueng Chuan 394b0dd75c implement set/get for cameraParams 2021-07-19 06:57:07 +08:00
KIU Shueng Chuan 8a6640c419 treat self.opts as private. provide accessors 2021-07-19 04:52:49 +08:00
Ogi Moore 66ec0996f4
Merge pull request #1610 from j9ac9k/boost-plotline-performance
Boost plotline performance
2021-07-18 13:44:01 -07:00
Ogi Moore 699ed578f4
Merge pull request #1913 from ixjlyons/update-sphinx
Bump sphinx and theme versions
2021-07-18 12:46:09 -07:00
Kenneth Lyons 0ae6b753fc Bump sphinx and theme versions 2021-07-18 12:37:29 -07:00
Ogi Moore 4e293dd17d
Merge pull request #1909 from NilsNemitz/None-check_AxisItem_label
None-check AxisItem.label before access
2021-07-18 10:44:48 -07:00
Nils Nemitz 8b4db67ae9 None-check self.label before access 2021-07-18 17:24:55 +09:00
KIU Shueng Chuan d9726dbcc1 implement GLPainterItem 2021-07-18 14:14:13 +08:00
Ogi Moore d5990bf32c Uncomment ViewBox.itemChange
This handles calls for ViewBox.prepareForPaint
2021-07-17 22:30:36 -07:00
Dennis Goeries cdb427ff2f Provide a different plot test 2021-07-17 22:29:06 -07:00
Ogi Moore ed66eef203
Merge pull request #1902 from pijyoi/simpler_mvp
simplify modelview projection computation
2021-07-17 22:03:32 -07:00
Ogi Moore 58aac09387
Merge pull request #1903 from pijyoi/cleanup_glv
Cleanup GLViewWidget
2021-07-17 21:59:40 -07:00
Kenneth Lyons ba7129a719
Add option to limit LinearRegionitem bounds to a secondary item (#1834)
* Added clipItem option to LinearRegionItem

* Added a clipItem option to LinearRegionItem

Handle case when no self.viewBox() is yet available

* Implement LinearRegionItem clipItem

* Undo unnecessary change

* Update clipItem doc

* Fixup docstring formatting

* Cleanup

* Support clearing clipItem via setBounds. Fix initialization bug

* Add tests for LinearRegionItem clipItem

* Better clipItem demo in crosshair example

* Another test to verify claim in docstring

Co-authored-by: Arjun Chennu <arjun.chennu@gmail.com>
Co-authored-by: Ogi Moore <ognyan.moore@gmail.com>
Co-authored-by: Arjun Chennu <achennu@mpi-bremen.de>
2021-07-17 21:02:06 -07:00
KIU Shueng Chuan cbc9b4d310 catch specific KeyError exception 2021-07-18 10:45:58 +08:00
Ogi Moore ddab4180e9
Merge pull request #1907 from pijyoi/fix_glvol_arm64
fix GLVolumeItem example for arm64
2021-07-17 18:16:52 -07:00
KIU Shueng Chuan 6f49ede5c1 glDisable(GL_TEXTURE_3D) -> glDisable(GL_TEXTURE_2D) 2021-07-18 09:09:57 +08:00
Ogi Moore 76f3612245
Merge pull request #1904 from Artturin/tests-h5py-optional
tests/exporters/test_hdf5.py: skip if no h5py
2021-07-17 16:35:30 -07:00
Artturin 2de5cd78da tests/exporters/test_hdf5.py: skip if no h5py 2021-07-18 01:26:22 +03:00
Ogi Moore ddf73c2ecf
Merge pull request #1901 from outofculture/where-will-metaarray-be
put new MetaArray location in deprecation warning
2021-07-17 09:50:16 -07:00
KIU Shueng Chuan fa77dae941 render upright image (was previously transposed image) 2021-07-17 21:19:48 +08:00
KIU Shueng Chuan 5283eeb71b remove override of devicePixelRatio() 2021-07-17 18:55:55 +08:00
KIU Shueng Chuan aca627ac8c GLTextItem: use device independent pixels for viewport 2021-07-17 18:39:59 +08:00
KIU Shueng Chuan 31e10fdc1d raise ValueError instead of ctypes.ArgumentError
ctypes.ArgumentError got imported through PyOpenGL import *
2021-07-17 18:29:42 +08:00
KIU Shueng Chuan f85a1015ad fix GLTextItem to use relative imports 2021-07-17 18:28:09 +08:00
KIU Shueng Chuan e10dbfd9e1 change examples to use setCameraPosition 2021-07-17 18:25:21 +08:00
KIU Shueng Chuan 6ca81fdddb Revert "restore opts['viewport'] after clobbering"
This reverts commit 7a17cda956.
2021-07-17 18:25:21 +08:00
KIU Shueng Chuan 7a17cda956 restore opts['viewport'] after clobbering 2021-07-17 16:14:25 +08:00
KIU Shueng Chuan dfd5b5dc1b fix attribute access in failure branch 2021-07-17 16:06:44 +08:00
KIU Shueng Chuan 5b2674c9d5 change some deviceWidth() to width()
viewport / region use device pixels: deviceWidth()
anywhere else uses device independent pixels : width()
2021-07-17 15:59:14 +08:00
KIU Shueng Chuan f43f795950 don't check opengl version again during paint
we already fail upfront at initializeGL(), so any error that occurs
here won't be due to OpenGL version < 2.0
2021-07-17 15:59:14 +08:00
KIU Shueng Chuan 9bf6c01f58 fix renderToArray() broken for hidpi
define opts['viewport'] to be in device pixels.

note from the removed comments that there was one place assuming
opts['viewport'] was in device pixels and the other assuming that it was
in device independent pixels.
2021-07-17 14:49:47 +08:00
KIU Shueng Chuan bc52a2afe0 return ValueError for wrong argument, not RuntimeError 2021-07-17 13:46:11 +08:00
KIU Shueng Chuan fab505a431 remove unneeded call to makeCurrent()
in any case, context will not be valid until the widget is shown.
2021-07-17 13:32:33 +08:00
KIU Shueng Chuan 1814ff535d fail upfront for OpenGL ES instead of during item add 2021-07-17 13:30:20 +08:00
KIU Shueng Chuan f698ccc06e load background color from configOption 2021-07-17 13:05:40 +08:00
KIU Shueng Chuan ce4c6d95ed fix setCameraPosition not setting ele and azi in euler mode 2021-07-17 11:04:32 +08:00
KIU Shueng Chuan b5fc3d2a7e add comment about definition of viewport 2021-07-17 10:40:27 +08:00
KIU Shueng Chuan 1b00d3448a reimplement readQImage()
tested that call to repaint() is not needed
2021-07-17 10:34:27 +08:00
KIU Shueng Chuan 5d7dd101f2 load matrix instead of multiplying to identity 2021-07-17 10:21:26 +08:00
KIU Shueng Chuan 025ca08574 delete empty resizeGL() 2021-07-17 10:17:31 +08:00
KIU Shueng Chuan ee9b1565bd don't redefine width() and height()
Qt widgets define width() and height() to be in device independent
pixels. Don't change that meaning.
2021-07-17 10:14:53 +08:00
KIU Shueng Chuan e158034c07 remove devicePixelRatio argument. only needed for Qt4 2021-07-17 09:49:51 +08:00
KIU Shueng Chuan 31f9d0024a simplify modelview projection computation
1) no need to get rect(), which is actually defined as
	QRect(0, 0, width(), height())
2) use col-maj data() instead of row-maj copyDataTo()
   - glLoadMatrixf() takes col-maj
2021-07-17 07:56:21 +08:00
Martin Chase bcb629495c put new location in deprecation warning 2021-07-16 15:59:23 -07:00
Ogi Moore d28e1c8075
Merge pull request #1895 from j9ac9k/update-contributing-guide
Update contributing guide ahead of scipy sprints
2021-07-15 21:01:07 -07:00
Ogi Moore b79171dc3a Update contributing guide ahead of scipy sprints 2021-07-15 20:42:32 -07:00
Ogi Moore 910142aa6f
Merge pull request #1897 from pijyoi/fix_win32_plotcurveitem_opengl
PlotCurveItem: setup modelview and projection
2021-07-15 19:48:20 -07:00
KIU Shueng Chuan 7442ab1e52 PlotCurveItem: setup modelview and projection 2021-07-15 23:16:26 +08:00
Ogi Moore b9e079b10c
Merge pull request #1889 from swt2c/gltextitem_py310
Fix GLTextItem with Python 3.10
2021-07-12 09:57:01 -07:00
Scott Talbert db8180d88e Fix GLTextItem with Python 3.10
drawText() expects int arguments and Python 3.10 does not allow for
implicit rounding.
2021-07-11 22:04:00 -04:00
Ogi Moore 1ce9da36d3
Merge pull request #1891 from ixjlyons/readme-redundant-optional-deps 2021-07-11 13:14:59 -07:00
Kenneth Lyons bb94290d8e Clean up redundancy in README 2021-07-11 09:57:09 -07:00
KIU Shueng Chuan e17428b018 fix GLVolumeItem example for arm64 2021-07-09 21:12:58 +08:00
Ogi Moore 6b4385ce0d
Merge pull request #1885 from jennifer-manriquez/issue
Raise TypeError instead of Exception
2021-07-08 14:50:48 -07:00
Jennifer Manriquez 0c074ea005 Raise TypeError instead of Exception 2021-07-08 13:35:22 -05:00
220 changed files with 5482 additions and 3065 deletions

View File

@ -16,27 +16,27 @@ jobs:
- python-version: "3.7"
qt-lib: "pyqt"
qt-version: "PyQt5~=5.12.0"
numpy-version: "~=1.17.0"
numpy-version: "~=1.18.0"
- python-version: "3.7"
qt-lib: "pyside"
qt-version: "PySide2~=5.12.0"
numpy-version: "~=1.17.0"
numpy-version: "~=1.18.0"
- python-version: "3.8"
qt-lib: "pyqt"
qt-version: "PyQt5~=5.15.0"
numpy-version: "~=1.20.0"
numpy-version: "~=1.21.0"
- python-version: "3.8"
qt-lib: "pyside"
qt-version: "PySide2~=5.15.0"
numpy-version: "~=1.20.0"
numpy-version: "~=1.21.0"
- python-version: "3.9"
qt-lib: "pyqt"
qt-version: "PyQt6"
numpy-version: "~=1.20.0"
numpy-version: "~=1.21.0"
- python-version: "3.9"
qt-lib: "pyside"
qt-version: "PySide6"
numpy-version: "~=1.20.0"
numpy-version: "~=1.21.0"
steps:
- name: Checkout
uses: actions/checkout@v2
@ -101,8 +101,7 @@ jobs:
- name: Run Tests
run: |
mkdir $SCREENSHOT_DIR
pytest tests -v \
--junitxml pytest.xml
pytest tests -v
pytest examples -v
shell: bash
- name: Upload Screenshots

33
.github/workflows/python-publish.yml vendored Normal file
View File

@ -0,0 +1,33 @@
# This workflows will upload a Python Package using setup.py/twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install wheel twine setuptools
- name: Build and publish
env:
TWINE_USERNAME: __token__
# The PYPI_PASSWORD must be a pypi token with the "pypi-" prefix with sufficient permissions to upload this package
# https://pypi.org/help/#apitoken
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

View File

@ -1,3 +1,78 @@
pyqtgraph-0.12.3
Highlights:
- PlotCurveItem render speed is now substantially faster
- #1868/#1873 Example app now has filter text input
- #1910 PlotSpeedTest now has parameter tree control panel
New Features:
- #1844 More parameter item types (File, Calendar, ProgressBar, Font, Pen, Slider)
- #1865 Matplotlib colormaps viridis, plasma, magma and inferno are now included in pyqtgraph
- #1911 Extend Colormap with HSL cycles and subset generation
- #1932 Make anti-aliasing optional for paintGL in PlotCurveItem
- #1944 Expand use of QColor functions/methods, including setNamedColor
- #1952 Add checklist parameter item
- #1998 ThreadTrace can now save to a file
Performance Enhancement:
- #1927 Reduce ColorMap inefficiencies
- #1956 use QByteArray as backing store in arrayToQPath
- #1965 perform arrayToQPath in chunks
Bug Fixes:
- #1845 Fix zoom behavior with showGrid by separating mouse events stolen by AxisItem
- #1860 RemoteGraphicsView and RemoteSpeedTest now work under windows venv environments
- #1865 Fixed matplotlib colormap importer code
- #1869 Fix ColorBarItem tick position on export
- #1871 Allow adding items to GLViewWidget before showing plot
- #1875 Fix calls in mouse methods in GLViewWidgets due to missing event.localPos() in PyQt6
- #1876 Fix for improper placement of ROI handle positions in some cases
- #1889/#2003 Fix call to drawText in GLTextItem and GLGradientLegendItem on Python 3.10
- #1897/#1902 Re-enable "experimental" feature with fix for PlotCurveItem with OpenGL on Windows
- #1907 Fix GLVolumeItem example for arm64 platforms
- #1909 Check if AxisItem.label is None before and exit early in resizeEvent
- #1920 arrayToQPath can handle empty paths
- #1936 QPolygonF creation can now handle empty arrays
- #1968 Fix output of clip_array in colormap.modulatedBarData not being assigned
- #1973 Fix PlotItem.updateDecimate unhiding intentionally hidden curves
- #1974 Fix ImageView levelMode with levelMode == 'rgba'
- #1987 Fix HistogramLUTItem itemChanged with use of autoLevel
- #2009 Fix ROI curves hidding in ImageView
API/Behavior Changes:
- #1992 Reverted to traditional log10 mode for PlotDataItem
- #1840 Allow border=False in GraphicsLayout
- #1846 Reduced pollution to pg.namespace
- #1853 ColorMap.getColors and getStops behavior changes
- #1864 Draw GradientLegend in ViewBox coordinates
- #1885 Raise TypeError instead of general Exception if functions.eq is unable to determine equality
- #1903 Cleanup GLViewWidget
- #1908 More readable parameters for ColorBarItem
- #1914 Emit deprecation warning for use of pyqtgraph.ptime
- #1928 Restore previous signature of TargetItem.setPos
- #1940 fix log mode by reverting to previous formulation
- #1954 Deprecate use of values opt for list parameter type
- #1995 ColorButton now takes optional padding argument instead of hardcoded value of 6
Other:
- #1862/#1901 MetaArray now under deprecation warning, to be removed in a future version
- #1892 Add GLPainterItem Example
- #1844 Debugged elusive intermitted CI segfault
- #1870/#1891 Updated README.md
- #1895 Update CONTRIBUTING.md
- #1913 Bump sphinx and theme versions
- #1919 Re-organize paramtypes
- #1935 Remove some unused imports
- #1939 Remove usage of python2_3.py
- #1941 Remove str casting of QTextEdit.toPlainText output
- #1942 Add EOF newline to files missing it
- #1943 Remove python2 code paths
- #1951 Fix typos in docs
- #1957 Bump minimum numpy version to 1.18
- #1968 Fix ImageView calling deprecated QGraphicsItem.scale()
- #1985 delegate float LUTs to makeARGB with warning
- #2014 Replace couple absolute imports with relative imports
pyqtgraph-0.12.2
Highlights

View File

@ -4,77 +4,86 @@ Contributions to pyqtgraph are welcome! Be kind and respectful! See [our Code of
Please use the following guidelines when preparing changes:
## Submitting Code Changes
## Development Environment Creation
* The preferred method for submitting changes is by github pull request against the "master" branch.
* Pull requests should include only a focused and related set of changes. Mixed features and unrelated changes may be rejected.
* For major changes, it is recommended to discuss your plans on the mailing list or in a github issue before putting in too much effort.
* The following deprecations are being considered by the maintainers
* `pyqtgraph.opengl` may be deprecated and replaced with `VisPy` functionality
* After v0.11, pyqtgraph will adopt [NEP-29](https://numpy.org/neps/nep-0029-deprecation_policy.html) which will effectively mean that python2 support will be deprecated
* Qt4 will be deprecated shortly, as well as Qt5<5.9 (and potentially <5.12)
First thing to do is fork the repository, and clone your own fork to your local computer.
```bash
git clone https://github.com/<username>/pyqtgraph.git
cd pyqtgraph
```
While there is nothing preventing users from using `conda` environments, as a general principle, we recommend using the `venv` module for creating an otherwise empty virtual environment. Furthermore, at this time, WSL is not supported (it can likely be made to work, but you're on your own if you go down this route).
```bash
python3.9 -m venv .venv
source .venv/bin/activate
# on windows this would be .venv/Scripts/activate
python -m pip install --upgrade wheel setuptools pip
python -m pip install numpy scipy pyside6 -e .
```
Before making changes to the code-base, create a different branch with a name that should be unique (this makes it easier for maintainers to examine the proposed changes locally).
```bash
git switch -c my-new-feature
```
When you're ready to submit the pull request, do so via the github, and the target of the pull request should be the `master` branch in the pyqtgraph repo.
Pull requests should include only a focused and related set of changes. Mixed features and unrelated changes may be rejected.
For major changes, it is recommended to discuss your plans on the mailing list or in a github issue/discussion before putting in too much effort.
PyQtGraph has adopted [NEP-29](https://numpy.org/neps/nep-0029-deprecation_policy.html) which governs the timeline for phasing out support for numpy and python versions.
## Documentation
* Writing proper documentation and unit tests is highly encouraged. PyQtGraph uses pytest style testing, so tests should usually be included in a tests/ directory adjacent to the relevant code.
* Documentation is generated with sphinx; please check that docstring changes compile correctly
* Writing proper documentation and unit tests is highly encouraged. PyQtGraph uses [`pytest`](https://docs.pytest.org/) for testing.
* Documentation is generated with sphinx, and usage of [numpy-docstyle](https://numpydoc.readthedocs.io/en/latest/format.html) is encouraged (many places in the library do not use this docstring style at present, it's a gradual process to migrate).
* The docs built for this PR can be previewed by clicking on the "Details" link for the read-the-docs entry in the checks section of the PR conversation page.
## Style guidelines
### Rules
### Formatting ~~Rules~~ Suggestions
* PyQtGraph prefers PEP8 for most style issues, but this is not enforced rigorously as long as the code is clean and readable.
* Use `python setup.py style` to see whether your code follows the mandatory style guidelines checked by flake8.
* Exception 1: All variable names should use camelCase rather than underscore_separation. This is done for consistency with Qt
* Exception 2: Function docstrings use ReStructuredText tables for describing arguments:
```text
============== ========================================================
**Arguments:**
argName1 (type) Description of argument
argName2 (type) Description of argument. Longer descriptions must
be wrapped within the column guidelines defined by the
"====" header and footer.
============== ========================================================
```
QObject subclasses that implement new signals should also describe
these in a similar table.
* Variable and Function/Methods that are intended to be part of the public API should be camelCase.
* "Private" methods/variables should have a leading underscore (`_`) before the name.
### Pre-Commit
PyQtGraph developers are highly encouraged to (but not required) to use [`pre-commit`](https://pre-commit.com/). `pre-commit` does a number of checks when attempting to commit the code to ensure it conforms to various standards, such as `flake8`, utf-8 encoding pragma, line-ending fixers, and so on. If any of the checks fail, the commit will be rejected, and you will have the opportunity to make the necessary fixes before adding and committing a file again. This ensures that every commit made conforms to (most) of the styling standards that the library enforces; and you will most likely pass the code style checks by the CI.
PyQtGraph developers are highly encouraged to (but not required) to use [`pre-commit`](https://pre-commit.com/). `pre-commit` does a number of checks when attempting to commit the code to being committed, such as ensuring no large files are accidentally added, address mixed-line-endings types and so on. Check the [pre-commit documentation](https://pre-commit.com) on how to setup.
To make use of `pre-commit`, have it available in your `$PATH` and run `pre-commit install` from the root directory of PyQtGraph.
## Testing
## Testing Setting up a test environment
### Dependencies
### Basic Setup
* tox
* tox-conda
* pytest
* pytest-cov
* pytest-xdist
* Optional: pytest-xvfb
* Optional: pytest-xvfb (used on linux with headless displays)
If you have `pytest<5` (used in python2), you may also want to install `pytest-faulthandler==1.6` plugin to output extra debugging information in case of test failures. This isn't necessary with `pytest>=5`
To run the test suite, after installing the above dependencies run
```bash
python -m pytest examples tests
```
### Tox
As PyQtGraph supports a wide array of Qt-bindings, and python versions, we make use of `tox` to test against most of the configurations in our test matrix. As some of the qt-bindings are only installable via `conda`, `conda` needs to be in your `PATH`, and we utilize the `tox-conda` plugin.
As PyQtGraph supports a wide array of Qt-bindings, and python versions, we make use of `tox` to test against as many supported configurations as feasible. With tox installed, simply run `tox` and it will run through all the configurations. This should be done if there is uncertainty regarding changes working on specific combinations of PyQt bindings and/or python versions.
* Tests for a module should ideally cover all code in that module, i.e., statement coverage should be at 100%.
* To measure the test coverage, un `pytest --cov -n 4` to run the test suite with coverage on 4 cores.
### Continuous Integration
### Continous Integration
For our Continuous Integration, we utilize Azure Pipelines. Tested configurations are visible on [README](README.md). More information on coverage and test failures can be found on the respective tabs of the [build results page](https://dev.azure.com/pyqtgraph/pyqtgraph/_build?definitionId=1)
For our Continuous Integration, we utilize Github Actions. Tested configurations are visible on [README](README.md).
### Benchmarks
( *Still under development* ) To ensure this library is performant, we use [Air Speed Velocity (asv)](https://asv.readthedocs.io/en/stable/) to run benchmarks. For developing on core functions and classes, be aware of any impact your changes have on their speed. To configure and run asv:
```
```bash
pip install asv
python setup.py asv_config
asv run

View File

@ -35,22 +35,13 @@ Currently this means:
* Python 3.7+
* Qt 5.12-5.15, 6.1
* Required
* [PyQt5](https://www.riverbankcomputing.com/software/pyqt/), [PyQt6](https://www.riverbankcomputing.com/software/pyqt/), [PySide2](https://wiki.qt.io/Qt_for_Python) or [PySide6](https://wiki.qt.io/Qt_for_Python)
* [`numpy`](https://github.com/numpy/numpy) 1.17+
* Optional
* `scipy` for image processing
* `pyopengl` for 3D graphics
* `pyopengl` on macOS Big Sur only works with python 3.9.1+
* `hdf5` for large hdf5 binary format support
* `colorcet` for supplemental colormaps
* [`cupy`](https://docs.cupy.dev/en/stable/install.html) for CUDA-enhanced image processing
* On Windows, CUDA toolkit must be >= 11.1
* [`numba`] used to accelerate repeated image display for ImageItem
* [PyQt5](https://www.riverbankcomputing.com/software/pyqt/),
[PyQt6](https://www.riverbankcomputing.com/software/pyqt/),
[PySide2](https://wiki.qt.io/Qt_for_Python), or
[PySide6](https://wiki.qt.io/Qt_for_Python)
* [`numpy`](https://github.com/numpy/numpy) 1.18+
Optional added functionalities
------------------------------
### Optional added functionalities
Through 3rd part libraries, additional functionality may be added to PyQtGraph, see the table below for a summary.
@ -100,7 +91,7 @@ Support
Installation Methods
--------------------
* From PyPI:
* From PyPI:
* Last released version: `pip install pyqtgraph`
* Latest development version: `pip install git+https://github.com/pyqtgraph/pyqtgraph@master`
* From conda
@ -115,7 +106,7 @@ Documentation
The official documentation lives at [pyqtgraph.readthedocs.io](https://pyqtgraph.readthedocs.io)
The easiest way to learn PyQtGraph is to browse through the examples; run `python -m pyqtgraph.examples` to launch the examples application.
The easiest way to learn PyQtGraph is to browse through the examples; run `python -m pyqtgraph.examples` to launch the examples application.
Used By
-------

View File

@ -0,0 +1,30 @@
import numpy as np
import pyqtgraph as pg
rng = np.random.default_rng(12345)
class _TimeSuite:
params = ([10_000, 100_000, 1_000_000], ['all', 'finite', 'pairs', 'array'])
def setup(self, nelems, connect):
self.xdata = np.arange(nelems, dtype=np.float64)
self.ydata = rng.standard_normal(nelems, dtype=np.float64)
if connect == 'array':
self.connect_array = np.ones(nelems, dtype=bool)
if self.have_nonfinite:
self.ydata[::5000] = np.nan
def time_test(self, nelems, connect):
if connect == 'array':
connect = self.connect_array
pg.arrayToQPath(self.xdata, self.ydata, connect=connect)
class TimeSuiteAllFinite(_TimeSuite):
def __init__(self):
super().__init__()
self.have_nonfinite = False
class TimeSuiteWithNonFinite(_TimeSuite):
def __init__(self):
super().__init__()
self.have_nonfinite = True

View File

@ -1,5 +1,5 @@
pyside2
numpy
pyopengl
sphinx==3.4.3
sphinx_rtd_theme==0.5.1
sphinx==4.1.1
sphinx_rtd_theme==0.5.2

View File

@ -0,0 +1,7 @@
GLGraphItem
===========
.. autoclass:: pyqtgraph.opengl.GLGraphItem
:members:
.. automethod:: pyqtgraph.opengl.GLGraphItem.__init__

View File

@ -19,6 +19,7 @@ Contents:
glviewwidget
glgriditem
glgraphitem
glsurfaceplotitem
glvolumeitem
glimageitem

View File

@ -34,12 +34,6 @@ Qt uses the classes QColor, QPen, and QBrush to determine how to draw lines and
.. autofunction:: pyqtgraph.colorCIELab
.. autofunction:: pyqtgraph.colorTuple
.. autofunction:: pyqtgraph.colorStr
.. autofunction:: pyqtgraph.glColor
.. autofunction:: pyqtgraph.colorDistance
@ -108,3 +102,16 @@ Miscellaneous Functions
.. autofunction:: pyqtgraph.systemInfo
.. autofunction:: pyqtgraph.exit
Legacy Color Helper Functions
-------------------------------
The following helper functions should no longer be used. The functionality that they implement is trivial and it is suggested that the user use the equivalent QColor methods directly.
.. autofunction:: pyqtgraph.colorTuple
.. autofunction:: pyqtgraph.colorStr
.. autofunction:: pyqtgraph.glColor

View File

@ -101,7 +101,7 @@ Embedding PyQtGraph as a sub-package of a larger project
When writing applications or python packages that make use of pyqtgraph, it is most common to install pyqtgraph system-wide (or within a virtualenv) and simply call `import pyqtgraph` from within your application. The main benefit to this is that pyqtgraph is configured independently of your application and thus you (or your users) are free to install newer versions of pyqtgraph without changing anything in your application. This is standard practice when developing with python.
Occasionally, a specific program needs to be kept in working order for an extended amount of time after development has been completed. This is often the case for single-purpose scientific applications. If we want to ensure that the software will still work ten years later, then it is preferrable to tie it to a very specific version of pyqtgraph and *avoid* importing the system-installed version, which may be much newer and potentially incompatible. This is especially true when the application requires site-specific modifications to the pyqtgraph package.
Occasionally, a specific program needs to be kept in working order for an extended amount of time after development has been completed. This is often the case for single-purpose scientific applications. If we want to ensure that the software will still work ten years later, then it is preferable to tie it to a very specific version of pyqtgraph and *avoid* importing the system-installed version, which may be much newer and potentially incompatible. This is especially true when the application requires site-specific modifications to the pyqtgraph package.
To support such a separate local installation, all internal import statements in pyqtgraph are relative. That means that pyqtgraph never refers to itself internally as 'pyqtgraph'. This allows the package to be renamed or used as a sub-package without any naming conflicts with other versions of pyqtgraph on the system.

View File

@ -3,6 +3,8 @@ Parameter
.. autofunction:: pyqtgraph.parametertree.registerParameterType
.. autofunction:: pyqtgraph.parametertree.registerParameterItemType
.. autoclass:: pyqtgraph.parametertree.Parameter
:members:

View File

@ -1,3 +1,7 @@
..
This file is auto-generated from pyqtgraph/tools/rebuildPtreeRst.py. Do not modify by hand! Instead, rerun the
generation script with `python pyqtgraph/tools/rebuildPtreeRst.py`.
Built-in Parameter Types
========================
@ -6,7 +10,25 @@ Built-in Parameter Types
Parameters
----------
.. autoclass:: SimpleParameter
.. autoclass:: ActionParameter
:members:
.. autoclass:: CalendarParameter
:members:
.. autoclass:: ChecklistParameter
:members:
.. autoclass:: ColorMapParameter
:members:
.. autoclass:: ColorParameter
:members:
.. autoclass:: FileParameter
:members:
.. autoclass:: FontParameter
:members:
.. autoclass:: GroupParameter
@ -15,16 +37,46 @@ Parameters
.. autoclass:: ListParameter
:members:
.. autoclass:: TextParameter
.. autoclass:: PenParameter
:members:
.. autoclass:: ActionParameter
.. autoclass:: ProgressBarParameter
:members:
.. autoclass:: SimpleParameter
:members:
.. autoclass:: SliderParameter
:members:
.. autoclass:: TextParameter
:members:
ParameterItems
--------------
.. autoclass:: WidgetParameterItem
.. autoclass:: ActionParameterItem
:members:
.. autoclass:: BoolParameterItem
:members:
.. autoclass:: CalendarParameterItem
:members:
.. autoclass:: ChecklistParameterItem
:members:
.. autoclass:: ColorMapParameterItem
:members:
.. autoclass:: ColorParameterItem
:members:
.. autoclass:: FileParameterItem
:members:
.. autoclass:: FontParameterItem
:members:
.. autoclass:: GroupParameterItem
@ -33,8 +85,20 @@ ParameterItems
.. autoclass:: ListParameterItem
:members:
.. autoclass:: TextParameterItem
.. autoclass:: NumericParameterItem
:members:
.. autoclass:: ActionParameterItem
.. autoclass:: PenParameterItem
:members:
.. autoclass:: ProgressBarParameterItem
:members:
.. autoclass:: SliderParameterItem
:members:
.. autoclass:: StrParameterItem
:members:
.. autoclass:: TextParameterItem
:members:

View File

@ -16,7 +16,7 @@ To select a 2D region from an image, pyqtgraph uses the :class:`ROI <pyqtgraph.R
To automatically extract a region of image data using an ROI and an ImageItem, use :func:`ROI.getArrayRegion <pyqtgraph.ROI.getArrayRegion>`. ROI classes use the :func:`affineSlice <pyqtgraph.affineSlice>` function to perform this extraction.
ROI can also be used as a control for moving/rotating/scaling items in a scene similar to most vetctor graphics editing applications.
ROI can also be used as a control for moving/rotating/scaling items in a scene similar to most vector graphics editing applications.
See the ROITypes example for more information.

View File

@ -41,7 +41,7 @@ class MainWindow(QtWidgets.QMainWindow):
cmap = pg.colormap.get('CET-L9')
bar = pg.ColorBarItem(
interactive=False, values= (0, 30_000), cmap=cmap,
interactive=False, values= (0, 30_000), colorMap=cmap,
label='vertical fixed color bar'
)
bar.setImageItem( i1, insert_in=p1 )
@ -59,11 +59,11 @@ class MainWindow(QtWidgets.QMainWindow):
cmap = pg.colormap.get('CET-L4')
bar = pg.ColorBarItem(
values = (0, 30_000),
cmap=cmap,
colorMap=cmap,
label='horizontal color bar',
limits = (0, None),
rounding=1000,
orientation = 'horizontal',
orientation = 'h',
pen='#8888FF', hoverPen='#EEEEFF', hoverBrush='#EEEEFF80'
)
bar.setImageItem( i2, insert_in=p2 )
@ -83,7 +83,7 @@ class MainWindow(QtWidgets.QMainWindow):
limits = (-30_000, 30_000), # start with full range...
rounding=1000,
width = 10,
cmap=cmap )
colorMap=cmap )
bar.setImageItem( [i3, i4] )
bar.setLevels( low=-5_000, high=15_000) # ... then adjust to retro sunset.

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Demonstrate use of GLLinePlotItem to draw cross-sections of a surface.
This example demonstrates the use of GLBarGraphItem.
"""
## Add path to library (just for examples; you do not need this)
@ -13,9 +13,9 @@ import numpy as np
app = pg.mkQApp("GLBarGraphItem Example")
w = gl.GLViewWidget()
w.opts['distance'] = 40
w.show()
w.setWindowTitle('pyqtgraph example: GLBarGraphItem')
w.setCameraPosition(distance=40)
gx = gl.GLGridItem()
gx.rotate(90, 0, 1, 0)

View File

@ -0,0 +1,43 @@
import initExample
import pyqtgraph as pg
import pyqtgraph.opengl as gl
import numpy
app = pg.mkQApp()
w = gl.GLViewWidget()
w.show()
w.setWindowTitle("pyqtgraph example: GLGradientLegendItem")
w.setCameraPosition(distance=60)
gx = gl.GLGridItem()
gx.rotate(90, 0, 1, 0)
w.addItem(gx)
md = gl.MeshData.cylinder(rows=10, cols=20, radius=[5.0, 5], length=20.0)
md._vertexes[:, 2] = md._vertexes[:, 2] - 10
# set color based on z coordinates
color_map = pg.colormap.get("CET-L10")
h = md.vertexes()[:, 2]
# remember these
h_max, h_min = h.max(), h.min()
h = (h - h_min) / (h_max - h_min)
colors = color_map.map(h, mode="float")
md.setFaceColors(colors)
m = gl.GLMeshItem(meshdata=md, smooth=True)
w.addItem(m)
legendLabels = numpy.linspace(h_max, h_min, 5)
legendPos = numpy.linspace(1, 0, 5)
legend = dict(zip(map(str, legendLabels), legendPos))
gll = gl.GLGradientLegendItem(
pos=(10, 10), size=(50, 300), gradient=color_map, labels=legend
)
w.addItem(gll)
## Start Qt event loop unless running in interactive mode.
if __name__ == "__main__":
pg.exec()

47
examples/GLGraphItem.py Normal file
View File

@ -0,0 +1,47 @@
"""
Demonstrates use of GLGraphItem
"""
## Add path to library (just for examples; you do not need this)
import initExample
import pyqtgraph as pg
import pyqtgraph.opengl as gl
import numpy as np
app = pg.mkQApp("GLGraphItem Example")
w = gl.GLViewWidget()
w.setCameraPosition(distance=20)
w.show()
edges = np.array([
[0, 2],
[0, 3],
[1, 2],
[1, 3],
[2, 3]
])
nodes = np.array(
[
[0, 0, 0],
[1, 0, 0],
[0, 1, 0],
[1, 1, 1]
]
)
edgeColor=pg.glColor("w")
gi = gl.GLGraphItem(
edges=edges,
nodePositions=nodes,
edgeWidth=1.,
nodeSize=10.
)
w.addItem(gi)
if __name__ == "__main__":
pg.exec()

View File

@ -15,9 +15,9 @@ import numpy as np
app = pg.mkQApp("GLImageItem Example")
w = gl.GLViewWidget()
w.opts['distance'] = 200
w.show()
w.setWindowTitle('pyqtgraph example: GLImageItem')
w.setCameraPosition(distance=200)
## create volume data set to slice three images from
shape = (100,100,70)

View File

@ -6,16 +6,15 @@ Demonstrate use of GLLinePlotItem to draw cross-sections of a surface.
## Add path to library (just for examples; you do not need this)
import initExample
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import pyqtgraph as pg
import numpy as np
app = pg.mkQApp("GLLinePlotItem Example")
w = gl.GLViewWidget()
w.opts['distance'] = 40
w.show()
w.setWindowTitle('pyqtgraph example: GLLinePlotItem')
w.setCameraPosition(distance=40)
gx = gl.GLGridItem()
gx.rotate(90, 0, 1, 0)
@ -37,7 +36,7 @@ for i in range(n):
d = np.hypot(x, yi)
z = 10 * np.cos(d) / (d+1)
pts = np.column_stack([x, np.full_like(x, yi), z])
plt = gl.GLLinePlotItem(pos=pts, color=pg.glColor((i,n*1.3)), width=(i+1)/10., antialias=True)
plt = gl.GLLinePlotItem(pos=pts, color=pg.mkColor((i,n*1.3)), width=(i+1)/10., antialias=True)
w.addItem(plt)
if __name__ == '__main__':

94
examples/GLPainterItem.py Normal file
View File

@ -0,0 +1,94 @@
"""
Demonstrate using QPainter on a subclass of GLGraphicsItem.
"""
## Add path to library (just for examples; you do not need this)
import initExample
import pyqtgraph as pg
import pyqtgraph.opengl
from pyqtgraph.Qt import QtCore, QtGui
import OpenGL.GL as GL
SIZE = 32
class GLPainterItem(pg.opengl.GLGraphicsItem.GLGraphicsItem):
def __init__(self, **kwds):
super().__init__()
glopts = kwds.pop('glOptions', 'additive')
self.setGLOptions(glopts)
def compute_projection(self):
modelview = GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX)
projection = GL.glGetDoublev(GL.GL_PROJECTION_MATRIX)
mvp = projection.T @ modelview.T
mvp = QtGui.QMatrix4x4(mvp.ravel().tolist())
# note that QRectF.bottom() != QRect.bottom()
rect = QtCore.QRectF(self.view().rect())
ndc_to_viewport = QtGui.QMatrix4x4()
ndc_to_viewport.viewport(rect.left(), rect.bottom(), rect.width(), -rect.height())
return ndc_to_viewport * mvp
def paint(self):
self.setupGLState()
painter = QtGui.QPainter(self.view())
self.draw(painter)
painter.end()
def draw(self, painter):
painter.setPen(QtCore.Qt.GlobalColor.white)
painter.setRenderHints(QtGui.QPainter.RenderHint.Antialiasing | QtGui.QPainter.RenderHint.TextAntialiasing)
rect = self.view().rect()
af = QtCore.Qt.AlignmentFlag
painter.drawText(rect, af.AlignTop | af.AlignRight, 'TR')
painter.drawText(rect, af.AlignBottom | af.AlignLeft, 'BL')
painter.drawText(rect, af.AlignBottom | af.AlignRight, 'BR')
opts = self.view().cameraParams()
lines = []
center = opts['center']
lines.append(f"center : ({center.x():.1f}, {center.y():.1f}, {center.z():.1f})")
for key in ['distance', 'fov', 'elevation', 'azimuth']:
lines.append(f"{key} : {opts[key]:.1f}")
xyz = self.view().cameraPosition()
lines.append(f"xyz : ({xyz.x():.1f}, {xyz.y():.1f}, {xyz.z():.1f})")
info = "\n".join(lines)
painter.drawText(rect, af.AlignTop | af.AlignLeft, info)
project = self.compute_projection()
hsize = SIZE // 2
for xi in range(-hsize, hsize+1):
for yi in range(-hsize, hsize+1):
if xi == -hsize and yi == -hsize:
# skip one corner for visual orientation
continue
vec3 = QtGui.QVector3D(xi, yi, 0)
pos = project.map(vec3).toPointF()
painter.drawEllipse(pos, 1, 1)
pg.mkQApp("GLPainterItem Example")
glv = pg.opengl.GLViewWidget()
glv.show()
glv.setWindowTitle('pyqtgraph example: GLPainterItem')
glv.setCameraPosition(distance=50, elevation=90, azimuth=0)
griditem = pg.opengl.GLGridItem()
griditem.setSize(SIZE, SIZE)
griditem.setSpacing(1, 1)
glv.addItem(griditem)
axisitem = pg.opengl.GLAxisItem()
axisitem.setSize(SIZE/2, SIZE/2, 1)
glv.addItem(axisitem)
paintitem = GLPainterItem()
glv.addItem(paintitem)
if __name__ == '__main__':
pg.exec()

View File

@ -15,9 +15,9 @@ import numpy as np
app = pg.mkQApp("GLScatterPlotItem Example")
w = gl.GLViewWidget()
w.opts['distance'] = 20
w.show()
w.setWindowTitle('pyqtgraph example: GLScatterPlotItem')
w.setCameraPosition(distance=20)
g = gl.GLGridItem()
w.addItem(g)

View File

@ -11,9 +11,9 @@ import pyqtgraph.opengl as gl
pg.mkQApp("GLViewWidget Example")
w = gl.GLViewWidget()
w.opts['distance'] = 20
w.show()
w.setWindowTitle('pyqtgraph example: GLViewWidget')
w.setCameraPosition(distance=20)
ax = gl.GLAxisItem()
ax.setSize(5,5,5)

View File

@ -14,9 +14,9 @@ from pyqtgraph import functions as fn
app = pg.mkQApp("GLVolumeItem Example")
w = gl.GLViewWidget()
w.opts['distance'] = 200
w.show()
w.setWindowTitle('pyqtgraph example: GLVolumeItem')
w.setCameraPosition(distance=200)
g = gl.GLGridItem()
g.scale(10, 10, 1)
@ -46,8 +46,27 @@ with np.errstate(divide = 'ignore'):
negative = np.log(fn.clip_array(-data, 0, -data.min())**2)
d2 = np.empty(data.shape + (4,), dtype=np.ubyte)
d2[..., 0] = positive * (255./positive.max())
d2[..., 1] = negative * (255./negative.max())
# Original Code
# d2[..., 0] = positive * (255./positive.max())
# d2[..., 1] = negative * (255./negative.max())
# Reformulated Code
# Both positive.max() and negative.max() are negative-valued.
# Thus the next 2 lines are _not_ bounded to [0, 255]
positive = positive * (255./positive.max())
negative = negative * (255./negative.max())
# When casting to ubyte, the original code relied on +Inf to be
# converted to 0. On arm64, it gets converted to 255.
# Thus the next 2 lines change +Inf explicitly to 0 instead.
positive[np.isinf(positive)] = 0
negative[np.isinf(negative)] = 0
# When casting to ubyte, the original code relied on the conversion
# to do modulo 256. The next 2 lines do it explicitly instead as
# documentation.
d2[..., 0] = positive.astype(int) % 256
d2[..., 1] = negative.astype(int) % 256
d2[..., 2] = d2[...,1]
d2[..., 3] = d2[..., 0]*0.3 + d2[..., 1]*0.3
d2[..., 3] = (d2[..., 3].astype(float) / 255.) **2 * 255

View File

@ -9,7 +9,7 @@ import initExample
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import pyqtgraph as pg
import pyqtgraph.ptime as ptime
from time import perf_counter
app = pg.mkQApp("ImageItem Example")
@ -33,27 +33,27 @@ view.setRange(QtCore.QRectF(0, 0, 600, 600))
data = np.random.normal(size=(15, 600, 600), loc=1024, scale=64).astype(np.uint16)
i = 0
updateTime = ptime.time()
fps = 0
updateTime = perf_counter()
elapsed = 0
timer = QtCore.QTimer()
timer.setSingleShot(True)
# not using QTimer.singleShot() because of persistence on PyQt. see PR #1605
def updateData():
global img, data, i, updateTime, fps
global img, data, i, updateTime, elapsed
## Display the data
img.setImage(data[i])
i = (i+1) % data.shape[0]
timer.start(1)
now = ptime.time()
fps2 = 1.0 / (now-updateTime)
now = perf_counter()
elapsed_now = now - updateTime
updateTime = now
fps = fps * 0.9 + fps2 * 0.1
#print "%0.1f fps" % fps
elapsed = elapsed * 0.9 + elapsed_now * 0.1
# print(f"{1 / elapsed:.1f} fps")
timer.timeout.connect(updateData)
updateData()

View File

@ -11,7 +11,8 @@ import initExample
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from pyqtgraph.ptime import time
from time import perf_counter
# pg.setConfigOptions(useOpenGL=True)
app = pg.mkQApp("MultiPlot Speed Test")
@ -38,7 +39,7 @@ plot.addItem(rgn)
data = np.random.normal(size=(nPlots*23,nSamples))
ptr = 0
lastTime = time()
lastTime = perf_counter()
fps = None
count = 0
def update():
@ -49,7 +50,7 @@ def update():
curves[i].setData(data[(ptr+i)%data.shape[0]])
ptr += nPlots
now = time()
now = perf_counter()
dt = now - lastTime
lastTime = now
if fps is None:

View File

@ -6,10 +6,9 @@ Demonstrates very basic use of PColorMeshItem
## Add path to library (just for examples; you do not need this)
import initExample
from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.Qt import QtCore
import numpy as np
import pyqtgraph as pg
import pyqtgraph.ptime as ptime
app = pg.mkQApp("PColorMesh Example")

View File

@ -7,46 +7,200 @@ Update a simple plot as rapidly as possible to measure speed.
## Add path to library (just for examples; you do not need this)
import initExample
from pyqtgraph.Qt import QtGui, QtCore
from collections import deque
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, QT_LIB
import numpy as np
import pyqtgraph as pg
from pyqtgraph.ptime import time
from time import perf_counter
import pyqtgraph.parametertree as ptree
import pyqtgraph.functions as fn
import itertools
import argparse
if QT_LIB.startswith('PyQt'):
wrapinstance = pg.Qt.sip.wrapinstance
else:
wrapinstance = pg.Qt.shiboken.wrapInstance
# defaults here result in the same configuration as the original PlotSpeedTest
parser = argparse.ArgumentParser()
parser.add_argument('--noise', dest='noise', action='store_true')
parser.add_argument('--no-noise', dest='noise', action='store_false')
parser.set_defaults(noise=True)
parser.add_argument('--nsamples', default=5000, type=int)
parser.add_argument('--frames', default=50, type=int)
parser.add_argument('--fsample', default=1000, type=float)
parser.add_argument('--frequency', default=0, type=float)
parser.add_argument('--amplitude', default=5, type=float)
parser.add_argument('--opengl', dest='use_opengl', action='store_true')
parser.add_argument('--no-opengl', dest='use_opengl', action='store_false')
parser.set_defaults(use_opengl=None)
parser.add_argument('--allow-opengl-toggle', action='store_true',
help="""Allow on-the-fly change of OpenGL setting. This may cause unwanted side effects.
""")
args = parser.parse_args()
if args.use_opengl is not None:
pg.setConfigOption('useOpenGL', args.use_opengl)
pg.setConfigOption('enableExperimental', args.use_opengl)
# don't limit frame rate to vsync
sfmt = QtGui.QSurfaceFormat()
sfmt.setSwapInterval(0)
QtGui.QSurfaceFormat.setDefaultFormat(sfmt)
class LineInstances:
def __init__(self):
self.alloc(0)
def alloc(self, size):
self.arr = np.empty((size, 4), dtype=np.float64)
self.ptrs = list(map(wrapinstance,
itertools.count(self.arr.ctypes.data, self.arr.strides[0]),
itertools.repeat(QtCore.QLineF, self.arr.shape[0])))
def array(self, size):
if size > self.arr.shape[0]:
self.alloc(size + 16)
return self.arr[:size]
def instances(self, size):
return self.ptrs[:size]
class MonkeyCurveItem(pg.PlotCurveItem):
def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
self.monkey_mode = ''
self._lineInstances = LineInstances()
def setMethod(self, param, value):
self.monkey_mode = value
def paint(self, painter, opt, widget):
if self.monkey_mode not in ['drawPolyline', 'drawLines']:
return super().paint(painter, opt, widget)
painter.setRenderHint(painter.RenderHint.Antialiasing, self.opts['antialias'])
painter.setPen(pg.mkPen(self.opts['pen']))
if self.monkey_mode == 'drawPolyline':
painter.drawPolyline(fn.arrayToQPolygonF(self.xData, self.yData))
elif self.monkey_mode == 'drawLines':
lines = self._lineInstances
npts = len(self.xData)
even_slice = slice(0, 0+(npts-0)//2*2)
odd_slice = slice(1, 1+(npts-1)//2*2)
for sl in [even_slice, odd_slice]:
npairs = (sl.stop - sl.start) // 2
memory = lines.array(npairs).reshape((-1, 2))
memory[:, 0] = self.xData[sl]
memory[:, 1] = self.yData[sl]
painter.drawLines(lines.instances(npairs))
app = pg.mkQApp("Plot Speed Test")
p = pg.plot()
p.setWindowTitle('pyqtgraph example: PlotSpeedTest')
p.setRange(QtCore.QRectF(0, -10, 5000, 20))
p.setLabel('bottom', 'Index', units='B')
curve = p.plot()
default_pen = pg.mkPen()
#curve.setFillBrush((0, 0, 100, 100))
#curve.setFillLevel(0)
children = [
dict(name='sigopts', title='Signal Options', type='group', children=[
dict(name='noise', type='bool', value=args.noise),
dict(name='nsamples', type='int', limits=[0, None], value=args.nsamples),
dict(name='frames', type='int', limits=[1, None], value=args.frames),
dict(name='fsample', title='sample rate', type='float', value=args.fsample, units='Hz'),
dict(name='frequency', type='float', value=args.frequency, units='Hz'),
dict(name='amplitude', type='float', value=args.amplitude),
]),
dict(name='useOpenGL', type='bool', value=pg.getConfigOption('useOpenGL'),
readonly=not args.allow_opengl_toggle),
dict(name='enableExperimental', type='bool', value=pg.getConfigOption('enableExperimental')),
dict(name='pen', type='pen', value=default_pen),
dict(name='antialias', type='bool', value=pg.getConfigOption('antialias')),
dict(name='connect', type='list', limits=['all', 'pairs', 'finite', 'array'], value='all'),
dict(name='skipFiniteCheck', type='bool', value=False),
dict(name='plotMethod', title='Plot Method', type='list', limits=['pyqtgraph', 'drawPolyline', 'drawLines'])
]
#lr = pg.LinearRegionItem([100, 4900])
#p.addItem(lr)
params = ptree.Parameter.create(name='Parameters', type='group', children=children)
pt = ptree.ParameterTree(showHeader=False)
pt.setParameters(params)
pw = pg.PlotWidget()
splitter = QtWidgets.QSplitter()
splitter.addWidget(pt)
splitter.addWidget(pw)
splitter.show()
data = np.random.normal(size=(50,5000))
ptr = 0
lastTime = time()
fps = None
pw.setWindowTitle('pyqtgraph example: PlotSpeedTest')
pw.setLabel('bottom', 'Index', units='B')
curve = MonkeyCurveItem(pen=default_pen)
pw.addItem(curve)
rollingAverageSize = 1000
elapsed = deque(maxlen=rollingAverageSize)
def resetTimings(*args):
elapsed.clear()
def makeData(*args):
global data, connect_array, ptr
sigopts = params.child('sigopts')
nsamples = sigopts['nsamples']
frames = sigopts['frames']
Fs = sigopts['fsample']
A = sigopts['amplitude']
F = sigopts['frequency']
ttt = np.arange(frames * nsamples, dtype=np.float64) / Fs
data = A*np.sin(2*np.pi*F*ttt).reshape((frames, nsamples))
if sigopts['noise']:
data += np.random.normal(size=data.shape)
connect_array = np.ones(data.shape[-1], dtype=bool)
ptr = 0
pw.setRange(QtCore.QRectF(0, -10, nsamples, 20))
def onUseOpenGLChanged(param, enable):
pw.useOpenGL(enable)
def onEnableExperimentalChanged(param, enable):
pg.setConfigOption('enableExperimental', enable)
def onPenChanged(param, pen):
curve.setPen(pen)
params.child('sigopts').sigTreeStateChanged.connect(makeData)
params.child('useOpenGL').sigValueChanged.connect(onUseOpenGLChanged)
params.child('enableExperimental').sigValueChanged.connect(onEnableExperimentalChanged)
params.child('pen').sigValueChanged.connect(onPenChanged)
params.child('plotMethod').sigValueChanged.connect(curve.setMethod)
params.sigTreeStateChanged.connect(resetTimings)
makeData()
fpsLastUpdate = perf_counter()
def update():
global curve, data, ptr, p, lastTime, fps
curve.setData(data[ptr%10])
ptr += 1
now = time()
dt = now - lastTime
lastTime = now
if fps is None:
fps = 1.0/dt
else:
s = np.clip(dt*3., 0, 1)
fps = fps * (1-s) + (1.0/dt) * s
p.setTitle('%0.2f fps' % fps)
app.processEvents() ## force complete redraw for every plot
global curve, data, ptr, elapsed, fpsLastUpdate
options = ['antialias', 'connect', 'skipFiniteCheck']
kwds = { k : params[k] for k in options }
if kwds['connect'] == 'array':
kwds['connect'] = connect_array
# Measure
t_start = perf_counter()
curve.setData(data[ptr], **kwds)
app.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents)
t_end = perf_counter()
elapsed.append(t_end - t_start)
ptr = (ptr + 1) % data.shape[0]
# update fps at most once every 0.2 secs
if t_end - fpsLastUpdate > 0.2:
fpsLastUpdate = t_end
average = np.mean(elapsed)
fps = 1 / average
pw.setTitle('%0.2f fps - %0.1f ms avg' % (fps, average * 1_000))
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)
if __name__ == '__main__':
pg.exec()

View File

@ -16,6 +16,7 @@ from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import pyqtgraph.widgets.RemoteGraphicsView
import numpy as np
from time import perf_counter
app = pg.mkQApp()
@ -45,7 +46,7 @@ rplt = view.pg.PlotItem()
rplt._setProxyOptions(deferGetattr=True) ## speeds up access to rplt.plot
view.setCentralItem(rplt)
lastUpdate = pg.ptime.time()
lastUpdate = perf_counter()
avgFps = 0.0
def update():
@ -62,7 +63,7 @@ def update():
if lcheck.isChecked():
lplt.plot(data, clear=True)
now = pg.ptime.time()
now = perf_counter()
fps = 1.0 / (now - lastUpdate)
lastUpdate = now
avgFps = avgFps * 0.8 + fps * 0.2

View File

@ -12,9 +12,9 @@ import initExample
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore, QtWidgets
from pyqtgraph.ptime import time
import pyqtgraph.parametertree as ptree
import pyqtgraph.graphicsItems.ScatterPlotItem
from time import perf_counter
translate = QtCore.QCoreApplication.translate
@ -26,7 +26,7 @@ param = ptree.Parameter.create(name=translate('ScatterPlot', 'Parameters'), type
dict(name='randomize', title=translate('ScatterPlot', 'Randomize: '), type='bool', value=False),
dict(name='pxMode', title='pxMode: ', type='bool', value=True),
dict(name='useCache', title='useCache: ', type='bool', value=True),
dict(name='mode', title=translate('ScatterPlot', 'Mode: '), type='list', values={translate('ScatterPlot', 'New Item'): 'newItem', translate('ScatterPlot', 'Reuse Item'): 'reuseItem', translate('ScatterPlot', 'Simulate Pan/Zoom'): 'panZoom', translate('ScatterPlot', 'Simulate Hover'): 'hover'}, value='reuseItem'),
dict(name='mode', title=translate('ScatterPlot', 'Mode: '), type='list', limits={translate('ScatterPlot', 'New Item'): 'newItem', translate('ScatterPlot', 'Reuse Item'): 'reuseItem', translate('ScatterPlot', 'Simulate Pan/Zoom'): 'panZoom', translate('ScatterPlot', 'Simulate Hover'): 'hover'}, value='reuseItem'),
])
for c in param.children():
c.setDefault(c.value())
@ -43,7 +43,7 @@ data = {}
item = pg.ScatterPlotItem()
hoverBrush = pg.mkBrush('y')
ptr = 0
lastTime = time()
lastTime = perf_counter()
fps = None
timer = QtCore.QTimer()
@ -104,7 +104,7 @@ def update():
new.setBrush(hoverBrush)
ptr += 1
now = time()
now = perf_counter()
dt = now - lastTime
lastTime = now
if fps is None:

View File

@ -15,8 +15,9 @@ import sys
import numpy as np
import pyqtgraph as pg
import pyqtgraph.ptime as ptime
from pyqtgraph.Qt import QtGui, QtCore, QT_LIB
from time import perf_counter
pg.setConfigOption('imageAxisOrder', 'row-major')
@ -247,7 +248,7 @@ ui.numbaCheck.toggled.connect(noticeNumbaCheck)
ptr = 0
lastTime = ptime.time()
lastTime = perf_counter()
fps = None
def update():
global ui, ptr, lastTime, fps, LUT, img
@ -281,7 +282,7 @@ def update():
#img.setImage(data[ptr%data.shape[0]], autoRange=False)
ptr += 1
now = ptime.time()
now = perf_counter()
dt = now - lastTime
lastTime = now
if fps is None:

View File

@ -4,7 +4,7 @@
ViewBox is the general-purpose graphical container that allows the user to
zoom / pan to inspect any area of a 2D coordinate system.
This unimaginative example demonstrates the constrution of a ViewBox-based
This unimaginative example demonstrates the construction of a ViewBox-based
plot area with axes, very similar to the way PlotItem is built.
"""

View File

@ -0,0 +1,93 @@
from pyqtgraph.parametertree import Parameter
from pyqtgraph.parametertree.Parameter import PARAM_TYPES
from pyqtgraph.parametertree.parameterTypes import GroupParameter
from ._paramtreecfg import cfg
_encounteredTypes = {'group'}
def makeChild(chType, cfgDict):
_encounteredTypes.add(chType)
param = Parameter.create(name='widget', type=chType)
param.setDefault(param.value())
def setOpt(_param, _val):
# Treat blank strings as "None" to allow 'unsetting' that option
if isinstance(_val, str) and _val == '':
_val = None
param.setOpts(**{_param.name(): _val})
optsChildren = []
metaChildren = []
for optName, optVals in cfgDict.items():
child = Parameter.create(name=optName, **optVals)
if ' ' in optName:
metaChildren.append(child)
else:
optsChildren.append(child)
child.sigValueChanged.connect(setOpt)
# Poplate initial options
for p in optsChildren:
setOpt(p, p.value())
grp = Parameter.create(name=f'Sample {chType.title()}', type='group', children=metaChildren + [param] + optsChildren)
grp.setOpts(expanded=False)
return grp
def makeMetaChild(name, cfgDict):
children = []
for chName, chOpts in cfgDict.items():
if not isinstance(chOpts, dict):
ch = Parameter.create(name=chName, type=chName, value=chOpts)
else:
ch = Parameter.create(name=chName, **chOpts)
_encounteredTypes.add(ch.type())
children.append(ch)
param = Parameter.create(name=name, type='group', children=children)
param.setOpts(expanded=False)
return param
def makeAllParamTypes():
children = []
for name, paramCfg in cfg.items():
if ' ' in name:
children.append(makeMetaChild(name, paramCfg))
else:
children.append(makeChild(name, paramCfg))
params = Parameter.create(name='Example Parameters', type='group', children=children)
# Slider needs minor tweak
sliderGrp = params.child('Sample Slider')
slider = sliderGrp.child('widget')
slider.setOpts(limits=[0, 100])
# Also minor tweak to meta opts
def setOpt(_param, _val):
infoChild.setOpts(**{_param.name(): _val})
meta = params.child('Applies to All Types')
infoChild = meta.child('Extra Information')
for child in meta.children()[1:]:
child.sigValueChanged.connect(setOpt)
def onChange(_param, _val):
if _val == 'Use span':
span = slider.opts.pop('span', None)
slider.setOpts(span=span)
else:
limits = slider.opts.pop('limits', None)
slider.setOpts(limits=limits)
sliderGrp.child('How to Set').sigValueChanged.connect(onChange)
def activate(action):
for ch in params:
if isinstance(ch, GroupParameter):
ch.setOpts(expanded=action.name() == 'Expand All')
for name in 'Collapse', 'Expand':
btn = Parameter.create(name=f'{name} All', type='action')
btn.sigActivated.connect(activate)
params.insertChild(0, btn)
missing = set(PARAM_TYPES).difference(_encounteredTypes)
if missing:
raise RuntimeError(f'{missing} parameters are not represented')
return params

187
examples/_paramtreecfg.py Normal file
View File

@ -0,0 +1,187 @@
import numpy as np
from pyqtgraph.Qt import QtWidgets
from pyqtgraph.parametertree.parameterTypes import QtEnumParameter as enum
dlg = QtWidgets.QFileDialog
cfg = {
'list': {
'limits': {
'type': 'checklist',
'limits': ['a', 'b', 'c']
}
},
'file': {
'acceptMode': {
'type': 'list',
'limits': list(enum(dlg.AcceptMode, dlg).enumMap)
},
'fileMode': {
'type': 'list',
'limits': list(enum(dlg.FileMode, dlg).enumMap)
},
'viewMode': {
'type': 'list',
'limits': list(enum(dlg.ViewMode, dlg).enumMap)
},
'dialogLabel': {
'type': 'list',
'limits': list(enum(dlg.DialogLabel, dlg).enumMap)
},
'relativeTo': {
'type': 'str',
'value': None
},
'directory': {
'type': 'str',
'value': None
},
'windowTitle': {
'type': 'str',
'value': None
},
'nameFilter': {
'type': 'str',
'value': None
}
},
'float': {
'Float Information': {
'type': 'str',
'readonly': True,
'value': 'Note that all options except "finite" also apply to "int" parameters',
},
'step': {
'type': 'float',
'limits': [0, None],
'value': 1,
},
'limits': {
'type': 'list',
'limits': {'[0, None]': [0, None], '[1, 5]': [1, 5]},
},
'suffix': {
'type': 'list',
'limits': ['Hz', 's', 'm'],
},
'siPrefix': {
'type': 'bool',
'value': True
},
'finite': {
'type': 'bool',
'value': True,
},
'dec': {
'type': 'bool',
'value': False,
},
'minStep': {
'type': 'float',
'value': 1.0e-12,
},
},
'checklist': {
'limits': {
'type': 'checklist',
'limits': ['one', 'two', 'three', 'four'],
},
'exclusive': {
'type': 'bool',
'value': False,
}
},
'pen': {
'Pen Information': {
'type': 'str',
'value': 'Click the button to see options',
'readonly': True,
},
},
'slider': {
'step': {
'type': 'float',
'limits': [0, None],
'value': 1, },
'format': {
'type': 'str',
'value': '{0:>3}',
},
'precision': {
'type': 'int',
'value': 2,
'limits': [1, None],
},
'span': {
'type': 'list',
'limits': {'linspace(-pi, pi)': np.linspace(-np.pi, np.pi), 'arange(10)**2': np.arange(10) ** 2},
},
'How to Set': {
'type': 'list',
'limits': ['Use span', 'Use step + limits'],
}
},
'calendar': {
'format': {
'type': 'str',
'value': 'MM DD',
}
},
'Applies to All Types': {
'Extra Information': {
'type': 'text',
'value': 'These apply to all parameters. Watch how this text box is altered by any setting you change.',
'default': 'These apply to all parameters. Watch how this text box is altered by any setting you change.',
'readonly': True,
},
'readonly': {
'type': 'bool',
'value': True,
},
'removable': {
'type': 'bool',
'tip': 'Adds a context menu option to remove this parameter',
'value': False,
},
'visible': {
'type': 'bool',
'value': True,
},
'disabled': {
'type': 'bool',
'value': False,
},
'title': {
'type': 'str',
'value': 'Meta Options',
},
'default': {
'tip': 'The default value that gets set when clicking the arrow in the right column',
'type': 'str',
},
'expanded': {
'type': 'bool',
'value': True,
},
},
'No Extra Options': {
'text': 'Unlike the other parameters shown, these don\'t have extra settable options.\n' \
+ 'Note: "int" *does* have the same options as float, mentioned above',
'int': 10,
'str': 'Hi, world!',
'color': '#fff',
'bool': False,
'colormap': None,
'progress': 50,
'action': None,
'font': 'Inter',
}
}

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
Demonstrates some customized mouse interaction by drawing a crosshair that follows
the mouse.
@ -31,14 +32,16 @@ p1.setAutoVisible(y=True)
#create numpy arrays
#make the numbers large to show that the xrange shows data from 10000 to all the way 0
#make the numbers large to show that the range shows data from 10000 to all the way 0
data1 = 10000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
data2 = 15000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000)
p1.plot(data1, pen="r")
p1.plot(data2, pen="g")
p2.plot(data1, pen="w")
p2d = p2.plot(data1, pen="w")
# bound the LinearRegionItem to the plotted data
region.setClipItem(p2d)
def update():
region.setZValue(10)

View File

@ -117,4 +117,4 @@ class Ui_Form(object):
self.searchFiles.setItemText(1, QCoreApplication.translate("Form", u"Content Search", None))
self.loadedFileLabel.setText("")
# retranslateUi
# retranslateUi

View File

@ -117,4 +117,4 @@ class Ui_Form(object):
self.searchFiles.setItemText(1, QCoreApplication.translate("Form", u"Content Search", None))
self.loadedFileLabel.setText("")
# retranslateUi
# retranslateUi

View File

@ -51,9 +51,9 @@ class HDF5Plot(pg.PlotCurveItem):
return # no ViewBox yet
# Determine what data range must be read from HDF5
xrange = vb.viewRange()[0]
start = max(0,int(xrange[0])-1)
stop = min(len(self.hdf5), int(xrange[1]+2))
range_ = vb.viewRange()[0]
start = max(0,int(range_[0])-1)
stop = min(len(self.hdf5), int(range_[1]+2))
# Decide by how much we should downsample
ds = int((stop-start) / self.limit) + 1

View File

@ -106,7 +106,7 @@ def imageHoverEvent(event):
val = data[i, j]
ppos = img.mapToParent(pos)
x, y = ppos.x(), ppos.y()
p1.setTitle("pos: (%0.1f, %0.1f) pixel: (%d, %d) value: %g" % (x, y, i, j, val))
p1.setTitle("pos: (%0.1f, %0.1f) pixel: (%d, %d) value: %.3g" % (x, y, i, j, val))
# Monkey-patch the image to use our custom hover function.
# This is generally discouraged (you should subclass ImageItem instead),

View File

@ -4,7 +4,8 @@ import initExample ## Add path to library (just for examples; you do not need th
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from pyqtgraph.ptime import time
from time import perf_counter
app = pg.mkQApp("Infinite Line Performance")
p = pg.plot()
@ -20,7 +21,7 @@ for i in range(100):
data = np.random.normal(size=(50, 5000))
ptr = 0
lastTime = time()
lastTime = perf_counter()
fps = None
@ -28,7 +29,7 @@ def update():
global curve, data, ptr, p, lastTime, fps
curve.setData(data[ptr % 10])
ptr += 1
now = time()
now = perf_counter()
dt = now - lastTime
lastTime = now
if fps is None:

View File

@ -29,4 +29,4 @@ try:
import faulthandler
faulthandler.enable()
except ImportError:
pass
pass

View File

@ -1,38 +1,44 @@
# -*- coding: utf-8 -*-
"""
Test programmatically setting log transformation modes.
Demonstrate programmatic setting of log transformation modes.
"""
import initExample ## Add path to library (just for examples; you do not need this)
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
app = pg.mkQApp("Log Axis Example")
w = pg.GraphicsLayoutWidget(show=True)
w.setWindowTitle('pyqtgraph example: logAxis')
p1 = w.addPlot(0,0, title="X Semilog")
w.resize(800,800)
w.setWindowTitle('pyqtgraph example: Log Axis, or How to Recognise Different Types of Curves from Quite a Long Way Away')
p0 = w.addPlot(0,0, title="Linear")
p1 = w.addPlot(0,1, title="X Semilog")
p2 = w.addPlot(1,0, title="Y Semilog")
p3 = w.addPlot(2,0, title="XY Log")
p1.showGrid(True, True)
p2.showGrid(True, True)
p3.showGrid(True, True)
p3 = w.addPlot(1,1, title="XY Log")
# configure logarithmic axis scaling:
p1.setLogMode(True, False)
p2.setLogMode(False, True)
p3.setLogMode(True, True)
# 1000 points from 0.1 to 10, chosen to give a compatible range of values across curves:
x = np.logspace(-1, 1, 1000)
plotdata = ( # legend entry, color, and plotted equation:
('1 / 3x' , '#ff9d47', 1./(3*x) ),
('sqrt x' , '#b3cf00', 1/np.sqrt(x) ),
('exp. decay', '#00a0b5', 5 * np.exp(-x/1) ),
('-log x' , '#a54dff', - np.log10(x) )
)
p0.addLegend(offset=(-20,20)) # include legend only in top left plot
for p in (p0, p1, p2, p3): # draw identical numerical data in all four plots
p.showGrid(True, True) # turn on grid for all four plots
p.showAxes(True, size=(40,None)) # show a full frame, and reserve identical room for y labels
for name, color, y in plotdata: # draw all four curves as defined in plotdata
pen = pg.mkPen(color, width=2)
p.plot( x,y, pen=pen, name=name )
w.show()
y = np.random.normal(size=1000)
x = np.linspace(0, 1, 1000)
p1.plot(x, y)
p2.plot(x, y)
p3.plot(x, y)
#p.getAxis('bottom').setLogMode(True)
if __name__ == '__main__':
pg.exec()

View File

@ -4,6 +4,7 @@ import initExample ## Add path to library (just for examples; you do not need th
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
from time import perf_counter
app = pg.mkQApp()
plt = pg.PlotWidget()
@ -18,7 +19,7 @@ plt.show()
plt.enableAutoRange(False, False)
def plot():
start = pg.ptime.time()
start = perf_counter()
n = 15
pts = 100
x = np.linspace(0, 0.8, pts)
@ -38,8 +39,8 @@ def plot():
#item.setPen(pg.mkPen('w'))
#plt.addItem(item)
dt = pg.ptime.time() - start
print("Create plots took: %0.3fms" % (dt*1000))
dt = perf_counter() - start
print(f"Create plots tooks {dt * 1000:.3f} ms")
## Plot and clear 5 times, printing the time it took
for i in range(5):
@ -55,7 +56,7 @@ for i in range(5):
def fastPlot():
## Different approach: generate a single item with all data points.
## This runs about 20x faster.
start = pg.ptime.time()
start = perf_counter()
n = 15
pts = 100
x = np.linspace(0, 0.8, pts)
@ -71,7 +72,7 @@ def fastPlot():
item.setPen(pg.mkPen('w'))
plt.addItem(item)
dt = pg.ptime.time() - start
dt = perf_counter() - start
print("Create plots took: %0.3fms" % (dt*1000))

View File

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

View File

@ -5,7 +5,6 @@ import time
import numpy as np
import pyqtgraph.multiprocess as mp
import pyqtgraph as pg
from pyqtgraph.python2_3 import xrange
print( "\n=================\nParallelize")
@ -32,7 +31,7 @@ start = time.time()
with pg.ProgressDialog('processing serially..', maximum=len(tasks)) as dlg:
for i, x in enumerate(tasks):
tot = 0
for j in xrange(size):
for j in range(size):
tot += j * x
results[i] = tot
dlg += 1
@ -46,7 +45,7 @@ start = time.time()
with mp.Parallelize(enumerate(tasks), results=results2, workers=1, progressDialog='processing serially (using Parallelizer)..') as tasker:
for i, x in tasker:
tot = 0
for j in xrange(size):
for j in range(size):
tot += j * x
tasker.results[i] = tot
print( "\nParallel time, 1 worker: %0.2f" % (time.time() - start))
@ -57,9 +56,8 @@ start = time.time()
with mp.Parallelize(enumerate(tasks), results=results3, progressDialog='processing in parallel..') as tasker:
for i, x in tasker:
tot = 0
for j in xrange(size):
for j in range(size):
tot += j * x
tasker.results[i] = tot
print( "\nParallel time, %d workers: %0.2f" % (mp.Parallelize.suggestedWorkerCount(), time.time() - start))
print( "Results match serial: %s" % str(results3 == results))

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
This example demonstrates the use of pyqtgraph's parametertree system. This provides
a simple way to generate user interfaces that control sets of parameters. The example
@ -6,17 +5,20 @@ demonstrates a variety of different parameter types (int, float, list, etc.)
as well as some customized parameter types
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
# `makeAllParamTypes` creates several parameters from a dictionary of config specs.
# This contains information about the options for each parameter so they can be directly
# inserted into the example parameter tree. To create your own parameters, simply follow
# the guidelines demonstrated by other parameters created here.
from examples._buildParamTypes import makeAllParamTypes
from pyqtgraph.Qt import QtGui
app = pg.mkQApp("Parameter Tree Example")
import pyqtgraph.parametertree.parameterTypes as pTypes
from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType
from pyqtgraph.parametertree import Parameter, ParameterTree
## test subclassing parameters
@ -62,29 +64,7 @@ class ScalableGroup(pTypes.GroupParameter):
params = [
{'name': 'Basic parameter data types', 'type': 'group', 'children': [
{'name': 'Integer', 'type': 'int', 'value': 10},
{'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1, 'finite': False},
{'name': 'String', 'type': 'str', 'value': "hi", 'tip': 'Well hello'},
{'name': 'List', 'type': 'list', 'values': [1,2,3], 'value': 2},
{'name': 'Named List', 'type': 'list', 'values': {"one": 1, "two": "twosies", "three": [3,3,3]}, 'value': 2},
{'name': 'Boolean', 'type': 'bool', 'value': True, 'tip': "This is a checkbox"},
{'name': 'Color', 'type': 'color', 'value': "#FF0", 'tip': "This is a color button"},
{'name': 'Gradient', 'type': 'colormap'},
{'name': 'Subgroup', 'type': 'group', 'children': [
{'name': 'Sub-param 1', 'type': 'int', 'value': 10},
{'name': 'Sub-param 2', 'type': 'float', 'value': 1.2e6},
]},
{'name': 'Text Parameter', 'type': 'text', 'value': 'Some text...'},
{'name': 'Action Parameter', 'type': 'action', 'tip': 'Click me'},
]},
{'name': 'Numerical Parameter Options', 'type': 'group', 'children': [
{'name': 'Units + SI prefix', 'type': 'float', 'value': 1.2e-6, 'step': 1e-6, 'siPrefix': True, 'suffix': 'V'},
{'name': 'Limits (min=7;max=15)', 'type': 'int', 'value': 11, 'limits': (7, 15), 'default': -6},
{'name': 'Int suffix', 'type': 'int', 'value': 9, 'suffix': 'V'},
{'name': 'DEC stepping', 'type': 'float', 'value': 1.2e6, 'dec': True, 'step': 1, 'minStep': 1.0e-12, 'siPrefix': True, 'suffix': 'Hz'},
]},
makeAllParamTypes(),
{'name': 'Save/Restore functionality', 'type': 'group', 'children': [
{'name': 'Save State', 'type': 'action'},
{'name': 'Restore State', 'type': 'action', 'children': [
@ -92,12 +72,6 @@ params = [
{'name': 'Remove extra items', 'type': 'bool', 'value': True},
]},
]},
{'name': 'Extra Parameter Options', 'type': 'group', 'children': [
{'name': 'Read-only', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'readonly': True},
{'name': 'Disabled', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'enabled': False},
{'name': 'Renamable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'renamable': True},
{'name': 'Removable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'removable': True},
]},
{'name': 'Custom context menu', 'type': 'group', 'children': [
{'name': 'List contextMenu', 'type': 'float', 'value': 0, 'context': [
'menu1',
@ -145,11 +119,10 @@ for child in p.children():
ch2.sigValueChanging.connect(valueChanging)
def save():
global state
state = p.saveState()
def restore():
global state
add = p['Save/Restore functionality', 'Restore State', 'Add missing items']
@ -175,8 +148,10 @@ layout.addWidget(t2, 1, 1, 1, 1)
win.show()
## test save/restore
s = p.saveState()
p.restoreState(s)
state = p.saveState()
p.restoreState(state)
compareState = p.saveState()
assert pg.eq(compareState, state)
if __name__ == '__main__':
pg.exec()

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import numpy as np
import collections
import sys, os
@ -6,7 +7,8 @@ from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.parametertree import Parameter, ParameterTree
from pyqtgraph.parametertree import types as pTypes
import pyqtgraph.configfile
from pyqtgraph.python2_3 import xrange
from time import perf_counter
class RelativityGUI(QtGui.QWidget):
@ -25,10 +27,10 @@ class RelativityGUI(QtGui.QWidget):
self.objectGroup = ObjectGroupParam()
self.params = Parameter.create(name='params', type='group', children=[
dict(name='Load Preset..', type='list', values=[]),
#dict(name='Unit System', type='list', values=['', 'MKS']),
dict(name='Load Preset..', type='list', limits=[]),
#dict(name='Unit System', type='list', limits=['', 'MKS']),
dict(name='Duration', type='float', value=10.0, step=0.1, limits=[0.1, None]),
dict(name='Reference Frame', type='list', values=[]),
dict(name='Reference Frame', type='list', limits=[]),
dict(name='Animate', type='bool', value=True),
dict(name='Animation Speed', type='float', value=1.0, dec=True, step=0.1, limits=[0.0001, None]),
dict(name='Recalculate Worldlines', type='action'),
@ -130,13 +132,13 @@ class RelativityGUI(QtGui.QWidget):
def setAnimation(self, a):
if a:
self.lastAnimTime = pg.ptime.time()
self.lastAnimTime = perf_counter()
self.animTimer.start(int(self.animDt*1000))
else:
self.animTimer.stop()
def stepAnimation(self):
now = pg.ptime.time()
now = perf_counter()
dt = (now-self.lastAnimTime) * self.params['Animation Speed']
self.lastAnimTime = now
self.animTime += dt
@ -518,7 +520,7 @@ class Simulation:
dt = self.dt
tVals = np.linspace(0, dt*(nPts-1), nPts)
for cl in self.clocks.values():
for i in xrange(1,nPts):
for i in range(1,nPts):
nextT = tVals[i]
while True:
tau1, tau2 = cl.accelLimits()
@ -564,7 +566,7 @@ class Simulation:
## These are the set of proper times (in the reference frame) that will be simulated
ptVals = np.linspace(ref.pt, ref.pt + dt*(nPts-1), nPts)
for i in xrange(1,nPts):
for i in range(1,nPts):
## step reference clock ahead one time step in its proper time
nextPt = ptVals[i] ## this is where (when) we want to end up

View File

@ -7,6 +7,7 @@ import initExample ## Add path to library (just for examples; you do not need th
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
from time import perf_counter
win = pg.GraphicsLayoutWidget(show=True)
win.setWindowTitle('pyqtgraph example: Scrolling Plots')
@ -67,7 +68,7 @@ def update2():
chunkSize = 100
# Remove chunks after we have 10
maxChunks = 10
startTime = pg.ptime.time()
startTime = perf_counter()
win.nextRow()
p5 = win.addPlot(colspan=2)
p5.setLabel('bottom', 'Time', 's')
@ -78,7 +79,7 @@ ptr5 = 0
def update3():
global p5, data5, ptr5, curves
now = pg.ptime.time()
now = perf_counter()
for c in curves:
c.setPos(-(now-startTime), 0)

View File

@ -70,51 +70,15 @@ conditionalExamples = {
False,
reason="Test is being problematic on CI machines"
),
'GLVolumeItem.py': exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
),
'GLIsosurface.py': exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
),
'GLSurfacePlot.py': exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
),
'GLScatterPlotItem.py': exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
),
'GLshaders.py': exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
),
'GLTextItem.py': exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
),
'GLLinePlotItem.py': exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
),
'GLMeshItem.py': exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
),
'GLImageItem.py': exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
),
'GLBarGraphItem.py': exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
),
'GLViewWidget.py': exceptionCondition(
}
openglExamples = ['GLViewWidget.py']
openglExamples.extend(utils.examples_['3D Graphics'].values())
for key in openglExamples:
conditionalExamples[key] = exceptionCondition(
not darwin_opengl_broken,
reason=darwin_opengl_reason
)
}
@pytest.mark.skipif(
Qt.QT_LIB == "PySide2"

View File

@ -74,6 +74,9 @@ examples_ = OrderedDict([
('Mesh', 'GLMeshItem.py'),
('Image', 'GLImageItem.py'),
('Text', 'GLTextItem.py'),
('BarGraph', 'GLBarGraphItem.py'),
('Painter', 'GLPainterItem.py'),
('Gradient Legend', 'GLGradientLegendItem.py')
])),
('Widgets', OrderedDict([
('PlotWidget', 'PlotWidget.py'),
@ -105,7 +108,6 @@ others = dict([
('ScaleBar', 'ScaleBar.py'),
('ViewBox', 'ViewBox.py'),
('GradientEditor', 'GradientEditor.py'),
('GLBarGraphItem', 'GLBarGraphItem.py'),
('GLViewWidget', 'GLViewWidget.py'),
('DiffTreeWidget', 'DiffTreeWidget.py'),
('MultiPlotWidget', 'MultiPlotWidget.py'),

View File

@ -1 +1 @@
from .chain import ChainSim
from .chain import ChainSim

View File

@ -67,7 +67,7 @@ class ChainSim(pg.QtCore.QObject):
def update(self):
# approximate physics with verlet integration
now = pg.ptime.time()
now = time.perf_counter()
if self.lasttime is None:
dt = 0
else:

View File

@ -1,17 +1,15 @@
# -*- coding: utf-8 -*-
import time
import weakref
import warnings
from time import perf_counter, perf_counter_ns
from ..Qt import QtCore, QtGui, QT_LIB, isQObjectAlive
from ..Point import Point
from .. import functions as fn
from .. import ptime as ptime
from .mouseEvents import *
from .. import debug as debug
from .. import getConfigOption
getMillis = lambda: int(round(time.time() * 1000))
getMillis = lambda: perf_counter_ns() // 10 ** 6
if QT_LIB.startswith('PyQt'):
@ -30,15 +28,15 @@ class GraphicsScene(QtGui.QGraphicsScene):
events, but this turned out to be impossible because the constructor for QGraphicsMouseEvent
is private)
* Generates MouseClicked events in addition to the usual press/move/release events.
(This works around a problem where it is impossible to have one item respond to a
drag if another is watching for a click.)
* Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects
* Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu.
* Allows items to decide _before_ a mouse click which item will be the recipient of mouse events.
This lets us indicate unambiguously to the user which item they are about to click/drag on
* Eats mouseMove events that occur too soon after a mouse press.
* Reimplements items() and itemAt() to circumvent PyQt bug
* Generates MouseClicked events in addition to the usual press/move/release events.
(This works around a problem where it is impossible to have one item respond to a
drag if another is watching for a click.)
* Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects
* Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu.
* Allows items to decide _before_ a mouse click which item will be the recipient of mouse events.
This lets us indicate unambiguously to the user which item they are about to click/drag on
* Eats mouseMove events that occur too soon after a mouse press.
* Reimplements items() and itemAt() to circumvent PyQt bug
====================== ====================================================================
**Signals**
@ -198,7 +196,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
# button is pressed' send mouseMoveEvents and mouseDragEvents
super().mouseMoveEvent(ev)
if self.mouseGrabberItem() is None:
now = ptime.time()
now = perf_counter()
init = False
## keep track of which buttons are involved in dragging
for btn in [QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.MouseButton.MiddleButton, QtCore.Qt.MouseButton.RightButton]:

View File

@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
from time import perf_counter
from ..Point import Point
from ..Qt import QtCore, QtGui
from ..Qt import QtCore
import weakref
from .. import ptime as ptime
class MouseDragEvent(object):
"""
@ -164,7 +165,7 @@ class MouseClickEvent(object):
self._button = pressEvent.button()
self._buttons = pressEvent.buttons()
self._modifiers = pressEvent.modifiers()
self._time = ptime.time()
self._time = perf_counter()
self.acceptedItem = None
def accept(self):
@ -377,4 +378,4 @@ class HoverEvent(object):
return self.__dragItems

View File

@ -12,7 +12,6 @@ This module exists to smooth out some of the differences between PySide and PyQt
import os, sys, re, time, subprocess, warnings
from ..python2_3 import asUnicode
PYSIDE = 'PySide'
PYSIDE2 = 'PySide2'
@ -73,7 +72,7 @@ class _StringIO(object):
self.data.append(data)
def getvalue(self):
return ''.join(map(asUnicode, self.data)).encode('utf8')
return ''.join(map(str, self.data)).encode('utf8')
def _loadUiType(uiFile):
@ -152,7 +151,12 @@ if QT_LIB == PYQT5:
_copy_attrs(PyQt5.QtGui, QtGui)
_copy_attrs(PyQt5.QtWidgets, QtWidgets)
from PyQt5 import sip, uic
try:
from PyQt5 import sip
except ImportError:
# some Linux distros package it this way (e.g. Ubuntu)
import sip
from PyQt5 import uic
try:
from PyQt5 import QtSvg
@ -203,11 +207,9 @@ elif QT_LIB == PYSIDE2:
except ImportError as err:
QtTest = FailedImport(err)
import shiboken2
isQObjectAlive = shiboken2.isValid
import shiboken2 as shiboken
import PySide2
VERSION_INFO = 'PySide2 ' + PySide2.__version__ + ' Qt ' + QtCore.__version__
elif QT_LIB == PYSIDE6:
import PySide6.QtCore, PySide6.QtGui, PySide6.QtWidgets
_copy_attrs(PySide6.QtCore, QtCore)
@ -227,8 +229,7 @@ elif QT_LIB == PYSIDE6:
except ImportError as err:
QtTest = FailedImport(err)
import shiboken6
isQObjectAlive = shiboken6.isValid
import shiboken6 as shiboken
import PySide6
VERSION_INFO = 'PySide6 ' + PySide6.__version__ + ' Qt ' + QtCore.__version__
@ -314,6 +315,7 @@ if QT_LIB in [PYQT6, PYSIDE6]:
if QT_LIB in [PYSIDE2, PYSIDE6]:
QtVersion = QtCore.__version__
loadUiType = _loadUiType
isQObjectAlive = shiboken.isValid
# PySide does not implement qWait
if not isinstance(QtTest, FailedImport):

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
from time import perf_counter
import weakref
from .Qt import QtCore
from .ptime import time
from . import ThreadsafeTimer
from .functions import SignalBlock
@ -60,7 +60,7 @@ class SignalProxy(QtCore.QObject):
self.timer.stop()
self.timer.start(int(self.delay * 1000) + 1)
else:
now = time()
now = perf_counter()
if self.lastFlushTime is None:
leakTime = 0
else:
@ -76,7 +76,7 @@ class SignalProxy(QtCore.QObject):
return False
args, self.args = self.args, None
self.timer.stop()
self.lastFlushTime = time()
self.lastFlushTime = perf_counter()
self.sigDelayed.emit(args)
return True

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from .Qt import QtCore, QtGui
from .Qt import QtCore
__all__ = ['ThreadsafeTimer']

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from .Qt import QtCore, QtGui
from .Qt import QtGui
from . import functions as fn
from .Vector import Vector
import numpy as np

View File

@ -8,9 +8,8 @@ This class addresses the problem of having to save and restore the state
of a large group of widgets.
"""
from .Qt import QtCore, QtGui, QT_LIB
from .Qt import QtCore, QtGui
import weakref, inspect
from .python2_3 import asUnicode
__all__ = ['WidgetGroup']
@ -45,7 +44,7 @@ def comboState(w):
except AttributeError:
pass
if data is None:
return asUnicode(w.itemText(ind))
return str(w.itemText(ind))
else:
return data
@ -280,4 +279,4 @@ class WidgetGroup(QtCore.QObject):
#print "%s: Cached value %s != set value %s" % (name, str(self.cache[name]), str(v1))

View File

@ -4,12 +4,12 @@ PyQtGraph - Scientific Graphics and GUI Library for Python
www.pyqtgraph.org
"""
__version__ = '0.12.2'
__version__ = '0.12.3'
### import all the goodies and add some helper functions for easy CLI use
## 'Qt' is a local module; it is intended mainly to cover up the differences
## between PyQt4 and PySide.
## between PyQt and PySide.
from .Qt import QtCore, QtGui, mkQApp
from .Qt import exec_ as exec
@ -22,14 +22,6 @@ import numpy ## pyqtgraph requires numpy
import os, sys
## check python version
## Allow anything >= 2.7
if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] < 6):
raise Exception("Pyqtgraph requires Python version 2.6 or greater (this is %d.%d)" % (sys.version_info[0], sys.version_info[1]))
## helpers for 2/3 compatibility
from . import python2_3
## in general openGL is poorly supported with Qt+GraphicsView.
## we only enable it where the performance benefit is critical.
## Note this only applies to 2D graphics; 3D graphics always use OpenGL.
@ -137,9 +129,6 @@ def renamePyc(startDir):
os.rename(fileName, name2)
path = os.path.split(__file__)[0]
if __version__ is None and not hasattr(sys, 'frozen') and sys.version_info[0] == 2: ## If we are frozen, there's a good chance we don't have the original .py files anymore.
renamePyc(path)
## Import almost everything to make it available from a single namespace
## don't import the more complex systems--canvas, parametertree, flowchart, dockarea
@ -282,7 +271,6 @@ from .ThreadsafeTimer import *
# indirect imports used within library
from .GraphicsScene import GraphicsScene
from .python2_3 import asUnicode
from .util.cupy_helper import getCupy
# indirect imports known to be used outside of the library
@ -360,9 +348,9 @@ def exit():
This function does the following in an attempt to 'safely' terminate
the process:
* Invoke atexit callbacks
* Close all open file handles
* os._exit()
* Invoke atexit callbacks
* Close all open file handles
* os._exit()
Note: there is some potential for causing damage with this function if you
are using objects that _require_ their destructors to be called (for example,

View File

@ -9,8 +9,6 @@ import importlib
ui_template = importlib.import_module(
f'.CanvasTemplate_{QT_LIB.lower()}', package=__package__)
import numpy as np
from .. import debug
import weakref
import gc
from .CanvasManager import CanvasManager

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from .Canvas import *
from .CanvasItem import *
from .CanvasItem import *

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import numpy as np
from .Qt import QtGui, QtCore
from .functions import mkColor, eq, colorDistance
from .functions import mkColor, eq, colorDistance, clip_scalar, clip_array
from os import path, listdir
from collections.abc import Callable, Sequence
import warnings
@ -18,10 +19,10 @@ def listMaps(source=None):
Parameters
----------
source: str, optional
Color map source. If omitted, locally stored maps are listed. Otherwise
Color map source. If omitted, locally stored maps are listed. Otherwise:
- 'matplotlib' lists maps that can be imported from Matplotlib
- 'colorcet' lists maps that can be imported from ColorCET
- 'matplotlib' lists maps that can be imported from Matplotlib
- 'colorcet' lists maps that can be imported from ColorCET
Returns
-------
@ -33,7 +34,7 @@ def listMaps(source=None):
files = listdir( pathname )
list_of_maps = []
for filename in files:
if filename[-4:] == '.csv':
if filename[-4:] == '.csv' or filename[-4:] == '.hex':
list_of_maps.append(filename[:-4])
return list_of_maps
elif source.lower() == 'matplotlib':
@ -68,10 +69,10 @@ def get(name, source=None, skipCache=False):
be a path to a file in the local folder. See the files in the
``pyqtgraph/colors/maps/`` folder for examples of the format.
source: str, optional
If omitted, a locally stored map is returned. Otherwise
If omitted, a locally stored map is returned. Otherwise:
- 'matplotlib' imports a map defined by Matplotlib.
- 'colorcet' imports a map defined by ColorCET.
- 'matplotlib' imports a map defined by Matplotlib.
- 'colorcet' imports a map defined by ColorCET.
skipCache: bool, optional
If `skipCache=True`, the internal cache is skipped and a new
@ -95,11 +96,11 @@ def _getFromFile(name):
filename = path.join(dirname, 'colors/maps/'+filename)
if not path.isfile( filename ): # try suffixes if file is not found:
if path.isfile( filename+'.csv' ): filename += '.csv'
elif path.isfile( filename+'.txt' ): filename += '.txt'
elif path.isfile( filename+'.hex' ): filename += '.hex'
with open(filename,'r') as fh:
idx = 0
color_list = []
if filename[-4:].lower() != '.txt':
if filename[-4:].lower() != '.hex':
csv_mode = True
else:
csv_mode = False
@ -122,18 +123,21 @@ def _getFromFile(name):
elif len(hex_str) == 4: # parse as abbreviated RGBA
hex_str = 2*hex_str[0] + 2*hex_str[1] + 2*hex_str[2] + 2*hex_str[3]
if len(hex_str) < 6: continue # not enough information
color_tuple = tuple( bytes.fromhex( hex_str ) )
try:
color_tuple = tuple( bytes.fromhex( hex_str ) )
except ValueError as e:
raise ValueError(f"failed to convert hexadecimal value '{hex_str}'.") from e
color_list.append( color_tuple )
idx += 1
# end of line reading loop
# end of open
cm = ColorMap(
cmap = ColorMap( name=name,
pos=np.linspace(0.0, 1.0, len(color_list)),
color=color_list) #, names=color_names)
if cm is not None:
cm.name = name
_mapCache[name] = cm
return cm
if cmap is not None:
cmap.name = name
_mapCache[name] = cmap
return cmap
def getFromMatplotlib(name):
"""
@ -146,7 +150,7 @@ def getFromMatplotlib(name):
import matplotlib.pyplot as mpl_plt
except ModuleNotFoundError:
return None
cm = None
cmap = None
col_map = mpl_plt.get_cmap(name)
if hasattr(col_map, '_segmentdata'): # handle LinearSegmentedColormap
data = col_map._segmentdata
@ -164,21 +168,22 @@ def getFromMatplotlib(name):
positions[idx2] = tup[0]
comp_vals[idx2] = tup[1] # these are sorted in the raw data
col_data[:,idx] = np.interp(col_data[:,3], positions, comp_vals)
cm = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
cmap = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
# some color maps (gnuplot in particular) are defined by RGB component functions:
elif ('red' in data) and isinstance(data['red'], Callable):
col_data = np.zeros((64, 4))
col_data[:,-1] = np.linspace(0., 1., 64)
for idx, key in enumerate(['red','green','blue']):
col_data[:,idx] = np.clip( data[key](col_data[:,-1]), 0, 1)
cm = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
cmap = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5)
elif hasattr(col_map, 'colors'): # handle ListedColormap
col_data = np.array(col_map.colors)
cm = ColorMap(pos=np.linspace(0.0, 1.0, col_data.shape[0]), color=255*col_data[:,:3]+0.5 )
if cm is not None:
cm.name = name
_mapCache[name] = cm
return cm
cmap = ColorMap( name=name,
pos = np.linspace(0.0, 1.0, col_data.shape[0]), color=255*col_data[:,:3]+0.5 )
if cmap is not None:
cmap.name = name
_mapCache[name] = cmap
return cmap
def getFromColorcet(name):
""" Generates a ColorMap object from a colorcet definition. Same as ``colormap.get(name, source='colorcet')``. """
@ -191,20 +196,65 @@ def getFromColorcet(name):
for hex_str in color_strings:
if hex_str[0] != '#': continue
if len(hex_str) != 7:
raise ValueError('Invalid color string '+str(hex_str)+' in colorcet import.')
raise ValueError(f"Invalid color string '{hex_str}' in colorcet import.")
color_tuple = tuple( bytes.fromhex( hex_str[1:] ) )
color_list.append( color_tuple )
if len(color_list) == 0:
return None
cm = ColorMap(
pos=np.linspace(0.0, 1.0, len(color_list)),
cmap = ColorMap( name=name,
pos=np.linspace(0.0, 1.0, len(color_list)),
color=color_list) #, names=color_names)
if cm is not None:
cm.name = name
_mapCache[name] = cm
return cm
if cmap is not None:
cmap.name = name
_mapCache[name] = cmap
return cmap
def makeMonochrome(color='green'):
def makeHslCycle( hue=0.0, saturation=1.0, lightness=0.5, steps=36 ):
"""
Returns a ColorMap object that traces a circular or spiraling path around the HSL color space.
Parameters
----------
hue : float or tuple of floats
Starting point or (start, end) for hue. Values can lie outside the [0 to 1] range
to realize multiple cycles. For a single value, one full hue cycle is generated.
The default starting hue is 0.0 (red).
saturation : float or tuple of floats, optional
Saturation value for the colors in the cycle, in the range of [0 to 1].
If a (start, end) tuple is given, saturation gradually changes between these values.
The default saturation is 1.0.
lightness : float or tuple of floats, optional
Lightness value for the colors in the cycle, in the range of [0 to 1].
If a (start, end) tuple is given, lightness gradually changes between these values.
The default lightness is 1.0.
steps: int, optional
Number of steps in the cycle. Between these steps, the color map will interpolate in RGB space.
The default number of steps is 36, generating a color map with 37 stops.
"""
if isinstance( hue, (tuple, list) ):
hueA, hueB = hue
else:
hueA = hue
hueB = hueA + 1.0
if isinstance( saturation, (tuple, list) ):
satA, satB = saturation
else:
satA = satB = saturation
if isinstance( lightness, (tuple, list) ):
lgtA, lgtB = lightness
else:
lgtA = lgtB = lightness
hue_vals = np.linspace(hueA, hueB, num=steps+1)
sat_vals = np.linspace(satA, satB, num=steps+1)
lgt_vals = np.linspace(lgtA, lgtB, num=steps+1)
color_list = []
for hue, sat, lgt in zip( hue_vals, sat_vals, lgt_vals):
qcol = QtGui.QColor.fromHslF( hue%1.0, sat, lgt )
color_list.append( qcol )
name = f'Hue {hueA:0.2f}-{hueB:0.2f}'
return ColorMap( None, color_list, name=name )
def makeMonochrome(color='neutral'):
"""
Returns a ColorMap object with a dark to bright ramp and adjustable tint.
@ -257,11 +307,10 @@ def makeMonochrome(color='green'):
h_val, s_val, l_min, l_max = color
else:
raise ValueError(f"Invalid color descriptor '{color}'")
l_vals = np.linspace(l_min, l_max, num=10)
l_vals = np.linspace(l_min, l_max, num=16)
color_list = []
for l_val in l_vals:
qcol = QtGui.QColor()
qcol.setHslF( h_val, s_val, l_val )
qcol = QtGui.QColor.fromHslF( h_val, s_val, l_val )
color_list.append( qcol )
return ColorMap( None, color_list, name=name, linearize=True )
@ -282,7 +331,7 @@ def modulatedBarData(length=768, width=32):
data = np.zeros( (length, width) )
for idx in range(width):
data[:,idx] = gradient + (idx/(width-1)) * modulation
np.clip(data, 0.0, 1.0)
clip_array(data, 0.0, 1.0, out=data)
return data
class ColorMap(object):
@ -389,8 +438,9 @@ class ColorMap(object):
if mapping in [self.CLIP, self.REPEAT, self.DIVERGING, self.MIRROR]:
self.mapping_mode = mapping # only allow defined values
else:
raise ValueError("Undefined mapping type '{:s}'".format(str(mapping)) )
raise ValueError(f"Undefined mapping type '{mapping}'")
self.stopsCache = {}
def __str__(self):
""" provide human-readable identifier """
if self.name is None:
@ -427,6 +477,73 @@ class ColorMap(object):
self.pos = 1.0 - np.flip( self.pos )
self.color = np.flip( self.color, axis=0 )
self.stopsCache = {}
def getSubset(self, start, span):
"""
Returns a new ColorMap object that extracts the subset specified by 'start' and 'length'
to the full 0.0 to 1.0 range. A negative length results in a color map that is reversed
relative to the original.
Parameters
----------
start : float (0.0 to 1.0)
Starting value that defines the 0.0 value of the new color map.
span : float (-1.0 to 1.0)
span of the extracted region. The orignal color map will be trated as cyclical
if the extracted interval exceeds the 0.0 to 1.0 range.
"""
pos, col = self.getStops( mode=ColorMap.FLOAT )
start = clip_scalar(start, 0.0, 1.0)
span = clip_scalar(span, -1.0, 1.0)
if span == 0.0:
raise ValueError("'length' needs to be non-zero")
stop = (start + span)
if stop > 1.0 or stop < 0.0: stop = stop % 1.0
# find indices *inside* range, start and end will be added by sampling later
if span > 0:
ref_pos = start # lowest position value at start
idxA = np.searchsorted( pos, start, side='right' )
idxB = np.searchsorted( pos, stop , side='left' ) # + 1 # right-side element of interval
wraps = bool( stop < start ) # wraps around?
else:
ref_pos = stop # lowest position value at stop
idxA = np.searchsorted( pos, stop , side='right')
idxB = np.searchsorted( pos, start, side='left' ) # + 1 # right-side element of interval
wraps = bool( stop > start ) # wraps around?
if wraps: # wraps around:
length1 = (len(pos)-idxA) # before wrap
length2 = idxB # after wrap
new_length = length1 + length2 + 2 # combined; plus edge elements
new_pos = np.zeros( new_length )
new_col = np.zeros( (new_length, 4) )
new_pos[ 1:length1+1] = (0 + pos[idxA:] - ref_pos) / span # starting point lie in 0 to 1 range
new_pos[length1+1:-1] = (1 + pos[:idxB] - ref_pos) / span # end point wrapped to -1 to 0 range
new_pos[length1] -= np.copysign(1e-6, span) # breaks degeneracy of shifted 0.0 and 1.0 values
new_col[ 1:length1+1] = col[idxA:]
new_col[length1+1:-1] = col[:idxB]
else: # does not wrap around:
new_length = (idxB - idxA) + 2 # two additional edge values will be added
new_pos = np.zeros( new_length )
new_col = np.zeros( (new_length, 4) )
new_pos[1:-1] = (pos[idxA:idxB] - ref_pos) / span
new_col[1:-1] = col[idxA:idxB]
if span < 0: # for reversed subsets, positions now progress 0 to -1 and need to be flipped
new_pos += 1.0
new_pos = np.flip( new_pos)
new_col = np.flip( new_col, axis=0 )
new_pos[ 0] = 0.0
new_col[ 0] = self.mapToFloat(start)
new_pos[-1] = 1.0
new_col[-1] = self.mapToFloat(stop)
cmap = ColorMap( pos=new_pos, color=255.*new_col )
cmap.name = f"{self.name}[{start:.2f}({span:+.2f})]"
return cmap
def map(self, data, mode=BYTE):
"""
@ -443,9 +560,9 @@ class ColorMap(object):
mode: str or int, optional
Determines return format:
- `ColorMap.BYTE` or 'byte': Colors are returned as 0-255 unsigned bytes. (default)
- `ColorMap.FLOAT` or 'float': Colors are returned as 0.0-1.0 floats.
- `ColorMap.QCOLOR` or 'qcolor': Colors are returned as QColor objects.
- `ColorMap.BYTE` or 'byte': Colors are returned as 0-255 unsigned bytes. (default)
- `ColorMap.FLOAT` or 'float': Colors are returned as 0.0-1.0 floats.
- `ColorMap.QCOLOR` or 'qcolor': Colors are returned as QColor objects.
Returns
-------
@ -462,7 +579,7 @@ class ColorMap(object):
mode = self.enumMap[mode.lower()]
if mode == self.QCOLOR:
pos, color = self.getStops(self.BYTE)
pos, color = self.getStops(self.FLOAT)
else:
pos, color = self.getStops(mode)
@ -487,9 +604,9 @@ class ColorMap(object):
# Convert to QColor if requested
if mode == self.QCOLOR:
if np.isscalar(data):
return QtGui.QColor(*interp)
return QtGui.QColor.fromRgbF(*interp)
else:
return [QtGui.QColor(*x) for x in interp]
return [QtGui.QColor.fromRgbF(*x.tolist()) for x in interp]
else:
return interp
@ -516,6 +633,9 @@ class ColorMap(object):
When no parameters are given for `p1` and `p2`, the gradient is mapped to the
`y` coordinates 0 to 1, unless the color map is defined for a more limited range.
This is a somewhat expensive operation, and it is recommended to store and reuse the returned
gradient instead of repeatedly regenerating it.
Parameters
----------
@ -531,8 +651,7 @@ class ColorMap(object):
p2 = QtCore.QPointF(self.pos.max()-self.pos.min(),0)
grad = QtGui.QLinearGradient(p1, p2)
pos, color = self.getStops(mode=self.BYTE)
color = [QtGui.QColor(*x) for x in color]
pos, color = self.getStops(mode=self.QCOLOR)
if self.mapping_mode == self.MIRROR:
pos_n = (1. - np.flip(pos)) / 2
col_n = np.flip( color, axis=0 )
@ -550,20 +669,22 @@ class ColorMap(object):
Returns a QBrush painting with the color map applied over the selected span of plot values.
When the mapping mode is set to `ColorMap.MIRROR`, the selected span includes the color map twice,
first in reversed order and then normal.
It is recommended to store and reuse this gradient brush instead of regenerating it repeatedly.
Parameters
----------
span : tuple (min, max), default (0.0, 1.0)
Span of data values covered by the gradient:
- Color map value 0.0 will appear at `min`,
- Color map value 1.0 will appear at `max`.
- Color map value 0.0 will appear at `min`,
- Color map value 1.0 will appear at `max`.
orientation : str, default 'vertical'
Orientiation of the gradient:
- 'vertical': `span` corresponds to the `y` coordinate.
- 'horizontal': `span` corresponds to the `x` coordinate.
- 'vertical': `span` corresponds to the `y` coordinate.
- 'horizontal': `span` corresponds to the `x` coordinate.
"""
if orientation == 'vertical':
grad = self.getGradient( p1=QtCore.QPointF(0.,span[0]), p2=QtCore.QPointF(0.,span[1]) )
@ -576,20 +697,23 @@ class ColorMap(object):
def getPen(self, span=(0.,1.), orientation='vertical', width=1.0):
"""
Returns a QPen that draws according to the color map based on vertical or horizontal position.
It is recommended to store and reuse this gradient pen instead of regenerating it repeatedly.
Parameters
----------
span : tuple (min, max), default (0.0, 1.0)
Span of the data values covered by the gradient:
- Color map value 0.0 will appear at `min`.
- Color map value 1.0 will appear at `max`.
- Color map value 0.0 will appear at `min`.
- Color map value 1.0 will appear at `max`.
orientation : str, default 'vertical'
Orientiation of the gradient:
- 'vertical' creates a vertical gradient, where `span` corresponds to the `y` coordinate.
- 'horizontal' creates a horizontal gradient, where `span` correspnds to the `x` coordinate.
- 'vertical' creates a vertical gradient, where `span` corresponds to the `y` coordinate.
- 'horizontal' creates a horizontal gradient, where `span` correspnds to the `x` coordinate.
width : int or float
Width of the pen in pixels on screen.
@ -636,8 +760,10 @@ class ColorMap(object):
color = color.astype(float) / 255.
elif mode == self.QCOLOR:
if color.dtype.kind == 'f':
color = (color*255).astype(np.ubyte)
color = [QtGui.QColor(*x) for x in color]
factory = QtGui.QColor.fromRgbF
else:
factory = QtGui.QColor.fromRgb
color = [factory(*x.tolist()) for x in color]
self.stopsCache[mode] = (self.pos, color)
return self.stopsCache[mode]

View File

@ -0,0 +1,13 @@
; PyQtGraph's "relaxed" plot color palette
; This is the darker variant for plotting on a light background ("light mode")
;
#f97f10 ; orange
#e5bb00 ; yellow
#94ab00 ; grass
#12a12a ; green
#007c8c ; sea
#0e56c2 ; blue
#813be3 ; indigo
#c01188 ; purple
#e23512 ; red
#f97f10 ; orange

View File

@ -0,0 +1,13 @@
; PyQtGraph's "relaxed" plot color palette
; This is the brighter variant for plotting on a dark background ("dark mode")
;
#ff9d47 ; orange
#f7e100 ; yellow
#b3cf00 ; grass
#1ec23a ; green
#00a0b5 ; sea
#1f78ff ; blue
#a54dff ; indigo
#e22ca8 ; purple
#ff532b ; red
#ff9d47 ; orange

View File

@ -14,7 +14,6 @@ import numpy
from collections import OrderedDict
import tempfile
from . import units
from .python2_3 import asUnicode, basestring
from .Qt import QtCore
from .Point import Point
from .colormap import ColorMap
@ -40,11 +39,11 @@ class ParseError(Exception):
def writeConfigFile(data, fname):
s = genString(data)
with open(fname, 'w') as fd:
with open(fname, 'wt') as fd:
fd.write(s)
def readConfigFile(fname):
def readConfigFile(fname, **scope):
#cwd = os.getcwd()
global GLOBAL_PATH
if GLOBAL_PATH is not None:
@ -53,14 +52,29 @@ def readConfigFile(fname):
fname = fname2
GLOBAL_PATH = os.path.dirname(os.path.abspath(fname))
local = {**scope, **units.allUnits}
local['OrderedDict'] = OrderedDict
local['readConfigFile'] = readConfigFile
local['Point'] = Point
local['QtCore'] = QtCore
local['ColorMap'] = ColorMap
local['datetime'] = datetime
# Needed for reconstructing numpy arrays
local['array'] = numpy.array
for dtype in ['int8', 'uint8',
'int16', 'uint16', 'float16',
'int32', 'uint32', 'float32',
'int64', 'uint64', 'float64']:
local[dtype] = getattr(numpy, dtype)
try:
#os.chdir(newDir) ## bad.
with open(fname) as fd:
s = asUnicode(fd.read())
with open(fname, "rt") as fd:
s = fd.read()
s = s.replace("\r\n", "\n")
s = s.replace("\r", "\n")
data = parseString(s)[1]
data = parseString(s, **local)[1]
except ParseError:
sys.exc_info()[1].fileName = fname
raise
@ -73,7 +87,7 @@ def readConfigFile(fname):
def appendConfigFile(data, fname):
s = genString(data)
with open(fname, 'a') as fd:
with open(fname, 'at') as fd:
fd.write(s)
@ -94,10 +108,10 @@ def genString(data, indent=''):
s += indent + sk + ': ' + repr(data[k]).replace("\n", "\\\n") + '\n'
return s
def parseString(lines, start=0):
def parseString(lines, start=0, **scope):
data = OrderedDict()
if isinstance(lines, basestring):
if isinstance(lines, str):
lines = lines.replace("\\\n", "")
lines = lines.split('\n')
lines = [l for l in lines if re.search(r'\S', l) and not re.match(r'\s*#', l)] ## remove empty lines
@ -136,33 +150,19 @@ def parseString(lines, start=0):
v = v.strip()
## set up local variables to use for eval
local = units.allUnits.copy()
local['OrderedDict'] = OrderedDict
local['readConfigFile'] = readConfigFile
local['Point'] = Point
local['QtCore'] = QtCore
local['ColorMap'] = ColorMap
local['datetime'] = datetime
# Needed for reconstructing numpy arrays
local['array'] = numpy.array
for dtype in ['int8', 'uint8',
'int16', 'uint16', 'float16',
'int32', 'uint32', 'float32',
'int64', 'uint64', 'float64']:
local[dtype] = getattr(numpy, dtype)
if len(k) < 1:
raise ParseError('Missing name preceding colon', ln+1, l)
if k[0] == '(' and k[-1] == ')': ## If the key looks like a tuple, try evaluating it.
try:
k1 = eval(k, local)
k1 = eval(k, scope)
if type(k1) is tuple:
k = k1
except:
# If tuple conversion fails, keep the string
pass
if re.search(r'\S', v) and v[0] != '#': ## eval the value
try:
val = eval(v, local)
val = eval(v, scope)
except:
ex = sys.exc_info()[1]
raise ParseError("Error evaluating expression '%s': [%s: %s]" % (v, ex.__class__.__name__, str(ex)), (ln+1), l)
@ -172,7 +172,7 @@ def parseString(lines, start=0):
val = {}
else:
#print "Going deeper..", ln+1
(ln, val) = parseString(lines, start=ln+1)
(ln, val) = parseString(lines, start=ln+1, **scope)
data[k] = val
#print k, repr(val)
except ParseError:

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from ..Qt import QtCore, QtGui
from ..python2_3 import asUnicode
class CmdInput(QtGui.QLineEdit):
@ -25,10 +25,10 @@ class CmdInput(QtGui.QLineEdit):
self.execCmd()
else:
super().keyPressEvent(ev)
self.history[0] = asUnicode(self.text())
self.history[0] = self.text()
def execCmd(self):
cmd = asUnicode(self.text())
cmd = self.text()
if len(self.history) == 1 or cmd != self.history[1]:
self.history.insert(1, cmd)
self.history[0] = ""

View File

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
import sys, re, os, time, traceback, subprocess
import sys, re, traceback, subprocess
import pickle
from ..Qt import QtCore, QtGui, QT_LIB
from ..python2_3 import basestring
from .. import exceptionHandling as exceptionHandling
from .. import getConfigOption
from ..functions import SignalBlock
@ -17,18 +16,18 @@ class ConsoleWidget(QtGui.QWidget):
Widget displaying console output and accepting command input.
Implements:
- eval python expressions / exec python statements
- storable history of commands
- exception handling allowing commands to be interpreted in the context of any level in the exception stack frame
- eval python expressions / exec python statements
- storable history of commands
- exception handling allowing commands to be interpreted in the context of any level in the exception stack frame
Why not just use python in an interactive shell (or ipython) ? There are a few reasons:
- pyside does not yet allow Qt event processing and interactive shell at the same time
- on some systems, typing in the console _blocks_ the qt event loop until the user presses enter. This can
be baffling and frustrating to users since it would appear the program has frozen.
- some terminals (eg windows cmd.exe) have notoriously unfriendly interfaces
- ability to add extra features like exception stack introspection
- ability to have multiple interactive prompts, including for spawned sub-processes
- pyside does not yet allow Qt event processing and interactive shell at the same time
- on some systems, typing in the console _blocks_ the qt event loop until the user presses enter. This can
be baffling and frustrating to users since it would appear the program has frozen.
- some terminals (eg windows cmd.exe) have notoriously unfriendly interfaces
- ability to add extra features like exception stack introspection
- ability to have multiple interactive prompts, including for spawned sub-processes
"""
_threadException = QtCore.Signal(object)
@ -454,7 +453,7 @@ class ConsoleWidget(QtGui.QWidget):
if filterStr != '':
if isinstance(exc, Exception):
msg = traceback.format_exception_only(type(exc), exc)
elif isinstance(exc, basestring):
elif isinstance(exc, str):
msg = exc
else:
msg = repr(exc)

View File

@ -1 +1 @@
from .Console import ConsoleWidget
from .Console import ConsoleWidget

View File

@ -9,8 +9,9 @@ Distributed under MIT/X11 license. See license.txt for more information.
from __future__ import print_function
import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile, threading
import contextlib
import warnings
from . import ptime
from time import perf_counter
from numpy import ndarray
from .Qt import QtCore, QT_LIB
from .util import cprint
@ -21,6 +22,17 @@ if sys.version.startswith("3.8") and QT_LIB == "PySide2":
from .util.mutex import Mutex
# credit to Wolph: https://stackoverflow.com/a/17603000
@contextlib.contextmanager
def open_maybe_console(filename=None):
fh = sys.stdout if filename is None else open(filename, "w", encoding='utf-8')
try:
yield fh
finally:
if fh is not sys.stdout:
fh.close()
__ftraceDepth = 0
def ftrace(func):
"""Decorator used for marking the beginning and end of function calls.
@ -167,13 +179,15 @@ def listObjs(regex='Q', typ=None):
def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ignore=None):
def findRefPath(startObj, endObj, maxLen=8, restart=True, seen=None, path=None, ignore=None):
"""Determine all paths of object references from startObj to endObj"""
refs = []
if path is None:
path = [endObj]
if ignore is None:
ignore = {}
if seen is None:
seen = {}
ignore[id(sys._getframe())] = None
ignore[id(path)] = None
ignore[id(seen)] = None
@ -529,7 +543,7 @@ class Profiler(object):
obj._delayed = delayed
obj._markCount = 0
obj._finished = False
obj._firstTime = obj._lastTime = ptime.time()
obj._firstTime = obj._lastTime = perf_counter()
obj._newMsg("> Entering " + obj._name)
return obj
@ -541,7 +555,7 @@ class Profiler(object):
if msg is None:
msg = str(self._markCount)
self._markCount += 1
newTime = ptime.time()
newTime = perf_counter()
self._newMsg(" %s: %0.4f ms",
msg, (newTime - self._lastTime) * 1000)
self._lastTime = newTime
@ -569,7 +583,7 @@ class Profiler(object):
if msg is not None:
self(msg)
self._newMsg("< Exiting %s, total time: %0.4f ms",
self._name, (ptime.time() - self._firstTime) * 1000)
self._name, (perf_counter() - self._firstTime) * 1000)
type(self)._depth -= 1
if self._depth < 1:
self.flush()
@ -582,6 +596,7 @@ class Profiler(object):
def profile(code, name='profile_run', sort='cumulative', num=30):
"""Common-use for cProfile"""
import pstats
cProfile.run(code, name)
stats = pstats.Stats(name)
stats.sort_stats(sort)
@ -800,7 +815,7 @@ class ObjTracker(object):
continue
try:
ref = weakref.ref(obj)
ref = weakref.ref(o)
except:
ref = None
refs[oid] = ref
@ -1132,10 +1147,11 @@ class ThreadTrace(object):
Used to debug freezing by starting a new thread that reports on the
location of other threads periodically.
"""
def __init__(self, interval=10.0):
def __init__(self, interval=10.0, logFile=None):
self.interval = interval
self.lock = Mutex()
self._stop = False
self.logFile = logFile
self.start()
def stop(self):
@ -1151,35 +1167,41 @@ class ThreadTrace(object):
self.thread.start()
def run(self):
while True:
with self.lock:
if self._stop is True:
return
print("\n============= THREAD FRAMES: ================")
for id, frame in sys._current_frames().items():
if id == threading.current_thread().ident:
continue
iter = 0
with open_maybe_console(self.logFile) as printFile:
while True:
with self.lock:
if self._stop is True:
return
# try to determine a thread name
try:
name = threading._active.get(id, None)
except:
name = None
if name is None:
printFile.write(f"\n============= THREAD FRAMES {iter}: ================\n")
for id, frame in sys._current_frames().items():
if id == threading.current_thread().ident:
continue
# try to determine a thread name
try:
# QThread._names must be manually set by thread creators.
name = QtCore.QThread._names.get(id)
name = threading._active.get(id, None)
except:
name = None
if name is None:
name = "???"
if name is None:
try:
# QThread._names must be manually set by thread creators.
name = QtCore.QThread._names.get(id)
except:
name = None
if name is None:
name = "???"
print("<< thread %d \"%s\" >>" % (id, name))
traceback.print_stack(frame)
print("===============================================\n")
time.sleep(self.interval)
printFile.write("<< thread %d \"%s\" >>\n" % (id, name))
tb = str(''.join(traceback.format_stack(frame)))
printFile.write(tb)
printFile.write("\n")
printFile.write("===============================================\n\n")
printFile.flush()
iter += 1
time.sleep(self.interval)
class ThreadColor(object):
@ -1229,4 +1251,3 @@ def enableFaulthandler():
return True
except ImportError:
return False

View File

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
from ..Qt import QtCore, QtGui, QtWidgets
import weakref
from .Dock import Dock
class Container(object):
#sigStretchChanged = QtCore.Signal() ## can't do this here; not a QObject.
@ -245,7 +247,7 @@ class TContainer(Container, QtGui.QWidget):
def _insertItem(self, item, index):
if not isinstance(item, Dock.Dock):
if not isinstance(item, Dock):
raise Exception("Tab containers may hold only docks, not other containers.")
self.stack.insertWidget(index, item)
self.hTabLayout.insertWidget(index, item.label)
@ -288,5 +290,3 @@ class TContainer(Container, QtGui.QWidget):
x = max(x, wx)
y = max(y, wy)
self.setStretch(x, y)
from . import Dock

View File

@ -3,7 +3,6 @@ from ..Qt import QtCore, QtGui
from .DockDrop import *
from ..widgets.VerticalLabel import VerticalLabel
from ..python2_3 import asUnicode
class Dock(QtGui.QWidget, DockDrop):
@ -125,7 +124,7 @@ class Dock(QtGui.QWidget, DockDrop):
"""
Gets the text displayed in the title bar for this dock.
"""
return asUnicode(self.label.text())
return self.label.text()
def setTitle(self, text):
"""

View File

@ -1,11 +1,9 @@
# -*- coding: utf-8 -*-
import weakref
from ..Qt import QtCore, QtGui
from ..Qt import QtGui
from .Container import *
from .DockDrop import *
from .Dock import Dock
from .. import debug as debug
from ..python2_3 import basestring
class DockArea(Container, QtGui.QWidget, DockDrop):
@ -62,7 +60,7 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
container = self.topContainer
neighbor = None
else:
if isinstance(relativeTo, basestring):
if isinstance(relativeTo, str):
relativeTo = self.docks[relativeTo]
container = self.getContainer(relativeTo)
if container is None:

View File

@ -1,2 +1,2 @@
from .DockArea import DockArea
from .Dock import Dock
from .Dock import Dock

View File

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
from ..Qt import QtGui, QtCore
from ..Qt import QtCore
from .Exporter import Exporter
from ..parametertree import Parameter
from .. import PlotItem
from ..python2_3 import asUnicode
translate = QtCore.QCoreApplication.translate
@ -16,9 +15,9 @@ class CSVExporter(Exporter):
def __init__(self, item):
Exporter.__init__(self, item)
self.params = Parameter(name='params', type='group', children=[
{'name': 'separator', 'title': translate("Exporter", 'separator'), 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']},
{'name': 'separator', 'title': translate("Exporter", 'separator'), 'type': 'list', 'value': 'comma', 'limits': ['comma', 'tab']},
{'name': 'precision', 'title': translate("Exporter", 'precision'), 'type': 'int', 'value': 10, 'limits': [0, None]},
{'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']}
{'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list', 'limits': ['(x,y) per plot', '(x,y,y,y) for all plots']}
])
def parameters(self):
@ -60,7 +59,7 @@ class CSVExporter(Exporter):
sep = '\t'
with open(fileName, 'w') as fd:
fd.write(sep.join(map(asUnicode, header)) + '\n')
fd.write(sep.join(map(str, header)) + '\n')
i = 0
numFormat = '%%0.%dg' % self.params['precision']
numRows = max([len(d[0]) for d in data])

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from ..widgets.FileDialog import FileDialog
from ..Qt import QtGui, QtCore, QtSvg
from ..python2_3 import asUnicode, basestring
from ..Qt import QtGui, QtCore
from ..GraphicsScene import GraphicsScene
import os, re
LastExportDirectory = None
@ -48,7 +48,7 @@ class Exporter(object):
self.fileDialog.setFileMode(QtGui.QFileDialog.FileMode.AnyFile)
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptMode.AcceptSave)
if filter is not None:
if isinstance(filter, basestring):
if isinstance(filter, str):
self.fileDialog.setNameFilter(filter)
elif isinstance(filter, list):
self.fileDialog.setNameFilters(filter)
@ -62,13 +62,12 @@ class Exporter(object):
return
def fileSaveFinished(self, fileName):
fileName = asUnicode(fileName)
global LastExportDirectory
LastExportDirectory = os.path.split(fileName)[0]
## If file name does not match selected extension, append it now
ext = os.path.splitext(fileName)[1].lower().lstrip('.')
selectedExt = re.search(r'\*\.(\w+)\b', asUnicode(self.fileDialog.selectedNameFilter()))
selectedExt = re.search(r'\*\.(\w+)\b', self.fileDialog.selectedNameFilter())
if selectedExt is not None:
selectedExt = selectedExt.groups()[0].lower()
if ext != selectedExt:

View File

@ -1,4 +1,5 @@
from ..Qt import QtGui, QtCore
# -*- coding: utf-8 -*-
from ..Qt import QtCore
from .Exporter import Exporter
from ..parametertree import Parameter
from .. import PlotItem
@ -25,7 +26,7 @@ class HDF5Exporter(Exporter):
self.params = Parameter(name='params', type='group', children=[
{'name': 'Name', 'title': translate("Exporter", 'Name'), 'type': 'str', 'value': 'Export', },
{'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list',
'values': ['(x,y) per plot', '(x,y,y,y) for all plots']},
'limits': ['(x,y) per plot', '(x,y,y,y) for all plots']},
])
def parameters(self):

View File

@ -98,7 +98,7 @@ class ImageExporter(Exporter):
painter.end()
if self.params['invertValue']:
bg = fn.qimage_to_ndarray(self.png)
bg = fn.ndarray_from_qimage(self.png)
if sys.byteorder == 'little':
cv = slice(0, 3)
else:

View File

@ -95,19 +95,19 @@ class MatplotlibExporter(Exporter):
linestyle = ''
else:
linestyle = '-'
color = tuple([c/255. for c in fn.colorTuple(pen.color())])
color = pen.color().getRgbF()
symbol = opts['symbol']
if symbol == 't':
symbol = '^'
symbolPen = fn.mkPen(opts['symbolPen'])
symbolBrush = fn.mkBrush(opts['symbolBrush'])
markeredgecolor = tuple([c/255. for c in fn.colorTuple(symbolPen.color())])
markerfacecolor = tuple([c/255. for c in fn.colorTuple(symbolBrush.color())])
markeredgecolor = symbolPen.color().getRgbF()
markerfacecolor = symbolBrush.color().getRgbF()
markersize = opts['symbolSize']
if opts['fillLevel'] is not None and opts['fillBrush'] is not None:
fillBrush = fn.mkBrush(opts['fillBrush'])
fillcolor = tuple([c/255. for c in fn.colorTuple(fillBrush.color())])
fillcolor = fillBrush.color().getRgbF()
ax.fill_between(x=x, y1=y, y2=opts['fillLevel'], facecolor=fillcolor)
pl = ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(),

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from .Exporter import Exporter
from ..parametertree import Parameter
from ..Qt import QtGui, QtCore, QtSvg
import re
from ..Qt import QtGui, QtCore
translate = QtCore.QCoreApplication.translate

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from .Exporter import Exporter
from ..python2_3 import asUnicode
from ..parametertree import Parameter
from ..Qt import QtGui, QtCore, QtSvg, QT_LIB
from ..Qt import QtGui, QtCore, QtSvg
from .. import debug
from .. import functions as fn
import re
@ -78,7 +78,7 @@ class SVGExporter(Exporter):
QtGui.QApplication.clipboard().setMimeData(md)
else:
with open(fileName, 'wb') as fh:
fh.write(asUnicode(xml).encode('utf-8'))
fh.write(str(xml).encode('utf-8'))
# Includes space for extra attributes
xmlHeader = """\
@ -119,7 +119,7 @@ def generateSvg(item, options={}):
defsXml += "</defs>\n"
svgAttributes = ' viewBox ="0 0 %f %f"' % (options["width"], options["height"])
c = options['background']
backgroundtag = '<rect width="100%%" height="100%%" style="fill:rgba(%f, %f, %f, %d)" />\n' % (c.red(), c.blue(), c.green(), c.alpha()/255.0)
backgroundtag = '<rect width="100%%" height="100%%" style="fill:rgba(%d, %d, %d, %f)" />\n' % (c.red(), c.green(), c.blue(), c.alphaF())
return (xmlHeader % svgAttributes) + backgroundtag + defsXml + node.toprettyxml(indent=' ') + "\n</svg>\n"

View File

@ -17,7 +17,6 @@ from .. import configfile as configfile
from .. import dockarea as dockarea
from . import FlowchartGraphicsView
from .. import functions as fn
from ..python2_3 import asUnicode
def strDict(d):
return dict([(str(k), v) for k, v in d.items()])
@ -512,7 +511,6 @@ class Flowchart(Node):
self.fileDialog.fileSelected.connect(self.loadFile)
return
## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs..
fileName = asUnicode(fileName)
state = configfile.readConfigFile(fileName)
self.restoreState(state, clear=True)
self.viewBox.autoRange()
@ -532,7 +530,6 @@ class Flowchart(Node):
self.fileDialog.show()
self.fileDialog.fileSelected.connect(self.saveFile)
return
fileName = asUnicode(fileName)
configfile.writeConfigFile(self.saveState(), fileName)
self.sigFileSaved.emit(fileName)
@ -653,7 +650,7 @@ class FlowchartCtrlWidget(QtGui.QWidget):
#self.setCurrentFile(newFile)
def fileSaved(self, fileName):
self.setCurrentFile(asUnicode(fileName))
self.setCurrentFile(fileName)
self.ui.saveBtn.success("Saved.")
def saveClicked(self):
@ -682,7 +679,7 @@ class FlowchartCtrlWidget(QtGui.QWidget):
#self.setCurrentFile(newFile)
def setCurrentFile(self, fileName):
self.currentFileName = asUnicode(fileName)
self.currentFileName = fileName
if fileName is None:
self.ui.fileNameLabel.setText("<b>[ new ]</b>")
else:

View File

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
from ..Qt import QtGui, QtCore
from ..widgets.GraphicsView import GraphicsView
from ..GraphicsScene import GraphicsScene
from ..graphicsItems.ViewBox import ViewBox
translate = QtCore.QCoreApplication.translate

View File

@ -5,7 +5,6 @@ from .. import functions as fn
from .Terminal import *
from collections import OrderedDict
from ..debug import *
import numpy as np
import warnings
translate = QtCore.QCoreApplication.translate
@ -18,7 +17,7 @@ class Node(QtCore.QObject):
Node represents the basic processing unit of a flowchart.
A Node subclass implements at least:
1) A list of input / ouptut terminals and their properties
1) A list of input / output terminals and their properties
2) a process() function which takes the names of input terminals as keyword arguments and returns a dict with the names of output terminals as keys.
A flowchart thus consists of multiple instances of Node subclasses, each of which is connected
@ -505,7 +504,7 @@ class NodeGraphicsItem(GraphicsObject):
#GraphicsObject.setZValue(self, z)
def labelChanged(self):
newName = str(self.nameItem.toPlainText())
newName = self.nameItem.toPlainText()
if newName != self.node.name():
self.node.rename(newName)

View File

@ -311,7 +311,7 @@ class TerminalGraphicsItem(GraphicsObject):
self.menu = None
def labelChanged(self):
newName = str(self.label.toPlainText())
newName = self.label.toPlainText()
if newName != self.term.name():
self.term.rename(newName)

View File

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
from .Flowchart import *
from .library import getNodeType, registerNodeType, getNodeTree
from .library import getNodeType, registerNodeType, getNodeTree

View File

@ -4,8 +4,6 @@ from ...Qt import QtGui, QtCore, QtWidgets
import numpy as np
import sys
from .common import *
from ...SRTTransform import SRTTransform
from ...Point import Point
from ...widgets.TreeWidget import TreeWidget
from ...graphicsItems.LinearRegionItem import LinearRegionItem
@ -179,7 +177,7 @@ class TextEdit(QtWidgets.QTextEdit):
self.lastText = None
def focusOutEvent(self, ev):
text = str(self.toPlainText())
text = self.toPlainText()
if text != self.lastText:
self.lastText = text
self.on_update()
@ -236,26 +234,23 @@ class EvalNode(Node):
l.update(args)
## try eval first, then exec
try:
text = str(self.text.toPlainText()).replace('\n', ' ')
text = self.text.toPlainText().replace('\n', ' ')
output = eval(text, globals(), l)
except SyntaxError:
fn = "def fn(**args):\n"
run = "\noutput=fn(**args)\n"
text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run
if sys.version_info.major == 2:
exec(text)
elif sys.version_info.major == 3:
ldict = locals()
exec(text, globals(), ldict)
output = ldict['output']
text = fn + "\n".join([" "+l for l in self.text.toPlainText().split('\n')]) + run
ldict = locals()
exec(text, globals(), ldict)
output = ldict['output']
except:
print("Error processing node: %s" % self.name())
print(f"Error processing node: {self.name()}")
raise
return output
def saveState(self):
state = Node.saveState(self)
state['text'] = str(self.text.toPlainText())
state['text'] = self.text.toPlainText()
#state['terminals'] = self.saveTerminals()
return state
@ -481,4 +476,3 @@ class AsType(CtrlNode):
def processData(self, data):
s = self.stateGroup.state()
return data.astype(s['dtype'])

View File

@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
from ..Node import Node
import weakref
from ...Qt import QtCore, QtGui
from ...graphicsItems.ScatterPlotItem import ScatterPlotItem
from ...graphicsItems.PlotCurveItem import PlotCurveItem
from ... import PlotDataItem, ComboBox
from .common import *

View File

@ -1,11 +1,8 @@
# -*- coding: utf-8 -*-
import numpy as np
from ...Qt import QtCore, QtGui
from ..Node import Node
from . import functions
from ... import functions as pgfn
from .common import *
from ...python2_3 import xrange
from ... import PolyLineROI
from ... import Point
from ... import metaarray as metaarray
@ -322,7 +319,7 @@ class RemovePeriodic(CtrlNode):
## flatten spikes at f0 and harmonics
f0 = self.ctrls['f0'].value()
for i in xrange(1, self.ctrls['harmonics'].value()+2):
for i in range(1, self.ctrls['harmonics'].value()+2):
f = f0 * i # target frequency
## determine index range to check for this frequency

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
import numpy as np
from ...metaarray import MetaArray
from ...python2_3 import basestring, xrange
def downsample(data, n, axis=0, xvals='subsample'):
@ -307,7 +307,7 @@ def suggestDType(x):
return float
elif isinstance(x, int):
return int
#elif isinstance(x, basestring): ## don't try to guess correct string length; use object instead.
#elif isinstance(x, str): ## don't try to guess correct string length; use object instead.
#return '<U%d' % len(x)
else:
return object
@ -330,7 +330,7 @@ def removePeriodic(data, f0=60.0, dt=None, harmonics=10, samples=4):
freqs = np.linspace(0.0, (len(ft)-1) * df, len(ft))
## flatten spikes at f0 and harmonics
for i in xrange(1, harmonics + 2):
for i in range(1, harmonics + 2):
f = f0 * i # target frequency
## determine index range to check for this frequency
@ -354,4 +354,4 @@ def removePeriodic(data, f0=60.0, dt=None, harmonics=10, samples=4):
return data2

View File

@ -7,7 +7,6 @@ Distributed under MIT/X11 license. See license.txt for more information.
from __future__ import division
import ctypes
import decimal
import re
import struct
@ -24,7 +23,6 @@ from .Qt import QtGui, QtCore, QT_LIB, QtVersion
from . import Qt
from .metaarray import MetaArray
from collections import OrderedDict
from .python2_3 import asUnicode, basestring
# in order of appearance in this file.
# add new functions to this list only if they are to reside in pg namespace.
@ -42,7 +40,7 @@ __all__ = [
'makeRGBA', 'makeARGB',
# 'try_fastpath_argb', 'ndarray_to_qimage',
'makeQImage',
# 'qimage_to_ndarray',
# 'ndarray_from_qimage',
'imageToArray', 'colorToAlpha',
'gaussianFilter', 'downsample', 'arrayToQPath',
# 'ndarray_from_qpolygonf', 'create_qpolygonf', 'arrayToQPolygonF',
@ -65,7 +63,7 @@ Colors = {
's': QtGui.QColor(100,100,150,255),
}
SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY')
SI_PREFIXES = 'yzafpnµm kMGTPEZY'
SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY'
SI_PREFIX_EXPONENTS = dict([(SI_PREFIXES[i], (i-8)*3) for i in range(len(SI_PREFIXES))])
SI_PREFIX_EXPONENTS['u'] = -6
@ -132,7 +130,7 @@ def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, al
return fmt % (x*p, pref, suffix)
else:
if allowUnicode:
plusminus = space + asUnicode("±") + space
plusminus = space + "±" + space
else:
plusminus = " +/- "
fmt = "%." + str(precision) + "g%s%s%s%s"
@ -164,7 +162,6 @@ def siParse(s, regex=FLOAT_REGEX, suffix=None):
contains a suffix, it is discarded. This enables interpreting
characters following the numerical value as an SI prefix.
"""
s = asUnicode(s)
s = s.strip()
if suffix is not None and len(suffix) > 0:
if s[-len(suffix):] != suffix:
@ -227,7 +224,7 @@ class Color(QtGui.QColor):
def glColor(self):
"""Return (r,g,b,a) normalized for use in opengl"""
return (self.red()/255., self.green()/255., self.blue()/255., self.alpha()/255.)
return self.getRgbF()
def __getitem__(self, ind):
return (self.red, self.green, self.blue, self.alpha)[ind]()
@ -254,13 +251,23 @@ def mkColor(*args):
"""
err = 'Not sure how to make a color from "%s"' % str(args)
if len(args) == 1:
if isinstance(args[0], basestring):
if isinstance(args[0], str):
c = args[0]
if len(c) == 1:
try:
return Colors[c]
except KeyError:
raise ValueError('No color named "%s"' % c)
have_alpha = len(c) in [5, 9] and c[0] == '#' # "#RGBA" and "#RRGGBBAA"
if not have_alpha:
# try parsing SVG named colors, including "#RGB" and "#RRGGBB".
# note that QColor.setNamedColor() treats a 9-char hex string as "#AARRGGBB".
qcol = QtGui.QColor()
qcol.setNamedColor(c)
if qcol.isValid():
return qcol
# on failure, fallback to pyqtgraph parsing
# this includes the deprecated case of non-#-prefixed hex strings
if c[0] == '#':
c = c[1:]
else:
@ -393,9 +400,7 @@ def mkPen(*args, **kargs):
def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0):
"""Generate a QColor from HSVa values. (all arguments are float 0.0-1.0)"""
c = QtGui.QColor()
c.setHsvF(hue, sat, val, alpha)
return c
return QtGui.QColor.fromHsvF(hue, sat, val, alpha)
# Matrices and math taken from "CIELab Color Space" by Gernot Hoffmann
# http://docs-hoffmann.de/cielab03022003.pdf
@ -478,10 +483,7 @@ def CIELabColor(L, a, b, alpha=1.0):
else:
arr_sRGB[idx] = 12.92 * val # (s)
arr_sRGB = clip_array( arr_sRGB, 0.0, 1.0 ) # avoid QColor errors
qcol = QtGui.QColor()
qcol.setRgbF( *arr_sRGB )
if alpha < 1.0: qcol.setAlpha(alpha)
return qcol
return QtGui.QColor.fromRgbF( *arr_sRGB, alpha )
def colorCIELab(qcol):
"""
@ -558,7 +560,7 @@ def colorDistance(colors, metric='CIE76'):
def colorTuple(c):
"""Return a tuple (R,G,B,A) from a QColor"""
return (c.red(), c.green(), c.blue(), c.alpha())
return c.getRgb()
def colorStr(c):
"""Generate a hex string code from a QColor"""
@ -584,10 +586,7 @@ def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, mi
v = maxValue
h = minHue + (indh * (maxHue-minHue)) // hues
c = QtGui.QColor()
c.setHsv(h, sat, v)
c.setAlpha(alpha)
return c
return QtGui.QColor.fromHsv(h, sat, v, alpha)
def glColor(*args, **kargs):
@ -596,7 +595,7 @@ def glColor(*args, **kargs):
Accepts same arguments as :func:`mkColor <pyqtgraph.mkColor>`.
"""
c = mkColor(*args, **kargs)
return (c.red()/255., c.green()/255., c.blue()/255., c.alpha()/255.)
return c.getRgbF()
@ -722,7 +721,7 @@ def eq(a, b):
else:
return e.all()
else:
raise Exception("== operator returned type %s" % str(type(e)))
raise TypeError("== operator returned type %s" % str(type(e)))
def affineSliceCoords(shape, origin, vectors, axes):
@ -1124,7 +1123,10 @@ def transformCoordinates(tr, coords, transpose=False):
m = m[:, :-1]
## map coordinates and return
mapped = (m*coords).sum(axis=1) ## apply scale/rotate
# nan or inf points will not plot, but should not generate warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", RuntimeWarning)
mapped = (m*coords).sum(axis=1) ## apply scale/rotate
mapped += translate
if transpose:
@ -1682,24 +1684,28 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True):
return ndarray_to_qimage(imgData, imgFormat)
def qimage_to_ndarray(qimg):
def ndarray_from_qimage(qimg):
img_ptr = qimg.bits()
if hasattr(img_ptr, 'setsize'): # PyQt sip.voidptr
if img_ptr is None:
raise ValueError("Null QImage not supported")
h, w = qimg.height(), qimg.width()
bpl = qimg.bytesPerLine()
depth = qimg.depth()
logical_bpl = w * depth // 8
if QT_LIB.startswith('PyQt'):
# sizeInBytes() was introduced in Qt 5.10
# however PyQt5 5.12 will fail with:
# "TypeError: QImage.sizeInBytes() is a private method"
# note that sizeInBytes() works fine with:
# PyQt5 5.15, PySide2 5.12, PySide2 5.15
try:
# 64-bits size
nbytes = qimg.sizeInBytes()
except (TypeError, AttributeError):
# 32-bits size
nbytes = qimg.byteCount()
img_ptr.setsize(nbytes)
img_ptr.setsize(h * bpl)
memory = np.frombuffer(img_ptr, dtype=np.ubyte).reshape((h, bpl))
memory = memory[:, :logical_bpl]
depth = qimg.depth()
if depth in (8, 24, 32):
dtype = np.uint8
nchan = depth // 8
@ -1708,10 +1714,12 @@ def qimage_to_ndarray(qimg):
nchan = depth // 16
else:
raise ValueError("Unsupported Image Type")
shape = qimg.height(), qimg.width()
shape = h, w
if nchan != 1:
shape = shape + (nchan,)
return np.frombuffer(img_ptr, dtype=dtype).reshape(shape)
arr = memory.view(dtype).reshape(shape)
return arr
def imageToArray(img, copy=False, transpose=True):
@ -1721,7 +1729,7 @@ def imageToArray(img, copy=False, transpose=True):
the QImage is collected before the array, there may be trouble).
The array will have shape (width, height, (b,g,r,a)).
"""
arr = qimage_to_ndarray(img)
arr = ndarray_from_qimage(img)
fmt = img.format()
if fmt == img.Format.Format_RGB32:
@ -1865,6 +1873,156 @@ def downsample(data, n, axis=0, xvals='subsample'):
return MetaArray(d2, info=info)
def _compute_backfill_indices(isfinite):
# the presence of inf/nans result in an empty QPainterPath being generated
# this behavior started in Qt 5.12.3 and was introduced in this commit
# https://github.com/qt/qtbase/commit/c04bd30de072793faee5166cff866a4c4e0a9dd7
# We therefore replace non-finite values
# credit: Divakar https://stackoverflow.com/a/41191127/643629
mask = ~isfinite
idx = np.arange(len(isfinite))
idx[mask] = -1
np.maximum.accumulate(idx, out=idx)
first = np.searchsorted(idx, 0)
if first < len(isfinite):
# Replace all non-finite entries from beginning of arr with the first finite one
idx[:first] = first
return idx
else:
return None
def _arrayToQPath_all(x, y, finiteCheck):
n = x.shape[0]
if n == 0:
return QtGui.QPainterPath()
backfill_idx = None
if finiteCheck:
isfinite = np.isfinite(x) & np.isfinite(y)
if not np.all(isfinite):
backfill_idx = _compute_backfill_indices(isfinite)
chunksize = 10000
numchunks = (n + chunksize - 1) // chunksize
minchunks = 3
if numchunks < minchunks:
# too few chunks, batching would be a pessimization
poly = create_qpolygonf(n)
arr = ndarray_from_qpolygonf(poly)
if backfill_idx is None:
arr[:, 0] = x
arr[:, 1] = y
else:
arr[:, 0] = x[backfill_idx]
arr[:, 1] = y[backfill_idx]
path = QtGui.QPainterPath()
if hasattr(path, 'reserve'): # Qt 5.13
path.reserve(n)
path.addPolygon(poly)
return path
# at this point, we have numchunks >= minchunks
path = QtGui.QPainterPath()
if hasattr(path, 'reserve'): # Qt 5.13
path.reserve(n)
subpoly = QtGui.QPolygonF()
subpath = None
for idx in range(numchunks):
sl = slice(idx*chunksize, min((idx+1)*chunksize, n))
currsize = sl.stop - sl.start
if currsize != subpoly.size():
if hasattr(subpoly, 'resize'):
subpoly.resize(currsize)
else:
subpoly.fill(QtCore.QPointF(), currsize)
subarr = ndarray_from_qpolygonf(subpoly)
if backfill_idx is None:
subarr[:, 0] = x[sl]
subarr[:, 1] = y[sl]
else:
bfv = backfill_idx[sl] # view
subarr[:, 0] = x[bfv]
subarr[:, 1] = y[bfv]
if subpath is None:
subpath = QtGui.QPainterPath()
subpath.addPolygon(subpoly)
path.connectPath(subpath)
if hasattr(subpath, 'clear'): # Qt 5.13
subpath.clear()
else:
subpath = None
return path
def _arrayToQPath_finite(x, y, isfinite=None):
n = x.shape[0]
if n == 0:
return QtGui.QPainterPath()
if isfinite is None:
isfinite = np.isfinite(x) & np.isfinite(y)
path = QtGui.QPainterPath()
if hasattr(path, 'reserve'): # Qt 5.13
path.reserve(n)
sidx = np.nonzero(~isfinite)[0] + 1
# note: the chunks are views
xchunks = np.split(x, sidx)
ychunks = np.split(y, sidx)
chunks = list(zip(xchunks, ychunks))
# create a single polygon able to hold the largest chunk
maxlen = max(len(chunk) for chunk in xchunks)
subpoly = create_qpolygonf(maxlen)
subarr = ndarray_from_qpolygonf(subpoly)
# resize and fill do not change the capacity
if hasattr(subpoly, 'resize'):
subpoly_resize = subpoly.resize
else:
# PyQt will be less efficient
subpoly_resize = lambda n, v=QtCore.QPointF() : subpoly.fill(v, n)
# notes:
# - we backfill the non-finite in order to get the same image as the
# old codepath on the CI. somehow P1--P2 gets rendered differently
# from P1--P2--P2
# - we do not generate MoveTo(s) that are not followed by a LineTo,
# thus the QPainterPath can be different from the old codepath's
# all chunks except the last chunk have a trailing non-finite
for xchunk, ychunk in chunks[:-1]:
lc = len(xchunk)
if lc <= 1:
# len 1 means we have a string of non-finite
continue
subpoly_resize(lc)
subarr[:lc, 0] = xchunk
subarr[:lc, 1] = ychunk
subarr[lc-1] = subarr[lc-2] # fill non-finite with its neighbour
path.addPolygon(subpoly)
# handle last chunk, which is either all-finite or empty
for xchunk, ychunk in chunks[-1:]:
lc = len(xchunk)
if lc <= 1:
# can't draw a line with just 1 point
continue
subpoly_resize(lc)
subarr[:lc, 0] = xchunk
subarr[:lc, 1] = ychunk
path.addPolygon(subpoly)
return path
def arrayToQPath(x, y, connect='all', finiteCheck=True):
"""
Convert an array of x,y coordinates to QPainterPath as efficiently as
@ -1887,7 +2045,7 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True):
only values with 1 will connect to the previous point. Def
finiteCheck : bool, default Ture
When false, the check for finite values will be skipped, which can
improve performance. If finite values are present in `x` or `y`,
improve performance. If nonfinite values are present in `x` or `y`,
an empty QPainterPath will be generated.
Returns
@ -1920,165 +2078,108 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True):
This binary format may change in future versions of Qt
"""
path = QtGui.QPainterPath()
n = x.shape[0]
if n == 0:
return QtGui.QPainterPath()
connect_array = None
if isinstance(connect, np.ndarray):
# make connect argument contain only str type
connect_array, connect = connect, 'array'
use_qpolygonf = connect == 'all'
isfinite = None
if connect == 'finite':
isfinite = np.isfinite(x) & np.isfinite(y)
if not finiteCheck:
# if user specified to skip finite check, then that forces use_qpolygonf
use_qpolygonf = True
# if user specified to skip finite check, then we skip the heuristic
return _arrayToQPath_finite(x, y)
# otherwise use a heuristic
# if non-finite aren't that many, then use_qpolyponf
isfinite = np.isfinite(x) & np.isfinite(y)
nonfinite_cnt = n - np.sum(isfinite)
all_isfinite = nonfinite_cnt == 0
if all_isfinite:
# delegate to connect='all'
connect = 'all'
finiteCheck = False
elif nonfinite_cnt / n < 2 / 100:
return _arrayToQPath_finite(x, y, isfinite)
else:
# otherwise use a heuristic
# if non-finite aren't that many, then use_qpolyponf
nonfinite_cnt = n - np.sum(isfinite)
if nonfinite_cnt / n < 2 / 100:
use_qpolygonf = True
finiteCheck = False
if nonfinite_cnt == 0:
connect = 'all'
# delegate to connect=ndarray
# finiteCheck=True, all_isfinite=False
connect = 'array'
connect_array = isfinite
if use_qpolygonf:
backstore = create_qpolygonf(n)
arr = np.frombuffer(ndarray_from_qpolygonf(backstore), dtype=[('x', 'f8'), ('y', 'f8')])
else:
backstore = bytearray(4 + n*20 + 8)
arr = np.frombuffer(backstore, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')],
count=n, offset=4)
struct.pack_into('>i', backstore, 0, n)
# cStart, fillRule (Qt.FillRule.OddEvenFill)
struct.pack_into('>ii', backstore, 4+n*20, 0, 0)
if connect == 'all':
return _arrayToQPath_all(x, y, finiteCheck)
# Fill array with vertex values
arr['x'] = x
arr['y'] = y
backstore = QtCore.QByteArray()
backstore.resize(4 + n*20 + 8) # contents uninitialized
backstore.replace(0, 4, struct.pack('>i', n))
# cStart, fillRule (Qt.FillRule.OddEvenFill)
backstore.replace(4+n*20, 8, struct.pack('>ii', 0, 0))
arr = np.frombuffer(backstore, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')],
count=n, offset=4)
# the presence of inf/nans result in an empty QPainterPath being generated
# this behavior started in Qt 5.12.3 and was introduced in this commit
# https://github.com/qt/qtbase/commit/c04bd30de072793faee5166cff866a4c4e0a9dd7
# We therefore replace non-finite values
backfill_idx = None
if finiteCheck:
if isfinite is None:
isfinite = np.isfinite(x) & np.isfinite(y)
if not np.all(isfinite):
# credit: Divakar https://stackoverflow.com/a/41191127/643629
mask = ~isfinite
idx = np.arange(len(x))
idx[mask] = -1
np.maximum.accumulate(idx, out=idx)
first = np.searchsorted(idx, 0)
if first < len(x):
# Replace all non-finite entries from beginning of arr with the first finite one
idx[:first] = first
arr[:] = arr[:][idx]
all_isfinite = np.all(isfinite)
if not all_isfinite:
backfill_idx = _compute_backfill_indices(isfinite)
if backfill_idx is None:
arr['x'] = x
arr['y'] = y
else:
arr['x'] = x[backfill_idx]
arr['y'] = y[backfill_idx]
# decide which points are connected by lines
if connect == 'all':
path.addPolygon(backstore)
return path
elif connect == 'pairs':
arr['c'][::2] = 0
if connect == 'pairs':
arr['c'][0::2] = 0
arr['c'][1::2] = 1 # connect every 2nd point to every 1st one
elif connect == 'finite':
elif connect == 'array':
# Let's call a point with either x or y being nan is an invalid point.
# A point will anyway not connect to an invalid point regardless of the
# 'c' value of the invalid point. Therefore, we should set 'c' to 0 for
# the next point of an invalid point.
if not use_qpolygonf:
arr[1:]['c'] = isfinite[:-1]
else:
sidx = np.nonzero(~isfinite)[0] + 1
chunks = np.split(arr, sidx) # note: the chunks are views
# create a single polygon able to hold the largest chunk
maxlen = max(len(chunk) for chunk in chunks)
subpoly = create_qpolygonf(maxlen)
subarr = np.frombuffer(ndarray_from_qpolygonf(subpoly), dtype=arr.dtype)
# resize and fill do not change the capacity
if hasattr(subpoly, 'resize'):
subpoly_resize = subpoly.resize
else:
# PyQt will be less efficient
subpoly_resize = lambda n, v=QtCore.QPointF() : subpoly.fill(v, n)
# notes:
# - we backfill the non-finite in order to get the same image as the
# old codepath on the CI. somehow P1--P2 gets rendered differently
# from P1--P2--P2
# - we do not generate MoveTo(s) that are not followed by a LineTo,
# thus the QPainterPath can be different from the old codepath's
# all chunks except the last chunk have a trailing non-finite
for chunk in chunks[:-1]:
lc = len(chunk)
if lc <= 1:
# len 1 means we have a string of non-finite
continue
subpoly_resize(lc)
subarr[:lc] = chunk
subarr[lc-1] = subarr[lc-2] # fill non-finite with its neighbour
path.addPolygon(subpoly)
# handle last chunk, which is either all-finite or empty
for chunk in chunks[-1:]:
lc = len(chunk)
if lc <= 1:
# can't draw a line with just 1 point
continue
subpoly_resize(lc)
subarr[:lc] = chunk
path.addPolygon(subpoly)
return path
elif connect == 'array':
arr[1:]['c'] = connect_array[:-1]
arr['c'][:1] = 0 # the first vertex has no previous vertex to connect
arr['c'][1:] = connect_array[:-1]
else:
raise ValueError('connect argument must be "all", "pairs", "finite", or array')
arr[0]['c'] = 0 # the first vertex has no previous vertex to connect
path = QtGui.QPainterPath()
if hasattr(path, 'reserve'): # Qt 5.13
path.reserve(n)
# create QDataStream object and stream into QPainterPath
path.strn = backstore
if QT_LIB == "PyQt6" and QtCore.PYQT_VERSION < 0x60101:
# due to issue detailed here:
# https://www.riverbankcomputing.com/pipermail/pyqt/2021-May/043942.html
buf = QtCore.QByteArray(path.strn, len(path.strn))
else:
buf = QtCore.QByteArray(path.strn)
ds = QtCore.QDataStream(buf)
ds = QtCore.QDataStream(backstore)
ds >> path
return path
def ndarray_from_qpolygonf(polyline):
nbytes = 2 * len(polyline) * 8
if QT_LIB == "PySide2":
buffer = Qt.shiboken2.VoidPtr(polyline.data(), nbytes, True)
elif QT_LIB == "PySide6":
buffer = Qt.shiboken6.VoidPtr(polyline.data(), nbytes, True)
else:
if QT_LIB.startswith('PyQt'):
buffer = polyline.data()
if buffer is None:
buffer = Qt.sip.voidptr(0)
buffer.setsize(nbytes)
else:
ptr = polyline.data()
if ptr is None:
ptr = 0
buffer = Qt.shiboken.VoidPtr(ptr, nbytes, True)
memory = np.frombuffer(buffer, np.double).reshape((-1, 2))
return memory
def create_qpolygonf(size):
if QtVersion.startswith("5"):
polyline = QtGui.QPolygonF(size)
polyline = QtGui.QPolygonF()
if QT_LIB.startswith('PyQt'):
polyline.fill(QtCore.QPointF(), size)
else:
polyline = QtGui.QPolygonF()
if QT_LIB == "PySide6":
polyline.resize(size)
else:
polyline.fill(QtCore.QPointF(), size)
polyline.resize(size)
return polyline
def arrayToQPolygonF(x, y):
@ -2192,9 +2293,9 @@ def arrayToQPolygonF(x, y):
#index = tetFields[0] + tetFields[1]*2 + tetFields[2]*4 + tetFields[3]*8
### add facets
#for i in xrange(index.shape[0]): # data x-axis
#for j in xrange(index.shape[1]): # data y-axis
#for k in xrange(index.shape[2]): # data z-axis
#for i in range(index.shape[0]): # data x-axis
#for j in range(index.shape[1]): # data y-axis
#for k in range(index.shape[2]): # data z-axis
#for f in indexFacets[index[i,j,k]]: # faces to generate for this tet
#pts = []
#for l in [0,1,2]: # points in this face
@ -2397,11 +2498,6 @@ def traceImage(image, values, smooth=0.5):
If image is RGB or RGBA, then the shape of values should be (nvals, 3/4)
The parameter *smooth* is expressed in pixels.
"""
try:
import scipy.ndimage as ndi
except ImportError:
raise Exception("traceImage() requires the package scipy.ndimage, but it is not importable.")
if values.ndim == 2:
values = values.T
values = values[np.newaxis, np.newaxis, ...].astype(float)
@ -3089,9 +3185,9 @@ def disconnect(signal, slot):
This method augments Qt's Signal.disconnect():
* Return bool indicating whether disconnection was successful, rather than
raising an exception
* Attempt to disconnect prior versions of the slot when using pg.reload
* Return bool indicating whether disconnection was successful, rather than
raising an exception
* Attempt to disconnect prior versions of the slot when using pg.reload
"""
while True:
try:

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from math import hypot
from ..Qt import QtGui, QtCore
from ..Qt import QtGui
from .. import functions as fn
import numpy as np
__all__ = ['ArrowItem']
class ArrowItem(QtGui.QGraphicsPathItem):

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from ..Qt import QtGui, QtCore
from ..python2_3 import asUnicode
import numpy as np
from ..Point import Point
from .. import debug as debug
@ -158,7 +157,7 @@ class AxisItem(GraphicsWidget):
tickAlpha (float or int or None) If None, pyqtgraph will draw the
ticks with the alpha it deems appropriate. Otherwise,
the alpha will be fixed at the value passed. With int,
accepted values are [0..255]. With vaule of type
accepted values are [0..255]. With value of type
float, accepted values are from [0..1].
=================== =======================================================
@ -233,6 +232,10 @@ class AxisItem(GraphicsWidget):
## Set the position of the label
nudge = 5
if self.label is None: # self.label is set to None on close, but resize events can still occur.
self.picture = None
return
br = self.label.boundingRect()
p = QtCore.QPointF(0, 0)
if self.orientation == 'left':
@ -310,16 +313,15 @@ class AxisItem(GraphicsWidget):
if not self.autoSIPrefix or self.autoSIPrefixScale == 1.0:
units = ''
else:
units = asUnicode('(x%g)') % (1.0/self.autoSIPrefixScale)
units = '(x%g)' % (1.0/self.autoSIPrefixScale)
else:
#print repr(self.labelUnitPrefix), repr(self.labelUnits)
units = asUnicode('(%s%s)') % (asUnicode(self.labelUnitPrefix), asUnicode(self.labelUnits))
units = '(%s%s)' % (self.labelUnitPrefix, self.labelUnits)
s = asUnicode('%s %s') % (asUnicode(self.labelText), asUnicode(units))
s = '%s %s' % (self.labelText, units)
style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle])
return asUnicode("<span style='%s'>%s</span>") % (style, asUnicode(s))
return "<span style='%s'>%s</span>" % (style, s)
def _updateMaxTextSize(self, x):
## Informs that the maximum tick size orthogonal to the axis has
@ -431,7 +433,7 @@ class AxisItem(GraphicsWidget):
self._pen = fn.mkPen(*args, **kwargs)
else:
self._pen = fn.mkPen(getConfigOption('foreground'))
self.labelStyle['color'] = '#' + fn.colorStr(self._pen.color())[:6]
self.labelStyle['color'] = self._pen.color().name() # #RRGGBB
self._updateLabel()
def textPen(self):
@ -449,7 +451,7 @@ class AxisItem(GraphicsWidget):
self._textPen = fn.mkPen(*args, **kwargs)
else:
self._textPen = fn.mkPen(getConfigOption('foreground'))
self.labelStyle['color'] = '#' + fn.colorStr(self._textPen.color())[:6]
self.labelStyle['color'] = self._textPen.color().name() # #RRGGBB
self._updateLabel()
def setScale(self, scale=None):
@ -841,36 +843,31 @@ class AxisItem(GraphicsWidget):
def logTickStrings(self, values, scale, spacing):
estrings = ["%0.1g"%x for x in 10 ** np.array(values).astype(float) * np.array(scale)]
if sys.version_info < (3, 0):
# python 2 does not support unicode strings like that
return estrings
else: # python 3+
convdict = {"0": "",
"1": "¹",
"2": "²",
"3": "³",
"4": "",
"5": "",
"6": "",
"7": "",
"8": "",
"9": "",
}
dstrings = []
for e in estrings:
if e.count("e"):
v, p = e.split("e")
sign = "" if p[0] == "-" else ""
pot = "".join([convdict[pp] for pp in p[1:].lstrip("0")])
if v == "1":
v = ""
else:
v = v + "·"
dstrings.append(v + "10" + sign + pot)
convdict = {"0": "",
"1": "¹",
"2": "²",
"3": "³",
"4": "",
"5": "",
"6": "",
"7": "",
"8": "",
"9": "",
}
dstrings = []
for e in estrings:
if e.count("e"):
v, p = e.split("e")
sign = "" if p[0] == "-" else ""
pot = "".join([convdict[pp] for pp in p[1:].lstrip("0")])
if v == "1":
v = ""
else:
dstrings.append(e)
return dstrings
v = v + "·"
dstrings.append(v + "10" + sign + pot)
else:
dstrings.append(e)
return dstrings
def generateDrawSpecs(self, p):
"""
@ -1066,7 +1063,7 @@ class AxisItem(GraphicsWidget):
if s is None:
rects.append(None)
else:
br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignmentFlag.AlignCenter, asUnicode(s))
br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignmentFlag.AlignCenter, s)
## boundingRect is usually just a bit too large
## (but this probably depends on per-font metrics?)
br.setHeight(br.height() * 0.8)
@ -1108,7 +1105,6 @@ class AxisItem(GraphicsWidget):
vstr = strings[j]
if vstr is None: ## this tick was ignored because it is out of bounds
continue
vstr = asUnicode(vstr)
x = tickPositions[i][j]
#textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignmentFlag.AlignCenter, vstr)
textRect = rects[j]

View File

@ -7,6 +7,7 @@ from .LinearRegionItem import LinearRegionItem
import weakref
import math
import warnings
import numpy as np
__all__ = ['ColorBarItem']
@ -35,33 +36,40 @@ class ColorBarItem(PlotItem):
sigLevelsChanged = QtCore.Signal(object)
sigLevelsChangeFinished = QtCore.Signal(object)
def __init__(self, values=(0,1), width=25, cmap=None, label=None,
def __init__(self, values=(0,1), width=25, colorMap=None, label=None,
interactive=True, limits=None, rounding=1,
orientation='vertical', pen='w', hoverPen='r', hoverBrush='#FF000080' ):
orientation='vertical', pen='w', hoverPen='r', hoverBrush='#FF000080', cmap=None ):
"""
Create a new ColorBarItem.
Creates a new ColorBarItem.
============== ===========================================================================
**Arguments:**
values The range of values as tuple (min, max)
width (default=25) The width of the displayed color bar
cmap ColorMap object, look-up table is also applied to assigned ImageItem(s)
colorMap ColorMap object, look-up table is also applied to assigned ImageItem(s)
label (default=None) Label applied to color bar axis
interactive (default=True) Handles are displayed to interactively adjust level range
limits Limits to adjustment range as (low, high) tuple, None disables limit
rounding (default=1) Adjusted range values are rounded to multiples of this values
orientation (default='vertical') 'horizontal' gives a horizontal color bar
orientation (default='vertical') 'horizontal' or 'h' gives a horizontal color bar.
pen color of adjustement handles in interactive mode
hoverPen color of adjustement handles when hovered over
hoverBrush color of movable center region when hovered over
============== ===========================================================================
"""
super().__init__()
if cmap is not None:
warnings.warn(
"The parameter 'cmap' has been renamed to 'colorMap' for clarity. "
"The old name will no longer be available in any version of PyQtGraph released after July 2022.",
DeprecationWarning, stacklevel=2
)
colorMap = cmap
self.img_list = [] # list of controlled ImageItems
self.values = values
self.cmap = cmap
self._colorMap = colorMap
self.rounding = rounding
self.horizontal = bool(orientation == 'horizontal')
self.horizontal = bool( orientation in ('h', 'horizontal') )
self.lo_prv, self.hi_prv = self.values # remember previous values while adjusting range
if limits is None:
@ -107,7 +115,7 @@ class ColorBarItem(PlotItem):
self.bar.setImage( np.linspace(0, 1, 256).reshape( (1,-1) ) )
if label is not None: self.getAxis('left').setLabel(label)
self.addItem(self.bar)
if cmap is not None: self.setCmap(cmap)
if cmap is not None: self.setColorMap(cmap)
if interactive:
if self.horizontal:
@ -156,13 +164,36 @@ class ColorBarItem(PlotItem):
insert_in.layout.setColumnFixedWidth(4, 5) # enforce some space to axis on the left
self._update_items( update_cmap = True )
# Maintain compatibility for old name of color bar setting method.
def setCmap(self, cmap):
warnings.warn(
"The method 'setCmap' has been renamed to 'setColorMap' for clarity. "
"The old name will no longer be available in any version of PyQtGraph released after July 2022.",
DeprecationWarning, stacklevel=2
)
self.setColorMap(cmap)
def setColorMap(self, colorMap):
"""
sets a ColorMap object to determine the ColorBarItem's look-up table. The same
Sets a ColorMap object to determine the ColorBarItem's look-up table. The same
look-up table is applied to any assigned ImageItem.
"""
self.cmap = cmap
self._colorMap = colorMap
self._update_items( update_cmap = True )
def colorMap(self):
"""
Returns the assigned ColorMap object.
"""
return self._colorMap
@property
def cmap(self):
warnings.warn(
"Direct access to ColorMap.cmap is deprecated and will no longer be available in any "
"version of PyQtGraph released after July 2022. Please use 'ColorMap.colorMap()' instead.",
DeprecationWarning, stacklevel=2)
return self._colorMap
def setLevels(self, values=None, low=None, high=None ):
"""
@ -170,7 +201,7 @@ class ColorBarItem(PlotItem):
============== ===========================================================================
**Arguments:**
values Specify levels by tuple (low, high). Either value can be None to leave
values specifies levels as tuple (low, high). Either value can be None to leave
to previous value unchanged. Takes precedence over low and high parameters.
low new low level to be applied to color bar and assigned images
high new high level to be applied to color bar and assigned images
@ -199,15 +230,15 @@ class ColorBarItem(PlotItem):
""" internal: update color maps for bar and assigned ImageItems """
# update color bar:
self.axis.setRange( self.values[0], self.values[1] )
if update_cmap and self.cmap is not None:
self.bar.setLookupTable( self.cmap.getLookupTable(nPts=256) )
if update_cmap and self._colorMap is not None:
self.bar.setLookupTable( self._colorMap.getLookupTable(nPts=256) )
# update assigned ImageItems, too:
for img_weakref in self.img_list:
img = img_weakref()
if img is None: continue # dereference weakref
img.setLevels( self.values ) # (min,max) tuple
if update_cmap and self.cmap is not None:
img.setLookupTable( self.cmap.getLookupTable(nPts=256) )
if update_cmap and self._colorMap is not None:
img.setLookupTable( self._colorMap.getLookupTable(nPts=256) )
def _regionChanged(self):
""" internal: snap adjusters back to default positions on release """

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
from math import atan2, degrees
from ..Qt import QtGui, QtCore
from . import ArrowItem
from ..functions import clip_scalar
from ..Point import Point
import weakref
from .GraphicsObject import GraphicsObject

View File

@ -1,8 +1,8 @@
from ..Qt import QtGui, QtCore
# -*- coding: utf-8 -*-
from ..Qt import QtGui
from .GraphicsObject import GraphicsObject
from .. import getConfigOption
from .. import functions as fn
import numpy as np
__all__ = ['ErrorBarItem']
@ -34,15 +34,15 @@ class ErrorBarItem(GraphicsObject):
Valid keyword options are:
x, y, height, width, top, bottom, left, right, beam, pen
* x and y must be numpy arrays specifying the coordinates of data points.
* height, width, top, bottom, left, right, and beam may be numpy arrays,
single values, or None to disable. All values should be positive.
* top, bottom, left, and right specify the lengths of bars extending
in each direction.
* If height is specified, it overrides top and bottom.
* If width is specified, it overrides left and right.
* beam specifies the width of the beam at the end of each bar.
* pen may be any single argument accepted by pg.mkPen().
* x and y must be numpy arrays specifying the coordinates of data points.
* height, width, top, bottom, left, right, and beam may be numpy arrays,
single values, or None to disable. All values should be positive.
* top, bottom, left, and right specify the lengths of bars extending
in each direction.
* If height is specified, it overrides top and bottom.
* If width is specified, it overrides left and right.
* beam specifies the width of the beam at the end of each bar.
* pen may be any single argument accepted by pg.mkPen().
This method was added in version 0.9.9. For prior versions, use setOpts.
"""

View File

@ -4,7 +4,6 @@ import weakref
import numpy as np
from ..Qt import QtGui, QtCore
from .. import functions as fn
from .GraphicsObject import GraphicsObject
from .GraphicsWidget import GraphicsWidget
from ..widgets.SpinBox import SpinBox
from collections import OrderedDict
@ -582,7 +581,7 @@ class GradientEditorItem(TickSliderItem):
for t,x in self.listTicks():
pos.append(x)
c = t.color
color.append([c.red(), c.green(), c.blue(), c.alpha()])
color.append(c.getRgb())
return ColorMap(np.array(pos), np.array(color, dtype=np.ubyte))
def updateGradient(self):
@ -672,13 +671,13 @@ class GradientEditorItem(TickSliderItem):
if toQColor:
return QtGui.QColor(c) # always copy colors before handing them out
else:
return (c.red(), c.green(), c.blue(), c.alpha())
return c.getRgb()
if x >= ticks[-1][1]:
c = ticks[-1][0].color
if toQColor:
return QtGui.QColor(c) # always copy colors before handing them out
else:
return (c.red(), c.green(), c.blue(), c.alpha())
return c.getRgb()
x2 = ticks[0][1]
for i in range(1,len(ticks)):
@ -709,12 +708,11 @@ class GradientEditorItem(TickSliderItem):
h = h1 * (1.-f) + h2 * f
s = s1 * (1.-f) + s2 * f
v = v1 * (1.-f) + v2 * f
c = QtGui.QColor()
c.setHsv(*map(int, [h,s,v]))
c = QtGui.QColor.fromHsv(int(h), int(s), int(v))
if toQColor:
return c
else:
return (c.red(), c.green(), c.blue(), c.alpha())
return c.getRgb()
def getLookupTable(self, nPts, alpha=None):
"""
@ -758,8 +756,8 @@ class GradientEditorItem(TickSliderItem):
return False
if ticks[0][1] != 0.0 or ticks[1][1] != 1.0:
return False
c1 = fn.colorTuple(ticks[0][0].color)
c2 = fn.colorTuple(ticks[1][0].color)
c1 = ticks[0][0].color.getRgb()
c2 = ticks[1][0].color.getRgb()
if c1 != (0,0,0,255) or c2 != (255,255,255,255):
return False
return True
@ -795,7 +793,7 @@ class GradientEditorItem(TickSliderItem):
ticks = []
for t in self.ticks:
c = t.color
ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha())))
ticks.append((self.ticks[t], c.getRgb()))
state = {'mode': self.colorMode,
'ticks': ticks,
'ticksVisible': next(iter(self.ticks)).isVisible()}

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from .. import functions as fn
from .GraphicsObject import GraphicsObject
from .ScatterPlotItem import ScatterPlotItem
@ -36,14 +37,14 @@ class GraphItem(GraphicsObject):
pen The pen to use when drawing lines between connected
nodes. May be one of:
* QPen
* a single argument to pass to pg.mkPen
* a record array of length M
with fields (red, green, blue, alpha, width). Note
that using this option may have a significant performance
cost.
* None (to disable connection drawing)
* 'default' to use the default foreground color.
* QPen
* a single argument to pass to pg.mkPen
* a record array of length M
with fields (red, green, blue, alpha, width). Note
that using this option may have a significant performance
cost.
* None (to disable connection drawing)
* 'default' to use the default foreground color.
symbolPen The pen(s) used for drawing nodes.
symbolBrush The brush(es) used for drawing nodes.
@ -84,11 +85,11 @@ class GraphItem(GraphicsObject):
Set the pen used to draw graph lines.
May be:
* None to disable line drawing
* Record array with fields (red, green, blue, alpha, width)
* Any set of arguments and keyword arguments accepted by
:func:`mkPen <pyqtgraph.mkPen>`.
* 'default' to use the default foreground color.
* None to disable line drawing
* Record array with fields (red, green, blue, alpha, width)
* Any set of arguments and keyword arguments accepted by
:func:`mkPen <pyqtgraph.mkPen>`.
* 'default' to use the default foreground color.
"""
if len(args) == 1 and len(kwargs) == 0:
self.pen = args[0]

View File

@ -1,4 +1,5 @@
from ..Qt import QtGui, QtCore
# -*- coding: utf-8 -*-
from ..Qt import QtGui
from .. import functions as fn
from .GraphicsWidget import GraphicsWidget
## Must be imported at the end to avoid cyclic-dependency hell:

View File

@ -1,4 +1,5 @@
from ..Qt import QtGui, QtCore, QT_LIB
# -*- coding: utf-8 -*-
from ..Qt import QtGui, QT_LIB
if QT_LIB.startswith('PyQt'):
from ..Qt import sip
from .GraphicsItem import GraphicsItem

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