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

Outer Radius Estimation

Before fitting an ellipse to the outer ring edge, the pipeline needs a radius estimate to anchor the search. The outer radius estimator samples radial intensity profiles around the proposal center and identifies the outer ring edge as a peak in the aggregated radial derivative.

Why This Stage Exists

A ring marker has multiple concentric edges (inner ring, code band boundaries, outer ring). Without guidance, an edge sampler might lock onto the wrong edge. This estimator uses the MarkerScalePrior to focus the search on a narrow window around the expected outer radius, avoiding confusion with stronger inner or code-band edges.

Algorithm

Radial Intensity Sampling

From the proposal center, the estimator casts theta_samples (default: 48) radial rays evenly spaced in angle. Along each ray, radial_samples (default: 64) intensity values are sampled at uniform radial steps within a search window:

window = [r_expected - search_halfwidth, r_expected + search_halfwidth]

where r_expected is the nominal outer radius from MarkerScalePrior and search_halfwidth_px (default: 4.0 px) defines the search extent. The window minimum is clamped to at least 1.0 px.

When a PixelMapper is active, sampling is distortion-aware: the DistortionAwareSampler maps working-frame coordinates to image-frame coordinates for pixel lookup, using bilinear interpolation.

Radial Derivative Computation

For each ray, the sampled intensity profile is differentiated using central differences to produce a dI/dr curve:

d[i] = (I[i+1] - I[i-1]) / (2 * r_step)   for interior samples
d[0] = (I[1] - I[0]) / r_step               forward difference at boundary
d[N-1] = (I[N-1] - I[N-2]) / r_step         backward difference at boundary

A 3-point moving average smooth is applied to reduce noise.

Theta Coverage Check

Rays that go out of image bounds are discarded. If the fraction of valid rays falls below min_theta_coverage (default: 0.6), the estimate fails. This prevents unstable results when the marker is partially occluded or near the image boundary.

Polarity Selection and Aggregation

The outer ring edge has a characteristic sign in dI/dr depending on contrast polarity:

  • Dark-to-light (Polarity::Pos): Moving outward, intensity increases at the outer edge (dark ring interior to bright background).
  • Light-to-dark (Polarity::Neg): The opposite convention.

The grad_polarity setting (default: DarkToLight) determines which polarities are tried. In Auto mode, both are evaluated and the best is selected.

For each polarity candidate, the per-theta derivative curves are aggregated at each radial sample using the configured AngularAggregator:

  • Median (default): Robust to outlier rays from code-band sectors.
  • TrimmedMean: Trims a configurable fraction of extreme values before averaging.

Peak Detection

Local maxima in the aggregated response (or its negation for Neg polarity) are identified. Peaks at the search window boundaries are excluded. Each peak is evaluated for theta consistency: the fraction of per-theta peaks that fall within a tolerance of the aggregated peak radius. Peaks with theta consistency below min_theta_consistency (default: 0.35) are rejected.

Eccentricity-Aware Radius Model

A single aggregated radius describes a circular edge well but penalizes strongly tilted markers: on an eccentric ring the per-theta peaks spread over ±(a − b)/2, so many rays fall outside the constant-radius consistency tolerance near the major and minor axes. To handle this, the estimator fits a second-harmonic radius model to the per-theta peaks:

r(θ) = c0 + c1·cos 2θ + c2·sin 2θ

This is the first-order radial signature of an eccentric ring seen from its center — the edge radius oscillates at twice the ray angle (a circle has c1 = c2 = 0). The fit is a least-squares solve over the basis [1, cos 2θ, sin 2θ] with one outlier-rejection refit round (rays locked onto a wrong, closer edge would otherwise bias a plain fit).

The model is attached to a hypothesis only when it clears every attach gate — otherwise the constant-radius path is kept:

  • Same edge: the model mean c0 sits within half the (half-)search window of the aggregated peak radius, so it describes the same edge.
  • Plausible amplitude: the peak deviation √(c1² + c2²) is at most 35 % of c0 (a real ellipse, not a runaway fit) and at least the constant-radius consistency tolerance (below that it cannot help).
  • 2× SNR over its own residuals: the amplitude is at least twice the RMS of the fit’s inlier residuals — a fit merely chasing a noisy (e.g. heavily blurred) peak field fails this gate.
  • Strictly better: the model’s theta consistency, measured against r(θ) per ray, exceeds the constant-radius consistency. Ties keep the constant-radius path.

When attached, the model drives both the theta-consistency gate and the per-ray refinement: each ray’s local edge search recenters on r_outer_px + (r(θ) − c0), so strongly tilted markers stop losing rays near the axes. Near-circular and noisy peak fields keep the constant-radius path, so the nominal (mostly circular) benchmark suite is unchanged.

Multiple Hypotheses

When allow_two_hypotheses is enabled (default: true), the estimator may return up to two hypotheses if the runner-up peak has at least second_peak_min_rel (default: 85%) of the best peak’s strength. Multiple hypotheses improve robustness when the expected radius is slightly off: both candidates are evaluated in the outer fit stage and the better one is selected.

Output

The OuterEstimate struct contains:

  • r_outer_expected_px: The expected radius from the scale prior.
  • search_window_px: The [min, max] radial search window.
  • polarity: The selected contrast polarity.
  • hypotheses: Up to two OuterHypothesis structs, sorted best-first, each with r_outer_px, peak_strength, theta_consistency, and an optional radius_model (the attached RadialHarmonic when the eccentricity model earned its place).
  • status: Ok or Failed with a diagnostic reason.

Configuration

The OuterEstimationConfig struct controls this stage:

ParameterDefaultDescription
search_halfwidth_px4.0Search half-width around expected radius
radial_samples64Number of radial samples per ray
aggregatorMedianAngular aggregation method
grad_polarityDarkToLightExpected edge polarity
min_theta_coverage0.6Minimum fraction of valid rays
min_theta_consistency0.35Minimum fraction of rays agreeing with peak (or with the attached eccentricity model)
allow_two_hypothesestrueEmit runner-up hypothesis if strong enough
second_peak_min_rel0.85Runner-up must be this fraction of best peak
refine_halfwidth_px1.0Per-theta local refinement half-width

The number of angular rays is not stored in this config. It is passed from EdgeSampleConfig::n_rays (default 48) so the caller can synchronize angular density between outer estimation and edge sampling.

When DetectConfig derives parameters from MarkerScalePrior, the search halfwidth is expanded to cover the full diameter range.

Connection to Adjacent Stages

The outer estimate receives the proposal center from the proposal stage and the expected radius from MarkerScalePrior. Its hypotheses are consumed by the outer ellipse fit stage, which samples edge points near each hypothesis radius and fits ellipses to evaluate which hypothesis produces the best detection.

Source: ring/outer_estimate.rs, ring/radial_profile.rs