|
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:`*` |