diff options
Diffstat (limited to 'silx/gui/plot/tools/profile/ScatterProfileToolBar.py')
-rw-r--r-- | silx/gui/plot/tools/profile/ScatterProfileToolBar.py | 431 |
1 files changed, 0 insertions, 431 deletions
diff --git a/silx/gui/plot/tools/profile/ScatterProfileToolBar.py b/silx/gui/plot/tools/profile/ScatterProfileToolBar.py deleted file mode 100644 index fd21515..0000000 --- a/silx/gui/plot/tools/profile/ScatterProfileToolBar.py +++ /dev/null @@ -1,431 +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 profile tools for scatter plots. -""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "28/06/2018" - - -import logging -import threading -import time - -import numpy - -try: - from scipy.interpolate import LinearNDInterpolator -except ImportError: - LinearNDInterpolator = None - - # Fallback using local Delaunay and matplotlib interpolator - from silx.third_party.scipy_spatial import Delaunay - import matplotlib.tri - -from ._BaseProfileToolBar import _BaseProfileToolBar -from .... import qt -from ... import items - - -_logger = logging.getLogger(__name__) - - -# TODO support log scale - - -class _InterpolatorInitThread(qt.QThread): - """Thread building a scatter interpolator - - This works in greedy mode in that the signal is only emitted - when no other request is pending - """ - - sigInterpolatorReady = qt.Signal(object) - """Signal emitted whenever an interpolator is ready - - It provides a 3-tuple (points, values, interpolator) - """ - - _RUNNING_THREADS_TO_DELETE = [] - """Store reference of no more used threads but still running""" - - def __init__(self): - super(_InterpolatorInitThread, self).__init__() - self._lock = threading.RLock() - self._pendingData = None - self._firstFallbackRun = True - - def discard(self, obj=None): - """Wait for pending thread to complete and delete then - - Connect this to the destroyed signal of widget using this thread - """ - if self.isRunning(): - self.cancel() - self._RUNNING_THREADS_TO_DELETE.append(self) # Keep a reference - self.finished.connect(self.__finished) - - def __finished(self): - """Handle finished signal of threads to delete""" - try: - self._RUNNING_THREADS_TO_DELETE.remove(self) - except ValueError: - _logger.warning('Finished thread no longer in reference list') - - def request(self, points, values): - """Request new initialisation of interpolator - - :param numpy.ndarray points: Point coordinates (N, D) - :param numpy.ndarray values: Values the N points (1D array) - """ - with self._lock: - # Possibly replace already pending data - self._pendingData = points, values - - if not self.isRunning(): - self.start() - - def cancel(self): - """Cancel any running/pending requests""" - with self._lock: - self._pendingData = 'cancelled' - - def run(self): - """Run the init of the scatter interpolator""" - if LinearNDInterpolator is None: - self.run_matplotlib() - else: - self.run_scipy() - - def run_matplotlib(self): - """Run the init of the scatter interpolator""" - if self._firstFallbackRun: - self._firstFallbackRun = False - _logger.warning( - "scipy.spatial.LinearNDInterpolator not available: " - "Scatter plot interpolator initialisation can freeze the GUI.") - - while True: - with self._lock: - data = self._pendingData - self._pendingData = None - - if data in (None, 'cancelled'): - return - - points, values = data - - startTime = time.time() - try: - delaunay = Delaunay(points) - except: - _logger.warning( - "Cannot triangulate scatter data") - else: - with self._lock: - data = self._pendingData - - if data is not None: # Break point - _logger.info('Interpolator discarded after %f s', - time.time() - startTime) - else: - - x, y = points.T - triangulation = matplotlib.tri.Triangulation( - x, y, triangles=delaunay.simplices) - - interpolator = matplotlib.tri.LinearTriInterpolator( - triangulation, values) - - with self._lock: - data = self._pendingData - - if data is not None: - _logger.info('Interpolator discarded after %f s', - time.time() - startTime) - else: - # No other processing requested: emit the signal - _logger.info("Interpolator initialised in %f s", - time.time() - startTime) - - # Wrap interpolator to have same API as scipy's one - def wrapper(points): - return interpolator(*points.T) - - self.sigInterpolatorReady.emit( - (points, values, wrapper)) - - def run_scipy(self): - """Run the init of the scatter interpolator""" - while True: - with self._lock: - data = self._pendingData - self._pendingData = None - - if data in (None, 'cancelled'): - return - - points, values = data - - startTime = time.time() - try: - interpolator = LinearNDInterpolator(points, values) - except: - _logger.warning( - "Cannot initialise scatter profile interpolator") - else: - with self._lock: - data = self._pendingData - - if data is not None: # Break point - _logger.info('Interpolator discarded after %f s', - time.time() - startTime) - else: - # First call takes a while, do it here - interpolator([(0., 0.)]) - - with self._lock: - data = self._pendingData - - if data is not None: - _logger.info('Interpolator discarded after %f s', - time.time() - startTime) - else: - # No other processing requested: emit the signal - _logger.info("Interpolator initialised in %f s", - time.time() - startTime) - self.sigInterpolatorReady.emit( - (points, values, interpolator)) - - -class ScatterProfileToolBar(_BaseProfileToolBar): - """QToolBar providing scatter 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`. - """ - - def __init__(self, parent=None, plot=None, title='Scatter Profile'): - super(ScatterProfileToolBar, self).__init__(parent, plot, title) - - self.__nPoints = 1024 - self.__interpolator = None - self.__interpolatorCache = None # points, values, interpolator - - self.__initThread = _InterpolatorInitThread() - self.destroyed.connect(self.__initThread.discard) - self.__initThread.sigInterpolatorReady.connect( - self.__interpolatorReady) - - roiManager = self._getRoiManager() - if roiManager is None: - _logger.error( - "Error during scatter profile toolbar initialisation") - else: - roiManager.sigInteractiveModeStarted.connect( - self.__interactionStarted) - roiManager.sigInteractiveModeFinished.connect( - self.__interactionFinished) - if roiManager.isStarted(): - self.__interactionStarted(roiManager.getCurrentInteractionModeRoiClass()) - - def __interactionStarted(self, roiClass): - """Handle start of ROI interaction""" - plot = self.getPlotWidget() - if plot is None: - return - - plot.sigActiveScatterChanged.connect(self.__activeScatterChanged) - - scatter = plot._getActiveItem(kind='scatter') - legend = None if scatter is None else scatter.getLegend() - self.__activeScatterChanged(None, legend) - - def __interactionFinished(self): - """Handle end of ROI interaction""" - plot = self.getPlotWidget() - if plot is None: - return - - plot.sigActiveScatterChanged.disconnect(self.__activeScatterChanged) - - scatter = plot._getActiveItem(kind='scatter') - legend = None if scatter is None else scatter.getLegend() - self.__activeScatterChanged(legend, None) - - def __activeScatterChanged(self, previous, legend): - """Handle change of active scatter - - :param Union[str,None] previous: - :param Union[str,None] legend: - """ - self.__initThread.cancel() - - # Reset interpolator - self.__interpolator = None - - plot = self.getPlotWidget() - if plot is None: - _logger.error("Associated PlotWidget no longer exists") - - else: - if previous is not None: # Disconnect signal - scatter = plot.getScatter(previous) - if scatter is not None: - scatter.sigItemChanged.disconnect( - self.__scatterItemChanged) - - if legend is not None: - scatter = plot.getScatter(legend) - if scatter is None: - _logger.error("Cannot retrieve active scatter") - - else: - scatter.sigItemChanged.connect(self.__scatterItemChanged) - points = numpy.transpose(numpy.array(( - scatter.getXData(copy=False), - scatter.getYData(copy=False)))) - values = scatter.getValueData(copy=False) - - self.__updateInterpolator(points, values) - - # Refresh profile - self.updateProfile() - - def __scatterItemChanged(self, event): - """Handle update of active scatter plot item - - :param ItemChangedType event: - """ - if event == items.ItemChangedType.DATA: - self.__interpolator = None - scatter = self.sender() - if scatter is None: - _logger.error("Cannot retrieve updated scatter item") - - else: - points = numpy.transpose(numpy.array(( - scatter.getXData(copy=False), - scatter.getYData(copy=False)))) - values = scatter.getValueData(copy=False) - - self.__updateInterpolator(points, values) - - # Handle interpolator init thread - - def __updateInterpolator(self, points, values): - """Update used interpolator with new data""" - if (self.__interpolatorCache is not None and - len(points) == len(self.__interpolatorCache[0]) and - numpy.all(numpy.equal(self.__interpolatorCache[0], points)) and - numpy.all(numpy.equal(self.__interpolatorCache[1], values))): - # Reuse previous interpolator - _logger.info( - 'Scatter changed: Reuse previous interpolator') - self.__interpolator = self.__interpolatorCache[2] - - else: - # Interpolator needs update: Start background processing - _logger.info( - 'Scatter changed: Rebuild interpolator') - self.__interpolator = None - self.__interpolatorCache = None - self.__initThread.request(points, values) - - def __interpolatorReady(self, data): - """Handle end of init interpolator thread""" - points, values, interpolator = data - self.__interpolator = interpolator - self.__interpolatorCache = None if interpolator is None else data - self.updateProfile() - - def hasPendingOperations(self): - return self.__initThread.isRunning() - - # Number of points - - def getNPoints(self): - """Returns the number of points of the profiles - - :rtype: int - """ - return self.__nPoints - - def setNPoints(self, npoints): - """Set the number of points of the profiles - - :param int npoints: - """ - npoints = int(npoints) - if npoints < 1: - raise ValueError("Unsupported number of points: %d" % npoints) - else: - self.__nPoints = npoints - - # Overridden methods - - def computeProfileTitle(self, x0, y0, x1, y1): - """Compute corresponding plot title - - :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 self.hasPendingOperations(): - return 'Pre-processing data...' - - else: - return super(ScatterProfileToolBar, self).computeProfileTitle( - x0, y0, x1, y1) - - def computeProfile(self, x0, y0, x1, y1): - """Compute corresponding 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 - """ - if self.__interpolator is None: - return None - - nPoints = self.getNPoints() - - points = numpy.transpose(( - numpy.linspace(x0, x1, nPoints, endpoint=True), - numpy.linspace(y0, y1, nPoints, endpoint=True))) - - values = self.__interpolator(points) - - if not numpy.any(numpy.isfinite(values)): - return None # Profile outside convex hull - - return points, values |