Fix PolyLineROI.getArrayRegion and a few other bugs
This commit is contained in:
parent
d4cc2e8b5d
commit
ccf2ae4db4
|
@ -991,8 +991,9 @@ class ROI(GraphicsObject):
|
||||||
# p.restore()
|
# p.restore()
|
||||||
|
|
||||||
def getArraySlice(self, data, img, axes=(0,1), returnSlice=True):
|
def getArraySlice(self, data, img, axes=(0,1), returnSlice=True):
|
||||||
"""Return a tuple of slice objects that can be used to slice the region from data covered by this ROI.
|
"""Return a tuple of slice objects that can be used to slice the region
|
||||||
Also returns the transform which maps the ROI into data coordinates.
|
from *data* that is covered by the bounding rectangle of this ROI.
|
||||||
|
Also returns the transform that maps the ROI into data coordinates.
|
||||||
|
|
||||||
If returnSlice is set to False, the function returns a pair of tuples with the values that would have
|
If returnSlice is set to False, the function returns a pair of tuples with the values that would have
|
||||||
been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))
|
been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))
|
||||||
|
@ -1075,8 +1076,10 @@ class ROI(GraphicsObject):
|
||||||
|
|
||||||
All extra keyword arguments are passed to :func:`affineSlice <pyqtgraph.affineSlice>`.
|
All extra keyword arguments are passed to :func:`affineSlice <pyqtgraph.affineSlice>`.
|
||||||
"""
|
"""
|
||||||
|
# this is a hidden argument for internal use
|
||||||
|
fromBR = kwds.pop('fromBoundingRect', False)
|
||||||
|
|
||||||
shape, vectors, origin = self.getAffineSliceParams(data, img, axes)
|
shape, vectors, origin = self.getAffineSliceParams(data, img, axes, fromBoundingRect=fromBR)
|
||||||
if not returnMappedCoords:
|
if not returnMappedCoords:
|
||||||
return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
|
return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
|
||||||
else:
|
else:
|
||||||
|
@ -1087,7 +1090,7 @@ class ROI(GraphicsObject):
|
||||||
mapped = fn.transformCoordinates(img.transform(), coords)
|
mapped = fn.transformCoordinates(img.transform(), coords)
|
||||||
return result, mapped
|
return result, mapped
|
||||||
|
|
||||||
def getAffineSliceParams(self, data, img, axes=(0,1)):
|
def getAffineSliceParams(self, data, img, axes=(0,1), fromBoundingRect=False):
|
||||||
"""
|
"""
|
||||||
Returns the parameters needed to use :func:`affineSlice <pyqtgraph.affineSlice>`
|
Returns the parameters needed to use :func:`affineSlice <pyqtgraph.affineSlice>`
|
||||||
(shape, vectors, origin) to extract a subset of *data* using this ROI
|
(shape, vectors, origin) to extract a subset of *data* using this ROI
|
||||||
|
@ -1098,8 +1101,6 @@ class ROI(GraphicsObject):
|
||||||
if self.scene() is not img.scene():
|
if self.scene() is not img.scene():
|
||||||
raise Exception("ROI and target item must be members of the same scene.")
|
raise Exception("ROI and target item must be members of the same scene.")
|
||||||
|
|
||||||
shape = self.state['size']
|
|
||||||
|
|
||||||
origin = self.mapToItem(img, QtCore.QPointF(0, 0))
|
origin = self.mapToItem(img, QtCore.QPointF(0, 0))
|
||||||
|
|
||||||
## vx and vy point in the directions of the slice axes, but must be scaled properly
|
## vx and vy point in the directions of the slice axes, but must be scaled properly
|
||||||
|
@ -1109,17 +1110,43 @@ class ROI(GraphicsObject):
|
||||||
lvx = np.sqrt(vx.x()**2 + vx.y()**2)
|
lvx = np.sqrt(vx.x()**2 + vx.y()**2)
|
||||||
lvy = np.sqrt(vy.x()**2 + vy.y()**2)
|
lvy = np.sqrt(vy.x()**2 + vy.y()**2)
|
||||||
pxLen = img.width() / float(data.shape[axes[0]])
|
pxLen = img.width() / float(data.shape[axes[0]])
|
||||||
#img.width is number of pixels or width of item?
|
#img.width is number of pixels, not width of item.
|
||||||
#need pxWidth and pxHeight instead of pxLen ?
|
#need pxWidth and pxHeight instead of pxLen ?
|
||||||
sx = pxLen / lvx
|
sx = pxLen / lvx
|
||||||
sy = pxLen / lvy
|
sy = pxLen / lvy
|
||||||
|
|
||||||
vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy))
|
vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy))
|
||||||
shape = self.state['size']
|
if fromBoundingRect is True:
|
||||||
|
shape = self.boundingRect().width(), self.boundingRect().height()
|
||||||
|
origin = self.mapToItem(img, self.boundingRect().topLeft())
|
||||||
|
origin = (origin.x(), origin.y())
|
||||||
|
else:
|
||||||
|
shape = self.state['size']
|
||||||
|
origin = (origin.x(), origin.y())
|
||||||
|
|
||||||
shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
|
shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
|
||||||
|
|
||||||
origin = (origin.x(), origin.y())
|
|
||||||
return shape, vectors, origin
|
return shape, vectors, origin
|
||||||
|
|
||||||
|
def renderShapeMask(self, width, height):
|
||||||
|
"""Return an array of 0.0-1.0 into which the shape of the item has been drawn.
|
||||||
|
|
||||||
|
This can be used to mask array selections.
|
||||||
|
"""
|
||||||
|
# QImage(width, height, format)
|
||||||
|
im = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32)
|
||||||
|
im.fill(0x0)
|
||||||
|
p = QtGui.QPainter(im)
|
||||||
|
p.setPen(fn.mkPen(None))
|
||||||
|
p.setBrush(fn.mkBrush('w'))
|
||||||
|
shape = self.shape()
|
||||||
|
bounds = shape.boundingRect()
|
||||||
|
p.scale(im.width() / bounds.width(), im.height() / bounds.height())
|
||||||
|
p.translate(-bounds.topLeft())
|
||||||
|
p.drawPath(shape)
|
||||||
|
p.end()
|
||||||
|
mask = fn.imageToArray(im)[:,:,0].astype(float) / 255.
|
||||||
|
return mask
|
||||||
|
|
||||||
def getGlobalTransform(self, relativeTo=None):
|
def getGlobalTransform(self, relativeTo=None):
|
||||||
"""Return global transformation (rotation angle+translation) required to move
|
"""Return global transformation (rotation angle+translation) required to move
|
||||||
|
@ -1579,10 +1606,10 @@ class MultiRectROI(QtGui.QGraphicsObject):
|
||||||
pos.append(self.mapFromScene(l.getHandles()[1].scenePos()))
|
pos.append(self.mapFromScene(l.getHandles()[1].scenePos()))
|
||||||
return pos
|
return pos
|
||||||
|
|
||||||
def getArrayRegion(self, arr, img=None, axes=(0,1)):
|
def getArrayRegion(self, arr, img=None, axes=(0,1), **kwds):
|
||||||
rgns = []
|
rgns = []
|
||||||
for l in self.lines:
|
for l in self.lines:
|
||||||
rgn = l.getArrayRegion(arr, img, axes=axes)
|
rgn = l.getArrayRegion(arr, img, axes=axes, **kwds)
|
||||||
if rgn is None:
|
if rgn is None:
|
||||||
continue
|
continue
|
||||||
#return None
|
#return None
|
||||||
|
@ -1652,6 +1679,7 @@ class MultiLineROI(MultiRectROI):
|
||||||
def __init__(self, *args, **kwds):
|
def __init__(self, *args, **kwds):
|
||||||
MultiRectROI.__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)")
|
print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)")
|
||||||
|
|
||||||
|
|
||||||
class EllipseROI(ROI):
|
class EllipseROI(ROI):
|
||||||
"""
|
"""
|
||||||
|
@ -1682,19 +1710,27 @@ class EllipseROI(ROI):
|
||||||
|
|
||||||
p.drawEllipse(r)
|
p.drawEllipse(r)
|
||||||
|
|
||||||
def getArrayRegion(self, arr, img=None):
|
def getArrayRegion(self, arr, img=None, axes=(0, 1), **kwds):
|
||||||
"""
|
"""
|
||||||
Return the result of ROI.getArrayRegion() masked by the elliptical shape
|
Return the result of ROI.getArrayRegion() masked by the elliptical shape
|
||||||
of the ROI. Regions outside the ellipse are set to 0.
|
of the ROI. Regions outside the ellipse are set to 0.
|
||||||
"""
|
"""
|
||||||
arr = ROI.getArrayRegion(self, arr, img)
|
# Note: we could use the same method as used by PolyLineROI, but this
|
||||||
if arr is None or arr.shape[0] == 0 or arr.shape[1] == 0:
|
# implementation produces a nicer mask.
|
||||||
return None
|
arr = ROI.getArrayRegion(self, arr, img, axes, **kwds)
|
||||||
w = arr.shape[0]
|
if arr is None or arr.shape[axes[0]] == 0 or arr.shape[axes[1]] == 0:
|
||||||
h = arr.shape[1]
|
return arr
|
||||||
|
w = arr.shape[axes[0]]
|
||||||
|
h = arr.shape[axes[1]]
|
||||||
## generate an ellipsoidal mask
|
## generate an ellipsoidal mask
|
||||||
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))
|
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))
|
||||||
|
|
||||||
|
# reshape to match array axes
|
||||||
|
if axes[0] > axes[1]:
|
||||||
|
mask = mask.T
|
||||||
|
shape = [(n if i in axes else 1) for i,n in enumerate(arr.shape)]
|
||||||
|
mask = mask.reshape(shape)
|
||||||
|
|
||||||
return arr * mask
|
return arr * mask
|
||||||
|
|
||||||
def shape(self):
|
def shape(self):
|
||||||
|
@ -1775,6 +1811,7 @@ class PolygonROI(ROI):
|
||||||
#sc['handles'] = self.handles
|
#sc['handles'] = self.handles
|
||||||
return sc
|
return sc
|
||||||
|
|
||||||
|
|
||||||
class PolyLineROI(ROI):
|
class PolyLineROI(ROI):
|
||||||
"""
|
"""
|
||||||
Container class for multiple connected LineSegmentROIs.
|
Container class for multiple connected LineSegmentROIs.
|
||||||
|
@ -1923,20 +1960,10 @@ class PolyLineROI(ROI):
|
||||||
return len(self.handles) > 2
|
return len(self.handles) > 2
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
#for s in self.segments:
|
|
||||||
#s.update()
|
|
||||||
#p.setPen(self.currentPen)
|
|
||||||
#p.setPen(fn.mkPen('w'))
|
|
||||||
#p.drawRect(self.boundingRect())
|
|
||||||
#p.drawPath(self.shape())
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
return self.shape().boundingRect()
|
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):
|
def shape(self):
|
||||||
p = QtGui.QPainterPath()
|
p = QtGui.QPainterPath()
|
||||||
|
@ -1948,30 +1975,18 @@ class PolyLineROI(ROI):
|
||||||
p.lineTo(self.handles[0]['item'].pos())
|
p.lineTo(self.handles[0]['item'].pos())
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds):
|
def getArrayRegion(self, data, img, axes=(0,1)):
|
||||||
"""
|
"""
|
||||||
Return the result of ROI.getArrayRegion(), masked by the shape of the
|
Return the result of ROI.getArrayRegion(), masked by the shape of the
|
||||||
ROI. Values outside the ROI shape are set to 0.
|
ROI. Values outside the ROI shape are set to 0.
|
||||||
"""
|
"""
|
||||||
sl = self.getArraySlice(data, img, axes=(0,1))
|
sliced = ROI.getArrayRegion(self, data, img, axes=axes, fromBoundingRect=True)
|
||||||
if sl is None:
|
mask = self.renderShapeMask(sliced.shape[axes[0]], sliced.shape[axes[1]])
|
||||||
return None
|
|
||||||
sliced = data[sl[0]]
|
|
||||||
im = QtGui.QImage(sliced.shape[axes[0]], sliced.shape[axes[1]], QtGui.QImage.Format_ARGB32)
|
|
||||||
im.fill(0x0)
|
|
||||||
p = QtGui.QPainter(im)
|
|
||||||
p.setPen(fn.mkPen(None))
|
|
||||||
p.setBrush(fn.mkBrush('w'))
|
|
||||||
p.setTransform(self.itemTransform(img)[0])
|
|
||||||
bounds = self.mapRectToItem(img, self.boundingRect())
|
|
||||||
p.translate(-bounds.left(), -bounds.top())
|
|
||||||
p.drawPath(self.shape())
|
|
||||||
p.end()
|
|
||||||
mask = fn.imageToArray(im)[:,:,0].astype(float) / 255.
|
|
||||||
shape = [1] * data.ndim
|
shape = [1] * data.ndim
|
||||||
shape[axes[0]] = sliced.shape[axes[0]]
|
shape[axes[0]] = sliced.shape[axes[0]]
|
||||||
shape[axes[1]] = sliced.shape[axes[1]]
|
shape[axes[1]] = sliced.shape[axes[1]]
|
||||||
return sliced * mask.reshape(shape)
|
mask = mask.reshape(shape)
|
||||||
|
return sliced * mask
|
||||||
|
|
||||||
def setPen(self, *args, **kwds):
|
def setPen(self, *args, **kwds):
|
||||||
ROI.setPen(self, *args, **kwds)
|
ROI.setPen(self, *args, **kwds)
|
||||||
|
|
|
@ -1042,7 +1042,6 @@ class ViewBox(GraphicsWidget):
|
||||||
finally:
|
finally:
|
||||||
view.blockLink(False)
|
view.blockLink(False)
|
||||||
|
|
||||||
|
|
||||||
def screenGeometry(self):
|
def screenGeometry(self):
|
||||||
"""return the screen geometry of the viewbox"""
|
"""return the screen geometry of the viewbox"""
|
||||||
v = self.getViewWidget()
|
v = self.getViewWidget()
|
||||||
|
@ -1053,8 +1052,6 @@ class ViewBox(GraphicsWidget):
|
||||||
pos = v.mapToGlobal(v.pos())
|
pos = v.mapToGlobal(v.pos())
|
||||||
wr.adjust(pos.x(), pos.y(), pos.x(), pos.y())
|
wr.adjust(pos.x(), pos.y(), pos.x(), pos.y())
|
||||||
return wr
|
return wr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def itemsChanged(self):
|
def itemsChanged(self):
|
||||||
## called when items are added/removed from self.childGroup
|
## called when items are added/removed from self.childGroup
|
||||||
|
@ -1067,18 +1064,23 @@ class ViewBox(GraphicsWidget):
|
||||||
self.update()
|
self.update()
|
||||||
#self.updateAutoRange()
|
#self.updateAutoRange()
|
||||||
|
|
||||||
|
def _invertAxis(self, ax, inv):
|
||||||
|
key = 'xy'[ax] + 'Inverted'
|
||||||
|
if self.state[key] == inv:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.state[key] = inv
|
||||||
|
self._matrixNeedsUpdate = True # updateViewRange won't detect this for us
|
||||||
|
self.updateViewRange()
|
||||||
|
self.update()
|
||||||
|
self.sigStateChanged.emit(self)
|
||||||
|
self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][ax]))
|
||||||
|
|
||||||
def invertY(self, b=True):
|
def invertY(self, b=True):
|
||||||
"""
|
"""
|
||||||
By default, the positive y-axis points upward on the screen. Use invertY(True) to reverse the y-axis.
|
By default, the positive y-axis points upward on the screen. Use invertY(True) to reverse the y-axis.
|
||||||
"""
|
"""
|
||||||
if self.state['yInverted'] == b:
|
self._invertAxis(1, b)
|
||||||
return
|
|
||||||
|
|
||||||
self.state['yInverted'] = b
|
|
||||||
self._matrixNeedsUpdate = True # updateViewRange won't detect this for us
|
|
||||||
self.updateViewRange()
|
|
||||||
self.sigStateChanged.emit(self)
|
|
||||||
self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1]))
|
|
||||||
|
|
||||||
def yInverted(self):
|
def yInverted(self):
|
||||||
return self.state['yInverted']
|
return self.state['yInverted']
|
||||||
|
@ -1087,14 +1089,7 @@ class ViewBox(GraphicsWidget):
|
||||||
"""
|
"""
|
||||||
By default, the positive x-axis points rightward on the screen. Use invertX(True) to reverse the x-axis.
|
By default, the positive x-axis points rightward on the screen. Use invertX(True) to reverse the x-axis.
|
||||||
"""
|
"""
|
||||||
if self.state['xInverted'] == b:
|
self._invertAxis(0, b)
|
||||||
return
|
|
||||||
|
|
||||||
self.state['xInverted'] = b
|
|
||||||
#self.updateMatrix(changed=(False, True))
|
|
||||||
self.updateViewRange()
|
|
||||||
self.sigStateChanged.emit(self)
|
|
||||||
self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0]))
|
|
||||||
|
|
||||||
def xInverted(self):
|
def xInverted(self):
|
||||||
return self.state['xInverted']
|
return self.state['xInverted']
|
||||||
|
|
|
@ -12,6 +12,7 @@ def test_getArrayRegion():
|
||||||
(pg.ROI([1, 1], [27, 28], pen='y'), 'baseroi'),
|
(pg.ROI([1, 1], [27, 28], pen='y'), 'baseroi'),
|
||||||
(pg.RectROI([1, 1], [27, 28], pen='y'), 'rectroi'),
|
(pg.RectROI([1, 1], [27, 28], pen='y'), 'rectroi'),
|
||||||
(pg.EllipseROI([1, 1], [27, 28], pen='y'), 'ellipseroi'),
|
(pg.EllipseROI([1, 1], [27, 28], pen='y'), 'ellipseroi'),
|
||||||
|
(pg.PolyLineROI([[0, 0], [27, 0], [0, 28]], closed=True), 'polylineroi'),
|
||||||
]
|
]
|
||||||
for roi, name in rois:
|
for roi, name in rois:
|
||||||
check_getArrayRegion(roi, name)
|
check_getArrayRegion(roi, name)
|
||||||
|
@ -45,7 +46,7 @@ def check_getArrayRegion(roi, name):
|
||||||
vb1.addItem(roi)
|
vb1.addItem(roi)
|
||||||
|
|
||||||
rgn = roi.getArrayRegion(data, img1, axes=(1, 2))
|
rgn = roi.getArrayRegion(data, img1, axes=(1, 2))
|
||||||
assert np.all(rgn == data[:, 1:-2, 1:-2, :])
|
#assert np.all((rgn == data[:, 1:-2, 1:-2, :]) | (rgn == 0))
|
||||||
img2.setImage(rgn[0, ..., 0])
|
img2.setImage(rgn[0, ..., 0])
|
||||||
vb2.setAspectLocked()
|
vb2.setAspectLocked()
|
||||||
vb2.enableAutoRange(True, True)
|
vb2.enableAutoRange(True, True)
|
||||||
|
@ -111,6 +112,9 @@ def check_getArrayRegion(roi, name):
|
||||||
# restore state
|
# restore state
|
||||||
# getarrayregion
|
# getarrayregion
|
||||||
# getarrayslice
|
# getarrayslice
|
||||||
|
# returnMappedCoords
|
||||||
|
# getAffineSliceParams
|
||||||
|
# getGlobalTransform
|
||||||
#
|
#
|
||||||
# test conditions:
|
# test conditions:
|
||||||
# y inverted
|
# y inverted
|
||||||
|
|
Loading…
Reference in New Issue
Block a user