diff options
Diffstat (limited to 'silx/math')
81 files changed, 0 insertions, 24358 deletions
diff --git a/silx/math/__init__.py b/silx/math/__init__.py deleted file mode 100644 index d8b7d81..0000000 --- a/silx/math/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-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 package provides some processing functions for 1D, 2D, 3D or nD arrays. - -For additional processing functions dedicated to 2D images, -see the silx.image package. -For OpenCL-based processing functions see the silx.opencl package. - -See silx documentation: http://www.silx.org/doc/silx/latest/ -""" - -__authors__ = ["D. Naudet", "V.A. Sole", "P. Knobel"] -__license__ = "MIT" -__date__ = "11/05/2017" - -from .histogram import Histogramnd # noqa -from .histogram import HistogramndLut # noqa -from .medianfilter import medfilt, medfilt1d, medfilt2d diff --git a/silx/math/calibration.py b/silx/math/calibration.py deleted file mode 100644 index 658e2dc..0000000 --- a/silx/math/calibration.py +++ /dev/null @@ -1,180 +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 classes to calibrate data. - -Classes -------- - -- :class:`NoCalibration` -- :class:`LinearCalibration` -- :class:`ArrayCalibration` - -""" -import numpy - - -class AbstractCalibration(object): - """A calibration is a transformation to be applied to an axis (i.e. a 1D array). - - """ - def __init__(self): - super(AbstractCalibration, self).__init__() - - def __call__(self, x): - """Apply calibration to an axis or to a value. - - :param x: Axis (1-D array), or value""" - raise NotImplementedError( - "AbstractCalibration can not be used directly. " + - "You must subclass it and implement __call__") - - def is_affine(self): - """Returns True for an affine calibration of the form - :math:`x \\mapsto a + b * x`, or else False. - """ - return False - - def get_slope(self): - raise NotImplementedError( - "get_slope is implemented only for affine calibrations") - - -class NoCalibration(AbstractCalibration): - """No calibration :math:`x \\mapsto x` - """ - def __init__(self): - super(NoCalibration, self).__init__() - - def __call__(self, x): - return x - - def is_affine(self): - return True - - def get_slope(self): - return 1. - - -class LinearCalibration(AbstractCalibration): - """Linear calibration :math:`x \\mapsto a + b x`, - where *a* is the y-intercept and *b* is the slope. - - :param y_intercept: y-intercept - :param slope: Slope of the affine transformation - """ - def __init__(self, y_intercept, slope): - super(LinearCalibration, self).__init__() - self.constant = y_intercept - self.slope = slope - - def __call__(self, x): - return self.constant + self.slope * x - - def is_affine(self): - return True - - def get_slope(self): - return self.slope - - -class ArrayCalibration(AbstractCalibration): - """One-to-one mapping calibration, defined by an array *x'*, - such as :math:`x \\mapsto x'`. - - This calibration can only be applied to x arrays of the same length as the - calibration array *x'*. - It is typically applied to an axis of indices or - channels (:math:`0, 1, ..., n-1`). - - :param x1: Calibration array""" - def __init__(self, x1): - super(ArrayCalibration, self).__init__() - if not isinstance(x1, (list, tuple)) and not hasattr(x1, "shape"): - raise TypeError( - "The calibration array must be a sequence (list, dataset, array)") - self.calibration_array = numpy.array(x1) - self._is_affine = None - - def __call__(self, x): - # calibrate the entire axis - if isinstance(x, (list, tuple, numpy.ndarray)) and \ - len(self.calibration_array) == len(x): - return self.calibration_array - # calibrate one value, by index - if isinstance(x, int) and x < len(self.calibration_array): - return self.calibration_array[x] - raise ValueError("ArrayCalibration must be applied to array of same size " - "or to index.") - - def is_affine(self): - """If all values in the calibration array are regularly spaced, - return True.""" - if self._is_affine is None: - delta_x = self.calibration_array[1:] - self.calibration_array[:-1] - # use a less strict relative tolerance to account for rounding errors - # e.g. when using float64 into float32 (see #1823) - if not numpy.isclose(delta_x, delta_x[0], rtol=1e-4).all(): - self._is_affine = False - else: - self._is_affine = True - return self._is_affine - - def get_slope(self): - """If the calibration array is regularly spaced, return the spacing.""" - if not self.is_affine(): - raise AttributeError( - "get_slope only makes sense for affine transformations" - ) - return self.calibration_array[1] - self.calibration_array[0] - - -class FunctionCalibration(AbstractCalibration): - """Calibration defined by a function *f*, such as :math:`x \\mapsto f(x)`*. - - :param function: Calibration function""" - def __init__(self, function, is_affine=False): - super(FunctionCalibration, self).__init__() - if not hasattr(function, "__call__"): - raise TypeError("The calibration function must be a callable") - self.function = function - self._is_affine = is_affine - - def __call__(self, x): - return self.function(x) - - def is_affine(self): - """Return True if calibration is affine. - This is False by default, unless the object is instantiated with - ``is_affine=True``.""" - return self._is_affine - - def get_slope(self): - """If the calibration array is regularly spaced, return the spacing.""" - if not self.is_affine(): - raise AttributeError( - "get_slope only makes sense for affine transformations" - ) - # fixme: what if function is not defined at x=1 or x=2? - return self.function(2) - self.function(1) diff --git a/silx/math/chistogramnd.pyx b/silx/math/chistogramnd.pyx deleted file mode 100644 index 8484f35..0000000 --- a/silx/math/chistogramnd.pyx +++ /dev/null @@ -1,1251 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-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. -# -# ############################################################################*/ - -__authors__ = ["D. Naudet"] -__license__ = "MIT" -__date__ = "02/10/2017" - -cimport numpy as cnumpy # noqa -cimport cython -import numpy as np - -cimport silx.math.histogramnd_c as histogramnd_c - - -def chistogramnd(sample, - histo_range, - n_bins, - weights=None, - weight_min=None, - weight_max=None, - last_bin_closed=False, - histo=None, - weighted_histo=None, - wh_dtype=None): - """Computes the multidimensional histogram of some data. - - :param sample: - The data to be histogrammed. - Its shape must be either - (N,) if it contains one dimensional coordinates, - or an (N,D) array where the rows are the - coordinates of points in a D dimensional space. - The following dtypes are supported : :class:`numpy.float64`, - :class:`numpy.float32`, :class:`numpy.int32`. - - .. warning:: if sample is not a C_CONTIGUOUS ndarray (e.g : a non - contiguous slice) then histogramnd will have to do make an internal - copy. - :type sample: :class:`numpy.array` - - :param histo_range: - A (N, 2) array containing the histogram range along each dimension, - where N is the sample's number of dimensions. - :type histo_range: array_like - - :param n_bins: - The number of bins : - * a scalar (same number of bins for all dimensions) - * a D elements array (number of bins for each dimensions) - :type n_bins: scalar or array_like - - :param weights: - A N elements numpy array of values associated with - each sample. - The values of the *weighted_histo* array - returned by the function are equal to the sum of - the weights associated with the samples falling - into each bin. - The following dtypes are supported : :class:`numpy.float64`, - :class:`numpy.float32`, :class:`numpy.int32`. - - .. note:: If None, the weighted histogram returned will be None. - :type weights: *optional*, :class:`numpy.array` - - :param weight_min: - Use this parameter to filter out all samples whose - weights are lower than this value. - - .. note:: This value will be cast to the same type - as *weights*. - :type weight_min: *optional*, scalar - - :param weight_max: - Use this parameter to filter out all samples whose - weights are higher than this value. - - .. note:: This value will be cast to the same type - as *weights*. - - :type weight_max: *optional*, scalar - - :param last_bin_closed: - By default the last bin is half - open (i.e.: [x,y) ; x included, y - excluded), like all the other bins. - Set this parameter to true if you want - the LAST bin to be closed. - :type last_bin_closed: *optional*, :class:`python.boolean` - - :param histo: - Use this parameter if you want to pass your - own histogram array instead of the one - created by this function. New values - will be added to this array. The returned array - will then be this one (same reference). - - .. warning:: If the histo array was created by a previous - call to histogramnd then the user is - responsible for providing the same parameters - (*n_bins*, *histo_range*, ...). - :type histo: *optional*, :class:`numpy.array` - - :param weighted_histo: - Use this parameter if you want to pass your - own weighted histogram array instead of - the created by this function. New - values will be added to this array. The returned array - will then be this one (same reference). - - .. warning:: If the weighted_histo array was created by a previous - call to histogramnd then the user is - responsible for providing the same parameters - (*n_bins*, *histo_range*, ...). - - .. warning:: if weighted_histo is not a C_CONTIGUOUS ndarray (e.g : a - non contiguous slice) then histogramnd will have to do make an - internal copy. - :type weighted_histo: *optional*, :class:`numpy.array` - - :param wh_dtype: type of the weighted histogram array. This parameter is - ignored if *weighted_histo* is provided. If not provided, the - weighted histogram array will contain values of the same type as - *weights*. Allowed values are : `numpu.double` and `numpy.float32`. - :type wh_dtype: *optional*, numpy data type - - :return: Histogram (bin counts, always returned), weighted histogram of - the sample (or *None* if weights is *None*) and bin edges for each - dimension. - :rtype: *tuple* (:class:`numpy.array`, :class:`numpy.array`, `tuple`) or - (:class:`numpy.array`, None, `tuple`) - """ - - if wh_dtype is None: - wh_dtype = np.double - elif wh_dtype not in (np.double, np.float32): - raise ValueError('<wh_dtype> type not supported : {0}.'.format(wh_dtype)) - - if (weighted_histo is not None and - weighted_histo.flags['C_CONTIGUOUS'] is False): - raise ValueError('<weighted_histo> must be a C_CONTIGUOUS numpy array.') - - if histo is not None and histo.flags['C_CONTIGUOUS'] is False: - raise ValueError('<histo> must be a C_CONTIGUOUS numpy array.') - - s_shape = sample.shape - - n_dims = 1 if len(s_shape) == 1 else s_shape[1] - - if weights is not None: - w_shape = weights.shape - - # making sure the sample and weights sizes are coherent - # 2 different cases : 2D sample (N,M) and 1D (N) - if len(w_shape) != 1 or w_shape[0] != s_shape[0]: - raise ValueError('<weights> must be an array whose length ' - 'is equal to the number of samples.') - - weights_type = weights.dtype - else: - weights_type = None - - # just in case those arent numpy arrays - # (this allows the user to provide native python lists, - # => easier for testing) - i_histo_range = histo_range - histo_range = np.array(histo_range) - err_histo_range = False - - if n_dims == 1: - if histo_range.shape == (2,): - pass - elif histo_range.shape == (1, 2): - histo_range.shape = -1 - else: - err_histo_range = True - elif n_dims != 1 and histo_range.shape != (n_dims, 2): - err_histo_range = True - - if err_histo_range: - raise ValueError('<histo_range> error : expected {n_dims} sets of ' - 'lower and upper bin edges, ' - 'got the following instead : {histo_range}. ' - '(provided <sample> contains ' - '{n_dims}D values)' - ''.format(histo_range=i_histo_range, - n_dims=n_dims)) - - # check range value - if np.inf in histo_range: - raise ValueError('Range parameter should be finite value') - if np.nan in histo_range: - raise ValueError('Range value can\'t be nan') - - # checking n_bins size - n_bins = np.array(n_bins, ndmin=1) - if len(n_bins) == 1: - n_bins = np.tile(n_bins, n_dims) - elif n_bins.shape != (n_dims,): - raise ValueError('n_bins must be either a scalar (same number ' - 'of bins for all dimensions) or ' - 'an array (number of bins for each ' - 'dimension).') - - # checking if None is in n_bins, otherwise a rather cryptic - # exception is thrown when calling np.zeros - # also testing for negative/null values - if np.any(np.equal(n_bins, None)) or np.any(n_bins <= 0): - raise ValueError('<n_bins> : only positive values allowed.') - - output_shape = tuple(n_bins) - - # checking the histo array, if provided - if histo is None: - histo = np.zeros(output_shape, dtype=np.uint32) - else: - if histo.shape != output_shape: - raise ValueError('Provided <histo> array doesn\'t have ' - 'a shape compatible with <n_bins> ' - ': should be {0} instead of {1}.' - ''.format(output_shape, histo.shape)) - if histo.dtype != np.uint32: - raise ValueError('Provided <histo> array doesn\'t have ' - 'the expected type ' - ': should be {0} instead of {1}.' - ''.format(np.uint32, histo.dtype)) - - # checking the weighted_histo array, if provided - if weights_type is None: - # no weights provided, not creating the weighted_histo array - weighted_histo = None - elif weighted_histo is None: - # weights provided, but no weighted_histo, creating it - if wh_dtype is None: - wh_dtype = weights_type - weighted_histo = np.zeros(output_shape, dtype=wh_dtype) - else: - # weighted_histo provided, checking shape/dtype - if weighted_histo.shape != output_shape: - raise ValueError('Provided <weighted_histo> array doesn\'t have ' - 'a shape compatible with <n_bins> ' - ': should be {0} instead of {1}.' - ''.format(output_shape, weighted_histo.shape)) - if (weighted_histo.dtype != np.float64 and - weighted_histo.dtype != np.float32): - raise ValueError('Provided <weighted_histo> array doesn\'t have ' - 'the expected type ' - ': should be {0} or {1} instead of {2}.' - ''.format(np.double, - np.float32, - weighted_histo.dtype)) - - option_flags = 0 - - if weight_min is not None: - option_flags |= histogramnd_c.HISTO_WEIGHT_MIN - else: - weight_min = 0 - - if weight_max is not None: - option_flags |= histogramnd_c.HISTO_WEIGHT_MAX - else: - weight_max = 0 - - if last_bin_closed is not None and last_bin_closed: - option_flags |= histogramnd_c.HISTO_LAST_BIN_CLOSED - - sample_type = sample.dtype - sample_type = sample_type.newbyteorder('N') - - n_elem = sample.size // n_dims - - bin_edges = np.zeros(n_bins.sum() + n_bins.size, dtype=np.double) - - # wanted to store the functions in a dict (with the supported types - # as keys, but I couldn't find a way to make it work with cdef - # functions. so I have to explicitly list them all... - - def raise_unsupported_type(): - raise TypeError('Case not supported - sample:{0} ' - 'and weights:{1}.' - ''.format(sample_type, weights_type)) - - sample_c = np.ascontiguousarray(sample.reshape((sample.size,)), - dtype=sample_type) - - weights_c = (np.ascontiguousarray(weights.reshape((weights.size,)), - dtype=weights.dtype.newbyteorder('N')) - if weights is not None else None) - - histo_range_c = np.ascontiguousarray(histo_range.reshape((histo_range.size,)), - dtype=np.double) - - n_bins_c = np.ascontiguousarray(n_bins.reshape((n_bins.size,)), - dtype=np.int32) - - histo_c = histo.reshape((histo.size,)) - - if weighted_histo is not None: - cumul_c = weighted_histo.reshape((weighted_histo.size,)) - else: - cumul_c = None - - bin_edges_c = np.ascontiguousarray(bin_edges.reshape((bin_edges.size,)), - dtype=bin_edges.dtype.newbyteorder('N')) - - rc = 0 - - if weighted_histo is None or weighted_histo.dtype == np.double: - - if sample_type == np.float64: - - if weights_type == np.float64 or weights_type is None: - - rc = _histogramnd_double_double_double(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.float32: - - rc = _histogramnd_double_float_double(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.int32: - - rc = _histogramnd_double_int32_t_double(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - else: - raise_unsupported_type() - - # endif sample_type == np.float64 - elif sample_type == np.float32: - - if weights_type == np.float64 or weights_type is None: - - rc = _histogramnd_float_double_double(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.float32: - - rc = _histogramnd_float_float_double(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.int32: - - rc = _histogramnd_float_int32_t_double(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - else: - raise_unsupported_type() - - # endif sample_type == np.float32 - elif sample_type == np.int32: - - if weights_type == np.float64 or weights_type is None: - - rc = _histogramnd_int32_t_double_double(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.float32: - - rc = _histogramnd_int32_t_float_double(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.int32: - - rc = _histogramnd_int32_t_int32_t_double(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - else: - raise_unsupported_type() - - # endif sample_type == np.int32: - else: - raise_unsupported_type() - - # endif weighted_histo is None or weighted_histo.dtype == np.double: - elif weighted_histo.dtype == np.float32: - - if sample_type == np.float64: - - if weights_type == np.float64 or weights_type is None: - - rc = _histogramnd_double_double_float(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.float32: - - rc = _histogramnd_double_float_float(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.int32: - - rc = _histogramnd_double_int32_t_float(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - else: - raise_unsupported_type() - - # endif sample_type == np.float64 - elif sample_type == np.float32: - - if weights_type == np.float64 or weights_type is None: - - rc = _histogramnd_float_double_float(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.float32: - - rc = _histogramnd_float_float_float(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.int32: - - rc = _histogramnd_float_int32_t_float(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - else: - raise_unsupported_type() - - # endif sample_type == np.float32 - elif sample_type == np.int32: - - if weights_type == np.float64 or weights_type is None: - - rc = _histogramnd_int32_t_double_float(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.float32: - - rc = _histogramnd_int32_t_float_float(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - elif weights_type == np.int32: - - rc = _histogramnd_int32_t_int32_t_float(sample_c, - weights_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - histo_c, - cumul_c, - bin_edges_c, - option_flags, - weight_min=weight_min, - weight_max=weight_max) - - else: - raise_unsupported_type() - - # endif sample_type == np.int32: - else: - raise_unsupported_type() - - # end elseif weighted_histo.dtype == np.float32: - else: - # this isnt supposed to happen since weighted_histo type was checked earlier - raise_unsupported_type() - - if rc != histogramnd_c.HISTO_OK: - if rc == histogramnd_c.HISTO_ERR_ALLOC: - raise MemoryError('histogramnd failed to allocate memory.') - else: - raise Exception('histogramnd returned an error : {0}' - ''.format(rc)) - - edges = [] - offset = 0 - for i_dim in range(n_dims): - edges.append(bin_edges[offset:offset + n_bins[i_dim] + 1]) - offset += n_bins[i_dim] + 1 - - return histo, weighted_histo, tuple(edges) - -# ===================== -# double sample, double cumul -# ===================== - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_double_double_double(double[:] sample, - double[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - double[:] cumul, - double[:] bin_edges, - int option_flags, - double weight_min, - double weight_max) nogil: - - return histogramnd_c.histogramnd_double_double_double(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_double_float_double(double[:] sample, - float[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - double[:] cumul, - double[:] bin_edges, - int option_flags, - float weight_min, - float weight_max) nogil: - - return histogramnd_c.histogramnd_double_float_double(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_double_int32_t_double(double[:] sample, - cnumpy.int32_t[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - double[:] cumul, - double[:] bin_edges, - int option_flags, - cnumpy.int32_t weight_min, - cnumpy.int32_t weight_max) nogil: - - return histogramnd_c.histogramnd_double_int32_t_double(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -# ===================== -# float sample, double cumul -# ===================== - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_float_double_double(float[:] sample, - double[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - double[:] cumul, - double[:] bin_edges, - int option_flags, - double weight_min, - double weight_max) nogil: - - return histogramnd_c.histogramnd_float_double_double(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_float_float_double(float[:] sample, - float[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - double[:] cumul, - double[:] bin_edges, - int option_flags, - float weight_min, - float weight_max) nogil: - - return histogramnd_c.histogramnd_float_float_double(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_float_int32_t_double(float[:] sample, - cnumpy.int32_t[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - double[:] cumul, - double[:] bin_edges, - int option_flags, - cnumpy.int32_t weight_min, - cnumpy.int32_t weight_max) nogil: - - return histogramnd_c.histogramnd_float_int32_t_double(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -# ===================== -# numpy.int32_t sample, double cumul -# ===================== - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_int32_t_double_double(cnumpy.int32_t[:] sample, - double[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - double[:] cumul, - double[:] bin_edges, - int option_flags, - double weight_min, - double weight_max) nogil: - - return histogramnd_c.histogramnd_int32_t_double_double(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_int32_t_float_double(cnumpy.int32_t[:] sample, - float[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - double[:] cumul, - double[:] bin_edges, - int option_flags, - float weight_min, - float weight_max) nogil: - - return histogramnd_c.histogramnd_int32_t_float_double(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_int32_t_int32_t_double(cnumpy.int32_t[:] sample, - cnumpy.int32_t[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - double[:] cumul, - double[:] bin_edges, - int option_flags, - cnumpy.int32_t weight_min, - cnumpy.int32_t weight_max) nogil: - - return histogramnd_c.histogramnd_int32_t_int32_t_double(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -# ===================== -# double sample, float cumul -# ===================== - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_double_double_float(double[:] sample, - double[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - float[:] cumul, - double[:] bin_edges, - int option_flags, - double weight_min, - double weight_max) nogil: - - return histogramnd_c.histogramnd_double_double_float(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_double_float_float(double[:] sample, - float[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - float[:] cumul, - double[:] bin_edges, - int option_flags, - float weight_min, - float weight_max) nogil: - - return histogramnd_c.histogramnd_double_float_float(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_double_int32_t_float(double[:] sample, - cnumpy.int32_t[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - float[:] cumul, - double[:] bin_edges, - int option_flags, - cnumpy.int32_t weight_min, - cnumpy.int32_t weight_max) nogil: - - return histogramnd_c.histogramnd_double_int32_t_float(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -# ===================== -# float sample, float cumul -# ===================== - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_float_double_float(float[:] sample, - double[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - float[:] cumul, - double[:] bin_edges, - int option_flags, - double weight_min, - double weight_max) nogil: - - return histogramnd_c.histogramnd_float_double_float(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_float_float_float(float[:] sample, - float[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - float[:] cumul, - double[:] bin_edges, - int option_flags, - float weight_min, - float weight_max) nogil: - - return histogramnd_c.histogramnd_float_float_float(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_float_int32_t_float(float[:] sample, - cnumpy.int32_t[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - float[:] cumul, - double[:] bin_edges, - int option_flags, - cnumpy.int32_t weight_min, - cnumpy.int32_t weight_max) nogil: - - return histogramnd_c.histogramnd_float_int32_t_float(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -# ===================== -# numpy.int32_t sample, float cumul -# ===================== - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_int32_t_double_float(cnumpy.int32_t[:] sample, - double[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - float[:] cumul, - double[:] bin_edges, - int option_flags, - double weight_min, - double weight_max) nogil: - - return histogramnd_c.histogramnd_int32_t_double_float(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_int32_t_float_float(cnumpy.int32_t[:] sample, - float[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - float[:] cumul, - double[:] bin_edges, - int option_flags, - float weight_min, - float weight_max) nogil: - - return histogramnd_c.histogramnd_int32_t_float_float(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -cdef int _histogramnd_int32_t_int32_t_float(cnumpy.int32_t[:] sample, - cnumpy.int32_t[:] weights, - int n_dims, - int n_elem, - double[:] histo_range, - int[:] n_bins, - cnumpy.uint32_t[:] histo, - float[:] cumul, - double[:] bin_edges, - int option_flags, - cnumpy.int32_t weight_min, - cnumpy.int32_t weight_max) nogil: - - return histogramnd_c.histogramnd_int32_t_int32_t_float(&sample[0], - &weights[0], - n_dims, - n_elem, - &histo_range[0], - &n_bins[0], - &histo[0], - &cumul[0], - &bin_edges[0], - option_flags, - weight_min, - weight_max) diff --git a/silx/math/chistogramnd_lut.pyx b/silx/math/chistogramnd_lut.pyx deleted file mode 100644 index 3a3f05e..0000000 --- a/silx/math/chistogramnd_lut.pyx +++ /dev/null @@ -1,435 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-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. -# -# ############################################################################*/ - -__authors__ = ["D. Naudet"] -__license__ = "MIT" -__date__ = "15/05/2016" - - -cimport numpy as cnumpy # noqa -cimport cython -import numpy as np - -ctypedef fused sample_t: - cnumpy.float64_t - cnumpy.float32_t - cnumpy.int32_t - cnumpy.int64_t - -ctypedef fused cumul_t: - cnumpy.float64_t - cnumpy.float32_t - cnumpy.int32_t - cnumpy.int64_t - -ctypedef fused weights_t: - cnumpy.float64_t - cnumpy.float32_t - cnumpy.int32_t - cnumpy.int64_t - -ctypedef fused lut_t: - cnumpy.int64_t - cnumpy.int32_t - cnumpy.int16_t - - -def histogramnd_get_lut(sample, - histo_range, - n_bins, - last_bin_closed=False): - """TBD - - :param sample: - The data to be histogrammed. - Its shape must be either (N,) if it contains one dimensional - coordinates, or an (N, D) array where the rows are the - coordinates of points in a D dimensional space. - The following dtypes are supported : :class:`numpy.float64`, - :class:`numpy.float32`, :class:`numpy.int32`. - :type sample: :class:`numpy.array` - - :param histo_range: - A (N, 2) array containing the histogram range along each dimension, - where N is the sample's number of dimensions. - :type histo_range: array_like - - :param n_bins: - The number of bins : - * a scalar (same number of bins for all dimensions) - * a D elements array (number of bins for each dimensions) - :type n_bins: scalar or array_like - - :param last_bin_closed: - By default the last bin is half - open (i.e.: [x,y) ; x included, y - excluded), like all the other bins. - Set this parameter to true if you want - the LAST bin to be closed. - :type last_bin_closed: *optional*, :class:`python.boolean` - - :return: The indices for each sample and the histogram (bin counts). - :rtype: tuple : (:class:`numpy.array`, :class:`numpy.array`) - """ - - s_shape = sample.shape - - n_dims = 1 if len(s_shape) == 1 else s_shape[1] - - # just in case those arent numpy arrays - # (this allows the user to provide native python lists, - # => easier for testing) - i_histo_range = histo_range - histo_range = np.array(histo_range) - err_histo_range = False - - if n_dims == 1: - if histo_range.shape == (2,): - pass - elif histo_range.shape == (1, 2): - histo_range.reshape(-1) - else: - err_histo_range = True - elif n_dims != 1 and histo_range.shape != (n_dims, 2): - err_histo_range = True - - if err_histo_range: - raise ValueError('<histo_range> error : expected {n_dims} sets of ' - 'lower and upper bin edges, ' - 'got the following instead : {histo_range}. ' - '(provided <sample> contains ' - '{n_dims}D values)' - ''.format(histo_range=i_histo_range, - n_dims=n_dims)) - - histo_range = np.double(histo_range) - - # checking n_bins size - n_bins = np.array(n_bins, ndmin=1) - if len(n_bins) == 1: - n_bins = np.tile(n_bins, n_dims) - elif n_bins.shape != (n_dims,): - raise ValueError('n_bins must be either a scalar (same number ' - 'of bins for all dimensions) or ' - 'an array (number of bins for each ' - 'dimension).') - - # checking if None is in n_bins, otherwise a rather cryptic - # exception is thrown when calling np.zeros - # also testing for negative/null values - if np.any(np.equal(n_bins, None)) or np.any(n_bins <= 0): - raise ValueError('<n_bins> : only positive values allowed.') - - sample_type = sample.dtype - - n_elem = sample.size // n_dims - - if n_bins.prod(dtype=np.uint64) < 2**15: - lut_dtype = np.int16 - elif n_bins.prod(dtype=np.uint64) < 2**31: - lut_dtype = np.int32 - - else: - lut_dtype = np.int64 - - # allocating the output arrays - lut = np.zeros(n_elem, dtype=lut_dtype) - histo = np.zeros(n_bins, dtype=np.uint32) - - dtype = sample.dtype.newbyteorder("N") - sample_c = np.ascontiguousarray(sample.reshape((sample.size,)), - dtype=dtype) - - histo_range_c = np.ascontiguousarray(histo_range.reshape((histo_range.size,)), - dtype=histo_range.dtype.newbyteorder("N")) - - n_bins_c = np.ascontiguousarray(n_bins.reshape((n_bins.size,)), - dtype=np.int32) - - lut_c = np.ascontiguousarray(lut.reshape((lut.size,))) - histo_c = np.ascontiguousarray(histo.reshape((histo.size,)), - dtype=histo.dtype.newbyteorder('N')) - - rc = 0 - - try: - rc = _histogramnd_get_lut_fused(sample_c, - n_dims, - n_elem, - histo_range_c, - n_bins_c, - lut_c, - histo_c, - last_bin_closed) - except TypeError as ex: - raise TypeError('Type not supported - sample : {0}' - ''.format(sample_type)) - - if rc != 0: - raise Exception('histogramnd returned an error : {0}' - ''.format(rc)) - - edges = [] - histo_range = histo_range.reshape(-1) - for i_dim in range(n_dims): - dim_edges = np.zeros(n_bins[i_dim] + 1) - rng_min = histo_range[2 * i_dim] - rng_max = histo_range[2 * i_dim + 1] - dim_edges[:-1] = (rng_min + np.arange(n_bins[i_dim]) * - ((rng_max - rng_min) / n_bins[i_dim])) - dim_edges[-1] = rng_max - edges.append(dim_edges) - - return lut, histo, tuple(edges) - - -# ===================== -# ===================== - - -def histogramnd_from_lut(weights, - histo_lut, - histo=None, - weighted_histo=None, - shape=None, - dtype=None, - weight_min=None, - weight_max=None): - """ - dtype ignored if weighted_histo provided - """ - - if histo is None and weighted_histo is None: - if shape is None: - raise ValueError('At least one of the following parameters has to ' - 'be provided : <shape> or <histo> or ' - '<weighted_histo>') - - if shape is not None: - if histo is not None and list(histo.shape) != list(shape): - raise ValueError('The <shape> value does not match' - 'the <histo> shape.') - - if(weighted_histo is not None and - list(weighted_histo.shape) != list(shape)): - raise ValueError('The <shape> value does not match' - 'the <weighted_histo> shape.') - else: - if histo is not None: - shape = histo.shape - else: - shape = weighted_histo.shape - - if histo is not None: - if histo.dtype != np.uint32: - raise ValueError('Provided <histo> array doesn\'t have ' - 'the expected type ' - ': should be {0} instead of {1}.' - ''.format(np.uint32, histo.dtype)) - - if weighted_histo is not None: - if histo.shape != weighted_histo.shape: - raise ValueError('The <histo> shape does not match' - 'the <weighted_histo> shape.') - else: - histo = np.zeros(shape, dtype=np.uint32) - - w_dtype = weights.dtype - - if dtype is None: - if weighted_histo is None: - dtype = w_dtype - else: - dtype = weighted_histo.dtype - elif weighted_histo is not None: - if weighted_histo.dtype != dtype: - raise ValueError('Provided <dtype> and <weighted_histo>\'s dtype' - ' do not match.') - dtype = weighted_histo.dtype - - if weighted_histo is None: - weighted_histo = np.zeros(shape, dtype=dtype) - - if histo_lut.size != weights.size: - raise ValueError('The LUT and weights arrays must have the same ' - 'number of elements.') - - w_c = np.ascontiguousarray(weights.reshape((weights.size,)), - dtype=weights.dtype.newbyteorder('N')) - - h_c = np.ascontiguousarray(histo.reshape((histo.size,)), - dtype=histo.dtype.newbyteorder('N')) - - w_h_c = np.ascontiguousarray(weighted_histo.reshape((weighted_histo.size,)), - dtype=weighted_histo.dtype.newbyteorder('N')) # noqa - - h_lut_c = np.ascontiguousarray(histo_lut.reshape((histo_lut.size,)), - histo_lut.dtype.newbyteorder('N')) - - rc = 0 - - if weight_min is None: - weight_min = 0 - filt_min_weights = False - else: - filt_min_weights = True - - if weight_max is None: - weight_max = 0 - filt_max_weights = False - else: - filt_max_weights = True - - try: - _histogramnd_from_lut_fused(w_c, - h_lut_c, - h_c, - w_h_c, - weights.size, - filt_min_weights, - w_dtype.type(weight_min), - filt_max_weights, - w_dtype.type(weight_max)) - except TypeError as ex: - print(ex) - raise TypeError('Case not supported - weights:{0} ' - 'and histo:{1}.' - ''.format(weights.dtype, histo.dtype)) - - return histo, weighted_histo - - -# ===================== -# ===================== - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -@cython.cdivision(True) -def _histogramnd_from_lut_fused(weights_t[:] i_weights, - lut_t[:] i_lut, - cnumpy.uint32_t[:] o_histo, - cumul_t[:] o_weighted_histo, - int i_n_elems, - bint i_filt_min_weights, - weights_t i_weight_min, - bint i_filt_max_weights, - weights_t i_weight_max): - with nogil: - for i in range(i_n_elems): - if (i_lut[i] >= 0): - if i_filt_min_weights and i_weights[i] < i_weight_min: - continue - if i_filt_max_weights and i_weights[i] > i_weight_max: - continue - o_histo[i_lut[i]] += 1 - o_weighted_histo[i_lut[i]] += <cumul_t>i_weights[i] # noqa - - -# ===================== -# ===================== - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.initializedcheck(False) -@cython.nonecheck(False) -@cython.cdivision(True) -def _histogramnd_get_lut_fused(sample_t[:] i_sample, - int i_n_dims, - int i_n_elems, - double[:] i_histo_range, - int[:] i_n_bins, - lut_t[:] o_lut, - cnumpy.uint32_t[:] o_histo, - bint last_bin_closed): - - cdef: - int i = 0 - long elem_idx = 0 - long max_idx = 0 - long lut_idx = -1 - - # computed bin index (i_sample -> grid) - long bin_idx = 0 - - sample_t elem_coord = 0 - - double[50] g_min - double[50] g_max - double[50] bins_range - - for i in range(i_n_dims): - g_min[i] = i_histo_range[2*i] - g_max[i] = i_histo_range[2*i+1] - bins_range[i] = g_max[i] - g_min[i] - - elem_idx = 0 - i_n_dims - max_idx = i_n_elems * i_n_dims - i_n_dims - - with nogil: - while elem_idx < max_idx: - elem_idx += i_n_dims - lut_idx += 1 - - bin_idx = 0 - - for i in range(i_n_dims): - elem_coord = i_sample[elem_idx+i] - # ===================== - # Element is rejected if any of the following is NOT true : - # 1. coordinate is >= than the minimum value - # 2. coordinate is <= than the maximum value - # 3. coordinate==maximum value and last_bin_closed is True - # ===================== - if elem_coord < g_min[i]: - bin_idx = -1 - break - - # Here we make the assumption that most of the time - # there will be more coordinates inside the grid interval - # (one test) - # than coordinates higher or equal to the max - # (two tests) - if elem_coord < g_max[i]: - bin_idx = <long>(bin_idx * i_n_bins[i] + # noqa - (((elem_coord - g_min[i]) * i_n_bins[i]) / - bins_range[i])) - else: - # if equal and the last bin is closed : - # put it in the last bin - # else : discard - if last_bin_closed and elem_coord == g_max[i]: - bin_idx = (bin_idx + 1) * i_n_bins[i] - 1 - else: - bin_idx = -1 - break - - o_lut[lut_idx] = bin_idx - if bin_idx >= 0: - o_histo[bin_idx] += 1 - - return 0 diff --git a/silx/math/colormap.pyx b/silx/math/colormap.pyx deleted file mode 100644 index 2cefe04..0000000 --- a/silx/math/colormap.pyx +++ /dev/null @@ -1,559 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018-2020 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 :func:`cmap` which applies a colormap to a dataset. -""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "16/05/2018" - - -import os -cimport cython -from cython.parallel import prange -cimport numpy as cnumpy -from libc.math cimport frexp, sinh, sqrt -from .math_compatibility cimport asinh, isnan, isfinite, lrint, INFINITY, NAN - -import logging -import numbers - -import numpy - -__all__ = ['cmap'] - -_logger = logging.getLogger(__name__) - - -cdef int DEFAULT_NUM_THREADS -if hasattr(os, 'sched_getaffinity'): - DEFAULT_NUM_THREADS = min(4, len(os.sched_getaffinity(0))) -elif os.cpu_count() is not None: - DEFAULT_NUM_THREADS = min(4, os.cpu_count()) -else: # Fallback - DEFAULT_NUM_THREADS = 1 -# Number of threads to use for the computation (initialized to up to 4) - -cdef int USE_OPENMP_THRESHOLD = 1000 -"""OpenMP is not used for arrays with less elements than this threshold""" - -# Supported data types -ctypedef fused data_types: - cnumpy.uint8_t - cnumpy.int8_t - cnumpy.uint16_t - cnumpy.int16_t - cnumpy.uint32_t - cnumpy.int32_t - cnumpy.uint64_t - cnumpy.int64_t - float - double - long double - - -# Data types using a LUT to apply the colormap -ctypedef fused lut_types: - cnumpy.uint8_t - cnumpy.int8_t - cnumpy.uint16_t - cnumpy.int16_t - - -# Data types using default colormap implementation -ctypedef fused default_types: - cnumpy.uint32_t - cnumpy.int32_t - cnumpy.uint64_t - cnumpy.int64_t - float - double - long double - - -# Supported colors/output types -ctypedef fused image_types: - cnumpy.uint8_t - float - - -# Normalization - -ctypedef double (*NormalizationFunction)(double) nogil - - -cdef class Normalization: - """Base class for colormap normalization""" - - def apply(self, data, double vmin, double vmax): - """Apply normalization. - - :param Union[float,numpy.ndarray] data: - :param float vmin: Lower bound of the range - :param float vmax: Upper bound of the range - :rtype: Union[float,numpy.ndarray] - """ - cdef int length - cdef double[:] result - - if isinstance(data, numbers.Real): - return self.apply_double(<double> data, vmin, vmax) - else: - data = numpy.array(data, copy=False) - length = <int> data.size - result = numpy.empty(length, dtype=numpy.float64) - data1d = numpy.ravel(data) - for index in range(length): - result[index] = self.apply_double( - <double> data1d[index], vmin, vmax) - return numpy.array(result).reshape(data.shape) - - def revert(self, data, double vmin, double vmax): - """Revert normalization. - - :param Union[float,numpy.ndarray] data: - :param float vmin: Lower bound of the range - :param float vmax: Upper bound of the range - :rtype: Union[float,numpy.ndarray] - """ - cdef int length - cdef double[:] result - - if isinstance(data, numbers.Real): - return self.revert_double(<double> data, vmin, vmax) - else: - data = numpy.array(data, copy=False) - length = <int> data.size - result = numpy.empty(length, dtype=numpy.float64) - data1d = numpy.ravel(data) - for index in range(length): - result[index] = self.revert_double( - <double> data1d[index], vmin, vmax) - return numpy.array(result).reshape(data.shape) - - cdef double apply_double(self, double value, double vmin, double vmax) nogil: - """Apply normalization to a floating point value - - Override in subclass - - :param float value: - :param float vmin: Lower bound of the range - :param float vmax: Upper bound of the range - """ - return value - - cdef double revert_double(self, double value, double vmin, double vmax) nogil: - """Apply inverse of normalization to a floating point value - - Override in subclass - - :param float value: - :param float vmin: Lower bound of the range - :param float vmax: Upper bound of the range - """ - return value - - -cdef class LinearNormalization(Normalization): - """Linear normalization""" - - cdef double apply_double(self, double value, double vmin, double vmax) nogil: - return value - - cdef double revert_double(self, double value, double vmin, double vmax) nogil: - return value - - -cdef class LogarithmicNormalization(Normalization): - """Logarithmic normalization using a fast log approximation""" - cdef: - readonly int lutsize - readonly double[::1] lut # LUT used for fast log approximation - - def __cinit__(self, int lutsize=4096): - # Initialize log approximation LUT - self.lutsize = lutsize - self.lut = numpy.log2( - numpy.linspace(0.5, 1., lutsize + 1, - endpoint=True).astype(numpy.float64)) - # index_lut can overflow of 1 - self.lut[lutsize] = self.lut[lutsize - 1] - - def __dealloc__(self): - self.lut = None - - @cython.wraparound(False) - @cython.boundscheck(False) - @cython.nonecheck(False) - @cython.cdivision(True) - cdef double apply_double(self, double value, double vmin, double vmax) nogil: - """Return log10(value) fast approximation based on LUT""" - cdef double result = NAN # if value < 0.0 or value == NAN - cdef int exponent, index_lut - cdef double mantissa # in [0.5, 1) unless value == 0 NaN or +/-inf - - if value <= 0.0 or not isfinite(value): - if value == 0.0: - result = - INFINITY - elif value > 0.0: # i.e., value = +INFINITY - result = value # i.e. +INFINITY - else: - mantissa = frexp(value, &exponent) - index_lut = lrint(self.lutsize * 2 * (mantissa - 0.5)) - # 1/log2(10) = 0.30102999566398114 - result = 0.30102999566398114 * (<double> exponent + - self.lut[index_lut]) - return result - - cdef double revert_double(self, double value, double vmin, double vmax) nogil: - return 10**value - - -cdef class ArcsinhNormalization(Normalization): - """Inverse hyperbolic sine normalization""" - - cdef double apply_double(self, double value, double vmin, double vmax) nogil: - return asinh(value) - - cdef double revert_double(self, double value, double vmin, double vmax) nogil: - return sinh(value) - - -cdef class SqrtNormalization(Normalization): - """Square root normalization""" - - cdef double apply_double(self, double value, double vmin, double vmax) nogil: - return sqrt(value) - - cdef double revert_double(self, double value, double vmin, double vmax) nogil: - return value**2 - - -cdef class PowerNormalization(Normalization): - """Gamma correction: - - Linear normalization to [0, 1] followed by power normalization. - - :param gamma: Gamma correction factor - """ - - cdef: - readonly double gamma - - def __cinit__(self, double gamma): - self.gamma = gamma - - def __init__(self, gamma): - # Needed for multiple inheritance to work - pass - - cdef double apply_double(self, double value, double vmin, double vmax) nogil: - if vmin == vmax: - return 0. - elif value <= vmin: - return 0. - elif value >= vmax: - return 1. - else: - return ((value - vmin) / (vmax - vmin))**self.gamma - - cdef double revert_double(self, double value, double vmin, double vmax) nogil: - if value <= 0.: - return vmin - elif value >= 1.: - return vmax - else: - return vmin + (vmax - vmin) * value**(1.0/self.gamma) - - -# Colormap - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.nonecheck(False) -@cython.cdivision(True) -cdef image_types[:, ::1] compute_cmap( - default_types[:] data, - image_types[:, ::1] colors, - Normalization normalization, - double vmin, - double vmax, - image_types[::1] nan_color): - """Apply colormap to data. - - :param data: Input data - :param colors: Colors look-up-table - :param vmin: Lower bound of the colormap range - :param vmax: Upper bound of the colormap range - :param nan_color: Color to use for NaN value - :param normalization: Normalization to apply - :return: Data converted to colors - """ - cdef image_types[:, ::1] output - cdef double scale, value, normalized_vmin, normalized_vmax - cdef int length, nb_channels, nb_colors - cdef int channel, index, lut_index, num_threads - - nb_colors = <int> colors.shape[0] - nb_channels = <int> colors.shape[1] - length = <int> data.size - - output = numpy.empty((length, nb_channels), - dtype=numpy.array(colors, copy=False).dtype) - - normalized_vmin = normalization.apply_double(vmin, vmin, vmax) - normalized_vmax = normalization.apply_double(vmax, vmin, vmax) - - if not isfinite(normalized_vmin) or not isfinite(normalized_vmax): - raise ValueError('Colormap range is not valid') - - if normalized_vmin == normalized_vmax: - scale = 0. - else: - scale = nb_colors / (normalized_vmax - normalized_vmin) - - if length < USE_OPENMP_THRESHOLD: - num_threads = 1 - else: - num_threads = min( - DEFAULT_NUM_THREADS, - int(os.environ.get("OMP_NUM_THREADS", DEFAULT_NUM_THREADS))) - - with nogil: - for index in prange(length, num_threads=num_threads): - value = normalization.apply_double( - <double> data[index], vmin, vmax) - - # Handle NaN - if isnan(value): - for channel in range(nb_channels): - output[index, channel] = nan_color[channel] - continue - - if value <= normalized_vmin: - lut_index = 0 - elif value >= normalized_vmax: - lut_index = nb_colors - 1 - else: - lut_index = <int>((value - normalized_vmin) * scale) - # Index can overflow of 1 - if lut_index >= nb_colors: - lut_index = nb_colors - 1 - - for channel in range(nb_channels): - output[index, channel] = colors[lut_index, channel] - - return output - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.nonecheck(False) -@cython.cdivision(True) -cdef image_types[:, ::1] compute_cmap_with_lut( - lut_types[:] data, - image_types[:, ::1] colors, - Normalization normalization, - double vmin, - double vmax, - image_types[::1] nan_color): - """Convert data to colors using look-up table to speed the process. - - Only supports data of types: uint8, uint16, int8, int16. - - :param data: Input data - :param colors: Colors look-up-table - :param vmin: Lower bound of the colormap range - :param vmax: Upper bound of the colormap range - :param nan_color: Color to use for NaN values - :param normalization: Normalization to apply - :return: The generated image - """ - cdef image_types[:, ::1] output - cdef double[:] values - cdef image_types[:, ::1] lut - cdef int type_min, type_max - cdef int nb_channels, length - cdef int channel, index, lut_index, num_threads - - length = <int> data.size - nb_channels = <int> colors.shape[1] - - if lut_types is cnumpy.int8_t: - type_min = -128 - type_max = 127 - elif lut_types is cnumpy.uint8_t: - type_min = 0 - type_max = 255 - elif lut_types is cnumpy.int16_t: - type_min = -32768 - type_max = 32767 - else: # uint16_t - type_min = 0 - type_max = 65535 - - colors_dtype = numpy.array(colors).dtype - - values = numpy.arange(type_min, type_max + 1, dtype=numpy.float64) - lut = compute_cmap( - values, colors, normalization, vmin, vmax, nan_color) - - output = numpy.empty((length, nb_channels), dtype=colors_dtype) - - if length < USE_OPENMP_THRESHOLD: - num_threads = 1 - else: - num_threads = min( - DEFAULT_NUM_THREADS, - int(os.environ.get("OMP_NUM_THREADS", DEFAULT_NUM_THREADS))) - - with nogil: - # Apply LUT - for index in prange(length, num_threads=num_threads): - lut_index = data[index] - type_min - for channel in range(nb_channels): - output[index, channel] = lut[lut_index, channel] - - return output - - -# Normalizations without parameters -_BASIC_NORMALIZATIONS = { - 'linear': LinearNormalization(), - 'log': LogarithmicNormalization(), - 'arcsinh': ArcsinhNormalization(), - 'sqrt': SqrtNormalization(), - } - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.nonecheck(False) -@cython.cdivision(True) -def _cmap(data_types[:] data, - image_types[:, ::1] colors, - Normalization normalization, - double vmin, - double vmax, - image_types[::1] nan_color): - """Implementation of colormap. - - Use :func:`cmap`. - - :param data: Input data - :param colors: Colors look-up-table - :param normalization: Normalization object to apply - :param vmin: Lower bound of the colormap range - :param vmax: Upper bound of the colormap range - :param nan_color: Color to use for NaN value. - :return: The generated image - """ - cdef image_types[:, ::1] output - - # Proxy for calling the right implementation depending on data type - if data_types in lut_types: # Use LUT implementation - output = compute_cmap_with_lut( - data, colors, normalization, vmin, vmax, nan_color) - - elif data_types in default_types: # Use default implementation - output = compute_cmap( - data, colors, normalization, vmin, vmax, nan_color) - - else: - raise ValueError('Unsupported data type') - - return numpy.array(output, copy=False) - - -def cmap(data, - colors, - double vmin, - double vmax, - normalization='linear', - nan_color=None): - """Convert data to colors with provided colors look-up table. - - :param numpy.ndarray data: The input data - :param numpy.ndarray colors: Color look-up table as a 2D array. - It MUST be of type uint8 or float32 - :param vmin: Data value to map to the beginning of colormap. - :param vmax: Data value to map to the end of the colormap. - :param Union[str,Normalization] normalization: - Either a :class:`Normalization` instance or a str in: - - - 'linear' (default) - - 'log' - - 'arcsinh' - - 'sqrt' - - 'gamma' - - :param nan_color: Color to use for NaN value. - Default: A color with all channels set to 0 - :return: Array of colors. The shape of the - returned array is that of data array + the last dimension of colors. - The dtype of the returned array is that of the colors array. - :rtype: numpy.ndarray - """ - cdef int nb_channels - cdef Normalization norm - - # Make data a numpy array of native endian type (no need for contiguity) - data = numpy.array(data, copy=False) - native_endian_dtype = data.dtype.newbyteorder('N') - if native_endian_dtype.kind == 'f' and native_endian_dtype.itemsize == 2: - native_endian_dtype = "=f4" # Use native float32 instead of float16 - data = numpy.array(data, copy=False, dtype=native_endian_dtype) - - # Make colors a contiguous array of native endian type - colors = numpy.array(colors, copy=False) - nb_channels = colors.shape[colors.ndim - 1] - colors = numpy.ascontiguousarray(colors, - dtype=colors.dtype.newbyteorder('N')) - - # Make normalization a Normalization object - if isinstance(normalization, str): - norm = _BASIC_NORMALIZATIONS.get(normalization, None) - if norm is None: - raise ValueError('Unsupported normalization %s' % normalization) - else: - norm = normalization - - # Check nan_color - if nan_color is None: - nan_color = numpy.zeros((nb_channels,), dtype=colors.dtype) - else: - nan_color = numpy.ascontiguousarray( - nan_color, dtype=colors.dtype).reshape(-1) - assert nan_color.shape == (nb_channels,) - - image = _cmap( - data.reshape(-1), - colors.reshape(-1, nb_channels), - norm, - vmin, - vmax, - nan_color) - image.shape = data.shape + (nb_channels,) - - return image diff --git a/silx/math/combo.pyx b/silx/math/combo.pyx deleted file mode 100644 index e24edda..0000000 --- a/silx/math/combo.pyx +++ /dev/null @@ -1,329 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-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 combination of statistics as single operation. - -For now it provides min/max (and optionally positive min) and indices -of first occurrences (i.e., argmin/argmax) in a single pass. -""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "24/04/2018" - -cimport cython -from .math_compatibility cimport isnan, isfinite, INFINITY - - -import numpy - - -# All supported types -ctypedef fused _number: - float - double - long double - signed char - signed short - signed int - signed long - signed long long - unsigned char - unsigned short - unsigned int - unsigned long - unsigned long long - -# All supported floating types: -# cython.floating + long double -ctypedef fused _floating: - float - double - long double - - -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 occurrence of the minimum value") - argmax = property( - lambda self: self._argmax, - doc="Index of the first occurrence 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 occurrence.""") - - 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): - """:func:`min_max` implementation including infinite values - - See :func:`min_max` for documentation. - """ - cdef: - _number value, minimum, min_pos, 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 _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 0 < 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) - - -@cython.initializedcheck(False) -@cython.boundscheck(False) -@cython.wraparound(False) -def _finite_min_max(_floating[::1] data, bint min_positive=False): - """:func:`min_max` implementation for floats skipping infinite values - - See :func:`min_max` for documentation. - """ - cdef: - _floating value, minimum, min_pos, 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: - minimum = INFINITY - maximum = -INFINITY - min_pos = INFINITY - - if not min_positive: - for index in range(length): - value = data[index] - if isfinite(value): - if value > maximum: - maximum = value - max_index = index - if value < minimum: - minimum = value - min_index = index - - else: - for index in range(index, length): - value = data[index] - if isfinite(value): - if value > maximum: - maximum = value - max_index = index - if value < minimum: - minimum = value - min_index = index - - if 0. < value < min_pos: - min_pos = value - min_pos_index = index - - return _MinMaxResult(minimum if isfinite(minimum) else None, - min_pos if isfinite(min_pos) else None, - maximum if isfinite(maximum) else None, - min_index if isfinite(minimum) else None, - min_pos_index if isfinite(min_pos) else None, - max_index if isfinite(maximum) else None) - - -def min_max(data not None, bint min_positive=False, bint finite=False): - """Returns min, max and optionally strictly positive min of data. - - It also computes the indices of first occurrence of min/max. - - NaNs are ignored while computing min/max unless all data is NaNs, - in which case returned min/max are NaNs. - - The result data type is that of the input data, except for the following cases. - For input using non-native bytes order, the result is returned as native - floating-point or integers. For input using 16-bits floating-point, - the result is returned as 32-bits floating-point. - - 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 - - If *finite* is True, min/max information is computed only from finite data. - Then, all result fields (include minimum and maximum) can be None - when all data is infinity or NaN. - - :param data: Array-like dataset - :param bool min_positive: True to compute the positive min and argmin - Default: False. - :param bool finite: True to compute min/max from finite data only - Default: False. - :returns: An object with minimum, maximum and min_positive attributes - and the indices of first occurrence 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 - """ - data = numpy.array(data, copy=False) - native_endian_dtype = data.dtype.newbyteorder('N') - if native_endian_dtype.kind == 'f' and native_endian_dtype.itemsize == 2: - # Use native float32 instead of float16 - native_endian_dtype = "=f4" - data = numpy.ascontiguousarray(data, dtype=native_endian_dtype).ravel() - if finite and data.dtype.kind == 'f': - return _finite_min_max(data, min_positive) - else: - return _min_max(data, min_positive) diff --git a/silx/math/fft/__init__.py b/silx/math/fft/__init__.py deleted file mode 100644 index ea12cd6..0000000 --- a/silx/math/fft/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -__authors__ = ["P. Paleo"] -__license__ = "MIT" -__date__ = "12/12/2018" - -from .fft import FFT diff --git a/silx/math/fft/basefft.py b/silx/math/fft/basefft.py deleted file mode 100644 index 854ca37..0000000 --- a/silx/math/fft/basefft.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python -# 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. -# -# ###########################################################################*/ -import numpy as np -from pkg_resources import parse_version - - -def check_version(package, required_version): - """ - Check whether a given package version is superior or equal to required_version. - """ - try: - ver = getattr(package, "__version__") - except AttributeError: - try: - ver = getattr(package, "version") - except Exception: - return False - req_v = parse_version(required_version) - ver_v = parse_version(ver) - return ver_v >= req_v - - -class BaseFFT(object): - """ - Base class for all FFT backends. - """ - def __init__(self, **kwargs): - self.__get_args(**kwargs) - - if self.shape is None and self.dtype is None and self.template is None: - raise ValueError("Please provide either (shape and dtype) or template") - if self.template is not None: - self.shape = self.template.shape - self.dtype = self.template.dtype - self.user_data = self.template - self.data_allocated = False - self.__calc_axes() - self.__set_dtypes() - self.__calc_shape() - - def __get_args(self, **kwargs): - expected_args = { - "shape": None, - "dtype": None, - "template": None, - "shape_out": None, - "axes": None, - "normalize": "rescale", - } - for arg_name, default_val in expected_args.items(): - if arg_name not in kwargs: - # Base class was not instantiated properly - raise ValueError("Please provide argument %s" % arg_name) - setattr(self, arg_name, default_val) - for arg_name, arg_val in kwargs.items(): - setattr(self, arg_name, arg_val) - - def __set_dtypes(self): - dtypes_mapping = { - np.dtype("float32"): np.complex64, - np.dtype("float64"): np.complex128, - np.dtype("complex64"): np.complex64, - np.dtype("complex128"): np.complex128 - } - dp = { - np.dtype("float32"): np.float64, - np.dtype("complex64"): np.complex128 - } - self.dtype_in = np.dtype(self.dtype) - if self.dtype_in not in dtypes_mapping: - raise ValueError("Invalid input data type: got %s" % - self.dtype_in - ) - self.dtype_out = dtypes_mapping[self.dtype_in] - - def __calc_shape(self): - # TODO allow for C2C even for real input data (?) - if self.dtype_in in [np.float32, np.float64]: - last_dim = self.shape[-1]//2 + 1 - # FFTW convention - self.shape_out = self.shape[:-1] + (self.shape[-1]//2 + 1,) - else: - self.shape_out = self.shape - - def __calc_axes(self): - default_axes = tuple(range(len(self.shape))) - if self.axes is None: - self.axes = default_axes - self.user_axes = None - else: - self.user_axes = self.axes - # Handle possibly negative axes - self.axes = tuple(np.array(default_axes)[np.array(self.user_axes)]) - - def _allocate(self, shape, dtype): - raise ValueError("This should be implemented by back-end FFT") - - def set_data(self, dst, src, shape, dtype, copy=True): - raise ValueError("This should be implemented by back-end FFT") - - def allocate_arrays(self): - if not(self.data_allocated): - self.data_in = self._allocate(self.shape, self.dtype_in) - self.data_out = self._allocate(self.shape_out, self.dtype_out) - self.data_allocated = True - - def set_input_data(self, data, copy=True): - if data is None: - return self.data_in - else: - return self.set_data(self.data_in, data, self.shape, self.dtype_in, copy=copy, name="data_in") - - def set_output_data(self, data, copy=True): - if data is None: - return self.data_out - else: - return self.set_data(self.data_out, data, self.shape_out, self.dtype_out, copy=copy, name="data_out") - - def fft(self, array, **kwargs): - raise ValueError("This should be implemented by back-end FFT") - - def ifft(self, array, **kwargs): - raise ValueError("This should be implemented by back-end FFT") diff --git a/silx/math/fft/clfft.py b/silx/math/fft/clfft.py deleted file mode 100644 index dad8ec1..0000000 --- a/silx/math/fft/clfft.py +++ /dev/null @@ -1,286 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018-2019 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. -# -# ###########################################################################*/ -import numpy as np - -from .basefft import BaseFFT, check_version -try: - import pyopencl as cl - import pyopencl.array as parray - import gpyfft - from gpyfft.fft import FFT as cl_fft - from ...opencl.common import ocl - __have_clfft__ = True -except ImportError: - __have_clfft__ = False - - -# Check gpyfft version -__required_gpyfft_version__ = "0.3.0" -if __have_clfft__: - __have_clfft__ = check_version(gpyfft, __required_gpyfft_version__) - - -class CLFFT(BaseFFT): - """Initialize a clfft plan. - - Please see FFT class for parameters help. - - CLFFT-specific parameters - -------------------------- - - :param pyopencl.Context ctx: - If set to other than None, an existing pyopencl context is used. - :param bool fast_math: - If set to True, computations will be done with "fast math" mode, - i.e., more speed but less accuracy. - :param bool choose_best_device: - Whether to automatically choose the best available OpenCL device. - """ - def __init__( - self, - shape=None, - dtype=None, - template=None, - shape_out=None, - axes=None, - normalize="rescale", - ctx=None, - fast_math=False, - choose_best_device=True, - ): - if not(__have_clfft__) or not(__have_clfft__): - raise ImportError("Please install pyopencl and gpyfft >= %s to use the OpenCL back-end" % __required_gpyfft_version__) - - super(CLFFT, self).__init__( - shape=shape, - dtype=dtype, - template=template, - shape_out=shape_out, - axes=axes, - normalize=normalize, - ) - self.ctx = ctx - self.choose_best_device = choose_best_device - self.fast_math = fast_math - self.backend = "clfft" - - self.fix_axes() - self.init_context_queue() - self.allocate_arrays() - self.real_transform = np.isrealobj(self.data_in) - self.compute_forward_plan() - self.compute_inverse_plan() - self.refs = { - "data_in": self.data_in, - "data_out": self.data_out, - } - # TODO - # Either pyopencl ElementWiseKernel, or built-in clfft callbacks - if self.normalize != "rescale": - raise NotImplementedError( - "Normalization modes other than rescale are not implemented with OpenCL backend yet." - ) - - def fix_axes(self): - """ - "Fix" axes. - - clfft does not have the same convention as FFTW/cuda/numpy. - """ - self.axes = self.axes[::-1] - - def _allocate(self, shape, dtype): - ary = parray.empty(self.queue, shape, dtype=dtype) - ary.fill(0) - return ary - - - def check_array(self, array, shape, dtype, copy=True): - if array.shape != shape: - raise ValueError("Invalid data shape: expected %s, got %s" % - (shape, array.shape) - ) - if array.dtype != dtype: - raise ValueError("Invalid data type: expected %s, got %s" % - (dtype, array.dtype) - ) - - - def set_data(self, dst, src, shape, dtype, copy=True, name=None): - """ - dst is a device array owned by the current instance - (either self.data_in or self.data_out). - - copy is ignored for device<-> arrays. - """ - self.check_array(src, shape, dtype) - if isinstance(src, np.ndarray): - if name == "data_out": - # Makes little sense to provide output=numpy_array - return dst - if not(src.flags["C_CONTIGUOUS"]): - src = np.ascontiguousarray(src, dtype=dtype) - # working on underlying buffer is notably faster - #~ dst[:] = src[:] - evt = cl.enqueue_copy(self.queue, dst.data, src) - evt.wait() - elif isinstance(src, parray.Array): - # No copy, use the data as self.d_input or self.d_output - # (this prevents the use of in-place transforms, however). - # We have to keep their old references. - if name is None: - # This should not happen - raise ValueError("Please provide either copy=True or name != None") - assert id(self.refs[name]) == id(dst) # DEBUG - setattr(self, name, src) - return src - else: - raise ValueError( - "Invalid array type %s, expected numpy.ndarray or pyopencl.array" % - type(src) - ) - return dst - - - def recover_array_references(self): - self.data_in = self.refs["data_in"] - self.data_out = self.refs["data_out"] - - - def init_context_queue(self): - if self.ctx is None: - if self.choose_best_device: - self.ctx = ocl.create_context() - else: - self.ctx = cl.create_some_context() - self.queue = cl.CommandQueue(self.ctx) - - - def compute_forward_plan(self): - self.plan_forward = cl_fft( - self.ctx, - self.queue, - self.data_in, - out_array=self.data_out, - axes=self.axes, - fast_math=self.fast_math, - real=self.real_transform, - ) - - - def compute_inverse_plan(self): - self.plan_inverse = cl_fft( - self.ctx, - self.queue, - self.data_out, - out_array=self.data_in, - axes=self.axes, - fast_math=self.fast_math, - real=self.real_transform, - ) - - - def update_forward_plan_arrays(self): - self.plan_forward.data = self.data_in - self.plan_forward.result = self.data_out - - - def update_inverse_plan_arrays(self): - self.plan_inverse.data = self.data_out - self.plan_inverse.result = self.data_in - - - def copy_output_if_numpy(self, dst, src): - if isinstance(dst, parray.Array): - return - # working on underlying buffer is notably faster - #~ dst[:] = src[:] - evt = cl.enqueue_copy(self.queue, dst, src.data) - evt.wait() - - - def fft(self, array, output=None, do_async=False): - """ - Perform a (forward) Fast Fourier Transform. - - :param Union[numpy.ndarray,pyopencl.array] array: - Input data. Must be consistent with the current context. - :param Union[numpy.ndarray,pyopencl.array] output: - Output data. By default, output is a numpy.ndarray. - :param bool do_async: - Whether to perform operation in asynchronous mode. - Default is False, meaning that we wait for transform to complete. - """ - self.set_input_data(array, copy=False) - self.set_output_data(output, copy=False) - self.update_forward_plan_arrays() - event, = self.plan_forward.enqueue() - if not(do_async): - event.wait() - if output is not None: - self.copy_output_if_numpy(output, self.data_out) - res = output - else: - res = self.data_out.get() - self.recover_array_references() - return res - - - def ifft(self, array, output=None, do_async=False): - """ - Perform a (inverse) Fast Fourier Transform. - - :param Union[numpy.ndarray,pyopencl.array] array: - Input data. Must be consistent with the current context. - :param Union[numpy.ndarray,pyopencl.array] output: - Output data. By default, output is a numpy.ndarray. - :param bool do_async: - Whether to perform operation in asynchronous mode. - Default is False, meaning that we wait for transform to complete. - """ - self.set_output_data(array, copy=False) - self.set_input_data(output, copy=False) - self.update_inverse_plan_arrays() - event, = self.plan_inverse.enqueue(forward=False) - if not(do_async): - event.wait() - if output is not None: - self.copy_output_if_numpy(output, self.data_in) - res = output - else: - res = self.data_in.get() - self.recover_array_references() - return res - - - def __del__(self): - # It seems that gpyfft underlying clFFT destructors are not called. - # This results in the following warning: - # Warning: Program terminating, but clFFT resources not freed. - # Please consider explicitly calling clfftTeardown( ) - del self.plan_forward - del self.plan_inverse - diff --git a/silx/math/fft/cufft.py b/silx/math/fft/cufft.py deleted file mode 100644 index 848f3e6..0000000 --- a/silx/math/fft/cufft.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018-2019 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. -# -# ###########################################################################*/ -import numpy as np - -from .basefft import BaseFFT -try: - import pycuda.gpuarray as gpuarray - from skcuda.fft import Plan - from skcuda.fft import fft as cu_fft - from skcuda.fft import ifft as cu_ifft - __have_cufft__ = True -except ImportError: - __have_cufft__ = False - - -class CUFFT(BaseFFT): - """Initialize a cufft plan - - Please see FFT class for parameters help. - - CUFFT-specific parameters - -------------------------- - - :param pycuda.driver.Stream stream: - Stream with which to associate the plan. If no stream is specified, - the default stream is used. - """ - def __init__( - self, - shape=None, - dtype=None, - template=None, - shape_out=None, - axes=None, - normalize="rescale", - stream=None, - ): - if not(__have_cufft__) or not(__have_cufft__): - raise ImportError("Please install pycuda and scikit-cuda to use the CUDA back-end") - - super(CUFFT, self).__init__( - shape=shape, - dtype=dtype, - template=template, - shape_out=shape_out, - axes=axes, - normalize=normalize, - ) - self.cufft_stream = stream - self.backend = "cufft" - - self.configure_batched_transform() - self.allocate_arrays() - self.real_transform = np.isrealobj(self.data_in) - self.compute_forward_plan() - self.compute_inverse_plan() - self.refs = { - "data_in": self.data_in, - "data_out": self.data_out, - } - self.configure_normalization() - - def _allocate(self, shape, dtype): - return gpuarray.zeros(shape, dtype) - - # TODO support batched transform where batch is other than dimension 0 - def configure_batched_transform(self): - self.cufft_batch_size = 1 - self.cufft_shape = self.shape - if (self.axes is not None) and (len(self.axes) < len(self.shape)): - # In the easiest case, the transform is computed along the fastest dimensions: - # - 1D transforms of lines of 2D data - # - 2D transforms of images of 3D data (stacked along slow dim) - # - 1D transforms of 3D data along fastest dim - # Otherwise, we have to configure cuda "advanced memory layout", - # which is not implemented yet. - - data_ndims = len(self.shape) - supported_axes = { - 2: [(1,)], - 3: [(1, 2), (2, 1), (1,), (2,)], - } - if self.axes not in supported_axes[data_ndims]: - raise NotImplementedError("With the CUDA backend, batched transform is only supported along fastest dimensions") - self.cufft_batch_size = self.shape[0] - self.cufft_shape = self.shape[1:] - if data_ndims == 3 and len(self.axes) == 1: - # 1D transform on 3D data: here only supported along fast dim, - # so batch_size is Nx*Ny - self.cufft_batch_size = np.prod(self.shape[:2]) - self.cufft_shape = (self.shape[-1],) - if len(self.cufft_shape) == 1: - self.cufft_shape = self.cufft_shape[0] - - def configure_normalization(self): - # TODO - if self.normalize == "ortho": - raise NotImplementedError( - "Normalization mode 'ortho' is not implemented with CUDA backend yet." - ) - self.cufft_scale_inverse = (self.normalize == "rescale") - - def check_array(self, array, shape, dtype, copy=True): - if array.shape != shape: - raise ValueError("Invalid data shape: expected %s, got %s" % - (shape, array.shape)) - if array.dtype != dtype: - raise ValueError("Invalid data type: expected %s, got %s" % - (dtype, array.dtype)) - - def set_data(self, dst, src, shape, dtype, copy=True, name=None): - """ - dst is a device array owned by the current instance - (either self.data_in or self.data_out). - - copy is ignored for device<-> arrays. - """ - self.check_array(src, shape, dtype) - if isinstance(src, np.ndarray): - if name == "data_out": - # Makes little sense to provide output=numpy_array - return dst - if not(src.flags["C_CONTIGUOUS"]): - src = np.ascontiguousarray(src, dtype=dtype) - dst[:] = src[:] - elif isinstance(src, gpuarray.GPUArray): - # No copy, use the data as self.d_input or self.d_output - # (this prevents the use of in-place transforms, however). - # We have to keep their old references. - if name is None: - # This should not happen - raise ValueError("Please provide either copy=True or name != None") - assert id(self.refs[name]) == id(dst) # DEBUG - setattr(self, name, src) - return src - else: - raise ValueError( - "Invalid array type %s, expected numpy.ndarray or pycuda.gpuarray" % - type(src) - ) - return dst - - def recover_array_references(self): - self.data_in = self.refs["data_in"] - self.data_out = self.refs["data_out"] - - def compute_forward_plan(self): - self.plan_forward = Plan( - self.cufft_shape, - self.dtype, - self.dtype_out, - batch=self.cufft_batch_size, - stream=self.cufft_stream, - # cufft extensible plan API is only supported after 0.5.1 - # (commit 65288d28ca0b93e1234133f8d460dc6becb65121) - # but there is still no official 0.5.2 - #~ auto_allocate=True # cufft extensible plan API - ) - - def compute_inverse_plan(self): - self.plan_inverse = Plan( - self.cufft_shape, # not shape_out - self.dtype_out, - self.dtype, - batch=self.cufft_batch_size, - stream=self.cufft_stream, - # cufft extensible plan API is only supported after 0.5.1 - # (commit 65288d28ca0b93e1234133f8d460dc6becb65121) - # but there is still no official 0.5.2 - #~ auto_allocate=True - ) - - def copy_output_if_numpy(self, dst, src): - if isinstance(dst, gpuarray.GPUArray): - return - dst[:] = src[:] - - def fft(self, array, output=None): - """ - Perform a (forward) Fast Fourier Transform. - - :param Union[numpy.ndarray,pycuda.gpuarray] array: - Input data. Must be consistent with the current context. - :param Union[numpy.ndarray,pycuda.gpuarray] output: - Output data. By default, output is a numpy.ndarray. - """ - data_in = self.set_input_data(array, copy=False) - data_out = self.set_output_data(output, copy=False) - - cu_fft( - data_in, - data_out, - self.plan_forward, - scale=False - ) - - if output is not None: - self.copy_output_if_numpy(output, self.data_out) - res = output - else: - res = self.data_out.get() - self.recover_array_references() - return res - - def ifft(self, array, output=None): - """ - Perform a (inverse) Fast Fourier Transform. - - :param Union[numpy.ndarray,pycuda.gpuarray] array: - Input data. Must be consistent with the current context. - :param Union[numpy.ndarray,pycuda.gpuarray] output: - Output data. By default, output is a numpy.ndarray. - """ - data_in = self.set_output_data(array, copy=False) - data_out = self.set_input_data(output, copy=False) - - cu_ifft( - data_in, - data_out, - self.plan_inverse, - scale=self.cufft_scale_inverse, - ) - - if output is not None: - self.copy_output_if_numpy(output, self.data_in) - res = output - else: - res = self.data_in.get() - self.recover_array_references() - return res diff --git a/silx/math/fft/fft.py b/silx/math/fft/fft.py deleted file mode 100644 index eb0d73b..0000000 --- a/silx/math/fft/fft.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018-2019 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. -# -# ###########################################################################*/ -from .fftw import FFTW -from .clfft import CLFFT -from .npfft import NPFFT -from .cufft import CUFFT - - -def FFT( - shape=None, - dtype=None, - template=None, - shape_out=None, - axes=None, - normalize="rescale", - backend="numpy", - **kwargs -): - """ - Initialize a FFT plan. - - :param List[int] shape: - Shape of the input data. - :param numpy.dtype dtype: - Data type of the input data. - :param numpy.ndarray template: - Optional data, replacement for "shape" and "dtype". - If provided, the arguments "shape" and "dtype" are ignored, - and are instead inferred from it. - :param List[int] shape_out: - Optional shape of output data. - By default, the data has the same shape as the input - data (in case of C2C transform), or a shape with the last dimension halved - (in case of R2C transform). If shape_out is provided, it must be greater - or equal than the shape of input data. In this case, FFT is performed - with zero-padding. - :param List[int] axes: - Axes along which FFT is computed. - * For 2D transform: axes=(1,0) - * For batched 1D transform of 2D image: axes=(0,) - :param str normalize: - Whether to normalize FFT and IFFT. Possible values are: - * "rescale": in this case, Fourier data is divided by "N" - before IFFT, so that (FFT(data)) = data - * "ortho": in this case, FFT and IFFT are adjoint of eachother, - the transform is unitary. Both FFT and IFFT are scaled with 1/sqrt(N). - * "none": no normalizatio is done : IFFT(FFT(data)) = data*N - :param str backend: - FFT Backend to use. Value can be "numpy", "fftw", "opencl", "cuda". - """ - backends = { - "numpy": NPFFT, - "np": NPFFT, - "fftw": FFTW, - "opencl": CLFFT, - "clfft": CLFFT, - "cuda": CUFFT, - "cufft": CUFFT, - } - - backend = backend.lower() - if backend not in backends: - raise ValueError("Unknown backend %s, available are %s" % (backend, backends)) - F = backends[backend]( - shape=shape, - dtype=dtype, - template=template, - shape_out=shape_out, - axes=axes, - normalize=normalize, - **kwargs - ) - return F diff --git a/silx/math/fft/fftw.py b/silx/math/fft/fftw.py deleted file mode 100644 index ff6966c..0000000 --- a/silx/math/fft/fftw.py +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018-2019 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. -# -# ###########################################################################*/ -import numpy as np - -from .basefft import BaseFFT, check_version -try: - import pyfftw - __have_fftw__ = True -except ImportError: - __have_fftw__ = False - - -# Check pyfftw version -__required_pyfftw_version__ = "0.10.0" -if __have_fftw__: - __have_fftw__ = check_version(pyfftw, __required_pyfftw_version__) - - -class FFTW(BaseFFT): - """Initialize a FFTW plan. - - Please see FFT class for parameters help. - - FFTW-specific parameters - ------------------------- - - :param bool check_alignment: - If set to True and "data" is provided, this will enforce the input data - to be "byte aligned", which might imply extra memory usage. - :param int num_threads: - Number of threads for computing FFT. - """ - def __init__( - self, - shape=None, - dtype=None, - template=None, - shape_out=None, - axes=None, - normalize="rescale", - check_alignment=False, - num_threads=1, - ): - if not(__have_fftw__): - raise ImportError("Please install pyfftw >= %s to use the FFTW back-end" % __required_pyfftw_version__) - super(FFTW, self).__init__( - shape=shape, - dtype=dtype, - template=template, - shape_out=shape_out, - axes=axes, - normalize=normalize, - ) - self.check_alignment = check_alignment - self.num_threads = num_threads - self.backend = "fftw" - - self.allocate_arrays() - self.set_fftw_flags() - self.compute_forward_plan() - self.compute_inverse_plan() - self.refs = { - "data_in": self.data_in, - "data_out": self.data_out, - } - - def set_fftw_flags(self): - self.fftw_flags = ('FFTW_MEASURE', ) # TODO - self.fftw_planning_timelimit = None # TODO - self.fftw_norm_modes = { - "rescale": {"ortho": False, "normalize": True}, - "ortho": {"ortho": True, "normalize": False}, - "none": {"ortho": False, "normalize": False}, - } - if self.normalize not in self.fftw_norm_modes: - raise ValueError("Unknown normalization mode %s. Possible values are %s" % - (self.normalize, self.fftw_norm_modes.keys()) - ) - self.fftw_norm_mode = self.fftw_norm_modes[self.normalize] - - def _allocate(self, shape, dtype): - return pyfftw.zeros_aligned(shape, dtype=dtype) - - def check_array(self, array, shape, dtype, copy=True): - if array.shape != shape: - raise ValueError("Invalid data shape: expected %s, got %s" % - (shape, array.shape) - ) - if array.dtype != dtype: - raise ValueError("Invalid data type: expected %s, got %s" % - (dtype, array.dtype) - ) - - def set_data(self, self_array, array, shape, dtype, copy=True, name=None): - """ - :param self_array: array owned by the current instance - (either self.data_in or self.data_out). - :type: numpy.ndarray - :param self_array: data to set - :type: numpy.ndarray - :type tuple shape: shape of the array - :param dtype: type of the array - :type: numpy.dtype - :param bool copy: should we copy the array - :param str name: name of the array - - Copies are avoided when possible. - """ - self.check_array(array, shape, dtype) - if id(self.refs[name]) == id(array): - # nothing to do: fft is performed on self.data_in or self.data_out - arr_to_use = self.refs[name] - if self.check_alignment and not(pyfftw.is_byte_aligned(array)): - # If the array is not properly aligned, - # create a temp. array copy it to self.data_in or self.data_out - self_array[:] = array[:] - arr_to_use = self_array - else: - # If the array is properly aligned, use it directly - if copy: - arr_to_use = np.copy(array) - else: - arr_to_use = array - return arr_to_use - - def compute_forward_plan(self): - self.plan_forward = pyfftw.FFTW( - self.data_in, - self.data_out, - axes=self.axes, - direction='FFTW_FORWARD', - flags=self.fftw_flags, - threads=self.num_threads, - planning_timelimit=self.fftw_planning_timelimit, - # the following seems to be taken into account only when using __call__ - ortho=self.fftw_norm_mode["ortho"], - normalise_idft=self.fftw_norm_mode["normalize"], - ) - - def compute_inverse_plan(self): - self.plan_inverse = pyfftw.FFTW( - self.data_out, - self.data_in, - axes=self.axes, - direction='FFTW_BACKWARD', - flags=self.fftw_flags, - threads=self.num_threads, - planning_timelimit=self.fftw_planning_timelimit, - # the following seem to be taken into account only when using __call__ - ortho=self.fftw_norm_mode["ortho"], - normalise_idft=self.fftw_norm_mode["normalize"], - ) - - def fft(self, array, output=None): - """ - Perform a (forward) Fast Fourier Transform. - - :param numpy.ndarray array: - Input data. Must be consistent with the current context. - :param numpy.ndarray output: - Optional output data. - """ - data_in = self.set_input_data(array, copy=False) - data_out = self.set_output_data(output, copy=False) - self.plan_forward.update_arrays(data_in, data_out) - # execute.__call__ does both update_arrays() and normalization - self.plan_forward( - ortho=self.fftw_norm_mode["ortho"], - ) - self.plan_forward.update_arrays(self.refs["data_in"], self.refs["data_out"]) - return data_out - - def ifft(self, array, output=None): - """ - Perform a (inverse) Fast Fourier Transform. - - :param numpy.ndarray array: - Input data. Must be consistent with the current context. - :param numpy.ndarray output: - Optional output data. - """ - data_in = self.set_output_data(array, copy=False) - data_out = self.set_input_data(output, copy=False) - self.plan_inverse.update_arrays(data_in, data_out) - # execute.__call__ does both update_arrays() and normalization - self.plan_inverse( - ortho=self.fftw_norm_mode["ortho"], - normalise_idft=self.fftw_norm_mode["normalize"] - ) - self.plan_inverse.update_arrays(self.refs["data_out"], self.refs["data_in"]) - return data_out diff --git a/silx/math/fft/npfft.py b/silx/math/fft/npfft.py deleted file mode 100644 index 20351de..0000000 --- a/silx/math/fft/npfft.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018-2019 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. -# -# ###########################################################################*/ -import numpy as np - -from .basefft import BaseFFT - - -class NPFFT(BaseFFT): - """Initialize a numpy plan. - - Please see FFT class for parameters help. - """ - def __init__( - self, - shape=None, - dtype=None, - template=None, - shape_out=None, - axes=None, - normalize="rescale", - ): - super(NPFFT, self).__init__( - shape=shape, - dtype=dtype, - template=template, - shape_out=shape_out, - axes=axes, - normalize=normalize, - ) - self.backend = "numpy" - self.real_transform = False - if template is not None and np.isrealobj(template): - self.real_transform = True - # For numpy functions. - # TODO Issue warning if user wants ifft(fft(data)) = N*data ? - if normalize != "ortho": - self.normalize = None - self.set_fft_functions() - #~ self.allocate_arrays() # not needed for this backend - self.compute_plans() - - - def set_fft_functions(self): - # (fwd, inv) = _fft_functions[is_real][ndim] - self._fft_functions = { - True: { - 1: (np.fft.rfft, np.fft.irfft), - 2: (np.fft.rfft2, np.fft.irfft2), - 3: (np.fft.rfftn, np.fft.irfftn), - }, - False: { - 1: (np.fft.fft, np.fft.ifft), - 2: (np.fft.fft2, np.fft.ifft2), - 3: (np.fft.fftn, np.fft.ifftn), - } - } - - - def _allocate(self, shape, dtype): - return np.zeros(self.queue, shape, dtype=dtype) - - - def compute_plans(self): - ndim = len(self.shape) - funcs = self._fft_functions[self.real_transform][np.minimum(ndim, 3)] - if np.version.version[:4] in ["1.8.", "1.9."]: - # norm keyword was introduced in 1.10 and we support numpy >= 1.8 - self.numpy_args = {} - else: - self.numpy_args = {"norm": self.normalize} - # Batched transform - if (self.user_axes is not None) and len(self.user_axes) < ndim: - funcs = self._fft_functions[self.real_transform][np.minimum(ndim-1, 3)] - self.numpy_args["axes"] = self.user_axes - # Special case of batched 1D transform on 2D data - if ndim == 2: - assert len(self.user_axes) == 1 - self.numpy_args["axis"] = self.user_axes[0] - self.numpy_args.pop("axes") - self.numpy_funcs = funcs - - - def fft(self, array): - """ - Perform a (forward) Fast Fourier Transform. - - :param numpy.ndarray array: - Input data. Must be consistent with the current context. - """ - return self.numpy_funcs[0](array, **self.numpy_args) - - - def ifft(self, array): - """ - Perform a (inverse) Fast Fourier Transform. - - :param numpy.ndarray array: - Input data. Must be consistent with the current context. - """ - return self.numpy_funcs[1](array, **self.numpy_args) - diff --git a/silx/math/fft/setup.py b/silx/math/fft/setup.py deleted file mode 100644 index 76bb864..0000000 --- a/silx/math/fft/setup.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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. -# -# ############################################################################*/ - -__authors__ = ["P. Naudet"] -__license__ = "MIT" -__date__ = "12/12/2018" - -import numpy -from numpy.distutils.misc_util import Configuration - - -def configuration(parent_package='', top_path=None): - config = Configuration('fft', parent_package, top_path) - config.add_subpackage('test') - return config - - -if __name__ == "__main__": - from numpy.distutils.core import setup - setup(configuration=configuration) diff --git a/silx/math/fft/test/__init__.py b/silx/math/fft/test/__init__.py deleted file mode 100644 index 83f8926..0000000 --- a/silx/math/fft/test/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-2019 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. -# -# ############################################################################*/ - -from .test_fft import suite diff --git a/silx/math/fft/test/test_fft.py b/silx/math/fft/test/test_fft.py deleted file mode 100644 index 9ef2fd2..0000000 --- a/silx/math/fft/test/test_fft.py +++ /dev/null @@ -1,270 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018-2019 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. -# -# ###########################################################################*/ -"""Test of the FFT module""" - -import numpy as np -import unittest -import logging -try: - from scipy.misc import ascent - __have_scipy = True -except ImportError: - __have_scipy = False -from silx.utils.testutils import ParametricTestCase -from silx.math.fft.fft import FFT -from silx.math.fft.clfft import __have_clfft__ -from silx.math.fft.cufft import __have_cufft__ -from silx.math.fft.fftw import __have_fftw__ - -from silx.test.utils import test_options - -logger = logging.getLogger(__name__) - - -class TransformInfos(object): - def __init__(self): - self.dimensions = [ - "1D", - "batched_1D", - "2D", - "batched_2D", - "3D", - ] - self.modes = { - "R2C": np.float32, - "R2C_double": np.float64, - "C2C": np.complex64, - "C2C_double": np.complex128, - } - self.sizes = { - "1D": [(128,), (127,)], - "2D": [(128, 128), (128, 127), (127, 128), (127, 127)], - "3D": [(64, 64, 64), (64, 64, 63), (64, 63, 64), (63, 64, 64), - (64, 63, 63), (63, 64, 63), (63, 63, 64), (63, 63, 63)] - } - self.axes = { - "1D": None, - "batched_1D": (-1,), - "2D": None, - "batched_2D": (-2, -1), - "3D": None, - } - self.sizes["batched_1D"] = self.sizes["2D"] - self.sizes["batched_2D"] = self.sizes["3D"] - - -class TestData(object): - def __init__(self): - self.data = ascent().astype("float32") - self.data1d = self.data[:, 0] # non-contiguous data - self.data3d = np.tile(self.data[:64, :64], (64, 1, 1)) - self.data_refs = { - 1: self.data1d, - 2: self.data, - 3: self.data3d, - } - - -@unittest.skipUnless(__have_scipy, "scipy is missing") -class TestFFT(ParametricTestCase): - """Test cuda/opencl/fftw backends of FFT""" - - def setUp(self): - self.tol = { - np.dtype("float32"): 1e-3, - np.dtype("float64"): 1e-9, - np.dtype("complex64"): 1e-3, - np.dtype("complex128"): 1e-9, - } - self.transform_infos = TransformInfos() - self.test_data = TestData() - - @staticmethod - def calc_mae(arr1, arr2): - """ - Compute the Max Absolute Error between two arrays - """ - return np.max(np.abs(arr1 - arr2)) - - @unittest.skipIf(not __have_cufft__, - "cuda back-end requires pycuda and scikit-cuda") - def test_cuda(self): - import pycuda.autoinit - - # Error is higher when using cuda. fast_math mode ? - self.tol[np.dtype("float32")] *= 2 - - self.__run_tests(backend="cuda") - - @unittest.skipIf(not __have_clfft__, - "opencl back-end requires pyopencl and gpyfft") - def test_opencl(self): - from silx.opencl.common import ocl - if ocl is not None: - self.__run_tests(backend="opencl", ctx=ocl.create_context()) - - @unittest.skipIf(not __have_fftw__, - "fftw back-end requires pyfftw") - def test_fftw(self): - self.__run_tests(backend="fftw") - - def __run_tests(self, backend, **extra_args): - """Run all tests with the given backend - - :param str backend: - :param dict extra_args: Additional arguments to provide to FFT - """ - for trdim in self.transform_infos.dimensions: - for mode in self.transform_infos.modes: - for size in self.transform_infos.sizes[trdim]: - with self.subTest(trdim=trdim, mode=mode, size=size): - self.__test(backend, trdim, mode, size, **extra_args) - - def __test(self, backend, trdim, mode, size, **extra_args): - """Compare given backend with numpy for given conditions""" - logger.debug("backend: %s, trdim: %s, mode: %s, size: %s", - backend, trdim, mode, str(size)) - if size == "3D" and test_options.TEST_LOW_MEM: - self.skipTest("low mem") - - ndim = len(size) - input_data = self.test_data.data_refs[ndim].astype( - self.transform_infos.modes[mode]) - tol = self.tol[np.dtype(input_data.dtype)] - if trdim == "3D": - tol *= 10 # Error is relatively high in high dimensions - # It seems that cuda has problems with C2D batched 1D - if trdim == "batched_1D" and backend == "cuda" and mode == "C2C": - tol *= 10 - - # Python < 3.5 does not want to mix **extra_args with existing kwargs - fft_args = { - "template": input_data, - "axes": self.transform_infos.axes[trdim], - "backend": backend, - } - fft_args.update(extra_args) - F = FFT( - **fft_args - ) - F_np = FFT( - template=input_data, - axes=self.transform_infos.axes[trdim], - backend="numpy" - ) - - # Forward FFT - res = F.fft(input_data) - res_np = F_np.fft(input_data) - mae = self.calc_mae(res, res_np) - all_close = np.allclose(res, res_np, atol=tol, rtol=tol), - self.assertTrue( - all_close, - "FFT %s:%s, MAE(%s, numpy) = %f (tol = %.2e)" % (mode, trdim, backend, mae, tol) - ) - - # Inverse FFT - res2 = F.ifft(res) - mae = self.calc_mae(res2, input_data) - self.assertTrue( - mae < tol, - "IFFT %s:%s, MAE(%s, numpy) = %f" % (mode, trdim, backend, mae) - ) - - -@unittest.skipUnless(__have_scipy, "scipy is missing") -class TestNumpyFFT(ParametricTestCase): - """ - Test the Numpy backend individually. - """ - - def setUp(self): - transforms = { - "1D": { - True: (np.fft.rfft, np.fft.irfft), - False: (np.fft.fft, np.fft.ifft), - }, - "2D": { - True: (np.fft.rfft2, np.fft.irfft2), - False: (np.fft.fft2, np.fft.ifft2), - }, - "3D": { - True: (np.fft.rfftn, np.fft.irfftn), - False: (np.fft.fftn, np.fft.ifftn), - }, - } - transforms["batched_1D"] = transforms["1D"] - transforms["batched_2D"] = transforms["2D"] - self.transforms = transforms - self.transform_infos = TransformInfos() - self.test_data = TestData() - - def test(self): - """Test the numpy backend against native fft. - - Results should be exactly the same. - """ - for trdim in self.transform_infos.dimensions: - for mode in self.transform_infos.modes: - for size in self.transform_infos.sizes[trdim]: - with self.subTest(trdim=trdim, mode=mode, size=size): - self.__test(trdim, mode, size) - - def __test(self, trdim, mode, size): - logger.debug("trdim: %s, mode: %s, size: %s", trdim, mode, str(size)) - ndim = len(size) - input_data = self.test_data.data_refs[ndim].astype( - self.transform_infos.modes[mode]) - np_fft, np_ifft = self.transforms[trdim][np.isrealobj(input_data)] - - F = FFT( - template=input_data, - axes=self.transform_infos.axes[trdim], - backend="numpy" - ) - # Test FFT - res = F.fft(input_data) - ref = np_fft(input_data) - self.assertTrue(np.allclose(res, ref)) - - # Test IFFT - res2 = F.ifft(res) - ref2 = np_ifft(ref) - self.assertTrue(np.allclose(res2, ref2)) - - -def suite(): - suite = unittest.TestSuite() - for cls in (TestNumpyFFT, TestFFT): - suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(cls)) - return suite - - -if __name__ == '__main__': - unittest.main(defaultTest="suite") - - diff --git a/silx/math/fit/__init__.py b/silx/math/fit/__init__.py deleted file mode 100644 index 29e6a9e..0000000 --- a/silx/math/fit/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "22/06/2016" - - -from .leastsq import leastsq, chisq_alpha_beta -from .leastsq import \ - CFREE, CPOSITIVE, CQUOTED, CFIXED, \ - CFACTOR, CDELTA, CSUM - -from .functions import * -from .filters import * -from .peaks import peak_search, guess_fwhm -from .fitmanager import FitManager -from .fittheory import FitTheory diff --git a/silx/math/fit/bgtheories.py b/silx/math/fit/bgtheories.py deleted file mode 100644 index 631c43e..0000000 --- a/silx/math/fit/bgtheories.py +++ /dev/null @@ -1,440 +0,0 @@ -# coding: utf-8 -#/*########################################################################## -# -# Copyright (c) 2004-2020 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 modules defines a set of background model functions and associated -estimation functions in a format that can be imported into a -:class:`silx.math.fit.FitManager` object. - -A background function is a function that you want to add to a regular fit -function prior to fitting the sum of both functions. This is useful, for -instance, if you need to fit multiple gaussian peaks in an array of -measured data points when the measurement is polluted by a background signal. - -The models include common background models such as a constant value or a -linear background. - -It also includes background computation filters - *strip* and *snip* - that -can extract a more complex low-curvature background signal from a signal with -peaks having higher curvatures. - -The source code of this module can serve as a template for defining your -own fit background theories. The minimal skeleton of such a theory definition -file is:: - - from silx.math.fit.fittheory import FitTheory - - def bgfunction1(x, y0, …): - bg_signal = … - return bg_signal - - def estimation_function1(x, y): - … - estimated_params = … - constraints = … - return estimated_params, constraints - - THEORY = { - 'bg_theory_name1': FitTheory( - description='Description of theory 1', - function=bgfunction1, - parameters=('param name 1', 'param name 2', …), - estimate=estimation_function1, - configure=configuration_function1, - derivative=derivative_function1, - is_background=True), - 'theory_name_2': …, - } -""" -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "16/01/2017" - -from collections import OrderedDict -import numpy -from silx.math.fit.filters import strip, snip1d,\ - savitsky_golay -from silx.math.fit.fittheory import FitTheory - -CONFIG = { - "SmoothingFlag": False, - "SmoothingWidth": 5, - "AnchorsFlag": False, - "AnchorsList": [], - "StripWidth": 2, - "StripIterations": 5000, - "StripThresholdFactor": 1.0, - "SnipWidth": 16, - "EstimatePolyOnStrip": True -} - -# to avoid costly computations when parameters stay the same -_BG_STRIP_OLDY = numpy.array([]) -_BG_STRIP_OLDPARS = [0, 0] -_BG_STRIP_OLDBG = numpy.array([]) - -_BG_SNIP_OLDY = numpy.array([]) -_BG_SNIP_OLDWIDTH = None -_BG_SNIP_OLDBG = numpy.array([]) - - -_BG_OLD_ANCHORS = [] -_BG_OLD_ANCHORS_FLAG = None - -_BG_SMOOTH_OLDWIDTH = None -_BG_SMOOTH_OLDFLAG = None - - -def _convert_anchors_to_indices(x): - """Anchors stored in CONFIG["AnchorsList"] are abscissa. - Convert then to indices (take first index where x >= anchor), - then return the list of indices. - - :param x: Original array of abscissa - :return: List of indices of anchors in x array. - If CONFIG['AnchorsFlag'] is False or None, or if the list - of indices is empty, return None. - """ - # convert anchor X abscissa to index - if CONFIG['AnchorsFlag'] and CONFIG['AnchorsList'] is not None: - anchors_indices = [] - for anchor_x in CONFIG['AnchorsList']: - if anchor_x <= x[0]: - continue - # take the first index where x > anchor_x - indices = numpy.nonzero(x >= anchor_x)[0] - if len(indices): - anchors_indices.append(min(indices)) - if not len(anchors_indices): - anchors_indices = None - else: - anchors_indices = None - - return anchors_indices - - -def strip_bg(x, y0, width, niter): - """Extract and return the strip bg from y0. - - Use anchors coordinates in CONFIG["AnchorsList"] if flag - CONFIG["AnchorsFlag"] is True. Convert anchors from x coordinate - to array index prior to passing it to silx.math.fit.filters.strip - - :param x: Abscissa array - :param x: Ordinate array (data values at x positions) - :param width: strip width - :param niter: strip niter - """ - global _BG_STRIP_OLDY - global _BG_STRIP_OLDPARS - global _BG_STRIP_OLDBG - global _BG_SMOOTH_OLDWIDTH - global _BG_SMOOTH_OLDFLAG - global _BG_OLD_ANCHORS - global _BG_OLD_ANCHORS_FLAG - - parameters_changed =\ - _BG_STRIP_OLDPARS != [width, niter] or\ - _BG_SMOOTH_OLDWIDTH != CONFIG["SmoothingWidth"] or\ - _BG_SMOOTH_OLDFLAG != CONFIG["SmoothingFlag"] or\ - _BG_OLD_ANCHORS_FLAG != CONFIG["AnchorsFlag"] or\ - _BG_OLD_ANCHORS != CONFIG["AnchorsList"] - - # same parameters - if not parameters_changed: - # same data - if numpy.array_equal(_BG_STRIP_OLDY, y0): - # same result - return _BG_STRIP_OLDBG - - _BG_STRIP_OLDY = y0 - _BG_STRIP_OLDPARS = [width, niter] - _BG_SMOOTH_OLDWIDTH = CONFIG["SmoothingWidth"] - _BG_SMOOTH_OLDFLAG = CONFIG["SmoothingFlag"] - _BG_OLD_ANCHORS = CONFIG["AnchorsList"] - _BG_OLD_ANCHORS_FLAG = CONFIG["AnchorsFlag"] - - y1 = savitsky_golay(y0, CONFIG["SmoothingWidth"]) if CONFIG["SmoothingFlag"] else y0 - - anchors_indices = _convert_anchors_to_indices(x) - - background = strip(y1, - w=width, - niterations=niter, - factor=CONFIG["StripThresholdFactor"], - anchors=anchors_indices) - - _BG_STRIP_OLDBG = background - - return background - - -def snip_bg(x, y0, width): - """Compute the snip bg for y0""" - global _BG_SNIP_OLDY - global _BG_SNIP_OLDWIDTH - global _BG_SNIP_OLDBG - global _BG_SMOOTH_OLDWIDTH - global _BG_SMOOTH_OLDFLAG - global _BG_OLD_ANCHORS - global _BG_OLD_ANCHORS_FLAG - - parameters_changed =\ - _BG_SNIP_OLDWIDTH != width or\ - _BG_SMOOTH_OLDWIDTH != CONFIG["SmoothingWidth"] or\ - _BG_SMOOTH_OLDFLAG != CONFIG["SmoothingFlag"] or\ - _BG_OLD_ANCHORS_FLAG != CONFIG["AnchorsFlag"] or\ - _BG_OLD_ANCHORS != CONFIG["AnchorsList"] - - # same parameters - if not parameters_changed: - # same data - if numpy.sum(_BG_SNIP_OLDY == y0) == len(y0): - # same result - return _BG_SNIP_OLDBG - - _BG_SNIP_OLDY = y0 - _BG_SNIP_OLDWIDTH = width - _BG_SMOOTH_OLDWIDTH = CONFIG["SmoothingWidth"] - _BG_SMOOTH_OLDFLAG = CONFIG["SmoothingFlag"] - _BG_OLD_ANCHORS = CONFIG["AnchorsList"] - _BG_OLD_ANCHORS_FLAG = CONFIG["AnchorsFlag"] - - y1 = savitsky_golay(y0, CONFIG["SmoothingWidth"]) if CONFIG["SmoothingFlag"] else y0 - - anchors_indices = _convert_anchors_to_indices(x) - - if anchors_indices is None or not len(anchors_indices): - anchors_indices = [0, len(y1) - 1] - - background = numpy.zeros_like(y1) - previous_anchor = 0 - for anchor_index in anchors_indices: - if (anchor_index > previous_anchor) and (anchor_index < len(y1)): - background[previous_anchor:anchor_index] =\ - snip1d(y1[previous_anchor:anchor_index], - width) - previous_anchor = anchor_index - - if previous_anchor < len(y1): - background[previous_anchor:] = snip1d(y1[previous_anchor:], - width) - - _BG_SNIP_OLDBG = background - - return background - - -def estimate_linear(x, y): - """ - Estimate the linear parameters (constant, slope) of a y signal. - - Strip peaks, then perform a linear regression. - """ - bg = strip_bg(x, y, - width=CONFIG["StripWidth"], - niter=CONFIG["StripIterations"]) - n = float(len(bg)) - Sy = numpy.sum(bg) - Sx = float(numpy.sum(x)) - Sxx = float(numpy.sum(x * x)) - Sxy = float(numpy.sum(x * bg)) - - deno = n * Sxx - (Sx * Sx) - if deno != 0: - bg = (Sxx * Sy - Sx * Sxy) / deno - slope = (n * Sxy - Sx * Sy) / deno - else: - bg = 0.0 - slope = 0.0 - estimated_par = [bg, slope] - # code = 0: FREE - constraints = [[0, 0, 0], [0, 0, 0]] - return estimated_par, constraints - - -def estimate_strip(x, y): - """Estimation function for strip parameters. - - Return parameters as defined in CONFIG dict, - set constraints to FIXED. - """ - estimated_par = [CONFIG["StripWidth"], - CONFIG["StripIterations"]] - constraints = numpy.zeros((len(estimated_par), 3), numpy.float64) - # code = 3: FIXED - constraints[0][0] = 3 - constraints[1][0] = 3 - return estimated_par, constraints - - -def estimate_snip(x, y): - """Estimation function for snip parameters. - - Return parameters as defined in CONFIG dict, - set constraints to FIXED. - """ - estimated_par = [CONFIG["SnipWidth"]] - constraints = numpy.zeros((len(estimated_par), 3), numpy.float64) - # code = 3: FIXED - constraints[0][0] = 3 - return estimated_par, constraints - - -def poly(x, y, *pars): - """Order n polynomial. - The order of the polynomial is defined by the number of - coefficients (``*pars``). - - """ - p = numpy.poly1d(pars) - return p(x) - - -def estimate_poly(x, y, deg=2): - """Estimate polynomial coefficients. - - """ - # extract bg signal with strip, to estimate polynomial on background - if CONFIG["EstimatePolyOnStrip"]: - y = strip_bg(x, y, - CONFIG["StripWidth"], - CONFIG["StripIterations"]) - pcoeffs = numpy.polyfit(x, y, deg) - cons = numpy.zeros((deg + 1, 3), numpy.float64) - return pcoeffs, cons - - -def estimate_quadratic_poly(x, y): - """Estimate quadratic polynomial coefficients. - """ - return estimate_poly(x, y, deg=2) - - -def estimate_cubic_poly(x, y): - """Estimate cubic polynomial coefficients. - """ - return estimate_poly(x, y, deg=3) - - -def estimate_quartic_poly(x, y): - """Estimate degree 4 polynomial coefficients. - """ - return estimate_poly(x, y, deg=4) - - -def estimate_quintic_poly(x, y): - """Estimate degree 5 polynomial coefficients. - """ - return estimate_poly(x, y, deg=5) - - -def configure(**kw): - """Update the CONFIG dict - """ - # inspect **kw to find known keys, update them in CONFIG - for key in CONFIG: - if key in kw: - CONFIG[key] = kw[key] - - return CONFIG - - -THEORY = OrderedDict( - (('No Background', - FitTheory( - description="No background function", - function=lambda x, y0: numpy.zeros_like(x), - parameters=[], - is_background=True)), - ('Constant', - FitTheory( - description='Constant background', - function=lambda x, y0, c: c * numpy.ones_like(x), - parameters=['Constant', ], - estimate=lambda x, y: ([min(y)], [[0, 0, 0]]), - is_background=True)), - ('Linear', - FitTheory( - description="Linear background, parameters 'Constant' and" - " 'Slope'", - function=lambda x, y0, a, b: a + b * x, - parameters=['Constant', 'Slope'], - estimate=estimate_linear, - configure=configure, - is_background=True)), - ('Strip', - FitTheory( - description="Compute background using a strip filter\n" - "Parameters 'StripWidth', 'StripIterations'", - function=strip_bg, - parameters=['StripWidth', 'StripIterations'], - estimate=estimate_strip, - configure=configure, - is_background=True)), - ('Snip', - FitTheory( - description="Compute background using a snip filter\n" - "Parameter 'SnipWidth'", - function=snip_bg, - parameters=['SnipWidth'], - estimate=estimate_snip, - configure=configure, - is_background=True)), - ('Degree 2 Polynomial', - FitTheory( - description="Quadratic polynomial background, Parameters " - "'a', 'b' and 'c'\ny = a*x^2 + b*x +c", - function=poly, - parameters=['a', 'b', 'c'], - estimate=estimate_quadratic_poly, - configure=configure, - is_background=True)), - ('Degree 3 Polynomial', - FitTheory( - description="Cubic polynomial background, Parameters " - "'a', 'b', 'c' and 'd'\n" - "y = a*x^3 + b*x^2 + c*x + d", - function=poly, - parameters=['a', 'b', 'c', 'd'], - estimate=estimate_cubic_poly, - configure=configure, - is_background=True)), - ('Degree 4 Polynomial', - FitTheory( - description="Quartic polynomial background\n" - "y = a*x^4 + b*x^3 + c*x^2 + d*x + e", - function=poly, - parameters=['a', 'b', 'c', 'd', 'e'], - estimate=estimate_quartic_poly, - configure=configure, - is_background=True)), - ('Degree 5 Polynomial', - FitTheory( - description="Quaintic polynomial background\n" - "y = a*x^5 + b*x^4 + c*x^3 + d*x^2 + e*x + f", - function=poly, - parameters=['a', 'b', 'c', 'd', 'e', 'f'], - estimate=estimate_quintic_poly, - configure=configure, - is_background=True)))) diff --git a/silx/math/fit/filters.pyx b/silx/math/fit/filters.pyx deleted file mode 100644 index da1f6f5..0000000 --- a/silx/math/fit/filters.pyx +++ /dev/null @@ -1,416 +0,0 @@ -# coding: utf-8 -#/*########################################################################## -# Copyright (C) 2016-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 background extraction functions and smoothing -functions. These functions are extracted from PyMca module SpecFitFuns. - -Index of background extraction functions: ------------------------------------------- - - - :func:`strip` - - :func:`snip1d` - - :func:`snip2d` - - :func:`snip3d` - -Smoothing functions: --------------------- - - - :func:`savitsky_golay` - - :func:`smooth1d` - - :func:`smooth2d` - - :func:`smooth3d` - -References: ------------ - -.. [Morhac97] Miroslav Morháč et al. - Background elimination methods for multidimensional coincidence γ-ray spectra. - Nucl. Instruments and Methods in Physics Research A401 (1997) 113-132. - https://doi.org/10.1016/S0168-9002(97)01023-1 - -.. [Ryan88] C.G. Ryan et al. - SNIP, a statistics-sensitive background treatment for the quantitative analysis of PIXE spectra in geoscience applications. - Nucl. Instruments and Methods in Physics Research B34 (1988) 396-402*. - https://doi.org/10.1016/0168-583X(88)90063-8 - -API documentation: -------------------- - -""" - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "15/05/2017" - -import logging -import numpy - -_logger = logging.getLogger(__name__) - -cimport cython -cimport silx.math.fit.filters_wrapper as filters_wrapper - - -def strip(data, w=1, niterations=1000, factor=1.0, anchors=None): - """Extract background from data using the strip algorithm, as explained at - http://pymca.sourceforge.net/stripbackground.html. - - In its simplest implementation it is just as an iterative procedure - depending on two parameters. These parameters are the strip background - width ``w``, and the number of iterations. At each iteration, if the - contents of channel ``i``, ``y(i)``, is above the average of the contents - of the channels at ``w`` channels of distance, ``y(i-w)`` and - ``y(i+w)``, ``y(i)`` is replaced by the average. - At the end of the process we are left with something that resembles a spectrum - in which the peaks have been stripped. - - :param data: Data array - :type data: numpy.ndarray - :param w: Strip width - :param niterations: number of iterations - :param factor: scaling factor applied to the average of ``y(i-w)`` and - ``y(i+w)`` before comparing to ``y(i)`` - :param anchors: Array of anchors, indices of points that will not be - modified during the stripping procedure. - :return: Data with peaks stripped away - """ - cdef: - double[::1] input_c - double[::1] output - long[::1] anchors_c - - if not isinstance(data, numpy.ndarray): - if not hasattr(data, "__len__"): - raise TypeError("data must be a sequence (list, tuple) " + - "or a numpy array") - data_shape = (len(data), ) - else: - data_shape = data.shape - - input_c = numpy.array(data, - copy=True, - dtype=numpy.float64, - order='C').reshape(-1) - - output = numpy.empty(shape=(input_c.size,), - dtype=numpy.float64) - - if anchors is not None and len(anchors): - # numpy.int_ is the same as C long (http://docs.scipy.org/doc/numpy/user/basics.types.html) - anchors_c = numpy.array(anchors, - copy=False, - dtype=numpy.int_, - order='C') - len_anchors = anchors_c.size - else: - # Make a dummy length-1 array, because if I use shape=(0,) I get the error - # IndexError: Out of bounds on buffer access (axis 0) - anchors_c = numpy.empty(shape=(1,), - dtype=numpy.int_) - len_anchors = 0 - - - status = filters_wrapper.strip(&input_c[0], input_c.size, - factor, niterations, w, - &anchors_c[0], len_anchors, &output[0]) - - return numpy.asarray(output).reshape(data_shape) - - -def snip1d(data, snip_width): - """Estimate the baseline (background) of a 1D data vector by clipping peaks. - - Implementation of the algorithm SNIP in 1D is described in [Morhac97]_. - The original idea for 1D and the low-statistics-digital-filter (lsdf) comes - from [Ryan88]_. - - :param data: Data array, preferably 1D and of type *numpy.float64*. - Else, the data array will be flattened and converted to - *dtype=numpy.float64* prior to applying the snip filter. - :type data: numpy.ndarray - :param snip_width: Width of the snip operator, in number of samples. - A sample will be iteratively compared to it's neighbors up to a - distance of ``snip_width`` samples. This parameters has a direct - influence on the speed of the algorithm. - :type width: int - :return: Baseline of the input array, as an array of the same shape. - :rtype: numpy.ndarray - """ - cdef: - double[::1] data_c - - if not isinstance(data, numpy.ndarray): - if not hasattr(data, "__len__"): - raise TypeError("data must be a sequence (list, tuple) " + - "or a numpy array") - data_shape = (len(data), ) - else: - data_shape = data.shape - - data_c = numpy.array(data, - copy=True, - dtype=numpy.float64, - order='C').reshape(-1) - - filters_wrapper.snip1d(&data_c[0], data_c.size, snip_width) - - return numpy.asarray(data_c).reshape(data_shape) - - -def snip2d(data, snip_width): - """Estimate the baseline (background) of a 2D data signal by clipping peaks. - - Implementation of the algorithm SNIP in 2D described in [Morhac97]_. - - :param data: 2D array - :type data: numpy.ndarray - :param width: Width of the snip operator, in number of samples. A wider - snip operator will result in a smoother result (lower frequency peaks - will be clipped), and a longer computation time. - :type width: int - :return: Baseline of the input array, as an array of the same shape. - :rtype: numpy.ndarray - """ - cdef: - double[::1] data_c - - if not isinstance(data, numpy.ndarray): - if not hasattr(data, "__len__") or not hasattr(data[0], "__len__"): - raise TypeError("data must be a 2D sequence (list, tuple) " + - "or a 2D numpy array") - nrows = len(data) - ncolumns = len(data[0]) - data_shape = (len(data), len(data[0])) - - else: - data_shape = data.shape - nrows = data_shape[0] - if len(data_shape) == 2: - ncolumns = data_shape[1] - else: - raise TypeError("data array must be 2-dimensional") - - data_c = numpy.array(data, - copy=True, - dtype=numpy.float64, - order='C').reshape(-1) - - filters_wrapper.snip2d(&data_c[0], nrows, ncolumns, snip_width) - - return numpy.asarray(data_c).reshape(data_shape) - - -def snip3d(data, snip_width): - """Estimate the baseline (background) of a 3D data signal by clipping peaks. - - Implementation of the algorithm SNIP in 3D described in [Morhac97]_. - - :param data: 3D array - :type data: numpy.ndarray - :param width: Width of the snip operator, in number of samples. A wider - snip operator will result in a smoother result (lower frequency peaks - will be clipped), and a longer computation time. - :type width: int - - :return: Baseline of the input array, as an array of the same shape. - :rtype: numpy.ndarray - """ - cdef: - double[::1] data_c - - if not isinstance(data, numpy.ndarray): - if not hasattr(data, "__len__") or not hasattr(data[0], "__len__") or\ - not hasattr(data[0][0], "__len__"): - raise TypeError("data must be a 3D sequence (list, tuple) " + - "or a 3D numpy array") - nx = len(data) - ny = len(data[0]) - nz = len(data[0][0]) - data_shape = (len(data), len(data[0]), len(data[0][0])) - else: - data_shape = data.shape - nrows = data_shape[0] - if len(data_shape) == 3: - nx = data_shape[0] - ny = data_shape[1] - nz = data_shape[2] - else: - raise TypeError("data array must be 3-dimensional") - - data_c = numpy.array(data, - copy=True, - dtype=numpy.float64, - order='C').reshape(-1) - - filters_wrapper.snip3d(&data_c[0], nx, ny, nz, snip_width) - - return numpy.asarray(data_c).reshape(data_shape) - - -def savitsky_golay(data, npoints=5): - """Smooth a curve using a Savitsky-Golay filter. - - :param data: Input data - :type data: 1D numpy array - :param npoints: Size of the smoothing operator in number of samples - Must be between 3 and 100. - :return: Smoothed data - """ - cdef: - double[::1] data_c - double[::1] output - - data_c = numpy.array(data, - dtype=numpy.float64, - order='C').reshape(-1) - - output = numpy.empty(shape=(data_c.size,), - dtype=numpy.float64) - - status = filters_wrapper.SavitskyGolay(&data_c[0], data_c.size, - npoints, &output[0]) - - if status: - _logger.error("Smoothing failed. Check that npoints is greater " + - "than 3 and smaller than 100.") - - return numpy.asarray(output).reshape(data.shape) - - -def smooth1d(data): - """Simple smoothing for 1D data. - - For a data array :math:`y` of length :math:`n`, the smoothed array - :math:`ys` is calculated as a weighted average of neighboring samples: - - :math:`ys_0 = 0.75 y_0 + 0.25 y_1` - - :math:`ys_i = 0.25 (y_{i-1} + 2 y_i + y_{i+1})` for :math:`0 < i < n-1` - - :math:`ys_{n-1} = 0.25 y_{n-2} + 0.75 y_{n-1}` - - - :param data: 1D data array - :type data: numpy.ndarray - :return: Smoothed data - :rtype: numpy.ndarray(dtype=numpy.float64) - """ - cdef: - double[::1] data_c - - if not isinstance(data, numpy.ndarray): - if not hasattr(data, "__len__"): - raise TypeError("data must be a sequence (list, tuple) " + - "or a numpy array") - data_shape = (len(data), ) - else: - data_shape = data.shape - - data_c = numpy.array(data, - copy=True, - dtype=numpy.float64, - order='C').reshape(-1) - - filters_wrapper.smooth1d(&data_c[0], data_c.size) - - return numpy.asarray(data_c).reshape(data_shape) - - -def smooth2d(data): - """Simple smoothing for 2D data: - :func:`smooth1d` is applied succesively along both axis - - :param data: 2D data array - :type data: numpy.ndarray - :return: Smoothed data - :rtype: numpy.ndarray(dtype=numpy.float64) - """ - cdef: - double[::1] data_c - - if not isinstance(data, numpy.ndarray): - if not hasattr(data, "__len__") or not hasattr(data[0], "__len__"): - raise TypeError("data must be a 2D sequence (list, tuple) " + - "or a 2D numpy array") - nrows = len(data) - ncolumns = len(data[0]) - data_shape = (len(data), len(data[0])) - - else: - data_shape = data.shape - nrows = data_shape[0] - if len(data_shape) == 2: - ncolumns = data_shape[1] - else: - raise TypeError("data array must be 2-dimensional") - - data_c = numpy.array(data, - copy=True, - dtype=numpy.float64, - order='C').reshape(-1) - - filters_wrapper.smooth2d(&data_c[0], nrows, ncolumns) - - return numpy.asarray(data_c).reshape(data_shape) - - -def smooth3d(data): - """Simple smoothing for 3D data: - :func:`smooth2d` is applied on each 2D slice of the data volume along all - 3 axis - - :param data: 2D data array - :type data: numpy.ndarray - :return: Smoothed data - :rtype: numpy.ndarray(dtype=numpy.float64) - """ - cdef: - double[::1] data_c - - if not isinstance(data, numpy.ndarray): - if not hasattr(data, "__len__") or not hasattr(data[0], "__len__") or\ - not hasattr(data[0][0], "__len__"): - raise TypeError("data must be a 3D sequence (list, tuple) " + - "or a 3D numpy array") - nx = len(data) - ny = len(data[0]) - nz = len(data[0][0]) - data_shape = (len(data), len(data[0]), len(data[0][0])) - else: - data_shape = data.shape - nrows = data_shape[0] - if len(data_shape) == 3: - nx = data_shape[0] - ny = data_shape[1] - nz = data_shape[2] - else: - raise TypeError("data array must be 3-dimensional") - - data_c = numpy.array(data, - copy=True, - dtype=numpy.float64, - order='C').reshape(-1) - - filters_wrapper.smooth3d(&data_c[0], nx, ny, nz) - - return numpy.asarray(data_c).reshape(data_shape) diff --git a/silx/math/fit/filters/include/filters.h b/silx/math/fit/filters/include/filters.h deleted file mode 100644 index 1ee9a95..0000000 --- a/silx/math/fit/filters/include/filters.h +++ /dev/null @@ -1,45 +0,0 @@ -/*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ - -#ifndef FITFILTERS_H -#define FITFILTERS_H - -/* Background functions */ -void snip1d(double *data, int size, int width); -//void snip1d_multiple(double *data, int n_channels, int snip_width, int n_spectra); -void snip2d(double *data, int nrows, int ncolumns, int width); -void snip3d(double *data, int nx, int ny, int nz, int width); - -int strip(double* input, long len_input, double c, long niter, int deltai, - long* anchors, long len_anchors, double* output); - -/* Smoothing functions */ - -int SavitskyGolay(double* input, long len_input, int npoints, double* output); - -void smooth1d(double *data, int size); -void smooth2d(double *data, int size0, int size1); -void smooth3d(double *data, int size0, int size1, int size2); - - -#endif /* #define FITFILTERS_H */ diff --git a/silx/math/fit/filters/src/smoothnd.c b/silx/math/fit/filters/src/smoothnd.c deleted file mode 100644 index cb96961..0000000 --- a/silx/math/fit/filters/src/smoothnd.c +++ /dev/null @@ -1,317 +0,0 @@ -#/*########################################################################## -# -# Copyright (c) 2004-2016 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. -# -#############################################################################*/ -#include <stdlib.h> -#include <string.h> -#include <math.h> -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -#define MAX_SAVITSKY_GOLAY_WIDTH 101 -#define MIN_SAVITSKY_GOLAY_WIDTH 3 - -/* Wrapped functions */ -void smooth1d(double *data, int size); -void smooth2d(double *data, int size0, int size1); -void smooth3d(double *data, int size0, int size1, int size2); -int SavitskyGolay(double* input, long len_input, int npoints, double* output); - -/* Internal functions */ -long index2d(long row_idx, long col_idx, long ncols); -long index3d(long x_idx, long y_idx, long z_idx, long ny, long nz); -void smooth1d_rows(double *data, long nrows, long ncols); -void smooth1d_cols(double *data, long nrows, long ncols); -void smooth1d_x(double *data, long nx, long ny, long nz); -void smooth1d_y(double *data, long nx, long ny, long nz); -void smooth1d_z(double *data, long nx, long ny, long nz); -void smooth2d_yzslice(double *data, long nx, long ny, long nz); -void smooth2d_xzslice(double *data, long nx, long ny, long nz); -void smooth2d_xyslice(double *data, long nx, long ny, long nz); - - -/* Simple smoothing of a 1D array */ -void smooth1d(double *data, int size) -{ - long i; - double prev_sample; - double next_sample; - - if (size < 3) - { - return; - } - prev_sample = data[0]; - for (i=0; i<(size-1); i++) - { - next_sample = 0.25 * (prev_sample + 2 * data[i] + data[i+1]); - prev_sample = data[i]; - data[i] = next_sample; - } - data[size-1] = 0.25 * prev_sample + 0.75 * data[size-1]; - return; -} - -/* Smoothing of a 2D array*/ -void smooth2d(double *data, int nrows, int ncols) -{ - /* smooth the first dimension (rows) */ - smooth1d_rows(data, nrows, ncols); - - /* smooth the 2nd dimension */ - smooth1d_cols(data, nrows, ncols); -} - -/* Smoothing of a 3D array */ -void smooth3d(double *data, int nx, int ny, int nz) -{ - smooth2d_xyslice(data, nx, ny, nz); - smooth2d_xzslice(data, nx, ny, nz); - smooth2d_yzslice(data, nx, ny, nz); -} - -/* 1D Savitsky-Golay smoothing */ -int SavitskyGolay(double* input, long len_input, int npoints, double* output) -{ - - //double dpoints = 5.; - double coeff[MAX_SAVITSKY_GOLAY_WIDTH]; - int i, j, m; - double dhelp, den; - double *data; - - memcpy(output, input, len_input * sizeof(double)); - - if (!(npoints % 2)) npoints +=1; - - if((npoints < MIN_SAVITSKY_GOLAY_WIDTH) || (len_input < npoints) || \ - (npoints > MAX_SAVITSKY_GOLAY_WIDTH)) - { - /* do not smooth data */ - return 1; - } - - /* calculate the coefficients */ - m = (int) (npoints/2); - den = (double) ((2*m-1) * (2*m+1) * (2*m + 3)); - for (i=0; i<= m; i++){ - coeff[m+i] = (double) (3 * (3*m*m + 3*m - 1 - 5*i*i )); - coeff[m-i] = coeff[m+i]; - } - - /* simple smoothing at the beginning */ - for (j=0; j<=(int)(npoints/3); j++) - { - smooth1d(output, m); - } - - /* simple smoothing at the end */ - for (j=0; j<=(int)(npoints/3); j++) - { - smooth1d((output+len_input-m-1), m); - } - - /*one does not need the whole spectrum buffer, but code is clearer */ - data = (double *) malloc(len_input * sizeof(double)); - memcpy(data, output, len_input * sizeof(double)); - - /* the actual SG smoothing in the middle */ - for (i=m; i<(len_input-m); i++){ - dhelp = 0; - for (j=-m;j<=m;j++) { - dhelp += coeff[m+j] * (*(data+i+j)); - } - if(dhelp > 0.0){ - *(output+i) = dhelp / den; - } - } - free(data); - return (0); -} - -/*********************/ -/* Utility functions */ -/*********************/ - -long index2d(long row_idx, long col_idx, long ncols) -{ - return (row_idx*ncols+col_idx); -} - -/* Apply smooth 1d on all rows in a 2D array*/ -void smooth1d_rows(double *data, long nrows, long ncols) -{ - long row_idx; - - for (row_idx=0; row_idx < nrows; row_idx++) - { - smooth1d(&data[row_idx * ncols], ncols); - } -} - -/* Apply smooth 1d on all columns in a 2D array*/ -void smooth1d_cols(double *data, long nrows, long ncols) -{ - long row_idx, col_idx; - long this_idx2d, next_idx2d; - double prev_sample; - double next_sample; - - for (col_idx=0; col_idx < ncols; col_idx++) - { - prev_sample = data[index2d(0, col_idx, ncols)]; - for (row_idx=0; row_idx<(nrows-1); row_idx++) - { - this_idx2d = index2d(row_idx, col_idx, ncols); - next_idx2d = index2d(row_idx+1, col_idx, ncols); - - next_sample = 0.25 * (prev_sample + \ - 2 * data[this_idx2d] + \ - data[next_idx2d]); - prev_sample = data[this_idx2d]; - data[this_idx2d] = next_sample; - } - - this_idx2d = index2d(nrows-1, col_idx, ncols); - data[this_idx2d] = 0.25 * prev_sample + 0.75 * data[this_idx2d]; - } -} - -long index3d(long x_idx, long y_idx, long z_idx, long ny, long nz) -{ - return ((x_idx*ny + y_idx) * nz + z_idx); -} - -/* Apply smooth 1d along first dimension in a 3D array*/ -void smooth1d_x(double *data, long nx, long ny, long nz) -{ - long x_idx, y_idx, z_idx; - long this_idx3d, next_idx3d; - double prev_sample; - double next_sample; - - for (y_idx=0; y_idx < ny; y_idx++) - { - for (z_idx=0; z_idx < nz; z_idx++) - { - prev_sample = data[index3d(0, y_idx, z_idx, ny, nz)]; - for (x_idx=0; x_idx<(nx-1); x_idx++) - { - this_idx3d = index3d(x_idx, y_idx, z_idx, ny, nz); - next_idx3d = index3d(x_idx+1, y_idx, z_idx, ny, nz); - - next_sample = 0.25 * (prev_sample + \ - 2 * data[this_idx3d] + \ - data[next_idx3d]); - prev_sample = data[this_idx3d]; - data[this_idx3d] = next_sample; - } - - this_idx3d = index3d(nx-1, y_idx, z_idx, ny, nz); - data[this_idx3d] = 0.25 * prev_sample + 0.75 * data[this_idx3d]; - } - } -} - -/* Apply smooth 1d along second dimension in a 3D array*/ -void smooth1d_y(double *data, long nx, long ny, long nz) -{ - long x_idx, y_idx, z_idx; - long this_idx3d, next_idx3d; - double prev_sample; - double next_sample; - - for (x_idx=0; x_idx < nx; x_idx++) - { - for (z_idx=0; z_idx < nz; z_idx++) - { - prev_sample = data[index3d(x_idx, 0, z_idx, ny, nz)]; - for (y_idx=0; y_idx<(ny-1); y_idx++) - { - this_idx3d = index3d(x_idx, y_idx, z_idx, ny, nz); - next_idx3d = index3d(x_idx, y_idx+1, z_idx, ny, nz); - - next_sample = 0.25 * (prev_sample + \ - 2 * data[this_idx3d] + \ - data[next_idx3d]); - prev_sample = data[this_idx3d]; - data[this_idx3d] = next_sample; - } - - this_idx3d = index3d(x_idx, ny-1, z_idx, ny, nz); - data[this_idx3d] = 0.25 * prev_sample + 0.75 * data[this_idx3d]; - } - } -} - -/* Apply smooth 1d along third dimension in a 3D array*/ -void smooth1d_z(double *data, long nx, long ny, long nz) -{ - long x_idx, y_idx; - long idx3d_first_sample; - - for (x_idx=0; x_idx < nx; x_idx++) - { - for (y_idx=0; y_idx < ny; y_idx++) - { - idx3d_first_sample = index3d(x_idx, y_idx, 0, ny, nz); - /*We can use regular 1D smoothing function because z samples - are contiguous in memory*/ - smooth1d(&data[idx3d_first_sample], nz); - } - } -} - -/* 2D smoothing of a YZ slice in a 3D volume*/ -void smooth2d_yzslice(double *data, long nx, long ny, long nz) -{ - long x_idx; - long slice_size = ny * nz; - - /* a YZ slice is a "normal" 2D array of memory-contiguous data*/ - for (x_idx=0; x_idx < nx; x_idx++) - { - smooth2d(&data[x_idx*slice_size], ny, nz); - } -} - -/* 2D smoothing of a XZ slice in a 3D volume*/ -void smooth2d_xzslice(double *data, long nx, long ny, long nz) -{ - - /* smooth along the first dimension */ - smooth1d_x(data, nx, ny, nz); - - /* smooth along the third dimension */ - smooth1d_z(data, nx, ny, nz); -} - -/* 2D smoothing of a XY slice in a 3D volume*/ -void smooth2d_xyslice(double *data, long nx, long ny, long nz) -{ - /* smooth along the first dimension */ - smooth1d_x(data, nx, ny, nz); - - /* smooth along the second dimension */ - smooth1d_y(data, nx, ny, nz); -} - diff --git a/silx/math/fit/filters/src/snip1d.c b/silx/math/fit/filters/src/snip1d.c deleted file mode 100644 index 994a272..0000000 --- a/silx/math/fit/filters/src/snip1d.c +++ /dev/null @@ -1,149 +0,0 @@ -#/*########################################################################## -# Copyright (c) 2004-2016 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. -# -#############################################################################*/ -/* - Implementation of the algorithm SNIP in 1D described in - Miroslav Morhac et al. Nucl. Instruments and Methods in Physics Research A401 (1997) 113-132. - - The original idea for 1D and the low-statistics-digital-filter (lsdf) come from - C.G. Ryan et al. Nucl. Instruments and Methods in Physics Research B34 (1988) 396-402. -*/ -#include <stdlib.h> -#include <string.h> -#include <math.h> - -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -void lls(double *data, int size); -void lls_inv(double *data, int size); -void snip1d(double *data, int n_channels, int snip_width); -void snip1d_multiple(double *data, int n_channels, int snip_width, int n_spectra); -void lsdf(double *data, int size, int fwhm, double f, double A, double M, double ratio); - -void lls(double *data, int size) -{ - int i; - for (i=0; i< size; i++) - { - data[i] = log(log(sqrt(data[i]+1.0)+1.0)+1.0); - } -} - -void lls_inv(double *data, int size) -{ - int i; - double tmp; - for (i=0; i< size; i++) - { - /* slightly different than the published formula because - with the original formula: - - tmp = exp(exp(data[i]-1.0)-1.0); - data[i] = tmp * tmp - 1.0; - - one does not recover the original data */ - - tmp = exp(exp(data[i])-1.0)-1.0; - data[i] = tmp * tmp - 1.0; - } -} - -void lsdf(double *data, int size, int fwhm, double f, double A, double M, double ratio) -{ - int channel, i, j; - double L, R, S; - int width; - double dhelp; - - width = (int) (f * fwhm); - for (channel=width; channel<(size-width); channel++) - { - i = width; - while(i>0) - { - L=0; - R=0; - for(j=channel-i; j<channel; j++) - { - L += data[j]; - } - for(j=channel+1; j<channel+i; j++) - { - R += data[j]; - } - S = data[channel] + L + R; - if (S<M) - { - data[channel] = S /(2*i+1); - break; - } - dhelp = (R+1)/(L+1); - if ((dhelp < ratio) && (dhelp > (1/ratio))) - { - if (S<(A*sqrt(data[channel]))) - { - data[channel] = S /(2*i+1); - break; - } - } - i=i-1; - } - } -} - - -void snip1d(double *data, int n_channels, int snip_width) -{ - snip1d_multiple(data, n_channels, snip_width, 1); -} - -void snip1d_multiple(double *data, int n_channels, int snip_width, int n_spectra) -{ - int i; - int j; - int p; - int offset; - double *w; - - i = (int) (0.5 * snip_width); - /* lsdf(data, size, i, 1.5, 75., 10., 1.3); */ - - w = (double *) malloc(n_channels * sizeof(double)); - - for (j=0; j < n_spectra; j++) - { - offset = j * n_channels; - for (p = snip_width; p > 0; p--) - { - for (i=p; i<(n_channels - p); i++) - { - w[i] = MIN(data[i + offset], 0.5*(data[i + offset - p] + data[ i + offset + p])); - } - for (i=p; i<(n_channels - p); i++) - { - data[i+offset] = w[i]; - } - } - } - free(w); -} diff --git a/silx/math/fit/filters/src/snip2d.c b/silx/math/fit/filters/src/snip2d.c deleted file mode 100644 index 235759c..0000000 --- a/silx/math/fit/filters/src/snip2d.c +++ /dev/null @@ -1,96 +0,0 @@ -#/*########################################################################## -# -# The PyMca X-Ray Fluorescence Toolkit -# -# Copyright (c) 2004-2014 European Synchrotron Radiation Facility -# -# This file is part of the PyMca X-ray Fluorescence Toolkit developed at -# the ESRF by the Software group. -# -# 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. -# -#############################################################################*/ -/* - Implementation of the algorithm SNIP in 2D described in - Miroslav Morhac et al. Nucl. Instruments and Methods in Physics Research A401 (1997) 113-132. -*/ -#include <stdlib.h> -#include <string.h> -#include <math.h> -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -void lls(double *data, int size); -void lls_inv(double *data, int size); - -void snip2d(double *data, int nrows, int ncolumns, int width) -{ - int i, j; - int p; - int size; - double *w; - double P1, P2, P3, P4; - double S1, S2, S3, S4; - double dhelp; - int iminuspxncolumns; /* (i-p) * ncolumns */ - int ixncolumns; /* i * ncolumns */ - int ipluspxncolumns; /* (i+p) * ncolumns */ - - size = nrows * ncolumns; - w = (double *) malloc(size * sizeof(double)); - - for (p=width; p > 0; p--) - { - for (i=p; i<(nrows-p); i++) - { - iminuspxncolumns = (i-p) * ncolumns; - ixncolumns = i * ncolumns; - ipluspxncolumns = (i+p) * ncolumns; - for (j=p; j<(ncolumns-p); j++) - { - P4 = data[ iminuspxncolumns + (j-p)]; /* P4 = data[i-p][j-p] */ - S4 = data[ iminuspxncolumns + j]; /* S4 = data[i-p][j] */ - P2 = data[ iminuspxncolumns + (j+p)]; /* P2 = data[i-p][j+p] */ - S3 = data[ ixncolumns + (j-p)]; /* S3 = data[i][j-p] */ - S2 = data[ ixncolumns + (j+p)]; /* S2 = data[i][j+p] */ - P3 = data[ ipluspxncolumns + (j-p)]; /* P3 = data[i+p][j-p] */ - S1 = data[ ipluspxncolumns + j]; /* S1 = data[i+p][j] */ - P1 = data[ ipluspxncolumns + (j+p)]; /* P1 = data[i+p][j+p] */ - dhelp = 0.5*(P1+P3); - S1 = MAX(S1, dhelp) - dhelp; - dhelp = 0.5*(P1+P2); - S2 = MAX(S2, dhelp) - dhelp; - dhelp = 0.5*(P3+P4); - S3 = MAX(S3, dhelp) - dhelp; - dhelp = 0.5*(P2+P4); - S4 = MAX(S4, dhelp) - dhelp; - w[ixncolumns + j] = MIN(data[ixncolumns + j], 0.5 * (S1+S2+S3+S4) + 0.25 * (P1+P2+P3+P4)); - } - } - for (i=p; i<(nrows-p); i++) - { - ixncolumns = i * ncolumns; - for (j=p; j<(ncolumns-p); j++) - { - data[ixncolumns + j] = w[ixncolumns + j]; - } - } - } - free(w); -} diff --git a/silx/math/fit/filters/src/snip3d.c b/silx/math/fit/filters/src/snip3d.c deleted file mode 100644 index cf48ee4..0000000 --- a/silx/math/fit/filters/src/snip3d.c +++ /dev/null @@ -1,186 +0,0 @@ -#/*########################################################################## -# -# The PyMca X-Ray Fluorescence Toolkit -# -# Copyright (c) 2004-2014 European Synchrotron Radiation Facility -# -# This file is part of the PyMca X-ray Fluorescence Toolkit developed at -# the ESRF by the Software group. -# -# 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. -# -#############################################################################*/ -/* - Implementation of the algorithm SNIP in 3D described in - Miroslav Morhac et al. Nucl. Instruments and Methods in Physics Research A401 (1997) 113-132. -*/ - -#include <stdlib.h> -#include <string.h> -#include <math.h> -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -void lls(double *data, int size); -void lls_inv(double *data, int size); - -void snip3d(double *data, int nx, int ny, int nz, int width) -{ - int i, j, k; - int p; - int size; - double *w; - double P1, P2, P3, P4, P5, P6, P7, P8; - double R1, R2, R3, R4, R5, R6; - double S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12; - double dhelp; - long ioffset; - long iplus; - long imin; - long joffset; - long jplus; - long jmin; - - size = nx * ny * nz; - w = (double *) malloc(size * sizeof(double)); - - for (p=width; p > 0; p--) - { - for (i=p; i<(nx-p); i++) - { - ioffset = i * ny * nz; - iplus = (i + p) * ny * nz; - imin = (i - p) * ny * nz; - for (j=p; j<(ny-p); j++) - { - joffset = j * nz; - jplus = (j + p) * nz; - jmin = (j - p) * nz; - for (k=p; k<(nz-p); k++) - { - P1 = data[iplus + jplus + k-p]; /* P1 = data[i+p][j+p][k-p] */ - P2 = data[imin + jplus + k-p]; /* P2 = data[i-p][j+p][k-p] */ - P3 = data[iplus + jmin + k-p]; /* P3 = data[i+p][j-p][k-p] */ - P4 = data[imin + jmin + k-p]; /* P4 = data[i-p][j-p][k-p] */ - P5 = data[iplus + jplus + k+p]; /* P5 = data[i+p][j+p][k+p] */ - P6 = data[imin + jplus + k+p]; /* P6 = data[i-p][j+p][k+p] */ - P7 = data[imin + jmin + k+p]; /* P7 = data[i-p][j-p][k+p] */ - P8 = data[iplus + jmin + k+p]; /* P8 = data[i+p][j-p][k+p] */ - - S1 = data[iplus + joffset + k-p]; /* S1 = data[i+p][j][k-p] */ - S2 = data[ioffset + jmin + k-p]; /* S2 = data[i][j+p][k-p] */ - S3 = data[imin + joffset + k-p]; /* S3 = data[i-p][j][k-p] */ - S4 = data[ioffset + jmin + k-p]; /* S4 = data[i][j-p][k-p] */ - S5 = data[imin + joffset + k+p]; /* S5 = data[i-p][j][k+p] */ - S6 = data[ioffset + jplus + k+p]; /* S6 = data[i][j+p][k+p] */ - S7 = data[imin + joffset + k+p]; /* S7 = data[i-p][j][k+p] */ - S8 = data[ioffset + jmin + k+p]; /* S8 = data[i][j-p][k+p] */ - S9 = data[imin + jplus + k]; /* S9 = data[i-p][j+p][k] */ - S10 = data[imin + jmin + k]; /* S10 = data[i-p][j-p][k] */ - S11 = data[iplus + jmin + k]; /* S11 = data[i+p][j-p][k] */ - S12 = data[iplus + jplus + k]; /* S12 = data[i+p][j+p][k] */ - - R1 = data[ioffset + joffset + k-p]; /* R1 = data[i][j][k-p] */ - R2 = data[ioffset + joffset + k+p]; /* R2 = data[i][j][k+p] */ - R3 = data[imin + joffset + k]; /* R3 = data[i-p][j][k] */ - R4 = data[iplus + joffset + k]; /* R4 = data[i+p][j][k] */ - R5 = data[ioffset + jplus + k]; /* R5 = data[i][j+p][k] */ - R6 = data[ioffset + jmin + k]; /* R6 = data[i][j-p][k] */ - - dhelp = 0.5*(P1+P3); - S1 = MAX(S1, dhelp) - dhelp; - - dhelp = 0.5*(P1+P2); - S2 = MAX(S2, dhelp) - dhelp; - - dhelp = 0.5*(P2+P4); - S3 = MAX(S3, dhelp) - dhelp; - - dhelp = 0.5*(P3+P4); - S4 = MAX(S4, dhelp) - dhelp; - - dhelp = 0.5*(P5+P8); /* Different from paper (P5+P7) but according to drawing */ - S5 = MAX(S5, dhelp) - dhelp; - - dhelp = 0.5*(P5+P6); - S6 = MAX(S6, dhelp) - dhelp; - - dhelp = 0.5*(P6+P7); /* Different from paper (P6+P8) but according to drawing */ - S7 = MAX(S7, dhelp) - dhelp; - - dhelp = 0.5*(P7+P8); - S8 = MAX(S8, dhelp) - dhelp; - - dhelp = 0.5*(P2+P6); - S9 = MAX(S9, dhelp) - dhelp; - - dhelp = 0.5*(P4+P7); /* Different from paper (P4+P8) but according to drawing */ - S10 = MAX(S10, dhelp) - dhelp; - - dhelp = 0.5*(P3+P8); /* Different from paper (P1+P5) but according to drawing */ - S11 = MAX(S11, dhelp) - dhelp; - - dhelp = 0.5*(P1+P5); /* Different from paper (P3+P7) but according to drawing */ - S12 = MAX(S12, dhelp) - dhelp; - - /* The published formulae correspond to have: - P7 and P8 interchanged, and S11 and S12 interchanged - with respect to the published drawing */ - - dhelp = 0.5 * (S1+S2+S3+S4) + 0.25 * (P1+P2+P3+P4); - R1 = MAX(R1, dhelp) - dhelp; - - dhelp = 0.5 * (S5+S6+S7+S8) + 0.25 * (P5+P6+P7+P8); - R2 = MAX(R2, dhelp) - dhelp; - - dhelp = 0.5 * (S3+S7+S9+S10) + 0.25 * (P2+P4+P6+P7); /* Again same P7 and P8 change */ - R3 = MAX(R3, dhelp) - dhelp; - - dhelp = 0.5 * (S1+S5+S11+S12) + 0.25 * (P1+P3+P5+P8); /* Again same P7 and P8 change */ - R4 = MAX(R4, dhelp) - dhelp; - - dhelp = 0.5 * (S2+S6+S9+S12) + 0.25 * (P1+P2+P5+P6); /* Again same S11 and S12 change */ - R5 = MAX(R5, dhelp) - dhelp; - - dhelp = 0.5 * (S4+S8+S10+S11) + 0.25 * (P3+P4+P7+P8); /* Again same S11 and S12 change */ - R6 = MAX(R6, dhelp) - dhelp; - - dhelp = 0.5 * (R1 + R2 + R3 + R4 + R5 + R6) +\ - 0.25 * (S1 + S2 + S3 + S4 + S5 + S6) +\ - 0.25 * (S7 + S8 + S9 + S10 + S11 + S12) +\ - 0.125 * (P1 + P2 + P3 + P4 + P5 + P6 + P7 + P8); - w[ioffset + joffset + k] = MIN(data[ioffset + joffset + k], dhelp); - } - } - } - for (i=p; i<(nx-p); i++) - { - ioffset = i * ny * nz; - for (j=p; j<(ny-p); j++) - { - joffset = j * nz; - for (k=p; k<(nz-p); j++) - { - data[ioffset + joffset + k] = w[ioffset + joffset + k]; - } - } - } - } - free(w); -} diff --git a/silx/math/fit/filters/src/strip.c b/silx/math/fit/filters/src/strip.c deleted file mode 100644 index dec0742..0000000 --- a/silx/math/fit/filters/src/strip.c +++ /dev/null @@ -1,118 +0,0 @@ -#/*########################################################################## -# Copyright (c) 2004-2016 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 file provides a background strip function, to isolate low frequency - background signal from a spectrum (and later substact it from the signal - to be left only with the peaks to be fitted). - - It is adapted from PyMca source file "SpecFitFuns.c". The main difference - with the original code is that this code does not handle the python - wrapping, which is done elsewhere using cython. - - Authors: V.A. Sole, P. Knobel - License: MIT - Last modified: 17/06/2016 -*/ - -#include <string.h> - -#include <stdio.h> - -/* strip(double* input, double c, long niter, double* output) - - The strip background is probably PyMca's most popular background model. - - In its simplest implementation it is just as an iterative procedure depending - on two parameters. These parameters are the strip background width w, and the - strip background number of iterations. At each iteration, if the contents of - channel i, y(i), is above the average of the contents of the channels at w - channels of distance, y(i-w) and y(i+w), y(i) is replaced by the average. - At the end of the process we are left with something that resembles a spectrum - in which the peaks have been "stripped". - - Parameters: - - - input: Input data array - - c: scaling factor applied to the average of y(i-w) and y(i+w) before - comparing to y(i) - - niter: number of iterations - - deltai: operator width (in number of channels) - - anchors: Array of anchors, indices of points that will not be - modified during the stripping procedure. - - output: output array - -*/ -int strip(double* input, long len_input, - double c, long niter, int deltai, - long* anchors, long len_anchors, - double* output) -{ - long iter_index, array_index, anchor_index, anchor; - int anchor_nearby_flag; - double t_mean; - - memcpy(output, input, len_input * sizeof(double)); - - if (deltai <=0) deltai = 1; - - if (len_input < (2*deltai+1)) return(-1); - - if (len_anchors > 0) { - for (iter_index = 0; iter_index < niter; iter_index++) { - for (array_index = deltai; array_index < len_input - deltai; array_index++) { - /* if index is within +- deltai of an anchor, don't do anything */ - anchor_nearby_flag = 0; - for (anchor_index=0; anchor_index<len_anchors; anchor_index++) - { - anchor = anchors[anchor_index]; - if (array_index > (anchor - deltai) && array_index < (anchor + deltai)) - { - anchor_nearby_flag = 1; - break; - } - } - /* skip this array_index index */ - if (anchor_nearby_flag) { - continue; - } - - t_mean = 0.5 * (input[array_index-deltai] + input[array_index+deltai]); - if (input[array_index] > (t_mean * c)) - output[array_index] = t_mean; - } - memcpy(input, output, len_input * sizeof(double)); - } - } - else { - for (iter_index = 0; iter_index < niter; iter_index++) { - for (array_index=deltai; array_index < len_input - deltai; array_index++) { - t_mean = 0.5 * (input[array_index-deltai] + input[array_index+deltai]); - - if (input[array_index] > (t_mean * c)) - output[array_index] = t_mean; - } - memcpy(input, output, len_input * sizeof(double)); - } - } - return(0); -} diff --git a/silx/math/fit/filters_wrapper.pxd b/silx/math/fit/filters_wrapper.pxd deleted file mode 100644 index e4f7c72..0000000 --- a/silx/math/fit/filters_wrapper.pxd +++ /dev/null @@ -1,71 +0,0 @@ -# coding: utf-8 -#/*########################################################################## -# Copyright (C) 2016 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. -# -#############################################################################*/ - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "22/06/2016" - -cimport cython - -cdef extern from "filters.h": - void snip1d(double *data, - int size, - int width) - - void snip2d(double *data, - int nrows, - int ncolumns, - int width) - - void snip3d(double *data, - int nx, - int ny, - int nz, - int width) - - int strip(double* input, - long len_input, - double c, - long niter, - int deltai, - long* anchors, - long len_anchors, - double* output) - - int SavitskyGolay(double* input, - long len_input, - int npoints, - double* output) - - void smooth1d(double *data, - int size) - - void smooth2d(double *data, - int size0, - int size1) - - void smooth3d(double *data, - int size0, - int size1, - int size2) diff --git a/silx/math/fit/fitmanager.py b/silx/math/fit/fitmanager.py deleted file mode 100644 index b60e073..0000000 --- a/silx/math/fit/fitmanager.py +++ /dev/null @@ -1,1087 +0,0 @@ -# coding: utf-8 -# /*######################################################################### -# -# Copyright (c) 2004-2020 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 tool to perform advanced fitting. The actual fit relies -on :func:`silx.math.fit.leastsq`. - -This module deals with: - - - handling of the model functions (using a set of default functions or - loading custom user functions) - - handling of estimation function, that are used to determine the number - of parameters to be fitted for functions with unknown number of - parameters (such as the sum of a variable number of gaussian curves), - and find reasonable initial parameters for input to the iterative - fitting algorithm - - handling of custom derivative functions that can be passed as a - parameter to :func:`silx.math.fit.leastsq` - - providing different background models - -""" -from collections import OrderedDict -import logging -import numpy -from numpy.linalg.linalg import LinAlgError -import os -import sys - -from .filters import strip, smooth1d -from .leastsq import leastsq -from .fittheory import FitTheory -from . import bgtheories - - -__authors__ = ["V.A. Sole", "P. Knobel"] -__license__ = "MIT" -__date__ = "16/01/2017" - -_logger = logging.getLogger(__name__) - - -class FitManager(object): - """ - Fit functions manager - - :param x: Abscissa data. If ``None``, :attr:`xdata` is set to - ``numpy.array([0.0, 1.0, 2.0, ..., len(y)-1])`` - :type x: Sequence or numpy array or None - :param y: The dependant data ``y = f(x)``. ``y`` must have the same - shape as ``x`` if ``x`` is not ``None``. - :type y: Sequence or numpy array or None - :param sigmay: The uncertainties in the ``ydata`` array. These can be - used as weights in the least-squares problem, if ``weight_flag`` - is ``True``. - If ``None``, the uncertainties are assumed to be 1, unless - ``weight_flag`` is ``True``, in which case the square-root - of ``y`` is used. - :type sigmay: Sequence or numpy array or None - :param weight_flag: If this parameter is ``True`` and ``sigmay`` - uncertainties are not specified, the square root of ``y`` is used - as weights in the least-squares problem. If ``False``, the - uncertainties are set to 1. - :type weight_flag: boolean - """ - def __init__(self, x=None, y=None, sigmay=None, weight_flag=False): - """ - """ - self.fitconfig = { - 'WeightFlag': weight_flag, - 'fitbkg': 'No Background', - 'fittheory': None, - # Next few parameters are defined for compatibility with legacy theories - # which take the background as argument for their estimation function - 'StripWidth': 2, - 'StripIterations': 5000, - 'StripThresholdFactor': 1.0, - 'SmoothingFlag': False - } - """Dictionary of fit configuration parameters. - These parameters can be modified using the :meth:`configure` method. - - Keys are: - - - 'fitbkg': name of the function used for fitting a low frequency - background signal - - 'FwhmPoints': default full width at half maximum value for the - peaks'. - - 'Sensitivity': Sensitivity parameter for the peak detection - algorithm (:func:`silx.math.fit.peak_search`) - """ - - self.theories = OrderedDict() - """Dictionary of fit theories, defining functions to be fitted - to individual peaks. - - Keys are descriptive theory names (e.g "Gaussians" or "Step up"). - Values are :class:`silx.math.fit.fittheory.FitTheory` objects with - the following attributes: - - - *"function"* is the fit function for an individual peak - - *"parameters"* is a sequence of parameter names - - *"estimate"* is the parameter estimation function - - *"configure"* is the function returning the configuration dict - for the theory in the format described in the :attr:` fitconfig` - documentation - - *"derivative"* (optional) is a custom derivative function, whose - signature is described in the documentation of - :func:`silx.math.fit.leastsq.leastsq` - (``model_deriv(xdata, parameters, index)``). - - *"description"* is a description string - """ - - self.selectedtheory = None - """Name of currently selected theory. This name matches a key in - :attr:`theories`.""" - - self.bgtheories = OrderedDict() - """Dictionary of background theories. - - See :attr:`theories` for documentation on theories. - """ - - # Load default theories (constant, linear, strip) - self.loadbgtheories(bgtheories) - - self.selectedbg = 'No Background' - """Name of currently selected background theory. This name must be - an existing key in :attr:`bgtheories`.""" - - self.fit_results = [] - """This list stores detailed information about all fit parameters. - It is initialized in :meth:`estimate` and completed with final fit - values in :meth:`runfit`. - - Each fit parameter is stored as a dictionary with following fields: - - - 'name': Parameter name. - - 'estimation': Estimated value. - - 'group': Group number. Group 0 corresponds to the background - function parameters. Group ``n`` (for ``n>0``) corresponds to - the fit function parameters for the n-th peak. - - 'code': Constraint code - - - 0 - FREE - - 1 - POSITIVE - - 2 - QUOTED - - 3 - FIXED - - 4 - FACTOR - - 5 - DELTA - - 6 - SUM - - - 'cons1': - - - Ignored if 'code' is FREE, POSITIVE or FIXED. - - Min value of the parameter if code is QUOTED - - Index of fitted parameter to which 'cons2' is related - if code is FACTOR, DELTA or SUM. - - - 'cons2': - - - Ignored if 'code' is FREE, POSITIVE or FIXED. - - Max value of the parameter if QUOTED - - Factor to apply to related parameter with index 'cons1' if - 'code' is FACTOR - - Difference with parameter with index 'cons1' if - 'code' is DELTA - - Sum obtained when adding parameter with index 'cons1' if - 'code' is SUM - - - 'fitresult': Fitted value. - - 'sigma': Standard deviation for the parameter estimate - - 'xmin': Lower limit of the ``x`` data range on which the fit - was performed - - 'xmax': Upeer limit of the ``x`` data range on which the fit - was performed - """ - - self.parameter_names = [] - """This list stores all fit parameter names: background function - parameters and fit function parameters for every peak. It is filled - in :meth:`estimate`. - - It is the responsibility of the estimate function defined in - :attr:`theories` to determine how many parameters are needed, - based on how many peaks are detected and how many parameters are needed - to fit an individual peak. - """ - - self.setdata(x, y, sigmay) - - ################## - # Public methods # - ################## - def addbackground(self, bgname, bgtheory): - """Add a new background theory to dictionary :attr:`bgtheories`. - - :param bgname: String with the name describing the function - :param bgtheory: :class:`FitTheory` object - :type bgtheory: :class:`silx.math.fit.fittheory.FitTheory` - """ - self.bgtheories[bgname] = bgtheory - - def addtheory(self, name, theory=None, - function=None, parameters=None, - estimate=None, configure=None, derivative=None, - description=None, pymca_legacy=False): - """Add a new theory to dictionary :attr:`theories`. - - You can pass a name and a :class:`FitTheory` object as arguments, or - alternatively provide all arguments necessary to instantiate a new - :class:`FitTheory` object. - - See :meth:`loadtheories` for more information on estimation functions, - configuration functions and custom derivative functions. - - :param name: String with the name describing the function - :param theory: :class:`FitTheory` object, defining a fit function and - associated information (estimation function, description…). - If this parameter is provided, all other parameters, except for - ``name``, are ignored. - :type theory: :class:`silx.math.fit.fittheory.FitTheory` - :param callable function: Mandatory argument if ``theory`` is not provided. - See documentation for :attr:`silx.math.fit.fittheory.FitTheory.function`. - :param List[str] parameters: Mandatory argument if ``theory`` is not provided. - See documentation for :attr:`silx.math.fit.fittheory.FitTheory.parameters`. - :param callable estimate: See documentation for - :attr:`silx.math.fit.fittheory.FitTheory.estimate` - :param callable configure: See documentation for - :attr:`silx.math.fit.fittheory.FitTheory.configure` - :param callable derivative: See documentation for - :attr:`silx.math.fit.fittheory.FitTheory.derivative` - :param str description: See documentation for - :attr:`silx.math.fit.fittheory.FitTheory.description` - :param config_widget: See documentation for - :attr:`silx.math.fit.fittheory.FitTheory.config_widget` - :param bool pymca_legacy: See documentation for - :attr:`silx.math.fit.fittheory.FitTheory.pymca_legacy` - """ - if theory is not None: - self.theories[name] = theory - - elif function is not None and parameters is not None: - self.theories[name] = FitTheory( - description=description, - function=function, - parameters=parameters, - estimate=estimate, - configure=configure, - derivative=derivative, - pymca_legacy=pymca_legacy - ) - - else: - raise TypeError("You must supply a FitTheory object or define " + - "a fit function and its parameters.") - - def addbgtheory(self, name, theory=None, - function=None, parameters=None, - estimate=None, configure=None, - derivative=None, description=None): - """Add a new theory to dictionary :attr:`bgtheories`. - - You can pass a name and a :class:`FitTheory` object as arguments, or - alternatively provide all arguments necessary to instantiate a new - :class:`FitTheory` object. - - :param name: String with the name describing the function - :param theory: :class:`FitTheory` object, defining a fit function and - associated information (estimation function, description…). - If this parameter is provided, all other parameters, except for - ``name``, are ignored. - :type theory: :class:`silx.math.fit.fittheory.FitTheory` - :param function function: Mandatory argument if ``theory`` is not provided. - See documentation for :attr:`silx.math.fit.fittheory.FitTheory.function`. - :param list[str] parameters: Mandatory argument if ``theory`` is not provided. - See documentation for :attr:`silx.math.fit.fittheory.FitTheory.parameters`. - :param function estimate: See documentation for - :attr:`silx.math.fit.fittheory.FitTheory.estimate` - :param function configure: See documentation for - :attr:`silx.math.fit.fittheory.FitTheory.configure` - :param function derivative: See documentation for - :attr:`silx.math.fit.fittheory.FitTheory.derivative` - :param str description: See documentation for - :attr:`silx.math.fit.fittheory.FitTheory.description` - """ - if theory is not None: - self.bgtheories[name] = theory - - elif function is not None and parameters is not None: - self.bgtheories[name] = FitTheory( - description=description, - function=function, - parameters=parameters, - estimate=estimate, - configure=configure, - derivative=derivative, - is_background=True - ) - - else: - raise TypeError("You must supply a FitTheory object or define " + - "a background function and its parameters.") - - def configure(self, **kw): - """Configure the current theory by filling or updating the - :attr:`fitconfig` dictionary. - Call the custom configuration function, if any. This allows the user - to modify the behavior of the custom fit function or the custom - estimate function. - - This methods accepts only named parameters. All ``**kw`` parameters - are expected to be fields of :attr:`fitconfig` to be updated, unless - they have a special meaning for the custom configuration function - of the currently selected theory.. - - This method returns the modified config dictionary returned by the - custom configuration function. - """ - # inspect **kw to find known keys, update them in self.fitconfig - for key in self.fitconfig: - if key in kw: - self.fitconfig[key] = kw[key] - - # initialize dict with existing config dict - result = {} - result.update(self.fitconfig) - - if "WeightFlag" in kw: - if kw["WeightFlag"]: - self.enableweight() - else: - self.disableweight() - - if self.selectedtheory is None: - return result - - # Apply custom configuration function - custom_config_fun = self.theories[self.selectedtheory].configure - if custom_config_fun is not None: - result.update(custom_config_fun(**kw)) - - custom_bg_config_fun = self.bgtheories[self.selectedbg].configure - if custom_bg_config_fun is not None: - result.update(custom_bg_config_fun(**kw)) - - # Update self.fitconfig with custom config - for key in self.fitconfig: - if key in result: - self.fitconfig[key] = result[key] - - result.update(self.fitconfig) - return result - - def estimate(self, callback=None): - """ - Fill :attr:`fit_results` with an estimation of the fit parameters. - - At first, the background parameters are estimated, if a background - model has been specified. - Then, a custom estimation function related to the model function is - called. - - This process determines the number of needed fit parameters and - provides an initial estimation for them, to serve as an input for the - actual iterative fitting performed in :meth:`runfit`. - - :param callback: Optional callback function, conforming to the - signature ``callback(data)`` with ``data`` being a dictionary. - This callback function is called before and after the estimation - process, and is given a dictionary containing the values of - :attr:`state` (``'Estimate in progress'`` or ``'Ready to Fit'``) - and :attr:`chisq`. - This is used for instance in :mod:`silx.gui.fit.FitWidget` to - update a widget displaying a status message. - :return: Estimated parameters - """ - self.state = 'Estimate in progress' - self.chisq = None - - if callback is not None: - callback(data={'chisq': self.chisq, - 'status': self.state}) - - CONS = {0: 'FREE', - 1: 'POSITIVE', - 2: 'QUOTED', - 3: 'FIXED', - 4: 'FACTOR', - 5: 'DELTA', - 6: 'SUM', - 7: 'IGNORE'} - - # Filter-out not finite data - xwork = self.xdata[self._finite_mask] - ywork = self.ydata[self._finite_mask] - - # estimate the background - bg_params, bg_constraints = self.estimate_bkg(xwork, ywork) - - # estimate the function - try: - fun_params, fun_constraints = self.estimate_fun(xwork, ywork) - except LinAlgError: - self.state = 'Estimate failed' - if callback is not None: - callback(data={'status': self.state}) - raise - - # build the names - self.parameter_names = [] - - for bg_param_name in self.bgtheories[self.selectedbg].parameters: - self.parameter_names.append(bg_param_name) - - fun_param_names = self.theories[self.selectedtheory].parameters - param_index, peak_index = 0, 0 - while param_index < len(fun_params): - peak_index += 1 - for fun_param_name in fun_param_names: - self.parameter_names.append(fun_param_name + "%d" % peak_index) - param_index += 1 - - self.fit_results = [] - nb_fun_params_per_group = len(fun_param_names) - group_number = 0 - xmin = min(xwork) - xmax = max(xwork) - nb_bg_params = len(bg_params) - for (pindex, pname) in enumerate(self.parameter_names): - # First come background parameters - if pindex < nb_bg_params: - estimation_value = bg_params[pindex] - constraint_code = CONS[int(bg_constraints[pindex][0])] - cons1 = bg_constraints[pindex][1] - cons2 = bg_constraints[pindex][2] - # then come peak function parameters - else: - fun_param_index = pindex - nb_bg_params - - # increment group_number for each new fitted peak - if (fun_param_index % nb_fun_params_per_group) == 0: - group_number += 1 - - estimation_value = fun_params[fun_param_index] - constraint_code = CONS[int(fun_constraints[fun_param_index][0])] - # cons1 is the index of another fit parameter. In the global - # fit_results, we must adjust the index to account for the bg - # params added to the start of the list. - cons1 = fun_constraints[fun_param_index][1] - if constraint_code in ["FACTOR", "DELTA", "SUM"]: - cons1 += nb_bg_params - cons2 = fun_constraints[fun_param_index][2] - - self.fit_results.append({'name': pname, - 'estimation': estimation_value, - 'group': group_number, - 'code': constraint_code, - 'cons1': cons1, - 'cons2': cons2, - 'fitresult': 0.0, - 'sigma': 0.0, - 'xmin': xmin, - 'xmax': xmax}) - - self.state = 'Ready to Fit' - self.chisq = None - self.niter = 0 - - if callback is not None: - callback(data={'chisq': self.chisq, - 'status': self.state}) - return numpy.append(bg_params, fun_params) - - def fit(self): - """Convenience method to call :meth:`estimate` followed by :meth:`runfit`. - - :return: Output of :meth:`runfit`""" - self.estimate() - return self.runfit() - - def gendata(self, x=None, paramlist=None, estimated=False): - """Return a data array using the currently selected fit function - and the fitted parameters. - - :param x: Independent variable where the function is calculated. - If ``None``, use :attr:`xdata`. - :param paramlist: List of dictionaries, each dictionary item being a - fit parameter. The dictionary's format is documented in - :attr:`fit_results`. - If ``None`` (default), use parameters from :attr:`fit_results`. - :param estimated: If *True*, use estimated parameters. - :return: :meth:`fitfunction` calculated for parameters whose code is - not set to ``"IGNORE"``. - - This calculates :meth:`fitfunction` on `x` data using fit parameters - from a list of parameter dictionaries, if field ``code`` is not set - to ``"IGNORE"``. - """ - x = self.xdata if x is None else numpy.array(x, copy=False) - - if paramlist is None: - paramlist = self.fit_results - active_params = [] - for param in paramlist: - if param['code'] not in ['IGNORE', 7]: - if not estimated: - active_params.append(param['fitresult']) - else: - active_params.append(param['estimation']) - - # Mask x with not finite (support nD x) - finite_mask = numpy.all(numpy.isfinite(x), axis=tuple(range(1, x.ndim))) - - if numpy.all(finite_mask): # All values are finite: fast path - return self.fitfunction(numpy.array(x, copy=True), *active_params) - - else: # Only run fitfunction on finite data and complete result with NaNs - # Create result with same number as elements as x, filling holes with NaNs - result = numpy.full((x.shape[0],), numpy.nan, dtype=numpy.float64) - result[finite_mask] = self.fitfunction( - numpy.array(x[finite_mask], copy=True), *active_params) - return result - - def get_estimation(self): - """Return the list of fit parameter names.""" - if self.state not in ["Ready to fit", "Fit in progress", "Ready"]: - _logger.warning("get_estimation() called before estimate() completed") - return [param["estimation"] for param in self.fit_results] - - def get_names(self): - """Return the list of fit parameter estimations.""" - if self.state not in ["Ready to fit", "Fit in progress", "Ready"]: - msg = "get_names() called before estimate() completed, " - msg += "names are not populated at this stage" - _logger.warning(msg) - return [param["name"] for param in self.fit_results] - - def get_fitted_parameters(self): - """Return the list of fitted parameters.""" - if self.state not in ["Ready"]: - msg = "get_fitted_parameters() called before runfit() completed, " - msg += "results are not available a this stage" - _logger.warning(msg) - return [param["fitresult"] for param in self.fit_results] - - def loadtheories(self, theories): - """Import user defined fit functions defined in an external Python - source file, and save them in :attr:`theories`. - - An example of such a file can be found in the sources of - :mod:`silx.math.fit.fittheories`. It must contain a - dictionary named ``THEORY`` with the following structure:: - - THEORY = { - 'theory_name_1': - FitTheory(description='Description of theory 1', - function=fitfunction1, - parameters=('param name 1', 'param name 2', …), - estimate=estimation_function1, - configure=configuration_function1, - derivative=derivative_function1), - 'theory_name_2': - FitTheory(…), - } - - See documentation of :mod:`silx.math.fit.fittheories` and - :mod:`silx.math.fit.fittheory` for more - information on designing your fit functions file. - - This method can also load user defined functions in the legacy - format used in *PyMca*. - - :param theories: Name of python source file, or module containing the - definition of fit functions. - :raise: ImportError if theories cannot be imported - """ - from types import ModuleType - if isinstance(theories, ModuleType): - theories_module = theories - else: - # if theories is not a module, it must be a string - string_types = (basestring,) if sys.version_info[0] == 2 else (str,) # noqa - if not isinstance(theories, string_types): - raise ImportError("theory must be a python module, a module" + - "name or a python filename") - # if theories is a filename - if os.path.isfile(theories): - sys.path.append(os.path.dirname(theories)) - f = os.path.basename(os.path.splitext(theories)[0]) - theories_module = __import__(f) - # if theories is a module name - else: - theories_module = __import__(theories) - - if hasattr(theories_module, "INIT"): - theories.INIT() - - if not hasattr(theories_module, "THEORY"): - msg = "File %s does not contain a THEORY dictionary" % theories - raise ImportError(msg) - - elif isinstance(theories_module.THEORY, dict): - # silx format for theory definition - for theory_name, fittheory in list(theories_module.THEORY.items()): - self.addtheory(theory_name, fittheory) - else: - self._load_legacy_theories(theories_module) - - def loadbgtheories(self, theories): - """Import user defined background functions defined in an external Python - module (source file), and save them in :attr:`theories`. - - An example of such a file can be found in the sources of - :mod:`silx.math.fit.fittheories`. It must contain a - dictionary named ``THEORY`` with the following structure:: - - THEORY = { - 'theory_name_1': - FitTheory(description='Description of theory 1', - function=fitfunction1, - parameters=('param name 1', 'param name 2', …), - estimate=estimation_function1, - configure=configuration_function1, - 'theory_name_2': - FitTheory(…), - } - - See documentation of :mod:`silx.math.fit.bgtheories` and - :mod:`silx.math.fit.fittheory` for more - information on designing your background functions file. - - :param theories: Module or name of python source file containing the - definition of background functions. - :raise: ImportError if theories cannot be imported - """ - from types import ModuleType - if isinstance(theories, ModuleType): - theories_module = theories - else: - # if theories is not a module, it must be a string - string_types = (basestring,) if sys.version_info[0] == 2 else (str,) # noqa - if not isinstance(theories, string_types): - raise ImportError("theory must be a python module, a module" + - "name or a python filename") - # if theories is a filename - if os.path.isfile(theories): - sys.path.append(os.path.dirname(theories)) - f = os.path.basename(os.path.splitext(theories)[0]) - theories_module = __import__(f) - # if theories is a module name - else: - theories_module = __import__(theories) - - if hasattr(theories_module, "INIT"): - theories.INIT() - - if not hasattr(theories_module, "THEORY"): - msg = "File %s does not contain a THEORY dictionary" % theories - raise ImportError(msg) - - elif isinstance(theories_module.THEORY, dict): - # silx format for theory definition - for theory_name, fittheory in list(theories_module.THEORY.items()): - self.addbgtheory(theory_name, fittheory) - - def setbackground(self, theory): - """Choose a background type from within :attr:`bgtheories`. - - This updates :attr:`selectedbg`. - - :param theory: The name of the background to be used. - :raise: KeyError if ``theory`` is not a key of :attr:`bgtheories``. - """ - if theory in self.bgtheories: - self.selectedbg = theory - else: - msg = "No theory with name %s in bgtheories.\n" % theory - msg += "Available theories: %s\n" % self.bgtheories.keys() - raise KeyError(msg) - - # run configure to apply our fitconfig to the selected theory - # through its custom config function - self.configure(**self.fitconfig) - - def setdata(self, x, y, sigmay=None, xmin=None, xmax=None): - """Set data attributes: - - - ``xdata0``, ``ydata0`` and ``sigmay0`` store the initial data - and uncertainties. These attributes are not modified after - initialization. - - ``xdata``, ``ydata`` and ``sigmay`` store the data after - removing values where ``xdata < xmin`` or ``xdata > xmax``. - These attributes may be modified at a latter stage by filters. - - :param x: Abscissa data. If ``None``, :attr:`xdata`` is set to - ``numpy.array([0.0, 1.0, 2.0, ..., len(y)-1])`` - :type x: Sequence or numpy array or None - :param y: The dependant data ``y = f(x)``. ``y`` must have the same - shape as ``x`` if ``x`` is not ``None``. - :type y: Sequence or numpy array or None - :param sigmay: The uncertainties in the ``ydata`` array. These are - used as weights in the least-squares problem. - If ``None``, the uncertainties are assumed to be 1. - :type sigmay: Sequence or numpy array or None - :param xmin: Lower value of x values to use for fitting - :param xmax: Upper value of x values to use for fitting - """ - if y is None: - self.xdata0 = numpy.array([], numpy.float64) - self.ydata0 = numpy.array([], numpy.float64) - # self.sigmay0 = numpy.array([], numpy.float64) - self.xdata = numpy.array([], numpy.float64) - self.ydata = numpy.array([], numpy.float64) - # self.sigmay = numpy.array([], numpy.float64) - - else: - self.ydata0 = numpy.array(y) - self.ydata = numpy.array(y) - if x is None: - self.xdata0 = numpy.arange(len(self.ydata0)) - self.xdata = numpy.arange(len(self.ydata0)) - else: - self.xdata0 = numpy.array(x) - self.xdata = numpy.array(x) - - # default weight - if sigmay is None: - self.sigmay0 = None - self.sigmay = numpy.sqrt(self.ydata) if self.fitconfig["WeightFlag"] else None - else: - self.sigmay0 = numpy.array(sigmay) - self.sigmay = numpy.array(sigmay) if self.fitconfig["WeightFlag"] else None - - # take the data between limits, using boolean array indexing - if (xmin is not None or xmax is not None) and len(self.xdata): - xmin = xmin if xmin is not None else min(self.xdata) - xmax = xmax if xmax is not None else max(self.xdata) - bool_array = (self.xdata >= xmin) & (self.xdata <= xmax) - self.xdata = self.xdata[bool_array] - self.ydata = self.ydata[bool_array] - self.sigmay = self.sigmay[bool_array] if sigmay is not None else None - - self._finite_mask = numpy.logical_and( - numpy.all(numpy.isfinite(self.xdata), axis=tuple(range(1, self.xdata.ndim))), - numpy.isfinite(self.ydata)) - - def enableweight(self): - """This method can be called to set :attr:`sigmay`. If :attr:`sigmay0` was filled with - actual uncertainties in :meth:`setdata`, use these values. - Else, use ``sqrt(self.ydata)``. - """ - if self.sigmay0 is None: - self.sigmay = numpy.sqrt(self.ydata) if self.fitconfig["WeightFlag"] else None - else: - self.sigmay = self.sigmay0 - - def disableweight(self): - """This method can be called to set :attr:`sigmay` equal to ``None``. - As a result, :func:`leastsq` will consider that the weights in the - least square problem are 1 for all samples.""" - self.sigmay = None - - def settheory(self, theory): - """Pick a theory from :attr:`theories`. - - :param theory: Name of the theory to be used. - :raise: KeyError if ``theory`` is not a key of :attr:`theories`. - """ - if theory is None: - self.selectedtheory = None - elif theory in self.theories: - self.selectedtheory = theory - else: - msg = "No theory with name %s in theories.\n" % theory - msg += "Available theories: %s\n" % self.theories.keys() - raise KeyError(msg) - - # run configure to apply our fitconfig to the selected theory - # through its custom config function - self.configure(**self.fitconfig) - - def runfit(self, callback=None): - """Run the actual fitting and fill :attr:`fit_results` with fit results. - - Before running this method, :attr:`fit_results` must already be - populated with a list of all parameters and their estimated values. - For this, run :meth:`estimate` beforehand. - - :param callback: Optional callback function, conforming to the - signature ``callback(data)`` with ``data`` being a dictionary. - This callback function is called before and after the estimation - process, and is given a dictionary containing the values of - :attr:`state` (``'Fit in progress'`` or ``'Ready'``) - and :attr:`chisq`. - This is used for instance in :mod:`silx.gui.fit.FitWidget` to - update a widget displaying a status message. - :return: Tuple ``(fitted parameters, uncertainties, infodict)``. - *infodict* is the dictionary returned by - :func:`silx.math.fit.leastsq` when called with option - ``full_output=True``. Uncertainties is a sequence of uncertainty - values associated with each fitted parameter. - """ - # self.dataupdate() - - self.state = 'Fit in progress' - self.chisq = None - - if callback is not None: - callback(data={'chisq': self.chisq, - 'status': self.state}) - - param_val = [] - param_constraints = [] - # Initial values are set to the ones computed in estimate() - for param in self.fit_results: - param_val.append(param['estimation']) - param_constraints.append([param['code'], param['cons1'], param['cons2']]) - - # Filter-out not finite data - ywork = self.ydata[self._finite_mask] - xwork = self.xdata[self._finite_mask] - - try: - params, covariance_matrix, infodict = leastsq( - self.fitfunction, # bg + actual model function - xwork, ywork, param_val, - sigma=self.sigmay, - constraints=param_constraints, - model_deriv=self.theories[self.selectedtheory].derivative, - full_output=True, left_derivative=True) - except LinAlgError: - self.state = 'Fit failed' - callback(data={'status': self.state}) - raise - - sigmas = infodict['uncertainties'] - - for i, param in enumerate(self.fit_results): - if param['code'] != 'IGNORE': - param['fitresult'] = params[i] - param['sigma'] = sigmas[i] - - self.chisq = infodict["reduced_chisq"] - self.niter = infodict["niter"] - self.state = 'Ready' - - if callback is not None: - callback(data={'chisq': self.chisq, - 'status': self.state}) - - return params, sigmas, infodict - - ################### - # Private methods # - ################### - def fitfunction(self, x, *pars): - """Function to be fitted. - - This is the sum of the selected background function plus - the selected fit model function. - - :param x: Independent variable where the function is calculated. - :param pars: Sequence of all fit parameters. The first few parameters - are background parameters, then come the peak function parameters. - :return: Output of the fit function with ``x`` as input and ``pars`` - as fit parameters. - """ - result = numpy.zeros(numpy.shape(x), numpy.float64) - - if self.selectedbg is not None: - bg_pars_list = self.bgtheories[self.selectedbg].parameters - nb_bg_pars = len(bg_pars_list) - - bgfun = self.bgtheories[self.selectedbg].function - result += bgfun(x, self.ydata, *pars[0:nb_bg_pars]) - else: - nb_bg_pars = 0 - - selectedfun = self.theories[self.selectedtheory].function - result += selectedfun(x, *pars[nb_bg_pars:]) - - return result - - def estimate_bkg(self, x, y): - """Estimate background parameters using the function defined in - the current fit configuration. - - To change the selected background model, attribute :attr:`selectdbg` - must be changed using method :meth:`setbackground`. - - The actual background function to be used is - referenced in :attr:`bgtheories` - - :param x: Sequence of x data - :param y: sequence of y data - :return: Tuple of two sequences and one data array - ``(estimated_param, constraints, bg_data)``: - - - ``estimated_param`` is a list of estimated values for each - background parameter. - - ``constraints`` is a 2D sequence of dimension ``(n_parameters, 3)`` - - - ``constraints[i][0]``: Constraint code. - See explanation about codes in :attr:`fit_results` - - - ``constraints[i][1]`` - See explanation about 'cons1' in :attr:`fit_results` - documentation. - - - ``constraints[i][2]`` - See explanation about 'cons2' in :attr:`fit_results` - documentation. - """ - background_estimate_function = self.bgtheories[self.selectedbg].estimate - if background_estimate_function is not None: - return background_estimate_function(x, y) - else: - return [], [] - - def estimate_fun(self, x, y): - """Estimate fit parameters using the function defined in - the current fit configuration. - - :param x: Sequence of x data - :param y: sequence of y data - :param bg: Background signal, to be subtracted from ``y`` before fitting. - :return: Tuple of two sequences ``(estimated_param, constraints)``: - - - ``estimated_param`` is a list of estimated values for each - background parameter. - - ``constraints`` is a 2D sequence of dimension (n_parameters, 3) - - - ``constraints[i][0]``: Constraint code. - See explanation about codes in :attr:`fit_results` - - - ``constraints[i][1]`` - See explanation about 'cons1' in :attr:`fit_results` - documentation. - - - ``constraints[i][2]`` - See explanation about 'cons2' in :attr:`fit_results` - documentation. - :raise: ``TypeError`` if estimation function is not callable - - """ - estimatefunction = self.theories[self.selectedtheory].estimate - if hasattr(estimatefunction, '__call__'): - if not self.theories[self.selectedtheory].pymca_legacy: - return estimatefunction(x, y) - else: - # legacy pymca estimate functions have a different signature - if self.fitconfig["fitbkg"] == "No Background": - bg = numpy.zeros_like(y) - else: - if self.fitconfig["SmoothingFlag"]: - y = smooth1d(y) - bg = strip(y, - w=self.fitconfig["StripWidth"], - niterations=self.fitconfig["StripIterations"], - factor=self.fitconfig["StripThresholdFactor"]) - # fitconfig can be filled by user defined config function - xscaling = self.fitconfig.get('Xscaling', 1.0) - yscaling = self.fitconfig.get('Yscaling', 1.0) - return estimatefunction(x, y, bg, xscaling, yscaling) - else: - raise TypeError("Estimation function in attribute " + - "theories[%s]" % self.selectedtheory + - " must be callable.") - - def _load_legacy_theories(self, theories_module): - """Load theories from a custom module in the old PyMca format. - - See PyMca5.PyMcaMath.fitting.SpecfitFunctions for an example. - """ - mandatory_attributes = ["THEORY", "PARAMETERS", - "FUNCTION", "ESTIMATE"] - err_msg = "Custom fit function file must define: " - err_msg += ", ".join(mandatory_attributes) - for attr in mandatory_attributes: - if not hasattr(theories_module, attr): - raise ImportError(err_msg) - - derivative = theories_module.DERIVATIVE if hasattr(theories_module, "DERIVATIVE") else None - configure = theories_module.CONFIGURE if hasattr(theories_module, "CONFIGURE") else None - estimate = theories_module.ESTIMATE if hasattr(theories_module, "ESTIMATE") else None - if isinstance(theories_module.THEORY, (list, tuple)): - # multiple fit functions - for i in range(len(theories_module.THEORY)): - deriv = derivative[i] if derivative is not None else None - config = configure[i] if configure is not None else None - estim = estimate[i] if estimate is not None else None - self.addtheory(theories_module.THEORY[i], - FitTheory( - theories_module.FUNCTION[i], - theories_module.PARAMETERS[i], - estim, - config, - deriv, - pymca_legacy=True)) - else: - # single fit function - self.addtheory(theories_module.THEORY, - FitTheory( - theories_module.FUNCTION, - theories_module.PARAMETERS, - estimate, - configure, - derivative, - pymca_legacy=True)) - - -def test(): - from .functions import sum_gauss - from . import fittheories - from . import bgtheories - - # Create synthetic data with a sum of gaussian functions - x = numpy.arange(1000).astype(numpy.float64) - - p = [1000, 100., 250, - 255, 690., 45, - 1500, 800.5, 95] - y = 0.5 * x + 13 + sum_gauss(x, *p) - - # Fitting - fit = FitManager() - # more sensitivity necessary to resolve - # overlapping peaks at x=690 and x=800.5 - fit.setdata(x=x, y=y) - fit.loadtheories(fittheories) - fit.settheory('Gaussians') - fit.loadbgtheories(bgtheories) - fit.setbackground('Linear') - fit.estimate() - fit.runfit() - - print("Searched parameters = ", p) - print("Obtained parameters : ") - dummy_list = [] - for param in fit.fit_results: - print(param['name'], ' = ', param['fitresult']) - dummy_list.append(param['fitresult']) - print("chisq = ", fit.chisq) - - # Plot - constant, slope = dummy_list[:2] - p1 = dummy_list[2:] - print(p1) - y2 = slope * x + constant + sum_gauss(x, *p1) - - try: - from silx.gui import qt - from silx.gui.plot.PlotWindow import PlotWindow - app = qt.QApplication([]) - pw = PlotWindow(control=True) - pw.addCurve(x, y, "Original") - pw.addCurve(x, y2, "Fit result") - pw.legendsDockWidget.show() - pw.show() - app.exec_() - except ImportError: - _logger.warning("Could not import qt to display fit result as curve") - - -if __name__ == "__main__": - test() diff --git a/silx/math/fit/fittheories.py b/silx/math/fit/fittheories.py deleted file mode 100644 index 6b19a38..0000000 --- a/silx/math/fit/fittheories.py +++ /dev/null @@ -1,1374 +0,0 @@ -# coding: utf-8 -#/*########################################################################## -# -# Copyright (c) 2004-2020 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 modules provides a set of fit functions and associated -estimation functions in a format that can be imported into a -:class:`silx.math.fit.FitManager` instance. - -These functions are well suited for fitting multiple gaussian shaped peaks -typically found in spectroscopy data. The estimation functions are designed -to detect how many peaks are present in the data, and provide an initial -estimate for their height, their center location and their full-width -at half maximum (fwhm). - -The limitation of these estimation algorithms is that only gaussians having a -similar fwhm can be detected by the peak search algorithm. -This *search fwhm* can be defined by the user, if -he knows the characteristics of his data, or can be automatically estimated -based on the fwhm of the largest peak in the data. - -The source code of this module can serve as template for defining your own -fit functions. - -The functions to be imported by :meth:`FitManager.loadtheories` are defined by -a dictionary :const:`THEORY`: with the following structure:: - - from silx.math.fit.fittheory import FitTheory - - THEORY = { - 'theory_name_1': FitTheory( - description='Description of theory 1', - function=fitfunction1, - parameters=('param name 1', 'param name 2', …), - estimate=estimation_function1, - configure=configuration_function1, - derivative=derivative_function1), - - 'theory_name_2': FitTheory(…), - } - -.. note:: - - Consider using an OrderedDict instead of a regular dictionary, when - defining your own theory dictionary, if the order matters to you. - This will likely be the case if you intend to load a selection of - functions in a GUI such as :class:`silx.gui.fit.FitManager`. - -Theory names can be customized (e.g. ``gauss, lorentz, splitgauss``…). - -The mandatory parameters for :class:`FitTheory` are ``function`` and -``parameters``. - -You can also define an ``INIT`` function that will be executed by -:meth:`FitManager.loadtheories`. - -See the documentation of :class:`silx.math.fit.fittheory.FitTheory` -for more information. - -Module members: ---------------- -""" -import numpy -from collections import OrderedDict -import logging - -from silx.math.fit import functions -from silx.math.fit.peaks import peak_search, guess_fwhm -from silx.math.fit.filters import strip, savitsky_golay -from silx.math.fit.leastsq import leastsq -from silx.math.fit.fittheory import FitTheory - -_logger = logging.getLogger(__name__) - -__authors__ = ["V.A. Sole", "P. Knobel"] -__license__ = "MIT" -__date__ = "15/05/2017" - - -DEFAULT_CONFIG = { - 'NoConstraintsFlag': False, - 'PositiveFwhmFlag': True, - 'PositiveHeightAreaFlag': True, - 'SameFwhmFlag': False, - 'QuotedPositionFlag': False, # peak not outside data range - 'QuotedEtaFlag': False, # force 0 < eta < 1 - # Peak detection - 'AutoScaling': False, - 'Yscaling': 1.0, - 'FwhmPoints': 8, - 'AutoFwhm': True, - 'Sensitivity': 2.5, - 'ForcePeakPresence': True, - # Hypermet - 'HypermetTails': 15, - 'QuotedFwhmFlag': 0, - 'MaxFwhm2InputRatio': 1.5, - 'MinFwhm2InputRatio': 0.4, - # short tail parameters - 'MinGaussArea4ShortTail': 50000., - 'InitialShortTailAreaRatio': 0.050, - 'MaxShortTailAreaRatio': 0.100, - 'MinShortTailAreaRatio': 0.0010, - 'InitialShortTailSlopeRatio': 0.70, - 'MaxShortTailSlopeRatio': 2.00, - 'MinShortTailSlopeRatio': 0.50, - # long tail parameters - 'MinGaussArea4LongTail': 1000.0, - 'InitialLongTailAreaRatio': 0.050, - 'MaxLongTailAreaRatio': 0.300, - 'MinLongTailAreaRatio': 0.010, - 'InitialLongTailSlopeRatio': 20.0, - 'MaxLongTailSlopeRatio': 50.0, - 'MinLongTailSlopeRatio': 5.0, - # step tail - 'MinGaussHeight4StepTail': 5000., - 'InitialStepTailHeightRatio': 0.002, - 'MaxStepTailHeightRatio': 0.0100, - 'MinStepTailHeightRatio': 0.0001, - # Hypermet constraints - # position in range [estimated position +- estimated fwhm/2] - 'HypermetQuotedPositionFlag': True, - 'DeltaPositionFwhmUnits': 0.5, - 'SameSlopeRatioFlag': 1, - 'SameAreaRatioFlag': 1, - # Strip bg removal - 'StripBackgroundFlag': True, - 'SmoothingFlag': True, - 'SmoothingWidth': 5, - 'StripWidth': 2, - 'StripIterations': 5000, - 'StripThresholdFactor': 1.0} -"""This dictionary defines default configuration parameters that have effects -on fit functions and estimation functions, mainly on fit constraints. -This dictionary is accessible as attribute :attr:`FitTheories.config`, -which can be modified by configuration functions defined in -:const:`CONFIGURE`. -""" - -CFREE = 0 -CPOSITIVE = 1 -CQUOTED = 2 -CFIXED = 3 -CFACTOR = 4 -CDELTA = 5 -CSUM = 6 -CIGNORED = 7 - - -class FitTheories(object): - """Class wrapping functions from :class:`silx.math.fit.functions` - and providing estimate functions for all of these fit functions.""" - def __init__(self, config=None): - if config is None: - self.config = DEFAULT_CONFIG - else: - self.config = config - - def ahypermet(self, x, *pars): - """ - Wrapping of :func:`silx.math.fit.functions.sum_ahypermet` without - the tail flags in the function signature. - - Depending on the value of `self.config['HypermetTails']`, one can - activate or deactivate the various terms of the hypermet function. - - `self.config['HypermetTails']` must be an integer between 0 and 15. - It is a set of 4 binary flags, one for activating each one of the - hypermet terms: *gaussian function, short tail, long tail, step*. - - For example, 15 can be expressed as ``1111`` in base 2, so a flag of - 15 means all terms are active. - """ - g_term = self.config['HypermetTails'] & 1 - st_term = (self.config['HypermetTails'] >> 1) & 1 - lt_term = (self.config['HypermetTails'] >> 2) & 1 - step_term = (self.config['HypermetTails'] >> 3) & 1 - return functions.sum_ahypermet(x, *pars, - gaussian_term=g_term, st_term=st_term, - lt_term=lt_term, step_term=step_term) - - def poly(self, x, *pars): - """Order n polynomial. - The order of the polynomial is defined by the number of - coefficients (``*pars``). - - """ - p = numpy.poly1d(pars) - return p(x) - - @staticmethod - def estimate_poly(x, y, n=2): - """Estimate polynomial coefficients for a degree n polynomial. - - """ - pcoeffs = numpy.polyfit(x, y, n) - constraints = numpy.zeros((n + 1, 3), numpy.float64) - return pcoeffs, constraints - - def estimate_quadratic(self, x, y): - """Estimate quadratic coefficients - - """ - return self.estimate_poly(x, y, n=2) - - def estimate_cubic(self, x, y): - """Estimate coefficients for a degree 3 polynomial - - """ - return self.estimate_poly(x, y, n=3) - - def estimate_quartic(self, x, y): - """Estimate coefficients for a degree 4 polynomial - - """ - return self.estimate_poly(x, y, n=4) - - def estimate_quintic(self, x, y): - """Estimate coefficients for a degree 5 polynomial - - """ - return self.estimate_poly(x, y, n=5) - - def strip_bg(self, y): - """Return the strip background of y, using parameters from - :attr:`config` dictionary (*StripBackgroundFlag, StripWidth, - StripIterations, StripThresholdFactor*)""" - remove_strip_bg = self.config.get('StripBackgroundFlag', False) - if remove_strip_bg: - if self.config['SmoothingFlag']: - y = savitsky_golay(y, self.config['SmoothingWidth']) - strip_width = self.config['StripWidth'] - strip_niterations = self.config['StripIterations'] - strip_thr_factor = self.config['StripThresholdFactor'] - return strip(y, w=strip_width, - niterations=strip_niterations, - factor=strip_thr_factor) - else: - return numpy.zeros_like(y) - - def guess_yscaling(self, y): - """Estimate scaling for y prior to peak search. - A smoothing filter is applied to y to estimate the noise level - (chi-squared) - - :param y: Data array - :return: Scaling factor - """ - # ensure y is an array - yy = numpy.array(y, copy=False) - - # smooth - convolution_kernel = numpy.ones(shape=(3,)) / 3. - ysmooth = numpy.convolve(y, convolution_kernel, mode="same") - - # remove zeros - idx_array = numpy.fabs(y) > 0.0 - yy = yy[idx_array] - ysmooth = ysmooth[idx_array] - - # compute scaling factor - chisq = numpy.mean((yy - ysmooth)**2 / numpy.fabs(yy)) - if chisq > 0: - return 1. / chisq - else: - return 1.0 - - def peak_search(self, y, fwhm, sensitivity): - """Search for peaks in y array, after padding the array and - multiplying its value by a scaling factor. - - :param y: 1-D data array - :param int fwhm: Typical full width at half maximum for peaks, - in number of points. This parameter is used for to discriminate between - true peaks and background fluctuations. - :param float sensitivity: Sensitivity parameter. This is a threshold factor - for peak detection. Only peaks larger than the standard deviation - of the noise multiplied by this sensitivity parameter are detected. - :return: List of peak indices - """ - # add padding - ysearch = numpy.ones((len(y) + 2 * fwhm,), numpy.float64) - ysearch[0:fwhm] = y[0] - ysearch[-1:-fwhm - 1:-1] = y[len(y)-1] - ysearch[fwhm:fwhm + len(y)] = y[:] - - scaling = self.guess_yscaling(y) if self.config["AutoScaling"] else self.config["Yscaling"] - - if len(ysearch) > 1.5 * fwhm: - peaks = peak_search(scaling * ysearch, - fwhm=fwhm, sensitivity=sensitivity) - return [peak_index - fwhm for peak_index in peaks - if 0 <= peak_index - fwhm < len(y)] - else: - return [] - - def estimate_height_position_fwhm(self, x, y): - """Estimation of *Height, Position, FWHM* of peaks, for gaussian-like - curves. - - This functions finds how many parameters are needed, based on the - number of peaks detected. Then it estimates the fit parameters - with a few iterations of fitting gaussian functions. - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - Parameters to be estimated for each peak are: - *Height, Position, FWHM*. - Fit constraints depend on :attr:`config`. - """ - fittedpar = [] - - bg = self.strip_bg(y) - - if self.config['AutoFwhm']: - search_fwhm = guess_fwhm(y) - else: - search_fwhm = int(float(self.config['FwhmPoints'])) - search_sens = float(self.config['Sensitivity']) - - if search_fwhm < 3: - _logger.warning("Setting peak fwhm to 3 (lower limit)") - search_fwhm = 3 - self.config['FwhmPoints'] = 3 - - if search_sens < 1: - _logger.warning("Setting peak search sensitivity to 1. " + - "(lower limit to filter out noise peaks)") - search_sens = 1 - self.config['Sensitivity'] = 1 - - npoints = len(y) - - # Find indices of peaks in data array - peaks = self.peak_search(y, - fwhm=search_fwhm, - sensitivity=search_sens) - - if not len(peaks): - forcepeak = int(float(self.config.get('ForcePeakPresence', 0))) - if forcepeak: - delta = y - bg - # get index of global maximum - # (first one if several samples are equal to this value) - peaks = [numpy.nonzero(delta == delta.max())[0][0]] - - # Find index of largest peak in peaks array - index_largest_peak = 0 - if len(peaks) > 0: - # estimate fwhm as 5 * sampling interval - sig = 5 * abs(x[npoints - 1] - x[0]) / npoints - peakpos = x[int(peaks[0])] - if abs(peakpos) < 1.0e-16: - peakpos = 0.0 - param = numpy.array( - [y[int(peaks[0])] - bg[int(peaks[0])], peakpos, sig]) - height_largest_peak = param[0] - peak_index = 1 - for i in peaks[1:]: - param2 = numpy.array( - [y[int(i)] - bg[int(i)], x[int(i)], sig]) - param = numpy.concatenate((param, param2)) - if param2[0] > height_largest_peak: - height_largest_peak = param2[0] - index_largest_peak = peak_index - peak_index += 1 - - # Subtract background - xw = x - yw = y - bg - - cons = numpy.zeros((len(param), 3), numpy.float64) - - # peak height must be positive - cons[0:len(param):3, 0] = CPOSITIVE - # force peaks to stay around their position - cons[1:len(param):3, 0] = CQUOTED - - # set possible peak range to estimated peak +- guessed fwhm - if len(xw) > search_fwhm: - fwhmx = numpy.fabs(xw[int(search_fwhm)] - xw[0]) - cons[1:len(param):3, 1] = param[1:len(param):3] - 0.5 * fwhmx - cons[1:len(param):3, 2] = param[1:len(param):3] + 0.5 * fwhmx - else: - shape = [max(1, int(x)) for x in (param[1:len(param):3])] - cons[1:len(param):3, 1] = min(xw) * numpy.ones( - shape, - numpy.float64) - cons[1:len(param):3, 2] = max(xw) * numpy.ones( - shape, - numpy.float64) - - # ensure fwhm is positive - cons[2:len(param):3, 0] = CPOSITIVE - - # run a quick iterative fit (4 iterations) to improve - # estimations - fittedpar, _, _ = leastsq(functions.sum_gauss, xw, yw, param, - max_iter=4, constraints=cons.tolist(), - full_output=True) - - # set final constraints based on config parameters - cons = numpy.zeros((len(fittedpar), 3), numpy.float64) - peak_index = 0 - for i in range(len(peaks)): - # Setup height area constrains - if not self.config['NoConstraintsFlag']: - if self.config['PositiveHeightAreaFlag']: - cons[peak_index, 0] = CPOSITIVE - cons[peak_index, 1] = 0 - cons[peak_index, 2] = 0 - peak_index += 1 - - # Setup position constrains - if not self.config['NoConstraintsFlag']: - if self.config['QuotedPositionFlag']: - cons[peak_index, 0] = CQUOTED - cons[peak_index, 1] = min(x) - cons[peak_index, 2] = max(x) - peak_index += 1 - - # Setup positive FWHM constrains - if not self.config['NoConstraintsFlag']: - if self.config['PositiveFwhmFlag']: - cons[peak_index, 0] = CPOSITIVE - cons[peak_index, 1] = 0 - cons[peak_index, 2] = 0 - if self.config['SameFwhmFlag']: - if i != index_largest_peak: - cons[peak_index, 0] = CFACTOR - cons[peak_index, 1] = 3 * index_largest_peak + 2 - cons[peak_index, 2] = 1.0 - peak_index += 1 - - return fittedpar, cons - - def estimate_agauss(self, x, y): - """Estimation of *Area, Position, FWHM* of peaks, for gaussian-like - curves. - - This functions uses :meth:`estimate_height_position_fwhm`, then - converts the height parameters to area under the curve with the - formula ``area = sqrt(2*pi) * height * fwhm / (2 * sqrt(2 * log(2))`` - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - Parameters to be estimated for each peak are: - *Area, Position, FWHM*. - Fit constraints depend on :attr:`config`. - """ - fittedpar, cons = self.estimate_height_position_fwhm(x, y) - # get the number of found peaks - npeaks = len(fittedpar) // 3 - for i in range(npeaks): - height = fittedpar[3 * i] - fwhm = fittedpar[3 * i + 2] - # Replace height with area in fittedpar - fittedpar[3 * i] = numpy.sqrt(2 * numpy.pi) * height * fwhm / ( - 2.0 * numpy.sqrt(2 * numpy.log(2))) - return fittedpar, cons - - def estimate_alorentz(self, x, y): - """Estimation of *Area, Position, FWHM* of peaks, for Lorentzian - curves. - - This functions uses :meth:`estimate_height_position_fwhm`, then - converts the height parameters to area under the curve with the - formula ``area = height * fwhm * 0.5 * pi`` - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - Parameters to be estimated for each peak are: - *Area, Position, FWHM*. - Fit constraints depend on :attr:`config`. - """ - fittedpar, cons = self.estimate_height_position_fwhm(x, y) - # get the number of found peaks - npeaks = len(fittedpar) // 3 - for i in range(npeaks): - height = fittedpar[3 * i] - fwhm = fittedpar[3 * i + 2] - # Replace height with area in fittedpar - fittedpar[3 * i] = (height * fwhm * 0.5 * numpy.pi) - return fittedpar, cons - - def estimate_splitgauss(self, x, y): - """Estimation of *Height, Position, FWHM1, FWHM2* of peaks, for - asymmetric gaussian-like curves. - - This functions uses :meth:`estimate_height_position_fwhm`, then - adds a second (identical) estimation of FWHM to the fit parameters - for each peak, and the corresponding constraint. - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - Parameters to be estimated for each peak are: - *Height, Position, FWHM1, FWHM2*. - Fit constraints depend on :attr:`config`. - """ - fittedpar, cons = self.estimate_height_position_fwhm(x, y) - # get the number of found peaks - npeaks = len(fittedpar) // 3 - estimated_parameters = [] - estimated_constraints = numpy.zeros((4 * npeaks, 3), numpy.float64) - for i in range(npeaks): - for j in range(3): - estimated_parameters.append(fittedpar[3 * i + j]) - # fwhm2 estimate = fwhm1 - estimated_parameters.append(fittedpar[3 * i + 2]) - # height - estimated_constraints[4 * i, 0] = cons[3 * i, 0] - estimated_constraints[4 * i, 1] = cons[3 * i, 1] - estimated_constraints[4 * i, 2] = cons[3 * i, 2] - # position - estimated_constraints[4 * i + 1, 0] = cons[3 * i + 1, 0] - estimated_constraints[4 * i + 1, 1] = cons[3 * i + 1, 1] - estimated_constraints[4 * i + 1, 2] = cons[3 * i + 1, 2] - # fwhm1 - estimated_constraints[4 * i + 2, 0] = cons[3 * i + 2, 0] - estimated_constraints[4 * i + 2, 1] = cons[3 * i + 2, 1] - estimated_constraints[4 * i + 2, 2] = cons[3 * i + 2, 2] - # fwhm2 - estimated_constraints[4 * i + 3, 0] = cons[3 * i + 2, 0] - estimated_constraints[4 * i + 3, 1] = cons[3 * i + 2, 1] - estimated_constraints[4 * i + 3, 2] = cons[3 * i + 2, 2] - if cons[3 * i + 2, 0] == CFACTOR: - # convert indices of related parameters - # (this happens if SameFwhmFlag == True) - estimated_constraints[4 * i + 2, 1] = \ - int(cons[3 * i + 2, 1] / 3) * 4 + 2 - estimated_constraints[4 * i + 3, 1] = \ - int(cons[3 * i + 2, 1] / 3) * 4 + 3 - return estimated_parameters, estimated_constraints - - def estimate_pvoigt(self, x, y): - """Estimation of *Height, Position, FWHM, eta* of peaks, for - pseudo-Voigt curves. - - Pseudo-Voigt are a sum of a gaussian curve *G(x)* and a lorentzian - curve *L(x)* with the same height, center, fwhm parameters: - ``y(x) = eta * G(x) + (1-eta) * L(x)`` - - This functions uses :meth:`estimate_height_position_fwhm`, then - adds a constant estimation of *eta* (0.5) to the fit parameters - for each peak, and the corresponding constraint. - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - Parameters to be estimated for each peak are: - *Height, Position, FWHM, eta*. - Constraint for the eta parameter can be set to QUOTED (0.--1.) - by setting :attr:`config`['QuotedEtaFlag'] to ``True``. - If this is not the case, the constraint code is set to FREE. - """ - fittedpar, cons = self.estimate_height_position_fwhm(x, y) - npeaks = len(fittedpar) // 3 - newpar = [] - newcons = numpy.zeros((4 * npeaks, 3), numpy.float64) - # find out related parameters proper index - if not self.config['NoConstraintsFlag']: - if self.config['SameFwhmFlag']: - j = 0 - # get the index of the free FWHM - for i in range(npeaks): - if cons[3 * i + 2, 0] != 4: - j = i - for i in range(npeaks): - if i != j: - cons[3 * i + 2, 1] = 4 * j + 2 - for i in range(npeaks): - newpar.append(fittedpar[3 * i]) - newpar.append(fittedpar[3 * i + 1]) - newpar.append(fittedpar[3 * i + 2]) - newpar.append(0.5) - # height - newcons[4 * i, 0] = cons[3 * i, 0] - newcons[4 * i, 1] = cons[3 * i, 1] - newcons[4 * i, 2] = cons[3 * i, 2] - # position - newcons[4 * i + 1, 0] = cons[3 * i + 1, 0] - newcons[4 * i + 1, 1] = cons[3 * i + 1, 1] - newcons[4 * i + 1, 2] = cons[3 * i + 1, 2] - # fwhm - newcons[4 * i + 2, 0] = cons[3 * i + 2, 0] - newcons[4 * i + 2, 1] = cons[3 * i + 2, 1] - newcons[4 * i + 2, 2] = cons[3 * i + 2, 2] - # Eta constrains - newcons[4 * i + 3, 0] = CFREE - newcons[4 * i + 3, 1] = 0 - newcons[4 * i + 3, 2] = 0 - if self.config['QuotedEtaFlag']: - newcons[4 * i + 3, 0] = CQUOTED - newcons[4 * i + 3, 1] = 0.0 - newcons[4 * i + 3, 2] = 1.0 - return newpar, newcons - - def estimate_splitpvoigt(self, x, y): - """Estimation of *Height, Position, FWHM1, FWHM2, eta* of peaks, for - asymmetric pseudo-Voigt curves. - - This functions uses :meth:`estimate_height_position_fwhm`, then - adds an identical FWHM2 parameter and a constant estimation of - *eta* (0.5) to the fit parameters for each peak, and the corresponding - constraints. - - Constraint for the eta parameter can be set to QUOTED (0.--1.) - by setting :attr:`config`['QuotedEtaFlag'] to ``True``. - If this is not the case, the constraint code is set to FREE. - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - Parameters to be estimated for each peak are: - *Height, Position, FWHM1, FWHM2, eta*. - """ - fittedpar, cons = self.estimate_height_position_fwhm(x, y) - npeaks = len(fittedpar) // 3 - newpar = [] - newcons = numpy.zeros((5 * npeaks, 3), numpy.float64) - # find out related parameters proper index - if not self.config['NoConstraintsFlag']: - if self.config['SameFwhmFlag']: - j = 0 - # get the index of the free FWHM - for i in range(npeaks): - if cons[3 * i + 2, 0] != 4: - j = i - for i in range(npeaks): - if i != j: - cons[3 * i + 2, 1] = 4 * j + 2 - for i in range(npeaks): - # height - newpar.append(fittedpar[3 * i]) - # position - newpar.append(fittedpar[3 * i + 1]) - # fwhm1 - newpar.append(fittedpar[3 * i + 2]) - # fwhm2 estimate equal to fwhm1 - newpar.append(fittedpar[3 * i + 2]) - # eta - newpar.append(0.5) - # constraint codes - # ---------------- - # height - newcons[5 * i, 0] = cons[3 * i, 0] - # position - newcons[5 * i + 1, 0] = cons[3 * i + 1, 0] - # fwhm1 - newcons[5 * i + 2, 0] = cons[3 * i + 2, 0] - # fwhm2 - newcons[5 * i + 3, 0] = cons[3 * i + 2, 0] - # cons 1 - # ------ - newcons[5 * i, 1] = cons[3 * i, 1] - newcons[5 * i + 1, 1] = cons[3 * i + 1, 1] - newcons[5 * i + 2, 1] = cons[3 * i + 2, 1] - newcons[5 * i + 3, 1] = cons[3 * i + 2, 1] - # cons 2 - # ------ - newcons[5 * i, 2] = cons[3 * i, 2] - newcons[5 * i + 1, 2] = cons[3 * i + 1, 2] - newcons[5 * i + 2, 2] = cons[3 * i + 2, 2] - newcons[5 * i + 3, 2] = cons[3 * i + 2, 2] - - if cons[3 * i + 2, 0] == CFACTOR: - # fwhm2 connstraint depends on fwhm1 - newcons[5 * i + 3, 1] = newcons[5 * i + 2, 1] + 1 - # eta constraints - newcons[5 * i + 4, 0] = CFREE - newcons[5 * i + 4, 1] = 0 - newcons[5 * i + 4, 2] = 0 - if self.config['QuotedEtaFlag']: - newcons[5 * i + 4, 0] = CQUOTED - newcons[5 * i + 4, 1] = 0.0 - newcons[5 * i + 4, 2] = 1.0 - return newpar, newcons - - def estimate_apvoigt(self, x, y): - """Estimation of *Area, Position, FWHM1, eta* of peaks, for - pseudo-Voigt curves. - - This functions uses :meth:`estimate_pvoigt`, then converts the height - parameter to area. - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - Parameters to be estimated for each peak are: - *Area, Position, FWHM, eta*. - """ - fittedpar, cons = self.estimate_pvoigt(x, y) - npeaks = len(fittedpar) // 4 - # Assume 50% of the area is determined by the gaussian and 50% by - # the Lorentzian. - for i in range(npeaks): - height = fittedpar[4 * i] - fwhm = fittedpar[4 * i + 2] - fittedpar[4 * i] = 0.5 * (height * fwhm * 0.5 * numpy.pi) +\ - 0.5 * (height * fwhm / (2.0 * numpy.sqrt(2 * numpy.log(2))) - ) * numpy.sqrt(2 * numpy.pi) - return fittedpar, cons - - def estimate_ahypermet(self, x, y): - """Estimation of *area, position, fwhm, st_area_r, st_slope_r, - lt_area_r, lt_slope_r, step_height_r* of peaks, for hypermet curves. - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - Parameters to be estimated for each peak are: - *area, position, fwhm, st_area_r, st_slope_r, - lt_area_r, lt_slope_r, step_height_r* . - """ - yscaling = self.config.get('Yscaling', 1.0) - if yscaling == 0: - yscaling = 1.0 - fittedpar, cons = self.estimate_height_position_fwhm(x, y) - npeaks = len(fittedpar) // 3 - newpar = [] - newcons = numpy.zeros((8 * npeaks, 3), numpy.float64) - main_peak = 0 - # find out related parameters proper index - if not self.config['NoConstraintsFlag']: - if self.config['SameFwhmFlag']: - j = 0 - # get the index of the free FWHM - for i in range(npeaks): - if cons[3 * i + 2, 0] != 4: - j = i - for i in range(npeaks): - if i != j: - cons[3 * i + 2, 1] = 8 * j + 2 - main_peak = j - for i in range(npeaks): - if fittedpar[3 * i] > fittedpar[3 * main_peak]: - main_peak = i - - for i in range(npeaks): - height = fittedpar[3 * i] - position = fittedpar[3 * i + 1] - fwhm = fittedpar[3 * i + 2] - area = (height * fwhm / (2.0 * numpy.sqrt(2 * numpy.log(2))) - ) * numpy.sqrt(2 * numpy.pi) - # the gaussian parameters - newpar.append(area) - newpar.append(position) - newpar.append(fwhm) - # print "area, pos , fwhm = ",area,position,fwhm - # Avoid zero derivatives because of not calculating contribution - g_term = 1 - st_term = 1 - lt_term = 1 - step_term = 1 - if self.config['HypermetTails'] != 0: - g_term = self.config['HypermetTails'] & 1 - st_term = (self.config['HypermetTails'] >> 1) & 1 - lt_term = (self.config['HypermetTails'] >> 2) & 1 - step_term = (self.config['HypermetTails'] >> 3) & 1 - if g_term == 0: - # fix the gaussian parameters - newcons[8 * i, 0] = CFIXED - newcons[8 * i + 1, 0] = CFIXED - newcons[8 * i + 2, 0] = CFIXED - # the short tail parameters - if ((area * yscaling) < - self.config['MinGaussArea4ShortTail']) | \ - (st_term == 0): - newpar.append(0.0) - newpar.append(0.0) - newcons[8 * i + 3, 0] = CFIXED - newcons[8 * i + 3, 1] = 0.0 - newcons[8 * i + 3, 2] = 0.0 - newcons[8 * i + 4, 0] = CFIXED - newcons[8 * i + 4, 1] = 0.0 - newcons[8 * i + 4, 2] = 0.0 - else: - newpar.append(self.config['InitialShortTailAreaRatio']) - newpar.append(self.config['InitialShortTailSlopeRatio']) - newcons[8 * i + 3, 0] = CQUOTED - newcons[8 * i + 3, 1] = self.config['MinShortTailAreaRatio'] - newcons[8 * i + 3, 2] = self.config['MaxShortTailAreaRatio'] - newcons[8 * i + 4, 0] = CQUOTED - newcons[8 * i + 4, 1] = self.config['MinShortTailSlopeRatio'] - newcons[8 * i + 4, 2] = self.config['MaxShortTailSlopeRatio'] - # the long tail parameters - if ((area * yscaling) < - self.config['MinGaussArea4LongTail']) | \ - (lt_term == 0): - newpar.append(0.0) - newpar.append(0.0) - newcons[8 * i + 5, 0] = CFIXED - newcons[8 * i + 5, 1] = 0.0 - newcons[8 * i + 5, 2] = 0.0 - newcons[8 * i + 6, 0] = CFIXED - newcons[8 * i + 6, 1] = 0.0 - newcons[8 * i + 6, 2] = 0.0 - else: - newpar.append(self.config['InitialLongTailAreaRatio']) - newpar.append(self.config['InitialLongTailSlopeRatio']) - newcons[8 * i + 5, 0] = CQUOTED - newcons[8 * i + 5, 1] = self.config['MinLongTailAreaRatio'] - newcons[8 * i + 5, 2] = self.config['MaxLongTailAreaRatio'] - newcons[8 * i + 6, 0] = CQUOTED - newcons[8 * i + 6, 1] = self.config['MinLongTailSlopeRatio'] - newcons[8 * i + 6, 2] = self.config['MaxLongTailSlopeRatio'] - # the step parameters - if ((height * yscaling) < - self.config['MinGaussHeight4StepTail']) | \ - (step_term == 0): - newpar.append(0.0) - newcons[8 * i + 7, 0] = CFIXED - newcons[8 * i + 7, 1] = 0.0 - newcons[8 * i + 7, 2] = 0.0 - else: - newpar.append(self.config['InitialStepTailHeightRatio']) - newcons[8 * i + 7, 0] = CQUOTED - newcons[8 * i + 7, 1] = self.config['MinStepTailHeightRatio'] - newcons[8 * i + 7, 2] = self.config['MaxStepTailHeightRatio'] - # if self.config['NoConstraintsFlag'] == 1: - # newcons=numpy.zeros((8*npeaks, 3),numpy.float64) - if npeaks > 0: - if g_term: - if self.config['PositiveHeightAreaFlag']: - for i in range(npeaks): - newcons[8 * i, 0] = CPOSITIVE - if self.config['PositiveFwhmFlag']: - for i in range(npeaks): - newcons[8 * i + 2, 0] = CPOSITIVE - if self.config['SameFwhmFlag']: - for i in range(npeaks): - if i != main_peak: - newcons[8 * i + 2, 0] = CFACTOR - newcons[8 * i + 2, 1] = 8 * main_peak + 2 - newcons[8 * i + 2, 2] = 1.0 - if self.config['HypermetQuotedPositionFlag']: - for i in range(npeaks): - delta = self.config['DeltaPositionFwhmUnits'] * fwhm - newcons[8 * i + 1, 0] = CQUOTED - newcons[8 * i + 1, 1] = newpar[8 * i + 1] - delta - newcons[8 * i + 1, 2] = newpar[8 * i + 1] + delta - if self.config['SameSlopeRatioFlag']: - for i in range(npeaks): - if i != main_peak: - newcons[8 * i + 4, 0] = CFACTOR - newcons[8 * i + 4, 1] = 8 * main_peak + 4 - newcons[8 * i + 4, 2] = 1.0 - newcons[8 * i + 6, 0] = CFACTOR - newcons[8 * i + 6, 1] = 8 * main_peak + 6 - newcons[8 * i + 6, 2] = 1.0 - if self.config['SameAreaRatioFlag']: - for i in range(npeaks): - if i != main_peak: - newcons[8 * i + 3, 0] = CFACTOR - newcons[8 * i + 3, 1] = 8 * main_peak + 3 - newcons[8 * i + 3, 2] = 1.0 - newcons[8 * i + 5, 0] = CFACTOR - newcons[8 * i + 5, 1] = 8 * main_peak + 5 - newcons[8 * i + 5, 2] = 1.0 - return newpar, newcons - - def estimate_stepdown(self, x, y): - """Estimation of parameters for stepdown curves. - - The functions estimates gaussian parameters for the derivative of - the data, takes the largest gaussian peak and uses its estimated - parameters to define the center of the step and its fwhm. The - estimated amplitude returned is simply ``max(y) - min(y)``. - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit newconstraints. - Parameters to be estimated for each stepdown are: - *height, centroid, fwhm* . - """ - crappyfilter = [-0.25, -0.75, 0.0, 0.75, 0.25] - cutoff = len(crappyfilter) // 2 - y_deriv = numpy.convolve(y, - crappyfilter, - mode="valid") - - # make the derivative's peak have the same amplitude as the step - if max(y_deriv) > 0: - y_deriv = y_deriv * max(y) / max(y_deriv) - - fittedpar, newcons = self.estimate_height_position_fwhm( - x[cutoff:-cutoff], y_deriv) - - data_amplitude = max(y) - min(y) - - # use parameters from largest gaussian found - if len(fittedpar): - npeaks = len(fittedpar) // 3 - largest_index = 0 - largest = [data_amplitude, - fittedpar[3 * largest_index + 1], - fittedpar[3 * largest_index + 2]] - for i in range(npeaks): - if fittedpar[3 * i] > largest[0]: - largest_index = i - largest = [data_amplitude, - fittedpar[3 * largest_index + 1], - fittedpar[3 * largest_index + 2]] - else: - # no peak was found - largest = [data_amplitude, # height - x[len(x)//2], # center: middle of x range - self.config["FwhmPoints"] * (x[1] - x[0])] # fwhm: default value - - # Setup constrains - newcons = numpy.zeros((3, 3), numpy.float64) - if not self.config['NoConstraintsFlag']: - # Setup height constrains - if self.config['PositiveHeightAreaFlag']: - newcons[0, 0] = CPOSITIVE - newcons[0, 1] = 0 - newcons[0, 2] = 0 - - # Setup position constrains - if self.config['QuotedPositionFlag']: - newcons[1, 0] = CQUOTED - newcons[1, 1] = min(x) - newcons[1, 2] = max(x) - - # Setup positive FWHM constrains - if self.config['PositiveFwhmFlag']: - newcons[2, 0] = CPOSITIVE - newcons[2, 1] = 0 - newcons[2, 2] = 0 - - return largest, newcons - - def estimate_slit(self, x, y): - """Estimation of parameters for slit curves. - - The functions estimates stepup and stepdown parameters for the largest - steps, and uses them for calculating the center (middle between stepup - and stepdown), the height (maximum amplitude in data), the fwhm - (distance between the up- and down-step centers) and the beamfwhm - (average of FWHM for up- and down-step). - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - Parameters to be estimated for each slit are: - *height, position, fwhm, beamfwhm* . - """ - largestup, cons = self.estimate_stepup(x, y) - largestdown, cons = self.estimate_stepdown(x, y) - fwhm = numpy.fabs(largestdown[1] - largestup[1]) - beamfwhm = 0.5 * (largestup[2] + largestdown[1]) - beamfwhm = min(beamfwhm, fwhm / 10.0) - beamfwhm = max(beamfwhm, (max(x) - min(x)) * 3.0 / len(x)) - - y_minus_bg = y - self.strip_bg(y) - height = max(y_minus_bg) - - i1 = numpy.nonzero(y_minus_bg >= 0.5 * height)[0] - xx = numpy.take(x, i1) - position = (xx[0] + xx[-1]) / 2.0 - fwhm = xx[-1] - xx[0] - largest = [height, position, fwhm, beamfwhm] - cons = numpy.zeros((4, 3), numpy.float64) - # Setup constrains - if not self.config['NoConstraintsFlag']: - # Setup height constrains - if self.config['PositiveHeightAreaFlag']: - cons[0, 0] = CPOSITIVE - cons[0, 1] = 0 - cons[0, 2] = 0 - - # Setup position constrains - if self.config['QuotedPositionFlag']: - cons[1, 0] = CQUOTED - cons[1, 1] = min(x) - cons[1, 2] = max(x) - - # Setup positive FWHM constrains - if self.config['PositiveFwhmFlag']: - cons[2, 0] = CPOSITIVE - cons[2, 1] = 0 - cons[2, 2] = 0 - - # Setup positive FWHM constrains - if self.config['PositiveFwhmFlag']: - cons[3, 0] = CPOSITIVE - cons[3, 1] = 0 - cons[3, 2] = 0 - return largest, cons - - def estimate_stepup(self, x, y): - """Estimation of parameters for a single step up curve. - - The functions estimates gaussian parameters for the derivative of - the data, takes the largest gaussian peak and uses its estimated - parameters to define the center of the step and its fwhm. The - estimated amplitude returned is simply ``max(y) - min(y)``. - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - Parameters to be estimated for each stepup are: - *height, centroid, fwhm* . - """ - crappyfilter = [0.25, 0.75, 0.0, -0.75, -0.25] - cutoff = len(crappyfilter) // 2 - y_deriv = numpy.convolve(y, crappyfilter, mode="valid") - if max(y_deriv) > 0: - y_deriv = y_deriv * max(y) / max(y_deriv) - - fittedpar, cons = self.estimate_height_position_fwhm( - x[cutoff:-cutoff], y_deriv) - - # for height, use the data amplitude after removing the background - data_amplitude = max(y) - min(y) - - # find params of the largest gaussian found - if len(fittedpar): - npeaks = len(fittedpar) // 3 - largest_index = 0 - largest = [data_amplitude, - fittedpar[3 * largest_index + 1], - fittedpar[3 * largest_index + 2]] - for i in range(npeaks): - if fittedpar[3 * i] > largest[0]: - largest_index = i - largest = [fittedpar[3 * largest_index], - fittedpar[3 * largest_index + 1], - fittedpar[3 * largest_index + 2]] - else: - # no peak was found - largest = [data_amplitude, # height - x[len(x)//2], # center: middle of x range - self.config["FwhmPoints"] * (x[1] - x[0])] # fwhm: default value - - newcons = numpy.zeros((3, 3), numpy.float64) - # Setup constrains - if not self.config['NoConstraintsFlag']: - # Setup height constraints - if self.config['PositiveHeightAreaFlag']: - newcons[0, 0] = CPOSITIVE - newcons[0, 1] = 0 - newcons[0, 2] = 0 - - # Setup position constraints - if self.config['QuotedPositionFlag']: - newcons[1, 0] = CQUOTED - newcons[1, 1] = min(x) - newcons[1, 2] = max(x) - - # Setup positive FWHM constraints - if self.config['PositiveFwhmFlag']: - newcons[2, 0] = CPOSITIVE - newcons[2, 1] = 0 - newcons[2, 2] = 0 - - return largest, newcons - - def estimate_periodic_gauss(self, x, y): - """Estimation of parameters for periodic gaussian curves: - *number of peaks, distance between peaks, height, position of the - first peak, fwhm* - - The functions detects all peaks, then computes the parameters the - following way: - - - *distance*: average of distances between detected peaks - - *height*: average height of detected peaks - - *fwhm*: fwhm of the highest peak (in number of samples) if - field ``'AutoFwhm'`` in :attr:`config` is ``True``, else take - the default value (field ``'FwhmPoints'`` in :attr:`config`) - - :param x: Array of abscissa values - :param y: Array of ordinate values (``y = f(x)``) - :return: Tuple of estimated fit parameters and fit constraints. - """ - yscaling = self.config.get('Yscaling', 1.0) - if yscaling == 0: - yscaling = 1.0 - - bg = self.strip_bg(y) - - if self.config['AutoFwhm']: - search_fwhm = guess_fwhm(y) - else: - search_fwhm = int(float(self.config['FwhmPoints'])) - search_sens = float(self.config['Sensitivity']) - - if search_fwhm < 3: - search_fwhm = 3 - - if search_sens < 1: - search_sens = 1 - - if len(y) > 1.5 * search_fwhm: - peaks = peak_search(yscaling * y, fwhm=search_fwhm, - sensitivity=search_sens) - else: - peaks = [] - npeaks = len(peaks) - if not npeaks: - fittedpar = [] - cons = numpy.zeros((len(fittedpar), 3), numpy.float64) - return fittedpar, cons - - fittedpar = [0.0, 0.0, 0.0, 0.0, 0.0] - - # The number of peaks - fittedpar[0] = npeaks - - # The separation between peaks in x units - delta = 0.0 - height = 0.0 - for i in range(npeaks): - height += y[int(peaks[i])] - bg[int(peaks[i])] - if i != npeaks - 1: - delta += (x[int(peaks[i + 1])] - x[int(peaks[i])]) - - # delta between peaks - if npeaks > 1: - fittedpar[1] = delta / (npeaks - 1) - - # starting height - fittedpar[2] = height / npeaks - - # position of the first peak - fittedpar[3] = x[int(peaks[0])] - - # Estimate the fwhm - fittedpar[4] = search_fwhm - - # setup constraints - cons = numpy.zeros((5, 3), numpy.float64) - cons[0, 0] = CFIXED # the number of gaussians - if npeaks == 1: - cons[1, 0] = CFIXED # the delta between peaks - else: - cons[1, 0] = CFREE - j = 2 - # Setup height area constrains - if not self.config['NoConstraintsFlag']: - if self.config['PositiveHeightAreaFlag']: - # POSITIVE = 1 - cons[j, 0] = CPOSITIVE - cons[j, 1] = 0 - cons[j, 2] = 0 - j += 1 - - # Setup position constrains - if not self.config['NoConstraintsFlag']: - if self.config['QuotedPositionFlag']: - # QUOTED = 2 - cons[j, 0] = CQUOTED - cons[j, 1] = min(x) - cons[j, 2] = max(x) - j += 1 - - # Setup positive FWHM constrains - if not self.config['NoConstraintsFlag']: - if self.config['PositiveFwhmFlag']: - # POSITIVE=1 - cons[j, 0] = CPOSITIVE - cons[j, 1] = 0 - cons[j, 2] = 0 - j += 1 - return fittedpar, cons - - def configure(self, **kw): - """Add new / unknown keyword arguments to :attr:`config`, - update entries in :attr:`config` if the parameter name is a existing - key. - - :param kw: Dictionary of keyword arguments. - :return: Configuration dictionary :attr:`config` - """ - if not kw.keys(): - return self.config - for key in kw.keys(): - notdone = 1 - # take care of lower / upper case problems ... - for config_key in self.config.keys(): - if config_key.lower() == key.lower(): - self.config[config_key] = kw[key] - notdone = 0 - if notdone: - self.config[key] = kw[key] - return self.config - -fitfuns = FitTheories() - -THEORY = OrderedDict(( - ('Gaussians', - FitTheory(description='Gaussian functions', - function=functions.sum_gauss, - parameters=('Height', 'Position', 'FWHM'), - estimate=fitfuns.estimate_height_position_fwhm, - configure=fitfuns.configure)), - ('Lorentz', - FitTheory(description='Lorentzian functions', - function=functions.sum_lorentz, - parameters=('Height', 'Position', 'FWHM'), - estimate=fitfuns.estimate_height_position_fwhm, - configure=fitfuns.configure)), - ('Area Gaussians', - FitTheory(description='Gaussian functions (area)', - function=functions.sum_agauss, - parameters=('Area', 'Position', 'FWHM'), - estimate=fitfuns.estimate_agauss, - configure=fitfuns.configure)), - ('Area Lorentz', - FitTheory(description='Lorentzian functions (area)', - function=functions.sum_alorentz, - parameters=('Area', 'Position', 'FWHM'), - estimate=fitfuns.estimate_alorentz, - configure=fitfuns.configure)), - ('Pseudo-Voigt Line', - FitTheory(description='Pseudo-Voigt functions', - function=functions.sum_pvoigt, - parameters=('Height', 'Position', 'FWHM', 'Eta'), - estimate=fitfuns.estimate_pvoigt, - configure=fitfuns.configure)), - ('Area Pseudo-Voigt', - FitTheory(description='Pseudo-Voigt functions (area)', - function=functions.sum_apvoigt, - parameters=('Area', 'Position', 'FWHM', 'Eta'), - estimate=fitfuns.estimate_apvoigt, - configure=fitfuns.configure)), - ('Split Gaussian', - FitTheory(description='Asymmetric gaussian functions', - function=functions.sum_splitgauss, - parameters=('Height', 'Position', 'LowFWHM', - 'HighFWHM'), - estimate=fitfuns.estimate_splitgauss, - configure=fitfuns.configure)), - ('Split Lorentz', - FitTheory(description='Asymmetric lorentzian functions', - function=functions.sum_splitlorentz, - parameters=('Height', 'Position', 'LowFWHM', 'HighFWHM'), - estimate=fitfuns.estimate_splitgauss, - configure=fitfuns.configure)), - ('Split Pseudo-Voigt', - FitTheory(description='Asymmetric pseudo-Voigt functions', - function=functions.sum_splitpvoigt, - parameters=('Height', 'Position', 'LowFWHM', - 'HighFWHM', 'Eta'), - estimate=fitfuns.estimate_splitpvoigt, - configure=fitfuns.configure)), - ('Step Down', - FitTheory(description='Step down function', - function=functions.sum_stepdown, - parameters=('Height', 'Position', 'FWHM'), - estimate=fitfuns.estimate_stepdown, - configure=fitfuns.configure)), - ('Step Up', - FitTheory(description='Step up function', - function=functions.sum_stepup, - parameters=('Height', 'Position', 'FWHM'), - estimate=fitfuns.estimate_stepup, - configure=fitfuns.configure)), - ('Slit', - FitTheory(description='Slit function', - function=functions.sum_slit, - parameters=('Height', 'Position', 'FWHM', 'BeamFWHM'), - estimate=fitfuns.estimate_slit, - configure=fitfuns.configure)), - ('Atan', - FitTheory(description='Arctan step up function', - function=functions.atan_stepup, - parameters=('Height', 'Position', 'Width'), - estimate=fitfuns.estimate_stepup, - configure=fitfuns.configure)), - ('Hypermet', - FitTheory(description='Hypermet functions', - function=fitfuns.ahypermet, # customized version of functions.sum_ahypermet - parameters=('G_Area', 'Position', 'FWHM', 'ST_Area', - 'ST_Slope', 'LT_Area', 'LT_Slope', 'Step_H'), - estimate=fitfuns.estimate_ahypermet, - configure=fitfuns.configure)), - # ('Periodic Gaussians', - # FitTheory(description='Periodic gaussian functions', - # function=functions.periodic_gauss, - # parameters=('N', 'Delta', 'Height', 'Position', 'FWHM'), - # estimate=fitfuns.estimate_periodic_gauss, - # configure=fitfuns.configure)) - ('Degree 2 Polynomial', - FitTheory(description='Degree 2 polynomial' - '\ny = a*x^2 + b*x +c', - function=fitfuns.poly, - parameters=['a', 'b', 'c'], - estimate=fitfuns.estimate_quadratic)), - ('Degree 3 Polynomial', - FitTheory(description='Degree 3 polynomial' - '\ny = a*x^3 + b*x^2 + c*x + d', - function=fitfuns.poly, - parameters=['a', 'b', 'c', 'd'], - estimate=fitfuns.estimate_cubic)), - ('Degree 4 Polynomial', - FitTheory(description='Degree 4 polynomial' - '\ny = a*x^4 + b*x^3 + c*x^2 + d*x + e', - function=fitfuns.poly, - parameters=['a', 'b', 'c', 'd', 'e'], - estimate=fitfuns.estimate_quartic)), - ('Degree 5 Polynomial', - FitTheory(description='Degree 5 polynomial' - '\ny = a*x^5 + b*x^4 + c*x^3 + d*x^2 + e*x + f', - function=fitfuns.poly, - parameters=['a', 'b', 'c', 'd', 'e', 'f'], - estimate=fitfuns.estimate_quintic)), -)) -"""Dictionary of fit theories: fit functions and their associated estimation -function, parameters list, configuration function and description. -""" - - -def test(a): - from silx.math.fit import fitmanager - x = numpy.arange(1000).astype(numpy.float64) - p = [1500, 100., 50.0, - 1500, 700., 50.0] - y_synthetic = functions.sum_gauss(x, *p) + 1 - - fit = fitmanager.FitManager(x, y_synthetic) - fit.addtheory('Gaussians', functions.sum_gauss, ['Height', 'Position', 'FWHM'], - a.estimate_height_position_fwhm) - fit.settheory('Gaussians') - fit.setbackground('Linear') - - fit.estimate() - fit.runfit() - - y_fit = fit.gendata() - - print("Fit parameter names: %s" % str(fit.get_names())) - print("Theoretical parameters: %s" % str(numpy.append([1, 0], p))) - print("Fitted parameters: %s" % str(fit.get_fitted_parameters())) - - try: - from silx.gui import qt - from silx.gui.plot import plot1D - app = qt.QApplication([]) - - # Offset of 1 to see the difference in log scale - plot1D(x, (y_synthetic + 1, y_fit), "Input data + 1, Fit") - - app.exec_() - except ImportError: - _logger.warning("Unable to load qt binding, can't plot results.") - - -if __name__ == "__main__": - test(fitfuns) diff --git a/silx/math/fit/fittheory.py b/silx/math/fit/fittheory.py deleted file mode 100644 index fa42e6b..0000000 --- a/silx/math/fit/fittheory.py +++ /dev/null @@ -1,161 +0,0 @@ -# coding: utf-8 -#/*########################################################################## -# -# Copyright (c) 2004-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 defines the :class:`FitTheory` object that is used by -:class:`silx.math.fit.FitManager` to define fit functions and background -models. -""" - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "09/08/2016" - - -class FitTheory(object): - """This class defines a fit theory, which consists of: - - - a model function, the actual function to be fitted - - parameters names - - an estimation function, that return the estimated initial parameters - that serve as input for :func:`silx.math.fit.leastsq` - - an optional configuration function, that can be used to modify - configuration parameters to alter the behavior of the fit function - and the estimation function - - an optional derivative function, that replaces the default model - derivative used in :func:`silx.math.fit.leastsq` - """ - def __init__(self, function, parameters, - estimate=None, configure=None, derivative=None, - description=None, pymca_legacy=False, is_background=False): - """ - :param function function: Actual function. See documentation for - :attr:`function`. - :param list[str] parameters: List of parameter names for the function. - See documentation for :attr:`parameters`. - :param function estimate: Optional estimation function. - See documentation for :attr:`estimate` - :param function configure: Optional configuration function. - See documentation for :attr:`configure` - :param function derivative: Optional custom derivative function. - See documentation for :attr:`derivative` - :param str description: Optional description string. - See documentation for :attr:`description` - :param bool pymca_legacy: Flag to indicate that the theory is a PyMca - legacy theory. See documentation for :attr:`pymca_legacy` - :param bool is_background: Flag to indicate that the theory is a - background theory. This has implications regarding the function's - signature, as explained in the documentation for :attr:`function`. - """ - self.function = function - """Regular fit functions must have the signature ``f(x, *params) -> y``, - where *x* is a 1D array of values for the independent variable, - *params* are the parameters to be fitted and *y* is the output array - that we want to have the best fit to a series of data points. - - Background functions used by :class:`FitManager` must have a slightly - different signature: ``f(x, y0, *params) -> bg``, where *y0* is the - array of original data points and *bg* is the background signal that - we want to subtract from the data array prior to fitting the regular - fit function. - - The number of parameters must be the same as in :attr:`parameters`, or - a multiple of this number if the function is defined as a sum of a - variable number of base functions and if :attr:`estimate` is designed - to be able to estimate the number of needed base functions. - """ - - self.parameters = parameters - """List of parameters names. - - This list can contain the minimum number of parameters, if the - function takes a variable number of parameters, - and if the estimation function is responsible for finding the number - of required parameters """ - - self.estimate = estimate - """The estimation function should have the following signature:: - - f(x, y) -> (estimated_param, constraints) - - Parameters: - - - ``x`` is a sequence of values for the independent variable - - ``y`` is a sequence of the same length as ``x`` containing the - data to be fitted - - Return values: - - - ``estimated_param`` is a sequence of estimated fit parameters to - be used as initial values for an iterative fit. - - ``constraints`` is a sequence of shape *(n, 3)*, where *n* is the - number of estimated parameters, containing the constraints for each - parameter to be fitted. See :func:`silx.math.fit.leastsq` for more - explanations about constraints.""" - if estimate is None: - self.estimate = self.default_estimate - - self.configure = configure - """The optional configuration function must conform to the signature - ``f(**kw) -> dict`` (i.e it must accept any named argument and - return a dictionary). - It can be used to modify configuration parameters to alter the - behavior of the fit function and the estimation function.""" - - self.derivative = derivative - """The optional derivative function must conform to the signature - ``model_deriv(xdata, parameters, index)``, where parameters is a - sequence with the current values of the fitting parameters, index is - the fitting parameter index for which the the derivative has to be - provided in the supplied array of xdata points.""" - - self.description = description - """Optional description string for this particular fit theory.""" - - self.pymca_legacy = pymca_legacy - """This attribute can be set to *True* to indicate that the theory - is a PyMca legacy theory. - - This tells :mod:`silx.math.fit.fitmanager` that the signature of - the estimate function is:: - - f(x, y, bg, xscaling, yscaling) -> (estimated_param, constraints) - """ - - self.is_background = is_background - """Flag to indicate that the theory is background theory. - - A background function is an secondary function that needs to be added - to the main fit function to better fit the original data. - If this flag is set to *True*, modules using this theory are informed - that :attr:`function` has the signature ``f(x, y0, *params) -> bg``, - instead of the usual fit function signature.""" - - def default_estimate(self, x=None, y=None, bg=None): - """Default estimate function. Return an array of *ones* as the - initial estimated parameters, and set all constraints to zero - (FREE)""" - estimated_parameters = [1. for _ in self.parameters] - estimated_constraints = [[0, 0, 0] for _ in self.parameters] - return estimated_parameters, estimated_constraints diff --git a/silx/math/fit/functions.pyx b/silx/math/fit/functions.pyx deleted file mode 100644 index 1f78563..0000000 --- a/silx/math/fit/functions.pyx +++ /dev/null @@ -1,985 +0,0 @@ -# coding: utf-8 -#/*########################################################################## -# Copyright (C) 2016-2020 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 fit functions. - -List of fit functions: ------------------------ - - - :func:`sum_gauss` - - :func:`sum_agauss` - - :func:`sum_splitgauss` - - :func:`sum_fastagauss` - - - :func:`sum_apvoigt` - - :func:`sum_pvoigt` - - :func:`sum_splitpvoigt` - - - :func:`sum_lorentz` - - :func:`sum_alorentz` - - :func:`sum_splitlorentz` - - - :func:`sum_stepdown` - - :func:`sum_stepup` - - :func:`sum_slit` - - - :func:`sum_ahypermet` - - :func:`sum_fastahypermet` - -Full documentation: -------------------- - -""" - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "16/08/2017" - -import logging -import numpy - -_logger = logging.getLogger(__name__) - -cimport cython -cimport silx.math.fit.functions_wrapper as functions_wrapper - - -def erf(x): - """Return the gaussian error function - - :param x: Independent variable where the gaussian error function is - calculated - :type x: numpy.ndarray or scalar - :return: Gaussian error function ``y=erf(x)`` - :raise: IndexError if ``x`` is an empty array - """ - cdef: - double[::1] x_c - double[::1] y_c - - - # force list into numpy array - if not hasattr(x, "shape"): - x = numpy.asarray(x) - - for len_dim in x.shape: - if len_dim == 0: - raise IndexError("Cannot compute erf for an empty array") - - x_c = numpy.array(x, copy=False, dtype=numpy.float64, order='C').reshape(-1) - y_c = numpy.empty(shape=(x_c.size,), dtype=numpy.float64) - - status = functions_wrapper.erf_array(&x_c[0], x_c.size, &y_c[0]) - - return numpy.asarray(y_c).reshape(x.shape) - - -def erfc(x): - """Return the gaussian complementary error function - - :param x: Independent variable where the gaussian complementary error - function is calculated - :type x: numpy.ndarray or scalar - :return: Gaussian complementary error function ``y=erfc(x)`` - :type rtype: numpy.ndarray - :raise: IndexError if ``x`` is an empty array - """ - cdef: - double[::1] x_c - double[::1] y_c - - # force list into numpy array - if not hasattr(x, "shape"): - x = numpy.asarray(x) - - for len_dim in x.shape: - if len_dim == 0: - raise IndexError("Cannot compute erfc for an empty array") - - x_c = numpy.array(x, copy=False, dtype=numpy.float64, order='C').reshape(-1) - y_c = numpy.empty(shape=(x_c.size,), dtype=numpy.float64) - - status = functions_wrapper.erfc_array(&x_c[0], x_c.size, &y_c[0]) - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_gauss(x, *params): - """Return a sum of gaussian functions defined by *(height, centroid, fwhm)*, - where: - - - *height* is the peak amplitude - - *centroid* is the peak x-coordinate - - *fwhm* is the full-width at half maximum - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of gaussian parameters (length must be a multiple - of 3): - *(height1, centroid1, fwhm1, height2, centroid2, fwhm2,...)* - :return: Array of sum of gaussian functions at each ``x`` coordinate. - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No gaussian parameters specified. " + - "At least 3 parameters are required.") - - # ensure float64 (double) type and 1D contiguous data layout in memory - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_gauss( - &x_c[0], x.size, - ¶ms_c[0], params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - # reshape y_c to match original, possibly unusual, data shape - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_agauss(x, *params): - """Return a sum of gaussian functions defined by *(area, centroid, fwhm)*, - where: - - - *area* is the area underneath the peak - - *centroid* is the peak x-coordinate - - *fwhm* is the full-width at half maximum - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of gaussian parameters (length must be a multiple - of 3): - *(area1, centroid1, fwhm1, area2, centroid2, fwhm2,...)* - :return: Array of sum of gaussian functions at each ``x`` coordinate. - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No gaussian parameters specified. " + - "At least 3 parameters are required.") - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_agauss( - &x_c[0], x.size, - ¶ms_c[0], params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_fastagauss(x, *params): - """Return a sum of gaussian functions defined by *(area, centroid, fwhm)*, - where: - - - *area* is the area underneath the peak - - *centroid* is the peak x-coordinate - - *fwhm* is the full-width at half maximum - - This implementation differs from :func:`sum_agauss` by the usage of a - lookup table with precalculated exponential values. This might speed up - the computation for large numbers of individual gaussian functions. - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of gaussian parameters (length must be a multiple - of 3): - *(area1, centroid1, fwhm1, area2, centroid2, fwhm2,...)* - :return: Array of sum of gaussian functions at each ``x`` coordinate. - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No gaussian parameters specified. " + - "At least 3 parameters are required.") - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_fastagauss( - &x_c[0], x.size, - ¶ms_c[0], params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_splitgauss(x, *params): - """Return a sum of gaussian functions defined by *(area, centroid, fwhm1, fwhm2)*, - where: - - - *height* is the peak amplitude - - *centroid* is the peak x-coordinate - - *fwhm1* is the full-width at half maximum for the distribution - when ``x < centroid`` - - *fwhm2* is the full-width at half maximum for the distribution - when ``x > centroid`` - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of gaussian parameters (length must be a multiple - of 4): - *(height1, centroid1, fwhm11, fwhm21, height2, centroid2, fwhm12, fwhm22,...)* - :return: Array of sum of split gaussian functions at each ``x`` coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No gaussian parameters specified. " + - "At least 4 parameters are required.") - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_splitgauss( - &x_c[0], x.size, - ¶ms_c[0], params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_apvoigt(x, *params): - """Return a sum of pseudo-Voigt functions, defined by *(area, centroid, fwhm, - eta)*. - - The pseudo-Voigt profile ``PV(x)`` is an approximation of the Voigt - profile using a linear combination of a Gaussian curve ``G(x)`` and a - Lorentzian curve ``L(x)`` instead of their convolution. - - - *area* is the area underneath both G(x) and L(x) - - *centroid* is the peak x-coordinate for both functions - - *fwhm* is the full-width at half maximum of both functions - - *eta* is the Lorentz factor: PV(x) = eta * L(x) + (1 - eta) * G(x) - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of pseudo-Voigt parameters (length must be a multiple - of 4): - *(area1, centroid1, fwhm1, eta1, area2, centroid2, fwhm2, eta2,...)* - :return: Array of sum of pseudo-Voigt functions at each ``x`` coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 4 parameters are required.") - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_apvoigt( - &x_c[0], x.size, - ¶ms_c[0], params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_pvoigt(x, *params): - """Return a sum of pseudo-Voigt functions, defined by *(height, centroid, - fwhm, eta)*. - - The pseudo-Voigt profile ``PV(x)`` is an approximation of the Voigt - profile using a linear combination of a Gaussian curve ``G(x)`` and a - Lorentzian curve ``L(x)`` instead of their convolution. - - - *height* is the peak amplitude of G(x) and L(x) - - *centroid* is the peak x-coordinate for both functions - - *fwhm* is the full-width at half maximum of both functions - - *eta* is the Lorentz factor: PV(x) = eta * L(x) + (1 - eta) * G(x) - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of pseudo-Voigt parameters (length must be a multiple - of 4): - *(height1, centroid1, fwhm1, eta1, height2, centroid2, fwhm2, eta2,...)* - :return: Array of sum of pseudo-Voigt functions at each ``x`` coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 4 parameters are required.") - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_pvoigt( - &x_c[0], x.size, - ¶ms_c[0], params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_splitpvoigt(x, *params): - """Return a sum of split pseudo-Voigt functions, defined by *(height, - centroid, fwhm1, fwhm2, eta)*. - - The pseudo-Voigt profile ``PV(x)`` is an approximation of the Voigt - profile using a linear combination of a Gaussian curve ``G(x)`` and a - Lorentzian curve ``L(x)`` instead of their convolution. - - - *height* is the peak amplitudefor G(x) and L(x) - - *centroid* is the peak x-coordinate for both functions - - *fwhm1* is the full-width at half maximum of both functions - when ``x < centroid`` - - *fwhm2* is the full-width at half maximum of both functions - when ``x > centroid`` - - *eta* is the Lorentz factor: PV(x) = eta * L(x) + (1 - eta) * G(x) - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of pseudo-Voigt parameters (length must be a multiple - of 5): - *(height1, centroid1, fwhm11, fwhm21, eta1,...)* - :return: Array of sum of split pseudo-Voigt functions at each ``x`` - coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 5 parameters are required.") - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_splitpvoigt( - &x_c[0], x.size, - ¶ms_c[0], params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_lorentz(x, *params): - """Return a sum of Lorentz distributions, also known as Cauchy distribution, - defined by *(height, centroid, fwhm)*. - - - *height* is the peak amplitude - - *centroid* is the peak x-coordinate - - *fwhm* is the full-width at half maximum - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of Lorentz parameters (length must be a multiple - of 3): - *(height1, centroid1, fwhm1,...)* - :return: Array of sum Lorentz functions at each ``x`` - coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 3 parameters are required.") - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_lorentz( - &x_c[0], x.size, - ¶ms_c[0], params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_alorentz(x, *params): - """Return a sum of Lorentz distributions, also known as Cauchy distribution, - defined by *(area, centroid, fwhm)*. - - - *area* is the area underneath the peak - - *centroid* is the peak x-coordinate for both functions - - *fwhm* is the full-width at half maximum - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of Lorentz parameters (length must be a multiple - of 3): - *(area1, centroid1, fwhm1,...)* - :return: Array of sum of Lorentz functions at each ``x`` - coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 3 parameters are required.") - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_alorentz( - &x_c[0], x.size, - ¶ms_c[0], params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_splitlorentz(x, *params): - """Return a sum of split Lorentz distributions, - defined by *(height, centroid, fwhm1, fwhm2)*. - - - *height* is the peak amplitude - - *centroid* is the peak x-coordinate for both functions - - *fwhm1* is the full-width at half maximum for ``x < centroid`` - - *fwhm2* is the full-width at half maximum for ``x > centroid`` - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of Lorentz parameters (length must be a multiple - of 4): - *(height1, centroid1, fwhm11, fwhm21...)* - :return: Array of sum of Lorentz functions at each ``x`` - coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 4 parameters are required.") - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_splitlorentz( - &x_c[0], x.size, - ¶ms_c[0], params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_stepdown(x, *params): - """Return a sum of stepdown functions. - defined by *(height, centroid, fwhm)*. - - - *height* is the step's amplitude - - *centroid* is the step's x-coordinate - - *fwhm* is the full-width at half maximum for the derivative, - which is a measure of the *sharpness* of the step-down's edge - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of stepdown parameters (length must be a multiple - of 3): - *(height1, centroid1, fwhm1,...)* - :return: Array of sum of stepdown functions at each ``x`` - coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 3 parameters are required.") - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_stepdown(&x_c[0], - x.size, - ¶ms_c[0], - params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_stepup(x, *params): - """Return a sum of stepup functions. - defined by *(height, centroid, fwhm)*. - - - *height* is the step's amplitude - - *centroid* is the step's x-coordinate - - *fwhm* is the full-width at half maximum for the derivative, - which is a measure of the *sharpness* of the step-up's edge - - :param x: Independent variable where the gaussians are calculated - :type x: numpy.ndarray - :param params: Array of stepup parameters (length must be a multiple - of 3): - *(height1, centroid1, fwhm1,...)* - :return: Array of sum of stepup functions at each ``x`` - coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 3 parameters are required.") - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_stepup(&x_c[0], - x.size, - ¶ms_c[0], - params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_slit(x, *params): - """Return a sum of slit functions. - defined by *(height, position, fwhm, beamfwhm)*. - - - *height* is the slit's amplitude - - *position* is the center of the slit's x-coordinate - - *fwhm* is the full-width at half maximum of the slit - - *beamfwhm* is the full-width at half maximum of the - derivative, which is a measure of the *sharpness* - of the edges of the slit - - :param x: Independent variable where the slits are calculated - :type x: numpy.ndarray - :param params: Array of slit parameters (length must be a multiple - of 4): - *(height1, centroid1, fwhm1, beamfwhm1,...)* - :return: Array of sum of slit functions at each ``x`` - coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 4 parameters are required.") - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_slit(&x_c[0], - x.size, - ¶ms_c[0], - params_c.size, - &y_c[0]) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_ahypermet(x, *params, - gaussian_term=True, st_term=True, lt_term=True, step_term=True): - """Return a sum of ahypermet functions. - defined by *(area, position, fwhm, st_area_r, st_slope_r, lt_area_r, - lt_slope_r, step_height_r)*. - - - *area* is the area underneath the gaussian peak - - *position* is the center of the various peaks and the position of - the step down - - *fwhm* is the full-width at half maximum of the terms - - *st_area_r* is factor between the gaussian area and the area of the - short tail term - - *st_slope_r* is a ratio related to the slope of the short tail - in the low ``x`` values (the lower, the steeper) - - *lt_area_r* is ratio between the gaussian area and the area of the - long tail term - - *lt_slope_r* is a ratio related to the slope of the long tail - in the low ``x`` values (the lower, the steeper) - - *step_height_r* is the ratio between the height of the step down - and the gaussian height - - A hypermet function is a sum of four functions (terms): - - - a gaussian term - - a long tail term - - a short tail term - - a step down term - - :param x: Independent variable where the hypermets are calculated - :type x: numpy.ndarray - :param params: Array of hypermet parameters (length must be a multiple - of 8): - *(area1, position1, fwhm1, st_area_r1, st_slope_r1, lt_area_r1, - lt_slope_r1, step_height_r1...)* - :param gaussian_term: If ``True``, enable gaussian term. Default ``True`` - :param st_term: If ``True``, enable gaussian term. Default ``True`` - :param lt_term: If ``True``, enable gaussian term. Default ``True`` - :param step_term: If ``True``, enable gaussian term. Default ``True`` - :return: Array of sum of hypermet functions at each ``x`` coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 8 parameters are required.") - - # Sum binary flags to activate various terms of the equation - tail_flags = 1 if gaussian_term else 0 - if st_term: - tail_flags += 2 - if lt_term: - tail_flags += 4 - if step_term: - tail_flags += 8 - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_ahypermet(&x_c[0], - x.size, - ¶ms_c[0], - params_c.size, - &y_c[0], - tail_flags) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def sum_fastahypermet(x, *params, - gaussian_term=True, st_term=True, - lt_term=True, step_term=True): - """Return a sum of hypermet functions defined by *(area, position, fwhm, - st_area_r, st_slope_r, lt_area_r, lt_slope_r, step_height_r)*. - - - *area* is the area underneath the gaussian peak - - *position* is the center of the various peaks and the position of - the step down - - *fwhm* is the full-width at half maximum of the terms - - *st_area_r* is factor between the gaussian area and the area of the - short tail term - - *st_slope_r* is a parameter related to the slope of the short tail - in the low ``x`` values (the lower, the steeper) - - *lt_area_r* is factor between the gaussian area and the area of the - long tail term - - *lt_slope_r* is a parameter related to the slope of the long tail - in the low ``x`` values (the lower, the steeper) - - *step_height_r* is the factor between the height of the step down - and the gaussian height - - A hypermet function is a sum of four functions (terms): - - - a gaussian term - - a long tail term - - a short tail term - - a step down term - - This function differs from :func:`sum_ahypermet` by the use of a lookup - table for calculating exponentials. This offers better performance when - calculating many functions for large ``x`` arrays. - - :param x: Independent variable where the hypermets are calculated - :type x: numpy.ndarray - :param params: Array of hypermet parameters (length must be a multiple - of 8): - *(area1, position1, fwhm1, st_area_r1, st_slope_r1, lt_area_r1, - lt_slope_r1, step_height_r1...)* - :param gaussian_term: If ``True``, enable gaussian term. Default ``True`` - :param st_term: If ``True``, enable gaussian term. Default ``True`` - :param lt_term: If ``True``, enable gaussian term. Default ``True`` - :param step_term: If ``True``, enable gaussian term. Default ``True`` - :return: Array of sum of hypermet functions at each ``x`` coordinate - """ - cdef: - double[::1] x_c - double[::1] params_c - double[::1] y_c - - if not len(params): - raise IndexError("No parameters specified. " + - "At least 8 parameters are required.") - - # Sum binary flags to activate various terms of the equation - tail_flags = 1 if gaussian_term else 0 - if st_term: - tail_flags += 2 - if lt_term: - tail_flags += 4 - if step_term: - tail_flags += 8 - - # TODO (maybe): - # Set flags according to params, to move conditional - # branches out of the C code. - # E.g., set st_term = False if any of the st_slope_r params - # (params[8*i + 4]) is 0, to prevent division by 0. Same thing for - # lt_slope_r (params[8*i + 6]) and lt_term. - - x_c = numpy.array(x, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - params_c = numpy.array(params, - copy=False, - dtype=numpy.float64, - order='C').reshape(-1) - y_c = numpy.empty(shape=(x.size,), - dtype=numpy.float64) - - status = functions_wrapper.sum_fastahypermet(&x_c[0], - x.size, - ¶ms_c[0], - params_c.size, - &y_c[0], - tail_flags) - - if status: - raise IndexError("Wrong number of parameters for function") - - return numpy.asarray(y_c).reshape(x.shape) - - -def atan_stepup(x, a, b, c): - """ - Step up function using an inverse tangent. - - :param x: Independent variable where the function is calculated - :type x: numpy array - :param a: Height of the step up - :param b: Center of the step up - :param c: Parameter related to the slope of the step. A lower ``c`` - value yields a sharper step. - :return: ``a * (0.5 + (arctan((x - b) / c) / pi))`` - :rtype: numpy array - """ - if not hasattr(x, "shape"): - x = numpy.array(x) - return a * (0.5 + (numpy.arctan((1.0 * x - b) / c) / numpy.pi)) - - -def periodic_gauss(x, *pars): - """ - Return a sum of gaussian functions defined by - *(npeaks, delta, height, centroid, fwhm)*, - where: - - - *npeaks* is the number of gaussians peaks - - *delta* is the constant distance between 2 peaks - - *height* is the peak amplitude of all the gaussians - - *centroid* is the peak x-coordinate of the first gaussian - - *fwhm* is the full-width at half maximum for all the gaussians - - :param x: Independent variable where the function is calculated - :param pars: *(npeaks, delta, height, centroid, fwhm)* - :return: Sum of ``npeaks`` gaussians - """ - - if not len(pars): - raise IndexError("No parameters specified. " + - "At least 5 parameters are required.") - - newpars = numpy.zeros((pars[0], 3), numpy.float64) - for i in range(int(pars[0])): - newpars[i, 0] = pars[2] - newpars[i, 1] = pars[3] + i * pars[1] - newpars[:, 2] = pars[4] - return sum_gauss(x, newpars) diff --git a/silx/math/fit/functions/include/functions.h b/silx/math/fit/functions/include/functions.h deleted file mode 100644 index de4209b..0000000 --- a/silx/math/fit/functions/include/functions.h +++ /dev/null @@ -1,68 +0,0 @@ -/*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ - -#ifndef FITFUNCTIONS_H -#define FITFUNCTIONS_H - -/* Helper functions */ -int test_params(int len_params, int len_params_one_function, char* fun_name, char* param_names); -double myerfc(double x); -double myerf(double x); -int erfc_array(double* x, int len_x, double* y); -int erf_array(double* x, int len_x, double* y); - -/* Background functions */ -void snip1d(double *data, int size, int width); -//void snip1d_multiple(double *data, int n_channels, int snip_width, int n_spectra); -void snip2d(double *data, int nrows, int ncolumns, int width); -void snip3d(double *data, int nx, int ny, int nz, int width); - -int strip(double* input, long len_input, double c, long niter, int deltai, - long* anchors, long len_anchors, double* output); - -/* Smoothing functions */ - -int SavitskyGolay(double* input, long len_input, int npoints, double* output); - -/* Fit functions */ -int sum_gauss(double* x, int len_x, double* pgauss, int len_pgauss, double* y); -int sum_agauss(double* x, int len_x, double* pgauss, int len_pgauss, double* y); -int sum_fastagauss(double* x, int len_x, double* pgauss, int len_pgauss, double* y); -int sum_splitgauss(double* x, int len_x, double* pgauss, int len_pgauss, double* y); - -int sum_apvoigt(double* x, int len_x, double* pvoigt, int len_pvoigt, double* y); -int sum_pvoigt(double* x, int len_x, double* pvoigt, int len_pvoigt, double* y); -int sum_splitpvoigt(double* x, int len_x, double* pvoigt, int len_pvoigt, double* y); - -int sum_lorentz(double* x, int len_x, double* plorentz, int len_plorentz, double* y); -int sum_alorentz(double* x, int len_x, double* plorentz, int len_plorentz, double* y); -int sum_splitlorentz(double* x, int len_x, double* plorentz, int len_plorentz, double* y); - -int sum_stepdown(double* x, int len_x, double* pdstep, int len_pdstep, double* y); -int sum_stepup(double* x, int len_x, double* pustep, int len_pustep, double* y); -int sum_slit(double* x, int len_x, double* pslit, int len_pslit, double* y); - -int sum_ahypermet(double* x, int len_x, double* phypermet, int len_phypermet, double* y, int tail_flags); -int sum_fastahypermet(double* x, int len_x, double* phypermet, int len_phypermet, double* y, int tail_flags); - -#endif /* #define FITFUNCTIONS_H */ diff --git a/silx/math/fit/functions/src/funs.c b/silx/math/fit/functions/src/funs.c deleted file mode 100644 index aae173f..0000000 --- a/silx/math/fit/functions/src/funs.c +++ /dev/null @@ -1,1265 +0,0 @@ -#/*########################################################################## -# Copyright (c) 2004-2016 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 file provides fit functions. - - It is adapted from PyMca source file "SpecFitFuns.c". The main difference - with the original code is that this code does not handle the python - wrapping, which is done elsewhere using cython. - - Authors: V.A. Sole, P. Knobel - License: MIT - Last modified: 17/06/2016 -*/ -#include <math.h> -#include <stdlib.h> -#include <stdio.h> -#include "functions.h" - -#ifndef M_PI -#define M_PI 3.1415926535 -#endif - -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -#if defined(_WIN32) -#define erf myerf -#define erfc myerfc -#endif - -#define LOG2 0.69314718055994529 - - -int test_params(int len_params, - int len_params_one_function, - char* fun_name, - char* param_names) -{ - if (len_params % len_params_one_function) { - printf("[%s]Error: Number of parameters must be a multiple of %d.", - fun_name, len_params_one_function); - printf("\nParameters expected for %s: %s\n", - fun_name, param_names); - return(1); - } - if (len_params == 0) { - printf("[%s]Error: No parameters specified.", fun_name); - printf("\nParameters expected for %s: %s\n", - fun_name, param_names); - return(1); - } - return(0); -} - -/* Complementary error function for a single value*/ -double myerfc(double x) -{ - double z; - double t; - double r; - - z=fabs(x); - t=1.0/(1.0+0.5*z); - r=t * exp(-z * z - 1.26551223 + t * (1.00002368 + t * (0.3740916 + - t * (0.09678418 + t * (-0.18628806 + t * (0.27886807 + t * (-1.13520398 + - t * (1.48851587 + t * (-0.82215223+t*0.17087277))))))))); - if (x<0) - r=2.0-r; - return (r); -} - -/* Gauss error function for a single value*/ -double myerf(double x) -{ - return (1.0 - myerfc(x)); -} - -/* Gauss error function for an array - y[i]=erf(x[i]) - returns status code 0 -*/ -int erf_array(double* x, int len_x, double* y) -{ - int j; - for (j=0; j<len_x; j++) { - y[j] = erf(x[j]); - } - return(0); -} - -/* Complementary error function for an array - y[i]=erfc(x[i]) - returns status code 0*/ -int erfc_array(double* x, int len_x, double* y) -{ - int j; - for (j=0; j<len_x; j++) { - y[j] = erfc(x[j]); - } - return(0); -} - -/* Use lookup table for fast exp computation */ -double fastexp(double x) -{ - int expindex; - static double EXP[5000] = {0.0}; - int i; - -/*initialize */ - if (EXP[0] < 1){ - for (i=0;i<5000;i++){ - EXP[i] = exp(-0.01 * i); - } - } -/*calculate*/ - if (x < 0){ - x = -x; - if (x < 50){ - expindex = (int) (x * 100); - return EXP[expindex]*(1.0 - (x - 0.01 * expindex)) ; - }else if (x < 100) { - expindex = (int) (x * 10); - return pow(EXP[expindex]*(1.0 - (x - 0.1 * expindex)),10) ; - }else if (x < 1000){ - expindex = (int) x; - return pow(EXP[expindex]*(1.0 - (x - expindex)),20) ; - }else if (x < 10000){ - expindex = (int) (x * 0.1); - return pow(EXP[expindex]*(1.0 - (x - 10.0 * expindex)),30) ; - }else{ - return 0; - } - }else{ - if (x < 50){ - expindex = (int) (x * 100); - return 1.0/EXP[expindex]*(1.0 - (x - 0.01 * expindex)) ; - }else if (x < 100) { - expindex = (int) (x * 10); - return pow(EXP[expindex]*(1.0 - (x - 0.1 * expindex)),-10) ; - }else{ - return exp(x); - } - } -} - - -/* sum_gauss - Sum of gaussian functions, defined by (height, centroid, fwhm) - - *height* is the peak amplitude - *centroid* is the peak x-coordinate - *fwhm* is the full-width at half maximum - - Parameters: - ----------- - - - x: Independant variable where the gaussians are calculated. - - len_x: Number of elements in the x array. - - pvoigt: Array of gaussian parameters: - (height1, centroid1, fwhm1, height2, centroid2, fwhm2,...) - - len_pgauss: Number of elements in the pgauss array. Must be - a multiple of 3. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_gauss(double* x, int len_x, double* pgauss, int len_pgauss, double* y) -{ - int i, j; - double dhelp, inv_two_sqrt_two_log2, sigma; - double fwhm, centroid, height; - - if (test_params(len_pgauss, 3, "sum_gauss", "height, centroid, fwhm")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - inv_two_sqrt_two_log2 = 1.0 / (2.0 * sqrt(2.0 * LOG2)); - - for (i=0; i<len_pgauss/3; i++) { - height = pgauss[3*i]; - centroid = pgauss[3*i+1]; - fwhm = pgauss[3*i+2]; - - sigma = fwhm * inv_two_sqrt_two_log2; - - for (j=0; j<len_x; j++) { - dhelp = (x[j] - centroid) / sigma; - if (dhelp <= 20) { - y[j] += height * exp (-0.5 * dhelp * dhelp); - } - } - } - return(0); -} - -/* sum_agauss - Sum of gaussian functions defined by (area, centroid, fwhm) - - *area* is the area underneath the peak - *centroid* is the peak x-coordinate - *fwhm* is the full-width at half maximum - - Parameters: - ----------- - - - x: Independant variable where the gaussians are calculated. - - len_x: Number of elements in the x array. - - pgauss: Array of gaussian parameters: - (area1, centroid1, fwhm1, area2, centroid2, fwhm2,...) - - len_pgauss: Number of elements in the pgauss array. Must be - a multiple of 3. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_agauss(double* x, int len_x, double* pgauss, int len_pgauss, double* y) -{ - int i, j; - double dhelp, height, sqrt2PI, sigma, inv_two_sqrt_two_log2; - double fwhm, centroid, area; - - if (test_params(len_pgauss, 3, "sum_agauss", "area, centroid, fwhm")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - inv_two_sqrt_two_log2 = 1.0 / (2.0 * sqrt(2.0 * LOG2)); - sqrt2PI = sqrt(2.0*M_PI); - - for (i=0; i<len_pgauss/3; i++) { - area = pgauss[3*i]; - centroid = pgauss[3*i+1]; - fwhm = pgauss[3*i+2]; - - sigma = fwhm * inv_two_sqrt_two_log2; - height = area / (sigma * sqrt2PI); - - for (j=0; j<len_x; j++) { - dhelp = (x[j] - centroid)/sigma; - if (dhelp <= 35) { - y[j] += height * exp (-0.5 * dhelp * dhelp); - } - } - } - return(0); -} - - -/* sum_fastagauss - Sum of gaussian functions defined by (area, centroid, fwhm). - This implementation uses a lookup table of precalculated exp values - and a limited development (exp(-x) = 1 - x for small values of x) - - *area* is the area underneath the peak - *centroid* is the peak x-coordinate - *fwhm* is the full-width at half maximum - - Parameters: - ----------- - - - x: Independant variable where the gaussians are calculated. - - len_x: Number of elements in the x array. - - pgauss: Array of gaussian parameters: - (area1, centroid1, fwhm1, area2, centroid2, fwhm2,...) - - len_pgauss: Number of elements in the pgauss array. Must be - a multiple of 3. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ - -int sum_fastagauss(double* x, int len_x, double* pgauss, int len_pgauss, double* y) -{ - int i, j, expindex; - double dhelp, height, sqrt2PI, sigma, inv_two_sqrt_two_log2; - double fwhm, centroid, area; - static double EXP[5000]; - - if (test_params(len_pgauss, 3, "sum_fastagauss", "area, centroid, fwhm")) { - return(1); - } - - if (EXP[0] < 1){ - for (i=0; i<5000; i++){ - EXP[i] = exp(-0.01 * i); - } - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - inv_two_sqrt_two_log2 = 1.0 / (2.0 * sqrt(2.0 * LOG2)); - sqrt2PI = sqrt(2.0*M_PI); - - for (i=0; i<len_pgauss/3; i++) { - area = pgauss[3*i]; - centroid = pgauss[3*i+1]; - fwhm = pgauss[3*i+2]; - - sigma = fwhm * inv_two_sqrt_two_log2; - height = area / (sigma * sqrt2PI); - - for (j=0; j<len_x; j++) { - dhelp = (x[j] - centroid)/sigma; - if (dhelp <= 15){ - dhelp = 0.5 * dhelp * dhelp; - if (dhelp < 50){ - expindex = (int) (dhelp * 100); - y[j] += height * EXP[expindex] * (1.0 - (dhelp - 0.01 * expindex)); - } - else if (dhelp < 100) { - expindex = (int) (dhelp * 10); - y[j] += height * pow(EXP[expindex] * (1.0 - (dhelp - 0.1 * expindex)), 10); - } - else if (dhelp < 1000){ - expindex = (int) (dhelp); - y[j] += height * pow(EXP[expindex] * (1.0 - (dhelp - expindex)), 20); - } - } - } - } - return(0); -} - -/* sum_splitgauss - Sum of split gaussian functions, defined by (height, centroid, fwhm1, fwhm2) - - *height* is the peak amplitude - *centroid* is the peak x-coordinate - *fwhm1* is the full-width at half maximum of the left half of the curve (x < centroid) - *fwhm1* is the full-width at half maximum of the right half of the curve (x > centroid) - - Parameters: - ----------- - - - x: Independant variable where the gaussians are calculated. - - len_x: Number of elements in the x array. - - pgauss: Array of gaussian parameters: - (height1, centroid1, fwhm11, fwhm21, height2, centroid2, fwhm12, fwhm22,...) - - len_pgauss: Number of elements in the pgauss array. Must be - a multiple of 4. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_splitgauss(double* x, int len_x, double* pgauss, int len_pgauss, double* y) -{ - int i, j; - double dhelp, inv_two_sqrt_two_log2, sigma1, sigma2; - double fwhm1, fwhm2, centroid, height; - - if (test_params(len_pgauss, 4, "sum_splitgauss", "height, centroid, fwhm1, fwhm2")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - inv_two_sqrt_two_log2 = 1.0 / (2.0 * sqrt(2.0 * LOG2)); - - for (i=0; i<len_pgauss/4; i++) { - height = pgauss[4*i]; - centroid = pgauss[4*i+1]; - fwhm1 = pgauss[4*i+2]; - fwhm2 = pgauss[4*i+3]; - - sigma1 = fwhm1 * inv_two_sqrt_two_log2; - sigma2 = fwhm2 * inv_two_sqrt_two_log2; - - for (j=0; j<len_x; j++) { - dhelp = (x[j] - centroid); - if (dhelp > 0) { - /* Use fwhm2 when x > centroid */ - dhelp = dhelp / sigma2; - } - else { - /* Use fwhm1 when x < centroid */ - dhelp = dhelp / sigma1; - } - - if (dhelp <= 20) { - y[j] += height * exp (-0.5 * dhelp * dhelp); - } - } - } - return(0); -} - -/* sum_apvoigt - Sum of pseudo-Voigt functions, defined by (area, centroid, fwhm, eta). - - The pseudo-Voigt profile PV(x) is an approximation of the Voigt profile - using a linear combination of a Gaussian curve G(x) and a Lorentzian curve - L(x) instead of their convolution. - - *area* is the area underneath both G(x) and L(x) - *centroid* is the peak x-coordinate for both functions - *fwhm* is the full-width at half maximum of both functions - *eta* is the Lorentz factor: PV(x) = eta * L(x) + (1 - eta) * G(x) - - Parameters: - ----------- - - - x: Independant variable where the gaussians are calculated. - - len_x: Number of elements in the x array. - - pvoigt: Array of Voigt function parameters: - (area1, centroid1, fwhm1, eta1, area2, centroid2, fwhm2, eta2,...) - - len_voigt: Number of elements in the pvoigt array. Must be - a multiple of 4. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_apvoigt(double* x, int len_x, double* pvoigt, int len_pvoigt, double* y) -{ - int i, j; - double dhelp, inv_two_sqrt_two_log2, sqrt2PI, sigma, height; - double area, centroid, fwhm, eta; - - if (test_params(len_pvoigt, 4, "sum_apvoigt", "area, centroid, fwhm, eta")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - inv_two_sqrt_two_log2 = 1.0 / (2.0 * sqrt(2.0 * LOG2)); - sqrt2PI = sqrt(2.0*M_PI); - - - for (i=0; i<len_pvoigt/4; i++) { - area = pvoigt[4*i]; - centroid = pvoigt[4*i+1]; - fwhm = pvoigt[4*i+2]; - eta = pvoigt[4*i+3]; - - sigma = fwhm * inv_two_sqrt_two_log2; - height = area / (sigma * sqrt2PI); - - for (j=0; j<len_x; j++) { - /* Lorentzian term */ - dhelp = (x[j] - centroid) / (0.5 * fwhm); - dhelp = 1.0 + (dhelp * dhelp); - y[j] += eta * (area / (0.5 * M_PI * fwhm * dhelp)); - - /* Gaussian term */ - dhelp = (x[j] - centroid) / sigma; - if (dhelp <= 35) { - y[j] += (1.0 - eta) * height * exp (-0.5 * dhelp * dhelp); - } - } - } - return(0); -} - -/* sum_pvoigt - Sum of pseudo-Voigt functions, defined by (height, centroid, fwhm, eta). - - The pseudo-Voigt profile PV(x) is an approximation of the Voigt profile - using a linear combination of a Gaussian curve G(x) and a Lorentzian curve - L(x) instead of their convolution. - - *height* is the peak amplitude of G(x) and L(x) - *centroid* is the peak x-coordinate for both functions - *fwhm* is the full-width at half maximum of both functions - *eta* is the Lorentz factor: PV(x) = eta * L(x) + (1 - eta) * G(x) - - Parameters: - ----------- - - - x: Independant variable where the gaussians are calculated. - - len_x: Number of elements in the x array. - - pvoigt: Array of Voigt function parameters: - (height1, centroid1, fwhm1, eta1, height2, centroid2, fwhm2, eta2,...) - - len_voigt: Number of elements in the pvoigt array. Must be - a multiple of 4. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_pvoigt(double* x, int len_x, double* pvoigt, int len_pvoigt, double* y) -{ - int i, j; - double dhelp, inv_two_sqrt_two_log2, sigma; - double height, centroid, fwhm, eta; - - if (test_params(len_pvoigt, 4, "sum_pvoigt", "height, centroid, fwhm, eta")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - inv_two_sqrt_two_log2 = 1.0 / (2.0 * sqrt(2.0 * LOG2)); - - for (i=0; i<len_pvoigt/4; i++) { - height = pvoigt[4*i]; - centroid = pvoigt[4*i+1]; - fwhm = pvoigt[4*i+2]; - eta = pvoigt[4*i+3]; - - sigma = fwhm * inv_two_sqrt_two_log2; - - for (j=0; j<len_x; j++) { - /* Lorentzian term */ - dhelp = (x[j] - centroid) / (0.5 * fwhm); - dhelp = 1.0 + (dhelp * dhelp); - y[j] += eta * height / dhelp; - - /* Gaussian term */ - dhelp = (x[j] - centroid) / sigma; - if (dhelp <= 35) { - y[j] += (1.0 - eta) * height * exp (-0.5 * dhelp * dhelp); - } - } - } - return(0); -} - -/* sum_splitpvoigt - Sum of split pseudo-Voigt functions, defined by - (height, centroid, fwhm1, fwhm2, eta). - - The pseudo-Voigt profile PV(x) is an approximation of the Voigt profile - using a linear combination of a Gaussian curve G(x) and a Lorentzian curve - L(x) instead of their convolution. - - *height* is the peak amplitude of G(x) and L(x) - *centroid* is the peak x-coordinate for both functions - *fwhm1* is the full-width at half maximum of both functions for x < centroid - *fwhm2* is the full-width at half maximum of both functions for x > centroid - *eta* is the Lorentz factor: PV(x) = eta * L(x) + (1 - eta) * G(x) - - Parameters: - ----------- - - - x: Independant variable where the gaussians are calculated. - - len_x: Number of elements in the x array. - - pvoigt: Array of Voigt function parameters: - (height1, centroid1, fwhm11, fwhm21, eta1, ...) - - len_voigt: Number of elements in the pvoigt array. Must be - a multiple of 5. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_splitpvoigt(double* x, int len_x, double* pvoigt, int len_pvoigt, double* y) -{ - int i, j; - double dhelp, inv_two_sqrt_two_log2, x_minus_centroid, sigma1, sigma2; - double height, centroid, fwhm1, fwhm2, eta; - - if (test_params(len_pvoigt, 5, "sum_splitpvoigt", "height, centroid, fwhm1, fwhm2, eta")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - inv_two_sqrt_two_log2 = 1.0 / (2.0 * sqrt(2.0 * LOG2)); - - for (i=0; i<len_pvoigt/5; i++) { - height = pvoigt[5*i]; - centroid = pvoigt[5*i+1]; - fwhm1 = pvoigt[5*i+2]; - fwhm2 = pvoigt[5*i+3]; - eta = pvoigt[5*i+4]; - - sigma1 = fwhm1 * inv_two_sqrt_two_log2; - sigma2 = fwhm2 * inv_two_sqrt_two_log2; - - for (j=0; j<len_x; j++) { - x_minus_centroid = (x[j] - centroid); - - /* Use fwhm2 when x > centroid */ - if (x_minus_centroid > 0) { - /* Lorentzian term */ - dhelp = x_minus_centroid / (0.5 * fwhm2); - dhelp = 1.0 + (dhelp * dhelp); - y[j] += eta * height / dhelp; - - /* Gaussian term */ - dhelp = x_minus_centroid / sigma2; - if (dhelp <= 35) { - y[j] += (1.0 - eta) * height * exp (-0.5 * dhelp * dhelp); - } - } - /* Use fwhm1 when x < centroid */ - else { - /* Lorentzian term */ - dhelp = x_minus_centroid / (0.5 * fwhm1); - dhelp = 1.0 + (dhelp * dhelp); - y[j] += eta * height / dhelp; - - /* Gaussian term */ - dhelp = x_minus_centroid / sigma1; - if (dhelp <= 35) { - y[j] += (1.0 - eta) * height * exp (-0.5 * dhelp * dhelp); - } - } - } - } - return(0); -} - -/* sum_lorentz - Sum of Lorentz functions, defined by (height, centroid, fwhm). - - *height* is the peak amplitude - *centroid* is the peak's x-coordinate - *fwhm* is the full-width at half maximum - - Parameters: - ----------- - - - x: Independant variable where the Lorentzians are calculated. - - len_x: Number of elements in the x array. - - plorentz: Array of lorentz function parameters: - (height1, centroid1, fwhm1, ...) - - len_lorentz: Number of elements in the plorentz array. Must be - a multiple of 3. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_lorentz(double* x, int len_x, double* plorentz, int len_plorentz, double* y) -{ - int i, j; - double dhelp; - double height, centroid, fwhm; - - if (test_params(len_plorentz, 3, "sum_lorentz", "height, centroid, fwhm")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - for (i=0; i<len_plorentz/3; i++) { - height = plorentz[3*i]; - centroid = plorentz[3*i+1]; - fwhm = plorentz[3*i+2]; - - for (j=0; j<len_x; j++) { - dhelp = (x[j] - centroid) / (0.5 * fwhm); - dhelp = 1.0 + (dhelp * dhelp); - y[j] += height / dhelp; - } - } - return(0); -} - - -/* sum_alorentz - Sum of Lorentz functions, defined by (area, centroid, fwhm). - - *area* is the area underneath the peak - *centroid* is the peak's x-coordinate - *fwhm* is the full-width at half maximum - - Parameters: - ----------- - - - x: Independant variable where the Lorentzians are calculated. - - len_x: Number of elements in the x array. - - plorentz: Array of lorentz function parameters: - (area1, centroid1, fwhm1, ...) - - len_lorentz: Number of elements in the plorentz array. Must be - a multiple of 3. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_alorentz(double* x, int len_x, double* plorentz, int len_plorentz, double* y) -{ - int i, j; - double dhelp; - double area, centroid, fwhm; - - if (test_params(len_plorentz, 3, "sum_alorentz", "area, centroid, fwhm")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - for (i=0; i<len_plorentz/3; i++) { - area = plorentz[3*i]; - centroid = plorentz[3*i+1]; - fwhm = plorentz[3*i+2]; - - for (j=0; j<len_x; j++) { - dhelp = (x[j] - centroid) / (0.5 * fwhm); - dhelp = 1.0 + (dhelp * dhelp); - y[j] += area / (0.5 * M_PI * fwhm * dhelp); - } - } - return(0); -} - - -/* sum_splitlorentz - Sum of Lorentz functions, defined by (height, centroid, fwhm1, fwhm2). - - *height* is the peak amplitude - *centroid* is the peak's x-coordinate - *fwhm1* is the full-width at half maximum for x < centroid - *fwhm2* is the full-width at half maximum for x > centroid - - Parameters: - ----------- - - - x: Independant variable where the Lorentzians are calculated. - - len_x: Number of elements in the x array. - - plorentz: Array of lorentz function parameters: - (height1, centroid1, fwhm11, fwhm21 ...) - - len_lorentz: Number of elements in the plorentz array. Must be - a multiple of 4. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_splitlorentz(double* x, int len_x, double* plorentz, int len_plorentz, double* y) -{ - int i, j; - double dhelp; - double height, centroid, fwhm1, fwhm2; - - if (test_params(len_plorentz, 4, "sum_splitlorentz", "height, centroid, fwhm1, fwhm2")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - for (i=0; i<len_plorentz/4; i++) { - height = plorentz[4*i]; - centroid = plorentz[4*i+1]; - fwhm1 = plorentz[4*i+2]; - fwhm2 = plorentz[4*i+3]; - - for (j=0; j<len_x; j++) { - dhelp = (x[j] - centroid); - if (dhelp>0) { - dhelp = dhelp / (0.5 * fwhm2); - } - else { - dhelp = dhelp / (0.5 * fwhm1); - } - dhelp = 1.0 + (dhelp * dhelp); - y[j] += height / dhelp; - } - } - return(0); -} - -/* sum_stepdown - Sum of stepdown functions, defined by (height, centroid, fwhm). - - *height* is the step amplitude - *centroid* is the step's x-coordinate - *fwhm* is the full-width at half maximum of the derivative - - Parameters: - ----------- - - - x: Independant variable where the stepdown functions are calculated. - - len_x: Number of elements in the x array. - - pdstep: Array of downstpe function parameters: - (height1, centroid1, fwhm1, ...) - - len_pdstep: Number of elements in the pdstep array. Must be - a multiple of 3. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_stepdown(double* x, int len_x, double* pdstep, int len_pdstep, double* y) -{ - int i, j; - double dhelp, sqrt2_inv_2_sqrt_two_log2 ; - double height, centroid, fwhm; - - if (test_params(len_pdstep, 3, "sum_stepdown", "height, centroid, fwhm")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - sqrt2_inv_2_sqrt_two_log2 = sqrt(2.0) / (2.0 * sqrt(2.0 * LOG2)); - - for (i=0; i<len_pdstep/3; i++) { - height = pdstep[3*i]; - centroid = pdstep[3*i+1]; - fwhm = pdstep[3*i+2]; - - for (j=0; j<len_x; j++) { - dhelp = fwhm * sqrt2_inv_2_sqrt_two_log2; - dhelp = (x[j] - centroid) / dhelp; - y[j] += height * 0.5 * erfc(dhelp); - } - } - return(0); -} - -/* sum_stepup - Sum of stepup functions, defined by (height, centroid, fwhm). - - *height* is the step amplitude - *centroid* is the step's x-coordinate - *fwhm* is the full-width at half maximum of the derivative - - Parameters: - ----------- - - - x: Independant variable where the stepup functions are calculated. - - len_x: Number of elements in the x array. - - pustep: Array of stepdown function parameters: - (height1, centroid1, fwhm1, ...) - - len_pustep: Number of elements in the pustep array. Must be - a multiple of 3. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_stepup(double* x, int len_x, double* pustep, int len_pustep, double* y) -{ - int i, j; - double dhelp, sqrt2_inv_2_sqrt_two_log2 ; - double height, centroid, fwhm; - - if (test_params(len_pustep, 3, "sum_stepup", "height, centroid, fwhm")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - sqrt2_inv_2_sqrt_two_log2 = sqrt(2.0) / (2.0 * sqrt(2.0 * LOG2)); - - for (i=0; i<len_pustep/3; i++) { - height = pustep[3*i]; - centroid = pustep[3*i+1]; - fwhm = pustep[3*i+2]; - - for (j=0; j<len_x; j++) { - dhelp = fwhm * sqrt2_inv_2_sqrt_two_log2; - dhelp = (x[j] - centroid) / dhelp; - y[j] += height * 0.5 * (1.0 + erf(dhelp)); - } - } - return(0); -} - - -/* sum_slit - Sum of slit functions, defined by (height, position, fwhm, beamfwhm). - - *height* is the slit height - *position* is the slit's center x-coordinate - *fwhm* is the full-width at half maximum of the slit - *beamfwhm* is the full-width at half maximum of derivative's peaks - - Parameters: - ----------- - - - x: Independant variable where the slit functions are calculated. - - len_x: Number of elements in the x array. - - pslit: Array of slit function parameters: - (height1, centroid1, fwhm1, beamfwhm1 ...) - - len_pslit: Number of elements in the pslit array. Must be - a multiple of 3. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - Adapted from PyMca module SpecFitFuns -*/ -int sum_slit(double* x, int len_x, double* pslit, int len_pslit, double* y) -{ - int i, j; - double dhelp, dhelp1, dhelp2, sqrt2_inv_2_sqrt_two_log2, centroid1, centroid2; - double height, position, fwhm, beamfwhm; - - if (test_params(len_pslit, 4, "sum_slit", "height, centroid, fwhm, beamfwhm")) { - return(1); - } - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - sqrt2_inv_2_sqrt_two_log2 = sqrt(2.0) / (2.0 * sqrt(2.0 * LOG2)); - - for (i=0; i<len_pslit/4; i++) { - height = pslit[4*i]; - position = pslit[4*i+1]; - fwhm = pslit[4*i+2]; - beamfwhm = pslit[4*i+3]; - - centroid1 = position - 0.5 * fwhm; - centroid2 = position + 0.5 * fwhm; - - for (j=0; j<len_x; j++) { - dhelp = beamfwhm * sqrt2_inv_2_sqrt_two_log2; - dhelp1 = (x[j] - centroid1) / dhelp; - dhelp2 = (x[j] - centroid2) / dhelp; - y[j] += height * 0.25 * (1.0 + erf(dhelp1)) * erfc(dhelp2); - } - } - return(0); -} - - -/* sum_ahypermet - Sum of hypermet functions, defined by - (area, position, fwhm, st_area_r, st_slope_r, lt_area_r, lt_slope_r, step_height_r). - - - *area* is the area underneath the gaussian peak - - *position* is the center of the various peaks and the position of - the step down - - *fwhm* is the full-width at half maximum of the terms - - *st_area_r* is factor between the gaussian area and the area of the - short tail term - - *st_slope_r* is a parameter related to the slope of the short tail - in the low ``x`` values (the lower, the steeper) - - *lt_area_r* is factor between the gaussian area and the area of the - long tail term - - *lt_slope_r* is a parameter related to the slope of the long tail - in the low ``x`` values (the lower, the steeper) - - *step_height_r* is the factor between the height of the step down - and the gaussian height - - Parameters: - ----------- - - - x: Independant variable where the functions are calculated. - - len_x: Number of elements in the x array. - - phypermet: Array of hypermet function parameters: - *(area1, position1, fwhm1, st_area_r1, st_slope_r1, lt_area_r1, - lt_slope_r1, step_height_r1, ...)* - - len_phypermet: Number of elements in the phypermet array. Must be - a multiple of 8. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - tail_flags: sum of binary flags to activate the various terms of the - function: - - - 1 (b0001): Gaussian term - - 2 (b0010): st term - - 4 (b0100): lt term - - 8 (b1000): step term - - E.g., to activate all termsof the hypermet, use ``tail_flags = 1 + 2 + 4 + 8 = 15`` - - Adapted from PyMca module SpecFitFuns -*/ -int sum_ahypermet(double* x, int len_x, double* phypermet, int len_phypermet, double* y, int tail_flags) -{ - int i, j; - int g_term_flag, st_term_flag, lt_term_flag, step_term_flag; - double c1, c2, sigma, height, sigma_sqrt2, sqrt2PI, inv_2_sqrt_2_log2, x_minus_position, epsilon; - double area, position, fwhm, st_area_r, st_slope_r, lt_area_r, lt_slope_r, step_height_r; - - if (test_params(len_phypermet, 8, "sum_hypermet", - "height, centroid, fwhm, st_area_r, st_slope_r, lt_area_r, lt_slope_r, step_height_r")) { - return(1); - } - - g_term_flag = tail_flags & 1; - st_term_flag = (tail_flags>>1) & 1; - lt_term_flag = (tail_flags>>2) & 1; - step_term_flag = (tail_flags>>3) & 1; - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - /* define epsilon to compare floating point values with 0. */ - epsilon = 0.00000000001; - - sqrt2PI= sqrt(2.0 * M_PI); - inv_2_sqrt_2_log2 = 1.0 / (2.0 * sqrt(2.0 * LOG2)); - - for (i=0; i<len_phypermet/8; i++) { - area = phypermet[8*i]; - position = phypermet[8*i+1]; - fwhm = phypermet[8*i+2]; - st_area_r = phypermet[8*i+3]; - st_slope_r = phypermet[8*i+4]; - lt_area_r = phypermet[8*i+5]; - lt_slope_r = phypermet[8*i+6]; - step_height_r = phypermet[8*i+7]; - - sigma = fwhm * inv_2_sqrt_2_log2; - height = area / (sigma * sqrt2PI); - - /* Prevent division by 0 */ - if (sigma == 0) { - printf("fwhm must not be equal to 0"); - return(1); - } - sigma_sqrt2 = sigma * 1.4142135623730950488; - - for (j=0; j<len_x; j++) { - x_minus_position = x[j] - position; - c2 = (0.5 * x_minus_position * x_minus_position) / (sigma * sigma); - /* gaussian term */ - if (g_term_flag) { - y[j] += exp(-c2) * height; - } - - /* st term */ - if (st_term_flag) { - if (fabs(st_slope_r) > epsilon) { - c1 = st_area_r * 0.5 * \ - erfc((x_minus_position/sigma_sqrt2) + 0.5 * sigma_sqrt2 / st_slope_r); - y[j] += ((area * c1) / st_slope_r) * \ - exp(0.5 * (sigma / st_slope_r) * (sigma / st_slope_r) + \ - (x_minus_position / st_slope_r)); - } - } - - /* lt term */ - if (lt_term_flag) { - if (fabs(lt_slope_r) > epsilon) { - c1 = lt_area_r * \ - 0.5 * erfc((x_minus_position/sigma_sqrt2) + 0.5 * sigma_sqrt2 / lt_slope_r); - y[j] += ((area * c1) / lt_slope_r) * \ - exp(0.5 * (sigma / lt_slope_r) * (sigma / lt_slope_r) + \ - (x_minus_position / lt_slope_r)); - } - } - - /* step term flag */ - if (step_term_flag) { - y[j] += step_height_r * (area / (sigma * sqrt2PI)) * \ - 0.5 * erfc(x_minus_position / sigma_sqrt2); - } - } - } - return(0); -} - -/* sum_fastahypermet - - Sum of hypermet functions, defined by - (area, position, fwhm, st_area_r, st_slope_r, lt_area_r, lt_slope_r, step_height_r). - - - *area* is the area underneath the gaussian peak - - *position* is the center of the various peaks and the position of - the step down - - *fwhm* is the full-width at half maximum of the terms - - *st_area_r* is factor between the gaussian area and the area of the - short tail term - - *st_slope_r* is a parameter related to the slope of the short tail - in the low ``x`` values (the lower, the steeper) - - *lt_area_r* is factor between the gaussian area and the area of the - long tail term - - *lt_slope_r* is a parameter related to the slope of the long tail - in the low ``x`` values (the lower, the steeper) - - *step_height_r* is the factor between the height of the step down - and the gaussian height - - Parameters: - ----------- - - - x: Independant variable where the functions are calculated. - - len_x: Number of elements in the x array. - - phypermet: Array of hypermet function parameters: - *(area1, position1, fwhm1, st_area_r1, st_slope_r1, lt_area_r1, - lt_slope_r1, step_height_r1, ...)* - - len_phypermet: Number of elements in the phypermet array. Must be - a multiple of 8. - - y: Output array. Must have memory allocated for the same number - of elements as x (len_x). - - tail_flags: sum of binary flags to activate the various terms of the - function: - - - 1 (b0001): Gaussian term - - 2 (b0010): st term - - 4 (b0100): lt term - - 8 (b1000): step term - - E.g., to activate all termsof the hypermet, use ``tail_flags = 1 + 2 + 4 + 8 = 15`` - - Adapted from PyMca module SpecFitFuns -*/ -int sum_fastahypermet(double* x, int len_x, double* phypermet, int len_phypermet, double* y, int tail_flags) -{ - int i, j; - int g_term_flag, st_term_flag, lt_term_flag, step_term_flag; - double c1, c2, sigma, height, sigma_sqrt2, sqrt2PI, inv_2_sqrt_2_log2, x_minus_position, epsilon; - double area, position, fwhm, st_area_r, st_slope_r, lt_area_r, lt_slope_r, step_height_r; - - if (test_params(len_phypermet, 8, "sum_hypermet", - "height, centroid, fwhm, st_area_r, st_slope_r, lt_area_r, lt_slope_r, step_height_r")) { - return(1); - } - - g_term_flag = tail_flags & 1; - st_term_flag = (tail_flags>>1) & 1; - lt_term_flag = (tail_flags>>2) & 1; - step_term_flag = (tail_flags>>3) & 1; - - /* Initialize output array */ - for (j=0; j<len_x; j++) { - y[j] = 0.; - } - - /* define epsilon to compare floating point values with 0. */ - epsilon = 0.00000000001; - - sqrt2PI= sqrt(2.0 * M_PI); - inv_2_sqrt_2_log2 = 1.0 / (2.0 * sqrt(2.0 * LOG2)); - - for (i=0; i<len_phypermet/8; i++) { - area = phypermet[8*i]; - position = phypermet[8*i+1]; - fwhm = phypermet[8*i+2]; - st_area_r = phypermet[8*i+3]; - st_slope_r = phypermet[8*i+4]; - lt_area_r = phypermet[8*i+5]; - lt_slope_r = phypermet[8*i+6]; - step_height_r = phypermet[8*i+7]; - - sigma = fwhm * inv_2_sqrt_2_log2; - height = area / (sigma * sqrt2PI); - - /* Prevent division by 0 */ - if (sigma == 0) { - printf("fwhm must not be equal to 0"); - return(1); - } - sigma_sqrt2 = sigma * 1.4142135623730950488; - - for (j=0; j<len_x; j++) { - x_minus_position = x[j] - position; - c2 = (0.5 * x_minus_position * x_minus_position) / (sigma * sigma); - /* gaussian term */ - if (g_term_flag && c2 < 100) { - y[j] += fastexp(-c2) * height; - } - - /* st term */ - if (st_term_flag && (fabs(st_slope_r) > epsilon) && (x_minus_position / st_slope_r) <= 612) { - c1 = st_area_r * 0.5 * \ - erfc((x_minus_position/sigma_sqrt2) + 0.5 * sigma_sqrt2 / st_slope_r); - y[j] += ((area * c1) / st_slope_r) * \ - fastexp(0.5 * (sigma / st_slope_r) * (sigma / st_slope_r) +\ - (x_minus_position / st_slope_r)); - } - - /* lt term */ - if (lt_term_flag && (fabs(lt_slope_r) > epsilon) && (x_minus_position / lt_slope_r) <= 612) { - c1 = lt_area_r * \ - 0.5 * erfc((x_minus_position/sigma_sqrt2) + 0.5 * sigma_sqrt2 / lt_slope_r); - y[j] += ((area * c1) / lt_slope_r) * \ - fastexp(0.5 * (sigma / lt_slope_r) * (sigma / lt_slope_r) +\ - (x_minus_position / lt_slope_r)); - - } - - /* step term flag */ - if (step_term_flag) { - y[j] += step_height_r * (area / (sigma * sqrt2PI)) *\ - 0.5 * erfc(x_minus_position / sigma_sqrt2); - } - } - } - return(0); -} - -void pileup(double* x, long len_x, double* ret, int input2, double zero, double gain) -{ - //int input2=0; - //double zero=0.0; - //double gain=1.0; - - int i, j, k; - double *px, *pret, *pall; - - /* the pointer to the starting position of par data */ - px = x; - pret = ret; - - *pret = 0; - k = (int )(zero/gain); - for (i=input2; i<len_x; i++){ - pall = x; - if ((i+k) >= 0) - { - pret = (double *) ret+(i+k); - for (j=0; j<len_x-i-k ;j++){ - *pret += *px * (*pall); - pall++; - pret++; - } - } - px++; - } -} diff --git a/silx/math/fit/functions_wrapper.pxd b/silx/math/fit/functions_wrapper.pxd deleted file mode 100644 index 780116c..0000000 --- a/silx/math/fit/functions_wrapper.pxd +++ /dev/null @@ -1,170 +0,0 @@ -# coding: utf-8 -#/*########################################################################## -# Copyright (C) 2016 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. -# -#############################################################################*/ - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "14/06/2016" - -cimport cython - -cdef extern from "functions.h": - int erfc_array(double* x, - int len_x, - double* y) - - int erf_array(double* x, - int len_x, - double* y); - - void snip1d(double *data, - int size, - int width) - - void snip2d(double *data, - int nrows, - int ncolumns, - int width) - - void snip3d(double *data, - int nx, - int ny, - int nz, - int width) - - int strip(double* input, - long len_input, - double c, - long niter, - int deltai, - long* anchors, - long len_anchors, - double* output) - - int sum_gauss(double* x, - int len_x, - double* pgauss, - int len_pgauss, - double* y) - - int sum_agauss(double* x, - int len_x, - double* pgauss, - int len_pgauss, - double* y) - - int sum_fastagauss(double* x, - int len_x, - double* pgauss, - int len_pgauss, - double* y) - - int sum_splitgauss(double* x, - int len_x, - double* pgauss, - int len_pgauss, - double* y) - - int sum_apvoigt(double* x, - int len_x, - double* pvoigt, - int len_pvoigt, - double* y) - - int sum_pvoigt(double* x, - int len_x, - double* pvoigt, - int len_pvoigt, - double* y) - - int sum_splitpvoigt(double* x, - int len_x, - double* pvoigt, - int len_pvoigt, - double* y) - - int sum_lorentz(double* x, - int len_x, - double* plorentz, - int len_plorentz, - double* y) - - int sum_alorentz(double* x, - int len_x, - double* plorentz, - int len_plorentz, - double* y) - - int sum_splitlorentz(double* x, - int len_x, - double* plorentz, - int len_plorentz, - double* y) - - int sum_stepdown(double* x, - int len_x, - double* pdstep, - int len_pdstep, - double* y) - - int sum_stepup(double* x, - int len_x, - double* pustep, - int len_pustep, - double* y) - - int sum_slit(double* x, - int len_x, - double* pslit, - int len_pslit, - double* y) - - int sum_ahypermet(double* x, - int len_x, - double* phypermet, - int len_phypermet, - double* y, - int tail_flags) - - int sum_fastahypermet(double* x, - int len_x, - double* phypermet, - int len_phypermet, - double* y, - int tail_flags) - - long seek(long begin_index, - long end_index, - long nsamples, - double fwhm, - double sensitivity, - double debug_info, - long max_npeaks, - double * data, - double * peaks, - double * relevances) - - int SavitskyGolay(double* input, - long len_input, - int npoints, - double* output) diff --git a/silx/math/fit/leastsq.py b/silx/math/fit/leastsq.py deleted file mode 100644 index 3df1a35..0000000 --- a/silx/math/fit/leastsq.py +++ /dev/null @@ -1,901 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2004-2020 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 implements a Levenberg-Marquardt algorithm with constraints on the -fitted parameters without introducing any other dependendency than numpy. - -If scipy dependency is not an issue, and no constraints are applied to the fitting -parameters, there is no real gain compared to the use of scipy.optimize.curve_fit -other than a more conservative calculation of uncertainties on fitted parameters. - -This module is a refactored version of PyMca Gefit.py module. -""" -__authors__ = ["V.A. Sole"] -__license__ = "MIT" -__date__ = "15/05/2017" -__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" - -import numpy -from numpy.linalg import inv -from numpy.linalg.linalg import LinAlgError -import time -import logging -import copy - -_logger = logging.getLogger(__name__) - -# codes understood by the routine -CFREE = 0 -CPOSITIVE = 1 -CQUOTED = 2 -CFIXED = 3 -CFACTOR = 4 -CDELTA = 5 -CSUM = 6 -CIGNORED = 7 - -def leastsq(model, xdata, ydata, p0, sigma=None, - constraints=None, model_deriv=None, epsfcn=None, - deltachi=None, full_output=None, - check_finite=True, - left_derivative=False, - max_iter=100): - """ - Use non-linear least squares Levenberg-Marquardt algorithm to fit a function, f, to - data with optional constraints on the fitted parameters. - - Assumes ``ydata = f(xdata, *params) + eps`` - - :param model: callable - The model function, f(x, ...). It must take the independent - variable as the first argument and the parameters to fit as - separate remaining arguments. - The returned value is a one dimensional array of floats. - - :param xdata: An M-length sequence. - The independent variable where the data is measured. - - :param ydata: An M-length sequence - The dependent data --- nominally f(xdata, ...) - - :param p0: N-length sequence - Initial guess for the parameters. - - :param sigma: None or M-length sequence, optional - If not None, the uncertainties in the ydata array. These are used as - weights in the least-squares problem - i.e. minimising ``np.sum( ((f(xdata, *popt) - ydata) / sigma)**2 )`` - If None, the uncertainties are assumed to be 1 - - :param constraints: - If provided, it is a 2D sequence of dimension (n_parameters, 3) where, - for each parameter denoted by the index i, the meaning is - - - constraints[i][0] - - - 0 - Free (CFREE) - - 1 - Positive (CPOSITIVE) - - 2 - Quoted (CQUOTED) - - 3 - Fixed (CFIXED) - - 4 - Factor (CFACTOR) - - 5 - Delta (CDELTA) - - 6 - Sum (CSUM) - - - - constraints[i][1] - - - Ignored if constraints[i][0] is 0, 1, 3 - - Min value of the parameter if constraints[i][0] is CQUOTED - - Index of fitted parameter to which it is related - - - constraints[i][2] - - - Ignored if constraints[i][0] is 0, 1, 3 - - Max value of the parameter if constraints[i][0] is CQUOTED - - Factor to apply to related parameter with index constraints[i][1] - - Difference with parameter with index constraints[i][1] - - Sum obtained when adding parameter with index constraints[i][1] - :type constraints: *optional*, None or 2D sequence - - :param model_deriv: - None (default) or function providing the derivatives of the fitting function respect to the fitted parameters. - It will be called as model_deriv(xdata, parameters, index) where parameters is a sequence with the current - values of the fitting parameters, index is the fitting parameter index for which the the derivative has - to be provided in the supplied array of xdata points. - :type model_deriv: *optional*, None or callable - - - :param epsfcn: float - A variable used in determining a suitable parameter variation when - calculating the numerical derivatives (for model_deriv=None). - Normally the actual step length will be sqrt(epsfcn)*x - Original Gefit module was using epsfcn 1.0e-5 while default value - is now numpy.finfo(numpy.float64).eps as in scipy - :type epsfcn: *optional*, float - - :param deltachi: float - A variable used to control the minimum change in chisq to consider the - fitting process not worth to be continued. Default is 0.1 %. - :type deltachi: *optional*, float - - :param full_output: bool, optional - non-zero to return all optional outputs. The default is None what will give a warning in case - of a constrained fit without having set this kweyword. - - :param check_finite: bool, optional - If True, check that the input arrays do not contain nans of infs, - and raise a ValueError if they do. Setting this parameter to - False will ignore input arrays values containing nans. - Default is True. - - :param left_derivative: - This parameter only has an influence if no derivative function - is provided. When True the left and right derivatives of the - model will be calculated for each fitted parameters thus leading to - the double number of function evaluations. Default is False. - Original Gefit module was always using left_derivative as True. - :type left_derivative: *optional*, bool - - :param max_iter: Maximum number of iterations (default is 100) - - :return: Returns a tuple of length 2 (or 3 if full_ouput is True) with the content: - - ``popt``: array - Optimal values for the parameters so that the sum of the squared error - of ``f(xdata, *popt) - ydata`` is minimized - ``pcov``: 2d array - If no constraints are applied, this array contains the estimated covariance - of popt. The diagonal provides the variance of the parameter estimate. - To compute one standard deviation errors use ``perr = np.sqrt(np.diag(pcov))``. - If constraints are applied, this array does not contain the estimated covariance of - the parameters actually used during the fitting process but the uncertainties after - recalculating the covariance if all the parameters were free. - To get the actual uncertainties following error propagation of the actually fitted - parameters one should set full_output to True and access the uncertainties key. - ``infodict``: dict - a dictionary of optional outputs with the keys: - - ``uncertainties`` - The actual uncertainty on the optimized parameters. - ``nfev`` - The number of function calls - ``fvec`` - The function evaluated at the output - ``niter`` - The number of iterations performed - ``chisq`` - The chi square ``np.sum( ((f(xdata, *popt) - ydata) / sigma)**2 )`` - ``reduced_chisq`` - The chi square ``np.sum( ((f(xdata, *popt) - ydata) / sigma)**2 )`` divided - by the number of degrees of freedom ``(M - number_of_free_parameters)`` - """ - function_call_counter = 0 - if numpy.isscalar(p0): - p0 = [p0] - parameters = numpy.array(p0, dtype=numpy.float64, copy=False) - if deltachi is None: - deltachi = 0.001 - - # NaNs can not be handled - if check_finite: - xdata = numpy.asarray_chkfinite(xdata) - ydata = numpy.asarray_chkfinite(ydata) - if sigma is not None: - sigma = numpy.asarray_chkfinite(sigma) - else: - sigma = numpy.ones((ydata.shape), dtype=numpy.float64) - ydata.shape = -1 - sigma.shape = -1 - else: - ydata = numpy.asarray(ydata) - xdata = numpy.asarray(xdata) - ydata.shape = -1 - if sigma is not None: - sigma = numpy.asarray(sigma) - else: - sigma = numpy.ones((ydata.shape), dtype=numpy.float64) - sigma.shape = -1 - # get rid of NaN in input data - idx = numpy.isfinite(ydata) - if False in idx: - # xdata must have a shape able to be understood by the user function - # in principle, one should not need to change it, however, if there are - # points to be excluded, one has to be able to exclude them. - # We can only hope that the sequence is properly arranged - if xdata.size == ydata.size: - if len(xdata.shape) != 1: - msg = "Need to reshape input xdata." - _logger.warning(msg) - xdata.shape = -1 - else: - raise ValueError("Cannot reshape xdata to deal with NaN in ydata") - ydata = ydata[idx] - xdata = xdata[idx] - sigma = sigma[idx] - idx = numpy.isfinite(sigma) - if False in idx: - # xdata must have a shape able to be understood by the user function - # in principle, one should not need to change it, however, if there are - # points to be excluded, one has to be able to exclude them. - # We can only hope that the sequence is properly arranged - ydata = ydata[idx] - xdata = xdata[idx] - sigma = sigma[idx] - idx = numpy.isfinite(xdata) - filter_xdata = False - if False in idx: - # What to do? - try: - # Let's see if the function is able to deal with non-finite data - msg = "Checking if function can deal with non-finite data" - _logger.debug(msg) - evaluation = model(xdata, *parameters) - function_call_counter += 1 - if evaluation.shape != ydata.shape: - if evaluation.size == ydata.size: - msg = "Supplied function does not return a proper array of floats." - msg += "\nFunction should be rewritten to return a 1D array of floats." - msg += "\nTrying to reshape output." - _logger.warning(msg) - evaluation.shape = ydata.shape - if False in numpy.isfinite(evaluation): - msg = "Supplied function unable to handle non-finite x data" - msg += "\nAttempting to filter out those x data values." - _logger.warning(msg) - filter_xdata = True - else: - filter_xdata = False - evaluation = None - except: - # function cannot handle input data - filter_xdata = True - if filter_xdata: - if xdata.size != ydata.size: - raise ValueError("xdata contains non-finite data that cannot be filtered") - else: - # we leave the xdata as they where - old_shape = xdata.shape - xdata.shape = ydata.shape - idx0 = numpy.isfinite(xdata) - xdata.shape = old_shape - ydata = ydata[idx0] - xdata = xdata[idx] - sigma = sigma[idx0] - weight = 1.0 / (sigma + numpy.equal(sigma, 0)) - weight0 = weight * weight - - nparameters = len(parameters) - - if epsfcn is None: - epsfcn = numpy.finfo(numpy.float64).eps - else: - epsfcn = max(epsfcn, numpy.finfo(numpy.float64).eps) - - # check if constraints have been passed as text - constrained_fit = False - if constraints is not None: - # make sure we work with a list of lists - input_constraints = constraints - tmp_constraints = [None] * len(input_constraints) - for i in range(nparameters): - tmp_constraints[i] = list(input_constraints[i]) - constraints = tmp_constraints - for i in range(nparameters): - if hasattr(constraints[i][0], "upper"): - txt = constraints[i][0].upper() - if txt == "FREE": - constraints[i][0] = CFREE - elif txt == "POSITIVE": - constraints[i][0] = CPOSITIVE - elif txt == "QUOTED": - constraints[i][0] = CQUOTED - elif txt == "FIXED": - constraints[i][0] = CFIXED - elif txt == "FACTOR": - constraints[i][0] = CFACTOR - constraints[i][1] = int(constraints[i][1]) - elif txt == "DELTA": - constraints[i][0] = CDELTA - constraints[i][1] = int(constraints[i][1]) - elif txt == "SUM": - constraints[i][0] = CSUM - constraints[i][1] = int(constraints[i][1]) - elif txt in ["IGNORED", "IGNORE"]: - constraints[i][0] = CIGNORED - else: - #I should raise an exception - raise ValueError("Unknown constraint %s" % constraints[i][0]) - if constraints[i][0] > 0: - constrained_fit = True - if constrained_fit: - if full_output is None: - _logger.info("Recommended to set full_output to True when using constraints") - - # Levenberg-Marquardt algorithm - fittedpar = parameters.__copy__() - flambda = 0.001 - iiter = max_iter - #niter = 0 - last_evaluation=None - x = xdata - y = ydata - chisq0 = -1 - iteration_counter = 0 - while (iiter > 0): - weight = weight0 - """ - I cannot evaluate the initial chisq here because I do not know - if some parameters are to be ignored, otherways I could do it as follows: - if last_evaluation is None: - yfit = model(x, *fittedpar) - last_evaluation = yfit - chisq0 = (weight * pow(y-yfit, 2)).sum() - and chisq would not need to be recalculated. - Passing the last_evaluation assumes that there are no parameters being - ignored or not between calls. - """ - iteration_counter += 1 - chisq0, alpha0, beta, internal_output = chisq_alpha_beta( - model, fittedpar, - x, y, weight, constraints=constraints, - model_deriv=model_deriv, - epsfcn=epsfcn, - left_derivative=left_derivative, - last_evaluation=last_evaluation, - full_output=True) - n_free = internal_output["n_free"] - free_index = internal_output["free_index"] - noigno = internal_output["noigno"] - fitparam = internal_output["fitparam"] - function_calls = internal_output["function_calls"] - function_call_counter += function_calls - #print("chisq0 = ", chisq0, n_free, fittedpar) - #raise - nr, nc = alpha0.shape - flag = 0 - #lastdeltachi = chisq0 - while flag == 0: - alpha = alpha0 * (1.0 + flambda * numpy.identity(nr)) - deltapar = numpy.dot(beta, inv(alpha)) - if constraints is None: - newpar = fitparam + deltapar [0] - else: - newpar = parameters.__copy__() - pwork = numpy.zeros(deltapar.shape, numpy.float64) - for i in range(n_free): - if constraints is None: - pwork [0] [i] = fitparam [i] + deltapar [0] [i] - elif constraints [free_index[i]][0] == CFREE: - pwork [0] [i] = fitparam [i] + deltapar [0] [i] - elif constraints [free_index[i]][0] == CPOSITIVE: - #abs method - pwork [0] [i] = fitparam [i] + deltapar [0] [i] - #square method - #pwork [0] [i] = (numpy.sqrt(fitparam [i]) + deltapar [0] [i]) * \ - # (numpy.sqrt(fitparam [i]) + deltapar [0] [i]) - elif constraints[free_index[i]][0] == CQUOTED: - pmax = max(constraints[free_index[i]][1], - constraints[free_index[i]][2]) - pmin = min(constraints[free_index[i]][1], - constraints[free_index[i]][2]) - A = 0.5 * (pmax + pmin) - B = 0.5 * (pmax - pmin) - if B != 0: - pwork [0] [i] = A + \ - B * numpy.sin(numpy.arcsin((fitparam[i] - A)/B)+ \ - deltapar [0] [i]) - else: - txt = "Error processing constrained fit\n" - txt += "Parameter limits are %g and %g\n" % (pmin, pmax) - txt += "A = %g B = %g" % (A, B) - raise ValueError("Invalid parameter limits") - newpar[free_index[i]] = pwork [0] [i] - newpar = numpy.array(_get_parameters(newpar, constraints)) - workpar = numpy.take(newpar, noigno) - yfit = model(x, *workpar) - if last_evaluation is None: - if len(yfit.shape) > 1: - msg = "Supplied function does not return a 1D array of floats." - msg += "\nFunction should be rewritten." - msg += "\nTrying to reshape output." - _logger.warning(msg) - yfit.shape = -1 - function_call_counter += 1 - chisq = (weight * pow(y-yfit, 2)).sum() - absdeltachi = chisq0 - chisq - if absdeltachi < 0: - flambda *= 10.0 - if flambda > 1000: - flag = 1 - iiter = 0 - else: - flag = 1 - fittedpar = newpar.__copy__() - lastdeltachi = 100 * (absdeltachi / (chisq + (chisq == 0))) - if iteration_counter < 2: - # ignore any limit, the fit *has* to be improved - pass - elif (lastdeltachi) < deltachi: - iiter = 0 - elif absdeltachi < numpy.sqrt(epsfcn): - iiter = 0 - _logger.info("Iteration finished due to too small absolute chi decrement") - chisq0 = chisq - flambda = flambda / 10.0 - last_evaluation = yfit - iiter = iiter - 1 - # this is the covariance matrix of the actually fitted parameters - cov0 = inv(alpha0) - if constraints is None: - cov = cov0 - else: - # yet another call needed with all the parameters being free except those - # that are FIXED and that will be assigned a 100 % uncertainty. - new_constraints = copy.deepcopy(constraints) - flag_special = [0] * len(fittedpar) - for idx, constraint in enumerate(constraints): - if constraints[idx][0] in [CFIXED, CIGNORED]: - flag_special[idx] = constraints[idx][0] - else: - new_constraints[idx][0] = CFREE - new_constraints[idx][1] = 0 - new_constraints[idx][2] = 0 - chisq, alpha, beta, internal_output = chisq_alpha_beta( - model, fittedpar, - x, y, weight, constraints=new_constraints, - model_deriv=model_deriv, - epsfcn=epsfcn, - left_derivative=left_derivative, - last_evaluation=last_evaluation, - full_output=True) - # obtained chisq should be identical to chisq0 - try: - cov = inv(alpha) - except LinAlgError: - _logger.critical("Error calculating covariance matrix after successful fit") - cov = None - if cov is not None: - for idx, value in enumerate(flag_special): - if value in [CFIXED, CIGNORED]: - cov = numpy.insert(numpy.insert(cov, idx, 0, axis=1), idx, 0, axis=0) - cov[idx, idx] = fittedpar[idx] * fittedpar[idx] - - if not full_output: - return fittedpar, cov - else: - sigma0 = numpy.sqrt(abs(numpy.diag(cov0))) - sigmapar = _get_sigma_parameters(fittedpar, sigma0, constraints) - ddict = {} - ddict["chisq"] = chisq0 - ddict["reduced_chisq"] = chisq0 / (len(yfit)-n_free) - ddict["covariance"] = cov0 - ddict["uncertainties"] = sigmapar - ddict["fvec"] = last_evaluation - ddict["nfev"] = function_call_counter - ddict["niter"] = iteration_counter - return fittedpar, cov, ddict #, chisq/(len(yfit)-len(sigma0)), sigmapar,niter,lastdeltachi - -def chisq_alpha_beta(model, parameters, x, y, weight, constraints=None, - model_deriv=None, epsfcn=None, left_derivative=False, - last_evaluation=None, full_output=False): - - """ - Get chi square, the curvature matrix alpha and the matrix beta according to the input parameters. - If all the parameters are unconstrained, the covariance matrix is the inverse of the alpha matrix. - - :param model: callable - The model function, f(x, ...). It must take the independent - variable as the first argument and the parameters to fit as - separate remaining arguments. - The returned value is a one dimensional array of floats. - - :param parameters: N-length sequence - Values of parameters at which function and derivatives are to be calculated. - - :param x: An M-length sequence. - The independent variable where the data is measured. - - :param y: An M-length sequence - The dependent data --- nominally f(xdata, ...) - - :param weight: M-length sequence - Weights to be applied in the calculation of chi square - As a reminder ``chisq = np.sum(weigth * (model(x, *parameters) - y)**2)`` - - :param constraints: - If provided, it is a 2D sequence of dimension (n_parameters, 3) where, - for each parameter denoted by the index i, the meaning is - - - constraints[i][0] - - - 0 - Free (CFREE) - - 1 - Positive (CPOSITIVE) - - 2 - Quoted (CQUOTED) - - 3 - Fixed (CFIXED) - - 4 - Factor (CFACTOR) - - 5 - Delta (CDELTA) - - 6 - Sum (CSUM) - - - - constraints[i][1] - - - Ignored if constraints[i][0] is 0, 1, 3 - - Min value of the parameter if constraints[i][0] is CQUOTED - - Index of fitted parameter to which it is related - - - constraints[i][2] - - - Ignored if constraints[i][0] is 0, 1, 3 - - Max value of the parameter if constraints[i][0] is CQUOTED - - Factor to apply to related parameter with index constraints[i][1] - - Difference with parameter with index constraints[i][1] - - Sum obtained when adding parameter with index constraints[i][1] - :type constraints: *optional*, None or 2D sequence - - :param model_deriv: - None (default) or function providing the derivatives of the fitting function respect to the fitted parameters. - It will be called as model_deriv(xdata, parameters, index) where parameters is a sequence with the current - values of the fitting parameters, index is the fitting parameter index for which the the derivative has - to be provided in the supplied array of xdata points. - :type model_deriv: *optional*, None or callable - - - :param epsfcn: float - A variable used in determining a suitable parameter variation when - calculating the numerical derivatives (for model_deriv=None). - Normally the actual step length will be sqrt(epsfcn)*x - Original Gefit module was using epsfcn 1.0e-10 while default value - is now numpy.finfo(numpy.float64).eps as in scipy - :type epsfcn: *optional*, float - - :param left_derivative: - This parameter only has an influence if no derivative function - is provided. When True the left and right derivatives of the - model will be calculated for each fitted parameters thus leading to - the double number of function evaluations. Default is False. - Original Gefit module was always using left_derivative as True. - :type left_derivative: *optional*, bool - - :param last_evaluation: An M-length array - Used for optimization purposes. If supplied, this array will be taken as the result of - evaluating the function, that is as the result of ``model(x, *parameters)`` thus avoiding - the evaluation call. - - :param full_output: bool, optional - Additional output used for internal purposes with the keys: - ``function_calls`` - The number of model function calls performed. - ``fitparam`` - A sequence with the actual free parameters - ``free_index`` - Sequence with the indices of the free parameters in input parameters sequence. - ``noigno`` - Sequence with the indices of the original parameters considered in the calculations. - """ - if epsfcn is None: - epsfcn = numpy.finfo(numpy.float64).eps - else: - epsfcn = max(epsfcn, numpy.finfo(numpy.float64).eps) - #nr0, nc = data.shape - n_param = len(parameters) - if constraints is None: - derivfactor = numpy.ones((n_param, )) - n_free = n_param - noigno = numpy.arange(n_param) - free_index = noigno * 1 - fitparam = parameters * 1 - else: - n_free = 0 - fitparam = [] - free_index = [] - noigno = [] - derivfactor = [] - for i in range(n_param): - if constraints[i][0] != CIGNORED: - noigno.append(i) - if constraints[i][0] == CFREE: - fitparam.append(parameters [i]) - derivfactor.append(1.0) - free_index.append(i) - n_free += 1 - elif constraints[i][0] == CPOSITIVE: - fitparam.append(abs(parameters[i])) - derivfactor.append(1.0) - #fitparam.append(numpy.sqrt(abs(parameters[i]))) - #derivfactor.append(2.0*numpy.sqrt(abs(parameters[i]))) - free_index.append(i) - n_free += 1 - elif constraints[i][0] == CQUOTED: - pmax = max(constraints[i][1], constraints[i][2]) - pmin =min(constraints[i][1], constraints[i][2]) - if ((pmax-pmin) > 0) & \ - (parameters[i] <= pmax) & \ - (parameters[i] >= pmin): - A = 0.5 * (pmax + pmin) - B = 0.5 * (pmax - pmin) - fitparam.append(parameters[i]) - derivfactor.append(B*numpy.cos(numpy.arcsin((parameters[i] - A)/B))) - free_index.append(i) - n_free += 1 - elif (pmax-pmin) > 0: - print("WARNING: Quoted parameter outside boundaries") - print("Initial value = %f" % parameters[i]) - print("Limits are %f and %f" % (pmin, pmax)) - print("Parameter will be kept at its starting value") - fitparam = numpy.array(fitparam, numpy.float64) - alpha = numpy.zeros((n_free, n_free), numpy.float64) - beta = numpy.zeros((1, n_free), numpy.float64) - #delta = (fitparam + numpy.equal(fitparam, 0.0)) * 0.00001 - delta = (fitparam + numpy.equal(fitparam, 0.0)) * numpy.sqrt(epsfcn) - nr = y.size - ############## - # Prior to each call to the function one has to re-calculate the - # parameters - pwork = parameters.__copy__() - for i in range(n_free): - pwork [free_index[i]] = fitparam [i] - if n_free == 0: - raise ValueError("No free parameters to fit") - function_calls = 0 - if not left_derivative: - if last_evaluation is not None: - f2 = last_evaluation - else: - f2 = model(x, *parameters) - f2.shape = -1 - function_calls += 1 - for i in range(n_free): - if model_deriv is None: - #pwork = parameters.__copy__() - pwork[free_index[i]] = fitparam [i] + delta [i] - newpar = _get_parameters(pwork.tolist(), constraints) - newpar = numpy.take(newpar, noigno) - f1 = model(x, *newpar) - f1.shape = -1 - function_calls += 1 - if left_derivative: - pwork[free_index[i]] = fitparam [i] - delta [i] - newpar = _get_parameters(pwork.tolist(), constraints) - newpar=numpy.take(newpar, noigno) - f2 = model(x, *newpar) - function_calls += 1 - help0 = (f1 - f2) / (2.0 * delta[i]) - else: - help0 = (f1 - f2) / (delta[i]) - help0 = help0 * derivfactor[i] - pwork[free_index[i]] = fitparam [i] - #removed I resize outside the loop: - #help0 = numpy.resize(help0, (1, nr)) - else: - help0 = model_deriv(x, pwork, free_index[i]) - help0 = help0 * derivfactor[i] - - if i == 0: - deriv = help0 - else: - deriv = numpy.concatenate((deriv, help0), 0) - - #line added to resize outside the loop - deriv = numpy.resize(deriv, (n_free, nr)) - if last_evaluation is None: - if constraints is None: - yfit = model(x, *fitparam) - yfit.shape = -1 - else: - newpar = _get_parameters(pwork.tolist(), constraints) - newpar = numpy.take(newpar, noigno) - yfit = model(x, *newpar) - yfit.shape = -1 - function_calls += 1 - else: - yfit = last_evaluation - deltay = y - yfit - help0 = weight * deltay - for i in range(n_free): - derivi = numpy.resize(deriv[i, :], (1, nr)) - help1 = numpy.resize(numpy.sum((help0 * derivi), 1), (1, 1)) - if i == 0: - beta = help1 - else: - beta = numpy.concatenate((beta, help1), 1) - help1 = numpy.inner(deriv, weight*derivi) - if i == 0: - alpha = help1 - else: - alpha = numpy.concatenate((alpha, help1), 1) - chisq = (help0 * deltay).sum() - if full_output: - ddict = {} - ddict["n_free"] = n_free - ddict["free_index"] = free_index - ddict["noigno"] = noigno - ddict["fitparam"] = fitparam - ddict["derivfactor"] = derivfactor - ddict["function_calls"] = function_calls - return chisq, alpha, beta, ddict - else: - return chisq, alpha, beta - - -def _get_parameters(parameters, constraints): - """ - Apply constraints to input parameters. - - Parameters not depending on other parameters, they are returned as the input. - - Parameters depending on other parameters, return the value after applying the - relation to the parameter wo which they are related. - """ - # 0 = Free 1 = Positive 2 = Quoted - # 3 = Fixed 4 = Factor 5 = Delta - if constraints is None: - return parameters * 1 - newparam = [] - #first I make the free parameters - #because the quoted ones put troubles - for i in range(len(constraints)): - if constraints[i][0] == CFREE: - newparam.append(parameters[i]) - elif constraints[i][0] == CPOSITIVE: - #newparam.append(parameters[i] * parameters[i]) - newparam.append(abs(parameters[i])) - elif constraints[i][0] == CQUOTED: - newparam.append(parameters[i]) - elif abs(constraints[i][0]) == CFIXED: - newparam.append(parameters[i]) - else: - newparam.append(parameters[i]) - for i in range(len(constraints)): - if constraints[i][0] == CFACTOR: - newparam[i] = constraints[i][2] * newparam[int(constraints[i][1])] - elif constraints[i][0] == CDELTA: - newparam[i] = constraints[i][2] + newparam[int(constraints[i][1])] - elif constraints[i][0] == CIGNORED: - # The whole ignored stuff should not be documented because setting - # a parameter to 0 is not the same as being ignored. - # Being ignored should imply the parameter is simply not accounted for - # and should be stripped out of the list of parameters by the program - # using this module - newparam[i] = 0 - elif constraints[i][0] == CSUM: - newparam[i] = constraints[i][2]-newparam[int(constraints[i][1])] - return newparam - - -def _get_sigma_parameters(parameters, sigma0, constraints): - """ - Internal function propagating the uncertainty on the actually fitted parameters and related parameters to the - final parameters considering the applied constraints. - - Parameters - ---------- - parameters : 1D sequence of length equal to the number of free parameters N - The parameters actually used in the fitting process. - sigma0 : 1D sequence of length N - Uncertainties calculated as the square-root of the diagonal of - the covariance matrix - constraints : The set of constraints applied in the fitting process - """ - # 0 = Free 1 = Positive 2 = Quoted - # 3 = Fixed 4 = Factor 5 = Delta - if constraints is None: - return sigma0 - n_free = 0 - sigma_par = numpy.zeros(parameters.shape, numpy.float64) - for i in range(len(constraints)): - if constraints[i][0] == CFREE: - sigma_par [i] = sigma0[n_free] - n_free += 1 - elif constraints[i][0] == CPOSITIVE: - #sigma_par [i] = 2.0 * sigma0[n_free] - sigma_par [i] = sigma0[n_free] - n_free += 1 - elif constraints[i][0] == CQUOTED: - pmax = max(constraints [i][1], constraints [i][2]) - pmin = min(constraints [i][1], constraints [i][2]) - # A = 0.5 * (pmax + pmin) - B = 0.5 * (pmax - pmin) - if (B > 0) & (parameters [i] < pmax) & (parameters [i] > pmin): - sigma_par [i] = abs(B * numpy.cos(parameters[i]) * sigma0[n_free]) - n_free += 1 - else: - sigma_par [i] = parameters[i] - elif abs(constraints[i][0]) == CFIXED: - sigma_par[i] = parameters[i] - for i in range(len(constraints)): - if constraints[i][0] == CFACTOR: - sigma_par [i] = constraints[i][2]*sigma_par[int(constraints[i][1])] - elif constraints[i][0] == CDELTA: - sigma_par [i] = sigma_par[int(constraints[i][1])] - elif constraints[i][0] == CSUM: - sigma_par [i] = sigma_par[int(constraints[i][1])] - return sigma_par - - -def main(argv=None): - if argv is None: - npoints = 10000 - elif hasattr(argv, "__len__"): - if len(argv) > 1: - npoints = int(argv[1]) - else: - print("Usage:") - print("fit [npoints]") - else: - # expected a number - npoints = argv - - def gauss(t0, *param0): - param = numpy.array(param0) - t = numpy.array(t0) - dummy = 2.3548200450309493 * (t - param[3]) / param[4] - return param[0] + param[1] * t + param[2] * myexp(-0.5 * dummy * dummy) - - - def myexp(x): - # put a (bad) filter to avoid over/underflows - # with no python looping - return numpy.exp(x * numpy.less(abs(x), 250)) -\ - 1.0 * numpy.greater_equal(abs(x), 250) - - xx = numpy.arange(npoints, dtype=numpy.float64) - yy = gauss(xx, *[10.5, 2, 1000.0, 20., 15]) - sy = numpy.sqrt(abs(yy)) - parameters = [0.0, 1.0, 900.0, 25., 10] - stime = time.time() - - fittedpar, cov, ddict = leastsq(gauss, xx, yy, parameters, - sigma=sy, - left_derivative=False, - full_output=True, - check_finite=True) - etime = time.time() - sigmapars = numpy.sqrt(numpy.diag(cov)) - print("Took ", etime - stime, "seconds") - print("Function calls = ", ddict["nfev"]) - print("chi square = ", ddict["chisq"]) - print("Fitted pars = ", fittedpar) - print("Sigma pars = ", sigmapars) - try: - from scipy.optimize import curve_fit as cfit - SCIPY = True - except ImportError: - SCIPY = False - if SCIPY: - counter = 0 - stime = time.time() - scipy_fittedpar, scipy_cov = cfit(gauss, - xx, - yy, - parameters, - sigma=sy) - etime = time.time() - print("Scipy Took ", etime - stime, "seconds") - print("Counter = ", counter) - print("scipy = ", scipy_fittedpar) - print("Sigma = ", numpy.sqrt(numpy.diag(scipy_cov))) - -if __name__ == "__main__": - main() diff --git a/silx/math/fit/peaks.pyx b/silx/math/fit/peaks.pyx deleted file mode 100644 index a4fce89..0000000 --- a/silx/math/fit/peaks.pyx +++ /dev/null @@ -1,175 +0,0 @@ -# coding: utf-8 -#/*########################################################################## -# Copyright (C) 2016-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 a peak search function and tools related to peak -analysis. -""" - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "15/05/2017" - -import logging -import numpy - -from silx.math.fit import filters - -_logger = logging.getLogger(__name__) - -cimport cython -from libc.stdlib cimport free - -cimport silx.math.fit.peaks_wrapper as peaks_wrapper - - -def peak_search(y, fwhm, sensitivity=3.5, - begin_index=None, end_index=None, - debug=False, relevance_info=False): - """Find peaks in a curve. - - :param y: Data array - :type y: numpy.ndarray - :param fwhm: Estimated full width at half maximum of the typical peaks we - are interested in (expressed in number of samples) - :param sensitivity: Threshold factor used for peak detection. Only peaks - with amplitudes higher than ``σ * sensitivity`` - where ``σ`` is the - standard deviation of the noise - qualify as peaks. - :param begin_index: Index of the first sample of the region of interest - in the ``y`` array. If ``None``, start from the first sample. - :param end_index: Index of the last sample of the region of interest in - the ``y`` array. If ``None``, process until the last sample. - :param debug: If ``True``, print debug messages. Default: ``False`` - :param relevance_info: If ``True``, add a second dimension with relevance - information to the output array. Default: ``False`` - :return: 1D sequence with indices of peaks in the data - if ``relevance_info`` is ``False``. - Else, sequence of ``(peak_index, peak_relevance)`` tuples (one tuple - per peak). - :raise: ``IndexError`` if the number of peaks is too large to fit in the - output array. - """ - cdef: - int i - double[::1] y_c - double* peaks_c - double* relevances_c - - y_c = numpy.array(y, - copy=True, - dtype=numpy.float64, - order='C').reshape(-1) - if debug: - debug = 1 - else: - debug = 0 - - if begin_index is None: - begin_index = 0 - if end_index is None: - end_index = y_c.size - 1 - - n_peaks = peaks_wrapper.seek(begin_index, end_index, y_c.size, - fwhm, sensitivity, debug, - &y_c[0], &peaks_c, &relevances_c) - - - # A negative return value means that peaks were found but not enough - # memory could be allocated for all - if n_peaks < 0 and n_peaks != -123456: - msg = "Before memory allocation error happened, " - msg += "we found %d peaks.\n" % abs(n_peaks) - _logger.debug(msg) - msg = "" - for i in range(abs(n_peaks)): - msg += "peak index %f, " % peaks_c[i] - msg += "relevance %f\n" % relevances_c[i] - _logger.debug(msg) - free(peaks_c) - free(relevances_c) - raise MemoryError("Failed to reallocate memory for output arrays") - # Special value -123456 is returned if the initial memory allocation - # fails, before any search could be performed - elif n_peaks == -123456: - raise MemoryError("Failed to allocate initial memory for " + - "output arrays") - - peaks = numpy.empty(shape=(n_peaks,), - dtype=numpy.float64) - relevances = numpy.empty(shape=(n_peaks,), - dtype=numpy.float64) - - for i in range(n_peaks): - peaks[i] = peaks_c[i] - relevances[i] = relevances_c[i] - - free(peaks_c) - free(relevances_c) - - if not relevance_info: - return peaks - else: - return list(zip(peaks, relevances)) - - -def guess_fwhm(y): - """Return the full-width at half maximum for the largest peak in - the data array. - - The algorithm removes the background, then finds a global maximum - and its corresponding FWHM. - - This value can be used as an initial fit parameter, used as input for - an iterative fit function. - - :param y: Data to be used for guessing the fwhm. - :return: Estimation of full-width at half maximum, based on fwhm of - the global maximum. - """ - # set at a minimum value for the fwhm - fwhm_min = 4 - - # remove data background (computed with a strip filter) - background = filters.strip(y, w=1, niterations=1000) - yfit = y - background - - # basic peak search: find the global maximum - maximum = max(yfit) - # find indices of all values == maximum - idx = numpy.nonzero(yfit == maximum)[0] - # take the last one (if any) - if not len(idx): - return 0 - posindex = idx[-1] - height = yfit[posindex] - - # now find the width of the peak at half maximum - imin = posindex - while yfit[imin] > 0.5 * height and imin > 0: - imin -= 1 - imax = posindex - while yfit[imax] > 0.5 * height and imax < len(yfit) - 1: - imax += 1 - - fwhm = max(imax - imin - 1, fwhm_min) - - return fwhm diff --git a/silx/math/fit/peaks/include/peaks.h b/silx/math/fit/peaks/include/peaks.h deleted file mode 100644 index bd25d96..0000000 --- a/silx/math/fit/peaks/include/peaks.h +++ /dev/null @@ -1,32 +0,0 @@ -/*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ - -#ifndef PEAKS_H -#define PEAKS_H - -/* Smoothing functions */ - -long seek(long begin_index, long end_index, long nsamples, double fwhm, double sensitivity, - double debug_info, double *data, double **peaks, double **relevances); - -#endif /* #define PEAKS_H */ diff --git a/silx/math/fit/peaks/src/peaks.c b/silx/math/fit/peaks/src/peaks.c deleted file mode 100644 index 65cb4f6..0000000 --- a/silx/math/fit/peaks/src/peaks.c +++ /dev/null @@ -1,255 +0,0 @@ -#/*########################################################################## -# Copyright (c) 2004-2016 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. -# -#############################################################################*/ -#include <math.h> -#include <stdlib.h> -#include <stdio.h> -#include "peaks.h" - - -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -/* Peak search function, adapted from PyMca SpecFitFuns - - This uses a convolution with the second-derivative of a gaussian curve, to - smooth the data. - - Arguments: - - - begin_index: First index of the region of interest in the input data - array - - end_index: Last index of the region of interest in the input data - array - - nsamples: Number of samples in the input array - - fwhm: Full width at half maximum for the gaussian used for smoothing. - - sensitivity: - - debug_info: If different from 0, print debugging messages - - data: input array of 1D data - - peaks: pointer to output array of peak indices - - relevances: pointer to output array of peak relevances -*/ -long seek(long begin_index, - long end_index, - long nsamples, - double fwhm, - double sensitivity, - double debug_info, - double *data, - double **peaks, - double **relevances) -{ - /* local variables */ - double *peaks0, *relevances0; - double *realloc_peaks, *realloc_relevances; - double sigma, sigma2, sigma4; - long max_gfactor = 100; - double gfactor[100]; - long nr_factor; - double lowthreshold; - double data2[2]; - double nom; - double den2; - long channel1; - long lld; - long cch; - long cfac, cfac2, max_cfac; - long ihelp1, ihelp2; - long i; - long max_npeaks = 100; - long n_peaks = 0; - double peakstarted = 0; - - peaks0 = malloc(100 * sizeof(double)); - relevances0 = malloc(100 * sizeof(double)); - if (peaks0 == NULL || relevances0 == NULL) { - printf("Error: failed to allocate memory for peaks array."); - return(-123456); - } - /* Make sure the peaks matrix is filled with zeros */ - for (i=0;i<100;i++){ - peaks0[i] = 0.0; - relevances0[i] = 0.0; - } - /* Output pointers */ - *peaks = peaks0; - *relevances = relevances0; - - /* prepare the calculation of the Gaussian scaling factors */ - - sigma = fwhm / 2.35482; - sigma2 = sigma * sigma; - sigma4 = sigma2 * sigma2; - lowthreshold = 0.01 / sigma2; - - /* calculate the factors until lower threshold reached */ - nr_factor = 0; - max_cfac = MIN(max_gfactor, ((end_index - begin_index - 2) / 2) - 1); - for (cfac=0; cfac < max_cfac; cfac++) { - nr_factor++; - cfac2 = (cfac+1) * (cfac+1); - gfactor[cfac] = (sigma2 - cfac2) * exp(-cfac2/(sigma2*2.0)) / sigma4; - - if ((gfactor[cfac] < lowthreshold) - && (gfactor[cfac] > (-lowthreshold))){ - break; - } - } - - /* What comes now is specific to MCA spectra ... */ - lld = 0; - while (data[lld] == 0) { - lld++; - } - lld = lld + (int) (0.5 * fwhm); - - channel1 = begin_index - nr_factor - 1; - channel1 = MAX (channel1, lld); - if(debug_info){ - printf("nrfactor = %ld\n", nr_factor); - } - /* calculates smoothed value and variance at begincalc */ - cch = MAX(begin_index, 0); - nom = data[cch] / sigma2; - den2 = data[cch] / sigma4; - for (cfac = 0; cfac < nr_factor; cfac++){ - ihelp1 = cch-cfac; - if (ihelp1 < 0){ - ihelp1 = 0; - } - ihelp2 = cch+cfac; - if (ihelp2 >= nsamples){ - ihelp2 = nsamples-1; - } - nom += gfactor[cfac] * (data[ihelp2] + data[ihelp1]); - den2 += gfactor[cfac] * gfactor[cfac] * - (data[ihelp2] + data[ihelp1]); - } - - /* now normalize the smoothed value to the standard deviation */ - if (den2 <= 0.0) { - data2[1] = 0.0; - }else{ - data2[1] = nom / sqrt(den2); - } - data[0] = data[1]; - - while (cch <= MIN(end_index,nsamples-2)){ - /* calculate gaussian smoothed values */ - data2[0] = data2[1]; - cch++; - nom = data[cch]/sigma2; - den2 = data[cch] / sigma4; - for (cfac = 1; cfac < nr_factor; cfac++){ - ihelp1 = cch-cfac; - if (ihelp1 < 0){ - ihelp1 = 0; - } - ihelp2 = cch+cfac; - if (ihelp2 >= nsamples){ - ihelp2 = nsamples-1; - } - nom += gfactor[cfac-1] * (data[ihelp2] + data[ihelp1]); - den2 += gfactor[cfac-1] * gfactor[cfac-1] * - (data[ihelp2] + data[ihelp1]); - } - /* now normalize the smoothed value to the standard deviation */ - if (den2 <= 0) { - data2[1] = 0; - }else{ - data2[1] = nom / sqrt(den2); - } - /* look if the current point falls in a peak */ - if (data2[1] > sensitivity) { - if(peakstarted == 0){ - if (data2[1] > data2[0]){ - /* this second test is to prevent a peak from outside - the region from being detected at the beginning of the search */ - peakstarted=1; - } - } - /* there is a peak */ - if (debug_info){ - printf("At cch = %ld y[cch] = %g\n", cch, data[cch]); - printf("data2[0] = %g\n", data2[0]); - printf("data2[1] = %g\n", data2[1]); - printf("sensitivity = %g\n", sensitivity); - } - if(peakstarted == 1){ - /* look for the top of the peak */ - if (data2[1] < data2[0]) { - /* we are close to the top of the peak */ - if (debug_info){ - printf("we are close to the top of the peak\n"); - } - if (n_peaks == max_npeaks) { - max_npeaks = max_npeaks + 100; - realloc_peaks = realloc(peaks0, max_npeaks * sizeof(double)); - realloc_relevances = realloc(relevances0, max_npeaks * sizeof(double)); - if (realloc_peaks == NULL || realloc_relevances == NULL) { - printf("Error: failed to extend memory for peaks array."); - *peaks = peaks0; - *relevances = relevances0; - return(-n_peaks); - } - else { - peaks0 = realloc_peaks; - relevances0 = realloc_relevances; - } - } - peaks0[n_peaks] = cch-1; - relevances0[n_peaks] = data2[0]; - n_peaks++; - peakstarted=2; - } - } - /* Doublet case */ - if(peakstarted == 2){ - if ((cch-peaks0[n_peaks-1]) > 0.6 * fwhm) { - if (data2[1] > data2[0]){ - if(debug_info){ - printf("We may have a doublet\n"); - } - peakstarted=1; - } - } - } - }else{ - if (peakstarted==1){ - /* We were on a peak but we did not find the top */ - if(debug_info){ - printf("We were on a peak but we did not find the top\n"); - } - } - peakstarted=0; - } - } - if(debug_info){ - for (i=0;i< n_peaks;i++){ - printf("Peak %ld found at ",i+1); - printf("index %g with y = %g\n", peaks0[i],data[(long ) peaks0[i]]); - } - } - *peaks = peaks0; - *relevances = relevances0; - return (n_peaks); -} diff --git a/silx/math/fit/peaks_wrapper.pxd b/silx/math/fit/peaks_wrapper.pxd deleted file mode 100644 index 4c77dc6..0000000 --- a/silx/math/fit/peaks_wrapper.pxd +++ /dev/null @@ -1,41 +0,0 @@ -# coding: utf-8 -#/*########################################################################## -# Copyright (C) 2016 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. -# -#############################################################################*/ - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "22/06/2016" - -cimport cython - -cdef extern from "peaks.h": - long seek(long begin_index, - long end_index, - long nsamples, - double fwhm, - double sensitivity, - double debug_info, - double * data, - double ** peaks, - double ** relevances) - diff --git a/silx/math/fit/setup.py b/silx/math/fit/setup.py deleted file mode 100644 index 649387f..0000000 --- a/silx/math/fit/setup.py +++ /dev/null @@ -1,85 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-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. -# -# ############################################################################*/ - - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "22/06/2016" - - -import os.path - -from numpy.distutils.misc_util import Configuration - - -def configuration(parent_package='', top_path=None): - config = Configuration('fit', parent_package, top_path) - config.add_subpackage('test') - - # ===================================== - # fit functions - # ===================================== - fun_src = [os.path.join('functions', "src", "funs.c"), - "functions.pyx"] - fun_inc = [os.path.join('functions', 'include')] - - config.add_extension('functions', - sources=fun_src, - include_dirs=fun_inc, - language='c') - - # ===================================== - # fit filters - # ===================================== - filt_src = [os.path.join('filters', "src", srcf) - for srcf in ["smoothnd.c", "snip1d.c", - "snip2d.c", "snip3d.c", "strip.c"]] - filt_src.append("filters.pyx") - filt_inc = [os.path.join('filters', 'include')] - - config.add_extension('filters', - sources=filt_src, - include_dirs=filt_inc, - language='c') - - # ===================================== - # peaks - # ===================================== - peaks_src = [os.path.join('peaks', "src", "peaks.c"), - "peaks.pyx"] - peaks_inc = [os.path.join('peaks', 'include')] - - config.add_extension('peaks', - sources=peaks_src, - include_dirs=peaks_inc, - language='c') - # ===================================== - # ===================================== - return config - - -if __name__ == "__main__": - from numpy.distutils.core import setup - - setup(configuration=configuration) diff --git a/silx/math/fit/test/__init__.py b/silx/math/fit/test/__init__.py deleted file mode 100644 index d3d8ce8..0000000 --- a/silx/math/fit/test/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "22/06/2016" - -import unittest - -from .test_fit import suite as test_curve_fit -from .test_functions import suite as test_fitfuns -from .test_filters import suite as test_fitfilters -from .test_peaks import suite as test_peaks -from .test_fitmanager import suite as test_fitmanager -from .test_bgtheories import suite as test_bgtheories - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTest(test_curve_fit()) - test_suite.addTest(test_fitfuns()) - test_suite.addTest(test_fitfilters()) - test_suite.addTest(test_peaks()) - test_suite.addTest(test_fitmanager()) - test_suite.addTest(test_bgtheories()) - return test_suite diff --git a/silx/math/fit/test/test_bgtheories.py b/silx/math/fit/test/test_bgtheories.py deleted file mode 100644 index e9fea37..0000000 --- a/silx/math/fit/test/test_bgtheories.py +++ /dev/null @@ -1,169 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ -import copy -import unittest -import numpy -import random - -from silx.math.fit import bgtheories -from silx.math.fit.functions import sum_gauss - - -class TestBgTheories(unittest.TestCase): - """ - """ - def setUp(self): - self.x = numpy.arange(100) - self.y = 10 + 0.05 * self.x + sum_gauss(self.x, 10., 45., 15.) - # add a very narrow high amplitude peak to test strip and snip - self.y += sum_gauss(self.x, 100., 75., 2.) - self.narrow_peak_index = list(self.x).index(75) - random.seed() - - def tearDown(self): - pass - - def testTheoriesAttrs(self): - for theory_name in bgtheories.THEORY: - self.assertIsInstance(theory_name, str) - self.assertTrue(hasattr(bgtheories.THEORY[theory_name], - "function")) - self.assertTrue(hasattr(bgtheories.THEORY[theory_name].function, - "__call__")) - # Ensure legacy functions are not renamed accidentally - self.assertTrue( - {"No Background", "Constant", "Linear", "Strip", "Snip"}.issubset( - set(bgtheories.THEORY))) - - def testNoBg(self): - nobgfun = bgtheories.THEORY["No Background"].function - self.assertTrue(numpy.array_equal(nobgfun(self.x, self.y), - numpy.zeros_like(self.x))) - # default estimate - self.assertEqual(bgtheories.THEORY["No Background"].estimate(self.x, self.y), - ([], [])) - - def testConstant(self): - consfun = bgtheories.THEORY["Constant"].function - c = random.random() * 100 - self.assertTrue(numpy.array_equal(consfun(self.x, self.y, c), - c * numpy.ones_like(self.x))) - # default estimate - esti_par, cons = bgtheories.THEORY["Constant"].estimate(self.x, self.y) - self.assertEqual(cons, - [[0, 0, 0]]) - self.assertAlmostEqual(esti_par, - min(self.y)) - - def testLinear(self): - linfun = bgtheories.THEORY["Linear"].function - a = random.random() * 100 - b = random.random() * 100 - self.assertTrue(numpy.array_equal(linfun(self.x, self.y, a, b), - a + b * self.x)) - # default estimate - esti_par, cons = bgtheories.THEORY["Linear"].estimate(self.x, self.y) - - self.assertEqual(cons, - [[0, 0, 0], [0, 0, 0]]) - self.assertAlmostEqual(esti_par[0], 10, places=3) - self.assertAlmostEqual(esti_par[1], 0.05, places=3) - - def testStrip(self): - stripfun = bgtheories.THEORY["Strip"].function - anchors = sorted(random.sample(list(self.x), 4)) - anchors_indices = [list(self.x).index(a) for a in anchors] - - # we really want to strip away the narrow peak - anchors_indices_copy = copy.deepcopy(anchors_indices) - for idx in anchors_indices_copy: - if abs(idx - self.narrow_peak_index) < 5: - anchors_indices.remove(idx) - anchors.remove(self.x[idx]) - - width = 2 - niter = 1000 - bgtheories.THEORY["Strip"].configure(AnchorsList=anchors, AnchorsFlag=True) - - bg = stripfun(self.x, self.y, width, niter) - - # assert peak amplitude has been decreased - self.assertLess(bg[self.narrow_peak_index], - self.y[self.narrow_peak_index]) - - # default estimate - for i in anchors_indices: - self.assertEqual(bg[i], self.y[i]) - - # estimated parameters are equal to the default ones in the config dict - bgtheories.THEORY["Strip"].configure(StripWidth=7, StripIterations=8) - esti_par, cons = bgtheories.THEORY["Strip"].estimate(self.x, self.y) - self.assertTrue(numpy.array_equal(cons, [[3, 0, 0], [3, 0, 0]])) - self.assertEqual(esti_par, [7, 8]) - - def testSnip(self): - snipfun = bgtheories.THEORY["Snip"].function - anchors = sorted(random.sample(list(self.x), 4)) - anchors_indices = [list(self.x).index(a) for a in anchors] - - # we want to strip away the narrow peak, so remove nearby anchors - anchors_indices_copy = copy.deepcopy(anchors_indices) - for idx in anchors_indices_copy: - if abs(idx - self.narrow_peak_index) < 5: - anchors_indices.remove(idx) - anchors.remove(self.x[idx]) - - width = 16 - bgtheories.THEORY["Snip"].configure(AnchorsList=anchors, AnchorsFlag=True) - bg = snipfun(self.x, self.y, width) - - # assert peak amplitude has been decreased - self.assertLess(bg[self.narrow_peak_index], - self.y[self.narrow_peak_index], - "Snip didn't decrease the peak amplitude.") - - # anchored data must remain fixed - for i in anchors_indices: - self.assertEqual(bg[i], self.y[i]) - - # estimated parameters are equal to the default ones in the config dict - bgtheories.THEORY["Snip"].configure(SnipWidth=7) - esti_par, cons = bgtheories.THEORY["Snip"].estimate(self.x, self.y) - self.assertTrue(numpy.array_equal(cons, [[3, 0, 0]])) - self.assertEqual(esti_par, [7]) - - -test_cases = (TestBgTheories,) - - -def suite(): - loader = unittest.defaultTestLoader - test_suite = unittest.TestSuite() - for test_class in test_cases: - tests = loader.loadTestsFromTestCase(test_class) - test_suite.addTests(tests) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/fit/test/test_filters.py b/silx/math/fit/test/test_filters.py deleted file mode 100644 index 078b998..0000000 --- a/silx/math/fit/test/test_filters.py +++ /dev/null @@ -1,137 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ -import numpy -import unittest -from silx.math.fit import filters -from silx.math.fit import functions -from silx.test.utils import add_relative_noise - - -class TestSmooth(unittest.TestCase): - """ - Unit tests of smoothing functions. - - Test that the difference between a synthetic curve with 5% added random - noise and the result of smoothing that signal is less than 5%. We compare - the sum of all samples in each curve. - """ - def setUp(self): - x = numpy.arange(5000) - # (height1, center1, fwhm1, beamfwhm...) - slit_params = (50, 500, 200, 100, - 50, 600, 80, 30, - 20, 2000, 150, 150, - 50, 2250, 110, 100, - 40, 3000, 50, 10, - 23, 4980, 250, 20) - - self.y1 = functions.sum_slit(x, *slit_params) - # 5% noise - self.y1 = add_relative_noise(self.y1, 5.) - - # (height1, center1, fwhm1...) - step_params = (50, 500, 200, - 50, 600, 80, - 20, 2000, 150, - 50, 2250, 110, - 40, 3000, 50, - 23, 4980, 250,) - - self.y2 = functions.sum_stepup(x, *step_params) - # 5% noise - self.y2 = add_relative_noise(self.y2, 5.) - - self.y3 = functions.sum_stepdown(x, *step_params) - # 5% noise - self.y3 = add_relative_noise(self.y3, 5.) - - def tearDown(self): - pass - - def testSavitskyGolay(self): - npts = 25 - for y in [self.y1, self.y2, self.y3]: - smoothed_y = filters.savitsky_golay(y, npoints=npts) - - # we added +-5% of random noise. The difference must be much lower - # than 5%. - diff = abs(sum(smoothed_y) - sum(y)) / sum(y) - self.assertLess(diff, 0.05, - "Difference between data with 5%% noise and " + - "smoothed data is > 5%% (%f %%)" % (diff * 100)) - - # Try various smoothing levels - npts += 25 - - def testSmooth1d(self): - """Test the 1D smoothing against the formula - ys[i] = (y[i-1] + 2 * y[i] + y[i+1]) / 4 (for 1 < i < n-1)""" - smoothed_y = filters.smooth1d(self.y1) - - for i in range(1, len(self.y1) - 1): - self.assertAlmostEqual(4 * smoothed_y[i], - self.y1[i-1] + 2 * self.y1[i] + self.y1[i+1]) - - def testSmooth2d(self): - """Test that a 2D smoothing is the same as two successive and - orthogonal 1D smoothings""" - x = numpy.arange(10000) - - noise = 2 * numpy.random.random(10000) - 1 - noise *= 0.05 - y = x * (1 + noise) - - y.shape = (100, 100) - - smoothed_y = filters.smooth2d(y) - - intermediate_smooth = numpy.zeros_like(y) - expected_smooth = numpy.zeros_like(y) - # smooth along first dimension - for i in range(0, y.shape[0]): - intermediate_smooth[i, :] = filters.smooth1d(y[i, :]) - - # smooth along second dimension - for j in range(0, y.shape[1]): - expected_smooth[:, j] = filters.smooth1d(intermediate_smooth[:, j]) - - for i in range(0, y.shape[0]): - for j in range(0, y.shape[1]): - self.assertAlmostEqual(smoothed_y[i, j], - expected_smooth[i, j]) - - -test_cases = (TestSmooth,) - - -def suite(): - loader = unittest.defaultTestLoader - test_suite = unittest.TestSuite() - for test_class in test_cases: - tests = loader.loadTestsFromTestCase(test_class) - test_suite.addTests(tests) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/fit/test/test_fit.py b/silx/math/fit/test/test_fit.py deleted file mode 100644 index 3fdf394..0000000 --- a/silx/math/fit/test/test_fit.py +++ /dev/null @@ -1,387 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-2020 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. -# -# ############################################################################*/ -""" -Nominal tests of the leastsq function. -""" - -import unittest - -import numpy -import sys - -from silx.utils import testutils -from silx.math.fit.leastsq import _logger as fitlogger - - -class Test_leastsq(unittest.TestCase): - """ - Unit tests of the leastsq function. - """ - - ndims = None - - def setUp(self): - try: - from silx.math.fit import leastsq - self.instance = leastsq - except ImportError: - self.instance = None - - def myexp(x): - # put a (bad) filter to avoid over/underflows - # with no python looping - return numpy.exp(x*numpy.less(abs(x), 250)) - \ - 1.0 * numpy.greater_equal(abs(x), 250) - - self.my_exp = myexp - - def gauss(x, *params): - params = numpy.array(params, copy=False, dtype=numpy.float64) - result = params[0] + params[1] * x - for i in range(2, len(params), 3): - p = params[i:(i+3)] - dummy = 2.3548200450309493*(x - p[1])/p[2] - result += p[0] * self.my_exp(-0.5 * dummy * dummy) - return result - - self.gauss = gauss - - def gauss_derivative(x, params, idx): - if idx == 0: - return numpy.ones(len(x), numpy.float64) - if idx == 1: - return x - gaussian_peak = (idx - 2) // 3 - gaussian_parameter = (idx - 2) % 3 - actual_idx = 2 + 3 * gaussian_peak - p = params[actual_idx:(actual_idx+3)] - if gaussian_parameter == 0: - return self.gauss(x, *[0, 0, 1.0, p[1], p[2]]) - if gaussian_parameter == 1: - tmp = self.gauss(x, *[0, 0, p[0], p[1], p[2]]) - tmp *= 2.3548200450309493*(x - p[1])/p[2] - return tmp * 2.3548200450309493/p[2] - if gaussian_parameter == 2: - tmp = self.gauss(x, *[0, 0, p[0], p[1], p[2]]) - tmp *= 2.3548200450309493*(x - p[1])/p[2] - return tmp * 2.3548200450309493*(x - p[1])/(p[2]*p[2]) - - self.gauss_derivative = gauss_derivative - - def tearDown(self): - self.instance = None - self.gauss = None - self.gauss_derivative = None - self.my_exp = None - self.model_function = None - self.model_derivative = None - - def testImport(self): - self.assertTrue(self.instance is not None, - "Cannot import leastsq from silx.math.fit") - - def testUnconstrainedFitNoWeight(self): - parameters_actual = [10.5, 2, 1000.0, 20., 15] - x = numpy.arange(10000.) - y = self.gauss(x, *parameters_actual) - parameters_estimate = [0.0, 1.0, 900.0, 25., 10] - model_function = self.gauss - - fittedpar, cov = self.instance(model_function, x, y, parameters_estimate) - test_condition = numpy.allclose(parameters_actual, fittedpar) - if not test_condition: - msg = "Unsuccessfull fit\n" - for i in range(len(fittedpar)): - msg += "Expected %g obtained %g\n" % (parameters_actual[i], - fittedpar[i]) - self.assertTrue(test_condition, msg) - - def testUnconstrainedFitWeight(self): - parameters_actual = [10.5,2,1000.0,20.,15] - x = numpy.arange(10000.) - y = self.gauss(x, *parameters_actual) - sigma = numpy.sqrt(y) - parameters_estimate = [0.0, 1.0, 900.0, 25., 10] - model_function = self.gauss - - fittedpar, cov = self.instance(model_function, x, y, - parameters_estimate, - sigma=sigma) - test_condition = numpy.allclose(parameters_actual, fittedpar) - if not test_condition: - msg = "Unsuccessfull fit\n" - for i in range(len(fittedpar)): - msg += "Expected %g obtained %g\n" % (parameters_actual[i], - fittedpar[i]) - self.assertTrue(test_condition, msg) - - def testDerivativeFunction(self): - parameters_actual = [10.5, 2, 10000.0, 20., 150, 5000, 900., 300] - x = numpy.arange(10000.) - y = self.gauss(x, *parameters_actual) - delta = numpy.sqrt(numpy.finfo(numpy.float64).eps) - for i in range(len(parameters_actual)): - p = parameters_actual * 1 - if p[i] == 0: - delta_par = delta - else: - delta_par = p[i] * delta - if i > 2: - p[0] = 0.0 - p[1] = 0.0 - p[i] += delta_par - yPlus = self.gauss(x, *p) - p[i] = parameters_actual[i] - delta_par - yMinus = self.gauss(x, *p) - numerical_derivative = (yPlus - yMinus) / (2 * delta_par) - #numerical_derivative = (self.gauss(x, *p) - y) / delta_par - p[i] = parameters_actual[i] - derivative = self.gauss_derivative(x, p, i) - diff = numerical_derivative - derivative - test_condition = numpy.allclose(numerical_derivative, - derivative, atol=5.0e-6) - if not test_condition: - msg = "Error calculating derivative of parameter %d." % i - msg += "\n diff min = %g diff max = %g" % (diff.min(), diff.max()) - self.assertTrue(test_condition, msg) - - def testConstrainedFit(self): - CFREE = 0 - CPOSITIVE = 1 - CQUOTED = 2 - CFIXED = 3 - CFACTOR = 4 - CDELTA = 5 - CSUM = 6 - parameters_actual = [10.5, 2, 10000.0, 20., 150, 5000, 900., 300] - x = numpy.arange(10000.) - y = self.gauss(x, *parameters_actual) - parameters_estimate = [0.0, 1.0, 900.0, 25., 10, 400, 850, 200] - model_function = self.gauss - model_deriv = self.gauss_derivative - constraints_all_free = [[0, 0, 0]] * len(parameters_actual) - constraints_all_positive = [[1, 0, 0]] * len(parameters_actual) - constraints_delta_position = [[0, 0, 0]] * len(parameters_actual) - constraints_delta_position[6] = [CDELTA, 3, 880] - constraints_sum_position = constraints_all_positive * 1 - constraints_sum_position[6] = [CSUM, 3, 920] - constraints_factor = constraints_delta_position * 1 - constraints_factor[2] = [CFACTOR, 5, 2] - constraints_list = [None, - constraints_all_free, - constraints_all_positive, - constraints_delta_position, - constraints_sum_position] - - # for better code coverage, the warning recommending to set full_output - # to True when using constraints should be shown at least once - full_output = True - for index, constraints in enumerate(constraints_list): - if index == 2: - full_output = None - elif index == 3: - full_output = 0 - for model_deriv in [None, self.gauss_derivative]: - for sigma in [None, numpy.sqrt(y)]: - fittedpar, cov = self.instance(model_function, x, y, - parameters_estimate, - sigma=sigma, - constraints=constraints, - model_deriv=model_deriv, - full_output=full_output)[:2] - full_output = True - - test_condition = numpy.allclose(parameters_actual, fittedpar) - if not test_condition: - msg = "Unsuccessfull fit\n" - for i in range(len(fittedpar)): - msg += "Expected %g obtained %g\n" % (parameters_actual[i], - fittedpar[i]) - self.assertTrue(test_condition, msg) - - def testUnconstrainedFitAnalyticalDerivative(self): - parameters_actual = [10.5, 2, 1000.0, 20., 15] - x = numpy.arange(10000.) - y = self.gauss(x, *parameters_actual) - sigma = numpy.sqrt(y) - parameters_estimate = [0.0, 1.0, 900.0, 25., 10] - model_function = self.gauss - model_deriv = self.gauss_derivative - - fittedpar, cov = self.instance(model_function, x, y, - parameters_estimate, - sigma=sigma, - model_deriv=model_deriv) - test_condition = numpy.allclose(parameters_actual, fittedpar) - if not test_condition: - msg = "Unsuccessfull fit\n" - for i in range(len(fittedpar)): - msg += "Expected %g obtained %g\n" % (parameters_actual[i], - fittedpar[i]) - self.assertTrue(test_condition, msg) - - @testutils.test_logging(fitlogger.name, warning=2) - def testBadlyShapedData(self): - parameters_actual = [10.5, 2, 1000.0, 20., 15] - x = numpy.arange(10000.).reshape(1000, 10) - y = self.gauss(x, *parameters_actual) - sigma = numpy.sqrt(y) - parameters_estimate = [0.0, 1.0, 900.0, 25., 10] - model_function = self.gauss - - for check_finite in [True, False]: - fittedpar, cov = self.instance(model_function, x, y, - parameters_estimate, - sigma=sigma, - check_finite=check_finite) - test_condition = numpy.allclose(parameters_actual, fittedpar) - if not test_condition: - msg = "Unsuccessfull fit\n" - for i in range(len(fittedpar)): - msg += "Expected %g obtained %g\n" % (parameters_actual[i], - fittedpar[i]) - self.assertTrue(test_condition, msg) - - @testutils.test_logging(fitlogger.name, warning=3) - def testDataWithNaN(self): - parameters_actual = [10.5, 2, 1000.0, 20., 15] - x = numpy.arange(10000.).reshape(1000, 10) - y = self.gauss(x, *parameters_actual) - sigma = numpy.sqrt(y) - parameters_estimate = [0.0, 1.0, 900.0, 25., 10] - model_function = self.gauss - x[500] = numpy.inf - # check default behavior - try: - self.instance(model_function, x, y, - parameters_estimate, - sigma=sigma) - except ValueError: - info = "%s" % sys.exc_info()[1] - self.assertTrue("array must not contain inf" in info) - - # check requested behavior - try: - self.instance(model_function, x, y, - parameters_estimate, - sigma=sigma, - check_finite=True) - except ValueError: - info = "%s" % sys.exc_info()[1] - self.assertTrue("array must not contain inf" in info) - - fittedpar, cov = self.instance(model_function, x, y, - parameters_estimate, - sigma=sigma, - check_finite=False) - test_condition = numpy.allclose(parameters_actual, fittedpar) - if not test_condition: - msg = "Unsuccessfull fit\n" - for i in range(len(fittedpar)): - msg += "Expected %g obtained %g\n" % (parameters_actual[i], - fittedpar[i]) - self.assertTrue(test_condition, msg) - - # testing now with ydata containing NaN - x = numpy.arange(10000.).reshape(1000, 10) - y[500] = numpy.nan - fittedpar, cov = self.instance(model_function, x, y, - parameters_estimate, - sigma=sigma, - check_finite=False) - - test_condition = numpy.allclose(parameters_actual, fittedpar) - if not test_condition: - msg = "Unsuccessfull fit\n" - for i in range(len(fittedpar)): - msg += "Expected %g obtained %g\n" % (parameters_actual[i], - fittedpar[i]) - self.assertTrue(test_condition, msg) - - # testing now with sigma containing NaN - sigma[300] = numpy.nan - fittedpar, cov = self.instance(model_function, x, y, - parameters_estimate, - sigma=sigma, - check_finite=False) - test_condition = numpy.allclose(parameters_actual, fittedpar) - if not test_condition: - msg = "Unsuccessfull fit\n" - for i in range(len(fittedpar)): - msg += "Expected %g obtained %g\n" % (parameters_actual[i], - fittedpar[i]) - self.assertTrue(test_condition, msg) - - def testUncertainties(self): - """Test for validity of uncertainties in returned full-output - dictionary. This is a non-regression test for pull request #197""" - parameters_actual = [10.5, 2, 1000.0, 20., 15, 2001.0, 30.1, 16] - x = numpy.arange(10000.) - y = self.gauss(x, *parameters_actual) - parameters_estimate = [0.0, 1.0, 900.0, 25., 10., 1500., 20., 2.0] - - # test that uncertainties are not 0. - fittedpar, cov, infodict = self.instance(self.gauss, x, y, parameters_estimate, - full_output=True) - uncertainties = infodict["uncertainties"] - self.assertEqual(len(uncertainties), len(parameters_actual)) - self.assertEqual(len(uncertainties), len(fittedpar)) - for uncertainty in uncertainties: - self.assertNotAlmostEqual(uncertainty, 0.) - - # set constraint FIXED for half the parameters. - # This should cause leastsq to return 100% uncertainty. - parameters_estimate = [10.6, 2.1, 1000.1, 20.1, 15.1, 2001.1, 30.2, 16.1] - CFIXED = 3 - CFREE = 0 - constraints = [] - for i in range(len(parameters_estimate)): - if i % 2: - constraints.append([CFIXED, 0, 0]) - else: - constraints.append([CFREE, 0, 0]) - fittedpar, cov, infodict = self.instance(self.gauss, x, y, parameters_estimate, - constraints=constraints, - full_output=True) - uncertainties = infodict["uncertainties"] - for i in range(len(parameters_estimate)): - if i % 2: - # test that all FIXED parameters have 100% uncertainty - self.assertAlmostEqual(uncertainties[i], - parameters_estimate[i]) - - -test_cases = (Test_leastsq,) - -def suite(): - loader = unittest.defaultTestLoader - test_suite = unittest.TestSuite() - for test_class in test_cases: - tests = loader.loadTestsFromTestCase(test_class) - test_suite.addTests(tests) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/fit/test/test_fitmanager.py b/silx/math/fit/test/test_fitmanager.py deleted file mode 100644 index acac242..0000000 --- a/silx/math/fit/test/test_fitmanager.py +++ /dev/null @@ -1,513 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-2020 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. -# -# ############################################################################*/ -""" -Tests for fitmanager module -""" - -import unittest -import numpy -import os.path - -from silx.math.fit import fitmanager -from silx.math.fit import fittheories -from silx.math.fit import bgtheories -from silx.math.fit.fittheory import FitTheory -from silx.math.fit.functions import sum_gauss, sum_stepdown, sum_stepup - -from silx.utils.testutils import ParametricTestCase -from silx.test.utils import temp_dir - -custom_function_definition = """ -import copy -from silx.math.fit.fittheory import FitTheory - -CONFIG = {'d': 1.} - -def myfun(x, a, b, c): - "Model function" - return (a * x**2 + b * x + c) / CONFIG['d'] - -def myesti(x, y): - "Initial parameters for iterative fit (a, b, c) = (1, 1, 1)" - return (1., 1., 1.), ((0, 0, 0), (0, 0, 0), (0, 0, 0)) - -def myconfig(d=1., **kw): - "This function can modify CONFIG" - CONFIG["d"] = d - return CONFIG - -def myderiv(x, parameters, index): - "Custom derivative (does not work, causes singular matrix)" - pars_plus = copy.copy(parameters) - pars_plus[index] *= 1.0001 - - pars_minus = parameters - pars_minus[index] *= copy.copy(0.9999) - - delta_fun = myfun(x, *pars_plus) - myfun(x, *pars_minus) - delta_par = parameters[index] * 0.0001 * 2 - - return delta_fun / delta_par - -THEORY = { - 'my fit theory': - FitTheory(function=myfun, - parameters=('A', 'B', 'C'), - estimate=myesti, - configure=myconfig, - derivative=myderiv) -} - -""" - -old_custom_function_definition = """ -CONFIG = {'d': 1.0} - -def myfun(x, a, b, c): - "Model function" - return (a * x**2 + b * x + c) / CONFIG['d'] - -def myesti(x, y, bg, xscalinq, yscaling): - "Initial parameters for iterative fit (a, b, c) = (1, 1, 1)" - return (1., 1., 1.), ((0, 0, 0), (0, 0, 0), (0, 0, 0)) - -def myconfig(**kw): - "Update or complete CONFIG dictionary" - for key in kw: - CONFIG[key] = kw[key] - return CONFIG - -THEORY = ['my fit theory'] -PARAMETERS = [('A', 'B', 'C')] -FUNCTION = [myfun] -ESTIMATE = [myesti] -CONFIGURE = [myconfig] - -""" - - -def _order_of_magnitude(x): - return numpy.log10(x).round() - - -class TestFitmanager(ParametricTestCase): - """ - Unit tests of multi-peak functions. - """ - def setUp(self): - pass - - def tearDown(self): - pass - - def testFitManager(self): - """Test fit manager on synthetic data using a gaussian function - and a linear background""" - # Create synthetic data with a sum of gaussian functions - x = numpy.arange(1000).astype(numpy.float64) - - p = [1000, 100., 250, - 255, 650., 45, - 1500, 800.5, 95] - linear_bg = 2.65 * x + 13 - y = linear_bg + sum_gauss(x, *p) - - y_with_nans = numpy.array(y) - y_with_nans[::10] = numpy.nan - - x_with_nans = numpy.array(x) - x_with_nans[5::15] = numpy.nan - - tests = { - 'all finite': (x, y), - 'y with NaNs': (x, y_with_nans), - 'x with NaNs': (x_with_nans, y), - } - - for name, (xdata, ydata) in tests.items(): - with self.subTest(name=name): - # Fitting - fit = fitmanager.FitManager() - fit.setdata(x=xdata, y=ydata) - fit.loadtheories(fittheories) - # Use one of the default fit functions - fit.settheory('Gaussians') - fit.setbackground('Linear') - fit.estimate() - fit.runfit() - - # fit.fit_results[] - - # first 2 parameters are related to the linear background - self.assertEqual(fit.fit_results[0]["name"], "Constant") - self.assertAlmostEqual(fit.fit_results[0]["fitresult"], 13) - self.assertEqual(fit.fit_results[1]["name"], "Slope") - self.assertAlmostEqual(fit.fit_results[1]["fitresult"], 2.65) - - for i, param in enumerate(fit.fit_results[2:]): - param_number = i // 3 + 1 - if i % 3 == 0: - self.assertEqual(param["name"], - "Height%d" % param_number) - elif i % 3 == 1: - self.assertEqual(param["name"], - "Position%d" % param_number) - elif i % 3 == 2: - self.assertEqual(param["name"], - "FWHM%d" % param_number) - - self.assertAlmostEqual(param["fitresult"], - p[i]) - self.assertAlmostEqual(_order_of_magnitude(param["estimation"]), - _order_of_magnitude(p[i])) - - def testLoadCustomFitFunction(self): - """Test FitManager using a custom fit function defined in an external - file and imported with FitManager.loadtheories""" - # Create synthetic data with a sum of gaussian functions - x = numpy.arange(100).astype(numpy.float64) - - # a, b, c are the fit parameters - # d is a known scaling parameter that is set using configure() - a, b, c, d = 1.5, 2.5, 3.5, 4.5 - y = (a * x**2 + b * x + c) / d - - # Fitting - fit = fitmanager.FitManager() - fit.setdata(x=x, y=y) - - # Create a temporary function definition file, and import it - with temp_dir() as tmpDir: - tmpfile = os.path.join(tmpDir, 'customfun.py') - # custom_function_definition - fd = open(tmpfile, "w") - fd.write(custom_function_definition) - fd.close() - fit.loadtheories(tmpfile) - tmpfile_pyc = os.path.join(tmpDir, 'customfun.pyc') - if os.path.exists(tmpfile_pyc): - os.unlink(tmpfile_pyc) - os.unlink(tmpfile) - - fit.settheory('my fit theory') - # Test configure - fit.configure(d=4.5) - fit.estimate() - fit.runfit() - - self.assertEqual(fit.fit_results[0]["name"], - "A1") - self.assertAlmostEqual(fit.fit_results[0]["fitresult"], - 1.5) - self.assertEqual(fit.fit_results[1]["name"], - "B1") - self.assertAlmostEqual(fit.fit_results[1]["fitresult"], - 2.5) - self.assertEqual(fit.fit_results[2]["name"], - "C1") - self.assertAlmostEqual(fit.fit_results[2]["fitresult"], - 3.5) - - def testLoadOldCustomFitFunction(self): - """Test FitManager using a custom fit function defined in an external - file and imported with FitManager.loadtheories (legacy PyMca format)""" - # Create synthetic data with a sum of gaussian functions - x = numpy.arange(100).astype(numpy.float64) - - # a, b, c are the fit parameters - # d is a known scaling parameter that is set using configure() - a, b, c, d = 1.5, 2.5, 3.5, 4.5 - y = (a * x**2 + b * x + c) / d - - # Fitting - fit = fitmanager.FitManager() - fit.setdata(x=x, y=y) - - # Create a temporary function definition file, and import it - with temp_dir() as tmpDir: - tmpfile = os.path.join(tmpDir, 'oldcustomfun.py') - # custom_function_definition - fd = open(tmpfile, "w") - fd.write(old_custom_function_definition) - fd.close() - fit.loadtheories(tmpfile) - tmpfile_pyc = os.path.join(tmpDir, 'oldcustomfun.pyc') - if os.path.exists(tmpfile_pyc): - os.unlink(tmpfile_pyc) - os.unlink(tmpfile) - - fit.settheory('my fit theory') - fit.configure(d=4.5) - fit.estimate() - fit.runfit() - - self.assertEqual(fit.fit_results[0]["name"], - "A1") - self.assertAlmostEqual(fit.fit_results[0]["fitresult"], - 1.5) - self.assertEqual(fit.fit_results[1]["name"], - "B1") - self.assertAlmostEqual(fit.fit_results[1]["fitresult"], - 2.5) - self.assertEqual(fit.fit_results[2]["name"], - "C1") - self.assertAlmostEqual(fit.fit_results[2]["fitresult"], - 3.5) - - def testAddTheory(self, estimate=True): - """Test FitManager using a custom fit function imported with - FitManager.addtheory""" - # Create synthetic data with a sum of gaussian functions - x = numpy.arange(100).astype(numpy.float64) - - # a, b, c are the fit parameters - # d is a known scaling parameter that is set using configure() - a, b, c, d = -3.14, 1234.5, 10000, 4.5 - y = (a * x**2 + b * x + c) / d - - # Fitting - fit = fitmanager.FitManager() - fit.setdata(x=x, y=y) - - # Define and add the fit theory - CONFIG = {'d': 1.} - - def myfun(x_, a_, b_, c_): - """"Model function""" - return (a_ * x_**2 + b_ * x_ + c_) / CONFIG['d'] - - def myesti(x_, y_): - """"Initial parameters for iterative fit: - (a, b, c) = (1, 1, 1) - Constraints all set to 0 (FREE)""" - return (1., 1., 1.), ((0, 0, 0), (0, 0, 0), (0, 0, 0)) - - def myconfig(d_=1., **kw): - """This function can modify CONFIG""" - CONFIG["d"] = d_ - return CONFIG - - def myderiv(x_, parameters, index): - """Custom derivative""" - pars_plus = numpy.array(parameters, copy=True) - pars_plus[index] *= 1.001 - - pars_minus = numpy.array(parameters, copy=True) - pars_minus[index] *= 0.999 - - delta_fun = myfun(x_, *pars_plus) - myfun(x_, *pars_minus) - delta_par = parameters[index] * 0.001 * 2 - - return delta_fun / delta_par - - fit.addtheory("polynomial", - FitTheory(function=myfun, - parameters=["A", "B", "C"], - estimate=myesti if estimate else None, - configure=myconfig, - derivative=myderiv)) - - fit.settheory('polynomial') - fit.configure(d_=4.5) - fit.estimate() - params1, sigmas, infodict = fit.runfit() - - self.assertEqual(fit.fit_results[0]["name"], - "A1") - self.assertAlmostEqual(fit.fit_results[0]["fitresult"], - -3.14) - self.assertEqual(fit.fit_results[1]["name"], - "B1") - # params1[1] is the same as fit.fit_results[1]["fitresult"] - self.assertAlmostEqual(params1[1], - 1234.5) - self.assertEqual(fit.fit_results[2]["name"], - "C1") - self.assertAlmostEqual(params1[2], - 10000) - - # change configuration scaling factor and check that the fit returns - # different values - fit.configure(d_=5.) - fit.estimate() - params2, sigmas, infodict = fit.runfit() - for p1, p2 in zip(params1, params2): - self.assertFalse(numpy.array_equal(p1, p2), - "Fit parameters are equal even though the " + - "configuration has been changed") - - def testNoEstimate(self): - """Ensure that the in the absence of the estimation function, - the default estimation function :meth:`FitTheory.default_estimate` - is used.""" - self.testAddTheory(estimate=False) - - def testStep(self): - """Test fit manager on a step function with a more complex estimate - function than the gaussian (convolution filter)""" - for theory_name, theory_fun in (('Step Down', sum_stepdown), - ('Step Up', sum_stepup)): - # Create synthetic data with a sum of gaussian functions - x = numpy.arange(1000).astype(numpy.float64) - - # ('Height', 'Position', 'FWHM') - p = [1000, 439, 250] - - constantbg = 13 - y = theory_fun(x, *p) + constantbg - - # Fitting - fit = fitmanager.FitManager() - fit.setdata(x=x, y=y) - fit.loadtheories(fittheories) - fit.settheory(theory_name) - fit.setbackground('Constant') - - fit.estimate() - - params, sigmas, infodict = fit.runfit() - - # first parameter is the constant background - self.assertAlmostEqual(params[0], 13, places=5) - for i, param in enumerate(params[1:]): - self.assertAlmostEqual(param, p[i], places=5) - self.assertAlmostEqual(_order_of_magnitude(fit.fit_results[i+1]["estimation"]), - _order_of_magnitude(p[i])) - - -def quadratic(x, a, b, c): - return a * x**2 + b * x + c - - -def cubic(x, a, b, c, d): - return a * x**3 + b * x**2 + c * x + d - - -class TestPolynomials(unittest.TestCase): - """Test polynomial fit theories and fit background""" - def setUp(self): - self.x = numpy.arange(100).astype(numpy.float64) - - def testQuadraticBg(self): - gaussian_params = [100, 45, 8] - poly_params = [0.05, -2, 3] - p = numpy.poly1d(poly_params) - - y = p(self.x) + sum_gauss(self.x, *gaussian_params) - - fm = fitmanager.FitManager(self.x, y) - fm.loadbgtheories(bgtheories) - fm.loadtheories(fittheories) - fm.settheory("Gaussians") - fm.setbackground("Degree 2 Polynomial") - esti_params = fm.estimate() - fit_params = fm.runfit()[0] - - for p, pfit in zip(poly_params + gaussian_params, fit_params): - self.assertAlmostEqual(p, - pfit) - - def testCubicBg(self): - gaussian_params = [1000, 45, 8] - poly_params = [0.0005, -0.05, 3, -4] - p = numpy.poly1d(poly_params) - - y = p(self.x) + sum_gauss(self.x, *gaussian_params) - - fm = fitmanager.FitManager(self.x, y) - fm.loadtheories(fittheories) - fm.settheory("Gaussians") - fm.setbackground("Degree 3 Polynomial") - esti_params = fm.estimate() - fit_params = fm.runfit()[0] - - for p, pfit in zip(poly_params + gaussian_params, fit_params): - self.assertAlmostEqual(p, - pfit) - - def testQuarticcBg(self): - gaussian_params = [10000, 69, 25] - poly_params = [5e-10, 0.0005, 0.005, 2, 4] - p = numpy.poly1d(poly_params) - - y = p(self.x) + sum_gauss(self.x, *gaussian_params) - - fm = fitmanager.FitManager(self.x, y) - fm.loadtheories(fittheories) - fm.settheory("Gaussians") - fm.setbackground("Degree 4 Polynomial") - esti_params = fm.estimate() - fit_params = fm.runfit()[0] - - for p, pfit in zip(poly_params + gaussian_params, fit_params): - self.assertAlmostEqual(p, - pfit, - places=5) - - def _testPoly(self, poly_params, theory, places=5): - p = numpy.poly1d(poly_params) - - y = p(self.x) - - fm = fitmanager.FitManager(self.x, y) - fm.loadbgtheories(bgtheories) - fm.loadtheories(fittheories) - fm.settheory(theory) - esti_params = fm.estimate() - fit_params = fm.runfit()[0] - - for p, pfit in zip(poly_params, fit_params): - self.assertAlmostEqual(p, pfit, places=places) - - def testQuadratic(self): - self._testPoly([0.05, -2, 3], - "Degree 2 Polynomial") - - def testCubic(self): - self._testPoly([0.0005, -0.05, 3, -4], - "Degree 3 Polynomial") - - def testQuartic(self): - self._testPoly([1, -2, 3, -4, -5], - "Degree 4 Polynomial") - - def testQuintic(self): - self._testPoly([1, -2, 3, -4, -5, 6], - "Degree 5 Polynomial", - places=4) - - -test_cases = (TestFitmanager, TestPolynomials) - - -def suite(): - loader = unittest.defaultTestLoader - test_suite = unittest.TestSuite() - for test_class in test_cases: - tests = loader.loadTestsFromTestCase(test_class) - test_suite.addTests(tests) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/fit/test/test_functions.py b/silx/math/fit/test/test_functions.py deleted file mode 100644 index ce7dbd6..0000000 --- a/silx/math/fit/test/test_functions.py +++ /dev/null @@ -1,272 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ -""" -Tests for functions module -""" - -import unittest -import numpy -import math - -from silx.math.fit import functions - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "21/07/2016" - -class Test_functions(unittest.TestCase): - """ - Unit tests of multi-peak functions. - """ - def setUp(self): - self.x = numpy.arange(11) - - # height, center, sigma1, sigma2 - (h, c, s1, s2) = (7., 5., 3., 2.1) - self.g_params = { - "height": h, - "center": c, - #"sigma": s, - "fwhm1": 2 * math.sqrt(2 * math.log(2)) * s1, - "fwhm2": 2 * math.sqrt(2 * math.log(2)) * s2, - "area1": h * s1 * math.sqrt(2 * math.pi) - } - # result of `7 * scipy.signal.gaussian(11, 3)` - self.scipy_gaussian = numpy.array( - [1.74546546, 2.87778603, 4.24571462, 5.60516182, 6.62171628, - 7., 6.62171628, 5.60516182, 4.24571462, 2.87778603, - 1.74546546] - ) - - # result of: - # numpy.concatenate((7 * scipy.signal.gaussian(11, 3)[0:5], - # 7 * scipy.signal.gaussian(11, 2.1)[5:11])) - self.scipy_asym_gaussian = numpy.array( - [1.74546546, 2.87778603, 4.24571462, 5.60516182, 6.62171628, - 7., 6.24968751, 4.44773692, 2.52313452, 1.14093853, 0.41124877] - ) - - def tearDown(self): - pass - - def testGauss(self): - """Compare sum_gauss with scipy.signals.gaussian""" - y = functions.sum_gauss(self.x, - self.g_params["height"], - self.g_params["center"], - self.g_params["fwhm1"]) - - for i in range(11): - self.assertAlmostEqual(y[i], self.scipy_gaussian[i]) - - def testAGauss(self): - """Compare sum_agauss with scipy.signals.gaussian""" - y = functions.sum_agauss(self.x, - self.g_params["area1"], - self.g_params["center"], - self.g_params["fwhm1"]) - for i in range(11): - self.assertAlmostEqual(y[i], self.scipy_gaussian[i]) - - def testFastAGauss(self): - """Compare sum_fastagauss with scipy.signals.gaussian - Limit precision to 3 decimal places.""" - y = functions.sum_fastagauss(self.x, - self.g_params["area1"], - self.g_params["center"], - self.g_params["fwhm1"]) - for i in range(11): - self.assertAlmostEqual(y[i], self.scipy_gaussian[i], 3) - - - def testSplitGauss(self): - """Compare sum_splitgauss with scipy.signals.gaussian""" - y = functions.sum_splitgauss(self.x, - self.g_params["height"], - self.g_params["center"], - self.g_params["fwhm1"], - self.g_params["fwhm2"]) - for i in range(11): - self.assertAlmostEqual(y[i], self.scipy_asym_gaussian[i]) - - def testErf(self): - """Compare erf with math.erf""" - # scalars - self.assertAlmostEqual(functions.erf(0.14), math.erf(0.14), places=5) - self.assertAlmostEqual(functions.erf(0), math.erf(0), places=5) - self.assertAlmostEqual(functions.erf(-0.74), math.erf(-0.74), places=5) - - # lists - x = [-5, -2, -1.5, -0.6, 0, 0.1, 2, 3] - erfx = functions.erf(x) - for i in range(len(x)): - self.assertAlmostEqual(erfx[i], - math.erf(x[i]), - places=5) - - # ndarray - x = numpy.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]) - erfx = functions.erf(x) - for i in range(x.shape[0]): - for j in range(x.shape[1]): - self.assertAlmostEqual(erfx[i, j], - math.erf(x[i, j]), - places=5) - - def testErfc(self): - """Compare erf with math.erf""" - # scalars - self.assertAlmostEqual(functions.erfc(0.14), math.erfc(0.14), places=5) - self.assertAlmostEqual(functions.erfc(0), math.erfc(0), places=5) - self.assertAlmostEqual(functions.erfc(-0.74), math.erfc(-0.74), places=5) - - # lists - x = [-5, -2, -1.5, -0.6, 0, 0.1, 2, 3] - erfcx = functions.erfc(x) - for i in range(len(x)): - self.assertAlmostEqual(erfcx[i], math.erfc(x[i]), places=5) - - # ndarray - x = numpy.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]) - erfcx = functions.erfc(x) - for i in range(x.shape[0]): - for j in range(x.shape[1]): - self.assertAlmostEqual(erfcx[i, j], math.erfc(x[i, j]), places=5) - - def testAtanStepUp(self): - """Compare atan_stepup with math.atan - - atan_stepup(x, a, b, c) = a * (0.5 + (arctan((x - b) / c) / pi))""" - x0 = numpy.arange(100) / 6.33 - y0 = functions.atan_stepup(x0, 11.1, 22.2, 3.33) - - for x, y in zip(x0, y0): - self.assertAlmostEqual( - 11.1 * (0.5 + math.atan((x - 22.2) / 3.33) / math.pi), - y - ) - - def testStepUp(self): - """sanity check for step up: - - - derivative must be largest around the step center - - max value must be close to height parameter - - """ - x0 = numpy.arange(1000) - center = 444 - height = 1234 - fwhm = 210 - y0 = functions.sum_stepup(x0, height, center, fwhm) - - self.assertLess(max(y0), height) - self.assertAlmostEqual(max(y0), height, places=1) - self.assertAlmostEqual(min(y0), 0, places=1) - - deriv0 = _numerical_derivative(functions.sum_stepup, x0, [height, center, fwhm]) - - # Test center position within +- 1 sample of max derivative - index_max_deriv = numpy.argmax(deriv0) - self.assertLess(abs(index_max_deriv - center), - 1) - - def testStepDown(self): - """sanity check for step down: - - - absolute value of derivative must be largest around the step center - - max value must be close to height parameter - - """ - x0 = numpy.arange(1000) - center = 444 - height = 1234 - fwhm = 210 - y0 = functions.sum_stepdown(x0, height, center, fwhm) - - self.assertLess(max(y0), height) - self.assertAlmostEqual(max(y0), height, places=1) - self.assertAlmostEqual(min(y0), 0, places=1) - - deriv0 = _numerical_derivative(functions.sum_stepdown, x0, [height, center, fwhm]) - - # Test center position within +- 1 sample of max derivative - index_min_deriv = numpy.argmax(-deriv0) - self.assertLess(abs(index_min_deriv - center), - 1) - - def testSlit(self): - """sanity check for slit: - - - absolute value of derivative must be largest around the step center - - max value must be close to height parameter - - """ - x0 = numpy.arange(1000) - center = 444 - height = 1234 - fwhm = 210 - beamfwhm = 30 - y0 = functions.sum_slit(x0, height, center, fwhm, beamfwhm) - - self.assertAlmostEqual(max(y0), height, places=1) - self.assertAlmostEqual(min(y0), 0, places=1) - - deriv0 = _numerical_derivative(functions.sum_slit, x0, [height, center, fwhm, beamfwhm]) - - # Test step up center position (center - fwhm/2) within +- 1 sample of max derivative - index_max_deriv = numpy.argmax(deriv0) - self.assertLess(abs(index_max_deriv - (center - fwhm/2)), - 1) - # Test step down center position (center + fwhm/2) within +- 1 sample of min derivative - index_min_deriv = numpy.argmin(deriv0) - self.assertLess(abs(index_min_deriv - (center + fwhm/2)), - 1) - - -def _numerical_derivative(f, x, params=[], delta_factor=0.0001): - """Compute the numerical derivative of ``f`` for all values of ``x``. - - :param f: function - :param x: Array of evenly spaced abscissa values - :param params: list of additional parameters - :return: Array of derivative values - """ - deltax = (x[1] - x[0]) * delta_factor - y_plus = f(x + deltax, *params) - y_minus = f(x - deltax, *params) - - return (y_plus - y_minus) / (2 * deltax) - -test_cases = (Test_functions,) - -def suite(): - loader = unittest.defaultTestLoader - test_suite = unittest.TestSuite() - for test_class in test_cases: - tests = loader.loadTestsFromTestCase(test_class) - test_suite.addTests(tests) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/fit/test/test_peaks.py b/silx/math/fit/test/test_peaks.py deleted file mode 100644 index 17eb75d..0000000 --- a/silx/math/fit/test/test_peaks.py +++ /dev/null @@ -1,146 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ -""" -Tests for peaks module -""" - -import unittest -import numpy -import math - -from silx.math.fit import functions -from silx.math.fit import peaks - -class Test_peak_search(unittest.TestCase): - """ - Unit tests of peak_search on various types of multi-peak functions. - """ - def setUp(self): - self.x = numpy.arange(5000) - # (height1, center1, fwhm1, ...) - self.h_c_fwhm = (50, 500, 100, - 50, 600, 80, - 20, 2000, 100, - 50, 2250, 110, - 40, 3000, 99, - 23, 4980, 80) - # (height1, center1, fwhm1, eta1 ...) - self.h_c_fwhm_eta = (50, 500, 100, 0.4, - 50, 600, 80, 0.5, - 20, 2000, 100, 0.6, - 50, 2250, 110, 0.7, - 40, 3000, 99, 0.8, - 23, 4980, 80, 0.3,) - # (height1, center1, fwhm11, fwhm21, ...) - self.h_c_fwhm_fwhm = (50, 500, 100, 85, - 50, 600, 80, 110, - 20, 2000, 100, 100, - 50, 2250, 110, 99, - 40, 3000, 99, 110, - 23, 4980, 80, 80,) - # (height1, center1, fwhm11, fwhm21, eta1 ...) - self.h_c_fwhm_fwhm_eta = (50, 500, 100, 85, 0.4, - 50, 600, 80, 110, 0.5, - 20, 2000, 100, 100, 0.6, - 50, 2250, 110, 99, 0.7, - 40, 3000, 99, 110, 0.8, - 23, 4980, 80, 80, 0.3,) - # (area1, center1, fwhm1, ...) - self.a_c_fwhm = (2550, 500, 100, - 2000, 600, 80, - 500, 2000, 100, - 4000, 2250, 110, - 2300, 3000, 99, - 3333, 4980, 80) - # (area1, center1, fwhm1, eta1 ...) - self.a_c_fwhm_eta = (500, 500, 100, 0.4, - 500, 600, 80, 0.5, - 200, 2000, 100, 0.6, - 500, 2250, 110, 0.7, - 400, 3000, 99, 0.8, - 230, 4980, 80, 0.3,) - # (area, position, fwhm, st_area_r, st_slope_r, lt_area_r, lt_slope_r, step_height_r) - self.hypermet_params = (1000, 500, 200, 0.2, 100, 0.3, 100, 0.05, - 1000, 1000, 200, 0.2, 100, 0.3, 100, 0.05, - 1000, 2000, 200, 0.2, 100, 0.3, 100, 0.05, - 1000, 2350, 200, 0.2, 100, 0.3, 100, 0.05, - 1000, 3000, 200, 0.2, 100, 0.3, 100, 0.05, - 1000, 4900, 200, 0.2, 100, 0.3, 100, 0.05,) - - - def tearDown(self): - pass - - def get_peaks(self, function, params): - """ - - :param function: Multi-peak function - :param params: Parameter for this function - :return: list of (peak, relevance) tuples - """ - y = function(self.x, *params) - return peaks.peak_search(y=y, fwhm=100, relevance_info=True) - - def testPeakSearch_various_functions(self): - """Run peak search on a variety of synthetic functions, and - check that result falls within +-25 samples of the actual peak - (reasonable delta considering a fwhm of ~100 samples) and effects - of overlapping peaks).""" - f_p = ((functions.sum_gauss, self.h_c_fwhm ), - (functions.sum_lorentz, self.h_c_fwhm), - (functions.sum_pvoigt, self.h_c_fwhm_eta), - (functions.sum_splitgauss, self.h_c_fwhm_fwhm), - (functions.sum_splitlorentz, self.h_c_fwhm_fwhm), - (functions.sum_splitpvoigt, self.h_c_fwhm_fwhm_eta), - (functions.sum_agauss, self.a_c_fwhm), - (functions.sum_fastagauss, self.a_c_fwhm), - (functions.sum_alorentz, self.a_c_fwhm), - (functions.sum_apvoigt, self.a_c_fwhm_eta), - (functions.sum_ahypermet, self.hypermet_params), - (functions.sum_fastahypermet, self.hypermet_params),) - - for function, params in f_p: - peaks = self.get_peaks(function, params) - - self.assertEqual(len(peaks), 6, - "Wrong number of peaks detected") - - for i in range(6): - theoretical_peak_index = params[i*(len(params)//6) + 1] - found_peak_index = peaks[i][0] - self.assertLess(abs(found_peak_index - theoretical_peak_index), 25) - - -test_cases = (Test_peak_search,) - -def suite(): - loader = unittest.defaultTestLoader - test_suite = unittest.TestSuite() - for test_class in test_cases: - tests = loader.loadTestsFromTestCase(test_class) - test_suite.addTests(tests) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/histogram.py b/silx/math/histogram.py deleted file mode 100644 index af9ee68..0000000 --- a/silx/math/histogram.py +++ /dev/null @@ -1,593 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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 function and a class to compute multidimensional -histograms. - - -Classes -======= - -- :class:`Histogramnd` : multi dimensional histogram. -- :class:`HistogramndLut` : optimized to compute several histograms from data sharing the same coordinates. - -Examples -======== - -Single histogram ----------------- - -Given some 3D data: - ->>> import numpy as np ->>> shape = (10**7, 3) ->>> sample = np.random.random(shape) * 500 ->>> weights = np.random.random((shape[0],)) - -Computing the histogram with Histogramnd : - ->>> from silx.math import Histogramnd ->>> n_bins = 35 ->>> ranges = [[40., 150.], [-130., 250.], [0., 505]] ->>> histo, w_histo, edges = Histogramnd(sample, n_bins=n_bins, histo_range=ranges, weights=weights) - -Histogramnd can accumulate sets of data that don't have the same -coordinates : - ->>> from silx.math import Histogramnd ->>> histo_obj = Histogramnd(sample, n_bins=n_bins, histo_range=ranges, weights=weights) ->>> sample_2 = np.random.random(shape) * 200 ->>> weights_2 = np.random.random((shape[0],)) ->>> histo_obj.accumulate(sample_2, weights=weights_2) - -And then access the results: - ->>> histo = histo_obj.histo ->>> weighted_histo = histo_obj.weighted_histo - -or even: - ->>> histo, w_histo, edges = histo_obj - -Accumulating histograms (LUT) ------------------------------ -In some situations we need to compute the weighted histogram of several -sets of data (weights) that have the same coordinates (sample). - -Again, some data (2 sets of weights) : - ->>> import numpy as np ->>> shape = (10**7, 3) ->>> sample = np.random.random(shape) * 500 ->>> weights_1 = np.random.random((shape[0],)) ->>> weights_2 = np.random.random((shape[0],)) - -And getting the result with HistogramLut : - ->>> from silx.math import HistogramndLut - ->>> n_bins = 35 ->>> ranges = [[40., 150.], [-130., 250.], [0., 505]] - ->>> histo_lut = HistogramndLut(sample, ranges, n_bins) - -First call, with weight_1 : - ->>> histo_lut.accumulate(weights_1) - -Second call, with weight_2 : - ->>> histo_lut.accumulate(weights_2) - -Retrieving the results (this is a copy of what's actually stored in -this instance) : - ->>> histo = histo_lut.histo ->>> w_histo = histo_lut.weighted_histo - -Note that the following code gives the same result, but the -HistogramndLut instance does not store the accumulated weighted histogram. - -First call with weights_1 - ->>> histo, w_histo = histo_lut.apply_lut(weights_1) - -Second call with weights_2 - ->>> histo, w_histo = histo_lut.apply_lut(weights_2, histo=histo, weighted_histo=w_histo) - -Bin edges ---------- -When computing an histogram the caller is asked to provide the histogram -range along each coordinates (parameter *histo_range*). This parameter must -be given a [N, 2] array where N is the number of dimensions of the histogram. - -In other words, the caller must provide, for each dimension, -the left edge of the first (*leftmost*) bin, and the right edge of the -last (*rightmost*) bin. - -E.g. : for a 1D sample, for a histo_range equal to [0, 10] and n_bins=4, the -bins ranges will be : - -* [0, 2.5[, [2.5, 5[, [5, 7.5[, [7.5, 10 **[** if last_bin_closed = **False** -* [0, 2.5[, [2.5, 5[, [5, 7.5[, [7.5, 10 **]** if last_bin_closed = **True** - -.... -""" - -__authors__ = ["D. Naudet"] -__license__ = "MIT" -__date__ = "02/10/2017" - -import numpy as np -from .chistogramnd import chistogramnd as _chistogramnd # noqa -from .chistogramnd_lut import histogramnd_get_lut as _histo_get_lut -from .chistogramnd_lut import histogramnd_from_lut as _histo_from_lut - - -class Histogramnd(object): - """ - Computes the multidimensional histogram of some data. - """ - - def __init__(self, - sample, - histo_range, - n_bins, - weights=None, - weight_min=None, - weight_max=None, - last_bin_closed=False, - wh_dtype=None): - """ - :param sample: - The data to be histogrammed. - Its shape must be either - (N,) if it contains one dimensional coordinates, - or an (N,D) array where the rows are the - coordinates of points in a D dimensional space. - The following dtypes are supported : :class:`numpy.float64`, - :class:`numpy.float32`, :class:`numpy.int32`. - - .. warning:: if sample is not a C_CONTIGUOUS ndarray (e.g : a non - contiguous slice) then histogramnd will have to do make an internal - copy. - :type sample: :class:`numpy.array` - - :param histo_range: - A (N, 2) array containing the histogram range along each dimension, - where N is the sample's number of dimensions. - :type histo_range: array_like - - :param n_bins: - The number of bins : - * a scalar (same number of bins for all dimensions) - * a D elements array (number of bins for each dimensions) - :type n_bins: scalar or array_like - - :param weights: - A N elements numpy array of values associated with - each sample. - The values of the *weighted_histo* array - returned by the function are equal to the sum of - the weights associated with the samples falling - into each bin. - The following dtypes are supported : :class:`numpy.float64`, - :class:`numpy.float32`, :class:`numpy.int32`. - - .. note:: If None, the weighted histogram returned will be None. - :type weights: *optional*, :class:`numpy.array` - - :param weight_min: - Use this parameter to filter out all samples whose - weights are lower than this value. - - .. note:: This value will be cast to the same type - as *weights*. - :type weight_min: *optional*, scalar - - :param weight_max: - Use this parameter to filter out all samples whose - weights are higher than this value. - - .. note:: This value will be cast to the same type - as *weights*. - - :type weight_max: *optional*, scalar - - :param last_bin_closed: - By default the last bin is half - open (i.e.: [x,y) ; x included, y - excluded), like all the other bins. - Set this parameter to true if you want - the LAST bin to be closed. - :type last_bin_closed: *optional*, :class:`python.boolean` - - :param wh_dtype: type of the weighted histogram array. - If not provided, the weighted histogram array will contain values - of type numpy.double. Allowed values are : `numpy.double` and - `numpy.float32` - :type wh_dtype: *optional*, numpy data type - """ - - self.__histo_range = histo_range - self.__n_bins = n_bins - self.__last_bin_closed = last_bin_closed - self.__wh_dtype = wh_dtype - - if sample is None: - self.__data = [None, None, None] - else: - self.__data = _chistogramnd(sample, - self.__histo_range, - self.__n_bins, - weights=weights, - weight_min=weight_min, - weight_max=weight_max, - last_bin_closed=self.__last_bin_closed, - wh_dtype=self.__wh_dtype) - - def __getitem__(self, key): - """ - If necessary, results can be unpacked from an instance of Histogramnd : - *histogram*, *weighted histogram*, *bins edge*. - - Example : - - .. code-block:: python - - histo, w_histo, edges = Histogramnd(sample, histo_range, n_bins, weights) - - """ - return self.__data[key] - - def accumulate(self, - sample, - weights=None, - weight_min=None, - weight_max=None): - """ - Computes the multidimensional histogram of some data and accumulates it - into the histogram held by this instance of Histogramnd. - - :param sample: - The data to be histogrammed. - Its shape must be either - (N,) if it contains one dimensional coordinates, - or an (N,D) array where the rows are the - coordinates of points in a D dimensional space. - The following dtypes are supported : :class:`numpy.float64`, - :class:`numpy.float32`, :class:`numpy.int32`. - - .. warning:: if sample is not a C_CONTIGUOUS ndarray (e.g : a non - contiguous slice) then histogramnd will have to do make an internal - copy. - :type sample: :class:`numpy.array` - - :param weights: - A N elements numpy array of values associated with - each sample. - The values of the *weighted_histo* array - returned by the function are equal to the sum of - the weights associated with the samples falling - into each bin. - The following dtypes are supported : :class:`numpy.float64`, - :class:`numpy.float32`, :class:`numpy.int32`. - - .. note:: If None, the weighted histogram returned will be None. - :type weights: *optional*, :class:`numpy.array` - - :param weight_min: - Use this parameter to filter out all samples whose - weights are lower than this value. - - .. note:: This value will be cast to the same type - as *weights*. - :type weight_min: *optional*, scalar - - :param weight_max: - Use this parameter to filter out all samples whose - weights are higher than this value. - - .. note:: This value will be cast to the same type - as *weights*. - :type weight_max: *optional*, scalar - """ - result = _chistogramnd(sample, - self.__histo_range, - self.__n_bins, - weights=weights, - weight_min=weight_min, - weight_max=weight_max, - last_bin_closed=self.__last_bin_closed, - histo=self.__data[0], - weighted_histo=self.__data[1], - wh_dtype=self.__wh_dtype) - if self.__data[0] is None: - self.__data = result - elif self.__data[1] is None and result[1] is not None: - self.__data = result - - histo = property(lambda self: self[0]) - """ Histogram array, or None if this instance was initialized without - <sample> and accumulate has not been called yet. - - .. note:: this is a **reference** to the array store in this - Histogramnd instance, use with caution. - """ - weighted_histo = property(lambda self: self[1]) - """ Weighted Histogram, or None if this instance was initialized without - <sample>, or no weights have been passed to __init__ nor accumulate. - - .. note:: this is a **reference** to the array store in this - Histogramnd instance, use with caution. - """ - edges = property(lambda self: self[2]) - """ Bins edges, or None if this instance was initialized without - <sample> and accumulate has not been called yet. - """ - - -class HistogramndLut(object): - """ - The HistogramndLut class allows you to bin data onto a regular grid. - The use of HistogramndLut is interesting when several sets of data that - share the same coordinates (*sample*) have to be mapped onto the same grid. - """ - - def __init__(self, - sample, - histo_range, - n_bins, - last_bin_closed=False, - dtype=None): - """ - :param sample: - The coordinates of the data to be histogrammed. - Its shape must be either (N,) if it contains one dimensional - coordinates, or an (N, D) array where the rows are the - coordinates of points in a D dimensional space. - The following dtypes are supported : :class:`numpy.float64`, - :class:`numpy.float32`, :class:`numpy.int32`. - :type sample: :class:`numpy.array` - - :param histo_range: - A (N, 2) array containing the histogram range along each dimension, - where N is the sample's number of dimensions. - :type histo_range: array_like - - :param n_bins: - The number of bins : - * a scalar (same number of bins for all dimensions) - * a D elements array (number of bins for each dimensions) - :type n_bins: scalar or array_like - - :param dtype: data type of the weighted histogram. If None, the data type - will be the same as the first weights array provided (on first call of - the instance). - :type dtype: `numpy.dtype` - - :param last_bin_closed: - By default the last bin is half - open (i.e.: [x,y) ; x included, y - excluded), like all the other bins. - Set this parameter to true if you want - the LAST bin to be closed. - :type last_bin_closed: *optional*, :class:`python.boolean` - """ - lut, histo, edges = _histo_get_lut(sample, - histo_range, - n_bins, - last_bin_closed=last_bin_closed) - - self.__n_bins = np.array(histo.shape) - self.__histo_range = histo_range - self.__lut = lut - self.__histo = None - self.__weighted_histo = None - self.__edges = edges - self.__dtype = dtype - self.__shape = histo.shape - self.__last_bin_closed = last_bin_closed - self.clear() - - def clear(self): - """ - Resets the instance (zeroes the histograms). - """ - self.__weighted_histo = None - self.__histo = None - - @property - def lut(self): - """ - Copy of the Lut - """ - return self.__lut.copy() - - def histo(self, copy=True): - """ - Histogram (a copy of it), or None if `~accumulate` has not been called yet - (or clear was just called). - If *copy* is set to False then the actual reference to the array is - returned *(use with caution)*. - """ - if copy and self.__histo is not None: - return self.__histo.copy() - return self.__histo - - def weighted_histo(self, copy=True): - """ - Weighted histogram (a copy of it), or None if `~accumulate` has not been called yet - (or clear was just called). If *copy* is set to False then the actual - reference to the array is returned *(use with caution)*. - """ - if copy and self.__weighted_histo is not None: - return self.__weighted_histo.copy() - return self.__weighted_histo - - @property - def histo_range(self): - """ - Bins ranges. - """ - return self.__histo_range.copy() - - @property - def n_bins(self): - """ - Number of bins in each direction. - """ - return self.__n_bins.copy() - - @property - def bins_edges(self): - """ - Bins edges of the histograms, one array for each dimensions. - """ - return tuple([edges[:] for edges in self.__edges]) - - @property - def last_bin_closed(self): - """ - Returns True if the rightmost bin in each dimension is close (i.e : - values equal to the rightmost bin edge is included in the bin). - """ - return self.__last_bin_closed - - def accumulate(self, - weights, - weight_min=None, - weight_max=None): - """ - Computes the multidimensional histogram of some data and adds it to - the current histogram stored by this instance. The results can be - retrieved with the :attr:`~.histo` and :attr:`~.weighted_histo` - properties. - - :param weights: - A numpy array of values associated with each sample. The number of - elements in the array must be the same as the number of samples - provided at instantiation time. - :type histo_range: array_like - - :param weight_min: - Use this parameter to filter out all samples whose - weights are lower than this value. - - .. note:: This value will be cast to the same type - as *weights*. - :type weight_min: *optional*, scalar - - :param weight_max: - Use this parameter to filter out all samples whose - weights are higher than this value. - - .. note:: This value will be cast to the same type - as *weights*. - - :type weight_max: *optional*, scalar - """ - if self.__dtype is None: - self.__dtype = weights.dtype - - histo, w_histo = _histo_from_lut(weights, - self.__lut, - histo=self.__histo, - weighted_histo=self.__weighted_histo, - shape=self.__shape, - dtype=self.__dtype, - weight_min=weight_min, - weight_max=weight_max) - - if self.__histo is None: - self.__histo = histo - - if self.__weighted_histo is None: - self.__weighted_histo = w_histo - - def apply_lut(self, - weights, - histo=None, - weighted_histo=None, - weight_min=None, - weight_max=None): - """ - Computes the multidimensional histogram of some data and returns the - result (it is NOT added to the current histogram stored by this - instance). - - :param weights: - A numpy array of values associated with each sample. The number of - elements in the array must be the same as the number of samples - provided at instantiation time. - :type histo_range: array_like - - :param histo: - Use this parameter if you want to pass your - own histogram array instead of the one - created by this function. New values - will be added to this array. The returned array - will then be this one. - :type histo: *optional*, :class:`numpy.array` - - :param weighted_histo: - Use this parameter if you want to pass your - own weighted histogram array instead of - the created by this function. New - values will be added to this array. The returned array - will then be this one (same reference). - :type weighted_histo: *optional*, :class:`numpy.array` - - :param weight_min: - Use this parameter to filter out all samples whose - weights are lower than this value. - - .. note:: This value will be cast to the same type - as *weights*. - :type weight_min: *optional*, scalar - - :param weight_max: - Use this parameter to filter out all samples whose - weights are higher than this value. - - .. note:: This value will be cast to the same type - as *weights*. - :type weight_max: *optional*, scalar - """ - histo, w_histo = _histo_from_lut(weights, - self.__lut, - histo=histo, - weighted_histo=weighted_histo, - shape=self.__shape, - dtype=self.__dtype, - weight_min=weight_min, - weight_max=weight_max) - self.__dtype = w_histo.dtype - return histo, w_histo - -if __name__ == '__main__': - pass diff --git a/silx/math/histogramnd/include/histogramnd_c.h b/silx/math/histogramnd/include/histogramnd_c.h deleted file mode 100644 index abe464f..0000000 --- a/silx/math/histogramnd/include/histogramnd_c.h +++ /dev/null @@ -1,313 +0,0 @@ -/*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ - -#ifndef HISTOGRAMND_C_H -#define HISTOGRAMND_C_H - -/* checking for MSVC version because VS 2008 doesnt fully support C99 - so inttypes.h and stdint.h are not provided with the compiler. */ -#if defined(_MSC_VER) && _MSC_VER < 1600 - #include "msvc/stdint.h" -#else - #include <inttypes.h> -#endif - -#include "templates.h" - -/** Allowed flag values for the i_opt_flags arguments. - */ -typedef enum { - HISTO_NONE = 0, /**< No options. */ - HISTO_WEIGHT_MIN = 1, /**< Filter weights with i_weight_min. */ - HISTO_WEIGHT_MAX = 1<<1, /**< Filter weights with i_weight_max. */ - HISTO_LAST_BIN_CLOSED = 1<<2 /**< Last bin is closed. */ -} histo_opt_type; - -/** Return codees for the histogramnd function. - */ -typedef enum { - HISTO_OK = 0, /**< No error. */ - HISTO_ERR_ALLOC /**< Failed to allocate memory. */ -} histo_rc_t; - -/*===================== - * double sample, double cumul - * ==================== -*/ - -int histogramnd_double_double_double(double *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - double *o_cumul, - double *o_bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max); - -int histogramnd_double_float_double(double *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - double *o_cumul, - double *o_bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max); - -int histogramnd_double_int32_t_double(double *i_sample, - int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - double *o_cumul, - double *o_bin_edges, - int i_opt_flags, - int32_t i_weight_min, - int32_t i_weight_max); - -/*===================== - * float sample, double cumul - * ==================== -*/ -int histogramnd_float_double_double(float *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - double *o_cumul, - double *o_bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max); - -int histogramnd_float_float_double(float *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - double *o_cumul, - double *o_bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max); - -int histogramnd_float_int32_t_double(float *i_sample, - int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - double *o_cumul, - double *o_bin_edges, - int i_opt_flags, - int32_t i_weight_min, - int32_t i_weight_max); - -/*===================== - * int32_t sample, double cumul - * ==================== -*/ -int histogramnd_int32_t_double_double(int32_t *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - double *o_cumul, - double *o_bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max); - -int histogramnd_int32_t_float_double(int32_t *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - double *o_cumul, - double *o_bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max); - -int histogramnd_int32_t_int32_t_double(int32_t *i_sample, - int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - double *o_cumul, - double *o_bin_edges, - int i_opt_flags, - int32_t i_weight_min, - int32_t i_weight_max); - -/*===================== - * double sample, float cumul - * ==================== -*/ - -int histogramnd_double_double_float(double *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - float *o_cumul, - double *o_bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max); - -int histogramnd_double_float_float(double *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - float *o_cumul, - double *o_bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max); - -int histogramnd_double_int32_t_float(double *i_sample, - int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - float *o_cumul, - double *o_bin_edges, - int i_opt_flags, - int32_t i_weight_min, - int32_t i_weight_max); - -/*===================== - * float sample, float cumul - * ==================== -*/ -int histogramnd_float_double_float(float *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - float *o_cumul, - double *o_bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max); - -int histogramnd_float_float_float(float *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - float *o_cumul, - double *o_bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max); - -int histogramnd_float_int32_t_float(float *i_sample, - int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - float *o_cumul, - double *o_bin_edges, - int i_opt_flags, - int32_t i_weight_min, - int32_t i_weight_max); - -/*===================== - * int32_t sample, double cumul - * ==================== -*/ -int histogramnd_int32_t_double_float(int32_t *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - float *o_cumul, - double *o_bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max); - -int histogramnd_int32_t_float_float(int32_t *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - float *o_cumul, - double *o_bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max); - -int histogramnd_int32_t_int32_t_float(int32_t *i_sample, - int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - uint32_t *o_histo, - float *o_cumul, - double *o_bin_edges, - int i_opt_flags, - int32_t i_weight_min, - int32_t i_weight_max); - -#endif /* #define HISTOGRAMND_C_H */ diff --git a/silx/math/histogramnd/include/msvc/stdint.h b/silx/math/histogramnd/include/msvc/stdint.h deleted file mode 100644 index e236bb0..0000000 --- a/silx/math/histogramnd/include/msvc/stdint.h +++ /dev/null @@ -1,247 +0,0 @@ -// ISO C9x compliant stdint.h for Microsoft Visual Studio -// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 -// -// Copyright (c) 2006-2008 Alexander Chemeris -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. The name of the author may be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _MSC_VER // [ -#error "Use this header only with Microsoft Visual C++ compilers!" -#endif // _MSC_VER ] - -#ifndef _MSC_STDINT_H_ // [ -#define _MSC_STDINT_H_ - -#if _MSC_VER > 1000 -#pragma once -#endif - -#include <limits.h> - -// For Visual Studio 6 in C++ mode and for many Visual Studio versions when -// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}' -// or compiler give many errors like this: -// error C2733: second C linkage of overloaded function 'wmemchr' not allowed -#ifdef __cplusplus -extern "C" { -#endif -# include <wchar.h> -#ifdef __cplusplus -} -#endif - -// Define _W64 macros to mark types changing their size, like intptr_t. -#ifndef _W64 -# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 -# define _W64 __w64 -# else -# define _W64 -# endif -#endif - - -// 7.18.1 Integer types - -// 7.18.1.1 Exact-width integer types - -// Visual Studio 6 and Embedded Visual C++ 4 doesn't -// realize that, e.g. char has the same size as __int8 -// so we give up on __intX for them. -#if (_MSC_VER < 1300) - typedef char int8_t; - typedef short int16_t; - typedef int int32_t; - typedef unsigned char uint8_t; - typedef unsigned short uint16_t; - typedef unsigned int uint32_t; -#else - typedef __int8 int8_t; - typedef __int16 int16_t; - typedef __int32 int32_t; - typedef unsigned __int8 uint8_t; - typedef unsigned __int16 uint16_t; - typedef unsigned __int32 uint32_t; -#endif -typedef __int64 int64_t; -typedef unsigned __int64 uint64_t; - - -// 7.18.1.2 Minimum-width integer types -typedef int8_t int_least8_t; -typedef int16_t int_least16_t; -typedef int32_t int_least32_t; -typedef int64_t int_least64_t; -typedef uint8_t uint_least8_t; -typedef uint16_t uint_least16_t; -typedef uint32_t uint_least32_t; -typedef uint64_t uint_least64_t; - -// 7.18.1.3 Fastest minimum-width integer types -typedef int8_t int_fast8_t; -typedef int16_t int_fast16_t; -typedef int32_t int_fast32_t; -typedef int64_t int_fast64_t; -typedef uint8_t uint_fast8_t; -typedef uint16_t uint_fast16_t; -typedef uint32_t uint_fast32_t; -typedef uint64_t uint_fast64_t; - -// 7.18.1.4 Integer types capable of holding object pointers -#ifdef _WIN64 // [ - typedef __int64 intptr_t; - typedef unsigned __int64 uintptr_t; -#else // _WIN64 ][ - typedef _W64 int intptr_t; - typedef _W64 unsigned int uintptr_t; -#endif // _WIN64 ] - -// 7.18.1.5 Greatest-width integer types -typedef int64_t intmax_t; -typedef uint64_t uintmax_t; - - -// 7.18.2 Limits of specified-width integer types - -#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 - -// 7.18.2.1 Limits of exact-width integer types -#define INT8_MIN ((int8_t)_I8_MIN) -#define INT8_MAX _I8_MAX -#define INT16_MIN ((int16_t)_I16_MIN) -#define INT16_MAX _I16_MAX -#define INT32_MIN ((int32_t)_I32_MIN) -#define INT32_MAX _I32_MAX -#define INT64_MIN ((int64_t)_I64_MIN) -#define INT64_MAX _I64_MAX -#define UINT8_MAX _UI8_MAX -#define UINT16_MAX _UI16_MAX -#define UINT32_MAX _UI32_MAX -#define UINT64_MAX _UI64_MAX - -// 7.18.2.2 Limits of minimum-width integer types -#define INT_LEAST8_MIN INT8_MIN -#define INT_LEAST8_MAX INT8_MAX -#define INT_LEAST16_MIN INT16_MIN -#define INT_LEAST16_MAX INT16_MAX -#define INT_LEAST32_MIN INT32_MIN -#define INT_LEAST32_MAX INT32_MAX -#define INT_LEAST64_MIN INT64_MIN -#define INT_LEAST64_MAX INT64_MAX -#define UINT_LEAST8_MAX UINT8_MAX -#define UINT_LEAST16_MAX UINT16_MAX -#define UINT_LEAST32_MAX UINT32_MAX -#define UINT_LEAST64_MAX UINT64_MAX - -// 7.18.2.3 Limits of fastest minimum-width integer types -#define INT_FAST8_MIN INT8_MIN -#define INT_FAST8_MAX INT8_MAX -#define INT_FAST16_MIN INT16_MIN -#define INT_FAST16_MAX INT16_MAX -#define INT_FAST32_MIN INT32_MIN -#define INT_FAST32_MAX INT32_MAX -#define INT_FAST64_MIN INT64_MIN -#define INT_FAST64_MAX INT64_MAX -#define UINT_FAST8_MAX UINT8_MAX -#define UINT_FAST16_MAX UINT16_MAX -#define UINT_FAST32_MAX UINT32_MAX -#define UINT_FAST64_MAX UINT64_MAX - -// 7.18.2.4 Limits of integer types capable of holding object pointers -#ifdef _WIN64 // [ -# define INTPTR_MIN INT64_MIN -# define INTPTR_MAX INT64_MAX -# define UINTPTR_MAX UINT64_MAX -#else // _WIN64 ][ -# define INTPTR_MIN INT32_MIN -# define INTPTR_MAX INT32_MAX -# define UINTPTR_MAX UINT32_MAX -#endif // _WIN64 ] - -// 7.18.2.5 Limits of greatest-width integer types -#define INTMAX_MIN INT64_MIN -#define INTMAX_MAX INT64_MAX -#define UINTMAX_MAX UINT64_MAX - -// 7.18.3 Limits of other integer types - -#ifdef _WIN64 // [ -# define PTRDIFF_MIN _I64_MIN -# define PTRDIFF_MAX _I64_MAX -#else // _WIN64 ][ -# define PTRDIFF_MIN _I32_MIN -# define PTRDIFF_MAX _I32_MAX -#endif // _WIN64 ] - -#define SIG_ATOMIC_MIN INT_MIN -#define SIG_ATOMIC_MAX INT_MAX - -#ifndef SIZE_MAX // [ -# ifdef _WIN64 // [ -# define SIZE_MAX _UI64_MAX -# else // _WIN64 ][ -# define SIZE_MAX _UI32_MAX -# endif // _WIN64 ] -#endif // SIZE_MAX ] - -// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h> -#ifndef WCHAR_MIN // [ -# define WCHAR_MIN 0 -#endif // WCHAR_MIN ] -#ifndef WCHAR_MAX // [ -# define WCHAR_MAX _UI16_MAX -#endif // WCHAR_MAX ] - -#define WINT_MIN 0 -#define WINT_MAX _UI16_MAX - -#endif // __STDC_LIMIT_MACROS ] - - -// 7.18.4 Limits of other integer types - -#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 - -// 7.18.4.1 Macros for minimum-width integer constants - -#define INT8_C(val) val##i8 -#define INT16_C(val) val##i16 -#define INT32_C(val) val##i32 -#define INT64_C(val) val##i64 - -#define UINT8_C(val) val##ui8 -#define UINT16_C(val) val##ui16 -#define UINT32_C(val) val##ui32 -#define UINT64_C(val) val##ui64 - -// 7.18.4.2 Macros for greatest-width integer constants -#define INTMAX_C INT64_C -#define UINTMAX_C UINT64_C - -#endif // __STDC_CONSTANT_MACROS ] - - -#endif // _MSC_STDINT_H_ ] diff --git a/silx/math/histogramnd/include/templates.h b/silx/math/histogramnd/include/templates.h deleted file mode 100644 index 490eed3..0000000 --- a/silx/math/histogramnd/include/templates.h +++ /dev/null @@ -1,30 +0,0 @@ -/*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ - -#ifndef TEMPLATES_H_ -#define TEMPLATES_H_ - -#define CONCAT(X,Y,Z,T) X##_##Y##_##Z##_##T -#define TEMPLATE(X,Y,Z,T) CONCAT(X,Y,Z,T) - -#endif diff --git a/silx/math/histogramnd/src/histogramnd_c.c b/silx/math/histogramnd/src/histogramnd_c.c deleted file mode 100644 index fc9d77e..0000000 --- a/silx/math/histogramnd/src/histogramnd_c.c +++ /dev/null @@ -1,301 +0,0 @@ -/*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ - -#include "histogramnd_c.h" - -/*===================== - * double sample, double cumul - * ===================== -*/ -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T double -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T double -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T double -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T double -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T float -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T double -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T double -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T int32_t -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T double -#include "histogramnd_template.c" - -/*===================== - * float sample, double cumul - * ===================== -*/ -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T float -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T double -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T double -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T float -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T float -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T double -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T float -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T int32_t -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T double -#include "histogramnd_template.c" - -/*===================== - * int32_t sample, double cumul - * ===================== -*/ -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T int32_t -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T double -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T double -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T int32_t -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T float -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T double -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T int32_t -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T int32_t -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T double -#include "histogramnd_template.c" - - -/*===================== - * double sample, float cumul - * ===================== -*/ -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T double -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T double -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T float -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T double -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T float -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T float -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T double -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T int32_t -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T float -#include "histogramnd_template.c" - -/*===================== - * float sample, float cumul - * ===================== -*/ -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T float -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T double -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T float -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T float -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T float -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T float -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T float -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T int32_t -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T float -#include "histogramnd_template.c" - -/*===================== - * int32_t sample, float cumul - * ===================== -*/ -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T int32_t -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T double -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T float -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T int32_t -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T float -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T float -#include "histogramnd_template.c" - -#ifdef HISTO_SAMPLE_T -#undef HISTO_SAMPLE_T -#endif -#define HISTO_SAMPLE_T int32_t -#ifdef HISTO_WEIGHT_T -#undef HISTO_WEIGHT_T -#endif -#define HISTO_WEIGHT_T int32_t -#ifdef HISTO_CUMUL_T -#undef HISTO_CUMUL_T -#endif -#define HISTO_CUMUL_T float -#include "histogramnd_template.c" diff --git a/silx/math/histogramnd/src/histogramnd_template.c b/silx/math/histogramnd/src/histogramnd_template.c deleted file mode 100644 index 0276bb4..0000000 --- a/silx/math/histogramnd/src/histogramnd_template.c +++ /dev/null @@ -1,260 +0,0 @@ -/*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ - -#include "templates.h" - -#include <stdio.h> -#include <stdlib.h> -#include <math.h> -#include <stdarg.h> - -#ifdef HISTO_SAMPLE_T -#ifdef HISTO_WEIGHT_T -#ifdef HISTO_CUMUL_T - -int TEMPLATE(histogramnd, HISTO_SAMPLE_T, HISTO_WEIGHT_T, HISTO_CUMUL_T) - (HISTO_SAMPLE_T *i_sample, - HISTO_WEIGHT_T *i_weights, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bins, - uint32_t *o_histo, - HISTO_CUMUL_T *o_cumul, - double *o_bin_edges, - int i_opt_flags, - HISTO_WEIGHT_T i_weight_min, - HISTO_WEIGHT_T i_weight_max) -{ - /* some counters */ - int i = 0, j = 0; - long elem_idx = 0; - - HISTO_WEIGHT_T * weight_ptr = 0; - HISTO_SAMPLE_T elem_coord = 0.; - - /* computed bin index (i_sample -> grid) */ - long bin_idx = 0; - - double * g_min = 0; - double * g_max = 0; - double * range = 0; - - /* ================================ - * Parsing options, if any. - * ================================ - */ - - int filt_min_weight = 0; - int filt_max_weight = 0; - int last_bin_closed = 0; - - /* Testing the option flags */ - if(i_opt_flags & HISTO_WEIGHT_MIN) - { - filt_min_weight = 1; - } - - if(i_opt_flags & HISTO_WEIGHT_MAX) - { - filt_max_weight = 1; - } - - if(i_opt_flags & HISTO_LAST_BIN_CLOSED) - { - last_bin_closed = 1; - } - - /* storing the min & max bin coordinates in their own arrays because - * i_bin_ranges = [[min0, max0], [min1, max1], ...] - * (mostly for the sake of clarity) - * (maybe faster access too?) - */ - g_min = (double *) malloc(i_n_dim *sizeof(double)); - g_max = (double *) malloc(i_n_dim * sizeof(double)); - /* range used to convert from i_coords to bin indices in the grid */ - range = (double *) malloc(i_n_dim * sizeof(double)); - - if(!g_min || !g_max || !range) - { - free(g_min); - free(g_max); - free(range); - return HISTO_ERR_ALLOC; - } - - j = 0; - for(i=0; i<i_n_dim; i++) - { - g_min[i] = i_bin_ranges[i*2]; - g_max[i] = i_bin_ranges[i*2+1]; - range[i] = g_max[i]-g_min[i]; - - for(bin_idx=0; bin_idx<i_n_bins[i]; j++, bin_idx++) - { - o_bin_edges[j] = g_min[i] + - bin_idx * (range[i] / i_n_bins[i]); - } - o_bin_edges[j++] = g_max[i]; - } - - weight_ptr = i_weights; - - if(!i_weights) - { - /* if weights are not provided there no point in trying to filter them - * (!! careful if you change this, some code below relies on it !!) - */ - filt_min_weight = 0; - filt_max_weight = 0; - - /* If the weights array is not provided then there is no point - * updating the weighted histogram, only the bin counts (o_histo) - * will be filled. - * (!! careful if you change this, some code below relies on it !!) - */ - o_cumul = 0; - } - - /* tried to use pointers instead of indices here, but it didn't - * seem any faster (probably because the compiler - * optimizes stuff anyway), - * so i'm keeping the "indices" version, for the sake of clarity - */ - for(elem_idx=0; - elem_idx<i_n_elem*i_n_dim; - elem_idx+=i_n_dim, weight_ptr++) - { - /* no testing the validity of weight_ptr here, because if it is NULL - * then filt_min_weight/filt_max_weight will be 0. - * (see code above) - */ - if(filt_min_weight && *weight_ptr<i_weight_min) - { - continue; - } - if(filt_max_weight && *weight_ptr>i_weight_max) - { - continue; - } - - bin_idx = 0; - - for(i=0; i<i_n_dim; i++) - { - elem_coord = i_sample[elem_idx+i]; - - /* ===================== - * Element is rejected if any of the following is NOT true : - * 1. coordinate is >= than the minimum value - * 2. coordinate is <= than the maximum value - * 3. coordinate==maximum value and last_bin_closed is True - * ===================== - */ - if(elem_coord<g_min[i]) - { - bin_idx = -1; - break; - } - - /* Here we make the assumption that most of the time - * there will be more coordinates inside the grid interval - * (one test) - * than coordinates higher or equal to the max - * (two tests) - */ - if(elem_coord<g_max[i]) - { - /* Warning : the following factorization seems to - * increase the effect of precision error. - * bin_idx = (long)floor( - * (bin_idx + - * (elem_coord-g_min[i])/range[i]) * - * i_n_bins[i] - * ); - */ - - /* Not using floor to speed up things. - * We don't (?) need all the error checking provided by - * the built-in floor(). - * Also the value is supposed to be always positive. - */ - bin_idx = bin_idx * i_n_bins[i] + - (long)( - ((elem_coord-g_min[i]) * i_n_bins[i]) / - range[i] - ); - } - else /* ===> elem_coord>=g_max[i] */ - { - /* if equal and the last bin is closed : - * put it in the last bin - * else : discard - */ - if(last_bin_closed && elem_coord==g_max[i]) - { - bin_idx = (bin_idx + 1) * i_n_bins[i] - 1; - } - else - { - bin_idx = -1; - break; - } - } /* if(elem_coord<g_max[i]) */ - - } /* for(i=0; i<i_n_dim; i++) */ - - /* element is out of the grid */ - if(bin_idx==-1) - { - continue; - } - - if(o_histo) - { - o_histo[bin_idx] += 1; - } - if(o_cumul) - { - /* not testing the pointer since o_cumul is null if - * i_weights is null. - */ - o_cumul[bin_idx] += (HISTO_CUMUL_T) *weight_ptr; - } - - } /* for(elem_idx=0; elem_idx<i_n_elem*i_n_dim; elem_idx+=i_n_dim) */ - - free(g_min); - free(g_max); - free(range); - - /* For now just returning 0 (OK) since all the checks are done in - * python. This might change later if people want to call this - * function directly from C (might have to implement error codes). - */ - return HISTO_OK; -} - -#endif -#endif -#endif diff --git a/silx/math/histogramnd_c.pxd b/silx/math/histogramnd_c.pxd deleted file mode 100644 index 35db529..0000000 --- a/silx/math/histogramnd_c.pxd +++ /dev/null @@ -1,299 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-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. -# -# ############################################################################*/ - -__authors__ = ["D. Naudet"] -__license__ = "MIT" -__date__ = "01/02/2016" - -cimport numpy as cnumpy - -cdef extern from "histogramnd_c.h": - - ctypedef enum histo_opt_type: - HISTO_NONE - HISTO_WEIGHT_MIN - HISTO_WEIGHT_MAX - HISTO_LAST_BIN_CLOSED - - ctypedef enum histo_rc_t: - HISTO_OK - HISTO_ERR_ALLOC - - # ===================== - # double sample, double cumul - # ===================== - - int histogramnd_double_double_double(double *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - double *o_cumul, - double * bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max) nogil - - int histogramnd_double_float_double(double *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - double *o_cumul, - double * bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max) nogil - - int histogramnd_double_int32_t_double(double *i_sample, - cnumpy.int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - double *o_cumul, - double * bin_edges, - int i_opt_flags, - cnumpy.int32_t i_weight_min, - cnumpy.int32_t i_weight_max) nogil - - # ===================== - # float sample, double cumul - # ===================== - - int histogramnd_float_double_double(float *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - double *o_cumul, - double * bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max) nogil - - int histogramnd_float_float_double(float *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - double *o_cumul, - double * bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max) nogil - - int histogramnd_float_int32_t_double(float *i_sample, - cnumpy.int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - double *o_cumul, - double * bin_edges, - int i_opt_flags, - cnumpy.int32_t i_weight_min, - cnumpy.int32_t i_weight_max) nogil - - # ===================== - # numpy.int32_t sample, double cumul - # ===================== - - int histogramnd_int32_t_double_double(cnumpy.int32_t *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - double *o_cumul, - double * bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max) nogil - - int histogramnd_int32_t_float_double(cnumpy.int32_t *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - double *o_cumul, - double * bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max) nogil - - int histogramnd_int32_t_int32_t_double(cnumpy.int32_t *i_sample, - cnumpy.int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - double *o_cumul, - double * bin_edges, - int i_opt_flags, - cnumpy.int32_t i_weight_min, - cnumpy.int32_t i_weight_max) nogil - - # ===================== - # double sample, float cumul - # ===================== - - int histogramnd_double_double_float(double *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - float *o_cumul, - double * bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max) nogil - - int histogramnd_double_float_float(double *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - float *o_cumul, - double * bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max) nogil - - int histogramnd_double_int32_t_float(double *i_sample, - cnumpy.int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - float *o_cumul, - double * bin_edges, - int i_opt_flags, - cnumpy.int32_t i_weight_min, - cnumpy.int32_t i_weight_max) nogil - - # ===================== - # float sample, float cumul - # ===================== - - int histogramnd_float_double_float(float *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - float *o_cumul, - double * bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max) nogil - - int histogramnd_float_float_float(float *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - float *o_cumul, - double * bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max) nogil - - int histogramnd_float_int32_t_float(float *i_sample, - cnumpy.int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - float *o_cumul, - double * bin_edges, - int i_opt_flags, - cnumpy.int32_t i_weight_min, - cnumpy.int32_t i_weight_max) nogil - - # ===================== - # numpy.int32_t sample, float cumul - # ===================== - - int histogramnd_int32_t_double_float(cnumpy.int32_t *i_sample, - double *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - float *o_cumul, - double * bin_edges, - int i_opt_flags, - double i_weight_min, - double i_weight_max) nogil - - int histogramnd_int32_t_float_float(cnumpy.int32_t *i_sample, - float *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - float *o_cumul, - double * bin_edges, - int i_opt_flags, - float i_weight_min, - float i_weight_max) nogil - - int histogramnd_int32_t_int32_t_float(cnumpy.int32_t *i_sample, - cnumpy.int32_t *i_weigths, - int i_n_dim, - int i_n_elem, - double *i_bin_ranges, - int *i_n_bin, - cnumpy.uint32_t *o_histo, - float *o_cumul, - double * bin_edges, - int i_opt_flags, - cnumpy.int32_t i_weight_min, - cnumpy.int32_t i_weight_max) nogil diff --git a/silx/math/include/math_compatibility.h b/silx/math/include/math_compatibility.h deleted file mode 100644 index 3d69c0c..0000000 --- a/silx/math/include/math_compatibility.h +++ /dev/null @@ -1,53 +0,0 @@ -# /*########################################################################## -# -# 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 header provides libc math functions and macros across platforms. - - Needed as VisualStudio 2008 (i.e., Python2.7) is missing some functions/macros. -*/ - -#ifndef __MATH_COMPATIBILITY_H__ -#define __MATH_COMPATIBILITY_H__ - -#include <math.h> - -#ifndef INFINITY -#define INFINITY (DBL_MAX+DBL_MAX) -#endif - -#ifndef NAN -#define NAN (INFINITY-INFINITY) -#endif - -#if (defined (_MSC_VER) && _MSC_VER < 1800) -#include <float.h> - -/* Make sure asinh returns -inf rather than NaN for v=-inf */ -#define asinh(v) (v == -INFINITY ? v : log((v) + sqrt((v)*(v) + 1))) - -#define isnan(v) _isnan(v) -#define isfinite(v) _finite(v) -#define lrint(v) ((long int) (v)) -#endif - -#endif /*__MATH_COMPATIBILITY_H__*/ diff --git a/silx/math/interpolate.pyx b/silx/math/interpolate.pyx deleted file mode 100644 index c79224a..0000000 --- a/silx/math/interpolate.pyx +++ /dev/null @@ -1,165 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2019 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 :func:`interp3d` to perform trilinear interpolation. -""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "11/07/2019" - - -import cython -from cython.parallel import prange -import numpy - -cimport cython -from libc.math cimport floor -cimport numpy as cnumpy - - -ctypedef fused _floating: - float - double - -ctypedef fused _floating_pts: - float - double - - -@cython.initializedcheck(False) -@cython.boundscheck(False) -@cython.wraparound(False) -cdef inline double trilinear_interpolation( - _floating[:, :, :] values, - _floating_pts pos0, - _floating_pts pos1, - _floating_pts pos2, - double fill_value) nogil: - """Evaluate the trilinear interpolation at a given position - - :param values: 3D dataset from which to do the interpolation - :param pos0: Dimension 0 coordinate at which to evaluate the interpolation - :param pos1: Dimension 1 coordinate at which to evaluate the interpolation - :param pos2: Dimension 2 coordinate at which to evaluate the interpolation - :param fill_value: Value to return for points outside data - """ - cdef: - int i0, i1, i2 # Indices - int i0_plus1, i1_plus1, i2_plus1 # Indices+1 - double delta - double c00, c01, c10, c11, c0, c1 - double c - - if (pos0 < 0. or pos0 > (values.shape[0] -1) or - pos1 < 0. or pos1 > (values.shape[1] -1) or - pos2 < 0. or pos2 > (values.shape[2] -1)): - return fill_value - - i0 = < int > floor(pos0) - i1 = < int > floor(pos1) - i2 = < int > floor(pos2) - - # Clip i+1 indices to data volume - # In this case, corresponding dX is 0. - i0_plus1 = min(i0 + 1, values.shape[0] - 1) - i1_plus1 = min(i1 + 1, values.shape[1] - 1) - i2_plus1 = min(i2 + 1, values.shape[2] - 1) - - if pos2 == i2: # Avoids multiplication by 0 (which yields to NaN with inf) - c00 = <double> values[i0, i1, i2] - c10 = <double> values[i0, i1_plus1, i2] - c01 = <double> values[i0_plus1, i1, i2] - c11 = <double> values[i0_plus1, i1_plus1, i2] - else: - delta = pos2 - i2 - c00 = (<double> values[i0, i1, i2]) * (1. - delta) + (<double> values[i0, i1, i2_plus1]) * delta - c10 = (<double> values[i0, i1_plus1, i2]) * (1. - delta) + (<double> values[i0, i1_plus1, i2_plus1]) * delta - c01 = (<double> values[i0_plus1, i1, i2]) * (1. - delta) + (<double> values[i0_plus1, i1, i2_plus1]) * delta - c11 = (<double> values[i0_plus1, i1_plus1, i2]) * (1. - delta) + (<double> values[i0_plus1, i1_plus1, i2_plus1]) * delta - - if pos1 == i1: # Avoids multiplication by 0 (which yields to NaN with inf) - c0 = c00 - c1 = c01 - else: - delta = pos1 - i1 - c0 = c00 * (1. - delta) + c10 * delta - c1 = c01 * (1. - delta) + c11 * delta - - if pos0 == i0: # Avoids multiplication by 0 (which yields to NaN with inf) - c = c0 - else: - delta = pos0 - i0 - c = c0 * (1 - delta) + c1 * delta - - return c - - -@cython.boundscheck(False) -@cython.wraparound(False) -def interp3d(_floating[:, :, :] values not None, - _floating_pts[:, :] xi not None, - str method='linear', - double fill_value=numpy.nan): - """Trilinear interpolation in a regular grid. - - Perform trilinear interpolation of the 3D dataset at given points - - :param numpy.ndarray values: 3D dataset of floating point values - :param numpy.ndarray xi: (N, 3) sampling points - :param str method: Interpolation method to use in: - - 'linear': Trilinear interpolation - - 'linear_omp': Trilinear interpolation with OpenMP parallelism - :param float fill_value: - Value to use for points outside the volume (default: nan) - :return: Values evaluated at given input points. - :rtype: numpy.ndarray - """ - if _floating is cnumpy.float32_t: - dtype = numpy.float32 - elif _floating is cnumpy.float64_t: - dtype = numpy.float64 - else: # This should not happen - raise ValueError("Unsupported input dtype") - - cdef: - int npoints = xi.shape[0] - _floating[:] result = numpy.empty((npoints,), dtype=dtype) - int index - double c_fill_value = fill_value - - if method == 'linear': - with nogil: - for index in range(npoints): - result[index] = < _floating > trilinear_interpolation( - values, xi[index, 0], xi[index, 1], xi[index, 2], c_fill_value) - - elif method == 'linear_omp': - for index in prange(npoints, nogil=True): - result[index] = < _floating > trilinear_interpolation( - values, xi[index, 0], xi[index, 1], xi[index, 2], c_fill_value) - else: - raise ValueError("Unsupported method: %s" % method) - - return numpy.array(result, copy=False)
\ No newline at end of file diff --git a/silx/math/marchingcubes.pyx b/silx/math/marchingcubes.pyx deleted file mode 100644 index 0409691..0000000 --- a/silx/math/marchingcubes.pyx +++ /dev/null @@ -1,246 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2015-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 marching cubes implementation. - -It provides a :class:`MarchingCubes` class allowing to build an isosurface -from data provided as a 3D data set or slice by slice. -""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "16/08/2017" - - -import numpy -cimport numpy as cnumpy -cimport cython - -cimport silx.math.mc as mc - - -# From numpy_common.pxi to avoid warnings while compiling C code -# See this thread: -# https://mail.python.org/pipermail//cython-devel/2012-March/002137.html -cdef extern from *: - bint FALSE "0" - void import_array() - void import_umath() - -if FALSE: - import_array() - import_umath() - - -cdef class MarchingCubes: - """Compute isosurface using marching cubes algorithm. - - It builds a surface from a 3D scalar dataset as a 3D contour at a - given value. - The resulting surface is not topologically correct. - - See: http://paulbourke.net/geometry/polygonise/ - - Lorensen, W. E. and Cline, H. E. Marching cubes: A high resolution 3D - surface construction algorithm. Computer Graphics, 21, 4 (July 1987). - ACM, 163-169. - - Generated vertex and normal coordinates are in the same order - as input array, i.e., (dim 0, dim 1, dim 2). - - Expected indices in memory of a (2, 2, 2) dataset: - - dim 0 (depth) - | - | - 4 +------+ 5 - /| /| - / | / | - 6 +------+ 7| - | | | | - |0 +---|--+ 1 --- dim 2 (width) - | / | / - |/ |/ - 2 +------+ 3 - / - / - dim 1 (height) - - Example with a 3D data set: - - >>> vertices, normals, indices = MarchingCubes(data, isolevel=1.) - - Example of code for processing a list of images: - - >>> mc = MarchingCubes(isolevel=1.) # Create object with iso-level=1 - >>> previous_image = images[0] - >>> for image in images[1:]: - ... mc.process_image(previous_image, image) # Process one slice - ... previous_image = image - - >>> vertices = mc.get_vertices() # Array of vertex positions - >>> normals = mc.get_normals() # Array of normals - >>> triangle_indices = mc.get_indices() # Array of indices of vertices - - :param data: 3D dataset of float32 or None - :type data: numpy.ndarray of float32 of dimension 3 - :param float isolevel: The value for which to generate the isosurface - :param bool invert_normals: - True (default) for normals oriented in direction of gradient descent - :param sampling: Sampling along each dimension (depth, height, width) - """ - cdef mc.MarchingCubes[float, float] * c_mc # Pointer to the C++ instance - - def __cinit__(self, data=None, isolevel=None, - invert_normals=True, sampling=(1, 1, 1)): - self.c_mc = new mc.MarchingCubes[float, float](isolevel) - self.c_mc.invert_normals = bool(invert_normals) - self.c_mc.sampling[0] = sampling[0] - self.c_mc.sampling[1] = sampling[1] - self.c_mc.sampling[2] = sampling[2] - - if data is not None: - self.process(data) - - def __dealloc__(self): - del self.c_mc - - def __getitem__(self, key): - """Allows one to unpack object as a single liner: - - vertices, normals, indices = MarchingCubes(...) - """ - if key == 0: - return self.get_vertices() - elif key == 1: - return self.get_normals() - elif key == 2: - return self.get_indices() - else: - raise IndexError("Index out of range") - - def process(self, data): - """Compute an isosurface from a 3D scalar field. - - This builds vertices, normals and indices arrays. - Vertices and normals coordinates are in the same order as input array, - i.e., (dim 0, dim 1, dim 2). - - :param numpy.ndarray data: 3D scalar field - """ - # Make sure data is a 3D contiguous array of native endian float32 - data = numpy.ascontiguousarray(data, dtype='=f4') - assert data.ndim == 3 - cdef float[:] c_data = numpy.ravel(data) - cdef unsigned int depth, height, width - - depth = data.shape[0] - height = data.shape[1] - width = data.shape[2] - - self.c_mc.process(&c_data[0], depth, height, width) - - def process_slice(self, slice0, slice1): - """Process a new slice to build the isosurface. - - :param numpy.ndarray slice0: Slice previously provided as slice1. - :param numpy.ndarray slice1: Slice to process. - """ - # Make sure slices are 2D contiguous arrays of native endian float32 - slice0 = numpy.ascontiguousarray(slice0, dtype='=f4') - assert slice0.ndim == 2 - slice1 = numpy.ascontiguousarray(slice1, dtype='=f4') - assert slice1.ndim == 2 - - assert slice0.shape[0] == slice1.shape[0] - assert slice0.shape[1] == slice1.shape[1] - - cdef float[:] c_slice0 = numpy.ravel(slice0) - cdef float[:] c_slice1 = numpy.ravel(slice1) - - if self.c_mc.depth == 0: - # Starts a new isosurface, bootstrap with slice size - self.c_mc.set_slice_size(slice1.shape[0], slice1.shape[1]) - - assert slice1.shape[0] == self.c_mc.height - assert slice1.shape[1] == self.c_mc.width - - self.c_mc.process_slice(&c_slice0[0], &c_slice1[0]) - - def finish_process(self): - """Clear internal cache after processing slice by slice.""" - self.c_mc.finish_process() - - def reset(self): - """Reset internal resources including computed isosurface info.""" - self.c_mc.reset() - - @cython.embedsignature(False) - @property - def shape(self): - """The shape of the processed scalar field (depth, height, width).""" - return self.c_mc.depth, self.c_mc.height, self.c_mc.width - - @cython.embedsignature(False) - @property - def sampling(self): - """The sampling over each dimension (depth, height, width). - - Default: 1, 1, 1 - """ - return (self.c_mc.sampling[0], - self.c_mc.sampling[1], - self.c_mc.sampling[2]) - - @cython.embedsignature(False) - @property - def isolevel(self): - """The iso-level at which to generate the isosurface""" - return self.c_mc.isolevel - - @cython.embedsignature(False) - @property - def invert_normals(self): - """True to use gradient descent as normals.""" - return self.c_mc.invert_normals - - def get_vertices(self): - """Vertices currently computed (ndarray of dim NbVertices x 3) - - Order is dim0, dim1, dim2 (i.e., z, y, x if dim0 is depth). - """ - return numpy.array(self.c_mc.vertices).reshape(-1, 3) - - def get_normals(self): - """Normals currently computed (ndarray of dim NbVertices x 3) - - Order is dim0, dim1, dim2 (i.e., z, y, x if dim0 is depth). - """ - return numpy.array(self.c_mc.normals).reshape(-1, 3) - - def get_indices(self): - """Triangle indices currently computed (ndarray of dim NbTriangles x 3) - """ - return numpy.array(self.c_mc.indices, - dtype=numpy.uint32).reshape(-1, 3) diff --git a/silx/math/marchingcubes/mc.hpp b/silx/math/marchingcubes/mc.hpp deleted file mode 100644 index 82eced9..0000000 --- a/silx/math/marchingcubes/mc.hpp +++ /dev/null @@ -1,724 +0,0 @@ -/*########################################################################## -# -# Copyright (c) 2015-2016 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. -# -# ###########################################################################*/ -#ifndef __mc_HPP__ -#define __mc_HPP__ - -#include <iostream> -#include <cmath> -#include <map> -#include <stdexcept> -#include <vector> -#include <assert.h> - - -extern const int MCTriangleTable[256][16]; -extern const unsigned int MCEdgeIndexToCoordOffsets[12][4]; - -#define DEPTH_IDX 0 -#define HEIGHT_IDX 1 -#define WIDTH_IDX 2 - -/** Class Marching cubes - * - * Implements the marching cube algorithm and provides an API to process - * data image by image. - * - * Dimension convention used is (dim0, dim1, dim2) denoted as - * (depth, height, width) with dim2 (= width) being contiguous in memory. - * - * If data is provided as (depth, height, width), resulting vertices - * and normals will be stored as (z, y, x) - * - * Indices in memory for a single cube: - * - * dim 0 (depth) - * | - * | - * 4 +------+ 5 - * /| /| - * / | / | - * 6 +------+ 7| - * | | | | - * |0 +---|--+ 1 --- dim 2 (width) - * | / | / - * |/ |/ - * 2 +------+ 3 - * / - * / - * dim 1 (height) - */ -template <typename FloatIn, typename FloatOut> -class MarchingCubes { -public: - /** Create a marching cube object. - * - * @param level Level at which to build the isosurface - */ - MarchingCubes(const FloatIn level); - - ~MarchingCubes(); - - /** Process a 3D scalar field - * - * @param data Pointer to the data set - * @param depth The 1st dimension of the data set - * @param height The 2nd dimension of the data set - * @param width The 3rd dimension of the data set - * (tightly packed in memory) - */ - void process(const FloatIn * data, - const unsigned int depth, - const unsigned int height, - const unsigned int width); - - /** Init dimension of slices - * - * @param height Height in pixels of the slices - * @param width Width in pixels of the slices - */ - void set_slice_size(const unsigned int height, - const unsigned int width); - - /** Process a slice (i.e., an image) - * - * The size of the images MUST match height and width provided to - * set_slice_size. - * - * The marching cube process 2 consecutive images at a time. - * A slice provided as next parameter MUST be provided as current - * parameter for the next call. - * Example with 3 images: - * - * float * img1; - * float * img2; - * float * img3; - * ... - * mc = MarchingCubes<float>(100.); - * mc.set_slice_size(10, 10); - * mc.process_slice(img1, img2); - * mc.process_slice(img2, img3); - * mc.finish_process(); - * - * @param slice0 Pointer to the nth slice data - * @param slice1 Pointer to the (n+1)th slice of data - */ - void process_slice(const FloatIn * slice0, - const FloatIn * slice1); - - /** Clear marching cube processing internal cache. */ - void finish_process(); - - /** Reset all internal data and counters. */ - void reset(); - - /** Vertices of the isosurface (x, y, z) */ - std::vector<FloatOut> vertices; - - /** Approximation of normals at the vertices (nx, ny, nz) - * - * Current implementation provides coarse (but fast) normals computation - */ - std::vector<FloatOut> normals; - - /** Triangle indices */ - std::vector<unsigned int> indices; - - unsigned int depth; /**< Number of images currently processed */ - unsigned int height; /**< Images height in pixels */ - unsigned int width; /**< Images width in pixels */ - - /** Sampling of the data (depth, height, width) - * - * Default: 1, 1, 1 - */ - unsigned int sampling[3]; - - FloatIn isolevel; /**< Iso level to use */ - bool invert_normals; /**< True to inverse gradient as normals */ - -private: - - /** Start to build isosurface starting with first slice - * - * Bootstrap cache edge_indices - * - * @param slice The first slice of the data - * @param next The second slice - */ - void first_slice(const FloatIn * slice, - const FloatIn * next); - - /** Process an edge - * - * @param value0 Data at 'begining' of edge - * @param value Data at 'end' of edge - * @param depth Depth coordinate of the edge position - * @param row Row coordinate of the edge - * @param col Column coordinate of the edge - * @param direction Direction of the edge: 0 for x, 1 for y and 2 for z - * @param previous - * @param current - * @param next - */ - void process_edge(const FloatIn value0, - const FloatIn value, - const unsigned int depth, - const unsigned int row, - const unsigned int col, - const unsigned int direction, - const FloatIn * previous, - const FloatIn * current, - const FloatIn * next); - - /** Return the bit mask of cube corners <= the iso-value. - * - * @param slice1 1st slice of the cube to consider - * @param slice2 2nd slice of the cube to consider - * @param row Row of the cube to consider - * @param col Column of the cube to consider - * @return The bit mask of cube corners <= the iso-value - */ - unsigned char get_cell_code(const FloatIn * slice1, - const FloatIn * slice2, - const unsigned int row, - const unsigned int col); - - /** Compute an edge index from position and edge direction. - * - * @param depth Depth of the origin of the edge - * @param row Row of the origin of the edge - * @param col Column of the origin of the edge - * @param direction 0 for x, 1 for y, 2 for z - * @return The (4D) index of the edge - */ - unsigned int edge_index(const unsigned int depth, - const unsigned int row, - const unsigned int col, - const unsigned int direction); - - /** For each dimension, a map from edge index to vertex index - * - * This caches indices for previously processed slice. - * - * Edge index is the linearized position of the edge using size + 1 - * in all dimensions as coordinates plus the direction as 4th coord. - * WARNING: direction 0 for x, 1 for y and 2 for z - */ - std::map<unsigned int, unsigned int> * edge_indices; -}; - - -/* Implementation */ - -template <typename FloatIn, typename FloatOut> -MarchingCubes<FloatIn, FloatOut>::MarchingCubes(const FloatIn level) -{ - this->edge_indices = 0; - this->reset(); - this->height = 0; - this->width = 0; - this->isolevel = level; - this->invert_normals = true; - this->sampling[0] = 1; - this->sampling[1] = 1; - this->sampling[2] = 1; -} - -template <typename FloatIn, typename FloatOut> -MarchingCubes<FloatIn, FloatOut>::~MarchingCubes() -{ -} - -template <typename FloatIn, typename FloatOut> -void -MarchingCubes<FloatIn, FloatOut>::reset() -{ - this->depth = 0; - this->vertices.clear(); - this->normals.clear(); - this->indices.clear(); - if (this->edge_indices != 0) { - delete this->edge_indices; - this->edge_indices = 0; - } -} - -template <typename FloatIn, typename FloatOut> -void -MarchingCubes<FloatIn, FloatOut>::finish_process() -{ - if (this->edge_indices != 0) { - delete this->edge_indices; - this->edge_indices = 0; - } -} - - -template <typename FloatIn, typename FloatOut> -void -MarchingCubes<FloatIn, FloatOut>::process(const FloatIn * data, - const unsigned int depth, - const unsigned int height, - const unsigned int width) -{ - assert(data != NULL); - unsigned int size = height * width * this->sampling[DEPTH_IDX]; - - /* number of slices minus - 1 to process */ - const unsigned int nb_slices = (depth - 1) / this->sampling[DEPTH_IDX]; - - this->reset(); - this->set_slice_size(height, width); - - for (unsigned int index=0; index < nb_slices; index++) { - const FloatIn * slice0 = data + (index * size); - const FloatIn * slice1 = slice0 + size; - - this->process_slice(slice0, slice1); - } - this->finish_process(); - - this->depth = depth; /* Forced as it might be < depth otherwise */ -} - - -template <typename FloatIn, typename FloatOut> -void -MarchingCubes<FloatIn, FloatOut>::set_slice_size(const unsigned int height, - const unsigned int width) -{ - this->reset(); - this->height = height; - this->width = width; -} - - -template <typename FloatIn, typename FloatOut> -void -MarchingCubes<FloatIn, FloatOut>::process_slice(const FloatIn * slice0, - const FloatIn * slice1) -{ - assert(slice0 != NULL); - assert(slice1 != NULL); - unsigned int row, col; - - if (this->edge_indices == 0) { - /* No previously processed slice, bootstrap */ - this->first_slice(slice0, slice1); - } - - /* Keep reference to cache from previous slice */ - std::map<unsigned int, unsigned int> * previous_edge_indices = - this->edge_indices; - - /* Init cache for this slice */ - this->edge_indices = new std::map<unsigned int, unsigned int>(); - - /* Loop over slice to add vertices */ - for (row=0; row < this->height; row += this->sampling[HEIGHT_IDX]) { - unsigned int line_index = row * this->width; - - for (col=0; col < this->width; col += this->sampling[WIDTH_IDX]) { - unsigned int item_index = line_index + col; - - FloatIn value0 = slice1[item_index]; - - /* Test forward edges and add vertices in the current slice plane */ - if (col < (width - this->sampling[WIDTH_IDX])) { - FloatIn value = slice1[item_index + this->sampling[WIDTH_IDX]]; - - this->process_edge(value0, value, this->depth, row, col, 0, - slice0, slice1, 0); - } - - if (row < (height - this->sampling[HEIGHT_IDX])) { - /* Value from next line*/ - FloatIn value = slice1[item_index + this->width * this->sampling[HEIGHT_IDX]]; - - this->process_edge(value0, value, this->depth, row, col, 1, - slice0, slice1, 0); - } - - /* Test backward edges and add vertices in z direction */ - { - FloatIn value = slice0[item_index]; - - /* Expect forward edge, so pass: previous, current */ - this->process_edge(value, value0, - this->depth - this->sampling[DEPTH_IDX], - row, col, 2, - 0, slice0, slice1); - } - - } - } - - /* Loop over cubes to add triangle indices */ - for (row=0; row < this->height - this->sampling[HEIGHT_IDX]; row += this->sampling[HEIGHT_IDX]) { - for (col=0; col < this->width - this->sampling[WIDTH_IDX]; col += this->sampling[WIDTH_IDX]) { - unsigned char code = this->get_cell_code(slice0, slice1, - row, col); - - if (code == 0) { - continue; - } - - const int * edgeIndexPtr = &MCTriangleTable[code][0]; - for (; *edgeIndexPtr >= 0; edgeIndexPtr++) { - const unsigned int * offsets = \ - MCEdgeIndexToCoordOffsets[*edgeIndexPtr]; - - unsigned int edge_index = this->edge_index( - this->depth - this->sampling[DEPTH_IDX] + offsets[DEPTH_IDX] * this->sampling[DEPTH_IDX], - row + offsets[HEIGHT_IDX] * this->sampling[HEIGHT_IDX], - col + offsets[WIDTH_IDX] * this->sampling[WIDTH_IDX], - offsets[3]); - - /* Add vertex index to the list of indices */ - std::map<unsigned int, unsigned int>::iterator it, end; - if (offsets[DEPTH_IDX] == 0 && offsets[3] != 2) { - it = previous_edge_indices->find(edge_index); - end = previous_edge_indices->end(); - } else { - it = this->edge_indices->find(edge_index); - end = this->edge_indices->end(); - } - if (it == end) { - throw std::runtime_error( - "Internal error: cannot build triangle indices."); - } - else { - this->indices.push_back(it->second); - } - } - - } - } - - /* Clean-up previous slice cache */ - delete previous_edge_indices; - - this->depth += this->sampling[DEPTH_IDX]; -} - - -template <typename FloatIn, typename FloatOut> -void -MarchingCubes<FloatIn, FloatOut>::first_slice(const FloatIn * slice, - const FloatIn * next) -{ - assert(slice != NULL); - assert(next != NULL); - /* Init cache for this slice */ - this->edge_indices = new std::map<unsigned int, unsigned int>(); - - unsigned int row, col; - - /* Loop over slice, and add isosurface vertices in the slice plane */ - for (row=0; row < this->height; row += this->sampling[HEIGHT_IDX]) { - unsigned int line_index = row * this->width; - - for (col=0; col < this->width; col += this->sampling[WIDTH_IDX]) { - unsigned int item_index = line_index + col; - - /* For each point test forward edges */ - FloatIn value0 = slice[item_index]; - - if (col < (width - this->sampling[WIDTH_IDX])) { - FloatIn value = slice[item_index + this->sampling[WIDTH_IDX]]; - - this->process_edge(value0, value, this->depth, row, col, 0, - 0, slice, next); - } - - if (row < (height - this->sampling[HEIGHT_IDX])) { - /* Value from next line */ - FloatIn value = slice[item_index + this->width * this->sampling[HEIGHT_IDX]]; - - this->process_edge(value0, value, this->depth, row, col, 1, - 0, slice, next); - } - } - } - - this->depth += this->sampling[DEPTH_IDX]; -} - - -template <typename FloatIn, typename FloatOut> -inline unsigned int -MarchingCubes<FloatIn, FloatOut>::edge_index(const unsigned int depth, - const unsigned int row, - const unsigned int col, - const unsigned int direction) -{ - return ((depth * (this->height + 1) + row) * - (this->width + 1) + col) * 3 + direction; -} - - -template <typename FloatIn, typename FloatOut> -inline void -MarchingCubes<FloatIn, FloatOut>::process_edge(const FloatIn value0, - const FloatIn value, - const unsigned int depth, - const unsigned int row, - const unsigned int col, - const unsigned int direction, - const FloatIn * previous, - const FloatIn * current, - const FloatIn * next) -{ - assert(current != NULL); - - if ((value0 <= this->isolevel) ^ (value <= this->isolevel)) { - - /* Crossing iso-surface, store it */ - FloatIn offset = (this->isolevel - value0) / (value - value0); - - /* Store edge to vertex index correspondance */ - unsigned int edge_index = this->edge_index(depth, row, col, direction); - (*this->edge_indices)[edge_index] = this->vertices.size() / 3; - - /* Store vertex as (z, y, x) */ - if (direction == 0) { - this->vertices.push_back((FloatOut) depth); - this->vertices.push_back((FloatOut) row); - this->vertices.push_back( - (FloatOut) col + offset * this->sampling[WIDTH_IDX]); - } - else if (direction == 1) { - this->vertices.push_back((FloatOut) depth); - this->vertices.push_back( - (FloatOut) row + offset * this->sampling[HEIGHT_IDX]); - this->vertices.push_back((FloatOut) col); - } - else if (direction == 2) { - this->vertices.push_back( - (FloatOut) depth + offset * this->sampling[DEPTH_IDX]); - this->vertices.push_back((FloatOut) row); - this->vertices.push_back((FloatOut) col); - } else { - throw std::runtime_error( - "Internal error: dimension > 3, never event."); - } - - /* Store normal as (nz, ny, nx) */ - FloatOut nz, ny, nx; - const FloatIn * slice0 = (previous != 0) ? previous : current; - const FloatIn * slice1 = (previous != 0) ? current : next; - - unsigned int row_offset = this->width * this->sampling[HEIGHT_IDX]; - - if (direction == 0) { - { /* nz */ - unsigned int item, item_next_col; - - item = row * this->width + col; - if (col >= this->width - this->sampling[WIDTH_IDX]) { - /* For last column, use previous column */ - item -= this->sampling[WIDTH_IDX]; - } - item_next_col = item + this->sampling[WIDTH_IDX]; - - nz = ((1. - offset) * (slice1[item] - slice0[item]) + - offset * (slice1[item_next_col] - slice0[item_next_col])); - } - - { /* ny */ - unsigned int item, item_next_col; - - item = row * this->width + col; - if (row >= this->height - this->sampling[HEIGHT_IDX]) { - /* For last row, use previous row */ - item -= row_offset; - } - if (col >= this->width - this->sampling[WIDTH_IDX]) { - /* For last column, use previous column */ - item -= this->sampling[WIDTH_IDX]; - } - item_next_col = item + this->sampling[WIDTH_IDX]; - - ny = ((1. - offset) * (current[item + row_offset] - - current[item]) + - offset * (current[item_next_col + row_offset] - - current[item_next_col])); - } - - nx = value - value0; - - } else if (direction == 1) { - { /* nz */ - unsigned int item, item_next_row; - - item = row * this->width + col; - if (row >= this->height - this->sampling[HEIGHT_IDX]) { - /* For last row, use previous row */ - item -= row_offset; - } - item_next_row = item + row_offset; - - nz = ((1. - offset) * (slice1[item] - slice0[item]) + - offset * (slice1[item_next_row] - slice0[item_next_row])); - } - - ny = value - value0; - - { /* nx */ - unsigned int item, item_next_row; - - item = row * this->width + col; - if (row >= this->height - this->sampling[HEIGHT_IDX]) { - /* For last row, use previous row */ - item -= row_offset; - } - if (col >= this->width - this->sampling[WIDTH_IDX]) { - /* For last column, use previous column */ - item -= this->sampling[WIDTH_IDX]; - } - - item_next_row = item + row_offset; - - nx = ((1. - offset) * (current[item + this->sampling[WIDTH_IDX]] - current[item]) + - offset * (current[item_next_row + this->sampling[WIDTH_IDX]] - current[item_next_row])); - } - - } else { /* direction == 2 */ - assert(direction == 2); - /* Previous should always be 0, only here in case this changes */ - const FloatIn * other_slice = (previous != 0) ? previous : next; - - nz = value - value0; - - { /* ny */ - unsigned int item, item_next_row; - - item = row * this->width + col; - if (row >= this->height - this->sampling[HEIGHT_IDX]) { - /* For last row, use previous row */ - item -= row_offset; - } - item_next_row = item + row_offset; - - ny = ((1. - offset) * (current[item_next_row] - current[item]) + - offset * (other_slice[item_next_row] - other_slice[item])); - } - - { /* nx */ - unsigned int item; - - item = row * this->width + col; - if (col >= this->width - this->sampling[WIDTH_IDX]) { - /* For last column, use previous column */ - item -= this->sampling[WIDTH_IDX]; - } - const unsigned int item_next_col = item + this->sampling[WIDTH_IDX]; - - nx = ((1. - offset) * (current[item_next_col] - current[item]) + - offset * (other_slice[item_next_col] - other_slice[item])); - } - } - - /* apply sampling scaling */ - nz /= (FloatOut) this->sampling[0]; - ny /= (FloatOut) this->sampling[1]; - nx /= (FloatOut) this->sampling[2]; - - /* normalisation */ - FloatOut norm = sqrt(nz * nz + ny * ny + nx * nx); - if (this->invert_normals) { /* Normal inversion */ - norm *= -1.; - } - - if (norm != 0) { - nz /= norm; - ny /= norm; - nx /= norm; - } - this->normals.push_back(nz); - this->normals.push_back(ny); - this->normals.push_back(nx); - } -} - - -template <typename FloatIn, typename FloatOut> -inline unsigned char -MarchingCubes<FloatIn, FloatOut>::get_cell_code(const FloatIn * slice1, - const FloatIn * slice2, - const unsigned int row, - const unsigned int col) -{ - assert(slice1 != NULL); - assert(slice2 != NULL); - unsigned int item = row * this->width + col; - unsigned int item_next_row = item + this->width * this->sampling[HEIGHT_IDX]; - unsigned char code = 0; - - /* Cube convention for cell code: - * WARNING: This differ from layout in memory - * - * 4 +------+ 5 - * /| /| - * / | / | - * 7 +------+ 6| - * | | | | - * |0 +---|--+ 1 - * | / | / - * |/ |/ - * 3 +------+ 2 - * - */ - /* First slice */ - if (slice1[item] <= this->isolevel) { - code |= 1 << 0; - } - if (slice1[item + this->sampling[WIDTH_IDX]] <= this->isolevel) { - code |= 1 << 1; - } - if (slice1[item_next_row + this->sampling[WIDTH_IDX]] <= this->isolevel) { - code |= 1 << 2; - } - if (slice1[item_next_row] <= this->isolevel) { - code |= 1 << 3; - } - - /* Second slice */ - if (slice2[item] <= this->isolevel) { - code |= 1 << 4; - } - if (slice2[item + this->sampling[WIDTH_IDX]] <= this->isolevel) { - code |= 1 << 5; - } - if (slice2[item_next_row + this->sampling[WIDTH_IDX]] <= this->isolevel) { - code |= 1 << 6; - } - if (slice2[item_next_row] <= this->isolevel) { - code |= 1 << 7; - } - - return code; -} - -#endif /*__mc_HPP__*/ diff --git a/silx/math/marchingcubes/mc_lut.cpp b/silx/math/marchingcubes/mc_lut.cpp deleted file mode 100644 index 7998f1b..0000000 --- a/silx/math/marchingcubes/mc_lut.cpp +++ /dev/null @@ -1,316 +0,0 @@ -# /*########################################################################## -# -# Copyright (c) 2015-2016 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. -# -# ###########################################################################*/ - -#include "mc.hpp" - - -/** Gives edge index of triangles vertices for each of the 256 possible cubes. - * - * Table taken from http://paulbourke.net/geometry/polygonise/ - * Author: Cory Bloyd - * Originially this code is public domain, - * relicensed here as MIT to provide a license. - * - * The cube index is a bit mask of cube corners <= isoValue. - * See vertexOffset for the place of each corner in the bit mask. - */ -const int MCTriangleTable[256][16] = { - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1}, - {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1}, - {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1}, - {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, - {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}, - {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, - {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1}, - {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1}, - {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, - {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1}, - {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, - {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1}, - {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, - {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, - {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, - {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1}, - {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, - {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, - {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1}, - {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1}, - {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, - {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1}, - {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1}, - {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, - {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, - {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1}, - {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, - {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1}, - {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1}, - {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, - {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, - {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1}, - {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, - {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, - {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}, - {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, - {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1}, - {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, - {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1}, - {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1}, - {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, - {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1}, - {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}, - {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, - {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1}, - {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1}, - {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, - {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1}, - {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, - {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1}, - {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, - {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1}, - {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, - {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1}, - {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, - {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1}, - {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}, - {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, - {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1}, - {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1}, - {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1}, - {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1}, - {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, - {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1}, - {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, - {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1}, - {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, - {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, - {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1}, - {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, - {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, - {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1}, - {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, - {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, - {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1}, - {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, - {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1}, - {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, - {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, - {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, - {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, - {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, - {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1}, - {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1}, - {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, - {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1}, - {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, - {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1}, - {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, - {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1}, - {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1}, - {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, - {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1}, - {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1}, - {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, - {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1}, - {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, - {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, - {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1}, - {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, - {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1}, - {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, - {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, - {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, - {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1}, - {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, - {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1}, - {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, - {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1}, - {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1}, - {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, - {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1}, - {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, - {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1}, - {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, - {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1}, - {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, - {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1}, - {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, - {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, - {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1}, - {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, - {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1}, - {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, - {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1}, - {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, - {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, - {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1}, - {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}, - {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, - {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1}, - {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1}, - {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, - {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1}, - {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, - {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1}, - {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, - {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1}, - {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1}, - {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, - {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, - {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1}, - {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, - {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1}, - {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, - {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1}, - {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, - {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, - {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1}, - {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, - {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1}, - {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, - {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, - {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1}, - {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, - {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1}, - {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, - {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1}, - {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, - {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1}, - {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, - {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1}, - {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, - {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1}, - {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, - {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1}, - {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, - {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, - {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} -}; - - -/** List edge origin and direction for each edge index in [0-12). - * - * For each edge, gives the first vertices as 3 coordinates from the origin - * of the cube and the direction of the edge as the 4th value. - */ -const unsigned int MCEdgeIndexToCoordOffsets[12][4] = { - {0, 0, 0, 0}, - {0, 0, 1, 1}, - {0, 1, 0, 0}, - {0, 0, 0, 1}, - {1, 0, 0, 0}, - {1, 0, 1, 1}, - {1, 1, 0, 0}, - {1, 0, 0, 1}, - {0, 0, 0, 2}, - {0, 0, 1, 2}, - {0, 1, 1, 2}, - {0, 1, 0, 2} -}; diff --git a/silx/math/math_compatibility.pxd b/silx/math/math_compatibility.pxd deleted file mode 100644 index ddaa550..0000000 --- a/silx/math/math_compatibility.pxd +++ /dev/null @@ -1,35 +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.
-#
-# ############################################################################*/
-
-# Provides Visual Studio 2008 missing math functions/macros
-
-cdef extern from "math_compatibility.h":
- double asinh(double x) nogil
- bint isnan(double x) nogil
- bint isfinite(double x) nogil
- long int lrint(double x) nogil
-
- double INFINITY
- double NAN
diff --git a/silx/math/mc.pxd b/silx/math/mc.pxd deleted file mode 100644 index b1c81e7..0000000 --- a/silx/math/mc.pxd +++ /dev/null @@ -1,51 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2015-2016 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. -# -# ###########################################################################*/ - -from libcpp.vector cimport vector as std_vector -from libcpp cimport bool - -cdef extern from "mc.hpp": - cdef cppclass MarchingCubes[FloatIn, FloatOut]: - MarchingCubes(FloatIn level) except + - void process(FloatIn * data, - unsigned int depth, - unsigned int height, - unsigned int width) except + - void set_slice_size(unsigned int height, - unsigned int width) - void process_slice(FloatIn * slice0, - FloatIn * slice1) except + - void finish_process() - void reset() - - unsigned int depth - unsigned int height - unsigned int width - unsigned int sampling[3] - FloatIn isolevel - bool invert_normals - std_vector[FloatOut] vertices - std_vector[FloatOut] normals - std_vector[unsigned int] indices diff --git a/silx/math/medianfilter/__init__.py b/silx/math/medianfilter/__init__.py deleted file mode 100644 index 2b05f06..0000000 --- a/silx/math/medianfilter/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ - -__authors__ = ["H. Payno"] -__license__ = "MIT" -__date__ = "02/05/2017" - - -from .medianfilter import (medfilt, medfilt1d, medfilt2d) diff --git a/silx/math/medianfilter/include/median_filter.hpp b/silx/math/medianfilter/include/median_filter.hpp deleted file mode 100644 index 7e42980..0000000 --- a/silx/math/medianfilter/include/median_filter.hpp +++ /dev/null @@ -1,284 +0,0 @@ -/*########################################################################## -# -# Copyright (c) 2017-2019 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. -# -# ###########################################################################*/ -// __authors__ = ["H. Payno"] -// __license__ = "MIT" -// __date__ = "10/02/2017" - -#ifndef MEDIAN_FILTER -#define MEDIAN_FILTER - -#include <vector> -#include <assert.h> -#include <algorithm> -#include <signal.h> -#include <iostream> -#include <cmath> -#include <cfloat> - -/* Needed for pytohn2.7 on Windows... */ -#ifndef INFINITY -#define INFINITY (DBL_MAX+DBL_MAX) -#endif - -#ifndef NAN -#define NAN (INFINITY-INFINITY) -#endif - -// Modes for the median filter -enum MODE{ - NEAREST=0, - REFLECT=1, - MIRROR=2, - SHRINK=3, - CONSTANT=4, -}; - -// Simple function browsing a deque and registering the min and max values -// and if those values are unique or not -template<typename T> -void getMinMax(std::vector<T>& v, T& min, T&max, - typename std::vector<T>::const_iterator end){ - // init min and max values - typename std::vector<T>::const_iterator it = v.begin(); - if (v.size() == 0){ - raise(SIGINT); - }else{ - min = max = *it; - } - it++; - - // Browse all the deque - while(it!=end){ - // check if repeated (should always be before min/max setting) - T value = *it; - if(value > max) max = value; - if(value < min) min = value; - - it++; - } -} - - -// apply the median filter only on limited part of the vector -// In case of even number of elements (either due to NaNs in the window -// or for image borders in shrink mode): -// the highest of the 2 central values is returned -template<typename T> -inline T median(std::vector<T>& v, int window_size) { - int pivot = window_size / 2; - std::nth_element(v.begin(), v.begin() + pivot, v.begin()+window_size); - return v[pivot]; -} - - -// return the index into 0, (length_max - 1) in reflect mode -inline int reflect(int index, int length_max){ - int res = index; - // if the index is negative get the positive symmetrical value - if(res < 0){ - res += 1; - res = -res; - } - // then apply the reflect algorithm. Frequency is 2 max length - res = res % (2*length_max); - if(res >= length_max){ - res = 2*length_max - res -1; - res = res % length_max; - } - return res; -} - -// return the index into 0, (length_max - 1) in mirror mode -inline int mirror(int index, int length_max){ - int res = index; - // if the index is negative get the positive symmetrical value - if(res < 0){ - res = -res; - } - int rightLimit = length_max -1; - // apply the redundancy each two right limit - res = res % (2*rightLimit); - if(res >= length_max){ - int distToRedundancy = (2*rightLimit) - res; - res = distToRedundancy; - } - return res; -} - -/* Provide a way to access NaN that also works for integers*/ - -template<typename T> -inline T NotANumber(void) { - assert(false); //This should never be called - return 0; -} - -template<> -inline float NotANumber<float>(void) { return NAN; } - -template<> -inline double NotANumber<double>(void) { return NAN; } - - -// Browse the column of pixel_x -template<typename T> -void median_filter( - const T* input, - T* output, - int* kernel_dim, // two values : 0:width, 1:height - int* image_dim, // two values : 0:width, 1:height - int y_pixel, // the x pixel to process - int x_pixel_range_min, - int x_pixel_range_max, - bool conditional, - int pMode, - T cval) { - - assert(kernel_dim[0] > 0); - assert(kernel_dim[1] > 0); - assert(y_pixel >= 0); - assert(image_dim[0] > 0); - assert(image_dim[1] > 0); - assert(y_pixel >= 0); - assert(y_pixel < image_dim[0]); - assert(x_pixel_range_max < image_dim[1]); - assert(x_pixel_range_min <= x_pixel_range_max); - // kernel odd assertion - assert((kernel_dim[0] - 1)%2 == 0); - assert((kernel_dim[1] - 1)%2 == 0); - - // # this should be move up to avoid calculation each time - int halfKernel_x = (kernel_dim[1] - 1) / 2; - int halfKernel_y = (kernel_dim[0] - 1) / 2; - - MODE mode = static_cast<MODE>(pMode); - - // init buffer - std::vector<T> window_values(kernel_dim[0]*kernel_dim[1]); - - bool not_horizontal_border = (y_pixel >= halfKernel_y && y_pixel < image_dim[0] - halfKernel_y); - - for(int x_pixel=x_pixel_range_min; x_pixel <= x_pixel_range_max; x_pixel ++ ){ - typename std::vector<T>::iterator it = window_values.begin(); - // fill the vector - - if (not_horizontal_border && - x_pixel >= halfKernel_x && x_pixel < image_dim[1] - halfKernel_x) { - //This is not a border, just fill it - for(int win_y=y_pixel-halfKernel_y; win_y<= y_pixel+halfKernel_y; win_y++) { - for(int win_x = x_pixel-halfKernel_x; win_x <= x_pixel+halfKernel_x; win_x++){ - T value = input[win_y*image_dim[1] + win_x]; - if (value == value) { // Ignore NaNs - *it = value; - ++it; - } - } - } - - } else { // This is a border, handle the special case - for(int win_y=y_pixel-halfKernel_y; win_y<= y_pixel+halfKernel_y; win_y++) - { - for(int win_x = x_pixel-halfKernel_x; win_x <= x_pixel+halfKernel_x; win_x++) - { - T value = 0; - int index_x = win_x; - int index_y = win_y; - - switch(mode){ - case NEAREST: - index_x = std::min(std::max(win_x, 0), image_dim[1] - 1); - index_y = std::min(std::max(win_y, 0), image_dim[0] - 1); - value = input[index_y*image_dim[1] + index_x]; - break; - - case REFLECT: - index_x = reflect(win_x, image_dim[1]); - index_y = reflect(win_y, image_dim[0]); - value = input[index_y*image_dim[1] + index_x]; - break; - - case MIRROR: - index_x = mirror(win_x, image_dim[1]); - // deal with 1d case - if(win_y == 0 && image_dim[0] == 1){ - index_y = 0; - }else{ - index_y = mirror(win_y, image_dim[0]); - } - value = input[index_y*image_dim[1] + index_x]; - break; - - case SHRINK: - if ((index_x < 0) || (index_x > image_dim[1] -1) || - (index_y < 0) || (index_y > image_dim[0] -1)) { - continue; - } - value = input[index_y*image_dim[1] + index_x]; - break; - case CONSTANT: - if ((index_x < 0) || (index_x > image_dim[1] -1) || - (index_y < 0) || (index_y > image_dim[0] -1)) { - value = cval; - } else { - value = input[index_y*image_dim[1] + index_x]; - } - break; - } - - if (value == value) { // Ignore NaNs - *it = value; - ++it; - } - } - } - } - - //window_size can be smaller than kernel size in shrink mode or if there is NaNs - int window_size = std::distance(window_values.begin(), it); - - if (window_size == 0) { - // Window is empty, this is the case when all values are NaNs - output[image_dim[1]*y_pixel + x_pixel] = NotANumber<T>(); - } else { - // apply the median value if needed for this pixel - const T currentPixelValue = input[image_dim[1]*y_pixel + x_pixel]; - if (conditional == true){ - typename std::vector<T>::iterator window_end = window_values.begin() + window_size; - T min = 0; - T max = 0; - getMinMax(window_values, min, max, window_end); - // NaNs are propagated through unchanged - if ((currentPixelValue == max) || (currentPixelValue == min)){ - output[image_dim[1]*y_pixel + x_pixel] = median<T>(window_values, window_size); - }else{ - output[image_dim[1]*y_pixel + x_pixel] = currentPixelValue; - } - }else{ - output[image_dim[1]*y_pixel + x_pixel] = median<T>(window_values, window_size); - } - } - } -} - -#endif // MEDIAN_FILTER diff --git a/silx/math/medianfilter/median_filter.pxd b/silx/math/medianfilter/median_filter.pxd deleted file mode 100644 index 2fc0283..0000000 --- a/silx/math/medianfilter/median_filter.pxd +++ /dev/null @@ -1,42 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2015-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. -# -# ###########################################################################*/ - -from libcpp cimport bool - -# pyx -cdef extern from "median_filter.hpp": - cdef extern void median_filter[T](const T* image, - T* output, - int* kernel_dim, - int* image_dim, - int x_pixel_range_min, - int x_pixel_range_max, - int y_pixel_range_min, - int y_pixel_range_max, - bool conditional, - T cval) nogil; - - cdef extern int reflect(int index, int length_max); - cdef extern int mirror(int index, int length_max); diff --git a/silx/math/medianfilter/medianfilter.pyx b/silx/math/medianfilter/medianfilter.pyx deleted file mode 100644 index fe05a78..0000000 --- a/silx/math/medianfilter/medianfilter.pyx +++ /dev/null @@ -1,496 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2015-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 median filter function for 1D and 2D arrays. -""" - -__authors__ = ["H. Payno", "J. Kieffer"] -__license__ = "MIT" -__date__ = "02/05/2017" - - -from cython.parallel import prange -cimport cython -cimport silx.math.medianfilter.median_filter as median_filter -import numpy -cimport numpy as cnumpy -from libcpp cimport bool - -import numbers - -ctypedef unsigned long uint64 -ctypedef unsigned int uint32 -ctypedef unsigned short uint16 - - -MODES = {'nearest': 0, 'reflect': 1, 'mirror': 2, 'shrink': 3, 'constant': 4} - - -def medfilt1d(data, - kernel_size=3, - bool conditional=False, - mode='nearest', - cval=0): - """Function computing the median filter of the given input. - - Behavior at boundaries: the algorithm is reducing the size of the - window/kernel for pixels at boundaries (there is no mirroring). - - Not-a-Number (NaN) float values are ignored. - If the window only contains NaNs, it evaluates to NaN. - - In event of an even number of valid values in the window (either - because of NaN values or on image border in shrink mode), - the highest of the 2 central sorted values is taken. - - :param numpy.ndarray data: the array for which we want to apply - the median filter. Should be 1d. - :param kernel_size: the dimension of the kernel. - :type kernel_size: int - :param bool conditional: True if we want to apply a conditional median - filtering. - :param str mode: the algorithm used to determine how values at borders - are determined: 'nearest', 'reflect', 'mirror', 'shrink', 'constant' - :param cval: Value used outside borders in 'constant' mode - - :returns: the array with the median value for each pixel. - """ - return medfilt(data, kernel_size, conditional, mode, cval) - - -def medfilt2d(image, - kernel_size=3, - bool conditional=False, - mode='nearest', - cval=0): - """Function computing the median filter of the given input. - Behavior at boundaries: the algorithm is reducing the size of the - window/kernel for pixels at boundaries (there is no mirroring). - - Not-a-Number (NaN) float values are ignored. - If the window only contains NaNs, it evaluates to NaN. - - In event of an even number of valid values in the window (either - because of NaN values or on image border in shrink mode), - the highest of the 2 central sorted values is taken. - - :param numpy.ndarray data: the array for which we want to apply - the median filter. Should be 2d. - :param kernel_size: the dimension of the kernel. - :type kernel_size: For 1D should be an int for 2D should be a tuple or - a list of (kernel_height, kernel_width) - :param bool conditional: True if we want to apply a conditional median - filtering. - :param str mode: the algorithm used to determine how values at borders - are determined: 'nearest', 'reflect', 'mirror', 'shrink', 'constant' - :param cval: Value used outside borders in 'constant' mode - - :returns: the array with the median value for each pixel. - """ - return medfilt(image, kernel_size, conditional, mode, cval) - - -def medfilt(data, - kernel_size=3, - bool conditional=False, - mode='nearest', - cval=0): - """Function computing the median filter of the given input. - Behavior at boundaries: the algorithm is reducing the size of the - window/kernel for pixels at boundaries (there is no mirroring). - - Not-a-Number (NaN) float values are ignored. - If the window only contains NaNs, it evaluates to NaN. - - In event of an even number of valid values in the window (either - because of NaN values or on image border in shrink mode), - the highest of the 2 central sorted values is taken. - - :param numpy.ndarray data: the array for which we want to apply - the median filter. Should be 1d or 2d. - :param kernel_size: the dimension of the kernel. - :type kernel_size: For 1D should be an int for 2D should be a tuple or - a list of (kernel_height, kernel_width) - :param bool conditional: True if we want to apply a conditional median - filtering. - :param str mode: the algorithm used to determine how values at borders - are determined: 'nearest', 'reflect', 'mirror', 'shrink', 'constant' - :param cval: Value used outside borders in 'constant' mode - - :returns: the array with the median value for each pixel. - """ - if mode not in MODES: - err = 'Requested mode %s is unknown.' % mode - raise ValueError(err) - - if data.ndim > 2: - raise ValueError( - "Invalid data shape. Dimension of the array should be 1 or 2") - - # Handle case of scalar kernel size - if isinstance(kernel_size, numbers.Integral): - kernel_size = [kernel_size] * data.ndim - - assert len(kernel_size) == data.ndim - - # Convert 1D arrays to 2D - reshaped = False - if len(data.shape) == 1: - data = data.reshape(1, data.shape[0]) - kernel_size = [1, kernel_size[0]] - reshaped = True - - # simple median filter apply into a 2D buffer - output_buffer = numpy.zeros_like(data) - check(data, output_buffer) - - ker_dim = numpy.array(kernel_size, dtype=numpy.int32) - - if data.dtype == numpy.float64: - medfilterfc = _median_filter_float64 - elif data.dtype == numpy.float32: - medfilterfc = _median_filter_float32 - elif data.dtype == numpy.int64: - medfilterfc = _median_filter_int64 - elif data.dtype == numpy.uint64: - medfilterfc = _median_filter_uint64 - elif data.dtype == numpy.int32: - medfilterfc = _median_filter_int32 - elif data.dtype == numpy.uint32: - medfilterfc = _median_filter_uint32 - elif data.dtype == numpy.int16: - medfilterfc = _median_filter_int16 - elif data.dtype == numpy.uint16: - medfilterfc = _median_filter_uint16 - else: - raise ValueError("%s type is not managed by the median filter" % data.dtype) - - medfilterfc(input_buffer=data, - output_buffer=output_buffer, - kernel_size=ker_dim, - conditional=conditional, - mode=MODES[mode], - cval=cval) - - if reshaped: - output_buffer.shape = -1 # Convert to 1D array - - return output_buffer - - -def check(input_buffer, output_buffer): - """Simple check on the two buffers to make sure we can apply the median filter - """ - if (input_buffer.flags['C_CONTIGUOUS'] is False): - raise ValueError('<input_buffer> must be a C_CONTIGUOUS numpy array.') - - if (output_buffer.flags['C_CONTIGUOUS'] is False): - raise ValueError('<output_buffer> must be a C_CONTIGUOUS numpy array.') - - if not (len(input_buffer.shape) <= 2): - raise ValueError('<input_buffer> dimension must mo higher than 2.') - - if not (len(output_buffer.shape) <= 2): - raise ValueError('<output_buffer> dimension must mo higher than 2.') - - if not(input_buffer.dtype == output_buffer.dtype): - raise ValueError('input buffer and output_buffer must be of the same type') - - if not (input_buffer.shape == output_buffer.shape): - raise ValueError('input buffer and output_buffer must be of the same dimension and same dimension') - - -######### implementations of the include/median_filter.hpp function ############ -@cython.cdivision(True) -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.initializedcheck(False) -def reflect(int index, int length_max): - """find the correct index into [0, length_max-1] for index in reflect mode - - :param int index: the index to move into [0, length_max-1] in reflect mode - :param int length_max: the higher bound limit - """ - return median_filter.reflect(index, length_max) - - -@cython.cdivision(True) -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.initializedcheck(False) -def mirror(int index, int length_max): - """find the correct index into [0, length_max-1] for index in mirror mode - - :param int index: the index to move into [0, length_max-1] in mirror mode - :param int length_max: the higher bound limit - """ - return median_filter.mirror(index, length_max) - - -@cython.cdivision(True) -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.initializedcheck(False) -def _median_filter_float32(float[:, ::1] input_buffer not None, - float[:, ::1] output_buffer not None, - cnumpy.int32_t[::1] kernel_size not None, - bool conditional, - int mode, - float cval): - - cdef: - int y = 0 - int image_dim = input_buffer.shape[1] - 1 - int[2] buffer_shape - buffer_shape[0] = input_buffer.shape[0] - buffer_shape[1] = input_buffer.shape[1] - - for y in prange(input_buffer.shape[0], nogil=True): - median_filter.median_filter[float](<float*> & input_buffer[0,0], - <float*> & output_buffer[0,0], - <int*>& kernel_size[0], - <int*>buffer_shape, - y, - 0, - image_dim, - conditional, - mode, - cval) - - -@cython.cdivision(True) -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.initializedcheck(False) -def _median_filter_float64(double[:, ::1] input_buffer not None, - double[:, ::1] output_buffer not None, - cnumpy.int32_t[::1] kernel_size not None, - bool conditional, - int mode, - double cval): - - cdef: - int y = 0 - int image_dim = input_buffer.shape[1] - 1 - int[2] buffer_shape - buffer_shape[0] = input_buffer.shape[0] - buffer_shape[1] = input_buffer.shape[1] - - for y in prange(input_buffer.shape[0], nogil=True): - median_filter.median_filter[double](<double*> & input_buffer[0, 0], - <double*> & output_buffer[0, 0], - <int*>&kernel_size[0], - <int*>buffer_shape, - y, - 0, - image_dim, - conditional, - mode, - cval) - - -@cython.cdivision(True) -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.initializedcheck(False) -def _median_filter_int64(cnumpy.int64_t[:, ::1] input_buffer not None, - cnumpy.int64_t[:, ::1] output_buffer not None, - cnumpy.int32_t[::1] kernel_size not None, - bool conditional, - int mode, - cnumpy.int64_t cval): - - cdef: - int y = 0 - int image_dim = input_buffer.shape[1] - 1 - int[2] buffer_shape - buffer_shape[0] = input_buffer.shape[0] - buffer_shape[1] = input_buffer.shape[1] - - for y in prange(input_buffer.shape[0], nogil=True): - median_filter.median_filter[long](<long*> & input_buffer[0,0], - <long*> & output_buffer[0, 0], - <int*>&kernel_size[0], - <int*>buffer_shape, - y, - 0, - image_dim, - conditional, - mode, - cval) - -@cython.cdivision(True) -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.initializedcheck(False) -def _median_filter_uint64(cnumpy.uint64_t[:, ::1] input_buffer not None, - cnumpy.uint64_t[:, ::1] output_buffer not None, - cnumpy.int32_t[::1] kernel_size not None, - bool conditional, - int mode, - cnumpy.uint64_t cval): - - cdef: - int y = 0 - int image_dim = input_buffer.shape[1] - 1 - int[2] buffer_shape - buffer_shape[0] = input_buffer.shape[0] - buffer_shape[1] = input_buffer.shape[1] - - for y in prange(input_buffer.shape[0], nogil=True): - median_filter.median_filter[uint64](<uint64*> & input_buffer[0,0], - <uint64*> & output_buffer[0, 0], - <int*>&kernel_size[0], - <int*>buffer_shape, - y, - 0, - image_dim, - conditional, - mode, - cval) - - -@cython.cdivision(True) -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.initializedcheck(False) -def _median_filter_int32(cnumpy.int32_t[:, ::1] input_buffer not None, - cnumpy.int32_t[:, ::1] output_buffer not None, - cnumpy.int32_t[::1] kernel_size not None, - bool conditional, - int mode, - cnumpy.int32_t cval): - - cdef: - int y = 0 - int image_dim = input_buffer.shape[1] - 1 - int[2] buffer_shape - buffer_shape[0] = input_buffer.shape[0] - buffer_shape[1] = input_buffer.shape[1] - - for y in prange(input_buffer.shape[0], nogil=True): - median_filter.median_filter[int](<int*> & input_buffer[0,0], - <int*> & output_buffer[0, 0], - <int*>&kernel_size[0], - <int*>buffer_shape, - y, - 0, - image_dim, - conditional, - mode, - cval) - - -@cython.cdivision(True) -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.initializedcheck(False) -def _median_filter_uint32(cnumpy.uint32_t[:, ::1] input_buffer not None, - cnumpy.uint32_t[:, ::1] output_buffer not None, - cnumpy.int32_t[::1] kernel_size not None, - bool conditional, - int mode, - cnumpy.uint32_t cval): - - cdef: - int y = 0 - int image_dim = input_buffer.shape[1] - 1 - int[2] buffer_shape - buffer_shape[0] = input_buffer.shape[0] - buffer_shape[1] = input_buffer.shape[1] - - for y in prange(input_buffer.shape[0], nogil=True): - median_filter.median_filter[uint32](<uint32*> & input_buffer[0,0], - <uint32*> & output_buffer[0, 0], - <int*>&kernel_size[0], - <int*>buffer_shape, - y, - 0, - image_dim, - conditional, - mode, - cval) - - -@cython.cdivision(True) -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.initializedcheck(False) -def _median_filter_int16(cnumpy.int16_t[:, ::1] input_buffer not None, - cnumpy.int16_t[:, ::1] output_buffer not None, - cnumpy.int32_t[::1] kernel_size not None, - bool conditional, - int mode, - cnumpy.int16_t cval): - - cdef: - int y = 0 - int image_dim = input_buffer.shape[1] - 1 - int[2] buffer_shape - buffer_shape[0] = input_buffer.shape[0] - buffer_shape[1] = input_buffer.shape[1] - - for y in prange(input_buffer.shape[0], nogil=True): - median_filter.median_filter[short](<short*> & input_buffer[0,0], - <short*> & output_buffer[0, 0], - <int*>&kernel_size[0], - <int*>buffer_shape, - y, - 0, - image_dim, - conditional, - mode, - cval) - - -@cython.cdivision(True) -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.initializedcheck(False) -def _median_filter_uint16( - cnumpy.uint16_t[:, ::1] input_buffer not None, - cnumpy.uint16_t[:, ::1] output_buffer not None, - cnumpy.int32_t[::1] kernel_size not None, - bool conditional, - int mode, - cnumpy.uint16_t cval): - - cdef: - int y = 0 - int image_dim = input_buffer.shape[1] - 1 - int[2] buffer_shape, - buffer_shape[0] = input_buffer.shape[0] - buffer_shape[1] = input_buffer.shape[1] - - for y in prange(input_buffer.shape[0], nogil=True): - median_filter.median_filter[uint16](<uint16*> & input_buffer[0, 0], - <uint16*> & output_buffer[0, 0], - <int*>&kernel_size[0], - <int*>buffer_shape, - y, - 0, - image_dim, - conditional, - mode, - cval) diff --git a/silx/math/medianfilter/setup.py b/silx/math/medianfilter/setup.py deleted file mode 100644 index d228357..0000000 --- a/silx/math/medianfilter/setup.py +++ /dev/null @@ -1,59 +0,0 @@ -# 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. -# -# ############################################################################*/ - -__authors__ = ["D. Naudet"] -__license__ = "MIT" -__date__ = "02/05/2017" - - -import numpy - -from numpy.distutils.misc_util import Configuration - - -def configuration(parent_package='', top_path=None): - config = Configuration('medianfilter', parent_package, top_path) - config.add_subpackage('test') - - # ===================================== - # median filter - # ===================================== - medfilt_src = ['medianfilter.pyx'] - medfilt_inc = ['include', numpy.get_include()] - extra_link_args = ['-fopenmp'] - extra_compile_args = ['-fopenmp'] - config.add_extension('medianfilter', - sources=medfilt_src, - include_dirs=[medfilt_inc], - language='c++', - extra_link_args=extra_link_args, - extra_compile_args=extra_compile_args) - - return config - - -if __name__ == "__main__": - from numpy.distutils.core import setup - - setup(configuration=configuration)
\ No newline at end of file diff --git a/silx/math/medianfilter/test/__init__.py b/silx/math/medianfilter/test/__init__.py deleted file mode 100644 index 92a6524..0000000 --- a/silx/math/medianfilter/test/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-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. -# -# ############################################################################*/ -__authors__ = ["H. Payno"] -__license__ = "MIT" -__date__ = "22/06/2016" - -import unittest - -from . import test_medianfilter - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTest(test_medianfilter.suite()) - return test_suite diff --git a/silx/math/medianfilter/test/benchmark.py b/silx/math/medianfilter/test/benchmark.py deleted file mode 100644 index cbb16b3..0000000 --- a/silx/math/medianfilter/test/benchmark.py +++ /dev/null @@ -1,122 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2017-2019 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. -# -# ############################################################################*/ -"""Tests of the median filter""" - -__authors__ = ["H. Payno"] -__license__ = "MIT" -__date__ = "02/05/2017" - -from silx.gui import qt -from silx.math.medianfilter import medfilt2d as medfilt2d_silx -import numpy -import numpy.random -from timeit import Timer -from silx.gui.plot import Plot1D -import logging - -try: - import scipy -except: - scipy = None -else: - import scipy.ndimage - -try: - import PyMca5.PyMca as pymca -except: - pymca = None -else: - from PyMca5.PyMca.median import medfilt2d as medfilt2d_pymca - -logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) - - -class BenchmarkMedianFilter(object): - """Simple benchmark of the median fiter silx vs scipy""" - - NB_ITER = 3 - - def __init__(self, imageWidth, kernels): - self.img = numpy.random.rand(imageWidth, imageWidth) - self.kernels = kernels - - self.run() - - def run(self): - self.execTime = {} - for kernel in self.kernels: - self.execTime[kernel] = self.bench(kernel) - - def bench(self, width): - def execSilx(): - medfilt2d_silx(self.img, width) - - def execScipy(): - scipy.ndimage.median_filter(input=self.img, - size=width, - mode='nearest') - - def execPymca(): - medfilt2d_pymca(self.img, width) - - execTime = {} - - t = Timer(execSilx) - execTime["silx"] = t.timeit(BenchmarkMedianFilter.NB_ITER) - logger.info( - 'exec time silx (kernel size = %s) is %s' % (width, execTime["silx"])) - - if scipy is not None: - t = Timer(execScipy) - execTime["scipy"] = t.timeit(BenchmarkMedianFilter.NB_ITER) - logger.info( - 'exec time scipy (kernel size = %s) is %s' % (width, execTime["scipy"])) - if pymca is not None: - t = Timer(execPymca) - execTime["pymca"] = t.timeit(BenchmarkMedianFilter.NB_ITER) - logger.info( - 'exec time pymca (kernel size = %s) is %s' % (width, execTime["pymca"])) - - return execTime - - def getExecTimeFor(self, id): - res = [] - for k in self.kernels: - res.append(self.execTime[k][id]) - return res - - -app = qt.QApplication([]) -kernels = [3, 5, 7, 11, 15] -benchmark = BenchmarkMedianFilter(imageWidth=1000, kernels=kernels) -plot = Plot1D() -plot.addCurve(x=kernels, y=benchmark.getExecTimeFor("silx"), legend='silx') -if scipy is not None: - plot.addCurve(x=kernels, y=benchmark.getExecTimeFor("scipy"), legend='scipy') -if pymca is not None: - plot.addCurve(x=kernels, y=benchmark.getExecTimeFor("pymca"), legend='pymca') -plot.show() -app.exec_() -del app diff --git a/silx/math/medianfilter/test/test_medianfilter.py b/silx/math/medianfilter/test/test_medianfilter.py deleted file mode 100644 index 3a45b3d..0000000 --- a/silx/math/medianfilter/test/test_medianfilter.py +++ /dev/null @@ -1,740 +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. -# -# ############################################################################ -"""Tests of the median filter""" - -__authors__ = ["H. Payno"] -__license__ = "MIT" -__date__ = "17/01/2018" - -import unittest -import numpy -from silx.math.medianfilter import medfilt2d, medfilt1d -from silx.math.medianfilter.medianfilter import reflect, mirror -from silx.math.medianfilter.medianfilter import MODES as silx_mf_modes -from silx.utils.testutils import ParametricTestCase -try: - import scipy - import scipy.misc -except: - scipy = None -else: - import scipy.ndimage - -import logging -_logger = logging.getLogger(__name__) - -RANDOM_FLOAT_MAT = numpy.array([ - [0.05564293, 0.62717157, 0.75002406, 0.40555336, 0.70278975], - [0.76532598, 0.02839148, 0.05272484, 0.65166994, 0.42161216], - [0.23067427, 0.74219128, 0.56049024, 0.44406320, 0.28773158], - [0.81025249, 0.20303021, 0.68382382, 0.46372299, 0.81281709], - [0.94691602, 0.07813661, 0.81651256, 0.84220106, 0.33623165]]) - -RANDOM_INT_MAT = numpy.array([ - [0, 5, 2, 6, 1], - [2, 3, 1, 7, 1], - [9, 8, 6, 7, 8], - [5, 6, 8, 2, 4]]) - - -class TestMedianFilterNearest(ParametricTestCase): - """Unit tests for the median filter in nearest mode""" - - def testFilter3_100(self): - """Test median filter on a 10x10 matrix with a 3x3 kernel.""" - dataIn = numpy.arange(100, dtype=numpy.int32) - dataIn = dataIn.reshape((10, 10)) - - dataOut = medfilt2d(image=dataIn, - kernel_size=(3, 3), - conditional=False, - mode='nearest') - self.assertTrue(dataOut[0, 0] == 1) - self.assertTrue(dataOut[9, 0] == 90) - self.assertTrue(dataOut[9, 9] == 98) - - self.assertTrue(dataOut[0, 9] == 9) - self.assertTrue(dataOut[0, 4] == 5) - self.assertTrue(dataOut[9, 4] == 93) - self.assertTrue(dataOut[4, 4] == 44) - - def testFilter3_9(self): - "Test median filter on a 3x3 matrix with a 3x3 kernel." - dataIn = numpy.array([0, -1, 1, - 12, 6, -2, - 100, 4, 12], - dtype=numpy.int16) - dataIn = dataIn.reshape((3, 3)) - dataOut = medfilt2d(image=dataIn, - kernel_size=(3, 3), - conditional=False, - mode='nearest') - self.assertTrue(dataOut.shape == dataIn.shape) - self.assertTrue(dataOut[1, 1] == 4) - self.assertTrue(dataOut[0, 0] == 0) - self.assertTrue(dataOut[0, 1] == 0) - self.assertTrue(dataOut[1, 0] == 6) - - def testFilterWidthOne(self): - """Make sure a filter of one by one give the same result as the input - """ - dataIn = numpy.arange(100, dtype=numpy.int32) - dataIn = dataIn.reshape((10, 10)) - - dataOut = medfilt2d(image=dataIn, - kernel_size=(1, 1), - conditional=False, - mode='nearest') - - self.assertTrue(numpy.array_equal(dataIn, dataOut)) - - def testFilter3_1d(self): - """Test binding and result of the 1d filter""" - self.assertTrue(numpy.array_equal( - medfilt1d(RANDOM_INT_MAT[0], kernel_size=3, conditional=False, - mode='nearest'), - [0, 2, 5, 2, 1]) - ) - - def testFilter3Conditionnal(self): - """Test that the conditional filter apply correctly in a 10x10 matrix - with a 3x3 kernel - """ - dataIn = numpy.arange(100, dtype=numpy.int32) - dataIn = dataIn.reshape((10, 10)) - - dataOut = medfilt2d(image=dataIn, - kernel_size=(3, 3), - conditional=True, - mode='nearest') - self.assertTrue(dataOut[0, 0] == 1) - self.assertTrue(dataOut[0, 1] == 1) - self.assertTrue(numpy.array_equal(dataOut[1:8, 1:8], dataIn[1:8, 1:8])) - self.assertTrue(dataOut[9, 9] == 98) - - def testFilter3_1D(self): - """Simple test of a 3x3 median filter on a 1D array""" - dataIn = numpy.arange(100, dtype=numpy.int32) - - dataOut = medfilt2d(image=dataIn, - kernel_size=(5), - conditional=False, - mode='nearest') - - self.assertTrue(dataOut[0] == 0) - self.assertTrue(dataOut[9] == 9) - self.assertTrue(dataOut[99] == 99) - - def testNaNs(self): - """Test median filter on image with NaNs in nearest mode""" - # Data with a NaN in first corner - nan_corner = numpy.arange(100.).reshape(10, 10) - nan_corner[0, 0] = numpy.nan - output = medfilt2d( - nan_corner, kernel_size=3, conditional=False, mode='nearest') - self.assertEqual(output[0, 0], 10) - self.assertEqual(output[0, 1], 2) - self.assertEqual(output[1, 0], 11) - self.assertEqual(output[1, 1], 12) - - # Data with some NaNs - some_nans = numpy.arange(100.).reshape(10, 10) - some_nans[0, 1] = numpy.nan - some_nans[1, 1] = numpy.nan - some_nans[1, 0] = numpy.nan - output = medfilt2d( - some_nans, kernel_size=3, conditional=False, mode='nearest') - self.assertEqual(output[0, 0], 0) - self.assertEqual(output[0, 1], 2) - self.assertEqual(output[1, 0], 20) - self.assertEqual(output[1, 1], 20) - - -class TestMedianFilterReflect(ParametricTestCase): - """Unit test for the median filter in reflect mode""" - - def testArange9(self): - """Test from a 3x3 window to RANDOM_FLOAT_MAT""" - img = numpy.arange(9, dtype=numpy.int32) - img = img.reshape(3, 3) - kernel = (3, 3) - res = medfilt2d(image=img, - kernel_size=kernel, - conditional=False, - mode='reflect') - self.assertTrue( - numpy.array_equal(res.ravel(), [1, 2, 2, 3, 4, 5, 6, 6, 7])) - - def testRandom10(self): - """Test a (5, 3) window to a RANDOM_FLOAT_MAT""" - kernel = (5, 3) - - thRes = numpy.array([ - [0.23067427, 0.56049024, 0.56049024, 0.4440632, 0.42161216], - [0.23067427, 0.62717157, 0.56049024, 0.56049024, 0.46372299], - [0.62717157, 0.62717157, 0.56049024, 0.56049024, 0.4440632], - [0.76532598, 0.68382382, 0.56049024, 0.56049024, 0.42161216], - [0.81025249, 0.68382382, 0.56049024, 0.68382382, 0.46372299]]) - - res = medfilt2d(image=RANDOM_FLOAT_MAT, - kernel_size=kernel, - conditional=False, - mode='reflect') - - self.assertTrue(numpy.array_equal(thRes, res)) - - def testApplyReflect1D(self): - """Test the reflect function used for the median filter in reflect mode - """ - # test for inside values - self.assertTrue(reflect(2, 3) == 2) - # test for boundaries values - self.assertTrue(reflect(3, 3) == 2) - self.assertTrue(reflect(4, 3) == 1) - self.assertTrue(reflect(5, 3) == 0) - self.assertTrue(reflect(6, 3) == 0) - self.assertTrue(reflect(7, 3) == 1) - self.assertTrue(reflect(-1, 3) == 0) - self.assertTrue(reflect(-2, 3) == 1) - self.assertTrue(reflect(-3, 3) == 2) - self.assertTrue(reflect(-4, 3) == 2) - self.assertTrue(reflect(-5, 3) == 1) - self.assertTrue(reflect(-6, 3) == 0) - self.assertTrue(reflect(-7, 3) == 0) - - def testRandom10Conditionnal(self): - """Test the median filter in reflect mode and with the conditionnal - option""" - kernel = (3, 1) - - thRes = numpy.array([ - [0.05564293, 0.62717157, 0.75002406, 0.40555336, 0.70278975], - [0.23067427, 0.62717157, 0.56049024, 0.44406320, 0.42161216], - [0.76532598, 0.20303021, 0.56049024, 0.46372299, 0.42161216], - [0.81025249, 0.20303021, 0.68382382, 0.46372299, 0.33623165], - [0.94691602, 0.07813661, 0.81651256, 0.84220106, 0.33623165]]) - - res = medfilt2d(image=RANDOM_FLOAT_MAT, - kernel_size=kernel, - conditional=True, - mode='reflect') - self.assertTrue(numpy.array_equal(thRes, res)) - - def testNaNs(self): - """Test median filter on image with NaNs in reflect mode""" - # Data with a NaN in first corner - nan_corner = numpy.arange(100.).reshape(10, 10) - nan_corner[0, 0] = numpy.nan - output = medfilt2d( - nan_corner, kernel_size=3, conditional=False, mode='reflect') - self.assertEqual(output[0, 0], 10) - self.assertEqual(output[0, 1], 2) - self.assertEqual(output[1, 0], 11) - self.assertEqual(output[1, 1], 12) - - # Data with some NaNs - some_nans = numpy.arange(100.).reshape(10, 10) - some_nans[0, 1] = numpy.nan - some_nans[1, 1] = numpy.nan - some_nans[1, 0] = numpy.nan - output = medfilt2d( - some_nans, kernel_size=3, conditional=False, mode='reflect') - self.assertEqual(output[0, 0], 0) - self.assertEqual(output[0, 1], 2) - self.assertEqual(output[1, 0], 20) - self.assertEqual(output[1, 1], 20) - - def testFilter3_1d(self): - """Test binding and result of the 1d filter""" - self.assertTrue(numpy.array_equal( - medfilt1d(RANDOM_INT_MAT[0], kernel_size=5, conditional=False, - mode='reflect'), - [2, 2, 2, 2, 2]) - ) - - -class TestMedianFilterMirror(ParametricTestCase): - """Unit test for the median filter in mirror mode - """ - - def testApplyMirror1D(self): - """Test the reflect function used for the median filter in mirror mode - """ - # test for inside values - self.assertTrue(mirror(2, 3) == 2) - # test for boundaries values - self.assertTrue(mirror(4, 4) == 2) - self.assertTrue(mirror(5, 4) == 1) - self.assertTrue(mirror(6, 4) == 0) - self.assertTrue(mirror(7, 4) == 1) - self.assertTrue(mirror(8, 4) == 2) - self.assertTrue(mirror(-1, 4) == 1) - self.assertTrue(mirror(-2, 4) == 2) - self.assertTrue(mirror(-3, 4) == 3) - self.assertTrue(mirror(-4, 4) == 2) - self.assertTrue(mirror(-5, 4) == 1) - self.assertTrue(mirror(-6, 4) == 0) - - def testRandom10(self): - """Test a (5, 3) window to a random array""" - kernel = (3, 5) - - thRes = numpy.array([ - [0.05272484, 0.40555336, 0.42161216, 0.42161216, 0.42161216], - [0.56049024, 0.56049024, 0.4440632, 0.4440632, 0.4440632], - [0.56049024, 0.46372299, 0.46372299, 0.46372299, 0.46372299], - [0.68382382, 0.56049024, 0.56049024, 0.46372299, 0.56049024], - [0.68382382, 0.46372299, 0.68382382, 0.46372299, 0.68382382]]) - - res = medfilt2d(image=RANDOM_FLOAT_MAT, - kernel_size=kernel, - conditional=False, - mode='mirror') - - self.assertTrue(numpy.array_equal(thRes, res)) - - def testRandom10Conditionnal(self): - """Test the median filter in reflect mode and with the conditionnal - option""" - kernel = (1, 3) - - thRes = numpy.array([ - [0.62717157, 0.62717157, 0.62717157, 0.70278975, 0.40555336], - [0.02839148, 0.05272484, 0.05272484, 0.42161216, 0.65166994], - [0.74219128, 0.56049024, 0.56049024, 0.44406320, 0.44406320], - [0.20303021, 0.68382382, 0.46372299, 0.68382382, 0.46372299], - [0.07813661, 0.81651256, 0.81651256, 0.81651256, 0.84220106]]) - - res = medfilt2d(image=RANDOM_FLOAT_MAT, - kernel_size=kernel, - conditional=True, - mode='mirror') - - self.assertTrue(numpy.array_equal(thRes, res)) - - def testNaNs(self): - """Test median filter on image with NaNs in mirror mode""" - # Data with a NaN in first corner - nan_corner = numpy.arange(100.).reshape(10, 10) - nan_corner[0, 0] = numpy.nan - output = medfilt2d( - nan_corner, kernel_size=3, conditional=False, mode='mirror') - self.assertEqual(output[0, 0], 11) - self.assertEqual(output[0, 1], 11) - self.assertEqual(output[1, 0], 11) - self.assertEqual(output[1, 1], 12) - - # Data with some NaNs - some_nans = numpy.arange(100.).reshape(10, 10) - some_nans[0, 1] = numpy.nan - some_nans[1, 1] = numpy.nan - some_nans[1, 0] = numpy.nan - output = medfilt2d( - some_nans, kernel_size=3, conditional=False, mode='mirror') - self.assertEqual(output[0, 0], 0) - self.assertEqual(output[0, 1], 12) - self.assertEqual(output[1, 0], 21) - self.assertEqual(output[1, 1], 20) - - def testFilter3_1d(self): - """Test binding and result of the 1d filter""" - self.assertTrue(numpy.array_equal( - medfilt1d(RANDOM_INT_MAT[0], kernel_size=5, conditional=False, - mode='mirror'), - [2, 5, 2, 5, 2]) - ) - -class TestMedianFilterShrink(ParametricTestCase): - """Unit test for the median filter in mirror mode - """ - - def testRandom_3x3(self): - """Test the median filter in shrink mode and with the conditionnal - option""" - kernel = (3, 3) - - thRes = numpy.array([ - [0.62717157, 0.62717157, 0.62717157, 0.65166994, 0.65166994], - [0.62717157, 0.56049024, 0.56049024, 0.44406320, 0.44406320], - [0.74219128, 0.56049024, 0.46372299, 0.46372299, 0.46372299], - [0.74219128, 0.68382382, 0.56049024, 0.56049024, 0.46372299], - [0.81025249, 0.81025249, 0.68382382, 0.81281709, 0.81281709]]) - - res = medfilt2d(image=RANDOM_FLOAT_MAT, - kernel_size=kernel, - conditional=False, - mode='shrink') - - self.assertTrue(numpy.array_equal(thRes, res)) - - def testBounds(self): - """Test the median filter in shrink mode with 3 different kernels - which should return the same result due to the large values of kernels - used. - """ - kernel1 = (1, 9) - kernel2 = (1, 11) - kernel3 = (1, 21) - - thRes = numpy.array([[2, 2, 2, 2, 2], - [2, 2, 2, 2, 2], - [8, 8, 8, 8, 8], - [5, 5, 5, 5, 5]]) - - resK1 = medfilt2d(image=RANDOM_INT_MAT, - kernel_size=kernel1, - conditional=False, - mode='shrink') - - resK2 = medfilt2d(image=RANDOM_INT_MAT, - kernel_size=kernel2, - conditional=False, - mode='shrink') - - resK3 = medfilt2d(image=RANDOM_INT_MAT, - kernel_size=kernel3, - conditional=False, - mode='shrink') - - self.assertTrue(numpy.array_equal(resK1, thRes)) - self.assertTrue(numpy.array_equal(resK2, resK1)) - self.assertTrue(numpy.array_equal(resK3, resK1)) - - def testRandom_3x3Conditionnal(self): - """Test the median filter in reflect mode and with the conditionnal - option""" - kernel = (3, 3) - - thRes = numpy.array([ - [0.05564293, 0.62717157, 0.62717157, 0.40555336, 0.65166994], - [0.62717157, 0.56049024, 0.05272484, 0.65166994, 0.42161216], - [0.23067427, 0.74219128, 0.56049024, 0.44406320, 0.46372299], - [0.81025249, 0.20303021, 0.68382382, 0.46372299, 0.81281709], - [0.81025249, 0.81025249, 0.81651256, 0.81281709, 0.81281709]]) - - res = medfilt2d(image=RANDOM_FLOAT_MAT, - kernel_size=kernel, - conditional=True, - mode='shrink') - - self.assertTrue(numpy.array_equal(res, thRes)) - - def testRandomInt(self): - """Test 3x3 kernel on RANDOM_INT_MAT - """ - kernel = (3, 3) - - thRes = numpy.array([[3, 2, 5, 2, 6], - [5, 3, 6, 6, 7], - [6, 6, 6, 6, 7], - [8, 8, 7, 7, 7]]) - - resK1 = medfilt2d(image=RANDOM_INT_MAT, - kernel_size=kernel, - conditional=False, - mode='shrink') - - self.assertTrue(numpy.array_equal(resK1, thRes)) - - def testNaNs(self): - """Test median filter on image with NaNs in shrink mode""" - # Data with a NaN in first corner - nan_corner = numpy.arange(100.).reshape(10, 10) - nan_corner[0, 0] = numpy.nan - output = medfilt2d( - nan_corner, kernel_size=3, conditional=False, mode='shrink') - self.assertEqual(output[0, 0], 10) - self.assertEqual(output[0, 1], 10) - self.assertEqual(output[1, 0], 11) - self.assertEqual(output[1, 1], 12) - - # Data with some NaNs - some_nans = numpy.arange(100.).reshape(10, 10) - some_nans[0, 1] = numpy.nan - some_nans[1, 1] = numpy.nan - some_nans[1, 0] = numpy.nan - output = medfilt2d( - some_nans, kernel_size=3, conditional=False, mode='shrink') - self.assertEqual(output[0, 0], 0) - self.assertEqual(output[0, 1], 2) - self.assertEqual(output[1, 0], 20) - self.assertEqual(output[1, 1], 20) - - def testFilter3_1d(self): - """Test binding and result of the 1d filter""" - self.assertTrue(numpy.array_equal( - medfilt1d(RANDOM_INT_MAT[0], kernel_size=3, conditional=False, - mode='shrink'), - [5, 2, 5, 2, 6]) - ) - -class TestMedianFilterConstant(ParametricTestCase): - """Unit test for the median filter in constant mode - """ - - def testRandom10(self): - """Test a (5, 3) window to a random array""" - kernel = (3, 5) - - thRes = numpy.array([ - [0., 0.02839148, 0.05564293, 0.02839148, 0.], - [0.05272484, 0.40555336, 0.4440632, 0.42161216, 0.28773158], - [0.05272484, 0.44406320, 0.46372299, 0.42161216, 0.28773158], - [0.20303021, 0.46372299, 0.56049024, 0.44406320, 0.33623165], - [0., 0.07813661, 0.33623165, 0.07813661, 0.]]) - - res = medfilt2d(image=RANDOM_FLOAT_MAT, - kernel_size=kernel, - conditional=False, - mode='constant') - - self.assertTrue(numpy.array_equal(thRes, res)) - - RANDOM_FLOAT_MAT = numpy.array([ - [0.05564293, 0.62717157, 0.75002406, 0.40555336, 0.70278975], - [0.76532598, 0.02839148, 0.05272484, 0.65166994, 0.42161216], - [0.23067427, 0.74219128, 0.56049024, 0.44406320, 0.28773158], - [0.81025249, 0.20303021, 0.68382382, 0.46372299, 0.81281709], - [0.94691602, 0.07813661, 0.81651256, 0.84220106, 0.33623165]]) - - def testRandom10Conditionnal(self): - """Test the median filter in reflect mode and with the conditionnal - option""" - kernel = (1, 3) - - print(RANDOM_FLOAT_MAT) - - thRes = numpy.array([ - [0.05564293, 0.62717157, 0.62717157, 0.70278975, 0.40555336], - [0.02839148, 0.05272484, 0.05272484, 0.42161216, 0.42161216], - [0.23067427, 0.56049024, 0.56049024, 0.44406320, 0.28773158], - [0.20303021, 0.68382382, 0.46372299, 0.68382382, 0.46372299], - [0.07813661, 0.81651256, 0.81651256, 0.81651256, 0.33623165]]) - - res = medfilt2d(image=RANDOM_FLOAT_MAT, - kernel_size=kernel, - conditional=True, - mode='constant') - - self.assertTrue(numpy.array_equal(thRes, res)) - - def testNaNs(self): - """Test median filter on image with NaNs in constant mode""" - # Data with a NaN in first corner - nan_corner = numpy.arange(100.).reshape(10, 10) - nan_corner[0, 0] = numpy.nan - output = medfilt2d(nan_corner, - kernel_size=3, - conditional=False, - mode='constant', - cval=0) - self.assertEqual(output[0, 0], 0) - self.assertEqual(output[0, 1], 2) - self.assertEqual(output[1, 0], 10) - self.assertEqual(output[1, 1], 12) - - # Data with some NaNs - some_nans = numpy.arange(100.).reshape(10, 10) - some_nans[0, 1] = numpy.nan - some_nans[1, 1] = numpy.nan - some_nans[1, 0] = numpy.nan - output = medfilt2d(some_nans, - kernel_size=3, - conditional=False, - mode='constant', - cval=0) - self.assertEqual(output[0, 0], 0) - self.assertEqual(output[0, 1], 0) - self.assertEqual(output[1, 0], 0) - self.assertEqual(output[1, 1], 20) - - def testFilter3_1d(self): - """Test binding and result of the 1d filter""" - self.assertTrue(numpy.array_equal( - medfilt1d(RANDOM_INT_MAT[0], kernel_size=5, conditional=False, - mode='constant'), - [0, 2, 2, 2, 1]) - ) - -class TestGeneralExecution(ParametricTestCase): - """Some general test on median filter application""" - - def testTypes(self): - """Test that all needed types have their implementation of the median - filter - """ - for mode in silx_mf_modes: - for testType in [numpy.float32, numpy.float64, numpy.int16, - numpy.uint16, numpy.int32, numpy.int64, - numpy.uint64]: - with self.subTest(mode=mode, type=testType): - data = (numpy.random.rand(10, 10) * 65000).astype(testType) - out = medfilt2d(image=data, - kernel_size=(3, 3), - conditional=False, - mode=mode) - self.assertTrue(out.dtype.type is testType) - - def testInputDataIsNotModify(self): - """Make sure input data is not modify by the median filter""" - dataIn = numpy.arange(100, dtype=numpy.int32) - dataIn = dataIn.reshape((10, 10)) - dataInCopy = dataIn.copy() - - for mode in silx_mf_modes: - with self.subTest(mode=mode): - medfilt2d(image=dataIn, - kernel_size=(3, 3), - conditional=False, - mode=mode) - self.assertTrue(numpy.array_equal(dataIn, dataInCopy)) - - def testAllNaNs(self): - """Test median filter on image all NaNs""" - all_nans = numpy.empty((10, 10), dtype=numpy.float32) - all_nans[:] = numpy.nan - - for mode in silx_mf_modes: - for conditional in (True, False): - with self.subTest(mode=mode, conditional=conditional): - output = medfilt2d( - all_nans, - kernel_size=3, - conditional=conditional, - mode=mode, - cval=numpy.nan) - self.assertTrue(numpy.all(numpy.isnan(output))) - - def testConditionalWithNaNs(self): - """Test that NaNs are propagated through conditional median filter""" - for mode in silx_mf_modes: - with self.subTest(mode=mode): - image = numpy.ones((10, 10), dtype=numpy.float32) - nan_mask = numpy.zeros_like(image, dtype=bool) - nan_mask[0, 0] = True - nan_mask[4, :] = True - nan_mask[6, 4] = True - image[nan_mask] = numpy.nan - output = medfilt2d( - image, - kernel_size=3, - conditional=True, - mode=mode) - out_isnan = numpy.isnan(output) - self.assertTrue(numpy.all(out_isnan[nan_mask])) - self.assertFalse( - numpy.any(out_isnan[numpy.logical_not(nan_mask)])) - - -def _getScipyAndSilxCommonModes(): - """return the mode which are comparable between silx and scipy""" - modes = silx_mf_modes.copy() - del modes['shrink'] - return modes - - -@unittest.skipUnless(scipy is not None, "scipy not available") -class TestVsScipy(ParametricTestCase): - """Compare scipy.ndimage.median_filter vs silx.math.medianfilter - on comparable - """ - def testWithArange(self): - """Test vs scipy with different kernels on arange matrix""" - data = numpy.arange(10000, dtype=numpy.int32) - data = data.reshape(100, 100) - - kernels = [(3, 7), (7, 5), (1, 1), (3, 3)] - modesToTest = _getScipyAndSilxCommonModes() - for kernel in kernels: - for mode in modesToTest: - with self.subTest(kernel=kernel, mode=mode): - resScipy = scipy.ndimage.median_filter(input=data, - size=kernel, - mode=mode) - resSilx = medfilt2d(image=data, - kernel_size=kernel, - conditional=False, - mode=mode) - - self.assertTrue(numpy.array_equal(resScipy, resSilx)) - - def testRandomMatrice(self): - """Test vs scipy with different kernels on RANDOM_FLOAT_MAT""" - kernels = [(3, 7), (7, 5), (1, 1), (3, 3)] - modesToTest = _getScipyAndSilxCommonModes() - for kernel in kernels: - for mode in modesToTest: - with self.subTest(kernel=kernel, mode=mode): - resScipy = scipy.ndimage.median_filter(input=RANDOM_FLOAT_MAT, - size=kernel, - mode=mode) - - resSilx = medfilt2d(image=RANDOM_FLOAT_MAT, - kernel_size=kernel, - conditional=False, - mode=mode) - - self.assertTrue(numpy.array_equal(resScipy, resSilx)) - - def testAscentOrLena(self): - """Test vs scipy with """ - if hasattr(scipy.misc, 'ascent'): - img = scipy.misc.ascent() - else: - img = scipy.misc.lena() - - kernels = [(3, 1), (3, 5), (5, 9), (9, 3)] - modesToTest = _getScipyAndSilxCommonModes() - - for kernel in kernels: - for mode in modesToTest: - with self.subTest(kernel=kernel, mode=mode): - resScipy = scipy.ndimage.median_filter(input=img, - size=kernel, - mode=mode) - - resSilx = medfilt2d(image=img, - kernel_size=kernel, - conditional=False, - mode=mode) - - self.assertTrue(numpy.array_equal(resScipy, resSilx)) - - -def suite(): - test_suite = unittest.TestSuite() - for test in [TestGeneralExecution, - TestVsScipy, - TestMedianFilterNearest, - TestMedianFilterReflect, - TestMedianFilterMirror, - TestMedianFilterShrink, - TestMedianFilterConstant]: - test_suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(test)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/math/setup.py b/silx/math/setup.py deleted file mode 100644 index 8cc15e6..0000000 --- a/silx/math/setup.py +++ /dev/null @@ -1,99 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-2019 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. -# -# ############################################################################*/ - -__authors__ = ["D. Naudet"] -__license__ = "MIT" -__date__ = "27/03/2017" - -import os.path - -import numpy - -from numpy.distutils.misc_util import Configuration - - -def configuration(parent_package='', top_path=None): - config = Configuration('math', parent_package, top_path) - config.add_subpackage('test') - config.add_subpackage('fit') - config.add_subpackage('medianfilter') - config.add_subpackage('fft') - - # ===================================== - # histogramnd - # ===================================== - histo_src = [os.path.join('histogramnd', 'src', 'histogramnd_c.c'), - 'chistogramnd.pyx'] - histo_inc = [os.path.join('histogramnd', 'include'), - numpy.get_include()] - - config.add_extension('chistogramnd', - sources=histo_src, - include_dirs=histo_inc, - language='c') - - # ===================================== - # histogramnd_lut - # ===================================== - config.add_extension('chistogramnd_lut', - sources=['chistogramnd_lut.pyx'], - include_dirs=histo_inc, - language='c') - # ===================================== - # marching cubes - # ===================================== - mc_src = [os.path.join('marchingcubes', 'mc_lut.cpp'), - 'marchingcubes.pyx'] - config.add_extension('marchingcubes', - sources=mc_src, - include_dirs=['marchingcubes', numpy.get_include()], - language='c++') - - # min/max - config.add_extension('combo', - sources=['combo.pyx'], - include_dirs=['include'], - language='c') - - config.add_extension('colormap', - sources=["colormap.pyx"], - language='c', - include_dirs=['include', numpy.get_include()], - extra_link_args=['-fopenmp'], - extra_compile_args=['-fopenmp']) - - config.add_extension('interpolate', - sources=["interpolate.pyx"], - language='c', - include_dirs=['include', numpy.get_include()], - extra_link_args=['-fopenmp'], - extra_compile_args=['-fopenmp']) - - return config - - -if __name__ == "__main__": - from numpy.distutils.core import setup - - setup(configuration=configuration) diff --git a/silx/math/test/__init__.py b/silx/math/test/__init__.py deleted file mode 100644 index e9f29f3..0000000 --- a/silx/math/test/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-2019 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. -# -# ############################################################################*/ - -__authors__ = ["D. Naudet"] -__license__ = "MIT" -__date__ = "04/07/2016" - -import unittest - -from .test_histogramnd_error import suite as test_histo_error -from .test_histogramnd_nominal import suite as test_histo_nominal -from .test_histogramnd_vs_np import suite as test_histo_vs_np -from .test_HistogramndLut_nominal import suite as test_histolut_nominal -from ..fit.test import suite as test_fit_suite -from .test_marchingcubes import suite as test_marchingcubes_suite -from ..medianfilter.test import suite as test_medianfilter_suite -from .test_combo import suite as test_combo_suite -from .test_calibration import suite as test_calibration_suite -from .test_colormap import suite as test_colormap_suite -from .test_interpolate import suite as test_interpolate_suite -from ..fft.test import suite as test_fft_suite - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTest(test_histo_nominal()) - test_suite.addTest(test_histo_error()) - test_suite.addTest(test_histo_vs_np()) - test_suite.addTest(test_fit_suite()) - test_suite.addTest(test_histolut_nominal()) - test_suite.addTest(test_marchingcubes_suite()) - test_suite.addTest(test_medianfilter_suite()) - test_suite.addTest(test_combo_suite()) - test_suite.addTest(test_calibration_suite()) - test_suite.addTest(test_colormap_suite()) - test_suite.addTest(test_interpolate_suite()) - test_suite.addTest(test_fft_suite()) - return test_suite diff --git a/silx/math/test/benchmark_combo.py b/silx/math/test/benchmark_combo.py deleted file mode 100644 index e179f76..0000000 --- a/silx/math/test/benchmark_combo.py +++ /dev/null @@ -1,203 +0,0 @@ -# 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. -# -# ############################################################################*/ -"""Benchmarks of the combo module""" - -from __future__ import division - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "17/01/2018" - - -import logging -import os.path -import time -import unittest - -import numpy - -from silx.test.utils import temp_dir -from silx.utils.testutils import ParametricTestCase - -from silx.math import combo - -_logger = logging.getLogger(__name__) -_logger.setLevel(logging.DEBUG) - - -class BenchmarkMinMax(ParametricTestCase): - """Benchmark of min max combo""" - - DTYPES = ('float32', 'float64', - 'int8', 'int16', 'int32', 'int64', - 'uint8', 'uint16', 'uint32', 'uint64') - - ARANGE = 'ascent', 'descent', 'random' - - EXPONENT = 3, 4, 5, 6, 7 - - def test_benchmark_min_max(self): - """Benchmark min_max without min positive. - - Compares with: - - - numpy.nanmin, numpy.nanmax and - - numpy.argmin, numpy.argmax - - It runs bench for different types, different data size and 3 - data sets: increasing , decreasing and random data. - """ - durations = {'min/max': [], 'argmin/max': [], 'combo': []} - - _logger.info('Benchmark against argmin/argmax and nanmin/nanmax') - - for dtype in self.DTYPES: - for arange in self.ARANGE: - for exponent in self.EXPONENT: - size = 10**exponent - with self.subTest(dtype=dtype, size=size, arange=arange): - if arange == 'ascent': - data = numpy.arange(0, size, 1, dtype=dtype) - elif arange == 'descent': - data = numpy.arange(size, 0, -1, dtype=dtype) - else: - if dtype in ('float32', 'float64'): - data = numpy.random.random(size) - else: - data = numpy.random.randint(10**6, size=size) - data = numpy.array(data, dtype=dtype) - - start = time.time() - ref_min = numpy.nanmin(data) - ref_max = numpy.nanmax(data) - durations['min/max'].append(time.time() - start) - - start = time.time() - ref_argmin = numpy.argmin(data) - ref_argmax = numpy.argmax(data) - durations['argmin/max'].append(time.time() - start) - - start = time.time() - result = combo.min_max(data, min_positive=False) - durations['combo'].append(time.time() - start) - - _logger.info( - '%s-%s-10**%d\tx%.2f argmin/max x%.2f min/max', - dtype, arange, exponent, - durations['argmin/max'][-1] / durations['combo'][-1], - durations['min/max'][-1] / durations['combo'][-1]) - - self.assertEqual(result.minimum, ref_min) - self.assertEqual(result.maximum, ref_max) - self.assertEqual(result.argmin, ref_argmin) - self.assertEqual(result.argmax, ref_argmax) - - self.show_results('min/max', durations, 'combo') - - def test_benchmark_min_pos(self): - """Benchmark min_max wit min positive. - - Compares with: - - - numpy.nanmin(data[data > 0]); numpy.nanmin(pos); numpy.nanmax(pos) - - It runs bench for different types, different data size and 3 - data sets: increasing , decreasing and random data. - """ - durations = {'min/max': [], 'combo': []} - - _logger.info('Benchmark against min, max, positive min') - - for dtype in self.DTYPES: - for arange in self.ARANGE: - for exponent in self.EXPONENT: - size = 10**exponent - with self.subTest(dtype=dtype, size=size, arange=arange): - if arange == 'ascent': - data = numpy.arange(0, size, 1, dtype=dtype) - elif arange == 'descent': - data = numpy.arange(size, 0, -1, dtype=dtype) - else: - if dtype in ('float32', 'float64'): - data = numpy.random.random(size) - else: - data = numpy.random.randint(10**6, size=size) - data = numpy.array(data, dtype=dtype) - - start = time.time() - ref_min_positive = numpy.nanmin(data[data > 0]) - ref_min = numpy.nanmin(data) - ref_max = numpy.nanmax(data) - durations['min/max'].append(time.time() - start) - - start = time.time() - result = combo.min_max(data, min_positive=True) - durations['combo'].append(time.time() - start) - - _logger.info( - '%s-%s-10**%d\tx%.2f min/minpos/max', - dtype, arange, exponent, - durations['min/max'][-1] / durations['combo'][-1]) - - self.assertEqual(result.min_positive, ref_min_positive) - self.assertEqual(result.minimum, ref_min) - self.assertEqual(result.maximum, ref_max) - - self.show_results('min/max/min positive', durations, 'combo') - - def show_results(self, title, durations, ref_key): - try: - from matplotlib import pyplot - except ImportError: - _logger.warning('matplotlib not available') - return - - pyplot.title(title) - pyplot.xlabel('-'.join(self.DTYPES)) - pyplot.ylabel('duration (sec)') - for label, values in durations.items(): - pyplot.semilogy(values, label=label) - pyplot.legend() - pyplot.show() - - pyplot.title(title) - pyplot.xlabel('-'.join(self.DTYPES)) - pyplot.ylabel('Duration ratio') - ref = numpy.array(durations[ref_key]) - for label, values in durations.items(): - values = numpy.array(values) - pyplot.plot(values/ref, label=label + ' / ' + ref_key) - pyplot.legend() - pyplot.show() - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTests( - unittest.defaultTestLoader.loadTestsFromTestCase(BenchmarkMinMax)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/test/histo_benchmarks.py b/silx/math/test/histo_benchmarks.py deleted file mode 100644 index 7d3216d..0000000 --- a/silx/math/test/histo_benchmarks.py +++ /dev/null @@ -1,269 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ -""" -histogramnd benchmarks, vs numpy.histogramdd (bin counts and weights). -""" - -import numpy as np - -import time - -from silx.math import histogramnd - - -def print_times(t0s, t1s, t2s, t3s): - c_times = t1s - t0s - np_times = t2s - t1s - np_w_times = t3s - t2s - - time_txt = 'min : {0: <7.3f}; max : {1: <7.3f}; avg : {2: <7.3f}' - - print('\tTimes :') - print('\tC : ' + time_txt.format(c_times.min(), - c_times.max(), - c_times.mean())) - print('\tNP : ' + time_txt.format(np_times.min(), - np_times.max(), - np_times.mean())) - print('\tNP(W) : ' + time_txt.format(np_w_times.min(), - np_w_times.max(), - np_w_times.mean())) - - -def commpare_results(txt, - times, - result_c, - result_np, - result_np_w, - sample, - weights, - raise_ex=False): - - if result_np: - hits_cmp = np.array_equal(result_c[0], result_np[0]) - else: - hits_cmp = None - - if result_np_w and result_c[1] is not None: - weights_cmp = np.array_equal(result_c[1], result_np_w[0]) - else: - weights_cmp = None - - if((hits_cmp is not None and not hits_cmp) or - (weights_cmp is not None and not weights_cmp)): - err_txt = (txt + ' : results arent the same : ' - 'hits : {0}, ' - 'weights : {1}.' - ''.format('OK' if hits_cmp else 'NOK', - 'OK' if weights_cmp else 'NOK')) - print('\t' + err_txt) - if raise_ex: - raise ValueError(err_txt) - return False - - result_txt = ' : results OK. c : {0: <7.3f};'.format(times[0]) - if result_np or result_np_w: - result_txt += (' np : {0: <7.3f}; ' - 'np (weights) {1: <7.3f}.' - ''.format(times[1], times[2])) - print('\t' + txt + result_txt) - return True - - -def benchmark(n_loops, - sample_shape, - sample_rng, - weights_rng, - histo_range, - n_bins, - weight_min, - weight_max, - last_bin_closed, - dtype=np.double, - do_weights=True, - do_numpy=True): - - int_min = 0 - int_max = 100000 - - sample = np.random.randint(int_min, - high=int_max, - size=sample_shape).astype(np.double) - sample = (sample_rng[0] + - (sample - int_min) * - (sample_rng[1] - sample_rng[0]) / - (int_max - int_min)) - sample = sample.astype(dtype) - - if do_weights: - weights = np.random.randint(int_min, - high=int_max, - size=(ssetup.pyample_shape[0],)) - weights = weights.astype(np.double) - weights = (weights_rng[0] + - (weights - int_min) * - (weights_rng[1] - weights_rng[0]) / - (int_max - int_min)) - else: - weights = None - - t0s = [] - t1s = [] - t2s = [] - t3s = [] - - for i in range(n_loops): - t0s.append(time.time()) - result_c = histogramnd(sample, - histo_range, - n_bins, - weights=weights, - weight_min=weight_min, - weight_max=weight_max, - last_bin_closed=last_bin_closed) - t1s.append(time.time()) - if do_numpy: - result_np = np.histogramdd(sample, - bins=n_bins, - range=histo_range) - t2s.append(time.time()) - result_np_w = np.histogramdd(sample, - bins=n_bins, - range=histo_range, - weights=weights) - t3s.append(time.time()) - else: - result_np = None - result_np_w = None - t2s.append(0) - t3s.append(0) - - commpare_results('Run {0}'.format(i), - [t1s[-1] - t0s[-1], t2s[-1] - t1s[-1], t3s[-1] - t2s[-1]], - result_c, - result_np, - result_np_w, - sample, - weights) - - print_times(np.array(t0s), np.array(t1s), np.array(t2s), np.array(t3s)) - - -def run_benchmark(dtype=np.double, - do_weights=True, - do_numpy=True): - n_loops = 5 - - weights_rng = [0., 100.] - sample_rng = [0., 100.] - - weight_min = None - weight_max = None - last_bin_closed = True - - # ==================================================== - # ==================================================== - # 1D - # ==================================================== - # ==================================================== - - print('==========================') - print(' 1D [{0}]'.format(dtype)) - print('==========================') - sample_shape = (10**7,) - histo_range = [[0., 100.]] - n_bins = 30 - - benchmark(n_loops, - sample_shape, - sample_rng, - weights_rng, - histo_range, - n_bins, - weight_min, - weight_max, - last_bin_closed, - dtype=dtype, - do_weights=True, - do_numpy=do_numpy) - - # ==================================================== - # ==================================================== - # 2D - # ==================================================== - # ==================================================== - - print('==========================') - print(' 2D [{0}]'.format(dtype)) - print('==========================') - sample_shape = (10**7, 2) - histo_range = [[0., 100.], [0., 100.]] - n_bins = 30 - - benchmark(n_loops, - sample_shape, - sample_rng, - weights_rng, - histo_range, - n_bins, - weight_min, - weight_max, - last_bin_closed, - dtype=dtype, - do_weights=True, - do_numpy=do_numpy) - - # ==================================================== - # ==================================================== - # 3D - # ==================================================== - # ==================================================== - - print('==========================') - print(' 3D [{0}]'.format(dtype)) - print('==========================') - sample_shape = (10**7, 3) - histo_range = np.array([[0., 100.], [0., 100.], [0., 100.]]) - n_bins = 30 - - benchmark(n_loops, - sample_shape, - sample_rng, - weights_rng, - histo_range, - n_bins, - weight_min, - weight_max, - last_bin_closed, - dtype=dtype, - do_weights=True, - do_numpy=do_numpy) - -if __name__ == '__main__': - types = (np.double, np.int32, np.float32,) - - for t in types: - run_benchmark(t, - do_weights=True, - do_numpy=True) diff --git a/silx/math/test/test_HistogramndLut_nominal.py b/silx/math/test/test_HistogramndLut_nominal.py deleted file mode 100644 index 08ca682..0000000 --- a/silx/math/test/test_HistogramndLut_nominal.py +++ /dev/null @@ -1,587 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-2019 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. -# -# ############################################################################*/ -""" -Nominal tests of the HistogramndLut function. -""" - -import unittest - -import numpy as np - -from silx.math import HistogramndLut - - -def _get_bin_edges(histo_range, n_bins, n_dims): - edges = [] - for i_dim in range(n_dims): - edges.append(histo_range[i_dim, 0] + - np.arange(n_bins[i_dim] + 1) * - (histo_range[i_dim, 1] - histo_range[i_dim, 0]) / - n_bins[i_dim]) - return tuple(edges) - - -# ============================================================== -# ============================================================== -# ============================================================== - - -class _TestHistogramndLut_nominal(unittest.TestCase): - """ - Unit tests of the HistogramndLut class. - """ - - ndims = None - - def setUp(self): - ndims = self.ndims - self.tested_dim = ndims-1 - - if ndims is None: - raise ValueError('ndims class member not set.') - - sample = np.array([5.5, -3.3, - 0., -0.5, - 3.3, 8.8, - -7.7, 6.0, - -4.0]) - - weights = np.array([500.5, -300.3, - 0.01, -0.5, - 300.3, 800.8, - -700.7, 600.6, - -400.4]) - - n_elems = len(sample) - - if ndims == 1: - shape = (n_elems,) - else: - shape = (n_elems, ndims) - - self.sample = np.zeros(shape=shape, dtype=sample.dtype) - if ndims == 1: - self.sample = sample - else: - self.sample[..., ndims-1] = sample - - self.weights = weights - - # the tests are performed along one dimension, - # all the other bins indices along the other dimensions - # are expected to be 2 - # (e.g : when testing a 2D sample : [0, x] will go into - # bin [2, y] because of the bin ranges [-2, 2] and n_bins = 4 - # for the first dimension) - self.other_axes_index = 2 - self.histo_range = np.repeat([[-2., 2.]], ndims, axis=0) - self.histo_range[ndims-1] = [-4., 6.] - - self.n_bins = np.array([4]*ndims) - self.n_bins[ndims-1] = 5 - - if ndims == 1: - def fill_histo(h, v, dim, op=None): - if op: - h[:] = op(h[:], v) - else: - h[:] = v - self.fill_histo = fill_histo - else: - def fill_histo(h, v, dim, op=None): - idx = [self.other_axes_index]*len(h.shape) - idx[dim] = slice(0, None) - idx = tuple(idx) - if op: - h[idx] = op(h[idx], v) - else: - h[idx] = v - self.fill_histo = fill_histo - - def test_nominal_bin_edges(self): - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - bin_edges = instance.bins_edges - - expected_edges = _get_bin_edges(self.histo_range, - self.n_bins, - self.ndims) - - for i_edges, edges in enumerate(expected_edges): - self.assertTrue(np.array_equal(bin_edges[i_edges], - expected_edges[i_edges]), - msg='Testing bin_edges for dim {0}' - ''.format(i_edges+1)) - - def test_nominal_histo_range(self): - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - histo_range = instance.histo_range - - self.assertTrue(np.array_equal(histo_range, self.histo_range)) - - def test_nominal_last_bin_closed(self): - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - last_bin_closed = instance.last_bin_closed - - self.assertEqual(last_bin_closed, False) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins, - last_bin_closed=True) - - last_bin_closed = instance.last_bin_closed - - self.assertEqual(last_bin_closed, True) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins, - last_bin_closed=False) - - last_bin_closed = instance.last_bin_closed - - self.assertEqual(last_bin_closed, False) - - def test_nominal_n_bins_array(self): - - test_n_bins = np.arange(self.ndims) + 10 - instance = HistogramndLut(self.sample, - self.histo_range, - test_n_bins) - - n_bins = instance.n_bins - - self.assertTrue(np.array_equal(test_n_bins, n_bins)) - - def test_nominal_n_bins_scalar(self): - - test_n_bins = 10 - expected_n_bins = np.array([test_n_bins] * self.ndims) - instance = HistogramndLut(self.sample, - self.histo_range, - test_n_bins) - - n_bins = instance.n_bins - - self.assertTrue(np.array_equal(expected_n_bins, n_bins)) - - def test_nominal_histo_ref(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - instance.accumulate(self.weights) - - histo = instance.histo() - w_histo = instance.weighted_histo() - histo_ref = instance.histo(copy=False) - w_histo_ref = instance.weighted_histo(copy=False) - - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - self.assertTrue(np.array_equal(histo_ref, expected_h)) - self.assertTrue(np.array_equal(w_histo_ref, expected_c)) - - histo_ref[0, ...] = histo_ref[0, ...] + 10 - w_histo_ref[0, ...] = w_histo_ref[0, ...] + 20 - - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - self.assertFalse(np.array_equal(histo_ref, expected_h)) - self.assertFalse(np.array_equal(w_histo_ref, expected_c)) - - histo_2 = instance.histo() - w_histo_2 = instance.weighted_histo() - - self.assertFalse(np.array_equal(histo_2, expected_h)) - self.assertFalse(np.array_equal(w_histo_2, expected_c)) - self.assertTrue(np.array_equal(histo_2, histo_ref)) - self.assertTrue(np.array_equal(w_histo_2, w_histo_ref)) - - def test_nominal_accumulate_once(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - instance.accumulate(self.weights) - - histo = instance.histo() - w_histo = instance.weighted_histo() - - self.assertEqual(w_histo.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - self.assertTrue(np.array_equal(instance.histo(), expected_h)) - self.assertTrue(np.array_equal(instance.weighted_histo(), - expected_c)) - - def test_nominal_accumulate_twice(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - # calling accumulate twice - expected_h *= 2 - expected_c *= 2 - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - instance.accumulate(self.weights) - - instance.accumulate(self.weights) - - histo = instance.histo() - w_histo = instance.weighted_histo() - - self.assertEqual(w_histo.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - self.assertTrue(np.array_equal(instance.histo(), expected_h)) - self.assertTrue(np.array_equal(instance.weighted_histo(), - expected_c)) - - def test_nominal_apply_lut_once(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - histo, w_histo = instance.apply_lut(self.weights) - - self.assertEqual(w_histo.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - self.assertEqual(instance.histo(), None) - self.assertEqual(instance.weighted_histo(), None) - - def test_nominal_apply_lut_twice(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - # calling apply_lut twice - expected_h *= 2 - expected_c *= 2 - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - histo, w_histo = instance.apply_lut(self.weights) - histo_2, w_histo_2 = instance.apply_lut(self.weights, - histo=histo, - weighted_histo=w_histo) - - self.assertEqual(id(histo), id(histo_2)) - self.assertEqual(id(w_histo), id(w_histo_2)) - self.assertEqual(w_histo.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - self.assertEqual(instance.histo(), None) - self.assertEqual(instance.weighted_histo(), None) - - def test_nominal_accumulate_last_bin_closed(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 2]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 1101.1]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins, - last_bin_closed=True) - - instance.accumulate(self.weights) - - histo = instance.histo() - w_histo = instance.weighted_histo() - - self.assertEqual(w_histo.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - - def test_nominal_accumulate_weight_min_max(self): - """ - """ - weight_min = -299.9 - weight_max = 499.9 - - expected_h_tpl = np.array([0, 1, 1, 1, 0]) - expected_c_tpl = np.array([0., -0.5, 0.01, 300.3, 0.]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - instance.accumulate(self.weights, - weight_min=weight_min, - weight_max=weight_max) - - histo = instance.histo() - w_histo = instance.weighted_histo() - - self.assertEqual(w_histo.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - - def test_nominal_accumulate_forced_int32(self): - """ - double weights, int32 weighted_histogram - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700, 0, 0, 300, 500]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins, - dtype=np.int32) - - instance.accumulate(self.weights) - - histo = instance.histo() - w_histo = instance.weighted_histo() - - self.assertEqual(w_histo.dtype, np.int32) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - - def test_nominal_accumulate_forced_float32(self): - """ - int32 weights, float32 weighted_histogram - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700., 0., 0., 300., 500.]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.float32) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins, - dtype=np.float32) - - instance.accumulate(self.weights.astype(np.int32)) - - histo = instance.histo() - w_histo = instance.weighted_histo() - - self.assertEqual(w_histo.dtype, np.float32) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - - def test_nominal_accumulate_int32(self): - """ - int32 weights - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700, 0, 0, 300, 500]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.int32) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - instance.accumulate(self.weights.astype(np.int32)) - - histo = instance.histo() - w_histo = instance.weighted_histo() - - self.assertEqual(w_histo.dtype, np.int32) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - - def test_nominal_accumulate_int32_double(self): - """ - int32 weights - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700, 0, 0, 300, 500]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.int32) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - instance = HistogramndLut(self.sample, - self.histo_range, - self.n_bins) - - instance.accumulate(self.weights.astype(np.int32)) - instance.accumulate(self.weights) - - histo = instance.histo() - w_histo = instance.weighted_histo() - - expected_h *= 2 - expected_c *= 2 - - self.assertEqual(w_histo.dtype, np.int32) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(w_histo, expected_c)) - - def testNoneNativeTypes(self): - type = self.sample.dtype.newbyteorder("B") - sampleB = self.sample.astype(type) - - type = self.sample.dtype.newbyteorder("L") - sampleL = self.sample.astype(type) - - histo_inst = HistogramndLut(sampleB, - self.histo_range, - self.n_bins) - - histo_inst = HistogramndLut(sampleL, - self.histo_range, - self.n_bins) - - -class TestHistogramndLut_nominal_1d(_TestHistogramndLut_nominal): - ndims = 1 - - -class TestHistogramndLut_nominal_2d(_TestHistogramndLut_nominal): - ndims = 2 - - -class TestHistogramndLut_nominal_3d(_TestHistogramndLut_nominal): - ndims = 3 - - -# ============================================================== -# ============================================================== -# ============================================================== - - -test_cases = (TestHistogramndLut_nominal_1d, - TestHistogramndLut_nominal_2d, - TestHistogramndLut_nominal_3d,) - - -def suite(): - loader = unittest.defaultTestLoader - test_suite = unittest.TestSuite() - for test_class in test_cases: - tests = loader.loadTestsFromTestCase(test_class) - test_suite.addTests(tests) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/test/test_calibration.py b/silx/math/test/test_calibration.py deleted file mode 100644 index 5a0c20e..0000000 --- a/silx/math/test/test_calibration.py +++ /dev/null @@ -1,158 +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. -# -# ############################################################################*/ -"""Tests of the calibration module""" - -from __future__ import division - -__authors__ = ["P. Knobel"] -__license__ = "MIT" -__date__ = "14/05/2018" - - -import unittest - -import numpy - -from silx.math.calibration import NoCalibration, LinearCalibration, \ - ArrayCalibration, FunctionCalibration - - -X = numpy.array([3.14, 2.73, 1337]) - - -class TestNoCalibration(unittest.TestCase): - def setUp(self): - self.calib = NoCalibration() - - def testIsAffine(self): - self.assertTrue(self.calib.is_affine()) - - def testSlope(self): - self.assertEqual(self.calib.get_slope(), 1.) - - def testYIntercept(self): - self.assertEqual(self.calib(0.), - 0.) - - def testCall(self): - self.assertTrue(numpy.array_equal(self.calib(X), X)) - - -class TestLinearCalibration(unittest.TestCase): - def setUp(self): - self.y_intercept = 1.5 - self.slope = 2.5 - self.calib = LinearCalibration(y_intercept=self.y_intercept, - slope=self.slope) - - def testIsAffine(self): - self.assertTrue(self.calib.is_affine()) - - def testSlope(self): - self.assertEqual(self.calib.get_slope(), self.slope) - - def testYIntercept(self): - self.assertEqual(self.calib(0.), - self.y_intercept) - - def testCall(self): - self.assertTrue(numpy.array_equal(self.calib(X), - self.y_intercept + self.slope * X)) - - -class TestArrayCalibration(unittest.TestCase): - def setUp(self): - self.arr = numpy.array([45.2, 25.3, 666., -8.]) - self.calib = ArrayCalibration(self.arr) - self.affine_calib = ArrayCalibration([0.1, 0.2, 0.3]) - - def testIsAffine(self): - self.assertFalse(self.calib.is_affine()) - self.assertTrue(self.affine_calib.is_affine()) - - def testSlope(self): - with self.assertRaises(AttributeError): - self.calib.get_slope() - self.assertEqual(self.affine_calib.get_slope(), - 0.1) - - def testYIntercept(self): - self.assertEqual(self.calib(0), - self.arr[0]) - - def testCall(self): - with self.assertRaises(ValueError): - # X is an array with a different shape - self.calib(X) - - with self.assertRaises(ValueError): - # floats are not valid indices - self.calib(3.14) - - self.assertTrue( - numpy.array_equal(self.calib([1, 2, 3, 4]), - self.arr)) - - for idx, value in enumerate(self.arr): - self.assertEqual(self.calib(idx), value) - - -class TestFunctionCalibration(unittest.TestCase): - def setUp(self): - self.non_affine_fun = numpy.sin - self.non_affine_calib = FunctionCalibration(self.non_affine_fun) - - self.affine_fun = lambda x: 52. * x + 0.01 - self.affine_calib = FunctionCalibration(self.affine_fun, - is_affine=True) - - def testIsAffine(self): - self.assertFalse(self.non_affine_calib.is_affine()) - self.assertTrue(self.affine_calib.is_affine()) - - def testSlope(self): - with self.assertRaises(AttributeError): - self.non_affine_calib.get_slope() - self.assertAlmostEqual(self.affine_calib.get_slope(), - 52.) - - def testCall(self): - for x in X: - self.assertAlmostEqual(self.non_affine_calib(x), - self.non_affine_fun(x)) - self.assertAlmostEqual(self.affine_calib(x), - self.affine_fun(x)) - - -def suite(): - test_suite = unittest.TestSuite() - loadTests = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite.addTest(loadTests(TestNoCalibration)) - test_suite.addTest(loadTests(TestArrayCalibration)) - test_suite.addTest(loadTests(TestLinearCalibration)) - test_suite.addTest(loadTests(TestFunctionCalibration)) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/test/test_colormap.py b/silx/math/test/test_colormap.py deleted file mode 100644 index 4e80710..0000000 --- a/silx/math/test/test_colormap.py +++ /dev/null @@ -1,266 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018-2020 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. -# -# ############################################################################*/ -"""Test for colormap mapping implementation""" - -from __future__ import division - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "16/05/2018" - - -import logging -import sys -import unittest - -import numpy - -from silx.utils.testutils import ParametricTestCase -from silx.math import colormap - - -_logger = logging.getLogger(__name__) - - -class TestNormalization(ParametricTestCase): - """Test silx.math.colormap.Normalization sub classes""" - - def _testCodec(self, normalization, rtol=1e-5): - """Test apply/revert for normalizations""" - test_data = (numpy.arange(1, 10, dtype=numpy.int32), - numpy.linspace(1., 100., 1000, dtype=numpy.float32), - numpy.linspace(-1., 1., 100, dtype=numpy.float32), - 1., - 1) - - for index in range(len(test_data)): - with self.subTest(normalization=normalization, data_index=index): - data = test_data[index] - normalized = normalization.apply(data, 1., 100.) - result = normalization.revert(normalized, 1., 100.) - - self.assertTrue(numpy.array_equal( - numpy.isnan(normalized), numpy.isnan(result))) - - if isinstance(data, numpy.ndarray): - notNaN = numpy.logical_not(numpy.isnan(result)) - data = data[notNaN] - result = result[notNaN] - self.assertTrue(numpy.allclose(data, result, rtol=rtol)) - - def testLinearNormalization(self): - """Test for LinearNormalization""" - normalization = colormap.LinearNormalization() - self._testCodec(normalization) - - def testLogarithmicNormalization(self): - """Test for LogarithmicNormalization""" - normalization = colormap.LogarithmicNormalization() - # relative tolerance is higher because of the log approximation - self._testCodec(normalization, rtol=1e-3) - - # Specific extra tests - self.assertTrue(numpy.isnan(normalization.apply(-1., 1., 100.))) - self.assertTrue(numpy.isnan(normalization.apply(numpy.nan, 1., 100.))) - self.assertEqual(normalization.apply(numpy.inf, 1., 100.), numpy.inf) - self.assertEqual(normalization.apply(0, 1., 100.), - numpy.inf) - - def testArcsinhNormalization(self): - """Test for ArcsinhNormalization""" - self._testCodec(colormap.ArcsinhNormalization()) - - def testSqrtNormalization(self): - """Test for SqrtNormalization""" - normalization = colormap.SqrtNormalization() - self._testCodec(normalization) - - # Specific extra tests - self.assertTrue(numpy.isnan(normalization.apply(-1., 0., 100.))) - self.assertTrue(numpy.isnan(normalization.apply(numpy.nan, 0., 100.))) - self.assertEqual(normalization.apply(numpy.inf, 0., 100.), numpy.inf) - self.assertEqual(normalization.apply(0, 0., 100.), 0.) - - -class TestColormap(ParametricTestCase): - """Test silx.math.colormap.cmap""" - - NORMALIZATIONS = ( - 'linear', - 'log', - 'arcsinh', - 'sqrt', - colormap.LinearNormalization(), - colormap.LogarithmicNormalization(), - colormap.PowerNormalization(2.), - colormap.PowerNormalization(0.5)) - - @staticmethod - def ref_colormap(data, colors, vmin, vmax, normalization, nan_color): - """Reference implementation of colormap - - :param numpy.ndarray data: Data to convert - :param numpy.ndarray colors: Color look-up-table - :param float vmin: Lower bound of the colormap range - :param float vmax: Upper bound of the colormap range - :param str normalization: Normalization to use - :param Union[numpy.ndarray, None] nan_color: Color to use for NaN - """ - norm_functions = {'linear': lambda v: v, - 'log': numpy.log10, - 'arcsinh': numpy.arcsinh, - 'sqrt': numpy.sqrt} - - if isinstance(normalization, str): - norm_function = norm_functions[normalization] - else: - def norm_function(value): - return normalization.apply(value, vmin, vmax) - - with numpy.errstate(divide='ignore', invalid='ignore'): - # Ignore divide by zero and invalid value encountered in log10, sqrt - norm_data, vmin, vmax = map(norm_function, (data, vmin, vmax)) - - if normalization == 'arcsinh' and sys.platform == 'win32': - # There is a difference of behavior of numpy.arcsinh - # between Windows and other OS for results of infinite values - # This makes Windows behaves as Linux and MacOS - norm_data[data == numpy.inf] = numpy.inf - norm_data[data == -numpy.inf] = -numpy.inf - - nb_colors = len(colors) - scale = nb_colors / (vmax - vmin) - - # Substraction must be done in float to avoid overflow with uint - indices = numpy.clip(scale * (norm_data - float(vmin)), - 0, nb_colors - 1) - indices[numpy.isnan(indices)] = nb_colors # Use an extra index for NaN - indices = indices.astype('uint') - - # Add NaN color to array - if nan_color is None: - nan_color = (0,) * colors.shape[-1] - colors = numpy.append(colors, numpy.atleast_2d(nan_color), axis=0) - - return colors[indices] - - def _test(self, data, colors, vmin, vmax, normalization, nan_color): - """Run test of colormap against alternative implementation - - :param numpy.ndarray data: Data to convert - :param numpy.ndarray colors: Color look-up-table - :param float vmin: Lower bound of the colormap range - :param float vmax: Upper bound of the colormap range - :param str normalization: Normalization to use - :param Union[numpy.ndarray, None] nan_color: Color to use for NaN - """ - image = colormap.cmap( - data, colors, vmin, vmax, normalization, nan_color) - - ref_image = self.ref_colormap( - data, colors, vmin, vmax, normalization, nan_color) - - self.assertTrue(numpy.allclose(ref_image, image)) - self.assertEqual(image.dtype, colors.dtype) - self.assertEqual(image.shape, data.shape + (colors.shape[-1],)) - - def test(self): - """Test all dtypes with finite data - - Test all supported types and endianness - """ - colors = numpy.zeros((256, 4), dtype=numpy.uint8) - colors[:, 0] = numpy.arange(len(colors)) - colors[:, 3] = 255 - - # Generates (u)int and floats types - dtypes = [e + k + i for e in '<>' for k in 'uif' for i in '1248' - if k != 'f' or i != '1'] - dtypes.append(numpy.dtype(numpy.longdouble).name) # Add long double - - for normalization in self.NORMALIZATIONS: - for dtype in dtypes: - with self.subTest(dtype=dtype, normalization=normalization): - _logger.info('normalization: %s, dtype: %s', - normalization, dtype) - data = numpy.arange(-5, 15, dtype=dtype).reshape(4, 5) - - self._test(data, colors, 1, 10, normalization, None) - - def test_not_finite(self): - """Test float data with not finite values""" - colors = numpy.zeros((256, 4), dtype=numpy.uint8) - colors[:, 0] = numpy.arange(len(colors)) - colors[:, 3] = 255 - - test_data = { # message: data - 'no finite values': (float('inf'), float('-inf'), float('nan')), - 'only NaN': (float('nan'), float('nan'), float('nan')), - 'mix finite/not finite': (float('inf'), float('-inf'), 1., float('nan')), - } - - for normalization in self.NORMALIZATIONS: - for msg, data in test_data.items(): - with self.subTest(msg, normalization=normalization): - _logger.info('normalization: %s, %s', normalization, msg) - data = numpy.array(data, dtype=numpy.float64) - self._test(data, colors, 1, 10, normalization, (0, 0, 0, 0)) - - def test_errors(self): - """Test raising exception for bad vmin, vmax, normalization parameters - """ - colors = numpy.zeros((256, 4), dtype=numpy.uint8) - colors[:, 0] = numpy.arange(len(colors)) - colors[:, 3] = 255 - - data = numpy.arange(10, dtype=numpy.float64) - - test_params = [ # (vmin, vmax, normalization) - (-1., 2., 'log'), - (0., 1., 'log'), - (1., 0., 'log'), - (-1., 1., 'sqrt'), - (1., -1., 'sqrt'), - ] - - for vmin, vmax, normalization in test_params: - with self.subTest( - vmin=vmin, vmax=vmax, normalization=normalization): - _logger.info('normalization: %s, range: [%f, %f]', - normalization, vmin, vmax) - with self.assertRaises(ValueError): - self._test(data, colors, vmin, vmax, normalization, None) - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(TestColormap)) - test_suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(TestNormalization)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/math/test/test_combo.py b/silx/math/test/test_combo.py deleted file mode 100644 index 1335763..0000000 --- a/silx/math/test/test_combo.py +++ /dev/null @@ -1,218 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-2020 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. -# -# ############################################################################*/ -"""Tests of the combo module""" - -from __future__ import division - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "17/01/2018" - - -import unittest - -import numpy - -from silx.utils.testutils import ParametricTestCase - -from silx.math.combo import min_max - - -class TestMinMax(ParametricTestCase): - """Tests of min max combo""" - - FLOATING_DTYPES = 'float32', 'float64' - if hasattr(numpy, "float128"): - FLOATING_DTYPES += ('float128',) - SIGNED_INT_DTYPES = 'int8', 'int16', 'int32', 'int64' - UNSIGNED_INT_DTYPES = 'uint8', 'uint16', 'uint32', 'uint64' - DTYPES = FLOATING_DTYPES + SIGNED_INT_DTYPES + UNSIGNED_INT_DTYPES - - def _numpy_min_max(self, data, min_positive=False, finite=False): - """Reference numpy implementation of min_max - - :param numpy.ndarray data: Data set to use for test - :param bool min_positive: True to test with positive min - :param bool finite: True to only test finite values - """ - data = numpy.array(data, copy=False) - if data.size == 0: - raise ValueError('Zero-sized array') - - minimum = None - argmin = None - maximum = None - argmax = None - min_pos = None - argmin_pos = None - - if finite: - filtered_data = data[numpy.isfinite(data)] - else: - filtered_data = data - - if filtered_data.size > 0: - if numpy.all(numpy.isnan(filtered_data)): - minimum = numpy.nan - argmin = 0 - maximum = numpy.nan - argmax = 0 - else: - minimum = numpy.nanmin(filtered_data) - # nanargmin equivalent - argmin = numpy.where(data == minimum)[0][0] - maximum = numpy.nanmax(filtered_data) - # nanargmax equivalent - argmax = numpy.where(data == maximum)[0][0] - - if min_positive: - with numpy.errstate(invalid='ignore'): - # Ignore invalid value encountered in greater - pos_data = filtered_data[filtered_data > 0] - if pos_data.size > 0: - min_pos = numpy.min(pos_data) - argmin_pos = numpy.where(data == min_pos)[0][0] - - return minimum, min_pos, maximum, argmin, argmin_pos, argmax - - def _test_min_max(self, data, min_positive, finite=False): - """Compare min_max with numpy for the given dataset - - :param numpy.ndarray data: Data set to use for test - :param bool min_positive: True to test with positive min - :param bool finite: True to only test finite values - """ - minimum, min_pos, maximum, argmin, argmin_pos, argmax = \ - self._numpy_min_max(data, min_positive, finite) - - result = min_max(data, min_positive, finite) - - self.assertSimilar(minimum, result.minimum) - self.assertSimilar(min_pos, result.min_positive) - self.assertSimilar(maximum, result.maximum) - self.assertSimilar(argmin, result.argmin) - self.assertSimilar(argmin_pos, result.argmin_positive) - self.assertSimilar(argmax, result.argmax) - - def assertSimilar(self, a, b): - """Assert that a and b are both None or NaN or that a == b.""" - self.assertTrue((a is None and b is None) or - (numpy.isnan(a) and numpy.isnan(b)) or - a == b) - - def test_different_datasets(self): - """Test min_max with different numpy.arange datasets.""" - size = 1000 - - for dtype in self.DTYPES: - - tests = { - '0 to N': (0, 1), - 'N-1 to 0': (size - 1, -1)} - if dtype not in self.UNSIGNED_INT_DTYPES: - tests['N/2 to -N/2'] = size // 2, -1 - tests['0 to -N'] = 0, -1 - - for name, (start, step) in tests.items(): - for min_positive in (True, False): - with self.subTest(dtype=dtype, - min_positive=min_positive, - data=name): - data = numpy.arange( - start, start + step * size, step, dtype=dtype) - - self._test_min_max(data, min_positive) - - def test_nodata(self): - """Test min_max with None and empty array""" - for dtype in self.DTYPES: - with self.subTest(dtype=dtype): - with self.assertRaises(TypeError): - min_max(None) - - data = numpy.array((), dtype=dtype) - with self.assertRaises(ValueError): - min_max(data) - - NAN_TEST_DATA = [ - (float('nan'), float('nan')), # All NaNs - (float('nan'), 1.0), # NaN first and positive - (float('nan'), -1.0), # NaN first and negative - (1.0, 2.0, float('nan')), # NaN last and positive - (-1.0, -2.0, float('nan')), # NaN last and negative - (1.0, float('nan'), -1.0), # Some NaN - ] - - def test_nandata(self): - """Test min_max with NaN in data""" - for dtype in self.FLOATING_DTYPES: - for data in self.NAN_TEST_DATA: - with self.subTest(dtype=dtype, data=data): - data = numpy.array(data, dtype=dtype) - self._test_min_max(data, min_positive=True) - - INF_TEST_DATA = [ - [float('inf')] * 3, # All +inf - [float('-inf')] * 3, # All -inf - (float('inf'), float('-inf')), # + and - inf - (float('inf'), float('-inf'), float('nan')), # +/-inf, nan last - (float('nan'), float('-inf'), float('inf')), # +/-inf, nan first - (float('inf'), float('nan'), float('-inf')), # +/-inf, nan center - ] - - def test_infdata(self): - """Test min_max with inf.""" - for dtype in self.FLOATING_DTYPES: - for data in self.INF_TEST_DATA: - with self.subTest(dtype=dtype, data=data): - data = numpy.array(data, dtype=dtype) - self._test_min_max(data, min_positive=True) - - def test_finite(self): - """Test min_max with finite=True""" - tests = [ - (-1., 2., 0.), # Basic test - (float('nan'), float('inf'), float('-inf')), # NaN + Inf - (float('nan'), float('inf'), -2, float('-inf')), # NaN + Inf + 1 value - (float('inf'), -3, -2), # values + inf - ] - tests += self.INF_TEST_DATA - tests += self.NAN_TEST_DATA - - for dtype in self.FLOATING_DTYPES: - for data in tests: - with self.subTest(dtype=dtype, data=data): - data = numpy.array(data, dtype=dtype) - self._test_min_max(data, min_positive=True, finite=True) - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTests( - unittest.defaultTestLoader.loadTestsFromTestCase(TestMinMax)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/test/test_histogramnd_error.py b/silx/math/test/test_histogramnd_error.py deleted file mode 100644 index a8c66a0..0000000 --- a/silx/math/test/test_histogramnd_error.py +++ /dev/null @@ -1,535 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ - -__authors__ = ["D. Naudet"] -__license__ = "MIT" -__date__ = "01/02/2016" - -""" -Tests of the histogramnd function, error cases. -""" -import sys -import platform -import unittest - -import numpy as np - -from silx.math.chistogramnd import chistogramnd as histogramnd -from silx.math import Histogramnd - - -# ============================================================== -# ============================================================== -# ============================================================== - - -class _Test_chistogramnd_errors(unittest.TestCase): - """ - Unit tests of the chistogramnd error cases. - """ - def setUp(self): - raise NotImplementedError('') - - def test_weights_shape(self): - """ - """ - - for err_w_shape in self.err_weights_shapes: - test_msg = ('Testing invalid weights shape : {0}' - ''.format(err_w_shape)) - - err_weights = np.random.randint(0, - high=10, - size=err_w_shape) - err_weights = err_weights.astype(np.double) - - ex_str = None - try: - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=err_weights)[0:2] - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str, msg=test_msg) - self.assertEqual(ex_str, - '<weights> must be an array whose length ' - 'is equal to the number of samples.') - - def test_histo_range_shape(self): - """ - """ - n_dims = 1 if len(self.s_shape) == 1 else self.s_shape[1] - expected_txt_tpl = ('<histo_range> error : expected {n_dims} sets ' - 'of lower and upper bin edges, ' - 'got the following instead : {histo_range}. ' - '(provided <sample> contains ' - '{n_dims}D values)') - - for err_histo_range in self.err_histo_range_shapes: - test_msg = ('Testing invalid histo_range shape : {0}' - ''.format(err_histo_range)) - - expected_txt = expected_txt_tpl.format(histo_range=err_histo_range, - n_dims=n_dims) - - ex_str = None - try: - histo, cumul = histogramnd(self.sample, - err_histo_range, - self.n_bins, - weights=self.weights)[0:2] - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str, msg=test_msg) - self.assertEqual(ex_str, expected_txt, msg=test_msg) - - def test_nbins_shape(self): - """ - """ - - expected_txt = ('n_bins must be either a scalar (same number ' - 'of bins for all dimensions) or ' - 'an array (number of bins for each ' - 'dimension).') - - for err_n_bins in self.err_n_bins_shapes: - test_msg = ('Testing invalid n_bins shape : {0}' - ''.format(err_n_bins)) - - ex_str = None - try: - histo, cumul = histogramnd(self.sample, - self.histo_range, - err_n_bins, - weights=self.weights)[0:2] - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str, msg=test_msg) - self.assertEqual(ex_str, expected_txt, msg=test_msg) - - def test_nbins_values(self): - """ - """ - expected_txt = ('<n_bins> : only positive values allowed.') - - for err_n_bins in self.err_n_bins_values: - test_msg = ('Testing invalid n_bins value : {0}' - ''.format(err_n_bins)) - - ex_str = None - try: - histo, cumul = histogramnd(self.sample, - self.histo_range, - err_n_bins, - weights=self.weights)[0:2] - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str, msg=test_msg) - self.assertEqual(ex_str, expected_txt, msg=test_msg) - - def test_histo_shape(self): - """ - """ - for err_h_shape in self.err_histo_shapes: - - # windows & python 2.7 : numpy shapes are long values - if platform.system() == 'Windows': - version = (sys.version_info.major, sys.version_info.minor) - if version <= (2, 7): - err_h_shape = tuple([long(val) for val in err_h_shape]) - - test_msg = ('Testing invalid histo shape : {0}' - ''.format(err_h_shape)) - - expected_txt = ('Provided <histo> array doesn\'t have ' - 'a shape compatible with <n_bins> ' - ': should be {0} instead of {1}.' - ''.format(self.h_shape, err_h_shape)) - - histo = np.zeros(shape=err_h_shape, dtype=np.uint32) - - ex_str = None - try: - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - histo=histo)[0:2] - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str, msg=test_msg) - self.assertEqual(ex_str, expected_txt, msg=test_msg) - - def test_histo_dtype(self): - """ - """ - for err_h_dtype in self.err_histo_dtypes: - test_msg = ('Testing invalid histo dtype : {0}' - ''.format(err_h_dtype)) - - histo = np.zeros(shape=self.h_shape, dtype=err_h_dtype) - - expected_txt = ('Provided <histo> array doesn\'t have ' - 'the expected type ' - ': should be {0} instead of {1}.' - ''.format(np.uint32, histo.dtype)) - - ex_str = None - try: - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - histo=histo)[0:2] - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str, msg=test_msg) - self.assertEqual(ex_str, expected_txt, msg=test_msg) - - def test_weighted_histo_shape(self): - """ - """ - # using the same values as histo - for err_h_shape in self.err_histo_shapes: - - # windows & python 2.7 : numpy shapes are long values - if platform.system() == 'Windows': - version = (sys.version_info.major, sys.version_info.minor) - if version <= (2, 7): - err_h_shape = tuple([long(val) for val in err_h_shape]) - - test_msg = ('Testing invalid weighted_histo shape : {0}' - ''.format(err_h_shape)) - - expected_txt = ('Provided <weighted_histo> array doesn\'t have ' - 'a shape compatible with <n_bins> ' - ': should be {0} instead of {1}.' - ''.format(self.h_shape, err_h_shape)) - - cumul = np.zeros(shape=err_h_shape, dtype=np.double) - - ex_str = None - try: - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - weighted_histo=cumul)[0:2] - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str, msg=test_msg) - self.assertEqual(ex_str, expected_txt, msg=test_msg) - - def test_cumul_dtype(self): - """ - """ - # using the same values as histo - for err_h_dtype in self.err_histo_dtypes: - test_msg = ('Testing invalid weighted_histo dtype : {0}' - ''.format(err_h_dtype)) - - cumul = np.zeros(shape=self.h_shape, dtype=err_h_dtype) - - expected_txt = ('Provided <weighted_histo> array doesn\'t have ' - 'the expected type ' - ': should be {0} or {1} instead of {2}.' - ''.format(np.float64, np.float32, cumul.dtype)) - - ex_str = None - try: - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - weighted_histo=cumul)[0:2] - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str, msg=test_msg) - self.assertEqual(ex_str, expected_txt, msg=test_msg) - - def test_wh_histo_dtype(self): - """ - """ - # using the same values as histo - for err_h_dtype in self.err_histo_dtypes: - test_msg = ('Testing invalid wh_dtype dtype : {0}' - ''.format(err_h_dtype)) - - expected_txt = ('<wh_dtype> type not supported : {0}.' - ''.format(err_h_dtype)) - - ex_str = None - try: - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - wh_dtype=err_h_dtype)[0:2] - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str, msg=test_msg) - self.assertEqual(ex_str, expected_txt, msg=test_msg) - - def test_unmanaged_dtypes(self): - """ - """ - for err_unmanaged_dtype in self.err_unmanaged_dtypes: - test_msg = ('Testing unmanaged dtypes : {0}' - ''.format(err_unmanaged_dtype)) - - sample = self.sample.astype(err_unmanaged_dtype[0]) - weights = self.weights.astype(err_unmanaged_dtype[1]) - - expected_txt = ('Case not supported - sample:{0} ' - 'and weights:{1}.' - ''.format(sample.dtype, - weights.dtype)) - - ex_str = None - try: - histogramnd(sample, - self.histo_range, - self.n_bins, - weights=weights) - except TypeError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str, msg=test_msg) - self.assertEqual(ex_str, expected_txt, msg=test_msg) - - def test_uncontiguous_histo(self): - """ - """ - # non contiguous array - shape = np.array(self.n_bins, ndmin=1) - shape[0] *= 2 - histo_tmp = np.zeros(shape) - histo = histo_tmp[::2, ...] - - expected_txt = ('<histo> must be a C_CONTIGUOUS numpy array.') - - ex_str = None - try: - histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - histo=histo) - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str) - self.assertEqual(ex_str, expected_txt) - - def test_uncontiguous_weighted_histo(self): - """ - """ - # non contiguous array - shape = np.array(self.n_bins, ndmin=1) - shape[0] *= 2 - cumul_tmp = np.zeros(shape) - cumul = cumul_tmp[::2, ...] - - expected_txt = ('<weighted_histo> must be a C_CONTIGUOUS numpy array.') - - ex_str = None - try: - histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - weighted_histo=cumul) - except ValueError as ex: - ex_str = str(ex) - - self.assertIsNotNone(ex_str) - self.assertEqual(ex_str, expected_txt) - - -class Test_chistogramnd_1D_errors(_Test_chistogramnd_errors): - """ - Unit tests of the 1D histogramnd error cases. - """ - - def setUp(self): - # nominal values - self.n_elements = 1000 - self.s_shape = (self.n_elements,) - self.w_shape = (self.n_elements,) - - self.histo_range = [0., 100.] - self.n_bins = 10 - - self.h_shape = (self.n_bins,) - - self.sample = np.random.randint(0, - high=10, - size=self.s_shape) - self.sample = self.sample.astype(np.double) - - self.weights = np.random.randint(0, - high=10, - size=self.w_shape) - self.weights = self.weights.astype(np.double) - - self.err_weights_shapes = ((self.n_elements+1,), - (self.n_elements-1,), - (self.n_elements-1, 3)) - self.err_histo_range_shapes = ([0.], - [0., 1., 2.], - [[0.], [1.]]) - self.err_n_bins_shapes = ([10, 2], - [[10], [2]]) - self.err_n_bins_values = (0, - [-10], - None) - self.err_histo_shapes = ((self.n_bins+1,), - (self.n_bins-1,), - (self.n_bins, self.n_bins)) - # these are used for testing the histo parameter as well - # as the weighted_histo parameter. - self.err_histo_dtypes = (np.uint16, - np.float16) - - self.err_unmanaged_dtypes = ((np.double, np.uint16), - (np.uint16, np.double), - (np.uint16, np.uint16)) - -class Test_chistogramnd_ND_range(unittest.TestCase): - """ - - """ - - def test_invalid_histo_range(self): - data = np.random.random((60, 60)) - nbins = 10 - - with self.assertRaises(ValueError): - histo_range = data.min(), np.inf - - Histogramnd(sample=data.ravel(), - histo_range=histo_range, - n_bins=nbins) - - histo_range = data.min(), np.nan - - Histogramnd(sample=data.ravel(), - histo_range=histo_range, - n_bins=nbins) - - -class Test_chistogramnd_ND_errors(_Test_chistogramnd_errors): - """ - Unit tests of the 3D histogramnd error cases. - """ - - def setUp(self): - # nominal values - self.n_elements = 1000 - self.s_shape = (self.n_elements, 3) - self.w_shape = (self.n_elements,) - - self.histo_range = [[0., 100.], [0., 100.], [0., 100.]] - self.n_bins = (10, 20, 30) - - self.h_shape = self.n_bins - - self.sample = np.random.randint(0, - high=10, - size=self.s_shape) - self.sample = self.sample.astype(np.double) - - self.weights = np.random.randint(0, - high=10, - size=self.w_shape) - self.weights = self.weights.astype(np.double) - - self.err_weights_shapes = ((self.n_elements+1,), - (self.n_elements-1,), - (self.n_elements-1, 3)) - self.err_histo_range_shapes = ([0.], - [0., 1.], - [[0., 10.], [0., 10.]], - [0., 10., 0, 10., 0, 10.]) - self.err_n_bins_shapes = ([10, 2], - [[10], [20], [30]]) - self.err_n_bins_values = (0, - [-10], - [10, 20, -4], - None, - [10, None, 30]) - self.err_histo_shapes = ((self.n_bins[0]+1, - self.n_bins[1], - self.n_bins[2]), - (self.n_bins[0], - self.n_bins[1], - self.n_bins[2]-1), - (self.n_bins[0], - self.n_bins[1]), - (self.n_bins[1], - self.n_bins[0], - self.n_bins[2]), - (self.n_bins[0], - self.n_bins[1], - self.n_bins[2], - 10) - ) - # these are used for testing the histo parameter as well - # as the weighted_histo parameter. - self.err_histo_dtypes = (np.uint16, - np.float16) - - self.err_unmanaged_dtypes = ((np.double, np.uint16), - (np.uint16, np.double), - (np.uint16, np.uint16)) -# ============================================================== -# ============================================================== -# ============================================================== - - -test_cases = (Test_chistogramnd_1D_errors, - Test_chistogramnd_ND_errors, - Test_chistogramnd_ND_range) - - -def suite(): - loader = unittest.defaultTestLoader - test_suite = unittest.TestSuite() - for test_class in test_cases: - tests = loader.loadTestsFromTestCase(test_class) - test_suite.addTests(tests) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/test/test_histogramnd_nominal.py b/silx/math/test/test_histogramnd_nominal.py deleted file mode 100644 index 92febdd..0000000 --- a/silx/math/test/test_histogramnd_nominal.py +++ /dev/null @@ -1,949 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016-2019 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. -# -# ############################################################################*/ -""" -Nominal tests of the histogramnd function. -""" - -import unittest - -import numpy as np - -from silx.math.chistogramnd import chistogramnd as histogramnd -from silx.math import Histogramnd - - -def _get_bin_edges(histo_range, n_bins, n_dims): - edges = [] - for i_dim in range(n_dims): - edges.append(histo_range[i_dim, 0] + - np.arange(n_bins[i_dim] + 1) * - (histo_range[i_dim, 1] - histo_range[i_dim, 0]) / - n_bins[i_dim]) - return tuple(edges) - - -# ============================================================== -# ============================================================== -# ============================================================== - - -class _Test_chistogramnd_nominal(unittest.TestCase): - """ - Unit tests of the histogramnd function. - """ - - ndims = None - - def setUp(self): - ndims = self.ndims - self.tested_dim = ndims-1 - - if ndims is None: - raise ValueError('ndims class member not set.') - - sample = np.array([5.5, -3.3, - 0., -0.5, - 3.3, 8.8, - -7.7, 6.0, - -4.0]) - - weights = np.array([500.5, -300.3, - 0.01, -0.5, - 300.3, 800.8, - -700.7, 600.6, - -400.4]) - - n_elems = len(sample) - - if ndims == 1: - shape = (n_elems,) - else: - shape = (n_elems, ndims) - - self.sample = np.zeros(shape=shape, dtype=sample.dtype) - if ndims == 1: - self.sample = sample - else: - self.sample[..., ndims-1] = sample - - self.weights = weights - - # the tests are performed along one dimension, - # all the other bins indices along the other dimensions - # are expected to be 2 - # (e.g : when testing a 2D sample : [0, x] will go into - # bin [2, y] because of the bin ranges [-2, 2] and n_bins = 4 - # for the first dimension) - self.other_axes_index = 2 - self.histo_range = np.repeat([[-2., 2.]], ndims, axis=0) - self.histo_range[ndims-1] = [-4., 6.] - - self.n_bins = np.array([4]*ndims) - self.n_bins[ndims-1] = 5 - - if ndims == 1: - def fill_histo(h, v, dim, op=None): - if op: - h[:] = op(h[:], v) - else: - h[:] = v - self.fill_histo = fill_histo - else: - def fill_histo(h, v, dim, op=None): - idx = [self.other_axes_index]*len(h.shape) - idx[dim] = slice(0, None) - idx = tuple(idx) - if op: - h[idx] = op(h[idx], v) - else: - h[idx] = v - self.fill_histo = fill_histo - - def test_nominal(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo, cumul, bin_edges = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights) - - expected_edges = _get_bin_edges(self.histo_range, - self.n_bins, - self.ndims) - - self.assertEqual(cumul.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - for i_edges, edges in enumerate(expected_edges): - self.assertTrue(np.array_equal(bin_edges[i_edges], - expected_edges[i_edges]), - msg='Testing bin_edges for dim {0}' - ''.format(i_edges+1)) - - def test_nominal_wh_dtype(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.float32) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo, cumul, bin_edges = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - wh_dtype=np.float32) - - self.assertEqual(cumul.dtype, np.float32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.allclose(cumul, expected_c)) - - def test_nominal_uncontiguous_sample(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - shape = list(self.sample.shape) - shape[0] *= 2 - sample = np.zeros(shape, dtype=self.sample.dtype) - uncontig_sample = sample[::2, ...] - uncontig_sample[:] = self.sample - - self.assertFalse(uncontig_sample.flags['C_CONTIGUOUS'], - msg='Making sure the array is not contiguous.') - - histo, cumul, bin_edges = histogramnd(uncontig_sample, - self.histo_range, - self.n_bins, - weights=self.weights) - - self.assertEqual(cumul.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - def test_nominal_uncontiguous_weights(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - shape = list(self.weights.shape) - shape[0] *= 2 - weights = np.zeros(shape, dtype=self.weights.dtype) - uncontig_weights = weights[::2, ...] - uncontig_weights[:] = self.weights - - self.assertFalse(uncontig_weights.flags['C_CONTIGUOUS'], - msg='Making sure the array is not contiguous.') - - histo, cumul, bin_edges = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=uncontig_weights) - - self.assertEqual(cumul.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - def test_nominal_wo_weights(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=None)[0:2] - - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(cumul is None) - - def test_nominal_wo_weights_w_cumul(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - - # creating an array of ones just to make sure that - # it is not cleared by histogramnd - cumul_in = np.ones(self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=None, - weighted_histo=cumul_in)[0:2] - - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(cumul is None) - self.assertTrue(np.array_equal(cumul_in, - np.ones(shape=self.n_bins, - dtype=np.double))) - - def test_nominal_wo_weights_w_histo(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - - # creating an array of ones just to make sure that - # it is not cleared by histogramnd - histo_in = np.ones(self.n_bins, dtype=np.uint32) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=None, - histo=histo_in)[0:2] - - self.assertTrue(np.array_equal(histo, expected_h + 1)) - self.assertTrue(cumul is None) - self.assertEqual(id(histo), id(histo_in)) - - def test_nominal_last_bin_closed(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 2]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 1101.1]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=True)[0:2] - - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - def test_int32_weights_double_weights_range(self): - """ - """ - weight_min = -299.9 # ===> will be cast to -299 - weight_max = 499.9 # ===> will be cast to 499 - - expected_h_tpl = np.array([0, 1, 1, 1, 0]) - expected_c_tpl = np.array([0., 0., 0., 300., 0.]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights.astype(np.int32), - weight_min=weight_min, - weight_max=weight_max)[0:2] - - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - def test_reuse_histo(self): - """ - """ - - expected_h_tpl = np.array([2, 3, 2, 2, 2]) - expected_c_tpl = np.array([0.0, -7007, -5.0, 0.1, 3003.0]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights)[0:2] - - sample_2 = self.sample[:] - if len(sample_2.shape) == 1: - idx = (slice(0, None),) - else: - idx = slice(0, None), self.tested_dim - - sample_2[idx] += 2 - - histo_2, cumul = histogramnd(sample_2, # <==== !! - self.histo_range, - self.n_bins, - weights=10 * self.weights, # <==== !! - histo=histo)[0:2] - - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - self.assertEqual(id(histo), id(histo_2)) - - def test_reuse_cumul(self): - """ - """ - - expected_h_tpl = np.array([0, 2, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -7007.5, -4.99, 300.4, 3503.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights)[0:2] - - sample_2 = self.sample[:] - if len(sample_2.shape) == 1: - idx = (slice(0, None),) - else: - idx = slice(0, None), self.tested_dim - - sample_2[idx] += 2 - - histo, cumul_2 = histogramnd(sample_2, # <==== !! - self.histo_range, - self.n_bins, - weights=10 * self.weights, # <==== !! - weighted_histo=cumul)[0:2] - - self.assertEqual(cumul.dtype, np.float64) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.allclose(cumul, expected_c, rtol=10e-15)) - self.assertEqual(id(cumul), id(cumul_2)) - - def test_reuse_cumul_float(self): - """ - """ - - expected_h_tpl = np.array([0, 2, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -7007.5, -4.99, 300.4, 3503.5], - dtype=np.float32) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo, cumul = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights)[0:2] - - # converting the cumul array to float - cumul = cumul.astype(np.float32) - - sample_2 = self.sample[:] - if len(sample_2.shape) == 1: - idx = (slice(0, None),) - else: - idx = slice(0, None), self.tested_dim - - sample_2[idx] += 2 - - histo, cumul_2 = histogramnd(sample_2, # <==== !! - self.histo_range, - self.n_bins, - weights=10 * self.weights, # <==== !! - weighted_histo=cumul)[0:2] - - self.assertEqual(cumul.dtype, np.float32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertEqual(id(cumul), id(cumul_2)) - self.assertTrue(np.allclose(cumul, expected_c, rtol=10e-15)) - -class _Test_Histogramnd_nominal(unittest.TestCase): - """ - Unit tests of the Histogramnd class. - """ - - ndims = None - - def setUp(self): - ndims = self.ndims - self.tested_dim = ndims-1 - - if ndims is None: - raise ValueError('ndims class member not set.') - - sample = np.array([5.5, -3.3, - 0., -0.5, - 3.3, 8.8, - -7.7, 6.0, - -4.0]) - - weights = np.array([500.5, -300.3, - 0.01, -0.5, - 300.3, 800.8, - -700.7, 600.6, - -400.4]) - - n_elems = len(sample) - - if ndims == 1: - shape = (n_elems,) - else: - shape = (n_elems, ndims) - - self.sample = np.zeros(shape=shape, dtype=sample.dtype) - if ndims == 1: - self.sample = sample - else: - self.sample[..., ndims-1] = sample - - self.weights = weights - - # the tests are performed along one dimension, - # all the other bins indices along the other dimensions - # are expected to be 2 - # (e.g : when testing a 2D sample : [0, x] will go into - # bin [2, y] because of the bin ranges [-2, 2] and n_bins = 4 - # for the first dimension) - self.other_axes_index = 2 - self.histo_range = np.repeat([[-2., 2.]], ndims, axis=0) - self.histo_range[ndims-1] = [-4., 6.] - - self.n_bins = np.array([4]*ndims) - self.n_bins[ndims-1] = 5 - - if ndims == 1: - def fill_histo(h, v, dim, op=None): - if op: - h[:] = op(h[:], v) - else: - h[:] = v - self.fill_histo = fill_histo - else: - def fill_histo(h, v, dim, op=None): - idx = [self.other_axes_index]*len(h.shape) - idx[dim] = slice(0, None) - if op: - h[idx] = op(h[idx], v) - else: - h[idx] = v - self.fill_histo = fill_histo - - def test_nominal(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo = Histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights) - - histo, cumul, bin_edges = histo - - expected_edges = _get_bin_edges(self.histo_range, - self.n_bins, - self.ndims) - - self.assertEqual(cumul.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - for i_edges, edges in enumerate(expected_edges): - self.assertTrue(np.array_equal(bin_edges[i_edges], - expected_edges[i_edges]), - msg='Testing bin_edges for dim {0}' - ''.format(i_edges+1)) - - def test_nominal_wh_dtype(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.float32) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo, cumul, bin_edges = Histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - wh_dtype=np.float32) - - self.assertEqual(cumul.dtype, np.float32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.allclose(cumul, expected_c)) - - def test_nominal_uncontiguous_sample(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - shape = list(self.sample.shape) - shape[0] *= 2 - sample = np.zeros(shape, dtype=self.sample.dtype) - uncontig_sample = sample[::2, ...] - uncontig_sample[:] = self.sample - - self.assertFalse(uncontig_sample.flags['C_CONTIGUOUS'], - msg='Making sure the array is not contiguous.') - - histo, cumul, bin_edges = Histogramnd(uncontig_sample, - self.histo_range, - self.n_bins, - weights=self.weights) - - self.assertEqual(cumul.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - def test_nominal_uncontiguous_weights(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - shape = list(self.weights.shape) - shape[0] *= 2 - weights = np.zeros(shape, dtype=self.weights.dtype) - uncontig_weights = weights[::2, ...] - uncontig_weights[:] = self.weights - - self.assertFalse(uncontig_weights.flags['C_CONTIGUOUS'], - msg='Making sure the array is not contiguous.') - - histo, cumul, bin_edges = Histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=uncontig_weights) - - self.assertEqual(cumul.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - def test_nominal_wo_weights(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - - histo, cumul = Histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=None)[0:2] - - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(cumul is None) - - def test_nominal_last_bin_closed(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 2]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 1101.1]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo, cumul = Histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=True)[0:2] - - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - def test_int32_weights_double_weights_range(self): - """ - """ - weight_min = -299.9 # ===> will be cast to -299 - weight_max = 499.9 # ===> will be cast to 499 - - expected_h_tpl = np.array([0, 1, 1, 1, 0]) - expected_c_tpl = np.array([0., 0., 0., 300., 0.]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo, cumul = Histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights.astype(np.int32), - weight_min=weight_min, - weight_max=weight_max)[0:2] - - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - def test_nominal_no_sample(self): - """ - """ - - histo_inst = Histogramnd(None, - self.histo_range, - self.n_bins) - - histo, weighted_histo, edges = histo_inst - - self.assertIsNone(histo) - self.assertIsNone(weighted_histo) - self.assertIsNone(edges) - self.assertIsNone(histo_inst.histo) - self.assertIsNone(histo_inst.weighted_histo) - self.assertIsNone(histo_inst.edges) - - def test_empty_init_accumulate(self): - """ - """ - expected_h_tpl = np.array([2, 1, 1, 1, 1]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo_inst = Histogramnd(None, - self.histo_range, - self.n_bins) - - histo_inst.accumulate(self.sample, - weights=self.weights) - - histo = histo_inst.histo - cumul = histo_inst.weighted_histo - bin_edges = histo_inst.edges - - expected_edges = _get_bin_edges(self.histo_range, - self.n_bins, - self.ndims) - - self.assertEqual(cumul.dtype, np.float64) - self.assertEqual(histo.dtype, np.uint32) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - for i_edges, edges in enumerate(expected_edges): - self.assertTrue(np.array_equal(bin_edges[i_edges], - expected_edges[i_edges]), - msg='Testing bin_edges for dim {0}' - ''.format(i_edges+1)) - - def test_accumulate(self): - """ - """ - - expected_h_tpl = np.array([2, 3, 2, 2, 2]) - expected_c_tpl = np.array([-700.7, -7007.5, -4.99, 300.4, 3503.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo_inst = Histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights) - - sample_2 = self.sample[:] - if len(sample_2.shape) == 1: - idx = (slice(0, None),) - else: - idx = slice(0, None), self.tested_dim - - sample_2[idx] += 2 - - histo_inst.accumulate(sample_2, # <==== !! - weights=10 * self.weights) # <==== !! - - histo = histo_inst.histo - cumul = histo_inst.weighted_histo - bin_edges = histo_inst.edges - - self.assertEqual(cumul.dtype, np.float64) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.allclose(cumul, expected_c, rtol=10e-15)) - - def test_accumulate_no_weights(self): - """ - """ - - expected_h_tpl = np.array([2, 3, 2, 2, 2]) - expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo_inst = Histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights) - - sample_2 = self.sample[:] - if len(sample_2.shape) == 1: - idx = (slice(0, None),) - else: - idx = slice(0, None), self.tested_dim - - sample_2[idx] += 2 - - histo_inst.accumulate(sample_2) # <==== !! - - histo = histo_inst.histo - cumul = histo_inst.weighted_histo - bin_edges = histo_inst.edges - - self.assertEqual(cumul.dtype, np.float64) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.allclose(cumul, expected_c, rtol=10e-15)) - - def test_accumulate_no_weights_at_init(self): - """ - """ - - expected_h_tpl = np.array([2, 3, 2, 2, 2]) - expected_c_tpl = np.array([0.0, -700.7, -0.5, 0.01, 300.3]) - - expected_h = np.zeros(shape=self.n_bins, dtype=np.double) - expected_c = np.zeros(shape=self.n_bins, dtype=np.double) - - self.fill_histo(expected_h, expected_h_tpl, self.ndims-1) - self.fill_histo(expected_c, expected_c_tpl, self.ndims-1) - - histo_inst = Histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=None) # <==== !! - - cumul = histo_inst.weighted_histo - self.assertIsNone(cumul) - - sample_2 = self.sample[:] - if len(sample_2.shape) == 1: - idx = (slice(0, None),) - else: - idx = slice(0, None), self.tested_dim - - sample_2[idx] += 2 - - histo_inst.accumulate(sample_2, - weights=self.weights) # <==== !! - - histo = histo_inst.histo - cumul = histo_inst.weighted_histo - bin_edges = histo_inst.edges - - self.assertEqual(cumul.dtype, np.float64) - self.assertTrue(np.array_equal(histo, expected_h)) - self.assertTrue(np.array_equal(cumul, expected_c)) - - def testNoneNativeTypes(self): - type = self.sample.dtype.newbyteorder("B") - sampleB = self.sample.astype(type) - - type = self.sample.dtype.newbyteorder("L") - sampleL = self.sample.astype(type) - - histo_inst = Histogramnd(sampleB, - self.histo_range, - self.n_bins, - weights=self.weights) - - histo_inst = Histogramnd(sampleL, - self.histo_range, - self.n_bins, - weights=self.weights) - - -class Test_chistogram_nominal_1d(_Test_chistogramnd_nominal): - ndims = 1 - - -class Test_chistogram_nominal_2d(_Test_chistogramnd_nominal): - ndims = 2 - - -class Test_chistogram_nominal_3d(_Test_chistogramnd_nominal): - ndims = 3 - - -class Test_Histogramnd_nominal_1d(_Test_Histogramnd_nominal): - ndims = 1 - - -class Test_Histogramnd_nominal_2d(_Test_Histogramnd_nominal): - ndims = 2 - - -class Test_Histogramnd_nominal_3d(_Test_Histogramnd_nominal): - ndims = 3 - - -# ============================================================== -# ============================================================== -# ============================================================== - - -test_cases = (Test_chistogram_nominal_1d, - Test_chistogram_nominal_2d, - Test_chistogram_nominal_3d, - Test_Histogramnd_nominal_1d, - # Test_Histogramnd_nominal_2d, - # Test_Histogramnd_nominal_3d - ) - - -def suite(): - loader = unittest.defaultTestLoader - test_suite = unittest.TestSuite() - for test_class in test_cases: - tests = loader.loadTestsFromTestCase(test_class) - test_suite.addTests(tests) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/test/test_histogramnd_vs_np.py b/silx/math/test/test_histogramnd_vs_np.py deleted file mode 100644 index 36d71f9..0000000 --- a/silx/math/test/test_histogramnd_vs_np.py +++ /dev/null @@ -1,848 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ -""" -Tests for the histogramnd function. -Results are compared to numpy's histogramdd. -""" - -import unittest -import operator - -import numpy as np - -from silx.math.chistogramnd import chistogramnd as histogramnd - -# ============================================================== -# ============================================================== -# ============================================================== - -_RTOL_DICT = {np.float64: 10**-13, - np.float32: 10**-5} - -# ============================================================== -# ============================================================== -# ============================================================== - - -def _add_values_to_array_if_missing(array, values, n_values): - max_in_col = np.any(array[:, ...] == values, axis=0) - - if len(array.shape) == 1: - if not max_in_col: - rnd_idx = np.random.randint(0, - high=len(array)-1, - size=(n_values,)) - array[rnd_idx] = values - else: - for i in range(len(max_in_col)): - if not max_in_col[i]: - rnd_idx = np.random.randint(0, - high=len(array)-1, - size=(n_values,)) - array[rnd_idx, i] = values[i] - - -def _get_values_index(array, values, op=operator.lt): - idx = op(array[:, ...], values) - if array.ndim > 1: - idx = np.all(idx, axis=1) - return np.where(idx)[0] - - -def _get_in_range_indices(array, - minvalues, - maxvalues, - minop=operator.ge, - maxop=operator.lt): - idx = np.logical_and(minop(array, minvalues), - maxop(array, maxvalues)) - if array.ndim > 1: - idx = np.all(idx, axis=1) - return np.where(idx)[0] - - -class _TestHistogramnd(unittest.TestCase): - - """ - Unit tests of the histogramnd function. - """ - sample_rng = None - weights_rng = None - n_dims = None - - filter_min = None - filter_max = None - - histo_range = None - n_bins = None - - dtype_sample = None - dtype_weights = None - - def generate_data(self): - - self.longMessage = True - - int_min = 0 - int_max = 100000 - n_elements = 10**5 - - if self.n_dims == 1: - shape = (n_elements,) - else: - shape = (n_elements, self.n_dims,) - - self.rng_state = np.random.get_state() - - self.state_msg = ('Current RNG state :\n' - '{0}'.format(self.rng_state)) - - sample = np.random.randint(int_min, - high=int_max, - size=shape) - - sample = sample.astype(self.dtype_sample) - sample = (self.sample_rng[0] + - (sample-int_min) * - (self.sample_rng[1]-self.sample_rng[0]) / - (int_max-int_min)).astype(self.dtype_sample) - - weights = np.random.randint(int_min, - high=int_max, - size=(n_elements,)) - weights = weights.astype(self.dtype_weights) - weights = (self.weights_rng[0] + - (weights-int_min) * - (self.weights_rng[1]-self.weights_rng[0]) / - (int_max-int_min)).astype(self.dtype_weights) - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!! - # !!!!!!!!!!!!!!!!!!!!!!!!!!!! - # the bins range are cast to the same type as the sample - # in order to get the same results as numpy - # (which doesnt cast the range) - self.histo_range = np.array(self.histo_range).astype(self.dtype_sample) - - # adding some values that are equal to the max - # in order to test the opened/closed last bin - bins_max = [b[1] for b in self.histo_range] - _add_values_to_array_if_missing(sample, - bins_max, - 100) - - # adding some values that are equal to the min weight value - # in order to test the filters - _add_values_to_array_if_missing(weights, - self.weights_rng[0], - 100) - - # adding some values that are equal to the max weight value - # in order to test the filters - _add_values_to_array_if_missing(weights, - self.weights_rng[1], - 100) - - return sample, weights - - def setUp(self): - self.sample, self.weights = self.generate_data() - self.rtol = _RTOL_DICT.get(self.dtype_weights, None) - - def array_compare(self, ar_a, ar_b): - if self.rtol is None: - return np.array_equal(ar_a, ar_b) - return np.allclose(ar_a, ar_b, self.rtol) - - def test_bin_ranges(self): - """ - - """ - result_c = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=True) - - result_np = np.histogramdd(self.sample, - bins=self.n_bins, - range=self.histo_range) - - for i_edges, edges in enumerate(result_c[2]): - # allclose for now until I can try with the latest version (TBD) - # of numpy - self.assertTrue(np.allclose(edges, - result_np[1][i_edges]), - msg='{0}. Testing bin_edges for dim {1}.' - ''.format(self.state_msg, i_edges+1)) - - def test_last_bin_closed(self): - """ - - """ - result_c = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=True) - - result_np = np.histogramdd(self.sample, - bins=self.n_bins, - range=self.histo_range) - - result_np_w = np.histogramdd(self.sample, - bins=self.n_bins, - range=self.histo_range, - weights=self.weights) - - # comparing "hits" - hits_cmp = np.array_equal(result_c[0], - result_np[0]) - # comparing weights - weights_cmp = np.array_equal(result_c[1], - result_np_w[0]) - - self.assertTrue(hits_cmp, msg=self.state_msg) - self.assertTrue(weights_cmp, msg=self.state_msg) - - bins_min = [rng[0] for rng in self.histo_range] - bins_max = [rng[1] for rng in self.histo_range] - inrange_idx = _get_in_range_indices(self.sample, - bins_min, - bins_max, - minop=operator.ge, - maxop=operator.le) - - self.assertEqual(result_c[0].sum(), inrange_idx.shape[0], - msg=self.state_msg) - - # we have to sum the weights using the same precision as the - # histogramnd function - weights_sum = self.weights[inrange_idx].astype(result_c[1].dtype).sum() - self.assertTrue(self.array_compare(result_c[1].sum(), weights_sum), - msg=self.state_msg) - - def test_last_bin_open(self): - """ - - """ - result_c = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=False) - - bins_max = [rng[1] for rng in self.histo_range] - filtered_idx = _get_values_index(self.sample, bins_max) - - result_np = np.histogramdd(self.sample[filtered_idx], - bins=self.n_bins, - range=self.histo_range) - - result_np_w = np.histogramdd(self.sample[filtered_idx], - bins=self.n_bins, - range=self.histo_range, - weights=self.weights[filtered_idx]) - - # comparing "hits" - hits_cmp = np.array_equal(result_c[0], result_np[0]) - # comparing weights - weights_cmp = np.array_equal(result_c[1], - result_np_w[0]) - - self.assertTrue(hits_cmp, msg=self.state_msg) - self.assertTrue(weights_cmp, msg=self.state_msg) - - bins_min = [rng[0] for rng in self.histo_range] - bins_max = [rng[1] for rng in self.histo_range] - inrange_idx = _get_in_range_indices(self.sample, - bins_min, - bins_max, - minop=operator.ge, - maxop=operator.lt) - - self.assertEqual(result_c[0].sum(), len(inrange_idx), - msg=self.state_msg) - # we have to sum the weights using the same precision as the - # histogramnd function - weights_sum = self.weights[inrange_idx].astype(result_c[1].dtype).sum() - self.assertTrue(self.array_compare(result_c[1].sum(), weights_sum), - msg=self.state_msg) - - def test_filter_min(self): - """ - - """ - result_c = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=True, - weight_min=self.filter_min) - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!! - filter_min = self.dtype_weights(self.filter_min) - - weight_idx = _get_values_index(self.weights, - filter_min, # <------ !!! - operator.ge) - - result_np = np.histogramdd(self.sample[weight_idx], - bins=self.n_bins, - range=self.histo_range) - - result_np_w = np.histogramdd(self.sample[weight_idx], - bins=self.n_bins, - range=self.histo_range, - weights=self.weights[weight_idx]) - - # comparing "hits" - hits_cmp = np.array_equal(result_c[0], - result_np[0]) - # comparing weights - weights_cmp = np.array_equal(result_c[1], result_np_w[0]) - - self.assertTrue(hits_cmp, msg=self.state_msg) - self.assertTrue(weights_cmp, msg=self.state_msg) - - bins_min = [rng[0] for rng in self.histo_range] - bins_max = [rng[1] for rng in self.histo_range] - inrange_idx = _get_in_range_indices(self.sample[weight_idx], - bins_min, - bins_max, - minop=operator.ge, - maxop=operator.le) - - inrange_idx = weight_idx[inrange_idx] - - self.assertEqual(result_c[0].sum(), len(inrange_idx), - msg=self.state_msg) - - # we have to sum the weights using the same precision as the - # histogramnd function - weights_sum = self.weights[inrange_idx].astype(result_c[1].dtype).sum() - self.assertTrue(self.array_compare(result_c[1].sum(), weights_sum), - msg=self.state_msg) - - def test_filter_max(self): - """ - - """ - result_c = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=True, - weight_max=self.filter_max) - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!! - filter_max = self.dtype_weights(self.filter_max) - - weight_idx = _get_values_index(self.weights, - filter_max, # <------ !!! - operator.le) - - result_np = np.histogramdd(self.sample[weight_idx], - bins=self.n_bins, - range=self.histo_range) - - result_np_w = np.histogramdd(self.sample[weight_idx], - bins=self.n_bins, - range=self.histo_range, - weights=self.weights[weight_idx]) - - # comparing "hits" - hits_cmp = np.array_equal(result_c[0], - result_np[0]) - # comparing weights - weights_cmp = np.array_equal(result_c[1], result_np_w[0]) - - self.assertTrue(hits_cmp, msg=self.state_msg) - self.assertTrue(weights_cmp, msg=self.state_msg) - - bins_min = [rng[0] for rng in self.histo_range] - bins_max = [rng[1] for rng in self.histo_range] - inrange_idx = _get_in_range_indices(self.sample[weight_idx], - bins_min, - bins_max, - minop=operator.ge, - maxop=operator.le) - - inrange_idx = weight_idx[inrange_idx] - - self.assertEqual(result_c[0].sum(), len(inrange_idx), - msg=self.state_msg) - - # we have to sum the weights using the same precision as the - # histogramnd function - weights_sum = self.weights[inrange_idx].astype(result_c[1].dtype).sum() - self.assertTrue(self.array_compare(result_c[1].sum(), weights_sum), - msg=self.state_msg) - - def test_filter_minmax(self): - """ - - """ - result_c = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=True, - weight_min=self.filter_min, - weight_max=self.filter_max) - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!! - filter_min = self.dtype_weights(self.filter_min) - filter_max = self.dtype_weights(self.filter_max) - - weight_idx = _get_in_range_indices(self.weights, - filter_min, # <------ !!! - filter_max, # <------ !!! - minop=operator.ge, - maxop=operator.le) - - result_np = np.histogramdd(self.sample[weight_idx], - bins=self.n_bins, - range=self.histo_range) - - result_np_w = np.histogramdd(self.sample[weight_idx], - bins=self.n_bins, - range=self.histo_range, - weights=self.weights[weight_idx]) - - # comparing "hits" - hits_cmp = np.array_equal(result_c[0], - result_np[0]) - # comparing weights - weights_cmp = np.array_equal(result_c[1], result_np_w[0]) - - self.assertTrue(hits_cmp) - self.assertTrue(weights_cmp) - - bins_min = [rng[0] for rng in self.histo_range] - bins_max = [rng[1] for rng in self.histo_range] - inrange_idx = _get_in_range_indices(self.sample[weight_idx], - bins_min, - bins_max, - minop=operator.ge, - maxop=operator.le) - - inrange_idx = weight_idx[inrange_idx] - - self.assertEqual(result_c[0].sum(), len(inrange_idx), - msg=self.state_msg) - - # we have to sum the weights using the same precision as the - # histogramnd function - weights_sum = self.weights[inrange_idx].astype(result_c[1].dtype).sum() - self.assertTrue(self.array_compare(result_c[1].sum(), weights_sum), - msg=self.state_msg) - - def test_reuse_histo(self): - """ - - """ - result_c_1 = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=True) - - result_np_1 = np.histogramdd(self.sample, - bins=self.n_bins, - range=self.histo_range) - - np.histogramdd(self.sample, - bins=self.n_bins, - range=self.histo_range, - weights=self.weights) - - sample_2, weights_2 = self.generate_data() - - result_c_2 = histogramnd(sample_2, - self.histo_range, - self.n_bins, - weights=weights_2, - last_bin_closed=True, - histo=result_c_1[0]) - - result_np_2 = np.histogramdd(sample_2, - bins=self.n_bins, - range=self.histo_range) - - result_np_w_2 = np.histogramdd(sample_2, - bins=self.n_bins, - range=self.histo_range, - weights=weights_2) - - # comparing "hits" - hits_cmp = np.array_equal(result_c_2[0], - result_np_1[0] + - result_np_2[0]) - # comparing weights - weights_cmp = np.array_equal(result_c_2[1], - result_np_w_2[0]) - - self.assertTrue(hits_cmp, msg=self.state_msg) - self.assertTrue(weights_cmp, msg=self.state_msg) - - def test_reuse_cumul(self): - """ - - """ - result_c = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=True) - - np.histogramdd(self.sample, - bins=self.n_bins, - range=self.histo_range) - - result_np_w = np.histogramdd(self.sample, - bins=self.n_bins, - range=self.histo_range, - weights=self.weights) - - sample_2, weights_2 = self.generate_data() - - result_c_2 = histogramnd(sample_2, - self.histo_range, - self.n_bins, - weights=weights_2, - last_bin_closed=True, - weighted_histo=result_c[1]) - - result_np_2 = np.histogramdd(sample_2, - bins=self.n_bins, - range=self.histo_range) - - result_np_w_2 = np.histogramdd(sample_2, - bins=self.n_bins, - range=self.histo_range, - weights=weights_2) - - # comparing "hits" - hits_cmp = np.array_equal(result_c_2[0], - result_np_2[0]) - # comparing weights - - self.assertTrue(hits_cmp, msg=self.state_msg) - self.assertTrue(self.array_compare(result_c_2[1], - result_np_w[0] + result_np_w_2[0]), - msg=self.state_msg) - - def test_reuse_cumul_float(self): - """ - - """ - n_bins = np.array(self.n_bins, ndmin=1) - if len(self.sample.shape) == 2: - if len(n_bins) == self.sample.shape[1]: - shp = tuple([x for x in n_bins]) - else: - shp = (self.n_bins,) * self.sample.shape[1] - cumul = np.zeros(shp, dtype=np.float32) - else: - shp = (self.n_bins,) - cumul = np.zeros(shp, dtype=np.float32) - - result_c_1 = histogramnd(self.sample, - self.histo_range, - self.n_bins, - weights=self.weights, - last_bin_closed=True, - weighted_histo=cumul) - - result_np_1 = np.histogramdd(self.sample, - bins=self.n_bins, - range=self.histo_range) - - result_np_w_1 = np.histogramdd(self.sample, - bins=self.n_bins, - range=self.histo_range, - weights=self.weights) - - # comparing "hits" - hits_cmp = np.array_equal(result_c_1[0], - result_np_1[0]) - - self.assertTrue(hits_cmp, msg=self.state_msg) - self.assertEqual(result_c_1[1].dtype, np.float32, msg=self.state_msg) - - bins_min = [rng[0] for rng in self.histo_range] - bins_max = [rng[1] for rng in self.histo_range] - inrange_idx = _get_in_range_indices(self.sample, - bins_min, - bins_max, - minop=operator.ge, - maxop=operator.le) - weights_sum = \ - self.weights[inrange_idx].astype(np.float32).sum(dtype=np.float64) - self.assertTrue(np.allclose(result_c_1[1].sum(dtype=np.float64), - weights_sum), msg=self.state_msg) - self.assertTrue(np.allclose(result_c_1[1].sum(dtype=np.float64), - result_np_w_1[0].sum(dtype=np.float64)), - msg=self.state_msg) - - -class _TestHistogramnd_1d(_TestHistogramnd): - - """ - Unit tests of the 1D histogramnd function. - """ - - sample_rng = [-55., 100.] - weights_rng = [-70., 150.] - n_dims = 1 - filter_min = -15.6 - filter_max = 85.7 - - histo_range = [[-30.2, 90.3]] - n_bins = 30 - - dtype = None - - -class _TestHistogramnd_2d(_TestHistogramnd): - - """ - Unit tests of the 1D histogramnd function. - """ - - sample_rng = [-50.2, 100.99] - weights_rng = [70., 150.] - n_dims = 2 - filter_min = 81.7 - filter_max = 135.3 - - histo_range = [[10., 90.], [20., 70.]] - n_bins = 30 - - dtype = None - - -class _TestHistogramnd_3d(_TestHistogramnd): - - """ - Unit tests of the 1D histogramnd function. - """ - - sample_rng = [10.2, 200.9] - weights_rng = [0., 100.] - n_dims = 3 - filter_min = 31.5 - filter_max = 83.7 - - histo_range = [[30.8, 150.2], [20.1, 90.9], [10.1, 195.]] - n_bins = 30 - - dtype = None - - -# ################################################################ -# ################################################################ -# ################################################################ -# ################################################################ - - -class TestHistogramnd_1d_double_double(_TestHistogramnd_1d): - dtype_sample = np.double - dtype_weights = np.double - - -class TestHistogramnd_1d_double_float(_TestHistogramnd_1d): - dtype_sample = np.double - dtype_weights = np.float32 - - -class TestHistogramnd_1d_double_int32(_TestHistogramnd_1d): - dtype_sample = np.double - dtype_weights = np.int32 - - -class TestHistogramnd_1d_float_double(_TestHistogramnd_1d): - dtype_sample = np.float32 - dtype_weights = np.double - - -class TestHistogramnd_1d_float_float(_TestHistogramnd_1d): - dtype_sample = np.float32 - dtype_weights = np.float32 - - -class TestHistogramnd_1d_float_int32(_TestHistogramnd_1d): - dtype_sample = np.float32 - dtype_weights = np.int32 - - -class TestHistogramnd_1d_int32_double(_TestHistogramnd_1d): - dtype_sample = np.int32 - dtype_weights = np.double - - -class TestHistogramnd_1d_int32_float(_TestHistogramnd_1d): - dtype_sample = np.int32 - dtype_weights = np.float32 - - -class TestHistogramnd_1d_int32_int32(_TestHistogramnd_1d): - dtype_sample = np.int32 - dtype_weights = np.int32 - - -class TestHistogramnd_2d_double_double(_TestHistogramnd_2d): - dtype_sample = np.double - dtype_weights = np.double - - -class TestHistogramnd_2d_double_float(_TestHistogramnd_2d): - dtype_sample = np.double - dtype_weights = np.float32 - - -class TestHistogramnd_2d_double_int32(_TestHistogramnd_2d): - dtype_sample = np.double - dtype_weights = np.int32 - - -class TestHistogramnd_2d_float_double(_TestHistogramnd_2d): - dtype_sample = np.float32 - dtype_weights = np.double - - -class TestHistogramnd_2d_float_float(_TestHistogramnd_2d): - dtype_sample = np.float32 - dtype_weights = np.float32 - - -class TestHistogramnd_2d_float_int32(_TestHistogramnd_2d): - dtype_sample = np.float32 - dtype_weights = np.int32 - - -class TestHistogramnd_2d_int32_double(_TestHistogramnd_2d): - dtype_sample = np.int32 - dtype_weights = np.double - - -class TestHistogramnd_2d_int32_float(_TestHistogramnd_2d): - dtype_sample = np.int32 - dtype_weights = np.float32 - - -class TestHistogramnd_2d_int32_int32(_TestHistogramnd_2d): - dtype_sample = np.int32 - dtype_weights = np.int32 - - -class TestHistogramnd_3d_double_double(_TestHistogramnd_3d): - dtype_sample = np.double - dtype_weights = np.double - - -class TestHistogramnd_3d_double_float(_TestHistogramnd_3d): - dtype_sample = np.double - dtype_weights = np.float32 - - -class TestHistogramnd_3d_double_int32(_TestHistogramnd_3d): - dtype_sample = np.double - dtype_weights = np.int32 - - -class TestHistogramnd_3d_float_double(_TestHistogramnd_3d): - dtype_sample = np.float32 - dtype_weights = np.double - - -class TestHistogramnd_3d_float_float(_TestHistogramnd_3d): - dtype_sample = np.float32 - dtype_weights = np.float32 - - -class TestHistogramnd_3d_float_int32(_TestHistogramnd_3d): - dtype_sample = np.float32 - dtype_weights = np.int32 - - -class TestHistogramnd_3d_int32_double(_TestHistogramnd_3d): - dtype_sample = np.int32 - dtype_weights = np.double - - -class TestHistogramnd_3d_int32_float(_TestHistogramnd_3d): - dtype_sample = np.int32 - dtype_weights = np.float32 - - -class TestHistogramnd_3d_int32_int32(_TestHistogramnd_3d): - dtype_sample = np.int32 - dtype_weights = np.int32 - - -# ============================================================== -# ============================================================== -# ============================================================== - - -test_cases = (TestHistogramnd_1d_double_double, - TestHistogramnd_1d_double_float, - TestHistogramnd_1d_double_int32, - TestHistogramnd_1d_float_double, - TestHistogramnd_1d_float_float, - TestHistogramnd_1d_float_int32, - TestHistogramnd_1d_int32_double, - TestHistogramnd_1d_int32_float, - TestHistogramnd_1d_int32_int32, - TestHistogramnd_2d_double_double, - TestHistogramnd_2d_double_float, - TestHistogramnd_2d_double_int32, - TestHistogramnd_2d_float_double, - TestHistogramnd_2d_float_float, - TestHistogramnd_2d_float_int32, - TestHistogramnd_2d_int32_double, - TestHistogramnd_2d_int32_float, - TestHistogramnd_2d_int32_int32, - TestHistogramnd_3d_double_double, - TestHistogramnd_3d_double_float, - TestHistogramnd_3d_double_int32, - TestHistogramnd_3d_float_double, - TestHistogramnd_3d_float_float, - TestHistogramnd_3d_float_int32, - TestHistogramnd_3d_int32_double, - TestHistogramnd_3d_int32_float, - TestHistogramnd_3d_int32_int32,) - - -def suite(): - loader = unittest.defaultTestLoader - test_suite = unittest.TestSuite() - for test_class in test_cases: - tests = loader.loadTestsFromTestCase(test_class) - test_suite.addTests(tests) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") diff --git a/silx/math/test/test_interpolate.py b/silx/math/test/test_interpolate.py deleted file mode 100644 index 9989302..0000000 --- a/silx/math/test/test_interpolate.py +++ /dev/null @@ -1,136 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2019 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. -# -# ############################################################################*/ -"""Test for interpolate module""" - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "11/07/2019" - - -import unittest - -import numpy -try: - from scipy.interpolate import interpn -except ImportError: - interpn = None - -from silx.utils.testutils import ParametricTestCase -from silx.math import interpolate - - -@unittest.skipUnless(interpn is not None, "scipy missing") -class TestInterp3d(ParametricTestCase): - """Test silx.math.interpolate.interp3d""" - - @staticmethod - def ref_interp3d(data, points): - """Reference implementation of interp3d based on scipy - - :param numpy.ndarray data: 3D floating dataset - :param numpy.ndarray points: Array of points of shape (N, 3) - """ - return interpn( - [numpy.arange(dim, dtype=data.dtype) for dim in data.shape], - data, - points, - method='linear') - - def test_random_data(self): - """Test interp3d with random data""" - size = 32 - npoints = 10 - - ref_data = numpy.random.random((size, size, size)) - ref_points = numpy.random.random(npoints*3).reshape(npoints, 3) * (size -1) - - for dtype in (numpy.float32, numpy.float64): - data = ref_data.astype(dtype) - points = ref_points.astype(dtype) - ref_result = self.ref_interp3d(data, points) - - for method in (u'linear', u'linear_omp'): - with self.subTest(method=method): - result = interpolate.interp3d(data, points, method=method) - self.assertTrue(numpy.allclose(ref_result, result)) - - def test_notfinite_data(self): - """Test interp3d with NaN and inf""" - data = numpy.ones((3, 3, 3), dtype=numpy.float64) - data[0, 0, 0] = numpy.nan - data[2, 2, 2] = numpy.inf - points = numpy.array([(0.5, 0.5, 0.5), - (1.5, 1.5, 1.5)]) - - for method in (u'linear', u'linear_omp'): - with self.subTest(method=method): - result = interpolate.interp3d( - data, points, method=method) - self.assertTrue(numpy.isnan(result[0])) - self.assertTrue(result[1] == numpy.inf) - - def test_points_outside(self): - """Test interp3d with points outside the volume""" - data = numpy.ones((4, 4, 4), dtype=numpy.float64) - points = numpy.array([(-0.1, -0.1, -0.1), - (3.1, 3.1, 3.1), - (-0.1, 1., 1.), - (1., 1., 3.1)]) - - for method in (u'linear', u'linear_omp'): - for fill_value in (numpy.nan, 0., -1.): - with self.subTest(method=method): - result = interpolate.interp3d( - data, points, method=method, fill_value=fill_value) - if numpy.isnan(fill_value): - self.assertTrue(numpy.all(numpy.isnan(result))) - else: - self.assertTrue(numpy.all(numpy.equal(result, fill_value))) - - def test_integer_points(self): - """Test interp3d with integer points coord""" - data = numpy.arange(4**3, dtype=numpy.float64).reshape(4, 4, 4) - points = numpy.array([(0., 0., 0.), - (0., 0., 1.), - (2., 3., 0.), - (3., 3., 3.)]) - - ref_result = data[tuple(points.T.astype(numpy.int32))] - - for method in (u'linear', u'linear_omp'): - with self.subTest(method=method): - result = interpolate.interp3d(data, points, method=method) - self.assertTrue(numpy.allclose(ref_result, result)) - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTest( - unittest.defaultTestLoader.loadTestsFromTestCase(TestInterp3d)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/silx/math/test/test_marchingcubes.py b/silx/math/test/test_marchingcubes.py deleted file mode 100644 index 41f7e30..0000000 --- a/silx/math/test/test_marchingcubes.py +++ /dev/null @@ -1,188 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# Copyright (C) 2016 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. -# -# ############################################################################*/ -"""Tests of the marchingcubes module""" - -from __future__ import division - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "17/01/2018" - -import unittest - -import numpy - -from silx.utils.testutils import ParametricTestCase - -from silx.math import marchingcubes - - -class TestMarchingCubes(ParametricTestCase): - """Tests of marching cubes""" - - def assertAllClose(self, array1, array2, msg=None, - rtol=1e-05, atol=1e-08): - """Assert that the 2 numpy.ndarrays are almost equal. - - :param str msg: Message to provide when assert fails - :param float rtol: Relative tolerance, see :func:`numpy.allclose` - :param float atol: Absolute tolerance, see :func:`numpy.allclose` - """ - if not numpy.allclose(array1, array2, rtol, atol): - raise self.failureException(msg) - - def test_cube(self): - """Unit tests with a single cube""" - - # No isosurface - cube_zero = numpy.zeros((2, 2, 2), dtype=numpy.float32) - - result = marchingcubes.MarchingCubes(cube_zero, 1.) - self.assertEqual(result.shape, cube_zero.shape) - self.assertEqual(result.isolevel, 1.) - self.assertEqual(result.invert_normals, True) - - vertices, normals, indices = result - self.assertEqual(len(vertices), 0) - self.assertEqual(len(normals), 0) - self.assertEqual(len(indices), 0) - - # Cube array dimensions: shape = (dim 0, dim 1, dim2) - # - # dim 0 (Z) - # ^ - # | - # 4 +------+ 5 - # /| /| - # / | / | - # 6 +------+ 7| - # | | | | - # |0 +---|--+ 1 -> dim 2 (X) - # | / | / - # |/ |/ - # 2 +------+ 3 - # / - # dim 1 (Y) - - # isosurface perpendicular to dim 0 (Z) - cube = numpy.array( - (((0., 0.), (0., 0.)), - ((1., 1.), (1., 1.))), dtype=numpy.float32) - level = 0.5 - vertices, normals, indices = marchingcubes.MarchingCubes( - cube, level, invert_normals=False) - self.assertAllClose(vertices[:, 0], level) - self.assertAllClose(normals, (1., 0., 0.)) - self.assertEqual(len(indices), 2) - - # isosurface perpendicular to dim 1 (Y) - cube = numpy.array( - (((0., 0.), (1., 1.)), - ((0., 0.), (1., 1.))), dtype=numpy.float32) - level = 0.2 - vertices, normals, indices = marchingcubes.MarchingCubes(cube, level) - self.assertAllClose(vertices[:, 1], level) - self.assertAllClose(normals, (0., -1., 0.)) - self.assertEqual(len(indices), 2) - - # isosurface perpendicular to dim 2 (X) - cube = numpy.array( - (((0., 1.), (0., 1.)), - ((0., 1.), (0., 1.))), dtype=numpy.float32) - level = 0.9 - vertices, normals, indices = marchingcubes.MarchingCubes( - cube, level, invert_normals=False) - self.assertAllClose(vertices[:, 2], level) - self.assertAllClose(normals, (0., 0., 1.)) - self.assertEqual(len(indices), 2) - - # isosurface normal in dim1, dim 0 (Y, Z) plane - cube = numpy.array( - (((0., 0.), (0., 0.)), - ((0., 0.), (1., 1.))), dtype=numpy.float32) - level = 0.5 - vertices, normals, indices = marchingcubes.MarchingCubes(cube, level) - self.assertAllClose(normals[:, 2], 0.) - self.assertEqual(len(indices), 2) - - def test_sampling(self): - """Test different sampling, comparing to reference without sampling""" - isolevel = 0.5 - size = 9 - chessboard = numpy.zeros((size, size, size), dtype=numpy.float32) - chessboard.reshape(-1)[::2] = 1 # OK as long as dimensions are odd - - ref_result = marchingcubes.MarchingCubes(chessboard, isolevel) - - samplings = [ - (2, 1, 1), - (1, 2, 1), - (1, 1, 2), - (2, 2, 2), - (3, 3, 3), - (1, 3, 1), - (1, 1, 3), - ] - - for sampling in samplings: - with self.subTest(sampling=sampling): - sampling = numpy.array(sampling) - - data = 1e6 * numpy.ones( - sampling * size, dtype=numpy.float32) - # Copy ref chessboard in data according to sampling - data[::sampling[0], ::sampling[1], ::sampling[2]] = chessboard - - result = marchingcubes.MarchingCubes(data, isolevel, - sampling=sampling) - # Compare vertices normalized with shape - self.assertAllClose( - ref_result.get_vertices() / ref_result.shape, - result.get_vertices() / result.shape, - atol=0., rtol=0.) - - # Compare normals - # This comparison only works for normals aligned with axes - # otherwise non uniform sampling would make different normals - self.assertAllClose(ref_result.get_normals(), - result.get_normals(), - atol=0., rtol=0.) - - self.assertAllClose(ref_result.get_indices(), - result.get_indices(), - atol=0., rtol=0.) - - -test_cases = (TestMarchingCubes,) - - -def suite(): - test_suite = unittest.TestSuite() - for test_class in test_cases: - test_suite.addTests( - unittest.defaultTestLoader.loadTestsFromTestCase(test_class)) - return test_suite - -if __name__ == '__main__': - unittest.main(defaultTest="suite") |