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
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