This commit is contained in:
Luke Campagnola 2012-03-01 22:17:55 -05:00
parent d4e8e2b883
commit aaece4badc
7 changed files with 215 additions and 423 deletions

View File

@ -298,6 +298,7 @@ class Canvas(QtGui.QWidget):
Common options are name, pos, scale, and z Common options are name, pos, scale, and z
""" """
citem = CanvasItem(item, **opts) citem = CanvasItem(item, **opts)
item._canvasItem = citem
self.addItem(citem) self.addItem(citem)
return citem return citem
@ -493,12 +494,13 @@ class Canvas(QtGui.QWidget):
def removeItem(self, item): def removeItem(self, item):
if isinstance(item, CanvasItem): if isinstance(item, CanvasItem):
item.setCanvas(None) item.setCanvas(None)
#self.view.scene().removeItem(item.item)
self.itemList.removeTopLevelItem(item.listItem) self.itemList.removeTopLevelItem(item.listItem)
#del self.items[item.name]
self.items.remove(item) self.items.remove(item)
else: else:
self.view.removeItem(item) if hasattr(item, '_canvasItem'):
self.removeItem(item._canvasItem)
else:
self.view.removeItem(item)
## disconnect signals, remove from list, etc.. ## disconnect signals, remove from list, etc..

View File

@ -11,14 +11,14 @@ class ArrowItem(QtGui.QGraphicsPolygonItem):
def __init__(self, **opts): def __init__(self, **opts):
QtGui.QGraphicsPolygonItem.__init__(self) QtGui.QGraphicsPolygonItem.__init__(self, opts.get('parent', None))
defOpts = { defOpts = {
'style': 'tri', 'style': 'tri',
'pxMode': True, 'pxMode': True,
'size': 20, 'size': 20,
'angle': -150, 'angle': -150, ## If the angle is 0, the arrow points left
'pos': (0,0), 'pos': (0,0),
'width': 8, 'width': None, ## width is automatically size / 2.
'tipAngle': 25, 'tipAngle': 25,
'baseAngle': 90, 'baseAngle': 90,
'pen': (200,200,200), 'pen': (200,200,200),
@ -38,10 +38,15 @@ class ArrowItem(QtGui.QGraphicsPolygonItem):
self.opts = opts self.opts = opts
if opts['style'] == 'tri': if opts['style'] == 'tri':
if opts['width'] is None:
width = opts['size'] / 2.
else:
width = opts['width']
points = [ points = [
QtCore.QPointF(0,0), QtCore.QPointF(0,0),
QtCore.QPointF(opts['size'],-opts['width']/2.), QtCore.QPointF(opts['size'],-width/2.),
QtCore.QPointF(opts['size'],opts['width']/2.), QtCore.QPointF(opts['size'],width/2.),
] ]
poly = QtGui.QPolygonF(points) poly = QtGui.QPolygonF(points)

View File

@ -284,7 +284,7 @@ class AxisItem(GraphicsWidget):
lengthInPixels = Point(points[1] - points[0]).length() lengthInPixels = Point(points[1] - points[0]).length()
## decide optimal tick spacing in pixels ## decide optimal tick spacing in pixels
pixelSpacing = np.log(lengthInPixels+10) * 3 pixelSpacing = np.log(lengthInPixels+10) * 2
optimalTickCount = lengthInPixels / pixelSpacing optimalTickCount = lengthInPixels / pixelSpacing
## Determine optimal tick spacing ## Determine optimal tick spacing
@ -328,9 +328,11 @@ class AxisItem(GraphicsWidget):
## draw three different intervals, long ticks first ## draw three different intervals, long ticks first
texts = [] texts = []
for i in [2,1,0]: for i in [2,1,0]:
if i1+i > len(intervals): if i1+i >= len(intervals) or i1+i < 0:
print "AxisItem.paint error: i1=%d, i=%d, len(intervals)=%d" % (i1, i, len(intervals))
continue continue
## spacing for this interval ## spacing for this interval
sp = pw*intervals[i1+i] sp = pw*intervals[i1+i]
## determine starting tick ## determine starting tick

View File

@ -37,7 +37,8 @@ def rectStr(r):
class ROI(GraphicsObject): class ROI(GraphicsObject):
"""Generic region-of-interest widget. """Generic region-of-interest widget.
Can be used for implementing many types of selection box with rotate/translate/scale handles.""" Can be used for implementing many types of selection box with rotate/translate/scale handles.
"""
sigRegionChangeFinished = QtCore.Signal(object) sigRegionChangeFinished = QtCore.Signal(object)
sigRegionChangeStarted = QtCore.Signal(object) sigRegionChangeStarted = QtCore.Signal(object)
@ -53,6 +54,8 @@ class ROI(GraphicsObject):
self.translatable = movable self.translatable = movable
self.rotateAllowed = True self.rotateAllowed = True
self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted.
if pen is None: if pen is None:
pen = (255, 255, 255) pen = (255, 255, 255)
self.setPen(pen) self.setPen(pen)
@ -78,29 +81,33 @@ class ROI(GraphicsObject):
#self.setFlag(self.ItemIsSelectable, True) #self.setFlag(self.ItemIsSelectable, True)
def getState(self): def getState(self):
return self.state.copy() return self.stateCopy()
def stateCopy(self):
sc = {}
sc['pos'] = Point(self.state['pos'])
sc['size'] = Point(self.state['size'])
sc['angle'] = self.state['angle']
return sc
def saveState(self): def saveState(self):
"""Return the state of the widget in a format suitable for storing to disk.""" """Return the state of the widget in a format suitable for storing to disk. (Points are converted to tuple)"""
state = {} state = {}
state['pos'] = tuple(self.state['pos']) state['pos'] = tuple(self.state['pos'])
state['size'] = tuple(self.state['size']) state['size'] = tuple(self.state['size'])
state['angle'] = self.state['angle'] state['angle'] = self.state['angle']
return state return state
def setState(self, state): def setState(self, state, update=True):
self.setPos(state['pos'], update=False) self.setPos(state['pos'], update=False)
self.setSize(state['size'], update=False) self.setSize(state['size'], update=False)
self.setAngle(state['angle']) self.setAngle(state['angle'], update=update)
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:
h['item'].setZValue(z+1) h['item'].setZValue(z+1)
def sceneBounds(self):
return self.sceneTransform().mapRect(self.boundingRect())
def parentBounds(self): def parentBounds(self):
return self.mapToParent(self.boundingRect()).boundingRect() return self.mapToParent(self.boundingRect()).boundingRect()
@ -118,34 +125,123 @@ class ROI(GraphicsObject):
def angle(self): def angle(self):
return self.getState()['angle'] return self.getState()['angle']
def setPos(self, pos, update=True): def setPos(self, pos, update=True, finish=True):
#print "setPos() called." """Set the position of the ROI (in the parent's coordinate system).
By default, this will cause both sigStateChanged and sigStateChangeFinished to be emitted.
If finish is False, then sigStateChangeFinished will not be emitted. You can then use
stateChangeFinished() to cause the signal to be emitted after a series of state changes.
If update is False, the state change will be remembered but not processed and no signals
will be emitted. You can then use stateChanged() to complete the state change. This allows
multiple change functions to be called sequentially while minimizing processing overhead
and repeated signals. Setting update=False also forces finish=False.
"""
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.stateChanged(finish=finish)
self.handleChange()
def setSize(self, size, update=True): def setSize(self, size, update=True, finish=True):
"""Set the size of the ROI. May be specified as a QPoint, Point, or list of two values.
See setPos() for an explanation of the update and finish arguments.
"""
size = Point(size) size = Point(size)
self.prepareGeometryChange() self.prepareGeometryChange()
self.state['size'] = size self.state['size'] = size
if update: if update:
self.updateHandles() self.stateChanged(finish=finish)
self.handleChange()
def setAngle(self, angle, update=True): def setAngle(self, angle, update=True, finish=True):
"""Set the angle of rotation (in degrees) for this ROI.
See setPos() for an explanation of the update and finish arguments.
"""
self.state['angle'] = angle self.state['angle'] = angle
tr = QtGui.QTransform() tr = QtGui.QTransform()
#tr.rotate(-angle * 180 / np.pi) #tr.rotate(-angle * 180 / np.pi)
tr.rotate(angle) tr.rotate(angle)
self.setTransform(tr) self.setTransform(tr)
if update: if update:
self.updateHandles() self.stateChanged(finish=finish)
self.handleChange()
def scale(self, s, center=[0,0], update=True, finish=True):
"""
Resize the ROI by scaling relative to *center*.
See setPos() for an explanation of the *update* and *finish* arguments.
"""
c = self.mapToScene(Point(center) * self.state['size'])
self.prepareGeometryChange()
newSize = self.state['size'] * s
c1 = self.mapToScene(Point(center) * newSize)
newPos = self.state['pos'] + c - c1
self.setSize(newSize, update=False)
self.setPos(self.state['pos'], update=update, finish=finish)
def translate(self, *args, **kargs):
"""
Move the ROI to a new position.
Accepts either (x, y, snap) or ([x,y], snap) as arguments
If the ROI is bounded and the move would exceed boundaries, then the ROI
is moved to the nearest acceptable position instead.
snap can be:
None (default): use self.translateSnap and self.snapSize to determine whether/how to snap
False: do not snap
Point(w,h) snap to rectangular grid with spacing (w,h)
True: snap using self.snapSize (and ignoring self.translateSnap)
Also accepts *update* and *finish* arguments (see setPos() for a description of these).
"""
if len(args) == 1:
pt = args[0]
else:
pt = args
newState = self.stateCopy()
newState['pos'] = newState['pos'] + pt
## snap position
#snap = kargs.get('snap', None)
#if (snap is not False) and not (snap is None and self.translateSnap is False):
snap = kargs.get('snap', None)
if snap is None:
snap = self.translateSnap
if snap is not False:
newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap)
#d = ev.scenePos() - self.mapToScene(self.pressPos)
if self.maxBounds is not None:
r = self.stateRect(newState)
#r0 = self.sceneTransform().mapRect(self.boundingRect())
d = Point(0,0)
if self.maxBounds.left() > r.left():
d[0] = self.maxBounds.left() - r.left()
elif self.maxBounds.right() < r.right():
d[0] = self.maxBounds.right() - r.right()
if self.maxBounds.top() > r.top():
d[1] = self.maxBounds.top() - r.top()
elif self.maxBounds.bottom() < r.bottom():
d[1] = self.maxBounds.bottom() - r.bottom()
newState['pos'] += d
#self.state['pos'] = newState['pos']
update = kargs.get('update', True)
finish = kargs.get('finish', True)
self.setPos(newState['pos'], update=update, finish=finish)
#if 'update' not in kargs or kargs['update'] is True:
#self.stateChanged()
def rotate(self, angle, center=(0,0), angleSnap=False, update=True, finish=True):
pass
#self.setAngle(self.angle()+angle, update=update, finish=finish)
def addTranslateHandle(self, pos, axes=None, item=None, name=None): def addTranslateHandle(self, pos, axes=None, item=None, name=None):
pos = Point(pos) pos = Point(pos)
return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item}) return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item})
@ -236,47 +332,6 @@ class ROI(GraphicsObject):
for h in self.handles: for h in self.handles:
h['item'].hide() h['item'].hide()
#def mousePressEvent(self, ev):
### Bug: sometimes we get events we shouldn't.
#p = ev.pos()
#if not self.isMoving and not self.shape().contains(p):
#ev.ignore()
#return
#if ev.button() == QtCore.Qt.LeftButton:
#self.setSelected(True)
#if self.translatable:
#self.isMoving = True
#self.preMoveState = self.getState()
#self.cursorOffset = self.scenePos() - ev.scenePos()
##self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
#self.sigRegionChangeStarted.emit(self)
#ev.accept()
#else:
#ev.ignore()
#elif ev.button() == QtCore.Qt.RightButton:
#if self.isMoving:
#ev.accept()
#self.cancelMove()
#else:
#ev.ignore()
#else:
#ev.ignore()
#def mouseMoveEvent(self, ev):
##print "mouse move", ev.pos()
#if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton:
#snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None
##if self.translateSnap or (ev.modifiers() & QtCore.Qt.ControlModifier):
##snap = Point(self.snapSize, self.snapSize)
#newPos = ev.scenePos() + self.cursorOffset
#newPos = self.mapSceneToParent(newPos)
#self.translate(newPos - self.pos(), snap=snap)
#def mouseReleaseEvent(self, ev):
#if self.translatable:
#self.isMoving = False
##self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
#self.sigRegionChangeFinished.emit(self)
def hoverEvent(self, ev): def hoverEvent(self, ev):
if self.translatable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): if self.translatable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
@ -288,7 +343,7 @@ class ROI(GraphicsObject):
def mouseDragEvent(self, ev): def mouseDragEvent(self, ev):
if ev.isStart(): if ev.isStart():
p = ev.pos() #p = ev.pos()
#if not self.isMoving and not self.shape().contains(p): #if not self.isMoving and not self.shape().contains(p):
#ev.ignore() #ev.ignore()
#return #return
@ -298,7 +353,6 @@ class ROI(GraphicsObject):
self.isMoving = True self.isMoving = True
self.preMoveState = self.getState() self.preMoveState = self.getState()
self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos())
#self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
self.sigRegionChangeStarted.emit(self) self.sigRegionChangeStarted.emit(self)
ev.accept() ev.accept()
else: else:
@ -306,21 +360,16 @@ class ROI(GraphicsObject):
elif ev.isFinish(): elif ev.isFinish():
if self.translatable: if self.translatable:
if self.isMoving:
self.stateChangeFinished()
self.isMoving = False self.isMoving = False
#self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
self.sigRegionChangeFinished.emit(self)
return return
if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton: if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton:
snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None
#if self.translateSnap or (ev.modifiers() & QtCore.Qt.ControlModifier):
#snap = Point(self.snapSize, self.snapSize)
newPos = self.mapToParent(ev.pos()) + self.cursorOffset newPos = self.mapToParent(ev.pos()) + self.cursorOffset
#newPos = self.mapSceneToParent(newPos) self.translate(newPos - self.pos(), snap=snap, finish=False)
self.translate(newPos - self.pos(), snap=snap)
def mouseClickEvent(self, ev): def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton: if ev.button() == QtCore.Qt.RightButton:
if self.isMoving: if self.isMoving:
@ -329,24 +378,24 @@ class ROI(GraphicsObject):
else: else:
ev.ignore() ev.ignore()
def cancelMove(self): def cancelMove(self):
self.isMoving = False self.isMoving = False
self.setState(self.preMoveState) self.setState(self.preMoveState)
def pointDragEvent(self, pt, ev): #def pointDragEvent(self, pt, ev):
if ev.isStart(): ### just for handling drag start/stop.
self.isMoving = True ### drag moves are handled through movePoint()
self.preMoveState = self.getState()
#if ev.isStart():
#self.isMoving = True
#self.preMoveState = self.getState()
self.sigRegionChangeStarted.emit(self) #self.sigRegionChangeStarted.emit(self)
elif ev.isFinish(): #elif ev.isFinish():
self.isMoving = False #self.isMoving = False
self.sigRegionChangeFinished.emit(self) #self.sigRegionChangeFinished.emit(self)
return #return
#self.movePoint(pt, ev.scenePos(), ev.modifiers())
#def pointPressEvent(self, pt, ev): #def pointPressEvent(self, pt, ev):
@ -368,37 +417,20 @@ class ROI(GraphicsObject):
#def pointMoveEvent(self, pt, ev): #def pointMoveEvent(self, pt, ev):
#self.movePoint(pt, ev.scenePos(), ev.modifiers()) #self.movePoint(pt, ev.scenePos(), ev.modifiers())
def stateCopy(self):
sc = {}
sc['pos'] = Point(self.state['pos'])
sc['size'] = Point(self.state['size'])
sc['angle'] = self.state['angle']
return sc
def updateHandles(self):
#print "update", self.handles
for h in self.handles:
#print " try", h
if h['item'] in self.childItems():
p = h['pos']
#print h['pos'] * self.state['size']
h['item'].setPos(h['pos'] * self.state['size'])
#else:
#print " Not child!", self.childItems()
def checkPointMove(self, pt, pos, modifiers): def checkPointMove(self, pt, pos, modifiers):
"""When handles move, they must ask the ROI if the move is acceptable.
By default, this always returns True. Subclasses may wish override.
"""
return True return True
def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()): def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True):
#print "movePoint() called." ## called by Handles when they are moved.
## pos is the new position of the handle in scene coords, as requested by the handle. ## pos is the new position of the handle in scene coords, as requested by the handle.
newState = self.stateCopy() newState = self.stateCopy()
h = self.handles[pt] h = self.handles[pt]
#p0 = self.mapToScene(h['item'].pos())
## p0 is current (before move) position of handle in scene coords
p0 = self.mapToScene(h['pos'] * self.state['size']) p0 = self.mapToScene(h['pos'] * self.state['size'])
p1 = Point(pos) p1 = Point(pos)
@ -410,15 +442,10 @@ class ROI(GraphicsObject):
if h.has_key('center'): if h.has_key('center'):
c = h['center'] c = h['center']
cs = c * self.state['size'] cs = c * self.state['size']
#lpOrig = h['pos'] -
#lp0 = self.mapFromScene(p0) - cs
#lp1 = self.mapFromScene(p1) - cs
lp0 = self.mapFromParent(p0) - cs lp0 = self.mapFromParent(p0) - cs
lp1 = self.mapFromParent(p1) - cs lp1 = self.mapFromParent(p1) - cs
if h['type'] == 't': if h['type'] == 't':
#p0 = Point(self.mapToScene(h['item'].pos()))
#p1 = Point(pos + self.mapToScene(self.pressHandlePos) - self.mapToScene(self.pressPos))
snap = True if (modifiers & QtCore.Qt.ControlModifier) else None snap = True if (modifiers & QtCore.Qt.ControlModifier) else None
#if self.translateSnap or (): #if self.translateSnap or ():
#snap = Point(self.snapSize, self.snapSize) #snap = Point(self.snapSize, self.snapSize)
@ -426,14 +453,10 @@ class ROI(GraphicsObject):
elif h['type'] == 'f': elif h['type'] == 'f':
h['item'].setPos(self.mapFromScene(pos)) h['item'].setPos(self.mapFromScene(pos))
#self.emit(QtCore.SIGNAL('regionChanged'), self) self.freeHandleMoved = True
self.sigRegionChanged.emit(self) #self.sigRegionChanged.emit(self) ## should be taken care of by call to stateChanged()
elif h['type'] == 's': elif h['type'] == 's':
#c = h['center']
#cs = c * self.state['size']
#p1 = (self.mapFromScene(ev.scenePos()) + self.pressHandlePos - self.pressPos) - cs
## If a handle and its center have the same x or y value, we can't scale across that axis. ## If a handle and its center have the same x or y value, we can't scale across that axis.
if h['center'][0] == h['pos'][0]: if h['center'][0] == h['pos'][0]:
lp1[0] = 0 lp1[0] = 0
@ -485,13 +508,12 @@ class ROI(GraphicsObject):
return return
self.setPos(newState['pos'], update=False) self.setPos(newState['pos'], update=False)
self.prepareGeometryChange() self.setSize(newState['size'], update=False)
self.state = newState
## move handles to their new locations
self.updateHandles()
elif h['type'] in ['r', 'rf']: elif h['type'] in ['r', 'rf']:
if h['type'] == 'rf':
self.freeHandleMoved = True
if not self.rotateAllowed: if not self.rotateAllowed:
return return
## If the handle is directly over its center point, we can't compute an angle. ## If the handle is directly over its center point, we can't compute an angle.
@ -507,10 +529,9 @@ class ROI(GraphicsObject):
## create rotation transform ## create rotation transform
tr = QtGui.QTransform() tr = QtGui.QTransform()
#tr.rotate(-ang * 180. / np.pi)
tr.rotate(ang) tr.rotate(ang)
## mvoe ROI so that center point remains stationary after rotate ## move ROI so that center point remains stationary after rotate
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
newState['pos'] = newState['pos'] + cc newState['pos'] = newState['pos'] + cc
@ -520,60 +541,22 @@ class ROI(GraphicsObject):
r = self.stateRect(newState) r = self.stateRect(newState)
if not self.maxBounds.contains(r): if not self.maxBounds.contains(r):
return return
self.setTransform(tr) #self.setTransform(tr)
self.setPos(newState['pos'], update=False) self.setPos(newState['pos'], update=False)
self.state = newState self.setAngle(ang, update=False)
#self.state = newState
## If this is a free-rotate handle, its distance from the center may change. ## If this is a free-rotate handle, its distance from the center may change.
if h['type'] == 'rf': if h['type'] == 'rf':
h['item'].setPos(self.mapFromScene(p1)) ## changes ROI coordinates of handle h['item'].setPos(self.mapFromScene(p1)) ## changes ROI coordinates of handle
#elif h['type'] == 'rf':
### If the handle is directly over its center point, we can't compute an angle.
#if lp1.length() == 0 or lp0.length() == 0:
#return
### determine new rotation angle, constrained if necessary
#pos = Point(pos)
#ang = newState['angle'] + lp0.angle(lp1)
#if ang is None:
##h['item'].setPos(self.mapFromScene(Point(pos[0], 0.0))) ## changes ROI coordinates of handle
#h['item'].setPos(self.mapFromScene(pos))
#return
#if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
#ang = round(ang / (np.pi/12.)) * (np.pi/12.)
#tr = QtGui.QTransform()
#tr.rotate(-ang * 180. / np.pi)
#cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos'])
#newState['angle'] = ang
#newState['pos'] = newState['pos'] + cc
#if self.maxBounds is not None:
#r = self.stateRect(newState)
#if not self.maxBounds.contains(r):
#return
#self.setTransform(tr)
#self.setPos(newState['pos'], update=False)
#self.state = newState
#h['item'].setPos(self.mapFromScene(pos)) ## changes ROI coordinates of handle
##self.emit(QtCore.SIGNAL('regionChanged'), self)
elif h['type'] == 'sr': elif h['type'] == 'sr':
#newState = self.stateCopy()
if h['center'][0] == h['pos'][0]: if h['center'][0] == h['pos'][0]:
scaleAxis = 1 scaleAxis = 1
else: else:
scaleAxis = 0 scaleAxis = 0
#c = h['center']
#cs = c * self.state['size']
#p0 = Point(h['item'].pos()) - cs
#p1 = (self.mapFromScene(ev.scenePos()) + self.pressHandlePos - self.pressPos) - cs
if lp1.length() == 0 or lp0.length() == 0: if lp1.length() == 0 or lp0.length() == 0:
return return
@ -586,14 +569,14 @@ class ROI(GraphicsObject):
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
if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): #if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier):
if self.scaleSnap: ## use CTRL only for angular snap here.
newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize
if newState['size'][scaleAxis] == 0: if newState['size'][scaleAxis] == 0:
newState['size'][scaleAxis] = 1 newState['size'][scaleAxis] = 1
c1 = c * newState['size'] c1 = c * newState['size']
tr = QtGui.QTransform() tr = QtGui.QTransform()
#tr.rotate(-ang * 180. / np.pi)
tr.rotate(ang) tr.rotate(ang)
cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos']) cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos'])
@ -603,94 +586,48 @@ class ROI(GraphicsObject):
r = self.stateRect(newState) r = self.stateRect(newState)
if not self.maxBounds.contains(r): if not self.maxBounds.contains(r):
return return
self.setTransform(tr) #self.setTransform(tr)
self.setPos(newState['pos'], update=False) #self.setPos(newState['pos'], update=False)
self.prepareGeometryChange() #self.prepareGeometryChange()
self.state = newState #self.state = newState
self.setState(newState, update=False)
self.updateHandles() self.stateChanged(finish=finish)
self.handleChange()
def handleChange(self): def stateChanged(self, finish=True):
"""The state of the ROI has changed; redraw if needed.""" """Process changes to the state of the ROI.
#print "handleChange() called." If there are any changes, then the positions of handles are updated accordingly
and sigRegionChanged is emitted. If finish is True, then
sigRegionChangeFinished will also be emitted."""
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
changed = True changed = True
self.lastState = self.stateCopy() self.lastState = self.stateCopy()
#print "changed =", changed
if changed: if changed:
#print "handle changed." ## Move all handles to match the current configuration of the ROI
for h in self.handles:
if h['item'] in self.childItems():
p = h['pos']
h['item'].setPos(h['pos'] * self.state['size'])
self.update() self.update()
#self.emit(QtCore.SIGNAL('regionChanged'), self) self.sigRegionChanged.emit(self)
elif self.freeHandleMoved:
self.sigRegionChanged.emit(self) self.sigRegionChanged.emit(self)
self.freeHandleMoved = False
def scale(self, s, center=[0,0]):
c = self.mapToScene(Point(center) * self.state['size'])
self.prepareGeometryChange()
self.state['size'] = self.state['size'] * s
c1 = self.mapToScene(Point(center) * self.state['size'])
self.state['pos'] = self.state['pos'] + c - c1
self.setPos(self.state['pos'])
self.updateHandles()
def translate(self, *args, **kargs):
"""accepts either (x, y, snap) or ([x,y], snap) as arguments
snap can be:
None (default): use self.translateSnap and self.snapSize to determine whether/how to snap
False: do no snap
Point(w,h) snap to rectangular grid with spacing (w,h)
True: snap using self.snapSize (and ignoring self.translateSnap)
"""
if len(args) == 1:
pt = args[0]
else:
pt = args
newState = self.stateCopy() if finish:
newState['pos'] = newState['pos'] + pt self.stateChangeFinished()
## snap position def stateChangeFinished(self):
#snap = kargs.get('snap', None) self.sigRegionChangeFinished.emit(self)
#if (snap is not False) and not (snap is None and self.translateSnap is False):
snap = kargs.get('snap', None)
if snap is None:
snap = self.translateSnap
if snap is not False:
newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap)
#d = ev.scenePos() - self.mapToScene(self.pressPos)
if self.maxBounds is not None:
r = self.stateRect(newState)
#r0 = self.sceneTransform().mapRect(self.boundingRect())
d = Point(0,0)
if self.maxBounds.left() > r.left():
d[0] = self.maxBounds.left() - r.left()
elif self.maxBounds.right() < r.right():
d[0] = self.maxBounds.right() - r.right()
if self.maxBounds.top() > r.top():
d[1] = self.maxBounds.top() - r.top()
elif self.maxBounds.bottom() < r.bottom():
d[1] = self.maxBounds.bottom() - r.bottom()
newState['pos'] += d
self.state['pos'] = newState['pos']
self.setPos(self.state['pos'])
#if 'update' not in kargs or kargs['update'] is True:
self.handleChange()
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])
@ -950,166 +887,6 @@ class ROI(GraphicsObject):
self.setState(st) self.setState(st)
#class Handle(QtGui.QGraphicsItem):
#types = { ## defines number of sides, start angle for each handle type
#'t': (4, np.pi/4),
#'f': (4, np.pi/4),
#'s': (4, 0),
#'r': (12, 0),
#'sr': (12, 0),
#'rf': (12, 0),
#}
#def __init__(self, radius, typ=None, pen=(200, 200, 220), parent=None):
##print " create item with parent", parent
#self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10)
#QtGui.QGraphicsItem.__init__(self, parent)
#self.setFlags(self.flags() | self.ItemIgnoresTransformations | self.ItemSendsScenePositionChanges)
#self.setZValue(11)
#self.roi = []
#self.radius = radius
#self.typ = typ
#self.pen = fn.mkPen(pen)
#self.currentPen = self.pen
#self.pen.setWidth(0)
#self.pen.setCosmetic(True)
#self.isMoving = False
#self.sides, self.startAng = self.types[typ]
#self.buildPath()
#self.updateShape()
#def connectROI(self, roi, i):
#self.roi.append((roi, i))
##def boundingRect(self):
##return self.bounds
#def hoverEvent(self, ev):
#if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
#self.currentPen = fn.mkPen(255, 0,0)
#else:
#self.currentPen = self.pen
#self.update()
#def mouseClickEvent(self, ev):
### right-click cancels drag
#if ev.button() == QtCore.Qt.RightButton and self.isMoving:
#self.isMoving = False ## prevents any further motion
#for r in self.roi:
#r[0].cancelMove()
#ev.accept()
#def mouseDragEvent(self, ev):
#if ev.button() != QtCore.Qt.LeftButton:
#return
#ev.accept()
### Inform ROIs that a drag is happening
### note: the ROI is informed that the handle has moved using ROI.movePoint
### this is for other (more nefarious) purposes.
#for r in self.roi:
#r[0].pointDragEvent(r[1], ev)
#if ev.isFinish():
#self.isMoving = False
#elif ev.isStart():
#self.isMoving = True
#self.cursorOffset = self.scenePos() - ev.buttonDownScenePos()
#if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click.
#pos = ev.scenePos() + self.cursorOffset
#self.movePoint(pos, ev.modifiers())
#def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier()):
#for r in self.roi:
#if not r[0].checkPointMove(r[1], pos, modifiers):
#return
##print "point moved; inform %d ROIs" % len(self.roi)
## A handle can be used by multiple ROIs; tell each to update its handle position
#for r in self.roi:
#r[0].movePoint(r[1], pos, modifiers)
#def buildPath(self):
#size = self.radius
#self.path = QtGui.QPainterPath()
#ang = self.startAng
#dt = 2*np.pi / self.sides
#for i in range(0, self.sides+1):
#x = size * cos(ang)
#y = size * sin(ang)
#ang += dt
#if i == 0:
#self.path.moveTo(x, y)
#else:
#self.path.lineTo(x, y)
#def paint(self, p, opt, widget):
#### determine rotation of transform
##m = self.sceneTransform()
###mi = m.inverted()[0]
##v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0))
##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))
###size = (size.x()*size.x() + size.y() * size.y()) ** 0.5
##size = self.radius
##bounds = QtCore.QRectF(-size, -size, size*2, size*2)
##if bounds != self.bounds:
##self.bounds = bounds
##self.prepareGeometryChange()
#p.setRenderHints(p.Antialiasing, True)
#p.setPen(self.currentPen)
##p.rotate(va * 180. / 3.1415926)
##p.drawPath(self.path)
#p.drawPath(self.shape())
##ang = self.startAng + va
##dt = 2*np.pi / self.sides
##for i in range(0, self.sides):
##x1 = size * cos(ang)
##y1 = size * sin(ang)
##x2 = size * cos(ang+dt)
##y2 = size * sin(ang+dt)
##ang += dt
##p.drawLine(Point(x1, y1), Point(x2, y2))
#def shape(self):
#return self._shape
#def boundingRect(self):
#return self.shape().boundingRect()
#def updateShape(self):
### determine rotation of transform
#m = self.sceneTransform()
##mi = m.inverted()[0]
#v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0))
#va = np.arctan2(v.y(), v.x())
#tr = QtGui.QTransform()
#tr.rotate(va * 180. / 3.1415926)
##tr.scale(self.radius, self.radius)
#self._shape = tr.map(self.path)
#self.prepareGeometryChange()
#def itemChange(self, change, value):
#ret = QtGui.QGraphicsItem.itemChange(self, change, value)
#if change == self.ItemScenePositionHasChanged:
#self.updateShape()
#return ret
class Handle(UIGraphicsItem): class Handle(UIGraphicsItem):
types = { ## defines number of sides, start angle for each handle type types = { ## defines number of sides, start angle for each handle type
@ -1160,8 +937,9 @@ class Handle(UIGraphicsItem):
## right-click cancels drag ## right-click cancels drag
if ev.button() == QtCore.Qt.RightButton and self.isMoving: if ev.button() == QtCore.Qt.RightButton and self.isMoving:
self.isMoving = False ## prevents any further motion self.isMoving = False ## prevents any further motion
for r in self.roi: self.movePoint(self.startPos, finish=True)
r[0].cancelMove() #for r in self.roi:
#r[0].cancelMove()
ev.accept() ev.accept()
@ -1173,29 +951,31 @@ class Handle(UIGraphicsItem):
## Inform ROIs that a drag is happening ## Inform ROIs that a drag is happening
## note: the ROI is informed that the handle has moved using ROI.movePoint ## note: the ROI is informed that the handle has moved using ROI.movePoint
## this is for other (more nefarious) purposes. ## this is for other (more nefarious) purposes.
for r in self.roi: #for r in self.roi:
r[0].pointDragEvent(r[1], ev) #r[0].pointDragEvent(r[1], ev)
if ev.isFinish(): if ev.isFinish():
if self.isMoving:
for r in self.roi:
r[0].stateChangeFinished()
self.isMoving = False self.isMoving = False
elif ev.isStart(): elif ev.isStart():
self.isMoving = True self.isMoving = True
self.startPos = self.scenePos()
self.cursorOffset = self.scenePos() - ev.buttonDownScenePos() self.cursorOffset = self.scenePos() - ev.buttonDownScenePos()
if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click. if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click.
pos = ev.scenePos() + self.cursorOffset pos = ev.scenePos() + self.cursorOffset
self.movePoint(pos, ev.modifiers()) self.movePoint(pos, ev.modifiers(), finish=False)
def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True):
def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier()):
for r in self.roi: for r in self.roi:
if not r[0].checkPointMove(r[1], pos, modifiers): if not r[0].checkPointMove(r[1], pos, modifiers):
return return
#print "point moved; inform %d ROIs" % len(self.roi) #print "point moved; inform %d ROIs" % len(self.roi)
# A handle can be used by multiple ROIs; tell each to update its handle position # A handle can be used by multiple ROIs; tell each to update its handle position
for r in self.roi: for r in self.roi:
r[0].movePoint(r[1], pos, modifiers) r[0].movePoint(r[1], pos, modifiers, finish=finish)
def buildPath(self): def buildPath(self):
size = self.radius size = self.radius
@ -1629,8 +1409,8 @@ class SpiralROI(ROI):
for h in self.handles: for h in self.handles:
h['pos'] = h['item'].pos()/self.state['size'][0] h['pos'] = h['item'].pos()/self.state['size'][0]
def handleChange(self): def stateChanged(self):
ROI.handleChange(self) ROI.stateChanged(self)
if len(self.handles) > 1: if len(self.handles) > 1:
self.path = QtGui.QPainterPath() self.path = QtGui.QPainterPath()
h0 = Point(self.handles[0]['item'].pos()).length() h0 = Point(self.handles[0]['item'].pos()).length()

View File

@ -206,7 +206,10 @@ class ViewBox(GraphicsWidget):
#print "addItem:", item, item.boundingRect() #print "addItem:", item, item.boundingRect()
def removeItem(self, item): def removeItem(self, item):
self.addedItems.remove(item) try:
self.addedItems.remove(item)
except:
pass
self.scene().removeItem(item) self.scene().removeItem(item)
self.updateAutoRange() self.updateAutoRange()

View File

@ -23,9 +23,9 @@ from pyqtgraph.graphicsItems.ViewBox import *
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import sys import sys
#from numpy import ndarray #from numpy import ndarray
import ptime import pyqtgraph.ptime as ptime
import numpy as np import numpy as np
import debug import pyqtgraph.debug as debug
from pyqtgraph.SignalProxy import SignalProxy from pyqtgraph.SignalProxy import SignalProxy

View File

@ -11,7 +11,7 @@ from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL, QtSvg
from pyqtgraph.Point import Point from pyqtgraph.Point import Point
#from vector import * #from vector import *
import sys, os import sys, os
import debug #import debug
from FileDialog import FileDialog from FileDialog import FileDialog
from pyqtgraph.GraphicsScene import GraphicsScene from pyqtgraph.GraphicsScene import GraphicsScene
import numpy as np import numpy as np