longitudinal maneuvers: summary table (#33790)

* bob the builder

* table

* clean up
pull/33776/head
Shane Smiskol 8 months ago committed by GitHub
parent 3c5d8da469
commit 9674c7b5af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 54
      tools/longitudinal_maneuvers/generate_report.py

@ -8,6 +8,7 @@ import pprint
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from tabulate import tabulate
from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.logreader import LogReader
from openpilot.system.hardware.hw import Paths from openpilot.system.hardware.hw import Paths
@ -22,17 +23,21 @@ def report(platform, route, _description, CP, maneuvers):
output_fn = output_path / f"{platform}_{route.replace('/', '_')}.html" output_fn = output_path / f"{platform}_{route.replace('/', '_')}.html"
output_path.mkdir(exist_ok=True) output_path.mkdir(exist_ok=True)
target_cross_times = defaultdict(list) target_cross_times = defaultdict(list)
with open(output_fn, "w") as f:
f.write("<h1>Longitudinal maneuver report</h1>\n") builder = [
f.write(f"<h3>{platform}</h3>\n") "<style>summary { cursor: pointer; }\n td, th { padding: 8px; } </style>\n",
f.write(f"<h3>{route}</h3>\n") "<h1>Longitudinal maneuver report</h1>\n",
f"<h3>{platform}</h3>\n",
f"<h3>{route}</h3>\n"
]
if _description is not None: if _description is not None:
f.write(f"<h3>Description: {_description}</h3>\n") builder.append(f"<h3>Description: {_description}</h3>\n")
f.write(f"<details><summary><h3 style='display: inline-block;'>CarParams</h3></summary><pre>{format_car_params(CP)}</pre></details>\n") builder.append(f"<details><summary><h3 style='display: inline-block;'>CarParams</h3></summary><pre>{format_car_params(CP)}</pre></details>\n")
builder.append('{ summary }') # to be replaced below
for description, runs in maneuvers: for description, runs in maneuvers:
print(f'plotting maneuver: {description}, runs: {len(runs)}') print(f'plotting maneuver: {description}, runs: {len(runs)}')
f.write("<div style='border-top: 1px solid #000; margin: 20px 0;'></div>\n") builder.append("<div style='border-top: 1px solid #000; margin: 20px 0;'></div>\n")
f.write(f"<h2>{description}</h2>\n") builder.append(f"<h2>{description}</h2>\n")
for run, msgs in enumerate(runs): for run, msgs in enumerate(runs):
t_carControl, carControl = zip(*[(m.logMonoTime, m.carControl) for m in msgs if m.which() == 'carControl'], strict=True) t_carControl, carControl = zip(*[(m.logMonoTime, m.carControl) for m in msgs if m.which() == 'carControl'], strict=True)
t_carOutput, carOutput = zip(*[(m.logMonoTime, m.carOutput) for m in msgs if m.which() == 'carOutput'], strict=True) t_carOutput, carOutput = zip(*[(m.logMonoTime, m.carOutput) for m in msgs if m.which() == 'carOutput'], strict=True)
@ -54,30 +59,30 @@ def report(platform, route, _description, CP, maneuvers):
_open = 'open' if maneuver_valid else '' _open = 'open' if maneuver_valid else ''
title = f'Run #{int(run)+1}' + (' <span style="color: red">(invalid maneuver!)</span>' if not maneuver_valid else '') title = f'Run #{int(run)+1}' + (' <span style="color: red">(invalid maneuver!)</span>' if not maneuver_valid else '')
f.write(f"<details {_open}><summary><h3 style='display: inline-block;'>{title}</h3></summary>\n") builder.append(f"<details {_open}><summary><h3 style='display: inline-block;'>{title}</h3></summary>\n")
# get first acceleration target and first intersection # get first acceleration target and first intersection
aTarget = longitudinalPlan[0].aTarget aTarget = longitudinalPlan[0].aTarget
target_cross_time = None target_cross_time = None
f.write(f'<h3 style="font-weight: normal">Initial aTarget: {aTarget} m/s^2') builder.append(f'<h3 style="font-weight: normal">Initial aTarget: {aTarget} m/s^2')
# Localizer is noisy, require two consecutive 20Hz frames above threshold # Localizer is noisy, require two consecutive 20Hz frames above threshold
prev_crossed = False prev_crossed = False
for t, lp in zip(t_livePose, livePose, strict=True): for t, lp in zip(t_livePose, livePose, strict=True):
crossed = (0 < aTarget < lp.accelerationDevice.x) or (0 > aTarget > lp.accelerationDevice.x) crossed = (0 < aTarget < lp.accelerationDevice.x) or (0 > aTarget > lp.accelerationDevice.x)
if crossed and prev_crossed: if crossed and prev_crossed:
f.write(f', <strong>crossed in {t:.3f}s</strong>') builder.append(f', <strong>crossed in {t:.3f}s</strong>')
target_cross_time = t target_cross_time = t
if maneuver_valid: if maneuver_valid:
target_cross_times[description].append(t) target_cross_times[description].append(t)
break break
prev_crossed = crossed prev_crossed = crossed
else: else:
f.write(', <strong>not crossed</strong>') builder.append(', <strong>not crossed</strong>')
f.write('</h3>') builder.append('</h3>')
pitches = [math.degrees(m.orientationNED[1]) for m in carControl] pitches = [math.degrees(m.orientationNED[1]) for m in carControl]
f.write(f'<h3 style="font-weight: normal">Average pitch: <strong>{sum(pitches) / len(pitches):0.2f} degrees</strong></h3>') builder.append(f'<h3 style="font-weight: normal">Average pitch: <strong>{sum(pitches) / len(pitches):0.2f} degrees</strong></h3>')
plt.rcParams['font.size'] = 40 plt.rcParams['font.size'] = 40
fig = plt.figure(figsize=(30, 26)) fig = plt.figure(figsize=(30, 26))
@ -116,16 +121,25 @@ def report(platform, route, _description, CP, maneuvers):
buffer = io.BytesIO() buffer = io.BytesIO()
fig.savefig(buffer, format='png') fig.savefig(buffer, format='png')
buffer.seek(0) buffer.seek(0)
f.write(f"<img src='data:image/png;base64,{base64.b64encode(buffer.getvalue()).decode()}' style='width:100%; max-width:800px;'>\n") builder.append(f"<img src='data:image/png;base64,{base64.b64encode(buffer.getvalue()).decode()}' style='width:100%; max-width:800px;'>\n")
f.write("</details>\n") builder.append("</details>\n")
f.write("<h2>Summary</h2>\n") summary = ["<h2>Summary</h2>\n"]
cols = ['maneuver', 'crossed', 'runs', 'mean', 'min', 'max']
table = []
for description, runs in maneuvers: for description, runs in maneuvers:
times = target_cross_times[description] times = target_cross_times[description]
f.write(f"<h3>{description}</h3>\n") l = [description, len(times), len(runs)]
f.write(f"<p>Target crossed {len(times)} out of {len(runs)} runs</p>\n")
if len(times): if len(times):
f.write(f"<p>Mean time to cross: {sum(times) / len(times):.3f}s, min: {min(times):.3f}s, max: {max(times):.3f}s</p>\n") l.extend([round(sum(times) / len(times), 2), round(min(times), 2), round(max(times), 2)])
table.append(l)
summary.append(tabulate(table, headers=cols, tablefmt='html', numalign='left') + '\n')
sum_idx = builder.index('{ summary }')
builder[sum_idx:sum_idx + 1] = summary
with open(output_fn, "w") as f:
f.write(''.join(builder))
print(f"\nReport written to {output_fn}\n") print(f"\nReport written to {output_fn}\n")

Loading…
Cancel
Save