Commit f09f8645 authored by Kelly Guo's avatar Kelly Guo Committed by David Hoeller

Fixes tiled rendering limitations and frame offset issue (#113)

# Description

This fix removes the limitations of tiled rendering, allowing for
non-square resolutions and non-perfect square number of
tiles/environments. In addition, for small resolutions (width or height
<265), DLAA denoiser is used by default as a workaround to avoid frame
offset issues. An additional `rerender_on_reset` flag is added to the
environment configs to allow for an extra `render` call in reset APIs to
make sure sensor observations are updated after reset.

## Type of change

- Bug fix (non-breaking change which fixes an issue)

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
parent ac7fafd9
......@@ -60,21 +60,6 @@ environment. For example:
python source/standalone/workflows/rl_games/train.py --task=Isaac-Cartpole-RGB-Camera-Direct-v0 --headless --enable_cameras
.. warning::
There are currently a few limitations with tiled rendering:
* Number of cameras must be a perfect square
* Tile resolution must be a square
* Due to upsampling in the denoising process, image quality may appear different when running with different numbers of cameras.
To overcome this issue, we can use the DLAA denoiser at the cost of some performance.
.. code-block:: python
import omni.replicator.core as rep
rep.settings.set_render_rtx_realtime(antialiasing="DLAA")
Annotators and Data Types
^^^^^^^^^^^^^^^^^^^^^^^^^
......
......@@ -57,6 +57,9 @@ exts."omni.renderer.core".present.enabled=false
rtx.raytracing.cached.enabled = false
rtx.ambientOcclusion.enabled = false
# Set the DLSS model
rtx.post.dlss.execMode = 0 # can be 0 (Performance), 1 (Balanced), 2 (Quality), or 3 (Auto)
# Avoids unnecessary GPU context initialization
renderer.multiGpu.maxGpuCount=1
......
......@@ -206,8 +206,8 @@ app.runLoops.rendering_1.rateLimitUsePrecisionSleep = true
app.runLoops.rendering_1.syncToPresent = false
app.runLoopsGlobal.syncToPresent = false
app.vsync = false
exts.omni.kit.renderer.core.present.enabled = false
exts.omni.kit.renderer.core.present.presentAfterRendering = false
exts."omni.kit.renderer.core".present.enabled = false
exts."omni.kit.renderer.core".present.presentAfterRendering = false
persistent.app.viewport.defaults.tickRate = 120
rtx-transient.dlssg.enabled = false
......
......@@ -57,6 +57,9 @@ exts."omni.renderer.core".present.enabled=false
rtx.raytracing.cached.enabled = false
rtx.ambientOcclusion.enabled = false
# Set the DLSS model
rtx.post.dlss.execMode = 0 # can be 0 (Performance), 1 (Balanced), 2 (Quality), or 3 (Auto)
# Avoids unnecessary GPU context initialization
renderer.multiGpu.maxGpuCount=1
......
......@@ -529,6 +529,10 @@ class DirectRLEnv(gym.Env):
"""
self.scene.reset(env_ids)
# if sensors are added to the scene, make sure we render to reflect changes in reset
if len(env_ids) > 0 and self.sim.has_rtx_sensors() and self.cfg.rerender_on_reset:
self.sim.render()
# apply events such as randomization for environments that need a reset
if self.cfg.events:
if "reset" in self.event_manager.available_modes:
......
......@@ -127,3 +127,14 @@ class DirectRLEnvCfg:
Please refer to the :class:`omni.isaac.lab.utils.noise.NoiseModel` class for more details.
"""
rerender_on_reset: bool = False
"""Whether a render step is performed again after at least one environment has been reset.
Defaults to False, which means no render step will be performed after reset.
* When this is False, data collected from sensors after performing reset will be stale and will not reflect the
latest states in simulation caused by the reset.
* When this is True, an extra render step will be performed to update the sensor data
to reflect the latest states from the reset. This comes at a cost of performance as an additional render
step will be performed after each time an environment is reset.
"""
......@@ -350,6 +350,9 @@ class ManagerBasedEnv:
"""
# reset the internal buffers of the scene elements
self.scene.reset(env_ids)
# if sensors are added to the scene, make sure we render to reflect changes in reset
if len(env_ids) > 0 and self.sim.has_rtx_sensors() and self.cfg.rerender_on_reset:
self.sim.render()
# apply events such as randomization for environments that need a reset
if "reset" in self.event_manager.available_modes:
env_step_count = self._sim_step_counter // self.cfg.decimation
......
......@@ -95,3 +95,14 @@ class ManagerBasedEnvCfg:
Please refer to the :class:`omni.isaac.lab.managers.EventManager` class for more details.
"""
rerender_on_reset: bool = False
"""Whether a render step is performed again after at least one environment has been reset.
Defaults to False, which means no render step will be performed after reset.
* When this is False, data collected from sensors after performing reset will be stale and will not reflect the
latest states in simulation caused by the reset.
* When this is True, an extra render step will be performed to update the sensor data
to reflect the latest states from the reset. This comes at a cost of performance as an additional render
step will be performed after each time an environment is reset.
"""
......@@ -319,6 +319,9 @@ class ManagerBasedRLEnv(ManagerBasedEnv, gym.Env):
self.curriculum_manager.compute(env_ids=env_ids)
# reset the internal buffers of the scene elements
self.scene.reset(env_ids)
# if sensors are added to the scene, make sure we render to reflect changes in reset
if len(env_ids) > 0 and self.sim.has_rtx_sensors() and self.cfg.rerender_on_reset:
self.sim.render()
# apply events such as randomizations for environments that need a reset
if "reset" in self.event_manager.available_modes:
env_step_count = self._sim_step_counter // self.cfg.decimation
......
......@@ -398,6 +398,10 @@ class Camera(SensorBase):
f" the number of environments ({self._num_envs})."
)
# WAR: use DLAA antialiasing to avoid frame offset issue at small resolutions
if self.cfg.width < 265 or self.cfg.height < 265:
rep.settings.set_render_rtx_realtime(antialiasing="DLAA")
# Create all env_ids buffer
self._ALL_INDICES = torch.arange(self._view.count, device=self._device, dtype=torch.long)
# Create frame count buffer
......
......@@ -189,6 +189,10 @@ class TiledCamera(Camera):
)
self._render_product_paths = [rp.path]
# WAR: use DLAA antialiasing to avoid frame offset issue at small resolutions
if self._tiling_grid_shape()[0] * self.cfg.width < 265 or self._tiling_grid_shape()[1] * self.cfg.height < 265:
rep.settings.set_render_rtx_realtime(antialiasing="DLAA")
# Define the annotators based on requested data types
self._annotators = dict()
for annotator_type in self.cfg.data_types:
......@@ -377,7 +381,7 @@ class TiledCamera(Camera):
def _tiling_grid_shape(self) -> tuple[int, int]:
"""Returns a tuple containing the tiling grid dimension."""
cols = round(math.sqrt(self._view.count))
cols = math.ceil(math.sqrt(self._view.count))
rows = math.ceil(self._view.count / cols)
return (cols, rows)
......
......@@ -518,6 +518,71 @@ class TestCamera(unittest.TestCase):
self.assertEqual(output["instance_segmentation_fast"].dtype, torch.int32)
self.assertEqual(output["instance_id_segmentation_fast"].dtype, torch.int32)
def test_camera_large_resolution_all_colorize(self):
"""Test camera resolution is correctly set for all types with colorization enabled."""
# Add all types
camera_cfg = copy.deepcopy(self.camera_cfg)
camera_cfg.data_types = [
"rgb",
"rgba",
"depth",
"distance_to_camera",
"distance_to_image_plane",
"normals",
"motion_vectors",
"semantic_segmentation",
"instance_segmentation_fast",
"instance_id_segmentation_fast",
]
camera_cfg.colorize_instance_id_segmentation = True
camera_cfg.colorize_instance_segmentation = True
camera_cfg.colorize_semantic_segmentation = True
camera_cfg.width = 512
camera_cfg.height = 512
# Create camera
camera = Camera(camera_cfg)
# Play sim
self.sim.reset()
# Simulate for a few steps
# note: This is a workaround to ensure that the textures are loaded.
# Check "Known Issues" section in the documentation for more details.
for _ in range(5):
self.sim.step()
camera.update(self.dt)
# expected sizes
hw_1c_shape = (1, camera_cfg.height, camera_cfg.width, 1)
hw_2c_shape = (1, camera_cfg.height, camera_cfg.width, 2)
hw_3c_shape = (1, camera_cfg.height, camera_cfg.width, 3)
hw_4c_shape = (1, camera_cfg.height, camera_cfg.width, 4)
# access image data and compare shapes
output = camera.data.output
self.assertEqual(output["rgb"].shape, hw_3c_shape)
self.assertEqual(output["rgba"].shape, hw_4c_shape)
self.assertEqual(output["depth"].shape, hw_1c_shape)
self.assertEqual(output["distance_to_camera"].shape, hw_1c_shape)
self.assertEqual(output["distance_to_image_plane"].shape, hw_1c_shape)
self.assertEqual(output["normals"].shape, hw_3c_shape)
self.assertEqual(output["motion_vectors"].shape, hw_2c_shape)
self.assertEqual(output["semantic_segmentation"].shape, hw_4c_shape)
self.assertEqual(output["instance_segmentation_fast"].shape, hw_4c_shape)
self.assertEqual(output["instance_id_segmentation_fast"].shape, hw_4c_shape)
# access image data and compare dtype
output = camera.data.output
self.assertEqual(output["rgb"].dtype, torch.uint8)
self.assertEqual(output["rgba"].dtype, torch.uint8)
self.assertEqual(output["depth"].dtype, torch.float)
self.assertEqual(output["distance_to_camera"].dtype, torch.float)
self.assertEqual(output["distance_to_image_plane"].dtype, torch.float)
self.assertEqual(output["normals"].dtype, torch.float)
self.assertEqual(output["motion_vectors"].dtype, torch.float)
self.assertEqual(output["semantic_segmentation"].dtype, torch.uint8)
self.assertEqual(output["instance_segmentation_fast"].dtype, torch.uint8)
self.assertEqual(output["instance_id_segmentation_fast"].dtype, torch.uint8)
def test_camera_resolution_rgb_only(self):
"""Test camera resolution is correctly set for RGB only."""
# Add all types
......
......@@ -123,7 +123,7 @@ class TestTiledCamera(unittest.TestCase):
def test_multi_camera_init(self):
"""Test multi-camera initialization."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -176,7 +176,7 @@ class TestTiledCamera(unittest.TestCase):
def test_rgb_only_camera(self):
"""Test initialization with only RGB."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -254,7 +254,7 @@ class TestTiledCamera(unittest.TestCase):
def test_depth_only_camera(self):
"""Test initialization with only depth."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -306,7 +306,7 @@ class TestTiledCamera(unittest.TestCase):
def test_rgba_only_camera(self):
"""Test initialization with only RGBA."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -358,7 +358,7 @@ class TestTiledCamera(unittest.TestCase):
def test_distance_to_camera_only_camera(self):
"""Test initialization with only distance_to_camera."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -410,7 +410,7 @@ class TestTiledCamera(unittest.TestCase):
def test_distance_to_image_plane_only_camera(self):
"""Test initialization with only distance_to_image_plane."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -462,7 +462,7 @@ class TestTiledCamera(unittest.TestCase):
def test_normals_only_camera(self):
"""Test initialization with only normals."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -514,7 +514,7 @@ class TestTiledCamera(unittest.TestCase):
def test_motion_vectors_only_camera(self):
"""Test initialization with only motion_vectors."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -566,7 +566,7 @@ class TestTiledCamera(unittest.TestCase):
def test_semantic_segmentation_colorize_only_camera(self):
"""Test initialization with only semantic_segmentation."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -619,7 +619,7 @@ class TestTiledCamera(unittest.TestCase):
def test_instance_segmentation_fast_colorize_only_camera(self):
"""Test initialization with only instance_segmentation_fast."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -672,7 +672,7 @@ class TestTiledCamera(unittest.TestCase):
def test_instance_id_segmentation_fast_colorize_only_camera(self):
"""Test initialization with only instance_id_segmentation_fast."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -725,7 +725,7 @@ class TestTiledCamera(unittest.TestCase):
def test_semantic_segmentation_non_colorize_only_camera(self):
"""Test initialization with only semantic_segmentation."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -780,7 +780,7 @@ class TestTiledCamera(unittest.TestCase):
def test_instance_segmentation_fast_non_colorize_only_camera(self):
"""Test initialization with only instance_segmentation_fast."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -834,7 +834,7 @@ class TestTiledCamera(unittest.TestCase):
def test_instance_id_segmentation_fast_non_colorize_only_camera(self):
"""Test initialization with only instance_id_segmentation_fast."""
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -900,7 +900,201 @@ class TestTiledCamera(unittest.TestCase):
"instance_id_segmentation_fast",
]
num_cameras = 4
num_cameras = 9
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
# Create camera
camera_cfg = copy.deepcopy(self.camera_cfg)
camera_cfg.data_types = all_annotator_types
camera_cfg.prim_path = "/World/Origin_.*/CameraSensor"
camera = TiledCamera(camera_cfg)
# Check simulation parameter is set correctly
self.assertTrue(self.sim.has_rtx_sensors())
# Play sim
self.sim.reset()
# Check if camera is initialized
self.assertTrue(camera.is_initialized)
# Check if camera prim is set correctly and that it is a camera prim
self.assertEqual(camera._sensor_prims[1].GetPath().pathString, "/World/Origin_1/CameraSensor")
self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera)
self.assertListEqual(sorted(camera.data.output.keys()), sorted(all_annotator_types))
# Simulate for a few steps
# note: This is a workaround to ensure that the textures are loaded.
# Check "Known Issues" section in the documentation for more details.
for _ in range(5):
self.sim.step()
# Check buffers that exists and have correct shapes
self.assertEqual(camera.data.pos_w.shape, (num_cameras, 3))
self.assertEqual(camera.data.quat_w_ros.shape, (num_cameras, 4))
self.assertEqual(camera.data.quat_w_world.shape, (num_cameras, 4))
self.assertEqual(camera.data.quat_w_opengl.shape, (num_cameras, 4))
self.assertEqual(camera.data.intrinsic_matrices.shape, (num_cameras, 3, 3))
self.assertEqual(camera.data.image_shape, (self.camera_cfg.height, self.camera_cfg.width))
# Simulate physics
for _ in range(10):
# perform rendering
self.sim.step()
# update camera
camera.update(self.dt)
# check image data
for data_type, im_data in camera.data.output.to_dict().items():
if data_type in ["rgb", "normals"]:
self.assertEqual(im_data.shape, (num_cameras, self.camera_cfg.height, self.camera_cfg.width, 3))
elif data_type in [
"rgba",
"semantic_segmentation",
"instance_segmentation_fast",
"instance_id_segmentation_fast",
]:
self.assertEqual(im_data.shape, (num_cameras, self.camera_cfg.height, self.camera_cfg.width, 4))
for i in range(num_cameras):
self.assertGreater((im_data[i] / 255.0).mean().item(), 0.0)
elif data_type in ["motion_vectors"]:
self.assertEqual(im_data.shape, (num_cameras, self.camera_cfg.height, self.camera_cfg.width, 2))
for i in range(num_cameras):
self.assertGreater(im_data[i].mean().item(), 0.0)
elif data_type in ["depth", "distance_to_camera", "distance_to_image_plane"]:
self.assertEqual(im_data.shape, (num_cameras, self.camera_cfg.height, self.camera_cfg.width, 1))
for i in range(num_cameras):
self.assertGreater(im_data[i].mean().item(), 0.0)
# access image data and compare dtype
output = camera.data.output
info = camera.data.info
self.assertEqual(output["rgb"].dtype, torch.uint8)
self.assertEqual(output["rgba"].dtype, torch.uint8)
self.assertEqual(output["depth"].dtype, torch.float)
self.assertEqual(output["distance_to_camera"].dtype, torch.float)
self.assertEqual(output["distance_to_image_plane"].dtype, torch.float)
self.assertEqual(output["normals"].dtype, torch.float)
self.assertEqual(output["motion_vectors"].dtype, torch.float)
self.assertEqual(output["semantic_segmentation"].dtype, torch.uint8)
self.assertEqual(output["instance_segmentation_fast"].dtype, torch.uint8)
self.assertEqual(output["instance_id_segmentation_fast"].dtype, torch.uint8)
self.assertEqual(type(info["semantic_segmentation"]), dict)
self.assertEqual(type(info["instance_segmentation_fast"]), dict)
self.assertEqual(type(info["instance_id_segmentation_fast"]), dict)
del camera
def test_all_annotators_low_resolution_camera(self):
"""Test initialization with all supported annotators."""
all_annotator_types = [
"rgb",
"rgba",
"depth",
"distance_to_camera",
"distance_to_image_plane",
"normals",
"motion_vectors",
"semantic_segmentation",
"instance_segmentation_fast",
"instance_id_segmentation_fast",
]
num_cameras = 2
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
# Create camera
camera_cfg = copy.deepcopy(self.camera_cfg)
camera_cfg.height = 40
camera_cfg.width = 40
camera_cfg.data_types = all_annotator_types
camera_cfg.prim_path = "/World/Origin_.*/CameraSensor"
camera = TiledCamera(camera_cfg)
# Check simulation parameter is set correctly
self.assertTrue(self.sim.has_rtx_sensors())
# Play sim
self.sim.reset()
# Check if camera is initialized
self.assertTrue(camera.is_initialized)
# Check if camera prim is set correctly and that it is a camera prim
self.assertEqual(camera._sensor_prims[1].GetPath().pathString, "/World/Origin_1/CameraSensor")
self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera)
self.assertListEqual(sorted(camera.data.output.keys()), sorted(all_annotator_types))
# Simulate for a few steps
# note: This is a workaround to ensure that the textures are loaded.
# Check "Known Issues" section in the documentation for more details.
for _ in range(5):
self.sim.step()
# Check buffers that exists and have correct shapes
self.assertEqual(camera.data.pos_w.shape, (num_cameras, 3))
self.assertEqual(camera.data.quat_w_ros.shape, (num_cameras, 4))
self.assertEqual(camera.data.quat_w_world.shape, (num_cameras, 4))
self.assertEqual(camera.data.quat_w_opengl.shape, (num_cameras, 4))
self.assertEqual(camera.data.intrinsic_matrices.shape, (num_cameras, 3, 3))
self.assertEqual(camera.data.image_shape, (camera_cfg.height, camera_cfg.width))
# Simulate physics
for _ in range(10):
# perform rendering
self.sim.step()
# update camera
camera.update(self.dt)
# check image data
for data_type, im_data in camera.data.output.to_dict().items():
if data_type in ["rgb", "normals"]:
self.assertEqual(im_data.shape, (num_cameras, camera_cfg.height, camera_cfg.width, 3))
elif data_type in [
"rgba",
"semantic_segmentation",
"instance_segmentation_fast",
"instance_id_segmentation_fast",
]:
self.assertEqual(im_data.shape, (num_cameras, camera_cfg.height, camera_cfg.width, 4))
for i in range(num_cameras):
self.assertGreater((im_data[i] / 255.0).mean().item(), 0.0)
elif data_type in ["motion_vectors"]:
self.assertEqual(im_data.shape, (num_cameras, camera_cfg.height, camera_cfg.width, 2))
for i in range(num_cameras):
self.assertGreater(im_data[i].mean().item(), 0.0)
elif data_type in ["depth", "distance_to_camera", "distance_to_image_plane"]:
self.assertEqual(im_data.shape, (num_cameras, camera_cfg.height, camera_cfg.width, 1))
for i in range(num_cameras):
self.assertGreater(im_data[i].mean().item(), 0.0)
# access image data and compare dtype
output = camera.data.output
info = camera.data.info
self.assertEqual(output["rgb"].dtype, torch.uint8)
self.assertEqual(output["rgba"].dtype, torch.uint8)
self.assertEqual(output["depth"].dtype, torch.float)
self.assertEqual(output["distance_to_camera"].dtype, torch.float)
self.assertEqual(output["distance_to_image_plane"].dtype, torch.float)
self.assertEqual(output["normals"].dtype, torch.float)
self.assertEqual(output["motion_vectors"].dtype, torch.float)
self.assertEqual(output["semantic_segmentation"].dtype, torch.uint8)
self.assertEqual(output["instance_segmentation_fast"].dtype, torch.uint8)
self.assertEqual(output["instance_id_segmentation_fast"].dtype, torch.uint8)
self.assertEqual(type(info["semantic_segmentation"]), dict)
self.assertEqual(type(info["instance_segmentation_fast"]), dict)
self.assertEqual(type(info["instance_id_segmentation_fast"]), dict)
del camera
def test_all_annotators_non_perfect_square_number_camera(self):
"""Test initialization with all supported annotators."""
all_annotator_types = [
"rgb",
"rgba",
"depth",
"distance_to_camera",
"distance_to_image_plane",
"normals",
"motion_vectors",
"semantic_segmentation",
"instance_segmentation_fast",
"instance_id_segmentation_fast",
]
num_cameras = 11
for i in range(num_cameras):
prim_utils.create_prim(f"/World/Origin_{i}", "Xform")
......@@ -1054,7 +1248,7 @@ class TestTiledCamera(unittest.TestCase):
),
height=540,
width=960,
data_types=["distance_to_camera"],
data_types=["distance_to_image_plane"],
)
# set aperture offsets to 0, as currently not supported for usd/ tiled camera
......@@ -1080,7 +1274,7 @@ class TestTiledCamera(unittest.TestCase):
# filter nan and inf from output
cam_tiled_output = camera_tiled.data.output["depth"].clone()
cam_usd_output = camera_usd.data.output["distance_to_camera"].clone()
cam_usd_output = camera_usd.data.output["distance_to_image_plane"].clone()
cam_tiled_output[torch.isnan(cam_tiled_output)] = 0
cam_tiled_output[torch.isinf(cam_tiled_output)] = 0
cam_usd_output[torch.isnan(cam_usd_output)] = 0
......@@ -1100,19 +1294,12 @@ class TestTiledCamera(unittest.TestCase):
)
# check image data
# FIXME: The tiled camera output is not exactly equal to the usd camera output. This should be fixed with the
# update to the new tiled camera implementation. Test will fail, if the difference between the images
# disappears. Check again @pascal-roth.
# Intended test
# torch.testing.assert_close(
# cam_tiled_output[..., 0],
# cam_usd_output,
# atol=5e-5,
# rtol=5e-6,
# )
# current failure case
self.assertTrue(torch.max(torch.abs(cam_tiled_output[..., 0] - cam_usd_output)).item() > 1)
torch.testing.assert_close(
cam_tiled_output[..., 0],
cam_usd_output[..., 0],
atol=5e-5,
rtol=5e-6,
)
del camera_tiled
del camera_usd
......
# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Launch Isaac Sim Simulator first."""
import argparse
import sys
from omni.isaac.lab.app import AppLauncher, run_tests
# add argparse arguments
parser = argparse.ArgumentParser(
description=(
"Test Isaac-Cartpole-RGB-Camera-Direct-v0 environment with different resolutions and number of environments."
)
)
parser.add_argument("--save_images", action="store_true", default=False, help="Save out renders to file.")
parser.add_argument("unittest_args", nargs="*")
# parse the arguments
args_cli = parser.parse_args()
# set the sys.argv to the unittest_args
sys.argv[1:] = args_cli.unittest_args
# launch the simulator
app_launcher = AppLauncher(headless=True, enable_cameras=True)
simulation_app = app_launcher.app
"""Rest everything follows."""
import gymnasium as gym
import sys
import unittest
import omni.usd
from omni.isaac.lab.envs import DirectRLEnv, DirectRLEnvCfg, ManagerBasedRLEnv, ManagerBasedRLEnvCfg
from omni.isaac.lab.sensors import save_images_to_file
import omni.isaac.lab_tasks # noqa: F401
from omni.isaac.lab_tasks.utils.parse_cfg import parse_env_cfg
class TestTiledCameraCartpole(unittest.TestCase):
"""Test cases for all registered environments."""
@classmethod
def setUpClass(cls):
# acquire all Isaac environments names
cls.registered_tasks = list()
cls.registered_tasks.append("Isaac-Cartpole-RGB-Camera-Direct-v0")
print(">>> All registered environments:", cls.registered_tasks)
def test_tiled_resolutions_tiny(self):
"""Define settings for resolution and number of environments"""
num_envs = 1024
tile_widths = range(32, 48)
tile_heights = range(32, 48)
self._launch_tests(tile_widths, tile_heights, num_envs)
def test_tiled_resolutions_small(self):
"""Define settings for resolution and number of environments"""
num_envs = 300
tile_widths = range(128, 156)
tile_heights = range(128, 156)
self._launch_tests(tile_widths, tile_heights, num_envs)
def test_tiled_resolutions_medium(self):
"""Define settings for resolution and number of environments"""
num_envs = 64
tile_widths = range(320, 400, 20)
tile_heights = range(320, 400, 20)
self._launch_tests(tile_widths, tile_heights, num_envs)
def test_tiled_resolutions_large(self):
"""Define settings for resolution and number of environments"""
num_envs = 4
tile_widths = range(480, 640, 40)
tile_heights = range(480, 640, 40)
self._launch_tests(tile_widths, tile_heights, num_envs)
def test_tiled_resolutions_edge_cases(self):
"""Define settings for resolution and number of environments"""
num_envs = 1000
tile_widths = [12, 67, 93, 147]
tile_heights = [12, 67, 93, 147]
self._launch_tests(tile_widths, tile_heights, num_envs)
def test_tiled_num_envs_edge_cases(self):
"""Define settings for resolution and number of environments"""
num_envs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 53, 359, 733, 927]
tile_widths = [67, 93, 147]
tile_heights = [67, 93, 147]
for n_envs in num_envs:
self._launch_tests(tile_widths, tile_heights, n_envs)
"""
Helper functions.
"""
def _launch_tests(self, tile_widths: int, tile_heights: int, num_envs: int):
"""Run through different resolutions for tiled rendering"""
device = "cuda:0"
task_name = "Isaac-Cartpole-RGB-Camera-Direct-v0"
# iterate over all registered environments
for width in tile_widths:
for height in tile_heights:
with self.subTest(width=width, height=height):
# create a new stage
omni.usd.get_context().new_stage()
# parse configuration
env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg = parse_env_cfg(
task_name, device=device, num_envs=num_envs
)
env_cfg.tiled_camera.width = width
env_cfg.tiled_camera.height = height
print(f">>> Running test for resolution: {width} x {height}")
# check environment
self._run_environment(env_cfg)
# close the environment
print(f">>> Closing environment: {task_name}")
print("-" * 80)
def _run_environment(self, env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg):
"""Run environment and capture a rendered image."""
# create environment
env: ManagerBasedRLEnv | DirectRLEnv = gym.make("Isaac-Cartpole-RGB-Camera-Direct-v0", cfg=env_cfg)
# this flag is necessary to prevent a bug where the simulation gets stuck randomly when running the
# test on many environments.
env.sim.set_setting("/physics/cooking/ujitsoCollisionCooking", False)
# reset environment
obs, _ = env.reset()
# save image
if args_cli.save_images:
save_images_to_file(
obs["policy"] + 0.93,
f"output_{env.num_envs}_{env_cfg.tiled_camera.width}x{env_cfg.tiled_camera.height}.png",
)
# close the environment
env.close()
if __name__ == "__main__":
run_tests()
Changelog
---------
0.10.3 (2024-09-05)
~~~~~~~~~~~~~~~~~~~
Added
^^^^^
* Added environment config flag ``rerender_on_reset`` to allow updating sensor data after a reset.
0.10.2 (2024-08-23)
~~~~~~~~~~~~~~~~~~~
......
......@@ -14,4 +14,5 @@ TESTS_TO_SKIP = [
# lab_tasks
"test_data_collector.py", # Failing
"test_record_video.py", # Failing
"test_tiled_camera_env.py", # Need to improve the logic
]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment