Validate band index in read_to_array for backend parity (#1673)#1676
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes backend parity for GeoTIFF reads by validating the band argument in the eager read_to_array path, matching the existing validation behavior in the dask and GPU implementations (issue #1673).
Changes:
- Add
bandindex validation inread_to_arrayto reject negative and out-of-range band indices with a consistentIndexErrormessage. - Add regression tests covering invalid band values and asserting parity between
read_to_arrayandread_geotiff_dask. - Preserve existing behavior for valid
bandvalues andband=None.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
xrspatial/geotiff/_reader.py |
Adds eager-path band validation to prevent negative/out-of-range indexing and align error behavior with other backends. |
xrspatial/geotiff/tests/test_band_validation_1673.py |
Adds regression tests ensuring invalid band values raise consistent IndexErrors and eager/dask parity holds. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # leaks a raw numpy ``IndexError`` with the internal slice | ||
| # shape. Mirrors the dask path's pre-flight validator (see | ||
| # ``read_geotiff_dask`` in ``__init__.py``) and the GPU path | ||
| # so all backends agree on the contract: 0-based positive |
| Two failure modes follow: | ||
|
|
||
| * ``band=-1`` silently selects the last channel via numpy negative | ||
| indexing. The public contract is "0-based positive index", so |
brendancol
added a commit
that referenced
this pull request
May 12, 2026
Addresses Copilot review feedback on PR #1676: "positive" excludes 0 in standard math usage, but band=0 is valid. Switch to "non-negative" in the three places the contract is documented (_reader.py comment, _vrt.py comment, test_band_validation_1673.py module docstring).
The local eager path applied `arr[:, :, band]` with no pre-flight
check, so `band=-1` silently selected the last channel via numpy
negative indexing and `band>=samples_per_pixel` leaked a raw numpy
IndexError with the internal slice shape.
Add the same validator the dask and GPU paths already run, raising
`IndexError("band=N out of range for M-band file.")` so all three
backends agree on the contract: 0-based positive index only.
The HTTP branch accepts `band` but does not slice on it yet; issue
#1669 will add the slice. A note in the new validator flags that
the same check should land there at that point.
Addresses Copilot review feedback on PR #1676: "positive" excludes 0 in standard math usage, but band=0 is valid. Switch to "non-negative" in the three places the contract is documented (_reader.py comment, _vrt.py comment, test_band_validation_1673.py module docstring).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1673.
Problem
xrspatial/geotiff/_reader.py:1963-1964appliedbandwith no validation:Two failure modes:
band=-1silently selected the last channel via numpy negative indexing.band=NwithN >= samples_per_pixelraised a raw numpyIndexErrorwhose message ("index N is out of bounds for axis 2 with size M") leaked the internal slice shape.The dask path (
read_geotiff_daskin__init__.py:1773-1780) and the GPU path (__init__.py:2413-2421) both validatebandup front and raiseIndexError("band=N out of range for M-band file."). The local eager path did not. Same public call, different error behaviour per backend.Fix
Add the same validator in
read_to_array, placed after the IFD is selected and the window is validated so it shares the existing parse work and rejects before any pixel allocation. The check mirrors the dask path verbatim, soread_to_arrayandread_geotiff_daskraise identicalIndexErrormessages for the same invalid input.The HTTP branch (
_read_cog_http) acceptsbandbut does not slice on it yet; #1669 will add the slice. A comment in the new validator notes that the same check should land there at that point.Tests
xrspatial/geotiff/tests/test_band_validation_1673.pyadds:band=-1,band=samples_per_pixel,band=samples_per_pixel+100against a 3-band tiled tiff. Each raisesIndexErrorwith the expectedband=N out of rangesubstring.read_to_arrayandread_geotiff_daskraise the same error type and contain the same diagnostic substring for the same invalidband.band=Nonecontinue to work unchanged.All 7 new tests fail against
mainand pass after the patch.Verification
Full geotiff suite: 1622 passed, 7 skipped. The 3
test_features::TestPalettefailures are pre-existing matplotlib deepcopy recursion issues unrelated to this change (also fail onmain).