Add quad corner refinement and homography warp for rectified identify crops; wire warped capture through identifyTrackedCardCapture with validation and axis-aligned fallback; add detection frame a11y labels. Co-authored-by: Cursor <cursoragent@cursor.com>
93 lines
2.3 KiB
JavaScript
93 lines
2.3 KiB
JavaScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
boundsFromCorners,
|
|
computeHomography,
|
|
isValidCardQuad,
|
|
orderQuadCorners,
|
|
} from '../../lib/scanner-card-warp.js';
|
|
|
|
describe('orderQuadCorners', () => {
|
|
it('orders corners as top-left, top-right, bottom-right, bottom-left', () => {
|
|
const points = [
|
|
{ x: 10, y: 10 },
|
|
{ x: 90, y: 12 },
|
|
{ x: 88, y: 140 },
|
|
{ x: 12, y: 138 },
|
|
];
|
|
|
|
expect(orderQuadCorners(points)).toEqual([
|
|
{ x: 10, y: 10 },
|
|
{ x: 90, y: 12 },
|
|
{ x: 88, y: 140 },
|
|
{ x: 12, y: 138 },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('boundsFromCorners', () => {
|
|
it('returns the enclosing axis-aligned rectangle', () => {
|
|
const corners = [
|
|
{ x: 10, y: 20 },
|
|
{ x: 110, y: 25 },
|
|
{ x: 105, y: 165 },
|
|
{ x: 15, y: 160 },
|
|
];
|
|
|
|
expect(boundsFromCorners(corners)).toEqual({
|
|
x: 10,
|
|
y: 20,
|
|
width: 100,
|
|
height: 145,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('isValidCardQuad', () => {
|
|
it('accepts a convex card-shaped quad', () => {
|
|
const corners = [
|
|
{ x: 0, y: 0 },
|
|
{ x: 50, y: 0 },
|
|
{ x: 50, y: 70 },
|
|
{ x: 0, y: 70 },
|
|
];
|
|
expect(isValidCardQuad(corners)).toBe(true);
|
|
});
|
|
|
|
it('rejects a quad with implausible aspect ratio', () => {
|
|
const corners = [
|
|
{ x: 0, y: 0 },
|
|
{ x: 200, y: 0 },
|
|
{ x: 200, y: 70 },
|
|
{ x: 0, y: 70 },
|
|
];
|
|
expect(isValidCardQuad(corners)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('computeHomography', () => {
|
|
it('maps source corners to the destination rectangle', () => {
|
|
const src = [
|
|
{ x: 0, y: 0 },
|
|
{ x: 100, y: 0 },
|
|
{ x: 100, y: 140 },
|
|
{ x: 0, y: 140 },
|
|
];
|
|
const matrix = computeHomography(src, 200, 280);
|
|
|
|
const mappedTopLeft = applyForward(matrix, 0, 0);
|
|
expect(mappedTopLeft.x).toBeCloseTo(0, 4);
|
|
expect(mappedTopLeft.y).toBeCloseTo(0, 4);
|
|
|
|
const mappedBottomRight = applyForward(matrix, 100, 140);
|
|
expect(mappedBottomRight.x).toBeCloseTo(200, 4);
|
|
expect(mappedBottomRight.y).toBeCloseTo(280, 4);
|
|
});
|
|
});
|
|
|
|
function applyForward(matrix, x, y) {
|
|
const denom = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2];
|
|
return {
|
|
x: (matrix[0][0] * x + matrix[0][1] * y + matrix[0][2]) / denom,
|
|
y: (matrix[1][0] * x + matrix[1][1] * y + matrix[1][2]) / denom,
|
|
};
|
|
}
|