Skip to content

Commit a744a53

Browse files
authored
Merge pull request #879 from mgxd/add/t2w-template-dim
ADD: Make template dimensions support T2w as well
2 parents 1dc0327 + 785c40f commit a744a53

File tree

2 files changed

+56
-7
lines changed

2 files changed

+56
-7
lines changed

niworkflows/interfaces/images.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ def _run_interface(self, runtime):
365365

366366
CONFORMATION_TEMPLATE = """\t\t<h3 class="elem-title">Anatomical Conformation</h3>
367367
\t\t<ul class="elem-desc">
368-
\t\t\t<li>Input T1w images: {n_t1w}</li>
368+
\t\t\t<li>Input {anat} images: {n_anat}</li>
369369
\t\t\t<li>Output orientation: RAS</li>
370370
\t\t\t<li>Output dimensions: {dims}</li>
371371
\t\t\t<li>Output voxel size: {zooms}</li>
@@ -378,8 +378,15 @@ def _run_interface(self, runtime):
378378

379379

380380
class _TemplateDimensionsInputSpec(BaseInterfaceInputSpec):
381+
anat_type = traits.Enum("T1w", "T2w", usedefault=True, desc="Anatomical image type")
382+
anat_list = InputMultiObject(
383+
File(exists=True), xor=["t1w_list"], desc="input anatomical images"
384+
)
381385
t1w_list = InputMultiObject(
382-
File(exists=True), mandatory=True, desc="input T1w images"
386+
File(exists=True),
387+
xor=["anat_list"],
388+
deprecated="1.14.0",
389+
new_name="anat_list",
383390
)
384391
max_scale = traits.Float(
385392
3.0, usedefault=True, desc="Maximum scaling factor in images to accept"
@@ -388,6 +395,7 @@ class _TemplateDimensionsInputSpec(BaseInterfaceInputSpec):
388395

389396
class _TemplateDimensionsOutputSpec(TraitedSpec):
390397
t1w_valid_list = OutputMultiObject(exists=True, desc="valid T1w images")
398+
anat_valid_list = OutputMultiObject(exists=True, desc="valid anatomical images")
391399
target_zooms = traits.Tuple(
392400
traits.Float, traits.Float, traits.Float, desc="Target zoom information"
393401
)
@@ -399,8 +407,8 @@ class _TemplateDimensionsOutputSpec(TraitedSpec):
399407

400408
class TemplateDimensions(SimpleInterface):
401409
"""
402-
Finds template target dimensions for a series of T1w images, filtering low-resolution images,
403-
if necessary.
410+
Finds template target dimensions for a series of anatomical images, filtering low-resolution
411+
images, if necessary.
404412
405413
Along each axis, the minimum voxel size (zoom) and the maximum number of voxels (shape) are
406414
found across images.
@@ -426,7 +434,8 @@ def _generate_segment(self, discards, dims, zooms):
426434
)
427435
zoom_fmt = "{:.02g}mm x {:.02g}mm x {:.02g}mm".format(*zooms)
428436
return CONFORMATION_TEMPLATE.format(
429-
n_t1w=len(self.inputs.t1w_list),
437+
anat=self.inputs.anat_type,
438+
n_anat=len(self.inputs.anat_list),
430439
dims="x".join(map(str, dims)),
431440
zooms=zoom_fmt,
432441
n_discards=len(discards),
@@ -435,7 +444,10 @@ def _generate_segment(self, discards, dims, zooms):
435444

436445
def _run_interface(self, runtime):
437446
# Load images, orient as RAS, collect shape and zoom data
438-
in_names = np.array(self.inputs.t1w_list)
447+
if not self.inputs.anat_list: # Deprecate: 1.14.0
448+
self.inputs.anat_list = self.inputs.t1w_list
449+
450+
in_names = np.array(self.inputs.anat_list)
439451
orig_imgs = np.vectorize(nb.load)(in_names)
440452
reoriented = np.vectorize(nb.as_closest_canonical)(orig_imgs)
441453
all_zooms = np.array([img.header.get_zooms()[:3] for img in reoriented])
@@ -452,7 +464,8 @@ def _run_interface(self, runtime):
452464

453465
# Ignore dropped images
454466
valid_fnames = np.atleast_1d(in_names[valid]).tolist()
455-
self._results["t1w_valid_list"] = valid_fnames
467+
self._results["anat_valid_list"] = valid_fnames
468+
self._results["t1w_valid_list"] = valid_fnames # Deprecate: 1.14.0
456469

457470
# Set target shape information
458471
target_zooms = all_zooms[valid].min(axis=0)

niworkflows/interfaces/tests/test_images.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#
2323
"""Test images module."""
2424
import time
25+
from pathlib import Path
2526
import numpy as np
2627
import nibabel as nb
2728
from nipype.pipeline import engine as pe
@@ -179,3 +180,38 @@ def test_RobustAverage(tmpdir, shape):
179180

180181
assert out_file.shape == (10, 10, 10)
181182
assert np.allclose(out_file.get_fdata(), 1.0)
183+
184+
185+
def test_TemplateDimensions(tmp_path):
186+
"""Exercise the various types of inputs."""
187+
shapes = [
188+
(10, 10, 10),
189+
(11, 11, 11),
190+
]
191+
zooms = [
192+
(1, 1, 1),
193+
(0.9, 0.9, 0.9),
194+
]
195+
196+
for i, (shape, zoom) in enumerate(zip(shapes, zooms)):
197+
img = nb.Nifti1Image(np.ones(shape, dtype="float32"), np.eye(4))
198+
img.header.set_zooms(zoom)
199+
img.to_filename(tmp_path / f"test{i}.nii")
200+
201+
anat_list = [str(tmp_path / f"test{i}.nii") for i in range(2)]
202+
td = im.TemplateDimensions(anat_list=anat_list)
203+
res = td.run()
204+
205+
report = Path(res.outputs.out_report).read_text()
206+
assert "Input T1w images: 2" in report
207+
assert "Output dimensions: 11x11x11" in report
208+
assert "Output voxel size: 0.9mm x 0.9mm x 0.9mm" in report
209+
assert "Discarded images: 0" in report
210+
211+
assert res.outputs.t1w_valid_list == anat_list
212+
assert res.outputs.anat_valid_list == anat_list
213+
assert np.allclose(res.outputs.target_zooms, (0.9, 0.9, 0.9))
214+
assert res.outputs.target_shape == (11, 11, 11)
215+
216+
with pytest.warns(UserWarning, match="t1w_list .* is deprecated"):
217+
im.TemplateDimensions(t1w_list=anat_list)

0 commit comments

Comments
 (0)