Skip to content

Commit 1272b80

Browse files
authored
chore(layout): Expose the camera setting for interface parallel_sim. (#39)
* chore(layout): Expose the camera setting for interface parallel_sim and update layout file.
1 parent cf3b919 commit 1272b80

File tree

10 files changed

+87
-36
lines changed

10 files changed

+87
-36
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ Example: generate multiple parallel simulation envs with `gym.make` and record s
258258
python embodied_gen/scripts/parallel_sim.py \
259259
--layout_file "outputs/layouts_gen/task_0000/layout.json" \
260260
--output_dir "outputs/parallel_sim/task_0000" \
261-
--num_envs 20
261+
--num_envs 16
262262
```
263263

264264
### 🖼️ Real-to-Sim Digital Twin

embodied_gen/envs/pick_embodiedgen.py

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
import json
1818
import os
19-
from copy import deepcopy
2019

2120
import numpy as np
2221
import sapien
@@ -26,6 +25,7 @@
2625
from mani_skill.sensors.camera import CameraConfig
2726
from mani_skill.utils import sapien_utils
2827
from mani_skill.utils.building import actors
28+
from mani_skill.utils.building.ground import build_ground
2929
from mani_skill.utils.registration import register_env
3030
from mani_skill.utils.structs.actor import Actor
3131
from mani_skill.utils.structs.pose import Pose
@@ -78,6 +78,14 @@ def __init__(
7878
# Add small offset in z-axis to avoid collision.
7979
self.objs_z_offset = kwargs.pop("objs_z_offset", 0.002)
8080
self.robot_z_offset = kwargs.pop("robot_z_offset", 0.002)
81+
self.camera_cfg = kwargs.pop("camera_cfg", None)
82+
if self.camera_cfg is None:
83+
self.camera_cfg = dict(
84+
camera_eye=[0.9, 0.0, 1.1],
85+
camera_target_pt=[0.0, 0.0, 0.9],
86+
image_hw=[256, 256],
87+
fovy_deg=75,
88+
)
8189

8290
self.layouts = self.init_env_layouts(
8391
layout_file, num_envs, replace_objs
@@ -106,22 +114,30 @@ def __init__(
106114
def init_env_layouts(
107115
layout_file: str, num_envs: int, replace_objs: bool
108116
) -> list[LayoutInfo]:
109-
layout = LayoutInfo.from_dict(json.load(open(layout_file, "r")))
110117
layouts = []
111118
for env_idx in range(num_envs):
112119
if replace_objs and env_idx > 0:
113-
layout = bfs_placement(deepcopy(layout))
114-
layouts.append(layout)
120+
layout_info = bfs_placement(layout_file)
121+
else:
122+
layout_info = json.load(open(layout_file, "r"))
123+
layout_info = LayoutInfo.from_dict(layout_info)
124+
125+
layout_path = layout_file.replace(".json", f"_env{env_idx}.json")
126+
with open(layout_path, "w") as f:
127+
json.dump(layout_info.to_dict(), f, indent=4)
128+
129+
layouts.append(layout_path)
115130

116131
return layouts
117132

118133
@staticmethod
119134
def compute_robot_init_pose(
120-
layouts: list[LayoutInfo], num_envs: int, z_offset: float = 0.0
135+
layouts: list[str], num_envs: int, z_offset: float = 0.0
121136
) -> list[list[float]]:
122137
robot_pose = []
123138
for env_idx in range(num_envs):
124-
layout = layouts[env_idx]
139+
layout = json.load(open(layouts[env_idx], "r"))
140+
layout = LayoutInfo.from_dict(layout)
125141
robot_node = layout.relation[Scene3DItemEnum.ROBOT.value]
126142
x, y, z, qx, qy, qz, qw = layout.position[robot_node]
127143
robot_pose.append([x, y, z + z_offset, qw, qx, qy, qz])
@@ -154,19 +170,27 @@ def _default_sensor_configs(self):
154170
@property
155171
def _default_human_render_camera_configs(self):
156172
pose = sapien_utils.look_at(
157-
eye=[0.9, 0.0, 1.1], target=[0.0, 0.0, 0.9]
173+
eye=self.camera_cfg["camera_eye"],
174+
target=self.camera_cfg["camera_target_pt"],
158175
)
159176

160177
return CameraConfig(
161-
"render_camera", pose, 256, 256, np.deg2rad(75), 0.01, 100
178+
"render_camera",
179+
pose,
180+
self.camera_cfg["image_hw"][1],
181+
self.camera_cfg["image_hw"][0],
182+
np.deg2rad(self.camera_cfg["fovy_deg"]),
183+
0.01,
184+
100,
162185
)
163186

164187
def _load_agent(self, options: dict):
188+
self.ground = build_ground(self.scene)
165189
super()._load_agent(options, sapien.Pose(p=[-10, 0, 10]))
166190

167191
def _load_scene(self, options: dict):
168192
all_objects = []
169-
logger.info(f"Loading assets and decomposition mesh collisions...")
193+
logger.info(f"Loading EmbodiedGen assets...")
170194
for env_idx in range(self.num_envs):
171195
env_actors = load_assets_from_layout_file(
172196
self.scene,
@@ -229,20 +253,26 @@ def _initialize_episode(self, env_idx: torch.Tensor, options: dict):
229253
self.agent.controller.controllers["gripper"].reset()
230254

231255
def render_gs3d_images(
232-
self, layouts: list[LayoutInfo], num_envs: int, init_quat: list[float]
256+
self, layouts: list[str], num_envs: int, init_quat: list[float]
233257
) -> dict[str, np.ndarray]:
234258
sim_coord_align = (
235259
torch.tensor(SIM_COORD_ALIGN).to(torch.float32).to(self.device)
236260
)
237261
cameras = self.scene.sensors.copy()
238262
cameras.update(self.scene.human_render_cameras)
239263

240-
bg_node = layouts[0].relation[Scene3DItemEnum.BACKGROUND.value]
241-
gs_path = os.path.join(layouts[0].assets[bg_node], "gs_model.ply")
264+
# Preload the background Gaussian Splatting model.
265+
asset_root = os.path.dirname(layouts[0])
266+
layout = LayoutInfo.from_dict(json.load(open(layouts[0], "r")))
267+
bg_node = layout.relation[Scene3DItemEnum.BACKGROUND.value]
268+
gs_path = os.path.join(
269+
asset_root, layout.assets[bg_node], "gs_model.ply"
270+
)
242271
raw_gs: GaussianOperator = GaussianOperator.load_from_ply(gs_path)
243272
bg_images = dict()
244273
for env_idx in tqdm(range(num_envs), desc="Pre-rendering Background"):
245-
layout = layouts[env_idx]
274+
layout = json.load(open(layouts[env_idx], "r"))
275+
layout = LayoutInfo.from_dict(layout)
246276
x, y, z, qx, qy, qz, qw = layout.position[bg_node]
247277
qx, qy, qz, qw = quaternion_multiply([qx, qy, qz, qw], init_quat)
248278
init_pose = torch.tensor([x, y, z, qx, qy, qz, qw])

embodied_gen/scripts/compose_layout.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,7 @@ def entrypoint(**kwargs):
5050
out_scene_path = f"{output_dir}/Iscene.glb"
5151
out_layout_path = f"{output_dir}/layout.json"
5252

53-
with open(args.layout_path, "r") as f:
54-
layout_info = LayoutInfo.from_dict(json.load(f))
55-
56-
layout_info = bfs_placement(layout_info, seed=args.seed)
53+
layout_info = bfs_placement(args.layout_path, seed=args.seed)
5754
with open(out_layout_path, "w") as f:
5855
json.dump(layout_info.to_dict(), f, indent=4)
5956

embodied_gen/scripts/gen_layout.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,15 @@ def entrypoint() -> None:
119119
match_scene_path = f"{os.path.dirname(args.bg_list)}/{match_key}"
120120
bg_save_dir = os.path.join(output_root, "background")
121121
copytree(match_scene_path, bg_save_dir, dirs_exist_ok=True)
122-
layout_info.assets[bg_node] = bg_save_dir
122+
layout_info.assets[bg_node] = "background"
123123

124124
# BFS layout placement.
125+
layout_path = f"{output_root}/layout.json"
126+
with open(layout_path, "w") as f:
127+
json.dump(layout_info.to_dict(), f, indent=4)
128+
125129
layout_info = bfs_placement(
126-
layout_info,
130+
layout_path,
127131
limit_reach_range=True if args.insert_robot else False,
128132
seed=args.seed_layout,
129133
)

embodied_gen/scripts/parallel_sim.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
monkey_patch_maniskill()
2121
import json
2222
from collections import defaultdict
23-
from dataclasses import dataclass
23+
from dataclasses import dataclass, field
2424
from typing import Literal
2525

2626
import gymnasium as gym
@@ -69,6 +69,18 @@ class ParallelSimConfig:
6969
reach_target_only: bool = True
7070
"""Whether to only reach target without full action"""
7171

72+
# Camera settings
73+
camera_eye: list[float] = field(default_factory=lambda: [0.9, 0.0, 1.1])
74+
"""Camera eye position [x, y, z] in global coordiante system"""
75+
camera_target_pt: list[float] = field(
76+
default_factory=lambda: [0.0, 0.0, 0.9]
77+
)
78+
"""Camera target(look-at) point [x, y, z] in global coordiante system"""
79+
image_hw: list[int] = field(default_factory=lambda: [512, 512])
80+
"""Rendered image height and width [height, width]"""
81+
fovy_deg: float = 75
82+
"""Camera vertical field of view in degrees"""
83+
7284

7385
def entrypoint(**kwargs):
7486
if kwargs is None or len(kwargs) == 0:
@@ -83,6 +95,12 @@ def entrypoint(**kwargs):
8395
enable_shadow=cfg.enable_shadow,
8496
layout_file=cfg.layout_file,
8597
control_mode=cfg.control_mode,
98+
camera_cfg=dict(
99+
camera_eye=cfg.camera_eye,
100+
camera_target_pt=cfg.camera_target_pt,
101+
image_hw=cfg.image_hw,
102+
fovy_deg=cfg.fovy_deg,
103+
),
86104
)
87105
env = RecordEpisode(
88106
env,

embodied_gen/scripts/simulate_sapien.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,16 @@ def entrypoint(**kwargs):
9191
fovy_deg=cfg.fovy_deg,
9292
)
9393
with open(cfg.layout_path, "r") as f:
94-
layout_data = json.load(f)
95-
layout_data: LayoutInfo = LayoutInfo.from_dict(layout_data)
94+
layout_data: LayoutInfo = LayoutInfo.from_dict(json.load(f))
9695

9796
actors = load_assets_from_layout_file(
9897
scene_manager.scene,
99-
layout_data,
98+
cfg.layout_path,
10099
cfg.z_offset,
101100
cfg.init_quat,
102101
)
103102
agent = load_mani_skill_robot(
104-
scene_manager.scene, layout_data, cfg.control_freq
103+
scene_manager.scene, cfg.layout_path, cfg.control_freq
105104
)
106105

107106
frames = defaultdict(list)
@@ -134,8 +133,9 @@ def entrypoint(**kwargs):
134133
if "Foreground" not in cfg.render_keys:
135134
return
136135

136+
asset_root = os.path.dirname(cfg.layout_path)
137137
bg_node = layout_data.relation[Scene3DItemEnum.BACKGROUND.value]
138-
gs_path = f"{layout_data.assets[bg_node]}/gs_model.ply"
138+
gs_path = f"{asset_root}/{layout_data.assets[bg_node]}/gs_model.ply"
139139
gs_model: GaussianOperator = GaussianOperator.load_from_ply(gs_path)
140140
x, y, z, qx, qy, qz, qw = layout_data.position[bg_node]
141141
qx, qy, qz, qw = quaternion_multiply([qx, qy, qz, qw], cfg.init_quat)

embodied_gen/scripts/textto3d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def text_to_3d(**kwargs) -> dict:
187187
logger.warning(
188188
f"Node {node}, {TXTGEN_CHECKER.__class__.__name__}: {qa_result}"
189189
)
190-
results["assets"][node] = f"{node_save_dir}/result"
190+
results["assets"][node] = f"asset3d/{save_node}/result"
191191
results["quality"][node] = qa_result
192192

193193
if qa_flag is None or qa_flag is True:

embodied_gen/utils/geometry.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# implied. See the License for the specific language governing
1515
# permissions and limitations under the License.
1616

17+
import json
1718
import os
1819
import random
1920
from collections import defaultdict, deque
@@ -32,7 +33,6 @@
3233
from embodied_gen.utils.log import logger
3334

3435
__all__ = [
35-
"bfs_placement",
3636
"with_seed",
3737
"matrix_to_pose",
3838
"pose_to_matrix",
@@ -222,7 +222,7 @@ def check_reachable(
222222

223223
@with_seed("seed")
224224
def bfs_placement(
225-
layout_info: LayoutInfo,
225+
layout_file: str,
226226
floor_margin: float = 0,
227227
beside_margin: float = 0.1,
228228
max_attempts: int = 3000,
@@ -232,6 +232,8 @@ def bfs_placement(
232232
robot_dim: float = 0.12,
233233
seed: int = None,
234234
) -> LayoutInfo:
235+
layout_info = LayoutInfo.from_dict(json.load(open(layout_file, "r")))
236+
asset_dir = os.path.dirname(layout_file)
235237
object_mapping = layout_info.objs_mapping
236238
position = {} # node: [x, y, z, qx, qy, qz, qw]
237239
parent_bbox_xy = {}
@@ -254,6 +256,7 @@ def bfs_placement(
254256
mesh_path = (
255257
f"{layout_info.assets[node]}/mesh/{node.replace(' ', '_')}.obj"
256258
)
259+
mesh_path = os.path.join(asset_dir, mesh_path)
257260
mesh_info[node]["path"] = mesh_path
258261
mesh = trimesh.load(mesh_path)
259262
vertices = mesh.vertices

embodied_gen/utils/monkey_patches.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def get_rgba_tensor(camera, return_alpha):
175175
seg_labels = camera.get_obs(
176176
rgb=False, depth=False, segmentation=True, position=False
177177
)["segmentation"]
178-
masks = np.where((seg_labels.cpu() > 0), 255, 0).astype(
178+
masks = np.where((seg_labels.cpu() > 1), 255, 0).astype(
179179
np.uint8
180180
)
181181
masks = torch.tensor(masks).to(color.device)

embodied_gen/utils/simulation.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def load_actor_from_urdf(
124124

125125
def load_assets_from_layout_file(
126126
scene: ManiSkillScene | sapien.Scene,
127-
layout: LayoutInfo | str,
127+
layout: str,
128128
z_offset: float = 0.0,
129129
init_quat: list[float] = [0, 0, 0, 1],
130130
env_idx: int = None,
@@ -133,19 +133,18 @@ def load_assets_from_layout_file(
133133
134134
Args:
135135
scene (sapien.Scene | ManiSkillScene): The SAPIEN or ManiSkill scene to load assets into.
136-
layout (LayoutInfo): The layout information data.
136+
layout (str): The layout file path.
137137
z_offset (float): Offset to apply to the Z-coordinate of non-context objects.
138138
init_quat (List[float]): Initial quaternion (x, y, z, w) for orientation adjustment.
139139
env_idx (int): Environment index for multi-environment setup.
140140
"""
141-
if isinstance(layout, str) and layout.endswith(".json"):
142-
layout = LayoutInfo.from_dict(json.load(open(layout, "r")))
143-
141+
asset_root = os.path.dirname(layout)
142+
layout = LayoutInfo.from_dict(json.load(open(layout, "r")))
144143
actors = dict()
145144
for node in layout.assets:
146145
file_dir = layout.assets[node]
147146
file_name = f"{node.replace(' ', '_')}.urdf"
148-
urdf_file = os.path.join(file_dir, file_name)
147+
urdf_file = os.path.join(asset_root, file_dir, file_name)
149148

150149
if layout.objs_mapping[node] == Scene3DItemEnum.BACKGROUND.value:
151150
continue

0 commit comments

Comments
 (0)