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
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 aRuntimeError.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_FACTORgate 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, default30.0px. Minimum surviving vertex count perTERMINATOR_ARCfor feasibility. Shorter terminators do not constrain a 2-D translation enough to be worth the LM iteration.spurious_dt_rms_factor— float, default5.0(dimensionless). Final DT residual exceeding this many terminator-sigmas marks the result spurious.spurious_dt_floor_px— float, default4.0px. 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, default6(count). Below this Tukey-inlier count the M-estimator covariance is uninformative; the result is flagged spurious.at_edge_tolerance_px— float, default1.0px. A converged offset whose absolute distance from any search-window axis bound falls within this tolerance is flaggedat_edge. Matches the bilinear-DT half-cell width.rotation_at_edge_fraction— float, default0.95(dimensionless). Whenfit_camera_rotationis true, the converged rotation magnitude tripsat_edgeonce it crosses this fraction of the per-imagemax_rotation_degcap.
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 consumedTERMINATOR_ARCfeatures.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-featureTerminatorArcFlags). 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:
src/nav/nav_technique/nav_technique_body_terminator.py—BodyTerminatorNavand its private aggregation / polyline-mask helpers.src/nav/nav_technique/dt_fitting.py— the shared coarse-NCC and LM-refinement helpers documented at DT Fitting (Shared Polyline-vs-Image Fitter).src/nav/nav_orchestrator/image_derivatives.py— the per-image gradient / DT derivatives attached toNavContext; documented at Image Derivatives (Shared Gradient and Edge DT).src/nav/nav_technique/confidence.py— the shared sigmoid-combination formula evaluator; documented at Confidence Calibration (Shared Sigmoid-of-Linear Combination).src/nav/nav_technique/diagnostics.py—BodyTerminatorDiagnostics; documented at Per-Technique Diagnostics (Shared Dataclass Family).
Public class
BodyTerminatorNav, base
NavTechnique. Self-registers via
__init_subclass__ so the orchestrator’s NavTechnique._registry discovers it.
Class attributes:
name—'BodyTerminatorNav'.accepts_feature_types—frozenset({TERMINATOR_ARC}).requires_prior—False. 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
visible_terminator_arc_fraction— vertex-weighted average of the per-feature visible-arc fraction across consumedTERMINATOR_ARCfeatures. 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():
Open a logged section. Fail fast (
RuntimeError) if eitherimage_edge_dt_extorimage_gradient_vu_extis missing.Filter the offered features down to
TERMINATOR_ARCpolylines whose surviving vertex count is at leastmin_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.Build a binary polyline mask and pull the search-window margin off the observation via
search_window_for_obs(). Runcoarse_ncc_search()on the polyline mask and the thresholded edge mask to obtain an integer seed offset.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 viarotation_pivot_distance_px().Call
lm_subpixel_refine()with the polyline, per-vertex sigmas, the edge DT, the gradient image, the integer seed, and the rotation options.Result-shape branches on
fit_camera_rotation:No rotation fit.
covariance_px2is the (2, 2) translation block. Any non-(2, 2) covariance returned by the refiner is logged at WARNING and truncated.rotation_radandsigma_rotation_radareNone.Rotation fit.
covariance_px2is the (3, 3) translation + rotation information matrix.rotation_radis the converged angle andsigma_rotation_radis the square root of its diagonal. An unexpected covariance shape raisesRuntimeError.
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.
Build a
BodyTerminatorDiagnostics, evaluate the confidence spec viaevaluate_sigmoid_combination(), log the per-term breakdown vialog_confidence_breakdown(), and assemble theNavTechniqueResult.
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, imageN1597846115_2)A high-phase terminator arc with no other features in the FOV.
BodyTerminatorNavconsumes the singleTERMINATOR_ARCfeature 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, imageN1484593951_2)Rhea visible in the upper right with about 22 % of the disc off-frame. The body model emits a
TERMINATOR_ARCfeature alongsideLIMB_ARCandBODY_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. TheBodyLimbNavfit 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, imageN1487595731_1)Dione and Rhea both visible and overlapping at phase angle approximately 90 degrees. Two
TERMINATOR_ARCfeatures are offered;BodyTerminatorNavfuses 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 reportsstatus=conflicted.