imageAxisOrder config option now accepts "row-major" and "col-major" instead of "normal" and "legacy"
ImageItems can individually control their axis order image tests pass with axis order check
This commit is contained in:
parent
54fbfdb918
commit
a76fc37112
@ -19,9 +19,10 @@ foreground See :func:`mkColor` 'd' Default foreground col
|
||||
background See :func:`mkColor` 'k' Default background for :class:`GraphicsView`.
|
||||
antialias bool False Enabling antialiasing causes lines to be drawn with
|
||||
smooth edges at the cost of reduced performance.
|
||||
imageAxisOrder str 'legacy' For 'normal', image data is expected in the standard row-major (row, col) order.
|
||||
For 'legacy', image data is expected in reversed column-major (col, row) order.
|
||||
The default is 'legacy' for backward compatibility, but this may
|
||||
imageAxisOrder str 'legacy' For 'row-major', image data is expected in the standard row-major
|
||||
(row, col) order. For 'col-major', image data is expected in
|
||||
reversed column-major (col, row) order.
|
||||
The default is 'col-major' for backward compatibility, but this may
|
||||
change in the future.
|
||||
editorCommand str or None None Command used to invoke code editor from ConsoleWidget.
|
||||
exitCleanup bool True Attempt to work around some exit crash bugs in PyQt and PySide.
|
||||
|
@ -59,9 +59,9 @@ CONFIG_OPTIONS = {
|
||||
'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide
|
||||
'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code)
|
||||
'crashWarning': False, # If True, print warnings about situations that may result in a crash
|
||||
'imageAxisOrder': 'legacy', # For 'normal', image data is expected in the standard (row, col) order.
|
||||
# For 'legacy', image data is expected in reversed (col, row) order.
|
||||
# The default is 'legacy' for backward compatibility, but this will
|
||||
'imageAxisOrder': 'col-major', # For 'row-major', image data is expected in the standard (row, col) order.
|
||||
# For 'col-major', image data is expected in reversed (col, row) order.
|
||||
# The default is 'col-major' for backward compatibility, but this may
|
||||
# change in the future.
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,8 @@ class ImageItem(GraphicsObject):
|
||||
self.lut = None
|
||||
self.autoDownsample = False
|
||||
|
||||
self.axisOrder = getConfigOption('imageAxisOrder')
|
||||
|
||||
# In some cases, we use a modified lookup table to handle both rescaling
|
||||
# and LUT more efficiently
|
||||
self._effectiveLut = None
|
||||
@ -87,13 +89,13 @@ class ImageItem(GraphicsObject):
|
||||
def width(self):
|
||||
if self.image is None:
|
||||
return None
|
||||
axis = 0 if getConfigOption('imageAxisOrder') == 'legacy' else 1
|
||||
axis = 0 if self.axisOrder == 'col-major' else 1
|
||||
return self.image.shape[axis]
|
||||
|
||||
def height(self):
|
||||
if self.image is None:
|
||||
return None
|
||||
axis = 1 if getConfigOption('imageAxisOrder') == 'legacy' else 0
|
||||
axis = 1 if self.axisOrder == 'col-major' else 0
|
||||
return self.image.shape[axis]
|
||||
|
||||
def boundingRect(self):
|
||||
@ -150,7 +152,8 @@ class ImageItem(GraphicsObject):
|
||||
self.update()
|
||||
|
||||
def setOpts(self, update=True, **kargs):
|
||||
|
||||
if 'axisOrder' in kargs:
|
||||
self.axisOrder = kargs['axisOrder']
|
||||
if 'lut' in kargs:
|
||||
self.setLookupTable(kargs['lut'], update=update)
|
||||
if 'levels' in kargs:
|
||||
@ -220,8 +223,8 @@ class ImageItem(GraphicsObject):
|
||||
|
||||
imageitem.setImage(imagedata.T)
|
||||
|
||||
This requirement can be changed by the ``imageAxisOrder``
|
||||
:ref:`global configuration option <apiref_config>`.
|
||||
This requirement can be changed by calling ``image.setOpts(axisOrder='row-major')`` or
|
||||
by changing the ``imageAxisOrder`` :ref:`global configuration option <apiref_config>`.
|
||||
|
||||
|
||||
"""
|
||||
@ -320,10 +323,12 @@ class ImageItem(GraphicsObject):
|
||||
if w == 0 or h == 0:
|
||||
self.qimage = None
|
||||
return
|
||||
xds = int(1.0/w)
|
||||
yds = int(1.0/h)
|
||||
image = fn.downsample(self.image, xds, axis=0)
|
||||
image = fn.downsample(image, yds, axis=1)
|
||||
xds = max(1, int(1.0 / w))
|
||||
yds = max(1, int(1.0 / h))
|
||||
axes = [1, 0] if self.axisOrder == 'row-major' else [0, 1]
|
||||
image = fn.downsample(self.image, xds, axis=axes[0])
|
||||
image = fn.downsample(image, yds, axis=axes[1])
|
||||
self._lastDownsample = (xds, yds)
|
||||
else:
|
||||
image = self.image
|
||||
|
||||
@ -351,7 +356,7 @@ class ImageItem(GraphicsObject):
|
||||
# Assume images are in column-major order for backward compatibility
|
||||
# (most images are in row-major order)
|
||||
|
||||
if getConfigOption('imageAxisOrder') == 'legacy':
|
||||
if self.axisOrder == 'col-major':
|
||||
image = image.transpose((1, 0, 2)[:image.ndim])
|
||||
|
||||
argb, alpha = fn.makeARGB(image, lut=lut, levels=levels)
|
||||
@ -370,7 +375,7 @@ class ImageItem(GraphicsObject):
|
||||
p.setCompositionMode(self.paintMode)
|
||||
profile('set comp mode')
|
||||
|
||||
shape = self.image.shape[:2] if getConfigOption('imageAxisOrder') == 'legacy' else self.image.shape[:2][::-1]
|
||||
shape = self.image.shape[:2] if self.axisOrder == 'col-major' else self.image.shape[:2][::-1]
|
||||
p.drawImage(QtCore.QRectF(0,0,*shape), self.qimage)
|
||||
profile('p.drawImage')
|
||||
if self.border is not None:
|
||||
|
@ -1031,7 +1031,7 @@ class ROI(GraphicsObject):
|
||||
|
||||
## Modify transform to scale from image coords to data coords
|
||||
axisOrder = getConfigOption('imageAxisOrder')
|
||||
if axisOrder == 'normal':
|
||||
if axisOrder == 'row-major':
|
||||
tr.scale(float(dShape[1]) / img.width(), float(dShape[0]) / img.height())
|
||||
else:
|
||||
tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height())
|
||||
@ -1040,7 +1040,7 @@ class ROI(GraphicsObject):
|
||||
dataBounds = tr.mapRect(self.boundingRect())
|
||||
|
||||
## Intersect transformed ROI bounds with data bounds
|
||||
if axisOrder == 'normal':
|
||||
if axisOrder == 'row-major':
|
||||
intBounds = dataBounds.intersected(QtCore.QRectF(0, 0, dShape[1], dShape[0]))
|
||||
else:
|
||||
intBounds = dataBounds.intersected(QtCore.QRectF(0, 0, dShape[0], dShape[1]))
|
||||
@ -1050,7 +1050,7 @@ class ROI(GraphicsObject):
|
||||
(int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))),
|
||||
(int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top())))
|
||||
)
|
||||
if axisOrder == 'normal':
|
||||
if axisOrder == 'row-major':
|
||||
bounds = bounds[::-1]
|
||||
|
||||
if returnSlice:
|
||||
@ -1078,7 +1078,7 @@ class ROI(GraphicsObject):
|
||||
correspond to the (x, y) axes of *img*. If the
|
||||
global configuration variable
|
||||
:ref:`imageAxisOrder <apiref_config>` is set to
|
||||
'normal', then the axes are instead specified in
|
||||
'row-major', then the axes are instead specified in
|
||||
(y, x) order.
|
||||
returnMappedCoords (bool) If True, the array slice is returned along
|
||||
with a corresponding array of coordinates that were
|
||||
@ -1149,7 +1149,7 @@ class ROI(GraphicsObject):
|
||||
|
||||
shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
|
||||
|
||||
if getConfigOption('imageAxisOrder') == 'normal':
|
||||
if getConfigOption('imageAxisOrder') == 'row-major':
|
||||
vectors = [vectors[1][::-1], vectors[0][::-1]]
|
||||
shape = shape[::-1]
|
||||
origin = origin[::-1]
|
||||
@ -1649,7 +1649,7 @@ class MultiRectROI(QtGui.QGraphicsObject):
|
||||
|
||||
## make sure orthogonal axis is the same size
|
||||
## (sometimes fp errors cause differences)
|
||||
if getConfigOption('imageAxisOrder') == 'normal':
|
||||
if getConfigOption('imageAxisOrder') == 'row-major':
|
||||
axes = axes[::-1]
|
||||
ms = min([r.shape[axes[1]] for r in rgns])
|
||||
sl = [slice(None)] * rgns[0].ndim
|
||||
|
@ -7,15 +7,24 @@ from pyqtgraph.tests import assertImageApproved
|
||||
|
||||
app = pg.mkQApp()
|
||||
|
||||
class TransposedImageItem(pg.ImageItem):
|
||||
def setImage(self, image=None, **kwds):
|
||||
if image is not None:
|
||||
image = np.swapaxes(image, 0, 1)
|
||||
return pg.ImageItem.setImage(self, image, **kwds)
|
||||
|
||||
def test_ImageItem():
|
||||
|
||||
def test_ImageItem(transpose=False):
|
||||
|
||||
w = pg.GraphicsWindow()
|
||||
view = pg.ViewBox()
|
||||
w.setCentralWidget(view)
|
||||
w.resize(200, 200)
|
||||
w.show()
|
||||
img = pg.ImageItem(border=0.5)
|
||||
if transpose:
|
||||
img = TransposedImageItem(border=0.5)
|
||||
else:
|
||||
img = pg.ImageItem(border=0.5)
|
||||
view.addItem(img)
|
||||
|
||||
|
||||
@ -77,7 +86,10 @@ def test_ImageItem():
|
||||
assertImageApproved(w, 'imageitem/gradient_rgba_float', 'RGBA float gradient.')
|
||||
|
||||
# checkerboard to test alpha
|
||||
img2 = pg.ImageItem()
|
||||
if transpose:
|
||||
img2 = TransposedImageItem()
|
||||
else:
|
||||
img2 = pg.ImageItem()
|
||||
img2.setImage(np.fromfunction(lambda x,y: (x+y)%2, (10, 10)), levels=[-1,2])
|
||||
view.addItem(img2)
|
||||
img2.scale(10, 10)
|
||||
@ -103,9 +115,23 @@ def test_ImageItem():
|
||||
|
||||
img.setAutoDownsample(True)
|
||||
assertImageApproved(w, 'imageitem/resolution_with_downsampling_x', 'Resolution test with downsampling axross x axis.')
|
||||
assert img._lastDownsample == (5, 1)
|
||||
|
||||
img.setImage(data.T, levels=[-1, 1])
|
||||
assertImageApproved(w, 'imageitem/resolution_with_downsampling_y', 'Resolution test with downsampling across y axis.')
|
||||
assert img._lastDownsample == (1, 5)
|
||||
|
||||
view.hide()
|
||||
|
||||
def test_ImageItem_axisorder():
|
||||
# All image tests pass again using the opposite axis order
|
||||
origMode = pg.getConfigOption('imageAxisOrder')
|
||||
altMode = 'row-major' if origMode == 'col-major' else 'col-major'
|
||||
pg.setConfigOptions(imageAxisOrder=altMode)
|
||||
try:
|
||||
test_ImageItem(transpose=True)
|
||||
finally:
|
||||
pg.setConfigOptions(imageAxisOrder=origMode)
|
||||
|
||||
|
||||
@pytest.mark.skipif(pg.Qt.USE_PYSIDE, reason="pyside does not have qWait")
|
||||
|
@ -344,7 +344,7 @@ class ImageTester(QtGui.QWidget):
|
||||
for i, v in enumerate(self.views):
|
||||
v.setAspectLocked(1)
|
||||
v.invertY()
|
||||
v.image = ImageItem()
|
||||
v.image = ImageItem(axisOrder='row-major')
|
||||
v.image.setAutoDownsample(True)
|
||||
v.addItem(v.image)
|
||||
v.label = TextItem(labelText[i])
|
||||
@ -371,9 +371,9 @@ class ImageTester(QtGui.QWidget):
|
||||
message += '\nImage1: %s %s Image2: %s %s' % (im1.shape, im1.dtype, im2.shape, im2.dtype)
|
||||
self.label.setText(message)
|
||||
|
||||
self.views[0].image.setImage(im1.transpose(1, 0, 2))
|
||||
self.views[1].image.setImage(im2.transpose(1, 0, 2))
|
||||
diff = makeDiffImage(im1, im2).transpose(1, 0, 2)
|
||||
self.views[0].image.setImage(im1)
|
||||
self.views[1].image.setImage(im2)
|
||||
diff = makeDiffImage(im1, im2)
|
||||
|
||||
self.views[2].image.setImage(diff)
|
||||
self.views[0].autoRange()
|
||||
|
Loading…
Reference in New Issue
Block a user