Add Sensor PPISP controller support for camera post-processing#5947
Conversation
There was a problem hiding this comment.
📋 Review: Add Sensor PPISP Controller Support for Camera Post-Processing
Summary
This PR adds PPISPAuto controller graph support to the Isaac Lab PPISP pipeline. It introduces a controller network (CNN feature pooling + MLP) that predicts per-camera exposure and color-latent parameters from HDR input images, enabling adaptive camera ISP at runtime. The implementation provides both a pure-Warp reference path and a native CUDA snippet backend for high-environment-count tiled camera workloads.
Overall, this is a well-structured, high-quality feature addition with strong test coverage. The architecture cleanly separates concerns (USD parsing → weight extraction → Warp/native execution → PPISP application).
✅ Strengths
- Excellent test coverage: NumPy reference equivalence, native-vs-Warp equivalence, cross-renderer integration (Isaac RTX, OVRTX, Newton), and an opt-in performance sweep
- Dual backend with auto-selection heuristic: Provides correctness reference (Warp) and performance path (native CUDA) with a sensible automatic choice based on env count and tile size
- Clean separation of parsing from execution:
controller_source.pyhandles only USD/asset parsing;kernels.pyhandles only GPU execution - Backwards compatible: Existing static PPISP path is untouched; the controller path is additive
- Good defensive coding: Weight count validation at import time, comprehensive input validation in kernel entry points
🔍 Findings (from initial review)
1. CI Failure: Changelog Fragment Check (Medium) ✅ Resolved
The changelog CI job is now passing with the .skip and .minor.rst fragments in place.
2. controller_source.py — Regex-based weight parsing is fragile (Medium)
File: source/isaaclab_ppisp/isaaclab_ppisp/controller_source.py, _extract_controller_weights()
The weight extraction relies on a regex matching kControllerWeights[TOTAL_WEIGHTS] = { ... };. The code comment acknowledges this fragility ("A renamed symbol or a numeric [241961] extent would break this match"). Consider:
- Adding a fallback pattern that also matches the literal numeric extent
kControllerWeights[241961] - Documenting the expected source format in a docstring so NRE export changes can be coordinated
3. kernels.py — Shared memory in native snippets may collide on large block dims (Low)
File: source/isaaclab_ppisp/isaaclab_ppisp/kernels.py
The native pool snippet declares __shared__ float gs_reduce[256] (matching PPISP_CONTROLLER_POOL_THREAD_GROUP_SIZE). If the block dim is ever changed without updating the snippet template, shared memory will silently under-allocate. Since the snippet uses Template.substitute() with a hardcoded dict, this is safe today, but a static assertion in the CUDA snippet (e.g., static_assert(blockDim.x == 256)) would catch any future mismatch at kernel compilation.
4. pipeline.py — Unbounded buffer cache growth (Low)
File: source/isaaclab_ppisp/isaaclab_ppisp/pipeline.py
_controller_buffers_by_shape caches scratch buffers keyed by (device, num_cameras, height, width). In scenarios where camera resolution or env count changes dynamically (e.g., curriculum learning with varying camera counts), old entries are never evicted. For typical fixed-resolution use cases this is fine, but consider adding an LRU eviction or a maximum cache size for robustness.
5. cfg.py — assert used for non-optional logic (Low)
File: source/isaaclab_ppisp/isaaclab_ppisp/cfg.py, line in _merge_shader_inputs_with_cfg
assert ppisp_cfg.shader_prim_path is not NoneUsing assert for runtime validation is fragile (assertions are stripped with -O). Consider using an explicit if ... raise ValueError(...) instead, especially since this is in the resolution path that renderer backends call.
6. generate_synthetic_gaussian_asset.py — Large embedded string literals (Informational)
The test fixture generator embeds ~400 lines of CUDA/Lua/USDA as module-level string constants. This works well and keeps the test self-contained, but if these grow further, extracting them to fixture files alongside the test might improve maintainability.
🧪 Test Assessment
The test additions are comprehensive and well-designed:
- Unit tests (
test_ppisp_kernels.py): Controller network matches NumPy reference, native matches Warp, color latent ordering is correct - Integration tests (3 renderer test files): Controller output matches static PPISP across Isaac RTX, OVRTX, and Newton
- USD parsing tests (
test_ppisp.py): Embedded weight loading, shader preference order - Performance benchmark (
test_ppisp_pipeline_perf.py): Opt-in sweep with CSV output, memory guards, proper GPU timing via Warp events
One minor suggestion: the performance test uses lambda closures in timing loops (lines with _time_gpu_operation(lambda: ...)). Due to Python's late binding, if any captured variable is reassigned in the loop body, the lambda may reference the wrong value. This appears safe in the current code since the captured arrays are not reassigned within the timing section, but worth noting.
📊 Verdict
Approve with minor suggestions. This is a solid, well-tested feature addition that follows Isaac Lab's existing patterns. The findings above are all low-to-medium severity and none block merging.
🔄 Updates
Update 2 (a55496c — 2026-06-04T11:49:39Z):
The PR was rebased on top of the develop branch (previous head was c4d7e19, now a55496cc33b7c06c5896d989b7deca586887c357). This incorporates base-branch changes (version bumps, changelog rollups, other packages' changes) but introduces no substantive changes to the PPISP feature code.
Previous findings status:
- Finding #1 (CI changelog): Resolved ✅
- Finding #2 (regex fragility): Unchanged — still uses
TOTAL_WEIGHTSsymbol - Finding #3 (shared memory size): Unchanged — hardcoded
gs_reduce[256] - Finding #4 (buffer cache unbounded): Unchanged
- Finding #5 (
assertvs raise): Still usesassert ppisp_cfg.shader_prim_path is not None - Finding #6 (embedded strings): Unchanged — informational only
No new issues identified in this rebase. The PR remains ready to merge pending CI green.
Update (3e495ee): All previous concerns addressed:
- ✅ Fixed: Magic literal
1601replaced with named constantPPISP_CONTROLLER_MLP_INPUT_DIMin the offset table and both MLP kernel branches. - ✅ Fixed:
file://URI handling now uses properurlparse/unquotevia_file_uri_to_path(), correctly handling UNC authority-form URIs (with dedicated test coverage).
No new issues found in the incremental changes.
9ede90b to
162af5d
Compare
Greptile SummaryThis PR adds
Confidence Score: 4/5The new controller inference path is well-structured and the dual-backend design is sound; the only comments are style-level maintainability concerns around hardcoded constants. The hardcoded literal source/isaaclab_ppisp/isaaclab_ppisp/kernels.py — the hardcoded 1601 literal in the offset table and MLP kernel loops Important Files Changed
Sequence DiagramsequenceDiagram
participant USD as USD Stage
participant CS as controller_source.py
participant CFG as PpispCfg
participant PL as PpispPipeline
participant KNL as kernels.py
USD->>CS: ppisp_cfg_from_usd_shader(PPISPAuto shader)
CS->>CS: _find_controller_shader_for_auto_shader()
CS->>CS: _read_shader_source_asset() [local/USDZ/omni.client/Ar]
CS->>CS: _extract_controller_weights() [regex → 241 961 floats]
CS-->>CFG: PpispCfg(controller_weights, controller_prior_exposure)
Note over PL: PpispPipeline.__init__(cfg)
PL->>PL: normalize_ppisp_cfg(cfg)
loop per frame
PL->>PL: _compute_controller_params(hdr)
PL->>PL: _controller_weights_by_device [cache wp.array]
PL->>PL: _controller_buffers [cache scratch (features, params)]
PL->>KNL: compute_ppisp_controller_params(hdr, weights, features, params)
KNL->>KNL: _resolve_ppisp_controller_backend() → warp/native
alt Warp backend
KNL->>KNL: launch_tiled _ppisp_controller_pool_features_kernel
KNL->>KNL: launch_tiled _ppisp_controller_mlp_kernel
else Native CUDA backend
KNL->>KNL: launch_tiled _ppisp_controller_pool_features_native_kernel
KNL->>KNL: launch_tiled _ppisp_controller_mlp_native_kernel
end
KNL-->>PL: controller_params (N×9)
PL->>KNL: apply_ppisp_to_rgba_with_controller_params(hdr, rgba, cfg, params)
KNL-->>PL: LDR RGBA output
end
|
a55496c to
3e495ee
Compare
There was a problem hiding this comment.
Re-review (commit 3e495ee):
All previously raised concerns have been addressed in this update:
✅ Magic literal 1601 replaced - Now uses PPISP_CONTROLLER_MLP_INPUT_DIM consistently across all offset calculations and kernel code
✅ file:// URI handling fixed - _file_uri_to_path() now uses urlparse to correctly handle UNC-style URIs (e.g., file://server/share/path) with proper authority preservation
The new commits also include:
- Substantial test coverage for the controller network (NumPy reference implementation, native vs Warp comparison)
- Performance benchmark suite for the PPISP pipeline
- Proper
urlparse/unquotefor file URI decoding with tests verifying authority handling
No new blocking issues identified in this update. The PPISP controller implementation looks solid.
Update (b3173b0): Reviewed incremental changes. This commit completes the architectural shift from shader-based to camera-attribute-based PPISP discovery (ppisp_cfg_from_usd_camera replaces ppisp_cfg_from_usd_shader), adds native CUDA controller pool/MLP kernels via wp.func_native with proper Template substitution of architecture constants, and introduces PpispPipeline controller weight caching with per-device/shape buffer management. Tests updated comprehensively. No new issues found.
Update (4ddbcfb): Reviewed incremental changes. This commit finalizes the camera-attribute PPISP model:
- Demo scripts refactored: shared binding helpers extracted into
_demo_utils.py, removing ~100 lines of duplication betweenppisp_camera.pyandppisp_camera_ovrtx.py resolve_and_normalizenow gracefully handlescamera_prim_path=Noneby falling back to stage-wide discovery- Renderer
prepare_camerashooks (Isaac RTX, OVRTX, Newton) updated to tolerate emptyspec.camera_prim_paths - Comprehensive new integration tests for camera-authored static attrs and controller-vs-static equivalence across all three renderer backends
- Unrelated quality fixes included:
build_simulation_contextdevice-override semantics,disabled_fabric_change_notifieskitless no-op, OVPhysX redundant USD replication removal, Newton visualizer particle options
No new issues found.
Update (da239e0): Reviewed incremental changes. Significant additions in this push:
- Newton Fabric body binding overhaul:
_rename_builder_labelsnow returns fabric body bindings and_initialize_fabric_body_primshandles per-body Fabric attribute writes (creates missing prims as Xforms with world-matrix attrs). Well-tested with newtest_newton_fabric_body_sync.py. split_clone_templateutility: Extracted from ad-hocpartition("{}")calls across Newton, OVPhysX, and PhysX cloner modules. Adds aValueErrorwhen the template lacks{}. New tests intest_cloner.py.- Multi-backend task migration: Stack (UR10, Galbot) and Place (Agibot) tasks expose
PhysicsCfgpresets with config-time validation rejecting unsupported backend/feature combos (surface gripper on Newton, reversed-joint robots). - Teleop device fix: SE(3) delta devices (keyboard/gamepad/spacemouse) moved from
ReachEnvCfgbase toik_rel_env_cfg.pyonly, fixing action-shape mismatch on absolute-IK and joint-pos variants. isaacteleopimport guard: Galbot stack configs now tolerate missingisaacteleopdependency.- Newton visualizer
set_camera_viewfix: Stores updated eye/lookat in cfg and applies immediately even before initialization. - Surface gripper wildcard regex:
re.sub(r"(?<!\.)\\*", ".*", ...)converts bare*to.*forGripperViewcompatibility. - OVPhysX CI markers: Removed
@pytest.mark.isaacsim_cifrom all OVPhysX tests (device-split marker handles CI scheduling). - Rendering test refactor:
_make_sensor_data_type_paramshelper de-duplicates the AOV combination matrix.
No new issues found.
Update (1bc33e5): Reviewed incremental changes. This push is primarily a large-scale documentation cleanup:
- Removed deprecated
--headlessflag from ~50+ code examples across docs (RL training, imitation learning, benchmarks, quickstart, policy deployment, troubleshooting, etc.). Commands now run headless by default when no visualizer is selected. - PPISP test fixtures expanded:
generate_synthetic_gaussian_asset.pygainsrender_synthetic_gaussian_scene_with_static_ppisp_attrsandrender_synthetic_gaussian_scene_with_controller_ppisp_attrshelpers plusassert_images_meaningfully_different/assert_ppisp_controller_matches_staticassertion utilities. - New integration tests across Isaac RTX, Newton, and OVRTX backends for camera-authored static PPISP attributes and controller-vs-static equivalence.
isaaclab_teleopREADME updated to reference--visualizer none/--viz noneinstead of deprecated--headless.- Changelog skip files added for
isaaclabandisaaclab_physxpackages (internal-only changes).
No new issues found.
Update (1bc33e5, re-trigger): Base branch (develop) was updated with PR #6102 (deprecated --headless flag docs cleanup), causing a webhook re-trigger. The PR HEAD remains at commit 1bc33e59 which was already reviewed above. No new changes to review.
Update (02a0a3b): Reviewed incremental changes. This push includes:
- RMPFlow quaternion convention fix (
rmp_flow.py): Removes the spuriousconvert_quat(..., to="xyzw")call — IsaacLab quaternions are already(x, y, z, w), so the double-conversion was scrambling the orientation target. New regression testtest_rmpflow_reset_stability.pyvalidates that zero-command arms hold their reset pose across 4 RMPFlow task configs. - PPISP docs updated for controller workflow (explains
ppisp:controllerWeightscamera attribute, controller-then-image-pass pipeline, per-view predictions). - Teleop docs: Added note about humanoid arm joint limits and RMPFlow preference.
- Task config cleanup: Removed redundant zero-offset
OffsetCfgentries and added viewer eye/lookat defaults for Galbot/Agibot tasks. - Minor: Augmented imitation docs tip about verifying models directory, changelog entries.
No new issues found.
3e495ee to
b3173b0
Compare
da239e0 to
1bc33e5
Compare
|
@pbarejko : CI failures seem unrelated. |
02a0a3b to
4e8db02
Compare
4e8db02 to
d8d9fd0
Compare
Description
Adds PPISP
PPISPAutocontroller support to IsaacLab camera post-processing.This change parses embedded PPISP controller weights from USD/SPG-authored controller CUDA sources, stores them in
PpispCfg, and evaluates the controller in Warp before applying PPISP.The controller predicts per-camera exposure and color-latent parameters, while existing static PPISP config continues to provide responsivity, vignetting, and CRF parameters.
The implementation keeps the original pure-Warp controller path as a correctness reference and adds a native
wp.func_nativeCUDA-snippet backend for high-env-count tiled camera workloads. The default controller backend uses an automatic heuristic: Warp for single/large images, native CUDA snippets for many small camera tiles.Also adds tests for:
PPISPAutocontroller parsingDependencies:
Fixes #
Type of change
Screenshots
Not applicable. This is camera post-processing and renderer/backend integration behavior.
Checklist
pre-commitchecks with./isaaclab.sh --formatconfig/extension.tomlfileCONTRIBUTORS.mdor my name already exists there