ProbMap.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import numpy as np
  4. import logging
  5. """
  6. Tracking implementation for the perimeter monitoring problem
  7. Implementations
  8. ---------------
  9. 1. ProbMap := Probability map for estimating targets position
  10. References
  11. ----------
  12. [1] Hu, J.; Xie, L.; Lum, K.Y.; Xu, J. Multiagent Information Fusion and
  13. Cooperative Control in Target Search. IEEE Trans. Control Syst. Technol.
  14. 2013, 21, 1223–1235.
  15. """
  16. class ProbMap:
  17. def __init__(self, width_meter, height_meter, resolution,
  18. center_x, center_y, init_val=0.01, false_alarm_prob=0.05):
  19. """Generate a probability map
  20. Args:
  21. width_meter (int): width of the area [m]
  22. height_meter (int): height of the area [m]
  23. resolution (float): grid resolution [m]
  24. center_x (float): center x position [m]
  25. center_y (float): center y position [m]
  26. init_val (float, optional): Initial value for all cells. Defaults to 0.01.
  27. false_alarm_prob (float, optional): False alarm probability of the detector. Defaults to 0.05.
  28. """
  29. # TODO make this grid map unlimited, deprecate the width and height params
  30. # number of cells for width
  31. self.width = int(np.ceil(width_meter / resolution))
  32. # number of cells for height
  33. self.height = int(np.ceil(height_meter / resolution))
  34. self.resolution = resolution
  35. self.center_x = center_x
  36. self.center_y = center_y
  37. self.init_val = init_val
  38. self.false_alarm_prob = false_alarm_prob
  39. self._left_lower_x = self.center_x - self.width / 2.0 * self.resolution
  40. self._left_lower_y = self.center_y - self.height / 2.0 * self.resolution
  41. self.ndata = self.width * self.height
  42. # this stores all data, {grid_inx: grid_value}
  43. self.non_empty_cell = dict()
  44. def _calc_xy_index_from_pos(self, pos, lower_pos, max_index):
  45. """Calculate the grid index by position
  46. """
  47. ind = int(np.floor((pos - lower_pos) / self.resolution))
  48. if not 0 <= ind <= max_index:
  49. # XXX may not need this warning
  50. logging.warning("Position not within the area")
  51. return ind
  52. def _calc_pos_from_xy_index(self, ind, lower_pos, _max_index):
  53. """Calculate the position by grid index
  54. """
  55. pos = ind*self.resolution+lower_pos+self.resolution/2.0
  56. return pos
  57. def get_value_from_xy_index(self, index):
  58. # type: (tuple) -> None
  59. """Get the value from given cell
  60. """
  61. return self.non_empty_cell[index]
  62. def get_xy_index_from_xy_pos(self, x_pos, y_pos):
  63. """Get grid index from position
  64. Args:
  65. x_pos ([type]): x position [m]
  66. y_pos ([type]): y position [m]
  67. Returns:
  68. tuple: the grid index of self.non_empty_cell
  69. """
  70. x_ind = self._calc_xy_index_from_pos(
  71. x_pos, self._left_lower_x, self.width)
  72. y_ind = self._calc_xy_index_from_pos(
  73. y_pos, self._left_lower_y, self.height)
  74. return tuple([int(x_ind), int(y_ind)])
  75. def get_xy_pos_from_xy_index(self, x_ind, y_ind):
  76. """get_xy_pos_from_xy_index
  77. """
  78. x_pos = self._calc_pos_from_xy_index(
  79. x_ind, self._left_lower_x, self.width)
  80. y_pos = self._calc_pos_from_xy_index(
  81. y_ind, self._left_lower_y, self.height)
  82. return tuple([x_pos, y_pos])
  83. def get_value_from_xy_pos(self, x_pos, y_pos):
  84. cell_ind = self.get_xy_index_from_xy_pos(x_pos, y_pos)
  85. return self.get_value_from_xy_index(cell_ind)
  86. def set_value_from_xy_index(self, index, val):
  87. """Stores the value in grid map
  88. Args:
  89. index (tuple): 2D tuple of x, y coordinates.
  90. val (float): Value that needs to be stored.
  91. """
  92. # If Q value after update is small enough to make the probability be zero,
  93. # it's safe to delete the cell for a better memory usage
  94. if val == 700.0:
  95. self.delete_value_from_xy_index(index)
  96. else:
  97. self.non_empty_cell[index] = val
  98. def delete_value_from_xy_index(self, index):
  99. """Delete the item from grid map
  100. Args:
  101. index (tuple): 2D tuple of x, y coordinates.
  102. """
  103. try:
  104. del self.non_empty_cell[index]
  105. except KeyError:
  106. pass
  107. def generate_shareable_v(self, local_measurement):
  108. # type: (dict) -> dict
  109. """Generate the shareable information from local detection
  110. Args:
  111. local_measurement (dict): local detections
  112. Returns:
  113. dict: converted shareable detection info
  114. """
  115. meas_index = dict()
  116. for _target_id, meas in local_measurement.items():
  117. x_pos, y_pos, meas_confidence = meas
  118. point_ind = tuple(
  119. self.get_xy_index_from_xy_pos(x_pos, y_pos))
  120. # meas_index[point_ind] = meas_confidence
  121. meas_index[point_ind] = np.log(
  122. self.false_alarm_prob/meas_confidence)
  123. return meas_index
  124. def generate_zero_meas(self):
  125. def cut(x): return 1e-6 if x <= 1e-6 else 1 - \
  126. 1e-6 if x >= 1-1e-6 else x
  127. meas_confidence = cut(np.random.normal(0.85, 0.1))
  128. x = np.log((1-self.false_alarm_prob)/(1-meas_confidence))
  129. return x
  130. def map_update(self, local_measurement, neighbor_measurement, N, d):
  131. """Update the probability map using measurements from local and neighbors
  132. Args:
  133. local_measurement (dict): Contains local detections like {id1:[x1, y1, confidence1], id2:[x2, y2, confidence2]}
  134. neighbor_measurement (dict): Contains neighbors' detections
  135. N (int): Number of all trackers (working on the same perimeter)
  136. d (int): Number of all neighbors
  137. """
  138. def bound_Q(Q):
  139. # 700 is big enough to make 1/(1+exp(700)) -> 0 and 1/(1+exp(-700)) -> 1
  140. return max(min(Q, 700), -700)
  141. # Get the weight of measurements
  142. weight_local = 1. - (d-1.)/N
  143. weight_neighbor = 1./N
  144. # Time decaying factor
  145. # NOTE Fine tune this param to get a good performance
  146. alpha = 5
  147. T = 0.1
  148. decay_factor = np.exp(-alpha*T)
  149. # The diagram below shows the composition of the information for each update
  150. # ┌─────────────────────────────────────────────────────┐
  151. # │ Whole area .─────────. │
  152. # │ ,─' Local '─. │
  153. # │ .─────────.,' measurement `. │
  154. # │ ,─' Existing ╱'─. ╲ │
  155. # │ ,' Cell ; `. : │
  156. # │ ,' │ 2 `. 5 │ │
  157. # │ ; │ : │ │
  158. # │ ; : .─────────. ; │
  159. # │ ; ╲ ,─' : '─. ╱ │
  160. # │ │ ╲,' 4 │ 6 `. │
  161. # │ │ 1 ╱`. │ ,' ╲ │
  162. # │ : ; '─. ; ,─' : │
  163. # │ : │ `───────' │ │
  164. # │ : │ 3 ; │ │
  165. # │ ╲ : ╱ 7 ; │
  166. # │ `. ╲ ,' ╱ │
  167. # │ `. ╲,' Neighbor ╱ │
  168. # │ '─. ,─'`. measurement ,' │
  169. # │ `───────' '─. ,─' │
  170. # │ `───────' │
  171. # └─────────────────────────────────────────────────────┘
  172. # update all existing grids (Area 1,2,3,4)
  173. for cell_ind in list(self.non_empty_cell):
  174. # Check if it's in area 2 or 4
  175. if cell_ind in local_measurement:
  176. v_local = local_measurement[cell_ind]
  177. del local_measurement[cell_ind]
  178. else:
  179. # If not, we believe there is no targets in that grid
  180. v_local = self.generate_zero_meas()
  181. if cell_ind in neighbor_measurement:
  182. v_neighbors = neighbor_measurement[cell_ind]
  183. del neighbor_measurement[cell_ind]
  184. else:
  185. v_neighbors = sum(
  186. [self.generate_zero_meas() for i in range(d)])
  187. Q = weight_local*(self.non_empty_cell[cell_ind] + v_local) + weight_neighbor * (
  188. d*self.non_empty_cell[cell_ind]+v_neighbors)
  189. self.set_value_from_xy_index(cell_ind, bound_Q(decay_factor * Q))
  190. # If got measurement for a new grid (Grids in area 5, 6, 7)
  191. else:
  192. # get the union set of all remaining measurements (Union of area 5, 6, 7)
  193. all_meas = set(list(local_measurement) +
  194. list(neighbor_measurement))
  195. for cell_ind in all_meas:
  196. try:
  197. v_local = local_measurement[cell_ind]
  198. except KeyError:
  199. v_local = self.generate_zero_meas()
  200. try:
  201. v_neighbors = neighbor_measurement[cell_ind]
  202. except KeyError:
  203. v_neighbors = sum(
  204. [self.generate_zero_meas() for i in range(d)])
  205. Q = weight_local*(self.init_val + v_local) + weight_neighbor * (
  206. d*self.init_val+v_neighbors)
  207. self.set_value_from_xy_index(
  208. cell_ind, bound_Q(decay_factor * Q))
  209. def consensus(self, neighbors_map):
  210. # type: (dict) -> None
  211. """Merge neighbors map into local map and make a consensus
  212. Args:
  213. neighbors_map (dict): Contains all values from neighbors and have a count of it. Format: {(x, y):[value, count]}
  214. """
  215. for cell_ind, value in self.non_empty_cell.items():
  216. if cell_ind in neighbors_map.keys():
  217. # Calculate the average value of Q
  218. Q = (neighbors_map[cell_ind][0]+value) / \
  219. (neighbors_map[cell_ind][1]+1)
  220. self.set_value_from_xy_index(cell_ind, Q)
  221. del neighbors_map[cell_ind]
  222. else:
  223. for cell_ind, value_and_count in neighbors_map.items():
  224. Q = value_and_count[0]/value_and_count[1]
  225. self.set_value_from_xy_index(cell_ind, Q)
  226. def convert_to_prob_map(self, threshold, normalization=False):
  227. """Convert log value to probability value [0~1]
  228. Args:
  229. threshold (float): Values higher than this will be returned
  230. """
  231. lower_threshold = 0.2
  232. if threshold < 0.5:
  233. # shrink the lower threshold value
  234. lower_threshold *= threshold
  235. logging.warning(
  236. "Got probability threshold smaller than 0.5, it's not recommended.")
  237. self.prob_map = dict()
  238. max_prob = lower_threshold
  239. if normalization:
  240. # Generate the full prob map
  241. for cell_ind in list(self.non_empty_cell):
  242. value = self.non_empty_cell[cell_ind]
  243. # Decode the probability value
  244. prob = 1./(1.+np.exp(value))
  245. if prob > max_prob:
  246. max_prob = prob
  247. self.prob_map[cell_ind] = prob
  248. # Normalize the whole map and delete data which is small enough
  249. for cell_ind in list(self.prob_map):
  250. factor = 1/max_prob
  251. normed_prob = factor*self.prob_map[cell_ind]
  252. if normed_prob >= threshold:
  253. self.prob_map[cell_ind] = normed_prob
  254. else:
  255. del self.prob_map[cell_ind]
  256. if normed_prob < lower_threshold:
  257. # keep some uncertainty between the lower and upper thresholds
  258. self.delete_value_from_xy_index(cell_ind)
  259. else:
  260. for cell_ind in list(self.non_empty_cell):
  261. value = self.non_empty_cell[cell_ind]
  262. # Decode the probability value
  263. prob = 1./(1.+np.exp(value))
  264. if prob >= threshold:
  265. self.prob_map[cell_ind] = prob
  266. elif prob < lower_threshold:
  267. # keep some uncertainty between the lower and upper thresholds
  268. self.delete_value_from_xy_index(cell_ind)
  269. def get_target_est(self, threshold, normalization=False):
  270. """Get all targets' estimated position
  271. Args:
  272. threshold (float): Probability threshold value to filter out the targets
  273. Returns:
  274. list: Targets' position
  275. """
  276. # if normalization:
  277. # logging.warning(
  278. # "Using normalization for PorbMap, the real probability will be hidden.")
  279. self.convert_to_prob_map(threshold, normalization)
  280. targets_est = list(self.prob_map.keys())
  281. for i in range(len(targets_est)):
  282. # XXX since we don't need z-data, I put a placeholder here
  283. x, y = self.get_xy_pos_from_xy_index(
  284. targets_est[i][0], targets_est[i][1])
  285. targets_est[i] = [x, y, 150]
  286. return targets_est
  287. class ProbMapData:
  288. def __init__(self):
  289. self.myid = -1
  290. self.type = 'n'
  291. self.grid_ind = list()
  292. self.values = list()