@ -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,30 +71,52 @@ class Recorder:
# -------- EGO POSE --------
# -------- EGO POSE --------
transform = vehicle . get_transform ( )
transform = vehicle . get_transform ( )
# -------- GROUND TRUTH VEHICLE S --------
# -------- GROUND TRUTH ACTOR S --------
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_actors = [ ]
gt_vehicles = [ ]
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 ( )
a = actor . get_acceleration ( )
speed = ( v . x * * 2 + v . y * * 2 + v . z * * 2 ) * * 0.5
# 2. Geometry
bb = actor . bounding_box
extent = bb . extent # half-size
gt_vehicles . append ( {
# 3. Relative Metrics (ADAS)
rel_metrics = self . _calculate_relative_metrics ( vehicle , actor )
gt_actors . append ( {
" id " : actor . id ,
" 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
" 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 --------
@ -99,15 +124,13 @@ class Recorder:
" 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 ,
" radar " : radar_file ,
" lidar " : lidar_file ,
" lidar " : lidar_file ,
" ground_truth " : gt_vehicle s ,
" ground_truth " : gt_actor s ,
}
}
# 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 )