tracking.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. """
  2. Offline Tracking Simulator for the AGV.
  3. """
  4. # 1. Run the simulator normally until end of trace duration, using the csv-logs
  5. # - [TEST 1] Use the 'true' times for the values
  6. # - [TEST 2] Use the 'arrived_at' times for the values
  7. # - Passively obtain the system state (pseudo-checkpointing)
  8. # - PID component outputs
  9. # - odometry output [can be stored in a file for future reference]
  10. # 2. Whenever the error becomes too large (at time t)
  11. # - Terminate Simulator (termination function)
  12. # - Find last "good" system state
  13. # - store all past states (before the "good" state)
  14. # - Alter model variables w.r.t. last "good" system state (at time t-dt)
  15. # - Clear all collection units in the model
  16. # - Find new parameter values to ensure the correct position at time t
  17. # - Log/plot the parameter values
  18. # - Restart the simulation (back to 1.)
  19. import numpy as np
  20. import pandas as pd
  21. from CBD.simulator import Simulator
  22. from AGV import AGVVirtual
  23. import cv2
  24. from tqdm import tqdm
  25. import matplotlib.pyplot as plt
  26. EPS = 0.00001
  27. ERROR = 0.1
  28. WERR = np.pi / 2
  29. class TrackingSimulator:
  30. def __init__(self):
  31. self.model = None
  32. self.sim = None
  33. self.dt = 0.2
  34. self.setup()
  35. self.physical = pd.read_csv("trace_vidH.csv") # Trajectory of the recognized AGV
  36. self.physical["heading"] *= -1
  37. self.clean_path()
  38. self.set_state([self.physical["x"][0], self.physical["y"][0], self.physical["heading"][0]])
  39. self.path = list(zip(self.physical["x"], self.physical["y"], self.physical["heading"]))
  40. self.trace = []
  41. self.traces = []
  42. self.__offset_i = 1
  43. self.params = {0.0: (0.018, 0.211)}
  44. def setup(self):
  45. self.model = AGVVirtual("AGV", 0.018, 0.211, "obtained.csv", v=0.033, T=35, Kp=-0.01)
  46. self.sim = Simulator(self.model)
  47. self.sim.setDeltaT(self.dt)
  48. self.sim.setTerminationCondition(self.check)
  49. def check(self, model, curIt):
  50. x, y, w = self.get_state()
  51. time = self.sim.getTime()
  52. rtime = self.sim.getRelativeTime()
  53. off, a = self.offset2(time)
  54. if off > ERROR:
  55. return True
  56. if abs(round(rtime*1000) % round(self.dt*1000)) < EPS:
  57. self.trace.append((time, self.__offset_i, self.get_state(), off, self.get_params()))
  58. return False
  59. def run(self):
  60. TCP = self.model.getBlockByName("TCP")
  61. time = self.physical["time"][0]
  62. end_time = TCP.data[TCP.time_col][-1]
  63. try:
  64. back = None
  65. for i in tqdm(range(100)):
  66. if back is None:
  67. self.sim.run(end_time, time)
  68. back = self.sim.getTime()
  69. if len(self.traces) == 0 or len(self.traces[-1]) > 1:
  70. self.traces.append(self.trace[:])
  71. self.trace.clear()
  72. if back < end_time:
  73. # time, self.__offset_i, state, _, (r, d) = self.last_good_state()
  74. # if state is None: break
  75. now = back - 1.0
  76. current = self.get_state()
  77. if np.isnan(current[0]):
  78. print("WHY???!!!")
  79. target = self.get_target_state(now)
  80. self.setup()
  81. self.set_state(target)
  82. time = now
  83. # print(target[2])
  84. back = None
  85. # w_bad = current[2] - state[2]
  86. # w_good = target[2] - state[2]
  87. # if w_bad > w_good:
  88. # d *= 2
  89. # elif w_bad < w_good:
  90. # d /= 4
  91. # else:
  92. # v_bad = self.euclidean(state[0], state[1], current[0], current[1])
  93. # v_good = self.euclidean(state[0], state[1], target[0], target[1])
  94. # if v_bad < v_good:
  95. # r *= 2
  96. # else:
  97. # r /= 4
  98. # # TODO: what with "too slow" on a straight segment --> divide by zero!
  99. # # Given the trajectory, compute the wheel speeds
  100. # w = current[2] - state[2]
  101. # if np.isnan(w) or abs(w) < EPS:
  102. # v = self.euclidean(state[0], state[1], current[0], current[1])
  103. # else:
  104. # v = self.circle_radius(state, current) * abs(w)
  105. # phiL = (v - w * d / 2) / r
  106. # phiR = (v + w * d / 2) / r
  107. #
  108. # if phiL == 0 or phiR == 0 or phiL == -phiR:
  109. # print("HO! SHTAHP")
  110. # back = time
  111. # continue
  112. #
  113. # # Given the wheel speeds, compute r and d
  114. # w = target[2] - state[2]
  115. # if np.isnan(w) or abs(w) < EPS:
  116. # v = self.euclidean(state[0], state[1], target[0], target[1])
  117. # else:
  118. # v = self.circle_radius(state, target) * abs(w)
  119. # d2 = 2 * v / w * (phiR - phiL) / (phiR + phiL)
  120. # r2 = 1. / phiL * (v - w * d / 2)
  121. #
  122. # print(d2, r2)
  123. #
  124. # if np.isnan(w) or np.isnan(v) or np.isnan(r2) or np.isnan(d2) or np.isnan(phiL) or np.isnan(phiR) or np.isinf(d2) or np.isinf(r2):
  125. # print("HERE")
  126. # self.set_params(r, d)
  127. #
  128. # self.params[now] = r, d
  129. else:
  130. break
  131. finally:
  132. trd = self.params.items()
  133. t, r, d = [x for x, _ in trd], [x for _, (x, __) in trd], [x for _, (__, x) in trd]
  134. df = pd.DataFrame({"time": t, "r": r, "d": d})
  135. df.to_csv("tracking.csv")
  136. def clean_path(self):
  137. to_drop = []
  138. # dists = []
  139. for idx, row in self.physical.iterrows():
  140. subset = self.physical[self.physical["time"] <= row["time"] - 0.2]
  141. if len(subset) == 0: continue
  142. prev = subset.iloc[-1]
  143. dist = self.euclidean(prev["x"], prev["y"], row["x"], row["y"])
  144. # dists.append(dist)
  145. # REMOVE NOISE
  146. if dist > 0.0125:
  147. to_drop.append(idx)
  148. self.physical.drop(to_drop, inplace=True)
  149. # fig, ax = plt.subplots(1, 1)
  150. # r = ax.boxplot(dists)
  151. # fig.show()
  152. # print(sorted(dists))
  153. def get_state(self):
  154. dd = self.model.getBlockByName("plot").data
  155. if len(dd) == 0:
  156. x = self.model.findBlock("odo.init_x")[0].getValue()
  157. y = self.model.findBlock("odo.init_y")[0].getValue()
  158. heading = self.model.findBlock("odo.init_w")[0].getValue()
  159. else:
  160. x, y = self.model.getBlockByName("plot").data[-1]
  161. heading = self.model.getBlockByName("headingPlot").data_xy[1][-1]
  162. return x, y, heading
  163. def get_target_state(self, time):
  164. obj = self.physical[self.physical["time"] <= time].iloc[-1]
  165. return obj["x"], obj["y"], obj["heading"]
  166. def set_state(self, state):
  167. x, y, heading = state
  168. self.model.findBlock("odo.init_x")[0].setValue(x)
  169. self.model.findBlock("odo.init_y")[0].setValue(y)
  170. self.model.findBlock("odo.init_w")[0].setValue(heading)
  171. def get_params(self):
  172. d = self.model.findBlock("StaticPlant.WheelAxis")[0].getValue()
  173. r = 1. / self.model.findBlock("StaticPlant.WheelRadius")[0].getValue()
  174. return r, d
  175. def set_params(self, r, d):
  176. self.model.findBlock("StaticPlant.WheelAxis")[0].setValue(d)
  177. self.model.findBlock("StaticPlant.WheelRadius")[0].setValue(1./r)
  178. def last_good_state(self):
  179. if len(self.traces) >= 1:
  180. if len(self.traces[-1]) >= 1:
  181. if len(self.traces[-1]) > 20:
  182. return self.traces[-1][-20]
  183. elif len(self.traces[-1]) > 10:
  184. return self.traces[-1][-10]
  185. elif len(self.traces[-1]) > 5:
  186. return self.traces[-1][-5]
  187. else:
  188. self.traces.pop()
  189. return self.traces[-1][0]
  190. elif len(self.traces) >= 2 and len(self.traces[-2]) >= 1:
  191. return self.traces[-2][0]
  192. return 0.0, -1, None, 0, (0, 0)
  193. def offset(self):
  194. position = self.get_state()
  195. dist = float("inf")
  196. angles = float("-inf"), float("inf")
  197. self.__offset_i -= 1
  198. for p1 in self.path:
  199. p2 = self.path[self.__offset_i-1]
  200. d = self.distance_to_line(p2, p1, position)
  201. if d < dist:
  202. dist, angles = d, (p1[2], p2[2])
  203. if dist < ERROR:
  204. break
  205. self.__offset_i += 1
  206. self.__offset_i %= len(self.path)
  207. return dist, angles
  208. def offset2(self, ctime):
  209. moment = self.physical[self.physical["time"] <= ctime].iloc[-1]
  210. position = self.get_state()
  211. return self.euclidean(moment["x"], moment["y"], position[0], position[1]), \
  212. (moment["heading"] - np.pi/6, moment["heading"] + np.pi/6)
  213. def distance_to_line(self, p1, p2, p3):
  214. """
  215. Computes the distance from a point p3 to the line segment given by (p1, p2).
  216. Implementation based upon:
  217. https://stackoverflow.com/a/6853926
  218. """
  219. px = p2[0] - p1[0]
  220. py = p2[1] - p1[1]
  221. norm = px * px + py * py
  222. if norm == 0.0:
  223. ud = 0.0
  224. else:
  225. ud = float((p3[0] - p1[0]) * px + (p3[1] - p1[1]) * py) / norm
  226. ud = min(max(ud, 0.0), 1.0) # limits line segment to endpoints
  227. xd = p1[0] + ud * px
  228. yd = p1[1] + ud * py
  229. return self.euclidean(p3[0], p3[1], xd, yd)
  230. @staticmethod
  231. def euclidean(x1, y1, x2, y2):
  232. dx = x2 - x1
  233. dy = y2 - y1
  234. return ((dx * dx) + (dy * dy)) ** 0.5
  235. def circle_radius(self, A, B):
  236. cot = lambda x: 1. / np.tan(x)
  237. x = (B[1] - A[1] + cot(B[2]) * B[0] - cot(A[2]) * A[0]) / (cot(B[2]) - cot(A[2]))
  238. y = -cot(A[2]) * (x - A[0]) + A[1]
  239. return self.euclidean(x, y, A[0], A[1])
  240. if __name__ == '__main__':
  241. ts = TrackingSimulator()
  242. ts.run()
  243. fig = plt.figure()
  244. ax = fig.add_subplot(111, projection='3d')
  245. ax.set_xlabel("X (m)")
  246. ax.set_ylabel("Y (m)")
  247. ax.set_zlabel("Time (s)")
  248. ax.plot(ts.physical["x"], ts.physical["y"], ts.physical["time"], label="physical", ls=':')
  249. for trace in ts.traces:
  250. if len(trace) < 5: continue
  251. r, d = trace[-1][4]
  252. times = np.asarray(trace)[:,0]
  253. ax.plot([x[2][0] for x in trace], [x[2][1] for x in trace], times, label=f"r = {r:.2f}; d = {d:.2f}")
  254. # ax.legend()
  255. plt.tight_layout()
  256. plt.savefig("tracking.png")
  257. plt.show()
  258. # fig, axs = plt.subplots(3, 3)
  259. # axs[0].set_xlabel("X (m)")
  260. # axs[0].set_ylabel("Y (m)")
  261. # axs[1].set_xlabel("Time (s)")
  262. # # ax.plot(ts.physical["x"], ts.physical["y"], ts.physical["time"], label="physical", ls=':')
  263. # # for trace in ts.traces:
  264. # # if len(trace) < 5: continue
  265. # # r, d = trace[-1][4]
  266. # # times = np.asarray(trace)[:,0]
  267. # # ax.plot([x[2][0] for x in trace], [x[2][1] for x in trace], times, label=f"r = {r:.2f}; d = {d:.2f}")
  268. # # # ax.legend()
  269. # # plt.savefig("tracking.png")
  270. # plt.show()