Simulator: add metadrive as optional simulator (#29935)
* Add metadrive sim * use monotonic * don't use cuda if it's not available * Cleanup metadrive patches * PR suggestions * fix typopull/29995/head
parent
897566957d
commit
e02519bb71
7 changed files with 229 additions and 31 deletions
@ -0,0 +1,156 @@ |
||||
import math |
||||
import numpy as np |
||||
import time |
||||
|
||||
from openpilot.tools.sim.bridge.common import World, SimulatorBridge |
||||
from openpilot.tools.sim.lib.common import vec3, SimulatorState |
||||
from openpilot.tools.sim.lib.camerad import W, H |
||||
|
||||
|
||||
def apply_metadrive_patches(): |
||||
from metadrive.engine.core.engine_core import EngineCore |
||||
from metadrive.engine.core.image_buffer import ImageBuffer |
||||
from metadrive.obs.image_obs import ImageObservation |
||||
|
||||
# By default, metadrive won't try to use cuda images unless it's used as a sensor for vehicles, so patch that in |
||||
def add_image_sensor_patched(self, name: str, cls, args): |
||||
if self.global_config["image_on_cuda"]:# and name == self.global_config["vehicle_config"]["image_source"]: |
||||
sensor = cls(*args, self, cuda=True) |
||||
else: |
||||
sensor = cls(*args, self, cuda=False) |
||||
assert isinstance(sensor, ImageBuffer), "This API is for adding image sensor" |
||||
self.sensors[name] = sensor |
||||
|
||||
EngineCore.add_image_sensor = add_image_sensor_patched |
||||
|
||||
# we aren't going to use the built-in observation stack, so disable it to save time |
||||
def observe_patched(self, vehicle): |
||||
return self.state |
||||
|
||||
ImageObservation.observe = observe_patched |
||||
|
||||
|
||||
class MetaDriveWorld(World): |
||||
def __init__(self, env, ticks_per_frame: float, dual_camera = False): |
||||
super().__init__(dual_camera) |
||||
self.env = env |
||||
self.ticks_per_frame = ticks_per_frame |
||||
self.dual_camera = dual_camera |
||||
|
||||
self.steer_ratio = 15 |
||||
|
||||
self.vc = [0.0,0.0] |
||||
|
||||
self.reset_time = 0 |
||||
|
||||
def get_cam_as_rgb(self, cam): |
||||
cam = self.env.engine.sensors[cam] |
||||
img = cam.perceive(self.env.vehicle, clip=False) |
||||
if type(img) != np.ndarray: |
||||
img = img.get() # convert cupy array to numpy |
||||
return img |
||||
|
||||
def apply_controls(self, steer_angle, throttle_out, brake_out): |
||||
steer_metadrive = steer_angle * 1 / (self.env.vehicle.MAX_STEERING * self.steer_ratio) |
||||
steer_metadrive = np.clip(steer_metadrive, -1, 1) |
||||
|
||||
if (time.monotonic() - self.reset_time) > 5: |
||||
self.vc[0] = steer_metadrive |
||||
|
||||
if throttle_out: |
||||
self.vc[1] = throttle_out/10 |
||||
else: |
||||
self.vc[1] = -brake_out |
||||
else: |
||||
self.vc[0] = 0 |
||||
self.vc[1] = 0 |
||||
|
||||
def read_sensors(self, state: SimulatorState): |
||||
state.velocity = vec3(x=float(self.env.vehicle.velocity[0]), y=float(self.env.vehicle.velocity[1]), z=0) |
||||
state.gps.from_xy(self.env.vehicle.position) |
||||
state.bearing = float(math.degrees(self.env.vehicle.heading_theta)) |
||||
state.steering_angle = self.env.vehicle.steering * self.env.vehicle.MAX_STEERING |
||||
state.valid = True |
||||
|
||||
def read_cameras(self): |
||||
if self.dual_camera: |
||||
self.wide_road_image = self.get_cam_as_rgb("rgb_wide") |
||||
self.road_image = self.get_cam_as_rgb("rgb_road") |
||||
|
||||
def tick(self): |
||||
obs, _, terminated, _, info = self.env.step(self.vc) |
||||
|
||||
if terminated: |
||||
self.env.reset() |
||||
self.reset_time = time.monotonic() |
||||
|
||||
def close(self): |
||||
pass |
||||
|
||||
|
||||
class MetaDriveBridge(SimulatorBridge): |
||||
TICKS_PER_FRAME = 2 |
||||
|
||||
def __init__(self, args): |
||||
self.should_render = False |
||||
|
||||
super(MetaDriveBridge, self).__init__(args) |
||||
|
||||
def spawn_world(self): |
||||
print("----------------------------------------------------------") |
||||
print("---- Spawning Metadrive world, this might take awhile ----") |
||||
print("----------------------------------------------------------") |
||||
from metadrive.component.sensors.rgb_camera import RGBCamera |
||||
from metadrive.component.sensors.base_camera import _cuda_enable |
||||
from metadrive.envs.metadrive_env import MetaDriveEnv |
||||
from panda3d.core import Vec3 |
||||
|
||||
apply_metadrive_patches() |
||||
|
||||
C3_POSITION = Vec3(0, 0, 1) |
||||
|
||||
class RGBCameraWide(RGBCamera): |
||||
def __init__(self, *args, **kwargs): |
||||
super(RGBCameraWide, self).__init__(*args, **kwargs) |
||||
cam = self.get_cam() |
||||
cam.setPos(C3_POSITION) |
||||
lens = self.get_lens() |
||||
lens.setFov(160) |
||||
|
||||
class RGBCameraRoad(RGBCamera): |
||||
def __init__(self, *args, **kwargs): |
||||
super(RGBCameraRoad, self).__init__(*args, **kwargs) |
||||
cam = self.get_cam() |
||||
cam.setPos(C3_POSITION) |
||||
lens = self.get_lens() |
||||
lens.setFov(40) |
||||
|
||||
sensors = { |
||||
"rgb_road": (RGBCameraRoad, W, H, ) |
||||
} |
||||
|
||||
if self.dual_camera: |
||||
sensors["rgb_wide"] = (RGBCameraWide, W, H) |
||||
|
||||
env = MetaDriveEnv( |
||||
dict( |
||||
use_render=self.should_render, |
||||
vehicle_config=dict( |
||||
enable_reverse=False, |
||||
image_source="rgb_road", |
||||
spawn_longitude=15 |
||||
), |
||||
sensors=sensors, |
||||
image_on_cuda=_cuda_enable, |
||||
image_observation=True, |
||||
interface_panel=[], |
||||
out_of_route_done=False, |
||||
on_continuous_line_done=False, |
||||
crash_vehicle_done=False, |
||||
crash_object_done=False, |
||||
) |
||||
) |
||||
|
||||
env.reset() |
||||
|
||||
return MetaDriveWorld(env, self.TICKS_PER_FRAME) |
@ -0,0 +1,43 @@ |
||||
#!/usr/bin/env python3 |
||||
import subprocess |
||||
import time |
||||
import unittest |
||||
|
||||
from openpilot.selfdrive.manager.helpers import unblock_stdout |
||||
from openpilot.tools.sim.run_bridge import parse_args |
||||
from openpilot.tools.sim.bridge.carla import CarlaBridge |
||||
from openpilot.tools.sim.tests.test_sim_bridge import SIM_DIR, TestSimBridgeBase |
||||
|
||||
|
||||
class TestCarlaBridge(TestSimBridgeBase): |
||||
""" |
||||
Tests need Carla simulator to run |
||||
""" |
||||
carla_process = None |
||||
|
||||
def setUp(self): |
||||
super().setUp() |
||||
|
||||
# We want to make sure that carla_sim docker isn't still running. |
||||
subprocess.run("docker rm -f carla_sim", shell=True, stderr=subprocess.PIPE, check=False) |
||||
self.carla_process = subprocess.Popen("./start_carla.sh", cwd=SIM_DIR) |
||||
|
||||
# Too many lagging messages in bridge.py can cause a crash. This prevents it. |
||||
unblock_stdout() |
||||
# Wait 10 seconds to startup carla |
||||
time.sleep(10) |
||||
|
||||
def create_bridge(self): |
||||
return CarlaBridge(parse_args([])) |
||||
|
||||
def tearDown(self): |
||||
super().tearDown() |
||||
|
||||
# Stop carla simulator by removing docker container |
||||
subprocess.run("docker rm -f carla_sim", shell=True, stderr=subprocess.PIPE, check=False) |
||||
if self.carla_process is not None: |
||||
self.carla_process.wait() |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
unittest.main() |
@ -0,0 +1,15 @@ |
||||
#!/usr/bin/env python3 |
||||
import unittest |
||||
|
||||
from openpilot.tools.sim.run_bridge import parse_args |
||||
from openpilot.tools.sim.bridge.metadrive import MetaDriveBridge |
||||
from openpilot.tools.sim.tests.test_sim_bridge import TestSimBridgeBase |
||||
|
||||
|
||||
class TestMetaDriveBridge(TestSimBridgeBase): |
||||
def create_bridge(self): |
||||
return MetaDriveBridge(parse_args([])) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
unittest.main() |
Loading…
Reference in new issue