ProbMap.py 15 KB

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