Skip to content

Commit 576feff

Browse files
committed
add preprocessing utils
1 parent f45ce74 commit 576feff

File tree

1 file changed

+255
-0
lines changed
  • nipype/workflows/dmri/preprocess

1 file changed

+255
-0
lines changed
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
4+
# vi: set ft=python sts=4 ts=4 sw=4 et:
5+
# @Author: oesteban
6+
# @Date: 2014-08-30 10:53:13
7+
# @Last Modified by: oesteban
8+
# @Last Modified time: 2014-08-30 16:06:15
9+
10+
def cleanup_edge_pipeline(name='Cleanup'):
11+
"""
12+
Perform some de-spiking filtering to clean up the edge of the fieldmap
13+
(copied from fsl_prepare_fieldmap)
14+
"""
15+
import nipype.pipeline.engine as pe
16+
import nipype.interfaces.utility as niu
17+
import nipype.interfaces.fsl as fsl
18+
19+
inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_mask']),
20+
name='inputnode')
21+
outputnode = pe.Node(niu.IdentityInterface(fields=['out_file']),
22+
name='outputnode')
23+
24+
fugue = pe.Node(fsl.FUGUE(save_fmap=True, despike_2dfilter=True, despike_threshold=2.1),
25+
name='Despike')
26+
erode = pe.Node(fsl.maths.MathsCommand(nan2zeros=True,
27+
args='-kernel 2D -ero'), name='MskErode')
28+
newmsk = pe.Node(fsl.MultiImageMaths(op_string='-sub %s -thr 0.5 -bin'),
29+
name='NewMask')
30+
applymsk = pe.Node(fsl.ApplyMask(nan2zeros=True), name='ApplyMask')
31+
join = pe.Node(niu.Merge(2), name='Merge')
32+
addedge = pe.Node(fsl.MultiImageMaths(op_string='-mas %s -add %s'),
33+
name='AddEdge')
34+
35+
wf = pe.Workflow(name=name)
36+
wf.connect([
37+
(inputnode, fugue, [('in_file', 'fmap_in_file'),
38+
('in_mask', 'mask_file')])
39+
,(inputnode, erode, [('in_mask', 'in_file')])
40+
,(inputnode, newmsk, [('in_mask', 'in_file')])
41+
,(erode, newmsk, [('out_file', 'operand_files')])
42+
,(fugue, applymsk, [('fmap_out_file', 'in_file')])
43+
,(newmsk, applymsk, [('out_file', 'mask_file')])
44+
,(erode, join, [('out_file', 'in1')])
45+
,(applymsk, join, [('out_file', 'in2')])
46+
,(inputnode, addedge, [('in_file', 'in_file')])
47+
,(join, addedge, [('out', 'operand_files')])
48+
,(addedge, outputnode, [('out_file', 'out_file')])
49+
])
50+
return wf
51+
52+
53+
def recompose_dwi(in_dwi, in_bval, in_corrected, out_file=None):
54+
"""
55+
Recompose back the dMRI data accordingly the b-values table after EC correction
56+
"""
57+
import numpy as np
58+
import nibabel as nb
59+
import os.path as op
60+
61+
if out_file is None:
62+
fname,ext = op.splitext(op.basename(in_dwi))
63+
if ext == ".gz":
64+
fname,ext2 = op.splitext(fname)
65+
ext = ext2 + ext
66+
out_file = op.abspath("%s_eccorrect%s" % (fname, ext))
67+
68+
im = nb.load(in_dwi)
69+
dwidata = im.get_data()
70+
bvals = np.loadtxt(in_bval)
71+
non_b0 = np.where(bvals!=0)[0].tolist()
72+
73+
if len(non_b0)!=len(in_corrected):
74+
raise RuntimeError('Length of DWIs in b-values table and after correction should match')
75+
76+
for bindex, dwi in zip(non_b0, in_corrected):
77+
dwidata[...,bindex] = nb.load(dwi).get_data()
78+
79+
nb.Nifti1Image(dwidata, im.get_affine(), im.get_header()).to_filename(out_file)
80+
return out_file
81+
82+
def recompose_xfm(in_bval, in_xfms):
83+
"""
84+
Insert identity transformation matrices in b0 volumes to build up a list
85+
"""
86+
import numpy as np
87+
import os.path as op
88+
89+
bvals = np.loadtxt(in_bval)
90+
out_matrix = np.array([np.eye(4)] * len(bvals))
91+
xfms = iter([np.loadtxt(xfm) for xfm in in_xfms])
92+
out_files = []
93+
94+
for i, b in enumerate(bvals):
95+
if b == 0.0:
96+
mat = np.eye(4)
97+
else:
98+
mat = xfms.next()
99+
100+
out_name = 'eccor_%04d.mat' % i
101+
out_files.append(out_name)
102+
np.savetxt(out_name, mat)
103+
104+
return out_files
105+
106+
107+
def b0_average(in_dwi, in_bval, out_file=None):
108+
"""
109+
A function that averages the *b0* volumes from a DWI dataset.
110+
111+
.. warning:: *b0* should be already registered (head motion artifact should
112+
be corrected).
113+
114+
"""
115+
import numpy as np
116+
import nibabel as nb
117+
import os.path as op
118+
119+
if out_file is None:
120+
fname,ext = op.splitext(op.basename(in_dwi))
121+
if ext == ".gz":
122+
fname,ext2 = op.splitext(fname)
123+
ext = ext2 + ext
124+
out_file = op.abspath("%s_avg_b0%s" % (fname, ext))
125+
126+
imgs = np.array(nb.four_to_three(nb.load(in_dwi)))
127+
bval = np.loadtxt(in_bval)
128+
b0s = [im.get_data().astype(np.float32) for im in imgs[np.where(bval==0)]]
129+
b0 = np.average(np.array(b0s), axis=0)
130+
131+
hdr = imgs[0].get_header().copy()
132+
hdr.set_data_shape(b0.shape)
133+
hdr.set_xyzt_units('mm')
134+
hdr.set_data_dtype(np.float32)
135+
nb.Nifti1Image(b0, imgs[0].get_affine(), hdr).to_filename(out_file)
136+
return out_file
137+
138+
139+
def rotate_bvecs(in_bvec, in_matrix):
140+
"""
141+
Rotates the input bvec file accordingly with a list of matrices.
142+
143+
.. note:: the input affine matrix transforms points in the destination image to their \
144+
corresponding coordinates in the original image. Therefore, this matrix should be inverted \
145+
first, as we want to know the target position of :math:`\\vec{r}`.
146+
147+
"""
148+
import os
149+
import numpy as np
150+
151+
name, fext = os.path.splitext(os.path.basename(in_bvec))
152+
if fext == '.gz':
153+
name, _ = os.path.splitext(name)
154+
out_file = os.path.abspath('./%s_rotated.bvec' % name)
155+
bvecs = np.loadtxt(in_bvec).T
156+
new_bvecs = []
157+
158+
if len(bvecs) != len(in_matrix):
159+
raise RuntimeError('Number of b-vectors and rotation matrices should match.')
160+
161+
for bvec, mat in zip(bvecs, in_matrix):
162+
if np.all(bvec==0.0):
163+
new_bvecs.append(bvec)
164+
else:
165+
invrot = np.linalg.inv(np.loadtxt(mat))[:3,:3]
166+
newbvec = invrot.dot(bvec)
167+
new_bvecs.append((newbvec/np.linalg.norm(newbvec)))
168+
169+
np.savetxt(out_file, np.array(new_bvecs).T, fmt='%0.15f')
170+
return out_file
171+
172+
def siemens2rads(in_file, out_file=None):
173+
"""
174+
Converts input phase difference map to rads
175+
"""
176+
import numpy as np
177+
import nibabel as nb
178+
import os.path as op
179+
import math
180+
181+
if out_file is None:
182+
fname, fext = op.splitext(op.basename(in_file))
183+
if fext == '.gz':
184+
fname, _ = op.splitext(fname)
185+
out_file = op.abspath('./%s_rads.nii.gz' % fname)
186+
187+
in_file = np.atleast_1d(in_file).tolist()
188+
im = nb.load(in_file[0])
189+
data = im.get_data().astype(np.float32)
190+
hdr = im.get_header().copy()
191+
192+
if len(in_file) == 2:
193+
data = data - nb.load(in_file[1]).get_data().astype(np.float32)
194+
elif (data.ndim == 4) and (data.shape[-1] == 2):
195+
data = np.squeeze(data[...,0] - data[...,1])
196+
hdr.set_data_shape(data.shape[:3])
197+
198+
imin = data.min()
199+
imax = data.max()
200+
data = (2.0 * math.pi * (data - imin)/(imax-imin)) - math.pi
201+
hdr.set_data_dtype(np.float32)
202+
hdr.set_xyzt_units('mm')
203+
hdr['datatype'] = 16
204+
nb.Nifti1Image(data, im.get_affine(), hdr).to_filename(out_file)
205+
return out_file
206+
207+
def rads2radsec(in_file, delta_te, out_file=None):
208+
"""
209+
Converts input phase difference map to rads
210+
"""
211+
import numpy as np
212+
import nibabel as nb
213+
import os.path as op
214+
import math
215+
216+
if out_file is None:
217+
fname, fext = op.splitext(op.basename(in_file))
218+
if fext == '.gz':
219+
fname, _ = op.splitext(fname)
220+
out_file = op.abspath('./%s_radsec.nii.gz' % fname)
221+
222+
im = nb.load(in_file)
223+
data = im.get_data().astype(np.float32) * (1.0/delta_te)
224+
nb.Nifti1Image(data, im.get_affine(),
225+
im.get_header()).to_filename(out_file)
226+
return out_file
227+
228+
def demean_image(in_file, in_mask=None, out_file=None):
229+
"""
230+
Demean image data inside mask
231+
"""
232+
import numpy as np
233+
import nibabel as nb
234+
import os.path as op
235+
import math
236+
237+
if out_file is None:
238+
fname, fext = op.splitext(op.basename(in_file))
239+
if fext == '.gz':
240+
fname, _ = op.splitext(fname)
241+
out_file = op.abspath('./%s_demean.nii.gz' % fname)
242+
243+
im = nb.load(in_file)
244+
data = im.get_data().astype(np.float32)
245+
msk = np.ones_like(data)
246+
247+
if not in_mask is None:
248+
msk = nb.load(in_mask).get_data().astype(np.float32)
249+
msk[msk>0] = 1.0
250+
msk[msk<1] = 0.0
251+
252+
mean = np.median(data[msk==1].reshape(-1))
253+
data[msk==1] = data[msk==1] - mean
254+
nb.Nifti1Image(data, im.get_affine(), im.get_header()).to_filename(out_file)
255+
return out_file

0 commit comments

Comments
 (0)