Skip to content

geotiff: apply TIFF Orientation tag on HTTP COG full reads (#1717)#1724

Merged
brendancol merged 1 commit into
mainfrom
issue-1717
May 12, 2026
Merged

geotiff: apply TIFF Orientation tag on HTTP COG full reads (#1717)#1724
brendancol merged 1 commit into
mainfrom
issue-1717

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

_read_cog_http's full-read branch returned the raw decoded buffer without running _apply_orientation, so the same file came back with a different pixel order over HTTP than locally. This pulls the orientation + geo_info update into a helper that both paths call.

Repro

import tifffile, numpy as np
from xrspatial.geotiff._reader import read_to_array, _read_cog_http

arr = np.arange(48, dtype=np.uint8).reshape(6, 8)
tifffile.imwrite("oriented.tif", arr, extratags=[(274, 'H', 1, 4, True)])

local, _ = read_to_array("oriented.tif")
http, _ = _read_cog_http("http://.../oriented.tif")
assert np.array_equal(local, http)  # fails before this PR

Fix

New _apply_orientation_with_geo(arr, geo_info, orientation) in _reader.py; read_to_array and _read_cog_http both call it. The windowed-read + non-default-orientation rejection is unchanged on both paths.

Tests

xrspatial/geotiff/tests/test_http_orientation_1717.py covers orientations 1-8 via the same in-memory loopback server pattern as test_cog_http_concurrent.py. Full local vs HTTP reads match (array and transform), windowed HTTP reads against non-default orientation still raise ValueError.

16 new tests pass. test_cog_http_concurrent.py, test_orientation.py, test_orientation_gpu.py, and the other test_http_* suites still pass.

Closes #1717

@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 12, 2026
@brendancol brendancol requested a review from Copilot May 12, 2026 19:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a backend-parity bug in the GeoTIFF reader where full HTTP COG reads (_read_cog_http) returned the decoded pixel buffer without applying the TIFF Orientation tag (274), causing different pixel order (and potentially different geospatial transform interpretation) compared to local reads.

Changes:

  • Apply the Orientation tag handling to the HTTP full-read path so HTTP and local reads return consistent arrays and GeoInfo.
  • Factor shared orientation + GeoInfo.transform update logic into a new _apply_orientation_with_geo(...) helper.
  • Add a new HTTP parity test suite covering orientations 1–8 and ensuring windowed HTTP reads still reject non-default orientation.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
xrspatial/geotiff/_reader.py Adds a shared orientation+georef helper and uses it in both local and HTTP read paths.
xrspatial/geotiff/tests/test_http_orientation_1717.py New regression tests to ensure HTTP full reads match local reads for all orientation values and preserve the windowed-read rejection behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +99 to +120
arr_local, geo_local = read_to_array(str(path))

httpd, port = _serve(payload)
try:
url = f'http://127.0.0.1:{port}/orient_{orientation}.tif'
arr_http, geo_http = _read_cog_http(url)
finally:
httpd.shutdown()
httpd.server_close()

assert arr_http.shape == arr_local.shape, (
f"orientation={orientation}: HTTP shape {arr_http.shape} != "
f"local shape {arr_local.shape}"
)
np.testing.assert_array_equal(
arr_http, arr_local,
err_msg=f"orientation={orientation}: HTTP pixels differ from local",
)
assert geo_http.transform == geo_local.transform, (
f"orientation={orientation}: transform mismatch "
f"http={geo_http.transform} local={geo_local.transform}"
)
The HTTP path in `_read_cog_http` returned the raw decoded buffer for
full reads, skipping the `_apply_orientation` remap that the local path
runs in `read_to_array`. Opening the same file locally vs over HTTP
produced different pixel orders and transforms for any Orientation tag
value other than 1.

Extract the orientation + geo_info update into
`_apply_orientation_with_geo` and call it from both paths. The existing
rejection of windowed reads against non-default orientation is kept
unchanged on both paths.

Tests cover orientations 1-8 via local + HTTP round-trip against the
same in-memory loopback server used by `test_cog_http_concurrent.py`,
plus a regression check that windowed HTTP reads still raise on
non-default orientation.
@brendancol brendancol merged commit 75324c1 into main May 12, 2026
10 of 11 checks passed
@brendancol brendancol deleted the issue-1717 branch May 15, 2026 04:41
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.

geotiff: HTTP COG full reads skip TIFF Orientation tag

2 participants