Useful Snippets#
Various examples of Isaac Sim Replicator snippets that can be run as Standalone Applications or from the UI using the Script Editor.
Annotator and Custom Writer Data from Multiple Cameras#
Example on how to access data from multiple cameras in a scene using annotators or custom writers. The standalone example can also be found at: standalone_examples/api/omni.replicator.isaac/multi-camera.py
.
Annotator and Custom Writer Data from Multiple Cameras
1from isaacsim import SimulationApp
2
3simulation_app = SimulationApp(launch_config={"headless": False})
4
5import os
6import omni.usd
7import omni.kit
8import omni.replicator.core as rep
9from omni.replicator.core import AnnotatorRegistry, Writer
10from PIL import Image
11from pxr import UsdGeom, Sdf
12
13NUM_FRAMES = 5
14
15# Save rgb image to file
16def save_rgb(rgb_data, file_name):
17 rgb_img = Image.fromarray(rgb_data, "RGBA")
18 rgb_img.save(file_name + ".png")
19
20
21# Randomize cube color every frame using a replicator randomizer
22def cube_color_randomizer():
23 cube_prims = rep.get.prims(path_pattern="Cube")
24 with cube_prims:
25 rep.randomizer.color(colors=rep.distribution.uniform((0, 0, 0), (1, 1, 1)))
26 return cube_prims.node
27
28
29# Access data through a custom replicator writer
30class MyWriter(Writer):
31 def __init__(self, rgb: bool = True):
32 self._frame_id = 0
33 if rgb:
34 self.annotators.append(AnnotatorRegistry.get_annotator("rgb"))
35 # Create writer output directory
36 self.file_path = os.path.join(os.getcwd(), "_out_mc_writer", "")
37 print(f"Writing writer data to {self.file_path}")
38 dir = os.path.dirname(self.file_path)
39 os.makedirs(dir, exist_ok=True)
40
41 def write(self, data):
42 for annotator in data.keys():
43 annotator_split = annotator.split("-")
44 if len(annotator_split) > 1:
45 render_product_name = annotator_split[-1]
46 if annotator.startswith("rgb"):
47 save_rgb(data[annotator], f"{self.file_path}/{render_product_name}_frame_{self._frame_id}")
48 self._frame_id += 1
49
50
51rep.WriterRegistry.register(MyWriter)
52
53# Create a new stage with a dome light
54omni.usd.get_context().new_stage()
55stage = omni.usd.get_context().get_stage()
56dome_light = stage.DefinePrim("/World/DomeLight", "DomeLight")
57dome_light.CreateAttribute("inputs:intensity", Sdf.ValueTypeNames.Float).Set(900.0)
58
59# Create cube
60cube_prim = stage.DefinePrim("/World/Cube", "Cube")
61UsdGeom.Xformable(cube_prim).AddTranslateOp().Set((0.0, 5.0, 1.0))
62
63# Register cube color randomizer to trigger on every frame
64rep.randomizer.register(cube_color_randomizer)
65with rep.trigger.on_frame():
66 rep.randomizer.cube_color_randomizer()
67
68# Create cameras
69camera_prim1 = stage.DefinePrim("/World/Camera1", "Camera")
70UsdGeom.Xformable(camera_prim1).AddTranslateOp().Set((0.0, 10.0, 20.0))
71UsdGeom.Xformable(camera_prim1).AddRotateXYZOp().Set((-15.0, 0.0, 0.0))
72
73camera_prim2 = stage.DefinePrim("/World/Camera2", "Camera")
74UsdGeom.Xformable(camera_prim2).AddTranslateOp().Set((-10.0, 15.0, 15.0))
75UsdGeom.Xformable(camera_prim2).AddRotateXYZOp().Set((-45.0, 0.0, 45.0))
76
77# Create render products
78rp1 = rep.create.render_product(str(camera_prim1.GetPrimPath()), resolution=(320, 320))
79rp2 = rep.create.render_product(str(camera_prim2.GetPrimPath()), resolution=(640, 640))
80rp3 = rep.create.render_product("/OmniverseKit_Persp", (1024, 1024))
81
82# Acess the data through a custom writer
83writer = rep.WriterRegistry.get("MyWriter")
84writer.initialize(rgb=True)
85writer.attach([rp1, rp2, rp3])
86
87# Acess the data through annotators
88rgb_annotators = []
89for rp in [rp1, rp2, rp3]:
90 rgb = rep.AnnotatorRegistry.get_annotator("rgb")
91 rgb.attach(rp)
92 rgb_annotators.append(rgb)
93
94# Create annotator output directory
95file_path = os.path.join(os.getcwd(), "_out_mc_annot", "")
96print(f"Writing annotator data to {file_path}")
97dir = os.path.dirname(file_path)
98os.makedirs(dir, exist_ok=True)
99
100# Data will be captured manually using step
101rep.orchestrator.set_capture_on_play(False)
102
103for i in range(NUM_FRAMES):
104 # The step function provides new data to the annotators, triggers the randomizers and the writer
105 rep.orchestrator.step(rt_subframes=4)
106 for j, rgb_annot in enumerate(rgb_annotators):
107 save_rgb(rgb_annot.get_data(), f"{dir}/rp{j}_step_{i}")
108
109simulation_app.close()
Annotator and Custom Writer Data from Multiple Cameras
1import asyncio
2import os
3import omni.usd
4import omni.kit
5import omni.replicator.core as rep
6from omni.replicator.core import AnnotatorRegistry, Writer
7from PIL import Image
8from pxr import UsdGeom, Sdf
9
10NUM_FRAMES = 5
11
12# Save rgb image to file
13def save_rgb(rgb_data, file_name):
14 rgb_img = Image.fromarray(rgb_data, "RGBA")
15 rgb_img.save(file_name + ".png")
16
17
18# Randomize cube color every frame using a replicator randomizer
19def cube_color_randomizer():
20 cube_prims = rep.get.prims(path_pattern="Cube")
21 with cube_prims:
22 rep.randomizer.color(colors=rep.distribution.uniform((0, 0, 0), (1, 1, 1)))
23 return cube_prims.node
24
25
26# Access data through a custom replicator writer
27class MyWriter(Writer):
28 def __init__(self, rgb: bool = True):
29 self._frame_id = 0
30 if rgb:
31 self.annotators.append(AnnotatorRegistry.get_annotator("rgb"))
32 # Create writer output directory
33 self.file_path = os.path.join(os.getcwd(), "_out_mc_writer", "")
34 print(f"Writing writer data to {self.file_path}")
35 dir = os.path.dirname(self.file_path)
36 os.makedirs(dir, exist_ok=True)
37
38 def write(self, data):
39 for annotator in data.keys():
40 annotator_split = annotator.split("-")
41 if len(annotator_split) > 1:
42 render_product_name = annotator_split[-1]
43 if annotator.startswith("rgb"):
44 save_rgb(data[annotator], f"{self.file_path}/{render_product_name}_frame_{self._frame_id}")
45 self._frame_id += 1
46
47
48rep.WriterRegistry.register(MyWriter)
49
50# Create a new stage with a dome light
51omni.usd.get_context().new_stage()
52stage = omni.usd.get_context().get_stage()
53dome_light = stage.DefinePrim("/World/DomeLight", "DomeLight")
54dome_light.CreateAttribute("inputs:intensity", Sdf.ValueTypeNames.Float).Set(900.0)
55
56# Create cube
57cube_prim = stage.DefinePrim("/World/Cube", "Cube")
58UsdGeom.Xformable(cube_prim).AddTranslateOp().Set((0.0, 5.0, 1.0))
59
60# Register cube color randomizer to trigger on every frame
61rep.randomizer.register(cube_color_randomizer)
62with rep.trigger.on_frame():
63 rep.randomizer.cube_color_randomizer()
64
65# Create cameras
66camera_prim1 = stage.DefinePrim("/World/Camera1", "Camera")
67UsdGeom.Xformable(camera_prim1).AddTranslateOp().Set((0.0, 10.0, 20.0))
68UsdGeom.Xformable(camera_prim1).AddRotateXYZOp().Set((-15.0, 0.0, 0.0))
69
70camera_prim2 = stage.DefinePrim("/World/Camera2", "Camera")
71UsdGeom.Xformable(camera_prim2).AddTranslateOp().Set((-10.0, 15.0, 15.0))
72UsdGeom.Xformable(camera_prim2).AddRotateXYZOp().Set((-45.0, 0.0, 45.0))
73
74# Create render products
75rp1 = rep.create.render_product(str(camera_prim1.GetPrimPath()), resolution=(320, 320))
76rp2 = rep.create.render_product(str(camera_prim2.GetPrimPath()), resolution=(640, 640))
77rp3 = rep.create.render_product("/OmniverseKit_Persp", (1024, 1024))
78
79# Acess the data through a custom writer
80writer = rep.WriterRegistry.get("MyWriter")
81writer.initialize(rgb=True)
82writer.attach([rp1, rp2, rp3])
83
84# Acess the data through annotators
85rgb_annotators = []
86for rp in [rp1, rp2, rp3]:
87 rgb = rep.AnnotatorRegistry.get_annotator("rgb")
88 rgb.attach(rp)
89 rgb_annotators.append(rgb)
90
91# Create annotator output directory
92file_path = os.path.join(os.getcwd(), "_out_mc_annot", "")
93print(f"Writing annotator data to {file_path}")
94dir = os.path.dirname(file_path)
95os.makedirs(dir, exist_ok=True)
96
97# Data will be captured manually using step
98rep.orchestrator.set_capture_on_play(False)
99
100async def run_example_async():
101 for i in range(NUM_FRAMES):
102 # The step function provides new data to the annotators, triggers the randomizers and the writer
103 await rep.orchestrator.step_async(rt_subframes=4)
104 for j, rgb_annot in enumerate(rgb_annotators):
105 save_rgb(rgb_annot.get_data(), f"{dir}/rp{j}_step_{i}")
106
107
108asyncio.ensure_future(run_example_async())
Synthetic Data Access at Specific Simulation Timepoints#
Example on how to access synthetic data (rgb, semantic segmentation) from multiple cameras in a simulation scene at specific events using annotators or writers. The standalone example can also be found at: standalone_examples/api/omni.replicator.isaac/simulation_get_data.py
.
Synthetic Data Access at Specific Simulation Timepoints
1from isaacsim import SimulationApp
2
3simulation_app = SimulationApp(launch_config={"renderer": "RayTracedLighting", "headless": False})
4
5import json
6import os
7
8import carb.settings
9import numpy as np
10import omni
11import omni.replicator.core as rep
12from omni.isaac.core import World
13from omni.isaac.core.objects import DynamicCuboid
14from omni.isaac.core.utils.semantics import add_update_semantics
15from PIL import Image
16
17
18# Util function to save rgb annotator data
19def write_rgb_data(rgb_data, file_path):
20 rgb_img = Image.fromarray(rgb_data, "RGBA")
21 rgb_img.save(file_path + ".png")
22
23
24# Util function to save semantic segmentation annotator data
25def write_sem_data(sem_data, file_path):
26 id_to_labels = sem_data["info"]["idToLabels"]
27 with open(file_path + ".json", "w") as f:
28 json.dump(id_to_labels, f)
29 sem_image_data = np.frombuffer(sem_data["data"], dtype=np.uint8).reshape(*sem_data["data"].shape, -1)
30 sem_img = Image.fromarray(sem_image_data, "RGBA")
31 sem_img.save(file_path + ".png")
32
33
34# Create a new stage with the default ground plane
35omni.usd.get_context().new_stage()
36
37# Setup the simulation world
38world = World()
39world.scene.add_default_ground_plane()
40world.reset()
41
42# Setting capture on play to False will prevent the replicator from capturing data each frame
43carb.settings.get_settings().set("/omni/replicator/captureOnPlay", False)
44
45# Create a camera and render product to collect the data from
46cam = rep.create.camera(position=(5, 5, 5), look_at=(0, 0, 0))
47rp = rep.create.render_product(cam, (512, 512))
48
49# Set the output directory for the data
50out_dir = os.getcwd() + "/_out_sim_event"
51os.makedirs(out_dir, exist_ok=True)
52print(f"Outputting data to {out_dir}..")
53
54# Example of using a writer to save the data
55writer = rep.WriterRegistry.get("BasicWriter")
56writer.initialize(
57 output_dir=f"{out_dir}/writer", rgb=True, semantic_segmentation=True, colorize_semantic_segmentation=True
58)
59writer.attach(rp)
60
61# Run a preview to ensure the replicator graph is initialized
62rep.orchestrator.preview()
63
64# Example of accessing the data directly from annotators
65rgb_annot = rep.AnnotatorRegistry.get_annotator("rgb")
66rgb_annot.attach(rp)
67sem_annot = rep.AnnotatorRegistry.get_annotator("semantic_segmentation", init_params={"colorize": True})
68sem_annot.attach(rp)
69
70# Spawn and drop a few cubes, capture data when they stop moving
71for i in range(5):
72 cuboid = world.scene.add(DynamicCuboid(prim_path=f"/World/Cuboid_{i}", name=f"Cuboid_{i}", position=(0, 0, 10 + i)))
73 add_update_semantics(cuboid.prim, "Cuboid")
74
75 for s in range(500):
76 world.step(render=False)
77 vel = np.linalg.norm(cuboid.get_linear_velocity())
78 if vel < 0.1:
79 print(f"Cube_{i} stopped moving after {s} simulation steps, writing data..")
80 # Tigger the writer and update the annotators with new data
81 rep.orchestrator.step(rt_subframes=4, delta_time=0.0, pause_timeline=False)
82 write_rgb_data(rgb_annot.get_data(), f"{out_dir}/Cube_{i}_step_{s}_rgb")
83 write_sem_data(sem_annot.get_data(), f"{out_dir}/Cube_{i}_step_{s}_sem")
84 break
85
86simulation_app.close()
Synthetic Data Access at Specific Simulation Timepoints
1import asyncio
2import json
3import os
4
5import carb.settings
6import numpy as np
7import omni
8import omni.replicator.core as rep
9from omni.isaac.core import World
10from omni.isaac.core.objects import DynamicCuboid
11from omni.isaac.core.utils.semantics import add_update_semantics
12from PIL import Image
13
14
15# Util function to save rgb annotator data
16def write_rgb_data(rgb_data, file_path):
17 rgb_img = Image.fromarray(rgb_data, "RGBA")
18 rgb_img.save(file_path + ".png")
19
20
21# Util function to save semantic segmentation annotator data
22def write_sem_data(sem_data, file_path):
23 id_to_labels = sem_data["info"]["idToLabels"]
24 with open(file_path + ".json", "w") as f:
25 json.dump(id_to_labels, f)
26 sem_image_data = np.frombuffer(sem_data["data"], dtype=np.uint8).reshape(*sem_data["data"].shape, -1)
27 sem_img = Image.fromarray(sem_image_data, "RGBA")
28 sem_img.save(file_path + ".png")
29
30
31# Create a new stage with the default ground plane
32omni.usd.get_context().new_stage()
33
34# Setup the simulation world
35world = World()
36world.scene.add_default_ground_plane()
37
38
39# Setting capture on play to False will prevent the replicator from capturing data each frame
40carb.settings.get_settings().set("/omni/replicator/captureOnPlay", False)
41
42# Create a camera and render product to collect the data from
43cam = rep.create.camera(position=(5, 5, 5), look_at=(0, 0, 0))
44rp = rep.create.render_product(cam, (512, 512))
45
46# Set the output directory for the data
47out_dir = os.getcwd() + "/_out_sim_event"
48os.makedirs(out_dir, exist_ok=True)
49print(f"Outputting data to {out_dir}..")
50
51# Example of using a writer to save the data
52writer = rep.WriterRegistry.get("BasicWriter")
53writer.initialize(
54 output_dir=f"{out_dir}/writer", rgb=True, semantic_segmentation=True, colorize_semantic_segmentation=True
55)
56writer.attach(rp)
57
58# Run a preview to ensure the replicator graph is initialized
59rep.orchestrator.preview()
60
61# Example of accessing the data directly from annotators
62rgb_annot = rep.AnnotatorRegistry.get_annotator("rgb")
63rgb_annot.attach(rp)
64sem_annot = rep.AnnotatorRegistry.get_annotator("semantic_segmentation", init_params={"colorize": True})
65sem_annot.attach(rp)
66
67
68async def run_example_async():
69 await world.initialize_simulation_context_async()
70 await world.reset_async()
71
72 # Spawn and drop a few cubes, capture data when they stop moving
73 for i in range(5):
74 cuboid = world.scene.add(
75 DynamicCuboid(prim_path=f"/World/Cuboid_{i}", name=f"Cuboid_{i}", position=(0, 0, 10 + i))
76 )
77 add_update_semantics(cuboid.prim, "Cuboid")
78
79 for s in range(500):
80 await omni.kit.app.get_app().next_update_async()
81 vel = np.linalg.norm(cuboid.get_linear_velocity())
82 if vel < 0.1:
83 print(f"Cube_{i} stopped moving after {s} simulation steps, writing data..")
84 # Tigger the writer and update the annotators with new data
85 await rep.orchestrator.step_async(rt_subframes=4, delta_time=0.0, pause_timeline=False)
86 write_rgb_data(rgb_annot.get_data(), f"{out_dir}/Cube_{i}_step_{s}_rgb")
87 write_sem_data(sem_annot.get_data(), f"{out_dir}/Cube_{i}_step_{s}_sem")
88 break
89
90
91asyncio.ensure_future(run_example_async())
Custom Event Randomization and Writing#
The following example showcases the use of custom events to trigger randomizations and data writing at various times throughout the simulation. The standalone example can also be found at: standalone_examples/api/omni.replicator.isaac/custom_event_and_write.py
.
Custom Event Randomization and Writing
1from isaacsim import SimulationApp
2
3simulation_app = SimulationApp(launch_config={"headless": False})
4
5import os
6
7import omni.replicator.core as rep
8import omni.usd
9
10omni.usd.get_context().new_stage()
11distance_light = rep.create.light(rotation=(315, 0, 0), intensity=4000, light_type="distant")
12
13large_cube = rep.create.cube(scale=1.25, position=(1, 1, 0))
14small_cube = rep.create.cube(scale=0.75, position=(-1, -1, 0))
15large_cube_prim = large_cube.get_output_prims()["prims"][0]
16small_cube_prim = small_cube.get_output_prims()["prims"][0]
17
18rp = rep.create.render_product("/OmniverseKit_Persp", (512, 512))
19writer = rep.WriterRegistry.get("BasicWriter")
20out_dir = os.getcwd() + "/_out_custom_event"
21print(f"Writing data to {out_dir}")
22writer.initialize(output_dir=out_dir, rgb=True)
23writer.attach(rp)
24
25with rep.trigger.on_custom_event(event_name="randomize_large_cube"):
26 with large_cube:
27 rep.randomizer.rotation()
28
29with rep.trigger.on_custom_event(event_name="randomize_small_cube"):
30 with small_cube:
31 rep.randomizer.rotation()
32
33
34def run_example():
35 print(f"Randomizing small cube")
36 rep.utils.send_og_event(event_name="randomize_small_cube")
37 print("Capturing frame")
38 rep.orchestrator.step(rt_subframes=8)
39
40 print("Moving small cube")
41 small_cube_prim.GetAttribute("xformOp:translate").Set((-2, -2, 0))
42 print("Capturing frame")
43 rep.orchestrator.step(rt_subframes=8)
44
45 print(f"Randomizing large cube")
46 rep.utils.send_og_event(event_name="randomize_large_cube")
47 print("Capturing frame")
48 rep.orchestrator.step(rt_subframes=8)
49
50 print("Moving large cube")
51 large_cube_prim.GetAttribute("xformOp:translate").Set((2, 2, 0))
52 print("Capturing frame")
53 rep.orchestrator.step(rt_subframes=8)
54
55 # Wait until all the data is saved to disk
56 rep.orchestrator.wait_until_complete()
57
58
59run_example()
60
61simulation_app.close()
Custom Event Randomization and Writing
1import asyncio
2import os
3
4import omni.replicator.core as rep
5import omni.usd
6
7omni.usd.get_context().new_stage()
8distance_light = rep.create.light(rotation=(315, 0, 0), intensity=4000, light_type="distant")
9
10large_cube = rep.create.cube(scale=1.25, position=(1, 1, 0))
11small_cube = rep.create.cube(scale=0.75, position=(-1, -1, 0))
12large_cube_prim = large_cube.get_output_prims()["prims"][0]
13small_cube_prim = small_cube.get_output_prims()["prims"][0]
14
15rp = rep.create.render_product("/OmniverseKit_Persp", (512, 512))
16writer = rep.WriterRegistry.get("BasicWriter")
17out_dir = os.getcwd() + "/_out_custom_event"
18print(f"Writing data to {out_dir}")
19writer.initialize(output_dir=out_dir, rgb=True)
20writer.attach(rp)
21
22with rep.trigger.on_custom_event(event_name="randomize_large_cube"):
23 with large_cube:
24 rep.randomizer.rotation()
25
26with rep.trigger.on_custom_event(event_name="randomize_small_cube"):
27 with small_cube:
28 rep.randomizer.rotation()
29
30
31async def run_example_async():
32 print(f"Randomizing small cube")
33 rep.utils.send_og_event(event_name="randomize_small_cube")
34 print("Capturing frame")
35 await rep.orchestrator.step_async(rt_subframes=8)
36
37 print("Moving small cube")
38 small_cube_prim.GetAttribute("xformOp:translate").Set((-2, -2, 0))
39 print("Capturing frame")
40 await rep.orchestrator.step_async(rt_subframes=8)
41
42 print(f"Randomizing large cube")
43 rep.utils.send_og_event(event_name="randomize_large_cube")
44 print("Capturing frame")
45 await rep.orchestrator.step_async(rt_subframes=8)
46
47 print("Moving large cube")
48 large_cube_prim.GetAttribute("xformOp:translate").Set((2, 2, 0))
49 print("Capturing frame")
50 await rep.orchestrator.step_async(rt_subframes=8)
51
52 # Wait until all the data is saved to disk
53 await rep.orchestrator.wait_until_complete_async()
54
55
56asyncio.ensure_future(run_example_async())
Motion Blur#
This example demonstrates how to capture motion blur data using RTX - Real-Time and RTX - Interactive (Path Tracing) rendering modes. For the RTX - Real-Time mode, details on the motion blur parameters can be found here. For the RTX – Interactive (Path Tracing) mode, motion blur is achieved by rendering multiple subframes (/omni/replicator/pathTracedMotionBlurSubSamples
) and combining them to create the effect. The example uses animated and physics-enabled assets with synchronized motion. Keyframe animated assets can be advanced at any custom delta time due to their interpolated motion, whereas physics-enabled assets require a custom physics FPS to ensure motion samples at any custom delta time. The example showcases how to compute the target physics FPS, change it if needed, and restore the original physics FPS after capturing the motion blur. The standalone example can also be found at: standalone_examples/api/omni.replicator.isaac/motion_blur.py
.
Motion Blur
1from isaacsim import SimulationApp
2
3simulation_app = SimulationApp({"headless": False})
4
5import os
6
7import carb.settings
8import omni.kit.app
9import omni.replicator.core as rep
10import omni.timeline
11import omni.usd
12from omni.isaac.nucleus import get_assets_root_path
13from pxr import PhysxSchema, Sdf, UsdGeom, UsdPhysics
14
15# Paths to the animated and physics-ready assets
16PHYSICS_ASSET_URL = "/Isaac/Props/YCB/Axis_Aligned_Physics/003_cracker_box.usd"
17ANIM_ASSET_URL = "/Isaac/Props/YCB/Axis_Aligned/003_cracker_box.usd"
18
19# -z velocities and start locations of the animated (left side) and physics (right side) assets (stage units/s)
20ASSET_VELOCITIES = [0, 5, 10]
21ASSET_X_MIRRORED_LOCATIONS = [(0.5, 0, 0.3), (0.3, 0, 0.3), (0.1, 0, 0.3)]
22
23# Used to calculate how many frames to animate the assets to maintain the same velocity as the physics assets
24ANIMATION_DURATION = 10
25
26# Create a new stage with animated and physics-enabled assets with synchronized motion
27def setup_stage():
28 # Create new stage
29 omni.usd.get_context().new_stage()
30 stage = omni.usd.get_context().get_stage()
31 timeline = omni.timeline.get_timeline_interface()
32 timeline.set_end_time(ANIMATION_DURATION)
33
34 # Create lights
35 dome_light = stage.DefinePrim("/World/DomeLight", "DomeLight")
36 dome_light.CreateAttribute("inputs:intensity", Sdf.ValueTypeNames.Float).Set(100.0)
37 distant_light = stage.DefinePrim("/World/DistantLight", "DistantLight")
38 if not distant_light.GetAttribute("xformOp:rotateXYZ"):
39 UsdGeom.Xformable(distant_light).AddRotateXYZOp()
40 distant_light.GetAttribute("xformOp:rotateXYZ").Set((-75, 0, 0))
41 distant_light.CreateAttribute("inputs:intensity", Sdf.ValueTypeNames.Float).Set(2500)
42
43 # Setup the physics assets with gravity disabled and the requested velocity
44 assets_root_path = get_assets_root_path()
45 physics_asset_url = assets_root_path + PHYSICS_ASSET_URL
46 for loc, vel in zip(ASSET_X_MIRRORED_LOCATIONS, ASSET_VELOCITIES):
47 prim = stage.DefinePrim(f"/World/physics_asset_{int(abs(vel))}", "Xform")
48 prim.GetReferences().AddReference(physics_asset_url)
49 if not prim.GetAttribute("xformOp:translate"):
50 UsdGeom.Xformable(prim).AddTranslateOp()
51 prim.GetAttribute("xformOp:translate").Set(loc)
52 prim.GetAttribute("physxRigidBody:disableGravity").Set(True)
53 prim.GetAttribute("physxRigidBody:angularDamping").Set(0.0)
54 prim.GetAttribute("physxRigidBody:linearDamping").Set(0.0)
55 prim.GetAttribute("physics:velocity").Set((0, 0, -vel))
56
57 # Setup animated assets maintaining the same velocity as the physics assets
58 anim_asset_url = assets_root_path + ANIM_ASSET_URL
59 for loc, vel in zip(ASSET_X_MIRRORED_LOCATIONS, ASSET_VELOCITIES):
60 start_loc = (-loc[0], loc[1], loc[2])
61 prim = stage.DefinePrim(f"/World/anim_asset_{int(abs(vel))}", "Xform")
62 prim.GetReferences().AddReference(anim_asset_url)
63 if not prim.GetAttribute("xformOp:translate"):
64 UsdGeom.Xformable(prim).AddTranslateOp()
65 anim_distance = vel * ANIMATION_DURATION
66 end_loc = (start_loc[0], start_loc[1], start_loc[2] - anim_distance)
67 end_keyframe = timeline.get_time_codes_per_seconds() * ANIMATION_DURATION
68 # Timesampled keyframe (animated) translation
69 prim.GetAttribute("xformOp:translate").Set(start_loc, time=0)
70 prim.GetAttribute("xformOp:translate").Set(end_loc, time=end_keyframe)
71
72
73# Capture motion blur frames with the given delta time step and render mode
74def run_motion_blur_example(num_frames=3, custom_delta_time=None, use_path_tracing=True, pt_subsamples=8, pt_spp=64):
75 # Create a new stage with the assets
76 setup_stage()
77 stage = omni.usd.get_context().get_stage()
78
79 # Set replicator settings (capture only on request and enable motion blur)
80 carb.settings.get_settings().set("/omni/replicator/captureOnPlay", False)
81 carb.settings.get_settings().set("/omni/replicator/captureMotionBlur", True)
82
83 # Set motion blur settings based on the render mode
84 if use_path_tracing:
85 print(f"[MotionBlur] Setting PathTracing render mode motion blur settings")
86 carb.settings.get_settings().set("/rtx/rendermode", "PathTracing")
87 # (int): Total number of samples for each rendered pixel, per frame.
88 carb.settings.get_settings().set("/rtx/pathtracing/spp", pt_spp)
89 # (int): Maximum number of samples to accumulate per pixel. When this count is reached the rendering stops until a scene or setting change is detected, restarting the rendering process. Set to 0 to remove this limit.
90 carb.settings.get_settings().set("/rtx/pathtracing/totalSpp", pt_spp)
91 carb.settings.get_settings().set("/rtx/pathtracing/optixDenoiser/enabled", 0)
92 # Number of sub samples to render if in PathTracing render mode and motion blur is enabled.
93 carb.settings.get_settings().set("/omni/replicator/pathTracedMotionBlurSubSamples", pt_subsamples)
94 else:
95 print(f"[MotionBlur] Setting RayTracedLighting render mode motion blur settings")
96 carb.settings.get_settings().set("/rtx/rendermode", "RayTracedLighting")
97 # 0: Disabled, 1: TAA, 2: FXAA, 3: DLSS, 4:RTXAA
98 carb.settings.get_settings().set("/rtx/post/aa/op", 2)
99 # (float): The fraction of the largest screen dimension to use as the maximum motion blur diameter.
100 carb.settings.get_settings().set("/rtx/post/motionblur/maxBlurDiameterFraction", 0.02)
101 # (float): Exposure time fraction in frames (1.0 = one frame duration) to sample.
102 carb.settings.get_settings().set("/rtx/post/motionblur/exposureFraction", 1.0)
103 # (int): Number of samples to use in the filter. A higher number improves quality at the cost of performance.
104 carb.settings.get_settings().set("/rtx/post/motionblur/numSamples", 8)
105
106 # Setup camera and writer
107 camera = rep.create.camera(position=(0, 1.5, 0), look_at=(0, 0, 0), name="MotionBlurCam")
108 render_product = rep.create.render_product(camera, (1920, 1080))
109 basic_writer = rep.WriterRegistry.get("BasicWriter")
110 delta_time_str = "None" if custom_delta_time is None else f"{custom_delta_time:.4f}"
111 render_mode_str = f"pt_subsamples_{pt_subsamples}_spp_{pt_spp}" if use_path_tracing else "rt"
112 output_directory = os.getcwd() + f"/_out_motion_blur_dt_{delta_time_str}_{render_mode_str}"
113 print(f"[MotionBlur] Output directory: {output_directory}")
114 basic_writer.initialize(output_dir=output_directory, rgb=True)
115 basic_writer.attach(render_product)
116
117 # Run a few updates to make sure all materials are fully loaded for capture
118 for _ in range(50):
119 simulation_app.update()
120
121 # Use the physics scene to modify the physics FPS (if needed) to guarantee motion samples at any custom delta time
122 physx_scene = None
123 for prim in stage.Traverse():
124 if prim.IsA(UsdPhysics.Scene):
125 physx_scene = PhysxSchema.PhysxSceneAPI.Apply(prim)
126 break
127 if physx_scene is None:
128 print(f"[MotionBlur] Creating a new PhysicsScene")
129 physics_scene = UsdPhysics.Scene.Define(stage, "/PhysicsScene")
130 physx_scene = PhysxSchema.PhysxSceneAPI.Apply(stage.GetPrimAtPath("/PhysicsScene"))
131
132 # Check the target physics depending on the custom delta time and the render mode
133 target_physics_fps = stage.GetTimeCodesPerSecond() if custom_delta_time is None else 1 / custom_delta_time
134 if use_path_tracing:
135 target_physics_fps *= pt_subsamples
136
137 # Check if the physics FPS needs to be increased to match the custom delta time
138 orig_physics_fps = physx_scene.GetTimeStepsPerSecondAttr().Get()
139 if target_physics_fps > orig_physics_fps:
140 print(f"[MotionBlur] Changing physics FPS from {orig_physics_fps} to {target_physics_fps}")
141 physx_scene.GetTimeStepsPerSecondAttr().Set(target_physics_fps)
142
143 # Start the timeline for physics updates in the step function
144 timeline = omni.timeline.get_timeline_interface()
145 timeline.play()
146
147 # Capture frames
148 for i in range(num_frames):
149 print(f"[MotionBlur] \tCapturing frame {i}")
150 rep.orchestrator.step(delta_time=custom_delta_time)
151
152 # Restore the original physics FPS
153 if target_physics_fps > orig_physics_fps:
154 print(f"[MotionBlur] Restoring physics FPS from {target_physics_fps} to {orig_physics_fps}")
155 physx_scene.GetTimeStepsPerSecondAttr().Set(orig_physics_fps)
156
157 # Switch back to the raytracing render mode
158 if use_path_tracing:
159 print(f"[MotionBlur] Restoring render mode to RayTracedLighting")
160 carb.settings.get_settings().set("/rtx/rendermode", "RayTracedLighting")
161
162
163def run_motion_blur_examples():
164 motion_blur_step_duration = [None, 1 / 30, 1 / 60, 1 / 240]
165 for custom_delta_time in motion_blur_step_duration:
166 # RayTracing examples
167 run_motion_blur_example(custom_delta_time=custom_delta_time, use_path_tracing=False)
168 # PathTracing examples
169 spps = [32, 128]
170 motion_blur_sub_samples = [4, 16]
171 for motion_blur_sub_sample in motion_blur_sub_samples:
172 for spp in spps:
173 run_motion_blur_example(
174 custom_delta_time=custom_delta_time,
175 use_path_tracing=True,
176 pt_subsamples=motion_blur_sub_sample,
177 pt_spp=spp,
178 )
179
180
181run_motion_blur_examples()
182
183simulation_app.close()
Motion Blur
1import asyncio
2import os
3
4import carb.settings
5import omni.kit.app
6import omni.replicator.core as rep
7import omni.timeline
8import omni.usd
9from omni.isaac.nucleus import get_assets_root_path
10from pxr import PhysxSchema, Sdf, UsdGeom, UsdPhysics
11
12# Paths to the animated and physics-ready assets
13PHYSICS_ASSET_URL = "/Isaac/Props/YCB/Axis_Aligned_Physics/003_cracker_box.usd"
14ANIM_ASSET_URL = "/Isaac/Props/YCB/Axis_Aligned/003_cracker_box.usd"
15
16# -z velocities and start locations of the animated (left side) and physics (right side) assets (stage units/s)
17ASSET_VELOCITIES = [0, 5, 10]
18ASSET_X_MIRRORED_LOCATIONS = [(0.5, 0, 0.3), (0.3, 0, 0.3), (0.1, 0, 0.3)]
19
20# Used to calculate how many frames to animate the assets to maintain the same velocity as the physics assets
21ANIMATION_DURATION = 10
22
23# Create a new stage with animated and physics-enabled assets with synchronized motion
24def setup_stage():
25 # Create new stage
26 omni.usd.get_context().new_stage()
27 stage = omni.usd.get_context().get_stage()
28 timeline = omni.timeline.get_timeline_interface()
29 timeline.set_end_time(ANIMATION_DURATION)
30
31 # Create lights
32 dome_light = stage.DefinePrim("/World/DomeLight", "DomeLight")
33 dome_light.CreateAttribute("inputs:intensity", Sdf.ValueTypeNames.Float).Set(100.0)
34 distant_light = stage.DefinePrim("/World/DistantLight", "DistantLight")
35 if not distant_light.GetAttribute("xformOp:rotateXYZ"):
36 UsdGeom.Xformable(distant_light).AddRotateXYZOp()
37 distant_light.GetAttribute("xformOp:rotateXYZ").Set((-75, 0, 0))
38 distant_light.CreateAttribute("inputs:intensity", Sdf.ValueTypeNames.Float).Set(2500)
39
40 # Setup the physics assets with gravity disabled and the requested velocity
41 assets_root_path = get_assets_root_path()
42 physics_asset_url = assets_root_path + PHYSICS_ASSET_URL
43 for loc, vel in zip(ASSET_X_MIRRORED_LOCATIONS, ASSET_VELOCITIES):
44 prim = stage.DefinePrim(f"/World/physics_asset_{int(abs(vel))}", "Xform")
45 prim.GetReferences().AddReference(physics_asset_url)
46 if not prim.GetAttribute("xformOp:translate"):
47 UsdGeom.Xformable(prim).AddTranslateOp()
48 prim.GetAttribute("xformOp:translate").Set(loc)
49 prim.GetAttribute("physxRigidBody:disableGravity").Set(True)
50 prim.GetAttribute("physxRigidBody:angularDamping").Set(0.0)
51 prim.GetAttribute("physxRigidBody:linearDamping").Set(0.0)
52 prim.GetAttribute("physics:velocity").Set((0, 0, -vel))
53
54 # Setup animated assets maintaining the same velocity as the physics assets
55 anim_asset_url = assets_root_path + ANIM_ASSET_URL
56 for loc, vel in zip(ASSET_X_MIRRORED_LOCATIONS, ASSET_VELOCITIES):
57 start_loc = (-loc[0], loc[1], loc[2])
58 prim = stage.DefinePrim(f"/World/anim_asset_{int(abs(vel))}", "Xform")
59 prim.GetReferences().AddReference(anim_asset_url)
60 if not prim.GetAttribute("xformOp:translate"):
61 UsdGeom.Xformable(prim).AddTranslateOp()
62 anim_distance = vel * ANIMATION_DURATION
63 end_loc = (start_loc[0], start_loc[1], start_loc[2] - anim_distance)
64 end_keyframe = timeline.get_time_codes_per_seconds() * ANIMATION_DURATION
65 # Timesampled keyframe (animated) translation
66 prim.GetAttribute("xformOp:translate").Set(start_loc, time=0)
67 prim.GetAttribute("xformOp:translate").Set(end_loc, time=end_keyframe)
68
69
70# Capture motion blur frames with the given delta time step and render mode
71async def run_motion_blur_example_async(
72 num_frames=3, custom_delta_time=None, use_path_tracing=True, pt_subsamples=8, pt_spp=64
73):
74 # Create a new stage with the assets
75 setup_stage()
76 stage = omni.usd.get_context().get_stage()
77
78 # Set replicator settings (capture only on request and enable motion blur)
79 carb.settings.get_settings().set("/omni/replicator/captureOnPlay", False)
80 carb.settings.get_settings().set("/omni/replicator/captureMotionBlur", True)
81
82 # Set motion blur settings based on the render mode
83 if use_path_tracing:
84 print(f"[MotionBlur] Setting PathTracing render mode motion blur settings")
85 carb.settings.get_settings().set("/rtx/rendermode", "PathTracing")
86 # (int): Total number of samples for each rendered pixel, per frame.
87 carb.settings.get_settings().set("/rtx/pathtracing/spp", pt_spp)
88 # (int): Maximum number of samples to accumulate per pixel. When this count is reached the rendering stops until a scene or setting change is detected, restarting the rendering process. Set to 0 to remove this limit.
89 carb.settings.get_settings().set("/rtx/pathtracing/totalSpp", pt_spp)
90 carb.settings.get_settings().set("/rtx/pathtracing/optixDenoiser/enabled", 0)
91 # Number of sub samples to render if in PathTracing render mode and motion blur is enabled.
92 carb.settings.get_settings().set("/omni/replicator/pathTracedMotionBlurSubSamples", pt_subsamples)
93 else:
94 print(f"[MotionBlur] Setting RayTracedLighting render mode motion blur settings")
95 carb.settings.get_settings().set("/rtx/rendermode", "RayTracedLighting")
96 # 0: Disabled, 1: TAA, 2: FXAA, 3: DLSS, 4:RTXAA
97 carb.settings.get_settings().set("/rtx/post/aa/op", 2)
98 # (float): The fraction of the largest screen dimension to use as the maximum motion blur diameter.
99 carb.settings.get_settings().set("/rtx/post/motionblur/maxBlurDiameterFraction", 0.02)
100 # (float): Exposure time fraction in frames (1.0 = one frame duration) to sample.
101 carb.settings.get_settings().set("/rtx/post/motionblur/exposureFraction", 1.0)
102 # (int): Number of samples to use in the filter. A higher number improves quality at the cost of performance.
103 carb.settings.get_settings().set("/rtx/post/motionblur/numSamples", 8)
104
105 # Setup camera and writer
106 camera = rep.create.camera(position=(0, 1.5, 0), look_at=(0, 0, 0), name="MotionBlurCam")
107 render_product = rep.create.render_product(camera, (1920, 1080))
108 basic_writer = rep.WriterRegistry.get("BasicWriter")
109 delta_time_str = "None" if custom_delta_time is None else f"{custom_delta_time:.4f}"
110 render_mode_str = f"pt_subsamples_{pt_subsamples}_spp_{pt_spp}" if use_path_tracing else "rt"
111 output_directory = os.getcwd() + f"/_out_motion_blur_dt_{delta_time_str}_{render_mode_str}"
112 print(f"[MotionBlur] Output directory: {output_directory}")
113 basic_writer.initialize(output_dir=output_directory, rgb=True)
114 basic_writer.attach(render_product)
115
116 # Run a few updates to make sure all materials are fully loaded for capture
117 for _ in range(50):
118 await omni.kit.app.get_app().next_update_async()
119
120 # Use the physics scene to modify the physics FPS (if needed) to guarantee motion samples at any custom delta time
121 physx_scene = None
122 for prim in stage.Traverse():
123 if prim.IsA(UsdPhysics.Scene):
124 physx_scene = PhysxSchema.PhysxSceneAPI.Apply(prim)
125 break
126 if physx_scene is None:
127 print(f"[MotionBlur] Creating a new PhysicsScene")
128 physics_scene = UsdPhysics.Scene.Define(stage, "/PhysicsScene")
129 physx_scene = PhysxSchema.PhysxSceneAPI.Apply(stage.GetPrimAtPath("/PhysicsScene"))
130
131 # Check the target physics depending on the custom delta time and the render mode
132 target_physics_fps = stage.GetTimeCodesPerSecond() if custom_delta_time is None else 1 / custom_delta_time
133 if use_path_tracing:
134 target_physics_fps *= pt_subsamples
135
136 # Check if the physics FPS needs to be increased to match the custom delta time
137 orig_physics_fps = physx_scene.GetTimeStepsPerSecondAttr().Get()
138 if target_physics_fps > orig_physics_fps:
139 print(f"[MotionBlur] Changing physics FPS from {orig_physics_fps} to {target_physics_fps}")
140 physx_scene.GetTimeStepsPerSecondAttr().Set(target_physics_fps)
141
142 # Start the timeline for physics updates in the step function
143 timeline = omni.timeline.get_timeline_interface()
144 timeline.play()
145
146 # Capture frames
147 for i in range(num_frames):
148 print(f"[MotionBlur] \tCapturing frame {i}")
149 await rep.orchestrator.step_async(delta_time=custom_delta_time)
150
151 # Restore the original physics FPS
152 if target_physics_fps > orig_physics_fps:
153 print(f"[MotionBlur] Restoring physics FPS from {target_physics_fps} to {orig_physics_fps}")
154 physx_scene.GetTimeStepsPerSecondAttr().Set(orig_physics_fps)
155
156 # Switch back to the raytracing render mode
157 if use_path_tracing:
158 print(f"[MotionBlur] Restoring render mode to RayTracedLighting")
159 carb.settings.get_settings().set("/rtx/rendermode", "RayTracedLighting")
160
161
162async def run_motion_blur_examples_async():
163 motion_blur_step_duration = [None, 1 / 30, 1 / 60, 1 / 240]
164 for custom_delta_time in motion_blur_step_duration:
165 # RayTracing examples
166 await run_motion_blur_example_async(custom_delta_time=custom_delta_time, use_path_tracing=False)
167 # PathTracing examples
168 spps = [32, 128]
169 motion_blur_sub_samples = [4, 16]
170 for motion_blur_sub_sample in motion_blur_sub_samples:
171 for spp in spps:
172 await run_motion_blur_example_async(
173 custom_delta_time=custom_delta_time,
174 use_path_tracing=True,
175 pt_subsamples=motion_blur_sub_sample,
176 pt_spp=spp,
177 )
178
179
180asyncio.ensure_future(run_motion_blur_examples_async())
Subscribers and Events at Custom FPS#
Examples of subscribing to various events (such as stage, physics, and render/app), setting custom update rates, and adjusting various related settings. The standalone example can also be found at: standalone_examples/api/omni.replicator.isaac/subscribers_and_events.py
.
Subscribers and Events at Custom FPS
1from isaacsim import SimulationApp
2
3simulation_app = SimulationApp({"headless": False})
4
5import asyncio
6import time
7
8import carb.events
9import carb.settings
10import omni.kit.app
11import omni.physx
12import omni.timeline
13import omni.usd
14from pxr import PhysxSchema, UsdPhysics
15
16# TIMELINE / STAGE
17USE_CUSTOM_TIMELINE_SETTINGS = False
18USE_FIXED_TIME_STEPPING = False
19PLAY_EVERY_FRAME = True
20PLAY_DELAY_COMPENSATION = 0.0
21SUBSAMPLE_RATE = 1
22STAGE_FPS = 30.0
23
24# PHYSX
25USE_CUSTOM_PHYSX_FPS = False
26PHYSX_FPS = 60.0
27MIN_SIM_FPS = 30
28
29# Simulations can also be enabled/disabled at runtime
30DISABLE_SIMULATIONS = False
31
32# APP / RENDER
33LIMIT_APP_FPS = False
34APP_FPS = 120
35
36# Duration after which to clear subscribers and print the cached events
37MAX_DURATION = 3.0
38PRINT_EVENTS = False
39
40
41def on_timeline_event(event: omni.timeline.TimelineEventType):
42 global timeline_sub
43 global timeline_events
44 global wall_start_time
45 elapsed_wall_time = time.time() - wall_start_time
46
47 # Cache only time advance events
48 if event.type == omni.timeline.TimelineEventType.CURRENT_TIME_TICKED.value:
49 event_name = omni.timeline.TimelineEventType(event.type).name
50 event_payload = event.payload
51 timeline_events.append((elapsed_wall_time, event_name, event_payload))
52
53 # Clear subscriber and print cached events
54 if elapsed_wall_time > MAX_DURATION:
55 if timeline_sub is not None:
56 timeline_sub.unsubscribe()
57 timeline_sub = None
58 num_events = len(timeline_events)
59 fps = num_events / MAX_DURATION
60 print(f"[timeline] captured {num_events} events with aprox {fps} FPS")
61 if PRINT_EVENTS:
62 for i, (wall_time, event_name, payload) in enumerate(timeline_events):
63 print(f"\t[timeline][{i}]\ttime={wall_time:.4f};\tevent={event_name};\tpayload={payload}")
64
65
66def on_physics_step(dt: float):
67 global physx_events
68 global wall_start_time
69 elapsed_wall_time = time.time() - wall_start_time
70
71 # Cache physics events
72 physx_events.append((elapsed_wall_time, dt))
73
74 # Clear subscriber and print cached events
75 if elapsed_wall_time > MAX_DURATION:
76 # Physics unsubscription needs to be defered from the callback function
77 # see: '[Error] [omni.physx.plugin] Subscription cannot be changed during the event call'
78 async def clear_physx_sub_async():
79 global physx_sub
80 if physx_sub is not None:
81 physx_sub.unsubscribe()
82 physx_sub = None
83
84 asyncio.ensure_future(clear_physx_sub_async())
85 num_events = len(physx_events)
86 fps = num_events / MAX_DURATION
87 print(f"[physics] captured {num_events} events with aprox {fps} FPS")
88 if PRINT_EVENTS:
89 for i, (wall_time, dt) in enumerate(physx_events):
90 print(f"\t[physics][{i}]\ttime={wall_time:.4f};\tdt={dt};")
91
92
93def on_stage_render_event(event: omni.usd.StageRenderingEventType):
94 global stage_render_sub
95 global stage_render_events
96 global wall_start_time
97 elapsed_wall_time = time.time() - wall_start_time
98
99 event_name = omni.usd.StageRenderingEventType(event.type).name
100 event_payload = event.payload
101 stage_render_events.append((elapsed_wall_time, event_name, event_payload))
102
103 if elapsed_wall_time > MAX_DURATION:
104 if stage_render_sub is not None:
105 stage_render_sub.unsubscribe()
106 stage_render_sub = None
107 num_events = len(stage_render_events)
108 fps = num_events / MAX_DURATION
109 print(f"[stage render] captured {num_events} events with aprox {fps} FPS")
110 if PRINT_EVENTS:
111 for i, (wall_time, event_name, payload) in enumerate(stage_render_events):
112 print(f"\t[stage render][{i}]\ttime={wall_time:.4f};\tevent={event_name};\tpayload={payload}")
113
114
115def on_app_update(event: carb.events.IEvent):
116 global app_sub
117 global app_update_events
118 global wall_start_time
119 elapsed_wall_time = time.time() - wall_start_time
120
121 event_type = event.type
122 event_payload = event.payload
123 app_update_events.append((elapsed_wall_time, event_type, event_payload))
124
125 if elapsed_wall_time > MAX_DURATION:
126 if app_sub is not None:
127 app_sub.unsubscribe()
128 app_sub = None
129 num_events = len(app_update_events)
130 fps = num_events / MAX_DURATION
131 print(f"[app] captured {num_events} events with aprox {fps} FPS")
132 if PRINT_EVENTS:
133 for i, (wall_time, event_type, payload) in enumerate(app_update_events):
134 print(f"\t[app][{i}]\ttime={wall_time:.4f};\tevent={event_type};\tpayload={payload}")
135
136
137stage = omni.usd.get_context().get_stage()
138timeline = omni.timeline.get_timeline_interface()
139
140
141if USE_CUSTOM_TIMELINE_SETTINGS:
142 # Ideal to make simulation and animation synchronized.
143 # Default: True in editor, False in standalone.
144 # NOTE:
145 # - It may limit the frame rate (see 'timeline.set_play_every_frame') such that the elapsed wall clock time matches the frame's delta time.
146 # - If the app runs slower than this, animation playback may slow down (see 'CompensatePlayDelayInSecs').
147 # - For performance benchmarks, turn this off or set a very high target in `timeline.set_target_framerate`
148 carb.settings.get_settings().set("/app/player/useFixedTimeStepping", USE_FIXED_TIME_STEPPING)
149
150 # This compensates for frames that require more computation time than the frame's fixed delta time, by temporarily speeding up playback.
151 # The parameter represents the length of these "faster" playback periods, which means that it must be larger than the fixed frame time to take effect.
152 # Default: 0.0
153 # NOTE:
154 # - only effective if `useFixedTimeStepping` is set to True
155 # - setting a large value results in long fast playback after a huge lag spike
156 carb.settings.get_settings().set("/app/player/CompensatePlayDelayInSecs", PLAY_DELAY_COMPENSATION)
157
158 # If set to True, no frames are skipped and in every frame time advances by `1 / TimeCodesPerSecond`.
159 # Default: False
160 # NOTE:
161 # - only effective if `useFixedTimeStepping` is set to True
162 # - simulation is usually faster than real-time and processing is only limited by the frame rate of the runloop
163 # - useful for recording
164 # - same as `carb.settings.get_settings().set("/app/player/useFastMode", PLAY_EVERY_FRAME)`
165 timeline.set_play_every_frame(PLAY_EVERY_FRAME)
166
167 # Timeline sub-stepping, i.e. how many times updates are called (update events are dispatched) each frame.
168 # Default: 1
169 # NOTE: same as `carb.settings.get_settings().set("/app/player/timelineSubsampleRate", SUBSAMPLE_RATE)`
170 timeline.set_ticks_per_frame(SUBSAMPLE_RATE)
171
172 # Time codes per second for the stage
173 # NOTE: same as `stage.SetTimeCodesPerSecond(STAGE_FPS)` and `carb.settings.get_settings().set("/app/stage/timeCodesPerSecond", STAGE_FPS)`
174 timeline.set_time_codes_per_second(STAGE_FPS)
175
176
177# Create a PhysX scene to set the physics time step
178if USE_CUSTOM_PHYSX_FPS:
179 physx_scene = None
180 for prim in stage.Traverse():
181 if prim.IsA(UsdPhysics.Scene):
182 physx_scene = PhysxSchema.PhysxSceneAPI.Apply(prim)
183 break
184 if physx_scene is None:
185 physics_scene = UsdPhysics.Scene.Define(stage, "/PhysicsScene")
186 physx_scene = PhysxSchema.PhysxSceneAPI.Apply(stage.GetPrimAtPath("/PhysicsScene"))
187
188 # Time step for the physics simulation
189 # Default: 60.0
190 physx_scene.GetTimeStepsPerSecondAttr().Set(PHYSX_FPS)
191
192 # Minimum simulation frequency to prevent clamping; if the frame rate drops below this,
193 # physics steps are discarded to avoid app slowdown if the overall frame rate is too low.
194 # Default: 30.0
195 # NOTE: Matching `minFrameRate` with `TimeStepsPerSecond` ensures a single physics step per update.
196 carb.settings.get_settings().set("/persistent/simulation/minFrameRate", MIN_SIM_FPS)
197
198
199# Throttle Render/UI/Main thread update rate
200if LIMIT_APP_FPS:
201 # Enable rate limiting of the main run loop (UI, rendering, etc.)
202 # Default: False
203 carb.settings.get_settings().set("/app/runLoops/main/rateLimitEnabled", LIMIT_APP_FPS)
204
205 # FPS limit of the main run loop (UI, rendering, etc.)
206 # Default: 120
207 # NOTE: disabled if `/app/player/useFixedTimeStepping` is False
208 carb.settings.get_settings().set("/app/runLoops/main/rateLimitFrequency", int(APP_FPS))
209
210
211# Simulations can be selectively disabled (or toggled at specific times)
212if DISABLE_SIMULATIONS:
213 carb.settings.get_settings().set("/app/player/playSimulations", False)
214
215
216# Start the timeline
217timeline.set_current_time(0)
218timeline.set_end_time(MAX_DURATION + 1)
219timeline.set_looping(False)
220timeline.play()
221timeline.commit()
222wall_start_time = time.time()
223
224# Subscribe and cache various events for a limited duration
225timeline_events = []
226timeline_sub = timeline.get_timeline_event_stream().create_subscription_to_pop(on_timeline_event)
227physx_events = []
228physx_sub = omni.physx.get_physx_interface().subscribe_physics_step_events(on_physics_step)
229stage_render_events = []
230stage_render_sub = omni.usd.get_context().get_rendering_event_stream().create_subscription_to_pop(on_stage_render_event)
231app_update_events = []
232app_sub = omni.kit.app.get_app().get_update_event_stream().create_subscription_to_pop(on_app_update)
233
234# Keep the simulation running until the duration is passed
235while simulation_app.is_running():
236 if time.time() - wall_start_time > MAX_DURATION + 0.1:
237 break
238 simulation_app.update()
239
240simulation_app.close()
Subscribers and Events at Custom FPS
1import asyncio
2import time
3
4import carb.events
5import carb.settings
6import omni.kit.app
7import omni.physx
8import omni.timeline
9import omni.usd
10from pxr import PhysxSchema, UsdPhysics
11
12# TIMELINE / STAGE
13USE_CUSTOM_TIMELINE_SETTINGS = False
14USE_FIXED_TIME_STEPPING = False
15PLAY_EVERY_FRAME = True
16PLAY_DELAY_COMPENSATION = 0.0
17SUBSAMPLE_RATE = 1
18STAGE_FPS = 30.0
19
20# PHYSX
21USE_CUSTOM_PHYSX_FPS = False
22PHYSX_FPS = 60.0
23MIN_SIM_FPS = 30
24
25# Simulations can also be enabled/disabled at runtime
26DISABLE_SIMULATIONS = False
27
28# APP / RENDER
29LIMIT_APP_FPS = False
30APP_FPS = 120
31
32# Duration after which to clear subscribers and print the cached events
33MAX_DURATION = 3.0
34PRINT_EVENTS = False
35
36
37def on_timeline_event(event: omni.timeline.TimelineEventType):
38 global timeline_sub
39 global timeline_events
40 global wall_start_time
41 elapsed_wall_time = time.time() - wall_start_time
42
43 # Cache only time advance events
44 if event.type == omni.timeline.TimelineEventType.CURRENT_TIME_TICKED.value:
45 event_name = omni.timeline.TimelineEventType(event.type).name
46 event_payload = event.payload
47 timeline_events.append((elapsed_wall_time, event_name, event_payload))
48
49 # Clear subscriber and print cached events
50 if elapsed_wall_time > MAX_DURATION:
51 if timeline_sub is not None:
52 timeline_sub.unsubscribe()
53 timeline_sub = None
54 num_events = len(timeline_events)
55 fps = num_events / MAX_DURATION
56 print(f"[timeline] captured {num_events} events with aprox {fps} FPS")
57 if PRINT_EVENTS:
58 for i, (wall_time, event_name, payload) in enumerate(timeline_events):
59 print(f"\t[timeline][{i}]\ttime={wall_time:.4f};\tevent={event_name};\tpayload={payload}")
60
61
62def on_physics_step(dt: float):
63 global physx_events
64 global wall_start_time
65 elapsed_wall_time = time.time() - wall_start_time
66
67 # Cache physics events
68 physx_events.append((elapsed_wall_time, dt))
69
70 # Clear subscriber and print cached events
71 if elapsed_wall_time > MAX_DURATION:
72 # Physics unsubscription needs to be defered from the callback function
73 # see: '[Error] [omni.physx.plugin] Subscription cannot be changed during the event call'
74 async def clear_physx_sub_async():
75 global physx_sub
76 if physx_sub is not None:
77 physx_sub.unsubscribe()
78 physx_sub = None
79
80 asyncio.ensure_future(clear_physx_sub_async())
81 num_events = len(physx_events)
82 fps = num_events / MAX_DURATION
83 print(f"[physics] captured {num_events} events with aprox {fps} FPS")
84 if PRINT_EVENTS:
85 for i, (wall_time, dt) in enumerate(physx_events):
86 print(f"\t[physics][{i}]\ttime={wall_time:.4f};\tdt={dt};")
87
88
89def on_stage_render_event(event: omni.usd.StageRenderingEventType):
90 global stage_render_sub
91 global stage_render_events
92 global wall_start_time
93 elapsed_wall_time = time.time() - wall_start_time
94
95 event_name = omni.usd.StageRenderingEventType(event.type).name
96 event_payload = event.payload
97 stage_render_events.append((elapsed_wall_time, event_name, event_payload))
98
99 if elapsed_wall_time > MAX_DURATION:
100 if stage_render_sub is not None:
101 stage_render_sub.unsubscribe()
102 stage_render_sub = None
103 num_events = len(stage_render_events)
104 fps = num_events / MAX_DURATION
105 print(f"[stage render] captured {num_events} events with aprox {fps} FPS")
106 if PRINT_EVENTS:
107 for i, (wall_time, event_name, payload) in enumerate(stage_render_events):
108 print(f"\t[stage render][{i}]\ttime={wall_time:.4f};\tevent={event_name};\tpayload={payload}")
109
110
111def on_app_update(event: carb.events.IEvent):
112 global app_sub
113 global app_update_events
114 global wall_start_time
115 elapsed_wall_time = time.time() - wall_start_time
116
117 event_type = event.type
118 event_payload = event.payload
119 app_update_events.append((elapsed_wall_time, event_type, event_payload))
120
121 if elapsed_wall_time > MAX_DURATION:
122 if app_sub is not None:
123 app_sub.unsubscribe()
124 app_sub = None
125 num_events = len(app_update_events)
126 fps = num_events / MAX_DURATION
127 print(f"[app] captured {num_events} events with aprox {fps} FPS")
128 if PRINT_EVENTS:
129 for i, (wall_time, event_type, payload) in enumerate(app_update_events):
130 print(f"\t[app][{i}]\ttime={wall_time:.4f};\tevent={event_type};\tpayload={payload}")
131
132
133stage = omni.usd.get_context().get_stage()
134timeline = omni.timeline.get_timeline_interface()
135
136
137if USE_CUSTOM_TIMELINE_SETTINGS:
138 # Ideal to make simulation and animation synchronized.
139 # Default: True in editor, False in standalone.
140 # NOTE:
141 # - It may limit the frame rate (see 'timeline.set_play_every_frame') such that the elapsed wall clock time matches the frame's delta time.
142 # - If the app runs slower than this, animation playback may slow down (see 'CompensatePlayDelayInSecs').
143 # - For performance benchmarks, turn this off or set a very high target in `timeline.set_target_framerate`
144 carb.settings.get_settings().set("/app/player/useFixedTimeStepping", USE_FIXED_TIME_STEPPING)
145
146 # This compensates for frames that require more computation time than the frame's fixed delta time, by temporarily speeding up playback.
147 # The parameter represents the length of these "faster" playback periods, which means that it must be larger than the fixed frame time to take effect.
148 # Default: 0.0
149 # NOTE:
150 # - only effective if `useFixedTimeStepping` is set to True
151 # - setting a large value results in long fast playback after a huge lag spike
152 carb.settings.get_settings().set("/app/player/CompensatePlayDelayInSecs", PLAY_DELAY_COMPENSATION)
153
154 # If set to True, no frames are skipped and in every frame time advances by `1 / TimeCodesPerSecond`.
155 # Default: False
156 # NOTE:
157 # - only effective if `useFixedTimeStepping` is set to True
158 # - simulation is usually faster than real-time and processing is only limited by the frame rate of the runloop
159 # - useful for recording
160 # - same as `carb.settings.get_settings().set("/app/player/useFastMode", PLAY_EVERY_FRAME)`
161 timeline.set_play_every_frame(PLAY_EVERY_FRAME)
162
163 # Timeline sub-stepping, i.e. how many times updates are called (update events are dispatched) each frame.
164 # Default: 1
165 # NOTE: same as `carb.settings.get_settings().set("/app/player/timelineSubsampleRate", SUBSAMPLE_RATE)`
166 timeline.set_ticks_per_frame(SUBSAMPLE_RATE)
167
168 # Time codes per second for the stage
169 # NOTE: same as `stage.SetTimeCodesPerSecond(STAGE_FPS)` and `carb.settings.get_settings().set("/app/stage/timeCodesPerSecond", STAGE_FPS)`
170 timeline.set_time_codes_per_second(STAGE_FPS)
171
172
173# Create a PhysX scene to set the physics time step
174if USE_CUSTOM_PHYSX_FPS:
175 physx_scene = None
176 for prim in stage.Traverse():
177 if prim.IsA(UsdPhysics.Scene):
178 physx_scene = PhysxSchema.PhysxSceneAPI.Apply(prim)
179 break
180 if physx_scene is None:
181 physics_scene = UsdPhysics.Scene.Define(stage, "/PhysicsScene")
182 physx_scene = PhysxSchema.PhysxSceneAPI.Apply(stage.GetPrimAtPath("/PhysicsScene"))
183
184 # Time step for the physics simulation
185 # Default: 60.0
186 physx_scene.GetTimeStepsPerSecondAttr().Set(PHYSX_FPS)
187
188 # Minimum simulation frequency to prevent clamping; if the frame rate drops below this,
189 # physics steps are discarded to avoid app slowdown if the overall frame rate is too low.
190 # Default: 30.0
191 # NOTE: Matching `minFrameRate` with `TimeStepsPerSecond` ensures a single physics step per update.
192 carb.settings.get_settings().set("/persistent/simulation/minFrameRate", MIN_SIM_FPS)
193
194
195# Throttle Render/UI/Main thread update rate
196if LIMIT_APP_FPS:
197 # Enable rate limiting of the main run loop (UI, rendering, etc.)
198 # Default: False
199 carb.settings.get_settings().set("/app/runLoops/main/rateLimitEnabled", LIMIT_APP_FPS)
200
201 # FPS limit of the main run loop (UI, rendering, etc.)
202 # Default: 120
203 # NOTE: disabled if `/app/player/useFixedTimeStepping` is False
204 carb.settings.get_settings().set("/app/runLoops/main/rateLimitFrequency", int(APP_FPS))
205
206
207# Simulations can be selectively disabled (or toggled at specific times)
208if DISABLE_SIMULATIONS:
209 carb.settings.get_settings().set("/app/player/playSimulations", False)
210
211
212# Start the timeline
213timeline.set_current_time(0)
214timeline.set_end_time(MAX_DURATION + 1)
215timeline.set_looping(False)
216timeline.play()
217timeline.commit()
218wall_start_time = time.time()
219
220# Subscribe and cache various events for a limited duration
221timeline_events = []
222timeline_sub = timeline.get_timeline_event_stream().create_subscription_to_pop(on_timeline_event)
223physx_events = []
224physx_sub = omni.physx.get_physx_interface().subscribe_physics_step_events(on_physics_step)
225stage_render_events = []
226stage_render_sub = omni.usd.get_context().get_rendering_event_stream().create_subscription_to_pop(on_stage_render_event)
227app_update_events = []
228app_sub = omni.kit.app.get_app().get_update_event_stream().create_subscription_to_pop(on_app_update)
Accessing Writer and Annotator Data at Custom FPS#
Example of how to trigger a writer and access annotator data at a custom FPS, with product rendering disabled when the data is not needed. The standalone example can also be found at: standalone_examples/api/omni.replicator.isaac/custom_fps_writer_annotator.py
.
Note
It is currently not possible to change timeline (stage) FPS after the replicator graph creation as it causes a graph reset. This issue is being addressed. As a workaround make sure you are setting the timeline (stage) parameters before creating the replicator graph.
Accessing Writer and Annotator Data at Custom FPS
1from isaacsim import SimulationApp
2
3simulation_app = SimulationApp({"headless": False})
4
5import os
6
7import carb.settings
8import omni.kit.app
9import omni.replicator.core as rep
10import omni.timeline
11import omni.usd
12
13# NOTE: To avoid FPS delta misses make sure the sensor framerate is divisible by the timeline framerate
14STAGE_FPS = 60.0
15SENSOR_FPS = 10.0
16SENSOR_DT = 1.0 / SENSOR_FPS
17
18
19def run_custom_fps_example(num_frames=10):
20 # Create a new stage
21 omni.usd.get_context().new_stage()
22
23 # Disable capture on play (data will only be accessed at custom times)
24 carb.settings.get_settings().set("/omni/replicator/captureOnPlay", False)
25
26 # Set the timeline parameters
27 timeline = omni.timeline.get_timeline_interface()
28 timeline.set_looping(False)
29 timeline.set_current_time(0.0)
30 timeline.set_end_time(10)
31 timeline.set_time_codes_per_second(STAGE_FPS)
32 timeline.play()
33 timeline.commit()
34
35 # Create a light and a semantically annotated cube
36 rep.create.light()
37 rep.create.cube(semantics=[("class", "cube")])
38
39 # Create a render product and disable it (it will re-enabled when data is needed)
40 rp = rep.create.render_product("/OmniverseKit_Persp", (512, 512), name="rp")
41 rp.hydra_texture.set_updates_enabled(False)
42
43 # Create a writer and an annotator as different ways to access the data
44 out_dir_rgb = os.getcwd() + "/_out_writer_fps_rgb"
45 print(f"Writer data will be written to: {out_dir_rgb}")
46 writer_rgb = rep.WriterRegistry.get("BasicWriter")
47 writer_rgb.initialize(output_dir=out_dir_rgb, rgb=True)
48 # NOTE: 'trigger=None' is needed to make sure the writer is only triggered at the custom schedule times
49 writer_rgb.attach(rp, trigger=None)
50 annot_depth = rep.AnnotatorRegistry.get_annotator("distance_to_camera")
51 annot_depth.attach(rp)
52
53 # Run the simulation for the given number of frames and access the data at the desired framerates
54 previous_time = timeline.get_current_time()
55 elapsed_time = 0.0
56 for i in range(num_frames):
57 current_time = timeline.get_current_time()
58 elapsed_time += current_time - previous_time
59 print(f"[{i}] current_time={current_time:.4f}, elapsed_time={elapsed_time:.4f}/{SENSOR_DT:.4f};")
60
61 # Check if enough time has passed to trigger the sensor
62 if elapsed_time >= SENSOR_DT:
63 # Reset the elapsed time with the difference to the optimal trigger time (when the timeline fps is not divisible by the sensor framerate)
64 elapsed_time = elapsed_time - SENSOR_DT
65
66 # Enable render products for data access
67 rp.hydra_texture.set_updates_enabled(True)
68
69 # Write will be scheduled at the next step call
70 writer_rgb.schedule_write()
71
72 # Step needs to be called after scheduling the write
73 rep.orchestrator.step(delta_time=0.0)
74
75 # After step, the annotator data is available and in sync with the stage
76 annot_data = annot_depth.get_data()
77 print(f"\tWriter triggered and annotator data shape={annot_data.shape};")
78
79 # Disable render products to avoid unnecessary rendering
80 rp.hydra_texture.set_updates_enabled(False)
81
82 # Restart the timeline if it has been paused by the replicator step function
83 if not timeline.is_playing():
84 timeline.play()
85
86 previous_time = current_time
87 # Advance the app (timeline) by one frame
88 simulation_app.update()
89
90 # Make sure the writer finishes writing the data to disk
91 rep.orchestrator.wait_until_complete()
92
93
94# Run the example for a given number of frames
95run_custom_fps_example(num_frames=50)
96
97# Close the application
98simulation_app.close()
Accessing Writer and Annotator Data at Custom FPS
1import asyncio
2import os
3
4import carb.settings
5import omni.kit.app
6import omni.replicator.core as rep
7import omni.timeline
8import omni.usd
9
10# NOTE: To avoid FPS delta misses make sure the sensor framerate is divisible by the timeline framerate
11STAGE_FPS = 60.0
12SENSOR_FPS = 10.0
13SENSOR_DT = 1.0 / SENSOR_FPS
14
15
16async def run_custom_fps_example_async(num_frames=10):
17 # Create a new stage
18 await omni.usd.get_context().new_stage_async()
19
20 # Disable capture on play (data will only be accessed at custom times)
21 carb.settings.get_settings().set("/omni/replicator/captureOnPlay", False)
22
23 # Set the timeline parameters
24 timeline = omni.timeline.get_timeline_interface()
25 timeline.set_looping(False)
26 timeline.set_current_time(0.0)
27 timeline.set_end_time(10)
28 timeline.set_time_codes_per_second(STAGE_FPS)
29 timeline.play()
30 timeline.commit()
31
32 # Create a light and a semantically annotated cube
33 rep.create.light()
34 rep.create.cube(semantics=[("class", "cube")])
35
36 # Create a render product and disable it (it will re-enabled when data is needed)
37 rp = rep.create.render_product("/OmniverseKit_Persp", (512, 512), name="rp")
38 rp.hydra_texture.set_updates_enabled(False)
39
40 # Create a writer and an annotator as different ways to access the data
41 out_dir_rgb = os.getcwd() + "/_out_writer_fps_rgb"
42 print(f"Writer data will be written to: {out_dir_rgb}")
43 writer_rgb = rep.WriterRegistry.get("BasicWriter")
44 writer_rgb.initialize(output_dir=out_dir_rgb, rgb=True)
45 # NOTE: 'trigger=None' is needed to make sure the writer is only triggered at the custom schedule times
46 writer_rgb.attach(rp, trigger=None)
47 annot_depth = rep.AnnotatorRegistry.get_annotator("distance_to_camera")
48 annot_depth.attach(rp)
49
50 # Run the simulation for the given number of frames and access the data at the desired framerates
51 previous_time = timeline.get_current_time()
52 elapsed_time = 0.0
53 for i in range(num_frames):
54 current_time = timeline.get_current_time()
55 elapsed_time += current_time - previous_time
56 print(f"[{i}] current_time={current_time:.4f}, elapsed_time={elapsed_time:.4f}/{SENSOR_DT:.4f};")
57
58 # Check if enough time has passed to trigger the sensor
59 if elapsed_time >= SENSOR_DT:
60 # Reset the elapsed time with the difference to the optimal trigger time (when the timeline fps is not divisible by the sensor framerate)
61 elapsed_time = elapsed_time - SENSOR_DT
62
63 # Enable render products for data access
64 rp.hydra_texture.set_updates_enabled(True)
65
66 # Write will be scheduled at the next step call
67 writer_rgb.schedule_write()
68
69 # Step needs to be called after scheduling the write
70 await rep.orchestrator.step_async(delta_time=0.0)
71
72 # After step, the annotator data is available and in sync with the stage
73 annot_data = annot_depth.get_data()
74 print(f"\tWriter triggered and annotator data shape={annot_data.shape};")
75
76 # Disable render products to avoid unnecessary rendering
77 rp.hydra_texture.set_updates_enabled(False)
78
79 # Restart the timeline if it has been paused by the replicator step function
80 if not timeline.is_playing():
81 timeline.play()
82
83 previous_time = current_time
84 # Advance the app (timeline) by one frame
85 await omni.kit.app.get_app().next_update_async()
86
87 # Make sure the writer finishes writing the data to disk
88 await rep.orchestrator.wait_until_complete_async()
89
90
91# Run the example for a given number of frames
92asyncio.ensure_future(run_custom_fps_example_async(num_frames=50))