From 7cd3e663f9f686bcd4d68880593b72c8dd6a1b24 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 10 Jul 2013 00:02:16 -0400 Subject: [PATCH 01/14] experimental GL video widget temporary fix for text parameter ignoring expanded option Don't use os.EX_OK in pg.exit() --- examples/VideoSpeedTest.py | 5 ++ examples/VideoTemplate.ui | 73 +++++++++++++++------ examples/VideoTemplate_pyqt.py | 65 +++++++++++++------ examples/VideoTemplate_pyside.py | 65 +++++++++++++------ examples/parametertree.py | 15 +++-- pyqtgraph/__init__.py | 2 +- pyqtgraph/graphicsItems/ROI.py | 8 ++- pyqtgraph/parametertree/parameterTypes.py | 8 ++- pyqtgraph/widgets/RawImageWidget.py | 78 +++++++++++++++++++---- 9 files changed, 243 insertions(+), 76 deletions(-) diff --git a/examples/VideoSpeedTest.py b/examples/VideoSpeedTest.py index dd392189..d7a4e1e0 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 3dddb928..d73b0dc9 100644 --- a/examples/VideoTemplate.ui +++ b/examples/VideoTemplate.ui @@ -6,8 +6,8 @@ 0 0 - 985 - 674 + 695 + 798 @@ -17,33 +17,62 @@ - - - - - 0 - 0 - - - - - - - - + - RawImageWidget (unscaled; faster) + RawImageWidget true - + - GraphicsView + ImageItem (scaled; slower) + GraphicsView + ImageItem + + + + + + + 2 + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + RawGLImageWidget @@ -250,6 +279,12 @@ QDoubleSpinBox
pyqtgraph
+ + RawImageGLWidget + QWidget +
pyqtgraph.widgets.RawImageWidget
+ 1 +
diff --git a/examples/VideoTemplate_pyqt.py b/examples/VideoTemplate_pyqt.py index c3430e2d..f61a5e46 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 d19e0f23..d0db5eff 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 4c5d7275..c600d1be 100644 --- a/examples/parametertree.py +++ b/examples/parametertree.py @@ -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 d83e0ec0..b1a05835 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) diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index a5e25a2f..033aab42 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/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 28e1e618..51f0be64 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -619,9 +619,15 @@ class TextParameterItem(WidgetParameterItem): 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/RawImageWidget.py b/pyqtgraph/widgets/RawImageWidget.py index ea5c98a0..a780f463 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) + From 5a2b9462055fefda8faaac5eb139d62ec09bc21f Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 10 Jul 2013 14:30:16 -0400 Subject: [PATCH 02/14] ViewBox bugfixes: - drag rect now has large ZValue - fixed view linking with inverted y axis --- pyqtgraph/graphicsItems/ViewBox/ViewBox.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index ea04bb16..7657a6bd 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) From 46901ae83ae4ede0fa4bcd0db58404f37eccec2a Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 12 Jul 2013 13:14:09 -0400 Subject: [PATCH 03/14] ListParameter bugfix: allow unhashable types as parameter values. --- examples/parametertree.py | 2 +- pyqtgraph/parametertree/parameterTypes.py | 55 ++++++++++++----------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/examples/parametertree.py b/examples/parametertree.py index c600d1be..c0eb50db 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'}, diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 51f0be64..c3a9420e 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) From 6131427deae168419a31e5c30571ae234365e495 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Sat, 13 Jul 2013 16:06:48 -0400 Subject: [PATCH 04/14] added error message when GL shaders are not available --- pyqtgraph/opengl/shaders.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyqtgraph/opengl/shaders.py b/pyqtgraph/opengl/shaders.py index b1652850..5ef20776 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: From 3eeffd3b1dc187234bf3c4a467fc03381aa85077 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Sat, 13 Jul 2013 16:42:36 -0400 Subject: [PATCH 05/14] GLLinePLotItem accepts array of colors (thanks Felix!) --- README.txt | 1 + pyqtgraph/opengl/items/GLLinePlotItem.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.txt b/README.txt index d209ef01..85e2b24a 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/pyqtgraph/opengl/items/GLLinePlotItem.py b/pyqtgraph/opengl/items/GLLinePlotItem.py index bb5ce2f6..75d48c86 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, self.pos.size / self.pos.shape[-1]) finally: + glDisableClientState(GL_COLOR_ARRAY) glDisableClientState(GL_VERTEX_ARRAY) From ef8c47e8c84f5e5c6cb7c43c81fd3224584ffc52 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Sun, 4 Aug 2013 14:35:28 -0400 Subject: [PATCH 06/14] Allow QtProcess without local QApplication --- pyqtgraph/multiprocess/processes.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pyqtgraph/multiprocess/processes.py b/pyqtgraph/multiprocess/processes.py index 2b345e8b..7d147a1d 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) From 79bd7ea187a3b65d16c5ce3a9e08b5b932318ed1 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Sun, 4 Aug 2013 14:36:14 -0400 Subject: [PATCH 07/14] documentation, bugfix --- pyqtgraph/graphicsItems/PlotCurveItem.py | 3 +++ pyqtgraph/graphicsItems/PlotDataItem.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py index 881dcf2d..742c73ef 100644 --- a/pyqtgraph/graphicsItems/PlotCurveItem.py +++ b/pyqtgraph/graphicsItems/PlotCurveItem.py @@ -261,6 +261,9 @@ class PlotCurveItem(GraphicsObject): by :func:`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 1ae528ba..f76a8b74 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] From 2095a4c8aeb3d8ba5ac45836fe0c34dfebdd1151 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 16 Aug 2013 21:28:03 -0400 Subject: [PATCH 08/14] Support for FFT with non-uniform time sampling --- pyqtgraph/graphicsItems/PlotDataItem.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pyqtgraph/graphicsItems/PlotDataItem.py b/pyqtgraph/graphicsItems/PlotDataItem.py index f76a8b74..f9f2febe 100644 --- a/pyqtgraph/graphicsItems/PlotDataItem.py +++ b/pyqtgraph/graphicsItems/PlotDataItem.py @@ -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: From 6b3cfbc6fb90b9cdf8f7bbdaaf890e0d2ddd411f Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Sun, 18 Aug 2013 23:02:01 -0400 Subject: [PATCH 09/14] Fixed parametertree selection bug --- pyqtgraph/parametertree/ParameterTree.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/parametertree/ParameterTree.py b/pyqtgraph/parametertree/ParameterTree.py index e57430ea..866875e5 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 From 160b1ee45f2625f23152b393ffd776ff1991e3dd Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 21 Aug 2013 10:40:19 -0600 Subject: [PATCH 10/14] Python3 bugfixes --- pyqtgraph/exporters/Exporter.py | 2 +- pyqtgraph/exporters/ImageExporter.py | 2 +- pyqtgraph/flowchart/Flowchart.py | 7 ++++--- pyqtgraph/opengl/GLViewWidget.py | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pyqtgraph/exporters/Exporter.py b/pyqtgraph/exporters/Exporter.py index 43a8c330..6371a3b9 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 bdb8b9be..d1d78e7d 100644 --- a/pyqtgraph/exporters/ImageExporter.py +++ b/pyqtgraph/exporters/ImageExporter.py @@ -42,7 +42,7 @@ 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()] + 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 be0d86e5..81f9e163 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/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index d8f70055..fe52065a 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(lambda a,b: cmp(a.depthValue(), b.depthValue())) + items.sort(key=lambda a: a.depthValue()) for i in items: if not i.visible(): continue From d3f56c6df3376e79e61a8ec8f62cff08d5851ce8 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Thu, 22 Aug 2013 10:02:39 -0600 Subject: [PATCH 11/14] fixed PySide bug listing image formats --- pyqtgraph/exporters/ImageExporter.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyqtgraph/exporters/ImageExporter.py b/pyqtgraph/exporters/ImageExporter.py index d1d78e7d..b14ed513 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 @@ -42,7 +42,10 @@ class ImageExporter(Exporter): def export(self, fileName=None, toBytes=False, copy=False): if fileName is None and not toBytes and not copy: - filter = ["*."+bytes(f).decode('UTF-8') 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: From 824e4b378bec6372aadc07e5aaad95b2a7f6a744 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 23 Aug 2013 22:08:32 -0600 Subject: [PATCH 12/14] Corrected behavior of GraphicsView.setBackground --- pyqtgraph/widgets/GraphicsView.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py index 6ddfe930..0c8921f6 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() From 42553854a9f4f3df9642a0dcf5abd04a459f08b4 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 23 Aug 2013 22:27:09 -0600 Subject: [PATCH 13/14] pg.plot() and pg.PlotWidget() now accept background argument ImageExporter correctly handles QBrush with style=NoBrush --- pyqtgraph/__init__.py | 2 +- pyqtgraph/exporters/ImageExporter.py | 6 +++++- pyqtgraph/widgets/PlotWidget.py | 8 +++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py index b1a05835..c1b62041 100644 --- a/pyqtgraph/__init__.py +++ b/pyqtgraph/__init__.py @@ -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/ImageExporter.py b/pyqtgraph/exporters/ImageExporter.py index b14ed513..a9b44ab4 100644 --- a/pyqtgraph/exporters/ImageExporter.py +++ b/pyqtgraph/exporters/ImageExporter.py @@ -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)}, diff --git a/pyqtgraph/widgets/PlotWidget.py b/pyqtgraph/widgets/PlotWidget.py index 1fa07f2a..7b3c685c 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 `. """ - 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__() ` + and all others are passed to :func:`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) From 91aa2f1c16fbe0075ae0fc4426e7c2a3f038c2ac Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 27 Aug 2013 12:00:26 -0600 Subject: [PATCH 14/14] fixed TextParameter editor disappearing after focus lost --- pyqtgraph/parametertree/parameterTypes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index c3a9420e..3300171f 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -616,6 +616,7 @@ 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)