Skip to content

Commit 1bcb389

Browse files
Boundary testing and timing
Added testing for whether bounds are respected and whether timing of algorithms make sense
1 parent 8d1a418 commit 1bcb389

File tree

3 files changed

+70
-28
lines changed

3 files changed

+70
-28
lines changed

src/standardized/OGC_AmsterdamUMC_biexp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class OGC_AmsterdamUMC_biexp(OsipiBase):
2727
required_initial_guess_optional = True
2828
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
2929

30-
def __init__(self, bvalues=None, thresholds=None, bounds=([0, 0, 0.005, 0.7],[0.005, 0.7, 0.2, 1.3]), initial_guess=None, fitS0=False):
30+
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=False):
3131
"""
3232
Everything this algorithm requires should be implemented here.
3333
Number of segmentation thresholds, bounds, etc.
@@ -45,7 +45,7 @@ def initialize(self, bounds, initial_guess, fitS0):
4545
else:
4646
self.bounds=bounds
4747
if initial_guess is None:
48-
self.initial_guess = [0.001, 0.001, 0.01, 1]
48+
self.initial_guess = [0.001, 0.1, 0.01, 1]
4949
else:
5050
self.initial_guess = initial_guess
5151
self.fitS0=fitS0

tests/IVIMmodels/unit_tests/algorithms.json

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,22 @@
1515
"OJ_GU_seg"
1616
],
1717
"IAR_LU_biexp": {
18-
"tolerances": {
19-
20-
}
18+
"skip_bounds": true
2119
},
2220
"IAR_LU_modified_mix": {
23-
"tolerances": {
24-
25-
}
21+
"skip_bounds": true
2622
},
2723
"IAR_LU_modified_topopro": {
28-
"tolerances": {
29-
30-
}
24+
"skip_bounds": true
3125
},
3226
"IAR_LU_segmented_2step": {
33-
"tolerances": {
34-
35-
}
27+
"skip_bounds": true
3628
},
3729
"IAR_LU_segmented_3step": {
38-
"tolerances": {
39-
40-
}
30+
"skip_bounds": true
4131
},
4232
"IAR_LU_subtracted": {
43-
"tolerances": {
44-
45-
}
33+
"skip_bounds": true
4634
},
4735
"ETP_SRI_LinearFitting": {
4836
"options": {
@@ -73,16 +61,25 @@
7361
"D": 0.001,
7462
"Dp": 2
7563
}
76-
}
64+
},
65+
"skip_bounds": true
7766
},
7867
"OGC_AmsterdamUMC_biexp_segmented": {
79-
"tolerances": {
80-
81-
}
68+
"skip_bounds": true
8269
},
8370
"OJ_GU_seg": {
84-
"tolerances": {
85-
86-
}
71+
"test_bounds": false
72+
},
73+
"OGC_AmsterdamUMC_Bayesian_biexp": {
74+
"test_bounds": false
75+
},
76+
"MUMC_biexp": {
77+
"test_bounds": false
78+
},
79+
"PvH_KB_NKI_IVIMfit": {
80+
"test_bounds": false
81+
},
82+
"OGC_AmsterdamUMC_biexp": {
83+
"test_bounds": true
8784
}
8885
}

tests/IVIMmodels/unit_tests/test_ivim_fit.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import pathlib
66
import os
7+
import time
78

89
from src.wrappers.OsipiBase import OsipiBase
910
from utilities.data_simulation.GenerateData import GenerateData
@@ -158,19 +159,63 @@ def data_ivim_fit_saved():
158159
tolerances = algorithm_dict.get("tolerances", {})
159160
yield name, bvals, data, algorithm, xfail, kwargs, tolerances
160161

161-
162162
@pytest.mark.parametrize("name, bvals, data, algorithm, xfail, kwargs, tolerances", data_ivim_fit_saved())
163163
def test_ivim_fit_saved(name, bvals, data, algorithm, xfail, kwargs, tolerances, request):
164164
if xfail["xfail"]:
165165
mark = pytest.mark.xfail(reason="xfail", strict=xfail["strict"])
166166
request.node.add_marker(mark)
167+
start_time = time.time() # Record the start time
167168
fit = OsipiBase(algorithm=algorithm, **kwargs)
168169
signal = signal_helper(data["data"])
169170
tolerances = tolerances_helper(tolerances, data)
170171
[f_fit, Dp_fit, D_fit] = fit.osipi_fit(signal, bvals)
172+
elapsed_time = time.time() - start_time # Calculate elapsed time
171173
npt.assert_allclose(f_fit,data['f'], rtol=tolerances["rtol"]["f"], atol=tolerances["atol"]["f"])
172174
if data['f']<0.80: # we need some signal for D to be detected
173175
npt.assert_allclose(D_fit,data['D'], rtol=tolerances["rtol"]["D"], atol=tolerances["atol"]["D"])
174176
if data['f']>0.03: #we need some f for D* to be interpretable
175177
npt.assert_allclose(Dp_fit,data['Dp'], rtol=tolerances["rtol"]["Dp"], atol=tolerances["atol"]["Dp"])
178+
assert elapsed_time < 2, f"Algorithm {name} took {elapsed_time} seconds, which is longer than 2 second to fit per voxel" #less than 0.5 seconds per voxel
179+
180+
def bound_input():
181+
# Find the algorithms from algorithms.json
182+
file = pathlib.Path(__file__)
183+
algorithm_path = file.with_name('algorithms.json')
184+
with algorithm_path.open() as f:
185+
algorithm_information = json.load(f)
186+
187+
# Load generic test data generated from the included phantom: phantoms/MR_XCAT_qMRI
188+
generic = file.with_name('generic.json')
189+
with generic.open() as f:
190+
all_data = json.load(f)
191+
192+
algorithms = algorithm_information["algorithms"]
193+
bvals = all_data.pop('config')
194+
bvals = bvals['bvalues']
195+
for name, data in all_data.items():
196+
for algorithm in algorithms:
197+
algorithm_dict = algorithm_information.get(algorithm, {})
198+
xfail = {"xfail": name in algorithm_dict.get("xfail_names", {}),
199+
"strict": algorithm_dict.get("xfail_names", {}).get(name, True)}
200+
kwargs = algorithm_dict.get("options", {})
201+
tolerances = algorithm_dict.get("tolerances", {})
202+
test_bounds = algorithm_dict.get("test_bounds", {})
203+
if test_bounds:
204+
yield name, bvals, data, algorithm, xfail, kwargs, tolerances
205+
206+
207+
@pytest.mark.parametrize("name, bvals, data, algorithm, xfail, kwargs, tolerances", bound_input())
208+
def test_bounds(name, bvals, data, algorithm, xfail, kwargs, tolerances, request):
209+
bounds = ([0.0008, 0.2, 0.01, 1.1], [0.0012, 0.3, 0.02, 1.3])
210+
if xfail["xfail"]:
211+
mark = pytest.mark.xfail(reason="xfail", strict=xfail["strict"])
212+
request.node.add_marker(mark)
213+
# deliberately have silly bounds to see whether they are used
214+
fit = OsipiBase(algorithm=algorithm, bounds=bounds, initial_guess = [0.001, 0.25, 0.015, 1.2], **kwargs)
215+
signal = signal_helper(data["data"])
216+
tolerances = tolerances_helper(tolerances, data)
217+
[f_fit, Dp_fit, D_fit] = fit.osipi_fit(signal, bvals)
176218

219+
assert bounds[0][0] <= D_fit <= bounds[1][0], f"Result {D_fit} out of bounds for data: {name}"
220+
assert bounds[0][1] <= f_fit <= bounds[1][1], f"Result {f_fit} out of bounds for data: {name}"
221+
assert bounds[0][2] <= Dp_fit <= bounds[1][2], f"Result {Dp_fit} out of bounds for data: {name}"

0 commit comments

Comments
 (0)