Skip to content

Commit 7fd3dd8

Browse files
committed
refactor: centralize b-value management at initialization (#155)
- Remove bvalues parameter from all 27 algorithm ivim_fit() and ivim_fit_full_volume() signatures in src/standardized/ - All algorithms now access b-values via self.bvalues exclusively - Add **kwargs to algorithms that lacked it (OJ_GU_seg, PV_MUMC, PvH_KB_NKI, TF_reference) - OsipiBase.osipi_fit() and osipi_fit_full_volume() enforce ValueError if bvalues not set at init - Update all test files to pass bvalues at OsipiBase(...) init - Update WrapImage production wrappers to use new API - Remove now-redundant bvalue equality checks in DL algorithms Files modified: 33 (27 algorithms + OsipiBase + 3 tests + 2 wrappers)
1 parent be85ee3 commit 7fd3dd8

33 files changed

Lines changed: 106 additions & 204 deletions

WrapImage/nifti_wrapper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def loop_over_first_n_minus_1_dimensions(arr):
100100

101101
# Pass additional arguments to the algorithm
102102

103-
fit = OsipiBase(algorithm=args.algorithm)
103+
fit = OsipiBase(algorithm=args.algorithm, bvalues=bvals)
104104
f_image = []
105105
Dp_image = []
106106
D_image = []
@@ -109,7 +109,7 @@ def loop_over_first_n_minus_1_dimensions(arr):
109109
n = data.ndim
110110
total_iteration = np.prod(data.shape[:n-1])
111111
for idx, view in tqdm(loop_over_first_n_minus_1_dimensions(data), desc=f"{args.algorithm} is fitting", dynamic_ncols=True, total=total_iteration):
112-
fit_result = fit.osipi_fit(view, bvals)
112+
fit_result = fit.osipi_fit(view)
113113
f_image.append(fit_result["f"])
114114
Dp_image.append(fit_result["Dp"])
115115
D_image.append(fit_result["D"])

WrapImage/nifti_wrapper_kaapana.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def load_config():
127127
affine = np.array(affine_override).reshape(4, 4)
128128

129129
# Initialize model
130-
fit = OsipiBase(algorithm=algorithm)
130+
fit = OsipiBase(algorithm=algorithm, bvalues=bvals)
131131

132132
# Preallocate output arrays
133133
shape = data.shape[:data.ndim - 1]
@@ -142,7 +142,7 @@ def load_config():
142142
loop_over_first_n_minus_1_dimensions(data),
143143
desc="Fitting IVIM model", dynamic_ncols=True, total=total_iteration
144144
):
145-
fit_result = fit.osipi_fit(view, bvals)
145+
fit_result = fit.osipi_fit(view)
146146
f_image[idx] = fit_result["f"]
147147
Dp_image[idx] = fit_result["Dp"]
148148
D_image[idx] = fit_result["D"]

src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,11 @@ def algorithm(self,dwi_arr, bval_arr, LB0, UB0, x0in):
6666
(f_arr, D_arr, Dx_arr, s0_arr, fitted_dwi_arr, RSS, rms_val, chi, AIC, BIC, R_sq) = results
6767
return D_arr/1000, f_arr, Dx_arr/1000, s0_arr
6868

69-
def ivim_fit(self, signals, bvalues, **kwargs):
69+
def ivim_fit(self, signals, **kwargs):
7070
"""Perform the IVIM fit
7171
7272
Args:
7373
signals (array-like)
74-
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
7574
7675
Returns:
7776
_type_: _description_
@@ -81,7 +80,7 @@ def ivim_fit(self, signals, bvalues, **kwargs):
8180

8281
initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]]
8382

84-
bvalues=np.array(bvalues)
83+
bvalues=np.array(self.bvalues)
8584
LB = np.array(bounds[0])[[1,0,2,3]]
8685
UB = np.array(bounds[1])[[1,0,2,3]]
8786

src/standardized/DT_IIITN_WLS.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,16 @@ def __init__(self, bvalues=None, thresholds=None,
6666
)
6767
self.method = method.upper()
6868

69-
def ivim_fit(self, signals, bvalues, **kwargs):
69+
def ivim_fit(self, signals, **kwargs):
7070
"""Perform the IVIM fit using the selected method (WLS or RLM).
7171
7272
Args:
7373
signals (array-like): Signal intensities at each b-value.
74-
bvalues (array-like, optional): b-values for the signals.
7574
7675
Returns:
7776
dict: Dictionary with keys "D", "f", "Dp".
7877
"""
79-
bvalues = np.array(bvalues)
78+
bvalues = np.array(self.bvalues)
8079

8180
# Use threshold as cutoff if available
8281
cutoff = 200

src/standardized/ETP_SRI_LinearFitting.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,18 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
5959
# Check the inputs
6060

6161

62-
def ivim_fit(self, signals, bvalues=None, linear_fit_option=False, **kwargs):
62+
def ivim_fit(self, signals, linear_fit_option=False, **kwargs):
6363
"""Perform the IVIM fit
6464
6565
Args:
6666
signals (array-like)
67-
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
6867
linear_fit_option (bool, optional): This fit has an option to only run a linear fit. Defaults to False.
6968
7069
Returns:
7170
_type_: _description_
7271
"""
7372
signals[signals<0.0000001]=0.0000001
74-
if bvalues is None:
75-
bvalues = self.bvalues
73+
bvalues = self.bvalues
7674

7775
if self.thresholds is None:
7876
ETP_object = LinearFit()

src/standardized/IAR_LU_biexp.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,11 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
7171
self.IAR_algorithm = None
7272

7373

74-
def ivim_fit(self, signals, bvalues, **kwargs):
74+
def ivim_fit(self, signals, **kwargs):
7575
"""Perform the IVIM fit
7676
7777
Args:
7878
signals (array-like)
79-
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
8079
8180
Returns:
8281
_type_: _description_
@@ -88,10 +87,7 @@ def ivim_fit(self, signals, bvalues, **kwargs):
8887
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]
8988

9089
if self.IAR_algorithm is None:
91-
if bvalues is None:
92-
bvalues = self.bvalues
93-
else:
94-
bvalues = np.asarray(bvalues)
90+
bvalues = np.asarray(self.bvalues)
9591

9692
bvec = np.zeros((bvalues.size, 3))
9793
bvec[:,2] = 1
@@ -110,12 +106,11 @@ def ivim_fit(self, signals, bvalues, **kwargs):
110106
return results
111107

112108

113-
def ivim_fit_full_volume(self, signals, bvalues, **kwargs):
109+
def ivim_fit_full_volume(self, signals, **kwargs):
114110
"""Perform the IVIM fit
115111
116112
Args:
117113
signals (array-like)
118-
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
119114
120115
Returns:
121116
_type_: _description_
@@ -125,16 +120,14 @@ def ivim_fit_full_volume(self, signals, bvalues, **kwargs):
125120
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]
126121
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]
127122
if self.IAR_algorithm is None:
128-
if bvalues is None:
129-
bvalues = self.bvalues
130-
else:
131-
bvalues = np.asarray(bvalues)
123+
bvalues = np.asarray(self.bvalues)
132124

133125
bvec = np.zeros((bvalues.size, 3))
134126
bvec[:,2] = 1
135127
gtab = gradient_table(bvalues, bvecs=bvec, b0_threshold=0)
136128

137129
self.IAR_algorithm = IvimModelBiExp(gtab, bounds=bounds, initial_guess=initial_guess)
130+
bvalues = np.asarray(self.bvalues)
138131
b0_index = np.where(bvalues == 0)[0][0]
139132
mask = signals[...,b0_index]>0
140133
fit_results = self.IAR_algorithm.fit(signals, mask=mask)

src/standardized/IAR_LU_modified_mix.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,11 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
6868
self.IAR_algorithm = None
6969

7070

71-
def ivim_fit(self, signals, bvalues, **kwargs):
71+
def ivim_fit(self, signals, **kwargs):
7272
"""Perform the IVIM fit
7373
7474
Args:
7575
signals (array-like)
76-
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
7776
7877
Returns:
7978
_type_: _description_
@@ -83,10 +82,7 @@ def ivim_fit(self, signals, bvalues, **kwargs):
8382
[self.bounds["f"][1], self.bounds["Dp"][1]*1000, self.bounds["D"][1]*1000]]
8483

8584
if self.IAR_algorithm is None:
86-
if bvalues is None:
87-
bvalues = self.bvalues
88-
else:
89-
bvalues = np.asarray(bvalues)
85+
bvalues = np.asarray(self.bvalues)
9086

9187
bvec = np.zeros((bvalues.size, 3))
9288
bvec[:,2] = 1

src/standardized/IAR_LU_modified_topopro.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,11 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
7272
self.IAR_algorithm = None
7373

7474

75-
def ivim_fit(self, signals, bvalues, **kwargs):
75+
def ivim_fit(self, signals, **kwargs):
7676
"""Perform the IVIM fit
7777
7878
Args:
7979
signals (array-like)
80-
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
8180
8281
Returns:
8382
_type_: _description_
@@ -86,10 +85,7 @@ def ivim_fit(self, signals, bvalues, **kwargs):
8685
[self.bounds["f"][1], self.bounds["Dp"][1]*1000, self.bounds["D"][1]*1000]]
8786

8887
if self.IAR_algorithm is None:
89-
if bvalues is None:
90-
bvalues = self.bvalues
91-
else:
92-
bvalues = np.asarray(bvalues)
88+
bvalues = np.asarray(self.bvalues)
9389

9490
bvec = np.zeros((bvalues.size, 3))
9591
bvec[:,2] = 1

src/standardized/IAR_LU_segmented_2step.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,11 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
7272
self.IAR_algorithm = None
7373

7474

75-
def ivim_fit(self, signals, bvalues, thresholds=None, **kwargs):
75+
def ivim_fit(self, signals, **kwargs):
7676
"""Perform the IVIM fit
7777
7878
Args:
7979
signals (array-like)
80-
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
8180
8281
Returns:
8382
_type_: _description_
@@ -90,10 +89,7 @@ def ivim_fit(self, signals, bvalues, thresholds=None, **kwargs):
9089
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]
9190

9291
if self.IAR_algorithm is None:
93-
if bvalues is None:
94-
bvalues = self.bvalues
95-
else:
96-
bvalues = np.asarray(bvalues)
92+
bvalues = np.asarray(self.bvalues)
9793

9894
bvec = np.zeros((bvalues.size, 3))
9995
bvec[:,2] = 1

src/standardized/IAR_LU_segmented_3step.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,11 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
7575
self.IAR_algorithm = None
7676

7777

78-
def ivim_fit(self, signals, bvalues, **kwargs):
78+
def ivim_fit(self, signals, **kwargs):
7979
"""Perform the IVIM fit
8080
8181
Args:
8282
signals (array-like)
83-
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
8483
8584
Returns:
8685
_type_: _description_
@@ -93,10 +92,7 @@ def ivim_fit(self, signals, bvalues, **kwargs):
9392
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]
9493

9594
if self.IAR_algorithm is None:
96-
if bvalues is None:
97-
bvalues = self.bvalues
98-
else:
99-
bvalues = np.asarray(bvalues)
95+
bvalues = np.asarray(self.bvalues)
10096

10197
bvec = np.zeros((bvalues.size, 3))
10298
bvec[:,2] = 1

0 commit comments

Comments
 (0)