Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Compositional Target Model

A ringgrid target is described at runtime by a TargetLayout. Before 0.8 the only target was a hex lattice of 16-sector coded rings, modeled by the flat BoardLayout type (removed in 0.9). TargetLayout generalizes that into four orthogonal aspects that compose freely:

TargetLayout  =  lattice  ×  ring geometry  ×  coding  ×  optional fiducials
  • Lattice (LatticeGeometry) — how marker cells are arranged: Hex or Rect.
  • Ring geometry (RingGeometry) — the outer/inner radii shared by every marker.
  • Coding (MarkerCoding) — whether markers carry a decodable identity (Coded16) or are plain annuli (Plain).
  • Fiducials (OriginFiducials, optional) — filled dots that anchor origin and orientation for targets whose markers do not encode identity.

Each aspect is a small value type; TargetLayout::new composes and validates them. Geometry cannot be mutated in place — the derived cell cache (positions, ID↔coordinate lookups) would silently desync — so construction always goes through new, a preset, or a JSON loader.

Lattice geometry

VariantFieldsNearest-neighbor spacing
Hex(HexGeometry)rows, long_row_cols, pitch_mmpitch_mm × √3
Rect(RectGeometry)rows, cols, pitch_mmpitch_mm

The hex lattice uses axial rows that alternate between long and short rows; long_row_cols sets the long-row width. The first generated cell is normalized to board position [0, 0] mm, and generation order (top row first, left to right) is load-bearing — sequential IDs derive from it. Cell coordinates are axial (q, r) for hex and (col, row) for rect, both carried as projective_grid::Coord { u, v }.

Ring geometry

RingGeometry { outer_radius_mm, inner_radius_mm } is shared by every marker. For Coded16 markers these are the centerline radii of the stroked outer and inner rings; for Plain markers they bound the filled annulus directly. The outermost drawn radius differs accordingly: a stroked ring overshoots its centerline by half the stroke width, while a plain annulus does not.

Coding

VariantShapeIdentity
Coded16(CodedRingSpec)two stroked rings with a 16-sector code band between themcodebook ID (decoded per marker)
Plaina single filled annulusnone — cells are keyed by lattice coordinate

CodedRingSpec carries the ring_width_mm stroke and an optional id_assignment (see ID Assignment Optimization). Coded targets are capped at the embedded codebook size (893 codewords); a lattice with more cells than that is rejected for Coded16 but valid as Plain.

Fiducials

OriginFiducials { dot_radius_mm } are dark filled dots printed in the lattice gaps. They resolve the board origin and orientation for plain targets, whose markers carry no identity. Coded targets do not need them — decoded IDs already anchor every marker to a physical cell, and coded-with-fiducials is rejected.

Only the dot size is stored: positions are an L of three lattice gaps around cell (0, 0), derived from the lattice and read back via TargetLayout::fiducial_dots_mm(). See Origin Fiducials for the placement rule, validation, and anchoring.

Composition matrix — how each combination detects

Every built-in lattice × coding combination detects end-to-end, but the identity-bearing stages differ. The coded path decodes IDs and labels markers by codebook lookup; the plain path labels markers by their lattice position.

LatticeCodingLabeling pathID correctionOutput frame
HexCoded16decode → global filter → completionhex-neighbor BFS consensusAbsolute
RectCoded16decode → global filter → completion— (global filter + geometric verify only)Absolute
HexPlaindetect_grid labeling → completionAbsolute if fiducials resolve, else RelativeCanonical
RectPlaindetect_grid labeling → completionAbsolute if fiducials resolve, else RelativeCanonical

Key points:

  • Coded targets run the classic decode-anchored pipeline (see the Detection Pipeline). Every decoded ID maps to a physical board cell, so outputs are always in the absolute board frame.
  • ID correction is a hex-neighbor BFS consensus — its algorithmic domain — so it runs only for hex coded targets. Rect coded targets rely on the global RANSAC homography filter plus geometric verification instead.
  • Plain targets skip decoding entirely. Fitted ring centers are labeled with lattice coordinates by projective_grid::detect_grid (labeling only; the frame homography is refit in f64 by ringgrid’s RANSAC), then completion grows the labeled patch. See the plain-target path.
  • Plain outputs are in a canonical relative frame unless the target carries origin fiducials that resolve the origin; see Origin Fiducials.

Presets

Three presets cover the common cases:

PresetLatticeCodingCellsNotes
TargetLayout::default_hex()15-row hex, 8 mm pitchCoded16203the classic 200 mm ringgrid board
TargetLayout::coded_hex(...)hex (caller geometry)Coded16coded hex from direct geometry arguments
TargetLayout::rect_24x24()24×24 rect, 14 mm pitchPlain57624×24 plain target with three Ø2.8 mm origin dots

default_hex() is geometry-identical to the classic pre-0.9 hex board, so existing hex-coded workflows are unchanged.

Construction

#![allow(unused)]
fn main() {
use ringgrid::{
    TargetLayout, LatticeGeometry, RectGeometry, RingGeometry,
    MarkerCoding, OriginFiducials,
};

// A preset
let hex = TargetLayout::default_hex();
let rect = TargetLayout::rect_24x24();

// A custom plain rect target with origin dots — one call, dots derived
let target = TargetLayout::plain_rect(14.0, 12, 12, 5.6, 2.8, OriginDots::Auto)
    .expect("valid target");

// The compositional form, for a dot size you pick yourself
let target = TargetLayout::new(
    "my_rect",
    LatticeGeometry::Rect(RectGeometry { rows: 12, cols: 12, pitch_mm: 14.0 }),
    RingGeometry { outer_radius_mm: 5.6, inner_radius_mm: 2.8 },
    MarkerCoding::Plain,
    Some(OriginFiducials { dot_radius_mm: 1.4 }),
).expect("valid target");
}

The Detector / DetectConfig constructors take impl Into<TargetLayout>, so a TargetLayout (or anything convertible into one) can be passed directly.

Validation

TargetLayout::new rejects illegal targets up front:

  • non-finite or non-positive pitch, radii, or ring width;
  • inner_radius_mm >= outer_radius_mm, or a non-positive code-band gap for coded markers;
  • a drawn marker diameter that reaches or exceeds the minimum center spacing (markers would touch);
  • more cells than the codebook can encode, or an out-of-range / duplicate entry in id_assignment (coded targets);
  • coded markers combined with origin fiducials (the excluded matrix cell);
  • a fiducial dot radius large enough to overlap a marker’s drawn extent, or a board too small to hold the dot triad inside its marker field (see Origin Fiducials).

Legacy board_spec.json files still load unchanged: TargetLayout::from_json_* auto-migrates the v4 and v5 schemas to the canonical v6 spec.

Source: crates/ringgrid/src/target/ (layout.rs, lattice.rs, ring.rs, fiducials.rs)