diff options
Diffstat (limited to 'silx/math/fit')
-rw-r--r-- | silx/math/fit/fitmanager.py | 36 | ||||
-rw-r--r-- | silx/math/fit/test/test_fitmanager.py | 87 |
2 files changed, 77 insertions, 46 deletions
diff --git a/silx/math/fit/fitmanager.py b/silx/math/fit/fitmanager.py index f62dedb..2dc63a1 100644 --- a/silx/math/fit/fitmanager.py +++ b/silx/math/fit/fitmanager.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*######################################################################### # -# Copyright (c) 2004-2018 European Synchrotron Radiation Facility +# 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 @@ -411,8 +411,9 @@ class FitManager(object): 6: 'SUM', 7: 'IGNORE'} - xwork = self.xdata - ywork = self.ydata + # 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) @@ -516,8 +517,8 @@ class FitManager(object): from a list of parameter dictionaries, if field ``code`` is not set to ``"IGNORE"``. """ - if x is None: - x = self.xdata + x = self.xdata if x is None else numpy.array(x, copy=False) + if paramlist is None: paramlist = self.fit_results active_params = [] @@ -528,8 +529,18 @@ class FitManager(object): else: active_params.append(param['estimation']) - newdata = self.fitfunction(numpy.array(x), *active_params) - return newdata + # 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.""" @@ -750,6 +761,10 @@ class FitManager(object): 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. @@ -822,13 +837,14 @@ class FitManager(object): param_val.append(param['estimation']) param_constraints.append([param['code'], param['cons1'], param['cons2']]) - ywork = self.ydata - + # 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 - self.xdata, ywork, param_val, + xwork, ywork, param_val, sigma=self.sigmay, constraints=param_constraints, model_deriv=self.theories[self.selectedtheory].derivative, diff --git a/silx/math/fit/test/test_fitmanager.py b/silx/math/fit/test/test_fitmanager.py index 38c4802..7a643cb 100644 --- a/silx/math/fit/test/test_fitmanager.py +++ b/silx/math/fit/test/test_fitmanager.py @@ -1,6 +1,6 @@ # coding: utf-8 # /*########################################################################## -# Copyright (C) 2016-2017 European Synchrotron Radiation Facility +# 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 @@ -35,6 +35,7 @@ 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 = """ @@ -110,7 +111,7 @@ def _order_of_magnitude(x): return numpy.log10(x).round() -class TestFitmanager(unittest.TestCase): +class TestFitmanager(ParametricTestCase): """ Unit tests of multi-peak functions. """ @@ -132,40 +133,54 @@ class TestFitmanager(unittest.TestCase): linear_bg = 2.65 * x + 13 y = linear_bg + sum_gauss(x, *p) - # Fitting - fit = fitmanager.FitManager() - fit.setdata(x=x, y=y) - 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])) + 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 |