Skip to content

Commit e7e82b7

Browse files
committed
♻️ SSOT warp_to_template
1 parent 9169203 commit e7e82b7

File tree

4 files changed

+97
-91
lines changed

4 files changed

+97
-91
lines changed

CPAC/longitudinal_pipeline/longitudinal_workflow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ def anat_longitudinal_wf(subject_id: str, sub_list: list[dict], config: Configur
499499
# Rename nodes to include session name to avoid duplicates
500500
for key in strats_dct:
501501
for i, resource in enumerate(strats_dct[key]):
502-
resource = (
502+
strats_dct[key][i] = (
503503
resource[0].clone(f"{resource[0].name}_{session_id_list[i]}"),
504504
resource[1])
505505

CPAC/pipeline/cpac_pipeline.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,7 @@
9898
coregistration,
9999
create_func_to_T1template_xfm,
100100
create_func_to_T1template_symmetric_xfm,
101-
warp_wholeheadT1_to_template,
102-
warp_mask_to_template,
101+
warp_to_template,
103102
apply_phasediff_to_timeseries_separately,
104103
apply_blip_to_timeseries_separately,
105104
warp_timeseries_to_T1template,
@@ -1045,8 +1044,8 @@ def build_T1w_registration_stack(rpool, cfg, pipeline_blocks=None,
10451044
reg_blocks = [
10461045
[register_ANTs_anat_to_template, register_FSL_anat_to_template],
10471046
overwrite_transform_anat_to_template,
1048-
warp_wholeheadT1_to_template,
1049-
warp_mask_to_template(space)
1047+
warp_to_template("wholehead", space),
1048+
warp_to_template("mask", space)
10501049
]
10511050

10521051
if not rpool.check_rpool('desc-restore-brain_T1w'):

CPAC/registration/registration.py

Lines changed: 60 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
hardcoded_reg, \
3434
one_d_to_mat, \
3535
run_c3d, \
36-
run_c4d
36+
run_c4d, \
37+
prepend_space
3738
from CPAC.utils.interfaces.fsl import Merge as fslMerge
3839
from CPAC.utils.typing import LIST_OR_STR, TUPLE
3940
from CPAC.utils.utils import check_prov_for_motion_tool, check_prov_for_regtool
@@ -3512,121 +3513,94 @@ def apply_blip_to_timeseries_separately(wf, cfg, strat_pool, pipe_num,
35123513
return (wf, outputs)
35133514

35143515

3515-
@nodeblock(
3516-
name="transform_whole_head_T1w_to_T1template",
3517-
config=["registration_workflows", "anatomical_registration"],
3518-
switch=["run"],
3519-
inputs=[
3520-
(
3521-
["desc-head_T1w", "space-longitudinal_desc-reorient_T1w"],
3522-
["from-T1w_to-template_mode-image_xfm",
3523-
"from-longitudinal_to-template_mode-image_xfm"],
3524-
"space-template_desc-head_T1w",
3525-
),
3526-
"T1w-template",
3527-
],
3528-
outputs={"space-template_desc-head_T1w": {"Template": "T1w-template"}},
3529-
)
3530-
def warp_wholeheadT1_to_template(wf, cfg, strat_pool, pipe_num, opt=None):
3531-
xfm: list[str] = ["from-T1w_to-template_mode-image_xfm",
3532-
"from-longitudinal_to-template_mode-image_xfm"]
3533-
xfm_prov = strat_pool.get_cpac_provenance(xfm)
3534-
reg_tool = check_prov_for_regtool(xfm_prov)
3535-
3536-
num_cpus = cfg.pipeline_setup['system_config'][
3537-
'max_cores_per_participant']
3538-
3539-
num_ants_cores = cfg.pipeline_setup['system_config']['num_ants_threads']
3540-
3541-
apply_xfm = apply_transform(f'warp_wholehead_T1w_to_T1template_{pipe_num}',
3542-
reg_tool, time_series=False, num_cpus=num_cpus,
3543-
num_ants_cores=num_ants_cores)
3544-
3545-
if reg_tool == 'ants':
3546-
apply_xfm.inputs.inputspec.interpolation = cfg.registration_workflows[
3547-
'functional_registration']['func_registration_to_template'][
3548-
'ANTs_pipelines']['interpolation']
3549-
elif reg_tool == 'fsl':
3550-
apply_xfm.inputs.inputspec.interpolation = cfg.registration_workflows[
3551-
'functional_registration']['func_registration_to_template'][
3552-
'FNIRT_pipelines']['interpolation']
3553-
3554-
connect = strat_pool.get_data(["desc-head_T1w",
3555-
"space-longitudinal_desc-reorient_T1w"])
3556-
node, out = connect
3557-
wf.connect(node, out, apply_xfm, 'inputspec.input_image')
3558-
3559-
node, out = strat_pool.get_data("T1w-template")
3560-
wf.connect(node, out, apply_xfm, 'inputspec.reference')
3561-
3562-
node, out = strat_pool.get_data("from-T1w_to-template_mode-image_xfm")
3563-
wf.connect(node, out, apply_xfm, 'inputspec.transform')
3564-
3565-
outputs = {
3566-
'space-template_desc-head_T1w': (apply_xfm, 'outputspec.output_image')
3567-
}
3516+
def warp_to_template(warp_what: Literal["mask", "wholehead"],
3517+
space_from: Literal["longitudinal", "T1w"]) -> NodeBlockFunction:
3518+
"""Get a NodeBlockFunction to transform a resource from ``space`` to template.
35683519
3569-
return (wf, outputs)
3570-
3571-
def warp_mask_to_template(space: Literal["longitudinal", "T1w"]) -> NodeBlockFunction:
3572-
"""Get a NodeBlockFunction to transform a mask from ``space`` to template."""
3573-
@nodeblock(
3574-
name=f"transform_{space}-mask_to_T1-template",
3575-
switch=[
3520+
The resource being warped needs to be the first list or string in the tuple
3521+
in the first position of the decorator's "inputs".
3522+
"""
3523+
_decorators = {"mask": {
3524+
"name": f"transform_{space_from}-mask_to_T1-template",
3525+
"switch": [
35763526
["registration_workflows", "anatomical_registration", "run"],
35773527
["anatomical_preproc", "run"],
35783528
["anatomical_preproc", "brain_extraction", "run"],
35793529
],
3580-
inputs=[
3581-
(f"space-{space}_desc-brain_mask",
3582-
f"from-{space}_to-template_mode-image_xfm"),
3530+
"inputs": [
3531+
(f"space-{space_from}_desc-brain_mask",
3532+
f"from-{space_from}_to-template_mode-image_xfm"),
35833533
"T1w-template",
35843534
],
3585-
outputs={"space-template_desc-brain_mask": {"Template": "T1w-template"}},
3586-
)
3587-
def warp_mask_to_template_fxn(wf, cfg, strat_pool, pipe_num, opt=None):
3588-
"""Transform a mask to template space."""
3535+
"outputs": {"space-template_desc-brain_mask": {"Template": "T1w-template"}},
3536+
}, "wholehead": {
3537+
"name": f"transform_wholehead_{space_from}_to_T1template",
3538+
"config": ["registration_workflows", "anatomical_registration"],
3539+
"switch": ["run"],
3540+
"inputs": [
3541+
(
3542+
["desc-head_T1w", "desc-reorient_T1w"],
3543+
[f"from-{space_from}_to-template_mode-image_xfm",
3544+
f"from-{space_from}_to-template_mode-image_xfm"],
3545+
"space-template_desc-head_T1w",
3546+
),
3547+
"T1w-template",
3548+
],
3549+
"outputs": {"space-template_desc-head_T1w": {"Template": "T1w-template"}},
3550+
}}
3551+
if space_from != "T1w":
3552+
_decorators[warp_what]["inputs"][0] = tuple((prepend_space(
3553+
_decorators[warp_what]["inputs"][0][0], space_from),
3554+
*_decorators[warp_what]["inputs"][0][1:]
3555+
))
3556+
3557+
@nodeblock(**_decorators[warp_what])
3558+
def warp_to_template_fxn(wf, cfg, strat_pool, pipe_num, opt=None):
3559+
"""Transform a resource to template space."""
35893560

35903561
xfm_prov = strat_pool.get_cpac_provenance(
3591-
f'from-{space}_to-template_mode-image_xfm')
3562+
f'from-{space_from}_to-template_mode-image_xfm')
35923563
reg_tool = check_prov_for_regtool(xfm_prov)
35933564

35943565
num_cpus = cfg.pipeline_setup['system_config'][
35953566
'max_cores_per_participant']
35963567

35973568
num_ants_cores = cfg.pipeline_setup['system_config']['num_ants_threads']
35983569

3599-
apply_xfm = apply_transform(f'warp_T1mask_to_T1template_{pipe_num}',
3600-
reg_tool, time_series=False, num_cpus=num_cpus,
3601-
num_ants_cores=num_ants_cores)
3570+
apply_xfm = apply_transform(
3571+
f'warp_{space_from}{warp_what}_to_T1template_{pipe_num}',
3572+
reg_tool, time_series=False, num_cpus=num_cpus,
3573+
num_ants_cores=num_ants_cores)
36023574

3603-
apply_xfm.inputs.inputspec.interpolation = "NearestNeighbor"
3604-
'''
3605-
if reg_tool == 'ants':
3606-
apply_xfm.inputs.inputspec.interpolation = cfg.registration_workflows[
3607-
'functional_registration']['func_registration_to_template'][
3608-
'ANTs_pipelines']['interpolation']
3609-
elif reg_tool == 'fsl':
3575+
if warp_what == "mask":
3576+
apply_xfm.inputs.inputspec.interpolation = "NearestNeighbor"
3577+
else:
3578+
tool = "ANTs" if reg_tool == 'ants' else 'FNIRT' if reg_tool == 'fsl' else None
3579+
if not tool:
3580+
msg = f"Warp {warp_what} to template not implemented for {reg_tool}."
3581+
raise NotImplementedError(msg)
36103582
apply_xfm.inputs.inputspec.interpolation = cfg.registration_workflows[
36113583
'functional_registration']['func_registration_to_template'][
3612-
'FNIRT_pipelines']['interpolation']
3613-
'''
3614-
connect = strat_pool.get_data(f"space-{space}_desc-brain_mask")
3615-
node, out = connect
3584+
f'{tool}_pipelines']['interpolation']
3585+
3586+
# the resource being warped needs to be inputs[0][0] for this
3587+
node, out = strat_pool.get_data(_decorators[warp_what]["inputs"][0][0])
36163588
wf.connect(node, out, apply_xfm, 'inputspec.input_image')
36173589

36183590
node, out = strat_pool.get_data("T1w-template")
36193591
wf.connect(node, out, apply_xfm, 'inputspec.reference')
36203592

3621-
node, out = strat_pool.get_data(f"from-{space}_to-template_mode-image_xfm")
3593+
node, out = strat_pool.get_data(f"from-{space_from}_to-template_mode-image_xfm")
36223594
wf.connect(node, out, apply_xfm, 'inputspec.transform')
36233595

36243596
outputs = {
3625-
'space-template_desc-brain_mask': (apply_xfm, 'outputspec.output_image')
3597+
# there's only one output, so that's what we give here
3598+
list(_decorators[warp_what]["outputs"].keys())[0]: (
3599+
apply_xfm, 'outputspec.output_image')
36263600
}
36273601

36283602
return wf, outputs
3629-
return warp_mask_to_template_fxn
3603+
return warp_to_template_fxn
36303604

36313605

36323606
@nodeblock(

CPAC/registration/utils.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
1+
# Copyright (C) 2014-2024 C-PAC Developers
2+
3+
# This file is part of C-PAC.
4+
5+
# C-PAC is free software: you can redistribute it and/or modify it under
6+
# the terms of the GNU Lesser General Public License as published by the
7+
# Free Software Foundation, either version 3 of the License, or (at your
8+
# option) any later version.
9+
10+
# C-PAC is distributed in the hope that it will be useful, but WITHOUT
11+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13+
# License for more details.
14+
15+
# You should have received a copy of the GNU Lesser General Public
16+
# License along with C-PAC. If not, see <https://www.gnu.org/licenses/>.
17+
# pylint: disable=too-many-lines,ungrouped-imports,wrong-import-order
118
import os
19+
from typing import overload
220

321
import numpy as np
422

@@ -638,3 +656,18 @@ def run_c4d(input, output_name):
638656
os.system(cmd)
639657

640658
return output1, output2, output3
659+
660+
661+
@overload
662+
def prepend_space(resource: list[str], space: str) -> list[str]: ...
663+
@overload
664+
def prepend_space(resource: str, space: str) -> str: ...
665+
def prepend_space(resource: str | list[str], space: str) -> str | list[str]:
666+
"""Given a resource or list of resources, return same but with updated space."""
667+
if isinstance(resource, list):
668+
return [prepend_space(_, space) for _ in resource]
669+
if "space" not in resource:
670+
return f"space-{space}_{resource}"
671+
pre, post = resource.split("space-")
672+
_old_space, post = post.split("_", 1)
673+
return f"space-{space}_".join([pre, post])

0 commit comments

Comments
 (0)