diff --git a/src/app/calibration-ui.ts b/src/app/calibration-ui.ts index 12b99af..68ed23a 100644 --- a/src/app/calibration-ui.ts +++ b/src/app/calibration-ui.ts @@ -58,7 +58,7 @@ export function mountCalibration(deps: CalibrationDeps): void { const renderList = (errs?: number[]) => { list.innerHTML = points - .map((p, i) => `
  • (${p.machine[0]}, ${p.machine[1]}) → img(${p.image[0].toFixed(3)}, ${p.image[1].toFixed(3)})${errs ? ` err ${(errs[i]! * overlay.width).toFixed(1)}px` : ''}
  • `) + .map((p, i) => `
  • (${p.machine[0]}, ${p.machine[1]}) → img(${p.image[0].toFixed(3)}, ${p.image[1].toFixed(3)})${errs ? ` err ${errs[i]!.toFixed(1)}px` : ''}
  • `) .join(''); }; @@ -100,8 +100,12 @@ export function mountCalibration(deps: CalibrationDeps): void { const machine = points.map((p) => p.machine); const image = points.map((p) => p.image); const H = estimateHomography(machine, image); - const errs = residuals(H, machine, image); - renderList(errs); + // Per-axis pixel residual (normalized error scaled by canvas width/height) — used only for misclick display. + const errsPx = machine.map((m, i) => { + const p = applyHomography(H, m); + return Math.hypot((p[0] - image[i]![0]) * overlay.width, (p[1] - image[i]![1]) * overlay.height); + }); + renderList(errsPx); jsonEl.value = JSON.stringify( { imagePoints: image, machinePoints: machine, homography: H }, null, diff --git a/src/main.ts b/src/main.ts index 36f6f2c..ed4472b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -47,8 +47,6 @@ fileInput.addEventListener('change', async () => { render(); }); -// expose for the UI modules added in later tasks -export const app = { state, render, resize, overlay, stage, stream }; mountCalibration({ panel: document.getElementById('calib-panel') as HTMLElement, diff --git a/src/styles.css b/src/styles.css index 672d4e6..47f062a 100644 --- a/src/styles.css +++ b/src/styles.css @@ -2,8 +2,8 @@ body { margin: 0; display: flex; height: 100vh; font-family: system-ui, sans-serif; background: #111; color: #eee; } #stage { position: relative; flex: 1; overflow: hidden; background: #000; } #stream { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; } -#overlay { position: absolute; pointer-events: none; } -#overlay.interactive { pointer-events: auto; cursor: crosshair; } +#overlay { position: absolute; pointer-events: auto; cursor: default; } +#overlay.interactive { cursor: crosshair; } #panel { width: 320px; padding: 16px; overflow-y: auto; background: #1b1b1b; } #panel h1 { font-size: 16px; } .filebtn { display: inline-block; padding: 8px 12px; background: #2962ff; border-radius: 4px; cursor: pointer; }