summaryrefslogtreecommitdiff
path: root/silx/gui/plot/actions/histogram.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/actions/histogram.py')
-rw-r--r--silx/gui/plot/actions/histogram.py392
1 files changed, 0 insertions, 392 deletions
diff --git a/silx/gui/plot/actions/histogram.py b/silx/gui/plot/actions/histogram.py
deleted file mode 100644
index 0bba558..0000000
--- a/silx/gui/plot/actions/histogram.py
+++ /dev/null
@@ -1,392 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-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
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""
-:mod:`silx.gui.plot.actions.histogram` provides actions relative to histograms
-for :class:`.PlotWidget`.
-
-The following QAction are available:
-
-- :class:`PixelIntensitiesHistoAction`
-"""
-
-from __future__ import division
-
-__authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel"]
-__date__ = "01/12/2020"
-__license__ = "MIT"
-
-import numpy
-import logging
-import typing
-import weakref
-
-from .PlotToolAction import PlotToolAction
-
-from silx.math.histogram import Histogramnd
-from silx.math.combo import min_max
-from silx.gui import qt
-from silx.gui.plot import items
-from silx.gui.widgets.ElidedLabel import ElidedLabel
-from silx.utils.deprecation import deprecated
-
-_logger = logging.getLogger(__name__)
-
-
-class _ElidedLabel(ElidedLabel):
- """QLabel with a default size larger than what is displayed."""
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.setTextInteractionFlags(qt.Qt.TextSelectableByMouse)
-
- def sizeHint(self):
- hint = super().sizeHint()
- nbchar = max(len(self.getText()), 12)
- width = self.fontMetrics().boundingRect('#' * nbchar).width()
- return qt.QSize(max(hint.width(), width), hint.height())
-
-
-class _StatWidget(qt.QWidget):
- """Widget displaying a name and a value
-
- :param parent:
- :param name:
- """
-
- def __init__(self, parent=None, name: str=''):
- super().__init__(parent)
- layout = qt.QHBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
-
- keyWidget = qt.QLabel(parent=self)
- keyWidget.setText("<b>" + name.capitalize() + ":<b>")
- layout.addWidget(keyWidget)
- self.__valueWidget = _ElidedLabel(parent=self)
- self.__valueWidget.setText("-")
- self.__valueWidget.setTextInteractionFlags(
- qt.Qt.TextSelectableByMouse | qt.Qt.TextSelectableByKeyboard)
- layout.addWidget(self.__valueWidget)
-
- def setValue(self, value: typing.Optional[float]):
- """Set the displayed value
-
- :param value:
- """
- self.__valueWidget.setText(
- "-" if value is None else "{:.5g}".format(value))
-
-
-class HistogramWidget(qt.QWidget):
- """Widget displaying a histogram and some statistic indicators"""
-
- _SUPPORTED_ITEM_CLASS = items.ImageBase, items.Scatter
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.setWindowTitle('Histogram')
-
- self.__itemRef = None # weakref on the item to track
-
- layout = qt.QVBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
-
- # Plot
- # Lazy import to avoid circular dependencies
- from silx.gui.plot.PlotWindow import Plot1D
- self.__plot = Plot1D(self)
- layout.addWidget(self.__plot)
-
- self.__plot.setDataMargins(0.1, 0.1, 0.1, 0.1)
- self.__plot.getXAxis().setLabel("Value")
- self.__plot.getYAxis().setLabel("Count")
- posInfo = self.__plot.getPositionInfoWidget()
- posInfo.setSnappingMode(posInfo.SNAPPING_CURVE)
-
- # Stats display
- statsWidget = qt.QWidget(self)
- layout.addWidget(statsWidget)
- statsLayout = qt.QHBoxLayout(statsWidget)
- statsLayout.setContentsMargins(4, 4, 4, 4)
-
- self.__statsWidgets = dict(
- (name, _StatWidget(parent=statsWidget, name=name))
- for name in ("min", "max", "mean", "std", "sum"))
-
- for widget in self.__statsWidgets.values():
- statsLayout.addWidget(widget)
- statsLayout.addStretch(1)
-
- def getPlotWidget(self):
- """Returns :class:`PlotWidget` use to display the histogram"""
- return self.__plot
-
- def resetZoom(self):
- """Reset PlotWidget zoom"""
- self.getPlotWidget().resetZoom()
-
- def reset(self):
- """Clear displayed information"""
- self.getPlotWidget().clear()
- self.setStatistics()
-
- def getItem(self) -> typing.Optional[items.Item]:
- """Returns item used to display histogram and statistics."""
- return None if self.__itemRef is None else self.__itemRef()
-
- def setItem(self, item: typing.Optional[items.Item]):
- """Set item from which to display histogram and statistics.
-
- :param item:
- """
- previous = self.getItem()
- if previous is not None:
- previous.sigItemChanged.disconnect(self.__itemChanged)
-
- self.__itemRef = None if item is None else weakref.ref(item)
- if item is not None:
- if isinstance(item, self._SUPPORTED_ITEM_CLASS):
- # Only listen signal for supported items
- item.sigItemChanged.connect(self.__itemChanged)
- self._updateFromItem()
-
- def __itemChanged(self, event):
- """Handle update of the item"""
- if event in (items.ItemChangedType.DATA, items.ItemChangedType.MASK):
- self._updateFromItem()
-
- def _updateFromItem(self):
- """Update histogram and stats from the item"""
- item = self.getItem()
-
- if item is None:
- self.reset()
- return
-
- if not isinstance(item, self._SUPPORTED_ITEM_CLASS):
- _logger.error("Unsupported item", item)
- self.reset()
- return
-
- # Compute histogram and stats
- array = item.getValueData(copy=False)
-
- if array.size == 0:
- self.reset()
- return
-
- xmin, xmax = min_max(array, min_positive=False, finite=True)
- nbins = min(1024, int(numpy.sqrt(array.size)))
- data_range = xmin, xmax
-
- # bad hack: get 256 bins in the case we have a B&W
- if numpy.issubdtype(array.dtype, numpy.integer):
- if nbins > xmax - xmin:
- nbins = xmax - xmin
-
- nbins = max(2, nbins)
-
- data = array.ravel().astype(numpy.float32)
- histogram = Histogramnd(data, n_bins=nbins, histo_range=data_range)
- if len(histogram.edges) != 1:
- _logger.error("Error while computing the histogram")
- self.reset()
- return
-
- self.setHistogram(histogram.histo, histogram.edges[0])
- self.resetZoom()
- self.setStatistics(
- min_=xmin,
- max_=xmax,
- mean=numpy.nanmean(array),
- std=numpy.nanstd(array),
- sum_=numpy.nansum(array))
-
- def setHistogram(self, histogram, edges):
- """Set displayed histogram
-
- :param histogram: Bin values (N)
- :param edges: Bin edges (N+1)
- """
- self.getPlotWidget().addHistogram(
- histogram=histogram,
- edges=edges,
- legend='histogram',
- fill=True,
- color='#66aad7',
- resetzoom=False)
-
- def getHistogram(self, copy: bool=True):
- """Returns currently displayed histogram.
-
- :param copy: True to get a copy,
- False to get internal representation (Do not modify!)
- :return: (histogram, edges) or None
- """
- for item in self.getPlotWidget().getItems():
- if item.getName() == 'histogram':
- return (item.getValueData(copy=copy),
- item.getBinEdgesData(copy=copy))
- else:
- return None
-
- def setStatistics(self,
- min_: typing.Optional[float] = None,
- max_: typing.Optional[float] = None,
- mean: typing.Optional[float] = None,
- std: typing.Optional[float] = None,
- sum_: typing.Optional[float] = None):
- """Set displayed statistic indicators."""
- self.__statsWidgets['min'].setValue(min_)
- self.__statsWidgets['max'].setValue(max_)
- self.__statsWidgets['mean'].setValue(mean)
- self.__statsWidgets['std'].setValue(std)
- self.__statsWidgets['sum'].setValue(sum_)
-
-
-class _LastActiveItem(qt.QObject):
-
- sigActiveItemChanged = qt.Signal(object, object)
- """Emitted when the active plot item have changed"""
-
- def __init__(self, parent, plot):
- assert plot is not None
- super(_LastActiveItem, self).__init__(parent=parent)
- self.__plot = weakref.ref(plot)
- self.__item = None
- item = self.__findActiveItem()
- self.setActiveItem(item)
- plot.sigActiveImageChanged.connect(self._activeImageChanged)
- plot.sigActiveScatterChanged.connect(self._activeScatterChanged)
-
- def getPlotWidget(self):
- return self.__plot()
-
- def __findActiveItem(self):
- plot = self.getPlotWidget()
- image = plot.getActiveImage()
- if image is not None:
- return image
- scatter = plot.getActiveScatter()
- if scatter is not None:
- return scatter
-
- def getActiveItem(self):
- if self.__item is None:
- return None
- item = self.__item()
- if item is None:
- self.__item = None
- return item
-
- def setActiveItem(self, item):
- previous = self.getActiveItem()
- if previous is item:
- return
- if item is None:
- self.__item = None
- else:
- self.__item = weakref.ref(item)
- self.sigActiveItemChanged.emit(previous, item)
-
- def _activeImageChanged(self, previous, current):
- """Handle active image change"""
- plot = self.getPlotWidget()
- if current is None: # Fall-back to active scatter if any
- self.setActiveItem(plot.getActiveScatter())
- else:
- item = plot.getImage(current)
- if item is None:
- self.setActiveItem(None)
- elif isinstance(item, items.ImageBase):
- self.setActiveItem(item)
- else:
- # Do not touch anything, which is consistent with silx v0.12 behavior
- pass
-
- def _activeScatterChanged(self, previous, current):
- """Handle active scatter change"""
- plot = self.getPlotWidget()
- if current is None: # Fall-back to active image if any
- self.setActiveItem(plot.getActiveImage())
- else:
- item = plot.getScatter(current)
- self.setActiveItem(item)
-
-
-class PixelIntensitiesHistoAction(PlotToolAction):
- """QAction to plot the pixels intensities diagram
-
- :param plot: :class:`.PlotWidget` instance on which to operate
- :param parent: See :class:`QAction`
- """
-
- def __init__(self, plot, parent=None):
- PlotToolAction.__init__(self,
- plot,
- icon='pixel-intensities',
- text='pixels intensity',
- tooltip='Compute image intensity distribution',
- parent=parent)
- self._lastItemFilter = _LastActiveItem(self, plot)
-
- def _connectPlot(self, window):
- self._lastItemFilter.sigActiveItemChanged.connect(self._activeItemChanged)
- item = self._lastItemFilter.getActiveItem()
- self.getHistogramWidget().setItem(item)
- PlotToolAction._connectPlot(self, window)
-
- def _disconnectPlot(self, window):
- self._lastItemFilter.sigActiveItemChanged.disconnect(self._activeItemChanged)
- PlotToolAction._disconnectPlot(self, window)
- self.getHistogramWidget().setItem(None)
-
- def _activeItemChanged(self, previous, current):
- if self._isWindowInUse():
- self.getHistogramWidget().setItem(current)
-
- @deprecated(since_version='0.15.0')
- def computeIntensityDistribution(self):
- self.getHistogramWidget()._updateFromItem()
-
- def getHistogramWidget(self):
- """Returns the widget displaying the histogram"""
- return self._getToolWindow()
-
- @deprecated(since_version='0.15.0',
- replacement='getHistogramWidget().getPlotWidget()')
- def getHistogramPlotWidget(self):
- return self._getToolWindow().getPlotWidget()
-
- def _createToolWindow(self):
- return HistogramWidget(self.plot, qt.Qt.Window)
-
- def getHistogram(self) -> typing.Optional[numpy.ndarray]:
- """Return the last computed histogram
-
- :return: the histogram displayed in the HistogramWidget
- """
- histogram = self.getHistogramWidget().getHistogram()
- return None if histogram is None else histogram[0]