Skip to content

Commit 591259c

Browse files
committed
WIP
1 parent 81577d1 commit 591259c

File tree

12 files changed

+423
-476
lines changed

12 files changed

+423
-476
lines changed

.github/workflows/production.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
WANDB_API_KEY: ${{ secrets.WANDB_API_KEY }}
2323
HF_TOKEN: ${{ secrets.HF_TOKEN }}
2424
HF_HUB_DOWNLOAD_TIMEOUT: 60
25-
GENESIS_IMAGE_VER: "1_0"
25+
GENESIS_IMAGE_VER: "1_1"
2626
TIMEOUT_MINUTES: 180
2727

2828
steps:

examples/rigid/single_franka_batch_render.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ def main():
4242
pos=(1.5, 0.5, 1.5),
4343
lookat=(0.0, 0.0, 0.5),
4444
fov=45,
45-
GUI=True,
45+
GUI=args.vis,
4646
)
4747
cam_0.attach(franka.links[6], trans_to_T(np.array([0.0, 0.5, 0.0])))
4848
cam_1 = scene.add_camera(
4949
res=(512, 512),
5050
pos=(1.5, -0.5, 1.5),
5151
lookat=(0.0, 0.0, 0.5),
5252
fov=45,
53-
GUI=True,
53+
GUI=args.vis,
5454
)
5555
scene.add_light(
5656
pos=[0.0, 0.0, 1.5],
@@ -81,8 +81,8 @@ def main():
8181
rgba, depth, _, _ = scene.render_all_cameras(rgb=True, depth=True)
8282
exporter.export_frame_all_cameras(i, rgb=rgba, depth=depth)
8383
else:
84-
rgba, depth, _, _ = cam_0.render(rgb=True, depth=True)
85-
exporter.export_frame_single_camera(i, cam_0.idx, rgb=rgba, depth=depth)
84+
rgba, depth, _, _ = cam_1.render(rgb=True, depth=True)
85+
exporter.export_frame_single_camera(i, cam_1.idx, rgb=rgba, depth=depth)
8686

8787

8888
if __name__ == "__main__":

genesis/engine/entities/rigid_entity/rigid_link.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from typing import TYPE_CHECKING
22

33
import numpy as np
4-
from numpy.typing import ArrayLike
54
import taichi as ti
65
import torch
6+
from numpy.typing import ArrayLike
77

88
import genesis as gs
99
import trimesh

genesis/engine/scene.py

Lines changed: 54 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import os
2+
import pickle
3+
import time
24

35
import numpy as np
46
import torch
5-
import pickle
6-
import time
77
import taichi as ti
8+
from numpy.typing import ArrayLike
89

910
import genesis as gs
1011
import genesis.utils.geom as gu
@@ -434,95 +435,84 @@ def link_entities(
434435

435436
if child_link._parent_idx != -1:
436437
gs.logger.warning(
437-
"Child entity already has a parent link. This may cause the entity to break into parts. Make sure this operation is intended."
438+
"Child entity already has a parent link. This may cause the entity to break into parts. Make sure "
439+
"this operation is intended."
438440
)
439441
child_link._parent_idx = parent_link.idx
440442
parent_link._child_idxs.append(child_link.idx)
441443

442444
@gs.assert_unbuilt
443445
def add_light(
444446
self,
445-
morph: Morph,
446-
color=(1.0, 1.0, 1.0, 1.0),
447-
intensity=20.0,
448-
revert_dir=False,
449-
double_sided=False,
450-
beam_angle=180.0,
447+
*,
448+
morph: Morph | None = None,
449+
color: ArrayLike | None = (1.0, 1.0, 1.0, 1.0),
450+
intensity: float = 20.0,
451+
revert_dir: bool | None = False,
452+
double_sided: bool | None = False,
453+
beam_angle: float | None = 180.0,
454+
pos: ArrayLike | None = None,
455+
dir: ArrayLike | None = None,
456+
directional: bool | None = None,
457+
castshadow: bool | None = None,
458+
cutoff: float | None = None,
451459
):
452460
"""
453-
Add a light to the scene. Note that lights added this way can be instantiated from morphs
454-
(supporting `gs.morphs.Primitive` or `gs.morphs.Mesh`), and will only be used by the RayTracer renderer.
461+
Add a light to the scene.
462+
463+
Warning
464+
-------
465+
The signature of this method is different depending on the renderer being used, i.e.:
466+
- RayTracer: 'add_light(self, morph, color, intensity, revert_dir, double_sided, beam_angle)'
467+
- BatchRender: 'add_ligth(self, pos, dir, intensity, directional, castshadow, cutoff)'
468+
- Rasterizer: **Unsupported**
455469
456470
Parameters
457471
----------
458472
morph : gs.morphs.Morph
459-
The morph of the light. Must be an instance of `gs.morphs.Primitive` or `gs.morphs.Mesh`.
473+
The morph of the light. Must be an instance of `gs.morphs.Primitive` or `gs.morphs.Mesh`. Only supported by
474+
RayTracer.
460475
color : tuple of float, shape (3,)
461-
The color of the light, specified as (r, g, b).
476+
The color of the light, specified as (r, g, b). Only supported by RayTracer.
462477
intensity : float
463478
The intensity of the light.
464479
revert_dir : bool
465480
Whether to revert the direction of the light. If True, the light will be emitted towards the mesh's inside.
481+
Only supported by RayTracer.
466482
double_sided : bool
467-
Whether to emit light from both sides of surface.
483+
Whether to emit light from both sides of surface. Only supported by RayTracer.
468484
beam_angle : float
469-
The beam angle of the light.
470-
"""
471-
if isinstance(self.renderer_options, gs.renderers.BatchRenderer):
472-
gs.logger.warning(
473-
"This add_light() function is only supported when NOT using BatchRenderer."
474-
"Please use add_light(self, pos, dir, intensity, directional, castshadow, cutoff) instead."
475-
)
476-
return
477-
478-
if self.visualizer.raytracer is None:
479-
gs.logger.warning("Light is only supported by RayTracer renderer.")
480-
return
481-
482-
if not isinstance(morph, (gs.morphs.Primitive, gs.morphs.Mesh)):
483-
gs.raise_exception("Light morph only supports `gs.morphs.Primitive` or `gs.morphs.Mesh`.")
484-
485-
mesh = gs.Mesh.from_morph_surface(morph, gs.surfaces.Plastic(smooth=False))
486-
self.visualizer.raytracer.add_mesh_light(
487-
mesh, color, intensity, morph.pos, morph.quat, revert_dir, double_sided, beam_angle
488-
)
489-
490-
@gs.assert_unbuilt
491-
def add_light(
492-
self,
493-
pos,
494-
dir,
495-
intensity,
496-
directional,
497-
castshadow,
498-
cutoff,
499-
):
500-
"""
501-
Add a light to the scene for batch renderer.
502-
503-
Parameters
504-
----------
485+
The beam angle of the light. Only supported by RayTracer.
505486
pos : tuple of float, shape (3,)
506-
The position of the light, specified as (x, y, z).
487+
The position of the light, specified as (x, y, z). Only supported by BatchRenderer.
507488
dir : tuple of float, shape (3,)
508-
The direction of the light, specified as (x, y, z).
489+
The direction of the light, specified as (x, y, z). Only supported by BatchRenderer.
509490
intensity : float
510-
The intensity of the light.
491+
The intensity of the light. Only supported by BatchRenderer.
511492
directional : bool
512-
Whether the light is directional.
493+
Whether the light is directional. Only supported by BatchRenderer.
513494
castshadow : bool
514-
Whether the light casts shadows.
495+
Whether the light casts shadows. Only supported by BatchRenderer.
515496
cutoff : float
516-
The cutoff angle of the light in degrees.
497+
The cutoff angle of the light in degrees. Only supported by BatchRenderer.
517498
"""
518-
if not isinstance(self.renderer_options, gs.renderers.BatchRenderer):
519-
gs.logger.warning(
520-
"This add_light() function is only supported when using BatchRenderer."
521-
"Please use add_light(self, morph, color, intensity, revert_dir, double_sided, beam_angle) instead."
499+
if self._visualizer.batch_renderer is not None:
500+
if any(map(lambda e: e is None, (pos, dir, intensity, directional, castshadow, cutoff))):
501+
gs.raise_exception("Input arguments do not complain with expected signature when using 'BatchRenderer'")
502+
503+
self.visualizer.add_light(pos, dir, intensity, directional, castshadow, cutoff)
504+
elif self.visualizer.raytracer is not None:
505+
if any(map(lambda e: e is None, (morph, color, intensity, revert_dir, double_sided, beam_angle))):
506+
gs.raise_exception("Input arguments do not complain with expected signature when using 'RayTracer'")
507+
if not isinstance(morph, (gs.morphs.Primitive, gs.morphs.Mesh)):
508+
gs.raise_exception("Light morph only supports `gs.morphs.Primitive` or `gs.morphs.Mesh`.")
509+
510+
mesh = gs.Mesh.from_morph_surface(morph, gs.surfaces.Plastic(smooth=False))
511+
self.visualizer.raytracer.add_mesh_light(
512+
mesh, color, intensity, morph.pos, morph.quat, revert_dir, double_sided, beam_angle
522513
)
523-
return
524-
525-
self.visualizer.add_light(pos, dir, intensity, directional, castshadow, cutoff)
514+
else:
515+
gs.raise_exception("Adding lights is only supported by 'RayTracer' and 'BatchRenderer'.")
526516

527517
@gs.assert_unbuilt
528518
def add_camera(
@@ -1110,7 +1100,7 @@ def render_all_cameras(self, rgb=True, depth=False, normal=False, segmentation=F
11101100
Returns:
11111101
A tuple of tensors of shape (n_envs, H, W, 3) if rgb is not None,
11121102
otherwise a list of tensors of shape (n_envs, H, W, 1) if depth is not None.
1113-
If n_envs ==0, the first dimension of the tensor is squeezed.
1103+
If n_envs == 0, the first dimension of the tensor is squeezed.
11141104
"""
11151105
return self._visualizer.batch_renderer.render(rgb, depth, normal, segmentation, force_render)
11161106

genesis/utils/image_exporter.py

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,22 @@
99
from genesis.utils.misc import tensor_to_array
1010

1111

12-
class FrameImageExporter:
13-
@staticmethod
14-
def _export_frame_rgb_camera(i_env, export_dir, i_cam, i_step, rgb):
15-
# Take the rgb channel in case the rgb tensor has RGBA channel.
16-
rgb = np.flip(tensor_to_array(rgb[i_env, ..., :3]), axis=-1)
17-
cv2.imwrite(f"{export_dir}/rgb_cam{i_cam}_env{i_env}_{i_step:03d}.png", rgb)
12+
def _export_frame_rgb_camera(i_env, export_dir, i_cam, i_step, rgb):
13+
# Take the rgb channel in case the rgb tensor has RGBA channel.
14+
rgb = np.flip(tensor_to_array(rgb[i_env, ..., :3]), axis=-1)
15+
cv2.imwrite(f"{export_dir}/rgb_cam{i_cam}_env{i_env}_{i_step:03d}.png", rgb)
16+
1817

19-
@staticmethod
20-
def _export_frame_depth_camera(i_env, export_dir, i_cam, i_step, depth):
21-
depth = tensor_to_array(depth[i_env])
22-
cv2.imwrite(f"{export_dir}/depth_cam{i_cam}_env{i_env}_{i_step:03d}.png", depth)
18+
def _export_frame_depth_camera(i_env, export_dir, i_cam, i_step, depth):
19+
depth = tensor_to_array(depth[i_env])
20+
cv2.imwrite(f"{export_dir}/depth_cam{i_cam}_env{i_env}_{i_step:03d}.png", depth)
21+
22+
23+
class FrameImageExporter:
24+
"""
25+
This class enables exporting images from all cameras and all environments in batch and in parallel, unlike
26+
`Camera.(start|stop)_recording` API, which only allows for exporting images from a single camera and environment.
27+
"""
2328

2429
def __init__(self, export_dir, depth_clip_max=100, depth_scale="log"):
2530
self.export_dir = export_dir
@@ -38,7 +43,7 @@ def _normalize_depth(self, depth):
3843
Normalized depth tensor as uint8
3944
"""
4045
# Clip depth values
41-
depth = depth.clamp(0, self.depth_clip_max)
46+
depth = depth.clamp(0.0, self.depth_clip_max)
4247

4348
# Apply scaling if specified
4449
if self.depth_scale == "log":
@@ -49,7 +54,9 @@ def _normalize_depth(self, depth):
4954
depth_max = depth.amax(dim=(-3, -2), keepdim=True)
5055

5156
# Normalize to 0-255 range
52-
return ((depth - depth_min) / (depth_max - depth_min) * 255).to(torch.uint8)
57+
return torch.where(
58+
depth_max - depth_min > gs.EPS, ((depth_max - depth) / (depth_max - depth_min) * 255).to(torch.uint8), 0
59+
)
5360

5461
def export_frame_all_cameras(self, i_step, camera_idx=None, rgb=None, depth=None):
5562
"""
@@ -58,23 +65,25 @@ def export_frame_all_cameras(self, i_step, camera_idx=None, rgb=None, depth=None
5865
Args:
5966
i_step: The current step index.
6067
camera_idx: array of indices of cameras to export. If None, all cameras are exported.
61-
rgb: rgb image is a tuple of tensors of shape (n_envs, H, W, 3).
62-
depth: Depth image is a tuple of tensors of shape (n_envs, H, W).
68+
rgb: rgb image is a sequence of tensors of shape (n_envs, H, W, 3).
69+
depth: Depth image is a sequence of tensors of shape (n_envs, H, W).
6370
"""
6471
if rgb is None and depth is None:
65-
print("No rgb or depth to export")
72+
gs.logger.info("No rgb or depth images to export")
6673
return
67-
if rgb is not None:
68-
assert isinstance(rgb, tuple) and len(rgb) > 0, "rgb must be a tuple of tensors with length > 0"
69-
if depth is not None:
70-
assert isinstance(depth, tuple) and len(depth) > 0, "depth must be a tuple of tensors with length > 0"
74+
if rgb is not None and (not isinstance(rgb, (tuple, list)) or not rgb):
75+
gs.raise_exception("'rgb' must be a non-empty sequence of tensors.")
76+
if depth is not None and (not isinstance(depth, (tuple, list)) or not depth):
77+
gs.raise_exception("'depth' must be a non-empty sequence of tensors.")
7178
if camera_idx is None:
72-
camera_idx = range(len(depth if rgb is None else rgb))
79+
camera_idx = range(len(depth or rgb))
7380
for i_cam in camera_idx:
74-
rgb_cam = rgb[i_cam] if rgb is not None and i_cam < len(rgb) else None
75-
depth_cam = depth[i_cam] if depth is not None and i_cam < len(depth) else None
76-
if rgb_cam is not None or depth_cam is not None:
77-
self.export_frame_single_camera(i_step, i_cam, rgb_cam, depth_cam)
81+
rgb_cam, depth_cam = None, None
82+
if rgb is not None:
83+
rgb_cam = rgb[i_cam]
84+
if depth is not None:
85+
depth_cam = depth[i_cam]
86+
self.export_frame_single_camera(i_step, i_cam, rgb_cam, depth_cam)
7887

7988
def export_frame_single_camera(self, i_step, i_cam, rgb=None, depth=None):
8089
"""
@@ -92,10 +101,11 @@ def export_frame_single_camera(self, i_step, i_cam, rgb=None, depth=None):
92101
# Unsqueeze rgb to (n_envs, H, W, 3)
93102
if rgb.ndim == 3:
94103
rgb = rgb.unsqueeze(0)
95-
assert rgb.ndim == 4, "rgb must be of shape (n_envs, H, W, 3)"
104+
if rgb.ndim != 4 or rgb.shape[-1] != 3:
105+
gs.raise_exception("'rgb' must be a tensor of shape (n_envs, H, W, 3)")
96106

97107
rgb_job = partial(
98-
FrameImageExporter._export_frame_rgb_camera,
108+
_export_frame_rgb_camera,
99109
export_dir=self.export_dir,
100110
i_cam=i_cam,
101111
i_step=i_step,
@@ -112,12 +122,13 @@ def export_frame_single_camera(self, i_step, i_cam, rgb=None, depth=None):
112122
if depth.ndim == 3:
113123
depth = depth.unsqueeze(0)
114124
elif depth.ndim == 2:
115-
depth = depth.reshape(1, depth.shape[0], depth.shape[1], 1)
125+
depth = depth.reshape((1, *depth.shape, 1))
116126
depth = self._normalize_depth(depth)
117-
assert depth.ndim == 4, "depth must be of shape (n_envs, H, W, 1)"
127+
if depth.ndim != 4 or depth.shape[-1] != 1:
128+
gs.raise_exception("'rgb' must be a tensor of shape (n_envs, H, W, 1)")
118129

119130
depth_job = partial(
120-
FrameImageExporter._export_frame_depth_camera,
131+
_export_frame_depth_camera,
121132
export_dir=self.export_dir,
122133
i_cam=i_cam,
123134
i_step=i_step,

genesis/utils/misc.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,8 @@ def get_device(backend: gs_backend):
205205
total_mem = device_property.total_memory / 1024**3
206206
else: # pytorch tensors on cpu
207207
# logger may not be configured at this point
208-
getattr(gs, "logger", LOGGER).warning(
209-
"No Intel XPU device available. Falling back to CPU for torch device."
210-
)
208+
logger = getattr(gs, "logger", None) or LOGGER
209+
logger.warning("No Intel XPU device available. Falling back to CPU for torch device.")
211210
device, device_name, total_mem, _ = get_device(gs_backend.cpu)
212211

213212
elif backend == gs_backend.gpu:

0 commit comments

Comments
 (0)