Skip to content

Fix viewshed() mutating the caller's input raster (#2852)#2871

Merged
brendancol merged 2 commits into
mainfrom
issue-2852
Jun 3, 2026
Merged

Fix viewshed() mutating the caller's input raster (#2852)#2871
brendancol merged 2 commits into
mainfrom
issue-2852

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Closes #2852

viewshed() was changing its caller's input raster in place:

  • The CPU path reassigned raster.data to a float64 array, so an int16 input came back as float64 after the call.
  • The CuPy fallback (cupy present, rtxpy absent) reassigned raster.data to a numpy array, silently converting the caller's CuPy input.

Changes

  • CPU path: do the float64 conversion on a local copy and feed that to the sweep kernels; raster.data is left alone.
  • CuPy fallback: build a new numpy-backed DataArray for the CPU run instead of reassigning the input's .data.
  • Add regression tests that assert the input dtype and data are unchanged after viewshed().

Backend coverage

  • numpy: covered (int16 stays int16, data unchanged)
  • dask+numpy: covered (same assertion)
  • cupy: regression test for the no-rtx CPU fallback, skipped when cupy/rtx unavailable
  • dask+cupy: not separately exercised; relies on the same dask/cupy paths

Pure bug fix, no public API change. Skipped the user-guide-notebook and README feature-matrix steps for that reason. No docs .rst change needed (no new public function).

Test plan

  • pytest xrspatial/tests/test_viewshed.py passes (65 passed, 1 skipped, 1 xfailed)
  • New test_viewshed_does_not_mutate_input (numpy, dask) passes
  • Manual repro confirms int16 input stays int16 after the call

_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.
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label Jun 2, 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: 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 old copy=False aliased 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_data instead of reassigning raster.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

@brendancol brendancol merged commit 2754e27 into main Jun 3, 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.

viewshed() mutates the caller's input raster (dtype/array-type)

1 participant