Skip to content

Commit 088eb17

Browse files
daphne-cornelisseWael Boumediene DoulazmiWaelDLZ
authored
Fixes: Align WOSAC metric calculation with original (#258)
* quick commit so you can read the code * Improve naming of sampling argument to better describe its function. * Ensure that initialization works with Carla maps or other, when sdc_index=-1. * Simplify CARLA compatibility * Replace num_maps by wosac_num_maps in all the eval scripts * Comment about random baseline. * Bug fix: do not reweight by the total weight (0.95). * Add this back for now. * Delete agent shrinking code. * Ignore ttc metric when agents are not vehicles * Update WOSAC weights to align with 2024 challenge since we don't have the traffic light metric. * Update table * Revert MAX_AGENTS to original. * Update formatting. --------- Co-authored-by: Wael Boumediene Doulazmi <wbd2016@cs713.hpc.nyu.edu> Co-authored-by: Waël Doulazmi <waeldoulazmi@gmail.com>
1 parent 61a1e84 commit 088eb17

11 files changed

Lines changed: 158 additions & 123 deletions

File tree

docs/src/wosac.md

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,30 +55,18 @@ We provide baselines on a small curated dataset from the WOMD validation set wit
5555

5656
| Method | Realism meta-score | Kinematic metrics | Interactive metrics | Map-based metrics | minADE | ADE |
5757
|--------|-------------------|-------------------|---------------------|-------------------|--------|------|
58-
| Ground-truth (UB) | 0.832 | 0.606 | 0.846 | 0.961 | 0 | 0 |
59-
| π_Base self-play RL | 0.737 | 0.319 | 0.789 | 0.938 | 10.834 | 11.317 |
60-
| [SMART-tiny-CLSFT](https://arxiv.org/abs/2412.05334) | 0.805 | 0.534 | 0.830 | 0.949 | 1.124 | 3.123 |
61-
| π_Random | 0.485 | 0.214 | 0.657 | 0.408 | 6.477 | 18.286 |
58+
| Ground-truth (UB) | 0.8179 | 0.6070 | 0.9590 | 0.8722 | 0 | 0 |
59+
| Self-play RL agent | 0.6750 | 0.2798 | 0.7966 | 0.7811 | 10.8057 | 11.4108 |
60+
| [SMART-tiny-CLSFT](https://arxiv.org/abs/2412.05334) | 0.7818 | 0.5200 | 0.8914 | 0.8378 | 1.1236 | 3.1231 |
61+
| Random | 0.4459 | 0.0506 | 0.7843 | 0.4704 | 23.5936 | 25.0097 |
6262

6363
*Table: WOSAC baselines in PufferDrive on 229 selected clean held-out validation scenarios.*
6464

65-
66-
> ✏️ Download the dataset from [Hugging Face](https://huggingface.co/datasets/daphne-cornelisse/pufferdrive_wosac_val_clean) to reproduce these results or benchmark your policy.
67-
68-
69-
| Method | Realism meta-score | Kinematic metrics | Interactive metrics | Map-based metrics | minADE | ADE |
70-
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
71-
| Ground-truth (UB) | 0.833 | 0.574 | 0.864 | 0.958 | 0 | 0 |
72-
| π_Base self-play RL | 0.737 | 0.323 | 0.792 | 0.930 | 8.530 | 9.088 |
73-
| [SMART-tiny-CLSFT](https://arxiv.org/abs/2412.05334) | 0.795 | 0.504 | 0.832 | 0.932 | 1.182 | 2.857 |
74-
| π_Random | 0.497 | 0.238 | 0.656 | 0.430 | 6.395 | 18.617 |
75-
76-
*Table: WOSAC baselines in PufferDrive on validation 10k dataset.*
77-
78-
79-
> ✏️ Download the dataset from [Hugging Face](https://huggingface.co/datasets/daphne-cornelisse/pufferdrive_womd_val) to reproduce these results or benchmark your policy.
65+
- **Random agent:** Following the [WOSAC 2023 paper](https://arxiv.org/abs/2305.12032), the random agent samples future trajectories by independently sampling (x, y, θ) at each timestep from a Gaussian distribution in the AV coordinate frame `(mu=1.0, sigma=0.1)`, producing uncorrelated random motion over the horizon of 80 steps.
66+
- **Goal-conditioned self-play RL agent**: An agent trained through self-play RL to reach the end point points ("goals") without colliding or going off-road. Baseline can be reproduced using the default settings in the `drive.ini` file with the Waymo dataset. We also open-source the weights of this policy, see `pufferlib/resources/drive/puffer_drive_weights` `.bin` and `.pt`.
8067

8168

69+
> ✏️ Download the dataset from [Hugging Face](https://huggingface.co/datasets/daphne-cornelisse/pufferdrive_wosac_val_clean) to reproduce these results or benchmark your policy.
8270
8371
## Evaluating trajectories
8472

pufferlib/ocean/benchmark/evaluate_imported_trajectories.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ def evaluate_trajectories(simulated_trajectory_file, args):
7272
"""
7373
env_name = "puffer_drive"
7474
args["env"]["map_dir"] = args["eval"]["map_dir"]
75-
args["env"]["num_maps"] = args["eval"]["num_maps"]
76-
args["env"]["use_all_maps"] = True
75+
args["env"]["num_maps"] = args["eval"]["wosac_num_maps"]
76+
args["env"]["sequential_map_sampling"] = True
7777
dataset_name = args["env"]["map_dir"].split("/")[-1]
7878

7979
print(f"Running WOSAC realism evaluation with {dataset_name} dataset. \n")

pufferlib/ocean/benchmark/evaluator.py

Lines changed: 93 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ def _compute_metametric(self, metrics: pd.Series) -> float:
4848
weight = self.metrics_config.getfloat(field_name, "metametric_weight")
4949
metric_score = metrics[likelihood_field_name]
5050
metametric += weight * metric_score
51-
52-
weight_sum = sum(self.metrics_config.getfloat(fn, "metametric_weight") for fn in _METRIC_FIELD_NAMES)
53-
return metametric / weight_sum
51+
return metametric
5452

5553
def _get_histogram_params(self, metric_name: str):
5654
return (
@@ -121,6 +119,53 @@ def collect_simulated_trajectories(self, args, puffer_env, policy):
121119

122120
return trajectories
123121

122+
def collect_wosac_random_baseline(self, puffer_env):
123+
"""
124+
Random Baseline from Wosac 2023 paper
125+
"""
126+
driver = puffer_env.driver_env
127+
num_agents = puffer_env.observation_space.shape[0]
128+
129+
trajectories = {
130+
"x": np.zeros((num_agents, self.num_rollouts, self.sim_steps), dtype=np.float32),
131+
"y": np.zeros((num_agents, self.num_rollouts, self.sim_steps), dtype=np.float32),
132+
"heading": np.zeros((num_agents, self.num_rollouts, self.sim_steps), dtype=np.float32),
133+
"id": np.zeros((num_agents, self.num_rollouts, self.sim_steps), dtype=np.int32),
134+
}
135+
136+
for rollout_idx in range(self.num_rollouts):
137+
obs, info = puffer_env.reset()
138+
139+
# Do Initialization
140+
agent_state = driver.get_global_agent_state()
141+
trajectories["x"][:, rollout_idx, 0] = agent_state["x"]
142+
trajectories["y"][:, rollout_idx, 0] = agent_state["y"]
143+
trajectories["heading"][:, rollout_idx, 0] = agent_state["heading"]
144+
trajectories["id"][:, rollout_idx, 0] = agent_state["id"]
145+
146+
# Update using Gaussian:
147+
samples = np.random.normal(loc=1, scale=0.1, size=(num_agents, self.sim_steps, 3))
148+
for time_idx in range(1, self.sim_steps):
149+
dx, dy, d_heading = samples[:, time_idx, 0], samples[:, time_idx, 1], samples[:, time_idx, 2]
150+
x, y, heading = (
151+
trajectories["x"][:, rollout_idx, time_idx - 1],
152+
trajectories["y"][:, rollout_idx, time_idx - 1],
153+
trajectories["heading"][:, rollout_idx, time_idx - 1],
154+
)
155+
156+
cos_h = np.cos(heading)
157+
sin_h = np.sin(heading)
158+
159+
x += dx * cos_h - dy * sin_h
160+
y += dx * sin_h + dy * cos_h
161+
heading += d_heading
162+
163+
trajectories["x"][:, rollout_idx, time_idx] = x
164+
trajectories["y"][:, rollout_idx, time_idx] = y
165+
trajectories["heading"][:, rollout_idx, time_idx] = heading
166+
167+
return trajectories
168+
124169
def compute_metrics(
125170
self,
126171
ground_truth_trajectories: Dict,
@@ -159,6 +204,7 @@ def compute_metrics(
159204
ref_valid = ground_truth_trajectories["valid"]
160205
agent_length = agent_state["length"]
161206
agent_width = agent_state["width"]
207+
is_vehicle = ground_truth_trajectories["is_vehicle"]
162208
scenario_ids = ground_truth_trajectories["scenario_id"]
163209

164210
# We evaluate the metrics only for the Tracks to Predict.
@@ -172,6 +218,7 @@ def compute_metrics(
172218
eval_agent_length = agent_length[eval_mask]
173219
eval_agent_width = agent_width[eval_mask]
174220
eval_scenario_ids = scenario_ids[eval_mask]
221+
eval_is_vehicle = is_vehicle[eval_mask]
175222

176223
# Compute features
177224
# Kinematics-related features
@@ -337,60 +384,48 @@ def compute_metrics(
337384
sanity_check=False,
338385
)
339386

340-
speed_likelihood = np.exp(
341-
metrics._reduce_average_with_validity(
342-
linear_speed_log_likelihood,
343-
speed_validity[:, 0, :],
344-
axis=1,
345-
)
387+
speed_log_likelihood = metrics._reduce_average_with_validity(
388+
linear_speed_log_likelihood,
389+
speed_validity[:, 0, :],
390+
axis=1,
346391
)
347392

348-
accel_likelihood = np.exp(
349-
metrics._reduce_average_with_validity(
350-
linear_accel_log_likelihood,
351-
acceleration_validity[:, 0, :],
352-
axis=1,
353-
)
393+
accel_log_likelihood = metrics._reduce_average_with_validity(
394+
linear_accel_log_likelihood,
395+
acceleration_validity[:, 0, :],
396+
axis=1,
354397
)
355398

356-
angular_speed_likelihood = np.exp(
357-
metrics._reduce_average_with_validity(
358-
angular_speed_log_likelihood,
359-
speed_validity[:, 0, :],
360-
axis=1,
361-
)
399+
angular_speed_log_likelihood = metrics._reduce_average_with_validity(
400+
angular_speed_log_likelihood,
401+
speed_validity[:, 0, :],
402+
axis=1,
362403
)
363404

364-
angular_accel_likelihood = np.exp(
365-
metrics._reduce_average_with_validity(
366-
angular_accel_log_likelihood,
367-
acceleration_validity[:, 0, :],
368-
axis=1,
369-
)
405+
angular_accel_log_likelihood = metrics._reduce_average_with_validity(
406+
angular_accel_log_likelihood,
407+
acceleration_validity[:, 0, :],
408+
axis=1,
370409
)
371410

372-
distance_to_nearest_object_likelihood = np.exp(
373-
metrics._reduce_average_with_validity(
374-
distance_to_nearest_object_log_likelihood,
375-
eval_ref_valid[:, 0, :],
376-
axis=1,
377-
)
411+
distance_to_nearest_object_log_likelihood = metrics._reduce_average_with_validity(
412+
distance_to_nearest_object_log_likelihood,
413+
eval_ref_valid[:, 0, :],
414+
axis=1,
378415
)
379416

380-
time_to_collision_likelihood = np.exp(
381-
metrics._reduce_average_with_validity(
382-
time_to_collision_log_likelihood,
383-
eval_ref_valid[:, 0, :],
384-
axis=1,
385-
)
417+
# TTC is computed only for vehicles
418+
ttc_valid = eval_ref_valid & eval_is_vehicle[..., None]
419+
time_to_collision_log_likelihood = metrics._reduce_average_with_validity(
420+
time_to_collision_log_likelihood,
421+
ttc_valid[:, 0, :],
422+
axis=1,
386423
)
387424

388-
distance_to_road_edge_likelihood = np.exp(
389-
metrics._reduce_average_with_validity(
390-
distance_to_road_edge_log_likelihood,
391-
eval_ref_valid[:, 0, :],
392-
axis=1,
393-
)
425+
distance_to_road_edge_log_likelihood = metrics._reduce_average_with_validity(
426+
distance_to_road_edge_log_likelihood,
427+
eval_ref_valid[:, 0, :],
428+
axis=1,
394429
)
395430

396431
# Collision likelihood is computed by aggregating in time. For invalid objects
@@ -411,7 +446,6 @@ def compute_metrics(
411446
num_bins=2,
412447
use_bernoulli=True,
413448
)
414-
collision_likelihood = np.exp(collision_log_likelihood)
415449

416450
# Offroad likelihood (same pattern as collision)
417451
sim_offroad_indication = np.any(np.where(eval_ref_valid, sim_offroad_per_step, False), axis=2)
@@ -428,7 +462,6 @@ def compute_metrics(
428462
num_bins=2,
429463
use_bernoulli=True,
430464
)
431-
offroad_likelihood = np.exp(offroad_log_likelihood)
432465

433466
# Get agent IDs
434467
eval_agent_ids = ground_truth_trajectories["id"][eval_mask]
@@ -443,15 +476,15 @@ def compute_metrics(
443476
"num_offroad_ref": ref_num_offroad.flatten(),
444477
"ade": ade,
445478
"min_ade": min_ade,
446-
"likelihood_linear_speed": speed_likelihood,
447-
"likelihood_linear_acceleration": accel_likelihood,
448-
"likelihood_angular_speed": angular_speed_likelihood,
449-
"likelihood_angular_acceleration": angular_accel_likelihood,
450-
"likelihood_distance_to_nearest_object": distance_to_nearest_object_likelihood,
451-
"likelihood_time_to_collision": time_to_collision_likelihood,
452-
"likelihood_collision_indication": collision_likelihood,
453-
"likelihood_distance_to_road_edge": distance_to_road_edge_likelihood,
454-
"likelihood_offroad_indication": offroad_likelihood,
479+
"likelihood_linear_speed": speed_log_likelihood,
480+
"likelihood_linear_acceleration": accel_log_likelihood,
481+
"likelihood_angular_speed": angular_speed_log_likelihood,
482+
"likelihood_angular_acceleration": angular_accel_log_likelihood,
483+
"likelihood_distance_to_nearest_object": distance_to_nearest_object_log_likelihood,
484+
"likelihood_time_to_collision": time_to_collision_log_likelihood,
485+
"likelihood_collision_indication": collision_log_likelihood,
486+
"likelihood_distance_to_road_edge": distance_to_road_edge_log_likelihood,
487+
"likelihood_offroad_indication": offroad_log_likelihood,
455488
}
456489
)
457490

@@ -475,6 +508,10 @@ def compute_metrics(
475508
]
476509
].mean()
477510

511+
# Transform log-likelihoods to positive scores:
512+
likelihood_cols = [c for c in scene_level_results.columns if "likelihood" in c]
513+
scene_level_results[likelihood_cols] = np.exp(scene_level_results[likelihood_cols])
514+
478515
scene_level_results["realism_meta_score"] = scene_level_results.apply(self._compute_metametric, axis=1)
479516
scene_level_results["num_agents"] = df.groupby("scenario_id").size()
480517
scene_level_results = scene_level_results[

pufferlib/ocean/benchmark/metrics_sanity_check.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ def replace_rollouts_with_gt(simulated_traj, gt_traj, num_replacements):
2626
return modified
2727

2828

29-
def run_validation_experiment(config, vecenv, policy):
29+
def run_validation_experiment(config, vecenv):
3030
evaluator = WOSACEvaluator(config)
3131

3232
gt_trajectories = evaluator.collect_ground_truth_trajectories(vecenv)
33-
simulated_trajectories = evaluator.collect_simulated_trajectories(config, vecenv, policy)
33+
simulated_trajectories = evaluator.collect_wosac_random_baseline(vecenv)
3434
agent_state = vecenv.driver_env.get_global_agent_state()
3535
road_edge_polylines = vecenv.driver_env.get_road_edge_polylines()
3636

@@ -90,19 +90,17 @@ def main():
9090
config["eval"]["enabled"] = True
9191
config["eval"]["wosac_num_rollouts"] = 32
9292
config["env"]["map_dir"] = config["eval"]["map_dir"]
93-
config["env"]["num_maps"] = config["eval"]["num_maps"]
94-
config["env"]["use_all_maps"] = True
93+
config["env"]["num_maps"] = config["eval"]["wosac_num_maps"]
94+
config["env"]["sequential_map_sampling"] = True
9595

96-
config["env"]["num_agents"] = config["eval"]["wosac_num_agents"]
9796
config["env"]["init_mode"] = config["eval"]["wosac_init_mode"]
9897
config["env"]["control_mode"] = config["eval"]["wosac_control_mode"]
9998
config["env"]["init_steps"] = config["eval"]["wosac_init_steps"]
10099
config["env"]["goal_behavior"] = config["eval"]["wosac_goal_behavior"]
101100

102101
vecenv = load_env(args.env, config)
103-
policy = load_policy(config, vecenv, args.env)
104102

105-
results = run_validation_experiment(config, vecenv, policy)
103+
results = run_validation_experiment(config, vecenv)
106104
print("\n" + format_results_table(results))
107105

108106

pufferlib/ocean/benchmark/visual_sanity_check.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ def main():
104104
config["env"]["goal_behavior"] = config["eval"]["wosac_goal_behavior"]
105105

106106
config["env"]["map_dir"] = config["eval"]["map_dir"]
107-
config["env"]["num_maps"] = config["eval"]["num_maps"]
108-
config["env"]["use_all_maps"] = True
107+
config["env"]["num_maps"] = config["eval"]["wosac_num_maps"]
108+
config["env"]["sequential_map_sampling"] = True
109109

110110
vecenv = load_env(args.env, config)
111111
policy = load_policy(config, vecenv, args.env)

pufferlib/ocean/benchmark/wosac.ini

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
# Taken from WOSAC 2025 Sim Agents Challenge configuration
2-
# Link: https://github.com/waymo-research/waymo-open-dataset/blob/master/src/waymo_open_dataset/wdl_limited/sim_agents_metrics/challenge_2025_sim_agents_config.textproto
1+
# Taken from WOSAC 2024 Sim Agents Challenge configuration
2+
# Note: 2024 challenge does not include traffic_light_violation metric (weight = 0.0)
3+
# Traffic light violation was added in 2025 challenge with weight 0.05
34

45
[linear_speed]
56
histogram.min_val = 0.0
@@ -51,7 +52,7 @@ histogram.max_val = 40
5152
histogram.num_bins = 10
5253
histogram.additive_smoothing_pseudocount = 0.1
5354
independent_timesteps = true
54-
metametric_weight = 0.05
55+
metametric_weight = 0.1
5556

5657
[offroad_indication]
5758
bernoulli = true
@@ -67,4 +68,4 @@ metametric_weight = 0.1
6768

6869
[traffic_light_violation]
6970
bernoulli = true
70-
metametric_weight = 0.05
71+
metametric_weight = 0.0

pufferlib/ocean/drive/binding.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,21 @@ static PyObject *my_shared(PyObject *self, PyObject *args, PyObject *kwargs) {
7676
int init_steps = unpack(kwargs, "init_steps");
7777
int goal_behavior = unpack(kwargs, "goal_behavior");
7878
float goal_target_distance = unpack(kwargs, "goal_target_distance");
79-
int use_all_maps = unpack(kwargs, "use_all_maps");
79+
int sequential_map_sampling = unpack(kwargs, "sequential_map_sampling");
8080
clock_gettime(CLOCK_REALTIME, &ts);
8181
srand(ts.tv_nsec);
8282
int total_agent_count = 0;
8383
int env_count = 0;
84-
int max_envs = use_all_maps ? num_maps : num_agents;
84+
int max_envs = sequential_map_sampling ? num_maps : num_agents;
8585
int map_idx = 0;
8686
int maps_checked = 0;
8787
PyObject *agent_offsets = PyList_New(max_envs + 1);
8888
PyObject *map_ids = PyList_New(max_envs);
8989
// getting env count
90-
while (use_all_maps ? map_idx < max_envs : total_agent_count < num_agents && env_count < max_envs) {
90+
while (sequential_map_sampling ? map_idx < max_envs : total_agent_count < num_agents && env_count < max_envs) {
9191
char map_file[512];
92-
int map_id = use_all_maps ? map_idx++ : rand() % num_maps;
92+
// Take the next map in sequence or a random map
93+
int map_id = sequential_map_sampling ? map_idx++ : rand() % num_maps;
9394
Drive *env = calloc(1, sizeof(Drive));
9495
env->init_mode = init_mode;
9596
env->control_mode = control_mode;
@@ -102,7 +103,7 @@ static PyObject *my_shared(PyObject *self, PyObject *args, PyObject *kwargs) {
102103

103104
// Skip map if it doesn't contain any controllable agents
104105
if (env->active_agent_count == 0) {
105-
if (!use_all_maps) {
106+
if (!sequential_map_sampling) {
106107
maps_checked++;
107108

108109
// Safeguard: if we've checked all available maps and found no active agents, raise an error
@@ -154,7 +155,7 @@ static PyObject *my_shared(PyObject *self, PyObject *args, PyObject *kwargs) {
154155
}
155156
// printf("Generated %d environments to cover %d agents (requested %d agents)\n", env_count, total_agent_count,
156157
// num_agents);
157-
if (!use_all_maps && total_agent_count >= num_agents) {
158+
if (!sequential_map_sampling && total_agent_count >= num_agents) {
158159
total_agent_count = num_agents;
159160
}
160161
PyObject *final_total_agent_count = PyLong_FromLong(total_agent_count);

0 commit comments

Comments
 (0)