Skip to content

Fix multi-axis shear transform to compose individual shear matrices#8778

Open
aymuos15 wants to merge 3 commits intoProject-MONAI:devfrom
aymuos15:fix/shear-transform
Open

Fix multi-axis shear transform to compose individual shear matrices#8778
aymuos15 wants to merge 3 commits intoProject-MONAI:devfrom
aymuos15:fix/shear-transform

Conversation

@aymuos15
Copy link
Copy Markdown
Contributor

@aymuos15 aymuos15 commented Mar 17, 2026

Fix Shear transform

…roject-MONAI#8450)

The shear matrix was constructed by placing all coefficients into a
single identity matrix, producing a non-area-preserving transform
(determinant != 1) when multiple axes were sheared simultaneously.
This also meant that applying shears sequentially gave different
results from applying them all at once.

Fix by composing individual single-axis shear matrices via matrix
multiplication, which guarantees determinant = 1 and sequential
equivalence. Update hardcoded test expectations to match the
corrected math.

Fixes Project-MONAI#8450 Project-MONAI#8452

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

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Refactors internal shear matrix construction in monai/transforms/utils.py to compose elementary single-axis shear matrices via successive matrix multiplications instead of setting multiple off-diagonal entries directly. 2D now composes two rank-3 shears; 3D composes six elementary shears in a loop. Public API and error behavior unchanged. Tests updated: adjusted expected matrices for RandAffine/RandAffined outputs and added/updated tests verifying determinant ≈1 and sequential-equivalence of composed shears.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning Description is minimal and lacks required template sections (issue reference, types of changes, test/doc updates). Expand description to include issue reference, selection of change types, and checks for tests/docs as per template.
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: fixing shear transform by composing individual matrices instead of direct assignment.

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

@ericspod ericspod left a comment

Choose a reason for hiding this comment

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

Thanks @aymuos15 I think this looks fine, I'd like @atbenmurray to have a look as well if possible. We need to merge a fix shortly then can come back to this one.

@ericspod ericspod enabled auto-merge (squash) April 20, 2026 22:08
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

🤖 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/utils.py`:
- Around line 958-966: The Affine/AffineGrid documentation still describes
shear_params as direct off-diagonal assignments; update the Affine/AffineGrid
docstring (the Affine class and/or AffineGrid transform) to match the
composed-shear behavior: state that shear_params are composed shearing factors
(tuple of 2 floats for 2D, tuple of 6 for 3D), explain the multiplication order
used to compose single-axis shear matrices into the final affine (and that this
yields determinant 1), and give the same example matrix form (e.g., for 2D (Sx,
Sy) produce the composed matrix shown in the shear utils doc) so users
understand how parameters map to the resulting affine.
🪄 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: a3776a96-a3fc-41e5-bfaa-ba25d25359b4

📥 Commits

Reviewing files that changed from the base of the PR and between 37564b6 and 4f1be32.

📒 Files selected for processing (1)
  • monai/transforms/utils.py

Comment thread monai/transforms/utils.py
Comment on lines +958 to +966
coefs: shearing factors, a tuple of 2 floats for 2D, a tuple of 6 floats for 3D).
Individual single-axis shear matrices are composed (multiplied) in
coefficient order so that the result is a proper shear with determinant 1.
For 2D with coefs ``(Sx, Sy)`` the composed matrix is::

[
[1.0, coefs[0], coefs[1], 0.0],
[coefs[2], 1.0, coefs[3], 0.0],
[coefs[4], coefs[5], 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
[1.0, Sx, 0.0],
[Sy, 1.0 + Sx*Sy, 0.0],
[0.0, 0.0, 1.0],
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 | 🟡 Minor

Update the related Affine shear docs too.

This doc now describes composed shears, but monai/transforms/spatial/array.py:2160-2195 still documents shear_params as direct off-diagonal assignment. That will mislead users of Affine/AffineGrid.

Docs follow-up
-        shear_params: shearing factors for affine matrix, take a 3D affine as example::
+        shear_params: shearing factors for the composed shear matrix. For 3D, the six coefficients are applied
+            in coefficient order as elementary single-axis shears.

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 the current code and only fix it if needed.

In `@monai/transforms/utils.py` around lines 958 - 966, The Affine/AffineGrid
documentation still describes shear_params as direct off-diagonal assignments;
update the Affine/AffineGrid docstring (the Affine class and/or AffineGrid
transform) to match the composed-shear behavior: state that shear_params are
composed shearing factors (tuple of 2 floats for 2D, tuple of 6 for 3D), explain
the multiplication order used to compose single-axis shear matrices into the
final affine (and that this yields determinant 1), and give the same example
matrix form (e.g., for 2D (Sx, Sy) produce the composed matrix shown in the
shear utils doc) so users understand how parameters map to the resulting affine.

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.

2 participants