Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
915a15d
fix(transcode): import NuRec USD as LightField
moennen Apr 26, 2026
50243d9
feat(export): validate written LightField USD stages
moennen Apr 26, 2026
2bc90d2
feat(post-processing): add linear-to-sRGB module
moennen Apr 26, 2026
18b5a18
feat(export): add linear USD color-space toggle
moennen Apr 26, 2026
bb4375d
feat(export): add PPISP SPG USD export
moennen Apr 26, 2026
dc0d2a3
feat(export): add PPISP Omniverse fallback writer
moennen Apr 26, 2026
1239be7
feat(export): refine PPISP fallback selection
moennen Apr 26, 2026
a62f7c7
fix(export): compose USDZ scene data from root stage
moennen Apr 26, 2026
e216e9e
fix(export): make PPISP USD export usable from checkpoints
moennen Apr 27, 2026
c81bd51
feat(export): gate Omniverse USD authoring
moennen Apr 27, 2026
5c40e3f
fix(export): refine PPISP Omniverse export behavior
moennen Apr 27, 2026
f1a2327
feat(export): bake post-processing into SH
moennen Apr 28, 2026
67d557e
refactor(export): clarify post-processing native mode
moennen Apr 28, 2026
5d03315
feat(export): support fixed PPISP native export
moennen Apr 29, 2026
6f2d4ef
feat(tools): add image comparison viewer
moennen Apr 29, 2026
2b64148
fix(validation): avoid NaNs in linear to sRGB
moennen Apr 29, 2026
ab1108f
fix(tools): improve image comparison metrics
moennen Apr 30, 2026
7e41b45
feat(validation): add simple SH bake comparison
moennen Apr 30, 2026
6e835af
feat(export): add ParticleField sorting hint option
moennen May 5, 2026
36a0d2d
feat(export): PPISP controller SPG export
Apr 30, 2026
ef4b596
feat(export): add --ignore-ppisp-controller flag
Apr 30, 2026
a2d43b6
feat(tools): add PPISP controller triage script
Apr 30, 2026
ecb1a2a
fix(spg): route controller output through omni:rtx:aov RenderVar
Apr 30, 2026
5c7fd0f
fix(spg): drop 'require' substring from PPISP lua sandbox messages
Apr 30, 2026
4452bd6
fix(spg): probe slang buffer-helper API names at lua dispatch
Apr 30, 2026
206ce09
spg: broaden buffer-helper probe to surface metatable + all candidates
Apr 30, 2026
b096cb4
fix(spg): rename g_Weights -> weights to match USD attribute name
Apr 30, 2026
9800dae
fix(spg): move weights inside ParameterBlock to silence reflection wa…
May 1, 2026
3636a1c
feat(bake): warm-start fitted PPISP SH bake with simple-bake init
May 1, 2026
066aadd
feat(bake): interpolated-view sampling for the SH bake fit loop
May 1, 2026
c2ba74c
fix(bake): match PPISP fit reference encoding (linear -> sRGB + clamp)
May 1, 2026
872f50a
fix(bake): compose Jacobian through srgb_to_linear and clip outliers
May 1, 2026
32d0711
refactor(bake): drop random-pair-slerp view sampler
May 1, 2026
6093a0d
feat(tools): add ppisp_export bake-modes ablation tool
May 1, 2026
0b7a4c2
feat(export): default to DC-only sRGB warm-start + trajectory + 13 ep…
May 2, 2026
9973d68
fix(bake): produce gamma-space SH and co-optimise density
May 3, 2026
87670c9
feat(export): expose per-param-group bake LRs; default vignetting=none
May 3, 2026
4bb70bf
refactor(tools): trim bake-modes benchmark for the gamma-SH pipeline
May 3, 2026
542929b
perf(export): drop default bake epochs from 13 to 7
May 4, 2026
d326ef4
feat(export): add --output-scale to scale exported SH output
May 4, 2026
62ff7c5
feat(ppisp-spg): add user-overridable per-camera responsivity
May 4, 2026
0e791a2
chore(export): clean up PPISP PR docs
moennen May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,14 @@ python train.py --config-name apps/colmap_3dgrt.yaml path=data/mipnerf360/bonsai
python train.py --config-name apps/colmap_3dgut.yaml path=data/mipnerf360/bonsai out_dir=runs experiment_name=bonsai_3dgut dataset.downsample_factor=2 optimizer.type=selective_adam
```

### Post-processing (linear-to-sRGB and PPISP)

Hydra key: ``post_processing.method``. Values:

- **null** (default): no change to rendered RGB before the loss.
- **linear-to-srgb**: **IEC 61966-2-1** piecewise linear-to-sRGB encoding on ``pred_rgb`` (same rule as ``thirdparty/tiny-cuda-nn/scripts/common.py`` ``linear_to_srgb``). See ``threedgrut/utils/post_processing_linear_to_srgb.py``. Example: ``post_processing.method=linear-to-srgb``.
- **ppisp**: per-frame camera corrections; requires the ``ppisp`` package (see ``requirements.txt``) and uses the other ``post_processing.*`` fields in ``configs/base_gs.yaml``.

If you use MCMC and Selective Adam in your research, please cite [3dgs-mcmc](https://github.com/ubc-vision/3dgs-mcmc), [taming-3dgs](https://github.com/humansensinglab/taming-3dgs),
and the [gSplat](https://github.com/nerfstudio-project/gsplat/tree/main) library from which the code was adopted (links to the code are provided in the source files).

Expand Down
32 changes: 30 additions & 2 deletions configs/base_gs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,36 @@ export_usd:
enabled: false
path: ""
apply_normalizing_transform: true
format: standard # "nurec" for Omniverse USDVol internal format, "standard" for USDVol ParticleField3DGaussianSplat
format: standard # "nurec" for internal USDVol format, "standard" for USDVol ParticleField3DGaussianSplat
half_precision: false
export_cameras: true
export_background: true
# zDepth | cameraDistance | rayHitDistance
sorting_mode_hint: cameraDistance
# If true, Gaussian prim ColorSpaceAPI uses lin_rec709_scene; else srgb_rec709_display
linear_srgb: false
# Enable post-processing export when the checkpoint contains a supported module.
# Defaults to true; post-processing-export-mode controls how the effect is exported.
export_post_processing: true
# baked-sh fits a fixed post-processing transform into Gaussian SH coefficients.
# omni-native uses the module-specific Omniverse-native path; currently PPISP SPG.
# baked-sh | omni-native
post-processing-export-mode: baked-sh
# Optional fixed PPISP camera/frame for omni-native export. When frame is set,
# exposure/color are exported as static shader inputs instead of animation.
post-processing-export-camera-id: null
post-processing-export-frame-id: null
# Number of sequential passes over the train/reference set used for fitting.
post-processing-bake-epochs: 3
post-processing-bake-learning-rate: 0.001
post-processing-bake-camera-id: 0
post-processing-bake-frame-id: 0
# none: disable PPISP vignetting during bake. achromatic-fit: chromatic PPISP reference
# with an achromatic fit-only vignette; the achromatic vignette is not exported.
# none | achromatic-fit
ppisp-bake-vignetting-mode: achromatic-fit
# USD timeCodesPerSecond; time codes are bare frame indices so this sets playback speed
frames_per_second: 1.0

model:
density_activation: sigmoid
Expand Down Expand Up @@ -124,7 +149,10 @@ loss:

# Post-processing configuration
post_processing:
method: null # Possible values: null, "ppisp"
# null | "ppisp" | "linear-to-srgb"
# - linear-to-srgb: IEC piecewise linear-to-sRGB on pred_rgb (same as tiny-cuda-nn common.linear_to_srgb).
# No extra deps; no trainable weights. See threedgrut/utils/post_processing_linear_to_srgb.py.
method: null
# Enable the controller for predicting per-frame corrections for novel views.
# When false, zero corrections are used for novel views.
use_controller: true
Expand Down
162 changes: 162 additions & 0 deletions docs/ppisp-controller-export-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# PPISP Controller SPG Export — Feasibility & Plan

Scope: extend the existing PPISP SPG export to include the **controller** —
the per-camera CNN+MLP that predicts per-frame `exposure` and 8-d
`color_latents` from the rendered image.

Status: feasible. This document records the design before implementation.

---

## 1. Controller summary (from `ppisp.PPISP._PPISPController`)

Fixed architecture, one instance per camera. Inputs are the raw rendered
HDR image and an optional `prior_exposure` scalar.

```
Conv2d(3→16, 1×1, +bias)
MaxPool2d(3, stride=3)
ReLU
Conv2d(16→32, 1×1, +bias)
ReLU
Conv2d(32→64, 1×1, +bias)
AdaptiveAvgPool2d((5, 5))
Flatten # → 1600 features
concat(prior_exposure) # → 1601
Linear(1601 → 128) + ReLU
Linear(128 → 128) + ReLU
Linear(128 → 128) + ReLU
exposure_head: Linear(128 → 1)
color_head: Linear(128 → 8)
```

Total weights ≈ **240 K floats per camera**.

The output is **two scalar values for the whole image** (1 exposure, 8 colour
latents). Those nine numbers replace the current static USD time-samples on
the PPISP shader.

---

## 2. SPG capabilities used

The 3DGRUT SPG pipeline already uses **Slang** in the SPG runtime
(`*.slang`, `*.slang.lua`, `*.slang.usda`) — the public docs that describe
only CUDA kernels are out-of-date; the existing PPISP shader proves Slang
launch is supported.

Confirmed primitives:

- `slang.dispatch{ stage="compute", numthreads=…, grid=…, bind={…} }` per
shader prim.
- `slang.ParameterBlock(...)` for grouped scalar/vector inputs that map to
USD attributes.
- `slang.Texture2D / slang.RWTexture2D / slang.empty(shape, dtype)` for
bound textures and lua-allocated outputs.
- Shader-to-shader chaining via USD `omni:rtx:aov` connections on
`RenderVar` prims (the existing `LdrColor` → `PPISP` wiring uses this).

What we **do not** rely on:
- Multi-dispatch within one Lua launcher (one dispatch per shader prim).
- CooperativeVector / coopvec — not assumed available in the target Kit.
- Non-2D output buffers — only 2D images via `slang.empty`.

---

## 3. Two challenges and how we solve them

### 3.1 Adaptive avg pooling on a runtime-sized input

PyTorch's `AdaptiveAvgPool2d((5,5))` partitions the input into exactly 25
near-equal cells. The cell bounds are:

```
i = 0..4 (output row)
start_h = floor(i * H_in / 5)
end_h = ceil((i + 1) * H_in / 5)
```

Each Slang thread group computes one output cell `(i, j)` by reading every
input pixel in `[start_h, end_h) × [start_w, end_w)`, applying the
per-pixel CNN forward (3×3 max-pool fused with the surrounding 1×1
convolutions), and reducing the sum / divide-by-count in shared memory.

This works for arbitrary input resolution because the cell bounds are
computed inside the shader from `H_in, W_in`.

### 3.2 Baking MLP and CNN weights into Slang

Each camera's controller has unique weights. We generate **one Slang file
per camera** at export time, with all weights emitted as
`static const float[]` arrays. Slang's compiler can fold these into
constant memory, and there is no runtime upload step.

The generated file `ppisp_controller_<camIdx>.slang` includes a fixed
shared template (CNN forward, pool, MLP) and only differs in the weight
constants. The matching `*.slang.lua` and `*.slang.usda` are emitted per
camera as well so each `RenderProduct` references its own controller.

If weights ever exceed Slang's static-data limits we can fall back to
USD `float[]` inputs bound as a `StructuredBuffer<float>`, but for the
default architecture (~240 K floats) static arrays are fine.

---

## 4. SPG graph

```
HdrColor (RenderVar)
▼ (omni:rtx:aov connection)
PPISPController_<cam> Slang compute, single thread group
│ outputs ControllerParams (1×9 float image)
PPISP Slang compute, grid sized to image
│ reads HdrColor + ControllerParams + static vignetting/CRF
▼ outputs PPISPColor
LdrColor (RenderVar)
```

The existing `ppisp_writer.py` builds the second half. The new
controller writer creates the first stage and connects its output as
an additional input to the PPISP shader.

The PPISP slang shader is **generalised** to read the exposure and the 8
colour latents from a 1×9 single-channel float texture when one is bound,
falling back to its `ParameterBlock` defaults otherwise. This keeps the
legacy "static parameters per frame" path unchanged — important for users
who train without a controller.

---

## 5. Testing

Two-pronged approach:

1. **Unit-level Python check** that the generated Slang reproduces the
PyTorch controller's outputs to within a tight tolerance, using
`slangpy` to dispatch the controller shader against a reference image.

2. **Tool: `tools/render_ppisp_spg/`** — a slangpy-based runner
that opens an exported USD/USDZ, walks `/Render/<cam>` prims, finds
their SPG shader chain, and replays the chain on a supplied HDR input
for every authored time sample. Useful for visual regression and for
cross-checking that the PPISP USD asset produces the same image
sequence as the in-process `apply_post_processing` path used during
training.

The render tool intentionally does not try to reproduce Kit's full
RenderProduct pipeline; it executes only the SPG `compute` stages so it
remains independent of Kit and useful in headless CI.

---

## 6. Out of scope for this iteration

- Multi-dispatch optimisation of the controller (currently one slow but
correct compute pass).
- CoopVec acceleration of the MLP matmul.
- Quantising weights to fp16/bf16 to reduce shader source size.
- Runtime weight upload (large `float[]` USD inputs).

These can be added later if the basic export proves correct.
Loading
Loading