Download code/scripts/make_demo.py from changh95/bevformer-p150: direct link, hf CLI and curl.
- Browser
- Download file 22.6 kB
-
https://huggingface.co/changh95/bevformer-p150/resolve/main/code/scripts/make_demo.py
- Command line
-
hf download hf://changh95/bevformer-p150/code/scripts/make_demo.py
-
curl -L -o make_demo.py https://huggingface.co/changh95/bevformer-p150/resolve/main/code/scripts/make_demo.py
22.6 kB
| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """Render the model card's demo media (``media/``) from Tenstorrent p150 outputs. CPU only; tt-models workspace only | |
| (it reads the public datasets and reuses the drawing primitives of the research renderer | |
| ``research/bevformer/public_data/scripts/render_media.py``: 6-camera mosaics with projected 3D boxes, BEV plots, the | |
| dataviz reference palette's dark steps, renders <= 960 px wide, nothing cropped onto people or plates). | |
| # 1. TT outputs (device): the public sequences through the research runner (results/<tag>/<sequence>.json) and the | |
| # shipped sample through the API (examples/quickstart.py writes quickstart.json): | |
| bin/devrun -t 1800 -- python code/scripts/run_public_tt.py --tag tt_release pandaset:ps019_f30-40 pandaset:ps019_2hz | |
| bin/devrun -t 900 -- python examples/quickstart.py --out-dir <dir> | |
| # (nuScenes mini_val: tests/test_e2e_device.py::test_nuscenes_mini_val writes results/tt_e2e) | |
| # 2. renders (research venv: OpenCV, PIL): | |
| tools/research-venv/bin/python code/scripts/make_demo.py --sample-tt <dir>/quickstart.json --out media | |
| What it draws (every file is listed with its source, licence and changes in ``media/ATTRIBUTION.md``, written too): | |
| - PandaSet 019 frame 40 (CC BY 4.0 + PandaSet Dataset Terms): 6-camera mosaic + BEV of the TT boxes (history | |
| f30 -> f35 -> f40, 0.5 s steps); the TT-vs-CPU agreement (BEV of frame 40 side by side + the matched scores of | |
| the 16 frames of the 2 Hz sequence); an animation of the 2 Hz sequence (front camera + BEV); | |
| - nuScenes v1.0-mini scene-0103 / scene-0916 key-frame 20 (CC BY-NC-SA 4.0: labelled non-commercial in the image and | |
| in ATTRIBUTION.md) and the scene-0103 key-frame animation; | |
| - the shipped synthetic sample (generated by ``code/scripts/make_sample.py``, Apache-2.0): its six cameras with the TT | |
| boxes, and the BEV of the TT boxes next to the stored CPU reference. | |
| Boxes drawn: every detection the Autoware node publishes (score > 0.2), in the physical convention (length along the | |
| heading). Grey dashed outlines: dataset annotations (the synthetic sample: where its objects were placed). | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import importlib.util | |
| import json | |
| import math | |
| import os | |
| import sys | |
| from pathlib import Path | |
| from typing import Any, Dict, List, Tuple | |
| import numpy as np | |
| ROOT = Path(os.environ.get("TT_MODELS_ROOT", "/home/ubuntu/experiments/tt-models")) | |
| PUB = ROOT / "research" / "bevformer" / "public_data" | |
| CODE = Path(__file__).resolve().parents[1] | |
| BUNDLE = CODE.parent | |
| sys.path.insert(0, str(CODE)) | |
| THR = 0.2 | |
| TT_SUB = ("Tenstorrent Blackhole p150 output (tt-nn; ETH dispatch, 12x10 grid; fp32 weights, bf16 activations). " | |
| "Boxes: the Autoware node's detections (score > 0.2).") | |
| NC_NOTE = "nuScenes render: CC BY-NC-SA 4.0, non-commercial use only. (c) Motional AD Inc.; not endorsed." | |
| def load_renderer(): | |
| """research/bevformer/public_data/scripts/render_media.py as a module (trusted workspace code, not data).""" | |
| path = PUB / "scripts" / "render_media.py" | |
| spec = importlib.util.spec_from_file_location("bevformer_render_media", path) | |
| mod = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(mod) | |
| return mod | |
| R = load_renderer() | |
| P = R.P | |
| def results(dataset: str, seq: str, tag: str) -> Dict[str, Any]: | |
| return {f["frame_id"]: f for f in P.read_json(str(PUB / dataset / "results" / tag / f"{seq}.json"))["frames"]} | |
| def match(a: List[Dict], b: List[Dict], dist: float = 0.5) -> List[Tuple[Dict, Dict]]: | |
| """Greedy same-class matching within ``dist`` m (BEV centres), b in descending score order.""" | |
| used, out = set(), [] | |
| for rb in sorted(b, key=lambda d: -d["score"]): | |
| best, bd = None, dist | |
| for i, ta in enumerate(a): | |
| if i in used or ta["cls"] != rb["cls"]: | |
| continue | |
| dd = math.hypot(ta["x"] - rb["x"], ta["y"] - rb["y"]) | |
| if dd <= bd: | |
| best, bd = i, dd | |
| if best is not None: | |
| used.add(best) | |
| out.append((a[best], rb)) | |
| return out | |
| # ------------------------------------------------------------------------------------------------ public frames | |
| def card(dataset, seq, fid, tag, name, title, rows, attribution, out_dir: Path, cpu_tag="cpu"): | |
| z, fr, dets, gts, cams, pts = R.frame_bundle(dataset, seq, fid, tag=tag) | |
| bev = R.draw_bev(pts, dets, gts) | |
| cpu = [R.det_phys(d) for d in results(dataset, seq, cpu_tag)[fid]["dets_autoware"] if d["score"] > THR] | |
| m = match(dets, cpu) | |
| n_gt50 = sum(1 for g in gts if math.hypot(g["x"], g["y"]) < 50) | |
| mg = P.match_dets_to_gt([dict(d) for d in dets], [g for g in gts if math.hypot(g["x"], g["y"]) < 50], dist_thr=2.0) | |
| counts: Dict[str, int] = {} | |
| for d in dets: | |
| counts[R.CLS_GROUP[d["cls"]]] = counts.get(R.CLS_GROUP[d["cls"]], 0) + 1 | |
| err = max((abs(a["score"] - b["score"]) for a, b in m), default=0.0) | |
| stats = [f"TT detections (score > 0.2): {len(dets)}", | |
| " " + ", ".join(f"{k} {v}" for k, v in counts.items()), | |
| f"fp32 CPU reference: {len(cpu)}; matched {len(m)}", | |
| f" (same class, < 0.5 m; max |score diff| {err:.3f})", | |
| f"annotations within 50 m: {n_gt50}; matched {len(mg)} (< 2 m)", | |
| f"history: {'on' if fr['use_prev_bev'] else 'off (first frame)'}"] | |
| img = R.compose(cams, bev, title, TT_SUB, list(rows), stats, attribution) | |
| out = out_dir / name | |
| img.save(out, quality=88, optimize=True) | |
| print("wrote", out, img.size, out.stat().st_size, "B", flush=True) | |
| return {"file": name, "dataset": dataset, "sequence": seq, "frame": fid, "tt": len(dets), "cpu": len(cpu), | |
| "matched": len(m), "max_score_diff": round(err, 4)} | |
| def gif(dataset, seq, tag, name, out_dir: Path, size=300, cam_w=534, step=1): | |
| """Animated front camera + BEV of a sequence (TT outputs), drawn natively at GIF size.""" | |
| import cv2 | |
| from PIL import Image, ImageDraw | |
| man = P.read_json(str(PUB / dataset / "manifests" / f"{seq}.json")) | |
| ddir = PUB / dataset | |
| res = results(dataset, seq, tag) | |
| gt = {f["frame_id"]: f for f in P.read_json(str(ddir / "gt" / f"{seq}.json"))["frames"]} | |
| frames = [] | |
| for fr in man["frames"][::step]: | |
| fid = fr["frame_id"] | |
| if fid not in res: | |
| break | |
| z = np.load(ddir / "inputs" / seq / f"{fid}.npz") | |
| dets = [R.det_phys(d) for d in res[fid]["dets_autoware"] if d["score"] > THR] | |
| gts = [g for g in gt[fid]["boxes"] if g["cls"] is not None and abs(g["x"]) < 51.2 and abs(g["y"]) < 51.2 | |
| and (dataset != "nuscenes" or g["num_lidar_pts"] + g["num_radar_pts"] > 0)] | |
| i = P.CAMS.index("CAM_FRONT") | |
| cam = cv2.resize(P.to_node_size(P.decode_jpeg(z["jpeg_CAM_FRONT"])), (cam_w, size), interpolation=cv2.INTER_AREA) | |
| R.draw_boxes_on_camera(cam, dets, z["T_lidar_cam"][i], z["K"][i], cam_w / P.NET_W) | |
| bev = R.draw_bev(R.lidar_points(dataset, z), dets, gts, S=size) | |
| body = np.concatenate([cam, bev], 1)[:, :, ::-1] | |
| im = Image.new("RGB", (body.shape[1], body.shape[0] + 16), R.SURF) | |
| im.paste(Image.fromarray(body), (0, 0)) | |
| dr = ImageDraw.Draw(im) | |
| short = ("nuScenes (c) Motional AD Inc., CC BY-NC-SA 4.0, NON-COMMERCIAL; Motional does not endorse this work" | |
| if dataset == "nuscenes" else | |
| "Contains data from PandaSet (Scale AI and Hesai), CC BY 4.0 + PandaSet Dataset Terms; not endorsed") | |
| dr.text((6, body.shape[0] + 2), short, font=R.font(10), fill=R.INK2) | |
| dr.rectangle([0, 0, im.width, 17], fill=(0, 0, 0)) | |
| dr.text((6, 2), f"{seq} {fid} Tenstorrent p150 output detections > 0.2: {len(dets)}", font=R.font(11), | |
| fill=R.INK) | |
| frames.append(im) | |
| pal = frames[len(frames) // 2].quantize(colors=256, method=Image.Quantize.MEDIANCUT) | |
| q = [f.quantize(palette=pal, dither=Image.Dither.NONE) for f in frames] | |
| out = out_dir / name | |
| q[0].save(out, save_all=True, append_images=q[1:], duration=500, loop=0, optimize=True) | |
| print("wrote", out, len(q), "frames", out.stat().st_size, "B", flush=True) | |
| return {"file": name, "dataset": dataset, "sequence": seq, "frames": len(q)} | |
| def tt_vs_cpu(dataset, seq, fid, tag, name, out_dir: Path, all_seq: str, all_tag: str): | |
| """BEV of the CPU reference and of the TT output side by side (frame ``fid`` of ``seq``), plus a scatter of the | |
| matched boxes' TT vs CPU scores over every frame of ``all_seq``. No camera pixels.""" | |
| from PIL import Image, ImageDraw | |
| S, sz = 300, 250 | |
| z, fr, dets, gts, cams, pts = R.frame_bundle(dataset, seq, fid, tag=tag) | |
| cpu = [R.det_phys(d) for d in results(dataset, seq, "cpu")[fid]["dets_autoware"] if d["score"] > THR] | |
| left, right = R.draw_bev(pts, cpu, gts, S=S), R.draw_bev(pts, dets, gts, S=S) | |
| m = match(dets, cpu) | |
| pairs, n_cpu, n_tt, n_m = [], 0, 0, 0 | |
| tt_all, cpu_all = results(dataset, all_seq, all_tag), results(dataset, all_seq, "cpu") | |
| for k in cpu_all: | |
| if k not in tt_all: | |
| continue | |
| # the published boxes (score > 0.2) of both sides | |
| a = [R.det_phys(d) for d in tt_all[k]["dets_autoware"]] | |
| b = [R.det_phys(d) for d in cpu_all[k]["dets_autoware"]] | |
| mm = match(a, b) | |
| n_cpu += sum(1 for d in b if d["score"] > THR) | |
| n_tt += sum(1 for d in a if d["score"] > THR) | |
| n_m += sum(1 for x, y in mm if y["score"] > THR) | |
| pairs += [(y["score"], x["score"]) for x, y in mm] | |
| W_, H_ = 2 * S + 360, 56 + max(S, sz + 80) + 40 | |
| im = Image.new("RGB", (W_, H_), R.SURF) | |
| d = ImageDraw.Draw(im) | |
| d.text((12, 8), f"TT vs the fp32 CPU reference: PandaSet 019 frame {fid[1:]} (BEV) and the {len(tt_all)} frames " | |
| f"of the 2 Hz sequence", font=R.font(15, True), fill=R.INK) | |
| d.text((12, 32), f"left: CPU reference ({len(cpu)} boxes) middle: Tenstorrent p150 ({len(dets)} boxes, " | |
| f"{len(m)} matched: same class, < 0.5 m) right: scores", font=R.font(12), fill=R.INK2) | |
| im.paste(Image.fromarray(left[:, :, ::-1]), (0, 56)) | |
| im.paste(Image.fromarray(right[:, :, ::-1]), (S + 8, 56)) | |
| # scatter | |
| x0, y0 = 2 * S + 60, 76 | |
| d.rectangle([x0, y0, x0 + sz, y0 + sz], outline=R.GRID, fill=(0x20, 0x20, 0x1f)) | |
| for t in (0.2, 0.4, 0.6, 0.8): | |
| p = int(t * sz) | |
| d.line([(x0 + p, y0), (x0 + p, y0 + sz)], fill=R.GRID) | |
| d.line([(x0, y0 + sz - p), (x0 + sz, y0 + sz - p)], fill=R.GRID) | |
| d.line([(x0, y0 + sz), (x0 + sz, y0)], fill=R.MUTED) | |
| for c_, t_ in pairs: | |
| u, v = x0 + c_ * sz, y0 + sz - t_ * sz | |
| d.ellipse([u - 2, v - 2, u + 2, v + 2], fill=(0x39, 0x87, 0xe5)) | |
| d.text((x0, y0 + sz + 6), "CPU score -> (0 .. 1, grid 0.2)", font=R.font(11), fill=R.INK2) | |
| d.text((x0 - 40, y0 - 18), "TT score", font=R.font(11), fill=R.INK2) | |
| errs = [abs(a - b) for a, b in pairs] | |
| d.text((x0, y0 + sz + 22), f"{len(pairs)} matched boxes (published by both)", font=R.font(11), fill=R.INK2) | |
| d.text((x0, y0 + sz + 38), f"mean diff {np.mean([t - c for c, t in pairs]):+.4f}, max |diff| {max(errs):.3f}", | |
| font=R.font(11), fill=R.INK2) | |
| d.text((12, H_ - 30), "Contains data from PandaSet (Scale AI and Hesai), CC BY 4.0 + PandaSet Dataset Terms; LiDAR " | |
| "points (grey) for context only; not endorsed by Scale AI or Hesai.", font=R.font(10), | |
| fill=R.MUTED) | |
| d.text((12, H_ - 16), f"over all frames: CPU {n_cpu} boxes > 0.2, TT {n_tt}, matched {n_m} " | |
| f"(recall {n_m / max(n_cpu, 1):.3f}, precision {n_m / max(n_tt, 1):.3f})", | |
| font=R.font(10), fill=R.MUTED) | |
| out = out_dir / name | |
| im.save(out, optimize=True) | |
| print("wrote", out, im.size, out.stat().st_size, "B", flush=True) | |
| return {"file": name, "pairs": len(pairs), "recall": n_m / max(n_cpu, 1), "precision": n_m / max(n_tt, 1), | |
| "max_abs": max(errs), "mean": float(np.mean([t - c for c, t in pairs]))} | |
| # ------------------------------------------------------------------------------------------- the synthetic sample | |
| def _body_dets(body: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| names, rot = body["meta"]["class_names"], body["meta"]["rot"] | |
| out = [] | |
| for i, d in enumerate(body["detections"]): | |
| x, y, z = d["center"] | |
| w, l, h = d["size"] | |
| out.append(R.det_phys({"x": x, "y": y, "z": z, "w": w, "l": l, "h": h, "r": rot[i], "score": d["score"], | |
| "cls": names[d["label_id"]]})) | |
| return out | |
| def synthetic(tt_json: Path, name: str, out_dir: Path): | |
| import cv2 | |
| from PIL import Image, ImageDraw | |
| from tt_bevformer.host.calib import T_BASE_FROM_VIRTUAL_LIDAR | |
| from tt_bevformer.host.inputs import resolve_calibration | |
| samples = CODE / "tt_bevformer" / "samples" | |
| man = json.loads((samples / "synthetic_6cam.json").read_text()) | |
| cal = resolve_calibration(man["calibration"]) | |
| tt = _body_dets(json.loads(Path(tt_json).read_text())) | |
| ref = _body_dets(json.loads((samples / "synthetic_6cam.reference.json").read_text())) | |
| inv = np.linalg.inv(T_BASE_FROM_VIRTUAL_LIDAR) | |
| gts = [] | |
| for t in man["targets"]: | |
| p = inv @ np.array(t["center"] + [1.0]) | |
| l, w, h = t["size"] | |
| gts.append({"x": p[0], "y": p[1], "z": p[2], "l": l, "w": w, "h": h, "yaw": t["yaw"] + math.pi / 2, | |
| "cls": t["label"]}) | |
| cams = {} | |
| for c in P.CAMS: | |
| im = np.asarray(Image.open(samples / man["images"][c]).convert("RGB"))[:, :, ::-1].copy() | |
| im = cv2.resize(im, (R.CAM_W, R.CAM_H), interpolation=cv2.INTER_AREA) | |
| T = inv @ np.asarray(cal["cameras"][c]["T_ref_from_camera"], np.float64) | |
| R.draw_boxes_on_camera(im, tt, T, np.asarray(cal["cameras"][c]["intrinsics"], np.float64), R.CAM_W / 1600.0) | |
| cams[c] = im | |
| S = 300 | |
| b_tt, b_ref = R.draw_bev(None, tt, gts, S=S), R.draw_bev(None, ref, gts, S=S) | |
| m = match(tt, ref) | |
| err = max((abs(a["score"] - b["score"]) for a, b in m), default=0.0) | |
| head = 58 | |
| W_, H_ = R.W, head + 2 * R.CAM_H + S + 40 | |
| canvas = Image.new("RGB", (W_, H_), R.SURF) | |
| d = ImageDraw.Draw(canvas) | |
| d.text((12, 8), "The shipped sample: a synthetic six-camera test pattern (generated; no real scene)", | |
| font=R.font(16, True), fill=R.INK) | |
| d.text((12, 33), "Rendered road views; the pixels around six placed objects (front and back cameras) were optimised " | |
| "against the fp32 CPU reference. Boxes: TT output.", font=R.font(12), fill=R.INK2) | |
| for r_, row in enumerate(R.LAYOUT): | |
| for c_, cam in enumerate(row): | |
| x0, y0 = c_ * R.CAM_W, head + r_ * R.CAM_H | |
| canvas.paste(Image.fromarray(cams[cam][:, :, ::-1]), (x0, y0)) | |
| lab = cam.replace("CAM_", "") | |
| tw = d.textlength(lab, font=R.font(11, True)) | |
| d.rectangle([x0 + 4, y0 + 4, x0 + 10 + tw, y0 + 20], fill=(0, 0, 0)) | |
| d.text((x0 + 7, y0 + 5), lab, font=R.font(11, True), fill=R.INK) | |
| yb = head + 2 * R.CAM_H | |
| canvas.paste(Image.fromarray(b_tt[:, :, ::-1]), (0, yb)) | |
| canvas.paste(Image.fromarray(b_ref[:, :, ::-1]), (S + 10, yb)) | |
| d.text((6, yb + 4), "TT (p150)", font=R.font(11, True), fill=R.INK) | |
| d.text((S + 16, yb + 4), "CPU reference (stored)", font=R.font(11, True), fill=R.INK) | |
| x = 2 * S + 28 | |
| y = yb + 8 | |
| lines = [f"TT boxes: {len(tt)}; CPU reference: {len(ref)}", | |
| f"matched {len(m)} (same class, < 0.5 m)", f"max |score diff| {err:.4f}", ""] | |
| for a, b in sorted(m, key=lambda p: -p[1]["score"]): | |
| lines.append(f"{b['cls']:<13s} CPU {b['score']:.3f} TT {a['score']:.3f}") | |
| lines += ["", "grey dashed: where the objects were placed", "(Apache-2.0, code/scripts/make_sample.py)"] | |
| for ln in lines: | |
| d.text((x, y), ln, font=R.font(12), fill=R.INK2) | |
| y += 18 | |
| out = out_dir / name | |
| canvas.save(out, optimize=True) | |
| print("wrote", out, canvas.size, out.stat().st_size, "B", flush=True) | |
| return {"file": name, "tt": len(tt), "ref": len(ref), "matched": len(m), "max_score_diff": round(err, 4)} | |
| # ------------------------------------------------------------------------------------------------ attribution | |
| ATTRIBUTION = """# Media attribution (bevformer-p150) | |
| Every image here shows outputs of BEVFormer-small (the official `bevformer_small_epoch_24` checkpoint through the | |
| graph of Autoware's TensorRT export, weights `changh95/bevformer-small-weights` @ `0e6d9d9c7b8`) computed by this | |
| port on one Tenstorrent Blackhole p150 (ETH dispatch, 12x10 grid, 1 command queue; fp32 weights, bf16 activations), | |
| and, where a panel says so, by the port's fp32 CPU reference (`code/tt_bevformer/reference`). Rendered by | |
| `code/scripts/make_demo.py` from the TT runs of `code/scripts/run_public_tt.py` (the research workflow's runner on the | |
| public-data inputs of the tt-models workspace, `research/bevformer/public_data`) and from `examples/quickstart.py` on | |
| the shipped sample. Boxes: the detections the Autoware node publishes (score > 0.2), drawn with the length along the | |
| heading. Colours: vehicles blue, pedestrians orange, bicycles / motorcycles aqua, traffic cones / barriers grey (dataviz | |
| reference palette, dark steps); grey dashed outlines are the dataset annotations (the synthetic sample: where its | |
| objects were placed); grey dots are LiDAR points, context only (BEVFormer is camera-only). Renders are at most 960 px | |
| wide and do not crop or zoom onto people or plates. | |
| | file | source | scene / frame | licence (label next to the image) | changes | | |
| |---|---|---|---|---| | |
| {rows} | |
| **PandaSet:** Contains data from PandaSet (Scale AI and Hesai), https://pandaset.org, licensed under CC BY 4.0 and the PandaSet Dataset Terms. Changes: camera images downscaled; predicted 3D boxes, annotation outlines and LiDAR points drawn on top; BEV plots. Scale AI and Hesai do not endorse this work. Cite: P. Xiao et al., PandaSet: Advanced Sensor Suite Dataset for Autonomous Driving, ITSC 2021. The PandaSet Dataset Terms forbid using the data to identify people; the renders are small and do not crop or zoom onto faces or plates. | |
| **nuScenes:** Rendered from the nuScenes dataset, (c) Motional AD Inc., CC BY-NC-SA 4.0 and the nuScenes Terms of Use (https://www.nuscenes.org/terms-of-use). Non-commercial use only; adaptations under the same license. Motional does not endorse this work. Cite: H. Caesar et al., nuScenes: A Multimodal Dataset for Autonomous Driving, CVPR 2020. The rendered images are adaptations and are shared under CC BY-NC-SA 4.0 (non-commercial use only); their file names end in `_NC`. | |
| **Synthetic sample:** generated by `code/scripts/make_sample.py` of this repository (rendered road views and an | |
| optimised test pattern; no third-party data), Apache-2.0. | |
| """ | |
| PS_SRC = "PandaSet (HF mirror georghess/pandaset@e2e123a)" | |
| NS_SRC = "nuScenes v1.0-mini" | |
| PS_LIC = "CC BY 4.0 + PandaSet Dataset Terms" | |
| NS_LIC = "**CC BY-NC-SA 4.0 + nuScenes Terms of Use: non-commercial**" | |
| MOSAIC = "images downscaled to 320x180 tiles; TT boxes, annotation outlines, LiDAR points and a BEV plot drawn" | |
| def main() -> int: | |
| ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) | |
| ap.add_argument("--out", type=Path, default=BUNDLE / "media") | |
| ap.add_argument("--ps-tag", default="tt_release", help="results tag of the PandaSet TT run") | |
| ap.add_argument("--ns-tag", default="tt_e2e", help="results tag of the nuScenes TT run") | |
| ap.add_argument("--sample-tt", type=Path, help="the TT /predict body of the shipped sample") | |
| a = ap.parse_args() | |
| a.out.mkdir(parents=True, exist_ok=True) | |
| rows, index = [], [] | |
| ps_attr, ns_attr = R.PS_ATTR, R.NS_ATTR | |
| index.append(card("pandaset", "ps019_f30-40", "f40", a.ps_tag, "bevformer_pandaset_019_f40.jpg", | |
| "BEVFormer-small on a Tenstorrent p150: PandaSet 019 frame 40 (San Francisco)", | |
| ["Another rig than the training data (nuScenes):", "52 deg front camera, 92 deg side cameras,", | |
| "mounted 0.3-0.5 m higher. History: frames 30 -> 35 -> 40."], ps_attr, a.out)) | |
| rows.append(f"| `bevformer_pandaset_019_f40.jpg` | {PS_SRC} | sequence 019 (San Francisco), frame 40 (history 30, 35) | {PS_LIC} | {MOSAIC} |") | |
| index.append(tt_vs_cpu("pandaset", "ps019_f30-40", "f40", a.ps_tag, "bevformer_pandaset_019_tt_vs_cpu.png", a.out, | |
| "ps019_2hz", a.ps_tag)) | |
| rows.append(f"| `bevformer_pandaset_019_tt_vs_cpu.png` | {PS_SRC} | sequence 019, frame 40 (BEV only, no camera pixels) and the 16 frames of the 2 Hz sequence (scores) | {PS_LIC} | BEV plots of TT and CPU outputs, annotations, LiDAR points; a score scatter |") | |
| index.append(gif("pandaset", "ps019_2hz", a.ps_tag, "bevformer_pandaset_019_2hz.gif", a.out)) | |
| rows.append(f"| `bevformer_pandaset_019_2hz.gif` | {PS_SRC} | sequence 019, frames 0-75 at 2 Hz (16 frames) | {PS_LIC} | front camera downscaled to 534x300 + BEV, TT boxes, 256 colours |") | |
| for sc, city in (("scene-0103", "Boston"), ("scene-0916", "Singapore")): | |
| nm = f"bevformer_nuscenes_{sc}_kf20_NC.jpg" | |
| index.append(card("nuscenes", sc, "kf20", a.ns_tag, nm, | |
| f"BEVFormer-small on a Tenstorrent p150: nuScenes {sc} key-frame 20 ({city})", | |
| ["In-domain (trained on nuScenes train; mini_val", "is held out). History: key-frames 0-19.", | |
| NC_NOTE[:47], NC_NOTE[47:]], ns_attr, a.out)) | |
| rows.append(f"| `{nm}` | {NS_SRC} | {sc} (mini_val, {city}), key-frame 20 | {NS_LIC} | {MOSAIC} |") | |
| index.append(gif("nuscenes", "scene-0103", a.ns_tag, "bevformer_nuscenes_scene-0103_NC.gif", a.out, step=2)) | |
| rows.append(f"| `bevformer_nuscenes_scene-0103_NC.gif` | {NS_SRC} | scene-0103, key-frames 0-38 (every second, 20 frames) | {NS_LIC} | front camera downscaled to 534x300 + BEV, TT boxes, 256 colours |") | |
| if a.sample_tt: | |
| index.append(synthetic(a.sample_tt, "bevformer_synthetic_6cam.png", a.out)) | |
| rows.append("| `bevformer_synthetic_6cam.png` | generated (`code/scripts/make_sample.py`) | the shipped sample | Apache-2.0 | TT boxes on the six cameras; BEV of TT and of the stored CPU reference |") | |
| (a.out / "ATTRIBUTION.md").write_text(ATTRIBUTION.replace("{rows}", "\n".join(rows))) | |
| (a.out / "README.md").unlink(missing_ok=True) | |
| print(json.dumps(index, indent=1, default=float)) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |