Skip to content

Commit e4aff16

Browse files
Merge pull request #151 from ElinaPetersson/preproc-main
Test and simulation for brain denoising
2 parents d6b104a + bbb5531 commit e4aff16

File tree

6 files changed

+158
-0
lines changed

6 files changed

+158
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,10 @@ coverage.xml
4040
*.pyc
4141
phantoms/MR_XCAT_qMRI/*.json
4242
phantoms/MR_XCAT_qMRI/*.txt
43+
phantoms/brain/data/*.nii.gz
44+
phantoms/brain/ground_truth/*.nii.gz
4345
tests/IVIMmodels/unit_tests/models
4446
models
47+
temp
48+
.vscode
49+
.env

phantoms/__init__.py

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"tissues": ["WM","GM","CSF"],
3+
"D":[0.81e-3,0.86e-3,3e-3],
4+
"f":[0.044,0.033,0],
5+
"Dstar":[84e-3,76e-3,0],
6+
"T1":[1.0, 1.5, 3.4],
7+
"T2":[0.046,0.068,0.45]
8+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
from utilities.data_simulation.Download_data import download_data
8+
9+
10+
def simulate_brain_phantom(snr=100, TE=60e-3, TR=5, resolution=[3,3,3]):
11+
'''
12+
Simulation parameters can be set by changing the default values of the function arguments. The default values are chosen to be suitable for a diffusive regime phantom, but can be changed to simulate a ballistic regime phantom as well. The simulated image is saved in phantoms/brain/data with the name 'diffusive_sn{snr}_relax.nii.gz' or 'ballistic_sn{snr}_relax.nii.gz' depending on the regime. The corresponding bvals and cvals (if applicable) are also saved in the same folder.
13+
snr: signal-to-noise ratio of the simulated image
14+
TE: echo time in seconds
15+
TR: repetition time in seconds
16+
resolution: voxel size in mm
17+
'''
18+
download_data()
19+
20+
DIFFUSIVE_REGIME = 'diffusive'
21+
BALLISTIC_REGIME = 'ballistic'
22+
23+
regime = DIFFUSIVE_REGIME # only adapted for diffusive regime for now, but can be easily changed to simulate ballistic regime as well by changing this variable and providing the corresponding ground truth parameters in the json file
24+
25+
folder = os.path.dirname(__file__)
26+
27+
# Ground truth
28+
nii = nib.load(os.path.join(os.path.split(os.path.split(folder)[0])[0],'download','Phantoms','brain','ground_truth','hrgt_icbm_2009a_nls_3t.nii.gz'))
29+
segmentation = np.squeeze(nii.get_fdata()[...,-1])
30+
31+
with open(os.path.join(folder,'ground_truth',regime+'_relax_groundtruth.json'), 'r') as f:
32+
ivim_pars = json.load(f)
33+
S0 = 1
34+
35+
# Sequence parameters
36+
bval_file = os.path.join(folder,'ground_truth',regime+'.bval')
37+
b = np.loadtxt(bval_file)
38+
if regime == BALLISTIC_REGIME:
39+
cval_file = bval_file.replace('bval','cval')
40+
c = np.loadtxt(cval_file)
41+
42+
# Calculate signal
43+
S = np.zeros(list(np.shape(segmentation))+[b.size])
44+
print(ivim_pars)
45+
if regime == BALLISTIC_REGIME:
46+
Db = ivim_pars["Db"]
47+
for i,(D,f,vd,T1,T2) in enumerate(zip(ivim_pars["D"],ivim_pars["f"],ivim_pars["vd"],ivim_pars["T1"],ivim_pars["T2"])):
48+
S[segmentation==i+1,:] = S0*((1-f)*np.exp(-b*D)+f*np.exp(-b*Db-c**2*vd**2))*np.exp(-TE/T2)*(1-np.exp(-TR/T1))
49+
else:
50+
for i,(D,f,Dstar,T1,T2) in enumerate(zip(ivim_pars["D"],ivim_pars["f"],ivim_pars["Dstar"],ivim_pars["T1"],ivim_pars["T2"])):
51+
S[segmentation==i+1,:] = S0*((1-f)*np.exp(-b*D)+f*np.exp(-b*Dstar))*np.exp(-TE/T2)*(1-np.exp(-TR/T1))
52+
53+
# Resample to suitable resolution
54+
im = zoom(S,np.append(np.diag(nii.affine)[:3]/np.array(resolution),1),order=1)
55+
sz = im.shape
56+
57+
# Save image without noise for reference
58+
nii_out = nib.Nifti1Image(im,np.eye(4))
59+
base_name = os.path.join(folder,'data','{}_reference_relax'.format(regime,snr))
60+
nib.save(nii_out,base_name+'.nii.gz')
61+
shutil.copyfile(bval_file,base_name+'.bval')
62+
63+
# Add Rician noise
64+
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])))
65+
66+
# Save as image and sequence parameters
67+
nii_out = nib.Nifti1Image(im_noise,np.eye(4))
68+
base_name = os.path.join(folder,'data','{}_snr{}_relax'.format(regime,snr))
69+
nib.save(nii_out,base_name+'.nii.gz')
70+
shutil.copyfile(bval_file,base_name+'.bval')
71+
if regime == BALLISTIC_REGIME:
72+
shutil.copyfile(cval_file,base_name+'.cval')
73+
74+
# Resample and save segmentation
75+
segmentation = zoom(segmentation,np.diag(nii.affine)[:3]/np.array(resolution),order=0)
76+
nii_out = nib.Nifti1Image(segmentation,np.eye(4))
77+
base_name = os.path.join(folder,'data','{}_snr{}_relax_mask'.format(regime,snr))
78+
nib.save(nii_out,base_name+'.nii.gz')
79+
80+
81+
if __name__ == "__main__":
82+
simulate_brain_phantom()

tests/IVIMpreproc/unit_tests/__init__.py

Whitespace-only changes.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'''
2+
1. Load brain phantom
3+
2. Add Rician noise
4+
3. Denoise using denoise algorithm
5+
4. Check that sum of squared residuals decreased after denoising
6+
'''
7+
import os
8+
9+
import numpy as np
10+
from phantoms.brain.sim_brain_phantom_preproc import simulate_brain_phantom
11+
import nibabel as nib
12+
import matplotlib.pyplot as plt
13+
from src.original.EP_GU.brain_pipeline import denoise_wrap
14+
15+
16+
def test_denoise_wrap():
17+
# Simulate phantom with noise
18+
snr = 50
19+
simulate_brain_phantom(snr = snr)
20+
21+
# Denoising
22+
im_file = 'phantoms/brain/data/diffusive_snr{}_relax.nii.gz'.format(snr)
23+
im_file_denoised = denoise_wrap(im_file)
24+
S_denoised = nib.load(im_file_denoised).get_fdata()
25+
26+
# Compute sum of squared residuals before denoising
27+
mask = nib.load(os.path.join('phantoms','brain','data','diffusive_snr{}_relax_mask.nii.gz'.format(snr))).get_fdata()
28+
ref_file = 'phantoms/brain/data/diffusive_reference_relax.nii.gz'
29+
S = nib.load(im_file).get_fdata()
30+
S_ref = nib.load(ref_file).get_fdata()
31+
ssr_before = np.sum((S[mask!=0,:]-S_ref[mask!=0,:])**2)
32+
ssr_after = np.sum((S_denoised[mask!=0,:]-S_ref[mask!=0,:])**2)
33+
34+
35+
36+
# print('SSR before denoising: {}'.format(ssr_before))
37+
# print('SSR after denoising: {}'.format(ssr_after))
38+
39+
# plt.subplot(1,3,1)
40+
# plt.imshow(np.rot90(S_ref[:,:,30,10]),cmap='gray')
41+
# plt.title('Reference')
42+
# plt.xticks([])
43+
# plt.yticks([])
44+
# plt.subplot(1,3,2)
45+
# plt.imshow(np.rot90(S[:,:,30,10]),cmap='gray')
46+
# plt.title('Noisy')
47+
# plt.xticks([])
48+
# plt.yticks([])
49+
# plt.subplot(1,3,3)
50+
# plt.imshow(np.rot90(S_denoised[:,:,30,10]),cmap='gray')
51+
# plt.title('Denoised')
52+
# plt.xticks([])
53+
# plt.yticks([])
54+
# plt.show()
55+
56+
# Check that sum of squared residuals decreased after denoising
57+
if ssr_after < ssr_before:
58+
assert True
59+
else:
60+
assert False
61+
62+
63+

0 commit comments

Comments
 (0)