Commit 21b60f9
[Odin] Fix semantic_segmentation camera observations reaching the policy as integers (#7531)
## Description
Every `semantic_segmentation` camera row in benchmark dispatch
`20260901-153531` (image built from `origin/release/3.0.0` at
`f88dbc59c82`, `rsl_rl`, all core tasks) failed at the first training
step — **60 failed rows** total, with:
```
RuntimeError: Input type (unsigned char) and bias type (float) should be the same
```
The failure is renderer-independent: `isaacsim_rtx` 18 rows, `ovrtx` 18,
`newton_renderer` 24. The segmentation output reaches the feature
extractor's first convolution still as an integer tensor.
### Root cause
Both Cartpole camera observation paths normalize only RGB-like and
`depth` data types, so `semantic_segmentation` falls through
unconverted:
-
`source/isaaclab_tasks/isaaclab_tasks/core/cartpole/mdp/observations.py`
(`CameraImageStack.__call__`)
-
`source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py`
(`CartpoleCameraEnv._get_observations`)
Both files are byte-identical between `release/3.0.0` and `develop`, so
the bug is present on `develop` as-is.
### Why the fix belongs in the observation term
The renderer is producing exactly what its published contract says it
should — `NewtonWarpRenderer.supported_output_types` deliberately emits
RGBA `uint8` when colorized and a single `int32` id channel when not,
*"matching the Isaac RTX / OVRTX contract so backend-independent
consumers see the same dtype"*. Segmentation label ids are integers;
making a renderer emit floats would break every consumer that reads ids
(visualization, semantic id lookup, dataset export).
Nor does it belong in the feature extractor: the extractor's contract is
"float32 image in", and the observation pipeline already owns
dtype/layout normalization for every other camera data type via
`normalize_camera_image`. Making the CNN defensively cast would paper
over the same gap for every future task and duplicate logic that already
exists.
So the fix goes where the gap is: the observation term, routed through
the shared helper.
### The int32-vs-uint8 subtlety
Segmentation has **two** dtypes depending on
`colorize_semantic_segmentation`:
- `colorize=True` (the `CameraCfg` default, and what the failing sweep
used): RGBA `uint8`, 4 channels
- `colorize=False`: a single `int32` label-id channel
`normalize_camera_image` already handled the colorized `uint8` case
correctly — it was simply never called for this data type. The
non-colorized `int32` case was **not** handled: it fell through every
branch and was returned unchanged, so a fix that only wired up the
existing call would still break `colorize=False`.
The `int32` half is not demonstrated by the sweep (see the caveat
below), but it is not opportunistic scope creep: the caller now
dispatches on the data type alone, so the helper is the single place
that decides what segmentation means, and leaving it to silently return
`int32` unchanged would ship a fix that reads as complete while still
crashing under `colorize=False`.
Handled by keying on the tensor dtype rather than on the `colorize` flag
or a hardcoded uint8 assumption:
- In `normalize_camera_image`, non-`uint8` segmentation is cast to
`float32`. Label ids carry no meaningful scale, so they are cast and
**not** rescaled — applying `(x / 255) - mean` to label ids would be
inventing semantics.
- In both Cartpole callers, the uint8 deferred-normalize fast path
(which keeps the frame-stack ring buffer in `uint8` for cheaper per-step
copies) is gated on `camera_data.dtype == torch.uint8`, so colorized
segmentation rides it and `int32` label maps are normalized before
entering the ring.
## Type of change
- Bug fix (non-breaking change which fixes an issue)
## Test evidence
Construction-only reproduction, no simulator needed. The bug fires when
the extractor first sees an observation, so exercising the observation
term directly on stub camera output is sufficient and much faster.
Extended `source/isaaclab/test/utils/test_images.py` with the `int32`
case next to the existing colorized case, and dropped
`semantic_segmentation` from the "unknown type passthrough"
parametrization — that class asserts `out is src` under the heading
"Unknown data_types return the input unchanged", and segmentation is no
longer unknown. It would still pass by the accident that `.float()` on a
float32 tensor returns self, so leaving it would have left the suite
documenting the opposite of the new behaviour.
Added
`source/isaaclab_tasks/test/core/test_cartpole_camera_observations.py`
for the observation term itself; no sim-free test of it existed (the
`test_rendering_cartpole*.py` neighbours need a renderer and golden
images, and the `*_camera_presets.py` files only resolve configs). Two
tests, each parametrized over `frame_stack` `[1, 2]` so both the
immediate and the deferred-normalize branch are covered, and each
asserting exact values rather than just dtype.
Both dtypes are covered: colorized `uint8` RGBA and non-colorized
`int32`.
**Without the fix** (the three source files reverted to
`origin/develop`, tests kept):
```
$ uv run --frozen --extra dev python -m pytest source/isaaclab/test/utils/test_images.py \
source/isaaclab_tasks/test/core/test_cartpole_camera_observations.py -q
FAILED test_images.py::TestNormalizeCameraImageSegmentation::test_non_colorized_semantic_segmentation_is_cast_to_float[cpu]
FAILED test_images.py::TestNormalizeCameraImageSegmentation::test_non_colorized_semantic_segmentation_is_cast_to_float[cuda:0]
FAILED test_cartpole_camera_observations.py::test_colorized_segmentation_is_normalized_like_rgb[cpu-1]
FAILED test_cartpole_camera_observations.py::test_colorized_segmentation_is_normalized_like_rgb[cpu-2]
FAILED test_cartpole_camera_observations.py::test_colorized_segmentation_is_normalized_like_rgb[cuda:0-1]
FAILED test_cartpole_camera_observations.py::test_colorized_segmentation_is_normalized_like_rgb[cuda:0-2]
FAILED test_cartpole_camera_observations.py::test_non_colorized_segmentation_is_cast_to_float[cpu-1]
FAILED test_cartpole_camera_observations.py::test_non_colorized_segmentation_is_cast_to_float[cpu-2]
FAILED test_cartpole_camera_observations.py::test_non_colorized_segmentation_is_cast_to_float[cuda:0-1]
FAILED test_cartpole_camera_observations.py::test_non_colorized_segmentation_is_cast_to_float[cuda:0-2]
10 failed, 62 passed in 8.56s
```
Note that the pre-existing colorized-`uint8` helper test passes on
`develop`: `normalize_camera_image` always handled that case correctly,
and the crash came from the Cartpole callers never invoking it.
**With the fix:**
```
$ uv run --frozen --extra dev python -m pytest source/isaaclab/test/utils/test_images.py \
source/isaaclab_tasks/test/core/test_cartpole_camera_observations.py -q
72 passed in 3.53s
```
`uv run --frozen isaaclab -f` passes clean.
### Caveats a reviewer should know
- The sweep exercised the **colorized `uint8` path**:
`CameraCfg.colorize_semantic_segmentation` defaults to `True` and the
Cartpole config declares `observation_space=[4, 96, 96]` (4 channels =
RGBA). The `int32` path is reachable only with `colorize=False`; it was
genuinely broken (the helper returned it unchanged) but the 60 rows do
not prove it.
- The **direct-environment edit is not covered by a test**.
`CartpoleCameraEnv._get_observations` calls
`super()._get_observations()`, which needs a constructed environment, so
it cannot be exercised sim-free. The edit is line-for-line identical to
the manager-term edit, which is tested.
## Relationship to #7440
#7440 touches `isaaclab/utils/images.py` and the shared
`isaaclab/envs/mdp/observations.py::image` term, but **neither Cartpole
file**, so it does not fix this. Its `images.py` work is a fused
normalize+layout-conversion perf change that adds an
`output_channel_dim` parameter; it leaves the segmentation dispatch
condition semantically unchanged and does not add `int32` handling. This
PR adds an early-return branch above that condition and leaves #7440's
line untouched, so the two should merge cleanly in either order.
## Checklist
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation (docstrings
for `normalize_camera_image`)
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective
- [x] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file (changelog fragments;
`extension.toml` is generated)
## Release backport
- [x] <!-- backport-active-release --> Backport this pull request to the
active release branch after it merges into `develop`
(cherry picked from commit d7d0976)1 parent a4d4297 commit 21b60f9
7 files changed
Lines changed: 130 additions & 11 deletions
File tree
- source
- isaaclab_tasks
- changelog.d
- isaaclab_tasks/core/cartpole
- mdp
- test/core
- isaaclab
- changelog.d
- isaaclab/utils
- test/utils
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| 55 | + | |
| 56 | + | |
55 | 57 | | |
56 | 58 | | |
57 | 59 | | |
| |||
79 | 81 | | |
80 | 82 | | |
81 | 83 | | |
82 | | - | |
83 | | - | |
84 | | - | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
85 | 88 | | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
86 | 93 | | |
87 | 94 | | |
88 | 95 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
149 | | - | |
150 | | - | |
| 149 | + | |
| 150 | + | |
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
| |||
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
165 | 180 | | |
166 | 181 | | |
167 | 182 | | |
| |||
192 | 207 | | |
193 | 208 | | |
194 | 209 | | |
195 | | - | |
| 210 | + | |
196 | 211 | | |
197 | 212 | | |
198 | 213 | | |
| |||
Lines changed: 10 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
Lines changed: 7 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
11 | 13 | | |
12 | 14 | | |
13 | 15 | | |
| |||
78 | 80 | | |
79 | 81 | | |
80 | 82 | | |
| 83 | + | |
81 | 84 | | |
82 | 85 | | |
83 | | - | |
84 | | - | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
85 | 89 | | |
86 | 90 | | |
87 | 91 | | |
88 | 92 | | |
89 | | - | |
| 93 | + | |
90 | 94 | | |
91 | 95 | | |
92 | 96 | | |
| |||
Lines changed: 5 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
50 | 53 | | |
51 | 54 | | |
52 | | - | |
| 55 | + | |
53 | 56 | | |
54 | 57 | | |
55 | 58 | | |
| |||
Lines changed: 72 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
0 commit comments