From 1b621ded46f5b7569211eb7aa97b19ace9486414 Mon Sep 17 00:00:00 2001 From: nsubiron Date: Thu, 23 Nov 2017 15:38:29 +0100 Subject: [PATCH] #32 Comment the client API --- PythonClient/carla/client.py | 47 +++++++++++++++++---------- PythonClient/carla/image_converter.py | 9 ++--- PythonClient/carla/sensor.py | 16 +++++---- PythonClient/carla/settings.py | 22 +++++++++---- PythonClient/carla/tcp.py | 19 +++++++++-- PythonClient/carla/util.py | 2 +- PythonClient/manual_control.py | 9 +++-- 7 files changed, 82 insertions(+), 42 deletions(-) diff --git a/PythonClient/carla/client.py b/PythonClient/carla/client.py index 17b26aab4..748200928 100644 --- a/PythonClient/carla/client.py +++ b/PythonClient/carla/client.py @@ -21,44 +21,50 @@ except ImportError: raise RuntimeError('cannot import "carla_server_pb2.py", run the protobuf compiler to generate this file') - VehicleControl = carla_protocol.Control @contextmanager def make_carla_client(host, world_port, timeout=15): + """Context manager for creating and connecting a CarlaClient.""" with util.make_connection(CarlaClient, host, world_port, timeout) as client: yield client class CarlaClient(object): + """The CARLA client. Manages communications with the CARLA server.""" + def __init__(self, host, world_port, timeout=15): self._world_client = tcp.TCPClient(host, world_port, timeout) self._stream_client = tcp.TCPClient(host, world_port + 1, timeout) self._control_client = tcp.TCPClient(host, world_port + 2, timeout) self._current_settings = None - # Controls the state, if an episode is already started. self._is_episode_requested = False self._sensor_names = [] - def connect(self): - self._world_client.connect() + def connect(self, connection_attempts=10): + """ + Try to establish a connection to a CARLA server at the given host:port. + """ + self._world_client.connect(connection_attempts) def disconnect(self): + """Disconnect from server.""" self._control_client.disconnect() self._stream_client.disconnect() self._world_client.disconnect() def connected(self): + """Return whether there is an active connection.""" return self._world_client.connected() def load_settings(self, carla_settings): """ - Load new settings and request a new episode based on these settings to - the server. carla_settings object must be convertible to a str holding a - CarlaSettings.ini. + Load new settings and request a new episode based on these settings. + carla_settings object must be convertible to a str holding the contents + of a CarlaSettings.ini file. - Returns a protobuf object holding the scene description. + Return a protobuf object holding the scene description. """ self._current_settings = carla_settings return self._request_new_episode(carla_settings) @@ -68,8 +74,10 @@ class CarlaClient(object): """ Start the new episode at the player start given by the player_start_index. The list of player starts is retrieved by - "load_settings". Requests a new episode based on the last settings - loaded by "load_settings". + "load_settings". + + The new episode is started based on the last settings loaded by + "load_settings". This function waits until the server answers with an EpisodeReady. """ @@ -101,9 +109,9 @@ class CarlaClient(object): def read_data(self): """ - Read data sent from the server this frame. The episode must be started. - Return the protobuf object with the measurements followed by the raw - data of the sensors. + Read the data sent from the server this frame. The episode must be + started. Return a pair containing the protobuf object containing the + measurements followed by the raw data of the sensors. """ # Read measurements. data = self._stream_client.read() @@ -116,7 +124,12 @@ class CarlaClient(object): return pb_message, self._parse_raw_sensor_data(raw_sensor_data) def send_control(self, *args, **kwargs): - """Send vehicle control for the current frame.""" + """ + Send the VehicleControl to be applied this frame. + + If synchronous mode was requested, the server will pause the simulation + until this message is received. + """ if isinstance(args[0] if args else None, carla_protocol.Control): pb_message = args[0] else: @@ -130,9 +143,8 @@ class CarlaClient(object): def _request_new_episode(self, carla_settings): """ - Request a new episode. Internal function to request information about a - new episode episode that is going to start. It also prepare the client - for reset by disconnecting stream and control clients. + Internal function to request a new episode. Prepare the client for a new + episode by disconnecting agent clients. """ # Disconnect agent clients. self._stream_client.disconnect() @@ -154,6 +166,7 @@ class CarlaClient(object): return pb_message def _parse_raw_sensor_data(self, raw_data): + """Return a dict of {'sensor_name': sensor_data, ...}.""" return dict((name, data) for name, data in zip( self._sensor_names, self._iterate_sensor_data(raw_data))) diff --git a/PythonClient/carla/image_converter.py b/PythonClient/carla/image_converter.py index 5c5be47b8..1a1c2e718 100644 --- a/PythonClient/carla/image_converter.py +++ b/PythonClient/carla/image_converter.py @@ -15,11 +15,8 @@ provides considerably better performance. try: - import numpy - except ImportError: - raise RuntimeError('cannot import numpy, make sure numpy package is installed') @@ -92,10 +89,10 @@ def depth_to_array(image): return grayscale -def depth_to_grayscale(image): +def depth_to_logarithmic_grayscale(image): """ - Convert an image containing CARLA encoded depth-map to logarithmic - grayscale. + Convert an image containing CARLA encoded depth-map to a logarithmic + grayscale image array. """ grayscale = depth_to_array(image) # Convert to logarithmic depth. diff --git a/PythonClient/carla/sensor.py b/PythonClient/carla/sensor.py index 714eb5adf..e8cccb9e9 100644 --- a/PythonClient/carla/sensor.py +++ b/PythonClient/carla/sensor.py @@ -17,14 +17,17 @@ import os class Sensor(object): """ - Base class for sensor descriptions. Used to add sensors to the - CarlaSettings. + Base class for sensor descriptions. Used to add sensors to CarlaSettings. """ pass class Camera(Sensor): - """Camera description. To be added to CarlaSettings.""" + """ + Camera description. This class can be added to a CarlaSettings object to add + a camera to the player vehicle. + """ + def __init__(self, name, **kwargs): self.CameraName = name self.PostProcessing = 'SceneFinal' @@ -66,14 +69,13 @@ class Camera(Sensor): class SensorData(object): - """ - Base class for sensor data returned from the server. - """ + """Base class for sensor data returned from the server.""" pass class Image(SensorData): - """Data generated by a camera.""" + """Data generated by a Camera.""" + def __init__(self, width, height, image_type, raw_data): assert len(raw_data) == 4 * width * height self.width = width diff --git a/PythonClient/carla/settings.py b/PythonClient/carla/settings.py index 7493218b7..5f75f92aa 100644 --- a/PythonClient/carla/settings.py +++ b/PythonClient/carla/settings.py @@ -27,7 +27,11 @@ MAX_NUMBER_OF_WEATHER_IDS = 14 class CarlaSettings(object): - """CARLA settings object. Convertible to str as CarlaSettings.ini.""" + """ + The CarlaSettings object controls the settings of an episode. The __str__ + method retrieves an str with a CarlaSettings.ini file contents. + """ + def __init__(self, **kwargs): # [CARLA/Server] self.SynchronousMode = True @@ -49,25 +53,27 @@ class CarlaSettings(object): raise ValueError('CarlaSettings: no key named %r' % key) setattr(self, key, value) - def get_number_of_agents(self): - if not self.SendNonPlayerAgentsInfo: - return 0 - return self.NumberOfVehicles + self.NumberOfPedestrians - def randomize_seeds(self): + """ + Randomize the seeds of the new episode's pseudo-random number + generators. + """ self.SeedVehicles = random.getrandbits(16) self.SeedPedestrians = random.getrandbits(16) def randomize_weather(self): + """Randomized the WeatherId.""" self.WeatherId = random.randint(0, MAX_NUMBER_OF_WEATHER_IDS) def add_sensor(self, sensor): + """Add a sensor to the player vehicle (see sensor.py).""" if isinstance(sensor, carla_sensor.Camera): self._cameras.append(sensor) else: raise ValueError('Sensor not supported') def __str__(self): + """Converts this object to an INI formatted string.""" ini = ConfigParser() ini.optionxform=str S_SERVER = 'CARLA/Server' @@ -117,6 +123,10 @@ class CarlaSettings(object): def _get_sensor_names(settings): + """ + Return a list with the names of the sensors defined in the settings object. + The settings object can be a CarlaSettings or an INI formatted string. + """ if isinstance(settings, CarlaSettings): return [camera.CameraName for camera in settings._cameras] ini = ConfigParser() diff --git a/PythonClient/carla/tcp.py b/PythonClient/carla/tcp.py index 8f2a51563..ad472b068 100644 --- a/PythonClient/carla/tcp.py +++ b/PythonClient/carla/tcp.py @@ -16,6 +16,14 @@ class TCPConnectionError(Exception): class TCPClient(object): + """ + Basic networking client for TCP connections. Errors occurred during + networking operations are raised as TCPConnectionError. + + Received messages are expected to be prepended by a int32 defining the + message size. Messages are sent following this convention. + """ + def __init__(self, host, port, timeout): self._host = host self._port = port @@ -23,8 +31,10 @@ class TCPClient(object): self._socket = None self._logprefix = '(%s:%s) ' % (self._host, self._port) - def connect(self): - for attempt in range(10): + def connect(self, connection_attempts=10): + """Try to establish a connection to the given host:port.""" + connection_attempts = max(1, connection_attempts) + for attempt in range(1, connection_attempts + 1): try: self._socket = socket.create_connection(address=(self._host, self._port), timeout=self._timeout) self._socket.settimeout(self._timeout) @@ -37,15 +47,18 @@ class TCPClient(object): self._reraise_exception_as_tcp_error('failed to connect', exception) def disconnect(self): + """Disconnect any active connection.""" if self._socket is not None: logging.debug(self._logprefix + 'disconnecting') self._socket.close() self._socket = None def connected(self): + """Return whether there is an active connection.""" return self._socket is not None def write(self, message): + """Send message to the server.""" if self._socket is None: raise TCPConnectionError(self._logprefix + 'not connected') header = struct.pack('