Skip to content

open_geotiff windowed reads of non-georef TIFFs produce float64 half-pixel-shifted coords #1710

@brendancol

Description

@brendancol

Description

A TIFF with no GeoTIFF tags (no ModelPixelScale, no ModelTiepoint, no GeoKeys) gets different y/x coordinates depending on whether window= is passed.

Full read takes the has_georef=False branch in _geo_to_coords and returns integer pixel coords [0, 1, 2, ...] with int64 dtype.

Windowed read of the same file skips _geo_to_coords entirely and synthesises coords from the default GeoTransform(origin=0, pixel_width=1, pixel_height=-1). That produces float64 coords like [-0.5, -1.5, -2.5, ...] because the PixelIsArea half-pixel shift gets applied to a unit transform that was never real.

Every read path has the same split: open_geotiff, read_geotiff_dask, read_geotiff_gpu, and dask+cupy.

Reproduction

import os, tempfile
import numpy as np
from xrspatial.geotiff import open_geotiff
from xrspatial.geotiff._writer import write

with tempfile.TemporaryDirectory() as tmp:
    arr = np.arange(64, dtype=np.float32).reshape(8, 8)
    path = os.path.join(tmp, "no_georef.tif")
    write(arr, path, compression="none", tiled=False)

    full = open_geotiff(path)
    win = open_geotiff(path, window=(0, 0, 4, 4))

    print(full.y.dtype, full.y.values[:4])  # int64 [0 1 2 3]
    print(win.y.dtype,  win.y.values)       # float64 [-0.5 -1.5 -2.5 -3.5]

Expected

Windowed reads of a non-georef TIFF should match the full-read convention: integer pixel indices [r0, r0+1, ..., r1-1] for y and [c0, c0+1, ..., c1-1] for x, dtype int64.

The DataArray also should not carry a synthetic attrs['transform'] for a file with no GeoTIFF tags. A non-georef file should round-trip through to_geotiff without picking up a fabricated identity transform.

Affected code

  • xrspatial/geotiff/__init__.py open_geotiff lines 695-707 (eager numpy windowed coord)
  • xrspatial/geotiff/__init__.py read_geotiff_dask lines 1839-1864 (dask windowed coord)
  • xrspatial/geotiff/__init__.py _gpu_apply_window_band lines 2271-2298 (GPU windowed coord)
  • xrspatial/geotiff/__init__.py _populate_attrs_from_geo_info lines 438-451 (transform attr emission)

Severity

MEDIUM. Only triggers for TIFFs without GeoTIFF tags, which are rare in spatial workflows. But the silent int64-to-float64 shift and the half-pixel coord offset between full and windowed reads of the same file can quietly break downstream coord-based math.

Found by

Metadata propagation sweep, 2026-05-12.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions