From f509b5e24231d501fef8f5f7603f23c4b1f4afb4 Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar Date: Fri, 14 Mar 2025 02:27:15 +0530 Subject: [PATCH 01/13] Ignore virtual environment folder --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e1046173..2b8bb268 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,6 @@ nosetests.xml coverage.xml *.pyc phantoms/MR_XCAT_qMRI/*.json -phantoms/MR_XCAT_qMRI/*.txt \ No newline at end of file +phantoms/MR_XCAT_qMRI/*.txt +osipivenv/ + From 542a272815f8392c2117f23794ed4923bae5226a Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Fri, 21 Mar 2025 03:07:36 +0530 Subject: [PATCH 02/13] implemented validation functions --- src/wrappers/OsipiBase.py | 44 ++++++++++++++------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index 715f1c4c..39fa675c 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -221,48 +221,36 @@ def osipi_accepts_dimension(self, dim): def osipi_check_required_bvalues(self): """Checks if the input bvalues fulfil the algorithm requirements""" - - #if self.bvalues.size < self.required_bvalues: - #print("Conformance error: Number of b-values.") - #return False - #else: - #return True + if not hasattr(self, "required_bvalues"): + raise AttributeError("required_bvalues not defined for this algorithm") + if self.bvalues is None: + raise ValueError("bvalues are not provided") + if len(self.bvalues) < self.required_bvalues: + raise ValueError(f"Atleast {self.required_bvalues} are required, but only {len(self.bvalues)} were provided") return True def osipi_check_required_thresholds(self): """Checks if the number of input thresholds fulfil the algorithm requirements""" - - #if (len(self.thresholds) < self.required_thresholds[0]) or (len(self.thresholds) > self.required_thresholds[1]): - #print("Conformance error: Number of thresholds.") - #return False - #else: - #return True + if not hasattr(self, "required_thresholds"): + raise AttributeError("required_thresholds is not defined for this algorithm") + if self.thresholds is None: + raise ValueError("thresholds are not provided") + if len(self.thresholds) < self.required_thresholds[0] and len(self.thresholds) > self.required_thresholds[1]: + raise ValueError(f"Thresholds should be between {self.required_thresholds[0]} and {self.required_thresholds[1]}") return True def osipi_check_required_bounds(self): """Checks if input bounds fulfil the algorithm requirements""" - #if self.required_bounds is True and self.bounds is None: - #print("Conformance error: Bounds.") - #return False - #else: - #return True + if self.required_bounds is False and self.bounds is None: + raise ValueError("bounds are required but not provided") return True def osipi_check_required_initial_guess(self): """Checks if input initial guess fulfil the algorithm requirements""" - - #if self.required_initial_guess is True and self.initial_guess is None: - #print("Conformance error: Initial guess") - #return False - #else: - #return True + if self.required_initial_guess is False and self.initial_guess is None: + raise ValueError("initial_guess are required but not provided") return True - - def osipi_check_required_bvalues(): - """Minimum number of b-values required""" - pass - def osipi_author(): """Author identification""" return '' From 38a2e726ee0d2c42910ab20eda8396d8c338ac77 Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Fri, 21 Mar 2025 03:18:55 +0530 Subject: [PATCH 03/13] added validation checks in fit() and fit_full_volume() --- src/wrappers/OsipiBase.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index 39fa675c..5037a21f 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -52,7 +52,11 @@ def osipi_fit(self, data, bvalues=None, **kwargs): """Fits the data with the bvalues Returns [S0, f, Dstar, D] """ - + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + # We should first check whether the attributes in the __init__ are not None # Then check if they are input here, if they are, these should overwrite the attributes use_bvalues = bvalues if bvalues is not None else self.bvalues @@ -122,6 +126,11 @@ def osipi_fit_full_volume(self, data, bvalues=None, **kwargs): Returns: results (dict): Dict with key each containing an array which is a parametric map. """ + + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() try: use_bvalues = bvalues if bvalues is not None else self.bvalues From 91ead9d2a5a281743e2e66449a20265ab298142f Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Fri, 21 Mar 2025 03:54:57 +0530 Subject: [PATCH 04/13] updated subclasses to implement validation check --- src/standardized wip/IAR_LU_linear.py | 9 +++++++++ src/standardized/ETP_SRI_LinearFitting.py | 11 ++++++++++- src/standardized/IAR_LU_biexp.py | 16 +++++++++++++++- src/standardized/IAR_LU_modified_mix.py | 9 +++++++++ src/standardized/IAR_LU_modified_topopro.py | 9 +++++++++ src/standardized/IAR_LU_segmented_2step.py | 9 +++++++++ src/standardized/IAR_LU_segmented_3step.py | 9 +++++++++ src/standardized/IAR_LU_subtracted.py | 9 +++++++++ .../OGC_AmsterdamUMC_Bayesian_biexp.py | 12 ++++++++++-- src/standardized/OGC_AmsterdamUMC_biexp.py | 11 +++++++++-- .../OGC_AmsterdamUMC_biexp_segmented.py | 9 +++++++++ src/standardized/OJ_GU_seg.py | 9 +++++++++ src/standardized/PV_MUMC_biexp.py | 9 +++++++++ src/standardized/PvH_KB_NKI_IVIMfit.py | 11 +++++++++-- src/wrappers/OsipiBase.py | 10 ++++------ 15 files changed, 138 insertions(+), 14 deletions(-) diff --git a/src/standardized wip/IAR_LU_linear.py b/src/standardized wip/IAR_LU_linear.py index 7ff310d0..bac78e31 100644 --- a/src/standardized wip/IAR_LU_linear.py +++ b/src/standardized wip/IAR_LU_linear.py @@ -39,6 +39,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_linear, self).__init__(bvalues, thresholds, bounds, initial_guess) # Check the inputs + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -61,6 +65,11 @@ def ivim_fit(self, signals, bvalues=None): Returns: _type_: _description_ """ + + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() if self.IAR_algorithm is None: if bvalues is None: diff --git a/src/standardized/ETP_SRI_LinearFitting.py b/src/standardized/ETP_SRI_LinearFitting.py index 41aa1665..eeaf2121 100644 --- a/src/standardized/ETP_SRI_LinearFitting.py +++ b/src/standardized/ETP_SRI_LinearFitting.py @@ -42,6 +42,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non the requirements. """ super(ETP_SRI_LinearFitting, self).__init__(bvalues=bvalues, thresholds=thresholds, bounds=bounds, initial_guess=initial_guess) + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() # Could be a good idea to have all the submission-specfic variable be # defined with initials? @@ -49,8 +53,8 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.ETP_stats = stats # Check the inputs - + def ivim_fit(self, signals, bvalues=None, linear_fit_option=False, **kwargs): """Perform the IVIM fit @@ -62,6 +66,11 @@ def ivim_fit(self, signals, bvalues=None, linear_fit_option=False, **kwargs): Returns: _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/IAR_LU_biexp.py b/src/standardized/IAR_LU_biexp.py index 3192b6ca..d4dab80d 100644 --- a/src/standardized/IAR_LU_biexp.py +++ b/src/standardized/IAR_LU_biexp.py @@ -44,6 +44,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_biexp, self).__init__(bvalues, thresholds, bounds, initial_guess) # Check the inputs + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -66,7 +70,12 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ - + + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues @@ -99,6 +108,11 @@ def ivim_fit_full_volume(self, signals, bvalues, **kwargs): _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/IAR_LU_modified_mix.py b/src/standardized/IAR_LU_modified_mix.py index eb667971..c72b800e 100644 --- a/src/standardized/IAR_LU_modified_mix.py +++ b/src/standardized/IAR_LU_modified_mix.py @@ -44,6 +44,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_modified_mix, self).__init__(bvalues, thresholds, bounds, initial_guess) # Check the inputs + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -67,6 +71,11 @@ def ivim_fit(self, signals, bvalues, **kwargs): _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/IAR_LU_modified_topopro.py b/src/standardized/IAR_LU_modified_topopro.py index 5b7cb7a8..15525907 100644 --- a/src/standardized/IAR_LU_modified_topopro.py +++ b/src/standardized/IAR_LU_modified_topopro.py @@ -44,6 +44,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_modified_topopro, self).__init__(bvalues, thresholds, bounds, initial_guess) # Check the inputs + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -67,6 +71,11 @@ def ivim_fit(self, signals, bvalues, **kwargs): _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/IAR_LU_segmented_2step.py b/src/standardized/IAR_LU_segmented_2step.py index c0849ede..5f4ae50e 100644 --- a/src/standardized/IAR_LU_segmented_2step.py +++ b/src/standardized/IAR_LU_segmented_2step.py @@ -44,6 +44,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_segmented_2step, self).__init__(bvalues, thresholds, bounds, initial_guess) # Check the inputs + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -67,6 +71,11 @@ def ivim_fit(self, signals, bvalues, thresholds=None, **kwargs): _type_: _description_ """ print(thresholds) + + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() if self.IAR_algorithm is None: if bvalues is None: diff --git a/src/standardized/IAR_LU_segmented_3step.py b/src/standardized/IAR_LU_segmented_3step.py index 390d86a5..2e4be7e3 100644 --- a/src/standardized/IAR_LU_segmented_3step.py +++ b/src/standardized/IAR_LU_segmented_3step.py @@ -44,6 +44,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_segmented_3step, self).__init__(bvalues, thresholds, bounds, initial_guess) # Check the inputs + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -67,6 +71,11 @@ def ivim_fit(self, signals, bvalues, **kwargs): _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/IAR_LU_subtracted.py b/src/standardized/IAR_LU_subtracted.py index 19dd3ec4..a114ae34 100644 --- a/src/standardized/IAR_LU_subtracted.py +++ b/src/standardized/IAR_LU_subtracted.py @@ -44,6 +44,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(IAR_LU_subtracted, self).__init__(bvalues, thresholds, bounds, initial_guess) # Check the inputs + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -67,6 +71,11 @@ def ivim_fit(self, signals, bvalues, **kwargs): _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py b/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py index 430bddbf..c8909261 100644 --- a/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py +++ b/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py @@ -19,8 +19,7 @@ class OGC_AmsterdamUMC_Bayesian_biexp(OsipiBase): # Algorithm requirements required_bvalues = 4 - required_thresholds = [0, - 0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds + required_thresholds = [0,0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds required_bounds = False required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False @@ -45,6 +44,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=([0, 0, 0.005, 0.7],[0. datain is a 2D array with values of D, f, D* (and S0) that will form the prior. """ super(OGC_AmsterdamUMC_Bayesian_biexp, self).__init__(bvalues=bvalues, bounds=bounds, thresholds=thresholds, initial_guess=initial_guess) #, fitS0, prior_in) + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() self.OGC_algorithm = fit_bayesian self.initialize(bounds, initial_guess, fitS0, prior_in) @@ -76,6 +79,11 @@ def ivim_fit(self, signals, bvalues, initial_guess=None, **kwargs): Returns: _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + if initial_guess is not None and len(initial_guess) == 4: self.initial_guess = initial_guess bvalues=np.array(bvalues) diff --git a/src/standardized/OGC_AmsterdamUMC_biexp.py b/src/standardized/OGC_AmsterdamUMC_biexp.py index ea59b885..220dada0 100644 --- a/src/standardized/OGC_AmsterdamUMC_biexp.py +++ b/src/standardized/OGC_AmsterdamUMC_biexp.py @@ -19,8 +19,7 @@ class OGC_AmsterdamUMC_biexp(OsipiBase): # Algorithm requirements required_bvalues = 4 - required_thresholds = [0, - 0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds + required_thresholds = [0,0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds required_bounds = False required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False @@ -42,6 +41,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=([0, 0, 0.005, 0.7],[0. """ #super(OGC_AmsterdamUMC_biexp, self).__init__(bvalues, bounds, initial_guess, fitS0) super(OGC_AmsterdamUMC_biexp, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess) + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() self.OGC_algorithm = fit_least_squares #self.initialize(bounds, initial_guess, fitS0) self.fitS0=fitS0 @@ -67,6 +70,10 @@ def ivim_fit(self, signals, bvalues, initial_guess=None, **kwargs): Returns: _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() if initial_guess is not None and len(initial_guess) == 4: self.initial_guess = initial_guess diff --git a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py index 43ab1818..f3f0f6e4 100644 --- a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py +++ b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py @@ -41,6 +41,10 @@ def __init__(self, bvalues=None, thresholds=150, bounds=([0, 0, 0.005],[0.005, 0 the requirements. """ super(OGC_AmsterdamUMC_biexp_segmented, self).__init__(bvalues, thresholds, bounds, initial_guess) + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() self.OGC_algorithm = fit_segmented self.initialize(bounds, initial_guess, thresholds) @@ -69,6 +73,11 @@ def ivim_fit(self, signals, bvalues, initial_guess=None, **kwargs): Returns: _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + if initial_guess is not None and len(initial_guess) == 4: self.initial_guess = initial_guess bvalues=np.array(bvalues) diff --git a/src/standardized/OJ_GU_seg.py b/src/standardized/OJ_GU_seg.py index bd55e63e..ada741fd 100644 --- a/src/standardized/OJ_GU_seg.py +++ b/src/standardized/OJ_GU_seg.py @@ -42,6 +42,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(OJ_GU_seg, self).__init__(bvalues, thresholds, bounds, initial_guess) # Check the inputs + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() # Initialize the algorithm @@ -56,6 +60,11 @@ def ivim_fit(self, signals, bvalues=None): Returns: _type_: _description_ """ + + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/PV_MUMC_biexp.py b/src/standardized/PV_MUMC_biexp.py index 0e10d5d3..01271f14 100644 --- a/src/standardized/PV_MUMC_biexp.py +++ b/src/standardized/PV_MUMC_biexp.py @@ -37,6 +37,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non the requirements. """ super(PV_MUMC_biexp, self).__init__(bvalues=bvalues, thresholds=thresholds, bounds=bounds, initial_guess=initial_guess) + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() self.PV_algorithm = fit_least_squares @@ -51,6 +55,11 @@ def ivim_fit(self, signals, bvalues=None): _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + if self.thresholds is None: self.thresholds = 200 diff --git a/src/standardized/PvH_KB_NKI_IVIMfit.py b/src/standardized/PvH_KB_NKI_IVIMfit.py index 66e201dd..016d6210 100644 --- a/src/standardized/PvH_KB_NKI_IVIMfit.py +++ b/src/standardized/PvH_KB_NKI_IVIMfit.py @@ -19,8 +19,7 @@ class PvH_KB_NKI_IVIMfit(OsipiBase): # Algorithm requirements required_bvalues = 4 - required_thresholds = [0, - 0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds + required_thresholds = [0,0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds required_bounds = False required_bounds_optional = False # Bounds may not be required but are optional required_initial_guess = False @@ -41,6 +40,10 @@ def __init__(self, bvalues=None, thresholds=None,bounds=None,initial_guess=None) the requirements. """ super(PvH_KB_NKI_IVIMfit, self).__init__(bvalues=bvalues, thresholds=thresholds,bounds=bounds,initial_guess=initial_guess) + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() self.NKI_algorithm = generate_IVIMmaps_standalone @@ -54,6 +57,10 @@ def ivim_fit(self, signals, bvalues=None): Returns: _type_: _description_ """ + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() #bvalues = np.array(bvalues) bvalues = bvalues.tolist() #NKI code expects a list instead of nparray # reshape signal as the NKI code expects a 4D array diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index 5037a21f..af1a5ddc 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -216,16 +216,14 @@ def osipi_accepted_dimensions(self): (True, True, False, False, False, False) """ - #return (False,) * 6 - return True + return getattr(self, 'accepted_dimensions', (1, 3)) def osipi_accepts_dimension(self, dim): """Query if the selection dimension is fittable""" - #accepted = self.accepted_dimensions() - #if dim < 0 or dim > len(accepted): - #return False - #return accepted[dim] + min_dim, max_dim = self.osipi_accepted_dimensions() + if not (min_dim <= dim <= max_dim): + raise ValueError(f"Dimension {dim}D not supported. Requires {min_dim}-{max_dim}D spatial data") return True def osipi_check_required_bvalues(self): From e550f807cb5b633e9285e7b2f5b38a6fa820678d Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Sat, 29 Mar 2025 17:07:37 +0530 Subject: [PATCH 05/13] centralized input validation --- src/standardized wip/IAR_LU_linear.py | 11 --------- src/standardized/ETP_SRI_LinearFitting.py | 10 -------- src/standardized/IAR_LU_biexp.py | 15 ------------ src/standardized/IAR_LU_modified_mix.py | 12 +--------- src/standardized/IAR_LU_modified_topopro.py | 10 -------- src/standardized/IAR_LU_segmented_2step.py | 9 ------- src/standardized/IAR_LU_segmented_3step.py | 10 -------- src/standardized/IAR_LU_subtracted.py | 9 ------- .../OGC_AmsterdamUMC_Bayesian_biexp.py | 9 +------ src/standardized/OGC_AmsterdamUMC_biexp.py | 9 +------ .../OGC_AmsterdamUMC_biexp_segmented.py | 16 ++++--------- src/standardized/OJ_GU_seg.py | 10 -------- src/standardized/PV_MUMC_biexp.py | 9 ------- src/standardized/PvH_KB_NKI_IVIMfit.py | 8 ------- src/wrappers/OsipiBase.py | 24 ++++++++++++------- 15 files changed, 22 insertions(+), 149 deletions(-) diff --git a/src/standardized wip/IAR_LU_linear.py b/src/standardized wip/IAR_LU_linear.py index bac78e31..fcbb200e 100644 --- a/src/standardized wip/IAR_LU_linear.py +++ b/src/standardized wip/IAR_LU_linear.py @@ -38,12 +38,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non """ super(IAR_LU_linear, self).__init__(bvalues, thresholds, bounds, initial_guess) - # Check the inputs - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() - # Initialize the algorithm if self.bvalues is not None: bvec = np.zeros((self.bvalues.size, 3)) @@ -65,11 +59,6 @@ def ivim_fit(self, signals, bvalues=None): Returns: _type_: _description_ """ - - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() if self.IAR_algorithm is None: if bvalues is None: diff --git a/src/standardized/ETP_SRI_LinearFitting.py b/src/standardized/ETP_SRI_LinearFitting.py index 4a5928a7..a617c502 100644 --- a/src/standardized/ETP_SRI_LinearFitting.py +++ b/src/standardized/ETP_SRI_LinearFitting.py @@ -52,12 +52,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non # defined with initials? self.ETP_weighting = weighting self.ETP_stats = stats - - # Check the inputs - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() def ivim_fit(self, signals, bvalues=None, linear_fit_option=False, **kwargs): @@ -71,10 +65,6 @@ def ivim_fit(self, signals, bvalues=None, linear_fit_option=False, **kwargs): Returns: _type_: _description_ """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() signals[signals<0.0000001]=0.0000001 if bvalues is None: diff --git a/src/standardized/IAR_LU_biexp.py b/src/standardized/IAR_LU_biexp.py index 1da7a249..b9d60e10 100644 --- a/src/standardized/IAR_LU_biexp.py +++ b/src/standardized/IAR_LU_biexp.py @@ -46,11 +46,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non print('warning, bounds from wrapper are not (yet) used in this algorithm') self.use_bounds = False self.use_initial_guess = False - # Check the inputs - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -74,11 +69,6 @@ def ivim_fit(self, signals, bvalues, **kwargs): _type_: _description_ """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() - if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues @@ -110,11 +100,6 @@ def ivim_fit_full_volume(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ - - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() if self.IAR_algorithm is None: if bvalues is None: diff --git a/src/standardized/IAR_LU_modified_mix.py b/src/standardized/IAR_LU_modified_mix.py index e0166e38..8cc4b661 100644 --- a/src/standardized/IAR_LU_modified_mix.py +++ b/src/standardized/IAR_LU_modified_mix.py @@ -46,11 +46,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non print('warning, bounds from wrapper are not (yet) used in this algorithm') self.use_bounds = False self.use_initial_guess = False - # Check the inputs - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -73,12 +68,7 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ - - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() - + if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/IAR_LU_modified_topopro.py b/src/standardized/IAR_LU_modified_topopro.py index 4f36958a..20e93cc3 100644 --- a/src/standardized/IAR_LU_modified_topopro.py +++ b/src/standardized/IAR_LU_modified_topopro.py @@ -46,11 +46,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non print('warning, bounds from wrapper are not (yet) used in this algorithm') self.use_bounds = False self.use_initial_guess = False - # Check the inputs - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -74,11 +69,6 @@ def ivim_fit(self, signals, bvalues, **kwargs): _type_: _description_ """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() - if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/IAR_LU_segmented_2step.py b/src/standardized/IAR_LU_segmented_2step.py index 85d3ced9..97a238b8 100644 --- a/src/standardized/IAR_LU_segmented_2step.py +++ b/src/standardized/IAR_LU_segmented_2step.py @@ -46,11 +46,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non print('warning, bounds from wrapper are not (yet) used in this algorithm') self.use_bounds = False self.use_initial_guess = False - # Check the inputs - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -75,10 +70,6 @@ def ivim_fit(self, signals, bvalues, thresholds=None, **kwargs): """ print(thresholds) - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() if self.IAR_algorithm is None: if bvalues is None: diff --git a/src/standardized/IAR_LU_segmented_3step.py b/src/standardized/IAR_LU_segmented_3step.py index 0fc33d78..8928dcef 100644 --- a/src/standardized/IAR_LU_segmented_3step.py +++ b/src/standardized/IAR_LU_segmented_3step.py @@ -46,11 +46,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non print('warning, bounds from wrapper are not (yet) used in this algorithm') self.use_bounds = False self.use_initial_guess = False - # Check the inputs - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -74,11 +69,6 @@ def ivim_fit(self, signals, bvalues, **kwargs): _type_: _description_ """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() - if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/IAR_LU_subtracted.py b/src/standardized/IAR_LU_subtracted.py index c910875d..ea22f42f 100644 --- a/src/standardized/IAR_LU_subtracted.py +++ b/src/standardized/IAR_LU_subtracted.py @@ -46,11 +46,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non print('warning, bounds from wrapper are not (yet) used in this algorithm') self.use_bounds = False self.use_initial_guess = False - # Check the inputs - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() # Initialize the algorithm if self.bvalues is not None: @@ -74,10 +69,6 @@ def ivim_fit(self, signals, bvalues, **kwargs): _type_: _description_ """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() if self.IAR_algorithm is None: if bvalues is None: diff --git a/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py b/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py index edddda1f..062148f3 100644 --- a/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py +++ b/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py @@ -46,10 +46,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non datain is a 2D array with values of D, f, D* (and S0) that will form the prior. """ super(OGC_AmsterdamUMC_Bayesian_biexp, self).__init__(bvalues=bvalues, bounds=bounds, thresholds=thresholds, initial_guess=initial_guess) #, fitS0, prior_in) - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() self.OGC_algorithm = fit_bayesian self.initialize(bounds, initial_guess, fitS0, prior_in) self.fit_segmented=fit_segmented @@ -89,10 +85,6 @@ def ivim_fit(self, signals, bvalues, initial_guess=None, **kwargs): Returns: _type_: _description_ """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() if initial_guess is not None and len(initial_guess) == 4: self.initial_guess = initial_guess @@ -110,5 +102,6 @@ def ivim_fit(self, signals, bvalues, initial_guess=None, **kwargs): results["D"] = fit_results[0] results["f"] = fit_results[1] results["Dp"] = fit_results[2] + results["S0"] = fit_results[3] return results \ No newline at end of file diff --git a/src/standardized/OGC_AmsterdamUMC_biexp.py b/src/standardized/OGC_AmsterdamUMC_biexp.py index f23508fc..bd603024 100644 --- a/src/standardized/OGC_AmsterdamUMC_biexp.py +++ b/src/standardized/OGC_AmsterdamUMC_biexp.py @@ -42,10 +42,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non """ #super(OGC_AmsterdamUMC_biexp, self).__init__(bvalues, bounds, initial_guess, fitS0) super(OGC_AmsterdamUMC_biexp, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess) - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() self.OGC_algorithm = fit_least_squares self.fitS0=fitS0 self.initialize(bounds, initial_guess, fitS0) @@ -76,10 +72,6 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() bvalues=np.array(bvalues) fit_results = self.OGC_algorithm(bvalues, signals, p0=self.initial_guess, bounds=self.bounds, fitS0=self.fitS0) @@ -88,5 +80,6 @@ def ivim_fit(self, signals, bvalues, **kwargs): results["D"] = fit_results[0] results["f"] = fit_results[1] results["Dp"] = fit_results[2] + results["S0"] = fit_results[3] return results \ No newline at end of file diff --git a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py index 171bbf4a..78e076f6 100644 --- a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py +++ b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py @@ -19,8 +19,7 @@ class OGC_AmsterdamUMC_biexp_segmented(OsipiBase): # Algorithm requirements required_bvalues = 4 - required_thresholds = [1, - 1] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds + required_thresholds = [1,1] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds required_bounds = False required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False @@ -42,10 +41,6 @@ def __init__(self, bvalues=None, thresholds=150, bounds=None, initial_guess=None the requirements. """ super(OGC_AmsterdamUMC_biexp_segmented, self).__init__(bvalues, thresholds, bounds, initial_guess) - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() self.OGC_algorithm = fit_segmented self.initialize(bounds, initial_guess, thresholds) @@ -78,13 +73,9 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() - if initial_guess is not None and len(initial_guess) == 4: - self.initial_guess = initial_guess + if self.initial_guess is not None and len(self.initial_guess) == 4: + self.initial_guess = self.initial_guess bvalues=np.array(bvalues) fit_results = self.OGC_algorithm(bvalues, signals, bounds=self.bounds, cutoff=self.thresholds, p0=self.initial_guess) @@ -92,5 +83,6 @@ def ivim_fit(self, signals, bvalues, **kwargs): results["D"] = fit_results[0] results["f"] = fit_results[1] results["Dp"] = fit_results[2] + results["S0"] = fit_results[3] return results \ No newline at end of file diff --git a/src/standardized/OJ_GU_seg.py b/src/standardized/OJ_GU_seg.py index c605bd50..3e574007 100644 --- a/src/standardized/OJ_GU_seg.py +++ b/src/standardized/OJ_GU_seg.py @@ -44,11 +44,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non print('warning, bounds from wrapper are not (yet) used in this algorithm') self.use_bounds = False self.use_initial_guess = False - # Check the inputs - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() # Initialize the algorithm @@ -63,11 +58,6 @@ def ivim_fit(self, signals, bvalues=None): Returns: _type_: _description_ """ - - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() if bvalues is None: bvalues = self.bvalues diff --git a/src/standardized/PV_MUMC_biexp.py b/src/standardized/PV_MUMC_biexp.py index 80907c04..821f1345 100644 --- a/src/standardized/PV_MUMC_biexp.py +++ b/src/standardized/PV_MUMC_biexp.py @@ -37,10 +37,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non the requirements. """ super(PV_MUMC_biexp, self).__init__(bvalues=bvalues, thresholds=thresholds, bounds=bounds, initial_guess=initial_guess) - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() self.PV_algorithm = fit_least_squares if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') @@ -59,11 +55,6 @@ def ivim_fit(self, signals, bvalues=None): _type_: _description_ """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() - if self.thresholds is None: self.thresholds = 200 diff --git a/src/standardized/PvH_KB_NKI_IVIMfit.py b/src/standardized/PvH_KB_NKI_IVIMfit.py index 2d866132..fb6ff226 100644 --- a/src/standardized/PvH_KB_NKI_IVIMfit.py +++ b/src/standardized/PvH_KB_NKI_IVIMfit.py @@ -40,10 +40,6 @@ def __init__(self, bvalues=None, thresholds=None,bounds=None,initial_guess=None) the requirements. """ super(PvH_KB_NKI_IVIMfit, self).__init__(bvalues=bvalues, thresholds=thresholds,bounds=bounds,initial_guess=initial_guess) - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() self.NKI_algorithm = generate_IVIMmaps_standalone if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') @@ -61,10 +57,6 @@ def ivim_fit(self, signals, bvalues=None): Returns: _type_: _description_ """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() #bvalues = np.array(bvalues) bvalues = bvalues.tolist() #NKI code expects a list instead of nparray # reshape signal as the NKI code expects a 4D array diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index f100db35..8c098124 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -16,6 +16,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.initial_guess = np.asarray(initial_guess) if initial_guess is not None else None self.use_bounds = True self.use_initial_guess = True + + # Validate the inputs + self.osipi_validate_inputs() + # If the user inputs an algorithm to OsipiBase, it is intereprete as initiating # an algorithm object with that name. if algorithm: @@ -53,10 +57,7 @@ def osipi_fit(self, data, bvalues=None, **kwargs): """Fits the data with the bvalues Returns [S0, f, Dstar, D] """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() + self.osipi_validate_inputs() # We should first check whether the attributes in the __init__ are not None # Then check if they are input here, if they are, these should overwrite the attributes @@ -128,10 +129,7 @@ def osipi_fit_full_volume(self, data, bvalues=None, **kwargs): results (dict): Dict with key each containing an array which is a parametric map. """ - self.osipi_check_required_bvalues() - self.osipi_check_required_thresholds() - self.osipi_check_required_bounds() - self.osipi_check_required_initial_guess() + self.osipi_validate_inputs() try: use_bvalues = bvalues if bvalues is not None else self.bvalues @@ -210,6 +208,14 @@ def osipi_print_requirements(self): else: print(f"Initial guess required: {self.required_initial_guess} and is not optional") + def osipi_validate_inputs(self): + """Validates the inputs of the algorithm.""" + self.osipi_check_required_bvalues() + self.osipi_check_required_thresholds() + self.osipi_check_required_bounds() + self.osipi_check_required_initial_guess() + self.osipi_accepts_dimension(self.data.ndim) + def osipi_accepted_dimensions(self): """The array of accepted dimensions e.g. @@ -244,7 +250,7 @@ def osipi_check_required_thresholds(self): if self.thresholds is None: raise ValueError("thresholds are not provided") if len(self.thresholds) < self.required_thresholds[0] and len(self.thresholds) > self.required_thresholds[1]: - raise ValueError(f"Thresholds should be between {self.required_thresholds[0]} and {self.required_thresholds[1]}") + raise ValueError(f"Thresholds should be between {self.required_thresholds[0]} and {self.required_thresholds[1]} but {len(self.thresholds)} were provided") return True def osipi_check_required_bounds(self): From 4739089a4c9c0bbbae5c5ec435b65c9d4552290c Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Sat, 29 Mar 2025 17:09:40 +0530 Subject: [PATCH 06/13] removed local changes --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2b8bb268..30c97a25 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,4 @@ coverage.xml *.pyc phantoms/MR_XCAT_qMRI/*.json phantoms/MR_XCAT_qMRI/*.txt -osipivenv/ From 953e8728832142f2e4f8c5019ce539d00475d7b8 Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:53:45 +0530 Subject: [PATCH 07/13] remove duplicated call --- src/standardized/ETP_SRI_LinearFitting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/standardized/ETP_SRI_LinearFitting.py b/src/standardized/ETP_SRI_LinearFitting.py index a617c502..b9cf8731 100644 --- a/src/standardized/ETP_SRI_LinearFitting.py +++ b/src/standardized/ETP_SRI_LinearFitting.py @@ -41,7 +41,6 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non Our OsipiBase object could contain functions that compare the inputs with the requirements. """ - super(ETP_SRI_LinearFitting, self).__init__(bvalues=bvalues, thresholds=thresholds, bounds=bounds, initial_guess=initial_guess) super(ETP_SRI_LinearFitting, self).__init__(bvalues, thresholds, bounds, initial_guess) if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') From d49d63f65716f4dd33aea3491d2dbc9045f07959 Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:47:35 +0530 Subject: [PATCH 08/13] lenient check in __init__ --- src/wrappers/OsipiBase.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index 8c098124..ec4a6401 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -18,7 +18,14 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.use_initial_guess = True # Validate the inputs - self.osipi_validate_inputs() + if self.bvalues is not None: + self.osipi_check_required_bvalues() + if self.thresholds is not None: + self.osipi_check_required_thresholds() + if self.bounds is not None: + self.osipi_check_required_bounds() + if self.initial_guess is not None: + self.osipi_check_required_initial_guess() # If the user inputs an algorithm to OsipiBase, it is intereprete as initiating # an algorithm object with that name. From 04058d15c2bf4a749f742ac568bb2376edfc1b23 Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:12:15 +0530 Subject: [PATCH 09/13] remove return statements --- src/wrappers/OsipiBase.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index ec4a6401..dced5a6d 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -217,11 +217,11 @@ def osipi_print_requirements(self): def osipi_validate_inputs(self): """Validates the inputs of the algorithm.""" + self.osipi_accepts_dimension(self.data.ndim) self.osipi_check_required_bvalues() self.osipi_check_required_thresholds() self.osipi_check_required_bounds() self.osipi_check_required_initial_guess() - self.osipi_accepts_dimension(self.data.ndim) def osipi_accepted_dimensions(self): """The array of accepted dimensions @@ -238,7 +238,6 @@ def osipi_accepts_dimension(self, dim): min_dim, max_dim = self.osipi_accepted_dimensions() if not (min_dim <= dim <= max_dim): raise ValueError(f"Dimension {dim}D not supported. Requires {min_dim}-{max_dim}D spatial data") - return True def osipi_check_required_bvalues(self): """Checks if the input bvalues fulfil the algorithm requirements""" @@ -248,7 +247,6 @@ def osipi_check_required_bvalues(self): raise ValueError("bvalues are not provided") if len(self.bvalues) < self.required_bvalues: raise ValueError(f"Atleast {self.required_bvalues} are required, but only {len(self.bvalues)} were provided") - return True def osipi_check_required_thresholds(self): """Checks if the number of input thresholds fulfil the algorithm requirements""" @@ -258,19 +256,16 @@ def osipi_check_required_thresholds(self): raise ValueError("thresholds are not provided") if len(self.thresholds) < self.required_thresholds[0] and len(self.thresholds) > self.required_thresholds[1]: raise ValueError(f"Thresholds should be between {self.required_thresholds[0]} and {self.required_thresholds[1]} but {len(self.thresholds)} were provided") - return True def osipi_check_required_bounds(self): """Checks if input bounds fulfil the algorithm requirements""" if self.required_bounds is False and self.bounds is None: raise ValueError("bounds are required but not provided") - return True def osipi_check_required_initial_guess(self): """Checks if input initial guess fulfil the algorithm requirements""" if self.required_initial_guess is False and self.initial_guess is None: raise ValueError("initial_guess are required but not provided") - return True def osipi_author(): """Author identification""" From a2d8a22d5d4b5a0a5d42ddf55773448d98b2771a Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Thu, 24 Apr 2025 09:45:51 +0530 Subject: [PATCH 10/13] store `data` before validation --- src/wrappers/OsipiBase.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index dced5a6d..e4e4102b 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -64,6 +64,7 @@ def osipi_fit(self, data, bvalues=None, **kwargs): """Fits the data with the bvalues Returns [S0, f, Dstar, D] """ + self.data = np.asarray(data) self.osipi_validate_inputs() # We should first check whether the attributes in the __init__ are not None From 1dd548e04ef05675a54b872f7605edec8b0ce9cf Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Thu, 24 Apr 2025 11:41:00 +0530 Subject: [PATCH 11/13] update fit_full_volume --- src/wrappers/OsipiBase.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index e4e4102b..0b146db1 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -137,6 +137,7 @@ def osipi_fit_full_volume(self, data, bvalues=None, **kwargs): results (dict): Dict with key each containing an array which is a parametric map. """ + self.data = np.asarray(data) self.osipi_validate_inputs() try: From e399f3144ffada75f58ae1be7931a9719cbcdb2e Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Wed, 30 Apr 2025 15:55:16 +0530 Subject: [PATCH 12/13] modify algos to use a tuple for accepted_dimensions --- src/standardized wip/IAR_LU_linear.py | 2 +- src/standardized/ETP_SRI_LinearFitting.py | 3 +-- src/standardized/IAR_LU_biexp.py | 2 +- src/standardized/IAR_LU_modified_mix.py | 2 +- src/standardized/IAR_LU_modified_topopro.py | 2 +- src/standardized/IAR_LU_segmented_2step.py | 2 +- src/standardized/IAR_LU_segmented_3step.py | 2 +- src/standardized/IAR_LU_subtracted.py | 2 +- src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py | 2 +- src/standardized/OGC_AmsterdamUMC_biexp.py | 2 +- src/standardized/OGC_AmsterdamUMC_biexp_segmented.py | 2 +- src/standardized/OJ_GU_seg.py | 2 +- src/standardized/PV_MUMC_biexp.py | 2 +- src/standardized/PvH_KB_NKI_IVIMfit.py | 2 +- 14 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/standardized wip/IAR_LU_linear.py b/src/standardized wip/IAR_LU_linear.py index fcbb200e..602ba50b 100644 --- a/src/standardized wip/IAR_LU_linear.py +++ b/src/standardized wip/IAR_LU_linear.py @@ -26,7 +26,7 @@ class IAR_LU_linear(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, weighting=None, stats=False): """ diff --git a/src/standardized/ETP_SRI_LinearFitting.py b/src/standardized/ETP_SRI_LinearFitting.py index b9cf8731..3777afbb 100644 --- a/src/standardized/ETP_SRI_LinearFitting.py +++ b/src/standardized/ETP_SRI_LinearFitting.py @@ -25,8 +25,7 @@ class ETP_SRI_LinearFitting(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = False - accepted_dimensions = 1 - # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class supported_bounds = False diff --git a/src/standardized/IAR_LU_biexp.py b/src/standardized/IAR_LU_biexp.py index b9d60e10..7e638465 100644 --- a/src/standardized/IAR_LU_biexp.py +++ b/src/standardized/IAR_LU_biexp.py @@ -26,7 +26,7 @@ class IAR_LU_biexp(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class supported_bounds = True diff --git a/src/standardized/IAR_LU_modified_mix.py b/src/standardized/IAR_LU_modified_mix.py index 8cc4b661..4712d7c8 100644 --- a/src/standardized/IAR_LU_modified_mix.py +++ b/src/standardized/IAR_LU_modified_mix.py @@ -26,7 +26,7 @@ class IAR_LU_modified_mix(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class supported_bounds = True diff --git a/src/standardized/IAR_LU_modified_topopro.py b/src/standardized/IAR_LU_modified_topopro.py index 20e93cc3..ffe39427 100644 --- a/src/standardized/IAR_LU_modified_topopro.py +++ b/src/standardized/IAR_LU_modified_topopro.py @@ -26,7 +26,7 @@ class IAR_LU_modified_topopro(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class supported_bounds = True diff --git a/src/standardized/IAR_LU_segmented_2step.py b/src/standardized/IAR_LU_segmented_2step.py index 97a238b8..224d609a 100644 --- a/src/standardized/IAR_LU_segmented_2step.py +++ b/src/standardized/IAR_LU_segmented_2step.py @@ -26,7 +26,7 @@ class IAR_LU_segmented_2step(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class supported_bounds = True diff --git a/src/standardized/IAR_LU_segmented_3step.py b/src/standardized/IAR_LU_segmented_3step.py index 8928dcef..85b44f68 100644 --- a/src/standardized/IAR_LU_segmented_3step.py +++ b/src/standardized/IAR_LU_segmented_3step.py @@ -26,7 +26,7 @@ class IAR_LU_segmented_3step(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class supported_bounds = True diff --git a/src/standardized/IAR_LU_subtracted.py b/src/standardized/IAR_LU_subtracted.py index ea22f42f..fb7c1995 100644 --- a/src/standardized/IAR_LU_subtracted.py +++ b/src/standardized/IAR_LU_subtracted.py @@ -26,7 +26,7 @@ class IAR_LU_subtracted(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class supported_bounds = True diff --git a/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py b/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py index 062148f3..8355d3ac 100644 --- a/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py +++ b/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py @@ -24,7 +24,7 @@ class OGC_AmsterdamUMC_Bayesian_biexp(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) accepts_priors = True diff --git a/src/standardized/OGC_AmsterdamUMC_biexp.py b/src/standardized/OGC_AmsterdamUMC_biexp.py index bd603024..c59a7f3e 100644 --- a/src/standardized/OGC_AmsterdamUMC_biexp.py +++ b/src/standardized/OGC_AmsterdamUMC_biexp.py @@ -24,7 +24,7 @@ class OGC_AmsterdamUMC_biexp(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class diff --git a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py index 78e076f6..a82ed290 100644 --- a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py +++ b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py @@ -24,7 +24,7 @@ class OGC_AmsterdamUMC_biexp_segmented(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class diff --git a/src/standardized/OJ_GU_seg.py b/src/standardized/OJ_GU_seg.py index 3e574007..6b295efa 100644 --- a/src/standardized/OJ_GU_seg.py +++ b/src/standardized/OJ_GU_seg.py @@ -24,7 +24,7 @@ class OJ_GU_seg(OsipiBase): required_bounds_optional = False # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = False - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class supported_bounds = False diff --git a/src/standardized/PV_MUMC_biexp.py b/src/standardized/PV_MUMC_biexp.py index 821f1345..c17e108a 100644 --- a/src/standardized/PV_MUMC_biexp.py +++ b/src/standardized/PV_MUMC_biexp.py @@ -21,7 +21,7 @@ class PV_MUMC_biexp(OsipiBase): required_bounds_optional = True # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional = True - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class supported_bounds = True diff --git a/src/standardized/PvH_KB_NKI_IVIMfit.py b/src/standardized/PvH_KB_NKI_IVIMfit.py index fb6ff226..5054dd9d 100644 --- a/src/standardized/PvH_KB_NKI_IVIMfit.py +++ b/src/standardized/PvH_KB_NKI_IVIMfit.py @@ -24,7 +24,7 @@ class PvH_KB_NKI_IVIMfit(OsipiBase): required_bounds_optional = False # Bounds may not be required but are optional required_initial_guess = False required_initial_guess_optional =False - accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + accepted_dimensions = (1,1) #(min dimension, max dimension) # Supported inputs in the standardized class supported_bounds = False From e20a8e277835c097e7aa59c9c5494b34913175db Mon Sep 17 00:00:00 2001 From: Harshitha Sudhakar <111514477+harshithasudhakar@users.noreply.github.com> Date: Thu, 1 May 2025 17:03:26 +0530 Subject: [PATCH 13/13] exclude bvalue axis --- src/wrappers/OsipiBase.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index 0b146db1..219723e0 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -235,11 +235,14 @@ def osipi_accepted_dimensions(self): return getattr(self, 'accepted_dimensions', (1, 3)) def osipi_accepts_dimension(self, dim): - """Query if the selection dimension is fittable""" - + """Check if the spatial dimensions (excluding b-values) are supported.""" + spatial_dim = dim - 1 # Exclude last axis (b-values) min_dim, max_dim = self.osipi_accepted_dimensions() - if not (min_dim <= dim <= max_dim): - raise ValueError(f"Dimension {dim}D not supported. Requires {min_dim}-{max_dim}D spatial data") + if not (min_dim <= spatial_dim <= max_dim): + raise ValueError( + f"Spatial dimensions {spatial_dim}D not supported. " + f"Requires {min_dim}-{max_dim}D." + ) def osipi_check_required_bvalues(self): """Checks if the input bvalues fulfil the algorithm requirements"""