Skip to content

Fix hillshade gradient to use Horn's method (#1175)#1178

Merged
brendancol merged 3 commits into
masterfrom
issue-1175
Apr 9, 2026
Merged

Fix hillshade gradient to use Horn's method (#1175)#1178
brendancol merged 3 commits into
masterfrom
issue-1175

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

  • Switch _run_numpy and _gpu_calc_numba from np.gradient (2-point central difference) to Horn's 8-neighbor Sobel kernel, matching what slope and aspect already use
  • Remove the incorrect comment at hillshade.py:40 that claimed GDAL parity
  • Rewrite test_hillshade_gdal_equivalence to validate against a Horn's method reference instead of np.gradient
  • Add test_hillshade_horn_vs_central_diff to verify the two gradient methods actually produce different results on noisy terrain (max diff > 0.01)

Context

hillshade was computing gradients with np.gradient while slope and aspect used Horn's method. On noisy DEMs this caused up to 0.1 shading-unit divergence from GDAL. Smooth data was unaffected (diff < 0.001) since the two kernels converge when neighbors are similar.

The dask backends (_run_dask_numpy, _run_dask_cupy) call into the numpy/cupy chunk functions, so they pick up the fix automatically.

Test plan

  • pytest xrspatial/tests/test_hillshade.py -- all 56 tests pass (numpy, cupy, dask+numpy, dask+cupy, boundary modes, RTX shadows)
  • test_hillshade_gdal_equivalence confirms Horn reference match (atol=1e-6)
  • test_hillshade_horn_vs_central_diff confirms old method no longer matches

hillshade was using np.gradient (2-point central difference) for
gradients while slope and aspect already used Horn's 8-neighbor Sobel
kernel. This caused measurable divergence from GDAL on noisy terrain
(up to 0.1 shading units on rough DEMs).

Replace np.gradient in _run_numpy with an @ngjit Horn kernel matching
slope.py. Update the CUDA kernel (_gpu_calc_numba) with the same
8-neighbor stencil. Both dask backends inherit the fix automatically
through their chunk functions.

Update test_hillshade_gdal_equivalence to validate against Horn's
method reference instead of np.gradient. Add
test_hillshade_horn_vs_central_diff to confirm the two methods
produce meaningfully different results on noisy data.
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label Apr 8, 2026
@brendancol brendancol merged commit 871a51c into master Apr 9, 2026
11 checks passed
@brendancol brendancol deleted the issue-1175 branch May 4, 2026 19:48
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.

1 participant