Skip to content

Commit b3e3368

Browse files
Merge pull request #22 from OSIPI/main
merge main to brain_phantom
2 parents 7a34b82 + affcd21 commit b3e3368

File tree

4 files changed

+298
-1
lines changed

4 files changed

+298
-1
lines changed

doc/code_contributions_record.csv

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ IVIM,Fitting,Variable projection,See referenced article. Supports units in mm2/s
1515
IVIM,Fitting,Linear fit,Linear fit for D with extrapolation for f. Supports units in mm2/s and µm2/ms,IAR_LundUniversity,TF2.4_IVIM-MRI_CodeCollection/src/original/IAR_LundUniversity/ivim_fit_method_modified_linear.py,Modified by Ivan A. Rashid,Lund University,IvimModelLinear,tba,tbd,
1616
IVIM,Fitting,sIVIM fit,NLLS of the simplified IVIM model (sIVIM). Supports units in mm2/s and µm2/ms,IAR_LundUniversity,TF2.4_IVIM-MRI_CodeCollection/src/original/IAR_LundUniversity/ivim_fit_method_modified_sivim.py,Modified by Ivan A. Rashid,Lund University,IvimModelsIVIM,tba,tbd,
1717
IVIM,Fitting,Segmented NLLS fitting,MATLAB code,OJ_GU,TF2.4_IVIM-MRI_CodeCollection/src/original/OJ_GU/,Oscar Jalnefjord,University of Gothenburg,IVIM_seg,https://doi.org/10.1007/s10334-018-0697-5,tbd,
18-
IVIM,Fitting,Bayesian,MATLAB code,OJ_GU,TF2.4_IVIM-MRI_CodeCollection/src/original/OJ_GU/,Oscar Jalnefjord,University of Gothenburg,IVIM_bayes,https://doi.org/10.1002/mrm.26783,tbd,
18+
IVIM,Fitting,Bayesian,MATLAB code,OJ_GU,TF2.4_IVIM-MRI_CodeCollection/src/original/OJ_GU/,Oscar Jalnefjord,University of Gothenburg,IVIM_bayes,https://doi.org/10.1002/mrm.26783,tbd,
19+
IVIM,Fitting,Linear fit,Linear fit for D and D* and f. Intended to be extremely fast but not always accurate,ETP_SRI,TF2.4_IVIM-MRI_CodeCollection/src/original/ETP_SRI/LinearFitting.py,Eric Peterson,SRI International,,,tbd,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
Created on Aug 30 2022
3+
4+
Script to demonstrate usage of standalone DWI functions:
5+
Generate_ADC
6+
Generate_IVIMmaps
7+
8+
@authors: s.zijlema, p.vhoudt, k.baas, e.kooreman
9+
Group: Uulke van der Heide - NKI
10+
"""
11+
12+
import numpy as np
13+
from DWI_functions_standalone import generate_ADC_standalone, generate_IVIMmaps_standalone
14+
import matplotlib.pyplot as plt
15+
import pathlib
16+
17+
#Load some DWI data
18+
file=pathlib.Path(__file__)
19+
file_path = file.with_name('IVIM_b0_15_150_500.npy').as_posix()
20+
DWI_data = np.load(file_path)
21+
22+
# Specify b values that are in the data
23+
bvalues = [0, 15, 150, 500]
24+
25+
# Specifiy if you want to plot the results
26+
plot_results = False
27+
28+
# Generate ADC map and define the min and max b value that will be used for ADC fitting
29+
ADClog, b0_intercept, used_bvalues = generate_ADC_standalone(DWI_data,bvalues, bmin=150, bmax=500)
30+
31+
# Generate ADC and define the min and max b value that will be used for ADC fitting as well as the max b value
32+
# that will be used for separate Dstar fitting
33+
ADClog, perfFrac, Dstar = generate_IVIMmaps_standalone(DWI_data, bvalues, bminADC=150, bmaxADC=500, bmaxDstar=150)
34+
Slice = 5
35+
36+
if plot_results == True:
37+
# Plot the ADC, f and D* maps
38+
fig, axes = plt.subplots(1, 3, figsize=(10, 5))
39+
40+
axes[0].imshow(ADClog[:,Slice,:], cmap='viridis', vmin=0, vmax=5)
41+
axes[0].set_title("ADC")
42+
axes[0].axis('off')
43+
44+
axes[1].imshow(perfFrac[:,Slice,:], cmap='viridis', vmin=0, vmax=1)
45+
axes[1].set_title("f map")
46+
axes[1].axis('off')
47+
48+
axes[2].imshow(Dstar[:,Slice,:], cmap='viridis', vmin=0, vmax=200)
49+
axes[2].set_title("D*")
50+
axes[2].axis('off')
51+
52+
plt.tight_layout() # Adjust spacing between subplots
53+
plt.show()
54+
55+
print("finish")
56+
57+
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
DWI_functions.py
4+
====================================
5+
This module can be used to calculate ADC/IVIM maps.
6+
IVIM fitting is done in a segmented fashion: https://doi.org/10.3389/fonc.2021.705964
7+
8+
@authors: s.zijlema, p.vhoudt, k.baas, e.kooreman
9+
Group: Uulke van der Heide - NKI
10+
"""
11+
12+
import numpy as np
13+
14+
def generate_ADC_standalone(DWIdata, bvalues, bmin: int = 150, bmax: int = 1000, specificBvals: list = []):
15+
"""
16+
Function to create an ADC map from DWI data.
17+
Required input is a numpy array with the data and the corresponding b-values
18+
19+
Example use:
20+
generate_ADC(data, bvalues)
21+
22+
Parameters
23+
---------
24+
DWIdata: numpy array
25+
DWI data
26+
bvalues: list
27+
bvalues corresponding to the data
28+
bmin/bmax (optional)
29+
Used to select range of b-values that you want to include to calculate the ADC.
30+
Not used when specificBvals is defined.
31+
specificBvals (optional)
32+
List of b-values that should be used for ADC map calculation.
33+
If set, bmin/bmax options are not used.
34+
35+
Returns
36+
-------
37+
ADClog
38+
ADC map
39+
b0_intercept
40+
Map with y-intercepts (b=0) of ADC fit
41+
bvalues
42+
b-values that matched the criteria
43+
"""
44+
45+
#Obtain indices of matching b-values
46+
if specificBvals == []:
47+
#Select only b-values >=bmin and <=bmax
48+
bvalueindex = [i for i,v in enumerate(bvalues) if v >= bmin and v <= bmax]
49+
else: #manual b-value selection
50+
#Check if all specified b-values were acquired
51+
if not all(elem in bvalues for elem in specificBvals):
52+
raise Exception('Not all specified b-values were found. Available b-values: '+str(bvalues)+'. You requested: '+str(specificBvals))
53+
#Get b-value indices
54+
bvalueindex = [i for i,v in enumerate(bvalues) if v in specificBvals]
55+
56+
use_bvalues = [bvalues[index] for index in bvalueindex]
57+
if len(use_bvalues) < 2:
58+
raise Exception('Less than 2 b values were found that fulfill your min and max criteria.')
59+
60+
# Calculate ADC map using linear regression
61+
ADClog, b0_intercept = fit_ADCmap_loglinear_standalone(DWIdata, bvalues, use_bvalues)
62+
ADClog[ADClog<0] = 0 #set values below zero to zero
63+
64+
return ADClog, b0_intercept, use_bvalues
65+
66+
def generate_IVIMmaps_standalone(DWIdata, bvalues, bminADC=150, bmaxADC=1000, bmaxDstar=50):
67+
"""
68+
Function to calculate IVIM maps (perfusion fraction and D*).
69+
NOTE: D* is calculated using only the lowest two b-values.
70+
71+
Example use:
72+
generate_IVIMmaps(data, bvalues)
73+
74+
Parameters
75+
---------
76+
DWIdata: numpy array
77+
DWI data
78+
bvalues: list
79+
bvalues corresponding to the data
80+
bminADC/bmaxADC (optional)
81+
Used to select the range of b-values for ADC calculation.
82+
bmaxDstar (optional)
83+
Used to select the b-values for D* calculation.
84+
outputOptions
85+
['ADC','f','D*'] (default) will output the ADC, f, and D*.
86+
87+
88+
"""
89+
90+
# Calculate ADC map
91+
ADClog, b0_intercept, used_bvalues = generate_ADC_standalone(DWIdata, bvalues, bminADC, bmaxADC)
92+
93+
# Check if b=0 is acquired, otherwise f and D* cannot be calculated
94+
if not 0 in bvalues: # if b=0 is not acquired
95+
print('B=0 is not available. Hence, only the ADC is calculated.')
96+
return # stop execution
97+
98+
# Get b0 data
99+
b0index = bvalues.index(0) # gets first index of b=0
100+
DWIb0 = DWIdata[:, :, :, b0index] # b=0 data
101+
102+
#### Calculate perfusion fraction
103+
with np.errstate(divide='ignore', invalid='ignore'): # hide division by 0 warnings
104+
perfFrac = (DWIb0 - np.exp(b0_intercept)) / DWIb0 # perfusion fraction
105+
perfFrac[np.isnan(perfFrac)] = 0
106+
perfFrac[np.isinf(perfFrac)] = 0
107+
perfFrac[perfFrac < 0] = 0 # f <0 is not possible
108+
perfFrac[perfFrac > 1] = 1 # f >1 is also not possible
109+
110+
# Get matching b-values
111+
try:
112+
_, bvalues = select_b_values_standalone(bvalues, bmin=0, bmax=bmaxDstar)
113+
114+
# Check if b=0 was acquired
115+
if 0 not in bvalues:
116+
raise Exception('b=0 was not acquired')
117+
118+
if len(bvalues) > 2:
119+
print('More than two b-values were detected for D* calculation. ' +
120+
'Note that only the first two will be used for D*.')
121+
122+
except Exception as e:
123+
print("Could not calculate D* due to an error: " + str(e))
124+
return
125+
126+
# Use b=0 and the lowest b-value to calculate D*
127+
with np.errstate(divide='ignore', invalid='ignore'): # hide division by 0 warnings
128+
Dstar = -np.log(
129+
(DWIdata[:,:,:,1] / DWIdata[:,:,:,0] - (1 - perfFrac) * np.exp(-bvalues[1] * 0.001 * ADClog)) / perfFrac) / \
130+
bvalues[1] # Calculate D* using f, d, and the lowest 2 b-values
131+
Dstar[np.isnan(Dstar)] = 0
132+
Dstar[np.isinf(Dstar)] = 0
133+
Dstar = Dstar * 1000 # scale to same as ADC
134+
135+
return ADClog, perfFrac, Dstar
136+
137+
138+
def select_b_values_standalone(all_bvalues, bmin=0, bmax=float("inf"), specificBvals: list = []):
139+
"""
140+
Find b-values and their indices between bmin and bmax of a scan (bmin <= b <= b max)
141+
or of b-values that are defined in specificBvals.
142+
If specificBvals is defined, only b-values are returned that are acquired
143+
in the scan AND are present in specificBvals.
144+
145+
Parameters
146+
----------
147+
all_bvalues : list of int
148+
All b values that are in the data in the order that they are stored
149+
bmin : int
150+
Minimal b-value.
151+
bmax : int
152+
Maximal b-value.
153+
specificBvals : list
154+
List of b-values.
155+
156+
157+
Returns
158+
-------
159+
bvalueindex : list of int
160+
Indices of b-values between bmin and bmax.
161+
bvalues : list of int
162+
B-values between bmin and bmax.
163+
164+
"""
165+
166+
# Obtain indices of matching b-values
167+
if specificBvals == []:
168+
# Select only b-values >=bmin and <=bmax
169+
bvalueindex = [i for i, v in enumerate(all_bvalues) if v >= bmin and v <= bmax]
170+
else: # manual b-value selection
171+
# Check if all specified b-values were acquired
172+
if not all(elem in all_bvalues for elem in specificBvals):
173+
raise Exception('Not all specified b-values were found. Available b-values: ' + str(
174+
all_bvalues) + '. You requested: ' + str(specificBvals))
175+
176+
# Get b-value indices
177+
bvalueindex = [i for i, v in enumerate(all_bvalues) if v in specificBvals]
178+
179+
# Check if enough b-values were found
180+
if len(bvalueindex) < 2:
181+
raise Exception("No(t enough) b-values matched the selected range between " + str(bmin) + " and " + str(
182+
bmax) + " (found: " + str(len(bvalueindex)) + ")")
183+
184+
# Select matching b-values
185+
bvalues = [all_bvalues[i] for i in bvalueindex]
186+
187+
# Return index and b-values
188+
return bvalueindex, bvalues
189+
190+
191+
def fit_ADCmap_loglinear_standalone(DWIdata, all_bvalues, use_bvalues, size=[]):
192+
"""
193+
Function to calculate ADC based on linear regression.
194+
Taken from ADC_functions.py but adjusted to also output intercept and
195+
removed size input requirement (could be extracted from si_array).
196+
The ADC values have been validated with the Philips ADC maps (2022).
197+
198+
Parameters
199+
---------
200+
DWIdata
201+
DWI data
202+
all_bvalues
203+
list or array of b-values that are stored in the data
204+
use_bvalues
205+
list or array of selected b-values selected for calculation.
206+
207+
Returns
208+
-------
209+
ADC calculated ADC and the intercept with the y-axis at b=0 in the same
210+
x,y,z coordinates as the original DWI image (i.e. size)
211+
212+
"""
213+
214+
nr_bvalues = len(use_bvalues)
215+
216+
b_mat = np.ones([nr_bvalues, 2])
217+
for curBvalNr in range(nr_bvalues):
218+
b_mat[curBvalNr, 0] = use_bvalues[curBvalNr]
219+
220+
if size == []:
221+
size = DWIdata.shape[:-1] # b-values are stored in the last dimension
222+
223+
# Extract the b values that are used for calculation
224+
indices = [all_bvalues.index(value) for value in use_bvalues]
225+
DWIdata = DWIdata[:,:,:,indices]
226+
DWIdata = DWIdata.transpose(3, 0, 1, 2) # code below expects different format
227+
228+
with np.errstate(divide='ignore', invalid='ignore'): # hide division by 0 warnings
229+
log_si = -np.log(DWIdata.reshape(nr_bvalues, -1)) # calculate log of data and reshape to one row per b-value
230+
#log_si = -np.log(DWIdata.reshape(-1, nr_bvalues))
231+
log_si[np.isinf(log_si)] = 0 # to fix 0 values in si_array
232+
233+
ADC, intercept = np.linalg.lstsq(b_mat, log_si, rcond=None)[0] # calculate ADC and b=0 intercept
234+
ADC = ADC.reshape(*size) * 1000 # Convert ADC to 10^-3 mm^2/s
235+
b0_intercept = -intercept.reshape(*size) # can be used to calculate perfusion fraction
236+
237+
return ADC, b0_intercept
238+
239+

src/standardized/PV_MUMC_biexp.py

Whitespace-only changes.

0 commit comments

Comments
 (0)