whassan's picture
Deploy Sensorflow Studio
93efd9d verified
Raw History Blame Contribute Delete
5.66 kB
"""Deterministic synthetic multi-sensor scenes with known 3D ground truth.
Mirrors the conventions of the existing evaluation generator
(sensorflow/evaluation/synthetic.py: ego drives +x at 10 m/s, objects have
world-frame kinematics, class dimensions from CLASS_DIMS) but is purpose-built
for the sensor-simulation study: every ground-truth box carries a per-frame
camera-occlusion flag (planted contiguous occlusion windows) so that both
sensor simulators and the cohort evaluation can reason about occlusion.
Conditions cycle deterministically across sequences (day/clear, night/clear,
day/rain) so every cohort is well represented at any n_sequences >= 3.
"""
from __future__ import annotations
import math
from typing import List
import numpy as np
from pydantic import BaseModel, Field
# Reuse the platform's class dimension priors rather than duplicating them.
from sensorflow.evaluation.synthetic import CLASS_DIMS, CLASS_POOL
CLASSES = list(CLASS_DIMS.keys())
SAFETY_CLASSES = {"pedestrian", "cyclist", "motorcycle"}
DT = 0.1
EGO_SPEED = 10.0
class GTBox(BaseModel):
instance_id: str
class_name: str
bbox_3d: List[float] # [x, y, z, l, w, h, yaw] ego frame
occluded: bool = False # camera line-of-sight blocked this frame
distance: float = 0.0
class SceneFrame(BaseModel):
frame_id: str
index: int
gt: List[GTBox] = Field(default_factory=list)
class SceneSequence(BaseModel):
sequence_id: str
time_of_day: str = "day" # day | night
weather: str = "clear" # clear | rain
frames: List[SceneFrame] = Field(default_factory=list)
def generate_sequences(n_sequences: int = 6, frames_per_sequence: int = 24,
seed: int = 7) -> List[SceneSequence]:
"""Generate deterministic sequences with planted camera-occlusion windows."""
sequences: List[SceneSequence] = []
for qi in range(n_sequences):
rng = np.random.default_rng(seed * 100003 + qi * 7919)
time_of_day = "night" if qi % 3 == 1 else "day"
weather = "rain" if qi % 3 == 2 else "clear"
seq = SceneSequence(sequence_id=f"bev-seq-{seed}-{qi}",
time_of_day=time_of_day, weather=weather)
num_objects = int(rng.integers(7, 11))
objects = []
for oi in range(num_objects):
cls = CLASS_POOL[int(rng.integers(0, len(CLASS_POOL)))]
l, w, h = CLASS_DIMS[cls]
# Rejection-sample spawn positions so objects start >= 5 m apart
# (keeps BEV peaks separable; documented decode limitation).
for _ in range(30):
x0 = float(rng.uniform(10, 68))
y0 = float(rng.uniform(-13, 13))
if all(math.hypot(x0 - o["x"], y0 - o["y"]) >= 5.0 for o in objects):
break
heading = (float(rng.uniform(-math.pi, math.pi)) if cls == "pedestrian"
else float(rng.choice([0.0, math.pi, math.pi / 2, -math.pi / 2])))
speed = {"pedestrian": 1.4, "cyclist": 4.5, "motorcycle": 9.0,
"vehicle": 8.0, "truck": 7.0}[cls] * float(rng.uniform(0.6, 1.2))
objects.append({
"iid": f"{seq.sequence_id}-obj-{oi}", "cls": cls, "dims": (l, w, h),
"x": x0, "y": y0,
"vx": speed * math.cos(heading), "vy": speed * math.sin(heading),
"yaw": heading,
})
# Plant camera-occlusion windows: 2 objects per sequence lose camera
# line of sight for a contiguous 2-3 frame window mid-sequence.
occlusion = {}
if objects and frames_per_sequence >= 10:
occluded_idx = rng.choice(len(objects), size=min(2, len(objects)), replace=False)
for k, oi in enumerate(occluded_idx):
start = int(rng.integers(4, max(5, frames_per_sequence - 6)))
length = int(rng.integers(2, 4))
occlusion[objects[int(oi)]["iid"]] = (start, start + length)
for fi in range(frames_per_sequence):
ego_x = EGO_SPEED * DT * fi
frame = SceneFrame(frame_id=f"{seq.sequence_id}-f{fi:04d}", index=fi)
for obj in objects:
wx = obj["x"] + obj["vx"] * DT * fi
wy = obj["y"] + obj["vy"] * DT * fi
rel_x, rel_y = wx - ego_x, wy
if rel_x < 3.0 or rel_x > 78.0 or abs(rel_y) > 22.0:
continue
l, w, h = obj["dims"]
win = occlusion.get(obj["iid"])
frame.gt.append(GTBox(
instance_id=obj["iid"],
class_name=obj["cls"],
bbox_3d=[round(rel_x, 3), round(rel_y, 3), round(h / 2, 3),
l, w, h, round(obj["yaw"], 4)],
occluded=bool(win and win[0] <= fi < win[1]),
distance=round(math.hypot(rel_x, rel_y), 3),
))
seq.frames.append(frame)
sequences.append(seq)
return sequences
def gt_tracks(sequences: List[SceneSequence]) -> List[dict]:
"""Ground-truth tracks in the shape expected by sensorflow.metrics.temporal_mot."""
by_instance: dict = {}
for seq in sequences:
for frame in seq.frames:
for gt in frame.gt:
entry = by_instance.setdefault(
gt.instance_id,
{"instance_id": gt.instance_id, "track_id": gt.instance_id,
"class_name": gt.class_name, "frames": []})
entry["frames"].append({"frame_id": frame.frame_id, "bbox_3d": gt.bbox_3d})
return list(by_instance.values())