|
| 1 | +"""Build the GitHub Pages site: a demo landing (GIFs) + the live benchmark. |
| 2 | +
|
| 3 | + python3 tools/pages/build_site.py --repo-root . --out _site |
| 4 | +
|
| 5 | +Copies docs/*.gif, generates the adapter benchmark dashboard via the CLI |
| 6 | +(GPU-free dummy vs remote-over-HTTP), and writes a self-contained index.html. |
| 7 | +Used by .github/workflows/pages.yml. |
| 8 | +""" |
| 9 | +import argparse |
| 10 | +import glob |
| 11 | +import html |
| 12 | +import os |
| 13 | +import shutil |
| 14 | +import subprocess |
| 15 | +import sys |
| 16 | + |
| 17 | +REPO_URL = "https://github.com/rsasaki0109/worldmodels_ros2" |
| 18 | + |
| 19 | +CAPTIONS = { |
| 20 | + "imagination.gif": "Imagined future occupancy + risk from one ROS 2 call (dummy).", |
| 21 | + "jepa_compare.gif": "Same camera stream, two real World Models: I-JEPA vs V-JEPA 2 surprise.", |
| 22 | + "nav2_scoring.gif": "Nav2 candidate paths ranked by model-based risk; safest highlighted.", |
| 23 | + "ijepa_surprise.gif": "Real I-JEPA surprise spiking on scene changes (GPU).", |
| 24 | +} |
| 25 | +HERO = "imagination.gif" |
| 26 | +ORDER = ["jepa_compare.gif", "nav2_scoring.gif", "ijepa_surprise.gif"] |
| 27 | + |
| 28 | + |
| 29 | +def _card(gif: str) -> str: |
| 30 | + cap = html.escape(CAPTIONS.get(gif, gif)) |
| 31 | + return (f'<figure><img src="{gif}" alt="{cap}" loading="lazy">' |
| 32 | + f'<figcaption>{cap}</figcaption></figure>') |
| 33 | + |
| 34 | + |
| 35 | +def build_index(gifs: list) -> str: |
| 36 | + hero = _card(HERO) if HERO in gifs else "" |
| 37 | + gallery = "\n".join(_card(g) for g in ORDER if g in gifs) |
| 38 | + extras = "\n".join(_card(g) for g in sorted(gifs) |
| 39 | + if g != HERO and g not in ORDER) |
| 40 | + return f"""<!doctype html> |
| 41 | +<html lang="en"><head><meta charset="utf-8"> |
| 42 | +<meta name="viewport" content="width=device-width, initial-scale=1"> |
| 43 | +<title>world_model_ros2 — the ROS 2 layer for World Models</title> |
| 44 | +<style> |
| 45 | + :root{{color-scheme:dark}} |
| 46 | + body{{font-family:system-ui,sans-serif;margin:0;background:#15151a;color:#e9e9ee}} |
| 47 | + .wrap{{max-width:980px;margin:0 auto;padding:2rem 1.2rem 4rem}} |
| 48 | + h1{{font-size:1.8rem;margin:.2rem 0}} |
| 49 | + a{{color:#5ad1ff}} |
| 50 | + .tag{{color:#a6a6b3;font-size:1.05rem}} |
| 51 | + .btns{{margin:1.2rem 0 2rem}} |
| 52 | + .btn{{display:inline-block;background:#3b82f6;color:#fff;padding:.55rem 1rem;border-radius:8px;text-decoration:none;margin-right:.6rem;font-weight:600}} |
| 53 | + .btn.alt{{background:#2a2a33;color:#e9e9ee}} |
| 54 | + ul.hi{{line-height:1.7}} |
| 55 | + figure{{margin:0 0 1.6rem}} |
| 56 | + img{{width:100%;border:1px solid #2a2a33;border-radius:10px;display:block}} |
| 57 | + figcaption{{color:#a6a6b3;font-size:.92rem;margin-top:.4rem}} |
| 58 | + .grid{{display:grid;grid-template-columns:1fr 1fr;gap:1.4rem}} |
| 59 | + @media(max-width:720px){{.grid{{grid-template-columns:1fr}}}} |
| 60 | + footer{{color:#6f6f7a;margin-top:2rem;font-size:.9rem}} |
| 61 | +</style></head><body><div class="wrap"> |
| 62 | +<h1>world_model_ros2</h1> |
| 63 | +<p class="tag">Run existing World Models from ROS 2 — runtime, adapters, |
| 64 | +benchmark, visualization. <em>The ROS 2 layer for World Models, not another |
| 65 | +foundation model.</em></p> |
| 66 | +<div class="btns"> |
| 67 | + <a class="btn" href="{REPO_URL}">View on GitHub</a> |
| 68 | + <a class="btn alt" href="bench.html">Live benchmark →</a> |
| 69 | +</div> |
| 70 | +
|
| 71 | +<ul class="hi"> |
| 72 | + <li><b>Two real model backends, one contract</b> — I-JEPA (image) & V-JEPA 2 (video).</li> |
| 73 | + <li><b>Local ↔ remote split</b> over a shared JSON wire (Cosmos/DreamZero-ready).</li> |
| 74 | + <li><b>Compiled Nav2 costmap layer</b> — predicted occupancy into the live costmap.</li> |
| 75 | + <li><b>rosbag2 → LeRobot</b> dataset export, GPU-free.</li> |
| 76 | + <li><b>RViz + Foxglove</b> imagination viewer.</li> |
| 77 | +</ul> |
| 78 | +
|
| 79 | +{hero} |
| 80 | +<div class="grid"> |
| 81 | +{gallery} |
| 82 | +{extras} |
| 83 | +</div> |
| 84 | +
|
| 85 | +<footer>Auto-generated from the repo on every push · all GIFs are real |
| 86 | +pipeline / real model output · <a href="{REPO_URL}">source</a></footer> |
| 87 | +</div></body></html>""" |
| 88 | + |
| 89 | + |
| 90 | +def main(): |
| 91 | + ap = argparse.ArgumentParser() |
| 92 | + ap.add_argument("--repo-root", default=".") |
| 93 | + ap.add_argument("--out", default="_site") |
| 94 | + ap.add_argument("--runs", type=int, default=300) |
| 95 | + args = ap.parse_args() |
| 96 | + |
| 97 | + repo = os.path.abspath(args.repo_root) |
| 98 | + out = os.path.abspath(args.out) |
| 99 | + os.makedirs(out, exist_ok=True) |
| 100 | + |
| 101 | + gifs = [] |
| 102 | + for path in sorted(glob.glob(os.path.join(repo, "docs", "*.gif"))): |
| 103 | + shutil.copy(path, out) |
| 104 | + gifs.append(os.path.basename(path)) |
| 105 | + |
| 106 | + subprocess.run( |
| 107 | + [sys.executable, "-m", "world_model_py.cli", "bench-compare", |
| 108 | + "--adapters", "dummy,remote", "--runs", str(args.runs), |
| 109 | + "--out", os.path.join(out, "bench.html")], |
| 110 | + cwd=os.path.join(repo, "world_model_py"), check=True, |
| 111 | + ) |
| 112 | + |
| 113 | + with open(os.path.join(out, "index.html"), "w", encoding="utf-8") as fh: |
| 114 | + fh.write(build_index(gifs)) |
| 115 | + print(f"built site at {out} ({len(gifs)} gifs + bench.html + index.html)") |
| 116 | + |
| 117 | + |
| 118 | +if __name__ == "__main__": |
| 119 | + main() |
0 commit comments