66import json
77import matplotlib .pyplot as plt
88import numpy as np
9+ from matplotlib .collections import LineCollection
10+ from matplotlib .colors import Normalize
911
1012CMAP = "RdYlGn"
1113
@@ -21,7 +23,7 @@ def compute_safety_factor(value, safe_thresh, unsafe_thresh, flip=False):
2123 factor = 0.0
2224 else :
2325 factor = 1.0 - (abs_val - safe_thresh ) / (unsafe_thresh - safe_thresh )
24-
26+
2527 if flip :
2628 return 1.0 - factor
2729 return factor
@@ -40,7 +42,7 @@ def parse_behavior_log(filename):
4042 heading_rates = []
4143 pedestrian_times = []
4244 pedestrian_distances = []
43-
45+
4446 with open (filename , 'r' ) as f :
4547 for line in f :
4648 try :
@@ -73,8 +75,8 @@ def parse_behavior_log(filename):
7375 pedestrian_times .append (t )
7476 dist = np .sqrt (x_agent ** 2 + y_agent ** 2 )
7577 pedestrian_distances .append (dist )
76-
77- return (np .array (times ), np .array (accelerations ), np .array (heading_rates ),
78+
79+ return (np .array (times ), np .array (accelerations ), np .array (heading_rates ),
7880 np .array (pedestrian_times ), np .array (pedestrian_distances ))
7981
8082def parse_tracker_csv (filename ):
@@ -111,69 +113,137 @@ def add_safety_colorbar(figure):
111113 cbar = figure .colorbar (sm , cax = cbar_ax )
112114 cbar .set_label ("Comfort/Safety Level" )
113115
114- def plot_metrics (time_jerk , jerk , time_heading_acc , heading_acc , vehicle_time , cte ,
116+ def plot_metrics (time_jerk , jerk , time_heading_acc , heading_acc , time_accel , accel , vehicle_time , cte ,
115117 x_actual , y_actual , x_desired , y_desired , pedestrian_times , pedestrian_distances ):
116118 """Plots all metrics in 2x3 grid"""
117119 fig , axs = plt .subplots (2 , 3 , figsize = (12 , 8 ))
118120 fig .subplots_adjust (hspace = 0.375 , wspace = 0.35 )
119- axs [1 ,2 ].axis ('off' )
121+ # axs[1,2].axis('off')
120122
121123 plot_jerk (axs [0 ,0 ], time_jerk , jerk )
122124 plot_heading_acceleration (axs [0 ,1 ], time_heading_acc , heading_acc )
125+ plot_acceleration (axs [0 ,2 ], time_accel , accel )
123126 plot_crosstrack_error (axs [1 ,0 ], vehicle_time , cte )
124127 plot_position (axs [1 ,1 ], x_actual , y_actual , x_desired , y_desired )
125- plot_pedestrian_dist (axs [0 ,2 ], pedestrian_times , pedestrian_distances )
126-
128+ plot_pedestrian_dist (axs [1 ,2 ], pedestrian_times , pedestrian_distances )
129+
127130 # Colorbar on the right side
128131 add_safety_colorbar (fig )
129-
130132 plt .show ()
131133
132134def plot_jerk (axis , time , jerk , safe_thresh = 1.0 , unsafe_thresh = 2.5 ):
133- """Plots vehicle jerk (rate of acceleration) vs. time"""
134- safety_scores = np .vectorize (compute_safety_factor )(jerk , safe_thresh , unsafe_thresh )
135-
136- axis .plot (time , jerk , color = "black" , linewidth = 0.8 , alpha = 0.5 )
137- axis .scatter (time , jerk , c = safety_scores , cmap = CMAP , vmin = 0 , vmax = 1 , edgecolors = "black" )
135+ """
136+ Plots vehicle jerk (rate of acceleration) vs. time as a colored line.
137+ """
138+ # compute safety scores
139+ safety = np .vectorize (compute_safety_factor )(jerk , safe_thresh , unsafe_thresh )
140+
141+ # build line segments
142+ points = np .vstack ([time , jerk ]).T .reshape (- 1 ,1 ,2 )
143+ segments = np .concatenate ([points [:- 1 ], points [1 :]], axis = 1 )
144+
145+ # create colored LineCollection
146+ norm = Normalize (vmin = 0 , vmax = 1 )
147+ lc = LineCollection (segments , cmap = CMAP , norm = norm , linewidth = 1.5 )
148+ lc .set_array (safety [:- 1 ])
149+ axis .add_collection (lc )
138150
151+ # set limits & labels
152+ axis .set_xlim (time .min (), time .max ())
153+ axis .set_ylim (jerk .min (), jerk .max ())
139154 axis .set_xlabel ("Time (s)" )
140155 axis .set_ylabel ("Jerk (m/s³)" )
141156 axis .set_title ("Vehicle Jerk Over Time" )
142157 axis .grid (True )
143158
159+ def plot_acceleration (axis , time , acceleration , safe_thresh = 0.5 , unsafe_thresh = 1.5 ):
160+ """
161+ Plots vehicle acceleration vs. time as a colored line.
162+ """
163+ safety = np .vectorize (compute_safety_factor )(acceleration , safe_thresh , unsafe_thresh )
164+
165+ points = np .vstack ([time , acceleration ]).T .reshape (- 1 ,1 ,2 )
166+ segments = np .concatenate ([points [:- 1 ], points [1 :]], axis = 1 )
167+
168+ norm = Normalize (vmin = 0 , vmax = 1 )
169+ lc = LineCollection (segments , cmap = CMAP , norm = norm , linewidth = 1.5 )
170+ lc .set_array (safety [:- 1 ])
171+ axis .add_collection (lc )
172+
173+ axis .set_xlim (time .min (), time .max ())
174+ axis .set_ylim (acceleration .min (), acceleration .max ())
175+ axis .set_xlabel ("Time (s)" )
176+ axis .set_ylabel ("Acceleration (m/s²)" )
177+ axis .set_title ("Vehicle Acceleration Over Time" )
178+ axis .grid (True )
179+
144180def plot_heading_acceleration (axis , time , heading_acc , safe_thresh = 0.0075 , unsafe_thresh = 0.25 ):
145- """Plots vehicle heading acceleration vs. time"""
146- safety_scores = np .vectorize (compute_safety_factor )(heading_acc , safe_thresh , unsafe_thresh )
147-
148- axis .plot (time , heading_acc , color = "black" , linewidth = 0.8 , alpha = 0.5 )
149- axis .scatter (time , heading_acc , c = safety_scores , cmap = CMAP , vmin = 0 , vmax = 1 , edgecolors = "black" )
150-
181+ """
182+ Plots vehicle heading acceleration vs. time as a colored line.
183+ """
184+ safety = np .vectorize (compute_safety_factor )(heading_acc , safe_thresh , unsafe_thresh )
185+
186+ points = np .vstack ([time , heading_acc ]).T .reshape (- 1 ,1 ,2 )
187+ segments = np .concatenate ([points [:- 1 ], points [1 :]], axis = 1 )
188+
189+ norm = Normalize (vmin = 0 , vmax = 1 )
190+ lc = LineCollection (segments , cmap = CMAP , norm = norm , linewidth = 1.5 )
191+ lc .set_array (safety [:- 1 ])
192+ axis .add_collection (lc )
193+
194+ axis .set_xlim (time .min (), time .max ())
195+ axis .set_ylim (heading_acc .min (), heading_acc .max ())
151196 axis .set_xlabel ("Time (s)" )
152197 axis .set_ylabel ("Heading Acceleration (rad/s²)" )
153198 axis .set_title ("Vehicle Heading Acceleration Over Time" )
154199 axis .grid (True )
155200
156201def plot_crosstrack_error (axis , time , cte , safe_thresh = 0.1 , unsafe_thresh = 0.4 ):
157- """Plots vehicle cross track error vs. time"""
158- safety_scores = np .vectorize (compute_safety_factor )(cte , safe_thresh , unsafe_thresh )
159-
160- axis .plot (time , cte , color = "black" , linewidth = 0.8 , alpha = 0.5 )
161- axis .scatter (time , cte , c = safety_scores , cmap = CMAP , vmin = 0 , vmax = 1 , edgecolors = "black" )
162-
202+ """
203+ Plots vehicle cross track error vs. time as a colored line.
204+ """
205+ # compute safety scores for each point
206+ safety = np .vectorize (compute_safety_factor )(cte , safe_thresh , unsafe_thresh )
207+
208+ points = np .vstack ([time , cte ]).T .reshape (- 1 ,1 ,2 )
209+ segments = np .concatenate ([points [:- 1 ], points [1 :]], axis = 1 )
210+
211+ lc = LineCollection (segments , cmap = CMAP , norm = Normalize (0 ,1 ))
212+ lc .set_array (safety [:- 1 ])
213+ lc .set_linewidth (2.0 )
214+ axis .add_collection (lc )
215+
216+ # set axis limits
217+ axis .set_xlim (time .min (), time .max ())
218+ axis .set_ylim (cte .min (), cte .max ())
219+
163220 axis .set_xlabel ("Time (s)" )
164221 axis .set_ylabel ("Cross Track Error" )
165222 axis .set_title ("Vehicle Cross Track Error Over Time" )
166223 axis .grid (True )
167224
168- def plot_position (axis , x_actual , y_actual , x_desired , y_desired , safe_thresh = 1 , unsafe_thresh = 2.5 ):
169- """Plots vehicle actual and desired positions vs. time"""
170- position_error = np .sqrt ((x_desired - x_actual ) ** 2 + (y_desired - y_actual ) ** 2 )
171- safety_scores = np .vectorize (compute_safety_factor )(position_error , safe_thresh , unsafe_thresh )
172-
173- axis .plot (y_desired , x_desired , marker = '.' , linestyle = ':' , color = 'blue' , label = 'Desired' )
174- axis .plot (y_actual , x_actual , color = "black" , linewidth = 0.8 , alpha = 0.5 )
175- axis .scatter (y_actual , x_actual , c = safety_scores , cmap = CMAP , vmin = 0 , vmax = 1 , edgecolors = "black" )
176-
225+ def plot_position (axis , x_actual , y_actual , x_desired , y_desired ,
226+ safe_thresh = 1.0 , unsafe_thresh = 2.5 ):
227+ """
228+ Plots vehicle actual and desired positions
229+ """
230+ # compute positional error and safety at each point
231+ pos_error = np .sqrt ((x_desired - x_actual )** 2 + (y_desired - y_actual )** 2 )
232+ safety = np .vectorize (compute_safety_factor )(pos_error , safe_thresh , unsafe_thresh )
233+
234+ # actual path segments
235+ actual_pts = np .vstack ([y_actual , x_actual ]).T .reshape (- 1 ,1 ,2 )
236+ actual_segs = np .concatenate ([actual_pts [:- 1 ], actual_pts [1 :]], axis = 1 )
237+ norm = Normalize (vmin = 0 , vmax = 1 )
238+ lc_actual = LineCollection (actual_segs , cmap = CMAP , norm = norm )
239+ lc_actual .set_array (safety [:- 1 ])
240+ lc_actual .set_linewidth (2.0 )
241+ axis .add_collection (lc_actual )
242+
243+ # desired path as dashed gray line
244+ axis .plot (y_desired , x_desired ,
245+ linestyle = '--' , linewidth = 1.5 , color = 'gray' , label = 'Desired' )
246+
177247 axis .set_xlabel ("Y Position (m)" )
178248 axis .set_ylabel ("X Position (m)" )
179249 axis .set_title ("Vehicle Position vs. Desired Position" )
@@ -186,7 +256,7 @@ def plot_pedestrian_dist(axis, pedestrian_times, pedestrian_distances, safe_thre
186256 safety_scores = np .vectorize (compute_safety_factor )(pedestrian_distances , safe_thresh , unsafe_thresh , flip = True )
187257 axis .plot (pedestrian_times , pedestrian_distances , color = "black" , linewidth = 0.8 , alpha = 0.5 )
188258 axis .scatter (pedestrian_times , pedestrian_distances , c = safety_scores , cmap = CMAP , vmin = 0 , vmax = 1 , edgecolors = "black" )
189-
259+
190260 axis .set_xlabel ("Time (s)" )
191261 axis .set_ylabel ("Pedestrian Distance (m)" )
192262 axis .set_title ("Pedestrian Distance Over Time" )
@@ -196,41 +266,43 @@ def plot_pedestrian_dist(axis, pedestrian_times, pedestrian_distances, safe_thre
196266 if len (sys .argv ) != 2 :
197267 print ("Usage: python test_comfort_metrics.py <log_directory>" )
198268 sys .exit (1 )
199-
269+
200270 log_dir = sys .argv [1 ]
201271 behavior_file = os .path .join (log_dir , "behavior.json" )
202272 tracker_file = os .path .join (log_dir , "PurePursuitTrajectoryTracker_debug.csv" )
203-
273+
204274 # if behavior.json doesn't exist, print error and exit
205275 if not os .path .exists (behavior_file ):
206276 print ("Error: behavior.json file is missing in log folder." )
207277 sys .exit (1 )
208-
278+
209279 # Parse behavior log file and compute metrics
210280 times , accelerations , heading_rates , ped_times , ped_distances = parse_behavior_log (behavior_file )
211281 time_jerk , jerk = compute_derivative (times , accelerations )
212282 time_heading_acc , heading_acc = compute_derivative (times , heading_rates )
213-
283+
214284 # Pure pursuit tracker file exists: parse and plot all metrics
215285 if os .path .exists (tracker_file ):
216286 vehicle_time , cte , x_actual , y_actual , x_desired , y_desired = parse_tracker_csv (tracker_file )
217- plot_metrics (time_jerk , jerk , time_heading_acc , heading_acc , vehicle_time , cte ,
287+ plot_metrics (time_jerk , jerk , time_heading_acc , heading_acc , times , accelerations , vehicle_time , cte ,
218288 x_actual , y_actual , x_desired , y_desired , ped_times , ped_distances )
219-
289+
220290 print ("RMS Cross Track Error:" , np .sqrt (np .mean (cte ** 2 )), "m" )
221291 print ("RMS Position Error:" , np .sqrt (np .mean ((x_actual - x_desired )** 2 + (y_actual - y_desired )** 2 )), 'm' )
222292 # Pure pursuit tracker file is missing: plot only behavior.json metrics
223293 else :
224294 print ("Tracker file is missing. Skipping cross track error and vehicle position plots." )
225295 # Plot only jerk, heading acceleration, and pedestrian distance
226- fig , axs = plt .subplots (1 , 3 , figsize = (12 , 4 ))
227- plot_jerk (axs [0 ], time_jerk , jerk )
228- plot_heading_acceleration (axs [1 ], time_heading_acc , heading_acc )
229- plot_pedestrian_dist (axs [2 ], ped_times , ped_distances )
296+ fig , axs = plt .subplots (2 , 2 , figsize = (12 , 4 ))
297+ plot_jerk (axs [0 , 0 ], time_jerk , jerk )
298+ plot_heading_acceleration (axs [0 , 1 ], time_heading_acc , heading_acc )
299+ plot_acceleration (axs [1 , 0 ], times , accelerations )
300+ plot_pedestrian_dist (axs [1 , 1 ], ped_times , ped_distances )
301+
230302 add_safety_colorbar (fig )
231303 plt .show ()
232-
304+
233305 print ("RMS Jerk:" , np .sqrt (np .mean (jerk ** 2 )), "m/s³" )
234306 print ("RMS Heading Acceleration:" , np .sqrt (np .mean (heading_acc ** 2 )), "rad/s²" )
235307 if len (ped_distances ) > 0 :
236- print ("Minimum Pedestrian Distance:" , np .min (ped_distances ), "m" )
308+ print ("Minimum Pedestrian Distance:" , np .min (ped_distances ), "m" )
0 commit comments