Numerous fixes

added scatter plots
This commit is contained in:
Luke Campagnola 2010-11-21 22:50:04 -05:00
parent dbcdf7ec91
commit 661e4411e2
14 changed files with 2667 additions and 148 deletions

View File

@ -163,10 +163,16 @@ class TickSlider(QtGui.QGraphicsView):
class GradientWidget(TickSlider): class GradientWidget(TickSlider):
def __init__(self, *args, **kargs): def __init__(self, *args, **kargs):
TickSlider.__init__(self, *args, **kargs) TickSlider.__init__(self, *args, **kargs)
self.currentTick = None
self.currentTickColor = None
self.rectSize = 15 self.rectSize = 15
self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize)) self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize))
self.colorMode = 'rgb' self.colorMode = 'rgb'
self.colorDialog = QtGui.QColorDialog()
self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True)
QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged)
QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected)
#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)
@ -200,16 +206,27 @@ class GradientWidget(TickSlider):
self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize) self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize)
self.updateGradient() self.updateGradient()
def currentColorChanged(self, color):
if color.isValid() and self.currentTick is not None:
self.setTickColor(self.currentTick, color)
self.updateGradient()
def currentColorRejected(self):
self.setTickColor(self.currentTick, self.currentTickColor)
self.updateGradient()
def tickClicked(self, tick, ev): def tickClicked(self, tick, ev):
if ev.button() == QtCore.Qt.LeftButton: if ev.button() == QtCore.Qt.LeftButton:
if not tick.colorChangeAllowed: if not tick.colorChangeAllowed:
return return
color = QtGui.QColorDialog.getColor(tick.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) self.currentTick = tick
if color.isValid(): self.currentTickColor = tick.color
self.setTickColor(tick, color) self.colorDialog.setCurrentColor(tick.color)
self.updateGradient() self.colorDialog.open()
#color = QtGui.QColorDialog.getColor(tick.color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
#if color.isValid():
#self.setTickColor(tick, color)
#self.updateGradient()
elif ev.button() == QtCore.Qt.RightButton: elif ev.button() == QtCore.Qt.RightButton:
if not tick.removeAllowed: if not tick.removeAllowed:
return return
@ -267,7 +284,8 @@ class GradientWidget(TickSlider):
r = c1.red() * (1.-f) + c2.red() * f r = c1.red() * (1.-f) + c2.red() * f
g = c1.green() * (1.-f) + c2.green() * f g = c1.green() * (1.-f) + c2.green() * f
b = c1.blue() * (1.-f) + c2.blue() * f b = c1.blue() * (1.-f) + c2.blue() * f
return QtGui.QColor(r, g, b) a = c1.alpha() * (1.-f) + c2.alpha() * f
return QtGui.QColor(r, g, b,a)
elif self.colorMode == 'hsv': elif self.colorMode == 'hsv':
h1,s1,v1,_ = c1.getHsv() h1,s1,v1,_ = c1.getHsv()
h2,s2,v2,_ = c2.getHsv() h2,s2,v2,_ = c2.getHsv()
@ -292,6 +310,43 @@ class GradientWidget(TickSlider):
t.removeAllowed = True t.removeAllowed = True
return t return t
def saveState(self):
ticks = []
for t in self.ticks:
c = t.color
ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha())))
state = {'mode': self.colorMode, 'ticks': ticks}
return state
def restoreState(self, state):
self.setColorMode(state['mode'])
for t in self.ticks.keys():
self.removeTick(t)
for t in state['ticks']:
c = QtGui.QColor(*t[1])
self.addTick(t[0], c)
self.updateGradient()
class BlackWhiteSlider(GradientWidget):
def __init__(self, parent):
GradientWidget.__init__(self, parent)
self.getTick(0).colorChangeAllowed = False
self.getTick(1).colorChangeAllowed = False
self.allowAdd = False
self.setTickColor(self.getTick(1), QtGui.QColor(255,255,255))
self.setOrientation('right')
def getLevels(self):
return (self.tickValue(0), self.tickValue(1))
def setLevels(self, black, white):
self.setTickValue(0, black)
self.setTickValue(1, white)
class GammaWidget(TickSlider): class GammaWidget(TickSlider):
pass pass
@ -362,3 +417,30 @@ class Tick(QtGui.QGraphicsPolygonItem):
if __name__ == '__main__':
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
w.show()
w.resize(400,400)
cw = QtGui.QWidget()
w.setCentralWidget(cw)
l = QtGui.QGridLayout()
l.setSpacing(0)
cw.setLayout(l)
w1 = GradientWidget(orientation='top')
w2 = GradientWidget(orientation='right', allowAdd=False)
w2.setTickColor(1, QtGui.QColor(255,255,255))
w3 = GradientWidget(orientation='bottom')
w4 = TickSlider(orientation='left')
l.addWidget(w1, 0, 1)
l.addWidget(w2, 1, 2)
l.addWidget(w3, 2, 1)
l.addWidget(w4, 1, 0)

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
from GradientWidget import *
from PyQt4 import QtGui
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
w.show()
w.resize(400,400)
cw = QtGui.QWidget()
w.setCentralWidget(cw)
l = QtGui.QGridLayout()
l.setSpacing(0)
cw.setLayout(l)
w1 = GradientWidget(orientation='top')
w2 = GradientWidget(orientation='right', allowAdd=False)
w2.setTickColor(1, QtGui.QColor(255,255,255))
w3 = GradientWidget(orientation='bottom')
w4 = TickSlider(orientation='left')
l.addWidget(w1, 0, 1)
l.addWidget(w2, 1, 2)
l.addWidget(w3, 2, 1)
l.addWidget(w4, 1, 0)

View File

@ -27,6 +27,8 @@ class GraphicsView(QtGui.QGraphicsView):
enabled via enableMouse().""" enabled via enableMouse()."""
QtGui.QGraphicsView.__init__(self, parent) QtGui.QGraphicsView.__init__(self, parent)
if 'linux' in sys.platform: ## linux has bugs in opengl implementation
useOpenGL = False
self.useOpenGL(useOpenGL) self.useOpenGL(useOpenGL)
palette = QtGui.QPalette() palette = QtGui.QPalette()
@ -139,6 +141,7 @@ 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)
if propagate: if propagate:
for v in self.lockedViewports: for v in self.lockedViewports:
@ -190,7 +193,6 @@ class GraphicsView(QtGui.QGraphicsView):
#print "New Range:", self.range #print "New Range:", self.range
self.centralWidget.setGeometry(self.range) self.centralWidget.setGeometry(self.range)
self.updateMatrix(propagate) self.updateMatrix(propagate)
self.emit(QtCore.SIGNAL('viewChanged'), self.range)
def lockXRange(self, v1): def lockXRange(self, v1):

View File

@ -18,8 +18,9 @@ from graphicsItems import *
from widgets import ROI from widgets import ROI
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
import sys import sys
from numpy import ndarray #from numpy import ndarray
import ptime import ptime
import numpy as np
from SignalProxy import proxyConnect from SignalProxy import proxyConnect
@ -52,7 +53,7 @@ class ImageView(QtGui.QWidget):
self.ui.graphicsView.invertY() self.ui.graphicsView.invertY()
self.ui.graphicsView.enableMouse() self.ui.graphicsView.enableMouse()
self. ticks = [t[0] for t in self.ui.gradientWidget.listTicks()] self.ticks = [t[0] for t in self.ui.gradientWidget.listTicks()]
self.ticks[0].colorChangeAllowed = False self.ticks[0].colorChangeAllowed = False
self.ticks[1].colorChangeAllowed = False self.ticks[1].colorChangeAllowed = False
self.ui.gradientWidget.allowAdd = False self.ui.gradientWidget.allowAdd = False
@ -301,14 +302,14 @@ class ImageView(QtGui.QWidget):
axes = (1, 2) axes = (1, 2)
else: else:
return return
data = self.roi.getArrayRegion(image.view(ndarray), self.imageItem, axes) data = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes)
if data is not None: if data is not None:
while data.ndim > 1: while data.ndim > 1:
data = data.mean(axis=1) data = data.mean(axis=1)
self.roiCurve.setData(y=data, x=self.tVals) self.roiCurve.setData(y=data, x=self.tVals)
#self.ui.roiPlot.replot() #self.ui.roiPlot.replot()
def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None): def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None):
"""Set the image to be displayed in the widget. """Set the image to be displayed in the widget.
Options are: Options are:
img: ndarray; the image to be displayed. img: ndarray; the image to be displayed.
@ -319,16 +320,19 @@ class ImageView(QtGui.QWidget):
This is only needed to override the default guess. This is only needed to override the default guess.
""" """
if not isinstance(img, 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
if xvals is not None: if xvals is not None:
self.tVals = xvals self.tVals = xvals
elif hasattr(img, 'xvals'): elif hasattr(img, 'xvals'):
try:
self.tVals = img.xvals(0) self.tVals = img.xvals(0)
except:
self.tVals = np.arange(img.shape[0])
else: else:
self.tVals = arange(img.shape[0]) self.tVals = np.arange(img.shape[0])
#self.ui.timeSlider.setValue(0) #self.ui.timeSlider.setValue(0)
#self.ui.normStartSlider.setValue(0) #self.ui.normStartSlider.setValue(0)
#self.ui.timeSlider.setMaximum(img.shape[0]-1) #self.ui.timeSlider.setMaximum(img.shape[0]-1)
@ -346,13 +350,13 @@ class ImageView(QtGui.QWidget):
self.imageDisp = None self.imageDisp = None
if autoRange:
self.autoRange()
if autoLevels: if autoLevels:
self.autoLevels() self.autoLevels()
if levels is not None: if levels is not None:
self.levelMax = levels[1] self.levelMax = levels[1]
self.levelMin = levels[0] self.levelMin = levels[0]
self.currentIndex = 0
self.updateImage() self.updateImage()
if self.ui.roiBtn.isChecked(): if self.ui.roiBtn.isChecked():
self.roiChanged() self.roiChanged()
@ -361,6 +365,7 @@ class ImageView(QtGui.QWidget):
if self.axes['t'] is not None: if self.axes['t'] is not None:
#self.ui.roiPlot.show() #self.ui.roiPlot.show()
self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max()) self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max())
self.timeLine.setValue(0)
#self.ui.roiPlot.setMouseEnabled(False, False) #self.ui.roiPlot.setMouseEnabled(False, False)
if len(self.tVals) > 1: if len(self.tVals) > 1:
start = self.tVals.min() start = self.tVals.min()
@ -376,6 +381,14 @@ class ImageView(QtGui.QWidget):
#else: #else:
#self.ui.roiPlot.hide() #self.ui.roiPlot.hide()
self.imageItem.resetTransform()
if scale is not None:
self.imageItem.scale(*scale)
if scale is not None:
self.imageItem.setPos(*pos)
if autoRange:
self.autoRange()
self.roiClicked() self.roiClicked()
@ -389,10 +402,12 @@ class ImageView(QtGui.QWidget):
self.ui.gradientWidget.setTickValue(self.ticks[1], 1.0) self.ui.gradientWidget.setTickValue(self.ticks[1], 1.0)
self.imageItem.setLevels(white=self.whiteLevel(), black=self.blackLevel()) self.imageItem.setLevels(white=self.whiteLevel(), black=self.blackLevel())
def autoRange(self): def autoRange(self):
image = self.getProcessedImage() image = self.getProcessedImage()
self.ui.graphicsView.setRange(QtCore.QRectF(0, 0, image.shape[self.axes['x']], image.shape[self.axes['y']]), padding=0., lockAspect=True) #self.ui.graphicsView.setRange(QtCore.QRectF(0, 0, image.shape[self.axes['x']], image.shape[self.axes['y']]), padding=0., lockAspect=True)
self.ui.graphicsView.setRange(self.imageItem.sceneBoundingRect(), padding=0., lockAspect=True)
def getProcessedImage(self): def getProcessedImage(self):
if self.imageDisp is None: if self.imageDisp is None:
@ -408,7 +423,7 @@ class ImageView(QtGui.QWidget):
return image return image
div = self.ui.normDivideRadio.isChecked() div = self.ui.normDivideRadio.isChecked()
norm = image.view(ndarray).copy() norm = image.view(np.ndarray).copy()
#if div: #if div:
#norm = ones(image.shape) #norm = ones(image.shape)
#else: #else:
@ -498,7 +513,7 @@ class ImageView(QtGui.QWidget):
return (0,0) return (0,0)
totTime = xv[-1] + (xv[-1]-xv[-2]) totTime = xv[-1] + (xv[-1]-xv[-2])
#t = f * totTime #t = f * totTime
inds = argwhere(xv < t) inds = np.argwhere(xv < t)
if len(inds) < 1: if len(inds) < 1:
return (0,t) return (0,t)
ind = inds[-1,0] ind = inds[-1,0]

2129
PIL_Fix/Image.py-1.7 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@ from PyQt4 import QtGui, QtCore, QtSvg
#from ObjectWorkaround import * #from ObjectWorkaround import *
#tryWorkaround(QtCore, QtGui) #tryWorkaround(QtCore, QtGui)
import weakref import weakref
import numpy as np
try: try:
from WidgetGroup import * from WidgetGroup import *
@ -37,8 +38,6 @@ except:
HAVE_METAARRAY = False HAVE_METAARRAY = False
class PlotItem(QtGui.QGraphicsWidget): class PlotItem(QtGui.QGraphicsWidget):
"""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
@ -132,6 +131,7 @@ class PlotItem(QtGui.QGraphicsWidget):
self.items = [] self.items = []
self.curves = [] self.curves = []
self.dataItems = []
self.paramList = {} self.paramList = {}
self.avgCurves = {} self.avgCurves = {}
@ -436,7 +436,7 @@ class PlotItem(QtGui.QGraphicsWidget):
self.vb.setMouseEnabled(*state) self.vb.setMouseEnabled(*state)
def xRangeChanged(self, _, range): def xRangeChanged(self, _, range):
if any(isnan(range)) or any(isinf(range)): if any(np.isnan(range)) or any(np.isinf(range)):
raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender()))) raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender())))
self.ctrl.xMinText.setText('%0.5g' % range[0]) self.ctrl.xMinText.setText('%0.5g' % range[0])
self.ctrl.xMaxText.setText('%0.5g' % range[1]) self.ctrl.xMaxText.setText('%0.5g' % range[1])
@ -455,7 +455,7 @@ class PlotItem(QtGui.QGraphicsWidget):
self.emit(QtCore.SIGNAL('xRangeChanged'), self, range) self.emit(QtCore.SIGNAL('xRangeChanged'), self, range)
def yRangeChanged(self, _, range): def yRangeChanged(self, _, range):
if any(isnan(range)) or any(isinf(range)): if any(np.isnan(range)) or any(np.isinf(range)):
raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender()))) raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender())))
self.ctrl.yMinText.setText('%0.5g' % range[0]) self.ctrl.yMinText.setText('%0.5g' % range[0])
self.ctrl.yMaxText.setText('%0.5g' % range[1]) self.ctrl.yMaxText.setText('%0.5g' % range[1])
@ -545,6 +545,9 @@ class PlotItem(QtGui.QGraphicsWidget):
if not item in self.items: if not item in self.items:
return return
self.items.remove(item) self.items.remove(item)
if item in self.dataItems:
self.dataItems.remove(item)
if item.scene() is not None: if item.scene() is not None:
self.vb.removeItem(item) self.vb.removeItem(item)
if item in self.curves: if item in self.curves:
@ -571,12 +574,12 @@ class PlotItem(QtGui.QGraphicsWidget):
params = {} params = {}
if HAVE_METAARRAY and isinstance(data, MetaArray): if HAVE_METAARRAY and isinstance(data, MetaArray):
curve = self._plotMetaArray(data, x=x) curve = self._plotMetaArray(data, x=x)
elif isinstance(data, ndarray): elif isinstance(data, np.ndarray):
curve = self._plotArray(data, x=x) curve = self._plotArray(data, x=x)
elif isinstance(data, list): elif isinstance(data, list):
if x is not None: if x is not None:
x = array(x) x = np.array(x)
curve = self._plotArray(array(data), x=x) curve = self._plotArray(np.array(data), x=x)
elif data is None: elif data is None:
curve = PlotCurveItem() curve = PlotCurveItem()
else: else:
@ -589,6 +592,10 @@ class PlotItem(QtGui.QGraphicsWidget):
return curve return curve
def addDataItem(self, item):
self.addItem(item)
self.dataItems.append(item)
def addCurve(self, c, params=None): def addCurve(self, c, params=None):
if params is None: if params is None:
params = {} params = {}
@ -622,7 +629,7 @@ class PlotItem(QtGui.QGraphicsWidget):
percentScale = [self.ctrl.xAutoPercentSpin.value(), self.ctrl.yAutoPercentSpin.value()][ax] * 0.01 percentScale = [self.ctrl.xAutoPercentSpin.value(), self.ctrl.yAutoPercentSpin.value()][ax] * 0.01
mn = None mn = None
mx = None mx = None
for c in self.curves + [c[1] for c in self.avgCurves.values()]: for c in self.curves + [c[1] for c in self.avgCurves.values()] + self.dataItems:
if not c.isVisible(): if not c.isVisible():
continue continue
cmn, cmx = c.getRange(ax, percentScale) cmn, cmx = c.getRange(ax, percentScale)
@ -630,7 +637,7 @@ class PlotItem(QtGui.QGraphicsWidget):
mn = cmn mn = cmn
if mx is None or cmx > mx: if mx is None or cmx > mx:
mx = cmx mx = cmx
if mn is None or mx is None or any(isnan([mn, mx])) or any(isinf([mn, mx])): if mn is None or mx is None or any(np.isnan([mn, mx])) or any(np.isinf([mn, mx])):
continue continue
if mn == mx: if mn == mx:
mn -= 1 mn -= 1
@ -1013,7 +1020,7 @@ class PlotItem(QtGui.QGraphicsWidget):
else: else:
xv = x xv = x
c = PlotCurveItem() c = PlotCurveItem()
c.setData(x=xv, y=arr.view(ndarray)) c.setData(x=xv, y=arr.view(np.ndarray))
if autoLabel: if autoLabel:
name = arr._info[0].get('name', None) name = arr._info[0].get('name', None)

View File

@ -20,7 +20,10 @@ class Point(QtCore.QPointF):
def __init__(self, *args): def __init__(self, *args):
if len(args) == 1: if len(args) == 1:
if hasattr(args[0], '__getitem__'): if isinstance(args[0], QtCore.QSizeF):
QtCore.QPointF.__init__(self, float(args[0].width()), float(args[0].height()))
return
elif hasattr(args[0], '__getitem__'):
QtCore.QPointF.__init__(self, float(args[0][0]), float(args[0][1])) QtCore.QPointF.__init__(self, float(args[0][0]), float(args[0][1]))
return return
elif type(args[0]) in [float, int]: elif type(args[0]) in [float, int]:
@ -31,6 +34,9 @@ class Point(QtCore.QPointF):
return return
QtCore.QPointF.__init__(self, *args) QtCore.QPointF.__init__(self, *args)
def __reduce__(self):
return (Point, (self.x(), self.y()))
def __getitem__(self, i): def __getitem__(self, i):
if i == 0: if i == 0:
return self.x() return self.x()

View File

@ -24,14 +24,17 @@ class SignalProxy(QtCore.QObject):
self.args = None self.args = None
self.timers = 0 self.timers = 0
self.signal = signal self.signal = signal
self.block = False
def setDelay(self, delay): def setDelay(self, delay):
self.delay = delay self.delay = delay
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: 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
@ -39,6 +42,8 @@ class SignalProxy(QtCore.QObject):
def signal(self, *args): def signal(self, *args):
"""Received signal, queue to be forwarded later.""" """Received signal, queue to be forwarded later."""
if self.block:
return
self.waitUntil = time() + self.delay self.waitUntil = time() + self.delay
self.args = args self.args = args
self.timers += 1 self.timers += 1
@ -46,7 +51,7 @@ class SignalProxy(QtCore.QObject):
def tryEmit(self): def tryEmit(self):
"""Emit signal if it has been long enougn since receiving the last signal.""" """Emit signal if it has been long enougn since receiving the last signal."""
if self.args is None: if self.args is None or self.block:
return False return False
self.timers -= 1 self.timers -= 1
t = time() t = time()
@ -59,4 +64,6 @@ class SignalProxy(QtCore.QObject):
return True return True
def disconnect(self):
self.block = True

View File

@ -14,6 +14,7 @@ app = QtGui.QApplication([])
## Create window with GraphicsView widget ## Create window with GraphicsView widget
win = QtGui.QMainWindow() win = QtGui.QMainWindow()
view = GraphicsView() view = GraphicsView()
#view.useOpenGL(True)
win.setCentralWidget(view) win.setCentralWidget(view)
win.show() win.show()

38
examples/test_draw.py Normal file
View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
## Add path to library (just for examples; you do not need this)
import sys, os
sys.path = [os.path.join(os.path.dirname(__file__), '..', '..')] + sys.path
from pyqtgraph.GraphicsView import *
from pyqtgraph.graphicsItems import *
#from numpy import random
from PyQt4 import QtCore, QtGui
from scipy.ndimage import *
import numpy as np
app = QtGui.QApplication([])
## Create window with GraphicsView widget
win = QtGui.QMainWindow()
view = GraphicsView()
#view.useOpenGL(True)
win.setCentralWidget(view)
win.show()
## Allow mouse scale/pan
view.enableMouse()
## ..But lock the aspect ratio
view.setAspectLocked(True)
## Create image item
img = ImageItem(np.zeros((200,200)))
view.scene().addItem(img)
## Set initial view bounds
view.setRange(QtCore.QRectF(0, 0, 200, 200))
img.setDrawKernel(1)
img.setLevels(10,0)
#app.exec_()

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from PyQt4 import QtGui, QtCore
from pyqtgraph.PlotWidget import *
from pyqtgraph.graphicsItems import *
app = QtGui.QApplication([])
mw = QtGui.QMainWindow()
cw = PlotWidget()
mw.setCentralWidget(cw)
mw.show()
#s1 = SpotItem(5, pxMode=True, brush=QtGui.QBrush(QtGui.QColor(0, 0, 200)), pen=QtGui.QPen(QtGui.QColor(100,100,100)))
#s1.setPos(1, 0)
#s2 = SpotItem(.1, pxMode=False, brush=QtGui.QBrush(QtGui.QColor(0, 200, 0)), pen=QtGui.QPen(QtGui.QColor(100,100,100)))
#s2.setPos(0, 1)
#cw.addItem(s1)
#cw.addItem(s2)
import numpy as np
s1 = ScatterPlotItem(size=10, pen=QtGui.QPen(QtCore.Qt.NoPen), brush=QtGui.QBrush(QtGui.QColor(255, 255, 255, 20)))
pos = np.random.normal(size=(2,3000))
spots = [{'pos': pos[:,i]} for i in range(3000)]
s1.addPoints(spots)
cw.addDataItem(s1)

View File

@ -11,7 +11,8 @@ Provides ImageItem, PlotCurveItem, and ViewBox, amongst others.
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
from ObjectWorkaround import * from ObjectWorkaround import *
#tryWorkaround(QtCore, QtGui) #tryWorkaround(QtCore, QtGui)
from numpy import * #from numpy import *
import numpy as np
try: try:
import scipy.weave as weave import scipy.weave as weave
from scipy.weave import converters from scipy.weave import converters
@ -171,10 +172,14 @@ class GraphicsObject(QGraphicsObject):
class ImageItem(QtGui.QGraphicsPixmapItem): class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
if 'linux' not in sys.platform: ## disable weave optimization on linux--broken there.
useWeave = True useWeave = True
else:
useWeave = False
def __init__(self, image=None, copy=True, parent=None, *args): def __init__(self, image=None, copy=True, parent=None, *args):
QObjectWorkaround.__init__(self)
self.qimage = QtGui.QImage() self.qimage = QtGui.QImage()
self.pixmap = None self.pixmap = None
#self.useWeave = True #self.useWeave = True
@ -183,6 +188,8 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
self.alpha = 1.0 self.alpha = 1.0
self.image = None self.image = None
self.clipLevel = None self.clipLevel = None
self.drawKernel = None
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:
@ -223,6 +230,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
self.blackLevel = black self.blackLevel = black
self.updateImage() self.updateImage()
def getLevels(self):
return self.whiteLevel, self.blackLevel
def updateImage(self, image=None, copy=True, autoRange=False, clipMask=None, white=None, black=None): def updateImage(self, image=None, copy=True, autoRange=False, clipMask=None, white=None, black=None):
axh = {'x': 0, 'y': 1, 'c': 2} axh = {'x': 0, 'y': 1, 'c': 2}
#print "Update image", black, white #print "Update image", black, white
@ -231,11 +241,12 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
if black is not None: if black is not None:
self.blackLevel = black self.blackLevel = black
gotNewData = False
if image is None: if image is None:
if self.image is None: if self.image is None:
return return
else: else:
gotNewData = True
if copy: if copy:
self.image = image.copy() self.image = image.copy()
else: else:
@ -261,9 +272,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
try: try:
if not ImageItem.useWeave: if not ImageItem.useWeave:
raise Exception('Skipping weave compile') raise Exception('Skipping weave compile')
sim = ascontiguousarray(self.image) sim = np.ascontiguousarray(self.image)
sim.shape = sim.size sim.shape = sim.size
im = zeros(sim.shape, dtype=ubyte) im = np.empty(sim.shape, dtype=np.ubyte)
n = im.size n = im.size
code = """ code = """
@ -287,15 +298,15 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
#print "==============================================================================" #print "=============================================================================="
print "Weave compile failed, falling back to slower version." print "Weave compile failed, falling back to slower version."
self.image.shape = shape self.image.shape = shape
im = ((self.image - black) * scale).clip(0.,255.).astype(ubyte) im = ((self.image - black) * scale).clip(0.,255.).astype(np.ubyte)
try: try:
im1 = empty((im.shape[axh['y']], im.shape[axh['x']], 4), dtype=ubyte) im1 = np.empty((im.shape[axh['y']], im.shape[axh['x']], 4), dtype=np.ubyte)
except: except:
print im.shape, axh print im.shape, axh
raise raise
alpha = clip(int(255 * self.alpha), 0, 255) alpha = np.clip(int(255 * self.alpha), 0, 255)
# Fill image # Fill image
if im.ndim == 2: if im.ndim == 2:
im2 = im.transpose(axh['y'], axh['x']) im2 = im.transpose(axh['y'], axh['x'])
@ -303,7 +314,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
im1[..., 1] = im2 im1[..., 1] = im2
im1[..., 2] = im2 im1[..., 2] = im2
im1[..., 3] = alpha im1[..., 3] = alpha
elif im.ndim == 3: elif im.ndim == 3: #color image
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']]):
@ -335,9 +346,42 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
self.setPixmap(self.pixmap) self.setPixmap(self.pixmap)
self.update() self.update()
if gotNewData:
self.emit(QtCore.SIGNAL('imageChanged'))
def getPixmap(self): def getPixmap(self):
return self.pixmap.copy() return self.pixmap.copy()
def getHistogram(self, bins=500, step=3):
"""returns an x and y arrays containing the histogram values for the current image.
The step argument causes pixels to be skipped when computing the histogram to save time."""
stepData = self.image[::step, ::step]
hist = np.histogram(stepData, bins=bins)
return hist[1][:-1], hist[0]
def mousePressEvent(self, ev):
if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton:
self.drawAt(ev.pos())
ev.accept()
else:
ev.ignore()
def mouseMoveEvent(self, ev):
#print "mouse move", ev.pos()
if self.drawKernel is not None:
self.drawAt(ev.pos())
def mouseReleaseEvent(self, ev):
pass
def drawAt(self, pos):
self.image[int(pos.x()), int(pos.y())] += 1
self.updateImage()
def setDrawKernel(self, kernel=None):
self.drawKernel = kernel
class PlotCurveItem(GraphicsObject): class PlotCurveItem(GraphicsObject):
@ -376,9 +420,13 @@ class PlotCurveItem(GraphicsObject):
if self.xData is None: if self.xData is None:
return (None, None) return (None, None)
if self.xDisp is None: if self.xDisp is None:
nanMask = isnan(self.xData) | isnan(self.yData) nanMask = np.isnan(self.xData) | np.isnan(self.yData)
if any(nanMask):
x = self.xData[~nanMask] x = self.xData[~nanMask]
y = self.yData[~nanMask] y = self.yData[~nanMask]
else:
x = self.xData
y = self.yData
ds = self.opts['downsample'] ds = self.opts['downsample']
if ds > 1: if ds > 1:
x = x[::ds] x = x[::ds]
@ -387,11 +435,11 @@ class PlotCurveItem(GraphicsObject):
f = fft(y) / len(y) f = fft(y) / len(y)
y = abs(f[1:len(f)/2]) y = abs(f[1:len(f)/2])
dt = x[-1] - x[0] dt = x[-1] - x[0]
x = linspace(0, 0.5*len(x)/dt, len(y)) x = np.linspace(0, 0.5*len(x)/dt, len(y))
if self.opts['logMode'][0]: if self.opts['logMode'][0]:
x = log10(x) x = np.log10(x)
if self.opts['logMode'][1]: if self.opts['logMode'][1]:
y = log10(y) y = np.log10(y)
self.xDisp = x self.xDisp = x
self.yDisp = y self.yDisp = y
#print self.yDisp.shape, self.yDisp.min(), self.yDisp.max() #print self.yDisp.shape, self.yDisp.min(), self.yDisp.max()
@ -494,10 +542,10 @@ class PlotCurveItem(GraphicsObject):
def updateData(self, data, x=None, copy=False): def updateData(self, data, x=None, copy=False):
if isinstance(data, list): if isinstance(data, list):
data = array(data) data = np.array(data)
if isinstance(x, list): if isinstance(x, list):
x = array(x) x = np.array(x)
if not isinstance(data, ndarray) or data.ndim > 2: if not isinstance(data, np.ndarray) or data.ndim > 2:
raise Exception("Plot data must be 1 or 2D ndarray (data shape is %s)" % str(data.shape)) raise Exception("Plot data must be 1 or 2D ndarray (data shape is %s)" % str(data.shape))
if data.ndim == 2: ### If data is 2D array, then assume x and y values are in first two columns or rows. if data.ndim == 2: ### If data is 2D array, then assume x and y values are in first two columns or rows.
if x is not None: if x is not None:
@ -542,9 +590,9 @@ class PlotCurveItem(GraphicsObject):
## Create all vertices in path. The method used below creates a binary format so that all ## Create all vertices in path. The method used below creates a binary format so that all
## vertices can be read in at once. This binary format may change in future versions of Qt, ## vertices can be read in at once. This binary format may change in future versions of Qt,
## so the original (slower) method is left here for emergencies: ## so the original (slower) method is left here for emergencies:
#self.path.moveTo(x[0], y[0]) #path.moveTo(x[0], y[0])
#for i in range(1, y.shape[0]): #for i in range(1, y.shape[0]):
#self.path.lineTo(x[i], y[i]) # path.lineTo(x[i], y[i])
## Speed this up using >> operator ## Speed this up using >> operator
## Format is: ## Format is:
@ -558,7 +606,7 @@ class PlotCurveItem(GraphicsObject):
# #
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 = empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')])
# 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
@ -644,6 +692,112 @@ class PlotCurveItem(GraphicsObject):
#del self.xData, self.yData, self.xDisp, self.yDisp, self.path #del self.xData, self.yData, self.xDisp, self.yDisp, self.path
class ScatterPlotItem(QtGui.QGraphicsItem):
def __init__(self, spots=None, pxMode=True, pen=None, brush=None, size=5):
QtGui.QGraphicsItem.__init__(self)
self.spots = []
self.range = [[0,0], [0,0]]
if brush is None:
brush = QtGui.QBrush(QtGui.QColor(100, 100, 150))
self.brush = brush
if pen is None:
pen = QtGui.QPen(QtGui.QColor(200, 200, 200))
self.pen = pen
self.size = size
self.pxMode = pxMode
if spots is not None:
self.setPoints(spots)
def setPxMode(self, mode):
self.pxMode = mode
def clear(self):
for i in self.spots:
i.setParentItem(None)
s = i.scene()
if s is not None:
s.removeItem(i)
self.spots = []
def getRange(self, ax, percent):
return self.range[ax]
def setPoints(self, spots):
self.clear()
self.range = [[0,0],[0,0]]
self.addPoints(spots)
def addPoints(self, spots):
xmn = ymn = xmx = ymx = None
for s in spots:
pos = Point(s['pos'])
size = s.get('size', self.size)
brush = s.get('brush', self.brush)
pen = s.get('pen', self.pen)
pen.setCosmetic(True)
item = self.mkSpot(pos, size, self.pxMode, brush, pen)
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]]
def mkSpot(self, pos, size, pxMode, brush, pen):
item = SpotItem(size, pxMode, brush, pen)
item.setParentItem(self)
item.setPos(pos)
return item
def boundingRect(self):
((xmn, xmx), (ymn, ymx)) = self.range
return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
def paint(self, p, *args):
pass
class SpotItem(QtGui.QGraphicsItem):
def __init__(self, size, pxMode, brush, pen):
QtGui.QGraphicsItem.__init__(self)
if pxMode:
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
#self.setCacheMode(self.DeviceCoordinateCache) ## causes crash on linux
self.pen = pen
self.brush = brush
self.path = QtGui.QPainterPath()
s2 = size/2.
self.path.addEllipse(QtCore.QRectF(-s2, -s2, size, size))
def boundingRect(self):
return self.path.boundingRect()
def shape(self):
return self.path
def paint(self, p, *opts):
p.setPen(self.pen)
p.setBrush(self.brush)
p.drawPath(self.path)
class ROIPlotItem(PlotCurveItem): class ROIPlotItem(PlotCurveItem):
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
@ -978,7 +1132,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
self.update() self.update()
def setRange(self, mn, mx): def setRange(self, mn, mx):
if mn in [nan, inf, -inf] or mx in [nan, inf, -inf]: if mn in [np.nan, np.inf, -np.inf] or mx in [np.nan, np.inf, -np.inf]:
raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx))) raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx)))
self.range = [mn, mx] self.range = [mn, mx]
if self.autoScale: if self.autoScale:
@ -1049,7 +1203,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
if dif == 0.0: if dif == 0.0:
return return
#print "dif:", dif #print "dif:", dif
pw = 10 ** (floor(log10(dif))-1) pw = 10 ** (np.floor(np.log10(dif))-1)
for i in range(len(intervals)): for i in range(len(intervals)):
i1 = i i1 = i
if dif / (pw*intervals[i]) < 10: if dif / (pw*intervals[i]) < 10:
@ -1075,7 +1229,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
sp = pw*intervals[i] sp = pw*intervals[i]
## determine starting tick ## determine starting tick
start = ceil(self.range[0] / sp) * sp start = np.ceil(self.range[0] / sp) * sp
## determine number of ticks ## determine number of ticks
num = int(dif / sp) + 1 num = int(dif / sp) + 1
@ -1085,7 +1239,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
## Number of decimal places to print ## Number of decimal places to print
maxVal = max(abs(start), abs(last)) maxVal = max(abs(start), abs(last))
places = max(0, 1-int(log10(sp*self.scale))) places = max(0, 1-int(np.log10(sp*self.scale)))
## length of tick ## length of tick
h = min(self.tickLength, (self.tickLength*3 / num) - 1.) h = min(self.tickLength, (self.tickLength*3 / num) - 1.)
@ -1292,11 +1446,11 @@ class ViewBox(QtGui.QGraphicsWidget):
yd = pr[1][1] - pr[1][0] yd = pr[1][1] - pr[1][0]
if xd == 0 or yd == 0: if xd == 0 or yd == 0:
print "Warning: 0 range in view:", xd, yd print "Warning: 0 range in view:", xd, yd
return array([1,1]) return np.array([1,1])
#cs = self.canvas().size() #cs = self.canvas().size()
cs = self.boundingRect() cs = self.boundingRect()
scale = array([cs.width() / xd, cs.height() / yd]) scale = np.array([cs.width() / xd, cs.height() / yd])
#print "view scale:", scale #print "view scale:", scale
return scale return scale
@ -1324,7 +1478,7 @@ class ViewBox(QtGui.QGraphicsWidget):
#print self.range #print self.range
def translateBy(self, t, viewCoords=False): def translateBy(self, t, viewCoords=False):
t = t.astype(float) t = t.astype(np.float)
#print "translate:", t, self.viewScale() #print "translate:", t, self.viewScale()
if viewCoords: ## scale from pixels if viewCoords: ## scale from pixels
t /= self.viewScale() t /= self.viewScale()
@ -1339,25 +1493,25 @@ class ViewBox(QtGui.QGraphicsWidget):
def mouseMoveEvent(self, ev): def mouseMoveEvent(self, ev):
pos = array([ev.pos().x(), ev.pos().y()]) pos = np.array([ev.pos().x(), ev.pos().y()])
dif = pos - self.mousePos dif = pos - self.mousePos
dif *= -1 dif *= -1
self.mousePos = pos self.mousePos = pos
## Ignore axes if mouse is disabled ## Ignore axes if mouse is disabled
mask = array(self.mouseEnabled, dtype=float) mask = np.array(self.mouseEnabled, dtype=np.float)
## Scale or translate based on mouse button ## Scale or translate based on mouse button
if ev.buttons() & QtCore.Qt.LeftButton: if ev.buttons() & QtCore.Qt.LeftButton:
if not self.yInverted: if not self.yInverted:
mask *= 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)
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()
dif = array([dif.x(), dif.y()]) dif = np.array([dif.x(), dif.y()])
dif[0] *= -1 dif[0] *= -1
s = ((mask * 0.02) + 1) ** dif s = ((mask * 0.02) + 1) ** dif
#print mask, dif, s #print mask, dif, s
@ -1369,12 +1523,12 @@ class ViewBox(QtGui.QGraphicsWidget):
ev.ignore() ev.ignore()
def mousePressEvent(self, ev): def mousePressEvent(self, ev):
self.mousePos = array([ev.pos().x(), ev.pos().y()]) self.mousePos = np.array([ev.pos().x(), ev.pos().y()])
self.pressPos = self.mousePos.copy() self.pressPos = self.mousePos.copy()
ev.accept() ev.accept()
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
pos = array([ev.pos().x(), ev.pos().y()]) pos = np.array([ev.pos().x(), ev.pos().y()])
#if sum(abs(self.pressPos - pos)) < 3: ## Detect click #if sum(abs(self.pressPos - pos)) < 3: ## Detect click
#if ev.button() == QtCore.Qt.RightButton: #if ev.button() == QtCore.Qt.RightButton:
#self.ctrlMenu.popup(self.mapToGlobal(ev.pos())) #self.ctrlMenu.popup(self.mapToGlobal(ev.pos()))
@ -1396,7 +1550,7 @@ class ViewBox(QtGui.QGraphicsWidget):
min -= dy*0.5 min -= dy*0.5
max += dy*0.5 max += dy*0.5
#raise Exception("Tried to set range with 0 width.") #raise Exception("Tried to set range with 0 width.")
if any(isnan([min, max])) or any(isinf([min, max])): if any(np.isnan([min, max])) or any(np.isinf([min, max])):
raise Exception("Not setting range [%s, %s]" % (str(min), str(max))) raise Exception("Not setting range [%s, %s]" % (str(min), str(max)))
padding = (max-min) * padding padding = (max-min) * padding
@ -1422,7 +1576,7 @@ class ViewBox(QtGui.QGraphicsWidget):
max += dx*0.5 max += dx*0.5
#print "Warning: Tried to set range with 0 width." #print "Warning: Tried to set range with 0 width."
#raise Exception("Tried to set range with 0 width.") #raise Exception("Tried to set range with 0 width.")
if any(isnan([min, max])) or any(isinf([min, max])): if any(np.isnan([min, max])) or any(np.isinf([min, max])):
raise Exception("Not setting range [%s, %s]" % (str(min), str(max))) raise Exception("Not setting range [%s, %s]" % (str(min), str(max)))
padding = (max-min) * padding padding = (max-min) * padding
min -= padding min -= padding
@ -1474,6 +1628,7 @@ class InfiniteLine(GraphicsObject):
if movable: if movable:
self.setAcceptHoverEvents(True) self.setAcceptHoverEvents(True)
self.hasMoved = False
if pen is None: if pen is None:
@ -1585,13 +1740,13 @@ class InfiniteLine(GraphicsObject):
#print 'before', self.bounds #print 'before', self.bounds
if self.angle > 45: if self.angle > 45:
m = tan((90-self.angle) * pi / 180.) m = np.tan((90-self.angle) * np.pi / 180.)
y2 = vr.bottom() y2 = vr.bottom()
y1 = vr.top() y1 = vr.top()
x1 = self.p[0] + (y1 - self.p[1]) * m x1 = self.p[0] + (y1 - self.p[1]) * m
x2 = self.p[0] + (y2 - self.p[1]) * m x2 = self.p[0] + (y2 - self.p[1]) * m
else: else:
m = tan(self.angle * pi / 180.) m = np.tan(self.angle * np.pi / 180.)
x1 = vr.left() x1 = vr.left()
x2 = vr.right() x2 = vr.right()
y2 = self.p[1] + (x1 - self.p[0]) * m y2 = self.p[1] + (x1 - self.p[0]) * m
@ -1650,12 +1805,18 @@ 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.hasMoved = True
def mouseReleaseEvent(self, ev):
if self.hasMoved and ev.button() == QtCore.Qt.LeftButton:
self.hasMoved = False
self.emit(QtCore.SIGNAL('positionChangeFinished'), self)
class LinearRegionItem(GraphicsObject): class LinearRegionItem(GraphicsObject):
"""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="horizontal", 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)
self.orientation = orientation self.orientation = orientation
if hasattr(self, "ItemHasNoContents"): if hasattr(self, "ItemHasNoContents"):
@ -1680,6 +1841,7 @@ class LinearRegionItem(GraphicsObject):
for l in self.lines: for l in self.lines:
l.setParentItem(self) l.setParentItem(self)
l.connect(QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished)
l.connect(QtCore.SIGNAL('positionChanged'), self.lineMoved) l.connect(QtCore.SIGNAL('positionChanged'), self.lineMoved)
if brush is None: if brush is None:
@ -1698,12 +1860,16 @@ class LinearRegionItem(GraphicsObject):
self.updateBounds() self.updateBounds()
self.emit(QtCore.SIGNAL('regionChanged'), self) self.emit(QtCore.SIGNAL('regionChanged'), self)
def lineMoveFinished(self):
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
def updateBounds(self): def updateBounds(self):
vb = self.view().viewRect() vb = self.view().viewRect()
vals = [self.lines[0].value(), self.lines[1].value()] vals = [self.lines[0].value(), self.lines[1].value()]
if self.orientation[0] == 'h': if self.orientation[0] == 'h':
vb.setTop(max(vals)) vb.setTop(min(vals))
vb.setBottom(min(vals)) vb.setBottom(max(vals))
else: else:
vb.setLeft(min(vals)) vb.setLeft(min(vals))
vb.setRight(max(vals)) vb.setRight(max(vals))
@ -1713,14 +1879,19 @@ class LinearRegionItem(GraphicsObject):
def mousePressEvent(self, ev): def mousePressEvent(self, ev):
for l in self.lines: for l in self.lines:
l.mousePressEvent(ev) l.mousePressEvent(ev) ## pass event to both lines so they move together
#if self.movable and ev.button() == QtCore.Qt.LeftButton: #if self.movable and ev.button() == QtCore.Qt.LeftButton:
#ev.accept() #ev.accept()
#self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) #self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p)
#else: #else:
#ev.ignore() #ev.ignore()
def mouseReleaseEvent(self, ev):
for l in self.lines:
l.mouseReleaseEvent(ev)
def mouseMoveEvent(self, ev): def mouseMoveEvent(self, ev):
#print "move", ev.pos()
self.lines[0].blockSignals(True) # only want to update once self.lines[0].blockSignals(True) # only want to update once
for l in self.lines: for l in self.lines:
l.mouseMoveEvent(ev) l.mouseMoveEvent(ev)
@ -1858,8 +2029,10 @@ class GridItem(UIGraphicsItem):
self.picture = None self.picture = None
def viewChangedEvent(self, newRect, oldRect): def viewChangedEvent(self):
self.picture = None self.picture = None
UIGraphicsItem.viewChangedEvent(self)
#self.update()
def paint(self, p, opt, widget): def paint(self, p, opt, widget):
#p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) #p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100)))
@ -1883,8 +2056,8 @@ class GridItem(UIGraphicsItem):
unit = self.unitRect() unit = self.unitRect()
dim = [vr.width(), vr.height()] dim = [vr.width(), vr.height()]
lvr = self.boundingRect() lvr = self.boundingRect()
ul = array([lvr.left(), lvr.top()]) ul = np.array([lvr.left(), lvr.top()])
br = array([lvr.right(), lvr.bottom()]) br = np.array([lvr.right(), lvr.bottom()])
texts = [] texts = []
@ -1897,22 +2070,22 @@ class GridItem(UIGraphicsItem):
dist = br-ul dist = br-ul
nlTarget = 10.**i nlTarget = 10.**i
d = 10. ** floor(log10(abs(dist/nlTarget))+0.5) d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5)
ul1 = floor(ul / d) * d ul1 = np.floor(ul / d) * d
br1 = ceil(br / d) * d br1 = np.ceil(br / d) * d
dist = br1-ul1 dist = br1-ul1
nl = (dist / d) + 0.5 nl = (dist / d) + 0.5
for ax in range(0,2): ## Draw grid for both axes for ax in range(0,2): ## Draw grid for both axes
ppl = dim[ax] / nl[ax] ppl = dim[ax] / nl[ax]
c = clip(3.*(ppl-3), 0., 30.) c = np.clip(3.*(ppl-3), 0., 30.)
linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c)) linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c))
textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2)) textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2))
bx = (ax+1) % 2 bx = (ax+1) % 2
for x in range(0, int(nl[ax])): for x in range(0, int(nl[ax])):
p.setPen(linePen) p.setPen(linePen)
p1 = array([0.,0.]) p1 = np.array([0.,0.])
p2 = array([0.,0.]) p2 = np.array([0.,0.])
p1[ax] = ul1[ax] + x * d[ax] p1[ax] = ul1[ax] + x * d[ax]
p2[ax] = p1[ax] p2[ax] = p1[ax]
p1[bx] = ul[bx] p1[bx] = ul[bx]
@ -1969,7 +2142,7 @@ class ScaleBar(UIGraphicsItem):
p.scale(rect.width(), rect.height()) p.scale(rect.width(), rect.height())
p.drawRect(0, 0, 1, 1) p.drawRect(0, 0, 1, 1)
alpha = clip(((self.size/unit.width()) - 40.) * 255. / 80., 0, 255) alpha = np.clip(((self.size/unit.width()) - 40.) * 255. / 80., 0, 255)
p.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, alpha))) p.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, alpha)))
for i in range(1, 10): for i in range(1, 10):
#x2 = x + (x1-x) * 0.1 * i #x2 = x + (x1-x) * 0.1 * i

View File

@ -10,7 +10,8 @@ of array data from ImageItems.
""" """
from PyQt4 import QtCore, QtGui, QtOpenGL, QtSvg from PyQt4 import QtCore, QtGui, QtOpenGL, QtSvg
from numpy import array, arccos, dot, pi, zeros, vstack, ubyte, fromfunction, ceil, floor, arctan2 #from numpy import array, arccos, dot, pi, zeros, vstack, ubyte, fromfunction, ceil, floor, arctan2
import numpy as np
from numpy.linalg import norm from numpy.linalg import norm
import scipy.ndimage as ndimage import scipy.ndimage as ndimage
from Point import * from Point import *
@ -50,7 +51,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.state = {'pos': pos, 'size': size, 'angle': angle} self.state = {'pos': pos, 'size': size, 'angle': angle}
self.lastState = None self.lastState = None
self.setPos(pos) self.setPos(pos)
self.rotate(-angle * 180. / pi) self.rotate(-angle * 180. / np.pi)
self.setZValue(10) self.setZValue(10)
self.handleSize = 5 self.handleSize = 5
@ -63,6 +64,14 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.scaleSnap = scaleSnap self.scaleSnap = scaleSnap
self.setFlag(self.ItemIsSelectable, True) self.setFlag(self.ItemIsSelectable, True)
def getState(self):
return self.state.copy()
def setState(self, state):
self.setPos(state['pos'], update=False)
self.setSize(state['size'], update=False)
self.setAngle(state['angle'])
def setZValue(self, z): def setZValue(self, z):
QtGui.QGraphicsItem.setZValue(self, z) QtGui.QGraphicsItem.setZValue(self, z)
for h in self.handles: for h in self.handles:
@ -79,10 +88,12 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.update() self.update()
def setPos(self, pos, update=True): def setPos(self, pos, update=True):
#print "setPos() called."
pos = Point(pos) pos = Point(pos)
self.state['pos'] = pos self.state['pos'] = pos
QtGui.QGraphicsItem.setPos(self, pos) QtGui.QGraphicsItem.setPos(self, pos)
if update: if update:
self.updateHandles()
self.handleChange() self.handleChange()
def setSize(self, size, update=True): def setSize(self, size, update=True):
@ -93,35 +104,45 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.updateHandles() self.updateHandles()
self.handleChange() self.handleChange()
def addTranslateHandle(self, pos, axes=None, item=None): def setAngle(self, angle, update=True):
pos = Point(pos) self.state['angle'] = angle
return self.addHandle({'type': 't', 'pos': pos, 'item': item}) tr = QtGui.QTransform()
tr.rotate(-angle * 180 / np.pi)
self.setTransform(tr)
if update:
self.updateHandles()
self.handleChange()
def addFreeHandle(self, pos, axes=None, item=None):
pos = Point(pos)
return self.addHandle({'type': 'f', 'pos': pos, 'item': item})
def addScaleHandle(self, pos, center, axes=None, item=None): def addTranslateHandle(self, pos, axes=None, item=None, name=None):
pos = Point(pos)
return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item})
def addFreeHandle(self, pos, axes=None, item=None, name=None):
pos = Point(pos)
return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item})
def addScaleHandle(self, pos, center, axes=None, item=None, name=None):
pos = Point(pos) pos = Point(pos)
center = Point(center) center = Point(center)
info = {'type': 's', 'center': center, 'pos': pos, 'item': item} info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item}
if pos.x() == center.x(): if pos.x() == center.x():
info['xoff'] = True info['xoff'] = True
if pos.y() == center.y(): if pos.y() == center.y():
info['yoff'] = True info['yoff'] = True
return self.addHandle(info) return self.addHandle(info)
def addRotateHandle(self, pos, center, item=None): def addRotateHandle(self, pos, center, item=None, name=None):
pos = Point(pos) pos = Point(pos)
center = Point(center) center = Point(center)
return self.addHandle({'type': 'r', 'center': center, 'pos': pos, 'item': item}) return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item})
def addScaleRotateHandle(self, pos, center, item=None): def addScaleRotateHandle(self, pos, center, item=None, name=None):
pos = Point(pos) pos = Point(pos)
center = Point(center) center = Point(center)
if pos[0] != center[0] and pos[1] != center[1]: if pos[0] != center[0] and pos[1] != center[1]:
raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.") raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.")
return self.addHandle({'type': 'sr', 'center': center, 'pos': pos, 'item': item}) return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item})
def addHandle(self, info): def addHandle(self, info):
if not info.has_key('item') or info['item'] is None: if not info.has_key('item') or info['item'] is None:
@ -145,6 +166,26 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
#h.hide() #h.hide()
return h return h
def getLocalHandlePositions(self, index=None):
"""Returns the position of a handle in ROI coordinates"""
if index == None:
positions = []
for h in self.handles:
positions.append((h['name'], h['pos']))
return positions
else:
return (self.handles[index]['name'], self.handles[index]['pos'])
def getSceneHandlePositions(self, index = None):
if index == None:
positions = []
for h in self.handles:
positions.append((h['name'], h['item'].scenePos()))
return positions
else:
return (self.handles[index]['name'], self.handles[index]['item'].scenePos())
def mapSceneToParent(self, pt): def mapSceneToParent(self, pt):
return self.mapToParent(self.mapFromScene(pt)) return self.mapToParent(self.mapFromScene(pt))
@ -220,7 +261,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()): def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()):
#print "movePoint", pos #print "movePoint() called."
newState = self.stateCopy() newState = self.stateCopy()
h = self.handles[pt] h = self.handles[pt]
#p0 = self.mapToScene(h['item'].pos()) #p0 = self.mapToScene(h['item'].pos())
@ -248,6 +289,7 @@ 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)
elif h['type'] == 's': elif h['type'] == 's':
#c = h['center'] #c = h['center']
@ -312,11 +354,11 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
if ang is None: if ang is None:
return return
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
ang = round(ang / (pi/12.)) * (pi/12.) ang = round(ang / (np.pi/12.)) * (np.pi/12.)
tr = QtGui.QTransform() tr = QtGui.QTransform()
tr.rotate(-ang * 180. / pi) tr.rotate(-ang * 180. / np.pi)
cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos']) cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos'])
newState['angle'] = ang newState['angle'] = ang
@ -347,7 +389,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
if ang is None: if ang is None:
return return
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
ang = round(ang / (pi/12.)) * (pi/12.) ang = round(ang / (np.pi/12.)) * (np.pi/12.)
hs = abs(h['pos'][scaleAxis] - c[scaleAxis]) hs = abs(h['pos'][scaleAxis] - c[scaleAxis])
newState['size'][scaleAxis] = lp1.length() / hs newState['size'][scaleAxis] = lp1.length() / hs
@ -358,7 +400,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
c1 = c * newState['size'] c1 = c * newState['size']
tr = QtGui.QTransform() tr = QtGui.QTransform()
tr.rotate(-ang * 180. / pi) tr.rotate(-ang * 180. / np.pi)
cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos']) cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos'])
newState['angle'] = ang newState['angle'] = ang
@ -377,16 +419,21 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.handleChange() self.handleChange()
def handleChange(self): def handleChange(self):
#print "handleChange() called."
changed = False changed = False
#print "self.lastState:", self.lastState
if self.lastState is None: if self.lastState is None:
changed = True changed = True
else: else:
for k in self.state.keys(): for k in self.state.keys():
#print k, self.state[k], self.lastState[k]
if self.state[k] != self.lastState[k]: if self.state[k] != self.lastState[k]:
#print "state %s has changed; emit signal" % k #print "state %s has changed; emit signal" % k
changed = True changed = True
self.lastState = self.stateCopy() self.lastState = self.stateCopy()
#print "changed =", changed
if changed: if changed:
#print "handle changed."
self.update() self.update()
self.emit(QtCore.SIGNAL('regionChanged'), self) self.emit(QtCore.SIGNAL('regionChanged'), self)
@ -442,7 +489,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
def stateRect(self, state): def stateRect(self, state):
r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1]) r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1])
tr = QtGui.QTransform() tr = QtGui.QTransform()
tr.rotate(-state['angle'] * 180 / pi) tr.rotate(-state['angle'] * 180 / np.pi)
r = tr.mapRect(r) r = tr.mapRect(r)
return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1]) return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1])
@ -533,10 +580,10 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
## 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 / 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 / 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:
@ -557,7 +604,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
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 = 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
@ -588,7 +635,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
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 = 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())
@ -597,7 +644,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
## figure out the reverse transpose order ## figure out the reverse transpose order
tr2 = 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)
@ -627,10 +674,10 @@ class Handle(QtGui.QGraphicsItem):
self.pen.setCosmetic(True) self.pen.setCosmetic(True)
if typ == 't': if typ == 't':
self.sides = 4 self.sides = 4
self.startAng = pi/4 self.startAng = np.pi/4
elif typ == 'f': elif typ == 'f':
self.sides = 4 self.sides = 4
self.startAng = pi/4 self.startAng = np.pi/4
elif typ == 's': elif typ == 's':
self.sides = 4 self.sides = 4
self.startAng = 0 self.startAng = 0
@ -642,7 +689,7 @@ class Handle(QtGui.QGraphicsItem):
self.startAng = 0 self.startAng = 0
else: else:
self.sides = 4 self.sides = 4
self.startAng = pi/4 self.startAng = np.pi/4
def connectROI(self, roi, i): def connectROI(self, roi, i):
self.roi.append((roi, i)) self.roi.append((roi, i))
@ -684,7 +731,7 @@ class Handle(QtGui.QGraphicsItem):
m = self.sceneTransform() m = self.sceneTransform()
#mi = m.inverted()[0] #mi = m.inverted()[0]
v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0)) v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0))
va = arctan2(v.y(), v.x()) va = np.arctan2(v.y(), v.x())
## Determine length of unit vector in painter's coords ## Determine length of unit vector in painter's coords
#size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0)) #size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0))
@ -698,7 +745,7 @@ class Handle(QtGui.QGraphicsItem):
p.setRenderHints(p.Antialiasing, True) p.setRenderHints(p.Antialiasing, True)
p.setPen(self.pen) p.setPen(self.pen)
ang = self.startAng + va ang = self.startAng + va
dt = 2*pi / self.sides dt = 2*np.pi / self.sides
for i in range(0, self.sides): for i in range(0, self.sides):
x1 = size * cos(ang) x1 = size * cos(ang)
y1 = size * sin(ang) y1 = size * sin(ang)
@ -751,7 +798,7 @@ class LineROI(ROI):
c = Point(-width/2. * sin(ang), -width/2. * cos(ang)) c = Point(-width/2. * sin(ang), -width/2. * cos(ang))
pos1 = pos1 + c pos1 = pos1 + c
ROI.__init__(self, pos1, size=Point(l, width), angle=ang*180/pi, **args) ROI.__init__(self, pos1, size=Point(l, width), angle=ang*180/np.pi, **args)
self.addScaleRotateHandle([0, 0.5], [1, 0.5]) self.addScaleRotateHandle([0, 0.5], [1, 0.5])
self.addScaleRotateHandle([1, 0.5], [0, 0.5]) self.addScaleRotateHandle([1, 0.5], [0, 0.5])
self.addScaleHandle([0.5, 1], [0.5, 0.5]) self.addScaleHandle([0.5, 1], [0.5, 0.5])
@ -816,7 +863,7 @@ class MultiLineROI(QtGui.QGraphicsItem, QObjectWorkaround):
rgns.append(rgn) rgns.append(rgn)
#print l.state['size'] #print l.state['size']
#print [(r.shape) for r in rgns] #print [(r.shape) for r in rgns]
return vstack(rgns) return np.vstack(rgns)
class EllipseROI(ROI): class EllipseROI(ROI):
@ -843,7 +890,7 @@ class EllipseROI(ROI):
w = arr.shape[0] w = arr.shape[0]
h = arr.shape[1] h = arr.shape[1]
## generate an ellipsoidal mask ## generate an ellipsoidal mask
mask = fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h)) mask = np.fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h))
return arr * mask return arr * mask
@ -861,11 +908,17 @@ class CircleROI(EllipseROI):
self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5])
class PolygonROI(ROI): class PolygonROI(ROI):
def __init__(self, positions, **args): def __init__(self, positions, pos=None, **args):
ROI.__init__(self, [0,0], [100,100], **args) if pos is None:
pos = [0,0]
ROI.__init__(self, pos, [1,1], **args)
#ROI.__init__(self, positions[0])
for p in positions: for p in positions:
self.addFreeHandle(p) self.addFreeHandle(p)
def listPoints(self):
return [p['item'].pos() for p in self.handles]
def movePoint(self, *args, **kargs): def movePoint(self, *args, **kargs):
ROI.movePoint(self, *args, **kargs) ROI.movePoint(self, *args, **kargs)
self.prepareGeometryChange() self.prepareGeometryChange()