예제 #1
0
def init_single_subject_wf(
    opts: ArgumentParser,
    layout: BIDSLayout,
    run_uuid: str,
    work_dir: str,
    output_dir: str,
    name: str,
    subject_id: str,
    reportlets_dir: str,
):
    import nilearn
    if name in ('single_subject_wf', 'single_subject_test_wf'):
        # for documentation purposes
        subject_data = {
            'bold': ['/completely/made/up/path/sub-01_task-nback_bold.nii.gz']
        }
    else:
        subject_data = collect_data(layout, subject_id, None)[0]

    workflow = Workflow(name=name)
    workflow.base_dir = work_dir
    workflow.__desc__ = """
    Results included in this manuscript come from processing
    performed using *atlasTransform* {atlasTransform_ver}
    (@atlasTransform1; @atlasTransform; RRID:some.id),
    which is based on *Nipype* {nipype_ver}
    (@nipype1; @nipype2; RRID:SCR_002502).
    """.format(atlasTransform_ver=__version__, nipype_ver=nipype_ver)
    workflow.__postdesc__ = """
    Many internal operations of *atlasTransform* use
    *Nilearn* {nilearn_ver} [@nilearn, RRID:SCR_001362].
    ### Copyright Waiver
    The above boilerplate text was automatically generated by fMRIPrep
    with the express intention that users should copy and paste this
    text into their manuscripts *unchanged*.
    It is released under the [CC0]\
    (https://creativecommons.org/publicdomain/zero/1.0/) license.
    ### References
    """.format(nilearn_ver=nilearn.version.__version__)

    inputnode = pe.Node(niu.IdentityInterface(fields=['subjects_dir']),
                        name='inputnode')
    #
    # # require_masks = opts.source_format == 'bold'
    # bidssrc = pe.Node(BIDSPlusDataGrabber(subject_data=subject_data, require_masks=False),
    #                   name='bidssrc')
    #
    # # bids_info = pe.Node(BIDSInfo(
    # #     bids_dir=layout.root, bids_validate=False), name='bids_info')
    #
    # summary = pe.Node(SubjectSummary(),
    #     name='summary', run_without_submitting=True)
    #
    # about = pe.Node(AboutSummary(version=__version__,
    #                              command=' '.join(sys.argv)),
    #                 name='about', run_without_submitting=True)
    #
    # ds_report_summary = pe.Node(
    #     DerivativesDataSink(base_directory=reportlets_dir,
    #                         desc='summary', keep_dtype=True),
    #     name='ds_report_summary', run_without_submitting=True)
    #
    # ds_report_about = pe.Node(
    #     DerivativesDataSink(base_directory=reportlets_dir,
    #                         desc='about', keep_dtype=True),
    #     name='ds_report_about', run_without_submitting=True)

    # Preprocessing of T1w (includes registration to MNI)

    # workflow.connect([
    #     # (bidssrc, bids_info, [('bold', 'in_file')]),
    #     (inputnode, summary, [('subjects_dir', 'subjects_dir')]),
    #     (bidssrc, summary, [('t1w', 't1w'),
    #                         ('t2w', 't2w'),
    #                         ('bold', 'bold')]),
    #     # (bids_info, summary, [('subject', 'subject_id')]),
    #     (bidssrc, ds_report_summary, [('bold', 'source_file')]),
    #     # (summary, ds_report_summary, [('out_report', 'in_file')]),
    #     (bidssrc, ds_report_about, [('bold', 'source_file')]),
    #     (about, ds_report_about, [('out_report', 'in_file')]),
    # ])

    # Overwrite ``out_path_base`` of smriprep's DataSinks
    for node in workflow.list_node_names():
        if node.split('.')[-1].startswith('ds_'):
            workflow.get_node(node).interface.out_path_base = 'atlasTransform'

    for i in range(len(subject_data[opts.source])):
        transform_wf = init_atlas_transform_workflow(
            nifti=subject_data[opts.source][i],
            atlas_name=opts.atlas_name,
            options=opts,
            bids_dir=str(layout.root),
            name='atlas_transform_%d_wf' % i)
        workflow.connect([
            (inputnode, transform_wf, [('subjects_dir',
                                        'inputnode.subjects_dir')]),
        ])

        # outputs_wf = init_datasink_wf(bids_root=str(layout.root), output_dir=str(opts.output_dir), atlas_name=opts.atlas_name, name='ds_%d_wf' % i)
        #
        # workflow.connect([(transform_wf,outputs_wf,[('outputnode.transformed','inputnode.source_file')])])
        # # outputs_wf.inputs.source_file = subject_data['csv'][i] if subject_data.__contains__('mask') else subject_data['bold'][i]
        #
        # workflow.connect([(transform_wf, outputs_wf, [
        #     ('outputnode.transformed', 'inputnode.transformed')
        # ])])

    return workflow
예제 #2
0
def init_ica_aroma_wf(
    dt,
    aroma_melodic_dim=-200,
    err_on_aroma_warn=False,
    susan_fwhm=6.0,
    name='ica_aroma_wf',
):
    """
    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``)

    There is a current discussion on whether other confounds should be extracted
    before or after denoising `here
    <http://nbviewer.jupyter.org/github/nipreps/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 ecp.workflows.confounds import init_ica_aroma_wf
            wf = init_ica_aroma_wf(
                dt=1.0)

    Parameters
    ----------
    dt : :obj:`float`
        bold repetition time
    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)
    err_on_aroma_warn : :obj:`bool`
        Do not fail on ICA-AROMA errors
    susan_fwhm : :obj:`float`
        Kernel width (FWHM in mm) for the smoothing step with
        FSL ``susan`` (default: 6.0mm)
    name : :obj:`str`
        Name of workflow (default: ``ica_aroma_wf``)

    Inputs
    ------
    bold_std
        BOLD series NIfTI file in MNI152NLin6Asym space
    bold_mask_std
        BOLD mask for MNI152NLin6Asym space
    movpar_file
        movement parameter file
    skip_vols
        number of non steady state volumes
        
    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
    aroma_metatdata
        metadata
    out_report
        aroma out report

    """
    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).
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',
        'skip_vols',
    ]),
                        name='inputnode')

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

    # extract out to BOLD base
    rm_non_steady_state = pe.Node(Trim(), name='rm_nonsteady')
    trim_movement = pe.Node(TrimMovement(), name='trim_movement')

    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=dt,
                                  mm_thresh=0.5,
                                  out_stats=True,
                                  dim=aroma_melodic_dim),
                      name="melodic")

    # ica_aroma node
    ica_aroma = pe.Node(ICA_AROMARPT(denoise_type='no',
                                     generate_report=True,
                                     TR=dt,
                                     args='-np'),
                        name='ica_aroma')

    # 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')

    def _getbtthresh(medianval):
        return 0.75 * medianval

    # connect the nodes
    workflow.connect([
        (inputnode, ica_aroma, [('movpar_file', 'motion_parameters')]),
        (inputnode, rm_non_steady_state, [('skip_vols', 'begin_index')]),
        (inputnode, rm_non_steady_state, [('bold_std', 'in_file')]),
        (inputnode, calc_median_val, [('bold_mask_std', 'mask_file')]),
        (inputnode, trim_movement, [('movpar_file', 'movpar_file')]),
        (inputnode, trim_movement, [('skip_vols', 'skip_vols')]),
        (rm_non_steady_state, calc_median_val, [('out_file', 'in_file')]),
        (rm_non_steady_state, calc_bold_mean, [('out_file', '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, [('out_file', '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')]),
        (inputnode, melodic, [('bold_mask_std', 'mask')]),
        # connect nodes to ICA-AROMA
        (smooth, ica_aroma, [('smoothed_file', 'in_file')]),
        (inputnode, 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, outputnode, [('out_report', 'out_report')]),
    ])

    return workflow