Skip to content
Open
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
10 changes: 6 additions & 4 deletions source/isaaclab/isaaclab/sensors/camera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import isaaclab.utils.sensors as sensor_utils
from isaaclab.app.settings_manager import get_settings_manager
from isaaclab.renderers import BaseRenderer, Renderer
from isaaclab.sim.views import FrameView
from isaaclab.sim.views import UsdFrameView
from isaaclab.utils import has_kit, to_camel_case
from isaaclab.utils.math import (
convert_camera_frame_orientation_convention,
Expand Down Expand Up @@ -405,9 +405,11 @@ def _initialize_impl(self):
# references to prims located in the stage.
self._renderer.prepare_stage(self.stage, self._num_envs)

# Create a view for the sensor with Fabric enabled for fast pose queries.
# TODO: remove sync_usd_on_fabric_write=True once the GPU Fabric sync bug is fixed.
self._view = FrameView(self.cfg.prim_path, device=self._device, stage=self.stage, sync_usd_on_fabric_write=True)
# Camera uses UsdFrameView directly (not FrameView/FabricFrameView) because
# the RTX renderer / Replicator reads camera poses from USD prim paths, not
# from Fabric. Writing to Fabric + sync_usd_on_fabric_write was wasteful —
# this bypasses Fabric entirely for camera transforms.
self._view = UsdFrameView(self.cfg.prim_path, device=self._device, stage=self.stage)
# Check that sizes are correct
if self._view.count != self._num_envs:
raise RuntimeError(
Expand Down
3 changes: 1 addition & 2 deletions source/isaaclab/isaaclab/sim/views/usd_frame_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ def __init__(
stage: USD stage to search for prims. Defaults to None, in which case the current
active stage from the simulation context is used.
**kwargs: Additional keyword arguments (ignored). Allows forward-compatible
construction when callers pass backend-specific options like
``sync_usd_on_fabric_write``.
construction when callers pass backend-specific options.
Raises:
ValueError: If any matched prim is not Xformable or doesn't have standardized
Expand Down
76 changes: 76 additions & 0 deletions source/isaaclab/test/sensors/test_tiled_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,79 @@ def _populate_scene():
sim_utils.define_rigid_body_properties(prim_path, sim_utils.RigidBodyPropertiesCfg())
sim_utils.define_mass_properties(prim_path, sim_utils.MassPropertiesCfg(mass=5.0))
sim_utils.define_collision_properties(prim_path, sim_utils.CollisionPropertiesCfg())


# ------------------------------------------------------------------
# Camera pose → render validation (PrepareForReuse / Fabric path)
# ------------------------------------------------------------------


@pytest.mark.parametrize(
"device, camera_cls",
[
pytest.param("cpu", TiledCamera, id="cpu-tiled"),
pytest.param("cpu", Camera, id="cpu-non_tiled"),
pytest.param("cuda:0", TiledCamera, id="cuda:0-tiled"),
pytest.param("cuda:0", Camera, id="cuda:0-non_tiled"),
],
)
def test_camera_pose_update_reflected_in_render(setup_camera, device, camera_cls):
"""Camera pose changes via FrameView should be visible in rendered depth.

Moves camera close then far, renders depth, and verifies that the mean
valid depth from the far position is significantly larger (>1.5×) than
the close position. This validates that Fabric-side pose writes
(via PrepareForReuse) or USD writes are correctly propagated to the
RTX renderer.
"""
sim, _unused_cam_cfg, dt = setup_camera

cam_cfg = CameraCfg(
prim_path="/World/PoseTestCam",
height=128,
width=256,
update_period=0,
update_latest_camera_pose=True,
data_types=["distance_to_camera"],
spawn=sim_utils.PinholeCameraCfg(
focal_length=24.0,
focus_distance=400.0,
horizontal_aperture=20.955,
clipping_range=(0.1, 1.0e5),
),
)
camera = camera_cls(cam_cfg)
sim.reset()

target = torch.tensor([[0.0, 0.0, 0.0]], dtype=torch.float32, device=camera.device)
max_range = cam_cfg.spawn.clipping_range[1]

# -- close position --
eyes_close = torch.tensor([[2.0, 2.0, 2.0]], dtype=torch.float32, device=camera.device)
camera.set_world_poses_from_view(eyes_close, target)
sim.step()
camera.update(dt)
depth_close = camera.data.output["distance_to_camera"].clone()

# -- far position --
eyes_far = torch.tensor([[8.0, 8.0, 8.0]], dtype=torch.float32, device=camera.device)
camera.set_world_poses_from_view(eyes_far, target)
sim.step()
camera.update(dt)
depth_far = camera.data.output["distance_to_camera"].clone()

# -- validate --
valid_close = depth_close[depth_close < max_range]
valid_far = depth_far[depth_far < max_range]

assert valid_close.numel() > 0, "No valid close-range depth pixels"
assert valid_far.numel() > 0, "No valid far-range depth pixels"

mean_close = valid_close.mean().item()
mean_far = valid_far.mean().item()

assert mean_far > mean_close * 1.5, (
f"Far depth ({mean_far:.2f}) should be > 1.5× close depth ({mean_close:.2f}). "
"Camera pose change may not be reaching the renderer."
)
del camera
Loading