geotiff: move no-georef signal off coord shape onto attrs marker#2124
Merged
Conversation
The writer detected the read-side no-georef placeholder by coord shape: int64 ascending step-1 on both axes meant "no georef" and skipped transform synthesis. Real user grids that happened to match the same pattern (e.g. ``x=[500,501,502], y=[1000,1001]``) lost their georef on round-trip with no warning. Move the signal off shape onto ``attrs['_xrspatial_no_georef']``. The reader stamps the marker whenever it emits placeholder coords, the writer checks the marker (only), and a user-authored int64 grid without the marker falls through to ``coords_to_transform`` and keeps its georef. The shape helper ``_is_no_georef_sentinel`` stays in the module for its existing diagnostic tests but is no longer wired into the writer path. ``test_int_coord_sentinel_2087.py`` and the corresponding 3D helper test in ``test_no_georef_writer_round_trip_1949.py`` are updated to pin the new contract. Closes #2120
brendancol
commented
May 19, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
PR Review: geotiff: move no-georef signal off coord shape onto attrs marker
Blockers (must fix before merge)
None.
Suggestions (should fix, not blocking)
-
xrspatial/geotiff/_attrs.py:173— the mid-filefrom ._coords import _NO_GEOREF_KEYwith# noqa: E402,F401works but reads oddly._geotags.pyis imported by both_attrsand_coords, so parking the constant there would let both files pick it up at the top with no noqa. -
xrspatial/geotiff/_backends/vrt.py:268— VRT's no-transform branch leavescoords = {}and skips the marker. Safe today (no x/y coords for the writer to misinterpret) but if a future change adds placeholder coords here without the marker, the silent-strip is back. Settingattrs[_NO_GEOREF_KEY] = Truedefensively in thatelsewould prevent that.
Nits (optional improvements)
-
xrspatial/geotiff/_coords.py:64—_is_no_georef_sentinelis now only called fromtest_int_coord_sentinel_2087.py. The docstring already says so, but a one-line comment near the definition ("kept for diagnostic / test pinning only") would help anyone greping for callers. -
xrspatial/geotiff/_coords.py:61—_has_no_georef_markerusesis True(identity, not truthiness). That's deliberate -- a strayattrs['_xrspatial_no_georef'] = 'yes'shouldn't flip the writer -- but an inline note would save the next reader a moment.
What looks good
- The marker resolves a genuine ambiguity. The old shape-based check was forced to guess between two cases that share a shape; the new check just asks.
- All four read paths funnel through
_populate_attrs_from_geo_info, so the marker is stamped once and propagates uniformly. iseland the other in-memory ops preserveda.attrs, so the marker survives the usual transformations between read and write.- New tests in
test_no_georef_marker_2120.pycover both directions: user grid keeps georef, explicit marker writes no-georef. - The two existing test files that pinned the old behaviour were updated to pin the new contract rather than deleted.
Checklist
- Algorithm matches reference/paper -- N/A, contract change.
- All implemented backends produce consistent results -- eager / dask / GPU share
_populate_attrs_from_geo_info. - NaN handling is correct -- unchanged by this PR.
- Edge cases covered by tests -- user grid w/o marker, real no-georef read, slicing, explicit opt-in.
- Dask chunk boundaries handled correctly -- N/A, attrs-only change.
- No premature materialization or unnecessary copies -- N/A.
- Benchmark exists or is not needed -- not needed.
- README feature matrix updated (if applicable) -- N/A.
- Docstrings present and accurate --
_has_no_georef_marker,_is_no_georef_sentinel,coords_to_transform, andrequire_transform_for_georeferencedall rewritten for the new role.
- Move ``_NO_GEOREF_KEY`` to ``_geotags`` so both ``_attrs`` and ``_coords`` import it from a shared module without the mid-file ``# noqa: E402,F401`` workaround. - Stamp the marker defensively in both VRT no-transform branches (eager + chunked). The current code leaves coords empty so the writer has nothing to misinterpret, but the marker keeps the contract uniform across read paths. - Document the ``is True`` identity check on ``_has_no_georef_marker``: only the exact boolean flips the writer, so stray third-party stamps like ``'yes'`` cannot silently drop a transform. - Add a heading comment on ``_is_no_georef_sentinel`` flagging it as test-only after #2120 -- the writer no longer calls it.
brendancol
commented
May 19, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
Follow-up: all review items addressed
Suggestions
- Fixed —
_NO_GEOREF_KEYmoved to_geotags.py. Both_attrsand_coordsnow import it at the top of the file; the mid-file noqa import is gone. - Fixed — VRT no-transform branches (eager + chunked) now stamp
attrs[_NO_GEOREF_KEY] = Truedefensively, matching the contract from_populate_attrs_from_geo_info.
Nits
- Fixed — Added a heading comment on
_is_no_georef_sentinelflagging it as kept for tests only. - Fixed — Added an inline note on
_has_no_georef_markerdocumenting that theis Trueidentity check is deliberate (stray non-boolean stamps should not flip the writer).
Full geotiff suite still green: 4222 passed / 25 skipped.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2120
Summary
x=[500,501,502], y=[1000,1001]came back as[0,1,2]with no transform.attrs['_xrspatial_no_georef']. The reader stamps the marker together with the placeholder coords; the writer checks the marker and ignores coord shape.coords_to_transform, which synthesises a unit transform and preserves CRS attrs.Backend coverage
_populate_attrs_from_geo_info, which now stamps the marker.Test plan
test_no_georef_marker_2120.pycovering: user grid keeps CRS and gains a transform; explicit-marker writes skip transform synthesis; round-trip via a real no-georef file preserves the marker.test_int_coord_sentinel_2087.pyandtest_no_georef_writer_round_trip_1949.pyto pin the new contract (shape alone is no longer the signal).xrspatial/geotiff/testssuite: 4222 passed, 25 skipped.