|
import numpy as np |
|
import matplotlib as mpl |
|
|
|
from PIL import Image |
|
from io import BytesIO |
|
from typing import Union |
|
from fastf1.core import Session |
|
from matplotlib import pyplot as plt |
|
from matplotlib.collections import LineCollection |
|
|
|
|
|
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: Session) -> Image: |
|
|
|
weekend = session.event |
|
lap = session.laps.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'[Speed] {weekend["EventName"]} - {lap["Driver"]} #{lap["DriverNumber"]} ', 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") |
|
legend.set_label("Speed [km/h]") |
|
|
|
|
|
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: 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: Session) -> Image: |
|
weekend = session.event |
|
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) |
|
|
|
fig, ax = plt.subplots(sharex=True, sharey=True, figsize=(12, 6.75)) |
|
fig.suptitle(f'[Gear] {weekend["EventName"]} - {lap["Driver"]} #{lap["DriverNumber"]}', size=24, x=0.5, ha='center', 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) |
|
|
|
|
|
lc_comp = LineCollection(segments, norm=plt.Normalize(gear.min(), gear.max()), cmap=plt.cm.get_cmap('viridis', 8)) |
|
lc_comp.set_array(gear) |
|
lc_comp.set_linewidth(4) |
|
ax.add_collection(lc_comp) |
|
|
|
|
|
x_pad = (x.max() - x.min()) * 0.03 |
|
y_pad = (y.max() - y.min()) * 0.03 |
|
ax.set_xlim(x.min() - x_pad, x.max() + x_pad) |
|
ax.set_ylim(y.min() - y_pad, y.max() + y_pad) |
|
|
|
|
|
ax.set_aspect('equal', adjustable='datalim') |
|
|
|
|
|
cbaxes = fig.add_axes([0.25, 0.05, 0.5, 0.05]) |
|
normlegend = plt.Normalize(1, 8) |
|
legend = mpl.colorbar.ColorbarBase(cbaxes, norm=normlegend, cmap=plt.cm.get_cmap('viridis', 8), |
|
orientation="horizontal") |
|
legend.set_ticks(np.arange(1, 9)) |
|
legend.set_ticklabels(np.arange(1, 9)) |
|
legend.set_label("Gear") |
|
|
|
|
|
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 |
|
|