Skip to content

Fix read_vrt(band=N) using band 0's nodata sentinel (#1598)#1602

Merged
brendancol merged 4 commits into
mainfrom
deep-sweep-metadata-geotiff-2026-05-11-1598
May 11, 2026
Merged

Fix read_vrt(band=N) using band 0's nodata sentinel (#1598)#1602
brendancol merged 4 commits into
mainfrom
deep-sweep-metadata-geotiff-2026-05-11-1598

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Closes #1598.

Summary

  • read_vrt(path, band=N) always used vrt.bands[0].nodata to populate attrs['nodata'] and drive the integer-with-nodata float64 promotion. For multi-band VRTs with per-band <NoDataValue> tags, band N's actual sentinel was ignored: attrs advertised band 0's value, the promotion mask matched band 0's value, and band N's true sentinel pixels stayed as literal integers in an integer-dtype array.
  • Source the sentinel from vrt.bands[band].nodata when a band is selected. With no band kwarg, the read still surfaces band 0's sentinel to preserve the prior multi-band "first band wins" contract.

Test plan

  • New regression suite test_vrt_band_nodata_1598.py: band=0 sanity, the broken band=1 case (advertises and masks against the correct sentinel), and the no-band-kwarg fallback.
  • Existing VRT tests still pass: test_vrt_int_nodata_1564, test_vrt_write.

read_vrt sourced attrs['nodata'] and the integer-with-nodata float64
promotion mask from vrt.bands[0].nodata regardless of which band was
selected. A multi-band VRT with per-band <NoDataValue> tags would
silently return band N's pixels with band 0's sentinel reported in
attrs and band N's actual sentinel left as literal integers in the
array (no NaN, no float64 promotion).

Use vrt.bands[band].nodata when a band is selected, fall back to
band 0 when no band kwarg was given (preserves the prior multi-band
"first band wins" contract).

Tests cover band=0 sanity, the broken band=1 case, and the
multi-band fallback.
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 11, 2026
@brendancol brendancol requested a review from Copilot May 11, 2026 18:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes xrspatial.geotiff.read_vrt(path, band=N) so that when a specific band is selected it uses that band’s <NoDataValue> to populate attrs['nodata'] and to drive the integer-with-nodata NaN promotion, instead of always using band 0’s sentinel.

Changes:

  • Update read_vrt to source nodata from vrt.bands[band] when band is provided (otherwise keep the existing “band 0 wins” behavior).
  • Add a regression test suite covering band-specific nodata handling for multi-band VRTs.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
xrspatial/geotiff/__init__.py Fixes nodata selection for read_vrt(..., band=N) so attrs and integer masking follow the selected band.
xrspatial/geotiff/tests/test_vrt_band_nodata_1598.py Adds regression tests ensuring per-band nodata is honored and masking/promotion matches expectations.
.claude/sweep-metadata-state.csv Updates sweep/inspection metadata to reflect issue #1598 status.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread xrspatial/geotiff/__init__.py Outdated
Comment on lines +2743 to +2748
band_idx_for_nodata = band if band is not None else 0
# ``_vrt.read_vrt`` already validates ``band`` is in range; the
# extra guard keeps a clearer message if a future refactor
# widens the public range without updating the internal reader.
if 0 <= band_idx_for_nodata < len(vrt.bands):
nodata = vrt.bands[band_idx_for_nodata].nodata
@brendancol
Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

brendancol and others added 3 commits May 11, 2026 11:24
- Move the ``band`` range/type check into the internal ``_vrt.read_vrt``
  reader where ``len(vrt.bands)`` is already in scope. Reject negative
  indices, non-int values, and out-of-range positive values with a
  clear ``ValueError`` that names the available band count. Previously
  these would either silently return a different band (Python negative
  indexing), raise an opaque ``IndexError`` deep in the read path, or
  raise ``TypeError`` on the list index.
- Drop the now-redundant post-parse guard in the public ``read_vrt``
  and update the comment to reflect that ``band`` is validated upstream.
- Add three regression tests covering the negative, out-of-range, and
  non-int rejection paths.

The original bug from #1598 (band=N reads pairing with band 0's nodata
sentinel) was masked by these silent acceptances on the negative-band
edge: ``read_vrt(path, band=-1)`` previously returned the last band's
data with ``attrs['nodata']`` unset entirely.
@brendancol brendancol merged commit c80d0f3 into main May 11, 2026
10 of 11 checks passed
Copilot stopped work on behalf of brendancol due to an error May 11, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

read_vrt(band=N) reports band 0's nodata in attrs and skips masking for band N

3 participants