21
21
# https://www.nipreps.org/community/licensing/
22
22
#
23
23
"""Datasets with multiple phase encoded directions."""
24
- from nipype .pipeline import engine as pe
25
24
from nipype .interfaces import utility as niu
26
-
25
+ from nipype . pipeline import engine as pe
27
26
from niworkflows .engine .workflows import LiterateWorkflow as Workflow
28
27
29
28
from ... import data
32
31
_PEPOLAR_DESC = """\
33
32
A *B<sub>0</sub>*-nonuniformity map (or *fieldmap*) was estimated based on two (or more)
34
33
echo-planar imaging (EPI) references """
35
-
34
+ _PEPOLAR_METHOD = "PEB/PEPOLAR (phase-encoding based / PE-POLARity)"
36
35
37
36
def init_topup_wf (
38
37
grid_reference = 0 ,
@@ -88,13 +87,13 @@ def init_topup_wf(
88
87
89
88
"""
90
89
from nipype .interfaces .fsl .epi import TOPUP
91
- from niworkflows .interfaces .nibabel import MergeSeries , ReorientImage
92
90
from niworkflows .interfaces .images import RobustAverage
91
+ from niworkflows .interfaces .nibabel import MergeSeries , ReorientImage
93
92
94
- from ...utils .misc import front as _front
95
- from ...interfaces .epi import GetReadoutTime , SortPEBlips
96
- from ...interfaces .utils import UniformGrid , PadSlices , ReorientImageAndMetadata
97
93
from ...interfaces .bspline import TOPUPCoeffReorient
94
+ from ...interfaces .epi import GetReadoutTime , SortPEBlips
95
+ from ...interfaces .utils import PadSlices , ReorientImageAndMetadata , UniformGrid
96
+ from ...utils .misc import front as _front
98
97
from ..ancillary import init_brainextraction_wf
99
98
100
99
workflow = Workflow (name = name )
@@ -118,7 +117,7 @@ def init_topup_wf(
118
117
),
119
118
name = "outputnode" ,
120
119
)
121
- outputnode .inputs .method = "PEB/PEPOLAR (phase-encoding based / PE-POLARity)"
120
+ outputnode .inputs .method = _PEPOLAR_METHOD
122
121
123
122
# Calculate the total readout time of each run
124
123
readout_time = pe .MapNode (
@@ -238,13 +237,23 @@ def init_topup_wf(
238
237
return workflow
239
238
240
239
241
- def init_3dQwarp_wf (omp_nthreads = 1 , debug = False , name = "pepolar_estimate_wf" ):
240
+ def init_3dQwarp_wf (
241
+ omp_nthreads = 1 ,
242
+ debug = False ,
243
+ sloppy = False ,
244
+ name = "pepolar_estimate_wf" ):
242
245
"""
243
246
Create the PEPOLAR field estimation workflow based on AFNI's ``3dQwarp``.
244
247
245
248
This workflow takes in two EPI files that MUST have opposed
246
249
:abbr:`PE (phase-encoding)` direction.
247
250
Therefore, EPIs with orthogonal PE directions are not supported.
251
+ ``3dQwarp`` is used to generate a displacement field and correct
252
+ the reference image. The workflow also returns an estimated fieldmap,
253
+ which is the result of converting the displacement field to a fieldmap
254
+ and then regularizing it with a bspline field. This means that the unwarped
255
+ image is in general not what one would get by reconstructing the fieldmap
256
+ from fmap_coeff and warping the in_data.
248
257
249
258
Workflow Graph
250
259
.. workflow ::
@@ -267,25 +276,46 @@ def init_3dQwarp_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"):
267
276
------
268
277
in_data : :obj:`list` of :obj:`str`
269
278
A list of two EPI files, the first of which will be taken as reference.
279
+ The reference PhaseEncodingDirection should match the value for
280
+ in_reference.
281
+ metadata : :obj:`list` of :obj:`dict`
282
+ A list with length matching the length of in_data. Each element should be a
283
+ dict with keys that are strings and values of any type. One key should be
284
+ PhaseEncodingDirection and the values should be BIDS-valid codings.
270
285
271
286
Outputs
272
287
-------
273
288
fmap : :obj:`str`
274
289
The path of the estimated fieldmap.
275
290
fmap_ref : :obj:`str`
276
- The path of an unwarped conversion of the first element of ``in_data``.
291
+ The path of an unwarped conversion of files in ``in_data``.
292
+ fmap_mask : :obj:`str`
293
+ The path of mask corresponding to the ``fmap_ref`` output.
294
+ fmap_coeff : :obj:`str` or :obj:`list` of :obj:`str`
295
+ The path(s) of the B-Spline coefficients supporting the fieldmap.
296
+ method: :obj:`str`
297
+ Short description of the estimation method that was run.
298
+ out_warps: :obj:`str`
299
+ The displacement field from 3dQwarp, in ANTS format.
277
300
278
301
"""
279
302
from nipype .interfaces import afni
303
+ from niworkflows .func .util import init_enhance_and_skullstrip_bold_wf
304
+ from niworkflows .interfaces .fixes import FixHeaderRegistration as Registration
305
+ from niworkflows .interfaces .freesurfer import StructuralReference
280
306
from niworkflows .interfaces .header import CopyHeader
281
- from niworkflows .interfaces .fixes import (
282
- FixHeaderRegistration as Registration ,
283
- FixHeaderApplyTransforms as ApplyTransforms ,
307
+
308
+ from ...interfaces .bspline import (
309
+ DEFAULT_HF_ZOOMS_MM ,
310
+ DEFAULT_ZOOMS_MM ,
311
+ ApplyCoeffsField ,
312
+ BSplineApprox ,
284
313
)
285
- from niworkflows .interfaces .freesurfer import StructuralReference
286
- from niworkflows .func .util import init_enhance_and_skullstrip_bold_wf
287
- from ...utils .misc import front as _front , last as _last
288
- from ...interfaces .utils import Flatten , ConvertWarp
314
+ from ...interfaces .epi import GetReadoutTime
315
+ from ...interfaces .fmap import DisplacementsField2Fieldmap
316
+ from ...interfaces .utils import ConvertWarp , Flatten
317
+ from ...utils .misc import front , last
318
+
289
319
290
320
workflow = Workflow (name = name )
291
321
workflow .__desc__ = f"""{ _PEPOLAR_DESC } \
@@ -297,7 +327,22 @@ def init_3dQwarp_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"):
297
327
)
298
328
299
329
outputnode = pe .Node (
300
- niu .IdentityInterface (fields = ["fmap" , "fmap_ref" ]), name = "outputnode"
330
+ niu .IdentityInterface (
331
+ fields = [
332
+ "fmap" ,
333
+ "fmap_ref" ,
334
+ "fmap_mask" ,
335
+ "fmap_coeff" ,
336
+ "method" ,
337
+ "out_warps" ]),
338
+ name = "outputnode"
339
+ )
340
+ outputnode .inputs .method = _PEPOLAR_METHOD
341
+
342
+ readout_time = pe .Node (
343
+ GetReadoutTime (),
344
+ name = "readout_time" ,
345
+ run_without_submitting = True ,
301
346
)
302
347
303
348
flatten = pe .Node (Flatten (), name = "flatten" )
@@ -355,14 +400,24 @@ def init_3dQwarp_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"):
355
400
356
401
cphdr_warp = pe .Node (CopyHeader (), name = "cphdr_warp" , mem_gb = 0.01 )
357
402
358
- unwarp_reference = pe .Node (
359
- ApplyTransforms (
360
- dimension = 3 ,
361
- float = True ,
362
- interpolation = "LanczosWindowedSinc" ,
363
- ),
364
- name = "unwarp_reference" ,
403
+ # Extract the corresponding fieldmap in Hz
404
+ extract_field = pe .Node (
405
+ DisplacementsField2Fieldmap (), name = "extract_field"
406
+ )
407
+
408
+ # Regularize with B-Splines
409
+ bs_filter = pe .Node (
410
+ BSplineApprox (debug = debug , extrapolate = not debug ),
411
+ name = "bs_filter" ,
365
412
)
413
+ bs_filter .interface ._always_run = debug
414
+ bs_filter .inputs .bs_spacing = (
415
+ [DEFAULT_HF_ZOOMS_MM ] if not sloppy else [DEFAULT_ZOOMS_MM ]
416
+ )
417
+ if sloppy :
418
+ bs_filter .inputs .zooms_min = 4.0
419
+
420
+ unwarp = pe .Node (ApplyCoeffsField (), name = "unwarp" )
366
421
367
422
# fmt: off
368
423
workflow .connect ([
@@ -371,20 +426,32 @@ def init_3dQwarp_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"):
371
426
(flatten , sort_pe , [("out_list" , "inlist" )]),
372
427
(sort_pe , qwarp , [("qwarp_args" , "args" )]),
373
428
(sort_pe , merge_pes , [("sorted" , "in_files" )]),
374
- (merge_pes , pe0_wf , [(("out_file" , _front ), "inputnode.in_file" )]),
375
- (merge_pes , pe1_wf , [(("out_file" , _last ), "inputnode.in_file" )]),
429
+ (merge_pes , pe0_wf , [(("out_file" , front ), "inputnode.in_file" )]),
430
+ (merge_pes , pe1_wf , [(("out_file" , last ), "inputnode.in_file" )]),
376
431
(pe0_wf , align_pes , [("outputnode.skull_stripped_file" , "fixed_image" )]),
377
432
(pe1_wf , align_pes , [("outputnode.skull_stripped_file" , "moving_image" )]),
378
433
(pe0_wf , qwarp , [("outputnode.skull_stripped_file" , "in_file" )]),
379
434
(align_pes , qwarp , [("warped_image" , "base_file" )]),
380
- (inputnode , cphdr_warp , [(("in_data" , _front ), "hdr_file" )]),
435
+ (inputnode , cphdr_warp , [(("in_data" , front ), "hdr_file" )]),
381
436
(qwarp , cphdr_warp , [("source_warp" , "in_file" )]),
382
437
(cphdr_warp , to_ants , [("out_file" , "in_file" )]),
383
- (to_ants , unwarp_reference , [("out_file" , "transforms" )]),
384
- (inputnode , unwarp_reference , [("in_reference" , "reference_image" ),
385
- ("in_reference" , "input_image" )]),
386
- (unwarp_reference , outputnode , [("output_image" , "fmap_ref" )]),
387
- (to_ants , outputnode , [("out_file" , "fmap" )]),
438
+ (pe0_wf , extract_field , [("outputnode.skull_stripped_file" , "epi" )]),
439
+ (to_ants , extract_field , [("out_file" , "transform" )]),
440
+ (inputnode , readout_time , [(("metadata" , front ), "metadata" )]),
441
+ (readout_time , extract_field , [("readout_time" , "ro_time" ),
442
+ ("pe_direction" , "pe_dir" )]),
443
+ (pe1_wf , unwarp , [("outputnode.skull_stripped_file" , "in_data" )]),
444
+ (pe0_wf , bs_filter , [("outputnode.mask_file" , "in_mask" )]),
445
+ (extract_field , bs_filter , [("out_file" , "in_data" )]),
446
+ (bs_filter , unwarp , [("out_coeff" , "in_coeff" )]),
447
+ (readout_time , unwarp , [("readout_time" , "ro_time" ),
448
+ ("pe_direction" , "pe_dir" )]),
449
+ (bs_filter , outputnode , [("out_coeff" , "fmap_coeff" )]),
450
+ (qwarp , outputnode , [("warped_source" , "fmap_ref" )]),
451
+ (unwarp , outputnode , [("out_field" , "fmap" )]),
452
+ (pe0_wf , outputnode , [("outputnode.mask_file" , "fmap_mask" )]),
453
+ (to_ants , outputnode , [("out_file" , "out_warps" )])
454
+
388
455
])
389
456
# fmt: on
390
457
return workflow
0 commit comments