TelemetrySign in

Lap detection

Algorithm lap-detect-1.0.0+laps-1.0.0+centerline-1.0.0+projection-1.0.0 · source engine/src/dtta/engine/laps/{gate,crossings,classify}.py.

Dirt ovals are short, GPS is noisy at 10–18 Hz, and the car also drives pit roads and the infield. Lap detection therefore uses a gate with several guards and a full-loop progress check, never a single "distance to a point" rule.

Steps

  1. Working set — valid GPS samples in local metres, split into runs at gaps > 1 s. Heading is taken from a lap_heading_smooth_samples (5) moving average of positions and is undefined below lap_heading_min_speed_ms (2 m/s).
  2. Racing surface — samples faster than lap_race_min_speed_ms (8 m/s ≈ 18 mph) define the loop; a preliminary centerline gives its length L0L_0 and direction (sign of the net heading change: counter-clockwise = left turns).
  3. Start/finish gate — placed automatically at the centre of the longest straight (lowest curvature over the longest arc), perpendicular to the centerline, half-width w=max(15m,  3×median lateral spread)w = \max(15\,\mathrm{m},\; 3 \times \text{median lateral spread}) (lap_gate_min_halfwidth_m, lap_gate_spread_factor). Lap times of complete laps do not depend on where the gate sits.
  4. Crossings — consecutive samples whose segment intersects the gate, with speed ≥ lap_cross_min_speed_ms (3 m/s), heading within lap_gate_heading_max_deg (45°) of the gate direction, and at least lap_min_lap_s (6 s) after the previous accepted crossing. The crossing time is interpolated: tc=ti1+(titi1)λt_c = t_{i-1} + (t_i - t_{i-1})\,\lambda, where λ\lambda is the intersection parameter along the sample segment. Every rejected crossing keeps its reason. Crossing confidence: clip(1n/w)clip(cosθ)\mathrm{clip}(1 - |n|/w)\cdot\mathrm{clip}(\cos\theta) from the lateral offset nn at the gate and the heading error θ\theta.
  5. Lap assembly — intervals between consecutive crossings in one run. Before the first crossing: partial_first; after the last: partial_last.
  6. Progress check — each candidate lap is projected onto the centerline (see Track progress). A genuine lap covers ≥ lap_progress_min_fraction (95 %) of L0L_0, never steps backward more than lap_backward_jump_m (10 m), has an integrated distance within lap_distance_tolerance (±10 %) of L0L_0, and does not lose projection for more than proj_lost_limit (5) consecutive samples or 5 % of its samples.

Classification

Evaluated in this order; the first matching rule wins and the reason is stored on the lap.

result rule parameters
pit more than lap_pit_min_s (2 s) below lap_pit_speed_ms (2 m/s) stopped on track / pit road / red flag
invalid short_cut / reversed / distance_mismatch / projection_lost progress check failed see step 6
invalid gps_quality valid fraction < lap_gps_valid_fraction_min (0.9) or a gap > lap_max_gap_s (1 s)
invalid implausibly_fast lap time < lap_implausibly_fast_factor (0.95) × L0/vmaxL_0 / v_{max} physical bound
caution lap time > lap_caution_factor (1.35) × fastest candidate, or mean speed < lap_caution_speed_fraction (0.7) × best mean speed
racing everything else

Observed on real data: racing laps 18.8–22.9 s, caution laps 39–52 s, first partial 47–52 s, so the 1.35 factor separates cleanly. The fastest candidate is used (not a percentile) because sessions with a single green lap exist.

Only racing laps can be chosen as reference or comparison; caution laps are shown with their delta to the fastest lap for context.

Resolution of a lap time

The crossing interpolation resolves the crossing to a fraction of a sample interval; with ~0.2 m positional noise at 35 m/s the crossing time noise is of the order of 5–10 ms. Because the reference and the comparison lap cross the same gate with the same receiver, GPS latency cancels in the delta.

Verified

  • Hamilton County HERO9 heat: 8 racing laps 20.70–21.49 s, one caution, two partials; I-35 HERO11 heat: pace laps classified caution, green laps racing. Lap times match the independent heading-based reconnaissance within 0.3 s.
  • Synthetic oval: exact lap count and times; an infield excursion produces no lap; a stop on track → pit; a slow lap → caution. Tests: engine/tests/unit/test_laps_geometry.py, engine/tests/synthetic, regression goldens (lap count, classes, times ±5 ms).