Numerous fixes
added scatter plots
This commit is contained in:
parent
dbcdf7ec91
commit
661e4411e2
@ -163,10 +163,16 @@ class TickSlider(QtGui.QGraphicsView):
|
||||
class GradientWidget(TickSlider):
|
||||
def __init__(self, *args, **kargs):
|
||||
TickSlider.__init__(self, *args, **kargs)
|
||||
self.currentTick = None
|
||||
self.currentTickColor = None
|
||||
self.rectSize = 15
|
||||
self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize))
|
||||
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.scene.addItem(self.gradRect)
|
||||
@ -200,16 +206,27 @@ class GradientWidget(TickSlider):
|
||||
self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize)
|
||||
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):
|
||||
if ev.button() == QtCore.Qt.LeftButton:
|
||||
if not tick.colorChangeAllowed:
|
||||
return
|
||||
color = QtGui.QColorDialog.getColor(tick.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
|
||||
if color.isValid():
|
||||
self.setTickColor(tick, color)
|
||||
self.updateGradient()
|
||||
self.currentTick = tick
|
||||
self.currentTickColor = tick.color
|
||||
self.colorDialog.setCurrentColor(tick.color)
|
||||
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:
|
||||
if not tick.removeAllowed:
|
||||
return
|
||||
@ -267,7 +284,8 @@ class GradientWidget(TickSlider):
|
||||
r = c1.red() * (1.-f) + c2.red() * f
|
||||
g = c1.green() * (1.-f) + c2.green() * 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':
|
||||
h1,s1,v1,_ = c1.getHsv()
|
||||
h2,s2,v2,_ = c2.getHsv()
|
||||
@ -292,6 +310,43 @@ class GradientWidget(TickSlider):
|
||||
t.removeAllowed = True
|
||||
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):
|
||||
pass
|
||||
@ -361,4 +416,31 @@ class Tick(QtGui.QGraphicsPolygonItem):
|
||||
#self.view.tickChanged(self)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -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)
|
@ -27,6 +27,8 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
enabled via enableMouse()."""
|
||||
|
||||
QtGui.QGraphicsView.__init__(self, parent)
|
||||
if 'linux' in sys.platform: ## linux has bugs in opengl implementation
|
||||
useOpenGL = False
|
||||
self.useOpenGL(useOpenGL)
|
||||
|
||||
palette = QtGui.QPalette()
|
||||
@ -139,6 +141,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
#print " translate:", st
|
||||
self.setMatrix(m)
|
||||
self.currentScale = scale
|
||||
self.emit(QtCore.SIGNAL('viewChanged'), self.range)
|
||||
|
||||
if propagate:
|
||||
for v in self.lockedViewports:
|
||||
@ -190,7 +193,6 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
#print "New Range:", self.range
|
||||
self.centralWidget.setGeometry(self.range)
|
||||
self.updateMatrix(propagate)
|
||||
self.emit(QtCore.SIGNAL('viewChanged'), self.range)
|
||||
|
||||
|
||||
def lockXRange(self, v1):
|
||||
|
39
ImageView.py
39
ImageView.py
@ -18,8 +18,9 @@ from graphicsItems import *
|
||||
from widgets import ROI
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import sys
|
||||
from numpy import ndarray
|
||||
#from numpy import ndarray
|
||||
import ptime
|
||||
import numpy as np
|
||||
|
||||
from SignalProxy import proxyConnect
|
||||
|
||||
@ -52,7 +53,7 @@ class ImageView(QtGui.QWidget):
|
||||
self.ui.graphicsView.invertY()
|
||||
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[1].colorChangeAllowed = False
|
||||
self.ui.gradientWidget.allowAdd = False
|
||||
@ -301,14 +302,14 @@ class ImageView(QtGui.QWidget):
|
||||
axes = (1, 2)
|
||||
else:
|
||||
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:
|
||||
while data.ndim > 1:
|
||||
data = data.mean(axis=1)
|
||||
self.roiCurve.setData(y=data, x=self.tVals)
|
||||
#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.
|
||||
Options are:
|
||||
img: ndarray; the image to be displayed.
|
||||
@ -319,16 +320,19 @@ class ImageView(QtGui.QWidget):
|
||||
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.")
|
||||
self.image = img
|
||||
|
||||
if xvals is not None:
|
||||
self.tVals = xvals
|
||||
elif hasattr(img, 'xvals'):
|
||||
self.tVals = img.xvals(0)
|
||||
try:
|
||||
self.tVals = img.xvals(0)
|
||||
except:
|
||||
self.tVals = np.arange(img.shape[0])
|
||||
else:
|
||||
self.tVals = arange(img.shape[0])
|
||||
self.tVals = np.arange(img.shape[0])
|
||||
#self.ui.timeSlider.setValue(0)
|
||||
#self.ui.normStartSlider.setValue(0)
|
||||
#self.ui.timeSlider.setMaximum(img.shape[0]-1)
|
||||
@ -346,13 +350,13 @@ class ImageView(QtGui.QWidget):
|
||||
|
||||
|
||||
self.imageDisp = None
|
||||
if autoRange:
|
||||
self.autoRange()
|
||||
if autoLevels:
|
||||
self.autoLevels()
|
||||
if levels is not None:
|
||||
self.levelMax = levels[1]
|
||||
self.levelMin = levels[0]
|
||||
|
||||
self.currentIndex = 0
|
||||
self.updateImage()
|
||||
if self.ui.roiBtn.isChecked():
|
||||
self.roiChanged()
|
||||
@ -361,6 +365,7 @@ class ImageView(QtGui.QWidget):
|
||||
if self.axes['t'] is not None:
|
||||
#self.ui.roiPlot.show()
|
||||
self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max())
|
||||
self.timeLine.setValue(0)
|
||||
#self.ui.roiPlot.setMouseEnabled(False, False)
|
||||
if len(self.tVals) > 1:
|
||||
start = self.tVals.min()
|
||||
@ -376,6 +381,14 @@ class ImageView(QtGui.QWidget):
|
||||
#else:
|
||||
#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()
|
||||
|
||||
|
||||
@ -389,10 +402,12 @@ class ImageView(QtGui.QWidget):
|
||||
self.ui.gradientWidget.setTickValue(self.ticks[1], 1.0)
|
||||
self.imageItem.setLevels(white=self.whiteLevel(), black=self.blackLevel())
|
||||
|
||||
|
||||
def autoRange(self):
|
||||
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):
|
||||
if self.imageDisp is None:
|
||||
@ -408,7 +423,7 @@ class ImageView(QtGui.QWidget):
|
||||
return image
|
||||
|
||||
div = self.ui.normDivideRadio.isChecked()
|
||||
norm = image.view(ndarray).copy()
|
||||
norm = image.view(np.ndarray).copy()
|
||||
#if div:
|
||||
#norm = ones(image.shape)
|
||||
#else:
|
||||
@ -498,7 +513,7 @@ class ImageView(QtGui.QWidget):
|
||||
return (0,0)
|
||||
totTime = xv[-1] + (xv[-1]-xv[-2])
|
||||
#t = f * totTime
|
||||
inds = argwhere(xv < t)
|
||||
inds = np.argwhere(xv < t)
|
||||
if len(inds) < 1:
|
||||
return (0,t)
|
||||
ind = inds[-1,0]
|
||||
|
2129
PIL_Fix/Image.py-1.7
Normal file
2129
PIL_Fix/Image.py-1.7
Normal file
File diff suppressed because it is too large
Load Diff
27
PlotItem.py
27
PlotItem.py
@ -23,6 +23,7 @@ from PyQt4 import QtGui, QtCore, QtSvg
|
||||
#from ObjectWorkaround import *
|
||||
#tryWorkaround(QtCore, QtGui)
|
||||
import weakref
|
||||
import numpy as np
|
||||
|
||||
try:
|
||||
from WidgetGroup import *
|
||||
@ -37,8 +38,6 @@ except:
|
||||
HAVE_METAARRAY = False
|
||||
|
||||
|
||||
|
||||
|
||||
class PlotItem(QtGui.QGraphicsWidget):
|
||||
"""Plot graphics item that can be added to any graphics scene. Implements axis titles, scales, interactive viewbox."""
|
||||
lastFileDir = None
|
||||
@ -132,6 +131,7 @@ class PlotItem(QtGui.QGraphicsWidget):
|
||||
|
||||
self.items = []
|
||||
self.curves = []
|
||||
self.dataItems = []
|
||||
self.paramList = {}
|
||||
self.avgCurves = {}
|
||||
|
||||
@ -436,7 +436,7 @@ class PlotItem(QtGui.QGraphicsWidget):
|
||||
self.vb.setMouseEnabled(*state)
|
||||
|
||||
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())))
|
||||
self.ctrl.xMinText.setText('%0.5g' % range[0])
|
||||
self.ctrl.xMaxText.setText('%0.5g' % range[1])
|
||||
@ -455,7 +455,7 @@ class PlotItem(QtGui.QGraphicsWidget):
|
||||
self.emit(QtCore.SIGNAL('xRangeChanged'), 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())))
|
||||
self.ctrl.yMinText.setText('%0.5g' % range[0])
|
||||
self.ctrl.yMaxText.setText('%0.5g' % range[1])
|
||||
@ -545,6 +545,9 @@ class PlotItem(QtGui.QGraphicsWidget):
|
||||
if not item in self.items:
|
||||
return
|
||||
self.items.remove(item)
|
||||
if item in self.dataItems:
|
||||
self.dataItems.remove(item)
|
||||
|
||||
if item.scene() is not None:
|
||||
self.vb.removeItem(item)
|
||||
if item in self.curves:
|
||||
@ -571,12 +574,12 @@ class PlotItem(QtGui.QGraphicsWidget):
|
||||
params = {}
|
||||
if HAVE_METAARRAY and isinstance(data, MetaArray):
|
||||
curve = self._plotMetaArray(data, x=x)
|
||||
elif isinstance(data, ndarray):
|
||||
elif isinstance(data, np.ndarray):
|
||||
curve = self._plotArray(data, x=x)
|
||||
elif isinstance(data, list):
|
||||
if x is not None:
|
||||
x = array(x)
|
||||
curve = self._plotArray(array(data), x=x)
|
||||
x = np.array(x)
|
||||
curve = self._plotArray(np.array(data), x=x)
|
||||
elif data is None:
|
||||
curve = PlotCurveItem()
|
||||
else:
|
||||
@ -589,6 +592,10 @@ class PlotItem(QtGui.QGraphicsWidget):
|
||||
|
||||
return curve
|
||||
|
||||
def addDataItem(self, item):
|
||||
self.addItem(item)
|
||||
self.dataItems.append(item)
|
||||
|
||||
def addCurve(self, c, params=None):
|
||||
if params is None:
|
||||
params = {}
|
||||
@ -622,7 +629,7 @@ class PlotItem(QtGui.QGraphicsWidget):
|
||||
percentScale = [self.ctrl.xAutoPercentSpin.value(), self.ctrl.yAutoPercentSpin.value()][ax] * 0.01
|
||||
mn = 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():
|
||||
continue
|
||||
cmn, cmx = c.getRange(ax, percentScale)
|
||||
@ -630,7 +637,7 @@ class PlotItem(QtGui.QGraphicsWidget):
|
||||
mn = cmn
|
||||
if mx is None or cmx > mx:
|
||||
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
|
||||
if mn == mx:
|
||||
mn -= 1
|
||||
@ -1013,7 +1020,7 @@ class PlotItem(QtGui.QGraphicsWidget):
|
||||
else:
|
||||
xv = x
|
||||
c = PlotCurveItem()
|
||||
c.setData(x=xv, y=arr.view(ndarray))
|
||||
c.setData(x=xv, y=arr.view(np.ndarray))
|
||||
|
||||
if autoLabel:
|
||||
name = arr._info[0].get('name', None)
|
||||
|
8
Point.py
8
Point.py
@ -20,7 +20,10 @@ class Point(QtCore.QPointF):
|
||||
|
||||
def __init__(self, *args):
|
||||
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]))
|
||||
return
|
||||
elif type(args[0]) in [float, int]:
|
||||
@ -31,6 +34,9 @@ class Point(QtCore.QPointF):
|
||||
return
|
||||
QtCore.QPointF.__init__(self, *args)
|
||||
|
||||
def __reduce__(self):
|
||||
return (Point, (self.x(), self.y()))
|
||||
|
||||
def __getitem__(self, i):
|
||||
if i == 0:
|
||||
return self.x()
|
||||
|
@ -24,14 +24,17 @@ class SignalProxy(QtCore.QObject):
|
||||
self.args = None
|
||||
self.timers = 0
|
||||
self.signal = signal
|
||||
self.block = False
|
||||
|
||||
def setDelay(self, delay):
|
||||
self.delay = delay
|
||||
|
||||
def flush(self):
|
||||
"""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
|
||||
if self.block:
|
||||
return
|
||||
self.emit(self.signal, *self.args)
|
||||
self.args = None
|
||||
return True
|
||||
@ -39,6 +42,8 @@ class SignalProxy(QtCore.QObject):
|
||||
|
||||
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
|
||||
@ -46,7 +51,7 @@ class SignalProxy(QtCore.QObject):
|
||||
|
||||
def tryEmit(self):
|
||||
"""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
|
||||
self.timers -= 1
|
||||
t = time()
|
||||
@ -59,4 +64,6 @@ class SignalProxy(QtCore.QObject):
|
||||
return True
|
||||
|
||||
|
||||
def disconnect(self):
|
||||
self.block = True
|
||||
|
@ -14,6 +14,7 @@ app = QtGui.QApplication([])
|
||||
## Create window with GraphicsView widget
|
||||
win = QtGui.QMainWindow()
|
||||
view = GraphicsView()
|
||||
#view.useOpenGL(True)
|
||||
win.setCentralWidget(view)
|
||||
win.show()
|
||||
|
||||
|
38
examples/test_draw.py
Normal file
38
examples/test_draw.py
Normal 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_()
|
32
examples/test_scatterPlot.py
Normal file
32
examples/test_scatterPlot.py
Normal 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)
|
||||
|
||||
|
281
graphicsItems.py
281
graphicsItems.py
@ -11,7 +11,8 @@ Provides ImageItem, PlotCurveItem, and ViewBox, amongst others.
|
||||
from PyQt4 import QtGui, QtCore
|
||||
from ObjectWorkaround import *
|
||||
#tryWorkaround(QtCore, QtGui)
|
||||
from numpy import *
|
||||
#from numpy import *
|
||||
import numpy as np
|
||||
try:
|
||||
import scipy.weave as weave
|
||||
from scipy.weave import converters
|
||||
@ -171,10 +172,14 @@ class GraphicsObject(QGraphicsObject):
|
||||
|
||||
|
||||
|
||||
class ImageItem(QtGui.QGraphicsPixmapItem):
|
||||
useWeave = True
|
||||
class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
|
||||
if 'linux' not in sys.platform: ## disable weave optimization on linux--broken there.
|
||||
useWeave = True
|
||||
else:
|
||||
useWeave = False
|
||||
|
||||
def __init__(self, image=None, copy=True, parent=None, *args):
|
||||
QObjectWorkaround.__init__(self)
|
||||
self.qimage = QtGui.QImage()
|
||||
self.pixmap = None
|
||||
#self.useWeave = True
|
||||
@ -183,6 +188,8 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
|
||||
self.alpha = 1.0
|
||||
self.image = None
|
||||
self.clipLevel = None
|
||||
self.drawKernel = None
|
||||
|
||||
QtGui.QGraphicsPixmapItem.__init__(self, parent, *args)
|
||||
#self.pixmapItem = QtGui.QGraphicsPixmapItem(self)
|
||||
if image is not None:
|
||||
@ -222,6 +229,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
|
||||
if black is not None:
|
||||
self.blackLevel = black
|
||||
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):
|
||||
axh = {'x': 0, 'y': 1, 'c': 2}
|
||||
@ -231,11 +241,12 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
|
||||
if black is not None:
|
||||
self.blackLevel = black
|
||||
|
||||
|
||||
gotNewData = False
|
||||
if image is None:
|
||||
if self.image is None:
|
||||
return
|
||||
else:
|
||||
gotNewData = True
|
||||
if copy:
|
||||
self.image = image.copy()
|
||||
else:
|
||||
@ -261,9 +272,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
|
||||
try:
|
||||
if not ImageItem.useWeave:
|
||||
raise Exception('Skipping weave compile')
|
||||
sim = ascontiguousarray(self.image)
|
||||
sim = np.ascontiguousarray(self.image)
|
||||
sim.shape = sim.size
|
||||
im = zeros(sim.shape, dtype=ubyte)
|
||||
im = np.empty(sim.shape, dtype=np.ubyte)
|
||||
n = im.size
|
||||
|
||||
code = """
|
||||
@ -287,15 +298,15 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
|
||||
#print "=============================================================================="
|
||||
print "Weave compile failed, falling back to slower version."
|
||||
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:
|
||||
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:
|
||||
print im.shape, axh
|
||||
raise
|
||||
alpha = clip(int(255 * self.alpha), 0, 255)
|
||||
alpha = np.clip(int(255 * self.alpha), 0, 255)
|
||||
# Fill image
|
||||
if im.ndim == 2:
|
||||
im2 = im.transpose(axh['y'], axh['x'])
|
||||
@ -303,7 +314,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
|
||||
im1[..., 1] = im2
|
||||
im1[..., 2] = im2
|
||||
im1[..., 3] = alpha
|
||||
elif im.ndim == 3:
|
||||
elif im.ndim == 3: #color image
|
||||
im2 = im.transpose(axh['y'], axh['x'], axh['c'])
|
||||
|
||||
for i in range(0, im.shape[axh['c']]):
|
||||
@ -335,10 +346,43 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
|
||||
self.setPixmap(self.pixmap)
|
||||
self.update()
|
||||
|
||||
if gotNewData:
|
||||
self.emit(QtCore.SIGNAL('imageChanged'))
|
||||
|
||||
def getPixmap(self):
|
||||
return self.pixmap.copy()
|
||||
|
||||
def getHistogram(self, bins=500, step=3):
|
||||
"""returns an x and y arrays containing the histogram values for the current image.
|
||||
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 representing a single plot curve."""
|
||||
@ -376,9 +420,13 @@ class PlotCurveItem(GraphicsObject):
|
||||
if self.xData is None:
|
||||
return (None, None)
|
||||
if self.xDisp is None:
|
||||
nanMask = isnan(self.xData) | isnan(self.yData)
|
||||
x = self.xData[~nanMask]
|
||||
y = self.yData[~nanMask]
|
||||
nanMask = np.isnan(self.xData) | np.isnan(self.yData)
|
||||
if any(nanMask):
|
||||
x = self.xData[~nanMask]
|
||||
y = self.yData[~nanMask]
|
||||
else:
|
||||
x = self.xData
|
||||
y = self.yData
|
||||
ds = self.opts['downsample']
|
||||
if ds > 1:
|
||||
x = x[::ds]
|
||||
@ -387,11 +435,11 @@ class PlotCurveItem(GraphicsObject):
|
||||
f = fft(y) / len(y)
|
||||
y = abs(f[1:len(f)/2])
|
||||
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]:
|
||||
x = log10(x)
|
||||
x = np.log10(x)
|
||||
if self.opts['logMode'][1]:
|
||||
y = log10(y)
|
||||
y = np.log10(y)
|
||||
self.xDisp = x
|
||||
self.yDisp = y
|
||||
#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):
|
||||
if isinstance(data, list):
|
||||
data = array(data)
|
||||
data = np.array(data)
|
||||
if isinstance(x, list):
|
||||
x = array(x)
|
||||
if not isinstance(data, ndarray) or data.ndim > 2:
|
||||
x = np.array(x)
|
||||
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))
|
||||
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:
|
||||
@ -542,9 +590,9 @@ class PlotCurveItem(GraphicsObject):
|
||||
## 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,
|
||||
## 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]):
|
||||
#self.path.lineTo(x[i], y[i])
|
||||
# path.lineTo(x[i], y[i])
|
||||
|
||||
## Speed this up using >> operator
|
||||
## Format is:
|
||||
@ -558,7 +606,7 @@ class PlotCurveItem(GraphicsObject):
|
||||
#
|
||||
n = x.shape[0]
|
||||
# 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
|
||||
arr.data[12:20] = struct.pack('>ii', n, 0)
|
||||
# Fill array with vertex values
|
||||
@ -643,7 +691,113 @@ class PlotCurveItem(GraphicsObject):
|
||||
self.path = None
|
||||
#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):
|
||||
def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None):
|
||||
self.roi = roi
|
||||
@ -978,7 +1132,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
|
||||
self.update()
|
||||
|
||||
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)))
|
||||
self.range = [mn, mx]
|
||||
if self.autoScale:
|
||||
@ -1049,7 +1203,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
|
||||
if dif == 0.0:
|
||||
return
|
||||
#print "dif:", dif
|
||||
pw = 10 ** (floor(log10(dif))-1)
|
||||
pw = 10 ** (np.floor(np.log10(dif))-1)
|
||||
for i in range(len(intervals)):
|
||||
i1 = i
|
||||
if dif / (pw*intervals[i]) < 10:
|
||||
@ -1075,7 +1229,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
|
||||
sp = pw*intervals[i]
|
||||
|
||||
## determine starting tick
|
||||
start = ceil(self.range[0] / sp) * sp
|
||||
start = np.ceil(self.range[0] / sp) * sp
|
||||
|
||||
## determine number of ticks
|
||||
num = int(dif / sp) + 1
|
||||
@ -1085,7 +1239,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
|
||||
|
||||
## Number of decimal places to print
|
||||
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
|
||||
h = min(self.tickLength, (self.tickLength*3 / num) - 1.)
|
||||
@ -1292,11 +1446,11 @@ class ViewBox(QtGui.QGraphicsWidget):
|
||||
yd = pr[1][1] - pr[1][0]
|
||||
if xd == 0 or yd == 0:
|
||||
print "Warning: 0 range in view:", xd, yd
|
||||
return array([1,1])
|
||||
return np.array([1,1])
|
||||
|
||||
#cs = self.canvas().size()
|
||||
cs = self.boundingRect()
|
||||
scale = array([cs.width() / xd, cs.height() / yd])
|
||||
scale = np.array([cs.width() / xd, cs.height() / yd])
|
||||
#print "view scale:", scale
|
||||
return scale
|
||||
|
||||
@ -1324,7 +1478,7 @@ class ViewBox(QtGui.QGraphicsWidget):
|
||||
#print self.range
|
||||
|
||||
def translateBy(self, t, viewCoords=False):
|
||||
t = t.astype(float)
|
||||
t = t.astype(np.float)
|
||||
#print "translate:", t, self.viewScale()
|
||||
if viewCoords: ## scale from pixels
|
||||
t /= self.viewScale()
|
||||
@ -1339,25 +1493,25 @@ class ViewBox(QtGui.QGraphicsWidget):
|
||||
|
||||
|
||||
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 *= -1
|
||||
self.mousePos = pos
|
||||
|
||||
## 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
|
||||
if ev.buttons() & QtCore.Qt.LeftButton:
|
||||
if not self.yInverted:
|
||||
mask *= array([1, -1])
|
||||
mask *= np.array([1, -1])
|
||||
tr = dif*mask
|
||||
self.translateBy(tr, viewCoords=True)
|
||||
self.emit(QtCore.SIGNAL('rangeChangedManually'), self.mouseEnabled)
|
||||
ev.accept()
|
||||
elif ev.buttons() & QtCore.Qt.RightButton:
|
||||
dif = ev.screenPos() - ev.lastScreenPos()
|
||||
dif = array([dif.x(), dif.y()])
|
||||
dif = np.array([dif.x(), dif.y()])
|
||||
dif[0] *= -1
|
||||
s = ((mask * 0.02) + 1) ** dif
|
||||
#print mask, dif, s
|
||||
@ -1369,12 +1523,12 @@ class ViewBox(QtGui.QGraphicsWidget):
|
||||
ev.ignore()
|
||||
|
||||
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()
|
||||
ev.accept()
|
||||
|
||||
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 ev.button() == QtCore.Qt.RightButton:
|
||||
#self.ctrlMenu.popup(self.mapToGlobal(ev.pos()))
|
||||
@ -1396,7 +1550,7 @@ class ViewBox(QtGui.QGraphicsWidget):
|
||||
min -= dy*0.5
|
||||
max += dy*0.5
|
||||
#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)))
|
||||
|
||||
padding = (max-min) * padding
|
||||
@ -1422,7 +1576,7 @@ class ViewBox(QtGui.QGraphicsWidget):
|
||||
max += dx*0.5
|
||||
#print "Warning: 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)))
|
||||
padding = (max-min) * padding
|
||||
min -= padding
|
||||
@ -1474,6 +1628,7 @@ class InfiniteLine(GraphicsObject):
|
||||
if movable:
|
||||
self.setAcceptHoverEvents(True)
|
||||
|
||||
self.hasMoved = False
|
||||
|
||||
|
||||
if pen is None:
|
||||
@ -1585,13 +1740,13 @@ class InfiniteLine(GraphicsObject):
|
||||
#print 'before', self.bounds
|
||||
|
||||
if self.angle > 45:
|
||||
m = tan((90-self.angle) * pi / 180.)
|
||||
m = np.tan((90-self.angle) * np.pi / 180.)
|
||||
y2 = vr.bottom()
|
||||
y1 = vr.top()
|
||||
x1 = self.p[0] + (y1 - self.p[1]) * m
|
||||
x2 = self.p[0] + (y2 - self.p[1]) * m
|
||||
else:
|
||||
m = tan(self.angle * pi / 180.)
|
||||
m = np.tan(self.angle * np.pi / 180.)
|
||||
x1 = vr.left()
|
||||
x2 = vr.right()
|
||||
y2 = self.p[1] + (x1 - self.p[0]) * m
|
||||
@ -1650,12 +1805,18 @@ class InfiniteLine(GraphicsObject):
|
||||
def mouseMoveEvent(self, ev):
|
||||
self.setPos(self.mapToParent(ev.pos()) - self.pressDelta)
|
||||
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):
|
||||
"""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)
|
||||
self.orientation = orientation
|
||||
if hasattr(self, "ItemHasNoContents"):
|
||||
@ -1680,6 +1841,7 @@ class LinearRegionItem(GraphicsObject):
|
||||
|
||||
for l in self.lines:
|
||||
l.setParentItem(self)
|
||||
l.connect(QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished)
|
||||
l.connect(QtCore.SIGNAL('positionChanged'), self.lineMoved)
|
||||
|
||||
if brush is None:
|
||||
@ -1698,12 +1860,16 @@ class LinearRegionItem(GraphicsObject):
|
||||
self.updateBounds()
|
||||
self.emit(QtCore.SIGNAL('regionChanged'), self)
|
||||
|
||||
def lineMoveFinished(self):
|
||||
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
|
||||
|
||||
|
||||
def updateBounds(self):
|
||||
vb = self.view().viewRect()
|
||||
vals = [self.lines[0].value(), self.lines[1].value()]
|
||||
if self.orientation[0] == 'h':
|
||||
vb.setTop(max(vals))
|
||||
vb.setBottom(min(vals))
|
||||
vb.setTop(min(vals))
|
||||
vb.setBottom(max(vals))
|
||||
else:
|
||||
vb.setLeft(min(vals))
|
||||
vb.setRight(max(vals))
|
||||
@ -1713,14 +1879,19 @@ class LinearRegionItem(GraphicsObject):
|
||||
|
||||
def mousePressEvent(self, ev):
|
||||
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:
|
||||
#ev.accept()
|
||||
#self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p)
|
||||
#else:
|
||||
#ev.ignore()
|
||||
|
||||
def mouseReleaseEvent(self, ev):
|
||||
for l in self.lines:
|
||||
l.mouseReleaseEvent(ev)
|
||||
|
||||
def mouseMoveEvent(self, ev):
|
||||
#print "move", ev.pos()
|
||||
self.lines[0].blockSignals(True) # only want to update once
|
||||
for l in self.lines:
|
||||
l.mouseMoveEvent(ev)
|
||||
@ -1858,8 +2029,10 @@ class GridItem(UIGraphicsItem):
|
||||
self.picture = None
|
||||
|
||||
|
||||
def viewChangedEvent(self, newRect, oldRect):
|
||||
def viewChangedEvent(self):
|
||||
self.picture = None
|
||||
UIGraphicsItem.viewChangedEvent(self)
|
||||
#self.update()
|
||||
|
||||
def paint(self, p, opt, widget):
|
||||
#p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100)))
|
||||
@ -1883,8 +2056,8 @@ class GridItem(UIGraphicsItem):
|
||||
unit = self.unitRect()
|
||||
dim = [vr.width(), vr.height()]
|
||||
lvr = self.boundingRect()
|
||||
ul = array([lvr.left(), lvr.top()])
|
||||
br = array([lvr.right(), lvr.bottom()])
|
||||
ul = np.array([lvr.left(), lvr.top()])
|
||||
br = np.array([lvr.right(), lvr.bottom()])
|
||||
|
||||
texts = []
|
||||
|
||||
@ -1897,22 +2070,22 @@ class GridItem(UIGraphicsItem):
|
||||
|
||||
dist = br-ul
|
||||
nlTarget = 10.**i
|
||||
d = 10. ** floor(log10(abs(dist/nlTarget))+0.5)
|
||||
ul1 = floor(ul / d) * d
|
||||
br1 = ceil(br / d) * d
|
||||
d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5)
|
||||
ul1 = np.floor(ul / d) * d
|
||||
br1 = np.ceil(br / d) * d
|
||||
dist = br1-ul1
|
||||
nl = (dist / d) + 0.5
|
||||
for ax in range(0,2): ## Draw grid for both axes
|
||||
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))
|
||||
textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2))
|
||||
|
||||
bx = (ax+1) % 2
|
||||
for x in range(0, int(nl[ax])):
|
||||
p.setPen(linePen)
|
||||
p1 = array([0.,0.])
|
||||
p2 = array([0.,0.])
|
||||
p1 = np.array([0.,0.])
|
||||
p2 = np.array([0.,0.])
|
||||
p1[ax] = ul1[ax] + x * d[ax]
|
||||
p2[ax] = p1[ax]
|
||||
p1[bx] = ul[bx]
|
||||
@ -1969,7 +2142,7 @@ class ScaleBar(UIGraphicsItem):
|
||||
p.scale(rect.width(), rect.height())
|
||||
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)))
|
||||
for i in range(1, 10):
|
||||
#x2 = x + (x1-x) * 0.1 * i
|
||||
|
123
widgets.py
123
widgets.py
@ -10,7 +10,8 @@ of array data from ImageItems.
|
||||
"""
|
||||
|
||||
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
|
||||
import scipy.ndimage as ndimage
|
||||
from Point import *
|
||||
@ -50,7 +51,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
self.state = {'pos': pos, 'size': size, 'angle': angle}
|
||||
self.lastState = None
|
||||
self.setPos(pos)
|
||||
self.rotate(-angle * 180. / pi)
|
||||
self.rotate(-angle * 180. / np.pi)
|
||||
self.setZValue(10)
|
||||
|
||||
self.handleSize = 5
|
||||
@ -62,7 +63,15 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
self.rotateSnap = rotateSnap
|
||||
self.scaleSnap = scaleSnap
|
||||
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):
|
||||
QtGui.QGraphicsItem.setZValue(self, z)
|
||||
for h in self.handles:
|
||||
@ -79,10 +88,12 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
self.update()
|
||||
|
||||
def setPos(self, pos, update=True):
|
||||
#print "setPos() called."
|
||||
pos = Point(pos)
|
||||
self.state['pos'] = pos
|
||||
QtGui.QGraphicsItem.setPos(self, pos)
|
||||
if update:
|
||||
self.updateHandles()
|
||||
self.handleChange()
|
||||
|
||||
def setSize(self, size, update=True):
|
||||
@ -93,35 +104,45 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
self.updateHandles()
|
||||
self.handleChange()
|
||||
|
||||
def addTranslateHandle(self, pos, axes=None, item=None):
|
||||
def setAngle(self, angle, update=True):
|
||||
self.state['angle'] = angle
|
||||
tr = QtGui.QTransform()
|
||||
tr.rotate(-angle * 180 / np.pi)
|
||||
self.setTransform(tr)
|
||||
if update:
|
||||
self.updateHandles()
|
||||
self.handleChange()
|
||||
|
||||
|
||||
def addTranslateHandle(self, pos, axes=None, item=None, name=None):
|
||||
pos = Point(pos)
|
||||
return self.addHandle({'type': 't', 'pos': pos, 'item': item})
|
||||
return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item})
|
||||
|
||||
def addFreeHandle(self, pos, axes=None, item=None):
|
||||
def addFreeHandle(self, pos, axes=None, item=None, name=None):
|
||||
pos = Point(pos)
|
||||
return self.addHandle({'type': 'f', 'pos': pos, 'item': item})
|
||||
return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item})
|
||||
|
||||
def addScaleHandle(self, pos, center, axes=None, item=None):
|
||||
def addScaleHandle(self, pos, center, axes=None, item=None, name=None):
|
||||
pos = Point(pos)
|
||||
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():
|
||||
info['xoff'] = True
|
||||
if pos.y() == center.y():
|
||||
info['yoff'] = True
|
||||
return self.addHandle(info)
|
||||
|
||||
def addRotateHandle(self, pos, center, item=None):
|
||||
def addRotateHandle(self, pos, center, item=None, name=None):
|
||||
pos = Point(pos)
|
||||
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)
|
||||
center = Point(center)
|
||||
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.")
|
||||
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):
|
||||
if not info.has_key('item') or info['item'] is None:
|
||||
@ -144,6 +165,26 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
#else:
|
||||
#h.hide()
|
||||
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):
|
||||
return self.mapToParent(self.mapFromScene(pt))
|
||||
@ -220,7 +261,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
|
||||
|
||||
def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()):
|
||||
#print "movePoint", pos
|
||||
#print "movePoint() called."
|
||||
newState = self.stateCopy()
|
||||
h = self.handles[pt]
|
||||
#p0 = self.mapToScene(h['item'].pos())
|
||||
@ -248,6 +289,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
|
||||
elif h['type'] == 'f':
|
||||
h['item'].setPos(self.mapFromScene(pos))
|
||||
self.emit(QtCore.SIGNAL('regionChanged'), self)
|
||||
|
||||
elif h['type'] == 's':
|
||||
#c = h['center']
|
||||
@ -312,11 +354,11 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
if ang is None:
|
||||
return
|
||||
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.rotate(-ang * 180. / pi)
|
||||
tr.rotate(-ang * 180. / np.pi)
|
||||
|
||||
cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos'])
|
||||
newState['angle'] = ang
|
||||
@ -347,7 +389,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
if ang is None:
|
||||
return
|
||||
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])
|
||||
newState['size'][scaleAxis] = lp1.length() / hs
|
||||
@ -358,7 +400,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
|
||||
c1 = c * newState['size']
|
||||
tr = QtGui.QTransform()
|
||||
tr.rotate(-ang * 180. / pi)
|
||||
tr.rotate(-ang * 180. / np.pi)
|
||||
|
||||
cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos'])
|
||||
newState['angle'] = ang
|
||||
@ -377,16 +419,21 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
self.handleChange()
|
||||
|
||||
def handleChange(self):
|
||||
#print "handleChange() called."
|
||||
changed = False
|
||||
#print "self.lastState:", self.lastState
|
||||
if self.lastState is None:
|
||||
changed = True
|
||||
else:
|
||||
for k in self.state.keys():
|
||||
#print k, self.state[k], self.lastState[k]
|
||||
if self.state[k] != self.lastState[k]:
|
||||
#print "state %s has changed; emit signal" % k
|
||||
changed = True
|
||||
self.lastState = self.stateCopy()
|
||||
#print "changed =", changed
|
||||
if changed:
|
||||
#print "handle changed."
|
||||
self.update()
|
||||
self.emit(QtCore.SIGNAL('regionChanged'), self)
|
||||
|
||||
@ -442,7 +489,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
def stateRect(self, state):
|
||||
r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1])
|
||||
tr = QtGui.QTransform()
|
||||
tr.rotate(-state['angle'] * 180 / pi)
|
||||
tr.rotate(-state['angle'] * 180 / np.pi)
|
||||
r = tr.mapRect(r)
|
||||
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
|
||||
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
|
||||
rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / pi)
|
||||
rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi)
|
||||
roiDataTransform *= rot
|
||||
|
||||
## 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())
|
||||
if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6:
|
||||
# 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
|
||||
|
||||
# 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]]
|
||||
|
||||
### 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
|
||||
orig = Point(dataBounds.topLeft() - roiBounds.topLeft())
|
||||
@ -597,7 +644,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
|
||||
|
||||
## figure out the reverse transpose order
|
||||
tr2 = array(tr1)
|
||||
tr2 = np.array(tr1)
|
||||
for i in range(0, len(tr2)):
|
||||
tr2[tr1[i]] = i
|
||||
tr2 = tuple(tr2)
|
||||
@ -605,7 +652,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
## Untranspose array before returning
|
||||
return arr5.transpose(tr2)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -627,10 +674,10 @@ class Handle(QtGui.QGraphicsItem):
|
||||
self.pen.setCosmetic(True)
|
||||
if typ == 't':
|
||||
self.sides = 4
|
||||
self.startAng = pi/4
|
||||
self.startAng = np.pi/4
|
||||
elif typ == 'f':
|
||||
self.sides = 4
|
||||
self.startAng = pi/4
|
||||
self.startAng = np.pi/4
|
||||
elif typ == 's':
|
||||
self.sides = 4
|
||||
self.startAng = 0
|
||||
@ -642,7 +689,7 @@ class Handle(QtGui.QGraphicsItem):
|
||||
self.startAng = 0
|
||||
else:
|
||||
self.sides = 4
|
||||
self.startAng = pi/4
|
||||
self.startAng = np.pi/4
|
||||
|
||||
def connectROI(self, roi, i):
|
||||
self.roi.append((roi, i))
|
||||
@ -684,7 +731,7 @@ class Handle(QtGui.QGraphicsItem):
|
||||
m = self.sceneTransform()
|
||||
#mi = m.inverted()[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
|
||||
#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.setPen(self.pen)
|
||||
ang = self.startAng + va
|
||||
dt = 2*pi / self.sides
|
||||
dt = 2*np.pi / self.sides
|
||||
for i in range(0, self.sides):
|
||||
x1 = size * cos(ang)
|
||||
y1 = size * sin(ang)
|
||||
@ -751,7 +798,7 @@ class LineROI(ROI):
|
||||
c = Point(-width/2. * sin(ang), -width/2. * cos(ang))
|
||||
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([1, 0.5], [0, 0.5])
|
||||
self.addScaleHandle([0.5, 1], [0.5, 0.5])
|
||||
@ -816,7 +863,7 @@ class MultiLineROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
rgns.append(rgn)
|
||||
#print l.state['size']
|
||||
#print [(r.shape) for r in rgns]
|
||||
return vstack(rgns)
|
||||
return np.vstack(rgns)
|
||||
|
||||
|
||||
class EllipseROI(ROI):
|
||||
@ -843,7 +890,7 @@ class EllipseROI(ROI):
|
||||
w = arr.shape[0]
|
||||
h = arr.shape[1]
|
||||
## 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
|
||||
|
||||
@ -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])
|
||||
|
||||
class PolygonROI(ROI):
|
||||
def __init__(self, positions, **args):
|
||||
ROI.__init__(self, [0,0], [100,100], **args)
|
||||
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)
|
||||
|
||||
def listPoints(self):
|
||||
return [p['item'].pos() for p in self.handles]
|
||||
|
||||
def movePoint(self, *args, **kargs):
|
||||
ROI.movePoint(self, *args, **kargs)
|
||||
self.prepareGeometryChange()
|
||||
|
Loading…
Reference in New Issue
Block a user