Files
SlabUploader/frontend/src/geometry/rect.test.ts
T
grokbot_ty b328871549 fix(capture): rotate length-axis rectangle and scale from mask span
Length axis keeps the approved mask, replaces Flip 180 with a ± angle
slider, and sets scale from wood span along the approved direction rather
than bbox.longSidePx. Widths still sample every 6 in perpendicular to that
axis.
2026-09-14 16:37:00 +00:00

52 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { minAreaRect, orientedRect, maskSpanAlong } from './rect.ts';
import { measure } from '../measure/measure.ts';
// 100×20 horizontal slab
const W = 100, H = 20;
const bitset = new Uint8Array(W * H);
for (let x = 0; x < W; x++) for (let y = 0; y < H; y++) bitset[y * W + x] = 1;
test('maskSpanAlong of a 100×20 slab: X ≈ 99–100, Y ≈ 19–20', () => {
const sx = maskSpanAlong(bitset, W, H, 1, 0);
const sy = maskSpanAlong(bitset, W, H, 0, 1);
assert.ok(sx >= 99 && sx <= 100, `spanX ${sx}`);
assert.ok(sy >= 19 && sy <= 20, `spanY ${sy}`);
});
test('orientedRect changes angleDeg and preserves longSidePx', () => {
const prev = minAreaRect(bitset, W, H);
assert.ok(prev, 'minAreaRect returned a box');
const rotated = orientedRect(prev, 45);
assert.ok(Math.abs(rotated.angleDeg - 45) < 1e-9);
assert.equal(rotated.longSidePx, prev.longSidePx);
// The long side (angleDeg 0 → +x) must be preserved by the rotation.
const longOf = (c) => Math.hypot(c[3][0] - c[0][0], c[3][1] - c[0][1]);
assert.ok(Math.abs(longOf(rotated.corners) - longOf(prev.corners)) < 1e-9);
assert.ok(Math.abs(longOf(prev.corners) - prev.longSidePx) < 1e-9);
// angle normalization to [0, 180)
assert.ok(orientedRect(prev, 190).angleDeg >= 0 && orientedRect(prev, 190).angleDeg < 180);
});
test('measure scale uses mask span along the axis, not longSidePx', () => {
// Thin slab (20 long, 100 wide): minAreaRect's long side (angleDeg 90) is
// the 100-px dimension along +y. Rotate the axis to angleDeg 0 (+x), which
// crosses only the 20-px slab width — span 19, not longSidePx 99.
const TW = 20, TH = 100;
const thin = new Uint8Array(TW * TH).fill(1);
const thinBox = minAreaRect(thin, TW, TH);
assert.ok(thinBox && thinBox.longSidePx >= 99, `longSide ${thinBox?.longSidePx}`);
const rotated = orientedRect(thinBox, 0);
const lengthEighths = 24; // 3 in
const m = measure(thin, TW, TH, rotated, lengthEighths, 8, TW * TH);
// Scale = span along the axis / length. Span ≈ 19 → scale ≈ 6.3 px/in,
// far from the old longSidePx/3 ≈ 33.
const ex = rotated.corners[0][0] - rotated.corners[3][0];
const ey = rotated.corners[0][1] - rotated.corners[3][1];
const elen = Math.hypot(ex, ey);
const span = maskSpanAlong(thin, TW, TH, ex / elen, ey / elen);
assert.ok(Math.abs(m.scale - span / (lengthEighths / 8)) < 1e-9);
assert.ok(m.scale < 10, `scale ${m.scale} should be span-based, not ${thinBox.longSidePx / 3}`);
});