Spaces:
Running on Zero
Running on Zero
File size: 7,073 Bytes
af8bc9f | 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 | # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Raw-map polylines and translucent actor boxes for PAI quickstart previews.
Map vertices and actors are in the clip reference frame; sensor poses bring
both into the same ego frame as the generated cloud. This is display-only.
"""
import json
import tarfile
import cv2
import numpy as np
from utils.visualize.point_cloud import CAMERA_VIEWS, VIZ_KWARGS
MAP_KINDS = ("lanes", "lanelines", "road_boundaries", "crosswalks", "wait_lines",
"road_markings", "poles", "traffic_signs", "traffic_lights")
MAP_COLORS = ((168, 96, 224), (232, 86, 232), (226, 42, 214), (46, 204, 113),
(255, 214, 44), (255, 214, 44), (150, 92, 224), (131, 175, 155), (252, 157, 155))
BOX_COLORS = ((74, 144, 240), (52, 106, 204), (46, 204, 113), (255, 196, 44), (176, 184, 196))
EDGES = ((0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7))
def object_class(name):
aliases = {"Bus": "Truck", "Heavy_truck": "Truck", "Train_or_tram_car": "Truck",
"Trolley_bus": "Truck", "Trailer": "Truck", "Vehicle": "Car",
"Automobile": "Car", "Other_vehicle": "Car", "Person": "Pedestrian", "Rider": "Cyclist"}
return {"Car": 0, "Truck": 1, "Pedestrian": 2, "Cyclist": 3}.get(aliases.get(name, name), 4)
def _vertices(node):
if isinstance(node, dict):
for key, value in node.items():
if key == "vertices":
yield np.asarray(value, dtype=np.float32)
else:
yield from _vertices(value)
elif isinstance(node, list):
for item in node:
yield from _vertices(item)
def load_vectors(root, clip):
points, offsets, kinds = [], [0], []
for kind_id, kind in enumerate(MAP_KINDS):
with tarfile.open(root / f"3d_{kind}" / f"{clip}.tar") as archive:
members = [m for m in archive.getmembers() if m.isfile() and m.name.endswith(".json")]
if len(members) != 1:
raise ValueError(f"{clip}/{kind}: expected one vector-map JSON")
with archive.extractfile(members[0]) as handle:
data = json.load(handle)
for vertices in _vertices(data):
if vertices.ndim != 2 or vertices.shape[1] != 3 or not np.isfinite(vertices).all():
raise ValueError(f"{clip}/{kind}: invalid vertices")
if len(vertices) < 2:
continue
if kind in ("traffic_signs", "traffic_lights") and len(vertices) == 8:
vertices = vertices[[0, 1, 2, 3, 0, 4, 5, 6, 7, 4, 5, 1, 2, 6, 7, 3]]
elif kind in ("crosswalks", "road_markings"):
vertices = np.concatenate((vertices, vertices[:1]))
points.append(vertices)
offsets.append(offsets[-1] + len(vertices))
kinds.append(kind_id)
return dict(map_vertices=np.concatenate(points) if points else np.empty((0, 3), np.float32),
map_offsets=np.asarray(offsets, np.int64), map_kinds=np.asarray(kinds, np.int64))
def render_hdmap(sample, frame):
"""Return a white-background BGR frame matching the point-cloud camera."""
for key in ("map_vertices", "map_offsets", "map_kinds", "box_class"):
if key not in sample:
raise ValueError(f"Missing {key}; rerun scripts/data/prepare_pai_bidir.py for raw-map previews")
width, height = VIZ_KWARGS["width"], VIZ_KWARGS["height"]
camera = CAMERA_VIEWS["paper_view_down"]
center = np.array([camera["center"][a] for a in "xyz"]) * VIZ_KWARGS["range"]
eye = np.array([camera["eye"][a] for a in "xyz"]) * VIZ_KWARGS["range"]
eye = center + .6 * (eye - center)
forward = center - eye
forward /= np.linalg.norm(forward)
right = np.cross(forward, [0, 0, 1])
right /= np.linalg.norm(right)
up = np.cross(right, forward)
transform = np.linalg.inv(sample["pose_sensor_start_end"][frame, 0])
tangent = np.tan(np.deg2rad(VIZ_KWARGS.get("fov_deg", 60)) / 2)
def project(points, world=True):
local = points @ transform[:3, :3].T + transform[:3, 3] if world else points
relative = local - eye
depth = relative @ forward
x = (relative @ right / (np.maximum(depth, 1e-3) * tangent * width / height) * .5 + .5) * width
y = (1 - (relative @ up / (np.maximum(depth, 1e-3) * tangent) * .5 + .5 + .08)) * height
return np.clip(np.stack((x, y), -1), -100000, 100000).astype(np.int32), depth, local
image = np.full((height, width, 3), 255, np.uint8)
map_lines = []
box_lines = []
for start, end, kind in zip(sample["map_offsets"][:-1], sample["map_offsets"][1:], sample["map_kinds"]):
pixels, depth, local = project(sample["map_vertices"][start:end])
for i in range(len(pixels) - 1):
if min(depth[i:i+2]) > .1 and np.linalg.norm(local[i:i+2], axis=1).min() < 100:
map_lines.append((pixels[i], pixels[i+1], MAP_COLORS[kind][::-1]))
def box(size, pose, color, world=True):
corners = np.array([[1, 1, 1], [1, -1, 1], [-1, -1, 1], [-1, 1, 1],
[1, 1, -1], [1, -1, -1], [-1, -1, -1], [-1, 1, -1]]) * size / 2
pixels, depth, local = project(corners @ pose[:3, :3].T + pose[:3, 3], world)
if depth.min() <= .1 or np.linalg.norm(local.mean(0)) > 100:
return
overlay = image.copy()
hull = cv2.convexHull(pixels)
cv2.fillConvexPoly(overlay, hull, color, cv2.LINE_AA)
cv2.addWeighted(overlay, .25, image, .75, 0, dst=image)
box_lines.extend((pixels[a], pixels[b], color) for a, b in EDGES)
active = np.flatnonzero(sample["box_valid"][frame])
# Paint distant boxes first. Outlines are drawn after all translucent fills.
origin = sample["pose_sensor_start_end"][frame, 0, :3, 3]
active = sorted(active, key=lambda i: -np.linalg.norm(sample["box_object_to_world"][frame, i, :3, 3] - origin))
for i in active:
box(sample["box_lwh"][frame, i], sample["box_object_to_world"][frame, i],
BOX_COLORS[int(sample["box_class"][frame, i])][::-1])
ego = np.eye(4)
ego[2, 3] = -1.1
box(np.array([4.5, 2., 1.6]), ego, (40, 122, 240), world=False)
for a, b, color in map_lines:
cv2.line(image, tuple(a), tuple(b), color, 1, cv2.LINE_AA)
# Confine the soft halo to box edges; map polylines stay crisp and thin.
halo = np.zeros_like(image, dtype=np.float32)
coverage = np.zeros((height, width), dtype=np.float32)
for a, b, color in box_lines:
cv2.line(halo, tuple(a), tuple(b), color, 3)
cv2.line(coverage, tuple(a), tuple(b), 1., 3)
halo = cv2.GaussianBlur(halo, (5, 5), .8)
coverage = cv2.GaussianBlur(coverage, (5, 5), .8)[..., None]
image = np.clip(image * (1 - .12 * coverage) + .12 * halo, 0, 255).astype(np.uint8)
for a, b, color in box_lines:
cv2.line(image, tuple(a), tuple(b), color, 1, cv2.LINE_AA)
return image
|