Body Terminator Fit (BodyTerminatorNav)

Overview

BodyTerminatorNav recovers a single translation from one or more body terminator polylines by aligning each polyline against the image’s edge-distance-transform. The technique consumes every TERMINATOR_ARC feature offered by the orchestrator, weights every vertex of a given body uniformly by an inverse-variance derived from that body’s mean per-vertex normal sigma, and runs the same coarse-NCC plus Tukey-reweighted Levenberg-Marquardt refinement that DT Fitting (Shared Polyline-vs-Image Fitter) describes for the limb fit. The output is the joint translation that minimises the summed weighted squared distance from the model polylines to the image edges, plus a covariance derived from the M-estimator information matrix at convergence.

Feasibility passes when at least one offered TERMINATOR_ARC carries enough surviving vertices to constrain a 2-D translation; feasibility fails when every offered TERMINATOR_ARC has fewer than the minimum-arc-length floor (a body whose phase angle is too low for a usable terminator, or whose terminator is mostly hidden by the FOV boundary).

Theory

The technique belongs to the distance-transform family of polyline fitters: predicted polylines are shifted as a rigid body until their vertices lie as close as possible to the nearest image edge, where “as close as possible” is measured against a precomputed image-edge distance transform. The shared algorithmic infrastructure handles the heavy lifting; this section describes the cost function and conventions specific to the terminator fit.

Cost function

The technique minimises

\[C(\Delta v, \Delta u, \theta) = \sum_{i} w_{i}(\Delta v, \Delta u, \theta) \, \mathrm{DT}\bigl[\,R(\theta)\,(x_{i} - x_{p}) + x_{p} + (\Delta v, \Delta u)\,\bigr]^{2}\]

where \(x_{i}\) are the input vertices concatenated across every consumed TERMINATOR_ARC, \(x_{p}\) is the rotation pivot (the centroid of the concatenated vertices), \(R(\theta)\) is the in-plane rotation matrix, \(\mathrm{DT}\) is the bilinearly sampled image-edge distance transform, and the per-vertex weight \(w_{i}\) is the product of a per-body uniform precision (the inverse mean variance across the body’s terminator polyline) and a Tukey biweight evaluated at the scaled DT residual. When the per-instrument camera-rotation flag is off the parameter vector collapses to \((\Delta v, \Delta u)\) and the rotation term is dropped.

Per-body uniform weighting

Unlike the limb fit, every vertex of a given body’s terminator shares one inverse-variance weight derived from that body’s mean per-vertex normal sigma. Cross-body weighting reflects the structural fact that low-albedo bodies provide tighter terminators than high-albedo ones (an albedo gradient blurs the apparent terminator more than a smooth surface would), so the mean per-vertex sigma already captures the per-body terminator quality and a single per-body weight avoids over-weighting a long terminator from a low-confidence body.

Search strategy

The fit proceeds in the same two stages as the limb fit: a coarse integer cross-correlation between the rendered terminator polyline mask and the thresholded image edge mask, then a sub-pixel Levenberg-Marquardt refinement against the bilinearly sampled DT. See DT Fitting (Shared Polyline-vs-Image Fitter) for the per-iteration mechanics.

Robustness

The Tukey biweight redescender drops vertices whose scaled residuals exceed the Holland-Welsch cutoff. Polarity filtering — comparing the model normal at each vertex against the local image gradient direction — is enabled with the model normals oriented so the gradient should point from the dark side toward the lit side at the terminator. Polarity-rejected vertices contribute a synthetic near-infinite residual on every iteration so the Tukey biweight zeroes their weight on the first reweighting; this keeps the terminator fit from latching onto the opposite-polarity edge of a neighbouring body’s limb.

Restrictions and assumptions

  • The orchestrator must supply both an image-edge distance transform and a per-pixel gradient vector image on the per-image NavContext; in their absence the navigation aborts with a RuntimeError.

  • The terminator is assumed to be photometrically distinguishable from the limb. At phase angles below approximately 3 degrees the terminator is too close to the limb to be usefully separated; the model-side TERMINATOR_MIN_PHASE_FACTOR gate suppresses emission in that regime so the technique never sees those features.

  • The albedo penalty per body is supplied by the model side via the per-body BodyShape; the technique reads it off the feature flags but does not re-derive it.

  • Multi-body inputs are fused into a single translation by concatenating their per-vertex arrays. The joint-translation parameterisation cannot represent disagreement between bodies about the offset; if SPICE relative geometry is wrong the joint fit walks toward the higher-vertex-count body and the lower-vertex body’s residuals appear as outliers the Tukey weight zeroes out.

Sources of uncertainty

The reported covariance is the Moore-Penrose pseudoinverse of the M-estimator information matrix at convergence, scaled by the per-vertex Tukey weights. It does not capture systematic biases (an under-modelled per-body albedo gradient propagates straight into the covariance) and it does not capture model-side uncertainty in the SPICE prediction itself. When the converged offset sits within a small tolerance of any axis bound of the search window, or when the rotation parameter is at the configured fraction of its cap, the result is flagged at_edge and the confidence formula’s hard-zero gate forces confidence to zero. The spurious tests gate on the Tukey-weighted DT residual RMS, the unweighted (raw) DT residual RMS against the same threshold (so a fit where Tukey rejects a wholly mis-aligned arc cannot pass on its collapsed weighted RMS), the degenerate flag, the inlier count and fraction, and the LM displacement from the coarse seed; when any of these fails, the result is flagged spurious and similarly forced to zero.

Configuration

All numeric tunables for this technique live in techniques.BodyTerminatorNav.tuning in src/nav/config_files/config_510_techniques.yaml.

  • min_arc_px — float, default 30.0 px. Minimum surviving vertex count per TERMINATOR_ARC for feasibility. Shorter terminators do not constrain a 2-D translation enough to be worth the LM iteration.

  • spurious_dt_rms_factor — float, default 5.0 (dimensionless). Final DT residual exceeding this many terminator-sigmas marks the result spurious.

  • spurious_dt_floor_px — float, default 4.0 px. Floor of the spurious-detection threshold; terminators are softer than limbs so the floor sits one pixel wider than the limb fit’s.

  • spurious_min_inliers — int, default 6 (count). Below this Tukey-inlier count the M-estimator covariance is uninformative; the result is flagged spurious.

  • at_edge_tolerance_px — float, default 1.0 px. A converged offset whose absolute distance from any search-window axis bound falls within this tolerance is flagged at_edge. Matches the bilinear-DT half-cell width.

  • rotation_at_edge_fraction — float, default 0.95 (dimensionless). When fit_camera_rotation is true, the converged rotation magnitude trips at_edge once it crosses this fraction of the per-image max_rotation_deg cap.

Per-instrument overrides

The six keys above are global; the per-instrument YAML files in src/nav/config_files/config_4N0_inst_*.yaml do not override any of them. The search-window margin used by the at-edge test comes from the per-instrument InstrumentSettings rather than from this block.

Confidence formula

The technique reports a calibrated confidence in \([0, 1]\) produced by the shared sigmoid combination; see Confidence Calibration (Shared Sigmoid-of-Linear Combination) for the per-term arithmetic. The formula spec is techniques.BodyTerminatorNav in the same YAML file and consumes attributes off BodyTerminatorDiagnostics plus the at_edge and spurious flags.

  • visible_terminator_arc_fraction — alpha = 2.0, offset = 0.0, divisor = 1.0, no cap. Vertex-weighted average of the per-feature visible-arc fraction across consumed TERMINATOR_ARC features.

  • dt_fit_rms_px — alpha = -1.0, offset = 0.0, divisor = 1.0, no cap. Final root-mean-square DT residual after LM convergence; smaller is sharper.

  • visible_arc_px — alpha = 0.4, offset = 0.0, divisor = 100.0, cap at 1.0. Total surviving polyline length in pixels, capped after normalisation. More polyline earns confidence up to a 100-pixel saturation point.

  • mean_phase_angle_factor — alpha = 1.0, offset = 0.0, divisor = 1.0, no cap. Mean of \(\sin(\phi)\) across consumed terminators (read off the per-feature TerminatorArcFlags). High-phase scenes earn confidence; low phase pulls confidence down.

  • mean_albedo_penalty — alpha = -1.5, offset = 0.0, divisor = 1.0, no cap. Mean of the per-body albedo penalty (read off the per-feature reliability breakdown). High-albedo bodies pull confidence down; uniform low-albedo bodies leave it unchanged.

Hard-zero gate: at_edge and spurious either firing forces confidence to zero before the sigmoid evaluates. The constant baseline is \(\alpha_{0} = -1.0\). No post-sigmoid hard_cap is applied.

Implementation

Source files:

Public class BodyTerminatorNav, base NavTechnique. Self-registers via __init_subclass__ so the orchestrator’s NavTechnique._registry discovers it.

Class attributes:

  • name'BodyTerminatorNav'.

  • accepts_feature_typesfrozenset({TERMINATOR_ARC}).

  • requires_priorFalse. Runs in pass 1 of the orchestrator’s two-pass pipeline.

  • confidence_attributes{'at_edge', 'spurious', 'visible_terminator_arc_fraction', 'visible_arc_px', 'dt_fit_rms_px', 'lm_iterations', 'tukey_inlier_count', 'mean_phase_angle_factor', 'mean_albedo_penalty'}.

Public methods (autodocumented at nav.nav_technique): is_feasible() and navigate().

Diagnostics

BodyTerminatorDiagnostics:

  • visible_terminator_arc_fraction — vertex-weighted average of the per-feature visible-arc fraction across consumed TERMINATOR_ARC features. Consumed by the confidence formula.

  • visible_arc_px — total surviving polyline arc length in pixels. Consumed by the confidence formula.

  • dt_fit_rms_px — weighted RMS DT residual at the converged pose. Consumed by the confidence formula and by the spurious-detection gate.

  • lm_iterations — number of LM iterations actually performed.

  • tukey_inlier_count — number of vertices that retained a strictly positive Tukey weight at the final estimate. Consumed by the spurious-detection gate.

Call path traced through navigate():

  1. Open a logged section. Fail fast (RuntimeError) if either image_edge_dt_ext or image_gradient_vu_ext is missing.

  2. Filter the offered features down to TERMINATOR_ARC polylines whose surviving vertex count is at least min_arc_px, then concatenate the per-feature vertex arrays via the private aggregation helper. The helper also computes per-body sigma scalars (mean of the per-vertex normal sigmas) and the per-body phase / albedo flags.

  3. Build a binary polyline mask and pull the search-window margin off the observation via search_window_for_obs(). Run coarse_ncc_search() on the polyline mask and the thresholded edge mask to obtain an integer seed offset.

  4. Decide whether to fit camera rotation by reading fit_camera_rotation. When rotation is fit, the rotation pivot is set to the centroid of the concatenated vertices and the pivot-to-image-centre distance is computed via rotation_pivot_distance_px().

  5. Call lm_subpixel_refine() with the polyline, per-vertex sigmas, the edge DT, the gradient image, the integer seed, and the rotation options.

  6. Result-shape branches on fit_camera_rotation:

  7. Apply the at-edge tests against translation axis bounds and the rotation cap, plus the spurious tests against the final RMS, the inlier count, and the per-feature sigma-floor multiple.

  8. Build a BodyTerminatorDiagnostics, evaluate the confidence spec via evaluate_sigmoid_combination(), log the per-term breakdown via log_confidence_breakdown(), and assemble the NavTechniqueResult.

The feature_ids field preserves every consumed feature_id so the orchestrator’s curator can attribute each contribution at audit time.

Examples

high_phase_terminator (Cassini ISS NAC, image N1597846115_2)

A high-phase terminator arc with no other features in the FOV. BodyTerminatorNav consumes the single TERMINATOR_ARC feature offered by the body model and converges within ~1 px of the operator-verified offset \((\Delta v, \Delta u) = (5.19, 1.30)\) px.

body_partial_overflow (Cassini ISS NAC, image N1484593951_2)

Rhea visible in the upper right with about 22 % of the disc off-frame. The body model emits a TERMINATOR_ARC feature alongside LIMB_ARC and BODY_DISC; on this scene the terminator fit reports zero Tukey inliers (the LM does not iterate) and the spurious gate forces the confidence to zero. The BodyLimbNav fit is the technique that actually drives the orchestrator’s primary on this image; see Body Limb Fit (BodyLimbNav) for that walk-through.

multi_body (Cassini ISS NAC, image N1487595731_1)

Dione and Rhea both visible and overlapping at phase angle approximately 90 degrees. Two TERMINATOR_ARC features are offered; BodyTerminatorNav fuses them into a joint translation. On this scene the multi-body crescent geometry produces a coarse-NCC seed at a wrong local minimum (~31 px off-axis); the LM converges near that seed and reports sub-pixel RMS with most vertices as inliers, scoring high confidence on a wrong answer. The orchestrator’s ensemble combine, combined with the limb and disc techniques’ agreement around the operator-verified offset \((\Delta v, \Delta u) = (7.03, -18.42)\) px, refuses to commit and reports status=conflicted.