# Copyright (C) 2012-2023  C-PAC Developers
# This file is part of C-PAC.
# C-PAC is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
# C-PAC is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with C-PAC. If not, see <https://www.gnu.org/licenses/>.
"""Functional preprocessing"""
# pylint: disable=ungrouped-imports,wrong-import-order,wrong-import-position
from nipype import logging
from nipype.interfaces import afni, ants, fsl, utility as util
logger = logging.getLogger('nipype.workflow')
from CPAC.pipeline import nipype_pipeline_engine as pe
from CPAC.pipeline.nodeblock import nodeblock
from nipype.interfaces.afni import preprocess
from nipype.interfaces.afni import utils as afni_utils
from CPAC.func_preproc.utils import nullify
from CPAC.utils.interfaces.ants import AI  # niworkflows
from CPAC.utils.interfaces.ants import PrintHeader, SetDirectionByMatrix
from CPAC.utils.utils import add_afni_prefix
[docs]def collect_arguments(*args):
    command_args = []
    if args[0]:
        command_args += [args[1]]
    command_args += args[2:]
    return ' '.join(command_args) 
[docs]def anat_refined_mask(init_bold_mask=True, wf_name='init_bold_mask'):
    wf = pe.Workflow(name=wf_name)
    input_node = pe.Node(util.IdentityInterface(fields=['func',
                                                        'anatomical_brain_mask',
                                                        'anat_brain',
                                                        'init_func_brain_mask']),
                         name='inputspec')
    output_node = pe.Node(util.IdentityInterface(fields=['func_brain_mask']),
                          name='outputspec')
    # 1 Take single volume of func
    func_single_volume = pe.Node(interface=afni.Calc(),
                                 name='func_single_volume')
    # TODO add an option to select volume
    func_single_volume.inputs.set(
        expr='a',
        single_idx=1,
        outputtype='NIFTI_GZ'
    )
    wf.connect(input_node, 'func',
               func_single_volume, 'in_file_a')
    # 2 get temporary func brain
    func_tmp_brain = pe.Node(interface=afni_utils.Calc(),
                             name='func_tmp_brain')
    func_tmp_brain.inputs.expr = 'a*b'
    func_tmp_brain.inputs.outputtype = 'NIFTI_GZ'
    wf.connect(func_single_volume, 'out_file',
               func_tmp_brain, 'in_file_a')
    # 2.1 get a tmp func brain mask
    if init_bold_mask == True:
        # 2.1.1 N4BiasFieldCorrection single volume of raw_func
        func_single_volume_n4_corrected = pe.Node(
            interface=ants.N4BiasFieldCorrection(dimension=3,
                                                 copy_header=True,
                                                 bspline_fitting_distance=200),
            shrink_factor=2,
            name='func_single_volume_n4_corrected')
        func_single_volume_n4_corrected.inputs.args = '-r True'
        wf.connect(func_single_volume, 'out_file',
                   func_single_volume_n4_corrected, 'input_image')
        # 2.1.2 bet n4 corrected image - generate tmp func brain mask
        func_tmp_brain_mask = pe.Node(interface=fsl.BET(),
                                      name='func_tmp_brain_mask_pre')
        func_tmp_brain_mask.inputs.mask = True
        wf.connect(func_single_volume_n4_corrected, 'output_image',
                   func_tmp_brain_mask, 'in_file')
        # 2.1.3 dilate func tmp brain mask
        func_tmp_brain_mask_dil = pe.Node(interface=fsl.ImageMaths(),
                                          name='func_tmp_brain_mask_dil')
        func_tmp_brain_mask_dil.inputs.op_string = '-dilM'
        wf.connect(func_tmp_brain_mask, 'mask_file',
                   func_tmp_brain_mask_dil, 'in_file')
        wf.connect(func_tmp_brain_mask_dil, 'out_file',
                   func_tmp_brain, 'in_file_b')
    else:
        # 2.1.1 connect dilated init func brain mask
        wf.connect(input_node, 'init_func_brain_mask',
                   func_tmp_brain, 'in_file_b')
    # 3. get transformation of anat to func
    # 3.1 Register func tmp brain to anat brain to get func2anat matrix
    linear_reg_func_to_anat = pe.Node(interface=fsl.FLIRT(),
                                      name='func_to_anat_linear_reg')
    linear_reg_func_to_anat.inputs.cost = 'mutualinfo'
    linear_reg_func_to_anat.inputs.dof = 6
    wf.connect(func_tmp_brain, 'out_file',
               linear_reg_func_to_anat, 'in_file')
    wf.connect(input_node, 'anat_brain',
               linear_reg_func_to_anat, 'reference')
    # 3.2 Inverse func to anat affine
    inv_func_to_anat_affine = pe.Node(interface=fsl.ConvertXFM(),
                                      name='inv_func2anat_affine')
    inv_func_to_anat_affine.inputs.invert_xfm = True
    wf.connect(linear_reg_func_to_anat, 'out_matrix_file',
               inv_func_to_anat_affine, 'in_file')
    # 4. anat mask to func space
    # Transform anatomical mask to functional space to get BOLD mask
    reg_anat_mask_to_func = pe.Node(interface=fsl.FLIRT(),
                                    name='reg_anat_mask_to_func')
    reg_anat_mask_to_func.inputs.apply_xfm = True
    reg_anat_mask_to_func.inputs.cost = 'mutualinfo'
    reg_anat_mask_to_func.inputs.dof = 6
    reg_anat_mask_to_func.inputs.interp = 'nearestneighbour'
    wf.connect(input_node, 'anatomical_brain_mask',
               reg_anat_mask_to_func, 'in_file')
    wf.connect(func_tmp_brain, 'out_file',
               reg_anat_mask_to_func, 'reference')
    wf.connect(inv_func_to_anat_affine, 'out_file',
               reg_anat_mask_to_func, 'in_matrix_file')
    # 5. get final func mask: refine func tmp mask with anat_mask_in_func mask
    func_mask = pe.Node(interface=fsl.MultiImageMaths(), name='func_mask')
    func_mask.inputs.op_string = "-mul %s"
    wf.connect(reg_anat_mask_to_func, 'out_file',
               func_mask, 'operand_files')
    if init_bold_mask == True:
        wf.connect(func_tmp_brain_mask_dil, 'out_file',
                   func_mask, 'in_file')
    else:
        wf.connect(input_node, 'init_func_brain_mask',
                   func_mask, 'in_file')
    wf.connect(func_mask, 'out_file',
               output_node, 'func_brain_mask')
    return wf 
[docs]def anat_based_mask(wf_name='bold_mask'):
    """reference `DCAN lab BOLD mask <https://github.com/DCAN-Labs/DCAN-HCP/blob/master/fMRIVolume/scripts/DistortionCorrectionAndEPIToT1wReg_FLIRTBBRAndFreeSurferBBRbased.sh>`_
    """
    wf = pe.Workflow(name=wf_name)
    input_node = pe.Node(util.IdentityInterface(fields=['func',
                                                        'anat_brain',
                                                        'anat_head']),
                         name='inputspec')
    output_node = pe.Node(util.IdentityInterface(fields=['func_brain_mask']),
                         name='outputspec')
    # 0. Take single volume of func
    func_single_volume = pe.Node(interface=afni.Calc(),
                            name='func_single_volume')
    func_single_volume.inputs.set(
        expr='a',
        single_idx=1,
        outputtype='NIFTI_GZ'
    )
    wf.connect(input_node, 'func',
                func_single_volume, 'in_file_a')
    # 1. Register func head to anat head to get func2anat matrix
    linear_reg_func_to_anat = pe.Node(interface=fsl.FLIRT(),
                        name='func_to_anat_linear_reg')
    linear_reg_func_to_anat.inputs.dof = 6
    linear_reg_func_to_anat.inputs.interp = 'spline'
    linear_reg_func_to_anat.inputs.searchr_x = [30, 30]
    linear_reg_func_to_anat.inputs.searchr_y = [30, 30]
    linear_reg_func_to_anat.inputs.searchr_z = [30, 30]
    wf.connect(func_single_volume, 'out_file',
                linear_reg_func_to_anat, 'in_file')
    wf.connect(input_node, 'anat_head',
                linear_reg_func_to_anat, 'reference')
    # 2. Inverse func to anat affine, to get anat-to-func transform
    inv_func_to_anat_affine = pe.Node(interface=fsl.ConvertXFM(),
                            name='inv_func2anat_affine')
    inv_func_to_anat_affine.inputs.invert_xfm = True
    wf.connect(linear_reg_func_to_anat, 'out_matrix_file',
                inv_func_to_anat_affine, 'in_file')
    # 3. get BOLD mask
    # 3.1 Apply anat-to-func transform to transfer anatomical brain to functional space
    reg_anat_brain_to_func = pe.Node(interface=fsl.ApplyWarp(),
                            name='reg_anat_brain_to_func')
    reg_anat_brain_to_func.inputs.interp = 'nn'
    reg_anat_brain_to_func.inputs.relwarp = True
    wf.connect(input_node, 'anat_brain',
                reg_anat_brain_to_func, 'in_file')
    wf.connect(input_node, 'func',
                reg_anat_brain_to_func, 'ref_file')
    wf.connect(inv_func_to_anat_affine, 'out_file',
                reg_anat_brain_to_func, 'premat')
    # 3.2 Binarize transfered image and fill holes to get BOLD mask.
    # Binarize
    func_mask_bin = pe.Node(interface=fsl.ImageMaths(),
                                name='func_mask')
    func_mask_bin.inputs.op_string = '-bin'
    wf.connect(reg_anat_brain_to_func, 'out_file',
                func_mask_bin, 'in_file')
    wf.connect(func_mask_bin, 'out_file',
                output_node, 'func_brain_mask')
    return wf 
[docs]def create_scale_func_wf(scaling_factor, wf_name='scale_func'):
    """Workflow to scale func data.
    Workflow Inputs::
        inputspec.func : func file or a list of func/rest nifti file
            User input functional(T2*) Image
    Workflow Outputs::
        outputspec.scaled_func : str (nifti file)
            Path to Output image with scaled data
    Order of commands:
    - Scale the size of the dataset voxels by the factor 'fac'. For details see `3dcalc <https://afni.nimh.nih.gov/pub/dist/doc/program_help/3drefit.html>`_::
        3drefit -xyzscale fac rest.nii.gz
    Parameters
    ----------
    scaling_factor : float
        Scale the size of the dataset voxels by the factor.
    wf_name : str
        name of the workflow
    """
    # allocate a workflow object
    preproc = pe.Workflow(name=wf_name)
    # configure the workflow's input spec
    inputNode = pe.Node(util.IdentityInterface(fields=['func']),
                        name='inputspec')
    # configure the workflow's output spec
    outputNode = pe.Node(util.IdentityInterface(fields=['scaled_func']),
                         name='outputspec')
    # allocate a node to edit the functional file
    func_scale = pe.Node(interface=afni_utils.Refit(),
                         name='func_scale')
    func_scale.inputs.xyzscale = scaling_factor
    # wire in the func_get_idx node
    preproc.connect(inputNode, 'func',
                    func_scale, 'in_file')
    # wire the output
    preproc.connect(func_scale, 'out_file',
                    outputNode, 'scaled_func')
    return preproc 
[docs]def create_wf_edit_func(wf_name="edit_func"):
    """Workflow to edit the scan to the proscribed TRs.
    Workflow Inputs::
        inputspec.func : func file or a list of func/rest nifti file
            User input functional(T2*) Image
        inputspec.start_idx : str
            Starting volume/slice of the functional image (optional)
        inputspec.stop_idx : str
            Last volume/slice of the functional image (optional)
    Workflow Outputs::
        outputspec.edited_func : str (nifti file)
            Path to Output image with the initial few slices dropped
    Order of commands:
    - Get the start and the end volume index of the functional run. If not defined by the user, return the first and last volume.
        get_idx(in_files, stop_idx, start_idx)
    - Dropping the initial TRs. For details see `3dcalc <http://afni.nimh.nih.gov/pub/dist/doc/program_help/3dcalc.html>`_::
        3dcalc -a rest.nii.gz[4..299]
               -expr 'a'
               -prefix rest_3dc.nii.gz
    """
    # allocate a workflow object
    preproc = pe.Workflow(name=wf_name)
    # configure the workflow's input spec
    inputNode = pe.Node(util.IdentityInterface(fields=['func',
                                                       'start_idx',
                                                       'stop_idx']),
                        name='inputspec')
    # configure the workflow's output spec
    outputNode = pe.Node(util.IdentityInterface(fields=['edited_func']),
                         name='outputspec')
    # allocate a node to check that the requested edits are
    # reasonable given the data
    func_get_idx = pe.Node(util.Function(input_names=['in_files',
                                                      'stop_idx',
                                                      'start_idx'],
                                         output_names=['stopidx',
                                                       'startidx'],
                                         function=get_idx),
                           name='func_get_idx')
    # wire in the func_get_idx node
    preproc.connect(inputNode, 'func',
                    func_get_idx, 'in_files')
    preproc.connect(inputNode, 'start_idx',
                    func_get_idx, 'start_idx')
    preproc.connect(inputNode, 'stop_idx',
                    func_get_idx, 'stop_idx')
    # allocate a node to edit the functional file
    func_drop_trs = pe.Node(interface=afni_utils.Calc(),
                            name='func_drop_trs',
                            mem_gb=0.37,
                            mem_x=(739971956005215 / 151115727451828646838272,
                                   'in_file_a'))
    func_drop_trs.inputs.expr = 'a'
    func_drop_trs.inputs.outputtype = 'NIFTI_GZ'
    # wire in the inputs
    preproc.connect(inputNode, 'func',
                    func_drop_trs, 'in_file_a')
    preproc.connect(func_get_idx, 'startidx',
                    func_drop_trs, 'start_idx')
    preproc.connect(func_get_idx, 'stopidx',
                    func_drop_trs, 'stop_idx')
    # wire the output
    preproc.connect(func_drop_trs, 'out_file',
                    outputNode, 'edited_func')
    return preproc 
[docs]def slice_timing_wf(name='slice_timing', tpattern=None, tzero=None):
    # allocate a workflow object
    wf = pe.Workflow(name=name)
    # configure the workflow's input spec
    inputNode = pe.Node(util.IdentityInterface(fields=['func_ts',
                                                       'tr',
                                                       'tpattern']),
                        name='inputspec')
    # configure the workflow's output spec
    outputNode = pe.Node(
        util.IdentityInterface(fields=['slice_time_corrected']),
        name='outputspec')
    # create TShift AFNI node
    func_slice_timing_correction = pe.Node(interface=preprocess.TShift(),
                                           name='slice_timing',
                                           mem_gb=0.45,
                                           mem_x=(5247073869855161 /
                                                  604462909807314587353088,
                                                  'in_file'))
    func_slice_timing_correction.inputs.outputtype = 'NIFTI_GZ'
    if tzero is not None:
        func_slice_timing_correction.inputs.tzero = tzero
    wf.connect([
        (
            inputNode,
            func_slice_timing_correction,
            [
                (
                    'func_ts',
                    'in_file'
                ),
                # (
                #     # add the @ prefix to the tpattern file going into
                #     # AFNI 3dTshift - needed this so the tpattern file
                #     # output from get_scan_params would be tied downstream
                #     # via a connection (to avoid poofing)
                #     ('tpattern', nullify, add_afni_prefix),
                #     'tpattern'
                # ),
                (
                    ('tr', nullify),
                    'tr'
                ),
            ]
        ),
    ])
    if tpattern is not None:
        func_slice_timing_correction.inputs.tpattern = tpattern
    else:
        wf.connect(inputNode, ('tpattern', nullify, add_afni_prefix),
               func_slice_timing_correction, 'tpattern')
    wf.connect(func_slice_timing_correction, 'out_file',
               outputNode, 'slice_time_corrected')
    return wf 
[docs]def get_idx(in_files, stop_idx=None, start_idx=None):
    """
    Method to get the first and the last slice for
    the functional run. It verifies the user specified
    first and last slice. If the values are not valid, it
    calculates and returns the very first and the last slice
    Parameters
    ----------
    in_file : str (nifti file)
       Path to input functional run
    stop_idx : int
        Last volume to be considered, specified by user
        in the configuration file
    stop_idx : int
        First volume to be considered, specified by user
        in the configuration file
    Returns
    -------
    stop_idx :  int
        Value of first slice to consider for the functional run
    start_idx : int
        Value of last slice to consider for the functional run
    """
    # Import packages
    from nibabel import load
    # Init variables
    img = load(in_files)
    hdr = img.header
    shape = hdr.get_data_shape()
    # Check to make sure the input file is 4-dimensional
    if len(shape) != 4:
        raise TypeError('Input nifti file: %s is not a 4D file' % in_files)
    # Grab the number of volumes
    nvols = int(hdr.get_data_shape()[3])
    if (start_idx == None) or (int(start_idx) < 0) or (
        int(start_idx) > (nvols - 1)):
        startidx = 0
    else:
        startidx = int(start_idx)
    if (stop_idx in [None, "End"]) or (int(stop_idx) > (nvols - 1)):
        stopidx = nvols - 1
    else:
        stopidx = int(stop_idx)
    return stopidx, startidx 
[docs]@nodeblock(
    name='func_reorient',
    config=['functional_preproc', 'update_header'],
    switch=['run'],
    inputs=['bold'],
    outputs=['desc-preproc_bold', 'desc-reorient_bold']
)
def func_reorient(wf, cfg, strat_pool, pipe_num, opt=None):
    func_deoblique = pe.Node(interface=afni_utils.Refit(),
                             name=f'func_deoblique_{pipe_num}',
                             mem_gb=0.68,
                             mem_x=(4664065662093477 /
                                    1208925819614629174706176,
                                    'in_file'))
    func_deoblique.inputs.deoblique = True
    node, out = strat_pool.get_data('bold')
    wf.connect(node, out, func_deoblique, 'in_file')
    func_reorient = pe.Node(interface=afni_utils.Resample(),
                            name=f'func_reorient_{pipe_num}',
                            mem_gb=0,
                            mem_x=(0.0115, 'in_file', 't'))
    func_reorient.inputs.orientation = 'RPI'
    func_reorient.inputs.outputtype = 'NIFTI_GZ'
    wf.connect(func_deoblique, 'out_file', func_reorient, 'in_file')
    outputs = {
        'desc-preproc_bold': (func_reorient, 'out_file'),
        'desc-reorient_bold': (func_reorient, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='func_scaling',
    config=['functional_preproc', 'scaling'],
    switch=['run'],
    inputs=['desc-preproc_bold'],
    outputs=['desc-preproc_bold']
)
def func_scaling(wf, cfg, strat_pool, pipe_num, opt=None):
    scale_func_wf = create_scale_func_wf(
        scaling_factor=cfg.scaling_factor,
        wf_name=f"scale_func_{pipe_num}"
    )
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, scale_func_wf, 'inputspec.func')
    outputs = {
        'desc-preproc_bold': (scale_func_wf, 'outputspec.scaled_func')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='func_truncate',
    config=['functional_preproc', 'truncation'],
    inputs=['desc-preproc_bold'],
    outputs={'desc-preproc_bold': {
        'Description': 'Truncated functional time-series BOLD data.'}}
)
def func_truncate(wf, cfg, strat_pool, pipe_num, opt=None):
    # if cfg.functional_preproc['truncation']['start_tr'] == 0 and \
    #                cfg.functional_preproc['truncation']['stop_tr'] == None:
    #    data, key = strat_pool.get_data("desc-preproc_bold",
    #                                           True)
    #    outputs = {key: data}
    #    return (wf, outputs)
    trunc_wf = create_wf_edit_func(
        wf_name=f"edit_func_{pipe_num}"
    )
    trunc_wf.inputs.inputspec.start_idx = cfg.functional_preproc[
        'truncation']['start_tr']
    trunc_wf.inputs.inputspec.stop_idx = cfg.functional_preproc['truncation'][
        'stop_tr']
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, trunc_wf, 'inputspec.func')
    outputs = {
        'desc-preproc_bold': (trunc_wf, 'outputspec.edited_func')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='func_despike',
    config=['functional_preproc', 'despiking'],
    switch=['run'],
    option_key=['space'],
    option_val=['native'],
    inputs=['desc-preproc_bold'],
    outputs={'desc-preproc_bold': {
        'Description': 'De-spiked BOLD time-series via AFNI 3dDespike.'}}
)
def func_despike(wf, cfg, strat_pool, pipe_num, opt=None):
    despike = pe.Node(interface=preprocess.Despike(),
                      name=f'func_despiked_{pipe_num}',
                      mem_gb=0.66,
                      mem_x=(8251808479088459 / 1208925819614629174706176,
                             'in_file'))
    despike.inputs.outputtype = 'NIFTI_GZ'
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, despike, 'in_file')
    outputs = {
        'desc-preproc_bold': (despike, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='func_despike_template',
    config=['functional_preproc', 'despiking'],
    switch=['run'],
    option_key=['space'],
    option_val=['template'],
    inputs=[('space-template_desc-preproc_bold',
             'space-template_res-derivative_desc-preproc_bold'),
            'T1w-template-funcreg', 'T1w-template-deriv'],
    outputs={'space-template_desc-preproc_bold': {
        'Description': 'De-spiked BOLD time-series via AFNI 3dDespike.',
        'Template': 'T1w-template-funcreg'},
             'space-template_res-derivative_desc-preproc_bold': {
        'Description': 'De-spiked BOLD time-series via AFNI 3dDespike.',
        'Template': 'T1w-template-deriv'}}
)
def func_despike_template(wf, cfg, strat_pool, pipe_num, opt=None):
    despike = pe.Node(interface=preprocess.Despike(),
                      name=f'func_despiked_template_{pipe_num}',
                      mem_gb=0.66,
                      mem_x=(8251808479088459 / 1208925819614629174706176,
                             'in_file'))
    despike.inputs.outputtype = 'NIFTI_GZ'
    node, out = strat_pool.get_data("space-template_desc-preproc_bold")
    wf.connect(node, out, despike, 'in_file')
    outputs = {
        'space-template_desc-preproc_bold': (despike, 'out_file')
    }
    
    if strat_pool.get_data("space-template_res-derivative_desc-preproc_bold"):
        despike_funcderiv = pe.Node(interface=preprocess.Despike(),
                                    name=f'func_deriv_despiked_template_{pipe_num}',
                                    mem_gb=0.66,
                                    mem_x=(8251808479088459 / 1208925819614629174706176,
                                    'in_file'))
        despike_funcderiv.inputs.outputtype = 'NIFTI_GZ'
        node, out = strat_pool.get_data("space-template_res-derivative_desc-preproc_bold")
        wf.connect(node, out, despike_funcderiv, 'in_file')
        outputs.update({
            'space-template_res-derivative_desc-preproc_bold':
                (despike_funcderiv, 'out_file')})
    return (wf, outputs) 
[docs]@nodeblock(
    name='func_slice_time',
    config=['functional_preproc', 'slice_timing_correction'],
    switch=['run'],
    inputs=['desc-preproc_bold', 'TR', 'tpattern'],
    outputs={'desc-preproc_bold': {
        'Description': 'Slice-time corrected BOLD time-series via AFNI 3dTShift.'},
             'desc-stc_bold': {
        'Description': 'Slice-time corrected BOLD time-series via AFNI 3dTShift.'}}
)
def func_slice_time(wf, cfg, strat_pool, pipe_num, opt=None):
    slice_time = slice_timing_wf(name='func_slice_timing_correction_'
                                      f'{pipe_num}',
                                 tpattern=cfg.functional_preproc[
                                     'slice_timing_correction']['tpattern'],
                                 tzero=cfg.functional_preproc[
                                     'slice_timing_correction']['tzero'])
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, slice_time, 'inputspec.func_ts')
    node, out = strat_pool.get_data('TR')
    wf.connect(node, out, slice_time, 'inputspec.tr')
    node, out = strat_pool.get_data('tpattern')
    wf.connect(node, out, slice_time, 'inputspec.tpattern')
    outputs = {
        'desc-preproc_bold': (slice_time, 'outputspec.slice_time_corrected'),
        'desc-stc_bold': (slice_time, 'outputspec.slice_time_corrected')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='bold_mask_afni',
    switch=[['functional_preproc', 'run'],
            ['functional_preproc', 'func_masking', 'run']],
    option_key=['functional_preproc', 'func_masking', 'using'],
    option_val='AFNI',
    inputs=['desc-preproc_bold'],
    outputs={'space-bold_desc-brain_mask':
        {'Description': 'Binary brain mask of the BOLD functional time-series created by AFNI 3dAutomask.'}}
)
def bold_mask_afni(wf, cfg, strat_pool, pipe_num, opt=None):
    func_get_brain_mask = pe.Node(interface=preprocess.Automask(),
                                  name=f'func_get_brain_mask_AFNI_{pipe_num}')
    func_get_brain_mask.inputs.outputtype = 'NIFTI_GZ'
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, func_get_brain_mask, 'in_file')
    outputs = {
        'space-bold_desc-brain_mask': (func_get_brain_mask, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='bold_mask_fsl',
    switch=[['functional_preproc', 'run'],
            ['functional_preproc', 'func_masking', 'run']],
    option_key=['functional_preproc', 'func_masking', 'using'],
    option_val='FSL',
    inputs=['desc-preproc_bold'],
    outputs=['space-bold_desc-brain_mask']
)
def bold_mask_fsl(wf, cfg, strat_pool, pipe_num, opt=None):
    inputnode_bet = pe.Node(
        util.IdentityInterface(fields=['frac',
                                       'mesh_boolean',
                                       'outline',
                                       'padding',
                                       'radius',
                                       'reduce_bias',
                                       'remove_eyes',
                                       'robust',
                                       'skull',
                                       'surfaces',
                                       'threshold',
                                       'vertical_gradient']),
        name=f'BET_options_{pipe_num}')
    func_get_brain_mask = pe.Node(interface=fsl.BET(),
                                  name=f'func_get_brain_mask_BET_{pipe_num}')
    func_get_brain_mask.inputs.output_type = 'NIFTI_GZ'
    func_get_brain_mask.inputs.mask = True
    inputnode_bet.inputs.set(
        frac=cfg.functional_preproc['func_masking']['FSL-BET']['frac'],
        mesh_boolean=cfg.functional_preproc['func_masking']['FSL-BET'][
            'mesh_boolean'],
        outline=cfg.functional_preproc['func_masking']['FSL-BET'][
            'outline'],
        padding=cfg.functional_preproc['func_masking']['FSL-BET'][
            'padding'],
        radius=cfg.functional_preproc['func_masking']['FSL-BET']['radius'],
        reduce_bias=cfg.functional_preproc['func_masking']['FSL-BET'][
            'reduce_bias'],
        remove_eyes=cfg.functional_preproc['func_masking']['FSL-BET'][
            'remove_eyes'],
        robust=cfg.functional_preproc['func_masking']['FSL-BET']['robust'],
        skull=cfg.functional_preproc['func_masking']['FSL-BET']['skull'],
        surfaces=cfg.functional_preproc['func_masking']['FSL-BET'][
            'surfaces'],
        threshold=cfg.functional_preproc['func_masking']['FSL-BET'][
            'threshold'],
        vertical_gradient=
        cfg.functional_preproc['func_masking']['FSL-BET'][
            'vertical_gradient'],
    )
    wf.connect([
        (inputnode_bet, func_get_brain_mask, [
            ('frac', 'frac'),
            ('mesh_boolean', 'mesh'),
            ('outline', 'outline'),
            ('padding', 'padding'),
            ('radius', 'radius'),
            ('reduce_bias', 'reduce_bias'),
            ('remove_eyes', 'remove_eyes'),
            ('robust', 'robust'),
            ('skull', 'skull'),
            ('surfaces', 'surfaces'),
            ('threshold', 'threshold'),
            ('vertical_gradient', 'vertical_gradient'),
        ])
    ])
    if cfg.functional_preproc['func_masking']['FSL-BET'][
        'functional_mean_boolean']:
        func_skull_mean = pe.Node(interface=afni_utils.TStat(),
                                  name=f'func_mean_skull_{pipe_num}')
        func_skull_mean.inputs.options = '-mean'
        func_skull_mean.inputs.outputtype = 'NIFTI_GZ'
        node, out = strat_pool.get_data("desc-preproc_bold")
        wf.connect(node, out, func_skull_mean, 'in_file')
        out_node, out_file = (func_skull_mean, 'out_file')
        if cfg.functional_preproc['func_masking']['FSL-BET'][
            'functional_mean_thr']['run']:
            # T=$(fslstats ${subject}_tmean.nii.gz -p 98)
            threshold_T = pe.Node(interface=fsl.ImageStats(),
                                    name=f'func_mean_skull_thr_value_{pipe_num}',
                                    iterfield=['in_file'])
            threshold_T.inputs.op_string = "-p %f " % (cfg.functional_preproc['func_masking']['FSL-BET']['functional_mean_thr']['threshold_value'])
            wf.connect(func_skull_mean, 'out_file', threshold_T, 'in_file')
            # z=$(echo "$T / 10" | bc -l)
            def form_thr_string(thr):
                threshold_z = str(float(thr/10))
                return '-thr %s' % (threshold_z)
            form_thr_string = pe.Node(util.Function(input_names=['thr'],
                                            output_names=['out_str'],
                                            function=form_thr_string),
                                            name=f'form_thr_string_{pipe_num}')
            wf.connect(threshold_T, 'out_stat', form_thr_string, 'thr')
            # fslmaths ${subject}_tmean.nii.gz -thr ${z} ${subject}_tmean_thr.nii.gz
            func_skull_mean_thr = pe.Node(interface=fsl.ImageMaths(),
                                            name=f'func_mean_skull_thr_{pipe_num}')
            wf.connect(func_skull_mean, 'out_file', func_skull_mean_thr, 'in_file')
            wf.connect(form_thr_string, 'out_str', func_skull_mean_thr, 'op_string')
            out_node, out_file = (func_skull_mean_thr, 'out_file')
        if cfg.functional_preproc['func_masking']['FSL-BET'][
            'functional_mean_bias_correction']:
            # fast --nopve -B ${subject}_tmean_thr.nii.gz
            func_mean_skull_fast = pe.Node(interface=fsl.FAST(),
                                                    name=f'func_mean_skull_fast_{pipe_num}')
            func_mean_skull_fast.inputs.no_pve = True
            func_mean_skull_fast.inputs.output_biascorrected = True
            wf.connect(out_node, out_file, func_mean_skull_fast, 'in_files')
            out_node, out_file = (func_mean_skull_fast, 'restored_image')
        wf.connect(out_node, out_file, func_get_brain_mask, 'in_file')
    else:
        func_get_brain_mask.inputs.functional = True
        node, out = strat_pool.get_data("desc-preproc_bold")
        wf.connect(node, out, func_get_brain_mask, 'in_file')
    # erode one voxel of functional brian mask
    erode_one_voxel = pe.Node(interface=fsl.ErodeImage(),
                              name=f'erode_one_voxel_{pipe_num}')
    erode_one_voxel.inputs.kernel_shape = 'box'
    erode_one_voxel.inputs.kernel_size = 1.0
    wf.connect(func_get_brain_mask, 'mask_file',
               erode_one_voxel, 'in_file')
    outputs = {
        'space-bold_desc-brain_mask': (erode_one_voxel, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='bold_mask_fsl_afni',
    switch=[['functional_preproc', 'run'],
            ['functional_preproc', 'func_masking', 'run']],
    option_key=['functional_preproc', 'func_masking', 'using'],
    option_val='FSL_AFNI',
    inputs=[('motion-basefile', 'desc-preproc_bold'), 'FSL-AFNI-bold-ref', 'FSL-AFNI-brain-mask',
            'FSL-AFNI-brain-probseg'],
    outputs=['space-bold_desc-brain_mask', 'desc-ref_bold']
)
def bold_mask_fsl_afni(wf, cfg, strat_pool, pipe_num, opt=None):
    """fMRIPrep-style BOLD mask
    `Ref <https://github.com/nipreps/niworkflows/blob/maint/1.3.x/niworkflows/func/util.py#L246-L514>`_
    """
    # Initialize transforms with antsAI
    init_aff = pe.Node(
        AI(
            metric=("Mattes", 32, "Regular", 0.2),
            transform=("Affine", 0.1),
            search_factor=(20, 0.12),
            principal_axes=False,
            convergence=(10, 1e-6, 10),
            verbose=True,
        ),
        name=f"init_aff_{pipe_num}",
        n_procs=cfg.pipeline_setup['system_config']['num_OMP_threads'],
    )
    node, out = strat_pool.get_data('FSL-AFNI-bold-ref')
    wf.connect(node, out, init_aff, 'fixed_image')
    node, out = strat_pool.get_data('FSL-AFNI-brain-mask')
    wf.connect(node, out, init_aff, 'fixed_image_mask')
    init_aff.inputs.search_grid = (40, (0, 40, 40))
    # Set up spatial normalization
    norm = pe.Node(
        ants.Registration(
            winsorize_upper_quantile=0.98,
            winsorize_lower_quantile=0.05,
            float=True,
            metric=['Mattes'],
            metric_weight=[1],
            radius_or_number_of_bins=[64],
            transforms=['Affine'],
            transform_parameters=[[0.1]],
            number_of_iterations=[[200]],
            convergence_window_size=[10],
            convergence_threshold=[1.e-9],
            sampling_strategy=['Random', 'Random'],
            smoothing_sigmas=[[2]],
            sigma_units=['mm', 'mm', 'mm'],
            shrink_factors=[[2]],
            sampling_percentage=[0.2],
            use_histogram_matching=[True]
        ),
        name=f"norm_{pipe_num}",
        n_procs=cfg.pipeline_setup['system_config']['num_OMP_threads'],
    )
    node, out = strat_pool.get_data('FSL-AFNI-bold-ref')
    wf.connect(node, out, norm, 'fixed_image')
    map_brainmask = pe.Node(
        ants.ApplyTransforms(
            interpolation="BSpline",
            float=True,
        ),
        name=f"map_brainmask_{pipe_num}",
    )
    # Use the higher resolution and probseg for numerical stability in rounding
    node, out = strat_pool.get_data('FSL-AFNI-brain-probseg')
    wf.connect(node, out, map_brainmask, 'input_image')
    binarize_mask = pe.Node(interface=fsl.maths.MathsCommand(),
                            name=f'binarize_mask_{pipe_num}')
    binarize_mask.inputs.args = '-thr 0.85 -bin'
    # Dilate pre_mask
    pre_dilate = pe.Node(
        fsl.DilateImage(
            operation="max",
            kernel_shape="sphere",
            kernel_size=3.0,
            internal_datatype="char",
        ),
        name=f"pre_mask_dilate_{pipe_num}",
    )
    # Fix precision errors
    # https://github.com/ANTsX/ANTs/wiki/Inputs-do-not-occupy-the-same-physical-space#fixing-precision-errors
    print_header = pe.Node(PrintHeader(what_information=4),
                           name=f'print_header_{pipe_num}')
    set_direction = pe.Node(SetDirectionByMatrix(),
                            name=f'set_direction_{pipe_num}')
    # Run N4 normally, force num_threads=1 for stability (images are
    # small, no need for >1)
    n4_correct = pe.Node(
        ants.N4BiasFieldCorrection(
            dimension=3, copy_header=True, bspline_fitting_distance=200
        ),
        shrink_factor=2,
        rescale_intensities = True,
        name=f"n4_correct_{pipe_num}",
        n_procs=1,
    )
    skullstrip_first_pass = pe.Node(
        fsl.BET(frac=0.2, mask=True, functional=False),
        name=f'skullstrip_first_pass_{pipe_num}')
    bet_dilate = pe.Node(
        fsl.DilateImage(operation='max', kernel_shape='sphere',
                        kernel_size=6.0, internal_datatype='char'),
        name=f'skullstrip_first_dilate_{pipe_num}')
    bet_mask = pe.Node(fsl.ApplyMask(), name=f'skullstrip_first_mask_'
                                             f'{pipe_num}')
    unifize = pe.Node(afni_utils.Unifize(t2=True, outputtype='NIFTI_GZ',
                                         args='-clfrac 0.2 -rbt 18.3 65.0 90.0',
                                         out_file="uni.nii.gz"),
                      name=f'unifize_{pipe_num}')
    skullstrip_second_pass = pe.Node(
        preprocess.Automask(dilate=1, outputtype='NIFTI_GZ'),
        name=f'skullstrip_second_pass_{pipe_num}')
    combine_masks = pe.Node(fsl.BinaryMaths(operation='mul'),
                            name=f'combine_masks_{pipe_num}')
    apply_mask = pe.Node(fsl.ApplyMask(),
                         name=f'extract_ref_brain_bold_{pipe_num}')
    node, out = strat_pool.get_data(["motion-basefile"])
    wf.connect([(node, init_aff, [(out, "moving_image")]),
                (node, map_brainmask, [(out, "reference_image")]),
                (node, norm, [(out, "moving_image")]),
                (init_aff, norm, [
                    ("output_transform", "initial_moving_transform")]),
                (norm, map_brainmask, [
                    ("reverse_invert_flags", "invert_transform_flags"),
                    ("reverse_transforms", "transforms"),
                ]),
                (map_brainmask, binarize_mask, [("output_image", "in_file")]),
                (binarize_mask, pre_dilate, [("out_file", "in_file")]),
                (pre_dilate, print_header, [("out_file", "image")]),
                (print_header, set_direction, [("header", "direction")]),
                (node, set_direction, [(out, "infile"), (out, "outfile")]),
                (set_direction, n4_correct, [("outfile", "mask_image")]),
                (node, n4_correct, [(out, "input_image")]),
                (n4_correct, skullstrip_first_pass,
                 [('output_image', 'in_file')]),
                (skullstrip_first_pass, bet_dilate,
                 [('mask_file', 'in_file')]),
                (bet_dilate, bet_mask, [('out_file', 'mask_file')]),
                (skullstrip_first_pass, bet_mask, [('out_file', 'in_file')]),
                (bet_mask, unifize, [('out_file', 'in_file')]),
                (unifize, skullstrip_second_pass, [('out_file', 'in_file')]),
                (skullstrip_first_pass, combine_masks,
                 [('mask_file', 'in_file')]),
                (skullstrip_second_pass, combine_masks,
                 [('out_file', 'operand_file')]),
                (unifize, apply_mask, [('out_file', 'in_file')]),
                (combine_masks, apply_mask, [('out_file', 'mask_file')]),
                ])
    outputs = {
        'space-bold_desc-brain_mask': (combine_masks, 'out_file'),
        'desc-ref_bold': (apply_mask, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='bold_mask_anatomical_refined',
    switch=[['functional_preproc', 'run'],
            ['functional_preproc', 'func_masking', 'run']],
    option_key=['functional_preproc', 'func_masking', 'using'],
    option_val='Anatomical_Refined',
    inputs=[('bold', 'desc-preproc_bold'),
            ('desc-brain_T1w', ['space-T1w_desc-brain_mask', 'space-T1w_desc-acpcbrain_mask'])],
    outputs=['space-bold_desc-brain_mask']
)
def bold_mask_anatomical_refined(wf, cfg, strat_pool, pipe_num, opt=None):
    # binarize anat mask, in case it is not a binary mask.
    anat_brain_mask_bin = pe.Node(interface=fsl.ImageMaths(),
                                  name=f'anat_brain_mask_bin_{pipe_num}')
    anat_brain_mask_bin.inputs.op_string = '-bin'
    node, out = strat_pool.get_data(['space-T1w_desc-brain_mask',
                                     'space-T1w_desc-acpcbrain_mask'])
    wf.connect(node, out, anat_brain_mask_bin, 'in_file')
    # fill holes of anat mask
    anat_mask_filled = pe.Node(interface=afni.MaskTool(),
                               name=f'anat_brain_mask_filled_{pipe_num}')
    anat_mask_filled.inputs.fill_holes = True
    anat_mask_filled.inputs.outputtype = 'NIFTI_GZ'
    wf.connect(anat_brain_mask_bin, 'out_file',
               anat_mask_filled, 'in_file')
    # init_bold_mask : input raw func
    init_bold_mask = anat_refined_mask(init_bold_mask=True,
                                       wf_name=f'init_bold_mask_{pipe_num}')
    func_deoblique = pe.Node(interface=afni_utils.Refit(),
                             name=f'raw_func_deoblique_{pipe_num}')
    func_deoblique.inputs.deoblique = True
    node, out = strat_pool.get_data('bold')
    wf.connect(node, out, func_deoblique, 'in_file')
    func_reorient = pe.Node(interface=afni_utils.Resample(),
                            name=f'raw_func_reorient_{pipe_num}',
                            mem_gb=0,
                            mem_x=(0.0115, 'in_file', 't'))
    func_reorient.inputs.orientation = 'RPI'
    func_reorient.inputs.outputtype = 'NIFTI_GZ'
    wf.connect(func_deoblique, 'out_file',
               func_reorient, 'in_file')
    wf.connect(func_reorient, 'out_file',
               init_bold_mask, 'inputspec.func')
    wf.connect(anat_mask_filled, 'out_file',
               init_bold_mask, 'inputspec.anatomical_brain_mask')
    node, out = strat_pool.get_data('desc-brain_T1w')
    wf.connect(node, out, init_bold_mask, 'inputspec.anat_brain')
    # dilate init func brain mask
    func_tmp_brain_mask = pe.Node(interface=fsl.ImageMaths(),
                                  name=f'func_tmp_brain_mask_dil_{pipe_num}')
    func_tmp_brain_mask.inputs.op_string = '-dilM'
    wf.connect(init_bold_mask, 'outputspec.func_brain_mask',
               func_tmp_brain_mask, 'in_file')
    # refined_bold_mask : input motion corrected func
    refined_bold_mask = anat_refined_mask(init_bold_mask=False,
                                          wf_name='refined_bold_mask'
                                                  f'_{pipe_num}')
    node, out = strat_pool.get_data(["desc-preproc_bold",
                                     "bold"])
    wf.connect(node, out, refined_bold_mask, 'inputspec.func')
    node, out = strat_pool.get_data('desc-brain_T1w')
    wf.connect(node, out, refined_bold_mask, 'inputspec.anat_brain')
    wf.connect(func_tmp_brain_mask, 'out_file',
               refined_bold_mask, 'inputspec.init_func_brain_mask')
    # dilate anatomical mask
    if cfg.functional_preproc['func_masking']['Anatomical_Refined'][
        'anatomical_mask_dilation']:
        anat_mask_dilate = pe.Node(interface=afni.MaskTool(),
                                   name=f'anat_mask_dilate_{pipe_num}')
        anat_mask_dilate.inputs.dilate_inputs = '1'
        anat_mask_dilate.inputs.outputtype = 'NIFTI_GZ'
        wf.connect(anat_mask_filled, 'out_file',
                   anat_mask_dilate, 'in_file')
        wf.connect(anat_mask_dilate, 'out_file',
                   refined_bold_mask, 'inputspec.anatomical_brain_mask')
    else:
        wf.connect(anat_mask_filled, 'out_file',
                   refined_bold_mask, 'inputspec.anatomical_brain_mask')
    # get final func mask
    func_mask_final = pe.Node(interface=fsl.MultiImageMaths(),
                              name=f'func_mask_final_{pipe_num}')
    func_mask_final.inputs.op_string = "-mul %s"
    wf.connect(func_tmp_brain_mask, 'out_file',
               func_mask_final, 'in_file')
    wf.connect(refined_bold_mask, 'outputspec.func_brain_mask',
               func_mask_final, 'operand_files')
    outputs = {
        'space-bold_desc-brain_mask': (func_mask_final, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='bold_mask_anatomical_based',
    switch=[['functional_preproc', 'run'],
            ['functional_preproc', 'func_masking', 'run']],
    option_key=['functional_preproc', 'func_masking', 'using'],
    option_val='Anatomical_Based',
    inputs=['desc-preproc_bold', ('desc-brain_T1w', ['desc-preproc_T1w', 'desc-reorient_T1w', 'T1w'])],
    outputs=['space-bold_desc-brain_mask']
)
def bold_mask_anatomical_based(wf, cfg, strat_pool, pipe_num, opt=None):
    '''Generate the BOLD mask by basing it off of the anatomical brain mask.
    Adapted from `DCAN Lab's BOLD mask method from the ABCD pipeline <https://github.com/DCAN-Labs/DCAN-HCP/blob/master/fMRIVolume/scripts/DistortionCorrectionAndEPIToT1wReg_FLIRTBBRAndFreeSurferBBRbased.sh>`_.
    '''
    # 0. Take single volume of func
    func_single_volume = pe.Node(interface=afni.Calc(),
                                 name='func_single_volume')
    func_single_volume.inputs.set(
        expr='a',
        single_idx=1,
        outputtype='NIFTI_GZ'
    )
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, func_single_volume, 'in_file_a')
    # 1. Register func head to anat head to get func2anat matrix
    linear_reg_func_to_anat = pe.Node(interface=fsl.FLIRT(),
                                      name='func_to_anat_linear_reg')
    linear_reg_func_to_anat.inputs.dof = 6
    linear_reg_func_to_anat.inputs.interp = 'spline'
    linear_reg_func_to_anat.inputs.searchr_x = [30, 30]
    linear_reg_func_to_anat.inputs.searchr_y = [30, 30]
    linear_reg_func_to_anat.inputs.searchr_z = [30, 30]
    wf.connect(func_single_volume, 'out_file',
               linear_reg_func_to_anat, 'in_file')
    node, out = strat_pool.get_data(["desc-preproc_T1w", "desc-reorient_T1w",
                                     "T1w"])
    wf.connect(node, out, linear_reg_func_to_anat, 'reference')
    # 2. Inverse func to anat affine, to get anat-to-func transform
    inv_func_to_anat_affine = pe.Node(interface=fsl.ConvertXFM(),
                                      name='inv_func2anat_affine')
    inv_func_to_anat_affine.inputs.invert_xfm = True
    wf.connect(linear_reg_func_to_anat, 'out_matrix_file',
               inv_func_to_anat_affine, 'in_file')
    # 3. get BOLD mask
    # 3.1 Apply anat-to-func transform to transfer anatomical brain to functional space
    reg_anat_brain_to_func = pe.Node(interface=fsl.ApplyWarp(),
                                     name='reg_anat_brain_to_func')
    reg_anat_brain_to_func.inputs.interp = 'nn'
    reg_anat_brain_to_func.inputs.relwarp = True
    node, out = strat_pool.get_data("desc-brain_T1w")
    wf.connect(node, out, reg_anat_brain_to_func, 'in_file')
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, reg_anat_brain_to_func, 'ref_file')
    wf.connect(inv_func_to_anat_affine, 'out_file',
               reg_anat_brain_to_func, 'premat')
    # 3.2 Binarize transfered image
    func_mask_bin = pe.Node(interface=fsl.ImageMaths(),
                            name='func_mask_bin')
    func_mask_bin.inputs.op_string = '-abs -bin'
    wf.connect(reg_anat_brain_to_func, 'out_file',
               func_mask_bin, 'in_file')
    # 3.3 Fill holes to get BOLD mask
    func_mask_fill_holes = pe.Node(interface=afni.MaskTool(),
                                   name='func_mask_fill_holes')
    func_mask_fill_holes.inputs.fill_holes = True
    func_mask_fill_holes.inputs.outputtype = 'NIFTI_GZ'
    wf.connect(func_mask_bin, 'out_file',
               func_mask_fill_holes, 'in_file')
    outputs = {
        'space-bold_desc-brain_mask': (func_mask_fill_holes, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='bold_mask_anatomical_resampled',
    switch=[['functional_preproc', 'run'],
            ['functional_preproc', 'func_masking', 'run']],
    option_key=['functional_preproc', 'func_masking', 'using'],
    option_val='Anatomical_Resampled',
    inputs=['desc-preproc_bold', 'T1w-template-funcreg', 'space-template_desc-preproc_T1w',
            'space-template_desc-T1w_mask'],
    outputs=['space-template_res-bold_desc-brain_T1w', 'space-template_desc-bold_mask', 'space-bold_desc-brain_mask']
)
def bold_mask_anatomical_resampled(wf, cfg, strat_pool, pipe_num, opt=None):
    '''Resample anatomical brain mask in standard space to get BOLD brain mask in standard space
    Adapted from `DCAN Lab's BOLD mask method from the ABCD pipeline <https://github.com/DCAN-Labs/DCAN-HCP/blob/master/fMRIVolume/scripts/OneStepResampling.sh#L121-L132>`_.
    '''
    # applywarp --rel --interp=spline -i ${T1wImage} -r ${ResampRefIm} --premat=$FSLDIR/etc/flirtsch/ident.mat -o ${WD}/${T1wImageFile}.${FinalfMRIResolution}
    anat_brain_to_func_res = pe.Node(interface=fsl.ApplyWarp(),
                                     name=f'resample_anat_brain_in_standard_{pipe_num}')
    anat_brain_to_func_res.inputs.interp = 'spline'
    anat_brain_to_func_res.inputs.premat = cfg.registration_workflows[
        'anatomical_registration']['registration']['FSL-FNIRT']['identity_matrix']
    node, out = strat_pool.get_data('space-template_desc-preproc_T1w')
    wf.connect(node, out, anat_brain_to_func_res, 'in_file')
    node, out = strat_pool.get_data('T1w-template-funcreg')
    wf.connect(node, out, anat_brain_to_func_res, 'ref_file')
    # Create brain masks in this space from the FreeSurfer output (changing resolution)
    # applywarp --rel --interp=nn -i ${FreeSurferBrainMask}.nii.gz -r ${WD}/${T1wImageFile}.${FinalfMRIResolution} --premat=$FSLDIR/etc/flirtsch/ident.mat -o ${WD}/${FreeSurferBrainMaskFile}.${FinalfMRIResolution}.nii.gz
    anat_brain_mask_to_func_res = pe.Node(interface=fsl.ApplyWarp(),
                                          name=f'resample_anat_brain_mask_in_standard_{pipe_num}')
    anat_brain_mask_to_func_res.inputs.interp = 'nn'
    anat_brain_mask_to_func_res.inputs.premat = cfg.registration_workflows[
        'anatomical_registration']['registration']['FSL-FNIRT']['identity_matrix']
    node, out = strat_pool.get_data('space-template_desc-T1w_mask')
    wf.connect(node, out, anat_brain_mask_to_func_res, 'in_file')
    wf.connect(anat_brain_to_func_res, 'out_file',
               anat_brain_mask_to_func_res, 'ref_file')
    # Resample func mask in template space back to native space
    func_mask_template_to_native = pe.Node(
        interface=afni.Resample(),
        name=f'resample_func_mask_to_native_{pipe_num}',
        mem_gb=0,
        mem_x=(0.0115, 'in_file', 't'))
    func_mask_template_to_native.inputs.resample_mode = 'NN'
    func_mask_template_to_native.inputs.outputtype = 'NIFTI_GZ'
    wf.connect(anat_brain_mask_to_func_res, 'out_file',
        func_mask_template_to_native, 'in_file')
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, func_mask_template_to_native, 'master')
    outputs = {
        'space-template_res-bold_desc-brain_T1w': (anat_brain_to_func_res, 'out_file'),
        'space-template_desc-bold_mask': (anat_brain_mask_to_func_res, 'out_file'),
        'space-bold_desc-brain_mask': (func_mask_template_to_native, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='bold_mask_ccs',
    switch=[['functional_preproc', 'run'],
            ['functional_preproc', 'func_masking', 'run']],
    option_key=['functional_preproc', 'func_masking', 'using'],
    option_val='CCS_Anatomical_Refined',
    inputs=[['desc-motion_bold', 'desc-preproc_bold', 'bold'], 'desc-brain_T1w',
            ['desc-preproc_T1w', 'desc-reorient_T1w', 'T1w']],
    outputs=['space-bold_desc-brain_mask', 'desc-ref_bold']
)
def bold_mask_ccs(wf, cfg, strat_pool, pipe_num, opt=None):
    '''Generate the BOLD mask by basing it off of the anatomical brain.
    Adapted from `the BOLD mask method from the CCS pipeline <https://github.com/TingsterX/CCS/blob/master/ccs_01_funcpreproc.sh#L89-L110>`_.
    '''
    # Run 3dAutomask to generate func initial mask
    func_tmp_brain_mask = pe.Node(interface=preprocess.Automask(),
                                  name=f'func_tmp_brain_mask_AFNI_{pipe_num}')
    func_tmp_brain_mask.inputs.dilate = 1
    func_tmp_brain_mask.inputs.outputtype = 'NIFTI_GZ'
    node, out = strat_pool.get_data(["desc-motion_bold",
                                     "desc-preproc_bold",
                                     "bold"])
    wf.connect(node, out, func_tmp_brain_mask, 'in_file')
    # Extract 8th volume as func ROI
    func_roi = pe.Node(interface=fsl.ExtractROI(),
                       name=f'extract_func_roi_{pipe_num}')
    func_roi.inputs.t_min = 7
    func_roi.inputs.t_size = 1
    node, out = strat_pool.get_data(["desc-motion_bold",
                                     "desc-preproc_bold",
                                     "bold"])
    wf.connect(node, out, func_roi, 'in_file')
    # Apply func initial mask on func ROI volume
    func_tmp_brain = pe.Node(interface=fsl.maths.ApplyMask(),
                             name=f'get_func_tmp_brain_{pipe_num}')
    wf.connect(func_roi, 'roi_file',
               func_tmp_brain, 'in_file')
    wf.connect(func_tmp_brain_mask, 'out_file',
               func_tmp_brain, 'mask_file')
    # Register func tmp brain to anat brain to get func2anat matrix
    reg_func_to_anat = pe.Node(interface=fsl.FLIRT(),
                               name=f'func_to_anat_linear_reg_{pipe_num}')
    reg_func_to_anat.inputs.interp = 'trilinear'
    reg_func_to_anat.inputs.cost = 'corratio'
    reg_func_to_anat.inputs.dof = 6
    wf.connect(func_tmp_brain, 'out_file',
               reg_func_to_anat, 'in_file')
    node, out = strat_pool.get_data("desc-brain_T1w")
    wf.connect(node, out, reg_func_to_anat, 'reference')
    # Inverse func2anat matrix
    inv_func_to_anat_affine = pe.Node(interface=fsl.ConvertXFM(),
                                      name=f'inv_func2anat_affine_{pipe_num}')
    inv_func_to_anat_affine.inputs.invert_xfm = True
    wf.connect(reg_func_to_anat, 'out_matrix_file',
               inv_func_to_anat_affine, 'in_file')
    # Transform anat brain to func space
    reg_anat_brain_to_func = pe.Node(interface=fsl.FLIRT(),
                                     name=f'reg_anat_brain_to_func_{pipe_num}')
    reg_anat_brain_to_func.inputs.apply_xfm = True
    reg_anat_brain_to_func.inputs.interp = 'trilinear'
    node, out = strat_pool.get_data("desc-brain_T1w")
    wf.connect(node, out, reg_anat_brain_to_func, 'in_file')
    wf.connect(func_roi, 'roi_file',
               reg_anat_brain_to_func, 'reference')
    wf.connect(inv_func_to_anat_affine, 'out_file',
               reg_anat_brain_to_func, 'in_matrix_file')
    # Binarize and dilate anat brain in func space
    bin_anat_brain_in_func = pe.Node(interface=fsl.ImageMaths(),
                                     name=f'bin_anat_brain_in_func_{pipe_num}')
    bin_anat_brain_in_func.inputs.op_string = '-bin -dilM'
    wf.connect(reg_anat_brain_to_func, 'out_file',
               bin_anat_brain_in_func, 'in_file')
    # Binarize detectable func signals
    bin_func = pe.Node(interface=fsl.ImageMaths(),
                                     name=f'bin_func_{pipe_num}')
    bin_func.inputs.op_string = '-Tstd -bin'
    node, out = strat_pool.get_data(["desc-motion_bold",
                                     "desc-preproc_bold",
                                     "bold"])
    wf.connect(node, out, bin_func, 'in_file')
    # Take intersection of masks
    merge_func_mask = pe.Node(util.Merge(2),
                              name=f'merge_func_mask_{pipe_num}')
    wf.connect(func_tmp_brain_mask, 'out_file',
               merge_func_mask, 'in1')
    wf.connect(bin_anat_brain_in_func, 'out_file',
               merge_func_mask, 'in2')
    intersect_mask = pe.Node(interface=fsl.MultiImageMaths(),
                             name=f'intersect_mask_{pipe_num}')
    intersect_mask.inputs.op_string = '-mul %s -mul %s'
    intersect_mask.inputs.output_datatype = 'char'
    wf.connect(bin_func, 'out_file',
               intersect_mask, 'in_file')
    wf.connect(merge_func_mask, 'out',
               intersect_mask, 'operand_files')
    # this is the func input for coreg in ccs
    # TODO evaluate if it's necessary to use this brain
    example_func_brain = pe.Node(interface=fsl.maths.ApplyMask(),
                                 name=f'get_example_func_brain_{pipe_num}')
    wf.connect(func_roi, 'roi_file',
               example_func_brain, 'in_file')
    wf.connect(intersect_mask, 'out_file',
               example_func_brain, 'mask_file')
    outputs = {
        'space-bold_desc-brain_mask': (intersect_mask, 'out_file'),
        'desc-ref_bold': (example_func_brain, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='bold_masking',
    switch=[['functional_preproc', 'run'],
            ['functional_preproc', 'func_masking', 'run'],
            ['functional_preproc', 'func_masking', 'apply_func_mask_in_native_space']],
    inputs=[('desc-preproc_bold', 'space-bold_desc-brain_mask')],
    outputs={'desc-preproc_bold': {'Description': 'The skull-stripped BOLD time-series.', 'SkullStripped': True},
             'desc-brain_bold': {'Description': 'The skull-stripped BOLD time-series.', 'SkullStripped': True}}
)
def bold_masking(wf, cfg, strat_pool, pipe_num, opt=None):
    func_edge_detect = pe.Node(interface=afni_utils.Calc(),
                               name=f'func_extract_brain_{pipe_num}')
    func_edge_detect.inputs.expr = 'a*b'
    func_edge_detect.inputs.outputtype = 'NIFTI_GZ'
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, func_edge_detect, 'in_file_a')
    node, out = strat_pool.get_data("space-bold_desc-brain_mask")
    wf.connect(node, out, func_edge_detect, 'in_file_b')
    outputs = {
        'desc-preproc_bold': (func_edge_detect, 'out_file'),
        'desc-brain_bold': (func_edge_detect, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='func_mean',
    switch=[['functional_preproc', 'run'], ['functional_preproc', 'generate_func_mean', 'run']],
    inputs=['desc-preproc_bold'],
    outputs=['desc-mean_bold']
)
def func_mean(wf, cfg, strat_pool, pipe_num, opt=None):
    func_mean = pe.Node(interface=afni_utils.TStat(),
                        name=f'func_mean_{pipe_num}')
    func_mean.inputs.options = '-mean'
    func_mean.inputs.outputtype = 'NIFTI_GZ'
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, func_mean, 'in_file')
    outputs = {
        'desc-mean_bold': (func_mean, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='func_normalize',
    switch=[['functional_preproc', 'run'], ['functional_preproc', 'normalize_func', 'run']],
    inputs=['desc-preproc_bold'],
    outputs=['desc-preproc_bold']
)
def func_normalize(wf, cfg, strat_pool, pipe_num, opt=None):
    func_normalize = pe.Node(interface=fsl.ImageMaths(),
                             name=f'func_normalize_{pipe_num}',
                             mem_gb=0.7,
                             mem_x=(4538494663498653 /
                                    604462909807314587353088, 'in_file'))
    func_normalize.inputs.op_string = '-ing 10000'
    func_normalize.inputs.out_data_type = 'float'
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, func_normalize, 'in_file')
    outputs = {
        'desc-preproc_bold': (func_normalize, 'out_file')
    }
    return (wf, outputs) 
[docs]@nodeblock(
    name='func_mask_normalize',
    config=['functional_preproc'],
    switch=['run'],
    inputs=[('desc-preproc_bold', 'space-bold_desc-brain_mask')],
    outputs=['space-bold_desc-brain_mask']
)
def func_mask_normalize(wf, cfg, strat_pool, pipe_num, opt=None):
    func_mask_normalize = pe.Node(interface=fsl.ImageMaths(),
                                  name=f'func_mask_normalize_{pipe_num}',
                                  mem_gb=0.7,
                                  mem_x=(4538494663498653 /
                                         604462909807314587353088, 'in_file'))
    func_mask_normalize.inputs.op_string = '-Tmin -bin'
    func_mask_normalize.inputs.out_data_type = 'char'
    node, out = strat_pool.get_data("desc-preproc_bold")
    wf.connect(node, out, func_mask_normalize, 'in_file')
    outputs = {
        'space-bold_desc-brain_mask': (func_mask_normalize, 'out_file')
    }
    return (wf, outputs)