Skip to content

Commit 920d749

Browse files
Consolidate the segmentation observation tests
Fold the dtype/shape test into the two value-asserting tests and parametrize both on frame_stack, so every case now checks exact values instead of only the dtype. Drops four redundant cases while covering the deferred-normalize path for both segmentation dtypes.
1 parent fc0c62c commit 920d749

2 files changed

Lines changed: 29 additions & 41 deletions

File tree

source/isaaclab/isaaclab/utils/images.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ def normalize_camera_image(
8181
8282
Returns:
8383
The normalized tensor. For RGB-like and colorized semantic-segmentation input this is a
84-
fresh (or pre-allocated) float32 tensor; for non-colorized semantic segmentation it is a
85-
float32 view or copy of ``images``; for depth-like input it is ``images`` itself (mutated
86-
in place); for normals-like input it is a new tensor; for anything else, ``images``
84+
fresh (or pre-allocated) float32 tensor; for non-colorized semantic segmentation it is
85+
``images`` cast to float32; for depth-like input it is ``images`` itself (mutated in
86+
place); for normals-like input it is a new tensor; for anything else, ``images``
8787
unchanged.
8888
"""
8989
if data_type == "semantic_segmentation" and images.dtype != torch.uint8:
90-
# Non-colorized segmentation is an integer label map (``int32`` for every renderer). Label
91-
# ids have no meaningful scale, so only cast to float32 so downstream convolutions accept it.
90+
# Non-colorized segmentation is an integer label map (``int32`` for every renderer).
91+
# Label ids carry no scale, so cast for the downstream convolutions without rescaling.
9292
return images.float()
9393
if is_rgb_like(data_type) or (data_type == "semantic_segmentation" and images.dtype == torch.uint8):
9494
if images.dtype == torch.uint8 and images.ndim == 4 and images.is_contiguous():

source/isaaclab_tasks/test/core/test_cartpole_camera_observations.py

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
"""Sim-free tests for the Cartpole camera observation term.
77
8-
The term must hand the policy a float32 tensor for every camera data type. Segmentation is the
9-
interesting case: it is ``uint8`` RGBA when colorized and ``int32`` label ids when not, and the
10-
feature extractor's first convolution rejects both integer dtypes.
8+
Segmentation output is ``uint8`` RGBA when colorized and ``int32`` label ids when not. Either
9+
integer dtype crashes the feature extractor's first convolution, so the term must return float32
10+
for both. ``frame_stack`` is parametrized because the stacking path defers normalization.
1111
"""
1212

1313
from __future__ import annotations
@@ -29,56 +29,44 @@ def device(request):
2929
return request.param
3030

3131

32-
def _make_env(camera_output: dict[str, torch.Tensor], frame_stack: int, device: str) -> SimpleNamespace:
33-
"""Build the minimal environment stub the observation term reads from."""
34-
camera = SimpleNamespace(data=SimpleNamespace(output=camera_output))
35-
return SimpleNamespace(
32+
def _observe(images: torch.Tensor, frame_stack: int, device: str) -> torch.Tensor:
33+
"""Run the observation term over ``images`` using a minimal environment stub."""
34+
camera = SimpleNamespace(data=SimpleNamespace(output={"semantic_segmentation": images}))
35+
env = SimpleNamespace(
3636
cfg=SimpleNamespace(frame_stack=frame_stack),
37-
num_envs=next(iter(camera_output.values())).shape[0],
37+
num_envs=images.shape[0],
3838
device=device,
3939
scene=SimpleNamespace(sensors={"tiled_camera": camera}),
4040
)
41-
42-
43-
@pytest.mark.parametrize("frame_stack", [1, 2])
44-
@pytest.mark.parametrize(
45-
"dtype,num_channels",
46-
[(torch.uint8, 4), (torch.int32, 1)],
47-
ids=["colorized_uint8_rgba", "non_colorized_int32_labels"],
48-
)
49-
def test_semantic_segmentation_observation_is_float32(device, frame_stack, dtype, num_channels):
50-
"""Segmentation observations are float32 regardless of the renderer's ``colorize`` setting."""
51-
torch.manual_seed(0)
52-
images = torch.randint(0, 5, (2, 8, 8, num_channels), dtype=dtype, device=device)
53-
env = _make_env({"semantic_segmentation": images}, frame_stack, device)
5441
term = CameraImageStack(ObservationTermCfg(func=CameraImageStack), env)
42+
return term(env, SceneEntityCfg("tiled_camera"), "semantic_segmentation")
5543

56-
observation = term(env, SceneEntityCfg("tiled_camera"), "semantic_segmentation")
5744

58-
assert observation.dtype == torch.float32
59-
assert observation.shape == (2, frame_stack * num_channels, 8, 8)
45+
def _to_expected_layout(images: torch.Tensor, frame_stack: int) -> torch.Tensor:
46+
"""Convert BHWC to the channel-first layout, repeated as the ring buffer fills on first append."""
47+
return images.permute(0, 3, 1, 2).repeat(1, frame_stack, 1, 1)
6048

6149

62-
def test_colorized_segmentation_matches_rgb_normalization(device):
63-
"""Colorized segmentation gets the same ``(x / 255) - per-image mean`` treatment as RGB."""
50+
@pytest.mark.parametrize("frame_stack", [1, 2])
51+
def test_colorized_segmentation_is_normalized_like_rgb(device, frame_stack):
52+
"""Colorized uint8 RGBA segmentation gets the same ``(x / 255) - per-image mean`` as RGB."""
6453
torch.manual_seed(0)
6554
images = torch.randint(0, 255, (2, 8, 8, 4), dtype=torch.uint8, device=device)
66-
env = _make_env({"semantic_segmentation": images}, frame_stack=1, device=device)
67-
term = CameraImageStack(ObservationTermCfg(func=CameraImageStack), env)
6855

69-
observation = term(env, SceneEntityCfg("tiled_camera"), "semantic_segmentation")
56+
observation = _observe(images, frame_stack, device)
7057

7158
expected = images.float() / 255.0
7259
expected = expected - torch.mean(expected, dim=(1, 2), keepdim=True)
73-
torch.testing.assert_close(observation, expected.permute(0, 3, 1, 2), atol=1e-5, rtol=1e-5)
60+
assert observation.dtype == torch.float32
61+
torch.testing.assert_close(observation, _to_expected_layout(expected, frame_stack), atol=1e-5, rtol=1e-5)
7462

7563

76-
def test_non_colorized_segmentation_preserves_label_ids(device):
77-
"""Label ids carry no scale, so the int32 map is only cast, never rescaled."""
64+
@pytest.mark.parametrize("frame_stack", [1, 2])
65+
def test_non_colorized_segmentation_is_cast_to_float(device, frame_stack):
66+
"""Non-colorized int32 label ids are cast to float32 and, carrying no scale, left unrescaled."""
7867
images = torch.arange(2 * 8 * 8, dtype=torch.int32, device=device).reshape(2, 8, 8, 1) % 5
79-
env = _make_env({"semantic_segmentation": images}, frame_stack=1, device=device)
80-
term = CameraImageStack(ObservationTermCfg(func=CameraImageStack), env)
8168

82-
observation = term(env, SceneEntityCfg("tiled_camera"), "semantic_segmentation")
69+
observation = _observe(images, frame_stack, device)
8370

84-
torch.testing.assert_close(observation, images.to(torch.float32).permute(0, 3, 1, 2))
71+
assert observation.dtype == torch.float32
72+
torch.testing.assert_close(observation, _to_expected_layout(images.float(), frame_stack))

0 commit comments

Comments
 (0)