From bb507cf6d089a6aa7dcbbd8656a75969b754815f Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Tue, 17 May 2016 18:04:52 -0700 Subject: [PATCH] ROI tests pass FIX: PolyLineROI.setPoints() did not clear points previously API: Allow ROI.setPos(x, y) in addition to setPos([x, y]) --- pyqtgraph/graphicsItems/ROI.py | 43 +++++++++++++++++------ pyqtgraph/graphicsItems/tests/test_ROI.py | 33 +++++++++-------- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index ac2c6a9d..a9bcac06 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -213,7 +213,7 @@ class ROI(GraphicsObject): """Return the angle of the ROI in degrees.""" return self.getState()['angle'] - def setPos(self, pos, update=True, finish=True): + def setPos(self, pos, y=None, update=True, finish=True): """Set the position of the ROI (in the parent's coordinate system). By default, this will cause both sigRegionChanged and sigRegionChangeFinished to be emitted. @@ -225,10 +225,13 @@ class ROI(GraphicsObject): multiple change functions to be called sequentially while minimizing processing overhead and repeated signals. Setting update=False also forces finish=False. """ - # This avoids the temptation to do setPos(x, y) - if not isinstance(update, bool): - raise TypeError("update argument must be bool.") - pos = Point(pos) + if y is None: + pos = Point(pos) + else: + # avoid ambiguity where update is provided as a positional argument + if isinstance(y, bool): + raise TypeError("Positional arguments to setPos() must be numerical.") + pos = Point(pos, y) self.state['pos'] = pos QtGui.QGraphicsItem.setPos(self, pos) if update: @@ -921,8 +924,9 @@ class ROI(GraphicsObject): if self.lastState is None: changed = True else: - for k in list(self.state.keys()): - if self.state[k] != self.lastState[k]: + state = self.getState() + for k in list(state.keys()): + if state[k] != self.lastState[k]: changed = True self.prepareGeometryChange() @@ -942,7 +946,7 @@ class ROI(GraphicsObject): self.sigRegionChanged.emit(self) self.freeHandleMoved = False - self.lastState = self.stateCopy() + self.lastState = self.getState() if finish: self.stateChangeFinished() @@ -1133,6 +1137,9 @@ class ROI(GraphicsObject): This can be used to mask array selections. """ + if width == 0 or height == 0: + return np.empty((width, height), dtype=float) + # QImage(width, height, format) im = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32) im.fill(0x0) @@ -1864,6 +1871,8 @@ class PolyLineROI(ROI): if closed is not None: self.closed = closed + self.clearPoints() + for p in points: self.addFreeHandle(p) @@ -1877,7 +1886,14 @@ class PolyLineROI(ROI): Remove all handles and segments. """ while len(self.handles) > 0: - self.removeHandle(self.handles[0]['item']) + update = len(self.handles) == 1 + self.removeHandle(self.handles[0]['item'], updateSegments=update) + + def getState(self): + state = ROI.getState(self) + state['closed'] = self.closed + state['points'] = [Point(h.pos()) for h in self.getHandles()] + return state def saveState(self): state = ROI.saveState(self) @@ -1887,7 +1903,6 @@ class PolyLineROI(ROI): def setState(self, state): ROI.setState(self, state) - self.clearPoints() self.setPoints(state['points'], closed=state['closed']) def addSegment(self, h1, h2, index=None): @@ -1912,6 +1927,7 @@ class PolyLineROI(ROI): def addHandle(self, info, index=None): h = ROI.addHandle(self, info, index=index) h.sigRemoveRequested.connect(self.removeHandle) + self.stateChanged(finish=True) return h def segmentClicked(self, segment, ev=None, pos=None): ## pos should be in this item's coordinate system @@ -1944,6 +1960,7 @@ class PolyLineROI(ROI): handles.remove(handle) segments[0].replaceHandle(handle, handles[0]) self.removeSegment(segments[1]) + self.stateChanged(finish=True) def removeSegment(self, seg): for handle in seg.handles[:]: @@ -1973,19 +1990,23 @@ class PolyLineROI(ROI): for i in range(len(self.handles)): p.lineTo(self.handles[i]['item'].pos()) p.lineTo(self.handles[0]['item'].pos()) - return p + return p def getArrayRegion(self, data, img, axes=(0,1)): """ Return the result of ROI.getArrayRegion(), masked by the shape of the ROI. Values outside the ROI shape are set to 0. """ + br = self.boundingRect() + if br.width() > 1000: + raise Exception() sliced = ROI.getArrayRegion(self, data, img, axes=axes, fromBoundingRect=True) mask = self.renderShapeMask(sliced.shape[axes[0]], sliced.shape[axes[1]]) shape = [1] * data.ndim shape[axes[0]] = sliced.shape[axes[0]] shape[axes[1]] = sliced.shape[axes[1]] mask = mask.reshape(shape) + return sliced * mask def setPen(self, *args, **kwds): diff --git a/pyqtgraph/graphicsItems/tests/test_ROI.py b/pyqtgraph/graphicsItems/tests/test_ROI.py index ff1d20da..6b589edc 100644 --- a/pyqtgraph/graphicsItems/tests/test_ROI.py +++ b/pyqtgraph/graphicsItems/tests/test_ROI.py @@ -8,17 +8,24 @@ app = pg.mkQApp() def test_getArrayRegion(): + pr = pg.PolyLineROI([[0, 0], [27, 0], [0, 28]], closed=True) + pr.setPos(1, 1) rois = [ (pg.ROI([1, 1], [27, 28], pen='y'), 'baseroi'), (pg.RectROI([1, 1], [27, 28], pen='y'), 'rectroi'), (pg.EllipseROI([1, 1], [27, 28], pen='y'), 'ellipseroi'), - (pg.PolyLineROI([[0, 0], [27, 0], [0, 28]], closed=True), 'polylineroi'), + (pr, 'polylineroi'), ] for roi, name in rois: - check_getArrayRegion(roi, name) + # For some ROIs, resize should not be used. + testResize = not isinstance(roi, pg.PolyLineROI) + + check_getArrayRegion(roi, 'roi/'+name, testResize) -def check_getArrayRegion(roi, name): +def check_getArrayRegion(roi, name, testResize=True): + initState = roi.getState() + win = pg.GraphicsLayoutWidget() win.show() win.resize(200, 400) @@ -46,7 +53,7 @@ def check_getArrayRegion(roi, name): vb1.addItem(roi) rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) - #assert np.all((rgn == data[:, 1:-2, 1:-2, :]) | (rgn == 0)) + assert np.all((rgn == data[:, 1:-2, 1:-2, :]) | (rgn == 0)) img2.setImage(rgn[0, ..., 0]) vb2.setAspectLocked() vb2.enableAutoRange(True, True) @@ -56,7 +63,7 @@ def check_getArrayRegion(roi, name): assertImageApproved(win, name+'/roi_getarrayregion', 'Simple ROI region selection.') with pytest.raises(TypeError): - roi.setPos(0, 0) + roi.setPos(0, False) roi.setPos([0.5, 1.5]) rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) @@ -71,11 +78,12 @@ def check_getArrayRegion(roi, name): app.processEvents() assertImageApproved(win, name+'/roi_getarrayregion_rotate', 'Simple ROI region selection, rotation.') - roi.setSize([60, 60]) - rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) - img2.setImage(rgn[0, ..., 0]) - app.processEvents() - assertImageApproved(win, name+'/roi_getarrayregion_resize', 'Simple ROI region selection, resized.') + if testResize: + roi.setSize([60, 60]) + rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) + img2.setImage(rgn[0, ..., 0]) + app.processEvents() + assertImageApproved(win, name+'/roi_getarrayregion_resize', 'Simple ROI region selection, resized.') img1.scale(1, -1) img1.setPos(0, img1.height()) @@ -91,13 +99,10 @@ def check_getArrayRegion(roi, name): app.processEvents() assertImageApproved(win, name+'/roi_getarrayregion_inverty', 'Simple ROI region selection, view inverted.') - roi.setAngle(0) - roi.setSize(30, 30) - roi.setPos([0, 0]) + roi.setState(initState) img1.resetTransform() img1.setPos(0, 0) img1.scale(1, 0.5) - #img1.scale(0.5, 1) rgn = roi.getArrayRegion(data, img1, axes=(1, 2)) img2.setImage(rgn[0, ..., 0]) app.processEvents()