Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions monai/transforms/intensity/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1128,12 +1128,12 @@ def __init__(
self.upper = upper
self.sharpness_factor = sharpness_factor
self.channel_wise = channel_wise
if return_clipping_values:
self.clipping_values: list[tuple[float | None, float | None]] = []
self.return_clipping_values = return_clipping_values
self.dtype = dtype

def _clip(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
def _clip(
self, img: NdarrayOrTensor, clipping_values: list[tuple[float | None, float | None]] | None = None
) -> NdarrayOrTensor:
if self.sharpness_factor is not None:
lower_percentile = percentile(img, self.lower) if self.lower is not None else None
upper_percentile = percentile(img, self.upper) if self.upper is not None else None
Expand All @@ -1143,8 +1143,8 @@ def _clip(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
upper_percentile = percentile(img, self.upper) if self.upper is not None else percentile(img, 100)
img = clip(img, lower_percentile, upper_percentile)

if self.return_clipping_values:
self.clipping_values.append(
if clipping_values is not None:
clipping_values.append(
(
(
lower_percentile
Expand All @@ -1165,16 +1165,17 @@ def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
"""
Apply the transform to `img`.
"""
clipping_values: list[tuple[float | None, float | None]] | None = [] if self.return_clipping_values else None
img = convert_to_tensor(img, track_meta=get_track_meta())
img_t = convert_to_tensor(img, track_meta=False)
if self.channel_wise:
img_t = torch.stack([self._clip(img=d) for d in img_t]) # type: ignore
img_t = torch.stack([self._clip(img=d, clipping_values=clipping_values) for d in img_t]) # type: ignore
else:
img_t = self._clip(img=img_t)
img_t = self._clip(img=img_t, clipping_values=clipping_values)

img = convert_to_dst_type(img_t, dst=img)[0]
if self.return_clipping_values:
img.meta["clipping_values"] = self.clipping_values # type: ignore
if clipping_values is not None:
img.meta["clipping_values"] = clipping_values # type: ignore
Comment on lines +1177 to +1178

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against missing meta attribute.

If metadata tracking is disabled globally (get_track_meta() == False), img will not be a MetaTensor and accessing img.meta raises an AttributeError. Check for existence before assignment.

🛡️ Proposed fix
-        if clipping_values is not None:
+        if clipping_values is not None and hasattr(img, "meta"):
             img.meta["clipping_values"] = clipping_values  # type: ignore
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if clipping_values is not None:
img.meta["clipping_values"] = clipping_values # type: ignore
if clipping_values is not None and hasattr(img, "meta"):
img.meta["clipping_values"] = clipping_values # type: ignore
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/transforms/intensity/array.py` around lines 1177 - 1178, Update the
clipping_values assignment in the transform logic to verify that img has a meta
attribute before accessing it, while preserving the existing clipping_values
non-None condition and metadata assignment when available.


return img

Expand Down
24 changes: 24 additions & 0 deletions tests/transforms/test_clip_intensity_percentiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,29 @@ def test_channel_wise(self, p):
assert_allclose(result[i], p(expected), type_test="tensor", rtol=1e-4, atol=0)


class TestClipIntensityPercentilesClippingValues(unittest.TestCase):
def test_clipping_values_repeated_channel_wise_calls(self):
clipper = ClipIntensityPercentiles(lower=0, upper=100, channel_wise=True, return_clipping_values=True)
first = clipper(torch.tensor([[[0.0, 1.0]], [[10.0, 20.0]]]))
first_clipping_values = list(first.meta["clipping_values"])

second = clipper(torch.tensor([[[100.0, 200.0]], [[1000.0, 2000.0]]]))

self.assertEqual(first_clipping_values, [(0.0, 1.0), (10.0, 20.0)])
self.assertEqual(first.meta["clipping_values"], first_clipping_values)
self.assertEqual(second.meta["clipping_values"], [(100.0, 200.0), (1000.0, 2000.0)])

def test_clipping_values_repeated_non_channel_wise_calls(self):
clipper = ClipIntensityPercentiles(lower=0, upper=100, return_clipping_values=True)
first = clipper(torch.tensor([[[0.0, 1.0]]]))
first_clipping_values = list(first.meta["clipping_values"])

second = clipper(torch.tensor([[[100.0, 200.0]]]))

self.assertEqual(first_clipping_values, [(0.0, 1.0)])
self.assertEqual(first.meta["clipping_values"], first_clipping_values)
self.assertEqual(second.meta["clipping_values"], [(100.0, 200.0)])


if __name__ == "__main__":
unittest.main()
Loading