예제 #1
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
예제 #2
0
def init_anat_preproc_wf(
    workdir=None,
    freesurfer=False,
    no_compose_transforms=False,
    skull_strip_algorithm="ants",
    name="anat_preproc_wf",
):
    """
    modified from smriprep/workflows/anatomical.py
    """

    workflow = pe.Workflow(name=name)

    inputnode = pe.Node(niu.IdentityInterface(fields=["t1w", "metadata"]),
                        name="inputnode")

    buffernode = pe.Node(
        niu.IdentityInterface(fields=["t1w_brain", "t1w_mask"]),
        name="buffernode")

    outputnode = pe.Node(
        niu.IdentityInterface(fields=anat_preproc_wf_output_attrs, ),
        name="outputnode",
    )

    skull_strip_template = Reference.from_string(
        config.workflow.skull_strip_template)[0]

    # Step 1
    anat_validate = pe.Node(ValidateImage(),
                            name="anat_validate",
                            run_without_submitting=True)
    if skull_strip_algorithm == "none":
        brain_extraction_wf = init_n4_only_wf(
            omp_nthreads=config.nipype.omp_nthreads,
            atropos_use_random_seed=not config.workflow.skull_strip_fixed_seed,
        )
    elif skull_strip_algorithm == "ants":
        brain_extraction_wf = init_brain_extraction_wf(
            in_template=skull_strip_template.space,
            template_spec=skull_strip_template.spec,
            atropos_use_random_seed=not config.workflow.skull_strip_fixed_seed,
            omp_nthreads=config.nipype.omp_nthreads,
            normalization_quality="precise",
        )
    else:
        raise ValueError(
            f'Unknown skull_strip_algorithm "{skull_strip_algorithm}"')
    workflow.connect([
        (inputnode, anat_validate, [("t1w", "in_file")]),
        (anat_validate, brain_extraction_wf, [("out_file",
                                               "inputnode.in_files")]),
        (
            brain_extraction_wf,
            outputnode,
            [("outputnode.bias_corrected", "t1w_preproc")],
        ),
        (
            brain_extraction_wf,
            buffernode,
            [
                (("outputnode.out_file", first), "t1w_brain"),
                ("outputnode.out_mask", "t1w_mask"),
            ],
        ),
        (
            buffernode,
            outputnode,
            [("t1w_brain", "t1w_brain"), ("t1w_mask", "t1w_mask")],
        ),
    ])

    # Step 2
    t1w_dseg = pe.Node(
        fsl.FAST(segments=True, no_bias=True, probability_maps=True),
        name="t1w_dseg",
        mem_gb=3,
    )
    workflow.connect([
        (buffernode, t1w_dseg, [("t1w_brain", "in_files")]),
        (
            t1w_dseg,
            outputnode,
            [("tissue_class_map", "t1w_dseg"),
             ("probability_maps", "t1w_tpms")],
        ),
    ])

    # Step 3
    anat_norm_wf = init_anat_norm_wf(
        debug=config.execution.debug,
        omp_nthreads=config.nipype.omp_nthreads,
        templates=norm_templates
        if not no_compose_transforms else norm_templates + extra_templates,
    )
    workflow.connect([
        (
            inputnode,
            anat_norm_wf,
            [("t1w", "inputnode.orig_t1w")],
        ),
        (
            brain_extraction_wf,
            anat_norm_wf,
            [(("outputnode.bias_corrected", first), "inputnode.moving_image")],
        ),
        (buffernode, anat_norm_wf, [("t1w_mask", "inputnode.moving_mask")]),
        (
            t1w_dseg,
            anat_norm_wf,
            [("tissue_class_map", "inputnode.moving_segmentation")],
        ),
        (t1w_dseg, anat_norm_wf, [("probability_maps", "inputnode.moving_tpms")
                                  ]),
    ])

    # Write outputs
    anat_reports_wf = init_anat_reports_wf(freesurfer=freesurfer,
                                           output_dir="/")
    workflow.connect([
        (
            outputnode,
            anat_reports_wf,
            [
                ("t1w_preproc", "inputnode.t1w_preproc"),
                ("t1w_mask", "inputnode.t1w_mask"),
                ("t1w_dseg", "inputnode.t1w_dseg"),
            ],
        ),
        (inputnode, anat_reports_wf, [("t1w", "inputnode.source_file")]),
        (
            anat_norm_wf,
            anat_reports_wf,
            [
                ("poutputnode.template", "inputnode.template"),
                ("poutputnode.standardized", "inputnode.std_t1w"),
                ("poutputnode.std_mask", "inputnode.std_mask"),
            ],
        ),
    ])

    # Custom

    add_templates_by_composing_transforms(
        workflow,
        templates=extra_templates if not no_compose_transforms else [])

    make_reportnode(workflow, spaces=True)
    assert workdir is not None
    make_reportnode_datasink(workflow, workdir)

    if freesurfer:

        def get_subject(dic):
            return dic.get("subject")

        # 5. Surface reconstruction (--fs-no-reconall not set)
        surface_recon_wf = init_surface_recon_wf(
            name="surface_recon_wf",
            omp_nthreads=config.nipype.omp_nthreads,
            hires=config.workflow.hires,
        )
        subjects_dir = Path(workdir) / "subjects_dir"
        subjects_dir.mkdir(parents=True, exist_ok=True)
        surface_recon_wf.get_node("inputnode").inputs.subjects_dir = str(
            subjects_dir)
        workflow.connect([
            (
                inputnode,
                surface_recon_wf,
                [(("metadata", get_subject), "inputnode.subject_id")],
            ),
            (anat_validate, surface_recon_wf, [("out_file", "inputnode.t1w")]),
            (
                brain_extraction_wf,
                surface_recon_wf,
                [
                    (("outputnode.out_file", first),
                     "inputnode.skullstripped_t1"),
                    ("outputnode.out_segm", "inputnode.ants_segs"),
                    (("outputnode.bias_corrected", first),
                     "inputnode.corrected_t1"),
                ],
            ),
            (
                surface_recon_wf,
                anat_reports_wf,
                [
                    ("outputnode.subject_id", "inputnode.subject_id"),
                    ("outputnode.subjects_dir", "inputnode.subjects_dir"),
                ],
            ),
        ])

    return workflow
예제 #3
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
예제 #4
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
예제 #5
0
def main():

    parser = argparse.ArgumentParser()
    parser.add_argument("t1", help="Input T1 file", type=str)
    parser.add_argument("bspline", help="Bspline distance for N4", type=int)
    parser.add_argument("niter",
                        help="Number of iterations, will be multipled"
                        "by [niter]*5",
                        type=int)
    args = parser.parse_args()

    t1 = args.t1
    bspline = args.bspline
    niter = args.niter

    # Standard input
    workdir = os.getcwd()
    t1 = os.path.join(workdir, t1)

    # Initialize workflow
    wf = Workflow(name="bias_field")
    wf.base_dir = os.getcwd()

    # Set up input node of list type
    input_node = pe.Node(niu.IdentityInterface(fields=['in_files']),
                         name='inputnode')
    input_node.inputs.in_files = [t1]

    # Set up input node of value type
    single_file_buf = pe.Node(niu.IdentityInterface(fields=['input_file']),
                              name='inputfile')
    single_file_buf.inputs.input_file = t1

    # Initial skullstrip
    ants_wf = init_brain_extraction_wf(in_template='OASIS30ANTs',
                                       atropos_use_random_seed=False,
                                       normalization_quality='precise')

    # Apply N4 bias field correction
    # Iterate over:
    # bspline fitting distances
    # number of iterations
    n4 = pe.Node(N4BiasFieldCorrection(dimension=3,
                                       save_bias=True,
                                       copy_header=True,
                                       convergence_threshold=1e-7,
                                       shrink_factor=4,
                                       bspline_fitting_distance=bspline,
                                       n_iterations=[niter] * 5),
                 name='n4')

    outputnode = pe.Node(
        niu.IdentityInterface(fields=['corrected_t1', 'orig_t1']), name='out')

    datasink = pe.Node(DataSink(base_directory=workdir, container='n4_wf'),
                       name='sink')

    datasink.inputs.substitutions = [('_bspline_fitting_distance_',
                                      'bspline-'), ('n_iterations_', 'niter-')]

    wf.connect([[input_node, ants_wf, [('in_files', 'inputnode.in_files')]],
                [single_file_buf, n4, [('input_file', 'input_image')]],
                [ants_wf, n4, [('outputnode.out_mask', 'mask_image')]],
                [n4, outputnode, [('output_image', 'corrected_t1')]],
                [single_file_buf, outputnode, [('input_file', 'orig_t1')]],
                [
                    outputnode, datasink,
                    [('corrected_t1', 'corrected_img.@corrected'),
                     ('orig_t1', 'corrected_img.@orig')]
                ]])
    wf.run()
예제 #6
0
def init_templateflow_wf(
    bids_dir,
    output_dir,
    participant_label,
    mov_template,
    ref_template='MNI152NLin2009cAsym',
    use_float=True,
    omp_nthreads=None,
    mem_gb=3.0,
    modality='T1w',
    normalization_quality='precise',
    name='templateflow_wf',
    fs_subjects_dir=None,
):
    """
    A Nipype workflow to perform image registration between two templates
    *R* and *M*. *R* is the *reference template*, selected by a templateflow
    identifier such as ``MNI152NLin2009cAsym``, and *M* is the *moving
    template* (e.g., ``MNI152Lin``). This workflows maps data defined on
    template-*M* space onto template-*R* space.


    1. Run the subrogate images through ``antsBrainExtraction``.
    2. Recompute :abbr:`INU (intensity non-uniformity)` correction using
        the mask obtained in 1).
    3. Independently, run spatial normalization of every
       :abbr:`INU (intensity non-uniformity)` corrected image
       (supplied via ``in_files``) to both templates.
    4. Calculate an initialization between both templates, using them directly.
    5. Run multi-channel image registration of the images resulting from
        3). Both sets of images (one registered to *R* and another to *M*)
        are then used as reference and moving images in the registration
        framework.

    **Parameters**

    in_files: list of files
        a list of paths pointing to the images that will be used as surrogates
    mov_template: str
        a templateflow identifier for template-*M*
    ref_template: str
        a templateflow identifier for template-*R* (default: ``MNI152NLin2009cAsym``).


    """
    # number of participants
    ninputs = len(participant_label)
    ants_env = {
        'NSLOTS': '%d' % omp_nthreads,
        'ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS': '%d' % omp_nthreads,
        'OMP_NUM_THREADS': '%d' % omp_nthreads,
    }

    # Get path to templates
    tpl_ref = str(
        get_template(ref_template, suffix=modality, desc=None, resolution=1))
    tpl_ref_mask = str(
        get_template(ref_template, suffix='mask', desc='brain', resolution=1))
    tpl_mov = str(
        get_template(mov_template, suffix=modality, desc=None, resolution=1))
    tpl_mov_mask = str(
        get_template(mov_template, suffix='mask', desc='brain', resolution=1))

    wf = pe.Workflow(name)
    inputnode = pe.Node(niu.IdentityInterface(fields=['participant_label']),
                        name='inputnode')
    inputnode.iterables = ('participant_label',
                           sorted(list(participant_label)))

    pick_file = pe.Node(niu.Function(function=_bids_pick),
                        name='pick_file',
                        run_without_submitting=True)
    pick_file.inputs.bids_root = bids_dir

    ref_bex = init_brain_extraction_wf(
        in_template=ref_template,
        omp_nthreads=omp_nthreads,
        mem_gb=mem_gb,
        bids_suffix=modality,
        name='reference_bex',
    )

    mov_bex = init_brain_extraction_wf(
        in_template=mov_template,
        omp_nthreads=omp_nthreads,
        mem_gb=mem_gb,
        bids_suffix=modality,
        name='moving_bex',
    )

    ref_norm = pe.Node(Registration(from_file=pkgr.resource_filename(
        'niworkflows.data', 't1w-mni_registration_%s_000.json' %
        normalization_quality)),
                       name='ref_norm',
                       n_procs=omp_nthreads)
    ref_norm.inputs.fixed_image = tpl_ref
    ref_norm.inputs.fixed_image_masks = tpl_ref_mask
    ref_norm.inputs.environ = ants_env

    # Register the INU-corrected image to the other template
    mov_norm = pe.Node(Registration(from_file=pkgr.resource_filename(
        'niworkflows.data', 't1w-mni_registration_%s_000.json' %
        normalization_quality)),
                       name='mov_norm',
                       n_procs=omp_nthreads)
    mov_norm.inputs.fixed_image = tpl_mov
    mov_norm.inputs.fixed_image_masks = tpl_mov_mask
    mov_norm.inputs.environ = ants_env

    # Initialize between-templates transform with antsAI
    init_aff = pe.Node(AI(
        metric=('Mattes', 32, 'Regular', 0.2),
        transform=('Affine', 0.1),
        search_factor=(20, 0.12),
        principal_axes=False,
        convergence=(10, 1e-6, 10),
        verbose=True,
        fixed_image=tpl_ref,
        fixed_image_mask=tpl_ref_mask,
        moving_image=tpl_mov,
        moving_image_mask=tpl_mov_mask,
        environ=ants_env,
    ),
                       name='init_aff',
                       n_procs=omp_nthreads)

    ref_buffer = pe.JoinNode(niu.IdentityInterface(fields=['fixed_image']),
                             joinsource='inputnode',
                             joinfield='fixed_image',
                             name='ref_buffer')

    mov_buffer = pe.JoinNode(niu.IdentityInterface(fields=['moving_image']),
                             joinsource='inputnode',
                             joinfield='moving_image',
                             name='mov_buffer')

    flow = pe.Node(
        Registration(from_file=pkgr.resource_filename(
            'niworkflows.data', 't1w-mni_registration_%s_000.json' %
            normalization_quality)),
        name='flow_norm',
        n_procs=omp_nthreads,
    )
    flow.inputs.fixed_image_masks = tpl_ref_mask
    flow.inputs.moving_image_masks = tpl_mov_mask
    flow.inputs.metric = [[v] * ninputs for v in flow.inputs.metric]
    flow.inputs.metric_weight = [[1 / ninputs] * ninputs
                                 for _ in flow.inputs.metric_weight]
    flow.inputs.radius_or_number_of_bins = [
        [v] * ninputs for v in flow.inputs.radius_or_number_of_bins
    ]
    flow.inputs.sampling_percentage = [[v] * ninputs
                                       for v in flow.inputs.sampling_percentage
                                       ]
    flow.inputs.sampling_strategy = [[v] * ninputs
                                     for v in flow.inputs.sampling_strategy]
    flow.inputs.environ = ants_env

    # Datasinking
    ref_norm_ds = pe.Node(DerivativesDataSink(base_directory=str(
        output_dir.parent),
                                              out_path_base=output_dir.name,
                                              space=ref_template,
                                              desc='preproc',
                                              keep_dtype=True),
                          name='ref_norm_ds',
                          run_without_submitting=True)

    mov_norm_ds = pe.Node(DerivativesDataSink(base_directory=str(
        output_dir.parent),
                                              out_path_base=output_dir.name,
                                              space=mov_template,
                                              desc='preproc',
                                              keep_dtype=True),
                          name='mov_norm_ds',
                          run_without_submitting=True)

    xfm_ds = pe.Node(DerivativesDataSink(
        base_directory=str(output_dir.parent),
        out_path_base=output_dir.name,
        allowed_entities=['from', 'mode'],
        mode='image',
        suffix='xfm',
        source_file='group/tpl-{0}_T1w.nii.gz'.format(ref_template),
        **{'from': mov_template}),
                     name='xfm_ds',
                     run_without_submitting=True)

    wf.connect([
        (inputnode, pick_file, [('participant_label', 'participant_label')]),
        (pick_file, ref_bex, [('out', 'inputnode.in_files')]),
        (pick_file, mov_bex, [('out', 'inputnode.in_files')]),
        (ref_bex, ref_norm, [('outputnode.bias_corrected', 'moving_image'),
                             ('outputnode.out_mask', 'moving_image_masks'),
                             ('norm.forward_transforms',
                              'initial_moving_transform')]),
        (ref_bex, mov_norm, [('outputnode.bias_corrected', 'moving_image')]),
        (mov_bex, mov_norm, [('outputnode.out_mask', 'moving_image_masks'),
                             ('norm.forward_transforms',
                              'initial_moving_transform')]),
        (init_aff, flow, [('output_transform', 'initial_moving_transform')]),
        (ref_norm, ref_buffer, [('warped_image', 'fixed_image')]),
        (mov_norm, mov_buffer, [('warped_image', 'moving_image')]),
        (ref_buffer, flow, [('fixed_image', 'fixed_image')]),
        (mov_buffer, flow, [('moving_image', 'moving_image')]),
        (pick_file, ref_norm_ds, [('out', 'source_file')]),
        (ref_norm, ref_norm_ds, [('warped_image', 'in_file')]),
        (pick_file, mov_norm_ds, [('out', 'source_file')]),
        (mov_norm, mov_norm_ds, [('warped_image', 'in_file')]),
        (flow, xfm_ds, [('composite_transform', 'in_file')]),
    ])

    if fs_subjects_dir:
        fssource = pe.Node(FreeSurferSource(subjects_dir=str(fs_subjects_dir)),
                           name='fssource',
                           run_without_submitting=True)
        tonative = pe.Node(fs.Label2Vol(subjects_dir=str(fs_subjects_dir)),
                           name='tonative')
        tonii = pe.Node(fs.MRIConvert(out_type='niigz',
                                      resample_type='nearest'),
                        name='tonii')

        ref_aparc = pe.Node(ApplyTransforms(interpolation='MultiLabel',
                                            float=True,
                                            reference_image=tpl_ref,
                                            environ=ants_env),
                            name='ref_aparc',
                            mem_gb=1,
                            n_procs=omp_nthreads)

        mov_aparc = pe.Node(ApplyTransforms(interpolation='MultiLabel',
                                            float=True,
                                            reference_image=tpl_mov,
                                            environ=ants_env),
                            name='mov_aparc',
                            mem_gb=1,
                            n_procs=omp_nthreads)

        ref_aparc_buffer = pe.JoinNode(niu.IdentityInterface(fields=['aparc']),
                                       joinsource='inputnode',
                                       joinfield='aparc',
                                       name='ref_aparc_buffer')

        ref_join_labels = pe.Node(AntsJointFusion(
            target_image=[tpl_ref],
            out_label_fusion='merged_aparc.nii.gz',
            out_intensity_fusion_name_format='merged_aparc_intensity_%d.nii.gz',
            out_label_post_prob_name_format='merged_aparc_posterior_%d.nii.gz',
            out_atlas_voting_weight_name_format='merged_aparc_weight_%d.nii.gz',
            environ=ants_env,
        ),
                                  name='ref_join_labels',
                                  n_procs=omp_nthreads)

        ref_join_labels_ds = pe.Node(DerivativesDataSink(
            base_directory=str(output_dir.parent),
            out_path_base=output_dir.name,
            suffix='dtissue',
            desc='aparc',
            keep_dtype=False,
            source_file='group/tpl-{0}_T1w.nii.gz'.format(ref_template)),
                                     name='ref_join_labels_ds',
                                     run_without_submitting=True)

        ref_join_probs_ds = pe.Node(DerivativesDataSink(
            base_directory=str(output_dir.parent),
            out_path_base=output_dir.name,
            suffix='probtissue',
            desc='aparc',
            keep_dtype=False,
            source_file='group/tpl-{0}_T1w.nii.gz'.format(ref_template)),
                                    name='ref_join_probs_ds',
                                    run_without_submitting=True)

        # ref_join_voting_ds = pe.Node(
        #     DerivativesDataSink(
        #         base_directory=str(output_dir.parent),
        #         out_path_base=output_dir.name, space=ref_template,
        #         suffix='probtissue', desc='aparcvoting', keep_dtype=False,
        #         source_file='group/tpl-{0}_T1w.nii.gz'.format(ref_template)),
        #     name='ref_join_voting_ds', run_without_submitting=True)

        mov_aparc_buffer = pe.JoinNode(niu.IdentityInterface(fields=['aparc']),
                                       joinsource='inputnode',
                                       joinfield='aparc',
                                       name='mov_aparc_buffer')

        mov_join_labels = pe.Node(AntsJointFusion(
            target_image=[tpl_mov],
            out_label_fusion='merged_aparc.nii.gz',
            out_intensity_fusion_name_format='merged_aparc_intensity_%d.nii.gz',
            out_label_post_prob_name_format='merged_aparc_posterior_%d.nii.gz',
            out_atlas_voting_weight_name_format='merged_aparc_weight_%d.nii.gz',
            environ=ants_env,
        ),
                                  name='mov_join_labels',
                                  n_procs=omp_nthreads)

        mov_join_labels_ds = pe.Node(DerivativesDataSink(
            base_directory=str(output_dir.parent),
            out_path_base=output_dir.name,
            suffix='dtissue',
            desc='aparc',
            keep_dtype=False,
            source_file='group/tpl-{0}_T1w.nii.gz'.format(mov_template)),
                                     name='mov_join_labels_ds',
                                     run_without_submitting=True)

        mov_join_probs_ds = pe.Node(DerivativesDataSink(
            base_directory=str(output_dir.parent),
            out_path_base=output_dir.name,
            suffix='probtissue',
            desc='aparc',
            keep_dtype=False,
            source_file='group/tpl-{0}_T1w.nii.gz'.format(mov_template)),
                                    name='mov_join_probs_ds',
                                    run_without_submitting=True)

        ref_aparc_ds = pe.Node(DerivativesDataSink(
            base_directory=str(output_dir.parent),
            out_path_base=output_dir.name,
            space=ref_template,
            suffix='dtissue',
            desc='aparc',
            keep_dtype=False),
                               name='ref_aparc_ds',
                               run_without_submitting=True)

        mov_aparc_ds = pe.Node(DerivativesDataSink(
            base_directory=str(output_dir.parent),
            out_path_base=output_dir.name,
            space=mov_template,
            suffix='dtissue',
            desc='aparc',
            keep_dtype=False),
                               name='mov_aparc_ds',
                               run_without_submitting=True)
        # Extract surfaces
        cifti_wf = init_gifti_surface_wf(name='cifti_surfaces',
                                         subjects_dir=str(fs_subjects_dir))

        # Move surfaces to template spaces
        gii2csv = pe.MapNode(GiftiToCSV(itk_lps=True),
                             iterfield=['in_file'],
                             name='gii2csv')
        ref_map_surf = pe.MapNode(ApplyTransformsToPoints(dimension=3,
                                                          environ=ants_env),
                                  n_procs=omp_nthreads,
                                  name='ref_map_surf',
                                  iterfield=['input_file'])
        ref_csv2gii = pe.MapNode(CSVToGifti(itk_lps=True),
                                 name='ref_csv2gii',
                                 iterfield=['in_file', 'gii_file'])
        ref_surfs_buffer = pe.JoinNode(
            niu.IdentityInterface(fields=['surfaces']),
            joinsource='inputnode',
            joinfield='surfaces',
            name='ref_surfs_buffer')
        ref_surfs_unzip = pe.Node(UnzipJoinedSurfaces(),
                                  name='ref_surfs_unzip',
                                  run_without_submitting=True)
        ref_ply = pe.MapNode(SurfacesToPointCloud(),
                             name='ref_ply',
                             iterfield=['in_files'])
        ref_recon = pe.MapNode(PoissonRecon(),
                               name='ref_recon',
                               iterfield=['in_file'])
        ref_avggii = pe.MapNode(PLYtoGifti(),
                                name='ref_avggii',
                                iterfield=['in_file', 'surf_key'])
        ref_smooth = pe.MapNode(fs.SmoothTessellation(),
                                name='ref_smooth',
                                iterfield=['in_file'])

        ref_surfs_ds = pe.Node(DerivativesDataSink(
            base_directory=str(output_dir.parent),
            out_path_base=output_dir.name,
            space=ref_template,
            keep_dtype=False,
            compress=False),
                               name='ref_surfs_ds',
                               run_without_submitting=True)
        ref_avg_ds = pe.Node(DerivativesDataSink(
            base_directory=str(output_dir.parent),
            out_path_base=output_dir.name,
            space=ref_template,
            keep_dtype=False,
            compress=False,
            source_file='group/tpl-{0}_T1w.nii.gz'.format(ref_template)),
                             name='ref_avg_ds',
                             run_without_submitting=True)

        mov_map_surf = pe.MapNode(ApplyTransformsToPoints(dimension=3,
                                                          environ=ants_env),
                                  n_procs=omp_nthreads,
                                  name='mov_map_surf',
                                  iterfield=['input_file'])
        mov_csv2gii = pe.MapNode(CSVToGifti(itk_lps=True),
                                 name='mov_csv2gii',
                                 iterfield=['in_file', 'gii_file'])
        mov_surfs_buffer = pe.JoinNode(
            niu.IdentityInterface(fields=['surfaces']),
            joinsource='inputnode',
            joinfield='surfaces',
            name='mov_surfs_buffer')
        mov_surfs_unzip = pe.Node(UnzipJoinedSurfaces(),
                                  name='mov_surfs_unzip',
                                  run_without_submitting=True)
        mov_ply = pe.MapNode(SurfacesToPointCloud(),
                             name='mov_ply',
                             iterfield=['in_files'])
        mov_recon = pe.MapNode(PoissonRecon(),
                               name='mov_recon',
                               iterfield=['in_file'])
        mov_avggii = pe.MapNode(PLYtoGifti(),
                                name='mov_avggii',
                                iterfield=['in_file', 'surf_key'])
        mov_smooth = pe.MapNode(fs.SmoothTessellation(),
                                name='mov_smooth',
                                iterfield=['in_file'])

        mov_surfs_ds = pe.Node(DerivativesDataSink(
            base_directory=str(output_dir.parent),
            out_path_base=output_dir.name,
            space=mov_template,
            keep_dtype=False,
            compress=False),
                               name='mov_surfs_ds',
                               run_without_submitting=True)
        mov_avg_ds = pe.Node(DerivativesDataSink(
            base_directory=str(output_dir.parent),
            out_path_base=output_dir.name,
            space=mov_template,
            keep_dtype=False,
            compress=False,
            source_file='group/tpl-{0}_T1w.nii.gz'.format(mov_template)),
                             name='mov_avg_ds',
                             run_without_submitting=True)

        wf.connect([
            (inputnode, fssource, [(('participant_label', _sub_decorate),
                                    'subject_id')]),
            (inputnode, cifti_wf, [(('participant_label', _sub_decorate),
                                    'inputnode.subject_id')]),
            (pick_file, cifti_wf, [('out', 'inputnode.in_t1w')]),
            (pick_file, tonii, [('out', 'reslice_like')]),
            # Select DKT aparc
            (fssource, tonative, [(('aparc_aseg', _last), 'seg_file'),
                                  ('rawavg', 'template_file'),
                                  ('aseg', 'reg_header')]),
            (tonative, tonii, [('vol_label_file', 'in_file')]),
            (tonii, ref_aparc, [('out_file', 'input_image')]),
            (tonii, mov_aparc, [('out_file', 'input_image')]),
            (ref_norm, ref_aparc, [('composite_transform', 'transforms')]),
            (mov_norm, mov_aparc, [('composite_transform', 'transforms')]),
            (ref_buffer, ref_join_labels, [('fixed_image', 'atlas_image')]),
            (ref_aparc, ref_aparc_buffer, [('output_image', 'aparc')]),
            (ref_aparc_buffer, ref_join_labels,
             [('aparc', 'atlas_segmentation_image')]),
            (mov_buffer, mov_join_labels, [('moving_image', 'atlas_image')]),
            (mov_aparc, mov_aparc_buffer, [('output_image', 'aparc')]),
            (mov_aparc_buffer, mov_join_labels,
             [('aparc', 'atlas_segmentation_image')]),
            # Datasinks
            (ref_join_labels, ref_join_labels_ds, [('out_label_fusion',
                                                    'in_file')]),
            (ref_join_labels, ref_join_probs_ds,
             [('out_label_post_prob', 'in_file'),
              (('out_label_post_prob', _get_extra), 'extra_values')]),
            # (ref_join_labels, ref_join_voting_ds, [
            #     ('out_atlas_voting_weight_name_format', 'in_file')]),
            (mov_join_labels, mov_join_labels_ds, [('out_label_fusion',
                                                    'in_file')]),
            (mov_join_labels, mov_join_probs_ds,
             [('out_label_post_prob', 'in_file'),
              (('out_label_post_prob', _get_extra), 'extra_values')]),
            (pick_file, ref_aparc_ds, [('out', 'source_file')]),
            (ref_aparc, ref_aparc_ds, [('output_image', 'in_file')]),
            (pick_file, mov_aparc_ds, [('out', 'source_file')]),
            (mov_aparc, mov_aparc_ds, [('output_image', 'in_file')]),
            # Mapping ref surfaces
            (cifti_wf, gii2csv, [(('outputnode.surf_norm', _discard_inflated),
                                  'in_file')]),
            (gii2csv, ref_map_surf, [('out_file', 'input_file')]),
            (ref_norm, ref_map_surf, [(('inverse_composite_transform',
                                        _ensure_list), 'transforms')]),
            (ref_map_surf, ref_csv2gii, [('output_file', 'in_file')]),
            (cifti_wf, ref_csv2gii, [(('outputnode.surf_norm',
                                       _discard_inflated), 'gii_file')]),
            (pick_file, ref_surfs_ds, [('out', 'source_file')]),
            (ref_csv2gii, ref_surfs_ds, [('out_file', 'in_file'),
                                         (('out_file', _get_surf_extra),
                                          'extra_values')]),
            (ref_csv2gii, ref_surfs_buffer, [('out_file', 'surfaces')]),
            (ref_surfs_buffer, ref_surfs_unzip, [('surfaces', 'in_files')]),
            (ref_surfs_unzip, ref_ply, [('out_files', 'in_files')]),
            (ref_ply, ref_recon, [('out_file', 'in_file')]),
            (ref_recon, ref_avggii, [('out_file', 'in_file')]),
            (ref_surfs_unzip, ref_avggii, [('surf_keys', 'surf_key')]),
            (ref_avggii, ref_smooth, [('out_file', 'in_file')]),
            (ref_smooth, ref_avg_ds, [('surface', 'in_file'),
                                      (('surface', _get_surf_extra),
                                       'extra_values')]),

            # Mapping mov surfaces
            (gii2csv, mov_map_surf, [('out_file', 'input_file')]),
            (mov_norm, mov_map_surf, [(('inverse_composite_transform',
                                        _ensure_list), 'transforms')]),
            (mov_map_surf, mov_csv2gii, [('output_file', 'in_file')]),
            (cifti_wf, mov_csv2gii, [(('outputnode.surf_norm',
                                       _discard_inflated), 'gii_file')]),
            (pick_file, mov_surfs_ds, [('out', 'source_file')]),
            (mov_csv2gii, mov_surfs_ds, [('out_file', 'in_file'),
                                         (('out_file', _get_surf_extra),
                                          'extra_values')]),
            (mov_csv2gii, mov_surfs_buffer, [('out_file', 'surfaces')]),
            (mov_surfs_buffer, mov_surfs_unzip, [('surfaces', 'in_files')]),
            (mov_surfs_unzip, mov_ply, [('out_files', 'in_files')]),
            (mov_ply, mov_recon, [('out_file', 'in_file')]),
            (mov_recon, mov_avggii, [('out_file', 'in_file')]),
            (mov_surfs_unzip, mov_avggii, [('surf_keys', 'surf_key')]),
            (mov_avggii, mov_smooth, [('out_file', 'in_file')]),
            (mov_smooth, mov_avg_ds, [('surface', 'in_file'),
                                      (('surface', _get_surf_extra),
                                       'extra_values')]),
        ])

    return wf
예제 #7
0
파일: base.py 프로젝트: josephmje/tractify
def init_tract_wf():
    tract_wf = pe.Workflow(name="tract_wf")

    inputnode = pe.Node(
        niu.IdentityInterface(fields=[
            "subject_id",
            "session_id",
            "output_dir",
            "t1_file",
            "eddy_file",
            "eddy_mask",
            "dwi_mask",
            "bval",
            "bvec",
            "template",
            "atlas",
            "num_tracts",
        ]),
        name="inputnode",
    )

    outputnode = pe.Node(
        niu.IdentityInterface(fields=[
            "tck_file",
            "prob_weights",
            "shen_diff_space",
            "inv_len_conmat",
            "len_conmat",
        ]),
        name="outputnode",
    )

    # Skullstrip the t1, needs to map brain on brain
    t1_skullstrip = init_brain_extraction_wf()

    #register T1 to diffusion space first
    #flirt -dof 6 -in T1w_brain.nii.gz -ref nodif_brain.nii.gz -omat xformT1_2_diff.mat -out T1_diff
    flirt = pe.Node(fsl.FLIRT(dof=6), name="t1_flirt")

    to_list = lambda x: [x]

    # T1 should already be skull stripped and minimally preprocessed (from Freesurfer will do)
    #5ttgen fsl -nocrop -premasked T1_diff.nii.gz 5TT.mif
    gen5tt = pe.Node(mrtrix3.Generate5tt(algorithm='fsl',
                                         no_crop=True,
                                         premasked=True,
                                         out_file='5TT.mif'),
                     name="gen5tt")
    #5tt2gmwmi 5TT.mif gmwmi.mif
    gen5ttMask = pe.Node(mrtrix3.Generate5ttMask(out_file='gmwmi.mif'),
                         name="gen5ttMask")

    #SINGLE SHELL
    # generate response function
    #dwi2response tournier data.nii.gz -fslgrad data.eddy_rotated_bvecs dwi.bval response.txt
    responseSD = pe.Node(mrtrix3.ResponseSD(algorithm='tournier'),
                         name="responseSD")
    # generate FODs
    #dwi2fod csd data.nii.gz response.txt FOD.mif -mask nodif_brain_mask.nii.gz -fslgrad data.eddy_rotated_bvecs dwi.bval
    estimateFOD = pe.Node(mrtrix3.EstimateFOD(algorithm='csd',
                                              wm_odf='FOD.mif'),
                          name="estimateFOD")
    # perform probabilistic tractography
    #tckgen FOD.mif prob.tck -act 5TT.mif -seed_gmwmi gmwmi.mif -select 5000000 ## seeding from a binarised gmwmi
    tckgen = pe.Node(mrtrix3.Tractography(), name="tckgen")

    #mrview data.nii.gz -tractography.load prob.tck

    def gen_tuple(item1, item2):
        return (item1, item2)

    gen_tuple = pe.Node(
        niu.Function(
            input_names=["item1", "item2"],
            output_names=["out_tuple"],
            function=gen_tuple,
        ),
        name="gen_tuple",
    )
    #use sift to filter tracks based on spherical harmonics
    #tcksift2 prob.tck FOD.mif prob_weights.txt
    tcksift = pe.Node(mrtrix3.TrackSift2(out_weights="prob_weights.txt"),
                      name="tcksift")

    ## atlas reg
    #flirt -in T1w_brain.nii.gz -ref MNI152_T1_1mm_brain.nii.gz -omat xformT1_2_MNI.mat
    pre_atlas_flirt = pe.Node(fsl.FLIRT(), name="pre_atlas_flirt")
    #convert_xfm -omat xformMNI_2_T1.mat -inverse xformT12MNI.mat
    xfm_inv = pe.Node(fsl.ConvertXFM(invert_xfm=True), name="xfm_inv")
    #convert_xfm -omat xformMNI_2_diff.mat -concat xformT1_2_diff.mat xformMNI_2_T1.mat
    xfm_concat = pe.Node(fsl.ConvertXFM(concat_xfm=True), name="xfm_concat")

    #flirt -in shen268.nii.gz -ref T1_diff.nii.gz -applyxfm -init xformMNI_2_diff.mat -interp nearestneighbour -out shen_diff_space.nii.gz
    atlas_flirt = pe.Node(fsl.FLIRT(apply_xfm=True, interp='nearestneighbour'),
                          name="atlas_flirt")

    ## generate connectivity matrices
    #tck2connectome prob.tck shen_diff_space.nii.gz conmat_shen.csv -scale_invlength -zero_diagonal -symmetric -tck_weights_in prob_weights.txt -assignment_radial_search 2 -scale_invnodevol
    conmatgen = pe.Node(mrtrix3.BuildConnectome(out_file="conmat_shen.csv",
                                                scale_invlength=True,
                                                symmetric=True,
                                                zero_diagonal=True,
                                                search_radius=2,
                                                scale_invnodevol=True),
                        name="conmatgen")
    #tck2connectome prob.tck shen_diff_space.nii.gz conmat_length_shen.csv -zero_diagonal -symmetric -scale_length -stat_edge mean -assignment_radial_search 2
    conmatgen2 = pe.Node(mrtrix3.BuildConnectome(
        out_file="conmat_length_shen.csv",
        scale_length=True,
        symmetric=True,
        zero_diagonal=True,
        search_radius=2,
        stat_edge='mean'),
                         name="conmatgen2")

    # Convert mifs to niftis
    fod_convert = pe.Node(mrtrix3.MRConvert(out_filename="FOD.nii.gz"),
                          name="fod_convert")
    gmwmi_convert = pe.Node(mrtrix3.MRConvert(out_filename="gmwmi.nii.gz"),
                            name="gmwmi_convert")

    # Initialize output wf
    datasink = init_tract_output_wf()

    tract_wf.connect([
        # t1 flirt
        (inputnode, t1_skullstrip, [(("t1_file", to_list),
                                     "inputnode.in_files")]),
        (t1_skullstrip, flirt, [("outputnode.out_file", "in_file")]),
        (inputnode, flirt, [("dwi_mask", "reference")]),
        # response function + mask
        (flirt, gen5tt, [("out_file", "in_file")]),
        (gen5tt, gen5ttMask, [("out_file", "in_file")]),
        (inputnode, gen_tuple, [("bvec", "item1"), ("bval", "item2")]),
        (gen_tuple, responseSD, [("out_tuple", "grad_fsl")]),
        (inputnode, responseSD, [("eddy_mask", "in_mask")]),
        (inputnode, responseSD, [
            ("eddy_file", "in_file"),
        ]),
        # FOD gen
        (gen_tuple, estimateFOD, [("out_tuple", "grad_fsl")]),
        (inputnode, estimateFOD, [
            ("eddy_file", "in_file"),
        ]),
        (responseSD, estimateFOD, [("wm_file", "wm_txt")]),
        (inputnode, estimateFOD, [("eddy_mask", "mask_file")]),
        # tckgen
        (estimateFOD, tckgen, [("wm_odf", "in_file")]),
        (gen5tt, tckgen, [("out_file", "act_file")]),
        (gen5ttMask, tckgen, [("out_file", "seed_gmwmi")]),
        (inputnode, tckgen, [("num_tracts", "select")]),
        # tcksift
        (estimateFOD, tcksift, [("wm_odf", "in_fod")]),
        (tckgen, tcksift, [("out_file", "in_tracks")]),
        # atlas flirt
        (inputnode, pre_atlas_flirt, [("t1_file", "in_file"),
                                      ("template", "reference")]),
        (pre_atlas_flirt, xfm_inv, [("out_matrix_file", "in_file")]),
        (flirt, xfm_concat, [("out_matrix_file", "in_file2")]),
        (xfm_inv, xfm_concat, [("out_file", "in_file")]),
        # Shen atlas register
        (inputnode, atlas_flirt, [("atlas", "in_file")]),
        (flirt, atlas_flirt, [("out_file", "reference")]),
        (xfm_concat, atlas_flirt, [("out_file", "in_matrix_file")]),
        # generate connectivity matrices
        (tckgen, conmatgen, [("out_file", "in_file")]),
        (atlas_flirt, conmatgen, [("out_file", "in_parc")]),
        (tcksift, conmatgen, [("out_weights", "in_weights")]),
        (tckgen, conmatgen2, [("out_file", "in_file")]),
        (atlas_flirt, conmatgen2, [("out_file", "in_parc")]),
        # convert mifs to niftis
        (gen5tt, fod_convert, [("out_file", "in_file")]),
        (gen5ttMask, gmwmi_convert, [("out_file", "in_file")]),
        # outputnode
        (fod_convert, outputnode, [("converted", "fod_file")]),
        (gmwmi_convert, outputnode, [("converted", "gmwmi_file")]),
        (tcksift, outputnode, [("out_weights", "prob_weights")]),
        (atlas_flirt, outputnode, [("out_file", "shen_diff_space")]),
        (conmatgen, outputnode, [("out_file", "inv_len_conmat")]),
        (conmatgen2, outputnode, [("out_file", "len_conmat")]),
        # datasink
        (inputnode, datasink, [("subject_id", "inputnode.subject_id"),
                               ("session_id", "inputnode.session_id"),
                               ("output_dir", "inputnode.output_folder")]),
        (outputnode, datasink, [("fod_file", "inputnode.fod_file"),
                                ("gmwmi_file", "inputnode.gmwmi_file"),
                                ("prob_weights", "inputnode.prob_weights"),
                                ("shen_diff_space",
                                 "inputnode.shen_diff_space"),
                                ("inv_len_conmat", "inputnode.inv_len_conmat"),
                                ("len_conmat", "inputnode.len_conmat")]),
    ])

    return tract_wf
예제 #8
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