From 1dcf6fe1eba19fd32cd8ef1e9a85cd1966af52c2 Mon Sep 17 00:00:00 2001 From: "R. Garcia-Dias" Date: Mon, 12 Oct 2026 19:14:58 +0100 Subject: [PATCH] Fix Spacingd updating non-metadata entries that share the key prefix Since #8269, Spacingd refreshes every entry whose key starts with '_', assuming it is a metadata dict. Entries such as a string 'image_path' or an int 'image_index' then raise AttributeError, and a non-string key raises on startswith. Only update string-keyed entries that are mutable mappings. Adds tests for the metadata refresh introduced in #8269 and for leaving non-metadata entries untouched. Fixes #9165 Signed-off-by: R. Garcia-Dias --- monai/transforms/spatial/dictionary.py | 10 ++++++++-- tests/transforms/test_spacingd.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 58307377118..574def3edff 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -18,7 +18,7 @@ from __future__ import annotations import warnings -from collections.abc import Hashable, Mapping, Sequence +from collections.abc import Hashable, Mapping, MutableMapping, Sequence from typing import Any, cast import numpy as np @@ -523,7 +523,13 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = No lazy=lazy_, ) if isinstance(d[key], MetaTensor): - meta_keys = [k for k in d.keys() if k is not None and k.startswith(f"{key}_")] + # only refresh metadata dictionaries (e.g. "image_meta_dict"); other entries that merely + # share the prefix (e.g. a string "image_path") must be left untouched + meta_keys = [ + k + for k in d.keys() + if isinstance(k, str) and k.startswith(f"{key}_") and isinstance(d[k], MutableMapping) + ] for meta_key in meta_keys: if "filename_or_obj" in d[key].meta and is_supported_format( d[key].meta["filename_or_obj"], ["nii", "nii.gz"] diff --git a/tests/transforms/test_spacingd.py b/tests/transforms/test_spacingd.py index a3dcaf5c673..c3c1bf59cd6 100644 --- a/tests/transforms/test_spacingd.py +++ b/tests/transforms/test_spacingd.py @@ -173,6 +173,22 @@ def test_space_same_shape(self): out = Spacingd(("img_1", "img_2"), pixdim=1, ensure_same_shape=False)({"img_1": img_1, "img_2": img_2}) self.assertNotEqual(out["img_1"].shape, out["img_2"].shape) # ensure_same_shape False + def test_meta_dict_updated_for_nifti(self): + img = MetaTensor(torch.zeros(1, 8, 8, 8), affine=torch.eye(4), meta={"filename_or_obj": "img.nii.gz"}) + out = Spacingd("image", pixdim=2.0)({"image": img, "image_meta_dict": {"affine": torch.eye(4)}}) + # the metadata dict is refreshed from the resampled image (spacing 1 -> 2) + assert_allclose(affine_to_spacing(out["image_meta_dict"]["affine"]), (2.0, 2.0, 2.0), type_test=False) + + def test_non_meta_dict_entries_untouched(self): + # entries that share the "_" prefix but are not metadata dicts must not be modified (issue #9165) + img = MetaTensor(torch.zeros(1, 8, 8, 8), affine=torch.eye(4), meta={"filename_or_obj": "img.nii.gz"}) + data = {"image": img, "image_path": "img.nii.gz", "image_index": 3, 0: "int key"} + out = Spacingd("image", pixdim=2.0)(data) + self.assertEqual(out["image_path"], "img.nii.gz") + self.assertEqual(out["image_index"], 3) + self.assertEqual(out[0], "int key") + self.assertEqual(out["image"].shape, (1, 4, 4, 4)) + if __name__ == "__main__": unittest.main()