Skip to content

Wrapper super ivim #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
numpy<2
super-ivim-dc==0.4.0
nibabel
scipy
torchio
Expand Down
2 changes: 1 addition & 1 deletion src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class ASD_MemorialSloanKettering_QAMPER_IVIM(OsipiBase):
"""
Bi-exponential fitting algorithm by Oliver Gurney-Champion, Amsterdam UMC
Bi-exponential fitting algorithm by Eve LoCastro and Amita Shukla-Dave, Memorial Sloan Kettering
"""

# I'm thinking that we define default attributes for each submission like this
Expand Down
95 changes: 95 additions & 0 deletions src/standardized/TCML_TechnionIIT_SLS.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from src.wrappers.OsipiBase import OsipiBase
from super_ivim_dc.source.Classsic_ivim_fit import IVIM_fit_sls
import numpy as np

class TCML_TechnionIIT_SLS(OsipiBase):
"""
TCML_TechnionIIT_lsqBOBYQA fitting algorithm by Moti Freiman and Noam Korngut, TechnionIIT
"""

# I'm thinking that we define default attributes for each submission like this
# And in __init__, we can call the OsipiBase control functions to check whether
# the user inputs fulfil the requirements

# Some basic stuff that identifies the algorithm
id_author = "Moti Freiman and Noam Korngut, TechnIIT"
id_algorithm_type = "Bi-exponential fit, segmented fitting"
id_return_parameters = "f, D*, D, S0"
id_units = "seconds per milli metre squared or milliseconds per micro metre squared"

# 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_bounds = False
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?


# Supported inputs in the standardized class
supported_bounds = True
supported_initial_guess = True
supported_thresholds = True

def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True):
"""
Everything this algorithm requires should be implemented here.
Number of segmentation thresholds, bounds, etc.

Our OsipiBase object could contain functions that compare the inputs with
the requirements.
"""
super(TCML_TechnionIIT_SLS, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess)
self.fit_least_squares = IVIM_fit_sls
self.fitS0=fitS0
self.initialize(bounds, initial_guess, fitS0,thresholds)

def initialize(self, bounds, initial_guess, fitS0,thresholds):
if bounds is None:
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
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.use_initial_guess = True
self.fitS0=fitS0
self.use_initial_guess = True
self.use_bounds = True
if thresholds is None:
self.thresholds = 150
print('warning, no thresholds were defined, so default bounds are used of 150')
else:
self.thresholds = thresholds

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

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

Returns:
_type_: _description_
"""

bvalues=np.array(bvalues)
bounds=np.array(self.bounds)
bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]]
initial_guess = np.array(self.initial_guess)
initial_guess = initial_guess[[0, 2, 1, 3]]


fit_results = self.fit_least_squares(1 ,np.array(signals)[:,np.newaxis],bvalues, bounds,initial_guess,self.thresholds)

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

return results
90 changes: 90 additions & 0 deletions src/standardized/TCML_TechnionIIT_lsqBOBYQA.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from src.wrappers.OsipiBase import OsipiBase
from super_ivim_dc.source.Classsic_ivim_fit import fit_least_squers_BOBYQA
import numpy as np

class TCML_TechnionIIT_lsqBOBYQA(OsipiBase):
"""
TCML_TechnionIIT_lsqBOBYQA fitting algorithm by Moti Freiman and Noam Korngut, TechnionIIT
"""

# I'm thinking that we define default attributes for each submission like this
# And in __init__, we can call the OsipiBase control functions to check whether
# the user inputs fulfil the requirements

# Some basic stuff that identifies the algorithm
id_author = "Moti Freiman and Noam Korngut, TechnIIT"
id_algorithm_type = "Bi-exponential fit, BOBYQO"
id_return_parameters = "f, D*, D, S0"
id_units = "seconds per milli metre squared or milliseconds per micro metre squared"

# 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_bounds = False
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?


# Supported inputs in the standardized class
supported_bounds = True
supported_initial_guess = True
supported_thresholds = False

def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True):
"""
Everything this algorithm requires should be implemented here.
Number of segmentation thresholds, bounds, etc.

Our OsipiBase object could contain functions that compare the inputs with
the requirements.
"""
super(TCML_TechnionIIT_lsqBOBYQA, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess)
self.fit_least_squares = fit_least_squers_BOBYQA
self.fitS0=fitS0
self.initialize(bounds, initial_guess, fitS0)

def initialize(self, bounds, initial_guess, fitS0):
if 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.0003, 0.001, 0.009, 0],[0.01, 0.5,0.04, 3])
else:
bounds=bounds
self.bounds = bounds
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] # D, Dp, f, S0
else:
self.initial_guess = initial_guess
self.use_initial_guess = True
self.fitS0=fitS0
self.use_initial_guess = True
self.use_bounds = True

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

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

Returns:
_type_: _description_
"""

bvalues=np.array(bvalues)
bounds=np.array(self.bounds)
bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]]
initial_guess = np.array(self.initial_guess)
initial_guess = initial_guess[[0, 2, 1, 3]]

fit_results = self.fit_least_squares(1 ,bvalues, np.array(signals)[:,np.newaxis], bounds, initial_guess)

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

return results
81 changes: 81 additions & 0 deletions src/standardized/TCML_TechnionIIT_lsqlm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from src.wrappers.OsipiBase import OsipiBase
from super_ivim_dc.source.Classsic_ivim_fit import fit_least_squares_lm
import numpy as np

class TCML_TechnionIIT_lsqlm(OsipiBase):
"""
TCML_TechnionIIT_lsqlm fitting algorithm by Moti Freiman and Noam Korngut, TechnionIIT
"""

# I'm thinking that we define default attributes for each submission like this
# And in __init__, we can call the OsipiBase control functions to check whether
# the user inputs fulfil the requirements

# Some basic stuff that identifies the algorithm
id_author = "Moti Freiman and Noam Korngut, TechnIIT"
id_algorithm_type = "Bi-exponential fit"
id_return_parameters = "f, D*, D, S0"
id_units = "seconds per milli metre squared or milliseconds per micro metre squared"

# 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_bounds = False
required_bounds_optional = False # 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?


# Supported inputs in the standardized class
supported_bounds = False
supported_initial_guess = True
supported_thresholds = False

def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True):
"""
Everything this algorithm requires should be implemented here.
Number of segmentation thresholds, bounds, etc.

Our OsipiBase object could contain functions that compare the inputs with
the requirements.
"""
super(TCML_TechnionIIT_lsqlm, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess)
self.fit_least_squares = fit_least_squares_lm
self.fitS0=fitS0
self.initialize(bounds, initial_guess, fitS0)

def initialize(self, bounds, initial_guess, fitS0):
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]
else:
self.initial_guess = initial_guess
self.use_initial_guess = True
self.fitS0=fitS0
self.use_initial_guess = True
self.use_bounds = False

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

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

Returns:
_type_: _description_
"""

bvalues=np.array(bvalues)
initial_guess = np.array(self.initial_guess)
initial_guess = initial_guess[[0, 2, 1, 3]]
fit_results = self.fit_least_squares(1 ,bvalues, np.array(signals)[:,np.newaxis], initial_guess)

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

return self.D_and_Ds_swap(results)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason to just do it here and not generally in the wrapper?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good question. I think for bounded fits it is less of an issue. But it will not do any harm. But I will move it to the general wrapper indeed.

89 changes: 89 additions & 0 deletions src/standardized/TCML_TechnionIIT_lsqtrf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from src.wrappers.OsipiBase import OsipiBase
from super_ivim_dc.source.Classsic_ivim_fit import fit_least_squares_trf
import numpy as np

class TCML_TechnionIIT_lsqtrf(OsipiBase):
"""
TCML_TechnionIIT_lsqlm fitting algorithm by Moti Freiman and Noam Korngut, TechnionIIT
"""

# I'm thinking that we define default attributes for each submission like this
# And in __init__, we can call the OsipiBase control functions to check whether
# the user inputs fulfil the requirements

# Some basic stuff that identifies the algorithm
id_author = "Moti Freiman and Noam Korngut, TechnIIT"
id_algorithm_type = "Bi-exponential fit, Trust Region Reflective algorithm"
id_return_parameters = "f, D*, D, S0"
id_units = "seconds per milli metre squared or milliseconds per micro metre squared"

# 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_bounds = False
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?


# Supported inputs in the standardized class
supported_bounds = True
supported_initial_guess = True
supported_thresholds = False

def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True):
"""
Everything this algorithm requires should be implemented here.
Number of segmentation thresholds, bounds, etc.

Our OsipiBase object could contain functions that compare the inputs with
the requirements.
"""
super(TCML_TechnionIIT_lsqtrf, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess)
self.fit_least_squares = fit_least_squares_trf
self.fitS0=fitS0
self.initialize(bounds, initial_guess, fitS0)

def initialize(self, bounds, initial_guess, fitS0):
if bounds is None:
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
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.use_initial_guess = True
self.fitS0=fitS0
self.use_initial_guess = True
self.use_bounds = True

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

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

Returns:
_type_: _description_
"""

bvalues=np.array(bvalues)
bounds=np.array(self.bounds)
bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]]
initial_guess = np.array(self.initial_guess)
initial_guess = initial_guess[[0, 2, 1, 3]]
fit_results = self.fit_least_squares(1 ,bvalues, np.array(signals)[:,np.newaxis], bounds,initial_guess)

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

return results
7 changes: 7 additions & 0 deletions src/wrappers/OsipiBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,10 @@ def osipi_simple_bias_and_RMSE_test(self, SNR, bvalues, f, Dstar, D, noise_reali
print(f"D bias:\t{D_bias}\nD RMSE:\t{D_RMSE}")


def D_and_Ds_swap(self,results):
if results['D']>results['Dp']:
D=results['Dp']
results['Dp']=results['D']
results['D']=D
results['f']=1-results['f']
return results
2 changes: 2 additions & 0 deletions tests/IVIMmodels/unit_tests/algorithms.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"algorithms": [
"TCML_TechnionIIT_lsqlm",
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason to omit the other 2?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, the other two algorithms fail within the package itself. One refers to a variable that is not initiated. The other has the bounds not properly implemented. As the code is in a package, we cannot modify it. I've asked the authors to take a look.

"TCML_TechnionIIT_lsqtrf",
"ASD_MemorialSloanKettering_QAMPER_IVIM",
"ETP_SRI_LinearFitting",
"IAR_LU_biexp",
Expand Down
Loading
Loading