theOehrly / Fast-F1

FastF1 is a python package for accessing and analyzing Formula 1 results, schedules, timing data and telemetry
https://docs.fastf1.dev
MIT License
2.53k stars 263 forks source link

[Question and Suggestion] #5

Closed TempBet closed 2 years ago

TempBet commented 4 years ago

Hi, how can I make a Boxplot of the times with a type of rubber? I tried, but he tells me he doesn't accept timedelta

Also, is it possible to implement a function for the SpeedDelta?

theOehrly commented 4 years ago

Try to convert the timedelta values to seconds maybe. I think timedelta.dt.total_seconds() should do that.

Matplotlib doesn't like the timedelta data type.

What exactly do you mean with speed delta?

TempBet commented 4 years ago

Thanks for the reply. I mean a function similar to Timedelta, only that it traces the line of the difference between the speeds of two drivers.

I don't know if I explained myself well

theOehrly commented 4 years ago

I am planning to implement something like that, yes. In theory, you only need to "subtract" the two laps. But it is not actually that easy. To do this properly, you need to compare the time difference in relation to the on-track position. This means a relation between time and covered lap distance needs to be made first. Furthermore, this needs to be very exact if we want the data to be meaningful.

TempBet commented 4 years ago

Another question: is it possible to put an example on the documentation of how the Track class works, and what can you actually do with it?

theOehrly commented 4 years ago

Currently, there is "Distance to Driver Ahead" available as a calculated additional info. It is somewhat complicated to calculate this.

The track class is intended to serve as a more universal interface for calculating things like this.

There should be a better documentation, yes. But due to some changes to the position data by F1, it does not work with the 2020 season. Some major changes are necessary to make it work again. And I'm not motivated to write documentation for something that doesn't work anymore and needs to be changed anyways.

Maybe I'll add a more obvious notice about the fact that it doesn't work anymore.

theOehrly commented 4 years ago

Thanks for the reply. I mean a function similar to Timedelta, only that it traces the line of the difference between the speeds of two drivers.

I don't know if I explained myself well

Maybe you are interested in this:

https://theoehrly.github.io/Fast-F1/utils.html#fastf1.utils.delta_time

It's the delta time not the delta speed though.

I merged this while integrating a newer version of the original codebase. I have not tried or tested it really so I can not speak to the accuracy of the result.

TempBet commented 4 years ago

ahhh ok. I don't see it on the documentation.

another question. Is it possible to add (always if not already there) the possibility to plot the circuit and highlight the microsectors?

I am attaching an image that is perhaps self-explanatory. image

Is it also possible to add the difference in speed between two cars at a point in the plot?

theOehrly commented 4 years ago

Both of these things should be possible. But there's currently no easy way to do this. I very much want to add functionality to do this. But I probably won't get to it very soon.

TerminatorPlati commented 4 years ago

Hello. I use this issue to ask a question without opening another one.is it possible to do the plotting of all the telemetry for all the laps of the race for example? let me explain if I want to see the graph of the speed of the car for the whole length of the race, how can I do?

theOehrly commented 4 years ago

If I understand you right, this is what you are looking for:

import fastf1 as ff1
import matplotlib.pyplot as plt

session = ff1.get_session(2020, 5, 'R')
session.load_laps()

lec = session.car_data['16']

plt.figure()
plt.plot(lec['Time'], lec['Speed'])
plt.show()

And the 'Space' data is not available here. You can only use 'Time' as a reference.

This is not documented but should be. I'll add it to the ToDo list.

TerminatorPlati commented 4 years ago

If I understand you right, this is what you are looking for:

import fastf1 as ff1
import matplotlib.pyplot as plt

session = ff1.get_session(2020, 5, 'R')
session.load_laps()

lec = session.car_data['16']

plt.figure()
plt.plot(lec['Time'], lec['Speed'])
plt.show()

And the 'Space' data is not available here. You can only use 'Time' as a reference.

This is not documented but should be. I'll add it to the ToDo list.

Uhm, me it doesn't work. image

theOehrly commented 4 years ago

If I understand you right, this is what you are looking for:

import fastf1 as ff1
import matplotlib.pyplot as plt

session = ff1.get_session(2020, 5, 'R')
session.load_laps()

lec = session.car_data['16']

plt.figure()
plt.plot(lec['Time'], lec['Speed'])
plt.show()

And the 'Space' data is not available here. You can only use 'Time' as a reference. This is not documented but should be. I'll add it to the ToDo list.

Uhm, me it doesn't work. image

This really should work. Did you run exactly the example code for a test? You need to call load_laps() else you will get the error that you are getting. I just tested it again in a fresh venv with a fresh install from master and it did work.

TerminatorPlati commented 4 years ago

This really should work. Did you run exactly the example code for a test? You need to call load_laps() else you will get the error that you are getting. I just tested it again in a fresh venv with a fresh install from master and it did work.

ok, found why it didn't work. if it has enabled fast-f1 cache, the program breaks. (I already had the data of the race in question in the cache folder)

theOehrly commented 4 years ago

Yes, that makes sense. The cache is not very intelligent currently in that regard and does not get updated.

TerminatorPlati commented 4 years ago

Hi, why are there no telemetric data and LapTime of the first lap of each race? API problem? Or FastF1 problem?

theOehrly commented 4 years ago

Problem of FastF1 not the API.

Was this different in earlier versions or has this problem always existed? I sort of know what causes this. The data does exist but it can't be assigned to the first lap. I'm just a bit confused how this could have changed because of the latest changes. I can't take a closer look at this currently but will do soon.

TerminatorPlati commented 4 years ago

Problem of FastF1 not the API.

Was this different in earlier versions or has this problem always existed? I sort of know what causes this. The data does exist but it can't be assigned to the first lap. I'm just a bit confused how this could have changed because of the latest changes. I can't take a closer look at this currently but will do soon.

It seems to me that this has always existed

theOehrly commented 4 years ago

OK, that would make sense. If I remember correctly, the api doesn't provide a lap time for the first lap. This makes it difficult to determine the start of the race and therefore it is difficult to slice the data correctly. But I'll take another look and see if this can be done.

nicZox commented 3 years ago

Hello. I use this issue to share information that you may need for development or to extend Fast-F1. By intercepting the traffic of the app, I was able to find out the URL that manages the livetiming (I attach a photo). The application uses SignalR. Searching the internet for the address, I found a GH repository that uses this URL, I link it to you so you can check it out.

https://github.com/claudiopizzillo/F1client/

F1-APP

theOehrly commented 3 years ago

@nicZox Thanks for the info. I had already known that the app uses SignalR for livetiming. But I didn't know about that repositiory. That code is certainly worth looking at.

TerminatorPlati commented 3 years ago

Hi @theOehrly , I tried to make a graph with the data from the Belgian GP race. This is the result. Is it possible to have a more linear and not serrated trace?

Thanks Immagine 2021-03-10 100943

theOehrly commented 3 years ago

No, sadly not. This is the quality of the data. To get a smoother trace you would need to apply some sort of smoothing to it. The problem is that the traces have very sharp intentional changes when the car starts braking or accelerating. A simple smoothing algorithm will therefore always make cornering speeds, top speeds and the beginning of a braking zone less accurate. There may well be ways to smooth out the data without losing detail. But I haven't had time to look into this a lot.

TerminatorPlati commented 3 years ago

No, sadly not. This is the quality of the data. To get a smoother trace you would need to apply some sort of smoothing to it. The problem is that the traces have very sharp intentional changes when the car starts braking or accelerating. A simple smoothing algorithm will therefore always make cornering speeds, top speeds and the beginning of a braking zone less accurate. There may well be ways to smooth out the data without losing detail. But I haven't had time to look into this a lot.

Ok, could it be inaccurate cache data, compared to live/removed from API at the end of the 2020 season?

theOehrly commented 3 years ago

Ok, could it be inaccurate cache data, compared to live/removed from API at the end of the 2020 season?

I'm pretty sure that no, this has nothing to do with caching. Cached data is more or less an exact copy of the api data. Additionally, car telemetry is not modified by the api parser. It only "sorted" and converted into an object which is usable for us. I see no obvious way how inaccuracies could be introduced here. Or do you have some reference/comparison plot that has less jitter in the traces.

TerminatorPlati commented 3 years ago

@theOehrly

Is it possible to use the old version's cache files with 2.1?

image

theOehrly commented 3 years ago

Maybe yes. The .pkl files cannot be loaded with the new version anymore. But it should still be able to use the http cache. So I think it is likely that all cached sessions can be loaded. I'd say, just copy the .sqlite file into an empty new cache directory, tell the cache to use this directory with only that single file in it and see if it works.

Btw, I had any idea concerning your earlier question about the serrated traces. Which method are you using to get the car data? Lap.telemetry or Lap.get_car_data() ?

TerminatorPlati commented 3 years ago

Btw, I had any idea concerning your earlier question about the serrated traces. Which method are you using to get the car data? Lap.telemetry or Lap.get_car_data() ?

Like this

image

TerminatorPlati commented 3 years ago

Maybe yes. The .pkl files cannot be loaded with the new version anymore. But it should still be able to use the http cache. So I think it is likely that all cached sessions can be loaded. I'd say, just copy the .sqlite file into an empty new cache directory, tell the cache to use this directory with only that single file in it and see if it works.

I try

theOehrly commented 3 years ago

Btw, I had any idea concerning your earlier question about the serrated traces. Which method are you using to get the car data? Lap.telemetry or Lap.get_car_data() ?

Like this

image

You may want to try soemthing like this if you don't need the coordinates (X, Y, Z channels):

carData1= fastPilota1.get_car_data().add_distance()
Pilota1Space = carData1['Distance']
Pilota1Y = carData1['Speed']

This might give you a smoother trace because not interpolation is involved. See https://theoehrly.github.io/Fast-F1/core.html#fastf1.core.Lap

TerminatorPlati commented 3 years ago

Btw, I had any idea concerning your earlier question about the serrated traces. Which method are you using to get the car data? Lap.telemetry or Lap.get_car_data() ?

Like this image

You may want to try soemthing like this if you don't need the coordinates (X, Y, Z channels):

carData1= fastPilota1.get_car_data().add_distance()
Pilota1Space = carData1['Distance']
Pilota1Y = carData1['Speed']

This might give you a smoother trace because not interpolation is involved. See https://theoehrly.github.io/Fast-F1/core.html#fastf1.core.Lap

Oh yes, with these it is much smoother

TerminatorPlati commented 3 years ago

Maybe yes. The .pkl files cannot be loaded with the new version anymore. But it should still be able to use the http cache. So I think it is likely that all cached sessions can be loaded. I'd say, just copy the .sqlite file into an empty new cache directory, tell the cache to use this directory with only that single file in it and see if it works.

Btw, I had any idea concerning your earlier question about the serrated traces. Which method are you using to get the car data? Lap.telemetry or Lap.get_car_data() ?

No, it doesn't work. Session not found exception comes out.

theOehrly commented 3 years ago

Maybe yes. The .pkl files cannot be loaded with the new version anymore. But it should still be able to use the http cache. So I think it is likely that all cached sessions can be loaded. I'd say, just copy the .sqlite file into an empty new cache directory, tell the cache to use this directory with only that single file in it and see if it works. Btw, I had any idea concerning your earlier question about the serrated traces. Which method are you using to get the car data? Lap.telemetry or Lap.get_car_data() ?

No, it doesn't work. Session not found exception comes out.

I feared as much. Then there's no easy way to make it work, I'm sorry. The old cache saved the full Laps object. This does not play nicely if you change the code and therefore that object. The new cache saves parsed api data only to make this less problematic in future updates.

The cache was only intended to speed things up. It was never meant to hold the only copy of the data. But now we have that problem after the data is no longer available on the server.

So you'll have to use the old version to use the old cached data.

There's one rather manual way that might work. You could potentially load all the data in the old version. Then you export it to some 'static' data format like a csv file. And then you manually load that data in the new version. But that's probably quite time consuming and might require some modification of the data before it can be imported.

TerminatorPlati commented 3 years ago

It doesn't matter, thank you very much for what you are doing

TerminatorPlati commented 3 years ago

Hello, is there a method to prevent these errors from being drawn in the speed lines?

Thanks for what you are doing image

theOehrly commented 3 years ago

Hello, is there a method to prevent these errors from being drawn in the speed lines?

Thanks for what you are doing image

Not easily. You can only remove these steps by smoothing the data. But this brings has its own problems. See here... Screenshot 2021-04-06 234716

This was smoothed with a very simple running average filter.

You lose top speed and braking points. And the same problem occurs again when the car starts accelerating. So you also lose minimum cornering speeds.

I suppose there has to be some way to apply smoothing without degrading the quality of other parts of the data. But I currently don't have a solution.

TerminatorPlati commented 3 years ago

Hello, is there a method to prevent these errors from being drawn in the speed lines? Thanks for what you are doing image

Not easily. You can only remove these steps by smoothing the data. But this brings has its own problems. See here... Screenshot 2021-04-06 234716

This was smoothed with a very simple running average filter.

You lose top speed and braking points. And the same problem occurs again when the car starts accelerating. So you also lose minimum cornering speeds.

I suppose there has to be some way to apply smoothing without degrading the quality of other parts of the data. But I currently don't have a solution.

Ok, so from what you tell me it is not possible to do this without losing the consistency of the data. Did I get it right?

Furthermore, is it possible to improve the verticality of the data?

theOehrly commented 3 years ago

About the first one, the smoothing: It's not necessarily impossible. It's just a bit complicated. I am planning to look into it at some point. But no promises that that'll happen any time soon.

The second problem is different of course. And it is related to different things.

  1. The data from multiple cars needs to be properly synchronized. I'm relying on the timestamps that are sent with the data. But whether there is a small difference or not is difficult to tell.
  2. The timestamp for the beginning of a lap needs to be calculated correctly. The api does not provide proper timestamps for that and calculating them accurately is difficult. Accurate here means an error of only a few hundredths of a second (hopefully). This means that the data for the whole lap may be slightly shifted.
  3. The distance is calculated by integrating speed over time. This cannot be perfect because the speed data is not perfect in the first place. But it works surprisingly well. A better approach may be to align the data based on the car position (coordinates). But this is also considerably more complicated. It's also somewhere on the list of things I'd like to do.

The best solution to all of these problems would be to have better raw data.

Getting as much accuracy as possible from the data we have is difficult. It is a continuous process of improving and fine-tuning small things.

Long story short, there is no quick fix to improve this.

TerminatorPlati commented 3 years ago

Screenshot 2021-04-06 234716

You could share the code you used to do the data smoothing. I want to check out if I can improve something. In case I make a Pull Request

theOehrly commented 3 years ago

This was just some code I quickly wrote for the purpose of demonstrating the problem.

N_smooth = 3
data = ref.get_car_data().add_distance()
smooth_speed = np.convolve(data['Speed'], np.ones(N_smooth) / N_smooth, mode='same')

This is applying a running average over three elements at a time. It's basically the simplest smoothing you can do.

I don't actually think that this is a good approach. It probably won't get you anywhere.

I had tried something more complex quite some time ago. For example a Savitzky–Golay filter (scipy.signal.savgol_filter). But it didn't yield any usable results either. I haven't looked into it anymore since then.

TerminatorPlati commented 3 years ago

This was just some code I quickly wrote for the purpose of demonstrating the problem.

N_smooth = 3
data = ref.get_car_data().add_distance()
smooth_speed = np.convolve(data['Speed'], np.ones(N_smooth) / N_smooth, mode='same')

This is applying a running average over three elements at a time. It's basically the simplest smoothing you can do.

I don't actually think that this is a good approach. It probably won't get you anywhere.

I had tried something more complex quite some time ago. For example a Savitzky–Golay filter (scipy.signal.savgol_filter). But it didn't yield any usable results either. I haven't looked into it anymore since then.

Hi, I'm working on smoothing, but I have a problem that I don't know how to solve. As you can see in the image, the graph does not start with the vertical line of 0, do you know how to fix it?

Immagine 2021-04-13 193143

theOehrly commented 3 years ago

Yes, it's not very difficult to fix. But let me explain the reason for it first.

The method .get_car_data returns all telemetry samples between the start and the end of a lap. Of course, there is no data sample which exactly coincides with the beginning of the lap. This means there is a value slightly before 0 and one slightly after. The only way to get a value at exactly 0 is by interpolation. But interpolation usually decreases the accuracy of the data. Therefore, no interpolation is done by default. Some time ago you asked about the very serrated speed traces. You may remember that you were using .telemetry instead of .get_car_data at first. .telemetry merges the car data and position data which requires interpolation. This was a very good example for how interpolation can decrease the accuracy of the data. The takeaway from that is: only interpolate values if you really need to.

Now, to solve your problem, you can explicitly tell .get_car_data to interpolate the first and last value so that there is a value for the beginning and for the end of the lap.

data = ref.get_car_data(interpolate_edges=True).add_distance()

If you check https://theoehrly.github.io/Fast-F1/core.html#fastf1.core.Lap.get_car_data you see that keyword arguments are passed through to Telemetry.slice_by_lap. If you take a look at that you'll see the argument explained there. There are some more arguments for padding of the sliced data too.

JSEHV commented 3 years ago

Suggestion for a standard plot to compare 2 drivers with car data. Maybe a default function and let user supply parameters: year, wknd, ses, drv1, drv2. Colors in the graph are automatically adjusted to the drivers team color. Title is in F1 style font

import fastf1 as ff1
import matplotlib.font_manager as fm
from fastf1 import plotting
from matplotlib import pyplot as plt

plotting.setup_mpl()

ff1.Cache.enable_cache('/path/to/cache/folder')  # optional but recommended

year = 2021
wknd = 9
ses = 'Q'
drv1 = 'VER'
drv2 = 'NOR'

weekend = ff1.get_session(year, wknd)
session = ff1.get_session(year, wknd, ses)
laps = session.load_laps(with_telemetry=True)

first_driver = laps.pick_driver(drv1)
first_driver_info = session.get_driver(drv1)
first_color = plotting.team_color(first_driver_info.team)
second_driver = laps.pick_driver(drv2)
second_driver_info = session.get_driver(drv2)
second_color = plotting.team_color(second_driver_info.team)

first_driver = laps.pick_driver(drv1).pick_fastest()
first_car = first_driver.get_car_data()
second_driver = laps.pick_driver(drv2).pick_fastest()
second_car = second_driver.get_car_data()

fig, ax = plt.subplots(6)

prop = fm.FontProperties(fname='Formula1-Regular.otf')
fig.suptitle(f'{first_driver_info.familyname} vs {second_driver_info.familyname} - Fastest lap - {weekend.name} {year} {ses}', fontproperties=prop, size=18)

l2, = ax[0].plot(second_car['Time'], second_car['Speed'], color=second_color)
l1, = ax[0].plot(first_car['Time'], first_car['Speed'], color=first_color)
ax[1].plot(second_car['Time'], second_car['RPM'], color=second_color)
ax[1].plot(first_car['Time'], first_car['RPM'], color=first_color)
ax[2].plot(second_car['Time'], second_car['nGear'], color=second_color)
ax[2].plot(first_car['Time'], first_car['nGear'], color=first_color)
ax[3].plot(second_car['Time'], second_car['Throttle'], color=second_color)
ax[3].plot(first_car['Time'], first_car['Throttle'], color=first_color)
ax[4].plot(second_car['Time'], second_car['Brake'], color=second_color)
ax[4].plot(first_car['Time'], first_car['Brake'], color=first_color)
ax[5].plot(second_car['Time'], second_car['DRS'], color=second_color)
ax[5].plot(first_car['Time'], first_car['DRS'], color=first_color)

ax[0].set_ylabel("Speed [km/h]")
ax[1].set_ylabel("RPM [#]")
ax[2].set_ylabel("Gear [#]")
ax[3].set_ylabel("Throttle [%]")
ax[4].set_ylabel("Brake [%]")
ax[5].set_ylabel("DRS")

ax[0].get_xaxis().set_ticklabels([])
ax[1].get_xaxis().set_ticklabels([])
ax[2].get_xaxis().set_ticklabels([])
ax[3].get_xaxis().set_ticklabels([])
ax[4].get_xaxis().set_ticklabels([])

fig.align_ylabels()
fig.legend((l1, l2), (f'{first_driver_info.familyname}', f'{second_driver_info.familyname}'), 'upper right')

plt.subplots_adjust(left=0.06 ,right=0.99, top=0.9, bottom=0.05)
plt.show()

Result (example, used exact script as shown above):

graph_compare_drivers

theOehrly commented 3 years ago

This is a nice example in my opinion. It shows telemetry, driver info and weekend info. I think it's a good idea to include it somewhere in fastf1. But I don't think I'll implement this as a function that just creates your plot. I think this is pretty inflexible if someone wants to change something. I'm leaning more towards adding an example gallery to the docs, like matplotlib has for example.

But in any case, while that F1 font is nice. It's better not to include it in FastF1 itself. And requiring that everyone installs it manually is not really a good alternative either. And it might be nice to clean up the code a bit so it's not so much duplicate stuff.

JSEHV commented 3 years ago

Fully behind your explanation. Yes, I need to clean up the code and make it more flexible. Before putting more time in it, I preferred to share it with you, so feedback can be included :)

Just let me know how these examples can be best supplied/published. Will you need an issue created in Github for each?

JSEHV commented 3 years ago

@TempBet: Example of generating the track based on the supplied data, without and with sectors. Microsectors will be a next step, where based on eg. the speed coloring will be applied.

Title is in F1 style font

import fastf1 as ff1
import matplotlib.font_manager as fm
from fastf1 import plotting
from matplotlib import pyplot as plt
from labellines import labelLine, labelLines

def add_arrow(line, position=None, start_ind=None, direction='right', size=15, color=None):

    if color is None:
        color = line.get_color()

    xdata = line.get_xdata()
    ydata = line.get_ydata()

    if position is None:
        position = xdata.mean()
    # find closest index
    if start_ind is None:
        start_ind = len(xdata) // 2

    if direction == 'right':
        end_ind = start_ind + 1
    else:
        end_ind = start_ind - 1

    line.axes.annotate('',
        xytext=(xdata[start_ind], ydata[start_ind]),
        xy=(xdata[end_ind], ydata[end_ind]),
        arrowprops=dict(arrowstyle="-|>", color=color),
        size=size
    )

ff1.Cache.enable_cache('/path/to/cache/folder')  # optional but recommended

year = 2021
wknd = 9
ses = 'R'

weekend = ff1.get_session(year, wknd)
session = ff1.get_session(year, wknd, ses)
laps = session.load_laps(with_telemetry=True)
lap = laps.pick_fastest()

# Track without sectors

fig, ax1 = plt.subplots(figsize=(12, 6.75))

prop = fm.FontProperties(fname='Formula1-Bold.otf')
fig.suptitle(f'{weekend.name} {year}', fontproperties=prop, size=24, y=0.97)

ax1.plot(lap.telemetry['X'], lap.telemetry['Y'], color='black',  linestyle='-', linewidth=16)
line = ax1.plot(lap.telemetry['X'], lap.telemetry['Y'], color='red',  linestyle='-', linewidth=3)[0]

add_arrow(line, start_ind=1, size=20, color='red')

plt.subplots_adjust(left=0.01 ,right=0.99, top=0.9, bottom=0.01)
plt.axis('off')
plt.show()

# Track with sectors

sec1 = lap.Sector1Time
sec2 = lap.Sector1Time+lap.Sector2Time
sec3 = lap.Sector1Time+lap.Sector2Time+lap.Sector3Time
times = lap.telemetry[['Time', 'X', 'Y']]
rslt1 = times[(times['Time'] <= sec1)]
rslt2 = times[(times['Time'] <= sec2) & (times['Time'] > sec1)]
rslt3 = times[(times['Time'] <= sec3) & (times['Time'] > sec2)]

fig2, ax2 = plt.subplots(figsize=(12, 6.75))

prop = fm.FontProperties(fname='Formula1-Bold.otf')
fig2.suptitle(f'{weekend.name} {year}', fontproperties=prop, size=24, y=0.97)

ax2.plot(lap.telemetry['X'], lap.telemetry['Y'], color='black',  linestyle='-', linewidth=16)
line1 = ax2.plot(rslt1['X'], rslt1['Y'], label='sector 1', color='red', linestyle='-', linewidth=3)[0]
line2 = ax2.plot(rslt2['X'], rslt2['Y'], label='sector 2', color='cornflowerblue', linestyle='-', linewidth=3)[0]
line3 = ax2.plot(rslt3['X'], rslt3['Y'], label='sector 3', color='gold', linestyle='-', linewidth=3)[0]

add_arrow(line1, start_ind=1, size=20, color='white')
labelLines(plt.gca().get_lines(), backgroundcolor='black', fontsize=8, zorder=2.5)

plt.subplots_adjust(left=0.01 ,right=0.99, top=0.9, bottom=0.01)
plt.axis('off')
plt.show()

Result without sectors:

track_without_sectors

Result with sectors:

track_with_sectors

JSEHV commented 3 years ago

@TempBet: See below the example to use data for colored line segments on the track. In this example the Speed of a driver is used, but you can use any telemetry parameter. Adjust the colormap if you prefer a different colormap

import fastf1 as ff1
import matplotlib.font_manager as fm
import numpy as np
import matplotlib as mpl

from matplotlib import pyplot as plt
from labellines import labelLine, labelLines
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm

ff1.Cache.enable_cache('/path/to/cache/folder')  # optional but recommended

year = 2021
wknd = 9
ses = 'R'
driver = 'RIC'
colorvalue = 'Speed'
colormap = mpl.cm.plasma

weekend = ff1.get_session(year, wknd)
session = ff1.get_session(year, wknd, ses)
laps = session.load_laps(with_telemetry=True)
lap = laps.pick_driver(driver).pick_fastest()

# Track with segments

x = lap.telemetry['X']              # values for x-axis
y = lap.telemetry['Y']              # values for y-axis
dydx = lap.telemetry[colorvalue]     # value to base color gradient on

# Create a set of line segments so that we can color them individually
# This creates the points as a N x 1 x 2 array so that we can stack points
# together easily to get the segments. The segments array for line collection
# needs to be (numlines) x (points per line) x 2 (for x and y)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)

# Create a plot
fig, axs = plt.subplots(sharex=True, sharey=True, figsize=(12, 6.75))

# Create title for plotting
prop = fm.FontProperties(fname='Formula1-Bold.otf')
fig.suptitle(f'{weekend.name} {year} - {driver} - {colorvalue}', fontproperties=prop, size=24, y=0.97)

# Create background track line
axs.plot(lap.telemetry['X'], lap.telemetry['Y'], color='black', linestyle='-', linewidth=16 ,zorder=0)

# Create a continuous norm to map from data points to colors
norm = plt.Normalize(dydx.min(), dydx.max())
lc = LineCollection(segments, cmap=colormap, norm=norm, linestyle='-', linewidth=3)

# Set the values used for colormapping
lc.set_array(dydx)

# Merge all line segments together
line = axs.add_collection(lc)

# Create legend
cbaxes = fig.add_axes([0.25, 0.05, 0.5, 0.05])
normlegend = mpl.colors.Normalize(vmin=dydx.min(), vmax=dydx.max())
legend = mpl.colorbar.ColorbarBase(cbaxes, norm=normlegend, cmap=colormap, orientation="horizontal")

# Adjust margins and turn of axis
plt.subplots_adjust(left=0.1 ,right=0.9, top=0.9, bottom=0.12)
axs.axis('off')

# Show the plot
plt.show()

Result:

track_segments_speed

Results with just changing 2 parameters (and a driver of choice):

colorvalue = 'DRS'
colormap = mpl.cm.RdYlGn

track_segments_drs

colorvalue = 'nGear'
colormap = mpl.cm.Set2
(and replacing: dydx.min() with: 0, to fit the colors perfectly to the 8 gears)

track_segments_ngear

If you want a thicker inner line, just change the 3 to a higher value for 'linewidth' in: (The background black line is set to 16, but you can change that too. You'll find it, there are only 2 places where 'linewidth' is used)

lc = LineCollection(segments, cmap=colormap, norm=norm, linestyle='-', linewidth=3)
theOehrly commented 3 years ago

@JSEHV This should probably be discussed further in a separate issue. I have now updated the documentation. I had been working on this for a bit already. Now there is an example gallery too. See the examples directory. These are rendered in the documentation too. It's pretty empty still currently.

Imo, a separate issue for multiple plot suggestions is fine. And we can discuss it there. But if you want, you can also open a pull request for some gallery example files. An issue is fine for this too, though,

theOllieS commented 3 years ago

@TempBet: See below the example to use data for colored line segments on the track.

These examples are great. Would you have any idea of how to approach comparing the mini sector times of multiple drivers? I've been trying for a while but can't seem to be making much progress.

Thanks

PaulusPietsma commented 3 years ago

@theOehrly Hi there, nice work on this Python F1 API package! Looks really nice!

I want to build something similar myself, but a bit less fancy (mostly to learn and improve my Python skills). After some searching online, I could not find any documentation about the livetimings F1 API. So I was wondering how did you figure this out? Is there some API documentation out there that I can use to figure out which API calls are possible and what to expect to retrieve with these calls?

Thank you in advance!

theOehrly commented 3 years ago

@theOehrly Hi there, nice work on this Python F1 API package! Looks really nice!

I want to build something similar myself, but a bit less fancy (mostly to learn and improve my Python skills). After some searching online, I could not find any documentation about the livetimings F1 API. So I was wondering how did you figure this out? Is there some API documentation out there that I can use to figure out which API calls are possible and what to expect to retrieve with these calls?

Thank you in advance!

There's no API documentation, I'm sorry. For all that I know, the documentation here and the comments in the code are the best documentation (only maybe?) that you will find. But take everything of it with a grain of salt, as there's lots of guessing involved. The API isn't public and (sadly) not really intended for third parties (I guess).