@ -1,8 +1,11 @@
import os
import json
import math
import datetime
from types import SimpleNamespace
import numpy as np
import cv2
import datetime
class Recorder :
@ -68,46 +71,66 @@ class Recorder:
# -------- EGO POSE --------
transform = vehicle . get_transform ( )
# -------- GROUND TRUTH VEHICLE S --------
# -------- GROUND TRUTH ACTOR S --------
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 :
if actor . id == vehicle . id :
continue # skip ego
# 1. Physical State
t = actor . get_transform ( )
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 = {
" frame_id " : self . frame_id ,
" frame_id " : self . frame_id ,
" timestamp " : cam . timestamp ,
" 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
} ,
" camera " : cam_file ,
" radar " : radar_file ,
" lidar " : lidar_file ,
" ground_truth " : gt_vehicles ,
" radar " : radar_file ,
" lidar " : lidar_file ,
" ground_truth " : gt_actor s ,
}
# Merge scenario metadata (e.g. {"scenario": "braking", "brake_frame": 80})
@ -120,3 +143,72 @@ class Recorder:
# ---------------- CLEANUP ----------------
def close ( self ) :
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 )