Skip to content

Exclude centre cell from morph_erode/dilate when kernel[centre]==0 (#1397)#1398

Merged
brendancol merged 1 commit intomainfrom
issue-1397
May 1, 2026
Merged

Exclude centre cell from morph_erode/dilate when kernel[centre]==0 (#1397)#1398
brendancol merged 1 commit intomainfrom
issue-1397

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

  • Fix centre-cell leak in morph_erode and morph_dilate when the structuring element has a 0 at the centre. Previously the running min/max was seeded from the centre cell before the kernel loop, so the centre still influenced the result even when the kernel excluded it.
  • Drop the centre seed. Assign from the first kernel-included neighbour and compare against subsequent ones. NaN now only propagates when a NaN neighbour is actually included by the kernel.
  • Apply the same fix in all four backends: _erode_kernel_numpy, _dilate_kernel_numpy, _erode_gpu, _dilate_gpu.

Fixes #1397.

Test plan

  • Existing morphology tests pass (35 tests)
  • New tests cover centre-zero kernel behaviour for erode and dilate
  • New tests confirm NaN at the excluded centre does not propagate
  • Cross-backend parity tests for dask and cupy on centre-zero kernels
  • pytest xrspatial/tests/test_morphology.py xrspatial/tests/test_morphology_derived.py passes (70 tests)

…1397)

The numpy and CUDA kernel functions seeded the running min/max from the
centre cell before iterating the structuring element. When the kernel
had a 0 at the centre, the loop skipped the centre cell but the seed
value already came from it, so the centre still contaminated the
result.

Fix: drop the centre seed and assign from the first kernel-included
neighbour, then compare against subsequent ones. NaN propagation now
only fires when a NaN neighbour is actually included by the kernel.
Cells where the kernel covers no non-zero entries return NaN.

Affects all four backends (numpy, cupy, dask+numpy, dask+cupy).
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 1, 2026
@brendancol brendancol merged commit 44248f6 into main May 1, 2026
11 checks passed
brendancol added a commit that referenced this pull request May 1, 2026
…1401)

Three modules audited 2026-04-30.

- fire: clean (per-pixel ops only; NaN guards via x!=x; CUDA bounds
  use strict <; division guarded; CPU/GPU/dask paths line-for-line
  equivalent; no CRIT/HIGH/MEDIUM).

- terrain_metrics: LOW only (Inf input not rejected, propagates;
  dask+cupy non-nan boundary path double-pads with consistent output).

- morphology: HIGH x 2 fixed.
  - #1397 / PR #1398: morph_erode/dilate seeded centre cell into the
    running min/max even when kernel[centre]==0, contaminating the
    result with the excluded pixel (all four backends).
  - #1399 / PR #1400: _morph_chunk_numpy and _morph_chunk_cupy used
    result[hy:-hy, hx:-hx] which becomes the empty slice 0:-0 for
    1xN or Nx1 kernels, raising ValueError on the dask backends
    while numpy and cupy paths handled them.
@brendancol brendancol deleted the issue-1397 branch May 4, 2026 13:04
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.

morphology: centre-cell leak in erode/dilate when kernel[centre]==0

1 participant