Skip to content

fix(RandGridDistortiond): only convert transform keys when skipping transform#8920

Open
AlexanderSanin wants to merge 1 commit into
Project-MONAI:devfrom
AlexanderSanin:fix/rand-grid-distortion-collate-8604-v2
Open

fix(RandGridDistortiond): only convert transform keys when skipping transform#8920
AlexanderSanin wants to merge 1 commit into
Project-MONAI:devfrom
AlexanderSanin:fix/rand-grid-distortion-collate-8604-v2

Conversation

@AlexanderSanin

Copy link
Copy Markdown
Contributor

Description

Fixes #8604

Root Cause

RandGridDistortiond.__call__ uses convert_to_tensor(d, track_meta=...) on the entire input dict when the transform is skipped (_do_transform=False). convert_to_tensor recursively converts every value in the dict — including non-image entries such as scalars and integers — into PyTorch tensors.

When the DataLoader collates a batch of these dicts, MONAI's collate_meta_tensor_fn is invoked for entries that now appear as tensors. That collate path expects non-image keys to retain their original Python types; receiving 0-d tensors instead triggers:

AttributeError: 'int' object has no attribute 'numel'

Fix

Replace the whole-dict conversion with a per-key loop using self.key_iterator(d), so only the keys this transform is responsible for are converted. This matches the pattern already used in the for key, mode, padding_mode in self.key_iterator(...) loop further down in the same method, and mirrors the approach in sibling transforms such as RandAffined.

Changes

  • monai/transforms/spatial/dictionary.py: use key_iterator in the no-op branch of RandGridDistortiond.__call__
  • tests/transforms/test_rand_grid_distortiond.py: add regression test asserting that non-image dict entries (integer, string) are preserved when the transform is skipped

Test plan

  • New test test_no_transform_preserves_non_image_keys confirms integer and string dict entries survive a skipped transform
  • Existing parameterized tests (test_rand_grid_distortiond_0/1/2) still pass
  • Manually verified that a DataLoader with RandGridDistortiond(prob=0.0) no longer raises AttributeError when the batch contains integer metadata fields alongside image tensors

…ransform

When `_do_transform` is False, `RandGridDistortiond.__call__` was calling
`convert_to_tensor(d, ...)` on the entire input dict. This recursively
converts *all* values — including non-image entries such as integers and
scalars — into PyTorch tensors. The converted dict is then returned to the
DataLoader which hands it to MONAI's `collate_meta_tensor_fn`. That
collate path expects non-image entries to remain as their original Python
types; receiving 0-d tensors instead triggers an `AttributeError: 'int'
object has no attribute 'numel'` when the collate function iterates over
what it believes to be a batch of tensors.

Fix: iterate over `self.key_iterator(d)` and convert only those values,
exactly as the transform loop further down in the same method already does.
This matches the per-key pattern used in sibling transforms such as
`RandAffined` and leaves unrelated dict entries unchanged.

Also adds a regression test that verifies integer and string entries are
preserved when the transform is skipped (prob=0.0).

Closes Project-MONAI#8604

Signed-off-by: Oleksandr Sanin <alexaaander.sanin@gmail.com>
@AlexanderSanin

Copy link
Copy Markdown
Contributor Author

Hey @ericspod @garciadias. Could you, please, have a look at this?

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

RandGridDistortiond.__call__ had a bug where the skipped-transform branch (not self._do_transform) called convert_to_tensor on the entire input dictionary, coercing all values—including non-image entries like int or str—into tensors. The fix replaces this with a loop over key_iterator(d), converting only the transform's designated keys. A new test confirms that with prob=0.0, non-keyed dict entries (label as int, filename as str) retain their original Python types after the call.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly describes the main change: fixing RandGridDistortiond to convert only transform keys when skipping transform.
Description check ✅ Passed Description includes root cause, fix explanation, changes made, and test plan. Covers issue #8604 and demonstrates understanding of the problem.
Linked Issues check ✅ Passed PR fully addresses #8604 requirements: replaces whole-dict conversion with per-key iteration using key_iterator, matches sibling transform patterns, and includes regression test for non-image entries.
Out of Scope Changes check ✅ Passed Changes are tightly scoped: only RandGridDistortiond's no-op branch and related regression test modified. No unrelated alterations present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/transforms/test_rand_grid_distortiond.py (1)

88-96: ⚡ Quick win

Strengthen regression test with keyed-value conversion assertion.

The new test verifies non-key preservation, but it should also assert the keyed "img" is still tensor-converted in the no-op path to lock the full contract.

Suggested patch
 import numpy as np
+import torch
 from parameterized import parameterized
@@
         result = g(data)
+        self.assertIsInstance(result["img"], torch.Tensor)
         self.assertIsInstance(result["label"], int)
         self.assertIsInstance(result["filename"], str)

As per coding guidelines, "Ensure new or modified definitions will be covered by existing or new unit tests."

🤖 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 `@tests/transforms/test_rand_grid_distortiond.py` around lines 88 - 96, The
test_no_transform_preserves_non_image_keys method verifies that non-keyed
entries are preserved but does not validate that the keyed entry "img" is still
properly converted to a tensor when the RandGridDistortiond transform is skipped
due to prob=0.0. Add assertions after the g(data) call to verify that
result["img"] is properly converted to a tensor type, ensuring the full contract
of the transform is tested including the tensor conversion behavior for keyed
values even in the no-op probability path.

Source: Coding guidelines

🤖 Prompt for all review comments with 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.

Inline comments:
In `@monai/transforms/spatial/dictionary.py`:
- Around line 2313-2315: When first_key equals an empty tuple, the code
incorrectly converts the entire dictionary to tensors using convert_to_tensor,
which causes unwanted non-key coercion when allow_missing_keys is True and leads
to collation failures. In the if first_key == () block, simply return d
unchanged instead of calling convert_to_tensor(d, track_meta=get_track_meta()),
since an empty first_key means there are no configured keys to process.

---

Nitpick comments:
In `@tests/transforms/test_rand_grid_distortiond.py`:
- Around line 88-96: The test_no_transform_preserves_non_image_keys method
verifies that non-keyed entries are preserved but does not validate that the
keyed entry "img" is still properly converted to a tensor when the
RandGridDistortiond transform is skipped due to prob=0.0. Add assertions after
the g(data) call to verify that result["img"] is properly converted to a tensor
type, ensuring the full contract of the transform is tested including the tensor
conversion behavior for keyed values even in the no-op probability path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a6e1a8bf-052e-4610-a1c7-241311fcb4c5

📥 Commits

Reviewing files that changed from the base of the PR and between 15f5073 and 4a9889b.

📒 Files selected for processing (2)
  • monai/transforms/spatial/dictionary.py
  • tests/transforms/test_rand_grid_distortiond.py

Comment on lines 2313 to 2315
if first_key == ():
out = convert_to_tensor(d, track_meta=get_track_meta())
out: dict[Hashable, torch.Tensor] = convert_to_tensor(d, track_meta=get_track_meta())
return out

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid whole-dict tensor coercion when no configured key exists.

Line 2314 still converts the entire mapping when first_key == (). With allow_missing_keys=True, this can reintroduce non-key coercion and the same downstream collation failure this PR fixes. Return d unchanged (or only iterate key_iterator, which is empty here).

Suggested patch
         first_key: Hashable = self.first_key(d)
         if first_key == ():
-            out: dict[Hashable, torch.Tensor] = convert_to_tensor(d, track_meta=get_track_meta())
-            return out
+            return d

As per coding guidelines, "Examine code for logical error or inconsistencies, and suggest what may be changed to addressed these."

🤖 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/spatial/dictionary.py` around lines 2313 - 2315, When
first_key equals an empty tuple, the code incorrectly converts the entire
dictionary to tensors using convert_to_tensor, which causes unwanted non-key
coercion when allow_missing_keys is True and leads to collation failures. In the
if first_key == () block, simply return d unchanged instead of calling
convert_to_tensor(d, track_meta=get_track_meta()), since an empty first_key
means there are no configured keys to process.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AttributeError in DataLoader when using RandGridDistortiond transform

1 participant