File size: 3,928 Bytes
0ffbb74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
"""Hero figure for the README: caustic of a real agent run next to a camera frame.



Uses the saved run ``docs/runs/mystery_s7.json`` (a real Muse Spark run on a

"mystery beam"): the caustic plot is rebuilt from its logged tool calls exactly

as the demo's replay does. Saved runs keep no camera frames, so the frame is

re-simulated from the run's ground truth (same beam, camera, position and

exposure; noise from a fixed seed). The output is the same on every run::



    .venv\\Scripts\\python.exe scripts\\make_hero_figure.py

"""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt  # noqa: E402
import numpy as np  # noqa: E402

from beampilot.plots import caustic_figure  # noqa: E402
from beampilot.replay import RunState, load_run, replay_events  # noqa: E402
from beampilot.simulator import BeamParams, CameraConfig, render_frame  # noqa: E402

REPO = Path(__file__).resolve().parents[1]
RUN = REPO / "docs" / "runs" / "mystery_s7.json"
FRAME_ID = "m16"  # a far-field point, large enough to see on the full sensor
OUT = REPO / "docs" / "figures" / "hero.png"
FRAME_SEED = 0


def make_figure(run_path: Path, frame_id: str) -> plt.Figure:
    """Caustic (left) and one re-simulated camera frame (right)."""
    run = load_run(run_path)
    state = RunState()
    for kind, item in replay_events(run):
        if kind == "reply":
            state.apply_model_reply(item)
        else:
            state.apply_tool_call(item)
    state.set_report(run.data.get("report"))
    params = run.data["scenario_params"]
    stage = (params["stage_min_m"] * 1e3, params["stage_max_m"] * 1e3)

    fig = caustic_figure(state, stage)
    fig.set_size_inches(13.0, 4.8)
    caustic_ax = fig.axes[0]
    caustic_ax.set_position([0.06, 0.12, 0.54, 0.76])
    report = run.data["report"]
    m2 = [report["evaluation"][axis]["m2"] for axis in ("x", "y")]
    caustic_ax.set_title(
        f"Agent run on a mystery beam: {report['verdict']}, M²x {m2[0]:.3f}, "
        f"M²y {m2[1]:.3f} ({state.model_calls} model calls)",
        fontsize=11,
    )

    point = state.points[frame_id]
    beam = BeamParams(**params["beam"])
    camera = CameraConfig(**params["camera"])
    frame = render_frame(
        beam,
        camera,
        point.z_mm * 1e-3,
        point.exposure_ms,
        np.random.default_rng(FRAME_SEED),
    )
    span = camera.full_scale_counts - camera.offset_counts
    scaled = np.clip((frame - camera.offset_counts) / span, 0.0, 1.0)
    half_x = camera.nx * camera.pixel_size_m * 1e3 / 2
    half_y = camera.ny * camera.pixel_size_m * 1e3 / 2
    frame_ax = fig.add_axes([0.68, 0.12, 0.30, 0.76])
    frame_ax.imshow(
        np.sqrt(scaled),
        cmap="inferno",
        extent=(-half_x, half_x, half_y, -half_y),
        interpolation="nearest",
    )
    frame_ax.set_xlabel("x (mm)")
    frame_ax.set_ylabel("y (mm)")
    frame_ax.set_title(
        f"Camera frame {frame_id}: z = {point.z_mm:g} mm, {point.exposure_ms:g} ms\n"
        "(re-simulated from the run's ground truth, √ scale)",
        fontsize=10,
    )
    return fig


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--run", type=Path, default=RUN)
    parser.add_argument("--frame", default=FRAME_ID)
    parser.add_argument("--out", type=Path, default=OUT)
    args = parser.parse_args()
    fig = make_figure(args.run, args.frame)
    args.out.parent.mkdir(parents=True, exist_ok=True)
    # No software or date metadata, so the PNG is byte-identical on every run.
    fig.savefig(args.out, dpi=110, metadata={"Software": None})
    plt.close(fig)
    print(f"wrote {args.out}")
    return 0


if __name__ == "__main__":
    sys.exit(main())