| """Render side-by-side comparison plots for JS vs Python benchmark runs. |
| |
| Two columns Γ three rows: each row is a channel (left antenna, right |
| antenna, head Y); left column is the JS run, right column is the Python |
| run. Same y range per row so the comparison is honest. |
| """ |
|
|
| import csv |
| import sys |
| from pathlib import Path |
|
|
| import matplotlib |
| matplotlib.use("Agg") |
| import matplotlib.pyplot as plt |
|
|
|
|
| def load(path): |
| rows = [] |
| with open(path) as f: |
| for r in csv.DictReader(f): |
| rows.append({k: float(v) if v else None for k, v in r.items()}) |
| return rows |
|
|
|
|
| def col(rows, key): |
| return [r[key] for r in rows] |
|
|
|
|
| def plot_pair(ax, time, cmd, act, title, ylabel, ylim=None): |
| ax.plot(time, cmd, color="C0", linestyle="--", linewidth=1.2, label="commanded") |
| ax.plot(time, act, color="C3", linewidth=1.2, label="measured") |
| ax.set_title(title, fontsize=10) |
| ax.set_xlabel("t (s)", fontsize=9) |
| ax.set_ylabel(ylabel, fontsize=9) |
| ax.grid(True, alpha=0.3) |
| if ylim is not None: |
| ax.set_ylim(*ylim) |
| ax.legend(fontsize=8, loc="upper right") |
| ax.tick_params(labelsize=8) |
|
|
|
|
| def main(js_csv, py_csv, out_png): |
| js = load(js_csv) |
| py = load(py_csv) |
|
|
| js_t = col(js, "t_s") |
| py_t = col(py, "t_s") |
|
|
| |
| def span(*lists, pad=3): |
| vals = [v for lst in lists for v in lst if v is not None] |
| return min(vals) - pad, max(vals) + pad |
|
|
| yl_l = span(col(js, "left_commanded_deg"), col(js, "left_actual_deg"), |
| col(py, "left_commanded_deg"), col(py, "left_actual_deg")) |
| yl_r = span(col(js, "right_commanded_deg"), col(js, "right_actual_deg"), |
| col(py, "right_commanded_deg"), col(py, "right_actual_deg")) |
| yl_h = span(col(js, "head_y_commanded_cm"), col(js, "head_y_actual_cm"), |
| col(py, "head_y_commanded_cm"), col(py, "head_y_actual_cm"), pad=0.5) |
|
|
| fig, axes = plt.subplots(3, 2, figsize=(13, 9)) |
| plot_pair(axes[0, 0], js_t, col(js, "left_commanded_deg"), col(js, "left_actual_deg"), |
| "Left antenna β JS", "deg", yl_l) |
| plot_pair(axes[0, 1], py_t, col(py, "left_commanded_deg"), col(py, "left_actual_deg"), |
| "Left antenna β Python", "deg", yl_l) |
| plot_pair(axes[1, 0], js_t, col(js, "right_commanded_deg"), col(js, "right_actual_deg"), |
| "Right antenna β JS", "deg", yl_r) |
| plot_pair(axes[1, 1], py_t, col(py, "right_commanded_deg"), col(py, "right_actual_deg"), |
| "Right antenna β Python", "deg", yl_r) |
| plot_pair(axes[2, 0], js_t, col(js, "head_y_commanded_cm"), col(js, "head_y_actual_cm"), |
| "Head Y β JS", "cm", yl_h) |
| plot_pair(axes[2, 1], py_t, col(py, "head_y_commanded_cm"), col(py, "head_y_actual_cm"), |
| "Head Y β Python", "cm", yl_h) |
|
|
| fig.suptitle("Streaming benchmark: JS (browser) vs Python (script)", |
| fontsize=12, fontweight="bold") |
| fig.tight_layout(rect=(0, 0, 1, 0.97)) |
| fig.savefig(out_png, dpi=140) |
| print(f"Wrote {out_png}") |
|
|
|
|
| if __name__ == "__main__": |
| main(sys.argv[1], sys.argv[2], sys.argv[3]) |
|
|