diff --git a/README.txt b/README.txt index d209ef010051dad78794ad6df5b021b81c8729bb..85e2b24a29f9620b735fa70202c5e67d4387005e 100644 --- a/README.txt +++ b/README.txt @@ -12,6 +12,7 @@ Contributors: Christian Gavin Michael Cristopher Hogg Ulrich Leutner + Felix Schill Requirements: PyQt 4.7+ or PySide diff --git a/examples/VideoSpeedTest.py b/examples/VideoSpeedTest.py index dd392189a6456aabe7773e85332be8293ce8a74c..d7a4e1e0e4c9da7bdedde98b7720468f2ad0773e 100644 --- a/examples/VideoSpeedTest.py +++ b/examples/VideoSpeedTest.py @@ -130,8 +130,13 @@ def update(): if ui.rawRadio.isChecked(): ui.rawImg.setImage(data[ptr%data.shape[0]], lut=useLut, levels=useScale) + ui.stack.setCurrentIndex(1) + elif ui.rawGLRadio.isChecked(): + ui.rawGLImg.setImage(data[ptr%data.shape[0]], lut=useLut, levels=useScale) + ui.stack.setCurrentIndex(2) else: img.setImage(data[ptr%data.shape[0]], autoLevels=False, levels=useScale, lut=useLut) + ui.stack.setCurrentIndex(0) #img.setImage(data[ptr%data.shape[0]], autoRange=False) ptr += 1 diff --git a/examples/VideoTemplate.ui b/examples/VideoTemplate.ui index 3dddb9285d20962dd912bf7297fd0e8f1bbc3604..d73b0dc94f2258d0c1716de53528a370dc9a565a 100644 --- a/examples/VideoTemplate.ui +++ b/examples/VideoTemplate.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>985</width> - <height>674</height> + <width>695</width> + <height>798</height> </rect> </property> <property name="windowTitle"> @@ -17,33 +17,62 @@ <layout class="QGridLayout" name="gridLayout_2"> <item row="1" column="0" colspan="4"> <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="RawImageWidget" name="rawImg" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="GraphicsView" name="graphicsView"/> - </item> - <item row="1" column="0"> + <item row="3" column="0"> <widget class="QRadioButton" name="rawRadio"> <property name="text"> - <string>RawImageWidget (unscaled; faster)</string> + <string>RawImageWidget</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> - <item row="1" column="1"> + <item row="2" column="0"> <widget class="QRadioButton" name="gfxRadio"> <property name="text"> - <string>GraphicsView + ImageItem (scaled; slower)</string> + <string>GraphicsView + ImageItem</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QStackedWidget" name="stack"> + <property name="currentIndex"> + <number>2</number> + </property> + <widget class="QWidget" name="page"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="GraphicsView" name="graphicsView"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_2"> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="RawImageWidget" name="rawImg" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_3"> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="0" column="0"> + <widget class="RawImageGLWidget" name="rawGLImg" native="true"/> + </item> + </layout> + </widget> + </widget> + </item> + <item row="4" column="0"> + <widget class="QRadioButton" name="rawGLRadio"> + <property name="text"> + <string>RawGLImageWidget</string> </property> </widget> </item> @@ -250,6 +279,12 @@ <extends>QDoubleSpinBox</extends> <header>pyqtgraph</header> </customwidget> + <customwidget> + <class>RawImageGLWidget</class> + <extends>QWidget</extends> + <header>pyqtgraph.widgets.RawImageWidget</header> + <container>1</container> + </customwidget> </customwidgets> <resources/> <connections/> diff --git a/examples/VideoTemplate_pyqt.py b/examples/VideoTemplate_pyqt.py index c3430e2dc7ae013eb48f841908e0c25f6e34a9de..f61a5e46587fd1eca9aa2275473508ec90bdc233 100644 --- a/examples/VideoTemplate_pyqt.py +++ b/examples/VideoTemplate_pyqt.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file './examples/VideoTemplate.ui' +# Form implementation generated from reading ui file './VideoTemplate.ui' # -# Created: Sun Nov 4 18:24:20 2012 -# by: PyQt4 UI code generator 4.9.1 +# Created: Tue Jul 9 23:38:17 2013 +# by: PyQt4 UI code generator 4.9.3 # # WARNING! All changes made in this file will be lost! @@ -17,31 +17,55 @@ except AttributeError: class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) - MainWindow.resize(985, 674) + MainWindow.resize(695, 798) self.centralwidget = QtGui.QWidget(MainWindow) self.centralwidget.setObjectName(_fromUtf8("centralwidget")) self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget) self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) self.gridLayout = QtGui.QGridLayout() self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.rawImg = RawImageWidget(self.centralwidget) + self.rawRadio = QtGui.QRadioButton(self.centralwidget) + self.rawRadio.setChecked(True) + self.rawRadio.setObjectName(_fromUtf8("rawRadio")) + self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) + self.gfxRadio = QtGui.QRadioButton(self.centralwidget) + self.gfxRadio.setObjectName(_fromUtf8("gfxRadio")) + self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) + self.stack = QtGui.QStackedWidget(self.centralwidget) + self.stack.setObjectName(_fromUtf8("stack")) + self.page = QtGui.QWidget() + self.page.setObjectName(_fromUtf8("page")) + self.gridLayout_3 = QtGui.QGridLayout(self.page) + self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) + self.graphicsView = GraphicsView(self.page) + self.graphicsView.setObjectName(_fromUtf8("graphicsView")) + self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) + self.stack.addWidget(self.page) + self.page_2 = QtGui.QWidget() + self.page_2.setObjectName(_fromUtf8("page_2")) + self.gridLayout_4 = QtGui.QGridLayout(self.page_2) + self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) + self.rawImg = RawImageWidget(self.page_2) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) self.rawImg.setSizePolicy(sizePolicy) self.rawImg.setObjectName(_fromUtf8("rawImg")) - self.gridLayout.addWidget(self.rawImg, 0, 0, 1, 1) - self.graphicsView = GraphicsView(self.centralwidget) - self.graphicsView.setObjectName(_fromUtf8("graphicsView")) - self.gridLayout.addWidget(self.graphicsView, 0, 1, 1, 1) - self.rawRadio = QtGui.QRadioButton(self.centralwidget) - self.rawRadio.setChecked(True) - self.rawRadio.setObjectName(_fromUtf8("rawRadio")) - self.gridLayout.addWidget(self.rawRadio, 1, 0, 1, 1) - self.gfxRadio = QtGui.QRadioButton(self.centralwidget) - self.gfxRadio.setObjectName(_fromUtf8("gfxRadio")) - self.gridLayout.addWidget(self.gfxRadio, 1, 1, 1, 1) + self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) + self.stack.addWidget(self.page_2) + self.page_3 = QtGui.QWidget() + self.page_3.setObjectName(_fromUtf8("page_3")) + self.gridLayout_5 = QtGui.QGridLayout(self.page_3) + self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) + self.rawGLImg = RawImageGLWidget(self.page_3) + self.rawGLImg.setObjectName(_fromUtf8("rawGLImg")) + self.gridLayout_5.addWidget(self.rawGLImg, 0, 0, 1, 1) + self.stack.addWidget(self.page_3) + self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) + self.rawGLRadio = QtGui.QRadioButton(self.centralwidget) + self.rawGLRadio.setObjectName(_fromUtf8("rawGLRadio")) + self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) self.label = QtGui.QLabel(self.centralwidget) self.label.setObjectName(_fromUtf8("label")) @@ -130,12 +154,14 @@ class Ui_MainWindow(object): MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) + self.stack.setCurrentIndex(2) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) - self.rawRadio.setText(QtGui.QApplication.translate("MainWindow", "RawImageWidget (unscaled; faster)", None, QtGui.QApplication.UnicodeUTF8)) - self.gfxRadio.setText(QtGui.QApplication.translate("MainWindow", "GraphicsView + ImageItem (scaled; slower)", None, QtGui.QApplication.UnicodeUTF8)) + self.rawRadio.setText(QtGui.QApplication.translate("MainWindow", "RawImageWidget", None, QtGui.QApplication.UnicodeUTF8)) + self.gfxRadio.setText(QtGui.QApplication.translate("MainWindow", "GraphicsView + ImageItem", None, QtGui.QApplication.UnicodeUTF8)) + self.rawGLRadio.setText(QtGui.QApplication.translate("MainWindow", "RawGLImageWidget", None, QtGui.QApplication.UnicodeUTF8)) self.label.setText(QtGui.QApplication.translate("MainWindow", "Data type", None, QtGui.QApplication.UnicodeUTF8)) self.dtypeCombo.setItemText(0, QtGui.QApplication.translate("MainWindow", "uint8", None, QtGui.QApplication.UnicodeUTF8)) self.dtypeCombo.setItemText(1, QtGui.QApplication.translate("MainWindow", "uint16", None, QtGui.QApplication.UnicodeUTF8)) @@ -150,4 +176,5 @@ class Ui_MainWindow(object): self.fpsLabel.setText(QtGui.QApplication.translate("MainWindow", "FPS", None, QtGui.QApplication.UnicodeUTF8)) self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8)) -from pyqtgraph import SpinBox, GradientWidget, GraphicsView, RawImageWidget +from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget +from pyqtgraph import GradientWidget, SpinBox, GraphicsView, RawImageWidget diff --git a/examples/VideoTemplate_pyside.py b/examples/VideoTemplate_pyside.py index d19e0f23fd725bfa8f807d5b8979e44968ababed..d0db5effd9851a8ee609774b97ef96d05c8bfeb5 100644 --- a/examples/VideoTemplate_pyside.py +++ b/examples/VideoTemplate_pyside.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file './examples/VideoTemplate.ui' +# Form implementation generated from reading ui file './VideoTemplate.ui' # -# Created: Sun Nov 4 18:24:21 2012 -# by: pyside-uic 0.2.13 running on PySide 1.1.0 +# Created: Tue Jul 9 23:38:19 2013 +# by: pyside-uic 0.2.13 running on PySide 1.1.2 # # WARNING! All changes made in this file will be lost! @@ -12,31 +12,55 @@ from PySide import QtCore, QtGui class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(985, 674) + MainWindow.resize(695, 798) self.centralwidget = QtGui.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget) self.gridLayout_2.setObjectName("gridLayout_2") self.gridLayout = QtGui.QGridLayout() self.gridLayout.setObjectName("gridLayout") - self.rawImg = RawImageWidget(self.centralwidget) + self.rawRadio = QtGui.QRadioButton(self.centralwidget) + self.rawRadio.setChecked(True) + self.rawRadio.setObjectName("rawRadio") + self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) + self.gfxRadio = QtGui.QRadioButton(self.centralwidget) + self.gfxRadio.setObjectName("gfxRadio") + self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) + self.stack = QtGui.QStackedWidget(self.centralwidget) + self.stack.setObjectName("stack") + self.page = QtGui.QWidget() + self.page.setObjectName("page") + self.gridLayout_3 = QtGui.QGridLayout(self.page) + self.gridLayout_3.setObjectName("gridLayout_3") + self.graphicsView = GraphicsView(self.page) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) + self.stack.addWidget(self.page) + self.page_2 = QtGui.QWidget() + self.page_2.setObjectName("page_2") + self.gridLayout_4 = QtGui.QGridLayout(self.page_2) + self.gridLayout_4.setObjectName("gridLayout_4") + self.rawImg = RawImageWidget(self.page_2) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) self.rawImg.setSizePolicy(sizePolicy) self.rawImg.setObjectName("rawImg") - self.gridLayout.addWidget(self.rawImg, 0, 0, 1, 1) - self.graphicsView = GraphicsView(self.centralwidget) - self.graphicsView.setObjectName("graphicsView") - self.gridLayout.addWidget(self.graphicsView, 0, 1, 1, 1) - self.rawRadio = QtGui.QRadioButton(self.centralwidget) - self.rawRadio.setChecked(True) - self.rawRadio.setObjectName("rawRadio") - self.gridLayout.addWidget(self.rawRadio, 1, 0, 1, 1) - self.gfxRadio = QtGui.QRadioButton(self.centralwidget) - self.gfxRadio.setObjectName("gfxRadio") - self.gridLayout.addWidget(self.gfxRadio, 1, 1, 1, 1) + self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) + self.stack.addWidget(self.page_2) + self.page_3 = QtGui.QWidget() + self.page_3.setObjectName("page_3") + self.gridLayout_5 = QtGui.QGridLayout(self.page_3) + self.gridLayout_5.setObjectName("gridLayout_5") + self.rawGLImg = RawImageGLWidget(self.page_3) + self.rawGLImg.setObjectName("rawGLImg") + self.gridLayout_5.addWidget(self.rawGLImg, 0, 0, 1, 1) + self.stack.addWidget(self.page_3) + self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) + self.rawGLRadio = QtGui.QRadioButton(self.centralwidget) + self.rawGLRadio.setObjectName("rawGLRadio") + self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) self.label = QtGui.QLabel(self.centralwidget) self.label.setObjectName("label") @@ -125,12 +149,14 @@ class Ui_MainWindow(object): MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) + self.stack.setCurrentIndex(2) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) - self.rawRadio.setText(QtGui.QApplication.translate("MainWindow", "RawImageWidget (unscaled; faster)", None, QtGui.QApplication.UnicodeUTF8)) - self.gfxRadio.setText(QtGui.QApplication.translate("MainWindow", "GraphicsView + ImageItem (scaled; slower)", None, QtGui.QApplication.UnicodeUTF8)) + self.rawRadio.setText(QtGui.QApplication.translate("MainWindow", "RawImageWidget", None, QtGui.QApplication.UnicodeUTF8)) + self.gfxRadio.setText(QtGui.QApplication.translate("MainWindow", "GraphicsView + ImageItem", None, QtGui.QApplication.UnicodeUTF8)) + self.rawGLRadio.setText(QtGui.QApplication.translate("MainWindow", "RawGLImageWidget", None, QtGui.QApplication.UnicodeUTF8)) self.label.setText(QtGui.QApplication.translate("MainWindow", "Data type", None, QtGui.QApplication.UnicodeUTF8)) self.dtypeCombo.setItemText(0, QtGui.QApplication.translate("MainWindow", "uint8", None, QtGui.QApplication.UnicodeUTF8)) self.dtypeCombo.setItemText(1, QtGui.QApplication.translate("MainWindow", "uint16", None, QtGui.QApplication.UnicodeUTF8)) @@ -145,4 +171,5 @@ class Ui_MainWindow(object): self.fpsLabel.setText(QtGui.QApplication.translate("MainWindow", "FPS", None, QtGui.QApplication.UnicodeUTF8)) self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8)) -from pyqtgraph import SpinBox, GradientWidget, GraphicsView, RawImageWidget +from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget +from pyqtgraph import GradientWidget, SpinBox, GraphicsView, RawImageWidget diff --git a/examples/parametertree.py b/examples/parametertree.py index 4c5d7275e84d76f367a89e712360f4e8a0d34b72..c0eb50dbff3b69d59d86d2ca4ee08339552d4139 100644 --- a/examples/parametertree.py +++ b/examples/parametertree.py @@ -67,7 +67,7 @@ params = [ {'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1}, {'name': 'String', 'type': 'str', 'value': "hi"}, {'name': 'List', 'type': 'list', 'values': [1,2,3], 'value': 2}, - {'name': 'Named List', 'type': 'list', 'values': {"one": 1, "two": 2, "three": 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'}, @@ -139,14 +139,19 @@ p.param('Save/Restore functionality', 'Restore State').sigActivated.connect(rest ## Create two ParameterTree widgets, both accessing the same data t = ParameterTree() t.setParameters(p, showTop=False) -t.show() t.setWindowTitle('pyqtgraph example: Parameter Tree') -t.resize(400,800) t2 = ParameterTree() t2.setParameters(p, showTop=False) -t2.show() -t2.resize(400,800) - + +win = QtGui.QWidget() +layout = QtGui.QGridLayout() +win.setLayout(layout) +layout.addWidget(QtGui.QLabel("These are two views of the same data. They should always display the same values."), 0, 0, 1, 2) +layout.addWidget(t, 1, 0, 1, 1) +layout.addWidget(t2, 1, 1, 1, 1) +win.show() +win.resize(800,800) + ## test save/restore s = p.saveState() p.restoreState(s) diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py index d83e0ec001b53725763c7adac4c639d3f4743cba..c1b620419a12100ebbfa9dbd0c79727715de5d44 100644 --- a/pyqtgraph/__init__.py +++ b/pyqtgraph/__init__.py @@ -255,7 +255,7 @@ def exit(): ## close file handles os.closerange(3, 4096) ## just guessing on the maximum descriptor count.. - os._exit(os.EX_OK) + os._exit(0) @@ -281,7 +281,7 @@ def plot(*args, **kargs): #if len(args)+len(kargs) > 0: #w.plot(*args, **kargs) - pwArgList = ['title', 'labels', 'name', 'left', 'right', 'top', 'bottom'] + pwArgList = ['title', 'labels', 'name', 'left', 'right', 'top', 'bottom', 'background'] pwArgs = {} dataArgs = {} for k in kargs: diff --git a/pyqtgraph/exporters/Exporter.py b/pyqtgraph/exporters/Exporter.py index 43a8c33035ce8c604c669737c5e31008d51ccdcd..6371a3b973e712c4cafee423a26d26d5cfcb6e81 100644 --- a/pyqtgraph/exporters/Exporter.py +++ b/pyqtgraph/exporters/Exporter.py @@ -119,7 +119,7 @@ class Exporter(object): else: childs = root.childItems() rootItem = [root] - childs.sort(lambda a,b: cmp(a.zValue(), b.zValue())) + childs.sort(key=lambda a: a.zValue()) while len(childs) > 0: ch = childs.pop(0) tree = self.getPaintItems(ch) diff --git a/pyqtgraph/exporters/ImageExporter.py b/pyqtgraph/exporters/ImageExporter.py index bdb8b9be184cb63331ab686aad272d8e4f3571d0..a9b44ab441d29a57fd4a5cde2e41862fe5e127f9 100644 --- a/pyqtgraph/exporters/ImageExporter.py +++ b/pyqtgraph/exporters/ImageExporter.py @@ -1,6 +1,6 @@ from .Exporter import Exporter from pyqtgraph.parametertree import Parameter -from pyqtgraph.Qt import QtGui, QtCore, QtSvg +from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE import pyqtgraph as pg import numpy as np @@ -17,7 +17,11 @@ class ImageExporter(Exporter): scene = item.scene() else: scene = item - bg = scene.views()[0].backgroundBrush().color() + bgbrush = scene.views()[0].backgroundBrush() + bg = bgbrush.color() + if bgbrush.style() == QtCore.Qt.NoBrush: + bg.setAlpha(0) + self.params = Parameter(name='params', type='group', children=[ {'name': 'width', 'type': 'int', 'value': tr.width(), 'limits': (0, None)}, {'name': 'height', 'type': 'int', 'value': tr.height(), 'limits': (0, None)}, @@ -42,7 +46,10 @@ class ImageExporter(Exporter): def export(self, fileName=None, toBytes=False, copy=False): if fileName is None and not toBytes and not copy: - filter = ["*."+str(f) for f in QtGui.QImageWriter.supportedImageFormats()] + if USE_PYSIDE: + filter = ["*."+str(f) for f in QtGui.QImageWriter.supportedImageFormats()] + else: + filter = ["*."+bytes(f).decode('utf-8') for f in QtGui.QImageWriter.supportedImageFormats()] preferred = ['*.png', '*.tif', '*.jpg'] for p in preferred[::-1]: if p in filter: diff --git a/pyqtgraph/flowchart/Flowchart.py b/pyqtgraph/flowchart/Flowchart.py index be0d86e58b2647482b63ea3d223cd49a5d3803d9..81f9e1637712c356c1af502eb9fde35c62a6dd41 100644 --- a/pyqtgraph/flowchart/Flowchart.py +++ b/pyqtgraph/flowchart/Flowchart.py @@ -376,10 +376,10 @@ class Flowchart(Node): #tdeps[t] = lastNode if lastInd is not None: dels.append((lastInd+1, t)) - dels.sort(lambda a,b: cmp(b[0], a[0])) + #dels.sort(lambda a,b: cmp(b[0], a[0])) + dels.sort(key=lambda a: a[0], reverse=True) for i, t in dels: ops.insert(i, ('d', t)) - return ops @@ -491,7 +491,8 @@ class Flowchart(Node): self.clear() Node.restoreState(self, state) nodes = state['nodes'] - nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0])) + #nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0])) + nodes.sort(key=lambda a: a['pos'][0]) for n in nodes: if n['name'] in self._nodes: #self._nodes[n['name']].graphicsItem().moveBy(*n['pos']) diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py index 881dcf2d333c0114de4aa6c17ccbe111e876997f..742c73ef885dc360fa626a6e4857a0fc8cff9842 100644 --- a/pyqtgraph/graphicsItems/PlotCurveItem.py +++ b/pyqtgraph/graphicsItems/PlotCurveItem.py @@ -261,6 +261,9 @@ class PlotCurveItem(GraphicsObject): by :func:`mkBrush <pyqtgraph.mkBrush>` is allowed. antialias (bool) Whether to use antialiasing when drawing. This is disabled by default because it decreases performance. + stepMode If True, two orthogonal lines are drawn for each sample + as steps. This is commonly used when drawing histograms. + Note that in this case, len(x) == len(y) + 1 ============== ======================================================== If non-keyword arguments are used, they will be interpreted as diff --git a/pyqtgraph/graphicsItems/PlotDataItem.py b/pyqtgraph/graphicsItems/PlotDataItem.py index 1ae528ba2f4609085ca0d6c854a7bcb415c86e7c..f9f2febeb541b00d959160a00dd210d0f662aa4c 100644 --- a/pyqtgraph/graphicsItems/PlotDataItem.py +++ b/pyqtgraph/graphicsItems/PlotDataItem.py @@ -475,7 +475,7 @@ class PlotDataItem(GraphicsObject): if self.xClean is None: nanMask = np.isnan(self.xData) | np.isnan(self.yData) | np.isinf(self.xData) | np.isinf(self.yData) - if any(nanMask): + if nanMask.any(): self.dataMask = ~nanMask self.xClean = self.xData[self.dataMask] self.yClean = self.yData[self.dataMask] @@ -495,10 +495,7 @@ class PlotDataItem(GraphicsObject): ##y = resample(y[:len(x)*ds], len(x)) ## scipy.signal.resample causes nasty ringing #y = y[::ds] if self.opts['fftMode']: - f = np.fft.fft(y) / len(y) - y = abs(f[1:len(f)/2]) - dt = x[-1] - x[0] - x = np.linspace(0, 0.5*len(x)/dt, len(y)) + x,y = self._fourierTransform(x, y) if self.opts['logMode'][0]: x = np.log10(x) if self.opts['logMode'][1]: @@ -666,8 +663,21 @@ class PlotDataItem(GraphicsObject): self.xDisp = self.yDisp = None self.updateItems() - - + def _fourierTransform(self, x, y): + ## Perform fourier transform. If x values are not sampled uniformly, + ## then use interpolate.griddata to resample before taking fft. + dx = np.diff(x) + uniform = not np.any(np.abs(dx-dx[0]) > (abs(dx[0]) / 1000.)) + if not uniform: + import scipy.interpolate as interp + x2 = np.linspace(x[0], x[-1], len(x)) + y = interp.griddata(x, y, x2, method='linear') + x = x2 + f = np.fft.fft(y) / len(y) + y = abs(f[1:len(f)/2]) + dt = x[-1] - x[0] + x = np.linspace(0, 0.5*len(x)/dt, len(y)) + return x, y def dataType(obj): if hasattr(obj, '__len__') and len(obj) == 0: diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index a5e25a2f730dbdc3a8fbddf6d2cc5e749dff090d..033aab42a8f1325e3106fc6422c785aa3cdbb15e 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -49,7 +49,13 @@ class ROI(GraphicsObject): sigRegionChanged Emitted any time the position of the ROI changes, including while it is being dragged by the user. sigHoverEvent Emitted when the mouse hovers over the ROI. - sigClicked Emitted when the user clicks on the ROI + sigClicked Emitted when the user clicks on the ROI. + Note that clicking is disabled by default to prevent + stealing clicks from objects behind the ROI. To + enable clicking, call + roi.setAcceptedMouseButtons(QtCore.Qt.LeftButton). + See QtGui.QGraphicsItem documentation for more + details. sigRemoveRequested Emitted when the user selects 'remove' from the ROI's context menu (if available). ----------------------- ---------------------------------------------------- diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index ea04bb16638b8bac8ff925a9115ed96ce8ece03b..7657a6bdc9b2bf2fb25d6554db66715bcb4c53d4 100644 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -139,6 +139,7 @@ class ViewBox(GraphicsWidget): self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1) self.rbScaleBox.setPen(fn.mkPen((255,255,100), width=1)) self.rbScaleBox.setBrush(fn.mkBrush(255,255,0,100)) + self.rbScaleBox.setZValue(1e9) self.rbScaleBox.hide() self.addItem(self.rbScaleBox, ignoreBounds=True) @@ -792,12 +793,15 @@ class ViewBox(GraphicsWidget): else: overlap = min(sg.bottom(), vg.bottom()) - max(sg.top(), vg.top()) if overlap < min(vg.height()/3, sg.height()/3): ## if less than 1/3 of views overlap, - ## then just replicate the view + ## then just replicate the view y1 = vr.top() y2 = vr.bottom() else: ## views overlap; line them up upp = float(vr.height()) / vg.height() - y2 = vr.bottom() - (sg.y()-vg.y()) * upp + if self.yInverted(): + y2 = vr.bottom() + (sg.bottom()-vg.bottom()) * upp + else: + y2 = vr.bottom() + (sg.top()-vg.top()) * upp y1 = y2 - sg.height() * upp self.enableAutoRange(ViewBox.YAxis, False) self.setYRange(y1, y2, padding=0) diff --git a/pyqtgraph/multiprocess/processes.py b/pyqtgraph/multiprocess/processes.py index 2b345e8b3cbacb859854d6a7dfb174ced2579048..7d147a1d8629ebec28164150230dff3ed66acf6a 100644 --- a/pyqtgraph/multiprocess/processes.py +++ b/pyqtgraph/multiprocess/processes.py @@ -325,7 +325,8 @@ class QtProcess(Process): GUI. - A QTimer is also started on the parent process which polls for requests from the child process. This allows Qt signals emitted within the child - process to invoke slots on the parent process and vice-versa. + process to invoke slots on the parent process and vice-versa. This can + be disabled using processRequests=False in the constructor. Example:: @@ -342,18 +343,29 @@ class QtProcess(Process): def __init__(self, **kwds): if 'target' not in kwds: kwds['target'] = startQtEventLoop + self._processRequests = kwds.pop('processRequests', True) Process.__init__(self, **kwds) self.startEventTimer() def startEventTimer(self): from pyqtgraph.Qt import QtGui, QtCore ## avoid module-level import to keep bootstrap snappy. self.timer = QtCore.QTimer() - app = QtGui.QApplication.instance() - if app is None: - raise Exception("Must create QApplication before starting QtProcess") + if self._processRequests: + app = QtGui.QApplication.instance() + if app is None: + raise Exception("Must create QApplication before starting QtProcess, or use QtProcess(processRequests=False)") + self.startRequestProcessing() + + def startRequestProcessing(self, interval=0.01): + """Start listening for requests coming from the child process. + This allows signals to be connected from the child process to the parent. + """ self.timer.timeout.connect(self.processRequests) - self.timer.start(10) + self.timer.start(interval*1000) + def stopRequestProcessing(self): + self.timer.stop() + def processRequests(self): try: Process.processRequests(self) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 33e9d2fc7e1bf37352c61090ac9ee71eb90caf5b..89fef92ed6a8edc35eccfeb07df8afde0483cb99 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -167,7 +167,7 @@ class GLViewWidget(QtOpenGL.QGLWidget): else: items = item.childItems() items.append(item) - items.sort(key=lambda x: x.depthValue()) + items.sort(key=lambda a: a.depthValue()) for i in items: if not i.visible(): continue diff --git a/pyqtgraph/opengl/items/GLLinePlotItem.py b/pyqtgraph/opengl/items/GLLinePlotItem.py index 888af6643d66bd5d125e888b883357d91de7da84..23d227c9dbbe12279b93dc11945ba5059a22b8f6 100644 --- a/pyqtgraph/opengl/items/GLLinePlotItem.py +++ b/pyqtgraph/opengl/items/GLLinePlotItem.py @@ -30,8 +30,9 @@ class GLLinePlotItem(GLGraphicsItem): Arguments: ------------------------------------------------------------------------ pos (N,3) array of floats specifying point locations. - color tuple of floats (0.0-1.0) specifying - a color for the entire item. + color (N,4) array of floats (0.0-1.0) or + tuple of floats specifying + a single color for the entire item. width float specifying line width antialias enables smooth line drawing ==================== ================================================== @@ -71,9 +72,18 @@ class GLLinePlotItem(GLGraphicsItem): self.setupGLState() glEnableClientState(GL_VERTEX_ARRAY) + try: glVertexPointerf(self.pos) - glColor4f(*self.color) + + if isinstance(self.color, np.ndarray): + glEnableClientState(GL_COLOR_ARRAY) + glColorPointerf(self.color) + else: + if isinstance(self.color, QtGui.QColor): + glColor4f(*fn.glColor(self.color)) + else: + glColor4f(*self.color) glLineWidth(self.width) #glPointSize(self.width) @@ -85,6 +95,7 @@ class GLLinePlotItem(GLGraphicsItem): glDrawArrays(GL_LINE_STRIP, 0, int(self.pos.size / self.pos.shape[-1])) finally: + glDisableClientState(GL_COLOR_ARRAY) glDisableClientState(GL_VERTEX_ARRAY) diff --git a/pyqtgraph/opengl/shaders.py b/pyqtgraph/opengl/shaders.py index e8ca28d994740713df18c96e3b8fef8e18b3cd9e..515de33a0e4a0edfdea9431bebe7e7931691cae7 100644 --- a/pyqtgraph/opengl/shaders.py +++ b/pyqtgraph/opengl/shaders.py @@ -1,3 +1,4 @@ +import OpenGL from OpenGL.GL import * from OpenGL.GL import shaders import re @@ -218,6 +219,8 @@ class Shader(object): if self.compiled is None: try: self.compiled = shaders.compileShader(self.code, self.shaderType) + except OpenGL.NullFunctionError: + raise Exception("This OpenGL implementation does not support shader programs; many features on pyqtgraph will not work.") except RuntimeError as exc: ## Format compile errors a bit more nicely if len(exc.args) == 3: diff --git a/pyqtgraph/parametertree/ParameterTree.py b/pyqtgraph/parametertree/ParameterTree.py index e57430eaf9497123abc8030a999f04ef040cf188..866875e53642551ba76d6fce250746424472a46b 100644 --- a/pyqtgraph/parametertree/ParameterTree.py +++ b/pyqtgraph/parametertree/ParameterTree.py @@ -1,6 +1,7 @@ from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.widgets.TreeWidget import TreeWidget import os, weakref, re +from .ParameterItem import ParameterItem #import functions as fn @@ -103,7 +104,7 @@ class ParameterTree(TreeWidget): sel = self.selectedItems() if len(sel) != 1: sel = None - if self.lastSel is not None: + if self.lastSel is not None and isinstance(self.lastSel, ParameterItem): self.lastSel.selected(False) if sel is None: self.lastSel = None diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 28e1e618cb6c97e902cdd97f59d866e0a72092b0..3300171fcd77d71dd5916ba1f46ee132aa846760 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -476,32 +476,16 @@ class ListParameterItem(WidgetParameterItem): return w def value(self): - #vals = self.param.opts['limits'] key = asUnicode(self.widget.currentText()) - #if isinstance(vals, dict): - #return vals[key] - #else: - #return key - #print key, self.forward return self.forward.get(key, None) def setValue(self, val): - #vals = self.param.opts['limits'] - #if isinstance(vals, dict): - #key = None - #for k,v in vals.iteritems(): - #if v == val: - #key = k - #if key is None: - #raise Exception("Value '%s' not allowed." % val) - #else: - #key = unicode(val) self.targetValue = val - if val not in self.reverse: + if val not in self.reverse[0]: self.widget.setCurrentIndex(0) else: - key = self.reverse[val] + key = self.reverse[1][self.reverse[0].index(val)] ind = self.widget.findText(key) self.widget.setCurrentIndex(ind) @@ -531,8 +515,8 @@ class ListParameter(Parameter): itemClass = ListParameterItem def __init__(self, **opts): - self.forward = OrderedDict() ## name: value - self.reverse = OrderedDict() ## value: name + self.forward = OrderedDict() ## {name: value, ...} + self.reverse = ([], []) ## ([value, ...], [name, ...]) ## Parameter uses 'limits' option to define the set of allowed values if 'values' in opts: @@ -547,23 +531,40 @@ class ListParameter(Parameter): Parameter.setLimits(self, limits) #print self.name(), self.value(), limits - if self.value() not in self.reverse and len(self.reverse) > 0: - self.setValue(list(self.reverse.keys())[0]) + if len(self.reverse) > 0 and self.value() not in self.reverse[0]: + self.setValue(self.reverse[0][0]) + + #def addItem(self, name, value=None): + #if name in self.forward: + #raise Exception("Name '%s' is already in use for this parameter" % name) + #limits = self.opts['limits'] + #if isinstance(limits, dict): + #limits = limits.copy() + #limits[name] = value + #self.setLimits(limits) + #else: + #if value is not None: + #raise Exception ## raise exception or convert to dict? + #limits = limits[:] + #limits.append(name) + ## what if limits == None? @staticmethod def mapping(limits): - ## Return forward and reverse mapping dictionaries given a limit specification - forward = OrderedDict() ## name: value - reverse = OrderedDict() ## value: name + ## Return forward and reverse mapping objects given a limit specification + forward = OrderedDict() ## {name: value, ...} + reverse = ([], []) ## ([value, ...], [name, ...]) if isinstance(limits, dict): for k, v in limits.items(): forward[k] = v - reverse[v] = k + reverse[0].append(v) + reverse[1].append(k) else: for v in limits: n = asUnicode(v) forward[n] = v - reverse[v] = n + reverse[0].append(v) + reverse[1].append(n) return forward, reverse registerParameterType('list', ListParameter, override=True) @@ -615,13 +616,20 @@ registerParameterType('action', ActionParameter, override=True) class TextParameterItem(WidgetParameterItem): def __init__(self, param, depth): WidgetParameterItem.__init__(self, param, depth) + self.hideWidget = False self.subItem = QtGui.QTreeWidgetItem() self.addChild(self.subItem) def treeWidgetChanged(self): + ## TODO: fix so that superclass method can be called + ## (WidgetParameter should just natively support this style) + #WidgetParameterItem.treeWidgetChanged(self) self.treeWidget().setFirstItemColumnSpanned(self.subItem, True) self.treeWidget().setItemWidget(self.subItem, 0, self.textBox) - self.setExpanded(True) + + # for now, these are copied from ParameterItem.treeWidgetChanged + self.setHidden(not self.param.opts.get('visible', True)) + self.setExpanded(self.param.opts.get('expanded', True)) def makeWidget(self): self.textBox = QtGui.QTextEdit() diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py index 6ddfe93033f132767563049048b2809f9a59276b..0c8921f68503415d0cde88f35def173ce6439e7d 100644 --- a/pyqtgraph/widgets/GraphicsView.py +++ b/pyqtgraph/widgets/GraphicsView.py @@ -82,6 +82,7 @@ class GraphicsView(QtGui.QGraphicsView): ## This might help, but it's probably dangerous in the general case.. #self.setOptimizationFlag(self.DontSavePainterState, True) + self.setBackgroundRole(QtGui.QPalette.NoRole) self.setBackground(background) self.setFocusPolicy(QtCore.Qt.StrongFocus) @@ -138,12 +139,9 @@ class GraphicsView(QtGui.QGraphicsView): self._background = background if background == 'default': background = pyqtgraph.getConfigOption('background') - if background is None: - self.setBackgroundRole(QtGui.QPalette.NoRole) - else: - brush = fn.mkBrush(background) - self.setBackgroundBrush(brush) - + brush = fn.mkBrush(background) + self.setBackgroundBrush(brush) + def paintEvent(self, ev): self.scene().prepareForPaint() #print "GV: paint", ev.rect() diff --git a/pyqtgraph/widgets/PlotWidget.py b/pyqtgraph/widgets/PlotWidget.py index 1fa07f2ab4c26fd34ca7cf0229de14cc2b9e0ff9..7b3c685c8ac8406a3884c244834d7388f2c638ea 100644 --- a/pyqtgraph/widgets/PlotWidget.py +++ b/pyqtgraph/widgets/PlotWidget.py @@ -40,10 +40,12 @@ class PlotWidget(GraphicsView): For all other methods, use :func:`getPlotItem <pyqtgraph.PlotWidget.getPlotItem>`. """ - def __init__(self, parent=None, **kargs): - """When initializing PlotWidget, all keyword arguments except *parent* are passed + def __init__(self, parent=None, background='default', **kargs): + """When initializing PlotWidget, *parent* and *background* are passed to + :func:`GraphicsWidget.__init__() <pyqtgraph.GraphicsWidget.__init__>` + and all others are passed to :func:`PlotItem.__init__() <pyqtgraph.PlotItem.__init__>`.""" - GraphicsView.__init__(self, parent) + GraphicsView.__init__(self, parent, background=background) self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.enableMouse(False) self.plotItem = PlotItem(**kargs) diff --git a/pyqtgraph/widgets/RawImageWidget.py b/pyqtgraph/widgets/RawImageWidget.py index ea5c98a012efdbc3d31f64179c1f508b5135196a..a780f4633780f715b1b43d944d7e7b701d135831 100644 --- a/pyqtgraph/widgets/RawImageWidget.py +++ b/pyqtgraph/widgets/RawImageWidget.py @@ -11,8 +11,8 @@ import numpy as np class RawImageWidget(QtGui.QWidget): """ Widget optimized for very fast video display. - Generally using an ImageItem inside GraphicsView is fast enough, - but if you need even more performance, this widget is about as fast as it gets (but only in unscaled mode). + Generally using an ImageItem inside GraphicsView is fast enough. + On some systems this may provide faster video. See the VideoSpeedTest example for benchmarking. """ def __init__(self, parent=None, scaled=False): """ @@ -59,26 +59,82 @@ class RawImageWidget(QtGui.QWidget): p.end() if HAVE_OPENGL: + from OpenGL.GL import * class RawImageGLWidget(QtOpenGL.QGLWidget): """ Similar to RawImageWidget, but uses a GL widget to do all drawing. - Generally this will be about as fast as using GraphicsView + ImageItem, - but performance may vary on some platforms. + Perfomance varies between platforms; see examples/VideoSpeedTest for benchmarking. """ def __init__(self, parent=None, scaled=False): QtOpenGL.QGLWidget.__init__(self, parent=None) self.scaled = scaled self.image = None + self.uploaded = False + self.smooth = False + self.opts = None - def setImage(self, img): - self.image = fn.makeQImage(img) + def setImage(self, img, *args, **kargs): + """ + img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). + Extra arguments are sent to functions.makeARGB + """ + self.opts = (img, args, kargs) + self.image = None + self.uploaded = False self.update() - def paintEvent(self, ev): + def initializeGL(self): + self.texture = glGenTextures(1) + + def uploadTexture(self): + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.texture) + if self.smooth: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + else: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) + #glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) + shape = self.image.shape + + ### Test texture dimensions first + #glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) + #if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: + #raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.image.transpose((1,0,2))) + glDisable(GL_TEXTURE_2D) + + def paintGL(self): if self.image is None: - return - p = QtGui.QPainter(self) - p.drawImage(self.rect(), self.image) - p.end() + if self.opts is None: + return + img, args, kwds = self.opts + kwds['useRGBA'] = True + self.image, alpha = fn.makeARGB(img, *args, **kwds) + + if not self.uploaded: + self.uploadTexture() + + glViewport(0, 0, self.width(), self.height()) + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.texture) + glColor4f(1,1,1,1) + + glBegin(GL_QUADS) + glTexCoord2f(0,0) + glVertex3f(-1,-1,0) + glTexCoord2f(1,0) + glVertex3f(1, -1, 0) + glTexCoord2f(1,1) + glVertex3f(1, 1, 0) + glTexCoord2f(0,1) + glVertex3f(-1, 1, 0) + glEnd() + glDisable(GL_TEXTURE_3D) +