885 lines
34 KiB
Python
Executable File
885 lines
34 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# Copyright (c) 2018 Intel Labs.
|
|
# authors: German Ros (german.ros@intel.com)
|
|
#
|
|
# This work is licensed under the terms of the MIT license.
|
|
# For a copy, see <https://opensource.org/licenses/MIT>.
|
|
|
|
"""Example of automatic vehicle control from client side."""
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import collections
|
|
import datetime
|
|
import glob
|
|
import logging
|
|
import math
|
|
import os
|
|
import numpy.random as random
|
|
import re
|
|
import sys
|
|
import weakref
|
|
|
|
try:
|
|
import pygame
|
|
from pygame.locals import KMOD_CTRL
|
|
from pygame.locals import K_ESCAPE
|
|
from pygame.locals import K_q
|
|
except ImportError:
|
|
raise RuntimeError('cannot import pygame, make sure pygame package is installed')
|
|
|
|
try:
|
|
import numpy as np
|
|
except ImportError:
|
|
raise RuntimeError(
|
|
'cannot import numpy, make sure numpy package is installed')
|
|
|
|
# ==============================================================================
|
|
# -- Find CARLA module ---------------------------------------------------------
|
|
# ==============================================================================
|
|
try:
|
|
sys.path.append(glob.glob('../carla/dist/carla-*%d.%d-%s.egg' % (
|
|
sys.version_info.major,
|
|
sys.version_info.minor,
|
|
'win-amd64' if os.name == 'nt' else 'linux-x86_64'))[0])
|
|
except IndexError:
|
|
pass
|
|
|
|
# ==============================================================================
|
|
# -- Add PythonAPI for release mode --------------------------------------------
|
|
# ==============================================================================
|
|
try:
|
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/carla')
|
|
except IndexError:
|
|
pass
|
|
|
|
import carla
|
|
from carla import ColorConverter as cc
|
|
|
|
from agents.navigation.behavior_agent import BehaviorAgent # pylint: disable=import-error
|
|
from agents.navigation.basic_agent import BasicAgent # pylint: disable=import-error
|
|
from agents.navigation.constant_velocity_agent import ConstantVelocityAgent # pylint: disable=import-error
|
|
|
|
|
|
# ==============================================================================
|
|
# -- Global functions ----------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
def find_weather_presets():
|
|
"""Method to find weather presets"""
|
|
rgx = re.compile('.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)')
|
|
def name(x): return ' '.join(m.group(0) for m in rgx.finditer(x))
|
|
presets = [x for x in dir(carla.WeatherParameters) if re.match('[A-Z].+', x)]
|
|
return [(getattr(carla.WeatherParameters, x), name(x)) for x in presets]
|
|
|
|
|
|
def get_actor_display_name(actor, truncate=250):
|
|
"""Method to get actor display name"""
|
|
name = ' '.join(actor.type_id.replace('_', '.').title().split('.')[1:])
|
|
return (name[:truncate - 1] + u'\u2026') if len(name) > truncate else name
|
|
|
|
def get_actor_blueprints(world, filter, generation):
|
|
bps = world.get_blueprint_library().filter(filter)
|
|
|
|
if generation.lower() == "all":
|
|
return bps
|
|
|
|
# If the filter returns only one bp, we assume that this one needed
|
|
# and therefore, we ignore the generation
|
|
if len(bps) == 1:
|
|
return bps
|
|
|
|
try:
|
|
int_generation = int(generation)
|
|
# Check if generation is in available generations
|
|
if int_generation in [1, 2, 3]:
|
|
bps = [x for x in bps if int(x.get_attribute('generation')) == int_generation]
|
|
return bps
|
|
else:
|
|
print(" Warning! Actor Generation is not valid. No actor will be spawned.")
|
|
return []
|
|
except:
|
|
print(" Warning! Actor Generation is not valid. No actor will be spawned.")
|
|
return []
|
|
|
|
# ==============================================================================
|
|
# -- World ---------------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
class World(object):
|
|
""" Class representing the surrounding environment """
|
|
|
|
def __init__(self, carla_world, hud, args):
|
|
"""Constructor method"""
|
|
self._args = args
|
|
self.world = carla_world
|
|
try:
|
|
self.map = self.world.get_map()
|
|
except RuntimeError as error:
|
|
print('RuntimeError: {}'.format(error))
|
|
print(' The server could not send the OpenDRIVE (.xodr) file:')
|
|
print(' Make sure it exists, has the same name of your town, and is correct.')
|
|
sys.exit(1)
|
|
self.hud = hud
|
|
self.player = None
|
|
self.collision_sensor = None
|
|
self.lane_invasion_sensor = None
|
|
self.gnss_sensor = None
|
|
self.camera_manager = None
|
|
self._weather_presets = find_weather_presets()
|
|
self._weather_index = 0
|
|
self._actor_filter = args.filter
|
|
self._actor_generation = args.generation
|
|
self.restart(args)
|
|
self.world.on_tick(hud.on_world_tick)
|
|
self.recording_enabled = False
|
|
self.recording_start = 0
|
|
|
|
def restart(self, args):
|
|
"""Restart the world"""
|
|
# Keep same camera config if the camera manager exists.
|
|
cam_index = self.camera_manager.index if self.camera_manager is not None else 0
|
|
cam_pos_id = self.camera_manager.transform_index if self.camera_manager is not None else 0
|
|
|
|
# Get a random blueprint.
|
|
blueprint_list = get_actor_blueprints(self.world, self._actor_filter, self._actor_generation)
|
|
if not blueprint_list:
|
|
raise ValueError("Couldn't find any blueprints with the specified filters")
|
|
blueprint = random.choice(blueprint_list)
|
|
blueprint.set_attribute('role_name', 'hero')
|
|
if blueprint.has_attribute('color'):
|
|
color = random.choice(blueprint.get_attribute('color').recommended_values)
|
|
blueprint.set_attribute('color', color)
|
|
|
|
# Spawn the player.
|
|
if self.player is not None:
|
|
spawn_point = self.player.get_transform()
|
|
spawn_point.location.z += 2.0
|
|
spawn_point.rotation.roll = 0.0
|
|
spawn_point.rotation.pitch = 0.0
|
|
self.destroy()
|
|
self.player = self.world.try_spawn_actor(blueprint, spawn_point)
|
|
self.modify_vehicle_physics(self.player)
|
|
while self.player is None:
|
|
if not self.map.get_spawn_points():
|
|
print('There are no spawn points available in your map/town.')
|
|
print('Please add some Vehicle Spawn Point to your UE4 scene.')
|
|
sys.exit(1)
|
|
spawn_points = self.map.get_spawn_points()
|
|
spawn_point = random.choice(spawn_points) if spawn_points else carla.Transform()
|
|
self.player = self.world.try_spawn_actor(blueprint, spawn_point)
|
|
self.modify_vehicle_physics(self.player)
|
|
|
|
if self._args.sync:
|
|
self.world.tick()
|
|
else:
|
|
self.world.wait_for_tick()
|
|
|
|
# Set up the sensors.
|
|
self.collision_sensor = CollisionSensor(self.player, self.hud)
|
|
self.lane_invasion_sensor = LaneInvasionSensor(self.player, self.hud)
|
|
self.gnss_sensor = GnssSensor(self.player)
|
|
self.camera_manager = CameraManager(self.player, self.hud)
|
|
self.camera_manager.transform_index = cam_pos_id
|
|
self.camera_manager.set_sensor(cam_index, notify=False)
|
|
actor_type = get_actor_display_name(self.player)
|
|
self.hud.notification(actor_type)
|
|
|
|
def next_weather(self, reverse=False):
|
|
"""Get next weather setting"""
|
|
self._weather_index += -1 if reverse else 1
|
|
self._weather_index %= len(self._weather_presets)
|
|
preset = self._weather_presets[self._weather_index]
|
|
self.hud.notification('Weather: %s' % preset[1])
|
|
self.player.get_world().set_weather(preset[0])
|
|
|
|
def modify_vehicle_physics(self, actor):
|
|
#If actor is not a vehicle, we cannot use the physics control
|
|
try:
|
|
physics_control = actor.get_physics_control()
|
|
physics_control.use_sweep_wheel_collision = True
|
|
actor.apply_physics_control(physics_control)
|
|
except Exception:
|
|
pass
|
|
|
|
def tick(self, clock):
|
|
"""Method for every tick"""
|
|
self.hud.tick(self, clock)
|
|
|
|
def render(self, display):
|
|
"""Render world"""
|
|
self.camera_manager.render(display)
|
|
self.hud.render(display)
|
|
|
|
def destroy_sensors(self):
|
|
"""Destroy sensors"""
|
|
self.camera_manager.sensor.destroy()
|
|
self.camera_manager.sensor = None
|
|
self.camera_manager.index = None
|
|
|
|
def destroy(self):
|
|
"""Destroys all actors"""
|
|
actors = [
|
|
self.camera_manager.sensor,
|
|
self.collision_sensor.sensor,
|
|
self.lane_invasion_sensor.sensor,
|
|
self.gnss_sensor.sensor,
|
|
self.player]
|
|
for actor in actors:
|
|
if actor is not None:
|
|
actor.destroy()
|
|
|
|
|
|
# ==============================================================================
|
|
# -- KeyboardControl -----------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
class KeyboardControl(object):
|
|
def __init__(self, world):
|
|
world.hud.notification("Press 'H' or '?' for help.", seconds=4.0)
|
|
|
|
def parse_events(self):
|
|
for event in pygame.event.get():
|
|
if event.type == pygame.QUIT:
|
|
return True
|
|
if event.type == pygame.KEYUP:
|
|
if self._is_quit_shortcut(event.key):
|
|
return True
|
|
|
|
@staticmethod
|
|
def _is_quit_shortcut(key):
|
|
"""Shortcut for quitting"""
|
|
return (key == K_ESCAPE) or (key == K_q and pygame.key.get_mods() & KMOD_CTRL)
|
|
|
|
# ==============================================================================
|
|
# -- HUD -----------------------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
class HUD(object):
|
|
"""Class for HUD text"""
|
|
|
|
def __init__(self, width, height):
|
|
"""Constructor method"""
|
|
self.dim = (width, height)
|
|
font = pygame.font.Font(pygame.font.get_default_font(), 20)
|
|
font_name = 'courier' if os.name == 'nt' else 'mono'
|
|
fonts = [x for x in pygame.font.get_fonts() if font_name in x]
|
|
default_font = 'ubuntumono'
|
|
mono = default_font if default_font in fonts else fonts[0]
|
|
mono = pygame.font.match_font(mono)
|
|
self._font_mono = pygame.font.Font(mono, 12 if os.name == 'nt' else 14)
|
|
self._notifications = FadingText(font, (width, 40), (0, height - 40))
|
|
self.help = HelpText(pygame.font.Font(mono, 24), width, height)
|
|
self.server_fps = 0
|
|
self.frame = 0
|
|
self.simulation_time = 0
|
|
self._show_info = True
|
|
self._info_text = []
|
|
self._server_clock = pygame.time.Clock()
|
|
|
|
def on_world_tick(self, timestamp):
|
|
"""Gets informations from the world at every tick"""
|
|
self._server_clock.tick()
|
|
self.server_fps = self._server_clock.get_fps()
|
|
self.frame = timestamp.frame_count
|
|
self.simulation_time = timestamp.elapsed_seconds
|
|
|
|
def tick(self, world, clock):
|
|
"""HUD method for every tick"""
|
|
self._notifications.tick(world, clock)
|
|
if not self._show_info:
|
|
return
|
|
transform = world.player.get_transform()
|
|
vel = world.player.get_velocity()
|
|
control = world.player.get_control()
|
|
heading = 'N' if abs(transform.rotation.yaw) < 89.5 else ''
|
|
heading += 'S' if abs(transform.rotation.yaw) > 90.5 else ''
|
|
heading += 'E' if 179.5 > transform.rotation.yaw > 0.5 else ''
|
|
heading += 'W' if -0.5 > transform.rotation.yaw > -179.5 else ''
|
|
colhist = world.collision_sensor.get_collision_history()
|
|
collision = [colhist[x + self.frame - 200] for x in range(0, 200)]
|
|
max_col = max(1.0, max(collision))
|
|
collision = [x / max_col for x in collision]
|
|
vehicles = world.world.get_actors().filter('vehicle.*')
|
|
|
|
self._info_text = [
|
|
'Server: % 16.0f FPS' % self.server_fps,
|
|
'Client: % 16.0f FPS' % clock.get_fps(),
|
|
'',
|
|
'Vehicle: % 20s' % get_actor_display_name(world.player, truncate=20),
|
|
'Map: % 20s' % world.map.name.split('/')[-1],
|
|
'Simulation time: % 12s' % datetime.timedelta(seconds=int(self.simulation_time)),
|
|
'',
|
|
'Speed: % 15.0f km/h' % (3.6 * math.sqrt(vel.x**2 + vel.y**2 + vel.z**2)),
|
|
u'Heading:% 16.0f\N{DEGREE SIGN} % 2s' % (transform.rotation.yaw, heading),
|
|
'Location:% 20s' % ('(% 5.1f, % 5.1f)' % (transform.location.x, transform.location.y)),
|
|
'GNSS:% 24s' % ('(% 2.6f, % 3.6f)' % (world.gnss_sensor.lat, world.gnss_sensor.lon)),
|
|
'Height: % 18.0f m' % transform.location.z,
|
|
'']
|
|
if isinstance(control, carla.VehicleControl):
|
|
self._info_text += [
|
|
('Throttle:', control.throttle, 0.0, 1.0),
|
|
('Steer:', control.steer, -1.0, 1.0),
|
|
('Brake:', control.brake, 0.0, 1.0),
|
|
('Reverse:', control.reverse),
|
|
('Hand brake:', control.hand_brake),
|
|
('Manual:', control.manual_gear_shift),
|
|
'Gear: %s' % {-1: 'R', 0: 'N'}.get(control.gear, control.gear)]
|
|
elif isinstance(control, carla.WalkerControl):
|
|
self._info_text += [
|
|
('Speed:', control.speed, 0.0, 5.556),
|
|
('Jump:', control.jump)]
|
|
self._info_text += [
|
|
'',
|
|
'Collision:',
|
|
collision,
|
|
'',
|
|
'Number of vehicles: % 8d' % len(vehicles)]
|
|
|
|
if len(vehicles) > 1:
|
|
self._info_text += ['Nearby vehicles:']
|
|
|
|
def dist(l):
|
|
return math.sqrt((l.x - transform.location.x)**2 + (l.y - transform.location.y)
|
|
** 2 + (l.z - transform.location.z)**2)
|
|
vehicles = [(dist(x.get_location()), x) for x in vehicles if x.id != world.player.id]
|
|
|
|
for dist, vehicle in sorted(vehicles):
|
|
if dist > 200.0:
|
|
break
|
|
vehicle_type = get_actor_display_name(vehicle, truncate=22)
|
|
self._info_text.append('% 4dm %s' % (dist, vehicle_type))
|
|
|
|
def toggle_info(self):
|
|
"""Toggle info on or off"""
|
|
self._show_info = not self._show_info
|
|
|
|
def notification(self, text, seconds=2.0):
|
|
"""Notification text"""
|
|
self._notifications.set_text(text, seconds=seconds)
|
|
|
|
def error(self, text):
|
|
"""Error text"""
|
|
self._notifications.set_text('Error: %s' % text, (255, 0, 0))
|
|
|
|
def render(self, display):
|
|
"""Render for HUD class"""
|
|
if self._show_info:
|
|
info_surface = pygame.Surface((220, self.dim[1]))
|
|
info_surface.set_alpha(100)
|
|
display.blit(info_surface, (0, 0))
|
|
v_offset = 4
|
|
bar_h_offset = 100
|
|
bar_width = 106
|
|
for item in self._info_text:
|
|
if v_offset + 18 > self.dim[1]:
|
|
break
|
|
if isinstance(item, list):
|
|
if len(item) > 1:
|
|
points = [(x + 8, v_offset + 8 + (1 - y) * 30) for x, y in enumerate(item)]
|
|
pygame.draw.lines(display, (255, 136, 0), False, points, 2)
|
|
item = None
|
|
v_offset += 18
|
|
elif isinstance(item, tuple):
|
|
if isinstance(item[1], bool):
|
|
rect = pygame.Rect((bar_h_offset, v_offset + 8), (6, 6))
|
|
pygame.draw.rect(display, (255, 255, 255), rect, 0 if item[1] else 1)
|
|
else:
|
|
rect_border = pygame.Rect((bar_h_offset, v_offset + 8), (bar_width, 6))
|
|
pygame.draw.rect(display, (255, 255, 255), rect_border, 1)
|
|
fig = (item[1] - item[2]) / (item[3] - item[2])
|
|
if item[2] < 0.0:
|
|
rect = pygame.Rect(
|
|
(bar_h_offset + fig * (bar_width - 6), v_offset + 8), (6, 6))
|
|
else:
|
|
rect = pygame.Rect((bar_h_offset, v_offset + 8), (fig * bar_width, 6))
|
|
pygame.draw.rect(display, (255, 255, 255), rect)
|
|
item = item[0]
|
|
if item: # At this point has to be a str.
|
|
surface = self._font_mono.render(item, True, (255, 255, 255))
|
|
display.blit(surface, (8, v_offset))
|
|
v_offset += 18
|
|
self._notifications.render(display)
|
|
self.help.render(display)
|
|
|
|
# ==============================================================================
|
|
# -- FadingText ----------------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
class FadingText(object):
|
|
""" Class for fading text """
|
|
|
|
def __init__(self, font, dim, pos):
|
|
"""Constructor method"""
|
|
self.font = font
|
|
self.dim = dim
|
|
self.pos = pos
|
|
self.seconds_left = 0
|
|
self.surface = pygame.Surface(self.dim)
|
|
|
|
def set_text(self, text, color=(255, 255, 255), seconds=2.0):
|
|
"""Set fading text"""
|
|
text_texture = self.font.render(text, True, color)
|
|
self.surface = pygame.Surface(self.dim)
|
|
self.seconds_left = seconds
|
|
self.surface.fill((0, 0, 0, 0))
|
|
self.surface.blit(text_texture, (10, 11))
|
|
|
|
def tick(self, _, clock):
|
|
"""Fading text method for every tick"""
|
|
delta_seconds = 1e-3 * clock.get_time()
|
|
self.seconds_left = max(0.0, self.seconds_left - delta_seconds)
|
|
self.surface.set_alpha(500.0 * self.seconds_left)
|
|
|
|
def render(self, display):
|
|
"""Render fading text method"""
|
|
display.blit(self.surface, self.pos)
|
|
|
|
# ==============================================================================
|
|
# -- HelpText ------------------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
class HelpText(object):
|
|
""" Helper class for text render"""
|
|
|
|
def __init__(self, font, width, height):
|
|
"""Constructor method"""
|
|
lines = __doc__.split('\n')
|
|
self.font = font
|
|
self.dim = (680, len(lines) * 22 + 12)
|
|
self.pos = (0.5 * width - 0.5 * self.dim[0], 0.5 * height - 0.5 * self.dim[1])
|
|
self.seconds_left = 0
|
|
self.surface = pygame.Surface(self.dim)
|
|
self.surface.fill((0, 0, 0, 0))
|
|
for i, line in enumerate(lines):
|
|
text_texture = self.font.render(line, True, (255, 255, 255))
|
|
self.surface.blit(text_texture, (22, i * 22))
|
|
self._render = False
|
|
self.surface.set_alpha(220)
|
|
|
|
def toggle(self):
|
|
"""Toggle on or off the render help"""
|
|
self._render = not self._render
|
|
|
|
def render(self, display):
|
|
"""Render help text method"""
|
|
if self._render:
|
|
display.blit(self.surface, self.pos)
|
|
|
|
# ==============================================================================
|
|
# -- CollisionSensor -----------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
class CollisionSensor(object):
|
|
""" Class for collision sensors"""
|
|
|
|
def __init__(self, parent_actor, hud):
|
|
"""Constructor method"""
|
|
self.sensor = None
|
|
self.history = []
|
|
self._parent = parent_actor
|
|
self.hud = hud
|
|
world = self._parent.get_world()
|
|
blueprint = world.get_blueprint_library().find('sensor.other.collision')
|
|
self.sensor = world.spawn_actor(blueprint, carla.Transform(), attach_to=self._parent)
|
|
# We need to pass the lambda a weak reference to
|
|
# self to avoid circular reference.
|
|
weak_self = weakref.ref(self)
|
|
self.sensor.listen(lambda event: CollisionSensor._on_collision(weak_self, event))
|
|
|
|
def get_collision_history(self):
|
|
"""Gets the history of collisions"""
|
|
history = collections.defaultdict(int)
|
|
for frame, intensity in self.history:
|
|
history[frame] += intensity
|
|
return history
|
|
|
|
@staticmethod
|
|
def _on_collision(weak_self, event):
|
|
"""On collision method"""
|
|
self = weak_self()
|
|
if not self:
|
|
return
|
|
actor_type = get_actor_display_name(event.other_actor)
|
|
self.hud.notification('Collision with %r' % actor_type)
|
|
impulse = event.normal_impulse
|
|
intensity = math.sqrt(impulse.x ** 2 + impulse.y ** 2 + impulse.z ** 2)
|
|
self.history.append((event.frame, intensity))
|
|
if len(self.history) > 4000:
|
|
self.history.pop(0)
|
|
|
|
# ==============================================================================
|
|
# -- LaneInvasionSensor --------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
class LaneInvasionSensor(object):
|
|
"""Class for lane invasion sensors"""
|
|
|
|
def __init__(self, parent_actor, hud):
|
|
"""Constructor method"""
|
|
self.sensor = None
|
|
self._parent = parent_actor
|
|
self.hud = hud
|
|
world = self._parent.get_world()
|
|
bp = world.get_blueprint_library().find('sensor.other.lane_invasion')
|
|
self.sensor = world.spawn_actor(bp, carla.Transform(), attach_to=self._parent)
|
|
# We need to pass the lambda a weak reference to self to avoid circular
|
|
# reference.
|
|
weak_self = weakref.ref(self)
|
|
self.sensor.listen(lambda event: LaneInvasionSensor._on_invasion(weak_self, event))
|
|
|
|
@staticmethod
|
|
def _on_invasion(weak_self, event):
|
|
"""On invasion method"""
|
|
self = weak_self()
|
|
if not self:
|
|
return
|
|
lane_types = set(x.type for x in event.crossed_lane_markings)
|
|
text = ['%r' % str(x).split()[-1] for x in lane_types]
|
|
self.hud.notification('Crossed line %s' % ' and '.join(text))
|
|
|
|
# ==============================================================================
|
|
# -- GnssSensor --------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
class GnssSensor(object):
|
|
""" Class for GNSS sensors"""
|
|
|
|
def __init__(self, parent_actor):
|
|
"""Constructor method"""
|
|
self.sensor = None
|
|
self._parent = parent_actor
|
|
self.lat = 0.0
|
|
self.lon = 0.0
|
|
world = self._parent.get_world()
|
|
blueprint = world.get_blueprint_library().find('sensor.other.gnss')
|
|
self.sensor = world.spawn_actor(blueprint, carla.Transform(carla.Location(x=1.0, z=2.8)),
|
|
attach_to=self._parent)
|
|
# We need to pass the lambda a weak reference to
|
|
# self to avoid circular reference.
|
|
weak_self = weakref.ref(self)
|
|
self.sensor.listen(lambda event: GnssSensor._on_gnss_event(weak_self, event))
|
|
|
|
@staticmethod
|
|
def _on_gnss_event(weak_self, event):
|
|
"""GNSS method"""
|
|
self = weak_self()
|
|
if not self:
|
|
return
|
|
self.lat = event.latitude
|
|
self.lon = event.longitude
|
|
|
|
# ==============================================================================
|
|
# -- CameraManager -------------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
class CameraManager(object):
|
|
""" Class for camera management"""
|
|
|
|
def __init__(self, parent_actor, hud):
|
|
"""Constructor method"""
|
|
self.sensor = None
|
|
self.surface = None
|
|
self._parent = parent_actor
|
|
self.hud = hud
|
|
self.recording = False
|
|
bound_x = 0.5 + self._parent.bounding_box.extent.x
|
|
bound_y = 0.5 + self._parent.bounding_box.extent.y
|
|
bound_z = 0.5 + self._parent.bounding_box.extent.z
|
|
attachment = carla.AttachmentType
|
|
self._camera_transforms = [
|
|
(carla.Transform(carla.Location(x=-2.0*bound_x, y=+0.0*bound_y, z=2.0*bound_z), carla.Rotation(pitch=8.0)), attachment.SpringArmGhost),
|
|
(carla.Transform(carla.Location(x=+0.8*bound_x, y=+0.0*bound_y, z=1.3*bound_z)), attachment.Rigid),
|
|
(carla.Transform(carla.Location(x=+1.9*bound_x, y=+1.0*bound_y, z=1.2*bound_z)), attachment.SpringArmGhost),
|
|
(carla.Transform(carla.Location(x=-2.8*bound_x, y=+0.0*bound_y, z=4.6*bound_z), carla.Rotation(pitch=6.0)), attachment.SpringArmGhost),
|
|
(carla.Transform(carla.Location(x=-1.0, y=-1.0*bound_y, z=0.4*bound_z)), attachment.Rigid)]
|
|
|
|
self.transform_index = 1
|
|
self.sensors = [
|
|
['sensor.camera.rgb', cc.Raw, 'Camera RGB'],
|
|
['sensor.camera.depth', cc.Raw, 'Camera Depth (Raw)'],
|
|
['sensor.camera.depth', cc.Depth, 'Camera Depth (Gray Scale)'],
|
|
['sensor.camera.depth', cc.LogarithmicDepth, 'Camera Depth (Logarithmic Gray Scale)'],
|
|
['sensor.camera.semantic_segmentation', cc.Raw, 'Camera Semantic Segmentation (Raw)'],
|
|
['sensor.camera.semantic_segmentation', cc.CityScapesPalette,
|
|
'Camera Semantic Segmentation (CityScapes Palette)'],
|
|
['sensor.lidar.ray_cast', None, 'Lidar (Ray-Cast)']]
|
|
world = self._parent.get_world()
|
|
bp_library = world.get_blueprint_library()
|
|
for item in self.sensors:
|
|
blp = bp_library.find(item[0])
|
|
if item[0].startswith('sensor.camera'):
|
|
blp.set_attribute('image_size_x', str(hud.dim[0]))
|
|
blp.set_attribute('image_size_y', str(hud.dim[1]))
|
|
elif item[0].startswith('sensor.lidar'):
|
|
blp.set_attribute('range', '50')
|
|
item.append(blp)
|
|
self.index = None
|
|
|
|
def toggle_camera(self):
|
|
"""Activate a camera"""
|
|
self.transform_index = (self.transform_index + 1) % len(self._camera_transforms)
|
|
self.set_sensor(self.index, notify=False, force_respawn=True)
|
|
|
|
def set_sensor(self, index, notify=True, force_respawn=False):
|
|
"""Set a sensor"""
|
|
index = index % len(self.sensors)
|
|
needs_respawn = True if self.index is None else (
|
|
force_respawn or (self.sensors[index][0] != self.sensors[self.index][0]))
|
|
if needs_respawn:
|
|
if self.sensor is not None:
|
|
self.sensor.destroy()
|
|
self.surface = None
|
|
self.sensor = self._parent.get_world().spawn_actor(
|
|
self.sensors[index][-1],
|
|
self._camera_transforms[self.transform_index][0],
|
|
attach_to=self._parent,
|
|
attachment_type=self._camera_transforms[self.transform_index][1])
|
|
|
|
# We need to pass the lambda a weak reference to
|
|
# self to avoid circular reference.
|
|
weak_self = weakref.ref(self)
|
|
self.sensor.listen(lambda image: CameraManager._parse_image(weak_self, image))
|
|
if notify:
|
|
self.hud.notification(self.sensors[index][2])
|
|
self.index = index
|
|
|
|
def next_sensor(self):
|
|
"""Get the next sensor"""
|
|
self.set_sensor(self.index + 1)
|
|
|
|
def toggle_recording(self):
|
|
"""Toggle recording on or off"""
|
|
self.recording = not self.recording
|
|
self.hud.notification('Recording %s' % ('On' if self.recording else 'Off'))
|
|
|
|
def render(self, display):
|
|
"""Render method"""
|
|
if self.surface is not None:
|
|
display.blit(self.surface, (0, 0))
|
|
|
|
@staticmethod
|
|
def _parse_image(weak_self, image):
|
|
self = weak_self()
|
|
if not self:
|
|
return
|
|
if self.sensors[self.index][0].startswith('sensor.lidar'):
|
|
points = np.frombuffer(image.raw_data, dtype=np.dtype('f4'))
|
|
points = np.reshape(points, (int(points.shape[0] / 4), 4))
|
|
lidar_data = np.array(points[:, :2])
|
|
lidar_data *= min(self.hud.dim) / 100.0
|
|
lidar_data += (0.5 * self.hud.dim[0], 0.5 * self.hud.dim[1])
|
|
lidar_data = np.fabs(lidar_data) # pylint: disable=assignment-from-no-return
|
|
lidar_data = lidar_data.astype(np.int32)
|
|
lidar_data = np.reshape(lidar_data, (-1, 2))
|
|
lidar_img_size = (self.hud.dim[0], self.hud.dim[1], 3)
|
|
lidar_img = np.zeros(lidar_img_size)
|
|
lidar_img[tuple(lidar_data.T)] = (255, 255, 255)
|
|
self.surface = pygame.surfarray.make_surface(lidar_img)
|
|
else:
|
|
image.convert(self.sensors[self.index][1])
|
|
array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8"))
|
|
array = np.reshape(array, (image.height, image.width, 4))
|
|
array = array[:, :, :3]
|
|
array = array[:, :, ::-1]
|
|
self.surface = pygame.surfarray.make_surface(array.swapaxes(0, 1))
|
|
if self.recording:
|
|
image.save_to_disk('_out/%08d' % image.frame)
|
|
|
|
# ==============================================================================
|
|
# -- Game Loop ---------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
def game_loop(args):
|
|
"""
|
|
Main loop of the simulation. It handles updating all the HUD information,
|
|
ticking the agent and, if needed, the world.
|
|
"""
|
|
|
|
pygame.init()
|
|
pygame.font.init()
|
|
world = None
|
|
|
|
try:
|
|
if args.seed:
|
|
random.seed(args.seed)
|
|
|
|
client = carla.Client(args.host, args.port)
|
|
client.set_timeout(60.0)
|
|
|
|
traffic_manager = client.get_trafficmanager()
|
|
sim_world = client.get_world()
|
|
|
|
if args.sync:
|
|
settings = sim_world.get_settings()
|
|
settings.synchronous_mode = True
|
|
settings.fixed_delta_seconds = 0.05
|
|
sim_world.apply_settings(settings)
|
|
|
|
traffic_manager.set_synchronous_mode(True)
|
|
|
|
display = pygame.display.set_mode(
|
|
(args.width, args.height),
|
|
pygame.HWSURFACE | pygame.DOUBLEBUF)
|
|
|
|
hud = HUD(args.width, args.height)
|
|
world = World(client.get_world(), hud, args)
|
|
controller = KeyboardControl(world)
|
|
if args.agent == "Basic":
|
|
agent = BasicAgent(world.player, 30)
|
|
agent.follow_speed_limits(True)
|
|
elif args.agent == "Constant":
|
|
agent = ConstantVelocityAgent(world.player, 30)
|
|
ground_loc = world.world.ground_projection(world.player.get_location(), 5)
|
|
if ground_loc:
|
|
world.player.set_location(ground_loc.location + carla.Location(z=0.01))
|
|
agent.follow_speed_limits(True)
|
|
elif args.agent == "Behavior":
|
|
agent = BehaviorAgent(world.player, behavior=args.behavior)
|
|
|
|
# Set the agent destination
|
|
spawn_points = world.map.get_spawn_points()
|
|
destination = random.choice(spawn_points).location
|
|
agent.set_destination(destination)
|
|
|
|
clock = pygame.time.Clock()
|
|
|
|
while True:
|
|
clock.tick()
|
|
if args.sync:
|
|
world.world.tick()
|
|
else:
|
|
world.world.wait_for_tick()
|
|
if controller.parse_events():
|
|
return
|
|
|
|
world.tick(clock)
|
|
world.render(display)
|
|
pygame.display.flip()
|
|
|
|
if agent.done():
|
|
if args.loop:
|
|
agent.set_destination(random.choice(spawn_points).location)
|
|
world.hud.notification("Target reached", seconds=4.0)
|
|
print("The target has been reached, searching for another target")
|
|
else:
|
|
print("The target has been reached, stopping the simulation")
|
|
break
|
|
|
|
control = agent.run_step()
|
|
control.manual_gear_shift = False
|
|
world.player.apply_control(control)
|
|
|
|
finally:
|
|
|
|
if world is not None:
|
|
settings = world.world.get_settings()
|
|
settings.synchronous_mode = False
|
|
settings.fixed_delta_seconds = None
|
|
world.world.apply_settings(settings)
|
|
traffic_manager.set_synchronous_mode(True)
|
|
|
|
world.destroy()
|
|
|
|
pygame.quit()
|
|
|
|
|
|
# ==============================================================================
|
|
# -- main() --------------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
def main():
|
|
"""Main method"""
|
|
|
|
argparser = argparse.ArgumentParser(
|
|
description='CARLA Automatic Control Client')
|
|
argparser.add_argument(
|
|
'-v', '--verbose',
|
|
action='store_true',
|
|
dest='debug',
|
|
help='Print debug information')
|
|
argparser.add_argument(
|
|
'--host',
|
|
metavar='H',
|
|
default='127.0.0.1',
|
|
help='IP of the host server (default: 127.0.0.1)')
|
|
argparser.add_argument(
|
|
'-p', '--port',
|
|
metavar='P',
|
|
default=2000,
|
|
type=int,
|
|
help='TCP port to listen to (default: 2000)')
|
|
argparser.add_argument(
|
|
'--res',
|
|
metavar='WIDTHxHEIGHT',
|
|
default='1280x720',
|
|
help='Window resolution (default: 1280x720)')
|
|
argparser.add_argument(
|
|
'--sync',
|
|
action='store_true',
|
|
help='Synchronous mode execution')
|
|
argparser.add_argument(
|
|
'--filter',
|
|
metavar='PATTERN',
|
|
default='vehicle.*',
|
|
help='Actor filter (default: "vehicle.*")')
|
|
argparser.add_argument(
|
|
'--generation',
|
|
metavar='G',
|
|
default='2',
|
|
help='restrict to certain actor generation (values: "1","2","All" - default: "2")')
|
|
argparser.add_argument(
|
|
'-l', '--loop',
|
|
action='store_true',
|
|
dest='loop',
|
|
help='Sets a new random destination upon reaching the previous one (default: False)')
|
|
argparser.add_argument(
|
|
"-a", "--agent", type=str,
|
|
choices=["Behavior", "Basic", "Constant"],
|
|
help="select which agent to run",
|
|
default="Behavior")
|
|
argparser.add_argument(
|
|
'-b', '--behavior', type=str,
|
|
choices=["cautious", "normal", "aggressive"],
|
|
help='Choose one of the possible agent behaviors (default: normal) ',
|
|
default='normal')
|
|
argparser.add_argument(
|
|
'-s', '--seed',
|
|
help='Set seed for repeating executions (default: None)',
|
|
default=None,
|
|
type=int)
|
|
|
|
args = argparser.parse_args()
|
|
|
|
args.width, args.height = [int(x) for x in args.res.split('x')]
|
|
|
|
log_level = logging.DEBUG if args.debug else logging.INFO
|
|
logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
|
|
|
|
logging.info('listening to server %s:%s', args.host, args.port)
|
|
|
|
print(__doc__)
|
|
|
|
try:
|
|
game_loop(args)
|
|
|
|
except KeyboardInterrupt:
|
|
print('\nCancelled by user. Bye!')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|