Skip to content

Commit f5a27a4

Browse files
committed
visualize drift and frame alignment results
1 parent 87c3a92 commit f5a27a4

File tree

7 files changed

+488
-51
lines changed

7 files changed

+488
-51
lines changed

panther_msgs/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ add_message_files(
5757
Logtp.msg
5858
GoalReached.msg
5959
IsReady.msg
60+
Drift.msg
6061
)
6162

6263
## Generate services in the 'srv' folder

panther_msgs/msg/Drift.msg

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Header header
2+
float32[] drift_pos
3+
float32[] drift_euler

primer/other/data_extraction/plot_animation.py

+36-22
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ def get_data_sync_maps_and_data_sync_world(veh_name):
7373
# data extraction from bag file
7474
# Parse command line arguments
7575
parser = argparse.ArgumentParser(description="Extract images from a ROS bag.")
76-
parser.add_argument("bag_folder", help="Input directory.")
77-
parser.add_argument("output_dir", help="Output directory.")
76+
parser.add_argument("--bag_folder", help="Input directory.")
7877
args = parser.parse_args()
7978

8079
# cut off time for run.bag (Aug 2023)
@@ -102,6 +101,7 @@ def get_data_sync_maps_and_data_sync_world(veh_name):
102101
bag = rosbag.Bag(bag_text, "r")
103102

104103
veh_names = ["SQ01s", "SQ02s"]
104+
# veh_names = ["SQ01s"]
105105
data_sync_world = {}
106106
data_sync_maps = {}
107107
for veh_name in veh_names:
@@ -111,20 +111,28 @@ def get_data_sync_maps_and_data_sync_world(veh_name):
111111
bag.close()
112112

113113
# make sure the data has the same length
114-
shorter_length = min(len(data_sync_maps[veh_names[0]]), len(data_sync_maps[veh_names[1]]))
115-
for veh_name in veh_names:
116-
data_sync_maps[veh_name] = data_sync_maps[veh_name][:shorter_length]
117-
data_sync_world[veh_name] = data_sync_world[veh_name][:shorter_length]
114+
if len(veh_names) > 1:
115+
shorter_length = min(len(data_sync_maps[veh_names[0]]), len(data_sync_maps[veh_names[1]]))
116+
for veh_name in veh_names:
117+
data_sync_maps[veh_name] = data_sync_maps[veh_name][:shorter_length]
118+
data_sync_world[veh_name] = data_sync_world[veh_name][:shorter_length]
119+
120+
# get the last 5 data points
121+
data_sync_maps[veh_name] = data_sync_maps[veh_name][-5:]
122+
data_sync_world[veh_name] = data_sync_world[veh_name][-5:]
123+
118124

119125
# plot the data
120126
fig, ax = plt.subplots()
127+
121128
# hardcoded for now
122129
world0 = ax.scatter(data_sync_world[veh_names[0]][0].pose.position.x, data_sync_world[veh_names[0]][0].pose.position.y, label=f'vehicle1', color='orange', marker='s')
123130
maps0 = ax.scatter(data_sync_maps[veh_names[0]][0][0], data_sync_maps[veh_names[0]][0][1], label=f'map1', color='orange', marker='s')
124131
line0, = ax.plot(data_sync_world[veh_names[0]][0].pose.position.x, data_sync_world[veh_names[0]][0].pose.position.y, label=f'path1', color='orange', alpha=0.3)
125-
world1 = ax.scatter(data_sync_world[veh_names[1]][0].pose.position.x, data_sync_world[veh_names[1]][0].pose.position.y, label=f'vehicle2', color='red')
126-
maps1 = ax.scatter(data_sync_maps[veh_names[1]][0][0], data_sync_maps[veh_names[1]][0][1], label=f'map2', color='red')
127-
line1, = ax.plot(data_sync_world[veh_names[1]][0].pose.position.x, data_sync_world[veh_names[1]][0].pose.position.y, label=f'path2', color='red', alpha=0.3)
132+
if len(veh_names) > 1:
133+
world1 = ax.scatter(data_sync_world[veh_names[1]][0].pose.position.x, data_sync_world[veh_names[1]][0].pose.position.y, label=f'vehicle2', color='red')
134+
maps1 = ax.scatter(data_sync_maps[veh_names[1]][0][0], data_sync_maps[veh_names[1]][0][1], label=f'map2', color='red')
135+
line1, = ax.plot(data_sync_world[veh_names[1]][0].pose.position.x, data_sync_world[veh_names[1]][0].pose.position.y, label=f'path2', color='red', alpha=0.3)
128136
objects = ax.scatter([x[0] for x in object_gt], [x[1] for x in object_gt], c='g', marker='x', label=f'objects')
129137
ax.set(xlim=[-6, 6], ylim=[-6, 6], xlabel='x [m]', ylabel='y [m]')
130138
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
@@ -145,10 +153,11 @@ def update(frame):
145153
world0.set_offsets(data)
146154

147155
# agent 1
148-
x = data_sync_world[veh_names[1]][frame].pose.position.x
149-
y = data_sync_world[veh_names[1]][frame].pose.position.y
150-
data = np.stack([x, y]).T
151-
world1.set_offsets(data)
156+
if len(veh_names) > 1:
157+
x = data_sync_world[veh_names[1]][frame].pose.position.x
158+
y = data_sync_world[veh_names[1]][frame].pose.position.y
159+
data = np.stack([x, y]).T
160+
world1.set_offsets(data)
152161

153162
# update the line plot:
154163
# agent 0
@@ -158,10 +167,11 @@ def update(frame):
158167
maps0.set_offsets(data_map)
159168

160169
# agent 1
161-
x_map = data_sync_maps[veh_names[1]][frame][0]
162-
y_map = data_sync_maps[veh_names[1]][frame][1]
163-
data_map = np.stack([x_map, y_map]).T
164-
maps1.set_offsets(data_map)
170+
if len(veh_names) > 1:
171+
x_map = data_sync_maps[veh_names[1]][frame][0]
172+
y_map = data_sync_maps[veh_names[1]][frame][1]
173+
data_map = np.stack([x_map, y_map]).T
174+
maps1.set_offsets(data_map)
165175

166176
# plot history of path
167177
# agent 0
@@ -170,13 +180,17 @@ def update(frame):
170180
line0.set_data(x_line0, y_line0)
171181

172182
# agent 1
173-
x_line1.append(data_sync_world[veh_names[1]][frame].pose.position.x)
174-
y_line1.append(data_sync_world[veh_names[1]][frame].pose.position.y)
175-
line1.set_data(x_line1, y_line1)
183+
if len(veh_names) > 1:
184+
x_line1.append(data_sync_world[veh_names[1]][frame].pose.position.x)
185+
y_line1.append(data_sync_world[veh_names[1]][frame].pose.position.y)
186+
line1.set_data(x_line1, y_line1)
176187

177-
return world0, maps0, line0, world1, maps1, line1
188+
if len(veh_names) > 1:
189+
return world0, maps0, line0, world1, maps1, line1
190+
else:
191+
return world0, maps0, line0
178192

179193
animation_text = 'animation_' + bag_text.split('/')[-1][4:-4] + '.gif'
180194
ani = animation.FuncAnimation(fig=fig, func=update, frames=len(data_sync_world[veh_names[0]]), interval=300, blit=True)
181-
ani.save(os.path.join(args.output_dir, animation_text), writer='imagemagick')
195+
ani.save(os.path.join(args.bag_folder, animation_text), writer='imagemagick')
182196
# plt.show()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/usr/bin/env python
2+
# Author: Kota Kondo
3+
# example use: python plot_animation.py /media/kota/T7/frame/real_time_fastsam_exp/run.bag NX04 /media/kota/T7/frame/real_time_fastsam_exp
4+
5+
import os
6+
import argparse
7+
import rosbag
8+
import rospy
9+
from scipy.spatial.transform import Rotation as R
10+
import numpy as np
11+
import matplotlib.pyplot as plt
12+
from itertools import permutations, combinations
13+
from tf_bag import BagTfTransformer
14+
from scipy.interpolate import make_interp_spline
15+
16+
def smooth_data(x, y):
17+
X_Y_Spline = make_interp_spline(x, y)
18+
X_ = np.linspace(min(x), max(x), 10)
19+
Y_ = X_Y_Spline(X_)
20+
return X_, Y_
21+
22+
def get_transformation(bag, veh_pair):
23+
24+
# ego name
25+
ego_name = veh_pair[0]
26+
# other name
27+
other_name = veh_pair[1]
28+
29+
# topic name
30+
tpn_frame_align = f'/{ego_name}/frame_align'
31+
32+
# get euler angles from data_frame_align (transofrmation matrix from ego to other)
33+
euler_frame_align = []
34+
offsets_frame_align = []
35+
t_frame_align = []
36+
for topic, msg, t in bag.read_messages(topics=[tpn_frame_align]):
37+
if topic == tpn_frame_align and msg.frame_src == other_name and msg.frame_dest == ego_name:
38+
t_frame_align.append(t.to_sec())
39+
rotation_matrix = [[msg.transform[0], msg.transform[1], msg.transform[2]], [msg.transform[4], msg.transform[5], msg.transform[6]], [msg.transform[8], msg.transform[9], msg.transform[10]]]
40+
r = R.from_matrix(rotation_matrix)
41+
euler_frame_align.append(r.as_euler('xyz', degrees=True))
42+
offsets_frame_align.append([msg.transform[3], msg.transform[7], msg.transform[11]])
43+
44+
return euler_frame_align, offsets_frame_align, t_frame_align
45+
46+
def get_ground_truth_transformation(bag, frame_name, t_frame_align):
47+
48+
# get bag's start and end time
49+
bag_start_time = bag.get_start_time()
50+
bag_end_time = bag.get_end_time()
51+
time_discreted = np.linspace(bag_start_time, bag_end_time, len(t_frame_align)*5)
52+
53+
# bag transformer
54+
bag_transformer = BagTfTransformer(bag)
55+
56+
# get euler_actual_drift and offsets_actual_drift
57+
euler_actual_drift = []
58+
offsets_actual_drift = []
59+
for t in time_discreted:
60+
translation, quaternion = bag_transformer.lookupTransform("world", frame_name, rospy.Time.from_sec(t))
61+
r = R.from_quat(quaternion)
62+
euler_actual_drift.append(r.as_euler('xyz', degrees=True))
63+
offsets_actual_drift.append(translation)
64+
65+
return euler_actual_drift, offsets_actual_drift, time_discreted
66+
67+
def get_drift(bag, veh_name):
68+
69+
# topic name
70+
tpn_drift = f'/{veh_name}/drift'
71+
72+
# get drift from data_drift
73+
drift = []
74+
t_drift = []
75+
for topic, msg, t in bag.read_messages(topics=[tpn_drift]):
76+
if topic == tpn_drift:
77+
t_drift.append(t.to_sec())
78+
drift.append(msg)
79+
80+
return drift, t_drift
81+
82+
def main():
83+
84+
# data extraction from bag file
85+
# Parse command line arguments
86+
parser = argparse.ArgumentParser(description="Extract images from a ROS bag.")
87+
parser.add_argument("--bag_folder", help="Input directory.")
88+
args = parser.parse_args()
89+
VEH_NAMES = ["SQ01s", "SQ02s"]
90+
91+
# get bags from input directory
92+
bags = []
93+
for file in os.listdir(args.bag_folder):
94+
if file.endswith(".bag"):
95+
bags.append(os.path.join(args.bag_folder, file))
96+
97+
# sort bags by time
98+
bags.sort()
99+
100+
# for loop
101+
for bag_text in bags:
102+
103+
# open the bag
104+
bag = rosbag.Bag(bag_text, "r")
105+
106+
# get permutations of veh_names
107+
# veh_name_permutations = permutations(VEH_NAMES)
108+
109+
# get combinations of veh_names
110+
veh_name_combinations = combinations(VEH_NAMES,2)
111+
112+
euler_frame_align = []
113+
offsets_frame_align = []
114+
t_frame_align = []
115+
for veh_pair in veh_name_combinations:
116+
euler_frame_align, offsets_frame_align, t_frame_align = get_transformation(bag, veh_pair)
117+
118+
# get the actual transformatoin between the two vehicles
119+
# note: not sure why but directly comparing SQ01s/corrupted_world to SQ02s/corrupted_world by lookupTransform doesn't work
120+
# note: so just get each vehicle's transformation to the world and then subtract them
121+
# note: maybe the order of "world" and frame_name in lookupTransform matters?
122+
# agent 1
123+
# euler_gt1, offsets_gt1, t_actual = get_ground_truth_transformation(bag, VEH_NAMES[0], t_frame_align)
124+
# euler_corrupted1, offsets_corrupted1, _ = get_ground_truth_transformation(bag, VEH_NAMES[0]+"/corrupted_world", t_frame_align)
125+
126+
# euler_actual_drift1 = []
127+
# for i in range(len(euler_gt1)):
128+
# euler_actual_drift1.append(np.array(euler_corrupted1[i]) - np.array(euler_gt1[i]))
129+
130+
# offsets_actual_drift1 = []
131+
# for i in range(len(offsets_gt1)):
132+
# offsets_actual_drift1.append(np.array(offsets_corrupted1[i]) - np.array(offsets_gt1[i]))
133+
134+
# # agent 2
135+
# euler_gt2, offsets_gt2, _ = get_ground_truth_transformation(bag, VEH_NAMES[1], t_frame_align)
136+
# euler_corrupted2, offsets_corrupted2, _ = get_ground_truth_transformation(bag, VEH_NAMES[1]+"/corrupted_world", t_frame_align)
137+
138+
# euler_actual_drift2 = []
139+
# for i in range(len(euler_gt2)):
140+
# euler_actual_drift2.append(np.array(euler_corrupted2[i]) - np.array(euler_gt2[i]))
141+
142+
# offsets_actual_drift2 = []
143+
# for i in range(len(offsets_gt2)):
144+
# offsets_actual_drift2.append(np.array(offsets_corrupted2[i]) - np.array(offsets_gt2[i]))
145+
146+
# # subtract the two
147+
# euler_actual_drift = []
148+
# for i in range(len(euler_actual_drift1)):
149+
# euler_actual_drift.append(np.array(euler_actual_drift1[i]) - np.array(euler_actual_drift2[i]))
150+
# offsets_actual_drift = []
151+
# for i in range(len(offsets_actual_drift1)):
152+
# offsets_actual_drift.append(np.array(offsets_actual_drift1[i]) - np.array(offsets_actual_drift2[i]))
153+
154+
# get drift
155+
drift, t_plot = get_drift(bag, VEH_NAMES[0])
156+
# get euler_actual_drift and offsets_actual_drift
157+
euler_actual_drift_roll = []
158+
euler_actual_drift_pitch = []
159+
euler_actual_drift_yaw = []
160+
offsets_actual_drift_x = []
161+
offsets_actual_drift_y = []
162+
for i in range(len(drift)):
163+
euler_actual_drift_roll.append(np.rad2deg(drift[i].drift_euler[0]))
164+
euler_actual_drift_pitch.append(np.rad2deg(drift[i].drift_euler[1]))
165+
euler_actual_drift_yaw.append(np.rad2deg(drift[i].drift_euler[2]))
166+
offsets_actual_drift_x.append(drift[i].drift_pos[0])
167+
offsets_actual_drift_y.append(drift[i].drift_pos[1])
168+
169+
# close the bag
170+
bag.close()
171+
172+
# sometimes the artificially introduced drift has a little spikes in it (im guessing due to comm delay) so just smooth out the data
173+
# t_plot, euler_actual_drift_roll = smooth_data(t_actual, np.array(euler_actual_drift)[:, 0])
174+
# t_plot, euler_actual_drift_pitch = smooth_data(t_actual, np.array(euler_actual_drift)[:, 1])
175+
# t_plot, euler_actual_drift_yaw = smooth_data(t_actual, np.array(euler_actual_drift)[:, 2])
176+
# t_plot, offsets_actual_drift_x = smooth_data(t_actual, np.array(offsets_actual_drift)[:, 0])
177+
# t_plot, offsets_actual_drift_y = smooth_data(t_actual, np.array(offsets_actual_drift)[:, 1])
178+
179+
# plot
180+
fig, axs = plt.subplots(2, 1, figsize=(10, 10))
181+
axs[0].plot(t_frame_align, np.array(euler_frame_align)[:, 0], label='pred roll drift')
182+
axs[0].plot(t_frame_align, np.array(euler_frame_align)[:, 1], label='pred pitch drift')
183+
axs[0].plot(t_frame_align, np.array(euler_frame_align)[:, 2], label='pred yaw drift')
184+
axs[0].plot(t_plot, euler_actual_drift_roll, label='actual roll drift')
185+
axs[0].plot(t_plot, euler_actual_drift_pitch, label='actual pitch drift')
186+
axs[0].plot(t_plot, euler_actual_drift_yaw, label='actual yaw drift')
187+
axs[0].set_xlabel('time [s]')
188+
axs[0].set_ylabel('euler angles [deg]')
189+
axs[0].legend()
190+
axs[0].grid()
191+
axs[1].plot(t_frame_align, np.array(offsets_frame_align)[:, 0], label='pred x drift')
192+
axs[1].plot(t_frame_align, np.array(offsets_frame_align)[:, 1], label='pred y drift')
193+
# axs[1].plot(t_frame_align, np.array(offsets_frame_align)[:, 2], label='pred z drift')
194+
axs[1].plot(t_plot, offsets_actual_drift_x, label='actual x drift')
195+
axs[1].plot(t_plot, offsets_actual_drift_y, label='actual y drift')
196+
# axs[1].plot(t_frame_align, np.array(offsets_actual_drift)[:, 2], label='actual z drift')
197+
axs[1].set_xlabel('time [s]')
198+
axs[1].set_ylabel('offsets [m]')
199+
axs[1].legend()
200+
axs[1].grid()
201+
plt.tight_layout()
202+
plt.savefig(os.path.join(args.bag_folder, os.path.splitext(os.path.basename(bag_text))[0] + '.png'))
203+
plt.show()
204+
205+
if __name__ == '__main__':
206+
main()

0 commit comments

Comments
 (0)