Skip to content

fix(data): avoid divide-by-zero in pydicom affine for single-slice volumes#8956

Open
SID-6921 wants to merge 1 commit into
Project-MONAI:devfrom
SID-6921:fix/pydicom-affine-single-slice-zero-div
Open

fix(data): avoid divide-by-zero in pydicom affine for single-slice volumes#8956
SID-6921 wants to merge 1 commit into
Project-MONAI:devfrom
SID-6921:fix/pydicom-affine-single-slice-zero-div

Conversation

@SID-6921

Copy link
Copy Markdown

Summary:

  • fix PydicomReader._get_affine to skip lastImagePositionPatient z-step derivation when there is only one slice
  • add regression tests for single-slice and multi-slice metadata paths

Why:
For single-slice 3D DICOM segmentation metadata, n == 1 caused division by zero in affine z-step computation.

Validation:

  • python -m pytest tests/data/test_init_reader.py -k pydicom_reader_get_affine -q
  • python -m pre_commit run --files monai/data/image_reader.py tests/data/test_init_reader.py

Closes #8925

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8a8a6d17-2f12-4792-8d53-6db89827dff7

📥 Commits

Reviewing files that changed from the base of the PR and between a8d8ca6 and 6140c73.

📒 Files selected for processing (1)
  • tests/data/test_init_reader.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/data/test_init_reader.py

📝 Walkthrough

Walkthrough

Two unit tests are added to tests/data/test_init_reader.py for PydicomReader._get_affine. Both tests skip without Pydicom, construct metadata containing lastImagePositionPatient and MetaKeys.SPATIAL_SHAPE, invoke _get_affine(..., lps_to_ras=False), and assert the Z-axis affine entries (affine[0,2], affine[1,2], affine[2,2]). One test uses a single-slice case (n=1), the other a multi-slice case.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The linked issue requires guarding the affine calculation in monai/data/image_reader.py, but this PR only adds tests. Restore the code change that skips lastImagePositionPatient z-step derivation when n == 1, then keep the regression tests.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the divide-by-zero fix for single-slice pydicom affine handling.
Description check ✅ Passed The description is on-topic and includes summary, rationale, validation, and issue closure, though it does not match the template exactly.
Out of Scope Changes check ✅ Passed The changes are limited to regression tests for the reported affine bug and do not introduce unrelated scope.
✨ 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.

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
monai/data/image_reader.py (1)

729-771: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the new _get_affine fallback.

This definition changed, but its docstring still omits a Returns section and does not say that lastImagePositionPatient is ignored unless MetaKeys.SPATIAL_SHAPE[-1] > 1. Please document that single-slice inputs keep the default third column. As per path instructions, "Docstrings should be present for all definition which describe each variable, return value, and raised exception in the appropriate section of the Google-style of docstrings."

🤖 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/data/image_reader.py` around lines 729 - 771, The _get_affine docstring
is missing the new fallback behavior and return documentation. Update the
Google-style docstring for _get_affine to include a Returns section for the
affine matrix, and note that lastImagePositionPatient is only used when
MetaKeys.SPATIAL_SHAPE[-1] > 1; for single-slice inputs the default third column
remains unchanged.

Source: Path instructions

🤖 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 `@tests/data/test_init_reader.py`:
- Around line 104-135: Add Google-style docstrings to the two new test methods
in PydicomReader’s test class:
test_pydicom_reader_get_affine_single_slice_with_last_position and
test_pydicom_reader_get_affine_multi_slice_uses_last_position. Briefly describe
the purpose of each test, the key metadata variables they set up, and the
expected affine assertions in the Args/Returns/Raises sections as appropriate,
matching the project’s docstring requirements for test definitions.
- Around line 120-134: The PydicomReader._get_affine multi-slice test is too
weak because the current lastImagePositionPatient and MetaKeys.SPATIAL_SHAPE
values produce the same affine result as the default branch, so it does not
prove the special path executed. Update
test_pydicom_reader_get_affine_multi_slice_uses_last_position to use a
lastImagePositionPatient value and spatial shape that yield a non-default k3
term, then assert that affine[2, 2] (or the full third column) reflects the
branch-specific result so the test fails if _get_affine skips the
lastImagePositionPatient logic.

---

Outside diff comments:
In `@monai/data/image_reader.py`:
- Around line 729-771: The _get_affine docstring is missing the new fallback
behavior and return documentation. Update the Google-style docstring for
_get_affine to include a Returns section for the affine matrix, and note that
lastImagePositionPatient is only used when MetaKeys.SPATIAL_SHAPE[-1] > 1; for
single-slice inputs the default third column remains unchanged.
🪄 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: 2ddf1ea8-6e46-466f-b496-8d91deca2125

📥 Commits

Reviewing files that changed from the base of the PR and between 4ba89bd and 4a51549.

📒 Files selected for processing (2)
  • monai/data/image_reader.py
  • tests/data/test_init_reader.py

Comment on lines +104 to +135
def test_pydicom_reader_get_affine_single_slice_with_last_position(self):
reader = PydicomReader()
metadata = {
"00200037": {"Value": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]},
"00200032": {"Value": [10.0, 20.0, 30.0]},
"00280030": {"Value": [0.5, 0.25]},
"lastImagePositionPatient": np.array([10.0, 20.0, 30.0]),
MetaKeys.SPATIAL_SHAPE: np.array([64, 64, 1]),
}

affine = reader._get_affine(metadata, lps_to_ras=False)

np.testing.assert_allclose(affine[0, 2], 0.0)
np.testing.assert_allclose(affine[1, 2], 0.0)
np.testing.assert_allclose(affine[2, 2], 1.0)

def test_pydicom_reader_get_affine_multi_slice_uses_last_position(self):
reader = PydicomReader()
metadata = {
"00200037": {"Value": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]},
"00200032": {"Value": [0.0, 0.0, 0.0]},
"00280030": {"Value": [1.0, 1.0]},
"lastImagePositionPatient": np.array([0.0, 0.0, 4.0]),
MetaKeys.SPATIAL_SHAPE: np.array([8, 8, 5]),
}

affine = reader._get_affine(metadata, lps_to_ras=False)

np.testing.assert_allclose(affine[0, 2], 0.0)
np.testing.assert_allclose(affine[1, 2], 0.0)
np.testing.assert_allclose(affine[2, 2], 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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add docstrings to the new tests.

Both new test defs are missing Google-style docstrings. As per path instructions, "Docstrings should be present for all definition which describe each variable, return value, and raised exception in the appropriate section of the Google-style of docstrings."

🤖 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/data/test_init_reader.py` around lines 104 - 135, Add Google-style
docstrings to the two new test methods in PydicomReader’s test class:
test_pydicom_reader_get_affine_single_slice_with_last_position and
test_pydicom_reader_get_affine_multi_slice_uses_last_position. Briefly describe
the purpose of each test, the key metadata variables they set up, and the
expected affine assertions in the Args/Returns/Raises sections as appropriate,
matching the project’s docstring requirements for test definitions.

Source: Path instructions

Comment on lines +120 to +134
def test_pydicom_reader_get_affine_multi_slice_uses_last_position(self):
reader = PydicomReader()
metadata = {
"00200037": {"Value": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]},
"00200032": {"Value": [0.0, 0.0, 0.0]},
"00280030": {"Value": [1.0, 1.0]},
"lastImagePositionPatient": np.array([0.0, 0.0, 4.0]),
MetaKeys.SPATIAL_SHAPE: np.array([8, 8, 5]),
}

affine = reader._get_affine(metadata, lps_to_ras=False)

np.testing.assert_allclose(affine[0, 2], 0.0)
np.testing.assert_allclose(affine[1, 2], 0.0)
np.testing.assert_allclose(affine[2, 2], 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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the multi-slice fixture prove the branch ran.

With lastImagePositionPatient = [0, 0, 4] and MetaKeys.SPATIAL_SHAPE[-1] = 5, _get_affine() computes (k1, k2, k3) == (0, 0, 1), which is exactly the same as the pre-branch defaults. This test still passes if the lastImagePositionPatient path is skipped entirely.

Proposed fix
     def test_pydicom_reader_get_affine_multi_slice_uses_last_position(self):
         reader = PydicomReader()
         metadata = {
             "00200037": {"Value": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]},
             "00200032": {"Value": [0.0, 0.0, 0.0]},
             "00280030": {"Value": [1.0, 1.0]},
-            "lastImagePositionPatient": np.array([0.0, 0.0, 4.0]),
+            "lastImagePositionPatient": np.array([2.0, 4.0, 8.0]),
             MetaKeys.SPATIAL_SHAPE: np.array([8, 8, 5]),
         }

         affine = reader._get_affine(metadata, lps_to_ras=False)

-        np.testing.assert_allclose(affine[0, 2], 0.0)
-        np.testing.assert_allclose(affine[1, 2], 0.0)
-        np.testing.assert_allclose(affine[2, 2], 1.0)
+        np.testing.assert_allclose(affine[0, 2], 0.5)
+        np.testing.assert_allclose(affine[1, 2], 1.0)
+        np.testing.assert_allclose(affine[2, 2], 2.0)
📝 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
def test_pydicom_reader_get_affine_multi_slice_uses_last_position(self):
reader = PydicomReader()
metadata = {
"00200037": {"Value": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]},
"00200032": {"Value": [0.0, 0.0, 0.0]},
"00280030": {"Value": [1.0, 1.0]},
"lastImagePositionPatient": np.array([0.0, 0.0, 4.0]),
MetaKeys.SPATIAL_SHAPE: np.array([8, 8, 5]),
}
affine = reader._get_affine(metadata, lps_to_ras=False)
np.testing.assert_allclose(affine[0, 2], 0.0)
np.testing.assert_allclose(affine[1, 2], 0.0)
np.testing.assert_allclose(affine[2, 2], 1.0)
def test_pydicom_reader_get_affine_multi_slice_uses_last_position(self):
reader = PydicomReader()
metadata = {
"00200037": {"Value": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]},
"00200032": {"Value": [0.0, 0.0, 0.0]},
"00280030": {"Value": [1.0, 1.0]},
"lastImagePositionPatient": np.array([2.0, 4.0, 8.0]),
MetaKeys.SPATIAL_SHAPE: np.array([8, 8, 5]),
}
affine = reader._get_affine(metadata, lps_to_ras=False)
np.testing.assert_allclose(affine[0, 2], 0.5)
np.testing.assert_allclose(affine[1, 2], 1.0)
np.testing.assert_allclose(affine[2, 2], 2.0)
🤖 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/data/test_init_reader.py` around lines 120 - 134, The
PydicomReader._get_affine multi-slice test is too weak because the current
lastImagePositionPatient and MetaKeys.SPATIAL_SHAPE values produce the same
affine result as the default branch, so it does not prove the special path
executed. Update test_pydicom_reader_get_affine_multi_slice_uses_last_position
to use a lastImagePositionPatient value and spatial shape that yield a
non-default k3 term, then assert that affine[2, 2] (or the full third column)
reflects the branch-specific result so the test fails if _get_affine skips the
lastImagePositionPatient logic.

Comment thread monai/data/image_reader.py Outdated

# 3d
if "lastImagePositionPatient" in metadata:
if "lastImagePositionPatient" in metadata and metadata[MetaKeys.SPATIAL_SHAPE][-1] > 1:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @SID-6921 I'm afraid we just merged another PR that addressed this issue. Could you please look at the current state of the repo and see if that satisfies the issue? If so we can drop the change in this file but keep any of your tests that add value. Thanks!

@SID-6921

Copy link
Copy Markdown
Author

Thanks for the heads-up. I synced with current dev and removed the duplicate change in monai/data/image_reader.py from this PR. I kept the regression tests in ests/data/test_init_reader.py (single-slice and multi-slice affine metadata coverage), which still add value. Local validation passes for the updated PR scope.

…etadata

Signed-off-by: SID <nandasiddhardha@gmail.com>
@SID-6921 SID-6921 force-pushed the fix/pydicom-affine-single-slice-zero-div branch from a8d8ca6 to 6140c73 Compare June 30, 2026 04:25
@SID-6921

Copy link
Copy Markdown
Author

Update done: I rebased the PR branch onto current dev and force-pushed it so the duplicate image_reader.py fix is no longer part of this PR. The PR now keeps only the added regression coverage in ests/data/test_init_reader.py (single-slice and multi-slice affine metadata cases, guarded by optional Pydicom availability).

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.

fix: division by zero in DICOMReader._get_affine for single-slice 3D volumes

2 participants