Skip to content

Commit ef1f541

Browse files
Merge pull request #19 from OSIPI/brain_phantom
Brain phantom
2 parents affcd21 + b3e3368 commit ef1f541

File tree

8 files changed

+97
-0
lines changed

8 files changed

+97
-0
lines changed

phantoms/brain/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Code for generating ivim phantoms of the brain
2+
3+
Two sets of ground truth data are used for simulation:
4+
- Simulation in the diffusive regime (IVIM parameters D, f and D*): reference values from Ryghög et al. 2014 and b-values from Federau et al. 2012
5+
- Simulation in the ballistic regime (IVIM parameters D, f and vd): reference values and sequence parameters from Ahlgren et al. 2016
6+
7+
The segmentation used by the simulations is the ICBM 2009a nonlinear symmetric 3T atlas (https://nist.mni.mcgill.ca/icbm-152-nonlinear-atlases-2009/), the same as in e.g. ASLDRO (https://asldro.readthedocs.io/en/stable/).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0 10 20 30 40 50 60 70 80 90 100 120 140 160 180 200 10 20 30 40 50 60 70 80 90 100 120 140 160 180 200
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.471 0.666 0.816 0.942 1.054 1.154 1.247 1.333 1.414 1.490 1.632 1.763 1.885 1.999 2.107
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import os
2+
import numpy as np
3+
4+
# Data from Ahlgren et al 2016 NMRinBiomed
5+
Delta = 7.5e-3 # 7.5 ms
6+
delta = 7.3e-3 # 7.3 ms
7+
8+
# For DDE sequence we have
9+
# b = 2y^2G^2d^2(D-d/3), c = 2yGdD => c = sqrt(b 2D^2/(D-d/3))
10+
bval_file = os.path.join(os.path.dirname(__file__),'ballistic.bval')
11+
b = np.loadtxt(bval_file)
12+
c = np.sqrt(b*2*Delta**2/(Delta-delta/3))
13+
c[1:(c.size-1)//2+1] = 0 # flow compensated => c = 0
14+
cval_file = bval_file.replace('bval','cval')
15+
np.savetxt(cval_file,c,fmt='%.3f',newline=' ')
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"D":[1.2e-3,0.98e-3,3e-3],
3+
"f":[0.0243,0.0164,0],
4+
"vd":[1.71,1.73,0],
5+
"Db":1.75e-3
6+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0 10 20 30 40 80 110 140 170 200 300 400 500 600 700 800 900
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"D":[0.81e-3,0.86e-3,3e-3],
3+
"f":[0.044,0.033,0],
4+
"Dstar":[84e-3,76e-3,0]
5+
}

phantoms/brain/sim_brain_phantom.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import os
2+
import shutil
3+
import json
4+
import numpy as np
5+
import nibabel as nib
6+
from scipy.ndimage import zoom
7+
8+
DIFFUSIVE_REGIME = 'diffusive'
9+
BALLISTIC_REGIME = 'ballistic'
10+
11+
folder = os.path.dirname(__file__)
12+
13+
###########################
14+
# Simulation parameters #
15+
regime = DIFFUSIVE_REGIME
16+
snr = 200
17+
resolution = [3,3,3]
18+
# #
19+
###########################
20+
21+
22+
# Ground truth
23+
nii = nib.load(os.path.join(folder,'ground_truth','hrgt_icbm_2009a_nls_3t.nii.gz'))
24+
segmentation = np.squeeze(nii.get_fdata()[...,-1])
25+
26+
with open(os.path.join(folder,'ground_truth',regime+'_groundtruth.json'), 'r') as f:
27+
ivim_pars = json.load(f)
28+
S0 = 1
29+
30+
# Sequence parameters
31+
bval_file = os.path.join(folder,'ground_truth',regime+'.bval')
32+
b = np.loadtxt(bval_file)
33+
if regime == BALLISTIC_REGIME:
34+
cval_file = bval_file.replace('bval','cval')
35+
c = np.loadtxt(cval_file)
36+
37+
# Calculate signal
38+
S = np.zeros(list(np.shape(segmentation))+[b.size])
39+
40+
if regime == BALLISTIC_REGIME:
41+
Db = ivim_pars["Db"]
42+
for i,(D,f,vd) in enumerate(zip(ivim_pars["D"],ivim_pars["f"],ivim_pars["vd"])):
43+
S[segmentation==i+1,:] = S0*((1-f)*np.exp(-b*D)+f*np.exp(-b*Db-c**2*vd**2))
44+
else:
45+
for i,(D,f,Dstar) in enumerate(zip(ivim_pars["D"],ivim_pars["f"],ivim_pars["Dstar"])):
46+
S[segmentation==i+1,:] = S0*((1-f)*np.exp(-b*D)+f*np.exp(-b*Dstar))
47+
48+
# Resample to suitable resolution
49+
im = zoom(S,np.append(np.diag(nii.affine)[:3]/np.array(resolution),1),order=1)
50+
sz = im.shape
51+
52+
# Add noise
53+
im_noise = np.abs(im + S0/snr*(np.random.randn(sz[0],sz[1],sz[2],sz[3])+1j*np.random.randn(sz[0],sz[1],sz[2],sz[3])))
54+
55+
# Save as image and sequence parameters
56+
nii_out = nib.Nifti1Image(im_noise,np.eye(4))
57+
base_name = os.path.join(folder,'data','{}_snr{}'.format(regime,snr))
58+
nib.save(nii_out,base_name+'.nii.gz')
59+
shutil.copyfile(bval_file,base_name+'.bval')
60+
if regime == BALLISTIC_REGIME:
61+
shutil.copyfile(cval_file,base_name+'.cval')

0 commit comments

Comments
 (0)