Skip to content

Commit 9acda6b

Browse files
authored
Merge pull request #425 from mgxd/fix/composite-xfms
FIX: Generate composite transforms with `--multi-step-reg`
2 parents f95e40b + f327dd6 commit 9acda6b

File tree

5 files changed

+125
-48
lines changed

5 files changed

+125
-48
lines changed

Dockerfile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ RUN mkdir -p /opt/afni-latest \
7272
-name "3dAutomask" -or \
7373
-name "3dvolreg" \) -delete
7474

75-
# ANTs 2.4.4
75+
# ANTs 2.5.4
7676
FROM downloader as ants
7777
RUN mkdir -p /opt && \
78-
curl -sSLO "https://github.com/ANTsX/ANTs/releases/download/v2.4.4/ants-2.4.4-ubuntu-22.04-X64-gcc.zip" && \
79-
unzip ants-2.4.4-ubuntu-22.04-X64-gcc.zip -d /opt && \
80-
rm ants-2.4.4-ubuntu-22.04-X64-gcc.zip
78+
curl -sSLO "https://github.com/ANTsX/ANTs/releases/download/v2.5.4/ants-2.5.4-ubuntu-22.04-X64-gcc.zip" && \
79+
unzip ants-2.5.4-ubuntu-22.04-X64-gcc.zip -d /opt && \
80+
rm ants-2.5.4-ubuntu-22.04-X64-gcc.zip
8181

8282
# Connectome Workbench 1.5.0
8383
FROM downloader as workbench
@@ -180,7 +180,7 @@ RUN apt-get update -qq \
180180
&& ldconfig
181181

182182
COPY --from=afni /opt/afni-latest /opt/afni-latest
183-
COPY --from=ants /opt/ants-2.4.4 /opt/ants
183+
COPY --from=ants /opt/ants-2.5.4 /opt/ants
184184
COPY --from=workbench /opt/workbench /opt/workbench
185185

186186
# AFNI config

nibabies/interfaces/patches.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
freesurfer as fs,
55
)
66
from nipype.interfaces.ants.base import ANTSCommand, ANTSCommandInputSpec
7+
from nipype.interfaces.ants.registration import (
8+
CompositeTransformUtil as _CompositeTransformUtil,
9+
)
10+
from nipype.interfaces.ants.registration import (
11+
CompositeTransformUtilInputSpec as _CompositeTransformUtilInputSpec,
12+
)
13+
from nipype.interfaces.ants.registration import (
14+
CompositeTransformUtilOutputSpec as _CompositeTransformUtilOutputSpec,
15+
)
716
from nipype.interfaces.base import File, InputMultiObject, TraitedSpec, traits
817

918

@@ -105,3 +114,72 @@ def _list_outputs(self):
105114
outputs = self._outputs().get()
106115
outputs['out_xfm'] = Path(self.inputs.out_xfm).absolute()
107116
return outputs
117+
118+
119+
class CompositeTransformUtilInputSpec(_CompositeTransformUtilInputSpec):
120+
order_transforms = traits.Bool(
121+
True,
122+
usedefault=True,
123+
desc='Order disassembled transforms into [Affine, Displacement] pairs.',
124+
)
125+
126+
127+
class CompositeTransformUtilOutputSpec(_CompositeTransformUtilOutputSpec):
128+
out_transforms = traits.List(desc='list of transform components')
129+
130+
131+
class CompositeTransformUtil(_CompositeTransformUtil):
132+
"""Outputs have changed in newer versions of ANTs."""
133+
134+
input_spec = CompositeTransformUtilInputSpec
135+
output_spec = CompositeTransformUtilOutputSpec
136+
137+
def _list_outputs(self):
138+
outputs = self.output_spec().get()
139+
140+
# Ordering may change depending on forward/inverse transform
141+
# Forward: <prefix>_00_AffineTransform.mat, <prefix>_01_DisplacementFieldTransform.nii.gz
142+
# Inverse: <prefix>_01_AffineTransform.mat, <prefix>_00_DisplacementFieldTransform.nii.gz
143+
if self.inputs.process == 'disassemble':
144+
transforms = [
145+
str(Path(x).absolute())
146+
for x in sorted(Path().glob(f'{self.inputs.output_prefix}_*'))
147+
]
148+
149+
if self.inputs.order_transforms:
150+
transforms = _order_xfms(transforms)
151+
outputs['out_transforms'] = transforms
152+
153+
# Potentially could be more than one affine / displacement per composite transform...
154+
outputs['affine_transform'] = [
155+
x for x in transforms if 'AffineTransform' in Path(x).name
156+
][0]
157+
outputs['displacement_field'] = [
158+
x for x in transforms if 'DisplacementFieldTransform' in Path(x).name
159+
][0]
160+
elif self.inputs.process == 'assemble':
161+
outputs['out_file'] = Path(self.inputs.out_file).absolute()
162+
return outputs
163+
164+
165+
def _order_xfms(vals):
166+
"""
167+
Assumes [affine, displacement] or [displacement, affine] transform pairs.
168+
169+
>>> _order_xfms(['DisplacementFieldTransform.nii.gz', 'AffineTransform.mat'])
170+
['AffineTransform.mat', 'DisplacementFieldTransform.nii.gz']
171+
172+
>>> _order_xfms(['AffineTransform.mat', 'DisplacementFieldTransform.nii.gz'])
173+
['AffineTransform.mat', 'DisplacementFieldTransform.nii.gz']
174+
175+
>>> _order_xfms(['DisplacementFieldTransform.nii.gz', 'AffineTransform.mat', \
176+
'AffineTransform.mat'])
177+
['AffineTransform.mat', 'DisplacementFieldTransform.nii.gz', 'AffineTransform.mat']
178+
"""
179+
for i in range(0, len(vals) - 1, 2):
180+
if (
181+
'DisplacementFieldTransform' in Path(vals[i]).name
182+
and 'AffineTransform' in Path(vals[i + 1]).name
183+
):
184+
vals[i], vals[i + 1] = vals[i + 1], vals[i]
185+
return vals

nibabies/workflows/anatomical/fit.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,6 @@ def init_infant_anat_fit_wf(
991991
('anat2std_xfm', 'inputnode.anat2std_xfm'),
992992
('std2anat_xfm', 'inputnode.std2anat_xfm'),
993993
]),
994-
(anat_buffer, concat_reg_wf, [('anat_preproc', 'inputnode.anat_preproc')]),
995994
(sourcefile_buffer, ds_concat_reg_wf, [
996995
('anat_source_files', 'inputnode.source_files')
997996
]),
@@ -1909,7 +1908,6 @@ def init_infant_single_anat_fit_wf(
19091908
('anat2std_xfm', 'inputnode.anat2std_xfm'),
19101909
('std2anat_xfm', 'inputnode.std2anat_xfm'),
19111910
]),
1912-
(anat_buffer, concat_reg_wf, [('anat_preproc', 'inputnode.anat_preproc')]),
19131911
(sourcefile_buffer, ds_concat_reg_wf, [
19141912
('anat_source_files', 'inputnode.source_files')
19151913
]),

nibabies/workflows/anatomical/registration.py

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,11 @@
1515
from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms
1616
from smriprep.workflows.fit.registration import (
1717
TemplateDesc,
18-
TemplateFlowSelect,
1918
_fmt_cohort,
2019
get_metadata,
2120
tf_ver,
2221
)
2322

24-
from nibabies.config import DEFAULT_MEMORY_MIN_GB
25-
from nibabies.interfaces.patches import ConcatXFM
26-
2723

2824
def init_coregistration_wf(
2925
*,
@@ -312,7 +308,7 @@ def init_concat_registrations_wf(
312308
name='concat_registrations_wf',
313309
):
314310
"""
315-
Concatenate two transforms to produce a single transform, from native to ``template``.
311+
Concatenate two transforms to produce a single composite transform from native to template.
316312
317313
Parameters
318314
----------
@@ -347,6 +343,8 @@ def init_concat_registrations_wf(
347343
further use in downstream nodes.
348344
349345
"""
346+
from nibabies.interfaces.patches import CompositeTransformUtil
347+
350348
ntpls = len(templates)
351349
workflow = Workflow(name=name)
352350

@@ -384,9 +382,7 @@ def init_concat_registrations_wf(
384382
workflow.__desc__ += '.\n' if template == templates[-1] else ', '
385383

386384
inputnode = pe.Node(
387-
niu.IdentityInterface(
388-
fields=['template', 'anat_preproc', 'anat2std_xfm', 'intermediate', 'std2anat_xfm']
389-
),
385+
niu.IdentityInterface(fields=['template', 'intermediate', 'anat2std_xfm', 'std2anat_xfm']),
390386
name='inputnode',
391387
)
392388
inputnode.inputs.template = templates
@@ -413,29 +409,37 @@ def init_concat_registrations_wf(
413409
TemplateDesc(), run_without_submitting=True, iterfield='template', name='split_desc'
414410
)
415411

416-
tf_select = pe.MapNode(
417-
TemplateFlowSelect(resolution=1),
418-
name='tf_select',
419-
run_without_submitting=True,
420-
iterfield=['template', 'template_spec'],
412+
merge_anat2std = pe.Node(niu.Merge(2), name='merge_anat2std', run_without_submitting=True)
413+
merge_std2anat = merge_anat2std.clone('merge_std2anat')
414+
415+
disassemble_anat2std = pe.MapNode(
416+
CompositeTransformUtil(process='disassemble', output_prefix='anat2std'),
417+
iterfield=['in_file'],
418+
name='disassemble_anat2std',
421419
)
422420

423-
merge_anat2std = pe.MapNode(
424-
niu.Merge(2), name='merge_anat2std', iterfield=['in1', 'in2'], run_without_submitting=True
421+
disassemble_std2anat = pe.MapNode(
422+
CompositeTransformUtil(process='disassemble', output_prefix='std2anat'),
423+
iterfield=['in_file'],
424+
name='disassemble_std2anat',
425425
)
426-
merge_std2anat = merge_anat2std.clone('merge_std2anat')
427426

428-
concat_anat2std = pe.MapNode(
429-
ConcatXFM(),
430-
name='concat_anat2std',
431-
mem_gb=DEFAULT_MEMORY_MIN_GB,
432-
iterfield=['transforms', 'reference_image'],
427+
merge_anat2std_composites = pe.Node(
428+
niu.Merge(1, ravel_inputs=True),
429+
name='merge_anat2std_composites',
433430
)
434-
concat_std2anat = pe.MapNode(
435-
ConcatXFM(),
436-
name='concat_std2anat',
437-
mem_gb=DEFAULT_MEMORY_MIN_GB,
438-
iterfield=['transforms', 'reference_image'],
431+
merge_std2anat_composites = pe.Node(
432+
niu.Merge(1, ravel_inputs=True),
433+
name='merge_std2anat_composites',
434+
)
435+
436+
assemble_anat2std = pe.Node(
437+
CompositeTransformUtil(process='assemble', out_file='anat2std.h5'),
438+
name='assemble_anat2std',
439+
)
440+
assemble_std2anat = pe.Node(
441+
CompositeTransformUtil(process='assemble', out_file='std2anat.h5'),
442+
name='assemble_std2anat',
439443
)
440444

441445
fmt_cohort = pe.MapNode(
@@ -446,24 +450,24 @@ def init_concat_registrations_wf(
446450
)
447451

448452
workflow.connect([
453+
# Template concatenation
449454
(inputnode, merge_anat2std, [('anat2std_xfm', 'in2')]),
450455
(inputnode, merge_std2anat, [('std2anat_xfm', 'in2')]),
451-
(inputnode, concat_std2anat, [('anat_preproc', 'reference_image')]),
452456
(inputnode, intermed_xfms, [('intermediate', 'intermediate')]),
453457
(inputnode, intermed_xfms, [('template', 'std')]),
454-
455458
(intermed_xfms, merge_anat2std, [('int2std_xfm', 'in1')]),
456459
(intermed_xfms, merge_std2anat, [('std2int_xfm', 'in1')]),
457-
458-
(merge_anat2std, concat_anat2std, [('out', 'transforms')]),
459-
(merge_std2anat, concat_std2anat, [('out', 'transforms')]),
460-
460+
(merge_anat2std, disassemble_anat2std, [('out', 'in_file')]),
461+
(merge_std2anat, disassemble_std2anat, [('out', 'in_file')]),
462+
(disassemble_anat2std, merge_anat2std_composites, [('out_transforms', 'in1')]),
463+
(disassemble_std2anat, merge_std2anat_composites, [('out_transforms', 'in1')]),
464+
(merge_anat2std_composites, assemble_anat2std, [('out', 'in_file')]),
465+
(merge_std2anat_composites, assemble_std2anat, [('out', 'in_file')]),
466+
(assemble_anat2std, outputnode, [('out_file', 'anat2std_xfm')]),
467+
(assemble_std2anat, outputnode, [('out_file', 'std2anat_xfm')]),
468+
469+
# Template name wrangling
461470
(inputnode, split_desc, [('template', 'template')]),
462-
(split_desc, tf_select, [
463-
('name', 'template'),
464-
('spec', 'template_spec'),
465-
]),
466-
(tf_select, concat_anat2std, [('t1w_file', 'reference_image')]),
467471
(split_desc, fmt_cohort, [
468472
('name', 'template'),
469473
('spec', 'spec'),
@@ -472,8 +476,6 @@ def init_concat_registrations_wf(
472476
('template', 'template'),
473477
('spec', 'template_spec'),
474478
]),
475-
(concat_anat2std, outputnode, [('out_xfm', 'anat2std_xfm')]),
476-
(concat_std2anat, outputnode, [('out_xfm', 'std2anat_xfm')]),
477479
]) # fmt:skip
478480

479481
return workflow

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ dependencies = [
2424
"nipype >= 1.8.5",
2525
"nireports >= 23.2.0",
2626
"nitime",
27-
# "nitransforms >= 24.1.1",
28-
"nitransforms @ git+https://github.com/nipy/nitransforms.git@enh/itk-displacement-field",
27+
"nitransforms >= 24.1.1",
2928
"niworkflows >= 1.12.1",
3029
"numpy >= 1.21.0",
3130
"packaging",

0 commit comments

Comments
 (0)