ROI tests pass with row-major axis order

This commit is contained in:
Luke Campagnola 2016-08-25 18:18:15 -07:00
parent 956251f7ee
commit df691596a7
5 changed files with 80 additions and 25 deletions

View File

@ -3,6 +3,7 @@ from .Qt import QtCore, QtGui
from .Point import Point from .Point import Point
import numpy as np import numpy as np
class SRTTransform(QtGui.QTransform): class SRTTransform(QtGui.QTransform):
"""Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate """Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate
This transform has no shear; angles are always preserved. This transform has no shear; angles are always preserved.
@ -165,6 +166,7 @@ class SRTTransform(QtGui.QTransform):
def matrix(self): def matrix(self):
return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]]) return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]])
if __name__ == '__main__': if __name__ == '__main__':
from . import widgets from . import widgets

View File

@ -37,9 +37,6 @@ class GraphicsItem(object):
if register: if register:
GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items()
def getViewWidget(self): def getViewWidget(self):
""" """
Return the view widget for this item. Return the view widget for this item.
@ -95,7 +92,6 @@ class GraphicsItem(object):
def forgetViewBox(self): def forgetViewBox(self):
self._viewBox = None self._viewBox = None
def deviceTransform(self, viewportTransform=None): def deviceTransform(self, viewportTransform=None):
""" """
Return the transform that converts local item coordinates to device coordinates (usually pixels). Return the transform that converts local item coordinates to device coordinates (usually pixels).

View File

@ -279,6 +279,42 @@ class ImageItem(GraphicsObject):
if gotNewData: if gotNewData:
self.sigImageChanged.emit() self.sigImageChanged.emit()
def dataTransform(self):
"""Return the transform that maps from this image's input array to its
local coordinate system.
This transform corrects for the transposition that occurs when image data
is interpreted in row-major order.
"""
# Might eventually need to account for downsampling / clipping here
tr = QtGui.QTransform()
if self.axisOrder == 'row-major':
# transpose
tr.scale(1, -1)
tr.rotate(-90)
return tr
def inverseDataTransform(self):
"""Return the transform that maps from this image's local coordinate
system to its input array.
See dataTransform() for more information.
"""
tr = QtGui.QTransform()
if self.axisOrder == 'row-major':
# transpose
tr.scale(1, -1)
tr.rotate(-90)
return tr
def mapToData(self, obj):
tr = self.inverseDataTransform()
return tr.map(obj)
def mapFromData(self, obj):
tr = self.dataTransform()
return tr.map(obj)
def quickMinMax(self, targetSize=1e6): def quickMinMax(self, targetSize=1e6):
""" """
Estimate the min/max values of the image data by subsampling. Estimate the min/max values of the image data by subsampling.

View File

@ -1017,7 +1017,7 @@ class ROI(GraphicsObject):
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))
If the slice can not be computed (usually because the scene/transforms are not properly If the slice cannot be computed (usually because the scene/transforms are not properly
constructed yet), then the method returns None. constructed yet), then the method returns None.
""" """
## Determine shape of array along ROI axes ## Determine shape of array along ROI axes
@ -1104,7 +1104,8 @@ class ROI(GraphicsObject):
shape, vectors, origin = self.getAffineSliceParams(data, img, axes, fromBoundingRect=fromBR) 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) rgn = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
return rgn
else: else:
kwds['returnCoords'] = True kwds['returnCoords'] = True
result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
@ -1119,29 +1120,34 @@ class ROI(GraphicsObject):
(shape, vectors, origin) to extract a subset of *data* using this ROI (shape, vectors, origin) to extract a subset of *data* using this ROI
and *img* to specify the subset. and *img* to specify the subset.
If *fromBoundingRect* is True, then the ROI's bounding rectangle is used
rather than the shape of the ROI.
See :func:`getArrayRegion <pyqtgraph.ROI.getArrayRegion>` for more information. See :func:`getArrayRegion <pyqtgraph.ROI.getArrayRegion>` for more information.
""" """
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.")
origin = self.mapToItem(img, QtCore.QPointF(0, 0)) origin = img.mapToData(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
vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin vx = img.mapToData(self.mapToItem(img, QtCore.QPointF(1, 0))) - origin
vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin vy = img.mapToData(self.mapToItem(img, QtCore.QPointF(0, 1))) - origin
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, not 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
sx = 1.0 / lvx
sy = 1.0 / 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))
if fromBoundingRect is True: if fromBoundingRect is True:
shape = self.boundingRect().width(), self.boundingRect().height() shape = self.boundingRect().width(), self.boundingRect().height()
origin = self.mapToItem(img, self.boundingRect().topLeft()) origin = img.mapToData(self.mapToItem(img, self.boundingRect().topLeft()))
origin = (origin.x(), origin.y()) origin = (origin.x(), origin.y())
else: else:
shape = self.state['size'] shape = self.state['size']
@ -1150,10 +1156,10 @@ class ROI(GraphicsObject):
shape = [abs(shape[0]/sx), abs(shape[1]/sy)] shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
if getConfigOption('imageAxisOrder') == 'row-major': if getConfigOption('imageAxisOrder') == 'row-major':
vectors = [vectors[1][::-1], vectors[0][::-1]] # transpose output
vectors = vectors[::-1]
shape = shape[::-1] shape = shape[::-1]
origin = origin[::-1]
return shape, vectors, origin return shape, vectors, origin
def renderShapeMask(self, width, height): def renderShapeMask(self, width, height):

View File

@ -8,7 +8,7 @@ from pyqtgraph.tests import assertImageApproved, mouseMove, mouseDrag, mouseClic
app = pg.mkQApp() app = pg.mkQApp()
def test_getArrayRegion(): def test_getArrayRegion(transpose=False):
pr = pg.PolyLineROI([[0, 0], [27, 0], [0, 28]], closed=True) pr = pg.PolyLineROI([[0, 0], [27, 0], [0, 28]], closed=True)
pr.setPos(1, 1) pr.setPos(1, 1)
rois = [ rois = [
@ -23,13 +23,19 @@ def test_getArrayRegion():
origMode = pg.getConfigOption('imageAxisOrder') origMode = pg.getConfigOption('imageAxisOrder')
try: try:
pg.setConfigOptions(imageAxisOrder='col-major') if transpose:
check_getArrayRegion(roi, 'roi/'+name, testResize) pg.setConfigOptions(imageAxisOrder='row-major')
pg.setConfigOptions(imageAxisOrder='row-major') check_getArrayRegion(roi, 'roi/'+name, testResize, transpose=True)
check_getArrayRegion(roi, 'roi/'+name, testResize, transpose=True) else:
pg.setConfigOptions(imageAxisOrder='col-major')
check_getArrayRegion(roi, 'roi/'+name, testResize)
finally: finally:
pg.setConfigOptions(imageAxisOrder=origMode) pg.setConfigOptions(imageAxisOrder=origMode)
def test_getArrayRegion_axisorder():
test_getArrayRegion(transpose=True)
def check_getArrayRegion(roi, name, testResize=True, transpose=False): def check_getArrayRegion(roi, name, testResize=True, transpose=False):
initState = roi.getState() initState = roi.getState()
@ -55,8 +61,8 @@ def check_getArrayRegion(roi, name, testResize=True, transpose=False):
vb2.setPos(6, 203) vb2.setPos(6, 203)
vb2.resize(188, 191) vb2.resize(188, 191)
img1 = TransposedImageItem(border='w', transpose=transpose) img1 = pg.ImageItem(border='w')
img2 = TransposedImageItem(border='w', transpose=transpose) img2 = pg.ImageItem(border='w')
vb1.addItem(img1) vb1.addItem(img1)
vb2.addItem(img2) vb2.addItem(img2)
@ -68,6 +74,9 @@ def check_getArrayRegion(roi, name, testResize=True, transpose=False):
data[:, :, 2, :] += 10 data[:, :, 2, :] += 10
data[:, :, :, 3] += 10 data[:, :, :, 3] += 10
if transpose:
data = data.transpose(0, 2, 1, 3)
img1.setImage(data[0, ..., 0]) img1.setImage(data[0, ..., 0])
vb1.setAspectLocked() vb1.setAspectLocked()
vb1.enableAutoRange(True, True) vb1.enableAutoRange(True, True)
@ -75,6 +84,12 @@ def check_getArrayRegion(roi, name, testResize=True, transpose=False):
roi.setZValue(10) roi.setZValue(10)
vb1.addItem(roi) vb1.addItem(roi)
if isinstance(roi, pg.RectROI):
if transpose:
assert roi.getAffineSliceParams(data, img1, axes=(1, 2)) == ([28.0, 27.0], ((1.0, 0.0), (0.0, 1.0)), (1.0, 1.0))
else:
assert roi.getAffineSliceParams(data, img1, axes=(1, 2)) == ([27.0, 28.0], ((1.0, 0.0), (0.0, 1.0)), (1.0, 1.0))
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, :]) | (rgn == 0)) #assert np.all((rgn == data[:, 1:-2, 1:-2, :]) | (rgn == 0))
img2.setImage(rgn[0, ..., 0]) img2.setImage(rgn[0, ..., 0])