File size: 25,479 Bytes
77e3c67 | 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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 | #!/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())
|