summaryrefslogtreecommitdiff
path: root/silx/math/fit
diff options
context:
space:
mode:
Diffstat (limited to 'silx/math/fit')
-rw-r--r--silx/math/fit/fitmanager.py36
-rw-r--r--silx/math/fit/test/test_fitmanager.py87
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