diff --git a/smriprep/workflows/anatomical.py b/smriprep/workflows/anatomical.py index f879b2ea7e..78e19ad8bd 100644 --- a/smriprep/workflows/anatomical.py +++ b/smriprep/workflows/anatomical.py @@ -61,10 +61,10 @@ from .fit.registration import init_register_template_wf from .outputs import ( init_anat_reports_wf, - init_anat_second_derivatives_wf, init_ds_anat_volumes_wf, init_ds_dseg_wf, init_ds_fs_registration_wf, + init_ds_fs_segs_wf, init_ds_grayord_metrics_wf, init_ds_mask_wf, init_ds_surface_metrics_wf, @@ -287,7 +287,6 @@ def init_anat_preproc_wf( ds_std_volumes_wf = init_ds_anat_volumes_wf( bids_root=bids_root, output_dir=output_dir, - name='ds_std_volumes_wf', ) workflow.connect([ @@ -320,10 +319,10 @@ def init_anat_preproc_wf( ]), (anat_fit_wf, ds_std_volumes_wf, [ ('outputnode.t1w_valid_list', 'inputnode.source_files'), - ('outputnode.t1w_preproc', 'inputnode.t1w_preproc'), - ('outputnode.t1w_mask', 'inputnode.t1w_mask'), - ('outputnode.t1w_dseg', 'inputnode.t1w_dseg'), - ('outputnode.t1w_tpms', 'inputnode.t1w_tpms'), + ('outputnode.t1w_preproc', 'inputnode.anat_preproc'), + ('outputnode.t1w_mask', 'inputnode.anat_mask'), + ('outputnode.t1w_dseg', 'inputnode.anat_dseg'), + ('outputnode.t1w_tpms', 'inputnode.anat_tpms'), ]), (template_iterator_wf, ds_std_volumes_wf, [ ('outputnode.std_t1w', 'inputnode.ref_file'), @@ -335,17 +334,12 @@ def init_anat_preproc_wf( ]) # fmt:skip if freesurfer: - anat_second_derivatives_wf = init_anat_second_derivatives_wf( + ds_fs_segs_wf = init_ds_fs_segs_wf( bids_root=bids_root, output_dir=output_dir, - cifti_output=cifti_output, - ) - surface_derivatives_wf = init_surface_derivatives_wf( - cifti_output=cifti_output, - ) - ds_surfaces_wf = init_ds_surfaces_wf( - bids_root=bids_root, output_dir=output_dir, surfaces=['inflated'] ) + surface_derivatives_wf = init_surface_derivatives_wf() + ds_surfaces_wf = init_ds_surfaces_wf(output_dir=output_dir, surfaces=['inflated']) ds_curv_wf = init_ds_surface_metrics_wf( bids_root=bids_root, output_dir=output_dir, metrics=['curv'], name='ds_curv_wf' ) @@ -355,7 +349,7 @@ def init_anat_preproc_wf( ('outputnode.t1w_preproc', 'inputnode.reference'), ('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id'), - ('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), + ('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2anat_xfm'), ]), (anat_fit_wf, ds_surfaces_wf, [ ('outputnode.t1w_valid_list', 'inputnode.source_files'), @@ -369,12 +363,12 @@ def init_anat_preproc_wf( (surface_derivatives_wf, ds_curv_wf, [ ('outputnode.curv', 'inputnode.curv'), ]), - (anat_fit_wf, anat_second_derivatives_wf, [ + (anat_fit_wf, ds_fs_segs_wf, [ ('outputnode.t1w_valid_list', 'inputnode.source_files'), ]), - (surface_derivatives_wf, anat_second_derivatives_wf, [ - ('outputnode.out_aseg', 'inputnode.t1w_fs_aseg'), - ('outputnode.out_aparc', 'inputnode.t1w_fs_aparc'), + (surface_derivatives_wf, ds_fs_segs_wf, [ + ('outputnode.out_aseg', 'inputnode.anat_fs_aseg'), + ('outputnode.out_aparc', 'inputnode.anat_fs_aparc'), ]), (surface_derivatives_wf, outputnode, [ ('outputnode.out_aseg', 't1w_aseg'), @@ -765,10 +759,12 @@ def init_anat_fit_wf( longitudinal=longitudinal, omp_nthreads=omp_nthreads, num_files=num_t1w, - contrast='T1w', + image_type='T1w', name='anat_template_wf', ) - ds_template_wf = init_ds_template_wf(output_dir=output_dir, num_t1w=num_t1w) + ds_template_wf = init_ds_template_wf( + output_dir=output_dir, num_anat=num_t1w, image_type='T1w' + ) # fmt:off workflow.connect([ @@ -781,11 +777,11 @@ def init_anat_fit_wf( ('outputnode.out_report', 'inputnode.t1w_conform_report'), ]), (anat_template_wf, ds_template_wf, [ - ('outputnode.anat_realign_xfm', 'inputnode.t1w_ref_xfms'), + ('outputnode.anat_realign_xfm', 'inputnode.anat_ref_xfms'), ]), (sourcefile_buffer, ds_template_wf, [('source_files', 'inputnode.source_files')]), - (t1w_buffer, ds_template_wf, [('t1w_preproc', 'inputnode.t1w_preproc')]), - (ds_template_wf, outputnode, [('outputnode.t1w_preproc', 't1w_preproc')]), + (t1w_buffer, ds_template_wf, [('t1w_preproc', 'inputnode.anat_preproc')]), + (ds_template_wf, outputnode, [('outputnode.anat_preproc', 't1w_preproc')]), ]) # fmt:on else: @@ -954,16 +950,16 @@ def init_anat_fit_wf( workflow.connect([ (fast, lut_t1w_dseg, [('partial_volume_map', 'in_dseg')]), (sourcefile_buffer, ds_dseg_wf, [('source_files', 'inputnode.source_files')]), - (lut_t1w_dseg, ds_dseg_wf, [('out', 'inputnode.t1w_dseg')]), - (ds_dseg_wf, seg_buffer, [('outputnode.t1w_dseg', 't1w_dseg')]), + (lut_t1w_dseg, ds_dseg_wf, [('out', 'inputnode.anat_dseg')]), + (ds_dseg_wf, seg_buffer, [('outputnode.anat_dseg', 't1w_dseg')]), ]) if not have_tpms: ds_tpms_wf = init_ds_tpms_wf(output_dir=output_dir) workflow.connect([ (fast, fast2bids, [('partial_volume_files', 'inlist')]), (sourcefile_buffer, ds_tpms_wf, [('source_files', 'inputnode.source_files')]), - (fast2bids, ds_tpms_wf, [('out', 'inputnode.t1w_tpms')]), - (ds_tpms_wf, seg_buffer, [('outputnode.t1w_tpms', 't1w_tpms')]), + (fast2bids, ds_tpms_wf, [('out', 'inputnode.anat_tpms')]), + (ds_tpms_wf, seg_buffer, [('outputnode.anat_tpms', 't1w_tpms')]), ]) # fmt:on else: @@ -998,7 +994,9 @@ def init_anat_fit_wf( omp_nthreads=omp_nthreads, templates=templates, ) - ds_template_registration_wf = init_ds_template_registration_wf(output_dir=output_dir) + ds_template_registration_wf = init_ds_template_registration_wf( + output_dir=output_dir, image_type='T1w' + ) # fmt:off workflow.connect([ @@ -1081,17 +1079,17 @@ def init_anat_fit_wf( fsnative_xfms = precomputed.get('transforms', {}).get('fsnative') if not fsnative_xfms: - ds_fs_registration_wf = init_ds_fs_registration_wf(output_dir=output_dir) + ds_fs_registration_wf = init_ds_fs_registration_wf(output_dir=output_dir, image_type='T1w') # fmt:off workflow.connect([ (sourcefile_buffer, ds_fs_registration_wf, [ ('source_files', 'inputnode.source_files'), ]), (surface_recon_wf, ds_fs_registration_wf, [ - ('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), + ('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2anat_xfm'), ]), (ds_fs_registration_wf, outputnode, [ - ('outputnode.fsnative2t1w_xfm', 'fsnative2t1w_xfm'), + ('outputnode.fsnative2anat_xfm', 'fsnative2t1w_xfm'), ]), ]) # fmt:on @@ -1114,7 +1112,7 @@ def init_anat_fit_wf( (surface_recon_wf, refinement_wf, [ ('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id'), - ('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), + ('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2anat_xfm'), ]), (t1w_buffer, refinement_wf, [ ('t1w_preproc', 'inputnode.reference_image'), @@ -1135,7 +1133,7 @@ def init_anat_fit_wf( longitudinal=longitudinal, omp_nthreads=omp_nthreads, num_files=len(t2w), - contrast='T2w', + image_type='T2w', name='t2w_template_wf', ) bbreg = pe.Node( @@ -1216,15 +1214,13 @@ def init_anat_fit_wf( LOGGER.info(f'ANAT Stage 8: Creating GIFTI surfaces for {surfs + spheres}') if surfs: gifti_surfaces_wf = init_gifti_surfaces_wf(surfaces=surfs) - ds_surfaces_wf = init_ds_surfaces_wf( - bids_root=bids_root, output_dir=output_dir, surfaces=surfs - ) + ds_surfaces_wf = init_ds_surfaces_wf(output_dir=output_dir, surfaces=surfs) # fmt:off workflow.connect([ (surface_recon_wf, gifti_surfaces_wf, [ ('outputnode.subject_id', 'inputnode.subject_id'), ('outputnode.subjects_dir', 'inputnode.subjects_dir'), - ('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), + ('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2anat_xfm'), ]), (gifti_surfaces_wf, surfaces_buffer, [ (f'outputnode.{surf}', surf) for surf in surfs @@ -1240,7 +1236,7 @@ def init_anat_fit_wf( surfaces=spheres, to_scanner=False, name='gifti_spheres_wf' ) ds_spheres_wf = init_ds_surfaces_wf( - bids_root=bids_root, output_dir=output_dir, surfaces=spheres, name='ds_spheres_wf' + output_dir=output_dir, surfaces=spheres, name='ds_spheres_wf' ) # fmt:off workflow.connect([ @@ -1316,7 +1312,6 @@ def init_anat_fit_wf( LOGGER.info('ANAT Stage 9: Creating fsLR registration sphere') fsLR_reg_wf = init_fsLR_reg_wf() ds_fsLR_reg_wf = init_ds_surfaces_wf( - bids_root=bids_root, output_dir=output_dir, surfaces=['sphere_reg_fsLR'], name='ds_fsLR_reg_wf', @@ -1341,7 +1336,6 @@ def init_anat_fit_wf( LOGGER.info('ANAT Stage 10: Creating MSM-Sulc registration sphere') msm_sulc_wf = init_msm_sulc_wf(sloppy=sloppy) ds_msmsulc_wf = init_ds_surfaces_wf( - bids_root=bids_root, output_dir=output_dir, surfaces=['sphere_reg_msm'], name='ds_msmsulc_wf', @@ -1375,7 +1369,7 @@ def init_anat_template_wf( longitudinal: bool, omp_nthreads: int, num_files: int, - contrast: str, + image_type: ty.Literal['T1w', 'T2w'], name: str = 'anat_template_wf', ): """ @@ -1388,7 +1382,7 @@ def init_anat_template_wf( from smriprep.workflows.anatomical import init_anat_template_wf wf = init_anat_template_wf( - longitudinal=False, omp_nthreads=1, num_files=1, contrast="T1w" + longitudinal=False, omp_nthreads=1, num_files=1, image_type="T1w" ) Parameters @@ -1400,8 +1394,8 @@ def init_anat_template_wf( Maximum number of threads an individual process may use num_files : :obj:`int` Number of images - contrast : :obj:`str` - Name of contrast, for reporting purposes, e.g., T1w, T2w, PDw + image_type : :obj:`str` + MR image type (T1w, T2w, etc.) name : :obj:`str`, optional Workflow name (default: anat_template_wf) @@ -1427,8 +1421,8 @@ def init_anat_template_wf( if num_files > 1: fs_ver = fs.Info().looseversion() or '(version unknown)' workflow.__desc__ = f"""\ -An anatomical {contrast}-reference map was computed after registration of -{num_files} {contrast} images (after INU-correction) using +An anatomical {image_type}-reference map was computed after registration of +{num_files} {image} images (after INU-correction) using `mri_robust_template` [FreeSurfer {fs_ver}, @fs_template]. """ diff --git a/smriprep/workflows/outputs.py b/smriprep/workflows/outputs.py index 4a8764a6dd..1bb50f5ade 100644 --- a/smriprep/workflows/outputs.py +++ b/smriprep/workflows/outputs.py @@ -230,36 +230,39 @@ def init_anat_reports_wf(*, spaces, freesurfer, output_dir, sloppy=False, name=' def init_ds_template_wf( *, - num_t1w, - output_dir, - name='ds_template_wf', + num_anat: int, + output_dir: str, + image_type: ty.Literal['T1w', 'T2w'], + name: str = 'ds_template_wf', ): """ Save the subject-specific template Parameters ---------- - num_t1w : :obj:`int` - Number of T1w images + num_anat : :obj:`int` + Number of anatomical images output_dir : :obj:`str` Directory in which to save derivatives + image_type + MR image type (T1w, T2w, etc.) name : :obj:`str` Workflow name (default: ds_template_wf) Inputs ------ source_files - List of input T1w images - t1w_ref_xfms - List of affine transforms to realign input T1w images - t1w_preproc - The T1w reference map, which is calculated as the average of bias-corrected - and preprocessed T1w images, defining the anatomical space. + List of input anatomical images + anat_ref_xfms + List of affine transforms to realign input anatomical images + anat_preproc + The anatomical reference map, which is calculated as the average of bias-corrected + and preprocessed anatomical images, defining the anatomical space. Outputs ------- - t1w_preproc - The location in the output directory of the preprocessed T1w image + anat_preproc + The location in the output directory of the preprocessed anatomical image """ workflow = Workflow(name=name) @@ -268,49 +271,49 @@ def init_ds_template_wf( niu.IdentityInterface( fields=[ 'source_files', - 't1w_ref_xfms', - 't1w_preproc', + 'anat_ref_xfms', + 'anat_preproc', ] ), name='inputnode', ) - outputnode = pe.Node(niu.IdentityInterface(fields=['t1w_preproc']), name='outputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['anat_preproc']), name='outputnode') - ds_t1w_preproc = pe.Node( + ds_anat_preproc = pe.Node( DerivativesDataSink(base_directory=output_dir, desc='preproc', compress=True), - name='ds_t1w_preproc', + name='ds_anat_preproc', run_without_submitting=True, ) - ds_t1w_preproc.inputs.SkullStripped = False + ds_anat_preproc.inputs.SkullStripped = False # fmt:off workflow.connect([ - (inputnode, ds_t1w_preproc, [('t1w_preproc', 'in_file'), + (inputnode, ds_anat_preproc, [('anat_preproc', 'in_file'), ('source_files', 'source_file')]), - (ds_t1w_preproc, outputnode, [('out_file', 't1w_preproc')]), + (ds_anat_preproc, outputnode, [('out_file', 'anat_preproc')]), ]) # fmt:on - if num_t1w > 1: + if num_anat > 1: # Please note the dictionary unpacking to provide the from argument. # It is necessary because from is a protected keyword (not allowed as argument name). - ds_t1w_ref_xfms = pe.MapNode( + ds_anat_ref_xfms = pe.MapNode( DerivativesDataSink( base_directory=output_dir, - to='T1w', + to=image_type, mode='image', suffix='xfm', extension='txt', **{'from': 'orig'}, ), iterfield=['source_file', 'in_file'], - name='ds_t1w_ref_xfms', + name='ds_anat_ref_xfms', run_without_submitting=True, ) # fmt:off workflow.connect([ - (inputnode, ds_t1w_ref_xfms, [('source_files', 'source_file'), - ('t1w_ref_xfms', 'in_file')]), + (inputnode, ds_anat_ref_xfms, [('source_files', 'source_file'), + ('anat_ref_xfms', 'in_file')]), ]) # fmt:on @@ -321,7 +324,8 @@ def init_ds_mask_wf( *, bids_root: str, output_dir: str, - mask_type: str, + mask_type: ty.Literal['brain', 'roi', 'ribbon'], + extra_entities: dict | None = None, name='ds_mask_wf', ): """ @@ -333,13 +337,15 @@ def init_ds_mask_wf( Root path of BIDS dataset output_dir : :obj:`str` Directory in which to save derivatives + extra_entities : :obj:`dict` or None + Additional entities to add to filename name : :obj:`str` Workflow name (default: ds_mask_wf) Inputs ------ source_files - List of input T1w images + List of input anat images mask_file Mask to save @@ -360,14 +366,17 @@ def init_ds_mask_wf( raw_sources = pe.Node(niu.Function(function=_bids_relative), name='raw_sources') raw_sources.inputs.bids_root = bids_root + extra_entities = extra_entities or {} + ds_mask = pe.Node( DerivativesDataSink( base_directory=output_dir, desc=mask_type, suffix='mask', compress=True, + **extra_entities, ), - name='ds_t1w_mask', + name='ds_anat_mask', run_without_submitting=True, ) if mask_type == 'brain': @@ -388,7 +397,12 @@ def init_ds_mask_wf( return workflow -def init_ds_dseg_wf(*, output_dir, name='ds_dseg_wf'): +def init_ds_dseg_wf( + *, + output_dir: str, + extra_entities: dict | None = None, + name: str = 'ds_dseg_wf', +): """ Save discrete segmentations @@ -396,53 +410,64 @@ def init_ds_dseg_wf(*, output_dir, name='ds_dseg_wf'): ---------- output_dir : :obj:`str` Directory in which to save derivatives + extra_entities : :obj:`dict` or None + Additional entities to add to filename name : :obj:`str` Workflow name (default: ds_dseg_wf) Inputs ------ source_files - List of input T1w images - t1w_dseg - Segmentation in T1w space + List of input anatomical images + anat_dseg + Segmentation in anatomical space Outputs ------- - t1w_dseg + anat_dseg The location in the output directory of the discrete segmentation """ workflow = Workflow(name=name) inputnode = pe.Node( - niu.IdentityInterface(fields=['source_files', 't1w_dseg']), + niu.IdentityInterface(fields=['source_files', 'anat_dseg']), name='inputnode', ) - outputnode = pe.Node(niu.IdentityInterface(fields=['t1w_dseg']), name='outputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['anat_dseg']), name='outputnode') - ds_t1w_dseg = pe.Node( + extra_entities = extra_entities or {} + + ds_anat_dseg = pe.Node( DerivativesDataSink( base_directory=output_dir, suffix='dseg', compress=True, dismiss_entities=['desc'], + **extra_entities, ), - name='ds_t1w_dseg', + name='ds_anat_dseg', run_without_submitting=True, ) # fmt:off workflow.connect([ - (inputnode, ds_t1w_dseg, [('t1w_dseg', 'in_file'), + (inputnode, ds_anat_dseg, [('anat_dseg', 'in_file'), ('source_files', 'source_file')]), - (ds_t1w_dseg, outputnode, [('out_file', 't1w_dseg')]), + (ds_anat_dseg, outputnode, [('out_file', 'anat_dseg')]), ]) # fmt:on return workflow -def init_ds_tpms_wf(*, output_dir, name='ds_tpms_wf', tpm_labels=BIDS_TISSUE_ORDER): +def init_ds_tpms_wf( + *, + output_dir: str, + extra_entities: dict | None = None, + name: str = 'ds_tpms_wf', + tpm_labels: tuple = BIDS_TISSUE_ORDER, +): """ Save tissue probability maps @@ -450,6 +475,8 @@ def init_ds_tpms_wf(*, output_dir, name='ds_tpms_wf', tpm_labels=BIDS_TISSUE_ORD ---------- output_dir : :obj:`str` Directory in which to save derivatives + extra_entities : :obj:`dict` or None + Additional entities to add to filename name : :obj:`str` Workflow name (default: anat_derivatives_wf) tpm_labels : :obj:`tuple` @@ -458,41 +485,44 @@ def init_ds_tpms_wf(*, output_dir, name='ds_tpms_wf', tpm_labels=BIDS_TISSUE_ORD Inputs ------ source_files - List of input T1w images - t1w_tpms - Tissue probability maps in T1w space + List of input anatomical images + anat_tpms + Tissue probability maps in anatomical space Outputs ------- - t1w_tpms + anat_tpms The location in the output directory of the tissue probability maps """ workflow = Workflow(name=name) inputnode = pe.Node( - niu.IdentityInterface(fields=['source_files', 't1w_tpms']), + niu.IdentityInterface(fields=['source_files', 'anat_tpms']), name='inputnode', ) - outputnode = pe.Node(niu.IdentityInterface(fields=['t1w_tpms']), name='outputnode') + outputnode = pe.Node(niu.IdentityInterface(fields=['anat_tpms']), name='outputnode') + + extra_entities = extra_entities or {} - ds_t1w_tpms = pe.Node( + ds_anat_tpms = pe.Node( DerivativesDataSink( base_directory=output_dir, suffix='probseg', compress=True, dismiss_entities=['desc'], + **extra_entities, ), - name='ds_t1w_tpms', + name='ds_anat_tpms', run_without_submitting=True, ) - ds_t1w_tpms.inputs.label = tpm_labels + ds_anat_tpms.inputs.label = tpm_labels # fmt:off workflow.connect([ - (inputnode, ds_t1w_tpms, [('t1w_tpms', 'in_file'), + (inputnode, ds_anat_tpms, [('anat_tpms', 'in_file'), ('source_files', 'source_file')]), - (ds_t1w_tpms, outputnode, [('out_file', 't1w_tpms')]), + (ds_anat_tpms, outputnode, [('out_file', 'anat_tpms')]), ]) # fmt:on @@ -501,8 +531,9 @@ def init_ds_tpms_wf(*, output_dir, name='ds_tpms_wf', tpm_labels=BIDS_TISSUE_ORD def init_ds_template_registration_wf( *, - output_dir, - name='ds_template_registration_wf', + output_dir: str, + image_type: ty.Literal['T1w', 'T2w'], + name: str = 'ds_template_registration_wf', ): """ Save template registration transforms @@ -511,6 +542,8 @@ def init_ds_template_registration_wf( ---------- output_dir : :obj:`str` Directory in which to save derivatives + image_type : :obj:`str` + Anatomical image type (T1w, T2w, etc) name : :obj:`str` Workflow name (default: anat_derivatives_wf) @@ -519,7 +552,7 @@ def init_ds_template_registration_wf( template Template space and specifications source_files - List of input T1w images + List of input anatomical images anat2std_xfm Nonlinear spatial transform to resample imaging data given in anatomical space into standard space. @@ -538,44 +571,44 @@ def init_ds_template_registration_wf( name='outputnode', ) - ds_std2t1w_xfm = pe.MapNode( + ds_std2anat_xfm = pe.MapNode( DerivativesDataSink( base_directory=output_dir, - to='T1w', + to=image_type, mode='image', suffix='xfm', dismiss_entities=['desc'], ), iterfield=('in_file', 'from'), - name='ds_std2t1w_xfm', + name='ds_std2anat_xfm', run_without_submitting=True, ) - ds_t1w2std_xfm = pe.MapNode( + ds_anat2std_xfm = pe.MapNode( DerivativesDataSink( base_directory=output_dir, mode='image', suffix='xfm', dismiss_entities=['desc'], - **{'from': 'T1w'}, + **{'from': image_type}, ), iterfield=('in_file', 'to'), - name='ds_t1w2std_xfm', + name='ds_anat2std_xfm', run_without_submitting=True, ) # fmt:off workflow.connect([ - (inputnode, ds_t1w2std_xfm, [ + (inputnode, ds_anat2std_xfm, [ ('anat2std_xfm', 'in_file'), (('template', _combine_cohort), 'to'), ('source_files', 'source_file')]), - (inputnode, ds_std2t1w_xfm, [ + (inputnode, ds_std2anat_xfm, [ ('std2anat_xfm', 'in_file'), (('template', _combine_cohort), 'from'), ('source_files', 'source_file')]), - (ds_t1w2std_xfm, outputnode, [('out_file', 'anat2std_xfm')]), - (ds_std2t1w_xfm, outputnode, [('out_file', 'std2anat_xfm')]), + (ds_anat2std_xfm, outputnode, [('out_file', 'anat2std_xfm')]), + (ds_std2anat_xfm, outputnode, [('out_file', 'std2anat_xfm')]), ]) # fmt:on @@ -584,11 +617,13 @@ def init_ds_template_registration_wf( def init_ds_fs_registration_wf( *, - output_dir, - name='ds_fs_registration_wf', + output_dir: str, + image_type: ty.Literal['T1w', 'T2w'], + name: str = 'ds_fs_registration_wf', ): """ - Save rigid registration between subject anatomical template and FreeSurfer T1.mgz + Save rigid registration between subject anatomical template and either + FreeSurfer T1.mgz or T2.mgz Parameters ---------- @@ -600,17 +635,17 @@ def init_ds_fs_registration_wf( Inputs ------ source_files - List of input T1w images - fsnative2t1w_xfm + List of input anatomical images + fsnative2anat_xfm LTA-style affine matrix translating from FreeSurfer-conformed - subject space to T1w + subject space to T1/T2 Outputs ------- - t1w2fsnative_xfm - LTA-style affine matrix translating from T1w to + anat2fsnative_xfm + LTA-style affine matrix translating from T1/T2 to FreeSurfer-conformed subject space - fsnative2t1w_xfm + fsnative2anat_xfm LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w @@ -618,11 +653,11 @@ def init_ds_fs_registration_wf( workflow = Workflow(name=name) inputnode = pe.Node( - niu.IdentityInterface(fields=['source_files', 'fsnative2t1w_xfm']), + niu.IdentityInterface(fields=['source_files', 'fsnative2anat_xfm']), name='inputnode', ) outputnode = pe.Node( - niu.IdentityInterface(fields=['fsnative2t1w_xfm', 't1w2fsnative_xfm']), + niu.IdentityInterface(fields=['fsnative2anat_xfm', 'anat2fsnative_xfm']), name='outputnode', ) @@ -630,40 +665,40 @@ def init_ds_fs_registration_wf( # FS native space transforms lta2itk = pe.Node(ConcatenateXFMs(inverse=True), name='lta2itk', run_without_submitting=True) - ds_t1w_fsnative = pe.Node( + ds_anat_fsnative = pe.Node( DerivativesDataSink( base_directory=output_dir, mode='image', to='fsnative', suffix='xfm', extension='txt', - **{'from': 'T1w'}, + **{'from': image_type}, ), - name='ds_t1w_fsnative', + name='ds_anat_fsnative', run_without_submitting=True, ) - ds_fsnative_t1w = pe.Node( + ds_fsnative_anat = pe.Node( DerivativesDataSink( base_directory=output_dir, mode='image', - to='T1w', + to=image_type, suffix='xfm', extension='txt', **{'from': 'fsnative'}, ), - name='ds_fsnative_t1w', + name='ds_fsnative_anat', run_without_submitting=True, ) # fmt:off workflow.connect([ - (inputnode, lta2itk, [('fsnative2t1w_xfm', 'in_xfms')]), - (inputnode, ds_t1w_fsnative, [('source_files', 'source_file')]), - (lta2itk, ds_t1w_fsnative, [('out_inv', 'in_file')]), - (inputnode, ds_fsnative_t1w, [('source_files', 'source_file')]), - (lta2itk, ds_fsnative_t1w, [('out_xfm', 'in_file')]), - (ds_fsnative_t1w, outputnode, [('out_file', 'fsnative2t1w_xfm')]), - (ds_t1w_fsnative, outputnode, [('out_file', 't1w2fsnative_xfm')]), + (inputnode, lta2itk, [('fsnative2anat_xfm', 'in_xfms')]), + (inputnode, ds_anat_fsnative, [('source_files', 'source_file')]), + (lta2itk, ds_anat_fsnative, [('out_inv', 'in_file')]), + (inputnode, ds_fsnative_anat, [('source_files', 'source_file')]), + (lta2itk, ds_fsnative_anat, [('out_xfm', 'in_file')]), + (ds_fsnative_anat, outputnode, [('out_file', 'fsnative2anat_xfm')]), + (ds_anat_fsnative, outputnode, [('out_file', 'anat2fsnative_xfm')]), ]) # fmt:on return workflow @@ -671,7 +706,6 @@ def init_ds_fs_registration_wf( def init_ds_surfaces_wf( *, - bids_root: str, output_dir: str, surfaces: list[str], name='ds_surfaces_wf', @@ -693,7 +727,7 @@ def init_ds_surfaces_wf( Inputs ------ source_files - List of input T1w images + List of input anatomical images ```` Left and right GIFTIs for each surface passed to ``surfaces`` @@ -727,6 +761,8 @@ def init_ds_surfaces_wf( ds_surf.inputs.space, ds_surf.inputs.desc = 'fsaverage', 'reg' # Default if surf == 'sphere_reg_fsLR': ds_surf.inputs.space = 'fsLR' + elif surf == 'sphere_reg_dhcpAsym': + ds_surf.inputs.space = 'dhcpAsym' elif surf == 'sphere_reg_msm': ds_surf.inputs.space, ds_surf.inputs.desc = 'fsLR', 'msmsulc' @@ -891,13 +927,13 @@ def init_ds_anat_volumes_wf( inputnode = pe.Node( niu.IdentityInterface( fields=[ - # Original T1w image + # Original anat image 'source_files', - # T1w-space images - 't1w_preproc', - 't1w_mask', - 't1w_dseg', - 't1w_tpms', + # anat-space images + 'anat_preproc', + 'anat_mask', + 'anat_dseg', + 'anat_tpms', # Template 'ref_file', 'anat2std_xfm', @@ -916,7 +952,7 @@ def init_ds_anat_volumes_wf( gen_ref = pe.Node(GenerateSamplingReference(), name='gen_ref', mem_gb=0.01) # Mask T1w preproc images - mask_t1w = pe.Node(ApplyMask(), name='mask_t1w') + mask_anat = pe.Node(ApplyMask(), name='mask_anat') # Resample T1w-space inputs anat2std_t1w = pe.Node( @@ -977,15 +1013,15 @@ def init_ds_anat_volumes_wf( ('ref_file', 'fixed_image'), (('resolution', _is_native), 'keep_native'), ]), - (inputnode, mask_t1w, [ - ('t1w_preproc', 'in_file'), - ('t1w_mask', 'in_mask'), + (inputnode, mask_anat, [ + ('anat_preproc', 'in_file'), + ('anat_mask', 'in_mask'), ]), - (mask_t1w, anat2std_t1w, [('out_file', 'input_image')]), - (inputnode, anat2std_mask, [('t1w_mask', 'input_image')]), - (inputnode, anat2std_dseg, [('t1w_dseg', 'input_image')]), - (inputnode, anat2std_tpms, [('t1w_tpms', 'input_image')]), - (inputnode, gen_ref, [('t1w_preproc', 'moving_image')]), + (mask_anat, anat2std_t1w, [('out_file', 'input_image')]), + (inputnode, anat2std_mask, [('anat_mask', 'input_image')]), + (inputnode, anat2std_dseg, [('anat_dseg', 'input_image')]), + (inputnode, anat2std_tpms, [('anat_tpms', 'input_image')]), + (inputnode, gen_ref, [('anat_preproc', 'moving_image')]), (anat2std_t1w, ds_std_t1w, [('output_image', 'in_file')]), (anat2std_mask, ds_std_mask, [('output_image', 'in_file')]), (anat2std_dseg, ds_std_dseg, [('output_image', 'in_file')]), @@ -1016,13 +1052,12 @@ def init_ds_anat_volumes_wf( return workflow -def init_anat_second_derivatives_wf( +def init_ds_fs_segs_wf( *, bids_root: str, output_dir: str, - cifti_output: ty.Literal['91k', '170k', False], - name='anat_second_derivatives_wf', - tpm_labels=BIDS_TISSUE_ORDER, + extra_entities: dict | None = None, + name='ds_fs_segs_wf', ): """ Set up a battery of datasinks to store derivatives in the right location. @@ -1033,52 +1068,28 @@ def init_anat_second_derivatives_wf( Root path of BIDS dataset output_dir : :obj:`str` Directory in which to save derivatives + extra_entities : :obj:`dict` or None + Additional entities to add to filename name : :obj:`str` - Workflow name (default: anat_derivatives_wf) - tpm_labels : :obj:`tuple` - Tissue probability maps in order + Workflow name (default: ds_anat_segs_wf) Inputs ------ - template - Template space and specifications + anat_fs_aparc + FreeSurfer's aparc+aseg segmentation, in native anatomical space + anat_fs_aseg + FreeSurfer's aseg segmentation, in native anatomical space source_files - List of input T1w images - t1w_preproc - The T1w reference map, which is calculated as the average of bias-corrected - and preprocessed T1w images, defining the anatomical space. - t1w_mask - Mask of the ``t1w_preproc`` - t1w_dseg - Segmentation in T1w space - t1w_tpms - Tissue probability maps in T1w space - anat2std_xfm - Nonlinear spatial transform to resample imaging data given in anatomical space - into standard space. - surfaces - GIFTI surfaces (gray/white boundary, midthickness, pial, inflated) - morphometrics - GIFTIs of cortical thickness, curvature, and sulcal depth - t1w_fs_aseg - FreeSurfer's aseg segmentation, in native T1w space - t1w_fs_aparc - FreeSurfer's aparc+aseg segmentation, in native T1w space - cifti_morph - Morphometric CIFTI-2 dscalar files - cifti_metadata - JSON files containing metadata dictionaries - + List of input anatomical images """ workflow = Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface( fields=[ - 'template', 'source_files', - 't1w_fs_aseg', - 't1w_fs_aparc', + 'anat_fs_aseg', + 'anat_fs_aparc', ] ), name='inputnode', @@ -1087,28 +1098,38 @@ def init_anat_second_derivatives_wf( raw_sources = pe.Node(niu.Function(function=_bids_relative), name='raw_sources') raw_sources.inputs.bids_root = bids_root + extra_entities = extra_entities or {} + # Parcellations - ds_t1w_fsaseg = pe.Node( - DerivativesDataSink(base_directory=output_dir, desc='aseg', suffix='dseg', compress=True), - name='ds_t1w_fsaseg', + ds_anat_fsaseg = pe.Node( + DerivativesDataSink( + base_directory=output_dir, + desc='aseg', + suffix='dseg', + compress=True, + **extra_entities, + ), + name='ds_anat_fsaseg', run_without_submitting=True, ) - ds_t1w_fsparc = pe.Node( + ds_anat_fsparc = pe.Node( DerivativesDataSink( - base_directory=output_dir, desc='aparcaseg', suffix='dseg', compress=True + base_directory=output_dir, + desc='aparcaseg', + suffix='dseg', + compress=True, + **extra_entities, ), - name='ds_t1w_fsparc', + name='ds_anat_fsparc', run_without_submitting=True, ) - # fmt:off workflow.connect([ - (inputnode, ds_t1w_fsaseg, [('t1w_fs_aseg', 'in_file'), - ('source_files', 'source_file')]), - (inputnode, ds_t1w_fsparc, [('t1w_fs_aparc', 'in_file'), - ('source_files', 'source_file')]), - ]) - # fmt:on + (inputnode, ds_anat_fsaseg, [('anat_fs_aseg', 'in_file'), + ('source_files', 'source_file')]), + (inputnode, ds_anat_fsparc, [('anat_fs_aparc', 'in_file'), + ('source_files', 'source_file')]), + ]) # fmt:skip return workflow diff --git a/smriprep/workflows/surfaces.py b/smriprep/workflows/surfaces.py index 12e78c501a..c2a43b4f73 100644 --- a/smriprep/workflows/surfaces.py +++ b/smriprep/workflows/surfaces.py @@ -43,6 +43,7 @@ ) from niworkflows.interfaces.freesurfer import PatchedRobustRegister as RobustRegister from niworkflows.interfaces.nitransforms import ConcatenateXFMs +from niworkflows.interfaces.patches import FreeSurferSource from niworkflows.interfaces.utility import KeySelect from niworkflows.interfaces.workbench import ( MetricDilate, @@ -228,7 +229,7 @@ def init_surface_recon_wf( autorecon_resume_wf = init_autorecon_resume_wf(omp_nthreads=omp_nthreads) - get_surfaces = pe.Node(nio.FreeSurferSource(), name='get_surfaces') + get_surfaces = pe.Node(FreeSurferSource(), name='get_surfaces') midthickness = pe.MapNode( MakeMidthickness(thickness=True, distance=0.5, out_name='midthickness'), @@ -284,7 +285,7 @@ def init_surface_recon_wf( ]) # fmt:skip else: # Pretend to be the autorecon1 node so fsnative2t1w_xfm gets run ASAP - fs_base_inputs = autorecon1 = pe.Node(nio.FreeSurferSource(), name='fs_base_inputs') + fs_base_inputs = autorecon1 = pe.Node(FreeSurferSource(), name='fs_base_inputs') workflow.connect([ (inputnode, fs_base_inputs, [ @@ -328,7 +329,9 @@ def init_surface_recon_wf( return workflow -def init_refinement_wf(*, name='refinement_wf'): +def init_refinement_wf( + *, image_type: ty.Literal['T1w', 'T2w'] = 'T1w', name: str = 'refinement_wf' +) -> Workflow: r""" Refine ANTs brain extraction with FreeSurfer segmentation @@ -346,20 +349,12 @@ def init_refinement_wf(*, name='refinement_wf'): FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID - fsnative2t1w_xfm - LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w + fsnative2anat_xfm + LTA-style affine matrix translating from FreeSurfer-conformed subject space to anatomical reference_image Input - t2w - List of T2-weighted structural images (only first used) - flair - List of FLAIR images - skullstripped_t1 - Skull-stripped T1-weighted image (or mask of image) ants_segs Brain tissue segmentation from ANTS ``antsBrainExtraction.sh`` - corrected_t1 - INU-corrected, merged T1-weighted image subjects_dir FreeSurfer SUBJECTS_DIR subject_id @@ -367,14 +362,6 @@ def init_refinement_wf(*, name='refinement_wf'): Outputs ------- - subjects_dir - FreeSurfer SUBJECTS_DIR - subject_id - FreeSurfer subject ID - t1w2fsnative_xfm - LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space - fsnative2t1w_xfm - LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w out_brainmask Refined brainmask, derived from FreeSurfer's ``aseg`` volume @@ -397,7 +384,7 @@ def init_refinement_wf(*, name='refinement_wf'): fields=[ 'reference_image', 'ants_segs', - 'fsnative2t1w_xfm', + 'fsnative2anat_xfm', 'subjects_dir', 'subject_id', ] @@ -413,24 +400,22 @@ def init_refinement_wf(*, name='refinement_wf'): name='outputnode', ) - aseg_to_native_wf = init_segs_to_native_wf() + aseg_to_native_wf = init_segs_to_native_wf(image_type=image_type) refine = pe.Node(RefineBrainMask(), name='refine') - # fmt:off workflow.connect([ # Refine ANTs mask, deriving new mask from FS' aseg (inputnode, aseg_to_native_wf, [ ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('reference_image', 'inputnode.in_file'), - ('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), + ('fsnative2anat_xfm', 'inputnode.fsnative2anat_xfm'), ]), (inputnode, refine, [('reference_image', 'in_anat'), ('ants_segs', 'in_ants')]), (aseg_to_native_wf, refine, [('outputnode.out_file', 'in_aseg')]), (refine, outputnode, [('out_file', 'out_brainmask')]), - ]) - # fmt:on + ]) # fmt:skip return workflow @@ -610,8 +595,8 @@ def _dedup(in_list): def init_surface_derivatives_wf( *, - cifti_output: ty.Literal['91k', '170k', False] = False, - name='surface_derivatives_wf', + image_type: ty.Literal['T1w', 'T2w'] = 'T1w', + name: str = 'surface_derivatives_wf', ): r""" Generate sMRIPrep derivatives from FreeSurfer derivatives @@ -627,9 +612,9 @@ def init_surface_derivatives_wf( Inputs ------ reference - Reference image in native T1w space, for defining a resampling grid - fsnative2t1w_xfm - LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w + Reference image in native anatomical space, for defining a resampling grid + fsnative2anat_xfm + LTA-style affine matrix translating from FreeSurfer-conformed subject space to anatomical subjects_dir FreeSurfer SUBJECTS_DIR subject_id @@ -643,9 +628,9 @@ def init_surface_derivatives_wf( morphometrics GIFTIs of cortical thickness, curvature, and sulcal depth out_aseg - FreeSurfer's aseg segmentation, in native T1w space + FreeSurfer's aseg segmentation, in native anatomical space out_aparc - FreeSurfer's aparc+aseg segmentation, in native T1w space + FreeSurfer's aparc+aseg segmentation, in native anatomical space See also -------- @@ -659,7 +644,7 @@ def init_surface_derivatives_wf( fields=[ 'subjects_dir', 'subject_id', - 'fsnative2t1w_xfm', + 'fsnative2anat_xfm', 'reference', ] ), @@ -679,8 +664,8 @@ def init_surface_derivatives_wf( gifti_surfaces_wf = init_gifti_surfaces_wf(surfaces=['inflated']) gifti_morph_wf = init_gifti_morphometrics_wf(morphometrics=['curv']) - aseg_to_native_wf = init_segs_to_native_wf() - aparc_to_native_wf = init_segs_to_native_wf(segmentation='aparc_aseg') + aseg_to_native_wf = init_segs_to_native_wf(image_type=image_type) + aparc_to_native_wf = init_segs_to_native_wf(image_type=image_type, segmentation='aparc_aseg') # fmt:off workflow.connect([ @@ -688,7 +673,7 @@ def init_surface_derivatives_wf( (inputnode, gifti_surfaces_wf, [ ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), - ('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), + ('fsnative2anat_xfm', 'inputnode.fsnative2anat_xfm'), ]), (inputnode, gifti_morph_wf, [ ('subjects_dir', 'inputnode.subjects_dir'), @@ -698,13 +683,13 @@ def init_surface_derivatives_wf( ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('reference', 'inputnode.in_file'), - ('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), + ('fsnative2anat_xfm', 'inputnode.fsnative2anat_xfm'), ]), (inputnode, aparc_to_native_wf, [ ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('reference', 'inputnode.in_file'), - ('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), + ('fsnative2anat_xfm', 'inputnode.fsnative2anat_xfm'), ]), # Output @@ -863,8 +848,8 @@ def init_gifti_surfaces_wf( ``lh/rh.inflated``, and ``lh/rh.white``. Vertex coordinates are :py:class:`transformed - ` to align with native T1w space - when ``fsnative2t1w_xfm`` is provided. + ` to align with native anatomical space + when ``fsnative2anat_xfm`` is provided. Workflow Graph .. workflow:: @@ -880,7 +865,7 @@ def init_gifti_surfaces_wf( FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID - fsnative2t1w_xfm + fsnative2anat_xfm LTA formatted affine transform file Outputs @@ -896,7 +881,7 @@ def init_gifti_surfaces_wf( workflow = Workflow(name=name) inputnode = pe.Node( - niu.IdentityInterface(['subjects_dir', 'subject_id', 'fsnative2t1w_xfm']), + niu.IdentityInterface(['subjects_dir', 'subject_id', 'fsnative2anat_xfm']), name='inputnode', ) outputnode = pe.Node(niu.IdentityInterface(['surfaces', *surfaces]), name='outputnode') @@ -931,7 +916,7 @@ def init_gifti_surfaces_wf( ('subject_id', 'subject_id'), ]), (inputnode, fix_surfs, [ - ('fsnative2t1w_xfm', 'transform_file'), + ('fsnative2anat_xfm', 'transform_file'), ]), (get_surfaces, surface_list, [ (surf, f'in{i}') for i, surf in enumerate(surfaces, start=1) @@ -973,8 +958,6 @@ def init_gifti_morphometrics_wf( FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID - fsnative2t1w_xfm - LTA formatted affine transform file (inverse) Outputs ------- @@ -1002,7 +985,7 @@ def init_gifti_morphometrics_wf( name='outputnode', ) - get_subject = pe.Node(nio.FreeSurferSource(), name='get_surfaces') + get_subject = pe.Node(FreeSurferSource(), name='get_surfaces') morphometry_list = pe.Node( niu.Merge(len(morphometrics), ravel_inputs=True), @@ -1184,7 +1167,12 @@ def init_hcp_morphometrics_wf( return workflow -def init_segs_to_native_wf(*, name='segs_to_native', segmentation='aseg'): +def init_segs_to_native_wf( + *, + image_type: ty.Literal['T1w', 'T2w'] = 'T1w', + segmentation: ty.Literal['aseg', 'aparc_aseg', 'wmparc'] = 'aseg', + name: str = 'segs_to_native_wf', +) -> Workflow: """ Get a segmentation from FreeSurfer conformed space into native T1w space. @@ -1198,19 +1186,21 @@ def init_segs_to_native_wf(*, name='segs_to_native', segmentation='aseg'): Parameters ---------- + image_type + MR anatomical image type ('T1w' or 'T2w') segmentation The name of a segmentation ('aseg' or 'aparc_aseg' or 'wmparc') Inputs ------ in_file - Anatomical, merged T1w image after INU correction + Anatomical, merged anatomical image after INU correction subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID - fsnative2t1w_xfm - LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w + fsnative2anat_xfm + LTA-style affine matrix translating from FreeSurfer-conformed subject space to anatomical Outputs ------- @@ -1220,16 +1210,16 @@ def init_segs_to_native_wf(*, name='segs_to_native', segmentation='aseg'): """ workflow = Workflow(name=f'{name}_{segmentation}') inputnode = pe.Node( - niu.IdentityInterface(['in_file', 'subjects_dir', 'subject_id', 'fsnative2t1w_xfm']), + niu.IdentityInterface(['in_file', 'subjects_dir', 'subject_id', 'fsnative2anat_xfm']), name='inputnode', ) outputnode = pe.Node(niu.IdentityInterface(['out_file']), name='outputnode') # Extract the aseg and aparc+aseg outputs - fssource = pe.Node(nio.FreeSurferSource(), name='fs_datasource') + fssource = pe.Node(FreeSurferSource(), name='fs_datasource') lta = pe.Node(ConcatenateXFMs(out_fmt='fs'), name='lta', run_without_submitting=True) - # Resample from T1.mgz to T1w.nii.gz, applying any offset in fsnative2t1w_xfm, + # Resample from T1.mgz to T1w.nii.gz, applying any offset in fsnative2anat_xfm, # and convert to NIfTI while we're at it resample = pe.Node( fs.ApplyVolTransform(transformed_file='seg.nii.gz', interp='nearest'), @@ -1254,20 +1244,20 @@ def _sel(x): segmentation = (segmentation, _sel) - # fmt:off + anat = 'T2' if image_type == 'T2w' else 'T1' + workflow.connect([ (inputnode, fssource, [ ('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (inputnode, lta, [('in_file', 'reference'), - ('fsnative2t1w_xfm', 'in_xfms')]), - (fssource, lta, [('T1', 'moving')]), + ('fsnative2anat_xfm', 'in_xfms')]), + (fssource, lta, [(anat, 'moving')]), (inputnode, resample, [('in_file', 'target_file')]), (fssource, resample, [(segmentation, 'source_file')]), (lta, resample, [('out_xfm', 'lta_file')]), (resample, outputnode, [('transformed_file', 'out_file')]), - ]) - # fmt:on + ]) # fmt:skip return workflow