Skip to content

Fix slicing with negative and out-of-range indices - #979

Merged
Cadair merged 4 commits into
sunpy:mainfrom
nabobalis:negative-indices
Oct 22, 2026
Merged

Cadair merged 4 commits into
sunpy:mainfrom
nabobalis:negative-indices

Conversation

@nabobalis

@nabobalis nabobalis commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

AI Assistance Disclosure

AI tools were used for:

  • Code generation (e.g., when writing an implementation or fixing a bug)
  • Test/benchmark generation
  • Documentation (including examples)
  • Research and understanding
  • No AI tools were used

Regardless of AI use, the human contributor remains fully responsible for correctness, design choices, licensing compatibility, and long-term maintainability.

@alasdairwilson alasdairwilson left a comment

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.

LGTM, astropy/astropy#20554 has fixed a lot of this but not all (doesn't raise all these errors) and I still think we make this change to avoid pinning astropy to specific bugfix releases.

@nabobalis

Copy link
Copy Markdown
Member Author

MORE UPDATES TO ASTROPY

@nabobalis nabobalis mentioned this pull request Oct 19, 2026
4 of 5 tasks
@alasdairwilson alasdairwilson added this to the 2.4.3 milestone Oct 20, 2026
Comment thread changelog/979.bugfix.rst Outdated
@@ -0,0 +1 @@
Fix slicing `~ndcube.NDCube`, `~ndcube.NDCubeSequence` (including `~ndcube.NDCubeSequence.index_as_cube`), `~ndcube.NDCollection` and `~ndcube.NDMeta` with negative or out-of-range indices, which gave wrong world coordinates or raised errors with axis-aware metadata. A failed slice no longer empties the cube's ``meta``. Slicing to a length-0 axis (e.g. ``cube[2:2]``), or with a step along the common axis of ``index_as_cube``, now raises an `IndexError`.

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.

I feel like this should be 3 separate changelogs.

Comment thread ndcube/mixins/ndslicing.py Resolved
Comment thread ndcube/ndcollection.py Resolved
np.testing.assert_array_equal(cropped.data, data[3:13, 1:6])


def _time_cube(meta=None):

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.

This and the meta that's sometimes passed to it should be fixtures.

Comment thread ndcube/meta.py Resolved
Comment thread ndcube/ndcube_sequence.py Outdated
sequence_index, common_axis_index = \
utils.sequence.cube_like_index_to_sequence_and_common_axis_indices(
item[common_axis], common_axis, common_axis_lengths)
range(sum(common_axis_lengths))[item[common_axis]], common_axis, common_axis_lengths)

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.

can you add a comment here explaining that this range()[] trick is resolving negative indexes.

Comment thread ndcube/ndcube_sequence.py Resolved
Comment thread ndcube/mixins/ndslicing.py Resolved
Comment thread ndcube/mixins/ndslicing.py Resolved
Comment thread ndcube/mixins/ndslicing.py Resolved
NDCubeSlicingMixin.__getitem__ passed negative and out-of-range indices
straight to the WCS and NDMeta slicers. Before astropy 7.2.3/8.0.2 the
WCS slicer takes negative values as offsets from 0, so cube[-1] and
cube[-2:] sliced the WCS at negative pixels; from those versions on it
raises for them when the WCS has no array_shape. NDMeta's slicer
computed shapes with the wrong sign and raised. Resolve each item
against self.shape after sanitize_slices, and size NDMeta axes with
len(range(n)[item]). The slice step is dropped: sanitize_slices only
lets a step of 1 through, and astropy before 7.2.3/8.0.2 rejects an
explicit step when slicing an already-sliced WCS, so cube[1:][:2:1]
failed.

With NDMeta fixed, NDCollection's own negative-index workaround is no
longer needed, and it raised TypeError for slices with a None bound such
as collection[-2:], so remove it. NDCubeSequence.index_as_cube now
resolves the common-axis index against the total common-axis length; it
mishandled e.g. index_as_cube[:, -11:-5] and index_as_cube[:, -1].

Refs sunpy#860 and astropy/astropy#15557.
NDCubeSlicingMixin.__getitem__ took the sliceable meta off the cube
before sanitising the item and only put it back after a successful
slice, so a failed slice such as cube[::2] or cube[0, 0, 0, 0] left the
original cube with meta=None. Strip the meta only after the item has
been resolved, and restore it in a finally block.
A WCS cannot describe an empty axis, yet slices such as cube[2:2],
cube[3:1], cube[-1:1] and cube[10:] silently returned length-0 cubes.
Raise IndexError instead, as proposed in sunpy#860. Skip the check when the
cube already has a length-0 axis, so such cubes can still be sliced as
before.

NDCubeSequence.index_as_cube ignored a step along the common axis, so
index_as_cube[:, ::2] returned every row. Raise IndexError like
NDCube slicing does.
@Cadair
Cadair merged commit b0ac3a0 into sunpy:main Oct 22, 2026
7 of 10 checks passed
@alasdairwilson

Copy link
Copy Markdown
Member

@meeseeksdev backport to 2.4

alasdairwilson added a commit that referenced this pull request Oct 23, 2026
…-on-2.4

Backport PR #979 on branch 2.4 (Fix slicing with negative and out-of-range indices)
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.

3 participants