Skip to content

mask_and_scale=True silently corrupts multi-band rasters with differing per-band SCALE/OFFSET #2988

@brendancol

Description

@brendancol

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggeotiffGeoTIFF moduleinput-validationInput validation and error messages

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions