Exemple #1
0
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
Exemple #2
0
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
Exemple #4
0
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
Exemple #5
0
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:
Exemple #6
0
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
Exemple #7
0
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
Exemple #8
0
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
Exemple #9
0
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
Exemple #10
0
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
Exemple #11
0
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