|
import matplotlib as mpl |
|
import numpy as np |
|
from matplotlib import pyplot as plt |
|
from matplotlib.collections import LineCollection |
|
import fastf1 as ff1 |
|
from PIL import Image |
|
from io import BytesIO |
|
from typing import Union |
|
import json |
|
|
|
|
|
gp = Union[str, int] |
|
session_type = Union[str, int, None] |
|
|
|
|
|
def rotate(xy, *, angle): |
|
rot_mat = np.array([[np.cos(angle), np.sin(angle)], |
|
[-np.sin(angle), np.cos(angle)]]) |
|
return np.matmul(xy, rot_mat) |
|
|
|
|
|
|
|
def create_track_speed_visualization(session, driver_name: str) -> Image: |
|
|
|
weekend = session.event |
|
session.load() |
|
with open("assets/driver_abbreviations.json") as f: |
|
driver_abbreviations = json.load(f) |
|
driver_abbreviation = driver_abbreviations[driver_name] |
|
lap = session.laps.pick_drivers(driver_abbreviation).pick_fastest() |
|
|
|
|
|
x = lap.telemetry['X'] |
|
y = lap.telemetry['Y'] |
|
color = lap.telemetry['Speed'] |
|
|
|
|
|
points = np.array([x, y]).T.reshape(-1, 1, 2) |
|
segments = np.concatenate([points[:-1], points[1:]], axis=1) |
|
|
|
|
|
fig, ax = plt.subplots(sharex=True, sharey=True, figsize=(12, 6.75)) |
|
fig.suptitle(f'{weekend["EventName"]} - {driver_name} ', size=24, y=0.97) |
|
|
|
|
|
plt.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.12) |
|
ax.axis('off') |
|
|
|
|
|
|
|
ax.plot(lap.telemetry['X'], lap.telemetry['Y'], |
|
color='black', linestyle='-', linewidth=16, zorder=0) |
|
|
|
|
|
norm = plt.Normalize(color.min(), color.max()) |
|
lc = LineCollection(segments, cmap=mpl.colormaps['viridis'], norm=norm, |
|
linestyle='-', linewidth=5) |
|
|
|
|
|
lc.set_array(color) |
|
|
|
|
|
line = ax.add_collection(lc) |
|
|
|
|
|
cbaxes = fig.add_axes([0.25, 0.05, 0.5, 0.05]) |
|
normlegend = mpl.colors.Normalize(vmin=color.min(), vmax=color.max()) |
|
legend = mpl.colorbar.ColorbarBase(cbaxes, norm=normlegend, cmap=mpl.colormaps['viridis'], |
|
orientation="horizontal") |
|
|
|
|
|
fig = plt.gcf() |
|
|
|
|
|
buf = BytesIO() |
|
fig.savefig(buf, format='png', dpi=150, bbox_inches='tight') |
|
buf.seek(0) |
|
|
|
|
|
img_data = buf.getvalue() |
|
plt.close(fig) |
|
buf.close() |
|
|
|
|
|
img = Image.open(BytesIO(img_data)) |
|
return img |
|
|
|
|
|
def create_track_corners_visualization(session) -> Image: |
|
|
|
lap = session.laps.pick_fastest() |
|
pos = lap.get_pos_data() |
|
|
|
circuit_info = session.get_circuit_info() |
|
|
|
|
|
|
|
|
|
track = pos.loc[:, ('X', 'Y')].to_numpy() |
|
|
|
|
|
track_angle = circuit_info.rotation / 180 * np.pi |
|
|
|
|
|
rotated_track = rotate(track, angle=track_angle) |
|
plt.plot(rotated_track[:, 0], rotated_track[:, 1]) |
|
|
|
offset_vector = [500, 0] |
|
|
|
|
|
for _, corner in circuit_info.corners.iterrows(): |
|
|
|
txt = f"{corner['Number']}{corner['Letter']}" |
|
|
|
|
|
offset_angle = corner['Angle'] / 180 * np.pi |
|
|
|
|
|
offset_x, offset_y = rotate(offset_vector, angle=offset_angle) |
|
|
|
|
|
text_x = corner['X'] + offset_x |
|
text_y = corner['Y'] + offset_y |
|
|
|
|
|
text_x, text_y = rotate([text_x, text_y], angle=track_angle) |
|
|
|
|
|
track_x, track_y = rotate([corner['X'], corner['Y']], angle=track_angle) |
|
|
|
|
|
plt.scatter(text_x, text_y, color='grey', s=140) |
|
|
|
|
|
plt.plot([track_x, text_x], [track_y, text_y], color='grey') |
|
|
|
|
|
plt.text(text_x, text_y, txt, |
|
va='center_baseline', ha='center', size='small', color='white') |
|
|
|
|
|
plt.title(session.event['Location']) |
|
plt.xticks([]) |
|
plt.yticks([]) |
|
plt.axis('equal') |
|
|
|
|
|
fig = plt.gcf() |
|
|
|
|
|
buf = BytesIO() |
|
fig.savefig(buf, format='png', dpi=150, bbox_inches='tight') |
|
buf.seek(0) |
|
|
|
|
|
img_data = buf.getvalue() |
|
plt.close(fig) |
|
buf.close() |
|
|
|
|
|
img = Image.open(BytesIO(img_data)) |
|
return img |
|
|
|
|
|
def create_track_gear_visualization(session) -> Image: |
|
|
|
lap = session.laps.pick_fastest() |
|
tel = lap.get_telemetry() |
|
|
|
x = np.array(tel['X'].values) |
|
y = np.array(tel['Y'].values) |
|
|
|
points = np.array([x, y]).T.reshape(-1, 1, 2) |
|
segments = np.concatenate([points[:-1], points[1:]], axis=1) |
|
gear = tel['nGear'].to_numpy().astype(float) |
|
|
|
cmap = plt.cm.get_cmap('viridis', 8) |
|
norm = plt.Normalize(1, 8) |
|
lc_comp = LineCollection(segments, norm=norm, cmap=cmap) |
|
lc_comp.set_array(gear) |
|
lc_comp.set_linewidth(4) |
|
|
|
plt.gca().add_collection(lc_comp) |
|
plt.axis('equal') |
|
plt.tick_params(labelleft=False, left=False, labelbottom=False, bottom=False) |
|
|
|
plt.suptitle( |
|
f"Fastest Lap Gear Shift Visualization\n" |
|
f"{lap['Driver']} - {session.event['EventName']}" |
|
) |
|
|
|
cbar = plt.colorbar(mappable=lc_comp, label="Gear") |
|
cbar.set_ticks(np.arange(1, 9)) |
|
cbar.set_ticklabels(np.arange(1, 9)) |
|
|
|
|
|
fig = plt.gcf() |
|
|
|
|
|
buf = BytesIO() |
|
fig.savefig(buf, format='png', dpi=150, bbox_inches='tight') |
|
buf.seek(0) |
|
|
|
|
|
img_data = buf.getvalue() |
|
plt.close(fig) |
|
buf.close() |
|
|
|
|
|
img = Image.open(BytesIO(img_data)) |
|
return img |
|
|