Skip to content

Fix nested Compose map_items in forward and inverse paths#8787

Open
aymuos15 wants to merge 9 commits intoProject-MONAI:devfrom
aymuos15:worktree-fix-nested-compose-map-items
Open

Fix nested Compose map_items in forward and inverse paths#8787
aymuos15 wants to merge 9 commits intoProject-MONAI:devfrom
aymuos15:worktree-fix-nested-compose-map-items

Conversation

@aymuos15
Copy link
Copy Markdown
Contributor

@aymuos15 aymuos15 commented Mar 20, 2026

Summary

Fixes #7932, #7565

When a child Compose has a different map_items setting than its parent, the parent's apply_transform would expand list/tuple data before the child ever sees it — silently overriding the child's map_items.

This PR makes three coordinated changes so the child's map_items is respected:

  • Forward path (apply_transform): Skip list expansion when the transform is a Compose instance, letting it handle expansion via its own map_items in execute_compose.
  • Inverse path (_inverse_one helper): Delegate directly to Compose.inverse() for nested Compose objects (including RandomOrder and SomeOf) instead of routing through apply_transform(t.inverse, ...).
  • flatten(): Only inline nested Compose objects that share the same map_items as the parent. Children with a different map_items are preserved as-is.

Test plan

  • test_child_map_items_false_receives_list — parent map_items=True, child map_items=False: child receives list as-is
  • test_inverse_respects_child_map_items — inverse roundtrip with nested Compose
  • test_parent_no_map_child_map — parent map_items=False, child map_items=True: child maps over items
  • test_flatten_preserves_different_map_itemsflatten() does not merge children with different map_items

…t-MONAI#7932, Project-MONAI#7565)

When a child Compose has a different map_items setting than its parent,
the parent now delegates to the child instead of expanding list/tuple
data itself. This applies to forward execution (apply_transform),
flatten(), and the inverse path in Compose, RandomOrder, and SomeOf.

Signed-off-by: Soumya Snigdha Kundu <soumya_snigdha.kundu@kcl.ac.uk>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added a helper _inverse_one(...) to standardize inversion of single transforms and switched Compose.inverse, RandomOrder.inverse, and SomeOf.inverse to use it. Adjusted apply_transform so Compose can handle list/tuple expansion itself. Modified Compose.flatten() to inline nested Compose only when the child's map_items equals the parent's; otherwise preserve the nested Compose. Expanded tests to cover nested Compose behavior for map_items, flattening, and inversion.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.72% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main fix: addressing nested Compose map_items behavior in both forward and inverse paths.
Description check ✅ Passed Description covers objectives, changes, and test plan, but PR template requires specific sections and type checkboxes. Missing issue reference format and some template structure.
Linked Issues check ✅ Passed Changes fully address #7932's core requirement: providing mechanism to respect child Compose's map_items setting instead of forcing expansion.
Out of Scope Changes check ✅ Passed All changes are scoped to nested Compose map_items handling: apply_transform list expansion logic, inverse delegation via _inverse_one, and flatten() matching logic.

✏️ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
monai/transforms/compose.py (1)

40-51: Add Google-style docstring with Args/Returns.

Per coding guidelines, docstrings should describe parameters and return values.

📝 Proposed docstring
 def _inverse_one(
     t: InvertibleTransform,
     data: Any,
     map_items: bool | int,
     unpack_items: bool,
     log_stats: bool | str,
 ) -> Any:
-    """Invert a single transform, delegating directly to nested ``Compose`` objects."""
+    """Invert a single transform, delegating directly to nested ``Compose`` objects.
+
+    Args:
+        t: The invertible transform to invert.
+        data: Data to be inverted.
+        map_items: Whether to map over list/tuple items.
+        unpack_items: Whether to unpack data as parameters.
+        log_stats: Logger name or boolean for logging.
+
+    Returns:
+        The inverted data.
+    """
     if isinstance(t, Compose):
         return t.inverse(data)
     return apply_transform(t.inverse, data, map_items, unpack_items, lazy=False, log_stats=log_stats)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@monai/transforms/compose.py` around lines 40 - 51, Add a Google-style
docstring to the _inverse_one function describing its purpose, parameters and
return value: state that it inverts a single transform (delegating to
Compose.inverse when t is a Compose), document args t (InvertibleTransform),
data (Any), map_items (bool|int), unpack_items (bool), log_stats (bool|str), and
explain the return value (inverted data of type Any); also mention that
apply_transform is called for non-Compose transforms with lazy=False. Include
short notes on side effects (calls to Compose.inverse/apply_transform) if
relevant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@monai/transforms/compose.py`:
- Around line 40-51: Add a Google-style docstring to the _inverse_one function
describing its purpose, parameters and return value: state that it inverts a
single transform (delegating to Compose.inverse when t is a Compose), document
args t (InvertibleTransform), data (Any), map_items (bool|int), unpack_items
(bool), log_stats (bool|str), and explain the return value (inverted data of
type Any); also mention that apply_transform is called for non-Compose
transforms with lazy=False. Include short notes on side effects (calls to
Compose.inverse/apply_transform) if relevant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 13d05d32-401c-4be3-a001-687e58886c54

📥 Commits

Reviewing files that changed from the base of the PR and between daaedaa and 26c051f.

📒 Files selected for processing (3)
  • monai/transforms/compose.py
  • monai/transforms/transform.py
  • tests/transforms/compose/test_compose.py

Address CodeRabbit review comment: document Args/Returns for the
_inverse_one helper function per project coding guidelines.

Signed-off-by: Soumya Snigdha Kundu <soumya_snigdha.kundu@kcl.ac.uk>
Signed-off-by: Soumya Snigdha Kundu <soumya_snigdha.kundu@kcl.ac.uk>
Signed-off-by: Soumya Snigdha Kundu <soumya_snigdha.kundu@kcl.ac.uk>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
monai/transforms/compose.py (1)

354-361: flatten() doesn't preserve parent attributes (unlike OneOf.flatten()).

Line 361 returns Compose(new_transforms) with defaults. If someone calls flatten() expecting an equivalent executable pipeline, map_items, unpack_items, log_stats, lazy, and overrides are lost.

Current internal usage only reads .transforms, so no bug today.

Proposed fix
-        return Compose(new_transforms)
+        return Compose(new_transforms, self.map_items, self.unpack_items, self.log_stats, self.lazy, self.overrides)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@monai/transforms/compose.py` around lines 354 - 361, Compose.flatten()
currently builds and returns a new Compose instance with only new_transforms,
dropping parent attributes; update Compose.flatten to preserve and pass through
the parent's configuration (map_items, unpack_items, log_stats, lazy, overrides)
when constructing the returned Compose so the flattened pipeline is equivalent
to the original (mirroring OneOf.flatten behavior). Locate the Compose.flatten
implementation and change the return from Compose(new_transforms) to
Compose(new_transforms, map_items=self.map_items,
unpack_items=self.unpack_items, log_stats=self.log_stats, lazy=self.lazy,
overrides=self.overrides) or otherwise propagate those attributes from self into
the new Compose instance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@monai/transforms/compose.py`:
- Around line 354-361: Compose.flatten() currently builds and returns a new
Compose instance with only new_transforms, dropping parent attributes; update
Compose.flatten to preserve and pass through the parent's configuration
(map_items, unpack_items, log_stats, lazy, overrides) when constructing the
returned Compose so the flattened pipeline is equivalent to the original
(mirroring OneOf.flatten behavior). Locate the Compose.flatten implementation
and change the return from Compose(new_transforms) to Compose(new_transforms,
map_items=self.map_items, unpack_items=self.unpack_items,
log_stats=self.log_stats, lazy=self.lazy, overrides=self.overrides) or otherwise
propagate those attributes from self into the new Compose instance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4cdba609-bf44-46ad-a65c-afd42771ee1d

📥 Commits

Reviewing files that changed from the base of the PR and between 4595a45 and f732583.

📒 Files selected for processing (2)
  • monai/transforms/compose.py
  • tests/transforms/compose/test_compose.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/transforms/compose/test_compose.py

@atbenmurray
Copy link
Copy Markdown
Contributor

I'd like to have a careful think about this. There are a few ways in which nested Composes in their current form have problems, and they could do with being addressed holistically. At least, I'd like to make sure it doesn't potentially affect a broader fix.

@ericspod ericspod requested a review from atbenmurray March 28, 2026 10:05
Copy link
Copy Markdown
Contributor

@atbenmurray atbenmurray left a comment

Choose a reason for hiding this comment

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

My initial feeling is that this should just work and doesn't get in the way of any future refactors.

The change to flatten is an ancillary fix that you made while changing this, yes? Might be worth expanding the tests around this for outer and inner composes having different combinations of flags, and perhaps two internal composes, one of which has it and one of which doesn't.

@aymuos15 aymuos15 force-pushed the worktree-fix-nested-compose-map-items branch from e30ac95 to bf8e78e Compare May 4, 2026 08:10
@aymuos15
Copy link
Copy Markdown
Contributor Author

aymuos15 commented May 4, 2026

@atbenmurray thank you very much for pointing out the expanded tests. Have added them now.

@aymuos15 aymuos15 force-pushed the worktree-fix-nested-compose-map-items branch from bf8e78e to f89ab6f Compare May 4, 2026 08:12
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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/compose/test_compose.py (1)

796-803: ⚡ Quick win

Some new regressions still bypass the map_items branch.

map_items only matters for list/tuple inputs. These cases use a tensor or scalar, so the pre-fix implementation would still pass. Please drive at least one inverse roundtrip and the three-level nesting case through list/tuple data, and add the same inverse coverage for RandomOrder / SomeOf, since those call sites changed too.

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

Also applies to: 880-900, 902-914

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/transforms/compose/test_compose.py` around lines 796 - 803, The test
currently uses tensor/scalar inputs so it misses the map_items branch; update
tests (e.g., test_inverse_respects_child_map_items and the other noted blocks)
to include at least one inverse roundtrip using list and tuple inputs (e.g.,
[tensor,...] and (tensor,...)) and a three-level nested Compose (Compose inside
Compose inside Compose) where the inner Compose has map_items=False to ensure
delegation to child Compose.inverse; additionally add equivalent inverse
roundtrip tests for RandomOrder and SomeOf compositions (with nested
map_items=False children) to exercise the changed call sites and ensure
list/tuple paths are covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@monai/transforms/compose.py`:
- Around line 356-368: The flattening logic currently only checks t.map_items ==
self.map_items before inlining a nested Compose, which can change behavior if
t.unpack_items differs; update the condition used where Compose instances are
inlined (the block that calls t.flatten().transforms) to require both
t.map_items == self.map_items and t.unpack_items == self.unpack_items so you
only flatten when the parent's and child's unpack_items policies match; keep
using the same Compose.flatten() call and preserve other kwargs (log_stats,
lazy, overrides) as before.

---

Nitpick comments:
In `@tests/transforms/compose/test_compose.py`:
- Around line 796-803: The test currently uses tensor/scalar inputs so it misses
the map_items branch; update tests (e.g., test_inverse_respects_child_map_items
and the other noted blocks) to include at least one inverse roundtrip using list
and tuple inputs (e.g., [tensor,...] and (tensor,...)) and a three-level nested
Compose (Compose inside Compose inside Compose) where the inner Compose has
map_items=False to ensure delegation to child Compose.inverse; additionally add
equivalent inverse roundtrip tests for RandomOrder and SomeOf compositions (with
nested map_items=False children) to exercise the changed call sites and ensure
list/tuple paths are covered.
🪄 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: 391fe130-c76e-4df0-aa45-dfa77ececb7e

📥 Commits

Reviewing files that changed from the base of the PR and between f732583 and f89ab6f.

📒 Files selected for processing (2)
  • monai/transforms/compose.py
  • tests/transforms/compose/test_compose.py

Comment thread monai/transforms/compose.py
Expand test coverage for nested Compose transforms with different
map_items configurations, and forward map_items, unpack_items,
log_stats, lazy, and overrides through Compose.flatten() so the
flattened pipeline is equivalent to the original (mirroring
OneOf.flatten behavior).

Signed-off-by: Soumya Snigdha Kundu <soumya_snigdha.kundu@kcl.ac.uk>
@aymuos15 aymuos15 force-pushed the worktree-fix-nested-compose-map-items branch from f89ab6f to 9b94999 Compare May 4, 2026 08:29
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.

Option to Disable Compose list/tuple Expansion

2 participants