summaryrefslogtreecommitdiff
path: root/silx/gui/dialog/ColormapDialog.py
diff options
context:
space:
mode:
authorPicca Frédéric-Emmanuel <picca@debian.org>2022-02-02 14:19:58 +0100
committerPicca Frédéric-Emmanuel <picca@debian.org>2022-02-02 14:19:58 +0100
commit4e774db12d5ebe7a20eded6dd434a289e27999e5 (patch)
treea9822974ba45196f1e3740995ab157d6eb214a04 /silx/gui/dialog/ColormapDialog.py
parentd3194b1a9c4404ba93afac43d97172ab24c57098 (diff)
New upstream version 1.0.0+dfsg
Diffstat (limited to 'silx/gui/dialog/ColormapDialog.py')
-rw-r--r--silx/gui/dialog/ColormapDialog.py1771
1 files changed, 0 insertions, 1771 deletions
diff --git a/silx/gui/dialog/ColormapDialog.py b/silx/gui/dialog/ColormapDialog.py
deleted file mode 100644
index ca7ee97..0000000
--- a/silx/gui/dialog/ColormapDialog.py
+++ /dev/null
@@ -1,1771 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2020 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.
-#
-# ###########################################################################*/
-"""A QDialog widget to set-up the colormap.
-
-It uses a description of colormaps as dict compatible with :class:`Plot`.
-
-To run the following sample code, a QApplication must be initialized.
-
-Create the colormap dialog and set the colormap description and data range:
-
->>> from silx.gui.dialog.ColormapDialog import ColormapDialog
->>> from silx.gui.colors import Colormap
-
->>> dialog = ColormapDialog()
->>> colormap = Colormap(name='red', normalization='log',
-... vmin=1., vmax=2.)
-
->>> dialog.setColormap(colormap)
->>> colormap.setVRange(1., 100.) # This scale the width of the plot area
->>> dialog.show()
-
-Get the colormap description (compatible with :class:`Plot`) from the dialog:
-
->>> cmap = dialog.getColormap()
->>> cmap.getName()
-'red'
-
-It is also possible to display an histogram of the image in the dialog.
-This updates the data range with the range of the bins.
-
->>> import numpy
->>> image = numpy.random.normal(size=512 * 512).reshape(512, -1)
->>> hist, bin_edges = numpy.histogram(image, bins=10)
->>> dialog.setHistogram(hist, bin_edges)
-
-The updates of the colormap description are also available through the signal:
-:attr:`ColormapDialog.sigColormapChanged`.
-""" # noqa
-
-__authors__ = ["V.A. Sole", "T. Vincent", "H. Payno"]
-__license__ = "MIT"
-__date__ = "08/12/2020"
-
-import enum
-import logging
-
-import numpy
-
-from .. import qt
-from .. import utils
-from ..colors import Colormap, cursorColorForColormap
-from ..plot import PlotWidget
-from ..plot.items.axis import Axis
-from ..plot.items import BoundingRect
-from silx.gui.widgets.FloatEdit import FloatEdit
-import weakref
-from silx.math.combo import min_max
-from silx.gui.plot import items
-from silx.gui import icons
-from silx.gui.qt import inspect as qtinspect
-from silx.gui.widgets.ColormapNameComboBox import ColormapNameComboBox
-from silx.gui.widgets.WaitingPushButton import WaitingPushButton
-from silx.math.histogram import Histogramnd
-from silx.utils import deprecation
-from silx.gui.plot.items.roi import RectangleROI
-from silx.gui.plot.tools.roi import RegionOfInterestManager
-
-_logger = logging.getLogger(__name__)
-
-_colormapIconPreview = {}
-
-
-class _DataRefHolder(items.Item, items.ColormapMixIn):
- """Holder for a weakref of a numpy array.
-
- It provides features from `ColormapMixIn`.
- """
-
- def __init__(self, dataRef):
- items.Item.__init__(self)
- items.ColormapMixIn.__init__(self)
- self.__dataRef = dataRef
- self._updated(items.ItemChangedType.DATA)
-
- def getColormappedData(self, copy=True):
- return self.__dataRef()
-
-
-class _BoundaryWidget(qt.QWidget):
- """Widget to edit a boundary of the colormap (vmin or vmax)"""
-
- sigAutoScaleChanged = qt.Signal(object)
- """Signal emitted when the autoscale was changed
-
- True is sent as an argument if autoscale is set to true.
- """
-
- sigValueChanged = qt.Signal(object)
- """Signal emitted when value is changed
-
- The new value is sent as an argument.
- """
-
- def __init__(self, parent=None, value=0.0):
- qt.QWidget.__init__(self, parent=parent)
- self.setLayout(qt.QHBoxLayout())
- self.layout().setContentsMargins(0, 0, 0, 0)
- self._numVal = FloatEdit(parent=self, value=value)
- self.layout().addWidget(self._numVal)
- self._autoCB = qt.QCheckBox('auto', parent=self)
- self.layout().addWidget(self._autoCB)
- self._autoCB.setChecked(False)
- self._autoCB.setVisible(False)
-
- self._autoCB.toggled.connect(self._autoToggled)
- self._numVal.textEdited.connect(self.__textEdited)
- self._numVal.editingFinished.connect(self.__editingFinished)
- self.setFocusProxy(self._numVal)
-
- self.__textWasEdited = False
- """True if the text was edited, in order to send an event
- at the end of the user interaction"""
-
- self.__realValue = None
- """Store the real value set by setValue, to avoid
- rounding of the widget"""
-
- def __textEdited(self):
- self.__textWasEdited = True
-
- def __editingFinished(self):
- if self.__textWasEdited:
- value = self._numVal.value()
- self.__realValue = value
- with utils.blockSignals(self._numVal):
- # Fix the formatting
- self._numVal.setValue(self.__realValue)
- self.sigValueChanged.emit(value)
- self.__textWasEdited = False
-
- def isAutoChecked(self):
- return self._autoCB.isChecked()
-
- def getValue(self):
- """Returns the stored range. If autoscale is
- enabled, this returns None.
- """
- if self._autoCB.isChecked():
- return None
- if self.__realValue is not None:
- return self.__realValue
- return self._numVal.value()
-
- def _autoToggled(self, enabled):
- self._numVal.setEnabled(not enabled)
- self._updateDisplayedText()
- self.sigAutoScaleChanged.emit(enabled)
-
- def _updateDisplayedText(self):
- self.__textWasEdited = False
- if self._autoCB.isChecked() and self.__realValue is not None:
- with utils.blockSignals(self._numVal):
- self._numVal.setValue(self.__realValue)
-
- def setValue(self, value, isAuto=False):
- """Set the value of the boundary.
-
- :param float value: A finite value for the boundary
- :param bool isAuto: If true, the finite value was automatically computed
- from the data, else it is a fixed custom value.
- """
- assert value is not None
- self._autoCB.setChecked(isAuto)
- with utils.blockSignals(self._numVal):
- if isAuto or self.__realValue != value:
- if not self.__textWasEdited:
- self._numVal.setValue(value)
- self.__realValue = value
- self._numVal.setEnabled(not isAuto)
-
-
-class _AutoscaleModeComboBox(qt.QComboBox):
-
- DATA = {
- Colormap.MINMAX: ("Min/max", "Use the data min/max"),
- Colormap.STDDEV3: ("Mean ± 3 × stddev", "Use the data mean ± 3 × standard deviation"),
- }
-
- def __init__(self, parent: qt.QWidget):
- super(_AutoscaleModeComboBox, self).__init__(parent=parent)
- self.currentIndexChanged.connect(self.__updateTooltip)
- self._init()
-
- def _init(self):
- for mode in Colormap.AUTOSCALE_MODES:
- label, tooltip = self.DATA.get(mode, (mode, None))
- self.addItem(label, mode)
- if tooltip is not None:
- self.setItemData(self.count() - 1, tooltip, qt.Qt.ToolTipRole)
-
- def setCurrentIndex(self, index):
- self.__updateTooltip(index)
- super(_AutoscaleModeComboBox, self).setCurrentIndex(index)
-
- def __updateTooltip(self, index):
- if index > -1:
- tooltip = self.itemData(index, qt.Qt.ToolTipRole)
- else:
- tooltip = ""
- self.setToolTip(tooltip)
-
- def currentMode(self):
- index = self.currentIndex()
- return self.itemData(index)
-
- def setCurrentMode(self, mode):
- for index in range(self.count()):
- if mode == self.itemData(index):
- self.setCurrentIndex(index)
- return
- if mode is None:
- # If None was not a value
- self.setCurrentIndex(-1)
- return
- self.addItem(mode, mode)
- self.setCurrentIndex(self.count() - 1)
-
-
-class _AutoScaleButtons(qt.QWidget):
-
- autoRangeChanged = qt.Signal(object)
-
- def __init__(self, parent=None):
- qt.QWidget.__init__(self, parent=parent)
- layout = qt.QHBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
-
- self.setFocusPolicy(qt.Qt.NoFocus)
-
- self._bothAuto = qt.QPushButton(self)
- self._bothAuto.setText("Autoscale")
- self._bothAuto.setToolTip("Enable/disable the autoscale for both min and max")
- self._bothAuto.setCheckable(True)
- self._bothAuto.toggled[bool].connect(self.__bothToggled)
- self._bothAuto.setFocusPolicy(qt.Qt.TabFocus)
-
- self._minAuto = qt.QCheckBox(self)
- self._minAuto.setText("")
- self._minAuto.setToolTip("Enable/disable the autoscale for min")
- self._minAuto.toggled[bool].connect(self.__minToggled)
- self._minAuto.setFocusPolicy(qt.Qt.TabFocus)
-
- self._maxAuto = qt.QCheckBox(self)
- self._maxAuto.setText("")
- self._maxAuto.setToolTip("Enable/disable the autoscale for max")
- self._maxAuto.toggled[bool].connect(self.__maxToggled)
- self._maxAuto.setFocusPolicy(qt.Qt.TabFocus)
-
- layout.addStretch(1)
- layout.addWidget(self._minAuto)
- layout.addSpacing(20)
- layout.addWidget(self._bothAuto)
- layout.addSpacing(20)
- layout.addWidget(self._maxAuto)
- layout.addStretch(1)
-
- def __bothToggled(self, checked):
- autoRange = checked, checked
- self.setAutoRange(autoRange)
- self.autoRangeChanged.emit(autoRange)
-
- def __minToggled(self, checked):
- autoRange = self.getAutoRange()
- self.setAutoRange(autoRange)
- self.autoRangeChanged.emit(autoRange)
-
- def __maxToggled(self, checked):
- autoRange = self.getAutoRange()
- self.setAutoRange(autoRange)
- self.autoRangeChanged.emit(autoRange)
-
- def setAutoRangeFromColormap(self, colormap):
- vRange = colormap.getVRange()
- autoRange = vRange[0] is None, vRange[1] is None
- self.setAutoRange(autoRange)
-
- def setAutoRange(self, autoRange):
- if autoRange[0] == autoRange[1]:
- with utils.blockSignals(self._bothAuto):
- self._bothAuto.setChecked(autoRange[0])
- else:
- with utils.blockSignals(self._bothAuto):
- self._bothAuto.setChecked(False)
- with utils.blockSignals(self._minAuto):
- self._minAuto.setChecked(autoRange[0])
- with utils.blockSignals(self._maxAuto):
- self._maxAuto.setChecked(autoRange[1])
-
- def getAutoRange(self):
- return self._minAuto.isChecked(), self._maxAuto.isChecked()
-
-
-@enum.unique
-class _DataInPlotMode(enum.Enum):
- """Enum for each mode of display of the data in the plot."""
- RANGE = 'range'
- HISTOGRAM = 'histogram'
-
-
-class _ColormapHistogram(qt.QWidget):
- """Display the colormap and the data as a plot."""
-
- sigRangeMoving = qt.Signal(object, object)
- """Emitted when a mouse interaction moves the location
- of the colormap range in the plot.
-
- This signal contains 2 elements:
-
- - vmin: A float value if this range was moved, else None
- - vmax: A float value if this range was moved, else None
- """
-
- sigRangeMoved = qt.Signal(object, object)
- """Emitted when a mouse interaction stop.
-
- This signal contains 2 elements:
-
- - vmin: A float value if this range was moved, else None
- - vmax: A float value if this range was moved, else None
- """
-
- def __init__(self, parent):
- qt.QWidget.__init__(self, parent=parent)
- self._dataInPlotMode = _DataInPlotMode.RANGE
- self._finiteRange = None, None
- self._initPlot()
-
- self._histogramData = {}
- """Histogram displayed in the plot"""
-
- self._dragging = False, False
- """True, if the min or the max handle is dragging"""
-
- self._dataRange = {}
- """Histogram displayed in the plot"""
-
- self._invalidated = False
-
- def paintEvent(self, event):
- if self._invalidated:
- self._updateDataInPlot()
- self._invalidated = False
- self._updateMarkerPosition()
- return super(_ColormapHistogram, self).paintEvent(event)
-
- def getFiniteRange(self):
- """Returns the colormap range as displayed in the plot."""
- return self._finiteRange
-
- def setFiniteRange(self, vRange):
- """Set the colormap range to use in the plot.
-
- Here there is no concept of auto. The values should
- not be None, except if there is no range or marker
- to display.
- """
- # Do not reset the limit for handle about to be dragged
- if self._dragging[0]:
- vRange = self._finiteRange[0], vRange[1]
- if self._dragging[1]:
- vRange = vRange[0], self._finiteRange[1]
-
- if vRange == self._finiteRange:
- return
-
- self._finiteRange = vRange
- self.update()
-
- def getColormap(self):
- return self.parent().getColormap()
-
- def _getNormalizedHistogram(self):
- """Return an histogram already normalized according to the colormap
- normalization.
-
- Returns a tuple edges, counts
- """
- norm = self._getNorm()
- histogram = self._histogramData.get(norm, None)
- if histogram is None:
- histogram = self._computeNormalizedHistogram()
- self._histogramData[norm] = histogram
- return histogram
-
- def _computeNormalizedHistogram(self):
- colormap = self.getColormap()
- if colormap is None:
- norm = Colormap.LINEAR
- else:
- norm = colormap.getNormalization()
-
- # Try to use the histogram defined in the dialog
- histo = self.parent()._getHistogram()
- if histo is not None:
- counts, edges = histo
- normalizer = Colormap(normalization=norm)._getNormalizer()
- mask = normalizer.isValid(edges[:-1]) # Check lower bin edges only
- firstValid = numpy.argmax(mask) # edges increases monotonically
- if firstValid == 0: # Mask is all False or all True
- return (counts, edges) if mask[0] else (None, None)
- else: # Clip to valid values
- return counts[firstValid:], edges[firstValid:]
-
- data = self.parent()._getArray()
- if data is None:
- return None, None
- dataRange = self._getNormalizedDataRange()
- if dataRange[0] is None or dataRange[1] is None:
- return None, None
- counts, edges = self.parent().computeHistogram(data, scale=norm, dataRange=dataRange)
- return counts, edges
-
- def _getNormalizedDataRange(self):
- """Return a data range already normalized according to the colormap
- normalization.
-
- Returns a tuple with min and max
- """
- norm = self._getNorm()
- dataRange = self._dataRange.get(norm, None)
- if dataRange is None:
- dataRange = self._computeNormalizedDataRange()
- self._dataRange[norm] = dataRange
- return dataRange
-
- def _computeNormalizedDataRange(self):
- colormap = self.getColormap()
- if colormap is None:
- norm = Colormap.LINEAR
- else:
- norm = colormap.getNormalization()
-
- # Try to use the one defined in the dialog
- dataRange = self.parent()._getDataRange()
- if dataRange is not None:
- if norm in (Colormap.LINEAR, Colormap.GAMMA, Colormap.ARCSINH):
- return dataRange[0], dataRange[2]
- elif norm == Colormap.LOGARITHM:
- return dataRange[1], dataRange[2]
- elif norm == Colormap.SQRT:
- return dataRange[1], dataRange[2]
- else:
- _logger.error("Undefined %s normalization", norm)
-
- # Try to use the histogram defined in the dialog
- histo = self.parent()._getHistogram()
- if histo is not None:
- _histo, edges = histo
- normalizer = Colormap(normalization=norm)._getNormalizer()
- edges = edges[normalizer.isValid(edges)]
- if edges.size == 0:
- return None, None
- else:
- dataRange = min_max(edges, finite=True)
- return dataRange.minimum, dataRange.maximum
-
- item = self.parent()._getItem()
- if item is not None:
- # Trick to reach data range using colormap cache
- cm = Colormap()
- cm.setVRange(None, None)
- cm.setNormalization(norm)
- dataRange = item._getColormapAutoscaleRange(cm)
- return dataRange
-
- # If there is no item, there is no data
- return None, None
-
- def _getDisplayableRange(self):
- """Returns the selected min/max range to apply to the data,
- according to the used scale.
-
- One or both limits can be None in case it is not displayable in the
- current axes scale.
-
- :returns: Tuple{float, float}
- """
- scale = self._plot.getXAxis().getScale()
-
- def isDisplayable(pos):
- if pos is None:
- return False
- if scale == Axis.LOGARITHMIC:
- return pos > 0.0
- return True
-
- posMin, posMax = self.getFiniteRange()
- if not isDisplayable(posMin):
- posMin = None
- if not isDisplayable(posMax):
- posMax = None
-
- return posMin, posMax
-
- def _initPlot(self):
- """Init the plot to display the range and the values"""
- self._plot = PlotWidget(self)
- self._plot.setDataMargins(0.125, 0.125, 0.125, 0.125)
- self._plot.getXAxis().setLabel("Data Values")
- self._plot.getYAxis().setLabel("")
- self._plot.setInteractiveMode('select', zoomOnWheel=False)
- self._plot.setActiveCurveHandling(False)
- self._plot.setMinimumSize(qt.QSize(250, 200))
- self._plot.sigPlotSignal.connect(self._plotEventReceived)
- palette = self.palette()
- color = palette.color(qt.QPalette.Normal, qt.QPalette.Window)
- self._plot.setBackgroundColor(color)
- self._plot.setDataBackgroundColor("white")
-
- lut = numpy.arange(256)
- lut.shape = 1, -1
- self._plot.addImage(lut, legend='lut')
- self._lutItem = self._plot._getItem("image", "lut")
- self._lutItem.setVisible(False)
-
- self._plot.addScatter(x=[], y=[], value=[], legend='lut2')
- self._lutItem2 = self._plot._getItem("scatter", "lut2")
- self._lutItem2.setVisible(False)
- self.__lutY = numpy.array([-0.05] * 256)
- self.__lutV = numpy.arange(256)
-
- self._bound = BoundingRect()
- self._plot.addItem(self._bound)
- self._bound.setVisible(True)
-
- # Add plot for histogram
- self._plotToolbar = qt.QToolBar(self)
- self._plotToolbar.setFloatable(False)
- self._plotToolbar.setMovable(False)
- self._plotToolbar.setIconSize(qt.QSize(8, 8))
- self._plotToolbar.setStyleSheet("QToolBar { border: 0px }")
- self._plotToolbar.setOrientation(qt.Qt.Vertical)
-
- group = qt.QActionGroup(self._plotToolbar)
- group.setExclusive(True)
-
- action = qt.QAction("Data range", self)
- action.setToolTip("Display the data range within the colormap range. A fast data processing have to be done.")
- action.setIcon(icons.getQIcon('colormap-range'))
- action.setCheckable(True)
- action.setData(_DataInPlotMode.RANGE)
- action.setChecked(action.data() == self._dataInPlotMode)
- self._plotToolbar.addAction(action)
- group.addAction(action)
- action = qt.QAction("Histogram", self)
- action.setToolTip("Display the data histogram within the colormap range. A slow data processing have to be done. ")
- action.setIcon(icons.getQIcon('colormap-histogram'))
- action.setCheckable(True)
- action.setData(_DataInPlotMode.HISTOGRAM)
- action.setChecked(action.data() == self._dataInPlotMode)
- self._plotToolbar.addAction(action)
- group.addAction(action)
- group.triggered.connect(self._displayDataInPlotModeChanged)
-
- plotBoxLayout = qt.QHBoxLayout()
- plotBoxLayout.setContentsMargins(0, 0, 0, 0)
- plotBoxLayout.setSpacing(2)
- plotBoxLayout.addWidget(self._plotToolbar)
- plotBoxLayout.addWidget(self._plot)
- plotBoxLayout.setSizeConstraint(qt.QLayout.SetMinimumSize)
- self.setLayout(plotBoxLayout)
-
- def _plotEventReceived(self, event):
- """Handle events from the plot"""
- kind = event['event']
-
- if kind == 'markerMoving':
- value = event['xdata']
- if event['label'] == 'Min':
- self._dragging = True, False
- self._finiteRange = value, self._finiteRange[1]
- self._last = value, None
- self.sigRangeMoving.emit(*self._last)
- elif event['label'] == 'Max':
- self._dragging = False, True
- self._finiteRange = self._finiteRange[0], value
- self._last = None, value
- self.sigRangeMoving.emit(*self._last)
- self._updateLutItem(self._finiteRange)
- elif kind == 'markerMoved':
- self.sigRangeMoved.emit(*self._last)
- self._plot.resetZoom()
- self._dragging = False, False
- else:
- pass
-
- def _updateMarkerPosition(self):
- colormap = self.getColormap()
- posMin, posMax = self._getDisplayableRange()
-
- if colormap is None:
- isDraggable = False
- else:
- isDraggable = colormap.isEditable()
-
- with utils.blockSignals(self):
- if posMin is not None and not self._dragging[0]:
- self._plot.addXMarker(
- posMin,
- legend='Min',
- text='Min',
- draggable=isDraggable,
- color="blue",
- constraint=self._plotMinMarkerConstraint)
- if posMax is not None and not self._dragging[1]:
- self._plot.addXMarker(
- posMax,
- legend='Max',
- text='Max',
- draggable=isDraggable,
- color="blue",
- constraint=self._plotMaxMarkerConstraint)
-
- self._updateLutItem((posMin, posMax))
- self._plot.resetZoom()
-
- def _updateLutItem(self, vRange):
- colormap = self.getColormap()
- if colormap is None:
- return
-
- if vRange is None:
- posMin, posMax = self._getDisplayableRange()
- else:
- posMin, posMax = vRange
- if posMin is None or posMax is None:
- self._lutItem.setVisible(False)
- pos = posMax if posMin is None else posMin
- if pos is not None:
- self._bound.setBounds((pos, pos, -0.1, 0))
- else:
- self._bound.setBounds((0, 0, -0.1, 0))
- else:
- norm = colormap.getNormalization()
- normColormap = colormap.copy()
- normColormap.setVRange(0, 255)
- normColormap.setNormalization(Colormap.LINEAR)
- if norm == Colormap.LINEAR:
- scale = (posMax - posMin) / 256
- self._lutItem.setColormap(normColormap)
- self._lutItem.setOrigin((posMin, -0.09))
- self._lutItem.setScale((scale, 0.08))
- self._lutItem.setVisible(True)
- self._lutItem2.setVisible(False)
- elif norm == Colormap.LOGARITHM:
- self._lutItem2.setVisible(False)
- self._lutItem2.setColormap(normColormap)
- xx = numpy.geomspace(posMin, posMax, 256)
- self._lutItem2.setData(x=xx,
- y=self.__lutY,
- value=self.__lutV,
- copy=False)
- self._lutItem2.setSymbol("|")
- self._lutItem2.setVisible(True)
- self._lutItem.setVisible(False)
- else:
- # Fallback: Display with linear axis and applied normalization
- self._lutItem2.setVisible(False)
- normColormap.setNormalization(norm)
- self._lutItem2.setColormap(normColormap)
- xx = numpy.linspace(posMin, posMax, 256, endpoint=True)
- self._lutItem2.setData(
- x=xx,
- y=self.__lutY,
- value=self.__lutV,
- copy=False)
- self._lutItem2.setSymbol("|")
- self._lutItem2.setVisible(True)
- self._lutItem.setVisible(False)
-
- self._bound.setBounds((posMin, posMax, -0.1, 1))
-
- def _plotMinMarkerConstraint(self, x, y):
- """Constraint of the min marker"""
- _vmin, vmax = self.getFiniteRange()
- if vmax is None:
- return x, y
- return min(x, vmax), y
-
- def _plotMaxMarkerConstraint(self, x, y):
- """Constraint of the max marker"""
- vmin, _vmax = self.getFiniteRange()
- if vmin is None:
- return x, y
- return max(x, vmin), y
-
- def _setDataInPlotMode(self, mode):
- if self._dataInPlotMode == mode:
- return
- self._dataInPlotMode = mode
- self._updateDataInPlot()
-
- def _displayDataInPlotModeChanged(self, action):
- mode = action.data()
- self._setDataInPlotMode(mode)
-
- def invalidateData(self):
- self._histogramData = {}
- self._dataRange = {}
- self._invalidated = True
- self.update()
-
- def _updateDataInPlot(self):
- mode = self._dataInPlotMode
-
- norm = self._getNorm()
- if norm == Colormap.LINEAR:
- scale = Axis.LINEAR
- elif norm == Colormap.LOGARITHM:
- scale = Axis.LOGARITHMIC
- else:
- scale = Axis.LINEAR
-
- axis = self._plot.getXAxis()
- axis.setScale(scale)
-
- if mode == _DataInPlotMode.RANGE:
- dataRange = self._getNormalizedDataRange()
- xmin, xmax = dataRange
- if xmax is None or xmin is None:
- self._plot.remove(legend='Data', kind='histogram')
- else:
- histogram = numpy.array([1])
- bin_edges = numpy.array([xmin, xmax])
- self._plot.addHistogram(histogram,
- bin_edges,
- legend="Data",
- color='gray',
- align='center',
- fill=True,
- z=1)
-
- elif mode == _DataInPlotMode.HISTOGRAM:
- histogram, bin_edges = self._getNormalizedHistogram()
- if histogram is None or bin_edges is None:
- self._plot.remove(legend='Data', kind='histogram')
- else:
- histogram = numpy.array(histogram, copy=True)
- bin_edges = numpy.array(bin_edges, copy=True)
- with numpy.errstate(invalid='ignore'):
- norm_histogram = histogram / numpy.nanmax(histogram)
- self._plot.addHistogram(norm_histogram,
- bin_edges,
- legend="Data",
- color='gray',
- align='center',
- fill=True,
- z=1)
- else:
- _logger.error("Mode unsupported")
-
- def sizeHint(self):
- return self.layout().minimumSize()
-
- def updateLut(self):
- self._updateLutItem(None)
-
- def _getNorm(self):
- colormap = self.getColormap()
- if colormap is None:
- return Axis.LINEAR
- else:
- norm = colormap.getNormalization()
- return norm
-
- def updateNormalization(self):
- self._updateDataInPlot()
- self.update()
-
-
-class ColormapDialog(qt.QDialog):
- """A QDialog widget to set the colormap.
-
- :param parent: See :class:`QDialog`
- :param str title: The QDialog title
- """
-
- visibleChanged = qt.Signal(bool)
- """This event is sent when the dialog visibility change"""
-
- def __init__(self, parent=None, title="Colormap Dialog"):
- qt.QDialog.__init__(self, parent)
- self.setWindowTitle(title)
-
- self.__aboutToDelete = False
- self._colormap = None
-
- self._data = None
- """Weak ref to an external numpy array
- """
- self._itemHolder = None
- """Hard ref to a private item (used as holder to the data)
- This allow to reuse the item cache
- """
- self._item = None
- """Weak ref to an external item"""
-
- self._colormapChange = utils.LockReentrant()
- """Used as a semaphore to avoid editing the colormap object when we are
- only attempt to display it.
- Used instead of n connect and disconnect of the sigChanged. The
- disconnection to sigChanged was also limiting when this colormapdialog
- is used in the colormapaction and associated to the activeImageChanged.
- (because the activeImageChanged is send when the colormap changed and
- the self.setcolormap is a callback)
- """
-
- self.__colormapInvalidated = False
- self.__dataInvalidated = False
-
- self._histogramData = None
-
- self._dataRange = None
- """If defined 3-tuple containing information from a data:
- minimum, positive minimum, maximum"""
-
- self._colormapStoredState = None
-
- # Colormap row
- self._comboBoxColormap = ColormapNameComboBox(parent=self)
- self._comboBoxColormap.currentIndexChanged[int].connect(self._comboBoxColormapUpdated)
-
- # Normalization row
- self._comboBoxNormalization = qt.QComboBox(parent=self)
- normalizations = [
- ('Linear', Colormap.LINEAR),
- ('Gamma correction', Colormap.GAMMA),
- ('Arcsinh', Colormap.ARCSINH),
- ('Logarithmic', Colormap.LOGARITHM),
- ('Square root', Colormap.SQRT)]
- for name, userData in normalizations:
- try:
- icon = icons.getQIcon("colormap-norm-%s" % userData)
- except:
- icon = qt.QIcon()
- self._comboBoxNormalization.addItem(icon, name, userData)
- self._comboBoxNormalization.currentIndexChanged[int].connect(
- self._normalizationUpdated)
-
- self._gammaSpinBox = qt.QDoubleSpinBox(parent=self)
- self._gammaSpinBox.setEnabled(False)
- self._gammaSpinBox.setRange(0., 1000.)
- self._gammaSpinBox.setDecimals(4)
- if hasattr(qt.QDoubleSpinBox, "setStepType"):
- # Introduced in Qt 5.12
- self._gammaSpinBox.setStepType(qt.QDoubleSpinBox.AdaptiveDecimalStepType)
- else:
- self._gammaSpinBox.setSingleStep(0.1)
- self._gammaSpinBox.valueChanged.connect(self._gammaUpdated)
- self._gammaSpinBox.setValue(2.)
-
- autoScaleCombo = _AutoscaleModeComboBox(self)
- autoScaleCombo.currentIndexChanged.connect(self._autoscaleModeUpdated)
- self._autoScaleCombo = autoScaleCombo
-
- # Min row
- self._minValue = _BoundaryWidget(parent=self, value=1.0)
- self._minValue.sigAutoScaleChanged.connect(self._minAutoscaleUpdated)
- self._minValue.sigValueChanged.connect(self._minValueUpdated)
-
- # Max row
- self._maxValue = _BoundaryWidget(parent=self, value=10.0)
- self._maxValue.sigAutoScaleChanged.connect(self._maxAutoscaleUpdated)
- self._maxValue.sigValueChanged.connect(self._maxValueUpdated)
-
- self._autoButtons = _AutoScaleButtons(self)
- self._autoButtons.autoRangeChanged.connect(self._autoRangeButtonsUpdated)
-
- rangeLayout = qt.QGridLayout()
- miniFont = qt.QFont(self.font())
- miniFont.setPixelSize(8)
- labelMin = qt.QLabel("Min", self)
- labelMin.setFont(miniFont)
- labelMin.setAlignment(qt.Qt.AlignHCenter)
- labelMax = qt.QLabel("Max", self)
- labelMax.setAlignment(qt.Qt.AlignHCenter)
- labelMax.setFont(miniFont)
- rangeLayout.addWidget(labelMin, 0, 0)
- rangeLayout.addWidget(labelMax, 0, 1)
- rangeLayout.addWidget(self._minValue, 1, 0)
- rangeLayout.addWidget(self._maxValue, 1, 1)
- rangeLayout.addWidget(self._autoButtons, 2, 0, 1, -1, qt.Qt.AlignCenter)
-
- self._histoWidget = _ColormapHistogram(self)
- self._histoWidget.sigRangeMoving.connect(self._histogramRangeMoving)
- self._histoWidget.sigRangeMoved.connect(self._histogramRangeMoved)
-
- # Scale to buttons
- self._visibleAreaButton = qt.QPushButton(self)
- self._visibleAreaButton.setEnabled(False)
- self._visibleAreaButton.setText("Visible Area")
- self._visibleAreaButton.clicked.connect(
- self._handleScaleToVisibleAreaClicked,
- type=qt.Qt.QueuedConnection)
-
- # Place-holder for selected area ROI manager
- self._roiForColormapManager = None
-
- self._selectedAreaButton = WaitingPushButton(self)
- self._selectedAreaButton.setEnabled(False)
- self._selectedAreaButton.setText("Selection")
- self._selectedAreaButton.setIcon(icons.getQIcon("add-shape-rectangle"))
- self._selectedAreaButton.setCheckable(True)
- self._selectedAreaButton.setDisabledWhenWaiting(False)
- self._selectedAreaButton.toggled.connect(
- self._handleScaleToSelectionToggled,
- type=qt.Qt.QueuedConnection)
-
- # define modal buttons
- types = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel
- self._buttonsModal = qt.QDialogButtonBox(parent=self)
- self._buttonsModal.setStandardButtons(types)
- self._buttonsModal.accepted.connect(self.accept)
- self._buttonsModal.rejected.connect(self.reject)
-
- # define non modal buttons
- types = qt.QDialogButtonBox.Close | qt.QDialogButtonBox.Reset
- self._buttonsNonModal = qt.QDialogButtonBox(parent=self)
- self._buttonsNonModal.setStandardButtons(types)
- button = self._buttonsNonModal.button(qt.QDialogButtonBox.Close)
- button.clicked.connect(self.accept)
- button.setDefault(True)
- button = self._buttonsNonModal.button(qt.QDialogButtonBox.Reset)
- button.clicked.connect(self.resetColormap)
-
- self._buttonsModal.setFocus(qt.Qt.OtherFocusReason)
- self._buttonsNonModal.setFocus(qt.Qt.OtherFocusReason)
-
- # Set the colormap to default values
- self.setColormap(Colormap(name='gray', normalization='linear',
- vmin=None, vmax=None))
-
- self.setModal(self.isModal())
-
- formLayout = qt.QFormLayout(self)
- formLayout.setContentsMargins(10, 10, 10, 10)
- formLayout.addRow('Colormap:', self._comboBoxColormap)
- formLayout.addRow('Normalization:', self._comboBoxNormalization)
- formLayout.addRow('Gamma:', self._gammaSpinBox)
- formLayout.addRow(self._histoWidget)
- formLayout.addRow(rangeLayout)
- label = qt.QLabel('Mode:', self)
- self._autoscaleModeLabel = label
- label.setToolTip("Mode for autoscale. Algorithm used to find range in auto scale.")
- formLayout.addItem(qt.QSpacerItem(1, 1, qt.QSizePolicy.Fixed, qt.QSizePolicy.Fixed))
- formLayout.addRow(label, autoScaleCombo)
-
- layout = qt.QHBoxLayout()
- layout.setContentsMargins(0, 0, 0, 0)
- layout.addWidget(self._visibleAreaButton)
- layout.addWidget(self._selectedAreaButton)
- self._scaleToAreaGroup = qt.QGroupBox('Scale to:', self)
- self._scaleToAreaGroup.setLayout(layout)
- self._scaleToAreaGroup.setVisible(False)
- formLayout.addRow(self._scaleToAreaGroup)
-
- formLayout.addRow(self._buttonsModal)
- formLayout.addRow(self._buttonsNonModal)
- formLayout.setSizeConstraint(qt.QLayout.SetMinimumSize)
-
- self.setTabOrder(self._comboBoxColormap, self._comboBoxNormalization)
- self.setTabOrder(self._comboBoxNormalization, self._gammaSpinBox)
- self.setTabOrder(self._gammaSpinBox, self._minValue)
- self.setTabOrder(self._minValue, self._maxValue)
- self.setTabOrder(self._maxValue, self._autoButtons)
- self.setTabOrder(self._autoButtons, self._autoScaleCombo)
- self.setTabOrder(self._autoScaleCombo, self._visibleAreaButton)
- self.setTabOrder(self._visibleAreaButton, self._selectedAreaButton)
- self.setTabOrder(self._selectedAreaButton, self._buttonsModal)
- self.setTabOrder(self._buttonsModal, self._buttonsNonModal)
-
- self.setFixedSize(self.sizeHint())
- self._applyColormap()
-
- def _invalidateColormap(self):
- if self.isVisible():
- self._applyColormap()
- else:
- self.__colormapInvalidated = True
-
- def _invalidateData(self):
- if self.isVisible():
- self._updateWidgetRange()
- self._histoWidget.invalidateData()
- else:
- self.__dataInvalidated = True
-
- def _validate(self):
- if self.__colormapInvalidated:
- self._applyColormap()
- if self.__dataInvalidated:
- self._histoWidget.invalidateData()
- if self.__dataInvalidated or self.__colormapInvalidated:
- self._updateWidgetRange()
- self.__dataInvalidated = False
- self.__colormapInvalidated = False
-
- def showEvent(self, event):
- self.visibleChanged.emit(True)
- super(ColormapDialog, self).showEvent(event)
- if self.isVisible():
- self._validate()
-
- def closeEvent(self, event):
- if not self.isModal():
- self.accept()
- super(ColormapDialog, self).closeEvent(event)
-
- def hideEvent(self, event):
- self.visibleChanged.emit(False)
- super(ColormapDialog, self).hideEvent(event)
-
- def close(self):
- self.accept()
- qt.QDialog.close(self)
-
- def setModal(self, modal):
- assert type(modal) is bool
- self._buttonsNonModal.setVisible(not modal)
- self._buttonsModal.setVisible(modal)
- qt.QDialog.setModal(self, modal)
-
- def event(self, event):
- if event.type() == qt.QEvent.DeferredDelete:
- self.__aboutToDelete = True
- return super(ColormapDialog, self).event(event)
-
- def exec_(self):
- wasModal = self.isModal()
- self.setModal(True)
- result = super(ColormapDialog, self).exec_()
- if not self.__aboutToDelete:
- self.setModal(wasModal)
- return result
-
- def _getFiniteColormapRange(self):
- """Return a colormap range where auto ranges are fixed
- according to the available data.
- """
- colormap = self.getColormap()
- if colormap is None:
- return 1, 10
-
- item = self._getItem()
- if item is not None:
- return colormap.getColormapRange(item)
- # If there is not item, there is no data
- return colormap.getColormapRange(None)
-
- @staticmethod
- def computeDataRange(data):
- """Compute the data range as used by :meth:`setDataRange`.
-
- :param data: The data to process
- :rtype: List[Union[None,float]]
- """
- if data is None or len(data) == 0:
- return None, None, None
-
- dataRange = min_max(data, min_positive=True, finite=True)
- if dataRange.minimum is None:
- # Only non-finite data
- dataRange = None
-
- if dataRange is not None:
- dataRange = dataRange.minimum, dataRange.min_positive, dataRange.maximum
-
- if dataRange is None or len(dataRange) != 3:
- qt.QMessageBox.warning(
- None, "No Data",
- "Image data does not contain any real value")
- dataRange = 1., 1., 10.
-
- return dataRange
-
- @staticmethod
- def computeHistogram(data, scale=Axis.LINEAR, dataRange=None):
- """Compute the data histogram as used by :meth:`setHistogram`.
-
- :param data: The data to process
- :param dataRange: Optional range to compute the histogram, which is a
- tuple of min, max
- :rtype: Tuple(List(float),List(float)
- """
- # For compatibility
- if scale == Axis.LOGARITHMIC:
- scale = Colormap.LOGARITHM
-
- if data is None:
- return None, None
-
- if len(data) == 0:
- return None, None
-
- if data.ndim == 3: # RGB(A) images
- _logger.info('Converting current image from RGB(A) to grayscale\
- in order to compute the intensity distribution')
- data = (data[:,:, 0] * 0.299 +
- data[:,:, 1] * 0.587 +
- data[:,:, 2] * 0.114)
-
- # bad hack: get 256 continuous bins in the case we have a B&W
- normalizeData = True
- if numpy.issubdtype(data.dtype, numpy.ubyte):
- normalizeData = False
- elif numpy.issubdtype(data.dtype, numpy.integer):
- if dataRange is not None:
- xmin, xmax = dataRange
- if xmin is not None and xmax is not None:
- normalizeData = (xmax - xmin) > 255
-
- if normalizeData:
- if scale == Colormap.LOGARITHM:
- with numpy.errstate(divide='ignore', invalid='ignore'):
- data = numpy.log10(data)
-
- if dataRange is not None:
- xmin, xmax = dataRange
- if xmin is None:
- return None, None
- if normalizeData:
- if scale == Colormap.LOGARITHM:
- xmin, xmax = numpy.log10(xmin), numpy.log10(xmax)
- else:
- xmin, xmax = min_max(data, min_positive=False, finite=True)
-
- if xmin is None:
- return None, None
-
- nbins = min(256, int(numpy.sqrt(data.size)))
- data_range = xmin, xmax
-
- # bad hack: get 256 bins in the case we have a B&W
- if numpy.issubdtype(data.dtype, numpy.integer):
- if nbins > xmax - xmin:
- nbins = int(xmax - xmin)
-
- nbins = max(2, nbins)
- data = data.ravel().astype(numpy.float32)
-
- histogram = Histogramnd(data, n_bins=nbins, histo_range=data_range)
- bins = histogram.edges[0]
- if normalizeData:
- if scale == Colormap.LOGARITHM:
- bins = 10 ** bins
- return histogram.histo, bins
-
- def _getItem(self):
- if self._itemHolder is not None:
- return self._itemHolder
- if self._item is None:
- return None
- return self._item()
-
- def setItem(self, item):
- """Store the plot item.
-
- According to the state of the dialog, the item will be used to display
- the data range or the histogram of the data using :meth:`setDataRange`
- and :meth:`setHistogram`
- """
- # While event from items are not supported, we can't ignore dup items
- # old = self._getItem()
- # if old is item:
- # return
- self._data = None
- self._itemHolder = None
- try:
- if item is None:
- self._item = None
- else:
- if not isinstance(item, items.ColormapMixIn):
- self._item = None
- raise ValueError("Item %s is not supported" % item)
- self._item = weakref.ref(item, self._itemAboutToFinalize)
- finally:
- self._syncScaleToButtonsEnabled()
- self._dataRange = None
- self._histogramData = None
- self._invalidateData()
-
- def _getData(self):
- if self._data is None:
- return None
- return self._data()
-
- def setData(self, data):
- """Store the data
-
- According to the state of the dialog, the data will be used to display
- the data range or the histogram of the data using :meth:`setDataRange`
- and :meth:`setHistogram`
- """
- oldData = self._getData()
- if oldData is data:
- return
-
- self._item = None
- self._syncScaleToButtonsEnabled()
- if data is None:
- self._data = None
- self._itemHolder = None
- else:
- self._data = weakref.ref(data, self._dataAboutToFinalize)
- self._itemHolder = _DataRefHolder(self._data)
-
- self._dataRange = None
- self._histogramData = None
-
- self._invalidateData()
-
- def _getArray(self):
- data = self._getData()
- if data is not None:
- return data
- item = self._getItem()
- if item is not None:
- return item.getColormappedData(copy=False)
- return None
-
- def _colormapAboutToFinalize(self, weakrefColormap):
- """Callback when the data weakref is about to be finalized."""
- if self._colormap is weakrefColormap and qtinspect.isValid(self):
- self.setColormap(None)
-
- def _dataAboutToFinalize(self, weakrefData):
- """Callback when the data weakref is about to be finalized."""
- if self._data is weakrefData and qtinspect.isValid(self):
- self.setData(None)
-
- def _itemAboutToFinalize(self, weakref):
- """Callback when the data weakref is about to be finalized."""
- if self._item is weakref and qtinspect.isValid(self):
- self.setItem(None)
-
- @deprecation.deprecated(reason="It is private data", since_version="0.13")
- def getHistogram(self):
- histo = self._getHistogram()
- if histo is None:
- return None
- counts, bin_edges = histo
- return numpy.array(counts, copy=True), numpy.array(bin_edges, copy=True)
-
- def _getHistogram(self):
- """Returns the histogram defined by the dialog as metadata
- to describe the data in order to speed up the dialog.
-
- :return: (hist, bin_edges)
- :rtype: 2-tuple of numpy arrays"""
- return self._histogramData
-
- def setHistogram(self, hist=None, bin_edges=None):
- """Set the histogram to display.
-
- This update the data range with the bounds of the bins.
-
- :param hist: array-like of counts or None to hide histogram
- :param bin_edges: array-like of bins edges or None to hide histogram
- """
- if hist is None or bin_edges is None:
- self._histogramData = None
- else:
- self._histogramData = numpy.array(hist), numpy.array(bin_edges)
-
- self._invalidateData()
-
- def getColormap(self):
- """Return the colormap description.
-
- :rtype: ~silx.gui.colors.Colormap
- """
- if self._colormap is None:
- return None
- return self._colormap()
-
- def resetColormap(self):
- """
- Reset the colormap state before modification.
-
- ..note :: the colormap reference state is the state when set or the
- state when validated
- """
- colormap = self.getColormap()
- if colormap is not None and self._colormapStoredState is not None:
- if colormap != self._colormapStoredState:
- with self._colormapChange:
- colormap.setFromColormap(self._colormapStoredState)
- self._applyColormap()
-
- def _getDataRange(self):
- """Returns the data range defined by the dialog as metadata
- to describe the data in order to speed up the dialog.
-
- :return: (minimum, positiveMin, maximum)
- :rtype: 3-tuple of floats or None"""
- return self._dataRange
-
- def setDataRange(self, minimum=None, positiveMin=None, maximum=None):
- """Set the range of data to use for the range of the histogram area.
-
- :param float minimum: The minimum of the data
- :param float positiveMin: The positive minimum of the data
- :param float maximum: The maximum of the data
- """
- self._dataRange = minimum, positiveMin, maximum
- self._invalidateData()
-
- def _setColormapRange(self, xmin, xmax):
- """Set a new range to the held colormap and update the
- widget."""
- colormap = self.getColormap()
- if colormap is not None:
- with self._colormapChange:
- colormap.setVRange(xmin, xmax)
- self._updateWidgetRange()
-
- def setColormapRangeFromDataBounds(self, bounds):
- """Set the range of the colormap from current item and rect.
-
- If there is no ColormapMixIn item attached to the ColormapDialog,
- nothing is done.
-
- :param Union[List[float],None] bounds:
- (xmin, xmax, ymin, ymax) Rectangular region in data space
- """
- if bounds is None:
- return None # no-op
-
- colormap = self.getColormap()
- if colormap is None:
- return # no-op
-
- item = self._getItem()
- if not isinstance(item, items.ColormapMixIn):
- return None # no-op
-
- data = item.getColormappedData(copy=False)
-
- xmin, xmax, ymin, ymax = bounds
-
- if isinstance(item, items.ImageBase):
- ox, oy = item.getOrigin()
- sx, sy = item.getScale()
-
- ystart = max(0, int((ymin - oy) / sy))
- ystop = max(0, int(numpy.ceil((ymax - oy) / sy)))
- xstart = max(0, int((xmin - ox) / sx))
- xstop = max(0, int(numpy.ceil((xmax - ox) / sx)))
-
- subset = data[ystart:ystop, xstart:xstop]
-
- elif isinstance(item, items.Scatter):
- x = item.getXData(copy=False)
- y = item.getYData(copy=False)
- subset = data[
- numpy.logical_and(
- numpy.logical_and(xmin <= x, x <= xmax),
- numpy.logical_and(ymin <= y, y <= ymax))]
-
- if subset.size == 0:
- return # no-op
-
- vmin, vmax = colormap._computeAutoscaleRange(subset)
- self._setColormapRange(vmin, vmax)
-
- def _updateWidgetRange(self):
- """Update the colormap range displayed into the widget."""
- xmin, xmax = self._getFiniteColormapRange()
- colormap = self.getColormap()
- if colormap is not None:
- vRange = colormap.getVRange()
- autoMin, autoMax = (r is None for r in vRange)
- else:
- autoMin, autoMax = False, False
-
- with utils.blockSignals(self._minValue):
- self._minValue.setValue(xmin, autoMin)
- with utils.blockSignals(self._maxValue):
- self._maxValue.setValue(xmax, autoMax)
- with utils.blockSignals(self._histoWidget):
- self._histoWidget.setFiniteRange((xmin, xmax))
- with utils.blockSignals(self._autoButtons):
- self._autoButtons.setAutoRange((autoMin, autoMax))
- self._autoscaleModeLabel.setEnabled(autoMin or autoMax)
-
- def accept(self):
- self.storeCurrentState()
- qt.QDialog.accept(self)
-
- def storeCurrentState(self):
- """
- save the current value sof the colormap if the user want to undo is
- modifications
- """
- colormap = self.getColormap()
- if colormap is not None:
- self._colormapStoredState = colormap.copy()
- else:
- self._colormapStoredState = None
-
- def reject(self):
- self.resetColormap()
- qt.QDialog.reject(self)
-
- def setColormap(self, colormap):
- """Set the colormap description
-
- :param ~silx.gui.colors.Colormap colormap: the colormap to edit
- """
- assert colormap is None or isinstance(colormap, Colormap)
- if self._colormapChange.locked():
- return
-
- oldColormap = self.getColormap()
- if oldColormap is colormap:
- return
- if oldColormap is not None:
- oldColormap.sigChanged.disconnect(self._applyColormap)
-
- if colormap is not None:
- colormap.sigChanged.connect(self._applyColormap)
- colormap = weakref.ref(colormap, self._colormapAboutToFinalize)
-
- self._colormap = colormap
- self.storeCurrentState()
- self._invalidateColormap()
-
- def _updateResetButton(self):
- resetButton = self._buttonsNonModal.button(qt.QDialogButtonBox.Reset)
- rStateEnabled = False
- colormap = self.getColormap()
- if colormap is not None and colormap.isEditable():
- # can reset only in the case the colormap changed
- rStateEnabled = colormap != self._colormapStoredState
- resetButton.setEnabled(rStateEnabled)
-
- def _applyColormap(self):
- self._updateResetButton()
- if self._colormapChange.locked():
- return
-
- self._syncScaleToButtonsEnabled()
-
- colormap = self.getColormap()
- if colormap is None:
- self._comboBoxColormap.setEnabled(False)
- self._comboBoxNormalization.setEnabled(False)
- self._gammaSpinBox.setEnabled(False)
- self._autoScaleCombo.setEnabled(False)
- self._minValue.setEnabled(False)
- self._maxValue.setEnabled(False)
- self._autoButtons.setEnabled(False)
- self._autoscaleModeLabel.setEnabled(False)
- self._histoWidget.setVisible(False)
- self._histoWidget.setFiniteRange((None, None))
- else:
- assert colormap.getNormalization() in Colormap.NORMALIZATIONS
- with utils.blockSignals(self._comboBoxColormap):
- self._comboBoxColormap.setCurrentLut(colormap)
- self._comboBoxColormap.setEnabled(colormap.isEditable())
- with utils.blockSignals(self._comboBoxNormalization):
- index = self._comboBoxNormalization.findData(
- colormap.getNormalization())
- if index < 0:
- _logger.error('Unsupported normalization: %s' %
- colormap.getNormalization())
- else:
- self._comboBoxNormalization.setCurrentIndex(index)
- self._comboBoxNormalization.setEnabled(colormap.isEditable())
- with utils.blockSignals(self._gammaSpinBox):
- self._gammaSpinBox.setValue(
- colormap.getGammaNormalizationParameter())
- self._gammaSpinBox.setEnabled(
- colormap.getNormalization() == 'gamma' and
- colormap.isEditable())
- with utils.blockSignals(self._autoScaleCombo):
- self._autoScaleCombo.setCurrentMode(colormap.getAutoscaleMode())
- self._autoScaleCombo.setEnabled(colormap.isEditable())
- with utils.blockSignals(self._autoButtons):
- self._autoButtons.setEnabled(colormap.isEditable())
- self._autoButtons.setAutoRangeFromColormap(colormap)
-
- vmin, vmax = colormap.getVRange()
- if vmin is None or vmax is None:
- # Compute it only if needed
- dataRange = self._getFiniteColormapRange()
- else:
- dataRange = vmin, vmax
-
- with utils.blockSignals(self._minValue):
- self._minValue.setValue(vmin or dataRange[0], isAuto=vmin is None)
- self._minValue.setEnabled(colormap.isEditable())
- with utils.blockSignals(self._maxValue):
- self._maxValue.setValue(vmax or dataRange[1], isAuto=vmax is None)
- self._maxValue.setEnabled(colormap.isEditable())
- self._autoscaleModeLabel.setEnabled(vmin is None or vmax is None)
-
- with utils.blockSignals(self._histoWidget):
- self._histoWidget.setVisible(True)
- self._histoWidget.setFiniteRange(dataRange)
- self._histoWidget.updateNormalization()
-
- def _comboBoxColormapUpdated(self):
- """Callback executed when the combo box with the colormap LUT
- is updated by user input.
- """
- colormap = self.getColormap()
- if colormap is not None:
- with self._colormapChange:
- name = self._comboBoxColormap.getCurrentName()
- if name is not None:
- colormap.setName(name)
- else:
- lut = self._comboBoxColormap.getCurrentColors()
- colormap.setColormapLUT(lut)
- self._histoWidget.updateLut()
-
- def _autoRangeButtonsUpdated(self, autoRange):
- """Callback executed when the autoscale buttons widget
- is updated by user input.
- """
- dataRange = self._getFiniteColormapRange()
-
- # Final colormap range
- vmin = (dataRange[0] if not autoRange[0] else None)
- vmax = (dataRange[1] if not autoRange[1] else None)
-
- with self._colormapChange:
- colormap = self.getColormap()
- colormap.setVRange(vmin, vmax)
-
- with utils.blockSignals(self._minValue):
- self._minValue.setValue(vmin or dataRange[0], isAuto=vmin is None)
- with utils.blockSignals(self._maxValue):
- self._maxValue.setValue(vmax or dataRange[1], isAuto=vmax is None)
-
- self._updateWidgetRange()
-
- def _normalizationUpdated(self, index):
- """Callback executed when the normalization widget
- is updated by user input.
- """
- colormap = self.getColormap()
- if colormap is not None:
- normalization = self._comboBoxNormalization.itemData(index)
- self._gammaSpinBox.setEnabled(normalization == 'gamma')
-
- with self._colormapChange:
- colormap.setNormalization(normalization)
- self._histoWidget.updateNormalization()
-
- self._updateWidgetRange()
-
- def _gammaUpdated(self, value):
- """Callback used to update the gamma normalization parameter"""
- colormap = self.getColormap()
- if colormap is not None:
- colormap.setGammaNormalizationParameter(value)
-
- def _autoscaleModeUpdated(self):
- """Callback executed when the autoscale mode widget
- is updated by user input.
- """
- mode = self._autoScaleCombo.currentMode()
-
- colormap = self.getColormap()
- if colormap is not None:
- with self._colormapChange:
- colormap.setAutoscaleMode(mode)
-
- self._updateWidgetRange()
-
- def _minAutoscaleUpdated(self, autoEnabled):
- """Callback executed when the min autoscale from
- the lineedit is updated by user input"""
- colormap = self.getColormap()
- xmin, xmax = colormap.getVRange()
- if autoEnabled:
- xmin = None
- else:
- xmin, _xmax = self._getFiniteColormapRange()
- self._setColormapRange(xmin, xmax)
-
- def _maxAutoscaleUpdated(self, autoEnabled):
- """Callback executed when the max autoscale from
- the lineedit is updated by user input"""
- colormap = self.getColormap()
- xmin, xmax = colormap.getVRange()
- if autoEnabled:
- xmax = None
- else:
- _xmin, xmax = self._getFiniteColormapRange()
- self._setColormapRange(xmin, xmax)
-
- def _minValueUpdated(self, value):
- """Callback executed when the lineedit min value is
- updated by user input"""
- xmin = value
- xmax = self._maxValue.getValue()
- if xmax is not None and xmin > xmax:
- # FIXME: This should be done in the widget itself
- xmin = xmax
- with utils.blockSignals(self._minValue):
- self._minValue.setValue(xmin)
- self._setColormapRange(xmin, xmax)
-
- def _maxValueUpdated(self, value):
- """Callback executed when the lineedit max value is
- updated by user input"""
- xmin = self._minValue.getValue()
- xmax = value
- if xmin is not None and xmin > xmax:
- # FIXME: This should be done in the widget itself
- xmax = xmin
- with utils.blockSignals(self._maxValue):
- self._maxValue.setValue(xmax)
- self._setColormapRange(xmin, xmax)
-
- def _histogramRangeMoving(self, vmin, vmax):
- """Callback executed when for colormap range displayed in
- the histogram widget is moving.
-
- :param vmin: Update of the minimum range, else None
- :param vmax: Update of the maximum range, else None
- """
- colormap = self.getColormap()
- if vmin is not None:
- with self._colormapChange:
- colormap.setVMin(vmin)
- self._minValue.setValue(vmin)
- if vmax is not None:
- with self._colormapChange:
- colormap.setVMax(vmax)
- self._maxValue.setValue(vmax)
-
- def _histogramRangeMoved(self, vmin, vmax):
- """Callback executed when for colormap range displayed in
- the histogram widget has finished to move
- """
- xmin = self._minValue.getValue()
- xmax = self._maxValue.getValue()
- if vmin is None:
- vmin = xmin
- if vmax is None:
- vmax = xmax
- self._setColormapRange(vmin, vmax)
-
- def _syncScaleToButtonsEnabled(self):
- """Set the state of scale to buttons according to current item and colormap"""
- colormap = self.getColormap()
- enabled = self._item is not None and colormap is not None and colormap.isEditable()
- self._scaleToAreaGroup.setVisible(enabled)
- self._visibleAreaButton.setEnabled(enabled)
- if not enabled:
- self._selectedAreaButton.setChecked(False)
- self._selectedAreaButton.setEnabled(enabled)
-
- def _handleScaleToVisibleAreaClicked(self):
- """Set colormap range from current item's visible area"""
- item = self._getItem()
- if item is None:
- return # no-op
-
- bounds = item.getVisibleBounds()
- if bounds is None:
- return # no-op
-
- self.setColormapRangeFromDataBounds(bounds)
-
- def _handleScaleToSelectionToggled(self, checked=False):
- """Handle toggle of scale to selected are button"""
- # Reset any previous ROI manager
- if self._roiForColormapManager is not None:
- self._roiForColormapManager.clear()
- self._roiForColormapManager.stop()
- self._roiForColormapManager = None
-
- if not checked: # Reset button status
- self._selectedAreaButton.setWaiting(False)
- self._selectedAreaButton.setText("Selection")
- return
-
- item = self._getItem()
- if item is None:
- self._selectedAreaButton.setChecked(False)
- return # no-op
-
- plotWidget = item.getPlot()
- if plotWidget is None:
- self._selectedAreaButton.setChecked(False)
- return # no-op
-
- self._selectedAreaButton.setWaiting(True)
- self._selectedAreaButton.setText("Draw Area...")
-
- self._roiForColormapManager = RegionOfInterestManager(parent=plotWidget)
- cmap = self.getColormap()
- self._roiForColormapManager.setColor(
- 'black' if cmap is None else cursorColorForColormap(cmap.getName()))
- self._roiForColormapManager.sigInteractiveModeFinished.connect(
- self.__roiInteractiveModeFinished)
- self._roiForColormapManager.sigInteractiveRoiFinalized.connect(self.__roiFinalized)
- self._roiForColormapManager.start(RectangleROI)
-
- def __roiInteractiveModeFinished(self):
- self._selectedAreaButton.setChecked(False)
-
- def __roiFinalized(self, roi):
- self._selectedAreaButton.setChecked(False)
- if roi is not None:
- ox, oy = roi.getOrigin()
- width, height = roi.getSize()
- self.setColormapRangeFromDataBounds((ox, ox+width, oy, oy+height))
-
- def keyPressEvent(self, event):
- """Override key handling.
-
- It disables leaving the dialog when editing a text field.
-
- But several press of Return key can be use to validate and close the
- dialog.
- """
- if event.key() in (qt.Qt.Key_Enter, qt.Qt.Key_Return):
- # Bypass QDialog keyPressEvent
- # To avoid leaving the dialog when pressing enter on a text field
- if self._minValue.hasFocus():
- nextFocus = self._maxValue
- elif self._maxValue.hasFocus():
- if self.isModal():
- nextFocus = self._buttonsModal.button(qt.QDialogButtonBox.Apply)
- else:
- nextFocus = self._buttonsNonModal.button(qt.QDialogButtonBox.Close)
- else:
- nextFocus = None
- if nextFocus is not None:
- nextFocus.setFocus(qt.Qt.OtherFocusReason)
- else:
- super(ColormapDialog, self).keyPressEvent(event)