diff --git a/niworkflows/interfaces/images.py b/niworkflows/interfaces/images.py
index c1b2eaee76c..2669904196a 100644
--- a/niworkflows/interfaces/images.py
+++ b/niworkflows/interfaces/images.py
@@ -365,7 +365,7 @@ def _run_interface(self, runtime):
CONFORMATION_TEMPLATE = """\t\t
Anatomical Conformation
\t\t
-\t\t\t- Input T1w images: {n_t1w}
+\t\t\t- Input {anat} images: {n_anat}
\t\t\t- Output orientation: RAS
\t\t\t- Output dimensions: {dims}
\t\t\t- Output voxel size: {zooms}
@@ -378,8 +378,15 @@ def _run_interface(self, runtime):
class _TemplateDimensionsInputSpec(BaseInterfaceInputSpec):
+ anat_type = traits.Enum("T1w", "T2w", usedefault=True, desc="Anatomical image type")
+ anat_list = InputMultiObject(
+ File(exists=True), xor=["t1w_list"], desc="input anatomical images"
+ )
t1w_list = InputMultiObject(
- File(exists=True), mandatory=True, desc="input T1w images"
+ File(exists=True),
+ xor=["anat_list"],
+ deprecated="1.14.0",
+ new_name="anat_list",
)
max_scale = traits.Float(
3.0, usedefault=True, desc="Maximum scaling factor in images to accept"
@@ -388,6 +395,7 @@ class _TemplateDimensionsInputSpec(BaseInterfaceInputSpec):
class _TemplateDimensionsOutputSpec(TraitedSpec):
t1w_valid_list = OutputMultiObject(exists=True, desc="valid T1w images")
+ anat_valid_list = OutputMultiObject(exists=True, desc="valid anatomical images")
target_zooms = traits.Tuple(
traits.Float, traits.Float, traits.Float, desc="Target zoom information"
)
@@ -399,8 +407,8 @@ class _TemplateDimensionsOutputSpec(TraitedSpec):
class TemplateDimensions(SimpleInterface):
"""
- Finds template target dimensions for a series of T1w images, filtering low-resolution images,
- if necessary.
+ Finds template target dimensions for a series of anatomical images, filtering low-resolution
+ images, if necessary.
Along each axis, the minimum voxel size (zoom) and the maximum number of voxels (shape) are
found across images.
@@ -426,7 +434,8 @@ def _generate_segment(self, discards, dims, zooms):
)
zoom_fmt = "{:.02g}mm x {:.02g}mm x {:.02g}mm".format(*zooms)
return CONFORMATION_TEMPLATE.format(
- n_t1w=len(self.inputs.t1w_list),
+ anat=self.inputs.anat_type,
+ n_anat=len(self.inputs.anat_list),
dims="x".join(map(str, dims)),
zooms=zoom_fmt,
n_discards=len(discards),
@@ -435,7 +444,10 @@ def _generate_segment(self, discards, dims, zooms):
def _run_interface(self, runtime):
# Load images, orient as RAS, collect shape and zoom data
- in_names = np.array(self.inputs.t1w_list)
+ if not self.inputs.anat_list: # Deprecate: 1.14.0
+ self.inputs.anat_list = self.inputs.t1w_list
+
+ in_names = np.array(self.inputs.anat_list)
orig_imgs = np.vectorize(nb.load)(in_names)
reoriented = np.vectorize(nb.as_closest_canonical)(orig_imgs)
all_zooms = np.array([img.header.get_zooms()[:3] for img in reoriented])
@@ -452,7 +464,8 @@ def _run_interface(self, runtime):
# Ignore dropped images
valid_fnames = np.atleast_1d(in_names[valid]).tolist()
- self._results["t1w_valid_list"] = valid_fnames
+ self._results["anat_valid_list"] = valid_fnames
+ self._results["t1w_valid_list"] = valid_fnames # Deprecate: 1.14.0
# Set target shape information
target_zooms = all_zooms[valid].min(axis=0)
diff --git a/niworkflows/interfaces/tests/test_images.py b/niworkflows/interfaces/tests/test_images.py
index a013a7dcad0..d21ad53eb80 100644
--- a/niworkflows/interfaces/tests/test_images.py
+++ b/niworkflows/interfaces/tests/test_images.py
@@ -22,6 +22,7 @@
#
"""Test images module."""
import time
+from pathlib import Path
import numpy as np
import nibabel as nb
from nipype.pipeline import engine as pe
@@ -179,3 +180,38 @@ def test_RobustAverage(tmpdir, shape):
assert out_file.shape == (10, 10, 10)
assert np.allclose(out_file.get_fdata(), 1.0)
+
+
+def test_TemplateDimensions(tmp_path):
+ """Exercise the various types of inputs."""
+ shapes = [
+ (10, 10, 10),
+ (11, 11, 11),
+ ]
+ zooms = [
+ (1, 1, 1),
+ (0.9, 0.9, 0.9),
+ ]
+
+ for i, (shape, zoom) in enumerate(zip(shapes, zooms)):
+ img = nb.Nifti1Image(np.ones(shape, dtype="float32"), np.eye(4))
+ img.header.set_zooms(zoom)
+ img.to_filename(tmp_path / f"test{i}.nii")
+
+ anat_list = [str(tmp_path / f"test{i}.nii") for i in range(2)]
+ td = im.TemplateDimensions(anat_list=anat_list)
+ res = td.run()
+
+ report = Path(res.outputs.out_report).read_text()
+ assert "Input T1w images: 2" in report
+ assert "Output dimensions: 11x11x11" in report
+ assert "Output voxel size: 0.9mm x 0.9mm x 0.9mm" in report
+ assert "Discarded images: 0" in report
+
+ assert res.outputs.t1w_valid_list == anat_list
+ assert res.outputs.anat_valid_list == anat_list
+ assert np.allclose(res.outputs.target_zooms, (0.9, 0.9, 0.9))
+ assert res.outputs.target_shape == (11, 11, 11)
+
+ with pytest.warns(UserWarning, match="t1w_list .* is deprecated"):
+ im.TemplateDimensions(t1w_list=anat_list)