open_scenario.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. #!/usr/bin/env python
  2. # Copyright (c) 2019-2020 Intel Corporation
  3. #
  4. # This work is licensed under the terms of the MIT license.
  5. # For a copy, see <https://opensource.org/licenses/MIT>.
  6. """
  7. Basic scenario class using the OpenSCENARIO definition
  8. """
  9. from __future__ import print_function
  10. import itertools
  11. import os
  12. import py_trees
  13. from srunner.scenariomanager.scenarioatomics.atomic_behaviors import ChangeWeather, ChangeRoadFriction, ChangeParameter
  14. from srunner.scenariomanager.scenarioatomics.atomic_behaviors import ChangeActorControl, ChangeActorTargetSpeed
  15. from srunner.scenariomanager.timer import GameTime
  16. from srunner.scenarios.basic_scenario import BasicScenario
  17. from srunner.tools.openscenario_parser import OpenScenarioParser, oneshot_with_check, ParameterRef
  18. from srunner.tools.py_trees_port import Decorator
  19. def repeatable_behavior(behaviour, name=None):
  20. """
  21. This behaviour allows a composite with oneshot ancestors to run multiple
  22. times, resetting the oneshot variables after each execution
  23. """
  24. if not name:
  25. name = behaviour.name
  26. clear_descendant_variables = ClearBlackboardVariablesStartingWith(
  27. name="Clear Descendant Variables of {}".format(name),
  28. variable_name_beginning=name + ">"
  29. )
  30. # If it's a sequence, don't double-nest it in a redundant manner
  31. if isinstance(behaviour, py_trees.composites.Sequence):
  32. behaviour.add_child(clear_descendant_variables)
  33. sequence = behaviour
  34. else:
  35. sequence = py_trees.composites.Sequence(name="RepeatableBehaviour of {}".format(name))
  36. sequence.add_children([behaviour, clear_descendant_variables])
  37. return sequence
  38. class ClearBlackboardVariablesStartingWith(py_trees.behaviours.Success):
  39. """
  40. Clear the values starting with the specified string from the blackboard.
  41. Args:
  42. name (:obj:`str`): name of the behaviour
  43. variable_name_beginning (:obj:`str`): beginning of the names of variable to clear
  44. """
  45. def __init__(self,
  46. name="Clear Blackboard Variable Starting With",
  47. variable_name_beginning="dummy",
  48. ):
  49. super(ClearBlackboardVariablesStartingWith, self).__init__(name)
  50. self.variable_name_beginning = variable_name_beginning
  51. def initialise(self):
  52. """
  53. Delete the variables from the blackboard.
  54. """
  55. blackboard_variables = [key for key, _ in py_trees.blackboard.Blackboard().__dict__.items(
  56. ) if key.startswith(self.variable_name_beginning)]
  57. for variable in blackboard_variables:
  58. delattr(py_trees.blackboard.Blackboard(), variable)
  59. class StoryElementStatusToBlackboard(Decorator):
  60. """
  61. Reflect the status of the decorator's child story element to the blackboard.
  62. Args:
  63. child: the child behaviour or subtree
  64. story_element_type: the element type [act,scene,maneuver,event,action]
  65. element_name: the story element's name attribute
  66. """
  67. def __init__(self, child, story_element_type, element_name):
  68. super(StoryElementStatusToBlackboard, self).__init__(name=child.name, child=child)
  69. self.story_element_type = story_element_type
  70. self.element_name = element_name
  71. self.blackboard = py_trees.blackboard.Blackboard()
  72. def initialise(self):
  73. """
  74. Record the elements's start time on the blackboard
  75. """
  76. self.blackboard.set(
  77. name="({}){}-{}".format(self.story_element_type.upper(),
  78. self.element_name, "START"),
  79. value=GameTime.get_time(),
  80. overwrite=True
  81. )
  82. def update(self):
  83. """
  84. Reflect the decorated child's status
  85. Returns: the decorated child's status
  86. """
  87. return self.decorated.status
  88. def terminate(self, new_status):
  89. """
  90. Terminate and mark Blackboard entry with END
  91. """
  92. # Report whether we ended with End or Cancel
  93. # If we were ended or cancelled, our state will be INVALID and
  94. # We will have an ancestor (a parallel SUCCESS_ON_ALL) with a successful child/children
  95. # It's possible we ENDed AND CANCELled if both condition groups were true simultaneously
  96. # NOTE 'py_trees.common.Status.INVALID' is the status of a behaviur which was terminated by a parent
  97. rules = []
  98. if new_status == py_trees.common.Status.INVALID:
  99. # We were terminated from above unnaturally
  100. # Figure out if were ended or cancelled
  101. terminating_ancestor = self.parent
  102. while terminating_ancestor.status == py_trees.common.Status.INVALID:
  103. terminating_ancestor = terminating_ancestor.parent
  104. # We have found an ancestory which was not terminated by a parent
  105. # Check what caused it to terminate its children
  106. if terminating_ancestor.status == py_trees.common.Status.SUCCESS:
  107. successful_children = [
  108. child.name
  109. for child
  110. in terminating_ancestor.children
  111. if child.status == py_trees.common.Status.SUCCESS]
  112. if "StopTrigger" in successful_children:
  113. rules.append("END")
  114. # END is the default status unless we have a more detailed one
  115. rules = rules or ["END"]
  116. for rule in rules:
  117. self.blackboard.set(
  118. name="({}){}-{}".format(self.story_element_type.upper(),
  119. self.element_name, rule),
  120. value=GameTime.get_time(),
  121. overwrite=True
  122. )
  123. def get_xml_path(tree, node):
  124. """
  125. Extract the full path of a node within an XML tree
  126. Note: Catalogs are pulled from a separate file so the XML tree is split.
  127. This means that in order to get the XML path, it must be done in 2 steps.
  128. Some places in this python script do that by concatenating the results
  129. of 2 get_xml_path calls with another ">".
  130. Example: "Behavior>AutopilotSequence" + ">" + "StartAutopilot>StartAutopilot>StartAutopilot"
  131. """
  132. path = ""
  133. parent_map = {c: p for p in tree.iter() for c in p}
  134. cur_node = node
  135. while cur_node != tree:
  136. path = "{}>{}".format(cur_node.attrib.get('name'), path)
  137. cur_node = parent_map[cur_node]
  138. path = path[:-1]
  139. return path
  140. class OpenScenario(BasicScenario):
  141. """
  142. Implementation of the OpenSCENARIO scenario
  143. """
  144. def __init__(self, world, ego_vehicles, config, config_file, debug_mode=False, criteria_enable=True, timeout=300):
  145. """
  146. Setup all relevant parameters and create scenario
  147. """
  148. self.config = config
  149. self.route = None
  150. self.config_file = config_file
  151. # Timeout of scenario in seconds
  152. self.timeout = timeout
  153. super(OpenScenario, self).__init__(self.config.name, ego_vehicles=ego_vehicles, config=config,
  154. world=world, debug_mode=debug_mode,
  155. terminate_on_failure=False, criteria_enable=criteria_enable)
  156. def _initialize_parameters(self):
  157. """
  158. Parse ParameterAction from Init and update global osc parameters.
  159. """
  160. param_behavior = py_trees.composites.Parallel(
  161. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="ParametersInit")
  162. for i, global_action in enumerate(self.config.init.find('Actions').iter('GlobalAction')):
  163. maneuver_name = 'InitParams'
  164. if global_action.find('ParameterAction') is not None:
  165. parameter_action = global_action.find('ParameterAction')
  166. parameter_ref = parameter_action.attrib.get('parameterRef')
  167. if parameter_action.find('ModifyAction') is not None:
  168. action_rule = parameter_action.find('ModifyAction').find("Rule")
  169. if action_rule.find("AddValue") is not None:
  170. rule, value = '+', action_rule.find("AddValue").attrib.get('value')
  171. else:
  172. rule, value = '*', action_rule.find("MultiplyByValue").attrib.get('value')
  173. else:
  174. rule, value = None, parameter_action.find('SetAction').attrib.get('value')
  175. parameter_update = ChangeParameter(parameter_ref, value=ParameterRef(value), rule=rule,
  176. name=maneuver_name + '_%d' % i)
  177. param_behavior.add_child(oneshot_with_check(variable_name="InitialParameters" + '_%d' % i,
  178. behaviour=parameter_update))
  179. return param_behavior
  180. def _initialize_environment(self, world):
  181. """
  182. Initialization of weather and road friction.
  183. """
  184. pass
  185. def _create_environment_behavior(self):
  186. # Set the appropriate weather conditions
  187. env_behavior = py_trees.composites.Parallel(
  188. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="EnvironmentBehavior")
  189. weather_update = ChangeWeather(
  190. OpenScenarioParser.get_weather_from_env_action(self.config.init, self.config.catalogs))
  191. road_friction = ChangeRoadFriction(
  192. OpenScenarioParser.get_friction_from_env_action(self.config.init, self.config.catalogs))
  193. env_behavior.add_child(oneshot_with_check(variable_name="InitialWeather", behaviour=weather_update))
  194. env_behavior.add_child(oneshot_with_check(variable_name="InitRoadFriction", behaviour=road_friction))
  195. return env_behavior
  196. def _create_init_behavior(self):
  197. init_behavior = py_trees.composites.Parallel(
  198. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="InitBehaviour")
  199. for actor in self.config.other_actors + self.config.ego_vehicles:
  200. for carla_actor in self.other_actors + self.ego_vehicles:
  201. if (carla_actor is not None and 'role_name' in carla_actor.attributes and
  202. carla_actor.attributes['role_name'] == actor.rolename):
  203. actor_init_behavior = py_trees.composites.Sequence(name="InitActor{}".format(actor.rolename))
  204. controller_atomic = None
  205. for private in self.config.init.iter("Private"):
  206. if private.attrib.get('entityRef', None) == actor.rolename:
  207. for private_action in private.iter("PrivateAction"):
  208. for controller_action in private_action.iter('ControllerAction'):
  209. module, args = OpenScenarioParser.get_controller(
  210. controller_action, self.config.catalogs)
  211. controller_atomic = ChangeActorControl(
  212. carla_actor, control_py_module=module, args=args,
  213. scenario_file_path=os.path.dirname(self.config.filename))
  214. if controller_atomic is None:
  215. controller_atomic = ChangeActorControl(carla_actor, control_py_module=None, args={})
  216. actor_init_behavior.add_child(controller_atomic)
  217. if actor.speed > 0:
  218. actor_init_behavior.add_child(ChangeActorTargetSpeed(carla_actor, actor.speed, init_speed=True))
  219. init_behavior.add_child(actor_init_behavior)
  220. break
  221. return init_behavior
  222. def _create_behavior(self):
  223. """
  224. Basic behavior do nothing, i.e. Idle
  225. """
  226. stories_behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL,
  227. name="OSCStories")
  228. joint_actor_list = self.other_actors + self.ego_vehicles + [None]
  229. for story in self.config.stories:
  230. story_name = story.get("name")
  231. story_behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL,
  232. name=story_name)
  233. for act in story.iter("Act"):
  234. act_sequence = py_trees.composites.Sequence(
  235. name="Act StartConditions and behaviours")
  236. start_conditions = py_trees.composites.Parallel(
  237. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="StartConditions Group")
  238. parallel_behavior = py_trees.composites.Parallel(
  239. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="Maneuver + EndConditions Group")
  240. parallel_sequences = py_trees.composites.Parallel(
  241. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="Maneuvers")
  242. for sequence in act.iter("ManeuverGroup"):
  243. sequence_behavior = py_trees.composites.Sequence(name=sequence.attrib.get('name'))
  244. repetitions = sequence.attrib.get('maximumExecutionCount', 1)
  245. for _ in range(int(repetitions)):
  246. actor_ids = []
  247. for actor in sequence.iter("Actors"):
  248. for entity in actor.iter("EntityRef"):
  249. entity_name = entity.attrib.get('entityRef', None)
  250. for k, _ in enumerate(joint_actor_list):
  251. if (joint_actor_list[k] and
  252. entity_name == joint_actor_list[k].attributes['role_name']):
  253. actor_ids.append(k)
  254. break
  255. if not actor_ids:
  256. print("Warning: Maneuvergroup {} does not use reference actors!".format(
  257. sequence.attrib.get('name')))
  258. actor_ids.append(len(joint_actor_list) - 1)
  259. # Collect catalog reference maneuvers in order to process them at the same time as normal maneuvers
  260. catalog_maneuver_list = []
  261. for catalog_reference in sequence.iter("CatalogReference"):
  262. catalog_maneuver = OpenScenarioParser.get_catalog_entry(self.config.catalogs,
  263. catalog_reference)
  264. catalog_maneuver_list.append(catalog_maneuver)
  265. all_maneuvers = itertools.chain(iter(catalog_maneuver_list), sequence.iter("Maneuver"))
  266. single_sequence_iteration = py_trees.composites.Parallel(
  267. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name=sequence_behavior.name)
  268. for maneuver in all_maneuvers: # Iterates through both CatalogReferences and Maneuvers
  269. maneuver_parallel = py_trees.composites.Parallel(
  270. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL,
  271. name="Maneuver " + maneuver.attrib.get('name'))
  272. for event in maneuver.iter("Event"):
  273. event_sequence = py_trees.composites.Sequence(
  274. name="Event " + event.attrib.get('name'))
  275. parallel_actions = py_trees.composites.Parallel(
  276. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="Actions")
  277. for child in event.iter():
  278. if child.tag == "Action":
  279. for actor_id in actor_ids:
  280. maneuver_behavior = OpenScenarioParser.convert_maneuver_to_atomic(
  281. child, joint_actor_list[actor_id],
  282. joint_actor_list, self.config.catalogs)
  283. maneuver_behavior = StoryElementStatusToBlackboard(
  284. maneuver_behavior, "ACTION", child.attrib.get('name'))
  285. parallel_actions.add_child(
  286. oneshot_with_check(variable_name= # See note in get_xml_path
  287. get_xml_path(story, sequence) + '>' + \
  288. get_xml_path(maneuver, child) + '>' + \
  289. str(actor_id),
  290. behaviour=maneuver_behavior))
  291. if child.tag == "StartTrigger":
  292. # There is always one StartConditions block per Event
  293. parallel_condition_groups = self._create_condition_container(
  294. child, story, "Parallel Condition Groups", sequence, maneuver)
  295. event_sequence.add_child(
  296. parallel_condition_groups)
  297. parallel_actions = StoryElementStatusToBlackboard(
  298. parallel_actions, "EVENT", event.attrib.get('name'))
  299. event_sequence.add_child(parallel_actions)
  300. maneuver_parallel.add_child(
  301. oneshot_with_check(variable_name=get_xml_path(story, sequence) + '>' +
  302. get_xml_path(maneuver, event), # See get_xml_path
  303. behaviour=event_sequence))
  304. maneuver_parallel = StoryElementStatusToBlackboard(
  305. maneuver_parallel, "MANEUVER", maneuver.attrib.get('name'))
  306. single_sequence_iteration.add_child(
  307. oneshot_with_check(variable_name=get_xml_path(story, sequence) + '>' +
  308. maneuver.attrib.get('name'), # See get_xml_path
  309. behaviour=maneuver_parallel))
  310. # OpenSCENARIO refers to Sequences as Scenes in this instance
  311. single_sequence_iteration = StoryElementStatusToBlackboard(
  312. single_sequence_iteration, "SCENE", sequence.attrib.get('name'))
  313. single_sequence_iteration = repeatable_behavior(
  314. single_sequence_iteration, get_xml_path(story, sequence))
  315. sequence_behavior.add_child(single_sequence_iteration)
  316. if sequence_behavior.children:
  317. parallel_sequences.add_child(
  318. oneshot_with_check(variable_name=get_xml_path(story, sequence),
  319. behaviour=sequence_behavior))
  320. if parallel_sequences.children:
  321. parallel_sequences = StoryElementStatusToBlackboard(
  322. parallel_sequences, "ACT", act.attrib.get('name'))
  323. parallel_behavior.add_child(parallel_sequences)
  324. start_triggers = act.find("StartTrigger")
  325. if list(start_triggers) is not None:
  326. for start_condition in start_triggers:
  327. parallel_start_criteria = self._create_condition_container(start_condition,
  328. story,
  329. "StartConditions")
  330. if parallel_start_criteria.children:
  331. start_conditions.add_child(parallel_start_criteria)
  332. end_triggers = act.find("StopTrigger")
  333. if end_triggers is not None and list(end_triggers) is not None:
  334. for end_condition in end_triggers:
  335. parallel_end_criteria = self._create_condition_container(
  336. end_condition, story, "EndConditions", success_on_all=False)
  337. if parallel_end_criteria.children:
  338. parallel_behavior.add_child(parallel_end_criteria)
  339. if start_conditions.children:
  340. act_sequence.add_child(start_conditions)
  341. if parallel_behavior.children:
  342. act_sequence.add_child(parallel_behavior)
  343. if act_sequence.children:
  344. story_behavior.add_child(act_sequence)
  345. stories_behavior.add_child(oneshot_with_check(variable_name=get_xml_path(story, story) + '>' +
  346. story_name, # See get_xml_path
  347. behaviour=story_behavior))
  348. # Build behavior tree
  349. behavior = py_trees.composites.Parallel(
  350. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="behavior")
  351. init_parameters = self._initialize_parameters()
  352. if init_parameters is not None:
  353. behavior.add_child(oneshot_with_check(variable_name="InitialParameterSettings", behaviour=init_parameters))
  354. env_behavior = self._create_environment_behavior()
  355. if env_behavior is not None:
  356. behavior.add_child(oneshot_with_check(variable_name="InitialEnvironmentSettings", behaviour=env_behavior))
  357. init_behavior = self._create_init_behavior()
  358. if init_behavior is not None:
  359. behavior.add_child(oneshot_with_check(variable_name="InitialActorSettings", behaviour=init_behavior))
  360. behavior.add_child(stories_behavior)
  361. return behavior
  362. def _create_condition_container(self, node, story, name='Conditions Group', sequence=None,
  363. maneuver=None, success_on_all=True):
  364. """
  365. This is a generic function to handle conditions utilising ConditionGroups
  366. Each ConditionGroup is represented as a Sequence of Conditions
  367. The ConditionGroups are grouped under a SUCCESS_ON_ONE Parallel
  368. """
  369. parallel_condition_groups = py_trees.composites.Parallel(name,
  370. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE)
  371. for condition_group in node.iter("ConditionGroup"):
  372. if success_on_all:
  373. condition_group_sequence = py_trees.composites.Parallel(
  374. name="Condition Group", policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL)
  375. else:
  376. condition_group_sequence = py_trees.composites.Parallel(
  377. name="Condition Group", policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE)
  378. for condition in condition_group.iter("Condition"):
  379. criterion = OpenScenarioParser.convert_condition_to_atomic(
  380. condition, self.other_actors + self.ego_vehicles)
  381. if sequence is not None and maneuver is not None:
  382. xml_path = get_xml_path(story, sequence) + '>' + \
  383. get_xml_path(maneuver, condition) # See note in get_xml_path
  384. else:
  385. xml_path = get_xml_path(story, condition)
  386. criterion = oneshot_with_check(variable_name=xml_path, behaviour=criterion)
  387. condition_group_sequence.add_child(criterion)
  388. if condition_group_sequence.children:
  389. parallel_condition_groups.add_child(condition_group_sequence)
  390. return parallel_condition_groups
  391. def _create_test_criteria(self):
  392. """
  393. A list of all test criteria will be created that is later used
  394. in parallel behavior tree.
  395. """
  396. parallel_criteria = py_trees.composites.Parallel("EndConditions (Criteria Group)",
  397. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE)
  398. criteria = []
  399. for endcondition in self.config.storyboard.iter("StopTrigger"):
  400. for condition in endcondition.iter("Condition"):
  401. if condition.attrib.get('name').startswith('criteria_'):
  402. criteria.append(condition)
  403. for condition in criteria:
  404. criterion = OpenScenarioParser.convert_condition_to_atomic(condition, self.ego_vehicles)
  405. parallel_criteria.add_child(criterion)
  406. return parallel_criteria
  407. def __del__(self):
  408. """
  409. Remove all actors upon deletion
  410. """
  411. self.remove_all_actors()