Evaluation Metrics and Own Analyses
This page explains what the cpm_eval checks measure and how to read a recorded run in Python for your own
analyses and plots. See Recording and Evaluating Experiments for how to record a run and run cpm_eval.
The report.html of cpm_eval evaluate with the check summary and the plots of a run
Results of a Check
Every check reports one of the following results:
Result |
Meaning |
|---|---|
|
The check was evaluated on enough data and found no problem. |
|
The check found a problem, e.g. a collision. Collision and boundary checks fail as soon as one event is found, even if the coverage is low. |
|
Not enough data could be evaluated, usually because the coverage is below 70 % ( |
|
Required input is missing, e.g. the recorded map. |
|
The check does not apply, e.g. |
The coverage is the share of samples that could be evaluated. Samples are skipped, for example, before the first command or outside the time range of a trajectory.
The Checks
topic_sanityCounts the messages of every recorded topic. Fails if a topic has fewer than
topic_sanity_min_messages(1) messages, which usually means a component was not running.obsCompares the vehicle state with the IPS observation at the same time (within 10 ms). Reports the position, lateral, longitudinal and heading error (RMSE, maximum and 95th percentile) per vehicle. A large error means the vehicle’s state estimate and the IPS disagree. There is no error threshold; the check only fails on insufficient data.
states_speedReports the minimum, maximum and mean speed per vehicle.
command_followCompares the vehicle state with the command that was active at that time, i.e. the latest command whose
valid_after_stamplies before the sample. ForPATH_TRACKINGthe vehicle is projected onto the path, forTRAJECTORYthe reference is interpolated at the sample time. Reports the lateral, longitudinal, heading and speed errors and the share of samples within 10 cm and 5°.DIRECTandSPEED_CURVATUREcommands are not evaluated.vehicle_collisionChecks every pair of vehicles for overlapping footprints at the same time. A footprint is a 0.25 m x 0.125 m rectangle. An overlap larger than 0.004 m² is a collision. Requires at least two vehicles.
boundary_violationChecks whether a vehicle’s footprint leaves the drivable area, the union of all lanelets of the map, by more than 0.004 m².
lanelet_collisionChecks whether a vehicle stays within the lanelets of its own command, i.e. the lanelets along its path or trajectory and their routing connections. Leaving them by more than 0.004 m² counts as violation. This finds vehicles that cut corners or drift into the opposite lane even if they stay on the road.
All thresholds are defined in the default configuration of cpm_lab_evaluation and can be changed with
cpm_eval evaluate <run> --config <file>, for example:
vehicle_length_m: 0.250
vehicle_width_m: 0.125
minimum_coverage: 0.7
minimum_overlap_area_m2: 0.004
within_lat_tol_m: 0.10
within_yaw_tol_deg: 5.0
Reading a Run in Python
The bag of a run is a standard rosbag2 in MCAP format in <run>/bag. You can inspect it with the ROS2 tools:
ros2 bag info ~/cpm_lab/experiments/<run>/bag
# Replay the run, e.g. to view it in RViz or Lichtblick
export ROS_DOMAIN_ID=21
ros2 bag play ~/cpm_lab/experiments/<run>/bag
For your own analysis, read the messages with rosbag2_py. The following script writes the speed of every vehicle
over time to a CSV file. Source your ROS2 installation before running it:
import csv
import re
import sys
from pathlib import Path
import rosbag2_py
from rclpy.serialization import deserialize_message
from rosidl_runtime_py.utilities import get_message
run = Path(sys.argv[1])
reader = rosbag2_py.SequentialReader()
reader.open(
rosbag2_py.StorageOptions(uri=str(run / 'bag'), storage_id=''),
rosbag2_py.ConverterOptions('cdr', 'cdr'),
)
types = {topic.name: topic.type for topic in reader.get_all_topics_and_types()}
state_topic = re.compile(r'^/lab/vehicle_(\d+)/vehicleState$')
with open('speeds.csv', 'w', newline='') as file:
writer = csv.writer(file)
writer.writerow(['time_s', 'vehicle_id', 'x', 'y', 'speed'])
while reader.has_next():
topic, data, receive_time_ns = reader.read_next()
if not state_topic.match(topic):
continue
state = deserialize_message(data, get_message(types[topic]))
stamp = state.valid_after_stamp.sec + state.valid_after_stamp.nanosec * 1e-9
writer.writerow([stamp, state.vehicle_id, state.pose.position.x,
state.pose.position.y, state.twist.linear.x])
Run it with python3 speeds.py ~/cpm_lab/experiments/<run> and plot the CSV file with your favorite tool.
The recorded topics are:
Topic |
Type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The map used in the run is stored in <run>/map/runtime_map.bin and can be loaded with
cpm_lab_lanelet2_extension_py.from_bin_msg(list(Path('runtime_map.bin').read_bytes())). manifest.json contains
the vehicles, the planner, the start and stop time and the software versions of the run, which you should report in
your thesis.
The time of a sample is valid_after_stamp, the time at which a state was valid or a command becomes active. Use it
instead of the time the message was received.