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/214/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