@@ -53,9 +53,9 @@ def init_topup_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"):
53
53
54
54
Outputs
55
55
-------
56
- fieldmap : :obj:`str`
56
+ fmap : :obj:`str`
57
57
The path of the estimated fieldmap.
58
- corrected : :obj:`str`
58
+ fmap_ref : :obj:`str`
59
59
The path of an unwarped conversion of files in ``in_data``.
60
60
61
61
"""
@@ -74,8 +74,8 @@ def init_topup_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"):
74
74
outputnode = pe .Node (
75
75
niu .IdentityInterface (
76
76
fields = [
77
- "corrected " ,
78
- "fieldmap " ,
77
+ "fmap_ref " ,
78
+ "fmap " ,
79
79
"coefficients" ,
80
80
"jacobians" ,
81
81
"xfms" ,
@@ -87,7 +87,9 @@ def init_topup_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"):
87
87
88
88
concat_blips = pe .Node (MergeSeries (), name = "concat_blips" )
89
89
readout_time = pe .MapNode (
90
- GetReadoutTime (), name = "readout_time" , iterfield = ["metadata" , "in_file" ],
90
+ GetReadoutTime (),
91
+ name = "readout_time" ,
92
+ iterfield = ["metadata" , "in_file" ],
91
93
run_without_submitting = True ,
92
94
)
93
95
@@ -106,8 +108,8 @@ def init_topup_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"):
106
108
(inputnode , topup , [(("metadata" , _pe2fsl ), "encoding_direction" )]),
107
109
(readout_time , topup , [("readout_time" , "readout_times" )]),
108
110
(concat_blips , topup , [("out_file" , "in_file" )]),
109
- (topup , outputnode , [("out_corrected" , "corrected " ),
110
- ("out_field" , "fieldmap " ),
111
+ (topup , outputnode , [("out_corrected" , "fmap_ref " ),
112
+ ("out_field" , "fmap " ),
111
113
("out_fieldcoef" , "coefficients" ),
112
114
("out_jacs" , "jacobians" ),
113
115
("out_mats" , "xfms" ),
@@ -118,7 +120,7 @@ def init_topup_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"):
118
120
return workflow
119
121
120
122
121
- def init_3dQwarp_wf (pe_dir , omp_nthreads = 1 , name = "pepolar_estimate_wf" ):
123
+ def init_3dQwarp_wf (omp_nthreads = 1 , name = "pepolar_estimate_wf" ):
122
124
"""
123
125
Create the PEPOLAR field estimation workflow based on AFNI's ``3dQwarp``.
124
126
@@ -132,12 +134,10 @@ def init_3dQwarp_wf(pe_dir, omp_nthreads=1, name="pepolar_estimate_wf"):
132
134
:simple_form: yes
133
135
134
136
from sdcflows.models.pepolar import init_3dQwarp_wf
135
- wf = init_3dQwarp_wf(pe_dir="j" )
137
+ wf = init_3dQwarp_wf()
136
138
137
139
Parameters
138
140
----------
139
- pe_dir : :obj:`str`
140
- PE direction (BIDS compatible)
141
141
name : :obj:`str`
142
142
Name for this workflow
143
143
omp_nthreads : :obj:`int`
@@ -150,34 +150,66 @@ def init_3dQwarp_wf(pe_dir, omp_nthreads=1, name="pepolar_estimate_wf"):
150
150
151
151
Outputs
152
152
-------
153
- fieldmap : :obj:`str`
153
+ fmap : :obj:`str`
154
154
The path of the estimated fieldmap.
155
- corrected : :obj:`str`
155
+ fmap_ref : :obj:`str`
156
156
The path of an unwarped conversion of the first element of ``in_data``.
157
157
158
158
"""
159
159
from nipype .interfaces import afni
160
160
from niworkflows .interfaces import CopyHeader
161
- from niworkflows .interfaces .registration import ANTSApplyTransformsRPT
161
+ from niworkflows .interfaces .fixes import (
162
+ FixHeaderRegistration as Registration ,
163
+ FixHeaderApplyTransforms as ApplyTransforms ,
164
+ )
165
+ from niworkflows .interfaces .freesurfer import StructuralReference
166
+ from niworkflows .func .util import init_enhance_and_skullstrip_bold_wf
167
+ from ..interfaces .utils import Flatten
162
168
163
169
workflow = Workflow (name = name )
164
170
workflow .__desc__ = f"""{ _PEPOLAR_DESC } \
165
171
with `3dQwarp` @afni (AFNI { '' .join (['%02d' % v for v in afni .Info ().version () or []])} ).
166
172
"""
167
173
168
- inputnode = pe .Node (niu .IdentityInterface (fields = ["in_data" ]), name = "inputnode" )
174
+ inputnode = pe .Node (niu .IdentityInterface (fields = ["in_data" , "metadata" ]),
175
+ name = "inputnode" )
169
176
170
177
outputnode = pe .Node (
171
- niu .IdentityInterface (fields = ["fieldmap" , "corrected" ]), name = "outputnode"
178
+ niu .IdentityInterface (fields = ["fmap" , "fmap_ref" ]), name = "outputnode"
179
+ )
180
+
181
+ flatten = pe .Node (Flatten (), name = "flatten" )
182
+ sort_pe = pe .Node (niu .Function (
183
+ function = _sorted_pe , output_names = ["sorted" , "qwarp_args" ]),
184
+ name = "sort_pe" , run_without_submitting = True )
185
+
186
+ merge_pes = pe .MapNode (StructuralReference (
187
+ auto_detect_sensitivity = True ,
188
+ initial_timepoint = 1 ,
189
+ fixed_timepoint = True , # Align to first image
190
+ intensity_scaling = True ,
191
+ # 7-DOF (rigid + intensity)
192
+ no_iteration = True ,
193
+ subsample_threshold = 200 ,
194
+ out_file = 'template.nii.gz' ),
195
+ name = 'merge_pes' ,
196
+ iterfield = ["in_files" ],
197
+ )
198
+
199
+ pe0_wf = init_enhance_and_skullstrip_bold_wf (omp_nthreads = omp_nthreads , name = "pe0_wf" )
200
+ pe1_wf = init_enhance_and_skullstrip_bold_wf (omp_nthreads = omp_nthreads , name = "pe1_wf" )
201
+
202
+ align_pes = pe .Node (
203
+ Registration (
204
+ from_file = _pkg_fname ("sdcflows" , "data/translation_rigid.json" ),
205
+ output_warped_image = True ,
206
+ ),
207
+ name = "align_pes" ,
208
+ n_procs = omp_nthreads ,
172
209
)
173
210
174
211
qwarp = pe .Node (
175
212
afni .QwarpPlusMinus (
176
- args = {
177
- "i" : "-noYdis -noZdis" ,
178
- "j" : "-noXdis -noZdis" ,
179
- "k" : "-noXdis -noYdis" ,
180
- }[pe_dir [0 ]],
181
213
blur = [- 1 , - 1 ],
182
214
environ = {"OMP_NUM_THREADS" : f"{ omp_nthreads } " },
183
215
minpatch = 9 ,
@@ -194,9 +226,8 @@ def init_3dQwarp_wf(pe_dir, omp_nthreads=1, name="pepolar_estimate_wf"):
194
226
cphdr_warp = pe .Node (CopyHeader (), name = "cphdr_warp" , mem_gb = 0.01 )
195
227
196
228
unwarp_reference = pe .Node (
197
- ANTSApplyTransformsRPT (
229
+ ApplyTransforms (
198
230
dimension = 3 ,
199
- generate_report = False ,
200
231
float = True ,
201
232
interpolation = "LanczosWindowedSinc" ,
202
233
),
@@ -205,16 +236,25 @@ def init_3dQwarp_wf(pe_dir, omp_nthreads=1, name="pepolar_estimate_wf"):
205
236
206
237
# fmt: off
207
238
workflow .connect ([
208
- (inputnode , qwarp , [(("in_data" , _front ), "in_file" ),
209
- (("in_data" , _last ), "base_file" )]),
239
+ (inputnode , flatten , [("in_data" , "in_data" ),
240
+ ("metadata" , "in_meta" )]),
241
+ (flatten , sort_pe , [("out_list" , "inlist" )]),
242
+ (sort_pe , qwarp , [("qwarp_args" , "args" )]),
243
+ (sort_pe , merge_pes , [("sorted" , "in_files" )]),
244
+ (merge_pes , pe0_wf , [(("out_file" , _front ), "inputnode.in_file" )]),
245
+ (merge_pes , pe1_wf , [(("out_file" , _last ), "inputnode.in_file" )]),
246
+ (pe0_wf , align_pes , [("outputnode.skull_stripped_file" , "fixed_image" )]),
247
+ (pe1_wf , align_pes , [("outputnode.skull_stripped_file" , "moving_image" )]),
248
+ (pe0_wf , qwarp , [("outputnode.skull_stripped_file" , "in_file" )]),
249
+ (align_pes , qwarp , [("warped_image" , "base_file" )]),
210
250
(inputnode , cphdr_warp , [(("in_data" , _front ), "hdr_file" )]),
211
251
(qwarp , cphdr_warp , [("source_warp" , "in_file" )]),
212
252
(cphdr_warp , to_ants , [("out_file" , "in_file" )]),
213
253
(to_ants , unwarp_reference , [("out" , "transforms" )]),
214
254
(inputnode , unwarp_reference , [("in_reference" , "reference_image" ),
215
255
("in_reference" , "input_image" )]),
216
- (unwarp_reference , outputnode , [("output_image" , "corrected " )]),
217
- (to_ants , outputnode , [("out" , "fieldmap " )]),
256
+ (unwarp_reference , outputnode , [("output_image" , "fmap_ref " )]),
257
+ (to_ants , outputnode , [("out" , "fmap " )]),
218
258
])
219
259
# fmt: on
220
260
return workflow
@@ -238,11 +278,35 @@ def _fix_hdr(in_file, newpath=None):
238
278
def _pe2fsl (metadata ):
239
279
"""Convert ijk notation to xyz."""
240
280
return [
241
- m ["PhaseEncodingDirection" ].replace ("i" , "x" ).replace ("j" , "y" ).replace ("k" , "z" )
281
+ m ["PhaseEncodingDirection" ]
282
+ .replace ("i" , "x" )
283
+ .replace ("j" , "y" )
284
+ .replace ("k" , "z" )
242
285
for m in metadata
243
286
]
244
287
245
288
289
+ def _sorted_pe (inlist ):
290
+ out_ref = [inlist [0 ][0 ]]
291
+ out_opp = []
292
+
293
+ ref_pe = inlist [0 ][1 ]["PhaseEncodingDirection" ]
294
+ for d , m in inlist [1 :]:
295
+ pe = m ["PhaseEncodingDirection" ]
296
+ if pe == ref_pe :
297
+ out_ref .append (d )
298
+ elif pe [0 ] == ref_pe [0 ]:
299
+ out_opp .append (d )
300
+ else :
301
+ raise ValueError ("Cannot handle orthogonal PE encodings." )
302
+
303
+ return [out_ref , out_opp ], {
304
+ "i" : "-noYdis -noZdis" ,
305
+ "j" : "-noXdis -noZdis" ,
306
+ "k" : "-noXdis -noYdis" ,
307
+ }[ref_pe [0 ]]
308
+
309
+
246
310
def _front (inlist ):
247
311
if isinstance (inlist , (list , tuple )):
248
312
return inlist [0 ]
0 commit comments