diff options
Diffstat (limited to 'silx/gui/plot/stats/stats.py')
-rw-r--r-- | silx/gui/plot/stats/stats.py | 491 |
1 files changed, 0 insertions, 491 deletions
diff --git a/silx/gui/plot/stats/stats.py b/silx/gui/plot/stats/stats.py deleted file mode 100644 index a753989..0000000 --- a/silx/gui/plot/stats/stats.py +++ /dev/null @@ -1,491 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2017-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 :class:`Scatter` item of the :class:`Plot`. -""" - -__authors__ = ["H. Payno"] -__license__ = "MIT" -__date__ = "06/06/2018" - - -import numpy -from silx.gui.plot.items.curve import Curve as CurveItem -from silx.gui.plot.items.image import ImageBase as ImageItem -from silx.gui.plot.items.scatter import Scatter as ScatterItem -from silx.gui.plot.items.histogram import Histogram as HistogramItem -from silx.math.combo import min_max -from collections import OrderedDict -import logging - -logger = logging.getLogger(__name__) - - -class Stats(OrderedDict): - """Class to define a set of statistic relative to a dataset - (image, curve...). - - The goal of this class is to avoid multiple recalculation of some - basic operations such as filtering data area where the statistics has to - be apply. - Min and max are also stored because they can be used several time. - - :param List statslist: List of the :class:`Stat` object to be computed. - """ - def __init__(self, statslist=None): - OrderedDict.__init__(self) - _statslist = statslist if not None else [] - if statslist is not None: - for stat in _statslist: - self.add(stat) - - def calculate(self, item, plot, onlimits): - """ - Call all :class:`Stat` object registred and return the result of the - computation. - - :param item: the item for which we want statistics - :param plot: plot containing the item - :param bool onlimits: True if we want to apply statistic only on - visible data. - :return dict: dictionary with :class:`Stat` name as ket and result - of the calculation as value - """ - res = {} - if isinstance(item, CurveItem): - context = _CurveContext(item, plot, onlimits) - elif isinstance(item, ImageItem): - context = _ImageContext(item, plot, onlimits) - elif isinstance(item, ScatterItem): - context = _ScatterContext(item, plot, onlimits) - elif isinstance(item, HistogramItem): - context = _HistogramContext(item, plot, onlimits) - else: - raise ValueError('Item type not managed') - for statName, stat in list(self.items()): - if context.kind not in stat.compatibleKinds: - logger.debug('kind %s not managed by statistic %s' - % (context.kind, stat.name)) - res[statName] = None - else: - res[statName] = stat.calculate(context) - return res - - def __setitem__(self, key, value): - assert isinstance(value, StatBase) - OrderedDict.__setitem__(self, key, value) - - def add(self, stat): - self.__setitem__(key=stat.name, value=stat) - - -class _StatsContext(object): - """ - The context is designed to be a simple buffer and avoid repetition of - calculations that can appear during stats evaluation. - - .. warning:: this class gives access to the data to be used for computation - . It deal with filtering data visible by the user on plot. - The filtering is a simple data sub-sampling. No interpolation - is made to fit data to boundaries. - - :param item: the item for which we want to compute the context - :param str kind: the kind of the item - :param plot: the plot containing the item - :param bool onlimits: True if we want to apply statistic only on - visible data. - """ - def __init__(self, item, kind, plot, onlimits): - assert item - assert plot - assert type(onlimits) is bool - self.kind = kind - self.min = None - self.max = None - self.data = None - self.values = None - self.createContext(item, plot, onlimits) - - def createContext(self, item, plot, onlimits): - raise NotImplementedError("Base class") - - -class _CurveContext(_StatsContext): - """ - StatsContext for :class:`Curve` - - :param item: the item for which we want to compute the context - :param plot: the plot containing the item - :param bool onlimits: True if we want to apply statistic only on - visible data. - """ - def __init__(self, item, plot, onlimits): - _StatsContext.__init__(self, kind='curve', item=item, - plot=plot, onlimits=onlimits) - - def createContext(self, item, plot, onlimits): - xData, yData = item.getData(copy=True)[0:2] - - if onlimits: - minX, maxX = plot.getXAxis().getLimits() - yData = yData[(minX <= xData) & (xData <= maxX)] - xData = xData[(minX <= xData) & (xData <= maxX)] - - self.xData = xData - self.yData = yData - if len(yData) > 0: - self.min, self.max = min_max(yData) - else: - self.min, self.max = None, None - self.data = (xData, yData) - self.values = yData - - -class _HistogramContext(_StatsContext): - """ - StatsContext for :class:`Curve` - - :param item: the item for which we want to compute the context - :param plot: the plot containing the item - :param bool onlimits: True if we want to apply statistic only on - visible data. - """ - def __init__(self, item, plot, onlimits): - _StatsContext.__init__(self, kind='histogram', item=item, - plot=plot, onlimits=onlimits) - - def createContext(self, item, plot, onlimits): - xData, edges = item.getData(copy=True)[0:2] - yData = item._revertComputeEdges(x=edges, histogramType=item.getAlignment()) - if onlimits: - minX, maxX = plot.getXAxis().getLimits() - yData = yData[(minX <= xData) & (xData <= maxX)] - xData = xData[(minX <= xData) & (xData <= maxX)] - - self.xData = xData - self.yData = yData - if len(yData) > 0: - self.min, self.max = min_max(yData) - else: - self.min, self.max = None, None - self.data = (xData, yData) - self.values = yData - - -class _ScatterContext(_StatsContext): - """ - StatsContext for :class:`Scatter` - - :param item: the item for which we want to compute the context - :param plot: the plot containing the item - :param bool onlimits: True if we want to apply statistic only on - visible data. - """ - def __init__(self, item, plot, onlimits): - _StatsContext.__init__(self, kind='scatter', item=item, plot=plot, - onlimits=onlimits) - - def createContext(self, item, plot, onlimits): - xData, yData, valueData, xerror, yerror = item.getData(copy=True) - assert plot - if onlimits: - minX, maxX = plot.getXAxis().getLimits() - minY, maxY = plot.getYAxis().getLimits() - # filter on X axis - valueData = valueData[(minX <= xData) & (xData <= maxX)] - yData = yData[(minX <= xData) & (xData <= maxX)] - xData = xData[(minX <= xData) & (xData <= maxX)] - # filter on Y axis - valueData = valueData[(minY <= yData) & (yData <= maxY)] - xData = xData[(minY <= yData) & (yData <= maxY)] - yData = yData[(minY <= yData) & (yData <= maxY)] - if len(valueData) > 0: - self.min, self.max = min_max(valueData) - else: - self.min, self.max = None, None - self.data = (xData, yData, valueData) - self.values = valueData - - -class _ImageContext(_StatsContext): - """ - StatsContext for :class:`ImageBase` - - :param item: the item for which we want to compute the context - :param plot: the plot containing the item - :param bool onlimits: True if we want to apply statistic only on - visible data. - """ - def __init__(self, item, plot, onlimits): - _StatsContext.__init__(self, kind='image', item=item, - plot=plot, onlimits=onlimits) - - def createContext(self, item, plot, onlimits): - self.origin = item.getOrigin() - self.scale = item.getScale() - self.data = item.getData() - - if onlimits: - minX, maxX = plot.getXAxis().getLimits() - minY, maxY = plot.getYAxis().getLimits() - - XMinBound = int((minX - self.origin[0]) / self.scale[0]) - YMinBound = int((minY - self.origin[1]) / self.scale[1]) - XMaxBound = int((maxX - self.origin[0]) / self.scale[0]) - YMaxBound = int((maxY - self.origin[1]) / self.scale[1]) - - XMinBound = max(XMinBound, 0) - YMinBound = max(YMinBound, 0) - - if XMaxBound <= XMinBound or YMaxBound <= YMinBound: - return self.noDataSelected() - data = item.getData() - self.data = data[YMinBound:YMaxBound + 1, XMinBound:XMaxBound + 1] - else: - self.data = item.getData() - - if self.data.size > 0: - self.min, self.max = min_max(self.data) - else: - self.min, self.max = None, None - self.values = self.data - - -BASIC_COMPATIBLE_KINDS = { - 'curve': CurveItem, - 'image': ImageItem, - 'scatter': ScatterItem, - 'histogram': HistogramItem, -} - - -class StatBase(object): - """ - Base class for defining a statistic. - - :param str name: the name of the statistic. Must be unique. - :param compatibleKinds: the kind of items (curve, scatter...) for which - the statistic apply. - :rtype: List or tuple - """ - def __init__(self, name, compatibleKinds=BASIC_COMPATIBLE_KINDS, description=None): - self.name = name - self.compatibleKinds = compatibleKinds - self.description = description - - def calculate(self, context): - """ - compute the statistic for the given :class:`StatsContext` - - :param context: - :return dict: key is stat name, statistic computed is the dict value - """ - raise NotImplementedError('Base class') - - def getToolTip(self, kind): - """ - If necessary add a tooltip for a stat kind - - :param str kinf: the kind of item the statistic is compute for. - :return: tooltip or None if no tooltip - """ - return None - - -class Stat(StatBase): - """ - Create a StatBase class based on a function pointer. - - :param str name: name of the statistic. Used as id - :param fct: function which should have as unique mandatory parameter the - data. Should be able to adapt to all `kinds` defined as - compatible - :param tuple kinds: the compatible item kinds of the function (curve, - image...) - """ - def __init__(self, name, fct, kinds=BASIC_COMPATIBLE_KINDS): - StatBase.__init__(self, name, kinds) - self._fct = fct - - def calculate(self, context): - if context.kind in self.compatibleKinds: - return self._fct(context.values) - else: - raise ValueError('Kind %s not managed by %s' - '' % (context.kind, self.name)) - - -class StatMin(StatBase): - """ - Compute the minimal value on data - """ - def __init__(self): - StatBase.__init__(self, name='min') - - def calculate(self, context): - return context.min - - -class StatMax(StatBase): - """ - Compute the maximal value on data - """ - def __init__(self): - StatBase.__init__(self, name='max') - - def calculate(self, context): - return context.max - - -class StatDelta(StatBase): - """ - Compute the delta between minimal and maximal on data - """ - def __init__(self): - StatBase.__init__(self, name='delta') - - def calculate(self, context): - return context.max - context.min - - -class StatCoordMin(StatBase): - """ - Compute the first coordinates of the data minimal value - """ - def __init__(self): - StatBase.__init__(self, name='coords min') - - def calculate(self, context): - if context.kind in ('curve', 'histogram'): - return context.xData[numpy.argmin(context.yData)] - elif context.kind == 'scatter': - xData, yData, valueData = context.data - return (xData[numpy.argmin(valueData)], - yData[numpy.argmin(valueData)]) - elif context.kind == 'image': - scaleX, scaleY = context.scale - originX, originY = context.origin - index1D = numpy.argmin(context.data) - ySize = (context.data.shape[1]) - x = index1D % context.data.shape[1] - y = (index1D - x) / ySize - x = x * scaleX + originX - y = y * scaleY + originY - return (x, y) - else: - raise ValueError('kind not managed') - - def getToolTip(self, kind): - if kind in ('scatter', 'image'): - return '(x, y)' - else: - return None - -class StatCoordMax(StatBase): - """ - Compute the first coordinates of the data minimal value - """ - def __init__(self): - StatBase.__init__(self, name='coords max') - - def calculate(self, context): - if context.kind in ('curve', 'histogram'): - return context.xData[numpy.argmax(context.yData)] - elif context.kind == 'scatter': - xData, yData, valueData = context.data - return (xData[numpy.argmax(valueData)], - yData[numpy.argmax(valueData)]) - elif context.kind == 'image': - scaleX, scaleY = context.scale - originX, originY = context.origin - index1D = numpy.argmax(context.data) - ySize = (context.data.shape[1]) - x = index1D % context.data.shape[1] - y = (index1D - x) / ySize - x = x * scaleX + originX - y = y * scaleY + originY - return (x, y) - else: - raise ValueError('kind not managed') - - def getToolTip(self, kind): - if kind in ('scatter', 'image'): - return '(x, y)' - else: - return None - -class StatCOM(StatBase): - """ - Compute data center of mass - """ - def __init__(self): - StatBase.__init__(self, name='COM', description='Center of mass') - - def calculate(self, context): - if context.kind in ('curve', 'histogram'): - xData, yData = context.data - deno = numpy.sum(yData).astype(numpy.float32) - if deno == 0.: - return numpy.nan - else: - return numpy.sum(xData * yData).astype(numpy.float32) / deno - elif context.kind == 'scatter': - xData, yData, values = context.data - deno = numpy.sum(values).astype(numpy.float32) - if deno == 0.: - return numpy.nan, numpy.nan - else: - xcom = numpy.sum(xData * values).astype(numpy.float32) / deno - ycom = numpy.sum(yData * values).astype(numpy.float32) / deno - return (xcom, ycom) - elif context.kind == 'image': - yData = numpy.sum(context.data, axis=1) - xData = numpy.sum(context.data, axis=0) - dataXRange = range(context.data.shape[1]) - dataYRange = range(context.data.shape[0]) - xScale, yScale = context.scale - xOrigin, yOrigin = context.origin - - denoY = numpy.sum(yData) - if denoY == 0.: - ycom = numpy.nan - else: - ycom = numpy.sum(yData * dataYRange) / denoY - ycom = ycom * yScale + yOrigin - - denoX = numpy.sum(xData) - if denoX == 0.: - xcom = numpy.nan - else: - xcom = numpy.sum(xData * dataXRange) / denoX - xcom = xcom * xScale + xOrigin - return (xcom, ycom) - else: - raise ValueError('kind not managed') - - def getToolTip(self, kind): - if kind in ('scatter', 'image'): - return '(x, y)' - else: - return None |