suboptimumg.loganalysis#
Log analysis utilities for PERDA-based CAN data.
- class suboptimumg.loganalysis.FittedCurve(data, poly_order)[source]#
Bases:
objectPolynomial fit of tabulated [x, y] data with Horner evaluation.
- Parameters:
data (list of [x, y] pairs) – Raw tabulated input data.
poly_order (int) – Polynomial degree for the least-squares fit.
- x_raw, y_raw
Original input arrays.
- Type:
np.ndarray
- poly_order#
- Type:
int
- coeffs#
Polynomial coefficients in descending degree order
[c_n, c_{n-1}, ..., c_1, c_0], matchingnp.polyfitoutput and the order Horner’s method consumes directly.- Type:
np.ndarray
- class suboptimumg.loganalysis.IrlCar(car, setup)[source]#
Bases:
objectCar + real-world setup parameters for log analysis.
Wraps a
Carinstance (loaded fromcar.yaml) with the extra session-specific parameters fromvehicle_setup.yaml. Provides derived computation methods that combine both sources.- Parameters:
car (Car) – Simulation car object (carries mass, wheelbase, track widths, CG height, etc. via
car.params).setup (dict) – Processed vehicle setup dict (from
load_vehicle_setup()). Curve entries should already beFittedCurveobjects.
- aero_force_from_pots(front_pot_delta_mm, rear_pot_delta_mm, ax_g=0.0)[source]#
Estimate front/rear aero downforce from shock pot deltas.
- Parameters:
front_pot_delta_mm (float | ndarray) – Change in average axle pot displacement from a reference (zero-aero) state, in mm. Positive = compression.
rear_pot_delta_mm (float | ndarray) – Change in average axle pot displacement from a reference (zero-aero) state, in mm. Positive = compression.
ax_g (float | ndarray) – Longitudinal acceleration in g’s (positive = forward accel, negative = braking). Used to subtract weight transfer through the springs (accounting for anti-dive/squat).
- Returns:
Estimated aero downforce at each axle in Newtons.
- Return type:
(front_aero_N, rear_aero_N)
- classmethod from_yaml(car_yaml='parameters/car.yaml', setup_yaml='parameters/vehicle_setup.yaml')[source]#
Load both YAML files and construct an IrlCar.
- Parameters:
car_yaml (str)
setup_yaml (str)
- Return type:
- heave_to_force(heave_m, axle)[source]#
Heave displacement (m) -> vertical spring force (N).
F = heave_stiffness * heave_m- Parameters:
heave_m (float | ndarray)
axle (str)
- Return type:
float | ndarray
- lateral_weight_transfer(ay_g)[source]#
Lateral weight transfer per axle.
Combines elastic (roll stiffness distribution) and geometric (direct load path through CG) components.
- Parameters:
ay_g (float or array) – Lateral acceleration in g’s (positive = turning left).
- Returns:
Weight transfer at the front and rear axles in Newtons. Positive values indicate load transfer to the outside wheel.
- Return type:
(front_wt_N, rear_wt_N)
- left_tire_angle(sw_deg)[source]#
Left tire steer angle via ackermann symmetry:
-f(-x).- Parameters:
sw_deg (float | ndarray)
- Return type:
ndarray
- longitudinal_weight_transfer(ax_g)[source]#
Longitudinal weight transfer on the front axle.
- Parameters:
ax_g (float or array) – Longitudinal acceleration in g’s (positive = accelerating forward, negative = braking).
- Returns:
Change in front axle normal force (N). Negative under acceleration (rear loads up), positive under braking (front loads up). Anti-dive/squat percentages reduce the geometric weight transfer through the springs.
- Return type:
float or np.ndarray
- pot_to_force(pot_mm, axle)[source]#
Shock pot displacement (mm) -> vertical spring force (N).
Combines
pot_to_heave()andheave_to_force().- Parameters:
pot_mm (float | ndarray)
axle (str)
- Return type:
float | ndarray
- pot_to_heave(pot_mm, axle)[source]#
Shock pot displacement (mm) -> chassis heave displacement (m).
heave_m = pot_mm / 1000 * MR- Parameters:
pot_mm (float | ndarray)
axle (str)
- Return type:
float | ndarray
- right_tire_angle(sw_deg)[source]#
Right tire steer angle from the ackermann curve.
- Parameters:
sw_deg (float or array) – Steering wheel angle in degrees. Positive = right turn (right tire is inner, steers more); negative = left turn (right tire is outer, steers less).
- Return type:
ndarray
- suboptimumg.loganalysis.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_lineis 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.
Nonepicks the fastest (shortestLapTime).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.build_comparison_df(lapsim_results, track, lap_df, log_columns=None, dist_col='LapDist')[source]#
Merge QSS results and resampled log data into one DataFrame.
The result is indexed by distance and contains both
qss.*columns and the original log column names side by side.- Parameters:
lapsim_results (LapsimResults)
track (Track)
lap_df (pd.DataFrame)
log_columns (list[str] | None)
dist_col (str)
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.build_qss_track(lap_df, dx=0.1, curvature_col='body.curvature', velocity_col='groundSpeed', pos_n_col='posN', pos_e_col='posE', dist_col='LapDist', cutoff_freq=0.18, max_radius=300.0, show_plots=False)[source]#
Build a QSS-compatible Track from a trimmed lap DataFrame.
Pipeline: resample to uniform dx -> FFT spatial low-pass on curvature -> convert to absolute radius -> fit B-spline on XY -> construct
Track.- Parameters:
lap_df (DataFrame) – Single-lap DataFrame from
split_laps()(must have dist_col, curvature_col, velocity_col, position cols).dx (float) – Distance step in meters (default 0.1).
cutoff_freq (float) – Spatial frequency cutoff for curvature filter (1/m).
max_radius (float) – Radius clamp for near-straight segments.
show_plots (bool) – If True, display verification plots.
curvature_col (str)
velocity_col (str)
pos_n_col (str)
pos_e_col (str)
dist_col (str)
- Return type:
- suboptimumg.loganalysis.compute_accelerations(df, vel_body='pcm.vnav.velocityBody.x', curvature_body='body.curvature', vel_track='track.velocity.x', curvature_track='track.curvature', out_body='body.accLat', out_track='track.accLat')[source]#
Compute lateral acceleration from v^2 * curvature.
Computes both body and track frames by default. All column name arguments have defaults matching upstream function outputs.
- Parameters:
df (DataFrame)
vel_body (str)
curvature_body (str)
vel_track (str)
curvature_track (str)
out_body (str)
out_track (str)
- Return type:
DataFrame
- suboptimumg.loganalysis.compute_bicycle_steer_angle(df, irl_car, sw_col='ludwig.steeringWheel.angle', out_left='front.steerAngle.left', out_right='front.steerAngle.right', out_avg='front.steerAngle.bicycle')[source]#
Compute front tire steer angles from the steering wheel sensor.
Uses the Ackermann polynomial fit in irl_car to convert the steering wheel angle to individual left/right tire angles, then averages them for the bicycle-model front axle steer angle.
All outputs are in degrees.
- Parameters:
df (pd.DataFrame)
irl_car (IrlCar)
sw_col (str)
out_left (str)
out_right (str)
out_avg (str)
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.compute_body_slip_angle(df, track_heading_col='track.heading', yaw_col='pcm.vnav.yawPitchRoll.yaw', vel_body_col='pcm.vnav.velocityBody.x', low_speed_thresh=3.0, out_col='body.slipAngle')[source]#
Compute body slip angle (beta) from track heading and INS yaw.
Standard SAE sideslip: beta = track_heading - yaw, wrapped to +/-180. Positive beta = velocity vector to the right of the nose. Zeroed below low_speed_thresh where heading is unreliable.
- Parameters:
df (DataFrame)
track_heading_col (str)
yaw_col (str)
vel_body_col (str)
low_speed_thresh (float)
out_col (str)
- Return type:
DataFrame
- suboptimumg.loganalysis.compute_cda(df, irl_car, vel_col='groundSpeed', time_col='time_s', rho=1.225, low_speed_thresh=5.0, out_col='aero.cda')[source]#
Estimate CDA from coastdown deceleration (free-rolling assumption).
F_drag = -m * a_x - F_rollingthenCDA = F_drag / (0.5 * rho * v^2)The user should trim the DataFrame to a coasting segment (no throttle, no braking) before calling this, or filter the output to regions where throttle and brake are zero.
- Parameters:
df (DataFrame) – Must contain a velocity column and time index.
irl_car (IrlCar) – Provides mass, frontal area, and rolling resistance coefficient.
vel_col (str) – Velocity source (m/s).
rho (float) – Air density in kg/m^3.
low_speed_thresh (float) – Below this speed (m/s) CDA is zeroed.
out_col (str) – Output column name.
time_col (str)
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.compute_cla(df, irl_car, ref_time_range, vel_col='groundSpeed', pot_fl='ludwig.shockpot.frontLeft', pot_fr='ludwig.shockpot.frontRight', pot_rl='ludwig.shockpot.rearLeft', pot_rr='ludwig.shockpot.rearRight', ax_col='pcm.vnav.linearAccelBody.x', rho=1.225, low_speed_thresh=5.0, out_cla='aero.cla', out_cop='aero.cop')[source]#
Estimate CLA and aero CoP from shock pot compression.
A reference time range defines the “zero-aero” baseline (e.g. car sitting still). At each timestep, the change in average axle pot compression is converted to vertical force via the
IrlCarsuspension math, and the aero contribution is isolated by subtracting longitudinal weight transfer through the springs.- Parameters:
df (DataFrame) – Must contain shock pot columns, a velocity column, and a longitudinal acceleration column.
irl_car (IrlCar) – Provides mass, frontal area, heave stiffness, motion ratios, and anti-dive/squat percentages.
ref_time_range ((t_start, t_end)) – Index (
time_s) window where the car is stationary or at a known-zero-aero condition.vel_col (str) – Velocity source for dynamic pressure.
rho (float) – Air density in kg/m^3.
low_speed_thresh (float) – Below this speed (m/s) CLA and CoP are zeroed.
out_cla (str) – Output column names.
out_cop (str) – Output column names.
pot_fl (str)
pot_fr (str)
pot_rl (str)
pot_rr (str)
ax_col (str)
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.compute_curvature(df, yaw_rate_col='pcm.vnav.compensatedAngularRate.z', vel_body_col='pcm.vnav.velocityBody.x', track_heading_col='track.heading', vel_track_col='track.velocity.x', time_col='time_s', low_speed_thresh=3.0, median_window=8, radius_clip=500.0, out_body_curv='body.curvature', out_body_radius='body.radius', out_track_curv='track.curvature', out_track_radius='track.radius')[source]#
Compute path curvature and radius in body and track frames.
Body frame uses the measured yaw rate directly. Track frame differentiates the track heading. Both apply a rolling median and radius clipping.
- Parameters:
df (DataFrame)
yaw_rate_col (str)
vel_body_col (str)
track_heading_col (str)
vel_track_col (str)
time_col (str)
low_speed_thresh (float)
median_window (int)
radius_clip (float)
out_body_curv (str)
out_body_radius (str)
out_track_curv (str)
out_track_radius (str)
- Return type:
DataFrame
- suboptimumg.loganalysis.compute_elapsed_distance(dfs, pos_n_col='posN', pos_e_col='posE', out_col='distance')[source]#
Cumulative arc-length distance from local cartesian position columns.
- Parameters:
dfs (DataFrame or list[DataFrame])
pos_n_col (str) – Local cartesian position column names (must already exist).
pos_e_col (str) – Local cartesian position column names (must already exist).
out_col (str) – Name of the new distance column.
- Return type:
Same type as input, with out_col added.
- suboptimumg.loganalysis.compute_groundspeed(df, fl_col='pcm.wheelSpeeds.frontLeft', fr_col='pcm.wheelSpeeds.frontRight', out_col='groundSpeed')[source]#
Average front wheel speeds to estimate ground speed.
Uses undriven (front) wheels to avoid slip contamination from the driven (rear) axle.
- Parameters:
df (DataFrame)
fl_col (str)
fr_col (str)
out_col (str)
- Return type:
DataFrame
- suboptimumg.loganalysis.compute_kinematic_steer_angle(df, curvature_body='body.curvature', curvature_track='track.curvature', wheelbase=None, car_yaml='parameters/car.yaml', out_body='body.steerAngle', out_track='track.steerAngle')[source]#
Compute kinematic steer angle from curvature (bicycle model).
delta = atan(wheelbase * curvature)in degrees. Wheelbase is auto-loaded from car.yaml if not specified.- Parameters:
df (DataFrame)
curvature_body (str)
curvature_track (str)
wheelbase (float | None)
car_yaml (str)
out_body (str)
out_track (str)
- Return type:
DataFrame
- suboptimumg.loganalysis.compute_rear_slip_ratio(df, wheel_speed_col='pcm.moc.motor.wheelSpeed', ground_speed_col='groundSpeed', out_col='rear.slipRatio', low_speed_thresh=1.0)[source]#
Compute rear axle longitudinal slip ratio.
slip = (v_wheel - v_ground) / v_groundPositive = driven wheels spinning faster (traction loss). Zeroed below low_speed_thresh to avoid division blow-up.
- Parameters:
df (DataFrame)
wheel_speed_col (str)
ground_speed_col (str)
out_col (str)
low_speed_thresh (float)
- Return type:
DataFrame
- suboptimumg.loganalysis.compute_track_frame_velocities(df, vel_n_col='velN', vel_e_col='velE', yaw_col='pcm.vnav.yawPitchRoll.yaw', low_speed_thresh=3.0, out_vx='track.velocity.x', out_vy='track.velocity.y', out_heading='track.heading')[source]#
Compute track-frame (velocity-frame) velocities from NED components.
Track heading is the course angle (direction of travel), same convention as yaw (0 = North, 90 = East, wraps at +/-180). At low speed the heading falls back to INS yaw.
- Parameters:
df (DataFrame)
vel_n_col (str)
vel_e_col (str)
yaw_col (str)
low_speed_thresh (float)
out_vx (str)
out_vy (str)
out_heading (str)
- Return type:
DataFrame
- suboptimumg.loganalysis.convert_wheelspeeds_to_m_per_s(df, cols)[source]#
Convert wheel speed columns from mph to m/s.
Backs up original mph values into
{col}_mphcolumns before overwriting with the converted values.- Parameters:
df (pd.DataFrame)
cols (list[str]) – Column names to convert.
- Returns:
The same DataFrame, modified in-place and returned for chaining.
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.correct_motor_data(df, rpm_col='pcm.moc.motor.angularSpeed', out_col='pcm.moc.motor.wheelSpeed', gear_ratio=None, tire_radius_in=None, car_yaml='parameters/car.yaml')[source]#
Flip motor RPM sign and compute driven wheel speed.
The inverter logs motor RPM as negative for forward motion. This function negates the RPM (so positive = forward) and computes the corresponding driven wheel linear speed in m/s.
- Parameters:
df (pd.DataFrame)
rpm_col (str) – Motor angular speed column (RPM, raw sign: negative = forward).
out_col (str) – Column name for the computed wheel speed (m/s).
gear_ratio (float or None) – Overall gear ratio (motor RPM / wheel RPM). If
None, loaded from car_yaml (pwrtn.ratio).tire_radius_in (float or None) – Loaded tire radius in inches. If
None, loaded from car_yaml (tires.tire_radius).car_yaml (str) – Path to car YAML file, used when gear_ratio or tire_radius_in are not provided.
- Returns:
Modified in-place and returned for chaining.
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.derive_quantities(df)[source]#
Compute all standard derived quantities.
TODO: Wire up the full set of derived.py calls.
- Parameters:
df (DataFrame)
- Return type:
DataFrame
- suboptimumg.loganalysis.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). Thesf_lineis a pair of (N, E) tuples defining the S/F segment.If
sf_linecontains lat/lon tuples instead, convert them first withgps_to_local_cartesianand 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.
Noneuses full range.end_time (float or None) – Time window to search within.
Noneuses full range.
- Returns:
Copy of df with a
Lapcolumn added (0 = before first crossing, 1 = first lap, etc.).- Return type:
pd.DataFrame
- suboptimumg.loganalysis.dump_variables(analyzer)[source]#
Enumerate every variable in a loaded PERDA log.
Variable IDs are intentionally excluded because they are log-internal and must not be used for access.
- Parameters:
analyzer (Analyzer) – A loaded PERDA Analyzer instance.
- Returns:
Columns: cpp_name, num_points, start_time, end_time, duration_s, min_value, max_value, median_freq_hz.
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.fft_group(df, partial_path, log_y=False, stacked=False, **kwargs)[source]#
Plot FFT for all columns matching partial_path.*.
Default is linear y-axis and overlaid traces.
- Parameters:
df (pd.DataFrame)
partial_path (str)
log_y (bool)
stacked (bool)
- Return type:
go.Figure
- suboptimumg.loganalysis.filter_speeds(df, zscore_window=8.0, zscore_thresh=2.5, lowpass_hz=3.0)[source]#
Z-score + lowpass pipeline for wheel speed signals.
Z-score filter the four raw wheelspeed sensors.
Recompute groundSpeed from the cleaned front wheelspeeds.
Lowpass all wheelspeeds, motor wheelSpeed, and groundSpeed.
- Parameters:
df (DataFrame)
zscore_window (float)
zscore_thresh (float)
lowpass_hz (float)
- Return type:
DataFrame
- suboptimumg.loganalysis.gps_to_local_cartesian(dfs, lat_col='pcm.vnav.posLla.latitude', lon_col='pcm.vnav.posLla.longitude', origin=None)[source]#
Equirectangular projection from lat/lon to local North/East (meters).
Adds
posNandposEcolumns. If origin isNone, uses the first data point of the first DataFrame as the reference.- Parameters:
dfs (DataFrame or list[DataFrame])
lat_col (str)
lon_col (str)
origin ((lat_deg, lon_deg) or None)
- Return type:
Same type as input, with
posNandposEadded.
- suboptimumg.loganalysis.intake_vd_starter_kit(analyzer, deduplicate=True, resample_method='linear')[source]#
Load the standard vehicle-dynamics variable set into a DataFrame.
Wraps
perda_to_dataframewith the default VD variable list and ZOH deduplication on VectorNav position and velocity channels.pcm.vnav.gpsFixis always resampled with zero-order hold (it is a discrete state, not a continuous signal — linear interpolation between0and3would produce nonsensical fractional fix values that defeat downstream gpsFix-based masking).Sentinel
0.0samples inposLla.latitude/.longitude(VectorNav’s “no INS solution” marker) are stripped from the sourceDataInstancebefore resampling so dedup + linear interpolation don’t smear them into ramps across no-fix gaps.- Parameters:
analyzer (Analyzer)
deduplicate (bool)
resample_method (str | dict[str, str])
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.load_vehicle_setup(path='parameters/vehicle_setup.yaml')[source]#
Load a vehicle_setup.yaml configuration file.
Any YAML mapping containing both
data(list of[x, y]pairs) andpoly_order(int) is automatically converted to aFittedCurve. Everything else passes through as plain Python types.- Parameters:
path (str or Path) – Path to the YAML file.
- Returns:
Parsed YAML contents with curve entries as FittedCurve objects. Typical top-level keys:
alignment,steering,suspension.- Return type:
dict
- suboptimumg.loganalysis.logging_frequencies(analyzer, var_names)[source]#
Compute sample-rate statistics for a list of variables.
- Parameters:
analyzer (Analyzer) – A loaded PERDA Analyzer instance.
var_names (list[str]) – CAN variable names (
cpp_name) to analyse.
- Returns:
Columns: var_name, median_freq_hz, mean_freq_hz, min_freq_hz, max_freq_hz, num_samples, num_gaps.
num_gapscounts intervals exceeding 3x the median interval, indicating dropped samples.- Return type:
pd.DataFrame
- suboptimumg.loganalysis.lowpass_filter(dfs, columns, cutoff_hz, time_col='time_s', order=4)[source]#
Apply a Butterworth lowpass filter to one or more columns.
On first call for a given column the current data is saved to
<col>_pre_lp. Subsequent calls always filter from<col>_pre_lpso that re-filtering at a different cutoff does not stack, while still preserving any upstream filtering (e.g.zscore_filter) that was applied before this function.A
<col>_originalbackup is also created on the very first filter call (zscore or lowpass) to allow full reset viaundo_filters().- Parameters:
dfs (DataFrame or list[DataFrame]) – Accepts a single DataFrame or a list; applies to all.
columns (str or list[str]) – Column name(s) to filter.
cutoff_hz (float) – Cutoff frequency in Hz.
time_col (str) – Time column to derive sampling frequency from.
order (int) – Butterworth filter order (applied forward-backward, so effective order is 2x).
- Returns:
Same type as *dfs, with filtered columns overwritten and*
_pre_lpbackups created on first filter.
- Return type:
DataFrame | list[DataFrame]
- suboptimumg.loganalysis.lowpass_filter_by_distance(dfs, columns, cutoff_freq_per_meter, distance_col='distance', order=4)[source]#
Apply a Butterworth lowpass in the spatial domain (1/m).
Same semantics as
lowpass_filter()but the sample rate is derived from a distance column instead of time.- Parameters:
dfs (DataFrame or list[DataFrame])
columns (str or list[str])
cutoff_freq_per_meter (float) – Cutoff in cycles per meter (1/m).
distance_col (str)
order (int)
- Return type:
Same type as dfs.
- suboptimumg.loganalysis.lowpass_group(df, partial_path, cutoff_hz, **kwargs)[source]#
Lowpass filter all columns matching partial_path.*.
- Parameters:
df (DataFrame)
partial_path (str)
cutoff_hz (float)
- Return type:
DataFrame
- suboptimumg.loganalysis.measure_understeer_gradient(df, time_range, irl_car, test_type='constant_steer', min_ay_g=0.1, force_zero_intercept=False, fit_mode='linear', symmetric=False, show_ramp=False, overlay_diagnostics=False, sw_col='ludwig.steeringWheel.angle', ay_col='body.accLat', kinematic_col='body.steerAngle', bicycle_col='front.steerAngle.bicycle', vel_col='groundSpeed')[source]#
Measure the understeer gradient K from a steady-state test window.
Computes
understeer_angle = |delta_bicycle| - |delta_kinematic|and fits a line against|a_y|in g’s. The slope is the understeer gradient K (deg/g). K > 0 = understeer, K < 0 = oversteer.Works for both constant-speed/increasing-steer and constant-steer/increasing-speed test procedures — the core math is the same; test_type only affects the diagnostic subplot.
- Parameters:
df (DataFrame) – Must contain the columns referenced by the
*_colarguments. The DataFrame is not modified.time_range ((t_start, t_end)) –
time_swindow to analyse.irl_car (IrlCar) – Used only for display / future extensions.
test_type (str) –
"constant_steer"or"constant_speed". Controls which variable is shown in the diagnostic subplot.min_ay_g (float) – Minimum lateral acceleration (g) to include in the fit. Filters out low-speed noise.
force_zero_intercept (bool) – If
True, force the fit through the origin (intercept = 0). Useful when a systematic offset (e.g. steering sensor zero error) is inflating the intercept.fit_mode (str) –
"linear"(default) — classic K = slope of UG vs a_y."quadratic"— fitsUG = c2 * a_y^2 + c1 * a_y + c0."cubic"— fitsUG = c3 * a_y^3 + c2 * a_y^2 + c1 * a_y + c0. For non-linear modes the reported K is the local slope ata_y = 0(thec1term); higher-order terms capture progressive under/oversteer with rising lateral load.symmetric (bool) – If
True, force the fitted equation to be symmetric across the y-axis (f(-x) = f(x)) by zeroing all odd-degree terms. Quadratic becomesc2 * a_y^2 + c0; cubic likewise reduces to even-only terms. Combined withforce_zero_interceptthe quadratic/cubic fits collapse toc2 * a_y^2. Linear + symmetric is degenerate (constant only) and not allowed.show_ramp (bool) – If
True, add a subplot for the ramped variable (speed forconstant_steer, steering angle forconstant_speed).overlay_diagnostics (bool) – If
Trueandshow_rampisTrue, overlay both diagnostic traces on a single subplot with dual y-axes instead of separate subplots.sw_col (str) – Column names in df.
ay_col (str) – Column names in df.
kinematic_col (str) – Column names in df.
bicycle_col (str) – Column names in df.
vel_col (str) – Column names in df.
- Returns:
Multi-panel figure: scatter + fit (top), diagnostic time-series (bottom panel(s)).
- Return type:
go.Figure
- suboptimumg.loganalysis.patch_ned_velocity(df, vel_x_col='pcm.vnav.velocityBody.x', vel_y_col='pcm.vnav.velocityBody.y', vel_z_col='pcm.vnav.velocityBody.z', yaw_col='pcm.vnav.yawPitchRoll.yaw', ned_cols=('velN', 'velE', 'velD'))[source]#
Correct mislabeled NED velocities that were logged as body-frame.
Due to a VectorNav configuration issue,
velocityBody.x/y/zactually contains NED (North/East/Down) velocities rather than body-frame (Forward/Right/Down) velocities. This function:Copies the raw NED data into new columns (ned_cols).
Rotates NED → FRD body frame using yaw and overwrites the original
velocityBodycolumns with corrected values.
The rotation (yaw-only, ignoring pitch/roll for ground vehicles):
v_fwd = velN * cos(yaw) + velE * sin(yaw) v_right = -velN * sin(yaw) + velE * cos(yaw) v_down = velD (unchanged)
This is a temporary workaround. When the VectorNav is reconfigured to output body-frame velocities natively, skip this step entirely.
- Parameters:
df (pd.DataFrame) – DataFrame with velocity and yaw columns (typically from
perda_to_dataframe).vel_x_col (str) – The mislabeled velocity columns (actually NED).
vel_y_col (str) – The mislabeled velocity columns (actually NED).
vel_z_col (str) – The mislabeled velocity columns (actually NED).
yaw_col (str) – Heading column in degrees (0 = North, 90 = East, CW positive).
ned_cols (tuple[str, str, str]) – Names for the preserved NED copy columns.
- Returns:
The same DataFrame, modified in-place and returned for chaining.
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.perda_load_folder(folder, extra_vars=None, deduplicate=True, lat_var='pcm.vnav.posLla.latitude', lon_var='pcm.vnav.posLla.longitude', gps_fix_var='pcm.vnav.gpsFix', min_gps_fix=1.0)[source]#
Batch-load GPS data from all PER CSV logs using PERDA.
Uses PERDA’s
Analyzerfor CSV parsing instead of the custom lightweight parser inquick_load_gps().- Parameters:
folder (str or Path) – Directory containing
.csvlog files.extra_vars (list[str] or None) – Full CAN variable names to extract alongside GPS.
deduplicate (bool) – Collapse consecutive duplicate GPS positions.
lat_var (str) – Full CAN variable names for latitude and longitude.
lon_var (str) – Full CAN variable names for latitude and longitude.
gps_fix_var (str) – CAN variable name for GPS fix status.
min_gps_fix (float) – Minimum GPS fix value to keep.
- Returns:
Keyed by filename (not full path). Logs with no GPS lock are omitted.
- Return type:
dict[str, pd.DataFrame]
- suboptimumg.loganalysis.perda_load_gps(path, lat_var='pcm.vnav.posLla.latitude', lon_var='pcm.vnav.posLla.longitude', gps_fix_var='pcm.vnav.gpsFix', min_gps_fix=1.0, extra_vars=None, deduplicate=True)[source]#
Load GPS data from a PER CSV log using PERDA’s Analyzer.
- Parameters:
path (str or Path) – Path to a PER-format
.csvlog file.lat_var (str) – Full CAN variable names for latitude and longitude.
lon_var (str) – Full CAN variable names for latitude and longitude.
gps_fix_var (str) – CAN variable name for GPS fix status.
min_gps_fix (float) – Minimum GPS fix value to keep (0 = no fix, 3 = 3D fix).
extra_vars (list[str] or None) – Additional CAN variables to extract alongside GPS.
deduplicate (bool) – Collapse consecutive duplicate GPS positions to remove ZOH-inflated samples (GPS updates at ~4 Hz but is logged at CAN rate ~100 Hz).
- Returns:
DataFrame indexed by
time_swithlatitude,longitudecolumns (plus extras). ReturnsNoneif the file has no usable GPS data.- Return type:
pd.DataFrame or None
- suboptimumg.loganalysis.perda_to_dataframe(analyzer, var_names, resample_method='linear', resample_freq_hz=None, reference_var=None, deduplicate_vars=None)[source]#
Convert selected PERDA variables into a single aligned DataFrame.
Only loads the explicitly requested variables — never touches the full set of 200-300+ variables in a log.
- Parameters:
analyzer (Analyzer) – A loaded PERDA Analyzer instance.
var_names (list[str]) – CAN names of the variables to extract.
resample_method (str or dict[str, str]) – Interpolation method for aligning to a common time grid.
"linear"(default),"zoh"(zero-order hold / persist until update),"nearest", or"cubic". Pass a dict mapping variable names to methods to mix strategies.resample_freq_hz (float or None) – If given, resample to a uniform grid at this frequency (Hz). Otherwise the union of all source timestamps is used.
reference_var (str or None) – Left-join all variables to this variable’s timestamp grid. If
Noneand resample_freq_hz is alsoNone, the variable with the most samples is chosen automatically.deduplicate_vars (list[str] or None) – Variable names whose consecutive duplicate (ZOH) values should be collapsed before resampling. Useful for signals like GPS position that are logged at a high CAN rate but only update at a lower real rate (e.g. 100 Hz CAN / 4 Hz real). Deduplication keeps the first sample of each constant run so that resampling interpolates smoothly between real updates.
- Returns:
Index:
time_s(float64 seconds, zero-based from log start). One column per variable.- Return type:
pd.DataFrame
- suboptimumg.loganalysis.plot_comparison_trace(laps, variable='groundSpeed', qss_df=None, baseline='fastest', dist_col='LapDist', vel_col='groundSpeed', units='m/s', dx=0.1)[source]#
Multi-lap comparison trace with cumulative time-delta subplot.
- Parameters:
laps (dict[label, DataFrame]) – Trimmed lap DataFrames from
split_laps().variable (str) – Column to plot on the top subplot (log-world name).
qss_df (DataFrame or None) – If provided, the QSS series is included. Column names are auto-resolved via
LOG_TO_QSS.baseline (str or int) – Reference for time delta.
"fastest"picks the lap with the shortestLapTime."first"/"last"use positional order."qss"uses the QSS as baseline. An explicit key selects that lap.vel_col (str) – Velocity column used for time-delta computation (log-world name, auto-resolved for QSS).
dist_col (str)
units (str)
dx (float)
- Return type:
Figure
- suboptimumg.loganalysis.plot_fft(df, columns, domain='time', time_col='time_s', distance_col='distance', stacked=True, cutoff=None, log_x=True, log_y=False, xlim=None)[source]#
Plot FFT magnitude spectrum of one or more columns.
- Parameters:
df (pd.DataFrame)
columns (str or list[str])
domain (str) –
"time"or"distance".time_col (str)
distance_col (str)
stacked (bool) –
True-> one subplot per column;False-> all on one plot.cutoff (float or None) – If given, draw a vertical dashed line at this frequency.
log_x (bool) – Logarithmic x-axis.
log_y (bool) – Logarithmic y-axis.
xlim ((start, end) or None) – Restrict the FFT to a subdomain of the x-axis (time in seconds or distance in meters, depending on domain).
Noneuses the full range.
- Return type:
go.Figure
Note
No
max_display_hzdownsampling is applied here — the FFT output is already in the frequency domain and thinning would corrupt the spectrum shape.
- suboptimumg.loganalysis.plot_gps_trajectory(df, lat_col='pcm.vnav.posLla.latitude', lon_col='pcm.vnav.posLla.longitude', time_col='time_s', color_by='time', max_display_hz=100.0, width=700, height=700)[source]#
Interactive 2D GPS trajectory plot with time-based trim sliders.
- Parameters:
df (pd.DataFrame)
lat_col (str)
lon_col (str)
time_col (str) – Column used for slider values and colour mapping.
color_by (str) –
"time"colours by time_col.max_display_hz (float) – Target sample rate for display. Data is naively thinned (every Nth point) to approximately this rate. The source DataFrame is not modified.
width (int)
height (int)
- Returns:
figis the FigureWidget,widget_boxis the VBox with sliders that can bedisplay()-ed.- Return type:
(fig, widget_box)
- suboptimumg.loganalysis.plot_group(df, partial_path, stacked=False, **kwargs)[source]#
Plot all columns matching partial_path.*.
Default is overlaid traces on a single axis.
- Parameters:
df (pd.DataFrame)
partial_path (str)
stacked (bool)
- Return type:
go.Figure
- suboptimumg.loganalysis.plot_heatmap(baseline_df, comparison_df, variable='groundSpeed', baseline_label='Baseline', comparison_label='Comparison', pos_cols=('posN', 'posE'), dist_col='LapDist', units='m/s', dx=0.1)[source]#
3-panel 2D track heatmap comparing any two DataFrames.
Works for lap-vs-lap or lap-vs-QSS. Column names are auto-resolved via
LOG_TO_QSSso the caller always uses log-world names (e.g."groundSpeed").- Parameters:
baseline_df (DataFrame) – Any two lap DataFrames, or a lap and a
qss_df.comparison_df (DataFrame) – Any two lap DataFrames, or a lap and a
qss_df.variable (str) – Column to compare (log-world name).
pos_cols ((north_col, east_col)) – Position columns (log-world names).
units (str) – Label for colorbars and hover.
dx (float) – Resample step when the two DataFrames are on different grids.
baseline_label (str)
comparison_label (str)
dist_col (str)
- Return type:
Figure
- suboptimumg.loganalysis.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.
0orNonedisables.
- Return type:
go.Figure
- suboptimumg.loganalysis.plot_log(df, columns, stacked=True, domain='time', time_col='time_s', distance_col='distance', title=None, height_per_subplot=250, max_display_hz=100.0)[source]#
General-purpose log plotting function.
- Parameters:
df (pd.DataFrame) – The log DataFrame.
columns (list[str]) – Column names to plot.
stacked (bool) –
True-> one subplot per column (shared x-axis).False-> all traces on a single plot.domain (str) –
"time"-> x-axis is time_col."distance"-> x-axis is distance_col.time_col (str) – Column name for time. If the DataFrame index is named
time_s, it is used automatically when time_col is not found as a column.distance_col (str) – Column name for distance.
title (str or None) – Figure title. Defaults to a generated summary.
height_per_subplot (int) – Pixel height per subplot row (only used when stacked=True).
max_display_hz (float) – Target display sample rate for naive downsampling.
0orNonedisables downsampling.
- Return type:
go.Figure
- suboptimumg.loganalysis.plot_velocity_heatmap(comp_df, track, log_vel_col='groundSpeed', qss_vel_col='qss.velocity')[source]#
3-panel velocity heatmap (IRL vs QSS).
Thin wrapper around
plot_heatmap()for backward compatibility.- Parameters:
comp_df (DataFrame)
track (Track)
log_vel_col (str)
qss_vel_col (str)
- Return type:
Figure
- suboptimumg.loganalysis.plot_velocity_trace(comp_df, log_vel_col='groundSpeed', qss_vel_col='qss.velocity')[source]#
F1-style velocity trace (IRL vs QSS).
Thin wrapper around
plot_comparison_trace()for backward compatibility.- Parameters:
comp_df (DataFrame)
log_vel_col (str)
qss_vel_col (str)
- Return type:
Figure
- suboptimumg.loganalysis.prepare_gps_data(df, lat_col='latitude', lon_col='longitude', max_radius_m=5000.0, max_jump_m=30.0)[source]#
Full GPS cleaning pipeline: filter invalid positions, project to local cartesian, and remove spatial outliers.
Steps applied in order:
Drop rows where lat/lon are zero or outside physical range (
|lat| > 90or|lon| > 180).Equirectangular projection to local East/North meters, with the median lat/lon as origin.
Drop points farther than max_radius_m from the origin (warns if any are removed).
Drop points that jump more than max_jump_m from their predecessor (warns if any are removed).
ZOH deduplication of stale GPS samples should be handled upstream via
perda_to_dataframe(deduplicate_vars=...).Adds
posE(East, meters) andposN(North, meters) columns to the returned DataFrame.- Parameters:
df (pd.DataFrame) – Must contain lat_col and lon_col columns.
lat_col (str) – Column names for latitude and longitude in degrees.
lon_col (str) – Column names for latitude and longitude in degrees.
max_radius_m (float) – Maximum distance from the median position to keep.
max_jump_m (float) – Maximum point-to-point distance to keep.
- Returns:
Cleaned copy with
posEandposNcolumns added and invalid rows removed.- Return type:
pd.DataFrame
- suboptimumg.loganalysis.preprocess_gps(df)[source]#
Resolve GPS columns, clean/project, and compute elapsed distance.
- Parameters:
df (DataFrame)
- Return type:
DataFrame
- suboptimumg.loganalysis.preprocess_speeds(df, patch_ned=True, car_yaml='parameters/car.yaml')[source]#
NED velocity patch, wheelspeed unit conversion, motor correction, ground speed.
- Parameters:
df (DataFrame)
patch_ned (bool)
car_yaml (str)
- Return type:
DataFrame
- suboptimumg.loganalysis.qss_results_to_dataframe(lapsim_results, track)[source]#
Convert QSS lapsim output arrays into a distance-indexed DataFrame.
Columns use a
qss.prefix so they sit cleanly alongside log columns in a merged comparison DataFrame. Includes position columns (qss.posN,qss.posE) from the track.- Parameters:
lapsim_results (LapsimResults)
track (Track)
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.quick_load_folder(folder, lat_pattern='posLla.latitude', lon_pattern='posLla.longitude', extra_patterns=None, parallel=False, max_workers=None)[source]#
Batch-load GPS previews for all PER CSV logs in a folder.
- Parameters:
folder (str or Path) – Directory containing
.csvlog files.lat_pattern (str) – Passed to
quick_load_gps().lon_pattern (str) – Passed to
quick_load_gps().extra_patterns (list[str] or None) – Passed to
quick_load_gps().parallel (bool) – If
True, load files in parallel usingProcessPoolExecutor.max_workers (int or None) – Max worker processes when parallel=True. Defaults to
os.cpu_count() - 2(minimum 1).
- Returns:
Keyed by filename (not full path). Logs with no GPS lock are omitted.
- Return type:
dict[str, pd.DataFrame]
- suboptimumg.loganalysis.quick_load_gps(path, lat_pattern='posLla.latitude', lon_pattern='posLla.longitude', gps_fix_pattern='gpsFix', min_gps_fix=1.0, extra_patterns=None, _verbose=True)[source]#
Fast GPS extractor that parses a PER CSV without PERDA.
Reads only the header to discover variable IDs, then streams the data section keeping only lat/lon rows. Typically 5-10x faster than a full PERDA load for GPS preview purposes.
- Parameters:
path (str or Path) – Path to a PER-format
.csvlog file.lat_pattern (str) – Substrings matched (case-sensitive) against the
cpp_namein the header to identify latitude and longitude variable IDs.lon_pattern (str) – Substrings matched (case-sensitive) against the
cpp_namein the header to identify latitude and longitude variable IDs.extra_patterns (list[str] or None) – Additional variable patterns to extract alongside lat/lon (e.g.
["gpsFix", "posLla.altitude"]).gps_fix_pattern (str)
min_gps_fix (float)
_verbose (bool)
- Returns:
DataFrame indexed by
time_swithlatitude,longitudecolumns (plus any extras). ReturnsNoneif the file has no GPS lock (all lat/lon values are zero).- Return type:
pd.DataFrame or None
- suboptimumg.loganalysis.recompute_steering_angle(df, raw_col='ludwig.steeringWheel.raw', angle_col='ludwig.steeringWheel.angle', calibration=((1.86, -97.0), (2.93, 0.0), (3.96, 97.0)))[source]#
Regenerate
ludwig.steeringWheel.anglefrom the raw analog voltage.The on-vehicle DSP that produces
steeringWheel.anglehas been observed to drift relative to the raw pot signal. This function rebuilds the angle column fromsteeringWheel.rawusing a least-squares polynomial fit through the calibration points (quadratic by default — exact fit for the standard 3-point left/center/right calibration, capturing the small nonlinearity of the pot).Any existing
angle_colis preserved as{angle_col}_original.- Parameters:
df (pd.DataFrame) – Must contain raw_col.
raw_col (str) – Raw analog steering voltage column (volts).
angle_col (str) – Output / overwritten angle column (degrees).
calibration (tuple of (voltage, angle) pairs) – Calibration points. With three points a quadratic fits all of them exactly; with more points it’s a least-squares quadratic. With two points it falls back to a linear fit.
- Returns:
Modified in-place and returned for chaining.
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.resample_log_to_distance(lap_df, distance_grid, columns=None, dist_col='LapDist')[source]#
Resample selected log columns onto a QSS distance grid.
Uses linear interpolation via
numpy.interp().- Parameters:
lap_df (DataFrame) – Trimmed single-lap DataFrame.
distance_grid (ndarray) – Target distance array (from a built Track).
columns (list[str] or None) – Columns to resample. If
None, uses all non-Nonevalues fromQSS_TO_LOG.dist_col (str) – Distance column in lap_df.
- Return type:
DataFrame
- suboptimumg.loganalysis.resolve_gps_columns(dfs, ins_lat_col='pcm.vnav.posLla.latitude', ins_lon_col='pcm.vnav.posLla.longitude', fix_col='pcm.vnav.gpsFix', out_lat_col='latitude', out_lon_col='longitude')[source]#
Build unified latitude/longitude columns with INS-lock fallback.
The VN-300 INS solution (
posLla) reads zero when the INS hasn’t converged. This function creates cleanlatitude/longitudecolumns that mask out invalid (zero) samples and, when the INS lock is eventually acquired, use the INS-filtered position.When
fix_colis provided and present in the DataFrame, rows wheregpsFix == 0are also treated as invalid.Invalid rows are set to
NaNso downstream functions can handle them (e.g. drop, interpolate, or skip).- Parameters:
dfs (DataFrame or list[DataFrame])
ins_lat_col (str) – INS solution position columns (the only position variables in these CAN logs).
ins_lon_col (str) – INS solution position columns (the only position variables in these CAN logs).
fix_col (str or None) – GPS fix status column.
Noneskips the fix check.out_lat_col (str) – Names for the output columns.
out_lon_col (str) – Names for the output columns.
- Return type:
Same type as input, with out_lat_col and out_lon_col added.
- suboptimumg.loganalysis.sample_log(df, patterns=None, percentiles=None)[source]#
Print sampled values at key points through the log for debugging.
By default matches columns containing ‘pos’, ‘lat’, ‘latitude’, ‘lon’, ‘long’, or ‘longitude’ (case-insensitive).
- Parameters:
df (pd.DataFrame) – The log DataFrame (output of
perda_to_dataframe).patterns (list[str] or None) – Substrings to match against column names.
percentiles (list[int] or None) – Which percentile indices to sample. Default: [0, 25, 50, 75, 100].
- Return type:
None
- suboptimumg.loganalysis.simulate_on_track(lap_df, track, car_yaml='../parameters/car.yaml', velocity_col='groundSpeed')[source]#
Load a car, simulate on a Track, and return a QSS DataFrame.
- Parameters:
lap_df (DataFrame) – The trimmed single-lap DataFrame used to build the track.
track (Track) – A QSS-compatible Track (from
build_qss_track()).car_yaml (str) – Path to the car YAML config file.
velocity_col (str) – Column to read IRL initial speed from.
- Returns:
sim is the
CustomRuninstance (carries push/coast results). qss_df is a distance-indexed DataFrame withqss.*columns.- Return type:
(sim, qss_df)
- suboptimumg.loganalysis.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
Lapcolumn with arc-length buffer.Each lap DataFrame gets
LapTimeandLapDistcolumns 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]
- suboptimumg.loganalysis.trim_dataframe(df, time_col, start_time, end_time)[source]#
Return rows where time_col is between start_time and end_time (inclusive).
- Parameters:
df (DataFrame)
time_col (str)
start_time (float)
end_time (float)
- Return type:
DataFrame
- suboptimumg.loganalysis.undo_filters(df, columns)[source]#
Restore columns to their original (pre-filter) state.
Copies data back from
<col>_originaland removes all backup columns (_original,_pre_lp,_raw).- Parameters:
df (pd.DataFrame)
columns (str or list[str]) – Column name(s) to restore.
- Returns:
The same DataFrame, modified in-place and returned.
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.unwrap_degrees(df, columns, suffix='', discontinuity=180.0)[source]#
Unwrap angle columns (degrees) to remove +-180 discontinuities.
The VN-300 yaw output reads 0 at north, +90 east, +180 south, then jumps to -180 and climbs to 0. This creates step discontinuities that break derivatives and integrations.
np.unwrapremoves them by adding/subtracting 360 at each jump.- Parameters:
df (pd.DataFrame)
columns (str or list[str]) – Column name(s) to unwrap.
suffix (str) – If non-empty, the unwrapped result is written to
<col><suffix>and the original column is kept. If empty (default), the column is overwritten in place.discontinuity (float) – Maximum allowed step between consecutive samples (degrees). Jumps larger than this are treated as wraps.
- Returns:
Same DataFrame with unwrapped column(s).
- Return type:
pd.DataFrame
- suboptimumg.loganalysis.zfilter_group(df, partial_path, window_s=2.0, threshold=4.8, **kwargs)[source]#
Z-score filter all columns matching partial_path.*.
- Parameters:
df (DataFrame)
partial_path (str)
window_s (float)
threshold (float)
- Return type:
DataFrame
- suboptimumg.loganalysis.zscore_filter(dfs, columns, window_s=2.0, threshold=4.8, time_col='time_s')[source]#
Mask outliers using a rolling-window z-score.
For each sample, a z-score is computed relative to the local rolling mean and standard deviation. Samples exceeding threshold are replaced with
NaNand linearly interpolated.Always re-filters from
<col>_original(the true intake data) so that re-running with different parameters does not stack. If a<col>_pre_lpbackup exists (lowpass was already applied), it is updated to the new zscore output so subsequent lowpass calls chain correctly.- Parameters:
dfs (DataFrame or list[DataFrame])
columns (str or list[str]) – Column name(s) to filter.
window_s (float) – Rolling window size in seconds.
threshold (float) – Z-score threshold – samples with
|z| > thresholdare masked.time_col (str) – Time column used to convert window_s to a sample count.
- Returns:
Same type as *dfs, with outliers masked to NaN and*
_originalbackups created on first call.
- Return type:
DataFrame | list[DataFrame]
Modules
Post-intake data corrections for known CAN log issues. |
|
Derived quantities computed from raw log data. |
|
FFT plotting and digital lowpass filtering utilities. |
|
Polynomial curve fitting with Horner evaluation. |
|
GPS visualization, trimming, coordinate transforms, and distance computation. |
|
PERDA log -> aligned/resampled pandas DataFrame, plus fast GPS-only loader. |
|
IrlCar — real-world car wrapper combining Car + vehicle_setup parameters. |
|
Lap division, splitting, and alignment utilities. |
|
High-level convenience macros that chain multiple loganalysis steps. |
|
Generalized time-series and distance-series logging/plotting. |
|
Build QSS-compatible Track objects from processed lap DataFrames. |
|
Understeer gradient measurement from steady-state test data. |
|
Log introspection utilities: variable enumeration, frequency analysis, config loading. |