feat: correct uneven lighting and pick polarity automatically

Fit the background brightness from the photo border and subtract it, so
side-lit photos threshold cleanly. The clicked pixel (or, without a click,
the border) decides whether the piece is darker or lighter, replacing the
invert checkbox. Adds the MDF-on-wood-floor photo as a regression fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
immortaly007andClaude Opus 5.5 committed 2026-10-15 15:12:16 +02:00
1 parent 41baa25a01
commit 55a25ba978
10 files changed
+135 -36

No files matched your search

+3 -4
View File
@@ -10,10 +10,9 @@ import type { Point, RasterImage } from '../src/lib/types';
type FixtureCase = {
file: string;
threshold: number | null;
invert: boolean;
seed: Point | null;
pxPerMm: number;
expected: { widthMm: number; heightMm: number; holes: number };
expected: { widthMm: number; heightMm: number; holes?: number };
toleranceMm: number;
};
@@ -34,12 +33,12 @@ describe.skipIf(cases.length === 0)('detect on real photos', () => {
it.each(cases)('$file', (c) => {
const image = readJpeg(dir + c.file);
const threshold = c.threshold ?? otsuThreshold(cv, image);
const shape = detect(cv, image, { threshold, invert: c.invert, seed: c.seed, marginPx: 0, minHolePx: 3 * c.pxPerMm });
const shape = detect(cv, image, { threshold, seed: c.seed, marginPx: 0, minHolePx: 3 * c.pxPerMm });
expect(shape).not.toBeNull();
const mm = prepareForExport(shape!, c.pxPerMm)!;
const size = shapeSize(mm);
expect(Math.abs(size.width - c.expected.widthMm)).toBeLessThanOrEqual(c.toleranceMm);
expect(Math.abs(size.height - c.expected.heightMm)).toBeLessThanOrEqual(c.toleranceMm);
expect(mm.holes).toHaveLength(c.expected.holes);
if (c.expected.holes !== undefined) expect(mm.holes).toHaveLength(c.expected.holes);
});
});