Skip to content

test(reference): ground-truth tests and implementation notes (refs #87, category C1) - #117

Merged
kalwalt merged 1 commit into
devfrom
test/87-reference-c1
Aug 2, 2026
Merged

test(reference): ground-truth tests and implementation notes (refs #87, category C1)#117
kalwalt merged 1 commit into
devfrom
test/87-reference-c1

Conversation

@kalwalt

@kalwalt kalwalt commented Aug 1, 2026

Copy link
Copy Markdown
Member

Category C1 of the #87 plan. 18 new cases, 229 → 247 total. Tests and docs only, no src/ changes.

Why

This is the first part of the suite that pins actual output values. Parity compares against a vendored jsfeat that shares any inherited defect; the invariant tests constrain answers without fixing them. #115 showed an off-by-one in a loop bound surviving both, because it changed which code path produced a pixel without changing the value for a uniform image.

What is here

Reference implementations (tests/reference/imgproc.test.ts) — naive, obvious versions of box_blur, sobel, scharr and the integral image, written from each operation's definition and compared on non-uniform input. All match bit-exactly, so there are no tolerances anywhere.

Closed-form values (tests/reference/known-values.test.ts) — for what a reference implementation cannot catch, since the same misunderstanding would sit on both sides. The binomial kernels asserted to the bit; jsfeat's integer luma constants checked against real-valued BT.601 rather than against themselves; and inputs constructed from known answersA = U·diag(w)·Vᵀ requiring SVD to return the chosen singular values, a chosen homography requiring homography2d to recover it.

Three findings

1. Sobel and scharr use asymmetric border handling. Reflect vertically (BORDER_REFLECT_101), replicate horizontally:

srow0 = ((y > 0 ? y - 1 : 1) * w) | 0;   // vertical: reflect
trow0[0] = trow0[1];                      // horizontal: replicate

Assuming replication in both directions makes the interior match exactly (352/352) and nearly every border pixel disagree (35/80) — which reads like a wrong kernel when the kernel is fine. Now pinned by its own test.

2. get_gaussian_kernel size 7 is not the binomial row. It is [2,7,14,18,14,7,2]/64, not Pascal's [1,6,15,20,15,6,1]/64 — though sizes 3 and 5 are the binomial rows. Flatter peak, heavier tails. Pinned so a rewrite cannot "correct" it and silently change every 7-tap blur in the library.

3. My own first draft had the flaw it was written to prevent. Re-running the #115 off-by-one against it, the ground-truth test still missed it. At 23×17 that mutation perturbs only radius 3, by exactly 1 — precisely the slack a ±1 tolerance was allowing for the known truncation defect.

Fixed two ways: sweep five shapes rather than one (8×8 at radius 1 differs in 15 pixels), and model the defect instead of tolerating it — a reference variant scales by the float reciprocal exactly as the library does, so comparison is exact at every radius, and the truncation is asserted separately as "differs from exact division only at radius 3, and only by 1". Now caught.

A tolerance wide enough to absorb a known defect is wide enough to hide an unknown one.

docs/implementation-notes.md

Collects the conventions and quirks established across this whole effort, so they are not rediscovered: border conventions, fixed-point and rounding behaviour, the known defects (#102, #110, #111, #114) with the measurements behind them, behaviour that looks wrong but is not (identity warps, equalize_histogram on a uniform image, LK displacement limits), data-structure gotchas (8-byte padding, median returning the lower middle), and notes on what each layer of the suite can and cannot catch.

Corrections are kept rather than edited out — including the min(cols, rows) rule for #114 that turned out to be wrong.

Remaining

C2gaussian_blur (its arithmetic model still needs resolving; neither float nor a naive fixed-point reproduction matched) and the warps/resample. C3 — optional OpenCV cross-check, to be decided once C2 shows what is still unpinned.

Verified: prettier clean, tsc --noEmit clean, license-check clean, npm test 246 passed + 1 expected fail.

🤖 Generated with Claude Code

…#87)

Category C1 of the #87 plan. 18 new cases, 229 -> 247 total. Tests and docs
only, no src/ changes.

This is the first part of the suite that pins actual output values. Parity
compares against a vendored jsfeat that shares any inherited defect, and the
invariant tests constrain answers without fixing them — #115 showed an
off-by-one in a loop bound surviving both, because it changed which code path
produced a pixel without changing the value for a uniform image.

Two complementary kinds, in tests/reference/:

Reference implementations (imgproc.test.ts). Naive, obvious versions of
box_blur, sobel, scharr and the integral image, written from each operation's
definition and compared on non-uniform input. All match BIT-EXACTLY, so no
tolerances are involved anywhere.

Closed-form values (known-values.test.ts). Where a reference implementation
cannot help because the same misunderstanding would sit on both sides: the
binomial kernels asserted to the bit, jsfeat's integer luma constants checked
against real-valued BT.601 rather than against themselves, and inputs
CONSTRUCTED from known answers — A = U diag(w) V^T requiring SVD to return the
chosen singular values, and a chosen homography requiring homography2d to
recover it.

Three findings worth recording.

Sobel and scharr use ASYMMETRIC border handling: reflect vertically
(BORDER_REFLECT_101), replicate horizontally. Assuming replication in both
directions makes the interior match exactly (352/352) and nearly every border
pixel disagree (35/80) — a failure that reads like a wrong kernel when the
kernel is fine. Now pinned by its own test.

get_gaussian_kernel size 7 is [2,7,14,18,14,7,2]/64, NOT Pascal's
[1,6,15,20,15,6,1]/64, though sizes 3 and 5 are the binomial rows. Flatter
peak, heavier tails. Pinned so a rewrite cannot "correct" it and silently
change every 7-tap blur.

And a flaw in my own first draft. Re-running the #115 off-by-one against it:
the ground-truth test STILL missed it. At 23x17 that mutation perturbs only
radius 3, by exactly 1 — the slack a ±1 tolerance was allowing for the known
truncation defect. Fixed by sweeping five shapes and by MODELLING the defect
instead of tolerating it: a reference variant scales by the float reciprocal
exactly as the library does, so comparison is exact at every radius, and the
truncation is asserted separately as "differs from exact division only at
radius 3, and only by 1". Now caught. A tolerance wide enough to absorb a
known defect is wide enough to hide an unknown one.

Also adds docs/implementation-notes.md, collecting the conventions and quirks
established across this work: border conventions, fixed-point and rounding
behaviour, the known defects (#102, #110, #111, #114) with the measurements
behind them, behaviour that looks wrong but is not, data-structure gotchas,
and notes on what each layer of the test suite can and cannot catch.

Verified: prettier clean, tsc --noEmit clean, license-check clean,
npm test 246 passed + 1 expected fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kalwalt kalwalt added this to the 1.0.0 milestone Aug 1, 2026
@kalwalt kalwalt self-assigned this Aug 2, 2026
@kalwalt kalwalt added enhancement New feature or request tests CI/CD labels Aug 2, 2026
@kalwalt
kalwalt merged commit 7c268d9 into dev Aug 2, 2026
4 checks passed
@kalwalt
kalwalt deleted the test/87-reference-c1 branch August 3, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD enhancement New feature or request tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant