Skip to content

Commit 711f852

Browse files
Merge pull request #107 from OSIPI/wrapper_super_ivim
Wrapper super ivim After discussion we chose to discontinue testing in 3.9 and 3.10.
2 parents 8d93677 + 103b597 commit 711f852

File tree

10 files changed

+376
-5
lines changed

10 files changed

+376
-5
lines changed

.github/workflows/unit_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
fail-fast: false
1212
matrix:
1313
os: [ubuntu-latest, macos-latest, windows-latest]
14-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
14+
python-version: ["3.11", "3.12", "3.13"]
1515
exclude:
1616
- os: windows-latest
1717
python-version: "3.13"

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
numpy<2
1+
numpy
2+
super-ivim-dc>1.0.0
23
nibabel
34
scipy
45
torchio

src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

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

1111
# I'm thinking that we define default attributes for each submission like this
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from src.wrappers.OsipiBase import OsipiBase
2+
from super_ivim_dc.source.Classsic_ivim_fit import IVIM_fit_sls
3+
import numpy as np
4+
5+
class TCML_TechnionIIT_SLS(OsipiBase):
6+
"""
7+
TCML_TechnionIIT_lsqBOBYQA fitting algorithm by Moti Freiman and Noam Korngut, TechnionIIT
8+
"""
9+
10+
# I'm thinking that we define default attributes for each submission like this
11+
# And in __init__, we can call the OsipiBase control functions to check whether
12+
# the user inputs fulfil the requirements
13+
14+
# Some basic stuff that identifies the algorithm
15+
id_author = "Moti Freiman and Noam Korngut, TechnIIT"
16+
id_algorithm_type = "Bi-exponential fit, segmented fitting"
17+
id_return_parameters = "f, D*, D, S0"
18+
id_units = "seconds per milli metre squared or milliseconds per micro metre squared"
19+
20+
# Algorithm requirements
21+
required_bvalues = 4
22+
required_thresholds = [0,0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds
23+
required_bounds = False
24+
required_bounds_optional = False # Bounds may not be required but are optional
25+
required_initial_guess = False
26+
required_initial_guess_optional = True
27+
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
28+
29+
30+
# Supported inputs in the standardized class
31+
supported_bounds = False
32+
supported_initial_guess = True
33+
supported_thresholds = True
34+
35+
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True):
36+
"""
37+
Everything this algorithm requires should be implemented here.
38+
Number of segmentation thresholds, bounds, etc.
39+
40+
Our OsipiBase object could contain functions that compare the inputs with
41+
the requirements.
42+
"""
43+
super(TCML_TechnionIIT_SLS, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess)
44+
self.fit_least_squares = IVIM_fit_sls
45+
self.fitS0=fitS0
46+
self.initialize(bounds, initial_guess, fitS0,thresholds)
47+
48+
def initialize(self, bounds, initial_guess, fitS0,thresholds):
49+
if bounds is None:
50+
print('warning, only D* is bounded between 0.001 and 0.5)')
51+
self.bounds = ([0.0003, 0.001, 0.009, 0],[0.008, 0.5,0.04, 3])
52+
else:
53+
print('warning, although bounds are given, only D* is bounded)')
54+
bounds=bounds
55+
self.bounds = bounds
56+
if initial_guess is None:
57+
print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.1, 0.01, 1]')
58+
self.initial_guess = [0.001, 0.1, 0.01, 1] # D, Dp, f, S0
59+
else:
60+
self.initial_guess = initial_guess
61+
self.use_initial_guess = True
62+
self.fitS0=fitS0
63+
self.use_initial_guess = True
64+
self.use_bounds = True
65+
if thresholds is None:
66+
self.thresholds = 150
67+
print('warning, no thresholds were defined, so default bounds are used of 150')
68+
else:
69+
self.thresholds = thresholds
70+
71+
def ivim_fit(self, signals, bvalues, **kwargs):
72+
"""Perform the IVIM fit
73+
74+
Args:
75+
signals (array-like)
76+
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
77+
78+
Returns:
79+
_type_: _description_
80+
"""
81+
82+
bvalues=np.array(bvalues)
83+
bounds=np.array(self.bounds)
84+
bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]]
85+
initial_guess = np.array(self.initial_guess)
86+
initial_guess = initial_guess[[0, 2, 1, 3]]
87+
88+
89+
fit_results = self.fit_least_squares(1 ,np.array(signals)[:,np.newaxis],bvalues, bounds,initial_guess,self.thresholds)
90+
91+
results = {}
92+
results["D"] = fit_results[0]
93+
results["f"] = fit_results[2]
94+
results["Dp"] = fit_results[1]
95+
96+
return results
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from src.wrappers.OsipiBase import OsipiBase
2+
from super_ivim_dc.source.Classsic_ivim_fit import fit_least_squers_BOBYQA
3+
import numpy as np
4+
5+
class TCML_TechnionIIT_lsqBOBYQA(OsipiBase):
6+
"""
7+
TCML_TechnionIIT_lsqBOBYQA fitting algorithm by Moti Freiman and Noam Korngut, TechnionIIT
8+
"""
9+
10+
# I'm thinking that we define default attributes for each submission like this
11+
# And in __init__, we can call the OsipiBase control functions to check whether
12+
# the user inputs fulfil the requirements
13+
14+
# Some basic stuff that identifies the algorithm
15+
id_author = "Moti Freiman and Noam Korngut, TechnIIT"
16+
id_algorithm_type = "Bi-exponential fit, BOBYQO"
17+
id_return_parameters = "f, D*, D, S0"
18+
id_units = "seconds per milli metre squared or milliseconds per micro metre squared"
19+
20+
# Algorithm requirements
21+
required_bvalues = 4
22+
required_thresholds = [0,
23+
0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds
24+
required_bounds = False
25+
required_bounds_optional = True # Bounds may not be required but are optional
26+
required_initial_guess = False
27+
required_initial_guess_optional = True
28+
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
29+
30+
31+
# Supported inputs in the standardized class
32+
supported_bounds = True
33+
supported_initial_guess = True
34+
supported_thresholds = False
35+
36+
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True):
37+
"""
38+
Everything this algorithm requires should be implemented here.
39+
Number of segmentation thresholds, bounds, etc.
40+
41+
Our OsipiBase object could contain functions that compare the inputs with
42+
the requirements.
43+
"""
44+
super(TCML_TechnionIIT_lsqBOBYQA, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess)
45+
self.fit_least_squares = fit_least_squers_BOBYQA
46+
self.fitS0=fitS0
47+
self.initialize(bounds, initial_guess, fitS0)
48+
49+
def initialize(self, bounds, initial_guess, fitS0):
50+
if bounds is None:
51+
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]')
52+
self.bounds = ([0.0003, 0.001, 0.009, 0],[0.01, 0.5,0.04, 3])
53+
else:
54+
bounds=bounds
55+
self.bounds = bounds
56+
if initial_guess is None:
57+
print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]')
58+
self.initial_guess = [0.001, 0.1, 0.01, 1] # D, Dp, f, S0
59+
else:
60+
self.initial_guess = initial_guess
61+
self.use_initial_guess = True
62+
self.fitS0=fitS0
63+
self.use_initial_guess = True
64+
self.use_bounds = True
65+
66+
def ivim_fit(self, signals, bvalues, **kwargs):
67+
"""Perform the IVIM fit
68+
69+
Args:
70+
signals (array-like)
71+
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
72+
73+
Returns:
74+
_type_: _description_
75+
"""
76+
77+
bvalues=np.array(bvalues)
78+
bounds=np.array(self.bounds)
79+
bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]]
80+
initial_guess = np.array(self.initial_guess)
81+
initial_guess = initial_guess[[0, 2, 1, 3]]
82+
83+
fit_results = self.fit_least_squares(1 ,bvalues, np.array(signals)[:,np.newaxis], bounds, initial_guess)
84+
85+
results = {}
86+
results["D"] = fit_results[0]
87+
results["f"] = fit_results[2]
88+
results["Dp"] = fit_results[1]
89+
90+
return results
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from src.wrappers.OsipiBase import OsipiBase
2+
from super_ivim_dc.source.Classsic_ivim_fit import fit_least_squares_lm
3+
import numpy as np
4+
5+
class TCML_TechnionIIT_lsqlm(OsipiBase):
6+
"""
7+
TCML_TechnionIIT_lsqlm fitting algorithm by Moti Freiman and Noam Korngut, TechnionIIT
8+
"""
9+
10+
# I'm thinking that we define default attributes for each submission like this
11+
# And in __init__, we can call the OsipiBase control functions to check whether
12+
# the user inputs fulfil the requirements
13+
14+
# Some basic stuff that identifies the algorithm
15+
id_author = "Moti Freiman and Noam Korngut, TechnIIT"
16+
id_algorithm_type = "Bi-exponential fit"
17+
id_return_parameters = "f, D*, D, S0"
18+
id_units = "seconds per milli metre squared or milliseconds per micro metre squared"
19+
20+
# Algorithm requirements
21+
required_bvalues = 4
22+
required_thresholds = [0,
23+
0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds
24+
required_bounds = False
25+
required_bounds_optional = False # Bounds may not be required but are optional
26+
required_initial_guess = False
27+
required_initial_guess_optional = True
28+
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
29+
30+
31+
# Supported inputs in the standardized class
32+
supported_bounds = False
33+
supported_initial_guess = True
34+
supported_thresholds = False
35+
36+
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True):
37+
"""
38+
Everything this algorithm requires should be implemented here.
39+
Number of segmentation thresholds, bounds, etc.
40+
41+
Our OsipiBase object could contain functions that compare the inputs with
42+
the requirements.
43+
"""
44+
super(TCML_TechnionIIT_lsqlm, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess)
45+
self.fit_least_squares = fit_least_squares_lm
46+
self.fitS0=fitS0
47+
self.initialize(bounds, initial_guess, fitS0)
48+
49+
def initialize(self, bounds, initial_guess, fitS0):
50+
if initial_guess is None:
51+
print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.1, 0.01, 1]')
52+
self.initial_guess = [0.001, 0.1, 0.01, 1]
53+
else:
54+
self.initial_guess = initial_guess
55+
self.use_initial_guess = True
56+
self.fitS0=fitS0
57+
self.use_initial_guess = True
58+
self.use_bounds = False
59+
60+
def ivim_fit(self, signals, bvalues, **kwargs):
61+
"""Perform the IVIM fit
62+
63+
Args:
64+
signals (array-like)
65+
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
66+
67+
Returns:
68+
_type_: _description_
69+
"""
70+
71+
bvalues=np.array(bvalues)
72+
initial_guess = np.array(self.initial_guess)
73+
initial_guess = initial_guess[[0, 2, 1, 3]]
74+
fit_results = self.fit_least_squares(1 ,bvalues, np.array(signals)[:,np.newaxis], initial_guess)
75+
76+
results = {}
77+
results["D"] = fit_results[0]
78+
results["f"] = fit_results[2]
79+
results["Dp"] = fit_results[1]
80+
81+
return results
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from src.wrappers.OsipiBase import OsipiBase
2+
from super_ivim_dc.source.Classsic_ivim_fit import fit_least_squares_trf
3+
import numpy as np
4+
5+
class TCML_TechnionIIT_lsqtrf(OsipiBase):
6+
"""
7+
TCML_TechnionIIT_lsqlm fitting algorithm by Moti Freiman and Noam Korngut, TechnionIIT
8+
"""
9+
10+
# I'm thinking that we define default attributes for each submission like this
11+
# And in __init__, we can call the OsipiBase control functions to check whether
12+
# the user inputs fulfil the requirements
13+
14+
# Some basic stuff that identifies the algorithm
15+
id_author = "Moti Freiman and Noam Korngut, TechnIIT"
16+
id_algorithm_type = "Bi-exponential fit, Trust Region Reflective algorithm"
17+
id_return_parameters = "f, D*, D, S0"
18+
id_units = "seconds per milli metre squared or milliseconds per micro metre squared"
19+
20+
# Algorithm requirements
21+
required_bvalues = 4
22+
required_thresholds = [0,
23+
0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds
24+
required_bounds = False
25+
required_bounds_optional = True # Bounds may not be required but are optional
26+
required_initial_guess = False
27+
required_initial_guess_optional = True
28+
accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most?
29+
30+
31+
# Supported inputs in the standardized class
32+
supported_bounds = True
33+
supported_initial_guess = True
34+
supported_thresholds = False
35+
36+
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True):
37+
"""
38+
Everything this algorithm requires should be implemented here.
39+
Number of segmentation thresholds, bounds, etc.
40+
41+
Our OsipiBase object could contain functions that compare the inputs with
42+
the requirements.
43+
"""
44+
super(TCML_TechnionIIT_lsqtrf, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess)
45+
self.fit_least_squares = fit_least_squares_trf
46+
self.fitS0=fitS0
47+
self.initialize(bounds, initial_guess, fitS0)
48+
49+
def initialize(self, bounds, initial_guess, fitS0):
50+
if bounds is None:
51+
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])')
52+
self.bounds = ([0.0003, 0.001, 0.009, 0],[0.008, 0.5,0.04, 3])
53+
else:
54+
bounds=bounds
55+
self.bounds = bounds
56+
if initial_guess is None:
57+
print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.1, 0.01, 1]')
58+
self.initial_guess = [0.001, 0.1, 0.01, 1] # D, Dp, f, S0
59+
else:
60+
self.initial_guess = initial_guess
61+
self.use_initial_guess = True
62+
self.fitS0=fitS0
63+
self.use_initial_guess = True
64+
self.use_bounds = True
65+
66+
def ivim_fit(self, signals, bvalues, **kwargs):
67+
"""Perform the IVIM fit
68+
69+
Args:
70+
signals (array-like)
71+
bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None.
72+
73+
Returns:
74+
_type_: _description_
75+
"""
76+
77+
bvalues=np.array(bvalues)
78+
bounds=np.array(self.bounds)
79+
bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]]
80+
initial_guess = np.array(self.initial_guess)
81+
initial_guess = initial_guess[[0, 2, 1, 3]]
82+
fit_results = self.fit_least_squares(1 ,bvalues, np.array(signals)[:,np.newaxis], bounds,initial_guess)
83+
84+
results = {}
85+
results["D"] = fit_results[0]
86+
results["f"] = fit_results[2]
87+
results["Dp"] = fit_results[1]
88+
89+
return results

0 commit comments

Comments
 (0)