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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Fixed ``image_normals`` raising ``KeyError: 'data_type'`` when constructed through the observation manager.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Removed the unused ``CameraImageBase.default_data_type`` class attribute. Custom camera image terms
declare their default in the ``data_type`` argument of ``__call__`` instead.
20 changes: 7 additions & 13 deletions source/isaaclab/isaaclab/envs/mdp/observations.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,12 +390,11 @@ class CameraImageBase(ManagerTermBase):
"""Base for camera image terms: validates the data type and owns the frame stack.

Subclasses read their sensor output and pass it with a normalizer from
:mod:`isaaclab.utils.images` to :attr:`_frames`.
:mod:`isaaclab.utils.images` to :attr:`_frames`. Each subclass declares a
``data_type`` argument with a default in ``__call__``, which the manager
resolves into the term parameters before construction.
"""

default_data_type: str
"""Legacy data-type alias. Defaults are resolved from the subclass's ``__call__`` signature."""

def __init__(self, cfg: ObservationTermCfg, env: ManagerBasedEnv):
super().__init__(cfg, env)
data_type = cfg.params["data_type"]
Expand Down Expand Up @@ -440,8 +439,6 @@ class image_rgb(CameraImageBase):
The image, or ``out`` when supplied. Shape is ``(num_envs, H, W, 3 * frame_stack)``, or channel-first.
"""

default_data_type = "rgb"

@staticmethod
def _accepts(data_type: str) -> bool:
return is_rgb_like(data_type)
Expand Down Expand Up @@ -491,8 +488,6 @@ class image_depth(CameraImageBase):
or channel-first.
"""

default_data_type = "distance_to_image_plane"

@staticmethod
def _accepts(data_type: str) -> bool:
return is_depth_like(data_type)
Expand Down Expand Up @@ -530,13 +525,12 @@ class image_normals(CameraImageBase):
normalize: Whether to normalize the image. Defaults to True.
channel_first: Whether to return ``(num_envs, C, H, W)``. Defaults to False.
frame_stack: Number of recent frames to stack along the channel axis. Defaults to 1.
data_type: The surface-normal camera data type. Defaults to "normals".

Returns:
The normals image. Shape is ``(num_envs, H, W, 3 * frame_stack)``, or channel-first.
"""

default_data_type = "normals"

@staticmethod
def _accepts(data_type: str) -> bool:
return is_normals_like(data_type)
Expand All @@ -548,8 +542,10 @@ def __call__(
normalize: bool = True,
channel_first: bool = False,
frame_stack: int = 1,
*,
data_type: str = "normals",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(AI-generated) Making this keyword-only is fine, because release/3.0.0 keeps the old positional order. But the other image terms take data_type as their second positional argument, so please add a sentence to the PR description explaining the difference.

) -> torch.Tensor:
images = _read_camera_output(env, sensor_cfg, "normals")
images = _read_camera_output(env, sensor_cfg, data_type)
return self._frames(images, normalize_normals if normalize else None)


Expand All @@ -570,8 +566,6 @@ class image_segmentation(CameraImageBase):
The segmentation image. Shape is ``(num_envs, H, W, C * frame_stack)``, or channel-first.
"""

default_data_type = "semantic_segmentation"

@staticmethod
def _accepts(data_type: str) -> bool:
return "segmentation" in data_type
Expand Down
19 changes: 19 additions & 0 deletions source/isaaclab/test/envs/test_camera_image_mdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
pytestmark = pytest.mark.unit

from isaaclab.envs.mdp.observations import (
CameraImageBase,
image_depth,
image_features,
image_normals,
image_rgb,
image_segmentation,
stacked_image,
Expand Down Expand Up @@ -144,6 +146,23 @@ def test_buffer_on_cuda(self):
class TestModalityTerms:
"""Per-modality reading, validation and normalization."""

@pytest.mark.parametrize("term_cls", CameraImageBase.__subclasses__(), ids=lambda cls: cls.__name__)
def test_constructs_without_data_type(self, term_cls):
"""Every camera image term declares a default data type, so its config may omit it."""
outputs = {
name: _random_rgb() for name in ("rgb", "distance_to_image_plane", "normals", "semantic_segmentation")
}
_, observe = _make_term(term_cls, _make_env(outputs), normalize=False)
assert observe().shape == (NUM_ENVS, HEIGHT, WIDTH, CHANNELS)

def test_normals_map_to_unit_range(self):
"""Normalized normals are mapped from ``[-1, 1]`` to ``[0, 1]``."""
normals = torch.linspace(-1.0, 1.0, NUM_ENVS * HEIGHT * WIDTH * CHANNELS).reshape(
NUM_ENVS, HEIGHT, WIDTH, CHANNELS
)
_, observe = _make_term(image_normals, _make_env({"normals": normals}))
torch.testing.assert_close(observe(), (normals + 1.0) * 0.5)

def test_rgb_drops_alpha_and_accepts_constant_mean(self):
"""Albedo's unused alpha channel is dropped and a constant mean replaces the per-image mean."""
albedo = torch.randint(0, 255, (NUM_ENVS, HEIGHT, WIDTH, 4), dtype=torch.uint8)
Expand Down
Loading