From 4d846e2aadce127e21a1edeefdd4d5c9d1172994 Mon Sep 17 00:00:00 2001 From: Luke Campagnola <> Date: Tue, 14 Jun 2011 19:47:52 -0400 Subject: [PATCH] =?UTF-8?q?Updates=20merged=20from=20acq4:=20=20-=20disabl?= =?UTF-8?q?ed=20opengl=20(performance=20issues=20in=20Qt=204.7)=20=20-=20n?= =?UTF-8?q?umerous=20caching=20changes=20(disabled=20deviceCoordinateCache?= =?UTF-8?q?=20due=20to=20performance=20issues)=20=20-=20speed=20up=20loadi?= =?UTF-8?q?ng=20large=20images=20in=20ImageView=20=20-=20bugfixes=20from?= =?UTF-8?q?=20Ingo=20Bre=C3=9Fler=20=20-=20Transform=20rotation=20bugfix?= =?UTF-8?q?=20=20-=20Added=20debug=20module=20=20-=20Major=20performance?= =?UTF-8?q?=20enhancements=20for=20scatterplot,=20fixed=20point=20clicking?= =?UTF-8?q?=20issues=20=20=20=20=20=20**=20API=20change=20for=20scatterplo?= =?UTF-8?q?t=20click=20signals=20=20-=20Drawing=20on=20ImageItem=20is=20wo?= =?UTF-8?q?rking=20well=20now=20=20-=20PlotItem=20downsampling=20no=20long?= =?UTF-8?q?er=20uses=20scipy.signal.resample=20(this=20was=20creating=20ar?= =?UTF-8?q?tifacts)=20=20-=20Fixed=20ViewBox=20behavior=20when=20aspect-lo?= =?UTF-8?q?cked?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GraphicsView.py | 151 +++-- ImageView.py | 41 +- ImageViewTemplate.py | 340 +++++------ PlotItem.py | 40 +- PlotWidget.py | 2 +- Point.py | 13 +- Transform.py | 67 ++- debug.py | 882 +++++++++++++++++++++++++++++ examples/test_Arrow.py | 1 + examples/test_ImageItem.py | 22 +- examples/test_ImageView.py | 1 + examples/test_MultiPlotWidget.py | 1 + examples/test_PlotWidget.py | 2 + examples/test_ROItypes.py | 3 +- examples/test_draw.py | 1 + examples/test_scatterPlot.py | 68 ++- functions.py | 14 +- graphicsItems.py | 933 +++++++++++++++++++++---------- graphicsWindows.py | 1 + plotConfigTemplate.py | 149 ++--- widgets.py | 26 +- 21 files changed, 2098 insertions(+), 660 deletions(-) create mode 100644 debug.py diff --git a/GraphicsView.py b/GraphicsView.py index 2895d09b..9846d0c4 100644 --- a/GraphicsView.py +++ b/GraphicsView.py @@ -10,8 +10,8 @@ from PyQt4 import QtCore, QtGui, QtOpenGL, QtSvg #import time from Point import * #from vector import * -import sys -#import debug +import sys, os +import debug class GraphicsView(QtGui.QGraphicsView): @@ -19,8 +19,9 @@ class GraphicsView(QtGui.QGraphicsView): sigMouseReleased = QtCore.Signal(object) sigSceneMouseMoved = QtCore.Signal(object) #sigRegionChanged = QtCore.Signal(object) + lastFileDir = None - def __init__(self, parent=None, useOpenGL=True): + def __init__(self, parent=None, useOpenGL=False): """Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the viewed coordinate range. Also automatically creates a QGraphicsScene and a central QGraphicsWidget that is automatically scaled to the full view geometry. @@ -38,34 +39,27 @@ class GraphicsView(QtGui.QGraphicsView): useOpenGL = False self.useOpenGL(useOpenGL) - palette = QtGui.QPalette() + self.setCacheMode(self.CacheBackground) + brush = QtGui.QBrush(QtGui.QColor(0,0,0)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active,QtGui.QPalette.Base,brush) - brush = QtGui.QBrush(QtGui.QColor(0,0,0)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive,QtGui.QPalette.Base,brush) - brush = QtGui.QBrush(QtGui.QColor(244,244,244)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled,QtGui.QPalette.Base,brush) - self.setPalette(palette) - #self.setProperty("cursor",QtCore.QVariant(QtCore.Qt.ArrowCursor)) + self.setBackgroundBrush(brush) + self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setFrameShape(QtGui.QFrame.NoFrame) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor) self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) - #self.setResizeAnchor(QtGui.QGraphicsView.NoAnchor) - self.setViewportUpdateMode(QtGui.QGraphicsView.SmartViewportUpdate) - self.setSceneRect(QtCore.QRectF(-1e10, -1e10, 2e10, 2e10)) - #self.setSceneRect(1, 1, 0, 0) ## Set an empty (but non-zero) scene rect so that the view doesn't try to automatically update for us. - #self.setInteractive(False) + self.setViewportUpdateMode(QtGui.QGraphicsView.MinimalViewportUpdate) + + + #self.setSceneRect(QtCore.QRectF(-1e10, -1e10, 2e10, 2e10)) + self.lockedViewports = [] self.lastMousePos = None #self.setMouseTracking(False) self.aspectLocked = False - self.yInverted = True + #self.yInverted = True self.range = QtCore.QRectF(0, 0, 1, 1) self.autoPixelRange = True self.currentItem = None @@ -86,7 +80,7 @@ class GraphicsView(QtGui.QGraphicsView): self.clickAccepted = False #def paintEvent(self, *args): - #prof = debug.Profiler('GraphicsView.paintEvent '+str(id(self)), disabled=True) + #prof = debug.Profiler('GraphicsView.paintEvent '+str(id(self)), disabled=False) #QtGui.QGraphicsView.paintEvent(self, *args) #prof.finish() @@ -97,6 +91,7 @@ class GraphicsView(QtGui.QGraphicsView): self.currentItem = None self.sceneObj = None self.closed = True + self.setViewport(None) def useOpenGL(self, b=True): if b: @@ -116,6 +111,7 @@ class GraphicsView(QtGui.QGraphicsView): self.scene().removeItem(self.centralWidget) self.centralWidget = item self.sceneObj.addItem(item) + self.resizeEvent(None) def addItem(self, *args): return self.scene().addItem(*args) @@ -140,37 +136,43 @@ class GraphicsView(QtGui.QGraphicsView): self.updateMatrix() def updateMatrix(self, propagate=True): - #print "udpateMatrix:" - translate = Point(self.range.center()) - if self.range.width() == 0 or self.range.height() == 0: - return - scale = Point(self.size().width()/self.range.width(), self.size().height()/self.range.height()) - - m = QtGui.QTransform() - - ## First center the viewport at 0 - self.resetMatrix() - center = self.viewportTransform().inverted()[0].map(Point(self.width()/2., self.height()/2.)) - if self.yInverted: - m.translate(center.x(), center.y()) - #print " inverted; translate", center.x(), center.y() - else: - m.translate(center.x(), -center.y()) - #print " not inverted; translate", center.x(), -center.y() - - ## Now scale and translate properly + self.setSceneRect(self.range) if self.aspectLocked: - scale = Point(scale.min()) - if not self.yInverted: - scale = scale * Point(1, -1) - m.scale(scale[0], scale[1]) - #print " scale:", scale - st = translate - m.translate(-st[0], -st[1]) - #print " translate:", st - self.setTransform(m) - self.currentScale = scale - #self.emit(QtCore.SIGNAL('viewChanged'), self.range) + self.fitInView(self.range, QtCore.Qt.KeepAspectRatio) + else: + self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio) + + ##print "udpateMatrix:" + #translate = Point(self.range.center()) + #if self.range.width() == 0 or self.range.height() == 0: + #return + #scale = Point(self.size().width()/self.range.width(), self.size().height()/self.range.height()) + + #m = QtGui.QTransform() + + ### First center the viewport at 0 + #self.resetMatrix() + #center = self.viewportTransform().inverted()[0].map(Point(self.width()/2., self.height()/2.)) + #if self.yInverted: + #m.translate(center.x(), center.y()) + ##print " inverted; translate", center.x(), center.y() + #else: + #m.translate(center.x(), -center.y()) + ##print " not inverted; translate", center.x(), -center.y() + + ### Now scale and translate properly + #if self.aspectLocked: + #scale = Point(scale.min()) + #if not self.yInverted: + #scale = scale * Point(1, -1) + #m.scale(scale[0], scale[1]) + ##print " scale:", scale + #st = translate + #m.translate(-st[0], -st[1]) + ##print " translate:", st + #self.setTransform(m) + #self.currentScale = scale + ##self.emit(QtCore.SIGNAL('viewChanged'), self.range) self.sigRangeChanged.emit(self, self.range) if propagate: @@ -251,11 +253,11 @@ class GraphicsView(QtGui.QGraphicsView): r1.setBottom(r.bottom()) self.setRange(r1, padding=[0, padding], propagate=False) - def invertY(self, invert=True): - #if self.yInverted != invert: - #self.scale[1] *= -1. - self.yInverted = invert - self.updateMatrix() + #def invertY(self, invert=True): + ##if self.yInverted != invert: + ##self.scale[1] *= -1. + #self.yInverted = invert + #self.updateMatrix() def wheelEvent(self, ev): @@ -347,7 +349,7 @@ class GraphicsView(QtGui.QGraphicsView): def mouseMoveEvent(self, ev): if self.lastMousePos is None: self.lastMousePos = Point(ev.pos()) - delta = Point(ev.pos()) - self.lastMousePos + delta = Point(ev.pos() - self.lastMousePos) self.lastMousePos = Point(ev.pos()) QtGui.QGraphicsView.mouseMoveEvent(self, ev) @@ -371,7 +373,8 @@ class GraphicsView(QtGui.QGraphicsView): self.sigRangeChanged.emit(self, self.range) elif ev.buttons() in [QtCore.Qt.MidButton, QtCore.Qt.LeftButton]: ## Allow panning by left or mid button. - tr = -delta / self.currentScale + px = self.pixelSize() + tr = -delta * px self.translate(tr[0], tr[1]) #self.emit(QtCore.SIGNAL('regionChanged(QRectF)'), self.range) @@ -386,11 +389,28 @@ class GraphicsView(QtGui.QGraphicsView): #self.currentItem.mouseMoveEvent(pev) - + def pixelSize(self): + """Return vector with the length and width of one view pixel in scene coordinates""" + p0 = Point(0,0) + p1 = Point(1,1) + tr = self.transform().inverted()[0] + p01 = tr.map(p0) + p11 = tr.map(p1) + return Point(p11 - p01) + def writeSvg(self, fileName=None): if fileName is None: - fileName = str(QtGui.QFileDialog.getSaveFileName()) + self.fileDialog = QtGui.QFileDialog() + self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + if GraphicsView.lastFileDir is not None: + self.fileDialog.setDirectory(GraphicsView.lastFileDir) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.writeSvg) + return + fileName = str(fileName) + GraphicsView.lastFileDir = os.path.split(fileName)[0] self.svg = QtSvg.QSvgGenerator() self.svg.setFileName(fileName) self.svg.setSize(self.size()) @@ -400,7 +420,16 @@ class GraphicsView(QtGui.QGraphicsView): def writeImage(self, fileName=None): if fileName is None: - fileName = str(QtGui.QFileDialog.getSaveFileName()) + self.fileDialog = QtGui.QFileDialog() + self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + if GraphicsView.lastFileDir is not None: + self.fileDialog.setDirectory(GraphicsView.lastFileDir) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.writePng) + return + fileName = str(fileName) + GraphicsView.lastFileDir = os.path.split(fileName)[0] self.png = QtGui.QImage(self.size(), QtGui.QImage.Format_ARGB32) painter = QtGui.QPainter(self.png) rh = self.renderHints() diff --git a/ImageView.py b/ImageView.py index ce7a80be..3c293964 100644 --- a/ImageView.py +++ b/ImageView.py @@ -21,6 +21,7 @@ import sys #from numpy import ndarray import ptime import numpy as np +import debug from SignalProxy import proxyConnect @@ -53,7 +54,7 @@ class ImageView(QtGui.QWidget): self.ui.graphicsView.enableMouse(True) self.ui.graphicsView.autoPixelRange = False self.ui.graphicsView.setAspectLocked(True) - self.ui.graphicsView.invertY() + #self.ui.graphicsView.invertY() self.ui.graphicsView.enableMouse() self.ticks = [t[0] for t in self.ui.gradientWidget.listTicks()] @@ -209,12 +210,13 @@ class ImageView(QtGui.QWidget): key = self.keysPressed.keys()[0] if key == QtCore.Qt.Key_Right: self.play(20) - self.lastPlayTime = ptime.time() + 0.2 ## 2ms wait before start self.jumpFrames(1) + self.lastPlayTime = ptime.time() + 0.2 ## 2ms wait before start + ## This happens *after* jumpFrames, since it might take longer than 2ms elif key == QtCore.Qt.Key_Left: self.play(-20) - self.lastPlayTime = ptime.time() + 0.2 self.jumpFrames(-1) + self.lastPlayTime = ptime.time() + 0.2 elif key == QtCore.Qt.Key_Up: self.play(-100) elif key == QtCore.Qt.Key_Down: @@ -340,6 +342,8 @@ class ImageView(QtGui.QWidget): axes: {'t':0, 'x':1, 'y':2, 'c':3}; Dictionary indicating the interpretation for each axis. This is only needed to override the default guess. """ + prof = debug.Profiler('ImageView.setImage', disabled=True) + if not isinstance(img, np.ndarray): raise Exception("Image must be specified as ndarray.") self.image = img @@ -356,6 +360,7 @@ class ImageView(QtGui.QWidget): #self.ui.timeSlider.setValue(0) #self.ui.normStartSlider.setValue(0) #self.ui.timeSlider.setMaximum(img.shape[0]-1) + prof.mark('1') if axes is None: if img.ndim == 2: @@ -380,18 +385,23 @@ class ImageView(QtGui.QWidget): for x in ['t', 'x', 'y', 'c']: self.axes[x] = self.axes.get(x, None) + prof.mark('2') self.imageDisp = None - if autoLevels: + + + if levels is None and autoLevels: self.autoLevels() - if levels is not None: + if levels is not None: ## this does nothing since getProcessedImage sets these values again. self.levelMax = levels[1] self.levelMin = levels[0] + prof.mark('3') self.currentIndex = 0 self.updateImage() if self.ui.roiBtn.isChecked(): self.roiChanged() + prof.mark('4') if self.axes['t'] is not None: @@ -412,17 +422,20 @@ class ImageView(QtGui.QWidget): s.setBounds([start, stop]) #else: #self.ui.roiPlot.hide() + prof.mark('5') self.imageItem.resetTransform() if scale is not None: self.imageItem.scale(*scale) - if scale is not None: + if pos is not None: self.imageItem.setPos(*pos) + prof.mark('6') if autoRange: self.autoRange() self.roiClicked() - + prof.mark('7') + prof.finish() def autoLevels(self): image = self.getProcessedImage() @@ -445,10 +458,18 @@ class ImageView(QtGui.QWidget): if self.imageDisp is None: image = self.normalize(self.image) self.imageDisp = image - self.levelMax = float(image.max()) - self.levelMin = float(image.min()) + self.levelMin, self.levelMax = map(float, ImageView.quickMinMax(self.imageDisp)) return self.imageDisp - + + @staticmethod + def quickMinMax(data): + while data.size > 1e6: + ax = np.argmax(data.shape) + sl = [slice(None)] * data.ndim + sl[ax] = slice(None, None, 2) + data = data[sl] + return data.min(), data.max() + def normalize(self, image): if self.ui.normOffRadio.isChecked(): diff --git a/ImageViewTemplate.py b/ImageViewTemplate.py index 6e2cdc9f..fe283a74 100644 --- a/ImageViewTemplate.py +++ b/ImageViewTemplate.py @@ -1,167 +1,173 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'ImageViewTemplate.ui' -# -# Created: Sat Jul 17 13:05:44 2010 -# by: PyQt4 UI code generator 4.5.4 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(726, 588) - self.verticalLayout = QtGui.QVBoxLayout(Form) - self.verticalLayout.setSpacing(0) - self.verticalLayout.setMargin(0) - self.verticalLayout.setObjectName("verticalLayout") - self.splitter = QtGui.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Vertical) - self.splitter.setObjectName("splitter") - self.layoutWidget = QtGui.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.gridLayout = QtGui.QGridLayout(self.layoutWidget) - self.gridLayout.setMargin(0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.graphicsView = GraphicsView(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(10) - sizePolicy.setVerticalStretch(10) - sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth()) - self.graphicsView.setSizePolicy(sizePolicy) - self.graphicsView.setObjectName("graphicsView") - self.gridLayout.addWidget(self.graphicsView, 1, 0, 3, 1) - self.roiBtn = QtGui.QPushButton(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) - self.roiBtn.setSizePolicy(sizePolicy) - self.roiBtn.setMaximumSize(QtCore.QSize(30, 16777215)) - self.roiBtn.setCheckable(True) - self.roiBtn.setObjectName("roiBtn") - self.gridLayout.addWidget(self.roiBtn, 3, 3, 1, 1) - self.gradientWidget = GradientWidget(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(100) - sizePolicy.setHeightForWidth(self.gradientWidget.sizePolicy().hasHeightForWidth()) - self.gradientWidget.setSizePolicy(sizePolicy) - self.gradientWidget.setObjectName("gradientWidget") - self.gridLayout.addWidget(self.gradientWidget, 1, 3, 1, 1) - self.normBtn = QtGui.QPushButton(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.normBtn.sizePolicy().hasHeightForWidth()) - self.normBtn.setSizePolicy(sizePolicy) - self.normBtn.setMaximumSize(QtCore.QSize(30, 16777215)) - self.normBtn.setCheckable(True) - self.normBtn.setObjectName("normBtn") - self.gridLayout.addWidget(self.normBtn, 2, 3, 1, 1) - self.normGroup = QtGui.QGroupBox(self.layoutWidget) - self.normGroup.setObjectName("normGroup") - self.gridLayout_2 = QtGui.QGridLayout(self.normGroup) - self.gridLayout_2.setMargin(0) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.normSubtractRadio = QtGui.QRadioButton(self.normGroup) - self.normSubtractRadio.setObjectName("normSubtractRadio") - self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) - self.normDivideRadio = QtGui.QRadioButton(self.normGroup) - self.normDivideRadio.setChecked(False) - self.normDivideRadio.setObjectName("normDivideRadio") - self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) - self.label_5 = QtGui.QLabel(self.normGroup) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.label_5.setFont(font) - self.label_5.setObjectName("label_5") - self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) - self.label_3 = QtGui.QLabel(self.normGroup) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.label_3.setFont(font) - self.label_3.setObjectName("label_3") - self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) - self.label_4 = QtGui.QLabel(self.normGroup) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.label_4.setFont(font) - self.label_4.setObjectName("label_4") - self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) - self.normROICheck = QtGui.QCheckBox(self.normGroup) - self.normROICheck.setObjectName("normROICheck") - self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) - self.normXBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) - self.normXBlurSpin.setObjectName("normXBlurSpin") - self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) - self.label_8 = QtGui.QLabel(self.normGroup) - self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_8.setObjectName("label_8") - self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) - self.label_9 = QtGui.QLabel(self.normGroup) - self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_9.setObjectName("label_9") - self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) - self.normYBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) - self.normYBlurSpin.setObjectName("normYBlurSpin") - self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) - self.label_10 = QtGui.QLabel(self.normGroup) - self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_10.setObjectName("label_10") - self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) - self.normOffRadio = QtGui.QRadioButton(self.normGroup) - self.normOffRadio.setChecked(True) - self.normOffRadio.setObjectName("normOffRadio") - self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) - self.normTimeRangeCheck = QtGui.QCheckBox(self.normGroup) - self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") - self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) - self.normFrameCheck = QtGui.QCheckBox(self.normGroup) - self.normFrameCheck.setObjectName("normFrameCheck") - self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) - self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) - self.normTBlurSpin.setObjectName("normTBlurSpin") - self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) - self.gridLayout.addWidget(self.normGroup, 0, 0, 1, 4) - self.roiPlot = PlotWidget(self.splitter) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) - self.roiPlot.setSizePolicy(sizePolicy) - self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) - self.roiPlot.setObjectName("roiPlot") - self.verticalLayout.addWidget(self.splitter) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.roiBtn.setText(QtGui.QApplication.translate("Form", "R", None, QtGui.QApplication.UnicodeUTF8)) - self.normBtn.setText(QtGui.QApplication.translate("Form", "N", None, QtGui.QApplication.UnicodeUTF8)) - self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8)) - self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8)) - self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8)) - self.label_5.setText(QtGui.QApplication.translate("Form", "Operation:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_3.setText(QtGui.QApplication.translate("Form", "Mean:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_4.setText(QtGui.QApplication.translate("Form", "Blur:", None, QtGui.QApplication.UnicodeUTF8)) - self.normROICheck.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) - self.label_8.setText(QtGui.QApplication.translate("Form", "X", None, QtGui.QApplication.UnicodeUTF8)) - self.label_9.setText(QtGui.QApplication.translate("Form", "Y", None, QtGui.QApplication.UnicodeUTF8)) - self.label_10.setText(QtGui.QApplication.translate("Form", "T", None, QtGui.QApplication.UnicodeUTF8)) - self.normOffRadio.setText(QtGui.QApplication.translate("Form", "Off", None, QtGui.QApplication.UnicodeUTF8)) - self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8)) - self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8)) - -from GraphicsView import GraphicsView -from pyqtgraph.GradientWidget import GradientWidget -from PlotWidget import PlotWidget +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './lib/util/pyqtgraph/ImageViewTemplate.ui' +# +# Created: Wed May 18 20:44:20 2011 +# by: PyQt4 UI code generator 4.8.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(726, 588) + self.verticalLayout = QtGui.QVBoxLayout(Form) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setMargin(0) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName(_fromUtf8("splitter")) + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) + self.gridLayout = QtGui.QGridLayout(self.layoutWidget) + self.gridLayout.setMargin(0) + self.gridLayout.setSpacing(0) + self.gridLayout.setMargin(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.graphicsView = GraphicsView(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(10) + sizePolicy.setVerticalStretch(10) + sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth()) + self.graphicsView.setSizePolicy(sizePolicy) + self.graphicsView.setObjectName(_fromUtf8("graphicsView")) + self.gridLayout.addWidget(self.graphicsView, 1, 0, 3, 1) + self.roiBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) + self.roiBtn.setSizePolicy(sizePolicy) + self.roiBtn.setMaximumSize(QtCore.QSize(30, 16777215)) + self.roiBtn.setCheckable(True) + self.roiBtn.setObjectName(_fromUtf8("roiBtn")) + self.gridLayout.addWidget(self.roiBtn, 3, 3, 1, 1) + self.gradientWidget = GradientWidget(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(100) + sizePolicy.setHeightForWidth(self.gradientWidget.sizePolicy().hasHeightForWidth()) + self.gradientWidget.setSizePolicy(sizePolicy) + self.gradientWidget.setObjectName(_fromUtf8("gradientWidget")) + self.gridLayout.addWidget(self.gradientWidget, 1, 3, 1, 1) + self.normBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.normBtn.sizePolicy().hasHeightForWidth()) + self.normBtn.setSizePolicy(sizePolicy) + self.normBtn.setMaximumSize(QtCore.QSize(30, 16777215)) + self.normBtn.setCheckable(True) + self.normBtn.setObjectName(_fromUtf8("normBtn")) + self.gridLayout.addWidget(self.normBtn, 2, 3, 1, 1) + self.normGroup = QtGui.QGroupBox(self.layoutWidget) + self.normGroup.setObjectName(_fromUtf8("normGroup")) + self.gridLayout_2 = QtGui.QGridLayout(self.normGroup) + self.gridLayout_2.setMargin(0) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.normSubtractRadio = QtGui.QRadioButton(self.normGroup) + self.normSubtractRadio.setObjectName(_fromUtf8("normSubtractRadio")) + self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) + self.normDivideRadio = QtGui.QRadioButton(self.normGroup) + self.normDivideRadio.setChecked(False) + self.normDivideRadio.setObjectName(_fromUtf8("normDivideRadio")) + self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) + self.label_5 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.label_5.setFont(font) + self.label_5.setObjectName(_fromUtf8("label_5")) + self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) + self.label_3 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.label_3.setFont(font) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) + self.label_4 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.label_4.setFont(font) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) + self.normROICheck = QtGui.QCheckBox(self.normGroup) + self.normROICheck.setObjectName(_fromUtf8("normROICheck")) + self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) + self.normXBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normXBlurSpin.setObjectName(_fromUtf8("normXBlurSpin")) + self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) + self.label_8 = QtGui.QLabel(self.normGroup) + self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_8.setObjectName(_fromUtf8("label_8")) + self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) + self.label_9 = QtGui.QLabel(self.normGroup) + self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_9.setObjectName(_fromUtf8("label_9")) + self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) + self.normYBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normYBlurSpin.setObjectName(_fromUtf8("normYBlurSpin")) + self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) + self.label_10 = QtGui.QLabel(self.normGroup) + self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_10.setObjectName(_fromUtf8("label_10")) + self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) + self.normOffRadio = QtGui.QRadioButton(self.normGroup) + self.normOffRadio.setChecked(True) + self.normOffRadio.setObjectName(_fromUtf8("normOffRadio")) + self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) + self.normTimeRangeCheck = QtGui.QCheckBox(self.normGroup) + self.normTimeRangeCheck.setObjectName(_fromUtf8("normTimeRangeCheck")) + self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) + self.normFrameCheck = QtGui.QCheckBox(self.normGroup) + self.normFrameCheck.setObjectName(_fromUtf8("normFrameCheck")) + self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) + self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normTBlurSpin.setObjectName(_fromUtf8("normTBlurSpin")) + self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) + self.gridLayout.addWidget(self.normGroup, 0, 0, 1, 4) + self.roiPlot = PlotWidget(self.splitter) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) + self.roiPlot.setSizePolicy(sizePolicy) + self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) + self.roiPlot.setObjectName(_fromUtf8("roiPlot")) + self.verticalLayout.addWidget(self.splitter) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.roiBtn.setText(QtGui.QApplication.translate("Form", "R", None, QtGui.QApplication.UnicodeUTF8)) + self.normBtn.setText(QtGui.QApplication.translate("Form", "N", None, QtGui.QApplication.UnicodeUTF8)) + self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8)) + self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8)) + self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8)) + self.label_5.setText(QtGui.QApplication.translate("Form", "Operation:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("Form", "Mean:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.translate("Form", "Blur:", None, QtGui.QApplication.UnicodeUTF8)) + self.normROICheck.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) + self.label_8.setText(QtGui.QApplication.translate("Form", "X", None, QtGui.QApplication.UnicodeUTF8)) + self.label_9.setText(QtGui.QApplication.translate("Form", "Y", None, QtGui.QApplication.UnicodeUTF8)) + self.label_10.setText(QtGui.QApplication.translate("Form", "T", None, QtGui.QApplication.UnicodeUTF8)) + self.normOffRadio.setText(QtGui.QApplication.translate("Form", "Off", None, QtGui.QApplication.UnicodeUTF8)) + self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8)) + self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8)) + +from GraphicsView import GraphicsView +from pyqtgraph.GradientWidget import GradientWidget +from PlotWidget import PlotWidget diff --git a/PlotItem.py b/PlotItem.py index 347e0c12..d53d2bfc 100644 --- a/PlotItem.py +++ b/PlotItem.py @@ -285,7 +285,7 @@ class PlotItem(QtGui.QGraphicsWidget): def close(self): #print "delete", self - ## All this crap is needed to avoid PySide trouble. + ## Most of this crap is needed to avoid PySide trouble. ## The problem seems to be whenever scene.clear() leads to deletion of widgets (either through proxies or qgraphicswidgets) ## the solution is to manually remove all widgets before scene.clear() is called if self.ctrlMenu is None: ## already shut down @@ -305,8 +305,10 @@ class PlotItem(QtGui.QGraphicsWidget): self.scales = None self.scene().removeItem(self.vb) self.vb = None - for i in range(self.layout.count()): - self.layout.removeAt(i) + + ## causes invalid index errors: + #for i in range(self.layout.count()): + #self.layout.removeAt(i) for p in self.proxies: try: @@ -708,6 +710,11 @@ class PlotItem(QtGui.QGraphicsWidget): return curve + def scatterPlot(self, *args, **kargs): + sp = ScatterPlotItem(*args, **kargs) + self.addDataItem(sp) + return sp + def addDataItem(self, item): self.addItem(item) self.dataItems.append(item) @@ -1075,9 +1082,9 @@ class PlotItem(QtGui.QGraphicsWidget): mode = False return mode - #def wheelEvent(self, ev): - # disables panning the whole scene by mousewheel - #ev.accept() + def wheelEvent(self, ev): + # disables default panning the whole scene by mousewheel + ev.accept() def resizeEvent(self, ev): if self.ctrlBtn is None: ## already closed down @@ -1168,16 +1175,21 @@ class PlotItem(QtGui.QGraphicsWidget): return c def saveSvgClicked(self): - self.fileDialog = QtGui.QFileDialog() + fileName = QtGui.QFileDialog.getSaveFileName() + self.writeSvg(fileName) + + ## QFileDialog seems to be broken under OSX + #self.fileDialog = QtGui.QFileDialog() + ##if PlotItem.lastFileDir is not None: + ##self.fileDialog.setDirectory(PlotItem.lastFileDir) + #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + #self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) #if PlotItem.lastFileDir is not None: #self.fileDialog.setDirectory(PlotItem.lastFileDir) - self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - if PlotItem.lastFileDir is not None: - self.fileDialog.setDirectory(PlotItem.lastFileDir) - self.fileDialog.show() - #QtCore.QObject.connect(self.fileDialog, QtCore.SIGNAL('fileSelected(const QString)'), self.writeSvg) - self.fileDialog.fileSelected.connect(self.writeSvg) + + #self.fileDialog.show() + ##QtCore.QObject.connect(self.fileDialog, QtCore.SIGNAL('fileSelected(const QString)'), self.writeSvg) + #self.fileDialog.fileSelected.connect(self.writeSvg) #def svgFileSelected(self, fileName): ##PlotWidget.lastFileDir = os.path.split(fileName)[0] diff --git a/PlotWidget.py b/PlotWidget.py index c95547e7..1254b963 100644 --- a/PlotWidget.py +++ b/PlotWidget.py @@ -11,7 +11,7 @@ import exceptions class PlotWidget(GraphicsView): - sigRangeChanged = QtCore.Signal(object, object) + #sigRangeChanged = QtCore.Signal(object, object) ## already defined in GraphicsView """Widget implementing a graphicsView with a single PlotItem inside.""" def __init__(self, parent=None, **kargs): diff --git a/Point.py b/Point.py index 48f122da..b98dfad0 100644 --- a/Point.py +++ b/Point.py @@ -88,11 +88,14 @@ class Point(QtCore.QPointF): def _math_(self, op, x): #print "point math:", op - try: - return Point(getattr(QtCore.QPointF, op)(self, x)) - except: - x = Point(x) - return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1])) + #try: + #fn = getattr(QtCore.QPointF, op) + #pt = fn(self, x) + #print fn, pt, self, x + #return Point(pt) + #except AttributeError: + x = Point(x) + return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1])) def length(self): """Returns the vector length of this Point.""" diff --git a/Transform.py b/Transform.py index 0529b89c..9938a190 100644 --- a/Transform.py +++ b/Transform.py @@ -6,7 +6,8 @@ import numpy as np class Transform(QtGui.QTransform): """Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate - This transform always has 0 shear.""" + This transform always has 0 shear. + """ def __init__(self, init=None): QtGui.QTransform.__init__(self) self.reset() @@ -41,7 +42,8 @@ class Transform(QtGui.QTransform): ## detect flipped axes if dp2.angle(dp3) > 0: - da = 180 + #da = 180 + da = 0 sy = -1.0 else: da = 0 @@ -141,13 +143,37 @@ if __name__ == '__main__': win.setCentralWidget(cw) s = QtGui.QGraphicsScene() cw.setScene(s) + win.resize(600,600) + cw.enableMouse() + cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) - b = QtGui.QGraphicsRectItem(-5, -5, 10, 10) - b.setPen(QtGui.QPen(mkPen('y'))) - t1 = QtGui.QGraphicsTextItem() - t1.setHtml('R') - s.addItem(b) - s.addItem(t1) + class Item(QtGui.QGraphicsItem): + def __init__(self): + QtGui.QGraphicsItem.__init__(self) + self.b = QtGui.QGraphicsRectItem(20, 20, 20, 20, self) + self.b.setPen(QtGui.QPen(mkPen('y'))) + self.t1 = QtGui.QGraphicsTextItem(self) + self.t1.setHtml('R') + self.t1.translate(20, 20) + self.l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0, self) + self.l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10, self) + self.l1.setPen(QtGui.QPen(mkPen('y'))) + self.l2.setPen(QtGui.QPen(mkPen('y'))) + def boundingRect(self): + return QtCore.QRectF() + def paint(self, *args): + pass + + #s.addItem(b) + #s.addItem(t1) + item = Item() + s.addItem(item) + l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0) + l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10) + l1.setPen(QtGui.QPen(mkPen('r'))) + l2.setPen(QtGui.QPen(mkPen('r'))) + s.addItem(l1) + s.addItem(l2) tr1 = Transform() tr2 = Transform() @@ -172,19 +198,26 @@ if __name__ == '__main__': tr4.rotate(30) print "tr1 * tr4 = ", tr1*tr4 - w1 = widgets.TestROI((0,0), (50, 50)) - w2 = widgets.TestROI((0,0), (150, 150)) + w1 = widgets.TestROI((19,19), (22, 22), invertible=True) + #w2 = widgets.TestROI((0,0), (150, 150)) + w1.setZValue(10) s.addItem(w1) - s.addItem(w2) + #s.addItem(w2) w1Base = w1.getState() - w2Base = w2.getState() + #w2Base = w2.getState() def update(): tr1 = w1.getGlobalTransform(w1Base) - tr2 = w2.getGlobalTransform(w2Base) - t1.setTransform(tr1 * tr2) - w1.setState(w1Base) - w1.applyGlobalTransform(tr2) + #tr2 = w2.getGlobalTransform(w2Base) + item.setTransform(tr1) + + #def update2(): + #tr1 = w1.getGlobalTransform(w1Base) + #tr2 = w2.getGlobalTransform(w2Base) + #t1.setTransform(tr1) + #w1.setState(w1Base) + #w1.applyGlobalTransform(tr2) + w1.sigRegionChanged.connect(update) - w2.sigRegionChanged.connect(update) + #w2.sigRegionChanged.connect(update2) \ No newline at end of file diff --git a/debug.py b/debug.py new file mode 100644 index 00000000..2a2157db --- /dev/null +++ b/debug.py @@ -0,0 +1,882 @@ +# -*- coding: utf-8 -*- +""" +debug.py - Functions to aid in debugging +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile +import ptime +from numpy import ndarray +from PyQt4 import QtCore, QtGui + +__ftraceDepth = 0 +def ftrace(func): + """Decorator used for marking the beginning and end of function calls. + Automatically indents nested calls. + """ + def w(*args, **kargs): + global __ftraceDepth + pfx = " " * __ftraceDepth + print pfx + func.__name__ + " start" + __ftraceDepth += 1 + try: + rv = func(*args, **kargs) + finally: + __ftraceDepth -= 1 + print pfx + func.__name__ + " done" + return rv + return w + +def getExc(indent=4, prefix='| '): + tb = traceback.format_exc() + lines = [] + for l in tb.split('\n'): + lines.append(" "*indent + prefix + l) + return '\n'.join(lines) + +def printExc(msg='', indent=4, prefix='|'): + """Print an error message followed by an indented exception backtrace + (This function is intended to be called within except: blocks)""" + exc = getExc(indent, prefix + ' ') + print "[%s] %s\n" % (time.strftime("%H:%M:%S"), msg) + print " "*indent + prefix + '='*30 + '>>' + print exc + print " "*indent + prefix + '='*30 + '<<' + +def printTrace(msg='', indent=4, prefix='|'): + """Print an error message followed by an indented stack trace""" + trace = backtrace(1) + #exc = getExc(indent, prefix + ' ') + print "[%s] %s\n" % (time.strftime("%H:%M:%S"), msg) + print " "*indent + prefix + '='*30 + '>>' + for line in trace.split('\n'): + print " "*indent + prefix + " " + line + print " "*indent + prefix + '='*30 + '<<' + + +def backtrace(skip=0): + return ''.join(traceback.format_stack()[:-(skip+1)]) + + +def listObjs(regex='Q', typ=None): + """List all objects managed by python gc with class name matching regex. + Finds 'Q...' classes by default.""" + if typ is not None: + return [x for x in gc.get_objects() if isinstance(x, typ)] + else: + return [x for x in gc.get_objects() if re.match(regex, type(x).__name__)] + + + +def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, 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 = {} + ignore[id(sys._getframe())] = None + ignore[id(path)] = None + ignore[id(seen)] = None + prefix = " "*(8-maxLen) + #print prefix + str(map(type, path)) + prefix += " " + if restart: + #gc.collect() + seen.clear() + gc.collect() + newRefs = [r for r in gc.get_referrers(endObj) if id(r) not in ignore] + ignore[id(newRefs)] = None + #fo = allFrameObjs() + #newRefs = [] + #for r in gc.get_referrers(endObj): + #try: + #if r not in fo: + #newRefs.append(r) + #except: + #newRefs.append(r) + + for r in newRefs: + #print prefix+"->"+str(type(r)) + if type(r).__name__ in ['frame', 'function', 'listiterator']: + #print prefix+" FRAME" + continue + try: + if any([r is x for x in path]): + #print prefix+" LOOP", objChainString([r]+path) + continue + except: + print r + print path + raise + if r is startObj: + refs.append([r]) + print refPathString([startObj]+path) + continue + if maxLen == 0: + #print prefix+" END:", objChainString([r]+path) + continue + ## See if we have already searched this node. + ## If not, recurse. + tree = None + try: + cache = seen[id(r)] + if cache[0] >= maxLen: + tree = cache[1] + for p in tree: + print refPathString(p+path) + except KeyError: + pass + + ignore[id(tree)] = None + if tree is None: + tree = findRefPath(startObj, r, maxLen-1, restart=False, path=[r]+path, ignore=ignore) + seen[id(r)] = [maxLen, tree] + ## integrate any returned results + if len(tree) == 0: + #print prefix+" EMPTY TREE" + continue + else: + for p in tree: + refs.append(p+[r]) + #seen[id(r)] = [maxLen, refs] + return refs + + +def objString(obj): + """Return a short but descriptive string for any object""" + try: + if type(obj) in [int, long, float]: + return str(obj) + elif isinstance(obj, dict): + if len(obj) > 5: + return "" % (",".join(obj.keys()[:5])) + else: + return "" % (",".join(obj.keys())) + elif isinstance(obj, basestring): + if len(obj) > 50: + return '"%s..."' % obj[:50] + else: + return obj[:] + elif isinstance(obj, ndarray): + return "" % (str(obj.dtype), str(obj.shape)) + elif hasattr(obj, '__len__'): + if len(obj) > 5: + return "<%s [%s,...]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj[:5]])) + else: + return "<%s [%s]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj])) + else: + return "<%s %s>" % (type(obj).__name__, obj.__class__.__name__) + except: + return str(type(obj)) + +def refPathString(chain): + """Given a list of adjacent objects in a reference path, print the 'natural' path + names (ie, attribute names, keys, and indexes) that follow from one object to the next .""" + s = objString(chain[0]) + i = 0 + while i < len(chain)-1: + #print " -> ", i + i += 1 + o1 = chain[i-1] + o2 = chain[i] + cont = False + if isinstance(o1, list) or isinstance(o1, tuple): + if any([o2 is x for x in o1]): + s += "[%d]" % o1.index(o2) + continue + #print " not list" + if isinstance(o2, dict) and hasattr(o1, '__dict__') and o2 == o1.__dict__: + i += 1 + if i >= len(chain): + s += ".__dict__" + continue + o3 = chain[i] + for k in o2: + if o2[k] is o3: + s += '.%s' % k + cont = True + continue + #print " not __dict__" + if isinstance(o1, dict): + try: + if o2 in o1: + s += "[key:%s]" % objString(o2) + continue + except TypeError: + pass + for k in o1: + if o1[k] is o2: + s += "[%s]" % objString(k) + cont = True + continue + #print " not dict" + #for k in dir(o1): ## Not safe to request attributes like this. + #if getattr(o1, k) is o2: + #s += ".%s" % k + #cont = True + #continue + #print " not attr" + if cont: + continue + s += " ? " + sys.stdout.flush() + return s + + +def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False): + """Guess how much memory an object is using""" + ignoreTypes = [types.MethodType, types.UnboundMethodType, types.BuiltinMethodType, types.FunctionType, types.BuiltinFunctionType] + ignoreRegex = re.compile('(method-wrapper|Flag|ItemChange|Option|Mode)') + + + if ignore is None: + ignore = {} + + indent = ' '*depth + + try: + hash(obj) + hsh = obj + except: + hsh = "%s:%d" % (str(type(obj)), id(obj)) + + if hsh in ignore: + return 0 + ignore[hsh] = 1 + + try: + size = sys.getsizeof(obj) + except TypeError: + size = 0 + + if isinstance(obj, ndarray): + try: + size += len(obj.data) + except: + pass + + + if recursive: + if type(obj) in [list, tuple]: + if verbose: + print indent+"list:" + for o in obj: + s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) + if verbose: + print indent+' +', s + size += s + elif isinstance(obj, dict): + if verbose: + print indent+"list:" + for k in obj: + s = objectSize(obj[k], ignore=ignore, verbose=verbose, depth=depth+1) + if verbose: + print indent+' +', k, s + size += s + #elif isinstance(obj, QtCore.QObject): + #try: + #childs = obj.children() + #if verbose: + #print indent+"Qt children:" + #for ch in childs: + #s = objectSize(obj, ignore=ignore, verbose=verbose, depth=depth+1) + #size += s + #if verbose: + #print indent + ' +', ch.objectName(), s + + #except: + #pass + #if isinstance(obj, types.InstanceType): + gc.collect() + if verbose: + print indent+'attrs:' + for k in dir(obj): + if k in ['__dict__']: + continue + o = getattr(obj, k) + if type(o) in ignoreTypes: + continue + strtyp = str(type(o)) + if ignoreRegex.search(strtyp): + continue + #if isinstance(o, types.ObjectType) and strtyp == "": + #continue + + #if verbose: + #print indent, k, '?' + refs = [r for r in gc.get_referrers(o) if type(r) != types.FrameType] + if len(refs) == 1: + s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) + size += s + if verbose: + print indent + " +", k, s + #else: + #if verbose: + #print indent + ' -', k, len(refs) + return size + +class GarbageWatcher: + """ + Convenient dictionary for holding weak references to objects. + Mainly used to check whether the objects have been collect yet or not. + + Example: + gw = GarbageWatcher() + gw['objName'] = obj + gw['objName2'] = obj2 + gw.check() + + + """ + def __init__(self): + self.objs = weakref.WeakValueDictionary() + self.allNames = [] + + def add(self, obj, name): + self.objs[name] = obj + self.allNames.append(name) + + def __setitem__(self, name, obj): + self.add(obj, name) + + def check(self): + """Print a list of all watched objects and whether they have been collected.""" + gc.collect() + dead = self.allNames[:] + alive = [] + for k in self.objs: + dead.remove(k) + alive.append(k) + print "Deleted objects:", dead + print "Live objects:", alive + + def __getitem__(self, item): + return self.objs[item] + + +class Profiler: + """Simple profiler allowing measurement of multiple time intervals. + + Example: + prof = Profiler('Function') + ... do stuff ... + prof.mark('did stuff') + ... do other stuff ... + prof.mark('did other stuff') + prof.finish() + """ + depth = 0 + + def __init__(self, msg="Profiler", disabled=False): + self.depth = Profiler.depth + Profiler.depth += 1 + + self.disabled = disabled + if disabled: + return + self.t0 = ptime.time() + self.t1 = self.t0 + self.msg = " "*self.depth + msg + print self.msg, ">>> Started" + + def mark(self, msg=''): + if self.disabled: + return + t1 = ptime.time() + print " "+self.msg, msg, "%gms" % ((t1-self.t1)*1000) + self.t1 = t1 + + def finish(self): + if self.disabled: + return + t1 = ptime.time() + print self.msg, '<<< Finished, total time:', "%gms" % ((t1-self.t0)*1000) + + def __del__(self): + Profiler.depth -= 1 + + +def profile(code, name='profile_run', sort='cumulative', num=30): + """Common-use for cProfile""" + cProfile.run(code, name) + stats = pstats.Stats(name) + stats.sort_stats(sort) + stats.print_stats(num) + return stats + + + +#### Code for listing (nearly) all objects in the known universe +#### http://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects +# Recursively expand slist's objects +# into olist, using seen to track +# already processed objects. +def _getr(slist, olist, first=True): + i = 0 + for e in slist: + + oid = id(e) + typ = type(e) + if oid in olist or typ is int or typ is long: ## or e in olist: ## since we're excluding all ints, there is no longer a need to check for olist keys + continue + olist[oid] = e + if first and (i%1000) == 0: + gc.collect() + tl = gc.get_referents(e) + if tl: + _getr(tl, olist, first=False) + i += 1 +# The public function. +def get_all_objects(): + """Return a list of all live Python objects (excluding int and long), not including the list itself.""" + gc.collect() + gcl = gc.get_objects() + olist = {} + _getr(gcl, olist) + + del olist[id(olist)] + del olist[id(gcl)] + del olist[id(sys._getframe())] + return olist + + +def lookup(oid, objects=None): + """Return an object given its ID, if it exists.""" + if objects is None: + objects = get_all_objects() + return objects[oid] + + + + +class ObjTracker: + """ + Tracks all objects under the sun, reporting the changes between snapshots: what objects are created, deleted, and persistent. + This class is very useful for tracking memory leaks. The class goes to great (but not heroic) lengths to avoid tracking + its own internal objects. + + Example: + ot = ObjTracker() # takes snapshot of currently existing objects + ... do stuff ... + ot.diff() # prints lists of objects created and deleted since ot was initialized + ... do stuff ... + ot.diff() # prints lists of objects created and deleted since last call to ot.diff() + # also prints list of items that were created since initialization AND have not been deleted yet + # (if done correctly, this list can tell you about objects that were leaked) + + arrays = ot.findPersistent('ndarray') ## returns all objects matching 'ndarray' (string match, not instance checking) + ## that were considered persistent when the last diff() was run + + describeObj(arrays[0]) ## See if we can determine who has references to this array + """ + + + allObjs = {} ## keep track of all objects created and stored within class instances + allObjs[id(allObjs)] = None + + def __init__(self): + self.startRefs = {} ## list of objects that exist when the tracker is initialized {oid: weakref} + ## (If it is not possible to weakref the object, then the value is None) + self.startCount = {} + self.newRefs = {} ## list of objects that have been created since initialization + self.persistentRefs = {} ## list of objects considered 'persistent' when the last diff() was called + self.objTypes = {} + + ObjTracker.allObjs[id(self)] = None + self.objs = [self.__dict__, self.startRefs, self.startCount, self.newRefs, self.persistentRefs, self.objTypes] + self.objs.append(self.objs) + for v in self.objs: + ObjTracker.allObjs[id(v)] = None + + self.start() + + def findNew(self, regex): + """Return all objects matching regex that were considered 'new' when the last diff() was run.""" + return self.findTypes(self.newRefs, regex) + + def findPersistent(self, regex): + """Return all objects matching regex that were considered 'persistent' when the last diff() was run.""" + return self.findTypes(self.persistentRefs, regex) + + + def start(self): + """ + Remember the current set of objects as the comparison for all future calls to diff() + Called automatically on init, but can be called manually as well. + """ + refs, count, objs = self.collect() + for r in self.startRefs: + self.forgetRef(self.startRefs[r]) + self.startRefs.clear() + self.startRefs.update(refs) + for r in refs: + self.rememberRef(r) + self.startCount.clear() + self.startCount.update(count) + #self.newRefs.clear() + #self.newRefs.update(refs) + + def diff(self, **kargs): + """ + Compute all differences between the current object set and the reference set. + Print a set of reports for created, deleted, and persistent objects + """ + refs, count, objs = self.collect() ## refs contains the list of ALL objects + + ## Which refs have disappeared since call to start() (these are only displayed once, then forgotten.) + delRefs = {} + for i in self.startRefs.keys(): + if i not in refs: + delRefs[i] = self.startRefs[i] + del self.startRefs[i] + self.forgetRef(delRefs[i]) + for i in self.newRefs.keys(): + if i not in refs: + delRefs[i] = self.newRefs[i] + del self.newRefs[i] + self.forgetRef(delRefs[i]) + #print "deleted:", len(delRefs) + + ## Which refs have appeared since call to start() or diff() + persistentRefs = {} ## created since start(), but before last diff() + createRefs = {} ## created since last diff() + for o in refs: + if o not in self.startRefs: + if o not in self.newRefs: + createRefs[o] = refs[o] ## object has been created since last diff() + else: + persistentRefs[o] = refs[o] ## object has been created since start(), but before last diff() (persistent) + #print "new:", len(newRefs) + + ## self.newRefs holds the entire set of objects created since start() + for r in self.newRefs: + self.forgetRef(self.newRefs[r]) + self.newRefs.clear() + self.newRefs.update(persistentRefs) + self.newRefs.update(createRefs) + for r in self.newRefs: + self.rememberRef(self.newRefs[r]) + #print "created:", len(createRefs) + + ## self.persistentRefs holds all objects considered persistent. + self.persistentRefs.clear() + self.persistentRefs.update(persistentRefs) + + + print "----------- Count changes since start: ----------" + c1 = count.copy() + for k in self.startCount: + c1[k] = c1.get(k, 0) - self.startCount[k] + typs = c1.keys() + typs.sort(lambda a,b: cmp(c1[a], c1[b])) + for t in typs: + if c1[t] == 0: + continue + num = "%d" % c1[t] + print " " + num + " "*(10-len(num)) + str(t) + + print "----------- %d Deleted since last diff: ------------" % len(delRefs) + self.report(delRefs, objs, **kargs) + print "----------- %d Created since last diff: ------------" % len(createRefs) + self.report(createRefs, objs, **kargs) + print "----------- %d Created since start (persistent): ------------" % len(persistentRefs) + self.report(persistentRefs, objs, **kargs) + + + def __del__(self): + self.startRefs.clear() + self.startCount.clear() + self.newRefs.clear() + self.persistentRefs.clear() + + del ObjTracker.allObjs[id(self)] + for v in self.objs: + del ObjTracker.allObjs[id(v)] + + @classmethod + def isObjVar(cls, o): + return type(o) is cls or id(o) in cls.allObjs + + def collect(self): + print "Collecting list of all objects..." + gc.collect() + objs = get_all_objects() + frame = sys._getframe() + del objs[id(frame)] ## ignore the current frame + del objs[id(frame.f_code)] + + ignoreTypes = [int, long] + refs = {} + count = {} + for k in objs: + o = objs[k] + typ = type(o) + oid = id(o) + if ObjTracker.isObjVar(o) or typ in ignoreTypes: + continue + + try: + ref = weakref.ref(obj) + except: + ref = None + refs[oid] = ref + typ = type(o) + typStr = typeStr(o) + self.objTypes[oid] = typStr + ObjTracker.allObjs[id(typStr)] = None + count[typ] = count.get(typ, 0) + 1 + + print "All objects: %d Tracked objects: %d" % (len(objs), len(refs)) + return refs, count, objs + + def forgetRef(self, ref): + if ref is not None: + del ObjTracker.allObjs[id(ref)] + + def rememberRef(self, ref): + ## Record the address of the weakref object so it is not included in future object counts. + if ref is not None: + ObjTracker.allObjs[id(ref)] = None + + + def lookup(self, oid, ref, objs=None): + if ref is None or ref() is None: + try: + obj = lookup(oid, objects=objs) + except: + obj = None + else: + obj = ref() + return obj + + + def report(self, refs, allobjs=None, showIDs=False): + if allobjs is None: + allobjs = get_all_objects() + + count = {} + rev = {} + for oid in refs: + obj = self.lookup(oid, refs[oid], allobjs) + if obj is None: + typ = "[del] " + self.objTypes[oid] + else: + typ = typeStr(obj) + if typ not in rev: + rev[typ] = [] + rev[typ].append(oid) + c = count.get(typ, [0,0]) + count[typ] = [c[0]+1, c[1]+objectSize(obj)] + typs = count.keys() + typs.sort(lambda a,b: cmp(count[a][1], count[b][1])) + + for t in typs: + line = " %d\t%d\t%s" % (count[t][0], count[t][1], t) + if showIDs: + line += "\t"+",".join(map(str,rev[t])) + print line + + def findTypes(self, refs, regex): + allObjs = get_all_objects() + ids = {} + objs = [] + r = re.compile(regex) + for k in refs: + if r.search(self.objTypes[k]): + objs.append(self.lookup(k, refs[k], allObjs)) + return objs + + + + +def describeObj(obj, depth=4, path=None, ignore=None): + """ + Trace all reference paths backward, printing a list of different ways this object can be accessed. + Attempts to answer the question "who has a reference to this object" + """ + if path is None: + path = [obj] + if ignore is None: + ignore = {} ## holds IDs of objects used within the function. + ignore[id(sys._getframe())] = None + ignore[id(path)] = None + gc.collect() + refs = gc.get_referrers(obj) + ignore[id(refs)] = None + printed=False + for ref in refs: + if id(ref) in ignore: + continue + if id(ref) in map(id, path): + print "Cyclic reference: " + refPathString([ref]+path) + printed = True + continue + newPath = [ref]+path + if len(newPath) >= depth: + refStr = refPathString(newPath) + if '[_]' not in refStr: ## ignore '_' references generated by the interactive shell + print refStr + printed = True + else: + describeObj(ref, depth, newPath, ignore) + printed = True + if not printed: + print "Dead end: " + refPathString(path) + + + +def typeStr(obj): + """Create a more useful type string by making types report their class.""" + typ = type(obj) + if typ == types.InstanceType: + return "" % obj.__class__.__name__ + else: + return str(typ) + +def searchRefs(obj, *args): + """Pseudo-interactive function for tracing references backward. + Arguments: + obj: The initial object from which to start searching + args: A set of string or int arguments. + each integer selects one of obj's referrers to be the new 'obj' + each string indicates an action to take on the current 'obj': + t: print the types of obj's referrers + l: print the lengths of obj's referrers (if they have __len__) + i: print the IDs of obj's referrers + o: print obj + ro: return obj + rr: return list of obj's referrers + + Examples: + searchRefs(obj, 't') ## Print types of all objects referring to obj + searchRefs(obj, 't', 0, 't') ## ..then select the first referrer and print the types of its referrers + searchRefs(obj, 't', 0, 't', 'l') ## ..also print lengths of the last set of referrers + searchRefs(obj, 0, 1, 'ro') ## Select index 0 from obj's referrer, then select index 1 from the next set of referrers, then return that object + + """ + ignore = {id(sys._getframe()): None} + gc.collect() + refs = gc.get_referrers(obj) + ignore[id(refs)] = None + refs = [r for r in refs if id(r) not in ignore] + for a in args: + + #fo = allFrameObjs() + #refs = [r for r in refs if r not in fo] + + if type(a) is int: + obj = refs[a] + gc.collect() + refs = gc.get_referrers(obj) + ignore[id(refs)] = None + refs = [r for r in refs if id(r) not in ignore] + elif a == 't': + print map(typeStr, refs) + elif a == 'i': + print map(id, refs) + elif a == 'l': + def slen(o): + if hasattr(o, '__len__'): + return len(o) + else: + return None + print map(slen, refs) + elif a == 'o': + print obj + elif a == 'ro': + return obj + elif a == 'rr': + return refs + +def allFrameObjs(): + """Return list of frame objects in current stack. Useful if you want to ignore these objects in refernece searches""" + f = sys._getframe() + objs = [] + while f is not None: + objs.append(f) + objs.append(f.f_code) + #objs.append(f.f_locals) + #objs.append(f.f_globals) + #objs.append(f.f_builtins) + f = f.f_back + return objs + + +def findObj(regex): + """Return a list of objects whose typeStr matches regex""" + allObjs = get_all_objects() + objs = [] + r = re.compile(regex) + for i in allObjs: + obj = allObjs[i] + if r.search(typeStr(obj)): + objs.append(obj) + return objs + + + +def listRedundantModules(): + """List modules that have been imported more than once via different paths.""" + mods = {} + for name, mod in sys.modules.iteritems(): + if not hasattr(mod, '__file__'): + continue + mfile = os.path.abspath(mod.__file__) + if mfile[-1] == 'c': + mfile = mfile[:-1] + if mfile in mods: + print "module at %s has 2 names: %s, %s" % (mfile, name, mods[mfile]) + else: + mods[mfile] = name + + +def walkQObjectTree(obj, counts=None, verbose=False, depth=0): + """ + Walk through a tree of QObjects, doing nothing to them. + The purpose of this function is to find dead objects and generate a crash + immediately rather than stumbling upon them later. + Prints a count of the objects encountered, for fun. (or is it?) + """ + + if verbose: + print " "*depth + typeStr(obj) + report = False + if counts is None: + counts = {} + report = True + typ = str(type(obj)) + try: + counts[typ] += 1 + except KeyError: + counts[typ] = 1 + for child in obj.children(): + walkQObjectTree(child, counts, verbose, depth+1) + + return counts + +QObjCache = {} +def qObjectReport(verbose=False): + """Generate a report counting all QObjects and their types""" + global qObjCache + count = {} + for obj in findObj('PyQt'): + if isinstance(obj, QtCore.QObject): + oid = id(obj) + if oid not in QObjCache: + QObjCache[oid] = typeStr(obj) + " " + obj.objectName() + try: + QObjCache[oid] += " " + obj.parent().objectName() + QObjCache[oid] += " " + obj.text() + except: + pass + print "check obj", oid, unicode(QObjCache[oid]) + if obj.parent() is None: + walkQObjectTree(obj, count, verbose) + + typs = count.keys() + typs.sort() + for t in typs: + print count[t], "\t", t + diff --git a/examples/test_Arrow.py b/examples/test_Arrow.py index 07c12f30..7d0c4aad 100755 --- a/examples/test_Arrow.py +++ b/examples/test_Arrow.py @@ -11,6 +11,7 @@ import pyqtgraph as pg app = QtGui.QApplication([]) mw = QtGui.QMainWindow() +mw.resize(800,800) p = pg.PlotWidget() mw.setCentralWidget(p) diff --git a/examples/test_ImageItem.py b/examples/test_ImageItem.py index c86eb278..f48f0f51 100755 --- a/examples/test_ImageItem.py +++ b/examples/test_ImageItem.py @@ -12,6 +12,7 @@ app = QtGui.QApplication([]) ## Create window with GraphicsView widget win = QtGui.QMainWindow() +win.resize(800,800) view = pg.GraphicsView() #view.useOpenGL(True) win.setCentralWidget(view) @@ -40,12 +41,27 @@ def updateData(): ## Display the data img.updateImage(data[i]) i = (i+1) % data.shape[0] + + QtCore.QTimer.singleShot(20, updateData) # update image data every 20ms (or so) -t = QtCore.QTimer() -t.timeout.connect(updateData) -t.start(20) +#t = QtCore.QTimer() +#t.timeout.connect(updateData) +#t.start(20) +updateData() + + +def doWork(): + while True: + x = '.'.join(['%f'%i for i in range(100)]) ## some work for the thread to do + if time is None: ## main thread has started cleaning up, bail out now + break + time.sleep(1e-3) + +import thread +thread.start_new_thread(doWork, ()) + ## Start Qt event loop unless running in interactive mode. if sys.flags.interactive != 1: diff --git a/examples/test_ImageView.py b/examples/test_ImageView.py index 32868698..fd1fd8fe 100755 --- a/examples/test_ImageView.py +++ b/examples/test_ImageView.py @@ -13,6 +13,7 @@ app = QtGui.QApplication([]) ## Create window with ImageView widget win = QtGui.QMainWindow() +win.resize(800,800) imv = pg.ImageView() win.setCentralWidget(imv) win.show() diff --git a/examples/test_MultiPlotWidget.py b/examples/test_MultiPlotWidget.py index 621ca12b..4c72275b 100755 --- a/examples/test_MultiPlotWidget.py +++ b/examples/test_MultiPlotWidget.py @@ -18,6 +18,7 @@ except: app = QtGui.QApplication([]) mw = QtGui.QMainWindow() +mw.resize(800,800) pw = MultiPlotWidget() mw.setCentralWidget(pw) mw.show() diff --git a/examples/test_PlotWidget.py b/examples/test_PlotWidget.py index 70a8310d..2b2ef496 100755 --- a/examples/test_PlotWidget.py +++ b/examples/test_PlotWidget.py @@ -9,8 +9,10 @@ from PyQt4 import QtGui, QtCore import numpy as np import pyqtgraph as pg +#QtGui.QApplication.setGraphicsSystem('raster') app = QtGui.QApplication([]) mw = QtGui.QMainWindow() +mw.resize(800,800) cw = QtGui.QWidget() mw.setCentralWidget(cw) l = QtGui.QVBoxLayout() diff --git a/examples/test_ROItypes.py b/examples/test_ROItypes.py index 76623541..f080e0b4 100755 --- a/examples/test_ROItypes.py +++ b/examples/test_ROItypes.py @@ -12,8 +12,9 @@ import pyqtgraph as pg ## create GUI app = QtGui.QApplication([]) w = QtGui.QMainWindow() +w.resize(800,800) v = pg.GraphicsView() -v.invertY(True) ## Images usually have their Y-axis pointing downward +#v.invertY(True) ## Images usually have their Y-axis pointing downward v.setAspectLocked(True) v.enableMouse(True) v.autoPixelScale = False diff --git a/examples/test_draw.py b/examples/test_draw.py index 9b7e111f..b40932ba 100755 --- a/examples/test_draw.py +++ b/examples/test_draw.py @@ -12,6 +12,7 @@ app = QtGui.QApplication([]) ## Create window with GraphicsView widget win = QtGui.QMainWindow() +win.resize(800,800) view = pg.GraphicsView() #view.useOpenGL(True) win.setCentralWidget(view) diff --git a/examples/test_scatterPlot.py b/examples/test_scatterPlot.py index 329e07f1..e8d91eea 100755 --- a/examples/test_scatterPlot.py +++ b/examples/test_scatterPlot.py @@ -7,18 +7,74 @@ from PyQt4 import QtGui, QtCore import pyqtgraph as pg import numpy as np +#QtGui.QApplication.setGraphicsSystem('raster') app = QtGui.QApplication([]) + mw = QtGui.QMainWindow() -cw = pg.PlotWidget() +mw.resize(800,800) +cw = QtGui.QWidget() +layout = QtGui.QGridLayout() +cw.setLayout(layout) mw.setCentralWidget(cw) + +w1 = pg.PlotWidget() +layout.addWidget(w1, 0,0) + +w2 = pg.PlotWidget() +layout.addWidget(w2, 1,0) + +w3 = pg.GraphicsView() +w3.enableMouse() +w3.aspectLocked = True +layout.addWidget(w3, 0,1) + +w4 = pg.PlotWidget() +#vb = pg.ViewBox() +#w4.setCentralItem(vb) +layout.addWidget(w4, 1,1) + mw.show() -s1 = pg.ScatterPlotItem(size=10, pen=QtGui.QPen(QtCore.Qt.NoPen), brush=QtGui.QBrush(QtGui.QColor(255, 255, 255, 20))) -pos = np.random.normal(size=(2,3000)) -spots = [{'pos': pos[:,i]} for i in range(3000)] -s1.addPoints(spots) -cw.addDataItem(s1) +n = 3000 +s1 = pg.ScatterPlotItem(size=10, pen=QtGui.QPen(QtCore.Qt.NoPen), brush=QtGui.QBrush(QtGui.QColor(255, 255, 255, 20))) +pos = np.random.normal(size=(2,n), scale=1e-5) +spots = [{'pos': pos[:,i], 'data': 1} for i in range(n)] + [{'pos': [0,0], 'data': 1}] +s1.addPoints(spots) +w1.addDataItem(s1) + +def clicked(plot, points): + print "clicked points", points + +s1.sigClicked.connect(clicked) + + +s2 = pg.ScatterPlotItem(pxMode=False) +spots2 = [] +for i in range(10): + for j in range(10): + spots2.append({'pos': (1e-6*i, 1e-6*j), 'size': 1e-6, 'brush':pg.intColor(i*10+j, 100)}) +s2.addPoints(spots2) +w2.addDataItem(s2) + +s2.sigClicked.connect(clicked) + + +s3 = pg.ScatterPlotItem(size=10, pen=pg.mkPen('w'), pxMode=True) +pos = np.random.normal(size=(2,3000), scale=1e-5) +spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, 3000)} for i in range(3000)] +s3.addPoints(spots) +w3.addItem(s3) +w3.setRange(s3.boundingRect()) +s3.sigClicked.connect(clicked) + + +s4 = pg.ScatterPlotItem(identical=True, size=10, pen=QtGui.QPen(QtCore.Qt.NoPen), brush=QtGui.QBrush(QtGui.QColor(255, 255, 255, 20))) +#pos = np.random.normal(size=(2,n), scale=1e-5) +#spots = [{'pos': pos[:,i], 'data': 1} for i in range(n)] + [{'pos': [0,0], 'data': 1}] +s4.addPoints(spots) +w4.addDataItem(s4) + ## Start Qt event loop unless running in interactive mode. if sys.flags.interactive != 1: diff --git a/functions.py b/functions.py index cdb2c9fc..e5b6a41b 100644 --- a/functions.py +++ b/functions.py @@ -17,7 +17,7 @@ colorAbbrev = { } -from PyQt4 import QtGui +from PyQt4 import QtGui, QtCore import numpy as np import scipy.ndimage @@ -40,21 +40,27 @@ def siScale(x, minVal=1e-25): return (p, pref) def mkBrush(color): + if isinstance(color, QtGui.QBrush): + return color return QtGui.QBrush(mkColor(color)) -def mkPen(arg=None, color=None, width=1, style=None, cosmetic=True, hsv=None, ): +def mkPen(arg='default', color=None, width=1, style=None, cosmetic=True, hsv=None, ): """Convenience function for making pens. Examples: mkPen(color) mkPen(color, width=2) mkPen(cosmetic=False, width=4.5, color='r') mkPen({'color': "FF0", width: 2}) + mkPen(None) (no pen) """ if isinstance(arg, dict): return mkPen(**arg) - elif arg is not None: + elif arg != 'default': if isinstance(arg, QtGui.QPen): return arg - color = arg + elif arg is None: + style = QtCore.Qt.NoPen + else: + color = arg if color is None: color = mkColor(200, 200, 200) diff --git a/graphicsItems.py b/graphicsItems.py index be3dc028..63de57e0 100644 --- a/graphicsItems.py +++ b/graphicsItems.py @@ -21,14 +21,14 @@ try: except: pass from scipy.fftpack import fft -from scipy.signal import resample +#from scipy.signal import resample import scipy.stats #from metaarray import MetaArray from Point import * from functions import * import types, sys, struct import weakref -#import debug +import debug #from debug import * ## QGraphicsObject didn't appear until 4.6; this is for compatibility with 4.5 @@ -191,12 +191,13 @@ class ImageItem(QtGui.QGraphicsObject): else: useWeave = False - def __init__(self, image=None, copy=True, parent=None, border=None, *args): + def __init__(self, image=None, copy=True, parent=None, border=None, mode=None, *args): #QObjectWorkaround.__init__(self) QtGui.QGraphicsObject.__init__(self) #self.pixmapItem = QtGui.QGraphicsPixmapItem(self) self.qimage = QtGui.QImage() self.pixmap = None + self.paintMode = mode #self.useWeave = True self.blackLevel = None self.whiteLevel = None @@ -213,7 +214,11 @@ class ImageItem(QtGui.QGraphicsObject): if image is not None: self.updateImage(image, copy, autoRange=True) #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - + + def setCompositionMode(self, mode): + self.paintMode = mode + self.update() + def setAlpha(self, alpha): self.alpha = alpha self.updateImage() @@ -257,6 +262,8 @@ class ImageItem(QtGui.QGraphicsObject): return self.whiteLevel, self.blackLevel def updateImage(self, image=None, copy=True, autoRange=False, clipMask=None, white=None, black=None, axes=None): + prof = debug.Profiler('ImageItem.updateImage 0x%x' %id(self), disabled=True) + #debug.printTrace() if axes is None: axh = {'x': 0, 'y': 1, 'c': 2} else: @@ -280,6 +287,7 @@ class ImageItem(QtGui.QGraphicsObject): else: self.image = image.view(np.ndarray) #print " image max:", self.image.max(), "min:", self.image.min() + prof.mark('1') # Determine scale factors if autoRange or self.blackLevel is None: @@ -296,41 +304,49 @@ class ImageItem(QtGui.QGraphicsObject): else: scale = 0. + prof.mark('2') ## Recolor and convert to 8 bit per channel # Try using weave, then fall back to python shape = self.image.shape black = float(self.blackLevel) - try: - if not ImageItem.useWeave: - raise Exception('Skipping weave compile') - sim = np.ascontiguousarray(self.image) - sim.shape = sim.size - im = np.empty(sim.shape, dtype=np.ubyte) - n = im.size + white = float(self.whiteLevel) + + if black == 0 and white == 255 and self.image.dtype == np.ubyte: + im = self.image - code = """ - for( int i=0; i 255.0 ) - a = 255.0; - else if( a < 0.0 ) - a = 0.0; - im(i) = a; - } - """ - - weave.inline(code, ['sim', 'im', 'n', 'black', 'scale'], type_converters=converters.blitz, compiler = 'gcc') - sim.shape = shape - im.shape = shape - except: - if ImageItem.useWeave: - ImageItem.useWeave = False - #sys.excepthook(*sys.exc_info()) - #print "==============================================================================" - print "Weave compile failed, falling back to slower version." - self.image.shape = shape - im = ((self.image - black) * scale).clip(0.,255.).astype(np.ubyte) + else: + try: + if not ImageItem.useWeave: + raise Exception('Skipping weave compile') + sim = np.ascontiguousarray(self.image) + sim.shape = sim.size + im = np.empty(sim.shape, dtype=np.ubyte) + n = im.size + + code = """ + for( int i=0; i 255.0 ) + a = 255.0; + else if( a < 0.0 ) + a = 0.0; + im(i) = a; + } + """ + + weave.inline(code, ['sim', 'im', 'n', 'black', 'scale'], type_converters=converters.blitz, compiler = 'gcc') + sim.shape = shape + im.shape = shape + except: + if ImageItem.useWeave: + ImageItem.useWeave = False + #sys.excepthook(*sys.exc_info()) + #print "==============================================================================" + print "Weave compile failed, falling back to slower version." + self.image.shape = shape + im = ((self.image - black) * scale).clip(0.,255.).astype(np.ubyte) + prof.mark('3') try: im1 = np.empty((im.shape[axh['y']], im.shape[axh['x']], 4), dtype=np.ubyte) @@ -338,6 +354,7 @@ class ImageItem(QtGui.QGraphicsObject): print im.shape, axh raise alpha = np.clip(int(255 * self.alpha), 0, 255) + prof.mark('4') # Fill image if im.ndim == 2: im2 = im.transpose(axh['y'], axh['x']) @@ -363,33 +380,40 @@ class ImageItem(QtGui.QGraphicsObject): raise Exception("Image must be 2 or 3 dimensions") #self.im1 = im1 # Display image - + prof.mark('5') if self.clipLevel is not None or clipMask is not None: - if clipMask is not None: - mask = clipMask.transpose() - else: - mask = (self.image < self.clipLevel).transpose() - im1[..., 0][mask] *= 0.5 - im1[..., 1][mask] *= 0.5 - im1[..., 2][mask] = 255 + if clipMask is not None: + mask = clipMask.transpose() + else: + mask = (self.image < self.clipLevel).transpose() + im1[..., 0][mask] *= 0.5 + im1[..., 1][mask] *= 0.5 + im1[..., 2][mask] = 255 + prof.mark('6') #print "Final image:", im1.dtype, im1.min(), im1.max(), im1.shape self.ims = im1.tostring() ## Must be held in memory here because qImage won't do it for us :( + prof.mark('7') qimage = QtGui.QImage(buffer(self.ims), im1.shape[1], im1.shape[0], QtGui.QImage.Format_ARGB32) + prof.mark('8') self.pixmap = QtGui.QPixmap.fromImage(qimage) + prof.mark('9') ##del self.ims #self.pixmapItem.setPixmap(self.pixmap) self.update() + prof.mark('10') if gotNewData: #self.emit(QtCore.SIGNAL('imageChanged')) self.sigImageChanged.emit() + + prof.finish() def getPixmap(self): return self.pixmap.copy() def getHistogram(self, bins=500, step=3): - """returns an x and y arrays containing the histogram values for the current image. + """returns x and y arrays containing the histogram values for the current image. The step argument causes pixels to be skipped when computing the histogram to save time.""" stepData = self.image[::step, ::step] hist = np.histogram(stepData, bins=bins) @@ -397,7 +421,7 @@ class ImageItem(QtGui.QGraphicsObject): def mousePressEvent(self, ev): if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: - self.drawAt(ev.pos()) + self.drawAt(ev.pos(), ev) ev.accept() else: ev.ignore() @@ -405,24 +429,80 @@ class ImageItem(QtGui.QGraphicsObject): def mouseMoveEvent(self, ev): #print "mouse move", ev.pos() if self.drawKernel is not None: - self.drawAt(ev.pos()) + self.drawAt(ev.pos(), ev) def mouseReleaseEvent(self, ev): pass - def drawAt(self, pos): - self.image[int(pos.x()), int(pos.y())] += 1 - self.updateImage() + def tabletEvent(self, ev): + print ev.device() + print ev.pointerType() + print ev.pressure() + + def drawAt(self, pos, ev=None): + pos = [int(pos.x()), int(pos.y())] + dk = self.drawKernel + kc = self.drawKernelCenter + sx = [0,dk.shape[0]] + sy = [0,dk.shape[1]] + tx = [pos[0] - kc[0], pos[0] - kc[0]+ dk.shape[0]] + ty = [pos[1] - kc[1], pos[1] - kc[1]+ dk.shape[1]] - def setDrawKernel(self, kernel=None): + for i in [0,1]: + dx1 = -min(0, tx[i]) + dx2 = min(0, self.image.shape[0]-tx[i]) + tx[i] += dx1+dx2 + sx[i] += dx1+dx2 + + dy1 = -min(0, ty[i]) + dy2 = min(0, self.image.shape[1]-ty[i]) + ty[i] += dy1+dy2 + sy[i] += dy1+dy2 + + #print sx + #print sy + #print tx + #print ty + #print self.image.shape + #print self.image[tx[0]:tx[1], ty[0]:ty[1]].shape + #print dk[sx[0]:sx[1], sy[0]:sy[1]].shape + ts = (slice(tx[0],tx[1]), slice(ty[0],ty[1])) + ss = (slice(sx[0],sx[1]), slice(sy[0],sy[1])) + #src = dk[sx[0]:sx[1], sy[0]:sy[1]] + #mask = self.drawMask[sx[0]:sx[1], sy[0]:sy[1]] + mask = self.drawMask + src = dk + #print self.image[ts].shape, src.shape + + if callable(self.drawMode): + self.drawMode(dk, self.image, mask, ss, ts, ev) + else: + mask = mask[ss] + src = src[ss] + if self.drawMode == 'set': + if mask is not None: + self.image[ts] = self.image[ts] * (1-mask) + src * mask + else: + self.image[ts] = src + elif self.drawMode == 'add': + self.image[ts] += src + else: + raise Exception("Unknown draw mode '%s'" % self.drawMode) + self.updateImage() + + def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'): self.drawKernel = kernel + self.drawKernelCenter = center + self.drawMode = mode + self.drawMask = mask def paint(self, p, *args): #QtGui.QGraphicsPixmapItem.paint(self, p, *args) if self.pixmap is None: return - + if self.paintMode is not None: + p.setCompositionMode(self.paintMode) p.drawPixmap(self.boundingRect(), self.pixmap, QtCore.QRectF(0, 0, self.pixmap.width(), self.pixmap.height())) if self.border is not None: p.setPen(self.border) @@ -492,7 +572,8 @@ class PlotCurveItem(GraphicsObject): ds = self.opts['downsample'] if ds > 1: x = x[::ds] - y = resample(y[:len(x)*ds], len(x)) + #y = resample(y[:len(x)*ds], len(x)) ## scipy.signal.resample causes nasty ringing + y = y[::ds] if self.opts['spectrumMode']: f = fft(y) / len(y) y = abs(f[1:len(f)/2]) @@ -603,7 +684,7 @@ class PlotCurveItem(GraphicsObject): self.updateData(y, x, copy) def updateData(self, data, x=None, copy=False): - #prof = debug.Profiler('PlotCurveItem.updateData', disabled=True) + prof = debug.Profiler('PlotCurveItem.updateData', disabled=True) if isinstance(data, list): data = np.array(data) if isinstance(x, list): @@ -630,7 +711,11 @@ class PlotCurveItem(GraphicsObject): x = data[tuple(ind)] elif data.ndim == 1: y = data - #prof.mark("data checks") + prof.mark("data checks") + + self.setCacheMode(QtGui.QGraphicsItem.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly + ## Test this bug with test_PlotWidget and zoom in on the animated plot + self.prepareGeometryChange() if copy: self.yData = y.copy() @@ -641,7 +726,7 @@ class PlotCurveItem(GraphicsObject): self.xData = x.copy() else: self.xData = x - #prof.mark('copy') + prof.mark('copy') if x is None: self.xData = np.arange(0, self.yData.shape[0]) @@ -652,15 +737,19 @@ class PlotCurveItem(GraphicsObject): self.path = None self.xDisp = self.yDisp = None - #prof.mark('set') + prof.mark('set') self.update() - #prof.mark('update') + prof.mark('update') #self.emit(QtCore.SIGNAL('plotChanged'), self) self.sigPlotChanged.emit(self) - #prof.mark('emit') + prof.mark('emit') #prof.finish() + #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) + prof.mark('set cache mode') + prof.finish() def generatePath(self, x, y): + prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True) path = QtGui.QPainterPath() ## Create all vertices in path. The method used below creates a binary format so that all @@ -680,31 +769,31 @@ class PlotCurveItem(GraphicsObject): ## ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') - #prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True) - n = x.shape[0] # create empty array, pad with extra space on either end arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) - #prof.mark('create empty') # write first two integers + prof.mark('allocate empty') arr.data[12:20] = struct.pack('>ii', n, 0) + prof.mark('pack header') # Fill array with vertex values arr[1:-1]['x'] = x arr[1:-1]['y'] = y arr[1:-1]['c'] = 1 - #prof.mark('fill array') + prof.mark('fill array') # write last 0 lastInd = 20*(n+1) arr.data[lastInd:lastInd+4] = struct.pack('>i', 0) - + prof.mark('footer') # create datastream object and stream into path buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here - #prof.mark('create buffer') + prof.mark('create buffer') ds = QtCore.QDataStream(buf) - #prof.mark('create dataStream') + prof.mark('create datastream') ds >> path - #prof.mark('load path') - #prof.finish() + prof.mark('load') + + prof.finish() return path def boundingRect(self): @@ -729,7 +818,7 @@ class PlotCurveItem(GraphicsObject): return QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin) def paint(self, p, opt, widget): - #prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True) + prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True) if self.xData is None: return #if self.opts['spectrumMode']: @@ -741,7 +830,7 @@ class PlotCurveItem(GraphicsObject): if self.path is None: self.path = self.generatePath(*self.getData()) path = self.path - #prof.mark('generate path') + prof.mark('generate path') if self.shadow is not None: sp = QtGui.QPen(self.shadow) @@ -763,9 +852,9 @@ class PlotCurveItem(GraphicsObject): p.drawPath(path) p.setPen(cp) p.drawPath(path) - #prof.mark('drawPath') + prof.mark('drawPath') - #prof.finish() + prof.finish() #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) #p.drawRect(self.boundingRect()) @@ -822,10 +911,10 @@ class CurvePoint(QtGui.QGraphicsObject): self.setIndex(index) def setPos(self, pos): - self.setProperty('position', pos) + self.setProperty('position', float(pos))## cannot use numpy types here, MUST be python float. def setIndex(self, index): - self.setProperty('index', index) + self.setProperty('index', int(index)) ## cannot use numpy types here, MUST be python int. def event(self, ev): if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None: @@ -840,7 +929,7 @@ class CurvePoint(QtGui.QGraphicsObject): (x, y) = self.curve().getData() if index is None: - #print self.property('position').toDouble()[0], self.property('position').typeName() + #print ev.propertyName(), self.property('position').toDouble()[0], self.property('position').typeName() index = (len(x)-1) * clip(self.property('position').toDouble()[0], 0.0, 1.0) if index != int(index): ## interpolate floating-point values @@ -947,28 +1036,54 @@ class CurveArrow(CurvePoint): -class ScatterPlotItem(QtGui.QGraphicsWidget): +class ScatterPlotItem(GraphicsObject): - sigPointClicked = QtCore.Signal(object, object) + #sigPointClicked = QtCore.Signal(object, object) + sigClicked = QtCore.Signal(object, object) ## self, points - def __init__(self, spots=None, pxMode=True, pen=None, brush=None, size=5): - QtGui.QGraphicsWidget.__init__(self) + def __init__(self, spots=None, x=None, y=None, pxMode=True, pen='default', brush='default', size=5, identical=False, data=None): + """ + Arguments: + spots: list of dicts. Each dict specifies parameters for a single spot. + x,y: array of x,y values. Alternatively, specify spots['pos'] = (x,y) + pxMode: If True, spots are always the same size regardless of scaling + identical: If True, all spots are forced to look identical. + This can result in performance enhancement.""" + GraphicsObject.__init__(self) self.spots = [] self.range = [[0,0], [0,0]] + self.identical = identical + self._spotPixmap = None - if brush is None: - brush = QtGui.QBrush(QtGui.QColor(100, 100, 150)) - self.brush = brush + if brush == 'default': + self.brush = QtGui.QBrush(QtGui.QColor(100, 100, 150)) + else: + self.brush = mkBrush(brush) - if pen is None: - pen = QtGui.QPen(QtGui.QColor(200, 200, 200)) - self.pen = pen + if pen == 'default': + self.pen = QtGui.QPen(QtGui.QColor(200, 200, 200)) + else: + self.pen = mkPen(pen) self.size = size self.pxMode = pxMode - if spots is not None: - self.setPoints(spots) + if spots is not None or x is not None: + self.setPoints(spots, x, y, data) + + #self.optimize = optimize + #if optimize: + #self.spotImage = QtGui.QImage(size, size, QtGui.QImage.Format_ARGB32_Premultiplied) + #self.spotImage.fill(0) + #p = QtGui.QPainter(self.spotImage) + #p.setRenderHint(p.Antialiasing) + #p.setBrush(brush) + #p.setPen(pen) + #p.drawEllipse(0, 0, size, size) + #p.end() + #self.optimizePixmap = QtGui.QPixmap(self.spotImage) + #self.optimizeFragments = [] + #self.setFlags(self.flags() | self.ItemIgnoresTransformations) def setPxMode(self, mode): self.pxMode = mode @@ -985,15 +1100,28 @@ class ScatterPlotItem(QtGui.QGraphicsWidget): def getRange(self, ax, percent): return self.range[ax] - def setPoints(self, spots): + def setPoints(self, spots=None, x=None, y=None, data=None): self.clear() self.range = [[0,0],[0,0]] - self.addPoints(spots) + self.addPoints(spots, x, y, data) - def addPoints(self, spots): + def addPoints(self, spots=None, x=None, y=None, data=None): xmn = ymn = xmx = ymx = None - for s in spots: - pos = Point(s['pos']) + if spots is not None: + n = len(spots) + else: + n = len(x) + + for i in range(n): + if spots is not None: + s = spots[i] + pos = Point(s['pos']) + else: + s = {} + pos = Point(x[i], y[i]) + if data is not None: + s['data'] = data[i] + size = s.get('size', self.size) if self.pxMode: psize = 0 @@ -1013,17 +1141,40 @@ class ScatterPlotItem(QtGui.QGraphicsWidget): brush = s.get('brush', self.brush) pen = s.get('pen', self.pen) pen.setCosmetic(True) - data = s.get('data', None) - item = self.mkSpot(pos, size, self.pxMode, brush, pen, data) + data2 = s.get('data', None) + item = self.mkSpot(pos, size, self.pxMode, brush, pen, data2, index=len(self.spots)) self.spots.append(item) + #if self.optimize: + #item.hide() + #frag = QtGui.QPainter.PixmapFragment.create(pos, QtCore.QRectF(0, 0, size, size)) + #self.optimizeFragments.append(frag) self.range = [[xmn, xmx], [ymn, ymx]] + #def paint(self, p, *args): + #if not self.optimize: + #return + ##p.setClipRegion(self.boundingRect()) + #p.drawPixmapFragments(self.optimizeFragments, self.optimizePixmap) - def mkSpot(self, pos, size, pxMode, brush, pen, data): - item = SpotItem(size, pxMode, brush, pen, data) + def paint(self, *args): + pass + + def spotPixmap(self): + if not self.identical: + return None + if self._spotPixmap is None: + self._spotPixmap = PixmapSpotItem.makeSpotImage(self.size, self.pen, self.brush) + return self._spotPixmap + + def mkSpot(self, pos, size, pxMode, brush, pen, data, index=None): + if pxMode: + img = self.spotPixmap() + item = PixmapSpotItem(size, brush, pen, data, image=img, index=index) + else: + item = SpotItem(size, pxMode, brush, pen, data, index=index) item.setParentItem(self) item.setPos(pos) - item.sigClicked.connect(self.pointClicked) + #item.sigClicked.connect(self.pointClicked) return item def boundingRect(self): @@ -1031,32 +1182,92 @@ class ScatterPlotItem(QtGui.QGraphicsWidget): if xmn is None or xmx is None or ymn is None or ymx is None: return QtCore.QRectF() return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn) + return QtCore.QRectF(xmn-1, ymn-1, xmx-xmn+2, ymx-ymn+2) - def paint(self, p, *args): - pass - - def pointClicked(self, point): - self.sigPointClicked.emit(self, point) + #def pointClicked(self, point): + #self.sigPointClicked.emit(self, point) def points(self): return self.spots[:] -class SpotItem(QtGui.QGraphicsWidget): - sigClicked = QtCore.Signal(object) + def pointsAt(self, pos): + x = pos.x() + y = pos.y() + pw = self.pixelWidth() + ph = self.pixelHeight() + pts = [] + for s in self.spots: + sp = s.pos() + ss = s.size + sx = sp.x() + sy = sp.y() + s2x = s2y = ss * 0.5 + if self.pxMode: + s2x *= pw + s2y *= ph + if x > sx-s2x and x < sx+s2x and y > sy-s2y and y < sy+s2y: + pts.append(s) + #print "HIT:", x, y, sx, sy, s2x, s2y + #else: + #print "No hit:", (x, y), (sx, sy) + #print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y) + pts.sort(lambda a,b: cmp(b.zValue(), a.zValue())) + return pts + + + def mousePressEvent(self, ev): + QtGui.QGraphicsItem.mousePressEvent(self, ev) + if ev.button() == QtCore.Qt.LeftButton: + pts = self.pointsAt(ev.pos()) + if len(pts) > 0: + self.mouseMoved = False + self.ptsClicked = pts + ev.accept() + else: + #print "no spots" + ev.ignore() + else: + ev.ignore() + + def mouseMoveEvent(self, ev): + QtGui.QGraphicsItem.mouseMoveEvent(self, ev) + self.mouseMoved = True + pass - def __init__(self, size, pxMode, brush, pen, data): + def mouseReleaseEvent(self, ev): + QtGui.QGraphicsItem.mouseReleaseEvent(self, ev) + if not self.mouseMoved: + self.sigClicked.emit(self, self.ptsClicked) + + +class SpotItem(QtGui.QGraphicsWidget): + #sigClicked = QtCore.Signal(object) + + def __init__(self, size, pxMode, brush, pen, data, index=None): QtGui.QGraphicsWidget.__init__(self) - if pxMode: - self.setCacheMode(self.DeviceCoordinateCache) - self.setFlags(self.flags() | self.ItemIgnoresTransformations) - #self.setCacheMode(self.DeviceCoordinateCache) ## causes crash on linux + self.pxMode = pxMode + self.pen = pen self.brush = brush - self.path = QtGui.QPainterPath() self.size = size + self.index = index #s2 = size/2. + self.path = QtGui.QPainterPath() self.path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) - self.scale(size, size) + if pxMode: + #self.setCacheMode(self.DeviceCoordinateCache) ## broken. + self.setFlags(self.flags() | self.ItemIgnoresTransformations) + self.spotImage = QtGui.QImage(size, size, QtGui.QImage.Format_ARGB32_Premultiplied) + self.spotImage.fill(0) + p = QtGui.QPainter(self.spotImage) + p.setRenderHint(p.Antialiasing) + p.setBrush(brush) + p.setPen(pen) + p.drawEllipse(0, 0, size, size) + p.end() + self.pixmap = QtGui.QPixmap(self.spotImage) + else: + self.scale(size, size) self.data = data def setBrush(self, brush): @@ -1074,31 +1285,104 @@ class SpotItem(QtGui.QGraphicsWidget): return self.path def paint(self, p, *opts): - p.setPen(self.pen) - p.setBrush(self.brush) - p.drawPath(self.path) - - def mousePressEvent(self, ev): - QtGui.QGraphicsItem.mousePressEvent(self, ev) - if ev.button() == QtCore.Qt.LeftButton: - self.mouseMoved = False - ev.accept() + if self.pxMode: + p.drawPixmap(QtCore.QPoint(int(-0.5*self.size), int(-0.5*self.size)), self.pixmap) else: - ev.ignore() + p.setPen(self.pen) + p.setBrush(self.brush) + p.drawPath(self.path) + + #def mousePressEvent(self, ev): + #QtGui.QGraphicsItem.mousePressEvent(self, ev) + #if ev.button() == QtCore.Qt.LeftButton: + #self.mouseMoved = False + #ev.accept() + #else: + #ev.ignore() - def mouseMoveEvent(self, ev): - QtGui.QGraphicsItem.mouseMoveEvent(self, ev) - self.mouseMoved = True - pass + #def mouseMoveEvent(self, ev): + #QtGui.QGraphicsItem.mouseMoveEvent(self, ev) + #self.mouseMoved = True + #pass - def mouseReleaseEvent(self, ev): - QtGui.QGraphicsItem.mouseReleaseEvent(self, ev) - if not self.mouseMoved: - self.sigClicked.emit(self) + #def mouseReleaseEvent(self, ev): + #QtGui.QGraphicsItem.mouseReleaseEvent(self, ev) + #if not self.mouseMoved: + #self.sigClicked.emit(self) + +class PixmapSpotItem(QtGui.QGraphicsItem): + #sigClicked = QtCore.Signal(object) + + def __init__(self, size, brush, pen, data, image=None, index=None): + """This class draws a scale-invariant image centered at 0,0. + If no image is specified, then an antialiased circle is constructed instead. + It should be quite fast, but large spots will use a lot of memory.""" + + QtGui.QGraphicsItem.__init__(self) + self.pen = pen + self.brush = brush + self.size = size + self.index = index + self.setFlags(self.flags() | self.ItemIgnoresTransformations | self.ItemHasNoContents) + if image is None: + self.image = self.makeSpotImage(self.size, self.pen, self.brush) + else: + self.image = image + self.pixmap = QtGui.QPixmap(self.image) + #self.setPixmap(self.pixmap) + self.data = data + self.pi = QtGui.QGraphicsPixmapItem(self.pixmap, self) + self.pi.setPos(-0.5*size, -0.5*size) + + #self.translate(-0.5, -0.5) + def boundingRect(self): + return self.pi.boundingRect() + + @staticmethod + def makeSpotImage(size, pen, brush): + img = QtGui.QImage(size+2, size+2, QtGui.QImage.Format_ARGB32_Premultiplied) + img.fill(0) + p = QtGui.QPainter(img) + try: + p.setRenderHint(p.Antialiasing) + p.setBrush(brush) + p.setPen(pen) + p.drawEllipse(1, 1, size, size) + finally: + p.end() ## failure to end a painter properly causes crash. + return img + + #def paint(self, p, *args): + #p.setCompositionMode(p.CompositionMode_Plus) + #QtGui.QGraphicsPixmapItem.paint(self, p, *args) + + #def setBrush(self, brush): + #self.brush = mkBrush(brush) + #self.update() + + #def setPen(self, pen): + #self.pen = mkPen(pen) + #self.update() + + #def boundingRect(self): + #return self.path.boundingRect() + + #def shape(self): + #return self.path + + #def paint(self, p, *opts): + #if self.pxMode: + #p.drawPixmap(QtCore.QPoint(int(-0.5*self.size), int(-0.5*self.size)), self.pixmap) + #else: + #p.setPen(self.pen) + #p.setBrush(self.brush) + #p.drawPath(self.path) + + class ROIPlotItem(PlotCurveItem): """Plot curve that monitors an ROI and image for changes to automatically replot.""" @@ -1190,12 +1474,16 @@ class UIGraphicsItem(GraphicsObject): pass - +class DebugText(QtGui.QGraphicsTextItem): + def paint(self, *args): + p = debug.Profiler("DebugText.paint", disabled=True) + QtGui.QGraphicsTextItem.paint(self, *args) + p.finish() class LabelItem(QtGui.QGraphicsWidget): def __init__(self, text, parent=None, **args): QtGui.QGraphicsWidget.__init__(self, parent) - self.item = QtGui.QGraphicsTextItem(self) + self.item = DebugText(self) self.opts = args if 'color' not in args: self.opts['color'] = 'CCC' @@ -1319,7 +1607,7 @@ class ScaleItem(QtGui.QGraphicsWidget): self.showLabel(False) self.grid = False - + self.setCacheMode(self.DeviceCoordinateCache) def close(self): self.scene().removeItem(self.label) @@ -1478,6 +1766,7 @@ class ScaleItem(QtGui.QGraphicsWidget): return self.mapRectFromParent(self.geometry()) | self.mapRectFromScene(self.linkedView().mapRectToScene(self.linkedView().boundingRect())) def paint(self, p, opt, widget): + prof = debug.Profiler("ScaleItem.paint", disabled=True) p.setPen(self.pen) #bounds = self.boundingRect() @@ -1538,10 +1827,14 @@ class ScaleItem(QtGui.QGraphicsWidget): xs = -bounds.height() / dif else: xs = bounds.width() / dif + + prof.mark('init') tickPositions = set() # remembers positions of previously drawn ticks - ## draw ticks and text + ## draw ticks and generate list of texts to draw + ## (to improve performance, we do not interleave line and text drawing, since this causes unnecessary pipeline switching) ## draw three different intervals, long ticks first + texts = [] for i in reversed([i1, i1+1, i1+2]): if i > len(intervals): continue @@ -1614,33 +1907,14 @@ class ScaleItem(QtGui.QGraphicsWidget): rect = QtCore.QRectF(x-100, tickStop+self.tickLength, 200, height) p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) - p.drawText(rect, textFlags, vstr) - #p.drawRect(rect) - - ## Draw label - #if self.drawLabel: - #height = self.size().height() - #width = self.size().width() - #if self.orientation == 'left': - #p.translate(0, height) - #p.rotate(-90) - #rect = QtCore.QRectF(0, 0, height, self.textHeight) - #textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignTop - #elif self.orientation == 'right': - #p.rotate(10) - #rect = QtCore.QRectF(0, 0, height, width) - #textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignBottom - ##rect = QtCore.QRectF(tickStart+self.tickLength, x-(height/2), 100-self.tickLength, height) - #elif self.orientation == 'top': - #rect = QtCore.QRectF(0, 0, width, height) - #textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignTop - ##rect = QtCore.QRectF(x-100, tickStart-self.tickLength-height, 200, height) - #elif self.orientation == 'bottom': - #rect = QtCore.QRectF(0, 0, width, height) - #textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignBottom - ##rect = QtCore.QRectF(x-100, tickStart+self.tickLength, 200, height) - #p.drawText(rect, textFlags, self.labelString()) - ##p.drawRect(rect) + #p.drawText(rect, textFlags, vstr) + texts.append((rect, textFlags, vstr)) + + prof.mark('draw ticks') + for args in texts: + p.drawText(*args) + prof.mark('draw text') + prof.finish() def show(self): @@ -1675,15 +1949,21 @@ class ViewBox(QtGui.QGraphicsWidget): sigRangeChanged = QtCore.Signal(object, object) """Box that allows internal scaling/panning of children by mouse drag. Not compatible with GraphicsView having the same functionality.""" - def __init__(self, parent=None): + def __init__(self, parent=None, border=None): QtGui.QGraphicsWidget.__init__(self, parent) #self.gView = view #self.showGrid = showGrid - self.range = [[0,1], [0,1]] ## child coord. range visible [[xmin, xmax], [ymin, ymax]] + + ## separating targetRange and viewRange allows the view to be resized + ## while keeping all previously viewed contents visible + self.targetRange = [[0,1], [0,1]] ## child coord. range visible [[xmin, xmax], [ymin, ymax]] + self.viewRange = [[0,1], [0,1]] ## actual range viewed + self.wheelScaleFactor = -1.0 / 8.0 self.aspectLocked = False self.setFlag(QtGui.QGraphicsItem.ItemClipsChildrenToShape) #self.setFlag(QtGui.QGraphicsItem.ItemClipsToShape) + #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) #self.childGroup = QtGui.QGraphicsItemGroup(self) self.childGroup = ItemGroup(self) @@ -1695,7 +1975,7 @@ class ViewBox(QtGui.QGraphicsWidget): #self.picture = None self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) - self.drawFrame = False + self.border = border self.mouseEnabled = [True, True] @@ -1718,66 +1998,57 @@ class ViewBox(QtGui.QGraphicsWidget): def viewRect(self): try: - return QtCore.QRectF(self.range[0][0], self.range[1][0], self.range[0][1]-self.range[0][0], self.range[1][1] - self.range[1][0]) + vr0 = self.viewRange[0] + vr1 = self.viewRange[1] + return QtCore.QRectF(vr0[0], vr1[0], vr0[1]-vr0[0], vr1[1] - vr1[0]) except: - print "make qrectf failed:", self.range + print "make qrectf failed:", self.viewRange + raise + + def targetRect(self): + """Return the region which has been requested to be visible. + (this is not necessarily the same as the region that is *actually* visible)""" + try: + tr0 = self.targetRange[0] + tr1 = self.targetRange[1] + return QtCore.QRectF(tr0[0], tr1[0], tr0[1]-tr0[0], tr1[1] - tr1[0]) + except: + print "make qrectf failed:", self.targetRange raise - def updateMatrix(self): - #print "udpateMatrix:" - #print " range:", self.range - vr = self.viewRect() - translate = Point(vr.center()) - bounds = self.boundingRect() - #print " bounds:", bounds - if vr.height() == 0 or vr.width() == 0: - return - scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height()) - #print " scale:", scale - m = QtGui.QTransform() - - ## First center the viewport at 0 - self.childGroup.resetTransform() - center = self.transform().inverted()[0].map(bounds.center()) - #print " transform to center:", center - if self.yInverted: - m.translate(center.x(), -center.y()) - #print " inverted; translate", center.x(), center.y() - else: - m.translate(center.x(), center.y()) - #print " not inverted; translate", center.x(), -center.y() - - ## Now scale and translate properly - if self.aspectLocked: - scale = Point(scale.min()) - if not self.yInverted: - scale = scale * Point(1, -1) - m.scale(scale[0], scale[1]) - #print " scale:", scale - st = translate - m.translate(-st[0], -st[1]) - #print " translate:", st - self.childGroup.setTransform(m) - self.currentScale = scale - def invertY(self, b=True): self.yInverted = b self.updateMatrix() + def setAspectLocked(self, lock=True, ratio=1): + """If the aspect ratio is locked, view scaling is always forced to be isotropic. + By default, the ratio is set to 1; x and y both have the same scaling. + This ratio can be overridden (width/height), or use None to lock in the current ratio. + """ + if not lock: + self.aspectLocked = False + else: + vr = self.viewRect() + currentRatio = vr.width() / vr.height() + if ratio is None: + ratio = currentRatio + self.aspectLocked = ratio + if ratio != currentRatio: ## If this would change the current range, do that now + #self.setRange(0, self.viewRange[0][0], self.viewRange[0][1]) + self.updateMatrix() + def childTransform(self): m = self.childGroup.transform() m1 = QtGui.QTransform() m1.translate(self.childGroup.pos().x(), self.childGroup.pos().y()) return m*m1 - def setAspectLocked(self, s): - self.aspectLocked = s def viewScale(self): - pr = self.range + vr = self.viewRect() #print "viewScale:", self.range - xd = pr[0][1] - pr[0][0] - yd = pr[1][1] - pr[1][0] + xd = vr.width() + yd = vr.height() if xd == 0 or yd == 0: print "Warning: 0 range in view:", xd, yd return np.array([1,1]) @@ -1789,41 +2060,54 @@ class ViewBox(QtGui.QGraphicsWidget): return scale def scaleBy(self, s, center=None): + """Scale by s around given center point (or center of view)""" #print "scaleBy", s, center - xr, yr = self.range + #if self.aspectLocked: + #s[0] = s[1] + scale = Point(s) + if self.aspectLocked is not False: + scale[0] = self.aspectLocked * scale[1] + + + #xr, yr = self.range + vr = self.viewRect() if center is None: - xc = (xr[1] + xr[0]) * 0.5 - yc = (yr[1] + yr[0]) * 0.5 + center = Point(vr.center()) + #xc = (xr[1] + xr[0]) * 0.5 + #yc = (yr[1] + yr[0]) * 0.5 else: - (xc, yc) = center - - x1 = xc + (xr[0]-xc) * s[0] - x2 = xc + (xr[1]-xc) * s[0] - y1 = yc + (yr[0]-yc) * s[1] - y2 = yc + (yr[1]-yc) * s[1] + center = Point(center) + #(xc, yc) = center + #x1 = xc + (xr[0]-xc) * s[0] + #x2 = xc + (xr[1]-xc) * s[0] + #y1 = yc + (yr[0]-yc) * s[1] + #y2 = yc + (yr[1]-yc) * s[1] + tl = center + (vr.topLeft()-center) * scale + br = center + (vr.bottomRight()-center) * scale + #print xr, xc, s, (xr[0]-xc) * s[0], (xr[1]-xc) * s[0] #print [[x1, x2], [y1, y2]] - - - self.setXRange(x1, x2, update=False, padding=0) - self.setYRange(y1, y2, padding=0) + #if not self.aspectLocked: + #self.setXRange(x1, x2, update=False, padding=0) + #self.setYRange(y1, y2, padding=0) #print self.range + self.setRange(QtCore.QRectF(tl, br), padding=0) + def translateBy(self, t, viewCoords=False): t = t.astype(np.float) #print "translate:", t, self.viewScale() if viewCoords: ## scale from pixels t /= self.viewScale() - xr, yr = self.range - #self.setAxisScale(self.xBottom, xr[0] + t[0], xr[1] + t[0]) - #self.setAxisScale(self.yLeft, yr[0] + t[1], yr[1] + t[1]) + #xr, yr = self.range + + vr = self.viewRect() #print xr, yr, t - self.setXRange(xr[0] + t[0], xr[1] + t[0], update=False, padding=0) - self.setYRange(yr[0] + t[1], yr[1] + t[1], padding=0) - #self.replot(autoRange=False) - #self.updateMatrix() + #self.setXRange(xr[0] + t[0], xr[1] + t[0], update=False, padding=0) + #self.setYRange(yr[0] + t[1], yr[1] + t[1], padding=0) + self.setRange(vr.translated(Point(t)), padding=0) def wheelEvent(self, ev, axis=None): mask = np.array(self.mouseEnabled, dtype=np.float) @@ -1835,7 +2119,8 @@ class ViewBox(QtGui.QGraphicsWidget): # scale 'around' mouse cursor position center = Point(self.childGroup.transform().inverted()[0].map(ev.pos())) self.scaleBy(s, center) - self.emit(QtCore.SIGNAL('rangeChangedManually'), self.mouseEnabled) + #self.emit(QtCore.SIGNAL('rangeChangedManually'), self.mouseEnabled) + self.sigRangeChangedManually.emit(self.mouseEnabled) ev.accept() def mouseMoveEvent(self, ev): @@ -1858,6 +2143,8 @@ class ViewBox(QtGui.QGraphicsWidget): self.sigRangeChangedManually.emit(self.mouseEnabled) ev.accept() elif ev.buttons() & QtCore.Qt.RightButton: + if self.aspectLocked is not False: + mask[0] = 0 dif = ev.screenPos() - ev.lastScreenPos() dif = np.array([dif.x(), dif.y()]) dif[0] *= -1 @@ -1887,81 +2174,145 @@ class ViewBox(QtGui.QGraphicsWidget): self.mousePos = pos ev.accept() - def setRange(self, ax, min, max, padding=0.02, update=True): - if ax == 0: - self.setXRange(min, max, update=update, padding=padding) + def setRange(self, ax, min=None, max=None, padding=0.02, update=True): + if isinstance(ax, QtCore.QRectF): + changes = {0: [ax.left(), ax.right()], 1: [ax.top(), ax.bottom()]} + #if self.aspectLocked is not False: + #sbr = self.boundingRect() + #if sbr.width() == 0 or (ax.height()/ax.width()) > (sbr.height()/sbr.width()): + #chax = 0 + #else: + #chax = 1 + + + + + elif ax in [1,0]: + changes = {ax: [min,max]} + #if self.aspectLocked is not False: + #ax2 = 1 - ax + #ratio = self.aspectLocked + #r2 = self.range[ax2] + #d = ratio * (max-min) * 0.5 + #c = (self.range[ax2][1] + self.range[ax2][0]) * 0.5 + #changes[ax2] = [c-d, c+d] + else: - self.setYRange(min, max, update=update, padding=padding) + print ax + raise Exception("argument 'ax' must be 0, 1, or QRectF.") + + + changed = [False, False] + for ax, range in changes.iteritems(): + min, max = range + if min == max: ## If we requested 0 range, try to preserve previous scale. Otherwise just pick an arbitrary scale. + dy = self.viewRange[ax][1] - self.viewRange[ax][0] + if dy == 0: + dy = 1 + min -= dy*0.5 + max += dy*0.5 + padding = 0.0 + if any(np.isnan([min, max])) or any(np.isinf([min, max])): + raise Exception("Not setting range [%s, %s]" % (str(min), str(max))) + + p = (max-min) * padding + min -= p + max += p + + if self.targetRange[ax] != [min, max]: + self.targetRange[ax] = [min, max] + changed[ax] = True + + if update: + self.updateMatrix(changed) + + + def setYRange(self, min, max, update=True, padding=0.02): - #print "setYRange:", min, max - if min == max: ## If we requested no range, try to preserve previous scale. Otherwise just pick an arbitrary scale. - dy = self.range[1][1] - self.range[1][0] - if dy == 0: - dy = 1 - min -= dy*0.5 - max += dy*0.5 - #raise Exception("Tried to set range with 0 width.") - if any(np.isnan([min, max])) or any(np.isinf([min, max])): - raise Exception("Not setting range [%s, %s]" % (str(min), str(max))) - - padding = (max-min) * padding - min -= padding - max += padding - if self.range[1] != [min, max]: - #self.setAxisScale(self.yLeft, min, max) - self.range[1] = [min, max] - #self.ctrl.yMinText.setText('%g' % min) - #self.ctrl.yMaxText.setText('%g' % max) - #self.emit(QtCore.SIGNAL('yRangeChanged'), self, (min, max)) - self.sigYRangeChanged.emit(self, (min, max)) - #self.emit(QtCore.SIGNAL('viewChanged'), self) - self.sigRangeChanged.emit(self, self.range) - if update: - self.updateMatrix() + self.setRange(1, min, max, update=update, padding=padding) def setXRange(self, min, max, update=True, padding=0.02): - #print "setXRange:", min, max - if min == max: - dx = self.range[0][1] - self.range[0][0] - if dx == 0: - dx = 1 - min -= dx*0.5 - max += dx*0.5 - #print "Warning: Tried to set range with 0 width." - #raise Exception("Tried to set range with 0 width.") - if any(np.isnan([min, max])) or any(np.isinf([min, max])): - raise Exception("Not setting range [%s, %s]" % (str(min), str(max))) - padding = (max-min) * padding - min -= padding - max += padding - if self.range[0] != [min, max]: - #self.setAxisScale(self.xBottom, min, max) - self.range[0] = [min, max] - #self.ctrl.xMinText.setText('%g' % min) - #self.ctrl.xMaxText.setText('%g' % max) - #self.emit(QtCore.SIGNAL('xRangeChanged'), self, (min, max)) - self.sigXRangeChanged.emit(self, (min, max)) - #self.emit(QtCore.SIGNAL('viewChanged'), self) - self.sigRangeChanged.emit(self, self.range) - if update: - self.updateMatrix() + self.setRange(0, min, max, update=update, padding=padding) def autoRange(self, padding=0.02): br = self.childGroup.childrenBoundingRect() - #print br - #px = br.width() * padding - #py = br.height() * padding - self.setXRange(br.left(), br.right(), padding=padding, update=False) - self.setYRange(br.top(), br.bottom(), padding=padding) + self.setRange(br, padding=padding) + + + def updateMatrix(self, changed=None): + if changed is None: + changed = [False, False] + #print "udpateMatrix:" + #print " range:", self.range + tr = self.targetRect() + bounds = self.boundingRect() + ## set viewRect, given targetRect and possibly aspect ratio constraint + if self.aspectLocked is False or bounds.height() == 0: + self.viewRange = [self.targetRange[0][:], self.targetRange[1][:]] + else: + viewRatio = bounds.width() / bounds.height() + targetRatio = self.aspectLocked * tr.width() / tr.height() + if targetRatio > viewRatio: + ## target is wider than view + dy = 0.5 * (tr.width() / (self.aspectLocked * viewRatio) - tr.height()) + if dy != 0: + changed[1] = True + self.viewRange = [self.targetRange[0][:], [self.targetRange[1][0] - dy, self.targetRange[1][1] + dy]] + else: + dx = 0.5 * (tr.height() * viewRatio * self.aspectLocked - tr.width()) + if dx != 0: + changed[0] = True + self.viewRange = [[self.targetRange[0][0] - dx, self.targetRange[0][1] + dx], self.targetRange[1][:]] + + + vr = self.viewRect() + translate = Point(vr.center()) + #print " bounds:", bounds + if vr.height() == 0 or vr.width() == 0: + return + scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height()) + #print " scale:", scale + m = QtGui.QTransform() + + ## First center the viewport at 0 + self.childGroup.resetTransform() + center = self.transform().inverted()[0].map(bounds.center()) + #print " transform to center:", center + if self.yInverted: + m.translate(center.x(), -center.y()) + #print " inverted; translate", center.x(), center.y() + else: + m.translate(center.x(), center.y()) + #print " not inverted; translate", center.x(), -center.y() + + ## Now scale and translate properly + if not self.yInverted: + scale = scale * Point(1, -1) + m.scale(scale[0], scale[1]) + st = translate + m.translate(-st[0], -st[1]) + self.childGroup.setTransform(m) + self.currentScale = scale + + + if changed[0]: + self.sigXRangeChanged.emit(self, tuple(self.viewRange[0])) + if changed[1]: + self.sigYRangeChanged.emit(self, tuple(self.viewRange[1])) + if any(changed): + self.sigRangeChanged.emit(self, self.viewRange) + + + def boundingRect(self): return QtCore.QRectF(0, 0, self.size().width(), self.size().height()) def paint(self, p, opt, widget): - if self.drawFrame: + if self.border is not None: bounds = self.boundingRect() - p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) + p.setPen(self.border) #p.fillRect(bounds, QtGui.QColor(0, 0, 0)) p.drawRect(bounds) @@ -2429,7 +2780,7 @@ class GridItem(UIGraphicsItem): #print "no pic, draw.." self.generatePicture() p.drawPicture(0, 0, self.picture) - #print "draw" + #print "drawing Grid." def generatePicture(self): @@ -2465,9 +2816,15 @@ class GridItem(UIGraphicsItem): c = np.clip(3.*(ppl-3), 0., 30.) linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c)) textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2)) - + #linePen.setCosmetic(True) + #linePen.setWidth(1) bx = (ax+1) % 2 for x in range(0, int(nl[ax])): + linePen.setCosmetic(False) + if ax == 0: + linePen.setWidthF(self.pixelHeight()) + else: + linePen.setWidthF(self.pixelWidth()) p.setPen(linePen) p1 = np.array([0.,0.]) p2 = np.array([0.,0.]) diff --git a/graphicsWindows.py b/graphicsWindows.py index 4f51f052..8b8e8678 100644 --- a/graphicsWindows.py +++ b/graphicsWindows.py @@ -113,6 +113,7 @@ class ImageWindow(ImageView): def __init__(self, *args, **kargs): mkQApp() self.win = QtGui.QMainWindow() + self.win.resize(800,600) if 'title' in kargs: self.win.setWindowTitle(kargs['title']) del kargs['title'] diff --git a/plotConfigTemplate.py b/plotConfigTemplate.py index 5d30654a..e0063b14 100644 --- a/plotConfigTemplate.py +++ b/plotConfigTemplate.py @@ -1,242 +1,247 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'plotConfigTemplate.ui' +# Form implementation generated from reading ui file './lib/util/pyqtgraph/plotConfigTemplate.ui' # -# Created: Sat Jul 17 00:28:43 2010 -# by: PyQt4 UI code generator 4.7.2 +# Created: Wed May 18 20:44:20 2011 +# by: PyQt4 UI code generator 4.8.3 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + class Ui_Form(object): def setupUi(self, Form): - Form.setObjectName("Form") + Form.setObjectName(_fromUtf8("Form")) Form.resize(250, 340) Form.setMaximumSize(QtCore.QSize(250, 350)) self.gridLayout_3 = QtGui.QGridLayout(Form) self.gridLayout_3.setMargin(0) self.gridLayout_3.setSpacing(0) - self.gridLayout_3.setObjectName("gridLayout_3") + self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) self.tabWidget = QtGui.QTabWidget(Form) self.tabWidget.setMaximumSize(QtCore.QSize(16777215, 16777215)) - self.tabWidget.setObjectName("tabWidget") + self.tabWidget.setObjectName(_fromUtf8("tabWidget")) self.tab = QtGui.QWidget() - self.tab.setObjectName("tab") + self.tab.setObjectName(_fromUtf8("tab")) self.verticalLayout = QtGui.QVBoxLayout(self.tab) self.verticalLayout.setSpacing(0) self.verticalLayout.setMargin(0) - self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.groupBox = QtGui.QGroupBox(self.tab) - self.groupBox.setObjectName("groupBox") + self.groupBox.setObjectName(_fromUtf8("groupBox")) self.gridLayout = QtGui.QGridLayout(self.groupBox) self.gridLayout.setMargin(0) self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.xManualRadio = QtGui.QRadioButton(self.groupBox) - self.xManualRadio.setObjectName("xManualRadio") + self.xManualRadio.setObjectName(_fromUtf8("xManualRadio")) self.gridLayout.addWidget(self.xManualRadio, 0, 0, 1, 1) self.xMinText = QtGui.QLineEdit(self.groupBox) - self.xMinText.setObjectName("xMinText") + self.xMinText.setObjectName(_fromUtf8("xMinText")) self.gridLayout.addWidget(self.xMinText, 0, 1, 1, 1) self.xMaxText = QtGui.QLineEdit(self.groupBox) - self.xMaxText.setObjectName("xMaxText") + self.xMaxText.setObjectName(_fromUtf8("xMaxText")) self.gridLayout.addWidget(self.xMaxText, 0, 2, 1, 1) self.xAutoRadio = QtGui.QRadioButton(self.groupBox) self.xAutoRadio.setChecked(True) - self.xAutoRadio.setObjectName("xAutoRadio") + self.xAutoRadio.setObjectName(_fromUtf8("xAutoRadio")) self.gridLayout.addWidget(self.xAutoRadio, 1, 0, 1, 1) self.xAutoPercentSpin = QtGui.QSpinBox(self.groupBox) self.xAutoPercentSpin.setEnabled(True) self.xAutoPercentSpin.setMinimum(1) self.xAutoPercentSpin.setMaximum(100) self.xAutoPercentSpin.setSingleStep(1) - self.xAutoPercentSpin.setProperty("value", 100) - self.xAutoPercentSpin.setObjectName("xAutoPercentSpin") + self.xAutoPercentSpin.setProperty(_fromUtf8("value"), 100) + self.xAutoPercentSpin.setObjectName(_fromUtf8("xAutoPercentSpin")) self.gridLayout.addWidget(self.xAutoPercentSpin, 1, 1, 1, 2) self.xLinkCombo = QtGui.QComboBox(self.groupBox) - self.xLinkCombo.setObjectName("xLinkCombo") + self.xLinkCombo.setObjectName(_fromUtf8("xLinkCombo")) self.gridLayout.addWidget(self.xLinkCombo, 2, 1, 1, 2) self.xMouseCheck = QtGui.QCheckBox(self.groupBox) self.xMouseCheck.setChecked(True) - self.xMouseCheck.setObjectName("xMouseCheck") + self.xMouseCheck.setObjectName(_fromUtf8("xMouseCheck")) self.gridLayout.addWidget(self.xMouseCheck, 3, 1, 1, 1) self.xLogCheck = QtGui.QCheckBox(self.groupBox) - self.xLogCheck.setObjectName("xLogCheck") + self.xLogCheck.setObjectName(_fromUtf8("xLogCheck")) self.gridLayout.addWidget(self.xLogCheck, 3, 0, 1, 1) self.label = QtGui.QLabel(self.groupBox) - self.label.setObjectName("label") + self.label.setObjectName(_fromUtf8("label")) self.gridLayout.addWidget(self.label, 2, 0, 1, 1) self.verticalLayout.addWidget(self.groupBox) self.groupBox_2 = QtGui.QGroupBox(self.tab) - self.groupBox_2.setObjectName("groupBox_2") + self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) self.gridLayout_2 = QtGui.QGridLayout(self.groupBox_2) self.gridLayout_2.setMargin(0) self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setObjectName("gridLayout_2") + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) self.yManualRadio = QtGui.QRadioButton(self.groupBox_2) - self.yManualRadio.setObjectName("yManualRadio") + self.yManualRadio.setObjectName(_fromUtf8("yManualRadio")) self.gridLayout_2.addWidget(self.yManualRadio, 0, 0, 1, 1) self.yMinText = QtGui.QLineEdit(self.groupBox_2) - self.yMinText.setObjectName("yMinText") + self.yMinText.setObjectName(_fromUtf8("yMinText")) self.gridLayout_2.addWidget(self.yMinText, 0, 1, 1, 1) self.yMaxText = QtGui.QLineEdit(self.groupBox_2) - self.yMaxText.setObjectName("yMaxText") + self.yMaxText.setObjectName(_fromUtf8("yMaxText")) self.gridLayout_2.addWidget(self.yMaxText, 0, 2, 1, 1) self.yAutoRadio = QtGui.QRadioButton(self.groupBox_2) self.yAutoRadio.setChecked(True) - self.yAutoRadio.setObjectName("yAutoRadio") + self.yAutoRadio.setObjectName(_fromUtf8("yAutoRadio")) self.gridLayout_2.addWidget(self.yAutoRadio, 1, 0, 1, 1) self.yAutoPercentSpin = QtGui.QSpinBox(self.groupBox_2) self.yAutoPercentSpin.setEnabled(True) self.yAutoPercentSpin.setMinimum(1) self.yAutoPercentSpin.setMaximum(100) self.yAutoPercentSpin.setSingleStep(1) - self.yAutoPercentSpin.setProperty("value", 100) - self.yAutoPercentSpin.setObjectName("yAutoPercentSpin") + self.yAutoPercentSpin.setProperty(_fromUtf8("value"), 100) + self.yAutoPercentSpin.setObjectName(_fromUtf8("yAutoPercentSpin")) self.gridLayout_2.addWidget(self.yAutoPercentSpin, 1, 1, 1, 2) self.yLinkCombo = QtGui.QComboBox(self.groupBox_2) - self.yLinkCombo.setObjectName("yLinkCombo") + self.yLinkCombo.setObjectName(_fromUtf8("yLinkCombo")) self.gridLayout_2.addWidget(self.yLinkCombo, 2, 1, 1, 2) self.yMouseCheck = QtGui.QCheckBox(self.groupBox_2) self.yMouseCheck.setChecked(True) - self.yMouseCheck.setObjectName("yMouseCheck") + self.yMouseCheck.setObjectName(_fromUtf8("yMouseCheck")) self.gridLayout_2.addWidget(self.yMouseCheck, 3, 1, 1, 1) self.yLogCheck = QtGui.QCheckBox(self.groupBox_2) - self.yLogCheck.setObjectName("yLogCheck") + self.yLogCheck.setObjectName(_fromUtf8("yLogCheck")) self.gridLayout_2.addWidget(self.yLogCheck, 3, 0, 1, 1) self.label_2 = QtGui.QLabel(self.groupBox_2) - self.label_2.setObjectName("label_2") + self.label_2.setObjectName(_fromUtf8("label_2")) self.gridLayout_2.addWidget(self.label_2, 2, 0, 1, 1) self.verticalLayout.addWidget(self.groupBox_2) - self.tabWidget.addTab(self.tab, "") + self.tabWidget.addTab(self.tab, _fromUtf8("")) self.tab_2 = QtGui.QWidget() - self.tab_2.setObjectName("tab_2") + self.tab_2.setObjectName(_fromUtf8("tab_2")) self.verticalLayout_2 = QtGui.QVBoxLayout(self.tab_2) self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setMargin(0) - self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.powerSpectrumGroup = QtGui.QGroupBox(self.tab_2) self.powerSpectrumGroup.setCheckable(True) self.powerSpectrumGroup.setChecked(False) - self.powerSpectrumGroup.setObjectName("powerSpectrumGroup") + self.powerSpectrumGroup.setObjectName(_fromUtf8("powerSpectrumGroup")) self.verticalLayout_2.addWidget(self.powerSpectrumGroup) self.decimateGroup = QtGui.QGroupBox(self.tab_2) self.decimateGroup.setCheckable(True) - self.decimateGroup.setObjectName("decimateGroup") + self.decimateGroup.setObjectName(_fromUtf8("decimateGroup")) self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) self.gridLayout_4.setMargin(0) self.gridLayout_4.setSpacing(0) - self.gridLayout_4.setObjectName("gridLayout_4") + self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) self.manualDecimateRadio = QtGui.QRadioButton(self.decimateGroup) self.manualDecimateRadio.setChecked(True) - self.manualDecimateRadio.setObjectName("manualDecimateRadio") + self.manualDecimateRadio.setObjectName(_fromUtf8("manualDecimateRadio")) self.gridLayout_4.addWidget(self.manualDecimateRadio, 0, 0, 1, 1) self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) self.downsampleSpin.setMinimum(1) self.downsampleSpin.setMaximum(100000) - self.downsampleSpin.setProperty("value", 1) - self.downsampleSpin.setObjectName("downsampleSpin") + self.downsampleSpin.setProperty(_fromUtf8("value"), 1) + self.downsampleSpin.setObjectName(_fromUtf8("downsampleSpin")) self.gridLayout_4.addWidget(self.downsampleSpin, 0, 1, 1, 1) self.autoDecimateRadio = QtGui.QRadioButton(self.decimateGroup) self.autoDecimateRadio.setChecked(False) - self.autoDecimateRadio.setObjectName("autoDecimateRadio") + self.autoDecimateRadio.setObjectName(_fromUtf8("autoDecimateRadio")) self.gridLayout_4.addWidget(self.autoDecimateRadio, 1, 0, 1, 1) self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup) - self.maxTracesCheck.setObjectName("maxTracesCheck") + self.maxTracesCheck.setObjectName(_fromUtf8("maxTracesCheck")) self.gridLayout_4.addWidget(self.maxTracesCheck, 2, 0, 1, 1) self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup) - self.maxTracesSpin.setObjectName("maxTracesSpin") + self.maxTracesSpin.setObjectName(_fromUtf8("maxTracesSpin")) self.gridLayout_4.addWidget(self.maxTracesSpin, 2, 1, 1, 1) self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup) - self.forgetTracesCheck.setObjectName("forgetTracesCheck") + self.forgetTracesCheck.setObjectName(_fromUtf8("forgetTracesCheck")) self.gridLayout_4.addWidget(self.forgetTracesCheck, 3, 0, 1, 2) self.verticalLayout_2.addWidget(self.decimateGroup) self.averageGroup = QtGui.QGroupBox(self.tab_2) self.averageGroup.setCheckable(True) self.averageGroup.setChecked(False) - self.averageGroup.setObjectName("averageGroup") + self.averageGroup.setObjectName(_fromUtf8("averageGroup")) self.gridLayout_5 = QtGui.QGridLayout(self.averageGroup) self.gridLayout_5.setMargin(0) self.gridLayout_5.setSpacing(0) - self.gridLayout_5.setObjectName("gridLayout_5") + self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) self.avgParamList = QtGui.QListWidget(self.averageGroup) - self.avgParamList.setObjectName("avgParamList") + self.avgParamList.setObjectName(_fromUtf8("avgParamList")) self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) self.verticalLayout_2.addWidget(self.averageGroup) - self.tabWidget.addTab(self.tab_2, "") + self.tabWidget.addTab(self.tab_2, _fromUtf8("")) self.tab_3 = QtGui.QWidget() - self.tab_3.setObjectName("tab_3") + self.tab_3.setObjectName(_fromUtf8("tab_3")) self.verticalLayout_3 = QtGui.QVBoxLayout(self.tab_3) - self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.alphaGroup = QtGui.QGroupBox(self.tab_3) self.alphaGroup.setCheckable(True) - self.alphaGroup.setObjectName("alphaGroup") + self.alphaGroup.setObjectName(_fromUtf8("alphaGroup")) self.horizontalLayout = QtGui.QHBoxLayout(self.alphaGroup) - self.horizontalLayout.setObjectName("horizontalLayout") + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.autoAlphaCheck = QtGui.QCheckBox(self.alphaGroup) self.autoAlphaCheck.setChecked(False) - self.autoAlphaCheck.setObjectName("autoAlphaCheck") + self.autoAlphaCheck.setObjectName(_fromUtf8("autoAlphaCheck")) self.horizontalLayout.addWidget(self.autoAlphaCheck) self.alphaSlider = QtGui.QSlider(self.alphaGroup) self.alphaSlider.setMaximum(1000) - self.alphaSlider.setProperty("value", 1000) + self.alphaSlider.setProperty(_fromUtf8("value"), 1000) self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.alphaSlider.setObjectName("alphaSlider") + self.alphaSlider.setObjectName(_fromUtf8("alphaSlider")) self.horizontalLayout.addWidget(self.alphaSlider) self.verticalLayout_3.addWidget(self.alphaGroup) self.gridGroup = QtGui.QGroupBox(self.tab_3) self.gridGroup.setCheckable(True) self.gridGroup.setChecked(False) - self.gridGroup.setObjectName("gridGroup") + self.gridGroup.setObjectName(_fromUtf8("gridGroup")) self.verticalLayout_4 = QtGui.QVBoxLayout(self.gridGroup) - self.verticalLayout_4.setObjectName("verticalLayout_4") + self.verticalLayout_4.setObjectName(_fromUtf8("verticalLayout_4")) self.gridAlphaSlider = QtGui.QSlider(self.gridGroup) self.gridAlphaSlider.setMaximum(255) - self.gridAlphaSlider.setProperty("value", 70) + self.gridAlphaSlider.setProperty(_fromUtf8("value"), 70) self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.gridAlphaSlider.setObjectName("gridAlphaSlider") + self.gridAlphaSlider.setObjectName(_fromUtf8("gridAlphaSlider")) self.verticalLayout_4.addWidget(self.gridAlphaSlider) self.verticalLayout_3.addWidget(self.gridGroup) self.pointsGroup = QtGui.QGroupBox(self.tab_3) self.pointsGroup.setCheckable(True) - self.pointsGroup.setObjectName("pointsGroup") + self.pointsGroup.setObjectName(_fromUtf8("pointsGroup")) self.verticalLayout_5 = QtGui.QVBoxLayout(self.pointsGroup) - self.verticalLayout_5.setObjectName("verticalLayout_5") + self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5")) self.autoPointsCheck = QtGui.QCheckBox(self.pointsGroup) self.autoPointsCheck.setChecked(True) - self.autoPointsCheck.setObjectName("autoPointsCheck") + self.autoPointsCheck.setObjectName(_fromUtf8("autoPointsCheck")) self.verticalLayout_5.addWidget(self.autoPointsCheck) self.verticalLayout_3.addWidget(self.pointsGroup) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout_3.addItem(spacerItem) - self.tabWidget.addTab(self.tab_3, "") + self.tabWidget.addTab(self.tab_3, _fromUtf8("")) self.tab_4 = QtGui.QWidget() - self.tab_4.setObjectName("tab_4") + self.tab_4.setObjectName(_fromUtf8("tab_4")) self.gridLayout_7 = QtGui.QGridLayout(self.tab_4) - self.gridLayout_7.setObjectName("gridLayout_7") + self.gridLayout_7.setObjectName(_fromUtf8("gridLayout_7")) spacerItem1 = QtGui.QSpacerItem(59, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.gridLayout_7.addItem(spacerItem1, 0, 0, 1, 1) self.gridLayout_6 = QtGui.QGridLayout() - self.gridLayout_6.setObjectName("gridLayout_6") + self.gridLayout_6.setObjectName(_fromUtf8("gridLayout_6")) self.saveSvgBtn = QtGui.QPushButton(self.tab_4) - self.saveSvgBtn.setObjectName("saveSvgBtn") + self.saveSvgBtn.setObjectName(_fromUtf8("saveSvgBtn")) self.gridLayout_6.addWidget(self.saveSvgBtn, 0, 0, 1, 1) self.saveImgBtn = QtGui.QPushButton(self.tab_4) - self.saveImgBtn.setObjectName("saveImgBtn") + self.saveImgBtn.setObjectName(_fromUtf8("saveImgBtn")) self.gridLayout_6.addWidget(self.saveImgBtn, 1, 0, 1, 1) self.saveMaBtn = QtGui.QPushButton(self.tab_4) - self.saveMaBtn.setObjectName("saveMaBtn") + self.saveMaBtn.setObjectName(_fromUtf8("saveMaBtn")) self.gridLayout_6.addWidget(self.saveMaBtn, 2, 0, 1, 1) self.saveCsvBtn = QtGui.QPushButton(self.tab_4) - self.saveCsvBtn.setObjectName("saveCsvBtn") + self.saveCsvBtn.setObjectName(_fromUtf8("saveCsvBtn")) self.gridLayout_6.addWidget(self.saveCsvBtn, 3, 0, 1, 1) self.gridLayout_7.addLayout(self.gridLayout_6, 0, 1, 1, 1) spacerItem2 = QtGui.QSpacerItem(59, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.gridLayout_7.addItem(spacerItem2, 0, 2, 1, 1) spacerItem3 = QtGui.QSpacerItem(20, 211, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.gridLayout_7.addItem(spacerItem3, 1, 1, 1, 1) - self.tabWidget.addTab(self.tab_4, "") + self.tabWidget.addTab(self.tab_4, _fromUtf8("")) self.gridLayout_3.addWidget(self.tabWidget, 0, 0, 1, 1) self.retranslateUi(Form) diff --git a/widgets.py b/widgets.py index 6f36fe22..8516fefc 100644 --- a/widgets.py +++ b/widgets.py @@ -487,7 +487,7 @@ class ROI(QtGui.QGraphicsObject): if lp1.length() == 0 or lp0.length() == 0: return - ang = newState['angle'] + lp0.angle(lp1) + ang = newState['angle'] - lp0.angle(lp1) if ang is None: return if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): @@ -504,7 +504,7 @@ class ROI(QtGui.QGraphicsObject): c1 = c * newState['size'] tr = QtGui.QTransform() #tr.rotate(-ang * 180. / np.pi) - tr.rotate(-ang) + tr.rotate(ang) cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos']) newState['angle'] = ang @@ -665,9 +665,20 @@ class ROI(QtGui.QGraphicsObject): origin = self.mapToItem(img, QtCore.QPointF(0, 0)) + ## vx and vy point in the directions of the slice axes, but must be scaled properly vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin - vectors = ((vx.x(), vx.y()), (vy.x(), vy.y())) + + lvx = np.sqrt(vx.x()**2 + vx.y()**2) + lvy = np.sqrt(vy.x()**2 + vy.y()**2) + pxLen = img.width() / data.shape[axes[0]] + sx = pxLen / lvx + sy = pxLen / lvy + + vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) + shape = self.state['size'] + shape = [abs(shape[0]/sx), abs(shape[1]/sy)] + origin = (origin.x(), origin.y()) #print "shape", shape, "vectors", vectors, "origin", origin @@ -812,16 +823,9 @@ class ROI(QtGui.QGraphicsObject): def applyGlobalTransform(self, tr): st = self.getState() + st['scale'] = st['size'] st = Transform(st) - #trans = QtGui.QTransform() - #trans.translate(*translate) - #trans.rotate(-rotate) - - #x2, y2 = trans.map(*st['pos']) - - #self.setAngle(st['angle']+rotate*np.pi/180.) - #self.setPos([x2, y2]) st = (st * tr).saveState() st['size'] = st['scale'] self.setState(st)