From 5210c55e6721dafdf5320be1dbf955899bc42dad Mon Sep 17 00:00:00 2001 From: Martin Chase Date: Mon, 15 Feb 2021 06:06:55 -0800 Subject: [PATCH] Equilateral Triangle ROI (#1581) * feature TriangleROI Added equilateral TriangleROI. getArrayRegion is not working yet * show off (and implicitly test) our new TriangleROI * the PolyLineROI.getArrayRegion can be used by TriangleROI refactor the actual logic to live on ROI * refactors for readability * variable names * lint * docstring type and purpose Co-authored-by: Fekete Imre --- examples/ROIExamples.py | 1 + pyqtgraph/graphicsItems/ROI.py | 102 +++++++++++++++++++++++---------- 2 files changed, 74 insertions(+), 29 deletions(-) diff --git a/examples/ROIExamples.py b/examples/ROIExamples.py index 5391475a..abea9220 100644 --- a/examples/ROIExamples.py +++ b/examples/ROIExamples.py @@ -58,6 +58,7 @@ 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.TriangleROI([80, 75], 20, pen=(5, 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))) diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index 8362347e..efd0bdc2 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -30,7 +30,7 @@ __all__ = [ 'ROI', 'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI', 'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', - 'CrosshairROI', + 'CrosshairROI','TriangleROI' ] @@ -1182,6 +1182,35 @@ class ROI(GraphicsObject): mapped = fn.transformCoordinates(img.transform(), coords) return result, mapped + def _getArrayRegionForArbitraryShape(self, data, img, axes=(0,1), **kwds): + """ + Return the result of :meth:`~pyqtgraph.ROI.getArrayRegion`, masked by + the shape of the ROI. Values outside the ROI shape are set to 0. + + See :meth:`~pyqtgraph.ROI.getArrayRegion` for a description of the + arguments. + + Note: ``returnMappedCoords`` is not yet supported for this ROI type. + """ + br = self.boundingRect() + if br.width() > 1000: + raise Exception() + sliced = ROI.getArrayRegion(self, data, img, axes=axes, fromBoundingRect=True, **kwds) + + if img.axisOrder == "col-major": + mask = self.renderShapeMask(sliced.shape[axes[0]], sliced.shape[axes[1]]) + else: + mask = self.renderShapeMask(sliced.shape[axes[1]], sliced.shape[axes[0]]) + mask = mask.T + + # reshape mask to ensure it is applied to the correct data axes + 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 getAffineSliceParams(self, data, img, axes=(0,1), fromBoundingRect=False): """ Returns the parameters needed to use :func:`affineSlice ` @@ -2102,34 +2131,8 @@ class PolyLineROI(ROI): p.lineTo(self.handles[0]['item'].pos()) return p - def getArrayRegion(self, data, img, axes=(0,1), **kwds): - """ - Return the result of :meth:`~pyqtgraph.ROI.getArrayRegion`, masked by - the shape of the ROI. Values outside the ROI shape are set to 0. - - See :meth:`~pyqtgraph.ROI.getArrayRegion` for a description of the - arguments. - - Note: ``returnMappedCoords`` is not yet supported for this ROI type. - """ - br = self.boundingRect() - if br.width() > 1000: - raise Exception() - sliced = ROI.getArrayRegion(self, data, img, axes=axes, fromBoundingRect=True, **kwds) - - if img.axisOrder == 'col-major': - mask = self.renderShapeMask(sliced.shape[axes[0]], sliced.shape[axes[1]]) - else: - mask = self.renderShapeMask(sliced.shape[axes[1]], sliced.shape[axes[0]]) - mask = mask.T - - # reshape mask to ensure it is applied to the correct data axes - 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 getArrayRegion(self, *args, **kwds): + return self._getArrayRegionForArbitraryShape(*args, **kwds) def setPen(self, *args, **kwds): ROI.setPen(self, *args, **kwds) @@ -2344,3 +2347,44 @@ class RulerROI(LineSegmentROI): return r pxw = 50 * pxl return r.adjusted(-50, -50, 50, 50) + + +class TriangleROI(ROI): + """ + Equilateral triangle ROI subclass with one scale handle and one rotation handle. + Arguments + pos (length-2 sequence) The position of the ROI's origin. + size (float) The length of an edge of the triangle. + \**args All extra keyword arguments are passed to ROI() + ============== ============================================================= + """ + + def __init__(self, pos, size, **args): + ROI.__init__(self, pos, [size, size], **args) + self.aspectLocked = True + angles = np.linspace(0, np.pi * 4 / 3, 3) + verticies = (np.array((np.sin(angles), np.cos(angles))).T + 1.0) / 2.0 + self.poly = QtGui.QPolygonF() + for pt in verticies: + self.poly.append(QtCore.QPointF(*pt)) + self.addRotateHandle(verticies[0], [0.5, 0.5]) + self.addScaleHandle(verticies[1], [0.5, 0.5]) + + def paint(self, p, *args): + r = self.boundingRect() + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.scale(r.width(), r.height()) + p.setPen(self.currentPen) + p.drawPolygon(self.poly) + + def shape(self): + self.path = QtGui.QPainterPath() + r = self.boundingRect() + # scale the path to match whats on the screen + t = QtGui.QTransform() + t.scale(r.width(), r.height()) + self.path.addPolygon(self.poly) + return t.map(self.path) + + def getArrayRegion(self, *args, **kwds): + return self._getArrayRegionForArbitraryShape(*args, **kwds)