Target JSON (schema v6)
ringgrid.target.v6 is the canonical, compositional target schema. It mirrors
the target model one-to-one: a top-level object with
lattice, marker, coding, and optional fiducials sections. Loaders also
accept ringgrid.target.v6 and the legacy flat ringgrid.target.v4 schema and
migrate them on the fly; writers always emit v6.
What changed in v6: fiducials no longer stores dot coordinates. Only
dot_radius_mm is written; positions are derived from the lattice, so they
cannot fall out of step when the board’s dimensions change. See
migration below.
Annotated example — plain rect with origin dots
Produced by TargetLayout::rect_24x24() (abbreviated to a 4×4 lattice with
two dots so the shape is easy to read):
{
"schema": "ringgrid.target.v6", // schema tag; dispatched before full parse
"name": "ringgrid_rect_r4_c4_p14.000_o5.600_i2.800", // human-readable name
"lattice": {
"kind": "rect", // "rect" | "hex"
"rows": 4,
"cols": 4,
"pitch_mm": 14.0 // center-to-center spacing
},
"marker": {
"outer_radius_mm": 5.6, // annulus bounds (plain) / centerline (coded)
"inner_radius_mm": 2.8
},
"coding": {
"kind": "plain" // plain annulus, no identity code
},
"fiducials": { // optional; present only when defined
"dot_radius_mm": 1.4 // size only — positions derive from the lattice
}
}
Annotated example — coded hex
Produced by TargetLayout::default_hex() (shown here for a small 3-row lattice):
{
"schema": "ringgrid.target.v6",
"name": "ringgrid_hex_r3_c4_p8.000_o4.800_i3.200_w1.152",
"lattice": {
"kind": "hex",
"rows": 3,
"long_row_cols": 4, // markers in the long (even-offset) rows
"pitch_mm": 8.0
},
"marker": {
"outer_radius_mm": 4.8, // stroked-ring centerline radii
"inner_radius_mm": 3.2
},
"coding": {
"kind": "coded16",
"ring_width_mm": 1.152 // stroke width of the inner and outer rings
// "id_assignment": [ ... ] // optional; omitted ⇒ sequential 0,1,2,...
}
// no "fiducials" — coded markers anchor themselves via decoded IDs
}
Field reference
Top level
| Field | Type | Notes |
|---|---|---|
schema | string | "ringgrid.target.v6" (or legacy "ringgrid.target.v4" on input). |
name | string | Non-empty. Presets and CLI use a deterministic geometry-derived name. |
lattice | object | Tagged by kind. |
marker | object | Ring radii. |
coding | object | Tagged by kind. |
fiducials | object? | Omitted when the target defines no origin dots. |
Unknown top-level fields are rejected (deny_unknown_fields).
lattice
kind | Fields |
|---|---|
"hex" | rows, long_row_cols, pitch_mm |
"rect" | rows, cols, pitch_mm |
marker
outer_radius_mm, inner_radius_mm (both mm, inner < outer).
coding
kind | Fields |
|---|---|
"coded16" | ring_width_mm, optional id_assignment (array of codebook IDs, one per cell in generation order) |
"plain" | none |
A sequential id_assignment (0, 1, 2, …) is normalized back to the implicit
form and omitted on write.
fiducials
dot_radius_mm — the printed dot radius, and the only fiducial field. Dot
positions are an L of three lattice gaps around cell (0, 0), derived from
lattice at load time and exposed through TargetLayout::fiducial_dots_mm().
See Origin Fiducials.
Migrating from v5
A v5 file differs only in the schema tag and a fiducials.dots_mm array.
Loading one is automatic, with one guard: the stored coordinates are compared
against the derived ones, and a mismatch is an error rather than a silent
override. If they differed, the file describes a board whose dots are printed
somewhere this build would not look for them — regenerating the target (and
re-printing it) is the only correct fix.
In practice only rect targets generated with fiducials = "auto" by ringgrid
0.10.x are affected; their third dot moved by one pitch. Hex targets, the
rect_24x24 preset, and every hand-authored L are unchanged and migrate
silently.
v4 auto-migration
The pre-0.8 flat schema described a hex coded target with top-level
pitch_mm, rows, long_row_cols, marker_outer_radius_mm,
marker_inner_radius_mm, marker_ring_width_mm, and optional id_assignment:
{
"schema": "ringgrid.target.v4",
"name": "legacy",
"pitch_mm": 8.0,
"rows": 15,
"long_row_cols": 14,
"marker_outer_radius_mm": 4.8,
"marker_inner_radius_mm": 3.2,
"marker_ring_width_mm": 1.152
}
Every loader — TargetLayout::from_json_str / from_json_file, the CLI
--target flag, the CLI gen-target from-spec, and the Python / WASM detector
constructors — accepts this and migrates it to a Hex + Coded16 layout.
Writers only ever emit v5, so re-serializing a migrated target upgrades it:
#![allow(unused)]
fn main() {
let target = ringgrid::TargetLayout::from_json_str(v4_json)?; // accepts v4
let v5_json = target.to_json_string(); // emits v5
}
The checked-in tools/board/board_spec*.json fixtures are still v4 and load
unchanged, including their optimized id_assignment. An unknown or unsupported
schema tag is rejected with TargetValidationError::UnsupportedSchema.
Generating target JSON from the CLI
The maintainer-only ringgrid-dev gen-target writes target_spec.json (v5)
alongside printable SVG/PNG. (The published ringgrid CLI generates targets
with the recipe-driven ringgrid gen <recipe> instead — see
Target Generation.) It is a subcommand family:
# Classic hex coded target
ringgrid-dev gen-target hex \
--pitch_mm 8 --rows 15 --long_row_cols 14 \
--marker_outer_radius_mm 4.8 --marker_inner_radius_mm 3.2 \
--marker_ring_width_mm 1.152 \
--out_dir tools/out/target
# Rect plain target with origin dots
ringgrid-dev gen-target rect \
--pitch_mm 14 --rows 24 --cols 24 \
--marker_outer_radius_mm 5.6 --marker_inner_radius_mm 2.8 \
--dots --dot_radius_mm 1.4 \
--out_dir tools/out/target
# A built-in preset
ringgrid-dev gen-target preset default-hex --out_dir tools/out/target
ringgrid-dev gen-target preset rect24x24 --out_dir tools/out/target
# Re-render (and upgrade) an existing spec, v5 or legacy v4
ringgrid-dev gen-target from-spec --spec path/to/target_spec.json --out_dir tools/out/target
See Target Generation for the full flag reference and the equivalent Rust/Python paths.
Source: crates/ringgrid/src/target/schema.rs