def init_anat_preproc_wf(bids_root, freesurfer, hires, longitudinal, omp_nthreads, output_dir, output_spaces, num_t1w, reportlets_dir, skull_strip_template, debug=False, name='anat_preproc_wf', skull_strip_fixed_seed=False): """ Stage the anatomical preprocessing steps of *sMRIPrep*. This includes: - T1w reference: realigning and then averaging T1w images. - Brain extraction and INU (bias field) correction. - Brain tissue segmentation. - Spatial normalization to standard spaces. - Surface reconstruction with FreeSurfer_. .. include:: ../links.rst Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from collections import OrderedDict from smriprep.workflows.anatomical import init_anat_preproc_wf wf = init_anat_preproc_wf( bids_root='.', freesurfer=True, hires=True, longitudinal=False, num_t1w=1, omp_nthreads=1, output_dir='.', output_spaces=OrderedDict([ ('MNI152NLin2009cAsym', {}), ('fsaverage5', {})]), reportlets_dir='.', skull_strip_template=('MNI152NLin2009cAsym', {}), ) Parameters ---------- bids_root : str Path of the input BIDS dataset root debug : bool Enable debugging outputs freesurfer : bool Enable FreeSurfer surface reconstruction (increases runtime by 6h, at the very least) output_spaces : list List of spatial normalization targets. Some parts of pipeline will only be instantiated for some output spaces. Valid spaces: - Any template identifier from TemplateFlow - Path to a template folder organized following TemplateFlow's conventions hires : bool Enable sub-millimeter preprocessing in FreeSurfer longitudinal : bool Create unbiased structural template, regardless of number of inputs (may increase runtime) name : str, optional Workflow name (default: anat_preproc_wf) omp_nthreads : int Maximum number of threads an individual process may use output_dir : str Directory in which to save derivatives reportlets_dir : str Directory in which to save reportlets skull_strip_fixed_seed : bool Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 (default: ``False``). skull_strip_template : tuple Name of ANTs skull-stripping template and specifications. Inputs ------ t1w List of T1-weighted structural images t2w List of T2-weighted structural images flair List of FLAIR images subjects_dir FreeSurfer SUBJECTS_DIR Outputs ------- t1w_preproc The T1w reference map, which is calculated as the average of bias-corrected and preprocessed T1w images, defining the anatomical space. t1w_brain Skull-stripped ``t1w_preproc`` t1w_mask Brain (binary) mask estimated by brain extraction. t1w_dseg Brain tissue segmentation of the preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF). t1w_tpms List of tissue probability maps corresponding to ``t1w_dseg``. std_t1w T1w reference resampled in one or more standard spaces. std_mask Mask of skull-stripped template, in MNI space std_dseg Segmentation, resampled into MNI space std_tpms List of tissue probability maps in MNI space subjects_dir FreeSurfer SUBJECTS_DIR anat2std_xfm Nonlinear spatial transform to resample imaging data given in anatomical space into standard space. std2anat_xfm Inverse transform of the above. 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 surfaces GIFTI surfaces (gray/white boundary, midthickness, pial, inflated) See also -------- * :py:func:`~niworkflows.anat.ants.init_brain_extraction_wf` * :py:func:`~smriprep.workflows.surfaces.init_surface_recon_wf` """ workflow = Workflow(name=name) desc = """Anatomical data preprocessing : """ desc += """\ A total of {num_t1w} T1-weighted (T1w) images were found within the input BIDS dataset. All of them were corrected for intensity non-uniformity (INU) """ if num_t1w > 1 else """\ The T1-weighted (T1w) image was corrected for intensity non-uniformity (INU) """ desc += """\ with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} \ [@ants, RRID:SCR_004757]""" desc += '.\n' if num_t1w > 1 else ", and used as T1w-reference throughout the workflow.\n" desc += """\ The T1w-reference was then skull-stripped with a *Nipype* implementation of the `antsBrainExtraction.sh` workflow (from ANTs), using {skullstrip_tpl} as target template. Brain tissue segmentation of cerebrospinal fluid (CSF), white-matter (WM) and gray-matter (GM) was performed on the brain-extracted T1w using `fast` [FSL {fsl_ver}, RRID:SCR_002823, @fsl_fast]. """ workflow.__desc__ = desc.format( ants_ver=ANTsInfo.version() or '(version unknown)', fsl_ver=fsl.FAST().version or '(version unknown)', num_t1w=num_t1w, skullstrip_tpl=skull_strip_template[0], ) inputnode = pe.Node(niu.IdentityInterface( fields=['t1w', 't2w', 'roi', 'flair', 'subjects_dir', 'subject_id']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 't1w_preproc', 't1w_brain', 't1w_mask', 't1w_dseg', 't1w_tpms', 'template', 'std_t1w', 'anat2std_xfm', 'std2anat_xfm', 'joint_template', 'joint_anat2std_xfm', 'joint_std2anat_xfm', 'std_mask', 'std_dseg', 'std_tpms', 't1w_realign_xfm', 'subjects_dir', 'subject_id', 't1w2fsnative_xfm', 'fsnative2t1w_xfm', 'surfaces', 't1w_aseg', 't1w_aparc' ]), name='outputnode') buffernode = pe.Node( niu.IdentityInterface(fields=['t1w_brain', 't1w_mask']), name='buffernode') # 1. Anatomical reference generation - average input T1w images. anat_template_wf = init_anat_template_wf(longitudinal=longitudinal, omp_nthreads=omp_nthreads, num_t1w=num_t1w) anat_validate = pe.Node(ValidateImage(), name='anat_validate', run_without_submitting=True) # 2. Brain-extraction and INU (bias field) correction. brain_extraction_wf = init_brain_extraction_wf( in_template=skull_strip_template[0], template_spec=skull_strip_template[1], atropos_use_random_seed=not skull_strip_fixed_seed, omp_nthreads=omp_nthreads, normalization_quality='precise' if not debug else 'testing') # 3. Brain tissue segmentation t1w_dseg = pe.Node(fsl.FAST(segments=True, no_bias=True, probability_maps=True), name='t1w_dseg', mem_gb=3) workflow.connect([ (buffernode, t1w_dseg, [('t1w_brain', 'in_files')]), (t1w_dseg, outputnode, [('tissue_class_map', 't1w_dseg'), ('probability_maps', 't1w_tpms')]), ]) # 4. Spatial normalization vol_spaces = [k for k in output_spaces.keys() if not k.startswith('fs')] anat_norm_wf = init_anat_norm_wf( debug=debug, omp_nthreads=omp_nthreads, templates=[(v, output_spaces[v]) for v in vol_spaces], ) workflow.connect([ # Step 1. (inputnode, anat_template_wf, [('t1w', 'inputnode.t1w')]), (anat_template_wf, anat_validate, [('outputnode.t1w_ref', 'in_file')]), (anat_validate, brain_extraction_wf, [('out_file', 'inputnode.in_files')]), (brain_extraction_wf, outputnode, [('outputnode.bias_corrected', 't1w_preproc')]), (anat_template_wf, outputnode, [('outputnode.t1w_realign_xfm', 't1w_ref_xfms')]), (buffernode, outputnode, [('t1w_brain', 't1w_brain'), ('t1w_mask', 't1w_mask')]), # Steps 2, 3 and 4 (inputnode, anat_norm_wf, [(('t1w', fix_multi_T1w_source_name), 'inputnode.orig_t1w'), ('roi', 'inputnode.lesion_mask')]), (brain_extraction_wf, anat_norm_wf, [(('outputnode.bias_corrected', _pop), 'inputnode.moving_image')]), (buffernode, anat_norm_wf, [('t1w_mask', 'inputnode.moving_mask')]), (t1w_dseg, anat_norm_wf, [('tissue_class_map', 'inputnode.moving_segmentation')]), (t1w_dseg, anat_norm_wf, [('probability_maps', 'inputnode.moving_tpms') ]), (anat_norm_wf, outputnode, [ ('poutputnode.standardized', 'std_t1w'), ('poutputnode.template', 'template'), ('poutputnode.anat2std_xfm', 'anat2std_xfm'), ('poutputnode.std2anat_xfm', 'std2anat_xfm'), ('poutputnode.std_mask', 'std_mask'), ('poutputnode.std_dseg', 'std_dseg'), ('poutputnode.std_tpms', 'std_tpms'), ('outputnode.template', 'joint_template'), ('outputnode.anat2std_xfm', 'joint_anat2std_xfm'), ('outputnode.std2anat_xfm', 'joint_std2anat_xfm'), ]), ]) # Write outputs ############################################3 anat_reports_wf = init_anat_reports_wf(reportlets_dir=reportlets_dir, freesurfer=freesurfer) anat_derivatives_wf = init_anat_derivatives_wf( bids_root=bids_root, freesurfer=freesurfer, num_t1w=num_t1w, output_dir=output_dir, ) workflow.connect([ # Connect reportlets (inputnode, anat_reports_wf, [(('t1w', fix_multi_T1w_source_name), 'inputnode.source_file')]), (anat_template_wf, anat_reports_wf, [('outputnode.out_report', 'inputnode.t1w_conform_report')]), (outputnode, anat_reports_wf, [('t1w_preproc', 'inputnode.t1w_preproc'), ('t1w_dseg', 'inputnode.t1w_dseg'), ('t1w_mask', 'inputnode.t1w_mask'), ('std_t1w', 'inputnode.std_t1w'), ('std_mask', 'inputnode.std_mask')]), (anat_norm_wf, anat_reports_wf, [('poutputnode.template', 'inputnode.template'), ('poutputnode.template_spec', 'inputnode.template_spec')]), # Connect derivatives (anat_template_wf, anat_derivatives_wf, [('outputnode.t1w_valid_list', 'inputnode.source_files')]), (anat_norm_wf, anat_derivatives_wf, [('poutputnode.template', 'inputnode.template')]), (outputnode, anat_derivatives_wf, [ ('std_t1w', 'inputnode.std_t1w'), ('anat2std_xfm', 'inputnode.anat2std_xfm'), ('std2anat_xfm', 'inputnode.std2anat_xfm'), ('t1w_ref_xfms', 'inputnode.t1w_ref_xfms'), ('t1w_preproc', 'inputnode.t1w_preproc'), ('t1w_mask', 'inputnode.t1w_mask'), ('t1w_dseg', 'inputnode.t1w_dseg'), ('t1w_tpms', 'inputnode.t1w_tpms'), ('std_mask', 'inputnode.std_mask'), ('std_dseg', 'inputnode.std_dseg'), ('std_tpms', 'inputnode.std_tpms'), ('t1w2fsnative_xfm', 'inputnode.t1w2fsnative_xfm'), ('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), ('surfaces', 'inputnode.surfaces'), ]), ]) if not freesurfer: # Flag --fs-no-reconall is set - return workflow.connect([ (brain_extraction_wf, buffernode, [(('outputnode.out_file', _pop), 't1w_brain'), ('outputnode.out_mask', 't1w_mask')]), ]) return workflow # 5. Surface reconstruction (--fs-no-reconall not set) surface_recon_wf = init_surface_recon_wf(name='surface_recon_wf', omp_nthreads=omp_nthreads, hires=hires) applyrefined = pe.Node(fsl.ApplyMask(), name='applyrefined') workflow.connect([ (inputnode, surface_recon_wf, [('t2w', 'inputnode.t2w'), ('flair', 'inputnode.flair'), ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id')]), (anat_validate, surface_recon_wf, [('out_file', 'inputnode.t1w')]), (brain_extraction_wf, surface_recon_wf, [(('outputnode.out_file', _pop), 'inputnode.skullstripped_t1'), ('outputnode.out_segm', 'inputnode.ants_segs'), (('outputnode.bias_corrected', _pop), 'inputnode.corrected_t1')]), (brain_extraction_wf, applyrefined, [(('outputnode.bias_corrected', _pop), 'in_file')]), (surface_recon_wf, applyrefined, [('outputnode.out_brainmask', 'mask_file')]), (surface_recon_wf, outputnode, [('outputnode.subjects_dir', 'subjects_dir'), ('outputnode.subject_id', 'subject_id'), ('outputnode.t1w2fsnative_xfm', 't1w2fsnative_xfm'), ('outputnode.fsnative2t1w_xfm', 'fsnative2t1w_xfm'), ('outputnode.surfaces', 'surfaces'), ('outputnode.out_aseg', 't1w_aseg'), ('outputnode.out_aparc', 't1w_aparc')]), (applyrefined, buffernode, [('out_file', 't1w_brain')]), (surface_recon_wf, buffernode, [('outputnode.out_brainmask', 't1w_mask')]), (surface_recon_wf, anat_reports_wf, [('outputnode.subject_id', 'inputnode.subject_id'), ('outputnode.subjects_dir', 'inputnode.subjects_dir')]), (surface_recon_wf, anat_derivatives_wf, [ ('outputnode.out_aseg', 'inputnode.t1w_fs_aseg'), ('outputnode.out_aparc', 'inputnode.t1w_fs_aparc'), ]), ]) return workflow
def init_anat_preproc_wf( workdir=None, freesurfer=False, no_compose_transforms=False, skull_strip_algorithm="ants", name="anat_preproc_wf", ): """ modified from smriprep/workflows/anatomical.py """ workflow = pe.Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=["t1w", "metadata"]), name="inputnode") buffernode = pe.Node( niu.IdentityInterface(fields=["t1w_brain", "t1w_mask"]), name="buffernode") outputnode = pe.Node( niu.IdentityInterface(fields=anat_preproc_wf_output_attrs, ), name="outputnode", ) skull_strip_template = Reference.from_string( config.workflow.skull_strip_template)[0] # Step 1 anat_validate = pe.Node(ValidateImage(), name="anat_validate", run_without_submitting=True) if skull_strip_algorithm == "none": brain_extraction_wf = init_n4_only_wf( omp_nthreads=config.nipype.omp_nthreads, atropos_use_random_seed=not config.workflow.skull_strip_fixed_seed, ) elif skull_strip_algorithm == "ants": brain_extraction_wf = init_brain_extraction_wf( in_template=skull_strip_template.space, template_spec=skull_strip_template.spec, atropos_use_random_seed=not config.workflow.skull_strip_fixed_seed, omp_nthreads=config.nipype.omp_nthreads, normalization_quality="precise", ) else: raise ValueError( f'Unknown skull_strip_algorithm "{skull_strip_algorithm}"') workflow.connect([ (inputnode, anat_validate, [("t1w", "in_file")]), (anat_validate, brain_extraction_wf, [("out_file", "inputnode.in_files")]), ( brain_extraction_wf, outputnode, [("outputnode.bias_corrected", "t1w_preproc")], ), ( brain_extraction_wf, buffernode, [ (("outputnode.out_file", first), "t1w_brain"), ("outputnode.out_mask", "t1w_mask"), ], ), ( buffernode, outputnode, [("t1w_brain", "t1w_brain"), ("t1w_mask", "t1w_mask")], ), ]) # Step 2 t1w_dseg = pe.Node( fsl.FAST(segments=True, no_bias=True, probability_maps=True), name="t1w_dseg", mem_gb=3, ) workflow.connect([ (buffernode, t1w_dseg, [("t1w_brain", "in_files")]), ( t1w_dseg, outputnode, [("tissue_class_map", "t1w_dseg"), ("probability_maps", "t1w_tpms")], ), ]) # Step 3 anat_norm_wf = init_anat_norm_wf( debug=config.execution.debug, omp_nthreads=config.nipype.omp_nthreads, templates=norm_templates if not no_compose_transforms else norm_templates + extra_templates, ) workflow.connect([ ( inputnode, anat_norm_wf, [("t1w", "inputnode.orig_t1w")], ), ( brain_extraction_wf, anat_norm_wf, [(("outputnode.bias_corrected", first), "inputnode.moving_image")], ), (buffernode, anat_norm_wf, [("t1w_mask", "inputnode.moving_mask")]), ( t1w_dseg, anat_norm_wf, [("tissue_class_map", "inputnode.moving_segmentation")], ), (t1w_dseg, anat_norm_wf, [("probability_maps", "inputnode.moving_tpms") ]), ]) # Write outputs anat_reports_wf = init_anat_reports_wf(freesurfer=freesurfer, output_dir="/") workflow.connect([ ( outputnode, anat_reports_wf, [ ("t1w_preproc", "inputnode.t1w_preproc"), ("t1w_mask", "inputnode.t1w_mask"), ("t1w_dseg", "inputnode.t1w_dseg"), ], ), (inputnode, anat_reports_wf, [("t1w", "inputnode.source_file")]), ( anat_norm_wf, anat_reports_wf, [ ("poutputnode.template", "inputnode.template"), ("poutputnode.standardized", "inputnode.std_t1w"), ("poutputnode.std_mask", "inputnode.std_mask"), ], ), ]) # Custom add_templates_by_composing_transforms( workflow, templates=extra_templates if not no_compose_transforms else []) make_reportnode(workflow, spaces=True) assert workdir is not None make_reportnode_datasink(workflow, workdir) if freesurfer: def get_subject(dic): return dic.get("subject") # 5. Surface reconstruction (--fs-no-reconall not set) surface_recon_wf = init_surface_recon_wf( name="surface_recon_wf", omp_nthreads=config.nipype.omp_nthreads, hires=config.workflow.hires, ) subjects_dir = Path(workdir) / "subjects_dir" subjects_dir.mkdir(parents=True, exist_ok=True) surface_recon_wf.get_node("inputnode").inputs.subjects_dir = str( subjects_dir) workflow.connect([ ( inputnode, surface_recon_wf, [(("metadata", get_subject), "inputnode.subject_id")], ), (anat_validate, surface_recon_wf, [("out_file", "inputnode.t1w")]), ( brain_extraction_wf, surface_recon_wf, [ (("outputnode.out_file", first), "inputnode.skullstripped_t1"), ("outputnode.out_segm", "inputnode.ants_segs"), (("outputnode.bias_corrected", first), "inputnode.corrected_t1"), ], ), ( surface_recon_wf, anat_reports_wf, [ ("outputnode.subject_id", "inputnode.subject_id"), ("outputnode.subjects_dir", "inputnode.subjects_dir"), ], ), ]) return workflow
def init_anat_preproc_wf( *, bids_root, freesurfer, hires, longitudinal, t1w, omp_nthreads, output_dir, skull_strip_mode, skull_strip_template, spaces, debug=False, existing_derivatives=None, name='anat_preproc_wf', skull_strip_fixed_seed=False, ): """ Stage the anatomical preprocessing steps of *sMRIPrep*. This includes: - T1w reference: realigning and then averaging T1w images. - Brain extraction and INU (bias field) correction. - Brain tissue segmentation. - Spatial normalization to standard spaces. - Surface reconstruction with FreeSurfer_. .. include:: ../links.rst Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from niworkflows.utils.spaces import SpatialReferences, Reference from smriprep.workflows.anatomical import init_anat_preproc_wf wf = init_anat_preproc_wf( bids_root='.', freesurfer=True, hires=True, longitudinal=False, t1w=['t1w.nii.gz'], omp_nthreads=1, output_dir='.', skull_strip_mode='force', skull_strip_template=Reference('OASIS30ANTs'), spaces=SpatialReferences(spaces=['MNI152NLin2009cAsym', 'fsaverage5']), ) Parameters ---------- bids_root : :obj:`str` Path of the input BIDS dataset root existing_derivatives : :obj:`dict` or None Dictionary mapping output specification attribute names and paths to corresponding derivatives. freesurfer : :obj:`bool` Enable FreeSurfer surface reconstruction (increases runtime by 6h, at the very least) hires : :obj:`bool` Enable sub-millimeter preprocessing in FreeSurfer longitudinal : :obj:`bool` Create unbiased structural template, regardless of number of inputs (may increase runtime) t1w : :obj:`list` List of T1-weighted structural images. omp_nthreads : :obj:`int` Maximum number of threads an individual process may use output_dir : :obj:`str` Directory in which to save derivatives skull_strip_template : :py:class:`~niworkflows.utils.spaces.Reference` Spatial reference to use in atlas-based brain extraction. spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences` Object containing standard and nonstandard space specifications. debug : :obj:`bool` Enable debugging outputs name : :obj:`str`, optional Workflow name (default: anat_preproc_wf) skull_strip_mode : :obj:`str` Determiner for T1-weighted skull stripping (`force` ensures skull stripping, `skip` ignores skull stripping, and `auto` automatically ignores skull stripping if pre-stripped brains are detected). skull_strip_fixed_seed : :obj:`bool` Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 (default: ``False``). Inputs ------ t1w List of T1-weighted structural images t2w List of T2-weighted structural images roi A mask to exclude regions during standardization flair List of FLAIR images subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID Outputs ------- t1w_preproc The T1w reference map, which is calculated as the average of bias-corrected and preprocessed T1w images, defining the anatomical space. t1w_brain Skull-stripped ``t1w_preproc`` t1w_mask Brain (binary) mask estimated by brain extraction. t1w_dseg Brain tissue segmentation of the preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF). t1w_tpms List of tissue probability maps corresponding to ``t1w_dseg``. std_preproc T1w reference resampled in one or more standard spaces. std_mask Mask of skull-stripped template, in MNI space std_dseg Segmentation, resampled into MNI space std_tpms List of tissue probability maps in MNI space subjects_dir FreeSurfer SUBJECTS_DIR anat2std_xfm Nonlinear spatial transform to resample imaging data given in anatomical space into standard space. std2anat_xfm Inverse transform of the above. 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 surfaces GIFTI surfaces (gray/white boundary, midthickness, pial, inflated) See Also -------- * :py:func:`~niworkflows.anat.ants.init_brain_extraction_wf` * :py:func:`~smriprep.workflows.surfaces.init_surface_recon_wf` """ workflow = Workflow(name=name) num_t1w = len(t1w) desc = """Anatomical data preprocessing : """ desc += """\ A total of {num_t1w} T1-weighted (T1w) images were found within the input BIDS dataset.""".format(num_t1w=num_t1w) inputnode = pe.Node(niu.IdentityInterface( fields=['t1w', 't2w', 'roi', 'flair', 'subjects_dir', 'subject_id']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['template', 'subjects_dir', 'subject_id'] + get_outputnode_spec()), name='outputnode') # Connect reportlets workflows anat_reports_wf = init_anat_reports_wf( freesurfer=freesurfer, output_dir=output_dir, ) workflow.connect([ (outputnode, anat_reports_wf, [('t1w_preproc', 'inputnode.t1w_preproc'), ('t1w_mask', 'inputnode.t1w_mask'), ('t1w_dseg', 'inputnode.t1w_dseg')]), ]) if existing_derivatives is not None: LOGGER.log( 25, "Anatomical workflow will reuse prior derivatives found in the " "output folder (%s).", output_dir) desc += """ Anatomical preprocessing was reused from previously existing derivative objects.\n""" workflow.__desc__ = desc templates = existing_derivatives.pop('template') templatesource = pe.Node(niu.IdentityInterface(fields=['template']), name='templatesource') templatesource.iterables = [('template', templates)] outputnode.inputs.template = templates for field, value in existing_derivatives.items(): setattr(outputnode.inputs, field, value) anat_reports_wf.inputs.inputnode.source_file = fix_multi_T1w_source_name( [existing_derivatives['t1w_preproc']]) stdselect = pe.Node(KeySelect(fields=['std_preproc', 'std_mask'], keys=templates), name='stdselect', run_without_submitting=True) workflow.connect([ (inputnode, outputnode, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (inputnode, anat_reports_wf, [('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id')]), (templatesource, stdselect, [('template', 'key')]), (outputnode, stdselect, [('std_preproc', 'std_preproc'), ('std_mask', 'std_mask')]), (stdselect, anat_reports_wf, [ ('key', 'inputnode.template'), ('std_preproc', 'inputnode.std_t1w'), ('std_mask', 'inputnode.std_mask'), ]), ]) return workflow # The workflow is not cached. desc += """ All of them were corrected for intensity non-uniformity (INU) """ if num_t1w > 1 else """\ The T1-weighted (T1w) image was corrected for intensity non-uniformity (INU) """ desc += """\ with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} \ [@ants, RRID:SCR_004757]""" desc += '.\n' if num_t1w > 1 else ", and used as T1w-reference throughout the workflow.\n" desc += """\ The T1w-reference was then skull-stripped with a *Nipype* implementation of the `antsBrainExtraction.sh` workflow (from ANTs), using {skullstrip_tpl} as target template. Brain tissue segmentation of cerebrospinal fluid (CSF), white-matter (WM) and gray-matter (GM) was performed on the brain-extracted T1w using `fast` [FSL {fsl_ver}, RRID:SCR_002823, @fsl_fast]. """ workflow.__desc__ = desc.format( ants_ver=ANTsInfo.version() or '(version unknown)', fsl_ver=fsl.FAST().version or '(version unknown)', num_t1w=num_t1w, skullstrip_tpl=skull_strip_template.fullname, ) buffernode = pe.Node( niu.IdentityInterface(fields=['t1w_brain', 't1w_mask']), name='buffernode') # 1. Anatomical reference generation - average input T1w images. anat_template_wf = init_anat_template_wf(longitudinal=longitudinal, omp_nthreads=omp_nthreads, num_t1w=num_t1w) anat_validate = pe.Node(ValidateImage(), name='anat_validate', run_without_submitting=True) # 2. Brain-extraction and INU (bias field) correction. if skull_strip_mode == 'auto': import numpy as np import nibabel as nb def _is_skull_stripped(imgs): """Check if T1w images are skull-stripped.""" def _check_img(img): data = np.abs(nb.load(img).get_fdata(dtype=np.float32)) sidevals = data[0, :, :].sum() + data[-1, :, :].sum() + \ data[:, 0, :].sum() + data[:, -1, :].sum() + \ data[:, :, 0].sum() + data[:, :, -1].sum() return sidevals < 10 return all(_check_img(img) for img in imgs) skull_strip_mode = _is_skull_stripped(t1w) if skull_strip_mode in (True, 'skip'): brain_extraction_wf = init_n4_only_wf( omp_nthreads=omp_nthreads, atropos_use_random_seed=not skull_strip_fixed_seed, ) else: brain_extraction_wf = init_brain_extraction_wf( in_template=skull_strip_template.space, template_spec=skull_strip_template.spec, atropos_use_random_seed=not skull_strip_fixed_seed, omp_nthreads=omp_nthreads, normalization_quality='precise' if not debug else 'testing') # 4. Spatial normalization anat_norm_wf = init_anat_norm_wf( debug=debug, omp_nthreads=omp_nthreads, templates=spaces.get_spaces(nonstandard=False, dim=(3, )), ) workflow.connect([ # Step 1. (inputnode, anat_template_wf, [('t1w', 'inputnode.t1w')]), (anat_template_wf, anat_validate, [('outputnode.t1w_ref', 'in_file')]), (anat_validate, brain_extraction_wf, [('out_file', 'inputnode.in_files')]), (brain_extraction_wf, outputnode, [(('outputnode.bias_corrected', _pop), 't1w_preproc')]), (anat_template_wf, outputnode, [('outputnode.t1w_realign_xfm', 't1w_ref_xfms')]), (buffernode, outputnode, [('t1w_brain', 't1w_brain'), ('t1w_mask', 't1w_mask')]), # Steps 2, 3 and 4 (inputnode, anat_norm_wf, [(('t1w', fix_multi_T1w_source_name), 'inputnode.orig_t1w'), ('roi', 'inputnode.lesion_mask')]), (brain_extraction_wf, anat_norm_wf, [(('outputnode.bias_corrected', _pop), 'inputnode.moving_image')]), (buffernode, anat_norm_wf, [('t1w_mask', 'inputnode.moving_mask')]), (anat_norm_wf, outputnode, [ ('poutputnode.standardized', 'std_preproc'), ('poutputnode.std_mask', 'std_mask'), ('poutputnode.std_dseg', 'std_dseg'), ('poutputnode.std_tpms', 'std_tpms'), ('outputnode.template', 'template'), ('outputnode.anat2std_xfm', 'anat2std_xfm'), ('outputnode.std2anat_xfm', 'std2anat_xfm'), ]), ]) # Change LookUp Table - BIDS wants: 0 (bg), 1 (gm), 2 (wm), 3 (csf) lut_t1w_dseg = pe.Node(niu.Function(function=_apply_bids_lut), name='lut_t1w_dseg') workflow.connect([ (lut_t1w_dseg, anat_norm_wf, [('out', 'inputnode.moving_segmentation') ]), (lut_t1w_dseg, outputnode, [('out', 't1w_dseg')]), ]) # Connect reportlets workflow.connect([ (inputnode, anat_reports_wf, [(('t1w', fix_multi_T1w_source_name), 'inputnode.source_file')]), (outputnode, anat_reports_wf, [ ('std_preproc', 'inputnode.std_t1w'), ('std_mask', 'inputnode.std_mask'), ]), (anat_template_wf, anat_reports_wf, [('outputnode.out_report', 'inputnode.t1w_conform_report')]), (anat_norm_wf, anat_reports_wf, [('poutputnode.template', 'inputnode.template')]), ]) # Write outputs ############################################3 anat_derivatives_wf = init_anat_derivatives_wf( bids_root=bids_root, freesurfer=freesurfer, num_t1w=num_t1w, output_dir=output_dir, ) workflow.connect([ # Connect derivatives (anat_template_wf, anat_derivatives_wf, [('outputnode.t1w_valid_list', 'inputnode.source_files')]), (anat_norm_wf, anat_derivatives_wf, [('poutputnode.template', 'inputnode.template'), ('poutputnode.anat2std_xfm', 'inputnode.anat2std_xfm'), ('poutputnode.std2anat_xfm', 'inputnode.std2anat_xfm')]), (outputnode, anat_derivatives_wf, [ ('std_preproc', 'inputnode.std_t1w'), ('t1w_ref_xfms', 'inputnode.t1w_ref_xfms'), ('t1w_preproc', 'inputnode.t1w_preproc'), ('t1w_mask', 'inputnode.t1w_mask'), ('t1w_dseg', 'inputnode.t1w_dseg'), ('t1w_tpms', 'inputnode.t1w_tpms'), ('std_mask', 'inputnode.std_mask'), ('std_dseg', 'inputnode.std_dseg'), ('std_tpms', 'inputnode.std_tpms'), ]), ]) if not freesurfer: # Flag --fs-no-reconall is set - return # Brain tissue segmentation - FAST produces: 0 (bg), 1 (wm), 2 (csf), 3 (gm) t1w_dseg = pe.Node(fsl.FAST(segments=True, no_bias=True, probability_maps=True), name='t1w_dseg', mem_gb=3) lut_t1w_dseg.inputs.lut = (0, 3, 1, 2 ) # Maps: 0 -> 0, 3 -> 1, 1 -> 2, 2 -> 3. fast2bids = pe.Node(niu.Function(function=_probseg_fast2bids), name="fast2bids", run_without_submitting=True) workflow.connect([ (brain_extraction_wf, buffernode, [(('outputnode.out_file', _pop), 't1w_brain'), ('outputnode.out_mask', 't1w_mask')]), (buffernode, t1w_dseg, [('t1w_brain', 'in_files')]), (t1w_dseg, lut_t1w_dseg, [('partial_volume_map', 'in_dseg')]), (t1w_dseg, fast2bids, [('partial_volume_files', 'inlist')]), (fast2bids, anat_norm_wf, [('out', 'inputnode.moving_tpms')]), (fast2bids, outputnode, [('out', 't1w_tpms')]), ]) return workflow # Map FS' aseg labels onto three-tissue segmentation lut_t1w_dseg.inputs.lut = _aseg_to_three() split_seg = pe.Node(niu.Function(function=_split_segments), name='split_seg') # check for older IsRunning files and remove accordingly fs_isrunning = pe.Node(niu.Function(function=_fs_isRunning), overwrite=True, name='fs_isrunning') fs_isrunning.inputs.logger = LOGGER # 5. Surface reconstruction (--fs-no-reconall not set) surface_recon_wf = init_surface_recon_wf(name='surface_recon_wf', omp_nthreads=omp_nthreads, hires=hires) applyrefined = pe.Node(fsl.ApplyMask(), name='applyrefined') workflow.connect([ (inputnode, fs_isrunning, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (inputnode, surface_recon_wf, [('t2w', 'inputnode.t2w'), ('flair', 'inputnode.flair'), ('subject_id', 'inputnode.subject_id') ]), (fs_isrunning, surface_recon_wf, [('out', 'inputnode.subjects_dir')]), (anat_validate, surface_recon_wf, [('out_file', 'inputnode.t1w')]), (brain_extraction_wf, surface_recon_wf, [(('outputnode.out_file', _pop), 'inputnode.skullstripped_t1'), ('outputnode.out_segm', 'inputnode.ants_segs'), (('outputnode.bias_corrected', _pop), 'inputnode.corrected_t1')]), (brain_extraction_wf, applyrefined, [(('outputnode.bias_corrected', _pop), 'in_file')]), (surface_recon_wf, applyrefined, [('outputnode.out_brainmask', 'mask_file')]), (surface_recon_wf, lut_t1w_dseg, [('outputnode.out_aseg', 'in_dseg')]), (lut_t1w_dseg, split_seg, [('out', 'in_file')]), (split_seg, anat_norm_wf, [('out', 'inputnode.moving_tpms')]), (split_seg, outputnode, [('out', 't1w_tpms')]), (surface_recon_wf, outputnode, [('outputnode.subjects_dir', 'subjects_dir'), ('outputnode.subject_id', 'subject_id'), ('outputnode.t1w2fsnative_xfm', 't1w2fsnative_xfm'), ('outputnode.fsnative2t1w_xfm', 'fsnative2t1w_xfm'), ('outputnode.surfaces', 'surfaces'), ('outputnode.out_aseg', 't1w_aseg'), ('outputnode.out_aparc', 't1w_aparc')]), (applyrefined, buffernode, [('out_file', 't1w_brain')]), (surface_recon_wf, buffernode, [('outputnode.out_brainmask', 't1w_mask')]), (surface_recon_wf, anat_reports_wf, [('outputnode.subject_id', 'inputnode.subject_id'), ('outputnode.subjects_dir', 'inputnode.subjects_dir')]), (surface_recon_wf, anat_derivatives_wf, [ ('outputnode.out_aseg', 'inputnode.t1w_fs_aseg'), ('outputnode.out_aparc', 'inputnode.t1w_fs_aparc'), ]), (outputnode, anat_derivatives_wf, [ ('t1w2fsnative_xfm', 'inputnode.t1w2fsnative_xfm'), ('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), ('surfaces', 'inputnode.surfaces'), ]), ]) return workflow
def init_anat_preproc_wf( bids_root, freesurfer, fs_spaces, hires, longitudinal, omp_nthreads, output_dir, num_t1w, reportlets_dir, skull_strip_template, template, debug=False, name='anat_preproc_wf', skull_strip_fixed_seed=False): r""" This workflow controls the anatomical preprocessing stages of smriprep. This includes: - Creation of a structural template - Skull-stripping and bias correction - Tissue segmentation - Normalization - Surface reconstruction with FreeSurfer .. workflow:: :graph2use: orig :simple_form: yes from smriprep.workflows.anatomical import init_anat_preproc_wf wf = init_anat_preproc_wf( bids_root='.', freesurfer=True, fs_spaces=['T1w', 'fsnative', 'template', 'fsaverage5'], hires=True, longitudinal=False, omp_nthreads=1, output_dir='.', num_t1w=1, reportlets_dir='.', skull_strip_template='MNI152NLin2009cAsym', template='MNI152NLin2009cAsym', ) **Parameters** bids_root : str Path of the input BIDS dataset root debug : bool Enable debugging outputs freesurfer : bool Enable FreeSurfer surface reconstruction (increases runtime by 6h, at the very least) fs_spaces : list List of output spaces functional images are to be resampled to. Some pipeline components will only be instantiated for some output spaces. Valid spaces: - T1w - template - fsnative - fsaverage (or other pre-existing FreeSurfer templates) hires : bool Enable sub-millimeter preprocessing in FreeSurfer longitudinal : bool Create unbiased structural template, regardless of number of inputs (may increase runtime) name : str, optional Workflow name (default: anat_preproc_wf) omp_nthreads : int Maximum number of threads an individual process may use output_dir : str Directory in which to save derivatives reportlets_dir : str Directory in which to save reportlets skull_strip_fixed_seed : bool Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 (default: ``False``) skull_strip_template : str Name of ANTs skull-stripping template ('MNI152NLin2009cAsym', 'OASIS30ANTs' or 'NKI') template : str Name of template targeted by ``template`` output space **Inputs** t1w List of T1-weighted structural images t2w List of T2-weighted structural images flair List of FLAIR images subjects_dir FreeSurfer SUBJECTS_DIR **Outputs** t1_preproc Bias-corrected structural template, defining T1w space t1_brain Skull-stripped ``t1_preproc`` t1_mask Mask of the skull-stripped template image t1_seg Segmentation of preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) t1_tpms List of tissue probability maps in T1w space t1_2_mni T1w template, normalized to MNI space t1_2_mni_forward_transform ANTs-compatible affine-and-warp transform file t1_2_mni_reverse_transform ANTs-compatible affine-and-warp transform file (inverse) mni_mask Mask of skull-stripped template, in MNI space mni_seg Segmentation, resampled into MNI space mni_tpms List of tissue probability maps in MNI space subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID t1_2_fsnative_forward_transform LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space t1_2_fsnative_reverse_transform LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w surfaces GIFTI surfaces (gray/white boundary, midthickness, pial, inflated) **Subworkflows** * :py:func:`~niworkflows.anat.ants.init_brain_extraction_wf` * :py:func:`~smriprep.workflows.surfaces.init_surface_recon_wf` """ if isinstance(template, list): # THIS SHOULD BE DELETED template = template[0] template_meta = get_metadata(template) template_refs = ['@%s' % template.lower()] if template_meta.get('RRID', None): template_refs += ['RRID:%s' % template_meta['RRID']] workflow = Workflow(name=name) workflow.__postdesc__ = """\ Spatial normalization to the *{template_name}* [{template_refs}] was performed through nonlinear registration with `antsRegistration` (ANTs {ants_ver}), using brain-extracted versions of both T1w volume and template. Brain tissue segmentation of cerebrospinal fluid (CSF), white-matter (WM) and gray-matter (GM) was performed on the brain-extracted T1w using `fast` [FSL {fsl_ver}, RRID:SCR_002823, @fsl_fast]. """.format( ants_ver=ANTsInfo.version() or '<ver>', fsl_ver=fsl.FAST().version or '<ver>', template_name=template_meta['Name'], template_refs=', '.join(template_refs), ) desc = """Anatomical data preprocessing : """ desc += """\ A total of {num_t1w} T1-weighted (T1w) images were found within the input BIDS dataset. All of them were corrected for intensity non-uniformity (INU) """ if num_t1w > 1 else """\ The T1-weighted (T1w) image was corrected for intensity non-uniformity (INU) """ desc += """\ with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} \ [@ants, RRID:SCR_004757]""" desc += '.\n' if num_t1w > 1 else ", and used as T1w-reference throughout the workflow.\n" desc += """\ The T1w-reference was then skull-stripped with a *Nipype* implementation of the `antsBrainExtraction.sh` workflow (from ANTs), using {skullstrip_tpl} as target template. """.format(skullstrip_tpl=skull_strip_template) workflow.__desc__ = desc.format( num_t1w=num_t1w, ants_ver=ANTsInfo.version() or '<ver>' ) inputnode = pe.Node( niu.IdentityInterface(fields=['t1w', 't2w', 'roi', 'flair', 'subjects_dir', 'subject_id']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['t1_preproc', 't1_brain', 't1_mask', 't1_seg', 't1_tpms', 't1_2_mni', 't1_2_mni_forward_transform', 't1_2_mni_reverse_transform', 'mni_mask', 'mni_seg', 'mni_tpms', 'template_transforms', 'subjects_dir', 'subject_id', 't1_2_fsnative_forward_transform', 't1_2_fsnative_reverse_transform', 'surfaces', 't1_aseg', 't1_aparc']), name='outputnode') buffernode = pe.Node(niu.IdentityInterface( fields=['t1_brain', 't1_mask']), name='buffernode') anat_template_wf = init_anat_template_wf(longitudinal=longitudinal, omp_nthreads=omp_nthreads, num_t1w=num_t1w) # 3. Skull-stripping # Bias field correction is handled in skull strip workflows. brain_extraction_wf = init_brain_extraction_wf( in_template=skull_strip_template, atropos_use_random_seed=not skull_strip_fixed_seed, omp_nthreads=omp_nthreads, normalization_quality='precise' if not debug else 'testing') workflow.connect([ (inputnode, anat_template_wf, [('t1w', 'inputnode.t1w')]), (anat_template_wf, brain_extraction_wf, [ ('outputnode.t1_template', 'inputnode.in_files')]), (brain_extraction_wf, outputnode, [ ('outputnode.bias_corrected', 't1_preproc')]), (anat_template_wf, outputnode, [ ('outputnode.template_transforms', 't1_template_transforms')]), (buffernode, outputnode, [('t1_brain', 't1_brain'), ('t1_mask', 't1_mask')]), ]) # 4. Surface reconstruction if freesurfer: surface_recon_wf = init_surface_recon_wf(name='surface_recon_wf', omp_nthreads=omp_nthreads, hires=hires) applyrefined = pe.Node(fsl.ApplyMask(), name='applyrefined') workflow.connect([ (inputnode, surface_recon_wf, [ ('t2w', 'inputnode.t2w'), ('flair', 'inputnode.flair'), ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id')]), (anat_template_wf, surface_recon_wf, [('outputnode.t1_template', 'inputnode.t1w')]), (brain_extraction_wf, surface_recon_wf, [ (('outputnode.out_file', _pop), 'inputnode.skullstripped_t1'), ('outputnode.out_segm', 'inputnode.ants_segs'), (('outputnode.bias_corrected', _pop), 'inputnode.corrected_t1')]), (brain_extraction_wf, applyrefined, [ (('outputnode.bias_corrected', _pop), 'in_file')]), (surface_recon_wf, applyrefined, [ ('outputnode.out_brainmask', 'mask_file')]), (surface_recon_wf, outputnode, [ ('outputnode.subjects_dir', 'subjects_dir'), ('outputnode.subject_id', 'subject_id'), ('outputnode.t1_2_fsnative_forward_transform', 't1_2_fsnative_forward_transform'), ('outputnode.t1_2_fsnative_reverse_transform', 't1_2_fsnative_reverse_transform'), ('outputnode.surfaces', 'surfaces'), ('outputnode.out_aseg', 't1_aseg'), ('outputnode.out_aparc', 't1_aparc')]), (applyrefined, buffernode, [('out_file', 't1_brain')]), (surface_recon_wf, buffernode, [ ('outputnode.out_brainmask', 't1_mask')]), ]) else: workflow.connect([ (brain_extraction_wf, buffernode, [ (('outputnode.out_file', _pop), 't1_brain'), ('outputnode.out_mask', 't1_mask')]), ]) # 5. Segmentation t1_seg = pe.Node(fsl.FAST(segments=True, no_bias=True, probability_maps=True), name='t1_seg', mem_gb=3) workflow.connect([ (buffernode, t1_seg, [('t1_brain', 'in_files')]), (t1_seg, outputnode, [('tissue_class_map', 't1_seg'), ('probability_maps', 't1_tpms')]), ]) # 6. Spatial normalization (T1w to MNI registration) t1_2_mni = pe.Node( RobustMNINormalizationRPT( float=True, generate_report=True, flavor='testing' if debug else 'precise', ), name='t1_2_mni', n_procs=omp_nthreads, mem_gb=2 ) # Resample the brain mask and the tissue probability maps into mni space mni_mask = pe.Node( ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='MultiLabel'), name='mni_mask' ) mni_seg = pe.Node( ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='MultiLabel'), name='mni_seg' ) mni_tpms = pe.MapNode( ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='Linear'), iterfield=['input_image'], name='mni_tpms' ) # TODO isolate the spatial normalization workflow ############# ref_img = str(get_template(template, resolution=1, desc=None, suffix='T1w', extensions=['.nii', '.nii.gz'])) t1_2_mni.inputs.template = template mni_mask.inputs.reference_image = ref_img mni_seg.inputs.reference_image = ref_img mni_tpms.inputs.reference_image = ref_img workflow.connect([ (inputnode, t1_2_mni, [('roi', 'lesion_mask')]), (brain_extraction_wf, t1_2_mni, [ (('outputnode.bias_corrected', _pop), 'moving_image')]), (buffernode, t1_2_mni, [('t1_mask', 'moving_mask')]), (buffernode, mni_mask, [('t1_mask', 'input_image')]), (t1_2_mni, mni_mask, [('composite_transform', 'transforms')]), (t1_seg, mni_seg, [('tissue_class_map', 'input_image')]), (t1_2_mni, mni_seg, [('composite_transform', 'transforms')]), (t1_seg, mni_tpms, [('probability_maps', 'input_image')]), (t1_2_mni, mni_tpms, [('composite_transform', 'transforms')]), (t1_2_mni, outputnode, [ ('warped_image', 't1_2_mni'), ('composite_transform', 't1_2_mni_forward_transform'), ('inverse_composite_transform', 't1_2_mni_reverse_transform')]), (mni_mask, outputnode, [('output_image', 'mni_mask')]), (mni_seg, outputnode, [('output_image', 'mni_seg')]), (mni_tpms, outputnode, [('output_image', 'mni_tpms')]), ]) # spatial normalization ends here ############################### seg_rpt = pe.Node(ROIsPlot(colors=['magenta', 'b'], levels=[1.5, 2.5]), name='seg_rpt') anat_reports_wf = init_anat_reports_wf( reportlets_dir=reportlets_dir, template=template, freesurfer=freesurfer) workflow.connect([ (inputnode, anat_reports_wf, [ (('t1w', fix_multi_T1w_source_name), 'inputnode.source_file')]), (anat_template_wf, anat_reports_wf, [ ('outputnode.out_report', 'inputnode.t1_conform_report')]), (anat_template_wf, seg_rpt, [ ('outputnode.t1_template', 'in_file')]), (t1_seg, seg_rpt, [('tissue_class_map', 'in_rois')]), (outputnode, seg_rpt, [('t1_mask', 'in_mask')]), (seg_rpt, anat_reports_wf, [('out_report', 'inputnode.seg_report')]), (t1_2_mni, anat_reports_wf, [('out_report', 'inputnode.t1_2_mni_report')]), ]) if freesurfer: workflow.connect([ (surface_recon_wf, anat_reports_wf, [ ('outputnode.out_report', 'inputnode.recon_report')]), ]) anat_derivatives_wf = init_anat_derivatives_wf( bids_root=bids_root, freesurfer=freesurfer, output_dir=output_dir, template=template, ) workflow.connect([ (anat_template_wf, anat_derivatives_wf, [ ('outputnode.t1w_valid_list', 'inputnode.source_files')]), (outputnode, anat_derivatives_wf, [ ('t1_template_transforms', 'inputnode.t1_template_transforms'), ('t1_preproc', 'inputnode.t1_preproc'), ('t1_mask', 'inputnode.t1_mask'), ('t1_seg', 'inputnode.t1_seg'), ('t1_tpms', 'inputnode.t1_tpms'), ('t1_2_mni_forward_transform', 'inputnode.t1_2_mni_forward_transform'), ('t1_2_mni_reverse_transform', 'inputnode.t1_2_mni_reverse_transform'), ('t1_2_mni', 'inputnode.t1_2_mni'), ('mni_mask', 'inputnode.mni_mask'), ('mni_seg', 'inputnode.mni_seg'), ('mni_tpms', 'inputnode.mni_tpms'), ('t1_2_fsnative_forward_transform', 'inputnode.t1_2_fsnative_forward_transform'), ('surfaces', 'inputnode.surfaces'), ]), ]) if freesurfer: workflow.connect([ (surface_recon_wf, anat_derivatives_wf, [ ('outputnode.out_aseg', 'inputnode.t1_fs_aseg'), ('outputnode.out_aparc', 'inputnode.t1_fs_aparc'), ]), ]) return workflow
def main(): parser = argparse.ArgumentParser() parser.add_argument("t1", help="Input T1 file", type=str) parser.add_argument("bspline", help="Bspline distance for N4", type=int) parser.add_argument("niter", help="Number of iterations, will be multipled" "by [niter]*5", type=int) args = parser.parse_args() t1 = args.t1 bspline = args.bspline niter = args.niter # Standard input workdir = os.getcwd() t1 = os.path.join(workdir, t1) # Initialize workflow wf = Workflow(name="bias_field") wf.base_dir = os.getcwd() # Set up input node of list type input_node = pe.Node(niu.IdentityInterface(fields=['in_files']), name='inputnode') input_node.inputs.in_files = [t1] # Set up input node of value type single_file_buf = pe.Node(niu.IdentityInterface(fields=['input_file']), name='inputfile') single_file_buf.inputs.input_file = t1 # Initial skullstrip ants_wf = init_brain_extraction_wf(in_template='OASIS30ANTs', atropos_use_random_seed=False, normalization_quality='precise') # Apply N4 bias field correction # Iterate over: # bspline fitting distances # number of iterations n4 = pe.Node(N4BiasFieldCorrection(dimension=3, save_bias=True, copy_header=True, convergence_threshold=1e-7, shrink_factor=4, bspline_fitting_distance=bspline, n_iterations=[niter] * 5), name='n4') outputnode = pe.Node( niu.IdentityInterface(fields=['corrected_t1', 'orig_t1']), name='out') datasink = pe.Node(DataSink(base_directory=workdir, container='n4_wf'), name='sink') datasink.inputs.substitutions = [('_bspline_fitting_distance_', 'bspline-'), ('n_iterations_', 'niter-')] wf.connect([[input_node, ants_wf, [('in_files', 'inputnode.in_files')]], [single_file_buf, n4, [('input_file', 'input_image')]], [ants_wf, n4, [('outputnode.out_mask', 'mask_image')]], [n4, outputnode, [('output_image', 'corrected_t1')]], [single_file_buf, outputnode, [('input_file', 'orig_t1')]], [ outputnode, datasink, [('corrected_t1', 'corrected_img.@corrected'), ('orig_t1', 'corrected_img.@orig')] ]]) wf.run()
def init_templateflow_wf( bids_dir, output_dir, participant_label, mov_template, ref_template='MNI152NLin2009cAsym', use_float=True, omp_nthreads=None, mem_gb=3.0, modality='T1w', normalization_quality='precise', name='templateflow_wf', fs_subjects_dir=None, ): """ A Nipype workflow to perform image registration between two templates *R* and *M*. *R* is the *reference template*, selected by a templateflow identifier such as ``MNI152NLin2009cAsym``, and *M* is the *moving template* (e.g., ``MNI152Lin``). This workflows maps data defined on template-*M* space onto template-*R* space. 1. Run the subrogate images through ``antsBrainExtraction``. 2. Recompute :abbr:`INU (intensity non-uniformity)` correction using the mask obtained in 1). 3. Independently, run spatial normalization of every :abbr:`INU (intensity non-uniformity)` corrected image (supplied via ``in_files``) to both templates. 4. Calculate an initialization between both templates, using them directly. 5. Run multi-channel image registration of the images resulting from 3). Both sets of images (one registered to *R* and another to *M*) are then used as reference and moving images in the registration framework. **Parameters** in_files: list of files a list of paths pointing to the images that will be used as surrogates mov_template: str a templateflow identifier for template-*M* ref_template: str a templateflow identifier for template-*R* (default: ``MNI152NLin2009cAsym``). """ # number of participants ninputs = len(participant_label) ants_env = { 'NSLOTS': '%d' % omp_nthreads, 'ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS': '%d' % omp_nthreads, 'OMP_NUM_THREADS': '%d' % omp_nthreads, } # Get path to templates tpl_ref = str( get_template(ref_template, suffix=modality, desc=None, resolution=1)) tpl_ref_mask = str( get_template(ref_template, suffix='mask', desc='brain', resolution=1)) tpl_mov = str( get_template(mov_template, suffix=modality, desc=None, resolution=1)) tpl_mov_mask = str( get_template(mov_template, suffix='mask', desc='brain', resolution=1)) wf = pe.Workflow(name) inputnode = pe.Node(niu.IdentityInterface(fields=['participant_label']), name='inputnode') inputnode.iterables = ('participant_label', sorted(list(participant_label))) pick_file = pe.Node(niu.Function(function=_bids_pick), name='pick_file', run_without_submitting=True) pick_file.inputs.bids_root = bids_dir ref_bex = init_brain_extraction_wf( in_template=ref_template, omp_nthreads=omp_nthreads, mem_gb=mem_gb, bids_suffix=modality, name='reference_bex', ) mov_bex = init_brain_extraction_wf( in_template=mov_template, omp_nthreads=omp_nthreads, mem_gb=mem_gb, bids_suffix=modality, name='moving_bex', ) ref_norm = pe.Node(Registration(from_file=pkgr.resource_filename( 'niworkflows.data', 't1w-mni_registration_%s_000.json' % normalization_quality)), name='ref_norm', n_procs=omp_nthreads) ref_norm.inputs.fixed_image = tpl_ref ref_norm.inputs.fixed_image_masks = tpl_ref_mask ref_norm.inputs.environ = ants_env # Register the INU-corrected image to the other template mov_norm = pe.Node(Registration(from_file=pkgr.resource_filename( 'niworkflows.data', 't1w-mni_registration_%s_000.json' % normalization_quality)), name='mov_norm', n_procs=omp_nthreads) mov_norm.inputs.fixed_image = tpl_mov mov_norm.inputs.fixed_image_masks = tpl_mov_mask mov_norm.inputs.environ = ants_env # Initialize between-templates transform with antsAI init_aff = pe.Node(AI( metric=('Mattes', 32, 'Regular', 0.2), transform=('Affine', 0.1), search_factor=(20, 0.12), principal_axes=False, convergence=(10, 1e-6, 10), verbose=True, fixed_image=tpl_ref, fixed_image_mask=tpl_ref_mask, moving_image=tpl_mov, moving_image_mask=tpl_mov_mask, environ=ants_env, ), name='init_aff', n_procs=omp_nthreads) ref_buffer = pe.JoinNode(niu.IdentityInterface(fields=['fixed_image']), joinsource='inputnode', joinfield='fixed_image', name='ref_buffer') mov_buffer = pe.JoinNode(niu.IdentityInterface(fields=['moving_image']), joinsource='inputnode', joinfield='moving_image', name='mov_buffer') flow = pe.Node( Registration(from_file=pkgr.resource_filename( 'niworkflows.data', 't1w-mni_registration_%s_000.json' % normalization_quality)), name='flow_norm', n_procs=omp_nthreads, ) flow.inputs.fixed_image_masks = tpl_ref_mask flow.inputs.moving_image_masks = tpl_mov_mask flow.inputs.metric = [[v] * ninputs for v in flow.inputs.metric] flow.inputs.metric_weight = [[1 / ninputs] * ninputs for _ in flow.inputs.metric_weight] flow.inputs.radius_or_number_of_bins = [ [v] * ninputs for v in flow.inputs.radius_or_number_of_bins ] flow.inputs.sampling_percentage = [[v] * ninputs for v in flow.inputs.sampling_percentage ] flow.inputs.sampling_strategy = [[v] * ninputs for v in flow.inputs.sampling_strategy] flow.inputs.environ = ants_env # Datasinking ref_norm_ds = pe.Node(DerivativesDataSink(base_directory=str( output_dir.parent), out_path_base=output_dir.name, space=ref_template, desc='preproc', keep_dtype=True), name='ref_norm_ds', run_without_submitting=True) mov_norm_ds = pe.Node(DerivativesDataSink(base_directory=str( output_dir.parent), out_path_base=output_dir.name, space=mov_template, desc='preproc', keep_dtype=True), name='mov_norm_ds', run_without_submitting=True) xfm_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, allowed_entities=['from', 'mode'], mode='image', suffix='xfm', source_file='group/tpl-{0}_T1w.nii.gz'.format(ref_template), **{'from': mov_template}), name='xfm_ds', run_without_submitting=True) wf.connect([ (inputnode, pick_file, [('participant_label', 'participant_label')]), (pick_file, ref_bex, [('out', 'inputnode.in_files')]), (pick_file, mov_bex, [('out', 'inputnode.in_files')]), (ref_bex, ref_norm, [('outputnode.bias_corrected', 'moving_image'), ('outputnode.out_mask', 'moving_image_masks'), ('norm.forward_transforms', 'initial_moving_transform')]), (ref_bex, mov_norm, [('outputnode.bias_corrected', 'moving_image')]), (mov_bex, mov_norm, [('outputnode.out_mask', 'moving_image_masks'), ('norm.forward_transforms', 'initial_moving_transform')]), (init_aff, flow, [('output_transform', 'initial_moving_transform')]), (ref_norm, ref_buffer, [('warped_image', 'fixed_image')]), (mov_norm, mov_buffer, [('warped_image', 'moving_image')]), (ref_buffer, flow, [('fixed_image', 'fixed_image')]), (mov_buffer, flow, [('moving_image', 'moving_image')]), (pick_file, ref_norm_ds, [('out', 'source_file')]), (ref_norm, ref_norm_ds, [('warped_image', 'in_file')]), (pick_file, mov_norm_ds, [('out', 'source_file')]), (mov_norm, mov_norm_ds, [('warped_image', 'in_file')]), (flow, xfm_ds, [('composite_transform', 'in_file')]), ]) if fs_subjects_dir: fssource = pe.Node(FreeSurferSource(subjects_dir=str(fs_subjects_dir)), name='fssource', run_without_submitting=True) tonative = pe.Node(fs.Label2Vol(subjects_dir=str(fs_subjects_dir)), name='tonative') tonii = pe.Node(fs.MRIConvert(out_type='niigz', resample_type='nearest'), name='tonii') ref_aparc = pe.Node(ApplyTransforms(interpolation='MultiLabel', float=True, reference_image=tpl_ref, environ=ants_env), name='ref_aparc', mem_gb=1, n_procs=omp_nthreads) mov_aparc = pe.Node(ApplyTransforms(interpolation='MultiLabel', float=True, reference_image=tpl_mov, environ=ants_env), name='mov_aparc', mem_gb=1, n_procs=omp_nthreads) ref_aparc_buffer = pe.JoinNode(niu.IdentityInterface(fields=['aparc']), joinsource='inputnode', joinfield='aparc', name='ref_aparc_buffer') ref_join_labels = pe.Node(AntsJointFusion( target_image=[tpl_ref], out_label_fusion='merged_aparc.nii.gz', out_intensity_fusion_name_format='merged_aparc_intensity_%d.nii.gz', out_label_post_prob_name_format='merged_aparc_posterior_%d.nii.gz', out_atlas_voting_weight_name_format='merged_aparc_weight_%d.nii.gz', environ=ants_env, ), name='ref_join_labels', n_procs=omp_nthreads) ref_join_labels_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, suffix='dtissue', desc='aparc', keep_dtype=False, source_file='group/tpl-{0}_T1w.nii.gz'.format(ref_template)), name='ref_join_labels_ds', run_without_submitting=True) ref_join_probs_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, suffix='probtissue', desc='aparc', keep_dtype=False, source_file='group/tpl-{0}_T1w.nii.gz'.format(ref_template)), name='ref_join_probs_ds', run_without_submitting=True) # ref_join_voting_ds = pe.Node( # DerivativesDataSink( # base_directory=str(output_dir.parent), # out_path_base=output_dir.name, space=ref_template, # suffix='probtissue', desc='aparcvoting', keep_dtype=False, # source_file='group/tpl-{0}_T1w.nii.gz'.format(ref_template)), # name='ref_join_voting_ds', run_without_submitting=True) mov_aparc_buffer = pe.JoinNode(niu.IdentityInterface(fields=['aparc']), joinsource='inputnode', joinfield='aparc', name='mov_aparc_buffer') mov_join_labels = pe.Node(AntsJointFusion( target_image=[tpl_mov], out_label_fusion='merged_aparc.nii.gz', out_intensity_fusion_name_format='merged_aparc_intensity_%d.nii.gz', out_label_post_prob_name_format='merged_aparc_posterior_%d.nii.gz', out_atlas_voting_weight_name_format='merged_aparc_weight_%d.nii.gz', environ=ants_env, ), name='mov_join_labels', n_procs=omp_nthreads) mov_join_labels_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, suffix='dtissue', desc='aparc', keep_dtype=False, source_file='group/tpl-{0}_T1w.nii.gz'.format(mov_template)), name='mov_join_labels_ds', run_without_submitting=True) mov_join_probs_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, suffix='probtissue', desc='aparc', keep_dtype=False, source_file='group/tpl-{0}_T1w.nii.gz'.format(mov_template)), name='mov_join_probs_ds', run_without_submitting=True) ref_aparc_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, space=ref_template, suffix='dtissue', desc='aparc', keep_dtype=False), name='ref_aparc_ds', run_without_submitting=True) mov_aparc_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, space=mov_template, suffix='dtissue', desc='aparc', keep_dtype=False), name='mov_aparc_ds', run_without_submitting=True) # Extract surfaces cifti_wf = init_gifti_surface_wf(name='cifti_surfaces', subjects_dir=str(fs_subjects_dir)) # Move surfaces to template spaces gii2csv = pe.MapNode(GiftiToCSV(itk_lps=True), iterfield=['in_file'], name='gii2csv') ref_map_surf = pe.MapNode(ApplyTransformsToPoints(dimension=3, environ=ants_env), n_procs=omp_nthreads, name='ref_map_surf', iterfield=['input_file']) ref_csv2gii = pe.MapNode(CSVToGifti(itk_lps=True), name='ref_csv2gii', iterfield=['in_file', 'gii_file']) ref_surfs_buffer = pe.JoinNode( niu.IdentityInterface(fields=['surfaces']), joinsource='inputnode', joinfield='surfaces', name='ref_surfs_buffer') ref_surfs_unzip = pe.Node(UnzipJoinedSurfaces(), name='ref_surfs_unzip', run_without_submitting=True) ref_ply = pe.MapNode(SurfacesToPointCloud(), name='ref_ply', iterfield=['in_files']) ref_recon = pe.MapNode(PoissonRecon(), name='ref_recon', iterfield=['in_file']) ref_avggii = pe.MapNode(PLYtoGifti(), name='ref_avggii', iterfield=['in_file', 'surf_key']) ref_smooth = pe.MapNode(fs.SmoothTessellation(), name='ref_smooth', iterfield=['in_file']) ref_surfs_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, space=ref_template, keep_dtype=False, compress=False), name='ref_surfs_ds', run_without_submitting=True) ref_avg_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, space=ref_template, keep_dtype=False, compress=False, source_file='group/tpl-{0}_T1w.nii.gz'.format(ref_template)), name='ref_avg_ds', run_without_submitting=True) mov_map_surf = pe.MapNode(ApplyTransformsToPoints(dimension=3, environ=ants_env), n_procs=omp_nthreads, name='mov_map_surf', iterfield=['input_file']) mov_csv2gii = pe.MapNode(CSVToGifti(itk_lps=True), name='mov_csv2gii', iterfield=['in_file', 'gii_file']) mov_surfs_buffer = pe.JoinNode( niu.IdentityInterface(fields=['surfaces']), joinsource='inputnode', joinfield='surfaces', name='mov_surfs_buffer') mov_surfs_unzip = pe.Node(UnzipJoinedSurfaces(), name='mov_surfs_unzip', run_without_submitting=True) mov_ply = pe.MapNode(SurfacesToPointCloud(), name='mov_ply', iterfield=['in_files']) mov_recon = pe.MapNode(PoissonRecon(), name='mov_recon', iterfield=['in_file']) mov_avggii = pe.MapNode(PLYtoGifti(), name='mov_avggii', iterfield=['in_file', 'surf_key']) mov_smooth = pe.MapNode(fs.SmoothTessellation(), name='mov_smooth', iterfield=['in_file']) mov_surfs_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, space=mov_template, keep_dtype=False, compress=False), name='mov_surfs_ds', run_without_submitting=True) mov_avg_ds = pe.Node(DerivativesDataSink( base_directory=str(output_dir.parent), out_path_base=output_dir.name, space=mov_template, keep_dtype=False, compress=False, source_file='group/tpl-{0}_T1w.nii.gz'.format(mov_template)), name='mov_avg_ds', run_without_submitting=True) wf.connect([ (inputnode, fssource, [(('participant_label', _sub_decorate), 'subject_id')]), (inputnode, cifti_wf, [(('participant_label', _sub_decorate), 'inputnode.subject_id')]), (pick_file, cifti_wf, [('out', 'inputnode.in_t1w')]), (pick_file, tonii, [('out', 'reslice_like')]), # Select DKT aparc (fssource, tonative, [(('aparc_aseg', _last), 'seg_file'), ('rawavg', 'template_file'), ('aseg', 'reg_header')]), (tonative, tonii, [('vol_label_file', 'in_file')]), (tonii, ref_aparc, [('out_file', 'input_image')]), (tonii, mov_aparc, [('out_file', 'input_image')]), (ref_norm, ref_aparc, [('composite_transform', 'transforms')]), (mov_norm, mov_aparc, [('composite_transform', 'transforms')]), (ref_buffer, ref_join_labels, [('fixed_image', 'atlas_image')]), (ref_aparc, ref_aparc_buffer, [('output_image', 'aparc')]), (ref_aparc_buffer, ref_join_labels, [('aparc', 'atlas_segmentation_image')]), (mov_buffer, mov_join_labels, [('moving_image', 'atlas_image')]), (mov_aparc, mov_aparc_buffer, [('output_image', 'aparc')]), (mov_aparc_buffer, mov_join_labels, [('aparc', 'atlas_segmentation_image')]), # Datasinks (ref_join_labels, ref_join_labels_ds, [('out_label_fusion', 'in_file')]), (ref_join_labels, ref_join_probs_ds, [('out_label_post_prob', 'in_file'), (('out_label_post_prob', _get_extra), 'extra_values')]), # (ref_join_labels, ref_join_voting_ds, [ # ('out_atlas_voting_weight_name_format', 'in_file')]), (mov_join_labels, mov_join_labels_ds, [('out_label_fusion', 'in_file')]), (mov_join_labels, mov_join_probs_ds, [('out_label_post_prob', 'in_file'), (('out_label_post_prob', _get_extra), 'extra_values')]), (pick_file, ref_aparc_ds, [('out', 'source_file')]), (ref_aparc, ref_aparc_ds, [('output_image', 'in_file')]), (pick_file, mov_aparc_ds, [('out', 'source_file')]), (mov_aparc, mov_aparc_ds, [('output_image', 'in_file')]), # Mapping ref surfaces (cifti_wf, gii2csv, [(('outputnode.surf_norm', _discard_inflated), 'in_file')]), (gii2csv, ref_map_surf, [('out_file', 'input_file')]), (ref_norm, ref_map_surf, [(('inverse_composite_transform', _ensure_list), 'transforms')]), (ref_map_surf, ref_csv2gii, [('output_file', 'in_file')]), (cifti_wf, ref_csv2gii, [(('outputnode.surf_norm', _discard_inflated), 'gii_file')]), (pick_file, ref_surfs_ds, [('out', 'source_file')]), (ref_csv2gii, ref_surfs_ds, [('out_file', 'in_file'), (('out_file', _get_surf_extra), 'extra_values')]), (ref_csv2gii, ref_surfs_buffer, [('out_file', 'surfaces')]), (ref_surfs_buffer, ref_surfs_unzip, [('surfaces', 'in_files')]), (ref_surfs_unzip, ref_ply, [('out_files', 'in_files')]), (ref_ply, ref_recon, [('out_file', 'in_file')]), (ref_recon, ref_avggii, [('out_file', 'in_file')]), (ref_surfs_unzip, ref_avggii, [('surf_keys', 'surf_key')]), (ref_avggii, ref_smooth, [('out_file', 'in_file')]), (ref_smooth, ref_avg_ds, [('surface', 'in_file'), (('surface', _get_surf_extra), 'extra_values')]), # Mapping mov surfaces (gii2csv, mov_map_surf, [('out_file', 'input_file')]), (mov_norm, mov_map_surf, [(('inverse_composite_transform', _ensure_list), 'transforms')]), (mov_map_surf, mov_csv2gii, [('output_file', 'in_file')]), (cifti_wf, mov_csv2gii, [(('outputnode.surf_norm', _discard_inflated), 'gii_file')]), (pick_file, mov_surfs_ds, [('out', 'source_file')]), (mov_csv2gii, mov_surfs_ds, [('out_file', 'in_file'), (('out_file', _get_surf_extra), 'extra_values')]), (mov_csv2gii, mov_surfs_buffer, [('out_file', 'surfaces')]), (mov_surfs_buffer, mov_surfs_unzip, [('surfaces', 'in_files')]), (mov_surfs_unzip, mov_ply, [('out_files', 'in_files')]), (mov_ply, mov_recon, [('out_file', 'in_file')]), (mov_recon, mov_avggii, [('out_file', 'in_file')]), (mov_surfs_unzip, mov_avggii, [('surf_keys', 'surf_key')]), (mov_avggii, mov_smooth, [('out_file', 'in_file')]), (mov_smooth, mov_avg_ds, [('surface', 'in_file'), (('surface', _get_surf_extra), 'extra_values')]), ]) return wf
def init_tract_wf(): tract_wf = pe.Workflow(name="tract_wf") inputnode = pe.Node( niu.IdentityInterface(fields=[ "subject_id", "session_id", "output_dir", "t1_file", "eddy_file", "eddy_mask", "dwi_mask", "bval", "bvec", "template", "atlas", "num_tracts", ]), name="inputnode", ) outputnode = pe.Node( niu.IdentityInterface(fields=[ "tck_file", "prob_weights", "shen_diff_space", "inv_len_conmat", "len_conmat", ]), name="outputnode", ) # Skullstrip the t1, needs to map brain on brain t1_skullstrip = init_brain_extraction_wf() #register T1 to diffusion space first #flirt -dof 6 -in T1w_brain.nii.gz -ref nodif_brain.nii.gz -omat xformT1_2_diff.mat -out T1_diff flirt = pe.Node(fsl.FLIRT(dof=6), name="t1_flirt") to_list = lambda x: [x] # T1 should already be skull stripped and minimally preprocessed (from Freesurfer will do) #5ttgen fsl -nocrop -premasked T1_diff.nii.gz 5TT.mif gen5tt = pe.Node(mrtrix3.Generate5tt(algorithm='fsl', no_crop=True, premasked=True, out_file='5TT.mif'), name="gen5tt") #5tt2gmwmi 5TT.mif gmwmi.mif gen5ttMask = pe.Node(mrtrix3.Generate5ttMask(out_file='gmwmi.mif'), name="gen5ttMask") #SINGLE SHELL # generate response function #dwi2response tournier data.nii.gz -fslgrad data.eddy_rotated_bvecs dwi.bval response.txt responseSD = pe.Node(mrtrix3.ResponseSD(algorithm='tournier'), name="responseSD") # generate FODs #dwi2fod csd data.nii.gz response.txt FOD.mif -mask nodif_brain_mask.nii.gz -fslgrad data.eddy_rotated_bvecs dwi.bval estimateFOD = pe.Node(mrtrix3.EstimateFOD(algorithm='csd', wm_odf='FOD.mif'), name="estimateFOD") # perform probabilistic tractography #tckgen FOD.mif prob.tck -act 5TT.mif -seed_gmwmi gmwmi.mif -select 5000000 ## seeding from a binarised gmwmi tckgen = pe.Node(mrtrix3.Tractography(), name="tckgen") #mrview data.nii.gz -tractography.load prob.tck def gen_tuple(item1, item2): return (item1, item2) gen_tuple = pe.Node( niu.Function( input_names=["item1", "item2"], output_names=["out_tuple"], function=gen_tuple, ), name="gen_tuple", ) #use sift to filter tracks based on spherical harmonics #tcksift2 prob.tck FOD.mif prob_weights.txt tcksift = pe.Node(mrtrix3.TrackSift2(out_weights="prob_weights.txt"), name="tcksift") ## atlas reg #flirt -in T1w_brain.nii.gz -ref MNI152_T1_1mm_brain.nii.gz -omat xformT1_2_MNI.mat pre_atlas_flirt = pe.Node(fsl.FLIRT(), name="pre_atlas_flirt") #convert_xfm -omat xformMNI_2_T1.mat -inverse xformT12MNI.mat xfm_inv = pe.Node(fsl.ConvertXFM(invert_xfm=True), name="xfm_inv") #convert_xfm -omat xformMNI_2_diff.mat -concat xformT1_2_diff.mat xformMNI_2_T1.mat xfm_concat = pe.Node(fsl.ConvertXFM(concat_xfm=True), name="xfm_concat") #flirt -in shen268.nii.gz -ref T1_diff.nii.gz -applyxfm -init xformMNI_2_diff.mat -interp nearestneighbour -out shen_diff_space.nii.gz atlas_flirt = pe.Node(fsl.FLIRT(apply_xfm=True, interp='nearestneighbour'), name="atlas_flirt") ## generate connectivity matrices #tck2connectome prob.tck shen_diff_space.nii.gz conmat_shen.csv -scale_invlength -zero_diagonal -symmetric -tck_weights_in prob_weights.txt -assignment_radial_search 2 -scale_invnodevol conmatgen = pe.Node(mrtrix3.BuildConnectome(out_file="conmat_shen.csv", scale_invlength=True, symmetric=True, zero_diagonal=True, search_radius=2, scale_invnodevol=True), name="conmatgen") #tck2connectome prob.tck shen_diff_space.nii.gz conmat_length_shen.csv -zero_diagonal -symmetric -scale_length -stat_edge mean -assignment_radial_search 2 conmatgen2 = pe.Node(mrtrix3.BuildConnectome( out_file="conmat_length_shen.csv", scale_length=True, symmetric=True, zero_diagonal=True, search_radius=2, stat_edge='mean'), name="conmatgen2") # Convert mifs to niftis fod_convert = pe.Node(mrtrix3.MRConvert(out_filename="FOD.nii.gz"), name="fod_convert") gmwmi_convert = pe.Node(mrtrix3.MRConvert(out_filename="gmwmi.nii.gz"), name="gmwmi_convert") # Initialize output wf datasink = init_tract_output_wf() tract_wf.connect([ # t1 flirt (inputnode, t1_skullstrip, [(("t1_file", to_list), "inputnode.in_files")]), (t1_skullstrip, flirt, [("outputnode.out_file", "in_file")]), (inputnode, flirt, [("dwi_mask", "reference")]), # response function + mask (flirt, gen5tt, [("out_file", "in_file")]), (gen5tt, gen5ttMask, [("out_file", "in_file")]), (inputnode, gen_tuple, [("bvec", "item1"), ("bval", "item2")]), (gen_tuple, responseSD, [("out_tuple", "grad_fsl")]), (inputnode, responseSD, [("eddy_mask", "in_mask")]), (inputnode, responseSD, [ ("eddy_file", "in_file"), ]), # FOD gen (gen_tuple, estimateFOD, [("out_tuple", "grad_fsl")]), (inputnode, estimateFOD, [ ("eddy_file", "in_file"), ]), (responseSD, estimateFOD, [("wm_file", "wm_txt")]), (inputnode, estimateFOD, [("eddy_mask", "mask_file")]), # tckgen (estimateFOD, tckgen, [("wm_odf", "in_file")]), (gen5tt, tckgen, [("out_file", "act_file")]), (gen5ttMask, tckgen, [("out_file", "seed_gmwmi")]), (inputnode, tckgen, [("num_tracts", "select")]), # tcksift (estimateFOD, tcksift, [("wm_odf", "in_fod")]), (tckgen, tcksift, [("out_file", "in_tracks")]), # atlas flirt (inputnode, pre_atlas_flirt, [("t1_file", "in_file"), ("template", "reference")]), (pre_atlas_flirt, xfm_inv, [("out_matrix_file", "in_file")]), (flirt, xfm_concat, [("out_matrix_file", "in_file2")]), (xfm_inv, xfm_concat, [("out_file", "in_file")]), # Shen atlas register (inputnode, atlas_flirt, [("atlas", "in_file")]), (flirt, atlas_flirt, [("out_file", "reference")]), (xfm_concat, atlas_flirt, [("out_file", "in_matrix_file")]), # generate connectivity matrices (tckgen, conmatgen, [("out_file", "in_file")]), (atlas_flirt, conmatgen, [("out_file", "in_parc")]), (tcksift, conmatgen, [("out_weights", "in_weights")]), (tckgen, conmatgen2, [("out_file", "in_file")]), (atlas_flirt, conmatgen2, [("out_file", "in_parc")]), # convert mifs to niftis (gen5tt, fod_convert, [("out_file", "in_file")]), (gen5ttMask, gmwmi_convert, [("out_file", "in_file")]), # outputnode (fod_convert, outputnode, [("converted", "fod_file")]), (gmwmi_convert, outputnode, [("converted", "gmwmi_file")]), (tcksift, outputnode, [("out_weights", "prob_weights")]), (atlas_flirt, outputnode, [("out_file", "shen_diff_space")]), (conmatgen, outputnode, [("out_file", "inv_len_conmat")]), (conmatgen2, outputnode, [("out_file", "len_conmat")]), # datasink (inputnode, datasink, [("subject_id", "inputnode.subject_id"), ("session_id", "inputnode.session_id"), ("output_dir", "inputnode.output_folder")]), (outputnode, datasink, [("fod_file", "inputnode.fod_file"), ("gmwmi_file", "inputnode.gmwmi_file"), ("prob_weights", "inputnode.prob_weights"), ("shen_diff_space", "inputnode.shen_diff_space"), ("inv_len_conmat", "inputnode.inv_len_conmat"), ("len_conmat", "inputnode.len_conmat")]), ]) return tract_wf
def init_anat_preproc_wf(bids_root, freesurfer, hires, longitudinal, omp_nthreads, output_dir, output_spaces, num_t1w, reportlets_dir, skull_strip_template, debug=False, name='anat_preproc_wf', skull_strip_fixed_seed=False): """ This workflow controls the anatomical preprocessing stages of smriprep. This includes: - Creation of a structural template - Skull-stripping and bias correction - Tissue segmentation - Normalization - Surface reconstruction with FreeSurfer .. workflow:: :graph2use: orig :simple_form: yes from collections import OrderedDict from smriprep.workflows.anatomical import init_anat_preproc_wf wf = init_anat_preproc_wf( bids_root='.', freesurfer=True, hires=True, longitudinal=False, num_t1w=1, omp_nthreads=1, output_dir='.', output_spaces=OrderedDict([ ('MNI152NLin2009cAsym', {}), ('fsaverage5', {})]), reportlets_dir='.', skull_strip_template=('MNI152NLin2009cAsym', {}), ) **Parameters** bids_root : str Path of the input BIDS dataset root debug : bool Enable debugging outputs freesurfer : bool Enable FreeSurfer surface reconstruction (increases runtime by 6h, at the very least) output_spaces : list List of spatial normalization targets. Some parts of pipeline will only be instantiated for some output spaces. Valid spaces: - Any template identifier from TemplateFlow - Path to a template folder organized following TemplateFlow's conventions hires : bool Enable sub-millimeter preprocessing in FreeSurfer longitudinal : bool Create unbiased structural template, regardless of number of inputs (may increase runtime) name : str, optional Workflow name (default: anat_preproc_wf) omp_nthreads : int Maximum number of threads an individual process may use output_dir : str Directory in which to save derivatives reportlets_dir : str Directory in which to save reportlets skull_strip_fixed_seed : bool Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 (default: ``False``). skull_strip_template : tuple Name of ANTs skull-stripping template and specifications. **Inputs** t1w List of T1-weighted structural images t2w List of T2-weighted structural images flair List of FLAIR images subjects_dir FreeSurfer SUBJECTS_DIR **Outputs** t1_preproc Bias-corrected structural template, defining T1w space t1_brain Skull-stripped ``t1_preproc`` t1_mask Mask of the skull-stripped template image t1_seg Segmentation of preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) t1_tpms List of tissue probability maps in T1w space t1_2_tpl T1w template, normalized to MNI space t1_2_tpl_forward_transform ANTs-compatible affine-and-warp transform file t1_2_tpl_reverse_transform ANTs-compatible affine-and-warp transform file (inverse) tpl_mask Mask of skull-stripped template, in MNI space tpl_seg Segmentation, resampled into MNI space tpl_tpms List of tissue probability maps in MNI space subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID t1_2_fsnative_forward_transform LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space t1_2_fsnative_reverse_transform LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w surfaces GIFTI surfaces (gray/white boundary, midthickness, pial, inflated) **Subworkflows** * :py:func:`~niworkflows.anat.ants.init_brain_extraction_wf` * :py:func:`~smriprep.workflows.surfaces.init_surface_recon_wf` """ workflow = Workflow(name=name) desc = """Anatomical data preprocessing : """ desc += """\ A total of {num_t1w} T1-weighted (T1w) images were found within the input BIDS dataset. All of them were corrected for intensity non-uniformity (INU) """ if num_t1w > 1 else """\ The T1-weighted (T1w) image was corrected for intensity non-uniformity (INU) """ desc += """\ with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} \ [@ants, RRID:SCR_004757]""" desc += '.\n' if num_t1w > 1 else ", and used as T1w-reference throughout the workflow.\n" desc += """\ The T1w-reference was then skull-stripped with a *Nipype* implementation of the `antsBrainExtraction.sh` workflow (from ANTs), using {skullstrip_tpl} as target template. Brain tissue segmentation of cerebrospinal fluid (CSF), white-matter (WM) and gray-matter (GM) was performed on the brain-extracted T1w using `fast` [FSL {fsl_ver}, RRID:SCR_002823, @fsl_fast]. """ workflow.__desc__ = desc.format( ants_ver=ANTsInfo.version() or '(version unknown)', fsl_ver=fsl.FAST().version or '(version unknown)', num_t1w=num_t1w, skullstrip_tpl=skull_strip_template[0], ) inputnode = pe.Node(niu.IdentityInterface( fields=['t1w', 't2w', 'roi', 'flair', 'subjects_dir', 'subject_id']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 't1_preproc', 't1_brain', 't1_mask', 't1_seg', 't1_tpms', 'template', 'warped', 'forward_transform', 'reverse_transform', 'joint_template', 'joint_forward_transform', 'joint_reverse_transform', 'tpl_mask', 'tpl_seg', 'tpl_tpms', 'template_transforms', 'subjects_dir', 'subject_id', 't1_2_fsnative_forward_transform', 't1_2_fsnative_reverse_transform', 'surfaces', 't1_aseg', 't1_aparc' ]), name='outputnode') buffernode = pe.Node(niu.IdentityInterface(fields=['t1_brain', 't1_mask']), name='buffernode') anat_template_wf = init_anat_template_wf(longitudinal=longitudinal, omp_nthreads=omp_nthreads, num_t1w=num_t1w) anat_validate = pe.Node(ValidateImage(), name='anat_validate', run_without_submitting=True) # 3. Skull-stripping # Bias field correction is handled in skull strip workflows. brain_extraction_wf = init_brain_extraction_wf( in_template=skull_strip_template[0], template_spec=skull_strip_template[1], atropos_use_random_seed=not skull_strip_fixed_seed, omp_nthreads=omp_nthreads, normalization_quality='precise' if not debug else 'testing') workflow.connect([ (inputnode, anat_template_wf, [('t1w', 'inputnode.t1w')]), (anat_template_wf, anat_validate, [('outputnode.t1_template', 'in_file')]), (anat_validate, brain_extraction_wf, [('out_file', 'inputnode.in_files')]), (brain_extraction_wf, outputnode, [('outputnode.bias_corrected', 't1_preproc')]), (anat_template_wf, outputnode, [('outputnode.template_transforms', 't1_template_transforms')]), (buffernode, outputnode, [('t1_brain', 't1_brain'), ('t1_mask', 't1_mask')]), ]) # 4. Surface reconstruction if freesurfer: surface_recon_wf = init_surface_recon_wf(name='surface_recon_wf', omp_nthreads=omp_nthreads, hires=hires) applyrefined = pe.Node(fsl.ApplyMask(), name='applyrefined') workflow.connect([ (inputnode, surface_recon_wf, [('t2w', 'inputnode.t2w'), ('flair', 'inputnode.flair'), ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id')]), (anat_validate, surface_recon_wf, [('out_file', 'inputnode.t1w')]), (brain_extraction_wf, surface_recon_wf, [ (('outputnode.out_file', _pop), 'inputnode.skullstripped_t1'), ('outputnode.out_segm', 'inputnode.ants_segs'), (('outputnode.bias_corrected', _pop), 'inputnode.corrected_t1') ]), (brain_extraction_wf, applyrefined, [(('outputnode.bias_corrected', _pop), 'in_file')]), (surface_recon_wf, applyrefined, [('outputnode.out_brainmask', 'mask_file')]), (surface_recon_wf, outputnode, [('outputnode.subjects_dir', 'subjects_dir'), ('outputnode.subject_id', 'subject_id'), ('outputnode.t1_2_fsnative_forward_transform', 't1_2_fsnative_forward_transform'), ('outputnode.t1_2_fsnative_reverse_transform', 't1_2_fsnative_reverse_transform'), ('outputnode.surfaces', 'surfaces'), ('outputnode.out_aseg', 't1_aseg'), ('outputnode.out_aparc', 't1_aparc')]), (applyrefined, buffernode, [('out_file', 't1_brain')]), (surface_recon_wf, buffernode, [('outputnode.out_brainmask', 't1_mask')]), ]) else: workflow.connect([ (brain_extraction_wf, buffernode, [(('outputnode.out_file', _pop), 't1_brain'), ('outputnode.out_mask', 't1_mask')]), ]) # 5. Segmentation t1_seg = pe.Node(fsl.FAST(segments=True, no_bias=True, probability_maps=True), name='t1_seg', mem_gb=3) workflow.connect([ (buffernode, t1_seg, [('t1_brain', 'in_files')]), (t1_seg, outputnode, [('tissue_class_map', 't1_seg'), ('probability_maps', 't1_tpms')]), ]) seg_rpt = pe.Node(ROIsPlot(colors=['magenta', 'b'], levels=[1.5, 2.5]), name='seg_rpt') vol_spaces = [k for k in output_spaces.keys() if not k.startswith('fs')] # 6. Spatial normalization anat_norm_wf = init_anat_norm_wf( debug=debug, omp_nthreads=omp_nthreads, reportlets_dir=reportlets_dir, template_list=vol_spaces, template_specs=[output_spaces[k] for k in vol_spaces]) workflow.connect([ (inputnode, anat_norm_wf, [(('t1w', fix_multi_T1w_source_name), 'inputnode.orig_t1w'), ('roi', 'inputnode.lesion_mask')]), (brain_extraction_wf, anat_norm_wf, [(('outputnode.bias_corrected', _pop), 'inputnode.moving_image')]), (buffernode, anat_norm_wf, [('t1_mask', 'inputnode.moving_mask')]), (t1_seg, anat_norm_wf, [('tissue_class_map', 'inputnode.moving_segmentation')]), (t1_seg, anat_norm_wf, [('probability_maps', 'inputnode.moving_tpms') ]), (anat_norm_wf, outputnode, [ ('poutputnode.warped', 'warped'), ('poutputnode.template', 'template'), ('poutputnode.forward_transform', 'forward_transform'), ('poutputnode.reverse_transform', 'reverse_transform'), ('poutputnode.tpl_mask', 'tpl_mask'), ('poutputnode.tpl_seg', 'tpl_seg'), ('poutputnode.tpl_tpms', 'tpl_tpms'), ('outputnode.template', 'joint_template'), ('outputnode.forward_transform', 'joint_forward_transform'), ('outputnode.reverse_transform', 'joint_reverse_transform'), ]), ]) anat_reports_wf = init_anat_reports_wf(reportlets_dir=reportlets_dir, freesurfer=freesurfer) workflow.connect([ (inputnode, anat_reports_wf, [(('t1w', fix_multi_T1w_source_name), 'inputnode.source_file')]), (anat_template_wf, anat_reports_wf, [('outputnode.out_report', 'inputnode.t1_conform_report')]), (anat_template_wf, seg_rpt, [('outputnode.t1_template', 'in_file')]), (t1_seg, seg_rpt, [('tissue_class_map', 'in_rois')]), (outputnode, seg_rpt, [('t1_mask', 'in_mask')]), (seg_rpt, anat_reports_wf, [('out_report', 'inputnode.seg_report')]), ]) if freesurfer: workflow.connect([ (surface_recon_wf, anat_reports_wf, [('outputnode.out_report', 'inputnode.recon_report')]), ]) anat_derivatives_wf = init_anat_derivatives_wf( bids_root=bids_root, freesurfer=freesurfer, output_dir=output_dir, ) workflow.connect([ (anat_template_wf, anat_derivatives_wf, [('outputnode.t1w_valid_list', 'inputnode.source_files')]), (anat_norm_wf, anat_derivatives_wf, [('poutputnode.template', 'inputnode.template')]), (outputnode, anat_derivatives_wf, [ ('warped', 'inputnode.t1_2_tpl'), ('forward_transform', 'inputnode.t1_2_tpl_forward_transform'), ('reverse_transform', 'inputnode.t1_2_tpl_reverse_transform'), ('t1_template_transforms', 'inputnode.t1_template_transforms'), ('t1_preproc', 'inputnode.t1_preproc'), ('t1_mask', 'inputnode.t1_mask'), ('t1_seg', 'inputnode.t1_seg'), ('t1_tpms', 'inputnode.t1_tpms'), ('tpl_mask', 'inputnode.tpl_mask'), ('tpl_seg', 'inputnode.tpl_seg'), ('tpl_tpms', 'inputnode.tpl_tpms'), ('t1_2_fsnative_forward_transform', 'inputnode.t1_2_fsnative_forward_transform'), ('surfaces', 'inputnode.surfaces'), ]), ]) if freesurfer: workflow.connect([ (surface_recon_wf, anat_derivatives_wf, [ ('outputnode.out_aseg', 'inputnode.t1_fs_aseg'), ('outputnode.out_aparc', 'inputnode.t1_fs_aparc'), ]), ]) return workflow