summaryrefslogtreecommitdiff
path: root/silx/gui/plot/items/image.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/items/image.py')
-rw-r--r--silx/gui/plot/items/image.py125
1 files changed, 115 insertions, 10 deletions
diff --git a/silx/gui/plot/items/image.py b/silx/gui/plot/items/image.py
index fda4245..0d9c9a4 100644
--- a/silx/gui/plot/items/image.py
+++ b/silx/gui/plot/items/image.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2017-2020 European Synchrotron Radiation Facility
+# Copyright (c) 2017-2021 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -28,8 +28,7 @@ of the :class:`Plot`.
__authors__ = ["T. Vincent"]
__license__ = "MIT"
-__date__ = "20/10/2017"
-
+__date__ = "08/12/2020"
try:
from collections import abc
@@ -43,7 +42,6 @@ from ....utils.proxy import docstring
from .core import (DataItem, LabelsMixIn, DraggableMixIn, ColormapMixIn,
AlphaMixIn, ItemChangedType)
-
_logger = logging.getLogger(__name__)
@@ -80,8 +78,8 @@ def _convertImageToRgba32(image, copy=True):
if image.shape[-1] == 3:
new_image = numpy.empty((image.shape[0], image.shape[1], 4),
dtype=numpy.uint8)
- new_image[:, :, :3] = image
- new_image[:, :, 3] = 255
+ new_image[:,:,:3] = image
+ new_image[:,:, 3] = 255
return new_image # This is a copy anyway
else:
return numpy.array(image, copy=copy)
@@ -93,7 +91,7 @@ class ImageBase(DataItem, LabelsMixIn, DraggableMixIn, AlphaMixIn):
:param numpy.ndarray data: Initial image data
"""
- def __init__(self, data=None):
+ def __init__(self, data=None, mask=None):
DataItem.__init__(self)
LabelsMixIn.__init__(self)
DraggableMixIn.__init__(self)
@@ -101,7 +99,8 @@ class ImageBase(DataItem, LabelsMixIn, DraggableMixIn, AlphaMixIn):
if data is None:
data = numpy.zeros((0, 0, 4), dtype=numpy.uint8)
self._data = data
-
+ self._mask = mask
+ self.__valueDataCache = None # Store default data
self._origin = (0., 0.)
self._scale = (1., 1.)
@@ -186,13 +185,98 @@ class ImageBase(DataItem, LabelsMixIn, DraggableMixIn, AlphaMixIn):
:param numpy.ndarray data:
"""
+ previousShape = self._data.shape
self._data = data
+ self._valueDataChanged()
self._boundsChanged()
self._updated(ItemChangedType.DATA)
+ if (self.getMaskData(copy=False) is not None and
+ previousShape != self._data.shape):
+ # Data shape changed, so mask shape changes.
+ # Send event, mask is lazily updated in getMaskData
+ self._updated(ItemChangedType.MASK)
+
+ def getMaskData(self, copy=True):
+ """Returns the mask data
+
+ :param bool copy: True (Default) to get a copy,
+ False to use internal representation (do not modify!)
+ :rtype: Union[None,numpy.ndarray]
+ """
+ if self._mask is None:
+ return None
+
+ # Update mask if it does not match data shape
+ shape = self.getData(copy=False).shape[:2]
+ if self._mask.shape != shape:
+ # Clip/extend mask to match data
+ newMask = numpy.zeros(shape, dtype=self._mask.dtype)
+ newMask[:self._mask.shape[0], :self._mask.shape[1]] = self._mask[:shape[0], :shape[1]]
+ self._mask = newMask
+
+ return numpy.array(self._mask, copy=copy)
+
+ def setMaskData(self, mask, copy=True):
+ """Set the image data
+
+ :param numpy.ndarray data:
+ :param bool copy: True (Default) to make a copy,
+ False to use as is (do not modify!)
+ """
+ if mask is not None:
+ mask = numpy.array(mask, copy=copy)
+
+ shape = self.getData(copy=False).shape[:2]
+ if mask.shape != shape:
+ _logger.warning("Inconsistent shape between mask and data %s, %s", mask.shape, shape)
+ # Clip/extent is done lazily in getMaskData
+ elif self._mask is None:
+ return # No update
+
+ self._mask = mask
+ self._valueDataChanged()
+ self._updated(ItemChangedType.MASK)
+
+ def _valueDataChanged(self):
+ """Clear cache of default data array"""
+ self.__valueDataCache = None
+
+ def _getValueData(self, copy=True):
+ """Return data used by :meth:`getValueData`
+
+ :param bool copy:
+ :rtype: numpy.ndarray
+ """
+ return self.getData(copy=copy)
+
+ def getValueData(self, copy=True):
+ """Return data (converted to int or float) with mask applied.
+
+ Masked values are set to Not-A-Number.
+ It returns a 2D array of values (int or float).
+
+ :param bool copy:
+ :rtype: numpy.ndarray
+ """
+ if self.__valueDataCache is None:
+ data = self._getValueData(copy=False)
+ mask = self.getMaskData(copy=False)
+ if mask is not None:
+ if numpy.issubdtype(data.dtype, numpy.floating):
+ dtype = data.dtype
+ else:
+ dtype = numpy.float64
+ data = numpy.array(data, dtype=dtype, copy=True)
+ data[mask != 0] = numpy.NaN
+ self.__valueDataCache = data
+ return numpy.array(self.__valueDataCache, copy=copy)
+
def getRgbaImageData(self, copy=True):
"""Get the displayed RGB(A) image
+ :param bool copy: True (Default) to get a copy,
+ False to use internal representation (do not modify!)
:returns: numpy.ndarray of uint8 of shape (height, width, 4)
"""
raise NotImplementedError('This MUST be implemented in sub-class')
@@ -308,7 +392,7 @@ class ImageData(ImageBase, ColormapMixIn):
alphaImage = self.getAlphaData(copy=False)
if alphaImage is not None:
# Apply transparency
- image[:, :, 3] = image[:, :, 3] * alphaImage
+ image[:,:, 3] = image[:,:, 3] * alphaImage
return image
def getAlternativeImageData(self, copy=True):
@@ -358,7 +442,6 @@ class ImageData(ImageBase, ColormapMixIn):
_logger.warning(
'Converting complex image to absolute value to plot it.')
data = numpy.absolute(data)
- self._setColormappedData(data, copy=False)
if alternative is not None:
alternative = numpy.array(alternative, copy=copy)
@@ -378,6 +461,14 @@ class ImageData(ImageBase, ColormapMixIn):
super().setData(data)
+ def _updated(self, event=None, checkVisibility=True):
+ # Synchronizes colormapped data if changed
+ if event in (ItemChangedType.DATA, ItemChangedType.MASK):
+ self._setColormappedData(
+ self.getValueData(copy=False),
+ copy=False)
+ super()._updated(event=event, checkVisibility=checkVisibility)
+
class ImageRgba(ImageBase):
"""Description of an RGB(A) image"""
@@ -423,6 +514,20 @@ class ImageRgba(ImageBase):
assert data.shape[-1] in (3, 4)
super().setData(data)
+ def _getValueData(self, copy=True):
+ """Compute the intensity of the RGBA image as default data.
+
+ Conversion: https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion
+
+ :param bool copy:
+ """
+ rgba = self.getRgbaImageData(copy=False).astype(numpy.float32)
+ intensity = (rgba[:, :, 0] * 0.299 +
+ rgba[:, :, 1] * 0.587 +
+ rgba[:, :, 2] * 0.114)
+ intensity *= rgba[:, :, 3] / 255.
+ return intensity
+
class MaskImageData(ImageData):
"""Description of an image used as a mask.