Skip to content

Fix PixelIsPoint overview coord offset (#1642)#1645

Merged
brendancol merged 1 commit into
mainfrom
deep-sweep-accuracy-geotiff-2026-05-11-run4-01
May 12, 2026
Merged

Fix PixelIsPoint overview coord offset (#1642)#1645
brendancol merged 1 commit into
mainfrom
deep-sweep-accuracy-geotiff-2026-05-11-run4-01

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

Why

PR #1641 (issue #1640) made overview reads inherit the level-0
GeoKeys / ModelPixelScale / ModelTiepoint, but it kept origin_x and
origin_y unchanged. The default PixelIsArea convention says the
origin is the upper-left corner of pixel (0, 0), so that's correct
there. PixelIsPoint (GeoKey 1025 = 2) says the origin is the
center of pixel (0, 0). An overview pixel covering the first
scale_x columns of level 0 has its center at the centroid of those
level-0 pixels, so the inherited origin needs a shift.

The bug silently snapped coords by half an overview pixel on every
DEM-style PixelIsPoint COG (USGS, OpenTopography, Copernicus DEM all
emit RasterPixelIsPoint). da.sel, da.interp, and downstream
reproject/hillshade/slope chains pick up the wrong pixel position
without raising.

Before / after on a 1024x1024 PixelIsPoint COG with 10 m pixels and
origin (0, 0):

level0 x[:3]: [ 0. 10. 20.]
# before
level1 x[:3]: [ 0. 20. 40.]
# after
level1 x[:3]: [ 5. 25. 45.]   # centroid of level-0 0,1 = 5
level2 x[:3]: [15. 55. 95.]   # centroid of level-0 0..3 = 15
level3 x[:3]: [35. 115. 195.] # centroid of level-0 0..7 = 35

Test plan

Notes

The 13 new tests cover all four backends (numpy, dask+numpy, cupy,
dask+cupy) for both PixelIsPoint and a PixelIsArea regression
check, plus three unit-level helper tests via stubbed
extract_geo_info that exercise the math without going through the
writer/reader pipeline.

Found by /sweep-accuracy pass 18 on the geotiff module.

PR #1641 (issue #1640) inherits level-0 georef on overview reads but
keeps the level-0 origin_x / origin_y unchanged. That is correct for
PixelIsArea -- origin is the upper-left corner of pixel (0, 0), which
is also the upper-left corner of the overview's pixel (0, 0). It is
wrong for PixelIsPoint (GeoKey 1025 = 2): the origin is the center of
pixel (0, 0), and an overview pixel covering the first scale_x columns
of level 0 has its center at the centroid of those level-0 pixels
(origin + (scale - 1) * 0.5 * pixel_size_lvl0).

Before this fix, open_geotiff on a 1024x1024 PixelIsPoint COG with
10 m pixels and origin (0, 0) returned x[:3] = [0, 20, 40] for
overview_level=1 instead of [5, 25, 45]. Downstream sel / interp /
reproject silently snaps to the wrong pixel for any DEM-style
PixelIsPoint COG (USGS, OpenTopography, Copernicus DEM).

Fix in extract_geo_info_with_overview_inheritance: choose the
effective raster_type first (overview's own when it explicitly
declared non-default, otherwise inherit from level 0), then apply
origin_shift = (scale - 1) * 0.5 * pixel_size_lvl0 along each axis
when that effective raster_type is PixelIsPoint. The PixelIsArea path
is byte-equivalent to before.

Add 13 regression tests in test_overview_pixel_is_point_1642.py:
centroid identity across all four backends, transform-tuple values
across all four backends, uniform grid step, unit-level helper tests
for both raster_types via stubbed extract_geo_info, an own-geokeys-
not-clobbered case on PixelIsPoint, and a PixelIsArea regression
check so the #1640 contract still holds. All 1397 existing non-network
geotiff tests still pass.
@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 00:35
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

Fixes GeoTIFF overview georeferencing for raster_type=PixelIsPoint by adjusting the inherited origin so overview pixel centers align with the centroid of the level-0 pixels they aggregate (closing #1642). This keeps the PixelIsArea behavior unchanged while making overview reads accurate for DEM-style PixelIsPoint COGs.

Changes:

  • Update extract_geo_info_with_overview_inheritance to apply a (scale - 1) * 0.5 * pixel_size_lvl0 origin shift when the effective raster type is PixelIsPoint.
  • Add comprehensive regression tests covering all four read backends and both raster-type paths.
  • Update .claude/sweep-accuracy-state.csv to record the sweep result for #1642.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
xrspatial/geotiff/_geotags.py Applies a raster-type-dependent origin shift when inheriting level-0 georef for overview IFDs.
xrspatial/geotiff/tests/test_overview_pixel_is_point_1642.py New end-to-end + unit-level regression tests validating correct PixelIsPoint overview coords/transform across backends.
.claude/sweep-accuracy-state.csv Records the sweep-accuracy pass note for issue #1642.

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

@brendancol brendancol merged commit 2ca76db into main May 12, 2026
15 of 16 checks passed
@brendancol brendancol deleted the deep-sweep-accuracy-geotiff-2026-05-11-run4-01 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.

open_geotiff overview_level coord offset wrong for PixelIsPoint COGs

2 participants