This guide accompanied with the run_usd_camera.py script in the IsaacLab/scripts/tutorials/04_sensors
directory.
# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).# All rights reserved.## SPDX-License-Identifier: BSD-3-Clause"""This script shows how to use the camera sensor from the Isaac Lab framework.The camera sensor is created and interfaced through the Omniverse Replicator API. However, instead of usingthe simulator or OpenGL convention for the camera, we use the robotics or ROS convention.~~~bash# Usage with GUI./isaaclab.sh -p scripts/tutorials/04_sensors/run_usd_camera.py --enable_cameras# Usage with headless./isaaclab.sh -p scripts/tutorials/04_sensors/run_usd_camera.py --headless --enable_cameras~~~""""""Launch Isaac Sim Simulator first."""import argparsefrom isaaclab.app import AppLauncher# add argparse argumentsparser = argparse.ArgumentParser(description="This script demonstrates how to use the camera sensor.")parser.add_argument("--draw",action="store_true",default=False,help="Draw the pointcloud from camera at index specified by `--camera_id`.",)parser.add_argument("--save",action="store_true",default=False,help="Save the data from camera at index specified by `--camera_id`.",)parser.add_argument("--camera_id",type=int,choices={0, 1},default=0,help=("The camera ID to use for displaying points or saving the camera data. Default is 0."" The viewport will always initialize with the perspective of camera 0."),)# append AppLauncher cli argsAppLauncher.add_app_launcher_args(parser)# parse the argumentsargs_cli = parser.parse_args()# launch omniverse appapp_launcher = AppLauncher(args_cli)simulation_app = app_launcher.app"""Rest everything follows."""import numpy as npimport osimport randomimport torchimport isaacsim.core.utils.prims as prim_utilsimport omni.replicator.core as repimport isaaclab.sim as sim_utilsfrom isaaclab.assets import RigidObject, RigidObjectCfgfrom isaaclab.markers import VisualizationMarkersfrom isaaclab.markers.config import RAY_CASTER_MARKER_CFGfrom isaaclab.sensors.camera import Camera, CameraCfgfrom isaaclab.sensors.camera.utils import create_pointcloud_from_depthfrom isaaclab.utils import convert_dict_to_backenddef define_sensor() -> Camera:"""Defines the camera sensor to add to the scene."""# Setup camera sensor# In contrast to the ray-cast camera, we spawn the prim at these locations.# This means the camera sensor will be attached to these prims.prim_utils.create_prim("/World/Origin_00", "Xform")prim_utils.create_prim("/World/Origin_01", "Xform")camera_cfg = CameraCfg(prim_path="/World/Origin_.*/CameraSensor",update_period=0,height=480,width=640,data_types=["rgb","distance_to_image_plane","normals","semantic_segmentation","instance_segmentation_fast","instance_id_segmentation_fast",],colorize_semantic_segmentation=True,colorize_instance_id_segmentation=True,colorize_instance_segmentation=True,spawn=sim_utils.PinholeCameraCfg(focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5)),)# Create cameracamera = Camera(cfg=camera_cfg)return cameradef design_scene() -> dict:"""Design the scene."""# Populate scene# -- Ground-planecfg = sim_utils.GroundPlaneCfg()cfg.func("/World/defaultGroundPlane", cfg)# -- Lightscfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))cfg.func("/World/Light", cfg)# Create a dictionary for the scene entitiesscene_entities = {}# Xform to hold objectsprim_utils.create_prim("/World/Objects", "Xform")# Random objectsfor i in range(8):# sample random positionposition = np.random.rand(3) - np.asarray([0.05, 0.05, -1.0])position *= np.asarray([1.5, 1.5, 0.5])# sample random colorcolor = (random.random(), random.random(), random.random())# choose random prim typeprim_type = random.choice(["Cube", "Cone", "Cylinder"])common_properties = {"rigid_props": sim_utils.RigidBodyPropertiesCfg(),"mass_props": sim_utils.MassPropertiesCfg(mass=5.0),"collision_props": sim_utils.CollisionPropertiesCfg(),"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=color, metallic=0.5),"semantic_tags": [("class", prim_type)],}if prim_type == "Cube":shape_cfg = sim_utils.CuboidCfg(size=(0.25, 0.25, 0.25), **common_properties)elif prim_type == "Cone":shape_cfg = sim_utils.ConeCfg(radius=0.1, height=0.25, **common_properties)elif prim_type == "Cylinder":shape_cfg = sim_utils.CylinderCfg(radius=0.25, height=0.25, **common_properties)# Rigid Objectobj_cfg = RigidObjectCfg(prim_path=f"/World/Objects/Obj_{i:02d}",spawn=shape_cfg,init_state=RigidObjectCfg.InitialStateCfg(pos=position),)scene_entities[f"rigid_object{i}"] = RigidObject(cfg=obj_cfg)# Sensorscamera = define_sensor()# return the scene informationscene_entities["camera"] = camerareturn scene_entitiesdef run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict):"""Run the simulator."""# extract entities for simplified notationcamera: Camera = scene_entities["camera"]# Create replicator writeroutput_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "camera")rep_writer = rep.BasicWriter(output_dir=output_dir,frame_padding=0,colorize_instance_id_segmentation=camera.cfg.colorize_instance_id_segmentation,colorize_instance_segmentation=camera.cfg.colorize_instance_segmentation,colorize_semantic_segmentation=camera.cfg.colorize_semantic_segmentation,)# Camera positions, targets, orientationscamera_positions = torch.tensor([[2.5, 2.5, 2.5], [-2.5, -2.5, 2.5]], device=sim.device)camera_targets = torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], device=sim.device)# These orientations are in ROS-convention, and will position the cameras to view the origincamera_orientations = torch.tensor( # noqa: F841[[-0.1759, 0.3399, 0.8205, -0.4247], [-0.4247, 0.8205, -0.3399, 0.1759]], device=sim.device)# Set pose: There are two ways to set the pose of the camera.# -- Option-1: Set pose using viewcamera.set_world_poses_from_view(camera_positions, camera_targets)# -- Option-2: Set pose using ROS# camera.set_world_poses(camera_positions, camera_orientations, convention="ros")# Index of the camera to use for visualization and savingcamera_index = args_cli.camera_id# Create the markers for the --draw option outside of is_running() loopif sim.has_gui() and args_cli.draw:cfg = RAY_CASTER_MARKER_CFG.replace(prim_path="/Visuals/CameraPointCloud")cfg.markers["hit"].radius = 0.002pc_markers = VisualizationMarkers(cfg)# Simulate physicswhile simulation_app.is_running():# Step simulationsim.step()# Update camera datacamera.update(dt=sim.get_physics_dt())# Print camera infoprint(camera)if "rgb" in camera.data.output.keys():print("Received shape of rgb image : ", camera.data.output["rgb"].shape)if "distance_to_image_plane" in camera.data.output.keys():print("Received shape of depth image : ", camera.data.output["distance_to_image_plane"].shape)if "normals" in camera.data.output.keys():print("Received shape of normals : ", camera.data.output["normals"].shape)if "semantic_segmentation" in camera.data.output.keys():print("Received shape of semantic segm. : ", camera.data.output["semantic_segmentation"].shape)if "instance_segmentation_fast" in camera.data.output.keys():print("Received shape of instance segm. : ", camera.data.output["instance_segmentation_fast"].shape)if "instance_id_segmentation_fast" in camera.data.output.keys():print("Received shape of instance id segm.: ", camera.data.output["instance_id_segmentation_fast"].shape)print("-------------------------------")# Extract camera dataif args_cli.save:# Save images from camera at camera_index# note: BasicWriter only supports saving data in numpy format, so we need to convert the data to numpy.single_cam_data = convert_dict_to_backend({k: v[camera_index] for k, v in camera.data.output.items()}, backend="numpy")# Extract the other informationsingle_cam_info = camera.data.info[camera_index]# Pack data back into replicator format to save them using its writerrep_output = {"annotators": {}}for key, data, info in zip(single_cam_data.keys(), single_cam_data.values(), single_cam_info.values()):if info is not None:rep_output["annotators"][key] = {"render_product": {"data": data, **info}}else:rep_output["annotators"][key] = {"render_product": {"data": data}}# Save images# Note: We need to provide On-time data for Replicator to save the images.rep_output["trigger_outputs"] = {"on_time": camera.frame[camera_index]}rep_writer.write(rep_output)# Draw pointcloud if there is a GUI and --draw has been passedif sim.has_gui() and args_cli.draw and "distance_to_image_plane" in camera.data.output.keys():# Derive pointcloud from camera at camera_indexpointcloud = create_pointcloud_from_depth(intrinsic_matrix=camera.data.intrinsic_matrices[camera_index],depth=camera.data.output["distance_to_image_plane"][camera_index],position=camera.data.pos_w[camera_index],orientation=camera.data.quat_w_ros[camera_index],device=sim.device,)# In the first few steps, things are still being instanced and Camera.data# can be empty. If we attempt to visualize an empty pointcloud it will crash# the sim, so we check that the pointcloud is not empty.if pointcloud.size()[0] > 0:pc_markers.visualize(translations=pointcloud)def main():"""Main function."""# Load simulation contextsim_cfg = sim_utils.SimulationCfg(device=args_cli.device)sim = sim_utils.SimulationContext(sim_cfg)# Set main camerasim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0])# Design scenescene_entities = design_scene()# Play simulatorsim.reset()# Now we are ready!print("[INFO]: Setup complete...")# Run simulatorrun_simulator(sim, scene_entities)if __name__ == "__main__":# run the main functionmain()# close sim appsimulation_app.close()
Saving using Replicator Basic Writer
To save camera outputs, we use the basic write class from Omniverse Replicator. This class allows us to save the images in a numpy format. For more information on the basic writer, please check the documentation.
rep_writer = rep.BasicWriter(output_dir=output_dir,frame_padding=0,colorize_instance_id_segmentation=camera.cfg.colorize_instance_id_segmentation,colorize_instance_segmentation=camera.cfg.colorize_instance_segmentation,colorize_semantic_segmentation=camera.cfg.colorize_semantic_segmentation,)
While stepping the simulator, the images can be saved to the defined folder. Since the BasicWriter only supports saving data using NumPy format, we first need to convert the PyTorch sensors to NumPy arrays before packing them in a dictionary.
# Save images from camera at camera_index# note: BasicWriter only supports saving data in numpy format, so we need to convert the data to numpy.single_cam_data = convert_dict_to_backend({k: v[camera_index] for k, v in camera.data.output.items()}, backend="numpy")# Extract the other informationsingle_cam_info = camera.data.info[camera_index]
After this step, we can save the images using the BasicWriter.
# Pack data back into replicator format to save them using its writerrep_output = {"annotators": {}}for key, data, info in zip(single_cam_data.keys(), single_cam_data.values(), single_cam_info.values()):if info is not None:rep_output["annotators"][key] = {"render_product": {"data": data, **info}}else:rep_output["annotators"][key] = {"render_product": {"data": data}}# Save images# Note: We need to provide On-time data for Replicator to save the images.rep_output["trigger_outputs"] = {"on_time": camera.frame[camera_index]}rep_writer.write(rep_output)
Projection into 3D Space
We include utilities to project the depth image into 3D Space. The re-projection operations are done using PyTorch operations which allows faster computation.
from isaaclab.utils.math import transform_points, unproject_depth# Pointcloud in world framepoints_3d_cam = unproject_depth(camera.data.output["distance_to_image_plane"], camera.data.intrinsic_matrices)points_3d_world = transform_points(points_3d_cam, camera.data.pos_w, camera.data.quat_w_ros)
Alternately, we can use the isaaclab.sensors.camera.utils.create_pointcloud_from_depth function
to create a point cloud from the depth image and transform it to the world frame.
# Derive pointcloud from camera at camera_indexpointcloud = create_pointcloud_from_depth(intrinsic_matrix=camera.data.intrinsic_matrices[camera_index],depth=camera.data.output["distance_to_image_plane"][camera_index],position=camera.data.pos_w[camera_index],orientation=camera.data.quat_w_ros[camera_index],device=sim.device,)
The resulting point cloud can be visualized using the isaacsim.util.debug_draw extension from Isaac Sim.
This makes it easy to visualize the point cloud in the 3D space.
# In the first few steps, things are still being instanced and Camera.data# can be empty. If we attempt to visualize an empty pointcloud it will crash# the sim, so we check that the pointcloud is not empty.if pointcloud.size()[0] > 0:pc_markers.visualize(translations=pointcloud)
Executing the script
To run the accompanying script, execute the following command:
# Usage with saving and drawing./isaaclab.sh -p scripts/tutorials/04_sensors/run_usd_camera.py --save --draw --enable_cameras# Usage with saving only in headless mode./isaaclab.sh -p scripts/tutorials/04_sensors/run_usd_camera.py --save --headless --enable_cameras
The simulation should start, and you can observe different objects falling down. An output folder will be created
in the IsaacLab/scripts/tutorials/04_sensors directory, where the images will be saved. Additionally,
you should see the point cloud in the 3D space drawn on the viewport.
To stop the simulation, close the window, or use Ctrl+C in the terminal.