Skip to content

112. GPS Quality Filter

The GPS quality filter is a single-trajectory GPS-noise detector. It takes an already-decoded Ego trajectory — per-frame timestamps, WGS84 lon/lat, and a per-frame heading (yaw) — and returns a PASS/FAIL verdict plus supporting metrics and flags. It requires no map, running on input data only, making it inexpensive enough to run as a gate before any further more expensive steps.

112.1 Design goals

  1. High precision — every rejection must be defensible to a human reviewer (target ≥ 90%).
  2. Free recall — catch GPS-caused bad maps; accept that MapGen-internal bad maps (clean GPS → bad map) are out of scope.
  3. Cheap — runs on the Ego-pose stream alone, so it can gate the pipeline early.

112.2 Detection method

The filter processes each clip as follows.

  1. Local projection

    Project lon/lat to local ENU metres around the clip centroid (lat0 = mean(lat)):

    x = (lon − lon[0]) · 111320 · cos(lat0_rad)      # eastward, m
    y = (lat − lat[0]) · 111320                      # northward, m
    

    Flat-earth approximation; error ≪ 1 cm for clip extents under 2 km.

  2. Directional residual

    For each interior frame i (not an endpoint, with finite yaw), take the residual of its position from the midpoint of its two neighbours, then project that residual onto the vehicle heading axes:

    mid     = (P[i−1] + P[i+1]) / 2
    r_vec   = P[i] − mid
    r_lon(i) = r_vec · (cos yaw, sin yaw)     # longitudinal — along heading
    r_lat(i) = r_vec · (−sin yaw, cos yaw)    # lateral — perpendicular, left-positive
    
    • Lateral residual — corrupts MapGen perception by shifting lane offsets and object positions. This is what the gate fails on.
    • Longitudinal residual is speed wobble along the path — cosmetic for MapGen and informational only.
    • The decomposition is invariant under rigid rotation/translation of the trajectory — which is exactly why the algorithm generalizes: any parser that can supply positions + heading can reuse it unchanged.
  3. Usable-frame mask

    Only frames where the Ego is moving (speed > min_speed_m_s, default 5m/s) and on a straight section (|yaw_rate| < max_yaw_rate_deg_s, default 5°/s) are counted. On turns and while the Ego is parked, curvature and low speed inflate the GPS error estimate artificially.

  4. Per-clip aggregation

    On the usable frames, the filter computes the median (p50) and 95th-percentile (p95) of |r_lat| and |r_lon| (reported in cm), plus the dropout percentage invalid_pct.

  5. 1 Hz GPS-injection structure

    GPS receivers natively produce 1 Hz fixes; a 10 Hz stream is usually IMU-aided. When the raw 1Hz fix is injected once per second over IMU dead-reckoning, one frame-index-mod-10 phase carries a distinct error pattern. Detection runs on the total position error, not the directional one. The filter confirms this with two independent tests:

    • Phase testphase_ratio = std(worst-phase residuals) / median (std of the other phases).
    • Spectral testfft_ratio = |rFFT| at the 1 Hz bin / median background magnitude.

    Both must exceed their thresholds, and the amplitude must be non-trivial, for the test to fire.

    Note

    Clips with fewer than min_valid_frames (default 20) valid frames receive an immediate PASS with an info note — no statistics are computed.

112.3 Verdict logic and thresholds

A FAIL is produced by any severity flag; otherwise PASS. Info flags never fail a clip.

112.3.1 Severity flags

Flag Trigger Rationale
lateral-spikes lat_p95 > lat_spike_p95_m (0.60 m) A single frame off by > 60 cm corrupts object/lane geometry. 60 cm sits in the empty band of the Good-run distribution (Goods cluster < 50 cm; next is 121 cm).
noisy-lateral lat_p50 > lat_noisy_p50_m (0.05 m) and lat_p95 > lat_noisy_p95_m (0.30 m) Both median and tail elevated — persistent lateral noise, not occasional spikes. Max Good lat_p50 is 3.6 cm.
1Hz-injection phase_ratio > phase_ratio_min (4) and fft_ratio > fft_ratio_min (3) and lat_p95 > lat_1hz_amp_gate_m (0.20 m) Periodic 1 Hz structure at a non-trivial amplitude. The amplitude gate suppresses harmless noise-floor periodicity (Goods show phase ratios up to ~79 but tiny amplitude).
dropout+lateral invalid_pct > invalid_pct_dropout (5%) and (noisy-lateral or lateral-spikes) Dropout is only fail-worthy as a companion to real lateral noise.

112.3.2 Info flags

  • dropout (N%)
  • 1Hz-structure (low amplitude)
  • lateral-outliers (isolated spikes without sustained baseline)
  • longitudinal (cosmetic)

112.4 Threshold calibration

Thresholds were calibrated on 56 manually tagged runs (24 good and 32 bad). Each gate sits at or beyond the p95 of the relevant Good-run metric.

112.4.1 Confusion matrix

                 DetectorRejects
TaggedBad     False   True
False            23      1       ← 24 Good
True             21     11       ← 32 Bad
Metric Value
Precision 91.7% (11/12 rejections genuinely Bad)
Specificity 95.8% (23/24 Good correctly passed)
Recall 34.4% (11/32 Bad caught)
Accuracy 60.7%

Recall plateaus at approximately 35% because the 21 missed Bad runs all have clean GPS (lat_p95 < 30 cm) — they fail inside MapGen (lane fragmentation, connectivity holes, Ego off-map). A GPS-quality filter cannot flag those; catching them requires a complementary detector on MapGen-output topology, which is out of scope.

112.5 GPS quality filter configuration

All keys are nested under a gps_quality: sub-dict in the parser config: block. The full block is documented (commented, disabled) in nvidia_plugins/parsers/rclog_intervals_parser.yaml.

  • Control

    Key Default Purpose
    enabled false Master on/off. The gate is a no-op (SKIPPED) when false.
    action fail fail — reject the clip and fail the pipeline stage; warn — log and write metrics, never reject.
    fail_on_flags all severity flags Optional allow-list of fail-worthy flags. Any severity flag not listed is downgraded to info.

    When a clip is rejected, the error message includes the path to a metrics file with the full quality report.

  • Lateral residual gates (metres) — the primary tuning surface

    Key Default Raise to…
    lat_spike_p95_m 0.60 Tolerate larger single-frame lateral spikes
    lat_noisy_p50_m 0.05 Tolerate a higher sustained-noise median floor
    lat_noisy_p95_m 0.30 Tolerate a higher sustained-noise tail
    lat_1hz_amp_gate_m 0.20 Require larger 1 Hz amplitude before failing
  • 1vHz injection confirmation

    Key Default Purpose
    phase_ratio_min 4.0 Worst-phase / median-of-others std ratio
    fft_ratio_min 3.0 FFT-peak / background ratio at the injection frequency
    injection_period_hz 1.0 Expected raw-fix injection frequency
    sample_rate_hz 10.0 Native GPS rate (drives phase period and FFT bins)
  • Coverage, frame filtering and clip requirements

    Key Default Purpose
    invalid_pct_dropout 5.0 Dropout % that, combined with lateral noise, fails the clip
    min_speed_m_s 5.0 Exclude slow/parked frames (unreliable at low speed)
    max_yaw_rate_deg_s 5.0 Exclude turns (curvature inflates the GPS error estimate)
    min_valid_frames 20 Below this, short-circuit to PASS

112.6 Known limitations

Topic Detail
fail_on_flags config key Use fail_on_flags (not severity_flags) in your YAML config for the flag allow-list.
GPS sample rate Assumes a 10 Hz GPS source sampled at 1 Hz. For receivers at other rates, adjust sample_rate_hz and injection_period_hz in the config.
IMU heading If the IMU heading data is corrupted, the lateral and longitudinal breakdown of Ego movement is incorrect.
Recall ceiling The filter detects approximately 35% of poor-quality GPS clips. Higher recall requires enabling the MapGen stage.

112.7 Testing and verified behavior

Test suite Result Notes
Unit tests (test_gps_quality.py) 9 passed Covers clean→PASS, lateral-spikes→FAIL, sustained-noise→FAIL, too-few-frames short-circuit, threshold-override relaxation, wrapper SKIPPED/raise/warn/allow-list behavior
evaluate plugin unit tests 22 passed
evaluate integration suite 78 passed Real toolchain
NVIDIA suite 58 passed, 10 skipped Skips are pre-existing and unrelated
E2E flows (test_all_flows.py) 9 passed Includes the 5 rclog_* flows at default enabled: false — no regression to the default path
Parity (CLI vs BaseParser) Verified Identical full result dicts on all 19 real rclog clips; confirms the refactoring reproduces detector v4 exactly

112.8 GPS quality filter source files

File Description
evaluate/src/evaluation_pipeline/plugins/parsers/gps_quality.py Generic detector core (v4)
evaluate/src/evaluation_pipeline/plugins/parsers/__init__.py BaseParser.assess_gps_quality wrapper
evaluate/src/evaluation_pipeline/core/errors.py Defines QualityGateError
evaluate/src/evaluation_pipeline/stages/base.py Catches QualityGateError and maps it to ErrorType.QUALITY_GATE
env/nvidia/evaluation_pipeline/nvidia_plugins/parsers/rclog_parser.py NVIDIA rclog wiring (_run_gps_quality_gate)
env/nvidia/evaluation_pipeline/nvidia_plugins/parsers/rclog_scripts/rclog_to_clipgt.py Cached Ego trajectory decode (gps_samples())
env/nvidia/evaluation_pipeline/nvidia_plugins/parsers/rclog_intervals_parser.yaml Full GPS quality filter config block (commented, disabled by default)
evaluate/tests/pipeline/unit_tests/plugins/test_gps_quality.py Unit tests