Source code for silx.gui.plot.tools.profile.editors

# /*##########################################################################
#
# Copyright (c) 2018-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 editors which are used to custom profile ROI properties.
"""

__authors__ = ["V. Valls"]
__license__ = "MIT"
__date__ = "28/06/2018"

import logging

from silx.gui import qt

from silx.gui.utils import blockSignals
from silx.gui.plot.PlotToolButtons import ProfileOptionToolButton
from silx.gui.plot.PlotToolButtons import ProfileToolButton
from . import rois
from . import core


_logger = logging.getLogger(__name__)


class _NoProfileRoiEditor(qt.QWidget):
    sigDataCommited = qt.Signal()

    def setEditorData(self, roi):
        pass

    def setRoiData(self, roi):
        pass


class _DefaultImageProfileRoiEditor(qt.QWidget):
    sigDataCommited = qt.Signal()

    def __init__(self, parent=None):
        qt.QWidget.__init__(self, parent=parent)
        layout = qt.QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        self._initLayout(layout)

    def _initLayout(self, layout):
        self._lineWidth = qt.QSpinBox(self)
        self._lineWidth.setRange(1, 1000)
        self._lineWidth.setValue(1)
        self._lineWidth.valueChanged[int].connect(self._widgetChanged)

        self._methodsButton = ProfileOptionToolButton(parent=self, plot=None)
        self._methodsButton.sigMethodChanged.connect(self._widgetChanged)

        label = qt.QLabel("W:")
        label.setToolTip("Line width in pixels")
        layout.addWidget(label)
        layout.addWidget(self._lineWidth)
        layout.addWidget(self._methodsButton)

    def _widgetChanged(self, value=None):
        self.commitData()

    def commitData(self):
        self.sigDataCommited.emit()

    def setEditorData(self, roi):
        with blockSignals(self._lineWidth):
            self._lineWidth.setValue(roi.getProfileLineWidth())
        with blockSignals(self._methodsButton):
            method = roi.getProfileMethod()
            self._methodsButton.setMethod(method)

    def setRoiData(self, roi):
        lineWidth = self._lineWidth.value()
        roi.setProfileLineWidth(lineWidth)
        method = self._methodsButton.getMethod()
        roi.setProfileMethod(method)


class _DefaultImageStackProfileRoiEditor(_DefaultImageProfileRoiEditor):
    def _initLayout(self, layout):
        super(_DefaultImageStackProfileRoiEditor, self)._initLayout(layout)
        self._profileDim = ProfileToolButton(parent=self, plot=None)
        self._profileDim.sigDimensionChanged.connect(self._widgetChanged)
        layout.addWidget(self._profileDim)

    def setEditorData(self, roi):
        super(_DefaultImageStackProfileRoiEditor, self).setEditorData(roi)
        with blockSignals(self._profileDim):
            kind = roi.getProfileType()
            dim = {"1D": 1, "2D": 2}[kind]
            self._profileDim.setDimension(dim)

    def setRoiData(self, roi):
        super(_DefaultImageStackProfileRoiEditor, self).setRoiData(roi)
        dim = self._profileDim.getDimension()
        kind = {1: "1D", 2: "2D"}[dim]
        roi.setProfileType(kind)


class _DefaultScatterProfileRoiEditor(qt.QWidget):
    sigDataCommited = qt.Signal()

    def __init__(self, parent=None):
        qt.QWidget.__init__(self, parent=parent)

        self._nPoints = qt.QSpinBox(self)
        self._nPoints.setRange(1, 9999)
        self._nPoints.setValue(1024)
        self._nPoints.valueChanged[int].connect(self.__widgetChanged)

        layout = qt.QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        label = qt.QLabel("Samples:")
        label.setToolTip("Number of sample points of the profile")
        layout.addWidget(label)
        layout.addWidget(self._nPoints)

    def __widgetChanged(self, value=None):
        self.commitData()

    def commitData(self):
        self.sigDataCommited.emit()

    def setEditorData(self, roi):
        with blockSignals(self._nPoints):
            self._nPoints.setValue(roi.getNPoints())

    def setRoiData(self, roi):
        nPoints = self._nPoints.value()
        roi.setNPoints(nPoints)


[docs] class ProfileRoiEditorAction(qt.QWidgetAction): """ Action displaying GUI to edit the selected ROI. :param qt.QWidget parent: Parent widget """ def __init__(self, parent=None): super(ProfileRoiEditorAction, self).__init__(parent) self.__roiManager = None self.__roi = None self.__inhibiteReentance = None
[docs] def createWidget(self, parent): """Inherit the method to create a new editor""" widget = qt.QWidget(parent) layout = qt.QHBoxLayout(widget) if isinstance(parent, qt.QMenu): margins = layout.contentsMargins() layout.setContentsMargins(margins.left(), 0, margins.right(), 0) else: layout.setContentsMargins(0, 0, 0, 0) editorClass = self.getEditorClass(self.__roi) editor = editorClass(parent) editor.setEditorData(self.__roi) self.__setEditor(widget, editor) return widget
[docs] def deleteWidget(self, widget): """Inherit the method to delete an editor""" self.__setEditor(widget, None) return qt.QWidgetAction.deleteWidget(self, widget)
def _getEditor(self, widget): """Returns the editor contained in the widget holder""" layout = widget.layout() if layout.count() == 0: return None return layout.itemAt(0).widget()
[docs] def setRoiManager(self, roiManager): """ Connect this action to a ROI manager. :param RegionOfInterestManager roiManager: A ROI manager """ if self.__roiManager is roiManager: return if self.__roiManager is not None: self.__roiManager.sigCurrentRoiChanged.disconnect(self.__currentRoiChanged) self.__roiManager = roiManager if self.__roiManager is not None: self.__roiManager.sigCurrentRoiChanged.connect(self.__currentRoiChanged) self.__currentRoiChanged(roiManager.getCurrentRoi())
def __currentRoiChanged(self, roi): """Handle changes of the selected ROI""" if roi is not None and not isinstance(roi, core.ProfileRoiMixIn): return self.setProfileRoi(roi)
[docs] def setProfileRoi(self, roi): """Set a profile ROI to edit. :param ProfileRoiMixIn roi: A profile ROI """ if self.__roi is roi: return if self.__roi is not None: self.__roi.sigProfilePropertyChanged.disconnect(self.__roiPropertyChanged) self.__roi = roi if self.__roi is not None: self.__roi.sigProfilePropertyChanged.connect(self.__roiPropertyChanged) self._updateWidgets()
[docs] def getRoiProfile(self): """Returns the edited profile ROI. :rtype: ProfileRoiMixIn """ return self.__roi
def __roiPropertyChanged(self): """Handle changes on the property defining the ROI.""" self._updateWidgetValues() def __setEditor(self, widget, editor): """Set the editor to display. :param qt.QWidget editor: The editor to display """ previousEditor = self._getEditor(widget) if previousEditor is editor: return layout = widget.layout() if previousEditor is not None: try: previousEditor.sigDataCommited.disconnect(self._editorDataCommited) except (RuntimeError, TypeError): pass layout.removeWidget(previousEditor) previousEditor.deleteLater() if editor is not None: editor.sigDataCommited.connect(self._editorDataCommited) layout.addWidget(editor)
[docs] def getEditorClass(self, roi): """Returns the editor class to use according to the ROI.""" if roi is None: editorClass = _NoProfileRoiEditor elif isinstance( roi, (rois._DefaultImageStackProfileRoiMixIn, rois.ProfileImageStackCrossROI), ): # Must be done before the default image ROI # Cause ImageStack ROIs inherit from Image ROIs editorClass = _DefaultImageStackProfileRoiEditor elif isinstance( roi, (rois._DefaultImageProfileRoiMixIn, rois.ProfileImageCrossROI) ): editorClass = _DefaultImageProfileRoiEditor elif isinstance( roi, (rois._DefaultScatterProfileRoiMixIn, rois.ProfileScatterCrossROI) ): editorClass = _DefaultScatterProfileRoiEditor else: # Unsupported editorClass = _NoProfileRoiEditor return editorClass
def _updateWidgets(self): """Update the kind of editor to display, according to the selected profile ROI.""" parent = self.parent() editorClass = self.getEditorClass(self.__roi) for widget in self.createdWidgets(): editor = editorClass(parent) editor.setEditorData(self.__roi) self.__setEditor(widget, editor) def _updateWidgetValues(self): """Update the content of the displayed editor, according to the selected profile ROI.""" for widget in self.createdWidgets(): editor = self._getEditor(widget) if self.__inhibiteReentance is editor: continue editor.setEditorData(self.__roi) def _editorDataCommited(self): """Handle changes from the editor.""" editor = self.sender() if self.__roi is not None: self.__inhibiteReentance = editor editor.setRoiData(self.__roi) self.__inhibiteReentance = None