You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

nav_memory_map.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. # Copyright (c) 2017 Anki, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License in the file LICENSE.txt or at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. '''A 2D navigation memory map of the world around Cozmo.
  15. Cozmo builds a memory map of the navigable world around him as he drives
  16. around. This is mostly based on where objects are seen (the cubes, charger, and
  17. any custom objects), and also includes where Cozmo detects cliffs/drops, and
  18. visible edges (e.g. sudden changes in color).
  19. This differs from a standard occupancy map in that it doesn't deal with
  20. probabilities of occupancy, but instead encodes what type of content is there.
  21. To use the map you must first call :meth:`cozmo.world.World.request_nav_memory_map`
  22. with a positive frequency so that the data is streamed to the SDK.
  23. '''
  24. # __all__ should order by constants, event classes, other classes, functions.
  25. __all__ = ['EvtNewNavMemoryMap',
  26. 'NavMemoryMapGrid', 'NavMemoryMapGridNode',
  27. 'NodeContentTypes']
  28. from collections import namedtuple
  29. from . import event
  30. from . import logger
  31. from . import util
  32. from ._clad import CladEnumWrapper, _clad_to_game_iface
  33. class EvtNewNavMemoryMap(event.Event):
  34. '''Dispatched when a new memory map is received.'''
  35. nav_memory_map = 'A NavMemoryMapGrid object'
  36. class _NodeContentType(namedtuple('_NodeContentType', 'name id')):
  37. # Tuple mapping between CLAD ENodeContentTypeEnum name and ID
  38. # All instances will be members of ActionResults
  39. # Keep _NodeContentType as lightweight as a normal namedtuple
  40. __slots__ = ()
  41. def __str__(self):
  42. return 'NodeContentTypes.%s' % self.name
  43. class NodeContentTypes(CladEnumWrapper):
  44. """The content types for a :class:`NavMemoryMapGridNode`."""
  45. _clad_enum = _clad_to_game_iface.ENodeContentTypeEnum
  46. _entry_type = _NodeContentType
  47. #: The contents of the node is unknown.
  48. Unknown = _entry_type("Unknown", _clad_enum.Unknown)
  49. #: The node is clear of obstacles, because Cozmo has seen objects on the
  50. #: other side, but it might contain a cliff. The node will be marked as
  51. #: either :attr:`Cliff` or :attr:`ClearOfCliff` once Cozmo has driven there.
  52. ClearOfObstacle = _entry_type("ClearOfObstacle", _clad_enum.ClearOfObstacle)
  53. #: The node is clear of any cliffs (a sharp drop) or obstacles.
  54. ClearOfCliff = _entry_type("ClearOfCliff", _clad_enum.ClearOfCliff)
  55. #: The node contains a :class:`~cozmo.objects.LightCube`.
  56. ObstacleCube = _entry_type("ObstacleCube", _clad_enum.ObstacleCube)
  57. #: The node contains a :class:`~cozmo.objects.Charger`.
  58. ObstacleCharger = _entry_type("ObstacleCharger", _clad_enum.ObstacleCharger)
  59. #: The node contains a cliff (a sharp drop).
  60. Cliff = _entry_type("Cliff", _clad_enum.Cliff)
  61. #: The node contains a visible edge (based on the camera feed).
  62. VisionBorder = _entry_type("VisionBorder", _clad_enum.VisionBorder)
  63. # This entry is undocumented and not currently used
  64. _ObstacleProx = _entry_type("ObstacleProx", _clad_enum.ObstacleProx)
  65. NodeContentTypes._init_class()
  66. class NavMemoryMapGridNode:
  67. """A node in a :class:`NavMemoryMapGrid`.
  68. Leaf nodes contain content, all other nodes are split into 4 equally sized
  69. children.
  70. Child node indices are stored in the following X,Y orientation:
  71. +---+----+---+
  72. | ^ | 2 | 0 |
  73. +---+----+---+
  74. | Y | 3 | 1 |
  75. +---+----+---+
  76. | | X->| |
  77. +---+----+---+
  78. """
  79. def __init__(self, depth, size, center, parent):
  80. #: int: The depth of this node. I.e. how far down the quad-tree is it.
  81. self.depth = depth
  82. #: float: The size (width or length) of this square node.
  83. self.size = size
  84. #: :class:`~cozmo.util.Vector3`: The center of this node.
  85. self.center = center # type: util.Vector3
  86. #: :class:`NavMemoryMapGridNode`: The parent of this node. Is ``None`` for the root node.
  87. self.parent = parent # type: NavMemoryMapGridNode
  88. #: list of :class:`NavMemoryMapGridNode`: ``None`` for leaf nodes, a list of 4
  89. #: child nodes otherwise.
  90. self.children = None
  91. #: An attribute of :class:`NodeContentTypes`: The content type in this
  92. #: node. Only leaf nodes have content, this is ``None`` for all other
  93. #: nodes.
  94. self.content = None # type: _NodeContentType
  95. self._next_child = 0 # Used when building to track which branch to follow
  96. def __repr__(self):
  97. return '<%s center: %s size: %s content: %s>' % (
  98. self.__class__.__name__, self.center, self.size, self.content)
  99. def contains_point(self, x, y):
  100. """Test if the node contains the given x,y coordinates.
  101. Args:
  102. x (float): x coordinate for the point
  103. y (float): y coordinate for the point
  104. Returns:
  105. bool: True if the node contains the point, False otherwise.
  106. """
  107. half_size = self.size * 0.5
  108. dist_x = abs(self.center.x - x)
  109. dist_y = abs(self.center.y - y)
  110. return (dist_x <= half_size) and (dist_y <= half_size)
  111. def _get_node(self, x, y, assumed_in_bounds):
  112. if not assumed_in_bounds and not self.contains_point(x, y):
  113. # point is out of bounds
  114. return None
  115. if self.children is None:
  116. return self
  117. else:
  118. x_offset = 2 if x < self.center.x else 0
  119. y_offset = 1 if y < self.center.y else 0
  120. child_node = self.children[x_offset+y_offset]
  121. # child node is by definition in bounds / on boundary
  122. return child_node._get_node(x, y, True)
  123. def get_node(self, x, y):
  124. """Get the node at the given x,y coordinates.
  125. Args:
  126. x (float): x coordinate for the point
  127. y (float): y coordinate for the point
  128. Returns:
  129. :class:`NavMemoryMapGridNode`: The smallest node that includes the
  130. point. Will be ``None`` if the point is outside of the map.
  131. """
  132. return self._get_node(x, y, assumed_in_bounds=False)
  133. def get_content(self, x, y):
  134. """Get the node's content at the given x,y coordinates.
  135. Args:
  136. x (float): x coordinate for the point
  137. y (float): y coordinate for the point
  138. Returns:
  139. :class:`_NodeContentType`: The content included at that point.
  140. Will be :attr:`NodeContentTypes.Unknown` if the point is outside of
  141. the map.
  142. """
  143. node = self.get_node(x, y)
  144. if node:
  145. return node.content
  146. else:
  147. return NodeContentTypes.Unknown
  148. def _add_child(self, content, depth):
  149. """Add a child node to the quad tree.
  150. The quad-tree is serialized to a flat list of nodes, we deserialize
  151. back to a quad-tree structure here, with the depth of each node
  152. indicating where it is placed.
  153. Args:
  154. content (:class:`_NodeContentType`): The content to store in the leaf node
  155. depth (int): The depth that this leaf node is located at.
  156. Returns:
  157. bool: True if parent should use the next child for future _add_child
  158. calls (this is an internal implementation detail of h
  159. """
  160. if depth > self.depth:
  161. logger.error("NavMemoryMapGridNode depth %s > %s", depth, self.depth)
  162. if self._next_child > 3:
  163. logger.error("NavMemoryMapGridNode _next_child %s (>3) at depth %s", self._next_child, self.depth)
  164. if self.depth == depth:
  165. if self.content is not None:
  166. logger.error("NavMemoryMapGridNode: Clobbering %s at depth %s with %s",
  167. self.content, self.depth, content)
  168. self.content = content
  169. # This node won't be further subdivided, and is now full
  170. return True
  171. if self.children is None:
  172. # Create 4 child nodes for quad-tree structure
  173. next_depth = self.depth - 1
  174. next_size = self.size * 0.5
  175. offset = next_size * 0.5
  176. center1 = util.Vector3(self.center.x + offset, self.center.y + offset, self.center.z)
  177. center2 = util.Vector3(self.center.x + offset, self.center.y - offset, self.center.z)
  178. center3 = util.Vector3(self.center.x - offset, self.center.y + offset, self.center.z)
  179. center4 = util.Vector3(self.center.x - offset, self.center.y - offset, self.center.z)
  180. self.children = [NavMemoryMapGridNode(next_depth, next_size, center1, self),
  181. NavMemoryMapGridNode(next_depth, next_size, center2, self),
  182. NavMemoryMapGridNode(next_depth, next_size, center3, self),
  183. NavMemoryMapGridNode(next_depth, next_size, center4, self)]
  184. if self.children[self._next_child]._add_child(content, depth):
  185. # Child node is now full, start using the next child
  186. self._next_child += 1
  187. if self._next_child > 3:
  188. # All children are now full - parent should start using the next child
  189. return True
  190. else:
  191. # Empty children remain - parent can keep using this child
  192. return False
  193. class NavMemoryMapGrid:
  194. """A navigation memory map, stored as a quad-tree."""
  195. def __init__(self, origin_id, root_depth, root_size, root_center_x, root_center_y):
  196. #: int: The origin ID for the map. Only maps and :class:`~cozmo.util.Pose`
  197. #: objects of the same origin ID are in the same coordinate frame and
  198. #: can therefore be compared.
  199. self.origin_id = origin_id
  200. root_center = util.Vector3(root_center_x, root_center_y, 0.0)
  201. self._root_node = NavMemoryMapGridNode(root_depth, root_size, root_center, None)
  202. def __repr__(self):
  203. return '<%s center: %s size: %s>' % (
  204. self.__class__.__name__, self.center, self.size)
  205. @property
  206. def root_node(self):
  207. """:class:`NavMemoryMapGridNode`: The root node for the grid, contains all other nodes."""
  208. return self._root_node
  209. @property
  210. def size(self):
  211. """float: The size (width or length) of the square grid."""
  212. return self._root_node.size
  213. @property
  214. def center(self):
  215. """:class:`~cozmo.util.Vector3`: The center of this map."""
  216. return self._root_node.center
  217. def contains_point(self, x, y):
  218. """Test if the map contains the given x,y coordinates.
  219. Args:
  220. x (float): x coordinate for the point
  221. y (float): y coordinate for the point
  222. Returns:
  223. bool: True if the map contains the point, False otherwise.
  224. """
  225. return self._root_node.contains_point(x,y)
  226. def get_node(self, x, y):
  227. """Get the node at the given x,y coordinates.
  228. Args:
  229. x (float): x coordinate for the point
  230. y (float): y coordinate for the point
  231. Returns:
  232. :class:`NavMemoryMapGridNode`: The smallest node that includes the
  233. point. Will be ``None`` if the point is outside of the map.
  234. """
  235. return self._root_node.get_node(x, y)
  236. def get_content(self, x, y):
  237. """Get the map's content at the given x,y coordinates.
  238. Args:
  239. x (float): x coordinate for the point
  240. y (float): y coordinate for the point
  241. Returns:
  242. :class:`_NodeContentType`: The content included at that point.
  243. Will be :attr:`NodeContentTypes.Unknown` if the point is outside of
  244. the map.
  245. """
  246. return self._root_node.get_content(x, y)
  247. def _add_quad(self, content, depth):
  248. # Convert content int to our enum representation
  249. content = NodeContentTypes.find_by_id(content)
  250. self._root_node._add_child(content, depth)