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
c0sits 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 % ofc0(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 twoOuterHypothesisstructs, sorted best-first, each withr_outer_px,peak_strength,theta_consistency, and an optionalradius_model(the attachedRadialHarmonicwhen the eccentricity model earned its place).status:OkorFailedwith a diagnostic reason.
Configuration
The OuterEstimationConfig struct controls this stage:
| Parameter | Default | Description |
|---|---|---|
search_halfwidth_px | 4.0 | Search half-width around expected radius |
radial_samples | 64 | Number of radial samples per ray |
aggregator | Median | Angular aggregation method |
grad_polarity | DarkToLight | Expected edge polarity |
min_theta_coverage | 0.6 | Minimum fraction of valid rays |
min_theta_consistency | 0.35 | Minimum fraction of rays agreeing with peak (or with the attached eccentricity model) |
allow_two_hypotheses | true | Emit runner-up hypothesis if strong enough |
second_peak_min_rel | 0.85 | Runner-up must be this fraction of best peak |
refine_halfwidth_px | 1.0 | Per-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