Skip to content

Commit 6e7155f

Browse files
committed
Get plotter working with separate subplots for normal viz and for pose graph.
1 parent ed979d0 commit 6e7155f

File tree

4 files changed

+120
-80
lines changed

4 files changed

+120
-80
lines changed

ekf_ws/src/base_pkg/config/params.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pose_graph:
6161
# Params for GTSAM's Gauss-Newton solver.
6262
iteration_error_threshold: 0.00001 # Stop iterating once the change in error between steps is less than this value.
6363
max_iterations: 100 # max iterations to run optimization algorithm.
64+
verbose: False # if true, spam the console with a ton of logs.
6465

6566
# ----- Map -----
6667
map:
@@ -105,3 +106,4 @@ plotter:
105106
show_landmark_sigma_pts: True # for UKF, show sigma points for landmarks as well as vehicle.
106107
display_region_mult: 1.0 # amount to multiply map.bound by to get the display region.
107108
list_clicked_points: False # utility to display coords of all clicked points, for manually creating landmark maps.
109+
# for pose graph display.

ekf_ws/src/base_pkg/src/plotting_node.py

+66-48
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from base_pkg.msg import EKFState, UKFState, PoseGraphState
1212
from matplotlib.backend_bases import MouseButton
1313
from matplotlib import pyplot as plt
14+
from matplotlib import gridspec
1415
import numpy as np
1516
import atexit
1617
from math import cos, sin, pi, atan2, remainder, tau
@@ -86,84 +87,79 @@ def update_plot(filter:str, msg):
8687

8788
###################### POSE GRAPH #####################
8889
# 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+
9994
# plot all landmark position estimates.
10095
remove_plot("init_pg_landmarks")
10196
if len(graph_before_optimization.landmarks) > 0: # there might be none.
10297
lm_x = [graph_before_optimization.landmarks[i] for i in range(0,len(graph_before_optimization.landmarks),2)]
10398
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)
105100

106101
# plot all vehicle poses.
107102
remove_plot("init_pg_veh_pose_history")
108103
arrow_x_components = [config["plotter"]["arrow_len"]*cos(graph_before_optimization.yaw_v[i]) for i in range(graph_before_optimization.num_iterations)]
109104
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)
111106

112107
# plot all connections.
113108
# we know a connection exists between every vehicle pose and the pose on the immediate previous/next iterations.
114109
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)
116111
# measurement connections are not fully-connected, but rather encoded in the message.
117112
for j in range(len(graph_before_optimization.meas_connections) // 2): # there might be none.
118113
iter_veh_pose = graph_before_optimization.meas_connections[2*j]
119114
landmark_index = graph_before_optimization.meas_connections[2*j+1]
120115
# 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)
122117

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:
128123
# plot poses and connections in final resulting graph.
129124
remove_plot("result_pg_veh_pose_history")
130125
arrow_x_components = [config["plotter"]["arrow_len"]*cos(graph_after_optimization.yaw_v[i]) for i in range(graph_after_optimization.num_iterations)]
131126
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)
133128

134129
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)
136131

137132
# 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.
138133

139-
plt.title("Optimized estimate of vehicle pose history")
134+
pose_graph_fig.title.set_text("Optimized estimate of vehicle pose history")
140135
plt.draw() # update the plot
141-
plt.pause(3)
136+
plt.pause(0.00000000001)
142137

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
145141

146142
###################### TIMESTEP #####################
147143
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)
149145
#################### TRUE POSE #########################
150146
if config["plotter"]["show_true_traj"] and msg.timestep <= len(true_poses):
151147
pose = true_poses[msg.timestep-1]
152148
# plot the current veh pos, & remove previous.
153149
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)
155151

156152
####################### EKF/UKF SLAM #########################
157153
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")
159155
# compute length of state to use throughout. n = 3+2M
160156
n = int(len(msg.P)**(1/2))
161157
################ VEH POS #################
162158
# plot current estimated veh pos.
163159
if not config["plotter"]["show_entire_traj"]:
164160
remove_plot("veh_pos_est")
165161
# 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")
167163

168164
################ VEH COV ##################
169165
if config["plotter"]["show_veh_ellipse"]:
@@ -173,15 +169,15 @@ def update_plot(filter:str, msg):
173169
if not config["plotter"]["show_entire_traj"]:
174170
remove_plot("veh_cov_est")
175171
# 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)
177173

178174
############## LANDMARK EST ##################
179175
lm_x = [msg.landmarks[i] for i in range(1,len(msg.landmarks),3)]
180176
lm_y = [msg.landmarks[i] for i in range(2,len(msg.landmarks),3)]
181177
# remove old landmark estimates.
182178
remove_plot("lm_pos_est")
183179
# 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)
185181

186182
############## LANDMARK COV ###################
187183
if config["plotter"]["show_landmark_ellipses"]:
@@ -193,7 +189,7 @@ def update_plot(filter:str, msg):
193189
# extract 2x2 cov for this landmark.
194190
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]]]))
195191
# 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)
197193

198194
############## UKF SIGMA POINTS ##################
199195
if filter == "ukf":
@@ -209,14 +205,14 @@ def update_plot(filter:str, msg):
209205
for i in range(0, 2*n_sig+1):
210206
remove_plot("veh_sigma_pts_{:}".format(i))
211207
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)
213209
else: # just show x,y of pts.
214210
X_x = [msg.X[i*n_sig] for i in range(0,2*n_sig+1)]
215211
X_y = [msg.X[i*n_sig+1] for i in range(0,2*n_sig+1)]
216212
# remove old points.
217213
remove_plot("veh_sigma_pts")
218214
# 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)
220216

221217
################# LANDMARK SIGMA POINTS ##################
222218
if config["plotter"]["show_landmark_sigma_pts"]:
@@ -228,9 +224,9 @@ def update_plot(filter:str, msg):
228224
# remove old points.
229225
remove_plot("lm_sigma_pts")
230226
# 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)
232228

233-
# force desired window region.
229+
# force desired window region (prevents axes expanding when vehicle comes close to an edge of the plot).
234230
plt.xlim(display_region)
235231
plt.ylim(display_region)
236232
# do the plotting.
@@ -285,7 +281,9 @@ def get_true_landmark_map(msg):
285281
lm_x = [msg.data[i] for i in range(1,len(msg.data),3)]
286282
lm_y = [msg.data[i] for i in range(2,len(msg.data),3)]
287283
# 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)
289287

290288
def on_click(event):
291289
# global clicked_points
@@ -309,7 +307,7 @@ def get_color_map(msg):
309307
color_map = bridge.imgmsg_to_cv2(msg, desired_encoding='passthrough')
310308
# add the true map image to the plot. extent=(L,R,B,T) gives display bounds.
311309
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])
313311

314312

315313
def get_planned_path(msg):
@@ -320,9 +318,9 @@ def get_planned_path(msg):
320318
remove_plot("goal_pt")
321319
# only draw if the path is non-empty.
322320
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)
324322
# 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)
326324

327325
def main():
328326
global goal_pub
@@ -369,17 +367,37 @@ def main():
369367

370368

371369
# 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.
378398
plt.connect('button_press_event', on_click)
379399
plt.show()
380400

381-
382-
383401
rospy.spin()
384402

385403

ekf_ws/src/localization_pkg/include/localization_pkg/filter.h

+3
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ class PoseGraph: public Filter {
264264
std::vector<std::unordered_map<int, Eigen::MatrixXd>> measurements;
265265
// We also need to keep track of detected landmark positions in order to identify re-detections when the landmark ID is not provided.
266266
std::vector<Eigen::Vector2d> lm_positions;
267+
268+
// Desired logging behavior.
269+
bool verbose = false;
267270
};
268271

269272
#endif // FILTER_INTERFACE_H

0 commit comments

Comments
 (0)