Skip to content

fix: centre the Gaussian kernel and correct the CPU Scharr x-kernel - #58

Open
Hendrik-code wants to merge 1 commit into
hm/fix-spatial-samplingfrom
hm/fix-kernels
Open

fix: centre the Gaussian kernel and correct the CPU Scharr x-kernel#58
Hendrik-code wants to merge 1 commit into
hm/fix-spatial-samplingfrom
hm/fix-kernels

Conversation

@Hendrik-code

Copy link
Copy Markdown
Collaborator

Stacked on #57 — review that one first; this PR's diff is only its own commit.

Two convolution kernels that were not what their names say. Both silent: the pipeline runs, nothing raises, the output is simply not the operation the config asked for.

The Gaussian was not centred

get_gaussian_kernel1d sampled at arange(kernel_size) — 0, 1, 2 — putting the peak at index 0 rather than the centre tap. get_gaussian_kernel3d is the outer product of three of those, so its maximum sat at corner [0, 0, 0].

RandomGaussianBlurGPU and RandomUnsharpMaskGPU therefore blurred and translated the image by about a voxel. The segmentation mask is never convolved, so it did not move with it — the two came out misaligned. The kernel is now sampled on a centred linspace(-half, half, k).

The 2D CPU Scharr x-kernel was not a gradient operator

kernel_x = torch.tensor([[-3, 0, 3], [-10, 0, -10], [-3, 0, 3]])   # sums to -20
kernel_y = torch.tensor([[-3, -10, -3], [0, 0, 0], [3, 10, 3]])    # sums to 0

The middle row should be [-10, 0, 10]. Summing to −20 means it responds to constant regions. kernel_y on the very next line has always been correct, which is what marks this as a typo rather than an intentional variant. The 3D tables are unaffected.

Testing

unit_tests/test_kernel_correctness.py, 13 tests / 23 subtests. 18 checks fail against the parent commit, verified by stashing the source change and re-running.

The one that captures why the blur bug mattered:

def test_blurring_an_impulse_leaves_its_centre_of_mass_in_place(self):
    # the translation is the part that actually hurt: the mask does not move with it

test_the_old_uncentred_formula_really_was_off_centre pins the old formula explicitly so nobody reintroduces it as a simplification. The Scharr side asserts the defining properties — sums to zero, antisymmetric about its differencing axis, zero response on a constant image — plus a sanity check that it still detects the edge it is for, and a guard that the untouched Laplace branch of the same method still sums to zero.

Full suite: 38 passed, 165 subtests. ruff and mypy clean.

Compatibility

Models trained before this change saw the old behaviour and will not reproduce against it. No config key, parameter or default changed.

Two convolution kernels that were not what their names say. Both are silent: the
pipeline runs, nothing raises, the output is simply not the operation the config
asked for.

* get_gaussian_kernel1d sampled the Gaussian at arange(kernel_size) -- 0, 1, 2 --
  putting the peak at index 0 rather than the centre tap. get_gaussian_kernel3d
  is the outer product of three of those, so its maximum sat at corner [0,0,0].
  RandomGaussianBlurGPU and RandomUnsharpMaskGPU therefore blurred *and*
  translated the image by about a voxel. The segmentation mask is never
  convolved, so it did not move with it: the two came out misaligned. The kernel
  is now sampled on a centred linspace(-half, half, k).

* The 2D CPU Scharr x-kernel had [-10, 0, -10] as its middle row. The whole
  kernel summed to -20 instead of 0, which makes it not a gradient operator --
  it responds to constant regions. The sibling kernel_y on the next line has
  always been correct, which is what marks this as a typo rather than an
  intentional variant. The 3D tables are unaffected.

unit_tests/test_kernel_correctness.py covers both, including the property that
made the blur bug matter -- convolving a centred impulse must leave its centre of
mass in place on every axis. 18 of its checks fail against the previous
implementation.

Models trained before this change saw the old behaviour and will not reproduce
against it. No config key, parameter or default changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 20, 2026 11:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@NathanMolinier NathanMolinier left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants