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
- High precision — every rejection must be defensible to a human reviewer (target ≥ 90%).
- Free recall — catch GPS-caused bad maps; accept that MapGen-internal bad maps (clean GPS → bad map) are out of scope.
- 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.
-
Local projection
Project
lon/latto 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, mFlat-earth approximation; error ≪ 1 cm for clip extents under 2 km.
-
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.
-
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. -
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 percentageinvalid_pct. -
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 test —
phase_ratio = std(worst-phase residuals) / median (std of the other phases). - Spectral test —
fft_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. - Phase test —
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 enabledfalseMaster on/off. The gate is a no-op ( SKIPPED) when false.actionfailfail— reject the clip and fail the pipeline stage;warn— log and write metrics, never reject.fail_on_flagsall 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_m0.60Tolerate larger single-frame lateral spikes lat_noisy_p50_m0.05Tolerate a higher sustained-noise median floor lat_noisy_p95_m0.30Tolerate a higher sustained-noise tail lat_1hz_amp_gate_m0.20Require larger 1 Hz amplitude before failing -
1vHz injection confirmation
Key Default Purpose phase_ratio_min4.0Worst-phase / median-of-others std ratio fft_ratio_min3.0FFT-peak / background ratio at the injection frequency injection_period_hz1.0Expected raw-fix injection frequency sample_rate_hz10.0Native GPS rate (drives phase period and FFT bins) -
Coverage, frame filtering and clip requirements
Key Default Purpose invalid_pct_dropout5.0Dropout % that, combined with lateral noise, fails the clip min_speed_m_s5.0Exclude slow/parked frames (unreliable at low speed) max_yaw_rate_deg_s5.0Exclude turns (curvature inflates the GPS error estimate) min_valid_frames20Below 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 |