diff options
Diffstat (limited to 'silx/gui/plot/tools/profile/manager.py')
-rw-r--r-- | silx/gui/plot/tools/profile/manager.py | 1076 |
1 files changed, 0 insertions, 1076 deletions
diff --git a/silx/gui/plot/tools/profile/manager.py b/silx/gui/plot/tools/profile/manager.py deleted file mode 100644 index 68db9a6..0000000 --- a/silx/gui/plot/tools/profile/manager.py +++ /dev/null @@ -1,1076 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018-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. -# -# ###########################################################################*/ -"""This module provides a manager to compute and display profiles. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "28/06/2018" - -import logging -import weakref - -from silx.gui import qt -from silx.gui import colors -from silx.gui import utils - -from silx.utils.weakref import WeakMethodProxy -from silx.gui import icons -from silx.gui.plot import PlotWidget -from silx.gui.plot.tools.roi import RegionOfInterestManager -from silx.gui.plot.tools.roi import CreateRoiModeAction -from silx.gui.plot import items -from silx.gui.qt import silxGlobalThreadPool -from silx.gui.qt import inspect -from . import rois -from . import core -from . import editors - - -_logger = logging.getLogger(__name__) - - -class _RunnableComputeProfile(qt.QRunnable): - """Runner to process profiles - - :param qt.QThreadPool threadPool: The thread which will be used to - execute this runner. It is used to update the used signals - :param ~silx.gui.plot.items.Item item: Item in which the profile is - computed - :param ~silx.gui.plot.tools.profile.core.ProfileRoiMixIn roi: ROI - defining the profile shape and other characteristics - """ - - class _Signals(qt.QObject): - """Signal holder""" - resultReady = qt.Signal(object, object) - runnerFinished = qt.Signal(object) - - def __init__(self, threadPool, item, roi): - """Constructor - """ - super(_RunnableComputeProfile, self).__init__() - self._signals = self._Signals() - self._signals.moveToThread(threadPool.thread()) - self._item = item - self._roi = roi - self._cancelled = False - - def _lazyCancel(self): - """Cancel the runner if it is not yet started. - - The threadpool will still execute the runner, but this will process - nothing. - - This is only used with Qt<5.9 where QThreadPool.tryTake is not available. - """ - self._cancelled = True - - def autoDelete(self): - return False - - def getRoi(self): - """Returns the ROI in which the runner will compute a profile. - - :rtype: ~silx.gui.plot.tools.profile.core.ProfileRoiMixIn - """ - return self._roi - - @property - def resultReady(self): - """Signal emitted when the result of the computation is available. - - This signal provides 2 values: The ROI, and the computation result. - """ - return self._signals.resultReady - - @property - def runnerFinished(self): - """Signal emitted when runner have finished. - - This signal provides a single value: the runner itself. - """ - return self._signals.runnerFinished - - def run(self): - """Process the profile computation. - """ - if not self._cancelled: - try: - profileData = self._roi.computeProfile(self._item) - except Exception: - _logger.error("Error while computing profile", exc_info=True) - else: - self.resultReady.emit(self._roi, profileData) - self.runnerFinished.emit(self) - - -class ProfileWindow(qt.QMainWindow): - """ - Display a computed profile. - - The content can be described using :meth:`setRoiProfile` if the source of - the profile is a profile ROI, and :meth:`setProfile` for the data content. - """ - - sigClose = qt.Signal() - """Emitted by :meth:`closeEvent` (e.g. when the window is closed - through the window manager's close icon).""" - - def __init__(self, parent=None, backend=None): - qt.QMainWindow.__init__(self, parent=parent, flags=qt.Qt.Dialog) - - self.setWindowTitle('Profile window') - self._plot1D = None - self._plot2D = None - self._backend = backend - self._data = None - - widget = qt.QWidget() - self._layout = qt.QStackedLayout(widget) - self._layout.setContentsMargins(0, 0, 0, 0) - self.setCentralWidget(widget) - - def prepareWidget(self, roi): - """Called before the show to prepare the window to use with - a specific ROI.""" - if isinstance(roi, rois._DefaultImageStackProfileRoiMixIn): - profileType = roi.getProfileType() - else: - profileType = "1D" - if profileType == "1D": - self.getPlot1D() - elif profileType == "2D": - self.getPlot2D() - - def createPlot1D(self, parent, backend): - """Inherit this function to create your own plot to render 1D - profiles. The default value is a `Plot1D`. - - :param parent: The parent of this widget or None. - :param backend: The backend to use for the plot. - See :class:`PlotWidget` for the list of supported backend. - :rtype: PlotWidget - """ - # import here to avoid circular import - from ...PlotWindow import Plot1D - plot = Plot1D(parent=parent, backend=backend) - plot.setDataMargins(yMinMargin=0.1, yMaxMargin=0.1) - plot.setGraphYLabel('Profile') - plot.setGraphXLabel('') - return plot - - def createPlot2D(self, parent, backend): - """Inherit this function to create your own plot to render 2D - profiles. The default value is a `Plot2D`. - - :param parent: The parent of this widget or None. - :param backend: The backend to use for the plot. - See :class:`PlotWidget` for the list of supported backend. - :rtype: PlotWidget - """ - # import here to avoid circular import - from ...PlotWindow import Plot2D - return Plot2D(parent=parent, backend=backend) - - def getPlot1D(self, init=True): - """Return the current plot used to display curves and create it if it - does not yet exists and `init` is True. Else returns None.""" - if not init: - return self._plot1D - if self._plot1D is None: - self._plot1D = self.createPlot1D(self, self._backend) - self._layout.addWidget(self._plot1D) - return self._plot1D - - def _showPlot1D(self): - plot = self.getPlot1D() - self._layout.setCurrentWidget(plot) - - def getPlot2D(self, init=True): - """Return the current plot used to display image and create it if it - does not yet exists and `init` is True. Else returns None.""" - if not init: - return self._plot2D - if self._plot2D is None: - self._plot2D = self.createPlot2D(parent=self, backend=self._backend) - self._layout.addWidget(self._plot2D) - return self._plot2D - - def _showPlot2D(self): - plot = self.getPlot2D() - self._layout.setCurrentWidget(plot) - - def getCurrentPlotWidget(self): - return self._layout.currentWidget() - - def closeEvent(self, qCloseEvent): - self.sigClose.emit() - qCloseEvent.accept() - - def setRoiProfile(self, roi): - """Set the profile ROI which it the source of the following data - to display. - - :param ProfileRoiMixIn roi: The profile ROI data source - """ - if roi is None: - return - self.__color = colors.rgba(roi.getColor()) - - def _setImageProfile(self, data): - """ - Setup the window to display a new profile data which is represented - by an image. - - :param core.ImageProfileData data: Computed data profile - """ - plot = self.getPlot2D() - - plot.clear() - plot.setGraphTitle(data.title) - plot.getXAxis().setLabel(data.xLabel) - - - coords = data.coords - colormap = data.colormap - profileScale = (coords[-1] - coords[0]) / data.profile.shape[1], 1 - plot.addImage(data.profile, - legend="profile", - colormap=colormap, - origin=(coords[0], 0), - scale=profileScale) - plot.getYAxis().setLabel("Frame index (depth)") - - self._showPlot2D() - - def _setCurveProfile(self, data): - """ - Setup the window to display a new profile data which is represented - by a curve. - - :param core.CurveProfileData data: Computed data profile - """ - plot = self.getPlot1D() - - plot.clear() - plot.setGraphTitle(data.title) - plot.getXAxis().setLabel(data.xLabel) - plot.getYAxis().setLabel(data.yLabel) - - plot.addCurve(data.coords, - data.profile, - legend="level", - color=self.__color) - - self._showPlot1D() - - def _setRgbaProfile(self, data): - """ - Setup the window to display a new profile data which is represented - by a curve. - - :param core.RgbaProfileData data: Computed data profile - """ - plot = self.getPlot1D() - - plot.clear() - plot.setGraphTitle(data.title) - plot.getXAxis().setLabel(data.xLabel) - plot.getYAxis().setLabel(data.yLabel) - - self._showPlot1D() - - plot.addCurve(data.coords, data.profile, - legend="level", color="black") - plot.addCurve(data.coords, data.profile_r, - legend="red", color="red") - plot.addCurve(data.coords, data.profile_g, - legend="green", color="green") - plot.addCurve(data.coords, data.profile_b, - legend="blue", color="blue") - if data.profile_a is not None: - plot.addCurve(data.coords, data.profile_a, legend="alpha", color="gray") - - def clear(self): - """Clear the window profile""" - plot = self.getPlot1D(init=False) - if plot is not None: - plot.clear() - plot = self.getPlot2D(init=False) - if plot is not None: - plot.clear() - - def getProfile(self): - """Returns the profile data which is displayed""" - return self.__data - - def setProfile(self, data): - """ - Setup the window to display a new profile data. - - This method dispatch the result to a specific method according to the - data type. - - :param data: Computed data profile - """ - self.__data = data - if data is None: - self.clear() - elif isinstance(data, core.ImageProfileData): - self._setImageProfile(data) - elif isinstance(data, core.RgbaProfileData): - self._setRgbaProfile(data) - elif isinstance(data, core.CurveProfileData): - self._setCurveProfile(data) - else: - raise TypeError("Unsupported type %s" % type(data)) - - -class _ClearAction(qt.QAction): - """Action to clear the profile manager - - The action is only enabled if something can be cleaned up. - """ - - def __init__(self, parent, profileManager): - super(_ClearAction, self).__init__(parent) - self.__profileManager = weakref.ref(profileManager) - icon = icons.getQIcon('profile-clear') - self.setIcon(icon) - self.setText('Clear profile') - self.setToolTip('Clear the profiles') - self.setCheckable(False) - self.setEnabled(False) - self.triggered.connect(profileManager.clearProfile) - plot = profileManager.getPlotWidget() - roiManager = profileManager.getRoiManager() - plot.sigInteractiveModeChanged.connect(self.__modeUpdated) - roiManager.sigRoiChanged.connect(self.__roiListUpdated) - - def getProfileManager(self): - return self.__profileManager() - - def __roiListUpdated(self): - self.__update() - - def __modeUpdated(self, source): - self.__update() - - def __update(self): - profileManager = self.getProfileManager() - if profileManager is None: - return - roiManager = profileManager.getRoiManager() - if roiManager is None: - return - enabled = roiManager.isStarted() or len(roiManager.getRois()) > 0 - self.setEnabled(enabled) - - -class _StoreLastParamBehavior(qt.QObject): - """This object allow to store and restore the properties of the ROI - profiles""" - - def __init__(self, parent): - assert isinstance(parent, ProfileManager) - super(_StoreLastParamBehavior, self).__init__(parent=parent) - self.__properties = {} - self.__profileRoi = None - self.__filter = utils.LockReentrant() - - def _roi(self): - """Return the spied ROI""" - if self.__profileRoi is None: - return None - roi = self.__profileRoi() - if roi is None: - self.__profileRoi = None - return roi - - def setProfileRoi(self, roi): - """Set a profile ROI to spy. - - :param ProfileRoiMixIn roi: A profile ROI - """ - previousRoi = self._roi() - if previousRoi is roi: - return - if previousRoi is not None: - previousRoi.sigProfilePropertyChanged.disconnect(self._profilePropertyChanged) - self.__profileRoi = None if roi is None else weakref.ref(roi) - if roi is not None: - roi.sigProfilePropertyChanged.connect(self._profilePropertyChanged) - - def _profilePropertyChanged(self): - """Handle changes on the properties defining the profile ROI. - """ - if self.__filter.locked(): - return - roi = self.sender() - self.storeProperties(roi) - - def storeProperties(self, roi): - if isinstance(roi, (rois._DefaultImageStackProfileRoiMixIn, - rois.ProfileImageStackCrossROI)): - self.__properties["method"] = roi.getProfileMethod() - self.__properties["line-width"] = roi.getProfileLineWidth() - self.__properties["type"] = roi.getProfileType() - elif isinstance(roi, (rois._DefaultImageProfileRoiMixIn, - rois.ProfileImageCrossROI)): - self.__properties["method"] = roi.getProfileMethod() - self.__properties["line-width"] = roi.getProfileLineWidth() - elif isinstance(roi, (rois._DefaultScatterProfileRoiMixIn, - rois.ProfileScatterCrossROI)): - self.__properties["npoints"] = roi.getNPoints() - - def restoreProperties(self, roi): - with self.__filter: - if isinstance(roi, (rois._DefaultImageStackProfileRoiMixIn, - rois.ProfileImageStackCrossROI)): - value = self.__properties.get("method", None) - if value is not None: - roi.setProfileMethod(value) - value = self.__properties.get("line-width", None) - if value is not None: - roi.setProfileLineWidth(value) - value = self.__properties.get("type", None) - if value is not None: - roi.setProfileType(value) - elif isinstance(roi, (rois._DefaultImageProfileRoiMixIn, - rois.ProfileImageCrossROI)): - value = self.__properties.get("method", None) - if value is not None: - roi.setProfileMethod(value) - value = self.__properties.get("line-width", None) - if value is not None: - roi.setProfileLineWidth(value) - elif isinstance(roi, (rois._DefaultScatterProfileRoiMixIn, - rois.ProfileScatterCrossROI)): - value = self.__properties.get("npoints", None) - if value is not None: - roi.setNPoints(value) - - -class ProfileManager(qt.QObject): - """Base class for profile management tools - - :param plot: :class:`~silx.gui.plot.PlotWidget` on which to operate. - :param plot: :class:`~silx.gui.plot.tools.roi.RegionOfInterestManager` - on which to operate. - """ - def __init__(self, parent=None, plot=None, roiManager=None): - super(ProfileManager, self).__init__(parent) - - assert isinstance(plot, PlotWidget) - self._plotRef = weakref.ref( - plot, WeakMethodProxy(self.__plotDestroyed)) - - # Set-up interaction manager - if roiManager is None: - roiManager = RegionOfInterestManager(plot) - - self._roiManagerRef = weakref.ref(roiManager) - self._rois = [] - self._pendingRunners = [] - """List of ROIs which have to be updated""" - - self.__reentrantResults = {} - """Store reentrant result to avoid to skip some of them - cause the implementation uses a QEventLoop.""" - - self._profileWindowClass = ProfileWindow - """Class used to display the profile results""" - - self._computedProfiles = 0 - """Statistics for tests""" - - self.__itemTypes = [] - """Kind of items to use""" - - self.__tracking = False - """Is the plot active items are tracked""" - - self.__useColorFromCursor = True - """If true, force the ROI color with the colormap marker color""" - - self._item = None - """The selected item""" - - self.__singleProfileAtATime = True - """When it's true, only a single profile is displayed at a time.""" - - self._previousWindowGeometry = [] - - self._storeProperties = _StoreLastParamBehavior(self) - """If defined the profile properties of the last ROI are reused to the - new created ones""" - - # Listen to plot limits changed - plot.getXAxis().sigLimitsChanged.connect(self.requestUpdateAllProfile) - plot.getYAxis().sigLimitsChanged.connect(self.requestUpdateAllProfile) - - roiManager.sigInteractiveModeFinished.connect(self.__interactionFinished) - roiManager.sigInteractiveRoiCreated.connect(self.__roiCreated) - roiManager.sigRoiAdded.connect(self.__roiAdded) - roiManager.sigRoiAboutToBeRemoved.connect(self.__roiRemoved) - - def setSingleProfile(self, enable): - """ - Enable or disable the single profile mode. - - In single mode, the manager enforce a single ROI at the same - time. A new one will remove the previous one. - - If this mode is not enabled, many ROIs can be created, and many - profile windows will be displayed. - """ - self.__singleProfileAtATime = enable - - def isSingleProfile(self): - """ - Returns true if the manager is in a single profile mode. - - :rtype: bool - """ - return self.__singleProfileAtATime - - def __interactionFinished(self): - """Handle end of interactive mode""" - pass - - def __roiAdded(self, roi): - """Handle new ROI""" - # Filter out non profile ROIs - if not isinstance(roi, core.ProfileRoiMixIn): - return - self.__addProfile(roi) - - def __roiRemoved(self, roi): - """Handle removed ROI""" - # Filter out non profile ROIs - if not isinstance(roi, core.ProfileRoiMixIn): - return - self.__removeProfile(roi) - - def createProfileAction(self, profileRoiClass, parent=None): - """Create an action from a class of ProfileRoi - - :param core.ProfileRoiMixIn profileRoiClass: A class of a profile ROI - :param qt.QObject parent: The parent of the created action. - :rtype: qt.QAction - """ - if not issubclass(profileRoiClass, core.ProfileRoiMixIn): - raise TypeError("Type %s not expected" % type(profileRoiClass)) - roiManager = self.getRoiManager() - action = CreateRoiModeAction(parent, roiManager, profileRoiClass) - if hasattr(profileRoiClass, "ICON"): - action.setIcon(icons.getQIcon(profileRoiClass.ICON)) - if hasattr(profileRoiClass, "NAME"): - def articulify(word): - """Add an an/a article in the front of the word""" - first = word[1] if word[0] == 'h' else word[0] - if first in "aeiou": - return "an " + word - return "a " + word - action.setText('Define %s' % articulify(profileRoiClass.NAME)) - action.setToolTip('Enables %s selection mode' % profileRoiClass.NAME) - action.setSingleShot(True) - return action - - def createClearAction(self, parent): - """Create an action to clean up the plot from the profile ROIs. - - :param qt.QObject parent: The parent of the created action. - :rtype: qt.QAction - """ - action = _ClearAction(parent, self) - return action - - def createImageActions(self, parent): - """Create actions designed for image items. This actions created - new ROIs. - - :param qt.QObject parent: The parent of the created action. - :rtype: List[qt.QAction] - """ - profileClasses = [ - rois.ProfileImageHorizontalLineROI, - rois.ProfileImageVerticalLineROI, - rois.ProfileImageLineROI, - rois.ProfileImageDirectedLineROI, - rois.ProfileImageCrossROI, - ] - return [self.createProfileAction(pc, parent=parent) for pc in profileClasses] - - def createScatterActions(self, parent): - """Create actions designed for scatter items. This actions created - new ROIs. - - :param qt.QObject parent: The parent of the created action. - :rtype: List[qt.QAction] - """ - profileClasses = [ - rois.ProfileScatterHorizontalLineROI, - rois.ProfileScatterVerticalLineROI, - rois.ProfileScatterLineROI, - rois.ProfileScatterCrossROI, - ] - return [self.createProfileAction(pc, parent=parent) for pc in profileClasses] - - def createScatterSliceActions(self, parent): - """Create actions designed for regular scatter items. This actions - created new ROIs. - - This ROIs was designed to use the input data without interpolation, - like you could do with an image. - - :param qt.QObject parent: The parent of the created action. - :rtype: List[qt.QAction] - """ - profileClasses = [ - rois.ProfileScatterHorizontalSliceROI, - rois.ProfileScatterVerticalSliceROI, - rois.ProfileScatterCrossSliceROI, - ] - return [self.createProfileAction(pc, parent=parent) for pc in profileClasses] - - def createImageStackActions(self, parent): - """Create actions designed for stack image items. This actions - created new ROIs. - - This ROIs was designed to create both profile on the displayed image - and profile on the full stack (2D result). - - :param qt.QObject parent: The parent of the created action. - :rtype: List[qt.QAction] - """ - profileClasses = [ - rois.ProfileImageStackHorizontalLineROI, - rois.ProfileImageStackVerticalLineROI, - rois.ProfileImageStackLineROI, - rois.ProfileImageStackCrossROI, - ] - return [self.createProfileAction(pc, parent=parent) for pc in profileClasses] - - def createEditorAction(self, parent): - """Create an action containing GUI to edit the selected profile ROI. - - :param qt.QObject parent: The parent of the created action. - :rtype: qt.QAction - """ - action = editors.ProfileRoiEditorAction(parent) - action.setRoiManager(self.getRoiManager()) - return action - - def setItemType(self, image=False, scatter=False): - """Set the item type to use and select the active one. - - :param bool image: Image item are allowed - :param bool scatter: Scatter item are allowed - """ - self.__itemTypes = [] - plot = self.getPlotWidget() - item = None - if image: - self.__itemTypes.append("image") - item = plot.getActiveImage() - if scatter: - self.__itemTypes.append("scatter") - if item is None: - item = plot.getActiveScatter() - self.setPlotItem(item) - - def setProfileWindowClass(self, profileWindowClass): - """Set the class which will be instantiated to display profile result. - """ - self._profileWindowClass = profileWindowClass - - def setActiveItemTracking(self, tracking): - """Enable/disable the tracking of the active item of the plot. - - :param bool tracking: Tracking mode - """ - if self.__tracking == tracking: - return - plot = self.getPlotWidget() - if self.__tracking: - plot.sigActiveImageChanged.disconnect(self._activeImageChanged) - plot.sigActiveScatterChanged.disconnect(self._activeScatterChanged) - self.__tracking = tracking - if self.__tracking: - plot.sigActiveImageChanged.connect(self.__activeImageChanged) - plot.sigActiveScatterChanged.connect(self.__activeScatterChanged) - - def setDefaultColorFromCursorColor(self, enabled): - """Enabled/disable the use of the colormap cursor color to display the - ROIs. - - If set, the manager will update the color of the profile ROIs using the - current colormap cursor color from the selected item. - """ - self.__useColorFromCursor = enabled - - def __activeImageChanged(self, previous, legend): - """Handle plot item selection""" - if "image" in self.__itemTypes: - plot = self.getPlotWidget() - item = plot.getImage(legend) - self.setPlotItem(item) - - def __activeScatterChanged(self, previous, legend): - """Handle plot item selection""" - if "scatter" in self.__itemTypes: - plot = self.getPlotWidget() - item = plot.getScatter(legend) - self.setPlotItem(item) - - def __roiCreated(self, roi): - """Handle ROI creation""" - # Filter out non profile ROIs - if isinstance(roi, core.ProfileRoiMixIn): - if self._storeProperties is not None: - # Initialize the properties with the previous ones - self._storeProperties.restoreProperties(roi) - - def __addProfile(self, profileRoi): - """Add a new ROI to the manager.""" - if profileRoi.getFocusProxy() is None: - if self._storeProperties is not None: - # Follow changes on properties - self._storeProperties.setProfileRoi(profileRoi) - if self.__singleProfileAtATime: - # FIXME: It would be good to reuse the windows to avoid blinking - self.clearProfile() - - profileRoi._setProfileManager(self) - self._updateRoiColor(profileRoi) - self._rois.append(profileRoi) - self.requestUpdateProfile(profileRoi) - - def __removeProfile(self, profileRoi): - """Remove a ROI from the manager.""" - window = self._disconnectProfileWindow(profileRoi) - if window is not None: - geometry = window.geometry() - if not geometry.isEmpty(): - self._previousWindowGeometry.append(geometry) - self.clearProfileWindow(window) - if profileRoi in self._rois: - self._rois.remove(profileRoi) - - def _disconnectProfileWindow(self, profileRoi): - """Handle profile window close.""" - window = profileRoi.getProfileWindow() - profileRoi.setProfileWindow(None) - return window - - def clearProfile(self): - """Clear the associated ROI profile""" - roiManager = self.getRoiManager() - for roi in list(self._rois): - if roi.getFocusProxy() is not None: - # Skip sub ROIs, it will be removed by their parents - continue - roiManager.removeRoi(roi) - - if not roiManager.isDrawing(): - # Clean the selected mode - roiManager.stop() - - def hasPendingOperations(self): - """Returns true if a thread is still computing or displaying a profile. - - :rtype: bool - """ - return len(self.__reentrantResults) > 0 or len(self._pendingRunners) > 0 - - def requestUpdateAllProfile(self): - """Request to update the profile of all the managed ROIs. - """ - for roi in self._rois: - self.requestUpdateProfile(roi) - - def requestUpdateProfile(self, profileRoi): - """Request to update a specific profile ROI. - - :param ~core.ProfileRoiMixIn profileRoi: - """ - if profileRoi.computeProfile is None: - return - threadPool = silxGlobalThreadPool() - - # Clean up deprecated runners - for runner in list(self._pendingRunners): - if not inspect.isValid(runner): - self._pendingRunners.remove(runner) - continue - if runner.getRoi() is profileRoi: - if hasattr(threadPool, "tryTake"): - if threadPool.tryTake(runner): - self._pendingRunners.remove(runner) - else: # Support Qt<5.9 - runner._lazyCancel() - - item = self.getPlotItem() - if item is None or not isinstance(item, profileRoi.ITEM_KIND): - # This item is not compatible with this profile - profileRoi._setPlotItem(None) - profileWindow = profileRoi.getProfileWindow() - if profileWindow is not None: - profileWindow.setProfile(None) - return - - profileRoi._setPlotItem(item) - runner = _RunnableComputeProfile(threadPool, item, profileRoi) - runner.runnerFinished.connect(self.__cleanUpRunner) - runner.resultReady.connect(self.__displayResult) - self._pendingRunners.append(runner) - threadPool.start(runner) - - def __cleanUpRunner(self, runner): - """Remove a thread pool runner from the list of hold tasks. - - Called at the termination of the runner. - """ - if runner in self._pendingRunners: - self._pendingRunners.remove(runner) - - def __displayResult(self, roi, profileData): - """Display the result of a ROI. - - :param ~core.ProfileRoiMixIn profileRoi: A managed ROI - :param ~core.CurveProfileData profileData: Computed data profile - """ - if roi in self.__reentrantResults: - # Store the data to process it in the main loop - # And not a sub loop created by initProfileWindow - # This also remove the duplicated requested - self.__reentrantResults[roi] = profileData - return - - self.__reentrantResults[roi] = profileData - self._computedProfiles = self._computedProfiles + 1 - window = roi.getProfileWindow() - if window is None: - plot = self.getPlotWidget() - window = self.createProfileWindow(plot, roi) - # roi.profileWindow have to be set before initializing the window - # Cause the initialization is using QEventLoop - roi.setProfileWindow(window) - self.initProfileWindow(window, roi) - window.show() - - lastData = self.__reentrantResults.pop(roi) - window.setProfile(lastData) - - def __plotDestroyed(self, ref): - """Handle finalization of PlotWidget - - :param ref: weakref to the plot - """ - self._plotRef = None - self._roiManagerRef = None - self._pendingRunners = [] - - def setPlotItem(self, item): - """Set the plot item focused by the profile manager. - - :param ~silx.gui.plot.items.Item item: A plot item - """ - previous = self.getPlotItem() - if previous is item: - return - if item is None: - self._item = None - else: - item.sigItemChanged.connect(self.__itemChanged) - self._item = weakref.ref(item) - self._updateRoiColors() - self.requestUpdateAllProfile() - - def getDefaultColor(self, item): - """Returns the default ROI color to use according to the given item. - - :param ~silx.gui.plot.items.item.Item item: AN item - :rtype: qt.QColor - """ - color = 'pink' - if isinstance(item, items.ColormapMixIn): - colormap = item.getColormap() - name = colormap.getName() - if name is not None: - color = colors.cursorColorForColormap(name) - color = colors.asQColor(color) - return color - - def _updateRoiColors(self): - """Update ROI color according to the item selection""" - if not self.__useColorFromCursor: - return - item = self.getPlotItem() - color = self.getDefaultColor(item) - for roi in self._rois: - roi.setColor(color) - - def _updateRoiColor(self, roi): - """Update a specific ROI according to the current selected item. - - :param RegionOfInterest roi: The ROI to update - """ - if not self.__useColorFromCursor: - return - item = self.getPlotItem() - color = self.getDefaultColor(item) - roi.setColor(color) - - def __itemChanged(self, changeType): - """Handle item changes. - """ - if changeType in (items.ItemChangedType.DATA, - items.ItemChangedType.MASK, - items.ItemChangedType.POSITION, - items.ItemChangedType.SCALE): - self.requestUpdateAllProfile() - elif changeType == (items.ItemChangedType.COLORMAP): - self._updateRoiColors() - - def getPlotItem(self): - """Returns the item focused by the profile manager. - - :rtype: ~silx.gui.plot.items.Item - """ - if self._item is None: - return None - item = self._item() - if item is None: - self._item = None - return item - - def getPlotWidget(self): - """The plot associated to the profile manager. - - :rtype: ~silx.gui.plot.PlotWidget - """ - if self._plotRef is None: - return None - plot = self._plotRef() - if plot is None: - self._plotRef = None - return plot - - def getCurrentRoi(self): - """Returns the currently selected ROI, else None. - - :rtype: core.ProfileRoiMixIn - """ - roiManager = self.getRoiManager() - if roiManager is None: - return None - roi = roiManager.getCurrentRoi() - if not isinstance(roi, core.ProfileRoiMixIn): - return None - return roi - - def getRoiManager(self): - """Returns the used ROI manager - - :rtype: RegionOfInterestManager - """ - return self._roiManagerRef() - - def createProfileWindow(self, plot, roi): - """Create a new profile window. - - :param ~core.ProfileRoiMixIn roi: The plot containing the raw data - :param ~core.ProfileRoiMixIn roi: A managed ROI - :rtype: ~ProfileWindow - """ - return self._profileWindowClass(plot) - - def initProfileWindow(self, profileWindow, roi): - """This function is called just after the profile window creation in - order to initialize the window location. - - :param ~ProfileWindow profileWindow: - The profile window to initialize. - """ - # Enforce the use of one of the widgets - # To have the correct window size - profileWindow.prepareWidget(roi) - profileWindow.adjustSize() - - # Trick to avoid blinking while retrieving the right window size - # Display the window, hide it and wait for some event loops - profileWindow.show() - profileWindow.hide() - eventLoop = qt.QEventLoop(self) - for _ in range(10): - if not eventLoop.processEvents(): - break - - profileWindow.show() - if len(self._previousWindowGeometry) > 0: - geometry = self._previousWindowGeometry.pop() - profileWindow.setGeometry(geometry) - return - - window = self.getPlotWidget().window() - winGeom = window.frameGeometry() - qapp = qt.QApplication.instance() - desktop = qapp.desktop() - screenGeom = desktop.availableGeometry(window) - spaceOnLeftSide = winGeom.left() - spaceOnRightSide = screenGeom.width() - winGeom.right() - - profileGeom = profileWindow.frameGeometry() - profileWidth = profileGeom.width() - - # Align vertically to the center of the window - top = winGeom.top() + (winGeom.height() - profileGeom.height()) // 2 - - margin = 5 - if profileWidth < spaceOnRightSide: - # Place profile on the right - left = winGeom.right() + margin - elif profileWidth < spaceOnLeftSide: - # Place profile on the left - left = max(0, winGeom.left() - profileWidth - margin) - else: - # Move it as much as possible where there is more space - if spaceOnLeftSide > spaceOnRightSide: - left = 0 - else: - left = screenGeom.width() - profileGeom.width() - profileWindow.move(left, top) - - - def clearProfileWindow(self, profileWindow): - """Called when a profile window is not anymore needed. - - By default the window will be closed. But it can be - inherited to change this behavior. - """ - profileWindow.deleteLater() |