Skip to content

reproject: make transform_precision=0 force exact pyproj transforms#2654

Merged
brendancol merged 4 commits into
mainfrom
issue-2646
May 29, 2026
Merged

reproject: make transform_precision=0 force exact pyproj transforms#2654
brendancol merged 4 commits into
mainfrom
issue-2646

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Closes #2646

Problem

The reproject docstring says transform_precision=0 produces exact per-pixel pyproj transforms matching GDAL/rasterio. The code didn't honor that. Every backend tried the Numba/CUDA fast path before checking the flag, so for CRS pairs the fast path supports (WGS84/NAD83 <-> UTM, WGS84 <-> Web Mercator) the exact pyproj branch was never reached. The escape hatch silently did nothing on exactly the projections where users are most likely to reach for it.

Fix

Gate the fast path on transform_precision != 0 at all four call sites:

  • _transform_coords (numba)
  • _reproject_chunk_numpy (numba)
  • _reproject_chunk_cupy (CUDA)
  • the dask+cupy streaming loop in _reproject_dask_cupy (CUDA)

When transform_precision == 0, every backend now falls through to the per-pixel pyproj transform.

Backend coverage

numpy, dask+numpy go through _reproject_chunk_numpy / _transform_coords. cupy and dask+cupy go through the CUDA-gated paths. All four are covered.

Test plan

  • New TestExactPrecisionEscapeHatch verifies transform_precision=0 matches pyproj-exact coordinates and never calls try_numba_transform (spy raises if it does).
  • Confirmed the numba-skip tests fail on the pre-fix code and pass after.
  • Full test_reproject.py suite passes (386 tests).

GPU assertions run only where a CUDA runtime is present; the CI host without a GPU skips them, so the CUDA path fix is covered by code review plus the shared gating logic rather than an executed GPU test.

Dedupe duplicate module rows (last-write-wins by last_inspected) and
collapse multi-line notes to single physical lines. The notes had
embedded newlines, which the merge=union .gitattributes strategy splits
record-by-record, corrupting the file into a 156-column phantom row on
parallel-agent appends. One line per record keeps union merges safe.
…2646)

The docstring promises transform_precision=0 produces exact per-pixel
pyproj transforms, but every backend tried the Numba/CUDA fast path
before checking the flag. For CRS pairs the fast path supports
(WGS84/NAD83 <-> UTM, WGS84 <-> Web Mercator) the exact branch was
never reached, so the escape hatch did nothing.

Gate the fast path on transform_precision != 0 in all four call sites:
_transform_coords, _reproject_chunk_numpy, _reproject_chunk_cupy, and
the dask+cupy streaming loop.
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 29, 2026
Copy link
Copy Markdown
Contributor Author

@brendancol brendancol left a comment

Choose a reason for hiding this comment

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

PR Review: reproject: make transform_precision=0 force exact pyproj transforms

Blockers

None.

Suggestions

None.

Nits

  • The four gates repeat the precision != 0 check inline rather than sharing a helper. For four call sites that already had their own fast-path attempt block, inline is the right call and matches the surrounding style. Noting it so it reads as a deliberate choice, not an oversight.

What looks good

  • The fix covers all four fast-path entry points, not just the two named in the issue. The CUDA paths (_reproject_chunk_cupy, line 480; _reproject_dask_cupy streaming, line 1655) had the same bug and are now gated too.
  • The regression tests bite. test_transform_coords_precision_zero_skips_numba and its two siblings install a spy that raises if try_numba_transform runs, and they fail on the pre-fix code (verified by stashing the source change). test_numba_fast_path_active_for_this_pair is a sanity check so the regression can't pass vacuously on a CRS pair the fast path doesn't support.
  • test_transform_coords_precision_zero_matches_pyproj checks the exact path against an independent pyproj reference to 1e-6, so it confirms correct coordinates, not just that numba was skipped.
  • Pure guard additions. No change to NaN handling, output dtype, or the transform math.

Checklist

  • Algorithm matches the documented contract (docstring line 692: transform_precision=0 -> exact per-pixel pyproj)
  • All implemented backends gated consistently (numpy, dask+numpy via _reproject_chunk_numpy/_transform_coords; cupy and dask+cupy via the CUDA paths)
  • NaN handling unchanged
  • Edge cases: exact-path coordinate match tested against pyproj reference
  • Dask chunk boundaries: unchanged, fix is inside the per-chunk transform
  • No premature materialization or extra copies
  • Benchmark: not needed, this is a correctness fix on an existing function
  • README feature matrix: not applicable, no new function or backend change
  • Docstrings present and accurate (the fix makes the code match the existing docstring)

CUDA-path assertions only run where a CUDA runtime is present, so the GPU gating is covered by review plus shared logic rather than an executed GPU test on the CI host.

@brendancol brendancol merged commit ccf31d1 into main May 29, 2026
7 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.

reproject: transform_precision=0 does not force exact pyproj transforms

1 participant