1
1
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
2
2
# vi: set ft=python sts=4 ts=4 sw=4 et:
3
3
"""SDC workflows coordination."""
4
- from collections import defaultdict
5
-
6
4
from nipype .pipeline import engine as pe
7
5
from nipype .interfaces import utility as niu
8
6
from nipype import logging
9
7
10
8
from niworkflows .engine .workflows import LiterateWorkflow as Workflow
11
9
12
- # Fieldmap workflows
13
- from .pepolar import init_pepolar_unwarp_wf
14
10
15
11
LOGGER = logging .getLogger ('nipype.workflow' )
16
12
FMAP_PRIORITY = {
23
19
DEFAULT_MEMORY_MIN_GB = 0.01
24
20
25
21
26
- def init_sdc_wf ( distorted_ref , omp_nthreads = 1 , debug = False , ignore = None ):
22
+ def init_sdc_estimate_wf ( fmaps , epi_meta , omp_nthreads = 1 , debug = False , ignore = None ):
27
23
"""
28
24
Build a :abbr:`SDC (susceptibility distortion correction)` workflow.
29
25
30
- This workflow implements the heuristics to choose a
31
- :abbr:`SDC (susceptibility distortion correction)` strategy .
26
+ This workflow implements the heuristics to choose an estimation
27
+ methodology for :abbr:`SDC (susceptibility distortion correction)`.
32
28
When no field map information is present within the BIDS inputs,
33
29
the EXPERIMENTAL "fieldmap-less SyN" can be performed, using
34
30
the ``--use-syn`` argument. When ``--force-syn`` is specified,
@@ -41,41 +37,40 @@ def init_sdc_wf(distorted_ref, omp_nthreads=1, debug=False, ignore=None):
41
37
42
38
Parameters
43
39
----------
44
- distorted_ref : pybids.BIDSFile
45
- A BIDSFile object with suffix ``bold``, ``sbref`` or ``dwi``.
40
+ fmaps : list of pybids dicts
41
+ A list of dictionaries with the available fieldmaps
42
+ (and their metadata using the key ``'metadata'`` for the
43
+ case of :abbr:`PEPOLAR (Phase-Encoding POLARity)` fieldmaps).
44
+ epi_meta : dict
45
+ BIDS metadata dictionary corresponding to the
46
+ :abbr:`EPI (echo-planar imaging)` run (i.e., suffix ``bold``,
47
+ ``sbref``, or ``dwi``) for which the fieldmap is being estimated.
46
48
omp_nthreads : int
47
49
Maximum number of threads an individual process may use
48
50
debug : bool
49
51
Enable debugging outputs
50
52
51
53
Inputs
52
54
------
53
- distorted_ref
55
+ epi_file
54
56
A reference image calculated at a previous stage
55
- ref_brain
57
+ epi_brain
56
58
Same as above, but brain-masked
57
- ref_mask
59
+ epi_mask
58
60
Brain mask for the run
59
- t1_brain
61
+ t1w_brain
60
62
T1w image, brain-masked, for the fieldmap-less SyN method
61
63
std2anat_xfm
62
- List of standard -to-T1w transforms generated during spatial
64
+ Standard -to-T1w transform generated during spatial
63
65
normalization (only for the fieldmap-less SyN method).
64
- template : str
65
- Name of template from which prior knowledge will be mapped
66
- into the subject's T1w reference
67
- (only for the fieldmap-less SyN method)
68
- templates : str
69
- Name of templates that index the ``std2anat_xfm`` input list
70
- (only for the fieldmap-less SyN method).
71
66
72
67
Outputs
73
68
-------
74
- distorted_ref
69
+ epi_file
75
70
An unwarped BOLD reference
76
- bold_mask
71
+ epi_mask
77
72
The corresponding new mask after unwarping
78
- bold_ref_brain
73
+ epi_brain
79
74
Brain-extracted, unwarped BOLD reference
80
75
out_warp
81
76
The deformation field to unwarp the susceptibility distortions
@@ -90,19 +85,16 @@ def init_sdc_wf(distorted_ref, omp_nthreads=1, debug=False, ignore=None):
90
85
if not isinstance (ignore , (list , tuple )):
91
86
ignore = tuple (ignore )
92
87
93
- fmaps = defaultdict (list , [])
94
- for associated in distorted_ref .get_associations (kind = 'InformedBy' ):
95
- if associated .suffix in list (FMAP_PRIORITY .keys ()):
96
- fmaps [associated .suffix ].append (associated )
88
+ # TODO: To be removed (filter out unsupported fieldmaps):
89
+ fmaps = [fmap for fmap in fmaps if fmap ['suffix' ] in FMAP_PRIORITY ]
97
90
98
- workflow = Workflow (name = 'sdc_wf ' if distorted_ref else 'sdc_bypass_wf' )
91
+ workflow = Workflow (name = 'sdc_estimate_wf ' if fmaps else 'sdc_bypass_wf' )
99
92
inputnode = pe .Node (niu .IdentityInterface (
100
- fields = ['distorted_ref' , 'ref_brain' , 'ref_mask' ,
101
- 't1_brain' , 'std2anat_xfm' , 'template' , 'templates' ]),
93
+ fields = ['epi_file' , 'epi_brain' , 'epi_mask' , 't1w_brain' , 'std2anat_xfm' ]),
102
94
name = 'inputnode' )
103
95
104
96
outputnode = pe .Node (niu .IdentityInterface (
105
- fields = ['output_ref' , 'ref_mask ' , 'ref_brain ' ,
97
+ fields = ['output_ref' , 'epi_mask ' , 'epi_brain ' ,
106
98
'out_warp' , 'syn_ref' , 'method' ]),
107
99
name = 'outputnode' )
108
100
@@ -115,121 +107,125 @@ def init_sdc_wf(distorted_ref, omp_nthreads=1, debug=False, ignore=None):
115
107
"""
116
108
outputnode .inputs .method = 'None'
117
109
workflow .connect ([
118
- (inputnode , outputnode , [('distorted_ref ' , 'output_ref' ),
119
- ('ref_mask ' , 'ref_mask ' ),
120
- ('ref_brain ' , 'ref_brain ' )]),
110
+ (inputnode , outputnode , [('epi_file ' , 'output_ref' ),
111
+ ('epi_mask ' , 'epi_mask ' ),
112
+ ('epi_brain ' , 'epi_brain ' )]),
121
113
])
122
114
return workflow
123
115
124
116
workflow .__postdesc__ = """\
125
- Based on the estimated susceptibility distortion, an
126
- unwarped BOLD reference was calculated for a more accurate
127
- co-registration with the anatomical reference.
117
+ Based on the estimated susceptibility distortion, an unwarped
118
+ EPI (echo-planar imaging) reference was calculated for a more
119
+ accurate co-registration with the anatomical reference.
128
120
"""
129
121
122
+ # In case there are multiple fieldmaps prefer EPI
123
+ fmaps .sort (key = lambda fmap : FMAP_PRIORITY [fmap ['suffix' ]])
124
+ fmap = fmaps [0 ]
125
+
130
126
# PEPOLAR path
131
127
if 'epi' in fmaps :
128
+ from .pepolar import init_pepolar_unwarp_wf , check_pes
132
129
outputnode .inputs .method = 'PEB/PEPOLAR (phase-encoding based / PE-POLARity)'
130
+
131
+ # Filter out EPI fieldmaps to be used
132
+ fmaps_epi = [(epi .path , epi .get_metadata ()['PhaseEncodingDirection' ])
133
+ for epi in fmaps ['epi' ]]
134
+
135
+ # Find matched PE directions
136
+ matched_pe = check_pes (fmaps_epi , epi_meta ['PhaseEncodingDirection' ])
137
+
133
138
# Get EPI polarities and their metadata
134
139
sdc_unwarp_wf = init_pepolar_unwarp_wf (
135
- bold_meta = distorted_ref .get_metadata (),
136
- epi_fmaps = [(fmap , fmap .get_metadata ()["PhaseEncodingDirection" ])
137
- for fmap in fmaps ['epi' ]],
138
- omp_nthreads = omp_nthreads ,
139
- name = 'pepolar_unwarp_wf' )
140
+ matched_pe = matched_pe ,
141
+ omp_nthreads = omp_nthreads )
142
+ sdc_unwarp_wf .inputs .inputnode .epi_pe_dir = epi_meta ['PhaseEncodingDirection' ]
143
+ sdc_unwarp_wf .inputs .inputnode .fmaps_epi = fmaps_epi
140
144
141
145
workflow .connect ([
142
146
(inputnode , sdc_unwarp_wf , [
143
- ('distorted_ref ' , 'inputnode.in_reference' ),
144
- ('bold_mask ' , 'inputnode.in_mask ' ),
145
- ('bold_ref_brain ' , 'inputnode.in_reference_brain ' )]),
147
+ ('epi_file ' , 'inputnode.in_reference' ),
148
+ ('epi_brain ' , 'inputnode.in_reference_brain ' ),
149
+ ('epi_mask ' , 'inputnode.in_mask ' )]),
146
150
])
147
151
148
152
# FIELDMAP path
149
- # elif 'fieldmap' in fmaps:
150
- # # Import specific workflows here, so we don't break everything with one
151
- # # unused workflow.
152
- # suffices = {f.suffix for f in fmaps['fieldmap']}
153
- # if 'fieldmap' in suffices:
154
- # from .fmap import init_fmap_wf
155
- # outputnode.inputs.method = 'FMB (fieldmap-based)'
156
- # fmap_estimator_wf = init_fmap_wf(
157
- # omp_nthreads=omp_nthreads,
158
- # fmap_bspline=False)
159
- # # set inputs
160
- # fmap_estimator_wf.inputs.inputnode.fieldmap = fmap['fieldmap']
161
- # fmap_estimator_wf.inputs.inputnode.magnitude = fmap['magnitude']
162
-
163
- # if fmap['suffix'] == 'phasediff':
164
- # from .phdiff import init_phdiff_wf
165
- # fmap_estimator_wf = init_phdiff_wf(omp_nthreads=omp_nthreads)
166
- # # set inputs
167
- # fmap_estimator_wf.inputs.inputnode.phasediff = fmap['phasediff']
168
- # fmap_estimator_wf.inputs.inputnode.magnitude = [
169
- # fmap_ for key, fmap_ in sorted(fmap.items())
170
- # if key.startswith("magnitude")
171
- # ]
172
-
173
- # sdc_unwarp_wf = init_sdc_unwarp_wf(
174
- # omp_nthreads=omp_nthreads,
175
- # fmap_demean=fmap_demean,
176
- # debug=debug,
177
- # name='sdc_unwarp_wf')
178
- # sdc_unwarp_wf.inputs.inputnode.metadata = bold_meta
179
-
180
- # workflow.connect([
181
- # (inputnode, sdc_unwarp_wf, [
182
- # ('distorted_ref', 'inputnode.in_reference'),
183
- # ('bold_ref_brain', 'inputnode.in_reference_brain'),
184
- # ('bold_mask', 'inputnode.in_mask')]),
185
- # (fmap_estimator_wf, sdc_unwarp_wf, [
186
- # ('outputnode.fmap', 'inputnode.fmap'),
187
- # ('outputnode.fmap_ref', 'inputnode.fmap_ref'),
188
- # ('outputnode.fmap_mask', 'inputnode.fmap_mask')]),
189
- # ])
190
-
191
- # # FIELDMAP-less path
192
- # if any(fm['suffix'] == 'syn' for fm in fmaps):
193
- # # Select template
194
- # sdc_select_std = pe.Node(KeySelect(
195
- # fields=['std2anat_xfm']),
196
- # name='sdc_select_std', run_without_submitting=True)
197
-
198
- # syn_sdc_wf = init_syn_sdc_wf(
199
- # bold_pe=bold_meta.get('PhaseEncodingDirection', None),
200
- # omp_nthreads=omp_nthreads)
201
-
202
- # workflow.connect([
203
- # (inputnode, sdc_select_std, [
204
- # ('template', 'key'),
205
- # ('templates', 'keys'),
206
- # ('std2anat_xfm', 'std2anat_xfm')]),
207
- # (sdc_select_std, syn_sdc_wf, [
208
- # ('std2anat_xfm', 'inputnode.std2anat_xfm')]),
209
- # (inputnode, syn_sdc_wf, [
210
- # ('t1_brain', 'inputnode.t1_brain'),
211
- # ('distorted_ref', 'inputnode.distorted_ref'),
212
- # ('bold_ref_brain', 'inputnode.bold_ref_brain'),
213
- # ('template', 'inputnode.template')]),
214
- # ])
215
-
216
- # # XXX Eliminate branch when forcing isn't an option
217
- # if fmap['suffix'] == 'syn': # No fieldmaps, but --use-syn
218
- # outputnode.inputs.method = 'FLB ("fieldmap-less", SyN-based)'
219
- # sdc_unwarp_wf = syn_sdc_wf
220
- # else: # --force-syn was called when other fieldmap was present
221
- # sdc_unwarp_wf.__desc__ = None
222
- # workflow.connect([
223
- # (syn_sdc_wf, outputnode, [
224
- # ('outputnode.out_reference', 'syn_bold_ref')]),
225
- # ])
153
+ elif 'fieldmap' in fmaps :
154
+ from .unwarp import init_sdc_unwarp_wf
155
+ # Import specific workflows here, so we don't break everything with one
156
+ # unused workflow.
157
+ suffices = {f .suffix for f in fmaps ['fieldmap' ]}
158
+ if 'fieldmap' in suffices :
159
+ from .fmap import init_fmap_wf
160
+ outputnode .inputs .method = 'FMB (fieldmap-based)'
161
+ fmap_wf = init_fmap_wf (
162
+ omp_nthreads = omp_nthreads ,
163
+ fmap_bspline = False )
164
+ # set inputs
165
+ fmap_wf .inputs .inputnode .magnitude = fmap ['magnitude' ]
166
+ fmap_wf .inputs .inputnode .fieldmap = fmap ['fieldmap' ]
167
+ elif 'phasediff' in suffices :
168
+ from .phdiff import init_phdiff_wf
169
+ fmap_wf = init_phdiff_wf (omp_nthreads = omp_nthreads )
170
+ # set inputs
171
+ fmap_wf .inputs .inputnode .phasediff = fmap ['phasediff' ]
172
+ fmap_wf .inputs .inputnode .magnitude = [
173
+ fmap_ for key , fmap_ in sorted (fmap .items ())
174
+ if key .startswith ("magnitude" )
175
+ ]
176
+ else :
177
+ raise ValueError ('Fieldmaps of types %s are not supported' %
178
+ ', ' .join (['"%s"' % f for f in suffices ]))
179
+
180
+ sdc_unwarp_wf = init_sdc_unwarp_wf (
181
+ omp_nthreads = omp_nthreads ,
182
+ debug = debug ,
183
+ name = 'sdc_unwarp_wf' )
184
+ sdc_unwarp_wf .inputs .inputnode .metadata = epi_meta
185
+
186
+ workflow .connect ([
187
+ (inputnode , sdc_unwarp_wf , [
188
+ ('epi_file' , 'inputnode.in_reference' ),
189
+ ('epi_brain' , 'inputnode.in_reference_brain' ),
190
+ ('epi_mask' , 'inputnode.in_mask' )]),
191
+ (fmap_wf , sdc_unwarp_wf , [
192
+ ('outputnode.fmap' , 'inputnode.fmap' ),
193
+ ('outputnode.fmap_ref' , 'inputnode.fmap_ref' ),
194
+ ('outputnode.fmap_mask' , 'inputnode.fmap_mask' )]),
195
+ ])
196
+
197
+ # FIELDMAP-less path
198
+ if any (fm ['suffix' ] == 'syn' for fm in fmaps ):
199
+ from .syn import init_syn_sdc_wf
200
+ syn_sdc_wf = init_syn_sdc_wf (
201
+ bold_pe = epi_meta .get ('PhaseEncodingDirection' , None ),
202
+ omp_nthreads = omp_nthreads )
203
+
204
+ workflow .connect ([
205
+ (inputnode , syn_sdc_wf , [
206
+ ('t1w_brain' , 'inputnode.t1w_brain' ),
207
+ ('epi_file' , 'inputnode.epi_file' ),
208
+ ('epi_brain' , 'inputnode.epi_brain' ),
209
+ ('std2anat_xfm' , 'inputnode.std2anat_xfm' )]),
210
+ ])
211
+
212
+ # XXX Eliminate branch when forcing isn't an option
213
+ if fmap ['suffix' ] == 'syn' : # No fieldmaps, but --use-syn
214
+ outputnode .inputs .method = 'FLB ("fieldmap-less", SyN-based)'
215
+ sdc_unwarp_wf = syn_sdc_wf
216
+ else : # --force-syn was called when other fieldmap was present
217
+ sdc_unwarp_wf .__desc__ = None
218
+ workflow .connect ([
219
+ (syn_sdc_wf , outputnode , [
220
+ ('outputnode.out_reference' , 'syn_bold_ref' )]),
221
+ ])
226
222
227
223
workflow .connect ([
228
224
(sdc_unwarp_wf , outputnode , [
229
225
('outputnode.out_warp' , 'out_warp' ),
230
- ('outputnode.out_reference' , 'distorted_ref ' ),
231
- ('outputnode.out_reference_brain' , 'bold_ref_brain ' ),
232
- ('outputnode.out_mask' , 'bold_mask ' )]),
226
+ ('outputnode.out_reference' , 'epi_file ' ),
227
+ ('outputnode.out_reference_brain' , 'epi_brain ' ),
228
+ ('outputnode.out_mask' , 'epi_mask ' )]),
233
229
])
234
230
235
231
return workflow
0 commit comments