diff --git a/CARLA_VER b/CARLA_VER index 3cb97c603..ff9f1f726 100644 --- a/CARLA_VER +++ b/CARLA_VER @@ -1,2 +1,2 @@ HOST = https://carla-releases.s3.eu-west-3.amazonaws.com/Linux -RELEASE=CARLA_0.9.9 +RELEASE=CARLA_0.9.10 diff --git a/Dockerfile b/Dockerfile index aaa458058..bda085675 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,13 @@ from ubuntu:18.04 # Install base libs run apt-get update && apt-get install --no-install-recommends -y libpng16-16=1.6.34-1ubuntu0.18.04.2 \ libtiff5=4.0.9-5ubuntu0.3 libjpeg8=8c-2ubuntu8 build-essential=12.4ubuntu1 wget=1.19.4-1ubuntu2.2 git=1:2.17.1-1ubuntu0.7 \ - python3.6=3.6.9-1~18.04ubuntu1 python3.6-dev=3.6.9-1~18.04ubuntu1 python3-pip=9.0.1-2.3~ubuntu1.18.04.1 \ - && rm -rf /var/lib/apt/lists/* + python3.6 python3.6-dev python3-pip libxerces-c-dev \ + && rm -rf /var/lib/apt/lists/* # Install python requirements run pip3 install --user setuptools==46.3.0 wheel==0.34.2 && pip3 install py_trees==0.8.3 networkx==2.2 pygame==1.9.6 \ six==1.14.0 numpy==1.18.4 psutil==5.7.0 shapely==1.7.0 xmlschema==1.1.3 ephem==3.7.6.0 tabulate==0.8.7\ -&& mkdir -p /app/scenario_runner +&& mkdir -p /app/scenario_runner # Install scenario_runner copy . /app/scenario_runner @@ -19,7 +19,7 @@ copy . /app/scenario_runner # CARLA_HOST : uri for carla package without trailing slash. # For example, "https://carla-releases.s3.eu-west-3.amazonaws.com/Linux". # If this environment is not passed to docker build, the value -# is taken from CARLA_VER file inside the repository. +# is taken from CARLA_VER file inside the repository. # # CARLA_RELEASE : Name of the package to be used. For example, "CARLA_0.9.9". # If this environment is not passed to docker build, the value diff --git a/Docs/CHANGELOG.md b/Docs/CHANGELOG.md index 62d59063c..9880f58d7 100644 --- a/Docs/CHANGELOG.md +++ b/Docs/CHANGELOG.md @@ -27,6 +27,7 @@ * Changed manual control to be in par with the CARLA version. Among others, added vehicle lights, recording and some new sensors * Removed unsupported scenarios (ChallengeBasic and BackgroundActivity, VehicleTurnLeftAtJunction) * Added a new metrics module, which gives access to all the information about a scenario in order to allow the user to extract any desired information about the simulation. More information [here](metrics_module.md) +* Removed the default randomness at the ControlLoss scenario * OpenSCENARIO support: - Added support for controllers and provided default implementations for vehicles and pedestrians. This required changing the handling of actors, which results in that now all actors are controlled by an OSC controller. Supported controllers: - Pedestrian controller @@ -97,6 +98,7 @@ * Fixed exception bug in spawn function of CarlaDataProvider * Fixed access to private member of CARLA LocalPlanner inside OSC NpcVehicleControl * Fixed handling of OSC LanePosition (#625) +* Fixed bug causing the route repetitions to spawn different background activity * Fixed bug causing the rotate_point function inside RunningRedLightTest to not function properly. ### :ghost: Maintenance * Exposed traffic manager port flag to enable the execution of multiple scenarios on a single machine. diff --git a/requirements.txt b/requirements.txt index 6c92ceae4..bd16c1808 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ networkx==2.2 Shapely==1.6.4.post2 psutil xmlschema==1.0.18 -carla ephem tabulate opencv-python==4.2.0.32 diff --git a/scenario_runner.py b/scenario_runner.py index a7aeb4ef3..c9d893a85 100755 --- a/scenario_runner.py +++ b/scenario_runner.py @@ -88,6 +88,8 @@ def __init__(self, args): self.client = carla.Client(args.host, int(args.port)) self.client.set_timeout(self.client_timeout) + self.traffic_manager = self.client.get_trafficmanager(int(self._args.trafficManagerPort)) + dist = pkg_resources.get_distribution("carla") if LooseVersion(dist.version) < LooseVersion('0.9.8'): raise ImportError("CARLA version 0.9.8 or newer required. CARLA version found: {}".format(dist)) @@ -319,6 +321,9 @@ def _load_and_wait_for_world(self, town, ego_vehicles=None): settings.fixed_delta_seconds = 1.0 / self.frame_rate self.world.apply_settings(settings) + self.traffic_manager.set_synchronous_mode(True) + self.traffic_manager.set_random_device_seed(int(self._args.trafficManagerSeed)) + CarlaDataProvider.set_client(self.client) CarlaDataProvider.set_world(self.world) CarlaDataProvider.set_traffic_manager_port(int(self._args.trafficManagerPort)) @@ -509,6 +514,8 @@ def main(): help='Set the CARLA client timeout value in seconds') parser.add_argument('--trafficManagerPort', default='8000', help='Port to use for the TrafficManager (default: 8000)') + parser.add_argument('--trafficManagerSeed', default='0', + help='Seed used by the TrafficManager (default: 0)') parser.add_argument('--sync', action='store_true', help='Forces the simulation to run synchronously') parser.add_argument('--list', action="store_true", help='List all supported scenarios and exit') diff --git a/srunner/autoagents/human_agent.py b/srunner/autoagents/human_agent.py index 982da33dc..e311ef649 100644 --- a/srunner/autoagents/human_agent.py +++ b/srunner/autoagents/human_agent.py @@ -7,10 +7,9 @@ This module provides a human agent to control the ego vehicle via keyboard """ -import time -from threading import Thread -import cv2 -import numpy as np +from __future__ import print_function + +import json try: import pygame @@ -23,6 +22,7 @@ from pygame.locals import K_d from pygame.locals import K_s from pygame.locals import K_w + from pygame.locals import K_q except ImportError: raise RuntimeError('cannot import pygame, make sure pygame package is installed') @@ -37,13 +37,9 @@ class HumanInterface(object): Class to control a vehicle manually for debugging purposes """ - def __init__(self, parent): - self.quit = False - self._parent = parent + def __init__(self): self._width = 800 self._height = 600 - self._throttle_delta = 0.05 - self._steering_delta = 0.01 self._surface = None pygame.init() @@ -52,39 +48,23 @@ def __init__(self, parent): self._display = pygame.display.set_mode((self._width, self._height), pygame.HWSURFACE | pygame.DOUBLEBUF) pygame.display.set_caption("Human Agent") - def run(self): + def run_interface(self, input_data): """ Run the GUI """ - while not self._parent.agent_engaged and not self.quit: - time.sleep(0.5) - - controller = KeyboardControl() - while not self.quit: - self._clock.tick_busy_loop(20) - controller.parse_events(self._parent.current_control, self._clock) - # Process events - pygame.event.pump() - - # process sensor data - input_data = self._parent.sensor_interface.get_data() - image_center = input_data['Center'][1][:, :, -2::-1] - image_left = input_data['Left'][1][:, :, -2::-1] - image_right = input_data['Right'][1][:, :, -2::-1] - image_rear = input_data['Rear'][1][:, :, -2::-1] - - top_row = np.hstack((image_left, image_center, image_right)) - bottom_row = np.hstack((0 * image_rear, image_rear, 0 * image_rear)) - comp_image = np.vstack((top_row, bottom_row)) - # resize image - image_rescaled = cv2.resize(comp_image, dsize=(self._width, self._height), interpolation=cv2.INTER_CUBIC) - - # display image - self._surface = pygame.surfarray.make_surface(image_rescaled.swapaxes(0, 1)) - if self._surface is not None: - self._display.blit(self._surface, (0, 0)) - pygame.display.flip() + # process sensor data + image_center = input_data['Center'][1][:, :, -2::-1] + + # display image + self._surface = pygame.surfarray.make_surface(image_center.swapaxes(0, 1)) + if self._surface is not None: + self._display.blit(self._surface, (0, 0)) + pygame.display.flip() + def quit_interface(self): + """ + Stops the pygame window + """ pygame.quit() @@ -96,6 +76,7 @@ class HumanAgent(AutonomousAgent): current_control = None agent_engaged = False + prev_timestamp = 0 def setup(self, path_to_conf_file): """ @@ -103,14 +84,9 @@ def setup(self, path_to_conf_file): """ self.agent_engaged = False - self.current_control = carla.VehicleControl() - self.current_control.steer = 0.0 - self.current_control.throttle = 1.0 - self.current_control.brake = 0.0 - self.current_control.hand_brake = False - self._hic = HumanInterface(self) - self._thread = Thread(target=self._hic.run) - self._thread.start() + self.prev_timestamp = 0 + self._hic = HumanInterface() + self._controller = KeyboardControl(path_to_conf_file) def sensors(self): """ @@ -132,17 +108,7 @@ def sensors(self): """ sensors = [{'type': 'sensor.camera.rgb', 'x': 0.7, 'y': 0.0, 'z': 1.60, 'roll': 0.0, 'pitch': 0.0, 'yaw': 0.0, - 'width': 300, 'height': 200, 'fov': 100, 'id': 'Center'}, - - {'type': 'sensor.camera.rgb', 'x': 0.7, 'y': -0.4, 'z': 1.60, 'roll': 0.0, 'pitch': 0.0, - 'yaw': -45.0, 'width': 300, 'height': 200, 'fov': 100, 'id': 'Left'}, - - {'type': 'sensor.camera.rgb', 'x': 0.7, 'y': 0.4, 'z': 1.60, 'roll': 0.0, 'pitch': 0.0, 'yaw': 45.0, - 'width': 300, 'height': 200, 'fov': 100, 'id': 'Right'}, - - {'type': 'sensor.camera.rgb', 'x': -1.8, 'y': 0, 'z': 1.60, 'roll': 0.0, 'pitch': 0.0, - 'yaw': 180.0, 'width': 300, 'height': 200, 'fov': 130, 'id': 'Rear'}, - + 'width': 800, 'height': 600, 'fov': 100, 'id': 'Center'}, {'type': 'sensor.other.gnss', 'x': 0.7, 'y': -0.4, 'z': 1.60, 'id': 'GPS'} ] @@ -153,15 +119,18 @@ def run_step(self, input_data, timestamp): Execute one step of navigation. """ self.agent_engaged = True - time.sleep(0.1) - return self.current_control + self._hic.run_interface(input_data) + + control = self._controller.parse_events(timestamp - self.prev_timestamp) + self.prev_timestamp = timestamp + + return control def destroy(self): """ Cleanup """ - self._hic.quit = True - self._thread.join() + self._hic.quit_interface = True class KeyboardControl(object): @@ -170,33 +139,91 @@ class KeyboardControl(object): Keyboard control for the human agent """ - def __init__(self): + def __init__(self, path_to_conf_file): """ Init """ self._control = carla.VehicleControl() self._steer_cache = 0.0 + self._clock = pygame.time.Clock() + + # Get the mode + if path_to_conf_file: + + with (open(path_to_conf_file, "r")) as f: + lines = f.read().split("\n") + self._mode = lines[0].split(" ")[1] + self._endpoint = lines[1].split(" ")[1] + + # Get the needed vars + if self._mode == "log": + self._log_data = {'records': []} + + elif self._mode == "playback": + self._index = 0 + self._control_list = [] + + with open(self._endpoint) as fd: + try: + self._records = json.load(fd) + self._json_to_control() + except ValueError: + # Moving to Python 3.5+ this can be replaced with json.JSONDecodeError + pass + else: + self._mode = "normal" + self._endpoint = None - def parse_events(self, control, clock): + def _json_to_control(self): + """ + Parses the json file into a list of carla.VehicleControl + """ + + # transform strs into VehicleControl commands + for entry in self._records['records']: + control = carla.VehicleControl(throttle=entry['control']['throttle'], + steer=entry['control']['steer'], + brake=entry['control']['brake'], + hand_brake=entry['control']['hand_brake'], + reverse=entry['control']['reverse'], + manual_gear_shift=entry['control']['manual_gear_shift'], + gear=entry['control']['gear']) + self._control_list.append(control) + + def parse_events(self, timestamp): """ Parse the keyboard events and set the vehicle controls accordingly """ - for event in pygame.event.get(): - if event.type == pygame.QUIT: - return + # Move the vehicle + if self._mode == "playback": + self._parse_json_control() + else: + self._parse_vehicle_keys(pygame.key.get_pressed(), timestamp * 1000) + + # Record the control + if self._mode == "log": + self._record_control() - self._parse_vehicle_keys(pygame.key.get_pressed(), clock.get_time()) - control.steer = self._control.steer - control.throttle = self._control.throttle - control.brake = self._control.brake - control.hand_brake = self._control.hand_brake + return self._control def _parse_vehicle_keys(self, keys, milliseconds): """ Calculate new vehicle controls based on input keys """ - self._control.throttle = 0.6 if keys[K_UP] or keys[K_w] else 0.0 - steer_increment = 15.0 * 5e-4 * milliseconds + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return + elif event.type == pygame.KEYUP: + if event.key == K_q: + self._control.gear = 1 if self._control.reverse else -1 + self._control.reverse = self._control.gear < 0 + + if keys[K_UP] or keys[K_w]: + self._control.throttle = 0.6 + else: + self._control.throttle = 0.0 + + steer_increment = 3e-4 * milliseconds if keys[K_LEFT] or keys[K_a]: self._steer_cache -= steer_increment elif keys[K_RIGHT] or keys[K_d]: @@ -208,3 +235,42 @@ def _parse_vehicle_keys(self, keys, milliseconds): self._control.steer = round(self._steer_cache, 1) self._control.brake = 1.0 if keys[K_DOWN] or keys[K_s] else 0.0 self._control.hand_brake = keys[K_SPACE] + + def _parse_json_control(self): + """ + Gets the control corresponding to the current frame + """ + + if self._index < len(self._control_list): + self._control = self._control_list[self._index] + self._index += 1 + else: + print("JSON file has no more entries") + + def _record_control(self): + """ + Saves the list of control into a json file + """ + + new_record = { + 'control': { + 'throttle': self._control.throttle, + 'steer': self._control.steer, + 'brake': self._control.brake, + 'hand_brake': self._control.hand_brake, + 'reverse': self._control.reverse, + 'manual_gear_shift': self._control.manual_gear_shift, + 'gear': self._control.gear + } + } + + self._log_data['records'].append(new_record) + + def __del__(self): + """ + Delete method + """ + # Get ready to log user commands + if self._mode == "log" and self._log_data: + with open(self._endpoint, 'w') as fd: + json.dump(self._log_data, fd, indent=4, sort_keys=True) diff --git a/srunner/autoagents/human_agent_config.txt b/srunner/autoagents/human_agent_config.txt new file mode 100644 index 000000000..bd1e6d376 --- /dev/null +++ b/srunner/autoagents/human_agent_config.txt @@ -0,0 +1,9 @@ +mode: log # Either 'log' or 'playback' +file: test.json # path to the file with the controls + + +This is the configuration file of the human agent. This agent incorporates two modes. +The log mode parses the controls given to the vehicle into a dictionary and records them into a .json file. +This file can be read by the playback mode to control the vehicle in the same way, resulting in a deterministic agent. +The file name can be chosen, and is the second argument of this config file. + diff --git a/srunner/scenariomanager/actorcontrols/npc_vehicle_control.py b/srunner/scenariomanager/actorcontrols/npc_vehicle_control.py index ac9d7fb86..3d747946d 100644 --- a/srunner/scenariomanager/actorcontrols/npc_vehicle_control.py +++ b/srunner/scenariomanager/actorcontrols/npc_vehicle_control.py @@ -103,4 +103,4 @@ def run_step(self): yaw = self._actor.get_transform().rotation.yaw * (math.pi / 180) vx = math.cos(yaw) * target_speed vy = math.sin(yaw) * target_speed - self._actor.set_velocity(carla.Vector3D(vx, vy, 0)) + self._actor.set_target_velocity(carla.Vector3D(vx, vy, 0)) diff --git a/srunner/scenariomanager/actorcontrols/simple_vehicle_control.py b/srunner/scenariomanager/actorcontrols/simple_vehicle_control.py index 524af2026..9358727f8 100644 --- a/srunner/scenariomanager/actorcontrols/simple_vehicle_control.py +++ b/srunner/scenariomanager/actorcontrols/simple_vehicle_control.py @@ -168,7 +168,7 @@ def run_step(self): if self._reached_goal: # Reached the goal, so stop velocity = carla.Vector3D(0, 0, 0) - self._actor.set_velocity(velocity) + self._actor.set_target_velocity(velocity) return self._reached_goal = False @@ -250,7 +250,7 @@ def _set_new_velocity(self, next_location): velocity.x = direction.x / direction_norm * target_speed velocity.y = direction.y / direction_norm * target_speed - self._actor.set_velocity(velocity) + self._actor.set_target_velocity(velocity) # set new angular velocity current_yaw = CarlaDataProvider.get_transform(self._actor).rotation.yaw @@ -275,7 +275,7 @@ def _set_new_velocity(self, next_location): angular_velocity.z = 0 else: angular_velocity.z = delta_yaw / (direction_norm / target_speed) - self._actor.set_angular_velocity(angular_velocity) + self._actor.set_target_angular_velocity(angular_velocity) self._last_update = current_time diff --git a/srunner/scenariomanager/actorcontrols/vehicle_longitudinal_control.py b/srunner/scenariomanager/actorcontrols/vehicle_longitudinal_control.py index f9ebb3320..9d6b9ee82 100644 --- a/srunner/scenariomanager/actorcontrols/vehicle_longitudinal_control.py +++ b/srunner/scenariomanager/actorcontrols/vehicle_longitudinal_control.py @@ -66,4 +66,4 @@ def run_step(self): yaw = self._actor.get_transform().rotation.yaw * (math.pi / 180) vx = math.cos(yaw) * self._target_speed vy = math.sin(yaw) * self._target_speed - self._actor.set_velocity(carla.Vector3D(vx, vy, 0)) + self._actor.set_target_velocity(carla.Vector3D(vx, vy, 0)) diff --git a/srunner/scenariomanager/carla_data_provider.py b/srunner/scenariomanager/carla_data_provider.py index e9c7e1943..6b8b3c35e 100644 --- a/srunner/scenariomanager/carla_data_provider.py +++ b/srunner/scenariomanager/carla_data_provider.py @@ -60,7 +60,8 @@ class CarlaDataProvider(object): # pylint: disable=too-many-public-methods _blueprint_library = None _ego_vehicle_route = None _traffic_manager_port = 8000 - _rng = random.RandomState(2000) + _random_seed = 2000 + _rng = random.RandomState(_random_seed) @staticmethod def register_actor(actor): @@ -789,3 +790,4 @@ def cleanup(): CarlaDataProvider._client = None CarlaDataProvider._spawn_points = None CarlaDataProvider._spawn_index = 0 + CarlaDataProvider._rng = random.RandomState(CarlaDataProvider._random_seed) diff --git a/srunner/scenariomanager/scenario_manager.py b/srunner/scenariomanager/scenario_manager.py index 13a1474b5..054f58930 100644 --- a/srunner/scenariomanager/scenario_manager.py +++ b/srunner/scenariomanager/scenario_manager.py @@ -167,6 +167,9 @@ def _tick_scenario(self, timestamp): if self._agent is not None: ego_action = self._agent() + if self._agent is not None: + self.ego_vehicles[0].apply_control(ego_action) + # Tick scenario self.scenario_tree.tick_once() @@ -178,9 +181,6 @@ def _tick_scenario(self, timestamp): if self.scenario_tree.status != py_trees.common.Status.RUNNING: self._running = False - if self._agent is not None: - self.ego_vehicles[0].apply_control(ego_action) - if self._sync_mode and self._running and self._watchdog.get_status(): CarlaDataProvider.get_world().tick() diff --git a/srunner/scenariomanager/scenarioatomics/atomic_behaviors.py b/srunner/scenariomanager/scenarioatomics/atomic_behaviors.py index 749f5b610..309527f94 100644 --- a/srunner/scenariomanager/scenarioatomics/atomic_behaviors.py +++ b/srunner/scenariomanager/scenarioatomics/atomic_behaviors.py @@ -846,8 +846,8 @@ def initialise(self): super(ActorTransformSetterToOSCPosition, self).initialise() if self._actor.is_alive: - self._actor.set_velocity(carla.Vector3D(0, 0, 0)) - self._actor.set_angular_velocity(carla.Vector3D(0, 0, 0)) + self._actor.set_target_velocity(carla.Vector3D(0, 0, 0)) + self._actor.set_target_angular_velocity(carla.Vector3D(0, 0, 0)) def update(self): """ @@ -1767,7 +1767,7 @@ def initialise(self): vx = math.cos(yaw) * self._init_speed vy = math.sin(yaw) * self._init_speed - self._actor.set_velocity(carla.Vector3D(vx, vy, 0)) + self._actor.set_target_velocity(carla.Vector3D(vx, vy, 0)) def update(self): """ @@ -1877,8 +1877,8 @@ def __init__(self, actor, transform, physics=True, name="ActorTransformSetter"): def initialise(self): if self._actor.is_alive: - self._actor.set_velocity(carla.Vector3D(0, 0, 0)) - self._actor.set_angular_velocity(carla.Vector3D(0, 0, 0)) + self._actor.set_target_velocity(carla.Vector3D(0, 0, 0)) + self._actor.set_target_angular_velocity(carla.Vector3D(0, 0, 0)) self._actor.set_transform(self._transform) super(ActorTransformSetter, self).initialise() diff --git a/srunner/scenariomanager/timer.py b/srunner/scenariomanager/timer.py index 2c652d75c..c79f50ae5 100644 --- a/srunner/scenariomanager/timer.py +++ b/srunner/scenariomanager/timer.py @@ -72,6 +72,13 @@ def get_wallclocktime(): """ return GameTime._platform_timestamp + @staticmethod + def get_frame(): + """ + Returns elapsed game time + """ + return GameTime._last_frame + class SimulationTimeCondition(py_trees.behaviour.Behaviour): diff --git a/srunner/scenarios/control_loss.py b/srunner/scenarios/control_loss.py index 8dc91e337..0742b1bd0 100644 --- a/srunner/scenarios/control_loss.py +++ b/srunner/scenarios/control_loss.py @@ -12,7 +12,7 @@ regains control and corrects it's course. """ -import random +import numpy.random as random import py_trees import carla @@ -61,6 +61,7 @@ def __init__(self, world, ego_vehicles, config, randomize=False, debug_mode=Fals self._reference_waypoint = self._map.get_waypoint(config.trigger_points[0].location) self.loc_list = [] self.obj = [] + self._randomize = randomize super(ControlLoss, self).__init__("ControlLoss", ego_vehicles, config, @@ -72,8 +73,11 @@ def _initialize_actors(self, config): """ Custom initialization """ - self._distance = random.sample(range(10, 80), 3) - self._distance = sorted(self._distance) + if self._randomize: + self._distance = random.randint(low=10, high=80, size=3) + self._distance = sorted(self._distance) + else: + self._distance = [14, 48, 74] first_loc, _ = get_location_in_distance_from_wp(self._reference_waypoint, self._distance[0]) second_loc, _ = get_location_in_distance_from_wp(self._reference_waypoint, self._distance[1]) third_loc, _ = get_location_in_distance_from_wp(self._reference_waypoint, self._distance[2])