Skip to content

Fix Crop/CropOrPad silently leaving a fully-cropped axis uncropped - #1541

Open
SID-6921 wants to merge 1 commit into
TorchIO-project:mainfrom
SID-6921:fix-crop-entire-axis-silently-uncropped
Open

SID-6921 wants to merge 1 commit into
TorchIO-project:mainfrom
SID-6921:fix-crop-entire-axis-silently-uncropped

Conversation

@SID-6921

Copy link
Copy Markdown

Bug

Crop.apply_transform (src/torchio/transforms/spatial/crop.py) and _crop_image_lazy (src/torchio/transforms/spatial/crop_or_pad.py) both build slice stops as:

si - i1 or None

si - i1 already equals si (an open-ended stop, same as None) when i1 == 0. The or None doesn't help that case — but it also converts a stop of exactly 0 into None. A stop of 0 happens when i1 == si, i.e. the user crops an amount equal to the axis's full size (intending to remove the whole axis). slice(i0, None) means "to the end," the opposite of the intended empty slice — the axis is silently left completely uncropped instead of becoming length 0. No error, no warning.

Reproduction:

import torch, torchio as tio
image = tio.ScalarImage(torch.rand(1, 20, 20, 20))
subject = tio.Subject(t1=image)
result = tio.Crop(cropping=(0, 20, 0, 0, 0, 0))(subject)
print(result.t1.shape)  # (1, 20, 20, 20) -- should be (1, 0, 20, 20)

Fix

Remove the unneeded or None: si - i1 is correct as a literal stop value in every case (open-ended, partial, and full-axis crop alike), since slicing to si on an axis of length si is identical to slicing to None.

Tests

Added one test per affected path:

  • test_crop_entire_axis_from_the_end / test_crop_entire_axis_from_the_end_all_axes (tests/test_crop.py) for the eager Crop.apply_transform path.
  • test_crop_image_lazy_entire_axis_from_the_end (tests/test_crop_or_pad.py) for the lazy _crop_image_lazy path.

Mutation-verified: reverting either file's fix back to ... or None fails exactly the new test(s) for that file, with the rest of tests/test_crop.py and tests/test_crop_or_pad.py (84 tests total) still green. Full tests/test_crop.py + tests/test_crop_or_pad.py suite: 84 passed. Broader -k "crop or pad or spatial" slice: 252 passed.

Crop.apply_transform and _crop_image_lazy built slice stops as
`si - i1 or None`. `si - i1` is already `si` (equivalent to an
open-ended stop) when i1 == 0, so the `or None` was never needed for
that case -- but it also converts a stop of exactly 0 (cropping an
amount equal to the axis's full size, i.e. i1 == si) into None, which
means "slice to the end" instead of the intended empty slice. The
whole axis was silently left completely uncropped instead of becoming
length 0, with no error or warning.

Fixed by removing the unneeded `or None`: `si - i1` is correct as a
literal stop value in every case (open-ended crop, partial crop, and
full-axis crop alike).

Added a test for both the eager (Crop.apply_transform) and lazy
(_crop_image_lazy) paths. Mutation-verified: reverting to `or None`
in either file fails exactly the new tests for that file.
Copilot AI balanced review requested due to automatic review settings October 21, 2026 18:12

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@SID-6921

SID-6921 commented Nov 2, 2026

Copy link
Copy Markdown
Author

Checking in on this one — still green and mergeable, no review yet. Happy to make any further changes if needed.

This branch has not been deployed

No deployments
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