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:
Luke Campagnola 2016-06-16 08:54:52 -07:00
parent 54fbfdb918
commit a76fc37112
6 changed files with 62 additions and 30 deletions

View File

@ -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.

View File

@ -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.
}

View File

@ -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:

View File

@ -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

View File

@ -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")

View File

@ -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()