Switch to unified view

a b/docs/source/guide_fitting.rst
1
.. _guide_fitting:
2
3
**This guide is still under construction**
4
5
Fitting
6
-----------
7
8
Dosma supports cpu-parallelizable quantitative fitting based on
9
`scipy.optimize.curve_fit <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html>`_. 
10
11
To perform generic fitting to any array-like object using an arbitrary model function, we can use
12
:func:`dosma.curve_fit`. For example, we can use this function to fit an array to a
13
monoexponential model ``y = a * exp(b * x)`` using a maximum of 4 workers:
14
15
>>> from dosma import curve_fit, monoexponential
16
>>> curve_fit(monoexponential, x, y, num_workers=4)
17
18
Quantitative fitting is quite common in medical image analysis. For example,
19
quantitative MRI (qMRI) has enabled computing voxel-wise relaxation parameter maps
20
(e.g. |T2|, |T1rho|, etc.). We can fit a monoexponential model for each voxel across these registered_images,
21
where ``tc0`` is the initial guess for parameter :math:`-\frac{1}{b}` in the monoexponential model:
22
23
>>> from dosma import MonoExponentialFit
24
>>> tc0 = 30.0
25
>>> echo_times = np.asarray([10.0, 20.0, 50.0])
26
>>> fitter = MonoExponentialFit(tc0=tc0, num_workers=4)
27
>>> tc, r2_map = fitter.fit(echo_times, images)
28
29
If you don't have a good initial guess for ``tc0`` or expect the initial guess to be dependent on the voxel being fit
30
(which is often the case), you can specify that the initial guess should be determined based on results from a
31
polynomial fit over the log-linearized form of the monoexponential equation ``log(y) = log(a) - x/tc``:
32
33
>>> from dosma import MonoExponentialFit
34
>>> tc0 = "polyfit"
35
>>> echo_times = np.asarray([10.0, 20.0, 50.0])
36
>>> fitter = MonoExponentialFit(tc0=tc0, num_workers=4)
37
>>> tc, r2_map = fitter.fit(echo_times, images)
38
39
Custom model functions can also be provided and used with :class:`dosma.curve_fit` and :class:`dosma.CurveFitter` (recommended),
40
a class wrapper around :class:`dosma.curve_fit` that handles :class:`MedicalVolume` data and supports additional post-processing
41
on the fitted parameters. The commands below using :class:`dosma.CurveFitter` and :class:`dosma.curve_fit` are equivalent to the
42
``fitter`` above:
43
44
>>> from dosma import CurveFitter
45
>>> cfitter = CurveFitter(
46
... monoexponential, p0=(1.0, -1/tc0), num_workers=4, nan_to_num=0,
47
... out_ufuncs=[None, lambda x: -1/x], out_bounds=(0, 100))
48
>>> popt, r2_map = cfitter.fit(echo_times, images)
49
>>> tc = popt[..., 1]
50
51
>>> from dosma import curve_fit
52
>>> curve_fit(monoexponential, echo_times, [x.volume for x in images], p0=(1.0, -1/tc0), num_workers=4)
53
54
Non-linear curve fitting often requires carefully selected parameter initialization. In cases where
55
non-linear curve fitting fails, polynomial fitting may be more effective. Polynomials can be fit to
56
the data using :func:`dosma.polyfit` or :class:`dosma.PolyFitter` (recommended),
57
which is the polynomial fitting equivalent of ``CurveFitter``. Because polynomial fitting can also be
58
done as a single least squares problem, it may also often be faster than standard curve fitting.
59
The commands below use ``PolyFitter`` to fit to the log-linearized monoexponential fit
60
(i.e. ``log(y) = log(a) + b*x`` to some image data:
61
62
>>> from dosma import PolyFitter
63
>>> echo_times = np.asarray([10.0, 20.0, 50.0])
64
>>> pfitter = PolyFitter(deg=1, nan_to_num=0, out_ufuncs=[None, lambda x: -1/x], out_bounds=(0, 100))
65
>>> log_images = [np.log(img) for img in images]
66
>>> popt, r2_map = pfitter.fit(echo_times, log_images)
67
>>> tc = popt[..., 0]  # note ordering of parameters - see numpy.polyfit for more details.
68
69
We can also use the polyfit estimates to initialize the non-linear curve fit. For monoexponential
70
fitting, we can do the following:
71
72
>>> from dosma import CurveFitter, PolyFitter
73
>>> echo_times = np.asarray([10.0, 20.0, 50.0])
74
>>> pfitter = PolyFitter(deg=1, r2_threshold=0, num_workers=0)
75
>>> log_images = [np.log(img) for img in images]
76
>>> popt_pf, _ = pfitter.fit(echo_times, log_images)
77
>>> cfitter = CurveFitter(monoexponential, r2_threshold=0.9, nan_to_num=0, out_ufuncs=[None, lambda x: -1/x], out_bounds=(0, 100))
78
>>> popt, r2 = cfitter.fit(echo_times, images, p0={"a": popt_pf[..., 1], "b": popt_pf[..., 0]})
79
>>> tc = popt[..., 1] 
80
81
.. Substitutions
82
.. |T2| replace:: T\ :sub:`2`
83
.. |T1| replace:: T\ :sub:`1`
84
.. |T1rho| replace:: T\ :sub:`1`:math:`{\rho}`
85
.. |T2star| replace:: T\ :sub:`2`:sup:`*`