Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Turned `generate_xcpqc_files` on for all preconfigurations except `blank`.
- Introduced specific switch `restore_t1w_intensity` for `correct_restore_brain_intensity_abcd` nodeblock, enabling it by default only in `abcd-options` pre-config.
- Updated GitHub Actions to run automated integration and regression tests on HPC.
- Refactored `bold_mask_anatomical_resampled` nodeblock and related pipeline configs:
- Limited scope to template-space masking only.
- Removed broken support for native-space masking.
- Introduced a new `template_space_func_masking` section in the pipeline config for template-space-only methods.
- Moved `Anatomical_Resampled` masking method from `func_masking` to the `template_space_func_masking`.

### Fixed

Expand Down
21 changes: 16 additions & 5 deletions CPAC/func_preproc/func_motion.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,22 @@ def calc_motion_stats(wf, cfg, strat_pool, pipe_num, opt=None):
gen_motion_stats,
"inputspec.motion_correct",
)
wf.connect(
*strat_pool.get_data("space-bold_desc-brain_mask"),
gen_motion_stats,
"inputspec.mask",
)

if strat_pool.check_rpool("space-bold_desc-brain_mask"):
wf.connect(
*strat_pool.get_data("space-bold_desc-brain_mask"),
gen_motion_stats,
"inputspec.mask",
)
else:
automask = pe.Node(interface=afni.Automask(), name=f"automask_bold_{pipe_num}")
automask.inputs.dilate = 1
automask.inputs.outputtype = "NIFTI_GZ"

node, out = strat_pool.get_data("desc-preproc_bold")
wf.connect(node, out, automask, "in_file")
wf.connect(automask, "out_file", gen_motion_stats, "inputspec.mask")
Comment on lines +97 to +104
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, do we want to put automask.out_file in the resource pool as "space-bold_desc-brain_mask" or do we not care to keep it beyond this one connection?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about this one.
As far as I can tell, its not needed in the abcd-options pipeline as the bold is masked in template space.
I guess it won't hurt to push that into the resource pool anyway. I can check with @sgiavasis in the next meeting.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would lean towards no, but, actually-
Does HCP-ABCD do anything with the BOLD in native space? If so, how do they handle it?
If not - the answer is probably that they just do all of this in template space.

I recommend reviewing their pipeline choices first.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I'm getting at - it may be that we just need to make that jump to more flexible "native vs. template space" toggles for basically any single nodeblock that can interchangeably be done in any space.

The answer is probably "calculate motion stats in template space after masking in template space."
As always, my mandatory mention: the new engine could do this 😅


wf.connect(
*strat_pool.get_data("desc-movementParameters_motion"),
gen_motion_stats,
Expand Down
85 changes: 59 additions & 26 deletions CPAC/func_preproc/func_preproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1592,28 +1592,28 @@ def anat_brain_mask_to_bold_res(wf_name, cfg, pipe_num):
name="bold_mask_anatomical_resampled",
switch=[
["functional_preproc", "run"],
["functional_preproc", "func_masking", "run"],
["functional_preproc", "template_space_func_masking", "run"],
],
option_key=["functional_preproc", "func_masking", "using"],
option_key=["functional_preproc", "template_space_func_masking", "using"],
option_val="Anatomical_Resampled",
inputs=[
"desc-preproc_bold",
"T1w-template-funcreg",
"space-template_desc-preproc_T1w",
"space-template_desc-brain_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 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/1d90814/fMRIVolume/scripts/OneStepResampling.sh#L121-L132>`_.
"""
anat_brain_to_func_res = anat_brain_to_bold_res(wf, cfg, pipe_num)
anat_brain_to_func_res = anat_brain_to_bold_res(
wf_name="anat_brain_to_bold_res", cfg=cfg, pipe_num=pipe_num
)

node, out = strat_pool.get_data("space-template_desc-preproc_T1w")
wf.connect(
Expand Down Expand Up @@ -1644,26 +1644,6 @@ def bold_mask_anatomical_resampled(wf, cfg, strat_pool, pipe_num, opt=None):
"inputspec.space-template_desc-preproc_T1w",
)

# 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,
"outputspec.space-template_desc-bold_mask",
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,
Expand All @@ -1673,7 +1653,6 @@ def bold_mask_anatomical_resampled(wf, cfg, strat_pool, pipe_num, opt=None):
anat_brain_mask_to_func_res,
"outputspec.space-template_desc-bold_mask",
),
"space-bold_desc-brain_mask": (func_mask_template_to_native, "out_file"),
}

return (wf, outputs)
Expand Down Expand Up @@ -1860,6 +1839,60 @@ def bold_masking(wf, cfg, strat_pool, pipe_num, opt=None):
return (wf, outputs)


@nodeblock(
name="template_space_bold_masking",
switch=[
["functional_preproc", "run"],
["functional_preproc", "template_space_func_masking", "run"],
[
"functional_preproc",
"template_space_func_masking",
"apply_func_mask_in_template_space",
],
],
inputs=[("space-template_desc-preproc_bold", "space-template_desc-bold_mask")],
outputs={
"space-template_desc-preproc_bold": {
"Description": "The skull-stripped BOLD time-series.",
"SkullStripped": True,
},
"space-template_desc-brain_bold": {
"Description": "The skull-stripped BOLD time-series.",
"SkullStripped": True,
},
"space-template_desc-head_bold": {
"Description": "The non skull-stripped BOLD time-series.",
"SkullStripped": False,
},
},
)
def template_space_bold_masking(wf, cfg, strat_pool, pipe_num, opt=None):
"""Mask the bold in template space."""
func_edge_detect = pe.Node(
interface=afni_utils.Calc(),
name=f"template_space_func_extract_brain_{pipe_num}",
)

func_edge_detect.inputs.expr = "a*b"
func_edge_detect.inputs.outputtype = "NIFTI_GZ"

node_head_bold, out_head_bold = strat_pool.get_data(
"space-template_desc-preproc_bold"
)
wf.connect(node_head_bold, out_head_bold, func_edge_detect, "in_file_a")

node, out = strat_pool.get_data("space-template_desc-bold_mask")
wf.connect(node, out, func_edge_detect, "in_file_b")

outputs = {
"space-template_desc-preproc_bold": (func_edge_detect, "out_file"),
"space-template_desc-brain_bold": (func_edge_detect, "out_file"),
"space-template_desc-head_bold": (node_head_bold, out_head_bold),
}

return (wf, outputs)


@nodeblock(
name="func_mean",
switch=[
Expand Down
11 changes: 10 additions & 1 deletion CPAC/pipeline/cpac_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
bold_mask_fsl,
bold_mask_fsl_afni,
bold_masking,
template_space_bold_masking,
func_despike,
func_despike_template,
func_mean,
Expand Down Expand Up @@ -1275,7 +1276,6 @@ def build_workflow(subject_id, sub_dict, cfg, pipeline_name=None):
bold_mask_fsl_afni,
bold_mask_anatomical_refined,
bold_mask_anatomical_based,
bold_mask_anatomical_resampled,
bold_mask_ccs,
],
bold_masking,
Expand Down Expand Up @@ -1513,6 +1513,15 @@ def build_workflow(subject_id, sub_dict, cfg, pipeline_name=None):
warp_deriv_mask_to_EPItemplate,
]

# Template space functional masking
if cfg.functional_preproc["template_space_func_masking"]["run"]:
if not rpool.check_rpool("space-template_desc-bold_mask"):
pipeline_blocks += (bold_mask_anatomical_resampled,)
if cfg.functional_preproc["template_space_func_masking"][
"apply_func_mask_in_template_space"
]:
pipeline_blocks += (template_space_bold_masking,)

# Template-space nuisance regression
nuisance_template = (
cfg["nuisance_corrections", "2-nuisance_regression", "space"] == "template"
Expand Down
6 changes: 5 additions & 1 deletion CPAC/pipeline/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,6 @@ def sanitize(filename):
"FSL_AFNI",
"Anatomical_Refined",
"Anatomical_Based",
"Anatomical_Resampled",
"CCS_Anatomical_Refined",
]
)
Expand Down Expand Up @@ -1011,6 +1010,11 @@ def sanitize(filename):
},
"apply_func_mask_in_native_space": bool1_1,
},
"template_space_func_masking": {
"run": bool1_1,
"using": [In({"Anatomical_Resampled"})],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why even have using if there's only one option?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an attempt to keep it open if we have another method in future, that we may add here.
But, I am open to just make it for "Anatomical_Resampled".

"apply_func_mask_in_template_space": bool1_1,
},
"generate_func_mean": {
"run": bool1_1,
},
Expand Down
31 changes: 17 additions & 14 deletions CPAC/resources/configs/pipeline_config_abcd-options.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,17 @@ registration_workflows:
# input: ['Mean_Functional', 'Selected_Functional_Volume', 'fmriprep_reference']
input: [Selected_Functional_Volume]

# Mask the sbref created by coregistration input prep nodeblocks above before registration
mask_sbref: Off

# Choose coregistration interpolation
interpolation: spline

# Choose coregistration degree of freedom
dof: 12



func_registration_to_template:

# these options modify the application (to the functional data), not the calculation, of the
Expand Down Expand Up @@ -290,20 +295,15 @@ functional_preproc:
using: [PhaseDiff, Blip-FSL-TOPUP]

func_masking:
run: On

# Apply functional mask in native space
apply_func_mask_in_native_space: Off
run: Off

# using: ['AFNI', 'FSL', 'FSL_AFNI', 'Anatomical_Refined', 'Anatomical_Based', 'Anatomical_Resampled', 'CCS_Anatomical_Refined']
# FSL_AFNI: fMRIPrep-style BOLD mask. Ref: https://github.com/nipreps/niworkflows/blob/a221f612/niworkflows/func/util.py#L246-L514
# Anatomical_Refined: 1. binarize anat mask, in case it is not a binary mask. 2. fill holes of anat mask 3. init_bold_mask : input raw func → dilate init func brain mask 4. refined_bold_mask : input motion corrected func → dilate anatomical mask 5. get final func mask
# Anatomical_Based: 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.
# Anatomical_Resampled: 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. ("Create fMRI resolution standard space files for T1w image, wmparc, and brain mask […] don't use FLIRT to do spline interpolation with -applyisoxfm for the 2mm and 1mm cases because it doesn't know the peculiarities of the MNI template FOVs")
# CCS_Anatomical_Refined: Generate the BOLD mask by basing it off of the anatomical brain. Adapted from the BOLD mask method from the CCS pipeline.
# this is a fork point
template_space_func_masking:
run: On
# Anatomical_Resampled: 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. ("Create fMRI resolution standard space files for T1w image, wmparc, and brain mask […] don't use FLIRT to do spline interpolation with -applyisoxfm for the 2mm and 1mm cases because it doesn't know the peculiarities of the MNI template FOVs")
using: [Anatomical_Resampled]

apply_func_mask_in_template_space: On

generate_func_mean:

# Generate mean functional image
Expand All @@ -316,7 +316,7 @@ functional_preproc:

nuisance_corrections:
2-nuisance_regression:

run: Off
# Select which nuisance signal corrections to apply
Regressors:
- Name: default
Expand All @@ -329,6 +329,9 @@ nuisance_corrections:
include_delayed_squared: On
include_squared: On

# switch to Off if nuisance regression is off and you don't want to write out the regressors
create_regressors: Off

# Process and refine masks used to produce regressors and time series for
# regression.
regressor_masks:
Expand Down Expand Up @@ -385,10 +388,10 @@ regional_homogeneity:
# -----------------------
post_processing:
spatial_smoothing:
run: On
run: Off

z-scoring:
run: On
run: Off

seed_based_correlation_analysis:

Expand Down
16 changes: 6 additions & 10 deletions CPAC/resources/configs/pipeline_config_abcd-prep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,15 +226,11 @@ functional_preproc:
using: []

func_masking:
run: Off

# Apply functional mask in native space
apply_func_mask_in_native_space: Off

# using: ['AFNI', 'FSL', 'FSL_AFNI', 'Anatomical_Refined', 'Anatomical_Based', 'Anatomical_Resampled', 'CCS_Anatomical_Refined']
# FSL_AFNI: fMRIPrep-style BOLD mask. Ref: https://github.com/nipreps/niworkflows/blob/a221f612/niworkflows/func/util.py#L246-L514
# Anatomical_Refined: 1. binarize anat mask, in case it is not a binary mask. 2. fill holes of anat mask 3. init_bold_mask : input raw func → dilate init func brain mask 4. refined_bold_mask : input motion corrected func → dilate anatomical mask 5. get final func mask
# Anatomical_Based: 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.
# Anatomical_Resampled: 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. ("Create fMRI resolution standard space files for T1w image, wmparc, and brain mask […] don't use FLIRT to do spline interpolation with -applyisoxfm for the 2mm and 1mm cases because it doesn't know the peculiarities of the MNI template FOVs")
# CCS_Anatomical_Refined: Generate the BOLD mask by basing it off of the anatomical brain. Adapted from the BOLD mask method from the CCS pipeline.
# this is a fork point
template_space_func_masking:
run: On
# Anatomical_Resampled: 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. ("Create fMRI resolution standard space files for T1w image, wmparc, and brain mask […] don't use FLIRT to do spline interpolation with -applyisoxfm for the 2mm and 1mm cases because it doesn't know the peculiarities of the MNI template FOVs")
using: [Anatomical_Resampled]

apply_func_mask_in_template_space: On
10 changes: 8 additions & 2 deletions CPAC/resources/configs/pipeline_config_blank.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1139,11 +1139,10 @@ functional_preproc:
# Apply functional mask in native space
apply_func_mask_in_native_space: On

# using: ['AFNI', 'FSL', 'FSL_AFNI', 'Anatomical_Refined', 'Anatomical_Based', 'Anatomical_Resampled', 'CCS_Anatomical_Refined']
# using: ['AFNI', 'FSL', 'FSL_AFNI', 'Anatomical_Refined', 'Anatomical_Based', 'CCS_Anatomical_Refined']
# FSL_AFNI: fMRIPrep-style BOLD mask. Ref: https://github.com/nipreps/niworkflows/blob/a221f612/niworkflows/func/util.py#L246-L514
# Anatomical_Refined: 1. binarize anat mask, in case it is not a binary mask. 2. fill holes of anat mask 3. init_bold_mask : input raw func → dilate init func brain mask 4. refined_bold_mask : input motion corrected func → dilate anatomical mask 5. get final func mask
# Anatomical_Based: 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.
# Anatomical_Resampled: 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. ("Create fMRI resolution standard space files for T1w image, wmparc, and brain mask […] don't use FLIRT to do spline interpolation with -applyisoxfm for the 2mm and 1mm cases because it doesn't know the peculiarities of the MNI template FOVs")
# CCS_Anatomical_Refined: Generate the BOLD mask by basing it off of the anatomical brain. Adapted from the BOLD mask method from the CCS pipeline.
# this is a fork point
using: [AFNI]
Expand All @@ -1152,6 +1151,13 @@ functional_preproc:
# Choose whether or not to dilate the anatomical mask if you choose 'Anatomical_Refined' as the functional masking option. It will dilate one voxel if enabled.
anatomical_mask_dilation: Off

template_space_func_masking:
run: Off
# Anatomical_Resampled: 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. ("Create fMRI resolution standard space files for T1w image, wmparc, and brain mask […] don't use FLIRT to do spline interpolation with -applyisoxfm for the 2mm and 1mm cases because it doesn't know the peculiarities of the MNI template FOVs")
using: [Anatomical_Resampled]

apply_func_mask_in_template_space: Off

generate_func_mean:

# Generate mean functional image
Expand Down
3 changes: 1 addition & 2 deletions CPAC/resources/configs/pipeline_config_ccs-options.yml
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,10 @@ functional_preproc:
func_masking:
run: On

# using: ['AFNI', 'FSL', 'FSL_AFNI', 'Anatomical_Refined', 'Anatomical_Based', 'Anatomical_Resampled', 'CCS_Anatomical_Refined']
# using: ['AFNI', 'FSL', 'FSL_AFNI', 'Anatomical_Refined', 'Anatomical_Based', 'CCS_Anatomical_Refined']
# FSL_AFNI: fMRIPrep-style BOLD mask. Ref: https://github.com/nipreps/niworkflows/blob/a221f612/niworkflows/func/util.py#L246-L514
# Anatomical_Refined: 1. binarize anat mask, in case it is not a binary mask. 2. fill holes of anat mask 3. init_bold_mask : input raw func → dilate init func brain mask 4. refined_bold_mask : input motion corrected func → dilate anatomical mask 5. get final func mask
# Anatomical_Based: 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.
# Anatomical_Resampled: 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. ("Create fMRI resolution standard space files for T1w image, wmparc, and brain mask […] don't use FLIRT to do spline interpolation with -applyisoxfm for the 2mm and 1mm cases because it doesn't know the peculiarities of the MNI template FOVs")
# CCS_Anatomical_Refined: Generate the BOLD mask by basing it off of the anatomical brain. Adapted from the BOLD mask method from the CCS pipeline.
# this is a fork point
using: [CCS_Anatomical_Refined]
Expand Down
Loading