def init_anat_norm_wf( *, debug, omp_nthreads, templates, name="anat_norm_wf", ): """ Build an individual spatial normalization workflow using ``antsRegistration``. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from smriprep.workflows.norm import init_anat_norm_wf wf = init_anat_norm_wf( debug=False, omp_nthreads=1, templates=['MNI152NLin2009cAsym', 'MNI152NLin6Asym'], ) .. important:: This workflow defines an iterable input over the input parameter ``templates``, so Nipype will produce one copy of the downstream workflows which connect ``poutputnode.template`` or ``poutputnode.template_spec`` to their inputs (``poutputnode`` stands for *parametric output node*). Nipype refers to this expansion of the graph as *parameterized execution*. If a joint list of values is required (and thus cutting off parameterization), please use the equivalent outputs of ``outputnode`` (which *joins* all the parameterized execution paths). Parameters ---------- debug : :obj:`bool` Apply sloppy arguments to speed up processing. Use with caution, registration processes will be very inaccurate. omp_nthreads : :obj:`int` Maximum number of threads an individual process may use. templates : :obj:`list` of :obj:`str` List of standard space fullnames (e.g., ``MNI152NLin6Asym`` or ``MNIPediatricAsym:cohort-4``) which are targets for spatial normalization. Inputs ------ moving_image The input image that will be normalized to standard space. moving_mask A precise brain mask separating skull/skin/fat from brain structures. moving_segmentation A brain tissue segmentation of the ``moving_image``. moving_tpms tissue probability maps (TPMs) corresponding to the ``moving_segmentation``. lesion_mask (optional) A mask to exclude regions from the cost-function input domain to enable standardization of lesioned brains. orig_t1w The original T1w image from the BIDS structure. template Template name and specification Outputs ------- standardized The T1w after spatial normalization, in template space. anat2std_xfm The T1w-to-template transform. std2anat_xfm The template-to-T1w transform. std_mask The ``moving_mask`` in template space (matches ``standardized`` output). std_dseg The ``moving_segmentation`` in template space (matches ``standardized`` output). std_tpms The ``moving_tpms`` in template space (matches ``standardized`` output). template Template name extracted from the input parameter ``template``, for further use in downstream nodes. template_spec Template specifications extracted from the input parameter ``template``, for further use in downstream nodes. """ ntpls = len(templates) workflow = Workflow(name=name) if templates: workflow.__desc__ = """\ Volume-based spatial normalization to {targets} ({targets_id}) was performed through nonlinear registration with `antsRegistration` (ANTs {ants_ver}), using brain-extracted versions of both T1w reference and the T1w template. The following template{tpls} selected for spatial normalization: """.format( ants_ver=ANTsInfo.version() or "(version unknown)", targets="%s standard space%s" % ( defaultdict("several".format, { 1: "one", 2: "two", 3: "three", 4: "four" })[ntpls], "s" * (ntpls != 1), ), targets_id=", ".join(templates), tpls=(" was", "s were")[ntpls != 1], ) # Append template citations to description for template in templates: template_meta = get_metadata(template.split(":")[0]) template_refs = ["@%s" % template.split(":")[0].lower()] if template_meta.get("RRID", None): template_refs += ["RRID:%s" % template_meta["RRID"]] workflow.__desc__ += """\ *{template_name}* [{template_refs}; TemplateFlow ID: {template}]""".format( template=template, template_name=template_meta["Name"], template_refs=", ".join(template_refs), ) workflow.__desc__ += ".\n" if template == templates[-1] else ", " inputnode = pe.Node( niu.IdentityInterface(fields=[ "lesion_mask", "moving_image", "moving_mask", "moving_segmentation", "moving_tpms", "orig_t1w", "template", ]), name="inputnode", ) inputnode.iterables = [("template", templates)] out_fields = [ "anat2std_xfm", "standardized", "std2anat_xfm", "std_dseg", "std_mask", "std_tpms", "template", "template_spec", ] poutputnode = pe.Node(niu.IdentityInterface(fields=out_fields), name="poutputnode") split_desc = pe.Node(TemplateDesc(), run_without_submitting=True, name="split_desc") tf_select = pe.Node( TemplateFlowSelect(resolution=1 + debug), name="tf_select", run_without_submitting=True, ) # With the improvements from nipreps/niworkflows#342 this truncation is now necessary trunc_mov = pe.Node( ants.ImageMath(operation="TruncateImageIntensity", op2="0.01 0.999 256"), name="trunc_mov", ) registration = pe.Node( SpatialNormalization( float=True, flavor=["precise", "testing"][debug], ), name="registration", n_procs=omp_nthreads, mem_gb=2, ) # Resample T1w-space inputs tpl_moving = pe.Node( ApplyTransforms( dimension=3, default_value=0, float=True, interpolation="LanczosWindowedSinc", ), name="tpl_moving", ) std_mask = pe.Node(ApplyTransforms(interpolation="MultiLabel"), name="std_mask") std_dseg = pe.Node(ApplyTransforms(interpolation="MultiLabel"), name="std_dseg") std_tpms = pe.MapNode( ApplyTransforms(dimension=3, default_value=0, float=True, interpolation="Gaussian"), iterfield=["input_image"], name="std_tpms", ) # fmt:off workflow.connect([ (inputnode, split_desc, [('template', 'template')]), (inputnode, poutputnode, [('template', 'template')]), (inputnode, trunc_mov, [('moving_image', 'op1')]), (inputnode, registration, [('moving_mask', 'moving_mask'), ('lesion_mask', 'lesion_mask')]), (inputnode, tpl_moving, [('moving_image', 'input_image')]), (inputnode, std_mask, [('moving_mask', 'input_image')]), (split_desc, tf_select, [('name', 'template'), ('spec', 'template_spec')]), (split_desc, registration, [('name', 'template'), ('spec', 'template_spec')]), (tf_select, tpl_moving, [('t1w_file', 'reference_image')]), (tf_select, std_mask, [('t1w_file', 'reference_image')]), (tf_select, std_dseg, [('t1w_file', 'reference_image')]), (tf_select, std_tpms, [('t1w_file', 'reference_image')]), (trunc_mov, registration, [('output_image', 'moving_image')]), (registration, tpl_moving, [('composite_transform', 'transforms')]), (registration, std_mask, [('composite_transform', 'transforms')]), (inputnode, std_dseg, [('moving_segmentation', 'input_image')]), (registration, std_dseg, [('composite_transform', 'transforms')]), (inputnode, std_tpms, [('moving_tpms', 'input_image')]), (registration, std_tpms, [('composite_transform', 'transforms')]), (registration, poutputnode, [('composite_transform', 'anat2std_xfm'), ('inverse_composite_transform', 'std2anat_xfm')]), (tpl_moving, poutputnode, [('output_image', 'standardized')]), (std_mask, poutputnode, [('output_image', 'std_mask')]), (std_dseg, poutputnode, [('output_image', 'std_dseg')]), (std_tpms, poutputnode, [('output_image', 'std_tpms')]), (split_desc, poutputnode, [('spec', 'template_spec')]), ]) # fmt:on # Provide synchronized output outputnode = pe.JoinNode( niu.IdentityInterface(fields=out_fields), name="outputnode", joinsource="inputnode", ) # fmt:off workflow.connect([ (poutputnode, outputnode, [(f, f) for f in out_fields]), ]) # fmt:on return workflow
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_norm_wf( *, debug, omp_nthreads, templates, name="anat_norm_wf", ): """ Build an individual spatial normalization workflow using ``antsRegistration``. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from fmriprep_rodents.patch.workflows.anatomical import init_anat_norm_wf wf = init_anat_norm_wf( debug=False, omp_nthreads=1, templates=['Fischer344'], ) .. important:: This workflow defines an iterable input over the input parameter ``templates``, so Nipype will produce one copy of the downstream workflows which connect ``poutputnode.template`` or ``poutputnode.template_spec`` to their inputs (``poutputnode`` stands for *parametric output node*). Nipype refers to this expansion of the graph as *parameterized execution*. If a joint list of values is required (and thus cutting off parameterization), please use the equivalent outputs of ``outputnode`` (which *joins* all the parameterized execution paths). Parameters ---------- debug : :obj:`bool` Apply sloppy arguments to speed up processing. Use with caution, registration processes will be very inaccurate. omp_nthreads : :obj:`int` Maximum number of threads an individual process may use. templates : :obj:`list` of :obj:`str` List of standard space fullnames (e.g., ``MNI152NLin6Asym`` or ``MNIPediatricAsym:cohort-4``) which are targets for spatial normalization. Inputs ------ moving_image The input image that will be normalized to standard space. moving_mask A precise brain mask separating skull/skin/fat from brain structures. moving_segmentation A brain tissue segmentation of the ``moving_image``. moving_tpms tissue probability maps (TPMs) corresponding to the ``moving_segmentation``. lesion_mask (optional) A mask to exclude regions from the cost-function input domain to enable standardization of lesioned brains. orig_t1w The original T1w image from the BIDS structure. template Template name and specification Outputs ------- standardized The T1w after spatial normalization, in template space. anat2std_xfm The T1w-to-template transform. std2anat_xfm The template-to-T1w transform. std_mask The ``moving_mask`` in template space (matches ``standardized`` output). std_dseg The ``moving_segmentation`` in template space (matches ``standardized`` output). std_tpms The ``moving_tpms`` in template space (matches ``standardized`` output). template Template name extracted from the input parameter ``template``, for further use in downstream nodes. template_spec Template specifications extracted from the input parameter ``template``, for further use in downstream nodes. """ from collections import defaultdict from nipype.interfaces.ants import ImageMath from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms from smriprep.interfaces.templateflow import TemplateDesc from ..interfaces import RobustMNINormalization ntpls = len(templates) workflow = Workflow(name=name) if templates: workflow.__desc__ = """\ Volume-based spatial normalization to {targets} ({targets_id}) was performed through nonlinear registration with `antsRegistration` (ANTs {ants_ver}), using brain-extracted versions of both T1w reference and the T1w template. The following template{tpls} selected for spatial normalization: """.format( ants_ver=ANTsInfo.version() or '(version unknown)', targets='%s standard space%s' % (defaultdict( 'several'.format, {1: 'one', 2: 'two', 3: 'three', 4: 'four'})[ntpls], 's' * (ntpls != 1)), targets_id=', '.join(templates), tpls=(' was', 's were')[ntpls != 1] ) # Append template citations to description for template in templates: template_meta = get_metadata(template.split(':')[0]) template_refs = ['@%s' % template.split(':')[0].lower()] if template_meta.get('RRID', None): template_refs += ['RRID:%s' % template_meta['RRID']] workflow.__desc__ += """\ *{template_name}* [{template_refs}; TemplateFlow ID: {template}]""".format( template=template, template_name=template_meta['Name'], template_refs=', '.join(template_refs) ) workflow.__desc__ += (', ', '.')[template == templates[-1][0]] inputnode = pe.Node(niu.IdentityInterface(fields=[ 'lesion_mask', 'moving_image', 'moving_mask', 'moving_segmentation', 'moving_tpms', 'orig_t1w', 'template', ]), name='inputnode') inputnode.iterables = [('template', templates)] out_fields = [ 'anat2std_xfm', 'standardized', 'std2anat_xfm', 'std_dseg', 'std_mask', 'std_tpms', 'template', 'template_spec', ] poutputnode = pe.Node(niu.IdentityInterface(fields=out_fields), name='poutputnode') split_desc = pe.Node(TemplateDesc(), run_without_submitting=True, name='split_desc') tf_select = pe.Node(TemplateFlowSelect(), name='tf_select', run_without_submitting=True) # With the improvements from nipreps/niworkflows#342 this truncation is now necessary trunc_mov = pe.Node(ImageMath(operation='TruncateImageIntensity', op2='0.01 0.999 256'), name='trunc_mov') registration = pe.Node(RobustMNINormalization( float=True, flavor=['precise', 'testing'][debug], ), name='registration', n_procs=omp_nthreads, mem_gb=2) # Resample T1w-space inputs tpl_moving = pe.Node(ApplyTransforms( dimension=3, default_value=0, float=True, interpolation='LanczosWindowedSinc'), name='tpl_moving') std_mask = pe.Node(ApplyTransforms(interpolation='MultiLabel'), name='std_mask') std_dseg = pe.Node(ApplyTransforms(interpolation='MultiLabel'), name='std_dseg') std_tpms = pe.MapNode(ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='Gaussian'), iterfield=['input_image'], name='std_tpms') workflow.connect([ (inputnode, split_desc, [('template', 'template')]), (inputnode, poutputnode, [('template', 'template')]), (inputnode, trunc_mov, [('moving_image', 'op1')]), (inputnode, registration, [ ('moving_mask', 'moving_mask'), ('lesion_mask', 'lesion_mask')]), (inputnode, tpl_moving, [('moving_image', 'input_image')]), (inputnode, std_mask, [('moving_mask', 'input_image')]), (split_desc, tf_select, [('name', 'template'), ('spec', 'template_spec')]), (split_desc, registration, [('name', 'template'), (('spec', _no_atlas), 'template_spec')]), (tf_select, tpl_moving, [('t2w_file', 'reference_image')]), (tf_select, std_mask, [('t2w_file', 'reference_image')]), (tf_select, std_dseg, [('t2w_file', 'reference_image')]), (tf_select, std_tpms, [('t2w_file', 'reference_image')]), (trunc_mov, registration, [ ('output_image', 'moving_image')]), (registration, tpl_moving, [('composite_transform', 'transforms')]), (registration, std_mask, [('composite_transform', 'transforms')]), (inputnode, std_dseg, [('moving_segmentation', 'input_image')]), (registration, std_dseg, [('composite_transform', 'transforms')]), (inputnode, std_tpms, [('moving_tpms', 'input_image')]), (registration, std_tpms, [('composite_transform', 'transforms')]), (registration, poutputnode, [ ('composite_transform', 'anat2std_xfm'), ('inverse_composite_transform', 'std2anat_xfm')]), (tpl_moving, poutputnode, [('output_image', 'standardized')]), (std_mask, poutputnode, [('output_image', 'std_mask')]), (std_dseg, poutputnode, [('output_image', 'std_dseg')]), (std_tpms, poutputnode, [('output_image', 'std_tpms')]), (split_desc, poutputnode, [('spec', 'template_spec')]), ]) # Provide synchronized output outputnode = pe.JoinNode(niu.IdentityInterface(fields=out_fields), name='outputnode', joinsource='inputnode') workflow.connect([ (poutputnode, outputnode, [(f, f) for f in out_fields]), ]) 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
import pytest from ..itk import _applytfms from nipype.interfaces.ants.base import Info @pytest.mark.skipif(Info.version() is None, reason="Missing ANTs") @pytest.mark.parametrize("ext", (".nii", ".nii.gz")) @pytest.mark.parametrize("copy_dtype", (True, False)) @pytest.mark.parametrize("in_dtype", ("i2", "f4")) def test_applytfms(tmpdir, ext, copy_dtype, in_dtype): import numpy as np import nibabel as nb from pkg_resources import resource_filename as pkgr_fn in_file = str(tmpdir / ("src" + ext)) nii = nb.Nifti1Image(np.zeros((5, 5, 5), dtype=np.float32), np.eye(4)) nii.set_data_dtype(in_dtype) nii.to_filename(in_file) in_xform = pkgr_fn("niworkflows", "data/itkIdentityTransform.txt") ifargs = {"copy_dtype": copy_dtype, "reference_image": in_file} args = (in_file, in_xform, ifargs, 0, str(tmpdir)) out_file, cmdline = _applytfms(args) assert out_file == str(tmpdir / ("src_xform-%05d%s" % (0, ext))) out_nii = nb.load(out_file) assert np.allclose(nii.affine, out_nii.affine) assert np.allclose(nii.get_fdata(), out_nii.get_fdata()) if copy_dtype:
def init_anat_norm_wf( debug, omp_nthreads, templates, ): """ Build an individual spatial normalization workflow using ``antsRegistration``. .. workflow :: :graph2use: orig :simple_form: yes from smriprep.workflows.norm import init_anat_norm_wf wf = init_anat_norm_wf( debug=False, omp_nthreads=1, templates=[('MNI152NLin2009cAsym', {}), ('MNI152NLin6Asym', {})], ) **Parameters** debug : bool Apply sloppy arguments to speed up processing. Use with caution, registration processes will be very inaccurate. omp_nthreads : int Maximum number of threads an individual process may use. templates : list of tuples List of tuples containing TemplateFlow identifiers (e.g. ``MNI152NLin6Asym``) and corresponding specs, which specify target templates for spatial normalization. **Inputs** moving_image The input image that will be normalized to standard space. moving_mask A precise brain mask separating skull/skin/fat from brain structures. moving_segmentation A brain tissue segmentation of the ``moving_image``. moving_tpms tissue probability maps (TPMs) corresponding to the ``moving_segmentation``. lesion_mask (optional) A mask to exclude regions from the cost-function input domain to enable standardization of lesioned brains. orig_t1w The original T1w image from the BIDS structure. **Outputs** standardized The T1w after spatial normalization, in template space. anat2std_xfm The T1w-to-template transform. std2anat_xfm The template-to-T1w transform. std_mask The ``moving_mask`` in template space (matches ``standardized`` output). std_dseg The ``moving_segmentation`` in template space (matches ``standardized`` output). std_tpms The ``moving_tpms`` in template space (matches ``standardized`` output). template The input parameter ``template`` for further use in nodes depending on this workflow. """ templateflow = get_templates() missing_tpls = [ template for template, _ in templates if template not in templateflow ] if missing_tpls: raise ValueError("""\ One or more templates were not found (%s). Please make sure TemplateFlow is \ correctly installed and contains the given template identifiers.""" % ', '.join(missing_tpls)) ntpls = len(templates) workflow = Workflow('anat_norm_wf') workflow.__desc__ = """\ Volume-based spatial normalization to {targets} ({targets_id}) was performed through nonlinear registration with `antsRegistration` (ANTs {ants_ver}), using brain-extracted versions of both T1w reference and the T1w template. The following template{tpls} selected for spatial normalization: """.format(ants_ver=ANTsInfo.version() or '(version unknown)', targets='%s standard space%s' % (defaultdict('several'.format, { 1: 'one', 2: 'two', 3: 'three', 4: 'four' })[ntpls], 's' * (ntpls != 1)), targets_id=', '.join((t for t, _ in templates)), tpls=(' was', 's were')[ntpls != 1]) # Append template citations to description for template, _ in templates: template_meta = get_metadata(template) template_refs = ['@%s' % template.lower()] if template_meta.get('RRID', None): template_refs += ['RRID:%s' % template_meta['RRID']] workflow.__desc__ += """\ *{template_name}* [{template_refs}; TemplateFlow ID: {template}]""".format( template=template, template_name=template_meta['Name'], template_refs=', '.join(template_refs)) workflow.__desc__ += (', ', '.')[template == templates[-1][0]] inputnode = pe.Node(niu.IdentityInterface(fields=[ 'moving_image', 'moving_mask', 'moving_segmentation', 'moving_tpms', 'lesion_mask', 'orig_t1w', 'template' ]), name='inputnode') inputnode.iterables = [('template', templates)] out_fields = [ 'standardized', 'anat2std_xfm', 'std2anat_xfm', 'std_mask', 'std_dseg', 'std_tpms', 'template' ] poutputnode = pe.Node(niu.IdentityInterface(fields=out_fields), name='poutputnode') tf_select = pe.Node(TemplateFlowSelect(resolution=1 + debug), name='tf_select', run_without_submitting=True) # With the improvements from poldracklab/niworkflows#342 this truncation is now necessary trunc_mov = pe.Node(ImageMath(operation='TruncateImageIntensity', op2='0.01 0.999 256'), name='trunc_mov') registration = pe.Node(RobustMNINormalization( float=True, flavor=['precise', 'testing'][debug], ), name='registration', n_procs=omp_nthreads, mem_gb=2) # Resample T1w-space inputs tpl_moving = pe.Node(ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='LanczosWindowedSinc'), name='tpl_moving') std_mask = pe.Node(ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='MultiLabel'), name='std_mask') std_dseg = pe.Node(ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='MultiLabel'), name='std_dseg') std_tpms = pe.MapNode(ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='Gaussian'), iterfield=['input_image'], name='std_tpms') workflow.connect([ (inputnode, tf_select, [(('template', _get_name), 'template'), (('template', _get_spec), 'template_spec')]), (inputnode, registration, [(('template', _get_name), 'template'), (('template', _get_spec), 'template_spec') ]), (inputnode, trunc_mov, [('moving_image', 'op1')]), (inputnode, registration, [('moving_mask', 'moving_mask'), ('lesion_mask', 'lesion_mask')]), (inputnode, tpl_moving, [('moving_image', 'input_image')]), (inputnode, std_mask, [('moving_mask', 'input_image')]), (tf_select, tpl_moving, [('t1w_file', 'reference_image')]), (tf_select, std_mask, [('t1w_file', 'reference_image')]), (tf_select, std_dseg, [('t1w_file', 'reference_image')]), (tf_select, std_tpms, [('t1w_file', 'reference_image')]), (trunc_mov, registration, [('output_image', 'moving_image')]), (registration, tpl_moving, [('composite_transform', 'transforms')]), (registration, std_mask, [('composite_transform', 'transforms')]), (inputnode, std_dseg, [('moving_segmentation', 'input_image')]), (registration, std_dseg, [('composite_transform', 'transforms')]), (inputnode, std_tpms, [('moving_tpms', 'input_image')]), (registration, std_tpms, [('composite_transform', 'transforms')]), (registration, poutputnode, [('composite_transform', 'anat2std_xfm'), ('inverse_composite_transform', 'std2anat_xfm')]), (tpl_moving, poutputnode, [('output_image', 'standardized')]), (std_mask, poutputnode, [('output_image', 'std_mask')]), (std_dseg, poutputnode, [('output_image', 'std_dseg')]), (std_tpms, poutputnode, [('output_image', 'std_tpms')]), (inputnode, poutputnode, [('template', 'template')]), ]) # Provide synchronized output outputnode = pe.JoinNode(niu.IdentityInterface(fields=out_fields), name='outputnode', joinsource='inputnode') workflow.connect([ (poutputnode, outputnode, [(f, f) for f in out_fields]), ]) 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 init_anat_norm_wf( debug, omp_nthreads, reportlets_dir, template_list, template_specs=None, ): """ An individual spatial normalization workflow using ``antsRegistration``. .. workflow :: :graph2use: orig :simple_form: yes from smriprep.workflows.norm import init_anat_norm_wf wf = init_anat_norm_wf( debug=False, omp_nthreads=1, reportlets_dir='.', template_list=['MNI152NLin2009cAsym', 'MNI152NLin6Asym'], ) **Parameters** debug : bool Apply sloppy arguments to speed up processing. Use with caution, registration processes will be very inaccurate. omp_nthreads : int Maximum number of threads an individual process may use. reportlets_dir : str Directory in which to save reportlets. template_list : list of str List of TemplateFlow identifiers (e.g. ``MNI152NLin6Asym``) that specifies the target template for spatial normalization. In the future, this parameter should accept also paths to custom/private templates with TemplateFlow's organization. **Inputs** moving_image The input image that will be normalized to standard space. moving_mask A precise brain mask separating skull/skin/fat from brain structures. moving_segmentation A brain tissue segmentation of the ``moving_image``. moving_tpms tissue probability maps (TPMs) corresponding to the ``moving_segmentation``. lesion_mask (optional) A mask to exclude regions from the cost-function input domain to enable standardization of lesioned brains. orig_t1w The original T1w image from the BIDS structure. **Outputs** warped The T1w after spatial normalization, in template space. forward_transform The T1w-to-template transform. reverse_transform The template-to-T1w transform. tpl_mask The ``moving_mask`` in template space (matches ``warped`` output). tpl_seg The ``moving_segmentation`` in template space (matches ``warped`` output). tpl_tpms The ``moving_tpms`` in template space (matches ``warped`` output). template The input parameter ``template`` for further use in nodes depending on this workflow. """ if not isinstance(template_list, (list, tuple)): template_list = [template_list] templateflow = templates() if any(template not in templateflow for template in template_list): raise NotImplementedError( 'This is embarrassing - custom templates are not (yet) supported.' 'Please make sure none of the options already available via TemplateFlow ' 'fit your needs.') workflow = Workflow('anat_norm_wf') workflow.__desc__ = """\ Volume-based spatial normalization to {targets} ({targets_id}) was performed through nonlinear registration with `antsRegistration` (ANTs {ants_ver}), using brain-extracted versions of both T1w reference and the T1w template. The following template{tpls} selected for spatial normalization: """.format(ants_ver=ANTsInfo.version() or '(version unknown)', targets='%s standard space%s' % (defaultdict('several'.format, { 1: 'one', 2: 'two', 3: 'three', 4: 'four' })[len(template_list)], 's' * (len(template_list) != 1)), targets_id=', '.join(template_list), tpls=(' was', 's were')[len(template_list) != 1]) if not template_specs: template_specs = [{}] * len(template_list) if len(template_list) != len(template_specs): raise RuntimeError( 'Number of templates (%d) doesn\'t match the number of specs ' '(%d) provided.' % (len(template_list), len(template_specs))) # Append template citations to description for template in template_list: template_meta = get_metadata(template) template_refs = ['@%s' % template.lower()] if template_meta.get('RRID', None): template_refs += ['RRID:%s' % template_meta['RRID']] workflow.__desc__ += """\ *{template_name}* [{template_refs}; TemplateFlow ID: {template}]""".format( template=template, template_name=template_meta['Name'], template_refs=', '.join(template_refs)) workflow.__desc__ += (', ', '.')[template == template_list[-1]] inputnode = pe.Node(niu.IdentityInterface(fields=[ 'moving_image', 'moving_mask', 'moving_segmentation', 'moving_tpms', 'lesion_mask', 'orig_t1w', 'template' ]), name='inputnode') inputnode.iterables = [('template', template_list)] out_fields = [ 'warped', 'forward_transform', 'reverse_transform', 'tpl_mask', 'tpl_seg', 'tpl_tpms', 'template' ] poutputnode = pe.Node(niu.IdentityInterface(fields=out_fields), name='poutputnode') tpl_specs = pe.Node(niu.Function(function=_select_specs), name='tpl_specs', run_without_submitting=True) tpl_specs.inputs.template_list = template_list tpl_specs.inputs.template_specs = template_specs tpl_select = pe.Node(niu.Function(function=_get_template), name='tpl_select', run_without_submitting=True) # With the improvements from poldracklab/niworkflows#342 this truncation is now necessary trunc_mov = pe.Node(ImageMath(operation='TruncateImageIntensity', op2='0.01 0.999 256'), name='trunc_mov') registration = pe.Node(RobustMNINormalization( float=True, flavor=['precise', 'testing'][debug], ), name='registration', n_procs=omp_nthreads, mem_gb=2) # Resample T1w-space inputs tpl_moving = pe.Node(ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='LanczosWindowedSinc'), name='tpl_moving') tpl_mask = pe.Node(ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='MultiLabel'), name='tpl_mask') tpl_seg = pe.Node(ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='MultiLabel'), name='tpl_seg') tpl_tpms = pe.MapNode(ApplyTransforms(dimension=3, default_value=0, float=True, interpolation='Gaussian'), iterfield=['input_image'], name='tpl_tpms') workflow.connect([ (inputnode, tpl_specs, [('template', 'template')]), (inputnode, tpl_select, [('template', 'template')]), (inputnode, registration, [('template', 'template')]), (inputnode, trunc_mov, [('moving_image', 'op1')]), (inputnode, registration, [('moving_mask', 'moving_mask'), ('lesion_mask', 'lesion_mask')]), (inputnode, tpl_moving, [('moving_image', 'input_image')]), (inputnode, tpl_mask, [('moving_mask', 'input_image')]), (tpl_specs, tpl_select, [('out', 'template_spec')]), (tpl_specs, registration, [(('out', _drop_res), 'template_spec')]), (tpl_select, tpl_moving, [('out', 'reference_image')]), (tpl_select, tpl_mask, [('out', 'reference_image')]), (tpl_select, tpl_seg, [('out', 'reference_image')]), (tpl_select, tpl_tpms, [('out', 'reference_image')]), (trunc_mov, registration, [('output_image', 'moving_image')]), (registration, tpl_moving, [('composite_transform', 'transforms')]), (registration, tpl_mask, [('composite_transform', 'transforms')]), (inputnode, tpl_seg, [('moving_segmentation', 'input_image')]), (registration, tpl_seg, [('composite_transform', 'transforms')]), (inputnode, tpl_tpms, [('moving_tpms', 'input_image')]), (registration, tpl_tpms, [('composite_transform', 'transforms')]), (registration, poutputnode, [('composite_transform', 'forward_transform'), ('inverse_composite_transform', 'reverse_transform')]), (tpl_moving, poutputnode, [('output_image', 'warped')]), (tpl_mask, poutputnode, [('output_image', 'tpl_mask')]), (tpl_seg, poutputnode, [('output_image', 'tpl_seg')]), (tpl_tpms, poutputnode, [('output_image', 'tpl_tpms')]), (inputnode, poutputnode, [('template', 'template')]), ]) # Generate and store report msk_select = pe.Node(niu.Function( function=_get_template, input_names=['template', 'template_spec', 'suffix', 'desc']), name='msk_select', run_without_submitting=True) msk_select.inputs.desc = 'brain' msk_select.inputs.suffix = 'mask' norm_msk = pe.Node(niu.Function( function=_rpt_masks, output_names=['before', 'after'], input_names=['mask_file', 'before', 'after', 'after_mask']), name='norm_msk') norm_rpt = pe.Node(SimpleBeforeAfter(), name='norm_rpt', mem_gb=0.1) norm_rpt.inputs.after_label = 'Participant' # after ds_t1_2_tpl_report = pe.Node(DerivativesDataSink( base_directory=reportlets_dir, keep_dtype=True), name='ds_t1_2_tpl_report', run_without_submitting=True) workflow.connect([ (inputnode, msk_select, [('template', 'template')]), (inputnode, norm_rpt, [('template', 'before_label')]), (tpl_mask, norm_msk, [('output_image', 'after_mask')]), (tpl_specs, msk_select, [('out', 'template_spec')]), (msk_select, norm_msk, [('out', 'mask_file')]), (tpl_select, norm_msk, [('out', 'before')]), (tpl_moving, norm_msk, [('output_image', 'after')]), (norm_msk, norm_rpt, [('before', 'before'), ('after', 'after')]), (inputnode, ds_t1_2_tpl_report, [('template', 'space'), ('orig_t1w', 'source_file')]), (norm_rpt, ds_t1_2_tpl_report, [('out_report', 'in_file')]), ]) # Provide synchronized output outputnode = pe.JoinNode(niu.IdentityInterface(fields=out_fields), name='outputnode', joinsource='inputnode') workflow.connect([ (poutputnode, outputnode, [(f, f) for f in out_fields]), ]) return workflow
def init_anat_preproc_wf( *, bids_root, longitudinal, t2w, 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. .. 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='.', longitudinal=False, t2w=['t2w.nii.gz'], omp_nthreads=1, output_dir='.', skull_strip_mode='force', skull_strip_template=Reference('OASIS30ANTs'), spaces=SpatialReferences(spaces=['Fischer344']), ) 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. 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 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. anat_dseg Brain tissue segmentation of the preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF). anat_tpms List of tissue probability maps corresponding to ``anat_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. 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_t2w = len(t2w) desc = """Anatomical data preprocessing : """ desc += """\ A total of {num_t2w} T2-weighted (T2w) images were found within the input BIDS dataset.""" inputnode = pe.Node(niu.IdentityInterface(fields=["t2w", "roi"]), name="inputnode") outputnode = pe.Node( niu.IdentityInterface( fields=[ "t2w_preproc", "t2w_mask", "t2w_dseg", "t2w_tpms", "std_preproc", "std_mask", "std_dseg", "std_tpms", "anat2std_xfm", "std2anat_xfm", "template", ] ), name="outputnode", ) # Connect reportlets workflows anat_reports_wf = init_anat_reports_wf(output_dir=output_dir,) workflow.connect( [ ( outputnode, anat_reports_wf, [ ("t2w_preproc", "inputnode.t1w_preproc"), ("t2w_mask", "inputnode.t1w_mask"), ("t2w_dseg", "inputnode.anat_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_source_name( [existing_derivatives["t2w_preproc"]], modality="T2w" ) 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_t2w > 1 else """\ The T2-weighted (T2w) 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_t2w > 1 else ", and used as T2w-reference throughout the workflow.\n" ) desc += """\ The T2w-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_t2w=num_t2w, skullstrip_tpl=skull_strip_template.fullname, ) buffernode = pe.Node( niu.IdentityInterface(fields=["t2w_brain", "t2w_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_t2w ) 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(t2w) if skull_strip_mode in (True, "skip"): raise NotImplementedError("Cannot run on already skull-stripped images.") else: # ants_affine_init? brain_extraction_wf = init_rodent_brain_extraction_wf( template_id=skull_strip_template.space, omp_nthreads=omp_nthreads, debug=debug ) # 3. Spatial normalization anat_norm_wf = init_anat_norm_wf( debug=debug, omp_nthreads=omp_nthreads, templates=spaces.get_spaces(nonstandard=False, dim=(3,)), ) # fmt:off workflow.connect([ # Step 1. (inputnode, anat_template_wf, [('t2w', '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.out_corrected', _pop), 't2w_preproc')]), (anat_template_wf, outputnode, [ ('outputnode.t1w_realign_xfm', 't2w_ref_xfms')]), (buffernode, outputnode, [('t2w_brain', 't2w_brain'), ('t2w_mask', 't2w_mask')]), # Steps 2 and 3 (inputnode, anat_norm_wf, [ (('t2w', fix_multi_source_name), 'inputnode.orig_t1w'), ('roi', 'inputnode.lesion_mask')]), (brain_extraction_wf, anat_norm_wf, [ (('outputnode.out_corrected', _pop), 'inputnode.moving_image')]), (buffernode, anat_norm_wf, [('t2w_mask', 'inputnode.moving_mask')]), (anat_norm_wf, outputnode, [ ('poutputnode.standardized', 'std_preproc'), ('poutputnode.std_mask', 'std_mask'), ('outputnode.template', 'template'), ('outputnode.anat2std_xfm', 'anat2std_xfm'), ('outputnode.std2anat_xfm', 'std2anat_xfm'), ]), # Connect reportlets (inputnode, anat_reports_wf, [ (('t2w', fix_multi_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')]), ]) # fmt:off # Write outputs ############################################3 anat_derivatives_wf = init_anat_derivatives_wf( bids_root=bids_root, num_t1w=num_t2w, output_dir=output_dir, spaces=spaces, ) # fmt:off workflow.connect([ # Connect derivatives (anat_template_wf, anat_derivatives_wf, [ ('outputnode.t1w_valid_list', 'inputnode.source_files')]), (anat_norm_wf, anat_derivatives_wf, [ ('outputnode.template', 'inputnode.template'), ('outputnode.anat2std_xfm', 'inputnode.anat2std_xfm'), ('outputnode.std2anat_xfm', 'inputnode.std2anat_xfm') ]), (outputnode, anat_derivatives_wf, [ ('t2w_ref_xfms', 'inputnode.t1w_ref_xfms'), ('t2w_preproc', 'inputnode.t1w_preproc'), ('t2w_mask', 'inputnode.t1w_mask'), ]), ]) # fmt:on # 4. Brain tissue segmentation - FAST produces: 0 (bg), 1 (wm), 2 (csf), 3 (gm) gm_tpm = get("Fischer344", label="GM", suffix="probseg") wm_tpm = get("Fischer344", label="WM", suffix="probseg") csf_tpm = get("Fischer344", label="CSF", suffix="probseg") xfm_gm = pe.Node( ApplyTransforms(input_image=_pop(gm_tpm), interpolation="MultiLabel"), name="xfm_gm", ) xfm_wm = pe.Node( ApplyTransforms(input_image=_pop(wm_tpm), interpolation="MultiLabel"), name="xfm_wm", ) xfm_csf = pe.Node( ApplyTransforms(input_image=_pop(csf_tpm), interpolation="MultiLabel"), name="xfm_csf", ) mrg_tpms = pe.Node(niu.Merge(3), name="mrg_tpms") anat_dseg = pe.Node( FAST( segments=True, probability_maps=True, bias_iters=0, no_bias=True, ), name="anat_dseg", mem_gb=3, ) # Change LookUp Table - BIDS wants: 0 (bg), 1 (gm), 2 (wm), 3 (csf) lut_anat_dseg = pe.Node( niu.Function(function=_apply_bids_lut), name="lut_anat_dseg" ) lut_anat_dseg.inputs.lut = (0, 3, 2, 1) # Maps: 0 -> 0, 3 -> 1, 2 -> 2, 1 -> 3 fast2bids = pe.Node( niu.Function(function=_probseg_fast2bids), name="fast2bids", run_without_submitting=True, ) # 5. Move native dseg & tpms back to standard space xfm_dseg = pe.Node(ApplyTransforms(interpolation="MultiLabel"), name="xfm_dseg") xfm_tpms = pe.MapNode( ApplyTransforms( dimension=3, default_value=0, float=True, interpolation="Gaussian" ), iterfield=["input_image"], name="xfm_tpms", ) # fmt:off workflow.connect([ # step 4 (brain_extraction_wf, buffernode, [ (('outputnode.out_brain', _pop), 't2w_brain'), ('outputnode.out_mask', 't2w_mask')]), (buffernode, anat_dseg, [('t2w_brain', 'in_files')]), (brain_extraction_wf, xfm_gm, [( ('outputnode.out_corrected', _pop), 'reference_image')]), (brain_extraction_wf, xfm_wm, [( ('outputnode.out_corrected', _pop), 'reference_image')]), (brain_extraction_wf, xfm_csf, [( ('outputnode.out_corrected', _pop), 'reference_image')]), (anat_norm_wf, xfm_gm, [( 'outputnode.std2anat_xfm', 'transforms')]), (anat_norm_wf, xfm_wm, [( 'outputnode.std2anat_xfm', 'transforms')]), (anat_norm_wf, xfm_csf, [( 'outputnode.std2anat_xfm', 'transforms')]), (xfm_gm, mrg_tpms, [('output_image', 'in1')]), (xfm_wm, mrg_tpms, [('output_image', 'in2')]), (xfm_csf, mrg_tpms, [('output_image', 'in3')]), (mrg_tpms, anat_dseg, [('out', 'other_priors')]), (anat_dseg, lut_anat_dseg, [('partial_volume_map', 'in_dseg')]), (lut_anat_dseg, outputnode, [('out', 't2w_dseg')]), (anat_dseg, fast2bids, [('partial_volume_files', 'inlist')]), (fast2bids, outputnode, [('out', 't2w_tpms')]), (outputnode, anat_derivatives_wf, [ ('t2w_tpms', 'inputnode.anat_tpms'), ('t2w_dseg', 'inputnode.anat_dseg') ]), # step 5 (anat_norm_wf, xfm_dseg, [('poutputnode.standardized', 'reference_image')]), (lut_anat_dseg, xfm_dseg, [('out', 'input_image')]), (anat_norm_wf, xfm_dseg, [('poutputnode.anat2std_xfm', 'transforms')]), (anat_norm_wf, xfm_tpms, [('poutputnode.standardized', 'reference_image')]), (fast2bids, xfm_tpms, [('out', 'input_image')]), (anat_norm_wf, xfm_tpms, [('poutputnode.anat2std_xfm', 'transforms')]), (xfm_dseg, outputnode, [('output_image', 'std_dseg')]), (xfm_tpms, outputnode, [('output_image', 'std_tpms')]), (outputnode, anat_derivatives_wf, [ ('std_dseg', 'inputnode.std_dseg'), ('std_tpms', 'inputnode.std_tpms') ]), ]) # fmt:on return workflow
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
def init_infant_anat_wf( *, age_months, ants_affine_init, t1w, t2w, anat_modality, bids_root, existing_derivatives, freesurfer, longitudinal, omp_nthreads, output_dir, segmentation_atlases, skull_strip_mode, skull_strip_template, sloppy, spaces, name="infant_anat_wf", ): """ - T1w reference: realigning and then averaging anatomical images. - Brain extraction and INU (bias field) correction. - Brain tissue segmentation. - Spatial normalization to standard spaces. - Surface reconstruction with FreeSurfer_. Outputs ------- anat_preproc The anatomical reference map, which is calculated as the average of bias-corrected and preprocessed anatomical images, defining the anatomical space. anat_brain Skull-stripped ``anat_preproc`` anat_mask Brain (binary) mask estimated by brain extraction. anat_dseg Brain tissue segmentation of the preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF). anat_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 anat2fsnative_xfm LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space fsnative2anat_xfm LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w surfaces GIFTI surfaces (gray/white boundary, midthickness, pial, inflated) """ from nipype.interfaces.base import Undefined from nipype.interfaces.ants.base import Info as ANTsInfo from niworkflows.interfaces.images import ValidateImage from smriprep.workflows.anatomical import init_anat_template_wf, _probseg_fast2bids, _pop from smriprep.workflows.norm import init_anat_norm_wf from smriprep.workflows.outputs import ( init_anat_reports_wf, init_anat_derivatives_wf, ) from ...utils.misc import fix_multi_source_name from .brain_extraction import init_infant_brain_extraction_wf from .segmentation import init_anat_seg_wf from .surfaces import init_infant_surface_recon_wf # for now, T1w only num_t1w = len(t1w) if t1w else 0 num_t2w = len(t2w) if t2w else 0 wf = pe.Workflow(name=name) desc = """Anatomical data preprocessing : """ desc += f"""\ A total of {num_t1w} T1w and {num_t2w} T2w images were found within the input BIDS dataset.""" inputnode = pe.Node( niu.IdentityInterface( fields=["t1w", "t2w", "subject_id", "subjects_dir" ]), # FLAIR / ROI? name="inputnode", ) outputnode = pe.Node( niu.IdentityInterface(fields=[ "anat_preproc", "anat_brain", "anat_mask", "anat_dseg", "anat_tpms", "anat_ref_xfms", "std_preproc", "std_brain", "std_dseg", "std_tpms", "subjects_dir", "subject_id", "anat2std_xfm", "std2anat_xfm", "anat2fsnative_xfm", "fsnative2anat_xfm", "surfaces", "anat_aseg", "anat_aparc", ]), name="outputnode", ) # Connect reportlets workflows anat_reports_wf = init_anat_reports_wf( freesurfer=freesurfer, output_dir=output_dir, ) if existing_derivatives: raise NotImplementedError("Reusing derivatives is not yet supported.") desc += """ All of the T1-weighted images 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 modified 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 ANTs JointFusion, distributed with ANTs {ants_ver}. """ wf.__desc__ = desc.format( ants_ver=ANTsInfo.version() or '(version unknown)', skullstrip_tpl=skull_strip_template.fullname, ) # Define output workflows anat_reports_wf = init_anat_reports_wf(freesurfer=freesurfer, output_dir=output_dir) # HACK: remove resolution from TFSelect anat_reports_wf.get_node('tf_select').inputs.resolution = Undefined anat_derivatives_wf = init_anat_derivatives_wf( bids_root=bids_root, freesurfer=freesurfer, num_t1w=num_t1w, output_dir=output_dir, spaces=spaces, ) # HACK: remove resolution from TFSelect anat_derivatives_wf.get_node('select_tpl').inputs.resolution = Undefined # Multiple T1w files -> generate average reference t1w_template_wf = init_anat_template_wf( longitudinal=False, omp_nthreads=omp_nthreads, num_t1w=num_t1w, ) use_t2w = False if num_t2w: t2w_template_wf = init_t2w_template_wf( longitudinal=longitudinal, omp_nthreads=omp_nthreads, num_t2w=num_t2w, ) wf.connect(inputnode, 't2w', t2w_template_wf, 'inputnode.t2w') # TODO: determine cutoff (< 8 months) use_t2w = True anat_validate = pe.Node( ValidateImage(), name='anat_validate', run_without_submitting=True, ) # INU + Brain Extraction if skull_strip_mode != 'force': raise NotImplementedError("Skull stripping is currently required.") brain_extraction_wf = init_infant_brain_extraction_wf( age_months=age_months, mri_scheme=anat_modality.capitalize(), ants_affine_init=ants_affine_init, skull_strip_template=skull_strip_template.space, template_specs=skull_strip_template.spec, omp_nthreads=omp_nthreads, output_dir=Path(output_dir), sloppy=sloppy, use_t2w=use_t2w, ) # Ensure single outputs be_buffer = pe.Node( niu.IdentityInterface(fields=["anat_preproc", "anat_brain"]), name='be_buffer') # Segmentation - initial implementation should be simple: JLF anat_seg_wf = init_anat_seg_wf( age_months=age_months, anat_modality=anat_modality.capitalize(), template_dir=segmentation_atlases, sloppy=sloppy, omp_nthreads=omp_nthreads, ) # Spatial normalization (requires segmentation) anat_norm_wf = init_anat_norm_wf( debug=sloppy, omp_nthreads=omp_nthreads, templates=spaces.get_spaces(nonstandard=False, dim=(3, )), ) # HACK: remove resolution from TFSelect anat_norm_wf.get_node('tf_select').inputs.resolution = Undefined # HACK: requires patched niworkflows to allow setting resolution to none anat_norm_wf.get_node('registration').inputs.template_resolution = None # fmt: off if use_t2w: wf.connect(t2w_template_wf, 'outputnode.t2w_ref', brain_extraction_wf, 'inputnode.t2w') wf.connect([ (inputnode, t1w_template_wf, [ ('t1w', 'inputnode.t1w'), ]), (t1w_template_wf, outputnode, [ ('outputnode.t1w_realign_xfm', 'anat_ref_xfms'), ]), (t1w_template_wf, anat_validate, [ ('outputnode.t1w_ref', 'in_file'), ]), (anat_validate, brain_extraction_wf, [ ('out_file', 'inputnode.t1w'), ]), (brain_extraction_wf, be_buffer, [ (('outputnode.t1w_corrected', _pop), 'anat_preproc'), (('outputnode.t1w_corrected_brain', _pop), 'anat_brain'), (('outputnode.t1w_mask', _pop), 'anat_mask'), ]), (be_buffer, outputnode, [ ('anat_preproc', 'anat_preproc'), ('anat_brain', 'anat_brain'), ('anat_mask', 'anat_mask'), ]), (be_buffer, anat_seg_wf, [ ('anat_brain', 'inputnode.anat_brain'), ]), (anat_seg_wf, outputnode, [ ('outputnode.anat_dseg', 'anat_dseg'), ]), (anat_seg_wf, anat_norm_wf, [ ('outputnode.anat_dseg', 'inputnode.moving_segmentation'), ('outputnode.anat_tpms', 'inputnode.moving_tpms'), ]), (be_buffer, anat_norm_wf, [ ('anat_preproc', 'inputnode.moving_image'), ('anat_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'), ]), ( inputnode, anat_norm_wf, [ (('t1w', fix_multi_source_name), 'inputnode.orig_t1w'), # anat_validate? not used... ]), ]) wf.connect([ # reports (inputnode, anat_reports_wf, [ ('t1w', 'inputnode.source_file'), ]), (outputnode, anat_reports_wf, [ ('anat_preproc', 'inputnode.t1w_preproc'), ('anat_mask', 'inputnode.t1w_mask'), ('anat_dseg', 'inputnode.t1w_dseg'), ('std_preproc', 'inputnode.std_t1w'), ('std_mask', 'inputnode.std_mask'), ]), (t1w_template_wf, anat_reports_wf, [ ('outputnode.out_report', 'inputnode.t1w_conform_report'), ]), (anat_norm_wf, anat_reports_wf, [ ('poutputnode.template', 'inputnode.template'), ]), # derivatives (t1w_template_wf, anat_derivatives_wf, [ ('outputnode.t1w_valid_list', 'inputnode.source_files'), ('outputnode.t1w_realign_xfm', 'inputnode.t1w_ref_xfms'), ]), (be_buffer, anat_derivatives_wf, [ ('anat_mask', 'inputnode.t1w_mask'), ('anat_preproc', 'inputnode.t1w_preproc'), ]), (anat_norm_wf, anat_derivatives_wf, [ ('outputnode.template', 'inputnode.template'), ('outputnode.anat2std_xfm', 'inputnode.anat2std_xfm'), ('outputnode.std2anat_xfm', 'inputnode.std2anat_xfm'), ]), (anat_seg_wf, anat_derivatives_wf, [ ('outputnode.anat_dseg', 'inputnode.t1w_dseg'), ('outputnode.anat_tpms', 'inputnode.t1w_tpms'), ]), ]) if not freesurfer: return wf # FreeSurfer surfaces surface_recon_wf = init_infant_surface_recon_wf( age_months=age_months, use_aseg=bool(segmentation_atlases), ) wf.connect([ (anat_seg_wf, surface_recon_wf, [ ('outputnode.anat_aseg', 'inputnode.anat_aseg'), ]), (inputnode, surface_recon_wf, [ ('subject_id', 'inputnode.subject_id'), ('subjects_dir', 'inputnode.subjects_dir'), ('t2w', 'inputnode.t2w'), ]), (anat_validate, surface_recon_wf, [ ('out_file', 'inputnode.anat_orig'), ]), (be_buffer, surface_recon_wf, [ ('anat_brain', 'inputnode.anat_skullstripped'), ('anat_preproc', 'inputnode.anat_preproc'), ]), (surface_recon_wf, outputnode, [ ('outputnode.subjects_dir', 'subjects_dir'), ('outputnode.subject_id', 'subject_id'), ('outputnode.anat2fsnative_xfm', 'anat2fsnative_xfm'), ('outputnode.fsnative2anat_xfm', 'fsnative2anat_xfm'), ('outputnode.surfaces', 'surfaces'), ('outputnode.anat_aparc', 'anat_aparc'), ('outputnode.anat_aseg', 'anat_aseg'), ]), (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.anat_aseg', 'inputnode.t1w_fs_aseg'), ('outputnode.anat_aparc', 'inputnode.t1w_fs_aparc'), ('outputnode.anat2fsnative_xfm', 'inputnode.t1w2fsnative_xfm'), ('outputnode.fsnative2anat_xfm', 'inputnode.fsnative2t1w_xfm'), ('outputnode.surfaces', 'inputnode.surfaces'), ]), ]) # fmt: on return wf