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