ROI updates:
- ROI.movePoint now expects parent coordinates by default - Added ROI.getHandles() - Renamed MultiLineROI to MultiRectROI - Reorganized MultiRectROI, added addSegment and removeSegment methods (thanks Martin!)
This commit is contained in:
parent
2c679dfbcc
commit
eab1d75592
@ -36,12 +36,16 @@ img1a = pg.ImageItem(arr)
|
||||
v1a.addItem(img1a)
|
||||
img1b = pg.ImageItem()
|
||||
v1b.addItem(img1b)
|
||||
v1a.disableAutoRange('xy')
|
||||
v1b.disableAutoRange('xy')
|
||||
v1a.autoRange()
|
||||
v1b.autoRange()
|
||||
|
||||
rois = []
|
||||
rois.append(pg.RectROI([20, 20], [20, 20], pen=(0,9)))
|
||||
rois[-1].addRotateHandle([1,0], [0.5, 0.5])
|
||||
rois.append(pg.LineROI([0, 60], [20, 80], width=5, pen=(1,9)))
|
||||
rois.append(pg.MultiLineROI([[20, 90], [50, 60], [60, 90]], width=5, pen=(2,9)))
|
||||
rois.append(pg.MultiRectROI([[20, 90], [50, 60], [60, 90]], width=5, pen=(2,9)))
|
||||
rois.append(pg.EllipseROI([60, 10], [30, 20], pen=(3,9)))
|
||||
rois.append(pg.CircleROI([80, 50], [20, 20], pen=(4,9)))
|
||||
#rois.append(pg.LineSegmentROI([[110, 50], [20, 20]], pen=(5,9)))
|
||||
@ -70,6 +74,10 @@ r2a = pg.PolyLineROI([[0,0], [10,10], [10,30], [30,10]], closed=True)
|
||||
v2a.addItem(r2a)
|
||||
r2b = pg.PolyLineROI([[0,-20], [10,-10], [10,-30]], closed=False)
|
||||
v2a.addItem(r2b)
|
||||
v2a.disableAutoRange('xy')
|
||||
#v2b.disableAutoRange('xy')
|
||||
v2a.autoRange()
|
||||
#v2b.autoRange()
|
||||
|
||||
text = """Building custom ROI types<Br>
|
||||
ROIs can be built with a variety of different handle types<br>
|
||||
@ -107,6 +115,9 @@ r3b.addRotateHandle([0, 1], [1, 0])
|
||||
r3b.addScaleRotateHandle([0, 0.5], [0.5, 0.5])
|
||||
r3b.addScaleRotateHandle([1, 0.5], [0.5, 0.5])
|
||||
|
||||
v3.disableAutoRange('xy')
|
||||
v3.autoRange()
|
||||
|
||||
|
||||
text = """Transforming objects with ROI"""
|
||||
w4 = w.addLayout(row=1, col=1)
|
||||
@ -121,6 +132,9 @@ img4 = pg.ImageItem(arr)
|
||||
v4.addItem(r4)
|
||||
img4.setParentItem(r4)
|
||||
|
||||
v4.disableAutoRange('xy')
|
||||
v4.autoRange()
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -28,7 +28,7 @@ from .UIGraphicsItem import UIGraphicsItem
|
||||
__all__ = [
|
||||
'ROI',
|
||||
'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI',
|
||||
'LineROI', 'MultiLineROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI',
|
||||
'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI',
|
||||
]
|
||||
|
||||
|
||||
@ -370,7 +370,9 @@ class ROI(GraphicsObject):
|
||||
else:
|
||||
return (self.handles[index]['name'], self.handles[index]['item'].scenePos())
|
||||
|
||||
|
||||
def getHandles(self):
|
||||
return [h['item'] for h in self.handles]
|
||||
|
||||
def mapSceneToParent(self, pt):
|
||||
return self.mapToParent(self.mapFromScene(pt))
|
||||
|
||||
@ -538,19 +540,27 @@ class ROI(GraphicsObject):
|
||||
return True
|
||||
|
||||
|
||||
def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True):
|
||||
def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True, coords='parent'):
|
||||
## called by Handles when they are moved.
|
||||
## pos is the new position of the handle in scene coords, as requested by the handle.
|
||||
|
||||
newState = self.stateCopy()
|
||||
index = self.indexOfHandle(handle)
|
||||
h = self.handles[index]
|
||||
p0 = self.mapToScene(h['pos'] * self.state['size'])
|
||||
p0 = self.mapToParent(h['pos'] * self.state['size'])
|
||||
p1 = Point(pos)
|
||||
|
||||
if coords == 'parent':
|
||||
pass
|
||||
elif coords == 'scene':
|
||||
p1 = self.mapSceneToParent(p1)
|
||||
else:
|
||||
raise Exception("New point location must be given in either 'parent' or 'scene' coordinates.")
|
||||
|
||||
|
||||
## transform p0 and p1 into parent's coordinates (same as scene coords if there is no parent). I forget why.
|
||||
p0 = self.mapSceneToParent(p0)
|
||||
p1 = self.mapSceneToParent(p1)
|
||||
#p0 = self.mapSceneToParent(p0)
|
||||
#p1 = self.mapSceneToParent(p1)
|
||||
|
||||
## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1)
|
||||
if 'center' in h:
|
||||
@ -566,8 +576,8 @@ class ROI(GraphicsObject):
|
||||
self.translate(p1-p0, snap=snap, update=False)
|
||||
|
||||
elif h['type'] == 'f':
|
||||
newPos = self.mapFromScene(pos)
|
||||
h['item'].setPos(self.mapFromScene(pos))
|
||||
newPos = self.mapFromParent(p1)
|
||||
h['item'].setPos(newPos)
|
||||
h['pos'] = newPos
|
||||
self.freeHandleMoved = True
|
||||
#self.sigRegionChanged.emit(self) ## should be taken care of by call to stateChanged()
|
||||
@ -1212,7 +1222,7 @@ class Handle(UIGraphicsItem):
|
||||
#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.rois:
|
||||
r.movePoint(self, pos, modifiers, finish=finish)
|
||||
r.movePoint(self, pos, modifiers, finish=finish, coords='scene')
|
||||
|
||||
def buildPath(self):
|
||||
size = self.radius
|
||||
@ -1264,9 +1274,9 @@ class Handle(UIGraphicsItem):
|
||||
if self._shape is None:
|
||||
s = self.generateShape()
|
||||
if s is None:
|
||||
return self.shape
|
||||
return self.path
|
||||
self._shape = s
|
||||
self.prepareGeometryChange()
|
||||
self.prepareGeometryChange() ## beware--this can cause the view to adjust, which would immediately invalidate the shape.
|
||||
return self._shape
|
||||
|
||||
def boundingRect(self):
|
||||
@ -1357,9 +1367,16 @@ class LineROI(ROI):
|
||||
self.addScaleRotateHandle([1, 0.5], [0, 0.5])
|
||||
self.addScaleHandle([0.5, 1], [0.5, 0.5])
|
||||
|
||||
|
||||
|
||||
class MultiLineROI(QtGui.QGraphicsObject):
|
||||
class MultiRectROI(QtGui.QGraphicsObject):
|
||||
"""
|
||||
Chain of rectangular ROIs connected by handles.
|
||||
|
||||
This is generally used to mark a curved path through
|
||||
an image similarly to PolyLineROI. It differs in that each segment
|
||||
of the chain is rectangular instead of linear and thus has width.
|
||||
"""
|
||||
sigRegionChangeFinished = QtCore.Signal(object)
|
||||
sigRegionChangeStarted = QtCore.Signal(object)
|
||||
sigRegionChanged = QtCore.Signal(object)
|
||||
@ -1368,27 +1385,17 @@ class MultiLineROI(QtGui.QGraphicsObject):
|
||||
QtGui.QGraphicsObject.__init__(self)
|
||||
self.pen = pen
|
||||
self.roiArgs = args
|
||||
self.lines = []
|
||||
if len(points) < 2:
|
||||
raise Exception("Must start with at least 2 points")
|
||||
self.lines = []
|
||||
self.lines.append(ROI([0, 0], [1, 5], parent=self, pen=pen, **args))
|
||||
self.lines[-1].addScaleHandle([0.5, 1], [0.5, 0.5])
|
||||
h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5])
|
||||
h.movePoint(points[0])
|
||||
h.movePoint(points[0])
|
||||
for i in range(1, len(points)):
|
||||
h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5])
|
||||
if i < len(points)-1:
|
||||
self.lines.append(ROI([0, 0], [1, 5], parent=self, pen=pen, **args))
|
||||
self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=h)
|
||||
h.movePoint(points[i])
|
||||
h.movePoint(points[i])
|
||||
|
||||
for l in self.lines:
|
||||
l.translatable = False
|
||||
l.sigRegionChanged.connect(self.roiChangedEvent)
|
||||
l.sigRegionChangeStarted.connect(self.roiChangeStartedEvent)
|
||||
l.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent)
|
||||
|
||||
## create first segment
|
||||
self.addSegment(points[1], connectTo=points[0], scaleHandle=True)
|
||||
|
||||
## create remaining segments
|
||||
for p in points[2:]:
|
||||
self.addSegment(p)
|
||||
|
||||
|
||||
def paint(self, *args):
|
||||
pass
|
||||
@ -1411,7 +1418,13 @@ class MultiLineROI(QtGui.QGraphicsObject):
|
||||
def roiChangeFinishedEvent(self):
|
||||
self.sigRegionChangeFinished.emit(self)
|
||||
|
||||
|
||||
def getHandlePositions(self):
|
||||
"""Return the positions of all handles in local coordinates."""
|
||||
pos = [self.mapFromScene(self.lines[0].getHandles()[0].scenePos())]
|
||||
for l in self.lines:
|
||||
pos.append(self.mapFromScene(l.getHandles()[1].scenePos()))
|
||||
return pos
|
||||
|
||||
def getArrayRegion(self, arr, img=None, axes=(0,1)):
|
||||
rgns = []
|
||||
for l in self.lines:
|
||||
@ -1432,6 +1445,59 @@ class MultiLineROI(QtGui.QGraphicsObject):
|
||||
|
||||
return np.concatenate(rgns, axis=axes[0])
|
||||
|
||||
def addSegment(self, pos=(0,0), scaleHandle=False, connectTo=None):
|
||||
"""
|
||||
Add a new segment to the ROI connecting from the previous endpoint to *pos*.
|
||||
(pos is specified in the parent coordinate system of the MultiRectROI)
|
||||
"""
|
||||
|
||||
## by default, connect to the previous endpoint
|
||||
if connectTo is None:
|
||||
connectTo = self.lines[-1].getHandles()[1]
|
||||
|
||||
## create new ROI
|
||||
newRoi = ROI((0,0), [1, 5], parent=self, pen=self.pen, **self.roiArgs)
|
||||
self.lines.append(newRoi)
|
||||
|
||||
## Add first SR handle
|
||||
if isinstance(connectTo, Handle):
|
||||
self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=connectTo)
|
||||
newRoi.movePoint(connectTo, connectTo.scenePos(), coords='scene')
|
||||
else:
|
||||
h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5])
|
||||
newRoi.movePoint(h, connectTo, coords='scene')
|
||||
|
||||
## add second SR handle
|
||||
h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5])
|
||||
newRoi.movePoint(h, pos)
|
||||
|
||||
## optionally add scale handle (this MUST come after the two SR handles)
|
||||
if scaleHandle:
|
||||
newRoi.addScaleHandle([0.5, 1], [0.5, 0.5])
|
||||
|
||||
newRoi.translatable = False
|
||||
newRoi.sigRegionChanged.connect(self.roiChangedEvent)
|
||||
newRoi.sigRegionChangeStarted.connect(self.roiChangeStartedEvent)
|
||||
newRoi.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent)
|
||||
self.sigRegionChanged.emit(self)
|
||||
|
||||
|
||||
def removeSegment(self, index=-1):
|
||||
"""Remove a segment from the ROI."""
|
||||
roi = self.lines[index]
|
||||
self.lines.pop(index)
|
||||
self.scene().removeItem(roi)
|
||||
roi.sigRegionChanged.disconnect(self.roiChangedEvent)
|
||||
roi.sigRegionChangeStarted.disconnect(self.roiChangeStartedEvent)
|
||||
roi.sigRegionChangeFinished.disconnect(self.roiChangeFinishedEvent)
|
||||
|
||||
self.sigRegionChanged.emit(self)
|
||||
|
||||
|
||||
class MultiLineROI(MultiRectROI):
|
||||
def __init__(self, *args, **kwds):
|
||||
MultiRectROI.__init__(self, *args, **kwds)
|
||||
print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)")
|
||||
|
||||
class EllipseROI(ROI):
|
||||
def __init__(self, pos, size, **args):
|
||||
@ -1475,6 +1541,8 @@ 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):
|
||||
## deprecated. Use PloyLineROI instead.
|
||||
|
||||
def __init__(self, positions, pos=None, **args):
|
||||
if pos is None:
|
||||
pos = [0,0]
|
||||
@ -1483,16 +1551,17 @@ class PolygonROI(ROI):
|
||||
for p in positions:
|
||||
self.addFreeHandle(p)
|
||||
self.setZValue(1000)
|
||||
print("Warning: PolygonROI is deprecated. Use PolyLineROI instead.")
|
||||
|
||||
|
||||
def listPoints(self):
|
||||
return [p['item'].pos() for p in self.handles]
|
||||
|
||||
def movePoint(self, *args, **kargs):
|
||||
ROI.movePoint(self, *args, **kargs)
|
||||
self.prepareGeometryChange()
|
||||
for h in self.handles:
|
||||
h['pos'] = h['item'].pos()
|
||||
#def movePoint(self, *args, **kargs):
|
||||
#ROI.movePoint(self, *args, **kargs)
|
||||
#self.prepareGeometryChange()
|
||||
#for h in self.handles:
|
||||
#h['pos'] = h['item'].pos()
|
||||
|
||||
def paint(self, p, *args):
|
||||
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
@ -1687,103 +1756,33 @@ class LineSegmentROI(ROI):
|
||||
ROI.__init__(self, pos, [1,1], **args)
|
||||
#ROI.__init__(self, positions[0])
|
||||
if len(positions) > 2:
|
||||
raise Exception("LineSegmentROI can only be defined by 2 positions. This is an API change.")
|
||||
raise Exception("LineSegmentROI must be defined by exactly 2 positions. For more points, use PolyLineROI.")
|
||||
|
||||
for i, p in enumerate(positions):
|
||||
self.addFreeHandle(p, item=handles[i])
|
||||
|
||||
#self.setZValue(1000)
|
||||
#self.parentROI = None
|
||||
#self.hasParentROI = False
|
||||
#self.setAcceptsHandles(acceptsHandles)
|
||||
|
||||
#def setParentROI(self, parent):
|
||||
#self.parentROI = parent
|
||||
#if parent != None:
|
||||
#self.hasParentROI = True
|
||||
#else:
|
||||
#self.hasParentROI = False
|
||||
|
||||
#def setAcceptsHandles(self, b):
|
||||
#if b:
|
||||
#self.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
|
||||
#else:
|
||||
#self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
|
||||
|
||||
#def close(self):
|
||||
##for h in self.handles:
|
||||
##if len(h['item'].roi) == 1:
|
||||
##h['item'].scene().removeItem(h['item'])
|
||||
##elif h['item'].parentItem() == self:
|
||||
##h['item'].setParentItem(self.parentItem())
|
||||
|
||||
#self.scene().removeItem(self)
|
||||
|
||||
#def handleRemoved(self, handle):
|
||||
#self.parentROI.handleRemoved(self, handle)
|
||||
|
||||
#def hoverEvent(self, ev):
|
||||
#if (self.translatable or self.acceptsHandles) and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
|
||||
##print " setHover: True"
|
||||
#self.setMouseHover(True)
|
||||
#self.sigHoverEvent.emit(self)
|
||||
#else:
|
||||
##print " setHover: False"
|
||||
#self.setMouseHover(False)
|
||||
|
||||
#def mouseClickEvent(self, ev):
|
||||
#ROI.mouseClickEvent(self, ev) ## only checks for Right-clicks (for now anyway)
|
||||
#if ev.button() == QtCore.Qt.LeftButton:
|
||||
#if self.acceptsHandles:
|
||||
#ev.accept()
|
||||
#self.newHandleRequested(ev.pos()) ## ev.pos is the position in this item's coordinates
|
||||
#else:
|
||||
#ev.ignore()
|
||||
|
||||
#def newHandleRequested(self, evPos):
|
||||
#print "newHandleRequested"
|
||||
|
||||
#if evPos - self.handles[0].pos() == Point(0.,0.) or evPos-handles[1].pos() == Point(0.,0.):
|
||||
# return
|
||||
#self.parentROI.newHandleRequested(self, self.mapToParent(evPos)) ## so now evPos should be passed in in the parents coordinate system
|
||||
|
||||
def listPoints(self):
|
||||
return [p['item'].pos() for p in self.handles]
|
||||
|
||||
#def movePoint(self, *args, **kargs):
|
||||
#ROI.movePoint(self, *args, **kargs)
|
||||
#self.prepareGeometryChange()
|
||||
#for h in self.handles:
|
||||
#h['pos'] = h['item'].pos()
|
||||
|
||||
def paint(self, p, *args):
|
||||
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
p.setPen(self.currentPen)
|
||||
h1 = self.handles[0]['item'].pos()
|
||||
h2 = self.handles[1]['item'].pos()
|
||||
p.drawLine(h1, h2)
|
||||
#p.setPen(fn.mkPen('w'))
|
||||
#p.drawPath(self.shape())
|
||||
|
||||
#for i in range(len(self.handles)-1):
|
||||
#h1 = self.handles[i]['item'].pos()
|
||||
#h2 = self.handles[i+1]['item'].pos()
|
||||
#p.drawLine(h1, h2)
|
||||
|
||||
def boundingRect(self):
|
||||
return self.shape().boundingRect()
|
||||
#r = QtCore.QRectF()
|
||||
#for h in self.handles:
|
||||
#r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs
|
||||
#return r
|
||||
|
||||
def shape(self):
|
||||
p = QtGui.QPainterPath()
|
||||
#pw, ph = self.pixelSize()
|
||||
#pHyp = 4 * (pw**2 + ph**2)**0.5
|
||||
|
||||
h1 = self.handles[0]['item'].pos()
|
||||
h2 = self.handles[1]['item'].pos()
|
||||
dh = h2-h1
|
||||
if dh.length() == 0:
|
||||
return p
|
||||
pxv = self.pixelVectors(h2-h1)[1]
|
||||
|
||||
if pxv is None:
|
||||
@ -1799,14 +1798,6 @@ class LineSegmentROI(ROI):
|
||||
|
||||
return p
|
||||
|
||||
#def stateCopy(self):
|
||||
#sc = {}
|
||||
#sc['pos'] = Point(self.state['pos'])
|
||||
#sc['size'] = Point(self.state['size'])
|
||||
#sc['angle'] = self.state['angle']
|
||||
##sc['handles'] = self.handles
|
||||
#return sc
|
||||
|
||||
def getArrayRegion(self, data, img, axes=(0,1)):
|
||||
"""
|
||||
Use the position of this ROI relative to an imageItem to pull a slice from an array.
|
||||
@ -1849,11 +1840,11 @@ class SpiralROI(ROI):
|
||||
return QtCore.QRectF(-r*1.1, -r*1.1, 2.2*r, 2.2*r)
|
||||
#return self.bounds
|
||||
|
||||
def movePoint(self, *args, **kargs):
|
||||
ROI.movePoint(self, *args, **kargs)
|
||||
self.prepareGeometryChange()
|
||||
for h in self.handles:
|
||||
h['pos'] = h['item'].pos()/self.state['size'][0]
|
||||
#def movePoint(self, *args, **kargs):
|
||||
#ROI.movePoint(self, *args, **kargs)
|
||||
#self.prepareGeometryChange()
|
||||
#for h in self.handles:
|
||||
#h['pos'] = h['item'].pos()/self.state['size'][0]
|
||||
|
||||
def stateChanged(self):
|
||||
ROI.stateChanged(self)
|
||||
|
Loading…
Reference in New Issue
Block a user