Skip to content

Commit 2f66486

Browse files
authored
Adding render modalities (#596)
* Adding render modalities * Adding section in documentation for isaac installation * Adding isaac basic usage Authored by Abhishek Joshi
1 parent 400ca11 commit 2f66486

File tree

4 files changed

+127
-16
lines changed

4 files changed

+127
-16
lines changed

docs/modules/renderers.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ MuJoCo exposes users to an OpenGL context supported by [mujoco](https://mujoco.r
2323

2424
Users are also able to render using photorealistic methods through Isaac Sim. Specifically, we users are able to choose between two rendering modes: ray tracing and path tracing. For more information about Isaac Sim rendering options, please visit [here](https://docs.omniverse.nvidia.com/materials-and-rendering/latest/rtx-renderer.html). Isaac renderers are only available to those who are running on a Linux or Windows machine.
2525

26+
To install Isaac on your local system, please follow the instructions listed [here](https://isaac-sim.github.io/IsaacLab/main/source/setup/installation/pip_installation.html). Make sure to follow instructions to install both Isaac Sim and Isaac Lab.
27+
2628
### Ray tracing
2729
![Ray tracing](../images/gr1_cereal_ray_tracing.png "Ray tracing")
2830

@@ -33,6 +35,34 @@ Ray tracing can be performed in real time. We are currently working on enhancing
3335

3436
Path tracing typically offers higher quality and is ideal for offline learning. If you have the time to collect data and plan to train algorithms using offline data, we recommend using path tracing for its photorealistic results.
3537

38+
### Basic usage
39+
40+
Once all dependecies for Isaac rendering have been installed, users can run the `robosuite/scripts/render_dataset_with_omniverse.py` to render previously collected demonstrations using either ray tracing or path tracining. Below we highlight the arguments that can be passed into the script.
41+
42+
- **dataset**: Path to hdf5 dataset with the demonstrations to render.
43+
- **ds_format**: Dataset format (options include `robosuite` and `robomimic` depending on if the dataset was collected using robosuite or robomimic, respectively).
44+
- **episode**: Episode/demonstration to render. If no episode is provided, all demonstrations will be rendered.
45+
- **output_directory**: Directory to store outputs from Isaac rendering and USD generation.
46+
- **cameras**: List of cameras to render images. Cameras must be defined in robosuite.
47+
- **width**: Width of the rendered output.
48+
- **height**: Height of the rendered output.
49+
- **renderer**: Renderer mode to use (options include `RayTracedLighting` or `PathTracing`).
50+
- **save_video**: Whether to save the outputs renderings as a video.
51+
- **online**: Enables online rendering and will not save the USD for future rendering offline.
52+
- **skip_frames**: Renders every nth frame.
53+
- **hide_sites**: Hides all sites in the scene.
54+
- **reload_model**: Reloads the model from the Mujoco XML file.
55+
- **keep_models**: List of names of models to keep from the original Mujoco XML file.
56+
- **rgb**: Render with the RGB modality. If no other modality is selected, we default to rendering with RGB.
57+
- **normals**: Render with normals.
58+
- **semantic_segmentation**: Render with semantic segmentation.
59+
60+
Here is an example command to render an video of a demonstration using ray tracing with the RGB and normal modality.
61+
62+
```bash
63+
$ python robosuite/scripts/render_dataset_with_omniverse.py --dataset /home/abhishek/Documents/research/rpl/robosuite/robosuite/models/assets/demonstrations_private/1734107564_9898326/demo.hdf5 --ds_format robosuite --episode 1 --camera agentview frontview --width 1920 --height 1080 --renderer RayTracedLighting --save_video --hide_sites --rgb --normals
64+
```
65+
3666
### Rendering Speed
3767

3868
Below, we present a table showing the estimated frames per second when using these renderers. Note that the exact speed of rendering might depend on your machine and scene size. Larger scenes may take longer to render. Additionally, changing renderer inputs such as samples per pixel (spp) or max bounces might affect rendering speeds. The values below are estimates using the `Lift` task with an NVIDIA GeForce RTX 4090. We use an spp of 64 when rendering with path tracing.

robosuite/scripts/render_dataset_with_omniverse.py

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,23 @@
7474
"--keep_models", type=str, nargs="+", default=[], help="(optional) keep the model from the Mujoco XML file"
7575
)
7676

77+
# adding rendering types
78+
parser.add_argument(
79+
"--rgb",
80+
action="store_true",
81+
default=False,
82+
)
83+
parser.add_argument(
84+
"--normals",
85+
action="store_true",
86+
default=False,
87+
)
88+
parser.add_argument(
89+
"--semantic_segmentation",
90+
action="store_true",
91+
default=False,
92+
)
93+
7794
# Add arguments for launch
7895
AppLauncher.add_app_launcher_args(parser)
7996
# Parse the arguments
@@ -95,11 +112,13 @@
95112
import cv2
96113
import h5py
97114
import lxml.etree as ET
115+
import numpy as np
98116
import omni
99117
import omni.isaac.core.utils.stage as stage_utils
100118
import omni.kit.app
101119
import omni.replicator.core as rep
102120
import omni.timeline
121+
from pxr import Semantics
103122
from termcolor import colored
104123
from tqdm import tqdm
105124

@@ -124,6 +143,8 @@
124143
scene_option = mujoco.MjvOption()
125144
scene_option.geomgroup = [0, 1, 0, 0, 0, 0]
126145

146+
render_modalities = {"rgb": args.rgb, "normals": args.normals, "semantic_segmentation": args.semantic_segmentation}
147+
127148

128149
def make_sites_invisible(mujoco_xml):
129150
"""
@@ -343,6 +364,24 @@ def link_env_with_ov(self):
343364
)
344365
exp.update_scene(data=data, scene_option=scene_option)
345366
exp.add_light(pos=[0, 0, 0], intensity=1500, light_type="dome", light_name="dome_1")
367+
368+
# adds semantic information to objects in the scene
369+
if args.semantic_segmentation:
370+
for geom in exp.scene.geoms:
371+
geom_id = geom.objid
372+
geom_name = exp._get_geom_name(geom)
373+
if geom_id in self.env.model._geom_ids_to_classes:
374+
semantic_value = self.env.model._geom_ids_to_classes[geom_id]
375+
if "site" in geom_name or "None" in geom_name:
376+
continue
377+
prim = exp.geom_refs[geom_name].usd_prim
378+
instance_name = f"class_{semantic_value}"
379+
sem = Semantics.SemanticsAPI.Apply(prim, instance_name)
380+
sem.CreateSemanticTypeAttr()
381+
sem.CreateSemanticDataAttr()
382+
sem.GetSemanticTypeAttr().Set("class")
383+
sem.GetSemanticDataAttr().Set(semantic_value)
384+
346385
return exp
347386

348387
def update_simulation(self, index):
@@ -369,6 +408,8 @@ def __init__(
369408
output_dir: str = None,
370409
image_output_format: str = "png",
371410
rgb: bool = False,
411+
normals: bool = False,
412+
semantic_segmentation: bool = False,
372413
frame_padding: int = 4,
373414
):
374415
self._output_dir = output_dir
@@ -385,9 +426,16 @@ def __init__(
385426
self.data_structure = "annotator"
386427
self.write_ready = False
387428

388-
# RGB
429+
self.rgb = rgb
430+
self.normals = normals
431+
self.semantic_segmentation = semantic_segmentation
432+
389433
if rgb:
390434
self.annotators.append(rep.AnnotatorRegistry.get_annotator("rgb"))
435+
if normals:
436+
self.annotators.append(rep.AnnotatorRegistry.get_annotator("normals"))
437+
if semantic_segmentation:
438+
self.annotators.append(rep.AnnotatorRegistry.get_annotator("semantic_segmentation", {"colorize": True}))
391439

392440
def write(self, data: dict):
393441
"""Write function called from the OgnWriter node on every frame to process annotator output.
@@ -399,7 +447,25 @@ def write(self, data: dict):
399447
for annotator_name, annotator_data in data["annotators"].items():
400448
for idx, (render_product_name, anno_rp_data) in enumerate(annotator_data.items()):
401449
if annotator_name == "rgb":
402-
filepath = os.path.join(args.cameras[idx], f"rgb_{self._frame_id}.{self._image_output_format}")
450+
filepath = os.path.join(
451+
args.cameras[idx], "rgb", f"rgb_{self._frame_id}.{self._image_output_format}"
452+
)
453+
self._backend.write_image(filepath, anno_rp_data["data"])
454+
elif annotator_name == "normals":
455+
normals = anno_rp_data["data"][..., :3]
456+
norm_lengths = np.linalg.norm(normals, axis=-1, keepdims=True)
457+
normals_normalized = normals / np.clip(norm_lengths, 1e-8, None)
458+
img = ((normals_normalized + 1) / 2 * 255).astype(np.uint8)
459+
filepath = os.path.join(
460+
args.cameras[idx], "normals", f"normals_{self._frame_id}.{self._image_output_format}"
461+
)
462+
self._backend.write_image(filepath, img)
463+
elif annotator_name == "semantic_segmentation":
464+
filepath = os.path.join(
465+
args.cameras[idx],
466+
"semantic_segmentation",
467+
f"semantic_segmentation_{self._frame_id}.{self._image_output_format}",
468+
)
403469
self._backend.write_image(filepath, anno_rp_data["data"])
404470

405471
self._frame_id += 1
@@ -481,7 +547,12 @@ def init_recorder(self):
481547

482548
# Create writer for capturing generated data
483549
self.writer = rep.WriterRegistry.get(self.writer_name)
484-
self.writer.initialize(output_dir=self.output_dir, rgb=True)
550+
self.writer.initialize(
551+
output_dir=self.output_dir,
552+
rgb=args.rgb,
553+
normals=args.normals,
554+
semantic_segmentation=args.semantic_segmentation,
555+
)
485556

486557
print("Writer Initiazed")
487558

@@ -589,22 +660,27 @@ def create_video_from_frames(self, frame_folder, output_path, fps=30):
589660
video.release()
590661
print(f"Video saved: {output_path}")
591662

663+
def create_video(self, videos_folder, camera, data_type):
664+
camera_folder_path = os.path.join(self.output_dir, camera, data_type) # temp, change to render type
665+
if not os.path.isdir(camera_folder_path):
666+
return
667+
668+
# Construct output filename and path
669+
output_filename = f"{camera}_{data_type}.mp4"
670+
output_path = os.path.join(videos_folder, output_filename)
671+
672+
# Create the video from the frames in the camera folder
673+
self.create_video_from_frames(camera_folder_path, output_path)
674+
592675
def process_folders(self):
593676
videos_folder = os.path.join(self.output_dir, "videos")
594677
os.makedirs(videos_folder, exist_ok=True)
595678

596679
# Iterate over each camera folder in the output directory
597680
for camera in args.cameras:
598-
camera_folder_path = os.path.join(self.output_dir, camera)
599-
if not os.path.isdir(camera_folder_path):
600-
continue
601-
602-
# Construct output filename and path
603-
output_filename = f"{camera}_rgb.mp4"
604-
output_path = os.path.join(videos_folder, output_filename)
605-
606-
# Create the video from the frames in the camera folder
607-
self.create_video_from_frames(camera_folder_path, output_path)
681+
for render_modality, selected in render_modalities.items():
682+
if selected:
683+
self.create_video(videos_folder=videos_folder, camera=camera, data_type=render_modality)
608684

609685

610686
def main():

robosuite/utils/usd/exporter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,8 @@ def _get_geom_name(self, geom) -> str:
527527
geom_name = mujoco.mj_id2name(self.model, geom.objtype, geom.objid)
528528
if not geom_name:
529529
geom_name = "None"
530+
geom_name = geom_name.replace("-", "m_")
531+
geom_name = geom_name.replace("+", "p_")
530532
geom_name += f"_id{geom.objid}"
531533

532534
# adding additional naming information to differentiate

robosuite/utils/usd/objects.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ def __init__(
5959
self.rgba = rgba
6060
self.texture_file = texture_file
6161

62-
self.xform_path = f"/World/Mesh_Xform_{obj_name}"
62+
self.obj_name = self.obj_name.replace("-", "m_")
63+
self.obj_name = self.obj_name.replace("+", "p_")
64+
65+
self.xform_path = f"/World/Mesh_Xform_{self.obj_name}"
6366
self.usd_xform = UsdGeom.Xform.Define(stage, self.xform_path)
6467

6568
# defining ops required by update function
@@ -199,7 +202,7 @@ def __init__(
199202

200203
self.dataid = dataid
201204

202-
mesh_path = f"{self.xform_path}/Mesh_{obj_name}"
205+
mesh_path = f"{self.xform_path}/Mesh_{self.obj_name}"
203206
self.usd_mesh = UsdGeom.Mesh.Define(stage, mesh_path)
204207
self.usd_prim = stage.GetPrimAtPath(mesh_path)
205208

@@ -288,7 +291,7 @@ def __init__(
288291
self.mesh_config = mesh_config
289292
self.prim_mesh = self.generate_primitive_mesh()
290293

291-
mesh_path = f"{self.xform_path}/Mesh_{obj_name}"
294+
mesh_path = f"{self.xform_path}/Mesh_{self.obj_name}"
292295
self.usd_mesh = UsdGeom.Mesh.Define(stage, mesh_path)
293296
self.usd_prim = stage.GetPrimAtPath(mesh_path)
294297

0 commit comments

Comments
 (0)