tracinginsights commited on
Commit
3dc0572
·
1 Parent(s): 8411795

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +5 -436
main.py CHANGED
@@ -1,440 +1,9 @@
1
- import datetime
2
  import os
3
- import streamlit as st
4
- import numpy as np
5
- import math
6
- import fastf1
7
- import pandas as pd
8
- from fastapi import FastAPI
9
- from fastapi.middleware.cors import CORSMiddleware
10
- from fastapi.responses import FileResponse, HTMLResponse
11
 
 
12
 
13
- from pydantic import BaseModel
 
14
 
15
- import functools
16
- import math
17
- import numpy as np
18
- import concurrent.futures
19
-
20
-
21
- import available_data
22
-
23
- app = FastAPI()
24
-
25
-
26
-
27
- app.add_middleware(
28
- CORSMiddleware,
29
- allow_origins=["*"],
30
- allow_credentials=True,
31
- allow_methods=["*"],
32
- allow_headers=["*"],
33
- )
34
-
35
- FASTF1_CACHE_DIR = os.environ['FASTF1_CACHE_DIR']
36
-
37
- fastf1.Cache.enable_cache(FASTF1_CACHE_DIR)
38
-
39
-
40
- def smooth_derivative(t_in, v_in):
41
-
42
- #
43
- # Function to compute a smooth estimation of a derivative.
44
- # [REF: http://holoborodko.com/pavel/numerical-methods/numerical-derivative/smooth-low-noise-differentiators/]
45
- #
46
-
47
- # Configuration
48
- #
49
- # Derivative method: two options: 'smooth' or 'centered'. Smooth is more conservative
50
- # but helps to supress the very noisy signals. 'centered' is more agressive but more noisy
51
- method = "smooth"
52
-
53
- t = t_in.copy()
54
- v = v_in.copy()
55
-
56
- # (0) Prepare inputs
57
-
58
- # (0.1) Time needs to be transformed to seconds
59
- try:
60
- for i in range(0, t.size):
61
- t.iloc[i] = t.iloc[i].total_seconds()
62
- except:
63
- pass
64
-
65
- t = np.array(t)
66
- v = np.array(v)
67
-
68
- # (0.1) Assert they have the same size
69
- assert t.size == v.size
70
-
71
- # (0.2) Initialize output
72
- dvdt = np.zeros(t.size)
73
-
74
- # (1) Manually compute points out of the stencil
75
-
76
- # (1.1) First point
77
- dvdt[0] = (v[1] - v[0]) / (t[1] - t[0])
78
-
79
- # (1.2) Second point
80
- dvdt[1] = (v[2] - v[0]) / (t[2] - t[0])
81
-
82
- # (1.3) Third point
83
- dvdt[2] = (v[3] - v[1]) / (t[3] - t[1])
84
-
85
- # (1.4) Last points
86
- n = t.size
87
- dvdt[n - 1] = (v[n - 1] - v[n - 2]) / (t[n - 1] - t[n - 2])
88
- dvdt[n - 2] = (v[n - 1] - v[n - 3]) / (t[n - 1] - t[n - 3])
89
- dvdt[n - 3] = (v[n - 2] - v[n - 4]) / (t[n - 2] - t[n - 4])
90
-
91
- # (2) Compute the rest of the points
92
- if method == "smooth":
93
- c = [5.0 / 32.0, 4.0 / 32.0, 1.0 / 32.0]
94
- for i in range(3, t.size - 3):
95
- for j in range(1, 4):
96
- dvdt[i] += (
97
- 2 * j * c[j - 1] * (v[i + j] - v[i - j]) /
98
- (t[i + j] - t[i - j])
99
- )
100
- elif method == "centered":
101
- for i in range(3, t.size - 2):
102
- for j in range(1, 4):
103
- dvdt[i] = (v[i + 1] - v[i - 1]) / (t[i + 1] - t[i - 1])
104
-
105
- return dvdt
106
-
107
-
108
- def truncated_remainder(dividend, divisor):
109
- divided_number = dividend / divisor
110
- divided_number = (
111
- -int(-divided_number) if divided_number < 0 else int(divided_number)
112
- )
113
-
114
- remainder = dividend - divisor * divided_number
115
-
116
- return remainder
117
-
118
-
119
- def transform_to_pipi(input_angle):
120
- pi = math.pi
121
- revolutions = int((input_angle + np.sign(input_angle) * pi) / (2 * pi))
122
-
123
- p1 = truncated_remainder(input_angle + np.sign(input_angle) * pi, 2 * pi)
124
- p2 = (
125
- np.sign(
126
- np.sign(input_angle)
127
- + 2
128
- * (
129
- np.sign(
130
- math.fabs(
131
- (truncated_remainder(input_angle + pi, 2 * pi)) / (2 * pi)
132
- )
133
- )
134
- - 1
135
- )
136
- )
137
- ) * pi
138
-
139
- output_angle = p1 - p2
140
-
141
- return output_angle, revolutions
142
-
143
-
144
- def remove_acceleration_outliers(acc):
145
-
146
- acc_threshold_g = 7.5
147
- if math.fabs(acc[0]) > acc_threshold_g:
148
- acc[0] = 0.0
149
-
150
- for i in range(1, acc.size - 1):
151
- if math.fabs(acc[i]) > acc_threshold_g:
152
- acc[i] = acc[i - 1]
153
-
154
- if math.fabs(acc[-1]) > acc_threshold_g:
155
- acc[-1] = acc[-2]
156
-
157
- return acc
158
-
159
-
160
- def compute_accelerations(telemetry):
161
-
162
- v = np.array(telemetry["Speed"]) / 3.6
163
- lon_acc = smooth_derivative(telemetry["Time"], v) / 9.81
164
-
165
- dx = smooth_derivative(telemetry["Distance"], telemetry["X"])
166
- dy = smooth_derivative(telemetry["Distance"], telemetry["Y"])
167
-
168
- theta = np.zeros(dx.size)
169
- theta[0] = math.atan2(dy[0], dx[0])
170
- for i in range(0, dx.size):
171
- theta[i] = (
172
- theta[i - 1] +
173
- transform_to_pipi(math.atan2(dy[i], dx[i]) - theta[i - 1])[0]
174
- )
175
-
176
- kappa = smooth_derivative(telemetry["Distance"], theta)
177
- lat_acc = v * v * kappa / 9.81
178
-
179
- # Remove outliers
180
- lon_acc = remove_acceleration_outliers(lon_acc)
181
- lat_acc = remove_acceleration_outliers(lat_acc)
182
-
183
- return np.round(lon_acc,2), np.round(lat_acc,2)
184
-
185
- # @st.cache_data
186
- @app.get("/wdc", response_model=None)
187
- async def driver_standings() -> any:
188
- YEAR = 2023 #datetime.datetime.now().year
189
- df = pd.DataFrame(
190
- pd.read_html(f"https://www.formula1.com/en/results.html/{YEAR}/drivers.html")[0]
191
- )
192
- df = df[["Driver", "PTS", "Car"]]
193
- # reverse the order
194
- df = df.sort_values(by="PTS", ascending=True)
195
-
196
- # in Driver column only keep the last 3 characters
197
- df["Driver"] = df["Driver"].str[:-5]
198
-
199
- # add colors to the dataframe
200
- car_colors = available_data.team_colors(YEAR)
201
- df["fill"] = df["Car"].map(car_colors)
202
-
203
-
204
- # remove rows where points is 0
205
- df = df[df["PTS"] != 0]
206
- df.reset_index(inplace=True, drop=True)
207
- df.rename(columns={"PTS": "Points"}, inplace=True)
208
-
209
- return {"WDC":df.to_dict("records")}
210
-
211
- # @st.cache_data
212
- @app.get("/", response_model=None)
213
- async def root():
214
- return HTMLResponse(
215
- content="""<iframe src="https://tracinginsights-f1-analysis.hf.space" frameborder="0" style="width:100%; height:100%;" scrolling="yes" allowfullscreen:"yes"></iframe>""",
216
- status_code=200)
217
-
218
- # @st.cache_data
219
- @app.get("/years", response_model=None)
220
- async def years_available() -> any:
221
- # make a list from 2018 to current year
222
- current_year = datetime.datetime.now().year
223
- years = list(range(2018, current_year+1))
224
- # reverse the list to get the latest year first
225
- years.reverse()
226
- years = [{"label": str(year), "value": year} for year in years]
227
- return {"years": years}
228
-
229
-
230
- # format for events {"events":[{"label":"Saudi Arabian Grand Prix","value":2},{"label":"Bahrain Grand Prix","value":1},{"label":"Pre-Season Testing","value":"t1"}]}
231
-
232
- # @st.cache_data
233
- @app.get("/{year}", response_model=None)
234
- async def events_available(year: int) -> any:
235
- # get events available for a given year
236
- data = available_data.LatestData(year)
237
- events = data.get_events()
238
- events = [{"label": event, "value": event} for i, event in enumerate(events)]
239
- events.reverse()
240
-
241
- return {"events": events}
242
-
243
- # format for sessions {"sessions":[{"label":"FP1","value":"FP1"},{"label":"FP2","value":"FP2"},{"label":"FP3","value":"FP3"},{"label":"Qualifying","value":"Q"},{"label":"Race","value":"R"}]}
244
-
245
- # @st.cache_data
246
- @functools.cache
247
- @app.get("/{year}/{event}", response_model=None)
248
- async def sessions_available(year: int, event: str | int) -> any:
249
- # get sessions available for a given year and event
250
- data = available_data.LatestData(year)
251
- sessions = data.get_sessions(event)
252
- sessions = [{"label": session, "value": session} for session in sessions]
253
-
254
- return {"sessions": sessions}
255
-
256
- # format for drivers {"drivers":[{"color":"#fff500","label":"RIC","value":"RIC"},{"color":"#ff8700","label":"NOR","value":"NOR"},{"color":"#c00000","label":"VET","value":"VET"},{"color":"#0082fa","label":"LAT","value":"LAT"},{"color":"#787878","label":"GRO","value":"GRO"},{"color":"#ffffff","label":"GAS","value":"GAS"},{"color":"#f596c8","label":"STR","value":"STR"},{"color":"#787878","label":"MAG","value":"MAG"},{"color":"#0600ef","label":"ALB","value":"ALB"},{"color":"#ffffff","label":"KVY","value":"KVY"},{"color":"#fff500","label":"OCO","value":"OCO"},{"color":"#0600ef","label":"VER","value":"VER"},{"color":"#00d2be","label":"HAM","value":"HAM"},{"color":"#ff8700","label":"SAI","value":"SAI"},{"color":"#00d2be","label":"BOT","value":"BOT"},{"color":"#960000","label":"GIO","value":"GIO"}]}
257
-
258
- # @st.cache_data
259
- @functools.cache
260
- @app.get("/{year}/{event}/{session}", response_model=None)
261
- async def session_drivers(year: int, event: str | int, session: str) -> any:
262
-
263
- # get drivers available for a given year, event and session
264
- f1session = fastf1.get_session(year, event, session)
265
- api_path = f1session.api_path
266
- drivers_raw = fastf1.api.driver_info(api_path)
267
- drivers = [{
268
- "color": available_data.team_colors(year)[driver[1]['TeamName']],
269
- "label": driver[1]['Tla'],
270
- "value": driver[1]['Tla']
271
- } for driver in drivers_raw.items()]
272
-
273
- return {"drivers": drivers}
274
-
275
-
276
- # format for chartData {"chartData":[{"lapnumber":1},{
277
- # "VER":91.564,
278
- # "VER_compound":"SOFT",
279
- # "VER_compound_color":"#FF5733",
280
- # "lapnumber":2
281
- # },{"lapnumber":3},{"VER":90.494,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":4},{"lapnumber":5},{"VER":90.062,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":6},{"lapnumber":7},{"VER":89.815,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":8},{"VER":105.248,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":9},{"lapnumber":10},{"VER":89.79,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":11},{"VER":145.101,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":12},{"lapnumber":13},{"VER":89.662,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":14},{"lapnumber":15},{"VER":89.617,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":16},{"lapnumber":17},{"VER":140.717,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":18}]}
282
-
283
- # @st.cache_data
284
- @functools.cache
285
- @app.get("/{year}/{event}/{session}/{driver}", response_model=None)
286
- async def laps_data(year: int, event: str | int, session: str, driver: str) -> any:
287
-
288
-
289
- # get drivers available for a given year, event and session
290
- f1session = fastf1.get_session(year, event, session)
291
- f1session.load(telemetry=False, weather=False, messages=False)
292
- laps = f1session.laps
293
- team_colors = available_data.team_colors(year)
294
- # add team_colors dict to laps on Team column
295
-
296
- drivers = laps.Driver.unique()
297
- # for each driver in drivers, get the Team column from laps and get the color from team_colors dict
298
- drivers = [{"color": team_colors[laps[laps.Driver ==
299
- driver].Team.iloc[0]], "label": driver, "value": driver} for driver in drivers]
300
-
301
- driver_laps = laps.pick_driver(driver)
302
- driver_laps['LapTime'] = driver_laps['LapTime'].dt.total_seconds()
303
- compound_colors = {
304
- "SOFT": "#FF0000",
305
- "MEDIUM": "#FFFF00",
306
- "HARD": "#FFFFFF",
307
- "INTERMEDIATE": "#00FF00",
308
- "WET": "#088cd0",
309
-
310
- }
311
-
312
- driver_laps_data = []
313
-
314
- for _, row in driver_laps.iterrows():
315
-
316
- if row['LapTime'] > 0:
317
- lap = {f"{driver}": row['LapTime'],
318
- f"{driver}_compound": row['Compound'],
319
- f"{driver}_compound_color": compound_colors[row['Compound']],
320
- "lapnumber": row['LapNumber']}
321
- else:
322
- lap = {"lapnumber": row['LapNumber']}
323
-
324
- driver_laps_data.append(lap)
325
-
326
- return {"chartData": driver_laps_data}
327
-
328
-
329
- # @st.cache_data
330
- @functools.cache
331
- @app.get("/{year}/{event}/{session}/{driver}/{lap_number}", response_model=None)
332
- async def telemetry_data(year: int, event: str | int, session: str, driver: str, lap_number: int) -> any:
333
-
334
-
335
- f1session = fastf1.get_session(year, event, session)
336
- f1session.load(telemetry=True, weather=False, messages=False)
337
- laps = f1session.laps
338
-
339
- driver_laps = laps.pick_driver(driver)
340
- driver_laps['LapTime'] = driver_laps['LapTime'].dt.total_seconds()
341
-
342
- # get the telemetry for lap_number
343
- selected_lap = driver_laps[driver_laps.LapNumber == lap_number]
344
-
345
- telemetry = selected_lap.get_telemetry()
346
-
347
- lon_acc, lat_acc = compute_accelerations(telemetry)
348
- telemetry["lon_acc"] = lon_acc
349
- telemetry["lat_acc"] = lat_acc
350
-
351
- telemetry['Time'] = telemetry['Time'].dt.total_seconds()
352
-
353
- laptime = selected_lap.LapTime.values[0]
354
- data_key = f"{driver} - Lap {int(lap_number)} - {year} {session} [{int(laptime//60)}:{laptime%60}]"
355
-
356
- telemetry['DRS'] = telemetry['DRS'].apply(lambda x: 1 if x in [10,12,14] else 0)
357
-
358
- brake_tel = []
359
- drs_tel = []
360
- gear_tel = []
361
- rpm_tel = []
362
- speed_tel = []
363
- throttle_tel = []
364
- time_tel = []
365
- track_map = []
366
- lon_acc_tel = []
367
- lat_acc_tel = []
368
-
369
- for _, row in telemetry.iterrows():
370
-
371
- brake = {"x": row['Distance'],
372
- "y": row['Brake'],
373
- }
374
- brake_tel.append(brake)
375
-
376
- drs = {"x": row['Distance'],
377
- "y": row['DRS'],
378
- }
379
- drs_tel.append(drs)
380
-
381
- gear = {"x": row['Distance'],
382
- "y": row['nGear'],
383
- }
384
- gear_tel.append(gear)
385
-
386
- rpm = {"x": row['Distance'],
387
- "y": row['RPM'],
388
- }
389
- rpm_tel.append(rpm)
390
-
391
- speed = {"x": row['Distance'],
392
- "y": row['Speed'],
393
- }
394
- speed_tel.append(speed)
395
-
396
- throttle = {"x": row['Distance'],
397
- "y": row['Throttle'],
398
- }
399
- throttle_tel.append(throttle)
400
-
401
- time = {"x": row['Distance'],
402
- "y": row['Time'],
403
- }
404
- time_tel.append(time)
405
-
406
- lon_acc = {"x": row['Distance'],
407
- "y": row['lon_acc'],
408
- }
409
- lon_acc_tel.append(lon_acc)
410
-
411
- lat_acc = {"x": row['Distance'],
412
- "y": row['lat_acc'],
413
- }
414
- lat_acc_tel.append(lat_acc)
415
-
416
- track = {"x": row['X'],
417
- "y": row['Y'],
418
- }
419
- track_map.append(track)
420
-
421
-
422
-
423
- telemetry_data = {
424
- "telemetryData":{
425
- "brake": brake_tel,
426
- "dataKey": data_key,
427
- "drs": drs_tel,
428
- "gear": gear_tel,
429
- "rpm": rpm_tel,
430
- "speed": speed_tel,
431
- "throttle": throttle_tel,
432
- "time": time_tel,
433
- "lon_acc": lon_acc_tel,
434
- "lat_acc": lat_acc_tel,
435
- "trackMap": track_map,
436
- }
437
- }
438
-
439
- return telemetry_data
440
-
 
1
+ from git import Repo
2
  import os
 
 
 
 
 
 
 
 
3
 
4
+ GITHUB_PAT = os.environ['GITHUB']
5
 
6
+ if not os.path.exists('repo_directory'):
7
+ Repo.clone_from(f'https://tracinginsights:{GITHUB_PAT}@github.com/TracingInsights/fastf1api.git', 'repo_directory' )
8
 
9
+ from repo_directory import *