b328871549
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.
52 lines
2.5 KiB
TypeScript
52 lines
2.5 KiB
TypeScript
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}`);
|
||
});
|