Updates merged in from ACQ4:

- converted most old-style signals into new-style for PySide compatibility (beware: API changes)
  - removed ObjectWorkaround, now just using QGraphicsWidget
  - performance enhancements, particularly in ROI.getArrayRegion
  - numerous bugfixes
This commit is contained in:
Luke Campagnola 2011-04-05 10:35:50 -04:00
parent 397a1c8a66
commit 7629bca34d
21 changed files with 1006 additions and 542 deletions

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from PyQt4 import QtGui, QtCore
import weakref
class TickSlider(QtGui.QGraphicsView):
def __init__(self, parent=None, orientation='bottom', allowAdd=True, **kargs):
@ -161,6 +161,9 @@ class TickSlider(QtGui.QGraphicsView):
class GradientWidget(TickSlider):
sigGradientChanged = QtCore.Signal(object)
def __init__(self, *args, **kargs):
TickSlider.__init__(self, *args, **kargs)
self.currentTick = None
@ -171,8 +174,10 @@ class GradientWidget(TickSlider):
self.colorDialog = QtGui.QColorDialog()
self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True)
QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged)
QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected)
#QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged)
self.colorDialog.currentColorChanged.connect(self.currentColorChanged)
#QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected)
self.colorDialog.rejected.connect(self.currentColorRejected)
#self.gradient = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(100,0))
self.scene.addItem(self.gradRect)
@ -199,7 +204,8 @@ class GradientWidget(TickSlider):
def updateGradient(self):
self.gradient = self.getGradient()
self.gradRect.setBrush(QtGui.QBrush(self.gradient))
self.emit(QtCore.SIGNAL('gradientChanged'), self)
#self.emit(QtCore.SIGNAL('gradientChanged'), self)
self.sigGradientChanged.emit(self)
def setLength(self, newLen):
TickSlider.setLength(self, newLen)
@ -356,7 +362,7 @@ class Tick(QtGui.QGraphicsPolygonItem):
def __init__(self, view, pos, color, movable=True, scale=10):
#QObjectWorkaround.__init__(self)
self.movable = movable
self.view = view
self.view = weakref.ref(view)
self.scale = scale
self.color = color
#self.endTick = endTick
@ -385,7 +391,7 @@ class Tick(QtGui.QGraphicsPolygonItem):
newPos.setY(self.pos().y())
#newPos.setX(min(max(newPos.x(), 0), 100))
self.setPos(newPos)
self.view.tickMoved(self, newPos)
self.view().tickMoved(self, newPos)
self.movedSincePress = True
#self.emit(QtCore.SIGNAL('tickChanged'), self)
ev.accept()
@ -405,7 +411,7 @@ class Tick(QtGui.QGraphicsPolygonItem):
def mouseReleaseEvent(self, ev):
#print self, "release", ev.scenePos()
if not self.movedSincePress:
self.view.tickClicked(self, ev)
self.view().tickClicked(self, ev)
#if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos:
#color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)

View File

@ -11,9 +11,15 @@ from PyQt4 import QtCore, QtGui, QtOpenGL, QtSvg
from Point import *
#from vector import *
import sys
#import debug
class GraphicsView(QtGui.QGraphicsView):
sigRangeChanged = QtCore.Signal(object, object)
sigMouseReleased = QtCore.Signal(object)
sigSceneMouseMoved = QtCore.Signal(object)
#sigRegionChanged = QtCore.Signal(object)
def __init__(self, parent=None, useOpenGL=True):
"""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
@ -25,6 +31,7 @@ class GraphicsView(QtGui.QGraphicsView):
The view can be panned using the middle mouse button and scaled using the right mouse button if
enabled via enableMouse()."""
self.closed = False
QtGui.QGraphicsView.__init__(self, parent)
if 'linux' in sys.platform: ## linux has bugs in opengl implementation
@ -42,7 +49,7 @@ class GraphicsView(QtGui.QGraphicsView):
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.setProperty("cursor",QtCore.QVariant(QtCore.Qt.ArrowCursor))
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.setFrameShape(QtGui.QFrame.NoFrame)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
@ -78,6 +85,14 @@ class GraphicsView(QtGui.QGraphicsView):
self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False)
self.clickAccepted = False
#def paintEvent(self, *args):
#prof = debug.Profiler('GraphicsView.paintEvent '+str(id(self)), disabled=True)
#QtGui.QGraphicsView.paintEvent(self, *args)
#prof.finish()
def close(self):
self.closed = True
def useOpenGL(self, b=True):
if b:
v = QtOpenGL.QGLWidget()
@ -112,6 +127,8 @@ class GraphicsView(QtGui.QGraphicsView):
self.lastButtonReleased = None
def resizeEvent(self, ev):
if self.closed:
return
if self.autoPixelRange:
self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height())
self.setRange(self.range, padding=0, disableAutoPixel=False)
@ -148,7 +165,8 @@ class GraphicsView(QtGui.QGraphicsView):
#print " translate:", st
self.setMatrix(m)
self.currentScale = scale
self.emit(QtCore.SIGNAL('viewChanged'), self.range)
#self.emit(QtCore.SIGNAL('viewChanged'), self.range)
self.sigRangeChanged.emit(self, self.range)
if propagate:
for v in self.lockedViewports:
@ -201,6 +219,16 @@ class GraphicsView(QtGui.QGraphicsView):
self.centralWidget.setGeometry(self.range)
self.updateMatrix(propagate)
def scaleToImage(self, image):
"""Scales such that pixels in image are the same size as screen pixels. This may result in a significant performance increase."""
pxSize = image.pixelSize()
tl = image.sceneBoundingRect().topLeft()
w = self.size().width() * pxSize[0]
h = self.size().height() * pxSize[1]
range = QtCore.QRectF(tl.x(), tl.y(), w, h)
self.setRange(range, padding=0)
def lockXRange(self, v1):
if not v1 in self.lockedViewports:
@ -299,7 +327,8 @@ class GraphicsView(QtGui.QGraphicsView):
if not self.mouseEnabled:
return
#self.mouseTrail.append(Point(self.mapToScene(ev.pos())))
self.emit(QtCore.SIGNAL("mouseReleased"), ev)
#self.emit(QtCore.SIGNAL("mouseReleased"), ev)
self.sigMouseReleased.emit(ev)
self.lastButtonReleased = ev.button()
return ## Everything below disabled for now..
@ -320,7 +349,8 @@ class GraphicsView(QtGui.QGraphicsView):
QtGui.QGraphicsView.mouseMoveEvent(self, ev)
if not self.mouseEnabled:
return
self.emit(QtCore.SIGNAL("sceneMouseMoved(PyQt_PyObject)"), self.mapToScene(ev.pos()))
#self.emit(QtCore.SIGNAL("sceneMouseMoved(PyQt_PyObject)"), self.mapToScene(ev.pos()))
self.sigSceneMouseMoved.emit(self.mapToScene(ev.pos()))
#print "moved. Grabber:", self.scene().mouseGrabberItem()
@ -333,13 +363,15 @@ class GraphicsView(QtGui.QGraphicsView):
#if self.yInverted:
#scale[0] = 1. / scale[0]
self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos))
self.emit(QtCore.SIGNAL('regionChanged(QRectF)'), self.range)
#self.emit(QtCore.SIGNAL('regionChanged(QRectF)'), self.range)
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
self.translate(tr[0], tr[1])
self.emit(QtCore.SIGNAL('regionChanged(QRectF)'), self.range)
#self.emit(QtCore.SIGNAL('regionChanged(QRectF)'), self.range)
self.sigRangeChanged.emit(self, self.range)
#return ## Everything below disabled for now..

View File

@ -31,6 +31,9 @@ class PlotROI(ROI):
class ImageView(QtGui.QWidget):
sigTimeChanged = QtCore.Signal(object, object)
def __init__(self, parent=None, name="ImageView", *args):
QtGui.QWidget.__init__(self, parent, *args)
self.levelMax = 4096
@ -106,25 +109,38 @@ class ImageView(QtGui.QWidget):
setattr(self, fn, getattr(self.ui.graphicsView, fn))
#QtCore.QObject.connect(self.ui.timeSlider, QtCore.SIGNAL('valueChanged(int)'), self.timeChanged)
self.timeLine.connect(self.timeLine, QtCore.SIGNAL('positionChanged'), self.timeLineChanged)
#self.timeLine.connect(self.timeLine, QtCore.SIGNAL('positionChanged'), self.timeLineChanged)
self.timeLine.sigPositionChanged.connect(self.timeLineChanged)
#QtCore.QObject.connect(self.ui.whiteSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage)
#QtCore.QObject.connect(self.ui.blackSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage)
QtCore.QObject.connect(self.ui.gradientWidget, QtCore.SIGNAL('gradientChanged'), self.updateImage)
QtCore.QObject.connect(self.ui.roiBtn, QtCore.SIGNAL('clicked()'), self.roiClicked)
self.roi.connect(self.roi, QtCore.SIGNAL('regionChanged'), self.roiChanged)
QtCore.QObject.connect(self.ui.normBtn, QtCore.SIGNAL('toggled(bool)'), self.normToggled)
QtCore.QObject.connect(self.ui.normDivideRadio, QtCore.SIGNAL('clicked()'), self.updateNorm)
QtCore.QObject.connect(self.ui.normSubtractRadio, QtCore.SIGNAL('clicked()'), self.updateNorm)
QtCore.QObject.connect(self.ui.normOffRadio, QtCore.SIGNAL('clicked()'), self.updateNorm)
QtCore.QObject.connect(self.ui.normROICheck, QtCore.SIGNAL('clicked()'), self.updateNorm)
QtCore.QObject.connect(self.ui.normFrameCheck, QtCore.SIGNAL('clicked()'), self.updateNorm)
QtCore.QObject.connect(self.ui.normTimeRangeCheck, QtCore.SIGNAL('clicked()'), self.updateNorm)
QtCore.QObject.connect(self.playTimer, QtCore.SIGNAL('timeout()'), self.timeout)
#QtCore.QObject.connect(self.ui.gradientWidget, QtCore.SIGNAL('gradientChanged'), self.updateImage)
self.ui.gradientWidget.sigGradientChanged.connect(self.updateImage)
#QtCore.QObject.connect(self.ui.roiBtn, QtCore.SIGNAL('clicked()'), self.roiClicked)
self.ui.roiBtn.clicked.connect(self.roiClicked)
#self.roi.connect(self.roi, QtCore.SIGNAL('regionChanged'), self.roiChanged)
self.roi.sigRegionChanged.connect(self.roiChanged)
#QtCore.QObject.connect(self.ui.normBtn, QtCore.SIGNAL('toggled(bool)'), self.normToggled)
self.ui.normBtn.toggled.connect(self.normToggled)
#QtCore.QObject.connect(self.ui.normDivideRadio, QtCore.SIGNAL('clicked()'), self.updateNorm)
self.ui.normDivideRadio.clicked.connect(self.updateNorm)
#QtCore.QObject.connect(self.ui.normSubtractRadio, QtCore.SIGNAL('clicked()'), self.updateNorm)
self.ui.normSubtractRadio.clicked.connect(self.updateNorm)
#QtCore.QObject.connect(self.ui.normOffRadio, QtCore.SIGNAL('clicked()'), self.updateNorm)
self.ui.normOffRadio.clicked.connect(self.updateNorm)
#QtCore.QObject.connect(self.ui.normROICheck, QtCore.SIGNAL('clicked()'), self.updateNorm)
self.ui.normROICheck.clicked.connect(self.updateNorm)
#QtCore.QObject.connect(self.ui.normFrameCheck, QtCore.SIGNAL('clicked()'), self.updateNorm)
self.ui.normFrameCheck.clicked.connect(self.updateNorm)
#QtCore.QObject.connect(self.ui.normTimeRangeCheck, QtCore.SIGNAL('clicked()'), self.updateNorm)
self.ui.normTimeRangeCheck.clicked.connect(self.updateNorm)
#QtCore.QObject.connect(self.playTimer, QtCore.SIGNAL('timeout()'), self.timeout)
self.playTimer.timeout.connect(self.timeout)
##QtCore.QObject.connect(self.ui.normStartSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateNorm)
#QtCore.QObject.connect(self.ui.normStopSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateNorm)
self.normProxy = proxyConnect(self.normRgn, QtCore.SIGNAL('regionChanged'), self.updateNorm)
self.normRoi.connect(self.normRoi, QtCore.SIGNAL('regionChangeFinished'), self.updateNorm)
self.normProxy = proxyConnect(None, self.normRgn.sigRegionChanged, self.updateNorm)
#self.normRoi.connect(self.normRoi, QtCore.SIGNAL('regionChangeFinished'), self.updateNorm)
self.normRoi.sigRegionChangeFinished.connect(self.updateNorm)
self.ui.roiPlot.registerPlot(self.name + '_ROI')
@ -135,11 +151,16 @@ class ImageView(QtGui.QWidget):
#self.quit()
#QtGui.QWidget.__dtor__(self)
def quit(self):
def close(self):
self.ui.graphicsView.close()
self.ui.gradientWidget.sigGradientChanged.disconnect(self.updateImage)
self.scene.clear()
del self.image
del self.imageDisp
#self.image = None
#self.imageDisp = None
self.ui.roiPlot.close()
self.setParent(None)
def keyPressEvent(self, ev):
if ev.key() == QtCore.Qt.Key_Space:
@ -319,7 +340,6 @@ 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.
"""
if not isinstance(img, np.ndarray):
raise Exception("Image must be specified as ndarray.")
self.image = img
@ -348,7 +368,7 @@ class ImageView(QtGui.QWidget):
elif img.ndim == 4:
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3}
else:
raise Exception("Can not interpret image with dimensions %s" % (str(img)))
raise Exception("Can not interpret image with dimensions %s" % (str(img.shape)))
elif isinstance(axes, dict):
self.axes = axes.copy()
elif isinstance(axes, list) or isinstance(axes, tuple):
@ -441,7 +461,7 @@ class ImageView(QtGui.QWidget):
#else:
#norm = zeros(image.shape)
if div:
norm = norm.astype(float32)
norm = norm.astype(np.float32)
if self.ui.normTimeRangeCheck.isChecked() and image.ndim == 3:
(sind, start) = self.timeIndex(self.normRgn.lines[0])
@ -464,7 +484,7 @@ class ImageView(QtGui.QWidget):
if self.ui.normROICheck.isChecked() and image.ndim == 3:
n = self.normRoi.getArrayRegion(norm, self.imageItem, (1, 2)).mean(axis=1).mean(axis=1)
n = n[:,newaxis,newaxis]
n = n[:,np.newaxis,np.newaxis]
#print start, end, sind, eind
if div:
norm /= n
@ -483,7 +503,8 @@ class ImageView(QtGui.QWidget):
self.currentIndex = ind
self.updateImage()
#self.timeLine.setPos(time)
self.emit(QtCore.SIGNAL('timeChanged'), ind, time)
#self.emit(QtCore.SIGNAL('timeChanged'), ind, time)
self.sigTimeChanged.emit(ind, time)
def updateImage(self):
## Redraw image on screen

View File

@ -63,4 +63,7 @@ class MultiPlotItem(QtGui.QGraphicsWidget):
else:
raise Exception("Data type %s not (yet?) supported for MultiPlot." % type(data))
def close(self):
for p in self.plots:
p[0].close()

View File

@ -37,3 +37,7 @@ class MultiPlotWidget(GraphicsView):
def restoreState(self, state):
pass
#return self.plotItem.restoreState(state)
def close(self):
self.mPlotItem.close()
self.setParent(None)

View File

@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
from PyQt4 import QtGui, QtCore
"""For circumventing PyQt's lack of multiple inheritance (just until PySide becomes stable)"""
class Obj(QtCore.QObject):
def event(self, ev):
self.emit(QtCore.SIGNAL('event'), ev)
return QtCore.QObject.event(self, ev)
class QObjectWorkaround:
def __init__(self):
self._qObj_ = Obj()
self.connect(QtCore.SIGNAL('event'), self.event)
def connect(self, *args):
if args[0] is self:
return QtCore.QObject.connect(self._qObj_, *args[1:])
else:
return QtCore.QObject.connect(self._qObj_, *args)
def disconnect(self, *args):
return QtCore.QObject.disconnect(self._qObj_, *args)
def emit(self, *args):
return QtCore.QObject.emit(self._qObj_, *args)
def blockSignals(self, b):
return self._qObj_.blockSignals(b)
def setProperty(self, prop, val):
return self._qObj_.setProperty(prop, val)
def property(self, prop):
return self._qObj_.property(prop)
def event(self, ev):
pass
#class QGraphicsObject(QtGui.QGraphicsItem, QObjectWorkaround):
#def __init__(self, *args):
#QtGui.QGraphicsItem.__init__(self, *args)
#QObjectWorkaround.__init__(self)
class QGraphicsObject(QtGui.QGraphicsWidget):
def shape(self):
return QtGui.QGraphicsItem.shape(self)
#QGraphicsObject = QtGui.QGraphicsObject

View File

@ -25,6 +25,7 @@ from functions import *
#tryWorkaround(QtCore, QtGui)
import weakref
import numpy as np
#import debug
try:
from WidgetGroup import *
@ -40,6 +41,11 @@ except:
class PlotItem(QtGui.QGraphicsWidget):
sigYRangeChanged = QtCore.Signal(object, object)
sigXRangeChanged = QtCore.Signal(object, object)
sigRangeChanged = QtCore.Signal(object, object)
"""Plot graphics item that can be added to any graphics scene. Implements axis titles, scales, interactive viewbox."""
lastFileDir = None
managers = {}
@ -60,8 +66,10 @@ class PlotItem(QtGui.QGraphicsWidget):
proxy.setWidget(b)
proxy.setAcceptHoverEvents(False)
b.setStyleSheet("background-color: #000000; color: #888; font-size: 6pt")
QtCore.QObject.connect(self.ctrlBtn, QtCore.SIGNAL('clicked()'), self.ctrlBtnClicked)
QtCore.QObject.connect(self.autoBtn, QtCore.SIGNAL('clicked()'), self.enableAutoScale)
#QtCore.QObject.connect(self.ctrlBtn, QtCore.SIGNAL('clicked()'), self.ctrlBtnClicked)
self.ctrlBtn.clicked.connect(self.ctrlBtnClicked)
#QtCore.QObject.connect(self.autoBtn, QtCore.SIGNAL('clicked()'), self.enableAutoScale)
self.autoBtn.clicked.connect(self.enableAutoScale)
self.layout = QtGui.QGraphicsGridLayout()
@ -71,11 +79,15 @@ class PlotItem(QtGui.QGraphicsWidget):
self.layout.setVerticalSpacing(0)
self.vb = ViewBox()
QtCore.QObject.connect(self.vb, QtCore.SIGNAL('xRangeChanged'), self.xRangeChanged)
QtCore.QObject.connect(self.vb, QtCore.SIGNAL('yRangeChanged'), self.yRangeChanged)
QtCore.QObject.connect(self.vb, QtCore.SIGNAL('rangeChangedManually'), self.enableManualScale)
#QtCore.QObject.connect(self.vb, QtCore.SIGNAL('xRangeChanged'), self.xRangeChanged)
self.vb.sigXRangeChanged.connect(self.xRangeChanged)
#QtCore.QObject.connect(self.vb, QtCore.SIGNAL('yRangeChanged'), self.yRangeChanged)
self.vb.sigYRangeChanged.connect(self.yRangeChanged)
#QtCore.QObject.connect(self.vb, QtCore.SIGNAL('rangeChangedManually'), self.enableManualScale)
self.vb.sigRangeChangedManually.connect(self.enableManualScale)
QtCore.QObject.connect(self.vb, QtCore.SIGNAL('viewChanged'), self.viewChanged)
#QtCore.QObject.connect(self.vb, QtCore.SIGNAL('viewChanged'), self.viewChanged)
self.vb.sigRangeChanged.connect(self.viewRangeChanged)
self.layout.addItem(self.vb, 2, 1)
self.alpha = 1.0
@ -161,53 +173,81 @@ class PlotItem(QtGui.QGraphicsWidget):
self.setAcceptHoverEvents(True)
## Connect control widgets
QtCore.QObject.connect(c.xMinText, QtCore.SIGNAL('editingFinished()'), self.setManualXScale)
QtCore.QObject.connect(c.xMaxText, QtCore.SIGNAL('editingFinished()'), self.setManualXScale)
QtCore.QObject.connect(c.yMinText, QtCore.SIGNAL('editingFinished()'), self.setManualYScale)
QtCore.QObject.connect(c.yMaxText, QtCore.SIGNAL('editingFinished()'), self.setManualYScale)
#QtCore.QObject.connect(c.xMinText, QtCore.SIGNAL('editingFinished()'), self.setManualXScale)
c.xMinText.editingFinished.connect(self.setManualXScale)
#QtCore.QObject.connect(c.xMaxText, QtCore.SIGNAL('editingFinished()'), self.setManualXScale)
c.xMaxText.editingFinished.connect(self.setManualXScale)
#QtCore.QObject.connect(c.yMinText, QtCore.SIGNAL('editingFinished()'), self.setManualYScale)
c.yMinText.editingFinished.connect(self.setManualYScale)
#QtCore.QObject.connect(c.yMaxText, QtCore.SIGNAL('editingFinished()'), self.setManualYScale)
c.yMaxText.editingFinished.connect(self.setManualYScale)
QtCore.QObject.connect(c.xManualRadio, QtCore.SIGNAL('clicked()'), self.updateXScale)
QtCore.QObject.connect(c.yManualRadio, QtCore.SIGNAL('clicked()'), self.updateYScale)
#QtCore.QObject.connect(c.xManualRadio, QtCore.SIGNAL('clicked()'), self.updateXScale)
c.xManualRadio.clicked.connect(lambda: self.updateXScale())
#QtCore.QObject.connect(c.yManualRadio, QtCore.SIGNAL('clicked()'), self.updateYScale)
c.yManualRadio.clicked.connect(lambda: self.updateYScale())
QtCore.QObject.connect(c.xAutoRadio, QtCore.SIGNAL('clicked()'), self.updateXScale)
QtCore.QObject.connect(c.yAutoRadio, QtCore.SIGNAL('clicked()'), self.updateYScale)
#QtCore.QObject.connect(c.xAutoRadio, QtCore.SIGNAL('clicked()'), self.updateXScale)
c.xAutoRadio.clicked.connect(self.updateXScale)
#QtCore.QObject.connect(c.yAutoRadio, QtCore.SIGNAL('clicked()'), self.updateYScale)
c.yAutoRadio.clicked.connect(self.updateYScale)
QtCore.QObject.connect(c.xAutoPercentSpin, QtCore.SIGNAL('valueChanged(int)'), self.replot)
QtCore.QObject.connect(c.yAutoPercentSpin, QtCore.SIGNAL('valueChanged(int)'), self.replot)
#QtCore.QObject.connect(c.xAutoPercentSpin, QtCore.SIGNAL('valueChanged(int)'), self.replot)
c.xAutoPercentSpin.valueChanged.connect(self.replot)
#QtCore.QObject.connect(c.yAutoPercentSpin, QtCore.SIGNAL('valueChanged(int)'), self.replot)
c.yAutoPercentSpin.valueChanged.connect(self.replot)
#QtCore.QObject.connect(c.xLogCheck, QtCore.SIGNAL('toggled(bool)'), self.setXLog)
#QtCore.QObject.connect(c.yLogCheck, QtCore.SIGNAL('toggled(bool)'), self.setYLog)
QtCore.QObject.connect(c.alphaGroup, QtCore.SIGNAL('toggled(bool)'), self.updateAlpha)
QtCore.QObject.connect(c.alphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateAlpha)
QtCore.QObject.connect(c.autoAlphaCheck, QtCore.SIGNAL('toggled(bool)'), self.updateAlpha)
#QtCore.QObject.connect(c.alphaGroup, QtCore.SIGNAL('toggled(bool)'), self.updateAlpha)
c.alphaGroup.toggled.connect(self.updateAlpha)
#QtCore.QObject.connect(c.alphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateAlpha)
c.alphaSlider.valueChanged.connect(self.updateAlpha)
#QtCore.QObject.connect(c.autoAlphaCheck, QtCore.SIGNAL('toggled(bool)'), self.updateAlpha)
c.autoAlphaCheck.toggled.connect(self.updateAlpha)
QtCore.QObject.connect(c.gridGroup, QtCore.SIGNAL('toggled(bool)'), self.updateGrid)
QtCore.QObject.connect(c.gridAlphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateGrid)
#QtCore.QObject.connect(c.gridGroup, QtCore.SIGNAL('toggled(bool)'), self.updateGrid)
c.gridGroup.toggled.connect(self.updateGrid)
#QtCore.QObject.connect(c.gridAlphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateGrid)
c.gridAlphaSlider.valueChanged.connect(self.updateGrid)
QtCore.QObject.connect(c.powerSpectrumGroup, QtCore.SIGNAL('toggled(bool)'), self.updateSpectrumMode)
QtCore.QObject.connect(c.saveSvgBtn, QtCore.SIGNAL('clicked()'), self.saveSvgClicked)
QtCore.QObject.connect(c.saveImgBtn, QtCore.SIGNAL('clicked()'), self.saveImgClicked)
QtCore.QObject.connect(c.saveCsvBtn, QtCore.SIGNAL('clicked()'), self.saveCsvClicked)
#QtCore.QObject.connect(c.powerSpectrumGroup, QtCore.SIGNAL('toggled(bool)'), self.updateSpectrumMode)
c.powerSpectrumGroup.toggled.connect(self.updateSpectrumMode)
#QtCore.QObject.connect(c.saveSvgBtn, QtCore.SIGNAL('clicked()'), self.saveSvgClicked)
c.saveSvgBtn.clicked.connect(self.saveSvgClicked)
#QtCore.QObject.connect(c.saveImgBtn, QtCore.SIGNAL('clicked()'), self.saveImgClicked)
c.saveImgBtn.clicked.connect(self.saveImgClicked)
#QtCore.QObject.connect(c.saveCsvBtn, QtCore.SIGNAL('clicked()'), self.saveCsvClicked)
c.saveCsvBtn.clicked.connect(self.saveCsvClicked)
#QtCore.QObject.connect(c.gridGroup, QtCore.SIGNAL('toggled(bool)'), self.updateGrid)
#QtCore.QObject.connect(c.gridAlphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateGrid)
QtCore.QObject.connect(self.ctrl.xLinkCombo, QtCore.SIGNAL('currentIndexChanged(int)'), self.xLinkComboChanged)
QtCore.QObject.connect(self.ctrl.yLinkCombo, QtCore.SIGNAL('currentIndexChanged(int)'), self.yLinkComboChanged)
#QtCore.QObject.connect(self.ctrl.xLinkCombo, QtCore.SIGNAL('currentIndexChanged(int)'), self.xLinkComboChanged)
self.ctrl.xLinkCombo.currentIndexChanged.connect(self.xLinkComboChanged)
#QtCore.QObject.connect(self.ctrl.yLinkCombo, QtCore.SIGNAL('currentIndexChanged(int)'), self.yLinkComboChanged)
self.ctrl.yLinkCombo.currentIndexChanged.connect(self.yLinkComboChanged)
QtCore.QObject.connect(c.downsampleSpin, QtCore.SIGNAL('valueChanged(int)'), self.updateDownsampling)
#QtCore.QObject.connect(c.downsampleSpin, QtCore.SIGNAL('valueChanged(int)'), self.updateDownsampling)
c.downsampleSpin.valueChanged.connect(self.updateDownsampling)
QtCore.QObject.connect(self.ctrl.avgParamList, QtCore.SIGNAL('itemClicked(QListWidgetItem*)'), self.avgParamListClicked)
QtCore.QObject.connect(self.ctrl.averageGroup, QtCore.SIGNAL('toggled(bool)'), self.avgToggled)
#QtCore.QObject.connect(self.ctrl.avgParamList, QtCore.SIGNAL('itemClicked(QListWidgetItem*)'), self.avgParamListClicked)
self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked)
#QtCore.QObject.connect(self.ctrl.averageGroup, QtCore.SIGNAL('toggled(bool)'), self.avgToggled)
self.ctrl.averageGroup.toggled.connect(self.avgToggled)
#QtCore.QObject.connect(self.ctrl.pointsGroup, QtCore.SIGNAL('toggled(bool)'), self.updatePointMode)
#QtCore.QObject.connect(self.ctrl.autoPointsCheck, QtCore.SIGNAL('toggled(bool)'), self.updatePointMode)
QtCore.QObject.connect(self.ctrl.maxTracesCheck, QtCore.SIGNAL('toggled(bool)'), self.updateDecimation)
QtCore.QObject.connect(self.ctrl.maxTracesSpin, QtCore.SIGNAL('valueChanged(int)'), self.updateDecimation)
QtCore.QObject.connect(c.xMouseCheck, QtCore.SIGNAL('toggled(bool)'), self.mouseCheckChanged)
QtCore.QObject.connect(c.yMouseCheck, QtCore.SIGNAL('toggled(bool)'), self.mouseCheckChanged)
#QtCore.QObject.connect(self.ctrl.maxTracesCheck, QtCore.SIGNAL('toggled(bool)'), self.updateDecimation)
self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation)
#QtCore.QObject.connect(self.ctrl.maxTracesSpin, QtCore.SIGNAL('valueChanged(int)'), self.updateDecimation)
self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation)
#QtCore.QObject.connect(c.xMouseCheck, QtCore.SIGNAL('toggled(bool)'), self.mouseCheckChanged)
c.xMouseCheck.toggled.connect(self.mouseCheckChanged)
#QtCore.QObject.connect(c.yMouseCheck, QtCore.SIGNAL('toggled(bool)'), self.mouseCheckChanged)
c.yMouseCheck.toggled.connect(self.mouseCheckChanged)
self.xLinkPlot = None
self.yLinkPlot = None
@ -236,9 +276,16 @@ class PlotItem(QtGui.QGraphicsWidget):
if len(kargs) > 0:
self.plot(**kargs)
#def paint(self, *args):
#prof = debug.Profiler('PlotItem.paint', disabled=True)
#QtGui.QGraphicsWidget.paint(self, *args)
#prof.finish()
def __del__(self):
def close(self):
#print "delete", self
if self.manager is not None:
self.manager.sigWidgetListChanged.disconnect(self.updatePlotList)
self.manager.removeWidget(self.name)
def registerPlot(self, name):
@ -249,7 +296,8 @@ class PlotItem(QtGui.QGraphicsWidget):
PlotItem.managers[win] = PlotWidgetManager()
self.manager = PlotItem.managers[win]
self.manager.addWidget(self, name)
QtCore.QObject.connect(self.manager, QtCore.SIGNAL('widgetListChanged'), self.updatePlotList)
#QtCore.QObject.connect(self.manager, QtCore.SIGNAL('widgetListChanged'), self.updatePlotList)
self.manager.sigWidgetListChanged.connect(self.updatePlotList)
self.updatePlotList()
def updatePlotList(self):
@ -269,7 +317,8 @@ class PlotItem(QtGui.QGraphicsWidget):
except:
import gc
refs= gc.get_referrers(self)
print " error during update. Referrers are:", refs
print " error during update of", self
print " Referrers are:", refs
raise
def updateGrid(self, *args):
@ -291,8 +340,9 @@ class PlotItem(QtGui.QGraphicsWidget):
def viewChanged(self, *args):
self.emit(QtCore.SIGNAL('viewChanged'), *args)
def viewRangeChanged(self, vb, range):
#self.emit(QtCore.SIGNAL('viewChanged'), *args)
self.sigRangeChanged.emit(self, range)
def blockLink(self, b):
self.linksBlocked = b
@ -466,7 +516,8 @@ class PlotItem(QtGui.QGraphicsWidget):
#self.setLabel(l, unitPrefix='')
#self.getScale(l).setScale(1.0)
self.emit(QtCore.SIGNAL('xRangeChanged'), self, range)
#self.emit(QtCore.SIGNAL('xRangeChanged'), self, range)
self.sigXRangeChanged.emit(self, range)
def yRangeChanged(self, _, range):
if any(np.isnan(range)) or any(np.isinf(range)):
@ -484,7 +535,8 @@ class PlotItem(QtGui.QGraphicsWidget):
#else:
#self.setLabel(l, unitPrefix='')
#self.getScale(l).setScale(1.0)
self.emit(QtCore.SIGNAL('yRangeChanged'), self, range)
#self.emit(QtCore.SIGNAL('yRangeChanged'), self, range)
self.sigYRangeChanged.emit(self, range)
def enableAutoScale(self):
@ -568,7 +620,8 @@ class PlotItem(QtGui.QGraphicsWidget):
self.curves.remove(item)
self.updateDecimation()
self.updateParamList()
item.connect(item, QtCore.SIGNAL('plotChanged'), self.plotChanged)
#item.connect(item, QtCore.SIGNAL('plotChanged'), self.plotChanged)
item.sigPlotChanged.connect(self.plotChanged)
def clear(self):
for i in self.items[:]:
@ -644,7 +697,8 @@ class PlotItem(QtGui.QGraphicsWidget):
if self.ctrl.averageGroup.isChecked():
self.addAvgCurve(c)
c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged)
#c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged)
c.sigPlotChanged.connect(self.plotChanged)
self.plotChanged()
def plotChanged(self, curve=None):
@ -704,60 +758,141 @@ class PlotItem(QtGui.QGraphicsWidget):
self.paramList[p] = (i.checkState() == QtCore.Qt.Checked)
#print "paramList:", self.paramList
## This is bullshit.
def writeSvg(self, fileName=None):
if fileName is None:
fileName = QtGui.QFileDialog.getSaveFileName()
if isinstance(fileName, tuple):
raise Exception("Not implemented yet..")
fileName = str(fileName)
PlotItem.lastFileDir = os.path.dirname(fileName)
self.svg = QtSvg.QSvgGenerator()
self.svg.setFileName(fileName)
res = 120.
#bounds = self.mapRectToScene(self.boundingRect())
view = self.scene().views()[0]
bounds = view.viewport().rect()
bounds = QtCore.QRectF(0, 0, bounds.width(), bounds.height())
rect = self.vb.viewRect()
xRange = rect.left(), rect.right()
self.svg.setResolution(res)
#self.svg.setSize(QtCore.QSize(self.size().width(), self.size().height()))
self.svg.setViewBox(bounds)
svg = ""
fh = open(fileName, 'w')
self.svg.setSize(QtCore.QSize(bounds.width(), bounds.height()))
dx = max(rect.right(),0) - min(rect.left(),0)
ymn = min(rect.top(), rect.bottom())
ymx = max(rect.top(), rect.bottom())
dy = max(ymx,0) - min(ymn,0)
sx = 1.
sy = 1.
while dx*sx < 10:
sx *= 1000
while dy*sy < 10:
sy *= 1000
sy *= -1
painter = QtGui.QPainter(self.svg)
#self.scene().render(painter, QtCore.QRectF(), view.mapToScene(bounds).boundingRect())
#fh.write('<svg viewBox="%f %f %f %f">\n' % (rect.left()*sx, rect.top()*sx, rect.width()*sy, rect.height()*sy))
fh.write('<svg>\n')
fh.write('<path fill="none" stroke="#000000" stroke-opacity="0.5" stroke-width="1" d="M%f,0 L%f,0"/>\n' % (rect.left()*sx, rect.right()*sx))
fh.write('<path fill="none" stroke="#000000" stroke-opacity="0.5" stroke-width="1" d="M0,%f L0,%f"/>\n' % (rect.top()*sy, rect.bottom()*sy))
#items = self.scene().items()
#self.scene().views()[0].drawItems(painter, len(items), items)
#print view, type(view)
view.render(painter, bounds)
for item in self.curves:
if isinstance(item, PlotCurveItem):
color = colorStr(item.pen.color())
opacity = item.pen.color().alpha() / 255.
color = color[:6]
x, y = item.getData()
mask = (x > xRange[0]) * (x < xRange[1])
mask[:-1] += mask[1:]
m2 = mask.copy()
mask[1:] += m2[:-1]
x = x[mask]
y = y[mask]
painter.end()
x *= sx
y *= sy
## Workaround to set pen widths correctly
import re
data = open(fileName).readlines()
for i in range(len(data)):
line = data[i]
m = re.match(r'(<g .*)stroke-width="1"(.*transform="matrix\(([^\)]+)\)".*)', line)
if m is not None:
#print "Matched group:", line
g = m.groups()
matrix = map(float, g[2].split(','))
#print "matrix:", matrix
scale = max(abs(matrix[0]), abs(matrix[3]))
if scale == 0 or scale == 1.0:
#fh.write('<g fill="none" stroke="#%s" stroke-opacity="1" stroke-width="1">\n' % color)
fh.write('<path fill="none" stroke="#%s" stroke-opacity="%f" stroke-width="1" d="M%f,%f ' % (color, opacity, x[0], y[0]))
for i in xrange(1, len(x)):
fh.write('L%f,%f ' % (x[i], y[i]))
fh.write('"/>')
#fh.write("</g>")
for item in self.dataItems:
if isinstance(item, ScatterPlotItem):
pRect = item.boundingRect()
vRect = pRect.intersected(rect)
for point in item.points():
pos = point.pos()
if not rect.contains(pos):
continue
data[i] = g[0] + ' stroke-width="%0.2g" ' % (1.0/scale) + g[1] + '\n'
#print "old line:", line
#print "new line:", data[i]
open(fileName, 'w').write(''.join(data))
color = colorStr(point.brush.color())
opacity = point.brush.color().alpha() / 255.
color = color[:6]
x = pos.x() * sx
y = pos.y() * sy
fh.write('<circle cx="%f" cy="%f" r="1" fill="#%s" stroke="none" fill-opacity="%f"/>\n' % (x, y, color, opacity))
#fh.write('<path fill="none" stroke="#%s" stroke-opacity="%f" stroke-width="1" d="M%f,%f ' % (color, opacity, x[0], y[0]))
#for i in xrange(1, len(x)):
#fh.write('L%f,%f ' % (x[i], y[i]))
#fh.write('"/>')
## get list of curves, scatter plots
fh.write("</svg>\n")
#def writeSvg(self, fileName=None):
#if fileName is None:
#fileName = QtGui.QFileDialog.getSaveFileName()
#fileName = str(fileName)
#PlotItem.lastFileDir = os.path.dirname(fileName)
#self.svg = QtSvg.QSvgGenerator()
#self.svg.setFileName(fileName)
#res = 120.
#view = self.scene().views()[0]
#bounds = view.viewport().rect()
#bounds = QtCore.QRectF(0, 0, bounds.width(), bounds.height())
#self.svg.setResolution(res)
#self.svg.setViewBox(bounds)
#self.svg.setSize(QtCore.QSize(bounds.width(), bounds.height()))
#painter = QtGui.QPainter(self.svg)
#view.render(painter, bounds)
#painter.end()
### Workaround to set pen widths correctly
#import re
#data = open(fileName).readlines()
#for i in range(len(data)):
#line = data[i]
#m = re.match(r'(<g .*)stroke-width="1"(.*transform="matrix\(([^\)]+)\)".*)', line)
#if m is not None:
##print "Matched group:", line
#g = m.groups()
#matrix = map(float, g[2].split(','))
##print "matrix:", matrix
#scale = max(abs(matrix[0]), abs(matrix[3]))
#if scale == 0 or scale == 1.0:
#continue
#data[i] = g[0] + ' stroke-width="%0.2g" ' % (1.0/scale) + g[1] + '\n'
##print "old line:", line
##print "new line:", data[i]
#open(fileName, 'w').write(''.join(data))
def writeImage(self, fileName=None):
if fileName is None:
fileName = QtGui.QFileDialog.getSaveFileName()
if isinstance(fileName, tuple):
raise Exception("Not implemented yet..")
fileName = str(fileName)
PlotItem.lastFileDir = os.path.dirname(fileName)
self.png = QtGui.QImage(int(self.size().width()), int(self.size().height()), QtGui.QImage.Format_ARGB32)
@ -997,7 +1132,8 @@ class PlotItem(QtGui.QGraphicsWidget):
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)
#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]
@ -1012,7 +1148,8 @@ class PlotItem(QtGui.QGraphicsWidget):
self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
self.fileDialog.show()
QtCore.QObject.connect(self.fileDialog, QtCore.SIGNAL('fileSelected(const QString)'), self.writeImage)
#QtCore.QObject.connect(self.fileDialog, QtCore.SIGNAL('fileSelected(const QString)'), self.writeImage)
self.fileDialog.fileSelected.connect(self.writeImage)
def saveCsvClicked(self):
self.fileDialog = QtGui.QFileDialog()
@ -1023,13 +1160,17 @@ class PlotItem(QtGui.QGraphicsWidget):
self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
self.fileDialog.show()
QtCore.QObject.connect(self.fileDialog, QtCore.SIGNAL('fileSelected(const QString)'), self.writeCsv)
#QtCore.QObject.connect(self.fileDialog, QtCore.SIGNAL('fileSelected(const QString)'), self.writeCsv)
self.fileDialog.fileSelected.connect(self.writeCsv)
#def imgFileSelected(self, fileName):
##PlotWidget.lastFileDir = os.path.split(fileName)[0]
#self.writeImage(str(fileName))
class PlotWidgetManager(QtCore.QObject):
sigWidgetListChanged = QtCore.Signal(object)
"""Used for managing communication between PlotWidgets"""
def __init__(self):
QtCore.QObject.__init__(self)
@ -1037,12 +1178,14 @@ class PlotWidgetManager(QtCore.QObject):
def addWidget(self, w, name):
self.widgets[name] = w
self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys())
#self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys())
self.sigWidgetListChanged.emit(self.widgets.keys())
def removeWidget(self, name):
if name in self.widgets:
del self.widgets[name]
self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys())
#self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys())
self.sigWidgetListChanged.emit(self.widgets.keys())
def listWidgets(self):
@ -1055,21 +1198,29 @@ class PlotWidgetManager(QtCore.QObject):
return self.widgets[name]
def linkX(self, p1, p2):
QtCore.QObject.connect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged)
QtCore.QObject.connect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged)
#QtCore.QObject.connect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged)
p1.sigXRangeChanged.connect(p2.linkXChanged)
#QtCore.QObject.connect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged)
p2.sigXRangeChanged.connect(p1.linkXChanged)
p1.linkXChanged(p2)
#p2.setManualXScale()
def unlinkX(self, p1, p2):
QtCore.QObject.disconnect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged)
QtCore.QObject.disconnect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged)
#QtCore.QObject.disconnect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged)
p1.sigXRangeChanged.disconnect(p2.linkXChanged)
#QtCore.QObject.disconnect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged)
p2.sigXRangeChanged.disconnect(p1.linkXChanged)
def linkY(self, p1, p2):
QtCore.QObject.connect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged)
QtCore.QObject.connect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged)
#QtCore.QObject.connect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged)
p1.sigYRangeChanged.connect(p2.linkYChanged)
#QtCore.QObject.connect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged)
p2.sigYRangeChanged.connect(p1.linkYChanged)
p1.linkYChanged(p2)
#p2.setManualYScale()
def unlinkY(self, p1, p2):
QtCore.QObject.disconnect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged)
QtCore.QObject.disconnect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged)
#QtCore.QObject.disconnect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged)
p1.sigYRangeChanged.disconnect(p2.linkYChanged)
#QtCore.QObject.disconnect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged)
p2.sigYRangeChanged.disconnect(p1.linkYChanged)

View File

@ -10,6 +10,9 @@ from PlotItem import *
import exceptions
class PlotWidget(GraphicsView):
sigRangeChanged = QtCore.Signal(object, object)
"""Widget implementing a graphicsView with a single PlotItem inside."""
def __init__(self, parent=None, **kargs):
GraphicsView.__init__(self, parent)
@ -20,16 +23,22 @@ class PlotWidget(GraphicsView):
## Explicitly wrap methods from plotItem
for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setXRange', 'setYRange']:
setattr(self, m, getattr(self.plotItem, m))
QtCore.QObject.connect(self.plotItem, QtCore.SIGNAL('viewChanged'), self.viewChanged)
#QtCore.QObject.connect(self.plotItem, QtCore.SIGNAL('viewChanged'), self.viewChanged)
self.plotItem.sigRangeChanged.connect(self.viewRangeChanged)
#def __dtor__(self):
##print "Called plotWidget sip destructor"
#self.quit()
def quit(self):
self.plotItem.clear()
self.scene().clear()
#def quit(self):
def close(self):
self.plotItem.close()
self.plotItem = None
#self.scene().clear()
#self.mPlotItem.close()
self.setParent(None)
def __getattr__(self, attr): ## implicitly wrap methods from plotItem
if hasattr(self.plotItem, attr):
@ -38,8 +47,9 @@ class PlotWidget(GraphicsView):
return m
raise exceptions.NameError(attr)
def viewChanged(self, *args):
self.emit(QtCore.SIGNAL('viewChanged'), *args)
def viewRangeChanged(self, view, range):
#self.emit(QtCore.SIGNAL('viewChanged'), *args)
self.sigRangeChanged.emit(self, range)
def widgetGroupInterface(self):
return (None, PlotWidget.saveState, PlotWidget.restoreState)

View File

@ -34,6 +34,9 @@ class Point(QtCore.QPointF):
return
QtCore.QPointF.__init__(self, *args)
def __len__(self):
return 2
def __reduce__(self):
return (Point, (self.x(), self.y()))

View File

@ -2,14 +2,6 @@
from PyQt4 import QtCore
from ptime import time
def proxyConnect(source, signal, slot, delay=0.3):
"""Connect a signal to a slot with delay. Returns the SignalProxy
object that was created. Be sure to store this object so it is not
garbage-collected immediately."""
sp = SignalProxy(source, signal, delay)
sp.connect(sp, signal, slot)
return sp
class SignalProxy(QtCore.QObject):
"""Object which collects rapid-fire signals and condenses them
into a single signal. Used, for example, to prevent a SpinBox
@ -17,53 +9,70 @@ class SignalProxy(QtCore.QObject):
over it."""
def __init__(self, source, signal, delay=0.3):
"""Initialization arguments:
source - Any QObject that will emit signal, or None if signal is new style
signal - Output of QtCore.SIGNAL(...), or obj.signal for new style
delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s)"""
QtCore.QObject.__init__(self)
source.connect(source, signal, self.signal)
self.delay = delay
self.waitUntil = 0
self.args = None
self.timers = 0
if source is None:
signal.connect(self.signalReceived)
self.signal = QtCore.SIGNAL('signal')
else:
source.connect(source, signal, self.signalReceived)
self.signal = signal
self.delay = delay
self.args = None
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.flush)
self.block = False
def setDelay(self, delay):
self.delay = delay
def signalReceived(self, *args):
"""Received signal. Cancel previous timer and store args to be forwarded later."""
if self.block:
return
self.args = args
self.timer.stop()
self.timer.start((self.delay*1000)+1)
def flush(self):
"""If there is a signal queued up, send it now."""
if self.args is None or self.block:
return False
if self.block:
return
self.emit(self.signal, *self.args)
self.args = None
return True
def signal(self, *args):
"""Received signal, queue to be forwarded later."""
if self.block:
return
self.waitUntil = time() + self.delay
self.args = args
self.timers += 1
QtCore.QTimer.singleShot((self.delay*1000)+1, self.tryEmit)
def tryEmit(self):
"""Emit signal if it has been long enougn since receiving the last signal."""
if self.args is None or self.block:
return False
self.timers -= 1
t = time()
if t >= self.waitUntil:
return self.flush()
else:
if self.timers == 0:
self.timers += 1
QtCore.QTimer.singleShot((self.waitUntil - t) * 1000, self.tryEmit)
return True
def disconnect(self):
self.block = True
def proxyConnect(source, signal, slot, delay=0.3):
"""Connect a signal to a slot with delay. Returns the SignalProxy
object that was created. Be sure to store this object so it is not
garbage-collected immediately."""
sp = SignalProxy(source, signal, delay)
if source is None:
sp.connect(sp, QtCore.SIGNAL('signal'), slot)
else:
sp.connect(sp, signal, slot)
return sp
if __name__ == '__main__':
from PyQt4 import QtGui
app = QtGui.QApplication([])
win = QtGui.QMainWindow()
spin = QtGui.QSpinBox()
win.setCentralWidget(spin)
win.show()
def fn(*args):
print "Got signal:", args
proxy = proxyConnect(spin, QtCore.SIGNAL('valueChanged(int)'), fn)

View File

@ -3,17 +3,16 @@
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from pyqtgraph.GraphicsView import *
from pyqtgraph.graphicsItems import *
from numpy import random
from PyQt4 import QtCore, QtGui
from scipy.ndimage import *
import numpy as np
import pyqtgraph as pg
app = QtGui.QApplication([])
## Create window with GraphicsView widget
win = QtGui.QMainWindow()
view = GraphicsView()
view = pg.GraphicsView()
#view.useOpenGL(True)
win.setCentralWidget(view)
win.show()
@ -25,26 +24,29 @@ view.enableMouse()
view.setAspectLocked(True)
## Create image item
img = ImageItem()
img = pg.ImageItem()
view.scene().addItem(img)
## Set initial view bounds
view.setRange(QtCore.QRectF(0, 0, 200, 200))
## Create random image
data = np.random.normal(size=(50, 200, 200))
i = 0
def updateData():
global img
## Create random image
data = random.random((200, 200))
global img, data, i
## Display the data
img.updateImage(data)
img.updateImage(data[i])
i = (i+1) % data.shape[0]
# update image data every 20ms (or so)
t = QtCore.QTimer()
QtCore.QObject.connect(t, QtCore.SIGNAL('timeout()'), updateData)
t.timeout.connect(updateData)
t.start(20)
app.exec_()
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -3,24 +3,25 @@
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from pyqtgraph.ImageView import *
from numpy import random, linspace
import numpy as np
import scipy
from PyQt4 import QtCore, QtGui
from scipy.ndimage import *
import pyqtgraph as pg
app = QtGui.QApplication([])
## Create window with ImageView widget
win = QtGui.QMainWindow()
imv = ImageView()
imv = pg.ImageView()
win.setCentralWidget(imv)
win.show()
## Create random 3D data set
img = gaussian_filter(random.normal(size=(200, 200)), (5, 5)) * 20 + 100
img = img[newaxis,:,:]
decay = exp(-linspace(0,0.3,100))[:,newaxis,newaxis]
data = random.normal(size=(100, 200, 200))
## Create random 3D data set with noisy signals
img = scipy.ndimage.gaussian_filter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
img = img[np.newaxis,:,:]
decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis]
data = np.random.normal(size=(100, 200, 200))
data += img * decay
#for i in range(data.shape[0]):
@ -28,16 +29,18 @@ data += img * decay
data += 2
## Add time-varying signal
sig = zeros(data.shape[0])
sig[30:] += exp(-linspace(1,10, 70))
sig[40:] += exp(-linspace(1,10, 60))
sig[70:] += exp(-linspace(1,10, 30))
sig = np.zeros(data.shape[0])
sig[30:] += np.exp(-np.linspace(1,10, 70))
sig[40:] += np.exp(-np.linspace(1,10, 60))
sig[70:] += np.exp(-np.linspace(1,10, 30))
sig = sig[:,newaxis,newaxis] * 3
sig = sig[:,np.newaxis,np.newaxis] * 3
data[:,50:60,50:60] += sig
## Display the data
imv.setImage(data, xvals=linspace(1., 3., data.shape[0]))
imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0]))
app.exec_()
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -4,14 +4,16 @@
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from scipy import random
from numpy import linspace
from PyQt4 import QtGui, QtCore
from pyqtgraph.MultiPlotWidget import *
import pyqtgraph as pg
from pyqtgraph.MultiPlotWidget import MultiPlotWidget
try:
from metaarray import *
except:
print "MultiPlot is only used with MetaArray for now (and you do not have the metaarray module)"
print "MultiPlot is only used with MetaArray for now (and you do not have the metaarray package)"
exit()
app = QtGui.QApplication([])
@ -23,4 +25,7 @@ mw.show()
ma = MetaArray(random.random((3, 1000)), info=[{'name': 'Signal', 'cols': [{'name': 'Col1'}, {'name': 'Col2'}, {'name': 'Col3'}]}, {'name': 'Time', 'vals': linspace(0., 1., 1000)}])
pw.plot(ma)
app.exec_()
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -4,11 +4,10 @@
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from scipy import random
from PyQt4 import QtGui, QtCore
from pyqtgraph.PlotWidget import *
from pyqtgraph.graphicsItems import *
from PyQt4 import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
app = QtGui.QApplication([])
mw = QtGui.QMainWindow()
@ -17,77 +16,56 @@ mw.setCentralWidget(cw)
l = QtGui.QVBoxLayout()
cw.setLayout(l)
pw = PlotWidget()
pw = pg.PlotWidget(name='Plot1') ## giving the plots names allows us to link their axes together
l.addWidget(pw)
pw2 = PlotWidget()
pw2 = pg.PlotWidget(name='Plot2')
l.addWidget(pw2)
pw3 = PlotWidget()
pw3 = pg.PlotWidget()
l.addWidget(pw3)
pw.registerPlot('Plot1')
pw2.registerPlot('Plot2')
mw.show()
#p1 = PlotCurveItem()
#pw.addItem(p1)
## Create an empty plot curve to be filled later, set its pen
p1 = pw.plot()
p1.setPen((200,200,100))
## Add in some extra graphics
rect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, 0, 1, 1))
rect.setPen(QtGui.QPen(QtGui.QColor(100, 200, 100)))
pw.addItem(rect)
#pen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(255, 255, 255, 50)), 5)
#pen.setCosmetic(True)
#pen.setJoinStyle(QtCore.Qt.MiterJoin)
#p1.setShadowPen(pen)
p1.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255)))
#l1 = QtGui.QGraphicsLineItem(0, 2, 2, 3)
#l1.setPen(QtGui.QPen(QtGui.QColor(255,0,0)))
#l2 = InfiniteLine(pw2, 1.5, 90, movable=True)
#
#lr1 = LinearRegionItem(pw2, 'vertical', [1.1, 1.3])
#pw2.addItem(lr1)
#lr2 = LinearRegionItem(pw2, 'horizontal', [50, 100])
#pw2.addItem(lr2)
#l3 = InfiniteLine(pw, [1.5, 1.5], 45)
#pw.addItem(l1)
#pw2.addItem(l2)
#pw.addItem(l3)
pw3.plot(array([100000]*100))
mw.show()
def rand(n):
data = random.random(n)
data = np.random.random(n)
data[int(n*0.1):int(n*0.13)] += .5
data[int(n*0.18)] += 2
data[int(n*0.1):int(n*0.13)] *= 5
data[int(n*0.18)] *= 20
return data, arange(n, n+len(data)) / float(n)
data *= 1e-12
return data, np.arange(n, n+len(data)) / float(n)
def updateData():
yd, xd = rand(10000)
p1.updateData(yd, x=xd)
yd, xd = rand(10000)
updateData()
pw.autoRange()
## Start a timer to rapidly update the plot in pw
t = QtCore.QTimer()
QtCore.QObject.connect(t, QtCore.SIGNAL('timeout()'), updateData)
t.timeout.connect(updateData)
t.start(50)
## Multiple parameterized plots--we can autogenerate averages for these.
for i in range(0, 5):
for j in range(0, 3):
yd, xd = rand(10000)
pw2.plot(y=yd*(j+1), x=xd, params={'iter': i, 'val': j})
#app.exec_()
## Test large numbers
curve = pw3.plot(np.random.normal(size=100)*1e6)
curve.setPen('w') ## white pen
curve.setShadowPen(pg.mkPen((70,70,30), width=6, cosmetic=True))
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -4,32 +4,26 @@
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from numpy import random
from scipy import zeros, ones
from pyqtgraph.graphicsWindows import *
from pyqtgraph.graphicsItems import *
from pyqtgraph.widgets import *
from pyqtgraph.PlotWidget import *
from pyqtgraph.functions import mkPen
from PyQt4 import QtCore, QtGui
import numpy as np
import pyqtgraph as pg
## create GUI
app = QtGui.QApplication([])
class Win(QtGui.QMainWindow):
pass
w = Win()
v = GraphicsView(useOpenGL=False)
v.invertY(True)
w = QtGui.QMainWindow()
v = pg.GraphicsView()
v.invertY(True) ## Images usually have their Y-axis pointing downward
v.setAspectLocked(True)
v.enableMouse(True)
v.autoPixelScale = False
w.setCentralWidget(v)
s = v.scene()
v.setRange(QtCore.QRect(-2, -2, 220, 220))
w.show()
arr = ones((100, 100), dtype=float)
## Create image to display
arr = np.ones((100, 100), dtype=float)
arr[45:55, 45:55] = 0
arr[25, :] = 5
arr[:, 25] = 5
@ -38,21 +32,23 @@ arr[:, 75] = 5
arr[50, :] = 10
arr[:, 50] = 10
im1 = ImageItem(arr)
im2 = ImageItem(arr)
## Create image items, add to scene and set position
im1 = pg.ImageItem(arr)
im2 = pg.ImageItem(arr)
s.addItem(im1)
s.addItem(im2)
im2.moveBy(110, 20)
im3 = ImageItem()
im3 = pg.ImageItem()
s.addItem(im3)
im3.moveBy(0, 130)
im3.setZValue(10)
im4 = ImageItem()
im4 = pg.ImageItem()
s.addItem(im4)
im4.moveBy(110, 130)
im4.setZValue(10)
pi1 = PlotItem()
## create the plot
pi1 = pg.PlotItem()
s.addItem(pi1)
pi1.scale(0.5, 0.5)
pi1.setGeometry(0, 170, 300, 100)
@ -76,34 +72,39 @@ def updateRoiPlot(roi, data=None):
if data is not None:
roi.curve.updateData(data.mean(axis=1))
#def updatePlot(roi)
## Create a variety of different ROI types
rois = []
rois.append(TestROI([0, 0], [20, 20], maxBounds=QtCore.QRectF(-10, -10, 230, 140), pen=mkPen(0)))
rois.append(LineROI([0, 0], [20, 20], width=5, pen=mkPen(1)))
rois.append(MultiLineROI([[0, 50], [50, 60], [60, 30]], width=5, pen=mkPen(2)))
rois.append(EllipseROI([110, 10], [30, 20], pen=mkPen(3)))
rois.append(CircleROI([110, 50], [20, 20], pen=mkPen(4)))
rois.append(PolygonROI([[2,0], [2.1,0], [2,.1]], pen=mkPen(5)))
rois.append(pg.widgets.TestROI([0, 0], [20, 20], maxBounds=QtCore.QRectF(-10, -10, 230, 140), pen=(0,9)))
rois.append(pg.widgets.LineROI([0, 0], [20, 20], width=5, pen=(1,9)))
rois.append(pg.widgets.MultiLineROI([[0, 50], [50, 60], [60, 30]], width=5, pen=(2,9)))
rois.append(pg.widgets.EllipseROI([110, 10], [30, 20], pen=(3,9)))
rois.append(pg.widgets.CircleROI([110, 50], [20, 20], pen=(4,9)))
rois.append(pg.widgets.PolygonROI([[2,0], [2.1,0], [2,.1]], pen=(5,9)))
#rois.append(SpiralROI([20,30], [1,1], pen=mkPen(0)))
## Add each ROI to the scene and link its data to a plot curve with the same color
for r in rois:
s.addItem(r)
c = pi1.plot(pen=r.pen)
r.curve = c
r.connect(r, QtCore.SIGNAL('regionChanged'), updateRoi)
r.sigRegionChanged.connect(updateRoi)
def updateImage():
global im1, arr, lastRoi
r = abs(random.normal(loc=0, scale=(arr.max()-arr.min())*0.1, size=arr.shape))
r = abs(np.random.normal(loc=0, scale=(arr.max()-arr.min())*0.1, size=arr.shape))
im1.updateImage(arr + r)
updateRoi(lastRoi)
for r in rois:
updateRoiPlot(r)
## Rapidly update one of the images with random noise
t = QtCore.QTimer()
t.connect(t, QtCore.SIGNAL('timeout()'), updateImage)
t.timeout.connect(updateImage)
t.start(50)
w.show()
app.exec_()
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -3,18 +3,16 @@
import sys, os
sys.path = [os.path.join(os.path.dirname(__file__), '..', '..')] + sys.path
from pyqtgraph.GraphicsView import *
from pyqtgraph.graphicsItems import *
#from numpy import random
from PyQt4 import QtCore, QtGui
from scipy.ndimage import *
import numpy as np
import pyqtgraph as pg
app = QtGui.QApplication([])
## Create window with GraphicsView widget
win = QtGui.QMainWindow()
view = GraphicsView()
view = pg.GraphicsView()
#view.useOpenGL(True)
win.setCentralWidget(view)
win.show()
@ -26,7 +24,7 @@ view.enableMouse()
view.setAspectLocked(True)
## Create image item
img = ImageItem(np.zeros((200,200)))
img = pg.ImageItem(np.zeros((200,200)))
view.scene().addItem(img)
## Set initial view bounds
@ -35,4 +33,6 @@ view.setRange(QtCore.QRectF(0, 0, 200, 200))
img.setDrawKernel(1)
img.setLevels(10,0)
#app.exec_()
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -1,32 +1,26 @@
# -*- coding: utf-8 -*-
import sys, os
## Add path to library (just for examples; you do not need this)
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from PyQt4 import QtGui, QtCore
from pyqtgraph.PlotWidget import *
from pyqtgraph.graphicsItems import *
import pyqtgraph as pg
import numpy as np
app = QtGui.QApplication([])
mw = QtGui.QMainWindow()
cw = PlotWidget()
cw = pg.PlotWidget()
mw.setCentralWidget(cw)
mw.show()
#s1 = SpotItem(5, pxMode=True, brush=QtGui.QBrush(QtGui.QColor(0, 0, 200)), pen=QtGui.QPen(QtGui.QColor(100,100,100)))
#s1.setPos(1, 0)
#s2 = SpotItem(.1, pxMode=False, brush=QtGui.QBrush(QtGui.QColor(0, 200, 0)), pen=QtGui.QPen(QtGui.QColor(100,100,100)))
#s2.setPos(0, 1)
#cw.addItem(s1)
#cw.addItem(s2)
import numpy as np
s1 = ScatterPlotItem(size=10, pen=QtGui.QPen(QtCore.Qt.NoPen), brush=QtGui.QBrush(QtGui.QColor(255, 255, 255, 20)))
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)
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -4,10 +4,12 @@
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from scipy import random
## This example uses a ViewBox to create a PlotWidget-like interface
#from scipy import random
import numpy as np
from PyQt4 import QtGui, QtCore
from pyqtgraph.GraphicsView import *
from pyqtgraph.graphicsItems import *
import pyqtgraph as pg
app = QtGui.QApplication([])
mw = QtGui.QMainWindow()
@ -19,15 +21,15 @@ mw.show()
mw.resize(800, 600)
gv = GraphicsView(cw)
gv.enableMouse(False)
gv = pg.GraphicsView(cw)
gv.enableMouse(False) ## Mouse interaction will be handled by the ViewBox
l = QtGui.QGraphicsGridLayout()
l.setHorizontalSpacing(0)
l.setVerticalSpacing(0)
vb = ViewBox()
p1 = PlotCurveItem()
vb = pg.ViewBox()
p1 = pg.PlotCurveItem()
vb.addItem(p1)
vl.addWidget(gv)
@ -61,21 +63,21 @@ l.addItem(vb, 0, 1)
gv.centralWidget.setLayout(l)
xScale = ScaleItem(orientation='bottom', linkView=vb)
xScale = pg.ScaleItem(orientation='bottom', linkView=vb)
l.addItem(xScale, 1, 1)
yScale = ScaleItem(orientation='left', linkView=vb)
yScale = pg.ScaleItem(orientation='left', linkView=vb)
l.addItem(yScale, 0, 0)
xScale.setLabel(text=u"<span style='color: #ff0000; font-weight: bold'>X</span> <i>Axis</i>", units="s")
yScale.setLabel('Y Axis', units='V')
def rand(n):
data = random.random(n)
data = np.random.random(n)
data[int(n*0.1):int(n*0.13)] += .5
data[int(n*0.18)] += 2
data[int(n*0.1):int(n*0.13)] *= 5
data[int(n*0.18)] *= 20
return data, arange(n, n+len(data)) / float(n)
return data, np.arange(n, n+len(data)) / float(n)
def updateData():
@ -87,7 +89,9 @@ updateData()
vb.autoRange()
t = QtCore.QTimer()
QtCore.QObject.connect(t, QtCore.SIGNAL('timeout()'), updateData)
t.timeout.connect(updateData)
t.start(50)
#app.exec_()
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -18,7 +18,8 @@ colorAbbrev = {
from PyQt4 import QtGui
from numpy import clip, floor, log
import numpy as np
import scipy.ndimage
## Copied from acq4/lib/util/functions
SI_PREFIXES = u'yzafpnµm kMGTPEZY'
@ -28,7 +29,7 @@ def siScale(x, minVal=1e-25):
m = 0
x = 0
else:
m = int(clip(floor(log(abs(x))/log(1000)), -9.0, 9.0))
m = int(np.clip(np.floor(np.log(abs(x))/np.log(1000)), -9.0, 9.0))
if m == 0:
pref = ''
elif m < -8 or m > 8:
@ -145,13 +146,13 @@ def mkColor(*args):
return QtGui.QColor(r, g, b, a)
def colorTuple(c):
return (c.red(), c.blue(), c.green(), c.alpha())
return (c.red(), c.green(), c.blue(), c.alpha())
def colorStr(c):
"""Generate a hex string code from a QColor"""
return ('%02x'*4) % colorTuple(c)
def intColor(index, hues=9, values=3, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255):
def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255, **kargs):
"""Creates a QColor from a single index. Useful for stepping through a predefined list of colors.
- The argument "index" determines which color from the set will be returned
- All other arguments determine what the set of predefined colors will be
@ -163,9 +164,90 @@ def intColor(index, hues=9, values=3, maxValue=255, minValue=150, maxHue=360, mi
ind = int(index) % (hues * values)
indh = ind % hues
indv = ind / hues
if values > 1:
v = minValue + indv * ((maxValue-minValue) / (values-1))
else:
v = maxValue
h = minHue + (indh * (maxHue-minHue)) / hues
c = QtGui.QColor()
c.setHsv(h, sat, v)
c.setAlpha(alpha)
return c
def affineSlice(data, shape, origin, vectors, axes, **kargs):
"""Take an arbitrary slice through an array.
Parameters:
data: the original dataset
shape: the shape of the slice to take (Note the return value may have more dimensions than len(shape))
origin: the location in the original dataset that will become the origin in the sliced data.
vectors: list of unit vectors which point in the direction of the slice axes
each vector must be the same length as axes
If the vectors are not unit length, the result will be scaled.
If the vectors are not orthogonal, the result will be sheared.
axes: the axes in the original dataset which correspond to the slice vectors
Example: start with a 4D data set, take a diagonal-planar slice out of the last 3 axes
- data = array with dims (time, x, y, z) = (100, 40, 40, 40)
- The plane to pull out is perpendicular to the vector (x,y,z) = (1,1,1)
- The origin of the slice will be at (x,y,z) = (40, 0, 0)
- The we will slice a 20x20 plane from each timepoint, giving a final shape (100, 20, 20)
affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3))
Note the following:
len(shape) == len(vectors)
len(origin) == len(axes) == len(vectors[0])
"""
# sanity check
if len(shape) != len(vectors):
raise Exception("shape and vectors must have same length.")
if len(origin) != len(axes):
raise Exception("origin and axes must have same length.")
for v in vectors:
if len(v) != len(axes):
raise Exception("each vector must be same length as axes.")
shape = (np.ceil(shape[0]), np.ceil(shape[1]))
## transpose data so slice axes come first
trAx = range(data.ndim)
for x in axes:
trAx.remove(x)
tr1 = tuple(axes) + tuple(trAx)
data = data.transpose(tr1)
#print "tr1:", tr1
## dims are now [(slice axes), (other axes)]
## make sure vectors are arrays
vectors = np.array(vectors)
origin = np.array(origin)
origin.shape = (len(axes),) + (1,)*len(shape)
## Build array of sample locations.
grid = np.mgrid[tuple([slice(0,x) for x in shape])] ## mesh grid of indexes
#print shape, grid.shape
x = (grid[np.newaxis,...] * vectors.transpose()[(Ellipsis,) + (np.newaxis,)*len(shape)]).sum(axis=1) ## magic
x += origin
#print "X values:"
#print x
## iterate manually over unused axes since map_coordinates won't do it for us
extraShape = data.shape[len(axes):]
output = np.empty(tuple(shape) + extraShape, dtype=data.dtype)
for inds in np.ndindex(*extraShape):
ind = (Ellipsis,) + inds
#print data[ind].shape, x.shape, output[ind].shape, output.shape
output[ind] = scipy.ndimage.map_coordinates(data[ind], x, **kargs)
tr = range(output.ndim)
trb = []
for i in range(min(axes)):
ind = tr1.index(i) + (len(shape)-len(axes))
tr.remove(ind)
trb.append(ind)
tr2 = tuple(trb+tr)
## Untranspose array before returning
return output.transpose(tr2)

View File

@ -11,7 +11,7 @@ Provides ImageItem, PlotCurveItem, and ViewBox, amongst others.
from PyQt4 import QtGui, QtCore
if not hasattr(QtCore, 'Signal'):
QtCore.Signal = QtCore.pyqtSignal
from ObjectWorkaround import *
#from ObjectWorkaround import *
#tryWorkaround(QtCore, QtGui)
#from numpy import *
import numpy as np
@ -28,8 +28,16 @@ from Point import *
from functions import *
import types, sys, struct
import weakref
#import debug
#from debug import *
## QGraphicsObject didn't appear until 4.6; this is for compatibility with 4.5
if not hasattr(QtGui, 'QGraphicsObject'):
class QGraphicsObject(QtGui.QGraphicsWidget):
def shape(self):
return QtGui.QGraphicsItem.shape(self)
QtGui.QGraphicsObject = QGraphicsObject
## Should probably just use QGraphicsGroupItem and instruct it to pass events on to children..
class ItemGroup(QtGui.QGraphicsItem):
@ -68,12 +76,12 @@ class ItemGroup(QtGui.QGraphicsItem):
class GraphicsObject(QGraphicsObject):
class GraphicsObject(QtGui.QGraphicsObject):
"""Extends QGraphicsObject with a few important functions.
(Most of these assume that the object is in a scene with a single view)"""
def __init__(self, *args):
QGraphicsObject.__init__(self, *args)
QtGui.QGraphicsObject.__init__(self, *args)
self._view = None
def getViewWidget(self):
@ -174,14 +182,19 @@ class GraphicsObject(QGraphicsObject):
class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
class ImageItem(QtGui.QGraphicsObject):
sigImageChanged = QtCore.Signal()
if 'linux' not in sys.platform: ## disable weave optimization on linux--broken there.
useWeave = True
else:
useWeave = False
def __init__(self, image=None, copy=True, parent=None, border=None, *args):
QObjectWorkaround.__init__(self)
#QObjectWorkaround.__init__(self)
QtGui.QGraphicsObject.__init__(self)
#self.pixmapItem = QtGui.QGraphicsPixmapItem(self)
self.qimage = QtGui.QImage()
self.pixmap = None
#self.useWeave = True
@ -195,7 +208,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
border = mkPen(border)
self.border = border
QtGui.QGraphicsPixmapItem.__init__(self, parent, *args)
#QtGui.QGraphicsPixmapItem.__init__(self, parent, *args)
#self.pixmapItem = QtGui.QGraphicsPixmapItem(self)
if image is not None:
self.updateImage(image, copy, autoRange=True)
@ -219,6 +232,11 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
return None
return self.pixmap.height()
def boundingRect(self):
if self.pixmap is None:
return QtCore.QRectF(0., 0., 0., 0.)
return QtCore.QRectF(0., 0., float(self.width()), float(self.height()))
def setClipLevel(self, level=None):
self.clipLevel = level
@ -252,6 +270,8 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
return
else:
gotNewData = True
if self.image is None or image.shape != self.image.shape:
self.prepareGeometryChange()
if copy:
self.image = image.view(np.ndarray).copy()
else:
@ -323,7 +343,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
im2 = im.transpose(axh['y'], axh['x'], axh['c'])
for i in range(0, im.shape[axh['c']]):
im1[..., i] = im2[..., i]
im1[..., 2-i] = im2[..., i] ## for some reason, the colors line up as BGR in the final image.
for i in range(im.shape[axh['c']], 3):
im1[..., i] = 0
@ -345,14 +365,16 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
im1[..., 2][mask] = 255
#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 :(
qimage = QtGui.QImage(self.ims, im1.shape[1], im1.shape[0], QtGui.QImage.Format_ARGB32)
qimage = QtGui.QImage(buffer(self.ims), im1.shape[1], im1.shape[0], QtGui.QImage.Format_ARGB32)
self.pixmap = QtGui.QPixmap.fromImage(qimage)
##del self.ims
self.setPixmap(self.pixmap)
#self.pixmapItem.setPixmap(self.pixmap)
self.update()
if gotNewData:
self.emit(QtCore.SIGNAL('imageChanged'))
#self.emit(QtCore.SIGNAL('imageChanged'))
self.sigImageChanged.emit()
def getPixmap(self):
return self.pixmap.copy()
@ -397,8 +419,15 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
p.setPen(self.border)
p.drawRect(self.boundingRect())
def pixelSize(self):
"""return size of a single pixel in the image"""
br = self.sceneBoundingRect()
return br.width()/self.pixmap.width(), br.height()/self.pixmap.height()
class PlotCurveItem(GraphicsObject):
sigPlotChanged = QtCore.Signal(object)
"""Class representing a single plot curve."""
sigClicked = QtCore.Signal(object)
@ -565,6 +594,7 @@ class PlotCurveItem(GraphicsObject):
self.updateData(y, x, copy)
def updateData(self, data, x=None, copy=False):
#prof = debug.Profiler('PlotCurveItem.updateData', disabled=True)
if isinstance(data, list):
data = np.array(data)
if isinstance(x, list):
@ -591,7 +621,7 @@ class PlotCurveItem(GraphicsObject):
x = data[tuple(ind)]
elif data.ndim == 1:
y = data
#prof.mark("data checks")
self.prepareGeometryChange()
if copy:
self.yData = y.copy()
@ -602,6 +632,7 @@ class PlotCurveItem(GraphicsObject):
self.xData = x.copy()
else:
self.xData = x
#prof.mark('copy')
if x is None:
self.xData = np.arange(0, self.yData.shape[0])
@ -610,10 +641,15 @@ class PlotCurveItem(GraphicsObject):
raise Exception("X and Y arrays must be the same shape--got %s and %s." % (str(x.shape), str(y.shape)))
self.path = None
#self.specPath = None
self.xDisp = self.yDisp = None
#prof.mark('set')
self.update()
self.emit(QtCore.SIGNAL('plotChanged'), self)
#prof.mark('update')
#self.emit(QtCore.SIGNAL('plotChanged'), self)
self.sigPlotChanged.emit(self)
#prof.mark('emit')
#prof.finish()
def generatePath(self, x, y):
path = QtGui.QPainterPath()
@ -634,25 +670,32 @@ class PlotCurveItem(GraphicsObject):
## 0(i4)
##
## 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
arr.data[12:20] = struct.pack('>ii', n, 0)
# Fill array with vertex values
arr[1:-1]['x'] = x
arr[1:-1]['y'] = y
arr[1:-1]['c'] = 1
#prof.mark('fill array')
# write last 0
lastInd = 20*(n+1)
arr.data[lastInd:lastInd+4] = struct.pack('>i', 0)
# 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')
ds = QtCore.QDataStream(buf)
#prof.mark('create dataStream')
ds >> path
#prof.mark('load path')
#prof.finish()
return path
def boundingRect(self):
@ -677,6 +720,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)
if self.xData is None:
return
#if self.opts['spectrumMode']:
@ -688,6 +732,7 @@ class PlotCurveItem(GraphicsObject):
if self.path is None:
self.path = self.generatePath(*self.getData())
path = self.path
#prof.mark('generate path')
if self.shadow is not None:
sp = QtGui.QPen(self.shadow)
@ -709,7 +754,9 @@ class PlotCurveItem(GraphicsObject):
p.drawPath(path)
p.setPen(cp)
p.drawPath(path)
#prof.mark('drawPath')
#prof.finish()
#p.setPen(QtGui.QPen(QtGui.QColor(255,0,0)))
#p.drawRect(self.boundingRect())
@ -742,7 +789,7 @@ class PlotCurveItem(GraphicsObject):
self.sigClicked.emit(self)
class CurvePoint(QtGui.QGraphicsItem, QObjectWorkaround):
class CurvePoint(QtGui.QGraphicsObject):
"""A GraphicsItem that sets its location to a point on a PlotCurveItem.
The position along the curve is a property, and thus can be easily animated."""
@ -750,17 +797,16 @@ class CurvePoint(QtGui.QGraphicsItem, QObjectWorkaround):
"""Position can be set either as an index referring to the sample number or
the position 0.0 - 1.0"""
QtGui.QGraphicsItem.__init__(self)
QObjectWorkaround.__init__(self)
self.curve = None
QtGui.QGraphicsObject.__init__(self)
#QObjectWorkaround.__init__(self)
self.curve = weakref.ref(curve)
self.setParentItem(curve)
self.setProperty('position', 0.0)
self.setProperty('index', 0)
if hasattr(self, 'ItemHasNoContents'):
self.setFlags(self.flags() | self.ItemHasNoContents)
self.curve = curve
self.setParentItem(curve)
if pos is not None:
self.setPos(pos)
else:
@ -773,22 +819,22 @@ class CurvePoint(QtGui.QGraphicsItem, QObjectWorkaround):
self.setProperty('index', index)
def event(self, ev):
if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve is None:
return
if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None:
return False
if ev.propertyName() == 'index':
index = self.property('index').toInt()[0]
elif ev.propertyName() == 'position':
index = None
else:
return
return False
(x, y) = self.curve.getData()
(x, y) = self.curve().getData()
if index is None:
#print 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):
if index != int(index): ## interpolate floating-point values
i1 = int(index)
i2 = clip(i1+1, 0, len(x)-1)
s2 = index-i1
@ -806,6 +852,7 @@ class CurvePoint(QtGui.QGraphicsItem, QObjectWorkaround):
self.resetTransform()
self.rotate(180+ ang * 180 / np.pi)
QtGui.QGraphicsItem.setPos(self, *newPos)
return True
def boundingRect(self):
return QtCore.QRectF()
@ -814,7 +861,7 @@ class CurvePoint(QtGui.QGraphicsItem, QObjectWorkaround):
pass
def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1):
anim = QtCore.QPropertyAnimation(self._qObj_, prop)
anim = QtCore.QPropertyAnimation(self, prop)
anim.setDuration(duration)
anim.setStartValue(start)
anim.setEndValue(end)
@ -939,22 +986,27 @@ class ScatterPlotItem(QtGui.QGraphicsWidget):
for s in spots:
pos = Point(s['pos'])
size = s.get('size', self.size)
if self.pxMode:
psize = 0
else:
psize = size
if xmn is None:
xmn = pos[0]-psize
xmx = pos[0]+psize
ymn = pos[1]-psize
ymx = pos[1]+psize
else:
xmn = min(xmn, pos[0]-psize)
xmx = max(xmx, pos[0]+psize)
ymn = min(ymn, pos[1]-psize)
ymx = max(ymx, pos[1]+psize)
#print pos, xmn, xmx, ymn, ymx
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)
self.spots.append(item)
if xmn is None:
xmn = pos[0]-size
xmx = pos[0]+size
ymn = pos[1]-size
ymx = pos[1]+size
else:
xmn = min(xmn, pos[0]-size)
xmx = max(xmx, pos[0]+size)
ymn = min(ymn, pos[1]-size)
ymx = max(ymx, pos[1]+size)
self.range = [[xmn, xmx], [ymn, ymx]]
@ -967,7 +1019,8 @@ class ScatterPlotItem(QtGui.QGraphicsWidget):
def boundingRect(self):
((xmn, xmx), (ymn, ymx)) = self.range
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)
def paint(self, p, *args):
@ -985,13 +1038,15 @@ class SpotItem(QtGui.QGraphicsWidget):
def __init__(self, size, pxMode, brush, pen, data):
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.pen = pen
self.brush = brush
self.path = QtGui.QPainterPath()
s2 = size/2.
self.path.addEllipse(QtCore.QRectF(-s2, -s2, size, size))
#s2 = size/2.
self.path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))
self.scale(size, size)
self.data = data
def setBrush(self, brush):
@ -1036,6 +1091,7 @@ class SpotItem(QtGui.QGraphicsWidget):
class ROIPlotItem(PlotCurveItem):
"""Plot curve that monitors an ROI and image for changes to automatically replot."""
def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None):
self.roi = roi
self.roiData = data
@ -1043,7 +1099,8 @@ class ROIPlotItem(PlotCurveItem):
self.axes = axes
self.xVals = xVals
PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color)
roi.connect(roi, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent)
#roi.connect(roi, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent)
roi.sigRegionChanged.connect(self.roiChangedEvent)
#self.roiChangedEvent()
def getRoiData(self):
@ -1073,7 +1130,8 @@ class UIGraphicsItem(GraphicsObject):
self._viewRect = self._view().rect()
self._viewTransform = self.viewTransform()
self.setNewBounds()
QtCore.QObject.connect(view, QtCore.SIGNAL('viewChanged'), self.viewChangedEvent)
#QtCore.QObject.connect(view, QtCore.SIGNAL('viewChanged'), self.viewChangedEvent)
view.sigRangeChanged.connect(self.viewRangeChanged)
def viewRect(self):
"""Return the viewport widget rect"""
@ -1110,7 +1168,7 @@ class UIGraphicsItem(GraphicsObject):
self.bounds = self.viewTransform().inverted()[0].mapRect(bounds)
self.prepareGeometryChange()
def viewChangedEvent(self):
def viewRangeChanged(self):
"""Called when the view widget is resized"""
self.boundingRect()
self.update()
@ -1378,23 +1436,31 @@ class ScaleItem(QtGui.QGraphicsWidget):
def linkToView(self, view):
if self.orientation in ['right', 'left']:
signal = QtCore.SIGNAL('yRangeChanged')
if self.linkedView is not None and self.linkedView() is not None:
#view.sigYRangeChanged.disconnect(self.linkedViewChanged)
## should be this instead?
self.linkedView().sigYRangeChanged.disconnect(self.linkedViewChanged)
self.linkedView = weakref.ref(view)
view.sigYRangeChanged.connect(self.linkedViewChanged)
#signal = QtCore.SIGNAL('yRangeChanged')
else:
signal = QtCore.SIGNAL('xRangeChanged')
if self.linkedView is not None and self.linkedView() is not None:
#view.sigYRangeChanged.disconnect(self.linkedViewChanged)
## should be this instead?
self.linkedView().sigXRangeChanged.disconnect(self.linkedViewChanged)
self.linkedView = weakref.ref(view)
view.sigXRangeChanged.connect(self.linkedViewChanged)
#signal = QtCore.SIGNAL('xRangeChanged')
if self.linkedView is not None:
QtCore.QObject.disconnect(view, signal, self.linkedViewChanged)
self.linkedView = view
QtCore.QObject.connect(view, signal, self.linkedViewChanged)
def linkedViewChanged(self, _, newRange):
def linkedViewChanged(self, view, newRange):
self.setRange(*newRange)
def boundingRect(self):
if self.linkedView is None or self.grid is False:
if self.linkedView is None or self.linkedView() is None or self.grid is False:
return self.mapRectFromParent(self.geometry())
else:
return self.mapRectFromParent(self.geometry()) | self.mapRectFromScene(self.linkedView.mapRectToScene(self.linkedView.boundingRect()))
return self.mapRectFromParent(self.geometry()) | self.mapRectFromScene(self.linkedView().mapRectToScene(self.linkedView().boundingRect()))
def paint(self, p, opt, widget):
p.setPen(self.pen)
@ -1402,10 +1468,10 @@ class ScaleItem(QtGui.QGraphicsWidget):
#bounds = self.boundingRect()
bounds = self.mapRectFromParent(self.geometry())
if self.linkedView is None or self.grid is False:
if self.linkedView is None or self.linkedView() is None or self.grid is False:
tbounds = bounds
else:
tbounds = self.mapRectFromScene(self.linkedView.mapRectToScene(self.linkedView.boundingRect()))
tbounds = self.mapRectFromScene(self.linkedView().mapRectToScene(self.linkedView().boundingRect()))
if self.orientation == 'left':
p.drawLine(bounds.topRight(), bounds.bottomRight())
@ -1577,6 +1643,12 @@ class ScaleItem(QtGui.QGraphicsWidget):
class ViewBox(QtGui.QGraphicsWidget):
sigYRangeChanged = QtCore.Signal(object, object)
sigXRangeChanged = QtCore.Signal(object, object)
sigRangeChangedManually = QtCore.Signal(object)
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):
QtGui.QGraphicsWidget.__init__(self, parent)
@ -1745,7 +1817,8 @@ class ViewBox(QtGui.QGraphicsWidget):
mask *= np.array([1, -1])
tr = dif*mask
self.translateBy(tr, viewCoords=True)
self.emit(QtCore.SIGNAL('rangeChangedManually'), self.mouseEnabled)
#self.emit(QtCore.SIGNAL('rangeChangedManually'), self.mouseEnabled)
self.sigRangeChangedManually.emit(self.mouseEnabled)
ev.accept()
elif ev.buttons() & QtCore.Qt.RightButton:
dif = ev.screenPos() - ev.lastScreenPos()
@ -1755,7 +1828,8 @@ class ViewBox(QtGui.QGraphicsWidget):
#print mask, dif, s
center = Point(self.childGroup.transform().inverted()[0].map(ev.buttonDownPos(QtCore.Qt.RightButton)))
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()
else:
ev.ignore()
@ -1802,8 +1876,10 @@ class ViewBox(QtGui.QGraphicsWidget):
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.emit(QtCore.SIGNAL('viewChanged'), self)
#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()
@ -1827,8 +1903,10 @@ class ViewBox(QtGui.QGraphicsWidget):
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.emit(QtCore.SIGNAL('viewChanged'), self)
#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()
@ -1852,6 +1930,11 @@ class ViewBox(QtGui.QGraphicsWidget):
class InfiniteLine(GraphicsObject):
sigDragged = QtCore.Signal(object)
sigPositionChangeFinished = QtCore.Signal(object)
sigPositionChanged = QtCore.Signal(object)
def __init__(self, view, pos=0, angle=90, pen=None, movable=False, bounds=None):
GraphicsObject.__init__(self)
self.bounds = QtCore.QRectF() ## graphicsitem boundary
@ -1877,7 +1960,8 @@ class InfiniteLine(GraphicsObject):
#self.setFlag(self.ItemSendsScenePositionChanges)
#for p in self.getBoundingParents():
#QtCore.QObject.connect(p, QtCore.SIGNAL('viewChanged'), self.updateLine)
QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.updateLine)
#QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.updateLine)
self.view().sigRangeChanged.connect(self.updateLine)
def setMovable(self, m):
self.movable = m
@ -1935,7 +2019,8 @@ class InfiniteLine(GraphicsObject):
if self.p != newPos:
self.p = newPos
self.updateLine()
self.emit(QtCore.SIGNAL('positionChanged'), self)
#self.emit(QtCore.SIGNAL('positionChanged'), self)
self.sigPositionChanged.emit(self)
def getXPos(self):
return self.p[0]
@ -2048,17 +2133,23 @@ class InfiniteLine(GraphicsObject):
def mouseMoveEvent(self, ev):
self.setPos(self.mapToParent(ev.pos()) - self.pressDelta)
self.emit(QtCore.SIGNAL('dragged'), self)
#self.emit(QtCore.SIGNAL('dragged'), self)
self.sigDragged.emit(self)
self.hasMoved = True
def mouseReleaseEvent(self, ev):
if self.hasMoved and ev.button() == QtCore.Qt.LeftButton:
self.hasMoved = False
self.emit(QtCore.SIGNAL('positionChangeFinished'), self)
#self.emit(QtCore.SIGNAL('positionChangeFinished'), self)
self.sigPositionChangeFinished.emit(self)
class LinearRegionItem(GraphicsObject):
sigRegionChangeFinished = QtCore.Signal(object)
sigRegionChanged = QtCore.Signal(object)
"""Used for marking a horizontal or vertical region in plots."""
def __init__(self, view, orientation="vertical", vals=[0,1], brush=None, movable=True, bounds=None):
GraphicsObject.__init__(self)
@ -2080,12 +2171,15 @@ class LinearRegionItem(GraphicsObject):
self.lines = [
InfiniteLine(view, QtCore.QPointF(vals[0], 0), 90, movable=movable, bounds=bounds),
InfiniteLine(view, QtCore.QPointF(vals[1], 0), 90, movable=movable, bounds=bounds)]
QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.updateBounds)
#QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.updateBounds)
self.view().sigRangeChanged.connect(self.updateBounds)
for l in self.lines:
l.setParentItem(self)
l.connect(l, QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished)
l.connect(l, QtCore.SIGNAL('positionChanged'), self.lineMoved)
#l.connect(l, QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished)
l.sigPositionChangeFinished.connect(self.lineMoveFinished)
#l.connect(l, QtCore.SIGNAL('positionChanged'), self.lineMoved)
l.sigPositionChanged.connect(self.lineMoved)
if brush is None:
brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50))
@ -2106,10 +2200,12 @@ class LinearRegionItem(GraphicsObject):
def lineMoved(self):
self.updateBounds()
self.emit(QtCore.SIGNAL('regionChanged'), self)
#self.emit(QtCore.SIGNAL('regionChanged'), self)
self.sigRegionChanged.emit(self)
def lineMoveFinished(self):
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
#self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
self.sigRegionChangeFinished.emit(self)
def updateBounds(self):
@ -2200,11 +2296,13 @@ class VTickGroup(QtGui.QGraphicsPathItem):
if self.view is not None:
if relative:
#QtCore.QObject.connect(self.view, QtCore.SIGNAL('viewChanged'), self.rebuildTicks)
QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.rescale)
#QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.rescale)
self.view().sigRangeChanged.connect(self.rescale)
else:
try:
#QtCore.QObject.disconnect(self.view, QtCore.SIGNAL('viewChanged'), self.rebuildTicks)
QtCore.QObject.disconnect(self.view(), QtCore.SIGNAL('viewChanged'), self.rescale)
#QtCore.QObject.disconnect(self.view(), QtCore.SIGNAL('viewChanged'), self.rescale)
self.view().sigRangeChanged.disconnect(self.rescale)
except:
pass
self.rebuildTicks()
@ -2268,6 +2366,8 @@ class VTickGroup(QtGui.QGraphicsPathItem):
class GridItem(UIGraphicsItem):
"""Class used to make square grids in plots. NOT the grid used for running scanner sequences."""
def __init__(self, view, bounds=None, *args):
UIGraphicsItem.__init__(self, view, bounds)
#QtGui.QGraphicsItem.__init__(self, *args)
@ -2277,9 +2377,9 @@ class GridItem(UIGraphicsItem):
self.picture = None
def viewChangedEvent(self):
def viewRangeChanged(self):
self.picture = None
UIGraphicsItem.viewChangedEvent(self)
UIGraphicsItem.viewRangeChanged(self)
#self.update()
def paint(self, p, opt, widget):
@ -2417,7 +2517,20 @@ class ColorScaleBar(UIGraphicsItem):
self.gradient = g
self.update()
def setIntColorScale(self, minVal, maxVal, *args, **kargs):
colors = [intColor(i, maxVal-minVal, *args, **kargs) for i in range(minVal, maxVal)]
g = QtGui.QLinearGradient()
for i in range(len(colors)):
x = float(i)/len(colors)
g.setColorAt(x, colors[i])
self.setGradient(g)
if 'labels' not in kargs:
self.setLabels({str(minVal/10.): 0, str(maxVal): 1})
else:
self.setLabels({kargs['labels'][0]:0, kargs['labels'][1]:1})
def setLabels(self, l):
"""Defines labels to appear next to the color scale"""
self.labels = l
self.update()
@ -2429,7 +2542,7 @@ class ColorScaleBar(UIGraphicsItem):
labelWidth = 0
labelHeight = 0
for k in self.labels:
b = p.boundingRect(QtCore.QRectF(0, 0, 0, 0), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, k)
b = p.boundingRect(QtCore.QRectF(0, 0, 0, 0), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k))
labelWidth = max(labelWidth, b.width())
labelHeight = max(labelHeight, b.height())
@ -2484,6 +2597,6 @@ class ColorScaleBar(UIGraphicsItem):
lh = labelHeight/unit.height()
for k in self.labels:
y = y1 + self.labels[k] * (y2-y1)
p.drawText(QtCore.QRectF(tx/unit.width(), y/unit.height() - lh/2.0, 1000, lh), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, k)
p.drawText(QtCore.QRectF(tx/unit.width(), y/unit.height() - lh/2.0, 1000, lh), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k))

View File

@ -16,7 +16,8 @@ from numpy.linalg import norm
import scipy.ndimage as ndimage
from Point import *
from math import cos, sin
from ObjectWorkaround import *
import functions as fn
#from ObjectWorkaround import *
def rectStr(r):
return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height())
@ -33,10 +34,15 @@ def rectStr(r):
#return QtCore.QObject.connect(self._qObj_, *args)
class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
class ROI(QtGui.QGraphicsObject):
sigRegionChangeFinished = QtCore.Signal(object)
sigRegionChangeStarted = QtCore.Signal(object)
sigRegionChanged = QtCore.Signal(object)
def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None):
QObjectWorkaround.__init__(self)
QtGui.QGraphicsItem.__init__(self, parent)
#QObjectWorkaround.__init__(self)
QtGui.QGraphicsObject.__init__(self, parent)
pos = Point(pos)
size = Point(size)
self.aspectLocked = False
@ -45,7 +51,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
if pen is None:
self.pen = QtGui.QPen(QtGui.QColor(255, 255, 255))
else:
self.pen = pen
self.pen = fn.mkPen(pen)
self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255))
self.handles = []
self.state = {'pos': pos, 'size': size, 'angle': angle}
@ -212,7 +218,8 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.isMoving = True
self.preMoveState = self.getState()
self.cursorOffset = self.scenePos() - ev.scenePos()
self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
#self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
self.sigRegionChangeStarted.emit(self)
ev.accept()
elif ev.button() == QtCore.Qt.RightButton:
if self.isMoving:
@ -236,7 +243,8 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
def mouseReleaseEvent(self, ev):
if self.translatable:
self.isMoving = False
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
#self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
self.sigRegionChangeFinished.emit(self)
def cancelMove(self):
self.isMoving = False
@ -247,14 +255,16 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.isMoving = True
self.preMoveState = self.getState()
self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
#self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
self.sigRegionChangeStarted.emit(self)
#self.pressPos = self.mapFromScene(ev.scenePos())
#self.pressHandlePos = self.handles[pt]['item'].pos()
def pointReleaseEvent(self, pt, ev):
#print "release"
self.isMoving = False
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
#self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
self.sigRegionChangeFinished.emit(self)
def stateCopy(self):
sc = {}
@ -316,7 +326,8 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
elif h['type'] == 'f':
h['item'].setPos(self.mapFromScene(pos))
self.emit(QtCore.SIGNAL('regionChanged'), self)
#self.emit(QtCore.SIGNAL('regionChanged'), self)
self.sigRegionChanged.emit(self)
elif h['type'] == 's':
#c = h['center']
@ -509,7 +520,8 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
if changed:
#print "handle changed."
self.update()
self.emit(QtCore.SIGNAL('regionChanged'), self)
#self.emit(QtCore.SIGNAL('regionChanged'), self)
self.sigRegionChanged.emit(self)
def scale(self, s, center=[0,0]):
@ -628,104 +640,116 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
def getArrayRegion(self, data, img, axes=(0,1)):
shape = self.state['size']
## transpose data so x and y are the first 2 axes
trAx = range(0, data.ndim)
trAx.remove(axes[0])
trAx.remove(axes[1])
tr1 = tuple(axes) + tuple(trAx)
arr = data.transpose(tr1)
origin = self.mapToItem(img, QtCore.QPointF(0, 0))
## Determine the minimal area of the data we will need
(dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes)
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()))
origin = (origin.x(), origin.y())
## Pad data boundaries by 1px if possible
dataBounds = (
(max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])),
(max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1]))
)
#print "shape", shape, "vectors", vectors, "origin", origin
## Extract minimal data from array
arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]]
return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, order=1)
## Update roiDataTransform to reflect this extraction
roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0])
### (roiDataTransform now maps from ROI coords to extracted data coords)
### transpose data so x and y are the first 2 axes
#trAx = range(0, data.ndim)
#trAx.remove(axes[0])
#trAx.remove(axes[1])
#tr1 = tuple(axes) + tuple(trAx)
#arr = data.transpose(tr1)
### Determine the minimal area of the data we will need
#(dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes)
### Pad data boundaries by 1px if possible
#dataBounds = (
#(max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])),
#(max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1]))
#)
### Extract minimal data from array
#arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]]
### Update roiDataTransform to reflect this extraction
#roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0])
#### (roiDataTransform now maps from ROI coords to extracted data coords)
## Rotate array
if abs(self.state['angle']) > 1e-5:
arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1)
### Rotate array
#if abs(self.state['angle']) > 1e-5:
#arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1)
## update data transforms to reflect this rotation
rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi)
roiDataTransform *= rot
### update data transforms to reflect this rotation
#rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi)
#roiDataTransform *= rot
## The rotation also causes a shift which must be accounted for:
dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1])
rotBound = rot.mapRect(dataBound)
roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top())
### The rotation also causes a shift which must be accounted for:
#dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1])
#rotBound = rot.mapRect(dataBound)
#roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top())
else:
arr2 = arr1
#else:
#arr2 = arr1
### Shift off partial pixels
# 1. map ROI into current data space
roiBounds = roiDataTransform.mapRect(self.boundingRect())
#### Shift off partial pixels
## 1. map ROI into current data space
#roiBounds = roiDataTransform.mapRect(self.boundingRect())
# 2. Determine amount to shift data
shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom())
if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6:
# 3. pad array with 0s before shifting
arr2a = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype)
arr2a[1:-1, 1:-1] = arr2
## 2. Determine amount to shift data
#shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom())
#if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6:
## 3. pad array with 0s before shifting
#arr2a = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype)
#arr2a[1:-1, 1:-1] = arr2
# 4. shift array and udpate transforms
arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1)
roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1])
else:
arr3 = arr2
## 4. shift array and udpate transforms
#arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1)
#roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1])
#else:
#arr3 = arr2
### Extract needed region from rotated/shifted array
# 1. map ROI into current data space (round these values off--they should be exact integer values at this point)
roiBounds = roiDataTransform.mapRect(self.boundingRect())
#print self, roiBounds.height()
#import traceback
#traceback.print_stack()
#### Extract needed region from rotated/shifted array
## 1. map ROI into current data space (round these values off--they should be exact integer values at this point)
#roiBounds = roiDataTransform.mapRect(self.boundingRect())
##print self, roiBounds.height()
##import traceback
##traceback.print_stack()
roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height()))
#roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height()))
#2. intersect ROI with data bounds
dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1]))
##2. intersect ROI with data bounds
#dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1]))
#3. Extract data from array
db = dataBounds
bounds = (
(db.left(), db.right()+1),
(db.top(), db.bottom()+1)
)
arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]]
##3. Extract data from array
#db = dataBounds
#bounds = (
#(db.left(), db.right()+1),
#(db.top(), db.bottom()+1)
#)
#arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]]
### Create zero array in size of ROI
arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype)
#### Create zero array in size of ROI
#arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype)
## Fill array with ROI data
orig = Point(dataBounds.topLeft() - roiBounds.topLeft())
subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]]
subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]]
### Fill array with ROI data
#orig = Point(dataBounds.topLeft() - roiBounds.topLeft())
#subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]]
#subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]]
## figure out the reverse transpose order
tr2 = np.array(tr1)
for i in range(0, len(tr2)):
tr2[tr1[i]] = i
tr2 = tuple(tr2)
### figure out the reverse transpose order
#tr2 = np.array(tr1)
#for i in range(0, len(tr2)):
#tr2[tr1[i]] = i
#tr2 = tuple(tr2)
## Untranspose array before returning
return arr5.transpose(tr2)
### Untranspose array before returning
#return arr5.transpose(tr2)
@ -887,10 +911,14 @@ class LineROI(ROI):
self.addScaleHandle([0.5, 1], [0.5, 0.5])
class MultiLineROI(QtGui.QGraphicsItem, QObjectWorkaround):
class MultiLineROI(QtGui.QGraphicsObject):
sigRegionChangeFinished = QtCore.Signal(object)
sigRegionChangeStarted = QtCore.Signal(object)
sigRegionChanged = QtCore.Signal(object)
def __init__(self, points, width, pen=None, **args):
QObjectWorkaround.__init__(self)
QtGui.QGraphicsItem.__init__(self)
QtGui.QGraphicsObject.__init__(self)
self.pen = pen
self.roiArgs = args
if len(points) < 2:
@ -912,9 +940,12 @@ class MultiLineROI(QtGui.QGraphicsItem, QObjectWorkaround):
for l in self.lines:
l.translatable = False
#self.addToGroup(l)
l.connect(l, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent)
l.connect(l, QtCore.SIGNAL('regionChangeStarted'), self.roiChangeStartedEvent)
l.connect(l, QtCore.SIGNAL('regionChangeFinished'), self.roiChangeFinishedEvent)
#l.connect(l, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent)
l.sigRegionChanged.connect(self.roiChangedEvent)
#l.connect(l, QtCore.SIGNAL('regionChangeStarted'), self.roiChangeStartedEvent)
l.sigRegionChangeStarted.connect(self.roiChangeStartedEvent)
#l.connect(l, QtCore.SIGNAL('regionChangeFinished'), self.roiChangeFinishedEvent)
l.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent)
def paint(self, *args):
pass
@ -927,13 +958,16 @@ class MultiLineROI(QtGui.QGraphicsItem, QObjectWorkaround):
for l in self.lines[1:]:
w0 = l.state['size'][1]
l.scale([1.0, w/w0], center=[0.5,0.5])
self.emit(QtCore.SIGNAL('regionChanged'), self)
#self.emit(QtCore.SIGNAL('regionChanged'), self)
self.sigRegionChanged.emit(self)
def roiChangeStartedEvent(self):
self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
#self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
self.sigRegionChangeStarted.emit(self)
def roiChangeFinishedEvent(self):
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
#self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
self.sigRegionChangeFinished.emit(self)
def getArrayRegion(self, arr, img=None):
@ -1038,6 +1072,57 @@ class PolygonROI(ROI):
#sc['handles'] = self.handles
return sc
class LineSegmentROI(ROI):
def __init__(self, positions, pos=None, **args):
if pos is None:
pos = [0,0]
ROI.__init__(self, pos, [1,1], **args)
#ROI.__init__(self, positions[0])
for p in positions:
self.addFreeHandle(p)
self.setZValue(1000)
def listPoints(self):
return [p['item'].pos() for p in self.handles]
def movePoint(self, *args, **kargs):
ROI.movePoint(self, *args, **kargs)
self.prepareGeometryChange()
for h in self.handles:
h['pos'] = h['item'].pos()
def paint(self, p, *args):
p.setRenderHint(QtGui.QPainter.Antialiasing)
p.setPen(self.pen)
for i in range(len(self.handles)-1):
h1 = self.handles[i]['item'].pos()
h2 = self.handles[i-1]['item'].pos()
p.drawLine(h1, h2)
def boundingRect(self):
r = QtCore.QRectF()
for h in self.handles:
r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs
return r
def shape(self):
p = QtGui.QPainterPath()
p.moveTo(self.handles[0]['item'].pos())
for i in range(len(self.handles)):
p.lineTo(self.handles[i]['item'].pos())
return p
def stateCopy(self):
sc = {}
sc['pos'] = Point(self.state['pos'])
sc['size'] = Point(self.state['size'])
sc['angle'] = self.state['angle']
#sc['handles'] = self.handles
return sc
class SpiralROI(ROI):
def __init__(self, pos=None, size=None, **args):
if size == None: