geotiff: reject mixed per-band SCALE/OFFSET under mask_and_scale (#2988)#2993
Merged
Conversation
brendancol
commented
Jun 6, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
PR Review: geotiff: reject mixed per-band SCALE/OFFSET under mask_and_scale (#2988)
Blockers
None.
Suggestions
xrspatial/geotiff/_attrs.py:1541-- the docstring says the band-selected case falls back to "the dataset-level value, then the default", but the code returns early on any dataset-level key (line 1555), so the per-band branch is only reached when there is no dataset-level value. When a band is selected and that band has no per-band entry, the code returnsdefault, never a dataset-level value. Trim the docstring to "that band's per-band value, else the default".
Nits
xrspatial/geotiff/_attrs.py:1553--_resolve(name, dataset_key, default)is always called withname == dataset_key('SCALE'/'SCALE', 'OFFSET'/'OFFSET'). Thedataset_keyparameter is redundant; drop it and usenamefor both lookups.
What looks good
- The fix lands in the one shared helper both read paths call, and both eager and dask now pass
band, so they reject and scale identically. The dask call resolves scale/offset at graph-build time, so the rejection raises eagerly rather than at compute, matching the eager path. - Distinctness is compared on coerced floats, not raw strings, so "2.0" and "2.00" don't trip a false mixed-metadata error.
- Tests cover mixed SCALE, mixed OFFSET, band selection on both backends, uniform per-band, and the existing dataset-level cases still pass.
- Reuses the existing
MixedBandMetadataError, consistent with the per-band nodata rejection already in the module.
Checklist
- NaN handling is correct for the masking path (unchanged)
- Both implemented backends (eager, dask) produce consistent results
- Edge cases covered by tests
- No premature materialization
- Docstring present; one overstated line noted above
- README feature matrix -- not applicable, no new public function or backend change
brendancol
commented
Jun 6, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
Follow-up review (after ee02548)
Both findings from the first pass are addressed:
- Suggestion (docstring overstated the band-selected fallback): fixed. The docstring now reads "that band's per-band value (or the default when that band carries none)", which matches the code.
- Nit (redundant
dataset_keyparameter on_resolve): fixed._resolve(name, default)now usesnamefor both the dataset-level and per-band lookups.
No new findings. The 12 mask_and_scale tests still pass. Nothing else outstanding.
# Conflicts: # xrspatial/geotiff/_attrs.py # xrspatial/geotiff/tests/read/test_rioxarray_compat_2961.py
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 #2988
mask_and_scale=Trueread a multi-band raster with one(scale, offset)pair, falling back to band 0's per-band values when there was no dataset-level value. Bands with a different scale/offset came back numerically wrong while the attrs still looked valid, so downstream spatial functions had no signal that the data was corrupt.This makes
_extract_scale_offsetband-aware:SCALE/OFFSETstill applies to the whole array.MixedBandMetadataErrorwhen no band is selected, mirroring the existing per-band nodata handling. Selecting a single band withband=reads it with that band's own scale/offset.Both the eager (numpy) and dask read paths thread the selected band into the helper, so they reject and scale identically.
Backend coverage: CPU eager (numpy) and dask.
mask_and_scalealready raisesValueErrorwithgpu=Trueand on.vrtsources, so cupy / VRT paths are unaffected.Test plan:
band=selects that band's scale/offset (eager and dask agree)