From 0f28adb9f91924ccb924e649ddc9ab274eceba6f Mon Sep 17 00:00:00 2001 From: nsubiron Date: Sat, 18 Nov 2017 17:51:05 +0100 Subject: [PATCH] #32 Clean up carla_manual_control, make API more robust, add image converter --- .gitignore | 1 + PythonClient/carla/__init__.py | 3 - PythonClient/carla/client.py | 8 +- PythonClient/carla/image_converter.py | 86 +++++ PythonClient/carla/settings.py | 23 +- PythonClient/carla/tcp.py | 38 +- PythonClient/carla/util.py | 31 +- PythonClient/carla_manual_control.py | 465 ++++++++++-------------- PythonClient/client_example.py | 94 ++--- PythonClient/test/__init__.py | 0 PythonClient/{carla => test}/console.py | 5 +- PythonClient/test/test_client.py | 5 +- 12 files changed, 407 insertions(+), 352 deletions(-) create mode 100644 PythonClient/carla/image_converter.py create mode 100644 PythonClient/test/__init__.py rename PythonClient/{carla => test}/console.py (98%) diff --git a/.gitignore b/.gitignore index 8dc2d97e9..27841e473 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ Util/Build .tags* .vs __pycache__ +_images core diff --git a/PythonClient/carla/__init__.py b/PythonClient/carla/__init__.py index 5c2828245..e69de29bb 100644 --- a/PythonClient/carla/__init__.py +++ b/PythonClient/carla/__init__.py @@ -1,3 +0,0 @@ -from .carla import CARLA -from .protoc import SceneDescription,EpisodeStart,EpisodeReady,Control,Measurements,RequestNewEpisode - diff --git a/PythonClient/carla/client.py b/PythonClient/carla/client.py index 21ad4a9d7..3be2b2157 100644 --- a/PythonClient/carla/client.py +++ b/PythonClient/carla/client.py @@ -20,6 +20,9 @@ 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): with util.make_connection(CarlaClient, host, world_port, timeout) as client: @@ -101,7 +104,7 @@ class CarlaClient(object): pb_message.ParseFromString(data) # Read images. images_raw_data = self._stream_client.read() - return pb_message, CarlaImage.parse_raw_data(images_raw_data) + return pb_message, CarlaImage._parse_raw_data(images_raw_data) def send_control(self, *args, **kwargs): """Send vehicle control for the current frame.""" @@ -119,7 +122,7 @@ class CarlaClient(object): class CarlaImage(object): @staticmethod - def parse_raw_data(raw_data): + def _parse_raw_data(raw_data): getval = lambda index: struct.unpack('. + +""" +Handy conversions for CARLA images. + +The functions here are provided for real-time display, if you want to save the +converted images, save the images from Python without conversion and convert +them afterwards with the C++ implementation at "Util/ImageConverter" as it +provides considerably better performance. +""" + + +try: + + import numpy + +except ImportError: + + raise RuntimeError('cannot import numpy, make sure numpy package is installed') + + +def to_numpy_array(image): + """Convert a CARLA raw image to a BGRA numpy array.""" + array = numpy.frombuffer(image.raw, dtype=numpy.dtype("uint8")) + array = numpy.reshape(array, (image.height, image.width, 4)) + array = numpy.transpose(array, (1, 0, 2)) + return array + + +def to_rgb_array(image): + """Convert a CARLA raw image to a RGB numpy array.""" + array = to_numpy_array(image) + # Convert BGRA to RGB. + array = array[:, :, :3] + array = array[:, :, ::-1] + return array + + +def labels_to_cityscapes_palette(image): + """ + Converts an image containing CARLA semantic segmentation labels to + Cityscapes palette. + """ + classes = { + 0: [0, 0, 0], # None + 1: [70, 70, 70], # Buildings + 2: [190, 153, 153], # Fences + 3: [72, 0, 90], # Other + 4: [220, 20, 60], # Pedestrians + 5: [153, 153, 153], # Poles + 6: [157, 234, 50], # RoadLines + 7: [128, 64, 128], # Roads + 8: [244, 35, 232], # Sidewalks + 9: [107, 142, 35], # Vegetation + 10: [0, 0, 255], # Vehicles + 11: [102, 102, 156], # Walls + 12: [220, 220, 0] # TrafficSigns + } + array = to_numpy_array(image) + array = array[:, :, 2] + result = numpy.zeros((array.shape[0], array.shape[1], 3)) + for (key, value) in classes.items(): + result[numpy.where(array == key)] = value + return result + + +def depth_to_grayscale(image): + """ + Converts an image containing CARLA encoded depth-map to logarithmic + grayscale. + """ + array = to_numpy_array(image) + array = array.astype(numpy.float32) + # Apply (R + G * 256 + B * 256 * 256) / (256 * 256 * 256 - 1). + grayscale = numpy.dot(array[:, :,:3], [256.0 * 256.0, 256.0, 1.0]) + grayscale /= (256.0 * 256.0 * 256.0 - 1.0) + # Convert to logarithmic depth. + logdepth = numpy.ones(grayscale.shape) + (numpy.log(grayscale) / 5.70378) + logdepth = numpy.clip(logdepth, 0.0, 1.0) + logdepth *= 255.0 + # Expand to three colors. + return numpy.repeat(logdepth[:, :, numpy.newaxis], 3, axis=2) diff --git a/PythonClient/carla/settings.py b/PythonClient/carla/settings.py index dd6892939..de81fba3d 100644 --- a/PythonClient/carla/settings.py +++ b/PythonClient/carla/settings.py @@ -6,9 +6,17 @@ """CARLA Settings""" -import configparser import io import random +import sys + +try: + + from configparser import ConfigParser + +except ImportError: + + from ConfigParser import RawConfigParser as ConfigParser MAX_NUMBER_OF_WEATHER_IDS = 14 @@ -22,10 +30,10 @@ class Camera(object): self.ImageSizeX = 800 self.ImageSizeY = 600 self.CameraFOV = 90 - self.CameraPositionX = 15 + self.CameraPositionX = 140 self.CameraPositionY = 0 - self.CameraPositionZ = 123 - self.CameraRotationPitch = 8 + self.CameraPositionZ = 140 + self.CameraRotationPitch = 0 self.CameraRotationRoll = 0 self.CameraRotationYaw = 0 self.set(**kwargs) @@ -92,7 +100,7 @@ class CarlaSettings(object): self._cameras.append(camera) def __str__(self): - ini = configparser.ConfigParser() + ini = ConfigParser() ini.optionxform=str S_SERVER = 'CARLA/Server' S_LEVEL = 'CARLA/LevelSettings' @@ -131,6 +139,9 @@ class CarlaSettings(object): 'CameraRotationRoll', 'CameraRotationYaw']) - text = io.StringIO() + if sys.version_info >= (3, 0): + text = io.StringIO() + else: + text = io.BytesIO() ini.write(text) return text.getvalue().replace(' = ', '=') diff --git a/PythonClient/carla/tcp.py b/PythonClient/carla/tcp.py index 3033ae604..00a9abe25 100644 --- a/PythonClient/carla/tcp.py +++ b/PythonClient/carla/tcp.py @@ -11,21 +11,29 @@ import socket import struct +class TCPConnectionError(Exception): + pass + + class TCPClient(object): def __init__(self, host, port, timeout): self._host = host self._port = port self._timeout = timeout self._socket = None + self._logprefix = '(%s:%s) ' % (self._host, self._port) def connect(self): - self._socket = socket.create_connection(address=(self._host, self._port), timeout=self._timeout) - self._socket.settimeout(self._timeout) - self._log('connected') + try: + self._socket = socket.create_connection(address=(self._host, self._port), timeout=self._timeout) + self._socket.settimeout(self._timeout) + logging.debug(self._logprefix + 'connected') + except Exception as exception: + self._reraise_exception_as_tcp_error('failed to connect', exception) def disconnect(self): if self._socket is not None: - self._log('disconnecting...') + logging.debug(self._logprefix + 'disconnecting') self._socket.close() self._socket = None @@ -34,29 +42,35 @@ class TCPClient(object): def write(self, message): if self._socket is None: - raise RuntimeError('%s:%s not connected' % (self._host, self._port)) + raise TCPConnectionError(self._logprefix + 'not connected') header = struct.pack(' 0: - data = self._socket.recv(length) + try: + data = self._socket.recv(length) + except Exception as exception: + self._reraise_exception_as_tcp_error('failed to read data', exception) if not data: - raise RuntimeError('%s:%s connection closed' % (self._host, self._port)) + raise TCPConnectionError(self._logprefix + 'connection closed') buf += data length -= len(data) return buf - def _log(self, message, *args): - logging.debug('tcpclient %s:%d - ' + message, self._host, self._port, *args) + def _reraise_exception_as_tcp_error(self, message, exception): + raise TCPConnectionError('%s%s: %s' % (self._logprefix, message, exception)) diff --git a/PythonClient/carla/util.py b/PythonClient/carla/util.py index 606d41401..094dbbbb2 100644 --- a/PythonClient/carla/util.py +++ b/PythonClient/carla/util.py @@ -5,14 +5,11 @@ # For a copy, see . import datetime +import sys from contextlib import contextmanager -def to_hex_str(header): - return ':'.join('{:02x}'.format(ord(c)) for c in header) - - @contextmanager def make_connection(client_type, *args, **kwargs): """Context manager to create and connect a networking object.""" @@ -36,3 +33,29 @@ class StopWatch(object): def milliseconds(self): return 1000.0 * (self.end - self.start).total_seconds() + + +def to_hex_str(header): + return ':'.join('{:02x}'.format(ord(c)) for c in header) + + +if sys.version_info >= (3, 3): + + import shutil + + def print_over_same_line(text): + terminal_width = shutil.get_terminal_size((80, 20)).columns + empty_space = max(0, terminal_width - len(text)) + sys.stdout.write('\r' + text + empty_space * ' ') + sys.stdout.flush() + +else: + + # Workaround for older Python versions. + def print_over_same_line(text): + line_length = max(print_over_same_line._last_line_length, len(text)) + empty_space = max(0, line_length - len(text)) + sys.stdout.write('\r' + text + empty_space * ' ') + sys.stdout.flush() + print_over_same_line._last_line_length = line_length + print_over_same_line._last_line_length = 0 diff --git a/PythonClient/carla_manual_control.py b/PythonClient/carla_manual_control.py index d487914aa..cbcda8557 100755 --- a/PythonClient/carla_manual_control.py +++ b/PythonClient/carla_manual_control.py @@ -6,8 +6,8 @@ # This work is licensed under the terms of the MIT license. # For a copy, see . -# Keyboard controlling for carla. Please refer to carla_use_example for a -# simpler and more documented example. +# Keyboard controlling for carla. Please refer to client_example for a simpler +# and more documented example. """ Welcome to CARLA manual control. @@ -29,324 +29,237 @@ from __future__ import print_function import argparse import logging +import random import sys import time -import numpy as np - -import matplotlib.pyplot as plt - -import pygame -from pygame.locals import * - -from carla.client import CarlaClient +try: + import pygame + from pygame.locals import * +except ImportError: + raise RuntimeError('cannot import pygame, make sure pygame package is installed') try: - from carla.carla_server_pb2 import Control + import numpy as np except ImportError: - raise RuntimeError('cannot import "carla_server_pb2.py", run the protobuf compiler to generate this file') + raise RuntimeError('cannot import numpy, make sure numpy package is installed') + +from carla import image_converter +from carla.client import make_carla_client, VehicleControl +from carla.settings import CarlaSettings, Camera +from carla.tcp import TCPConnectionError +from carla.util import print_over_same_line -def join_classes(labels_image): - classes_join = { - 0: [0, 0, 0], # None - 1: [70, 70, 70], # Buildings - 2: [190, 153, 153], # Fences - 3: [72, 0, 90], # Other - 4: [220, 20, 60], # Pedestrians - 5: [153, 153, 153], # Poles - 6: [157, 234, 50], # RoadLines - 7: [128, 64, 128], # Roads - 8: [244, 35, 232], # Sidewalks - 9: [107, 142, 35], # Vegetation - 10: [0, 0, 255], # Vehicles - 11: [102, 102, 156], # Walls - 12: [220, 220, 0] # TrafficSigns - } - compressed_labels_image = np.zeros((labels_image.shape[0], labels_image.shape[1], 3)) - for (key, value) in classes_join.items(): - compressed_labels_image[np.where(labels_image == key)] = value - return compressed_labels_image +WINDOW_WIDTH = 800 +WINDOW_HEIGHT = 600 +MINI_WINDOW_WIDTH = 320 +MINI_WINDOW_HEIGHT = 180 -def grayscale_colormap(img, colormap): - """Make colormaps from grayscale.""" - cmap = plt.get_cmap(colormap) - rgba_img = cmap(img) - rgb_img = np.delete(rgba_img, 3, 2) - return rgb_img +def make_carla_settings(): + settings = CarlaSettings() + settings.set( + SynchronousMode=False, + NumberOfVehicles=15, + NumberOfPedestrians=30, + WeatherId=random.choice([1, 3, 7, 8, 14])) + settings.randomize_seeds() + camera0 = Camera('CameraRGB') + camera0.set_image_size(WINDOW_WIDTH, WINDOW_HEIGHT) + camera0.set_position(200, 0, 140) + camera0.set_rotation(0.0, 0.0, 0.0) + settings.add_camera(camera0) + camera1 = Camera('CameraDepth', PostProcessing='Depth') + camera1.set_image_size(MINI_WINDOW_WIDTH, MINI_WINDOW_HEIGHT) + camera1.set_position(200, 0, 140) + camera1.set_rotation(0.0, 0.0, 0.0) + settings.add_camera(camera1) + camera2 = Camera('CameraSemSeg', PostProcessing='SemanticSegmentation') + camera2.set_image_size(MINI_WINDOW_WIDTH, MINI_WINDOW_HEIGHT) + camera2.set_position(200, 0, 140) + camera2.set_rotation(0.0, 0.0, 0.0) + settings.add_camera(camera2) + return settings -def convert_depth(depth): - """Convert depth to human readable format.""" - depth = depth.astype(np.float32) - gray_depth = ((depth[:, :, 0] + # Red - depth[:, :, 1] * 256.0 + # Green - depth[:, :, 2] * 256.0 * 256.0)) # Blue - gray_depth /= (256.0 * 256.0 * 256.0 - 1) - color_depth = grayscale_colormap(gray_depth, 'jet') * 255 - return color_depth - - -def get_image_array(image): - new_image = np.frombuffer(image.raw, dtype=np.dtype("uint8")) - return [np.reshape(new_image, (image.height, image.width, 4))] - - -if sys.version_info >= (3, 3): - - import shutil - - def get_terminal_width(): - return shutil.get_terminal_size((80, 20)).columns - -else: - - def get_terminal_width(): - return 120 - - -class App(object): - def __init__( - self, - port=2000, - host='127.0.0.1', - config='./CarlaSettings.ini', - resolution=(800, 600), - verbose=True): - self._running = True - self._display_surf = None - self.port = port - self.host = host - with open(config, 'r') as fp: - self.config = fp.read() - self.verbose = verbose - self.resolution = resolution - self.size = self.weight, self.height = resolution - self.reverse_gear = False - - def on_init(self): - pygame.init() - print(__doc__) - time.sleep(3) - self._display_surf = pygame.display.set_mode( - self.size, pygame.HWSURFACE | pygame.DOUBLEBUF) - logging.debug('Started the PyGame Library') - self._running = True +class Timer(object): + def __init__(self): self.step = 0 - self.prev_step = 0 - self.prev_time = time.time() + self._lap_step = 0 + self._lap_time = time.time() - self.carla = CarlaClient(self.host, self.port) - while True: - try: - self.carla.connect() - break - except Exception as e: - logging.error("Cannot connect: %s", e) - time.sleep(1) - - scene = self.carla.request_new_episode(self.config) - self.num_pos = len(scene.player_start_spots) - player_start = np.random.randint(self.num_pos) - print("Starting episode...") - self.carla.start_episode(player_start) - self.prev_restart_time = time.time() - - def on_event(self, event): - if event.type == pygame.QUIT: - self._running = False - - def on_loop(self): + def tick(self): self.step += 1 - keys = pygame.key.get_pressed() - restart = False - control = Control() + def lap(self): + self._lap_step = self.step + self._lap_time = time.time() - pressed_keys = [] + def ticks_per_second(self): + return float(self.step - self._lap_step) / self.elapsed_seconds_since_lap() + + def elapsed_seconds_since_lap(self): + return time.time() - self._lap_time + + +class CarlaGame(object): + def __init__(self, carla_client): + self.client = carla_client + self._timer = None + self._display = None + self._main_image = None + self._mini_view_image1 = None + self._mini_view_image2 = None + self._is_on_reverse = False + + def execute(self): + pygame.init() + self._initialize_game() + try: + while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return + self._on_loop() + self._on_render() + finally: + pygame.quit() + + def _initialize_game(self): + self._display = pygame.display.set_mode( + (WINDOW_WIDTH, WINDOW_HEIGHT), + pygame.HWSURFACE | pygame.DOUBLEBUF) + logging.debug('pygame started') + self._on_new_episode() + + def _on_new_episode(self): + print('Requesting new episode...') + scene = self.client.request_new_episode(make_carla_settings()) + number_of_player_starts = len(scene.player_start_spots) + player_start = np.random.randint(number_of_player_starts) + self.client.start_episode(player_start) + self._timer = Timer() + + def _on_loop(self): + self._timer.tick() + + measurements, images = self.client.read_measurements() + + self._main_image = images[0] + self._mini_view_image1 = images[1] + self._mini_view_image2 = images[2] + + # Print measurements every second. + if self._timer.elapsed_seconds_since_lap() > 1.0: + self._print_player_measurements(measurements.player_measurements) + self._timer.lap() + + control = self._get_keyboard_control(pygame.key.get_pressed()) + + if control is None: + self._on_new_episode() + else: + self.client.send_control(control) + + def _get_keyboard_control(self, keys): + """ + Return a VehicleControl message based on the pressed keys. Return None + if a new episode was requested. + """ + if keys[K_r]: + return None + control = VehicleControl() if keys[K_LEFT] or keys[K_a]: control.steer = -1.0 - pressed_keys.append('left') if keys[K_RIGHT] or keys[K_d]: control.steer = 1.0 - pressed_keys.append('right') if keys[K_UP] or keys[K_w]: control.throttle = 1.0 - pressed_keys.append('reverse' if self.reverse_gear else 'forward') if keys[K_DOWN] or keys[K_s]: control.brake = 1.0 - pressed_keys.append('brake') if keys[K_SPACE]: control.hand_brake = True - pressed_keys.append('hand-brake') if keys[K_q]: - self.reverse_gear = not self.reverse_gear - pressed_keys.append('toggle reverse') - if keys[K_r]: - pressed_keys.append('reset') - if time.time() - self.prev_restart_time > 2.: - self.prev_restart_time = time.time() - restart = True + self._is_on_reverse = not self._is_on_reverse + control.reverse = self._is_on_reverse + return control - if time.time() - self.prev_restart_time < 2.: - control.throttle = 0.0 - control.steer = 0.0 + def _print_player_measurements(self, player_measurements): + message = 'Step {step} ({fps:.1f} FPS): ' + message += '{speed:.2f} km/h, ' + message += '{other_lane:.0f}% other lane, {offroad:.0f}% off-road' + message = message.format( + step=self._timer.step, + fps=self._timer.ticks_per_second(), + speed=player_measurements.forward_speed, + other_lane=100 * player_measurements.intersection_otherlane, + offroad=100 * player_measurements.intersection_offroad) + print_over_same_line(message) - control.reverse = self.reverse_gear + def _on_render(self): + gap_x = (WINDOW_WIDTH - 2 * MINI_WINDOW_WIDTH) / 3 + mini_image_y = WINDOW_HEIGHT - MINI_WINDOW_HEIGHT - gap_x - measurements, images = self.carla.read_measurements() + if self._main_image is not None: + array = image_converter.to_rgb_array(self._main_image) + surface = pygame.surfarray.make_surface(array) + self._display.blit(surface, (0, 0)) - self.carla.send_control(control) + if self._mini_view_image1 is not None: + array = image_converter.depth_to_grayscale(self._mini_view_image1) + surface = pygame.surfarray.make_surface(array) + self._display.blit(surface, (gap_x, mini_image_y)) - pack = measurements.player_measurements - self.img_vec = get_image_array(images[0]) - self.depth_vec = get_image_array(images[1]) - self.labels_vec = get_image_array(images[2]) - - if time.time() - self.prev_time > 1.: - message = 'Step {step} ({fps:.1f} FPS): ' - message += '{speed:.2f} km/h, ' - message += '{other_lane:.0f}% other lane, {offroad:.0f}% off-road' - message += ': pressed [%s]' % ', '.join(pressed_keys) - - message = message.format( - step=self.step, - fps=float(self.step - self.prev_step) / (time.time() - self.prev_time), - speed=pack.forward_speed, - other_lane=100 * pack.intersection_otherlane, - offroad=100 * pack.intersection_offroad) - - empty_space = max(0, get_terminal_width() - len(message)) - sys.stdout.write('\r' + message + empty_space * ' ') - sys.stdout.flush() - - self.prev_step = self.step - self.prev_time = time.time() - - if restart: - print('\n *** RESTART *** \n') - player_pos = np.random.randint(self.num_pos) - print(' Player pos %d \n' % (player_pos)) - self.carla.newEpisode(player_pos) - - - def on_render(self): - """ - The render method plots the First RGB, the First Depth and First - Semantic Segmentation Camera. - """ - - auxImgResolution = (320, 180) - auxImgYPos = self.height - auxImgResolution[1] - 25 - nImages = 2 - f = ((self.weight - nImages * auxImgResolution[0]) / (nImages + 1) + auxImgResolution[0]) - x_pos = (self.weight - nImages * auxImgResolution[0]) / (nImages + 1) - - if self.img_vec: - self.img_vec[0] = self.img_vec[0][:, :, :3] - self.img_vec[0] = self.img_vec[0][:, :, ::-1] - surface = pygame.surfarray.make_surface( - np.transpose(self.img_vec[0], (1, 0, 2))) - self._display_surf.blit(surface, (0, 0)) - - if self.depth_vec: - self.depth_vec[0] = self.depth_vec[0][:, :, :3] - self.depth_vec[0] = self.depth_vec[0][:, :, ::-1] - self.depth_vec[0] = convert_depth(self.depth_vec[0]) - surface = pygame.surfarray.make_surface( - np.transpose(self.depth_vec[0], (1, 0, 2))) - # Resize image - auxImgXPos = (self.weight / 4) - (auxImgResolution[0] / 2) - surface = pygame.transform.scale(surface, auxImgResolution) - self._display_surf.blit(surface, (x_pos, auxImgYPos)) - x_pos += f - - if self.labels_vec: - self.labels_vec[0] = join_classes(self.labels_vec[0][:, :, 2]) - surface = pygame.surfarray.make_surface( - np.transpose(self.labels_vec[0], (1, 0, 2))) - # Resize image - auxImgXPos = ((self.weight / 4) * 3) - (auxImgResolution[0] / 2) - surface = pygame.transform.scale(surface, auxImgResolution) - self._display_surf.blit(surface, (x_pos, auxImgYPos)) - x_pos += f + if self._mini_view_image2 is not None: + array = image_converter.labels_to_cityscapes_palette(self._mini_view_image2) + surface = pygame.surfarray.make_surface(array) + self._display.blit(surface, (2 * gap_x + MINI_WINDOW_WIDTH, mini_image_y)) pygame.display.flip() - def on_cleanup(self): - self.carla.disconnect() - pygame.quit() - - def on_execute(self): - if self.on_init() == False: - self._running = False - - while(self._running): - try: - - for event in pygame.event.get(): - self.on_event(event) - self.on_loop() - self.on_render() - - except Exception as e: - logging.exception(e) - self._running = False - break - - self.on_cleanup() - def main(): - parser = argparse.ArgumentParser( - description='Run the carla client manual that connects to CARLA server') - parser.add_argument( - 'host', - metavar='HOST', - type=str, - help='host to connect to') - parser.add_argument( - 'port', - metavar='PORT', + argparser = argparse.ArgumentParser(description=__doc__) + argparser.add_argument( + '-v', '--verbose', + action='store_true', + dest='debug', + help='print debug information') + argparser.add_argument( + '--host', + metavar='H', + default='localhost', + help='IP of the host server (default: localhost)') + argparser.add_argument( + '-p', '--port', + metavar='P', + default=2000, type=int, - help='port to connect to') + help='TCP port to listen to (default: 2000)') + args = argparser.parse_args() - parser.add_argument( - "-c", - "--config", - help="the path for the server .ini config file that the client sends", - type=str, - default="./CarlaSettings.ini") + log_level = logging.DEBUG if args.debug else logging.INFO + logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level) - parser.add_argument( - "-l", - "--log", - help="activate the log file", - action="store_true") - parser.add_argument( - "-lv", - "--log_verbose", - help="put the log file to screen", - action="store_true") - args = parser.parse_args() + logging.info('listening to server %s:%s', args.host, args.port) - if args.log or args.log_verbose: - LOG_FILENAME = 'log_manual_control.log' - logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG) - if args.log_verbose: # set of functions to put the logging to screen + print(__doc__) - root = logging.getLogger() - root.setLevel(logging.DEBUG) - ch = logging.StreamHandler(sys.stdout) - ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s') - ch.setFormatter(formatter) - root.addHandler(ch) + while True: + try: - theApp = App(port=args.port, host=args.host, config=args.config) - theApp.on_execute() + with make_carla_client(args.host, args.port) as client: + game = CarlaGame(client) + game.execute() + break + + except TCPConnectionError as error: + logging.error(error) + time.sleep(1) + except Exception as exception: + logging.exception(exception) + sys.exit(1) if __name__ == '__main__': diff --git a/PythonClient/client_example.py b/PythonClient/client_example.py index 74e73779e..c5b8e4644 100755 --- a/PythonClient/client_example.py +++ b/PythonClient/client_example.py @@ -8,6 +8,7 @@ """Basic CARLA client example.""" +from __future__ import print_function import argparse import logging @@ -18,11 +19,11 @@ import time from carla.client import make_carla_client -from carla.console import CarlaClientConsole from carla.settings import CarlaSettings, Camera +from carla.tcp import TCPConnectionError -def run_carla_client(host, port, autopilot_on, save_images_to_disk, image_filename_format): +def run_carla_client(host, port, autopilot_on, save_images_to_disk, image_filename_format, settings): # Here we will run 3 episodes with 300 frames each. number_of_episodes = 3 frames_per_episode = 300 @@ -38,40 +39,42 @@ def run_carla_client(host, port, autopilot_on, save_images_to_disk, image_filena for episode in range(0, number_of_episodes): # Start a new episode. - # Create a CarlaSettings object. This object is a handy wrapper - # around the CarlaSettings.ini file. Here we set the configuration - # we want for the new episode. - settings = CarlaSettings() - settings.set( - SynchronousMode=True, - NumberOfVehicles=30, - NumberOfPedestrians=50, - WeatherId=random.choice([1, 3, 7, 8, 14])) - settings.randomize_seeds() + if settings is None: + # Create a CarlaSettings object. This object is a handy wrapper + # around the CarlaSettings.ini file. Here we set the + # configuration we want for the new episode. + settings = CarlaSettings() + settings.set( + SynchronousMode=True, + NumberOfVehicles=20, + NumberOfPedestrians=40, + WeatherId=random.choice([1, 3, 7, 8, 14])) + settings.randomize_seeds() - # Now we want to add a couple of cameras to the player vehicle. We - # will collect the images produced by these cameras every frame. + # Now we want to add a couple of cameras to the player vehicle. + # We will collect the images produced by these cameras every + # frame. - # The default camera captures RGB images of the scene. - camera0 = Camera('CameraRGB') - # Set image resolution in pixels. - camera0.set_image_size(800, 600) - # Set its position relative to the car in centimeters. - camera0.set_position(30, 0, 130) - settings.add_camera(camera0) + # The default camera captures RGB images of the scene. + camera0 = Camera('CameraRGB') + # Set image resolution in pixels. + camera0.set_image_size(800, 600) + # Set its position relative to the car in centimeters. + camera0.set_position(30, 0, 130) + settings.add_camera(camera0) - # Let's add another camera producing ground-truth depth. - camera1 = Camera('CameraDepth', PostProcessing='Depth') - camera1.set_image_size(800, 600) - camera1.set_position(30, 0, 130) - settings.add_camera(camera1) + # Let's add another camera producing ground-truth depth. + camera1 = Camera('CameraDepth', PostProcessing='Depth') + camera1.set_image_size(800, 600) + camera1.set_position(30, 0, 130) + settings.add_camera(camera1) print('Requesting new episode...') # Now we request a new episode with these settings. The server # replies with a scene description containing the available start # spots for the player. Here instead of a CarlaSettings object we - # could also provide a CarlaSettings.ini file as string. + # can also provide a CarlaSettings.ini file as string. scene = client.request_new_episode(settings) # Choose one player start at random. @@ -152,8 +155,8 @@ def main(): argparser.add_argument( '--host', metavar='H', - default='127.0.0.1', - help='IP of the host server (default: 127.0.0.1)') + default='localhost', + help='IP of the host server (default: localhost)') argparser.add_argument( '-p', '--port', metavar='P', @@ -169,25 +172,24 @@ def main(): action='store_true', help='save images to disk') argparser.add_argument( - '-c', '--console', - action='store_true', - help='start the client console') + '-c', '--carla-settings', + metavar='PATH', + default=None, + help='Path to a "CarlaSettings.ini" file') args = argparser.parse_args() log_level = logging.DEBUG if args.debug else logging.INFO - logging.basicConfig(format='carla_client: %(levelname)s: %(message)s', level=log_level) + logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level) logging.info('listening to server %s:%s', args.host, args.port) - if args.console: - args.synchronous = True - cmd = CarlaClientConsole(args) - try: - cmd.cmdloop() - finally: - cmd.cleanup() - return + if args.carla_settings is not None: + logging.info('reading CarlaSettings from %r', args.carla_settings) + with open(args.carla_settings, 'r') as fp: + settings = fp.read() + else: + settings = None while True: try: @@ -197,14 +199,18 @@ def main(): port=args.port, autopilot_on=args.autopilot, save_images_to_disk=args.images_to_disk, - image_filename_format='_images/episode_{:0>3d}/camera_{:0>3d}/image_{:0>5d}.png') + image_filename_format='_images/episode_{:0>3d}/camera_{:0>3d}/image_{:0>5d}.png', + settings=settings) if end: return - except Exception as exception: - logging.error('exception: %s', exception) + except TCPConnectionError as error: + logging.error(error) time.sleep(1) + except Exception as exception: + logging.exception(exception) + sys.exit(1) if __name__ == '__main__': diff --git a/PythonClient/test/__init__.py b/PythonClient/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/PythonClient/carla/console.py b/PythonClient/test/console.py similarity index 98% rename from PythonClient/carla/console.py rename to PythonClient/test/console.py index 60eae2ed0..8e9dd0c16 100644 --- a/PythonClient/carla/console.py +++ b/PythonClient/test/console.py @@ -15,8 +15,8 @@ import threading import time -from .client import CarlaClient -from .settings import CarlaSettings, Camera +from carla.client import CarlaClient +from carla.settings import CarlaSettings, Camera class _Control(object): @@ -88,7 +88,6 @@ class CarlaClientConsole(cmd.Cmd): self.done = False self.thread.start() - def cleanup(self): self.do_disconnect() self.done = True diff --git a/PythonClient/test/test_client.py b/PythonClient/test/test_client.py index d41ce7e80..ed1c1d929 100755 --- a/PythonClient/test/test_client.py +++ b/PythonClient/test/test_client.py @@ -20,11 +20,12 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..')) import carla from carla.client import CarlaClient -from carla.console import CarlaClientConsole from carla.settings import CarlaSettings, Camera from carla.tcp import TCPClient from carla.util import make_connection +import console + def run_carla_client(args): with make_connection(CarlaClient, args.host, args.port, timeout=15) as client: @@ -140,7 +141,7 @@ def main(): logging.info('listening to server %s:%s', args.host, args.port) if args.console: - cmd = CarlaClientConsole(args) + cmd = console.CarlaClientConsole(args) try: cmd.cmdloop() finally: