Browse Source

Upgrade Ground Truth Schema (Critical for ADAS use-case)

Expanded object state to include physical (acceleration), geometric (bounding boxes),
and relative ADAS metrics (range, azimuth, closing velocity) for vehicles and pedestrians.
1843_integration
RUSHIL AMBARISH KADU 2 months ago
parent
commit
ff43a9efb6
  1. 32
      intel/context.md
  2. 138
      src/recorder.py

32
intel/context.md

@ -135,17 +135,35 @@ Writes one frame's worth of data to disk each tick.
{ {
"frame_id": 82, "frame_id": 82,
"timestamp": 1234.56, "timestamp": 1234.56,
"scenario": "braking",
"brake_frame": 80,
"braked": true,
"ego_pose": {"x": 12.3, "y": 4.5, "z": 0.0, "yaw": -91.2}, "ego_pose": {"x": 12.3, "y": 4.5, "z": 0.0, "yaw": -91.2},
"camera": "frame_000082.png",
"radar": "frame_000082.npy",
"lidar": "frame_000082.npy",
"ground_truth": [{"id": 42, "x": ..., "speed": 8.3}]
"ground_truth": [
{
"id": 123,
"class": "vehicle",
"type": "vehicle.tesla.model3",
"transform": {"x": 10.5, "y": 2.1, "z": 0.5, "yaw": 90.0, ...},
"velocity": {"vx": 5.0, "vy": 0.0, "vz": 0.0, "speed": 5.0},
"acceleration": {"ax": 0.1, "ay": 0.0, "az": 0.0},
"bounding_box": {"l": 4.5, "w": 2.0, "h": 1.5},
"relative": {
"range": 15.2,
"azimuth": -2.5,
"closing_velocity": 1.2
}
}
],
"scenario": "braking",
"brake_frame": 80
} }
``` ```
**ADAS Relative Metrics**:
- `range`: Euclidean distance (m).
- `azimuth`: Angle in ego-forward frame (degrees).
- `closing_velocity`: Rate of approach (m/s). Positive means getting closer.
**Scope**: Now tracks both `vehicle.*` and `walker.*` (pedestrians).
**`extra_meta` pattern:** `save()` accepts `extra_meta: dict` which is merged into the record. **`extra_meta` pattern:** `save()` accepts `extra_meta: dict` which is merged into the record.
Scenarios use `get_scenario_metadata()` to supply this — no recorder changes needed per scenario. Scenarios use `get_scenario_metadata()` to supply this — no recorder changes needed per scenario.

138
src/recorder.py

@ -1,8 +1,11 @@
import os import os
import json import json
import math
import datetime
from types import SimpleNamespace
import numpy as np import numpy as np
import cv2 import cv2
import datetime
class Recorder: class Recorder:
@ -68,46 +71,66 @@ class Recorder:
# -------- EGO POSE -------- # -------- EGO POSE --------
transform = vehicle.get_transform() transform = vehicle.get_transform()
# -------- GROUND TRUTH VEHICLES --------
# -------- GROUND TRUTH ACTORS --------
world = vehicle.get_world() world = vehicle.get_world()
actors = world.get_actors().filter("vehicle.*")
# Track both vehicles and pedestrians
actors = list(world.get_actors().filter("vehicle.*")) + \
list(world.get_actors().filter("walker.*"))
gt_vehicles = []
gt_actors = []
ego_loc = transform.location
ego_vel = vehicle.get_velocity()
for actor in actors: for actor in actors:
if actor.id == vehicle.id: if actor.id == vehicle.id:
continue # skip ego continue # skip ego
# 1. Physical State
t = actor.get_transform() t = actor.get_transform()
v = actor.get_velocity() v = actor.get_velocity()
speed = (v.x**2 + v.y**2 + v.z**2) ** 0.5
gt_vehicles.append({
"id": actor.id,
"x": t.location.x,
"y": t.location.y,
"z": t.location.z,
"vx": v.x,
"vy": v.y,
"vz": v.z,
"speed": speed
a = actor.get_acceleration()
# 2. Geometry
bb = actor.bounding_box
extent = bb.extent # half-size
# 3. Relative Metrics (ADAS)
rel_metrics = self._calculate_relative_metrics(vehicle, actor)
gt_actors.append({
"id": actor.id,
"class": self._get_actor_class(actor),
"type": actor.type_id,
"transform": {
"x": t.location.x, "y": t.location.y, "z": t.location.z,
"yaw": t.rotation.yaw, "pitch": t.rotation.pitch, "roll": t.rotation.roll
},
"velocity": {
"vx": v.x, "vy": v.y, "vz": v.z,
"speed": math.sqrt(v.x**2 + v.y**2 + v.z**2)
},
"acceleration": {
"ax": a.x, "ay": a.y, "az": a.z
},
"bounding_box": {
"l": extent.x * 2, "w": extent.y * 2, "h": extent.z * 2
},
"relative": rel_metrics
}) })
# -------- RECORD -------- # -------- RECORD --------
record = { record = {
"frame_id": self.frame_id,
"frame_id": self.frame_id,
"timestamp": cam.timestamp, "timestamp": cam.timestamp,
"ego_pose": { "ego_pose": {
"x": transform.location.x,
"y": transform.location.y,
"z": transform.location.z,
"x": transform.location.x, "y": transform.location.y, "z": transform.location.z,
"yaw": transform.rotation.yaw "yaw": transform.rotation.yaw
}, },
"camera": cam_file, "camera": cam_file,
"radar": radar_file,
"lidar": lidar_file,
"ground_truth": gt_vehicles,
"radar": radar_file,
"lidar": lidar_file,
"ground_truth": gt_actors,
} }
# Merge scenario metadata (e.g. {"scenario": "braking", "brake_frame": 80}) # Merge scenario metadata (e.g. {"scenario": "braking", "brake_frame": 80})
@ -120,3 +143,72 @@ class Recorder:
# ---------------- CLEANUP ---------------- # ---------------- CLEANUP ----------------
def close(self): def close(self):
self.meta_f.close() self.meta_f.close()
# ---------------- HELPERS ----------------
def _get_actor_class(self, actor) -> str:
"""Categorise actor into broad ADAS classes."""
type_id = actor.type_id
if "walker" in type_id:
return "pedestrian"
if "vehicle" in type_id:
if "bicycle" in type_id or "motorcycle" in type_id:
return "vru" # Vulnerable Road User
return "vehicle"
return "unknown"
def _calculate_relative_metrics(self, ego, npc) -> dict:
"""
Calculate relative range, azimuth, and closing velocity.
Uses right-handed coordinate projections where necessary.
"""
e_t = ego.get_transform()
n_t = npc.get_transform()
e_v = ego.get_velocity()
n_v = npc.get_velocity()
# 1. Range (Euclidean distance)
rel_pos_v = n_t.location - e_t.location
rng = rel_pos_v.length()
if rng < 0.1: # avoid div by zero
return {"range": rng, "azimuth": 0.0, "closing_velocity": 0.0}
# 2. Azimuth (Local angle in degrees)
# Project relative position into ego's local frame
# CARLA forward is X+, Right is Y+ (left-handed)
forward = e_t.get_forward_vector()
# Simple dot-product for angle (cosine similarity)
# We use atan2 for full 360 bearing
# rel_pos_v in ego local coordinates
rel_pos_local = self._to_local_location(e_t, n_t.location)
azimuth = math.degrees(math.atan2(rel_pos_local.y, rel_pos_local.x))
# 3. Closing Velocity (V_c)
# Projection of relative velocity onto the unit line-of-sight vector
# V_c = -(V_npc - V_ego) . (P_npc - P_ego) / |P_npc - P_ego|
rel_vel = n_v - e_v
unit_los = rel_pos_v / rng
closing_vel = -(rel_vel.x * unit_los.x + rel_vel.y * unit_los.y + rel_vel.z * unit_los.z)
return {
"range": round(rng, 3),
"azimuth": round(azimuth, 3),
"closing_velocity": round(closing_vel, 3)
}
def _to_local_location(self, transform, location):
"""Helper to convert world location to transform-local location."""
# Simple translation and rotation inverse
# CARLA locations also have raw_data but subtraction is easier
rel_loc = location - transform.location
# Inverse rotation (Yaw only for simplicity of horizontal metrics)
yaw_rad = math.radians(transform.rotation.yaw)
c, s = math.cos(yaw_rad), math.sin(yaw_rad)
# Rotate rel_loc vector by -yaw
lx = rel_loc.x * c + rel_loc.y * s
ly = -rel_loc.x * s + rel_loc.y * c
return SimpleNamespace(x=lx, y=ly, z=rel_loc.z)
Loading…
Cancel
Save