Summary
open_geotiff(path, gpu=True, window=...) on a TIFF with no GeoTIFF tags (no ModelPixelScale / ModelTiepoint / GeoKeyDirectory) returns float64 coords like [-0.5, -1.5, ...] synthesised from the default unit GeoTransform, instead of integer pixel coords [0, 1, 2, ...] that the eager numpy and dask paths emit.
This was fixed in #1710 for the eager numpy path (open_geotiff), the dask path (read_geotiff_dask), and the tiled GPU path (_gpu_apply_window_band). The stripped-fallback branch in read_geotiff_gpu was missed.
Reproduction
import numpy as np
from xrspatial.geotiff import open_geotiff
from xrspatial.geotiff._writer import write
arr = np.arange(64, dtype=np.float32).reshape(8, 8)
path = "/tmp/no_georef.tif"
write(arr, path, compression='none', tiled=False) # stripped, no georef
eager = open_geotiff(path, window=(0, 0, 4, 4))
dask = open_geotiff(path, chunks=4, window=(0, 0, 4, 4))
gpu = open_geotiff(path, gpu=True, window=(0, 0, 4, 4))
print(eager.y.dtype, eager.y.values) # int64 [0 1 2 3]
print(dask.y.dtype, dask.y.values) # int64 [0 1 2 3]
print(gpu.y.dtype, gpu.y.values) # float64 [-0.5 -1.5 -2.5 -3.5] <- WRONG
The eager / dask / tiled-GPU paths all guard on geo_info.has_georef. The stripped-GPU fallback in read_geotiff_gpu only checks t is None, which is never true for non-georef files because _extract_transform returns a default GeoTransform() instance with has_georef=False. The default transform has pixel_width=1.0, pixel_height=-1.0, so the PixelIsArea branch produces (c0 + 0.5) * 1.0 + 0 and (r0 + 0.5) * -1.0 + 0 -> [-0.5, -1.5, ...].
Affected backends
gpu=True stripped reads with window=...
gpu=True, chunks=... stripped reads with window=...
The tiled-GPU and CPU paths are not affected.
Severity
HIGH (Cat 2 - coords preservation). Coord dtype changes silently between the input full-read (int64) and the windowed-read on the GPU backend (float64), breaking backend parity. Downstream coord-based math (clipping, interp, xarray slicing) gets different answers depending on which backend is active.
Test failures
xrspatial/geotiff/tests/test_no_georef_windowed_coords_1710.py:
TestGpuWindowedCoords::test_windowed_read_integer_coords
TestGpuWindowedCoords::test_dask_cupy_windowed_integer_coords
TestBackendParity::test_dtype_parity_windowed
Summary
open_geotiff(path, gpu=True, window=...)on a TIFF with no GeoTIFF tags (noModelPixelScale/ModelTiepoint/GeoKeyDirectory) returns float64 coords like[-0.5, -1.5, ...]synthesised from the default unitGeoTransform, instead of integer pixel coords[0, 1, 2, ...]that the eager numpy and dask paths emit.This was fixed in #1710 for the eager numpy path (
open_geotiff), the dask path (read_geotiff_dask), and the tiled GPU path (_gpu_apply_window_band). The stripped-fallback branch inread_geotiff_gpuwas missed.Reproduction
The eager / dask / tiled-GPU paths all guard on
geo_info.has_georef. The stripped-GPU fallback inread_geotiff_gpuonly checkst is None, which is never true for non-georef files because_extract_transformreturns a defaultGeoTransform()instance withhas_georef=False. The default transform haspixel_width=1.0,pixel_height=-1.0, so the PixelIsArea branch produces(c0 + 0.5) * 1.0 + 0and(r0 + 0.5) * -1.0 + 0->[-0.5, -1.5, ...].Affected backends
gpu=Truestripped reads withwindow=...gpu=True, chunks=...stripped reads withwindow=...The tiled-GPU and CPU paths are not affected.
Severity
HIGH (Cat 2 - coords preservation). Coord dtype changes silently between the input full-read (int64) and the windowed-read on the GPU backend (float64), breaking backend parity. Downstream coord-based math (clipping, interp, xarray slicing) gets different answers depending on which backend is active.
Test failures
xrspatial/geotiff/tests/test_no_georef_windowed_coords_1710.py:TestGpuWindowedCoords::test_windowed_read_integer_coordsTestGpuWindowedCoords::test_dask_cupy_windowed_integer_coordsTestBackendParity::test_dtype_parity_windowed