test: add real-photo fixture harness for detection regressions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fad7deed5d
commit
0a97cb88af
3 files changed
+73
No files matched your search
@@ -0,0 +1,45 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import jpeg from 'jpeg-js';
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import { detect, otsuThreshold } from '../src/lib/detect';
|
||||
import { prepareForExport, shapeSize } from '../src/lib/geometry';
|
||||
import { loadOpenCV, type CV } from '../src/lib/opencv-loader';
|
||||
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 };
|
||||
toleranceMm: number;
|
||||
};
|
||||
|
||||
const dir = fileURLToPath(new URL('./fixtures/', import.meta.url));
|
||||
const cases: FixtureCase[] = JSON.parse(readFileSync(dir + 'manifest.json', 'utf8'));
|
||||
|
||||
function readJpeg(path: string): RasterImage {
|
||||
const decoded = jpeg.decode(readFileSync(path), { useTArray: true, formatAsRGBA: true });
|
||||
return { width: decoded.width, height: decoded.height, data: new Uint8ClampedArray(decoded.data) };
|
||||
}
|
||||
|
||||
describe.skipIf(cases.length === 0)('detect on real photos', () => {
|
||||
let cv: CV;
|
||||
beforeAll(async () => {
|
||||
({ cv } = await loadOpenCV());
|
||||
});
|
||||
|
||||
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 });
|
||||
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);
|
||||
});
|
||||
});
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
# Real photo fixtures
|
||||
|
||||
Regression photos for `detect()`. Each entry in `manifest.json` is checked by `tests/fixtures.test.ts`.
|
||||
|
||||
To add one:
|
||||
|
||||
1. Take a top-down photo of a scrap piece with something of known length visible.
|
||||
2. Rotate it upright and resize it so the long side is at most 2000 px. Save as JPEG.
|
||||
EXIF orientation is ignored by the test decoder.
|
||||
3. Measure the real piece's bounding box in mm.
|
||||
4. Open the photo in the app, set the scale, and note px/mm, the threshold you used, and,
|
||||
if needed, a seed point (shown in image px).
|
||||
5. Add an entry:
|
||||
|
||||
```json
|
||||
{
|
||||
"file": "plywood-1.jpg",
|
||||
"threshold": null,
|
||||
"invert": false,
|
||||
"seed": null,
|
||||
"pxPerMm": 3.21,
|
||||
"expected": { "widthMm": 412, "heightMm": 287, "holes": 2 },
|
||||
"toleranceMm": 8
|
||||
}
|
||||
```
|
||||
|
||||
`threshold: null` means Otsu's automatic value. `seed` is `{ "x": ..., "y": ... }` or `null`.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
[]
|
||||
Reference in new issue
Block a user