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
45 changes: 45 additions & 0 deletions docs/source/overview/core-concepts/sensors/camera.rst
Original file line number Diff line number Diff line change
Expand Up @@ -509,3 +509,48 @@ UNLABELLED instance ID (``1``) rather than a color value.

The ``idToLabels`` dict maps color to USD prim path. The ``idToSemantics`` dict maps color to
semantic label.

Background Color
~~~~~~~~~~~~~~~~

By default, pixels that do not intersect any geometry use the renderer's built-in background:
a dome-light sky on RTX backends, or a neutral gray on Newton Warp. You can override this with
a solid color by setting :attr:`~sensors.CameraCfg.background_color` on :class:`~sensors.CameraCfg`:

.. code-block:: python

from isaaclab.sensors import CameraCfg
import isaaclab.sim as sim_utils

tiled_camera = CameraCfg(
prim_path="/World/envs/env_.*/Camera",
data_types=["rgb"],
spawn=sim_utils.PinholeCameraCfg(
focal_length=24.0, focus_distance=400.0,
horizontal_aperture=20.955, clipping_range=(0.1, 20.0),
),
width=80,
height=80,
background_color=(0.0, 0.0, 0.0), # black background
)

The color is a 3-tuple of normalized RGB floats in ``[0, 1]``. Setting it to ``None`` (the
default) leaves the renderer's built-in background in place.

Each camera is configured independently: cameras with ``background_color=None`` and cameras
with a color set can coexist in the same scene. On the RTX backends (Isaac RTX and OVRTX) this
is achieved by writing ``omni:rtx:background:source:type`` / ``omni:rtx:background:source:color``
USD attributes directly on the render product, so no process-wide carb settings are modified.

.. list-table::
:header-rows: 1
:widths: 30 70

* - Backend
- Behavior when ``background_color`` is set
* - ``IsaacRtxRendererCfg``
- Per-render-product USD attributes on the Replicator render product.
* - ``OVRTXRendererCfg``
- Per-render-product USD attributes authored in the render scope.
* - ``NewtonWarpRendererCfg``
- Packed ARGB clear color used to fill the framebuffer before rasterization.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Added
^^^^^

* Added :attr:`~isaaclab.sensors.camera.CameraCfg.background_color` to
:class:`~isaaclab.sensors.camera.CameraCfg` as a unified cross-backend way to set the camera
background to a solid color. Accepts normalized RGB floats ``(r, g, b)`` in ``[0, 1]``;
defaults to ``None`` (each backend's original default background).
16 changes: 16 additions & 0 deletions source/isaaclab/isaaclab/sensors/camera/camera_cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,22 @@ class OffsetCfg:
on :attr:`renderer_cfg` instead.
"""

background_color: tuple[float, float, float] | None = None
"""Background color for the camera as normalized RGB floats in ``[0, 1]``.

When set, the renderer fills pixels that miss all geometry with the given solid color.
When ``None`` (the default), each backend uses its own default background
(dome-light for RTX backends, gray for Newton).

The value is specified as ``(red, green, blue)`` with each component in ``[0, 1]``.
For example, ``(0.0, 0.0, 0.0)`` is black and ``(1.0, 1.0, 1.0)`` is white.

Both RTX backends (Isaac RTX and OVRTX) apply this as a per-render-product USD
attribute (``omni:rtx:background:source:type`` / ``omni:rtx:background:source:color``)
so each camera is configured independently and cameras with ``background_color=None``
use the dome-light default without affecting other cameras.
"""

renderer_cfg: RendererCfg = field(default_factory=RendererCfg)
"""Renderer configuration for camera sensor."""

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Added
^^^^^

* Added support for :attr:`~isaaclab.sensors.camera.CameraCfg.background_color` in
:class:`~isaaclab_newton.renderers.NewtonWarpRenderer`. When set, converts the normalized RGB
color to an ARGB clear color passed to ``SensorTiledCamera.ClearData`` on each render call.
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ def __init__(self, newton_sensor: newton.sensors.SensorTiledCamera, spec: Camera
clipping_range = getattr(spawn, "clipping_range", None)
self.near_clip: float | None = float(clipping_range[0]) if clipping_range is not None else None
self.far_clip: float | None = float(clipping_range[1]) if clipping_range is not None else None

# ABGR clear color packed as uint32 — Newton's SensorTiledCamera reads the low byte as R,
# next as G, next as B, high byte as A (little-endian RGBA in memory). Default is 93% gray
# (0xFFEEEEEE), matching the RTX renderer background and improving visibility of dark objects.
background_color = getattr(spec.cfg, "background_color", None)
if background_color is not None:
r, g, b = (max(0, min(255, round(c * 255))) for c in background_color)
self.clear_color: int = (0xFF << 24) | (b << 16) | (g << 8) | r
else:
self.clear_color = 0xFFEEEEEE

# Post-render PPISP pipeline composed when ``spec.cfg.isp_cfg`` is set.
# ``isp_cfg`` is already fully normalized by ``prepare_cameras`` by the time it reaches here.
self.ppisp_pipeline: PpispPipeline | None = None
Expand Down Expand Up @@ -471,9 +482,8 @@ def render(self, render_data: RenderData):
depth_image=render_data.outputs.depth_image,
normal_image=render_data.outputs.normals_image,
shape_index_image=render_data.outputs.instance_segmentation_image,
# ARGB 93% gray to improve visibility of dark objects and align with RTX renderer background
clear_data=newton.sensors.SensorTiledCamera.ClearData(
clear_color=0xFFEEEEEE,
clear_color=render_data.clear_color,
**({"clear_depth": render_data.far_clip} if _use_depth_clear else {}),
),
kernel_block_dim=self.cfg.kernel_block_dim,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Added
^^^^^

* Added support for :attr:`~isaaclab.sensors.camera.CameraCfg.background_color` in
:class:`~isaaclab_ov.renderers.OVRTXRenderer`. When set, authors
``omni:rtx:background:source:type = "color"`` and ``omni:rtx:background:source:color`` on the
USD render product instead of the default ``"domeLight"`` background.
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ def _initialize_from_spec(self, spec: CameraRenderSpec):
data_types=data_types,
minimal_mode=_resolve_rtx_minimal_mode(data_types),
camera_rel_path=self._camera_rel_path,
background_color=getattr(spec.cfg, "background_color", None),
)
self._render_product_paths.append(render_product_path)

Expand Down
21 changes: 20 additions & 1 deletion source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def build_render_scope_usd(
tiled_height: int,
minimal_mode: int | None = None,
render_var_configs: list[tuple[str, str, str]] | None = None,
background_color: tuple[float, float, float] | None = None,
) -> str:
"""Build the Render scope USD string (def Scope Render, RenderProduct, Vars).

Expand All @@ -118,12 +119,25 @@ def build_render_scope_usd(
tiled_height: Height of the tiled image.
minimal_mode: RTX minimal mode. None if not requested. Valid values are 1, 2, 3.
render_var_configs: Render variables to author. Uses the single render var arguments if not provided.
background_color: Solid background color as normalized RGB floats ``(r, g, b)`` in ``[0, 1]``.
When set, the render product uses a solid color background instead of the dome light.
When ``None``, the default dome-light background is used.

Returns:
The USD string for the render scope.
"""
camera_rel_list = ", ".join([f"<{p}>" for p in camera_paths])

if background_color is None:
bg_attrs = ['token omni:rtx:background:source:type = "domeLight"']
else:
r, g, b = background_color
bg_attrs = [
'token omni:rtx:background:source:type = "color"',
f"float3 omni:rtx:background:source:color = ({r}, {g}, {b})",
]
background_lines = "\n ".join(bg_attrs)

if minimal_mode is None:
render_mode_lines = ['token omni:rtx:rendermode = "RealTimePathTracing"']
else:
Expand Down Expand Up @@ -151,7 +165,7 @@ def RenderProduct "{render_product_name}" (
prepend apiSchemas = ["OmniRtxSettingsCommonAdvancedAPI_1"]
) {{
rel camera = [{camera_rel_list}]
token omni:rtx:background:source:type = "domeLight"
{background_lines}
float omni:rtx:rt:ambientLight:intensity = 1.0
{render_mode_block}
token[] omni:rtx:waitForEvents = ["AllLoadingFinished", "OnlyOnFirstRequest"]
Expand Down Expand Up @@ -181,6 +195,7 @@ def build_render_product_as_string(
data_types: list[str],
minimal_mode: int | None = None,
camera_rel_path: str = "Camera",
background_color: tuple[float, float, float] | None = None,
) -> tuple[str, str]:
"""Build the render product USD snippet as a string.

Expand All @@ -193,6 +208,9 @@ def build_render_product_as_string(
data_types: Data types from sensor config.
minimal_mode: RTX minimal mode. None if not requested. Valid values are 1, 2, 3.
camera_rel_path: Camera prim path relative to the env root (e.g. ``"Camera"`` or ``"Robot/head_cam"``).
background_color: Solid background color as normalized RGB floats ``(r, g, b)`` in ``[0, 1]``.
When set, the render product uses a solid color background instead of the dome light.
When ``None``, the default dome-light background is used.

Returns:
Tuple of (render product USD snippet as a string, absolute render product prim path).
Expand All @@ -217,6 +235,7 @@ def build_render_product_as_string(
tiled_height,
minimal_mode,
render_var_configs,
background_color,
)
return camera_content, render_product_path

Expand Down
32 changes: 32 additions & 0 deletions source/isaaclab_ov/test/test_ovrtx_usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,38 @@ def _assert_export_omits_env_children(exported: str, env_indices: range | list[i
assert f'def Xform "Object_env{env_idx}_only"' not in exported


def test_build_render_scope_usd_default_background_is_dome_light():
"""Default background (background_color=None) uses domeLight source type."""
render_scope = build_render_scope_usd(
camera_paths=["/World/envs/env_0/Camera"],
render_product_name="RenderProduct",
render_var_path="/Render/Vars/LdrColor",
render_var_name="LdrColor",
source_name="LdrColor",
tiled_width=16,
tiled_height=8,
)
assert 'token omni:rtx:background:source:type = "domeLight"' in render_scope
assert "omni:rtx:background:source:color" not in render_scope


def test_build_render_scope_usd_solid_background_color():
"""Providing background_color emits color source type and the color attribute."""
render_scope = build_render_scope_usd(
camera_paths=["/World/envs/env_0/Camera"],
render_product_name="RenderProduct",
render_var_path="/Render/Vars/LdrColor",
render_var_name="LdrColor",
source_name="LdrColor",
tiled_width=16,
tiled_height=8,
background_color=(1.0, 0.0, 0.5),
)
assert 'token omni:rtx:background:source:type = "color"' in render_scope
assert "float3 omni:rtx:background:source:color = (1.0, 0.0, 0.5)" in render_scope
assert 'token omni:rtx:background:source:type = "domeLight"' not in render_scope


def test_ovrtx_rgb_hdr_uses_hdr_color_render_var():
"""Requesting RGB_HDR from OVRTX selects the HdrColor render variable."""
assert get_render_var_config(["rgb_hdr"]) == ("/Render/Vars/HdrColor", "HdrColor", "HdrColor")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Added
^^^^^

* Added support for :attr:`~isaaclab.sensors.camera.CameraCfg.background_color` in
:class:`~isaaclab_physx.renderers.IsaacRtxRenderer`. When set, applies
``/rtx/background/source/type = 2`` (Color) and ``/rtx/background/source/color`` via the
settings manager during camera setup.
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ def prepare_cameras(self, stage: Any, spec: CameraRenderSpec) -> None:
``exposure:*`` to neutral and applies ``OmniRtxCameraExposureAPI_1`` so
RTX's physical-camera exposure model does not compound on top of the
ISP. Without an ISP, the camera prim's authored exposure is left alone.

:attr:`~isaaclab.sensors.camera.CameraCfg.background_color` is applied
per-render-product in :meth:`create_render_data` via USD attributes.
"""
if spec.cfg.isp_cfg is None:
return
Expand Down Expand Up @@ -303,6 +306,24 @@ def create_render_data(self, spec: CameraRenderSpec) -> IsaacRtxRenderData:
rp = rep.create.render_product_tiled(cameras=cam_prim_paths, tile_resolution=(spec.cfg.width, spec.cfg.height))
render_product_paths = [rp.path]

# Apply background color as per-render-product USD attributes so each render product gets its own
# background without touching the process-wide /rtx/background carb settings.
background_color = getattr(spec.cfg, "background_color", None)
if background_color is not None:
r, g, b = background_color
rp_prim = stage.GetPrimAtPath(rp.path)
if rp_prim is not None and rp_prim.IsValid():
with Sdf.ChangeBlock():
rp_prim.CreateAttribute("omni:rtx:background:source:type", Sdf.ValueTypeNames.Token).Set("color")
rp_prim.CreateAttribute("omni:rtx:background:source:color", Sdf.ValueTypeNames.Float3).Set(
(r, g, b)
)
else:
logger.warning(
"create_render_data: render product prim at '%s' not found; background_color will not be applied.",
rp.path,
)

# Synthetic-data instance mapping filter for segmentation; before annotator attach.
SyntheticData.Get().set_instance_mapping_semantic_filter(
_camera_semantic_filter_predicate(self.cfg.semantic_filter)
Expand Down
Loading