summaryrefslogtreecommitdiff
path: root/silx/math/combo/combo.pyx
diff options
context:
space:
mode:
Diffstat (limited to 'silx/math/combo/combo.pyx')
-rw-r--r--silx/math/combo/combo.pyx238
1 files changed, 238 insertions, 0 deletions
diff --git a/silx/math/combo/combo.pyx b/silx/math/combo/combo.pyx
new file mode 100644
index 0000000..0390bce
--- /dev/null
+++ b/silx/math/combo/combo.pyx
@@ -0,0 +1,238 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016-2017 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 combination of statistics as single operation.
+
+For now it provides min/max (and optionally positive min) and indices
+of first occurences (i.e., argmin/argmax) in a single pass.
+"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "20/12/2016"
+
+cimport cython
+
+# Replacement from libc.math cimport isnan
+# which is not available on Windows for Python2.7
+cdef extern from "isnan.h":
+ bint isnan(double x) nogil
+
+import numpy
+
+
+ctypedef fused _number:
+ float
+ double
+ signed char
+ signed short
+ signed int
+ signed long
+ unsigned char
+ unsigned short
+ unsigned int
+ unsigned long long
+
+
+class _MinMaxResult(object):
+ """Object storing result from :func:`min_max`"""
+
+ def __init__(self, minimum, min_pos, maximum,
+ argmin, argmin_pos, argmax):
+ self._minimum = minimum
+ self._min_positive = min_pos
+ self._maximum = maximum
+
+ self._argmin = argmin
+ self._argmin_positive = argmin_pos
+ self._argmax = argmax
+
+ minimum = property(
+ lambda self: self._minimum,
+ doc="Minimum value of the array")
+ maximum = property(
+ lambda self: self._maximum,
+ doc="Maximum value of the array")
+
+ argmin = property(
+ lambda self: self._argmin,
+ doc="Index of the first occurence of the minimum value")
+ argmax = property(
+ lambda self: self._argmax,
+ doc="Index of the first occurence of the maximum value")
+
+ min_positive = property(
+ lambda self: self._min_positive,
+ doc="""Strictly positive minimum value
+
+ It is None if no value is strictly positive.
+ """)
+ argmin_positive = property(
+ lambda self: self._argmin_positive,
+ doc="""Index of the strictly positive minimum value.
+
+ It is None if no value is strictly positive.
+ It is the index of the first occurence.""")
+
+ def __getitem__(self, key):
+ if key == 0:
+ return self.minimum
+ elif key == 1:
+ return self.maximum
+ else:
+ raise IndexError("Index out of range")
+
+
+@cython.initializedcheck(False)
+@cython.boundscheck(False)
+@cython.wraparound(False)
+def _min_max(_number[::1] data, bint min_positive=False):
+ """See :func:`min_max` for documentation."""
+ cdef:
+ _number value, minimum, minpos, maximum
+ unsigned int length
+ unsigned int index = 0
+ unsigned int min_index = 0
+ unsigned int min_pos_index = 0
+ unsigned int max_index = 0
+
+ length = len(data)
+
+ if length == 0:
+ raise ValueError('Zero-size array')
+
+ with nogil:
+ # Init starting values
+ value = data[0]
+ minimum = value
+ maximum = value
+ if min_positive and value > 0:
+ min_pos = value
+ else:
+ min_pos = 0
+
+ if _number in cython.floating:
+ # For floating, loop until first not NaN value
+ for index in range(length):
+ value = data[index]
+ if not isnan(value):
+ minimum = value
+ min_index = index
+ maximum = value
+ max_index = index
+ break
+
+ if not min_positive:
+ for index in range(index, length):
+ value = data[index]
+ if value > maximum:
+ maximum = value
+ max_index = index
+ elif value < minimum:
+ minimum = value
+ min_index = index
+
+ else:
+ # Loop until min_pos is defined
+ for index in range(index, length):
+ value = data[index]
+ if value > maximum:
+ maximum = value
+ max_index = index
+ elif value < minimum:
+ minimum = value
+ min_index = index
+
+ if value > 0:
+ min_pos = value
+ min_pos_index = index
+ break
+
+ # Loop until the end
+ for index in range(index+1, length):
+ value = data[index]
+ if value > maximum:
+ maximum = value
+ max_index = index
+ else:
+ if value < minimum:
+ minimum = value
+ min_index = index
+
+ if value > 0 and value < min_pos:
+ min_pos = value
+ min_pos_index = index
+
+ return _MinMaxResult(minimum,
+ min_pos if min_pos > 0 else None,
+ maximum,
+ min_index,
+ min_pos_index if min_pos > 0 else None,
+ max_index)
+
+
+def min_max(data not None, bint min_positive=False):
+ """Returns min, max and optionally strictly positive min of data.
+
+ It also computes the indices of first occurence of min/max.
+
+ NaNs are ignored while computing min/max unless all data is NaNs,
+ in which case returned min/max are NaNs.
+
+ Examples:
+
+ >>> import numpy
+ >>> data = numpy.arange(10)
+
+ Usage as a function returning min and max:
+
+ >>> min_, max_ = min_max(data)
+
+ Usage as a function returning a result object to access all information:
+
+ >>> result = min_max(data) # Do not get positive min
+ >>> result.minimum, result.argmin
+ 0, 0
+ >>> result.maximum, result.argmax
+ 9, 10
+ >>> result.min_positive, result.argmin_positive # Not computed
+ None, None
+
+ Getting strictly positive min information:
+
+ >>> result = min_max(data, min_positive=True)
+ >>> result.min_positive, result.argmin_positive # Computed
+ 1, 1
+
+ :param data: Array-like dataset
+ :param bool min_positive: True to compute the positive min and argmin
+ Default: False.
+ :returns: An object with minimum, maximum and min_positive attributes
+ and the indices of first occurence in the flattened data:
+ argmin, argmax and argmin_positive attributes.
+ If all data is <= 0 or min_positive argument is False, then
+ min_positive and argmin_positive are None.
+ :raises: ValueError if data is empty
+ """
+ return _min_max(numpy.ascontiguousarray(data).ravel(), min_positive)