From f61b516bdf4913782cb7e9be23522e6adb926580 Mon Sep 17 00:00:00 2001 From: Thomas VINCENT Date: Wed, 7 Oct 2020 16:36:30 +0200 Subject: [PATCH 1/7] Add a DataItem base class for items with a data extent in the plot --- silx/gui/plot/items/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/silx/gui/plot/items/__init__.py b/silx/gui/plot/items/__init__.py index 4d4eac04cf..0484025e7b 100644 --- a/silx/gui/plot/items/__init__.py +++ b/silx/gui/plot/items/__init__.py @@ -32,7 +32,8 @@ __license__ = "MIT" __date__ = "22/06/2017" -from .core import (Item, LabelsMixIn, DraggableMixIn, ColormapMixIn, # noqa +from .core import (Item, DataItem, # noqa + LabelsMixIn, DraggableMixIn, ColormapMixIn, # noqa SymbolMixIn, ColorMixIn, YAxisMixIn, FillMixIn, # noqa AlphaMixIn, LineMixIn, ScatterVisualizationMixIn, # noqa ComplexMixIn, ItemChangedType, PointsBase) # noqa From 755af4468afd82ad72bd448aa114a1ec45260aed Mon Sep 17 00:00:00 2001 From: Thomas VINCENT Date: Wed, 7 Oct 2020 16:37:24 +0200 Subject: [PATCH 2/7] Handle DataItem in YAxisMixIn --- silx/gui/plot/items/core.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/silx/gui/plot/items/core.py b/silx/gui/plot/items/core.py index b38ed3fbed..d44e92b299 100644 --- a/silx/gui/plot/items/core.py +++ b/silx/gui/plot/items/core.py @@ -44,6 +44,7 @@ import six from ....utils.deprecation import deprecated +from ....utils.proxy import docstring from ....utils.enum import Enum as _Enum from ....math.combo import min_max from ... import qt @@ -376,6 +377,27 @@ def pick(self, x, y): return PickingResult(self, indices) +class DataItem(Item): + """Item with a data extent in the plot""" + + def _dataExtentChanged(self, checkVisibility: bool=True) -> None: + """Call this method in subclass when data extent has changed. + + :param bool checkVisibility: + """ + if not checkVisibility or self.isVisible(): + # TODO hackish data range implementation + plot = self.getPlot() + if plot is not None: + plot._invalidateDataRange() + + @docstring(Item) + def setVisible(self, visible: bool): + if visible != self.isVisible(): + self._dataExtentChanged(checkVisibility=False) + super().setVisible(visible) + + # Mix-in classes ############################################################## class ItemMixInBase(object): @@ -837,6 +859,8 @@ def setYAxis(self, yaxis): assert yaxis in ('left', 'right') if yaxis != self._yaxis: self._yaxis = yaxis + if isinstance(self, DataItem): + self._dataExtentChanged() self._updated(ItemChangedType.YAXIS) From ec20147fcf11eba2a3c92faee045b843c2f18690 Mon Sep 17 00:00:00 2001 From: Thomas VINCENT Date: Wed, 7 Oct 2020 16:38:38 +0200 Subject: [PATCH 3/7] Use DataItem in PointsBase (and so Scatter) and Curve --- silx/gui/plot/items/core.py | 10 +++------- silx/gui/plot/items/curve.py | 23 ----------------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/silx/gui/plot/items/core.py b/silx/gui/plot/items/core.py index d44e92b299..9a048d0b3d 100644 --- a/silx/gui/plot/items/core.py +++ b/silx/gui/plot/items/core.py @@ -1226,7 +1226,7 @@ def getCurrentVisualizationParameter(self, parameter): return self.getVisualizationParameter(parameter) -class PointsBase(Item, SymbolMixIn, AlphaMixIn): +class PointsBase(DataItem, SymbolMixIn, AlphaMixIn): """Base class for :class:`Curve` and :class:`Scatter`""" # note: _logFilterData must be overloaded if you overload # getData to change its signature @@ -1236,7 +1236,7 @@ class PointsBase(Item, SymbolMixIn, AlphaMixIn): on top of images.""" def __init__(self): - Item.__init__(self) + DataItem.__init__(self) SymbolMixIn.__init__(self) AlphaMixIn.__init__(self) self._x = () @@ -1508,11 +1508,7 @@ def setData(self, x, y, xerror=None, yerror=None, copy=True): self._filteredCache = {} # Reset cached filtered data self._clippedCache = {} # Reset cached clipped bool array - # TODO hackish data range implementation - if self.isVisible(): - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() + self._dataExtentChanged() self._updated(ItemChangedType.DATA) diff --git a/silx/gui/plot/items/curve.py b/silx/gui/plot/items/curve.py index 7922fa1af0..75e7f01e7a 100644 --- a/silx/gui/plot/items/curve.py +++ b/silx/gui/plot/items/curve.py @@ -185,15 +185,6 @@ def __init__(self): self._setBaseline(Curve._DEFAULT_BASELINE) - self.sigItemChanged.connect(self.__itemChanged) - - def __itemChanged(self, event): - if event == ItemChangedType.YAXIS: - # TODO hackish data range implementation - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - def _addBackendRenderer(self, backend): """Update backend renderer""" # Filter-out values <= 0 @@ -251,20 +242,6 @@ def __getitem__(self, item): else: raise IndexError("Index out of range: %s", str(item)) - def setVisible(self, visible): - """Set visibility of item. - - :param bool visible: True to display it, False otherwise - """ - visible = bool(visible) - # TODO hackish data range implementation - if self.isVisible() != visible: - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - - super(Curve, self).setVisible(visible) - @deprecated(replacement='Curve.getHighlightedStyle().getColor()', since_version='0.9.0') def getHighlightedColor(self): From 3ab1f4c28d6c411861040b9633e0077807d4317a Mon Sep 17 00:00:00 2001 From: Thomas VINCENT Date: Wed, 7 Oct 2020 16:39:40 +0200 Subject: [PATCH 4/7] Use DataItem in data, complex and RGBA image items --- silx/gui/plot/items/complex.py | 15 ++----- silx/gui/plot/items/image.py | 79 +++++++++++----------------------- 2 files changed, 29 insertions(+), 65 deletions(-) diff --git a/silx/gui/plot/items/complex.py b/silx/gui/plot/items/complex.py index 8f0694d4ef..0e492a04a1 100644 --- a/silx/gui/plot/items/complex.py +++ b/silx/gui/plot/items/complex.py @@ -124,10 +124,9 @@ class ImageComplexData(ImageBase, ColormapMixIn, ComplexMixIn): """Overrides supported ComplexMode""" def __init__(self): - ImageBase.__init__(self) + ImageBase.__init__(self, numpy.zeros((0, 0), dtype=numpy.complex64)) ColormapMixIn.__init__(self) ComplexMixIn.__init__(self) - self._data = numpy.zeros((0, 0), dtype=numpy.complex64) self._dataByModesCache = {} self._amplitudeRangeInfo = None, 2 @@ -264,17 +263,9 @@ def setData(self, data, copy=True): 'Image is not complex, converting it to complex to plot it.') data = numpy.array(data, dtype=numpy.complex64) - self._data = data self._dataByModesCache = {} self._setColormappedData(self.getData(copy=False), copy=False) - - # TODO hackish data range implementation - if self.isVisible(): - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - - self._updated(ItemChangedType.DATA) + super().setData(data) def getComplexData(self, copy=True): """Returns the image complex data @@ -283,7 +274,7 @@ def getComplexData(self, copy=True): False to use internal representation (do not modify!) :rtype: numpy.ndarray of complex """ - return numpy.array(self._data, copy=copy) + return super().getData(copy=copy) def getData(self, copy=True, mode=None): """Returns the image data corresponding to (current) mode. diff --git a/silx/gui/plot/items/image.py b/silx/gui/plot/items/image.py index 91c051db8f..c11d803c2a 100644 --- a/silx/gui/plot/items/image.py +++ b/silx/gui/plot/items/image.py @@ -40,7 +40,7 @@ import numpy from ....utils.proxy import docstring -from .core import (Item, LabelsMixIn, DraggableMixIn, ColormapMixIn, +from .core import (DataItem, LabelsMixIn, DraggableMixIn, ColormapMixIn, AlphaMixIn, ItemChangedType) @@ -87,15 +87,20 @@ def _convertImageToRgba32(image, copy=True): return numpy.array(image, copy=copy) -class ImageBase(Item, LabelsMixIn, DraggableMixIn, AlphaMixIn): - """Description of an image""" +class ImageBase(DataItem, LabelsMixIn, DraggableMixIn, AlphaMixIn): + """Description of an image - def __init__(self): - Item.__init__(self) + :param numpy.ndarray data: Initial image data + """ + + def __init__(self, data=None): + DataItem.__init__(self) LabelsMixIn.__init__(self) DraggableMixIn.__init__(self) AlphaMixIn.__init__(self) - self._data = numpy.zeros((0, 0, 4), dtype=numpy.uint8) + if data is None: + data = numpy.zeros((0, 0, 4), dtype=numpy.uint8) + self._data = data self._origin = (0., 0.) self._scale = (1., 1.) @@ -129,19 +134,6 @@ def __getitem__(self, item): else: raise IndexError("Index out of range: %s" % str(item)) - def setVisible(self, visible): - """Set visibility of item. - - :param bool visible: True to display it, False otherwise - """ - visible = bool(visible) - # TODO hackish data range implementation - if self.isVisible() != visible: - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - super(ImageBase, self).setVisible(visible) - def _isPlotLinear(self, plot): """Return True if plot only uses linear scale for both of x and y axes.""" @@ -189,6 +181,15 @@ def getData(self, copy=True): """ return numpy.array(self._data, copy=copy) + def setData(self, data): + """Set the image data + + :param Union[numpy.ndarray,None] data: + """ + self._data = data + self._dataExtentChanged() + self._updated(ItemChangedType.DATA) + def getRgbaImageData(self, copy=True): """Get the displayed RGB(A) image @@ -215,13 +216,7 @@ def setOrigin(self, origin): origin = float(origin), float(origin) if origin != self._origin: self._origin = origin - - # TODO hackish data range implementation - if self.isVisible(): - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - + self._dataExtentChanged() self._updated(ItemChangedType.POSITION) def getScale(self): @@ -244,13 +239,7 @@ def setScale(self, scale): if scale != self._scale: self._scale = scale - - # TODO hackish data range implementation - if self.isVisible(): - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - + self._dataExtentChanged() self._updated(ItemChangedType.SCALE) @@ -258,9 +247,8 @@ class ImageData(ImageBase, ColormapMixIn): """Description of a data image with a colormap""" def __init__(self): - ImageBase.__init__(self) + ImageBase.__init__(self, numpy.zeros((0, 0), dtype=numpy.float32)) ColormapMixIn.__init__(self) - self._data = numpy.zeros((0, 0), dtype=numpy.float32) self._alternativeImage = None self.__alpha = None @@ -370,7 +358,6 @@ def setData(self, data, alternative=None, alpha=None, copy=True): _logger.warning( 'Converting complex image to absolute value to plot it.') data = numpy.absolute(data) - self._data = data self._setColormappedData(data, copy=False) if alternative is not None: @@ -389,20 +376,14 @@ def setData(self, data, alternative=None, alpha=None, copy=True): alpha = numpy.clip(alpha, 0., 1.) self.__alpha = alpha - # TODO hackish data range implementation - if self.isVisible(): - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - - self._updated(ItemChangedType.DATA) + super().setData(data) class ImageRgba(ImageBase): """Description of an RGB(A) image""" def __init__(self): - ImageBase.__init__(self) + ImageBase.__init__(self, numpy.zeros((0, 0, 4), dtype=numpy.uint8)) def _addBackendRenderer(self, backend): """Update backend renderer""" @@ -440,15 +421,7 @@ def setData(self, data, copy=True): data = numpy.array(data, copy=copy) assert data.ndim == 3 assert data.shape[-1] in (3, 4) - self._data = data - - # TODO hackish data range implementation - if self.isVisible(): - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - - self._updated(ItemChangedType.DATA) + super().setData(data) class MaskImageData(ImageData): From 06e2dd18b2c693872537b63b6f60c4a358354a57 Mon Sep 17 00:00:00 2001 From: Thomas VINCENT Date: Wed, 7 Oct 2020 16:40:31 +0200 Subject: [PATCH 5/7] Use DataItem in Histogram --- silx/gui/plot/items/histogram.py | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/silx/gui/plot/items/histogram.py b/silx/gui/plot/items/histogram.py index 935f8d53f4..f3b078a6b7 100644 --- a/silx/gui/plot/items/histogram.py +++ b/silx/gui/plot/items/histogram.py @@ -38,7 +38,7 @@ except ImportError: # Python2 support import collections as abc -from .core import (Item, AlphaMixIn, BaselineMixIn, ColorMixIn, FillMixIn, +from .core import (DataItem, AlphaMixIn, BaselineMixIn, ColorMixIn, FillMixIn, LineMixIn, YAxisMixIn, ItemChangedType) _logger = logging.getLogger(__name__) @@ -100,7 +100,7 @@ def _getHistogramCurve(histogram, edges): # TODO: Yerror, test log scale -class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn, +class Histogram(DataItem, AlphaMixIn, ColorMixIn, FillMixIn, LineMixIn, YAxisMixIn, BaselineMixIn): """Description of an histogram""" @@ -119,7 +119,7 @@ class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn, _DEFAULT_BASELINE = None def __init__(self): - Item.__init__(self) + DataItem.__init__(self) AlphaMixIn.__init__(self) BaselineMixIn.__init__(self) ColorMixIn.__init__(self) @@ -219,19 +219,6 @@ def _getBounds(self): min(0, numpy.nanmin(values)), max(0, numpy.nanmax(values))) - def setVisible(self, visible): - """Set visibility of item. - - :param bool visible: True to display it, False otherwise - """ - visible = bool(visible) - # TODO hackish data range implementation - if self.isVisible() != visible: - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - super(Histogram, self).setVisible(visible) - def getValueData(self, copy=True): """The values of the histogram @@ -314,11 +301,7 @@ def setData(self, histogram, edges, align='center', baseline=None, self._alignement = align self._setBaseline(baseline) - if self.isVisible(): - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - + self._dataExtentChanged() self._updated(ItemChangedType.DATA) def getAlignment(self): From 564f26a488aa9b67a8297471ccfe68b6b1c7cefd Mon Sep 17 00:00:00 2001 From: Thomas VINCENT Date: Wed, 7 Oct 2020 16:41:10 +0200 Subject: [PATCH 6/7] Use DataItem in BoundingRect, XAxisExtent and YAxisExtent --- silx/gui/plot/items/shape.py | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/silx/gui/plot/items/shape.py b/silx/gui/plot/items/shape.py index 26aa03b742..b565c25a4a 100644 --- a/silx/gui/plot/items/shape.py +++ b/silx/gui/plot/items/shape.py @@ -36,7 +36,9 @@ import six from ... import colors -from .core import Item, ColorMixIn, FillMixIn, ItemChangedType, LineMixIn, YAxisMixIn +from .core import ( + Item, DataItem, + ColorMixIn, FillMixIn, ItemChangedType, LineMixIn, YAxisMixIn) _logger = logging.getLogger(__name__) @@ -154,7 +156,7 @@ def setLineBgColor(self, color, copy=True): self._updated(ItemChangedType.LINE_BG_COLOR) -class BoundingRect(Item, YAxisMixIn): +class BoundingRect(DataItem, YAxisMixIn): """An invisible shape which enforce the plot view to display the defined space on autoscale. @@ -166,21 +168,10 @@ class BoundingRect(Item, YAxisMixIn): """ def __init__(self): - Item.__init__(self) + DataItem.__init__(self) YAxisMixIn.__init__(self) self.__bounds = None - def _updated(self, event=None, checkVisibility=True): - if event in (ItemChangedType.YAXIS, - ItemChangedType.VISIBLE, - ItemChangedType.DATA): - # TODO hackish data range implementation - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - - super(BoundingRect, self)._updated(event, checkVisibility) - def setBounds(self, rect): """Set the bounding box of this item in data coordinates @@ -193,6 +184,7 @@ def setBounds(self, rect): if rect != self.__bounds: self.__bounds = rect + self._dataExtentChanged() self._updated(ItemChangedType.DATA) def _getBounds(self): @@ -217,7 +209,7 @@ def _getBounds(self): return self.__bounds -class _BaseExtent(Item): +class _BaseExtent(DataItem): """Base class for :class:`XAxisExtent` and :class:`YAxisExtent`. :param str axis: Either 'x' or 'y'. @@ -225,20 +217,10 @@ class _BaseExtent(Item): def __init__(self, axis='x'): assert axis in ('x', 'y') - Item.__init__(self) + DataItem.__init__(self) self.__axis = axis self.__range = 1., 100. - def _updated(self, event=None, checkVisibility=True): - if event in (ItemChangedType.VISIBLE, - ItemChangedType.DATA): - # TODO hackish data range implementation - plot = self.getPlot() - if plot is not None: - plot._invalidateDataRange() - - super(_BaseExtent, self)._updated(event, checkVisibility) - def setRange(self, min_, max_): """Set the range of the extent of this item in data coordinates. @@ -254,6 +236,7 @@ def setRange(self, min_, max_): if range_ != self.__range: self.__range = range_ + self._dataExtentChanged() self._updated(ItemChangedType.DATA) def getRange(self): From 16d3d1615b3fd9fae59d66f0a76c3c9d7e9f4409 Mon Sep 17 00:00:00 2001 From: Thomas VINCENT Date: Fri, 16 Oct 2020 14:02:35 +0200 Subject: [PATCH 7/7] rename _dataExtentChanged -> _boundsChanged --- silx/gui/plot/items/core.py | 10 +++++----- silx/gui/plot/items/histogram.py | 2 +- silx/gui/plot/items/image.py | 6 +++--- silx/gui/plot/items/shape.py | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/silx/gui/plot/items/core.py b/silx/gui/plot/items/core.py index 9a048d0b3d..5881bf7687 100644 --- a/silx/gui/plot/items/core.py +++ b/silx/gui/plot/items/core.py @@ -380,8 +380,8 @@ def pick(self, x, y): class DataItem(Item): """Item with a data extent in the plot""" - def _dataExtentChanged(self, checkVisibility: bool=True) -> None: - """Call this method in subclass when data extent has changed. + def _boundsChanged(self, checkVisibility: bool=True) -> None: + """Call this method in subclass when data bounds has changed. :param bool checkVisibility: """ @@ -394,7 +394,7 @@ def _dataExtentChanged(self, checkVisibility: bool=True) -> None: @docstring(Item) def setVisible(self, visible: bool): if visible != self.isVisible(): - self._dataExtentChanged(checkVisibility=False) + self._boundsChanged(checkVisibility=False) super().setVisible(visible) @@ -860,7 +860,7 @@ def setYAxis(self, yaxis): if yaxis != self._yaxis: self._yaxis = yaxis if isinstance(self, DataItem): - self._dataExtentChanged() + self._boundsChanged() self._updated(ItemChangedType.YAXIS) @@ -1508,7 +1508,7 @@ def setData(self, x, y, xerror=None, yerror=None, copy=True): self._filteredCache = {} # Reset cached filtered data self._clippedCache = {} # Reset cached clipped bool array - self._dataExtentChanged() + self._boundsChanged() self._updated(ItemChangedType.DATA) diff --git a/silx/gui/plot/items/histogram.py b/silx/gui/plot/items/histogram.py index f3b078a6b7..01d1ff9a3c 100644 --- a/silx/gui/plot/items/histogram.py +++ b/silx/gui/plot/items/histogram.py @@ -301,7 +301,7 @@ def setData(self, histogram, edges, align='center', baseline=None, self._alignement = align self._setBaseline(baseline) - self._dataExtentChanged() + self._boundsChanged() self._updated(ItemChangedType.DATA) def getAlignment(self): diff --git a/silx/gui/plot/items/image.py b/silx/gui/plot/items/image.py index c11d803c2a..42bff905a9 100644 --- a/silx/gui/plot/items/image.py +++ b/silx/gui/plot/items/image.py @@ -187,7 +187,7 @@ def setData(self, data): :param Union[numpy.ndarray,None] data: """ self._data = data - self._dataExtentChanged() + self._boundsChanged() self._updated(ItemChangedType.DATA) def getRgbaImageData(self, copy=True): @@ -216,7 +216,7 @@ def setOrigin(self, origin): origin = float(origin), float(origin) if origin != self._origin: self._origin = origin - self._dataExtentChanged() + self._boundsChanged() self._updated(ItemChangedType.POSITION) def getScale(self): @@ -239,7 +239,7 @@ def setScale(self, scale): if scale != self._scale: self._scale = scale - self._dataExtentChanged() + self._boundsChanged() self._updated(ItemChangedType.SCALE) diff --git a/silx/gui/plot/items/shape.py b/silx/gui/plot/items/shape.py index b565c25a4a..955dfe31f4 100644 --- a/silx/gui/plot/items/shape.py +++ b/silx/gui/plot/items/shape.py @@ -184,7 +184,7 @@ def setBounds(self, rect): if rect != self.__bounds: self.__bounds = rect - self._dataExtentChanged() + self._boundsChanged() self._updated(ItemChangedType.DATA) def _getBounds(self): @@ -236,7 +236,7 @@ def setRange(self, min_, max_): if range_ != self.__range: self.__range = range_ - self._dataExtentChanged() + self._boundsChanged() self._updated(ItemChangedType.DATA) def getRange(self):