Skip to content

Commit

Permalink
Refactor sensor api/camera creation/render mode (#481)
Browse files Browse the repository at this point in the history
* rename module to sensors

* update config

* remove _merge_extra_config

* refactor code

* almost done

* fix test and example

* add log level to config

* test

* fix test

* format

* vehicle panel to panel

* cancel extra render frame

* fix render bug

* fix shadow problem

* fix sensor API bug

* test pass

* panel -> dashboard

* use _render_mode to determine

* format log message

* format

* hide debug information

* fix bug
  • Loading branch information
QuanyiLi committed Aug 21, 2023
1 parent efca9ee commit e0bb542
Show file tree
Hide file tree
Showing 59 changed files with 698 additions and 662 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@
**/test_export_record_scenario/
/metadrive/assets/textures/heightfield.png
**/test_read_copy_waymo
/metadrive/tests/vis_functionality/*.png
File renamed without changes.
236 changes: 236 additions & 0 deletions metadrive/component/sensors/base_camera.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import logging

import numpy as np

from metadrive.utils.cuda import check_cudart_err

_cuda_enable = True
try:
import cupy as cp
from OpenGL.GL import GL_TEXTURE_2D # noqa F403
from cuda import cudart
from cuda.cudart import cudaGraphicsRegisterFlags
from panda3d.core import GraphicsOutput, Texture, GraphicsStateGuardianBase, DisplayRegionDrawCallbackData
except ImportError:
_cuda_enable = False
from panda3d.core import Vec3

from metadrive.engine.core.image_buffer import ImageBuffer


class BaseCamera(ImageBuffer):
"""
To enable the image observation, set image_observation to True.
Every objects share the same camera, to boost the efficiency and save memory.
Camera configuration is read from the global config automatically.
"""
# shape(dim_1, dim_2)
BUFFER_W = 84 # dim 1
BUFFER_H = 84 # dim 2
CAM_MASK = None
display_region_size = [1 / 3, 2 / 3, 0.8, 1.0]
attached_object = None

def __init__(self, engine, setup_pbr=False, need_cuda=False):
self._enable_cuda = need_cuda
super(BaseCamera, self).__init__(
self.BUFFER_W, self.BUFFER_H, Vec3(0., 0.8, 1.5), self.BKG_COLOR, setup_pbr=setup_pbr, engine=engine
)

width = self.BUFFER_W
height = self.BUFFER_H
if (width > 100 or height > 100) and not self.enable_cuda:
# Too large height or width will cause corruption in Mac.
logging.warning(
"You may using too large buffer! The height is {}, and width is {}. "
"It may lower the sample efficiency! Considering reduce buffer size or using cuda image by"
" set [image_on_cuda=True].".format(height, width)
)
self.cuda_graphics_resource = None
if self.enable_cuda:
assert _cuda_enable, "Can not enable cuda rendering pipeline"

# returned tensor property
self.cuda_dtype = np.uint8
self.cuda_shape = (self.BUFFER_W, self.BUFFER_H)
self.cuda_strides = None
self.cuda_order = "C"

self._cuda_buffer = None

# make texture
self.cuda_texture = Texture()
self.buffer.addRenderTexture(self.cuda_texture, GraphicsOutput.RTMBindOrCopy)

def _callback_func(cbdata: DisplayRegionDrawCallbackData):
# print("DRAW CALLBACK!!!!!!!!!!!!!!!11")
cbdata.upcall()
if not self.registered and self.texture_context_future.done():
self.register()
if self.registered:
with self as array:
self.cuda_rendered_result = array

# Fill the buffer due to multi-thread
for _ in range(3):
self.engine.graphicsEngine.renderFrame()
self.cam.node().getDisplayRegion(0).setDrawCallback(_callback_func)

self.gsg = GraphicsStateGuardianBase.getDefaultGsg()
self.texture_context_future = self.cuda_texture.prepare(self.gsg.prepared_objects)
self.cuda_texture_identifier = None
self.new_cuda_mem_ptr = None
self.cuda_rendered_result = None

@property
def enable_cuda(self):
return self is not None and self._enable_cuda

def get_image(self, base_object):
"""
Borrow the camera to get observations
"""
self.origin.reparentTo(base_object.origin)
ret = super(BaseCamera, self).get_image()
self.track(self.attached_object)
return ret

def save_image(self, base_object, name="debug.png"):
img = self.get_image(base_object)
img.write(name)

def get_pixels_array(self, base_object, clip=True) -> np.ndarray:
self.track(base_object)
if self.enable_cuda:
assert self.cuda_rendered_result is not None
ret = self.cuda_rendered_result[..., :-1][..., ::-1][::-1]
else:
ret = self.get_rgb_array_cpu()
if self.engine.global_config["rgb_to_grayscale"]:
ret = np.dot(ret[..., :3], [0.299, 0.587, 0.114])
if not clip:
return ret.astype(np.uint8)
else:
return ret / 255

def destroy(self):
if self.registered:
self.unregister()
ImageBuffer.destroy(self)

def get_cam(self):
return self.cam

def get_lens(self):
return self.lens

# # following functions are for onscreen render
# def add_display_region(self, display_region):
# super(BaseCamera, self).add_display_region(display_region)

def remove_display_region(self):
super(BaseCamera, self).remove_display_region()

def track(self, base_object):
if base_object is not None and self is not None:
self.attached_object = base_object
self.origin.reparentTo(base_object.origin)

def __del__(self):
if self.enable_cuda:
self.unregister()
super(BaseCamera, self).__del__()

"""
Following functions are cuda support
"""

@property
def cuda_array(self):
assert self.mapped
return cp.ndarray(
shape=(self.cuda_shape[1], self.cuda_shape[0], 4),
dtype=self.cuda_dtype,
strides=self.cuda_strides,
order=self.cuda_order,
memptr=self._cuda_buffer
)

@property
def cuda_buffer(self):
assert self.mapped
return self._cuda_buffer

@property
def graphics_resource(self):
assert self.registered
return self.cuda_graphics_resource

@property
def registered(self):
return self.cuda_graphics_resource is not None

@property
def mapped(self):
return self._cuda_buffer is not None

def __enter__(self):
return self.map()

def __exit__(self, exc_type, exc_value, trace):
self.unmap()
return False

def register(self):
self.cuda_texture_identifier = self.texture_context_future.result().getNativeId()
assert self.cuda_texture_identifier is not None
if self.registered:
return self.cuda_graphics_resource
self.cuda_graphics_resource = check_cudart_err(
cudart.cudaGraphicsGLRegisterImage(
self.cuda_texture_identifier, GL_TEXTURE_2D, cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsReadOnly
)
)
return self.cuda_graphics_resource

def unregister(self):
if self.registered:
self.unmap()
self.cuda_graphics_resource = check_cudart_err(
cudart.cudaGraphicsUnregisterResource(self.cuda_graphics_resource)
)
self.cam.node().getDisplayRegion(0).clearDrawCallback()

def map(self, stream=0):
if not self.registered:
raise RuntimeError("Cannot map an unregistered buffer.")
if self.mapped:
return self._cuda_buffer
check_cudart_err(cudart.cudaGraphicsMapResources(1, self.cuda_graphics_resource, stream))
array = check_cudart_err(cudart.cudaGraphicsSubResourceGetMappedArray(self.graphics_resource, 0, 0))
channelformat, cudaextent, flag = check_cudart_err(cudart.cudaArrayGetInfo(array))

depth = 1
byte = 4 # four channel
if self.new_cuda_mem_ptr is None:
success, self.new_cuda_mem_ptr = cudart.cudaMalloc(cudaextent.height * cudaextent.width * byte * depth)
check_cudart_err(
cudart.cudaMemcpy2DFromArray(
self.new_cuda_mem_ptr, cudaextent.width * byte * depth, array, 0, 0, cudaextent.width * byte * depth,
cudaextent.height, cudart.cudaMemcpyKind.cudaMemcpyDeviceToDevice
)
)
if self._cuda_buffer is None:
self._cuda_buffer = cp.cuda.MemoryPointer(
cp.cuda.UnownedMemory(self.new_cuda_mem_ptr, cudaextent.width * depth * byte * cudaextent.height, self),
0
)
return self.cuda_array

def unmap(self, stream=None):
if not self.registered:
raise RuntimeError("Cannot unmap an unregistered buffer.")
if not self.mapped:
return self
self._cuda_buffer = check_cudart_err(cudart.cudaGraphicsUnmapResources(1, self.cuda_graphics_resource, stream))
return self
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
from panda3d.core import Shader, RenderState, ShaderAttrib, GeoMipTerrain, LVector3, PNMImage
import cv2
import numpy as np
from panda3d.core import Shader, RenderState, ShaderAttrib, GeoMipTerrain

from metadrive.component.vehicle_module.base_camera import BaseCamera
from metadrive.component.sensors.base_camera import BaseCamera
from metadrive.constants import CamMask
from metadrive.constants import RENDER_MODE_NONE
from metadrive.engine.asset_loader import AssetLoader
from metadrive.engine.engine_utils import get_global_config, engine_initialized, get_engine


class DepthCamera(BaseCamera):
Expand All @@ -18,13 +16,10 @@ class DepthCamera(BaseCamera):
GROUND = None
GROUND_MODEL = None

def __init__(self):
assert engine_initialized(), "You should initialize engine before adding camera to vehicle"
config = get_global_config()["vehicle_config"]["depth_camera"]
self.BUFFER_W, self.BUFFER_H = config[0], config[1]
self.VIEW_GROUND = config[2]
cuda = True if get_global_config()["vehicle_config"]["image_source"] == "depth_camera" else False
super(DepthCamera, self).__init__(False, cuda)
def __init__(self, engine, width, height, cuda=False):
self.BUFFER_W, self.BUFFER_H = width, height
self.VIEW_GROUND = True # default true
super(DepthCamera, self).__init__(engine, False, cuda)
cam = self.get_cam()
lens = self.get_lens()

Expand All @@ -33,7 +28,7 @@ def __init__(self):

lens.setFov(60)
# lens.setAspectRatio(2.0)
if get_engine().mode == RENDER_MODE_NONE or not AssetLoader.initialized() or type(self)._singleton.init_num > 1:
if self.engine.mode == RENDER_MODE_NONE or not AssetLoader.initialized():
return
# add shader for it
# if get_global_config()["headless_machine_render"]:
Expand All @@ -59,22 +54,22 @@ def __init__(self):
# # model to enable the depth information of terrain
self.GROUND_MODEL = self.GROUND.getRoot()
self.GROUND_MODEL.setPos(-128, -128, self.GROUND_HEIGHT)
self.GROUND_MODEL.reparentTo(type(self)._singleton.engine.render)
self.GROUND_MODEL.reparentTo(self.engine.render)
self.GROUND_MODEL.hide(CamMask.AllOn)
self.GROUND_MODEL.show(CamMask.DepthCam)
self.GROUND.generate()

def track(self, base_object):
if self.VIEW_GROUND:
pos = base_object.origin.getPos()
type(self)._singleton.GROUND_MODEL.setPos(pos[0], pos[1], self.GROUND_HEIGHT)
# type(self)._singleton.GROUND_MODEL.setP(-base_object.origin.getR())
# type(self)._singleton.GROUND_MODEL.setR(-base_object.origin.getR())
self.GROUND_MODEL.setPos(pos[0], pos[1], self.GROUND_HEIGHT)
# self.GROUND_MODEL.setP(-base_object.origin.getR())
# self.GROUND_MODEL.setR(-base_object.origin.getR())
return super(DepthCamera, self).track(base_object)

def get_image(self, base_object):
type(self)._singleton.origin.reparentTo(base_object.origin)
img = super(DepthCamera, type(self)._singleton).get_rgb_array()
self.origin.reparentTo(base_object.origin)
img = super(DepthCamera, self).get_rgb_array_cpu()
self.track(self.attached_object)
return img

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ def add_cloud_point_vis(
point_x, point_y, height, num_lasers, laser_index, ANGLE_FACTOR, MARK_COLOR0, MARK_COLOR1, MARK_COLOR2
):
f = laser_index / num_lasers if ANGLE_FACTOR else 1
f *= 0.9
f += 0.1
return laser_index, (point_x, point_y, height), (f * MARK_COLOR0, f * MARK_COLOR1, f * MARK_COLOR2)


Expand Down Expand Up @@ -118,15 +120,10 @@ def __init__(self, num_lasers: int = 16, distance: float = 50, enable_show=True)
if show:
for laser_debug in range(self.num_lasers):
ball = AssetLoader.loader.loadModel(AssetLoader.file_path("models", "box.bam"))
ball.setScale(0.001)
ball.setScale(0.5)
ball.setColor(0., 0.5, 0.5, 1)
shape = BulletSphereShape(0.1)
ghost = BulletGhostNode('Lidar Point')
ghost.setIntoCollideMask(CollisionGroup.AllOff)
ghost.addShape(shape)
laser_np = self.origin.attachNewNode(ghost)
self.cloud_points_vis.append(laser_np)
ball.getChildren().reparentTo(laser_np)
ball.reparentTo(self.origin)
self.cloud_points_vis.append(ball)
# self.origin.flattenStrong()

def perceive(self, base_vehicle, physics_world, detector_mask: np.ndarray = None):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from panda3d.core import NodePath

from metadrive.component.lane.abs_lane import AbstractLane
from metadrive.component.vehicle_module.distance_detector import DistanceDetector
from metadrive.component.sensors.distance_detector import DistanceDetector
from metadrive.constants import CamMask, CollisionGroup
from metadrive.engine.engine_utils import get_engine
from metadrive.utils.coordinates_shift import panda_vector
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from panda3d.core import Vec3

from metadrive.component.vehicle_module.base_camera import BaseCamera
from metadrive.component.sensors.base_camera import BaseCamera
from metadrive.constants import CamMask
from metadrive.engine.engine_utils import get_global_config, engine_initialized

Expand All @@ -9,12 +9,9 @@ class MiniMap(BaseCamera):
CAM_MASK = CamMask.MiniMap
display_region_size = [0., 1 / 3, 0.8, 1.0]

def __init__(self):
assert engine_initialized(), "You should initialize engine before adding camera to vehicle"
config = get_global_config()["vehicle_config"]["mini_map"]
self.BUFFER_W, self.BUFFER_H = config[0], config[1]
height = config[2]
super(MiniMap, self).__init__()
def __init__(self, engine, width, height, z_pos, cuda=False):
self.BUFFER_W, self.BUFFER_H, height = width, height, z_pos
super(MiniMap, self).__init__(engine=engine, need_cuda=cuda)

cam = self.get_cam()
lens = self.get_lens()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from metadrive.component.vehicle_module.base_camera import BaseCamera
from metadrive.component.sensors.base_camera import BaseCamera
from metadrive.constants import CamMask
from metadrive.engine.engine_utils import engine_initialized, get_global_config
from direct.filter.CommonFilters import CommonFilters
Expand All @@ -11,12 +11,9 @@ class RGBCamera(BaseCamera):
CAM_MASK = CamMask.RgbCam
PBR_ADAPT = False

def __init__(self):
assert engine_initialized(), "You should initialize engine before adding camera to vehicle"
config = get_global_config()["vehicle_config"]["rgb_camera"]
self.BUFFER_W, self.BUFFER_H = config[0], config[1]
cuda = True if get_global_config()["vehicle_config"]["image_source"] == "rgb_camera" else False
super(RGBCamera, self).__init__(True, cuda)
def __init__(self, engine, width, height, cuda=False):
self.BUFFER_W, self.BUFFER_H = width, height
super(RGBCamera, self).__init__(engine, True, cuda)
cam = self.get_cam()
lens = self.get_lens()
# cam.lookAt(0, 2.4, 1.3)
Expand Down

0 comments on commit e0bb542

Please sign in to comment.