Skip to content

Recompute res attribute for downsampled preview() output#3570

Merged
brendancol merged 2 commits into
mainfrom
issue-3569
Jun 29, 2026
Merged

Recompute res attribute for downsampled preview() output#3570
brendancol merged 2 commits into
mainfrom
issue-3569

Conversation

@brendancol

Copy link
Copy Markdown
Contributor

Closes #3569.

preview() downsamples a raster and rebuilds the x/y coordinates for the smaller grid, but it copied the input's attrs verbatim. So attrs['res'] kept describing the original full-resolution grid, and any downstream code reading res for pixel-size math or georeferencing got the wrong value.

Change

  • Add _res_from_coords() in xrspatial/preview.py to read the pixel size off the output coordinate spacing (positive magnitude, matching how templates and terrain store res).
  • Recompute res once, right before preview() returns, after the second-pass refinement has settled the final coordinates. This covers every method (mean, median, max, min, nearest, bilinear) and all four backends in one place.
  • Only touch res when the input already had one, and only when both axes have at least two coordinates. A raster without res stays without it; a no-op passthrough keeps its original.

Backends

numpy, cupy, dask+numpy, dask+cupy — verified on all four. The one combination not covered is dask+cupy + bilinear, which already fails before this change in _zoom_block (scipy zoom on a cupy block); that is a separate pre-existing bug, untouched here.

Test plan

  • res matches the returned array's coordinate spacing for all six methods
  • source-grid res does not survive a downsample
  • input attrs is not mutated
  • passthrough (no downsample) keeps the original res
  • input without res stays without res
  • dask path through snap + refinement
  • cupy path
  • full test_preview.py suite: 59 passed

preview() rebuilds the x/y coords for the smaller grid but copied attrs
verbatim, so attrs['res'] still described the full-resolution input.
Recompute res from the output coordinate spacing before returning, for
every method and backend. Leave it absent when the input had no res.

@brendancol brendancol left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PR Review: Recompute res attribute for downsampled preview() output

Blockers

None.

Suggestions

  • preview() docstring (xrspatial/preview.py:350-353) still says the output has "updated coordinates" but not updated resolution. Worth a half-sentence so readers know res now tracks the downsampled grid.

Nits

  • _res_from_coords (xrspatial/preview.py:285-300) reads spacing from the first two coordinates only. That is exact for the uniform grids preview produces (interp over a linspace, or a strided isel), so it is fine here — just noting it assumes uniform spacing.

Observations (not action items)

  • nearest reports a slightly different res than mean/bilinear (5.0 vs 5.115 in my checks). That traces to how each path builds coords: nearest strides (spacing = factor x original) while the reduce/bilinear paths interpolate across the full original extent. The PR faithfully reflects whatever coords each path emits; the coord-construction difference predates this change.
  • If an input carries res but no x/y coords, the recompute is skipped and res stays stale. That input is self-contradictory (res describes coord spacing) and there is no coordinate to measure, so leaving it alone is the right call.

What looks good

  • One recompute point after the second-pass refinement covers all six methods and all four backends, instead of patching each helper.
  • Builds a fresh attrs dict, so the input is not mutated (covered by a test).
  • Only touches res when it was present and measurable; absent-res inputs and no-op passthroughs keep their original attrs, both tested.
  • Tests assert against the returned array's real coordinate spacing rather than a hardcoded number, and confirm the stale source value does not survive.

Checklist

  • Result reflects the actual output coordinates
  • All implemented backends produce consistent results (numpy/cupy/dask+numpy/dask+cupy; bilinear+dask+cupy excluded by a pre-existing unrelated bug)
  • No input mutation
  • Edge cases covered (passthrough, absent res, dask refinement)
  • No premature materialization or unnecessary copies
  • Docstrings present (minor wording suggestion above)
  • No README/benchmark change needed (bug fix, no API or perf change)

@brendancol brendancol left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Follow-up review (after docstring commit 1227f85)

Re-checked the diff after the follow-up commit.

  • Suggestion from the first pass (document the res recompute) — fixed. The preview() Returns section now states attrs['res'] is recomputed from the output spacing when the input carried a res.
  • Nit (first-two-coords spacing) and the two observations — left as written; they describe correct, intended behavior, not defects.

No new findings. flake8 clean on preview.py; the 17 res tests still pass. No blockers.

@brendancol brendancol merged commit 5b9c5b4 into main Jun 29, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

preview() copies stale res attribute to the downsampled output

1 participant