Skip to content

geotiff: write_geotiff_gpu drops crs_wkt, gdal_metadata, extra_tags, and other attrs #1563

@brendancol

Description

@brendancol

Describe the bug

write_geotiff_gpu silently drops most metadata attrs when writing a CuPy-backed DataArray:

  • crs_wkt (the WKT-only CRS fallback when no EPSG is recognized)
  • gdal_metadata / gdal_metadata_xml
  • extra_tags
  • image_description
  • extra_samples
  • colormap
  • x_resolution, y_resolution, resolution_unit

It also ignores attrs['transform'], recomputing the GeoTransform from coords via _coords_to_transform. That re-derivation drifts on fractional pixel sizes (e.g. 1 arc-second) because x[1] - x[0] is taken from already-rounded float64 coords. The CPU writer reads attrs['transform'] first and only falls back to _coords_to_transform if absent.

The CRS loss is the worst part. When the file's CRS only resolves to WKT (no recognized EPSG), attrs['crs'] is unset and attrs['crs_wkt'] carries the only CRS info. write_geotiff_gpu reads data.attrs.get('crs') but never 'crs_wkt', so the output has no GeoKeys at all and other readers see an ungeoreferenced file.

Repro

import numpy as np, cupy, xarray as xr, tempfile, os
from xrspatial.geotiff import open_geotiff, write_geotiff_gpu
from xrspatial.geotiff._geotags import _epsg_to_wkt

with tempfile.TemporaryDirectory() as d:
    wkt = _epsg_to_wkt(4326)
    arr = np.arange(64, dtype=np.float32).reshape(8, 8)
    da_gpu = xr.DataArray(
        cupy.asarray(arr), dims=['y','x'],
        coords={'y': np.arange(8.0), 'x': np.arange(8.0)},
        attrs={'crs_wkt': wkt, 'image_description': 'demo',
               'extra_samples': [1]},
    )
    out = os.path.join(d, 'gpu.tif')
    write_geotiff_gpu(da_gpu, out, compression='none')
    print(sorted(open_geotiff(out).attrs.keys()))
    # -> ['transform']  (crs/crs_wkt/image_description/extra_samples all lost)

The corresponding CPU to_geotiff call preserves crs, crs_wkt, extra_samples, extra_tags, gdal_metadata, gdal_metadata_xml, image_description, and transform.

Expected behavior

write_geotiff_gpu should follow the same attrs-resolution logic as the CPU path in to_geotiff: prefer attrs['transform'] over coord-derived transforms, resolve crs from int EPSG, WKT string, or crs_wkt, and forward gdal_metadata*, extra_tags, image_description, extra_samples, colormap, and the resolution tags to the underlying _assemble_tiff writer.

Why this matters

Round-tripping a GPU-read DataArray through to_geotiff (which auto-dispatches to GPU for CuPy data) silently strips CRS-WKT and rich tags. The to_geotiff -> open_geotiff round-trip is documented as preserving these attrs on the CPU path, and #1548 wired up the read side to emit the same keys across all backends; the write side is the remaining asymmetry.

Scope

xrspatial/geotiff/__init__.py::write_geotiff_gpu (the kwargs to _assemble_tiff).

Additional context

Found during the metadata propagation sweep on the geotiff subpackage.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggpuCuPy / CUDA GPU support

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions