Skip to content

geotiff: zero-band 3D writes silently produce a bogus single-band TIFF #2095

@brendancol

Description

@brendancol

Describe the bug

xrspatial.geotiff.to_geotiff (and the underlying write / write_streaming / GPU writers) silently writes a corrupt single-band TIFF when handed a 3D DataArray whose band axis has length 0.

_validate_writer_spatial_shape in xrspatial/geotiff/_validation.py validates the spatial height and width for 3D inputs, but does not check the band/sample dimension. A DataArray with shape (0, y, x) and dims ('band', 'y', 'x'), or shape (y, x, 0) with dims ('y', 'x', 'band'), passes validation. In _writer.py around line 1145, samples_per_pixel = first_arr.shape[2] if first_arr.ndim == 3 else 1 resolves to 0, and the file is assembled anyway around line 1198.

Reproduction

import numpy as np
import xarray as xr
from xrspatial.geotiff import to_geotiff, open_geotiff

# Band-first, zero bands
da1 = xr.DataArray(np.zeros((0, 5, 5), dtype="uint8"), dims=("band", "y", "x"))
to_geotiff(da1, "zero_bands_first.tif")
print(open_geotiff("zero_bands_first.tif").shape)  # -> (5, 5) uint8

# Band-last, zero bands
da2 = xr.DataArray(np.zeros((5, 5, 0), dtype="uint8"), dims=("y", "x", "band"))
to_geotiff(da2, "zero_bands_last.tif")
print(open_geotiff("zero_bands_last.tif").shape)  # -> (5, 5) uint8

Both calls succeed and the file round-trips as a (5, 5) single-band uint8 raster.

Expected behavior

The writer should fail closed at the public entry point with a clear message like "band/sample dimension must be positive; got shape (0, 5, 5) with 0 bands." No file should be written.

Why it matters

A clip, window, or selection that produces zero bands is a programmer error upstream. The writer treating that as "write a 2D raster of the same spatial size" is silent data fabrication. The on-disk file looks legitimate and reads back without an error from open_geotiff. Downstream consumers have no signal that the original band axis collapsed.

Scope of fix

  • Extend _validate_writer_spatial_shape (or add a peer validator) to reject bands == 0 on both layouts.
  • Wire the new check into every public writer entry point that already calls the spatial validator: to_geotiff / write / write_streaming and the GPU writer.
  • Tests for both layouts on numpy and dask inputs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    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