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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/standardized wip/IAR_LU_linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand All @@ -38,8 +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

# Initialize the algorithm
if self.bvalues is not None:
bvec = np.zeros((self.bvalues.size, 3))
Expand Down
6 changes: 2 additions & 4 deletions src/standardized/ETP_SRI_LinearFitting.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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, thresholds, bounds, initial_guess)
if bounds is not None:
print('warning, bounds from wrapper are not (yet) used in this algorithm')
Expand All @@ -55,10 +54,8 @@ 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



def ivim_fit(self, signals, bvalues=None, linear_fit_option=False, **kwargs):
"""Perform the IVIM fit

Expand All @@ -70,6 +67,7 @@ def ivim_fit(self, signals, bvalues=None, linear_fit_option=False, **kwargs):
Returns:
_type_: _description_
"""

signals[signals<0.0000001]=0.0000001
if bvalues is None:
bvalues = self.bvalues
Expand Down
5 changes: 2 additions & 3 deletions src/standardized/IAR_LU_biexp.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,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

# Initialize the algorithm
if self.bvalues is not None:
Expand All @@ -70,7 +69,7 @@ def ivim_fit(self, signals, bvalues, **kwargs):
Returns:
_type_: _description_
"""

if self.IAR_algorithm is None:
if bvalues is None:
bvalues = self.bvalues
Expand Down Expand Up @@ -103,7 +102,7 @@ def ivim_fit_full_volume(self, signals, bvalues, **kwargs):
Returns:
_type_: _description_
"""

if self.IAR_algorithm is None:
if bvalues is None:
bvalues = self.bvalues
Expand Down
2 changes: 1 addition & 1 deletion src/standardized/IAR_LU_modified_mix.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def ivim_fit(self, signals, bvalues, **kwargs):
Returns:
_type_: _description_
"""

if self.IAR_algorithm is None:
if bvalues is None:
bvalues = self.bvalues
Expand Down
1 change: 0 additions & 1 deletion src/standardized/IAR_LU_modified_topopro.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,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

# Initialize the algorithm
if self.bvalues is not None:
Expand Down
2 changes: 1 addition & 1 deletion src/standardized/IAR_LU_segmented_2step.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,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

# Initialize the algorithm
if self.bvalues is not None:
Expand All @@ -71,6 +70,7 @@ def ivim_fit(self, signals, bvalues, thresholds=None, **kwargs):
_type_: _description_
"""
print(thresholds)


if self.IAR_algorithm is None:
if bvalues is None:
Expand Down
1 change: 0 additions & 1 deletion src/standardized/IAR_LU_segmented_3step.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,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

# Initialize the algorithm
if self.bvalues is not None:
Expand Down
2 changes: 1 addition & 1 deletion src/standardized/IAR_LU_subtracted.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,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

# Initialize the algorithm
if self.bvalues is not None:
Expand All @@ -71,6 +70,7 @@ def ivim_fit(self, signals, bvalues, **kwargs):
_type_: _description_
"""


if self.IAR_algorithm is None:
if bvalues is None:
bvalues = self.bvalues
Expand Down
4 changes: 4 additions & 0 deletions src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ def ivim_fit(self, signals, bvalues, initial_guess=None, **kwargs):
Returns:
_type_: _description_
"""

if initial_guess is not None and len(initial_guess) == 4:
self.initial_guess = initial_guess
bvalues=np.array(bvalues)

epsilon = 0.000001
Expand All @@ -110,6 +113,7 @@ 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

Expand Down
4 changes: 2 additions & 2 deletions src/standardized/OGC_AmsterdamUMC_biexp.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,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
Expand Down Expand Up @@ -84,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
8 changes: 5 additions & 3 deletions src/standardized/OGC_AmsterdamUMC_biexp_segmented.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -77,13 +76,16 @@ def ivim_fit(self, signals, bvalues, **kwargs):
Returns:
_type_: _description_
"""


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)

results = {}
results["D"] = fit_results[0]
results["f"] = fit_results[1]
results["Dp"] = fit_results[2]
results["S0"] = fit_results[3]

return results
1 change: 0 additions & 1 deletion src/standardized/OJ_GU_seg.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +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

# Initialize the algorithm

Expand Down
3 changes: 1 addition & 2 deletions src/standardized/PvH_KB_NKI_IVIMfit.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,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
Expand Down
86 changes: 44 additions & 42 deletions src/wrappers/OsipiBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non
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.required_bounds_optional = getattr(self, "required_bounds_optional", True)
self.required_initial_guess_optional = getattr(self, "required_initial_guess_optional", True)
self.use_bounds = True
self.use_initial_guess = True
self.deep_learning = False
Expand Down Expand Up @@ -180,7 +182,9 @@ def osipi_fit(self, data, bvalues=None, njobs=1, **kwargs):
>>> f_map = results['f']
>>> D_map = results['D']
"""

self.data = np.asarray(data)
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
use_bvalues = bvalues if bvalues is not None else self.bvalues
Expand Down Expand Up @@ -321,6 +325,9 @@ def osipi_fit_full_volume(self, data, bvalues=None, **kwargs):
if results is False:
print("Full-volume fitting not supported.")
"""

self.data = np.asarray(data)
self.osipi_validate_inputs()

try:
use_bvalues = bvalues if bvalues is not None else self.bvalues
Expand Down Expand Up @@ -400,68 +407,63 @@ 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_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()

def osipi_accepted_dimensions(self):
"""The array of accepted dimensions
e.g.
(1D, 2D, 3D, 4D, 5D, 6D)
(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]
return True
"""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 <= 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):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b-values is another story. Here we cannot give a "default"

"""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
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")

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
return True

if not hasattr(self, "required_thresholds"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering whether for some attributes, like "required_thresholds", we should add "default" values when tests fail. Like, 200 would be an okay value for most body applications. Then we can do with a warning stating that the default value is used, instead.

return
if self.thresholds is None:
raise ValueError("thresholds are required but not provided")
min_t, max_t = self.required_thresholds
num_thresholds = len(self.thresholds) if hasattr(self.thresholds, '__len__') else 1
if num_thresholds < min_t or num_thresholds > max_t:
raise ValueError(f"Number of thresholds should be between {min_t} and {max_t} but {num_thresholds} were provided")

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
return True
if self.required_bounds_optional is False and self.bounds is None:
raise ValueError("bounds are required but not provided")

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
return True


def osipi_check_required_bvalues(self):
"""Minimum number of b-values required"""
pass
if self.required_initial_guess_optional is False and self.initial_guess is None:
raise ValueError("initial guess are required but not provided")

def osipi_author(self):
"""Author identification"""
Expand Down
6 changes: 3 additions & 3 deletions tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import sys
sys.path.append(r"C:\Users\ivan5\Box\OSIPI\TF2.4_IVIM-MRI_CodeCollection")
sys.path.append(r"D:\OpenSource\osipi_ivim\TF2.4_IVIM-MRI_CodeCollection")


import os
import numpy as np
from pathlib import Path
#from src.standardized.ETP_SRI_LinearFitting import ETP_SRI_LinearFitting
from src.standardized.ETP_SRI_LinearFitting import ETP_SRI_LinearFitting
from src.standardized.IAR_LU_biexp import IAR_LU_biexp
from src.standardized.IAR_LU_modified_mix import IAR_LU_modified_mix
#from src.standardized.IAR_LU_segmented_2step import IAR_LU_segmented_2step
#from src.standardized.PvH_KB_NKI_IVIMfit import PvH_KB_NKI_IVIMfit
#from src.standardized.PV_MUMC_biexp import PV_MUMC_biexp
from src.standardized.OGC_AmsterdamUMC_biexp import OGC_AmsterdamUMC_biexp
from src.standardized.TF_reference_IVIMfit import TF_reference_IVIMfit
#from src.standardized.TF_reference_IVIMfit import TF_reference_IVIMfit

## Simple test code...
# Used to just do a test run of an algorithm during development
Expand Down