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

FitMetrics & DecodeMetrics

These two structs provide detailed quality metrics for each detected marker. FitMetrics describes how well the ellipse(s) fit the observed edge points. DecodeMetrics describes how confidently the 16-sector code was matched to a codebook entry.

Source: crates/ringgrid/src/detector/marker_build.rs (FitMetrics), crates/ringgrid/src/marker/decode.rs (DecodeMetrics)

FitMetrics

FitMetrics is always present on every DetectedMarker. It reports edge sampling coverage and ellipse fit quality.

Fields

FieldTypeDescription
n_angles_totalusizeTotal number of radial rays cast from the candidate center.
n_angles_with_both_edgesusizeNumber of rays where both inner and outer ring edges were found.
n_points_outerusizeNumber of outer edge points used for the ellipse fit.
n_points_innerusizeNumber of inner edge points used for the inner ellipse fit. 0 if no inner fit was performed.
ransac_inlier_ratio_outerOption<f32>Fraction of outer edge points classified as RANSAC inliers.
ransac_inlier_ratio_innerOption<f32>Fraction of inner edge points classified as RANSAC inliers.
rms_residual_outerOption<f64>RMS Sampson distance of outer edge points to the fitted ellipse (in pixels).
rms_residual_innerOption<f64>RMS Sampson distance of inner edge points to the fitted ellipse (in pixels).
max_angular_gap_outerOption<f64>Largest angular gap between consecutive outer-edge samples (radians). Large gaps indicate missing ring coverage or occlusion.
max_angular_gap_innerOption<f64>Largest angular gap between consecutive inner-edge samples (radians).
inner_fit_statusOption<InnerFitStatus>Outcome of the inner-fit stage: ok, rejected, or failed.
inner_fit_reasonOption<InnerFitReason>Stable reason code explaining why inner fitting was rejected or failed.
neighbor_radius_ratioOption<f32>Ratio of this marker’s outer radius to nearby decoded neighbors. Low values can indicate inner-as-outer contamination.
inner_theta_consistencyOption<f32>Fraction of angular samples that agree on the inner-edge location.
radii_std_outer_pxOption<f32>Standard deviation of per-ray outer radii. High spread suggests unstable outer-edge sampling.
h_reproj_err_pxOption<f32>Final homography reprojection error for this marker in pixels. Present when a global homography is available.

Interpreting FitMetrics

RANSAC inlier ratio measures how consistently the edge points agree with the fitted ellipse:

ransac_inlier_ratio_outerInterpretation
> 0.90Excellent – clean edges with minimal outliers
0.80 – 0.90Good – some edge noise or partial occlusion
< 0.70Poor – significant outliers, possible false detection

RMS Sampson residual measures the geometric precision of the fit:

rms_residual_outerInterpretation
< 0.3 pxExcellent sub-pixel precision
0.3 – 0.5 pxGood precision
0.5 – 1.0 pxAcceptable but noisy
> 1.0 pxPoor fit, possibly wrong feature

Arc coverage is the ratio n_angles_with_both_edges / n_angles_total. It indicates how much of the ring perimeter was successfully sampled:

Coverage ratioInterpretation
> 0.85Full ring visible, high confidence
0.5 – 0.85Partial occlusion or edge-of-frame
< 0.5Severely occluded, likely unreliable

Angular gaps (max_angular_gap_outer, max_angular_gap_inner) help detect partial arcs. Even with a decent point count, a single large missing sector can make a fit less trustworthy than the residual alone suggests.

Neighbor radius ratio (neighbor_radius_ratio) is a structural sanity check added late in the pipeline. Values well below 1.0 compared with nearby decoded markers often indicate that some rays latched onto the inner ring instead of the outer one.

Homography reprojection error (h_reproj_err_px) is the most directly useful per-marker global consistency metric once a homography exists. Higher values mean the marker’s center disagrees with the board layout even if the local ellipse fit looked good.

DecodeMetrics

DecodeMetrics is present on a DetectedMarker when code decoding was attempted. It reports the raw sampled word and the quality of the codebook match.

Fields

FieldTypeDescription
observed_wordu16Raw 16-bit word sampled from the code band. Each bit corresponds to one sector (bright = 1, dark = 0).
best_idusizeIndex of the best-matching codebook entry in the active profile.
best_rotationu8Cyclic rotation (0–15) that produced the best match. Each unit is 22.5 degrees.
best_distu8Hamming distance between the observed word (at best rotation) and the codebook entry.
marginu8Gap between the best and second-best Hamming distances: second_best_dist - best_dist.
decode_confidencef32Heuristic confidence score in [0, 1], combining Hamming distance and margin.

Interpreting DecodeMetrics

Hamming distance (best_dist) tells how many of the 16 sectors disagree with the matched codeword:

best_distInterpretation
0Exact match – no bit errors
1 – 2Minor noise, still reliable
3At the default acceptance threshold
> 3Rejected by default (configurable via DecodeConfig::max_decode_dist)

Margin (margin) measures how unambiguous the match is. It is the difference in Hamming distance between the best and second-best codebook matches:

marginInterpretation
>= 4Highly unambiguous
3Reliable
2Acceptable but less certain
1Risky – two codewords are nearly tied
0Ambiguous – the match could be wrong

Decode confidence (decode_confidence) is a composite heuristic in [0, 1] that accounts for both Hamming distance and margin. Higher values indicate more reliable decodes. The default minimum threshold is 0.30 (configurable via DecodeConfig::min_decode_confidence).

Polarity handling

The decoder tries both normal and inverted polarity of the sampled word (bitwise NOT) and picks whichever produces the better codebook match. The observed_word in DecodeMetrics reflects the polarity that was actually used for matching.

Serialization

Both structs derive serde::Serialize and serde::Deserialize. Optional fields use #[serde(skip_serializing_if = "Option::is_none")] and are omitted from JSON output when absent.