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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ..._tasks/test/golden_images/dexsuite_kuka/newton-isaacsim_rtx_renderer-albedo.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .../dexsuite_kuka/newton-isaacsim_rtx_renderer-simple_shading_constant_diffuse.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ...mages/dexsuite_kuka/newton-isaacsim_rtx_renderer-simple_shading_diffuse_mdl.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ...n_images/dexsuite_kuka/newton-isaacsim_rtx_renderer-simple_shading_full_mdl.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ...b_tasks/test/golden_images/dexsuite_kuka/physx-isaacsim_rtx_renderer-albedo.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ...lab_tasks/test/golden_images/dexsuite_kuka/physx-isaacsim_rtx_renderer-rgba.png
100755 → 100644
Binary file modified ...s/dexsuite_kuka/physx-isaacsim_rtx_renderer-simple_shading_constant_diffuse.png
100755 → 100644
Binary file modified ...images/dexsuite_kuka/physx-isaacsim_rtx_renderer-simple_shading_diffuse_mdl.png
100755 → 100644
Binary file modified ...en_images/dexsuite_kuka/physx-isaacsim_rtx_renderer-simple_shading_full_mdl.png
100755 → 100644
Binary file modified ...clab_tasks/test/golden_images/shadow_hand/newton-isaacsim_rtx_renderer-rgba.png
100755 → 100644
Binary file modified ...den_images/shadow_hand/newton-isaacsim_rtx_renderer-simple_shading_full_mdl.png
100755 → 100644
Binary file modified ...lab_tasks/test/golden_images/shadow_hand/physx-isaacsim_rtx_renderer-albedo.png
100755 → 100644
Binary file modified ...aclab_tasks/test/golden_images/shadow_hand/physx-isaacsim_rtx_renderer-rgba.png
100755 → 100644
Binary file modified ...golden_images/shadow_hand/physx-isaacsim_rtx_renderer-semantic_segmentation.png
100755 → 100644
Binary file modified ...ges/shadow_hand/physx-isaacsim_rtx_renderer-simple_shading_constant_diffuse.png
100755 → 100644
Binary file modified ...n_images/shadow_hand/physx-isaacsim_rtx_renderer-simple_shading_diffuse_mdl.png
100755 → 100644
Binary file modified ...lden_images/shadow_hand/physx-isaacsim_rtx_renderer-simple_shading_full_mdl.png
100755 → 100644
62 changes: 30 additions & 32 deletions source/isaaclab_tasks/test/test_rendering_correctness.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
#
_PIXEL_L2_NORM_DIFFERENCE_THRESHOLD = 10.0

# The max percentage of pixels allowed to differ. If the percentage exceeds this value, the test will fail.
# The value is set case by case based on the screen space taken up by the env in camera output images. It
# needs to be large enough to tolerate minor rendering noise while small enough to catch unexpected changes.
_MAX_DIFFERENT_PIXELS_PERCENTAGE_BY_ENV_NAME = {
"cartpole": 1.0,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was 2.0 before

"shadow_hand": 3.0,
"dexsuite_kuka": 4.0,
}

_OVRTX_DISABLED = pytest.mark.skip(
reason="OVRTX is optional and experimental feature and temporarily is excluded from testing."
)
Expand All @@ -66,9 +75,6 @@
# "img_result_path": str | None, "img_golden_path": str | None}
_COMPARISON_SCORES: list[dict] = []

# Environment seed.
_ENV_SEED = 42


# ---------------------------------------------------------------------------
# Fixtures
Expand Down Expand Up @@ -643,7 +649,6 @@ def shadow_hand_env(request):
env_cfg = _apply_overrides_to_env_cfg(env_cfg, override_args)

env_cfg.scene.num_envs = 4
env_cfg.seed = _ENV_SEED

if data_type == "depth":
# Disable CNN forward pass as it cannot be meaningfully trained from depth alone and will raise a ValueError.
Expand All @@ -652,7 +657,6 @@ def shadow_hand_env(request):
env = None
try:
env = ShadowHandVisionEnv(env_cfg)
env.reset(seed=_ENV_SEED)
yield physics_backend, renderer, data_type, env
finally:
if env is not None:
Expand All @@ -662,13 +666,13 @@ def shadow_hand_env(request):
def test_shadow_hand(shadow_hand_env):
"""Camera output must contain at least one non-zero pixel (Shadow Hand vision env)."""
physics_backend, renderer, _, env = shadow_hand_env

test_name = "shadow_hand"
_validate_camera_outputs(
"shadow_hand",
test_name,
physics_backend,
renderer,
env._tiled_camera.data.output,
max_different_pixels_percentage=8.0,
max_different_pixels_percentage=_MAX_DIFFERENT_PIXELS_PERCENTAGE_BY_ENV_NAME[test_name],
)


Expand All @@ -691,12 +695,10 @@ def cartpole_env(request):
env_cfg = _apply_overrides_to_env_cfg(env_cfg, override_args)

env_cfg.scene.num_envs = 4
env_cfg.seed = _ENV_SEED

env = None
try:
env = CartpoleCameraEnv(env_cfg)
env.reset(seed=_ENV_SEED)
yield physics_backend, renderer, data_type, env
finally:
if env is not None:
Expand All @@ -706,13 +708,13 @@ def cartpole_env(request):
def test_cartpole(cartpole_env):
"""Camera output must contain at least one non-zero pixel (Cartpole camera env)."""
physics_backend, renderer, _, env = cartpole_env

test_name = "cartpole"
_validate_camera_outputs(
"cartpole",
test_name,
physics_backend,
renderer,
env._tiled_camera.data.output,
max_different_pixels_percentage=2.0,
max_different_pixels_percentage=_MAX_DIFFERENT_PIXELS_PERCENTAGE_BY_ENV_NAME[test_name],
)


Expand All @@ -739,12 +741,10 @@ def dexsuite_kuka_allegro_lift_env(request):
env_cfg = _apply_overrides_to_env_cfg(env_cfg, override_args)

env_cfg.scene.num_envs = 4
env_cfg.seed = _ENV_SEED

env = None
try:
env = ManagerBasedRLEnv(env_cfg)
env.reset(seed=_ENV_SEED)
yield physics_backend, renderer, data_type, env
finally:
if env is not None:
Expand All @@ -754,13 +754,13 @@ def dexsuite_kuka_allegro_lift_env(request):
def test_dexsuite_kuka_allegro_lift(dexsuite_kuka_allegro_lift_env):
"""Camera output must contain at least one non-zero pixel (Dexsuite Kuka-Allegro Lift, single camera)."""
physics_backend, renderer, _, env = dexsuite_kuka_allegro_lift_env

test_name = "dexsuite_kuka"
_validate_camera_outputs(
"dexsuite_kuka",
test_name,
physics_backend,
renderer,
env.scene.sensors["base_camera"].data.output,
max_different_pixels_percentage=10.0,
max_different_pixels_percentage=_MAX_DIFFERENT_PIXELS_PERCENTAGE_BY_ENV_NAME[test_name],
)


Expand All @@ -769,34 +769,32 @@ def test_dexsuite_kuka_allegro_lift(dexsuite_kuka_allegro_lift_env):
# ---------------------------------------------------------------------------

# Task IDs that expose camera/tiled_camera image observations; each is validated for non-blank rendering.
# The max different pixels percentage is set based on the screen space taken up by the env.
_RENDER_CORRECTNESS_TASK_IDS = [
"Isaac-Cartpole-Albedo-Camera-Direct-v0",
"Isaac-Cartpole-Camera-Presets-Direct-v0",
"Isaac-Cartpole-Depth-Camera-Direct-v0",
"Isaac-Cartpole-RGB-Camera-Direct-v0",
"Isaac-Cartpole-SimpleShading-Constant-Camera-Direct-v0",
"Isaac-Cartpole-SimpleShading-Diffuse-Camera-Direct-v0",
"Isaac-Cartpole-SimpleShading-Full-Camera-Direct-v0",
"Isaac-Repose-Cube-Shadow-Vision-Direct-v0",
("Isaac-Cartpole-Albedo-Camera-Direct-v0", "cartpole"),
("Isaac-Cartpole-Camera-Presets-Direct-v0", "cartpole"),
("Isaac-Cartpole-Depth-Camera-Direct-v0", "cartpole"),
("Isaac-Cartpole-RGB-Camera-Direct-v0", "cartpole"),
("Isaac-Cartpole-SimpleShading-Constant-Camera-Direct-v0", "cartpole"),
("Isaac-Cartpole-SimpleShading-Diffuse-Camera-Direct-v0", "cartpole"),
("Isaac-Cartpole-SimpleShading-Full-Camera-Direct-v0", "cartpole"),
("Isaac-Repose-Cube-Shadow-Vision-Direct-v0", "shadow_hand"),
]


@pytest.mark.parametrize("task_id", _RENDER_CORRECTNESS_TASK_IDS)
def test_registered_tasks(task_id):
@pytest.mark.parametrize("task_id, env_name", _RENDER_CORRECTNESS_TASK_IDS)
def test_registered_tasks(task_id, env_name):
"""Camera output must be non-empty for each registered task with camera-based observations."""
env = None
try:
env_cfg = parse_env_cfg(task_id, num_envs=4)
env_cfg.seed = _ENV_SEED

env = gym.make(task_id, cfg=env_cfg)
unwrapped: Any = env.unwrapped
sim = getattr(unwrapped, "sim", None)
if sim is not None:
sim._app_control_on_stop_handle = None

env.reset(seed=_ENV_SEED)

camera_outputs_nested_dict = _collect_camera_outputs(env)
num_camera_outputs = len(camera_outputs_nested_dict)
Comment on lines 798 to 799
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Camera data may be empty without a prior reset

gym.make() constructs the environment but, per the Gymnasium contract, sensor/observation data is not guaranteed to be populated until the first reset() call. If Isaac Sim defers filling camera buffers to reset(), then _collect_camera_outputs will return an empty dict and the assertion on line 794 (assert num_camera_outputs == 1) will fail with a confusing message rather than indicating the root cause.

The same pattern was removed from the other three env fixtures (shadow_hand_env, cartpole_env, dexsuite_kuka_allegro_lift_env). If those fixtures also rely on camera data being populated at construction time, any future environment that defers rendering until reset() will silently break here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fine. we will check whether camera outputs exist and contain non-zero pixels inside _validate_camera_outputs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Camera output buffers will be updated on invocation of Camera.data():

    @property
    def data(self) -> CameraData:
        # update sensors if needed
        self._update_outdated_buffers()
        # return the data
        return self._data

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks for clarifying! I see now that _validate_camera_outputs has the assertion assert len(camera_outputs) > 0 plus the per-tensor non-zero check, so empty or blank outputs will fail clearly rather than silently passing. That addresses my concern. 👍

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the clarification! You're right — the Camera.data property's lazy update via _update_outdated_buffers() ensures buffers are populated on access, making the explicit reset() unnecessary for camera data retrieval in this context.

This is a nice pattern that decouples buffer population from the reset lifecycle. My concern about the Gymnasium contract was misplaced here since Isaac Lab's camera sensor implementation handles this internally. 👍

assert num_camera_outputs == 1, f"[{task_id}] Expected 1 camera output, got {num_camera_outputs}."
Expand All @@ -808,7 +806,7 @@ def test_registered_tasks(task_id):
"default_physics",
"default_renderer",
camera_outputs,
max_different_pixels_percentage=5.0,
max_different_pixels_percentage=_MAX_DIFFERENT_PIXELS_PERCENTAGE_BY_ENV_NAME[env_name],
)
finally:
if env is not None:
Expand Down
Loading