Skip to content

geotiff: accept numpy integer CRS values at writers (#2082)#2085

Merged
brendancol merged 2 commits into
mainfrom
issue-2082
May 19, 2026
Merged

geotiff: accept numpy integer CRS values at writers (#2082)#2085
brendancol merged 2 commits into
mainfrom
issue-2082

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

  • Widen the writer-side isinstance(crs, int) checks to numbers.Integral so numpy integer scalars (np.int32, np.int64, np.uint16) hit the EPSG branch instead of falling through with no CRS written.
  • Add regression tests that round-trip the CRS through to_geotiff (path + BytesIO), the VRT-tiled branch, and write_geotiff_gpu, checking attrs['crs'] resolves to the expected EPSG instead of just asserting bytes hit disk.

Background

_validate_crs_arg at _crs.py:87 accepts numbers.Integral. The writer paths checked isinstance(crs, int), which is False for numpy integers. So to_geotiff(..., crs=np.int64(4326)) passed validation, fell through both writer branches, and produced a file with no EPSG and no crs_wkt. Reading the file back returned a DataArray with no CRS, silent metadata loss.

Three sites:

  • xrspatial/geotiff/_writers/eager.py:540 (eager to_geotiff)
  • xrspatial/geotiff/_writers/eager.py:871 (deprecated _write_vrt_tiled)
  • xrspatial/geotiff/_writers/gpu.py:349 (write_geotiff_gpu)

The VRT writer path goes through _resolve_crs_to_wkt, which already coerces numbers.Integral, so no change there.

The existing test_to_geotiff_accepts_numpy_int_epsg only asserted buf.getbuffer().nbytes > 0. A file with no CRS still has bytes, so that test passed even with the bug in place. The new tests check attrs['crs'] after round-trip.

Backend coverage

  • numpy: covered, fixed in eager.py
  • dask: same eager.py entry point, covered
  • cupy: fixed in gpu.py, regression test is cuda-gated
  • dask+cupy: same GPU writer entry point

Closes #2082.

Test plan

  • pytest xrspatial/geotiff/tests/test_numpy_int_crs_2082.py -- 9 pass (GPU test skipped without cuda)
  • pytest xrspatial/geotiff/tests/test_crs_arg_validation_1971.py xrspatial/geotiff/tests/test_conflicting_crs_write_1987.py -- 34 pass, no regressions
  • pytest xrspatial/geotiff/tests/ -k "crs or write" -- 845 pass, 1 pre-existing failure in test_size_param_validation_gpu_vrt_1776 unrelated to this change

@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 18, 2026
_validate_crs_arg already accepts numbers.Integral (so np.int32 /
np.int64 / np.uint16 pass), but the writers gated EPSG assignment
on isinstance(crs, int). Numpy integers failed that check and the
value silently fell through with no EPSG and no crs_wkt written;
read-back returned a DataArray with no CRS at all.

Widen the three writer-side checks (eager.py:540, eager.py:871,
gpu.py:349) to numbers.Integral and coerce to int(crs). Validator
already rejects bool so the writer branch is safe from the bool
subclass slip. The VRT writer path goes through _resolve_crs_to_wkt
which already coerces, so no change there.

Add regression tests that round-trip np.int32 / np.int64 / np.uint16
through to_geotiff (path + BytesIO), the VRT-tiled branch, and
write_geotiff_gpu (skipped without cuda). Tests check that
attrs['crs'] resolves to the expected EPSG, not just that bytes
got written -- the pre-existing test_to_geotiff_accepts_numpy_int_epsg
only asserted nbytes > 0, which passed even with no CRS in the file.
@brendancol brendancol merged commit aa4972f into main May 19, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: np.int64 CRS silently dropped by GeoTIFF writers

1 participant