Skip to content

Commit 69e60d2

Browse files
authored
Use sidecar header for remote .ovr decode byte order (#2314) (#2319)
1 parent df2331b commit 69e60d2

3 files changed

Lines changed: 386 additions & 2 deletions

File tree

xrspatial/geotiff/_backends/dask.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ def read_geotiff_dask(source: str, *,
261261
if sidecar is not None:
262262
from .._sidecar import close_sidecar
263263
close_sidecar(sidecar)
264+
# ``http_header`` carries the sidecar's ``TIFFHeader`` when
265+
# ``used_sidecar`` was True (``_parse_cog_http_meta`` swaps it
266+
# so ``byte_order`` matches the file the per-chunk range GETs
267+
# land on). Pass that through to the per-chunk decode step so a
268+
# mixed-endian base / ``.ovr`` pair decodes against the right
269+
# endianness. Issue #2314.
264270
http_meta = (http_header, http_ifd)
265271
if http_ifd.orientation != 1:
266272
raise ValueError(

xrspatial/geotiff/_cog_http.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,18 @@ def _parse_cog_http_meta(
147147
``sidecar`` and must close it; ``route_path`` is the URL/URI
148148
that per-chunk fetches should target; ``used_sidecar`` is
149149
``True`` iff the selected IFD came from the sidecar.
150+
151+
``header`` is the :class:`TIFFHeader` of the file the chosen
152+
IFD lives in: the sidecar's header when ``used_sidecar=True``,
153+
otherwise the base file's header. Callers that decode pixel
154+
bytes (the eager HTTP path, the dask chunk reader) MUST use
155+
this returned header for ``byte_order`` so the decode step
156+
interprets the right endianness; a big-endian ``.ovr`` paired
157+
with a little-endian base file would otherwise scramble the
158+
result. ``geo_info`` is still extracted from the base file's
159+
``header_bytes`` (sidecar IFDs typically carry no geokeys and
160+
inherit from the level-0 IFD that sits in the base buffer);
161+
that parse is unaffected by the swap. Issue #2314.
150162
"""
151163
if return_sidecar and source_path is None:
152164
# The 5-tuple contract guarantees ``route_path`` is a usable
@@ -242,17 +254,33 @@ def _parse_cog_http_meta(
242254
geo_info = extract_geo_info_with_overview_inheritance(
243255
ifd, ifds, header_bytes, header.byte_order,
244256
allow_rotated=allow_rotated)
257+
# When the chosen IFD lives in the sidecar, return the sidecar's own
258+
# ``TIFFHeader`` so the per-chunk / eager decode step sees the byte
259+
# order of the file the bytes actually came from. A big-endian
260+
# ``.ovr`` paired with a little-endian base (or vice versa) would
261+
# otherwise have its pixels reinterpreted with the wrong endianness
262+
# at ``_decode_strip_or_tile``. Mirrors the local sidecar path in
263+
# ``_reader.py:223`` which swaps to the sidecar header for the same
264+
# reason. Issue #2314.
265+
#
266+
# ``used_sidecar`` can only be True when ``sidecar`` is not None:
267+
# ``sidecar_ifd_ids`` is populated by ``discover_remote_sidecar``
268+
# only on the same branch that assigns ``sidecar`` (and stays empty
269+
# otherwise), so ``id(ifd) in sidecar_ifd_ids`` implies the sidecar
270+
# was loaded successfully. The branch below relies on that
271+
# invariant when it reads ``sidecar.header``.
272+
return_header = sidecar.header if used_sidecar else header
245273
if return_sidecar:
246274
route_path = sidecar.path if used_sidecar else source_path
247-
return (header, ifd, geo_info, header_bytes,
275+
return (return_header, ifd, geo_info, header_bytes,
248276
(sidecar, route_path, used_sidecar))
249277
# Caller did not opt into sidecar metadata. Close the sidecar (if
250278
# any was loaded) before returning so the buffer does not leak --
251279
# the legacy return tuple has no slot to hand it back through.
252280
if sidecar is not None:
253281
from ._sidecar import close_sidecar
254282
close_sidecar(sidecar)
255-
return header, ifd, geo_info, header_bytes
283+
return return_header, ifd, geo_info, header_bytes
256284

257285

258286
def _read_cog_http(url: str, overview_level: int | None = None,

0 commit comments

Comments
 (0)