From bacfd40c4150cec43ef3415916f1c1c0332f6e67 Mon Sep 17 00:00:00 2001 From: Brendan Collins Date: Sat, 16 May 2026 05:59:54 -0700 Subject: [PATCH 1/3] geotiff: route big-endian creation through GDAL ENDIANNESS kwarg (#1930) The corpus generator passed `endian=big` to rasterio.open. rasterio intercepts that lowercase kwarg and drops it on the way to GDAL, so fixtures declared `byte_order: big` were silently written little-endian. Use the GDAL-side `ENDIANNESS=BIG` creation option instead, which rasterio forwards verbatim. Isolated to one line in `_rasterio_kwargs`; no other behaviour changes. Phase 2 PR 3 of #1930 needs this so the tiled/stripped + byte-order matrix actually exercises both endianness on disk. --- xrspatial/geotiff/tests/golden_corpus/generate.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xrspatial/geotiff/tests/golden_corpus/generate.py b/xrspatial/geotiff/tests/golden_corpus/generate.py index 823cdf44..d2da3608 100644 --- a/xrspatial/geotiff/tests/golden_corpus/generate.py +++ b/xrspatial/geotiff/tests/golden_corpus/generate.py @@ -382,8 +382,11 @@ def _rasterio_kwargs(entry: dict[str, Any]) -> dict[str, Any]: kwargs["max_z_error"] = float(max_z) if entry["byte_order"] == "big": - # GDAL endian creation option. - kwargs["endian"] = "big" + # GDAL GTiff driver ENDIANNESS creation option. rasterio forwards + # unknown uppercase kwargs to GDAL as creation options verbatim; + # the lowercase ``endian`` kwarg is intercepted by rasterio and + # silently dropped, so we route through the GDAL name directly. + kwargs["ENDIANNESS"] = "BIG" nd = entry.get("nodata") if isinstance(nd, (int, float)): From f600582936e72b9505b2a357c6decfcbcc96707d Mon Sep 17 00:00:00 2001 From: Brendan Collins Date: Sat, 16 May 2026 06:00:06 -0700 Subject: [PATCH 2/3] geotiff: golden corpus phase 2.3 - tiled/stripped + byte order (#1930) Add the four layout x byte_order fixtures from the Phase 2 plan in #1930: * stripped_le_uint16 stripped layout, little-endian * tiled_le_uint16 tiled layout (16-pixel tiles), little-endian * stripped_be_uint16 stripped layout, big-endian * tiled_be_uint16 tiled layout (16-pixel tiles), big-endian All four share a 32x32 uint16 noise raster with `pixel_seed: 1930`, `nodata: 0`, no compression, and the default EPSG:4326 georef. The only axes that vary are the two the matrix is meant to exercise. 32x32 is chosen so GDAL keeps the tiled flag (it strips the flag when the image fits in a single tile). Files are ~2.5 KB each. Includes a smoke test (`test_golden_corpus_layout_endian_1930.py`) that opens each fixture with rasterio, checks the on-disk tiled flag and byte-order magic, and round-trips the rasterio bytes through `_oracle.compare_to_oracle` as an identity check so the oracle is known to accept these fixtures ahead of Phase 3 backend wiring. --- .../fixtures/stripped_be_uint16.tif | Bin 0 -> 2434 bytes .../fixtures/stripped_le_uint16.tif | Bin 0 -> 2434 bytes .../fixtures/tiled_be_uint16.tif | Bin 0 -> 2462 bytes .../fixtures/tiled_le_uint16.tif | Bin 0 -> 2462 bytes .../geotiff/tests/golden_corpus/manifest.yaml | 63 ++++++++++++ .../test_golden_corpus_layout_endian_1930.py | 97 ++++++++++++++++++ 6 files changed, 160 insertions(+) create mode 100644 xrspatial/geotiff/tests/golden_corpus/fixtures/stripped_be_uint16.tif create mode 100644 xrspatial/geotiff/tests/golden_corpus/fixtures/stripped_le_uint16.tif create mode 100644 xrspatial/geotiff/tests/golden_corpus/fixtures/tiled_be_uint16.tif create mode 100644 xrspatial/geotiff/tests/golden_corpus/fixtures/tiled_le_uint16.tif create mode 100644 xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py diff --git a/xrspatial/geotiff/tests/golden_corpus/fixtures/stripped_be_uint16.tif b/xrspatial/geotiff/tests/golden_corpus/fixtures/stripped_be_uint16.tif new file mode 100644 index 0000000000000000000000000000000000000000..8e400483a4a468216c60b420dc5c1c091182d814 GIT binary patch literal 2434 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 BFVO%1 literal 0 HcmV?d00001 diff --git a/xrspatial/geotiff/tests/golden_corpus/fixtures/stripped_le_uint16.tif b/xrspatial/geotiff/tests/golden_corpus/fixtures/stripped_le_uint16.tif new file mode 100644 index 0000000000000000000000000000000000000000..92df6b8506dadc825de520287c96102dabfdbb37 GIT binary patch literal 2434 zcmaiwc|6qH8^^z63&V_kxyl}8zl6wcEMZcNq6jgTDP(Lzi**nhYqBKD7+YCVgsd@^ zk$p?DPWEl6Y*XFI@7~v||9Ey`o)kNT3KU0e~vc61Jk#+5O2N8N|eI2=!Rq$|<;2~h9sSO+-~H2w4xl}mmT8yJz7GT}aDbRAV`x=?>1d6UU}3I^ z`CSJeWdMlv@=rg3NB=%gkASe%zhYu7diKm+z-|VB7HW*gD=ziL?I`Ib4w^Rpbn~^* z`ts{dtBdY287Z#L_%5G;hLS$+xu?CR_Xb{{i0EE-0bc0ad**Y211)so5#cFM@`L!+ z5#{p-4^%cxk%3~w_S|l-v;aP=%y@C*B_}%t`pLO1jTy25vU_PoWYs>oqNbcJD7lv3yCd>}6buhw?DzNxzGJ>(QsV!WY-MP+ z9lGZC5e8>SPaAAAXt7&}6WV{BM`zYg*(AF`T_PG?*oOu}*fJW%RT&_&aTt8wO&ChnZyi_v|4l3=n}p6W}|+<#!Z{MOxHM=dl)4;l?qwEHQ-zjEO>SWuvW55 znZI5CLYqE3gqYJ|LX=o(q{L=m@2@u%8A?+~jAFX88>Fa}g%%^R|Ui&JRIE0Sl)Vs7L!tOA#2i|xt?yz9Ww~W;DyV#Ms zCbt1QAS(+CE)ETcvTLi*pQDI`B;l-ktFBW;>D2xCOB~X#)ob(${P33j5Vn9lCUO{Q zk&$#mBceGvHO{$bCROyanuls9@~0~Sa{brf!m61$M4RQLoLA9jq8W1__K_`}DF%)OeRQzRNU{*0{w;sbG~~pk>H@Bg@iDBMHpSiL+s%sW zrFROq*v0Xw0ZzG0MS;z=4+ftaw0^|=6yN71H6UA|RiQ&Wl24G%s=*V}LGR!4#aoU- z!1m2$ImKtccGU4+$R%pqMMb?*vrH=RUX z+rqTEIgCf+W3FO^T4t9?U1zMVi;_6EE>)yB@;&8x4-Vwt_Hscxki!~5SUi>-8jlD( zjFl-oS3Tm(>BM z_R1&7@};QwP=eWB?&+w2`B|%PZRn?%AHf!eZ;3k{T;mo`Oq2R_+=Ozsk4F_4Y zbSqyZw6Cazux8oH8vkOyr25K4I0vm3I?e9Est#%TC zP5VC5!_w>L$bD=Eo#`4aMf#LmwhzgVQ2T=gfFM}TWd>f znS4$vy6@3+nJ|Ro(k+U-;VJSAAao*E%6%LQj2eqbol~e1Re~SSp>ErBo?pJ$$I}8A zErk+%RzV=S^xJq>ZwIcj1z&8b?x1%^VgLTFI-6+jtkBf*IjX77WRs9H72SY@tn*pS zS&BGa&|K`V(Z&`Ze5@%x7mZnmyU*hmELA>f@xEWWaM8L#QeX?E(+fS`39Scrg~e?a z;1uj6jNbw=%Ua(R*le;>FQ4A4)G@ay_uF@|t#Wky>LH!$RmEP$^&%yjHrebINbNJ4LSCNoKlsdpo4L*jdVn*KA@tHWFVj`!1UK?MKO~V>+ zwbxMu!hT%h5Su22yO}lD3=6Ul3Y&vCBNy+BF3vx!UGrsV>$hnDHPEahmI)V@08b&tr^X$R~S(~74WR8bN zJ5=;N-tSw!;y*mZDKE~rO$pq+Xak4f5GC9XoIWsM;ta?2+Us&H?(t*cfg4M@kD~E! z$?ud^zipk60w)SU&k7H~jIz4ct|)_8NntBKjJ&&!hd5&?_lKEszM2{jUrC`GS7*EG zZNW?7R})DD@NRFET~O=@UrU~(cg^8#8HNlB7b*xGm-lQ>@EJMlr4&Ac!mX@%ZarS;;eeCVBVu~o zBK{F;iG0~g`o?VU^q^0lxp4O@uc_}3HbO{I?vJnG0hXvVb*Qlr<%9g@)+yH20&1GD zQgG;+>`p;}p^4F_lJyPmisD(AwSC;P8j|1hTrmFK@D+_!xoBp+j8>-B`*)200rrJ2 A$p8QV literal 0 HcmV?d00001 diff --git a/xrspatial/geotiff/tests/golden_corpus/fixtures/tiled_be_uint16.tif b/xrspatial/geotiff/tests/golden_corpus/fixtures/tiled_be_uint16.tif new file mode 100644 index 0000000000000000000000000000000000000000..6a64efaca447ede1c92c1a9d1d8e855e9f804644 GIT binary patch literal 2462 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 OZkX1>)^h8H>Hh%lA~LuD literal 0 HcmV?d00001 diff --git a/xrspatial/geotiff/tests/golden_corpus/manifest.yaml b/xrspatial/geotiff/tests/golden_corpus/manifest.yaml index ad345ff0..b4e2ce69 100644 --- a/xrspatial/geotiff/tests/golden_corpus/manifest.yaml +++ b/xrspatial/geotiff/tests/golden_corpus/manifest.yaml @@ -141,3 +141,66 @@ fixtures: atol: 0.0 rtol: 0.0 lossy: false + + # Phase 2 PR 3 (#1930): layout x byte_order cross product on a single + # small uint16 raster. Same shape, same nodata, same compression-free + # encoding across all four; the only variation is tiled-vs-stripped and + # little-vs-big endian. uint16 is the smallest dtype where byte order + # is observable on disk. + - id: stripped_le_uint16 + description: >- + 32x32 uint16 raster, stripped layout, little-endian. Baseline for + the layout x byte_order matrix. + width: 32 + height: 32 + dtype: uint16 + byte_order: little + layout: stripped + blocksize: 16 + nodata: 0 + pixel_pattern: noise + pixel_seed: 1930 + tags: [fast, stripped, little_endian, layout_endian_matrix] + + - id: tiled_le_uint16 + description: >- + 32x32 uint16 raster, tiled layout (16-pixel tiles), little-endian. + width: 32 + height: 32 + dtype: uint16 + byte_order: little + layout: tiled + tile_size: 16 + nodata: 0 + pixel_pattern: noise + pixel_seed: 1930 + tags: [fast, tiled, little_endian, layout_endian_matrix] + + - id: stripped_be_uint16 + description: >- + 32x32 uint16 raster, stripped layout, big-endian. Exercises byte + swap on read against the little-endian sibling. + width: 32 + height: 32 + dtype: uint16 + byte_order: big + layout: stripped + blocksize: 16 + nodata: 0 + pixel_pattern: noise + pixel_seed: 1930 + tags: [fast, stripped, big_endian, layout_endian_matrix] + + - id: tiled_be_uint16 + description: >- + 32x32 uint16 raster, tiled layout (16-pixel tiles), big-endian. + width: 32 + height: 32 + dtype: uint16 + byte_order: big + layout: tiled + tile_size: 16 + nodata: 0 + pixel_pattern: noise + pixel_seed: 1930 + tags: [fast, tiled, big_endian, layout_endian_matrix] diff --git a/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py new file mode 100644 index 00000000..928a1c61 --- /dev/null +++ b/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py @@ -0,0 +1,97 @@ +"""Smoke test for the layout x byte_order fixture batch (Phase 2 PR 3 of #1930). + +Verifies that the four fixtures added by PR 3 exist on disk, parse as +TIFFs with the on-disk properties the manifest promises (layout flag and +byte-order magic), and round-trip through ``_oracle.compare_to_oracle`` +as an identity check. The identity round-trip is a thin assertion that +the oracle accepts these fixtures; the real backend-vs-oracle parity +checks land in Phase 3. +""" + +from __future__ import annotations + +import importlib +import pathlib + +import numpy as np +import pytest + +pytest.importorskip("yaml") +rasterio = pytest.importorskip("rasterio") + +from xrspatial.geotiff.tests.golden_corpus import _oracle + +generate = importlib.import_module( + "xrspatial.geotiff.tests.golden_corpus.generate" +) + +FIXTURES_DIR = ( + pathlib.Path(generate.__file__).resolve().parent / "fixtures" +) + +# (id, expected on-disk tiled flag, expected first two bytes) +LAYOUT_ENDIAN_FIXTURES = [ + ("stripped_le_uint16", False, b"II"), + ("tiled_le_uint16", True, b"II"), + ("stripped_be_uint16", False, b"MM"), + ("tiled_be_uint16", True, b"MM"), +] + + +@pytest.mark.parametrize("fid,expected_tiled,expected_magic", LAYOUT_ENDIAN_FIXTURES) +def test_fixture_exists_and_is_tiff(fid, expected_tiled, expected_magic): + """File is on disk, opens with rasterio, and has the promised on-disk layout/endianness.""" + path = FIXTURES_DIR / f"{fid}.tif" + assert path.exists(), f"missing fixture {path}" + with path.open("rb") as f: + assert f.read(2) == expected_magic, ( + f"{fid}: byte-order magic mismatch (expected {expected_magic!r})" + ) + with rasterio.open(path) as src: + assert src.dtypes[0] == "uint16" + assert src.shape == (32, 32) + assert src.is_tiled is expected_tiled, ( + f"{fid}: on-disk tiled flag mismatch" + ) + + +@pytest.mark.parametrize("fid,_tiled,_magic", LAYOUT_ENDIAN_FIXTURES) +def test_fixture_round_trips_through_oracle(fid, _tiled, _magic): + """A rasterio-built DataArray of the fixture passes compare_to_oracle. + + This is an identity check: we feed the oracle the same bytes it reads + internally. It asserts only that the oracle accepts the fixture shape + (dtype, transform, CRS, nodata, pixels) for the four new variants; + Phase 3 adds the real backend-vs-oracle parity tests. + """ + xr = pytest.importorskip("xarray") + path = FIXTURES_DIR / f"{fid}.tif" + with rasterio.open(path) as src: + pixels = src.read(1) + transform = src.transform + crs = src.crs + nodata = src.nodata + # Pixel-centre coords matching what xrspatial would synthesise. + ys = np.array( + [transform.f + (i + 0.5) * transform.e for i in range(src.height)], + dtype=np.float64, + ) + xs = np.array( + [transform.c + (i + 0.5) * transform.a for i in range(src.width)], + dtype=np.float64, + ) + + da = xr.DataArray( + pixels, + dims=("y", "x"), + coords={"y": ys, "x": xs}, + attrs={ + "transform": tuple(transform)[:6], + "crs": crs.to_epsg() if crs is not None else None, + "nodata": nodata, + "dtype": str(pixels.dtype), + }, + ) + + # No exception is the assertion. + _oracle.compare_to_oracle(path, da) From e7d8fade3106347eff5185c7400a4c3c1944c337 Mon Sep 17 00:00:00 2001 From: Brendan Collins Date: Sat, 16 May 2026 06:01:54 -0700 Subject: [PATCH 3/3] geotiff: address review on layout/byte-order smoke test (#1930) * Replace rasterio's pending-deprecated ``src.is_tiled`` with ``src.profile.get("tiled")``; the profile carries the same flag without the PendingDeprecationWarning. * Add ``test_le_and_be_pixels_match`` to lock in the matrix invariant that the LE and BE siblings only differ on disk, not in decoded pixel values. --- .../test_golden_corpus_layout_endian_1930.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py index 928a1c61..9d75f86c 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py @@ -50,11 +50,36 @@ def test_fixture_exists_and_is_tiff(fid, expected_tiled, expected_magic): with rasterio.open(path) as src: assert src.dtypes[0] == "uint16" assert src.shape == (32, 32) - assert src.is_tiled is expected_tiled, ( + # ``src.is_tiled`` is pending deprecation in rasterio 1.4+; the + # profile carries the same information without the warning. + assert src.profile.get("tiled", False) is expected_tiled, ( f"{fid}: on-disk tiled flag mismatch" ) +def test_le_and_be_pixels_match(): + """The little-endian and big-endian siblings carry identical logical pixels. + + Locks in the matrix invariant that the only axis varying between LE + and BE is on-disk byte order. If a future generator change starts + seeding the two endianness siblings differently, this test catches it + before Phase 3 backends start comparing decoded values. + """ + pairs = [ + ("stripped_le_uint16", "stripped_be_uint16"), + ("tiled_le_uint16", "tiled_be_uint16"), + ] + for le_id, be_id in pairs: + with rasterio.open(FIXTURES_DIR / f"{le_id}.tif") as src: + le_pixels = src.read(1) + with rasterio.open(FIXTURES_DIR / f"{be_id}.tif") as src: + be_pixels = src.read(1) + assert np.array_equal(le_pixels, be_pixels), ( + f"{le_id} vs {be_id}: logical pixels differ; byte_order axis " + "should be the only difference" + ) + + @pytest.mark.parametrize("fid,_tiled,_magic", LAYOUT_ENDIAN_FIXTURES) def test_fixture_round_trips_through_oracle(fid, _tiled, _magic): """A rasterio-built DataArray of the fixture passes compare_to_oracle.