11
11
from base_pkg .msg import EKFState , UKFState , PoseGraphState
12
12
from matplotlib .backend_bases import MouseButton
13
13
from matplotlib import pyplot as plt
14
+ from matplotlib import gridspec
14
15
import numpy as np
15
16
import atexit
16
17
from math import cos , sin , pi , atan2 , remainder , tau
@@ -86,84 +87,79 @@ def update_plot(filter:str, msg):
86
87
87
88
###################### POSE GRAPH #####################
88
89
# if we detect that pose-graph-slam has finished, kill the current plot and switch to that one.
89
- if graph_before_optimization is not None :
90
- # kill the current plot.
91
- rospy .logwarn ("PLT: killing current plot." )
92
- plt .clf ()
93
- # start the pose-graph plot.
94
- rospy .logwarn ("PLT: plotting pose graph instead of live sim." )
95
-
96
- """
97
- Plot the graph encoded in a PoseGraphState message.
98
- """
90
+ if config ["filter" ].lower () == "pose_graph" and graph_before_optimization is not None :
91
+ # Plot the graph encoded in a PoseGraphState message.
92
+ rospy .logwarn ("PLT: plotting pose graph subplot." )
93
+
99
94
# plot all landmark position estimates.
100
95
remove_plot ("init_pg_landmarks" )
101
96
if len (graph_before_optimization .landmarks ) > 0 : # there might be none.
102
97
lm_x = [graph_before_optimization .landmarks [i ] for i in range (0 ,len (graph_before_optimization .landmarks ),2 )]
103
98
lm_y = [graph_before_optimization .landmarks [i ] for i in range (1 ,len (graph_before_optimization .landmarks ),2 )]
104
- plots ["init_pg_landmarks" ] = plt .scatter (lm_x , lm_y , s = 30 , color = "red" , edgecolors = "black" , zorder = 6 )
99
+ plots ["init_pg_landmarks" ] = pose_graph_fig .scatter (lm_x , lm_y , s = 30 , color = "red" , edgecolors = "black" , zorder = 6 )
105
100
106
101
# plot all vehicle poses.
107
102
remove_plot ("init_pg_veh_pose_history" )
108
103
arrow_x_components = [config ["plotter" ]["arrow_len" ]* cos (graph_before_optimization .yaw_v [i ]) for i in range (graph_before_optimization .num_iterations )]
109
104
arrow_y_components = [config ["plotter" ]["arrow_len" ]* sin (graph_before_optimization .yaw_v [i ]) for i in range (graph_before_optimization .num_iterations )]
110
- plots ["init_pg_veh_pose_history" ] = plt .quiver (graph_before_optimization .x_v , graph_before_optimization .y_v , arrow_x_components , arrow_y_components , color = "blue" , width = 0.1 , zorder = 4 , edgecolor = "black" , pivot = "mid" , linewidth = 1 , minlength = 0.0001 )
105
+ plots ["init_pg_veh_pose_history" ] = pose_graph_fig .quiver (graph_before_optimization .x_v , graph_before_optimization .y_v , arrow_x_components , arrow_y_components , color = "blue" , width = 0.1 , zorder = 4 , edgecolor = "black" , pivot = "mid" , linewidth = 1 , minlength = 0.0001 )
111
106
112
107
# plot all connections.
113
108
# we know a connection exists between every vehicle pose and the pose on the immediate previous/next iterations.
114
109
remove_plot ("init_pg_cmd_connections" )
115
- plots ["init_pg_cmd_connections" ] = plt .plot (graph_before_optimization .x_v , graph_before_optimization .y_v , color = "blue" , zorder = 0 )
110
+ plots ["init_pg_cmd_connections" ] = pose_graph_fig .plot (graph_before_optimization .x_v , graph_before_optimization .y_v , color = "blue" , zorder = 0 )
116
111
# measurement connections are not fully-connected, but rather encoded in the message.
117
112
for j in range (len (graph_before_optimization .meas_connections ) // 2 ): # there might be none.
118
113
iter_veh_pose = graph_before_optimization .meas_connections [2 * j ]
119
114
landmark_index = graph_before_optimization .meas_connections [2 * j + 1 ]
120
115
# plot a line between the specified vehicle pose and landmark.
121
- plt .plot ([graph_before_optimization .x_v [iter_veh_pose ], lm_x [landmark_index ]], [graph_before_optimization .x_y [iter_veh_pose ], lm_y [landmark_index ]], color = "red" , zorder = 0 )
116
+ pose_graph_fig .plot ([graph_before_optimization .x_v [iter_veh_pose ], lm_x [landmark_index ]], [graph_before_optimization .x_y [iter_veh_pose ], lm_y [landmark_index ]], color = "red" , zorder = 0 )
122
117
123
- plt . title ( "Naive estimate of vehicle pose history" )
124
- plt . draw () # update the plot
125
- plt .pause ( 3 )
126
-
127
- if graph_after_optimization is not None :
118
+ if graph_after_optimization is None :
119
+ pose_graph_fig . title . set_text ( "Naive estimate of vehicle pose history" )
120
+ plt .draw () # update the plot
121
+ plt . pause ( 0.000001 )
122
+ else :
128
123
# plot poses and connections in final resulting graph.
129
124
remove_plot ("result_pg_veh_pose_history" )
130
125
arrow_x_components = [config ["plotter" ]["arrow_len" ]* cos (graph_after_optimization .yaw_v [i ]) for i in range (graph_after_optimization .num_iterations )]
131
126
arrow_y_components = [config ["plotter" ]["arrow_len" ]* sin (graph_after_optimization .yaw_v [i ]) for i in range (graph_after_optimization .num_iterations )]
132
- plots ["result_pg_veh_pose_history" ] = plt .quiver (graph_after_optimization .x_v , graph_after_optimization .y_v , arrow_x_components , arrow_y_components , color = "purple" , width = 0.1 , zorder = 5 , edgecolor = "black" , pivot = "mid" , linewidth = 1 , minlength = 0.0001 )
127
+ plots ["result_pg_veh_pose_history" ] = pose_graph_fig .quiver (graph_after_optimization .x_v , graph_after_optimization .y_v , arrow_x_components , arrow_y_components , color = "purple" , width = 0.1 , zorder = 5 , edgecolor = "black" , pivot = "mid" , linewidth = 1 , minlength = 0.0001 )
133
128
134
129
remove_plot ("result_pg_cmd_connections" )
135
- plots ["result_pg_cmd_connections" ] = plt .plot (graph_after_optimization .x_v , graph_after_optimization .y_v , color = "purple" , zorder = 1 )
130
+ plots ["result_pg_cmd_connections" ] = pose_graph_fig .plot (graph_after_optimization .x_v , graph_after_optimization .y_v , color = "purple" , zorder = 1 )
136
131
137
132
# TODO if we end up optimizing landmark positions too, plot those as well, since they may be different. since they're the same as init currently, don't bother plotting them.
138
133
139
- plt .title ("Optimized estimate of vehicle pose history" )
134
+ pose_graph_fig .title . set_text ("Optimized estimate of vehicle pose history" )
140
135
plt .draw () # update the plot
141
- plt .pause (3 )
136
+ plt .pause (0.00000000001 )
142
137
143
- rospy .logwarn ("PLT: skipping normal plot update loop." )
144
- return
138
+ plt .waitforbuttonpress (- 1 ) # wait forever until a user clicks the plot.
139
+ rospy .logwarn ("PLT: skipping normal plot update loop." )
140
+ return
145
141
146
142
###################### TIMESTEP #####################
147
143
remove_plot ("timestep" )
148
- plots ["timestep" ] = plt .text (- config ["map" ]["bound" ], config ["map" ]["bound" ], 't = ' + str (msg .timestep ), horizontalalignment = 'left' , verticalalignment = 'bottom' , zorder = 2 )
144
+ plots ["timestep" ] = sim_viz_fig .text (- config ["map" ]["bound" ], config ["map" ]["bound" ], 't = ' + str (msg .timestep ), horizontalalignment = 'left' , verticalalignment = 'bottom' , zorder = 2 )
149
145
#################### TRUE POSE #########################
150
146
if config ["plotter" ]["show_true_traj" ] and msg .timestep <= len (true_poses ):
151
147
pose = true_poses [msg .timestep - 1 ]
152
148
# plot the current veh pos, & remove previous.
153
149
remove_plot ("veh_pos_true" )
154
- plots ["veh_pos_true" ] = plt .arrow (pose .x , pose .y , config ["plotter" ]["arrow_len" ]* cos (pose .z ), config ["plotter" ]["arrow_len" ]* sin (pose .z ), color = "blue" , width = 0.1 , zorder = 2 )
150
+ plots ["veh_pos_true" ] = sim_viz_fig .arrow (pose .x , pose .y , config ["plotter" ]["arrow_len" ]* cos (pose .z ), config ["plotter" ]["arrow_len" ]* sin (pose .z ), color = "blue" , width = 0.1 , zorder = 2 )
155
151
156
152
####################### EKF/UKF SLAM #########################
157
153
if filter in ["ekf" , "ukf" ]:
158
- plt .title (filter .upper ()+ "-Estimated Trajectory and Landmarks" )
154
+ sim_viz_fig .title . set_text (filter .upper ()+ "-Estimated Trajectory and Landmarks" )
159
155
# compute length of state to use throughout. n = 3+2M
160
156
n = int (len (msg .P )** (1 / 2 ))
161
157
################ VEH POS #################
162
158
# plot current estimated veh pos.
163
159
if not config ["plotter" ]["show_entire_traj" ]:
164
160
remove_plot ("veh_pos_est" )
165
161
# draw a single pt with arrow to represent current veh pose.
166
- plots ["veh_pos_est" ] = plt .arrow (msg .x_v , msg .y_v , config ["plotter" ]["arrow_len" ]* cos (msg .yaw_v ), config ["plotter" ]["arrow_len" ]* sin (msg .yaw_v ), facecolor = "green" , width = 0.1 , zorder = 4 , edgecolor = "black" )
162
+ plots ["veh_pos_est" ] = sim_viz_fig .arrow (msg .x_v , msg .y_v , config ["plotter" ]["arrow_len" ]* cos (msg .yaw_v ), config ["plotter" ]["arrow_len" ]* sin (msg .yaw_v ), facecolor = "green" , width = 0.1 , zorder = 4 , edgecolor = "black" )
167
163
168
164
################ VEH COV ##################
169
165
if config ["plotter" ]["show_veh_ellipse" ]:
@@ -173,15 +169,15 @@ def update_plot(filter:str, msg):
173
169
if not config ["plotter" ]["show_entire_traj" ]:
174
170
remove_plot ("veh_cov_est" )
175
171
# plot the ellipse.
176
- plots ["veh_cov_est" ], = plt .plot (msg .x_v + veh_ell [0 ,:] , msg .y_v + veh_ell [1 ,:],'lightgrey' , zorder = 1 )
172
+ plots ["veh_cov_est" ], = sim_viz_fig .plot (msg .x_v + veh_ell [0 ,:] , msg .y_v + veh_ell [1 ,:],'lightgrey' , zorder = 1 )
177
173
178
174
############## LANDMARK EST ##################
179
175
lm_x = [msg .landmarks [i ] for i in range (1 ,len (msg .landmarks ),3 )]
180
176
lm_y = [msg .landmarks [i ] for i in range (2 ,len (msg .landmarks ),3 )]
181
177
# remove old landmark estimates.
182
178
remove_plot ("lm_pos_est" )
183
179
# plot new landmark estimates.
184
- plots ["lm_pos_est" ] = plt .scatter (lm_x , lm_y , s = 30 , color = "red" , edgecolors = "black" , zorder = 3 )
180
+ plots ["lm_pos_est" ] = sim_viz_fig .scatter (lm_x , lm_y , s = 30 , color = "red" , edgecolors = "black" , zorder = 3 )
185
181
186
182
############## LANDMARK COV ###################
187
183
if config ["plotter" ]["show_landmark_ellipses" ]:
@@ -193,7 +189,7 @@ def update_plot(filter:str, msg):
193
189
# extract 2x2 cov for this landmark.
194
190
lm_ell = cov_to_ellipse (np .array ([[msg .P [3 + 2 * i ],msg .P [4 + 2 * i ]],[msg .P [n + 3 + 2 * i ],msg .P [n + 4 + 2 * i ]]]))
195
191
# plot its ellipse.
196
- plots ["lm_cov_est_{:}" .format (lm_id )], = plt .plot (lm_x [i ]+ lm_ell [0 ,:], lm_y [i ]+ lm_ell [1 ,:], 'orange' , zorder = 1 )
192
+ plots ["lm_cov_est_{:}" .format (lm_id )], = sim_viz_fig .plot (lm_x [i ]+ lm_ell [0 ,:], lm_y [i ]+ lm_ell [1 ,:], 'orange' , zorder = 1 )
197
193
198
194
############## UKF SIGMA POINTS ##################
199
195
if filter == "ukf" :
@@ -209,14 +205,14 @@ def update_plot(filter:str, msg):
209
205
for i in range (0 , 2 * n_sig + 1 ):
210
206
remove_plot ("veh_sigma_pts_{:}" .format (i ))
211
207
yaw = msg .X [i * n_sig + 2 ] if veh_len == 3 else remainder (atan2 (msg .X [i * n_sig + 3 ], msg .X [i * n_sig + 2 ]), tau )
212
- plots ["veh_sigma_pts_{:}" .format (i )] = plt .arrow (msg .X [i * n_sig ], msg .X [i * n_sig + 1 ], config ["plotter" ]["arrow_len" ]* cos (yaw ), config ["plotter" ]["arrow_len" ]* sin (yaw ), color = "cyan" , width = 0.1 )
208
+ plots ["veh_sigma_pts_{:}" .format (i )] = sim_viz_fig .arrow (msg .X [i * n_sig ], msg .X [i * n_sig + 1 ], config ["plotter" ]["arrow_len" ]* cos (yaw ), config ["plotter" ]["arrow_len" ]* sin (yaw ), color = "cyan" , width = 0.1 )
213
209
else : # just show x,y of pts.
214
210
X_x = [msg .X [i * n_sig ] for i in range (0 ,2 * n_sig + 1 )]
215
211
X_y = [msg .X [i * n_sig + 1 ] for i in range (0 ,2 * n_sig + 1 )]
216
212
# remove old points.
217
213
remove_plot ("veh_sigma_pts" )
218
214
# plot sigma points.
219
- plots ["veh_sigma_pts" ] = plt .scatter (X_x , X_y , s = 30 , color = "tab:cyan" , zorder = 2 )
215
+ plots ["veh_sigma_pts" ] = sim_viz_fig .scatter (X_x , X_y , s = 30 , color = "tab:cyan" , zorder = 2 )
220
216
221
217
################# LANDMARK SIGMA POINTS ##################
222
218
if config ["plotter" ]["show_landmark_sigma_pts" ]:
@@ -228,9 +224,9 @@ def update_plot(filter:str, msg):
228
224
# remove old points.
229
225
remove_plot ("lm_sigma_pts" )
230
226
# plot sigma points.
231
- plots ["lm_sigma_pts" ] = plt .scatter (X_lm_x , X_lm_y , s = 30 , color = "tab:cyan" , zorder = 1 )
227
+ plots ["lm_sigma_pts" ] = sim_viz_fig .scatter (X_lm_x , X_lm_y , s = 30 , color = "tab:cyan" , zorder = 1 )
232
228
233
- # force desired window region.
229
+ # force desired window region (prevents axes expanding when vehicle comes close to an edge of the plot) .
234
230
plt .xlim (display_region )
235
231
plt .ylim (display_region )
236
232
# do the plotting.
@@ -285,7 +281,9 @@ def get_true_landmark_map(msg):
285
281
lm_x = [msg .data [i ] for i in range (1 ,len (msg .data ),3 )]
286
282
lm_y = [msg .data [i ] for i in range (2 ,len (msg .data ),3 )]
287
283
# plot the true landmark positions to compare to estimates.
288
- plt .scatter (lm_x , lm_y , s = 30 , color = "white" , edgecolors = "black" , zorder = 2 )
284
+ sim_viz_fig .scatter (lm_x , lm_y , s = 30 , color = "white" , edgecolors = "black" , zorder = 2 )
285
+ if config ["filter" ].lower () == "pose_graph" :
286
+ pose_graph_fig .scatter (lm_x , lm_y , s = 30 , color = "white" , edgecolors = "black" , zorder = 2 )
289
287
290
288
def on_click (event ):
291
289
# global clicked_points
@@ -309,7 +307,7 @@ def get_color_map(msg):
309
307
color_map = bridge .imgmsg_to_cv2 (msg , desired_encoding = 'passthrough' )
310
308
# add the true map image to the plot. extent=(L,R,B,T) gives display bounds.
311
309
edge = config ["map" ]["bound" ]
312
- plt .imshow (color_map , zorder = 0 , extent = [- edge , edge , - edge , edge ])
310
+ sim_viz_fig .imshow (color_map , zorder = 0 , extent = [- edge , edge , - edge , edge ])
313
311
314
312
315
313
def get_planned_path (msg ):
@@ -320,9 +318,9 @@ def get_planned_path(msg):
320
318
remove_plot ("goal_pt" )
321
319
# only draw if the path is non-empty.
322
320
if len (msg .data ) > 1 :
323
- plots ["planned_path" ] = plt .scatter ([msg .data [i ] for i in range (0 ,len (msg .data ),2 )], [msg .data [i ] for i in range (1 ,len (msg .data ),2 )], s = 12 , color = "purple" , zorder = 1 )
321
+ plots ["planned_path" ] = sim_viz_fig .scatter ([msg .data [i ] for i in range (0 ,len (msg .data ),2 )], [msg .data [i ] for i in range (1 ,len (msg .data ),2 )], s = 12 , color = "purple" , zorder = 1 )
324
322
# show the goal point (end of path).
325
- plots ["goal_pt" ] = plt .scatter (msg .data [- 2 ], msg .data [- 1 ], color = "yellow" , edgecolors = "black" , s = 40 , zorder = 2 )
323
+ plots ["goal_pt" ] = sim_viz_fig .scatter (msg .data [- 2 ], msg .data [- 1 ], color = "yellow" , edgecolors = "black" , s = 40 , zorder = 2 )
326
324
327
325
def main ():
328
326
global goal_pub
@@ -369,17 +367,37 @@ def main():
369
367
370
368
371
369
# startup the plot.
372
- plt .figure ()
373
- # set constant plot params.
374
- plt .axis ("equal" )
375
- plt .xlabel ("x (m)" )
376
- plt .ylabel ("y (m)" )
377
- # allow user to click on the plot to set the goal position.
370
+ global sim_viz_fig , pose_graph_fig
371
+ if config ["filter" ].lower () == "pose_graph" :
372
+ global fig
373
+ fig = plt .figure ()
374
+ # create 1 row of 2 plots, with a 1:1 size ratio for them.
375
+ gs = gridspec .GridSpec (1 , 2 , width_ratios = [1 , 1 ])
376
+ sim_viz_fig = plt .subplot (gs [0 ])
377
+ plt .title ("Ground truth and online filter progress" )
378
+ # force desired window region.
379
+ plt .xlim (display_region )
380
+ plt .ylim (display_region )
381
+ pose_graph_fig = plt .subplot (gs [1 ])
382
+ plt .title ("Pose graph progress" )
383
+ # force desired window region.
384
+ plt .xlim (display_region )
385
+ plt .ylim (display_region )
386
+
387
+ sim_viz_fig .set_aspect ('equal' )
388
+ pose_graph_fig .set_aspect ('equal' )
389
+ else :
390
+ # Will not be plotting a pose graph, so only open one plot window.
391
+ plt .figure ()
392
+ sim_viz_fig = plt .subplot ()
393
+ plt .axis ("equal" )
394
+ plt .xlabel ("x (m)" )
395
+ plt .ylabel ("y (m)" )
396
+
397
+ # allow user to click on the plot to set the goal position or kill the node.
378
398
plt .connect ('button_press_event' , on_click )
379
399
plt .show ()
380
400
381
-
382
-
383
401
rospy .spin ()
384
402
385
403
0 commit comments