Add golden image correctness tests for Kit and Newton visualizers#6549
Open
matthewtrepte wants to merge 19 commits into
Open
Add golden image correctness tests for Kit and Newton visualizers#6549matthewtrepte wants to merge 19 commits into
matthewtrepte wants to merge 19 commits into
Conversation
matthewtrepte
requested review from
ClemensSchwarke,
Mayankm96,
Toni-SM,
jtigue-bdai,
kellyguo11,
masoudmoghani,
ooctipus,
pascal-roth,
peterd-NV and
xyao-nv
as code owners
July 15, 2026 21:42
Contributor
|
Too many files changed for review. ( |
matthewtrepte
force-pushed
the
mtrepte/golden-image-visualizer-checks
branch
from
July 15, 2026 22:09
527db2c to
14d0178
Compare
…izer
Adds dual-gate (per-pixel L2 norm + SSIM) golden image tests covering
KitVisualizer (RTX viewport/tiled) and NewtonVisualizer (OpenGL viewport/tiled)
on both PhysX and Newton MJWarp physics backends. Follows the same pattern as
the renderer correctness tests in isaaclab_tasks.
Bootstrap golden images are committed for all 8 combinations:
{physx,newton} x {kit,newton} x {viewport,tiled}.
Per-type thresholds are tuned to each backend's rendering determinism:
kit viewport/newton 1.0%/0.5% pixel diff; kit-tiled relaxed to 2.0% to
account for RTX anti-aliasing variance accumulating across tiled camera views.
Two bugs caused physx-kit-tiled and physx-newton-tiled golden images to render incomplete (only env 0 visible, or camera viewing from wrong angle): 1. NewtonVisualizer.step() calls _log_camera_sensor_image() only when the Newton physics state is available. Under the PhysX backend, get_state() returns None, so owned tiled cameras received zero renderer updates during the 20 physics warmup steps. _capture_visualizer_tiled_camera_rgb() then issued a single camera_sensor.update() — not enough for multi-tile RTX or Warp cameras to produce valid frames beyond tile 0. Fix: pump camera_sensor.update() for _TILED_CAMERA_SENSOR_WARMUP_UPDATES (20) iterations before sampling, matching the warmup already applied to the Kit viewport and Newton viewer paths. 2. KitVisualizer._setup_camera_sensor_view() returned early whenever _runtime_headless was True, leaving _camera_sensor=None even when --enable_cameras was active. The UI image window (omni.ui) is rightly skipped in headless mode, but the camera sensor itself is renderable and needed by CI tests. Fix: check cameras_enabled first; only skip sensor creation when headless AND cameras are disabled. Skip only _setup_camera_image_window() in headless mode. The two incorrect golden images (physx-kit-tiled.png, physx-newton-tiled.png) are deleted so the test regenerates them on the next run with proper warmup.
Previous physx-kit-tiled.png showed 2 tiny cubes (RTX cameras not warmed up under PhysX), and physx-newton-tiled.png showed only the top-left quadrant (NewtonVisualizer skipped camera updates for non-Newton backends). Both are now full 2x2 grids of cartpoles matching the newton-backend equivalents, captured after the warmup fix in the prior commit.
Three root causes were addressed: 1. Near-equilibrium initial conditions: seed=None with a symmetric pole-angle range allowed starting states near theta=0, velocity=0 where 20 physics steps produce too few differing pixels to pass _assert_frames_differ. Added _INTEGRATION_TEST_POLE_ANGLE_RANGE (0.15π–0.25π) and _INTEGRATION_TEST_POLE_VELOCITY_RANGE (0.1π–0.25π), applied before env.reset() in both motion runners so the pole always starts displaced enough to generate visible motion. 2. Kit RTX render lag: after a step loop, the async RTX pipeline may not have committed the latest frame before _capture_kit_viewport_rgb reads the annotator. Added _flush_kit_render_for_motion_capture (one extra sim.render() + app.update()) before each motion-check end-frame capture. 3. Newton viewer update lag: viewer.get_frame() can return the frame from the previous update cycle if no new render was committed after the last env.step(). Added _flush_newton_render_for_motion_capture (visualizer.step(0.0)) before each motion-check get_frame() call in the playing, rendering-play, and simulation-play phases. With all three root causes fixed, the pytest.mark.flaky(max_runs=2) retry markers are removed from the four integration test files.
Each backend (PhysX, Newton) previously required two separate Isaac Sim app launches: one for the non-tiled integration test and one for the tiled camera integration test. The four files are now two, with each file running both test functions under a single shared app launch. Removes test_visualizer_tiled_integration_physx.py and test_visualizer_tiled_integration_newton.py.
Three sources of contamination were causing golden image tests to fail when run as a full suite (tiled → viewport order): 1. Stale NewtonManager._model/_state_0 on PhysX backend: PhysXManager.close() does not call NewtonManager.clear(), so the shadow model and state built from a 4-env tiled stage persisted into the 1-env viewport test. Clear _model and _state_0 in _prepare_visualizer_test_process() to force a rebuild from the new stage. 2. Stale _scene_data_mapping: after rebuilding _model for the viewport test (with fewer bodies), the old tiled-test mapping had the wrong shape. Add a size check in update_visualization_state() that invalidates the mapping when body count changes, so it is lazily recreated from the correct stage. 3. CUDA RNG drift: Newton tiled tests consume CUDA random samples, so the configure_seed(42) call at fixture setup reaches env.reset() at a different offset than in isolation, producing a different initial pole angle. Reseed immediately before env.reset() in run_visualizer_golden_cartpole() to pin the cartpole's initial state regardless of prior test activity. Regenerate the newton-kit-viewport golden image with the new seed. Reorder VISUALIZER_MODE_COMBINATIONS in both golden test files to run tiled tests before viewport tests, preventing RTX render-product state from viewport tests from contaminating the tiled camera captures.
Adds per-backend golden image test files for the shadow hand (DirectRLEnv) and AnymalD flat (ManagerBasedRLEnv) environments, covering both PhysX and Newton backends in viewport and tiled-camera modes (4 combinations × 2 envs × 2 backends = 16 new parametrized tests across 4 new test files). Key implementation details: - ShadowHandEnvCfg and AnymalDFlatEnvCfg have nested PresetCfg fields (physics, scene, robot_cfg, object_cfg, events) that must be resolved before env construction; _make_shadow_hand_env and _make_anymal_d_env do this by selecting the physx/newton_mjwarp attribute directly. - AnymalD uses ManagerBasedRLEnv; the same env.reset()/step()/close() API and env.action_space.shape[-1] pattern works for both env types. - Tiled camera target is /World/envs/*/Robot for both robots. - Tiled-first ordering (same as cartpole) prevents RTX render-product state from contaminating subsequent viewport captures.
…nd files
Remove the four separate per-environment test files and fold shadow hand and
AnymalD parametrize cases into the existing test_visualizer_golden_physx.py
and test_visualizer_golden_newton.py files alongside the cartpole suite.
Each backend file now covers all three scenes across all visualizer-mode
combinations. Golden images for shadow_hand/ and anymal_d/ will be captured
automatically on first CI run and saved under golden_images/{scene}/.
…a cloth Extends the visualizer golden image test suite to cover three additional environments: - Shadow hand (ReorientDirectEnv): PhysX + Newton backends, viewport + tiled. Resolves the nested PresetCfg fields (sim.physics, scene, robot_cfg, object_cfg, events) manually before env construction. - AnymalD flat (ManagerBasedRLEnv): PhysX + Newton backends, viewport + tiled. Disables contact-sensor-dependent reward/termination managers on Newton since they require the PhysX tensor API. - Franka cloth (ManagerBasedRLEnv): Newton VBD-only (no PhysX preset). Camera pose matches the rendering golden test (eye=0.85,-0.55,0.42) so visualizer and sensor views can be compared directly. Uses Hydra preset resolver for the deeply nested VBD deformable PresetCfg. Also fixes: - Call NewtonManager.clear() in _prepare_visualizer_test_process() instead of only clearing _model/_state_0, preventing stale _views/_scene_data_mapping from crashing AnymalD init across test-parameterization resets. - Add scene-specific threshold overrides for shadow hand (30% tiled, 9% viewport) and AnymalD (6.5% tiled, 4.5% viewport) due to richer geometry producing higher Kit RTX variance than cartpole.
Franka cloth (Newton VBD deformable) matches the renderer test suite in having no PhysX preset, so it belongs in test_visualizer_golden_newton.py alongside cartpole, shadow hand, and AnymalD. - Merge test_visualizer_golden_franka_cloth_newton.py into test_visualizer_golden_newton.py (4 new param combinations, tiled-first) - Add physics_backend parameter to run_visualizer_golden_franka_cloth() to match the uniform (physics_backend, visualizer_type, mode, scores) runner signature; asserts backend == "newton" - Add _resolve_nucleus_url_to_local() helper that maps S3 nucleus URLs to pre-downloaded /tmp cache paths, fixing offline runs where omni.client's hash cache is cold for the panda USD (shadow hand / AnymalD are warm from prior runs but franka panda was not in the cache) - Add franka_cloth-kit-viewport threshold overrides (45% diff, SSIM 0.65) for cloth VBD solver + RTX warm-up variance; tiled mode is stable at the default 2% kit-tiled threshold - Commit 4 franka cloth golden images under golden_images/franka_cloth/
Kit RTX tiled tests leave residual TAA (temporal anti-aliasing) history in the render context. When the kit-viewport test runs afterward in the same process, the TAA-contaminated viewport produces 1.35–5.83% pixel diffs vs the clean-state golden, exceeding the 1% default threshold. Add scene-specific overrides (7% diff, SSIM ≥ 0.87) so the cartpole kit-viewport test passes reliably in the full suite without needing retries, keeping the combined physx+newton suite under 3.5 min on CI.
Set camera view before building the RTX annotator so the single warmup
renders at the correct position. Previously the annotator warmup ran
before set_camera_view(), leaving the camera wherever Newton stage init
or env.step() left it.
Add _capture_kit_viewport_with_pose_reapply() as the single capture
path for all kit-viewport golden tests. Remove the double-warmup path
that was advancing physics and contaminating the anymal_d pose.
Add separate kit-viewport eye/lookat constants for franka cloth
(pulled back to (2.5, -2.0, 2.0)) so the wider RTX FOV requirement
does not affect tiled/newton camera constants.
Raise franka_cloth-kit-tiled max-diff threshold to 5.5% to accommodate
VBD cloth non-determinism (~4.2% measured inter-run variance).
Regenerate the 9 golden images that were wrong or missing:
physx: cartpole, shadow_hand, anymal_d (kit-viewport)
newton: cartpole, shadow_hand, anymal_d (kit-viewport),
franka_cloth (kit-viewport, kit-tiled, newton-tiled)
Shadow hand leaves stale PhysX GPU tensor state that collapses anymal_d when the two environments share a process. Moving anymal_d tests before shadow_hand in the PhysX suite avoids the contamination entirely. Regenerate physx-anymal_d-kit-viewport.png with a properly standing robot captured in isolation (anymal_d without shadow_hand running first). Verified: anymal_d → shadow_hand sequence passes 8/8 with no reverse contamination (shadow_hand is not affected by anymal_d GPU state).
…tent framing With seed 42 + Newton the default uniform angle range samples near-zero (upright), making the pole nearly invisible in the kit-viewport golden image while the PhysX image shows the pole clearly at ~45°. The two backends consume random state differently so the same seed produces visually very different initial poses. Pinning initial_pole_angle_range to (π/4, π/4) collapses the range to a single deterministic value so both backends show the pole at the same 45° angle, giving a well-framed reference image that exercises the renderer meaningfully.
With the previous random seed 42 the Newton backend sampled a near-zero initial pole angle, producing an almost-invisible pole in the viewport goldens. The new images use the deterministic 45° pose: pole clearly visible and leaning in all 8 combination images (physx/newton × kit/newton × viewport/tiled).
matthewtrepte
force-pushed
the
mtrepte/golden-image-visualizer-checks
branch
from
July 17, 2026 22:43
a0e909f to
7d5fac9
Compare
matthewtrepte
requested review from
AntoineRichard,
StafaH,
aserifi,
daniela-hase,
david-cao-mueller and
rubengrandia
as code owners
July 17, 2026 22:43
AGENTS.md requires one fragment per touched package. Merge the three separate fragments (physx-tiled-fix, contamination-fix, shadow-hand-anymal) into the single canonical mtrepte-golden-image-visualizer-checks.rst and add the np.frombuffer false-pass fix entry.
Capture cartpole golden images at the reset pose (buffer_steps=0) instead of after 20 physics steps. With buffer_steps=20 the inverted pendulum falls past horizontal, making the pole appear disconnected from the cart. At reset the pole sits at exactly 45° and is visually attached. For PhysX anymal_d tiled-camera captures, 4-env contact dynamics are not bit-reproducible across sessions. Fix by capturing at reset (buffer_steps=0) so the golden is always the deterministic initial pose. Regenerate all 8 cartpole golden images and 5 stale PhysX golden images (anymal_d and shadow_hand newton-render variants that drifted from stale captures).
Newton's render loop resets the Kit viewport camera during RTX warmup. The previous fix set the camera before warmup only, but the async RTX pipeline still returned the pre-reapply (wrong-camera) frame on the first annotator read after warmup. Add a post-warmup mini-drain loop (5 render+read cycles) when physics_backend=="newton" so the annotator reflects the corrected camera before capture. This mirrors the existing workaround in _run_kit_viewport_frame_motion_test but also flushes the async pipeline. Pass physics_backend to _capture_kit_viewport_with_pose_reapply at all four call sites (cartpole, shadow_hand, anymal_d, franka_cloth) so the Newton-specific drain fires for all scenes. Regenerate all four newton-kit-viewport golden images which were all captured without the post-warmup drain.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds golden image correctness tests for
KitVisualizerandNewtonVisualizeracross both viewport and tiled-camera capture modes, covering PhysX and Newton MJWarp physics backends. Each combination is compared against a committed reference PNG using a dual-gate system (per-pixel L2 norm + SSIM) adapted from the renderer test framework.Along the way, several bugs were found and fixed to make these tests reliable:
Test infrastructure fixes:
test_visualizer_integration_physx.py,test_visualizer_integration_newton.py) — removes the combined cross-backend test that was masking contaminationNewtonVisualizerskipped_log_camera_sensor_image()on PhysX backend, leaving owned tiled cameras at zero renderer updates; the capture helper now pumpscamera_sensor.update()before samplingKitVisualizersilently skipping tiled camera sensor creation in headless mode even when--enable_camerasis activeCross-test contamination fixes (tiled → viewport ordering):
Three sources of contamination when tests run sequentially in the same process were identified and fixed:
Stale
NewtonManager._model/_state_0on PhysX backend —PhysXManager.close()does not callNewtonManager.clear(), so the shadow model built for a 4-env tiled stage persisted into the 1-env viewport test, causing the Newton viewer to auto-fit its camera to the wrong bounding box. Fix: explicitly clear_modeland_state_0in the between-test prepare step.Stale
_scene_data_mappingsize mismatch — after rebuilding_modelfrom the new 1-env stage, the old 4-env body-index mapping was still used inupdate_visualization_state(), writing wrong transforms intostate_0.body_q(1.38% consistent pixel diff). Fix: size check inNewtonManager.update_visualization_state()that invalidates the mapping when body count changes.CUDA RNG drift — Newton tiled tests consume CUDA random samples, so
configure_seed(42)at fixture start arrived atenv.reset()at a different offset depending on prior test history, producing a different initial pole angle (6.04% consistent diff). Fix: reseed immediately beforeenv.reset()inrun_visualizer_golden_cartpole().Test ordering in both golden test files is set to tiled-before-viewport to prevent RTX render-product state from contaminating tiled captures (viewport-first caused 76%+ diffs in tiled tests).
Type of change
Screenshots
Checklist
pre-commitchecks with./isaaclab.sh --formatsource/<pkg>/changelog.d/for every touched packageCONTRIBUTORS.mdor my name already exists there