resample: fix dask nearest/bilinear chunk-seam values on downsampling (#2610)#2627
Merged
Merged
Conversation
The interp dask path used a fixed overlap depth of 1 input pixel for nearest/bilinear. Block-centered mapping places an output pixel's source coordinate up to ~(in/out)/2 pixels past the chunk _output_chunks assigned it to, so on a downsample the overlapped block was missing the true source row/column and map_coordinates clamped to the block edge, returning wrong values for whole chunk-seam rows. Add a per-axis downsample radius to the interp overlap depth in _run_dask_numpy and _run_dask_cupy, clamped per axis as before. Eager numpy/cupy are unaffected. Adds TestInterpDaskDownsampleSeam covering large ratios with chunks that do not divide evenly.
brendancol
commented
May 29, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
PR Review: resample dask nearest/bilinear chunk-seam fix (#2610)
Blockers (must fix before merge)
None.
Suggestions (should fix, not blocking)
None.
Nits (optional improvements)
_downsample_radiusre-importsmathat function scope._ensure_min_chunksizeand the aggregate branches already do the same local import, so this matches existing style. A module-levelimport mathwould be cleaner, but that pattern predates this PR, so leave it for a bug fix.
What looks good
- Root cause is correctly identified, and the fix mirrors the depth logic the aggregate path already uses.
_downsample_radiusreturns 0 for upsampling, so the upsample paths keep depth 1 and pay nothing. - The augmented depth feeds the
min_sizechunk-sizing, the_add_overlapcall, and the block function's origin offset from the same value, so they cannot drift apart. - The per-axis clamp against
global_in - 1is preserved, so tiny inputs and skinny axes still work. Checked on 2x2, single-chunk, and skinny-column cases. - Both
_run_dask_numpyand_run_dask_cupyare patched, so dask+numpy and dask+cupy stay in parity. Eager numpy/cupy are untouched. - Test coverage holds up: the new class fails on main and passes here, uses random data (a gradient would hide the bug), and uses chunk sizes that do not divide the input evenly. An off-band sweep of 660 ratio/size/chunk/method combinations all matched eager numpy within float32 round-off.
Checklist
- Algorithm matches the block-centered mapping; depth now covers the downsample displacement
- All four backends consistent (cupy and dask+cupy verified on a CUDA host)
- NaN handling unchanged (fix only widens overlap)
- Edge cases covered (small inputs, skinny axes, asymmetric per-axis ratios)
- Dask chunk boundaries handled correctly (the point of the fix)
- No premature materialization or extra copies
- Benchmark not needed (bug fix, no new API)
- README feature matrix already lists resample
- Docstrings present; new helper documents the radius rationale
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2610
What
nearestandbilinear. Block-centered mapping sends an output pixel to input coordinate(o + 0.5) * (in/out) - 0.5, which on a downsample can land up to ~(in/out)/2pixels past the chunk_output_chunksassigned it to. With depth 1 the overlapped block did not contain that source row/column, somap_coordinates(mode='nearest')clamped to the block edge and returned wrong values for whole chunk-seam rows._downsample_radius) to the interp overlap depth in_run_dask_numpyand_run_dask_cupy, clamped per axis exactly as before. The aggregate path already accounted for this; the interp path now matches.Backends
Test plan
TestInterpDaskDownsampleSeamcoversnearest/bilinear/cubicat ratios 0.33/0.2/0.1 with chunk layouts that do not divide the input evenly, plus an asymmetric-per-axis case. Fails onmain, passes here.test_resample.pysuite: 217 passed (includes cupy and dask+cupy parity on a CUDA host).