Skip to content

Commit a730038

Browse files
Kashu7100duburcqa
authored andcommitted
[FEATURE] Add debug camera for monitoring. (Genesis-Embodied-AI#1521)
* Add debug camera for monitoring. * Only add rasterizer camera if needed. --------- Co-authored-by: Alexis Duburcq <[email protected]>
1 parent 26a7adc commit a730038

File tree

5 files changed

+82
-23
lines changed

5 files changed

+82
-23
lines changed

examples/rigid/single_franka_batch_render.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def main():
1515
parser.add_argument("-r", "--render_all_cameras", action="store_true", default=False)
1616
parser.add_argument("-o", "--output_dir", type=str, default="img_output/test")
1717
parser.add_argument("-u", "--use_rasterizer", action="store_true", default=False)
18+
parser.add_argument("-d", "--debug", action="store_true", default=False)
1819
args = parser.parse_args()
1920

2021
########################## init ##########################
@@ -37,6 +38,14 @@ def main():
3738
)
3839

3940
########################## cameras ##########################
41+
debug_cam = scene.add_camera(
42+
res=(720, 1280),
43+
pos=(1.5, -0.5, 1.0),
44+
lookat=(0.0, 0.0, 0.5),
45+
fov=60,
46+
GUI=args.vis,
47+
debug=True,
48+
)
4049
cam_0 = scene.add_camera(
4150
res=(512, 512),
4251
pos=(1.5, 0.5, 1.5),
@@ -75,14 +84,20 @@ def main():
7584
# Create an image exporter
7685
exporter = FrameImageExporter(args.output_dir)
7786

87+
if args.debug:
88+
debug_cam.start_recording()
7889
for i in range(args.n_steps):
7990
scene.step()
91+
if args.debug:
92+
debug_cam.render()
8093
if args.render_all_cameras:
8194
rgba, depth, _, _ = scene.render_all_cameras(rgb=True, depth=True)
8295
exporter.export_frame_all_cameras(i, rgb=rgba, depth=depth)
8396
else:
8497
rgba, depth, _, _ = cam_1.render(rgb=True, depth=True)
8598
exporter.export_frame_single_camera(i, cam_1.idx, rgb=rgba, depth=depth)
99+
if args.debug:
100+
debug_cam.stop_recording("debug_cam.mp4")
86101

87102

88103
if __name__ == "__main__":

genesis/engine/scene.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ def add_camera(
529529
spp=256,
530530
denoise=None,
531531
env_idx=None,
532+
debug=False,
532533
):
533534
"""
534535
Add a camera to the scene.
@@ -567,6 +568,12 @@ def add_camera(
567568
Whether to denoise the camera's rendered image. Only available when using the RayTracer renderer. Defaults
568569
to True on Linux, otherwise False. If OptiX denoiser is not available in your platform, consider enabling
569570
the OIDN denoiser option when building the RayTracer.
571+
debug : bool
572+
Whether to use the debug camera. It enables to create cameras that can used to monitor / debug the
573+
simulation without being part of the "sensors". Their output is rendered by the usual simple Rasterizer
574+
systematically, no matter if BatchRender and RayTracer is enabled. This way, it is possible to record the
575+
simulation with arbitrary resolution and camera pose, without interfering with what robots can perceive
576+
from their environment. Defaults to False.
570577
571578
Returns
572579
-------
@@ -576,7 +583,7 @@ def add_camera(
576583
if denoise is None:
577584
denoise = sys.platform != "darwin"
578585
return self._visualizer.add_camera(
579-
res, pos, lookat, up, model, fov, aperture, focus_dist, GUI, spp, denoise, env_idx
586+
res, pos, lookat, up, model, fov, aperture, focus_dist, GUI, spp, denoise, env_idx, debug
580587
)
581588

582589
@gs.assert_unbuilt

genesis/vis/batch_renderer.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,19 @@ def build(self):
8585
if gs.backend != gs.cuda:
8686
gs.raise_exception("BatchRenderer requires CUDA backend.")
8787

88-
cameras = self._visualizer._cameras
88+
self._cameras = gs.List([camera for camera in self._visualizer._cameras if not camera.debug])
8989
lights = self._lights
9090
rigid = self._visualizer.scene.rigid_solver
9191
n_envs = max(self._visualizer.scene.n_envs, 1)
92-
res = cameras[0].res
92+
res = self._cameras[0].res
9393
gpu_id = gs.device.index
9494
use_rasterizer = self._renderer_options.use_rasterizer
9595

9696
# Cameras
97-
n_cameras = len(cameras)
98-
cameras_pos = torch.stack([camera.get_pos() for camera in cameras], dim=1)
99-
cameras_quat = torch.stack([camera.get_quat() for camera in cameras], dim=1)
100-
cameras_fov = torch.tensor([camera.fov for camera in cameras], dtype=gs.tc_float, device=gs.device)
97+
n_cameras = len(self._cameras)
98+
cameras_pos = torch.stack([camera.get_pos() for camera in self._cameras], dim=1)
99+
cameras_quat = torch.stack([camera.get_quat() for camera in self._cameras], dim=1)
100+
cameras_fov = torch.tensor([camera.fov for camera in self._cameras], dtype=gs.tc_float, device=gs.device)
101101

102102
# Build taichi arrays to store light properties once. If later we need to support dynamic lights, we should
103103
# consider storing light properties as taichi fields in Genesis.
@@ -179,8 +179,8 @@ def render(self, rgb=True, depth=False, segmentation=False, normal=False, force_
179179
self.update_scene()
180180

181181
# Render frame
182-
cameras_pos = torch.stack([camera.get_pos() for camera in self._visualizer._cameras], dim=1)
183-
cameras_quat = torch.stack([camera.get_quat() for camera in self._visualizer._cameras], dim=1)
182+
cameras_pos = torch.stack([camera.get_pos() for camera in self._cameras], dim=1)
183+
cameras_quat = torch.stack([camera.get_quat() for camera in self._cameras], dim=1)
184184
render_options = np.array((rgb_, depth_, False, False, aliasing), dtype=np.uint32)
185185
rgba_arr_all, depth_arr_all = self._renderer.render(
186186
self._visualizer.scene.rigid_solver, cameras_pos, cameras_quat, render_options
@@ -217,3 +217,7 @@ def reset(self):
217217
@property
218218
def lights(self):
219219
return self._lights
220+
221+
@property
222+
def cameras(self):
223+
return self._cameras

genesis/vis/camera.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import time
44
import math
5+
from functools import lru_cache
56

67
import cv2
78
import numpy as np
@@ -66,6 +67,14 @@ class Camera(Sensor):
6667
The far plane of the camera.
6768
transform : np.ndarray, shape (4, 4), optional
6869
The transform matrix of the camera.
70+
env_idx : int, optional
71+
The index of the environment to track the camera.
72+
debug : bool, optional
73+
Whether to use the debug camera. It enables to create cameras that can used to monitor / debug the
74+
simulation without being part of the "sensors". Their output is rendered by the usual simple Rasterizer
75+
systematically, no matter if BatchRender and RayTracer is enabled. This way, it is possible to record the
76+
simulation with arbitrary resolution and camera pose, without interfering with what robots can perceive
77+
from their environment. Defaults to False.
6978
"""
7079

7180
def __init__(
@@ -87,6 +96,7 @@ def __init__(
8796
far=100.0,
8897
transform=None,
8998
env_idx=None,
99+
debug=False,
90100
):
91101
self._idx = idx
92102
self._uid = gs.UID()
@@ -111,6 +121,7 @@ def __init__(
111121
self._is_built = False
112122
self._attached_link = None
113123
self._attached_offset_T = None
124+
self._debug = debug
114125

115126
self._env_idx = env_idx
116127
self._envs_offset = None
@@ -139,11 +150,10 @@ def build(self):
139150

140151
self._envs_offset = torch.as_tensor(self._visualizer._scene.envs_offset, dtype=gs.tc_float, device=gs.device)
141152

142-
self._batch_renderer = self._visualizer.batch_renderer
143153
self._rasterizer = self._visualizer.rasterizer
144-
self._raytracer = self._visualizer.raytracer
154+
self._raytracer = self._visualizer.raytracer if not self._debug else None
155+
self._batch_renderer = self._visualizer.batch_renderer if not self._debug else None
145156

146-
self._rasterizer.add_camera(self)
147157
if self._batch_renderer is not None:
148158
self._rgb_stacked = True
149159
self._other_stacked = True
@@ -157,6 +167,7 @@ def build(self):
157167
self._rgb_stacked = False
158168
self._other_stacked = False
159169
else:
170+
self._rasterizer.add_camera(self)
160171
self._rgb_stacked = self._visualizer._context.env_separate_rigid
161172
self._other_stacked = self._visualizer._context.env_separate_rigid
162173

@@ -323,13 +334,13 @@ def _batch_render(
323334
# If n_envs == 0, the second dimension of the output is camera.
324335
# Only return the current camera's image
325336
if rgb_arr:
326-
rgb_arr = rgb_arr[self._idx]
337+
rgb_arr = rgb_arr[self.idx]
327338
if depth:
328-
depth_arr = depth_arr[self._idx]
339+
depth_arr = depth_arr[self.idx]
329340
if segmentation:
330-
seg_arr = seg_arr[self._idx]
341+
seg_arr = seg_arr[self.idx]
331342
if normal:
332-
normal_arr = normal_arr[self._idx]
343+
normal_arr = normal_arr[self.idx]
333344
return rgb_arr, depth_arr, seg_arr, normal_arr
334345

335346
@gs.assert_built
@@ -533,7 +544,6 @@ def render_pointcloud(self, world_frame=True):
533544
point_cloud = point_cloud[:, :3].reshape((*depth_arr.shape, 3))
534545
return point_cloud, mask
535546

536-
@gs.assert_built
537547
def set_pose(self, transform=None, pos=None, lookat=None, up=None, env_idx=None):
538548
"""
539549
Set the pose of the camera.
@@ -608,9 +618,10 @@ def set_pose(self, transform=None, pos=None, lookat=None, up=None, env_idx=None)
608618
self._multi_env_transform_tensor[env_idx] = transform
609619
self._multi_env_quat_tensor[env_idx] = _T_to_quat_for_madrona(transform)
610620

611-
self._rasterizer.update_camera(self)
612621
if self._raytracer is not None:
613622
self._raytracer.update_camera(self)
623+
elif self._batch_renderer is None:
624+
self._rasterizer.update_camera(self)
614625

615626
@gs.assert_built
616627
def set_params(self, fov=None, aperture=None, focus_dist=None, intrinsics=None):
@@ -651,9 +662,10 @@ def set_params(self, fov=None, aperture=None, focus_dist=None, intrinsics=None):
651662
else:
652663
self._fov = intrinsics_fov
653664

654-
self._rasterizer.update_camera(self)
655665
if self._raytracer is not None:
656666
self._raytracer.update_camera(self)
667+
elif self._batch_renderer is None:
668+
self._rasterizer.update_camera(self)
657669

658670
@gs.assert_built
659671
def start_recording(self):
@@ -753,7 +765,7 @@ def is_built(self):
753765

754766
@property
755767
def idx(self):
756-
"""The integer index of the camera."""
768+
"""The global integer index of the camera."""
757769
return self._idx
758770

759771
@property
@@ -839,6 +851,11 @@ def env_idx(self):
839851
"""Index of the environment being tracked by the camera."""
840852
return self._env_idx
841853

854+
@property
855+
def debug(self):
856+
"""Whether the camera is a debug camera."""
857+
return self._debug
858+
842859
@property
843860
def pos(self):
844861
"""The current position of the camera for the tracked environment."""

genesis/vis/visualizer.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,33 @@ def destroy(self):
127127
self.viewer_lock = None
128128
self._renderer = None
129129

130-
def add_camera(self, res, pos, lookat, up, model, fov, aperture, focus_dist, GUI, spp, denoise, env_idx):
131-
cam_idx = len(self._cameras)
130+
def add_camera(self, res, pos, lookat, up, model, fov, aperture, focus_dist, GUI, spp, denoise, env_idx, debug):
131+
cam_idx = len([camera for camera in self._cameras if camera.debug == debug])
132132
camera = Camera(
133-
self, cam_idx, model, res, pos, lookat, up, fov, aperture, focus_dist, GUI, spp, denoise, env_idx=env_idx
133+
self,
134+
cam_idx,
135+
model,
136+
res,
137+
pos,
138+
lookat,
139+
up,
140+
fov,
141+
aperture,
142+
focus_dist,
143+
GUI,
144+
spp,
145+
denoise,
146+
env_idx=env_idx,
147+
debug=debug,
134148
)
135149
self._cameras.append(camera)
136150
return camera
137151

138152
def add_light(self, pos, dir, intensity, directional, castshadow, cutoff):
139153
if self._batch_renderer is not None:
140154
self._batch_renderer.add_light(pos, dir, intensity, directional, castshadow, cutoff)
155+
else:
156+
gs.raise_exception("`add_light` is specifically for batch renderer.")
141157

142158
def reset(self):
143159
self._t = -1

0 commit comments

Comments
 (0)