3
3
import nipype .pipeline .engine as pe
4
4
import nipype .interfaces .utility as niu
5
5
import nipype .interfaces .fsl as fsl
6
+ import nipype .interfaces .ants as ants
6
7
import os
7
8
8
- def motion_correct (name = 'motion_correct' ):
9
- """Creates a pipeline that corrects for head motion artifacts in dMRI sequences.
9
+ from .utils import *
10
+
11
+ def hmc_pipeline (name = 'motion_correct' ):
12
+ """
13
+ HMC stands for head-motion correction.
14
+
15
+ Creates a pipeline that corrects for head motion artifacts in dMRI sequences.
10
16
It takes a series of diffusion weighted images and rigidly co-registers
11
- them to one reference image. Finally, the `b`-matrix is rotated accordingly [1 ]_
17
+ them to one reference image. Finally, the `b`-matrix is rotated accordingly [Leemans09 ]_
12
18
making use of the rotation matrix obtained by FLIRT.
13
19
14
- Search angles have been limited to 3.5 degrees, based on results in [2 ]_.
20
+ Search angles have been limited to 3.5 degrees, based on results in [Yendiki13 ]_.
15
21
16
22
A list of rigid transformation matrices is provided, so that transforms can be
17
23
chained. This is useful to correct for artifacts with only one interpolation process (as
18
24
previously discussed `here <https://github.com/nipy/nipype/pull/530#issuecomment-14505042>`_),
19
- and also to compute nuisance regressors as proposed by [2 ]_.
25
+ and also to compute nuisance regressors as proposed by [Yendiki13 ]_.
20
26
21
27
.. warning:: This workflow rotates the `b`-vectors, so please be advised
22
28
that not all the dicom converters ensure the consistency between the resulting
23
29
nifti orientation and the gradients table (e.g. dcm2nii checks it).
24
30
25
31
.. admonition:: References
26
32
27
- .. [1] Leemans A, and Jones DK, Magn Reson Med. 2009 Jun;61(6):1336-49.
28
- doi: 10.1002/mrm.21890.
33
+ .. [Leemans09] Leemans A, and Jones DK, `The B-matrix must be rotated when correcting
34
+ for subject motion in DTI data <http://dx.doi.org/10.1002/mrm.21890>`_,
35
+ Magn Reson Med. 61(6):1336-49. 2009. doi: 10.1002/mrm.21890.
29
36
30
- .. [2 ] Yendiki A et al., Spurious group differences due to head motion in a
31
- diffusion MRI study. Neuroimage. 2013 Nov 21;88C:79-90 .
32
- doi: 10.1016/j.neuroimage.2013.11.027
37
+ .. [Yendiki13 ] Yendiki A et al., ` Spurious group differences due to head motion in
38
+ a diffusion MRI study <http://dx.doi.org/10.1016/j.neuroimage. 2013.11.027>`_ .
39
+ Neuroimage. 21(88C):79-90. 2013. doi: 10.1016/j.neuroimage.2013.11.027
33
40
34
41
Example
35
42
-------
@@ -91,14 +98,17 @@ def motion_correct(name='motion_correct'):
91
98
])
92
99
return wf
93
100
94
- def eddy_correct (name = 'eddy_correct' ):
95
- """Creates a pipeline that corrects for artifacts induced by Eddy currents in dMRI
101
+ def ecc_pipeline (name = 'eddy_correct' ):
102
+ """
103
+ ECC stands for Eddy currents correction.
104
+
105
+ Creates a pipeline that corrects for artifacts induced by Eddy currents in dMRI
96
106
sequences.
97
107
It takes a series of diffusion weighted images and linearly co-registers
98
108
them to one reference image (the average of all b0s in the dataset).
99
109
100
- DWIs are also modulated by the determinant of the Jacobian as indicated by [3 ]_ and
101
- [4 ]_.
110
+ DWIs are also modulated by the determinant of the Jacobian as indicated by [Jones10 ]_ and
111
+ [Rohde04 ]_.
102
112
103
113
A list of rigid transformation matrices can be provided, sourcing from a
104
114
:func:`.motion_correct` workflow, to initialize registrations in a *motion free*
@@ -110,12 +120,12 @@ def eddy_correct(name='eddy_correct'):
110
120
111
121
.. admonition:: References
112
122
113
- .. [3 ] Jones DK, `The signal intensity must be modulated by the determinant of \
114
- the Jacobian when correcting for eddy currents in diffusion MRI \
123
+ .. [Jones10 ] Jones DK, `The signal intensity must be modulated by the determinant of
124
+ the Jacobian when correcting for eddy currents in diffusion MRI
115
125
<http://cds.ismrm.org/protected/10MProceedings/files/1644_129.pdf>`_,
116
126
Proc. ISMRM 18th Annual Meeting, (2010).
117
127
118
- .. [4 ] Rohde et al., `Comprehensive Approach for Correction of Motion and Distortion \
128
+ .. [Rohde04 ] Rohde et al., `Comprehensive Approach for Correction of Motion and Distortion \
119
129
in Diffusion-Weighted MRI \
120
130
<http://stbb.nichd.nih.gov/pdf/com_app_cor_mri04.pdf>`_, MRM 51:103-114 (2004).
121
131
@@ -196,6 +206,99 @@ def eddy_correct(name='eddy_correct'):
196
206
])
197
207
return wf
198
208
209
+ def sdc_fmb (name = 'fmb_correction' ,
210
+ fugue_params = dict (smooth3d = 2.0 , despike_2dfilter = True ),
211
+ bmap_params = dict (delta_te = 2.46e-3 ),
212
+ epi_params = dict ()):
213
+ """
214
+ SDC stands for susceptibility distortion correction. FMB stands for fieldmap-based.
215
+
216
+ The fieldmap based method (FMB) implements SDC by using a mapping of the B0 field
217
+ as proposed by [Jezzard95]_. This workflow uses the implementation of FSL
218
+ (`FUGUE <http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FUGUE>`_). Phase unwrapping is performed
219
+ using `PRELUDE <http://fsl.fmrib.ox.ac.uk/fsl/fsl-4.1.9/fugue/prelude.html>`_
220
+ [Jenkinson03]_.
221
+
222
+
223
+ .. admonition:: References
224
+
225
+ .. [Jezzard95] Jezzard P, and Balaban RS, `Correction for geometric distortion in echo
226
+ planar images from B0 field variations <http://dx.doi.org/10.1002/mrm.1910340111>`_,
227
+ MRM 34(1):65-73. (1995). doi: 10.1002/mrm.1910340111.
228
+
229
+ .. [Jenkinson03] Jenkinson M., `Fast, automated, N-dimensional phase-unwrapping algorithm
230
+ <http://dx.doi.org/10.1002/mrm.10354>`_, MRM 49(1):193-197, 2003, doi: 10.1002/mrm.10354.
231
+
232
+ """
233
+ inputnode = pe .Node (niu .IdentityInterface (fields = ['in_file' , 'in_bval' , 'in_mask' ,
234
+ 'bmap_pha' , 'bmap_mag' ]),
235
+ name = 'inputnode' )
236
+
237
+
238
+ firstmag = pe .Node (fsl .ExtractROI (t_min = 0 , t_size = 1 ), name = 'GetFirst' )
239
+ n4 = pe .Node (ants .N4BiasFieldCorrection (dimension = 3 ), name = 'Bias' )
240
+ bet = pe .Node (fsl .BET (frac = 0.4 , mask = True ), name = 'BrainExtraction' )
241
+ dilate = pe .Node (fsl .maths .MathsCommand (nan2zeros = True ,
242
+ args = '-kernel sphere 5 -dilM' ), name = 'MskDilate' )
243
+ pha2rads = pe .Node (niu .Function (input_names = ['in_file' ], output_names = ['out_file' ],
244
+ function = siemens2rads ), name = 'PreparePhase' )
245
+ prelude = pe .Node (fsl .PRELUDE (process3d = True ), name = 'PhaseUnwrap' )
246
+ rad2rsec = pe .Node (niu .Function (input_names = ['in_file' , 'delta_te' ],
247
+ output_names = ['out_file' ], function = rads2radsec ), name = 'ToRadSec' )
248
+ rad2rsec .inputs .delta_te = bmap_params ['delta_te' ]
249
+
250
+ avg_b0 = pe .Node (niu .Function (input_names = ['in_dwi' , 'in_bval' ],
251
+ output_names = ['out_file' ], function = b0_average ), name = 'b0_avg' )
252
+
253
+ flirt = pe .Node (fsl .FLIRT (interp = 'spline' , cost = 'normmi' , cost_func = 'normmi' ,
254
+ dof = 6 , bins = 64 , save_log = True , padding_size = 10 ,
255
+ searchr_x = [- 4 ,4 ], searchr_y = [- 4 ,4 ], searchr_z = [- 4 ,4 ],
256
+ fine_search = 1 , coarse_search = 10 ),
257
+ name = 'BmapMag2B0' )
258
+ applyxfm = pe .Node (fsl .ApplyXfm (interp = 'spline' , padding_size = 10 , apply_xfm = True ),
259
+ name = 'BmapPha2B0' )
260
+
261
+ pre_fugue = pe .Node (fsl .FUGUE (save_fmap = True ), name = 'PreliminaryFugue' )
262
+ demean = pe .Node (niu .Function (input_names = ['in_file' , 'in_mask' ],
263
+ output_names = ['out_file' ], function = demean_image ),
264
+ name = 'DemeanFmap' )
265
+
266
+ cleanup = cleanup_edge_pipeline ()
267
+
268
+ wf = pe .Workflow (name = name )
269
+ wf .connect ([
270
+ (inputnode , pha2rads , [('bmap_pha' , 'in_file' )])
271
+ ,(inputnode , firstmag , [('bmap_mag' , 'in_file' )])
272
+ ,(inputnode , avg_b0 , [('in_file' , 'in_dwi' ),
273
+ ('in_bval' , 'in_bval' )])
274
+ ,(firstmag , n4 , [('roi_file' , 'input_image' )])
275
+ ,(n4 , bet , [('output_image' , 'in_file' )])
276
+ ,(bet , dilate , [('mask_file' , 'in_file' )])
277
+ ,(pha2rads , prelude , [('out_file' , 'phase_file' )])
278
+ ,(n4 , prelude , [('output_image' , 'magnitude_file' )])
279
+ ,(dilate , prelude , [('out_file' , 'mask_file' )])
280
+ ,(prelude , rad2rsec , [('unwrapped_phase_file' , 'in_file' )])
281
+
282
+ ,(avg_b0 , flirt , [('out_file' , 'reference' )])
283
+ ,(inputnode , flirt , [('in_mask' , 'ref_weight' )])
284
+ ,(n4 , flirt , [('output_image' , 'in_file' )])
285
+ ,(dilate , flirt , [('out_file' , 'in_weight' )])
286
+
287
+ ,(avg_b0 , applyxfm , [('out_file' , 'reference' )])
288
+ ,(rad2rsec , applyxfm , [('out_file' , 'in_file' )])
289
+ ,(flirt , applyxfm , [('out_matrix_file' , 'in_matrix_file' )])
290
+
291
+ ,(applyxfm , pre_fugue , [('out_file' , 'fmap_in_file' )])
292
+ ,(inputnode , pre_fugue , [('in_mask' , 'mask_file' )])
293
+
294
+ ,(pre_fugue , demean , [('fmap_out_file' , 'in_file' )])
295
+ ,(inputnode , demean , [('in_mask' , 'in_mask' )])
296
+
297
+ ,(demean , cleanup , [('out_file' , 'inputnode.in_file' )])
298
+ ,(inputnode , cleanup , [('in_mask' , 'inputnode.in_mask' )])
299
+ ])
300
+ return wf
301
+
199
302
200
303
def _checkrnum (ref_num ):
201
304
from nipype .interfaces .base import isdefined
@@ -226,132 +329,7 @@ def _nonb0(in_bval):
226
329
bvals = np .loadtxt (in_bval )
227
330
return np .where (bvals != 0 )[0 ].tolist ()
228
331
229
- def recompose_dwi (in_dwi , in_bval , in_corrected , out_file = None ):
230
- """
231
- Recompose back the dMRI data accordingly the b-values table after EC correction
232
- """
233
- import numpy as np
234
- import nibabel as nb
235
- import os .path as op
236
-
237
- if out_file is None :
238
- fname ,ext = op .splitext (op .basename (in_dwi ))
239
- if ext == ".gz" :
240
- fname ,ext2 = op .splitext (fname )
241
- ext = ext2 + ext
242
- out_file = op .abspath ("%s_eccorrect%s" % (fname , ext ))
243
-
244
- im = nb .load (in_dwi )
245
- dwidata = im .get_data ()
246
- bvals = np .loadtxt (in_bval )
247
- non_b0 = np .where (bvals != 0 )[0 ].tolist ()
248
-
249
- if len (non_b0 )!= len (in_corrected ):
250
- raise RuntimeError ('Length of DWIs in b-values table and after correction should match' )
251
-
252
- for bindex , dwi in zip (non_b0 , in_corrected ):
253
- dwidata [...,bindex ] = nb .load (dwi ).get_data ()
254
-
255
- nb .Nifti1Image (dwidata , im .get_affine (), im .get_header ()).to_filename (out_file )
256
- return out_file
257
-
258
- def recompose_xfm (in_bval , in_xfms ):
259
- """
260
- Insert identity transformation matrices in b0 volumes to build up a list
261
- """
262
- import numpy as np
263
- import os .path as op
264
-
265
- bvals = np .loadtxt (in_bval )
266
- out_matrix = np .array ([np .eye (4 )] * len (bvals ))
267
- xfms = iter ([np .loadtxt (xfm ) for xfm in in_xfms ])
268
- out_files = []
269
-
270
- for i , b in enumerate (bvals ):
271
- if b == 0.0 :
272
- mat = np .eye (4 )
273
- else :
274
- mat = xfms .next ()
275
-
276
- out_name = 'eccor_%04d.mat' % i
277
- out_files .append (out_name )
278
- np .savetxt (out_name , mat )
279
-
280
- return out_files
281
-
282
-
283
332
def _xfm_jacobian (in_xfm ):
284
333
import numpy as np
285
334
from math import fabs
286
335
return [fabs (np .linalg .det (np .loadtxt (xfm ))) for xfm in in_xfm ]
287
-
288
-
289
- def b0_average (in_dwi , in_bval , out_file = None ):
290
- """
291
- A function that averages the *b0* volumes from a DWI dataset.
292
-
293
- .. warning:: *b0* should be already registered (head motion artifact should
294
- be corrected).
295
-
296
- """
297
- import numpy as np
298
- import nibabel as nb
299
- import os .path as op
300
-
301
- if out_file is None :
302
- fname ,ext = op .splitext (op .basename (in_dwi ))
303
- if ext == ".gz" :
304
- fname ,ext2 = op .splitext (fname )
305
- ext = ext2 + ext
306
- out_file = op .abspath ("%s_avg_b0%s" % (fname , ext ))
307
-
308
- imgs = nb .four_to_three (nb .load (in_dwi ))
309
- bval = np .loadtxt (in_bval )
310
-
311
- b0s = []
312
-
313
- for bval , img in zip (bval , imgs ):
314
- if bval == 0 :
315
- b0s .append (img .get_data ())
316
-
317
- b0 = np .average (np .array (b0s ), axis = 0 )
318
-
319
- hdr = imgs [0 ].get_header ().copy ()
320
- nii = nb .Nifti1Image (b0 , imgs [0 ].get_affine (), hdr )
321
-
322
- nb .save (nii , out_file )
323
- return out_file
324
-
325
-
326
- def rotate_bvecs (in_bvec , in_matrix ):
327
- """
328
- Rotates the input bvec file accordingly with a list of matrices.
329
-
330
- .. note:: the input affine matrix transforms points in the destination image to their \
331
- corresponding coordinates in the original image. Therefore, this matrix should be inverted \
332
- first, as we want to know the target position of :math:`\\ vec{r}`.
333
-
334
- """
335
- import os
336
- import numpy as np
337
-
338
- name , fext = os .path .splitext (os .path .basename (in_bvec ))
339
- if fext == '.gz' :
340
- name , _ = os .path .splitext (name )
341
- out_file = os .path .abspath ('./%s_rotated.bvec' % name )
342
- bvecs = np .loadtxt (in_bvec ).T
343
- new_bvecs = []
344
-
345
- if len (bvecs ) != len (in_matrix ):
346
- raise RuntimeError ('Number of b-vectors and rotation matrices should match.' )
347
-
348
- for bvec , mat in zip (bvecs , in_matrix ):
349
- if np .all (bvec == 0.0 ):
350
- new_bvecs .append (bvec )
351
- else :
352
- invrot = np .linalg .inv (np .loadtxt (mat ))[:3 ,:3 ]
353
- newbvec = invrot .dot (bvec )
354
- new_bvecs .append ((newbvec / np .linalg .norm (newbvec )))
355
-
356
- np .savetxt (out_file , np .array (new_bvecs ).T , fmt = '%0.15f' )
357
- return out_file
0 commit comments