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
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
from lapbox.telemetry import channel_summary
print({k: round(v, 1) for k, v in channel_summary(tel).items()}){'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:
from lapbox.telemetry import resample
grid = resample(tel, num_points=500)
print(grid.head(3).round(1).to_string(index=False)) 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.0Interpolation 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:
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)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
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))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.0A 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:
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")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:
from lapbox.telemetry import detect_frozen_stretches
print(detect_frozen_stretches(tel).empty)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:

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:
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"]))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.