Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Switch `sink_native_transforms` under `registration_workflows` to output all `.mat` files in ANTs and FSL Transforms.
- `deoblique` field in pipeline config with `warp` and `refit` options to apply `3dWarp` or `3drefit` during data initialization.
- `organism` configuration option.
- Functionality to convert `space-T1w_desc-loose_brain_mask` and `space-T1w_desc-tight_brain_mask` into generic brain mask `space-T1w_desc-brain_mask` to use in brain extraction nodeblock downstream.
- `desc-ABCDpreproc_T1w` to the outputs
- `bc` to `lite` container images.

Expand Down
52 changes: 27 additions & 25 deletions CPAC/anat_preproc/anat_preproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1303,7 +1303,7 @@ def freesurfer_fsl_brain_connector(wf, cfg, strat_pool, pipe_num, opt):
# fslmaths tmp_mask.nii.gz -mas ${CCSDIR}/templates/MNI152_T1_1mm_first_brain_mask.nii.gz tmp_mask.nii.gz
apply_mask = pe.Node(interface=fsl.maths.ApplyMask(), name=f"apply_mask_{node_id}")

wf.connect(skullstrip, "out_file", apply_mask, "in_file")
wf.connect(skullstrip, "mask_file", apply_mask, "in_file")

node, out = strat_pool.get_data("T1w-brain-template-mask-ccs")
wf.connect(node, out, apply_mask, "mask_file")
Expand Down Expand Up @@ -1347,36 +1347,18 @@ def freesurfer_fsl_brain_connector(wf, cfg, strat_pool, pipe_num, opt):

wf.connect(combine_mask, "out_file", binarize_combined_mask, "in_file")

# CCS brain mask is in FS space, transfer it back to native T1 space
fs_fsl_brain_mask_to_native = pe.Node(
interface=freesurfer.ApplyVolTransform(),
name=f"fs_fsl_brain_mask_to_native_{node_id}",
)
fs_fsl_brain_mask_to_native.inputs.reg_header = True
fs_fsl_brain_mask_to_native.inputs.interp = "nearest"

wf.connect(
binarize_combined_mask, "out_file", fs_fsl_brain_mask_to_native, "source_file"
)

node, out = strat_pool.get_data("pipeline-fs_raw-average")
wf.connect(node, out, fs_fsl_brain_mask_to_native, "target_file")

node, out = strat_pool.get_data("freesurfer-subject-dir")
wf.connect(node, out, fs_fsl_brain_mask_to_native, "subjects_dir")

if opt == "FreeSurfer-BET-Tight":
outputs = {
"space-T1w_desc-tight_brain_mask": (
fs_fsl_brain_mask_to_native,
"transformed_file",
binarize_combined_mask,
"out_file",
)
}
elif opt == "FreeSurfer-BET-Loose":
outputs = {
"space-T1w_desc-loose_brain_mask": (
fs_fsl_brain_mask_to_native,
"transformed_file",
binarize_combined_mask,
"out_file",
)
}

Expand Down Expand Up @@ -2058,11 +2040,21 @@ def brain_mask_freesurfer_abcd(wf, cfg, strat_pool, pipe_num, opt=None):
"T1w-brain-template-mask-ccs",
"T1w-ACPC-template",
],
outputs=["space-T1w_desc-tight_brain_mask"],
outputs={
"space-T1w_desc-brain_mask": {
"Description": "Brain mask extracted using FreeSurfer-BET-Tight method",
"Method": "FreeSurfer-BET-Tight",
"Threshold": "tight",
}
},
)
def brain_mask_freesurfer_fsl_tight(wf, cfg, strat_pool, pipe_num, opt=None):
wf, outputs = freesurfer_fsl_brain_connector(wf, cfg, strat_pool, pipe_num, opt)

# Convert the tight brain mask to generic brain mask
outputs["space-T1w_desc-brain_mask"] = outputs.pop(
"space-T1w_desc-tight_brain_mask"
)
return (wf, outputs)


Expand Down Expand Up @@ -2107,11 +2099,21 @@ def brain_mask_acpc_freesurfer_abcd(wf, cfg, strat_pool, pipe_num, opt=None):
"T1w-brain-template-mask-ccs",
"T1w-ACPC-template",
],
outputs=["space-T1w_desc-loose_brain_mask"],
outputs={
"space-T1w_desc-brain_mask": {
"Description": "Brain mask extracted using FreeSurfer-BET-Loose method",
"Method": "FreeSurfer-BET-Loose",
"Threshold": "loose",
}
},
)
def brain_mask_freesurfer_fsl_loose(wf, cfg, strat_pool, pipe_num, opt=None):
wf, outputs = freesurfer_fsl_brain_connector(wf, cfg, strat_pool, pipe_num, opt)

# Convert the loose brain mask to generic brain mask
outputs["space-T1w_desc-brain_mask"] = outputs.pop(
"space-T1w_desc-loose_brain_mask"
)
return (wf, outputs)


Expand Down
73 changes: 73 additions & 0 deletions CPAC/anat_preproc/tests/test_anat_preproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
import nibabel as nib

from .. import anat_preproc
from unittest.mock import Mock, patch
from ..anat_preproc import (
brain_mask_freesurfer_fsl_loose,
brain_mask_freesurfer_fsl_tight,
)


class TestAnatPreproc:
Expand Down Expand Up @@ -269,3 +274,71 @@ def test_anat_brain(self):
# print 'correlation: ', correlation

assert correlation[0, 1] >= 0.97


@patch("CPAC.anat_preproc.anat_preproc.freesurfer_fsl_brain_connector")
def test_brain_mask_freesurfer_fsl_loose(mock_connector):
"""Test that brain_mask_freesurfer_fsl_loose correctly renames output key."""

mock_wf = Mock()
mock_cfg = Mock()
mock_strat_pool = Mock()
pipe_num = 1

mock_outputs = {
"space-T1w_desc-loose_brain_mask": "brain_mask_data",
"other_output": "other_data",
}

mock_connector.return_value = (mock_wf, mock_outputs)

result_wf, result_outputs = brain_mask_freesurfer_fsl_loose(
mock_wf, mock_cfg, mock_strat_pool, pipe_num
)

mock_connector.assert_called_once_with(
mock_wf, mock_cfg, mock_strat_pool, pipe_num, None
)

# Assert workflow returned unchanged
assert result_wf == mock_wf

# Assert output key was renamed correctly
assert "space-T1w_desc-brain_mask" in result_outputs
assert "space-T1w_desc-loose_brain_mask" not in result_outputs
assert result_outputs["space-T1w_desc-brain_mask"] == "brain_mask_data"
assert result_outputs["other_output"] == "other_data"


@patch("CPAC.anat_preproc.anat_preproc.freesurfer_fsl_brain_connector")
def test_brain_mask_freesurfer_fsl_tight(mock_connector):
"""Test that brain_mask_freesurfer_fsl_tight correctly renames output key."""

mock_wf = Mock()
mock_cfg = Mock()
mock_strat_pool = Mock()
pipe_num = 1

mock_outputs = {
"space-T1w_desc-tight_brain_mask": "brain_mask_data",
"other_output": "other_data",
}

mock_connector.return_value = (mock_wf, mock_outputs)

result_wf, result_outputs = brain_mask_freesurfer_fsl_tight(
mock_wf, mock_cfg, mock_strat_pool, pipe_num
)

mock_connector.assert_called_once_with(
mock_wf, mock_cfg, mock_strat_pool, pipe_num, None
)

# Assert workflow returned unchanged
assert result_wf == mock_wf

# Assert output key was renamed correctly
assert "space-T1w_desc-brain_mask" in result_outputs
assert "space-T1w_desc-tight_brain_mask" not in result_outputs
assert result_outputs["space-T1w_desc-brain_mask"] == "brain_mask_data"
assert result_outputs["other_output"] == "other_data"