Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/source/overview/gym/event_functors.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ This page lists all available event functors that can be used with the Event Man
"params": {"color_range": [[0.6, 0.6, 0.6], [1, 1, 1]],
"intensity_range": [0.5, 2.0]}}
```
* - {class}`~randomization.visual.randomize_indirect_lighting`
- Randomize indirect (IBL) lighting or emissive light. Implemented as a Functor class. Operates in one of two **mutually exclusive** modes — configuring both raises a ``ValueError``:

**HDR mode** — provide ``path`` pointing to a folder of ``.hdr`` files. A random file is selected on each call and applied as the environment map. The ``path`` is resolved via ``get_data_path``, supporting absolute paths, data-root-relative paths, and dataset-class paths.

```json
{"func": "randomize_indirect_lighting",
"mode": "interval", "interval_step": 10,
"params": {"path": "EnvMapHDR/EnvMapHDR"}}
```

**Emissive mode** — provide ``emissive_color_range`` (pair of RGB lists) and/or ``emissive_intensity_range`` (pair of floats). Color and intensity are sampled uniformly on each call and applied via ``set_emission_light``.

```json
{"func": "randomize_indirect_lighting",
"mode": "interval", "interval_step": 10,
"params": {"emissive_color_range": [[0.8, 0.8, 0.8], [1.0, 1.0, 1.0]],
"emissive_intensity_range": [80.0, 150.0]}}
```

Applies the same lighting to all environments.
* - {func}`~randomization.visual.randomize_camera_extrinsics`
- Randomize camera poses for viewpoint diversity. Supports both attach mode (pos/euler perturbation) and look_at mode (eye/target/up perturbation).

Expand Down
40 changes: 40 additions & 0 deletions embodichain/data/assets/materials.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,46 @@ def get_material_list(self) -> List[str]:
]


class EnvMapHDR(EmbodiChainDataset):
def __init__(self, data_root: str = None):
data_descriptor = o3d.data.DataDescriptor(
os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, material_assets, "EnvMapHDR.zip"),
"ea7abc8e955fe64069073d63834da60e",
)
prefix = type(self).__name__
path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root

super().__init__(prefix, data_descriptor, path)

def get_env_map_path(self, name: str) -> str:
"""Get the path of an HDR environment map.

Args:
name (str): The name of the HDR environment map.

Returns:
str: The path to the HDR environment map file.
"""
env_map_names = self.get_env_map_list()
if name not in env_map_names:
logger.log_error(
f"Invalid env map name: {name}. Available names are: {env_map_names}"
)
return str(Path(self.extract_dir) / "EnvMapHDR" / name)

def get_env_map_list(self) -> List[str]:
"""Get the names of all HDR environment maps.

Returns:
List[str]: The names of all HDR environment map files.
"""
return [
f.name
for f in Path(self.extract_dir).glob("EnvMapHDR/*.hdr")
if f.is_file()
]


class CocoBackground(EmbodiChainDataset):
def __init__(self, data_root: str = None):
data_descriptor = o3d.data.DataDescriptor(
Expand Down
4 changes: 4 additions & 0 deletions embodichain/lab/gym/envs/embodied_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
init_rollout_buffer_from_gym_space,
)
from embodichain.utils import configclass, logger
from embodichain.data import get_data_path

__all__ = ["EmbodiedEnvCfg", "EmbodiedEnv"]

Expand Down Expand Up @@ -932,6 +933,9 @@ def _setup_lights(self) -> None:
if self.cfg.light.indirect is not None:
if "emission_light" in self.cfg.light.indirect:
self.sim.set_emission_light(**self.cfg.light.indirect["emission_light"])
if "env_map" in self.cfg.light.indirect:
path = get_data_path(self.cfg.light.indirect["env_map"])
self.sim.set_indirect_lighting(path)

def _setup_background(self) -> None:
"""Setup the static rigid objects in the environment."""
Expand Down
133 changes: 133 additions & 0 deletions embodichain/lab/gym/envs/managers/randomization/visual.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import random
import copy
import numpy as np
from pathlib import Path

from typing import TYPE_CHECKING, Literal, Union, Dict

Expand Down Expand Up @@ -59,6 +60,7 @@
"set_rigid_object_visual_material",
"set_rigid_object_group_visual_material",
"randomize_visual_material",
"randomize_indirect_lighting",
]


Expand Down Expand Up @@ -742,3 +744,134 @@ def __call__(

env = self._env.sim.get_env()
env.clean_materials()


class randomize_indirect_lighting(Functor):
"""Randomize the environment's indirect (IBL) lighting or emissive light.

This functor operates in one of two mutually exclusive modes:

* **HDR mode** — ``path`` is provided. A random ``.hdr`` file is chosen from
the folder on every call and applied via :meth:`set_indirect_lighting`.
* **Emissive mode** — ``emissive_color_range`` and/or
``emissive_intensity_range`` are provided. The emissive light color and
intensity are sampled uniformly on every call and applied via
:meth:`set_emission_light`.

Providing both ``path`` and emissive parameters simultaneously is an error.

.. attention::
This functor applies the same lighting to all environments.

.. tip::
The ``path`` parameter is resolved via :func:`get_data_path`, so it
supports absolute paths, data-root-relative paths, and dataset-class
paths (e.g. ``"EnvMapHDR"``).

``emissive_color_range`` is a pair of ``[r, g, b]`` lists representing
the lower and upper bounds for sampling the emissive color, e.g.
``[[0.8, 0.8, 0.8], [1.0, 1.0, 1.0]]``.

``emissive_intensity_range`` is a ``[min, max]`` pair for the emissive
intensity scalar, e.g. ``[80.0, 150.0]``.
"""

def __init__(self, cfg: FunctorCfg, env: EmbodiedEnv):
"""Initialize the functor.

Args:
cfg: The configuration of the functor.

* **HDR mode**: set ``params["path"]`` to a folder of ``.hdr`` files.
* **Emissive mode**: set ``params["emissive_color_range"]``
(pair of RGB lists) and/or ``params["emissive_intensity_range"]``
(pair of floats).

env: The environment instance.

Raises:
ValueError: If both HDR and emissive params are provided, or if
neither is provided.
"""
super().__init__(cfg, env)

has_hdr = cfg.params.get("path", None) is not None
has_emissive = (
cfg.params.get("emissive_color_range", None) is not None
or cfg.params.get("emissive_intensity_range", None) is not None
)

if has_hdr and has_emissive:
raise ValueError(
"randomize_indirect_lighting: 'path' (HDR mode) and emissive "
"parameters ('emissive_color_range', 'emissive_intensity_range') "
"are mutually exclusive. Configure only one mode."
)
if not has_hdr and not has_emissive:
raise ValueError(
"randomize_indirect_lighting: provide either 'path' for HDR "
"mode, or 'emissive_color_range'/'emissive_intensity_range' for "
"emissive mode."
)

# HDR mode state
self._hdr_files: list[Path] = []
if has_hdr:
path = get_data_path(cfg.params["path"])
self._hdr_files = sorted(Path(path).glob("*.hdr"))
if not self._hdr_files:
logger.log_warning(
f"No .hdr files found in '{path}'. "
f"Indirect lighting randomization will be a no-op."
)

# Emissive mode state
self._emissive_color_range: tuple[list[float], list[float]] | None = (
cfg.params.get("emissive_color_range", None)
)
self._emissive_intensity_range: tuple[float, float] | None = cfg.params.get(
"emissive_intensity_range", None
)

def __call__(
self,
env: EmbodiedEnv,
env_ids: Union[torch.Tensor, None],
path: str | None = None,
) -> None:
"""Randomize lighting according to the configured mode.

In HDR mode a random ``.hdr`` file is selected and applied. In emissive
mode the emissive color and/or intensity are sampled and applied.

Args:
env: The environment instance.
env_ids: Target environment IDs (unused — lighting is global).
path: Ignored. Kept for interface compatibility with the event system.
"""
if self._hdr_files:
# HDR mode
selected = random.choice(self._hdr_files)
env.sim.set_indirect_lighting(str(selected))
return

# Emissive mode
emissive_color: list[float] | None = None
if self._emissive_color_range is not None:
color_tensor = sample_uniform(
lower=torch.tensor(self._emissive_color_range[0]),
upper=torch.tensor(self._emissive_color_range[1]),
size=(1, 3),
)
emissive_color = color_tensor.squeeze(0).tolist()

emissive_intensity: float | None = None
if self._emissive_intensity_range is not None:
emissive_intensity = float(
np.random.uniform(
self._emissive_intensity_range[0],
self._emissive_intensity_range[1],
)
)

env.sim.set_emission_light(color=emissive_color, intensity=emissive_intensity)
23 changes: 23 additions & 0 deletions embodichain/lab/scripts/preview_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@
python -m embodichain.lab.scripts.preview_asset \\
--asset_path /path/to/asset.usda \\
--headless

# Preview with a built-in environment map
python -m embodichain.lab.scripts.preview_asset \\
--asset_path /path/to/sugar_box.usda \\
--env_map "Studio"

# Preview with a custom HDR environment map
python -m embodichain.lab.scripts.preview_asset \\
--asset_path /path/to/sugar_box.usda \\
--env_map /path/to/environment.hdr
"""

from __future__ import annotations
Expand Down Expand Up @@ -208,6 +218,10 @@ def main(args: argparse.Namespace) -> None:
sim = SimulationManager(sim_cfg)

try:
if args.env_map:
log_info(f"Setting environment map: {args.env_map} ...", color="green")
sim.set_indirect_lighting(args.env_map)

assets = load_assets(sim, args)
log_info(f"Loaded {len(assets)} asset(s) successfully.", color="green")

Expand Down Expand Up @@ -318,6 +332,15 @@ def cli():
default="hybrid",
help="Renderer backend (default: hybrid).",
)
parser.add_argument(
"--env_map",
type=str,
default=None,
help=(
"Environment map for indirect lighting. Accepts a built-in IBL resource "
"name (e.g. 'Studio') or an absolute file path (.hdr/.png/.exr)."
),
)
parser.add_argument(
"--preview",
action="store_true",
Expand Down
11 changes: 11 additions & 0 deletions embodichain/lab/sim/sim_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,17 @@ def set_default_background(self) -> None:
self._default_plane.set_material(mat.get_instance("plane_mat").mat)
self._visual_materials[mat_name] = mat

def set_ground_plane_visibility(self, visible: bool) -> None:
"""_summary_

Args:
visible (bool): _description_
"""
if visible:
self._default_plane.set_visible(True)
else:
self._default_plane.set_visible(False)

def set_texture_cache(
self, key: str, texture: Union[torch.Tensor, List[torch.Tensor]]
) -> None:
Expand Down
2 changes: 1 addition & 1 deletion examples/sim/scene/scene_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def main():
num_lights = 8
radius = 5
height = 8
intensity = 200
intensity = 50
lights = []

for i in range(num_lights):
Expand Down
Loading
Loading