Esempio n. 1
0
def init_fmriprep_adapter_wf(
    name: str = "fmriprep_adapter_wf",
    memcalc: MemoryCalculator = MemoryCalculator.default(),
):
    workflow = pe.Workflow(name=name)

    inputnode = pe.Node(
        niu.IdentityInterface(
            fields=[
                "bold_std",
                "bold_mask_std",
                "spatial_reference",
                "confounds",
                "vals",
            ]
        ),
        name="inputnode",
    )
    outputnode = pe.Node(
        niu.IdentityInterface(fields=["files", "mask", "vals"]),
        name="outputnode",
    )

    select_std = pe.Node(
        KeySelect(fields=["bold_std", "bold_mask_std"]),
        name="select_std",
        run_without_submitting=True,
        nohash=True,
    )
    select_std.inputs.key = f"{constants.reference_space}_res-{constants.reference_res}"
    workflow.connect(inputnode, "bold_std", select_std, "bold_std")
    workflow.connect(inputnode, "bold_mask_std", select_std, "bold_mask_std")
    workflow.connect(inputnode, "spatial_reference", select_std, "keys")

    #
    applymask = pe.Node(
        interface=fsl.ApplyMask(),
        name="applymask",
        mem_gb=memcalc.series_std_gb,
    )
    workflow.connect(select_std, "bold_std", applymask, "in_file")
    workflow.connect(select_std, "bold_mask_std", applymask, "mask_file")

    #
    merge = pe.Node(niu.Merge(2), name="merge")
    workflow.connect(applymask, "out_file", merge, "in1")
    workflow.connect(inputnode, "confounds", merge, "in2")

    #
    workflow.connect(merge, "out", outputnode, "files")
    workflow.connect(select_std, "bold_mask_std", outputnode, "mask")
    workflow.connect(inputnode, "vals", outputnode, "vals")

    return workflow
Esempio n. 2
0
    def __call__(self,
                 parentworkflow,
                 out_wf,
                 in_wf,
                 out_nodename="outputnode",
                 in_nodename="inputnode"):
        if out_nodename is None:
            out_nodename = ""
        else:
            out_nodename = f"{out_nodename}."
        if in_nodename is None:
            in_nodename = ""
        else:
            in_nodename = f"{in_nodename}."
        parentworkflow.connect([(
            out_wf,
            in_wf,
            [(f"{out_nodename}{attr}", f"{in_nodename}{attr}")
             for attr in self._attrs],
        )])
        if self._keyAttr is not None:
            assert self._keyVal is not None
            assert self._keySelectAttrs is not None and len(
                self._keySelectAttrs) > 0

            hexval = hexdigest(
                (self._keyAttr, self._keyVal, self._keySelectAttrs))
            selectnodename = f"keyselect_from_{out_wf.name}_{hexval}"
            select = parentworkflow.get_node(selectnodename)
            if select is None:
                select = pe.Node(
                    interface=KeySelect(fields=self._keySelectAttrs,
                                        key=self._keyVal),
                    name=selectnodename,
                    run_without_submitting=True,
                )
                parentworkflow.connect(out_wf, self._keyAttr, select, "keys")
                parentworkflow.connect([
                    (
                        out_wf,
                        select,
                        [(f"{out_nodename}{attr}", attr)
                         for attr in self._keySelectAttrs],
                    ),
                ])
            parentworkflow.connect([
                (
                    select,
                    in_wf,
                    [(attr, f"{in_nodename}{attr}")
                     for attr in self._keySelectAttrs],
                ),
            ])
Esempio n. 3
0
def init_func_preproc_wf(
    aroma_melodic_dim,
    bold2t1w_dof,
    bold_file,
    cifti_output,
    debug,
    dummy_scans,
    err_on_aroma_warn,
    fmap_bspline,
    fmap_demean,
    force_syn,
    freesurfer,
    ignore,
    low_mem,
    medial_surface_nan,
    omp_nthreads,
    output_dir,
    output_spaces,
    regressors_all_comps,
    regressors_dvars_th,
    regressors_fd_th,
    reportlets_dir,
    t2s_coreg,
    use_aroma,
    use_bbr,
    use_syn,
    layout=None,
    num_bold=1,
):
    """
    This workflow controls the functional preprocessing stages of FMRIPREP.

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

        from fmriprep.workflows.bold import init_func_preproc_wf
        from collections import namedtuple, OrderedDict
        BIDSLayout = namedtuple('BIDSLayout', ['root'])
        wf = init_func_preproc_wf(
            aroma_melodic_dim=-200,
            bold2t1w_dof=9,
            bold_file='/completely/made/up/path/sub-01_task-nback_bold.nii.gz',
            cifti_output=False,
            debug=False,
            dummy_scans=None,
            err_on_aroma_warn=False,
            fmap_bspline=True,
            fmap_demean=True,
            force_syn=True,
            freesurfer=True,
            ignore=[],
            low_mem=False,
            medial_surface_nan=False,
            omp_nthreads=1,
            output_dir='.',
            output_spaces=OrderedDict([
                ('MNI152Lin', {}), ('fsaverage', {'density': '10k'}),
                ('T1w', {}), ('fsnative', {})]),
            regressors_all_comps=False,
            regressors_dvars_th=1.5,
            regressors_fd_th=0.5,
            reportlets_dir='.',
            t2s_coreg=False,
            use_aroma=False,
            use_bbr=True,
            use_syn=True,
            layout=BIDSLayout('.'),
            num_bold=1,
        )

    **Parameters**

        aroma_melodic_dim : int
            Maximum number of components identified by MELODIC within ICA-AROMA
            (default is -200, ie. no limitation).
        bold2t1w_dof : 6, 9 or 12
            Degrees-of-freedom for BOLD-T1w registration
        bold_file : str
            BOLD series NIfTI file
        cifti_output : bool
            Generate bold CIFTI file in output spaces
        debug : bool
            Enable debugging outputs
        dummy_scans : int or None
            Number of volumes to consider as non steady state
        err_on_aroma_warn : bool
            Do not crash on ICA-AROMA errors
        fmap_bspline : bool
            **Experimental**: Fit B-Spline field using least-squares
        fmap_demean : bool
            Demean voxel-shift map during unwarp
        force_syn : bool
            **Temporary**: Always run SyN-based SDC
        freesurfer : bool
            Enable FreeSurfer functional registration (bbregister) and resampling
            BOLD series to FreeSurfer surface meshes.
        ignore : list
            Preprocessing steps to skip (may include "slicetiming", "fieldmaps")
        low_mem : bool
            Write uncompressed .nii files in some cases to reduce memory usage
        medial_surface_nan : bool
            Replace medial wall values with NaNs on functional GIFTI files
        omp_nthreads : int
            Maximum number of threads an individual process may use
        output_dir : str
            Directory in which to save derivatives
        output_spaces : OrderedDict
            Ordered dictionary where keys are TemplateFlow ID strings (e.g. ``MNI152Lin``,
            ``MNI152NLin6Asym``, ``MNI152NLin2009cAsym``, or ``fsLR``) strings designating
            nonstandard references (e.g. ``T1w`` or ``anat``, ``sbref``, ``run``, etc.),
            or paths pointing to custom templates organized in a TemplateFlow-like structure.
            Values of the dictionary aggregate modifiers (e.g. the value for the key ``MNI152Lin``
            could be ``{'resolution': 2}`` if one wants the resampling to be done on the 2mm
            resolution version of the selected template).
        regressors_all_comps
            Return all CompCor component time series instead of the top fraction
        regressors_dvars_th
            Criterion for flagging DVARS outliers
        regressors_fd_th
            Criterion for flagging framewise displacement outliers
        reportlets_dir : str
            Absolute path of a directory in which reportlets will be temporarily stored
        t2s_coreg : bool
            For multiecho EPI, use the calculated T2*-map for T2*-driven coregistration
        use_aroma : bool
            Perform ICA-AROMA on MNI-resampled functional series
        use_bbr : bool or None
            Enable/disable boundary-based registration refinement.
            If ``None``, test BBR result for distortion before accepting.
            When using ``t2s_coreg``, BBR will be enabled by default unless
            explicitly specified otherwise.
        use_syn : bool
            **Experimental**: Enable ANTs SyN-based susceptibility distortion correction (SDC).
            If fieldmaps are present and enabled, this is not run, by default.
        layout : BIDSLayout
            BIDSLayout structure to enable metadata retrieval
        num_bold : int
            Total number of BOLD files that have been set for preprocessing
            (default is 1)

    **Inputs**

        bold_file
            BOLD series NIfTI file
        t1_preproc
            Bias-corrected structural template image
        t1_brain
            Skull-stripped ``t1_preproc``
        t1_mask
            Mask of the skull-stripped template image
        t1_seg
            Segmentation of preprocessed structural image, including
            gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF)
        t1_tpms
            List of tissue probability maps in T1w space
        anat2std_xfm
            ANTs-compatible affine-and-warp transform file
        std2anat_xfm
            ANTs-compatible affine-and-warp transform file (inverse)
        subjects_dir
            FreeSurfer SUBJECTS_DIR
        subject_id
            FreeSurfer subject ID
        t1_2_fsnative_forward_transform
            LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space
        t1_2_fsnative_reverse_transform
            LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w


    **Outputs**

        bold_t1
            BOLD series, resampled to T1w space
        bold_mask_t1
            BOLD series mask in T1w space
        bold_std
            BOLD series, resampled to template space
        bold_mask_std
            BOLD series mask in template space
        confounds
            TSV of confounds
        surfaces
            BOLD series, resampled to FreeSurfer surfaces
        aroma_noise_ics
            Noise components identified by ICA-AROMA
        melodic_mix
            FSL MELODIC mixing matrix
        bold_cifti
            BOLD CIFTI image
        cifti_variant
            combination of target spaces for `bold_cifti`


    **Subworkflows**

        * :py:func:`~fmriprep.workflows.bold.util.init_bold_reference_wf`
        * :py:func:`~fmriprep.workflows.bold.stc.init_bold_stc_wf`
        * :py:func:`~fmriprep.workflows.bold.hmc.init_bold_hmc_wf`
        * :py:func:`~fmriprep.workflows.bold.t2s.init_bold_t2s_wf`
        * :py:func:`~fmriprep.workflows.bold.registration.init_bold_t1_trans_wf`
        * :py:func:`~fmriprep.workflows.bold.registration.init_bold_reg_wf`
        * :py:func:`~fmriprep.workflows.bold.confounds.init_bold_confounds_wf`
        * :py:func:`~fmriprep.workflows.bold.confounds.init_ica_aroma_wf`
        * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_std_trans_wf`
        * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_preproc_trans_wf`
        * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_surf_wf`
        * :py:func:`~fmriprep.workflows.fieldmap.pepolar.init_pepolar_unwarp_wf`
        * :py:func:`~fmriprep.workflows.fieldmap.init_fmap_estimator_wf`
        * :py:func:`~fmriprep.workflows.fieldmap.init_sdc_unwarp_wf`
        * :py:func:`~fmriprep.workflows.fieldmap.init_nonlinear_sdc_wf`

    """
    from .resampling import NONSTANDARD_REFERENCES
    from ..fieldmap.base import init_sdc_wf  # Avoid circular dependency (#1066)

    # Filter out standard spaces to a separate dict
    std_spaces = OrderedDict([(key, modifiers)
                              for key, modifiers in output_spaces.items()
                              if key not in NONSTANDARD_REFERENCES])
    volume_std_spaces = OrderedDict([(key, modifiers)
                                     for key, modifiers in std_spaces.items()
                                     if not key.startswith('fs')])

    ref_file = bold_file
    mem_gb = {'filesize': 1, 'resampled': 1, 'largemem': 1}
    bold_tlen = 10
    multiecho = isinstance(bold_file, list)

    if multiecho:
        tes = [layout.get_metadata(echo)['EchoTime'] for echo in bold_file]
        ref_file = dict(zip(tes, bold_file))[min(tes)]

    if os.path.isfile(ref_file):
        bold_tlen, mem_gb = _create_mem_gb(ref_file)

    wf_name = _get_wf_name(ref_file)
    LOGGER.log(
        25, ('Creating bold processing workflow for "%s" (%.2f GB / %d TRs). '
             'Memory resampled/largemem=%.2f/%.2f GB.'), ref_file,
        mem_gb['filesize'], bold_tlen, mem_gb['resampled'], mem_gb['largemem'])

    sbref_file = None
    # For doc building purposes
    if not hasattr(layout, 'parse_file_entities'):
        LOGGER.log(25, 'No valid layout: building empty workflow.')
        metadata = {
            'RepetitionTime': 2.0,
            'SliceTiming': [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
            'PhaseEncodingDirection': 'j',
        }
        fmaps = [{
            'suffix':
            'phasediff',
            'phasediff':
            'sub-03/ses-2/fmap/sub-03_ses-2_run-1_phasediff.nii.gz',
            'magnitude1':
            'sub-03/ses-2/fmap/sub-03_ses-2_run-1_magnitude1.nii.gz',
            'magnitude2':
            'sub-03/ses-2/fmap/sub-03_ses-2_run-1_magnitude2.nii.gz',
        }]
        run_stc = True
        multiecho = False
    else:
        # Find associated sbref, if possible
        entities = layout.parse_file_entities(ref_file)
        entities['suffix'] = 'sbref'
        entities['extension'] = ['nii', 'nii.gz']  # Overwrite extensions
        files = layout.get(return_type='file', **entities)
        refbase = os.path.basename(ref_file)
        if 'sbref' in ignore:
            LOGGER.info("Single-band reference files ignored.")
        elif files and multiecho:
            LOGGER.warning("Single-band reference found, but not supported in "
                           "multi-echo workflows at this time. Ignoring.")
        elif files:
            sbref_file = files[0]
            sbbase = os.path.basename(sbref_file)
            if len(files) > 1:
                LOGGER.warning(
                    "Multiple single-band reference files found for {}; using "
                    "{}".format(refbase, sbbase))
            else:
                LOGGER.log(
                    25, "Using single-band reference file {}".format(sbbase))
        else:
            LOGGER.log(25,
                       "No single-band-reference found for {}".format(refbase))

        metadata = layout.get_metadata(ref_file)

        # Find fieldmaps. Options: (phase1|phase2|phasediff|epi|fieldmap|syn)
        fmaps = []
        if 'fieldmaps' not in ignore:
            for fmap in layout.get_fieldmap(ref_file, return_list=True):
                if fmap['suffix'] == 'phase':
                    LOGGER.warning("""\
Found phase1/2 type of fieldmaps, which are not currently supported. \
fMRIPrep will discard them for susceptibility distortion correction. \
Please, follow up on this issue at \
https://github.com/poldracklab/fmriprep/issues/1655.""")
                else:
                    fmap['metadata'] = layout.get_metadata(
                        fmap[fmap['suffix']])
                    fmaps.append(fmap)

        # Run SyN if forced or in the absence of fieldmap correction
        if force_syn or (use_syn and not fmaps):
            fmaps.append({'suffix': 'syn'})

        # Short circuits: (True and True and (False or 'TooShort')) == 'TooShort'
        run_stc = ("SliceTiming" in metadata and 'slicetiming' not in ignore
                   and (_get_series_len(ref_file) > 4 or "TooShort"))

    # Check if MEEPI for T2* coregistration target
    if t2s_coreg and not multiecho:
        LOGGER.warning(
            "No multiecho BOLD images found for T2* coregistration. "
            "Using standard EPI-T1 coregistration.")
        t2s_coreg = False

    # By default, force-bbr for t2s_coreg unless user specifies otherwise
    if t2s_coreg and use_bbr is None:
        use_bbr = True

    # Build workflow
    workflow = Workflow(name=wf_name)
    workflow.__desc__ = """

Functional data preprocessing

: For each of the {num_bold} BOLD runs found per subject (across all
tasks and sessions), the following preprocessing was performed.
""".format(num_bold=num_bold)

    workflow.__postdesc__ = """\
All resamplings can be performed with *a single interpolation
step* by composing all the pertinent transformations (i.e. head-motion
transform matrices, susceptibility distortion correction when available,
and co-registrations to anatomical and output spaces).
Gridded (volumetric) resamplings were performed using `antsApplyTransforms` (ANTs),
configured with Lanczos interpolation to minimize the smoothing
effects of other kernels [@lanczos].
Non-gridded (surface) resamplings were performed using `mri_vol2surf`
(FreeSurfer).
"""

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'bold_file', 'subjects_dir', 'subject_id', 't1_preproc', 't1_brain',
        't1_mask', 't1_seg', 't1_tpms', 't1_aseg', 't1_aparc', 'anat2std_xfm',
        'std2anat_xfm', 'template', 'joint_anat2std_xfm', 'joint_std2anat_xfm',
        'joint_template', 't1_2_fsnative_forward_transform',
        't1_2_fsnative_reverse_transform'
    ]),
                        name='inputnode')
    inputnode.inputs.bold_file = bold_file
    if sbref_file is not None:
        from niworkflows.interfaces.images import ValidateImage
        val_sbref = pe.Node(ValidateImage(in_file=sbref_file),
                            name='val_sbref')

    outputnode = pe.Node(niu.IdentityInterface(fields=[
        'bold_t1', 'bold_t1_ref', 'bold_mask_t1', 'bold_aseg_t1',
        'bold_aparc_t1', 'bold_std', 'bold_std_ref'
        'bold_mask_std', 'bold_aseg_std', 'bold_aparc_std', 'bold_native',
        'bold_cifti', 'cifti_variant', 'cifti_variant_key', 'surfaces',
        'confounds', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file',
        'confounds_metadata'
    ]),
                         name='outputnode')

    # BOLD buffer: an identity used as a pointer to either the original BOLD
    # or the STC'ed one for further use.
    boldbuffer = pe.Node(niu.IdentityInterface(fields=['bold_file']),
                         name='boldbuffer')

    summary = pe.Node(FunctionalSummary(
        slice_timing=run_stc,
        registration=('FSL', 'FreeSurfer')[freesurfer],
        registration_dof=bold2t1w_dof,
        pe_direction=metadata.get("PhaseEncodingDirection"),
        tr=metadata.get("RepetitionTime")),
                      name='summary',
                      mem_gb=DEFAULT_MEMORY_MIN_GB,
                      run_without_submitting=True)
    summary.inputs.dummy_scans = dummy_scans

    # CIfTI output: currently, we only support fsaverage{5,6}
    cifti_spaces = set(s for s in output_spaces.keys()
                       if s in ('fsaverage5', 'fsaverage6'))
    fsaverage_den = output_spaces.get('fsaverage', {}).get('den')
    if fsaverage_den:
        cifti_spaces.add(FSAVERAGE_DENSITY[fsaverage_den])
    cifti_output = cifti_output and cifti_spaces
    func_derivatives_wf = init_func_derivatives_wf(
        bids_root=layout.root,
        cifti_output=cifti_output,
        freesurfer=freesurfer,
        metadata=metadata,
        output_dir=output_dir,
        output_spaces=output_spaces,
        standard_spaces=list(std_spaces.keys()),
        use_aroma=use_aroma,
    )

    workflow.connect([
        (outputnode, func_derivatives_wf, [
            ('bold_t1', 'inputnode.bold_t1'),
            ('bold_t1_ref', 'inputnode.bold_t1_ref'),
            ('bold_aseg_t1', 'inputnode.bold_aseg_t1'),
            ('bold_aparc_t1', 'inputnode.bold_aparc_t1'),
            ('bold_mask_t1', 'inputnode.bold_mask_t1'),
            ('bold_native', 'inputnode.bold_native'),
            ('confounds', 'inputnode.confounds'),
            ('surfaces', 'inputnode.surfaces'),
            ('aroma_noise_ics', 'inputnode.aroma_noise_ics'),
            ('melodic_mix', 'inputnode.melodic_mix'),
            ('nonaggr_denoised_file', 'inputnode.nonaggr_denoised_file'),
            ('bold_cifti', 'inputnode.bold_cifti'),
            ('cifti_variant', 'inputnode.cifti_variant'),
            ('cifti_variant_key', 'inputnode.cifti_variant_key'),
            ('confounds_metadata', 'inputnode.confounds_metadata'),
        ]),
    ])

    # Generate a tentative boldref
    bold_reference_wf = init_bold_reference_wf(omp_nthreads=omp_nthreads)
    bold_reference_wf.inputs.inputnode.dummy_scans = dummy_scans
    if sbref_file is not None:
        workflow.connect([
            (val_sbref, bold_reference_wf, [('out_file',
                                             'inputnode.sbref_file')]),
        ])

    # Top-level BOLD splitter
    bold_split = pe.Node(FSLSplit(dimension='t'),
                         name='bold_split',
                         mem_gb=mem_gb['filesize'] * 3)

    # HMC on the BOLD
    bold_hmc_wf = init_bold_hmc_wf(name='bold_hmc_wf',
                                   mem_gb=mem_gb['filesize'],
                                   omp_nthreads=omp_nthreads)

    # calculate BOLD registration to T1w
    bold_reg_wf = init_bold_reg_wf(name='bold_reg_wf',
                                   freesurfer=freesurfer,
                                   use_bbr=use_bbr,
                                   bold2t1w_dof=bold2t1w_dof,
                                   mem_gb=mem_gb['resampled'],
                                   omp_nthreads=omp_nthreads,
                                   use_compression=False)

    # apply BOLD registration to T1w
    bold_t1_trans_wf = init_bold_t1_trans_wf(name='bold_t1_trans_wf',
                                             freesurfer=freesurfer,
                                             use_fieldwarp=(fmaps is not None
                                                            or use_syn),
                                             multiecho=multiecho,
                                             mem_gb=mem_gb['resampled'],
                                             omp_nthreads=omp_nthreads,
                                             use_compression=False)

    # get confounds
    bold_confounds_wf = init_bold_confs_wf(
        mem_gb=mem_gb['largemem'],
        metadata=metadata,
        regressors_all_comps=regressors_all_comps,
        regressors_fd_th=regressors_fd_th,
        regressors_dvars_th=regressors_dvars_th,
        name='bold_confounds_wf')
    bold_confounds_wf.get_node('inputnode').inputs.t1_transform_flags = [False]

    # Apply transforms in 1 shot
    # Only use uncompressed output if AROMA is to be run
    bold_bold_trans_wf = init_bold_preproc_trans_wf(
        mem_gb=mem_gb['resampled'],
        omp_nthreads=omp_nthreads,
        use_compression=not low_mem,
        use_fieldwarp=(fmaps is not None or use_syn),
        name='bold_bold_trans_wf')
    bold_bold_trans_wf.inputs.inputnode.name_source = ref_file

    # SLICE-TIME CORRECTION (or bypass) #############################################
    if run_stc is True:  # bool('TooShort') == True, so check True explicitly
        bold_stc_wf = init_bold_stc_wf(name='bold_stc_wf', metadata=metadata)
        workflow.connect([
            (bold_reference_wf, bold_stc_wf, [('outputnode.skip_vols',
                                               'inputnode.skip_vols')]),
            (bold_stc_wf, boldbuffer, [('outputnode.stc_file', 'bold_file')]),
        ])
        if not multiecho:
            workflow.connect([(bold_reference_wf, bold_stc_wf, [
                ('outputnode.bold_file', 'inputnode.bold_file')
            ])])
        else:  # for meepi, iterate through stc_wf for all workflows
            meepi_echos = boldbuffer.clone(name='meepi_echos')
            meepi_echos.iterables = ('bold_file', bold_file)
            workflow.connect([(meepi_echos, bold_stc_wf,
                               [('bold_file', 'inputnode.bold_file')])])
    elif not multiecho:  # STC is too short or False
        # bypass STC from original BOLD to the splitter through boldbuffer
        workflow.connect([(bold_reference_wf, boldbuffer,
                           [('outputnode.bold_file', 'bold_file')])])
    else:
        # for meepi, iterate over all meepi echos to boldbuffer
        boldbuffer.iterables = ('bold_file', bold_file)

    # SDC (SUSCEPTIBILITY DISTORTION CORRECTION) or bypass ##########################
    bold_sdc_wf = init_sdc_wf(fmaps,
                              metadata,
                              omp_nthreads=omp_nthreads,
                              debug=debug,
                              fmap_demean=fmap_demean,
                              fmap_bspline=fmap_bspline)
    # If no standard space is given, use the default for SyN-SDC
    if not volume_std_spaces or 'MNI152NLin2009cAsym' in volume_std_spaces:
        bold_sdc_wf.inputs.inputnode.template = 'MNI152NLin2009cAsym'
    else:
        bold_sdc_wf.inputs.inputnode.template = next(iter(volume_std_spaces))

    if not fmaps:
        LOGGER.warning('SDC: no fieldmaps found or they were ignored (%s).',
                       ref_file)
    elif fmaps[0]['suffix'] == 'syn':
        LOGGER.warning(
            'SDC: no fieldmaps found or they were ignored. '
            'Using EXPERIMENTAL "fieldmap-less SyN" correction '
            'for dataset %s.', ref_file)
    else:
        LOGGER.log(
            25, 'SDC: fieldmap estimation of type "%s" intended for %s found.',
            fmaps[0]['suffix'], ref_file)

    # Overwrite ``out_path_base`` of sdcflows' DataSinks
    for node in bold_sdc_wf.list_node_names():
        if node.split('.')[-1].startswith('ds_'):
            bold_sdc_wf.get_node(node).interface.out_path_base = 'fmriprep'

    # MULTI-ECHO EPI DATA #############################################
    if multiecho:
        from .util import init_skullstrip_bold_wf
        skullstrip_bold_wf = init_skullstrip_bold_wf(name='skullstrip_bold_wf')

        inputnode.inputs.bold_file = ref_file  # Replace reference w first echo

        join_echos = pe.JoinNode(
            niu.IdentityInterface(fields=['bold_files']),
            joinsource=('meepi_echos' if run_stc is True else 'boldbuffer'),
            joinfield=['bold_files'],
            name='join_echos')

        # create optimal combination, adaptive T2* map
        bold_t2s_wf = init_bold_t2s_wf(echo_times=tes,
                                       mem_gb=mem_gb['resampled'],
                                       omp_nthreads=omp_nthreads,
                                       t2s_coreg=t2s_coreg,
                                       name='bold_t2smap_wf')

        workflow.connect([
            (skullstrip_bold_wf, join_echos,
             [('outputnode.skull_stripped_file', 'bold_files')]),
            (join_echos, bold_t2s_wf, [('bold_files', 'inputnode.bold_file')]),
        ])

    # MAIN WORKFLOW STRUCTURE #######################################################
    workflow.connect([
        # Generate early reference
        (inputnode, bold_reference_wf, [('bold_file', 'inputnode.bold_file')]),
        # BOLD buffer has slice-time corrected if it was run, original otherwise
        (boldbuffer, bold_split, [('bold_file', 'in_file')]),
        # HMC
        (bold_reference_wf, bold_hmc_wf,
         [('outputnode.raw_ref_image', 'inputnode.raw_ref_image'),
          ('outputnode.bold_file', 'inputnode.bold_file')]),
        (bold_reference_wf, summary, [('outputnode.algo_dummy_scans',
                                       'algo_dummy_scans')]),
        # EPI-T1 registration workflow
        (
            inputnode,
            bold_reg_wf,
            [
                ('t1_brain', 'inputnode.t1_brain'),
                ('t1_seg', 'inputnode.t1_seg'),
                # Undefined if --no-freesurfer, but this is safe
                ('subjects_dir', 'inputnode.subjects_dir'),
                ('subject_id', 'inputnode.subject_id'),
                ('t1_2_fsnative_reverse_transform',
                 'inputnode.t1_2_fsnative_reverse_transform')
            ]),
        (inputnode, bold_t1_trans_wf, [('bold_file', 'inputnode.name_source'),
                                       ('t1_brain', 'inputnode.t1_brain'),
                                       ('t1_mask', 'inputnode.t1_mask'),
                                       ('t1_aseg', 'inputnode.t1_aseg'),
                                       ('t1_aparc', 'inputnode.t1_aparc')]),
        # unused if multiecho, but this is safe
        (bold_hmc_wf, bold_t1_trans_wf, [('outputnode.xforms',
                                          'inputnode.hmc_xforms')]),
        (bold_reg_wf, bold_t1_trans_wf, [('outputnode.itk_bold_to_t1',
                                          'inputnode.itk_bold_to_t1')]),
        (bold_t1_trans_wf, outputnode,
         [('outputnode.bold_t1', 'bold_t1'),
          ('outputnode.bold_t1_ref', 'bold_t1_ref'),
          ('outputnode.bold_aseg_t1', 'bold_aseg_t1'),
          ('outputnode.bold_aparc_t1', 'bold_aparc_t1')]),
        (bold_reg_wf, summary, [('outputnode.fallback', 'fallback')]),
        # SDC (or pass-through workflow)
        (inputnode, bold_sdc_wf, [('joint_template', 'inputnode.templates'),
                                  ('joint_std2anat_xfm',
                                   'inputnode.std2anat_xfm')]),
        (inputnode, bold_sdc_wf, [('t1_brain', 'inputnode.t1_brain')]),
        (bold_reference_wf, bold_sdc_wf,
         [('outputnode.ref_image', 'inputnode.bold_ref'),
          ('outputnode.ref_image_brain', 'inputnode.bold_ref_brain'),
          ('outputnode.bold_mask', 'inputnode.bold_mask')]),
        # For t2s_coreg, replace EPI-to-T1w registration inputs
        (bold_sdc_wf if not t2s_coreg else bold_t2s_wf, bold_reg_wf,
         [('outputnode.bold_ref_brain', 'inputnode.ref_bold_brain')]),
        (bold_sdc_wf if not t2s_coreg else bold_t2s_wf, bold_t1_trans_wf,
         [('outputnode.bold_ref_brain', 'inputnode.ref_bold_brain'),
          ('outputnode.bold_mask', 'inputnode.ref_bold_mask')]),
        (bold_sdc_wf, bold_t1_trans_wf, [('outputnode.out_warp',
                                          'inputnode.fieldwarp')]),
        (bold_sdc_wf, bold_bold_trans_wf,
         [('outputnode.out_warp', 'inputnode.fieldwarp'),
          ('outputnode.bold_mask', 'inputnode.bold_mask')]),
        (bold_sdc_wf, summary, [('outputnode.method', 'distortion_correction')
                                ]),
        # Connect bold_confounds_wf
        (inputnode, bold_confounds_wf, [('t1_tpms', 'inputnode.t1_tpms'),
                                        ('t1_mask', 'inputnode.t1_mask')]),
        (bold_hmc_wf, bold_confounds_wf, [('outputnode.movpar_file',
                                           'inputnode.movpar_file')]),
        (bold_reg_wf, bold_confounds_wf, [('outputnode.itk_t1_to_bold',
                                           'inputnode.t1_bold_xform')]),
        (bold_reference_wf, bold_confounds_wf, [('outputnode.skip_vols',
                                                 'inputnode.skip_vols')]),
        (bold_confounds_wf, outputnode, [
            ('outputnode.confounds_file', 'confounds'),
        ]),
        (bold_confounds_wf, outputnode, [
            ('outputnode.confounds_metadata', 'confounds_metadata'),
        ]),
        # Connect bold_bold_trans_wf
        (bold_split, bold_bold_trans_wf, [('out_files', 'inputnode.bold_file')]
         ),
        (bold_hmc_wf, bold_bold_trans_wf, [('outputnode.xforms',
                                            'inputnode.hmc_xforms')]),
        # Summary
        (outputnode, summary, [('confounds', 'confounds_file')]),
    ])

    # for standard EPI data, pass along correct file
    if not multiecho:
        workflow.connect([
            (inputnode, func_derivatives_wf, [('bold_file',
                                               'inputnode.source_file')]),
            (bold_bold_trans_wf, bold_confounds_wf,
             [('outputnode.bold', 'inputnode.bold'),
              ('outputnode.bold_mask', 'inputnode.bold_mask')]),
            (bold_split, bold_t1_trans_wf, [('out_files',
                                             'inputnode.bold_split')]),
        ])
    else:  # for meepi, create and use optimal combination
        workflow.connect([
            # update name source for optimal combination
            (inputnode, func_derivatives_wf,
             [(('bold_file', combine_meepi_source), 'inputnode.source_file')]),
            (bold_bold_trans_wf, skullstrip_bold_wf, [('outputnode.bold',
                                                       'inputnode.in_file')]),
            (bold_t2s_wf, bold_confounds_wf,
             [('outputnode.bold', 'inputnode.bold'),
              ('outputnode.bold_mask', 'inputnode.bold_mask')]),
            (bold_t2s_wf, bold_t1_trans_wf, [('outputnode.bold',
                                              'inputnode.bold_split')]),
        ])

    if fmaps:
        from ..fieldmap.unwarp import init_fmap_unwarp_report_wf
        # Report on BOLD correction
        fmap_unwarp_report_wf = init_fmap_unwarp_report_wf()
        workflow.connect([
            (inputnode, fmap_unwarp_report_wf, [('t1_seg', 'inputnode.in_seg')
                                                ]),
            (bold_reference_wf, fmap_unwarp_report_wf,
             [('outputnode.ref_image', 'inputnode.in_pre')]),
            (bold_reg_wf, fmap_unwarp_report_wf, [('outputnode.itk_t1_to_bold',
                                                   'inputnode.in_xfm')]),
            (bold_sdc_wf, fmap_unwarp_report_wf, [('outputnode.bold_ref',
                                                   'inputnode.in_post')]),
        ])

        # Overwrite ``out_path_base`` of unwarping DataSinks
        for node in fmap_unwarp_report_wf.list_node_names():
            if node.split('.')[-1].startswith('ds_'):
                fmap_unwarp_report_wf.get_node(
                    node).interface.out_path_base = 'fmriprep'

        if force_syn and fmaps[0]['suffix'] != 'syn':
            syn_unwarp_report_wf = init_fmap_unwarp_report_wf(
                name='syn_unwarp_report_wf', forcedsyn=True)
            workflow.connect([
                (inputnode, syn_unwarp_report_wf, [('t1_seg',
                                                    'inputnode.in_seg')]),
                (bold_reference_wf, syn_unwarp_report_wf,
                 [('outputnode.ref_image', 'inputnode.in_pre')]),
                (bold_reg_wf, syn_unwarp_report_wf,
                 [('outputnode.itk_t1_to_bold', 'inputnode.in_xfm')]),
                (bold_sdc_wf, syn_unwarp_report_wf,
                 [('outputnode.syn_bold_ref', 'inputnode.in_post')]),
            ])

            # Overwrite ``out_path_base`` of unwarping DataSinks
            for node in syn_unwarp_report_wf.list_node_names():
                if node.split('.')[-1].startswith('ds_'):
                    syn_unwarp_report_wf.get_node(
                        node).interface.out_path_base = 'fmriprep'

    # Map final BOLD mask into T1w space (if required)
    if 'T1w' in output_spaces or 'anat' in output_spaces:
        from niworkflows.interfaces.fixes import (FixHeaderApplyTransforms as
                                                  ApplyTransforms)

        boldmask_to_t1w = pe.Node(ApplyTransforms(interpolation='MultiLabel',
                                                  float=True),
                                  name='boldmask_to_t1w',
                                  mem_gb=0.1)
        workflow.connect([
            (bold_reg_wf, boldmask_to_t1w, [('outputnode.itk_bold_to_t1',
                                             'transforms')]),
            (bold_t1_trans_wf, boldmask_to_t1w, [('outputnode.bold_mask_t1',
                                                  'reference_image')]),
            (bold_bold_trans_wf if not multiecho else bold_t2s_wf,
             boldmask_to_t1w, [('outputnode.bold_mask', 'input_image')]),
            (boldmask_to_t1w, outputnode, [('output_image', 'bold_mask_t1')]),
        ])

    if set(['func', 'run', 'bold', 'boldref',
            'sbref']).intersection(output_spaces):
        workflow.connect([
            (bold_bold_trans_wf, outputnode, [('outputnode.bold',
                                               'bold_native')]),
            (bold_bold_trans_wf, func_derivatives_wf,
             [('outputnode.bold_ref', 'inputnode.bold_native_ref'),
              ('outputnode.bold_mask', 'inputnode.bold_mask_native')]),
        ])

    if volume_std_spaces:
        # Apply transforms in 1 shot
        # Only use uncompressed output if AROMA is to be run
        bold_std_trans_wf = init_bold_std_trans_wf(
            freesurfer=freesurfer,
            mem_gb=mem_gb['resampled'],
            omp_nthreads=omp_nthreads,
            standard_spaces=volume_std_spaces,
            name='bold_std_trans_wf',
            use_compression=not low_mem,
            use_fieldwarp=fmaps is not None,
        )
        workflow.connect([
            (inputnode, bold_std_trans_wf,
             [('joint_template', 'inputnode.templates'),
              ('joint_anat2std_xfm', 'inputnode.anat2std_xfm'),
              ('bold_file', 'inputnode.name_source'),
              ('t1_aseg', 'inputnode.bold_aseg'),
              ('t1_aparc', 'inputnode.bold_aparc')]),
            (bold_hmc_wf, bold_std_trans_wf, [('outputnode.xforms',
                                               'inputnode.hmc_xforms')]),
            (bold_reg_wf, bold_std_trans_wf, [('outputnode.itk_bold_to_t1',
                                               'inputnode.itk_bold_to_t1')]),
            (bold_bold_trans_wf if not multiecho else bold_t2s_wf,
             bold_std_trans_wf, [('outputnode.bold_mask',
                                  'inputnode.bold_mask')]),
            (bold_sdc_wf, bold_std_trans_wf, [('outputnode.out_warp',
                                               'inputnode.fieldwarp')]),
            (bold_std_trans_wf, outputnode,
             [('outputnode.bold_std', 'bold_std'),
              ('outputnode.bold_std_ref', 'bold_std_ref'),
              ('outputnode.bold_mask_std', 'bold_mask_std')]),
        ])

        if freesurfer:
            workflow.connect([
                (bold_std_trans_wf, func_derivatives_wf, [
                    ('poutputnode.bold_aseg_std', 'inputnode.bold_aseg_std'),
                    ('poutputnode.bold_aparc_std', 'inputnode.bold_aparc_std'),
                ]),
                (bold_std_trans_wf, outputnode,
                 [('outputnode.bold_aseg_std', 'bold_aseg_std'),
                  ('outputnode.bold_aparc_std', 'bold_aparc_std')]),
            ])

        if 'MNI152NLin2009cAsym' in std_spaces:
            carpetplot_wf = init_carpetplot_wf(standard_spaces=std_spaces,
                                               mem_gb=mem_gb['resampled'],
                                               metadata=metadata,
                                               name='carpetplot_wf')
            workflow.connect([
                (inputnode, carpetplot_wf, [('joint_std2anat_xfm',
                                             'inputnode.std2anat_xfm')]),
                (bold_bold_trans_wf if not multiecho else bold_t2s_wf,
                 carpetplot_wf, [('outputnode.bold', 'inputnode.bold'),
                                 ('outputnode.bold_mask',
                                  'inputnode.bold_mask')]),
                (bold_reg_wf, carpetplot_wf, [('outputnode.itk_t1_to_bold',
                                               'inputnode.t1_bold_xform')]),
                (bold_confounds_wf, carpetplot_wf,
                 [('outputnode.confounds_file', 'inputnode.confounds_file')]),
            ])

        if not multiecho:
            workflow.connect([(bold_split, bold_std_trans_wf,
                               [('out_files', 'inputnode.bold_split')])])
        else:
            split_opt_comb = bold_split.clone(name='split_opt_comb')
            workflow.connect([(bold_t2s_wf, split_opt_comb,
                               [('outputnode.bold', 'in_file')]),
                              (split_opt_comb, bold_std_trans_wf,
                               [('out_files', 'inputnode.bold_split')])])

        # Artifacts resampled in MNI space can only be sinked if they
        # were actually generated. See #1348.
        # Uses the parameterized outputnode to generate all outputs
        workflow.connect([
            (bold_std_trans_wf, func_derivatives_wf, [
                ('poutputnode.templates', 'inputnode.template'),
                ('poutputnode.bold_std_ref', 'inputnode.bold_std_ref'),
                ('poutputnode.bold_std', 'inputnode.bold_std'),
                ('poutputnode.bold_mask_std', 'inputnode.bold_mask_std'),
            ]),
        ])

        if use_aroma and 'MNI152NLin6Asym' in std_spaces:  # ICA-AROMA workflow
            from .confounds import init_ica_aroma_wf

            ica_aroma_wf = init_ica_aroma_wf(
                metadata=metadata,
                mem_gb=mem_gb['resampled'],
                omp_nthreads=omp_nthreads,
                use_fieldwarp=fmaps is not None,
                err_on_aroma_warn=err_on_aroma_warn,
                aroma_melodic_dim=aroma_melodic_dim,
                name='ica_aroma_wf')

            join = pe.Node(niu.Function(output_names=["out_file"],
                                        function=_to_join),
                           name='aroma_confounds')

            mrg_conf_metadata = pe.Node(niu.Merge(2),
                                        name='merge_confound_metadata',
                                        run_without_submitting=True)
            mrg_conf_metadata2 = pe.Node(DictMerge(),
                                         name='merge_confound_metadata2',
                                         run_without_submitting=True)
            workflow.disconnect([
                (bold_confounds_wf, outputnode, [
                    ('outputnode.confounds_file', 'confounds'),
                ]),
                (bold_confounds_wf, outputnode, [
                    ('outputnode.confounds_metadata', 'confounds_metadata'),
                ]),
            ])
            workflow.connect([
                (bold_std_trans_wf, ica_aroma_wf,
                 [('outputnode.bold_std', 'inputnode.bold_std'),
                  ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'),
                  ('outputnode.templates', 'inputnode.templates')]),
                (inputnode, ica_aroma_wf, [('bold_file',
                                            'inputnode.name_source')]),
                (bold_hmc_wf, ica_aroma_wf, [('outputnode.movpar_file',
                                              'inputnode.movpar_file')]),
                (bold_reference_wf, ica_aroma_wf, [('outputnode.skip_vols',
                                                    'inputnode.skip_vols')]),
                (bold_confounds_wf, join, [('outputnode.confounds_file',
                                            'in_file')]),
                (bold_confounds_wf, mrg_conf_metadata,
                 [('outputnode.confounds_metadata', 'in1')]),
                (ica_aroma_wf, join, [('outputnode.aroma_confounds',
                                       'join_file')]),
                (ica_aroma_wf, mrg_conf_metadata,
                 [('outputnode.aroma_metadata', 'in2')]),
                (mrg_conf_metadata, mrg_conf_metadata2, [('out', 'in_dicts')]),
                (ica_aroma_wf, outputnode,
                 [('outputnode.aroma_noise_ics', 'aroma_noise_ics'),
                  ('outputnode.melodic_mix', 'melodic_mix'),
                  ('outputnode.nonaggr_denoised_file', 'nonaggr_denoised_file')
                  ]),
                (join, outputnode, [('out_file', 'confounds')]),
                (mrg_conf_metadata2, outputnode, [('out_dict',
                                                   'confounds_metadata')]),
            ])

    # SURFACES ##################################################################################
    surface_spaces = [
        space for space in output_spaces.keys() if space.startswith('fs')
    ]
    if freesurfer and surface_spaces:
        LOGGER.log(25, 'Creating BOLD surface-sampling workflow.')
        bold_surf_wf = init_bold_surf_wf(mem_gb=mem_gb['resampled'],
                                         output_spaces=surface_spaces,
                                         medial_surface_nan=medial_surface_nan,
                                         name='bold_surf_wf')
        workflow.connect([
            (inputnode, bold_surf_wf,
             [('t1_preproc', 'inputnode.t1_preproc'),
              ('subjects_dir', 'inputnode.subjects_dir'),
              ('subject_id', 'inputnode.subject_id'),
              ('t1_2_fsnative_forward_transform',
               'inputnode.t1_2_fsnative_forward_transform')]),
            (bold_t1_trans_wf, bold_surf_wf, [('outputnode.bold_t1',
                                               'inputnode.source_file')]),
            (bold_surf_wf, outputnode, [('outputnode.surfaces', 'surfaces')]),
        ])

        if cifti_output:
            from niworkflows.interfaces.utility import KeySelect
            bold_surf_wf.__desc__ += """\
*Grayordinates* files [@hcppipelines], which combine surface-sampled
data and volume-sampled data, were also generated.
"""
            select_std = pe.Node(KeySelect(fields=['bold_std']),
                                 name='select_std',
                                 run_without_submitting=True)
            select_std.inputs.key = 'MNI152NLin2009cAsym'

            gen_cifti = pe.MapNode(GenerateCifti(),
                                   iterfield=["surface_target", "gifti_files"],
                                   name="gen_cifti")
            gen_cifti.inputs.TR = metadata.get("RepetitionTime")
            gen_cifti.inputs.surface_target = list(cifti_spaces)

            workflow.connect([
                (bold_std_trans_wf, select_std,
                 [('outputnode.templates', 'keys'),
                  ('outputnode.bold_std', 'bold_std')]),
                (bold_surf_wf, gen_cifti, [('outputnode.surfaces',
                                            'gifti_files')]),
                (inputnode, gen_cifti, [('subjects_dir', 'subjects_dir')]),
                (select_std, gen_cifti, [('bold_std', 'bold_file')]),
                (gen_cifti, outputnode, [('out_file', 'bold_cifti'),
                                         ('variant', 'cifti_variant'),
                                         ('variant_key', 'cifti_variant_key')
                                         ]),
            ])

    # REPORTING ############################################################
    ds_report_summary = pe.Node(DerivativesDataSink(desc='summary',
                                                    keep_dtype=True),
                                name='ds_report_summary',
                                run_without_submitting=True,
                                mem_gb=DEFAULT_MEMORY_MIN_GB)

    ds_report_validation = pe.Node(DerivativesDataSink(
        base_directory=reportlets_dir, desc='validation', keep_dtype=True),
                                   name='ds_report_validation',
                                   run_without_submitting=True,
                                   mem_gb=DEFAULT_MEMORY_MIN_GB)

    workflow.connect([
        (summary, ds_report_summary, [('out_report', 'in_file')]),
        (bold_reference_wf, ds_report_validation,
         [('outputnode.validation_report', 'in_file')]),
    ])

    # Fill-in datasinks of reportlets seen so far
    for node in workflow.list_node_names():
        if node.split('.')[-1].startswith('ds_report'):
            workflow.get_node(node).inputs.base_directory = reportlets_dir
            workflow.get_node(node).inputs.source_file = ref_file

    return workflow
Esempio n. 4
0
def init_anat_preproc_wf(
    *,
    bids_root,
    freesurfer,
    hires,
    longitudinal,
    t1w,
    omp_nthreads,
    output_dir,
    skull_strip_mode,
    skull_strip_template,
    spaces,
    debug=False,
    existing_derivatives=None,
    name='anat_preproc_wf',
    skull_strip_fixed_seed=False,
):
    """
    Stage the anatomical preprocessing steps of *sMRIPrep*.

    This includes:

      - T1w reference: realigning and then averaging T1w images.
      - Brain extraction and INU (bias field) correction.
      - Brain tissue segmentation.
      - Spatial normalization to standard spaces.
      - Surface reconstruction with FreeSurfer_.

    .. include:: ../links.rst

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

            from niworkflows.utils.spaces import SpatialReferences, Reference
            from smriprep.workflows.anatomical import init_anat_preproc_wf
            wf = init_anat_preproc_wf(
                bids_root='.',
                freesurfer=True,
                hires=True,
                longitudinal=False,
                t1w=['t1w.nii.gz'],
                omp_nthreads=1,
                output_dir='.',
                skull_strip_mode='force',
                skull_strip_template=Reference('OASIS30ANTs'),
                spaces=SpatialReferences(spaces=['MNI152NLin2009cAsym', 'fsaverage5']),
            )


    Parameters
    ----------
    bids_root : :obj:`str`
        Path of the input BIDS dataset root
    existing_derivatives : :obj:`dict` or None
        Dictionary mapping output specification attribute names and
        paths to corresponding derivatives.
    freesurfer : :obj:`bool`
        Enable FreeSurfer surface reconstruction (increases runtime by 6h,
        at the very least)
    hires : :obj:`bool`
        Enable sub-millimeter preprocessing in FreeSurfer
    longitudinal : :obj:`bool`
        Create unbiased structural template, regardless of number of inputs
        (may increase runtime)
    t1w : :obj:`list`
        List of T1-weighted structural images.
    omp_nthreads : :obj:`int`
        Maximum number of threads an individual process may use
    output_dir : :obj:`str`
        Directory in which to save derivatives
    skull_strip_template : :py:class:`~niworkflows.utils.spaces.Reference`
        Spatial reference to use in atlas-based brain extraction.
    spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences`
        Object containing standard and nonstandard space specifications.
    debug : :obj:`bool`
        Enable debugging outputs
    name : :obj:`str`, optional
        Workflow name (default: anat_preproc_wf)
    skull_strip_mode : :obj:`str`
        Determiner for T1-weighted skull stripping (`force` ensures skull stripping,
        `skip` ignores skull stripping, and `auto` automatically ignores skull stripping
        if pre-stripped brains are detected).
    skull_strip_fixed_seed : :obj:`bool`
        Do not use a random seed for skull-stripping - will ensure
        run-to-run replicability when used with --omp-nthreads 1
        (default: ``False``).

    Inputs
    ------
    t1w
        List of T1-weighted structural images
    t2w
        List of T2-weighted structural images
    roi
        A mask to exclude regions during standardization
    flair
        List of FLAIR images
    subjects_dir
        FreeSurfer SUBJECTS_DIR
    subject_id
        FreeSurfer subject ID

    Outputs
    -------
    t1w_preproc
        The T1w reference map, which is calculated as the average of bias-corrected
        and preprocessed T1w images, defining the anatomical space.
    t1w_brain
        Skull-stripped ``t1w_preproc``
    t1w_mask
        Brain (binary) mask estimated by brain extraction.
    t1w_dseg
        Brain tissue segmentation of the preprocessed structural image, including
        gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF).
    t1w_tpms
        List of tissue probability maps corresponding to ``t1w_dseg``.
    std_preproc
        T1w reference resampled in one or more standard spaces.
    std_mask
        Mask of skull-stripped template, in MNI space
    std_dseg
        Segmentation, resampled into MNI space
    std_tpms
        List of tissue probability maps in MNI space
    subjects_dir
        FreeSurfer SUBJECTS_DIR
    anat2std_xfm
        Nonlinear spatial transform to resample imaging data given in anatomical space
        into standard space.
    std2anat_xfm
        Inverse transform of the above.
    subject_id
        FreeSurfer subject ID
    t1w2fsnative_xfm
        LTA-style affine matrix translating from T1w to
        FreeSurfer-conformed subject space
    fsnative2t1w_xfm
        LTA-style affine matrix translating from FreeSurfer-conformed
        subject space to T1w
    surfaces
        GIFTI surfaces (gray/white boundary, midthickness, pial, inflated)

    See Also
    --------
    * :py:func:`~niworkflows.anat.ants.init_brain_extraction_wf`
    * :py:func:`~smriprep.workflows.surfaces.init_surface_recon_wf`

    """
    workflow = Workflow(name=name)
    num_t1w = len(t1w)
    desc = """Anatomical data preprocessing

: """
    desc += """\
A total of {num_t1w} T1-weighted (T1w) images were found within the input
BIDS dataset.""".format(num_t1w=num_t1w)

    inputnode = pe.Node(niu.IdentityInterface(
        fields=['t1w', 't2w', 'roi', 'flair', 'subjects_dir', 'subject_id']),
                        name='inputnode')

    outputnode = pe.Node(niu.IdentityInterface(
        fields=['template', 'subjects_dir', 'subject_id'] +
        get_outputnode_spec()),
                         name='outputnode')

    # Connect reportlets workflows
    anat_reports_wf = init_anat_reports_wf(
        freesurfer=freesurfer,
        output_dir=output_dir,
    )
    workflow.connect([
        (outputnode, anat_reports_wf, [('t1w_preproc',
                                        'inputnode.t1w_preproc'),
                                       ('t1w_mask', 'inputnode.t1w_mask'),
                                       ('t1w_dseg', 'inputnode.t1w_dseg')]),
    ])

    if existing_derivatives is not None:
        LOGGER.log(
            25,
            "Anatomical workflow will reuse prior derivatives found in the "
            "output folder (%s).", output_dir)
        desc += """
Anatomical preprocessing was reused from previously existing derivative objects.\n"""
        workflow.__desc__ = desc

        templates = existing_derivatives.pop('template')
        templatesource = pe.Node(niu.IdentityInterface(fields=['template']),
                                 name='templatesource')
        templatesource.iterables = [('template', templates)]
        outputnode.inputs.template = templates

        for field, value in existing_derivatives.items():
            setattr(outputnode.inputs, field, value)

        anat_reports_wf.inputs.inputnode.source_file = fix_multi_T1w_source_name(
            [existing_derivatives['t1w_preproc']])

        stdselect = pe.Node(KeySelect(fields=['std_preproc', 'std_mask'],
                                      keys=templates),
                            name='stdselect',
                            run_without_submitting=True)
        workflow.connect([
            (inputnode, outputnode, [('subjects_dir', 'subjects_dir'),
                                     ('subject_id', 'subject_id')]),
            (inputnode, anat_reports_wf,
             [('subjects_dir', 'inputnode.subjects_dir'),
              ('subject_id', 'inputnode.subject_id')]),
            (templatesource, stdselect, [('template', 'key')]),
            (outputnode, stdselect, [('std_preproc', 'std_preproc'),
                                     ('std_mask', 'std_mask')]),
            (stdselect, anat_reports_wf, [
                ('key', 'inputnode.template'),
                ('std_preproc', 'inputnode.std_t1w'),
                ('std_mask', 'inputnode.std_mask'),
            ]),
        ])
        return workflow

    # The workflow is not cached.
    desc += """
All of them were corrected for intensity non-uniformity (INU)
""" if num_t1w > 1 else """\
The T1-weighted (T1w) image was corrected for intensity non-uniformity (INU)
"""
    desc += """\
with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} \
[@ants, RRID:SCR_004757]"""
    desc += '.\n' if num_t1w > 1 else ", and used as T1w-reference throughout the workflow.\n"

    desc += """\
The T1w-reference was then skull-stripped with a *Nipype* implementation of
the `antsBrainExtraction.sh` workflow (from ANTs), using {skullstrip_tpl}
as target template.
Brain tissue segmentation of cerebrospinal fluid (CSF),
white-matter (WM) and gray-matter (GM) was performed on
the brain-extracted T1w using `fast` [FSL {fsl_ver}, RRID:SCR_002823,
@fsl_fast].
"""

    workflow.__desc__ = desc.format(
        ants_ver=ANTsInfo.version() or '(version unknown)',
        fsl_ver=fsl.FAST().version or '(version unknown)',
        num_t1w=num_t1w,
        skullstrip_tpl=skull_strip_template.fullname,
    )

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

    # 1. Anatomical reference generation - average input T1w images.
    anat_template_wf = init_anat_template_wf(longitudinal=longitudinal,
                                             omp_nthreads=omp_nthreads,
                                             num_t1w=num_t1w)

    anat_validate = pe.Node(ValidateImage(),
                            name='anat_validate',
                            run_without_submitting=True)

    # 2. Brain-extraction and INU (bias field) correction.
    if skull_strip_mode == 'auto':
        import numpy as np
        import nibabel as nb

        def _is_skull_stripped(imgs):
            """Check if T1w images are skull-stripped."""
            def _check_img(img):
                data = np.abs(nb.load(img).get_fdata(dtype=np.float32))
                sidevals = data[0, :, :].sum() + data[-1, :, :].sum() + \
                    data[:, 0, :].sum() + data[:, -1, :].sum() + \
                    data[:, :, 0].sum() + data[:, :, -1].sum()
                return sidevals < 10

            return all(_check_img(img) for img in imgs)

        skull_strip_mode = _is_skull_stripped(t1w)

    if skull_strip_mode in (True, 'skip'):
        brain_extraction_wf = init_n4_only_wf(
            omp_nthreads=omp_nthreads,
            atropos_use_random_seed=not skull_strip_fixed_seed,
        )
    else:
        brain_extraction_wf = init_brain_extraction_wf(
            in_template=skull_strip_template.space,
            template_spec=skull_strip_template.spec,
            atropos_use_random_seed=not skull_strip_fixed_seed,
            omp_nthreads=omp_nthreads,
            normalization_quality='precise' if not debug else 'testing')

    # 4. Spatial normalization
    anat_norm_wf = init_anat_norm_wf(
        debug=debug,
        omp_nthreads=omp_nthreads,
        templates=spaces.get_spaces(nonstandard=False, dim=(3, )),
    )

    workflow.connect([
        # Step 1.
        (inputnode, anat_template_wf, [('t1w', 'inputnode.t1w')]),
        (anat_template_wf, anat_validate, [('outputnode.t1w_ref', 'in_file')]),
        (anat_validate, brain_extraction_wf, [('out_file',
                                               'inputnode.in_files')]),
        (brain_extraction_wf, outputnode, [(('outputnode.bias_corrected',
                                             _pop), 't1w_preproc')]),
        (anat_template_wf, outputnode, [('outputnode.t1w_realign_xfm',
                                         't1w_ref_xfms')]),
        (buffernode, outputnode, [('t1w_brain', 't1w_brain'),
                                  ('t1w_mask', 't1w_mask')]),
        # Steps 2, 3 and 4
        (inputnode, anat_norm_wf, [(('t1w', fix_multi_T1w_source_name),
                                    'inputnode.orig_t1w'),
                                   ('roi', 'inputnode.lesion_mask')]),
        (brain_extraction_wf, anat_norm_wf,
         [(('outputnode.bias_corrected', _pop), 'inputnode.moving_image')]),
        (buffernode, anat_norm_wf, [('t1w_mask', 'inputnode.moving_mask')]),
        (anat_norm_wf, outputnode, [
            ('poutputnode.standardized', 'std_preproc'),
            ('poutputnode.std_mask', 'std_mask'),
            ('poutputnode.std_dseg', 'std_dseg'),
            ('poutputnode.std_tpms', 'std_tpms'),
            ('outputnode.template', 'template'),
            ('outputnode.anat2std_xfm', 'anat2std_xfm'),
            ('outputnode.std2anat_xfm', 'std2anat_xfm'),
        ]),
    ])

    # Change LookUp Table - BIDS wants: 0 (bg), 1 (gm), 2 (wm), 3 (csf)
    lut_t1w_dseg = pe.Node(niu.Function(function=_apply_bids_lut),
                           name='lut_t1w_dseg')

    workflow.connect([
        (lut_t1w_dseg, anat_norm_wf, [('out', 'inputnode.moving_segmentation')
                                      ]),
        (lut_t1w_dseg, outputnode, [('out', 't1w_dseg')]),
    ])

    # Connect reportlets
    workflow.connect([
        (inputnode, anat_reports_wf, [(('t1w', fix_multi_T1w_source_name),
                                       'inputnode.source_file')]),
        (outputnode, anat_reports_wf, [
            ('std_preproc', 'inputnode.std_t1w'),
            ('std_mask', 'inputnode.std_mask'),
        ]),
        (anat_template_wf, anat_reports_wf,
         [('outputnode.out_report', 'inputnode.t1w_conform_report')]),
        (anat_norm_wf, anat_reports_wf, [('poutputnode.template',
                                          'inputnode.template')]),
    ])

    # Write outputs ############################################3
    anat_derivatives_wf = init_anat_derivatives_wf(
        bids_root=bids_root,
        freesurfer=freesurfer,
        num_t1w=num_t1w,
        output_dir=output_dir,
    )

    workflow.connect([
        # Connect derivatives
        (anat_template_wf, anat_derivatives_wf, [('outputnode.t1w_valid_list',
                                                  'inputnode.source_files')]),
        (anat_norm_wf, anat_derivatives_wf,
         [('poutputnode.template', 'inputnode.template'),
          ('poutputnode.anat2std_xfm', 'inputnode.anat2std_xfm'),
          ('poutputnode.std2anat_xfm', 'inputnode.std2anat_xfm')]),
        (outputnode, anat_derivatives_wf, [
            ('std_preproc', 'inputnode.std_t1w'),
            ('t1w_ref_xfms', 'inputnode.t1w_ref_xfms'),
            ('t1w_preproc', 'inputnode.t1w_preproc'),
            ('t1w_mask', 'inputnode.t1w_mask'),
            ('t1w_dseg', 'inputnode.t1w_dseg'),
            ('t1w_tpms', 'inputnode.t1w_tpms'),
            ('std_mask', 'inputnode.std_mask'),
            ('std_dseg', 'inputnode.std_dseg'),
            ('std_tpms', 'inputnode.std_tpms'),
        ]),
    ])

    if not freesurfer:  # Flag --fs-no-reconall is set - return
        # Brain tissue segmentation - FAST produces: 0 (bg), 1 (wm), 2 (csf), 3 (gm)
        t1w_dseg = pe.Node(fsl.FAST(segments=True,
                                    no_bias=True,
                                    probability_maps=True),
                           name='t1w_dseg',
                           mem_gb=3)
        lut_t1w_dseg.inputs.lut = (0, 3, 1, 2
                                   )  # Maps: 0 -> 0, 3 -> 1, 1 -> 2, 2 -> 3.
        fast2bids = pe.Node(niu.Function(function=_probseg_fast2bids),
                            name="fast2bids",
                            run_without_submitting=True)

        workflow.connect([
            (brain_extraction_wf, buffernode,
             [(('outputnode.out_file', _pop), 't1w_brain'),
              ('outputnode.out_mask', 't1w_mask')]),
            (buffernode, t1w_dseg, [('t1w_brain', 'in_files')]),
            (t1w_dseg, lut_t1w_dseg, [('partial_volume_map', 'in_dseg')]),
            (t1w_dseg, fast2bids, [('partial_volume_files', 'inlist')]),
            (fast2bids, anat_norm_wf, [('out', 'inputnode.moving_tpms')]),
            (fast2bids, outputnode, [('out', 't1w_tpms')]),
        ])
        return workflow

    # Map FS' aseg labels onto three-tissue segmentation
    lut_t1w_dseg.inputs.lut = _aseg_to_three()
    split_seg = pe.Node(niu.Function(function=_split_segments),
                        name='split_seg')

    # check for older IsRunning files and remove accordingly
    fs_isrunning = pe.Node(niu.Function(function=_fs_isRunning),
                           overwrite=True,
                           name='fs_isrunning')
    fs_isrunning.inputs.logger = LOGGER

    # 5. Surface reconstruction (--fs-no-reconall not set)
    surface_recon_wf = init_surface_recon_wf(name='surface_recon_wf',
                                             omp_nthreads=omp_nthreads,
                                             hires=hires)
    applyrefined = pe.Node(fsl.ApplyMask(), name='applyrefined')
    workflow.connect([
        (inputnode, fs_isrunning, [('subjects_dir', 'subjects_dir'),
                                   ('subject_id', 'subject_id')]),
        (inputnode, surface_recon_wf, [('t2w', 'inputnode.t2w'),
                                       ('flair', 'inputnode.flair'),
                                       ('subject_id', 'inputnode.subject_id')
                                       ]),
        (fs_isrunning, surface_recon_wf, [('out', 'inputnode.subjects_dir')]),
        (anat_validate, surface_recon_wf, [('out_file', 'inputnode.t1w')]),
        (brain_extraction_wf, surface_recon_wf,
         [(('outputnode.out_file', _pop), 'inputnode.skullstripped_t1'),
          ('outputnode.out_segm', 'inputnode.ants_segs'),
          (('outputnode.bias_corrected', _pop), 'inputnode.corrected_t1')]),
        (brain_extraction_wf, applyrefined, [(('outputnode.bias_corrected',
                                               _pop), 'in_file')]),
        (surface_recon_wf, applyrefined, [('outputnode.out_brainmask',
                                           'mask_file')]),
        (surface_recon_wf, lut_t1w_dseg, [('outputnode.out_aseg', 'in_dseg')]),
        (lut_t1w_dseg, split_seg, [('out', 'in_file')]),
        (split_seg, anat_norm_wf, [('out', 'inputnode.moving_tpms')]),
        (split_seg, outputnode, [('out', 't1w_tpms')]),
        (surface_recon_wf, outputnode,
         [('outputnode.subjects_dir', 'subjects_dir'),
          ('outputnode.subject_id', 'subject_id'),
          ('outputnode.t1w2fsnative_xfm', 't1w2fsnative_xfm'),
          ('outputnode.fsnative2t1w_xfm', 'fsnative2t1w_xfm'),
          ('outputnode.surfaces', 'surfaces'),
          ('outputnode.out_aseg', 't1w_aseg'),
          ('outputnode.out_aparc', 't1w_aparc')]),
        (applyrefined, buffernode, [('out_file', 't1w_brain')]),
        (surface_recon_wf, buffernode, [('outputnode.out_brainmask',
                                         't1w_mask')]),
        (surface_recon_wf, anat_reports_wf,
         [('outputnode.subject_id', 'inputnode.subject_id'),
          ('outputnode.subjects_dir', 'inputnode.subjects_dir')]),
        (surface_recon_wf, anat_derivatives_wf, [
            ('outputnode.out_aseg', 'inputnode.t1w_fs_aseg'),
            ('outputnode.out_aparc', 'inputnode.t1w_fs_aparc'),
        ]),
        (outputnode, anat_derivatives_wf, [
            ('t1w2fsnative_xfm', 'inputnode.t1w2fsnative_xfm'),
            ('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'),
            ('surfaces', 'inputnode.surfaces'),
        ]),
    ])

    return workflow
Esempio n. 5
0
def init_ica_aroma_wf(
    mem_gb,
    metadata,
    omp_nthreads,
    aroma_melodic_dim=-200,
    err_on_aroma_warn=False,
    name='ica_aroma_wf',
    susan_fwhm=6.0,
):
    """
    Build a workflow that runs `ICA-AROMA`_.

    This workflow wraps `ICA-AROMA`_ to identify and remove motion-related
    independent components from a BOLD time series.

    The following steps are performed:

    #. Remove non-steady state volumes from the bold series.
    #. Smooth data using FSL `susan`, with a kernel width FWHM=6.0mm.
    #. Run FSL `melodic` outside of ICA-AROMA to generate the report
    #. Run ICA-AROMA
    #. Aggregate identified motion components (aggressive) to TSV
    #. Return ``classified_motion_ICs`` and ``melodic_mix`` for user to complete
       non-aggressive denoising in T1w space
    #. Calculate ICA-AROMA-identified noise components
       (columns named ``AROMAAggrCompXX``)

    Additionally, non-aggressive denoising is performed on the BOLD series
    resampled into MNI space.

    There is a current discussion on whether other confounds should be extracted
    before or after denoising `here
    <http://nbviewer.jupyter.org/github/poldracklab/fmriprep-notebooks/blob/922e436429b879271fa13e76767a6e73443e74d9/issue-817_aroma_confounds.ipynb>`__.

    .. _ICA-AROMA: https://github.com/maartenmennes/ICA-AROMA

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

            from fmriprep.workflows.bold.confounds import init_ica_aroma_wf
            wf = init_ica_aroma_wf(
                mem_gb=3,
                metadata={'RepetitionTime': 1.0},
                omp_nthreads=1)

    Parameters
    ----------
    metadata : :obj:`dict`
        BIDS metadata for BOLD file
    mem_gb : :obj:`float`
        Size of BOLD file in GB
    omp_nthreads : :obj:`int`
        Maximum number of threads an individual process may use
    name : :obj:`str`
        Name of workflow (default: ``bold_tpl_trans_wf``)
    susan_fwhm : :obj:`float`
        Kernel width (FWHM in mm) for the smoothing step with
        FSL ``susan`` (default: 6.0mm)
    err_on_aroma_warn : :obj:`bool`
        Do not fail on ICA-AROMA errors
    aroma_melodic_dim : :obj:`int`
        Set the dimensionality of the MELODIC ICA decomposition.
        Negative numbers set a maximum on automatic dimensionality estimation.
        Positive numbers set an exact number of components to extract.
        (default: -200, i.e., estimate <=200 components)

    Inputs
    ------
    itk_bold_to_t1
        Affine transform from ``ref_bold_brain`` to T1 space (ITK format)
    anat2std_xfm
        ANTs-compatible affine-and-warp transform file
    name_source
        BOLD series NIfTI file
        Used to recover original information lost during processing
    skip_vols
        number of non steady state volumes
    bold_split
        Individual 3D BOLD volumes, not motion corrected
    bold_mask
        BOLD series mask in template space
    hmc_xforms
        List of affine transforms aligning each volume to ``ref_image`` in ITK format
    movpar_file
        SPM-formatted motion parameters file

    Outputs
    -------
    aroma_confounds
        TSV of confounds identified as noise by ICA-AROMA
    aroma_noise_ics
        CSV of noise components identified by ICA-AROMA
    melodic_mix
        FSL MELODIC mixing matrix
    nonaggr_denoised_file
        BOLD series with non-aggressive ICA-AROMA denoising applied

    """
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.interfaces.segmentation import ICA_AROMARPT
    from niworkflows.interfaces.utility import KeySelect
    from niworkflows.interfaces.utils import TSV2JSON

    workflow = Workflow(name=name)
    workflow.__postdesc__ = """\
Automatic removal of motion artifacts using independent component analysis
[ICA-AROMA, @aroma] was performed on the *preprocessed BOLD on MNI space*
time-series after removal of non-steady state volumes and spatial smoothing
with an isotropic, Gaussian kernel of 6mm FWHM (full-width half-maximum).
Corresponding "non-aggresively" denoised runs were produced after such
smoothing.
Additionally, the "aggressive" noise-regressors were collected and placed
in the corresponding confounds file.
"""

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'bold_std',
        'bold_mask_std',
        'movpar_file',
        'name_source',
        'skip_vols',
        'spatial_reference',
    ]),
                        name='inputnode')

    outputnode = pe.Node(niu.IdentityInterface(fields=[
        'aroma_confounds', 'aroma_noise_ics', 'melodic_mix',
        'nonaggr_denoised_file', 'aroma_metadata'
    ]),
                         name='outputnode')

    # extract out to BOLD base
    select_std = pe.Node(KeySelect(fields=['bold_mask_std', 'bold_std']),
                         name='select_std',
                         run_without_submitting=True)
    select_std.inputs.key = 'MNI152NLin6Asym_res-2'

    rm_non_steady_state = pe.Node(niu.Function(function=_remove_volumes,
                                               output_names=['bold_cut']),
                                  name='rm_nonsteady')

    calc_median_val = pe.Node(fsl.ImageStats(op_string='-k %s -p 50'),
                              name='calc_median_val')
    calc_bold_mean = pe.Node(fsl.MeanImage(), name='calc_bold_mean')

    def _getusans_func(image, thresh):
        return [tuple([image, thresh])]

    getusans = pe.Node(niu.Function(function=_getusans_func,
                                    output_names=['usans']),
                       name='getusans',
                       mem_gb=0.01)

    smooth = pe.Node(fsl.SUSAN(fwhm=susan_fwhm), name='smooth')

    # melodic node
    melodic = pe.Node(fsl.MELODIC(no_bet=True,
                                  tr_sec=float(metadata['RepetitionTime']),
                                  mm_thresh=0.5,
                                  out_stats=True,
                                  dim=aroma_melodic_dim),
                      name="melodic")

    # ica_aroma node
    ica_aroma = pe.Node(ICA_AROMARPT(denoise_type='nonaggr',
                                     generate_report=True,
                                     TR=metadata['RepetitionTime'],
                                     args='-np'),
                        name='ica_aroma')

    add_non_steady_state = pe.Node(niu.Function(function=_add_volumes,
                                                output_names=['bold_add']),
                                   name='add_nonsteady')

    # extract the confound ICs from the results
    ica_aroma_confound_extraction = pe.Node(
        ICAConfounds(err_on_aroma_warn=err_on_aroma_warn),
        name='ica_aroma_confound_extraction')

    ica_aroma_metadata_fmt = pe.Node(TSV2JSON(index_column='IC',
                                              output=None,
                                              enforce_case=True,
                                              additional_metadata={
                                                  'Method': {
                                                      'Name':
                                                      'ICA-AROMA',
                                                      'Version':
                                                      getenv(
                                                          'AROMA_VERSION',
                                                          'n/a')
                                                  }
                                              }),
                                     name='ica_aroma_metadata_fmt')

    ds_report_ica_aroma = pe.Node(DerivativesDataSink(
        desc='aroma', datatype="figures", dismiss_entities=("echo", )),
                                  name='ds_report_ica_aroma',
                                  run_without_submitting=True,
                                  mem_gb=DEFAULT_MEMORY_MIN_GB)

    def _getbtthresh(medianval):
        return 0.75 * medianval

    # connect the nodes
    workflow.connect([
        (inputnode, select_std, [('spatial_reference', 'keys'),
                                 ('bold_std', 'bold_std'),
                                 ('bold_mask_std', 'bold_mask_std')]),
        (inputnode, ica_aroma, [('movpar_file', 'motion_parameters')]),
        (inputnode, rm_non_steady_state, [('skip_vols', 'skip_vols')]),
        (select_std, rm_non_steady_state, [('bold_std', 'bold_file')]),
        (select_std, calc_median_val, [('bold_mask_std', 'mask_file')]),
        (rm_non_steady_state, calc_median_val, [('bold_cut', 'in_file')]),
        (rm_non_steady_state, calc_bold_mean, [('bold_cut', 'in_file')]),
        (calc_bold_mean, getusans, [('out_file', 'image')]),
        (calc_median_val, getusans, [('out_stat', 'thresh')]),
        # Connect input nodes to complete smoothing
        (rm_non_steady_state, smooth, [('bold_cut', 'in_file')]),
        (getusans, smooth, [('usans', 'usans')]),
        (calc_median_val, smooth, [(('out_stat', _getbtthresh),
                                    'brightness_threshold')]),
        # connect smooth to melodic
        (smooth, melodic, [('smoothed_file', 'in_files')]),
        (select_std, melodic, [('bold_mask_std', 'mask')]),
        # connect nodes to ICA-AROMA
        (smooth, ica_aroma, [('smoothed_file', 'in_file')]),
        (select_std, ica_aroma, [('bold_mask_std', 'report_mask'),
                                 ('bold_mask_std', 'mask')]),
        (melodic, ica_aroma, [('out_dir', 'melodic_dir')]),
        # generate tsvs from ICA-AROMA
        (ica_aroma, ica_aroma_confound_extraction, [('out_dir', 'in_directory')
                                                    ]),
        (inputnode, ica_aroma_confound_extraction, [('skip_vols', 'skip_vols')
                                                    ]),
        (ica_aroma_confound_extraction, ica_aroma_metadata_fmt,
         [('aroma_metadata', 'in_file')]),
        # output for processing and reporting
        (ica_aroma_confound_extraction,
         outputnode, [('aroma_confounds', 'aroma_confounds'),
                      ('aroma_noise_ics', 'aroma_noise_ics'),
                      ('melodic_mix', 'melodic_mix')]),
        (ica_aroma_metadata_fmt, outputnode, [('output', 'aroma_metadata')]),
        (ica_aroma, add_non_steady_state, [('nonaggr_denoised_file',
                                            'bold_cut_file')]),
        (select_std, add_non_steady_state, [('bold_std', 'bold_file')]),
        (inputnode, add_non_steady_state, [('skip_vols', 'skip_vols')]),
        (add_non_steady_state, outputnode, [('bold_add',
                                             'nonaggr_denoised_file')]),
        (ica_aroma, ds_report_ica_aroma, [('out_report', 'in_file')]),
    ])

    return workflow
Esempio n. 6
0
def init_anat_derivatives_wf(
    *,
    bids_root,
    freesurfer,
    num_t1w,
    output_dir,
    spaces,
    name='anat_derivatives_wf',
    tpm_labels=("GM", "WM", "CSF"),
):
    """
    Set up a battery of datasinks to store derivatives in the right location.
    Parameters
    ----------
    bids_root : :obj:`str`
        Root path of BIDS dataset
    freesurfer : :obj:`bool`
        FreeSurfer was enabled
    num_t1w : :obj:`int`
        Number of T1w images
    output_dir : :obj:`str`
        Directory in which to save derivatives
    name : :obj:`str`
        Workflow name (default: anat_derivatives_wf)
    tpm_labels : :obj:`tuple`
        Tissue probability maps in order
    Inputs
    ------
    template
        Template space and specifications
    source_files
        List of input T1w images
    t1w_ref_xfms
        List of affine transforms to realign input T1w images
    t1w_preproc
        The T1w reference map, which is calculated as the average of bias-corrected
        and preprocessed T1w images, defining the anatomical space.
    t1w_mask
        Mask of the ``t1w_preproc``
    t1w_dseg
        Segmentation in T1w space
    t1w_tpms
        Tissue probability maps in T1w space
    anat2std_xfm
        Nonlinear spatial transform to resample imaging data given in anatomical space
        into standard space.
    std2anat_xfm
        Inverse transform of ``anat2std_xfm``
    std_t1w
        T1w reference resampled in one or more standard spaces.
    std_mask
        Mask of skull-stripped template, in standard space
    std_dseg
        Segmentation, resampled into standard space
    std_tpms
        Tissue probability maps in standard space
    t1w2fsnative_xfm
        LTA-style affine matrix translating from T1w to
        FreeSurfer-conformed subject space
    fsnative2t1w_xfm
        LTA-style affine matrix translating from FreeSurfer-conformed
        subject space to T1w
    surfaces
        GIFTI surfaces (gray/white boundary, midthickness, pial, inflated)
    t1w_fs_aseg
        FreeSurfer's aseg segmentation, in native T1w space
    t1w_fs_aparc
        FreeSurfer's aparc+aseg segmentation, in native T1w space
    """
    from niworkflows.interfaces.utility import KeySelect
    from smriprep.interfaces import DerivativesDataSink
    from smriprep.workflows.outputs import (
        _bids_relative, _combine_cohort, _is_native, _drop_path
    )

    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',
                    't1w2fsnative_xfm', 'fsnative2t1w_xfm', 'surfaces',
                    't1w_fs_aseg', 't1w_fs_aparc']),
        name='inputnode')

    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', 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'

    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.label = tpm_labels

    workflow.connect([
        (inputnode, raw_sources, [('source_files', 'in_files')]),
        (inputnode, ds_t1w_preproc, [('t1w_preproc', 'in_file'),
                                     ('source_files', 'source_file')]),
        (inputnode, ds_t1w_mask, [('t1w_mask', 'in_file'),
                                  ('source_files', 'source_file')]),
        (inputnode, ds_t1w_tpms, [('t1w_tpms', 'in_file'),
                                  ('source_files', 'source_file')]),
        (inputnode, ds_t1w_dseg, [('t1w_dseg', 'in_file'),
                                  ('source_files', 'source_file')]),
        (raw_sources, ds_t1w_mask, [('out', 'RawSources')]),
    ])

    # Transforms
    if spaces.get_spaces(nonstandard=False, dim=(3,)):
        ds_std2t1w_xfm = pe.MapNode(
            DerivativesDataSink(base_directory=output_dir, to='T1w', mode='image', suffix='xfm'),
            iterfield=('in_file', 'from'),
            name='ds_std2t1w_xfm', run_without_submitting=True)

        ds_t1w2std_xfm = pe.MapNode(
            DerivativesDataSink(base_directory=output_dir, mode='image', suffix='xfm',
                                **{'from': 'T1w'}),
            iterfield=('in_file', 'to'),
            name='ds_t1w2std_xfm', run_without_submitting=True)

        workflow.connect([
            (inputnode, ds_t1w2std_xfm, [
                ('anat2std_xfm', 'in_file'),
                (('template', _combine_cohort), 'to'),
                ('source_files', 'source_file')]),
            (inputnode, ds_std2t1w_xfm, [
                ('std2anat_xfm', 'in_file'),
                (('template', _combine_cohort), 'from'),
                ('source_files', '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, to='T1w', mode='image', suffix='xfm',
                                extension='txt', **{'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')]),
        ])

    # Write derivatives in standard spaces specified by --output-spaces
    if getattr(spaces, '_cached') is not None and spaces.cached.references:
        from niworkflows.interfaces.space import SpaceDataSource
        from niworkflows.interfaces.utils import GenerateSamplingReference
        from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms

        spacesource = pe.Node(SpaceDataSource(),
                              name='spacesource', run_without_submitting=True)
        spacesource.iterables = ('in_tuple', [
            (s.fullname, s.spec) for s in spaces.cached.get_standard(dim=(3,))
        ])

        gen_tplid = pe.Node(niu.Function(function=_fmt_cohort),
                            name="gen_tplid", run_without_submitting=True)

        select_xfm = pe.Node(KeySelect(
            fields=['anat2std_xfm']),
            name='select_xfm', run_without_submitting=True)
        select_tpl = pe.Node(TemplateFlowSelect(), name='select_tpl', run_without_submitting=True)

        gen_ref = pe.Node(GenerateSamplingReference(), name='gen_ref', mem_gb=0.01)

        # Resample T1w-space inputs
        anat2std_t1w = pe.Node(ApplyTransforms(
            dimension=3, default_value=0, float=True,
            interpolation='LanczosWindowedSinc'), name='anat2std_t1w')

        anat2std_mask = pe.Node(ApplyTransforms(
            interpolation='MultiLabel'), name='anat2std_mask'
        )
        anat2std_dseg = pe.Node(ApplyTransforms(
            interpolation='MultiLabel'), name='anat2std_dseg'
        )
        anat2std_tpms = pe.MapNode(ApplyTransforms(
            dimension=3, default_value=0, float=True, interpolation='Gaussian'),
            iterfield=['input_image'], name='anat2std_tpms'
        )

        ds_std_t1w = pe.Node(
            DerivativesDataSink(base_directory=output_dir, desc='preproc', keep_dtype=True,
                                compress=True),
            name='ds_std_t1w', run_without_submitting=True)
        ds_std_t1w.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'

        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)

        # CRITICAL: the sequence of labels here (CSF-GM-WM) is that of the output of FSL-FAST
        #           (intensity mean, per tissue). This order HAS to be matched also by the ``tpms``
        #           output in the data/io_spec.json file.
        ds_std_tpms.inputs.label = tpm_labels
        workflow.connect([
            (inputnode, anat2std_t1w, [('t1w_preproc', 'input_image')]),
            (inputnode, anat2std_mask, [('t1w_mask', 'input_image')]),
            (inputnode, anat2std_dseg, [('t1w_dseg', 'input_image')]),
            (inputnode, anat2std_tpms, [('t1w_tpms', 'input_image')]),
            (inputnode, gen_ref, [('t1w_preproc', 'moving_image')]),
            (inputnode, select_xfm, [
                ('anat2std_xfm', 'anat2std_xfm'),
                ('template', 'keys')]),
            (spacesource, gen_tplid, [('space', 'template'),
                                      ('cohort', 'cohort')]),
            (gen_tplid, select_xfm, [('out', 'key')]),
            (spacesource, select_tpl, [('space', 'template'),
                                       ('cohort', 'cohort'),
                                       (('resolution', _no_native), 'resolution')]),
            (spacesource, gen_ref, [(('resolution', _is_native), 'keep_native')]),
            (select_tpl, gen_ref, [('t2w_file', 'fixed_image')]),
            (anat2std_t1w, ds_std_t1w, [('output_image', 'in_file')]),
            (anat2std_mask, ds_std_mask, [('output_image', 'in_file')]),
            (anat2std_dseg, ds_std_dseg, [('output_image', 'in_file')]),
            (anat2std_tpms, ds_std_tpms, [('output_image', 'in_file')]),
            (select_tpl, ds_std_mask, [(('brain_mask', _drop_path), 'RawSources')]),
        ])

        workflow.connect(
            # Connect apply transforms nodes
            [
                (gen_ref, n, [('out_file', 'reference_image')])
                for n in (anat2std_t1w, anat2std_mask, anat2std_dseg, anat2std_tpms)
            ]
            + [
                (select_xfm, n, [('anat2std_xfm', 'transforms')])
                for n in (anat2std_t1w, anat2std_mask, anat2std_dseg, anat2std_tpms)
            ]
            # Connect the source_file input of these datasinks
            + [
                (inputnode, n, [('source_files', 'source_file')])
                for n in (ds_std_t1w, ds_std_mask, ds_std_dseg, ds_std_tpms)
            ]
            # Connect the space input of these datasinks
            + [
                (spacesource, n, [
                    ('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution')
                ])
                for n in (ds_std_t1w, ds_std_mask, ds_std_dseg, ds_std_tpms)
            ]
        )

    return workflow
Esempio n. 7
0
def init_func_preproc_wf(bold_file):
    """
    This workflow controls the functional preprocessing stages of *fMRIPrep*.

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

            from fmriprep.workflows.tests import mock_config
            from fmriprep import config
            from fmriprep.workflows.bold.base import init_func_preproc_wf
            with mock_config():
                bold_file = config.execution.bids_dir / 'sub-01' / 'func' \
                    / 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz'
                wf = init_func_preproc_wf(str(bold_file))

    Parameters
    ----------
    bold_file
        BOLD series NIfTI file

    Inputs
    ------
    bold_file
        BOLD series NIfTI file
    t1w_preproc
        Bias-corrected structural template image
    t1w_mask
        Mask of the skull-stripped template image
    t1w_dseg
        Segmentation of preprocessed structural image, including
        gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF)
    t1w_asec
        Segmentation of structural image, done with FreeSurfer.
    t1w_aparc
        Parcellation of structural image, done with FreeSurfer.
    t1w_tpms
        List of tissue probability maps in T1w space
    template
        List of templates to target
    anat2std_xfm
        List of transform files, collated with templates
    std2anat_xfm
        List of inverse transform files, collated with templates
    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

    Outputs
    -------
    bold_t1
        BOLD series, resampled to T1w space
    bold_mask_t1
        BOLD series mask in T1w space
    bold_std
        BOLD series, resampled to template space
    bold_mask_std
        BOLD series mask in template space
    confounds
        TSV of confounds
    surfaces
        BOLD series, resampled to FreeSurfer surfaces
    aroma_noise_ics
        Noise components identified by ICA-AROMA
    melodic_mix
        FSL MELODIC mixing matrix
    bold_cifti
        BOLD CIFTI image
    cifti_variant
        combination of target spaces for `bold_cifti`

    See Also
    --------

    * :py:func:`~niworkflows.func.util.init_bold_reference_wf`
    * :py:func:`~fmriprep.workflows.bold.stc.init_bold_stc_wf`
    * :py:func:`~fmriprep.workflows.bold.hmc.init_bold_hmc_wf`
    * :py:func:`~fmriprep.workflows.bold.t2s.init_bold_t2s_wf`
    * :py:func:`~fmriprep.workflows.bold.registration.init_bold_t1_trans_wf`
    * :py:func:`~fmriprep.workflows.bold.registration.init_bold_reg_wf`
    * :py:func:`~fmriprep.workflows.bold.confounds.init_bold_confounds_wf`
    * :py:func:`~fmriprep.workflows.bold.confounds.init_ica_aroma_wf`
    * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_std_trans_wf`
    * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_preproc_trans_wf`
    * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_surf_wf`
    * :py:func:`~sdcflows.workflows.fmap.init_fmap_wf`
    * :py:func:`~sdcflows.workflows.pepolar.init_pepolar_unwarp_wf`
    * :py:func:`~sdcflows.workflows.phdiff.init_phdiff_wf`
    * :py:func:`~sdcflows.workflows.syn.init_syn_sdc_wf`
    * :py:func:`~sdcflows.workflows.unwarp.init_sdc_unwarp_wf`

    """
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.func.util import init_bold_reference_wf
    from niworkflows.interfaces.nibabel import ApplyMask
    from niworkflows.interfaces.utility import KeySelect
    from niworkflows.interfaces.utils import DictMerge
    from sdcflows.workflows.base import init_sdc_estimate_wf, fieldmap_wrangler

    ref_file = bold_file
    mem_gb = {'filesize': 1, 'resampled': 1, 'largemem': 1}
    bold_tlen = 10
    multiecho = isinstance(bold_file, list)

    # Have some options handy
    layout = config.execution.layout
    omp_nthreads = config.nipype.omp_nthreads
    freesurfer = config.workflow.run_reconall
    spaces = config.workflow.spaces

    if multiecho:
        tes = [layout.get_metadata(echo)['EchoTime'] for echo in bold_file]
        ref_file = dict(zip(tes, bold_file))[min(tes)]

    if os.path.isfile(ref_file):
        bold_tlen, mem_gb = _create_mem_gb(ref_file)

    wf_name = _get_wf_name(ref_file)
    config.loggers.workflow.debug(
        'Creating bold processing workflow for "%s" (%.2f GB / %d TRs). '
        'Memory resampled/largemem=%.2f/%.2f GB.', ref_file,
        mem_gb['filesize'], bold_tlen, mem_gb['resampled'], mem_gb['largemem'])

    sbref_file = None
    # Find associated sbref, if possible
    entities = layout.parse_file_entities(ref_file)
    entities['suffix'] = 'sbref'
    entities['extension'] = ['nii', 'nii.gz']  # Overwrite extensions
    files = layout.get(return_type='file', **entities)
    refbase = os.path.basename(ref_file)
    if 'sbref' in config.workflow.ignore:
        config.loggers.workflow.info("Single-band reference files ignored.")
    elif files and multiecho:
        config.loggers.workflow.warning(
            "Single-band reference found, but not supported in "
            "multi-echo workflows at this time. Ignoring.")
    elif files:
        sbref_file = files[0]
        sbbase = os.path.basename(sbref_file)
        if len(files) > 1:
            config.loggers.workflow.warning(
                "Multiple single-band reference files found for {}; using "
                "{}".format(refbase, sbbase))
        else:
            config.loggers.workflow.info(
                "Using single-band reference file %s.", sbbase)
    else:
        config.loggers.workflow.info("No single-band-reference found for %s.",
                                     refbase)

    metadata = layout.get_metadata(ref_file)

    # Find fieldmaps. Options: (phase1|phase2|phasediff|epi|fieldmap|syn)
    fmaps = None
    if 'fieldmaps' not in config.workflow.ignore:
        fmaps = fieldmap_wrangler(layout,
                                  ref_file,
                                  use_syn=config.workflow.use_syn,
                                  force_syn=config.workflow.force_syn)
    elif config.workflow.use_syn or config.workflow.force_syn:
        # If fieldmaps are not enabled, activate SyN-SDC in unforced (False) mode
        fmaps = {'syn': False}

    # Short circuits: (True and True and (False or 'TooShort')) == 'TooShort'
    run_stc = (bool(metadata.get("SliceTiming"))
               and 'slicetiming' not in config.workflow.ignore
               and (_get_series_len(ref_file) > 4 or "TooShort"))

    # Check if MEEPI for T2* coregistration target
    if config.workflow.t2s_coreg and not multiecho:
        config.loggers.workflow.warning(
            "No multiecho BOLD images found for T2* coregistration. "
            "Using standard EPI-T1 coregistration.")
        config.workflow.t2s_coreg = False

    # By default, force-bbr for t2s_coreg unless user specifies otherwise
    if config.workflow.t2s_coreg and config.workflow.use_bbr is None:
        config.workflow.use_bbr = True

    # Build workflow
    workflow = Workflow(name=wf_name)
    workflow.__postdesc__ = """\
All resamplings can be performed with *a single interpolation
step* by composing all the pertinent transformations (i.e. head-motion
transform matrices, susceptibility distortion correction when available,
and co-registrations to anatomical and output spaces).
Gridded (volumetric) resamplings were performed using `antsApplyTransforms` (ANTs),
configured with Lanczos interpolation to minimize the smoothing
effects of other kernels [@lanczos].
Non-gridded (surface) resamplings were performed using `mri_vol2surf`
(FreeSurfer).
"""

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'bold_file', 'subjects_dir', 'subject_id', 't1w_preproc', 't1w_mask',
        't1w_dseg', 't1w_tpms', 't1w_aseg', 't1w_aparc', 'anat2std_xfm',
        'std2anat_xfm', 'template', 't1w2fsnative_xfm', 'fsnative2t1w_xfm'
    ]),
                        name='inputnode')
    inputnode.inputs.bold_file = bold_file
    if sbref_file is not None:
        from niworkflows.interfaces.images import ValidateImage
        val_sbref = pe.Node(ValidateImage(in_file=sbref_file),
                            name='val_sbref')

    outputnode = pe.Node(niu.IdentityInterface(fields=[
        'bold_t1', 'bold_t1_ref', 'bold_mask_t1', 'bold_aseg_t1',
        'bold_aparc_t1', 'bold_std', 'bold_std_ref', 'bold_mask_std',
        'bold_aseg_std', 'bold_aparc_std', 'bold_native', 'bold_cifti',
        'cifti_variant', 'cifti_metadata', 'cifti_density', 'surfaces',
        'confounds', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file',
        'confounds_metadata'
    ]),
                         name='outputnode')

    # Generate a brain-masked conversion of the t1w
    t1w_brain = pe.Node(ApplyMask(), name='t1w_brain')

    # BOLD buffer: an identity used as a pointer to either the original BOLD
    # or the STC'ed one for further use.
    boldbuffer = pe.Node(niu.IdentityInterface(fields=['bold_file']),
                         name='boldbuffer')

    summary = pe.Node(FunctionalSummary(
        slice_timing=run_stc,
        registration=('FSL', 'FreeSurfer')[freesurfer],
        registration_dof=config.workflow.bold2t1w_dof,
        registration_init=config.workflow.bold2t1w_init,
        pe_direction=metadata.get("PhaseEncodingDirection"),
        tr=metadata.get("RepetitionTime")),
                      name='summary',
                      mem_gb=config.DEFAULT_MEMORY_MIN_GB,
                      run_without_submitting=True)
    summary.inputs.dummy_scans = config.workflow.dummy_scans

    func_derivatives_wf = init_func_derivatives_wf(
        bids_root=layout.root,
        cifti_output=config.workflow.cifti_output,
        freesurfer=freesurfer,
        metadata=metadata,
        output_dir=str(config.execution.output_dir),
        spaces=spaces,
        use_aroma=config.workflow.use_aroma,
    )

    workflow.connect([
        (outputnode, func_derivatives_wf, [
            ('bold_t1', 'inputnode.bold_t1'),
            ('bold_t1_ref', 'inputnode.bold_t1_ref'),
            ('bold_aseg_t1', 'inputnode.bold_aseg_t1'),
            ('bold_aparc_t1', 'inputnode.bold_aparc_t1'),
            ('bold_mask_t1', 'inputnode.bold_mask_t1'),
            ('bold_native', 'inputnode.bold_native'),
            ('confounds', 'inputnode.confounds'),
            ('surfaces', 'inputnode.surf_files'),
            ('aroma_noise_ics', 'inputnode.aroma_noise_ics'),
            ('melodic_mix', 'inputnode.melodic_mix'),
            ('nonaggr_denoised_file', 'inputnode.nonaggr_denoised_file'),
            ('bold_cifti', 'inputnode.bold_cifti'),
            ('cifti_variant', 'inputnode.cifti_variant'),
            ('cifti_metadata', 'inputnode.cifti_metadata'),
            ('cifti_density', 'inputnode.cifti_density'),
            ('confounds_metadata', 'inputnode.confounds_metadata'),
        ]),
    ])

    # Generate a tentative boldref
    bold_reference_wf = init_bold_reference_wf(omp_nthreads=omp_nthreads)
    bold_reference_wf.inputs.inputnode.dummy_scans = config.workflow.dummy_scans
    if sbref_file is not None:
        workflow.connect([
            (val_sbref, bold_reference_wf, [('out_file',
                                             'inputnode.sbref_file')]),
        ])

    # Top-level BOLD splitter
    bold_split = pe.Node(FSLSplit(dimension='t'),
                         name='bold_split',
                         mem_gb=mem_gb['filesize'] * 3)

    # HMC on the BOLD
    bold_hmc_wf = init_bold_hmc_wf(name='bold_hmc_wf',
                                   mem_gb=mem_gb['filesize'],
                                   omp_nthreads=omp_nthreads)

    # calculate BOLD registration to T1w
    bold_reg_wf = init_bold_reg_wf(name='bold_reg_wf',
                                   freesurfer=freesurfer,
                                   use_bbr=config.workflow.use_bbr,
                                   bold2t1w_dof=config.workflow.bold2t1w_dof,
                                   bold2t1w_init=config.workflow.bold2t1w_init,
                                   mem_gb=mem_gb['resampled'],
                                   omp_nthreads=omp_nthreads,
                                   use_compression=False)

    # apply BOLD registration to T1w
    bold_t1_trans_wf = init_bold_t1_trans_wf(name='bold_t1_trans_wf',
                                             freesurfer=freesurfer,
                                             use_fieldwarp=bool(fmaps),
                                             multiecho=multiecho,
                                             mem_gb=mem_gb['resampled'],
                                             omp_nthreads=omp_nthreads,
                                             use_compression=False)

    # get confounds
    bold_confounds_wf = init_bold_confs_wf(
        mem_gb=mem_gb['largemem'],
        metadata=metadata,
        regressors_all_comps=config.workflow.regressors_all_comps,
        regressors_fd_th=config.workflow.regressors_fd_th,
        regressors_dvars_th=config.workflow.regressors_dvars_th,
        name='bold_confounds_wf')
    bold_confounds_wf.get_node('inputnode').inputs.t1_transform_flags = [False]

    # Apply transforms in 1 shot
    # Only use uncompressed output if AROMA is to be run
    bold_bold_trans_wf = init_bold_preproc_trans_wf(
        mem_gb=mem_gb['resampled'],
        omp_nthreads=omp_nthreads,
        use_compression=not config.execution.low_mem,
        use_fieldwarp=bool(fmaps),
        name='bold_bold_trans_wf')
    bold_bold_trans_wf.inputs.inputnode.name_source = ref_file

    # SLICE-TIME CORRECTION (or bypass) #############################################
    if run_stc is True:  # bool('TooShort') == True, so check True explicitly
        bold_stc_wf = init_bold_stc_wf(name='bold_stc_wf', metadata=metadata)
        workflow.connect([
            (bold_reference_wf, bold_stc_wf, [('outputnode.skip_vols',
                                               'inputnode.skip_vols')]),
            (bold_stc_wf, boldbuffer, [('outputnode.stc_file', 'bold_file')]),
        ])
        if not multiecho:
            workflow.connect([(bold_reference_wf, bold_stc_wf, [
                ('outputnode.bold_file', 'inputnode.bold_file')
            ])])
        else:  # for meepi, iterate through stc_wf for all workflows
            meepi_echos = boldbuffer.clone(name='meepi_echos')
            meepi_echos.iterables = ('bold_file', bold_file)
            workflow.connect([(meepi_echos, bold_stc_wf,
                               [('bold_file', 'inputnode.bold_file')])])
    elif not multiecho:  # STC is too short or False
        # bypass STC from original BOLD to the splitter through boldbuffer
        workflow.connect([(bold_reference_wf, boldbuffer,
                           [('outputnode.bold_file', 'bold_file')])])
    else:
        # for meepi, iterate over all meepi echos to boldbuffer
        boldbuffer.iterables = ('bold_file', bold_file)

    # SDC (SUSCEPTIBILITY DISTORTION CORRECTION) or bypass ##########################
    bold_sdc_wf = init_sdc_estimate_wf(fmaps,
                                       metadata,
                                       omp_nthreads=omp_nthreads,
                                       debug=config.execution.debug)

    # MULTI-ECHO EPI DATA #############################################
    if multiecho:
        from niworkflows.func.util import init_skullstrip_bold_wf
        skullstrip_bold_wf = init_skullstrip_bold_wf(name='skullstrip_bold_wf')

        inputnode.inputs.bold_file = ref_file  # Replace reference w first echo

        join_echos = pe.JoinNode(
            niu.IdentityInterface(fields=['bold_files']),
            joinsource=('meepi_echos' if run_stc is True else 'boldbuffer'),
            joinfield=['bold_files'],
            name='join_echos')

        # create optimal combination, adaptive T2* map
        bold_t2s_wf = init_bold_t2s_wf(echo_times=tes,
                                       mem_gb=mem_gb['resampled'],
                                       omp_nthreads=omp_nthreads,
                                       t2s_coreg=config.workflow.t2s_coreg,
                                       name='bold_t2smap_wf')

        workflow.connect([
            (skullstrip_bold_wf, join_echos,
             [('outputnode.skull_stripped_file', 'bold_files')]),
            (join_echos, bold_t2s_wf, [('bold_files', 'inputnode.bold_file')]),
        ])

    # MAIN WORKFLOW STRUCTURE #######################################################
    workflow.connect([
        (inputnode, t1w_brain, [('t1w_preproc', 'in_file'),
                                ('t1w_mask', 'in_mask')]),
        # Generate early reference
        (inputnode, bold_reference_wf, [('bold_file', 'inputnode.bold_file')]),
        # BOLD buffer has slice-time corrected if it was run, original otherwise
        (boldbuffer, bold_split, [('bold_file', 'in_file')]),
        # HMC
        (bold_reference_wf, bold_hmc_wf,
         [('outputnode.raw_ref_image', 'inputnode.raw_ref_image'),
          ('outputnode.bold_file', 'inputnode.bold_file')]),
        (bold_reference_wf, summary, [('outputnode.algo_dummy_scans',
                                       'algo_dummy_scans')]),
        # EPI-T1 registration workflow
        (
            inputnode,
            bold_reg_wf,
            [
                ('t1w_dseg', 'inputnode.t1w_dseg'),
                # Undefined if --fs-no-reconall, but this is safe
                ('subjects_dir', 'inputnode.subjects_dir'),
                ('subject_id', 'inputnode.subject_id'),
                ('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm')
            ]),
        (t1w_brain, bold_reg_wf, [('out_file', 'inputnode.t1w_brain')]),
        (inputnode, bold_t1_trans_wf, [('bold_file', 'inputnode.name_source'),
                                       ('t1w_mask', 'inputnode.t1w_mask'),
                                       ('t1w_aseg', 'inputnode.t1w_aseg'),
                                       ('t1w_aparc', 'inputnode.t1w_aparc')]),
        (t1w_brain, bold_t1_trans_wf, [('out_file', 'inputnode.t1w_brain')]),
        # unused if multiecho, but this is safe
        (bold_hmc_wf, bold_t1_trans_wf, [('outputnode.xforms',
                                          'inputnode.hmc_xforms')]),
        (bold_reg_wf, bold_t1_trans_wf, [('outputnode.itk_bold_to_t1',
                                          'inputnode.itk_bold_to_t1')]),
        (bold_t1_trans_wf, outputnode,
         [('outputnode.bold_t1', 'bold_t1'),
          ('outputnode.bold_t1_ref', 'bold_t1_ref'),
          ('outputnode.bold_aseg_t1', 'bold_aseg_t1'),
          ('outputnode.bold_aparc_t1', 'bold_aparc_t1')]),
        (bold_reg_wf, summary, [('outputnode.fallback', 'fallback')]),
        # SDC (or pass-through workflow)
        (t1w_brain, bold_sdc_wf, [('out_file', 'inputnode.t1w_brain')]),
        (bold_reference_wf, bold_sdc_wf,
         [('outputnode.ref_image', 'inputnode.epi_file'),
          ('outputnode.ref_image_brain', 'inputnode.epi_brain'),
          ('outputnode.bold_mask', 'inputnode.epi_mask')]),
        (bold_sdc_wf, bold_t1_trans_wf, [('outputnode.out_warp',
                                          'inputnode.fieldwarp')]),
        (bold_sdc_wf, bold_bold_trans_wf,
         [('outputnode.out_warp', 'inputnode.fieldwarp'),
          ('outputnode.epi_mask', 'inputnode.bold_mask')]),
        (bold_sdc_wf, summary, [('outputnode.method', 'distortion_correction')
                                ]),
        # Connect bold_confounds_wf
        (inputnode, bold_confounds_wf, [('t1w_tpms', 'inputnode.t1w_tpms'),
                                        ('t1w_mask', 'inputnode.t1w_mask')]),
        (bold_hmc_wf, bold_confounds_wf, [('outputnode.movpar_file',
                                           'inputnode.movpar_file')]),
        (bold_reg_wf, bold_confounds_wf, [('outputnode.itk_t1_to_bold',
                                           'inputnode.t1_bold_xform')]),
        (bold_reference_wf, bold_confounds_wf, [('outputnode.skip_vols',
                                                 'inputnode.skip_vols')]),
        (bold_confounds_wf, outputnode, [
            ('outputnode.confounds_file', 'confounds'),
        ]),
        (bold_confounds_wf, outputnode, [
            ('outputnode.confounds_metadata', 'confounds_metadata'),
        ]),
        # Connect bold_bold_trans_wf
        (bold_split, bold_bold_trans_wf, [('out_files', 'inputnode.bold_file')]
         ),
        (bold_hmc_wf, bold_bold_trans_wf, [('outputnode.xforms',
                                            'inputnode.hmc_xforms')]),
        # Summary
        (outputnode, summary, [('confounds', 'confounds_file')]),
    ])

    if not config.workflow.t2s_coreg:
        workflow.connect([
            (bold_sdc_wf, bold_reg_wf, [('outputnode.epi_brain',
                                         'inputnode.ref_bold_brain')]),
            (bold_sdc_wf, bold_t1_trans_wf,
             [('outputnode.epi_brain', 'inputnode.ref_bold_brain'),
              ('outputnode.epi_mask', 'inputnode.ref_bold_mask')]),
        ])
    else:
        workflow.connect([
            # For t2s_coreg, replace EPI-to-T1w registration inputs
            (bold_t2s_wf, bold_reg_wf, [('outputnode.bold_ref_brain',
                                         'inputnode.ref_bold_brain')]),
            (bold_t2s_wf, bold_t1_trans_wf,
             [('outputnode.bold_ref_brain', 'inputnode.ref_bold_brain'),
              ('outputnode.bold_mask', 'inputnode.ref_bold_mask')]),
        ])

    # for standard EPI data, pass along correct file
    if not multiecho:
        workflow.connect([
            (inputnode, func_derivatives_wf, [('bold_file',
                                               'inputnode.source_file')]),
            (bold_bold_trans_wf, bold_confounds_wf,
             [('outputnode.bold', 'inputnode.bold'),
              ('outputnode.bold_mask', 'inputnode.bold_mask')]),
            (bold_split, bold_t1_trans_wf, [('out_files',
                                             'inputnode.bold_split')]),
        ])
    else:  # for meepi, create and use optimal combination
        workflow.connect([
            # update name source for optimal combination
            (inputnode, func_derivatives_wf,
             [(('bold_file', combine_meepi_source), 'inputnode.source_file')]),
            (bold_bold_trans_wf, skullstrip_bold_wf, [('outputnode.bold',
                                                       'inputnode.in_file')]),
            (bold_t2s_wf, bold_confounds_wf,
             [('outputnode.bold', 'inputnode.bold'),
              ('outputnode.bold_mask', 'inputnode.bold_mask')]),
            (bold_t2s_wf, bold_t1_trans_wf, [('outputnode.bold',
                                              'inputnode.bold_split')]),
        ])

    if fmaps:
        from sdcflows.workflows.outputs import init_sdc_unwarp_report_wf
        # Report on BOLD correction
        fmap_unwarp_report_wf = init_sdc_unwarp_report_wf()
        workflow.connect([
            (inputnode, fmap_unwarp_report_wf, [('t1w_dseg',
                                                 'inputnode.in_seg')]),
            (bold_reference_wf, fmap_unwarp_report_wf,
             [('outputnode.ref_image', 'inputnode.in_pre')]),
            (bold_reg_wf, fmap_unwarp_report_wf, [('outputnode.itk_t1_to_bold',
                                                   'inputnode.in_xfm')]),
            (bold_sdc_wf, fmap_unwarp_report_wf, [('outputnode.epi_corrected',
                                                   'inputnode.in_post')]),
        ])

        # Overwrite ``out_path_base`` of unwarping DataSinks
        for node in fmap_unwarp_report_wf.list_node_names():
            if node.split('.')[-1].startswith('ds_'):
                fmap_unwarp_report_wf.get_node(
                    node).interface.out_path_base = 'fmriprep'

        for node in bold_sdc_wf.list_node_names():
            if node.split('.')[-1].startswith('ds_'):
                bold_sdc_wf.get_node(node).interface.out_path_base = 'fmriprep'

        if 'syn' in fmaps:
            sdc_select_std = pe.Node(KeySelect(fields=['std2anat_xfm']),
                                     name='sdc_select_std',
                                     run_without_submitting=True)
            sdc_select_std.inputs.key = 'MNI152NLin2009cAsym'
            workflow.connect([
                (inputnode, sdc_select_std, [('std2anat_xfm', 'std2anat_xfm'),
                                             ('template', 'keys')]),
                (sdc_select_std, bold_sdc_wf, [('std2anat_xfm',
                                                'inputnode.std2anat_xfm')]),
            ])

        if fmaps.get('syn') is True:  # SyN forced
            syn_unwarp_report_wf = init_sdc_unwarp_report_wf(
                name='syn_unwarp_report_wf', forcedsyn=True)
            workflow.connect([
                (inputnode, syn_unwarp_report_wf, [('t1w_dseg',
                                                    'inputnode.in_seg')]),
                (bold_reference_wf, syn_unwarp_report_wf,
                 [('outputnode.ref_image', 'inputnode.in_pre')]),
                (bold_reg_wf, syn_unwarp_report_wf,
                 [('outputnode.itk_t1_to_bold', 'inputnode.in_xfm')]),
                (bold_sdc_wf, syn_unwarp_report_wf, [('outputnode.syn_ref',
                                                      'inputnode.in_post')]),
            ])

            # Overwrite ``out_path_base`` of unwarping DataSinks
            for node in syn_unwarp_report_wf.list_node_names():
                if node.split('.')[-1].startswith('ds_'):
                    syn_unwarp_report_wf.get_node(
                        node).interface.out_path_base = 'fmriprep'

    # Map final BOLD mask into T1w space (if required)
    nonstd_spaces = set(spaces.get_nonstandard())
    if nonstd_spaces.intersection(('T1w', 'anat')):
        from niworkflows.interfaces.fixes import (FixHeaderApplyTransforms as
                                                  ApplyTransforms)

        boldmask_to_t1w = pe.Node(ApplyTransforms(interpolation='MultiLabel',
                                                  float=True),
                                  name='boldmask_to_t1w',
                                  mem_gb=0.1)
        workflow.connect([
            (bold_reg_wf, boldmask_to_t1w, [('outputnode.itk_bold_to_t1',
                                             'transforms')]),
            (bold_t1_trans_wf, boldmask_to_t1w, [('outputnode.bold_mask_t1',
                                                  'reference_image')]),
            (bold_bold_trans_wf if not multiecho else bold_t2s_wf,
             boldmask_to_t1w, [('outputnode.bold_mask', 'input_image')]),
            (boldmask_to_t1w, outputnode, [('output_image', 'bold_mask_t1')]),
        ])

    if nonstd_spaces.intersection(('func', 'run', 'bold', 'boldref', 'sbref')):
        workflow.connect([
            (bold_bold_trans_wf, outputnode, [('outputnode.bold',
                                               'bold_native')]),
            (bold_bold_trans_wf, func_derivatives_wf,
             [('outputnode.bold_ref', 'inputnode.bold_native_ref'),
              ('outputnode.bold_mask', 'inputnode.bold_mask_native')]),
        ])

    if spaces.get_spaces(nonstandard=False, dim=(3, )):
        # Apply transforms in 1 shot
        # Only use uncompressed output if AROMA is to be run
        bold_std_trans_wf = init_bold_std_trans_wf(
            freesurfer=freesurfer,
            mem_gb=mem_gb['resampled'],
            omp_nthreads=omp_nthreads,
            spaces=spaces,
            name='bold_std_trans_wf',
            use_compression=not config.execution.low_mem,
            use_fieldwarp=bool(fmaps),
        )
        workflow.connect([
            (inputnode, bold_std_trans_wf,
             [('template', 'inputnode.templates'),
              ('anat2std_xfm', 'inputnode.anat2std_xfm'),
              ('bold_file', 'inputnode.name_source'),
              ('t1w_aseg', 'inputnode.bold_aseg'),
              ('t1w_aparc', 'inputnode.bold_aparc')]),
            (bold_hmc_wf, bold_std_trans_wf, [('outputnode.xforms',
                                               'inputnode.hmc_xforms')]),
            (bold_reg_wf, bold_std_trans_wf, [('outputnode.itk_bold_to_t1',
                                               'inputnode.itk_bold_to_t1')]),
            (bold_bold_trans_wf if not multiecho else bold_t2s_wf,
             bold_std_trans_wf, [('outputnode.bold_mask',
                                  'inputnode.bold_mask')]),
            (bold_sdc_wf, bold_std_trans_wf, [('outputnode.out_warp',
                                               'inputnode.fieldwarp')]),
            (bold_std_trans_wf, outputnode,
             [('outputnode.bold_std', 'bold_std'),
              ('outputnode.bold_std_ref', 'bold_std_ref'),
              ('outputnode.bold_mask_std', 'bold_mask_std')]),
        ])

        if freesurfer:
            workflow.connect([
                (bold_std_trans_wf, func_derivatives_wf, [
                    ('outputnode.bold_aseg_std', 'inputnode.bold_aseg_std'),
                    ('outputnode.bold_aparc_std', 'inputnode.bold_aparc_std'),
                ]),
                (bold_std_trans_wf, outputnode,
                 [('outputnode.bold_aseg_std', 'bold_aseg_std'),
                  ('outputnode.bold_aparc_std', 'bold_aparc_std')]),
            ])

        if not multiecho:
            workflow.connect([(bold_split, bold_std_trans_wf,
                               [('out_files', 'inputnode.bold_split')])])
        else:
            split_opt_comb = bold_split.clone(name='split_opt_comb')
            workflow.connect([(bold_t2s_wf, split_opt_comb,
                               [('outputnode.bold', 'in_file')]),
                              (split_opt_comb, bold_std_trans_wf,
                               [('out_files', 'inputnode.bold_split')])])

        # func_derivatives_wf internally parametrizes over snapshotted spaces.
        workflow.connect([
            (bold_std_trans_wf, func_derivatives_wf, [
                ('outputnode.template', 'inputnode.template'),
                ('outputnode.spatial_reference',
                 'inputnode.spatial_reference'),
                ('outputnode.bold_std_ref', 'inputnode.bold_std_ref'),
                ('outputnode.bold_std', 'inputnode.bold_std'),
                ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'),
            ]),
        ])

        if config.workflow.use_aroma:  # ICA-AROMA workflow
            from .confounds import init_ica_aroma_wf
            ica_aroma_wf = init_ica_aroma_wf(
                mem_gb=mem_gb['resampled'],
                metadata=metadata,
                omp_nthreads=omp_nthreads,
                use_fieldwarp=bool(fmaps),
                err_on_aroma_warn=config.workflow.aroma_err_on_warn,
                aroma_melodic_dim=config.workflow.aroma_melodic_dim,
                name='ica_aroma_wf')

            join = pe.Node(niu.Function(output_names=["out_file"],
                                        function=_to_join),
                           name='aroma_confounds')

            mrg_conf_metadata = pe.Node(niu.Merge(2),
                                        name='merge_confound_metadata',
                                        run_without_submitting=True)
            mrg_conf_metadata2 = pe.Node(DictMerge(),
                                         name='merge_confound_metadata2',
                                         run_without_submitting=True)
            workflow.disconnect([
                (bold_confounds_wf, outputnode, [
                    ('outputnode.confounds_file', 'confounds'),
                ]),
                (bold_confounds_wf, outputnode, [
                    ('outputnode.confounds_metadata', 'confounds_metadata'),
                ]),
            ])
            workflow.connect([
                (inputnode, ica_aroma_wf, [('bold_file',
                                            'inputnode.name_source')]),
                (bold_hmc_wf, ica_aroma_wf, [('outputnode.movpar_file',
                                              'inputnode.movpar_file')]),
                (bold_reference_wf, ica_aroma_wf, [('outputnode.skip_vols',
                                                    'inputnode.skip_vols')]),
                (bold_confounds_wf, join, [('outputnode.confounds_file',
                                            'in_file')]),
                (bold_confounds_wf, mrg_conf_metadata,
                 [('outputnode.confounds_metadata', 'in1')]),
                (ica_aroma_wf, join, [('outputnode.aroma_confounds',
                                       'join_file')]),
                (ica_aroma_wf, mrg_conf_metadata,
                 [('outputnode.aroma_metadata', 'in2')]),
                (mrg_conf_metadata, mrg_conf_metadata2, [('out', 'in_dicts')]),
                (ica_aroma_wf, outputnode,
                 [('outputnode.aroma_noise_ics', 'aroma_noise_ics'),
                  ('outputnode.melodic_mix', 'melodic_mix'),
                  ('outputnode.nonaggr_denoised_file', 'nonaggr_denoised_file')
                  ]),
                (join, outputnode, [('out_file', 'confounds')]),
                (mrg_conf_metadata2, outputnode, [('out_dict',
                                                   'confounds_metadata')]),
                (bold_std_trans_wf, ica_aroma_wf,
                 [('outputnode.bold_std', 'inputnode.bold_std'),
                  ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'),
                  ('outputnode.spatial_reference',
                   'inputnode.spatial_reference')]),
            ])

    # SURFACES ##################################################################################
    # Freesurfer
    freesurfer_spaces = spaces.get_fs_spaces()
    if freesurfer and freesurfer_spaces:
        config.loggers.workflow.debug(
            'Creating BOLD surface-sampling workflow.')
        bold_surf_wf = init_bold_surf_wf(
            mem_gb=mem_gb['resampled'],
            surface_spaces=freesurfer_spaces,
            medial_surface_nan=config.workflow.medial_surface_nan,
            name='bold_surf_wf')
        workflow.connect([
            (inputnode, bold_surf_wf,
             [('t1w_preproc', 'inputnode.t1w_preproc'),
              ('subjects_dir', 'inputnode.subjects_dir'),
              ('subject_id', 'inputnode.subject_id'),
              ('t1w2fsnative_xfm', 'inputnode.t1w2fsnative_xfm')]),
            (bold_t1_trans_wf, bold_surf_wf, [('outputnode.bold_t1',
                                               'inputnode.source_file')]),
            (bold_surf_wf, outputnode, [('outputnode.surfaces', 'surfaces')]),
            (bold_surf_wf, func_derivatives_wf, [('outputnode.target',
                                                  'inputnode.surf_refs')]),
        ])

        # CIFTI output
        if config.workflow.cifti_output:
            from .resampling import init_bold_grayords_wf
            bold_grayords_wf = init_bold_grayords_wf(
                grayord_density=config.workflow.cifti_output,
                mem_gb=mem_gb['resampled'],
                repetition_time=metadata['RepetitionTime'])

            workflow.connect([
                (inputnode, bold_grayords_wf, [('subjects_dir',
                                                'inputnode.subjects_dir')]),
                (bold_std_trans_wf, bold_grayords_wf,
                 [('outputnode.bold_std', 'inputnode.bold_std'),
                  ('outputnode.spatial_reference',
                   'inputnode.spatial_reference')]),
                (bold_surf_wf, bold_grayords_wf, [
                    ('outputnode.surfaces', 'inputnode.surf_files'),
                    ('outputnode.target', 'inputnode.surf_refs'),
                ]),
                (bold_grayords_wf, outputnode,
                 [('outputnode.cifti_bold', 'bold_cifti'),
                  ('outputnode.cifti_variant', 'cifti_variant'),
                  ('outputnode.cifti_metadata', 'cifti_metadata'),
                  ('outputnode.cifti_density', 'cifti_density')]),
            ])

    if spaces.get_spaces(nonstandard=False, dim=(3, )):
        carpetplot_wf = init_carpetplot_wf(
            mem_gb=mem_gb['resampled'],
            metadata=metadata,
            cifti_output=config.workflow.cifti_output,
            name='carpetplot_wf')

        if config.workflow.cifti_output:
            workflow.connect(bold_grayords_wf, 'outputnode.cifti_bold',
                             carpetplot_wf, 'inputnode.cifti_bold')
        else:
            # Xform to 'MNI152NLin2009cAsym' is always computed.
            carpetplot_select_std = pe.Node(KeySelect(
                fields=['std2anat_xfm'], key='MNI152NLin2009cAsym'),
                                            name='carpetplot_select_std',
                                            run_without_submitting=True)

            workflow.connect([
                (inputnode, carpetplot_select_std, [('std2anat_xfm',
                                                     'std2anat_xfm'),
                                                    ('template', 'keys')]),
                (carpetplot_select_std, carpetplot_wf,
                 [('std2anat_xfm', 'inputnode.std2anat_xfm')]),
                (bold_bold_trans_wf if not multiecho else bold_t2s_wf,
                 carpetplot_wf, [('outputnode.bold', 'inputnode.bold'),
                                 ('outputnode.bold_mask',
                                  'inputnode.bold_mask')]),
                (bold_reg_wf, carpetplot_wf, [('outputnode.itk_t1_to_bold',
                                               'inputnode.t1_bold_xform')]),
            ])

        workflow.connect([(bold_confounds_wf, carpetplot_wf, [
            ('outputnode.confounds_file', 'inputnode.confounds_file')
        ])])

    # REPORTING ############################################################
    reportlets_dir = str(config.execution.work_dir / 'reportlets')
    ds_report_summary = pe.Node(DerivativesDataSink(desc='summary',
                                                    keep_dtype=True),
                                name='ds_report_summary',
                                run_without_submitting=True,
                                mem_gb=config.DEFAULT_MEMORY_MIN_GB)

    ds_report_validation = pe.Node(DerivativesDataSink(
        base_directory=reportlets_dir, desc='validation', keep_dtype=True),
                                   name='ds_report_validation',
                                   run_without_submitting=True,
                                   mem_gb=config.DEFAULT_MEMORY_MIN_GB)

    workflow.connect([
        (summary, ds_report_summary, [('out_report', 'in_file')]),
        (bold_reference_wf, ds_report_validation,
         [('outputnode.validation_report', 'in_file')]),
    ])

    # Fill-in datasinks of reportlets seen so far
    for node in workflow.list_node_names():
        if node.split('.')[-1].startswith('ds_report'):
            workflow.get_node(node).inputs.base_directory = reportlets_dir
            workflow.get_node(node).inputs.source_file = ref_file

    return workflow
Esempio n. 8
0
def init_func_derivatives_wf(
    bids_root,
    metadata,
    output_dir,
    spaces,
    use_aroma,
    name="func_derivatives_wf",
):
    """
    Set up a battery of datasinks to store derivatives in the right location.

    Parameters
    ----------
    bids_root : :obj:`str`
        Original BIDS dataset path.
    metadata : :obj:`dict`
        Metadata dictionary associated to the BOLD run.
    output_dir : :obj:`str`
        Where derivatives should be written out to.
    spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences`
        A container for storing, organizing, and parsing spatial normalizations. Composed of
        :py:class:`~niworkflows.utils.spaces.Reference` objects representing spatial references.
        Each ``Reference`` contains a space, which is a string of either TemplateFlow template IDs
        (e.g., ``MNI152Lin``, ``MNI152NLin6Asym``, ``MNIPediatricAsym``), nonstandard references
        (e.g., ``T1w`` or ``anat``, ``sbref``, ``run``, etc.), or a custom template located in
        the TemplateFlow root directory. Each ``Reference`` may also contain a spec, which is a
        dictionary with template specifications (e.g., a specification of ``{'resolution': 2}``
        would lead to resampling on a 2mm resolution of the space).
    use_aroma : :obj:`bool`
        Whether ``--use-aroma`` flag was set.
    name : :obj:`str`
        This workflow's identifier (default: ``func_derivatives_wf``).

    """
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.interfaces.utility import KeySelect
    from smriprep.workflows.outputs import _bids_relative

    nonstd_spaces = set(spaces.get_nonstandard())
    workflow = Workflow(name=name)

    inputnode = pe.Node(
        niu.IdentityInterface(fields=[
            "aroma_noise_ics",
            "bold_mask_std",
            "bold_mask_t1",
            "bold_std",
            "bold_std_ref",
            "bold_t1",
            "bold_t1_ref",
            "bold_native",
            "bold_native_ref",
            "bold_mask_native",
            "confounds",
            "confounds_metadata",
            "melodic_mix",
            "nonaggr_denoised_file",
            "source_file",
            "surf_files",
            "surf_refs",
            "template",
            "spatial_reference",
        ]),
        name="inputnode",
    )

    raw_sources = pe.Node(niu.Function(function=_bids_relative),
                          name="raw_sources")
    raw_sources.inputs.bids_root = bids_root

    ds_confounds = pe.Node(
        DerivativesDataSink(
            base_directory=output_dir,
            desc="confounds",
            suffix="regressors",
            dismiss_entities=("echo", ),
        ),
        name="ds_confounds",
        run_without_submitting=True,
        mem_gb=DEFAULT_MEMORY_MIN_GB,
    )

    # fmt:off
    workflow.connect([
        (inputnode, raw_sources, [('source_file', 'in_files')]),
        (inputnode, ds_confounds, [('source_file', 'source_file'),
                                   ('confounds', 'in_file'),
                                   ('confounds_metadata', 'meta_dict')]),
    ])
    # fmt:on

    if nonstd_spaces.intersection(("func", "run", "bold", "boldref", "sbref")):
        ds_bold_native = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                desc="preproc",
                compress=True,
                SkullStripped=False,
                RepetitionTime=metadata.get("RepetitionTime"),
                TaskName=metadata.get("TaskName"),
                dismiss_entities=("echo", ),
            ),
            name="ds_bold_native",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )
        ds_bold_native_ref = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                suffix="boldref",
                compress=True,
                dismiss_entities=("echo", ),
            ),
            name="ds_bold_native_ref",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )
        ds_bold_mask_native = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                desc="brain",
                suffix="mask",
                compress=True,
                dismiss_entities=("echo", ),
            ),
            name="ds_bold_mask_native",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )

        # fmt:off
        workflow.connect([
            (inputnode, ds_bold_native, [('source_file', 'source_file'),
                                         ('bold_native', 'in_file')]),
            (inputnode, ds_bold_native_ref, [('source_file', 'source_file'),
                                             ('bold_native_ref', 'in_file')]),
            (inputnode, ds_bold_mask_native, [('source_file', 'source_file'),
                                              ('bold_mask_native', 'in_file')
                                              ]),
            (raw_sources, ds_bold_mask_native, [('out', 'RawSources')]),
        ])
        # fmt:on

    # Resample to T1w space
    if nonstd_spaces.intersection(("T1w", "anat")):
        ds_bold_t1 = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                space="T1w",
                desc="preproc",
                compress=True,
                SkullStripped=False,
                RepetitionTime=metadata.get("RepetitionTime"),
                TaskName=metadata.get("TaskName"),
                dismiss_entities=("echo", ),
            ),
            name="ds_bold_t1",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )
        ds_bold_t1_ref = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                space="T1w",
                suffix="boldref",
                compress=True,
                dismiss_entities=("echo", ),
            ),
            name="ds_bold_t1_ref",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )

        ds_bold_mask_t1 = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                space="T1w",
                desc="brain",
                suffix="mask",
                compress=True,
                dismiss_entities=("echo", ),
            ),
            name="ds_bold_mask_t1",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )

        # fmt:off
        workflow.connect([
            (inputnode, ds_bold_t1, [('source_file', 'source_file'),
                                     ('bold_t1', 'in_file')]),
            (inputnode, ds_bold_t1_ref, [('source_file', 'source_file'),
                                         ('bold_t1_ref', 'in_file')]),
            (inputnode, ds_bold_mask_t1, [('source_file', 'source_file'),
                                          ('bold_mask_t1', 'in_file')]),
            (raw_sources, ds_bold_mask_t1, [('out', 'RawSources')]),
        ])
        # fmt:on

    if use_aroma:
        ds_aroma_noise_ics = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                suffix="AROMAnoiseICs",
                dismiss_entities=("echo", ),
            ),
            name="ds_aroma_noise_ics",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )
        ds_melodic_mix = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                desc="MELODIC",
                suffix="mixing",
                dismiss_entities=("echo", ),
            ),
            name="ds_melodic_mix",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )
        ds_aroma_std = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                space="MNI152NLin6Asym",
                desc="smoothAROMAnonaggr",
                compress=True,
            ),
            name="ds_aroma_std",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )

        # fmt:off
        workflow.connect([
            (inputnode, ds_aroma_noise_ics, [('source_file', 'source_file'),
                                             ('aroma_noise_ics', 'in_file')]),
            (inputnode, ds_melodic_mix, [('source_file', 'source_file'),
                                         ('melodic_mix', 'in_file')]),
            (inputnode, ds_aroma_std, [('source_file', 'source_file'),
                                       ('nonaggr_denoised_file', 'in_file')]),
        ])
        # fmt:on

    if getattr(spaces, "_cached") is None:
        return workflow

    # Store resamplings in standard spaces when listed in --output-spaces
    if spaces.cached.references:
        from niworkflows.interfaces.space import SpaceDataSource

        spacesource = pe.Node(SpaceDataSource(),
                              name="spacesource",
                              run_without_submitting=True)
        spacesource.iterables = (
            "in_tuple",
            [(s.fullname, s.spec)
             for s in spaces.cached.get_standard(dim=(3, ))],
        )

        select_std = pe.Node(
            KeySelect(fields=[
                "template", "bold_std", "bold_std_ref", "bold_mask_std"
            ]),
            name="select_std",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )

        ds_bold_std = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                desc="preproc",
                compress=True,
                SkullStripped=False,
                RepetitionTime=metadata.get("RepetitionTime"),
                TaskName=metadata.get("TaskName"),
                dismiss_entities=("echo", ),
            ),
            name="ds_bold_std",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )
        ds_bold_std_ref = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                suffix="boldref",
                compress=True,
                dismiss_entities=("echo", ),
            ),
            name="ds_bold_std_ref",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )
        ds_bold_mask_std = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                desc="brain",
                suffix="mask",
                compress=True,
                dismiss_entities=("echo", ),
            ),
            name="ds_bold_mask_std",
            run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB,
        )

        # fmt:off
        workflow.connect([
            (inputnode, ds_bold_std, [('source_file', 'source_file')]),
            (inputnode, ds_bold_std_ref, [('source_file', 'source_file')]),
            (inputnode, ds_bold_mask_std, [('source_file', 'source_file')]),
            (inputnode, select_std, [('bold_std', 'bold_std'),
                                     ('bold_std_ref', 'bold_std_ref'),
                                     ('bold_mask_std', 'bold_mask_std'),
                                     ('template', 'template'),
                                     ('spatial_reference', 'keys')]),
            (spacesource, select_std, [('uid', 'key')]),
            (select_std, ds_bold_std, [('bold_std', 'in_file')]),
            (spacesource, ds_bold_std, [('space', 'space'),
                                        ('cohort', 'cohort'),
                                        ('resolution', 'resolution'),
                                        ('density', 'density')]),
            (select_std, ds_bold_std_ref, [('bold_std_ref', 'in_file')]),
            (spacesource, ds_bold_std_ref, [('space', 'space'),
                                            ('cohort', 'cohort'),
                                            ('resolution', 'resolution'),
                                            ('density', 'density')]),
            (select_std, ds_bold_mask_std, [('bold_mask_std', 'in_file')]),
            (spacesource, ds_bold_mask_std, [('space', 'space'),
                                             ('cohort', 'cohort'),
                                             ('resolution', 'resolution'),
                                             ('density', 'density')]),
            (raw_sources, ds_bold_mask_std, [('out', 'RawSources')]),
        ])
        # fmt:on

    return workflow
Esempio n. 9
0
def init_func_preproc_wf(bold_file, has_fieldmap=False):
    """
    This workflow controls the functional preprocessing stages of *fMRIPrep*.

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

            from fmriprep.workflows.tests import mock_config
            from fmriprep import config
            from fmriprep.workflows.bold.base import init_func_preproc_wf
            with mock_config():
                bold_file = config.execution.bids_dir / 'sub-01' / 'func' \
                    / 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz'
                wf = init_func_preproc_wf(str(bold_file))

    Parameters
    ----------
    bold_file
        BOLD series NIfTI file
    has_fieldmap
        Signals the workflow to use inputnode fieldmap files

    Inputs
    ------
    bold_file
        BOLD series NIfTI file
    t1w_preproc
        Bias-corrected structural template image
    t1w_mask
        Mask of the skull-stripped template image
    t1w_dseg
        Segmentation of preprocessed structural image, including
        gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF)
    t1w_asec
        Segmentation of structural image, done with FreeSurfer.
    t1w_aparc
        Parcellation of structural image, done with FreeSurfer.
    t1w_tpms
        List of tissue probability maps in T1w space
    template
        List of templates to target
    anat2std_xfm
        List of transform files, collated with templates
    std2anat_xfm
        List of inverse transform files, collated with templates
    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
    bold_ref
        BOLD reference file
    bold_ref_xfm
        Transform file in LTA format from bold to reference
    n_dummy_scans
        Number of nonsteady states at the beginning of the BOLD run

    Outputs
    -------
    bold_t1
        BOLD series, resampled to T1w space
    bold_mask_t1
        BOLD series mask in T1w space
    bold_std
        BOLD series, resampled to template space
    bold_mask_std
        BOLD series mask in template space
    confounds
        TSV of confounds
    surfaces
        BOLD series, resampled to FreeSurfer surfaces
    aroma_noise_ics
        Noise components identified by ICA-AROMA
    melodic_mix
        FSL MELODIC mixing matrix
    bold_cifti
        BOLD CIFTI image
    cifti_variant
        combination of target spaces for `bold_cifti`

    See Also
    --------

    * :py:func:`~niworkflows.func.util.init_bold_reference_wf`
    * :py:func:`~fmriprep.workflows.bold.stc.init_bold_stc_wf`
    * :py:func:`~fmriprep.workflows.bold.hmc.init_bold_hmc_wf`
    * :py:func:`~fmriprep.workflows.bold.t2s.init_bold_t2s_wf`
    * :py:func:`~fmriprep.workflows.bold.registration.init_bold_t1_trans_wf`
    * :py:func:`~fmriprep.workflows.bold.registration.init_bold_reg_wf`
    * :py:func:`~fmriprep.workflows.bold.confounds.init_bold_confs_wf`
    * :py:func:`~fmriprep.workflows.bold.confounds.init_ica_aroma_wf`
    * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_std_trans_wf`
    * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_preproc_trans_wf`
    * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_surf_wf`
    * :py:func:`~sdcflows.workflows.fmap.init_fmap_wf`
    * :py:func:`~sdcflows.workflows.pepolar.init_pepolar_unwarp_wf`
    * :py:func:`~sdcflows.workflows.phdiff.init_phdiff_wf`
    * :py:func:`~sdcflows.workflows.syn.init_syn_sdc_wf`
    * :py:func:`~sdcflows.workflows.unwarp.init_sdc_unwarp_wf`

    """
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.interfaces.utility import DictMerge

    mem_gb = {'filesize': 1, 'resampled': 1, 'largemem': 1}
    bold_tlen = 10

    # Have some options handy
    omp_nthreads = config.nipype.omp_nthreads
    freesurfer = config.workflow.run_reconall
    spaces = config.workflow.spaces
    nibabies_dir = str(config.execution.nibabies_dir)

    # Extract BIDS entities and metadata from BOLD file(s)
    entities = extract_entities(bold_file)
    layout = config.execution.layout

    # Take first file as reference
    ref_file = pop_file(bold_file)
    metadata = layout.get_metadata(ref_file)
    # get original image orientation
    ref_orientation = get_img_orientation(ref_file)

    echo_idxs = listify(entities.get("echo", []))
    multiecho = len(echo_idxs) > 2
    if len(echo_idxs) == 1:
        config.loggers.workflow.warning(
            f"Running a single echo <{ref_file}> from a seemingly multi-echo dataset."
        )
        bold_file = ref_file  # Just in case - drop the list

    if len(echo_idxs) == 2:
        raise RuntimeError(
            "Multi-echo processing requires at least three different echos (found two)."
        )

    if multiecho:
        # Drop echo entity for future queries, have a boolean shorthand
        entities.pop("echo", None)
        # reorder echoes from shortest to largest
        tes, bold_file = zip(*sorted([(layout.get_metadata(bf)["EchoTime"], bf)
                                      for bf in bold_file]))
        ref_file = bold_file[0]  # Reset reference to be the shortest TE

    if os.path.isfile(ref_file):
        bold_tlen, mem_gb = _create_mem_gb(ref_file)

    wf_name = _get_wf_name(ref_file)
    config.loggers.workflow.debug(
        f'Creating bold processing workflow for <{ref_file}> ({mem_gb["filesize"]:.2f} GB '
        f'/ {bold_tlen} TRs). Memory resampled/largemem={mem_gb["resampled"]:.2f}'
        f'/{mem_gb["largemem"]:.2f} GB.')

    # Find associated sbref, if possible
    entities['suffix'] = 'sbref'
    entities['extension'] = ['.nii', '.nii.gz']  # Overwrite extensions
    sbref_files = layout.get(return_type='file', **entities)

    sbref_msg = f"No single-band-reference found for {os.path.basename(ref_file)}."
    if sbref_files and 'sbref' in config.workflow.ignore:
        sbref_msg = "Single-band reference file(s) found and ignored."
    elif sbref_files:
        sbref_msg = "Using single-band reference file(s) {}.".format(','.join(
            [os.path.basename(sbf) for sbf in sbref_files]))
    config.loggers.workflow.info(sbref_msg)

    if has_fieldmap:
        # Search for intended fieldmap
        from pathlib import Path
        import re
        from sdcflows.fieldmaps import get_identifier

        bold_rel = re.sub(r"^sub-[a-zA-Z0-9]*/", "",
                          str(Path(bold_file).relative_to(layout.root)))
        estimator_key = get_identifier(bold_rel)
        if not estimator_key:
            has_fieldmap = False
            config.loggers.workflow.critical(
                f"None of the available B0 fieldmaps are associated to <{bold_rel}>"
            )

    # Short circuits: (True and True and (False or 'TooShort')) == 'TooShort'
    run_stc = (bool(metadata.get("SliceTiming"))
               and 'slicetiming' not in config.workflow.ignore
               and (_get_series_len(ref_file) > 4 or "TooShort"))

    # Build workflow
    workflow = Workflow(name=wf_name)
    workflow.__postdesc__ = """\
All resamplings can be performed with *a single interpolation
step* by composing all the pertinent transformations (i.e. head-motion
transform matrices, susceptibility distortion correction when available,
and co-registrations to anatomical and output spaces).
Gridded (volumetric) resamplings were performed using `antsApplyTransforms` (ANTs),
configured with Lanczos interpolation to minimize the smoothing
effects of other kernels [@lanczos].
Non-gridded (surface) resamplings were performed using `mri_vol2surf`
(FreeSurfer).
"""

    inputnode = pe.Node(
        niu.IdentityInterface(fields=[
            'bold_file',
            # from smriprep
            'anat_preproc',
            'anat_brain',
            'anat_mask',
            'anat_dseg',
            'anat_tpms',
            'anat_aseg',
            'anat_aparc',
            'anat2std_xfm',
            'std2anat_xfm',
            'template',
            # from bold reference workflow
            'bold_ref',
            'bold_ref_xfm',
            'n_dummy_scans',
            # from sdcflows (optional)
            'fmap',
            'fmap_ref',
            'fmap_coeff',
            'fmap_mask',
            'fmap_id',
            # if reconstructing with FreeSurfer (optional)
            'anat2fsnative_xfm',
            'fsnative2anat_xfm',
            'subject_id',
            'subjects_dir',
        ]),
        name='inputnode')
    inputnode.inputs.bold_file = bold_file

    outputnode = pe.Node(niu.IdentityInterface(fields=[
        'bold_anat', 'bold_anat_ref', 'bold2anat_xfm', 'anat2bold_xfm',
        'bold_mask_anat', 'bold_aseg_anat', 'bold_aparc_anat', 'bold_std',
        'bold_std_ref', 'bold_mask_std', 'bold_aseg_std', 'bold_aparc_std',
        'bold_native', 'bold_cifti', 'cifti_variant', 'cifti_metadata',
        'cifti_density', 'surfaces', 'confounds', 'aroma_noise_ics',
        'melodic_mix', 'nonaggr_denoised_file', 'confounds_metadata'
    ]),
                         name='outputnode')

    # BOLD buffer: an identity used as a pointer to either the original BOLD
    # or the STC'ed one for further use.
    boldbuffer = pe.Node(niu.IdentityInterface(fields=['bold_file']),
                         name='boldbuffer')

    summary = pe.Node(FunctionalSummary(
        slice_timing=run_stc,
        registration=('FSL', 'FreeSurfer')[freesurfer],
        registration_dof=config.workflow.bold2t1w_dof,
        registration_init=config.workflow.bold2t1w_init,
        pe_direction=metadata.get("PhaseEncodingDirection"),
        echo_idx=echo_idxs,
        tr=metadata.get("RepetitionTime"),
        orientation=ref_orientation),
                      name='summary',
                      mem_gb=config.DEFAULT_MEMORY_MIN_GB,
                      run_without_submitting=True)
    summary.inputs.dummy_scans = config.workflow.dummy_scans
    # TODO: SDC: make dynamic
    summary.inputs.distortion_correction = 'None' if not has_fieldmap else 'TOPUP'

    func_derivatives_wf = init_func_derivatives_wf(
        bids_root=layout.root,
        cifti_output=config.workflow.cifti_output,
        freesurfer=freesurfer,
        metadata=metadata,
        output_dir=nibabies_dir,
        spaces=spaces,
        use_aroma=config.workflow.use_aroma,
        debug=config.execution.debug,
    )

    workflow.connect([
        (outputnode, func_derivatives_wf, [
            ('bold_anat', 'inputnode.bold_t1'),
            ('bold_anat_ref', 'inputnode.bold_t1_ref'),
            ('bold2anat_xfm', 'inputnode.bold2anat_xfm'),
            ('anat2bold_xfm', 'inputnode.anat2bold_xfm'),
            ('bold_aseg_anat', 'inputnode.bold_aseg_t1'),
            ('bold_aparc_anat', 'inputnode.bold_aparc_t1'),
            ('bold_mask_anat', 'inputnode.bold_mask_t1'),
            ('bold_native', 'inputnode.bold_native'),
            ('confounds', 'inputnode.confounds'),
            ('surfaces', 'inputnode.surf_files'),
            ('aroma_noise_ics', 'inputnode.aroma_noise_ics'),
            ('melodic_mix', 'inputnode.melodic_mix'),
            ('nonaggr_denoised_file', 'inputnode.nonaggr_denoised_file'),
            ('bold_cifti', 'inputnode.bold_cifti'),
            ('cifti_variant', 'inputnode.cifti_variant'),
            ('cifti_metadata', 'inputnode.cifti_metadata'),
            ('cifti_density', 'inputnode.cifti_density'),
            ('confounds_metadata', 'inputnode.confounds_metadata'),
            ('acompcor_masks', 'inputnode.acompcor_masks'),
            ('tcompcor_mask', 'inputnode.tcompcor_mask'),
        ]),
    ])

    # Extract BOLD validation from init_bold_reference_wf
    val_bold = pe.MapNode(
        ValidateImage(),
        name="val_bold",
        mem_gb=config.DEFAULT_MEMORY_MIN_GB,
        iterfield=["in_file"],
    )
    val_bold.inputs.in_file = listify(bold_file)

    # Top-level BOLD splitter
    bold_split = pe.Node(FSLSplit(dimension='t'),
                         name='bold_split',
                         mem_gb=mem_gb['filesize'] * 3)

    # HMC on the BOLD
    bold_hmc_wf = init_bold_hmc_wf(name='bold_hmc_wf',
                                   mem_gb=mem_gb['filesize'],
                                   omp_nthreads=omp_nthreads)

    # calculate BOLD registration to T1w
    bold_reg_wf = init_bold_reg_wf(
        bold2t1w_dof=config.workflow.bold2t1w_dof,
        bold2t1w_init=config.workflow.bold2t1w_init,
        freesurfer=freesurfer,
        mem_gb=mem_gb['resampled'],
        name='bold_reg_wf',
        omp_nthreads=omp_nthreads,
        sloppy=config.execution.sloppy,
        use_bbr=config.workflow.use_bbr,
    )

    # apply BOLD registration to T1w
    bold_t1_trans_wf = init_bold_t1_trans_wf(name='bold_t1_trans_wf',
                                             freesurfer=freesurfer,
                                             mem_gb=mem_gb['resampled'],
                                             omp_nthreads=omp_nthreads,
                                             use_compression=False)
    if not has_fieldmap:
        bold_t1_trans_wf.inputs.inputnode.fieldwarp = 'identity'

    # get confounds
    bold_confounds_wf = init_bold_confs_wf(
        mem_gb=mem_gb['largemem'],
        metadata=metadata,
        freesurfer=freesurfer,
        regressors_all_comps=config.workflow.regressors_all_comps,
        regressors_fd_th=config.workflow.regressors_fd_th,
        regressors_dvars_th=config.workflow.regressors_dvars_th,
        name='bold_confounds_wf')
    bold_confounds_wf.get_node('inputnode').inputs.t1_transform_flags = [False]

    # Apply transforms in 1 shot
    # Only use uncompressed output if AROMA is to be run
    bold_bold_trans_wf = init_bold_preproc_trans_wf(
        mem_gb=mem_gb['resampled'],
        omp_nthreads=omp_nthreads,
        use_compression=not config.execution.low_mem,
        use_fieldwarp=False,  # TODO: Fieldwarp is already applied in new sdcflow
        name='bold_bold_trans_wf')
    bold_bold_trans_wf.inputs.inputnode.name_source = ref_file

    # SLICE-TIME CORRECTION (or bypass) #############################################
    if run_stc is True:  # bool('TooShort') == True, so check True explicitly
        bold_stc_wf = init_bold_stc_wf(name='bold_stc_wf', metadata=metadata)
        workflow.connect([
            (inputnode, bold_stc_wf, [('n_dummy_scans', 'inputnode.skip_vols')
                                      ]),
            (bold_stc_wf, boldbuffer, [('outputnode.stc_file', 'bold_file')]),
        ])
        if not multiecho:
            workflow.connect([(val_bold, bold_stc_wf, [
                (("out_file", pop_file), 'inputnode.bold_file')
            ])])
        else:  # for meepi, iterate through stc_wf for all workflows
            meepi_echos = boldbuffer.clone(name='meepi_echos')
            meepi_echos.iterables = ('bold_file', bold_file)
            workflow.connect([(meepi_echos, bold_stc_wf,
                               [('bold_file', 'inputnode.bold_file')])])
    elif not multiecho:  # STC is too short or False
        # bypass STC from original BOLD to the splitter through boldbuffer
        workflow.connect([(val_bold, boldbuffer, [(("out_file", pop_file),
                                                   'bold_file')])])
    else:
        # for meepi, iterate over all meepi echos to boldbuffer
        boldbuffer.iterables = ('bold_file', bold_file)

    # MULTI-ECHO EPI DATA #############################################
    if multiecho:  # instantiate relevant interfaces, imports
        from niworkflows.func.util import init_skullstrip_bold_wf
        skullstrip_bold_wf = init_skullstrip_bold_wf(name='skullstrip_bold_wf')

        split_opt_comb = bold_split.clone(name='split_opt_comb')

        inputnode.inputs.bold_file = ref_file  # Replace reference w first echo

        join_echos = pe.JoinNode(
            niu.IdentityInterface(
                fields=['bold_files', 'skullstripped_bold_files']),
            joinsource=('meepi_echos' if run_stc is True else 'boldbuffer'),
            joinfield=['bold_files', 'skullstripped_bold_files'],
            name='join_echos')

        # create optimal combination, adaptive T2* map
        bold_t2s_wf = init_bold_t2s_wf(echo_times=tes,
                                       mem_gb=mem_gb['resampled'],
                                       omp_nthreads=omp_nthreads,
                                       name='bold_t2smap_wf')

    # Mask BOLD reference image
    final_boldref_masker = pe.Node(BrainExtraction(),
                                   name='final_boldref_masker')

    # MAIN WORKFLOW STRUCTURE #######################################################
    workflow.connect([
        # BOLD buffer has slice-time corrected if it was run, original otherwise
        (boldbuffer, bold_split, [('bold_file', 'in_file')]),
        # HMC
        (inputnode, bold_hmc_wf, [('bold_ref', 'inputnode.raw_ref_image')]),
        (inputnode, final_boldref_masker, [('bold_ref', 'in_file')]),
        (val_bold, bold_hmc_wf, [(("out_file", pop_file),
                                  'inputnode.bold_file')]),
        (inputnode, summary, [('n_dummy_scans', 'algo_dummy_scans')]),
        # EPI-T1 registration workflow
        (
            inputnode,
            bold_reg_wf,
            [
                ('anat_dseg', 'inputnode.t1w_dseg'),
                # Undefined if --fs-no-reconall, but this is safe
                ('subjects_dir', 'inputnode.subjects_dir'),
                ('subject_id', 'inputnode.subject_id'),
                ('fsnative2anat_xfm', 'inputnode.fsnative2t1w_xfm')
            ]),
        (inputnode, bold_reg_wf, [('anat_brain', 'inputnode.t1w_brain')]),
        (inputnode, bold_t1_trans_wf, [('bold_file', 'inputnode.name_source'),
                                       ('anat_mask', 'inputnode.t1w_mask'),
                                       ('anat_brain', 'inputnode.t1w_brain'),
                                       ('anat_aseg', 'inputnode.t1w_aseg'),
                                       ('anat_aparc', 'inputnode.t1w_aparc')]),
        (bold_reg_wf, outputnode,
         [('outputnode.itk_bold_to_t1', 'bold2anat_xfm'),
          ('outputnode.itk_t1_to_bold', 'anat2bold_xfm')]),
        (bold_reg_wf, bold_t1_trans_wf, [('outputnode.itk_bold_to_t1',
                                          'inputnode.itk_bold_to_t1')]),
        (bold_t1_trans_wf, outputnode,
         [('outputnode.bold_t1', 'bold_anat'),
          ('outputnode.bold_t1_ref', 'bold_anat_ref'),
          ('outputnode.bold_aseg_t1', 'bold_aseg_anat'),
          ('outputnode.bold_aparc_t1', 'bold_aparc_anat')]),
        (bold_reg_wf, summary, [('outputnode.fallback', 'fallback')]),
        # Connect bold_confounds_wf
        (inputnode, bold_confounds_wf, [('anat_tpms', 'inputnode.t1w_tpms'),
                                        ('anat_mask', 'inputnode.t1w_mask')]),
        (bold_hmc_wf, bold_confounds_wf,
         [('outputnode.movpar_file', 'inputnode.movpar_file'),
          ('outputnode.rmsd_file', 'inputnode.rmsd_file')]),
        (bold_reg_wf, bold_confounds_wf, [('outputnode.itk_t1_to_bold',
                                           'inputnode.t1_bold_xform')]),
        (inputnode, bold_confounds_wf, [('n_dummy_scans',
                                         'inputnode.skip_vols')]),
        (bold_confounds_wf, outputnode, [
            ('outputnode.confounds_file', 'confounds'),
            ('outputnode.confounds_metadata', 'confounds_metadata'),
            ('outputnode.acompcor_masks', 'acompcor_masks'),
            ('outputnode.tcompcor_mask', 'tcompcor_mask'),
        ]),
        # Connect bold_bold_trans_wf
        (bold_split, bold_bold_trans_wf, [('out_files', 'inputnode.bold_file')]
         ),
        (bold_hmc_wf, bold_bold_trans_wf, [('outputnode.xforms',
                                            'inputnode.hmc_xforms')]),
        # Summary
        (outputnode, summary, [('confounds', 'confounds_file')]),
    ])

    # for standard EPI data, pass along correct file
    if not multiecho:
        # TODO: Add SDC
        workflow.connect([
            (inputnode, func_derivatives_wf, [('bold_file',
                                               'inputnode.source_file')]),
            (bold_bold_trans_wf, bold_confounds_wf, [('outputnode.bold',
                                                      'inputnode.bold')]),
            # (bold_bold_trans_wf, final_boldref_wf, [
            #     ('outputnode.bold', 'inputnode.bold_file')]),
            (bold_split, bold_t1_trans_wf, [('out_files',
                                             'inputnode.bold_split')]),
            (bold_hmc_wf, bold_t1_trans_wf, [('outputnode.xforms',
                                              'inputnode.hmc_xforms')]),
            # (bold_sdc_wf, bold_t1_trans_wf, [
            #     ('outputnode.out_warp', 'inputnode.fieldwarp')])
        ])
    else:  # for meepi, use optimal combination
        workflow.connect([
            # update name source for optimal combination
            (inputnode, func_derivatives_wf,
             [(('bold_file', combine_meepi_source), 'inputnode.source_file')]),
            (bold_bold_trans_wf, join_echos, [('outputnode.bold', 'bold_files')
                                              ]),
            # (join_echos, final_boldref_wf, [
            #     ('bold_files', 'inputnode.bold_file')]),
            # TODO: Check with multi-echo data
            (bold_bold_trans_wf, skullstrip_bold_wf, [('outputnode.bold',
                                                       'inputnode.in_file')]),
            (skullstrip_bold_wf, join_echos,
             [('outputnode.skull_stripped_file', 'skullstripped_bold_files')]),
            (join_echos, bold_t2s_wf, [('skullstripped_bold_files',
                                        'inputnode.bold_file')]),
            (bold_t2s_wf, bold_confounds_wf, [('outputnode.bold',
                                               'inputnode.bold')]),
            (bold_t2s_wf, split_opt_comb, [('outputnode.bold', 'in_file')]),
            (split_opt_comb, bold_t1_trans_wf, [('out_files',
                                                 'inputnode.bold_split')]),
        ])

        # Already applied in bold_bold_trans_wf, which inputs to bold_t2s_wf
        bold_t1_trans_wf.inputs.inputnode.fieldwarp = 'identity'
        bold_t1_trans_wf.inputs.inputnode.hmc_xforms = 'identity'

    # Map final BOLD mask into T1w space (if required)
    nonstd_spaces = set(spaces.get_nonstandard())
    if nonstd_spaces.intersection(('T1w', 'anat')):
        from niworkflows.interfaces.fixes import (FixHeaderApplyTransforms as
                                                  ApplyTransforms)

        boldmask_to_t1w = pe.Node(ApplyTransforms(interpolation='MultiLabel'),
                                  name='boldmask_to_t1w',
                                  mem_gb=0.1)
        workflow.connect([
            (bold_reg_wf, boldmask_to_t1w, [('outputnode.itk_bold_to_t1',
                                             'transforms')]),
            (bold_t1_trans_wf, boldmask_to_t1w, [('outputnode.bold_mask_t1',
                                                  'reference_image')]),
            (boldmask_to_t1w, outputnode, [('output_image', 'bold_mask_anat')
                                           ]),
        ])

    if nonstd_spaces.intersection(('func', 'run', 'bold', 'boldref', 'sbref')):
        workflow.connect([
            (inputnode, func_derivatives_wf, [
                ('bold_ref', 'inputnode.bold_native_ref'),
            ]),
            (bold_bold_trans_wf if not multiecho else bold_t2s_wf, outputnode,
             [('outputnode.bold', 'bold_native')])
        ])

    if spaces.get_spaces(nonstandard=False, dim=(3, )):
        # Apply transforms in 1 shot
        # Only use uncompressed output if AROMA is to be run
        bold_std_trans_wf = init_bold_std_trans_wf(
            freesurfer=freesurfer,
            mem_gb=mem_gb['resampled'],
            omp_nthreads=omp_nthreads,
            spaces=spaces,
            name='bold_std_trans_wf',
            use_compression=not config.execution.low_mem,
        )
        if not has_fieldmap:
            bold_std_trans_wf.inputs.inputnode.fieldwarp = 'identity'

        workflow.connect([
            (inputnode, bold_std_trans_wf,
             [('template', 'inputnode.templates'),
              ('anat2std_xfm', 'inputnode.anat2std_xfm'),
              ('bold_file', 'inputnode.name_source'),
              ('anat_aseg', 'inputnode.bold_aseg'),
              ('anat_aparc', 'inputnode.bold_aparc')]),
            (bold_reg_wf, bold_std_trans_wf, [('outputnode.itk_bold_to_t1',
                                               'inputnode.itk_bold_to_t1')]),
            (bold_std_trans_wf, outputnode,
             [('outputnode.bold_std', 'bold_std'),
              ('outputnode.bold_std_ref', 'bold_std_ref'),
              ('outputnode.bold_mask_std', 'bold_mask_std')]),
        ])

        if freesurfer:
            workflow.connect([
                (bold_std_trans_wf, func_derivatives_wf, [
                    ('outputnode.bold_aseg_std', 'inputnode.bold_aseg_std'),
                    ('outputnode.bold_aparc_std', 'inputnode.bold_aparc_std'),
                ]),
                (bold_std_trans_wf, outputnode,
                 [('outputnode.bold_aseg_std', 'bold_aseg_std'),
                  ('outputnode.bold_aparc_std', 'bold_aparc_std')]),
            ])

        if not multiecho:
            # TODO: Add SDC
            workflow.connect([
                (bold_split, bold_std_trans_wf, [('out_files',
                                                  'inputnode.bold_split')]),
                # (bold_sdc_wf, bold_std_trans_wf, [
                #     ('outputnode.out_warp', 'inputnode.fieldwarp')]),
                (bold_hmc_wf, bold_std_trans_wf, [('outputnode.xforms',
                                                   'inputnode.hmc_xforms')]),
            ])
        else:
            workflow.connect([(split_opt_comb, bold_std_trans_wf,
                               [('out_files', 'inputnode.bold_split')])])

            # Already applied in bold_bold_trans_wf, which inputs to bold_t2s_wf
            bold_std_trans_wf.inputs.inputnode.fieldwarp = 'identity'
            bold_std_trans_wf.inputs.inputnode.hmc_xforms = 'identity'

        # func_derivatives_wf internally parametrizes over snapshotted spaces.
        workflow.connect([
            (bold_std_trans_wf, func_derivatives_wf, [
                ('outputnode.template', 'inputnode.template'),
                ('outputnode.spatial_reference',
                 'inputnode.spatial_reference'),
                ('outputnode.bold_std_ref', 'inputnode.bold_std_ref'),
                ('outputnode.bold_std', 'inputnode.bold_std'),
                ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'),
            ]),
        ])

        if config.workflow.use_aroma:  # ICA-AROMA workflow
            from .confounds import init_ica_aroma_wf
            ica_aroma_wf = init_ica_aroma_wf(
                mem_gb=mem_gb['resampled'],
                metadata=metadata,
                omp_nthreads=omp_nthreads,
                err_on_aroma_warn=config.workflow.aroma_err_on_warn,
                aroma_melodic_dim=config.workflow.aroma_melodic_dim,
                name='ica_aroma_wf')

            join = pe.Node(niu.Function(output_names=["out_file"],
                                        function=_to_join),
                           name='aroma_confounds')

            mrg_conf_metadata = pe.Node(niu.Merge(2),
                                        name='merge_confound_metadata',
                                        run_without_submitting=True)
            mrg_conf_metadata2 = pe.Node(DictMerge(),
                                         name='merge_confound_metadata2',
                                         run_without_submitting=True)
            workflow.disconnect([
                (bold_confounds_wf, outputnode, [
                    ('outputnode.confounds_file', 'confounds'),
                ]),
                (bold_confounds_wf, outputnode, [
                    ('outputnode.confounds_metadata', 'confounds_metadata'),
                ]),
            ])
            workflow.connect([
                (inputnode, ica_aroma_wf, [('bold_file',
                                            'inputnode.name_source')]),
                (bold_hmc_wf, ica_aroma_wf, [('outputnode.movpar_file',
                                              'inputnode.movpar_file')]),
                (inputnode, ica_aroma_wf, [('n_dummy_scans',
                                            'inputnode.skip_vols')]),
                (bold_confounds_wf, join, [('outputnode.confounds_file',
                                            'in_file')]),
                (bold_confounds_wf, mrg_conf_metadata,
                 [('outputnode.confounds_metadata', 'in1')]),
                (ica_aroma_wf, join, [('outputnode.aroma_confounds',
                                       'join_file')]),
                (ica_aroma_wf, mrg_conf_metadata,
                 [('outputnode.aroma_metadata', 'in2')]),
                (mrg_conf_metadata, mrg_conf_metadata2, [('out', 'in_dicts')]),
                (ica_aroma_wf, outputnode,
                 [('outputnode.aroma_noise_ics', 'aroma_noise_ics'),
                  ('outputnode.melodic_mix', 'melodic_mix'),
                  ('outputnode.nonaggr_denoised_file', 'nonaggr_denoised_file')
                  ]),
                (join, outputnode, [('out_file', 'confounds')]),
                (mrg_conf_metadata2, outputnode, [('out_dict',
                                                   'confounds_metadata')]),
                (bold_std_trans_wf, ica_aroma_wf,
                 [('outputnode.bold_std', 'inputnode.bold_std'),
                  ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'),
                  ('outputnode.spatial_reference',
                   'inputnode.spatial_reference')]),
            ])

    # SURFACES ##################################################################################
    # Freesurfer
    freesurfer_spaces = spaces.get_fs_spaces()
    if freesurfer and freesurfer_spaces:
        config.loggers.workflow.debug(
            'Creating BOLD surface-sampling workflow.')
        bold_surf_wf = init_bold_surf_wf(
            mem_gb=mem_gb['resampled'],
            surface_spaces=freesurfer_spaces,
            medial_surface_nan=config.workflow.medial_surface_nan,
            name='bold_surf_wf')
        workflow.connect([
            (inputnode, bold_surf_wf,
             [('subjects_dir', 'inputnode.subjects_dir'),
              ('subject_id', 'inputnode.subject_id'),
              ('anat2fsnative_xfm', 'inputnode.t1w2fsnative_xfm')]),
            (bold_t1_trans_wf, bold_surf_wf, [('outputnode.bold_t1',
                                               'inputnode.source_file')]),
            (bold_surf_wf, outputnode, [('outputnode.surfaces', 'surfaces')]),
            (bold_surf_wf, func_derivatives_wf, [('outputnode.target',
                                                  'inputnode.surf_refs')]),
        ])

        # CIFTI output
        if config.workflow.cifti_output:
            from .resampling import init_bold_grayords_wf
            bold_grayords_wf = init_bold_grayords_wf(
                grayord_density=config.workflow.cifti_output,
                mem_gb=mem_gb['resampled'],
                repetition_time=metadata['RepetitionTime'])

            workflow.connect([
                (inputnode, bold_grayords_wf, [('subjects_dir',
                                                'inputnode.subjects_dir')]),
                (bold_std_trans_wf, bold_grayords_wf,
                 [('outputnode.bold_std', 'inputnode.bold_std'),
                  ('outputnode.spatial_reference',
                   'inputnode.spatial_reference')]),
                (bold_surf_wf, bold_grayords_wf, [
                    ('outputnode.surfaces', 'inputnode.surf_files'),
                    ('outputnode.target', 'inputnode.surf_refs'),
                ]),
                (bold_grayords_wf, outputnode,
                 [('outputnode.cifti_bold', 'bold_cifti'),
                  ('outputnode.cifti_variant', 'cifti_variant'),
                  ('outputnode.cifti_metadata', 'cifti_metadata'),
                  ('outputnode.cifti_density', 'cifti_density')]),
            ])

    if spaces.get_spaces(nonstandard=False, dim=(3, )):
        if not config.workflow.cifti_output:
            config.loggers.workflow.critical(
                "The carpetplot requires CIFTI outputs")
        else:
            carpetplot_wf = init_carpetplot_wf(
                mem_gb=mem_gb['resampled'],
                metadata=metadata,
                cifti_output=bool(config.workflow.cifti_output),
                name='carpetplot_wf')

            workflow.connect([
                (bold_grayords_wf, carpetplot_wf, [('outputnode.cifti_bold',
                                                    'inputnode.cifti_bold')]),
                (bold_confounds_wf, carpetplot_wf,
                 [('outputnode.confounds_file', 'inputnode.confounds_file')]),
            ])

    # REPORTING ############################################################
    ds_report_summary = pe.Node(DerivativesDataSink(
        desc='summary', datatype="figures", dismiss_entities=("echo", )),
                                name='ds_report_summary',
                                run_without_submitting=True,
                                mem_gb=config.DEFAULT_MEMORY_MIN_GB)

    ds_report_validation = pe.Node(DerivativesDataSink(
        desc='validation', datatype="figures", dismiss_entities=("echo", )),
                                   name='ds_report_validation',
                                   run_without_submitting=True,
                                   mem_gb=config.DEFAULT_MEMORY_MIN_GB)

    workflow.connect([
        (summary, ds_report_summary, [('out_report', 'in_file')]),
        (val_bold, ds_report_validation, [(("out_report", pop_file), 'in_file')
                                          ]),
    ])

    # Fill-in datasinks of reportlets seen so far
    for node in workflow.list_node_names():
        if node.split('.')[-1].startswith('ds_report'):
            workflow.get_node(node).inputs.base_directory = nibabies_dir
            workflow.get_node(node).inputs.source_file = ref_file

    # Distortion correction
    if not has_fieldmap:
        # fmt: off
        # Finalize workflow with fieldmap-less connections
        workflow.connect([
            (inputnode, final_boldref_masker, [('bold_ref', 'in_file')]),
            (final_boldref_masker, bold_t1_trans_wf, [
                ('out_mask', 'inputnode.ref_bold_mask'),
                ('out_file', 'inputnode.ref_bold_brain'),
            ]),
            (final_boldref_masker, bold_reg_wf, [
                ('out_file', 'inputnode.ref_bold_brain'),
            ]),
            (final_boldref_masker, bold_confounds_wf,
             [('out_mask', 'inputnode.bold_mask')]),
        ])

        if nonstd_spaces.intersection(('T1w', 'anat')):
            workflow.connect([
                (final_boldref_masker, boldmask_to_t1w, [('out_mask',
                                                          'input_image')]),
            ])
        #         (final_boldref_wf, boldmask_to_t1w, [('outputnode.bold_mask', 'input_image')]),
        #     ])

        if nonstd_spaces.intersection(
            ('func', 'run', 'bold', 'boldref', 'sbref')):
            workflow.connect([
                (final_boldref_masker, func_derivatives_wf,
                 [('out_file', 'inputnode.bold_native_ref'),
                  ('out_mask', 'inputnode.bold_mask_native')]),
            ])
        #         (final_boldref_wf, func_derivatives_wf, [
        #             ('outputnode.ref_image', 'inputnode.bold_native_ref'),
        #             ('outputnode.bold_mask', 'inputnode.bold_mask_native')]),
        #     ])

        if spaces.get_spaces(nonstandard=False, dim=(3, )):
            workflow.connect([
                (final_boldref_masker, bold_std_trans_wf,
                 [('out_mask', 'inputnode.bold_mask')]),
            ])
        #         (final_boldref_wf, bold_std_trans_wf, [
        #             ('outputnode.bold_mask', 'inputnode.bold_mask')]),
        #     ])

        # fmt: on
        return workflow

    from niworkflows.interfaces.reportlets.registration import (
        SimpleBeforeAfterRPT as SimpleBeforeAfter, )
    from niworkflows.interfaces.utility import KeySelect
    from sdcflows.workflows.apply.registration import init_coeff2epi_wf
    from sdcflows.workflows.apply.correction import init_unwarp_wf

    coeff2epi_wf = init_coeff2epi_wf(
        debug="fieldmaps" in config.execution.debug,
        omp_nthreads=config.nipype.omp_nthreads,
        write_coeff=True,
    )
    unwarp_wf = init_unwarp_wf(debug="fieldmaps" in config.execution.debug,
                               omp_nthreads=config.nipype.omp_nthreads)
    unwarp_wf.inputs.inputnode.metadata = layout.get_metadata(str(bold_file))

    output_select = pe.Node(
        KeySelect(fields=["fmap", "fmap_ref", "fmap_coeff", "fmap_mask"]),
        name="output_select",
        run_without_submitting=True,
    )
    output_select.inputs.key = estimator_key[0]
    if len(estimator_key) > 1:
        config.loggers.workflow.warning(
            f"Several fieldmaps <{', '.join(estimator_key)}> are "
            f"'IntendedFor' <{bold_file}>, using {estimator_key[0]}")

    sdc_report = pe.Node(
        SimpleBeforeAfter(before_label="Distorted", after_label="Corrected"),
        name="sdc_report",
        mem_gb=0.1,
    )

    ds_report_sdc = pe.Node(
        DerivativesDataSink(base_directory=nibabies_dir,
                            desc="sdc",
                            suffix="bold",
                            datatype="figures",
                            dismiss_entities=("echo", )),
        name="ds_report_sdc",
        run_without_submitting=True,
    )

    unwarp_masker = pe.Node(BrainExtraction(), name='unwarp_masker')

    # fmt: off
    workflow.connect([
        (inputnode, output_select, [("fmap", "fmap"), ("fmap_ref", "fmap_ref"),
                                    ("fmap_coeff", "fmap_coeff"),
                                    ("fmap_mask", "fmap_mask"),
                                    ("fmap_id", "keys")]),
        (output_select, coeff2epi_wf, [("fmap_ref", "inputnode.fmap_ref"),
                                       ("fmap_coeff", "inputnode.fmap_coeff"),
                                       ("fmap_mask", "inputnode.fmap_mask")]),
        (inputnode, coeff2epi_wf, [("bold_ref", "inputnode.target_ref")]),
        (final_boldref_masker, coeff2epi_wf, [("out_file",
                                               "inputnode.target_mask")]),
        (inputnode, unwarp_wf, [("bold_ref", "inputnode.distorted")]),
        (coeff2epi_wf, unwarp_wf, [("outputnode.fmap_coeff",
                                    "inputnode.fmap_coeff")]),
        (inputnode, sdc_report, [("bold_ref", "before")]),
        (unwarp_wf, sdc_report, [("outputnode.corrected", "after"),
                                 ("outputnode.corrected_mask", "wm_seg")]),
        (inputnode, ds_report_sdc, [("bold_file", "source_file")]),
        (sdc_report, ds_report_sdc, [("out_report", "in_file")]),
        # remaining workflow connections
        (unwarp_wf, unwarp_masker, [('outputnode.corrected', 'in_file')]),
        (unwarp_masker, bold_confounds_wf, [('out_mask', 'inputnode.bold_mask')
                                            ]),
        (unwarp_masker, bold_t1_trans_wf,
         [('out_mask', 'inputnode.ref_bold_mask'),
          ('out_file', 'inputnode.ref_bold_brain')]),
        # (unwarp_masker, bold_bold_trans_wf, [
        #     ('out_mask', 'inputnode.bold_mask')]),  # Not used within workflow
        (unwarp_masker, bold_reg_wf, [('out_file', 'inputnode.ref_bold_brain')]
         ),
        # TODO: Add distortion correction method to sdcflow outputs?
        # (bold_sdc_wf, summary, [('outputnode.method', 'distortion_correction')]),
    ])

    if nonstd_spaces.intersection(('T1w', 'anat')):
        workflow.connect([
            (unwarp_masker, boldmask_to_t1w, [('out_mask', 'input_image')]),
        ])

    if nonstd_spaces.intersection(('func', 'run', 'bold', 'boldref', 'sbref')):
        workflow.connect([
            (unwarp_masker, func_derivatives_wf,
             [('out_file', 'inputnode.bold_native_ref'),
              ('out_mask', 'inputnode.bold_mask_native')]),
        ])

    if spaces.get_spaces(nonstandard=False, dim=(3, )):
        workflow.connect([
            (unwarp_masker, bold_std_trans_wf, [('out_mask',
                                                 'inputnode.bold_mask')]),
        ])
    # fmt: on

    # if not multiecho:
    #     (bold_sdc_wf, bold_t1_trans_wf, [
    #             ('outputnode.out_warp', 'inputnode.fieldwarp')])
    #     (bold_sdc_wf, bold_std_trans_wf, [
    #         ('outputnode.out_warp', 'inputnode.fieldwarp')]),
    # ])
    return workflow
Esempio n. 10
0
def init_anat_derivatives_wf(
    *,
    bids_root,
    freesurfer,
    num_t1w,
    output_dir,
    spaces,
    name="anat_derivatives_wf",
    tpm_labels=BIDS_TISSUE_ORDER,
):
    """
    Set up a battery of datasinks to store derivatives in the right location.

    Parameters
    ----------
    bids_root : :obj:`str`
        Root path of BIDS dataset
    freesurfer : :obj:`bool`
        FreeSurfer was enabled
    num_t1w : :obj:`int`
        Number of T1w images
    output_dir : :obj:`str`
        Directory in which to save derivatives
    name : :obj:`str`
        Workflow name (default: anat_derivatives_wf)
    tpm_labels : :obj:`tuple`
        Tissue probability maps in order

    Inputs
    ------
    template
        Template space and specifications
    source_files
        List of input T1w images
    t1w_ref_xfms
        List of affine transforms to realign input T1w images
    t1w_preproc
        The T1w reference map, which is calculated as the average of bias-corrected
        and preprocessed T1w images, defining the anatomical space.
    t1w_mask
        Mask of the ``t1w_preproc``
    t1w_dseg
        Segmentation in T1w space
    t1w_tpms
        Tissue probability maps in T1w space
    anat2std_xfm
        Nonlinear spatial transform to resample imaging data given in anatomical space
        into standard space.
    std2anat_xfm
        Inverse transform of ``anat2std_xfm``
    std_t1w
        T1w reference resampled in one or more standard spaces.
    std_mask
        Mask of skull-stripped template, in standard space
    std_dseg
        Segmentation, resampled into standard space
    std_tpms
        Tissue probability maps in standard space
    t1w2fsnative_xfm
        LTA-style affine matrix translating from T1w to
        FreeSurfer-conformed subject space
    fsnative2t1w_xfm
        LTA-style affine matrix translating from FreeSurfer-conformed
        subject space to T1w
    surfaces
        GIFTI surfaces (gray/white boundary, midthickness, pial, inflated)
    t1w_fs_aseg
        FreeSurfer's aseg segmentation, in native T1w space
    t1w_fs_aparc
        FreeSurfer's aparc+aseg segmentation, in native T1w space

    """
    from niworkflows.interfaces.utility import KeySelect

    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",
            "t1w2fsnative_xfm",
            "fsnative2t1w_xfm",
            "surfaces",
            "t1w_fs_aseg",
            "t1w_fs_aparc",
        ]),
        name="inputnode",
    )

    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",
                            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"

    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.label = tpm_labels

    # fmt:off
    workflow.connect([
        (inputnode, raw_sources, [('source_files', 'in_files')]),
        (inputnode, ds_t1w_preproc, [('t1w_preproc', 'in_file'),
                                     ('source_files', 'source_file')]),
        (inputnode, ds_t1w_mask, [('t1w_mask', 'in_file'),
                                  ('source_files', 'source_file')]),
        (inputnode, ds_t1w_tpms, [('t1w_tpms', 'in_file'),
                                  ('source_files', 'source_file')]),
        (inputnode, ds_t1w_dseg, [('t1w_dseg', 'in_file'),
                                  ('source_files', 'source_file')]),
        (raw_sources, ds_t1w_mask, [('out', 'RawSources')]),
    ])
    # fmt:on

    # Transforms
    if spaces.get_spaces(nonstandard=False, dim=(3, )):
        ds_std2t1w_xfm = pe.MapNode(
            DerivativesDataSink(base_directory=output_dir,
                                to="T1w",
                                mode="image",
                                suffix="xfm"),
            iterfield=("in_file", "from"),
            name="ds_std2t1w_xfm",
            run_without_submitting=True,
        )

        ds_t1w2std_xfm = pe.MapNode(
            DerivativesDataSink(base_directory=output_dir,
                                mode="image",
                                suffix="xfm",
                                **{"from": "T1w"}),
            iterfield=("in_file", "to"),
            name="ds_t1w2std_xfm",
            run_without_submitting=True,
        )

        # fmt:off
        workflow.connect([
            (inputnode, ds_t1w2std_xfm, [('anat2std_xfm', 'in_file'),
                                         (('template', _combine_cohort), 'to'),
                                         ('source_files', 'source_file')]),
            (inputnode, ds_std2t1w_xfm, [('std2anat_xfm', 'in_file'),
                                         (('template', _combine_cohort),
                                          'from'),
                                         ('source_files', 'source_file')]),
        ])
        # fmt:on

    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,
                to="T1w",
                mode="image",
                suffix="xfm",
                extension="txt",
                **{"from": "orig"},
            ),
            iterfield=["source_file", "in_file"],
            name="ds_t1w_ref_xfms",
            run_without_submitting=True,
        )
        # fmt:off
        workflow.connect([
            (inputnode, ds_t1w_ref_xfms, [('source_files', 'source_file'),
                                          ('t1w_ref_xfms', 'in_file')]),
        ])
        # fmt:on

    # Write derivatives in standard spaces specified by --output-spaces
    if getattr(spaces, "_cached") is not None and spaces.cached.references:
        from niworkflows.interfaces.space import SpaceDataSource
        from niworkflows.interfaces.nibabel import GenerateSamplingReference
        from niworkflows.interfaces.fixes import (
            FixHeaderApplyTransforms as ApplyTransforms, )

        from ..interfaces.templateflow import TemplateFlowSelect

        spacesource = pe.Node(SpaceDataSource(),
                              name="spacesource",
                              run_without_submitting=True)
        spacesource.iterables = (
            "in_tuple",
            [(s.fullname, s.spec)
             for s in spaces.cached.get_standard(dim=(3, ))],
        )

        gen_tplid = pe.Node(
            niu.Function(function=_fmt_cohort),
            name="gen_tplid",
            run_without_submitting=True,
        )

        select_xfm = pe.Node(
            KeySelect(fields=["anat2std_xfm"]),
            name="select_xfm",
            run_without_submitting=True,
        )
        select_tpl = pe.Node(TemplateFlowSelect(),
                             name="select_tpl",
                             run_without_submitting=True)

        gen_ref = pe.Node(GenerateSamplingReference(),
                          name="gen_ref",
                          mem_gb=0.01)

        # Mask T1w preproc images
        mask_t1w = pe.Node(ApplyMask(), name='mask_t1w')

        # Resample T1w-space inputs
        anat2std_t1w = pe.Node(
            ApplyTransforms(
                dimension=3,
                default_value=0,
                float=True,
                interpolation="LanczosWindowedSinc",
            ),
            name="anat2std_t1w",
        )

        anat2std_mask = pe.Node(ApplyTransforms(interpolation="MultiLabel"),
                                name="anat2std_mask")
        anat2std_dseg = pe.Node(ApplyTransforms(interpolation="MultiLabel"),
                                name="anat2std_dseg")
        anat2std_tpms = pe.MapNode(
            ApplyTransforms(dimension=3,
                            default_value=0,
                            float=True,
                            interpolation="Gaussian"),
            iterfield=["input_image"],
            name="anat2std_tpms",
        )

        ds_std_t1w = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir,
                desc="preproc",
                compress=True,
            ),
            name="ds_std_t1w",
            run_without_submitting=True,
        )
        ds_std_t1w.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"

        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,
        )

        # CRITICAL: the sequence of labels here (CSF-GM-WM) is that of the output of FSL-FAST
        #           (intensity mean, per tissue). This order HAS to be matched also by the ``tpms``
        #           output in the data/io_spec.json file.
        ds_std_tpms.inputs.label = tpm_labels
        # fmt:off
        workflow.connect([
            (inputnode, mask_t1w, [('t1w_preproc', 'in_file'),
                                   ('t1w_mask', 'in_mask')]),
            (mask_t1w, anat2std_t1w, [('out_file', 'input_image')]),
            (inputnode, anat2std_mask, [('t1w_mask', 'input_image')]),
            (inputnode, anat2std_dseg, [('t1w_dseg', 'input_image')]),
            (inputnode, anat2std_tpms, [('t1w_tpms', 'input_image')]),
            (inputnode, gen_ref, [('t1w_preproc', 'moving_image')]),
            (inputnode, select_xfm, [('anat2std_xfm', 'anat2std_xfm'),
                                     ('template', 'keys')]),
            (spacesource, gen_tplid, [('space', 'template'),
                                      ('cohort', 'cohort')]),
            (gen_tplid, select_xfm, [('out', 'key')]),
            (spacesource, select_tpl, [('space', 'template'),
                                       ('cohort', 'cohort'),
                                       (('resolution', _no_native),
                                        'resolution')]),
            (spacesource, gen_ref, [(('resolution', _is_native), 'keep_native')
                                    ]),
            (select_tpl, gen_ref, [('t1w_file', 'fixed_image')]),
            (anat2std_t1w, ds_std_t1w, [('output_image', 'in_file')]),
            (anat2std_mask, ds_std_mask, [('output_image', 'in_file')]),
            (anat2std_dseg, ds_std_dseg, [('output_image', 'in_file')]),
            (anat2std_tpms, ds_std_tpms, [('output_image', 'in_file')]),
            (select_tpl, ds_std_mask, [(('brain_mask', _drop_path),
                                        'RawSources')]),
        ])

        workflow.connect(
            # Connect apply transforms nodes
            [(gen_ref, n, [('out_file', 'reference_image')])
             for n in (anat2std_t1w, anat2std_mask, anat2std_dseg,
                       anat2std_tpms)] +
            [(select_xfm, n, [('anat2std_xfm', 'transforms')])
             for n in (anat2std_t1w, anat2std_mask, anat2std_dseg,
                       anat2std_tpms)]
            # Connect the source_file input of these datasinks
            + [(inputnode, n, [('source_files', 'source_file')])
               for n in (ds_std_t1w, ds_std_mask, ds_std_dseg, ds_std_tpms)]
            # Connect the space input of these datasinks
            + [(spacesource, n, [('space', 'space'), ('cohort', 'cohort'),
                                 ('resolution', 'resolution')])
               for n in (ds_std_t1w, ds_std_mask, ds_std_dseg, ds_std_tpms)])
        # fmt:on

    if not freesurfer:
        return workflow

    from niworkflows.interfaces.nitransforms import ConcatenateXFMs
    from niworkflows.interfaces.surf import Path2BIDS

    # FS native space transforms
    lta2itk_fwd = pe.Node(ConcatenateXFMs(),
                          name="lta2itk_fwd",
                          run_without_submitting=True)
    lta2itk_inv = pe.Node(ConcatenateXFMs(),
                          name="lta2itk_inv",
                          run_without_submitting=True)
    ds_t1w_fsnative = pe.Node(
        DerivativesDataSink(
            base_directory=output_dir,
            mode="image",
            to="fsnative",
            suffix="xfm",
            extension="txt",
            **{"from": "T1w"},
        ),
        name="ds_t1w_fsnative",
        run_without_submitting=True,
    )
    ds_fsnative_t1w = pe.Node(
        DerivativesDataSink(
            base_directory=output_dir,
            mode="image",
            to="T1w",
            suffix="xfm",
            extension="txt",
            **{"from": "fsnative"},
        ),
        name="ds_fsnative_t1w",
        run_without_submitting=True,
    )
    # Surfaces
    name_surfs = pe.MapNode(Path2BIDS(),
                            iterfield="in_file",
                            name="name_surfs",
                            run_without_submitting=True)
    ds_surfs = pe.MapNode(
        DerivativesDataSink(base_directory=output_dir, extension=".surf.gii"),
        iterfield=["in_file", "hemi", "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,
    )

    # fmt:off
    workflow.connect([
        (inputnode, lta2itk_fwd, [('t1w2fsnative_xfm', 'in_xfms')]),
        (inputnode, lta2itk_inv, [('fsnative2t1w_xfm', 'in_xfms')]),
        (inputnode, ds_t1w_fsnative, [('source_files', 'source_file')]),
        (lta2itk_fwd, ds_t1w_fsnative, [('out_xfm', 'in_file')]),
        (inputnode, ds_fsnative_t1w, [('source_files', 'source_file')]),
        (lta2itk_inv, ds_fsnative_t1w, [('out_xfm', 'in_file')]),
        (inputnode, name_surfs, [('surfaces', 'in_file')]),
        (inputnode, ds_surfs, [('surfaces', 'in_file'),
                               ('source_files', 'source_file')]),
        (name_surfs, ds_surfs, [('hemi', 'hemi'), ('suffix', 'suffix')]),
        (inputnode, ds_t1w_fsaseg, [('t1w_fs_aseg', 'in_file'),
                                    ('source_files', 'source_file')]),
        (inputnode, ds_t1w_fsparc, [('t1w_fs_aparc', 'in_file'),
                                    ('source_files', 'source_file')]),
    ])
    # fmt:on
    return workflow
Esempio n. 11
0
def add_templates_by_composing_transforms(workflow,
                                          templates=["MNI152NLin6Asym"]):
    anat_norm_wf = workflow.get_node("anat_norm_wf")
    outputnode = workflow.get_node("outputnode")

    if len(templates) == 0:
        workflow.connect([
            (
                anat_norm_wf,
                outputnode,
                [
                    ("outputnode.standardized", "std_preproc"),
                    ("outputnode.std_mask", "std_mask"),
                    ("outputnode.std_dseg", "std_dseg"),
                    ("outputnode.std_tpms", "std_tpms"),
                    ("outputnode.template", "template"),
                    ("outputnode.anat2std_xfm", "anat2std_xfm"),
                    ("outputnode.std2anat_xfm", "std2anat_xfm"),
                ],
            ),
        ])
        return

    templitersrc = pe.Node(
        niu.IdentityInterface(fields=["template"]),
        iterables=[("template", templates)],
        name="templitersrc",
    )

    movingsrc = pe.Node(
        niu.IdentityInterface(fields=[
            "moving_image", "moving_mask", "moving_segmentation", "moving_tpms"
        ]),
        name="movingsrc",
    )
    brain_extraction_wf = workflow.get_node("brain_extraction_wf")
    if brain_extraction_wf is None:
        brain_extraction_wf = workflow.get_node("n4_only_wf")
    workflow.connect([
        (
            brain_extraction_wf,
            movingsrc,
            [(("outputnode.bias_corrected", first), "moving_image")],
        ),
        (workflow.get_node("buffernode"), movingsrc, [("t1w_mask",
                                                       "moving_mask")]),
        (
            workflow.get_node("t1w_dseg"),
            movingsrc,
            [("tissue_class_map", "moving_segmentation")],
        ),
        (workflow.get_node("t1w_dseg"), movingsrc, [("probability_maps",
                                                     "moving_tpms")]),
    ])

    # gather inputs

    def select_xfm_to_compose(base_template_list=None, out_template=None):
        from pipeline import resources

        for base_template in base_template_list:
            xfm_file = resources.get(
                f"tpl_{out_template}_from_{base_template}_mode_image_xfm.h5")
            inv_xfm_file = resources.get(
                f"tpl_{base_template}_from_{out_template}_mode_image_xfm.h5")
            if xfm_file is not None and inv_xfm_file is not None:
                return base_template, xfm_file, inv_xfm_file
        raise ValueError("No xfm available")

    selectcompxfm = pe.Node(
        interface=niu.Function(
            input_names=["base_template_list", "out_template"],
            output_names=["base_template", "xfm", "inv_xfm"],
            function=select_xfm_to_compose,
        ),
        name="selectcompxfm",
    )
    workflow.connect(anat_norm_wf, "outputnode.template", selectcompxfm,
                     "base_template_list")
    workflow.connect(templitersrc, "template", selectcompxfm, "out_template")

    selectbasexfm = pe.Node(
        KeySelect(fields=["anat2std_xfm", "std2anat_xfm"]),
        name="selectbasexfm",
        run_without_submitting=True,
    )
    workflow.connect(selectcompxfm, "base_template", selectbasexfm, "key")
    workflow.connect(anat_norm_wf, "outputnode.template", selectbasexfm,
                     "keys")
    workflow.connect(anat_norm_wf, "outputnode.anat2std_xfm", selectbasexfm,
                     "anat2std_xfm")
    workflow.connect(anat_norm_wf, "outputnode.std2anat_xfm", selectbasexfm,
                     "std2anat_xfm")

    tf_select = pe.Node(TemplateFlowSelect(resolution=1),
                        name="tf_select",
                        run_without_submitting=True)
    workflow.connect(templitersrc, "template", tf_select, "template")

    # compose xfms

    mergexfm = pe.Node(niu.Merge(numinputs=2),
                       name="mergexfm",
                       run_without_submitting=True)
    workflow.connect(selectbasexfm, "anat2std_xfm", mergexfm, "in1")
    workflow.connect(selectcompxfm, "xfm", mergexfm, "in2")

    compxfm = pe.Node(
        ApplyTransforms(
            dimension=3,
            print_out_composite_warp_file=True,
            output_image="ants_t1_to_mniComposite.nii.gz",
        ),
        name="compxfm",
    )
    workflow.connect(tf_select, "t1w_file", compxfm, "reference_image")
    workflow.connect(mergexfm, "out", compxfm, "transforms")

    mergeinvxfm = pe.Node(niu.Merge(numinputs=2),
                          name="mergeinvxfm",
                          run_without_submitting=True)
    workflow.connect(selectcompxfm, "inv_xfm", mergeinvxfm, "in1")
    workflow.connect(selectbasexfm, "std2anat_xfm", mergeinvxfm, "in2")

    compinvxfm = pe.Node(
        ApplyTransforms(
            dimension=3,
            print_out_composite_warp_file=True,
            output_image="ants_t1_to_mniInverseComposite.nii.gz",
        ),
        name="compinvxfm",
    )
    workflow.connect(movingsrc, "moving_image", compinvxfm, "reference_image")
    workflow.connect(mergeinvxfm, "out", compinvxfm, "transforms")

    # apply xfms

    tpl_moving = pe.Node(
        ApplyTransforms(dimension=3,
                        default_value=0,
                        float=True,
                        interpolation="LanczosWindowedSinc"),
        name="tpl_moving",
    )
    workflow.connect(movingsrc, "moving_image", tpl_moving, "input_image")
    workflow.connect(tf_select, "t1w_file", tpl_moving, "reference_image")
    workflow.connect(compxfm, "output_image", tpl_moving, "transforms")

    std_mask = pe.Node(
        ApplyTransforms(dimension=3,
                        default_value=0,
                        float=True,
                        interpolation="MultiLabel"),
        name="std_mask",
    )
    workflow.connect(movingsrc, "moving_mask", std_mask, "input_image")
    workflow.connect(tf_select, "t1w_file", std_mask, "reference_image")
    workflow.connect(compxfm, "output_image", std_mask, "transforms")

    std_dseg = pe.Node(
        ApplyTransforms(dimension=3,
                        default_value=0,
                        float=True,
                        interpolation="MultiLabel"),
        name="std_dseg",
    )
    workflow.connect(movingsrc, "moving_segmentation", std_dseg, "input_image")
    workflow.connect(tf_select, "t1w_file", std_dseg, "reference_image")
    workflow.connect(compxfm, "output_image", std_dseg, "transforms")

    std_tpms = pe.MapNode(
        ApplyTransforms(dimension=3,
                        default_value=0,
                        float=True,
                        interpolation="Gaussian"),
        iterfield=["input_image"],
        name="std_tpms",
    )
    workflow.connect(movingsrc, "moving_tpms", std_tpms, "input_image")
    workflow.connect(tf_select, "t1w_file", std_tpms, "reference_image")
    workflow.connect(compxfm, "output_image", std_tpms, "transforms")

    # merge

    mergefields = [
        "template",
        "standardized",
        "std_mask",
        "std_dseg",
        "std_tpms",
        "anat2std_xfm",
        "std2anat_xfm",
    ]
    aliases = {"standardized": "std_preproc"}

    joinnode = pe.JoinNode(
        niu.IdentityInterface(fields=mergefields),
        name="joinnode",
        joinsource="templitersrc",
        joinfield="template",
    )
    workflow.connect(templitersrc, "template", joinnode, "template")
    workflow.connect(compxfm, "output_image", joinnode, "anat2std_xfm")
    workflow.connect(compinvxfm, "output_image", joinnode, "std2anat_xfm")
    workflow.connect(tpl_moving, "output_image", joinnode, "standardized")
    workflow.connect(std_mask, "output_image", joinnode, "std_mask")
    workflow.connect(std_dseg, "output_image", joinnode, "std_dseg")
    workflow.connect(std_tpms, "output_image", joinnode, "std_tpms")

    for field in mergefields:
        merge = pe.Node(niu.Merge(numinputs=2),
                        name=f"merge{field}",
                        run_without_submitting=True)
        workflow.connect(anat_norm_wf, f"outputnode.{field}", merge, "in1")
        workflow.connect(joinnode, field, merge, "in2")

        workflow.connect(merge, "out", outputnode,
                         aliases[field] if field in aliases else field)
Esempio n. 12
0
def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
    """
    Build a preprocessing workflow for one DWI run.

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

            from dmriprep.config.testing import mock_config
            from dmriprep import config
            from dmriprep.workflows.dwi.base import init_dwi_preproc_wf
            with mock_config():
                wf = init_dwi_preproc_wf(
                    f"{config.execution.layout.root}/"
                    "sub-THP0005/dwi/sub-THP0005_dwi.nii.gz"
                )

    Parameters
    ----------
    dwi_file : :obj:`os.PathLike`
        One diffusion MRI dataset to be processed.
    has_fieldmap : :obj:`bool`
        Build the workflow with a path to register a fieldmap to the DWI.

    Inputs
    ------
    dwi_file
        dwi NIfTI file
    in_bvec
        File path of the b-vectors
    in_bval
        File path of the b-values
    fmap
        File path of the fieldmap
    fmap_ref
        File path of the fieldmap reference
    fmap_coeff
        File path of the fieldmap coefficients
    fmap_mask
        File path of the fieldmap mask
    fmap_id
        The BIDS modality label of the fieldmap being used

    Outputs
    -------
    dwi_reference
        A 3D :math:`b = 0` reference, before susceptibility distortion correction.
    dwi_mask
        A 3D, binary mask of the ``dwi_reference`` above.
    gradients_rasb
        A *RASb* (RAS+ coordinates, scaled b-values, normalized b-vectors, BIDS-compatible)
        gradient table.

    See Also
    --------
    * :py:func:`~dmriprep.workflows.dwi.util.init_dwi_reference_wf`
    * :py:func:`~dmriprep.workflows.dwi.outputs.init_dwi_derivatives_wf`
    * :py:func:`~dmriprep.workflows.dwi.outputs.init_reportlets_wf`

    """
    from niworkflows.interfaces.reportlets.registration import (
        SimpleBeforeAfterRPT as SimpleBeforeAfter, )
    from ...interfaces.vectors import CheckGradientTable
    from .util import init_dwi_reference_wf
    from .outputs import init_dwi_derivatives_wf, init_reportlets_wf
    from .eddy import init_eddy_wf

    layout = config.execution.layout

    dwi_file = Path(dwi_file)
    config.loggers.workflow.debug(
        f"Creating DWI preprocessing workflow for <{dwi_file.name}>")

    if has_fieldmap:
        import re
        from sdcflows.fieldmaps import get_identifier

        dwi_rel = re.sub(r"^sub-[a-zA-Z0-9]*/", "",
                         str(dwi_file.relative_to(layout.root)))
        estimator_key = get_identifier(dwi_rel)
        if not estimator_key:
            has_fieldmap = False
            config.loggers.workflow.critical(
                f"None of the available B0 fieldmaps are associated to <{dwi_rel}>"
            )

    # Build workflow
    workflow = Workflow(name=_get_wf_name(dwi_file.name))

    inputnode = pe.Node(
        niu.IdentityInterface(fields=[
            # DWI
            "dwi_file",
            "in_bvec",
            "in_bval",
            # From SDCFlows
            "fmap",
            "fmap_ref",
            "fmap_coeff",
            "fmap_mask",
            "fmap_id",
            # From anatomical
            "t1w_preproc",
            "t1w_mask",
            "t1w_dseg",
            "t1w_aseg",
            "t1w_aparc",
            "t1w_tpms",
            "template",
            "anat2std_xfm",
            "std2anat_xfm",
            "subjects_dir",
            "subject_id",
            "t1w2fsnative_xfm",
            "fsnative2t1w_xfm",
        ]),
        name="inputnode",
    )
    inputnode.inputs.dwi_file = str(dwi_file.absolute())
    inputnode.inputs.in_bvec = str(layout.get_bvec(dwi_file))
    inputnode.inputs.in_bval = str(layout.get_bval(dwi_file))

    outputnode = pe.Node(
        niu.IdentityInterface(
            fields=["dwi_reference", "dwi_mask", "gradients_rasb"]),
        name="outputnode",
    )

    gradient_table = pe.Node(CheckGradientTable(), name="gradient_table")

    dwi_reference_wf = init_dwi_reference_wf(
        mem_gb=config.DEFAULT_MEMORY_MIN_GB,
        omp_nthreads=config.nipype.omp_nthreads)

    dwi_derivatives_wf = init_dwi_derivatives_wf(
        output_dir=str(config.execution.output_dir))

    # MAIN WORKFLOW STRUCTURE
    # fmt: off
    workflow.connect([
        (inputnode, gradient_table, [("dwi_file", "dwi_file"),
                                     ("in_bvec", "in_bvec"),
                                     ("in_bval", "in_bval")]),
        (inputnode, dwi_reference_wf, [("dwi_file", "inputnode.dwi_file")]),
        (inputnode, dwi_derivatives_wf, [("dwi_file", "inputnode.source_file")
                                         ]),
        (gradient_table, dwi_reference_wf, [("b0_ixs", "inputnode.b0_ixs")]),
        (gradient_table, outputnode, [("out_rasb", "gradients_rasb")]),
        (outputnode, dwi_derivatives_wf, [
            ("dwi_reference", "inputnode.dwi_ref"),
            ("dwi_mask", "inputnode.dwi_mask"),
        ]),
    ])
    # fmt: on

    if config.workflow.run_reconall:
        from niworkflows.interfaces.nibabel import ApplyMask
        from niworkflows.anat.coregistration import init_bbreg_wf
        from ...utils.misc import sub_prefix as _prefix

        # Mask the T1w
        t1w_brain = pe.Node(ApplyMask(), name="t1w_brain")

        bbr_wf = init_bbreg_wf(
            debug=config.execution.debug,
            epi2t1w_init=config.workflow.dwi2t1w_init,
            omp_nthreads=config.nipype.omp_nthreads,
        )

        ds_report_reg = pe.Node(
            DerivativesDataSink(
                base_directory=str(config.execution.output_dir),
                datatype="figures",
            ),
            name="ds_report_reg",
            run_without_submitting=True,
        )

        def _bold_reg_suffix(fallback):
            return "coreg" if fallback else "bbregister"

        # fmt: off
        workflow.connect([
            (inputnode, bbr_wf, [
                ("fsnative2t1w_xfm", "inputnode.fsnative2t1w_xfm"),
                (("subject_id", _prefix), "inputnode.subject_id"),
                ("subjects_dir", "inputnode.subjects_dir"),
            ]),
            # T1w Mask
            (inputnode, t1w_brain, [("t1w_preproc", "in_file"),
                                    ("t1w_mask", "in_mask")]),
            (inputnode, ds_report_reg, [("dwi_file", "source_file")]),
            # BBRegister
            (dwi_reference_wf, bbr_wf, [("outputnode.ref_image",
                                         "inputnode.in_file")]),
            (bbr_wf, ds_report_reg, [('outputnode.out_report', 'in_file'),
                                     (('outputnode.fallback',
                                       _bold_reg_suffix), 'desc')]),
        ])
        # fmt: on

    if "eddy" not in config.workflow.ignore:
        # Eddy distortion correction
        eddy_wf = init_eddy_wf(debug=config.execution.debug)
        eddy_wf.inputs.inputnode.metadata = layout.get_metadata(str(dwi_file))

        ds_report_eddy = pe.Node(
            DerivativesDataSink(
                base_directory=str(config.execution.output_dir),
                desc="eddy",
                datatype="figures",
            ),
            name="ds_report_eddy",
            run_without_submitting=True,
        )

        eddy_report = pe.Node(
            SimpleBeforeAfter(
                before_label="Distorted",
                after_label="Eddy Corrected",
            ),
            name="eddy_report",
            mem_gb=0.1,
        )

        # fmt:off
        workflow.connect([
            (dwi_reference_wf, eddy_wf, [
                ("outputnode.dwi_file", "inputnode.dwi_file"),
                ("outputnode.dwi_mask", "inputnode.dwi_mask"),
            ]),
            (inputnode, eddy_wf, [("in_bvec", "inputnode.in_bvec"),
                                  ("in_bval", "inputnode.in_bval")]),
            (dwi_reference_wf, eddy_report, [("outputnode.ref_image", "before")
                                             ]),
            (eddy_wf, eddy_report, [('outputnode.eddy_ref_image', 'after')]),
            (dwi_reference_wf, ds_report_eddy, [("outputnode.dwi_file",
                                                 "source_file")]),
            (eddy_report, ds_report_eddy, [("out_report", "in_file")]),
        ])
        # fmt:on

    # REPORTING ############################################################
    reportlets_wf = init_reportlets_wf(
        str(config.execution.output_dir),
        sdc_report=has_fieldmap,
    )
    # fmt: off
    workflow.connect([
        (inputnode, reportlets_wf, [("dwi_file", "inputnode.source_file")]),
        (dwi_reference_wf, reportlets_wf, [
            ("outputnode.validation_report", "inputnode.validation_report"),
        ]),
        (outputnode, reportlets_wf, [
            ("dwi_reference", "inputnode.dwi_ref"),
            ("dwi_mask", "inputnode.dwi_mask"),
        ]),
    ])
    # fmt: on

    if not has_fieldmap:
        # fmt: off
        workflow.connect([
            (dwi_reference_wf, outputnode,
             [("outputnode.ref_image", "dwi_reference"),
              ("outputnode.dwi_mask", "dwi_mask")]),
        ])
        # fmt: on
        return workflow

    from niworkflows.interfaces.utility import KeySelect
    from sdcflows.workflows.apply.registration import init_coeff2epi_wf
    from sdcflows.workflows.apply.correction import init_unwarp_wf

    coeff2epi_wf = init_coeff2epi_wf(
        debug=config.execution.debug,
        omp_nthreads=config.nipype.omp_nthreads,
        write_coeff=True,
    )
    unwarp_wf = init_unwarp_wf(debug=config.execution.debug,
                               omp_nthreads=config.nipype.omp_nthreads)
    unwarp_wf.inputs.inputnode.metadata = layout.get_metadata(str(dwi_file))

    output_select = pe.Node(
        KeySelect(fields=["fmap", "fmap_ref", "fmap_coeff", "fmap_mask"]),
        name="output_select",
        run_without_submitting=True,
    )
    output_select.inputs.key = estimator_key[0]
    if len(estimator_key) > 1:
        config.loggers.workflow.warning(
            f"Several fieldmaps <{', '.join(estimator_key)}> are "
            f"'IntendedFor' <{dwi_file}>, using {estimator_key[0]}")

    sdc_report = pe.Node(
        SimpleBeforeAfter(
            before_label="Distorted",
            after_label="Corrected",
        ),
        name="sdc_report",
        mem_gb=0.1,
    )

    # fmt: off
    workflow.connect([
        (inputnode, output_select, [("fmap", "fmap"), ("fmap_ref", "fmap_ref"),
                                    ("fmap_coeff", "fmap_coeff"),
                                    ("fmap_mask", "fmap_mask"),
                                    ("fmap_id", "keys")]),
        (output_select, coeff2epi_wf, [("fmap_ref", "inputnode.fmap_ref"),
                                       ("fmap_coeff", "inputnode.fmap_coeff"),
                                       ("fmap_mask", "inputnode.fmap_mask")]),
        (dwi_reference_wf, coeff2epi_wf,
         [("outputnode.ref_image", "inputnode.target_ref"),
          ("outputnode.dwi_mask", "inputnode.target_mask")]),
        (dwi_reference_wf, unwarp_wf, [("outputnode.ref_image",
                                        "inputnode.distorted")]),
        (coeff2epi_wf, unwarp_wf, [("outputnode.fmap_coeff",
                                    "inputnode.fmap_coeff")]),
        (dwi_reference_wf, sdc_report, [("outputnode.ref_image", "before")]),
        (unwarp_wf, sdc_report, [("outputnode.corrected", "after"),
                                 ("outputnode.corrected_mask", "wm_seg")]),
        (sdc_report, reportlets_wf, [("out_report", "inputnode.sdc_report")]),
        (unwarp_wf, outputnode, [("outputnode.corrected", "dwi_reference"),
                                 ("outputnode.corrected_mask", "dwi_mask")]),
    ])
    # fmt: on

    return workflow
Esempio n. 13
0
def init_func_preproc_wf(
    workdir=None, name="func_preproc_wf", fmap_type=None, memcalc=MemoryCalculator()
):
    """
    simplified from fmriprep/workflows/bold/base.py
    """

    workflow = pe.Workflow(name=name)

    inputnode = pe.Node(
        niu.IdentityInterface(
            fields=["bold_file", "fmaps", "metadata", *in_attrs_from_anat_preproc_wf]
        ),
        name="inputnode",
    )

    outputnode = pe.Node(
        niu.IdentityInterface(
            fields=[
                *func_preproc_wf_out_attrs,
                "aroma_confounds",
                "aroma_metadata",
                "movpar_file",
                "rmsd_file",
                "skip_vols",
            ]
        ),
        name="outputnode",
    )

    def get_repetition_time(dic):
        return dic.get("RepetitionTime")

    metadatanode = pe.Node(niu.IdentityInterface(fields=["repetition_time"]), name="metadatanode")
    workflow.connect(
        [(inputnode, metadatanode, [(("metadata", get_repetition_time), "repetition_time")],)]
    )

    # Generate a brain-masked conversion of the t1w
    t1w_brain = pe.Node(ApplyMask(), name="t1w_brain")
    workflow.connect(
        [(inputnode, t1w_brain, [("t1w_preproc", "in_file"), ("t1w_mask", "in_mask")])]
    )

    # Generate a tentative boldref
    bold_reference_wf = init_bold_reference_wf(omp_nthreads=config.nipype.omp_nthreads)
    bold_reference_wf.get_node("inputnode").inputs.dummy_scans = config.workflow.dummy_scans
    workflow.connect(inputnode, "bold_file", bold_reference_wf, "inputnode.bold_file")
    workflow.connect(bold_reference_wf, "outputnode.skip_vols", outputnode, "skip_vols")

    # SDC (SUSCEPTIBILITY DISTORTION CORRECTION) or bypass ##########################
    bold_sdc_wf = init_sdc_estimate_wf(fmap_type=fmap_type)
    workflow.connect(
        [
            (
                inputnode,
                bold_sdc_wf,
                [("fmaps", "inputnode.fmaps"), ("metadata", "inputnode.metadata")],
            ),
            (
                bold_reference_wf,
                bold_sdc_wf,
                [
                    ("outputnode.ref_image", "inputnode.epi_file"),
                    ("outputnode.ref_image_brain", "inputnode.epi_brain"),
                    ("outputnode.bold_mask", "inputnode.epi_mask"),
                ],
            ),
        ]
    )

    # Top-level BOLD splitter
    bold_split = pe.Node(FSLSplit(dimension="t"), name="bold_split", mem_gb=memcalc.series_gb * 3)
    workflow.connect(inputnode, "bold_file", bold_split, "in_file")

    # HMC on the BOLD
    bold_hmc_wf = init_bold_hmc_wf(
        name="bold_hmc_wf", mem_gb=memcalc.series_gb, omp_nthreads=config.nipype.omp_nthreads,
    )
    workflow.connect(
        [
            (
                bold_reference_wf,
                bold_hmc_wf,
                [
                    ("outputnode.raw_ref_image", "inputnode.raw_ref_image"),
                    ("outputnode.bold_file", "inputnode.bold_file"),
                ],
            ),
            (
                bold_hmc_wf,
                outputnode,
                [("outputnode.movpar_file", "movpar_file"), ("outputnode.rmsd_file", "rmsd_file")],
            ),
        ]
    )

    # calculate BOLD registration to T1w
    bold_reg_wf = init_bold_reg_wf(
        freesurfer=config.workflow.run_reconall,
        use_bbr=config.workflow.use_bbr,
        bold2t1w_dof=config.workflow.bold2t1w_dof,
        bold2t1w_init=config.workflow.bold2t1w_init,
        mem_gb=memcalc.series_std_gb,
        omp_nthreads=config.nipype.omp_nthreads,
        use_compression=False,
    )
    workflow.connect(
        [
            (inputnode, bold_reg_wf, [("t1w_dseg", "inputnode.t1w_dseg")]),
            (t1w_brain, bold_reg_wf, [("out_file", "inputnode.t1w_brain")]),
            (bold_sdc_wf, bold_reg_wf, [("outputnode.epi_brain", "inputnode.ref_bold_brain")],),
        ]
    )

    # apply BOLD registration to T1w
    bold_t1_trans_wf = init_bold_t1_trans_wf(
        name="bold_t1_trans_wf",
        freesurfer=config.workflow.run_reconall,
        use_fieldwarp=fmap_type is not None,
        multiecho=False,
        mem_gb=memcalc.series_std_gb,
        omp_nthreads=config.nipype.omp_nthreads,
        use_compression=False,
    )
    workflow.connect(
        [
            (
                inputnode,
                bold_t1_trans_wf,
                [("bold_file", "inputnode.name_source"), ("t1w_mask", "inputnode.t1w_mask")],
            ),
            (
                bold_sdc_wf,
                bold_t1_trans_wf,
                [
                    ("outputnode.epi_brain", "inputnode.ref_bold_brain"),
                    ("outputnode.epi_mask", "inputnode.ref_bold_mask"),
                    ("outputnode.out_warp", "inputnode.fieldwarp"),
                ],
            ),
            (t1w_brain, bold_t1_trans_wf, [("out_file", "inputnode.t1w_brain")]),
            (bold_split, bold_t1_trans_wf, [("out_files", "inputnode.bold_split")]),
            (bold_hmc_wf, bold_t1_trans_wf, [("outputnode.xforms", "inputnode.hmc_xforms")],),
            (
                bold_reg_wf,
                bold_t1_trans_wf,
                [("outputnode.itk_bold_to_t1", "inputnode.itk_bold_to_t1")],
            ),
        ]
    )

    # Apply transforms in 1 shot
    # Only use uncompressed output if AROMA is to be run
    bold_bold_trans_wf = init_bold_preproc_trans_wf(
        mem_gb=memcalc.series_std_gb,
        omp_nthreads=config.nipype.omp_nthreads,
        use_compression=not config.execution.low_mem,
        use_fieldwarp=True,
        name="bold_bold_trans_wf",
    )
    # bold_bold_trans_wf.inputs.inputnode.name_source = ref_file
    workflow.connect(
        [
            (inputnode, bold_bold_trans_wf, [("bold_file", "inputnode.name_source")]),
            (bold_split, bold_bold_trans_wf, [("out_files", "inputnode.bold_file")]),
            (bold_hmc_wf, bold_bold_trans_wf, [("outputnode.xforms", "inputnode.hmc_xforms")],),
            (
                bold_sdc_wf,
                bold_bold_trans_wf,
                [
                    ("outputnode.out_warp", "inputnode.fieldwarp"),
                    ("outputnode.epi_mask", "inputnode.bold_mask"),
                ],
            ),
        ]
    )

    # Apply transforms in 1 shot
    # Only use uncompressed output if AROMA is to be run
    bold_std_trans_wf = init_bold_std_trans_wf(
        freesurfer=config.workflow.run_reconall,
        mem_gb=memcalc.series_std_gb,
        omp_nthreads=config.nipype.omp_nthreads,
        spaces=config.workflow.spaces,
        name="bold_std_trans_wf",
        use_compression=not config.execution.low_mem,
        use_fieldwarp=fmap_type is not None,
    )
    workflow.connect(
        [
            (
                inputnode,
                bold_std_trans_wf,
                [
                    ("template", "inputnode.templates"),
                    ("anat2std_xfm", "inputnode.anat2std_xfm"),
                    ("bold_file", "inputnode.name_source"),
                ],
            ),
            (bold_split, bold_std_trans_wf, [("out_files", "inputnode.bold_split")]),
            (bold_hmc_wf, bold_std_trans_wf, [("outputnode.xforms", "inputnode.hmc_xforms")],),
            (
                bold_reg_wf,
                bold_std_trans_wf,
                [("outputnode.itk_bold_to_t1", "inputnode.itk_bold_to_t1")],
            ),
            (
                bold_bold_trans_wf,
                bold_std_trans_wf,
                [("outputnode.bold_mask", "inputnode.bold_mask")],
            ),
            (bold_sdc_wf, bold_std_trans_wf, [("outputnode.out_warp", "inputnode.fieldwarp")],),
            (
                bold_std_trans_wf,
                outputnode,
                [
                    ("outputnode.bold_std", "bold_std"),
                    ("outputnode.bold_std_ref", "bold_std_ref"),
                    ("outputnode.bold_mask_std", "bold_mask_std"),
                ],
            ),
        ]
    )

    ica_aroma_wf = init_ica_aroma_wf(
        mem_gb=memcalc.series_std_gb,
        metadata={"RepetitionTime": np.nan},
        omp_nthreads=config.nipype.omp_nthreads,
        use_fieldwarp=True,
        err_on_aroma_warn=config.workflow.aroma_err_on_warn,
        aroma_melodic_dim=config.workflow.aroma_melodic_dim,
        name="ica_aroma_wf",
    )
    ica_aroma_wf.get_node("ica_aroma").inputs.denoise_type = "no"
    ica_aroma_wf.remove_nodes([ica_aroma_wf.get_node("add_nonsteady")])
    workflow.connect(
        [
            (inputnode, ica_aroma_wf, [("bold_file", "inputnode.name_source")]),
            (
                metadatanode,
                ica_aroma_wf,
                [("repetition_time", "melodic.tr_sec",), ("repetition_time", "ica_aroma.TR")],
            ),
            (bold_hmc_wf, ica_aroma_wf, [("outputnode.movpar_file", "inputnode.movpar_file")]),
            (bold_reference_wf, ica_aroma_wf, [("outputnode.skip_vols", "inputnode.skip_vols")]),
            (
                bold_std_trans_wf,
                ica_aroma_wf,
                [
                    ("outputnode.bold_std", "inputnode.bold_std"),
                    ("outputnode.bold_mask_std", "inputnode.bold_mask_std"),
                    ("outputnode.spatial_reference", "inputnode.spatial_reference"),
                ],
            ),
            (
                ica_aroma_wf,
                outputnode,
                [
                    ("outputnode.aroma_noise_ics", "aroma_noise_ics"),
                    ("outputnode.melodic_mix", "melodic_mix"),
                    ("outputnode.nonaggr_denoised_file", "nonaggr_denoised_file"),
                    ("outputnode.aroma_confounds", "aroma_confounds"),
                    ("outputnode.aroma_metadata", "aroma_metadata"),
                ],
            ),
        ]
    )

    bold_confounds_wf = init_bold_confs_wf(
        mem_gb=memcalc.series_std_gb,
        metadata={},
        regressors_all_comps=config.workflow.regressors_all_comps,
        regressors_fd_th=config.workflow.regressors_fd_th,
        regressors_dvars_th=config.workflow.regressors_dvars_th,
        name="bold_confounds_wf",
    )
    bold_confounds_wf.get_node("inputnode").inputs.t1_transform_flags = [False]
    workflow.connect(
        [
            (
                inputnode,
                bold_confounds_wf,
                [("t1w_tpms", "inputnode.t1w_tpms"), ("t1w_mask", "inputnode.t1w_mask")],
            ),
            (
                bold_hmc_wf,
                bold_confounds_wf,
                [
                    ("outputnode.movpar_file", "inputnode.movpar_file"),
                    ("outputnode.rmsd_file", "inputnode.rmsd_file"),
                ],
            ),
            (
                bold_reg_wf,
                bold_confounds_wf,
                [("outputnode.itk_t1_to_bold", "inputnode.t1_bold_xform")],
            ),
            (
                bold_reference_wf,
                bold_confounds_wf,
                [("outputnode.skip_vols", "inputnode.skip_vols")],
            ),
            (bold_confounds_wf, outputnode, [("outputnode.confounds_file", "confounds")]),
            (
                bold_confounds_wf,
                outputnode,
                [("outputnode.confounds_metadata", "confounds_metadata")],
            ),
            (
                metadatanode,
                bold_confounds_wf,
                [
                    ("repetition_time", "acompcor.repetition_time"),
                    ("repetition_time", "tcompcor.repetition_time"),
                ],
            ),
            (
                bold_bold_trans_wf,
                bold_confounds_wf,
                [
                    ("outputnode.bold", "inputnode.bold"),
                    ("outputnode.bold_mask", "inputnode.bold_mask"),
                ],
            ),
        ]
    )

    carpetplot_wf = init_carpetplot_wf(
        mem_gb=memcalc.series_std_gb,
        metadata={"RepetitionTime": np.nan},
        cifti_output=config.workflow.cifti_output,
        name="carpetplot_wf",
    )
    carpetplot_select_std = pe.Node(
        KeySelect(fields=["std2anat_xfm"], key="MNI152NLin2009cAsym"),
        name="carpetplot_select_std",
        run_without_submitting=True,
    )

    workflow.connect(
        [
            (
                inputnode,
                carpetplot_select_std,
                [("std2anat_xfm", "std2anat_xfm"), ("template", "keys")],
            ),
            (carpetplot_select_std, carpetplot_wf, [("std2anat_xfm", "inputnode.std2anat_xfm")],),
            (
                bold_bold_trans_wf,
                carpetplot_wf,
                [
                    ("outputnode.bold", "inputnode.bold"),
                    ("outputnode.bold_mask", "inputnode.bold_mask"),
                ],
            ),
            (
                bold_reg_wf,
                carpetplot_wf,
                [("outputnode.itk_t1_to_bold", "inputnode.t1_bold_xform")],
            ),
            (
                bold_confounds_wf,
                carpetplot_wf,
                [("outputnode.confounds_file", "inputnode.confounds_file")],
            ),
            (metadatanode, carpetplot_wf, [("repetition_time", "conf_plot.tr")]),
        ]
    )

    # Custom

    make_reportnode(workflow)
    assert workdir is not None
    make_reportnode_datasink(workflow, workdir)

    return workflow
Esempio n. 14
0
def init_anat_preproc_wf(
    *,
    bids_root,
    longitudinal,
    t2w,
    omp_nthreads,
    output_dir,
    skull_strip_mode,
    skull_strip_template,
    spaces,
    debug=False,
    existing_derivatives=None,
    name="anat_preproc_wf",
    skull_strip_fixed_seed=False,
):
    """
    Stage the anatomical preprocessing steps of *sMRIPrep*.
    This includes:
      - T1w reference: realigning and then averaging T1w images.
      - Brain extraction and INU (bias field) correction.
      - Brain tissue segmentation.
      - Spatial normalization to standard spaces.
    .. include:: ../links.rst
    Workflow Graph
        .. workflow::
            :graph2use: orig
            :simple_form: yes
            from niworkflows.utils.spaces import SpatialReferences, Reference
            from smriprep.workflows.anatomical import init_anat_preproc_wf
            wf = init_anat_preproc_wf(
                bids_root='.',
                longitudinal=False,
                t2w=['t2w.nii.gz'],
                omp_nthreads=1,
                output_dir='.',
                skull_strip_mode='force',
                skull_strip_template=Reference('OASIS30ANTs'),
                spaces=SpatialReferences(spaces=['Fischer344']),
            )
    Parameters
    ----------
    bids_root : :obj:`str`
        Path of the input BIDS dataset root
    existing_derivatives : :obj:`dict` or None
        Dictionary mapping output specification attribute names and
        paths to corresponding derivatives.
    longitudinal : :obj:`bool`
        Create unbiased structural template, regardless of number of inputs
        (may increase runtime)
    t1w : :obj:`list`
        List of T1-weighted structural images.
    omp_nthreads : :obj:`int`
        Maximum number of threads an individual process may use
    output_dir : :obj:`str`
        Directory in which to save derivatives
    skull_strip_template : :py:class:`~niworkflows.utils.spaces.Reference`
        Spatial reference to use in atlas-based brain extraction.
    spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences`
        Object containing standard and nonstandard space specifications.
    debug : :obj:`bool`
        Enable debugging outputs
    name : :obj:`str`, optional
        Workflow name (default: anat_preproc_wf)
    skull_strip_mode : :obj:`str`
        Determiner for T1-weighted skull stripping (`force` ensures skull stripping,
        `skip` ignores skull stripping, and `auto` automatically ignores skull stripping
        if pre-stripped brains are detected).
    skull_strip_fixed_seed : :obj:`bool`
        Do not use a random seed for skull-stripping - will ensure
        run-to-run replicability when used with --omp-nthreads 1
        (default: ``False``).
    Inputs
    ------
    t1w
        List of T1-weighted structural images
    t2w
        List of T2-weighted structural images
    roi
        A mask to exclude regions during standardization
    flair
        List of FLAIR images

    Outputs
    -------
    t1w_preproc
        The T1w reference map, which is calculated as the average of bias-corrected
        and preprocessed T1w images, defining the anatomical space.
    t1w_brain
        Skull-stripped ``t1w_preproc``
    t1w_mask
        Brain (binary) mask estimated by brain extraction.
    anat_dseg
        Brain tissue segmentation of the preprocessed structural image, including
        gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF).
    anat_tpms
        List of tissue probability maps corresponding to ``anat_dseg``.
    std_preproc
        T1w reference resampled in one or more standard spaces.
    std_mask
        Mask of skull-stripped template, in MNI space
    std_dseg
        Segmentation, resampled into MNI space
    std_tpms
        List of tissue probability maps in MNI space
    subjects_dir
        FreeSurfer SUBJECTS_DIR
    anat2std_xfm
        Nonlinear spatial transform to resample imaging data given in anatomical space
        into standard space.
    std2anat_xfm
        Inverse transform of the above.
    See Also
    --------
    * :py:func:`~niworkflows.anat.ants.init_brain_extraction_wf`
    * :py:func:`~smriprep.workflows.surfaces.init_surface_recon_wf`
    """
    workflow = Workflow(name=name)
    num_t2w = len(t2w)
    desc = """Anatomical data preprocessing
: """
    desc += """\
A total of {num_t2w} T2-weighted (T2w) images were found within the input
BIDS dataset."""

    inputnode = pe.Node(niu.IdentityInterface(fields=["t2w", "roi"]), name="inputnode")

    outputnode = pe.Node(
        niu.IdentityInterface(
            fields=[
                "t2w_preproc",
                "t2w_mask",
                "t2w_dseg",
                "t2w_tpms",
                "std_preproc",
                "std_mask",
                "std_dseg",
                "std_tpms",
                "anat2std_xfm",
                "std2anat_xfm",
                "template",
            ]
        ),
        name="outputnode",
    )

    # Connect reportlets workflows
    anat_reports_wf = init_anat_reports_wf(output_dir=output_dir,)
    workflow.connect(
        [
            (
                outputnode,
                anat_reports_wf,
                [
                    ("t2w_preproc", "inputnode.t1w_preproc"),
                    ("t2w_mask", "inputnode.t1w_mask"),
                    ("t2w_dseg", "inputnode.anat_dseg"),
                ],
            ),
        ]
    )

    if existing_derivatives is not None:
        LOGGER.log(
            25,
            "Anatomical workflow will reuse prior derivatives found in the "
            "output folder (%s).",
            output_dir,
        )
        desc += """
Anatomical preprocessing was reused from previously existing derivative objects.\n"""
        workflow.__desc__ = desc

        templates = existing_derivatives.pop("template")
        templatesource = pe.Node(
            niu.IdentityInterface(fields=["template"]), name="templatesource"
        )
        templatesource.iterables = [("template", templates)]
        outputnode.inputs.template = templates

        for field, value in existing_derivatives.items():
            setattr(outputnode.inputs, field, value)

        anat_reports_wf.inputs.inputnode.source_file = fix_multi_source_name(
            [existing_derivatives["t2w_preproc"]], modality="T2w"
        )

        stdselect = pe.Node(
            KeySelect(fields=["std_preproc", "std_mask"], keys=templates),
            name="stdselect",
            run_without_submitting=True,
        )
        workflow.connect(
            [
                (
                    inputnode,
                    outputnode,
                    [("subjects_dir", "subjects_dir"), ("subject_id", "subject_id")],
                ),
                (
                    inputnode,
                    anat_reports_wf,
                    [
                        ("subjects_dir", "inputnode.subjects_dir"),
                        ("subject_id", "inputnode.subject_id"),
                    ],
                ),
                (templatesource, stdselect, [("template", "key")]),
                (
                    outputnode,
                    stdselect,
                    [("std_preproc", "std_preproc"), ("std_mask", "std_mask")],
                ),
                (
                    stdselect,
                    anat_reports_wf,
                    [
                        ("key", "inputnode.template"),
                        ("std_preproc", "inputnode.std_t1w"),
                        ("std_mask", "inputnode.std_mask"),
                    ],
                ),
            ]
        )
        return workflow

    # The workflow is not cached.
    desc += (
        """
All of them were corrected for intensity non-uniformity (INU)
"""
        if num_t2w > 1
        else """\
The T2-weighted (T2w) image was corrected for intensity non-uniformity (INU)
"""
    )
    desc += """\
with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} \
[@ants, RRID:SCR_004757]"""
    desc += (
        ".\n"
        if num_t2w > 1
        else ", and used as T2w-reference throughout the workflow.\n"
    )

    desc += """\
The T2w-reference was then skull-stripped with a *Nipype* implementation of
the `antsBrainExtraction.sh` workflow (from ANTs), using {skullstrip_tpl}
as target template.
Brain tissue segmentation of cerebrospinal fluid (CSF),
white-matter (WM) and gray-matter (GM) was performed on
the brain-extracted T1w using `fast` [FSL {fsl_ver}, RRID:SCR_002823,
@fsl_fast].
"""

    workflow.__desc__ = desc.format(
        ants_ver=ANTsInfo.version() or "(version unknown)",
        fsl_ver=fsl.FAST().version or "(version unknown)",
        num_t2w=num_t2w,
        skullstrip_tpl=skull_strip_template.fullname,
    )

    buffernode = pe.Node(
        niu.IdentityInterface(fields=["t2w_brain", "t2w_mask"]), name="buffernode"
    )

    # 1. Anatomical reference generation - average input T1w images.
    anat_template_wf = init_anat_template_wf(
        longitudinal=longitudinal, omp_nthreads=omp_nthreads, num_t1w=num_t2w
    )

    anat_validate = pe.Node(
        ValidateImage(), name="anat_validate", run_without_submitting=True
    )

    # 2. Brain-extraction and INU (bias field) correction.
    if skull_strip_mode == "auto":
        import numpy as np
        import nibabel as nb

        def _is_skull_stripped(imgs):
            """Check if T1w images are skull-stripped."""

            def _check_img(img):
                data = np.abs(nb.load(img).get_fdata(dtype=np.float32))
                sidevals = (
                    data[0, :, :].sum()
                    + data[-1, :, :].sum()
                    + data[:, 0, :].sum()
                    + data[:, -1, :].sum()
                    + data[:, :, 0].sum()
                    + data[:, :, -1].sum()
                )
                return sidevals < 10

            return all(_check_img(img) for img in imgs)

        skull_strip_mode = _is_skull_stripped(t2w)

    if skull_strip_mode in (True, "skip"):
        raise NotImplementedError("Cannot run on already skull-stripped images.")
    else:
        # ants_affine_init?
        brain_extraction_wf = init_rodent_brain_extraction_wf(
            template_id=skull_strip_template.space,
            omp_nthreads=omp_nthreads,
            debug=debug
        )

    # 3. Spatial normalization
    anat_norm_wf = init_anat_norm_wf(
        debug=debug,
        omp_nthreads=omp_nthreads,
        templates=spaces.get_spaces(nonstandard=False, dim=(3,)),
    )

    # fmt:off
    workflow.connect([
        # Step 1.
        (inputnode, anat_template_wf, [('t2w', 'inputnode.t1w')]),
        (anat_template_wf, anat_validate, [
            ('outputnode.t1w_ref', 'in_file')]),
        (anat_validate, brain_extraction_wf, [
            ('out_file', 'inputnode.in_files')]),
        (brain_extraction_wf, outputnode, [
            (('outputnode.out_corrected', _pop), 't2w_preproc')]),
        (anat_template_wf, outputnode, [
            ('outputnode.t1w_realign_xfm', 't2w_ref_xfms')]),
        (buffernode, outputnode, [('t2w_brain', 't2w_brain'),
                                  ('t2w_mask', 't2w_mask')]),
        # Steps 2 and 3
        (inputnode, anat_norm_wf, [
            (('t2w', fix_multi_source_name), 'inputnode.orig_t1w'),
            ('roi', 'inputnode.lesion_mask')]),
        (brain_extraction_wf, anat_norm_wf, [
            (('outputnode.out_corrected', _pop), 'inputnode.moving_image')]),
        (buffernode, anat_norm_wf, [('t2w_mask', 'inputnode.moving_mask')]),
        (anat_norm_wf, outputnode, [
            ('poutputnode.standardized', 'std_preproc'),
            ('poutputnode.std_mask', 'std_mask'),
            ('outputnode.template', 'template'),
            ('outputnode.anat2std_xfm', 'anat2std_xfm'),
            ('outputnode.std2anat_xfm', 'std2anat_xfm'),
        ]),
        # Connect reportlets
        (inputnode, anat_reports_wf, [
            (('t2w', fix_multi_source_name), 'inputnode.source_file')]),
        (outputnode, anat_reports_wf, [
            ('std_preproc', 'inputnode.std_t1w'),
            ('std_mask', 'inputnode.std_mask'),
        ]),
        (anat_template_wf, anat_reports_wf, [
            ('outputnode.out_report', 'inputnode.t1w_conform_report')]),
        (anat_norm_wf, anat_reports_wf, [
            ('poutputnode.template', 'inputnode.template')]),
    ])
    # fmt:off

    # Write outputs ############################################3
    anat_derivatives_wf = init_anat_derivatives_wf(
        bids_root=bids_root,
        num_t1w=num_t2w,
        output_dir=output_dir,
        spaces=spaces,
    )

    # fmt:off
    workflow.connect([
        # Connect derivatives
        (anat_template_wf, anat_derivatives_wf, [
            ('outputnode.t1w_valid_list', 'inputnode.source_files')]),
        (anat_norm_wf, anat_derivatives_wf, [
            ('outputnode.template', 'inputnode.template'),
            ('outputnode.anat2std_xfm', 'inputnode.anat2std_xfm'),
            ('outputnode.std2anat_xfm', 'inputnode.std2anat_xfm')
        ]),
        (outputnode, anat_derivatives_wf, [
            ('t2w_ref_xfms', 'inputnode.t1w_ref_xfms'),
            ('t2w_preproc', 'inputnode.t1w_preproc'),
            ('t2w_mask', 'inputnode.t1w_mask'),
        ]),
    ])
    # fmt:on

    # 4. Brain tissue segmentation - FAST produces: 0 (bg), 1 (wm), 2 (csf), 3 (gm)
    gm_tpm = get("Fischer344", label="GM", suffix="probseg")
    wm_tpm = get("Fischer344", label="WM", suffix="probseg")
    csf_tpm = get("Fischer344", label="CSF", suffix="probseg")

    xfm_gm = pe.Node(
        ApplyTransforms(input_image=_pop(gm_tpm), interpolation="MultiLabel"),
        name="xfm_gm",
    )
    xfm_wm = pe.Node(
        ApplyTransforms(input_image=_pop(wm_tpm), interpolation="MultiLabel"),
        name="xfm_wm",
    )
    xfm_csf = pe.Node(
        ApplyTransforms(input_image=_pop(csf_tpm), interpolation="MultiLabel"),
        name="xfm_csf",
    )

    mrg_tpms = pe.Node(niu.Merge(3), name="mrg_tpms")

    anat_dseg = pe.Node(
        FAST(
            segments=True,
            probability_maps=True,
            bias_iters=0,
            no_bias=True,
        ),
        name="anat_dseg",
        mem_gb=3,
    )

    # Change LookUp Table - BIDS wants: 0 (bg), 1 (gm), 2 (wm), 3 (csf)
    lut_anat_dseg = pe.Node(
        niu.Function(function=_apply_bids_lut), name="lut_anat_dseg"
    )
    lut_anat_dseg.inputs.lut = (0, 3, 2, 1)  # Maps: 0 -> 0, 3 -> 1, 2 -> 2, 1 -> 3

    fast2bids = pe.Node(
        niu.Function(function=_probseg_fast2bids),
        name="fast2bids",
        run_without_submitting=True,
    )

    # 5. Move native dseg & tpms back to standard space
    xfm_dseg = pe.Node(ApplyTransforms(interpolation="MultiLabel"), name="xfm_dseg")
    xfm_tpms = pe.MapNode(
        ApplyTransforms(
            dimension=3, default_value=0, float=True, interpolation="Gaussian"
        ),
        iterfield=["input_image"],
        name="xfm_tpms",
    )

    # fmt:off
    workflow.connect([
        # step 4
        (brain_extraction_wf, buffernode, [
            (('outputnode.out_brain', _pop), 't2w_brain'),
            ('outputnode.out_mask', 't2w_mask')]),
        (buffernode, anat_dseg, [('t2w_brain', 'in_files')]),
        (brain_extraction_wf, xfm_gm, [(
            ('outputnode.out_corrected', _pop), 'reference_image')]),
        (brain_extraction_wf, xfm_wm, [(
            ('outputnode.out_corrected', _pop), 'reference_image')]),
        (brain_extraction_wf, xfm_csf, [(
            ('outputnode.out_corrected', _pop), 'reference_image')]),
        (anat_norm_wf, xfm_gm, [(
            'outputnode.std2anat_xfm', 'transforms')]),
        (anat_norm_wf, xfm_wm, [(
            'outputnode.std2anat_xfm', 'transforms')]),
        (anat_norm_wf, xfm_csf, [(
            'outputnode.std2anat_xfm', 'transforms')]),
        (xfm_gm, mrg_tpms, [('output_image', 'in1')]),
        (xfm_wm, mrg_tpms, [('output_image', 'in2')]),
        (xfm_csf, mrg_tpms, [('output_image', 'in3')]),
        (mrg_tpms, anat_dseg, [('out', 'other_priors')]),
        (anat_dseg, lut_anat_dseg, [('partial_volume_map', 'in_dseg')]),
        (lut_anat_dseg, outputnode, [('out', 't2w_dseg')]),
        (anat_dseg, fast2bids, [('partial_volume_files', 'inlist')]),
        (fast2bids, outputnode, [('out', 't2w_tpms')]),
        (outputnode, anat_derivatives_wf, [
            ('t2w_tpms', 'inputnode.anat_tpms'),
            ('t2w_dseg', 'inputnode.anat_dseg')
        ]),
        # step 5
        (anat_norm_wf, xfm_dseg, [('poutputnode.standardized', 'reference_image')]),
        (lut_anat_dseg, xfm_dseg, [('out', 'input_image')]),
        (anat_norm_wf, xfm_dseg, [('poutputnode.anat2std_xfm', 'transforms')]),
        (anat_norm_wf, xfm_tpms, [('poutputnode.standardized', 'reference_image')]),
        (fast2bids, xfm_tpms, [('out', 'input_image')]),
        (anat_norm_wf, xfm_tpms, [('poutputnode.anat2std_xfm', 'transforms')]),
        (xfm_dseg, outputnode, [('output_image', 'std_dseg')]),
        (xfm_tpms, outputnode, [('output_image', 'std_tpms')]),
        (outputnode, anat_derivatives_wf, [
            ('std_dseg', 'inputnode.std_dseg'),
            ('std_tpms', 'inputnode.std_tpms')
        ]),
    ])
    # fmt:on
    return workflow
Esempio n. 15
0
def init_func_preproc_wf(bold_file):
    """
    This workflow controls the functional preprocessing stages of *fMRIPrep*.

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

            from fprodents.workflows.tests import mock_config
            from fprodents import config
            from fprodents.workflows.bold.base import init_func_preproc_wf
            with mock_config():
                bold_file = config.execution.bids_dir / 'sub-01' / 'func' \
                    / 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz'
                wf = init_func_preproc_wf(str(bold_file))

    Inputs
    ------
    bold_file
        BOLD series NIfTI file
    t1w_preproc
        Bias-corrected structural template image
    t1w_mask
        Mask of the skull-stripped template image
    anat_dseg
        Segmentation of preprocessed structural image, including
        gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF)
    t1w_asec
        Segmentation of structural image, done with FreeSurfer.
    t1w_aparc
        Parcellation of structural image, done with FreeSurfer.
    anat_tpms
        List of tissue probability maps in T1w space
    template
        List of templates to target
    anat2std_xfm
        List of transform files, collated with templates
    std2anat_xfm
        List of inverse transform files, collated with templates
    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

    Outputs
    -------
    bold_t1
        BOLD series, resampled to T1w space
    bold_mask_t1
        BOLD series mask in T1w space
    bold_std
        BOLD series, resampled to template space
    bold_mask_std
        BOLD series mask in template space
    confounds
        TSV of confounds
    surfaces
        BOLD series, resampled to FreeSurfer surfaces
    aroma_noise_ics
        Noise components identified by ICA-AROMA
    melodic_mix
        FSL MELODIC mixing matrix

    See Also
    --------

    * :py:func:`~fprodents.workflows.bold.stc.init_bold_stc_wf`
    * :py:func:`~fprodents.workflows.bold.hmc.init_bold_hmc_wf`
    * :py:func:`~fprodents.workflows.bold.t2s.init_bold_t2s_wf`
    * :py:func:`~fprodents.workflows.bold.registration.init_bold_t1_trans_wf`
    * :py:func:`~fprodents.workflows.bold.registration.init_bold_reg_wf`
    * :py:func:`~fprodents.workflows.bold.confounds.init_bold_confounds_wf`
    * :py:func:`~fprodents.workflows.bold.confounds.init_ica_aroma_wf`
    * :py:func:`~fprodents.workflows.bold.resampling.init_bold_std_trans_wf`
    * :py:func:`~fprodents.workflows.bold.resampling.init_bold_preproc_trans_wf`
    * :py:func:`~fprodents.workflows.bold.resampling.init_bold_surf_wf`

    """
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms
    from niworkflows.interfaces.nibabel import ApplyMask
    from niworkflows.interfaces.utility import KeySelect, DictMerge
    from nipype.interfaces.freesurfer.utils import LTAConvert

    from ...patch.utils import extract_entities

    mem_gb = {"filesize": 1, "resampled": 1, "largemem": 1}
    bold_tlen = 10

    # Have some options handy
    omp_nthreads = config.nipype.omp_nthreads
    spaces = config.workflow.spaces
    output_dir = str(config.execution.output_dir)

    # Extract BIDS entities and metadata from BOLD file(s)
    entities = extract_entities(bold_file)
    layout = config.execution.layout

    # Take first file as reference
    ref_file = pop_file(bold_file)
    metadata = layout.get_metadata(ref_file)

    echo_idxs = listify(entities.get("echo", []))
    multiecho = len(echo_idxs) > 2
    if len(echo_idxs) == 1:
        config.loggers.warning(
            f"Running a single echo <{ref_file}> from a seemingly multi-echo dataset."
        )
        bold_file = ref_file  # Just in case - drop the list

    if len(echo_idxs) == 2:
        raise RuntimeError(
            "Multi-echo processing requires at least three different echos (found two)."
        )

    if multiecho:
        # Drop echo entity for future queries, have a boolean shorthand
        entities.pop("echo", None)
        # reorder echoes from shortest to largest
        tes, bold_file = zip(*sorted([(layout.get_metadata(bf)["EchoTime"], bf)
                                      for bf in bold_file]))
        ref_file = bold_file[0]  # Reset reference to be the shortest TE

    if os.path.isfile(ref_file):
        bold_tlen, mem_gb = _create_mem_gb(ref_file)

    wf_name = _get_wf_name(ref_file)
    config.loggers.workflow.debug(
        "Creating bold processing workflow for <%s> (%.2f GB / %d TRs). "
        "Memory resampled/largemem=%.2f/%.2f GB.",
        ref_file,
        mem_gb["filesize"],
        bold_tlen,
        mem_gb["resampled"],
        mem_gb["largemem"],
    )

    # Find associated sbref, if possible
    entities["suffix"] = "sbref"
    entities["extension"] = ["nii", "nii.gz"]  # Overwrite extensions
    sbref_files = layout.get(return_type="file", **entities)

    sbref_msg = f"No single-band-reference found for {os.path.basename(ref_file)}."
    if sbref_files and "sbref" in config.workflow.ignore:
        sbref_msg = "Single-band reference file(s) found and ignored."
    elif sbref_files:
        sbref_msg = "Using single-band reference file(s) {}.".format(",".join(
            [os.path.basename(sbf) for sbf in sbref_files]))
    config.loggers.workflow.info(sbref_msg)

    # Check whether STC must/can be run
    run_stc = (bool(metadata.get("SliceTiming"))
               and 'slicetiming' not in config.workflow.ignore)

    # Build workflow
    workflow = Workflow(name=wf_name)
    workflow.__postdesc__ = """\
All resamplings can be performed with *a single interpolation
step* by composing all the pertinent transformations (i.e. head-motion
transform matrices, susceptibility distortion correction when available,
and co-registrations to anatomical and output spaces).
Gridded (volumetric) resamplings were performed using `antsApplyTransforms` (ANTs),
configured with Lanczos interpolation to minimize the smoothing
effects of other kernels [@lanczos].
Non-gridded (surface) resamplings were performed using `mri_vol2surf`
(FreeSurfer).
"""

    inputnode = pe.Node(
        niu.IdentityInterface(fields=[
            "bold_file",
            "ref_file",
            "bold_ref_xfm",
            "n_dummy_scans",
            "validation_report",
            "subjects_dir",
            "subject_id",
            "anat_preproc",
            "anat_mask",
            "anat_dseg",
            "anat_tpms",
            "anat2std_xfm",
            "std2anat_xfm",
            "template",
            "anat2fsnative_xfm",
            "fsnative2anat_xfm",
        ]),
        name="inputnode",
    )
    inputnode.inputs.bold_file = bold_file

    outputnode = pe.Node(
        niu.IdentityInterface(fields=[
            "bold_mask",
            "bold_t1",
            "bold_t1_ref",
            "bold_mask_t1",
            "bold_std",
            "bold_std_ref",
            "bold_mask_std",
            "bold_native",
            "surfaces",
            "confounds",
            "aroma_noise_ics",
            "melodic_mix",
            "nonaggr_denoised_file",
            "confounds_metadata",
        ]),
        name="outputnode",
    )

    # Generate a brain-masked conversion of the t1w and bold reference images
    t1w_brain = pe.Node(ApplyMask(), name="t1w_brain")

    lta_convert = pe.Node(LTAConvert(out_fsl=True, out_itk=True),
                          name="lta_convert")

    # BOLD buffer: an identity used as a pointer to either the original BOLD
    # or the STC'ed one for further use.
    boldbuffer = pe.Node(niu.IdentityInterface(fields=["bold_file"]),
                         name="boldbuffer")

    summary = pe.Node(
        FunctionalSummary(
            slice_timing=run_stc,
            registration="FSL",
            registration_dof=config.workflow.bold2t1w_dof,
            registration_init=config.workflow.bold2t1w_init,
            pe_direction=metadata.get("PhaseEncodingDirection"),
            echo_idx=echo_idxs,
            tr=metadata.get("RepetitionTime"),
            distortion_correction="<not implemented>",
        ),
        name="summary",
        mem_gb=config.DEFAULT_MEMORY_MIN_GB,
        run_without_submitting=True,
    )
    summary.inputs.dummy_scans = config.workflow.dummy_scans

    func_derivatives_wf = init_func_derivatives_wf(
        bids_root=layout.root,
        metadata=metadata,
        output_dir=output_dir,
        spaces=spaces,
        use_aroma=config.workflow.use_aroma,
    )

    # fmt:off
    workflow.connect([
        (outputnode, func_derivatives_wf, [
            ('bold_t1', 'inputnode.bold_t1'),
            ('bold_t1_ref', 'inputnode.bold_t1_ref'),
            ('bold_mask_t1', 'inputnode.bold_mask_t1'),
            ('bold_native', 'inputnode.bold_native'),
            ('confounds', 'inputnode.confounds'),
            ('surfaces', 'inputnode.surf_files'),
            ('aroma_noise_ics', 'inputnode.aroma_noise_ics'),
            ('melodic_mix', 'inputnode.melodic_mix'),
            ('nonaggr_denoised_file', 'inputnode.nonaggr_denoised_file'),
            ('confounds_metadata', 'inputnode.confounds_metadata'),
        ]),
    ])
    # fmt:on

    # Top-level BOLD splitter
    bold_split = pe.Node(FSLSplit(dimension="t"),
                         name="bold_split",
                         mem_gb=mem_gb["filesize"] * 3)

    # calculate BOLD registration to T1w
    bold_reg_wf = init_bold_reg_wf(
        bold2t1w_dof=config.workflow.bold2t1w_dof,
        bold2t1w_init=config.workflow.bold2t1w_init,
        mem_gb=mem_gb["resampled"],
        name="bold_reg_wf",
        omp_nthreads=omp_nthreads,
        use_compression=False,
    )

    # apply BOLD registration to T1w
    bold_t1_trans_wf = init_bold_t1_trans_wf(
        name="bold_t1_trans_wf",
        use_fieldwarp=False,
        multiecho=multiecho,
        mem_gb=mem_gb["resampled"],
        omp_nthreads=omp_nthreads,
        use_compression=False,
    )

    t1w_mask_bold_tfm = pe.Node(ApplyTransforms(interpolation="MultiLabel"),
                                name="t1w_mask_bold_tfm",
                                mem_gb=0.1)

    # get confounds
    bold_confounds_wf = init_bold_confs_wf(
        mem_gb=mem_gb["largemem"],
        metadata=metadata,
        regressors_all_comps=config.workflow.regressors_all_comps,
        regressors_fd_th=config.workflow.regressors_fd_th,
        regressors_dvars_th=config.workflow.regressors_dvars_th,
        name="bold_confounds_wf",
    )
    bold_confounds_wf.get_node("inputnode").inputs.t1_transform_flags = [False]

    # Apply transforms in 1 shot
    # Only use uncompressed output if AROMA is to be run
    bold_bold_trans_wf = init_bold_preproc_trans_wf(
        mem_gb=mem_gb["resampled"],
        omp_nthreads=omp_nthreads,
        use_compression=not config.execution.low_mem,
        use_fieldwarp=False,
        name="bold_bold_trans_wf",
    )
    bold_bold_trans_wf.inputs.inputnode.name_source = ref_file

    # SLICE-TIME CORRECTION (or bypass) #############################################
    if run_stc:
        bold_stc_wf = init_bold_stc_wf(name="bold_stc_wf", metadata=metadata)
        # fmt:off
        workflow.connect([
            (inputnode, bold_stc_wf, [("n_dummy_scans", "inputnode.skip_vols")
                                      ]),
            (bold_stc_wf, boldbuffer, [("outputnode.stc_file", "bold_file")]),
        ])
        # fmt:on
        if not multiecho:
            # fmt:off
            workflow.connect([(inputnode, bold_stc_wf,
                               [('bold_file', 'inputnode.bold_file')])])
            # fmt:on
        else:  # for meepi, iterate through stc_wf for all workflows
            meepi_echos = boldbuffer.clone(name="meepi_echos")
            meepi_echos.iterables = ("bold_file", bold_file)
            # fmt:off
            workflow.connect([(meepi_echos, bold_stc_wf,
                               [('bold_file', 'inputnode.bold_file')])])
            # fmt:on
    elif not multiecho:  # STC is too short or False
        # bypass STC from original BOLD to the splitter through boldbuffer
        # fmt:off
        workflow.connect([(inputnode, boldbuffer, [('bold_file', 'bold_file')])
                          ])
        # fmt:on
    else:
        # for meepi, iterate over all meepi echos to boldbuffer
        boldbuffer.iterables = ("bold_file", bold_file)

    # MULTI-ECHO EPI DATA #############################################
    if multiecho:
        from niworkflows.func.util import init_skullstrip_bold_wf

        skullstrip_bold_wf = init_skullstrip_bold_wf(name="skullstrip_bold_wf")

        inputnode.inputs.bold_file = ref_file  # Replace reference w first echo

        join_echos = pe.JoinNode(
            niu.IdentityInterface(fields=["bold_files"]),
            joinsource=("meepi_echos" if run_stc is True else "boldbuffer"),
            joinfield=["bold_files"],
            name="join_echos",
        )

        # create optimal combination, adaptive T2* map
        bold_t2s_wf = init_bold_t2s_wf(
            echo_times=tes,
            mem_gb=mem_gb["resampled"],
            omp_nthreads=omp_nthreads,
            name="bold_t2smap_wf",
        )

        # fmt:off
        workflow.connect([
            (skullstrip_bold_wf, join_echos,
             [('outputnode.skull_stripped_file', 'bold_files')]),
            (join_echos, bold_t2s_wf, [('bold_files', 'inputnode.bold_file')]),
        ])
        # fmt:on

    # MAIN WORKFLOW STRUCTURE #######################################################
    # fmt:off
    workflow.connect([
        (inputnode, bold_reg_wf, [('anat_preproc', 'inputnode.t1w_brain'),
                                  ('ref_file', 'inputnode.ref_bold_brain')]),
        (inputnode, t1w_brain, [('anat_preproc', 'in_file'),
                                ('anat_mask', 'in_mask')]),
        # convert bold reference LTA transform to other formats
        (inputnode, lta_convert, [('bold_ref_xfm', 'in_lta')]),
        # BOLD buffer has slice-time corrected if it was run, original otherwise
        (boldbuffer, bold_split, [('bold_file', 'in_file')]),
        (inputnode, summary, [('n_dummy_scans', 'algo_dummy_scans')]),
        # EPI-T1 registration workflow
        (inputnode, bold_t1_trans_wf, [('bold_file', 'inputnode.name_source'),
                                       ('anat_mask', 'inputnode.t1w_mask'),
                                       ('ref_file', 'inputnode.ref_bold_brain')
                                       ]),
        (t1w_brain, bold_t1_trans_wf, [('out_file', 'inputnode.t1w_brain')]),
        (lta_convert, bold_t1_trans_wf, [('out_itk', 'inputnode.hmc_xforms')]),
        (bold_reg_wf, bold_t1_trans_wf, [('outputnode.bold2anat',
                                          'inputnode.bold2anat')]),
        (bold_t1_trans_wf, outputnode, [('outputnode.bold_t1', 'bold_t1'),
                                        ('outputnode.bold_t1_ref',
                                         'bold_t1_ref')]),
        # transform T1 mask to BOLD
        (inputnode, t1w_mask_bold_tfm, [('anat_mask', 'input_image'),
                                        ('ref_file', 'reference_image')]),
        (bold_reg_wf, t1w_mask_bold_tfm, [('outputnode.anat2bold',
                                           'transforms')]),
        (t1w_mask_bold_tfm, outputnode, [('output_image', 'bold_mask')]),
        # Connect bold_confounds_wf
        (inputnode, bold_confounds_wf, [('anat_tpms', 'inputnode.anat_tpms'),
                                        ('anat_mask', 'inputnode.t1w_mask')]),
        (lta_convert, bold_confounds_wf, [('out_fsl', 'inputnode.movpar_file')
                                          ]),
        (bold_reg_wf, bold_confounds_wf, [('outputnode.anat2bold',
                                           'inputnode.anat2bold')]),
        (inputnode, bold_confounds_wf, [('n_dummy_scans',
                                         'inputnode.skip_vols')]),
        (t1w_mask_bold_tfm, bold_confounds_wf, [('output_image',
                                                 'inputnode.bold_mask')]),
        (bold_confounds_wf, outputnode, [('outputnode.confounds_file',
                                          'confounds')]),
        (bold_confounds_wf, outputnode, [('outputnode.confounds_metadata',
                                          'confounds_metadata')]),
        # Connect bold_bold_trans_wf
        (inputnode, bold_bold_trans_wf, [('ref_file', 'inputnode.bold_ref')]),
        (t1w_mask_bold_tfm, bold_bold_trans_wf, [('output_image',
                                                  'inputnode.bold_mask')]),
        (bold_split, bold_bold_trans_wf, [('out_files', 'inputnode.bold_file')
                                          ]),
        (lta_convert, bold_bold_trans_wf, [('out_itk', 'inputnode.hmc_xforms')
                                           ]),
        # Summary
        (outputnode, summary, [('confounds', 'confounds_file')]),
    ])
    # fmt:on

    # for standard EPI data, pass along correct file
    if not multiecho:
        # fmt:off
        workflow.connect([
            (inputnode, func_derivatives_wf, [('bold_file',
                                               'inputnode.source_file')]),
            (bold_bold_trans_wf, bold_confounds_wf, [('outputnode.bold',
                                                      'inputnode.bold')]),
            (bold_split, bold_t1_trans_wf, [('out_files',
                                             'inputnode.bold_split')]),
        ])
        # fmt:on
    else:  # for meepi, create and use optimal combination
        # fmt:off
        workflow.connect([
            # update name source for optimal combination
            (inputnode, func_derivatives_wf,
             [(('bold_file', combine_meepi_source), 'inputnode.source_file')]),
            (bold_bold_trans_wf, skullstrip_bold_wf, [('outputnode.bold',
                                                       'inputnode.in_file')]),
            (bold_t2s_wf, bold_confounds_wf, [('outputnode.bold',
                                               'inputnode.bold')]),
            (bold_t2s_wf, bold_t1_trans_wf, [('outputnode.bold',
                                              'inputnode.bold_split')]),
        ])
        # fmt:on

    # Map final BOLD mask into T1w space (if required)
    nonstd_spaces = set(spaces.get_nonstandard())
    if nonstd_spaces.intersection(("T1w", "anat")):
        from niworkflows.interfaces.fixes import (
            FixHeaderApplyTransforms as ApplyTransforms, )

        boldmask_to_t1w = pe.Node(
            ApplyTransforms(interpolation="MultiLabel"),
            name="boldmask_to_t1w",
            mem_gb=0.1,
        )
        # fmt:off
        workflow.connect([
            (bold_reg_wf, boldmask_to_t1w, [('outputnode.bold2anat',
                                             'transforms')]),
            (bold_t1_trans_wf, boldmask_to_t1w, [('outputnode.bold_mask_t1',
                                                  'reference_image')]),
            (t1w_mask_bold_tfm, boldmask_to_t1w, [('output_image',
                                                   'input_image')]),
            (boldmask_to_t1w, outputnode, [('output_image', 'bold_mask_t1')]),
        ])
        # fmt:on

    if nonstd_spaces.intersection(("func", "run", "bold", "boldref", "sbref")):
        # fmt:off
        workflow.connect([
            (bold_bold_trans_wf, outputnode, [('outputnode.bold',
                                               'bold_native')]),
            (bold_bold_trans_wf, func_derivatives_wf,
             [('outputnode.bold_ref', 'inputnode.bold_native_ref'),
              ('outputnode.bold_mask', 'inputnode.bold_mask_native')]),
        ])
        # fmt:on

    if spaces.get_spaces(nonstandard=False, dim=(3, )):
        # Apply transforms in 1 shot
        # Only use uncompressed output if AROMA is to be run
        bold_std_trans_wf = init_bold_std_trans_wf(
            mem_gb=mem_gb["resampled"],
            omp_nthreads=omp_nthreads,
            spaces=spaces,
            name="bold_std_trans_wf",
            use_compression=not config.execution.low_mem,
            use_fieldwarp=False,
        )
        # fmt:off
        workflow.connect([
            (inputnode, bold_std_trans_wf,
             [('template', 'inputnode.templates'),
              ('anat2std_xfm', 'inputnode.anat2std_xfm'),
              ('bold_file', 'inputnode.name_source')]),
            (t1w_mask_bold_tfm, bold_std_trans_wf, [('output_image',
                                                     'inputnode.bold_mask')]),
            (lta_convert, bold_std_trans_wf, [('out_itk',
                                               'inputnode.hmc_xforms')]),
            (bold_reg_wf, bold_std_trans_wf, [('outputnode.bold2anat',
                                               'inputnode.bold2anat')]),
            (bold_std_trans_wf, outputnode,
             [('outputnode.bold_std', 'bold_std'),
              ('outputnode.bold_std_ref', 'bold_std_ref'),
              ('outputnode.bold_mask_std', 'bold_mask_std')]),
        ])
        # fmt:on

        if not multiecho:
            # fmt:off
            workflow.connect([
                (bold_split, bold_std_trans_wf, [("out_files",
                                                  "inputnode.bold_split")]),
            ])
            # fmt:on
        else:
            split_opt_comb = bold_split.clone(name="split_opt_comb")
            # fmt:off
            workflow.connect([(bold_t2s_wf, split_opt_comb,
                               [('outputnode.bold', 'in_file')]),
                              (split_opt_comb, bold_std_trans_wf,
                               [('out_files', 'inputnode.bold_split')])])
            # fmt:on

        # func_derivatives_wf internally parametrizes over snapshotted spaces.
        # fmt:off
        workflow.connect([
            (bold_std_trans_wf, func_derivatives_wf, [
                ('outputnode.template', 'inputnode.template'),
                ('outputnode.spatial_reference',
                 'inputnode.spatial_reference'),
                ('outputnode.bold_std_ref', 'inputnode.bold_std_ref'),
                ('outputnode.bold_std', 'inputnode.bold_std'),
                ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'),
            ]),
        ])
        # fmt:on

        if config.workflow.use_aroma:  # ICA-AROMA workflow
            from .confounds import init_ica_aroma_wf

            ica_aroma_wf = init_ica_aroma_wf(
                mem_gb=mem_gb["resampled"],
                metadata=metadata,
                omp_nthreads=omp_nthreads,
                use_fieldwarp=False,
                err_on_aroma_warn=config.workflow.aroma_err_on_warn,
                aroma_melodic_dim=config.workflow.aroma_melodic_dim,
                name="ica_aroma_wf",
            )

            join = pe.Node(
                niu.Function(output_names=["out_file"], function=_to_join),
                name="aroma_confounds",
            )

            mrg_conf_metadata = pe.Node(
                niu.Merge(2),
                name="merge_confound_metadata",
                run_without_submitting=True,
            )
            mrg_conf_metadata2 = pe.Node(
                DictMerge(),
                name="merge_confound_metadata2",
                run_without_submitting=True,
            )

            # fmt:off
            workflow.disconnect([
                (bold_confounds_wf, outputnode, [
                    ('outputnode.confounds_file', 'confounds'),
                ]),
                (bold_confounds_wf, outputnode, [
                    ('outputnode.confounds_metadata', 'confounds_metadata'),
                ]),
            ])
            workflow.connect([
                (inputnode, ica_aroma_wf,
                 [('bold_file', 'inputnode.name_source'),
                  ('n_dummy_scans', 'inputnode.skip_vols')]),
                (lta_convert, ica_aroma_wf, [('out_fsl',
                                              'inputnode.movpar_file')]),
                (bold_confounds_wf, join, [('outputnode.confounds_file',
                                            'in_file')]),
                (bold_confounds_wf, mrg_conf_metadata,
                 [('outputnode.confounds_metadata', 'in1')]),
                (ica_aroma_wf, join, [('outputnode.aroma_confounds',
                                       'join_file')]),
                (ica_aroma_wf, mrg_conf_metadata,
                 [('outputnode.aroma_metadata', 'in2')]),
                (mrg_conf_metadata, mrg_conf_metadata2, [('out', 'in_dicts')]),
                (ica_aroma_wf, outputnode,
                 [('outputnode.aroma_noise_ics', 'aroma_noise_ics'),
                  ('outputnode.melodic_mix', 'melodic_mix'),
                  ('outputnode.nonaggr_denoised_file', 'nonaggr_denoised_file')
                  ]),
                (join, outputnode, [('out_file', 'confounds')]),
                (mrg_conf_metadata2, outputnode, [('out_dict',
                                                   'confounds_metadata')]),
                (bold_std_trans_wf, ica_aroma_wf,
                 [('outputnode.bold_std', 'inputnode.bold_std'),
                  ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'),
                  ('outputnode.spatial_reference',
                   'inputnode.spatial_reference')]),
            ])
            # fmt:on

    if spaces.get_spaces(nonstandard=False, dim=(3, )):
        carpetplot_wf = init_carpetplot_wf(
            mem_gb=mem_gb["resampled"],
            metadata=metadata,
            name="carpetplot_wf",
        )
        # Xform to 'Fischer344' is always computed.
        carpetplot_select_std = pe.Node(
            KeySelect(fields=["std2anat_xfm"], key="Fischer344"),
            name="carpetplot_select_std",
            run_without_submitting=True,
        )

        # fmt:off
        workflow.connect([
            (inputnode, carpetplot_select_std, [('std2anat_xfm',
                                                 'std2anat_xfm'),
                                                ('template', 'keys')]),
            (carpetplot_select_std, carpetplot_wf,
             [('std2anat_xfm', 'inputnode.std2anat_xfm')]),
            (bold_bold_trans_wf if not multiecho else bold_t2s_wf,
             carpetplot_wf, [('outputnode.bold', 'inputnode.bold')]),
            (t1w_mask_bold_tfm, carpetplot_wf, [('output_image',
                                                 'inputnode.bold_mask')]),
            (bold_reg_wf, carpetplot_wf, [('outputnode.anat2bold',
                                           'inputnode.anat2bold')]),
            (bold_confounds_wf, carpetplot_wf, [('outputnode.confounds_file',
                                                 'inputnode.confounds_file')]),
        ])
        # fmt:on

    # REPORTING ############################################################
    ds_report_summary = pe.Node(
        DerivativesDataSink(desc="summary",
                            datatype="figures",
                            dismiss_entities=("echo", )),
        name="ds_report_summary",
        run_without_submitting=True,
        mem_gb=config.DEFAULT_MEMORY_MIN_GB,
    )

    ds_report_validation = pe.Node(
        DerivativesDataSink(
            base_directory=output_dir,
            desc="validation",
            datatype="figures",
            dismiss_entities=("echo", ),
        ),
        name="ds_report_validation",
        run_without_submitting=True,
        mem_gb=config.DEFAULT_MEMORY_MIN_GB,
    )

    # fmt:off
    workflow.connect([
        (summary, ds_report_summary, [('out_report', 'in_file')]),
        (inputnode, ds_report_validation, [('validation_report', 'in_file')]),
    ])
    # fmt:on

    # Fill-in datasinks of reportlets seen so far
    for node in workflow.list_node_names():
        if node.split(".")[-1].startswith("ds_report"):
            workflow.get_node(node).inputs.base_directory = output_dir
            workflow.get_node(node).inputs.source_file = ref_file

    return workflow
Esempio n. 16
0
def init_bold_std_trans_wf(
    freesurfer,
    mem_gb,
    omp_nthreads,
    spaces,
    name='bold_std_trans_wf',
    use_compression=True,
    use_fieldwarp=False,
):
    """
    Sample fMRI into standard space with a single-step resampling of the original BOLD series.

    .. important::
        This workflow provides two outputnodes.
        One output node (with name ``poutputnode``) will be parameterized in a Nipype sense
        (see `Nipype iterables
        <https://miykael.github.io/nipype_tutorial/notebooks/basic_iteration.html>`__), and a
        second node (``outputnode``) will collapse the parameterized outputs into synchronous
        lists of the output fields listed below.

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

            from niworkflows.utils.spaces import SpatialReferences
            from fmriprep_rodents.workflows.bold import init_bold_std_trans_wf
            wf = init_bold_std_trans_wf(
                freesurfer=True,
                mem_gb=3,
                omp_nthreads=1,
                spaces=SpatialReferences(
                    spaces=['MNI152Lin',
                            ('MNIPediatricAsym', {'cohort': '6'})],
                    checkpoint=True),
            )

    Parameters
    ----------
    freesurfer : :obj:`bool`
        Whether to generate FreeSurfer's aseg/aparc segmentations on BOLD space.
    mem_gb : :obj:`float`
        Size of BOLD file in GB
    omp_nthreads : :obj:`int`
        Maximum number of threads an individual process may use
    spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences`
        A container for storing, organizing, and parsing spatial normalizations. Composed of
        :py:class:`~niworkflows.utils.spaces.Reference` objects representing spatial references.
        Each ``Reference`` contains a space, which is a string of either TemplateFlow template IDs
        (e.g., ``MNI152Lin``, ``MNI152NLin6Asym``, ``MNIPediatricAsym``), nonstandard references
        (e.g., ``T1w`` or ``anat``, ``sbref``, ``run``, etc.), or a custom template located in
        the TemplateFlow root directory. Each ``Reference`` may also contain a spec, which is a
        dictionary with template specifications (e.g., a specification of ``{'resolution': 2}``
        would lead to resampling on a 2mm resolution of the space).
    name : :obj:`str`
        Name of workflow (default: ``bold_std_trans_wf``)
    use_compression : :obj:`bool`
        Save registered BOLD series as ``.nii.gz``
    use_fieldwarp : :obj:`bool`
        Include SDC warp in single-shot transform from BOLD to MNI

    Inputs
    ------
    anat2std_xfm
        List of anatomical-to-standard space transforms generated during
        spatial normalization.
    bold_aparc
        FreeSurfer's ``aparc+aseg.mgz`` atlas projected into the T1w reference
        (only if ``recon-all`` was run).
    bold_aseg
        FreeSurfer's ``aseg.mgz`` atlas projected into the T1w reference
        (only if ``recon-all`` was run).
    bold_mask
        Skull-stripping mask of reference image
    bold_split
        Individual 3D volumes, not motion corrected
    fieldwarp
        a :abbr:`DFM (displacements field map)` in ITK format
    hmc_xforms
        List of affine transforms aligning each volume to ``ref_image`` in ITK format
    itk_bold_to_t1
        Affine transform from ``ref_bold_brain`` to T1 space (ITK format)
    name_source
        BOLD series NIfTI file
        Used to recover original information lost during processing
    templates
        List of templates that were applied as targets during
        spatial normalization.

    Outputs
    -------
    bold_std
        BOLD series, resampled to template space
    bold_std_ref
        Reference, contrast-enhanced summary of the BOLD series, resampled to template space
    bold_mask_std
        BOLD series mask in template space
    bold_aseg_std
        FreeSurfer's ``aseg.mgz`` atlas, in template space at the BOLD resolution
        (only if ``recon-all`` was run)
    bold_aparc_std
        FreeSurfer's ``aparc+aseg.mgz`` atlas, in template space at the BOLD resolution
        (only if ``recon-all`` was run)
    template
        Template identifiers synchronized correspondingly to previously
        described outputs.

    """
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.func.util import init_bold_reference_wf
    from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms
    from niworkflows.interfaces.itk import MultiApplyTransforms
    from niworkflows.interfaces.utility import KeySelect
    from niworkflows.interfaces.utils import GenerateSamplingReference
    from niworkflows.interfaces.nilearn import Merge
    from niworkflows.utils.spaces import format_reference

    workflow = Workflow(name=name)
    output_references = spaces.cached.get_spaces(nonstandard=False, dim=(3, ))
    std_vol_references = [(s.fullname, s.spec) for s in spaces.references
                          if s.standard and s.dim == 3]

    if len(output_references) == 1:
        workflow.__desc__ = """\
The BOLD time-series were resampled into standard space,
generating a *preprocessed BOLD run in {tpl} space*.
""".format(tpl=output_references[0])
    elif len(output_references) > 1:
        workflow.__desc__ = """\
The BOLD time-series were resampled into several standard spaces,
correspondingly generating the following *spatially-normalized,
preprocessed BOLD runs*: {tpl}.
""".format(tpl=', '.join(output_references))

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'anat2std_xfm',
        'bold_aparc',
        'bold_aseg',
        'bold_mask',
        'bold_split',
        'fieldwarp',
        'hmc_xforms',
        'itk_bold_to_t1',
        'name_source',
        'templates',
    ]),
                        name='inputnode')

    iterablesource = pe.Node(niu.IdentityInterface(fields=['std_target']),
                             name='iterablesource')
    # Generate conversions for every template+spec at the input
    iterablesource.iterables = [('std_target', std_vol_references)]

    split_target = pe.Node(niu.Function(
        function=_split_spec,
        input_names=['in_target'],
        output_names=['space', 'template', 'spec']),
                           run_without_submitting=True,
                           name='split_target')

    select_std = pe.Node(KeySelect(fields=['anat2std_xfm']),
                         name='select_std',
                         run_without_submitting=True)

    select_tpl = pe.Node(niu.Function(function=_select_template),
                         name='select_tpl',
                         run_without_submitting=True)

    gen_ref = pe.Node(GenerateSamplingReference(), name='gen_ref',
                      mem_gb=0.3)  # 256x256x256 * 64 / 8 ~ 150MB)

    mask_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel'),
                           name='mask_std_tfm',
                           mem_gb=1)

    # Write corrected file in the designated output dir
    mask_merge_tfms = pe.Node(niu.Merge(2),
                              name='mask_merge_tfms',
                              run_without_submitting=True,
                              mem_gb=DEFAULT_MEMORY_MIN_GB)

    nxforms = 3 + use_fieldwarp
    merge_xforms = pe.Node(niu.Merge(nxforms),
                           name='merge_xforms',
                           run_without_submitting=True,
                           mem_gb=DEFAULT_MEMORY_MIN_GB)
    workflow.connect([(inputnode, merge_xforms, [('hmc_xforms',
                                                  'in%d' % nxforms)])])

    if use_fieldwarp:
        workflow.connect([(inputnode, merge_xforms, [('fieldwarp', 'in3')])])

    bold_to_std_transform = pe.Node(MultiApplyTransforms(
        interpolation="LanczosWindowedSinc", float=True, copy_dtype=True),
                                    name='bold_to_std_transform',
                                    mem_gb=mem_gb * 3 * omp_nthreads,
                                    n_procs=omp_nthreads)

    merge = pe.Node(Merge(compress=use_compression),
                    name='merge',
                    mem_gb=mem_gb * 3)

    # Generate a reference on the target standard space
    gen_final_ref = init_bold_reference_wf(omp_nthreads=omp_nthreads,
                                           pre_mask=True)

    workflow.connect([
        (iterablesource, split_target, [('std_target', 'in_target')]),
        (iterablesource, select_tpl, [('std_target', 'template')]),
        (inputnode, select_std, [('anat2std_xfm', 'anat2std_xfm'),
                                 ('templates', 'keys')]),
        (inputnode, mask_std_tfm, [('bold_mask', 'input_image')]),
        (inputnode, gen_ref, [(('bold_split', _first), 'moving_image')]),
        (inputnode, merge_xforms, [(('itk_bold_to_t1', _aslist), 'in2')]),
        (inputnode, merge, [('name_source', 'header_source')]),
        (inputnode, mask_merge_tfms, [(('itk_bold_to_t1', _aslist), 'in2')]),
        (inputnode, bold_to_std_transform, [('bold_split', 'input_image')]),
        (split_target, select_std, [('space', 'key')]),
        (select_std, merge_xforms, [('anat2std_xfm', 'in1')]),
        (select_std, mask_merge_tfms, [('anat2std_xfm', 'in1')]),
        (split_target, gen_ref, [(('spec', _is_native), 'keep_native')]),
        (select_tpl, gen_ref, [('out', 'fixed_image')]),
        (merge_xforms, bold_to_std_transform, [('out', 'transforms')]),
        (gen_ref, bold_to_std_transform, [('out_file', 'reference_image')]),
        (gen_ref, mask_std_tfm, [('out_file', 'reference_image')]),
        (mask_merge_tfms, mask_std_tfm, [('out', 'transforms')]),
        (mask_std_tfm, gen_final_ref, [('output_image', 'inputnode.bold_mask')
                                       ]),
        (bold_to_std_transform, merge, [('out_files', 'in_files')]),
        (merge, gen_final_ref, [('out_file', 'inputnode.bold_file')]),
    ])

    output_names = [
        'bold_mask_std',
        'bold_std',
        'bold_std_ref',
        'spatial_reference',
        'template',
    ] + freesurfer * ['bold_aseg_std', 'bold_aparc_std']

    poutputnode = pe.Node(niu.IdentityInterface(fields=output_names),
                          name='poutputnode')
    workflow.connect([
        # Connecting outputnode
        (iterablesource, poutputnode, [(('std_target', format_reference),
                                        'spatial_reference')]),
        (merge, poutputnode, [('out_file', 'bold_std')]),
        (gen_final_ref, poutputnode, [('outputnode.ref_image', 'bold_std_ref')
                                      ]),
        (mask_std_tfm, poutputnode, [('output_image', 'bold_mask_std')]),
        (select_std, poutputnode, [('key', 'template')]),
    ])

    if freesurfer:
        # Sample the parcellation files to functional space
        aseg_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel'),
                               name='aseg_std_tfm',
                               mem_gb=1)
        aparc_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel'),
                                name='aparc_std_tfm',
                                mem_gb=1)

        workflow.connect([
            (inputnode, aseg_std_tfm, [('bold_aseg', 'input_image')]),
            (inputnode, aparc_std_tfm, [('bold_aparc', 'input_image')]),
            (select_std, aseg_std_tfm, [('anat2std_xfm', 'transforms')]),
            (select_std, aparc_std_tfm, [('anat2std_xfm', 'transforms')]),
            (gen_ref, aseg_std_tfm, [('out_file', 'reference_image')]),
            (gen_ref, aparc_std_tfm, [('out_file', 'reference_image')]),
            (aseg_std_tfm, poutputnode, [('output_image', 'bold_aseg_std')]),
            (aparc_std_tfm, poutputnode, [('output_image', 'bold_aparc_std')]),
        ])

    # Connect parametric outputs to a Join outputnode
    outputnode = pe.JoinNode(niu.IdentityInterface(fields=output_names),
                             name='outputnode',
                             joinsource='iterablesource')
    workflow.connect([
        (poutputnode, outputnode, [(f, f) for f in output_names]),
    ])
    return workflow
Esempio n. 17
0
def init_bold_grayords_wf(grayord_density,
                          mem_gb,
                          repetition_time,
                          name='bold_grayords_wf'):
    """
    Sample Grayordinates files onto the fsLR atlas.

    Outputs are in CIFTI2 format.

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

            from fmriprep_rodents.workflows.bold import init_bold_grayords_wf
            wf = init_bold_grayords_wf(mem_gb=0.1, grayord_density='91k')

    Parameters
    ----------
    grayord_density : :obj:`str`
        Either `91k` or `170k`, representing the total of vertices or *grayordinates*.
    mem_gb : :obj:`float`
        Size of BOLD file in GB
    name : :obj:`str`
        Unique name for the subworkflow (default: ``'bold_grayords_wf'``)

    Inputs
    ------
    bold_std : :obj:`str`
        List of BOLD conversions to standard spaces.
    spatial_reference :obj:`str`
        List of unique identifiers corresponding to the BOLD standard-conversions.
    subjects_dir : :obj:`str`
        FreeSurfer's subjects directory.
    surf_files : :obj:`str`
        List of BOLD files resampled on the fsaverage (ico7) surfaces.
    surf_refs :
        List of unique identifiers corresponding to the BOLD surface-conversions.

    Outputs
    -------
    cifti_bold : :obj:`str`
        List of BOLD grayordinates files - (L)eft and (R)ight.
    cifti_variant : :obj:`str`
        Only ``'HCP Grayordinates'`` is currently supported.
    cifti_metadata : :obj:`str`
        Path of metadata files corresponding to ``cifti_bold``.
    cifti_density : :obj:`str`
        Density (i.e., either `91k` or `170k`) of ``cifti_bold``.

    """
    import templateflow.api as tf
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.interfaces.cifti import GenerateCifti
    from niworkflows.interfaces.utility import KeySelect

    workflow = Workflow(name=name)
    workflow.__desc__ = """\
*Grayordinates* files [@hcppipelines] containing {density} samples were also
generated using the highest-resolution ``fsaverage`` as intermediate standardized
surface space.
""".format(density=grayord_density)

    fslr_density, mni_density = ('32k',
                                 '2') if grayord_density == '91k' else ('59k',
                                                                        '1')

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'bold_std',
        'spatial_reference',
        'subjects_dir',
        'surf_files',
        'surf_refs',
    ]),
                        name='inputnode')

    outputnode = pe.Node(niu.IdentityInterface(fields=[
        'cifti_bold',
        'cifti_variant',
        'cifti_metadata',
        'cifti_density',
    ]),
                         name='outputnode')

    # extract out to BOLD base
    select_std = pe.Node(KeySelect(fields=['bold_std']),
                         name='select_std',
                         run_without_submitting=True,
                         nohash=True)
    select_std.inputs.key = 'MNI152NLin6Asym_res-%s' % mni_density

    select_fs_surf = pe.Node(KeySelect(fields=['surf_files']),
                             name='select_fs_surf',
                             run_without_submitting=True,
                             mem_gb=DEFAULT_MEMORY_MIN_GB)
    select_fs_surf.inputs.key = 'fsaverage'

    # Setup Workbench command. LR ordering for hemi can be assumed, as it is imposed
    # by the iterfield of the MapNode in the surface sampling workflow above.
    resample = pe.MapNode(wb.MetricResample(method='ADAP_BARY_AREA',
                                            area_metrics=True),
                          name='resample',
                          iterfield=[
                              'in_file', 'out_file', 'new_sphere', 'new_area',
                              'current_sphere', 'current_area'
                          ])
    resample.inputs.current_sphere = [
        str(
            tf.get('fsaverage',
                   hemi=hemi,
                   density='164k',
                   desc='std',
                   suffix='sphere')) for hemi in 'LR'
    ]
    resample.inputs.current_area = [
        str(
            tf.get('fsaverage',
                   hemi=hemi,
                   density='164k',
                   desc='vaavg',
                   suffix='midthickness')) for hemi in 'LR'
    ]
    resample.inputs.new_sphere = [
        str(
            tf.get('fsLR',
                   space='fsaverage',
                   hemi=hemi,
                   density=fslr_density,
                   suffix='sphere')) for hemi in 'LR'
    ]
    resample.inputs.new_area = [
        str(
            tf.get('fsLR',
                   hemi=hemi,
                   density=fslr_density,
                   desc='vaavg',
                   suffix='midthickness')) for hemi in 'LR'
    ]
    resample.inputs.out_file = [
        'space-fsLR_hemi-%s_den-%s_bold.gii' % (h, grayord_density)
        for h in 'LR'
    ]

    gen_cifti = pe.Node(GenerateCifti(
        volume_target='MNI152NLin6Asym',
        surface_target='fsLR',
        TR=repetition_time,
        surface_density=fslr_density,
    ),
                        name="gen_cifti")

    workflow.connect([
        (inputnode, gen_cifti, [('subjects_dir', 'subjects_dir')]),
        (inputnode, select_std, [('bold_std', 'bold_std'),
                                 ('spatial_reference', 'keys')]),
        (inputnode, select_fs_surf, [('surf_files', 'surf_files'),
                                     ('surf_refs', 'keys')]),
        (select_fs_surf, resample, [('surf_files', 'in_file')]),
        (select_std, gen_cifti, [('bold_std', 'bold_file')]),
        (resample, gen_cifti, [('out_file', 'surface_bolds')]),
        (gen_cifti, outputnode, [('out_file', 'cifti_bold'),
                                 ('variant', 'cifti_variant'),
                                 ('out_metadata', 'cifti_metadata'),
                                 ('density', 'cifti_density')]),
    ])
    return workflow
Esempio n. 18
0
def init_carpetplot_wf(standard_spaces,
                       mem_gb,
                       metadata,
                       name="bold_carpet_wf"):
    """

    Resamples the MNI parcellation (ad-hoc parcellation derived from the
    Harvard-Oxford template and others).

    **Parameters**

        mem_gb : float
            Size of BOLD file in GB - please note that this size
            should be calculated after resamplings that may extend
            the FoV
        metadata : dict
            BIDS metadata for BOLD file
        name : str
            Name of workflow (default: ``bold_carpet_wf``)

    **Inputs**

        bold
            BOLD image, after the prescribed corrections (STC, HMC and SDC)
            when available.
        bold_mask
            BOLD series mask
        confounds_file
            TSV of all aggregated confounds
        t1_bold_xform
            Affine matrix that maps the T1w space into alignment with
            the native BOLD space
        std2anat_xfm
            ANTs-compatible affine-and-warp transform file

    **Outputs**

        out_carpetplot
            Path of the generated SVG file

    """
    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'bold', 'bold_mask', 'confounds_file', 't1_bold_xform', 'std2anat_xfm'
    ]),
                        name='inputnode')

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

    select_std = pe.Node(KeySelect(keys=list(standard_spaces.keys()),
                                   fields=['std2anat_xfm']),
                         name='select_std',
                         run_without_submitting=True)
    select_std.inputs.key = 'MNI152NLin2009cAsym'

    # List transforms
    mrg_xfms = pe.Node(niu.Merge(2), name='mrg_xfms')

    # Warp segmentation into EPI space
    resample_parc = pe.Node(ApplyTransforms(
        float=True,
        input_image=str(
            get_template('MNI152NLin2009cAsym',
                         resolution=1,
                         desc='carpet',
                         suffix='dseg',
                         extensions=['.nii', '.nii.gz'])),
        dimension=3,
        default_value=0,
        interpolation='MultiLabel'),
                            name='resample_parc')

    # Carpetplot and confounds plot
    conf_plot = pe.Node(FMRISummary(tr=metadata['RepetitionTime'],
                                    confounds_list=[
                                        ('global_signal', None, 'GS'),
                                        ('csf', None, 'GSCSF'),
                                        ('white_matter', None, 'GSWM'),
                                        ('std_dvars', None, 'DVARS'),
                                        ('framewise_displacement', 'mm', 'FD')
                                    ]),
                        name='conf_plot',
                        mem_gb=mem_gb)
    ds_report_bold_conf = pe.Node(DerivativesDataSink(desc='carpetplot',
                                                      keep_dtype=True),
                                  name='ds_report_bold_conf',
                                  run_without_submitting=True,
                                  mem_gb=DEFAULT_MEMORY_MIN_GB)

    workflow = Workflow(name=name)
    workflow.connect([
        (inputnode, select_std, [('std2anat_xfm', 'std2anat_xfm')]),
        (inputnode, mrg_xfms, [('t1_bold_xform', 'in1')]),
        (inputnode, resample_parc, [('bold_mask', 'reference_image')]),
        (select_std, mrg_xfms, [('std2anat_xfm', 'in2')]),
        (mrg_xfms, resample_parc, [('out', 'transforms')]),
        # Carpetplot
        (inputnode, conf_plot, [('bold', 'in_func'), ('bold_mask', 'in_mask'),
                                ('confounds_file', 'confounds_file')]),
        (resample_parc, conf_plot, [('output_image', 'in_segm')]),
        (conf_plot, ds_report_bold_conf, [('out_file', 'in_file')]),
        (conf_plot, outputnode, [('out_file', 'out_carpetplot')]),
    ])
    return workflow
Esempio n. 19
0
def init_sdc_wf(fmaps,
                bold_meta,
                omp_nthreads=1,
                debug=False,
                fmap_bspline=False,
                fmap_demean=True):
    """
    This workflow implements the heuristics to choose a
    :abbr:`SDC (susceptibility distortion correction)` strategy.
    When no field map information is present within the BIDS inputs,
    the EXPERIMENTAL "fieldmap-less SyN" can be performed, using
    the ``--use-syn`` argument. When ``--force-syn`` is specified,
    then the "fieldmap-less SyN" is always executed and reported
    despite of other fieldmaps available with higher priority.
    In the latter case (some sort of fieldmap(s) is available and
    ``--force-syn`` is requested), then the :abbr:`SDC (susceptibility
    distortion correction)` method applied is that with the
    highest priority.

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

        from fmriprep.workflows.fieldmap import init_sdc_wf
        wf = init_sdc_wf(
            fmaps=[{
                'suffix': 'phasediff',
                'phasediff': 'sub-03/ses-2/fmap/sub-03_ses-2_run-1_phasediff.nii.gz',
                'magnitude1': 'sub-03/ses-2/fmap/sub-03_ses-2_run-1_magnitude1.nii.gz',
                'magnitude2': 'sub-03/ses-2/fmap/sub-03_ses-2_run-1_magnitude2.nii.gz',
            }],
            bold_meta={
                'RepetitionTime': 2.0,
                'SliceTiming': [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
                'PhaseEncodingDirection': 'j',
            },
        )

    **Parameters**

        fmaps : list of pybids dicts
            A list of dictionaries with the available fieldmaps
            (and their metadata using the key ``'metadata'`` for the
            case of *epi* fieldmaps)
        bold_meta : dict
            BIDS metadata dictionary corresponding to the BOLD run
        omp_nthreads : int
            Maximum number of threads an individual process may use
        fmap_bspline : bool
            **Experimental**: Fit B-Spline field using least-squares
        fmap_demean : bool
            Demean voxel-shift map during unwarp
        debug : bool
            Enable debugging outputs

    **Inputs**
        bold_ref
            A BOLD reference calculated at a previous stage
        bold_ref_brain
            Same as above, but brain-masked
        bold_mask
            Brain mask for the BOLD run
        t1_brain
            T1w image, brain-masked, for the fieldmap-less SyN method
        std2anat_xfm
            List of standard-to-T1w transforms generated during spatial
            normalization (only for the fieldmap-less SyN method).
        template : str
            Name of template from which prior knowledge will be mapped
            into the subject's T1w reference
            (only for the fieldmap-less SyN method)
        templates : str
            Name of templates that index the ``std2anat_xfm`` input list
            (only for the fieldmap-less SyN method).


    **Outputs**
        bold_ref
            An unwarped BOLD reference
        bold_mask
            The corresponding new mask after unwarping
        bold_ref_brain
            Brain-extracted, unwarped BOLD reference
        out_warp
            The deformation field to unwarp the susceptibility distortions
        syn_bold_ref
            If ``--force-syn``, an unwarped BOLD reference with this
            method (for reporting purposes)

    """

    # TODO: To be removed (filter out unsupported fieldmaps):
    fmaps = [fmap for fmap in fmaps if fmap['suffix'] in FMAP_PRIORITY]

    workflow = Workflow(name='sdc_wf' if fmaps else 'sdc_bypass_wf')
    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'bold_ref', 'bold_ref_brain', 'bold_mask', 't1_brain', 'std2anat_xfm',
        'template', 'templates'
    ]),
                        name='inputnode')

    outputnode = pe.Node(niu.IdentityInterface(fields=[
        'bold_ref', 'bold_mask', 'bold_ref_brain', 'out_warp', 'syn_bold_ref',
        'method'
    ]),
                         name='outputnode')

    # No fieldmaps - forward inputs to outputs
    if not fmaps:
        outputnode.inputs.method = 'None'
        workflow.connect([
            (inputnode, outputnode, [('bold_ref', 'bold_ref'),
                                     ('bold_mask', 'bold_mask'),
                                     ('bold_ref_brain', 'bold_ref_brain')]),
        ])
        return workflow

    workflow.__postdesc__ = """\
Based on the estimated susceptibility distortion, an
unwarped BOLD reference was calculated for a more accurate
co-registration with the anatomical reference.
"""

    # In case there are multiple fieldmaps prefer EPI
    fmaps.sort(key=lambda fmap: FMAP_PRIORITY[fmap['suffix']])
    fmap = fmaps[0]

    # PEPOLAR path
    if fmap['suffix'] == 'epi':
        outputnode.inputs.method = 'PEB/PEPOLAR (phase-encoding based / PE-POLARity)'
        # Get EPI polarities and their metadata
        epi_fmaps = [(fmap_['epi'],
                      fmap_['metadata']["PhaseEncodingDirection"])
                     for fmap_ in fmaps if fmap_['suffix'] == 'epi']
        sdc_unwarp_wf = init_pepolar_unwarp_wf(bold_meta=bold_meta,
                                               epi_fmaps=epi_fmaps,
                                               omp_nthreads=omp_nthreads,
                                               name='pepolar_unwarp_wf')

        workflow.connect([
            (inputnode, sdc_unwarp_wf, [('bold_ref', 'inputnode.in_reference'),
                                        ('bold_mask', 'inputnode.in_mask'),
                                        ('bold_ref_brain',
                                         'inputnode.in_reference_brain')]),
        ])

    # FIELDMAP path
    if fmap['suffix'] in ['fieldmap', 'phasediff']:
        outputnode.inputs.method = 'FMB (%s-based)' % fmap['suffix']
        # Import specific workflows here, so we don't break everything with one
        # unused workflow.
        if fmap['suffix'] == 'fieldmap':
            from .fmap import init_fmap_wf
            fmap_estimator_wf = init_fmap_wf(omp_nthreads=omp_nthreads,
                                             fmap_bspline=fmap_bspline)
            # set inputs
            fmap_estimator_wf.inputs.inputnode.fieldmap = fmap['fieldmap']
            fmap_estimator_wf.inputs.inputnode.magnitude = fmap['magnitude']

        if fmap['suffix'] == 'phasediff':
            from .phdiff import init_phdiff_wf
            fmap_estimator_wf = init_phdiff_wf(omp_nthreads=omp_nthreads)
            # set inputs
            fmap_estimator_wf.inputs.inputnode.phasediff = fmap['phasediff']
            fmap_estimator_wf.inputs.inputnode.magnitude = [
                fmap_ for key, fmap_ in sorted(fmap.items())
                if key.startswith("magnitude")
            ]

        sdc_unwarp_wf = init_sdc_unwarp_wf(omp_nthreads=omp_nthreads,
                                           fmap_demean=fmap_demean,
                                           debug=debug,
                                           name='sdc_unwarp_wf')
        sdc_unwarp_wf.inputs.inputnode.metadata = bold_meta

        workflow.connect([
            (inputnode, sdc_unwarp_wf, [('bold_ref', 'inputnode.in_reference'),
                                        ('bold_ref_brain',
                                         'inputnode.in_reference_brain'),
                                        ('bold_mask', 'inputnode.in_mask')]),
            (fmap_estimator_wf, sdc_unwarp_wf,
             [('outputnode.fmap', 'inputnode.fmap'),
              ('outputnode.fmap_ref', 'inputnode.fmap_ref'),
              ('outputnode.fmap_mask', 'inputnode.fmap_mask')]),
        ])

    # FIELDMAP-less path
    if any(fm['suffix'] == 'syn' for fm in fmaps):
        # Select template
        sdc_select_std = pe.Node(KeySelect(fields=['std2anat_xfm']),
                                 name='sdc_select_std',
                                 run_without_submitting=True)

        syn_sdc_wf = init_syn_sdc_wf(bold_pe=bold_meta.get(
            'PhaseEncodingDirection', None),
                                     omp_nthreads=omp_nthreads)

        workflow.connect([
            (inputnode, sdc_select_std, [('template', 'key'),
                                         ('templates', 'keys'),
                                         ('std2anat_xfm', 'std2anat_xfm')]),
            (sdc_select_std, syn_sdc_wf, [('std2anat_xfm',
                                           'inputnode.std2anat_xfm')]),
            (inputnode, syn_sdc_wf, [('t1_brain', 'inputnode.t1_brain'),
                                     ('bold_ref', 'inputnode.bold_ref'),
                                     ('bold_ref_brain',
                                      'inputnode.bold_ref_brain'),
                                     ('template', 'inputnode.template')]),
        ])

        # XXX Eliminate branch when forcing isn't an option
        if fmap['suffix'] == 'syn':  # No fieldmaps, but --use-syn
            outputnode.inputs.method = 'FLB ("fieldmap-less", SyN-based)'
            sdc_unwarp_wf = syn_sdc_wf
        else:  # --force-syn was called when other fieldmap was present
            sdc_unwarp_wf.__desc__ = None
            workflow.connect([
                (syn_sdc_wf, outputnode, [('outputnode.out_reference',
                                           'syn_bold_ref')]),
            ])

    workflow.connect([
        (sdc_unwarp_wf, outputnode, [('outputnode.out_warp', 'out_warp'),
                                     ('outputnode.out_reference', 'bold_ref'),
                                     ('outputnode.out_reference_brain',
                                      'bold_ref_brain'),
                                     ('outputnode.out_mask', 'bold_mask')]),
    ])

    return workflow
Esempio n. 20
0
def init_func_derivatives_wf(
    bids_root,
    cifti_output,
    freesurfer,
    metadata,
    output_dir,
    spaces,
    use_aroma,
    name='func_derivatives_wf',
):
    """
    Set up a battery of datasinks to store derivatives in the right location.

    Parameters
    ----------
    bids_root : :obj:`str`
        Original BIDS dataset path.
    cifti_output : :obj:`bool`
        Whether the ``--cifti-output`` flag was set.
    freesurfer : :obj:`bool`
        Whether FreeSurfer anatomical processing was run.
    metadata : :obj:`dict`
        Metadata dictionary associated to the BOLD run.
    output_dir : :obj:`str`
        Where derivatives should be written out to.
    spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences`
        A container for storing, organizing, and parsing spatial normalizations. Composed of
        :py:class:`~niworkflows.utils.spaces.Reference` objects representing spatial references.
        Each ``Reference`` contains a space, which is a string of either TemplateFlow template IDs
        (e.g., ``MNI152Lin``, ``MNI152NLin6Asym``, ``MNIPediatricAsym``), nonstandard references
        (e.g., ``T1w`` or ``anat``, ``sbref``, ``run``, etc.), or a custom template located in
        the TemplateFlow root directory. Each ``Reference`` may also contain a spec, which is a
        dictionary with template specifications (e.g., a specification of ``{'resolution': 2}``
        would lead to resampling on a 2mm resolution of the space).
    use_aroma : :obj:`bool`
        Whether ``--use-aroma`` flag was set.
    name : :obj:`str`
        This workflow's identifier (default: ``func_derivatives_wf``).

    """
    from niworkflows.engine.workflows import LiterateWorkflow as Workflow
    from niworkflows.interfaces.utility import KeySelect
    from smriprep.workflows.outputs import _bids_relative

    nonstd_spaces = set(spaces.get_nonstandard())
    workflow = Workflow(name=name)

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'aroma_noise_ics', 'bold_aparc_std', 'bold_aparc_t1', 'bold_aseg_std',
        'bold_aseg_t1', 'bold_cifti', 'bold_mask_std', 'bold_mask_t1', 'bold_std',
        'bold_std_ref', 'bold_t1', 'bold_t1_ref', 'bold_native', 'bold_native_ref',
        'bold_mask_native', 'cifti_variant', 'cifti_metadata', 'cifti_density',
        'confounds', 'confounds_metadata', 'melodic_mix', 'nonaggr_denoised_file',
        'source_file', 'surf_files', 'surf_refs', 'template', 'spatial_reference']),
        name='inputnode')

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

    ds_confounds = pe.Node(DerivativesDataSink(
        base_directory=output_dir, desc='confounds', suffix='regressors',
        dismiss_entities=("echo",)),
        name="ds_confounds", run_without_submitting=True,
        mem_gb=DEFAULT_MEMORY_MIN_GB)
    workflow.connect([
        (inputnode, raw_sources, [('source_file', 'in_files')]),
        (inputnode, ds_confounds, [('source_file', 'source_file'),
                                   ('confounds', 'in_file'),
                                   ('confounds_metadata', 'meta_dict')]),
    ])

    if nonstd_spaces.intersection(('func', 'run', 'bold', 'boldref', 'sbref')):
        ds_bold_native = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir, desc='preproc', compress=True, SkullStripped=False,
                RepetitionTime=metadata.get('RepetitionTime'), TaskName=metadata.get('TaskName'),
                dismiss_entities=("echo",)),
            name='ds_bold_native', run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB)
        ds_bold_native_ref = pe.Node(
            DerivativesDataSink(base_directory=output_dir, suffix='boldref', compress=True,
                                dismiss_entities=("echo",)),
            name='ds_bold_native_ref', run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB)
        ds_bold_mask_native = pe.Node(
            DerivativesDataSink(base_directory=output_dir, desc='brain', suffix='mask',
                                compress=True, dismiss_entities=("echo",)),
            name='ds_bold_mask_native', run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB)

        workflow.connect([
            (inputnode, ds_bold_native, [('source_file', 'source_file'),
                                         ('bold_native', 'in_file')]),
            (inputnode, ds_bold_native_ref, [('source_file', 'source_file'),
                                             ('bold_native_ref', 'in_file')]),
            (inputnode, ds_bold_mask_native, [('source_file', 'source_file'),
                                              ('bold_mask_native', 'in_file')]),
            (raw_sources, ds_bold_mask_native, [('out', 'RawSources')]),
        ])

    # Resample to T1w space
    if nonstd_spaces.intersection(('T1w', 'anat')):
        ds_bold_t1 = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir, space='T1w', desc='preproc', compress=True,
                SkullStripped=False, RepetitionTime=metadata.get('RepetitionTime'),
                TaskName=metadata.get('TaskName'), dismiss_entities=("echo",)),
            name='ds_bold_t1', run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB)
        ds_bold_t1_ref = pe.Node(
            DerivativesDataSink(base_directory=output_dir, space='T1w', suffix='boldref',
                                compress=True, dismiss_entities=("echo",)),
            name='ds_bold_t1_ref', run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB)

        ds_bold_mask_t1 = pe.Node(
            DerivativesDataSink(base_directory=output_dir, space='T1w', desc='brain',
                                suffix='mask', compress=True, dismiss_entities=("echo",)),
            name='ds_bold_mask_t1', run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB)
        workflow.connect([
            (inputnode, ds_bold_t1, [('source_file', 'source_file'),
                                     ('bold_t1', 'in_file')]),
            (inputnode, ds_bold_t1_ref, [('source_file', 'source_file'),
                                         ('bold_t1_ref', 'in_file')]),
            (inputnode, ds_bold_mask_t1, [('source_file', 'source_file'),
                                          ('bold_mask_t1', 'in_file')]),
            (raw_sources, ds_bold_mask_t1, [('out', 'RawSources')]),
        ])
        if freesurfer:
            ds_bold_aseg_t1 = pe.Node(DerivativesDataSink(
                base_directory=output_dir, space='T1w', desc='aseg', suffix='dseg',
                compress=True, dismiss_entities=("echo",)),
                name='ds_bold_aseg_t1', run_without_submitting=True,
                mem_gb=DEFAULT_MEMORY_MIN_GB)
            ds_bold_aparc_t1 = pe.Node(DerivativesDataSink(
                base_directory=output_dir, space='T1w', desc='aparcaseg', suffix='dseg',
                compress=True, dismiss_entities=("echo",)),
                name='ds_bold_aparc_t1', run_without_submitting=True,
                mem_gb=DEFAULT_MEMORY_MIN_GB)
            workflow.connect([
                (inputnode, ds_bold_aseg_t1, [('source_file', 'source_file'),
                                              ('bold_aseg_t1', 'in_file')]),
                (inputnode, ds_bold_aparc_t1, [('source_file', 'source_file'),
                                               ('bold_aparc_t1', 'in_file')]),
            ])

    if use_aroma:
        ds_aroma_noise_ics = pe.Node(DerivativesDataSink(
            base_directory=output_dir, suffix='AROMAnoiseICs', dismiss_entities=("echo",)),
            name="ds_aroma_noise_ics", run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB)
        ds_melodic_mix = pe.Node(DerivativesDataSink(
            base_directory=output_dir, desc='MELODIC', suffix='mixing',
            dismiss_entities=("echo",)),
            name="ds_melodic_mix", run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB)
        ds_aroma_std = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir, space='MNI152NLin6Asym', desc='smoothAROMAnonaggr',
                compress=True),
            name='ds_aroma_std', run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB)

        workflow.connect([
            (inputnode, ds_aroma_noise_ics, [('source_file', 'source_file'),
                                             ('aroma_noise_ics', 'in_file')]),
            (inputnode, ds_melodic_mix, [('source_file', 'source_file'),
                                         ('melodic_mix', 'in_file')]),
            (inputnode, ds_aroma_std, [('source_file', 'source_file'),
                                       ('nonaggr_denoised_file', 'in_file')]),
        ])

    if getattr(spaces, '_cached') is None:
        return workflow

    # Store resamplings in standard spaces when listed in --output-spaces
    if spaces.cached.references:
        from niworkflows.interfaces.space import SpaceDataSource

        spacesource = pe.Node(SpaceDataSource(),
                              name='spacesource', run_without_submitting=True)
        spacesource.iterables = ('in_tuple', [
            (s.fullname, s.spec) for s in spaces.cached.get_standard(dim=(3,))
        ])

        select_std = pe.Node(KeySelect(
            fields=['template', 'bold_std', 'bold_std_ref', 'bold_mask_std']),
            name='select_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB)

        ds_bold_std = pe.Node(
            DerivativesDataSink(
                base_directory=output_dir, desc='preproc', compress=True, SkullStripped=False,
                RepetitionTime=metadata.get('RepetitionTime'), TaskName=metadata.get('TaskName'),
                dismiss_entities=("echo",)),
            name='ds_bold_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB)
        ds_bold_std_ref = pe.Node(
            DerivativesDataSink(base_directory=output_dir, suffix='boldref', compress=True,
                                dismiss_entities=("echo",)),
            name='ds_bold_std_ref', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB)
        ds_bold_mask_std = pe.Node(
            DerivativesDataSink(base_directory=output_dir, desc='brain', suffix='mask',
                                compress=True, dismiss_entities=("echo",)),
            name='ds_bold_mask_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB)

        workflow.connect([
            (inputnode, ds_bold_std, [('source_file', 'source_file')]),
            (inputnode, ds_bold_std_ref, [('source_file', 'source_file')]),
            (inputnode, ds_bold_mask_std, [('source_file', 'source_file')]),
            (inputnode, select_std, [('bold_std', 'bold_std'),
                                     ('bold_std_ref', 'bold_std_ref'),
                                     ('bold_mask_std', 'bold_mask_std'),
                                     ('template', 'template'),
                                     ('spatial_reference', 'keys')]),
            (spacesource, select_std, [('uid', 'key')]),
            (select_std, ds_bold_std, [('bold_std', 'in_file')]),
            (spacesource, ds_bold_std, [('space', 'space'),
                                        ('cohort', 'cohort'),
                                        ('resolution', 'resolution'),
                                        ('density', 'density')]),
            (select_std, ds_bold_std_ref, [('bold_std_ref', 'in_file')]),
            (spacesource, ds_bold_std_ref, [('space', 'space'),
                                            ('cohort', 'cohort'),
                                            ('resolution', 'resolution'),
                                            ('density', 'density')]),
            (select_std, ds_bold_mask_std, [('bold_mask_std', 'in_file')]),
            (spacesource, ds_bold_mask_std, [('space', 'space'),
                                             ('cohort', 'cohort'),
                                             ('resolution', 'resolution'),
                                             ('density', 'density')]),
            (raw_sources, ds_bold_mask_std, [('out', 'RawSources')]),
        ])

        if freesurfer:
            select_fs_std = pe.Node(KeySelect(
                fields=['bold_aseg_std', 'bold_aparc_std', 'template']),
                name='select_fs_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB)
            ds_bold_aseg_std = pe.Node(DerivativesDataSink(
                base_directory=output_dir, desc='aseg', suffix='dseg', compress=True,
                dismiss_entities=("echo",)),
                name='ds_bold_aseg_std', run_without_submitting=True,
                mem_gb=DEFAULT_MEMORY_MIN_GB)
            ds_bold_aparc_std = pe.Node(DerivativesDataSink(
                base_directory=output_dir, desc='aparcaseg', suffix='dseg', compress=True,
                dismiss_entities=("echo",)),
                name='ds_bold_aparc_std', run_without_submitting=True,
                mem_gb=DEFAULT_MEMORY_MIN_GB)
            workflow.connect([
                (spacesource, select_fs_std, [('uid', 'key')]),
                (inputnode, select_fs_std, [('bold_aseg_std', 'bold_aseg_std'),
                                            ('bold_aparc_std', 'bold_aparc_std'),
                                            ('template', 'template'),
                                            ('spatial_reference', 'keys')]),
                (select_fs_std, ds_bold_aseg_std, [('bold_aseg_std', 'in_file')]),
                (spacesource, ds_bold_aseg_std, [('space', 'space'),
                                                 ('cohort', 'cohort'),
                                                 ('resolution', 'resolution'),
                                                 ('density', 'density')]),
                (select_fs_std, ds_bold_aparc_std, [('bold_aparc_std', 'in_file')]),
                (spacesource, ds_bold_aparc_std, [('space', 'space'),
                                                  ('cohort', 'cohort'),
                                                  ('resolution', 'resolution'),
                                                  ('density', 'density')]),
                (inputnode, ds_bold_aseg_std, [('source_file', 'source_file')]),
                (inputnode, ds_bold_aparc_std, [('source_file', 'source_file')])
            ])

    fs_outputs = spaces.cached.get_fs_spaces()
    if freesurfer and fs_outputs:
        from niworkflows.interfaces.surf import Path2BIDS

        select_fs_surf = pe.Node(KeySelect(
            fields=['surfaces', 'surf_kwargs']), name='select_fs_surf',
            run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB)
        select_fs_surf.iterables = [('key', fs_outputs)]
        select_fs_surf.inputs.surf_kwargs = [{'space': s} for s in fs_outputs]

        name_surfs = pe.MapNode(Path2BIDS(pattern=r'(?P<hemi>[lr])h.\w+'),
                                iterfield='in_file', name='name_surfs',
                                run_without_submitting=True)

        ds_bold_surfs = pe.MapNode(DerivativesDataSink(
            base_directory=output_dir, extension="func.gii", dismiss_entities=("echo",)),
            iterfield=['in_file', 'hemi'], name='ds_bold_surfs',
            run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB)

        workflow.connect([
            (inputnode, select_fs_surf, [
                ('surf_files', 'surfaces'),
                ('surf_refs', 'keys')]),
            (select_fs_surf, name_surfs, [('surfaces', 'in_file')]),
            (inputnode, ds_bold_surfs, [('source_file', 'source_file')]),
            (select_fs_surf, ds_bold_surfs, [('surfaces', 'in_file'),
                                             ('key', 'space')]),
            (name_surfs, ds_bold_surfs, [('hemi', 'hemi')]),
        ])

    # CIFTI output
    if cifti_output:
        ds_bold_cifti = pe.Node(DerivativesDataSink(
            base_directory=output_dir, suffix='bold', compress=False, dismiss_entities=("echo",)),
            name='ds_bold_cifti', run_without_submitting=True,
            mem_gb=DEFAULT_MEMORY_MIN_GB)
        workflow.connect([
            (inputnode, ds_bold_cifti, [(('bold_cifti', _unlist), 'in_file'),
                                        ('source_file', 'source_file'),
                                        (('cifti_metadata', _get_surface), 'space'),
                                        ('cifti_density', 'density'),
                                        (('cifti_metadata', _read_json), 'meta_dict')])
        ])

    return workflow
Esempio n. 21
0
def init_bold_std_trans_wf(freesurfer,
                           mem_gb,
                           omp_nthreads,
                           standard_spaces,
                           name='bold_std_trans_wf',
                           use_compression=True,
                           use_fieldwarp=False):
    """
    This workflow samples functional images into standard space with a single
    resampling of the original BOLD series.

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

        from collections import OrderedDict
        from fmriprep.workflows.bold import init_bold_std_trans_wf
        wf = init_bold_std_trans_wf(
            freesurfer=True,
            mem_gb=3,
            omp_nthreads=1,
            standard_spaces=OrderedDict([('MNI152Lin', {}),
                                         ('fsaverage', {'density': '10k'})]),
        )

    **Parameters**

        freesurfer : bool
            Whether to generate FreeSurfer's aseg/aparc segmentations on BOLD space.
        mem_gb : float
            Size of BOLD file in GB
        omp_nthreads : int
            Maximum number of threads an individual process may use
        standard_spaces : OrderedDict
            Ordered dictionary where keys are TemplateFlow ID strings (e.g.,
            ``MNI152Lin``, ``MNI152NLin6Asym``, ``MNI152NLin2009cAsym``, or ``fsLR``),
            or paths pointing to custom templates organized in a TemplateFlow-like structure.
            Values of the dictionary aggregate modifiers (e.g., the value for the key ``MNI152Lin``
            could be ``{'resolution': 2}`` if one wants the resampling to be done on the 2mm
            resolution version of the selected template).
        name : str
            Name of workflow (default: ``bold_std_trans_wf``)
        use_compression : bool
            Save registered BOLD series as ``.nii.gz``
        use_fieldwarp : bool
            Include SDC warp in single-shot transform from BOLD to MNI

    **Inputs**

        anat2std_xfm
            List of anatomical-to-standard space transforms generated during
            spatial normalization.
        bold_aparc
            FreeSurfer's ``aparc+aseg.mgz`` atlas projected into the T1w reference
            (only if ``recon-all`` was run).
        bold_aseg
            FreeSurfer's ``aseg.mgz`` atlas projected into the T1w reference
            (only if ``recon-all`` was run).
        bold_mask
            Skull-stripping mask of reference image
        bold_split
            Individual 3D volumes, not motion corrected
        fieldwarp
            a :abbr:`DFM (displacements field map)` in ITK format
        hmc_xforms
            List of affine transforms aligning each volume to ``ref_image`` in ITK format
        itk_bold_to_t1
            Affine transform from ``ref_bold_brain`` to T1 space (ITK format)
        name_source
            BOLD series NIfTI file
            Used to recover original information lost during processing
        templates
            List of templates that were applied as targets during
            spatial normalization.

    **Outputs** - Two outputnodes are available. One output node (with name ``poutputnode``)
    will be parameterized in a Nipype sense (see `Nipype iterables
    <https://miykael.github.io/nipype_tutorial/notebooks/basic_iteration.html>`__), and a
    second node (``outputnode``) will collapse the parameterized outputs into synchronous
    lists of the following fields:

        bold_std
            BOLD series, resampled to template space
        bold_std_ref
            Reference, contrast-enhanced summary of the BOLD series, resampled to template space
        bold_mask_std
            BOLD series mask in template space
        bold_aseg_std
            FreeSurfer's ``aseg.mgz`` atlas, in template space at the BOLD resolution
            (only if ``recon-all`` was run)
        bold_aparc_std
            FreeSurfer's ``aparc+aseg.mgz`` atlas, in template space at the BOLD resolution
            (only if ``recon-all`` was run)
        templates
            Template identifiers synchronized correspondingly to previously
            described outputs.

    """

    # Filter ``standard_spaces``
    vol_std_spaces = [
        k for k in standard_spaces.keys() if not k.startswith('fs')
    ]

    workflow = Workflow(name=name)

    if len(vol_std_spaces) == 1:
        workflow.__desc__ = """\
The BOLD time-series were resampled into standard space,
generating a *preprocessed BOLD run in {tpl} space*.
""".format(tpl=vol_std_spaces)
    else:
        workflow.__desc__ = """\
The BOLD time-series were resampled into several standard spaces,
correspondingly generating the following *spatially-normalized,
preprocessed BOLD runs*: {tpl}.
""".format(tpl=', '.join(vol_std_spaces))

    inputnode = pe.Node(niu.IdentityInterface(fields=[
        'anat2std_xfm',
        'bold_aparc',
        'bold_aseg',
        'bold_mask',
        'bold_split',
        'fieldwarp',
        'hmc_xforms',
        'itk_bold_to_t1',
        'name_source',
        'templates',
    ]),
                        name='inputnode')

    select_std = pe.Node(KeySelect(fields=['resolution', 'anat2std_xfm']),
                         name='select_std',
                         run_without_submitting=True)

    select_std.inputs.resolution = [
        v.get('resolution') or v.get('res') or 'native'
        for k, v in list(standard_spaces.items()) if k in vol_std_spaces
    ]
    select_std.iterables = ('key', vol_std_spaces)

    select_tpl = pe.Node(niu.Function(function=_select_template),
                         name='select_tpl',
                         run_without_submitting=True)
    select_tpl.inputs.template_specs = standard_spaces

    gen_ref = pe.Node(GenerateSamplingReference(), name='gen_ref',
                      mem_gb=0.3)  # 256x256x256 * 64 / 8 ~ 150MB)

    mask_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel',
                                           float=True),
                           name='mask_std_tfm',
                           mem_gb=1)

    # Write corrected file in the designated output dir
    mask_merge_tfms = pe.Node(niu.Merge(2),
                              name='mask_merge_tfms',
                              run_without_submitting=True,
                              mem_gb=DEFAULT_MEMORY_MIN_GB)

    workflow.connect([
        (inputnode, select_std, [('templates', 'keys'),
                                 ('anat2std_xfm', 'anat2std_xfm')]),
        (inputnode, mask_std_tfm, [('bold_mask', 'input_image')]),
        (inputnode, gen_ref, [(('bold_split', _first), 'moving_image')]),
        (inputnode, mask_merge_tfms, [(('itk_bold_to_t1', _aslist), 'in2')]),
        (select_std, select_tpl, [('key', 'template')]),
        (select_std, mask_merge_tfms, [('anat2std_xfm', 'in1')]),
        (select_std, gen_ref, [(('resolution', _is_native), 'keep_native')]),
        (select_tpl, gen_ref, [('out', 'fixed_image')]),
        (mask_merge_tfms, mask_std_tfm, [('out', 'transforms')]),
        (gen_ref, mask_std_tfm, [('out_file', 'reference_image')]),
    ])

    nxforms = 4 if use_fieldwarp else 3
    merge_xforms = pe.Node(niu.Merge(nxforms),
                           name='merge_xforms',
                           run_without_submitting=True,
                           mem_gb=DEFAULT_MEMORY_MIN_GB)
    workflow.connect([(inputnode, merge_xforms, [('hmc_xforms',
                                                  'in%d' % nxforms)])])

    if use_fieldwarp:
        workflow.connect([(inputnode, merge_xforms, [('fieldwarp', 'in3')])])

    bold_to_std_transform = pe.Node(MultiApplyTransforms(
        interpolation="LanczosWindowedSinc", float=True, copy_dtype=True),
                                    name='bold_to_std_transform',
                                    mem_gb=mem_gb * 3 * omp_nthreads,
                                    n_procs=omp_nthreads)

    merge = pe.Node(Merge(compress=use_compression),
                    name='merge',
                    mem_gb=mem_gb * 3)

    # Generate a reference on the target T1w space
    gen_final_ref = init_bold_reference_wf(omp_nthreads=omp_nthreads,
                                           pre_mask=True)

    workflow.connect([
        (inputnode, merge_xforms, [(('itk_bold_to_t1', _aslist), 'in2')]),
        (inputnode, merge, [('name_source', 'header_source')]),
        (inputnode, bold_to_std_transform, [('bold_split', 'input_image')]),
        (select_std, merge_xforms, [('anat2std_xfm', 'in1')]),
        (merge_xforms, bold_to_std_transform, [('out', 'transforms')]),
        (gen_ref, bold_to_std_transform, [('out_file', 'reference_image')]),
        (bold_to_std_transform, merge, [('out_files', 'in_files')]),
        (merge, gen_final_ref, [('out_file', 'inputnode.bold_file')]),
        (mask_std_tfm, gen_final_ref, [('output_image', 'inputnode.bold_mask')
                                       ]),
    ])

    # Connect output nodes
    output_names = ['bold_std', 'bold_std_ref', 'bold_mask_std', 'templates']
    if freesurfer:
        output_names += ['bold_aseg_std', 'bold_aparc_std']

    # poutputnode - parametric output node
    poutputnode = pe.Node(niu.IdentityInterface(fields=output_names),
                          name='poutputnode')

    workflow.connect([
        (gen_final_ref, poutputnode, [('outputnode.ref_image', 'bold_std_ref')
                                      ]),
        (merge, poutputnode, [('out_file', 'bold_std')]),
        (mask_std_tfm, poutputnode, [('output_image', 'bold_mask_std')]),
        (select_std, poutputnode, [('key', 'templates')]),
    ])

    if freesurfer:
        # Sample the parcellation files to functional space
        aseg_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel',
                                               float=True),
                               name='aseg_std_tfm',
                               mem_gb=1)
        aparc_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel',
                                                float=True),
                                name='aparc_std_tfm',
                                mem_gb=1)

        workflow.connect([
            (inputnode, aseg_std_tfm, [('bold_aseg', 'input_image')]),
            (inputnode, aparc_std_tfm, [('bold_aparc', 'input_image')]),
            (select_std, aseg_std_tfm, [('anat2std_xfm', 'transforms')]),
            (select_std, aparc_std_tfm, [('anat2std_xfm', 'transforms')]),
            (gen_ref, aseg_std_tfm, [('out_file', 'reference_image')]),
            (gen_ref, aparc_std_tfm, [('out_file', 'reference_image')]),
            (aseg_std_tfm, poutputnode, [('output_image', 'bold_aseg_std')]),
            (aparc_std_tfm, poutputnode, [('output_image', 'bold_aparc_std')]),
        ])

    # Connect outputnode to the parameterized outputnode
    outputnode = pe.JoinNode(niu.IdentityInterface(fields=output_names),
                             name='outputnode',
                             joinsource='select_std')
    workflow.connect([(poutputnode, outputnode, [(f, f)
                                                 for f in output_names])])

    return workflow
Esempio n. 22
0
def init_anat_report_wf(workdir=None,
                        name="anat_report_wf",
                        memcalc=MemoryCalculator.default()):
    workflow = pe.Workflow(name=name)

    fmriprepreports = ["t1w_dseg_mask", "std_t1w"]
    fmriprepreportdatasinks = [f"ds_{fr}_report" for fr in fmriprepreports]

    inputnode = pe.Node(
        niu.IdentityInterface(fields=[
            "standardized",
            "std_mask",
            "template",
            "t1w_preproc",
            "t1w_mask",
            "t1w_dseg",
            *fmriprepreportdatasinks,
            "tags",
        ]),
        name="inputnode",
    )

    select_std = pe.Node(
        KeySelect(fields=["standardized", "std_mask"]),
        name="select_std",
        run_without_submitting=True,
        nohash=True,
    )
    select_std.inputs.key = constants.reference_space
    workflow.connect(inputnode, "standardized", select_std, "standardized")
    workflow.connect(inputnode, "std_mask", select_std, "std_mask")
    workflow.connect(inputnode, "template", select_std, "keys")

    #
    make_resultdicts = pe.Node(
        MakeResultdicts(
            reportkeys=["skull_strip_report", "t1_norm_rpt", *fmriprepreports
                        ]),
        name="make_resultdicts",
    )
    workflow.connect(inputnode, "tags", make_resultdicts, "tags")

    #
    resultdict_datasink = pe.Node(ResultdictDatasink(base_directory=workdir),
                                  name="resultdict_datasink")
    workflow.connect(make_resultdicts, "resultdicts", resultdict_datasink,
                     "indicts")

    #
    for fr, frd in zip(fmriprepreports, fmriprepreportdatasinks):
        workflow.connect(inputnode, frd, make_resultdicts, fr)

    # T1w segmentation
    skull_strip_report = pe.Node(SimpleShowMaskRPT(),
                                 name="skull_strip_report")
    workflow.connect(inputnode, "t1w_preproc", skull_strip_report,
                     "background_file")
    workflow.connect(inputnode, "t1w_mask", skull_strip_report, "mask_file")
    workflow.connect(skull_strip_report, "out_report", make_resultdicts,
                     "skull_strip_report")

    # T1 -> mni
    spaces = config.workflow.spaces
    assert isinstance(spaces, SpatialReferences)
    t1_norm_rpt = pe.Node(
        PlotRegistration(template=spaces.get_spaces()[0]),
        name="t1_norm_rpt",
        mem_gb=memcalc.min_gb,
    )
    workflow.connect(select_std, "standardized", t1_norm_rpt, "in_file")
    workflow.connect(select_std, "std_mask", t1_norm_rpt, "mask_file")
    workflow.connect(t1_norm_rpt, "out_report", make_resultdicts,
                     "t1_norm_rpt")

    return workflow