Skip to content

Commit 122cbe3

Browse files
Merge pull request #106 from OSIPI/wrapper_dev
Wrapper dev - attribute harmonization and signal normalization
2 parents 34e033b + 24c5bfa commit 122cbe3

19 files changed

+121
-72
lines changed

doc/Introduction_to_TF24_IVIM-MRI_CodeCollection_github_and_IVIM_Analysis_using_Python.ipynb

Lines changed: 68 additions & 50 deletions
Large diffs are not rendered by default.

ivim_simulation.bval

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0 50 100 500 1000

ivim_simulation.bvec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
1 0 0
2+
0 1 0
3+
0 0 1

src/standardized/ETP_SRI_LinearFitting.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ class ETP_SRI_LinearFitting(OsipiBase):
2525
required_bounds_optional = True # Bounds may not be required but are optional
2626
required_initial_guess = False
2727
required_initial_guess_optional = False
28-
accepted_dimensions = 1
2928
# Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
3029

3130
# Supported inputs in the standardized class
3231
supported_bounds = False
3332
supported_initial_guess = False
3433
supported_thresholds = True
34+
supported_dimensions = 1
35+
supported_priors = False
3536

3637
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, weighting=None, stats=False):
3738
"""

src/standardized/IAR_LU_biexp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ class IAR_LU_biexp(OsipiBase):
2626
required_bounds_optional = True # Bounds may not be required but are optional
2727
required_initial_guess = False
2828
required_initial_guess_optional = True
29-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
3029

3130
# Supported inputs in the standardized class
3231
supported_bounds = True
3332
supported_initial_guess = True
3433
supported_thresholds = False
34+
supported_dimensions = 1
35+
supported_priors = False
3536

3637
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, weighting=None, stats=False):
3738
"""

src/standardized/IAR_LU_modified_mix.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ class IAR_LU_modified_mix(OsipiBase):
2626
required_bounds_optional = True # Bounds may not be required but are optional
2727
required_initial_guess = False
2828
required_initial_guess_optional = True
29-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
3029

3130
# Supported inputs in the standardized class
3231
supported_bounds = True
3332
supported_initial_guess = False
3433
supported_thresholds = False
34+
supported_dimensions = 1
35+
supported_priors = False
3536

3637
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, weighting=None, stats=False):
3738
"""

src/standardized/IAR_LU_modified_topopro.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ class IAR_LU_modified_topopro(OsipiBase):
2626
required_bounds_optional = True # Bounds may not be required but are optional
2727
required_initial_guess = False
2828
required_initial_guess_optional = True
29-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
3029

3130
# Supported inputs in the standardized class
3231
supported_bounds = True
3332
supported_initial_guess = False
3433
supported_thresholds = False
34+
supported_dimensions = 1
35+
supported_priors = False
3536

3637
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, weighting=None, stats=False):
3738
"""

src/standardized/IAR_LU_segmented_2step.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ class IAR_LU_segmented_2step(OsipiBase):
2626
required_bounds_optional = True # Bounds may not be required but are optional
2727
required_initial_guess = False
2828
required_initial_guess_optional = True
29-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
3029

3130
# Supported inputs in the standardized class
3231
supported_bounds = True
3332
supported_initial_guess = True
3433
supported_thresholds = True
34+
supported_dimensions = 1
35+
supported_priors = False
3536

3637
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None):
3738
"""

src/standardized/IAR_LU_segmented_3step.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ class IAR_LU_segmented_3step(OsipiBase):
2626
required_bounds_optional = True # Bounds may not be required but are optional
2727
required_initial_guess = False
2828
required_initial_guess_optional = True
29-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
3029

3130
# Supported inputs in the standardized class
3231
supported_bounds = True
3332
supported_initial_guess = True
3433
supported_thresholds = False
34+
supported_dimensions = 1
35+
supported_priors = False
3536

3637
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, weighting=None, stats=False):
3738
"""

src/standardized/IAR_LU_subtracted.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ class IAR_LU_subtracted(OsipiBase):
2626
required_bounds_optional = True # Bounds may not be required but are optional
2727
required_initial_guess = False
2828
required_initial_guess_optional = True
29-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
3029

3130
# Supported inputs in the standardized class
3231
supported_bounds = True
3332
supported_initial_guess = True
3433
supported_thresholds = False
34+
supported_dimensions = 1
35+
supported_priors = False
3536

3637
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None):
3738
"""

src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ class OGC_AmsterdamUMC_Bayesian_biexp(OsipiBase):
2525
required_bounds_optional = True # Bounds may not be required but are optional
2626
required_initial_guess = False
2727
required_initial_guess_optional = True
28-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
29-
accepts_priors = True
3028

3129

3230
# Supported inputs in the standardized class
3331
supported_bounds = True
3432
supported_initial_guess = True
3533
supported_thresholds = True
34+
supported_dimensions = 1
35+
supported_priors = True
3636

3737
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True, prior_in=None):
3838

src/standardized/OGC_AmsterdamUMC_biexp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ class OGC_AmsterdamUMC_biexp(OsipiBase):
2525
required_bounds_optional = True # Bounds may not be required but are optional
2626
required_initial_guess = False
2727
required_initial_guess_optional = True
28-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
2928

3029

3130
# Supported inputs in the standardized class
3231
supported_bounds = True
3332
supported_initial_guess = True
3433
supported_thresholds = False
34+
supported_dimensions = 1
35+
supported_priors = False
3536

3637
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True):
3738
"""

src/standardized/OGC_AmsterdamUMC_biexp_segmented.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ class OGC_AmsterdamUMC_biexp_segmented(OsipiBase):
2525
required_bounds_optional = True # Bounds may not be required but are optional
2626
required_initial_guess = False
2727
required_initial_guess_optional = True
28-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
2928

3029

3130
# Supported inputs in the standardized class
3231
supported_bounds = True
3332
supported_initial_guess = True
3433
supported_thresholds = True
34+
supported_dimensions = 1
35+
supported_priors = False
3536

3637
def __init__(self, bvalues=None, thresholds=150, bounds=None, initial_guess=None):
3738
"""

src/standardized/OJ_GU_seg.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ class OJ_GU_seg(OsipiBase):
2424
required_bounds_optional = False # Bounds may not be required but are optional
2525
required_initial_guess = False
2626
required_initial_guess_optional = False
27-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
2827

2928
# Supported inputs in the standardized class
3029
supported_bounds = False
3130
supported_initial_guess = False
3231
supported_thresholds = True
32+
supported_dimensions = 1
33+
supported_priors = False
3334

3435
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, weighting=None, stats=False):
3536
"""

src/standardized/PV_MUMC_biexp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ class PV_MUMC_biexp(OsipiBase):
2121
required_bounds_optional = True # Bounds may not be required but are optional
2222
required_initial_guess = False
2323
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?
2524

2625
# Supported inputs in the standardized class
2726
supported_bounds = True
2827
supported_initial_guess = False
2928
supported_thresholds = True
29+
supported_dimensions = 1
30+
supported_priors = False
3031

3132
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, weighting=None, stats=False):
3233
"""

src/standardized/PvH_KB_NKI_IVIMfit.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ class PvH_KB_NKI_IVIMfit(OsipiBase):
2525
required_bounds_optional = False # Bounds may not be required but are optional
2626
required_initial_guess = False
2727
required_initial_guess_optional =False
28-
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
2928

3029
# Supported inputs in the standardized class
3130
supported_bounds = False
3231
supported_initial_guess = False
3332
supported_thresholds = False
33+
supported_dimensions = 1
34+
supported_priors = False
3435

3536
def __init__(self, bvalues=None, thresholds=None,bounds=None,initial_guess=None):
3637
"""

src/wrappers/OsipiBase.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,16 @@ def osipi_fit(self, data, bvalues=None, **kwargs):
103103
#args = [data[ijk], use_bvalues]
104104
#fit = list(self.ivim_fit(*args, **kwargs))
105105
#results[ijk] = fit
106+
minimum_bvalue = np.min(use_bvalues) # We normalize the signal to the minimum bvalue. Should be 0 or very close to 0.
107+
b0_indices = np.where(use_bvalues == minimum_bvalue)[0]
106108

107109
for ijk in tqdm(np.ndindex(data.shape[:-1]), total=np.prod(data.shape[:-1])):
108-
args = [data[ijk], use_bvalues]
110+
# Normalize array
111+
single_voxel_data = data[ijk]
112+
single_voxel_data_normalization_factor = np.mean(single_voxel_data[b0_indices])
113+
single_voxel_data_normalized = single_voxel_data/single_voxel_data_normalization_factor
114+
115+
args = [single_voxel_data_normalized, use_bvalues]
109116
fit = self.ivim_fit(*args, **kwargs) # For single voxel fits, we assume this is a dict with a float value per key.
110117
for key in list(fit.keys()):
111118
results[key][ijk] = fit[key]
@@ -141,7 +148,15 @@ def osipi_fit_full_volume(self, data, bvalues=None, **kwargs):
141148
for key in self.result_keys:
142149
results[key] = np.empty(list(data.shape[:-1]))
143150

144-
args = [data, use_bvalues]
151+
minimum_bvalue = np.min(use_bvalues) # We normalize the signal to the minimum bvalue. Should be 0 or very close to 0.
152+
b0_indices = np.where(use_bvalues == minimum_bvalue)[0]
153+
b0_mean = np.mean(data[..., b0_indices], axis=-1)
154+
155+
normalization_factors = np.array([b0_mean for i in range(data.shape[-1])])
156+
normalization_factors = np.moveaxis(normalization_factors, 0, -1)
157+
data_normalized = data/normalization_factors
158+
159+
args = [data_normalized, use_bvalues]
145160
fit = self.ivim_fit_full_volume(*args, **kwargs) # Assume this is a dict with an array per key representing the parametric maps
146161
for key in list(fit.keys()):
147162
results[key] = fit[key]

tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@ def ivim_model(b, S0=1, f=0.1, Dstar=0.01, D=0.001):
2222
return S0*(f*np.exp(-b*Dstar) + (1-f)*np.exp(-b*D))
2323

2424
signals = ivim_model(bvalues)
25-
data = np.array([signals, signals, signals])
25+
data = np.array([[signals, signals, signals], [signals, signals, signals]])
2626
#print(data)
2727
signals = data
2828

2929
#model = ETP_SRI_LinearFitting(thresholds=[200])
3030
if kwargs:
31-
results = model.osipi_fit(signals, bvalues, **kwargs)
31+
results = model.osipi_fit_full_volume(signals, bvalues, **kwargs)
3232
else:
33-
results = model.osipi_fit(signals, bvalues)
33+
results = model.osipi_fit_full_volume(signals, bvalues)
3434
print(results)
3535
#test = model.osipi_simple_bias_and_RMSE_test(SNR=20, bvalues=bvalues, f=0.1, Dstar=0.03, D=0.001, noise_realizations=10)
3636

3737
#model1 = ETP_SRI_LinearFitting(thresholds=[200])
38-
#model2 = IAR_LU_biexp(bounds=([0,0,0,0], [1,1,1,1]))
38+
model2 = IAR_LU_biexp(bounds=([0,0,0,0], [1,1,1,1]))
3939
#model2 = IAR_LU_modified_mix()
40-
model2 = OGC_AmsterdamUMC_biexp()
40+
#model2 = OGC_AmsterdamUMC_biexp()
4141

4242
#dev_test_run(model1)
4343
dev_test_run(model2)

tests/IVIMmodels/unit_tests/test_ivim_synthetic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_generated(algorithmlist, ivim_data, SNR, rtol, atol, fit_count, rician_
2525
Dp = data["Dp"]
2626
fit = OsipiBase(algorithm=ivim_algorithm)
2727
# here is a prior
28-
if use_prior and hasattr(fit, "accepts_priors") and fit.accepts_priors:
28+
if use_prior and hasattr(fit, "supported_priors") and fit.supported_priors:
2929
prior = [rng.normal(D, D/3, 10), rng.normal(f, f/3, 10), rng.normal(Dp, Dp/3, 10), rng.normal(1, 1/3, 10)]
3030
# prior = [np.repeat(D, 10)+np.random.normal(0,D/3,np.shape(np.repeat(D, 10))), np.repeat(f, 10)+np.random.normal(0,f/3,np.shape(np.repeat(D, 10))), np.repeat(Dp, 10)+np.random.normal(0,Dp/3,np.shape(np.repeat(D, 10))),np.repeat(np.ones_like(Dp), 10)+np.random.normal(0,1/3,np.shape(np.repeat(D, 10)))] # D, f, D*
3131
fit.initialize(prior_in=prior)

0 commit comments

Comments
 (0)