fix: drop glare holes before the margin and never auto-pick specks
Filtering holes after the margin let -2 mm grow 1 mm specks past the 3 mm cutoff, and could drop real holes shrunk by a positive margin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
abef0f982b
commit
41baa25a01
7 files changed
+59
-25
No files matched your search
+20
-1
@@ -11,7 +11,7 @@ beforeAll(async () => {
|
||||
});
|
||||
|
||||
const params = (overrides: Partial<DetectParams> = {}): DetectParams => ({
|
||||
threshold: 128, invert: false, seed: null, marginPx: 0, ...overrides,
|
||||
threshold: 128, invert: false, seed: null, marginPx: 0, minHolePx: 0, ...overrides,
|
||||
});
|
||||
|
||||
/** Light plate x 40..159, y 30..129 with hole r=15 at (100,80), plus a small light blob, on dark ground. */
|
||||
@@ -84,4 +84,23 @@ describe('detect', () => {
|
||||
});
|
||||
expect(detect(cv, speckled, params())!.holes).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('fills specks smaller than minHolePx before the margin can grow them', () => {
|
||||
const glare = grayImage(200, 160, (x, y) => {
|
||||
const plate = inRect(x, y, 40, 30, 160, 130) && !inCircle(x, y, 100, 80, 15) && !inCircle(x, y, 60, 50, 3);
|
||||
return plate ? 220 : 30;
|
||||
});
|
||||
expect(detect(cv, glare, params({ marginPx: -6 }))!.holes).toHaveLength(2);
|
||||
expect(detect(cv, glare, params({ marginPx: -6, minHolePx: 10 }))!.holes).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('keeps a long thin hole that is under minHolePx in only one direction', () => {
|
||||
const slotted = grayImage(200, 160, (x, y) => (inRect(x, y, 40, 30, 160, 130) && !inRect(x, y, 60, 60, 120, 66) ? 220 : 30));
|
||||
expect(detect(cv, slotted, params({ minHolePx: 10 }))!.holes).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not auto-pick a speck when the real piece touches the border', () => {
|
||||
const framed = grayImage(400, 300, (x, y) => (inRect(x, y, 0, 60, 240, 260) || inRect(x, y, 330, 100, 340, 110) ? 220 : 30));
|
||||
expect(detect(cv, framed, params())).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -34,7 +34,7 @@ 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 });
|
||||
const shape = detect(cv, image, { threshold, invert: c.invert, seed: c.seed, marginPx: 0, minHolePx: 3 * c.pxPerMm });
|
||||
expect(shape).not.toBeNull();
|
||||
const mm = prepareForExport(shape!, c.pxPerMm)!;
|
||||
const size = shapeSize(mm);
|
||||
|
||||
+3
-14
@@ -53,20 +53,9 @@ describe('prepareForExport', () => {
|
||||
expect(mm.outer).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('drops holes smaller than 3 mm in both directions (glare/speckle)', () => {
|
||||
const shapePx = {
|
||||
outer: denseSquare(0, 0, 400),
|
||||
holes: [denseSquare(100, 100, 8), denseSquare(200, 200, 40)], // 2 mm and 10 mm at 4 px/mm
|
||||
};
|
||||
const mm = prepareForExport(shapePx, 4)!;
|
||||
expect(mm.holes).toHaveLength(1);
|
||||
expect(shapeSize({ outer: mm.holes[0], holes: [] }).width).toBeCloseTo(10);
|
||||
});
|
||||
|
||||
it('keeps a long thin hole that is under 3 mm in only one direction', () => {
|
||||
const slot: Point[] = [{ x: 100, y: 100 }, { x: 300, y: 100 }, { x: 300, y: 104 }, { x: 100, y: 104 }];
|
||||
const mm = prepareForExport({ outer: denseSquare(0, 0, 400), holes: [slot] }, 4)!;
|
||||
expect(mm.holes).toHaveLength(1);
|
||||
it('keeps every hole, however small (speck filtering happens before the margin)', () => {
|
||||
const shapePx = { outer: denseSquare(0, 0, 400), holes: [denseSquare(100, 100, 8), denseSquare(200, 200, 40)] };
|
||||
expect(prepareForExport(shapePx, 4)!.holes).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('returns null when the outer ring degenerates', () => {
|
||||
|
||||
Reference in new issue
Block a user