diff options
Diffstat (limited to 'silx/gui/plot/ComplexImageView.py')
-rw-r--r-- | silx/gui/plot/ComplexImageView.py | 518 |
1 files changed, 0 insertions, 518 deletions
diff --git a/silx/gui/plot/ComplexImageView.py b/silx/gui/plot/ComplexImageView.py deleted file mode 100644 index dc6bf63..0000000 --- a/silx/gui/plot/ComplexImageView.py +++ /dev/null @@ -1,518 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2017-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. -# -# ###########################################################################*/ -"""This module provides a widget to view 2D complex data. - -The :class:`ComplexImageView` widget is dedicated to visualize a single 2D dataset -of complex data. -""" - -from __future__ import absolute_import - -__authors__ = ["Vincent Favre-Nicolin", "T. Vincent"] -__license__ = "MIT" -__date__ = "24/04/2018" - - -import logging -import collections -import numpy - -from ...utils.deprecation import deprecated -from .. import qt, icons -from .PlotWindow import Plot2D -from . import items -from .items import ImageComplexData -from silx.gui.widgets.FloatEdit import FloatEdit - -_logger = logging.getLogger(__name__) - - -# Widgets - -class _AmplitudeRangeDialog(qt.QDialog): - """QDialog asking for the amplitude range to display.""" - - sigRangeChanged = qt.Signal(tuple) - """Signal emitted when the range has changed. - - It provides the new range as a 2-tuple: (max, delta) - """ - - def __init__(self, - parent=None, - amplitudeRange=None, - displayedRange=(None, 2)): - super(_AmplitudeRangeDialog, self).__init__(parent) - self.setWindowTitle('Set Displayed Amplitude Range') - - if amplitudeRange is not None: - amplitudeRange = min(amplitudeRange), max(amplitudeRange) - self._amplitudeRange = amplitudeRange - self._defaultDisplayedRange = displayedRange - - layout = qt.QFormLayout() - self.setLayout(layout) - - if self._amplitudeRange is not None: - min_, max_ = self._amplitudeRange - layout.addRow( - qt.QLabel('Data Amplitude Range: [%g, %g]' % (min_, max_))) - - self._maxLineEdit = FloatEdit(parent=self) - self._maxLineEdit.validator().setBottom(0.) - self._maxLineEdit.setAlignment(qt.Qt.AlignRight) - - self._maxLineEdit.editingFinished.connect(self._rangeUpdated) - layout.addRow('Displayed Max.:', self._maxLineEdit) - - self._autoscale = qt.QCheckBox('autoscale') - self._autoscale.toggled.connect(self._autoscaleCheckBoxToggled) - layout.addRow('', self._autoscale) - - self._deltaLineEdit = FloatEdit(parent=self) - self._deltaLineEdit.validator().setBottom(1.) - self._deltaLineEdit.setAlignment(qt.Qt.AlignRight) - self._deltaLineEdit.editingFinished.connect(self._rangeUpdated) - layout.addRow('Displayed delta (log10 unit):', self._deltaLineEdit) - - buttons = qt.QDialogButtonBox(self) - buttons.addButton(qt.QDialogButtonBox.Ok) - buttons.addButton(qt.QDialogButtonBox.Cancel) - buttons.accepted.connect(self.accept) - buttons.rejected.connect(self.reject) - layout.addRow(buttons) - - # Set dialog from default values - self._resetDialogToDefault() - - self.rejected.connect(self._handleRejected) - - def _resetDialogToDefault(self): - """Set Widgets of the dialog from range information - """ - max_, delta = self._defaultDisplayedRange - - if max_ is not None: # Not in autoscale - displayedMax = max_ - elif self._amplitudeRange is not None: # Autoscale with data - displayedMax = self._amplitudeRange[1] - else: # Autoscale without data - displayedMax = '' - if displayedMax == "": - self._maxLineEdit.setText("") - else: - self._maxLineEdit.setValue(displayedMax) - self._maxLineEdit.setEnabled(max_ is not None) - - self._deltaLineEdit.setValue(delta) - - self._autoscale.setChecked(self._defaultDisplayedRange[0] is None) - - def getRangeInfo(self): - """Returns the current range as a 2-tuple (max, delta (in log10))""" - if self._autoscale.isChecked(): - max_ = None - else: - maxStr = self._maxLineEdit.text() - max_ = self._maxLineEdit.value() if maxStr else None - return max_, self._deltaLineEdit.value() if self._deltaLineEdit.text() else 2 - - def _handleRejected(self): - """Reset range info to default when rejected""" - self._resetDialogToDefault() - self._rangeUpdated() - - def _rangeUpdated(self): - """Handle QLineEdit editing finised""" - self.sigRangeChanged.emit(self.getRangeInfo()) - - def _autoscaleCheckBoxToggled(self, checked): - """Handle autoscale checkbox state changes""" - if checked: # Use default values - if self._amplitudeRange is None: - max_ = '' - else: - max_ = self._amplitudeRange[1] - if max_ == "": - self._maxLineEdit.setText("") - else: - self._maxLineEdit.setValue(max_) - self._maxLineEdit.setEnabled(not checked) - self._rangeUpdated() - - -class _ComplexDataToolButton(qt.QToolButton): - """QToolButton providing choices of complex data visualization modes - - :param parent: See :class:`QToolButton` - :param plot: The :class:`ComplexImageView` to control - """ - - _MODES = collections.OrderedDict([ - (ImageComplexData.ComplexMode.ABSOLUTE, ('math-amplitude', 'Amplitude')), - (ImageComplexData.ComplexMode.SQUARE_AMPLITUDE, - ('math-square-amplitude', 'Square amplitude')), - (ImageComplexData.ComplexMode.PHASE, ('math-phase', 'Phase')), - (ImageComplexData.ComplexMode.REAL, ('math-real', 'Real part')), - (ImageComplexData.ComplexMode.IMAGINARY, - ('math-imaginary', 'Imaginary part')), - (ImageComplexData.ComplexMode.AMPLITUDE_PHASE, - ('math-phase-color', 'Amplitude and Phase')), - (ImageComplexData.ComplexMode.LOG10_AMPLITUDE_PHASE, - ('math-phase-color-log', 'Log10(Amp.) and Phase')) - ]) - - _RANGE_DIALOG_TEXT = 'Set Amplitude Range...' - - def __init__(self, parent=None, plot=None): - super(_ComplexDataToolButton, self).__init__(parent=parent) - - assert plot is not None - self._plot2DComplex = plot - - menu = qt.QMenu(self) - menu.triggered.connect(self._triggered) - self.setMenu(menu) - - for mode, info in self._MODES.items(): - icon, text = info - action = qt.QAction(icons.getQIcon(icon), text, self) - action.setData(mode) - action.setIconVisibleInMenu(True) - menu.addAction(action) - - self._rangeDialogAction = qt.QAction(self) - self._rangeDialogAction.setText(self._RANGE_DIALOG_TEXT) - menu.addAction(self._rangeDialogAction) - - self.setPopupMode(qt.QToolButton.InstantPopup) - - self._modeChanged(self._plot2DComplex.getComplexMode()) - self._plot2DComplex.sigVisualizationModeChanged.connect( - self._modeChanged) - - def _modeChanged(self, mode): - """Handle change of visualization modes""" - icon, text = self._MODES[mode] - self.setIcon(icons.getQIcon(icon)) - self.setToolTip('Display the ' + text.lower()) - self._rangeDialogAction.setEnabled( - mode == ImageComplexData.ComplexMode.LOG10_AMPLITUDE_PHASE) - - def _triggered(self, action): - """Handle triggering of menu actions""" - actionText = action.text() - - if actionText == self._RANGE_DIALOG_TEXT: # Show dialog - # Get amplitude range - data = self._plot2DComplex.getData(copy=False) - - if data.size > 0: - absolute = numpy.absolute(data) - dataRange = (numpy.nanmin(absolute), numpy.nanmax(absolute)) - else: - dataRange = None - - # Show dialog - dialog = _AmplitudeRangeDialog( - parent=self, - amplitudeRange=dataRange, - displayedRange=self._plot2DComplex._getAmplitudeRangeInfo()) - dialog.sigRangeChanged.connect(self._rangeChanged) - dialog.exec_() - dialog.sigRangeChanged.disconnect(self._rangeChanged) - - else: # update mode - mode = action.data() - if isinstance(mode, ImageComplexData.ComplexMode): - self._plot2DComplex.setComplexMode(mode) - - def _rangeChanged(self, range_): - """Handle updates of range in the dialog""" - self._plot2DComplex._setAmplitudeRangeInfo(*range_) - - -class ComplexImageView(qt.QWidget): - """Display an image of complex data and allow to choose the visualization. - - :param parent: See :class:`QMainWindow` - """ - - ComplexMode = ImageComplexData.ComplexMode - """Complex Modes enumeration""" - - sigDataChanged = qt.Signal() - """Signal emitted when data has changed.""" - - sigVisualizationModeChanged = qt.Signal(object) - """Signal emitted when the visualization mode has changed. - - It provides the new visualization mode. - """ - - def __init__(self, parent=None): - super(ComplexImageView, self).__init__(parent) - if parent is None: - self.setWindowTitle('ComplexImageView') - - self._plot2D = Plot2D(self) - - layout = qt.QHBoxLayout(self) - layout.setSpacing(0) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self._plot2D) - self.setLayout(layout) - - # Create and add image to the plot - self._plotImage = ImageComplexData() - self._plotImage.setName('__ComplexImageView__complex_image__') - self._plotImage.sigItemChanged.connect(self._itemChanged) - self._plot2D.addItem(self._plotImage) - self._plot2D.setActiveImage(self._plotImage.getName()) - - toolBar = qt.QToolBar('Complex', self) - toolBar.addWidget( - _ComplexDataToolButton(parent=self, plot=self)) - - self._plot2D.insertToolBar(self._plot2D.getProfileToolbar(), toolBar) - - def _itemChanged(self, event): - """Handle item changed signal""" - if event is items.ItemChangedType.DATA: - self.sigDataChanged.emit() - elif event is items.ItemChangedType.VISUALIZATION_MODE: - mode = self.getComplexMode() - self.sigVisualizationModeChanged.emit(mode) - - def getPlot(self): - """Return the PlotWidget displaying the data""" - return self._plot2D - - def setData(self, data=None, copy=True): - """Set the complex data to display. - - :param numpy.ndarray data: 2D complex data - :param bool copy: True (default) to copy the data, - False to use provided data (do not modify!). - """ - if data is None: - data = numpy.zeros((0, 0), dtype=numpy.complex64) - - previousData = self._plotImage.getComplexData(copy=False) - - self._plotImage.setData(data, copy=copy) - - if previousData.shape != data.shape: - self.getPlot().resetZoom() - - def getData(self, copy=True): - """Get the currently displayed complex data. - - :param bool copy: True (default) to return a copy of the data, - False to return internal data (do not modify!). - :return: The complex data array. - :rtype: numpy.ndarray of complex with 2 dimensions - """ - return self._plotImage.getComplexData(copy=copy) - - def getDisplayedData(self, copy=True): - """Returns the displayed data depending on the visualization mode - - WARNING: The returned data can be a uint8 RGBA image - - :param bool copy: True (default) to return a copy of the data, - False to return internal data (do not modify!) - :rtype: numpy.ndarray of float with 2 dims or RGBA image (uint8). - """ - mode = self.getComplexMode() - if mode in (self.ComplexMode.AMPLITUDE_PHASE, - self.ComplexMode.LOG10_AMPLITUDE_PHASE): - return self._plotImage.getRgbaImageData(copy=copy) - else: - return self._plotImage.getData(copy=copy) - - # Backward compatibility - - Mode = ComplexMode - - @classmethod - @deprecated(replacement='supportedComplexModes', since_version='0.11.0') - def getSupportedVisualizationModes(cls): - return cls.supportedComplexModes() - - @deprecated(replacement='setComplexMode', since_version='0.11.0') - def setVisualizationMode(self, mode): - return self.setComplexMode(mode) - - @deprecated(replacement='getComplexMode', since_version='0.11.0') - def getVisualizationMode(self): - return self.getComplexMode() - - # Image item proxy - - @staticmethod - def supportedComplexModes(): - """Returns the supported visualization modes. - - Supported visualization modes are: - - - amplitude: The absolute value provided by numpy.absolute - - phase: The phase (or argument) provided by numpy.angle - - real: Real part - - imaginary: Imaginary part - - amplitude_phase: Color-coded phase with amplitude as alpha. - - log10_amplitude_phase: - Color-coded phase with log10(amplitude) as alpha. - - :rtype: List[ComplexMode] - """ - return ImageComplexData.supportedComplexModes() - - def setComplexMode(self, mode): - """Set the mode of visualization of the complex data. - - See :meth:`supportedComplexModes` for the list of - supported modes. - - How-to change visualization mode:: - - widget = ComplexImageView() - widget.setComplexMode(ComplexImageView.ComplexMode.PHASE) - # or - widget.setComplexMode('phase') - - :param Unions[ComplexMode,str] mode: The mode to use. - """ - self._plotImage.setComplexMode(mode) - - def getComplexMode(self): - """Get the current visualization mode of the complex data. - - :rtype: ComplexMode - """ - return self._plotImage.getComplexMode() - - def _setAmplitudeRangeInfo(self, max_=None, delta=2): - """Set the amplitude range to display for 'log10_amplitude_phase' mode. - - :param max_: Max of the amplitude range. - If None it autoscales to data max. - :param float delta: Delta range in log10 to display - """ - self._plotImage._setAmplitudeRangeInfo(max_, delta) - - def _getAmplitudeRangeInfo(self): - """Returns the amplitude range to use for 'log10_amplitude_phase' mode. - - :return: (max, delta), if max is None, then it autoscales to data max - :rtype: 2-tuple""" - return self._plotImage._getAmplitudeRangeInfo() - - def setColormap(self, colormap, mode=None): - """Set the colormap to use for amplitude, phase, real or imaginary. - - WARNING: This colormap is not used when displaying both - amplitude and phase. - - :param ~silx.gui.colors.Colormap colormap: The colormap - :param ComplexMode mode: If specified, set the colormap of this specific mode - """ - self._plotImage.setColormap(colormap, mode) - - def getColormap(self, mode=None): - """Returns the colormap used to display the data. - - :param ComplexMode mode: If specified, set the colormap of this specific mode - :rtype: ~silx.gui.colors.Colormap - """ - return self._plotImage.getColormap(mode=mode) - - def getOrigin(self): - """Returns the offset from origin at which to display the image. - - :rtype: 2-tuple of float - """ - return self._plotImage.getOrigin() - - def setOrigin(self, origin): - """Set the offset from origin at which to display the image. - - :param origin: (ox, oy) Offset from origin - :type origin: float or 2-tuple of float - """ - self._plotImage.setOrigin(origin) - - def getScale(self): - """Returns the scale of the image in data coordinates. - - :rtype: 2-tuple of float - """ - return self._plotImage.getScale() - - def setScale(self, scale): - """Set the scale of the image - - :param scale: (sx, sy) Scale of the image - :type scale: float or 2-tuple of float - """ - self._plotImage.setScale(scale) - - # PlotWidget API proxy - - def getXAxis(self): - """Returns the X axis - - :rtype: :class:`.items.Axis` - """ - return self.getPlot().getXAxis() - - def getYAxis(self): - """Returns an Y axis - - :rtype: :class:`.items.Axis` - """ - return self.getPlot().getYAxis(axis='left') - - def getGraphTitle(self): - """Return the plot main title as a str.""" - return self.getPlot().getGraphTitle() - - def setGraphTitle(self, title=""): - """Set the plot main title. - - :param str title: Main title of the plot (default: '') - """ - self.getPlot().setGraphTitle(title) - - def setKeepDataAspectRatio(self, flag): - """Set whether the plot keeps data aspect ratio or not. - - :param bool flag: True to respect data aspect ratio - """ - self.getPlot().setKeepDataAspectRatio(flag) - - def isKeepDataAspectRatio(self): - """Returns whether the plot is keeping data aspect ratio or not.""" - return self.getPlot().isKeepDataAspectRatio() |