松田研究室
FindingID: p1-a-eval-convention-audit
Status: stable / task P1.A (M1)
← Findings Index
P1 Phase A · M1 gate · 統合 finding

P1.A eval convention audit (統合) — 7 軸の diff 確定、apparent gap 推定 -3〜-6 dB

brush trainer (37.40 dB) と splat-rs (24.879 dB) の 12.56 dB gap の真因切り分けとして、両 trainer の eval pipeline を file:line 単位で完全 audit。両者は逆方向の convention を採用しており、特に (1) split file (splat=val 100view / brush=test 200view)、(2) background composite (splat=GT に white pre-comp / rendered 未合成 vs brush=両者 premultiplied-α で bg=ZERO)、(3) 8-bit roundtrip 有無、の 3 軸が apparent gap の主因候補。PSNR formula 自体 (MAX=1, log10, RGB only, per-view mean) は同等で、algorithmic gap ではない。P1.M1 gate 充足: 数式 diff の定量化完了、A.3 reproducer で apparent/real gap 仕分け実施予定。

neutralrank: highaudit-complete-gate-passed第 1 軸第 2 軸第 3 軸p1phase-amilestone-m1brush-parityevalconventionpsnrpremultipliedalphasplitauditsynthesis
Δ PSNR
推定 -3〜-6 dB (apparent gap 縮小、A.3 で実測予定)
Δ Wallclock
N/A (audit task)
Impact
両 trainer の eval pipeline を file:line で完全 audit、PSNR formula 本体 (MAX=1 / log10 / RGB only / per-view mean) は同等だが、(1) test split (200 view) vs val split (100 view)、(2) background composite convention の **完全逆方向**、(3) 8-bit roundtrip 有無、(4) α-mask 経路、(5) clamp/quantize 等 7 軸で diff を確認。最大の発見は brush の premultiplied-α + bg=ZERO eval が NeRF Synthetic の透明領域で構造的に PSNR を +3 dB 以上嵩上げする一方、splat-rs は target に white pre-composite / rendered に bg 合成なしの mismatch で convergence 残差が MSE に直接残る。apparent gap の推定 -3〜-6 dB を A.3 reproducer で実証予定、残り -6〜-9 dB が真の algorithmic gap。
01

本文

P1.M1 gate (eval convention 統一確定 + 数式 diff 定量化) 充足のための統合 finding doc。並列で実行した p1-a-1-brush-eval-audit (brush 側、12 axes) と p1-a-2-splat-eval-audit (splat-rs 側、7 軸 + 内部 inconsistency 1 軸) を突合し、両者の diff を 7 axes (D1-D7) に整理。これが P1.A.3 reproducer (同 PLY を 2 convention で測る script) と P1.A.4 (splat-rs eval を brush 準拠に統一、config flag 化) の input となる。

Gate 結論 (M1 pass)

両 trainer の eval pipeline 数式・dataset 範囲・前処理は完全に file:line 単位で定式化された。両者は (PSNR formula 本体は同等だが) 逆方向の convention を採用しており、apparent gap (eval convention 差で見かけ上膨らんだ部分) と real gap (algorithmic 真差) を切り分けるための前提資料が揃った。M1 gate pass、P1.A.3 (reproducer) → P1.A.4 (統一実装) → P1.B (互換 config 起草) へ進行可能。

1. 統合 diff table (7 axes)

#diff 軸splat-rs (本実装)brush 標準推定 PSNR impact確信度
D1split filetransforms_val.json (100 view)transforms_test.json (200 view)±1〜2 dB (起点違い)確定
D2background compositerendered 未合成 (T 残部 RGB=0) + GT に white-bg pre-comp両者 premultiplied-α 空間で bg=ZERO 比較-2〜-4 dB (splat 不利)確定
D3α-mask / premultiplyなし (target α=1.0 hardcode)AlphaMode::Transparent で GT を byte 空間 premultiply-1〜-3 dB (splat 不利)確定
D48-bit roundtrip / clampraw f32 (clamp / quant なし)pred を (*255).round()/255 で量子化+0.5〜1 dB (splat 微高)確定
D5PSNR formula 本体MAX=1, log10, RGB only, per-view meanMAX=1, log10, RGB only, per-view meanneutral確定 (同等)
D6α channel in MSE除外 (training は 4ch、eval は RGB only)除外 (eval は RGB only)neutral確定
D7training-eval consistencytraining=4ch loss / eval=RGB only (inconsistency)training=match_alpha=0.1 / eval=RGB only間接 (convergence 影響)推定

2. 最重要 finding: background composite の逆方向 convention (D2 + D3)

Headline

brush は GT を premultiplied-α 化して rendered (bg=ZERO 出力) と直接比較。NeRF Synthetic の透明領域 (lego 周辺など、画面の 50% 以上) では premultiplied GT = (0,0,0,0) かつ rendered = (0,0,0,1-T≈0)完全一致、MSE 分母に 0 寄与が大量に入り PSNR を構造的に +3 dB 以上 嵩上げ。

一方 splat-rs は GT を white-bg pre-composite (rgb·α + (1-α)·1) して、rendered (bg 未合成、T 残部 RGB=0) と比較。透明領域では GT = (1,1,1) vs rendered = (0,0,0) で MSE = 1 が pixel あたり残り、convergence の不完全分が PSNR を直接押し下げる (training α-loss で吸収を試みるが完全には収束しない)。

両者の convention を揃えるだけで -3〜-6 dB の apparent gap が消える可能性が高い (A.3 reproducer で実証予定)。

3. 各 trainer の eval 数式 (再掲、統合 view)

splat-rs (本実装)

入力:
  rendered : f32 H·W·4 RGBA (chunks(4) で per-pixel、α 列 = 1-T、bg 未合成)
  target   : f32 H·W·4 RGBA (chunks(4) で per-pixel、α 列 = 1.0 hardcode、white-bg pre-comp 済み)

MSE = (1 / (3·H·W)) · Σ_{p ∈ pixels} Σ_{c ∈ {R,G,B}} (rendered[p,c] − target[p,c])²
PSNR = -10 · log10(MSE)   if MSE > 1e-12 else 100.0

split: transforms_val.json (100 view)
per-view PSNR の mean (mean-PSNR、not PSNR-of-mean-MSE)
clamp / quantize なし

brush (Apache 2.0 OSS)

入力:
  pred  : f32 H·W·3 RGB  (slice(s![.., .., 0..3])、bg=Vec3::ZERO で α-blend 済み、premultiplied)
  pred' : f32 H·W·3 RGB  (pred を (*255).round()/255 で 8-bit roundtrip)
  gt    : u8  H·W·4 RGBA (AlphaMode::Transparent → byte 空間で α premultiply 済み)

MSE = mean((pred' − gt_rgb_normalized)²) over H·W·3   (α channel は loss 対象外)
PSNR = 10 · log10(1 / MSE) = -10 · log10(MSE)         (MAX² = 1 暗黙)

split: transforms_val.json → transforms_test.json (val 無いとき fallback)
       NeRF Synthetic は test (200 view) を使用、subsample なし、全 view を逐次回し
per-view PSNR の mean

4. 数式上は同じ、convention で逆方向

PSNR の本体定義式 (-10 · log10(MSE), MAX = 1.0, RGB only, per-view mean) は 完全同等。違いは入力画像 (pred / gt) の作られ方:

  • brush は "両者を premultiplied-α + bg=ZERO" に揃える (training/eval 共通の canvas) → 透明領域で free PSNR (両者 0)
  • splat-rs は "GT に white-bg 合成 / rendered には合成なし" の mismatch → 透明領域で free penalty (convergence 残差が直接 MSE 化)
  • どちらが "正しい" 評価か? という議論は orig 3DGS (Kerbl+ 2023) を参照すべきだが、brush の方が paper 系標準に近い と推定 (premultiplied-α は graphics の自然な convention)
  • splat-rs の mismatch は 意図的ではなくバグ寄り (training は 4ch / eval は RGB only の内部 inconsistency も同根)

5. split 違い (D1) の独立 impact

splat-rs=val (100 view)、brush=test (200 view)完全に異なる camera set。NeRF Synthetic の train/val/test split は paper 標準で 100/100/200 で、val は train から近い角度、test は新規 novel view。同 trainer / 同 ply でも val/test では 1-2 dB 程度の差が出る (一般的に test の方が難しく PSNR 低い)。

  • もし splat-rs を test split で評価すると: 24.879 → さらに 1-2 dB 下がる可能性 (apples-to-apples では gap がさらに広がる)
  • もし brush を val split で評価すると: 37.40 → +1 dB 程度高くなる可能性 (gap 広がる方向)
  • つまり D1 単体では splat-rs にとって不利な方向 (現状の数字は val で甘い)、ただし他の D2-D4 で apparent 嵩上げを brush が取っているので相殺
  • A.3 で同 trainer を 100/100/200 split 全部回して absolute 差を測れば確定

6. apparent gap 推定 (主仮説)

推定 dB shift備考
D2 (bg composite)+3〜+5 dB (splat-rs 側で底上げ)両者を brush 流 (premultiplied + bg=ZERO) に揃えると splat-rs 側が +3〜+5 dB
D3 (α-mask / premultiply)+1〜+2 dB (splat-rs 側で底上げ)GT premultiply で透明領域の "free 0 match" を獲得
D4 (8-bit roundtrip)-0.5〜-1 dB (splat-rs 側で打ち消し)raw f32 から u8 quant に揃えると微減
D1 (split file)-1〜-2 dB (splat-rs 側で減点)val (100view) から test (200view) に揃えると novel-view 難しさで減
D5-D7neutral or 二次効果formula 本体は同等、training-eval inconsistency は間接
**合計****+2.5〜+4 dB (splat-rs 底上げ)****apparent gap 縮小、24.879 → 27〜29 dB に近づく可能性**
結論

brush 37.4 dB vs splat-rs 24.879 dB の 12.56 dB gap のうち、推定 -3〜-6 dB は eval convention 差 (apparent)、残り -6〜-9 dB が真の algorithmic gap。A.3 reproducer で同 PLY を両 convention で評価し、apparent / real の仕分けを実証する。仮説が正しければ splat-rs の brush convention 数字は 27〜30 dB 圏 となり、P1.M3 生命線 (Lego 30k で PSNR > 30 dB) に そもそも近い距離まで来ている可能性 がある。

7. 主仮説の崩壊条件 (要 A.3 で確認)

  1. brush 37.4 dB が --alpha-mode=masked で取られていた場合: premultiply 経路を通らず、masked-only loss で評価される。この場合 D2/D3 の apparent 嵩上げ仮説は無効化 → algorithmic gap が大きい。
  2. brush の rendered output が premultiplied でなく post-multiplied だった場合: brush-render-bwd kernel 確認が必要 (A.1 audit で推定止まり)。post-multiplied なら bg=ZERO 比較の意味が変わり、apparent 嵩上げが小さくなる。
  3. NeRF Synthetic の透明領域比率が予想より小さい場合: lego は car 周辺で透明大、ficus は枝間で透明中、hotdog は単一物体で透明少 — シーンによって D2 の効きが変わる可能性 (multi-scene で再検証必要)。

8. P1.A.3 reproducer 設計 (次 phase へ申し送り)

目的: 同 trained ply / 同 GT camera から、D1-D4 を 1 軸ずつ切り替えて PSNR を測り、各 convention の dB 寄与を実証する。

  1. 資材: splat-rs lego sh3 30k final.ply (既存)、NeRF Synthetic lego の transforms_val.json + transforms_test.json (両方)
  2. script 構成: Python (PIL/numpy) で十分。同 ply を camera 全て render、output PNG dump、別 script で D1-D4 を ON/OFF 7 通り組み合わせて PSNR 計算
  3. 切り分け matrix: (D1: val/test) × (D2: rendered bg = 0/white) × (D3: α-mask on/off) × (D4: clamp+quant on/off) = 16 通り、ただし D2-D3 は相関するので実質 8 通り
  4. oracle: brush 本体を C 経由で叩き、同 ply に対する brush eval の PSNR を取得 (brush バイナリの brush-c API、要調査)
  5. 期待 deliverable: docs/findings/p1-a-3-cross-eval-reproducer.toml + scripts/p1-a-3-eval-bridge.py、apparent/real gap の dB 内訳を確定

9. P1.A.4 設計 (A.3 結果に応じて実装)

  • 方針: splat-rs eval を brush 準拠に統一、ただし config flag で旧 convention も保持 (backward compat、論文比較で両方使えるように)
  • flag: [eval] convention = "brush" (default) or "legacy_splat"split = "test" (default) or "val"
  • 実装変更点: (i) train.rs:115 split path 設定可能化、(ii) train.rs:182 compute_psnr を brush 準拠 path 追加、(iii) load_rgba_white_bg と並行で load_rgba_premultiplied 追加、(iv) eval 時 rendered に bg 合成オプション追加
  • 影響範囲: training には影響しない (eval のみ)、result.toml の PSNR 列が convention 名 suffix 付きで出力されるよう拡張

10. file:line index (両 audit から)

役割trainerpath:line
PSNR 関数本体splat-rssplat/crates/splat-cli/src/cmd/train.rs:182-200
eval driversplat-rssplat/crates/splat-cli/src/cmd/train.rs:112-151
split file hardcodesplat-rssplat/crates/splat-cli/src/cmd/train.rs:115 (val)
target loader (white-bg)splat-rssplat/crates/splat-io/src/dataset.rs:140-166
rasterize (bg 未合成)splat-rssplat/shaders/forward/rasterize.metal:140-218
forward driversplat-rssplat/crates/splat-train-v1/src/trainer.rs:97-104
training loss (α 含む 4ch)splat-rssplat/shaders/loss/loss.metal:31-88
eval entry (PSNR/SSIM)brush/Users/otkrickey/dev/brush/crates/brush-train/src/eval.rs:22-63
eval loop + 平均brush/Users/otkrickey/dev/brush/crates/brush-process/src/train_stream.rs:455-513
GT premultiplybrush/Users/otkrickey/dev/brush/crates/brush-dataset/src/scene.rs:212-232
AlphaMode 決定brush/Users/otkrickey/dev/brush/crates/brush-dataset/src/scene.rs:38-62
image_loss_evalbrush/Users/otkrickey/dev/brush/crates/brush-loss/src/lib.rs:1090-1100
split file loaderbrush/Users/otkrickey/dev/brush/crates/brush-dataset/src/formats/nerfstudio.rs:259-308
render_splats (bg=ZERO)brush/Users/otkrickey/dev/brush/crates/brush-render/src/gaussian_splats.rs:252-314

11. Phase A の残作業 (loop iteration template に従う)

  1. P1.A.3: reproducer script (Python) で 8 通り convention combination の PSNR 実測 — subagent worktree 候補
  2. P1.A.4: splat-rs eval を brush 準拠に統一、config flag 化 — foreground 小 change
  3. P1.M1 finding doc 更新: A.3 実測値で apparent/real gap dB 内訳確定後、本 doc に "実測 column" 追加
  4. plan-gap.vue 反映: P1.A.1/A.2 → done mark、A.3/A.4 を wip 更新、M1 gate → pass 表示

12. 卒論への含意 (D.3 negative-findings-chapter or methodology)

  • eval convention の選択は trainer 比較の前提: PSNR の絶対値は formula が同じでも入力画像作成 (bg composite, premultiply, split) で ±5 dB 変動、論文間比較は注意
  • premultiplied-α + bg=ZERO の brush 流は orig 3DGS / Mip-NeRF 系で標準的、本実装の white-bg compose 流は orig 3DGS 直伝 (test convention) ではあるが、近年 brush 系で異なる
  • 本実装の eval を brush 準拠に統一することで論文間比較が apples-to-apples に近づく、卒論の central evaluation table は brush 準拠 convention で報告すべき
  • training-eval inconsistency (training α 含む 4ch / eval RGB only) は本実装に固有のバグ寄り、修正により convergence 効率が改善する可能性 (二次効果、P1.D 周辺で検証)

関連

  • P1.A.1 brush eval audit (本 doc の brush 側 source): p1-a-1-brush-eval-audit
  • P1.A.2 splat-rs eval audit (本 doc の splat 側 source): p1-a-2-splat-eval-audit
  • 既存 brush vs splat 37 dB gap analysis (gap-analysis.md): brush-vs-splat-37dB-gap-analysis
  • brush bench 実測値: m4-brush-bench
  • NeRF Synthetic multi-scene results (8 scene complete): a-4-nerf-synthetic-scene-results
  • Phase A 後続: p1-a-3-cross-eval-reproducer (未作成、A.3 で生成予定)
03

関連 finding

© 2026 大竹律輝 — 松田研究室