scenario_runner.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. #!/usr/bin/env python
  2. # Copyright (c) 2018-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. Welcome to CARLA scenario_runner
  8. This is the main script to be executed when running a scenario.
  9. It loads the scenario configuration, loads the scenario and manager,
  10. and finally triggers the scenario execution.
  11. """
  12. from __future__ import print_function
  13. import glob
  14. import traceback
  15. import argparse
  16. from argparse import RawTextHelpFormatter
  17. from datetime import datetime
  18. from distutils.version import LooseVersion
  19. import importlib
  20. import inspect
  21. import os
  22. import signal
  23. import sys
  24. import time
  25. import json
  26. import pkg_resources
  27. import carla
  28. from srunner.scenarioconfigs.openscenario_configuration import OpenScenarioConfiguration
  29. from srunner.scenariomanager.carla_data_provider import CarlaDataProvider
  30. from srunner.scenariomanager.scenario_manager import ScenarioManager
  31. from srunner.scenarios.open_scenario import OpenScenario
  32. from srunner.scenarios.route_scenario import RouteScenario
  33. from srunner.tools.scenario_parser import ScenarioConfigurationParser
  34. from srunner.tools.route_parser import RouteParser
  35. # Version of scenario_runner
  36. VERSION = '0.9.13'
  37. class ScenarioRunner(object):
  38. """
  39. This is the core scenario runner module. It is responsible for
  40. running (and repeating) a single scenario or a list of scenarios.
  41. Usage:
  42. scenario_runner = ScenarioRunner(args)
  43. scenario_runner.run()
  44. del scenario_runner
  45. """
  46. ego_vehicles = []
  47. # Tunable parameters
  48. client_timeout = 10.0 # in seconds
  49. wait_for_world = 20.0 # in seconds
  50. frame_rate = 20.0 # in Hz
  51. # CARLA world and scenario handlers
  52. world = None
  53. manager = None
  54. finished = False
  55. additional_scenario_module = None
  56. agent_instance = None
  57. module_agent = None
  58. def __init__(self, args):
  59. """
  60. Setup CARLA client and world
  61. Setup ScenarioManager
  62. """
  63. self._args = args
  64. if args.timeout:
  65. self.client_timeout = float(args.timeout)
  66. # First of all, we need to create the client that will send the requests
  67. # to the simulator. Here we'll assume the simulator is accepting
  68. # requests in the localhost at port 2000.
  69. self.client = carla.Client(args.host, int(args.port))
  70. self.client.set_timeout(self.client_timeout)
  71. dist = pkg_resources.get_distribution("carla")
  72. if LooseVersion(dist.version) < LooseVersion('0.9.12'):
  73. raise ImportError("CARLA version 0.9.12 or newer required. CARLA version found: {}".format(dist))
  74. # Load agent if requested via command line args
  75. # If something goes wrong an exception will be thrown by importlib (ok here)
  76. if self._args.agent is not None:
  77. module_name = os.path.basename(args.agent).split('.')[0]
  78. sys.path.insert(0, os.path.dirname(args.agent))
  79. self.module_agent = importlib.import_module(module_name)
  80. # Create the ScenarioManager
  81. self.manager = ScenarioManager(self._args.debug, self._args.sync, self._args.timeout)
  82. # Create signal handler for SIGINT
  83. self._shutdown_requested = False
  84. if sys.platform != 'win32':
  85. signal.signal(signal.SIGHUP, self._signal_handler)
  86. signal.signal(signal.SIGINT, self._signal_handler)
  87. signal.signal(signal.SIGTERM, self._signal_handler)
  88. self._start_wall_time = datetime.now()
  89. def destroy(self):
  90. """
  91. Cleanup and delete actors, ScenarioManager and CARLA world
  92. """
  93. self._cleanup()
  94. if self.manager is not None:
  95. del self.manager
  96. if self.world is not None:
  97. del self.world
  98. if self.client is not None:
  99. del self.client
  100. def _signal_handler(self, signum, frame):
  101. """
  102. Terminate scenario ticking when receiving a signal interrupt
  103. """
  104. self._shutdown_requested = True
  105. if self.manager:
  106. self.manager.stop_scenario()
  107. def _get_scenario_class_or_fail(self, scenario):
  108. """
  109. Get scenario class by scenario name
  110. If scenario is not supported or not found, exit script
  111. """
  112. # Path of all scenario at "srunner/scenarios" folder + the path of the additional scenario argument
  113. scenarios_list = glob.glob("{}/srunner/scenarios/*.py".format(os.getenv('SCENARIO_RUNNER_ROOT', "./")))
  114. scenarios_list.append(self._args.additionalScenario)
  115. for scenario_file in scenarios_list:
  116. # Get their module
  117. module_name = os.path.basename(scenario_file).split('.')[0]
  118. sys.path.insert(0, os.path.dirname(scenario_file))
  119. scenario_module = importlib.import_module(module_name)
  120. # And their members of type class
  121. for member in inspect.getmembers(scenario_module, inspect.isclass):
  122. if scenario in member:
  123. return member[1]
  124. # Remove unused Python paths
  125. sys.path.pop(0)
  126. print("Scenario '{}' not supported ... Exiting".format(scenario))
  127. sys.exit(-1)
  128. def _cleanup(self):
  129. """
  130. Remove and destroy all actors
  131. """
  132. if self.finished:
  133. return
  134. self.finished = True
  135. # Simulation still running and in synchronous mode?
  136. if self.world is not None and self._args.sync:
  137. try:
  138. # Reset to asynchronous mode
  139. settings = self.world.get_settings()
  140. settings.synchronous_mode = False
  141. settings.fixed_delta_seconds = None
  142. self.world.apply_settings(settings)
  143. self.client.get_trafficmanager(int(self._args.trafficManagerPort)).set_synchronous_mode(False)
  144. except RuntimeError:
  145. sys.exit(-1)
  146. self.manager.cleanup()
  147. CarlaDataProvider.cleanup()
  148. for i, _ in enumerate(self.ego_vehicles):
  149. if self.ego_vehicles[i]:
  150. if not self._args.waitForEgo and self.ego_vehicles[i] is not None and self.ego_vehicles[i].is_alive:
  151. print("Destroying ego vehicle {}".format(self.ego_vehicles[i].id))
  152. self.ego_vehicles[i].destroy()
  153. self.ego_vehicles[i] = None
  154. self.ego_vehicles = []
  155. if self.agent_instance:
  156. self.agent_instance.destroy()
  157. self.agent_instance = None
  158. def _prepare_ego_vehicles(self, ego_vehicles):
  159. """
  160. Spawn or update the ego vehicles
  161. """
  162. if not self._args.waitForEgo:
  163. for vehicle in ego_vehicles:
  164. self.ego_vehicles.append(CarlaDataProvider.request_new_actor(vehicle.model,
  165. vehicle.transform,
  166. vehicle.rolename,
  167. color=vehicle.color,
  168. actor_category=vehicle.category))
  169. else:
  170. ego_vehicle_missing = True
  171. while ego_vehicle_missing:
  172. self.ego_vehicles = []
  173. ego_vehicle_missing = False
  174. for ego_vehicle in ego_vehicles:
  175. ego_vehicle_found = False
  176. carla_vehicles = CarlaDataProvider.get_world().get_actors().filter('vehicle.*')
  177. for carla_vehicle in carla_vehicles:
  178. if carla_vehicle.attributes['role_name'] == ego_vehicle.rolename:
  179. ego_vehicle_found = True
  180. self.ego_vehicles.append(carla_vehicle)
  181. break
  182. if not ego_vehicle_found:
  183. ego_vehicle_missing = True
  184. break
  185. for i, _ in enumerate(self.ego_vehicles):
  186. self.ego_vehicles[i].set_transform(ego_vehicles[i].transform)
  187. CarlaDataProvider.register_actor(self.ego_vehicles[i])
  188. # sync state
  189. if CarlaDataProvider.is_sync_mode():
  190. self.world.tick()
  191. else:
  192. self.world.wait_for_tick()
  193. def _analyze_scenario(self, config):
  194. """
  195. Provide feedback about success/failure of a scenario
  196. """
  197. # Create the filename
  198. current_time = str(datetime.now().strftime('%Y-%m-%d-%H-%M-%S'))
  199. junit_filename = None
  200. json_filename = None
  201. config_name = config.name
  202. if self._args.outputDir != '':
  203. config_name = os.path.join(self._args.outputDir, config_name)
  204. if self._args.junit:
  205. junit_filename = config_name + current_time + ".xml"
  206. if self._args.json:
  207. json_filename = config_name + current_time + ".json"
  208. filename = None
  209. if self._args.file:
  210. filename = config_name + current_time + ".txt"
  211. if not self.manager.analyze_scenario(self._args.output, filename, junit_filename, json_filename):
  212. print("All scenario tests were passed successfully!")
  213. else:
  214. print("Not all scenario tests were successful")
  215. if not (self._args.output or filename or junit_filename):
  216. print("Please run with --output for further information")
  217. def _record_criteria(self, criteria, name):
  218. """
  219. Filter the JSON serializable attributes of the criterias and
  220. dumps them into a file. This will be used by the metrics manager,
  221. in case the user wants specific information about the criterias.
  222. """
  223. file_name = name[:-4] + ".json"
  224. # Filter the attributes that aren't JSON serializable
  225. with open('temp.json', 'w', encoding='utf-8') as fp:
  226. criteria_dict = {}
  227. for criterion in criteria:
  228. criterion_dict = criterion.__dict__
  229. criteria_dict[criterion.name] = {}
  230. for key in criterion_dict:
  231. if key != "name":
  232. try:
  233. key_dict = {key: criterion_dict[key]}
  234. json.dump(key_dict, fp, sort_keys=False, indent=4)
  235. criteria_dict[criterion.name].update(key_dict)
  236. except TypeError:
  237. pass
  238. os.remove('temp.json')
  239. # Save the criteria dictionary into a .json file
  240. with open(file_name, 'w', encoding='utf-8') as fp:
  241. json.dump(criteria_dict, fp, sort_keys=False, indent=4)
  242. def _load_and_wait_for_world(self, town, ego_vehicles=None):
  243. """
  244. Load a new CARLA world and provide data to CarlaDataProvider
  245. """
  246. if self._args.reloadWorld:
  247. self.world = self.client.load_world(town)
  248. else:
  249. # if the world should not be reloaded, wait at least until all ego vehicles are ready
  250. ego_vehicle_found = False
  251. if self._args.waitForEgo:
  252. while not ego_vehicle_found and not self._shutdown_requested:
  253. vehicles = self.client.get_world().get_actors().filter('vehicle.*')
  254. for ego_vehicle in ego_vehicles:
  255. ego_vehicle_found = False
  256. for vehicle in vehicles:
  257. if vehicle.attributes['role_name'] == ego_vehicle.rolename:
  258. ego_vehicle_found = True
  259. break
  260. if not ego_vehicle_found:
  261. print("Not all ego vehicles ready. Waiting ... ")
  262. time.sleep(1)
  263. break
  264. self.world = self.client.get_world()
  265. if self._args.sync:
  266. settings = self.world.get_settings()
  267. settings.synchronous_mode = True
  268. settings.fixed_delta_seconds = 1.0 / self.frame_rate
  269. self.world.apply_settings(settings)
  270. CarlaDataProvider.set_client(self.client)
  271. CarlaDataProvider.set_world(self.world)
  272. # Wait for the world to be ready
  273. if CarlaDataProvider.is_sync_mode():
  274. self.world.tick()
  275. else:
  276. self.world.wait_for_tick()
  277. map_name = CarlaDataProvider.get_map().name.split('/')[-1]
  278. if map_name not in (town, "OpenDriveMap"):
  279. print("The CARLA server uses the wrong map: {}".format(map_name))
  280. print("This scenario requires to use map: {}".format(town))
  281. return False
  282. return True
  283. def _load_and_run_scenario(self, config):
  284. """
  285. Load and run the scenario given by config
  286. """
  287. result = False
  288. if not self._load_and_wait_for_world(config.town, config.ego_vehicles):
  289. self._cleanup()
  290. return False
  291. if self._args.agent:
  292. agent_class_name = self.module_agent.__name__.title().replace('_', '')
  293. try:
  294. self.agent_instance = getattr(self.module_agent, agent_class_name)(self._args.agentConfig)
  295. config.agent = self.agent_instance
  296. except Exception as e: # pylint: disable=broad-except
  297. traceback.print_exc()
  298. print("Could not setup required agent due to {}".format(e))
  299. self._cleanup()
  300. return False
  301. CarlaDataProvider.set_traffic_manager_port(int(self._args.trafficManagerPort))
  302. tm = self.client.get_trafficmanager(int(self._args.trafficManagerPort))
  303. tm.set_random_device_seed(int(self._args.trafficManagerSeed))
  304. if self._args.sync:
  305. tm.set_synchronous_mode(True)
  306. # Prepare scenario
  307. print("Preparing scenario: " + config.name)
  308. try:
  309. self._prepare_ego_vehicles(config.ego_vehicles)
  310. if self._args.openscenario:
  311. scenario = OpenScenario(world=self.world,
  312. ego_vehicles=self.ego_vehicles,
  313. config=config,
  314. config_file=self._args.openscenario,
  315. timeout=100000)
  316. elif self._args.route:
  317. scenario = RouteScenario(world=self.world,
  318. config=config,
  319. debug_mode=self._args.debug)
  320. else:
  321. scenario_class = self._get_scenario_class_or_fail(config.type)
  322. scenario = scenario_class(self.world,
  323. self.ego_vehicles,
  324. config,
  325. self._args.randomize,
  326. self._args.debug)
  327. except Exception as exception: # pylint: disable=broad-except
  328. print("The scenario cannot be loaded")
  329. traceback.print_exc()
  330. print(exception)
  331. self._cleanup()
  332. return False
  333. try:
  334. if self._args.record:
  335. recorder_name = "{}/{}/{}.log".format(
  336. os.getenv('SCENARIO_RUNNER_ROOT', "./"), self._args.record, config.name)
  337. self.client.start_recorder(recorder_name, True)
  338. # Load scenario and run it
  339. self.manager.load_scenario(scenario, self.agent_instance)
  340. self.manager.run_scenario()
  341. # Provide outputs if required
  342. self._analyze_scenario(config)
  343. # Remove all actors, stop the recorder and save all criterias (if needed)
  344. # scenario.remove_all_actors()
  345. if self._args.record:
  346. self.client.stop_recorder()
  347. self._record_criteria(self.manager.scenario.get_criteria(), recorder_name)
  348. result = True
  349. except Exception as e: # pylint: disable=broad-except
  350. traceback.print_exc()
  351. print(e)
  352. result = False
  353. self._cleanup()
  354. return result
  355. def _run_scenarios(self):
  356. """
  357. Run conventional scenarios (e.g. implemented using the Python API of ScenarioRunner)
  358. """
  359. result = False
  360. # Load the scenario configurations provided in the config file
  361. scenario_configurations = ScenarioConfigurationParser.parse_scenario_configuration(
  362. self._args.scenario,
  363. self._args.configFile)
  364. if not scenario_configurations:
  365. print("Configuration for scenario {} cannot be found!".format(self._args.scenario))
  366. return result
  367. # Execute each configuration
  368. for config in scenario_configurations:
  369. for _ in range(self._args.repetitions):
  370. self.finished = False
  371. result = self._load_and_run_scenario(config)
  372. self._cleanup()
  373. return result
  374. def _run_route(self):
  375. """
  376. Run the route scenario
  377. """
  378. result = False
  379. if self._args.route:
  380. routes = self._args.route[0]
  381. scenario_file = self._args.route[1]
  382. single_route = None
  383. if len(self._args.route) > 2:
  384. single_route = self._args.route[2]
  385. # retrieve routes
  386. route_configurations = RouteParser.parse_routes_file(routes, scenario_file, single_route)
  387. for config in route_configurations:
  388. for _ in range(self._args.repetitions):
  389. result = self._load_and_run_scenario(config)
  390. self._cleanup()
  391. return result
  392. def _run_openscenario(self):
  393. """
  394. Run a scenario based on OpenSCENARIO
  395. """
  396. # Load the scenario configurations provided in the config file
  397. if not os.path.isfile(self._args.openscenario):
  398. print("File does not exist")
  399. self._cleanup()
  400. return False
  401. openscenario_params = {}
  402. if self._args.openscenarioparams is not None:
  403. for entry in self._args.openscenarioparams.split(','):
  404. [key, val] = [m.strip() for m in entry.split(':')]
  405. openscenario_params[key] = val
  406. config = OpenScenarioConfiguration(self._args.openscenario, self.client, openscenario_params)
  407. result = self._load_and_run_scenario(config)
  408. self._cleanup()
  409. return result
  410. def run(self):
  411. """
  412. Run all scenarios according to provided commandline args
  413. """
  414. result = True
  415. if self._args.openscenario:
  416. result = self._run_openscenario()
  417. elif self._args.route:
  418. result = self._run_route()
  419. else:
  420. result = self._run_scenarios()
  421. print("No more scenarios .... Exiting")
  422. return result
  423. def main():
  424. """
  425. main function
  426. """
  427. description = ("CARLA Scenario Runner: Setup, Run and Evaluate scenarios using CARLA\n"
  428. "Current version: " + VERSION)
  429. # pylint: disable=line-too-long
  430. parser = argparse.ArgumentParser(description=description,
  431. formatter_class=RawTextHelpFormatter)
  432. parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + VERSION)
  433. parser.add_argument('--host', default='127.0.0.1',
  434. help='IP of the host server (default: localhost)')
  435. parser.add_argument('--port', default='3000',
  436. help='TCP port to listen to (default: 3000)')
  437. parser.add_argument('--timeout', default="10.0",
  438. help='Set the CARLA client timeout value in seconds')
  439. parser.add_argument('--trafficManagerPort', default='8000',
  440. help='Port to use for the TrafficManager (default: 8000)')
  441. parser.add_argument('--trafficManagerSeed', default='0',
  442. help='Seed used by the TrafficManager (default: 0)')
  443. parser.add_argument('--sync', action='store_true',
  444. help='Forces the simulation to run synchronously')
  445. parser.add_argument('--list', action="store_true", help='List all supported scenarios and exit')
  446. parser.add_argument(
  447. '--scenario', help='Name of the scenario to be executed. Use the preposition \'group:\' to run all scenarios of one class, e.g. ControlLoss or FollowLeadingVehicle')
  448. parser.add_argument('--openscenario', help='Provide an OpenSCENARIO definition')
  449. parser.add_argument('--openscenarioparams', help='Overwrited for OpenSCENARIO ParameterDeclaration')
  450. parser.add_argument(
  451. '--route', help='Run a route as a scenario (input: (route_file,scenario_file,[route id]))', nargs='+', type=str)
  452. parser.add_argument(
  453. '--agent', help="Agent used to execute the scenario. Currently only compatible with route-based scenarios.")
  454. parser.add_argument('--agentConfig', type=str, help="Path to Agent's configuration file", default="")
  455. parser.add_argument('--output', action="store_true", help='Provide results on stdout')
  456. parser.add_argument('--file', action="store_true", help='Write results into a txt file')
  457. parser.add_argument('--junit', action="store_true", help='Write results into a junit file')
  458. parser.add_argument('--json', action="store_true", help='Write results into a JSON file')
  459. parser.add_argument('--outputDir', default='', help='Directory for output files (default: this directory)')
  460. parser.add_argument('--configFile', default='', help='Provide an additional scenario configuration file (*.xml)')
  461. parser.add_argument('--additionalScenario', default='', help='Provide additional scenario implementations (*.py)')
  462. parser.add_argument('--debug', action="store_true", help='Run with debug output')
  463. parser.add_argument('--reloadWorld', action="store_true",
  464. help='Reload the CARLA world before starting a scenario (default=True)')
  465. parser.add_argument('--record', type=str, default='',
  466. help='Path were the files will be saved, relative to SCENARIO_RUNNER_ROOT.\nActivates the CARLA recording feature and saves to file all the criteria information.')
  467. parser.add_argument('--randomize', action="store_true", help='Scenario parameters are randomized')
  468. parser.add_argument('--repetitions', default=1, type=int, help='Number of scenario executions')
  469. parser.add_argument('--waitForEgo', action="store_true", help='Connect the scenario to an existing ego vehicle')
  470. arguments = parser.parse_args()
  471. # pylint: enable=line-too-long
  472. if arguments.list:
  473. print("Currently the following scenarios are supported:")
  474. print(*ScenarioConfigurationParser.get_list_of_scenarios(arguments.configFile), sep='\n')
  475. return 1
  476. if not arguments.scenario and not arguments.openscenario and not arguments.route:
  477. print("Please specify either a scenario or use the route mode\n\n")
  478. parser.print_help(sys.stdout)
  479. return 1
  480. if arguments.route and (arguments.openscenario or arguments.scenario):
  481. print("The route mode cannot be used together with a scenario (incl. OpenSCENARIO)'\n\n")
  482. parser.print_help(sys.stdout)
  483. return 1
  484. if arguments.agent and (arguments.openscenario or arguments.scenario):
  485. print("Agents are currently only compatible with route scenarios'\n\n")
  486. parser.print_help(sys.stdout)
  487. return 1
  488. if arguments.openscenarioparams and not arguments.openscenario:
  489. print("WARN: Ignoring --openscenarioparams when --openscenario is not specified")
  490. if arguments.route:
  491. arguments.reloadWorld = True
  492. if arguments.agent:
  493. arguments.sync = True
  494. scenario_runner = None
  495. result = True
  496. try:
  497. scenario_runner = ScenarioRunner(arguments)
  498. result = scenario_runner.run()
  499. except Exception: # pylint: disable=broad-except
  500. traceback.print_exc()
  501. finally:
  502. if scenario_runner is not None:
  503. scenario_runner.destroy()
  504. del scenario_runner
  505. return not result
  506. if __name__ == "__main__":
  507. sys.exit(main())