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

DetectedMarker

DetectedMarker represents a single detected ring marker in the image. Each marker carries its decoded ID (when available), pixel-space center, fitted ellipse parameters, and quality metrics.

Source: crates/ringgrid/src/detector/marker_build.rs

Fields

FieldTypeDescription
idOption<usize>Codebook index in the active profile. None if decoding was rejected due to insufficient confidence or Hamming distance.
board_xy_mmOption<[f64; 2]>Board-space marker location in millimeters (BoardLayout::xy_mm semantics). Present only when id is valid for the active board layout.
confidencef32Combined detection and decode confidence in [0, 1].
center[f64; 2]Marker center in raw image pixel coordinates [x, y].
center_mappedOption<[f64; 2]>Marker center in working-frame coordinates. Present only when a PixelMapper is active.
ellipse_outerOption<Ellipse>Fitted outer ring ellipse parameters.
ellipse_innerOption<Ellipse>Fitted inner ring ellipse parameters. Present when inner fitting succeeded.
edge_points_outerOption<Vec<[f64; 2]>>Raw sub-pixel outer edge inlier points used for ellipse fitting.
edge_points_innerOption<Vec<[f64; 2]>>Raw sub-pixel inner edge inlier points used for ellipse fitting.
fitFitMetricsFit quality metrics. See FitMetrics.
decodeOption<DecodeMetrics>Decode quality metrics. Present when decoding was attempted. See FitMetrics & DecodeMetrics.
sourceDetectionSourcePipeline path that produced the final marker: fit_decoded, completion, or seeded_pass.

DetectionSource

DetectedMarker.source tells you how the marker entered the final result:

  • fit_decoded – the normal proposal -> fit -> decode path
  • completion – the homography-guided completion stage filled a missing board marker
  • seeded_pass – the marker was re-fitted during mapper-based pass-2 detection

Center coordinate frames

The center field is always in raw image pixel coordinates, regardless of whether a PixelMapper is active. This ensures that downstream consumers can always overlay detections on the original image without coordinate conversion.

When a mapper is active (e.g., radial distortion correction), center_mapped provides the corresponding position in the working frame (undistorted pixel space). The working-frame center is used internally for homography fitting and completion, but the image-space center remains the canonical output.

Ellipse coordinate frame

The ellipse_outer and ellipse_inner fields use the Ellipse type with five parameters:

ParameterDescription
cx, cyEllipse center
aSemi-major axis length
bSemi-minor axis length
angleRotation angle in radians

When no mapper is active, the ellipse coordinates are in image space. When a mapper is active, ellipses are in the working frame (undistorted pixel space), because edge sampling and fitting operate in that frame. This means that ellipse_outer.cx may differ from center[0] when a mapper is active.

Markers without decoded IDs

Markers with id: None were detected (ellipse fitted successfully) but failed the codebook matching or structural verification stage. Possible reasons include:

  • Hamming distance to the nearest codeword exceeded the threshold.
  • Decode confidence fell below the minimum.
  • ID contradicted board-local structural consistency in id_correction.
  • Insufficient contrast in the code band.

These markers still have valid center, ellipse_outer, and fit fields. They can be useful for distortion estimation or as candidate positions, but they do not contribute to the homography fit.

ID/board consistency contract

Final emitted markers enforce strict ID/layout consistency:

  • if id is Some(i), then board_xy_mm is present and equals the active board layout coordinate of i
  • if id is None, then board_xy_mm is omitted
  • if a decoded ID is not found in the active board layout, it is cleared before output (id=None, board_xy_mm=None)

Serialization

DetectedMarker derives serde::Serialize and serde::Deserialize. All Option fields use #[serde(skip_serializing_if = "Option::is_none")], so absent fields are omitted from JSON output.

Example JSON

A fully decoded marker:

{
  "id": 127,
  "board_xy_mm": [40.0, 24.0],
  "confidence": 0.92,
  "center": [800.5, 600.2],
  "ellipse_outer": {
    "cx": 800.5, "cy": 600.2, "a": 16.3, "b": 15.9, "angle": 0.05
  },
  "ellipse_inner": {
    "cx": 800.4, "cy": 600.1, "a": 10.7, "b": 10.4, "angle": 0.06
  },
  "fit": {
    "n_angles_total": 64,
    "n_angles_with_both_edges": 60,
    "n_points_outer": 60,
    "n_points_inner": 55,
    "ransac_inlier_ratio_outer": 0.95,
    "ransac_inlier_ratio_inner": 0.91,
    "rms_residual_outer": 0.28,
    "rms_residual_inner": 0.35
  },
  "decode": {
    "observed_word": 52419,
    "best_id": 127,
    "best_rotation": 7,
    "best_dist": 0,
    "margin": 4,
    "decode_confidence": 0.92
  },
  "source": "fit_decoded"
}

A marker that was detected but not decoded:

{
  "confidence": 0.3,
  "center": [200.1, 150.8],
  "ellipse_outer": {
    "cx": 200.1, "cy": 150.8, "a": 14.2, "b": 12.1, "angle": 0.78
  },
  "fit": {
    "n_angles_total": 64,
    "n_angles_with_both_edges": 31,
    "n_points_outer": 42,
    "n_points_inner": 0,
    "ransac_inlier_ratio_outer": 0.72,
    "rms_residual_outer": 0.89
  },
  "source": "fit_decoded"
}