Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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.

### Changed

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

Check warning on line 1306 in CPAC/anat_preproc/anat_preproc.py

View check run for this annotation

Codecov / codecov/patch

CPAC/anat_preproc/anat_preproc.py#L1306

Added line #L1306 was not covered by tests

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 @@

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 @@
"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 @@
"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"