@vitavision/calib-targets
v0.10.1
Published
WebAssembly bindings for calib-targets calibration target detectors
Readme
@vitavision/calib-targets
WebAssembly bindings for the calib-targets Rust workspace. Run
chessboard, ChArUco, PuzzleBoard, and marker-board detection directly in
the browser from a canvas, an ImageBitmap, or any Uint8Array of
grayscale pixels.
- Tiny: ~436 KB raw, ~195 KB gzipped.
- No threads, no
imagecodec. Zero runtime dependencies. - Same detectors as the Rust facade — no algorithmic differences.
- Works in every modern browser supporting
wasm-bindgen.
Book & per-target chapters: https://vitalyvorobyev.github.io/calib-targets-rs/
Install
npm install @vitavision/calib-targets
# or, for the local build output:
scripts/build-wasm.sh # produces demo/pkg/Hello world
import init, {
default_chess_config,
default_chessboard_params,
detect_chessboard,
rgba_to_gray,
} from "@vitavision/calib-targets";
await init(); // initialise the WASM module once per page
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d")!;
// ... draw image to canvas ...
const rgba = new Uint8Array(ctx.getImageData(0, 0, canvas.width, canvas.height).data.buffer);
const gray = rgba_to_gray(rgba, canvas.width, canvas.height);
const result = detect_chessboard(
canvas.width, canvas.height, gray,
default_chess_config(),
default_chessboard_params(),
);
if (result) {
console.log(`labelled ${result.corners.length} corners`);
}Per-target examples
Every detector takes (w, h, pixels, chess_cfg, params) and returns a
plain JS object you can JSON.stringify.
Chessboard
import { default_chess_config, default_chessboard_params, detect_chessboard_best } from "@vitavision/calib-targets";
const chessCfg = default_chess_config();
chessCfg.threshold = { absolute: 15.0 };
const base = default_chessboard_params();
const configs = [
base,
{ ...base, min_labeled_corners: 12 },
{ ...base, max_components: 1 },
];
const best = detect_chessboard_best(width, height, gray, chessCfg, configs);ChArUco
import { detect_charuco } from "@vitavision/calib-targets";
const board = {
rows: 5, cols: 7, cell_size: 1.0,
marker_size_rel: 0.75,
dictionary: "DICT_4X4_50",
marker_layout: "opencv_charuco",
};
const params = {
board,
px_per_square: 60.0,
chessboard: default_chessboard_params(),
scan: { border_bits: 1, inset_frac: 0.06, marker_size_rel: 0.75,
min_border_score: 0.85, multi_threshold: true, dedup_by_id: true },
max_hamming: 2,
min_marker_inliers: 4,
};
const result = detect_charuco(width, height, gray, default_chess_config(), params);
// result.corners[].id is the ChArUco logical corner ID.PuzzleBoard
import {
default_puzzleboard_params,
detect_puzzleboard,
render_puzzleboard_bundle,
render_puzzleboard_png,
} from "@vitavision/calib-targets";
// Generate a PuzzleBoard PNG in the browser (PNG-only fast path).
const pngBytes = render_puzzleboard_png(10, 10, /*square_mm=*/20.0, /*dpi=*/150);
// Full JSON / SVG / PNG / DXF bundle — the DXF is the photolith-handoff
// flavor (AC1015 ASCII, $INSUNITS = 4 mm, Y-up cartesian).
const bundle = render_puzzleboard_bundle(10, 10, 20.0, 150);
// bundle.json_text / bundle.svg_text / bundle.dxf_text → string
// bundle.png_bytes → Uint8Array
const params = default_puzzleboard_params(10, 10);
params.decode.search_mode = { kind: "fixed_board" };
params.decode.scoring_mode = { kind: "soft_log_likelihood" };
const result = detect_puzzleboard(width, height, gray, default_chess_config(), params);
// Every corner has an absolute master ID: result.corners[0].id
// Soft-mode scoring evidence is available from detect_puzzleboard_with_diagnostics().The same render_*_bundle and render_*_png pairs exist for the other
three target families (render_chessboard_*, render_charuco_*,
render_marker_board_*); see the Functions table below.
Marker board
import { default_marker_board_params, detect_marker_board } from "@vitavision/calib-targets";
const params = default_marker_board_params();
params.layout = {
rows: 6, cols: 8, cell_size: 1.0,
circles: [
{ cell: { i: 2, j: 2 }, polarity: "white" },
{ cell: { i: 3, j: 2 }, polarity: "black" },
{ cell: { i: 2, j: 3 }, polarity: "white" },
],
};
const result = detect_marker_board(width, height, gray, default_chess_config(), params);Inputs
| Argument | Type | Notes |
|---|---|---|
| width, height | number | Image dimensions in pixels. |
| pixels | Uint8Array | Row-major grayscale buffer, length w*h. Use rgba_to_gray to convert from canvas RGBA. |
| chess_cfg | plain JS object | Start from default_chess_config() and override fields. |
| params | plain JS object | Per-detector shape; use default_*_params(...) and override. |
| configs (sweep) | params[] | Array of configs tried in order by detect_*_best. |
Outputs
All result types deserialise to plain JS objects matching the Rust
serde_json schema — JSON.stringify(result) gives you a canonical,
cross-language payload.
PuzzleBoard results include a compact decode summary. Raw observed
edges and soft-mode runner-up scoring evidence are returned by
detect_puzzleboard_with_diagnostics.
LabeledCorner (shared across grid detectors):
{
position: { x: number, y: number }, // sub-pixel image location
grid: { u: number, v: number } | null, // integer grid label, rebased to (0,0)
id: number | null, // ChArUco / PuzzleBoard ID
target_position: { x: number, y: number } | null, // mm on the printed board
score: number,
}Functions
| Function | Returns |
|---|---|
| detect_corners(w, h, px, cfg) | Corner[] |
| detect_chessboard(w, h, px, cfg, params) | ChessboardDetectionResult \| null |
| detect_chessboard_best(w, h, px, cfg, configs) | ChessboardDetectionResult \| null |
| detect_charuco(w, h, px, cfg, params) | CharucoDetectionResult (throws on error) |
| detect_charuco_best(w, h, px, configs) | CharucoDetectionResult (throws on all-fail) |
| detect_puzzleboard(w, h, px, cfg, params) | PuzzleBoardDetectionResult (throws on error) |
| detect_puzzleboard_best(w, h, px, configs) | PuzzleBoardDetectionResult (throws on all-fail) |
| detect_marker_board(w, h, px, cfg, params) | MarkerBoardDetectionResult \| null |
| detect_marker_board_best(w, h, px, configs) | MarkerBoardDetectionResult \| null |
| rgba_to_gray(rgba, w, h) | Uint8Array (BT.601) |
| render_chessboard_png(inner_rows, inner_cols, square_mm, dpi) | Uint8Array — encoded PNG |
| render_charuco_png(rows, cols, square_mm, marker_size_rel, dict_name, dpi) | Uint8Array |
| render_marker_board_png(inner_rows, inner_cols, square_mm, dpi) | Uint8Array |
| render_puzzleboard_png(rows, cols, square_mm, dpi) | Uint8Array |
| render_chessboard_bundle(inner_rows, inner_cols, square_mm, dpi) | GeneratedTargetBundle — { json_text, svg_text, png_bytes, dxf_text } |
| render_charuco_bundle(rows, cols, square_mm, marker_size_rel, dict_name, dpi) | GeneratedTargetBundle |
| render_marker_board_bundle(inner_rows, inner_cols, square_mm, dpi) | GeneratedTargetBundle |
| render_puzzleboard_bundle(rows, cols, square_mm, dpi) | GeneratedTargetBundle |
| default_chess_config(), default_chessboard_params(), default_puzzleboard_params(rows, cols), default_marker_board_params() | baseline configs |
Tuning difficult cases
- Always prefer
detect_*_bestoverdetect_*— the 3-config sweep solves most common tuning needs without writing code. - For blurry / low-contrast inputs, lower the chess threshold in one
of the sweep configs — e.g.
chess.threshold = { absolute: 8.0 }in raw ChESS response units, orchess.threshold = { relative: 0.05 }for a fraction of the per-frame peak response. - For small markers (< 12 px across), resize the source canvas up before
calling
detect_charuco*— WASM does not upscale for you. - Open the per-detector READMEs / the book tuning chapter for parameter-by-parameter guidance. Every knob has the same meaning as in the Rust facade.
Limitations
- One target per image. Same as the Rust facade; multiple boards in one frame are not disambiguated.
- No fisheye support. Moderate distortion is handled; severe wide-angle optics are not.
- Grayscale only. Convert from RGBA with
rgba_to_graybefore calling any detector. - No threads. The WASM build is single-threaded; heavy detection on 4K images may exceed 100 ms per call. Consider Web Workers.
Diagnostics are available: detect_chessboard_with_diagnostics (and the
ChArUco / marker-board / PuzzleBoard variants) return a
{ result, diagnostics } object. The diagnostics payloads carry a looser
stability promise than the typed results — see
typescript-extras.d.ts.
Build from source
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
scripts/build-wasm.sh # outputs to demo/pkg/Demo
A React / TypeScript / Vite demo app (using bun, not npm) lives at
demo/:
scripts/build-wasm.sh
cd demo && bun install && bun run devThe demo covers all four target types with live parameter tuning and canvas overlays.
License
MIT or Apache-2.0, at your option.
