Skip to content

Commit

Permalink
Continued work on a standard interface
Browse files Browse the repository at this point in the history
Added __inits__ to OsipiBase and the template class. Added methods to print algorithm requirements. Wrote some check for said requirements. Changed some variable and method names to make them easy to find.
  • Loading branch information
IvanARashid committed Aug 17, 2023
1 parent 10cb57a commit 92e6b57
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 40 deletions.
42 changes: 24 additions & 18 deletions src/standardized/ETP_SRI_LinearFitting.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,39 @@ class ETP_SRI_LinearFitting(OsipiBase):
# the user inputs fulfil the requirements

# Some basic stuff that identifies the algorithm
author = "Eric T. Peterson, SRI"
algorithm_type = "Linear fit"
return_parameters = "f, D*, D"
units = "seconds per milli metre squared"
id_author = "Eric T. Peterson, SRI"
id_algorithm_type = "Linear fit"
id_return_parameters = "f, D*, D"
id_units = "seconds per milli metre squared"

# Algorithm requirements
b_values_required = 3
thresholds_required = [1,1] # Interval from 1 to 1, in case submissions allow a custom number of thresholds
bounds_required = False
required_bvalues = 3
required_thresholds = [1,1] # Interval from 1 to 1, 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
required_initial_guess_optional = False
accepted_dimensions = 1

def __init__(self, thresholds, weighting=None, stats=False):
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, weighting=None, stats=False):
"""
Everything this method requires should be implemented here.
Number of segmentation thresholds, bounds, etc.
Our OsipiBase object could contain functions compare the inputs with
Our OsipiBase object could contain functions that compare the inputs with
the requirements.
"""
self.thresholds = thresholds
super(ETP_SRI_LinearFitting, self).__init__(bvalues, thresholds, bounds, initial_guess)

# Could be a good idea to have all the submission-specfic variable be
# defined with initials?
self.ETP_weighting = weighting
self.ETP_stats = stats

# Check the inputs
self.check_thresholds_required_osipi()


def ivim_fit(self, signals, b_values):
def ivim_fit(self, signals, bvalues):
"""
We may want this function to be as simple as
data and b-values in -> parameter estimates out.
Expand All @@ -51,8 +55,9 @@ def ivim_fit(self, signals, b_values):
This makes the execution of submissions easy and predictable.
All execution stepts requires should be performed here.
"""

ETP_object = LinearFit(self.thresholds[0])
f, D, Dstar = ETP_object.ivim_fit(b_values, signals)
f, D, Dstar = ETP_object.ivim_fit(bvalues, signals)



Expand All @@ -61,13 +66,14 @@ def ivim_fit(self, signals, b_values):


# Simple test code...
b_values = np.array([0, 200, 500, 800])
bvalues = np.array([0, 200, 500, 800])

def ivim_model(b, S0=1, f=0.1, Dstar=0.03, D=0.001):
return S0*(f*np.exp(-b*Dstar) + (1-f)*np.exp(-b*D))

signals = ivim_model(b_values)
signals = ivim_model(bvalues)

model = ETP_SRI_LinearFitting([200])
results = model.ivim_fit(signals, b_values)
test = model.simple_bias_and_RMSE_test(SNR=20, b_values=b_values, f=0.1, Dstar=0.03, D=0.001, noise_realizations=10)
model = ETP_SRI_LinearFitting(thresholds=[200])
results = model.ivim_fit(signals, bvalues)
test = model.osipi_simple_bias_and_RMSE_test(SNR=20, bvalues=bvalues, f=0.1, Dstar=0.03, D=0.001, noise_realizations=10)
#model.print_requirements_osipi()
116 changes: 94 additions & 22 deletions src/wrappers/OsipiBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,130 @@
class OsipiBase:
"""The base class for OSIPI IVIM fitting"""

#def __init__(self, author, data_dimension, thresholds_required, guess_required, bounds_required):
# pass
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None):
self.bvalues = np.asarray(bvalues)
self.thresholds = np.asarray(thresholds)
self.bounds = np.asarray(bounds)
self.initial_guess = np.asarray(initial_guess)

def fit_osipi(self, data=None, b_values=None, initial_guess=None, bounds=None, **kwargs):
def osipi_fit(self, data=None, bvalues=None, initial_guess=None, bounds=None, thresholds=None, **kwargs):
"""Fits the data with the bvalues
Returns [S0, f, D*, D]
"""
#self.parameter_estimates = self.ivim_fit(data, b_values)
#self.parameter_estimates = self.ivim_fit(data, bvalues)
pass

def osipi_print_requirements(self):
"""
Prints the requirements of the algorithm.
Attributes that are currently checked for are:
required_bvalues (int):
The lowest number of b-values required.
required_thresholds (array-like)
1D array-like of two ints [least number of b-value thresholds, most number of b-value_thresholds].
required_bounds (bool):
Whether bounds are required or not.
required_bounds_optional (bool):
Whether bounds are optional or not
required_initial_guess (bool):
Whether an initial guess is required or not.
required_initial_guess_optional (bool):
Whether an initial guess is optional or not.
"""
print("\n### Algorithm requirements ###")

# Check if the attributes exist and print them
if hasattr(self, "required_bvalues"):
print(f"Number of b-values: {self.required_bvalues}")

if hasattr(self, "required_thresholds"):
print(f"Numer of b-value thresholds [at least, at most]: {self.required_thresholds}")

if hasattr(self, "required_bounds") and hasattr(self, "required_bounds_optional"):
if self.required_bounds:
print(f"Bounds required: {self.required_bounds}")
else:
# Bounds may not be required but can be optional
if self.required_bounds_optional:
print(f"Bounds required: {self.required_bounds} but is optional")
else:
print(f"Bounds required: {self.required_bounds} and is not optional")

if hasattr(self, "required_initial_guess") and hasattr(self, "required_initial_guess_optional"):
if self.required_initial_guess:
print(f"Initial guess required: {self.required_initial_guess}")
else:
# Bounds may not be required but can be optional
if self.required_initial_guess_optional:
print(f"Initial guess required: {self.required_initial_guess} but is optional")
else:
print(f"Initial guess required: {self.required_initial_guess} and is not optional")

def accepted_dimensions_osipi(self):
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

def accepts_dimension_osipi(self, dim):
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]

def check_thresholds_required_osipi(self):
"""How many segmentation thresholds does it require?"""
if (len(self.thresholds) < self.thresholds_required[0]) or (len(self.thresholds) > self.thresholds_required[1]):
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

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

def check_guess_required_osipi():
"""Does it require an initial guess?"""
return False
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

def check_bounds_required_osipi():
"""Does it require bounds?"""
return False

def check_b_values_required_osipi():
def osipi_check_required_bvalues():
"""Minimum number of b-values required"""
pass

def author_osipi():
def osipi_author():
"""Author identification"""
return ''

def simple_bias_and_RMSE_test(self, SNR, b_values, f, Dstar, D, noise_realizations=100):
def osipi_simple_bias_and_RMSE_test(self, SNR, bvalues, f, Dstar, D, noise_realizations=100):
# Generate signal
b_values = np.asarray(b_values)
signals = f*np.exp(-b_values*Dstar) + (1-f)*np.exp(-b_values*D)
bvalues = np.asarray(bvalues)
signals = f*np.exp(-bvalues*Dstar) + (1-f)*np.exp(-bvalues*D)

f_estimates = np.zeros(noise_realizations)
Dstar_estimates = np.zeros(noise_realizations)
Expand All @@ -67,7 +139,7 @@ def simple_bias_and_RMSE_test(self, SNR, b_values, f, Dstar, D, noise_realizatio
noised_signal = np.array([norm.rvs(signal, sigma) for signal in signals])

# Perform fit with the noised signal
f_estimates[i], Dstar_estimates[i], D_estimates[i] = self.ivim_fit(noised_signal, b_values)
f_estimates[i], Dstar_estimates[i], D_estimates[i] = self.ivim_fit(noised_signal, bvalues)

# Calculate bias
f_bias = np.mean(f_estimates) - f
Expand Down
Binary file modified src/wrappers/__pycache__/OsipiBase.cpython-39.pyc
Binary file not shown.

0 comments on commit 92e6b57

Please sign in to comment.