Summary
write_geotiff_gpu does not handle band-first (band, y, x) 3D
DataArrays the way the CPU to_geotiff path does. The CPU writer
checks data.dims[0] in ('band', 'bands', 'channel') and moveaxis to
(y, x, band) (see __init__.py:1037). The GPU writer skips that
step entirely and treats arr.shape[2] as the band axis regardless of
dim names, so a rioxarray-style (band, y, x) CuPy DataArray is
written with the band axis stored as image width and the actual width
stored as samples-per-pixel.
to_geotiff(...) auto-dispatches to write_geotiff_gpu when the
input is CuPy-backed, so the same caller code that round-trips fine on
numpy silently produces a transposed, unreadable file on GPU.
Reproduction
import numpy as np, xarray as xr, cupy
from xrspatial.geotiff import open_geotiff, write_geotiff_gpu
arr = np.random.default_rng(0).integers(0, 255, (3, 16, 32), dtype=np.uint8)
da = xr.DataArray(
cupy.asarray(arr),
dims=['band', 'y', 'x'],
coords={'band': np.arange(3),
'y': np.arange(16, dtype=np.float64),
'x': np.arange(32, dtype=np.float64)},
attrs={'crs': 4326},
)
write_geotiff_gpu(da, '/tmp/bandfirst.tif', compression='none')
rd = open_geotiff('/tmp/bandfirst.tif')
print(rd.sizes) # Frozen({'y': 3, 'x': 16, 'band': 32}) — wrong
Expected: Frozen({'y': 16, 'x': 32, 'band': 3}).
Fix
Mirror the moveaxis branch from the CPU eager path before computing
height, width = arr.shape[:2]. Same condition: 3D and
data.dims[0] in ('band', 'bands', 'channel').
Found by the metadata-propagation sweep (Cat 3 + Cat 5).
Summary
write_geotiff_gpudoes not handle band-first(band, y, x)3DDataArrays the way the CPU
to_geotiffpath does. The CPU writerchecks
data.dims[0] in ('band', 'bands', 'channel')and moveaxis to(y, x, band)(see__init__.py:1037). The GPU writer skips thatstep entirely and treats
arr.shape[2]as the band axis regardless ofdim names, so a rioxarray-style
(band, y, x)CuPy DataArray iswritten with the band axis stored as image width and the actual width
stored as samples-per-pixel.
to_geotiff(...)auto-dispatches towrite_geotiff_gpuwhen theinput is CuPy-backed, so the same caller code that round-trips fine on
numpy silently produces a transposed, unreadable file on GPU.
Reproduction
Expected:
Frozen({'y': 16, 'x': 32, 'band': 3}).Fix
Mirror the moveaxis branch from the CPU eager path before computing
height, width = arr.shape[:2]. Same condition: 3D anddata.dims[0] in ('band', 'bands', 'channel').Found by the metadata-propagation sweep (Cat 3 + Cat 5).