Skip to content

Commit b7b368f

Browse files
author
IvanARashid
committed
Testing Pauliens code
1 parent d65aa4d commit b7b368f

File tree

2 files changed

+82
-41
lines changed

2 files changed

+82
-41
lines changed

src/original/PV_MUMC/two_step_IVIM_fit.py

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,34 @@
77
numpy
88
tqdm
99
scipy
10-
joblib
1110
"""
1211

1312
# load relevant libraries
14-
from scipy.optimize import curve_fit, nnls
13+
from scipy.optimize import curve_fit
1514
import numpy as np
16-
from joblib import Parallel, delayed
1715
import tqdm
1816

1917

2018

2119

2220
def two_exp_noS0(bvalues, Dpar, Fmv, Dmv):
23-
""" tri-exponential IVIM function, and S0 set to 1"""
21+
""" bi-exponential IVIM function, and S0 set to 1"""
2422
return Fmv * np.exp(-bvalues * Dmv) + (1 - Fmv ) * np.exp(-bvalues * Dpar)
2523

2624
def two_exp(bvalues, S0, Dpar, Fmv, Dmv):
27-
""" tri-exponential IVIM function"""
25+
""" bi-exponential IVIM function"""
2826
return S0 * (Fmv * np.exp(-bvalues * Dmv) + (1 - Fmv ) * np.exp(-bvalues * Dpar))
2927

3028

3129

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):
30+
def fit_least_squares_array(bvalues, dw_data, fitS0=False, bounds=([0.9, 0.0001, 0.0, 0.0025], [1.1, 0.0025, 0.5, 0.2]), cutoff=200):
3331
"""
3432
This is the LSQ implementation, in which we first estimate Dpar using a curve fit to b-values>=cutoff;
3533
Second, we fit the other parameters using all b-values, while fixing Dpar from step 1. This fit
3634
is done on an array.
3735
:param bvalues: 1D Array with the b-values
3836
: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])
37+
:param bounds: Array with fit bounds ([S0min, Dparmin, Fmvmin, Dmvmin],[S0max, Dparmax, Fmvmax, Dmvmax]).
4038
:param cutoff: cutoff b-value used in step 1
4139
:return Dpar: 1D Array with Dpar in each voxel
4240
:return Fmv: 1D Array with Fmv in each voxel
@@ -54,69 +52,56 @@ def fit_least_squares_array(bvalues, dw_data, fitS0=True, bounds=([0.9, 0.0001,
5452
return [Dpar, Fmv, Dmv, S0]
5553

5654

57-
def fit_least_squares(bvalues, dw_data, IR=False, 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):
55+
def fit_least_squares(bvalues, dw_data, S0_output=False, fitS0=False,
56+
bounds=([0.9, 0.0001, 0.0, 0.0025], [1.1, 0.003, 1, 0.2]), cutoff=200):
5957
"""
6058
This is the LSQ implementation, in which we first estimate Dpar using a curve fit to b-values>=cutoff;
6159
Second, we fit the other parameters using all b-values, while fixing Dpar from step 1. This fit
6260
is done on an array. It fits a single curve
6361
:param bvalues: 1D Array with the b-values
6462
: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])
63+
:param S0_output: Boolean determining whether to output (often a dummy) variable S0;
64+
:param fitS0: Boolean determining whether to fix S0 to 1;
65+
:param bounds: Array with fit bounds ([S0min, Dparmin, Fmvmin, Dmvmin],[S0max, Dparmax, Fmvmax, Dmvmax]).
6966
:param cutoff: cutoff b-value used in step 1
7067
:return S0: optional 1D Array with S0 in each voxel
7168
: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
7469
:return Fmv: scalar with Fmv of the specific voxel
7570
:return Dmv: scalar with Dmv of the specific voxel
7671
"""
7772

7873
#try:
79-
def monofit(bvalues, Dpar):
80-
return np.exp(-bvalues * Dpar)
74+
def monofit(bvalues, Dpar, Fmv):
75+
return (1-Fmv)*np.exp(-bvalues * Dpar)
8176

8277
high_b = bvalues[bvalues >= cutoff]
8378
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-
Dpar = params[0]
87-
79+
boundsmonoexp = ([bounds[0][1], bounds[0][2]], [bounds[1][1], bounds[1][2]])
80+
params, _ = curve_fit(monofit, high_b, high_dw_data, p0=[(bounds[1][1]-bounds[0][1])/2,(bounds[1][2]-bounds[0][2])/2], bounds=boundsmonoexp)
81+
Dpar1 = params[0]
8882
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-
Dpar1, Fmv, Dmv = params[0], params[1], params[2]
83+
boundsupdated=([bounds[0][2] , bounds[0][3] ],
84+
[bounds[1][2] , bounds[1][3] ])
85+
params, _ = curve_fit(lambda b, Fmv, Dmv: two_exp_noS0(b, Dpar1, Fmv, Dmv), bvalues, dw_data, p0=[(bounds[0][2]+bounds[1][2])/2, (bounds[0][3]+bounds[1][3])/2], bounds=boundsupdated)
86+
Fmv, Dmv = params[0], params[1]
9387
#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")
88+
#if Fmv < 1e-4:
89+
# Dmv = float("NaN")
9690

9791
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)
10192
boundsupdated = ([bounds[0][0] , bounds[0][2] , bounds[0][3] ],
10293
[bounds[1][0] , bounds[1][2] , bounds[1][3] ])
103-
params, _ = curve_fit(lambda bvalues, S0, Fmv, Dmv: two_exp(bvalues, S0, Dpar, Fmv, Dmv), bvalues, dw_data, p0=[1, (bounds[0][2]+bounds[1][2])/2, (bounds[0][3]+bounds[1][3])/2], bounds=boundsupdated)
94+
params, _ = curve_fit(lambda b, S0, Fmv, Dmv: two_exp(b, S0, Dpar1, Fmv, Dmv), bvalues, dw_data, p0=[1, (bounds[0][2]+bounds[1][2])/2, (bounds[0][3]+bounds[1][3])/2], bounds=boundsupdated)
10495
S0 = params[0]
10596
Fmv, Dmv = params[1] , params[2]
10697
#when the fraction of a compartment equals zero (or very very small), the corresponding diffusivity is non-existing (=NaN)
107-
if Fmv < 1e-4:
108-
Dmv = float("NaN")
98+
#if Fmv < 1e-4:
99+
# Dmv = float("NaN")
109100

110101
if S0_output:
111-
return Dpar, Fmv, Dmv, S0
112-
else:
113-
return Dpar, Fmv, Dmv
114-
#except:
115-
116-
if S0_output:
117-
return 0, 0, 0, 0, 0, 0
102+
return Dpar1, Fmv, Dmv, S0
118103
else:
119-
return 0, 0, 0, 0, 0
104+
return Dpar1, Fmv, Dmv
120105

121106

122107

src/standardized/PV_MUMC_biexp.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import numpy as np
2+
from src.wrappers.OsipiBase import OsipiBase
3+
from src.original.PV_MUMC.two_step_IVIM_fit import fit_least_squares_array, fit_least_squares
4+
5+
6+
class PV_MUMC_biexp(OsipiBase):
7+
"""
8+
Bi-exponential fitting algorithm by Paulien Voorter, Maastricht University
9+
"""
10+
11+
# Some basic stuff that identifies the algorithm
12+
id_author = "Paulien Voorter MUMC"
13+
id_algorithm_type = "Bi-exponential fit"
14+
id_return_parameters = "f, D*, D"
15+
id_units = "seconds per milli metre squared or milliseconds per micro metre squared"
16+
17+
# Algorithm requirements
18+
required_bvalues = 4
19+
required_thresholds = [0,0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds
20+
required_bounds = False
21+
required_bounds_optional = True # Bounds may not be required but are optional
22+
required_initial_guess = False
23+
required_initial_guess_optional = True
24+
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
25+
26+
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, weighting=None, stats=False):
27+
"""
28+
Everything this algorithm requires should be implemented here.
29+
Number of segmentation thresholds, bounds, etc.
30+
31+
Our OsipiBase object could contain functions that compare the inputs with
32+
the requirements.
33+
"""
34+
super(PV_MUMC_biexp, self).__init__(bvalues, None, bounds, None)
35+
self.PV_algorithm = fit_least_squares
36+
37+
38+
def ivim_fit(self, signals, bvalues=None):
39+
"""Perform the IVIM fit
40+
41+
Args:
42+
signals (array-like)
43+
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
44+
45+
Returns:
46+
_type_: _description_
47+
"""
48+
49+
50+
fit_results = self.PV_algorithm(bvalues, signals)
51+
52+
f = fit_results[1]
53+
Dstar = fit_results[2]
54+
D = fit_results[0]
55+
56+
return f, Dstar, D

0 commit comments

Comments
 (0)