Download code/scripts/make_demo.py from changh95/bevdet-p150: direct link, hf CLI and curl.
- Browser
- Download file 25.5 kB
-
https://huggingface.co/changh95/bevdet-p150/resolve/main/code/scripts/make_demo.py
- Command line
-
hf download hf://changh95/bevdet-p150/code/scripts/make_demo.py
-
curl -L -o make_demo.py https://huggingface.co/changh95/bevdet-p150/resolve/main/code/scripts/make_demo.py
25.5 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 the research renderer of ``research/bevdet/public_data/scripts/render_media.py``, | |
| whose drawing primitives it reuses: 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 of the demo frames (device; writes <results>/<dataset>/<manifest>.json): | |
| bin/devrun -t 1800 -- python code/scripts/run_public_tt.py <manifests> --out-dir <results> | |
| # 2. renders (research venv: OpenCV, PIL, PyYAML): | |
| tools/research-venv/bin/python code/scripts/make_demo.py --results <results> --sample-tt <synthetic tt 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, ego-pose mode | |
| (``port2hz``); the temporal modes (no history / Autoware node / ego-pose mode, TT, next to the CPU reference); the | |
| Autoware K quirk (scaled vs raw K, TT); the TT-vs-CPU agreement (BEV side by side + matched scores); | |
| - 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 GIF; | |
| - 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: score >= 0.4 on the public frames (the Autoware node publishes >= 0.2; the denser set is unreadable at | |
| this size), >= 0.2 on the synthetic sample. | |
| """ | |
| 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, Optional, Sequence, Tuple | |
| import numpy as np | |
| ROOT = Path(os.environ.get("TT_MODELS_ROOT", "/home/ubuntu/experiments/tt-models")) | |
| PUB = ROOT / "research" / "bevdet" / "public_data" | |
| CODE = Path(__file__).resolve().parents[1] | |
| BUNDLE = CODE.parent | |
| THR = 0.4 | |
| TITLE = "BEVDet (Autoware tensorrt_bevdet) on one Tenstorrent Blackhole p150" | |
| MODE_DOC = { | |
| "port2hz": "ego-pose mode (stream.T_world_from_ego): 8 key-frames of history at 2 Hz, aligned with the ego motion", | |
| "autoware": "Autoware node semantics: camera rate (10 Hz), history never aligned (identity ego pose)", | |
| "single": "one frame, no history (a reset frame: every history slot = the current map)", | |
| } | |
| def load_renderer(): | |
| """research/bevdet/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("bevdet_render_media", path) | |
| mod = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(mod) | |
| return mod | |
| R = None # the renderer module (set in main) | |
| def results(path: Path) -> Dict[str, Any]: | |
| return json.loads(path.read_text()) | |
| def frame_of(res: Dict[str, Any], name: str) -> Dict[str, Any]: | |
| return next(f for f in res["frames"] if f["name"] == name) | |
| def tt_footer(extra: str = "") -> Tuple[str, int, Tuple[int, int, int], bool]: | |
| return (f"TT = output of the port on one p150 (ETH dispatch, 12x10 grid, 1 CQ; fp32 weights, bf16 activations).{extra}", | |
| 11, R.INK2, False) | |
| # ----------------------------------------------------------------------------------------------- PandaSet | |
| def pandaset_mosaic(res_dir: Path, out: Path) -> str: | |
| res = results(res_dir / "PandaSet" / "port2hz__ps019_2hz.json") if (res_dir / "PandaSet").is_dir() else \ | |
| results(res_dir / "pandaset" / "port2hz__ps019_2hz.json") | |
| fr = frame_of(res, "f40") | |
| cams, pts, gt = R.ps_frame_data("019", 40, fr["ego2global"]) | |
| imgs = {c: R.P.load_bgr(cams[c]["path"]) for c in R.P.CAMS} | |
| boxes = fr["boxes"] | |
| n = len([b for b in boxes if b["score"] >= THR]) | |
| header = [(f"{TITLE}: PandaSet sequence 019, frame 40 (San Francisco)", 16, R.INK, True), | |
| (f"Tenstorrent p150 output, {MODE_DOC['port2hz']}. Cross-rig input: 1920x1080 cameras resized to " | |
| "1600x900 like the node, K scaled.", 12, R.INK2, False)] | |
| info = [("What is drawn", 14, R.INK, True), | |
| (f"TT detections with score >= {THR:g} ({n} of {len(boxes)} decoded; the node publishes >= 0.2). The " | |
| "weights were trained on nuScenes only: this rig (camera heights 1.8-2.1 m, 52 deg front / 92 deg side " | |
| "lenses) is out of domain, so ranges are biased (front ~15 % short).", 12, R.INK2, False)] | |
| footer = [(R.P.PS_ATTRIBUTION.format(changes="images downscaled to 320x180, 3D boxes, annotation outlines, LiDAR " | |
| "points and a BEV plot drawn on top"), 10, R.MUTED, False)] | |
| R.compose(cams, imgs, boxes, pts, gt, header, info, footer, THR).save(out, quality=88) | |
| return out.name | |
| def pandaset_dir(res_dir: Path) -> Path: | |
| for name in ("PandaSet", "pandaset"): | |
| if (res_dir / name).is_dir(): | |
| return res_dir / name | |
| raise FileNotFoundError(f"no PandaSet results under {res_dir}") | |
| def bev_with_caption(title: str, sub: str, boxes, pts, gt, size=380, rng=51.2): | |
| cap = R.text_strip(size, [(title, 12, R.INK, True), (sub, 11, R.INK2, False)]) | |
| return R.stack([cap, R.bev_panel(size, rng, pts, gt, boxes)], size) | |
| def grid(panels, cols: int, gap: int = 8): | |
| from PIL import Image | |
| w = max(p.width for p in panels) | |
| h = max(p.height for p in panels) | |
| rows = math.ceil(len(panels) / cols) | |
| out = Image.new("RGB", (cols * w + (cols - 1) * gap, rows * h + (rows - 1) * gap), R.PLANE) | |
| for i, p in enumerate(panels): | |
| out.paste(p, ((i % cols) * (w + gap), (i // cols) * (h + gap))) | |
| return out | |
| def pandaset_modes(res_dir: Path, out: Path) -> str: | |
| psd = pandaset_dir(res_dir) | |
| sets = [("single__ps019_f40", "f40", "TT: no history (reset frame)"), | |
| ("autoware__ps019_f30-40", "f40", "TT: Autoware node, 10 Hz, no ego motion"), | |
| ("port2hz__ps019_2hz", "f40", "TT: ego-pose mode, 2 Hz history"), | |
| (None, "f40", "CPU fp32 reference: ego-pose mode")] | |
| panels = [] | |
| for man, name, title in sets: | |
| res = results(psd / f"{man}.json") if man else results(PUB / "pandaset" / "results" / "port2hz__ps019_2hz.json") | |
| fr = frame_of(res, name) | |
| cams, pts, gt = R.ps_frame_data("019", 40, fr["ego2global"]) | |
| shown = [b for b in fr["boxes"] if b["score"] >= THR] | |
| panels.append(bev_with_caption(title, f"{len(shown)} boxes with score >= {THR:g}", shown, pts, gt)) | |
| body = grid(panels, 2) | |
| hdr = R.text_strip(body.width, [(f"Temporal modes on PandaSet 019 frame 40 (ego at 11 m/s), {TITLE}", 13, R.INK, | |
| True), | |
| ("Grey outlines: annotations. Blue vehicles, orange pedestrians, aqua two-wheelers, " | |
| "grey dots cones / barriers.", 11, R.INK2, False)]) | |
| ftr = R.text_strip(body.width, [tt_footer(" The ego pose is what makes the history useful at speed."), | |
| (R.P.PS_ATTRIBUTION.format(changes="BEV plots of model outputs, annotations and " | |
| "LiDAR points"), 10, R.MUTED, False)]) | |
| R.stack([hdr, body, ftr], body.width).save(out, optimize=True) | |
| return out.name | |
| def pandaset_kquirk(res_dir: Path, out: Path) -> str: | |
| psd = pandaset_dir(res_dir) | |
| panels = [] | |
| for man, title in (("single__ps019_f40", "K scaled to 1600x900 (the port's default)"), | |
| ("single_quirk__ps019_f40", "raw 1920x1080 K (the node's quirk)")): | |
| fr = results(psd / f"{man}.json")["frames"][0] | |
| cams, pts, gt = R.ps_frame_data("019", 40, fr["ego2global"]) | |
| shown = [b for b in fr["boxes"] if b["score"] >= THR] | |
| n02 = sum(b["score"] >= 0.2 for b in fr["boxes"]) | |
| panels.append(bev_with_caption(f"TT: {title}", f"{len(shown)} boxes with score >= {THR:g} ({n02} >= 0.2)", | |
| shown, pts, gt, size=400)) | |
| body = grid(panels, 2, gap=16) | |
| hdr = R.text_strip(body.width, [("The Autoware K quirk on a 1920x1080 rig: PandaSet 019 frame 40, one frame, " | |
| "Tenstorrent p150 output", 13, R.INK, True)]) | |
| ftr = R.text_strip(body.width, [tt_footer(" The node resizes the image to 1600x900 but keeps the camera's K; " | |
| "the port scales K by default (calibration.scale_intrinsics=false " | |
| "reproduces the node)."), | |
| (R.P.PS_ATTRIBUTION.format(changes="BEV plots of model outputs and annotations"), | |
| 10, R.MUTED, False)]) | |
| R.stack([hdr, body, ftr], body.width).save(out, optimize=True) | |
| return out.name | |
| def match_pairs(a: List[Dict[str, Any]], b: List[Dict[str, Any]], max_dist: float = 0.5): | |
| """Greedy same-label BEV-centre matching (the e2e gates' rule): a (descending score) -> nearest unused b.""" | |
| used = [False] * len(b) | |
| pairs = [] | |
| for i in sorted(range(len(a)), key=lambda k: -a[k]["score"]): | |
| best, bd = -1, max_dist | |
| for j, o in enumerate(b): | |
| if used[j] or o["label"] != a[i]["label"]: | |
| continue | |
| d = math.hypot(o["x"] - a[i]["x"], o["y"] - a[i]["y"]) | |
| if d <= bd: | |
| best, bd = j, d | |
| if best >= 0: | |
| used[best] = True | |
| pairs.append((i, best)) | |
| return pairs | |
| def score_scatter(pairs_scores: Sequence[Tuple[float, float]], size: int, caption: str): | |
| """TT score vs CPU score of matched boxes: one series (blue), the y = x line, thresholds 0.2 dashed.""" | |
| from PIL import Image, ImageDraw | |
| im = Image.new("RGB", (size, size), R.SURF) | |
| d = ImageDraw.Draw(im) | |
| pad_l, pad_b, pad_t, pad_r = 44, 40, 34, 14 | |
| x0, y0, x1, y1 = pad_l, pad_t, size - pad_r, size - pad_b | |
| lo, hi = 0.1, 1.0 | |
| def px(v, w): | |
| return (x0 + (v - lo) / (hi - lo) * (x1 - x0), y1 - (w - lo) / (hi - lo) * (y1 - y0)) | |
| f = R.font(11) | |
| for t in (0.2, 0.4, 0.6, 0.8, 1.0): | |
| gx, _ = px(t, lo) | |
| _, gy = px(lo, t) | |
| d.line([(gx, y0), (gx, y1)], fill=R.GRID, width=1) | |
| d.line([(x0, gy), (x1, gy)], fill=R.GRID, width=1) | |
| d.text((gx - 9, y1 + 5), f"{t:.1f}", font=f, fill=R.MUTED) | |
| d.text((8, gy - 7), f"{t:.1f}", font=f, fill=R.MUTED) | |
| d.line([px(lo, lo), px(hi, hi)], fill=R.AXIS, width=2) | |
| for a, b in pairs_scores: | |
| cx, cy = px(b, a) | |
| d.ellipse([cx - 3, cy - 3, cx + 3, cy + 3], fill=R.SERIES["vehicle"], outline=R.SURF) | |
| d.text((x0, 8), caption, font=R.font(12, True), fill=R.INK) | |
| d.text((x1 - 120, y1 + 22), "CPU fp32 score", font=f, fill=R.INK2) | |
| d.text((x0 + 4, y0 + 4), "TT score", font=f, fill=R.INK2) | |
| return im | |
| def pandaset_tt_vs_cpu(res_dir: Path, out: Path) -> Dict[str, Any]: | |
| """BEV of the TT boxes and of the CPU reference (ego-pose mode, frame 40), and TT vs CPU scores of the matched | |
| boxes (every frame of the 2 Hz run).""" | |
| psd = pandaset_dir(res_dir) | |
| tt = results(psd / "port2hz__ps019_2hz.json") | |
| cpu = results(PUB / "pandaset" / "results" / "port2hz__ps019_2hz.json") | |
| panels = [] | |
| for res, title in ((tt, "TT (Tenstorrent p150)"), (cpu, "CPU fp32 reference")): | |
| fr = frame_of(res, "f40") | |
| cams, pts, gt = R.ps_frame_data("019", 40, fr["ego2global"]) | |
| shown = [b for b in fr["boxes"] if b["score"] >= 0.2] | |
| panels.append(bev_with_caption(title, f"{len(shown)} boxes with score >= 0.2 (what the node publishes)", | |
| shown, pts, gt, size=300)) | |
| pairs, n_ref, n_hit = [], 0, 0 | |
| cpu_frames = {f["name"]: f for f in cpu["frames"]} | |
| for f in tt["frames"]: | |
| c = cpu_frames.get(f["name"]) | |
| if not c or "boxes" not in c: | |
| continue | |
| ref_pub = [b for b in c["boxes"] if b["score"] >= 0.2] | |
| n_ref += len(ref_pub) | |
| m = match_pairs(ref_pub, f["boxes"]) | |
| n_hit += len(m) | |
| pairs += [(f["boxes"][j]["score"], ref_pub[i]["score"]) for i, j in m] | |
| diffs = sorted(abs(a - b) for a, b in pairs) | |
| err = diffs[-1] if diffs else 0.0 | |
| p99 = float(np.percentile(diffs, 99)) if diffs else 0.0 | |
| scatter = score_scatter(pairs, 300, f"Matched boxes, all {len(tt['frames'])} frames") | |
| body = grid(panels + [scatter], 3, gap=8) | |
| hdr = R.text_strip(body.width, [("TT vs the CPU fp32 reference: PandaSet 019, ego-pose mode at 2 Hz", 13, R.INK, | |
| True), | |
| (f"{n_hit} of {n_ref} CPU boxes with score >= 0.2 have a same-class TT box within " | |
| f"0.5 m (recall {n_hit / max(n_ref, 1):.3f}); |score difference| of the matched " | |
| f"pairs: p99 {p99:.4f}, max {err:.3f}. Left / centre: frame 40.", 11, R.INK2, | |
| False)]) | |
| ftr = R.text_strip(body.width, [tt_footer(), (R.P.PS_ATTRIBUTION.format( | |
| changes="BEV plots of model outputs, annotations and LiDAR points; a score scatter"), 10, R.MUTED, False)]) | |
| R.stack([hdr, body, ftr], body.width).save(out, optimize=True) | |
| return {"file": out.name, "matched": n_hit, "reference": n_ref, "max_score_err": err, "p99_score_err": p99} | |
| # ----------------------------------------------------------------------------------------------- nuScenes | |
| def nuscenes_dir(res_dir: Path) -> Path: | |
| for name in ("nuScenes", "nuscenes"): | |
| if (res_dir / name).is_dir(): | |
| return res_dir / name | |
| raise FileNotFoundError(f"no nuScenes results under {res_dir}") | |
| def nuscenes_mosaic(res_dir: Path, scene: str, kf: int, out: Path) -> str: | |
| res = results(nuscenes_dir(res_dir) / f"port2hz__{scene}.json") | |
| fr = [f for f in res["frames"] if f.get("key_frame", True)][kf] | |
| rec, cams, pts, gt = R.ns_frame_data(scene, kf) | |
| imgs = {c: R.P.load_bgr(cams[c]["path"]) for c in R.P.CAMS} | |
| boxes = fr["boxes"] | |
| n = len([b for b in boxes if b["score"] >= THR]) | |
| place = {"scene-0103": "Boston Seaport", "scene-0916": "Singapore Queenstown"}[scene] | |
| header = [(f"{TITLE}: nuScenes {scene} ({place}, mini_val), key-frame {kf}", 16, R.INK, True), | |
| (f"Tenstorrent p150 output, {MODE_DOC['port2hz']}. In-domain data (the weights were trained on " | |
| "nuScenes). CC BY-NC-SA 4.0: non-commercial.", 12, R.INK2, False)] | |
| info = [("What is drawn", 14, R.INK, True), | |
| (f"TT detections with score >= {THR:g} ({n} of {len(boxes)} decoded; the Autoware node publishes " | |
| ">= 0.2). Boxes are in the ego frame at the CAM_FRONT stamp, projected into each camera with its own " | |
| "pose.", 12, R.INK2, False)] | |
| footer = [(R.P.NS_ATTRIBUTION + ". Changes: images downscaled to 320x180, 3D boxes, annotation outlines, LiDAR " | |
| "points and a BEV plot drawn on top. Label: CC BY-NC-SA 4.0, non-commercial.", 10, R.MUTED, False)] | |
| R.compose(cams, imgs, boxes, pts, gt, header, info, footer, THR).save(out, quality=88) | |
| return out.name | |
| def nuscenes_gif(res_dir: Path, scene: str, out: Path) -> str: | |
| from PIL import Image | |
| res = results(nuscenes_dir(res_dir) / f"port2hz__{scene}.json") | |
| frames = [f for f in res["frames"] if f.get("key_frame", True)] | |
| ims = [] | |
| for kf, fr in enumerate(frames): | |
| rec, cams, pts, gt = R.ns_frame_data(scene, kf) | |
| shown = [b for b in fr["boxes"] if b["score"] >= THR] | |
| img = R.P.load_bgr(cams["CAM_FRONT"]["path"]) | |
| tile = R.camera_tile(img, cams["CAM_FRONT"]["K"], cams["CAM_FRONT"]["T_cam_ego"], shown, 448, 252, "front") | |
| bev = R.bev_panel(252, 51.2, pts, gt, shown) | |
| row = Image.new("RGB", (700, 252), R.PLANE) | |
| row.paste(tile, (0, 0)) | |
| row.paste(bev, (448, 0)) | |
| hdr = R.text_strip(700, [(f"BEVDet on a Tenstorrent p150: nuScenes {scene}, key-frame {kf:02d} (2 Hz), ego-pose " | |
| f"mode, TT score >= {THR:g}. CC BY-NC-SA 4.0 (c) Motional, non-commercial; see " | |
| "media/ATTRIBUTION.md", 10, R.INK2, False)], pad=4) | |
| ims.append(R.stack([hdr, row], 700).quantize(colors=96, method=Image.Quantize.MEDIANCUT, | |
| dither=Image.Dither.NONE)) | |
| ims[0].save(out, save_all=True, append_images=ims[1:], duration=500, loop=0, optimize=True) | |
| return out.name | |
| # ------------------------------------------------------------------------------------------ synthetic sample | |
| def to_vendor(rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]: | |
| """/predict rows (gravity-centre z) -> vendor box dicts (bottom-centre z) for the renderer.""" | |
| out = [] | |
| for d in rows: | |
| (x, y, z), (l, w, h) = d["center"], d["size"] | |
| out.append({"x": x, "y": y, "z": z - h / 2, "l": l, "w": w, "h": h, "r": d["yaw"], "score": d["score"], | |
| "label": d["label_id"]}) | |
| return out | |
| def synthetic(tt_body: Path, out: Path) -> Dict[str, Any]: | |
| from PIL import Image | |
| sample = CODE / "tt_bevdet" / "samples" / "synthetic_6cam.json" | |
| man = json.loads(sample.read_text()) | |
| calib = json.loads((CODE / "tt_bevdet" / "calib" / "synthetic_6cam.json").read_text()) | |
| ref = json.loads((sample.parent / "synthetic_6cam.reference.json").read_text()) | |
| tt = json.loads(tt_body.read_text()) | |
| tt_boxes = [b for b in to_vendor(tt["detections"]) if b["score"] >= 0.2] | |
| ref_boxes = [b for b in to_vendor(ref["detections"]) if b["score"] >= 0.2] | |
| w = 960 | |
| tw, th = w // 3, (w // 3) * 9 // 16 | |
| names = {"CAM_FRONT_LEFT": "front left", "CAM_FRONT": "front", "CAM_FRONT_RIGHT": "front right", | |
| "CAM_BACK_LEFT": "back left", "CAM_BACK": "back", "CAM_BACK_RIGHT": "back right"} | |
| mos = Image.new("RGB", (w, 2 * th), R.PLANE) | |
| for i, c in enumerate(R.P.CAMS): | |
| bgr = np.asarray(Image.open(sample.parent / man["images"][c]).convert("RGB"))[:, :, ::-1] | |
| K = np.asarray(calib["cameras"][c]["intrinsics"], np.float64) | |
| T = np.linalg.inv(np.asarray(calib["cameras"][c]["T_ref_from_camera"], np.float64)) | |
| mos.paste(R.camera_tile(np.ascontiguousarray(bgr), K, T, tt_boxes, tw, th, names[c]), ((i % 3) * tw, | |
| (i // 3) * th)) | |
| targets = [(t["center"][0], t["center"][1], t["size"][0], t["size"][1], t["yaw"]) for t in man["targets"]] | |
| p_tt = bev_with_caption("TT (Tenstorrent p150)", f"{len(tt_boxes)} boxes with score >= 0.2", tt_boxes, None, | |
| targets, size=470, rng=30.0) | |
| p_ref = bev_with_caption("CPU fp32 reference (stored)", f"{len(ref_boxes)} boxes with score >= 0.2", ref_boxes, | |
| None, targets, size=470, rng=30.0) | |
| mid = grid([p_tt, p_ref], 2, gap=20) | |
| pairs = match_pairs(ref_boxes, to_vendor(tt["detections"])) | |
| errs = [abs(ref_boxes[i]["score"] - to_vendor(tt["detections"])[j]["score"]) for i, j in pairs] | |
| hdr = R.text_strip(w, [("The shipped sample: a synthetic six-camera test pattern (code/tt_bevdet/samples/" | |
| "synthetic_6cam.json)", 15, R.INK, True), | |
| ("Rendered road views whose network-visible pixels were optimised against the CPU reference " | |
| "so that the network reports eight objects (grey outlines: where they were placed). No real " | |
| f"scene. TT boxes drawn. {len(pairs)} of {len(ref_boxes)} reference boxes matched within " | |
| f"0.5 m, max |score difference| {max(errs, default=0.0):.4f}.", 11, R.INK2, False)]) | |
| ftr = R.text_strip(w, [tt_footer(" Generated data (code/scripts/make_sample.py), Apache-2.0."), | |
| ("Blue vehicles, orange pedestrians, aqua two-wheelers, grey dots cones / barriers.", 10, | |
| R.MUTED, False)]) | |
| R.stack([hdr, mos, mid, ftr], w).save(out, optimize=True) | |
| return {"file": out.name, "matched": len(pairs), "reference": len(ref_boxes), | |
| "max_score_err": max(errs, default=0.0)} | |
| ATTRIBUTION = """# Media attribution (bevdet-p150) | |
| Every image here shows outputs of the BEVDet network Autoware deploys (`bevdet_one_lt_d.onnx`, | |
| AutowareFoundation/tensorrt_bevdet v1.0) 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_bevdet/reference`). Rendered by `code/scripts/make_demo.py` from the TT runs of | |
| `code/scripts/run_public_tt.py` on the public-data manifests of the tt-models workspace (`research/bevdet/public_data`). | |
| Colours: vehicles blue, pedestrians orange, bicycles / motorcycles aqua, traffic cones / barriers grey dots (dataviz | |
| reference palette, dark steps); grey hairline outlines are the dataset annotations (the synthetic sample: where its | |
| objects were placed); LiDAR points are context only (BEVDet 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:** {ps} | |
| **nuScenes:** {ns}. The rendered images are adaptations and are shared under CC BY-NC-SA 4.0 (non-commercial use only). | |
| **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. | |
| """ | |
| def main() -> int: | |
| global R | |
| ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) | |
| ap.add_argument("--results", type=Path, required=True, help="run_public_tt.py --out-dir") | |
| ap.add_argument("--sample-tt", type=Path, required=True, help="TT /predict body of the shipped sample " | |
| "(smoke protocol)") | |
| ap.add_argument("--out", type=Path, default=BUNDLE / "media") | |
| a = ap.parse_args() | |
| R = load_renderer() | |
| a.out.mkdir(parents=True, exist_ok=True) | |
| rows, report = [], {} | |
| ps_lic, ns_lic = "CC BY 4.0 + PandaSet Dataset Terms", "**CC BY-NC-SA 4.0 + nuScenes Terms of Use: non-commercial**" | |
| f = pandaset_mosaic(a.results, a.out / "bevdet_pandaset_019_f40.jpg") | |
| rows.append(f"| `{f}` | PandaSet (HF mirror georghess/pandaset@e2e123a) | sequence 019 (San Francisco), frame 40 | " | |
| f"{ps_lic} | images downscaled to 320x180 tiles; TT boxes, annotation outlines, LiDAR points and a BEV " | |
| "plot drawn |") | |
| f = pandaset_modes(a.results, a.out / "bevdet_pandaset_019_f40_modes.png") | |
| rows.append(f"| `{f}` | PandaSet | sequence 019, frame 40 (BEV only, no camera pixels) | {ps_lic} | BEV plots of " | |
| "TT and CPU outputs, annotations, LiDAR points |") | |
| f = pandaset_kquirk(a.results, a.out / "bevdet_pandaset_019_f40_kquirk.png") | |
| rows.append(f"| `{f}` | PandaSet | sequence 019, frame 40 (BEV only) | {ps_lic} | BEV plots of TT outputs and " | |
| "annotations |") | |
| report["pandaset_tt_vs_cpu"] = r = pandaset_tt_vs_cpu(a.results, a.out / "bevdet_pandaset_019_tt_vs_cpu.png") | |
| rows.append(f"| `{r['file']}` | PandaSet | sequence 019, frames 0-40 at 2 Hz (BEV of frame 40 + scores) | {ps_lic} | " | |
| "BEV plots of TT and CPU outputs, annotations, LiDAR points; a score scatter |") | |
| for scene in ("scene-0103", "scene-0916"): | |
| f = nuscenes_mosaic(a.results, scene, 20, a.out / f"bevdet_nuscenes_{scene}_kf20_NC.jpg") | |
| rows.append(f"| `{f}` | nuScenes v1.0-mini | {scene} (mini_val), key-frame 20 | {ns_lic} | images downscaled " | |
| "to 320x180 tiles; TT boxes, annotation outlines, LiDAR points and a BEV plot drawn |") | |
| f = nuscenes_gif(a.results, "scene-0103", a.out / "bevdet_nuscenes_scene-0103_NC.gif") | |
| rows.append(f"| `{f}` | nuScenes v1.0-mini | scene-0103, all 40 key-frames (2 Hz) | {ns_lic} | front camera " | |
| "downscaled to 448x252 + BEV, TT boxes, 96 colours |") | |
| report["synthetic"] = r = synthetic(a.sample_tt, a.out / "bevdet_synthetic_6cam.png") | |
| rows.append(f"| `{r['file']}` | 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 |") | |
| ps_text = R.P.PS_ATTRIBUTION.format(changes="camera images downscaled; predicted 3D boxes, annotation outlines and " | |
| "LiDAR points drawn on top; BEV plots") | |
| ps_text += (" 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.") | |
| (a.out / "ATTRIBUTION.md").write_text(ATTRIBUTION.format(rows="\n".join(rows), ps=ps_text, ns=R.P.NS_ATTRIBUTION)) | |
| sizes = {p.name: p.stat().st_size for p in sorted(a.out.iterdir()) if p.is_file()} | |
| print(json.dumps({"report": report, "sizes": sizes}, indent=1)) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |