route_scenario.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  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. This module provides Challenge routes as standalone scenarios
  8. """
  9. from __future__ import print_function
  10. import math
  11. import traceback
  12. import xml.etree.ElementTree as ET
  13. from numpy import random
  14. import py_trees
  15. import carla
  16. from agents.navigation.local_planner import RoadOption
  17. # pylint: disable=line-too-long
  18. from srunner.scenarioconfigs.scenario_configuration import ScenarioConfiguration, ActorConfigurationData
  19. # pylint: enable=line-too-long
  20. from srunner.scenariomanager.carla_data_provider import CarlaDataProvider
  21. from srunner.scenariomanager.scenarioatomics.atomic_behaviors import Idle, ScenarioTriggerer
  22. from srunner.scenarios.basic_scenario import BasicScenario
  23. from srunner.tools.route_parser import RouteParser, TRIGGER_THRESHOLD, TRIGGER_ANGLE_THRESHOLD
  24. from srunner.tools.route_manipulation import interpolate_trajectory
  25. from srunner.tools.py_trees_port import oneshot_behavior
  26. from srunner.scenarios.control_loss import ControlLoss
  27. from srunner.scenarios.follow_leading_vehicle import FollowLeadingVehicle
  28. from srunner.scenarios.object_crash_vehicle import DynamicObjectCrossing
  29. from srunner.scenarios.object_crash_intersection import VehicleTurningRoute
  30. from srunner.scenarios.other_leading_vehicle import OtherLeadingVehicle
  31. from srunner.scenarios.maneuver_opposite_direction import ManeuverOppositeDirection
  32. from srunner.scenarios.junction_crossing_route import SignalJunctionCrossingRoute, NoSignalJunctionCrossingRoute
  33. from srunner.scenariomanager.scenarioatomics.atomic_criteria import (CollisionTest,
  34. InRouteTest,
  35. RouteCompletionTest,
  36. OutsideRouteLanesTest,
  37. RunningRedLightTest,
  38. RunningStopTest,
  39. ActorSpeedAboveThresholdTest)
  40. SECONDS_GIVEN_PER_METERS = 0.4
  41. NUMBER_CLASS_TRANSLATION = {
  42. "Scenario1": ControlLoss,
  43. "Scenario2": FollowLeadingVehicle,
  44. "Scenario3": DynamicObjectCrossing,
  45. "Scenario4": VehicleTurningRoute,
  46. "Scenario5": OtherLeadingVehicle,
  47. "Scenario6": ManeuverOppositeDirection,
  48. "Scenario7": SignalJunctionCrossingRoute,
  49. "Scenario8": SignalJunctionCrossingRoute,
  50. "Scenario9": SignalJunctionCrossingRoute,
  51. "Scenario10": NoSignalJunctionCrossingRoute
  52. }
  53. def convert_json_to_transform(actor_dict):
  54. """
  55. Convert a JSON string to a CARLA transform
  56. """
  57. return carla.Transform(location=carla.Location(x=float(actor_dict['x']), y=float(actor_dict['y']),
  58. z=float(actor_dict['z'])),
  59. rotation=carla.Rotation(roll=0.0, pitch=0.0, yaw=float(actor_dict['yaw'])))
  60. def convert_json_to_actor(actor_dict):
  61. """
  62. Convert a JSON string to an ActorConfigurationData dictionary
  63. """
  64. node = ET.Element('waypoint')
  65. node.set('x', actor_dict['x'])
  66. node.set('y', actor_dict['y'])
  67. node.set('z', actor_dict['z'])
  68. node.set('yaw', actor_dict['yaw'])
  69. return ActorConfigurationData.parse_from_node(node, 'simulation')
  70. def convert_transform_to_location(transform_vec):
  71. """
  72. Convert a vector of transforms to a vector of locations
  73. """
  74. location_vec = []
  75. for transform_tuple in transform_vec:
  76. location_vec.append((transform_tuple[0].location, transform_tuple[1]))
  77. return location_vec
  78. def compare_scenarios(scenario_choice, existent_scenario):
  79. """
  80. Compare function for scenarios based on distance of the scenario start position
  81. """
  82. def transform_to_pos_vec(scenario):
  83. """
  84. Convert left/right/front to a meaningful CARLA position
  85. """
  86. position_vec = [scenario['trigger_position']]
  87. if scenario['other_actors'] is not None:
  88. if 'left' in scenario['other_actors']:
  89. position_vec += scenario['other_actors']['left']
  90. if 'front' in scenario['other_actors']:
  91. position_vec += scenario['other_actors']['front']
  92. if 'right' in scenario['other_actors']:
  93. position_vec += scenario['other_actors']['right']
  94. return position_vec
  95. # put the positions of the scenario choice into a vec of positions to be able to compare
  96. choice_vec = transform_to_pos_vec(scenario_choice)
  97. existent_vec = transform_to_pos_vec(existent_scenario)
  98. for pos_choice in choice_vec:
  99. for pos_existent in existent_vec:
  100. dx = float(pos_choice['x']) - float(pos_existent['x'])
  101. dy = float(pos_choice['y']) - float(pos_existent['y'])
  102. dz = float(pos_choice['z']) - float(pos_existent['z'])
  103. dist_position = math.sqrt(dx * dx + dy * dy + dz * dz)
  104. dyaw = float(pos_choice['yaw']) - float(pos_choice['yaw'])
  105. dist_angle = math.sqrt(dyaw * dyaw)
  106. if dist_position < TRIGGER_THRESHOLD and dist_angle < TRIGGER_ANGLE_THRESHOLD:
  107. return True
  108. return False
  109. class RouteScenario(BasicScenario):
  110. """
  111. Implementation of a RouteScenario, i.e. a scenario that consists of driving along a pre-defined route,
  112. along which several smaller scenarios are triggered
  113. """
  114. def __init__(self, world, config, debug_mode=False, criteria_enable=True, timeout=300):
  115. """
  116. Setup all relevant parameters and create scenarios along route
  117. """
  118. self.config = config
  119. self.route = None
  120. self.sampled_scenarios_definitions = None
  121. self._update_route(world, config, debug_mode)
  122. ego_vehicle = self._update_ego_vehicle()
  123. self.list_scenarios = self._build_scenario_instances(world,
  124. ego_vehicle,
  125. self.sampled_scenarios_definitions,
  126. scenarios_per_tick=5,
  127. timeout=self.timeout,
  128. debug_mode=debug_mode)
  129. super(RouteScenario, self).__init__(name=config.name,
  130. ego_vehicles=[ego_vehicle],
  131. config=config,
  132. world=world,
  133. debug_mode=False,
  134. terminate_on_failure=False,
  135. criteria_enable=criteria_enable)
  136. def _update_route(self, world, config, debug_mode):
  137. """
  138. Update the input route, i.e. refine waypoint list, and extract possible scenario locations
  139. Parameters:
  140. - world: CARLA world
  141. - config: Scenario configuration (RouteConfiguration)
  142. """
  143. # Transform the scenario file into a dictionary
  144. world_annotations = RouteParser.parse_annotations_file(config.scenario_file)
  145. # prepare route's trajectory (interpolate and add the GPS route)
  146. gps_route, route = interpolate_trajectory(world, config.trajectory)
  147. potential_scenarios_definitions, _ = RouteParser.scan_route_for_scenarios(config.town, route, world_annotations)
  148. self.route = route
  149. CarlaDataProvider.set_ego_vehicle_route(convert_transform_to_location(self.route))
  150. if config.agent is not None:
  151. config.agent.set_global_plan(gps_route, self.route)
  152. # Sample the scenarios to be used for this route instance.
  153. self.sampled_scenarios_definitions = self._scenario_sampling(potential_scenarios_definitions)
  154. # Timeout of scenario in seconds
  155. self.timeout = self._estimate_route_timeout()
  156. # Print route in debug mode
  157. if debug_mode:
  158. self._draw_waypoints(world, self.route, vertical_shift=1.0, persistency=50000.0)
  159. def _update_ego_vehicle(self):
  160. """
  161. Set/Update the start position of the ego_vehicle
  162. """
  163. # move ego to correct position
  164. elevate_transform = self.route[0][0]
  165. elevate_transform.location.z += 0.5
  166. ego_vehicle = CarlaDataProvider.request_new_actor('vehicle.lincoln.mkz_2017',
  167. elevate_transform,
  168. rolename='hero')
  169. return ego_vehicle
  170. def _estimate_route_timeout(self):
  171. """
  172. Estimate the duration of the route
  173. """
  174. route_length = 0.0 # in meters
  175. prev_point = self.route[0][0]
  176. for current_point, _ in self.route[1:]:
  177. dist = current_point.location.distance(prev_point.location)
  178. route_length += dist
  179. prev_point = current_point
  180. return int(SECONDS_GIVEN_PER_METERS * route_length)
  181. # pylint: disable=no-self-use
  182. def _draw_waypoints(self, world, waypoints, vertical_shift, persistency=-1):
  183. """
  184. Draw a list of waypoints at a certain height given in vertical_shift.
  185. """
  186. for w in waypoints:
  187. wp = w[0].location + carla.Location(z=vertical_shift)
  188. size = 0.2
  189. if w[1] == RoadOption.LEFT: # Yellow
  190. color = carla.Color(255, 255, 0)
  191. elif w[1] == RoadOption.RIGHT: # Cyan
  192. color = carla.Color(0, 255, 255)
  193. elif w[1] == RoadOption.CHANGELANELEFT: # Orange
  194. color = carla.Color(255, 64, 0)
  195. elif w[1] == RoadOption.CHANGELANERIGHT: # Dark Cyan
  196. color = carla.Color(0, 64, 255)
  197. elif w[1] == RoadOption.STRAIGHT: # Gray
  198. color = carla.Color(128, 128, 128)
  199. else: # LANEFOLLOW
  200. color = carla.Color(0, 255, 0) # Green
  201. size = 0.1
  202. world.debug.draw_point(wp, size=size, color=color, life_time=persistency)
  203. world.debug.draw_point(waypoints[0][0].location + carla.Location(z=vertical_shift), size=0.2,
  204. color=carla.Color(0, 0, 255), life_time=persistency)
  205. world.debug.draw_point(waypoints[-1][0].location + carla.Location(z=vertical_shift), size=0.2,
  206. color=carla.Color(255, 0, 0), life_time=persistency)
  207. def _scenario_sampling(self, potential_scenarios_definitions, random_seed=0):
  208. """
  209. The function used to sample the scenarios that are going to happen for this route.
  210. """
  211. # fix the random seed for reproducibility
  212. rng = random.RandomState(random_seed)
  213. def position_sampled(scenario_choice, sampled_scenarios):
  214. """
  215. Check if a position was already sampled, i.e. used for another scenario
  216. """
  217. for existent_scenario in sampled_scenarios:
  218. # If the scenarios have equal positions then it is true.
  219. if compare_scenarios(scenario_choice, existent_scenario):
  220. return True
  221. return False
  222. # The idea is to randomly sample a scenario per trigger position.
  223. sampled_scenarios = []
  224. for trigger in potential_scenarios_definitions.keys():
  225. possible_scenarios = potential_scenarios_definitions[trigger]
  226. scenario_choice = rng.choice(possible_scenarios)
  227. del possible_scenarios[possible_scenarios.index(scenario_choice)]
  228. # We keep sampling and testing if this position is present on any of the scenarios.
  229. while position_sampled(scenario_choice, sampled_scenarios):
  230. if possible_scenarios is None or not possible_scenarios:
  231. scenario_choice = None
  232. break
  233. scenario_choice = rng.choice(possible_scenarios)
  234. del possible_scenarios[possible_scenarios.index(scenario_choice)]
  235. if scenario_choice is not None:
  236. sampled_scenarios.append(scenario_choice)
  237. return sampled_scenarios
  238. def _build_scenario_instances(self, world, ego_vehicle, scenario_definitions,
  239. scenarios_per_tick=5, timeout=300, debug_mode=False):
  240. """
  241. Based on the parsed route and possible scenarios, build all the scenario classes.
  242. """
  243. scenario_instance_vec = []
  244. if debug_mode:
  245. for scenario in scenario_definitions:
  246. loc = carla.Location(scenario['trigger_position']['x'],
  247. scenario['trigger_position']['y'],
  248. scenario['trigger_position']['z']) + carla.Location(z=2.0)
  249. world.debug.draw_point(loc, size=0.3, color=carla.Color(255, 0, 0), life_time=100000)
  250. world.debug.draw_string(loc, str(scenario['name']), draw_shadow=False,
  251. color=carla.Color(0, 0, 255), life_time=100000, persistent_lines=True)
  252. for scenario_number, definition in enumerate(scenario_definitions):
  253. # Get the class possibilities for this scenario number
  254. scenario_class = NUMBER_CLASS_TRANSLATION[definition['name']]
  255. # Create the other actors that are going to appear
  256. if definition['other_actors'] is not None:
  257. list_of_actor_conf_instances = self._get_actors_instances(definition['other_actors'])
  258. else:
  259. list_of_actor_conf_instances = []
  260. # Create an actor configuration for the ego-vehicle trigger position
  261. egoactor_trigger_position = convert_json_to_transform(definition['trigger_position'])
  262. scenario_configuration = ScenarioConfiguration()
  263. scenario_configuration.other_actors = list_of_actor_conf_instances
  264. scenario_configuration.trigger_points = [egoactor_trigger_position]
  265. scenario_configuration.subtype = definition['scenario_type']
  266. scenario_configuration.ego_vehicles = [ActorConfigurationData('vehicle.lincoln.mkz_2017',
  267. ego_vehicle.get_transform(),
  268. 'hero')]
  269. route_var_name = "ScenarioRouteNumber{}".format(scenario_number)
  270. scenario_configuration.route_var_name = route_var_name
  271. try:
  272. scenario_instance = scenario_class(world, [ego_vehicle], scenario_configuration,
  273. criteria_enable=False, timeout=timeout)
  274. # Do a tick every once in a while to avoid spawning everything at the same time
  275. if scenario_number % scenarios_per_tick == 0:
  276. if CarlaDataProvider.is_sync_mode():
  277. world.tick()
  278. else:
  279. world.wait_for_tick()
  280. scenario_number += 1
  281. except Exception as e: # pylint: disable=broad-except
  282. if debug_mode:
  283. traceback.print_exc()
  284. print("Skipping scenario '{}' due to setup error: {}".format(definition['name'], e))
  285. continue
  286. scenario_instance_vec.append(scenario_instance)
  287. return scenario_instance_vec
  288. def _get_actors_instances(self, list_of_antagonist_actors):
  289. """
  290. Get the full list of actor instances.
  291. """
  292. def get_actors_from_list(list_of_actor_def):
  293. """
  294. Receives a list of actor definitions and creates an actual list of ActorConfigurationObjects
  295. """
  296. sublist_of_actors = []
  297. for actor_def in list_of_actor_def:
  298. sublist_of_actors.append(convert_json_to_actor(actor_def))
  299. return sublist_of_actors
  300. list_of_actors = []
  301. # Parse vehicles to the left
  302. if 'front' in list_of_antagonist_actors:
  303. list_of_actors += get_actors_from_list(list_of_antagonist_actors['front'])
  304. if 'left' in list_of_antagonist_actors:
  305. list_of_actors += get_actors_from_list(list_of_antagonist_actors['left'])
  306. if 'right' in list_of_antagonist_actors:
  307. list_of_actors += get_actors_from_list(list_of_antagonist_actors['right'])
  308. return list_of_actors
  309. # pylint: enable=no-self-use
  310. def _initialize_actors(self, config):
  311. """
  312. Set other_actors to the superset of all scenario actors
  313. """
  314. # Create the background activity of the route
  315. town_amount = {
  316. 'Town01': 120,
  317. 'Town02': 100,
  318. 'Town03': 120,
  319. 'Town04': 200,
  320. 'Town05': 120,
  321. 'Town06': 150,
  322. 'Town07': 110,
  323. 'Town08': 180,
  324. 'Town09': 300,
  325. 'Town10': 120,
  326. }
  327. amount = town_amount[config.town] if config.town in town_amount else 0
  328. new_actors = CarlaDataProvider.request_new_batch_actors('vehicle.*',
  329. amount,
  330. carla.Transform(),
  331. autopilot=True,
  332. random_location=True,
  333. rolename='background')
  334. if new_actors is None:
  335. raise Exception("Error: Unable to add the background activity, all spawn points were occupied")
  336. for _actor in new_actors:
  337. self.other_actors.append(_actor)
  338. # Add all the actors of the specific scenarios to self.other_actors
  339. for scenario in self.list_scenarios:
  340. self.other_actors.extend(scenario.other_actors)
  341. def _create_behavior(self):
  342. """
  343. Basic behavior do nothing, i.e. Idle
  344. """
  345. scenario_trigger_distance = 1.5 # Max trigger distance between route and scenario
  346. behavior = py_trees.composites.Parallel(policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE)
  347. subbehavior = py_trees.composites.Parallel(name="Behavior",
  348. policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL)
  349. scenario_behaviors = []
  350. blackboard_list = []
  351. for i, scenario in enumerate(self.list_scenarios):
  352. if scenario.scenario.behavior is not None:
  353. route_var_name = scenario.config.route_var_name
  354. if route_var_name is not None:
  355. scenario_behaviors.append(scenario.scenario.behavior)
  356. blackboard_list.append([scenario.config.route_var_name,
  357. scenario.config.trigger_points[0].location])
  358. else:
  359. name = "{} - {}".format(i, scenario.scenario.behavior.name)
  360. oneshot_idiom = oneshot_behavior(name,
  361. behaviour=scenario.scenario.behavior,
  362. name=name)
  363. scenario_behaviors.append(oneshot_idiom)
  364. # Add behavior that manages the scenarios trigger conditions
  365. scenario_triggerer = ScenarioTriggerer(
  366. self.ego_vehicles[0],
  367. self.route,
  368. blackboard_list,
  369. scenario_trigger_distance,
  370. repeat_scenarios=False
  371. )
  372. subbehavior.add_child(scenario_triggerer) # make ScenarioTriggerer the first thing to be checked
  373. subbehavior.add_children(scenario_behaviors)
  374. subbehavior.add_child(Idle()) # The behaviours cannot make the route scenario stop
  375. behavior.add_child(subbehavior)
  376. return behavior
  377. def _create_test_criteria(self):
  378. """
  379. """
  380. criteria = []
  381. route = convert_transform_to_location(self.route)
  382. collision_criterion = CollisionTest(self.ego_vehicles[0], terminate_on_failure=False)
  383. route_criterion = InRouteTest(self.ego_vehicles[0],
  384. route=route,
  385. offroad_max=30,
  386. terminate_on_failure=True)
  387. completion_criterion = RouteCompletionTest(self.ego_vehicles[0], route=route)
  388. outsidelane_criterion = OutsideRouteLanesTest(self.ego_vehicles[0], route=route)
  389. red_light_criterion = RunningRedLightTest(self.ego_vehicles[0])
  390. stop_criterion = RunningStopTest(self.ego_vehicles[0])
  391. blocked_criterion = ActorSpeedAboveThresholdTest(self.ego_vehicles[0],
  392. speed_threshold=0.1,
  393. below_threshold_max_time=90.0,
  394. terminate_on_failure=True)
  395. criteria.append(completion_criterion)
  396. criteria.append(collision_criterion)
  397. criteria.append(route_criterion)
  398. criteria.append(outsidelane_criterion)
  399. criteria.append(red_light_criterion)
  400. criteria.append(stop_criterion)
  401. criteria.append(blocked_criterion)
  402. return criteria
  403. def __del__(self):
  404. """
  405. Remove all actors upon deletion
  406. """
  407. self.remove_all_actors()