Spaces:
Running
Running
| """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") | |
| # Shared y ranges (per row), computed across BOTH runs. | |
| 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]) | |