Example #1
0
def init_surface_recon_wf(omp_nthreads, hires, name='surface_recon_wf'):
    r"""
    Reconstruct anatomical surfaces using FreeSurfer's ``recon-all``.

    Reconstruction is performed in three phases.
    The first phase initializes the subject with T1w and T2w (if available)
    structural images and performs basic reconstruction (``autorecon1``) with the
    exception of skull-stripping.
    For example, a subject with only one session with T1w and T2w images
    would be processed by the following command::

        $ recon-all -sd <output dir>/freesurfer -subjid sub-<subject_label> \
            -i <bids-root>/sub-<subject_label>/anat/sub-<subject_label>_T1w.nii.gz \
            -T2 <bids-root>/sub-<subject_label>/anat/sub-<subject_label>_T2w.nii.gz \
            -autorecon1 \
            -noskullstrip

    The second phase imports an externally computed skull-stripping mask.
    This workflow refines the external brainmask using the internal mask
    implicit the the FreeSurfer's ``aseg.mgz`` segmentation,
    to reconcile ANTs' and FreeSurfer's brain masks.

    First, the ``aseg.mgz`` mask from FreeSurfer is refined in two
    steps, using binary morphological operations:

      1. With a binary closing operation the sulci are included
         into the mask. This results in a smoother brain mask
         that does not exclude deep, wide sulci.

      2. Fill any holes (typically, there could be a hole next to
         the pineal gland and the corpora quadrigemina if the great
         cerebral brain is segmented out).

    Second, the brain mask is grown, including pixels that have a high likelihood
    to the GM tissue distribution:

      3. Dilate and substract the brain mask, defining the region to search for candidate
         pixels that likely belong to cortical GM.

      4. Pixels found in the search region that are labeled as GM by ANTs
         (during ``antsBrainExtraction.sh``) are directly added to the new mask.

      5. Otherwise, estimate GM tissue parameters locally in  patches of ``ww`` size,
         and test the likelihood of the pixel to belong in the GM distribution.

    This procedure is inspired on mindboggle's solution to the problem:
    https://github.com/nipy/mindboggle/blob/7f91faaa7664d820fe12ccc52ebaf21d679795e2/mindboggle/guts/segment.py#L1660


    The final phase resumes reconstruction, using the T2w image to assist
    in finding the pial surface, if available.
    See :py:func:`~smriprep.workflows.surfaces.init_autorecon_resume_wf` for details.


    Memory annotations for FreeSurfer are based off `their documentation
    <https://surfer.nmr.mgh.harvard.edu/fswiki/SystemRequirements>`_.
    They specify an allocation of 4GB per subject. Here we define 5GB
    to have a certain margin.



    .. workflow::
        :graph2use: orig
        :simple_form: yes

        from smriprep.workflows.surfaces import init_surface_recon_wf
        wf = init_surface_recon_wf(omp_nthreads=1, hires=True)

    **Parameters**

        omp_nthreads : int
            Maximum number of threads an individual process may use
        hires : bool
            Enable sub-millimeter preprocessing in FreeSurfer

    **Inputs**

        t1w
            List of T1-weighted structural images
        t2w
            List of T2-weighted structural images (only first used)
        flair
            List of FLAIR images
        skullstripped_t1
            Skull-stripped T1-weighted image (or mask of image)
        ants_segs
            Brain tissue segmentation from ANTS ``antsBrainExtraction.sh``
        corrected_t1
            INU-corrected, merged T1-weighted image
        subjects_dir
            FreeSurfer SUBJECTS_DIR
        subject_id
            FreeSurfer subject ID

    **Outputs**

        subjects_dir
            FreeSurfer SUBJECTS_DIR
        subject_id
            FreeSurfer subject ID
        t1w2fsnative_xfm
            LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space
        fsnative2t1w_xfm
            LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w
        surfaces
            GIFTI surfaces for gray/white matter boundary, pial surface,
            midthickness (or graymid) surface, and inflated surfaces
        out_brainmask
            Refined brainmask, derived from FreeSurfer's ``aseg`` volume
        out_aseg
            FreeSurfer's aseg segmentation, in native T1w space
        out_aparc
            FreeSurfer's aparc+aseg segmentation, in native T1w space

    **Subworkflows**

        * :py:func:`~smriprep.workflows.surfaces.init_autorecon_resume_wf`
        * :py:func:`~smriprep.workflows.surfaces.init_gifti_surface_wf`
    """
    workflow = Workflow(name=name)
    workflow.__desc__ = """\
Brain surfaces were reconstructed using `recon-all` [FreeSurfer {fs_ver},
RRID:SCR_001847, @fs_reconall], and the brain mask estimated
previously was refined with a custom variation of the method to reconcile
ANTs-derived and FreeSurfer-derived segmentations of the cortical
gray-matter of Mindboggle [RRID:SCR_002438, @mindboggle].
""".format(fs_ver=fs.Info().looseversion() or '<ver>')

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        't1w', 't2w', 'flair', 'skullstripped_t1', 'corrected_t1', 'ants_segs',
        'subjects_dir', 'subject_id'
    ]),
                        name='inputnode')
    outputnode = pe.Node(niu.IdentityInterface(fields=[
        'subjects_dir', 'subject_id', 't1w2fsnative_xfm', 'fsnative2t1w_xfm',
        'surfaces', 'out_brainmask', 'out_aseg', 'out_aparc'
    ]),
                         name='outputnode')

    recon_config = pe.Node(FSDetectInputs(hires_enabled=hires),
                           name='recon_config')

    fov_check = pe.Node(niu.Function(function=_check_cw256), name='fov_check')

    autorecon1 = pe.Node(fs.ReconAll(directive='autorecon1',
                                     openmp=omp_nthreads),
                         name='autorecon1',
                         n_procs=omp_nthreads,
                         mem_gb=5)
    autorecon1.interface._can_resume = False
    autorecon1.interface._always_run = True

    skull_strip_extern = pe.Node(FSInjectBrainExtracted(),
                                 name='skull_strip_extern')

    fsnative2t1w_xfm = pe.Node(RobustRegister(auto_sens=True,
                                              est_int_scale=True),
                               name='fsnative2t1w_xfm')
    t1w2fsnative_xfm = pe.Node(LTAConvert(out_lta=True, invert=True),
                               name='t1w2fsnative_xfm')

    autorecon_resume_wf = init_autorecon_resume_wf(omp_nthreads=omp_nthreads)
    gifti_surface_wf = init_gifti_surface_wf()

    aseg_to_native_wf = init_segs_to_native_wf()
    aparc_to_native_wf = init_segs_to_native_wf(segmentation='aparc_aseg')
    refine = pe.Node(RefineBrainMask(), name='refine')

    workflow.connect([
        # Configuration
        (inputnode, recon_config, [('t1w', 't1w_list'), ('t2w', 't2w_list'),
                                   ('flair', 'flair_list')]),
        # Passing subjects_dir / subject_id enforces serial order
        (inputnode, autorecon1, [('subjects_dir', 'subjects_dir'),
                                 ('subject_id', 'subject_id')]),
        (autorecon1, skull_strip_extern, [('subjects_dir', 'subjects_dir'),
                                          ('subject_id', 'subject_id')]),
        (skull_strip_extern, autorecon_resume_wf,
         [('subjects_dir', 'inputnode.subjects_dir'),
          ('subject_id', 'inputnode.subject_id')]),
        (autorecon_resume_wf, gifti_surface_wf,
         [('outputnode.subjects_dir', 'inputnode.subjects_dir'),
          ('outputnode.subject_id', 'inputnode.subject_id')]),
        # Reconstruction phases
        (inputnode, autorecon1, [('t1w', 'T1_files')]),
        (inputnode, fov_check, [('t1w', 'in_files')]),
        (fov_check, autorecon1, [('out', 'flags')]),
        (
            recon_config,
            autorecon1,
            [
                ('t2w', 'T2_file'),
                ('flair', 'FLAIR_file'),
                ('hires', 'hires'),
                # First run only (recon-all saves expert options)
                ('mris_inflate', 'mris_inflate')
            ]),
        (inputnode, skull_strip_extern, [('skullstripped_t1', 'in_brain')]),
        (recon_config, autorecon_resume_wf, [('use_t2w', 'inputnode.use_T2'),
                                             ('use_flair',
                                              'inputnode.use_FLAIR')]),
        # Construct transform from FreeSurfer conformed image to sMRIPrep
        # reoriented image
        (inputnode, fsnative2t1w_xfm, [('t1w', 'target_file')]),
        (autorecon1, fsnative2t1w_xfm, [('T1', 'source_file')]),
        (fsnative2t1w_xfm, gifti_surface_wf, [('out_reg_file',
                                               'inputnode.fsnative2t1w_xfm')]),
        (fsnative2t1w_xfm, t1w2fsnative_xfm, [('out_reg_file', 'in_lta')]),
        # Refine ANTs mask, deriving new mask from FS' aseg
        (inputnode, refine, [('corrected_t1', 'in_anat'),
                             ('ants_segs', 'in_ants')]),
        (inputnode, aseg_to_native_wf, [('corrected_t1', 'inputnode.in_file')
                                        ]),
        (autorecon_resume_wf, aseg_to_native_wf,
         [('outputnode.subjects_dir', 'inputnode.subjects_dir'),
          ('outputnode.subject_id', 'inputnode.subject_id')]),
        (inputnode, aparc_to_native_wf, [('corrected_t1', 'inputnode.in_file')
                                         ]),
        (autorecon_resume_wf, aparc_to_native_wf,
         [('outputnode.subjects_dir', 'inputnode.subjects_dir'),
          ('outputnode.subject_id', 'inputnode.subject_id')]),
        (aseg_to_native_wf, refine, [('outputnode.out_file', 'in_aseg')]),

        # Output
        (autorecon_resume_wf, outputnode,
         [('outputnode.subjects_dir', 'subjects_dir'),
          ('outputnode.subject_id', 'subject_id')]),
        (gifti_surface_wf, outputnode, [('outputnode.surfaces', 'surfaces')]),
        (t1w2fsnative_xfm, outputnode, [('out_lta', 't1w2fsnative_xfm')]),
        (fsnative2t1w_xfm, outputnode, [('out_reg_file', 'fsnative2t1w_xfm')]),
        (refine, outputnode, [('out_file', 'out_brainmask')]),
        (aseg_to_native_wf, outputnode, [('outputnode.out_file', 'out_aseg')]),
        (aparc_to_native_wf, outputnode, [('outputnode.out_file', 'out_aparc')
                                          ]),
    ])

    return workflow
Example #2
0
def init_bold_surf_wf(mem_gb,
                      output_spaces,
                      medial_surface_nan,
                      name='bold_surf_wf'):
    """
    This workflow samples functional images to FreeSurfer surfaces

    For each vertex, the cortical ribbon is sampled at six points (spaced 20% of thickness apart)
    and averaged.

    Outputs are in GIFTI format.

    .. workflow::
        :graph2use: colored
        :simple_form: yes

        from fmriprep.workflows.bold import init_bold_surf_wf
        wf = init_bold_surf_wf(mem_gb=0.1,
                               output_spaces=['T1w', 'fsnative',
                                             'template', 'fsaverage5'],
                               medial_surface_nan=False)

    **Parameters**

        output_spaces : list
            List of output spaces functional images are to be resampled to
            Target spaces beginning with ``fs`` will be selected for resampling,
            such as ``fsaverage`` or related template spaces
            If the list contains ``fsnative``, images will be resampled to the
            individual subject's native surface
        medial_surface_nan : bool
            Replace medial wall values with NaNs on functional GIFTI files

    **Inputs**

        source_file
            Motion-corrected BOLD series in T1 space
        t1_preproc
            Bias-corrected structural template image
        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

    **Outputs**

        surfaces
            BOLD series, resampled to FreeSurfer surfaces

    """
    # Ensure volumetric spaces do not sneak into this workflow
    spaces = [space for space in output_spaces if space.startswith('fs')]

    workflow = Workflow(name=name)

    if spaces:
        workflow.__desc__ = """\
The BOLD time-series, were resampled to surfaces on the following
spaces: {out_spaces}.
""".format(out_spaces=', '.join(['*%s*' % s for s in spaces]))
    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'source_file', 't1_preproc', 'subject_id', 'subjects_dir',
        't1_2_fsnative_forward_transform'
    ]),
                        name='inputnode')

    outputnode = pe.Node(niu.IdentityInterface(fields=['surfaces']),
                         name='outputnode')

    def select_target(subject_id, space):
        """ Given a source subject ID and a target space, get the target subject ID """
        return subject_id if space == 'fsnative' else space

    targets = pe.MapNode(niu.Function(function=select_target),
                         iterfield=['space'],
                         name='targets',
                         mem_gb=DEFAULT_MEMORY_MIN_GB)
    targets.inputs.space = spaces

    # Rename the source file to the output space to simplify naming later
    rename_src = pe.MapNode(niu.Rename(format_string='%(subject)s',
                                       keep_ext=True),
                            iterfield='subject',
                            name='rename_src',
                            run_without_submitting=True,
                            mem_gb=DEFAULT_MEMORY_MIN_GB)
    rename_src.inputs.subject = spaces

    resampling_xfm = pe.Node(LTAConvert(in_lta='identity.nofile',
                                        out_lta=True),
                             name='resampling_xfm')
    set_xfm_source = pe.Node(ConcatenateLTA(out_type='RAS2RAS'),
                             name='set_xfm_source')

    sampler = pe.MapNode(fs.SampleToSurface(sampling_method='average',
                                            sampling_range=(0, 1, 0.2),
                                            sampling_units='frac',
                                            interp_method='trilinear',
                                            cortex_mask=True,
                                            override_reg_subj=True,
                                            out_type='gii'),
                         iterfield=['source_file', 'target_subject'],
                         iterables=('hemi', ['lh', 'rh']),
                         name='sampler',
                         mem_gb=mem_gb * 3)

    medial_nans = pe.MapNode(MedialNaNs(),
                             iterfield=['in_file', 'target_subject'],
                             name='medial_nans',
                             mem_gb=DEFAULT_MEMORY_MIN_GB)

    merger = pe.JoinNode(niu.Merge(1, ravel_inputs=True),
                         name='merger',
                         joinsource='sampler',
                         joinfield=['in1'],
                         run_without_submitting=True,
                         mem_gb=DEFAULT_MEMORY_MIN_GB)

    update_metadata = pe.MapNode(GiftiSetAnatomicalStructure(),
                                 iterfield='in_file',
                                 name='update_metadata',
                                 mem_gb=DEFAULT_MEMORY_MIN_GB)

    workflow.connect([
        (inputnode, targets, [('subject_id', 'subject_id')]),
        (inputnode, rename_src, [('source_file', 'in_file')]),
        (inputnode, resampling_xfm, [('source_file', 'source_file'),
                                     ('t1_preproc', 'target_file')]),
        (inputnode, set_xfm_source, [('t1_2_fsnative_forward_transform',
                                      'in_lta2')]),
        (resampling_xfm, set_xfm_source, [('out_lta', 'in_lta1')]),
        (inputnode, sampler, [('subjects_dir', 'subjects_dir'),
                              ('subject_id', 'subject_id')]),
        (set_xfm_source, sampler, [('out_file', 'reg_file')]),
        (targets, sampler, [('out', 'target_subject')]),
        (rename_src, sampler, [('out_file', 'source_file')]),
        (merger, update_metadata, [('out', 'in_file')]),
        (update_metadata, outputnode, [('out_file', 'surfaces')]),
    ])

    if medial_surface_nan:
        workflow.connect([
            (inputnode, medial_nans, [('subjects_dir', 'subjects_dir')]),
            (sampler, medial_nans, [('out_file', 'in_file')]),
            (targets, medial_nans, [('out', 'target_subject')]),
            (medial_nans, merger, [('out_file', 'in1')]),
        ])
    else:
        workflow.connect(sampler, 'out_file', merger, 'in1')

    return workflow
def init_bbreg_wf(use_bbr,
                  bold2t1w_dof,
                  bold2t1w_init,
                  omp_nthreads,
                  name='bbreg_wf'):
    """
    Build a workflow to run FreeSurfer's ``bbregister``.

    This workflow uses FreeSurfer's ``bbregister`` to register a BOLD image to
    a T1-weighted structural image.

    It is a counterpart to :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`,
    which performs the same task using FSL's FLIRT with a BBR cost function.
    The ``use_bbr`` option permits a high degree of control over registration.
    If ``False``, standard, affine coregistration will be performed using
    FreeSurfer's ``mri_coreg`` tool.
    If ``True``, ``bbregister`` will be seeded with the initial transform found
    by ``mri_coreg`` (equivalent to running ``bbregister --init-coreg``).
    If ``None``, after ``bbregister`` is run, the resulting affine transform
    will be compared to the initial transform found by ``mri_coreg``.
    Excessive deviation will result in rejecting the BBR refinement and
    accepting the original, affine registration.

    Workflow Graph
        .. workflow ::
            :graph2use: orig
            :simple_form: yes

            from fmriprep.workflows.bold.registration import init_bbreg_wf
            wf = init_bbreg_wf(use_bbr=True, bold2t1w_dof=9,
                               bold2t1w_init='register', omp_nthreads=1)


    Parameters
    ----------
    use_bbr : :obj:`bool` or None
        Enable/disable boundary-based registration refinement.
        If ``None``, test BBR result for distortion before accepting.
    bold2t1w_dof : 6, 9 or 12
        Degrees-of-freedom for BOLD-T1w registration
    bold2t1w_init : str, 'header' or 'register'
        If ``'header'``, use header information for initialization of BOLD and T1 images.
        If ``'register'``, align volumes by their centers.
    name : :obj:`str`, optional
        Workflow name (default: bbreg_wf)

    Inputs
    ------
    in_file
        Reference BOLD image to be registered
    fsnative2t1w_xfm
        FSL-style affine matrix translating from FreeSurfer T1.mgz to T1w
    subjects_dir
        FreeSurfer SUBJECTS_DIR
    subject_id
        FreeSurfer subject ID (must have folder in SUBJECTS_DIR)
    t1w_brain
        Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`)
    t1w_dseg
        Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`)

    Outputs
    -------
    itk_bold_to_t1
        Affine transform from ``ref_bold_brain`` to T1 space (ITK format)
    itk_t1_to_bold
        Affine transform from T1 space to BOLD space (ITK format)
    out_report
        Reportlet for assessing registration quality
    fallback
        Boolean indicating whether BBR was rejected (mri_coreg registration returned)

    """
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    # See https://github.com/nipreps/fmriprep/issues/768
    from niworkflows.interfaces.freesurfer import (
        PatchedBBRegisterRPT as BBRegisterRPT, PatchedMRICoregRPT as
        MRICoregRPT, PatchedLTAConvert as LTAConvert)
    from niworkflows.interfaces.nitransforms import ConcatenateXFMs

    workflow = Workflow(name=name)
    workflow.__desc__ = """\
The BOLD reference was then co-registered to the T1w reference using
`bbregister` (FreeSurfer) which implements boundary-based registration [@bbr].
Co-registration was configured with {dof} degrees of freedom{reason}.
""".format(dof={
        6: 'six',
        9: 'nine',
        12: 'twelve'
    }[bold2t1w_dof],
           reason='' if bold2t1w_dof == 6 else
           'to account for distortions remaining in the BOLD reference')

    inputnode = pe.Node(
        niu.IdentityInterface([
            'in_file',
            'fsnative2t1w_xfm',
            'subjects_dir',
            'subject_id',  # BBRegister
            't1w_dseg',
            't1w_brain'
        ]),  # FLIRT BBR
        name='inputnode')
    outputnode = pe.Node(niu.IdentityInterface(
        ['itk_bold_to_t1', 'itk_t1_to_bold', 'out_report', 'fallback']),
                         name='outputnode')

    if bold2t1w_init not in ("register", "header"):
        raise ValueError(
            f"Unknown BOLD-T1w initialization option: {bold2t1w_init}")

    # For now make BBR unconditional - in the future, we can fall back to identity,
    # but adding the flexibility without testing seems a bit dangerous
    if bold2t1w_init == "header":
        if use_bbr is False:
            raise ValueError("Cannot disable BBR and use header registration")
        if use_bbr is None:
            LOGGER.warning(
                "Initializing BBR with header; affine fallback disabled")
            use_bbr = True

    merge_ltas = pe.Node(niu.Merge(2),
                         name='merge_ltas',
                         run_without_submitting=True)
    concat_xfm = pe.Node(ConcatenateXFMs(inverse=True), name='concat_xfm')

    workflow.connect([
        # Output ITK transforms
        (inputnode, merge_ltas, [('fsnative2t1w_xfm', 'in2')]),
        (merge_ltas, concat_xfm, [('out', 'in_xfms')]),
        (concat_xfm, outputnode, [('out_xfm', 'itk_bold_to_t1')]),
        (concat_xfm, outputnode, [('out_inv', 'itk_t1_to_bold')]),
    ])

    # Define both nodes, but only connect conditionally
    mri_coreg = pe.Node(MRICoregRPT(dof=bold2t1w_dof,
                                    sep=[4],
                                    ftol=0.0001,
                                    linmintol=0.01,
                                    generate_report=not use_bbr),
                        name='mri_coreg',
                        n_procs=omp_nthreads,
                        mem_gb=5)

    bbregister = pe.Node(BBRegisterRPT(dof=bold2t1w_dof,
                                       contrast_type='t2',
                                       registered_file=True,
                                       out_lta_file=True,
                                       generate_report=True),
                         name='bbregister',
                         mem_gb=12)

    # Use mri_coreg
    if bold2t1w_init == "register":
        workflow.connect([
            (inputnode, mri_coreg, [('subjects_dir', 'subjects_dir'),
                                    ('subject_id', 'subject_id'),
                                    ('in_file', 'source_file')]),
        ])

        # Short-circuit workflow building, use initial registration
        if use_bbr is False:
            workflow.connect([
                (mri_coreg, outputnode, [('out_report', 'out_report')]),
                (mri_coreg, merge_ltas, [('out_lta_file', 'in1')])
            ])
            outputnode.inputs.fallback = True

            return workflow

    # Use bbregister
    workflow.connect([
        (inputnode, bbregister, [('subjects_dir', 'subjects_dir'),
                                 ('subject_id', 'subject_id'),
                                 ('in_file', 'source_file')]),
    ])

    if bold2t1w_init == "header":
        bbregister.inputs.init = "header"
    else:
        workflow.connect([(mri_coreg, bbregister, [('out_lta_file',
                                                    'init_reg_file')])])

    # Short-circuit workflow building, use boundary-based registration
    if use_bbr is True:
        workflow.connect([(bbregister, outputnode, [('out_report',
                                                     'out_report')]),
                          (bbregister, merge_ltas, [('out_lta_file', 'in1')])])
        outputnode.inputs.fallback = False

        return workflow

    # Only reach this point if bold2t1w_init is "register" and use_bbr is None

    transforms = pe.Node(niu.Merge(2),
                         run_without_submitting=True,
                         name='transforms')
    reports = pe.Node(niu.Merge(2),
                      run_without_submitting=True,
                      name='reports')

    lta_ras2ras = pe.MapNode(LTAConvert(out_lta=True),
                             iterfield=['in_lta'],
                             name='lta_ras2ras',
                             mem_gb=2)
    compare_transforms = pe.Node(niu.Function(function=compare_xforms),
                                 name='compare_transforms')

    select_transform = pe.Node(niu.Select(),
                               run_without_submitting=True,
                               name='select_transform')
    select_report = pe.Node(niu.Select(),
                            run_without_submitting=True,
                            name='select_report')

    workflow.connect([
        (bbregister, transforms, [('out_lta_file', 'in1')]),
        (mri_coreg, transforms, [('out_lta_file', 'in2')]),
        # Normalize LTA transforms to RAS2RAS (inputs are VOX2VOX) and compare
        (transforms, lta_ras2ras, [('out', 'in_lta')]),
        (lta_ras2ras, compare_transforms, [('out_lta', 'lta_list')]),
        (compare_transforms, outputnode, [('out', 'fallback')]),
        # Select output transform
        (transforms, select_transform, [('out', 'inlist')]),
        (compare_transforms, select_transform, [('out', 'index')]),
        (select_transform, merge_ltas, [('out', 'in1')]),
        # Select output report
        (bbregister, reports, [('out_report', 'in1')]),
        (mri_coreg, reports, [('out_report', 'in2')]),
        (reports, select_report, [('out', 'inlist')]),
        (compare_transforms, select_report, [('out', 'index')]),
        (select_report, outputnode, [('out', 'out_report')]),
    ])

    return workflow
def init_fsl_bbr_wf(use_bbr,
                    bold2t1w_dof,
                    bold2t1w_init,
                    sloppy=False,
                    name='fsl_bbr_wf'):
    """
    Build a workflow to run FSL's ``flirt``.

    This workflow uses FSL FLIRT to register a BOLD image to a T1-weighted
    structural image, using a boundary-based registration (BBR) cost function.
    It is a counterpart to :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`,
    which performs the same task using FreeSurfer's ``bbregister``.

    The ``use_bbr`` option permits a high degree of control over registration.
    If ``False``, standard, rigid coregistration will be performed by FLIRT.
    If ``True``, FLIRT-BBR will be seeded with the initial transform found by
    the rigid coregistration.
    If ``None``, after FLIRT-BBR is run, the resulting affine transform
    will be compared to the initial transform found by FLIRT.
    Excessive deviation will result in rejecting the BBR refinement and
    accepting the original, affine registration.

    Workflow Graph
        .. workflow ::
            :graph2use: orig
            :simple_form: yes

            from fmriprep.workflows.bold.registration import init_fsl_bbr_wf
            wf = init_fsl_bbr_wf(use_bbr=True, bold2t1w_dof=9, bold2t1w_init='register')


    Parameters
    ----------
    use_bbr : :obj:`bool` or None
        Enable/disable boundary-based registration refinement.
        If ``None``, test BBR result for distortion before accepting.
    bold2t1w_dof : 6, 9 or 12
        Degrees-of-freedom for BOLD-T1w registration
    bold2t1w_init : str, 'header' or 'register'
        If ``'header'``, use header information for initialization of BOLD and T1 images.
        If ``'register'``, align volumes by their centers.
    name : :obj:`str`, optional
        Workflow name (default: fsl_bbr_wf)

    Inputs
    ------
    in_file
        Reference BOLD image to be registered
    t1w_brain
        Skull-stripped T1-weighted structural image
    t1w_dseg
        FAST segmentation of ``t1w_brain``
    fsnative2t1w_xfm
        Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`)
    subjects_dir
        Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`)
    subject_id
        Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`)

    Outputs
    -------
    itk_bold_to_t1
        Affine transform from ``ref_bold_brain`` to T1w space (ITK format)
    itk_t1_to_bold
        Affine transform from T1 space to BOLD space (ITK format)
    out_report
        Reportlet for assessing registration quality
    fallback
        Boolean indicating whether BBR was rejected (rigid FLIRT registration returned)

    """
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.utils.images import dseg_label as _dseg_label
    from niworkflows.interfaces.freesurfer import PatchedLTAConvert as LTAConvert
    from niworkflows.interfaces.registration import FLIRTRPT
    workflow = Workflow(name=name)
    workflow.__desc__ = """\
The BOLD reference was then co-registered to the T1w reference using
`flirt` [FSL {fsl_ver}, @flirt] with the boundary-based registration [@bbr]
cost-function.
Co-registration was configured with nine degrees of freedom to account
for distortions remaining in the BOLD reference.
""".format(fsl_ver=FLIRTRPT().version or '<ver>')

    inputnode = pe.Node(
        niu.IdentityInterface([
            'in_file',
            'fsnative2t1w_xfm',
            'subjects_dir',
            'subject_id',  # BBRegister
            't1w_dseg',
            't1w_brain'
        ]),  # FLIRT BBR
        name='inputnode')
    outputnode = pe.Node(niu.IdentityInterface(
        ['itk_bold_to_t1', 'itk_t1_to_bold', 'out_report', 'fallback']),
                         name='outputnode')

    wm_mask = pe.Node(niu.Function(function=_dseg_label), name='wm_mask')
    wm_mask.inputs.label = 2  # BIDS default is WM=2
    flt_bbr_init = pe.Node(FLIRTRPT(dof=6,
                                    generate_report=not use_bbr,
                                    uses_qform=True),
                           name='flt_bbr_init')

    if bold2t1w_init not in ("register", "header"):
        raise ValueError(
            f"Unknown BOLD-T1w initialization option: {bold2t1w_init}")

    if bold2t1w_init == "header":
        raise NotImplementedError(
            "Header-based registration initialization not supported for FSL")

    invt_bbr = pe.Node(fsl.ConvertXFM(invert_xfm=True),
                       name='invt_bbr',
                       mem_gb=DEFAULT_MEMORY_MIN_GB)

    # BOLD to T1 transform matrix is from fsl, using c3 tools to convert to
    # something ANTs will like.
    fsl2itk_fwd = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True),
                          name='fsl2itk_fwd',
                          mem_gb=DEFAULT_MEMORY_MIN_GB)
    fsl2itk_inv = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True),
                          name='fsl2itk_inv',
                          mem_gb=DEFAULT_MEMORY_MIN_GB)

    workflow.connect([
        (inputnode, flt_bbr_init, [('in_file', 'in_file'),
                                   ('t1w_brain', 'reference')]),
        (inputnode, fsl2itk_fwd, [('t1w_brain', 'reference_file'),
                                  ('in_file', 'source_file')]),
        (inputnode, fsl2itk_inv, [('in_file', 'reference_file'),
                                  ('t1w_brain', 'source_file')]),
        (invt_bbr, fsl2itk_inv, [('out_file', 'transform_file')]),
        (fsl2itk_fwd, outputnode, [('itk_transform', 'itk_bold_to_t1')]),
        (fsl2itk_inv, outputnode, [('itk_transform', 'itk_t1_to_bold')]),
    ])

    # Short-circuit workflow building, use rigid registration
    if use_bbr is False:
        workflow.connect([
            (flt_bbr_init, invt_bbr, [('out_matrix_file', 'in_file')]),
            (flt_bbr_init, fsl2itk_fwd, [('out_matrix_file', 'transform_file')
                                         ]),
            (flt_bbr_init, outputnode, [('out_report', 'out_report')]),
        ])
        outputnode.inputs.fallback = True

        return workflow

    flt_bbr = pe.Node(FLIRTRPT(cost_func='bbr',
                               dof=bold2t1w_dof,
                               generate_report=True),
                      name='flt_bbr')

    FSLDIR = os.getenv('FSLDIR')
    if FSLDIR:
        flt_bbr.inputs.schedule = op.join(FSLDIR, 'etc/flirtsch/bbr.sch')
    else:
        # Should mostly be hit while building docs
        LOGGER.warning("FSLDIR unset - using packaged BBR schedule")
        flt_bbr.inputs.schedule = pkgr.resource_filename(
            'fmriprep', 'data/flirtsch/bbr.sch')

    workflow.connect([
        (inputnode, wm_mask, [('t1w_dseg', 'in_seg')]),
        (inputnode, flt_bbr, [('in_file', 'in_file')]),
        (flt_bbr_init, flt_bbr, [('out_matrix_file', 'in_matrix_file')]),
    ])

    if sloppy is True:
        downsample = pe.Node(niu.Function(
            function=_conditional_downsampling,
            output_names=["out_file", "out_mask"]),
                             name='downsample')
        workflow.connect([
            (inputnode, downsample, [("t1w_brain", "in_file")]),
            (wm_mask, downsample, [("out", "in_mask")]),
            (downsample, flt_bbr, [('out_file', 'reference'),
                                   ('out_mask', 'wm_seg')]),
        ])
    else:
        workflow.connect([
            (inputnode, flt_bbr, [('t1w_brain', 'reference')]),
            (wm_mask, flt_bbr, [('out', 'wm_seg')]),
        ])

    # Short-circuit workflow building, use boundary-based registration
    if use_bbr is True:
        workflow.connect([
            (flt_bbr, invt_bbr, [('out_matrix_file', 'in_file')]),
            (flt_bbr, fsl2itk_fwd, [('out_matrix_file', 'transform_file')]),
            (flt_bbr, outputnode, [('out_report', 'out_report')]),
        ])
        outputnode.inputs.fallback = False

        return workflow

    transforms = pe.Node(niu.Merge(2),
                         run_without_submitting=True,
                         name='transforms')
    reports = pe.Node(niu.Merge(2),
                      run_without_submitting=True,
                      name='reports')

    compare_transforms = pe.Node(niu.Function(function=compare_xforms),
                                 name='compare_transforms')

    select_transform = pe.Node(niu.Select(),
                               run_without_submitting=True,
                               name='select_transform')
    select_report = pe.Node(niu.Select(),
                            run_without_submitting=True,
                            name='select_report')

    fsl_to_lta = pe.MapNode(LTAConvert(out_lta=True),
                            iterfield=['in_fsl'],
                            name='fsl_to_lta')

    workflow.connect([
        (flt_bbr, transforms, [('out_matrix_file', 'in1')]),
        (flt_bbr_init, transforms, [('out_matrix_file', 'in2')]),
        # Convert FSL transforms to LTA (RAS2RAS) transforms and compare
        (inputnode, fsl_to_lta, [('in_file', 'source_file'),
                                 ('t1w_brain', 'target_file')]),
        (transforms, fsl_to_lta, [('out', 'in_fsl')]),
        (fsl_to_lta, compare_transforms, [('out_lta', 'lta_list')]),
        (compare_transforms, outputnode, [('out', 'fallback')]),
        # Select output transform
        (transforms, select_transform, [('out', 'inlist')]),
        (compare_transforms, select_transform, [('out', 'index')]),
        (select_transform, invt_bbr, [('out', 'in_file')]),
        (select_transform, fsl2itk_fwd, [('out', 'transform_file')]),
        (flt_bbr, reports, [('out_report', 'in1')]),
        (flt_bbr_init, reports, [('out_report', 'in2')]),
        (reports, select_report, [('out', 'inlist')]),
        (compare_transforms, select_report, [('out', 'index')]),
        (select_report, outputnode, [('out', 'out_report')]),
    ])

    return workflow
Example #5
0
def init_anat_template_wf(longitudinal,
                          omp_nthreads,
                          num_t1w,
                          name='anat_template_wf'):
    """
    Generate a canonically-oriented, structural average from all input T1w images.

    Workflow Graph
        .. workflow::
            :graph2use: orig
            :simple_form: yes

            from smriprep.workflows.anatomical import init_anat_template_wf
            wf = init_anat_template_wf(
                longitudinal=False, omp_nthreads=1, num_t1w=1)

    Parameters
    ----------
    longitudinal : bool
        Create unbiased structural average, regardless of number of inputs
        (may increase runtime)
    omp_nthreads : int
        Maximum number of threads an individual process may use
    num_t1w : int
        Number of T1w images
    name : str, optional
        Workflow name (default: anat_template_wf)

    Inputs
    ------
    t1w
        List of T1-weighted structural images

    Outputs
    -------
    t1w_ref
        Structural reference averaging input T1w images, defining the T1w space.
    t1w_realign_xfm
        List of affine transforms to realign input T1w images
    out_report
        Conformation report

    """
    workflow = Workflow(name=name)

    if num_t1w > 1:
        workflow.__desc__ = """\
A T1w-reference map was computed after registration of
{num_t1w} T1w images (after INU-correction) using
`mri_robust_template` [FreeSurfer {fs_ver}, @fs_template].
""".format(num_t1w=num_t1w, fs_ver=fs.Info().looseversion() or '<ver>')

    inputnode = pe.Node(niu.IdentityInterface(fields=['t1w']),
                        name='inputnode')
    outputnode = pe.Node(niu.IdentityInterface(
        fields=['t1w_ref', 't1w_valid_list', 't1w_realign_xfm', 'out_report']),
                         name='outputnode')

    # 0. Reorient T1w image(s) to RAS and resample to common voxel space
    t1w_ref_dimensions = pe.Node(TemplateDimensions(),
                                 name='t1w_ref_dimensions')
    t1w_conform = pe.MapNode(Conform(),
                             iterfield='in_file',
                             name='t1w_conform')

    workflow.connect([
        (inputnode, t1w_ref_dimensions, [('t1w', 't1w_list')]),
        (t1w_ref_dimensions, t1w_conform, [('t1w_valid_list', 'in_file'),
                                           ('target_zooms', 'target_zooms'),
                                           ('target_shape', 'target_shape')]),
        (t1w_ref_dimensions, outputnode, [('out_report', 'out_report'),
                                          ('t1w_valid_list', 't1w_valid_list')
                                          ]),
    ])

    if num_t1w == 1:
        get1st = pe.Node(niu.Select(index=[0]), name='get1st')
        outputnode.inputs.t1w_realign_xfm = [
            pkgr('smriprep', 'data/itkIdentityTransform.txt')
        ]

        workflow.connect([
            (t1w_conform, get1st, [('out_file', 'inlist')]),
            (get1st, outputnode, [('out', 't1w_ref')]),
        ])

        return workflow

    t1w_conform_xfm = pe.MapNode(LTAConvert(in_lta='identity.nofile',
                                            out_lta=True),
                                 iterfield=['source_file', 'target_file'],
                                 name='t1w_conform_xfm')

    # 1. Template (only if several T1w images)
    # 1a. Correct for bias field: the bias field is an additive factor
    #     in log-transformed intensity units. Therefore, it is not a linear
    #     combination of fields and N4 fails with merged images.
    # 1b. Align and merge if several T1w images are provided
    n4_correct = pe.MapNode(N4BiasFieldCorrection(dimension=3,
                                                  copy_header=True),
                            iterfield='input_image',
                            name='n4_correct',
                            n_procs=1)  # n_procs=1 for reproducibility
    # StructuralReference is fs.RobustTemplate if > 1 volume, copying otherwise
    t1w_merge = pe.Node(
        StructuralReference(
            auto_detect_sensitivity=True,
            initial_timepoint=1,  # For deterministic behavior
            intensity_scaling=True,  # 7-DOF (rigid + intensity)
            subsample_threshold=200,
            fixed_timepoint=not longitudinal,
            no_iteration=not longitudinal,
            transform_outputs=True,
        ),
        mem_gb=2 * num_t1w - 1,
        name='t1w_merge')

    # 2. Reorient template to RAS, if needed (mri_robust_template may set to LIA)
    t1w_reorient = pe.Node(image.Reorient(), name='t1w_reorient')

    concat_affines = pe.MapNode(ConcatenateLTA(out_type='RAS2RAS',
                                               invert_out=True),
                                iterfield=['in_lta1', 'in_lta2'],
                                name='concat_affines')

    lta_to_itk = pe.MapNode(LTAConvert(out_itk=True),
                            iterfield=['in_lta'],
                            name='lta_to_itk')

    def _set_threads(in_list, maximum):
        return min(len(in_list), maximum)

    workflow.connect([
        (t1w_ref_dimensions, t1w_conform_xfm, [('t1w_valid_list',
                                                'source_file')]),
        (t1w_conform, t1w_conform_xfm, [('out_file', 'target_file')]),
        (t1w_conform, n4_correct, [('out_file', 'input_image')]),
        (t1w_conform, t1w_merge,
         [(('out_file', _set_threads, omp_nthreads), 'num_threads'),
          (('out_file', add_suffix, '_template'), 'out_file')]),
        (n4_correct, t1w_merge, [('output_image', 'in_files')]),
        (t1w_merge, t1w_reorient, [('out_file', 'in_file')]),
        # Combine orientation and template transforms
        (t1w_conform_xfm, concat_affines, [('out_lta', 'in_lta1')]),
        (t1w_merge, concat_affines, [('transform_outputs', 'in_lta2')]),
        (concat_affines, lta_to_itk, [('out_file', 'in_lta')]),
        # Output
        (t1w_reorient, outputnode, [('out_file', 't1w_ref')]),
        (lta_to_itk, outputnode, [('out_itk', 't1w_realign_xfm')]),
    ])

    return workflow
Example #6
0
def init_anat_average_wf(
    *,
    bspline_fitting_distance=200,
    longitudinal=False,
    name="anat_average_wf",
    num_maps=1,
    omp_nthreads=None,
    sloppy=False,
):
    """
    Create an average from several images of the same modality.

    Each image undergoes a clipping step, removing background noise and
    high-intensity outliers, which is required by INU correction with the
    N4 algorithm.
    Then INU correction is performed for each of the inputs and the range
    of the image clipped again to fit within uint8.
    Finally, each image is reoriented to have RAS+ data matrix and, if
    more than one inputs, aligned and averaged with FreeSurfer's
    ``mri_robust_template``.

    Parameters
    ----------
    bspline_fitting_distance : :obj:`float`
        Distance in mm between B-Spline control points for N4 INU estimation.
    longitudinal : :obj:`bool`
        Whether an unbiased middle point should be calculated.
    name : :obj:`str`
        This particular workflow's unique name (Nipype requirement).
    num_maps : :obj:`int`
        Then number of input 3D volumes to be averaged.
    omp_nthreads : :obj:`int`
        The number of threads for individual processes in this workflow.
    sloppy : :obj:`bool`
        Run in *sloppy* mode.

    Inputs
    ------
    in_files : :obj:`list`
        A list of one or more input files. They can be 3D or 4D.

    Outputs
    -------
    out_file : :obj:`str`
        The output averaged reference file.
    valid_list : :obj:`list`
        A list of accepted/discarded volumes from the input list.
    realign_xfms : :obj:`list`
        List of rigid-body transformation matrices that bring every volume
        into alignment with the average reference.
    out_report : :obj:`str`
        Path to a reportlet summarizing what happened in this workflow.

    """
    from pkg_resources import resource_filename as pkgr
    from nipype.interfaces.ants import N4BiasFieldCorrection
    from nipype.interfaces.image import Reorient

    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.interfaces.header import ValidateImage
    from niworkflows.interfaces.nibabel import IntensityClip, SplitSeries
    from niworkflows.interfaces.freesurfer import (
        StructuralReference,
        PatchedLTAConvert as LTAConvert,
    )
    from niworkflows.interfaces.images import TemplateDimensions, Conform
    from niworkflows.interfaces.nitransforms import ConcatenateXFMs
    from niworkflows.utils.misc import add_suffix

    wf = Workflow(name=name)

    inputnode = pe.Node(niu.IdentityInterface(fields=["in_files"]),
                        name="inputnode")
    outputnode = pe.Node(
        niu.IdentityInterface(
            fields=["out_file", "valid_list", "realign_xfms", "out_report"]),
        name="outputnode",
    )

    # 1. Validate each of the input images
    validate = pe.MapNode(
        ValidateImage(),
        iterfield="in_file",
        name="validate",
        run_without_submitting=True,
    )

    # 2. Ensure we don't have two timepoints and implicitly squeeze image
    split = pe.MapNode(SplitSeries(), iterfield="in_file", name="split")

    # 3. INU correction of all independent volumes
    clip_preinu = pe.MapNode(IntensityClip(p_min=50),
                             iterfield="in_file",
                             name="clip_preinu")
    correct_inu = pe.MapNode(
        N4BiasFieldCorrection(
            dimension=3,
            save_bias=False,
            copy_header=True,
            n_iterations=[50] * (5 - 2 * sloppy),
            convergence_threshold=1e-7,
            shrink_factor=4,
            bspline_fitting_distance=bspline_fitting_distance,
        ),
        iterfield="input_image",
        n_procs=omp_nthreads,
        name="correct_inu",
    )
    clip_postinu = pe.MapNode(IntensityClip(p_min=10.0, p_max=99.5),
                              iterfield="in_file",
                              name="clip_postinu")

    # 4. Reorient T2w image(s) to RAS and resample to common voxel space
    ref_dimensions = pe.Node(TemplateDimensions(), name="ref_dimensions")
    conform = pe.MapNode(Conform(), iterfield="in_file", name="conform")
    # fmt:off
    wf.connect([
        (inputnode, ref_dimensions, [("in_files", "t1w_list")]),
        (inputnode, validate, [("in_files", "in_file")]),
        (validate, split, [("out_file", "in_file")]),
        (split, clip_preinu, [(("out_files", _flatten), "in_file")]),
        (clip_preinu, correct_inu, [("out_file", "input_image")]),
        (correct_inu, clip_postinu, [("output_image", "in_file")]),
        (ref_dimensions, conform, [("t1w_valid_list", "in_file"),
                                   ("target_zooms", "target_zooms"),
                                   ("target_shape", "target_shape")]),
        (ref_dimensions, outputnode, [("out_report", "out_report"),
                                      ("t1w_valid_list", "valid_list")]),
    ])
    # fmt:on

    # 5. Reorient template to RAS, if needed (mri_robust_template may set to LIA)
    ensure_ras = pe.Node(Reorient(), name="ensure_ras")

    if num_maps == 1:
        get1st = pe.Node(niu.Select(index=[0]), name="get1st")
        outputnode.inputs.realign_xfms = [
            pkgr("smriprep", "data/itkIdentityTransform.txt")
        ]
        # fmt:off
        wf.connect([
            (conform, get1st, [("out_file", "inlist")]),
            (get1st, ensure_ras, [("out", "in_file")]),
            (ensure_ras, outputnode, [("out_file", "out_file")]),
        ])
        # fmt:on
        return wf

    from nipype.interfaces import freesurfer as fs

    wf.__desc__ = f"""\
An anatomical reference-map was computed after registration of
{num_maps} images (after INU-correction) using
`mri_robust_template` [FreeSurfer {fs.Info().looseversion() or "<ver>"}, @fs_template].
"""

    conform_xfm = pe.MapNode(
        LTAConvert(in_lta="identity.nofile", out_lta=True),
        iterfield=["source_file", "target_file"],
        name="conform_xfm",
    )

    # 6. StructuralReference is fs.RobustTemplate if > 1 volume, copying otherwise
    merge = pe.Node(
        StructuralReference(
            auto_detect_sensitivity=True,
            initial_timepoint=1,  # For deterministic behavior
            intensity_scaling=True,  # 7-DOF (rigid + intensity)
            subsample_threshold=200,
            fixed_timepoint=not longitudinal,
            no_iteration=not longitudinal,
            transform_outputs=True,
        ),
        mem_gb=2 * num_maps - 1,
        name="merge",
    )

    # 7. Final intensity equalization/conformation
    clip_final = pe.Node(IntensityClip(p_min=2.0, p_max=99.9),
                         name="clip_final")

    merge_xfm = pe.MapNode(
        niu.Merge(2),
        name="merge_xfm",
        iterfield=["in1", "in2"],
        run_without_submitting=True,
    )
    concat_xfms = pe.MapNode(
        ConcatenateXFMs(inverse=True),
        name="concat_xfms",
        iterfield=["in_xfms"],
        run_without_submitting=True,
    )

    def _set_threads(in_list, maximum):
        return min(len(in_list), maximum)

    # fmt:off
    wf.connect([
        (ref_dimensions, conform_xfm, [("t1w_valid_list", "source_file")]),
        (conform, conform_xfm, [("out_file", "target_file")]),
        (conform, merge,
         [("out_file", "in_files"),
          (("out_file", _set_threads, omp_nthreads), "num_threads"),
          (("out_file", add_suffix, "_template"), "out_file")]),
        (merge, ensure_ras, [("out_file", "in_file")]),
        # Combine orientation and template transforms
        (conform_xfm, merge_xfm, [("out_lta", "in1")]),
        (merge, merge_xfm, [("transform_outputs", "in2")]),
        (merge_xfm, concat_xfms, [("out", "in_xfms")]),
        # Output
        (ensure_ras, clip_final, [("out_file", "in_file")]),
        (clip_final, outputnode, [("out_file", "out_file")]),
        (concat_xfms, outputnode, [("out_xfm", "realign_xfms")]),
    ])
    # fmt:on

    return wf
Example #7
0
def init_bbreg_wf(use_bbr, bold2t1w_dof, omp_nthreads, name='bbreg_wf'):
    """
    Build a workflow to run FreeSurfer's ``bbregister``.

    This workflow uses FreeSurfer's ``bbregister`` to register a BOLD image to
    a T1-weighted structural image.

    It is a counterpart to :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`,
    which performs the same task using FSL's FLIRT with a BBR cost function.
    The ``use_bbr`` option permits a high degree of control over registration.
    If ``False``, standard, affine coregistration will be performed using
    FreeSurfer's ``mri_coreg`` tool.
    If ``True``, ``bbregister`` will be seeded with the initial transform found
    by ``mri_coreg`` (equivalent to running ``bbregister --init-coreg``).
    If ``None``, after ``bbregister`` is run, the resulting affine transform
    will be compared to the initial transform found by ``mri_coreg``.
    Excessive deviation will result in rejecting the BBR refinement and
    accepting the original, affine registration.

    Workflow Graph
        .. workflow ::
            :graph2use: orig
            :simple_form: yes

            from fmriprep.workflows.bold.registration import init_bbreg_wf
            wf = init_bbreg_wf(use_bbr=True, bold2t1w_dof=9, omp_nthreads=1)


    Parameters
    ----------
    use_bbr : bool or None
        Enable/disable boundary-based registration refinement.
        If ``None``, test BBR result for distortion before accepting.
    bold2t1w_dof : 6, 9 or 12
        Degrees-of-freedom for BOLD-T1w registration
    name : str, optional
        Workflow name (default: bbreg_wf)

    Inputs
    ------
    in_file
        Reference BOLD image to be registered
    fsnative2t1w_xfm
        FSL-style affine matrix translating from FreeSurfer T1.mgz to T1w
    subjects_dir
        FreeSurfer SUBJECTS_DIR
    subject_id
        FreeSurfer subject ID (must have folder in SUBJECTS_DIR)
    t1w_brain
        Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`)
    t1w_dseg
        Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`)

    Outputs
    -------
    itk_bold_to_t1
        Affine transform from ``ref_bold_brain`` to T1 space (ITK format)
    itk_t1_to_bold
        Affine transform from T1 space to BOLD space (ITK format)
    out_report
        Reportlet for assessing registration quality
    fallback
        Boolean indicating whether BBR was rejected (mri_coreg registration returned)

    """
    workflow = Workflow(name=name)
    workflow.__desc__ = """\
The BOLD reference was then co-registered to the T1w reference using
`bbregister` (FreeSurfer) which implements boundary-based registration [@bbr].
Co-registration was configured with {dof} degrees of freedom{reason}.
""".format(dof={6: 'six', 9: 'nine', 12: 'twelve'}[bold2t1w_dof],
           reason='' if bold2t1w_dof == 6 else
                  'to account for distortions remaining in the BOLD reference')

    inputnode = pe.Node(
        niu.IdentityInterface([
            'in_file',
            'fsnative2t1w_xfm', 'subjects_dir', 'subject_id',  # BBRegister
            't1w_dseg', 't1w_brain']),  # FLIRT BBR
        name='inputnode')
    outputnode = pe.Node(
        niu.IdentityInterface(['itk_bold_to_t1', 'itk_t1_to_bold', 'out_report', 'fallback']),
        name='outputnode')

    mri_coreg = pe.Node(
        MRICoregRPT(dof=bold2t1w_dof, sep=[4], ftol=0.0001, linmintol=0.01,
                    generate_report=not use_bbr),
        name='mri_coreg', n_procs=omp_nthreads, mem_gb=5)

    lta_concat = pe.Node(ConcatenateLTA(out_file='out.lta'), name='lta_concat')
    # XXX LTA-FSL-ITK may ultimately be able to be replaced with a straightforward
    # LTA-ITK transform, but right now the translation parameters are off.
    lta2fsl_fwd = pe.Node(LTAConvert(out_fsl=True), name='lta2fsl_fwd')
    lta2fsl_inv = pe.Node(LTAConvert(out_fsl=True, invert=True), name='lta2fsl_inv')
    fsl2itk_fwd = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True),
                          name='fsl2itk_fwd', mem_gb=DEFAULT_MEMORY_MIN_GB)
    fsl2itk_inv = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True),
                          name='fsl2itk_inv', mem_gb=DEFAULT_MEMORY_MIN_GB)

    workflow.connect([
        (inputnode, mri_coreg, [('subjects_dir', 'subjects_dir'),
                                ('subject_id', 'subject_id'),
                                ('in_file', 'source_file')]),
        # Output ITK transforms
        (inputnode, lta_concat, [('fsnative2t1w_xfm', 'in_lta2')]),
        (lta_concat, lta2fsl_fwd, [('out_file', 'in_lta')]),
        (lta_concat, lta2fsl_inv, [('out_file', 'in_lta')]),
        (inputnode, fsl2itk_fwd, [('t1w_brain', 'reference_file'),
                                  ('in_file', 'source_file')]),
        (inputnode, fsl2itk_inv, [('in_file', 'reference_file'),
                                  ('t1w_brain', 'source_file')]),
        (lta2fsl_fwd, fsl2itk_fwd, [('out_fsl', 'transform_file')]),
        (lta2fsl_inv, fsl2itk_inv, [('out_fsl', 'transform_file')]),
        (fsl2itk_fwd, outputnode, [('itk_transform', 'itk_bold_to_t1')]),
        (fsl2itk_inv, outputnode, [('itk_transform', 'itk_t1_to_bold')]),
    ])

    # Short-circuit workflow building, use initial registration
    if use_bbr is False:
        workflow.connect([
            (mri_coreg, outputnode, [('out_report', 'out_report')]),
            (mri_coreg, lta_concat, [('out_lta_file', 'in_lta1')])])
        outputnode.inputs.fallback = True

        return workflow

    bbregister = pe.Node(
        BBRegisterRPT(dof=bold2t1w_dof, contrast_type='t2', registered_file=True,
                      out_lta_file=True, generate_report=True),
        name='bbregister', mem_gb=12)

    workflow.connect([
        (inputnode, bbregister, [('subjects_dir', 'subjects_dir'),
                                 ('subject_id', 'subject_id'),
                                 ('in_file', 'source_file')]),
        (mri_coreg, bbregister, [('out_lta_file', 'init_reg_file')]),
    ])

    # Short-circuit workflow building, use boundary-based registration
    if use_bbr is True:
        workflow.connect([
            (bbregister, outputnode, [('out_report', 'out_report')]),
            (bbregister, lta_concat, [('out_lta_file', 'in_lta1')])])
        outputnode.inputs.fallback = False

        return workflow

    transforms = pe.Node(niu.Merge(2), run_without_submitting=True, name='transforms')
    reports = pe.Node(niu.Merge(2), run_without_submitting=True, name='reports')

    lta_ras2ras = pe.MapNode(LTAConvert(out_lta=True), iterfield=['in_lta'],
                             name='lta_ras2ras', mem_gb=2)
    compare_transforms = pe.Node(niu.Function(function=compare_xforms), name='compare_transforms')

    select_transform = pe.Node(niu.Select(), run_without_submitting=True, name='select_transform')
    select_report = pe.Node(niu.Select(), run_without_submitting=True, name='select_report')

    workflow.connect([
        (bbregister, transforms, [('out_lta_file', 'in1')]),
        (mri_coreg, transforms, [('out_lta_file', 'in2')]),
        # Normalize LTA transforms to RAS2RAS (inputs are VOX2VOX) and compare
        (transforms, lta_ras2ras, [('out', 'in_lta')]),
        (lta_ras2ras, compare_transforms, [('out_lta', 'lta_list')]),
        (compare_transforms, outputnode, [('out', 'fallback')]),
        # Select output transform
        (transforms, select_transform, [('out', 'inlist')]),
        (compare_transforms, select_transform, [('out', 'index')]),
        (select_transform, lta_concat, [('out', 'in_lta1')]),
        # Select output report
        (bbregister, reports, [('out_report', 'in1')]),
        (mri_coreg, reports, [('out_report', 'in2')]),
        (reports, select_report, [('out', 'inlist')]),
        (compare_transforms, select_report, [('out', 'index')]),
        (select_report, outputnode, [('out', 'out_report')]),
    ])

    return workflow
Example #8
0
def init_anat_derivatives_wf(bids_root,
                             freesurfer,
                             num_t1w,
                             output_dir,
                             name='anat_derivatives_wf'):
    """Set up a battery of datasinks to store derivatives in the right location."""
    workflow = Workflow(name=name)

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'template', 'source_files', 't1w_ref_xfms', 't1w_preproc', 't1w_mask',
        't1w_dseg', 't1w_tpms', 'anat2std_xfm', 'std2anat_xfm', 'std_t1w',
        'std_mask', 'std_dseg', 'std_tpms', 't1w2fsnative_xfm',
        'fsnative2t1w_xfm', 'surfaces', 't1w_fs_aseg', 't1w_fs_aparc'
    ]),
                        name='inputnode')

    t1w_name = pe.Node(niu.Function(function=fix_multi_T1w_source_name),
                       name='t1w_name')
    raw_sources = pe.Node(niu.Function(function=_bids_relative),
                          name='raw_sources')
    raw_sources.inputs.bids_root = bids_root

    ds_t1w_preproc = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                                 desc='preproc',
                                                 keep_dtype=True,
                                                 compress=True),
                             name='ds_t1w_preproc',
                             run_without_submitting=True)
    ds_t1w_preproc.inputs.SkullStripped = False

    ds_t1w_mask = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                              desc='brain',
                                              suffix='mask',
                                              compress=True),
                          name='ds_t1w_mask',
                          run_without_submitting=True)
    ds_t1w_mask.inputs.Type = 'Brain'

    lut_t1w_dseg = pe.Node(niu.Function(function=_apply_default_bids_lut),
                           name='lut_t1w_dseg')
    ds_t1w_dseg = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                              suffix='dseg',
                                              compress=True),
                          name='ds_t1w_dseg',
                          run_without_submitting=True)

    ds_t1w_tpms = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                              suffix='probseg',
                                              compress=True),
                          name='ds_t1w_tpms',
                          run_without_submitting=True)
    ds_t1w_tpms.inputs.extra_values = ['label-CSF', 'label-GM', 'label-WM']

    ds_t1w_tpl = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                             desc='preproc',
                                             keep_dtype=True,
                                             compress=True),
                         name='ds_t1w_tpl',
                         run_without_submitting=True)
    ds_t1w_tpl.inputs.SkullStripped = True

    ds_std_mask = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                              desc='brain',
                                              suffix='mask',
                                              compress=True),
                          name='ds_std_mask',
                          run_without_submitting=True)
    ds_std_mask.inputs.Type = 'Brain'

    lut_std_dseg = pe.Node(niu.Function(function=_apply_default_bids_lut),
                           name='lut_std_dseg')
    ds_std_dseg = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                              suffix='dseg',
                                              compress=True),
                          name='ds_std_dseg',
                          run_without_submitting=True)

    ds_std_tpms = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                              suffix='probseg',
                                              compress=True),
                          name='ds_std_tpms',
                          run_without_submitting=True)
    ds_std_tpms.inputs.extra_values = ['label-CSF', 'label-GM', 'label-WM']

    # Transforms
    ds_t1w_tpl_inv_warp = pe.Node(DerivativesDataSink(
        base_directory=output_dir,
        allowed_entities=['from', 'to', 'mode'],
        to='T1w',
        mode='image',
        suffix='xfm'),
                                  name='ds_t1w_tpl_inv_warp',
                                  run_without_submitting=True)

    ds_t1w_tpl_warp = pe.Node(DerivativesDataSink(
        base_directory=output_dir,
        allowed_entities=['from', 'to', 'mode'],
        mode='image',
        suffix='xfm',
        **{'from': 'T1w'}),
                              name='ds_t1w_tpl_warp',
                              run_without_submitting=True)

    workflow.connect([
        (inputnode, t1w_name, [('source_files', 'in_files')]),
        (inputnode, raw_sources, [('source_files', 'in_files')]),
        (inputnode, ds_t1w_preproc, [('t1w_preproc', 'in_file')]),
        (inputnode, ds_t1w_mask, [('t1w_mask', 'in_file')]),
        (inputnode, lut_t1w_dseg, [('t1w_dseg', 'in_file')]),
        (inputnode, ds_t1w_tpms, [('t1w_tpms', 'in_file')]),
        (lut_t1w_dseg, ds_t1w_dseg, [('out', 'in_file')]),
        (t1w_name, ds_t1w_preproc, [('out', 'source_file')]),
        (t1w_name, ds_t1w_mask, [('out', 'source_file')]),
        (t1w_name, ds_t1w_dseg, [('out', 'source_file')]),
        (t1w_name, ds_t1w_tpms, [('out', 'source_file')]),
        (raw_sources, ds_t1w_mask, [('out', 'RawSources')]),
        # Template
        (inputnode, ds_t1w_tpl_warp, [('anat2std_xfm', 'in_file'),
                                      ('template', 'to')]),
        (inputnode, ds_t1w_tpl_inv_warp, [('std2anat_xfm', 'in_file'),
                                          ('template', 'from')]),
        (inputnode, ds_t1w_tpl, [('std_t1w', 'in_file'),
                                 ('template', 'space')]),
        (inputnode, ds_std_mask, [('std_mask', 'in_file'),
                                  ('template', 'space'),
                                  (('template', _rawsources), 'RawSources')]),
        (inputnode, ds_std_dseg, [('template', 'space')]),
        (inputnode, lut_std_dseg, [('std_dseg', 'in_file')]),
        (lut_std_dseg, ds_std_dseg, [('out', 'in_file')]),
        (inputnode, ds_std_tpms, [('std_tpms', 'in_file'),
                                  ('template', 'space')]),
        (t1w_name, ds_t1w_tpl_warp, [('out', 'source_file')]),
        (t1w_name, ds_t1w_tpl_inv_warp, [('out', 'source_file')]),
        (t1w_name, ds_t1w_tpl, [('out', 'source_file')]),
        (t1w_name, ds_std_mask, [('out', 'source_file')]),
        (t1w_name, ds_std_dseg, [('out', 'source_file')]),
        (t1w_name, ds_std_tpms, [('out', 'source_file')]),
    ])

    if num_t1w > 1:
        # Please note the dictionary unpacking to provide the from argument.
        # It is necessary because from is a protected keyword (not allowed as argument name).
        ds_t1w_ref_xfms = pe.MapNode(DerivativesDataSink(
            base_directory=output_dir,
            allowed_entities=['from', 'to', 'mode'],
            to='T1w',
            mode='image',
            suffix='xfm',
            **{'from': 'orig'}),
                                     iterfield=['source_file', 'in_file'],
                                     name='ds_t1w_ref_xfms',
                                     run_without_submitting=True)
        workflow.connect([
            (inputnode, ds_t1w_ref_xfms, [('source_files', 'source_file'),
                                          ('t1w_ref_xfms', 'in_file')]),
        ])

    if not freesurfer:
        return workflow

    # FS native space transforms
    lta2itk_fwd = pe.Node(LTAConvert(out_itk=True), name='lta2itk_fwd')
    lta2itk_inv = pe.Node(LTAConvert(out_itk=True), name='lta2itk_inv')
    ds_t1w_fsnative = pe.Node(DerivativesDataSink(
        base_directory=output_dir,
        allowed_entities=['from', 'to', 'mode'],
        mode='image',
        to='fsnative',
        suffix='xfm',
        **{'from': 'T1w'}),
                              name='ds_t1w_fsnative',
                              run_without_submitting=True)
    ds_fsnative_t1w = pe.Node(DerivativesDataSink(
        base_directory=output_dir,
        allowed_entities=['from', 'to', 'mode'],
        mode='image',
        to='T1w',
        suffix='xfm',
        **{'from': 'fsnative'}),
                              name='ds_fsnative_t1w',
                              run_without_submitting=True)
    # Surfaces
    name_surfs = pe.MapNode(GiftiNameSource(
        pattern=r'(?P<LR>[lr])h.(?P<surf>.+)_converted.gii',
        template='hemi-{LR}_{surf}.surf'),
                            iterfield='in_file',
                            name='name_surfs',
                            run_without_submitting=True)
    ds_surfs = pe.MapNode(DerivativesDataSink(base_directory=output_dir),
                          iterfield=['in_file', 'suffix'],
                          name='ds_surfs',
                          run_without_submitting=True)
    # Parcellations
    ds_t1w_fsaseg = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                                desc='aseg',
                                                suffix='dseg',
                                                compress=True),
                            name='ds_t1w_fsaseg',
                            run_without_submitting=True)
    ds_t1w_fsparc = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                                desc='aparcaseg',
                                                suffix='dseg',
                                                compress=True),
                            name='ds_t1w_fsparc',
                            run_without_submitting=True)

    workflow.connect([
        (inputnode, lta2itk_fwd, [('t1w2fsnative_xfm', 'in_lta')]),
        (inputnode, lta2itk_inv, [('fsnative2t1w_xfm', 'in_lta')]),
        (t1w_name, ds_t1w_fsnative, [('out', 'source_file')]),
        (lta2itk_fwd, ds_t1w_fsnative, [('out_itk', 'in_file')]),
        (t1w_name, ds_fsnative_t1w, [('out', 'source_file')]),
        (lta2itk_inv, ds_fsnative_t1w, [('out_itk', 'in_file')]),
        (inputnode, name_surfs, [('surfaces', 'in_file')]),
        (inputnode, ds_surfs, [('surfaces', 'in_file')]),
        (t1w_name, ds_surfs, [('out', 'source_file')]),
        (name_surfs, ds_surfs, [('out_name', 'suffix')]),
        (inputnode, ds_t1w_fsaseg, [('t1w_fs_aseg', 'in_file')]),
        (inputnode, ds_t1w_fsparc, [('t1w_fs_aparc', 'in_file')]),
        (t1w_name, ds_t1w_fsaseg, [('out', 'source_file')]),
        (t1w_name, ds_t1w_fsparc, [('out', 'source_file')]),
    ])

    return workflow
Example #9
0
def init_bold_surf_wf(mem_gb,
                      surface_spaces,
                      medial_surface_nan,
                      name='bold_surf_wf'):
    """
    Sample functional images to FreeSurfer surfaces.

    For each vertex, the cortical ribbon is sampled at six points (spaced 20% of thickness apart)
    and averaged.
    Outputs are in GIFTI format.

    Workflow Graph
        .. workflow::
            :graph2use: colored
            :simple_form: yes

            from fmriprep.workflows.bold import init_bold_surf_wf
            wf = init_bold_surf_wf(mem_gb=0.1,
                                   surface_spaces=['fsnative', 'fsaverage5'],
                                   medial_surface_nan=False)

    Parameters
    ----------
    surface_spaces : :obj:`list`
        List of FreeSurfer surface-spaces (either ``fsaverage{3,4,5,6,}`` or ``fsnative``)
        the functional images are to be resampled to.
        For ``fsnative``, images will be resampled to the individual subject's
        native surface.
    medial_surface_nan : :obj:`bool`
        Replace medial wall values with NaNs on functional GIFTI files

    Inputs
    ------
    source_file
        Motion-corrected BOLD series in T1 space
    t1w_preproc
        Bias-corrected structural template image
    subjects_dir
        FreeSurfer SUBJECTS_DIR
    subject_id
        FreeSurfer subject ID
    t1w2fsnative_xfm
        LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space

    Outputs
    -------
    surfaces
        BOLD series, resampled to FreeSurfer surfaces

    """
    workflow = Workflow(name=name)
    workflow.__desc__ = """\
The BOLD time-series were resampled onto the following surfaces
(FreeSurfer reconstruction nomenclature):
{out_spaces}.
""".format(out_spaces=', '.join(['*%s*' % s for s in surface_spaces]))

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'source_file', 't1w_preproc', 'subject_id', 'subjects_dir',
        't1w2fsnative_xfm'
    ]),
                        name='inputnode')
    itersource = pe.Node(niu.IdentityInterface(fields=['target']),
                         name='itersource')
    itersource.iterables = [('target', surface_spaces)]

    def select_target(subject_id, space):
        """Get the target subject ID, given a source subject ID and a target space."""
        return subject_id if space == 'fsnative' else space

    targets = pe.Node(niu.Function(function=select_target),
                      name='targets',
                      run_without_submitting=True,
                      mem_gb=DEFAULT_MEMORY_MIN_GB)

    # Rename the source file to the output space to simplify naming later
    rename_src = pe.Node(niu.Rename(format_string='%(subject)s',
                                    keep_ext=True),
                         name='rename_src',
                         run_without_submitting=True,
                         mem_gb=DEFAULT_MEMORY_MIN_GB)
    resampling_xfm = pe.Node(LTAConvert(in_lta='identity.nofile',
                                        out_lta=True),
                             name='resampling_xfm')
    set_xfm_source = pe.Node(ConcatenateLTA(out_type='RAS2RAS'),
                             name='set_xfm_source')

    sampler = pe.MapNode(fs.SampleToSurface(
        cortex_mask=True,
        interp_method='trilinear',
        out_type='gii',
        override_reg_subj=True,
        sampling_method='average',
        sampling_range=(0, 1, 0.2),
        sampling_units='frac',
    ),
                         iterfield=['hemi'],
                         name='sampler',
                         mem_gb=mem_gb * 3)
    sampler.inputs.hemi = ['lh', 'rh']
    update_metadata = pe.MapNode(GiftiSetAnatomicalStructure(),
                                 iterfield=['in_file'],
                                 name='update_metadata',
                                 mem_gb=DEFAULT_MEMORY_MIN_GB)

    outputnode = pe.JoinNode(
        niu.IdentityInterface(fields=['surfaces', 'target']),
        joinsource='itersource',
        name='outputnode')

    workflow.connect([
        (inputnode, targets, [('subject_id', 'subject_id')]),
        (inputnode, rename_src, [('source_file', 'in_file')]),
        (inputnode, resampling_xfm, [('source_file', 'source_file'),
                                     ('t1w_preproc', 'target_file')]),
        (inputnode, set_xfm_source, [('t1w2fsnative_xfm', 'in_lta2')]),
        (inputnode, sampler, [('subjects_dir', 'subjects_dir'),
                              ('subject_id', 'subject_id')]),
        (itersource, targets, [('target', 'space')]),
        (itersource, rename_src, [('target', 'subject')]),
        (resampling_xfm, set_xfm_source, [('out_lta', 'in_lta1')]),
        (set_xfm_source, sampler, [('out_file', 'reg_file')]),
        (targets, sampler, [('out', 'target_subject')]),
        (rename_src, sampler, [('out_file', 'source_file')]),
        (update_metadata, outputnode, [('out_file', 'surfaces')]),
        (itersource, outputnode, [('target', 'target')]),
    ])

    if not medial_surface_nan:
        workflow.connect(sampler, 'out_file', update_metadata, 'in_file')
        return workflow

    # Refine if medial vertices should be NaNs
    medial_nans = pe.MapNode(MedialNaNs(),
                             iterfield=['in_file'],
                             name='medial_nans',
                             mem_gb=DEFAULT_MEMORY_MIN_GB)

    workflow.connect([
        (inputnode, medial_nans, [('subjects_dir', 'subjects_dir')]),
        (sampler, medial_nans, [('out_file', 'in_file')]),
        (medial_nans, update_metadata, [('out_file', 'in_file')]),
    ])
    return workflow
Example #10
0
def init_infant_surface_recon_wf(*,
                                 age_months,
                                 use_aseg=False,
                                 name="infant_surface_recon_wf"):
    wf = pe.Workflow(name=name)
    inputnode = pe.Node(
        niu.IdentityInterface(fields=[
            "subjects_dir",
            "subject_id",
            "anat_orig",
            "anat_skullstripped",
            "anat_preproc",
            "anat_aseg",
            "t2w",
        ], ),
        name="inputnode",
    )
    outputnode = pe.Node(
        niu.IdentityInterface(fields=[
            "subjects_dir",
            "subject_id",
            "anat2fsnative_xfm",
            "fsnative2anat_xfm",
            "surfaces",
            "anat_aseg",
            "anat_aparc",
        ]),
        name="outputnode",
    )

    gen_recon_outdir = pe.Node(niu.Function(function=_gen_recon_dir),
                               name='gen_recon_outdir')

    # inject the intensity-normalized skull-stripped t1w from the brain extraction workflow
    recon = pe.Node(InfantReconAll(age=age_months), name="reconall")

    # these files are created by babyFS, but transforms are for masked anatomicals
    # https://github.com/freesurfer/freesurfer/blob/
    # 8b40551f096294cc6603ce928317b8df70bce23e/infant/infant_recon_all#L744
    # TODO: calculate full anat -> fsnative transform?
    get_tal_lta = pe.Node(
        niu.Function(function=_get_talairch_lta),
        name="get_tal_xfm",
    )
    fsnative2anat_xfm = pe.Node(
        LTAConvert(out_lta=True, invert=True),
        name="fsnative2anat_xfm",
    )

    # convert generated surfaces to GIFTIs
    gifti_surface_wf = init_gifti_surface_wf()

    get_aseg = pe.Node(niu.Function(function=_get_aseg), name='get_aseg')
    get_aparc = pe.Node(niu.Function(function=_get_aparc), name="get_aparc")
    aparc2nii = pe.Node(fs.MRIConvert(out_type="niigz"), name="aparc2nii")

    if use_aseg:
        # TODO: Add precomputed segmentation upon new babyFS rel
        wf.connect(inputnode, 'anat_aseg', recon, 'aseg_file')

    # fmt: off
    wf.connect([
        (inputnode, gen_recon_outdir, [
            ('subjects_dir', 'subjects_dir'),
            ('subject_id', 'subject_id'),
        ]),
        (inputnode, recon, [
            ('anat_skullstripped', 'mask_file'),
            ('subject_id', 'subject_id'),
        ]),
        (gen_recon_outdir, recon, [
            ('out', 'outdir'),
        ]),
        (recon, outputnode, [
            ('subject_id', 'subject_id'),
            (('outdir', _parent), 'subjects_dir'),
        ]),
        (recon, gifti_surface_wf, [
            ('subject_id', 'inputnode.subject_id'),
            (('outdir', _parent), 'inputnode.subjects_dir'),
        ]),
        (recon, get_aparc, [
            ('outdir', 'fs_subject_dir'),
        ]),
        (recon, get_aseg, [
            ('outdir', 'fs_subject_dir'),
        ]),
        (get_aseg, outputnode, [
            ('out', 'anat_aseg'),
        ]),
        (get_aparc, aparc2nii, [
            ('out', 'in_file'),
        ]),
        (aparc2nii, outputnode, [
            ('out_file', 'anat_aparc'),
        ]),
        (recon, get_tal_lta, [
            ('outdir', 'fs_subject_dir'),
        ]),
        (get_tal_lta, outputnode, [
            ('out', 'anat2fsnative_xfm'),
        ]),
        (get_tal_lta, fsnative2anat_xfm, [
            ('out', 'in_lta'),
        ]),
        (fsnative2anat_xfm, outputnode, [
            ('out_lta', 'fsnative2anat_xfm'),
        ]),
        (fsnative2anat_xfm, gifti_surface_wf,
         [('out_lta', 'inputnode.fsnative2t1w_xfm')]),
        (gifti_surface_wf, outputnode, [
            ('outputnode.surfaces', 'surfaces'),
        ]),
    ])
    # fmt: on
    return wf
Example #11
0
def init_bold_surf_wf(mem_gb,
                      output_spaces,
                      medial_surface_nan,
                      fslr_density=None,
                      name='bold_surf_wf'):
    """
    Sample functional images to FreeSurfer surfaces.

    For each vertex, the cortical ribbon is sampled at six points (spaced 20% of thickness apart)
    and averaged.
    Outputs are in GIFTI format.

    Workflow Graph
        .. workflow::
            :graph2use: colored
            :simple_form: yes

            from fmriprep.workflows.bold import init_bold_surf_wf
            wf = init_bold_surf_wf(mem_gb=0.1,
                                   output_spaces=['T1w', 'fsnative',
                                                 'template', 'fsaverage5'],
                                   medial_surface_nan=False)

    Parameters
    ----------
    output_spaces : list
        List of output spaces functional images are to be resampled to
        Target spaces beginning with ``fs`` will be selected for resampling,
        such as ``fsaverage`` or related template spaces
        If the list contains ``fsnative``, images will be resampled to the
        individual subject's native surface
        If the list contains ``fsLR``, images will be resampled twice;
        first to ``fsaverage`` and then to ``fsLR``.
    medial_surface_nan : bool
        Replace medial wall values with NaNs on functional GIFTI files
    fslr_density : str, optional
        Density of fsLR surface (32k or 59k)


    Inputs
    ------
    source_file
        Motion-corrected BOLD series in T1 space
    t1w_preproc
        Bias-corrected structural template image
    subjects_dir
        FreeSurfer SUBJECTS_DIR
    subject_id
        FreeSurfer subject ID
    t1w2fsnative_xfm
        LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space

    Outputs
    -------
    surfaces
        BOLD series, resampled to FreeSurfer surfaces

    """
    # Ensure volumetric spaces do not sneak into this workflow
    spaces = [space for space in output_spaces if space.startswith('fs')]

    workflow = Workflow(name=name)

    if spaces:
        workflow.__desc__ = """\
The BOLD time-series, were resampled to surfaces on the following
spaces: {out_spaces}.
""".format(out_spaces=', '.join(['*%s*' % s for s in spaces]))
    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'source_file', 't1w_preproc', 'subject_id', 'subjects_dir',
        't1w2fsnative_xfm'
    ]),
                        name='inputnode')

    to_fslr = False
    if 'fsLR' in output_spaces:
        to_fslr = 'fsaverage' in output_spaces and fslr_density
        spaces.pop(spaces.index('fsLR'))

    outputnode = pe.Node(niu.IdentityInterface(fields=['surfaces']),
                         name='outputnode')

    def select_target(subject_id, space):
        """Get the target subject ID, given a source subject ID and a target space."""
        return subject_id if space == 'fsnative' else space

    targets = pe.MapNode(niu.Function(function=select_target),
                         iterfield=['space'],
                         name='targets',
                         mem_gb=DEFAULT_MEMORY_MIN_GB)
    targets.inputs.space = spaces

    # Rename the source file to the output space to simplify naming later
    rename_src = pe.MapNode(niu.Rename(format_string='%(subject)s',
                                       keep_ext=True),
                            iterfield='subject',
                            name='rename_src',
                            run_without_submitting=True,
                            mem_gb=DEFAULT_MEMORY_MIN_GB)
    rename_src.inputs.subject = spaces

    resampling_xfm = pe.Node(LTAConvert(in_lta='identity.nofile',
                                        out_lta=True),
                             name='resampling_xfm')
    set_xfm_source = pe.Node(ConcatenateLTA(out_type='RAS2RAS'),
                             name='set_xfm_source')

    sampler = pe.MapNode(fs.SampleToSurface(sampling_method='average',
                                            sampling_range=(0, 1, 0.2),
                                            sampling_units='frac',
                                            interp_method='trilinear',
                                            cortex_mask=True,
                                            override_reg_subj=True,
                                            out_type='gii'),
                         iterfield=['source_file', 'target_subject'],
                         iterables=('hemi', ['lh', 'rh']),
                         name='sampler',
                         mem_gb=mem_gb * 3)

    if to_fslr:
        filter_fsavg = pe.Node(niu.Function(
            function=_select_fsaverage_hemi,
            output_names=['fsaverage_bold', 'hemi']),
                               name='filter_fsavg',
                               mem_gb=DEFAULT_MEMORY_MIN_GB,
                               run_without_submitting=True)

        rename_fslr = pe.Node(niu.Rename(format_string="%(hemi)s.fsLR",
                                         keep_ext=True,
                                         parse_string=r'^(?P<hemi>\w+)'),
                              name='rename_fslr',
                              mem_gb=DEFAULT_MEMORY_MIN_GB,
                              run_without_submitting=True)

        fetch_fslr_tpls = pe.Node(niu.Function(function=_fetch_fslr_templates,
                                               output_names=[
                                                   'fsaverage_sphere',
                                                   'fslr_sphere',
                                                   'fsaverage_midthick',
                                                   'fslr_midthick'
                                               ]),
                                  name='fetch_fslr_tpls',
                                  mem_gb=DEFAULT_MEMORY_MIN_GB,
                                  overwrite=True)
        fetch_fslr_tpls.inputs.den = fslr_density

        resample_fslr = pe.Node(wb.MetricResample(method='ADAP_BARY_AREA',
                                                  area_metrics=True),
                                name='resample_fslr')

        merge_fslr = pe.Node(niu.Merge(2),
                             name='merge_fslr',
                             mem_gb=DEFAULT_MEMORY_MIN_GB,
                             run_without_submitting=True)

        def _basename(in_file):
            import os
            return os.path.basename(in_file)

        workflow.connect([
            (sampler, filter_fsavg, [('out_file', 'in_files')]),
            (filter_fsavg, fetch_fslr_tpls, [('hemi', 'hemi')]),
            (filter_fsavg, rename_fslr, [('fsaverage_bold', 'in_file')]),
            (rename_fslr, resample_fslr, [('out_file', 'in_file')]),
            (rename_fslr, resample_fslr, [(('out_file', _basename), 'out_file')
                                          ]),
            (fetch_fslr_tpls, resample_fslr,
             [('fsaverage_sphere', 'current_sphere'),
              ('fslr_sphere', 'new_sphere'),
              ('fsaverage_midthick', 'current_area'),
              ('fslr_midthick', 'new_area')]),
            (sampler, merge_fslr, [('out_file', 'in1')]),
            (resample_fslr, merge_fslr, [('out_file', 'in2')]),
        ])

    merger = pe.JoinNode(niu.Merge(1, ravel_inputs=True),
                         name='merger',
                         joinsource='sampler',
                         joinfield=['in1'],
                         run_without_submitting=True,
                         mem_gb=DEFAULT_MEMORY_MIN_GB)

    if medial_surface_nan:
        medial_nans = pe.MapNode(MedialNaNs(),
                                 iterfield=['in_file'],
                                 name='medial_nans',
                                 mem_gb=DEFAULT_MEMORY_MIN_GB)

        workflow.connect([
            (inputnode, medial_nans, [('subjects_dir', 'subjects_dir')]),
            (medial_nans, merger, [('out_file', 'in1')]),
        ])

    update_metadata = pe.MapNode(GiftiSetAnatomicalStructure(),
                                 iterfield='in_file',
                                 name='update_metadata',
                                 mem_gb=DEFAULT_MEMORY_MIN_GB)

    workflow.connect([
        (inputnode, targets, [('subject_id', 'subject_id')]),
        (inputnode, rename_src, [('source_file', 'in_file')]),
        (inputnode, resampling_xfm, [('source_file', 'source_file'),
                                     ('t1w_preproc', 'target_file')]),
        (inputnode, set_xfm_source, [('t1w2fsnative_xfm', 'in_lta2')]),
        (resampling_xfm, set_xfm_source, [('out_lta', 'in_lta1')]),
        (inputnode, sampler, [('subjects_dir', 'subjects_dir'),
                              ('subject_id', 'subject_id')]),
        (set_xfm_source, sampler, [('out_file', 'reg_file')]),
        (targets, sampler, [('out', 'target_subject')]),
        (rename_src, sampler, [('out_file', 'source_file')]),
        (merger, update_metadata, [('out', 'in_file')]),
        (update_metadata, outputnode, [('out_file', 'surfaces')]),
    ])

    if to_fslr and medial_surface_nan:
        medial_nans.inputs.density = fslr_density
        workflow.connect(merge_fslr, 'out', medial_nans, 'in_file')
    elif to_fslr:
        workflow.connect(merge_fslr, 'out', merger, 'in1')
    elif medial_surface_nan:
        workflow.connect(sampler, 'out_file', medial_nans, 'in_file')
    else:
        workflow.connect(sampler, 'out_file', merger, 'in1')

    return workflow
Example #12
0
def init_t2w_template_wf(longitudinal,
                         omp_nthreads,
                         num_t2w,
                         name="anat_t2w_template_wf"):
    """
    Adapts :py:func:`~smriprep.workflows.anatomical.init_anat_template_wf` for T2w image reference
    """
    from pkg_resources import resource_filename as pkgr
    from nipype.interfaces import freesurfer as fs, image, ants
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.interfaces.freesurfer import (
        StructuralReference,
        PatchedLTAConvert as LTAConvert,
    )
    from niworkflows.interfaces.images import TemplateDimensions, Conform, ValidateImage
    from niworkflows.interfaces.nitransforms import ConcatenateXFMs
    from niworkflows.utils.misc import add_suffix

    wf = Workflow(name=name)

    inputnode = pe.Node(niu.IdentityInterface(fields=["t2w"]),
                        name="inputnode")
    outputnode = pe.Node(
        niu.IdentityInterface(fields=[
            "t2w_ref", "t2w_valid_list", "t2_realign_xfm", "out_report"
        ]),
        name="outputnode",
    )

    # 0. Reorient T2w image(s) to RAS and resample to common voxel space
    t2w_ref_dimensions = pe.Node(TemplateDimensions(),
                                 name='t2w_ref_dimensions')
    t2w_conform = pe.MapNode(Conform(),
                             iterfield='in_file',
                             name='t2w_conform')

    wf.connect([
        (inputnode, t2w_ref_dimensions, [('t2w', 't1w_list')]),
        (t2w_ref_dimensions, t2w_conform, [('t1w_valid_list', 'in_file'),
                                           ('target_zooms', 'target_zooms'),
                                           ('target_shape', 'target_shape')]),
        (t2w_ref_dimensions, outputnode, [('out_report', 'out_report'),
                                          ('t1w_valid_list', 't2w_valid_list')
                                          ]),
    ])

    if num_t2w == 1:
        get1st = pe.Node(niu.Select(index=[0]), name='get1st')
        outputnode.inputs.t2w_realign_xfm = [
            pkgr('smriprep', 'data/itkIdentityTransform.txt')
        ]

        wf.connect([
            (t2w_conform, get1st, [('out_file', 'inlist')]),
            (get1st, outputnode, [('out', 't2w_ref')]),
        ])

        return wf

    wf.__desc__ = f"""\
A T2w-reference map was computed after registration of
{num_t2w} T2w images (after INU-correction) using
`mri_robust_template` [FreeSurfer {fs.Info().looseversion() or "<ver>"}, @fs_template].
"""

    t2w_conform_xfm = pe.MapNode(LTAConvert(in_lta='identity.nofile',
                                            out_lta=True),
                                 iterfield=['source_file', 'target_file'],
                                 name='t2w_conform_xfm')

    # 1a. Correct for bias field: the bias field is an additive factor
    #     in log-transformed intensity units. Therefore, it is not a linear
    #     combination of fields and N4 fails with merged images.
    # 1b. Align and merge if several T1w images are provided
    n4_correct = pe.MapNode(ants.N4BiasFieldCorrection(dimension=3,
                                                       copy_header=True),
                            iterfield='input_image',
                            name='n4_correct',
                            n_procs=1)  # n_procs=1 for reproducibility

    # StructuralReference is fs.RobustTemplate if > 1 volume, copying otherwise
    t2w_merge = pe.Node(
        StructuralReference(
            auto_detect_sensitivity=True,
            initial_timepoint=1,  # For deterministic behavior
            intensity_scaling=True,  # 7-DOF (rigid + intensity)
            subsample_threshold=200,
            fixed_timepoint=not longitudinal,
            no_iteration=not longitudinal,
            transform_outputs=True,
        ),
        mem_gb=2 * num_t2w - 1,
        name='t2w_merge')

    # 2. Reorient template to RAS, if needed (mri_robust_template may set to LIA)
    t2w_reorient = pe.Node(image.Reorient(), name='t2w_reorient')

    merge_xfm = pe.MapNode(niu.Merge(2),
                           name='merge_xfm',
                           iterfield=['in1', 'in2'],
                           run_without_submitting=True)
    concat_xfms = pe.MapNode(ConcatenateXFMs(inverse=True),
                             name="concat_xfms",
                             iterfield=['in_xfms'],
                             run_without_submitting=True)

    def _set_threads(in_list, maximum):
        return min(len(in_list), maximum)

    wf.connect([
        (t2w_ref_dimensions, t2w_conform_xfm, [('t1w_valid_list',
                                                'source_file')]),
        (t2w_conform, t2w_conform_xfm, [('out_file', 'target_file')]),
        (t2w_conform, n4_correct, [('out_file', 'input_image')]),
        (t2w_conform, t2w_merge,
         [(('out_file', _set_threads, omp_nthreads), 'num_threads'),
          (('out_file', add_suffix, '_template'), 'out_file')]),
        (n4_correct, t2w_merge, [('output_image', 'in_files')]),
        (t2w_merge, t2w_reorient, [('out_file', 'in_file')]),
        # Combine orientation and template transforms
        (t2w_conform_xfm, merge_xfm, [('out_lta', 'in1')]),
        (t2w_merge, merge_xfm, [('transform_outputs', 'in2')]),
        (merge_xfm, concat_xfms, [('out', 'in_xfms')]),
        # Output
        (t2w_reorient, outputnode, [('out_file', 't2w_ref')]),
        (concat_xfms, outputnode, [('out_xfm', 't2w_realign_xfm')]),
    ])

    return wf
Example #13
0
def init_anat_derivatives_wf(bids_root,
                             freesurfer,
                             output_dir,
                             template,
                             name='anat_derivatives_wf'):
    """
    Set up a battery of datasinks to store derivatives in the right location
    """
    workflow = Workflow(name=name)

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'source_files', 't1_template_transforms', 't1_preproc', 't1_mask',
        't1_seg', 't1_tpms', 't1_2_mni_forward_transform',
        't1_2_mni_reverse_transform', 't1_2_mni', 'mni_mask', 'mni_seg',
        'mni_tpms', 't1_2_fsnative_forward_transform', 'surfaces',
        't1_fs_aseg', 't1_fs_aparc'
    ]),
                        name='inputnode')

    t1_name = pe.Node(niu.Function(function=fix_multi_T1w_source_name),
                      name='t1_name')
    raw_sources = pe.Node(niu.Function(function=_bids_relative),
                          name='raw_sources')
    raw_sources.inputs.bids_root = bids_root

    ds_t1_preproc = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                                desc='preproc',
                                                keep_dtype=True),
                            name='ds_t1_preproc',
                            run_without_submitting=True)
    ds_t1_preproc.inputs.SkullStripped = False

    ds_t1_mask = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                             desc='brain',
                                             suffix='mask'),
                         name='ds_t1_mask',
                         run_without_submitting=True)
    ds_t1_mask.inputs.Type = 'Brain'

    lut_t1_seg = pe.Node(niu.Function(function=_apply_default_bids_lut),
                         name='lut_t1_seg')
    ds_t1_seg = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                            suffix='dseg'),
                        name='ds_t1_seg',
                        run_without_submitting=True)

    ds_t1_tpms = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                             suffix='probseg'),
                         name='ds_t1_tpms',
                         run_without_submitting=True)
    ds_t1_tpms.inputs.extra_values = ['label-CSF', 'label-GM', 'label-WM']

    ds_t1_mni = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                            space=template,
                                            desc='preproc',
                                            keep_dtype=True),
                        name='ds_t1_mni',
                        run_without_submitting=True)
    ds_t1_mni.inputs.SkullStripped = True

    ds_mni_mask = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                              space=template,
                                              desc='brain',
                                              suffix='mask'),
                          name='ds_mni_mask',
                          run_without_submitting=True)
    ds_mni_mask.inputs.Type = 'Brain'
    ds_mni_mask.inputs.RawSources = 'tpl-{0}/tpl-{0}_res-01_desc-brain_mask.nii.gz'.format(
        template)

    lut_mni_seg = pe.Node(niu.Function(function=_apply_default_bids_lut),
                          name='lut_mni_seg')
    ds_mni_seg = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                             space=template,
                                             suffix='dseg'),
                         name='ds_mni_seg',
                         run_without_submitting=True)

    ds_mni_tpms = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                              space=template,
                                              suffix='probseg'),
                          name='ds_mni_tpms',
                          run_without_submitting=True)
    ds_mni_tpms.inputs.extra_values = ['label-CSF', 'label-GM', 'label-WM']

    # Transforms
    suffix_fmt = 'from-{}_to-{}_mode-image_xfm'.format
    ds_t1_mni_inv_warp = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                                     suffix=suffix_fmt(
                                                         template, 'T1w')),
                                 name='ds_t1_mni_inv_warp',
                                 run_without_submitting=True)

    ds_t1_template_transforms = pe.MapNode(
        DerivativesDataSink(base_directory=output_dir,
                            suffix=suffix_fmt('orig', 'T1w')),
        iterfield=['source_file', 'in_file'],
        name='ds_t1_template_transforms',
        run_without_submitting=True)

    ds_t1_mni_warp = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                                 suffix=suffix_fmt(
                                                     'T1w', template)),
                             name='ds_t1_mni_warp',
                             run_without_submitting=True)

    lta_2_itk = pe.Node(LTAConvert(out_itk=True), name='lta_2_itk')

    ds_t1_fsnative = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                                 suffix=suffix_fmt(
                                                     'T1w', 'fsnative')),
                             name='ds_t1_fsnative',
                             run_without_submitting=True)

    name_surfs = pe.MapNode(GiftiNameSource(
        pattern=r'(?P<LR>[lr])h.(?P<surf>.+)_converted.gii',
        template='hemi-{LR}_{surf}.surf'),
                            iterfield='in_file',
                            name='name_surfs',
                            run_without_submitting=True)

    ds_surfs = pe.MapNode(DerivativesDataSink(base_directory=output_dir),
                          iterfield=['in_file', 'suffix'],
                          name='ds_surfs',
                          run_without_submitting=True)

    workflow.connect([
        (inputnode, t1_name, [('source_files', 'in_files')]),
        (inputnode, raw_sources, [('source_files', 'in_files')]),
        (inputnode,
         ds_t1_template_transforms, [('source_files', 'source_file'),
                                     ('t1_template_transforms', 'in_file')]),
        (inputnode, ds_t1_preproc, [('t1_preproc', 'in_file')]),
        (inputnode, ds_t1_mask, [('t1_mask', 'in_file')]),
        (inputnode, lut_t1_seg, [('t1_seg', 'in_file')]),
        (inputnode, ds_t1_tpms, [('t1_tpms', 'in_file')]),
        (lut_t1_seg, ds_t1_seg, [('out', 'in_file')]),
        (t1_name, ds_t1_preproc, [('out', 'source_file')]),
        (t1_name, ds_t1_mask, [('out', 'source_file')]),
        (t1_name, ds_t1_seg, [('out', 'source_file')]),
        (t1_name, ds_t1_tpms, [('out', 'source_file')]),
        (raw_sources, ds_t1_mask, [('out', 'RawSources')]),
        # Template
        (inputnode, ds_t1_mni_warp, [('t1_2_mni_forward_transform', 'in_file')]
         ),
        (inputnode, ds_t1_mni_inv_warp, [('t1_2_mni_reverse_transform',
                                          'in_file')]),
        (inputnode, ds_t1_mni, [('t1_2_mni', 'in_file')]),
        (inputnode, ds_mni_mask, [('mni_mask', 'in_file')]),
        (inputnode, lut_mni_seg, [('mni_seg', 'in_file')]),
        (lut_mni_seg, ds_mni_seg, [('out', 'in_file')]),
        (inputnode, ds_mni_tpms, [('mni_tpms', 'in_file')]),
        (t1_name, ds_t1_mni_warp, [('out', 'source_file')]),
        (t1_name, ds_t1_mni_inv_warp, [('out', 'source_file')]),
        (t1_name, ds_t1_mni, [('out', 'source_file')]),
        (t1_name, ds_mni_mask, [('out', 'source_file')]),
        (t1_name, ds_mni_seg, [('out', 'source_file')]),
        (t1_name, ds_mni_tpms, [('out', 'source_file')]),
    ])

    if freesurfer:
        ds_t1_fsaseg = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                                   desc='aseg',
                                                   suffix='dseg'),
                               name='ds_t1_fsaseg',
                               run_without_submitting=True)
        ds_t1_fsparc = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                                   desc='aparcaseg',
                                                   suffix='dseg'),
                               name='ds_t1_fsparc',
                               run_without_submitting=True)
        ds_t1_fsparc = pe.Node(DerivativesDataSink(base_directory=output_dir,
                                                   desc='aparcaseg',
                                                   suffix='dseg'),
                               name='ds_t1_fsparc',
                               run_without_submitting=True)

        workflow.connect([
            (inputnode, lta_2_itk, [('t1_2_fsnative_forward_transform',
                                     'in_lta')]),
            (t1_name, ds_t1_fsnative, [('out', 'source_file')]),
            (lta_2_itk, ds_t1_fsnative, [('out_itk', 'in_file')]),
            (inputnode, name_surfs, [('surfaces', 'in_file')]),
            (inputnode, ds_surfs, [('surfaces', 'in_file')]),
            (t1_name, ds_surfs, [('out', 'source_file')]),
            (name_surfs, ds_surfs, [('out_name', 'suffix')]),
            (inputnode, ds_t1_fsaseg, [('t1_fs_aseg', 'in_file')]),
            (inputnode, ds_t1_fsparc, [('t1_fs_aparc', 'in_file')]),
            (t1_name, ds_t1_fsaseg, [('out', 'source_file')]),
            (t1_name, ds_t1_fsparc, [('out', 'source_file')]),
        ])

    return workflow