Reading one lap

Guides

Reading one lap

Corners, braking and full-throttle zones, g-forces and frozen car data from a single lap’s telemetry.

Everything lapbox reads from a single lap’s telemetry: a summary, a uniform grid, corners, braking and full-throttle zones, g-forces, and the stretches where the car data froze. The lap is Verstappen’s fastest in Silverstone 2025 qualifying.

Load a lap

Python
import fastf1

session = fastf1.get_session(2025, "Silverstone", "Q")
session.load(weather=False, messages=False)
tel = session.laps.pick_drivers("VER").pick_fastest().get_telemetry()

get_telemetry() merges the car data with the position data and adds Distance, which every function here needs.

Summary

Python
from lapbox.telemetry import channel_summary

print({k: round(v, 1) for k, v in channel_summary(tel).items()})
Output
{'lap_distance': 5825.9, 'max_speed': 324.0, 'mean_speed': 245.4, 'min_speed': 89.0, 'max_rpm': 12102.0, 'full_throttle_pct': 65.6, 'braking_pct': 12.4, 'max_gear': 8.0}

full_throttle_pct and braking_pct are shares of samples, and FastF1’s samples are spaced in time, not distance.

A uniform distance grid

Telemetry arrives at uneven distances. resample interpolates the channels onto an even grid, for plotting or your own maths:

Python
from lapbox.telemetry import resample

grid = resample(tel, num_points=500)
print(grid.head(3).round(1).to_string(index=False))
Output
 Distance  Speed  Throttle  Brake     RPM  nGear  DRS
      0.1  270.1     100.0    0.0 10878.9    6.0  8.0
     11.8  272.2     100.0    0.0 10892.1    6.7  8.0
     23.4  274.3     100.0    0.0 10939.5    7.0  8.0

Interpolation blends neighbouring samples, so a gear of 6.7 is a point between two samples, not a gear. Pass channels= to choose what to carry — any numeric column, such as X and Y.

Corners

detect_corners finds apexes as prominent speed minima; corner_analysis adds entry, minimum and exit speed and a class:

Python
from lapbox.telemetry import corner_analysis, detect_corners

apexes = detect_corners(tel)
print(f"{len(apexes)} corners")
grid = resample(tel, num_points=1000, channels=("Speed",))
for corner in corner_analysis(grid["Distance"], grid["Speed"], apexes["apex_distance"].tolist())[:4]:
    print(corner)
Output
8 corners
{'corner': 1, 'apex_distance': 893.8, 'entry_speed': 213.7, 'min_speed': 110.3, 'exit_speed': 143.3, 'speed_class': 'slow'}
{'corner': 2, 'apex_distance': 1032.4, 'entry_speed': 153.0, 'min_speed': 89.0, 'exit_speed': 167.5, 'speed_class': 'slow'}
{'corner': 3, 'apex_distance': 2008.2, 'entry_speed': 210.6, 'min_speed': 180.0, 'exit_speed': 171.3, 'speed_class': 'medium'}
{'corner': 4, 'apex_distance': 2161.6, 'entry_speed': 180.6, 'min_speed': 116.0, 'exit_speed': 168.7, 'speed_class': 'slow'}

Braking and full-throttle zones

Python
from lapbox.telemetry import detect_braking_zones, detect_full_throttle_zones

braking = detect_braking_zones(tel, min_length=20)
flat_out = detect_full_throttle_zones(tel, min_length=100)
print(f"{len(braking)} braking zones, {len(flat_out)} full-throttle zones")
print(braking.head(3).round(1).to_string(index=False))
Output
7 braking zones, 7 full-throttle zones
 start_distance  end_distance  length  entry_speed  min_speed  max_speed
          787.6         877.8    90.2        268.0      119.0      268.0
          965.1        1008.5    43.4        158.0      101.0      158.0
         1855.1        1953.1    98.0        318.0      190.0      318.0

A zone is a contiguous stretch with the brake on (or the throttle at 99 % or more); min_length drops short blips. FastF1’s Brake is on or off, so a zone says where the driver braked, not how hard.

G-forces

gforces derives longitudinal g from speed and lateral g from the curvature of the racing line. It needs speed and position on one grid:

Python
import numpy as np
from lapbox.telemetry import gforces

grid = resample(tel, num_points=600, channels=("Speed", "X", "Y"))
a_long, a_lat = gforces(grid["Distance"], grid["Speed"], grid["X"], grid["Y"])
print(f"hardest braking {a_long.min():.1f} g")
print(f"cornering load (95th percentile) {np.percentile(np.abs(a_lat), 95):.1f} g")
Output
hardest braking -3.9 g
cornering load (95th percentile) 5.2 g

Frozen car data

On some 2026 laps the timing feed repeats one car reading for one to 25 seconds while the car moves on. Distance is integrated from that frozen speed, so the rest of the lap is misplaced — the five worst laps measured came out 380 m short to 85 m long. detect_frozen_stretches finds these stretches; on this lap there are none:

Python
from lapbox.telemetry import detect_frozen_stretches

print(detect_frozen_stretches(tel).empty)
Output
True

A stretch counts when speed, RPM, gear and throttle all repeat for a second or more, or for half a second with a throttle above 100. On 183 laps from 2023, 2025 and three 2026 sessions without refusals it flagged none. Here is Bottas’s China 2026 qualifying lap, drawn with channel_traces, its frozen stretches shaded:

Bottas's fastest lap at China 2026 qualifying: speed, throttle, RPM and gear against distance, with two stretches shaded grey where the car data froze.
channel_traces(bot, channels=["Speed", "Throttle", "RPM", "nGear"], driver="BOT"): flat lines in the grey stretches are one reading repeated, not the car.

Rebuilding the lost speed from the car’s positions was tried and isn’t reliable yet — see Limits.

Everything at once

TelemetryAnalyzer runs the summary, both zone detectors and corner detection in one call:

Python
from lapbox.telemetry import TelemetryAnalyzer

lap = TelemetryAnalyzer().analyze(tel, driver="VER")
print(lap.driver, len(lap.corners), len(lap.braking_zones), round(lap.summary["max_speed"]))
Output
VER 8 7 324

It returns a LapAnalysis. Note that it detects zones with no minimum length, so its counts can differ from the filtered ones above.

These docs describe lapbox 0.1.0. Something wrong or missing? Open an issue.