Skip to content

Commit d785c4d

Browse files
Automated testing compatible with Wrapper
some work on initial guess testing, but did not work robust.
1 parent 59767b5 commit d785c4d

File tree

5 files changed

+93
-31
lines changed

5 files changed

+93
-31
lines changed

src/original/OGC_AmsterdamUMC/LSQ_fitting.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ def fit_segmented(bvalues, dw_data, bounds=([0, 0, 0.005],[0.005, 0.7, 0.2]), cu
130130
high_b = bvalues[bvalues >= cutoff]
131131
high_dw_data = dw_data[bvalues >= cutoff]
132132
# correct the bounds. Note that S0 bounds determine the max and min of f
133+
assert bounds[0][1] < p0[1]
134+
assert bounds[1][1] > p0[1]
133135
bounds1 = ([bounds[0][0], 0], [bounds[1][0], 10000000000])
134136
params, _ = curve_fit(lambda b, Dt, int: int * np.exp(-b * Dt ), high_b, high_dw_data,
135137
p0=(p0[0], p0[3]-p0[1]),
@@ -238,15 +240,15 @@ def fit_least_squares(bvalues, dw_data, S0_output=False, fitS0=True,
238240
# bounds are rescaled such that each parameter changes at roughly the same rate to help fitting.
239241
bounds2 = ([bounds[0][0] * 1000, bounds[0][1] * 10, bounds[0][2] * 10],
240242
[bounds[1][0] * 1000, bounds[1][1] * 10, bounds[1][2] * 10])
241-
p0=[p0[0]*1000,p0[1]*10,p0[2]*10]
242-
params, _ = curve_fit(ivimN_noS0, bvalues, dw_data, p0=p0, bounds=bounds2)
243+
p1=[p0[0]*1000,p0[1]*10,p0[2]*10]
244+
params, _ = curve_fit(ivimN_noS0, bvalues, dw_data, p0=p1, bounds=bounds2)
243245
S0 = 1
244246
else:
245247
# bounds are rescaled such that each parameter changes at roughly the same rate to help fitting.
246248
bounds2 = ([bounds[0][0] * 1000, bounds[0][1] * 10, bounds[0][2] * 10, bounds[0][3]],
247249
[bounds[1][0] * 1000, bounds[1][1] * 10, bounds[1][2] * 10, bounds[1][3]])
248-
p0=[p0[0]*1000,p0[1]*10,p0[2]*10,p0[3]]
249-
params, _ = curve_fit(ivimN, bvalues, dw_data, p0=p0, bounds=bounds2)
250+
p1=[p0[0]*1000,p0[1]*10,p0[2]*10,p0[3]]
251+
params, _ = curve_fit(ivimN, bvalues, dw_data, p0=p1, bounds=bounds2)
250252
S0 = params[3]
251253
# correct for the rescaling of parameters
252254
Dt, Fp, Dp = params[0] / 1000, params[1] / 10, params[2] / 10
@@ -259,10 +261,10 @@ def fit_least_squares(bvalues, dw_data, S0_output=False, fitS0=True,
259261
# if fit fails, then do a segmented fit instead
260262
# print('lsq fit failed, trying segmented')
261263
if S0_output:
262-
Dt, Fp, Dp = fit_segmented(bvalues, dw_data, bounds=bounds)
264+
Dt, Fp, Dp = fit_segmented(bvalues, dw_data, bounds=bounds,p0=p0)
263265
return Dt, Fp, Dp, 1
264266
else:
265-
return fit_segmented(bvalues, dw_data, bounds=bounds)
267+
return fit_segmented(bvalues, dw_data, bounds=bounds,p0=p0)
266268

267269

268270
def fit_least_squares_array_tri_exp(bvalues, dw_data, S0_output=True, fitS0=True, njobs=4,

src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class OGC_AmsterdamUMC_Bayesian_biexp(OsipiBase):
3434
supported_initial_guess = True
3535
supported_thresholds = True
3636

37-
def __init__(self, bvalues=None, thresholds=None, bounds=([0, 0, 0.005, 0.7],[0.005, 0.7, 0.2, 1.3]), initial_guess=[0.001, 0.1, 0.01, 1], fitS0=True, prior_in=None):
37+
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True, prior_in=None):
3838

3939
"""
4040
Everything this algorithm requires should be implemented here.
@@ -51,11 +51,20 @@ def __init__(self, bvalues=None, thresholds=None, bounds=([0, 0, 0.005, 0.7],[0.
5151
self.initialize(bounds, initial_guess, fitS0, prior_in)
5252
self.fit_segmented=fit_segmented
5353

54-
def initialize(self, bounds=([0, 0, 0.005, 0.7],[0.005, 0.7, 0.2, 1.3]), initial_guess=[0.001, 0.1, 0.01, 1], fitS0=True, prior_in=None):
55-
self.bounds=bounds
56-
self.use_bounds = True
57-
self.initial_guess = initial_guess
54+
def initialize(self, bounds=None, initial_guess=None, fitS0=True, prior_in=None):
55+
if bounds is None:
56+
print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]')
57+
self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3])
58+
else:
59+
self.bounds=bounds
60+
if initial_guess is None:
61+
print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]')
62+
self.initial_guess = [0.001, 0.001, 0.01, 1]
63+
else:
64+
self.initial_guess = initial_guess
5865
self.use_initial_guess = True
66+
self.use_bounds = True
67+
self.thresholds = 150
5968
if prior_in is None:
6069
print('using a flat prior between bounds')
6170
self.neg_log_prior=flat_neg_log_prior([self.bounds[0][0],self.bounds[1][0]],[self.bounds[0][1],self.bounds[1][1]],[self.bounds[0][2],self.bounds[1][2]],[self.bounds[0][3],self.bounds[1][3]])
@@ -81,7 +90,7 @@ def ivim_fit(self, signals, bvalues, initial_guess=None, **kwargs):
8190

8291
epsilon = 0.000001
8392
fit_results = fit_segmented(bvalues, signals, bounds=self.bounds, cutoff=self.thresholds, p0=self.initial_guess)
84-
fit_results=fit_results+(1,)
93+
fit_results=np.array(fit_results+(1,))
8594
for i in range(4):
8695
if fit_results[i] < self.bounds[0][i] : fit_results[0] = self.bounds[0][i]+epsilon
8796
if fit_results[i] > self.bounds[1][i] : fit_results[0] = self.bounds[1][i]-epsilon

src/standardized/OGC_AmsterdamUMC_biexp.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class OGC_AmsterdamUMC_biexp(OsipiBase):
3333
supported_initial_guess = True
3434
supported_thresholds = False
3535

36-
def __init__(self, bvalues=None, thresholds=None, bounds=([0, 0, 0.005, 0.7],[0.005, 0.7, 0.2, 1.3]), initial_guess=[0.001, 0.1, 0.01, 1], fitS0=True):
36+
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True):
3737
"""
3838
Everything this algorithm requires should be implemented here.
3939
Number of segmentation thresholds, bounds, etc.
@@ -48,14 +48,22 @@ def __init__(self, bvalues=None, thresholds=None, bounds=([0, 0, 0.005, 0.7],[0.
4848
self.initialize(bounds, initial_guess, fitS0)
4949

5050
def initialize(self, bounds, initial_guess, fitS0):
51-
self.bounds=bounds
52-
self.use_bounds = True
53-
self.initial_guess = initial_guess
54-
self.use_initial_guess = True
51+
if bounds is None:
52+
print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]')
53+
self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3])
54+
else:
55+
self.bounds=bounds
56+
if initial_guess is None:
57+
print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]')
58+
self.initial_guess = [0.001, 0.001, 0.01, 1]
59+
else:
60+
self.initial_guess = initial_guess
61+
self.use_initial_guess = True
5562
self.fitS0=fitS0
63+
self.use_initial_guess = True
64+
self.use_bounds = True
5665

57-
58-
def ivim_fit(self, signals, bvalues, initial_guess=None, **kwargs):
66+
def ivim_fit(self, signals, bvalues, **kwargs):
5967
"""Perform the IVIM fit
6068
6169
Args:
@@ -66,8 +74,6 @@ def ivim_fit(self, signals, bvalues, initial_guess=None, **kwargs):
6674
_type_: _description_
6775
"""
6876

69-
if initial_guess is not None and len(initial_guess) == 4:
70-
self.initial_guess = initial_guess
7177
bvalues=np.array(bvalues)
7278
fit_results = self.OGC_algorithm(bvalues, signals, p0=self.initial_guess, bounds=self.bounds, fitS0=self.fitS0)
7379

src/standardized/OGC_AmsterdamUMC_biexp_segmented.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class OGC_AmsterdamUMC_biexp_segmented(OsipiBase):
3333
supported_initial_guess = True
3434
supported_thresholds = True
3535

36-
def __init__(self, bvalues=None, thresholds=150, bounds=([0, 0, 0.005],[0.005, 0.7, 0.2]), initial_guess=[0.001, 0.01, 0.01,1]):
36+
def __init__(self, bvalues=None, thresholds=150, bounds=None, initial_guess=None):
3737
"""
3838
Everything this algorithm requires should be implemented here.
3939
Number of segmentation thresholds, bounds, etc.
@@ -47,12 +47,23 @@ def __init__(self, bvalues=None, thresholds=150, bounds=([0, 0, 0.005],[0.005, 0
4747

4848

4949
def initialize(self, bounds, initial_guess, thresholds):
50-
self.bounds=bounds
51-
self.use_bounds=True
52-
self.initial_guess = initial_guess
53-
self.use_initial_guess=True
54-
self.thresholds = thresholds
55-
50+
if bounds is None:
51+
print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]')
52+
self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3])
53+
else:
54+
self.bounds=bounds
55+
if initial_guess is None:
56+
print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]')
57+
self.initial_guess = [0.001, 0.001, 0.01, 1]
58+
else:
59+
self.initial_guess = initial_guess
60+
self.use_initial_guess = True
61+
self.use_bounds = True
62+
if thresholds is None:
63+
self.thresholds = 150
64+
print('warning, no thresholds were defined, so default bounds are used of 150')
65+
else:
66+
self.thresholds = thresholds
5667
def ivim_fit(self, signals, bvalues, **kwargs):
5768
"""Perform the IVIM fit
5869

tests/IVIMmodels/unit_tests/test_ivim_fit.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import os
77
import time
88

9+
from exceptiongroup import catch
10+
911
from src.wrappers.OsipiBase import OsipiBase
1012
from utilities.data_simulation.GenerateData import GenerateData
1113
#run using python -m pytest from the root folder
@@ -70,7 +72,7 @@ def test_ivim_fit_saved(name, bvals, data, algorithm, xfail, kwargs, tolerances,
7072
signal = signal_helper(data["data"])
7173
tolerances = tolerances_helper(tolerances, data)
7274
start_time = time.time() # Record the start time
73-
fit = OsipiBase(algorithm=algorithm, **kwargs)
75+
fit = OsipiBase(algorithm=algorithm, **kwargs)
7476
fit_result = fit.osipi_fit(signal, bvals)
7577
elapsed_time = time.time() - start_time # Calculate elapsed time
7678
def to_list_if_needed(value):
@@ -113,7 +115,7 @@ def test_default_bounds_and_initial_guesses(algorithm):
113115
fit = OsipiBase(algorithm=algorithm)
114116
#assert fit.bounds is not None, f"For {algorithm}, there is no default fit boundary"
115117
#assert fit.initial_guess is not None, f"For {algorithm}, there is no default fit initial guess"
116-
if fit.bounds is not None:
118+
if fit.use_bounds:
117119
assert 0 <= fit.bounds[0][0] <= 0.003, f"For {algorithm}, the default lower bound of D {fit.bounds[0][0]} is unrealistic"
118120
assert 0 <= fit.bounds[1][0] <= 0.01, f"For {algorithm}, the default upper bound of D {fit.bounds[1][0]} is unrealistic"
119121
assert 0 <= fit.bounds[0][1] <= 1, f"For {algorithm}, the default lower bound of f {fit.bounds[0][1]} is unrealistic"
@@ -123,7 +125,7 @@ def test_default_bounds_and_initial_guesses(algorithm):
123125
assert 0 <= fit.bounds[0][3] <= 1, f"For {algorithm}, the default lower bound of S {fit.bounds[0][3]} is unrealistic; note data is normaized"
124126
assert 1 <= fit.bounds[1][3] <= 1000, f"For {algorithm}, the default upper bound of S {fit.bounds[1][3]} is unrealistic; note data is normaized"
125127
assert fit.bounds[1][0] <= fit.bounds[0][2], f"For {algorithm}, the default upper bound of D {fit.bounds[1][0]} is higher than lower bound of D* {fit.bounds[0][2]}"
126-
if fit.initial_guess is not None:
128+
if fit.use_initial_guess:
127129
assert 0.0008 <= fit.initial_guess[0] <= 0.002, f"For {algorithm}, the default initial guess for D {fit.initial_guess[0]} is unrealistic"
128130
assert 0 <= fit.initial_guess[1] <= 0.5, f"For {algorithm}, the default initial guess for f {fit.initial_guess[1]} is unrealistic"
129131
assert 0.003 <= fit.initial_guess[2] <= 0.1, f"For {algorithm}, the default initial guess for Ds {fit.initial_guess[2]} is unrealistic"
@@ -169,5 +171,37 @@ def test_bounds(name, bvals, data, algorithm, xfail, kwargs, tolerances, request
169171
assert bounds[0][2] <= fit_result['Dp'] <= bounds[1][2], f"Result {fit_result['Dp']} out of bounds for data: {name}"
170172
# S0 is not returned as argument...
171173
#assert bounds[0][3] <= fit_result['S0'] <= bounds[1][3], f"Result {fit_result['S0']} out of bounds for data: {name}"
174+
'''if fit.use_initial_guess:
175+
passD=False
176+
passf=False
177+
passDp=False
178+
fit = OsipiBase(algorithm=algorithm, bounds=bounds, initial_guess=[0.0007, 0.25, 0.015, 1.2], **kwargs)
179+
try:
180+
fit_result = fit.osipi_fit(signal, bvals)
181+
if fit_result['D'] == 0:
182+
passD=True
183+
except:
184+
passD=True
185+
fit = OsipiBase(algorithm=algorithm, bounds=bounds, initial_guess=[0.001, 0.19, 0.015, 1.2], **kwargs)
186+
try:
187+
fit_result = fit.osipi_fit(signal, bvals)
188+
if fit_result['f'] == 0:
189+
passf=True
190+
except:
191+
passf = True
192+
try:
193+
fit = OsipiBase(algorithm=algorithm, bounds=bounds, initial_guess=[0.001, 0.25, 0.009, 1.2], **kwargs)
194+
if fit_result['Dp'] == 0:
195+
passDp = True
196+
except:
197+
passDp = True
198+
assert passD, f"Fit still passes when initial guess D is out of fit bounds; potentially initial guesses not respected for: {name}"
199+
assert passf, f"Fit still passes when initial guess f is out of fit bounds; potentially initial guesses not respected for: {name}"
200+
assert passDp, f"Fit still passes when initial guess Ds is out of fit bounds; potentially initial guesses not respected for: {name}" '''
201+
202+
203+
204+
205+
172206

173207

0 commit comments

Comments
 (0)