Fix viewshed() mutating the caller's input raster (#2852)#2871
Merged
Conversation
_viewshed_cpu reassigned raster.data to a float64 array, so an int16 input came back as float64 after the call. The CuPy fallback (no rtxpy) reassigned raster.data to a numpy array, converting the caller's CuPy input in place. Do the float64 conversion on a local copy in the CPU path, and build a new DataArray for the CuPy fallback instead of mutating the input. Add regression tests asserting the input dtype and data are unchanged after viewshed() on the numpy, dask, and CuPy-fallback backends.
brendancol
commented
Jun 2, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
PR Review: Fix viewshed() mutating the caller's input raster (#2852)
Blockers (must fix before merge)
None.
Suggestions (should fix, not blocking)
None.
Nits (optional improvements)
- viewshed.py:1583: the CPU path now always copies via
astype(np.float64)(default copy=True), even when the input is already float64. That is the safe choice here, since the float64 case is exactly where the oldcopy=Falsealiased the caller's buffer. Cost is one HxW copy. No change needed, just noting the tradeoff is deliberate.
What looks good
- Root cause fixed in both reported spots. The CPU path uses a local
raster_datainstead of reassigningraster.data, and the CuPy fallback builds a fresh DataArray rather than swapping the input's.data. - The new DataArray in the CuPy fallback carries coords, dims, and attrs across.
- Checked the sweep kernels (
_init_event_list,_viewshed_cpu_sweep): they only read from the raster array, so the float64 copy is not guarding against in-place writes, it purely protects the caller. - The dask Tier B path was already safe (it works on
raster.copy()), and the new dask regression test pins that contract end to end. - Tests assert both dtype and array contents are unchanged on numpy and dask, plus a cupy-fallback test guarded on cupy-present / rtx-absent.
Checklist
- Algorithm unchanged (pure input-mutation fix)
- All implemented backends produce consistent results
- NaN handling unaffected
- Edge cases: int16 input covered; mutation contract is the focus
- Dask chunk boundaries unaffected (output rebuilt from copy)
- No premature materialization or unnecessary copies (one deliberate float64 copy)
- Benchmark not needed (no perf-critical change)
- README feature matrix not applicable (no new function/backend)
- Docstrings present and accurate
# Conflicts: # xrspatial/viewshed.py
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 #2852
viewshed()was changing its caller's input raster in place:raster.datato a float64 array, so an int16 input came back as float64 after the call.raster.datato a numpy array, silently converting the caller's CuPy input.Changes
raster.datais left alone..data.viewshed().Backend coverage
Pure bug fix, no public API change. Skipped the user-guide-notebook and README feature-matrix steps for that reason. No docs
.rstchange needed (no new public function).Test plan
pytest xrspatial/tests/test_viewshed.pypasses (65 passed, 1 skipped, 1 xfailed)test_viewshed_does_not_mutate_input(numpy, dask) passes