Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,14 @@ phantoms/MR_XCAT_qMRI/*.json
phantoms/MR_XCAT_qMRI/*.txt
tests/IVIMmodels/unit_tests/models
models

# Custom additions for testing & local setups
ivim_test_venv/
venv/
.env/
env/
check_zenodo.py
*test_output*.txt
.vscode/
.DS_Store
Thumbs.db
53 changes: 35 additions & 18 deletions src/standardized/IAR_LU_biexp.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,13 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
bvec = np.zeros((self.bvalues.size, 3))
bvec[:,2] = 1
gtab = gradient_table(self.bvalues, bvec, b0_threshold=0)

self.IAR_algorithm = IvimModelBiExp(gtab, bounds=self.bounds, initial_guess=self.initial_guess)

# Convert dict bounds/initial_guess to list-of-lists as expected by IvimModelBiExp
bounds_list = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]],
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]
initial_guess_list = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]

self.IAR_algorithm = IvimModelBiExp(gtab, bounds=bounds_list, initial_guess=initial_guess_list)
else:
self.IAR_algorithm = None

Expand All @@ -71,31 +76,43 @@ def ivim_fit(self, signals, bvalues, **kwargs):

Args:
signals (array-like)
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used.

Returns:
_type_: _description_
dict: Fitted IVIM parameters f, Dp (D*), and D.
"""
# --- bvalues resolution ---
if bvalues is None:
if self.bvalues is None:
raise ValueError(
"IAR_LU_biexp: bvalues must be provided either at initialization or at fit time."
)
bvalues = self.bvalues
else:
bvalues = np.asarray(bvalues)

# Make sure bounds and initial guess conform to the algorithm requirements
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]],
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]
# Convert bounds and initial guess dicts to lists as expected by IvimModelBiExp
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]],
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]

if self.IAR_algorithm is None:
if bvalues is None:
bvalues = self.bvalues
else:
bvalues = np.asarray(bvalues)


# Guard: reinitialise if not yet built, OR if bvalues have changed since last build
current_bvals = None if self.IAR_algorithm is None else self.IAR_algorithm.bvals
bvalues_changed = (current_bvals is not None) and not np.array_equal(current_bvals, bvalues)

if self.IAR_algorithm is None or bvalues_changed:
bvec = np.zeros((bvalues.size, 3))
bvec[:,2] = 1
gtab = gradient_table(bvalues, bvecs=bvec, b0_threshold=0)

self.IAR_algorithm = IvimModelBiExp(gtab, bounds=bounds, initial_guess=initial_guess)

fit_results = self.IAR_algorithm.fit(signals)


try:
fit_results = self.IAR_algorithm.fit(signals)
except Exception as e:
print(f"IAR_LU_biexp: fit failed ({type(e).__name__}: {e}). Returning default parameters.")
results = {"f": self.initial_guess["f"], "Dp": self.initial_guess["Dp"], "D": self.initial_guess["D"]}
return results

results = {}
results["f"] = fit_results.model_params[1]
results["Dp"] = fit_results.model_params[2]
Expand Down
70 changes: 42 additions & 28 deletions src/standardized/IAR_LU_segmented_2step.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
bvec = np.zeros((self.bvalues.size, 3))
bvec[:,2] = 1
gtab = gradient_table(self.bvalues, bvec, b0_threshold=0)
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]

# Adapt the initial guess to the format needed for the algorithm
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]

self.IAR_algorithm = IvimModelSegmented2Step(gtab, bounds=bounds, initial_guess=initial_guess, b_threshold=self.thresholds)
# Convert dict bounds/initial_guess to list-of-lists as expected by IvimModelSegmented2Step
bounds_list = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]],
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]
initial_guess_list = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]

self.IAR_algorithm = IvimModelSegmented2Step(gtab, bounds=bounds_list, initial_guess=initial_guess_list, b_threshold=self.thresholds)
else:
self.IAR_algorithm = None

Expand All @@ -77,43 +77,57 @@ def ivim_fit(self, signals, bvalues, thresholds=None, **kwargs):

Args:
signals (array-like)
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used.

Returns:
_type_: _description_
dict: Fitted IVIM parameters f, Dp (D*), and D.
"""
# --- bvalues resolution ---
if bvalues is None:
if self.bvalues is None:
raise ValueError(
"IAR_LU_segmented_2step: bvalues must be provided either at initialization or at fit time."
)
bvalues = self.bvalues
else:
bvalues = np.asarray(bvalues)

# Adapt the bounds to the format needed for the algorithm
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]],
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]

# Adapt the initial guess to the format needed for the algorithm
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]
if self.IAR_algorithm is None:
if bvalues is None:
bvalues = self.bvalues
else:
bvalues = np.asarray(bvalues)

# Guard: reinitialise if the algorithm is not yet built, OR if bvalues have changed
# (calling with different bvalues than __init__ must rebuild the gradient table)
current_bvals = None if self.IAR_algorithm is None else self.IAR_algorithm.bvals
bvalues_changed = (current_bvals is not None) and not np.array_equal(current_bvals, bvalues)

if self.IAR_algorithm is None or bvalues_changed:
bvec = np.zeros((bvalues.size, 3))
bvec[:,2] = 1
gtab = gradient_table(bvalues, bvec, b0_threshold=0)

if self.thresholds is None:
self.thresholds = 200

self.IAR_algorithm = IvimModelSegmented2Step(gtab, bounds=bounds, initial_guess=initial_guess, b_threshold=self.thresholds)

fit_results = self.IAR_algorithm.fit(signals)

#f = fit_results.model_params[1]
#Dstar = fit_results.model_params[2]
#D = fit_results.model_params[3]

#return f, Dstar, D
self.IAR_algorithm = IvimModelSegmented2Step(
gtab, bounds=bounds, initial_guess=initial_guess, b_threshold=self.thresholds
)

try:
fit_results = self.IAR_algorithm.fit(signals)
except Exception as e:
print(f"IAR_LU_segmented_2step: fit failed ({type(e).__name__}: {e}). Returning default parameters.")
results = {"f": self.initial_guess["f"], "Dp": self.initial_guess["Dp"], "D": self.initial_guess["D"]}
return results

results = {}
results["f"] = fit_results.model_params[1]
results["Dp"] = fit_results.model_params[2]
results["D"] = fit_results.model_params[3]

# Ensure D < Dp (swap if the optimizer returned them in wrong order)
results = self.D_and_Ds_swap(results)

return results
67 changes: 37 additions & 30 deletions src/standardized/IAR_LU_segmented_3step.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,15 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
bvec[:,2] = 1
gtab = gradient_table(self.bvalues, bvec, b0_threshold=0)

# Adapt the bounds to the format needed for the algorithm
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]
# Adapt the bounds to the format needed for the algorithm (list-of-lists)
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]],
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]

# Adapt the initial guess to the format needed for the algorithm
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]

self.IAR_algorithm = IvimModelSegmented3Step(gtab, bounds=self.bounds, initial_guess=self.initial_guess)

# Use the converted list-of-lists bounds and initial_guess, NOT the raw dicts
self.IAR_algorithm = IvimModelSegmented3Step(gtab, bounds=bounds, initial_guess=initial_guess)
else:
self.IAR_algorithm = None

Expand All @@ -79,40 +80,46 @@ def ivim_fit(self, signals, bvalues, **kwargs):

Args:
signals (array-like)
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used.

Returns:
_type_: _description_
dict: Fitted IVIM parameters f, Dp (D*), and D.
"""
# Adapt the bounds to the format needed for the algorithm
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]

# Adapt the initial guess to the format needed for the algorithm
# --- bvalues resolution ---
if bvalues is None:
if self.bvalues is None:
raise ValueError(
"IAR_LU_segmented_3step: bvalues must be provided either at initialization or at fit time."
)
bvalues = self.bvalues
else:
bvalues = np.asarray(bvalues)

# Adapt bounds and initial guess dicts to list-of-lists as expected by IvimModelSegmented3Step
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]],
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]

if self.IAR_algorithm is None:
if bvalues is None:
bvalues = self.bvalues
else:
bvalues = np.asarray(bvalues)


# Guard: reinitialise if not yet built, OR if bvalues have changed since last build
current_bvals = None if self.IAR_algorithm is None else self.IAR_algorithm.bvals
bvalues_changed = (current_bvals is not None) and not np.array_equal(current_bvals, bvalues)

if self.IAR_algorithm is None or bvalues_changed:
bvec = np.zeros((bvalues.size, 3))
bvec[:,2] = 1
gtab = gradient_table(bvalues, bvec, b0_threshold=0)

self.IAR_algorithm = IvimModelSegmented3Step(gtab, bounds=bounds, initial_guess=initial_guess)
fit_results = self.IAR_algorithm.fit(signals)

#f = fit_results.model_params[1]
#Dstar = fit_results.model_params[2]
#D = fit_results.model_params[3]

#return f, Dstar, D

try:
fit_results = self.IAR_algorithm.fit(signals)
except Exception as e:
print(f"IAR_LU_segmented_3step: fit failed ({type(e).__name__}: {e}). Returning default parameters.")
results = {"f": self.initial_guess["f"], "Dp": self.initial_guess["Dp"], "D": self.initial_guess["D"]}
return results

results = {}
results["f"] = fit_results.model_params[1]
results["Dp"] = fit_results.model_params[2]
results["D"] = fit_results.model_params[3]

return results
68 changes: 36 additions & 32 deletions src/standardized/IAR_LU_subtracted.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,12 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
bvec[:,2] = 1
gtab = gradient_table(self.bvalues, bvec, b0_threshold=0)

# Adapt the bounds to the format needed for the algorithm
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]

# Adapt the initial guess to the format needed for the algorithm
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]

self.IAR_algorithm = IvimModelSubtracted(gtab, bounds=bounds, initial_guess=initial_guess)
# Convert dict bounds/initial_guess to list-of-lists as expected by IvimModelSubtracted
bounds_list = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]],
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]
initial_guess_list = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]

self.IAR_algorithm = IvimModelSubtracted(gtab, bounds=bounds_list, initial_guess=initial_guess_list)
else:
self.IAR_algorithm = None

Expand All @@ -77,40 +75,46 @@ def ivim_fit(self, signals, bvalues, **kwargs):

Args:
signals (array-like)
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used.

Returns:
_type_: _description_
dict: Fitted IVIM parameters f, Dp (D*), and D.
"""
# Adapt the bounds to the format needed for the algorithm
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]

# Adapt the initial guess to the format needed for the algorithm
# --- bvalues resolution ---
if bvalues is None:
if self.bvalues is None:
raise ValueError(
"IAR_LU_subtracted: bvalues must be provided either at initialization or at fit time."
)
bvalues = self.bvalues
else:
bvalues = np.asarray(bvalues)

# Adapt bounds and initial guess dicts to list-of-lists as expected by IvimModelSubtracted
bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]],
[self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]]
initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]]

if self.IAR_algorithm is None:
if bvalues is None:
bvalues = self.bvalues
else:
bvalues = np.asarray(bvalues)


# Guard: reinitialise if not yet built, OR if bvalues have changed since last build
current_bvals = None if self.IAR_algorithm is None else self.IAR_algorithm.bvals
bvalues_changed = (current_bvals is not None) and not np.array_equal(current_bvals, bvalues)

if self.IAR_algorithm is None or bvalues_changed:
bvec = np.zeros((bvalues.size, 3))
bvec[:,2] = 1
gtab = gradient_table(bvalues, bvec, b0_threshold=0)

self.IAR_algorithm = IvimModelSubtracted(gtab, bounds=bounds, initial_guess=initial_guess)
fit_results = self.IAR_algorithm.fit(signals)

#f = fit_results.model_params[1]
#Dstar = fit_results.model_params[2]
#D = fit_results.model_params[3]

#return f, Dstar, D

try:
fit_results = self.IAR_algorithm.fit(signals)
except Exception as e:
print(f"IAR_LU_subtracted: fit failed ({type(e).__name__}: {e}). Returning default parameters.")
results = {"f": self.initial_guess["f"], "Dp": self.initial_guess["Dp"], "D": self.initial_guess["D"]}
return results

results = {}
results["f"] = fit_results.model_params[1]
results["Dp"] = fit_results.model_params[2]
results["D"] = fit_results.model_params[3]

return results
Loading