Skip to content

Record Newton markers and overlays in videos#6280

Closed
ooctipus wants to merge 2 commits into
isaac-sim:developfrom
ooctipus:zhengyuz/newton-video-overlays
Closed

Record Newton markers and overlays in videos#6280
ooctipus wants to merge 2 commits into
isaac-sim:developfrom
ooctipus:zhengyuz/newton-video-overlays

Conversation

@ooctipus

Copy link
Copy Markdown
Collaborator

Summary

  • render Newton visualization markers with environment-major filtering and per-world offsets
  • synchronize selected environments, world spacing, camera settings, and marker overlays into Newton GL video capture
  • document the Newton video-recording API and add focused regression coverage

Testing

  • 75 passed: test_video_recorder.py, test_newton_gl_perspective_video.py, and test_newton_adapter.py
  • ./isaaclab.sh -d (documentation build succeeded)
  • pre-commit hooks on all 16 changed files (passed)
  • repository-wide ./isaaclab.sh -f passed every hook except check-git-lfs-pointers, which reports unrelated golden-image pointers already present on upstream/develop

test_visualization_markers.py was not run locally because the available environment is kitless and does not include Isaac Sim; its added regression cases are included for Isaac Sim CI.

@github-actions github-actions Bot added documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team labels Jun 27, 2026
@greptile-apps

greptile-apps Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR wires Newton visualizer state — selected environments, world spacing, and marker overlays — into the Newton GL video capture, so recorded clips now mirror the live viewer exactly. It also fixes the env-major marker filtering bug where the old block-major loop was sampling markers from the wrong indices.

  • _sync_newton_camera in video_recorder.py now calls set_visible_worlds, set_world_offsets, and set_frame_overlay_callback on the capture each frame, in that order, before updating the camera.
  • NewtonGlPerspectiveVideo gains three new public methods with pre-initialization deferral and equality-based deduplication to avoid redundant Newton GL cache rebuilds.
  • _filter_marker_state is rewritten from block-major to env-major iteration; the world_indices tensor added alongside the filtered batch is used by the new _apply_marker_world_offsets helper to look up per-env visual offsets from the Newton viewer.

Confidence Score: 3/5

The recording pipeline change is safe, but _apply_marker_world_offsets makes an unchecked assumption about how Newton's world_offsets array is indexed that the test suite cannot validate without a live Newton viewer.

The env-major filter rewrite is correct and the new capture APIs are well-designed with good test coverage. The open question is in _apply_marker_world_offsets: it indexes viewer.world_offsets with original env-IDs. All tests use a fake viewer with a hand-crafted (num_envs, 3) offset tensor, passing regardless of Newton's actual layout. If Newton's real ViewerGL only allocates one row per visible world (position-indexed), selective-visibility sessions would get wrong offsets or crash at index_select.

newton_visualization_markers.py — the _apply_marker_world_offsets function needs either a confirmed guarantee that world_offsets.shape[0] == num_envs always holds, or an added bounds guard before the index_select call.

Important Files Changed

Filename Overview
source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualization_markers.py Rewrites marker filtering from block-major to env-major layout (correct bug fix) and adds _apply_marker_world_offsets which indexes viewer.world_offsets by env-id — this assumption is not validated by the test suite for the selective-visibility case.
source/isaaclab/isaaclab/envs/utils/video_recorder.py Extends _sync_newton_camera to mirror visible-env selection, world spacing, and marker overlay callbacks into the capture object; logic is sound with minor per-frame overhead on the overlay callback setter.
source/isaaclab_newton/isaaclab_newton/video_recording/newton_gl_perspective_video.py Adds set_visible_worlds, set_world_offsets, and set_frame_overlay_callback with pre-init deferral, equality deduplication, and a try/finally guard around viewer.end_frame; well-tested.
source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py Extracts inline marker rendering into render_markers() method and wires world_spacing from cfg to viewer; clean refactor with no logic changes.
source/isaaclab/test/envs/test_video_recorder.py Comprehensive unit tests for the new sync logic covering env-id derivation paths, truncation, empty/None sentinel distinction, callback enable/disable, and ordering invariants.
source/isaaclab_newton/test/video_recording/test_newton_gl_perspective_video.py New test file covering deferred initialization, deduplication, pre-init last-write semantics, error propagation through try/finally, and frame-order invariants for the new capture APIs.
source/isaaclab/test/markers/test_visualization_markers.py Updates expected filtering result to env-major layout (correct fix) and adds two new tests for world-offset application with selective and all-env visibility.

Reviews (1): Last reviewed commit: "Record Newton visualizer overlays in vid..." | Re-trigger Greptile

Comment on lines +468 to +496
spacing = getattr(viewer, "_user_spacing", None)
if spacing is not None and all(float(spacing[axis]) == 0.0 for axis in range(3)):
return translations

world_offsets = getattr(viewer, "world_offsets", None)
if world_offsets is None:
return translations

if isinstance(world_offsets, torch.Tensor):
offsets = world_offsets
elif isinstance(world_offsets, np.ndarray):
offsets = torch.as_tensor(world_offsets)
else:
try:
offsets = wp.to_torch(world_offsets)
except (AttributeError, RuntimeError, TypeError):
return translations
if offsets.ndim != 2 or offsets.shape[1] != 3:
return translations

if world_indices is None:
if num_envs <= 0 or translations.shape[0] % num_envs != 0:
return translations
repeat_count = translations.shape[0] // num_envs
world_indices = torch.arange(num_envs, device=translations.device).repeat_interleave(repeat_count)

indices = world_indices.to(device=offsets.device)
selected_offsets = offsets.index_select(0, indices).to(device=translations.device, dtype=translations.dtype)
return translations + selected_offsets

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Unvalidated assumption: world_offsets is indexed by env-id

_apply_marker_world_offsets feeds world_indices (original env IDs, e.g. [1, 1, 3, 3]) directly into offsets.index_select(0, indices). This is correct only if Newton's world_offsets array is shaped (num_envs, 3) and indexed by env-id. If Newton allocates world_offsets only for the currently visible worlds and indexes them by position, then with visible_env_ids=[1, 3], world_offsets would have 2 rows. Indexing into it with env-id 3 would trigger an IndexError, and env-id 1 would receive the wrong offset. All tests set world_offsets manually on a _FakeNewtonMarkerViewer so they pass regardless of Newton's actual layout. A bounds guard or a confirmed guarantee from Newton's API would resolve the ambiguity. Does Newton's ViewerGL.world_offsets always have num_envs rows indexed by env-id, or does it only allocate rows for the currently visible worlds (indexed by position within the visible list)?

Comment on lines +468 to +470
spacing = getattr(viewer, "_user_spacing", None)
if spacing is not None and all(float(spacing[axis]) == 0.0 for axis in range(3)):
return translations

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Private Newton attribute access

viewer._user_spacing is an undocumented internal attribute of Newton's ViewerGL. The getattr fall-through means a missing attribute only loses the short-circuit optimization. However, the early-exit is logically redundant: if spacing is zero, Newton sets all rows of world_offsets to zero, and translations + selected_offsets returns the same result anyway. Removing the _user_spacing check and relying solely on world_offsets would make the function independent of Newton internals.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +251 to +256
set_frame_overlay_callback = getattr(self._capture, "set_frame_overlay_callback", None)
render_markers = getattr(self._live_visualizer, "render_markers", None)
if callable(set_frame_overlay_callback):
enable_markers = bool(getattr(self._live_visualizer.cfg, "enable_markers", False))
callback = render_markers if enable_markers and callable(render_markers) else None
set_frame_overlay_callback(callback)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 set_frame_overlay_callback called every frame without deduplication

set_visible_worlds and set_world_offsets both guard against redundant Newton calls via equality checks, but set_frame_overlay_callback is reset unconditionally on every sync. The callback is a bound method whose identity is stable across frames, so a simple is not identity check would mirror the deduplication pattern used elsewhere.

Suggested change
set_frame_overlay_callback = getattr(self._capture, "set_frame_overlay_callback", None)
render_markers = getattr(self._live_visualizer, "render_markers", None)
if callable(set_frame_overlay_callback):
enable_markers = bool(getattr(self._live_visualizer.cfg, "enable_markers", False))
callback = render_markers if enable_markers and callable(render_markers) else None
set_frame_overlay_callback(callback)
set_frame_overlay_callback = getattr(self._capture, "set_frame_overlay_callback", None)
render_markers = getattr(self._live_visualizer, "render_markers", None)
if callable(set_frame_overlay_callback):
enable_markers = bool(getattr(self._live_visualizer.cfg, "enable_markers", False))
callback = render_markers if enable_markers and callable(render_markers) else None
if getattr(self._capture, "_frame_overlay_callback", ...) is not callback:
set_frame_overlay_callback(callback)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@ooctipus

Copy link
Copy Markdown
Collaborator Author

Superseded by two focused PRs, as originally intended: marker world rendering in #6281 and video overlay recording in #6282.

@ooctipus ooctipus closed this Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant