Skip to content

Commit 24f1c68

Browse files
authored
Fixed manual_control usage (#79)
* Fixed manual_control usage * Move render related code to a dedicated class * Add tests for render * Add follow_leading_vehicle example from carla_scenario
1 parent 912944c commit 24f1c68

File tree

7 files changed

+498
-118
lines changed

7 files changed

+498
-118
lines changed

src/macad_gym/carla/multi_env.py

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
# from macad_gym.core.sensors.utils import get_transform_from_nearest_way_point
4040
from macad_gym.carla.reward import Reward
4141
from macad_gym.core.sensors.hud import HUD
42-
from macad_gym.viz.render import multi_view_render
42+
from macad_gym.viz.render import Render
4343
from macad_gym.carla.scenarios import Scenarios
4444

4545
# The following imports require carla to be imported already.
@@ -278,6 +278,21 @@ def __init__(self, configs=None):
278278
self._scenario_config = configs["scenarios"]
279279
self._env_config = configs["env"]
280280
self._actor_configs = configs["actors"]
281+
282+
# At most one actor can be manual controlled
283+
manual_control_count = 0
284+
for _, actor_config in self._actor_configs.items():
285+
if actor_config["manual_control"]:
286+
if "vehicle" not in actor_config["type"]:
287+
raise ValueError("Only vehicles can be manual controlled.")
288+
289+
manual_control_count += 1
290+
291+
assert manual_control_count <= 1, (
292+
"At most one actor can be manually controlled. "
293+
f"Found {manual_control_count} actors with manual_control=True"
294+
)
295+
281296
# Camera position is problematic for certain vehicles and even in
282297
# autopilot they are prone to error
283298
self.exclude_hard_vehicles = False
@@ -317,6 +332,24 @@ def __init__(self, configs=None):
317332
pygame.font.init() # for HUD
318333
self._hud = HUD(self._render_x_res, self._render_y_res)
319334

335+
# For manual_control
336+
self._control_clock = None
337+
self._manual_controller = None
338+
self._manual_control_camera_manager = None
339+
340+
# Render related
341+
Render.resize_screen(self._render_x_res, self._render_y_res)
342+
343+
self._camera_poses, window_dim = Render.get_surface_poses(
344+
[self._x_res, self._y_res], self._actor_configs)
345+
346+
if manual_control_count == 0:
347+
Render.resize_screen(window_dim[0], window_dim[1])
348+
else:
349+
self._manual_control_render_pose = (0, window_dim[1])
350+
Render.resize_screen(
351+
max(self._render_x_res, window_dim[0]), self._render_y_res + window_dim[1])
352+
320353
# Actions space
321354
if self._discrete_actions:
322355
self.action_space = Dict(
@@ -633,7 +666,8 @@ def _clear_server_state(self):
633666
if self._server_process:
634667
if IS_WINDOWS_PLATFORM:
635668
subprocess.call(
636-
["taskkill", "/F", "/T", "/PID", str(self._server_process.pid)]
669+
["taskkill", "/F", "/T", "/PID",
670+
str(self._server_process.pid)]
637671
)
638672
live_carla_processes.remove(self._server_process.pid)
639673
else:
@@ -693,9 +727,7 @@ def reset(self):
693727

694728
return self._obs_dict
695729

696-
# TODO: Is this function required?
697-
# TODO: Thought: Run server in headless mode always. Use pygame win on
698-
# client when render=True
730+
# ! Deprecated method
699731
def _on_render(self):
700732
"""Render the pygame window.
701733
@@ -704,11 +736,7 @@ def _on_render(self):
704736
Returns:
705737
N/A
706738
"""
707-
for cam in self._cameras.values():
708-
surface = cam._surface
709-
if surface is not None:
710-
self._display.blit(surface, (0, 0))
711-
pygame.display.flip()
739+
pass
712740

713741
def _spawn_new_actor(self, actor_id):
714742
"""Spawn an agent as per the blueprint at the given pose
@@ -867,7 +895,8 @@ def _reset(self, clean_world=True):
867895
)
868896
actor_config = self._actor_configs[actor_id]
869897

870-
try: # Try to spawn actor (soft reset) or fail and reinitialize the server before get back here
898+
# Try to spawn actor (soft reset) or fail and reinitialize the server before get back here
899+
try:
871900
self._actors[actor_id] = self._spawn_new_actor(actor_id)
872901
except RuntimeError as spawn_err:
873902
del self._done_dict[actor_id]
@@ -928,6 +957,21 @@ def _reset(self, clean_world=True):
928957
assert camera_manager.sensor.is_listening
929958
self._cameras.update({actor_id: camera_manager})
930959

960+
# Manual Control
961+
if actor_config["manual_control"]:
962+
self._control_clock = pygame.time.Clock()
963+
964+
self._manual_controller = KeyboardControl(
965+
self, actor_config["auto_control"])
966+
self._manual_controller.actor_id = actor_id
967+
968+
self.world.on_tick(self._hud.on_world_tick)
969+
self._manual_control_camera_manager = CameraManager(
970+
self._actors[actor_id], self._hud)
971+
self._manual_control_camera_manager.set_sensor(
972+
CAMERA_TYPES['rgb'].value - 1, pos=2, notify=False
973+
)
974+
931975
self._start_coord.update(
932976
{
933977
actor_id: [
@@ -1117,10 +1161,13 @@ def step(self, action_dict):
11171161
k for k, v in self._actor_configs.items() if v.get("render", False)
11181162
]
11191163
if render_required:
1120-
images = {k: self._decode_obs(k, v) for k, v in obs_dict.items()}
1121-
multi_view_render(
1122-
images, [self._x_res, self._y_res], self._actor_configs
1123-
)
1164+
images = {k: self._decode_obs(k, v)
1165+
for k, v in obs_dict.items() if self._actor_configs[k]["render"]}
1166+
1167+
Render.multi_view_render(images, self._camera_poses)
1168+
if self._manual_controller is None:
1169+
Render.dummy_event_handler()
1170+
11241171
return obs_dict, reward_dict, self._done_dict, info_dict
11251172
except Exception:
11261173
print(
@@ -1166,18 +1213,14 @@ def _step(self, actor_id, action):
11661213

11671214
config = self._actor_configs[actor_id]
11681215
if config["manual_control"]:
1169-
clock = pygame.time.Clock()
1170-
# pygame
1171-
self._display = pygame.display.set_mode(
1172-
(config["render_x_res"], config["render_y_res"]),
1173-
pygame.HWSURFACE | pygame.DOUBLEBUF,
1174-
)
1175-
logger.debug("pygame started")
1176-
controller = KeyboardControl(self, config["auto_control"])
1177-
controller.actor_id = actor_id
1178-
controller.parse_events(self, clock)
1179-
# TODO: Is this _on_render() method necessary? why?
1180-
self._on_render()
1216+
self._control_clock.tick(60)
1217+
self._manual_control_camera_manager._hud.tick(self.world, self._actors[actor_id], self._collisions[actor_id], self._control_clock)
1218+
self._manual_controller.parse_events(self, self._control_clock)
1219+
1220+
# TODO: consider move this to Render as well
1221+
self._manual_control_camera_manager.render(Render.get_screen(), self._manual_control_render_pose)
1222+
self._manual_control_camera_manager._hud.render(Render.get_screen(), self._manual_control_render_pose)
1223+
pygame.display.flip()
11811224
elif config["auto_control"]:
11821225
if getattr(self._actors[actor_id], "set_autopilot", 0):
11831226
self._actors[actor_id].set_autopilot(
@@ -1480,16 +1523,19 @@ def get_next_actions(measurements, is_discrete_actions):
14801523

14811524

14821525
if __name__ == "__main__":
1483-
argparser = argparse.ArgumentParser(description="CARLA Manual Control Client")
1484-
argparser.add_argument("--scenario", default="3", help="print debug information")
1526+
argparser = argparse.ArgumentParser(
1527+
description="CARLA Manual Control Client")
1528+
argparser.add_argument("--scenario", default="3",
1529+
help="print debug information")
14851530
# TODO: Fix the default path to the config.json;Should work after packaging
14861531
argparser.add_argument(
14871532
"--config",
14881533
default="src/macad_gym/carla/config.json",
14891534
help="print debug information",
14901535
)
14911536

1492-
argparser.add_argument("--map", default="Town01", help="print debug information")
1537+
argparser.add_argument("--map", default="Town01",
1538+
help="print debug information")
14931539

14941540
args = argparser.parse_args()
14951541

src/macad_gym/core/controllers/keyboard_control.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
class KeyboardControl(object):
3737
"""This class from carla, manual_control.py
3838
"""
39+
3940
def __init__(self, world, start_in_autopilot):
4041
self._autopilot_enabled = start_in_autopilot
4142
self._control = carla.VehicleControl()
@@ -80,26 +81,44 @@ def parse_events(self, world, clock):
8081
self._autopilot_enabled = not self._autopilot_enabled
8182
self.vehicle.set_autopilot(self._autopilot_enabled)
8283
if not self._autopilot_enabled:
84+
# FIXME: The actor_id is the name of the vehicle, not the id
8385
if self.actor_id == 0:
84-
self._parse_keys1(pygame.key.get_pressed(), clock.get_time())
86+
self._parse_keys1(
87+
pygame.key.get_pressed(), clock.get_time())
8588
elif self.actor_id == 1:
86-
self._parse_keys2(pygame.key.get_pressed(), clock.get_time())
87-
elif self.actor_id == -1: # use default ones
88-
self._parse_keys(pygame.key.get_pressed(), clock.get_time())
89+
self._parse_keys2(
90+
pygame.key.get_pressed(), clock.get_time())
91+
else: # use default ones
92+
self._parse_keys(pygame.key.get_pressed(),
93+
clock.get_time())
8994
self.vehicle.apply_control(self._control)
9095

9196
def _parse_keys(self, keys, milliseconds):
92-
self._control.throttle = 1.0 if keys[K_UP] or keys[K_w] else 0.0
97+
if keys[K_UP] or keys[K_w]:
98+
self._control.throttle = min(self._control.throttle + 0.1, 1.0)
99+
else:
100+
self._control.throttle = 0.0
101+
102+
if keys[K_DOWN] or keys[K_s]:
103+
self._control.brake = min(self._control.brake + 0.2, 1.0)
104+
else:
105+
self._control.brake = 0
106+
93107
steer_increment = 5e-4 * milliseconds
94108
if keys[K_LEFT] or keys[K_a]:
95-
self._steer_cache -= steer_increment
109+
if self._steer_cache > 0:
110+
self._steer_cache = 0
111+
else:
112+
self._steer_cache -= steer_increment
96113
elif keys[K_RIGHT] or keys[K_d]:
97-
self._steer_cache += steer_increment
114+
if self._steer_cache < 0:
115+
self._steer_cache = 0
116+
else:
117+
self._steer_cache += steer_increment
98118
else:
99119
self._steer_cache = 0.0
100120
self._steer_cache = min(0.7, max(-0.7, self._steer_cache))
101121
self._control.steer = round(self._steer_cache, 1)
102-
self._control.brake = 1.0 if keys[K_DOWN] or keys[K_s] else 0.0
103122
self._control.hand_brake = keys[K_SPACE]
104123

105124
def _parse_keys1(self, keys, milliseconds):

src/macad_gym/core/sensors/camera_manager.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
class CameraManager(object):
2222
"""This class from carla, manual_control.py
2323
"""
24+
2425
def __init__(self, parent_actor, hud):
2526
self.image = None # need image to encode obs.
2627
self.image_list = [] # for save images later.
@@ -34,9 +35,14 @@ def __init__(self, parent_actor, hud):
3435
self._camera_transforms = [
3536
carla.Transform(carla.Location(x=1.8, z=1.7)),
3637
carla.Transform(carla.Location(x=-5.5, z=2.8),
37-
carla.Rotation(pitch=-15))
38+
carla.Rotation(pitch=-15)),
39+
carla.Transform(carla.Location(
40+
x=-2.0*(0.5 + self._parent.bounding_box.extent.x),
41+
y=0.0,
42+
z=2.0*(0.5 + self._parent.bounding_box.extent.z)),
43+
carla.Rotation(pitch=8.0))
3844
]
39-
# 0 is dashcam view; 1 is tethered view
45+
# 0 is dashcam view; 1 is tethered view; 2 for spring arm view (manual_control)
4046
self._transform_index = 0
4147
self._sensors = [
4248
['sensor.camera.rgb', carla.ColorConverter.Raw, 'Camera RGB'],
@@ -114,7 +120,8 @@ def set_sensor(self, index, pos=0, notify=True):
114120
self.sensor = self._parent.get_world().spawn_actor(
115121
self._sensors[index][-1],
116122
self._camera_transforms[self._transform_index],
117-
attach_to=self._parent)
123+
attach_to=self._parent,
124+
attachment_type=carla.AttachmentType.Rigid if pos != 2 else carla.AttachmentType.SpringArm)
118125
# We need to pass the lambda a weak reference to self to avoid
119126
# circular reference.
120127
weak_self = weakref.ref(self)
@@ -132,9 +139,9 @@ def toggle_recording(self):
132139
self._hud.notification('Recording %s' %
133140
('On' if self._recording else 'Off'))
134141

135-
def render(self, display):
142+
def render(self, display, render_pose=(0, 0)):
136143
if self._surface is not None:
137-
display.blit(self._surface, (0, 0))
144+
display.blit(self._surface, render_pose)
138145

139146
@staticmethod
140147
def _parse_image(weak_self, image):

src/macad_gym/core/sensors/hud.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,30 @@ def on_world_tick(self, timestamp):
3838
self.frame_number = timestamp.frame_count
3939
self.simulation_time = timestamp.elapsed_seconds
4040

41-
def tick(self, world, clock):
41+
def tick(self, world, vehicle, collision_sensor, clock):
4242
if not self._show_info:
4343
return
44-
t = world.vehicle.get_transform()
45-
v = world.vehicle.get_velocity()
46-
c = world.vehicle.get_vehicle_control()
44+
45+
t = vehicle.get_transform()
46+
v = vehicle.get_velocity()
47+
c = vehicle.get_control()
48+
4749
heading = 'N' if abs(t.rotation.yaw) < 89.5 else ''
4850
heading += 'S' if abs(t.rotation.yaw) > 90.5 else ''
4951
heading += 'E' if 179.5 > t.rotation.yaw > 0.5 else ''
5052
heading += 'W' if -0.5 > t.rotation.yaw > -179.5 else ''
51-
colhist = world.collision_sensor.get_collision_history()
53+
colhist = collision_sensor.get_collision_history()
5254
collision = [
5355
colhist[x + self.frame_number - 200] for x in range(0, 200)
5456
]
5557
max_col = max(1.0, max(collision))
5658
collision = [x / max_col for x in collision]
57-
vehicles = world.world.get_actors().filter('vehicle.*')
59+
vehicles = world.get_actors().filter('vehicle.*')
5860
self._info_text = [
5961
'Server: % 16d FPS' % self.server_fps,
6062
'Client: % 16d FPS' % clock.get_fps(), '',
6163
'Vehicle: % 20s' %
62-
get_actor_display_name(world.vehicle, truncate=20),
63-
'Map: % 20s' % world.world.map_name,
64+
get_actor_display_name(vehicle, truncate=20),
6465
'Simulation time: % 12s' %
6566
datetime.timedelta(seconds=int(self.simulation_time)), '',
6667
'Speed: % 15.0f km/h' %
@@ -81,7 +82,7 @@ def tick(self, world, clock):
8182
# (l.y - t.location.y)**2 +
8283
# (l.z - t.location.z)**2)
8384
vehicles = [(self.distance(x.get_location(), t), x)
84-
for x in vehicles if x.id != world.vehicle.id]
85+
for x in vehicles if x.id != vehicle.id]
8586
for d, vehicle in sorted(vehicles):
8687
if d > 200.0:
8788
break
@@ -104,12 +105,12 @@ def error(self, text):
104105
logger.info("Notification error disabled: "+text)
105106
# self._notifications.set_text('Error: %s' % text, (255, 0, 0))
106107

107-
def render(self, display):
108+
def render(self, display, render_pose=(0,0)):
108109
if self._show_info:
109110
info_surface = pygame.Surface((220, self.dim[1]))
110111
info_surface.set_alpha(100)
111-
display.blit(info_surface, (0, 0))
112-
v_offset = 4
112+
display.blit(info_surface, render_pose)
113+
v_offset = 4 + render_pose[1]
113114
bar_h_offset = 100
114115
bar_width = 106
115116
for item in self._info_text:

0 commit comments

Comments
 (0)