npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vitavision/calib-targets

v0.8.0

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 image codec. 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.detection.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 base = default_chessboard_params();
const configs = [0.20, 0.15, 0.08].map(t => ({
  ...base,
  chess: { ...base.chess, threshold_value: t },
}));
const best = detect_chessboard_best(width, height, gray, 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.detection.corners[].id is the ChArUco logical corner ID.

PuzzleBoard

import { default_puzzleboard_params, detect_puzzleboard, render_puzzleboard_png } from "@vitavision/calib-targets";

// Generate a PuzzleBoard PNG in the browser (only PuzzleBoard is supported
// in-browser — see Limitations below).
const pngBytes = render_puzzleboard_png(10, 10, /*square_mm=*/20.0, /*dpi=*/150);

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.detection.corners[0].id
// Soft mode also reports result.decode.score_margin and the runner-up hypothesis.

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.

For PuzzleBoard this includes the selected decode.scoring_mode, and in soft mode the extra decode diagnostics score_best, score_runner_up, score_margin, and runner-up origin / transform.

LabeledCorner (shared across grid detectors):

{
  position: { x: number, y: number },          // sub-pixel image location
  grid:     { i: number, j: 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, 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_puzzleboard_png(rows, cols, square_mm, dpi) | Uint8Array — encoded PNG | | default_chess_config(), default_chessboard_params(), default_puzzleboard_params(rows, cols), default_marker_board_params() | baseline configs |

Tuning difficult cases

  • Always prefer detect_*_best over detect_* — the 3-config sweep solves most common tuning needs without writing code.
  • For blurry / low-contrast inputs, lower chess.threshold_value in one of the sweep configs.
  • 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

  • Target PNG generation is only supported for PuzzleBoard (render_puzzleboard_png). Chessboard, ChArUco, and marker-board PNG / SVG generation is not yet exposed in the WASM surface — generate them server-side via the Rust facade, the Python bindings, or the workspace CLI, and serve the images to the browser.
  • 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_gray before 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.
  • No detect_chessboard_debug. The debug-frame helper is Rust-only.

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 dev

The demo covers all four target types with live parameter tuning and canvas overlays.

License

MIT or Apache-2.0, at your option.