@@ -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
258286def _read_cog_http (url : str , overview_level : int | None = None ,
0 commit comments