summaryrefslogtreecommitdiff
path: root/silx/gui/plot/tools/profile/_BaseProfileToolBar.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/tools/profile/_BaseProfileToolBar.py')
-rw-r--r--silx/gui/plot/tools/profile/_BaseProfileToolBar.py430
1 files changed, 0 insertions, 430 deletions
diff --git a/silx/gui/plot/tools/profile/_BaseProfileToolBar.py b/silx/gui/plot/tools/profile/_BaseProfileToolBar.py
deleted file mode 100644
index 6d9d6d4..0000000
--- a/silx/gui/plot/tools/profile/_BaseProfileToolBar.py
+++ /dev/null
@@ -1,430 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2018 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 the base class for profile toolbars."""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "28/06/2018"
-
-
-import logging
-import weakref
-
-import numpy
-
-from silx.utils.weakref import WeakMethodProxy
-from silx.gui import qt, icons, colors
-from silx.gui.plot import PlotWidget, items
-from silx.gui.plot.ProfileMainWindow import ProfileMainWindow
-from silx.gui.plot.tools.roi import RegionOfInterestManager
-from silx.gui.plot.items import roi as roi_items
-
-
-_logger = logging.getLogger(__name__)
-
-
-class _BaseProfileToolBar(qt.QToolBar):
- """Base class for QToolBar plot profiling tools
-
- :param parent: See :class:`QToolBar`.
- :param plot: :class:`~silx.gui.plot.PlotWidget` on which to operate.
- :param str title: See :class:`QToolBar`.
- """
-
- sigProfileChanged = qt.Signal()
- """Signal emitted when the profile has changed"""
-
- def __init__(self, parent=None, plot=None, title=''):
- super(_BaseProfileToolBar, self).__init__(title, parent)
-
- self.__profile = None
- self.__profileTitle = ''
-
- assert isinstance(plot, PlotWidget)
- self._plotRef = weakref.ref(
- plot, WeakMethodProxy(self.__plotDestroyed))
-
- self._profileWindow = None
-
- # Set-up interaction manager
- roiManager = RegionOfInterestManager(plot)
- self._roiManagerRef = weakref.ref(roiManager)
-
- roiManager.sigInteractiveModeFinished.connect(self.__interactionFinished)
- roiManager.sigRoiChanged.connect(self.updateProfile)
- roiManager.sigRoiAdded.connect(self.__roiAdded)
-
- # Add interactive mode actions
- for kind, icon, tooltip in (
- (roi_items.HorizontalLineROI, 'shape-horizontal',
- 'Enables horizontal line profile selection mode'),
- (roi_items.VerticalLineROI, 'shape-vertical',
- 'Enables vertical line profile selection mode'),
- (roi_items.LineROI, 'shape-diagonal',
- 'Enables line profile selection mode')):
- action = roiManager.getInteractionModeAction(kind)
- action.setIcon(icons.getQIcon(icon))
- action.setToolTip(tooltip)
- self.addAction(action)
-
- # Add clear action
- action = qt.QAction(icons.getQIcon('profile-clear'),
- 'Clear Profile', self)
- action.setToolTip('Clear the profile')
- action.setCheckable(False)
- action.triggered.connect(self.clearProfile)
- self.addAction(action)
-
- # Initialize color
- self._color = None
- self.setColor('red')
-
- # Listen to plot limits changed
- plot.getXAxis().sigLimitsChanged.connect(self.updateProfile)
- plot.getYAxis().sigLimitsChanged.connect(self.updateProfile)
-
- # Listen to plot scale
- plot.getXAxis().sigScaleChanged.connect(self.__plotAxisScaleChanged)
- plot.getYAxis().sigScaleChanged.connect(self.__plotAxisScaleChanged)
-
- self.setDefaultProfileWindowEnabled(True)
-
- def getProfilePoints(self, copy=True):
- """Returns the profile sampling points as (x, y) or None
-
- :param bool copy: True to get a copy,
- False to get internal arrays (do not modify)
- :rtype: Union[numpy.ndarray,None]
- """
- if self.__profile is None:
- return None
- else:
- return numpy.array(self.__profile[0], copy=copy)
-
- def getProfileValues(self, copy=True):
- """Returns the values of the profile or None
-
- :param bool copy: True to get a copy,
- False to get internal arrays (do not modify)
- :rtype: Union[numpy.ndarray,None]
- """
- if self.__profile is None:
- return None
- else:
- return numpy.array(self.__profile[1], copy=copy)
-
- def getProfileTitle(self):
- """Returns the profile title
-
- :rtype: str
- """
- return self.__profileTitle
-
- # Handle plot reference
-
- def __plotDestroyed(self, ref):
- """Handle finalization of PlotWidget
-
- :param ref: weakref to the plot
- """
- self._plotRef = None
- self.setEnabled(False) # Profile is pointless
- for action in self.actions(): # TODO useful?
- self.removeAction(action)
-
- def getPlotWidget(self):
- """The :class:`~silx.gui.plot.PlotWidget` associated to the toolbar.
-
- :rtype: Union[~silx.gui.plot.PlotWidget,None]
- """
- return None if self._plotRef is None else self._plotRef()
-
- def _getRoiManager(self):
- """Returns the used ROI manager
-
- :rtype: RegionOfInterestManager
- """
- return self._roiManagerRef()
-
- # Profile Plot
-
- def isDefaultProfileWindowEnabled(self):
- """Returns True if the default floating profile window is used
-
- :rtype: bool
- """
- return self.getDefaultProfileWindow() is not None
-
- def setDefaultProfileWindowEnabled(self, enabled):
- """Set whether to use or not the default floating profile window.
-
- :param bool enabled: True to use, False to disable
- """
- if self.isDefaultProfileWindowEnabled() != enabled:
- if enabled:
- self._profileWindow = ProfileMainWindow(self)
- self._profileWindow.sigClose.connect(self.clearProfile)
- self.sigProfileChanged.connect(self.__updateDefaultProfilePlot)
-
- else:
- self.sigProfileChanged.disconnect(self.__updateDefaultProfilePlot)
- self._profileWindow.sigClose.disconnect(self.clearProfile)
- self._profileWindow.close()
- self._profileWindow = None
-
- def getDefaultProfileWindow(self):
- """Returns the default floating profile window if in use else None.
-
- See :meth:`isDefaultProfileWindowEnabled`
-
- :rtype: Union[ProfileMainWindow,None]
- """
- return self._profileWindow
-
- def __updateDefaultProfilePlot(self):
- """Update the plot of the default profile window"""
- profileWindow = self.getDefaultProfileWindow()
- if profileWindow is None:
- return
-
- profilePlot = profileWindow.getPlot()
- if profilePlot is None:
- return
-
- profilePlot.clear()
- profilePlot.setGraphTitle(self.getProfileTitle())
-
- points = self.getProfilePoints(copy=False)
- values = self.getProfileValues(copy=False)
-
- if points is not None and values is not None:
- if (numpy.abs(points[-1, 0] - points[0, 0]) >
- numpy.abs(points[-1, 1] - points[0, 1])):
- xProfile = points[:, 0]
- profilePlot.getXAxis().setLabel('X')
- else:
- xProfile = points[:, 1]
- profilePlot.getXAxis().setLabel('Y')
-
- profilePlot.addCurve(
- xProfile, values, legend='Profile', color=self._color)
-
- self._showDefaultProfileWindow()
-
- def _showDefaultProfileWindow(self):
- """If profile window was created by this toolbar,
- try to avoid overlapping with the toolbar's parent window.
- """
- profileWindow = self.getDefaultProfileWindow()
- roiManager = self._getRoiManager()
- if profileWindow is None or roiManager is None:
- return
-
- if roiManager.isStarted() and not profileWindow.isVisible():
- profileWindow.show()
- profileWindow.raise_()
-
- window = self.window()
- winGeom = window.frameGeometry()
- qapp = qt.QApplication.instance()
- desktop = qapp.desktop()
- screenGeom = desktop.availableGeometry(self)
- spaceOnLeftSide = winGeom.left()
- spaceOnRightSide = screenGeom.width() - winGeom.right()
-
- frameGeometry = profileWindow.frameGeometry()
- profileWindowWidth = frameGeometry.width()
- if profileWindowWidth < spaceOnRightSide:
- # Place profile on the right
- profileWindow.move(winGeom.right(), winGeom.top())
- elif profileWindowWidth < spaceOnLeftSide:
- # Place profile on the left
- profileWindow.move(
- max(0, winGeom.left() - profileWindowWidth), winGeom.top())
-
- # Handle plot in log scale
-
- def __plotAxisScaleChanged(self, scale):
- """Handle change of axis scale in the plot widget"""
- plot = self.getPlotWidget()
- if plot is None:
- return
-
- xScale = plot.getXAxis().getScale()
- yScale = plot.getYAxis().getScale()
-
- if xScale == items.Axis.LINEAR and yScale == items.Axis.LINEAR:
- self.setEnabled(True)
-
- else:
- roiManager = self._getRoiManager()
- if roiManager is not None:
- roiManager.stop() # Stop interactive mode
-
- self.clearProfile()
- self.setEnabled(False)
-
- # Profile color
-
- def getColor(self):
- """Returns the color used for the profile and ROI
-
- :rtype: QColor
- """
- return qt.QColor.fromRgbF(*self._color)
-
- def setColor(self, color):
- """Set the color to use for ROI and profile.
-
- :param color:
- Either a color name, a QColor, a list of uint8 or float in [0, 1].
- """
- self._color = colors.rgba(color)
- roiManager = self._getRoiManager()
- if roiManager is not None:
- roiManager.setColor(self._color)
- for roi in roiManager.getRois():
- roi.setColor(self._color)
- self.updateProfile()
-
- # Handle ROI manager
-
- def __interactionFinished(self):
- """Handle end of interactive mode"""
- self.clearProfile()
-
- profileWindow = self.getDefaultProfileWindow()
- if profileWindow is not None:
- profileWindow.hide()
-
- def __roiAdded(self, roi):
- """Handle new ROI"""
- roi.setLabel('Profile')
- roi.setEditable(True)
-
- # Remove any other ROI
- roiManager = self._getRoiManager()
- if roiManager is not None:
- for regionOfInterest in list(roiManager.getRois()):
- if regionOfInterest is not roi:
- roiManager.removeRoi(regionOfInterest)
-
- def computeProfile(self, x0, y0, x1, y1):
- """Compute corresponding profile
-
- Override in subclass to compute profile
-
- :param float x0: Profile start point X coord
- :param float y0: Profile start point Y coord
- :param float x1: Profile end point X coord
- :param float y1: Profile end point Y coord
- :return: (points, values) profile data or None
- """
- return None
-
- def computeProfileTitle(self, x0, y0, x1, y1):
- """Compute corresponding plot title
-
- This can be overridden to change title behavior.
-
- :param float x0: Profile start point X coord
- :param float y0: Profile start point Y coord
- :param float x1: Profile end point X coord
- :param float y1: Profile end point Y coord
- :return: Title to use
- :rtype: str
- """
- if x0 == x1:
- title = 'X = %g; Y = [%g, %g]' % (x0, y0, y1)
- elif y0 == y1:
- title = 'Y = %g; X = [%g, %g]' % (y0, x0, x1)
- else:
- m = (y1 - y0) / (x1 - x0)
- b = y0 - m * x0
- title = 'Y = %g * X %+g' % (m, b)
-
- return title
-
- def updateProfile(self):
- """Update profile according to current ROI"""
- roiManager = self._getRoiManager()
- if roiManager is None:
- roi = None
- else:
- rois = roiManager.getRois()
- roi = None if len(rois) == 0 else rois[0]
-
- if roi is None:
- self._setProfile(profile=None, title='')
- return
-
- # Get end points
- if isinstance(roi, roi_items.LineROI):
- points = roi.getEndPoints()
- x0, y0 = points[0]
- x1, y1 = points[1]
- elif isinstance(roi, (roi_items.VerticalLineROI, roi_items.HorizontalLineROI)):
- plot = self.getPlotWidget()
- if plot is None:
- self._setProfile(profile=None, title='')
- return
-
- elif isinstance(roi, roi_items.HorizontalLineROI):
- x0, x1 = plot.getXAxis().getLimits()
- y0 = y1 = roi.getPosition()
-
- elif isinstance(roi, roi_items.VerticalLineROI):
- x0 = x1 = roi.getPosition()
- y0, y1 = plot.getYAxis().getLimits()
-
- else:
- raise RuntimeError('Unsupported ROI for profile: {}'.format(roi.__class__))
-
- if x1 < x0 or (x1 == x0 and y1 < y0):
- # Invert points
- x0, y0, x1, y1 = x1, y1, x0, y0
-
- profile = self.computeProfile(x0, y0, x1, y1)
- title = self.computeProfileTitle(x0, y0, x1, y1)
- self._setProfile(profile=profile, title=title)
-
- def _setProfile(self, profile=None, title=''):
- """Set profile data and emit signal.
-
- :param profile: points and profile values
- :param str title:
- """
- self.__profile = profile
- self.__profileTitle = title
-
- self.sigProfileChanged.emit()
-
- def clearProfile(self):
- """Clear the current line ROI and associated profile"""
- roiManager = self._getRoiManager()
- if roiManager is not None:
- roiManager.clear()
-
- self._setProfile(profile=None, title='')