Describe the bug
open_geotiff(..., mask_and_scale=True) can silently corrupt a multi-band raster when the source carries per-band SCALE/OFFSET that differ between bands.
_extract_scale_offset in xrspatial/geotiff/_attrs.py returns a single (scale, offset) pair: it prefers the dataset-level SCALE/OFFSET and falls back to band 0's per-band values, then applies that one pair to the whole array. When bands have different scale/offset, every band other than band 0 is scaled with the wrong factor. The attrs still look valid (scale_factor / add_offset are stamped), so downstream spatial functions consume numerically wrong values with no signal that anything is off.
This is documented as a limitation in the mask_and_scale docstring today, but for a read pipeline that feeds spatial ops, silent wrong numbers are a data-corruption bug, not just a caveat.
Expected behavior
When mask_and_scale=True and the source carries differing per-band SCALE/OFFSET:
- If a single band is selected with
band=, apply that band's scale/offset.
- If no band is selected (the full multi-band array is returned), raise
MixedBandMetadataError instead of silently applying band 0's values. This mirrors the existing per-band nodata handling, which already rejects conflicting per-band sentinels by default and tells the caller to disambiguate.
A source with a single dataset-level scale/offset, or uniform per-band values, keeps working as before.
Additional context
Both the eager (numpy/cupy) read path and the dask read path call _extract_scale_offset, so the fix has to be applied consistently across both.
Describe the bug
open_geotiff(..., mask_and_scale=True)can silently corrupt a multi-band raster when the source carries per-band SCALE/OFFSET that differ between bands._extract_scale_offsetinxrspatial/geotiff/_attrs.pyreturns a single(scale, offset)pair: it prefers the dataset-levelSCALE/OFFSETand falls back to band 0's per-band values, then applies that one pair to the whole array. When bands have different scale/offset, every band other than band 0 is scaled with the wrong factor. The attrs still look valid (scale_factor/add_offsetare stamped), so downstream spatial functions consume numerically wrong values with no signal that anything is off.This is documented as a limitation in the
mask_and_scaledocstring today, but for a read pipeline that feeds spatial ops, silent wrong numbers are a data-corruption bug, not just a caveat.Expected behavior
When
mask_and_scale=Trueand the source carries differing per-band SCALE/OFFSET:band=, apply that band's scale/offset.MixedBandMetadataErrorinstead of silently applying band 0's values. This mirrors the existing per-band nodata handling, which already rejects conflicting per-band sentinels by default and tells the caller to disambiguate.A source with a single dataset-level scale/offset, or uniform per-band values, keeps working as before.
Additional context
Both the eager (numpy/cupy) read path and the dask read path call
_extract_scale_offset, so the fix has to be applied consistently across both.