evaluate_ate.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import numpy as np
  2. import csv
  3. import re
  4. import argparse
  5. import sys
  6. comment_pattern = re.compile(r'\s*#.*$')
  7. class Position:
  8. def __init__(self, timestamp, pos=None, rot_quat=None):
  9. self.timestamp = timestamp
  10. self.is_kf = False
  11. if pos is None and rot_quat is None:
  12. self.exist = False
  13. self.pos = pos # x,y,z
  14. self.rot_quat = rot_quat # w,x,y,z
  15. else:
  16. self.exist = True
  17. self.pos = pos # x,y,z
  18. self.rot_quat = rot_quat # w,x,y,z
  19. class Trajectory:
  20. def __init__(self, lst_poses=None):
  21. if lst_poses is not None:
  22. self.num_poses = len(lst_poses)
  23. self.timestamps = np.zeros((self.num_poses, 1))
  24. self.pos = np.zeros((self.num_poses, 3))
  25. self.q = np.zeros((self.num_poses, 4))
  26. for i in range(0, self.num_poses):
  27. self.timestamps[i] = lst_poses[i].timestamp
  28. if lst_poses[i].rot_quat is not None:
  29. for j in range(0, 4):
  30. self.q[i][j] = lst_poses[i].rot_quat[j]
  31. for j in range(0, 3):
  32. self.pos[i][j] = lst_poses[i].pos[j]
  33. # self.pos = np.transpose(self.pos)
  34. self.pos = np.transpose(self.pos)
  35. self.q = np.transpose(self.q)
  36. def load_file(path_file_name: str, delimiter=" "):
  37. lst_poses = []
  38. with open(path_file_name) as file:
  39. file = skip_comments(file)
  40. for line in file:
  41. vector = line.split(delimiter)
  42. # print(len(vector))
  43. if len(vector) == 8:
  44. # Correct position
  45. timestamp = float(vector[0])
  46. pos = [float(vector[1]), float(vector[2]), float(vector[3])]
  47. rot_quat = [float(vector[7]), float(vector[4]), float(vector[5]), float(vector[6])]
  48. pose = Position(timestamp, pos, rot_quat)
  49. lst_poses.append(pose)
  50. else:
  51. # We haven't got a position in this
  52. timestamp = float(vector[0])
  53. lst_poses.append(Position(timestamp))
  54. return lst_poses
  55. def associate(poses_test, poses_gt):
  56. num_match = 0
  57. i_test = 0
  58. i_gt = 0
  59. lst_check_test = []
  60. lst_check_gt = []
  61. while i_test < len(poses_test):
  62. if i_gt == len(poses_gt):
  63. break
  64. lst_check_test.append(poses_test[i_test])
  65. lst_check_gt.append(poses_gt[i_gt-1])
  66. num_match = num_match + 1
  67. i_test = i_test + 1
  68. elif np.abs(poses_test[i_test].timestamp - poses_gt[i_gt].timestamp)/1e3 < 1:
  69. lst_check_test.append(poses_test[i_test])
  70. lst_check_gt.append(poses_gt[i_gt])
  71. num_match = num_match + 1
  72. i_test = i_test + 1
  73. i_gt = i_gt + 1
  74. elif poses_test[i_test].timestamp < poses_gt[i_gt].timestamp:
  75. i_test = i_test + 1
  76. if i_test >= len(poses_test):
  77. break
  78. elif poses_test[i_test].timestamp > poses_gt[i_gt].timestamp:
  79. i_gt = i_gt + 1
  80. if i_gt >= len(poses_gt):
  81. break
  82. return lst_check_test, lst_check_gt
  83. def create_trajectory(lst_poses_test, lst_poses_gt):
  84. lst_exist_poses_test = []
  85. lst_exist_poses_gt = []
  86. for i in range(len(lst_poses_test)):
  87. if lst_poses_test[i].exist:
  88. lst_exist_poses_test.append(lst_poses_test[i])
  89. pose_gt = lst_poses_gt[i]
  90. lst_exist_poses_gt.append(pose_gt)
  91. traj_test = Trajectory(lst_exist_poses_test)
  92. traj_gt = Trajectory(lst_exist_poses_gt)
  93. return traj_test, traj_gt
  94. def skip_comments(lines):
  95. """
  96. A filter which skip/strip the comments and yield the
  97. rest of the lines
  98. :param lines: any object which we can iterate through such as a file
  99. object, list, tuple, or generator
  100. """
  101. global comment_pattern
  102. for line in lines:
  103. line = re.sub(comment_pattern, '', line).strip()
  104. if line:
  105. yield line
  106. def align(traj_test, traj_gt):
  107. mean_test = traj_test.pos.mean(1)
  108. mean_test = mean_test.reshape(mean_test.size, 1)
  109. mean_gt = traj_gt.pos.mean(1)
  110. mean_gt = mean_gt.reshape(mean_gt.size, 1)
  111. traj_centered_test = Trajectory()
  112. traj_centered_gt = Trajectory()
  113. traj_centered_test.pos = traj_test.pos - mean_test
  114. traj_centered_gt.pos = traj_gt.pos - mean_gt
  115. L = traj_centered_test.pos.shape[1]
  116. W = np.zeros((3, 3))
  117. for col in range(traj_centered_test.pos.shape[1]):
  118. W += np.outer(traj_centered_test.pos[:, col], traj_centered_gt.pos[:, col])
  119. U, d, Vh = np.linalg.linalg.svd(W.transpose())
  120. S = np.matrix(np.identity(3))
  121. if np.linalg.det(U) * np.linalg.det(Vh) < 0:
  122. # print("Negative")
  123. S[2, 2] = -1
  124. Rot = U * S * Vh
  125. # pose_rot_cent_test = np.matmul(Rot, traj_centered_test.pos)
  126. pose_rot_cent_test = Rot * np.matrix(traj_centered_test.pos)
  127. # print('Size center: {}'.format(pose_rot_cent_test.shape))
  128. # print('Size center2: {}'.format((Rot * np.matrix(traj_centered_test.pos)).shape))
  129. s = 1.0
  130. dots = 0.0
  131. norms = 0.0
  132. for column in range(traj_centered_gt.pos.shape[1]):
  133. dots += np.dot(traj_centered_gt.pos[:, column].transpose(), pose_rot_cent_test[:, column])
  134. normi = np.linalg.norm(traj_centered_test.pos[:, column])
  135. norms += normi * normi
  136. s = float(dots / norms)
  137. traj_length = 0
  138. for i in range(1, traj_centered_gt.pos.shape[1]):
  139. # print("Pos = ", _trajData.pos[:, i])
  140. traj_length = traj_length + np.linalg.norm(traj_centered_gt.pos[:, i] - traj_centered_gt.pos[:, i - 1])
  141. # print("Rotation matrix between model and GT: \n", Rot)
  142. trans_scale = mean_gt - s * np.matmul(Rot, mean_test)
  143. traj_aligned_scale = Trajectory()
  144. traj_aligned_scale.pos = s * Rot * np.matrix(traj_test.pos) + trans_scale
  145. trans = mean_gt - np.matmul(Rot, mean_test)
  146. traj_aligned = Trajectory()
  147. traj_aligned.pos = Rot * np.matrix(traj_test.pos) + trans
  148. # np.reshape(_traj_test.pos, (3, L))
  149. # print(_traj_test.pos.shape)
  150. error_traj_scale = traj_aligned_scale.pos - traj_gt.pos
  151. error_traj = traj_aligned.pos - traj_gt.pos
  152. ate_scale = np.squeeze(np.asarray(np.sqrt(np.sum(np.multiply(error_traj_scale, error_traj_scale), 0))))
  153. ateRMSE_scale = np.sqrt(np.dot(ate_scale, ate_scale) / len(ate_scale))
  154. ate = np.squeeze(np.asarray(np.sqrt(np.sum(np.multiply(error_traj, error_traj), 0))))
  155. ateRMSE = np.sqrt(np.dot(ate, ate) / len(ate))
  156. # Print the error
  157. print("ATE RMSE(7DoF): " + str(ateRMSE_scale))
  158. print("scale: %f " % s)
  159. print("ATE RMSE(6DoF): " + str(ateRMSE))
  160. if __name__ == '__main__':
  161. parser = argparse.ArgumentParser(description='''
  162. This script computes the absolute trajectory error from the ground truth trajectory and the estimated trajectory.
  163. ''')
  164. parser.add_argument('SLAM', help='estimated trajectory (format: timestamp tx ty tz qx qy qz qw)')
  165. parser.add_argument('GT', help='ground truth trajectory (format: timestamp tx ty tz qx qy qz qw)')
  166. args = parser.parse_args()
  167. str_file_gt = args.GT
  168. str_file_traj = args.SLAM
  169. lst_gt_poses = load_file(str_file_gt, ",")
  170. lst_traj_poses = load_file(str_file_traj, " ")
  171. lst_traj_poses2, lst_gt_poses2 = associate(lst_traj_poses, lst_gt_poses)
  172. traj_test, traj_gt = create_trajectory(lst_traj_poses2, lst_gt_poses2)
  173. align(traj_test, traj_gt)