Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
14f2da6
:necktie: Make XCPQC inputs optional
shnizzedy Jul 29, 2025
36e7562
:necktie: Group `ICA_AROMA_FSLreg` inputs
shnizzedy Jul 30, 2025
970afa5
:rewind: Revert ":necktie: Group `ICA_AROMA_FSLreg` inputs"
shnizzedy Jul 30, 2025
22df8c3
:loud_sound: Add `new_strats` to verbose logger
shnizzedy Aug 6, 2025
44b0f4b
:loud_sound: Include name when debugging strats
shnizzedy Aug 7, 2025
33bb943
:recycle: Un=daisy-chain registration options
shnizzedy Aug 7, 2025
1d89c09
:truck: `desc-ref_bold` → `sbref`
shnizzedy Aug 8, 2025
5d9dafc
:goal_net: Try to short-circuit crossed streams
shnizzedy Aug 8, 2025
1b6bd2e
:necktie: Move short-circuit
shnizzedy Aug 9, 2025
2bff2a5
:white_check_mark: Update `brain_mask_freesurfer_fsl` tests
shnizzedy Aug 11, 2025
33756d2
:loud_sound: Improve dropped variant logging
shnizzedy Aug 13, 2025
5376cf3
:recycle: Reduce initial number of connections; fix short-circuit
shnizzedy Aug 26, 2025
1a4c8d9
:recycle: Restore consecutive forking ability
shnizzedy Aug 29, 2025
cc5648e
:rewind: Revert ":recycle: Restore consecutive forking ability"
shnizzedy Sep 2, 2025
9c551be
:necktie: Stringify variants to handle chains
shnizzedy Sep 2, 2025
bac897f
:twisted_rightwards_arrows: Merge branch 'develop' into 'decrease-fra…
shnizzedy Sep 11, 2025
5a48533
adding match fov node to convert fs brain mask to native space
birajstha Sep 11, 2025
1df0639
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 11, 2025
5196fe4
bringing the FS brain mask to T1 space before anat skull stripping (#…
shnizzedy Sep 12, 2025
dcf6063
:twisted_rightwards_arrows: Merge branch 'develop' into 'decrease-fra…
shnizzedy Sep 15, 2025
ee00500
Merge branch 'develop' into decrease-fragility/xcpqc
shnizzedy Sep 18, 2025
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
308 changes: 103 additions & 205 deletions CPAC/anat_preproc/anat_preproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
fsl_aff_to_rigid,
fslmaths_command,
mri_convert,
normalize_wmparc,
pad,
VolumeRemoveIslands,
wb_command,
Expand Down Expand Up @@ -696,13 +695,15 @@ def afni_brain_connector(wf, cfg, strat_pool, pipe_num, opt):

wf.connect(anat_skullstrip, "out_file", anat_brain_mask, "in_file_a")

outputs = {}

if strat_pool.check_rpool("desc-preproc_T1w"):
outputs = {"space-T1w_desc-brain_mask": (anat_brain_mask, "out_file")}

elif strat_pool.check_rpool("desc-preproc_T2w"):
outputs = {"space-T2w_desc-brain_mask": (anat_brain_mask, "out_file")}

return (wf, outputs)
return wf, outputs


def fsl_brain_connector(wf, cfg, strat_pool, pipe_num, opt):
Expand Down Expand Up @@ -1322,22 +1323,29 @@ def freesurfer_fsl_brain_connector(wf, cfg, strat_pool, pipe_num, opt):

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

if opt == "FreeSurfer-BET-Tight":
outputs = {
"space-T1w_desc-tight_brain_mask": (
binarize_combined_mask,
"out_file",
)
}
elif opt == "FreeSurfer-BET-Loose":
outputs = {
"space-T1w_desc-loose_brain_mask": (
binarize_combined_mask,
"out_file",
)
}
# CCS brain mask is in FS space, transfer it back to native T1 space
match_fov_ccs_brain_mask = pe.Node(
interface=fsl.FLIRT(), name=f"match_fov_CCS_brain_mask_{node_id}"
)
match_fov_ccs_brain_mask.inputs.apply_xfm = True
match_fov_ccs_brain_mask.inputs.uses_qform = True
match_fov_ccs_brain_mask.inputs.interp = "nearestneighbour"

return (wf, outputs)
node, out = strat_pool.get_data("pipeline-fs_raw-average")
convert_fs_T1_to_nifti = pe.Node(
Function(
input_names=["in_file"], output_names=["out_file"], function=mri_convert
),
name=f"convert_fs_T1_to_nifti_for_ccs_{node_id}",
)
wf.connect(node, out, convert_fs_T1_to_nifti, "in_file")
wf.connect(
convert_fs_T1_to_nifti, "out_file", match_fov_ccs_brain_mask, "reference"
)

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

return wf, {"space-T1w_desc-brain_mask": (match_fov_ccs_brain_mask, "out_file")}


def mask_T2(wf_name="mask_T2"):
Expand Down Expand Up @@ -1408,9 +1416,8 @@ def mask_T2(wf_name="mask_T2"):
)
def anatomical_init(wf, cfg, strat_pool, pipe_num, opt=None):
if opt not in anatomical_init.option_val:
raise ValueError(
f"\n[!] Error: Invalid option for deoblique: {opt}. \nExpected one of {anatomical_init.option_val}"
)
msg = f"\n[!] Error: Invalid option for deoblique: {opt}. \nExpected one of {anatomical_init.option_val}"
raise ValueError(msg)

if opt == "warp":
anat_deoblique = pe.Node(
Expand Down Expand Up @@ -1500,15 +1507,15 @@ def acpc_align_head(wf, cfg, strat_pool, pipe_num, opt=None):
(
"desc-head_T1w",
"desc-preproc_T1w",
["space-T1w_desc-brain_mask", "space-T1w_desc-brain_mask"],
"space-T1w_desc-brain_mask",
),
"T1w-ACPC-template",
"T1w-brain-ACPC-template",
],
outputs=[
"desc-head_T1w",
"desc-preproc_T1w",
["space-T1w_desc-brain_mask", "space-T1w_desc-brain_mask"],
"space-T1w_desc-brain_mask",
"from-T1w_to-ACPC_mode-image_desc-aff2rig_xfm",
],
)
Expand Down Expand Up @@ -1939,211 +1946,102 @@ def brain_mask_acpc_unet(wf, cfg, strat_pool, pipe_num, opt=None):
["anatomical_preproc", "run"],
],
option_key=["anatomical_preproc", "brain_extraction", "using"],
option_val="FreeSurfer-Brainmask",
inputs=[
"pipeline-fs_raw-average",
"pipeline-fs_brainmask",
"freesurfer-subject-dir",
option_val=[
"FreeSurfer-ABCD",
"FreeSurfer-BET-Loose",
"FreeSurfer-BET-Tight",
"FreeSurfer-Brainmask",
],
outputs=["space-T1w_desc-brain_mask"],
)
def brain_mask_freesurfer(wf, cfg, strat_pool, pipe_num, opt=None):
wf, outputs = freesurfer_brain_connector(wf, cfg, strat_pool, pipe_num, opt)

return (wf, outputs)


@nodeblock(
name="brain_mask_acpc_freesurfer",
switch=[
["anatomical_preproc", "brain_extraction", "run"],
["anatomical_preproc", "run"],
],
option_key=["anatomical_preproc", "brain_extraction", "using"],
option_val="FreeSurfer-Brainmask",
inputs=[
"space-T1w_desc-brain_mask",
"pipeline-fs_raw-average",
"freesurfer-subject-dir",
],
outputs=["space-T1w_desc-acpcbrain_mask"],
)
def brain_mask_acpc_freesurfer(wf, cfg, strat_pool, pipe_num, opt=None):
wf, wf_outputs = freesurfer_brain_connector(wf, cfg, strat_pool, pipe_num, opt)

outputs = {"space-T1w_desc-acpcbrain_mask": wf_outputs["space-T1w_desc-brain_mask"]}

return (wf, outputs)


@nodeblock(
name="brain_mask_freesurfer_abcd",
switch=[
["anatomical_preproc", "brain_extraction", "run"],
["anatomical_preproc", "run"],
],
option_key=["anatomical_preproc", "brain_extraction", "using"],
option_val="FreeSurfer-ABCD",
inputs=[
["desc-restore_T1w", "desc-preproc_T1w"],
"pipeline-fs_wmparc",
"pipeline-fs_raw-average",
"freesurfer-subject-dir",
],
outputs=["space-T1w_desc-brain_mask"],
)
def brain_mask_freesurfer_abcd(wf, cfg, strat_pool, pipe_num, opt=None):
wf, outputs = freesurfer_abcd_brain_connector(wf, cfg, strat_pool, pipe_num, opt)

return (wf, outputs)


@nodeblock(
name="brain_mask_freesurfer_fsl_tight",
switch=[
["anatomical_preproc", "brain_extraction", "run"],
["anatomical_preproc", "run"],
],
option_key=["anatomical_preproc", "brain_extraction", "using"],
option_val="FreeSurfer-BET-Tight",
inputs=[
"pipeline-fs_brainmask",
"pipeline-fs_T1",
"pipeline-fs_raw-average",
"freesurfer-subject-dir",
(
["desc-restore_T1w", "desc-preproc_T1w"],
"space-T1w_desc-brain_mask",
"pipeline-fs_T1",
"pipeline-fs_wmparc",
"pipeline-fs_raw-average",
"pipeline-fs_brainmask",
"freesurfer-subject-dir",
),
"T1w-brain-template-mask-ccs",
"T1w-ACPC-template",
],
outputs={
"space-T1w_desc-brain_mask": {
"Description": "Brain mask extracted using FreeSurfer-BET-Tight method",
"Method": "FreeSurfer-BET-Tight",
"Threshold": "tight",
}
},
outputs={"space-T1w_desc-brain_mask": {}},
)
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)
def brain_mask_freesurfer(wf, cfg, strat_pool, pipe_num, opt=None):
assert isinstance(brain_mask_freesurfer.outputs, dict)
brain_mask_freesurfer.outputs["space-T1w_desc-brain_mask"] = {
"Description": f"Brain mask extracted using {opt} method",
"Method": opt,
}
match opt:
case "FreeSurfer-ABCD":
return freesurfer_abcd_brain_connector(wf, cfg, strat_pool, pipe_num, opt)
case "FreeSurfer-BET-Loose" | "FreeSurfer-BET-Tight":
brain_mask_freesurfer.outputs["space-T1w_desc-brain_mask"]["Threshold"] = (
opt.rsplit("-")[-1].lower()
)
return freesurfer_fsl_brain_connector(wf, cfg, strat_pool, pipe_num, opt)
case "FreeSurfer-Brainmask":
return freesurfer_brain_connector(wf, cfg, strat_pool, pipe_num, opt)
return wf, {}


@nodeblock(
name="brain_mask_acpc_freesurfer_abcd",
name="brain_mask_acpc_freesurfer",
switch=[
["anatomical_preproc", "brain_extraction", "run"],
["anatomical_preproc", "run"],
],
option_key=["anatomical_preproc", "brain_extraction", "using"],
option_val="FreeSurfer-ABCD",
inputs=[
["desc-restore_T1w", "desc-preproc_T1w"],
"pipeline-fs_wmparc",
"pipeline-fs_raw-average",
"freesurfer-subject-dir",
],
outputs=["space-T1w_desc-acpcbrain_mask"],
)
def brain_mask_acpc_freesurfer_abcd(wf, cfg, strat_pool, pipe_num, opt=None):
wf, wf_outputs = freesurfer_abcd_brain_connector(wf, cfg, strat_pool, pipe_num, opt)

outputs = {"space-T1w_desc-acpcbrain_mask": wf_outputs["space-T1w_desc-brain_mask"]}

return (wf, outputs)


@nodeblock(
name="brain_mask_freesurfer_fsl_loose",
switch=[
["anatomical_preproc", "brain_extraction", "run"],
["anatomical_preproc", "run"],
option_val=[
"FreeSurfer-ABCD",
"FreeSurfer-Brainmask",
"FreeSurfer-BET-Loose",
"FreeSurfer-BET-Tight",
],
option_key=["anatomical_preproc", "brain_extraction", "using"],
option_val="FreeSurfer-BET-Loose",
inputs=[
"pipeline-fs_brainmask",
"pipeline-fs_T1",
"pipeline-fs_raw-average",
"freesurfer-subject-dir",
"T1w-brain-template-mask-ccs",
"T1w-ACPC-template",
],
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)


@nodeblock(
name="brain_mask_acpc_freesurfer_fsl_tight",
switch=[
["anatomical_preproc", "brain_extraction", "run"],
["anatomical_preproc", "run"],
],
option_key=["anatomical_preproc", "brain_extraction", "using"],
option_val="FreeSurfer-BET-Tight",
inputs=[
"pipeline-fs_brainmask",
"pipeline-fs_T1",
"T1w-brain-template-mask-ccs",
(
["desc-restore_T1w", "desc-preproc_T1w"],
"space-T1w_desc-brain_mask",
"space-T1w_desc-acpcbrain_mask",
"pipeline-fs_brainmask",
"pipeline-fs_raw-average",
"pipeline-fs_T1",
"pipeline-fs_wmparc",
"freesurfer-subject-dir",
),
"T1w-ACPC-template",
],
outputs=["space-T1w_desc-tight_acpcbrain_mask"],
)
def brain_mask_acpc_freesurfer_fsl_tight(wf, cfg, strat_pool, pipe_num, opt=None):
wf, wf_outputs = freesurfer_fsl_brain_connector(wf, cfg, strat_pool, pipe_num, opt)

outputs = {
"space-T1w_desc-tight_acpcbrain_mask": wf_outputs[
"space-T1w_desc-tight_brain_mask"
]
}

return (wf, outputs)


@nodeblock(
name="brain_mask_acpc_freesurfer_fsl_loose",
switch=[
["anatomical_preproc", "brain_extraction", "run"],
["anatomical_preproc", "run"],
],
option_key=["anatomical_preproc", "brain_extraction", "using"],
option_val="FreeSurfer-BET-Loose",
inputs=[
"pipeline-fs_brainmask",
"pipeline-fs_T1",
"T1w-brain-template-mask-ccs",
"T1w-ACPC-template",
],
outputs=["space-T1w_desc-loose_acpcbrain_mask"],
outputs={"space-T1w_desc-acpcbrain_mask": {}},
)
def brain_mask_acpc_freesurfer_fsl_loose(wf, cfg, strat_pool, pipe_num, opt=None):
wf, wf_outputs = freesurfer_fsl_brain_connector(wf, cfg, strat_pool, pipe_num, opt)

outputs = {
"space-T1w_desc-loose_acpcbrain_mask": wf_outputs[
"space-T1w_desc-loose_brain_mask"
]
def brain_mask_acpc_freesurfer(wf, cfg, strat_pool, pipe_num, opt=None):
if opt != strat_pool.get_json("space-T1w_desc-brain_mask").get(
"CpacVariant", {}
).get("space-T1w_mask", opt):
# https://tenor.com/baIhQ.gif
return wf, {}
assert isinstance(brain_mask_acpc_freesurfer.outputs, dict)
outputs = wf_outputs = {}
key = "space-T1w_desc-brain_mask"
functions = {
"FreeSurfer-ABCD": freesurfer_abcd_brain_connector,
"FreeSurfer-Brainmask": freesurfer_brain_connector,
"FreeSurfer-BET-Loose": freesurfer_fsl_brain_connector,
"FreeSurfer-BET-Tight": freesurfer_fsl_brain_connector,
}
if opt in ["FreeSurfer-BET-Loose", "FreeSurfer-BET-Tight"]:
brain_mask_acpc_freesurfer.outputs["space-T1w_desc-acpcbrain_mask"] = {
"Description": f"Brain mask extracted using {opt} method",
"Method": opt,
"Threshold": opt.rsplit("-")[-1].lower(),
}
if opt in functions:
wf, wf_outputs = functions[opt](wf, cfg, strat_pool, pipe_num, opt)
if key in wf_outputs:
outputs = {"space-T1w_desc-acpcbrain_mask": wf_outputs[key]}

return (wf, outputs)
return wf, outputs


@nodeblock(
Expand Down
Loading