Fix nested Compose map_items in forward and inverse paths by aymuos15 · Pull Request #8787 · Project-MONAI/MONAI · GitHub
Skip to content

Fix nested Compose map_items in forward and inverse paths - #8787

Merged
ericspod merged 13 commits into
Project-MONAI:devfrom
aymuos15:worktree-fix-nested-compose-map-items
May 11, 2026
Merged

ericspod merged 13 commits into
Project-MONAI:devfrom
aymuos15:worktree-fix-nested-compose-map-items

Conversation

@aymuos15

@aymuos15 aymuos15 commented Mar 20, 2026 •

Copy link
Copy Markdown
Contributor

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_items — flatten() 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

coderabbitai Bot commented Mar 20, 2026 •

Copy link
Copy Markdown
Contributor

@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.

🧹 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>

@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.

🧹 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
Collaborator

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

@atbenmurray atbenmurray left a comment

Copy link
Copy Markdown
Collaborator

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

aymuos15 commented May 4, 2026

Copy link
Copy Markdown
Contributor Author

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

@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/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
@ericspod
ericspod requested a review from atbenmurray May 7, 2026 11:14

@atbenmurray atbenmurray left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good from here

@ericspod
ericspod enabled auto-merge (squash) May 11, 2026 12:36
@ericspod
ericspod merged commit eb5e8e1 into Project-MONAI:dev May 11, 2026
24 checks passed
Rusheel86 pushed a commit to Rusheel86/MONAI that referenced this pull request May 25, 2026
…NAI#8787)

## Summary
Fixes Project-MONAI#7932, Project-MONAI#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
- [x] `test_child_map_items_false_receives_list` — parent
`map_items=True`, child `map_items=False`: child receives list as-is
- [x] `test_inverse_respects_child_map_items` — inverse roundtrip with
nested Compose
- [x] `test_parent_no_map_child_map` — parent `map_items=False`, child
`map_items=True`: child maps over items
- [x] `test_flatten_preserves_different_map_items` — `flatten()` does
not merge children with different `map_items`

---------

Signed-off-by: Soumya Snigdha Kundu <soumya_snigdha.kundu@kcl.ac.uk>
Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
@aymuos15
aymuos15 deleted the worktree-fix-nested-compose-map-items branch June 7, 2026 01:50
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

3 participants