Spaces:
Paused
Paused
Download sensorflow/bevfusion/scenes.py from whassan/SensorflowStudio: direct link, hf CLI and curl.
- Browser
- Download file 5.66 kB
-
https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/bevfusion/scenes.py
- Command line
-
hf download hf://spaces/whassan/SensorflowStudio/sensorflow/bevfusion/scenes.py
-
curl -L -o scenes.py https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/bevfusion/scenes.py
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()) | |