Repository navigation
fix(wcs): reject negative slice start in WCS.slice - #20323
IMGillusion wants to merge 3 commits into
Conversation
A negative slice start (e.g. ``wcs[-6:]``) was silently interpreted as a pixel offset into the unsliced WCS, so slicing produced nonsensical world coordinates instead of raising. Reject it with an ``IndexError``, consistent with the negative-index handling added for SlicedLowLevelWCS. Closes astropy#8771.
|
Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.
|
astrofrog
left a comment
There was a problem hiding this comment.
Small comment below, this is ok otherwise
| @@ -0,0 +1,5 @@ | |||
| Fixing the slicing of a high-level ``WCS`` with a negative slice start (for | |||
| example ``wcs[-6:]``) now raises ``IndexError`` instead of silently | |||
There was a problem hiding this comment.
| example ``wcs[-6:]``) now raises ``IndexError`` instead of silently | |
| example ``wcs[-6:]``). This now raises ``IndexError`` instead of silently |
There was a problem hiding this comment.
Done — applied as suggested (d7e5ded). Thanks for the quick pass!
astrofrog
left a comment
There was a problem hiding this comment.
The following review comment was generated by Claude Fable, and I agree with the points raised (I suggested some of them)
Thanks! This is the same situation as #20320 though, so I would apply the same reasoning here: rather than rejecting negative starts unconditionally, we should support them properly when the shape is known and only raise when it is not. The case is even stronger on this path, because WCS.slice already uses the shape to resolve negative stops correctly, via nitems = len(range(self._naxis[wcs_index])[iview]) a few lines below your check. The only thing actually broken when _naxis is known is the crpix/distortion-shift arithmetic: today wcs[:, :, -30:] on a WCS with pixel_shape = (100, 100, 50) produces the correct _naxis of 30 but shifts crpix by +30 instead of -70, so the returned WCS is internally inconsistent rather than obviously wrong. So instead of the up-front loop, I would normalize each slice per axis just before the crpix arithmetic:
n = self._naxis[wcs_index]
if n > 0:
iview = slice(*iview.indices(n))
elif iview.start is not None and iview.start < 0:
raise IndexError(
"Negative indices are not supported when slicing a WCS with unknown shape (NAXIS not set)."
)slice.indices(n) returns a non-negative start/stop and preserves the step, so it should compose with the existing downsampling branch (negative steps are already rejected earlier), and since the SIP crpix and distortion table crval shifts all use iview.start, normalizing before the arithmetic fixes them in the same place. For a header-derived WCS, NAXIS* is set, so wcs[-6:, -6:] would then slice the WCS the same way numpy slices the data, and the error only remains for WCS objects that genuinely
have no shape information.
One more reason to keep the two PRs in sync: as soon as the view contains an integer index (e.g. wcs[0, -6:]), slice silently reroutes to SlicedFITSWCS, i.e. the code path #20320 touches. If the two adopt different policies, the same slice will behave differently depending on whether a dimension is dropped, so whatever we converge on there (see my
comment on that PR) should be mirrored here with the same semantics and error message.
For tests, I would add: negative start with pixel_shape set gives results identical to the equivalent positive slice (including crpix and array_shape); negative start without shape information raises IndexError; and a case with a SIP or distortion-corrected WCS to confirm the auxiliary shifts come out right.
Note the phrase "see my comment on that PR" assumes the #20320 comment is posted first (or around the same time) — post them in that order, or drop the parenthetical if you only comment here.
Description
WCS.slice(and thereforewcs[...]) silently misinterpreted a negativeslice start as a pixel offset into the unsliced WCS. For example, with
crpix=[5,5],crval=[0,1],cdelt=[1,1]:This is the legacy high-level
WCS.slicecode path; the wcsapiSlicedLowLevelWCSwrapper rejects negative starts since #15557. This PRmakes the two paths consistent: a negative slice start (or a negative start
combined with a step) now raises
IndexErrorwith a message explainingwhy, instead of producing garbage. This closes #8771, whose reporter listed
"raise an exception if it gets a negative index" as one of the acceptable
solutions.
Motivation and context
Related issue number: #8771
Checklist
about a code review request?
Tests
Added
test_slice_getitem_negative_start_raisesinastropy/wcs/tests/test_utils.py. Verified locally: with the pristinewcs.pythe new test fails with "DID NOT RAISE" (reproducing #8771),after the fix it passes; the full
test_utils.pyslice family shows noregression (only pre-existing network-dependent failures unrelated to this
change).
AI Disclosure
An AI coding agent assisted with this pull request: it helped diagnose the issue from the linked report, draft the code fix and the regression test, and write this PR description. The change was validated locally against the project's own test suite (red/green runs plus full-module regression) before submission.