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