fixed bug in graphicsItems/ImageItem.py: degenerate images (max==min) would raise exception in getHistogram()
This commit is contained in:
parent
574c5f3a47
commit
d261c2f0f2
@ -16,23 +16,23 @@ __all__ = ['ImageItem']
|
||||
class ImageItem(GraphicsObject):
|
||||
"""
|
||||
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
||||
|
||||
|
||||
GraphicsObject displaying an image. Optimized for rapid update (ie video display).
|
||||
This item displays either a 2D numpy array (height, width) or
|
||||
a 3D array (height, width, RGBa). This array is optionally scaled (see
|
||||
a 3D array (height, width, RGBa). This array is optionally scaled (see
|
||||
:func:`setLevels <pyqtgraph.ImageItem.setLevels>`) and/or colored
|
||||
with a lookup table (see :func:`setLookupTable <pyqtgraph.ImageItem.setLookupTable>`)
|
||||
before being displayed.
|
||||
|
||||
ImageItem is frequently used in conjunction with
|
||||
:class:`HistogramLUTItem <pyqtgraph.HistogramLUTItem>` or
|
||||
|
||||
ImageItem is frequently used in conjunction with
|
||||
:class:`HistogramLUTItem <pyqtgraph.HistogramLUTItem>` or
|
||||
:class:`HistogramLUTWidget <pyqtgraph.HistogramLUTWidget>` to provide a GUI
|
||||
for controlling the levels and lookup table used to display the image.
|
||||
"""
|
||||
|
||||
|
||||
sigImageChanged = QtCore.Signal()
|
||||
sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu
|
||||
|
||||
|
||||
def __init__(self, image=None, **kargs):
|
||||
"""
|
||||
See :func:`setImage <pyqtgraph.ImageItem.setImage>` for all allowed initialization arguments.
|
||||
@ -41,23 +41,23 @@ class ImageItem(GraphicsObject):
|
||||
self.menu = None
|
||||
self.image = None ## original image data
|
||||
self.qimage = None ## rendered image for display
|
||||
|
||||
|
||||
self.paintMode = None
|
||||
|
||||
|
||||
self.levels = None ## [min, max] or [[redMin, redMax], ...]
|
||||
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
|
||||
|
||||
|
||||
self.drawKernel = None
|
||||
self.border = None
|
||||
self.removable = False
|
||||
|
||||
|
||||
if image is not None:
|
||||
self.setImage(image, **kargs)
|
||||
else:
|
||||
@ -66,32 +66,32 @@ class ImageItem(GraphicsObject):
|
||||
def setCompositionMode(self, mode):
|
||||
"""Change the composition mode of the item (see QPainter::CompositionMode
|
||||
in the Qt documentation). This is useful when overlaying multiple ImageItems.
|
||||
|
||||
|
||||
============================================ ============================================================
|
||||
**Most common arguments:**
|
||||
QtGui.QPainter.CompositionMode_SourceOver Default; image replaces the background if it
|
||||
is opaque. Otherwise, it uses the alpha channel to blend
|
||||
the image with the background.
|
||||
QtGui.QPainter.CompositionMode_Overlay The image color is mixed with the background color to
|
||||
QtGui.QPainter.CompositionMode_Overlay The image color is mixed with the background color to
|
||||
reflect the lightness or darkness of the background.
|
||||
QtGui.QPainter.CompositionMode_Plus Both the alpha and color of the image and background pixels
|
||||
QtGui.QPainter.CompositionMode_Plus Both the alpha and color of the image and background pixels
|
||||
are added together.
|
||||
QtGui.QPainter.CompositionMode_Multiply The output is the image color multiplied by the background.
|
||||
============================================ ============================================================
|
||||
"""
|
||||
self.paintMode = mode
|
||||
self.update()
|
||||
|
||||
|
||||
def setBorder(self, b):
|
||||
self.border = fn.mkPen(b)
|
||||
self.update()
|
||||
|
||||
|
||||
def width(self):
|
||||
if self.image is None:
|
||||
return None
|
||||
axis = 0 if self.axisOrder == 'col-major' else 1
|
||||
return self.image.shape[axis]
|
||||
|
||||
|
||||
def height(self):
|
||||
if self.image is None:
|
||||
return None
|
||||
@ -111,10 +111,10 @@ class ImageItem(GraphicsObject):
|
||||
def setLevels(self, levels, update=True):
|
||||
"""
|
||||
Set image scaling levels. Can be one of:
|
||||
|
||||
|
||||
* [blackLevel, whiteLevel]
|
||||
* [[minRed, maxRed], [minGreen, maxGreen], [minBlue, maxBlue]]
|
||||
|
||||
|
||||
Only the first format is compatible with lookup tables. See :func:`makeARGB <pyqtgraph.makeARGB>`
|
||||
for more details on how levels are applied.
|
||||
"""
|
||||
@ -125,18 +125,18 @@ class ImageItem(GraphicsObject):
|
||||
self._effectiveLut = None
|
||||
if update:
|
||||
self.updateImage()
|
||||
|
||||
|
||||
def getLevels(self):
|
||||
return self.levels
|
||||
#return self.whiteLevel, self.blackLevel
|
||||
|
||||
def setLookupTable(self, lut, update=True):
|
||||
"""
|
||||
Set the lookup table (numpy array) to use for this image. (see
|
||||
Set the lookup table (numpy array) to use for this image. (see
|
||||
:func:`makeARGB <pyqtgraph.makeARGB>` for more information on how this is used).
|
||||
Optionally, lut can be a callable that accepts the current image as an
|
||||
Optionally, lut can be a callable that accepts the current image as an
|
||||
argument and returns the lookup table to use.
|
||||
|
||||
|
||||
Ordinarily, this table is supplied by a :class:`HistogramLUTItem <pyqtgraph.HistogramLUTItem>`
|
||||
or :class:`GradientEditorItem <pyqtgraph.GradientEditorItem>`.
|
||||
"""
|
||||
@ -149,7 +149,7 @@ class ImageItem(GraphicsObject):
|
||||
def setAutoDownsample(self, ads):
|
||||
"""
|
||||
Set the automatic downsampling mode for this ImageItem.
|
||||
|
||||
|
||||
Added in version 0.9.9
|
||||
"""
|
||||
self.autoDownsample = ads
|
||||
@ -198,44 +198,44 @@ class ImageItem(GraphicsObject):
|
||||
"""
|
||||
Update the image displayed by this item. For more information on how the image
|
||||
is processed before displaying, see :func:`makeARGB <pyqtgraph.makeARGB>`
|
||||
|
||||
|
||||
================= =========================================================================
|
||||
**Arguments:**
|
||||
image (numpy array) Specifies the image data. May be 2D (width, height) or
|
||||
image (numpy array) Specifies the image data. May be 2D (width, height) or
|
||||
3D (width, height, RGBa). The array dtype must be integer or floating
|
||||
point of any bit depth. For 3D arrays, the third dimension must
|
||||
be of length 3 (RGB) or 4 (RGBA). See *notes* below.
|
||||
autoLevels (bool) If True, this forces the image to automatically select
|
||||
autoLevels (bool) If True, this forces the image to automatically select
|
||||
levels based on the maximum and minimum values in the data.
|
||||
By default, this argument is true unless the levels argument is
|
||||
given.
|
||||
lut (numpy array) The color lookup table to use when displaying the image.
|
||||
See :func:`setLookupTable <pyqtgraph.ImageItem.setLookupTable>`.
|
||||
levels (min, max) The minimum and maximum values to use when rescaling the image
|
||||
data. By default, this will be set to the minimum and maximum values
|
||||
data. By default, this will be set to the minimum and maximum values
|
||||
in the image. If the image array has dtype uint8, no rescaling is necessary.
|
||||
opacity (float 0.0-1.0)
|
||||
compositionMode See :func:`setCompositionMode <pyqtgraph.ImageItem.setCompositionMode>`
|
||||
border Sets the pen used when drawing the image border. Default is None.
|
||||
autoDownsample (bool) If True, the image is automatically downsampled to match the
|
||||
screen resolution. This improves performance for large images and
|
||||
screen resolution. This improves performance for large images and
|
||||
reduces aliasing. If autoDownsample is not specified, then ImageItem will
|
||||
choose whether to downsample the image based on its size.
|
||||
================= =========================================================================
|
||||
|
||||
|
||||
**Notes:**
|
||||
|
||||
|
||||
|
||||
**Notes:**
|
||||
|
||||
For backward compatibility, image data is assumed to be in column-major order (column, row).
|
||||
However, most image data is stored in row-major order (row, column) and will need to be
|
||||
transposed before calling setImage()::
|
||||
|
||||
|
||||
imageitem.setImage(imagedata.T)
|
||||
|
||||
|
||||
This requirement can be changed by calling ``image.setOpts(axisOrder='row-major')`` or
|
||||
by changing the ``imageAxisOrder`` :ref:`global configuration option <apiref_config>`.
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
profile = debug.Profiler()
|
||||
|
||||
@ -292,7 +292,7 @@ class ImageItem(GraphicsObject):
|
||||
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.
|
||||
"""
|
||||
@ -307,7 +307,7 @@ class ImageItem(GraphicsObject):
|
||||
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()
|
||||
@ -339,7 +339,7 @@ class ImageItem(GraphicsObject):
|
||||
|
||||
def updateImage(self, *args, **kargs):
|
||||
## used for re-rendering qimage from self.image.
|
||||
|
||||
|
||||
## can we make any assumptions here that speed things up?
|
||||
## dtype, range, size are all the same?
|
||||
defaults = {
|
||||
@ -350,11 +350,11 @@ class ImageItem(GraphicsObject):
|
||||
|
||||
def render(self):
|
||||
# Convert data to QImage for display.
|
||||
|
||||
|
||||
profile = debug.Profiler()
|
||||
if self.image is None or self.image.size == 0:
|
||||
return
|
||||
|
||||
|
||||
# Request a lookup table if this image has only one channel
|
||||
if self.image.ndim == 2 or self.image.shape[2] == 1:
|
||||
if isinstance(self.lut, collections.Callable):
|
||||
@ -385,7 +385,7 @@ class ImageItem(GraphicsObject):
|
||||
image = fn.downsample(self.image, xds, axis=axes[0])
|
||||
image = fn.downsample(image, yds, axis=axes[1])
|
||||
self._lastDownsample = (xds, yds)
|
||||
|
||||
|
||||
# Check if downsampling reduced the image size to zero due to inf values.
|
||||
if image.size == 0:
|
||||
return
|
||||
@ -403,27 +403,27 @@ class ImageItem(GraphicsObject):
|
||||
levdiff = maxlev - minlev
|
||||
levdiff = 1 if levdiff == 0 else levdiff # don't allow division by 0
|
||||
if lut is None:
|
||||
efflut = fn.rescaleData(ind, scale=255./levdiff,
|
||||
efflut = fn.rescaleData(ind, scale=255./levdiff,
|
||||
offset=minlev, dtype=np.ubyte)
|
||||
else:
|
||||
lutdtype = np.min_scalar_type(lut.shape[0]-1)
|
||||
efflut = fn.rescaleData(ind, scale=(lut.shape[0]-1)/levdiff,
|
||||
offset=minlev, dtype=lutdtype, clip=(0, lut.shape[0]-1))
|
||||
efflut = lut[efflut]
|
||||
|
||||
|
||||
self._effectiveLut = efflut
|
||||
lut = self._effectiveLut
|
||||
levels = None
|
||||
|
||||
|
||||
# Convert single-channel image to 2D array
|
||||
if image.ndim == 3 and image.shape[-1] == 1:
|
||||
image = image[..., 0]
|
||||
|
||||
|
||||
# Assume images are in column-major order for backward compatibility
|
||||
# (most images are in row-major order)
|
||||
if self.axisOrder == 'col-major':
|
||||
image = image.transpose((1, 0, 2)[:image.ndim])
|
||||
|
||||
|
||||
argb, alpha = fn.makeARGB(image, lut=lut, levels=levels)
|
||||
self.qimage = fn.makeQImage(argb, alpha, transpose=False)
|
||||
|
||||
@ -453,26 +453,26 @@ class ImageItem(GraphicsObject):
|
||||
self.render()
|
||||
self.qimage.save(fileName, *args)
|
||||
|
||||
def getHistogram(self, bins='auto', step='auto', perChannel=False, targetImageSize=200,
|
||||
def getHistogram(self, bins='auto', step='auto', perChannel=False, targetImageSize=200,
|
||||
targetHistogramSize=500, **kwds):
|
||||
"""Returns x and y arrays containing the histogram values for the current image.
|
||||
For an explanation of the return format, see numpy.histogram().
|
||||
|
||||
|
||||
The *step* argument causes pixels to be skipped when computing the histogram to save time.
|
||||
If *step* is 'auto', then a step is chosen such that the analyzed data has
|
||||
dimensions roughly *targetImageSize* for each axis.
|
||||
|
||||
The *bins* argument and any extra keyword arguments are passed to
|
||||
|
||||
The *bins* argument and any extra keyword arguments are passed to
|
||||
np.histogram(). If *bins* is 'auto', then a bin number is automatically
|
||||
chosen based on the image characteristics:
|
||||
|
||||
* Integer images will have approximately *targetHistogramSize* bins,
|
||||
|
||||
* Integer images will have approximately *targetHistogramSize* bins,
|
||||
with each bin having an integer width.
|
||||
* All other types will have *targetHistogramSize* bins.
|
||||
|
||||
|
||||
If *perChannel* is True, then the histogram is computed once per channel
|
||||
and the output is a list of the results.
|
||||
|
||||
|
||||
This method is also used when automatically computing levels.
|
||||
"""
|
||||
if self.image is None or self.image.size == 0:
|
||||
@ -483,10 +483,13 @@ class ImageItem(GraphicsObject):
|
||||
if np.isscalar(step):
|
||||
step = (step, step)
|
||||
stepData = self.image[::step[0], ::step[1]]
|
||||
|
||||
|
||||
if bins == 'auto':
|
||||
mn = np.nanmin(stepData)
|
||||
mx = np.nanmax(stepData)
|
||||
if mx == mn:
|
||||
# degenerate image, arange will fail
|
||||
mx += 1
|
||||
if np.isnan(mn) or np.isnan(mx):
|
||||
# the data are all-nan
|
||||
return None, None
|
||||
@ -497,7 +500,7 @@ class ImageItem(GraphicsObject):
|
||||
else:
|
||||
# for float data, let numpy select the bins.
|
||||
bins = np.linspace(mn, mx, 500)
|
||||
|
||||
|
||||
if len(bins) == 0:
|
||||
bins = [mn, mx]
|
||||
|
||||
@ -524,7 +527,7 @@ class ImageItem(GraphicsObject):
|
||||
(see GraphicsItem::ItemIgnoresTransformations in the Qt documentation)
|
||||
"""
|
||||
self.setFlag(self.ItemIgnoresTransformations, b)
|
||||
|
||||
|
||||
def setScaledMode(self):
|
||||
self.setPxMode(False)
|
||||
|
||||
@ -534,14 +537,14 @@ class ImageItem(GraphicsObject):
|
||||
if self.qimage is None:
|
||||
return None
|
||||
return QtGui.QPixmap.fromImage(self.qimage)
|
||||
|
||||
|
||||
def pixelSize(self):
|
||||
"""return scene-size of a single pixel in the image"""
|
||||
br = self.sceneBoundingRect()
|
||||
if self.image is None:
|
||||
return 1,1
|
||||
return br.width()/self.width(), br.height()/self.height()
|
||||
|
||||
|
||||
def viewTransformChanged(self):
|
||||
if self.autoDownsample:
|
||||
self.qimage = None
|
||||
@ -582,7 +585,7 @@ class ImageItem(GraphicsObject):
|
||||
self.menu.addAction(remAct)
|
||||
self.menu.remAct = remAct
|
||||
return self.menu
|
||||
|
||||
|
||||
def hoverEvent(self, ev):
|
||||
if not ev.isExit() and self.drawKernel is not None and ev.acceptDrags(QtCore.Qt.LeftButton):
|
||||
ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it.
|
||||
@ -595,7 +598,7 @@ class ImageItem(GraphicsObject):
|
||||
#print(ev.device())
|
||||
#print(ev.pointerType())
|
||||
#print(ev.pressure())
|
||||
|
||||
|
||||
def drawAt(self, pos, ev=None):
|
||||
pos = [int(pos.x()), int(pos.y())]
|
||||
dk = self.drawKernel
|
||||
@ -604,7 +607,7 @@ class ImageItem(GraphicsObject):
|
||||
sy = [0,dk.shape[1]]
|
||||
tx = [pos[0] - kc[0], pos[0] - kc[0]+ dk.shape[0]]
|
||||
ty = [pos[1] - kc[1], pos[1] - kc[1]+ dk.shape[1]]
|
||||
|
||||
|
||||
for i in [0,1]:
|
||||
dx1 = -min(0, tx[i])
|
||||
dx2 = min(0, self.image.shape[0]-tx[i])
|
||||
@ -620,7 +623,7 @@ class ImageItem(GraphicsObject):
|
||||
ss = (slice(sx[0],sx[1]), slice(sy[0],sy[1]))
|
||||
mask = self.drawMask
|
||||
src = dk
|
||||
|
||||
|
||||
if isinstance(self.drawMode, collections.Callable):
|
||||
self.drawMode(dk, self.image, mask, ss, ts, ev)
|
||||
else:
|
||||
@ -636,7 +639,7 @@ class ImageItem(GraphicsObject):
|
||||
else:
|
||||
raise Exception("Unknown draw mode '%s'" % self.drawMode)
|
||||
self.updateImage()
|
||||
|
||||
|
||||
def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'):
|
||||
self.drawKernel = kernel
|
||||
self.drawKernelCenter = center
|
||||
|
Loading…
Reference in New Issue
Block a user