Skip to content

Commit 33ea3f7

Browse files
Add files via upload
1 parent 5081a67 commit 33ea3f7

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""
2+
January 2022 by Paulien Voorter
3+
p.voorter@maastrichtuniversity.nl
4+
https://www.github.com/paulienvoorter
5+
6+
requirements:
7+
numpy
8+
tqdm
9+
scipy
10+
joblib
11+
"""
12+
13+
# load relevant libraries
14+
from scipy.optimize import curve_fit, nnls
15+
import numpy as np
16+
from joblib import Parallel, delayed
17+
import tqdm
18+
19+
20+
21+
22+
def two_exp_noS0(bvalues, Dpar, Fmv, Dmv):
23+
""" tri-exponential IVIM function, and S0 set to 1"""
24+
return Fmv * np.exp(-bvalues * Dmv) + (1 - Fmv ) * np.exp(-bvalues * Dpar)
25+
26+
def two_exp(bvalues, S0, Dpar, Fmv, Dmv):
27+
""" tri-exponential IVIM function"""
28+
return S0 * (Fmv * np.exp(-bvalues * Dmv) + (1 - Fmv ) * np.exp(-bvalues * Dpar))
29+
30+
31+
32+
def fit_least_squares_array(bvalues, dw_data, fitS0=True, bounds=([0.9, 0.0001, 0.0, 0.0025], [1.1, 0.0025, 0.2, 0.2]), cutoff=200):
33+
"""
34+
This is the LSQ implementation, in which we first estimate Dpar using a curve fit to b-values>=cutoff;
35+
Second, we fit the other parameters using all b-values, while fixing Dpar from step 1. This fit
36+
is done on an array.
37+
:param bvalues: 1D Array with the b-values
38+
:param dw_data: 2D Array with diffusion-weighted signal in different voxels at different b-values
39+
:param bounds: Array with fit bounds ([S0min, Dparmin, Fintmin, Dintmin, Fmvmin, Dmvmin],[S0max, Dparmax, Fintmax, Dintmax, Fmvmax, Dmvmax]). default: ([0.9, 0.0001, 0.0, 0.0015, 0.0, 0.004], [1.1, 0.0015, 0.4, 0.004, 0.2, 0.2])
40+
:param cutoff: cutoff b-value used in step 1
41+
:return Dpar: 1D Array with Dpar in each voxel
42+
:return Fmv: 1D Array with Fmv in each voxel
43+
:return Dmv: 1D Array with Dmv in each voxel
44+
:return S0: 1D Array with S0 in each voxel
45+
"""
46+
# initialize empty arrays
47+
Dpar = np.zeros(len(dw_data))
48+
S0 = np.zeros(len(dw_data))
49+
Dmv = np.zeros(len(dw_data))
50+
Fmv = np.zeros(len(dw_data))
51+
for i in tqdm.tqdm(range(len(dw_data)), position=0, leave=True):
52+
# fill arrays with fit results on a per voxel base:
53+
Dpar[i], Fmv[i], Dmv[i], S0[i] = fit_least_squares(bvalues, dw_data[i, :], S0_output=True, fitS0=fitS0, bounds=bounds)
54+
return [Dpar, Fmv, Dmv, S0]
55+
56+
57+
def fit_least_squares(bvalues, dw_data, IR=True, S0_output=False, fitS0=True,
58+
bounds=([0.9, 0.0001, 0.0, 0.0025], [1.1, 0.0025, 0.2, 0.2]), cutoff=200):
59+
"""
60+
This is the LSQ implementation, in which we first estimate Dpar using a curve fit to b-values>=cutoff;
61+
Second, we fit the other parameters using all b-values, while fixing Dpar from step 1. This fit
62+
is done on an array. It fits a single curve
63+
:param bvalues: 1D Array with the b-values
64+
:param dw_data: 1D Array with diffusion-weighted signal in different voxels at different b-values
65+
:param IR: Boolean; True will fit the IVIM accounting for inversion recovery, False will fit IVIM without IR; default = True
66+
:param S0_output: Boolean determining whether to output (often a dummy) variable S0; default = False
67+
:param fix_S0: Boolean determining whether to fix S0 to 1; default = True
68+
:param bounds: Array with fit bounds ([S0min, Dparmin, Fintmin, Dintmin, Fmvmin, Dmvmin],[S0max, Dparmax, Fintmax, Dintmax, Fmvmax, Dmvmax]). Default: ([0, 0, 0, 0.005, 0, 0.06], [2.5, 0.005, 1, 0.06, 1, 0.5])
69+
:param cutoff: cutoff b-value used in step 1
70+
:return S0: optional 1D Array with S0 in each voxel
71+
:return Dpar: scalar with Dpar of the specific voxel
72+
:return Fint: scalar with Fint of the specific voxel
73+
:return Dint: scalar with Dint of the specific voxel
74+
:return Fmv: scalar with Fmv of the specific voxel
75+
:return Dmv: scalar with Dmv of the specific voxel
76+
"""
77+
78+
try:
79+
def monofit(bvalues, Dpar):
80+
return np.exp(-bvalues * Dpar)
81+
82+
high_b = bvalues[bvalues >= cutoff]
83+
high_dw_data = dw_data[bvalues >= cutoff]
84+
boundspar = ([bounds[0][1]], [bounds[1][1]])
85+
params, _ = curve_fit(monofit, high_b, high_dw_data, p0=[(bounds[1][1]-bounds[0][1])/2], bounds=boundspar)
86+
Dpar1 = params[0]
87+
88+
if not fitS0:
89+
boundsupdated=([Dpar1 , bounds[0][2] , bounds[0][3] ],
90+
[Dpar1 , bounds[1][2] , bounds[1][3] ])
91+
params, _ = curve_fit(two_exp_noS0, bvalues, dw_data, p0=[Dpar1, (bounds[0][2]+bounds[1][2])/2, (bounds[0][3]+bounds[1][3])/2], bounds=boundsupdated)
92+
Dpar, Fmv, Dmv = params[0], params[1], params[2]
93+
#when the fraction of a compartment equals zero (or very very small), the corresponding diffusivity is non-existing (=NaN)
94+
if Fmv < 1e-4:
95+
Dmv = float("NaN")
96+
97+
else:
98+
boundsupdated = ([bounds[0][0] , Dpar1 , bounds[0][2] , bounds[0][3] ],
99+
[bounds[1][0] , Dpar1, bounds[1][2] , bounds[1][3] ])
100+
params, _ = curve_fit(two_exp, bvalues, dw_data, p0=[1, Dpar1, (bounds[0][2]+bounds[1][2])/2, (bounds[0][3]+bounds[1][3])/2], bounds=boundsupdated)
101+
S0 = params[0]
102+
Dpar, Fmv, Dmv = params[1] , params[2] , params[3]
103+
#when the fraction of a compartment equals zero (or very very small), the corresponding diffusivity is non-existing (=NaN)
104+
if Fmv < 1e-4:
105+
Dmv = float("NaN")
106+
107+
if S0_output:
108+
return Dpar, Fmv, Dmv, S0
109+
else:
110+
return Dpar, Fmv, Dmv
111+
except:
112+
113+
if S0_output:
114+
return 0, 0, 0, 0, 0, 0
115+
else:
116+
return 0, 0, 0, 0, 0
117+
118+
119+

0 commit comments

Comments
 (0)