From b0e686e834b92668fe29b1bfa90b0636c5f9abe0 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Thu, 25 Sep 2025 13:18:21 +0200 Subject: [PATCH 01/16] Update simple_test_run_of_algorithm.py --- tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py b/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py index c2477f2..87ab0a9 100644 --- a/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py +++ b/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py @@ -16,7 +16,7 @@ ## Simple test code... # Used to just do a test run of an algorithm during development def dev_test_run(model, **kwargs): - bvalues = np.array([0, 50, 100, 150, 200, 500, 800]) + bvalues = np.array([0, 50, 0, 100, 0, 150, 200, 500, 0, 800]) def ivim_model(b, S0=1, f=0.1, Dstar=0.01, D=0.001): return S0*(f*np.exp(-b*Dstar) + (1-f)*np.exp(-b*D)) @@ -28,9 +28,9 @@ def ivim_model(b, S0=1, f=0.1, Dstar=0.01, D=0.001): #model = ETP_SRI_LinearFitting(thresholds=[200]) if kwargs: - results = model.osipi_fit_full_volume(signals, bvalues, **kwargs) + results = model.osipi_fit(signals, bvalues, **kwargs) else: - results = model.osipi_fit_full_volume(signals, bvalues) + results = model.osipi_fit(signals, bvalues) print(results) #test = model.osipi_simple_bias_and_RMSE_test(SNR=20, bvalues=bvalues, f=0.1, Dstar=0.03, D=0.001, noise_realizations=10) From 6d2bec521c581f6d6ca92eea3aa1b9024fbbe372 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Thu, 25 Sep 2025 14:04:11 +0200 Subject: [PATCH 02/16] Added default bounds and initial guess --- src/wrappers/OsipiBase.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index 5c4cd54..85f24c5 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -33,6 +33,12 @@ class OsipiBase: Name of an algorithm module in ``src/standardized`` to load dynamically. If supplied, the instance is immediately converted to that algorithm’s subclass via :meth:`osipi_initiate_algorithm`. + force_default_settings : bool, optional + If bounds and initial guesses are not provided, the wrapper will set + them to reasonable physical ones in the format [S0, f, Dp, D]. To prevent + this, set this bool to False. Default bounds are [0.7, 0, 0.005, 0] (lower) + and [1.3, 1.0, 0.2, 0.005] (upper). Default initial guess + [1, 0.1, 0.01, 0.001]. **kwargs Additional keyword arguments forwarded to the selected algorithm’s initializer if ``algorithm`` is provided. @@ -92,7 +98,7 @@ class OsipiBase: f_map = results["f"] """ - def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, algorithm=None, **kwargs): + def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, algorithm=None, force_default_settings=True, **kwargs): # Define the attributes as numpy arrays only if they are not None self.bvalues = np.asarray(bvalues) if bvalues is not None else None self.thresholds = np.asarray(thresholds) if thresholds is not None else None @@ -103,6 +109,18 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.deep_learning = False self.supervised = False self.stochastic = False + + if force_default_settings: + if self.bounds is None: + 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]') + self.bounds=([0.7, 0, 0.005, 0.0],[1.3, 1.0, 0.2, 0.005]) # These are defined in the order [S0, f, Dp, D] + self.forced_default_bounds = True + + if self.initial_guess is None: + print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') + self.initial_guess = [1, 0.1, 0.01, 0.001] # Defined in the order [S0, f, Dp, D] + self.forced_default_initial_guess = True + # If the user inputs an algorithm to OsipiBase, it is intereprete as initiating # an algorithm object with that name. if algorithm: From 29f19c0578764e573d0b1f0e5783c7a9b9804a02 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Wed, 15 Oct 2025 14:06:09 +0200 Subject: [PATCH 03/16] Added default bounds and initial guess as dictionaries in OsipiBase --- src/wrappers/OsipiBase.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index ee9cad1..2cf474d 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -113,18 +113,18 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non if force_default_settings: if self.bounds is None: 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]') - self.bounds=([0.7, 0, 0.005, 0.0],[1.3, 1.0, 0.2, 0.005]) # These are defined in the order [S0, f, Dp, D] + self.bounds = {"S0" : [0.7, 1.3], "f" : [0, 1.0], "Dp" : [0.005, 0.2], "D" : [0, 0.005]} # These are defined as [lower, upper] self.forced_default_bounds = True if self.initial_guess is None: print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') - self.initial_guess = [1, 0.1, 0.01, 0.001] # Defined in the order [S0, f, Dp, D] + self.initial_guess = {"S0" : 1, "f" : 0.1, "Dp" : 0.01, "D" : 0.001} self.forced_default_initial_guess = True # If the user inputs an algorithm to OsipiBase, it is intereprete as initiating # an algorithm object with that name. if algorithm: - self.osipi_initiate_algorithm(algorithm, bvalues=bvalues, thresholds=thresholds, bounds=bounds, initial_guess=initial_guess, **kwargs) + self.osipi_initiate_algorithm(algorithm, bvalues=self.bvalues, thresholds=self.thresholds, bounds=self.bounds, initial_guess=self.initial_guess, **kwargs) def osipi_initiate_algorithm(self, algorithm, **kwargs): """Turns the class into a specified one by the input. From 7dca19a0b64122eb93a80a7df0c4a1571771087e Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Wed, 15 Oct 2025 14:17:45 +0200 Subject: [PATCH 04/16] Dictionary implementation of bounds and initial guesses in testing code --- tests/IVIMmodels/unit_tests/test_ivim_fit.py | 39 ++++++++++---------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/IVIMmodels/unit_tests/test_ivim_fit.py b/tests/IVIMmodels/unit_tests/test_ivim_fit.py index 12bc213..4e439e7 100644 --- a/tests/IVIMmodels/unit_tests/test_ivim_fit.py +++ b/tests/IVIMmodels/unit_tests/test_ivim_fit.py @@ -50,7 +50,7 @@ def test_ivim_fit_saved(data_ivim_fit_saved, eng, request, record_property): tolerances = tolerances_helper(tolerances, data) fit = OsipiBase(algorithm=algorithm, **kwargs) if fit.use_bounds: - fit.bounds = ([0, 0, 0.005, 0.7], [0.005, 1.0, 0.2, 1.3]) + fit.bounds = {"S0" : [0.7, 1.3], "f" : [0, 1.0], "Dp" : [0.005, 0.2], "D" : [0, 0.005]} start_time = time.time() # Record the start time fit_result = fit.osipi_fit(signal, bvals) elapsed_time = time.time() - start_time # Calculate elapsed time @@ -95,20 +95,20 @@ def test_default_bounds_and_initial_guesses(algorithmlist,eng): #assert fit.bounds is not None, f"For {algorithm}, there is no default fit boundary" #assert fit.initial_guess is not None, f"For {algorithm}, there is no default fit initial guess" if fit.use_bounds: - assert 0 <= fit.bounds[0][0] <= 0.003, f"For {algorithm}, the default lower bound of D {fit.bounds[0][0]} is unrealistic" - assert 0 <= fit.bounds[1][0] <= 0.01, f"For {algorithm}, the default upper bound of D {fit.bounds[1][0]} is unrealistic" - assert 0 <= fit.bounds[0][1] <= 1, f"For {algorithm}, the default lower bound of f {fit.bounds[0][1]} is unrealistic" - assert 0 <= fit.bounds[1][1] <= 1, f"For {algorithm}, the default upper bound of f {fit.bounds[1][1]} is unrealistic" - assert 0.003 <= fit.bounds[0][2] <= 0.05, f"For {algorithm}, the default lower bound of Ds {fit.bounds[0][2]} is unrealistic" - assert 0.003 <= fit.bounds[1][2] <= 0.5, f"For {algorithm}, the default upper bound of Ds {fit.bounds[1][2]} is unrealistic" - 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" - 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" - 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]}" + assert 0 <= fit.bounds["D"][0] <= 0.003, f"For {algorithm}, the default lower bound of D {fit.bounds["D"][0]} is unrealistic" + assert 0 <= fit.bounds["D"][1] <= 0.01, f"For {algorithm}, the default upper bound of D {fit.bounds["D"][1]} is unrealistic" + assert 0 <= fit.bounds["f"][0] <= 1, f"For {algorithm}, the default lower bound of f {fit.bounds["f"][0]} is unrealistic" + assert 0 <= fit.bounds["f"][1] <= 1, f"For {algorithm}, the default upper bound of f {fit.bounds["f"][1]} is unrealistic" + assert 0.003 <= fit.bounds["Dp"][0] <= 0.05, f"For {algorithm}, the default lower bound of Dp {fit.bounds["Dp"][0]} is unrealistic" + assert 0.003 <= fit.bounds["Dp"][1] <= 0.5, f"For {algorithm}, the default upper bound of Dp {fit.bounds["Dp"][1]} is unrealistic" + assert 0 <= fit.bounds["S0"][0] <= 1, f"For {algorithm}, the default lower bound of S0 {fit.bounds["S0"][0]} is unrealistic; note data is normaized" + assert 1 <= fit.bounds["S0"][1] <= 1000, f"For {algorithm}, the default upper bound of S0 {fit.bounds["S0"][1]} is unrealistic; note data is normaized" + assert fit.bounds["D"][1] <= fit.bounds["Dp"][0], f"For {algorithm}, the default upper bound of D {fit.bounds["D"][1]} is higher than lower bound of Dp {fit.bounds["Dp"][0]}" if fit.use_initial_guess: - assert 0.0008 <= fit.initial_guess[0] <= 0.002, f"For {algorithm}, the default initial guess for D {fit.initial_guess[0]} is unrealistic" - assert 0 <= fit.initial_guess[1] <= 0.5, f"For {algorithm}, the default initial guess for f {fit.initial_guess[1]} is unrealistic" - assert 0.003 <= fit.initial_guess[2] <= 0.1, f"For {algorithm}, the default initial guess for Ds {fit.initial_guess[2]} is unrealistic" - assert 0.9 <= fit.initial_guess[3] <= 1.1, f"For {algorithm}, the default initial guess for S {fit.initial_guess[3]} is unrealistic; note signal is normalized" + assert 0.0008 <= fit.initial_guess["D"] <= 0.002, f"For {algorithm}, the default initial guess for D {fit.initial_guess["D"]} is unrealistic" + assert 0 <= fit.initial_guess["f"] <= 0.5, f"For {algorithm}, the default initial guess for f {fit.initial_guess["f"]} is unrealistic" + assert 0.003 <= fit.initial_guess["Dp"] <= 0.1, f"For {algorithm}, the default initial guess for Dp {fit.initial_guess["Dp"]} is unrealistic" + assert 0.9 <= fit.initial_guess["S0"] <= 1.1, f"For {algorithm}, the default initial guess for S0 {fit.initial_guess["S0"]} is unrealistic; note signal is normalized" def test_bounds(bound_input, eng): @@ -118,16 +118,17 @@ def test_bounds(bound_input, eng): pytest.skip(reason="Running without matlab; if Matlab is available please run pytest --withmatlab") else: kwargs = {**kwargs, 'eng': eng} - bounds = ([0.0008, 0.2, 0.01, 1.1], [0.0012, 0.3, 0.02, 1.3]) + #bounds = ([0.0008, 0.2, 0.01, 1.1], [0.0012, 0.3, 0.02, 1.3]) + bounds = {"S0" : [1.1, 1.3], "f" : [0.2, 0.3], "Dp" : [0.01, 0.02], "D" : [0.0008, 0.0012]} # deliberately have silly bounds to see whether they are used - fit = OsipiBase(algorithm=algorithm, bounds=bounds, initial_guess = [0.001, 0.25, 0.015, 1.2], **kwargs) + fit = OsipiBase(algorithm=algorithm, bounds=bounds, initial_guess={"S0" : 1.2, "f" : 0.25, "Dp" : 0.015, "D" : 0.001}, **kwargs) if fit.use_bounds: signal = signal_helper(data["data"]) fit_result = fit.osipi_fit(signal, bvals) - assert bounds[0][0] <= fit_result['D'] <= bounds[1][0], f"Result {fit_result['D']} out of bounds for data: {name}" - assert bounds[0][1] <= fit_result['f'] <= bounds[1][1], f"Result {fit_result['f']} out of bounds for data: {name}" - assert bounds[0][2] <= fit_result['Dp'] <= bounds[1][2], f"Result {fit_result['Dp']} out of bounds for data: {name}" + assert bounds["D"][0] <= fit_result['D'] <= bounds["D"][1], f"Result {fit_result['D']} out of bounds for data: {name}" + assert bounds["f"][0] <= fit_result['f'] <= bounds["f"][1], f"Result {fit_result['f']} out of bounds for data: {name}" + assert bounds["Dp"][0] <= fit_result['Dp'] <= bounds["Dp"][1], f"Result {fit_result['Dp']} out of bounds for data: {name}" # S0 is not returned as argument... #assert bounds[0][3] <= fit_result['S0'] <= bounds[1][3], f"Result {fit_result['S0']} out of bounds for data: {name}" '''if fit.use_initial_guess: From 22585e3d67728cab5949c68fffb8411e77b628b8 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Thu, 16 Oct 2025 09:59:53 +0200 Subject: [PATCH 05/16] Bug fix --- tests/IVIMmodels/unit_tests/test_ivim_fit.py | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/IVIMmodels/unit_tests/test_ivim_fit.py b/tests/IVIMmodels/unit_tests/test_ivim_fit.py index 4e439e7..67cbb07 100644 --- a/tests/IVIMmodels/unit_tests/test_ivim_fit.py +++ b/tests/IVIMmodels/unit_tests/test_ivim_fit.py @@ -95,20 +95,20 @@ def test_default_bounds_and_initial_guesses(algorithmlist,eng): #assert fit.bounds is not None, f"For {algorithm}, there is no default fit boundary" #assert fit.initial_guess is not None, f"For {algorithm}, there is no default fit initial guess" if fit.use_bounds: - assert 0 <= fit.bounds["D"][0] <= 0.003, f"For {algorithm}, the default lower bound of D {fit.bounds["D"][0]} is unrealistic" - assert 0 <= fit.bounds["D"][1] <= 0.01, f"For {algorithm}, the default upper bound of D {fit.bounds["D"][1]} is unrealistic" - assert 0 <= fit.bounds["f"][0] <= 1, f"For {algorithm}, the default lower bound of f {fit.bounds["f"][0]} is unrealistic" - assert 0 <= fit.bounds["f"][1] <= 1, f"For {algorithm}, the default upper bound of f {fit.bounds["f"][1]} is unrealistic" - assert 0.003 <= fit.bounds["Dp"][0] <= 0.05, f"For {algorithm}, the default lower bound of Dp {fit.bounds["Dp"][0]} is unrealistic" - assert 0.003 <= fit.bounds["Dp"][1] <= 0.5, f"For {algorithm}, the default upper bound of Dp {fit.bounds["Dp"][1]} is unrealistic" - assert 0 <= fit.bounds["S0"][0] <= 1, f"For {algorithm}, the default lower bound of S0 {fit.bounds["S0"][0]} is unrealistic; note data is normaized" - assert 1 <= fit.bounds["S0"][1] <= 1000, f"For {algorithm}, the default upper bound of S0 {fit.bounds["S0"][1]} is unrealistic; note data is normaized" - assert fit.bounds["D"][1] <= fit.bounds["Dp"][0], f"For {algorithm}, the default upper bound of D {fit.bounds["D"][1]} is higher than lower bound of Dp {fit.bounds["Dp"][0]}" + assert 0 <= fit.bounds["D"][0] <= 0.003, f"For {algorithm}, the default lower bound of D {fit.bounds['D'][0]} is unrealistic" + assert 0 <= fit.bounds["D"][1] <= 0.01, f"For {algorithm}, the default upper bound of D {fit.bounds['D'][1]} is unrealistic" + assert 0 <= fit.bounds["f"][0] <= 1, f"For {algorithm}, the default lower bound of f {fit.bounds['f'][0]} is unrealistic" + assert 0 <= fit.bounds["f"][1] <= 1, f"For {algorithm}, the default upper bound of f {fit.bounds['f'][1]} is unrealistic" + assert 0.003 <= fit.bounds["Dp"][0] <= 0.05, f"For {algorithm}, the default lower bound of Dp {fit.bounds['Dp'][0]} is unrealistic" + assert 0.003 <= fit.bounds["Dp"][1] <= 0.5, f"For {algorithm}, the default upper bound of Dp {fit.bounds['Dp'][1]} is unrealistic" + assert 0 <= fit.bounds["S0"][0] <= 1, f"For {algorithm}, the default lower bound of S0 {fit.bounds['S0'][0]} is unrealistic; note data is normaized" + assert 1 <= fit.bounds["S0"][1] <= 1000, f"For {algorithm}, the default upper bound of S0 {fit.bounds['S0'][1]} is unrealistic; note data is normaized" + assert fit.bounds["D"][1] <= fit.bounds["Dp"][0], f"For {algorithm}, the default upper bound of D {fit.bounds['D'][1]} is higher than lower bound of Dp {fit.bounds['Dp'][0]}" if fit.use_initial_guess: - assert 0.0008 <= fit.initial_guess["D"] <= 0.002, f"For {algorithm}, the default initial guess for D {fit.initial_guess["D"]} is unrealistic" - assert 0 <= fit.initial_guess["f"] <= 0.5, f"For {algorithm}, the default initial guess for f {fit.initial_guess["f"]} is unrealistic" - assert 0.003 <= fit.initial_guess["Dp"] <= 0.1, f"For {algorithm}, the default initial guess for Dp {fit.initial_guess["Dp"]} is unrealistic" - assert 0.9 <= fit.initial_guess["S0"] <= 1.1, f"For {algorithm}, the default initial guess for S0 {fit.initial_guess["S0"]} is unrealistic; note signal is normalized" + assert 0.0008 <= fit.initial_guess["D"] <= 0.002, f"For {algorithm}, the default initial guess for D {fit.initial_guess['D']} is unrealistic" + assert 0 <= fit.initial_guess["f"] <= 0.5, f"For {algorithm}, the default initial guess for f {fit.initial_guess['f']} is unrealistic" + assert 0.003 <= fit.initial_guess["Dp"] <= 0.1, f"For {algorithm}, the default initial guess for Dp {fit.initial_guess['Dp']} is unrealistic" + assert 0.9 <= fit.initial_guess["S0"] <= 1.1, f"For {algorithm}, the default initial guess for S0 {fit.initial_guess['S0']} is unrealistic; note signal is normalized" def test_bounds(bound_input, eng): From a3e36e648c64ba1e9310795ce93ab6974cc03806 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Fri, 17 Oct 2025 10:08:14 +0200 Subject: [PATCH 06/16] Some bounds handling stuff --- src/wrappers/OsipiBase.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index 2cf474d..0436a57 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -102,8 +102,8 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non # Define the attributes as numpy arrays only if they are not None self.bvalues = np.asarray(bvalues) if bvalues is not None else None self.thresholds = np.asarray(thresholds) if thresholds is not None else None - self.bounds = np.asarray(bounds) if bounds is not None else None - self.initial_guess = np.asarray(initial_guess) if initial_guess is not None else None + self.bounds = bounds if bounds is not None else None + self.initial_guess = initial_guess if initial_guess is not None else None self.use_bounds = True self.use_initial_guess = True self.deep_learning = False @@ -115,11 +115,16 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non 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]') self.bounds = {"S0" : [0.7, 1.3], "f" : [0, 1.0], "Dp" : [0.005, 0.2], "D" : [0, 0.005]} # These are defined as [lower, upper] self.forced_default_bounds = True + self.use_bounds = True if self.initial_guess is None: print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') self.initial_guess = {"S0" : 1, "f" : 0.1, "Dp" : 0.01, "D" : 0.001} self.forced_default_initial_guess = True + self.use_initial_guess = True + + self.osipi_bounds = self.bounds # self.bounds will change form, store it in the osipi format here + self.osipi_initial_guess = self.initial_guess # self.initial_guess will change form, store it in the osipi format here # If the user inputs an algorithm to OsipiBase, it is intereprete as initiating # an algorithm object with that name. From 57b938158313905629727ffcc3815ae4fb98f2c6 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Fri, 17 Oct 2025 10:09:49 +0200 Subject: [PATCH 07/16] Defined new variable, self.osipi_bounds which is used here. It is the bounds stored in the standardized OSIPI dictionary format. self.bounds will get manipulated by the individual algorithm subclasses --- tests/IVIMmodels/unit_tests/test_ivim_fit.py | 31 ++++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/IVIMmodels/unit_tests/test_ivim_fit.py b/tests/IVIMmodels/unit_tests/test_ivim_fit.py index 67cbb07..45b5bc0 100644 --- a/tests/IVIMmodels/unit_tests/test_ivim_fit.py +++ b/tests/IVIMmodels/unit_tests/test_ivim_fit.py @@ -48,9 +48,8 @@ def test_ivim_fit_saved(data_ivim_fit_saved, eng, request, record_property): request.node.add_marker(mark) signal = signal_helper(data["data"]) tolerances = tolerances_helper(tolerances, data) - fit = OsipiBase(algorithm=algorithm, **kwargs) - if fit.use_bounds: - fit.bounds = {"S0" : [0.7, 1.3], "f" : [0, 1.0], "Dp" : [0.005, 0.2], "D" : [0, 0.005]} + test_bounds = {"S0" : [0.7, 1.3], "f" : [0, 1.0], "Dp" : [0.005, 0.2], "D" : [0, 0.005]} + fit = OsipiBase(algorithm=algorithm, bounds=test_bounds, **kwargs) start_time = time.time() # Record the start time fit_result = fit.osipi_fit(signal, bvals) elapsed_time = time.time() - start_time # Calculate elapsed time @@ -95,20 +94,20 @@ def test_default_bounds_and_initial_guesses(algorithmlist,eng): #assert fit.bounds is not None, f"For {algorithm}, there is no default fit boundary" #assert fit.initial_guess is not None, f"For {algorithm}, there is no default fit initial guess" if fit.use_bounds: - assert 0 <= fit.bounds["D"][0] <= 0.003, f"For {algorithm}, the default lower bound of D {fit.bounds['D'][0]} is unrealistic" - assert 0 <= fit.bounds["D"][1] <= 0.01, f"For {algorithm}, the default upper bound of D {fit.bounds['D'][1]} is unrealistic" - assert 0 <= fit.bounds["f"][0] <= 1, f"For {algorithm}, the default lower bound of f {fit.bounds['f'][0]} is unrealistic" - assert 0 <= fit.bounds["f"][1] <= 1, f"For {algorithm}, the default upper bound of f {fit.bounds['f'][1]} is unrealistic" - assert 0.003 <= fit.bounds["Dp"][0] <= 0.05, f"For {algorithm}, the default lower bound of Dp {fit.bounds['Dp'][0]} is unrealistic" - assert 0.003 <= fit.bounds["Dp"][1] <= 0.5, f"For {algorithm}, the default upper bound of Dp {fit.bounds['Dp'][1]} is unrealistic" - assert 0 <= fit.bounds["S0"][0] <= 1, f"For {algorithm}, the default lower bound of S0 {fit.bounds['S0'][0]} is unrealistic; note data is normaized" - assert 1 <= fit.bounds["S0"][1] <= 1000, f"For {algorithm}, the default upper bound of S0 {fit.bounds['S0'][1]} is unrealistic; note data is normaized" - assert fit.bounds["D"][1] <= fit.bounds["Dp"][0], f"For {algorithm}, the default upper bound of D {fit.bounds['D'][1]} is higher than lower bound of Dp {fit.bounds['Dp'][0]}" + assert 0 <= fit.osipi_bounds["D"][0] <= 0.003, f"For {algorithm}, the default lower bound of D {fit.osipi_bounds['D'][0]} is unrealistic" + assert 0 <= fit.osipi_bounds["D"][1] <= 0.01, f"For {algorithm}, the default upper bound of D {fit.osipi_bounds['D'][1]} is unrealistic" + assert 0 <= fit.osipi_bounds["f"][0] <= 1, f"For {algorithm}, the default lower bound of f {fit.osipi_bounds['f'][0]} is unrealistic" + assert 0 <= fit.osipi_bounds["f"][1] <= 1, f"For {algorithm}, the default upper bound of f {fit.osipi_bounds['f'][1]} is unrealistic" + assert 0.003 <= fit.osipi_bounds["Dp"][0] <= 0.05, f"For {algorithm}, the default lower bound of Dp {fit.osipi_bounds['Dp'][0]} is unrealistic" + assert 0.003 <= fit.osipi_bounds["Dp"][1] <= 0.5, f"For {algorithm}, the default upper bound of Dp {fit.osipi_bounds['Dp'][1]} is unrealistic" + assert 0 <= fit.osipi_bounds["S0"][0] <= 1, f"For {algorithm}, the default lower bound of S0 {fit.osipi_bounds['S0'][0]} is unrealistic; note data is normaized" + assert 1 <= fit.osipi_bounds["S0"][1] <= 1000, f"For {algorithm}, the default upper bound of S0 {fit.osipi_bounds['S0'][1]} is unrealistic; note data is normaized" + assert fit.osipi_bounds["D"][1] <= fit.osipi_bounds["Dp"][0], f"For {algorithm}, the default upper bound of D {fit.osipi_bounds['D'][1]} is higher than lower bound of Dp {fit.osipi_bounds['Dp'][0]}" if fit.use_initial_guess: - assert 0.0008 <= fit.initial_guess["D"] <= 0.002, f"For {algorithm}, the default initial guess for D {fit.initial_guess['D']} is unrealistic" - assert 0 <= fit.initial_guess["f"] <= 0.5, f"For {algorithm}, the default initial guess for f {fit.initial_guess['f']} is unrealistic" - assert 0.003 <= fit.initial_guess["Dp"] <= 0.1, f"For {algorithm}, the default initial guess for Dp {fit.initial_guess['Dp']} is unrealistic" - assert 0.9 <= fit.initial_guess["S0"] <= 1.1, f"For {algorithm}, the default initial guess for S0 {fit.initial_guess['S0']} is unrealistic; note signal is normalized" + assert 0.0008 <= fit.osipi_initial_guess["D"] <= 0.002, f"For {algorithm}, the default initial guess for D {fit.osipi_initial_guess['D']} is unrealistic" + assert 0 <= fit.osipi_initial_guess["f"] <= 0.5, f"For {algorithm}, the default initial guess for f {fit.osipi_initial_guess['f']} is unrealistic" + assert 0.003 <= fit.osipi_initial_guess["Dp"] <= 0.1, f"For {algorithm}, the default initial guess for Dp {fit.osipi_initial_guess['Dp']} is unrealistic" + assert 0.9 <= fit.osipi_initial_guess["S0"] <= 1.1, f"For {algorithm}, the default initial guess for S0 {fit.osipi_initial_guess['S0']} is unrealistic; note signal is normalized" def test_bounds(bound_input, eng): From ebb6be7a670fd74adc0588d1811dd1c9bbd92b12 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Fri, 17 Oct 2025 10:10:28 +0200 Subject: [PATCH 08/16] Bugfixes to my own submissions --- .../IAR_LundUniversity/ivim_fit_method_modified_mix.py | 4 ++-- .../IAR_LundUniversity/ivim_fit_method_modified_topopro.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/original/IAR_LundUniversity/ivim_fit_method_modified_mix.py b/src/original/IAR_LundUniversity/ivim_fit_method_modified_mix.py index 1dde9a0..2a383c9 100644 --- a/src/original/IAR_LundUniversity/ivim_fit_method_modified_mix.py +++ b/src/original/IAR_LundUniversity/ivim_fit_method_modified_mix.py @@ -86,9 +86,9 @@ def __init__(self, gtab, bounds=None, maxiter=10, xtol=1e-8, rescale_units=False (bounds[0][1]*1000, bounds[1][1]*1000), \ (bounds[0][2]*1000, bounds[1][2]*1000)]) else: # Finally, if units if µm2/ms are already used - self.bounds = np.array([(bounds[0][0], bounds[1][0], \ + self.bounds = np.array([(bounds[0][0], bounds[1][0]), \ (bounds[0][1], bounds[1][1]), \ - (bounds[0][2], bounds[1][2]))]) + (bounds[0][2], bounds[1][2])]) @multi_voxel_fit def fit(self, data, bounds_de=None): diff --git a/src/original/IAR_LundUniversity/ivim_fit_method_modified_topopro.py b/src/original/IAR_LundUniversity/ivim_fit_method_modified_topopro.py index 7e0816d..460227e 100644 --- a/src/original/IAR_LundUniversity/ivim_fit_method_modified_topopro.py +++ b/src/original/IAR_LundUniversity/ivim_fit_method_modified_topopro.py @@ -84,9 +84,9 @@ def __init__(self, gtab, bounds=[[0, 0.005, 1e-5], [1, 0.1, 0.004]], \ (bounds[0][1]*1000, bounds[1][1]*1000), \ (bounds[0][2]*1000, bounds[1][2]*1000)]) else: # Finally, if units if µm2/ms are already used - self.bounds = np.array([(bounds[0][0], bounds[1][0], \ + self.bounds = np.array([(bounds[0][0], bounds[1][0]), \ (bounds[0][1], bounds[1][1]), \ - (bounds[0][2], bounds[1][2]))]) + (bounds[0][2], bounds[1][2])]) @multi_voxel_fit def fit(self, data): From 0aa925da1ba58d35f7bdb57d4029cec81afe8278 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Fri, 17 Oct 2025 10:11:23 +0200 Subject: [PATCH 09/16] Bounds adapted to IAR submissions (hopefully) --- src/standardized/IAR_LU_biexp.py | 13 +++++++++++-- src/standardized/IAR_LU_modified_mix.py | 13 +++++++++---- src/standardized/IAR_LU_modified_topopro.py | 16 ++++++++++++++-- src/standardized/IAR_LU_segmented_2step.py | 15 ++++++++++++--- src/standardized/IAR_LU_segmented_3step.py | 13 +++++++++++-- src/standardized/IAR_LU_subtracted.py | 13 +++++++++++-- 6 files changed, 68 insertions(+), 15 deletions(-) diff --git a/src/standardized/IAR_LU_biexp.py b/src/standardized/IAR_LU_biexp.py index ce882b3..7711fdb 100644 --- a/src/standardized/IAR_LU_biexp.py +++ b/src/standardized/IAR_LU_biexp.py @@ -45,10 +45,19 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_biexp, self).__init__(bvalues, thresholds, bounds, initial_guess) if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + if bounds is None: + self.use_bounds = False + if initial_guess is None: + self.use_initial_guess = False + # Check the inputs + # Adapt the standardized bounds to the format of the specific algorithm + self.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 standardized initial guess to the format of the specific algorithm + self.initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] + # Initialize the algorithm if self.bvalues is not None: bvec = np.zeros((self.bvalues.size, 3)) diff --git a/src/standardized/IAR_LU_modified_mix.py b/src/standardized/IAR_LU_modified_mix.py index 1100fde..3dc64d6 100644 --- a/src/standardized/IAR_LU_modified_mix.py +++ b/src/standardized/IAR_LU_modified_mix.py @@ -46,21 +46,26 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_modified_mix, self).__init__(bvalues, thresholds, bounds, initial_guess) if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + if bounds is None: + self.use_bounds = False + self.use_initial_guess = False # This algorithm does not use initial guesses # Additional options self.stochastic = True # Check the inputs - + # Adapt the standardized bounds to the format of the specific algorithm + self.bounds = [[self.bounds["f"][0], self.bounds["Dp"][0]*1000, self.bounds["D"][0]*1000], + [self.bounds["f"][1], self.bounds["Dp"][1]*1000, self.bounds["D"][1]*1000]] + #self.bounds = [[bounds["f"][0], bounds["f"][1]], [bounds["Dp"][0]*1000, bounds["Dp"][1]*1000], [bounds["D"][0]*1000, bounds["D"][1]*1000]] + # Initialize the algorithm if self.bvalues is not None: bvec = np.zeros((self.bvalues.size, 3)) bvec[:,2] = 1 gtab = gradient_table(self.bvalues, bvec, b0_threshold=0) - self.IAR_algorithm = IvimModelVP(gtab, bounds=self.bounds, rescale_results_to_mm2_s=True) + self.IAR_algorithm = IvimModelVP(gtab, bounds=self.bounds, rescale_units=False, rescale_results_to_mm2_s=True) else: self.IAR_algorithm = None diff --git a/src/standardized/IAR_LU_modified_topopro.py b/src/standardized/IAR_LU_modified_topopro.py index 09b675c..7f315cf 100644 --- a/src/standardized/IAR_LU_modified_topopro.py +++ b/src/standardized/IAR_LU_modified_topopro.py @@ -45,8 +45,20 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_modified_topopro, self).__init__(bvalues, thresholds, bounds, initial_guess) if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + if bounds is None: + self.use_bounds = False + self.use_initial_guess = False # This algorithm does not use initial guesses + + # Additional options + self.stochastic = True + + # Check the inputs + # Adapt the standardized bounds to the format of the specific algorithm + if self.bounds["Dp"][0] == self.bounds["D"][1]: + print('warning, bounds for D* and D are equal, this will likely cause fitting errors. Setting D_upper to 99 percent of D_upper') + self.bounds["D"][1] = self.bounds["D"][1]*0.99 + self.bounds = [[self.bounds["f"][0], self.bounds["Dp"][0]*1000, self.bounds["D"][0]*1000], + [self.bounds["f"][1], self.bounds["Dp"][1]*1000, self.bounds["D"][1]*1000]] # Check the inputs # Initialize the algorithm diff --git a/src/standardized/IAR_LU_segmented_2step.py b/src/standardized/IAR_LU_segmented_2step.py index c5be9b4..30be3a1 100644 --- a/src/standardized/IAR_LU_segmented_2step.py +++ b/src/standardized/IAR_LU_segmented_2step.py @@ -45,16 +45,25 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_segmented_2step, self).__init__(bvalues, thresholds, bounds, initial_guess) if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + if bounds is None: + self.use_bounds = False + if initial_guess is None: + self.use_initial_guess = False # Check the inputs + + # Adapt the bounds to the format needed for the algorithm + self.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 + self.initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] # Initialize the algorithm if self.bvalues is not None: bvec = np.zeros((self.bvalues.size, 3)) bvec[:,2] = 1 gtab = gradient_table(self.bvalues, bvec, b0_threshold=0) - + self.IAR_algorithm = IvimModelSegmented2Step(gtab, bounds=self.bounds, initial_guess=self.initial_guess, b_threshold=self.thresholds) else: self.IAR_algorithm = None diff --git a/src/standardized/IAR_LU_segmented_3step.py b/src/standardized/IAR_LU_segmented_3step.py index 73aeb42..84f4790 100644 --- a/src/standardized/IAR_LU_segmented_3step.py +++ b/src/standardized/IAR_LU_segmented_3step.py @@ -45,9 +45,18 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_segmented_3step, self).__init__(bvalues, thresholds, bounds, initial_guess) if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + if bounds is None: + self.use_bounds = False + if initial_guess is None: + self.use_initial_guess = False # Check the inputs + + # Adapt the bounds to the format needed for the algorithm + self.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 + self.initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] # Initialize the algorithm if self.bvalues is not None: diff --git a/src/standardized/IAR_LU_subtracted.py b/src/standardized/IAR_LU_subtracted.py index abd38d6..b4da98d 100644 --- a/src/standardized/IAR_LU_subtracted.py +++ b/src/standardized/IAR_LU_subtracted.py @@ -45,9 +45,18 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_subtracted, self).__init__(bvalues, thresholds, bounds, initial_guess) if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + if bounds is None: + self.use_bounds = False + if initial_guess is None: + self.use_initial_guess = False # Check the inputs + + # Adapt the bounds to the format needed for the algorithm + self.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 + self.initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] # Initialize the algorithm if self.bvalues is not None: From f6f6c8120ae5735368cf3993d577f10f874b8cf9 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Mon, 20 Oct 2025 09:29:34 +0200 Subject: [PATCH 10/16] Adapted bounds. But matlab testing is not working for me, function is not being found. --- .../ASD_MemorialSloanKettering_QAMPER_IVIM.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py b/src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py index d760982..6737e81 100644 --- a/src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py +++ b/src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py @@ -64,16 +64,17 @@ def algorithm(self,dwi_arr, bval_arr, LB0, UB0, x0in): return D_arr/1000, f_arr, Dx_arr/1000, s0_arr def initialize(self, bounds, initial_guess): - if bounds is None: + if self.bounds is None: print('warning, no bounds were defined, so algorithm-specific default bounds are used') self.bounds=([1e-6, 0, 0.004, 0],[0.003, 1.0, 0.2, 5]) else: - self.bounds=bounds - if initial_guess is None: + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) + if self.initial_guess is None: print('warning, no initial guesses were defined, so algorithm-specific default initial guess is used') self.initial_guess = [0.001, 0.2, 0.01, 1] else: - self.initial_guess = initial_guess + self.initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] self.use_initial_guess = True self.use_initial_guess = True self.use_bounds = True From 965289a816439104b9fad85201adc9306a22d9bc Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Mon, 20 Oct 2025 10:23:01 +0200 Subject: [PATCH 11/16] Dictionary bounds --- src/standardized/IVIM_NEToptim.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/standardized/IVIM_NEToptim.py b/src/standardized/IVIM_NEToptim.py index c4429d4..2595dbc 100644 --- a/src/standardized/IVIM_NEToptim.py +++ b/src/standardized/IVIM_NEToptim.py @@ -67,8 +67,9 @@ def initialize(self, bounds, initial_guess, fitS0, traindata, SNR): self.training_data(self.bvalues,n=1000000,SNR=SNR) self.arg=Arg() if bounds is not None: - self.arg.net_pars.cons_min = bounds[0] # Dt, Fp, Ds, S0 - self.arg.net_pars.cons_max = bounds[1] # Dt, Fp, Ds, S0 + self.arg.net_pars.cons_min = np.array([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]])#bounds[0] # Dt, Fp, Ds, S0 + self.arg.net_pars.cons_max = np.array([self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]])#bounds[1] # Dt, Fp, Ds, S0 + self.use_bounds = True if traindata is None: self.net = deep.learn_IVIM(self.train_data['data'], self.bvalues, self.arg) else: From fa70dcd6c6100e1d0ea403bec375a7ca91a04f7f Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Tue, 21 Oct 2025 19:03:20 +0200 Subject: [PATCH 12/16] Dictionary bounds and initial guess --- src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py | 5 +++-- src/standardized/OGC_AmsterdamUMC_biexp.py | 5 +++-- src/standardized/OGC_AmsterdamUMC_biexp_segmented.py | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py b/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py index 0a94dd2..1db05bd 100644 --- a/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py +++ b/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py @@ -63,12 +63,13 @@ def initialize(self, bounds=None, initial_guess=None, fitS0=True, prior_in=None, 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]') self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]) else: - self.bounds=bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) if initial_guess is None: print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') self.initial_guess = [0.001, 0.001, 0.01, 1] else: - self.initial_guess = initial_guess + self.initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] self.use_initial_guess = True self.use_bounds = True if thresholds is None: diff --git a/src/standardized/OGC_AmsterdamUMC_biexp.py b/src/standardized/OGC_AmsterdamUMC_biexp.py index 58f892d..d2c92fc 100644 --- a/src/standardized/OGC_AmsterdamUMC_biexp.py +++ b/src/standardized/OGC_AmsterdamUMC_biexp.py @@ -54,12 +54,13 @@ def initialize(self, bounds, initial_guess, fitS0): 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]') self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]) else: - self.bounds=bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) if initial_guess is None: print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') self.initial_guess = [0.001, 0.1, 0.01, 1] else: - self.initial_guess = initial_guess + self.initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] self.use_initial_guess = True self.fitS0=fitS0 self.use_initial_guess = True diff --git a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py index aebf822..5d92a14 100644 --- a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py +++ b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py @@ -53,12 +53,13 @@ def initialize(self, bounds, initial_guess, thresholds): 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]') self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]) else: - self.bounds=bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) if initial_guess is None: print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') self.initial_guess = [0.001, 0.001, 0.01, 1] else: - self.initial_guess = initial_guess + self.initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] self.use_initial_guess = True self.use_bounds = True if thresholds is None: From 558de67cc64121c7b71d1ebfcb42344cfb7b3118 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Tue, 21 Oct 2025 19:09:00 +0200 Subject: [PATCH 13/16] Dictionary bounds and initial guesses --- src/standardized/OJ_GU_bayesMATLAB.py | 5 +++-- src/standardized/OJ_GU_segMATLAB.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/standardized/OJ_GU_bayesMATLAB.py b/src/standardized/OJ_GU_bayesMATLAB.py index c9c008e..19da2d2 100644 --- a/src/standardized/OJ_GU_bayesMATLAB.py +++ b/src/standardized/OJ_GU_bayesMATLAB.py @@ -68,12 +68,13 @@ def initialize(self, bounds,initial_guess): 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]') self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]) else: - self.bounds=bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) if initial_guess is None: print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') self.initial_guess = [0.001, 0.1, 0.01, 1] else: - self.initial_guess = initial_guess + self.initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] self.use_initial_guess = True self.use_initial_guess = True self.use_bounds = True diff --git a/src/standardized/OJ_GU_segMATLAB.py b/src/standardized/OJ_GU_segMATLAB.py index c48a231..976a387 100644 --- a/src/standardized/OJ_GU_segMATLAB.py +++ b/src/standardized/OJ_GU_segMATLAB.py @@ -67,7 +67,8 @@ def initialize(self, bounds, thresholds): print('warning, no bounds were defined, so algorithm-specific default bounds are used') self.bounds=([1e-6, 0, 0.003, 0],[0.003, 1.0, 0.2, 5]) else: - self.bounds=bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) self.use_bounds = True if thresholds is None: self.thresholds = 200 From db3eb0919ebcb50ab649459fe8f24d764c778f36 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Tue, 21 Oct 2025 19:27:07 +0200 Subject: [PATCH 14/16] Dictionary bounds and initial guesses where applicable --- src/standardized/PV_MUMC_biexp.py | 6 +++++- src/standardized/TCML_TechnionIIT_SLS.py | 8 +++++--- src/standardized/TCML_TechnionIIT_lsqBOBYQA.py | 9 ++++++--- src/standardized/TCML_TechnionIIT_lsq_sls_BOBYQA.py | 8 +++++--- src/standardized/TCML_TechnionIIT_lsq_sls_lm.py | 8 +++++--- src/standardized/TCML_TechnionIIT_lsq_sls_trf.py | 8 +++++--- src/standardized/TCML_TechnionIIT_lsqlm.py | 3 ++- src/standardized/TCML_TechnionIIT_lsqtrf.py | 9 ++++++--- src/standardized/TF_reference_IVIMfit.py | 3 ++- 9 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/standardized/PV_MUMC_biexp.py b/src/standardized/PV_MUMC_biexp.py index 2872994..d192d71 100644 --- a/src/standardized/PV_MUMC_biexp.py +++ b/src/standardized/PV_MUMC_biexp.py @@ -41,7 +41,11 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.PV_algorithm = fit_least_squares if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False + + self.bounds = ([self.bounds["S0"][0], self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0]], + [self.bounds["S0"][1], self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1]]) + + self.use_bounds = True self.use_initial_guess = False diff --git a/src/standardized/TCML_TechnionIIT_SLS.py b/src/standardized/TCML_TechnionIIT_SLS.py index 7e9cc59..c99e1f4 100644 --- a/src/standardized/TCML_TechnionIIT_SLS.py +++ b/src/standardized/TCML_TechnionIIT_SLS.py @@ -51,10 +51,12 @@ def initialize(self, bounds, fitS0,thresholds): self.bounds = ([0.0003, 0.001, 0.009, 0],[0.008, 0.5,0.04, 3]) else: print('warning, although bounds are given, only D* is bounded)') - bounds=bounds - self.bounds = bounds + #bounds=bounds + #self.bounds = bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) self.fitS0=fitS0 - self.use_bounds = False + self.use_bounds = True if thresholds is None: self.thresholds = 150 print('warning, no thresholds were defined, so default bounds are used of 150') diff --git a/src/standardized/TCML_TechnionIIT_lsqBOBYQA.py b/src/standardized/TCML_TechnionIIT_lsqBOBYQA.py index 8cf370e..1fc5b10 100644 --- a/src/standardized/TCML_TechnionIIT_lsqBOBYQA.py +++ b/src/standardized/TCML_TechnionIIT_lsqBOBYQA.py @@ -51,13 +51,16 @@ def initialize(self, bounds, initial_guess, fitS0): 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]') self.bounds = ([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]) else: - bounds=bounds - self.bounds = bounds + #bounds=bounds + #self.bounds = bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) if initial_guess is None: print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') self.initial_guess = [0.001, 0.1, 0.02, 1] # D, Dp, f, S0 else: - self.initial_guess = initial_guess + #self.initial_guess = initial_guess + self.initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] self.use_initial_guess = True self.fitS0=fitS0 self.use_initial_guess = True diff --git a/src/standardized/TCML_TechnionIIT_lsq_sls_BOBYQA.py b/src/standardized/TCML_TechnionIIT_lsq_sls_BOBYQA.py index e444dd0..f9d2b6a 100644 --- a/src/standardized/TCML_TechnionIIT_lsq_sls_BOBYQA.py +++ b/src/standardized/TCML_TechnionIIT_lsq_sls_BOBYQA.py @@ -51,15 +51,17 @@ def initialize(self, bounds, fitS0, thresholds): print('warning, no bounds were defined, so default bounds are used of ([0.0003, 0.001, 0.009, 0],[0.008, 1.0,0.04, 3])') self.bounds = ([0.0003, 0.001, 0.009, 0],[0.008, 1.0 ,0.04, 3]) else: - bounds=bounds - self.bounds = bounds + #bounds=bounds + #self.bounds = bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) if thresholds is None: self.thresholds = 200 print('warning, no thresholds were defined, so default bounds are used of 200') else: self.thresholds = thresholds self.fitS0=fitS0 - self.use_bounds = False + self.use_bounds = True self.use_initial_guess = False def ivim_fit(self, signals, bvalues, **kwargs): diff --git a/src/standardized/TCML_TechnionIIT_lsq_sls_lm.py b/src/standardized/TCML_TechnionIIT_lsq_sls_lm.py index d6621c5..b48e0da 100644 --- a/src/standardized/TCML_TechnionIIT_lsq_sls_lm.py +++ b/src/standardized/TCML_TechnionIIT_lsq_sls_lm.py @@ -52,15 +52,17 @@ def initialize(self, bounds, fitS0,thresholds): 'warning, no bounds were defined, so default bounds are used of ([0.0003, 0.001, 0.009, 0],[0.008, 0.5,0.04, 3])') self.bounds = ([0.0003, 0.001, 0.009, 0], [0.008, 0.5, 0.04, 3]) else: - bounds = bounds - self.bounds = bounds + #bounds = bounds + #self.bounds = bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) if thresholds is None: self.thresholds = 150 print('warning, no thresholds were defined, so default bounds are used of 150') else: self.thresholds = thresholds self.fitS0=fitS0 - self.use_bounds = False + self.use_bounds = True self.use_initial_guess = False def ivim_fit(self, signals, bvalues, **kwargs): diff --git a/src/standardized/TCML_TechnionIIT_lsq_sls_trf.py b/src/standardized/TCML_TechnionIIT_lsq_sls_trf.py index 1d2fe6a..cb44e77 100644 --- a/src/standardized/TCML_TechnionIIT_lsq_sls_trf.py +++ b/src/standardized/TCML_TechnionIIT_lsq_sls_trf.py @@ -51,15 +51,17 @@ def initialize(self, bounds, fitS0, thresholds): print('warning, no bounds were defined, so default bounds are used of ([0.0003, 0.001, 0.009, 0],[0.008, 1.0,0.04, 3])') self.bounds = ([0.0003, 0.001, 0.009, 0],[0.008, 1.0,0.04, 3]) else: - bounds=bounds - self.bounds = bounds + #bounds=bounds + #self.bounds = bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) if thresholds is None: self.thresholds = 200 print('warning, no thresholds were defined, so default bounds are used of 200') else: self.thresholds = thresholds self.fitS0=fitS0 - self.use_bounds = False + self.use_bounds = True self.use_initial_guess = False def ivim_fit(self, signals, bvalues, **kwargs): diff --git a/src/standardized/TCML_TechnionIIT_lsqlm.py b/src/standardized/TCML_TechnionIIT_lsqlm.py index a7198eb..bd89c41 100644 --- a/src/standardized/TCML_TechnionIIT_lsqlm.py +++ b/src/standardized/TCML_TechnionIIT_lsqlm.py @@ -51,7 +51,8 @@ def initialize(self, bounds, initial_guess, fitS0): print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.1, 0.01, 1]') self.initial_guess = [0.001, 0.1, 0.01, 1] else: - self.initial_guess = initial_guess + #self.initial_guess = initial_guess + self.initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] self.use_initial_guess = True self.fitS0=fitS0 self.use_initial_guess = True diff --git a/src/standardized/TCML_TechnionIIT_lsqtrf.py b/src/standardized/TCML_TechnionIIT_lsqtrf.py index a91dded..3755999 100644 --- a/src/standardized/TCML_TechnionIIT_lsqtrf.py +++ b/src/standardized/TCML_TechnionIIT_lsqtrf.py @@ -51,13 +51,16 @@ def initialize(self, bounds, initial_guess, fitS0): print('warning, no bounds were defined, so default bounds are used of ([0.0003, 0.001, 0.009, 0],[0.008, 0.5,0.04, 3])') self.bounds = ([0.0003, 0.001, 0.009, 0],[0.008, 0.5,0.04, 3]) else: - bounds=bounds - self.bounds = bounds + #bounds=bounds + #self.bounds = bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) if initial_guess is None: print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.1, 0.01, 1]') self.initial_guess = [0.001, 0.1, 0.01, 1] # D, Dp, f, S0 else: - self.initial_guess = initial_guess + #self.initial_guess = initial_guess + self.initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] self.use_initial_guess = True self.fitS0=fitS0 self.use_initial_guess = True diff --git a/src/standardized/TF_reference_IVIMfit.py b/src/standardized/TF_reference_IVIMfit.py index 6bf355f..e39ee8e 100644 --- a/src/standardized/TF_reference_IVIMfit.py +++ b/src/standardized/TF_reference_IVIMfit.py @@ -51,7 +51,8 @@ def initialize(self, bounds, thresholds): print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005],[0.005, 1.0, 0.2]') self.bounds=([0, 0, 0.005, 0.8],[0.005, 1.0, 0.2, 1.2]) else: - self.bounds=bounds + self.bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) self.use_bounds = True if thresholds is None: self.thresholds = 200 From f6db6b69274a11f8719f9b5cda61a379983d11f7 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Tue, 21 Oct 2025 19:34:41 +0200 Subject: [PATCH 15/16] Adjusted documentation for dictionary bounds and initial guesses --- src/wrappers/OsipiBase.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index 0436a57..d701ebe 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -25,20 +25,24 @@ class OsipiBase: Diffusion b-values (s/mm²) matching the last dimension of the input data. thresholds : array-like, optional Thresholds used by specific algorithms (e.g., signal cutoffs). - bounds : array-like, optional - Parameter bounds for constrained optimization. - initial_guess : array-like, optional - Initial parameter estimates for the IVIM fit. + bounds : dict, optional + Parameter bounds for constrained optimization. Should be a dict with keys + like "S0", "f", "Dp", "D" and values as [lower, upper] lists or arrays. + E.g. {"S0" : [0.7, 1.3], "f" : [0, 1], "Dp" : [0.005, 0.2], "D" : [0, 0.005]}. + initial_guess : dict, optional + Initial parameter estimates for the IVIM fit. Should be a dict with keys + like "S0", "f", "Dp", "D" and float values. + E.g. {"S0" : 1, "f" : 0.1, "Dp" : 0.01, "D" : 0.001}. algorithm : str, optional Name of an algorithm module in ``src/standardized`` to load dynamically. If supplied, the instance is immediately converted to that algorithm’s subclass via :meth:`osipi_initiate_algorithm`. force_default_settings : bool, optional If bounds and initial guesses are not provided, the wrapper will set - them to reasonable physical ones in the format [S0, f, Dp, D]. To prevent - this, set this bool to False. Default bounds are [0.7, 0, 0.005, 0] (lower) - and [1.3, 1.0, 0.2, 0.005] (upper). Default initial guess - [1, 0.1, 0.01, 0.001]. + them to reasonable physical ones, i.e. {"S0":[0.7, 1.3], "f":[0, 1], + "Dp":[0.005, 0.2], "D":[0, 0.005]}. + To prevent this, set this bool to False. Default initial guess + {"S0" : 1, "f": 0.1, "Dp": 0.01, "D": 0.001}. **kwargs Additional keyword arguments forwarded to the selected algorithm’s initializer if ``algorithm`` is provided. From d7c67dd98a623d176970e8852adc4e804df54cd5 Mon Sep 17 00:00:00 2001 From: IvanARashid Date: Tue, 21 Oct 2025 22:57:37 +0200 Subject: [PATCH 16/16] Update simple_test_run_of_algorithm.py --- tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py b/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py index c2ea2c4..6df9cbb 100644 --- a/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py +++ b/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py @@ -36,7 +36,7 @@ def ivim_model(b, S0=1, f=0.1, Dstar=0.01, D=0.001): #test = model.osipi_simple_bias_and_RMSE_test(SNR=20, bvalues=bvalues, f=0.1, Dstar=0.03, D=0.001, noise_realizations=10) #model1 = ETP_SRI_LinearFitting(thresholds=[200]) -model2 = IAR_LU_biexp(bounds=([0,0,0,0], [1,1,1,1])) +model2 = IAR_LU_biexp() #model2 = IAR_LU_modified_mix() #model2 = OGC_AmsterdamUMC_biexp()