Skip to content

Commit c5e570a

Browse files
authored
Add camera extrinsics randomization functor (#32)
1 parent d07dbda commit c5e570a

File tree

10 files changed

+176
-57
lines changed

10 files changed

+176
-57
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
21
# EmbodiChain
32

43
![teaser](assets/imgs/teaser.jpg)
54

6-
[![Version](https://img.shields.io/badge/version-0.0.1-blue.svg)](https://github.com/DexForce/EmbodiChain/releases)
75
[![License](https://img.shields.io/github/license/DexForce/EmbodiChain)](LICENSE)
86
[![GitHub Pages](https://img.shields.io/badge/GitHub%20Pages-docs-blue?logo=github&logoColor=white)](https://dexforce.github.io/EmbodiChain/introduction.html)
7+
[![Python](https://img.shields.io/badge/python-3.10-blue.svg)](https://docs.python.org/3/whatsnew/3.10.html)
8+
[![Version](https://img.shields.io/badge/version-0.0.1-blue.svg)](https://github.com/DexForce/EmbodiChain/releases)
99

1010
---
1111

configs/gym/cobotmagic.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
},
6060
"sensor": [
6161
{
62+
"uid": "camera_1",
6263
"sensor_type": "Camera",
6364
"width": 640,
6465
"height": 480,

embodichain/lab/gym/envs/managers/events.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ def __init__(self, cfg: FunctorCfg, env: EmbodiedEnv):
111111

112112
# remove regular expression from patterns
113113
patterns = remove_regex_chars(patterns)
114-
full_path = get_data_path(f"{folder_path}/")
114+
self._full_path = get_data_path(f"{folder_path}/")
115115
self._asset_group_path = get_all_files_in_directory(
116-
full_path, patterns=patterns
116+
self._full_path, patterns=patterns
117117
)
118118
else:
119-
full_path = get_data_path(folder_path)
120-
self._asset_group_path = get_all_files_in_directory(full_path)
119+
self._full_path = get_data_path(folder_path)
120+
self._asset_group_path = get_all_files_in_directory(self._full_path)
121121

122122
def __call__(
123123
self,

embodichain/lab/gym/envs/managers/observations.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ def get_rigid_object_pose(
4040
) -> torch.Tensor:
4141
"""Get the world poses of the rigid objects in the environment.
4242
43+
If the rigid object with the specified UID does not exist in the environment,
44+
a zero tensor will be returned.
45+
4346
Args:
4447
env: The environment instance.
4548
obs: The observation dictionary.
@@ -49,6 +52,9 @@ def get_rigid_object_pose(
4952
A tensor of shape (num_envs, 4, 4) representing the world poses of the rigid objects.
5053
"""
5154

55+
if entity_cfg.uid not in env.sim.get_rigid_object_uid_list():
56+
return torch.zeros((env.num_envs, 4, 4), dtype=torch.float32)
57+
5258
obj = env.sim.get_rigid_object(entity_cfg.uid)
5359

5460
return obj.get_local_pose(to_matrix=True)
@@ -130,7 +136,10 @@ def compute_semantic_mask(
130136

131137
robot_mask = (mask_exp == robot_uids_exp).any(-1).squeeze_(-1)
132138

133-
foreground_assets = [env.sim.get_asset(uid) for uid in foreground_uids]
139+
asset_uids = env.sim.asset_uids
140+
foreground_assets = [
141+
env.sim.get_asset(uid) for uid in foreground_uids if uid in asset_uids
142+
]
134143

135144
# cat assets uid (num_envs, n) into dim 1
136145
foreground_uids = torch.cat(

embodichain/lab/gym/envs/managers/randomization/rendering.py

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@
3131
VisualMaterialCfg,
3232
)
3333
from embodichain.utils.string import resolve_matching_names
34-
from embodichain.utils.math import sample_uniform
34+
from embodichain.utils.math import (
35+
sample_uniform,
36+
quat_from_euler_xyz,
37+
euler_xyz_from_quat,
38+
)
3539
from embodichain.utils import logger
3640
from embodichain.data import get_data_path
3741

@@ -40,12 +44,135 @@
4044

4145

4246
__all__ = [
47+
"randomize_camera_extrinsics",
4348
"randomize_light",
4449
"randomize_camera_intrinsics",
4550
"randomize_visual_material",
4651
]
4752

4853

54+
def randomize_camera_extrinsics(
55+
env: EmbodiedEnv,
56+
env_ids: Union[torch.Tensor, None],
57+
entity_cfg: SceneEntityCfg,
58+
pos_range: Optional[tuple[list[float], list[float]]] = None,
59+
euler_range: Optional[tuple[list[float], list[float]]] = None,
60+
eye_range: Optional[tuple[list[float], list[float]]] = None,
61+
target_range: Optional[tuple[list[float], list[float]]] = None,
62+
up_range: Optional[tuple[list[float], list[float]]] = None,
63+
) -> None:
64+
"""
65+
Randomize camera extrinsic properties (position and orientation).
66+
67+
Behavior:
68+
- If extrinsics config has a parent field (attach mode), pos_range/euler_range are used to perturb the initial pose (pos, quat),
69+
and set_local_pose is called to attach the camera to the parent node. In this case, pose is related to parent.
70+
- If extrinsics config uses eye/target/up (no parent), eye_range/target_range/up_range are used to perturb the initial eye, target, up vectors,
71+
and look_at is called to set the camera orientation.
72+
73+
Args:
74+
env: The environment instance.
75+
env_ids: The environment IDs to apply the randomization.
76+
entity_cfg (SceneEntityCfg): The configuration of the scene entity to randomize.
77+
pos_range: Position perturbation range (attach mode).
78+
euler_range: Euler angle perturbation range (attach mode).
79+
eye_range: Eye position perturbation range (look_at mode).
80+
target_range: Target position perturbation range (look_at mode).
81+
up_range: Up vector perturbation range (look_at mode).
82+
"""
83+
camera: Union[Camera, StereoCamera] = env.sim.get_sensor(entity_cfg.uid)
84+
num_instance = len(env_ids)
85+
86+
extrinsics = camera.cfg.extrinsics
87+
88+
if extrinsics.parent is not None:
89+
# If extrinsics has a parent field, use pos/euler perturbation and attach camera to parent node
90+
init_pos = getattr(extrinsics, "pos", [0.0, 0.0, 0.0])
91+
init_quat = getattr(extrinsics, "quat", [0.0, 0.0, 0.0, 1.0])
92+
new_pose = torch.tensor(
93+
[init_pos + init_quat], dtype=torch.float32, device=env.device
94+
).repeat(num_instance, 1)
95+
if pos_range:
96+
random_value = sample_uniform(
97+
lower=torch.tensor(pos_range[0]),
98+
upper=torch.tensor(pos_range[1]),
99+
size=(num_instance, 3),
100+
)
101+
new_pose[:, :3] += random_value
102+
if euler_range:
103+
# 1. quat -> euler
104+
init_quat_np = (
105+
torch.tensor(init_quat, dtype=torch.float32, device=env.device)
106+
.unsqueeze_(0)
107+
.repeat(num_instance, 1)
108+
)
109+
init_euler = euler_xyz_from_quat(init_quat_np)
110+
# 2. Sample perturbation for euler angles
111+
random_value = sample_uniform(
112+
lower=torch.tensor(euler_range[0]),
113+
upper=torch.tensor(euler_range[1]),
114+
size=(num_instance, 3),
115+
)
116+
# 3. Add perturbation to each environment and convert back to quaternion
117+
new_quat = quat_from_euler_xyz(init_euler + random_value)
118+
new_pose[:, 3:7] = new_quat
119+
120+
camera.set_local_pose(new_pose, env_ids=env_ids)
121+
122+
elif extrinsics.eye is not None:
123+
# If extrinsics uses eye/target/up, use perturbation for look_at mode
124+
init_eye = (
125+
torch.tensor(extrinsics.eye, dtype=torch.float32, device=env.device)
126+
.unsqueeze(0)
127+
.repeat(num_instance, 1)
128+
)
129+
init_target = (
130+
torch.tensor(extrinsics.target, dtype=torch.float32, device=env.device)
131+
.unsqueeze(0)
132+
.repeat(num_instance, 1)
133+
)
134+
init_up = (
135+
torch.tensor(extrinsics.up, dtype=torch.float32, device=env.device)
136+
.unsqueeze(0)
137+
.repeat(num_instance, 1)
138+
)
139+
140+
if eye_range:
141+
eye_delta = sample_uniform(
142+
lower=torch.tensor(eye_range[0]),
143+
upper=torch.tensor(eye_range[1]),
144+
size=(num_instance, 3),
145+
)
146+
new_eye = init_eye + eye_delta
147+
else:
148+
new_eye = init_eye
149+
150+
if target_range:
151+
target_delta = sample_uniform(
152+
lower=torch.tensor(target_range[0]),
153+
upper=torch.tensor(target_range[1]),
154+
size=(num_instance, 3),
155+
)
156+
new_target = init_target + target_delta
157+
else:
158+
new_target = init_target
159+
160+
if up_range:
161+
up_delta = sample_uniform(
162+
lower=torch.tensor(up_range[0]),
163+
upper=torch.tensor(up_range[1]),
164+
size=(num_instance, 3),
165+
)
166+
new_up = init_up + up_delta
167+
else:
168+
new_up = init_up
169+
170+
camera.look_at(new_eye, new_target, new_up, env_ids=env_ids)
171+
172+
else:
173+
logger.log_error("Unsupported extrinsics format for camera randomization.")
174+
175+
49176
def randomize_light(
50177
env: EmbodiedEnv,
51178
env_ids: Union[torch.Tensor, None],

embodichain/lab/gym/envs/managers/randomization/spatial.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ def randomize_rigid_object_pose(
121121
relative_rotation (bool): Whether to randomize the rotation relative to the object's initial rotation. Default is False.
122122
"""
123123

124+
if entity_cfg.uid not in env.sim.get_rigid_object_uid_list():
125+
return
126+
124127
rigid_object: RigidObject = env.sim.get_rigid_object(entity_cfg.uid)
125128
num_instance = len(env_ids)
126129

embodichain/lab/sim/objects/gizmo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def _update_robot_ik(self, target_transform: torch.Tensor):
331331
new_qpos = new_qpos.unsqueeze(0) # Make it (1, dof) for set_qpos
332332

333333
# Update robot joint positions
334-
self.target.set_qpos(qpos=new_qpos, joint_ids=current_joint_ids)
334+
self.target.set_qpos(qpos=new_qpos[0], joint_ids=current_joint_ids)
335335
return True
336336
else:
337337
logger.log_warning("IK solution not found")

embodichain/lab/sim/objects/rigid_object.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,23 @@ def get_visual_material_inst(
516516
ids = env_ids if env_ids is not None else range(self.num_instances)
517517
return [self._visual_material[i] for i in ids]
518518

519+
def share_visual_material_inst(self, mat_insts: List[VisualMaterialInst]) -> None:
520+
"""Share material instances for the rigid object.
521+
522+
Args:
523+
mat_insts (List[VisualMaterialInst]): List of material instances to share.
524+
"""
525+
if len(self._entities) != len(mat_insts):
526+
logger.log_error(
527+
f"Length of entities {len(self._entities)} does not match length of material instances {len(mat_insts)}."
528+
)
529+
530+
for i, entity in enumerate(self._entities):
531+
if mat_insts[i] is None:
532+
continue
533+
entity.set_material(mat_insts[i].mat)
534+
self._visual_material[i] = mat_insts[i]
535+
519536
def get_body_scale(self, env_ids: Optional[Sequence[int]] = None) -> torch.Tensor:
520537
"""
521538
Retrieve the body scale for specified environment instances.

embodichain/lab/sim/sim_manager.py

Lines changed: 10 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -143,21 +143,12 @@ class SimulationManager:
143143
144144
This class is used to manage the global simulation environment and simulated assets.
145145
- assets loading, creation, modification and deletion.
146-
- assets include robots, fixed actors, dynamic actors and background.
146+
- assets include rigid objects, soft objects, articulations, robots, sensors and lights.
147147
- manager the scenes and the simulation environment.
148148
- parallel scenes simulation on both CPU and GPU.
149-
- sensors arrangement
150-
- lighting and indirect lighting
151-
- physics simulation parameters control
152-
- ...
153-
154-
Note:
155-
1. The arena is used as a standalone space for robots to simulate in. When :meth:`build_multiple_arenas` is called,
156-
it will create multiple arenas in a grid pattern. Meanwhile, each simulation assets adding interface will
157-
take an additional parameter `arena_index` to specify which arena to place the asset. The name of the asset to
158-
be added will be appended with the arena index to avoid name conflict.
159-
2. In GUI mode, the physics will be set to a fps (or a wait time for manual mode) for better visualization.
160-
149+
- create and setup the rendering related settings, eg. environment map, lighting, materials, etc.
150+
- physics simulation management, eg. time step, manual update, etc.
151+
- interactive control via gizmo and window callbacks events.
161152
162153
Args:
163154
sim_config (SimulationManagerCfg, optional): simulation configuration. Defaults to SimulationManagerCfg().
@@ -199,11 +190,8 @@ def __init__(
199190
self._world.set_delta_time(sim_config.physics_dt)
200191
self._world.show_coordinate_axis(False)
201192

202-
if sys.platform == "linux":
203-
dexsim.set_physics_config(**sim_config.physics_config.to_dexsim_args())
204-
dexsim.set_physics_gpu_memory_config(
205-
**sim_config.gpu_memory_config.to_dict()
206-
)
193+
dexsim.set_physics_config(**sim_config.physics_config.to_dexsim_args())
194+
dexsim.set_physics_gpu_memory_config(**sim_config.gpu_memory_config.to_dict())
207195

208196
self._is_initialized_gpu_physics = False
209197
self._ps = self._world.get_physics_scene()
@@ -216,9 +204,10 @@ def __init__(
216204
self._default_resources = SimResources()
217205

218206
# set unique material path to accelerate material creation.
207+
# TODO: This will be removed.
219208
if self.sim_config.enable_rt is False:
220209
self._env.set_unique_mat_path(
221-
os.path.join(self._material_cache_dir, "dexsim_mat")
210+
os.path.join(self._material_cache_dir, "default_mat")
222211
)
223212

224213
# arena is used as a standalone space for robots to simulate in.
@@ -661,6 +650,8 @@ def get_asset(
661650
return self._rigid_objects[uid]
662651
if uid in self._rigid_object_groups:
663652
return self._rigid_object_groups[uid]
653+
if uid in self._soft_objects:
654+
return self._soft_objects[uid]
664655
if uid in self._articulations:
665656
return self._articulations[uid]
666657

@@ -1261,34 +1252,6 @@ def remove_asset(self, uid: str) -> bool:
12611252

12621253
return False
12631254

1264-
def get_asset(
1265-
self, uid: str
1266-
) -> Optional[Union[RigidObject, Articulation, Robot, Light, BaseSensor]]:
1267-
"""Get an asset by its UID.
1268-
1269-
The asset can be a rigid object, articulation or robot.
1270-
1271-
Args:
1272-
uid (str): The UID of the asset.
1273-
"""
1274-
if uid in self._rigid_objects:
1275-
return self._rigid_objects[uid]
1276-
1277-
if uid in self._articulations:
1278-
return self._articulations[uid]
1279-
1280-
if uid in self._robots:
1281-
return self._robots[uid]
1282-
1283-
if uid in self._lights:
1284-
return self._lights[uid]
1285-
1286-
if uid in self._sensors:
1287-
return self._sensors[uid]
1288-
1289-
logger.log_warning(f"Asset {uid} not found.")
1290-
return None
1291-
12921255
def draw_marker(
12931256
self,
12941257
cfg: MarkerCfg,

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ dependencies = [
4848
"cvxpy==1.4.0",
4949
"ortools",
5050
"prettytable",
51-
"hdfdict@git+http://69.235.177.182:8081/externalrepo/hdfdict",
5251
"black==24.3.0",
5352
"h5py",
5453
]

0 commit comments

Comments
 (0)