Skip to content

Commit bf906d7

Browse files
authored
Merge pull request #740 from effigies/original_syn
[RTM] RF: Assume phase-encoding direction is A-P unless specified L-R
2 parents c884c4b + d765a8c commit bf906d7

File tree

3 files changed

+59
-166
lines changed

3 files changed

+59
-166
lines changed

fmriprep/interfaces/reports.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
FUNCTIONAL_TEMPLATE = """\t\t<h3 class="elem-title">Summary</h3>
3535
\t\t<ul class="elem-desc">
36+
\t\t\t<li>Phase-encoding (PE) direction: {pedir}</li>
3637
\t\t\t<li>Slice timing correction: {stc}</li>
3738
\t\t\t<li>Susceptibility distortion correction: {sdc}</li>
3839
\t\t\t<li>Registration: {registration}</li>
@@ -141,6 +142,8 @@ class FunctionalSummaryInputSpec(BaseInterfaceInputSpec):
141142
distortion_correction = traits.Enum('epi', 'fieldmap', 'phasediff', 'SyN', 'None',
142143
desc='Susceptibility distortion correction method',
143144
mandatory=True)
145+
pe_direction = traits.Enum(None, 'i', 'i-', 'j', 'j-', mandatory=True,
146+
desc='Phase-encoding direction detected')
144147
registration = traits.Enum('FLIRT', 'bbregister', mandatory=True,
145148
desc='Functional/anatomical registration method')
146149
output_spaces = traits.List(desc='Target spaces')
@@ -163,7 +166,11 @@ def _generate_segment(self):
163166
reg = {'FLIRT': 'FLIRT with boundary-based registration (BBR) metric',
164167
'bbregister': 'FreeSurfer boundary-based registration (bbregister)'
165168
}[self.inputs.registration]
166-
return FUNCTIONAL_TEMPLATE.format(stc=stc, sdc=sdc, registration=reg,
169+
if self.inputs.pe_direction is None:
170+
pedir = 'MISSING - Assuming Anterior-Posterior'
171+
else:
172+
pedir = {'i': 'Left-Right', 'j': 'Anterior-Posterior'}[self.inputs.pe_direction[0]]
173+
return FUNCTIONAL_TEMPLATE.format(pedir=pedir, stc=stc, sdc=sdc, registration=reg,
167174
output_spaces=', '.join(self.inputs.output_spaces),
168175
confounds=', '.join(self.inputs.confounds))
169176

fmriprep/workflows/bold.py

Lines changed: 39 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ def init_func_preproc_wf(bold_file, ignore, freesurfer,
222222
'magnitude2': 'sub-03/ses-2/fmap/sub-03_ses-2_run-1_magnitude2.nii.gz'
223223
}]
224224
run_stc = True
225+
bold_pe = 'j'
225226
else:
226227
metadata = layout.get_metadata(bold_file)
227228
# Find fieldmaps. Options: (phase1|phase2|phasediff|epi|fieldmap)
@@ -232,6 +233,7 @@ def init_func_preproc_wf(bold_file, ignore, freesurfer,
232233
run_stc = ("SliceTiming" in metadata and
233234
'slicetiming' not in ignore and
234235
(_get_series_len(bold_file) > 4 or "TooShort"))
236+
bold_pe = metadata.get("PhaseEncodingDirection")
235237

236238
# TODO: To be removed (supported fieldmaps):
237239
if not set([fmap['type'] for fmap in fmaps]).intersection(['phasediff', 'fieldmap', 'epi']):
@@ -254,7 +256,8 @@ def init_func_preproc_wf(bold_file, ignore, freesurfer,
254256
'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file']),
255257
name='outputnode')
256258

257-
summary = pe.Node(FunctionalSummary(output_spaces=output_spaces), name='summary',
259+
summary = pe.Node(FunctionalSummary(output_spaces=output_spaces,
260+
pe_direction=bold_pe), name='summary',
258261
mem_gb=0.05)
259262
summary.inputs.slice_timing = run_stc
260263
summary.inputs.registration = 'bbregister' if freesurfer else 'FLIRT'
@@ -462,16 +465,14 @@ def init_func_preproc_wf(bold_file, ignore, freesurfer,
462465

463466
if use_syn:
464467
nonlinear_sdc_wf = init_nonlinear_sdc_wf(
465-
bold_file=bold_file, layout=layout, freesurfer=freesurfer, bold2t1w_dof=bold2t1w_dof,
468+
bold_file=bold_file, bold_pe=bold_pe, freesurfer=freesurfer, bold2t1w_dof=bold2t1w_dof,
466469
template=template, omp_nthreads=omp_nthreads)
467470

468471
workflow.connect([
469472
(inputnode, nonlinear_sdc_wf, [
470473
('t1_brain', 'inputnode.t1_brain'),
471474
('t1_seg', 'inputnode.t1_seg'),
472-
('t1_2_mni_reverse_transform', 'inputnode.t1_2_mni_reverse_transform'),
473-
('subjects_dir', 'inputnode.subjects_dir'),
474-
('subject_id', 'inputnode.subject_id')]),
475+
('t1_2_mni_reverse_transform', 'inputnode.t1_2_mni_reverse_transform')]),
475476
(bold_reference_wf, nonlinear_sdc_wf, [
476477
('outputnode.ref_image_brain', 'inputnode.bold_ref')]),
477478
(nonlinear_sdc_wf, func_reports_wf, [
@@ -896,9 +897,9 @@ def init_bold_reg_wf(freesurfer, bold2t1w_dof, bold_file_size_gb, omp_nthreads,
896897
)
897898

898899
if freesurfer:
899-
bbr_wf = init_bbreg_wf(bold2t1w_dof, report=True)
900+
bbr_wf = init_bbreg_wf(bold2t1w_dof)
900901
else:
901-
bbr_wf = init_fsl_bbr_wf(bold2t1w_dof, report=True)
902+
bbr_wf = init_fsl_bbr_wf(bold2t1w_dof)
902903

903904
workflow.connect([
904905
(inputnode, bbr_wf, [
@@ -1242,20 +1243,17 @@ def _aslist(in_value):
12421243
return workflow
12431244

12441245

1245-
def init_nonlinear_sdc_wf(bold_file, layout, freesurfer, bold2t1w_dof,
1246-
template, omp_nthreads,
1246+
def init_nonlinear_sdc_wf(bold_file, freesurfer, bold2t1w_dof,
1247+
template, omp_nthreads, bold_pe='j',
12471248
atlas_threshold=3, name='nonlinear_sdc_wf'):
12481249
"""
12491250
This workflow takes a skull-stripped T1w image and reference BOLD image and
12501251
estimates a susceptibility distortion correction warp, using ANTs symmetric
12511252
normalization (SyN) and the average fieldmap atlas described in
12521253
[Treiber2016]_.
12531254
1254-
If the phase-encoding (PE) direction is known, the SyN deformation is
1255-
restricted to that direction; otherwise, deformation fields are calculated
1256-
for both the right-left and anterior-posterior directions, and selected
1257-
based on the unwarped file that can be aligned to the T1w image with the
1258-
lowest boundary-based registration (BBR) cost.
1255+
SyN deformation is restricted to the phase-encoding (PE) direction.
1256+
If no PE direction is specified, anterior-posterior PE is assumed.
12591257
12601258
SyN deformation is also restricted to regions that are expected to have a
12611259
>3mm (approximately 1 voxel) warp, based on the fieldmap atlas.
@@ -1270,7 +1268,7 @@ def init_nonlinear_sdc_wf(bold_file, layout, freesurfer, bold2t1w_dof,
12701268
from fmriprep.workflows.bold import init_nonlinear_sdc_wf
12711269
wf = init_nonlinear_sdc_wf(
12721270
bold_file='/dataset/sub-01/func/sub-01_task-rest_bold.nii.gz',
1273-
layout=None,
1271+
bold_pe='j',
12741272
freesurfer=True,
12751273
bold2t1w_dof=9,
12761274
template='MNI152NLin2009cAsym',
@@ -1286,10 +1284,6 @@ def init_nonlinear_sdc_wf(bold_file, layout, freesurfer, bold2t1w_dof,
12861284
FAST segmentation white and gray matter, in native T1w space
12871285
t1_2_mni_reverse_transform
12881286
inverse registration transform of T1w image to MNI template
1289-
subjects_dir
1290-
FreeSurfer subjects directory (if applicable)
1291-
subject_id
1292-
FreeSurfer subject_id (if applicable)
12931287
12941288
Outputs
12951289
@@ -1320,13 +1314,17 @@ def init_nonlinear_sdc_wf(bold_file, layout, freesurfer, bold2t1w_dof,
13201314
workflow = pe.Workflow(name=name)
13211315
inputnode = pe.Node(
13221316
niu.IdentityInterface(['t1_brain', 'bold_ref', 't1_2_mni_reverse_transform',
1323-
'subjects_dir', 'subject_id', 't1_seg']), # BBR requirements
1317+
't1_seg']),
13241318
name='inputnode')
13251319
outputnode = pe.Node(
13261320
niu.IdentityInterface(['out_reference_brain', 'out_mask', 'out_warp',
13271321
'out_warp_report', 'out_mask_report']),
13281322
name='outputnode')
13291323

1324+
if bold_pe is None or bold_pe not in ['i', 'j']:
1325+
LOGGER.warning('Incorrect phase-encoding direction, assuming PA (posterior-to-anterior')
1326+
bold_pe = 'j'
1327+
13301328
# Collect predefined data
13311329
# Atlas image and registration affine
13321330
atlas_img = pkgr.resource_filename('fmriprep', 'data/fmap_atlas.nii.gz')
@@ -1369,22 +1367,11 @@ def init_nonlinear_sdc_wf(bold_file, layout, freesurfer, bold2t1w_dof,
13691367
mem_gb=DEFAULT_MEMORY_MIN_GB)
13701368
fixed_image_masks.inputs.in1 = 'NULL'
13711369

1372-
if layout is None:
1373-
bold_pe = None
1374-
else:
1375-
bold_pe = layout.get_metadata(bold_file).get("PhaseEncodingDirection")
1376-
1377-
restrict_i = [[1, 0, 0], [1, 0, 0]]
1378-
restrict_j = [[0, 1, 0], [0, 1, 0]]
1379-
1380-
syn_i = pe.Node(
1381-
ants.Registration(from_file=syn_transform, num_threads=omp_nthreads,
1382-
restrict_deformation=restrict_i),
1383-
name='syn_i', n_procs=omp_nthreads)
1384-
syn_j = pe.Node(
1370+
restrict = [[int(bold_pe[0] == 'i'), int(bold_pe[0] == 'j'), 0]] * 2
1371+
syn = pe.Node(
13851372
ants.Registration(from_file=syn_transform, num_threads=omp_nthreads,
1386-
restrict_deformation=restrict_j),
1387-
name='syn_j', n_procs=omp_nthreads)
1373+
restrict_deformation=restrict),
1374+
name='syn', n_procs=omp_nthreads)
13881375

13891376
seg_2_ref = pe.Node(
13901377
ants.ApplyTransforms(interpolation='NearestNeighbor', float=True,
@@ -1411,79 +1398,23 @@ def init_nonlinear_sdc_wf(bold_file, layout, freesurfer, bold2t1w_dof,
14111398
(transform_list, atlas_2_ref, [('out', 'transforms')]),
14121399
(atlas_2_ref, threshold_atlas, [('output_image', 'in_file')]),
14131400
(threshold_atlas, fixed_image_masks, [('out_file', 'in2')]),
1414-
])
1415-
1416-
if bold_pe is None:
1417-
if freesurfer:
1418-
bbr_i_wf = init_bbreg_wf(bold2t1w_dof, report=False, reregister=False, name='bbr_i_wf')
1419-
bbr_j_wf = init_bbreg_wf(bold2t1w_dof, report=False, reregister=False, name='bbr_j_wf')
1420-
else:
1421-
bbr_i_wf = init_fsl_bbr_wf(bold2t1w_dof, report=False, name='bbr_i_wf')
1422-
bbr_j_wf = init_fsl_bbr_wf(bold2t1w_dof, report=False, name='bbr_j_wf')
1423-
1424-
def select_outputs(cost_i, warped_image_i, forward_transforms_i,
1425-
cost_j, warped_image_j, forward_transforms_j):
1426-
if cost_i < cost_j:
1427-
return warped_image_i, forward_transforms_i
1428-
else:
1429-
return warped_image_j, forward_transforms_j
1430-
1431-
pe_chooser = pe.Node(
1432-
niu.Function(function=select_outputs,
1433-
output_names=['warped_image', 'forward_transforms']),
1434-
name='pe_chooser', mem_gb=DEFAULT_MEMORY_MIN_GB)
1435-
1436-
workflow.connect([(inputnode, syn_i, [('bold_ref', 'moving_image')]),
1437-
(t1_2_ref, syn_i, [('output_image', 'fixed_image')]),
1438-
(fixed_image_masks, syn_i, [('out', 'fixed_image_masks')]),
1439-
(inputnode, syn_j, [('bold_ref', 'moving_image')]),
1440-
(t1_2_ref, syn_j, [('output_image', 'fixed_image')]),
1441-
(fixed_image_masks, syn_j, [('out', 'fixed_image_masks')]),
1442-
(inputnode, bbr_i_wf, [('subjects_dir', 'inputnode.subjects_dir'),
1443-
('subject_id', 'inputnode.subject_id'),
1444-
('t1_seg', 'inputnode.t1_seg'),
1445-
('t1_brain', 'inputnode.t1_brain')]),
1446-
(inputnode, bbr_j_wf, [('subjects_dir', 'inputnode.subjects_dir'),
1447-
('subject_id', 'inputnode.subject_id'),
1448-
('t1_seg', 'inputnode.t1_seg'),
1449-
('t1_brain', 'inputnode.t1_brain')]),
1450-
(syn_i, bbr_i_wf, [('warped_image', 'inputnode.in_file')]),
1451-
(syn_j, bbr_j_wf, [('warped_image', 'inputnode.in_file')]),
1452-
(bbr_i_wf, pe_chooser, [('outputnode.final_cost', 'cost_i')]),
1453-
(bbr_j_wf, pe_chooser, [('outputnode.final_cost', 'cost_j')]),
1454-
(syn_i, pe_chooser, [('warped_image', 'warped_image_i'),
1455-
('forward_transforms', 'forward_transforms_i')]),
1456-
(syn_j, pe_chooser, [('warped_image', 'warped_image_j'),
1457-
('forward_transforms', 'forward_transforms_j')]),
1458-
])
1459-
syn_out = pe_chooser
1460-
elif bold_pe[0] == 'i':
1461-
workflow.connect([(inputnode, syn_i, [('bold_ref', 'moving_image')]),
1462-
(t1_2_ref, syn_i, [('output_image', 'fixed_image')]),
1463-
(fixed_image_masks, syn_i, [('out', 'fixed_image_masks')]),
1464-
])
1465-
syn_out = syn_i
1466-
elif bold_pe[0] == 'j':
1467-
workflow.connect([(inputnode, syn_j, [('bold_ref', 'moving_image')]),
1468-
(t1_2_ref, syn_j, [('output_image', 'fixed_image')]),
1469-
(fixed_image_masks, syn_j, [('out', 'fixed_image_masks')]),
1470-
])
1471-
syn_out = syn_j
1472-
1473-
workflow.connect([(inputnode, seg_2_ref, [('t1_seg', 'input_image')]),
1474-
(ref_2_t1, seg_2_ref, [('forward_transforms', 'transforms')]),
1475-
(syn_out, seg_2_ref, [('warped_image', 'reference_image')]),
1476-
(seg_2_ref, sel_wm, [('output_image', 'in_seg')]),
1477-
(inputnode, syn_rpt, [('bold_ref', 'before')]),
1478-
(syn_out, syn_rpt, [('warped_image', 'after')]),
1479-
(sel_wm, syn_rpt, [('out', 'wm_seg')]),
1480-
(syn_out, skullstrip_bold_wf, [('warped_image', 'inputnode.in_file')]),
1481-
(syn_out, outputnode, [('forward_transforms', 'out_warp')]),
1482-
(skullstrip_bold_wf, outputnode, [
1483-
('outputnode.skull_stripped_file', 'out_reference_brain'),
1484-
('outputnode.mask_file', 'out_mask'),
1485-
('outputnode.out_report', 'out_mask_report')]),
1486-
(syn_rpt, outputnode, [('out_report', 'out_warp_report')])])
1401+
(inputnode, syn, [('bold_ref', 'moving_image')]),
1402+
(t1_2_ref, syn, [('output_image', 'fixed_image')]),
1403+
(fixed_image_masks, syn, [('out', 'fixed_image_masks')]),
1404+
(inputnode, seg_2_ref, [('t1_seg', 'input_image')]),
1405+
(ref_2_t1, seg_2_ref, [('forward_transforms', 'transforms')]),
1406+
(syn, seg_2_ref, [('warped_image', 'reference_image')]),
1407+
(seg_2_ref, sel_wm, [('output_image', 'in_seg')]),
1408+
(inputnode, syn_rpt, [('bold_ref', 'before')]),
1409+
(syn, syn_rpt, [('warped_image', 'after')]),
1410+
(sel_wm, syn_rpt, [('out', 'wm_seg')]),
1411+
(syn, skullstrip_bold_wf, [('warped_image', 'inputnode.in_file')]),
1412+
(syn, outputnode, [('forward_transforms', 'out_warp')]),
1413+
(skullstrip_bold_wf, outputnode, [
1414+
('outputnode.skull_stripped_file', 'out_reference_brain'),
1415+
('outputnode.mask_file', 'out_mask'),
1416+
('outputnode.out_report', 'out_mask_report')]),
1417+
(syn_rpt, outputnode, [('out_report', 'out_warp_report')])])
14871418

14881419
return workflow
14891420

0 commit comments

Comments
 (0)