openscenario_configuration.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. #!/usr/bin/env python
  2. # Copyright (c) 2019 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 the key configuration parameters for a scenario based on OpenSCENARIO
  8. """
  9. import logging
  10. import os
  11. import xml.etree.ElementTree as ET
  12. import xmlschema
  13. import carla
  14. # pylint: disable=line-too-long
  15. from srunner.scenarioconfigs.scenario_configuration import ActorConfigurationData, ScenarioConfiguration
  16. # pylint: enable=line-too-long
  17. from srunner.scenariomanager.carla_data_provider import CarlaDataProvider # workaround
  18. from srunner.tools.openscenario_parser import OpenScenarioParser, ParameterRef
  19. class OpenScenarioConfiguration(ScenarioConfiguration):
  20. """
  21. Limitations:
  22. - Only one Story + Init is supported per Storyboard
  23. """
  24. def __init__(self, filename, client, custom_params):
  25. self.xml_tree = ET.parse(filename)
  26. self.filename = filename
  27. self._custom_params = custom_params if custom_params is not None else {}
  28. self._validate_openscenario_configuration()
  29. self.client = client
  30. self.catalogs = {}
  31. self.other_actors = []
  32. self.ego_vehicles = []
  33. self.trigger_points = []
  34. self.weather = carla.WeatherParameters()
  35. self.storyboard = self.xml_tree.find("Storyboard")
  36. self.stories = self.storyboard.findall("Story")
  37. self.init = self.storyboard.find("Init")
  38. logging.basicConfig()
  39. self.logger = logging.getLogger("[SR:OpenScenarioConfiguration]")
  40. self._global_parameters = {}
  41. self._set_parameters()
  42. self._parse_openscenario_configuration()
  43. def _validate_openscenario_configuration(self):
  44. """
  45. Validate the given OpenSCENARIO config against the 1.0 XSD
  46. Note: This will throw if the config is not valid. But this is fine here.
  47. """
  48. xsd_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../openscenario/OpenSCENARIO.xsd")
  49. xsd = xmlschema.XMLSchema(xsd_file)
  50. xsd.validate(self.xml_tree)
  51. def _validate_openscenario_catalog_configuration(self, catalog_xml_tree):
  52. """
  53. Validate the given OpenSCENARIO catalog config against the 1.0 XSD
  54. Note: This will throw if the catalog config is not valid. But this is fine here.
  55. """
  56. xsd_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../openscenario/OpenSCENARIO.xsd")
  57. xsd = xmlschema.XMLSchema(xsd_file)
  58. xsd.validate(catalog_xml_tree)
  59. def _parse_openscenario_configuration(self):
  60. """
  61. Parse the given OpenSCENARIO config file, set and validate parameters
  62. """
  63. OpenScenarioParser.set_osc_filepath(os.path.dirname(self.filename))
  64. self._check_version()
  65. self._load_catalogs()
  66. self._set_scenario_name()
  67. self._set_carla_town()
  68. self._set_actor_information()
  69. self._validate_result()
  70. def _check_version(self):
  71. """
  72. Ensure correct OpenSCENARIO version is used
  73. """
  74. header = self.xml_tree.find("FileHeader")
  75. if not (header.attrib.get('revMajor') == "1" and header.attrib.get('revMinor') == "0"):
  76. raise AttributeError("Only OpenSCENARIO 1.0 is supported")
  77. def _load_catalogs(self):
  78. """
  79. Read Catalog xml files into dictionary for later use
  80. NOTE: Catalogs must have distinct names, even across different types
  81. """
  82. catalogs = self.xml_tree.find("CatalogLocations")
  83. if list(catalogs) is None:
  84. return
  85. catalog_types = ["Vehicle",
  86. "Controller",
  87. "Pedestrian",
  88. "MiscObject",
  89. "Environment",
  90. "Maneuver",
  91. "Trajectory",
  92. "Route"]
  93. for catalog_type in catalog_types:
  94. catalog = catalogs.find(catalog_type + "Catalog")
  95. if catalog is None:
  96. continue
  97. catalog_path = catalog.find("Directory").attrib.get('path') + "/" + catalog_type + "Catalog.xosc"
  98. if not os.path.isabs(catalog_path) and "xosc" in self.filename:
  99. catalog_path = os.path.dirname(os.path.abspath(self.filename)) + "/" + catalog_path
  100. if not os.path.isfile(catalog_path):
  101. self.logger.warning(" The %s path for the %s Catalog is invalid", catalog_path, catalog_type)
  102. else:
  103. xml_tree = ET.parse(catalog_path)
  104. self._validate_openscenario_catalog_configuration(xml_tree)
  105. catalog = xml_tree.find("Catalog")
  106. catalog_name = catalog.attrib.get("name")
  107. self.catalogs[catalog_name] = {}
  108. for entry in catalog:
  109. self.catalogs[catalog_name][entry.attrib.get("name")] = entry
  110. def _set_scenario_name(self):
  111. """
  112. Extract the scenario name from the OpenSCENARIO header information
  113. """
  114. header = self.xml_tree.find("FileHeader")
  115. self.name = header.attrib.get('description', 'Unknown')
  116. if self.name.startswith("CARLA:"):
  117. self.name = self.name[6:]
  118. OpenScenarioParser.set_use_carla_coordinate_system()
  119. def _set_carla_town(self):
  120. """
  121. Extract the CARLA town (level) from the RoadNetwork information from OpenSCENARIO
  122. Note: The specification allows multiple Logics elements within the RoadNetwork element.
  123. Hence, there can be multiple towns specified. We just use the _last_ one.
  124. """
  125. for logic in self.xml_tree.find("RoadNetwork").findall("LogicFile"):
  126. self.town = logic.attrib.get('filepath', None)
  127. if self.town is not None and ".xodr" in self.town:
  128. if not os.path.isabs(self.town):
  129. self.town = os.path.dirname(os.path.abspath(self.filename)) + "/" + self.town
  130. if not os.path.exists(self.town):
  131. raise AttributeError("The provided RoadNetwork '{}' does not exist".format(self.town))
  132. # workaround for relative positions during init
  133. world = self.client.get_world()
  134. wmap = None
  135. if world:
  136. world.get_settings()
  137. wmap = world.get_map()
  138. if world is None or (wmap is not None and wmap.name.split('/')[-1] != self.town):
  139. if ".xodr" in self.town:
  140. with open(self.town, 'r', encoding='utf-8') as od_file:
  141. data = od_file.read()
  142. index = data.find('<OpenDRIVE>')
  143. data = data[index:]
  144. old_map = ""
  145. if wmap is not None:
  146. old_map = wmap.to_opendrive()
  147. index = old_map.find('<OpenDRIVE>')
  148. old_map = old_map[index:]
  149. if data != old_map:
  150. self.logger.warning(" Wrong OpenDRIVE map in use. Forcing reload of CARLA world")
  151. vertex_distance = 2.0 # in meters
  152. wall_height = 1.0 # in meters
  153. extra_width = 0.6 # in meters
  154. world = self.client.generate_opendrive_world(str(data),
  155. carla.OpendriveGenerationParameters(
  156. vertex_distance=vertex_distance,
  157. wall_height=wall_height,
  158. additional_width=extra_width,
  159. smooth_junctions=True,
  160. enable_mesh_visibility=True))
  161. print("SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSs")
  162. else:
  163. self.logger.warning(" Wrong map in use. Forcing reload of CARLA world")
  164. self.client.load_world(self.town)
  165. world = self.client.get_world()
  166. CarlaDataProvider.set_world(world)
  167. if CarlaDataProvider.is_sync_mode():
  168. world.tick()
  169. else:
  170. world.wait_for_tick()
  171. else:
  172. CarlaDataProvider.set_world(world)
  173. def _set_parameters(self):
  174. """
  175. Parse the complete scenario definition file, and replace all parameter references
  176. with the actual values
  177. Set _global_parameters.
  178. """
  179. self.xml_tree, self._global_parameters = OpenScenarioParser.set_parameters(self.xml_tree, self._custom_params)
  180. for elem in self.xml_tree.iter():
  181. if elem.find('ParameterDeclarations') is not None:
  182. elem, _ = OpenScenarioParser.set_parameters(elem)
  183. OpenScenarioParser.set_global_parameters(self._global_parameters)
  184. def _set_actor_information(self):
  185. """
  186. Extract all actors and their corresponding specification
  187. NOTE: The rolename property has to be unique!
  188. """
  189. for entity in self.xml_tree.iter("Entities"):
  190. for obj in entity.iter("ScenarioObject"):
  191. rolename = obj.attrib.get('name', 'simulation')
  192. args = {}
  193. for prop in obj.iter("Property"):
  194. key = prop.get('name')
  195. value = prop.get('value')
  196. args[key] = value
  197. for catalog_reference in obj.iter("CatalogReference"):
  198. entry = OpenScenarioParser.get_catalog_entry(self.catalogs, catalog_reference)
  199. if entry.tag == "Vehicle":
  200. self._extract_vehicle_information(entry, rolename, entry, args)
  201. elif entry.tag == "Pedestrian":
  202. self._extract_pedestrian_information(entry, rolename, entry, args)
  203. elif entry.tag == "MiscObject":
  204. self._extract_misc_information(entry, rolename, entry, args)
  205. else:
  206. self.logger.debug(
  207. " A CatalogReference specifies a reference that is not an Entity. Skipping...")
  208. for vehicle in obj.iter("Vehicle"):
  209. self._extract_vehicle_information(obj, rolename, vehicle, args)
  210. for pedestrian in obj.iter("Pedestrian"):
  211. self._extract_pedestrian_information(obj, rolename, pedestrian, args)
  212. for misc in obj.iter("MiscObject"):
  213. self._extract_misc_information(obj, rolename, misc, args)
  214. # Set transform for all actors
  215. # This has to be done in a multi-stage loop to resolve relative position settings
  216. all_actor_transforms_set = False
  217. while not all_actor_transforms_set:
  218. all_actor_transforms_set = True
  219. for actor in self.other_actors + self.ego_vehicles:
  220. if actor.transform is None:
  221. try:
  222. actor.transform = self._get_actor_transform(actor.rolename)
  223. except AttributeError as e:
  224. if "Object '" in str(e):
  225. ref_actor_rolename = str(e).split('\'')[1]
  226. for ref_actor in self.other_actors + self.ego_vehicles:
  227. if ref_actor.rolename == ref_actor_rolename:
  228. if ref_actor.transform is not None:
  229. raise e
  230. break
  231. else:
  232. raise e
  233. if actor.transform is None:
  234. all_actor_transforms_set = False
  235. def _extract_vehicle_information(self, obj, rolename, vehicle, args):
  236. """
  237. Helper function to _set_actor_information for getting vehicle information from XML tree
  238. """
  239. color = None
  240. model = vehicle.attrib.get('name', "vehicle.*")
  241. category = vehicle.attrib.get('vehicleCategory', "car")
  242. ego_vehicle = False
  243. for prop in obj.iter("Property"):
  244. if prop.get('name', '') == 'type':
  245. ego_vehicle = prop.get('value') == 'ego_vehicle'
  246. if prop.get('name', '') == 'color':
  247. color = prop.get('value')
  248. speed = self._get_actor_speed(rolename)
  249. new_actor = ActorConfigurationData(
  250. model, None, rolename, speed, color=color, category=category, args=args)
  251. if ego_vehicle:
  252. self.ego_vehicles.append(new_actor)
  253. else:
  254. self.other_actors.append(new_actor)
  255. def _extract_pedestrian_information(self, obj, rolename, pedestrian, args):
  256. """
  257. Helper function to _set_actor_information for getting pedestrian information from XML tree
  258. """
  259. model = pedestrian.attrib.get('model', "walker.*")
  260. speed = self._get_actor_speed(rolename)
  261. new_actor = ActorConfigurationData(model, None, rolename, speed, category="pedestrian", args=args)
  262. self.other_actors.append(new_actor)
  263. def _extract_misc_information(self, obj, rolename, misc, args):
  264. """
  265. Helper function to _set_actor_information for getting vehicle information from XML tree
  266. """
  267. category = misc.attrib.get('miscObjectCategory')
  268. if category == "barrier":
  269. model = "static.prop.streetbarrier"
  270. elif category == "guardRail":
  271. model = "static.prop.chainbarrier"
  272. else:
  273. model = misc.attrib.get('name')
  274. new_actor = ActorConfigurationData(model, None, rolename, category="misc", args=args)
  275. self.other_actors.append(new_actor)
  276. def _get_actor_transform(self, actor_name):
  277. """
  278. Get the initial actor transform provided by the Init section
  279. Note: - The OpenScenario specification allows multiple definitions. We use the _first_ one
  280. - The OpenScenario specification allows different ways of specifying a position.
  281. We currently support the specification with absolute world coordinates and the relative positions
  282. RelativeWorld, RelativeObject and RelativeLane
  283. - When using relative positions the relevant reference point (e.g. transform of another actor)
  284. should be defined before!
  285. """
  286. actor_transform = carla.Transform()
  287. actor_found = False
  288. for private_action in self.init.iter("Private"):
  289. if private_action.attrib.get('entityRef', None) == actor_name:
  290. if actor_found:
  291. # pylint: disable=line-too-long
  292. self.logger.warning(
  293. " Warning: The actor '%s' was already assigned an initial position. Overwriting pose!", actor_name)
  294. # pylint: enable=line-too-long
  295. actor_found = True
  296. for position in private_action.iter('Position'):
  297. transform = OpenScenarioParser.convert_position_to_transform(
  298. position, actor_list=self.other_actors + self.ego_vehicles)
  299. if transform:
  300. actor_transform = transform
  301. if not actor_found:
  302. # pylint: disable=line-too-long
  303. self.logger.warning(
  304. " Warning: The actor '%s' was not assigned an initial position. Using (0,0,0)", actor_name)
  305. # pylint: enable=line-too-long
  306. return actor_transform
  307. def _get_actor_speed(self, actor_name):
  308. """
  309. Get the initial actor speed provided by the Init section
  310. """
  311. actor_speed = 0
  312. actor_found = False
  313. for private_action in self.init.iter("Private"):
  314. if private_action.attrib.get('entityRef', None) == actor_name:
  315. if actor_found:
  316. # pylint: disable=line-too-long
  317. self.logger.warning(
  318. " Warning: The actor '%s' was already assigned an initial speed. Overwriting inital speed!", actor_name)
  319. # pylint: enable=line-too-long
  320. actor_found = True
  321. for longitudinal_action in private_action.iter('LongitudinalAction'):
  322. for speed in longitudinal_action.iter('SpeedAction'):
  323. for target in speed.iter('SpeedActionTarget'):
  324. for absolute in target.iter('AbsoluteTargetSpeed'):
  325. speed = float(ParameterRef(absolute.attrib.get('value', 0)))
  326. if speed >= 0:
  327. actor_speed = speed
  328. else:
  329. raise AttributeError(
  330. "Warning: Speed value of actor {} must be positive. Speed set to 0.".format(actor_name)) # pylint: disable=line-too-long
  331. return actor_speed
  332. def _validate_result(self):
  333. """
  334. Check that the current scenario configuration is valid
  335. """
  336. if not self.name:
  337. raise AttributeError("No scenario name found")
  338. if not self.town:
  339. raise AttributeError("CARLA level not defined")
  340. if not self.ego_vehicles:
  341. self.logger.warning(" No ego vehicles defined in scenario")