Reason or Problem
The aspect tests mostly check that the backends agree with each other, not that any of them is correct. The planar tests in xrspatial/tests/test_aspect.py compare NumPy, dask, CuPy, and dask+CuPy, but all four run the same _cpu/_gpu kernels. A bug shared by every backend slips through, because the backends still match.
The missing-cellsize bug in #2760 is exactly this case. Planar aspect divides the Horn gradients by 8 but never by cellsize_x/cellsize_y, so it gives wrong answers on non-square cells, yet every backend agrees with every other one.
The QGIS fixture (qgis_aspect) is a real external reference, but its raster has square cells. It never touches rectangular cells and never checks that cell size is read from the coordinates.
Proposal
Add an analytic oracle for planar aspect that does not depend on any other backend.
Take a surface with a known constant gradient, z = gx*X + gy*Y (X east, Y north). The true aspect is a closed-form compass conversion of atan2(dz_dy, -dz_dx), with the gradient components scaled by the real cell sizes read from the x/y coordinate spacing. Compare xrspatial's output against that value instead of against another backend.
The oracle should use rectangular cells (xres != yres) so it checks that cell size comes from the coordinates, which is what the missing-cellsize bug gets wrong. Run it across whatever backends are available so it still works as a parity check, but the thing being asserted is the analytic value.
Coupling with #2760
The rectangular-cell oracle fails on current main because the missing-cellsize bug (#2760) is still there. This issue is only the test, not the fix. Mark the rectangular-cell assertions xfail with strict=False and point at #2760, so they start passing on their own once that fix lands. The square-cell oracle passes now and is not xfail.
Value
This gives the module a test that checks aspect correctness directly, including the rectangular-cell case the QGIS fixture skips. The xfail doubles as a tripwire: when #2760 merges, the rectangular oracle goes green and tells you the marker can come out.
Reason or Problem
The aspect tests mostly check that the backends agree with each other, not that any of them is correct. The planar tests in xrspatial/tests/test_aspect.py compare NumPy, dask, CuPy, and dask+CuPy, but all four run the same
_cpu/_gpukernels. A bug shared by every backend slips through, because the backends still match.The missing-cellsize bug in #2760 is exactly this case. Planar aspect divides the Horn gradients by 8 but never by
cellsize_x/cellsize_y, so it gives wrong answers on non-square cells, yet every backend agrees with every other one.The QGIS fixture (
qgis_aspect) is a real external reference, but its raster has square cells. It never touches rectangular cells and never checks that cell size is read from the coordinates.Proposal
Add an analytic oracle for planar aspect that does not depend on any other backend.
Take a surface with a known constant gradient,
z = gx*X + gy*Y(X east, Y north). The true aspect is a closed-form compass conversion ofatan2(dz_dy, -dz_dx), with the gradient components scaled by the real cell sizes read from the x/y coordinate spacing. Compare xrspatial's output against that value instead of against another backend.The oracle should use rectangular cells (xres != yres) so it checks that cell size comes from the coordinates, which is what the missing-cellsize bug gets wrong. Run it across whatever backends are available so it still works as a parity check, but the thing being asserted is the analytic value.
Coupling with #2760
The rectangular-cell oracle fails on current main because the missing-cellsize bug (#2760) is still there. This issue is only the test, not the fix. Mark the rectangular-cell assertions xfail with strict=False and point at #2760, so they start passing on their own once that fix lands. The square-cell oracle passes now and is not xfail.
Value
This gives the module a test that checks aspect correctness directly, including the rectangular-cell case the QGIS fixture skips. The xfail doubles as a tripwire: when #2760 merges, the rectangular oracle goes green and tells you the marker can come out.