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 -*- # -*- coding: utf-8 -*-
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
import weakref
class TickSlider(QtGui.QGraphicsView): class TickSlider(QtGui.QGraphicsView):
def __init__(self, parent=None, orientation='bottom', allowAdd=True, **kargs): def __init__(self, parent=None, orientation='bottom', allowAdd=True, **kargs):
@ -161,6 +161,9 @@ class TickSlider(QtGui.QGraphicsView):
class GradientWidget(TickSlider): class GradientWidget(TickSlider):
sigGradientChanged = QtCore.Signal(object)
def __init__(self, *args, **kargs): def __init__(self, *args, **kargs):
TickSlider.__init__(self, *args, **kargs) TickSlider.__init__(self, *args, **kargs)
self.currentTick = None self.currentTick = None
@ -171,8 +174,10 @@ class GradientWidget(TickSlider):
self.colorDialog = QtGui.QColorDialog() self.colorDialog = QtGui.QColorDialog()
self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, 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('currentColorChanged(const QColor&)'), self.currentColorChanged)
QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected) 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.gradient = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(100,0))
self.scene.addItem(self.gradRect) self.scene.addItem(self.gradRect)
@ -199,7 +204,8 @@ class GradientWidget(TickSlider):
def updateGradient(self): def updateGradient(self):
self.gradient = self.getGradient() self.gradient = self.getGradient()
self.gradRect.setBrush(QtGui.QBrush(self.gradient)) 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): def setLength(self, newLen):
TickSlider.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): def __init__(self, view, pos, color, movable=True, scale=10):
#QObjectWorkaround.__init__(self) #QObjectWorkaround.__init__(self)
self.movable = movable self.movable = movable
self.view = view self.view = weakref.ref(view)
self.scale = scale self.scale = scale
self.color = color self.color = color
#self.endTick = endTick #self.endTick = endTick
@ -385,7 +391,7 @@ class Tick(QtGui.QGraphicsPolygonItem):
newPos.setY(self.pos().y()) newPos.setY(self.pos().y())
#newPos.setX(min(max(newPos.x(), 0), 100)) #newPos.setX(min(max(newPos.x(), 0), 100))
self.setPos(newPos) self.setPos(newPos)
self.view.tickMoved(self, newPos) self.view().tickMoved(self, newPos)
self.movedSincePress = True self.movedSincePress = True
#self.emit(QtCore.SIGNAL('tickChanged'), self) #self.emit(QtCore.SIGNAL('tickChanged'), self)
ev.accept() ev.accept()
@ -405,7 +411,7 @@ class Tick(QtGui.QGraphicsPolygonItem):
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
#print self, "release", ev.scenePos() #print self, "release", ev.scenePos()
if not self.movedSincePress: 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: #if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos:
#color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) #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 Point import *
#from vector import * #from vector import *
import sys import sys
#import debug
class GraphicsView(QtGui.QGraphicsView): 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): def __init__(self, parent=None, useOpenGL=True):
"""Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the """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 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 The view can be panned using the middle mouse button and scaled using the right mouse button if
enabled via enableMouse().""" enabled via enableMouse()."""
self.closed = False
QtGui.QGraphicsView.__init__(self, parent) QtGui.QGraphicsView.__init__(self, parent)
if 'linux' in sys.platform: ## linux has bugs in opengl implementation if 'linux' in sys.platform: ## linux has bugs in opengl implementation
@ -42,7 +49,7 @@ class GraphicsView(QtGui.QGraphicsView):
brush.setStyle(QtCore.Qt.SolidPattern) brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled,QtGui.QPalette.Base,brush) palette.setBrush(QtGui.QPalette.Disabled,QtGui.QPalette.Base,brush)
self.setPalette(palette) 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.setFocusPolicy(QtCore.Qt.StrongFocus)
self.setFrameShape(QtGui.QFrame.NoFrame) self.setFrameShape(QtGui.QFrame.NoFrame)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) 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.scaleCenter = False ## should scaling center around view center (True) or mouse click (False)
self.clickAccepted = 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): def useOpenGL(self, b=True):
if b: if b:
v = QtOpenGL.QGLWidget() v = QtOpenGL.QGLWidget()
@ -112,6 +127,8 @@ class GraphicsView(QtGui.QGraphicsView):
self.lastButtonReleased = None self.lastButtonReleased = None
def resizeEvent(self, ev): def resizeEvent(self, ev):
if self.closed:
return
if self.autoPixelRange: if self.autoPixelRange:
self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height()) self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height())
self.setRange(self.range, padding=0, disableAutoPixel=False) self.setRange(self.range, padding=0, disableAutoPixel=False)
@ -148,7 +165,8 @@ class GraphicsView(QtGui.QGraphicsView):
#print " translate:", st #print " translate:", st
self.setMatrix(m) self.setMatrix(m)
self.currentScale = scale 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: if propagate:
for v in self.lockedViewports: for v in self.lockedViewports:
@ -201,6 +219,16 @@ class GraphicsView(QtGui.QGraphicsView):
self.centralWidget.setGeometry(self.range) self.centralWidget.setGeometry(self.range)
self.updateMatrix(propagate) 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): def lockXRange(self, v1):
if not v1 in self.lockedViewports: if not v1 in self.lockedViewports:
@ -299,7 +327,8 @@ class GraphicsView(QtGui.QGraphicsView):
if not self.mouseEnabled: if not self.mouseEnabled:
return return
#self.mouseTrail.append(Point(self.mapToScene(ev.pos()))) #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() self.lastButtonReleased = ev.button()
return ## Everything below disabled for now.. return ## Everything below disabled for now..
@ -320,7 +349,8 @@ class GraphicsView(QtGui.QGraphicsView):
QtGui.QGraphicsView.mouseMoveEvent(self, ev) QtGui.QGraphicsView.mouseMoveEvent(self, ev)
if not self.mouseEnabled: if not self.mouseEnabled:
return 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() #print "moved. Grabber:", self.scene().mouseGrabberItem()
@ -333,13 +363,15 @@ class GraphicsView(QtGui.QGraphicsView):
#if self.yInverted: #if self.yInverted:
#scale[0] = 1. / scale[0] #scale[0] = 1. / scale[0]
self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos)) 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. elif ev.buttons() in [QtCore.Qt.MidButton, QtCore.Qt.LeftButton]: ## Allow panning by left or mid button.
tr = -delta / self.currentScale tr = -delta / self.currentScale
self.translate(tr[0], tr[1]) 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.. #return ## Everything below disabled for now..

View File

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

View File

@ -63,4 +63,7 @@ class MultiPlotItem(QtGui.QGraphicsWidget):
else: else:
raise Exception("Data type %s not (yet?) supported for MultiPlot." % type(data)) 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): def restoreState(self, state):
pass pass
#return self.plotItem.restoreState(state) #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) #tryWorkaround(QtCore, QtGui)
import weakref import weakref
import numpy as np import numpy as np
#import debug
try: try:
from WidgetGroup import * from WidgetGroup import *
@ -40,6 +41,11 @@ except:
class PlotItem(QtGui.QGraphicsWidget): 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.""" """Plot graphics item that can be added to any graphics scene. Implements axis titles, scales, interactive viewbox."""
lastFileDir = None lastFileDir = None
managers = {} managers = {}
@ -60,8 +66,10 @@ class PlotItem(QtGui.QGraphicsWidget):
proxy.setWidget(b) proxy.setWidget(b)
proxy.setAcceptHoverEvents(False) proxy.setAcceptHoverEvents(False)
b.setStyleSheet("background-color: #000000; color: #888; font-size: 6pt") b.setStyleSheet("background-color: #000000; color: #888; font-size: 6pt")
QtCore.QObject.connect(self.ctrlBtn, QtCore.SIGNAL('clicked()'), self.ctrlBtnClicked) #QtCore.QObject.connect(self.ctrlBtn, QtCore.SIGNAL('clicked()'), self.ctrlBtnClicked)
QtCore.QObject.connect(self.autoBtn, QtCore.SIGNAL('clicked()'), self.enableAutoScale) 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() self.layout = QtGui.QGraphicsGridLayout()
@ -71,11 +79,15 @@ class PlotItem(QtGui.QGraphicsWidget):
self.layout.setVerticalSpacing(0) self.layout.setVerticalSpacing(0)
self.vb = ViewBox() self.vb = ViewBox()
QtCore.QObject.connect(self.vb, QtCore.SIGNAL('xRangeChanged'), self.xRangeChanged) #QtCore.QObject.connect(self.vb, QtCore.SIGNAL('xRangeChanged'), self.xRangeChanged)
QtCore.QObject.connect(self.vb, QtCore.SIGNAL('yRangeChanged'), self.yRangeChanged) self.vb.sigXRangeChanged.connect(self.xRangeChanged)
QtCore.QObject.connect(self.vb, QtCore.SIGNAL('rangeChangedManually'), self.enableManualScale) #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.layout.addItem(self.vb, 2, 1)
self.alpha = 1.0 self.alpha = 1.0
@ -161,53 +173,81 @@ class PlotItem(QtGui.QGraphicsWidget):
self.setAcceptHoverEvents(True) self.setAcceptHoverEvents(True)
## Connect control widgets ## Connect control widgets
QtCore.QObject.connect(c.xMinText, QtCore.SIGNAL('editingFinished()'), self.setManualXScale) #QtCore.QObject.connect(c.xMinText, QtCore.SIGNAL('editingFinished()'), self.setManualXScale)
QtCore.QObject.connect(c.xMaxText, QtCore.SIGNAL('editingFinished()'), self.setManualXScale) c.xMinText.editingFinished.connect(self.setManualXScale)
QtCore.QObject.connect(c.yMinText, QtCore.SIGNAL('editingFinished()'), self.setManualYScale) #QtCore.QObject.connect(c.xMaxText, QtCore.SIGNAL('editingFinished()'), self.setManualXScale)
QtCore.QObject.connect(c.yMaxText, QtCore.SIGNAL('editingFinished()'), self.setManualYScale) 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.xManualRadio, QtCore.SIGNAL('clicked()'), self.updateXScale)
QtCore.QObject.connect(c.yManualRadio, QtCore.SIGNAL('clicked()'), self.updateYScale) 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.xAutoRadio, QtCore.SIGNAL('clicked()'), self.updateXScale)
QtCore.QObject.connect(c.yAutoRadio, QtCore.SIGNAL('clicked()'), self.updateYScale) 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.xAutoPercentSpin, QtCore.SIGNAL('valueChanged(int)'), self.replot)
QtCore.QObject.connect(c.yAutoPercentSpin, 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.xLogCheck, QtCore.SIGNAL('toggled(bool)'), self.setXLog)
#QtCore.QObject.connect(c.yLogCheck, QtCore.SIGNAL('toggled(bool)'), self.setYLog) #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.alphaGroup, QtCore.SIGNAL('toggled(bool)'), self.updateAlpha)
QtCore.QObject.connect(c.alphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateAlpha) c.alphaGroup.toggled.connect(self.updateAlpha)
QtCore.QObject.connect(c.autoAlphaCheck, QtCore.SIGNAL('toggled(bool)'), 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.gridGroup, QtCore.SIGNAL('toggled(bool)'), self.updateGrid)
QtCore.QObject.connect(c.gridAlphaSlider, QtCore.SIGNAL('valueChanged(int)'), 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.powerSpectrumGroup, QtCore.SIGNAL('toggled(bool)'), self.updateSpectrumMode)
QtCore.QObject.connect(c.saveSvgBtn, QtCore.SIGNAL('clicked()'), self.saveSvgClicked) c.powerSpectrumGroup.toggled.connect(self.updateSpectrumMode)
QtCore.QObject.connect(c.saveImgBtn, QtCore.SIGNAL('clicked()'), self.saveImgClicked) #QtCore.QObject.connect(c.saveSvgBtn, QtCore.SIGNAL('clicked()'), self.saveSvgClicked)
QtCore.QObject.connect(c.saveCsvBtn, QtCore.SIGNAL('clicked()'), self.saveCsvClicked) 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.gridGroup, QtCore.SIGNAL('toggled(bool)'), self.updateGrid)
#QtCore.QObject.connect(c.gridAlphaSlider, QtCore.SIGNAL('valueChanged(int)'), 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.xLinkCombo, QtCore.SIGNAL('currentIndexChanged(int)'), self.xLinkComboChanged)
QtCore.QObject.connect(self.ctrl.yLinkCombo, QtCore.SIGNAL('currentIndexChanged(int)'), self.yLinkComboChanged) 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.avgParamList, QtCore.SIGNAL('itemClicked(QListWidgetItem*)'), self.avgParamListClicked)
QtCore.QObject.connect(self.ctrl.averageGroup, QtCore.SIGNAL('toggled(bool)'), self.avgToggled) 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.pointsGroup, QtCore.SIGNAL('toggled(bool)'), self.updatePointMode)
#QtCore.QObject.connect(self.ctrl.autoPointsCheck, 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.maxTracesCheck, QtCore.SIGNAL('toggled(bool)'), self.updateDecimation)
QtCore.QObject.connect(self.ctrl.maxTracesSpin, QtCore.SIGNAL('valueChanged(int)'), self.updateDecimation) self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation)
QtCore.QObject.connect(c.xMouseCheck, QtCore.SIGNAL('toggled(bool)'), self.mouseCheckChanged) #QtCore.QObject.connect(self.ctrl.maxTracesSpin, QtCore.SIGNAL('valueChanged(int)'), self.updateDecimation)
QtCore.QObject.connect(c.yMouseCheck, QtCore.SIGNAL('toggled(bool)'), self.mouseCheckChanged) 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.xLinkPlot = None
self.yLinkPlot = None self.yLinkPlot = None
@ -236,9 +276,16 @@ class PlotItem(QtGui.QGraphicsWidget):
if len(kargs) > 0: if len(kargs) > 0:
self.plot(**kargs) 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: if self.manager is not None:
self.manager.sigWidgetListChanged.disconnect(self.updatePlotList)
self.manager.removeWidget(self.name) self.manager.removeWidget(self.name)
def registerPlot(self, name): def registerPlot(self, name):
@ -249,7 +296,8 @@ class PlotItem(QtGui.QGraphicsWidget):
PlotItem.managers[win] = PlotWidgetManager() PlotItem.managers[win] = PlotWidgetManager()
self.manager = PlotItem.managers[win] self.manager = PlotItem.managers[win]
self.manager.addWidget(self, name) 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() self.updatePlotList()
def updatePlotList(self): def updatePlotList(self):
@ -269,7 +317,8 @@ class PlotItem(QtGui.QGraphicsWidget):
except: except:
import gc import gc
refs= gc.get_referrers(self) refs= gc.get_referrers(self)
print " error during update. Referrers are:", refs print " error during update of", self
print " Referrers are:", refs
raise raise
def updateGrid(self, *args): def updateGrid(self, *args):
@ -291,8 +340,9 @@ class PlotItem(QtGui.QGraphicsWidget):
def viewChanged(self, *args): def viewRangeChanged(self, vb, range):
self.emit(QtCore.SIGNAL('viewChanged'), *args) #self.emit(QtCore.SIGNAL('viewChanged'), *args)
self.sigRangeChanged.emit(self, range)
def blockLink(self, b): def blockLink(self, b):
self.linksBlocked = b self.linksBlocked = b
@ -466,7 +516,8 @@ class PlotItem(QtGui.QGraphicsWidget):
#self.setLabel(l, unitPrefix='') #self.setLabel(l, unitPrefix='')
#self.getScale(l).setScale(1.0) #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): def yRangeChanged(self, _, range):
if any(np.isnan(range)) or any(np.isinf(range)): if any(np.isnan(range)) or any(np.isinf(range)):
@ -484,7 +535,8 @@ class PlotItem(QtGui.QGraphicsWidget):
#else: #else:
#self.setLabel(l, unitPrefix='') #self.setLabel(l, unitPrefix='')
#self.getScale(l).setScale(1.0) #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): def enableAutoScale(self):
@ -568,7 +620,8 @@ class PlotItem(QtGui.QGraphicsWidget):
self.curves.remove(item) self.curves.remove(item)
self.updateDecimation() self.updateDecimation()
self.updateParamList() 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): def clear(self):
for i in self.items[:]: for i in self.items[:]:
@ -644,7 +697,8 @@ class PlotItem(QtGui.QGraphicsWidget):
if self.ctrl.averageGroup.isChecked(): if self.ctrl.averageGroup.isChecked():
self.addAvgCurve(c) 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() self.plotChanged()
def plotChanged(self, curve=None): def plotChanged(self, curve=None):
@ -704,60 +758,141 @@ class PlotItem(QtGui.QGraphicsWidget):
self.paramList[p] = (i.checkState() == QtCore.Qt.Checked) self.paramList[p] = (i.checkState() == QtCore.Qt.Checked)
#print "paramList:", self.paramList #print "paramList:", self.paramList
## This is bullshit.
def writeSvg(self, fileName=None): def writeSvg(self, fileName=None):
if fileName is None: if fileName is None:
fileName = QtGui.QFileDialog.getSaveFileName() fileName = QtGui.QFileDialog.getSaveFileName()
if isinstance(fileName, tuple):
raise Exception("Not implemented yet..")
fileName = str(fileName) fileName = str(fileName)
PlotItem.lastFileDir = os.path.dirname(fileName) PlotItem.lastFileDir = os.path.dirname(fileName)
self.svg = QtSvg.QSvgGenerator() rect = self.vb.viewRect()
self.svg.setFileName(fileName) xRange = rect.left(), rect.right()
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())
self.svg.setResolution(res) svg = ""
#self.svg.setSize(QtCore.QSize(self.size().width(), self.size().height())) fh = open(fileName, 'w')
self.svg.setViewBox(bounds)
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) #fh.write('<svg viewBox="%f %f %f %f">\n' % (rect.left()*sx, rect.top()*sx, rect.width()*sy, rect.height()*sy))
#self.scene().render(painter, QtCore.QRectF(), view.mapToScene(bounds).boundingRect()) 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) for item in self.curves:
view.render(painter, bounds) 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 #fh.write('<g fill="none" stroke="#%s" stroke-opacity="1" stroke-width="1">\n' % color)
import re fh.write('<path fill="none" stroke="#%s" stroke-opacity="%f" stroke-width="1" d="M%f,%f ' % (color, opacity, x[0], y[0]))
data = open(fileName).readlines() for i in xrange(1, len(x)):
for i in range(len(data)): fh.write('L%f,%f ' % (x[i], y[i]))
line = data[i]
m = re.match(r'(<g .*)stroke-width="1"(.*transform="matrix\(([^\)]+)\)".*)', line) fh.write('"/>')
if m is not None: #fh.write("</g>")
#print "Matched group:", line for item in self.dataItems:
g = m.groups() if isinstance(item, ScatterPlotItem):
matrix = map(float, g[2].split(','))
#print "matrix:", matrix pRect = item.boundingRect()
scale = max(abs(matrix[0]), abs(matrix[3])) vRect = pRect.intersected(rect)
if scale == 0 or scale == 1.0:
for point in item.points():
pos = point.pos()
if not rect.contains(pos):
continue continue
data[i] = g[0] + ' stroke-width="%0.2g" ' % (1.0/scale) + g[1] + '\n' color = colorStr(point.brush.color())
#print "old line:", line opacity = point.brush.color().alpha() / 255.
#print "new line:", data[i] color = color[:6]
open(fileName, 'w').write(''.join(data)) 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): def writeImage(self, fileName=None):
if fileName is None: if fileName is None:
fileName = QtGui.QFileDialog.getSaveFileName() fileName = QtGui.QFileDialog.getSaveFileName()
if isinstance(fileName, tuple):
raise Exception("Not implemented yet..")
fileName = str(fileName) fileName = str(fileName)
PlotItem.lastFileDir = os.path.dirname(fileName) PlotItem.lastFileDir = os.path.dirname(fileName)
self.png = QtGui.QImage(int(self.size().width()), int(self.size().height()), QtGui.QImage.Format_ARGB32) 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: if PlotItem.lastFileDir is not None:
self.fileDialog.setDirectory(PlotItem.lastFileDir) self.fileDialog.setDirectory(PlotItem.lastFileDir)
self.fileDialog.show() 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): #def svgFileSelected(self, fileName):
##PlotWidget.lastFileDir = os.path.split(fileName)[0] ##PlotWidget.lastFileDir = os.path.split(fileName)[0]
@ -1012,7 +1148,8 @@ class PlotItem(QtGui.QGraphicsWidget):
self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
self.fileDialog.show() 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): def saveCsvClicked(self):
self.fileDialog = QtGui.QFileDialog() self.fileDialog = QtGui.QFileDialog()
@ -1023,13 +1160,17 @@ class PlotItem(QtGui.QGraphicsWidget):
self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
self.fileDialog.show() 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): #def imgFileSelected(self, fileName):
##PlotWidget.lastFileDir = os.path.split(fileName)[0] ##PlotWidget.lastFileDir = os.path.split(fileName)[0]
#self.writeImage(str(fileName)) #self.writeImage(str(fileName))
class PlotWidgetManager(QtCore.QObject): class PlotWidgetManager(QtCore.QObject):
sigWidgetListChanged = QtCore.Signal(object)
"""Used for managing communication between PlotWidgets""" """Used for managing communication between PlotWidgets"""
def __init__(self): def __init__(self):
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
@ -1037,12 +1178,14 @@ class PlotWidgetManager(QtCore.QObject):
def addWidget(self, w, name): def addWidget(self, w, name):
self.widgets[name] = w 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): def removeWidget(self, name):
if name in self.widgets: if name in self.widgets:
del self.widgets[name] 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): def listWidgets(self):
@ -1055,21 +1198,29 @@ class PlotWidgetManager(QtCore.QObject):
return self.widgets[name] return self.widgets[name]
def linkX(self, p1, p2): def linkX(self, p1, p2):
QtCore.QObject.connect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged) #QtCore.QObject.connect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged)
QtCore.QObject.connect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged) p1.sigXRangeChanged.connect(p2.linkXChanged)
#QtCore.QObject.connect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged)
p2.sigXRangeChanged.connect(p1.linkXChanged)
p1.linkXChanged(p2) p1.linkXChanged(p2)
#p2.setManualXScale() #p2.setManualXScale()
def unlinkX(self, p1, p2): def unlinkX(self, p1, p2):
QtCore.QObject.disconnect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged) #QtCore.QObject.disconnect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged)
QtCore.QObject.disconnect(p2, QtCore.SIGNAL('xRangeChanged'), p1.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): def linkY(self, p1, p2):
QtCore.QObject.connect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged) #QtCore.QObject.connect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged)
QtCore.QObject.connect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged) p1.sigYRangeChanged.connect(p2.linkYChanged)
#QtCore.QObject.connect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged)
p2.sigYRangeChanged.connect(p1.linkYChanged)
p1.linkYChanged(p2) p1.linkYChanged(p2)
#p2.setManualYScale() #p2.setManualYScale()
def unlinkY(self, p1, p2): def unlinkY(self, p1, p2):
QtCore.QObject.disconnect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged) #QtCore.QObject.disconnect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged)
QtCore.QObject.disconnect(p2, QtCore.SIGNAL('yRangeChanged'), p1.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 import exceptions
class PlotWidget(GraphicsView): class PlotWidget(GraphicsView):
sigRangeChanged = QtCore.Signal(object, object)
"""Widget implementing a graphicsView with a single PlotItem inside.""" """Widget implementing a graphicsView with a single PlotItem inside."""
def __init__(self, parent=None, **kargs): def __init__(self, parent=None, **kargs):
GraphicsView.__init__(self, parent) GraphicsView.__init__(self, parent)
@ -20,16 +23,22 @@ class PlotWidget(GraphicsView):
## Explicitly wrap methods from plotItem ## Explicitly wrap methods from plotItem
for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setXRange', 'setYRange']: for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setXRange', 'setYRange']:
setattr(self, m, getattr(self.plotItem, m)) 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): #def __dtor__(self):
##print "Called plotWidget sip destructor" ##print "Called plotWidget sip destructor"
#self.quit() #self.quit()
def quit(self): #def quit(self):
self.plotItem.clear()
self.scene().clear() 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 def __getattr__(self, attr): ## implicitly wrap methods from plotItem
if hasattr(self.plotItem, attr): if hasattr(self.plotItem, attr):
@ -38,8 +47,9 @@ class PlotWidget(GraphicsView):
return m return m
raise exceptions.NameError(attr) raise exceptions.NameError(attr)
def viewChanged(self, *args): def viewRangeChanged(self, view, range):
self.emit(QtCore.SIGNAL('viewChanged'), *args) #self.emit(QtCore.SIGNAL('viewChanged'), *args)
self.sigRangeChanged.emit(self, range)
def widgetGroupInterface(self): def widgetGroupInterface(self):
return (None, PlotWidget.saveState, PlotWidget.restoreState) return (None, PlotWidget.saveState, PlotWidget.restoreState)

View File

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

View File

@ -2,14 +2,6 @@
from PyQt4 import QtCore from PyQt4 import QtCore
from ptime import time 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): class SignalProxy(QtCore.QObject):
"""Object which collects rapid-fire signals and condenses them """Object which collects rapid-fire signals and condenses them
into a single signal. Used, for example, to prevent a SpinBox into a single signal. Used, for example, to prevent a SpinBox
@ -17,53 +9,70 @@ class SignalProxy(QtCore.QObject):
over it.""" over it."""
def __init__(self, source, signal, delay=0.3): 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) QtCore.QObject.__init__(self)
source.connect(source, signal, self.signal) if source is None:
self.delay = delay signal.connect(self.signalReceived)
self.waitUntil = 0 self.signal = QtCore.SIGNAL('signal')
self.args = None else:
self.timers = 0 source.connect(source, signal, self.signalReceived)
self.signal = signal self.signal = signal
self.delay = delay
self.args = None
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.flush)
self.block = False self.block = False
def setDelay(self, delay): def setDelay(self, delay):
self.delay = 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): def flush(self):
"""If there is a signal queued up, send it now.""" """If there is a signal queued up, send it now."""
if self.args is None or self.block: if self.args is None or self.block:
return False return False
if self.block:
return
self.emit(self.signal, *self.args) self.emit(self.signal, *self.args)
self.args = None self.args = None
return True 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): def disconnect(self):
self.block = True 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 import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) 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 PyQt4 import QtCore, QtGui
from scipy.ndimage import * import numpy as np
import pyqtgraph as pg
app = QtGui.QApplication([]) app = QtGui.QApplication([])
## Create window with GraphicsView widget ## Create window with GraphicsView widget
win = QtGui.QMainWindow() win = QtGui.QMainWindow()
view = GraphicsView() view = pg.GraphicsView()
#view.useOpenGL(True) #view.useOpenGL(True)
win.setCentralWidget(view) win.setCentralWidget(view)
win.show() win.show()
@ -25,26 +24,29 @@ view.enableMouse()
view.setAspectLocked(True) view.setAspectLocked(True)
## Create image item ## Create image item
img = ImageItem() img = pg.ImageItem()
view.scene().addItem(img) view.scene().addItem(img)
## Set initial view bounds ## Set initial view bounds
view.setRange(QtCore.QRectF(0, 0, 200, 200)) view.setRange(QtCore.QRectF(0, 0, 200, 200))
## Create random image
data = np.random.normal(size=(50, 200, 200))
i = 0
def updateData(): def updateData():
global img global img, data, i
## Create random image
data = random.random((200, 200))
## Display the data ## Display the data
img.updateImage(data) img.updateImage(data[i])
i = (i+1) % data.shape[0]
# update image data every 20ms (or so) # update image data every 20ms (or so)
t = QtCore.QTimer() t = QtCore.QTimer()
QtCore.QObject.connect(t, QtCore.SIGNAL('timeout()'), updateData) t.timeout.connect(updateData)
t.start(20) t.start(20)
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_() app.exec_()

View File

@ -3,24 +3,25 @@
import sys, os import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) 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 PyQt4 import QtCore, QtGui
from scipy.ndimage import * import pyqtgraph as pg
app = QtGui.QApplication([]) app = QtGui.QApplication([])
## Create window with ImageView widget ## Create window with ImageView widget
win = QtGui.QMainWindow() win = QtGui.QMainWindow()
imv = ImageView() imv = pg.ImageView()
win.setCentralWidget(imv) win.setCentralWidget(imv)
win.show() win.show()
## Create random 3D data set ## Create random 3D data set with noisy signals
img = gaussian_filter(random.normal(size=(200, 200)), (5, 5)) * 20 + 100 img = scipy.ndimage.gaussian_filter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
img = img[newaxis,:,:] img = img[np.newaxis,:,:]
decay = exp(-linspace(0,0.3,100))[:,newaxis,newaxis] decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis]
data = random.normal(size=(100, 200, 200)) data = np.random.normal(size=(100, 200, 200))
data += img * decay data += img * decay
#for i in range(data.shape[0]): #for i in range(data.shape[0]):
@ -28,16 +29,18 @@ data += img * decay
data += 2 data += 2
## Add time-varying signal ## Add time-varying signal
sig = zeros(data.shape[0]) sig = np.zeros(data.shape[0])
sig[30:] += exp(-linspace(1,10, 70)) sig[30:] += np.exp(-np.linspace(1,10, 70))
sig[40:] += exp(-linspace(1,10, 60)) sig[40:] += np.exp(-np.linspace(1,10, 60))
sig[70:] += exp(-linspace(1,10, 30)) 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 data[:,50:60,50:60] += sig
## Display the data ## 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 import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from scipy import random from scipy import random
from numpy import linspace from numpy import linspace
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
from pyqtgraph.MultiPlotWidget import * import pyqtgraph as pg
from pyqtgraph.MultiPlotWidget import MultiPlotWidget
try: try:
from metaarray import * from metaarray import *
except: 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() exit()
app = QtGui.QApplication([]) 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)}]) 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) 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 import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) 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([]) app = QtGui.QApplication([])
mw = QtGui.QMainWindow() mw = QtGui.QMainWindow()
@ -17,77 +16,56 @@ mw.setCentralWidget(cw)
l = QtGui.QVBoxLayout() l = QtGui.QVBoxLayout()
cw.setLayout(l) cw.setLayout(l)
pw = PlotWidget() pw = pg.PlotWidget(name='Plot1') ## giving the plots names allows us to link their axes together
l.addWidget(pw) l.addWidget(pw)
pw2 = PlotWidget() pw2 = pg.PlotWidget(name='Plot2')
l.addWidget(pw2) l.addWidget(pw2)
pw3 = PlotWidget() pw3 = pg.PlotWidget()
l.addWidget(pw3) l.addWidget(pw3)
pw.registerPlot('Plot1') mw.show()
pw2.registerPlot('Plot2')
#p1 = PlotCurveItem() ## Create an empty plot curve to be filled later, set its pen
#pw.addItem(p1)
p1 = pw.plot() p1 = pw.plot()
p1.setPen((200,200,100))
## Add in some extra graphics
rect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, 0, 1, 1)) rect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, 0, 1, 1))
rect.setPen(QtGui.QPen(QtGui.QColor(100, 200, 100))) rect.setPen(QtGui.QPen(QtGui.QColor(100, 200, 100)))
pw.addItem(rect) 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): 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.1):int(n*0.13)] += .5
data[int(n*0.18)] += 2 data[int(n*0.18)] += 2
data[int(n*0.1):int(n*0.13)] *= 5 data[int(n*0.1):int(n*0.13)] *= 5
data[int(n*0.18)] *= 20 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(): def updateData():
yd, xd = rand(10000) yd, xd = rand(10000)
p1.updateData(yd, x=xd) p1.updateData(yd, x=xd)
yd, xd = rand(10000) ## Start a timer to rapidly update the plot in pw
updateData()
pw.autoRange()
t = QtCore.QTimer() t = QtCore.QTimer()
t.timeout.connect(updateData)
QtCore.QObject.connect(t, QtCore.SIGNAL('timeout()'), updateData)
t.start(50) t.start(50)
## Multiple parameterized plots--we can autogenerate averages for these.
for i in range(0, 5): for i in range(0, 5):
for j in range(0, 3): for j in range(0, 3):
yd, xd = rand(10000) yd, xd = rand(10000)
pw2.plot(y=yd*(j+1), x=xd, params={'iter': i, 'val': j}) 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 import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) 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 from PyQt4 import QtCore, QtGui
import numpy as np
import pyqtgraph as pg
## create GUI
app = QtGui.QApplication([]) app = QtGui.QApplication([])
w = QtGui.QMainWindow()
class Win(QtGui.QMainWindow): v = pg.GraphicsView()
pass v.invertY(True) ## Images usually have their Y-axis pointing downward
w = Win()
v = GraphicsView(useOpenGL=False)
v.invertY(True)
v.setAspectLocked(True) v.setAspectLocked(True)
v.enableMouse(True) v.enableMouse(True)
v.autoPixelScale = False v.autoPixelScale = False
w.setCentralWidget(v) w.setCentralWidget(v)
s = v.scene() s = v.scene()
v.setRange(QtCore.QRect(-2, -2, 220, 220)) 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[45:55, 45:55] = 0
arr[25, :] = 5 arr[25, :] = 5
arr[:, 25] = 5 arr[:, 25] = 5
@ -38,21 +32,23 @@ arr[:, 75] = 5
arr[50, :] = 10 arr[50, :] = 10
arr[:, 50] = 10 arr[:, 50] = 10
im1 = ImageItem(arr) ## Create image items, add to scene and set position
im2 = ImageItem(arr) im1 = pg.ImageItem(arr)
im2 = pg.ImageItem(arr)
s.addItem(im1) s.addItem(im1)
s.addItem(im2) s.addItem(im2)
im2.moveBy(110, 20) im2.moveBy(110, 20)
im3 = ImageItem() im3 = pg.ImageItem()
s.addItem(im3) s.addItem(im3)
im3.moveBy(0, 130) im3.moveBy(0, 130)
im3.setZValue(10) im3.setZValue(10)
im4 = ImageItem() im4 = pg.ImageItem()
s.addItem(im4) s.addItem(im4)
im4.moveBy(110, 130) im4.moveBy(110, 130)
im4.setZValue(10) im4.setZValue(10)
pi1 = PlotItem() ## create the plot
pi1 = pg.PlotItem()
s.addItem(pi1) s.addItem(pi1)
pi1.scale(0.5, 0.5) pi1.scale(0.5, 0.5)
pi1.setGeometry(0, 170, 300, 100) pi1.setGeometry(0, 170, 300, 100)
@ -76,34 +72,39 @@ def updateRoiPlot(roi, data=None):
if data is not None: if data is not None:
roi.curve.updateData(data.mean(axis=1)) roi.curve.updateData(data.mean(axis=1))
#def updatePlot(roi)
## Create a variety of different ROI types
rois = [] rois = []
rois.append(TestROI([0, 0], [20, 20], maxBounds=QtCore.QRectF(-10, -10, 230, 140), pen=mkPen(0))) rois.append(pg.widgets.TestROI([0, 0], [20, 20], maxBounds=QtCore.QRectF(-10, -10, 230, 140), pen=(0,9)))
rois.append(LineROI([0, 0], [20, 20], width=5, pen=mkPen(1))) rois.append(pg.widgets.LineROI([0, 0], [20, 20], width=5, pen=(1,9)))
rois.append(MultiLineROI([[0, 50], [50, 60], [60, 30]], width=5, pen=mkPen(2))) rois.append(pg.widgets.MultiLineROI([[0, 50], [50, 60], [60, 30]], width=5, pen=(2,9)))
rois.append(EllipseROI([110, 10], [30, 20], pen=mkPen(3))) rois.append(pg.widgets.EllipseROI([110, 10], [30, 20], pen=(3,9)))
rois.append(CircleROI([110, 50], [20, 20], pen=mkPen(4))) rois.append(pg.widgets.CircleROI([110, 50], [20, 20], pen=(4,9)))
rois.append(PolygonROI([[2,0], [2.1,0], [2,.1]], pen=mkPen(5))) 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))) #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: for r in rois:
s.addItem(r) s.addItem(r)
c = pi1.plot(pen=r.pen) c = pi1.plot(pen=r.pen)
r.curve = c r.curve = c
r.connect(r, QtCore.SIGNAL('regionChanged'), updateRoi) r.sigRegionChanged.connect(updateRoi)
def updateImage(): def updateImage():
global im1, arr, lastRoi 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) im1.updateImage(arr + r)
updateRoi(lastRoi) updateRoi(lastRoi)
for r in rois: for r in rois:
updateRoiPlot(r) updateRoiPlot(r)
## Rapidly update one of the images with random noise
t = QtCore.QTimer() t = QtCore.QTimer()
t.connect(t, QtCore.SIGNAL('timeout()'), updateImage) t.timeout.connect(updateImage)
t.start(50) 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 import sys, os
sys.path = [os.path.join(os.path.dirname(__file__), '..', '..')] + sys.path 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 PyQt4 import QtCore, QtGui
from scipy.ndimage import *
import numpy as np import numpy as np
import pyqtgraph as pg
app = QtGui.QApplication([]) app = QtGui.QApplication([])
## Create window with GraphicsView widget ## Create window with GraphicsView widget
win = QtGui.QMainWindow() win = QtGui.QMainWindow()
view = GraphicsView() view = pg.GraphicsView()
#view.useOpenGL(True) #view.useOpenGL(True)
win.setCentralWidget(view) win.setCentralWidget(view)
win.show() win.show()
@ -26,7 +24,7 @@ view.enableMouse()
view.setAspectLocked(True) view.setAspectLocked(True)
## Create image item ## Create image item
img = ImageItem(np.zeros((200,200))) img = pg.ImageItem(np.zeros((200,200)))
view.scene().addItem(img) view.scene().addItem(img)
## Set initial view bounds ## Set initial view bounds
@ -35,4 +33,6 @@ view.setRange(QtCore.QRectF(0, 0, 200, 200))
img.setDrawKernel(1) img.setDrawKernel(1)
img.setLevels(10,0) 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 -*- # -*- coding: utf-8 -*-
import sys, os 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__), '..', '..')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
from pyqtgraph.PlotWidget import * import pyqtgraph as pg
from pyqtgraph.graphicsItems import * import numpy as np
app = QtGui.QApplication([]) app = QtGui.QApplication([])
mw = QtGui.QMainWindow() mw = QtGui.QMainWindow()
cw = PlotWidget() cw = pg.PlotWidget()
mw.setCentralWidget(cw) mw.setCentralWidget(cw)
mw.show() mw.show()
s1 = pg.ScatterPlotItem(size=10, pen=QtGui.QPen(QtCore.Qt.NoPen), brush=QtGui.QBrush(QtGui.QColor(255, 255, 255, 20)))
#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)))
pos = np.random.normal(size=(2,3000)) pos = np.random.normal(size=(2,3000))
spots = [{'pos': pos[:,i]} for i in range(3000)] spots = [{'pos': pos[:,i]} for i in range(3000)]
s1.addPoints(spots) s1.addPoints(spots)
cw.addDataItem(s1) 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 import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) 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 PyQt4 import QtGui, QtCore
from pyqtgraph.GraphicsView import * import pyqtgraph as pg
from pyqtgraph.graphicsItems import *
app = QtGui.QApplication([]) app = QtGui.QApplication([])
mw = QtGui.QMainWindow() mw = QtGui.QMainWindow()
@ -19,15 +21,15 @@ mw.show()
mw.resize(800, 600) mw.resize(800, 600)
gv = GraphicsView(cw) gv = pg.GraphicsView(cw)
gv.enableMouse(False) gv.enableMouse(False) ## Mouse interaction will be handled by the ViewBox
l = QtGui.QGraphicsGridLayout() l = QtGui.QGraphicsGridLayout()
l.setHorizontalSpacing(0) l.setHorizontalSpacing(0)
l.setVerticalSpacing(0) l.setVerticalSpacing(0)
vb = ViewBox() vb = pg.ViewBox()
p1 = PlotCurveItem() p1 = pg.PlotCurveItem()
vb.addItem(p1) vb.addItem(p1)
vl.addWidget(gv) vl.addWidget(gv)
@ -61,21 +63,21 @@ l.addItem(vb, 0, 1)
gv.centralWidget.setLayout(l) gv.centralWidget.setLayout(l)
xScale = ScaleItem(orientation='bottom', linkView=vb) xScale = pg.ScaleItem(orientation='bottom', linkView=vb)
l.addItem(xScale, 1, 1) l.addItem(xScale, 1, 1)
yScale = ScaleItem(orientation='left', linkView=vb) yScale = pg.ScaleItem(orientation='left', linkView=vb)
l.addItem(yScale, 0, 0) l.addItem(yScale, 0, 0)
xScale.setLabel(text=u"<span style='color: #ff0000; font-weight: bold'>X</span> <i>Axis</i>", units="s") xScale.setLabel(text=u"<span style='color: #ff0000; font-weight: bold'>X</span> <i>Axis</i>", units="s")
yScale.setLabel('Y Axis', units='V') yScale.setLabel('Y Axis', units='V')
def rand(n): 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.1):int(n*0.13)] += .5
data[int(n*0.18)] += 2 data[int(n*0.18)] += 2
data[int(n*0.1):int(n*0.13)] *= 5 data[int(n*0.1):int(n*0.13)] *= 5
data[int(n*0.18)] *= 20 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(): def updateData():
@ -87,7 +89,9 @@ updateData()
vb.autoRange() vb.autoRange()
t = QtCore.QTimer() t = QtCore.QTimer()
QtCore.QObject.connect(t, QtCore.SIGNAL('timeout()'), updateData) t.timeout.connect(updateData)
t.start(50) 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 PyQt4 import QtGui
from numpy import clip, floor, log import numpy as np
import scipy.ndimage
## Copied from acq4/lib/util/functions ## Copied from acq4/lib/util/functions
SI_PREFIXES = u'yzafpnµm kMGTPEZY' SI_PREFIXES = u'yzafpnµm kMGTPEZY'
@ -28,7 +29,7 @@ def siScale(x, minVal=1e-25):
m = 0 m = 0
x = 0 x = 0
else: 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: if m == 0:
pref = '' pref = ''
elif m < -8 or m > 8: elif m < -8 or m > 8:
@ -145,13 +146,13 @@ def mkColor(*args):
return QtGui.QColor(r, g, b, a) return QtGui.QColor(r, g, b, a)
def colorTuple(c): def colorTuple(c):
return (c.red(), c.blue(), c.green(), c.alpha()) return (c.red(), c.green(), c.blue(), c.alpha())
def colorStr(c): def colorStr(c):
"""Generate a hex string code from a QColor""" """Generate a hex string code from a QColor"""
return ('%02x'*4) % colorTuple(c) 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. """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 - The argument "index" determines which color from the set will be returned
- All other arguments determine what the set of predefined colors will be - 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) ind = int(index) % (hues * values)
indh = ind % hues indh = ind % hues
indv = ind / hues indv = ind / hues
if values > 1:
v = minValue + indv * ((maxValue-minValue) / (values-1)) v = minValue + indv * ((maxValue-minValue) / (values-1))
else:
v = maxValue
h = minHue + (indh * (maxHue-minHue)) / hues h = minHue + (indh * (maxHue-minHue)) / hues
c = QtGui.QColor() c = QtGui.QColor()
c.setHsv(h, sat, v) c.setHsv(h, sat, v)
c.setAlpha(alpha)
return c 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 from PyQt4 import QtGui, QtCore
if not hasattr(QtCore, 'Signal'): if not hasattr(QtCore, 'Signal'):
QtCore.Signal = QtCore.pyqtSignal QtCore.Signal = QtCore.pyqtSignal
from ObjectWorkaround import * #from ObjectWorkaround import *
#tryWorkaround(QtCore, QtGui) #tryWorkaround(QtCore, QtGui)
#from numpy import * #from numpy import *
import numpy as np import numpy as np
@ -28,8 +28,16 @@ from Point import *
from functions import * from functions import *
import types, sys, struct import types, sys, struct
import weakref import weakref
#import debug
#from debug import * #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.. ## Should probably just use QGraphicsGroupItem and instruct it to pass events on to children..
class ItemGroup(QtGui.QGraphicsItem): 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. """Extends QGraphicsObject with a few important functions.
(Most of these assume that the object is in a scene with a single view)""" (Most of these assume that the object is in a scene with a single view)"""
def __init__(self, *args): def __init__(self, *args):
QGraphicsObject.__init__(self, *args) QtGui.QGraphicsObject.__init__(self, *args)
self._view = None self._view = None
def getViewWidget(self): 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. if 'linux' not in sys.platform: ## disable weave optimization on linux--broken there.
useWeave = True useWeave = True
else: else:
useWeave = False useWeave = False
def __init__(self, image=None, copy=True, parent=None, border=None, *args): def __init__(self, image=None, copy=True, parent=None, border=None, *args):
QObjectWorkaround.__init__(self) #QObjectWorkaround.__init__(self)
QtGui.QGraphicsObject.__init__(self)
#self.pixmapItem = QtGui.QGraphicsPixmapItem(self)
self.qimage = QtGui.QImage() self.qimage = QtGui.QImage()
self.pixmap = None self.pixmap = None
#self.useWeave = True #self.useWeave = True
@ -195,7 +208,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
border = mkPen(border) border = mkPen(border)
self.border = border self.border = border
QtGui.QGraphicsPixmapItem.__init__(self, parent, *args) #QtGui.QGraphicsPixmapItem.__init__(self, parent, *args)
#self.pixmapItem = QtGui.QGraphicsPixmapItem(self) #self.pixmapItem = QtGui.QGraphicsPixmapItem(self)
if image is not None: if image is not None:
self.updateImage(image, copy, autoRange=True) self.updateImage(image, copy, autoRange=True)
@ -219,6 +232,11 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
return None return None
return self.pixmap.height() 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): def setClipLevel(self, level=None):
self.clipLevel = level self.clipLevel = level
@ -252,6 +270,8 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
return return
else: else:
gotNewData = True gotNewData = True
if self.image is None or image.shape != self.image.shape:
self.prepareGeometryChange()
if copy: if copy:
self.image = image.view(np.ndarray).copy() self.image = image.view(np.ndarray).copy()
else: else:
@ -323,7 +343,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
im2 = im.transpose(axh['y'], axh['x'], axh['c']) im2 = im.transpose(axh['y'], axh['x'], axh['c'])
for i in range(0, im.shape[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): for i in range(im.shape[axh['c']], 3):
im1[..., i] = 0 im1[..., i] = 0
@ -345,14 +365,16 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
im1[..., 2][mask] = 255 im1[..., 2][mask] = 255
#print "Final image:", im1.dtype, im1.min(), im1.max(), im1.shape #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 :( 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) self.pixmap = QtGui.QPixmap.fromImage(qimage)
##del self.ims ##del self.ims
self.setPixmap(self.pixmap) #self.pixmapItem.setPixmap(self.pixmap)
self.update() self.update()
if gotNewData: if gotNewData:
self.emit(QtCore.SIGNAL('imageChanged')) #self.emit(QtCore.SIGNAL('imageChanged'))
self.sigImageChanged.emit()
def getPixmap(self): def getPixmap(self):
return self.pixmap.copy() return self.pixmap.copy()
@ -397,8 +419,15 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
p.setPen(self.border) p.setPen(self.border)
p.drawRect(self.boundingRect()) 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): class PlotCurveItem(GraphicsObject):
sigPlotChanged = QtCore.Signal(object)
"""Class representing a single plot curve.""" """Class representing a single plot curve."""
sigClicked = QtCore.Signal(object) sigClicked = QtCore.Signal(object)
@ -565,6 +594,7 @@ class PlotCurveItem(GraphicsObject):
self.updateData(y, x, copy) self.updateData(y, x, copy)
def updateData(self, data, x=None, copy=False): def updateData(self, data, x=None, copy=False):
#prof = debug.Profiler('PlotCurveItem.updateData', disabled=True)
if isinstance(data, list): if isinstance(data, list):
data = np.array(data) data = np.array(data)
if isinstance(x, list): if isinstance(x, list):
@ -591,7 +621,7 @@ class PlotCurveItem(GraphicsObject):
x = data[tuple(ind)] x = data[tuple(ind)]
elif data.ndim == 1: elif data.ndim == 1:
y = data y = data
#prof.mark("data checks")
self.prepareGeometryChange() self.prepareGeometryChange()
if copy: if copy:
self.yData = y.copy() self.yData = y.copy()
@ -602,6 +632,7 @@ class PlotCurveItem(GraphicsObject):
self.xData = x.copy() self.xData = x.copy()
else: else:
self.xData = x self.xData = x
#prof.mark('copy')
if x is None: if x is None:
self.xData = np.arange(0, self.yData.shape[0]) 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))) 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.path = None
#self.specPath = None
self.xDisp = self.yDisp = None self.xDisp = self.yDisp = None
#prof.mark('set')
self.update() 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): def generatePath(self, x, y):
path = QtGui.QPainterPath() path = QtGui.QPainterPath()
@ -634,25 +670,32 @@ class PlotCurveItem(GraphicsObject):
## 0(i4) ## 0(i4)
## ##
## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') ## 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] n = x.shape[0]
# create empty array, pad with extra space on either end # create empty array, pad with extra space on either end
arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')])
#prof.mark('create empty')
# write first two integers # write first two integers
arr.data[12:20] = struct.pack('>ii', n, 0) arr.data[12:20] = struct.pack('>ii', n, 0)
# Fill array with vertex values # Fill array with vertex values
arr[1:-1]['x'] = x arr[1:-1]['x'] = x
arr[1:-1]['y'] = y arr[1:-1]['y'] = y
arr[1:-1]['c'] = 1 arr[1:-1]['c'] = 1
#prof.mark('fill array')
# write last 0 # write last 0
lastInd = 20*(n+1) lastInd = 20*(n+1)
arr.data[lastInd:lastInd+4] = struct.pack('>i', 0) arr.data[lastInd:lastInd+4] = struct.pack('>i', 0)
# create datastream object and stream into path # create datastream object and stream into path
buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here
#prof.mark('create buffer')
ds = QtCore.QDataStream(buf) ds = QtCore.QDataStream(buf)
#prof.mark('create dataStream')
ds >> path ds >> path
#prof.mark('load path')
#prof.finish()
return path return path
def boundingRect(self): def boundingRect(self):
@ -677,6 +720,7 @@ class PlotCurveItem(GraphicsObject):
return QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin) return QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
def paint(self, p, opt, widget): def paint(self, p, opt, widget):
#prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True)
if self.xData is None: if self.xData is None:
return return
#if self.opts['spectrumMode']: #if self.opts['spectrumMode']:
@ -688,6 +732,7 @@ class PlotCurveItem(GraphicsObject):
if self.path is None: if self.path is None:
self.path = self.generatePath(*self.getData()) self.path = self.generatePath(*self.getData())
path = self.path path = self.path
#prof.mark('generate path')
if self.shadow is not None: if self.shadow is not None:
sp = QtGui.QPen(self.shadow) sp = QtGui.QPen(self.shadow)
@ -709,7 +754,9 @@ class PlotCurveItem(GraphicsObject):
p.drawPath(path) p.drawPath(path)
p.setPen(cp) p.setPen(cp)
p.drawPath(path) p.drawPath(path)
#prof.mark('drawPath')
#prof.finish()
#p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0)))
#p.drawRect(self.boundingRect()) #p.drawRect(self.boundingRect())
@ -742,7 +789,7 @@ class PlotCurveItem(GraphicsObject):
self.sigClicked.emit(self) 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. """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.""" 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 """Position can be set either as an index referring to the sample number or
the position 0.0 - 1.0""" the position 0.0 - 1.0"""
QtGui.QGraphicsItem.__init__(self) QtGui.QGraphicsObject.__init__(self)
QObjectWorkaround.__init__(self) #QObjectWorkaround.__init__(self)
self.curve = None self.curve = weakref.ref(curve)
self.setParentItem(curve)
self.setProperty('position', 0.0) self.setProperty('position', 0.0)
self.setProperty('index', 0) self.setProperty('index', 0)
if hasattr(self, 'ItemHasNoContents'): if hasattr(self, 'ItemHasNoContents'):
self.setFlags(self.flags() | self.ItemHasNoContents) self.setFlags(self.flags() | self.ItemHasNoContents)
self.curve = curve
self.setParentItem(curve)
if pos is not None: if pos is not None:
self.setPos(pos) self.setPos(pos)
else: else:
@ -773,22 +819,22 @@ class CurvePoint(QtGui.QGraphicsItem, QObjectWorkaround):
self.setProperty('index', index) self.setProperty('index', index)
def event(self, ev): def event(self, ev):
if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve is None: if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None:
return return False
if ev.propertyName() == 'index': if ev.propertyName() == 'index':
index = self.property('index').toInt()[0] index = self.property('index').toInt()[0]
elif ev.propertyName() == 'position': elif ev.propertyName() == 'position':
index = None index = None
else: else:
return return False
(x, y) = self.curve.getData() (x, y) = self.curve().getData()
if index is None: if index is None:
#print self.property('position').toDouble()[0], self.property('position').typeName() #print self.property('position').toDouble()[0], self.property('position').typeName()
index = (len(x)-1) * clip(self.property('position').toDouble()[0], 0.0, 1.0) 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) i1 = int(index)
i2 = clip(i1+1, 0, len(x)-1) i2 = clip(i1+1, 0, len(x)-1)
s2 = index-i1 s2 = index-i1
@ -806,6 +852,7 @@ class CurvePoint(QtGui.QGraphicsItem, QObjectWorkaround):
self.resetTransform() self.resetTransform()
self.rotate(180+ ang * 180 / np.pi) self.rotate(180+ ang * 180 / np.pi)
QtGui.QGraphicsItem.setPos(self, *newPos) QtGui.QGraphicsItem.setPos(self, *newPos)
return True
def boundingRect(self): def boundingRect(self):
return QtCore.QRectF() return QtCore.QRectF()
@ -814,7 +861,7 @@ class CurvePoint(QtGui.QGraphicsItem, QObjectWorkaround):
pass pass
def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1): 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.setDuration(duration)
anim.setStartValue(start) anim.setStartValue(start)
anim.setEndValue(end) anim.setEndValue(end)
@ -939,22 +986,27 @@ class ScatterPlotItem(QtGui.QGraphicsWidget):
for s in spots: for s in spots:
pos = Point(s['pos']) pos = Point(s['pos'])
size = s.get('size', self.size) 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) brush = s.get('brush', self.brush)
pen = s.get('pen', self.pen) pen = s.get('pen', self.pen)
pen.setCosmetic(True) pen.setCosmetic(True)
data = s.get('data', None) data = s.get('data', None)
item = self.mkSpot(pos, size, self.pxMode, brush, pen, data) item = self.mkSpot(pos, size, self.pxMode, brush, pen, data)
self.spots.append(item) 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]] self.range = [[xmn, xmx], [ymn, ymx]]
@ -967,7 +1019,8 @@ class ScatterPlotItem(QtGui.QGraphicsWidget):
def boundingRect(self): def boundingRect(self):
((xmn, xmx), (ymn, ymx)) = self.range ((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) return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
def paint(self, p, *args): def paint(self, p, *args):
@ -985,13 +1038,15 @@ class SpotItem(QtGui.QGraphicsWidget):
def __init__(self, size, pxMode, brush, pen, data): def __init__(self, size, pxMode, brush, pen, data):
QtGui.QGraphicsWidget.__init__(self) QtGui.QGraphicsWidget.__init__(self)
if pxMode: if pxMode:
self.setCacheMode(self.DeviceCoordinateCache)
self.setFlags(self.flags() | self.ItemIgnoresTransformations) self.setFlags(self.flags() | self.ItemIgnoresTransformations)
#self.setCacheMode(self.DeviceCoordinateCache) ## causes crash on linux #self.setCacheMode(self.DeviceCoordinateCache) ## causes crash on linux
self.pen = pen self.pen = pen
self.brush = brush self.brush = brush
self.path = QtGui.QPainterPath() self.path = QtGui.QPainterPath()
s2 = size/2. #s2 = size/2.
self.path.addEllipse(QtCore.QRectF(-s2, -s2, size, size)) self.path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))
self.scale(size, size)
self.data = data self.data = data
def setBrush(self, brush): def setBrush(self, brush):
@ -1036,6 +1091,7 @@ class SpotItem(QtGui.QGraphicsWidget):
class ROIPlotItem(PlotCurveItem): 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): def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None):
self.roi = roi self.roi = roi
self.roiData = data self.roiData = data
@ -1043,7 +1099,8 @@ class ROIPlotItem(PlotCurveItem):
self.axes = axes self.axes = axes
self.xVals = xVals self.xVals = xVals
PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color) 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() #self.roiChangedEvent()
def getRoiData(self): def getRoiData(self):
@ -1073,7 +1130,8 @@ class UIGraphicsItem(GraphicsObject):
self._viewRect = self._view().rect() self._viewRect = self._view().rect()
self._viewTransform = self.viewTransform() self._viewTransform = self.viewTransform()
self.setNewBounds() 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): def viewRect(self):
"""Return the viewport widget rect""" """Return the viewport widget rect"""
@ -1110,7 +1168,7 @@ class UIGraphicsItem(GraphicsObject):
self.bounds = self.viewTransform().inverted()[0].mapRect(bounds) self.bounds = self.viewTransform().inverted()[0].mapRect(bounds)
self.prepareGeometryChange() self.prepareGeometryChange()
def viewChangedEvent(self): def viewRangeChanged(self):
"""Called when the view widget is resized""" """Called when the view widget is resized"""
self.boundingRect() self.boundingRect()
self.update() self.update()
@ -1378,23 +1436,31 @@ class ScaleItem(QtGui.QGraphicsWidget):
def linkToView(self, view): def linkToView(self, view):
if self.orientation in ['right', 'left']: 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: 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) self.setRange(*newRange)
def boundingRect(self): 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()) return self.mapRectFromParent(self.geometry())
else: 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): def paint(self, p, opt, widget):
p.setPen(self.pen) p.setPen(self.pen)
@ -1402,10 +1468,10 @@ class ScaleItem(QtGui.QGraphicsWidget):
#bounds = self.boundingRect() #bounds = self.boundingRect()
bounds = self.mapRectFromParent(self.geometry()) 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 tbounds = bounds
else: else:
tbounds = self.mapRectFromScene(self.linkedView.mapRectToScene(self.linkedView.boundingRect())) tbounds = self.mapRectFromScene(self.linkedView().mapRectToScene(self.linkedView().boundingRect()))
if self.orientation == 'left': if self.orientation == 'left':
p.drawLine(bounds.topRight(), bounds.bottomRight()) p.drawLine(bounds.topRight(), bounds.bottomRight())
@ -1577,6 +1643,12 @@ class ScaleItem(QtGui.QGraphicsWidget):
class ViewBox(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.""" """Box that allows internal scaling/panning of children by mouse drag. Not compatible with GraphicsView having the same functionality."""
def __init__(self, parent=None): def __init__(self, parent=None):
QtGui.QGraphicsWidget.__init__(self, parent) QtGui.QGraphicsWidget.__init__(self, parent)
@ -1745,7 +1817,8 @@ class ViewBox(QtGui.QGraphicsWidget):
mask *= np.array([1, -1]) mask *= np.array([1, -1])
tr = dif*mask tr = dif*mask
self.translateBy(tr, viewCoords=True) 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() ev.accept()
elif ev.buttons() & QtCore.Qt.RightButton: elif ev.buttons() & QtCore.Qt.RightButton:
dif = ev.screenPos() - ev.lastScreenPos() dif = ev.screenPos() - ev.lastScreenPos()
@ -1755,7 +1828,8 @@ class ViewBox(QtGui.QGraphicsWidget):
#print mask, dif, s #print mask, dif, s
center = Point(self.childGroup.transform().inverted()[0].map(ev.buttonDownPos(QtCore.Qt.RightButton))) center = Point(self.childGroup.transform().inverted()[0].map(ev.buttonDownPos(QtCore.Qt.RightButton)))
self.scaleBy(s, center) 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() ev.accept()
else: else:
ev.ignore() ev.ignore()
@ -1802,8 +1876,10 @@ class ViewBox(QtGui.QGraphicsWidget):
self.range[1] = [min, max] self.range[1] = [min, max]
#self.ctrl.yMinText.setText('%g' % min) #self.ctrl.yMinText.setText('%g' % min)
#self.ctrl.yMaxText.setText('%g' % max) #self.ctrl.yMaxText.setText('%g' % max)
self.emit(QtCore.SIGNAL('yRangeChanged'), self, (min, max)) #self.emit(QtCore.SIGNAL('yRangeChanged'), self, (min, max))
self.emit(QtCore.SIGNAL('viewChanged'), self) self.sigYRangeChanged.emit(self, (min, max))
#self.emit(QtCore.SIGNAL('viewChanged'), self)
self.sigRangeChanged.emit(self, self.range)
if update: if update:
self.updateMatrix() self.updateMatrix()
@ -1827,8 +1903,10 @@ class ViewBox(QtGui.QGraphicsWidget):
self.range[0] = [min, max] self.range[0] = [min, max]
#self.ctrl.xMinText.setText('%g' % min) #self.ctrl.xMinText.setText('%g' % min)
#self.ctrl.xMaxText.setText('%g' % max) #self.ctrl.xMaxText.setText('%g' % max)
self.emit(QtCore.SIGNAL('xRangeChanged'), self, (min, max)) #self.emit(QtCore.SIGNAL('xRangeChanged'), self, (min, max))
self.emit(QtCore.SIGNAL('viewChanged'), self) self.sigXRangeChanged.emit(self, (min, max))
#self.emit(QtCore.SIGNAL('viewChanged'), self)
self.sigRangeChanged.emit(self, self.range)
if update: if update:
self.updateMatrix() self.updateMatrix()
@ -1852,6 +1930,11 @@ class ViewBox(QtGui.QGraphicsWidget):
class InfiniteLine(GraphicsObject): 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): def __init__(self, view, pos=0, angle=90, pen=None, movable=False, bounds=None):
GraphicsObject.__init__(self) GraphicsObject.__init__(self)
self.bounds = QtCore.QRectF() ## graphicsitem boundary self.bounds = QtCore.QRectF() ## graphicsitem boundary
@ -1877,7 +1960,8 @@ class InfiniteLine(GraphicsObject):
#self.setFlag(self.ItemSendsScenePositionChanges) #self.setFlag(self.ItemSendsScenePositionChanges)
#for p in self.getBoundingParents(): #for p in self.getBoundingParents():
#QtCore.QObject.connect(p, QtCore.SIGNAL('viewChanged'), self.updateLine) #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): def setMovable(self, m):
self.movable = m self.movable = m
@ -1935,7 +2019,8 @@ class InfiniteLine(GraphicsObject):
if self.p != newPos: if self.p != newPos:
self.p = newPos self.p = newPos
self.updateLine() self.updateLine()
self.emit(QtCore.SIGNAL('positionChanged'), self) #self.emit(QtCore.SIGNAL('positionChanged'), self)
self.sigPositionChanged.emit(self)
def getXPos(self): def getXPos(self):
return self.p[0] return self.p[0]
@ -2048,17 +2133,23 @@ class InfiniteLine(GraphicsObject):
def mouseMoveEvent(self, ev): def mouseMoveEvent(self, ev):
self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) 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 self.hasMoved = True
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
if self.hasMoved and ev.button() == QtCore.Qt.LeftButton: if self.hasMoved and ev.button() == QtCore.Qt.LeftButton:
self.hasMoved = False self.hasMoved = False
self.emit(QtCore.SIGNAL('positionChangeFinished'), self) #self.emit(QtCore.SIGNAL('positionChangeFinished'), self)
self.sigPositionChangeFinished.emit(self)
class LinearRegionItem(GraphicsObject): class LinearRegionItem(GraphicsObject):
sigRegionChangeFinished = QtCore.Signal(object)
sigRegionChanged = QtCore.Signal(object)
"""Used for marking a horizontal or vertical region in plots.""" """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): def __init__(self, view, orientation="vertical", vals=[0,1], brush=None, movable=True, bounds=None):
GraphicsObject.__init__(self) GraphicsObject.__init__(self)
@ -2080,12 +2171,15 @@ class LinearRegionItem(GraphicsObject):
self.lines = [ self.lines = [
InfiniteLine(view, QtCore.QPointF(vals[0], 0), 90, movable=movable, bounds=bounds), InfiniteLine(view, QtCore.QPointF(vals[0], 0), 90, movable=movable, bounds=bounds),
InfiniteLine(view, QtCore.QPointF(vals[1], 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: for l in self.lines:
l.setParentItem(self) l.setParentItem(self)
l.connect(l, QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished) #l.connect(l, QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished)
l.connect(l, QtCore.SIGNAL('positionChanged'), self.lineMoved) l.sigPositionChangeFinished.connect(self.lineMoveFinished)
#l.connect(l, QtCore.SIGNAL('positionChanged'), self.lineMoved)
l.sigPositionChanged.connect(self.lineMoved)
if brush is None: if brush is None:
brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50)) brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50))
@ -2106,10 +2200,12 @@ class LinearRegionItem(GraphicsObject):
def lineMoved(self): def lineMoved(self):
self.updateBounds() self.updateBounds()
self.emit(QtCore.SIGNAL('regionChanged'), self) #self.emit(QtCore.SIGNAL('regionChanged'), self)
self.sigRegionChanged.emit(self)
def lineMoveFinished(self): def lineMoveFinished(self):
self.emit(QtCore.SIGNAL('regionChangeFinished'), self) #self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
self.sigRegionChangeFinished.emit(self)
def updateBounds(self): def updateBounds(self):
@ -2200,11 +2296,13 @@ class VTickGroup(QtGui.QGraphicsPathItem):
if self.view is not None: if self.view is not None:
if relative: if relative:
#QtCore.QObject.connect(self.view, QtCore.SIGNAL('viewChanged'), self.rebuildTicks) #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: else:
try: try:
#QtCore.QObject.disconnect(self.view, QtCore.SIGNAL('viewChanged'), self.rebuildTicks) #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: except:
pass pass
self.rebuildTicks() self.rebuildTicks()
@ -2268,6 +2366,8 @@ class VTickGroup(QtGui.QGraphicsPathItem):
class GridItem(UIGraphicsItem): 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): def __init__(self, view, bounds=None, *args):
UIGraphicsItem.__init__(self, view, bounds) UIGraphicsItem.__init__(self, view, bounds)
#QtGui.QGraphicsItem.__init__(self, *args) #QtGui.QGraphicsItem.__init__(self, *args)
@ -2277,9 +2377,9 @@ class GridItem(UIGraphicsItem):
self.picture = None self.picture = None
def viewChangedEvent(self): def viewRangeChanged(self):
self.picture = None self.picture = None
UIGraphicsItem.viewChangedEvent(self) UIGraphicsItem.viewRangeChanged(self)
#self.update() #self.update()
def paint(self, p, opt, widget): def paint(self, p, opt, widget):
@ -2417,7 +2517,20 @@ class ColorScaleBar(UIGraphicsItem):
self.gradient = g self.gradient = g
self.update() 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): def setLabels(self, l):
"""Defines labels to appear next to the color scale"""
self.labels = l self.labels = l
self.update() self.update()
@ -2429,7 +2542,7 @@ class ColorScaleBar(UIGraphicsItem):
labelWidth = 0 labelWidth = 0
labelHeight = 0 labelHeight = 0
for k in self.labels: 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()) labelWidth = max(labelWidth, b.width())
labelHeight = max(labelHeight, b.height()) labelHeight = max(labelHeight, b.height())
@ -2484,6 +2597,6 @@ class ColorScaleBar(UIGraphicsItem):
lh = labelHeight/unit.height() lh = labelHeight/unit.height()
for k in self.labels: for k in self.labels:
y = y1 + self.labels[k] * (y2-y1) 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 import scipy.ndimage as ndimage
from Point import * from Point import *
from math import cos, sin from math import cos, sin
from ObjectWorkaround import * import functions as fn
#from ObjectWorkaround import *
def rectStr(r): def rectStr(r):
return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height()) 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) #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): 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) #QObjectWorkaround.__init__(self)
QtGui.QGraphicsItem.__init__(self, parent) QtGui.QGraphicsObject.__init__(self, parent)
pos = Point(pos) pos = Point(pos)
size = Point(size) size = Point(size)
self.aspectLocked = False self.aspectLocked = False
@ -45,7 +51,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
if pen is None: if pen is None:
self.pen = QtGui.QPen(QtGui.QColor(255, 255, 255)) self.pen = QtGui.QPen(QtGui.QColor(255, 255, 255))
else: else:
self.pen = pen self.pen = fn.mkPen(pen)
self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255)) self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255))
self.handles = [] self.handles = []
self.state = {'pos': pos, 'size': size, 'angle': angle} self.state = {'pos': pos, 'size': size, 'angle': angle}
@ -212,7 +218,8 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.isMoving = True self.isMoving = True
self.preMoveState = self.getState() self.preMoveState = self.getState()
self.cursorOffset = self.scenePos() - ev.scenePos() self.cursorOffset = self.scenePos() - ev.scenePos()
self.emit(QtCore.SIGNAL('regionChangeStarted'), self) #self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
self.sigRegionChangeStarted.emit(self)
ev.accept() ev.accept()
elif ev.button() == QtCore.Qt.RightButton: elif ev.button() == QtCore.Qt.RightButton:
if self.isMoving: if self.isMoving:
@ -236,7 +243,8 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
if self.translatable: if self.translatable:
self.isMoving = False self.isMoving = False
self.emit(QtCore.SIGNAL('regionChangeFinished'), self) #self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
self.sigRegionChangeFinished.emit(self)
def cancelMove(self): def cancelMove(self):
self.isMoving = False self.isMoving = False
@ -247,14 +255,16 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.isMoving = True self.isMoving = True
self.preMoveState = self.getState() 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.pressPos = self.mapFromScene(ev.scenePos())
#self.pressHandlePos = self.handles[pt]['item'].pos() #self.pressHandlePos = self.handles[pt]['item'].pos()
def pointReleaseEvent(self, pt, ev): def pointReleaseEvent(self, pt, ev):
#print "release" #print "release"
self.isMoving = False self.isMoving = False
self.emit(QtCore.SIGNAL('regionChangeFinished'), self) #self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
self.sigRegionChangeFinished.emit(self)
def stateCopy(self): def stateCopy(self):
sc = {} sc = {}
@ -316,7 +326,8 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
elif h['type'] == 'f': elif h['type'] == 'f':
h['item'].setPos(self.mapFromScene(pos)) 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': elif h['type'] == 's':
#c = h['center'] #c = h['center']
@ -509,7 +520,8 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
if changed: if changed:
#print "handle changed." #print "handle changed."
self.update() 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]): def scale(self, s, center=[0,0]):
@ -628,104 +640,116 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
def getArrayRegion(self, data, img, axes=(0,1)): def getArrayRegion(self, data, img, axes=(0,1)):
shape = self.state['size']
## transpose data so x and y are the first 2 axes origin = self.mapToItem(img, QtCore.QPointF(0, 0))
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 vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin
(dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes) 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 #print "shape", shape, "vectors", vectors, "origin", origin
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 return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, order=1)
arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]]
## Update roiDataTransform to reflect this extraction ### transpose data so x and y are the first 2 axes
roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0]) #trAx = range(0, data.ndim)
### (roiDataTransform now maps from ROI coords to extracted data coords) #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 ### Rotate array
if abs(self.state['angle']) > 1e-5: #if abs(self.state['angle']) > 1e-5:
arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1) #arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1)
## update data transforms to reflect this rotation ### update data transforms to reflect this rotation
rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi) #rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi)
roiDataTransform *= rot #roiDataTransform *= rot
## The rotation also causes a shift which must be accounted for: ### The rotation also causes a shift which must be accounted for:
dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1]) #dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1])
rotBound = rot.mapRect(dataBound) #rotBound = rot.mapRect(dataBound)
roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top()) #roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top())
else: #else:
arr2 = arr1 #arr2 = arr1
### Shift off partial pixels #### Shift off partial pixels
# 1. map ROI into current data space ## 1. map ROI into current data space
roiBounds = roiDataTransform.mapRect(self.boundingRect()) #roiBounds = roiDataTransform.mapRect(self.boundingRect())
# 2. Determine amount to shift data ## 2. Determine amount to shift data
shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom()) #shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom())
if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6: #if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6:
# 3. pad array with 0s before shifting ## 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 = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype)
arr2a[1:-1, 1:-1] = arr2 #arr2a[1:-1, 1:-1] = arr2
# 4. shift array and udpate transforms ## 4. shift array and udpate transforms
arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1) #arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1)
roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1]) #roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1])
else: #else:
arr3 = arr2 #arr3 = arr2
### Extract needed region from rotated/shifted array #### 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) ## 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()) #roiBounds = roiDataTransform.mapRect(self.boundingRect())
#print self, roiBounds.height() ##print self, roiBounds.height()
#import traceback ##import traceback
#traceback.print_stack() ##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 ##2. intersect ROI with data bounds
dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1])) #dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1]))
#3. Extract data from array ##3. Extract data from array
db = dataBounds #db = dataBounds
bounds = ( #bounds = (
(db.left(), db.right()+1), #(db.left(), db.right()+1),
(db.top(), db.bottom()+1) #(db.top(), db.bottom()+1)
) #)
arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]] #arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]]
### Create zero array in size of ROI #### Create zero array in size of ROI
arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype) #arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype)
## Fill array with ROI data ### Fill array with ROI data
orig = Point(dataBounds.topLeft() - roiBounds.topLeft()) #orig = Point(dataBounds.topLeft() - roiBounds.topLeft())
subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]] #subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]]
subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]] #subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]]
## figure out the reverse transpose order ### figure out the reverse transpose order
tr2 = np.array(tr1) #tr2 = np.array(tr1)
for i in range(0, len(tr2)): #for i in range(0, len(tr2)):
tr2[tr1[i]] = i #tr2[tr1[i]] = i
tr2 = tuple(tr2) #tr2 = tuple(tr2)
## Untranspose array before returning ### Untranspose array before returning
return arr5.transpose(tr2) #return arr5.transpose(tr2)
@ -887,10 +911,14 @@ class LineROI(ROI):
self.addScaleHandle([0.5, 1], [0.5, 0.5]) 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): def __init__(self, points, width, pen=None, **args):
QObjectWorkaround.__init__(self) QtGui.QGraphicsObject.__init__(self)
QtGui.QGraphicsItem.__init__(self)
self.pen = pen self.pen = pen
self.roiArgs = args self.roiArgs = args
if len(points) < 2: if len(points) < 2:
@ -912,9 +940,12 @@ class MultiLineROI(QtGui.QGraphicsItem, QObjectWorkaround):
for l in self.lines: for l in self.lines:
l.translatable = False l.translatable = False
#self.addToGroup(l) #self.addToGroup(l)
l.connect(l, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent) #l.connect(l, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent)
l.connect(l, QtCore.SIGNAL('regionChangeStarted'), self.roiChangeStartedEvent) l.sigRegionChanged.connect(self.roiChangedEvent)
l.connect(l, QtCore.SIGNAL('regionChangeFinished'), self.roiChangeFinishedEvent) #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): def paint(self, *args):
pass pass
@ -927,13 +958,16 @@ class MultiLineROI(QtGui.QGraphicsItem, QObjectWorkaround):
for l in self.lines[1:]: for l in self.lines[1:]:
w0 = l.state['size'][1] w0 = l.state['size'][1]
l.scale([1.0, w/w0], center=[0.5,0.5]) 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): def roiChangeStartedEvent(self):
self.emit(QtCore.SIGNAL('regionChangeStarted'), self) #self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
self.sigRegionChangeStarted.emit(self)
def roiChangeFinishedEvent(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): def getArrayRegion(self, arr, img=None):
@ -1038,6 +1072,57 @@ class PolygonROI(ROI):
#sc['handles'] = self.handles #sc['handles'] = self.handles
return sc 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): class SpiralROI(ROI):
def __init__(self, pos=None, size=None, **args): def __init__(self, pos=None, size=None, **args):
if size == None: if size == None: