Skip to content

Commit 55457a2

Browse files
committed
ENH: new artifact correction workflow on fsl
Uses TOPUP and Eddy for correction. Tested on the new dmri_preprocess.py example with the fsl course data.
1 parent 91b7880 commit 55457a2

File tree

4 files changed

+240
-52
lines changed

4 files changed

+240
-52
lines changed

examples/dmri_preprocessing.py

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# @Author: oesteban
44
# @Date: 2014-08-31 20:32:22
55
# @Last Modified by: oesteban
6-
# @Last Modified time: 2014-09-02 09:41:46
6+
# @Last Modified time: 2014-09-02 13:12:12
77
"""
88
===================
99
dMRI: Preprocessing
@@ -15,17 +15,18 @@
1515
This script, dmri_preprocessing.py, demonstrates how to prepare dMRI data
1616
for tractography and connectivity analysis with nipype.
1717
18-
We perform this analysis using the FSL course data, which can be acquired from here:
19-
http://www.fmrib.ox.ac.uk/fslcourse/fsl_course_data2.tar.gz
18+
We perform this analysis using the FSL course data, which can be acquired from
19+
here: http://www.fmrib.ox.ac.uk/fslcourse/fsl_course_data2.tar.gz
2020
2121
Can be executed in command line using ``python dmri_preprocessing.py``
2222
2323
2424
Import necessary modules from nipype.
2525
"""
26+
2627
import os # system functions
2728
import nipype.interfaces.io as nio # Data i/o
28-
import nipype.interfaces.utility as util # utility
29+
import nipype.interfaces.utility as niu # utility
2930
import nipype.algorithms.misc as misc
3031

3132
import nipype.pipeline.engine as pe # pypeline engine
@@ -34,7 +35,6 @@
3435
from nipype.interfaces import ants
3536

3637

37-
3838
"""
3939
Load specific nipype's workflows for preprocessing of dMRI data:
4040
:class:`nipype.workflows.dmri.preprocess.epi.all_peb_pipeline`,
@@ -43,8 +43,7 @@
4343
that is *A>>>P* or *-y* (in RAS systems).
4444
"""
4545

46-
from nipype.workflows.dmri.preprocess.epi import all_peb_pipeline
47-
46+
from nipype.workflows.dmri.preprocess.epi import all_fsl_pipeline, remove_bias
4847

4948
"""
5049
Map field names into individual subject runs
@@ -55,7 +54,7 @@
5554
bvals=[['subject_id', 'bvals']],
5655
dwi_rev=[['subject_id', 'nodif_PA']])
5756

58-
infosource = pe.Node(interface=util.IdentityInterface(fields=['subject_id']),
57+
infosource = pe.Node(interface=niu.IdentityInterface(fields=['subject_id']),
5958
name="infosource")
6059

6160
# Set the subject 1 identifier in subject_list,
@@ -102,7 +101,7 @@
102101
actual processing functions
103102
"""
104103

105-
inputnode = pe.Node(util.IdentityInterface(fields=["dwi", "bvecs", "bvals",
104+
inputnode = pe.Node(niu.IdentityInterface(fields=["dwi", "bvecs", "bvals",
106105
"dwi_rev"]), name="inputnode")
107106

108107

@@ -117,7 +116,7 @@
117116
Artifacts correction
118117
--------------------
119118
120-
We will use the combination of ```topup``` and ```eddy``` as suggested by FSL.
119+
We will use the combination of ``topup`` and ``eddy`` as suggested by FSL.
121120
122121
In order to configure the susceptibility distortion correction (SDC), we first
123122
write the specific parameters of our echo-planar imaging (EPI) images.
@@ -128,25 +127,23 @@
128127
129128
"""
130129

131-
epi_AP = {'echospacing': 66.5e-3, 'acc_factor': 1, 'enc_dir': 'y-'}
132-
epi_PA = {'echospacing': 66.5e-3, 'acc_factor': 1, 'enc_dir': 'y'}
133-
prep = all_peb_pipeline(epi_params=epi_AP, altepi_params=epi_PA)
130+
epi_AP = {'echospacing': 66.5e-3, 'enc_dir': 'y-'}
131+
epi_PA = {'echospacing': 66.5e-3, 'enc_dir': 'y'}
132+
prep = all_fsl_pipeline(epi_params=epi_AP, altepi_params=epi_PA)
134133

135134

136135
"""
137136
138137
Bias field correction
139138
---------------------
140139
141-
Finally, we set up a node to estimate a single multiplicative bias field from
142-
the *b0* image, as suggested in [Jeurissen2014]_.
140+
Finally, we set up a node to correct for a single multiplicative bias field
141+
from computed on the *b0* image, as suggested in [Jeurissen2014]_.
143142
144143
"""
145144

146-
n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3), name='Bias_b0')
147-
split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs')
148-
merge = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval', 'in_corrected'],
149-
output_names=['out_file'], function=recompose_dwi), name='MergeDWIs')
145+
bias = remove_bias()
146+
150147

151148
"""
152149
Connect nodes in workflow
@@ -165,6 +162,9 @@
165162
('dwi_rev', 'inputnode.alt_file'),
166163
('bvals', 'inputnode.in_bval'),
167164
('bvecs', 'inputnode.in_bvec')])
165+
,(prep, bias, [('outputnode.out_file', 'inputnode.in_file'),
166+
('outputnode.out_mask', 'inputnode.in_mask')])
167+
,(datasource, bias, [('bvals', 'inputnode.in_bval')])
168168
])
169169

170170

@@ -176,12 +176,3 @@
176176
wf.run()
177177
wf.write_graph()
178178

179-
180-
"""
181-
182-
.. admonition:: References
183-
184-
.. [Jeurissen2014] Jeurissen B. et al., `Multi-tissue constrained spherical deconvolution
185-
for improved analysis of multi-shell diffusion MRI data
186-
<http://dx.doi.org/10.1016/j.neuroimage.2014.07.061>`_. NeuroImage (2014).
187-
doi: 10.1016/j.neuroimage.2014.07.061
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
from epi import (all_fmb_pipeline, all_peb_pipeline,
2-
hmc_pipeline, ecc_pipeline, sdc_fmb, sdc_peb)
1+
from epi import (all_fmb_pipeline, all_peb_pipeline, all_fsl_pipeline,
2+
hmc_pipeline, ecc_pipeline, sdc_fmb, sdc_peb,
3+
remove_bias)

nipype/workflows/dmri/preprocess/epi.py

Lines changed: 147 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,12 @@ def all_fmb_pipeline(name='hmc_sdc_ecc'):
7070
def all_peb_pipeline(name='hmc_sdc_ecc',
7171
epi_params=dict(echospacing=0.77e-3,
7272
acc_factor=3,
73-
enc_dir='y-'),
73+
enc_dir='y-',
74+
epi_factor=1),
7475
altepi_params=dict(echospacing=0.77e-3,
7576
acc_factor=3,
76-
enc_dir='y')):
77+
enc_dir='y',
78+
epi_factor=1)):
7779
"""
7880
Builds a pipeline including three artifact corrections: head-motion correction (HMC),
7981
susceptibility-derived distortion correction (SDC), and Eddy currents-derived distortion
@@ -96,6 +98,10 @@ def all_peb_pipeline(name='hmc_sdc_ecc',
9698
sdc = sdc_peb(epi_params=epi_params, altepi_params=altepi_params)
9799
ecc = ecc_pipeline()
98100

101+
rot_bvec = pe.Node(niu.Function(input_names=['in_bvec', 'eddy_params'],
102+
output_names=['out_file'], function=eddy_rotate_bvecs),
103+
name='Rotate_Bvec')
104+
99105
regrid = pe.Node(fs.MRIConvert(vox_size=(2.0, 2.0, 2.0), out_orientation='RAS'),
100106
name='Reslice')
101107

@@ -120,6 +126,76 @@ def all_peb_pipeline(name='hmc_sdc_ecc',
120126
,(regrid, avg_b0_1, [('out_file', 'in_dwi')])
121127
,(inputnode, avg_b0_1, [('in_bval', 'in_bval')])
122128
,(avg_b0_1, bet_dwi1, [('out_file','in_file')])
129+
,(inputnode, rot_bvec, [('in_bvec', 'in_bvec')])
130+
,(ecc, rot_bvec, [('out_parameter', 'eddy_params')])
131+
,(bet_dwi1, outputnode, [('mask_file', 'out_mask')])
132+
,(rot_bvec, outputnode, [('out_file', 'out_bvec')])
133+
])
134+
return wf
135+
136+
137+
def all_fsl_pipeline(name='fsl_all_correct',
138+
epi_params=dict(echospacing=0.77e-3,
139+
acc_factor=3,
140+
enc_dir='y-'),
141+
altepi_params=dict(echospacing=0.77e-3,
142+
acc_factor=3,
143+
enc_dir='y')):
144+
"""
145+
Workflow that integrates FSL ``topup`` and ``eddy``.
146+
"""
147+
148+
inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bvec', 'in_bval',
149+
'alt_file']), name='inputnode')
150+
151+
outputnode = pe.Node(niu.IdentityInterface(fields=['out_file', 'out_mask',
152+
'out_bvec']), name='outputnode')
153+
154+
def _gen_index(in_file):
155+
import numpy as np
156+
import nibabel as nb
157+
import os
158+
out_file = os.path.abspath('index.txt')
159+
vols = nb.load(in_file).get_data().shape[-1]
160+
np.savetxt(out_file, np.ones((vols,)).T)
161+
return out_file
162+
163+
avg_b0_0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'],
164+
output_names=['out_file'], function=b0_average), name='b0_avg_pre')
165+
bet_dwi0 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_pre')
166+
167+
sdc = sdc_peb(epi_params=epi_params, altepi_params=altepi_params)
168+
ecc = pe.Node(fsl.Eddy(method='jac'), name='fsl_eddy')
169+
170+
regrid = pe.Node(fs.MRIConvert(vox_size=(2.0, 2.0, 2.0), out_orientation='RAS'),
171+
name='Reslice')
172+
avg_b0_1 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'],
173+
output_names=['out_file'], function=b0_average), name='b0_avg_post')
174+
bet_dwi1 = pe.Node(fsl.BET(frac=0.3, mask=True, robust=True), name='bet_dwi_post')
175+
176+
wf = pe.Workflow('dMRI_Artifacts_FSL')
177+
wf.connect([
178+
(inputnode, avg_b0_0, [('in_file', 'in_dwi'),
179+
('in_bval', 'in_bval')])
180+
,(avg_b0_0, bet_dwi0, [('out_file','in_file')])
181+
,(bet_dwi0, sdc, [('mask_file', 'inputnode.in_mask')])
182+
,(inputnode, sdc, [('in_file', 'inputnode.in_file'),
183+
('alt_file', 'inputnode.alt_file'),
184+
('in_bval', 'inputnode.in_bval')])
185+
186+
,(sdc, ecc, [('topup.out_enc_file', 'in_acqp'),
187+
('topup.out_fieldcoef', 'in_topup_fieldcoef'),
188+
('topup.out_movpar', 'in_topup_movpar')])
189+
,(bet_dwi0, ecc, [('mask_file', 'in_mask')])
190+
,(inputnode, ecc, [('in_file', 'in_file'),
191+
(('in_file', _gen_index), 'in_index'),
192+
('in_bval', 'in_bval'),
193+
('in_bvec', 'in_bvec')])
194+
,(ecc, regrid, [('out_corrected', 'in_file')])
195+
,(regrid, outputnode, [('out_file', 'out_file')])
196+
,(regrid, avg_b0_1, [('out_file', 'in_dwi')])
197+
,(inputnode, avg_b0_1, [('in_bval', 'in_bval')])
198+
,(avg_b0_1, bet_dwi1, [('out_file','in_file')])
123199
,(bet_dwi1, outputnode, [('mask_file', 'out_mask')])
124200
])
125201
return wf
@@ -218,6 +294,7 @@ def hmc_pipeline(name='motion_correct'):
218294
])
219295
return wf
220296

297+
221298
def ecc_pipeline(name='eddy_correct'):
222299
"""
223300
ECC stands for Eddy currents correction.
@@ -326,6 +403,7 @@ def ecc_pipeline(name='eddy_correct'):
326403
])
327404
return wf
328405

406+
329407
def sdc_fmb(name='fmb_correction',
330408
fugue_params=dict(smooth3d=2.0),
331409
bmap_params=dict(delta_te=2.46e-3),
@@ -462,10 +540,12 @@ def sdc_fmb(name='fmb_correction',
462540
def sdc_peb(name='peb_correction',
463541
epi_params=dict(echospacing=0.77e-3,
464542
acc_factor=3,
465-
enc_dir='y-'),
543+
enc_dir='y-',
544+
epi_factor=1),
466545
altepi_params=dict(echospacing=0.77e-3,
467546
acc_factor=3,
468-
enc_dir='y')):
547+
enc_dir='y',
548+
epi_factor=1)):
469549
"""
470550
SDC stands for susceptibility distortion correction. PEB stands for phase-encoding-based.
471551
@@ -517,25 +597,72 @@ def sdc_peb(name='peb_correction',
517597

518598
topup = pe.Node(fsl.TOPUP(), name='topup')
519599
topup.inputs.encoding_direction = [epi_params['enc_dir'], altepi_params['enc_dir']]
520-
topup.inputs.readout_times = [epi_params['echospacing']/(1.0*epi_params['acc_factor']),
521-
altepi_params['echospacing']/(1.0*altepi_params['acc_factor'])]
600+
topup.inputs.readout_times = [compute_readout(epi_params),
601+
compute_readout(altepi_params)]
602+
522603
unwarp = pe.Node(fsl.ApplyTOPUP(in_index=[1], method='jac'), name='unwarp')
523604

524605
wf = pe.Workflow(name=name)
525606
wf.connect([
526-
(inputnode, b0_ref, [('in_file','in_file'),
527-
(('ref_num', _checkrnum),'t_min')])
528-
,(inputnode, b0_alt, [('alt_file','in_file'),
529-
(('ref_num', _checkrnum),'t_min')])
530-
,(b0_ref, b0_comb, [('roi_file','in1')])
531-
,(b0_alt, b0_comb, [('roi_file','in2')])
607+
(inputnode, b0_ref, [('in_file', 'in_file'),
608+
(('ref_num', _checkrnum), 't_min')])
609+
,(inputnode, b0_alt, [('alt_file', 'in_file'),
610+
(('ref_num', _checkrnum), 't_min')])
611+
,(b0_ref, b0_comb, [('roi_file', 'in1')])
612+
,(b0_alt, b0_comb, [('roi_file', 'in2')])
532613
,(b0_comb, b0_merge, [('out', 'in_files')])
533-
,(b0_merge, topup, [('merged_file','in_file')])
534-
,(topup, unwarp, [('out_fieldcoef','in_topup_fieldcoef'),
535-
('out_movpar','in_topup_movpar'),
536-
('out_enc_file','encoding_file')])
537-
,(inputnode, unwarp, [('in_file','in_files')])
538-
,(unwarp, outputnode, [('out_corrected','out_file')])
614+
,(b0_merge, topup, [('merged_file', 'in_file')])
615+
,(topup, unwarp, [('out_fieldcoef', 'in_topup_fieldcoef'),
616+
('out_movpar', 'in_topup_movpar'),
617+
('out_enc_file', 'encoding_file')])
618+
,(inputnode, unwarp, [('in_file', 'in_files')])
619+
,(unwarp, outputnode, [('out_corrected', 'out_file')])
620+
])
621+
return wf
622+
623+
624+
def remove_bias(name='bias_correct'):
625+
"""
626+
This workflow estimates a single multiplicative bias field from the averaged *b0*
627+
image, as suggested in [Jeurissen2014]_.
628+
629+
.. admonition:: References
630+
631+
.. [Jeurissen2014] Jeurissen B. et al., `Multi-tissue constrained spherical deconvolution
632+
for improved analysis of multi-shell diffusion MRI data
633+
<http://dx.doi.org/10.1016/j.neuroimage.2014.07.061>`_. NeuroImage (2014).
634+
doi: 10.1016/j.neuroimage.2014.07.061
635+
636+
"""
637+
inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'in_bval',
638+
'in_mask']), name='inputnode')
639+
640+
outputnode = pe.Node(niu.IdentityInterface(fields=['out_file']),
641+
name='outputnode')
642+
643+
avg_b0 = pe.Node(niu.Function(input_names=['in_dwi', 'in_bval'],
644+
output_names=['out_file'], function=b0_average),
645+
name='b0_avg')
646+
n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3,
647+
save_bias=True, bspline_fitting_distance=600), name='Bias_b0')
648+
split = pe.Node(fsl.Split(dimension='t'), name='SplitDWIs')
649+
mult = pe.MapNode(fsl.MultiImageMaths(op_string='-div %s'),
650+
iterfield=['in_file'], name='RemoveBiasOfDWIs')
651+
thres = pe.MapNode(fsl.Threshold(thresh=0.0), iterfield=['in_file'],
652+
name='RemoveNegative')
653+
merge = pe.Node(fsl.utils.Merge(dimension='t'), name='MergeDWIs')
654+
655+
wf = pe.Workflow(name=name)
656+
wf.connect([
657+
(inputnode, avg_b0, [('in_file', 'in_dwi'),
658+
('in_bval', 'in_bval')])
659+
,(avg_b0, n4, [('out_file', 'input_image')])
660+
,(inputnode, n4, [('in_mask', 'mask_image')])
661+
,(inputnode, split, [('in_file', 'in_file')])
662+
,(n4, mult, [('bias_image', 'operand_files')])
663+
,(split, mult, [('out_files', 'in_file')])
664+
,(mult, thres, [('out_file', 'in_file')])
665+
,(thres, merge, [('out_file', 'in_files')])
539666
])
540667
return wf
541668

@@ -546,12 +673,13 @@ def _checkrnum(ref_num):
546673
return 0
547674
return ref_num
548675

676+
549677
def _checkinitxfm(in_bval, in_xfms=None):
550678
from nipype.interfaces.base import isdefined
551679
import numpy as np
552680
import os.path as op
553681
bvals = np.loadtxt(in_bval)
554-
non_b0 = np.where(bvals!=0)[0].tolist()
682+
non_b0 = np.where(bvals != 0)[0].tolist()
555683

556684
init_xfms = []
557685
if (in_xfms is None) or (not isdefined(in_xfms)) or (len(in_xfms)!=len(bvals)):

0 commit comments

Comments
 (0)