suboptimumg.loganalysis.laps#

Lap division, splitting, and alignment utilities.

Ported from vnav_lap_analyzer.ipynb cells 8 (lap detection) and 16 (splitting + alignment).

suboptimumg.loganalysis.laps.align_laps(lap_dfs, baseline_lap=None, sf_line=None, pos_cols=('posN', 'posE'), time_col='time_s')[source]#

Align all laps to a baseline via translation-only optimisation.

Uses cKDTree + Nelder-Mead to minimise mean log-nearest-neighbour distance after a rigid translation (no rotation, preserving yaw).

If sf_line is given, laps are re-trimmed at the S/F line after alignment.

Parameters:
  • lap_dfs (dict[int, DataFrame]) – As returned by split_laps().

  • baseline_lap (int or None) – Lap number to align to. None picks the fastest (shortest LapTime).

  • sf_line (((n1,e1), (n2,e2)) or None) – S/F line for re-trimming.

  • pos_cols (str)

  • time_col (str)

Returns:

Aligned (and optionally trimmed) lap DataFrames.

Return type:

dict[int, pd.DataFrame]

suboptimumg.loganalysis.laps.detect_lap_crossings(df, sf_line, time_col='time_s', pos_cols=('posN', 'posE'), min_lap_time_s=20.0, start_time=None, end_time=None)[source]#

Detect start/finish line crossings and label laps.

Works in local cartesian coordinates (posN, posE). The sf_line is a pair of (N, E) tuples defining the S/F segment.

If sf_line contains lat/lon tuples instead, convert them first with gps_to_local_cartesian and pick coordinates from the map plot.

Parameters:
  • df (pd.DataFrame) – Must contain pos_cols and time_col.

  • sf_line (((n1, e1), (n2, e2))) – Start/finish line endpoints in local cartesian.

  • time_col (str)

  • pos_cols ((str, str)) – (North column, East column).

  • min_lap_time_s (float) – Minimum time between crossings to accept as a real lap.

  • start_time (float or None) – Time window to search within. None uses full range.

  • end_time (float or None) – Time window to search within. None uses full range.

Returns:

Copy of df with a Lap column added (0 = before first crossing, 1 = first lap, etc.).

Return type:

pd.DataFrame

suboptimumg.loganalysis.laps.plot_lap_overlay(lap_dfs, baseline_lap=None, sf_line=None, pos_cols=('posN', 'posE'), time_col='time_s', max_display_hz=100.0)[source]#

Overlay all laps on a 2D scatter plot.

Parameters:
  • lap_dfs (dict[int, DataFrame])

  • baseline_lap (int or None)

  • sf_line (S/F line endpoints or None)

  • pos_cols ((north_col, east_col))

  • time_col (str) – Time column used to estimate sample rate for downsampling.

  • max_display_hz (float) – Target display sample rate. 0 or None disables.

Return type:

go.Figure

suboptimumg.loganalysis.laps.split_laps(df, buffer_distance_m=5.0, min_lap_distance_m=20.0, pos_cols=('posN', 'posE'), time_col='time_s')[source]#

Split a DataFrame by Lap column with arc-length buffer.

Each lap DataFrame gets LapTime and LapDist columns added.

Parameters:
  • df (pd.DataFrame) – Must have Lap, pos_cols, and time_col columns.

  • buffer_distance_m (float) – Extra path distance before/after each lap boundary.

  • min_lap_distance_m (float) – Discard laps shorter than this.

  • pos_cols (str)

  • time_col (str)

Returns:

Keyed by lap number (>0).

Return type:

dict[int, pd.DataFrame]