diff --git a/CLAUDE.md b/CLAUDE.md index 056b237..f9f8350 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,6 +40,7 @@ 補足: **Step 8-14 候補 γ (D1 ROI 整合の再確認) 完了 — クリア (2026-06-02)**.他の物理モデル変更に進む前の事前確認として実施.設定変更ゼロで,既存設定の数値検証のみ.シミュ ROI (24, 37, 464, 450) の物理範囲 (X∈[-45.31, +45.31] mm × Y∈[-46.92, +44.48] mm,幅 90.63×91.41 mm) と実機 ROI (539, 1015, 1400, 1409) の物理範囲 (実機 PNG の評価板黒縁検出から物理分解能 0.0654 mm/px を確定 → 幅 91.54×92.13 mm) を比較した結果,**物理サイズ差 < 1%・中心位置差 < 1 mm で整合**.3 列パネル `output/_slide_panels/roi_alignment_check.png` (①実機画像 + 評価板境界・実機 ROI / ②シミュ輝度マップ + 評価板境界・シミュ ROI / ③物理座標系で重ね合わせ) でも視覚確認.**残差 2.63× の主因は ROI 整合ではない**ことが確定.過去の設定者 (`scripts/select_white_board.py` GUI 選択 + シミュ ortho_scale 設計値) が物理整合性を意識して ROI を揃えていたことが裏付けられた.副次発見: 評価板は実機画像中央から下方向に約 4.2 mm (65 px) オフセット配置されているが (取付位置のミリ精度差),ROI もそのオフセットに沿って選択されているため物理領域として整合.次のアクション: 残候補 β (A1 単独) / α (A8+A9) / δ (β+α 全部入り) を選択して Step 8-14 を実施. 補足: **Step 8-14 候補 β (A1 内壁の非 Lambertian 性) 完了 — クリア (2026-06-02)**.候補リスト 🥇 1 位だった A1 を 3×3 グリッド感度分析で検証.スキーマ拡張 (`BlendBSDFConfig` + `RoughConductorBSDFConfig` 追加,`MaterialConfig` Union 拡張,forward reference + `model_rebuild()`) と `materials.py` 再帰変換 (`_build_bsdf_dict`) を新規実装.テンプレ `configs/scene_baseline_blend.yaml` + スイープスクリプト `scripts/sweep_blend_bsdf.py` + 比較パネルスクリプト `scripts/build_blend_bsdf_panel.py`.新規テスト 89 件追加 (608 → 697 全通過).**weight ∈ {0.05, 0.15, 0.30} × α ∈ {0.1, 0.3, 0.6} の 9 ジョブ実走 (75.5 分)**.結果: ベースライン純 diffuse (CoV=0.1826, 勾配=24.65%, C/P=1.327, 動径 min/max=0.615) と比較して **全 9 パターンが悪化または不変** (最良 w=0.05/α=0.6 で CoV=0.1851,最悪 w=0.30/α=0.1 で CoV=0.2433).実機 (CoV=0.0695) 方向と **逆方向に動く**.「形」は w=0.30 で下端中央に 2 つの specular 集中スポットが顕在化.物理的解釈: 純 Lambertian は cos 重み散乱で多重反射の均一化作用が最大.specular 成分は特定方向に光を集中させ均一化を阻害.**3D プリンタ白フィラメントは完全 Lambertian として扱うのが実機挙動に近い近似** と判明 (TECH_09 LED Lambertian 整合と並ぶ「物理モデル妥当性の裏付け」).**A1 は残差 2.63× の主因ではない**ことが確定.TECH_10 優先順位を更新: A1 を「クリア」化,🥇 1 位 A8 反射率 R=0.89 / 🥈 2 位 A9 max_depth 16+ / 🥉 3 位 B1 幾何精度 に繰り上げ.副次成果: スキーマ層に `blendbsdf` + `roughconductor` のサポートが入り,今後の物理モデル試行で再利用可能.比較パネル `output/_slide_panels/blend_bsdf_sweep.png` + `blend_bsdf_metrics.png`,全 9 ジョブ集約 `output/blend_sweep/summary.csv`.[TECH_10 § A1 検証結果](docs/05_TECH/TECH_10_残差主因候補の網羅整理.md#a1-検証結果2026-06-02-完了) / [TEST_01 § Step 8-14 候補 β](docs/06_TEST/TEST_01_キャリブレーション検証履歴.md#step-8-14-候補-β-a1-内壁の非-lambertian-性--混合-bsdf-感度分析) / [SPEC_02 materials](docs/04_SPEC/SPEC_02_シーン定義仕様.md#materials) 参照.次のアクション: Step 8-14 候補 α (A8 R=0.89 + A9 depth=16) の組み合わせ実施. 補足: **LED グループ別 sanity check 完了 — TEST_07 として報告 (2026-06-03)**.Step 8 系キャリブの主軸ではなく「シミュが下/右/左の 3 LED 基板を物理位置どおりに独立にモデル化できているか」の基礎健全性確認.`scripts/sanity_led_groups.py` で各基板の 6 LED だけを ON にした一時 YAML を生成 → 3 ジョブ sm-render (約 25 分) → 比較パネル.結果: **方向性 ✓** (bottom は下端中央/right は右側面寄り/left は左側面寄り),**左右対称性 ✓** (right mean=47.10 vs left mean=47.08),**量的バランスは bottom が right/left の 2.10× で強い**.ユーザー観察「bottom は上下の島,right/left は中心の円」は LED 配置で説明: bottom LED は評価板の下端真下に X 中央寄り集中 → 直接光が広範囲を叩く / right/left LED は側面外側で Y 中央付近に分散 → 直接光は端のみ・中央は多重反射経由 → 「直接光寄与が大きい bottom」vs「多重反射寄与が大きい right/left」の構造的違い.**線形性検証は別途未実施**(既存 `output/baseline/luminance.npy` が Step 8-1 時点の古いもので和との比較不可.Mitsuba は理論線形なので baseline 再生成で一致見込み).副次発見: 実機の right (Y∈[-25,+3]) と left (Y∈[-33,-5]) は **完全な左右対称設計ではない**(Fusion STL 実位置と整合).[TEST_07 LED グループ別動作確認](docs/06_TEST/TEST_07_LEDグループ別動作確認.md) 参照.**シミュレーションは 3 つの照明源を物理位置どおりに独立にモデル化できている**ことが確定し,Step 8 系の残差 2.63× 議論の前提が担保された. +補足: **Step 8-14 候補 α 前半 (A9 max_depth スイープ) 完了 — 数値改善のみクリア・形不変 (2026-06-03)**.ユーザー仮説「現状 d=8 では 0.85^8≈0.27 で取りこぼし大.0.01 未満まで増やすべき」を 5 値スイープ {8, 12, 16, 20, 28} で検証.`scripts/sweep_max_depth.py` (約 85 分) + `scripts/build_max_depth_panel.py` (収束カーブ + 数値表) を新規実装.結果: 全 4 形状指標 (CoV/勾配/C/P/動径 min/max) が **単調に実機方向に改善**.d=8→28 で **CoV=0.1817→0.1583** (残差 20.9% 縮小),勾配=24.01→20.24% (24.1%),C/P=1.316→1.254 (27.6%),動径 min/max=0.628→0.689 (26.3%).mean は 207.82 で固定 (アンカー正規化).**実用的な収束は d=16 で達成** (d=16→28 で CoV 1.3% のみ動く).理論累積 R^n より早く収束は「直接光と初期反射が支配的,後段は ambient のみ」の物理直感と整合.**「形」は完全に不変** (3 種類のパネル shared/independent/mean=1 正規化 全てで Y 軸単調勾配のまま contrast が緩むだけ).物理的解釈: 多重反射は **空間的に均一な ambient 成分**で,これを足しても暗/明領域の絶対差は縮むが空間配置は不変.「LED が下にしかない」配置の非対称性は除去不能.これは TEST_03「Step 8-1〜8-7 で形は全く変わっていない」と整合.**ユーザー仮説の評価**: 方向性は完全に正しい (21-28% 残差縮小).ただし実用収束は d=16 (理論累積より早い).d=28 まで増やしても追加改善 1% のみ.実機 (CoV=0.0695) には届かず,**残る残差は形改善で詰める必要**.**TECH_10 優先順位を再更新**: A9 を「数値改善のみクリア」化.**形改善の本筋候補は B1 幾何精度に集約**.A8 反射率 R=0.89 は数値改善目的では有効だが形改善は予想薄.新規候補として ε B1 と ζ LED 軸ティルトを追加.新キャリブベースライン候補: **d=16** (現状 d=8 は理論累積の 72.7% で取りこぼし大).副次成果: スイープ + 収束カーブ + 形比較パネル (`shape.png` 上下 2 段, `relative.png` mean=1) のテンプレが整備され今後の感度分析に流用可能.[TECH_10 § A9 検証結果](docs/05_TECH/TECH_10_残差主因候補の網羅整理.md#a9-検証結果2026-06-03-完了) / [TEST_01 § Step 8-14 候補 α 前半](docs/06_TEST/TEST_01_キャリブレーション検証履歴.md#step-8-14-候補-α-前半-a9-max_depth-スイープ-8-12-16-20-28) 参照.次のアクション: 候補 ε B1 筐体幾何精度 / 候補 ζ LED 軸方向ティルト仮説 のいずれかを実施. 実装済みモジュール: - `src/sm_tias_lightsim/uniformity.py` — Rec.709 輝度変換 + CoV/std/max-min(MiniTias-Evaluation コピー) diff --git "a/docs/05_TECH/TECH_10_\346\256\213\345\267\256\344\270\273\345\233\240\345\200\231\350\243\234\343\201\256\347\266\262\347\276\205\346\225\264\347\220\206.md" "b/docs/05_TECH/TECH_10_\346\256\213\345\267\256\344\270\273\345\233\240\345\200\231\350\243\234\343\201\256\347\266\262\347\276\205\346\225\264\347\220\206.md" index 87a0d7b..1b965b2 100644 --- "a/docs/05_TECH/TECH_10_\346\256\213\345\267\256\344\270\273\345\233\240\345\200\231\350\243\234\343\201\256\347\266\262\347\276\205\346\225\264\347\220\206.md" +++ "b/docs/05_TECH/TECH_10_\346\256\213\345\267\256\344\270\273\345\233\240\345\200\231\350\243\234\343\201\256\347\266\262\347\276\205\346\225\264\347\220\206.md" @@ -7,13 +7,13 @@ | 順位 | 候補 | 影響度 | 検証コスト | 推奨理由 | | --- | --- | --- | --- | --- | | ~~🥇 1 位~~ | ~~A1. 内壁の非 Lambertian 性(光沢成分)~~ | **✓ クリア** | — | **2026-06-02 検証完了**:3×3 グリッドで全 9 パターンが実機方向に作用しない(むしろ悪化)と判明.純 Lambertian が実機挙動に最も近い.[§ A1 検証結果](#a1-検証結果2026-06-02-完了) 参照 | -| **🥇 1 位** | **A8. 反射率 R=0.85 → 0.89(物理整合点)** | 中 | 低 | Step 8-6 で導出済の物理整合点.新方針 (linear+LSC) 下での再評価.A1 クリアで繰り上げ | -| **🥈 2 位** | **A9. max_depth 8 → 16+** | 小〜中 | 低 | 後段多重反射の取りこぼし可能性.A1 クリアで繰り上げ | -| **🥉 3 位** | **B1. 筐体内部の幾何精度** | 中 | 高 | 3D プリンタの実物理表面 vs 設計表面.A1 クリアで繰り上げ | +| ~~🥈 2 位~~ | ~~A9. max_depth 8 → 16+~~ | **✓ 数値改善のみクリア(形不変)** | — | **2026-06-03 検証完了**:5 値スイープ {8,12,16,20,28} で **CoV/勾配/C/P/動径 min/max が全て単調改善**(d=28 で残差 21-28% 縮小).ただし「形」は Y 軸単調勾配のまま完全に不変.均一化フィルタとして働くが空間再配分は起こさない.[§ A9 検証結果](#a9-検証結果2026-06-03-完了) 参照 | +| **🥇 1 位** | **A8. 反射率 R=0.85 → 0.89(物理整合点)** | 中(数値のみ予想) | 低 | Step 8-6 で導出済の物理整合点.d=16 ベースで再評価.**数値改善は予想,形改善は予想薄**(A9 と同系統の均一化フィルタ) | +| **🥇 1 位(形改善)** | **B1. 筐体内部の幾何精度** | **大の可能性** | 高 | 3D プリンタの実物理表面 vs 設計表面.A1/A9 クリアで **形改善の最後の砦**として最有力 | | ~~4 位~~ | ~~D1. シミュ ROI と実機 ROI の物理的整合~~ | **✓ クリア** | — | **2026-06-02 検証完了**:物理サイズ差 < 1%・中心位置差 < 1 mm で整合.残差主因から除外([§ D1 検証結果](#d1-検証結果2026-06-02-完了) 参照) | | 4 位 | A2/A3. LED 配光の個体差・樹脂ドーム | 小〜中 | 高 | データシート Lambertian 整合済 ([TECH_09](TECH_09_LEDデータシート調査結果.md)) | -→ **2026-06-02 更新**: 当時最有力だった A1 を 3×3 グリッドで検証したところ実機方向に作用しないと判明([§ A1 検証結果](#a1-検証結果2026-06-02-完了)).**現状の最有力は A8 反射率 R=0.89**.次いで A9 max_depth と B1 幾何精度. +→ **2026-06-03 更新**: A1 に続き **A9 max_depth も「数値改善のみクリア・形改善ではない」と確定**(5 値スイープで d=28 まで CoV 21% 縮小、ただし「形」は Y 軸単調勾配のまま不変).**形改善の最有力は B1 筐体幾何精度に集約**.数値改善目的の最有力は A8 反射率 R=0.89(だが形改善は予想薄). ## 背景 (Background) @@ -41,8 +41,8 @@ | A5 | 発光面の物理サイズ | 2.8mm 角(パッケージサイズ) | 小 | [Step 8-7](../06_TEST/TEST_01_キャリブレーション検証履歴.md) で 1.5mm 角試行.形状不変,mean のみ変化 | | A6 | LED 方向(光軸) | 全 LED (0, 0, -1) | 小 | 実機の基板は平面で妥当 | | A7 | LED 位置精度 | Fusion STL 抽出済 | 小 | [Step 8-9](../06_TEST/TEST_01_キャリブレーション検証履歴.md) で正確化済(1% 以内の形状変化) | -| **A8** | **反射率 R = 0.85 → 0.89** | R=0.85(暫定) | **中** | [Step 8-2](../06_TEST/TEST_01_キャリブレーション検証履歴.md) で 0.75〜0.92 スイープ済.[Step 8-6](#) で物理整合点 R≈0.89 を導出.新方針下で再評価の価値あり | -| **A9** | **max_depth = 8 → 16+** | d=8 | **小〜中** | [Step 8-4](#) で 8→12 試行.勾配やや改善.CLAUDE.md には「キャリブは d≥12 推奨」と記載.新方針下で再評価の価値あり | +| **A8** | **反射率 R = 0.85 → 0.89** | R=0.85(暫定) | **中(数値のみ予想)** | [Step 8-2](../06_TEST/TEST_01_キャリブレーション検証履歴.md) で 0.75〜0.92 スイープ済.[Step 8-6](#) で物理整合点 R≈0.89 を導出.d=16 ベース新方針下で再評価.A9 と同系統の均一化フィルタなので **数値改善は期待,形改善は予想薄** | +| ~~A9~~ | ~~max_depth = 8 → 16+~~ | d=8 → 推奨 d=16 | **✓ 数値改善のみクリア** | **2026-06-03 検証完了**:5 値スイープ {8,12,16,20,28} で CoV/勾配/C/P/動径 min/max が全て単調改善.d=8→28 で残差 21-28% 縮小.**ただし「形」は Y 軸単調勾配のまま完全に不変**.均一化フィルタとして働くが空間再配分は起こさない.新キャリブベースラインとして **d=16** 推奨.[§ A9 検証結果](#a9-検証結果2026-06-03-完了) 参照 | ### B. 幾何モデル系 @@ -174,6 +174,74 @@ | A2/A3 LED 配光の個体差 | データシート Lambertian 整合済([TECH_09](TECH_09_LEDデータシート調査結果.md)).実物の個体差ばらつきは実測ゴニオフォトメータが必要で高コスト | | E1/E2 環境・運用系 | 新品撮影・通常室温で影響軽微 | +## A9 検証結果(2026-06-03 完了) + +候補リストで **A9「max_depth 8 → 16+」** を 5 値スイープ {8, 12, 16, 20, 28} で検証したところ,**数値指標は実機方向に単調改善するが「形」は完全に不変**であることが確定した.均一化フィルタとして働くが空間再配分は起こさない. + +### 検証手順 + +1. **仮説**: 現状 max_depth=8 では多重反射の累積寄与の **理論累積 R/(1-R)=5.67 に対して 72.7% しか取り込んでいない**.残り 27% を打ち切っている.R=0.85 の場合,最終 bounce の寄与が 0.01 を下回るのは d≈28 付近. +2. **実装**: `scripts/sweep_max_depth.py` で `configs/scene_baseline.yaml` の `render.max_depth` のみを書き換え → sm-render → sm-evaluate (`configs/eval_baseline_dng_target.yaml`).約 85 分. +3. **可視化**: `scripts/build_max_depth_panel.py` で 1×5 輝度マップ + 収束カーブ生成.追加で「shared vs independent スケール 2 段比較」(`max_depth_sweep_shape.png`) と「mean=1 正規化」(`max_depth_sweep_relative.png`) で形の不変性を視覚化. + +### 結果 + +| max_depth | 理論累積 R^n | CoV | 勾配% | C/P | 動径 min/max | +| --- | --- | --- | --- | --- | --- | +| **8** | 72.7% | 0.1817 | 24.01 | 1.316 | 0.628 | +| 12 | 85.8% | 0.1651 | 21.35 | 1.272 | 0.671 | +| 16 | 92.6% | 0.1604 | 20.59 | 1.259 | 0.683 | +| 20 | 96.1% | 0.1589 | 20.35 | 1.256 | 0.687 | +| **28** | **98.7%** | **0.1583** | **20.24** | **1.254** | **0.689** | +| **実機 TARGET** | — | **0.0695** | **8.35** | **1.091** | **0.860** | + +### 残差縮小率(d=8 → 28) + +| 指標 | 縮小率 = (d8 − d28) / (d8 − TARGET) | +| --- | --- | +| CoV | **20.9%** | +| 勾配 | **24.1%** | +| C/P | **27.6%** | +| 動径 min/max | **26.3%** | + +### 収束観察 + +実用的な収束は **d=16 で達成**(d=16 → 28 で CoV 1.3% のみ動く).理論累積より早く収束.これは **「直接光と初期反射が支配的,後段の累積はわずかな ambient しか足さない」** という物理的直感と整合. + +### 「形」の観察(決定的) + +3 種類のパネル全てで **形は完全に不変**: + +- **共有スケール**: 全 5 画像とも下端中央が明るく上端が暗い Y 軸単調勾配で、contrast が d=8→28 で緩む +- **独立スケール(自己 contrast 最大化)**: 全 5 画像とも **同じ形を異なる contrast で再現** → **形が変わっていないことが視覚的に明白** +- **mean=1 正規化**: d=8 で [0.3〜1.3]、d=28 で [0.4〜1.2] と **相対輝度の範囲が狭まる**(ambient 化が進む)が,**空間パターン (Y 単調勾配) は不変** + +### 物理的解釈 + +多重反射の累積は **空間的にほぼ均一な ambient 成分**として働く.これを足すと: + +- 暗い領域と明るい領域の **絶対差が縮む** → 数値指標は改善 +- 暗い/明るい領域の **空間配置は不変** → 「形」は変わらない + +つまり多重反射は **均一化フィルタ**として作用するが,「LED が下にしかなく評価板の中央〜上端は内壁経由でしか光が届かない」という **配置と幾何に由来する非対称性は除去できない**.これは TEST_03 の主張「Step 8-1〜8-7 で形は全く変わっていない」と整合.**max_depth スイープも同じパターン**. + +### 判定と意味 + +- **A9 は数値改善には有効だが形改善ではない**ことが確定.候補リストから「数値改善のみクリア」として除外 +- **新キャリブベースラインは d=16 が筋**(d=28 と CoV 1.3% 差、レンダ時間 17 分 vs 30 分).物理整合の極致を求めるなら d=28 +- TECH_10 優先順位を再更新: A9 をクリア化,**形改善の本筋候補は B1 幾何精度に集約**.A8 は数値改善目的では有効だが形改善は予想薄 +- 副次成果: スイープ + 収束カーブ + 形比較パネルのテンプレが整備された.今後 spp など他のレンダパラメータ感度分析にも流用可能 + +### ユーザー仮説の評価 + +ユーザー仮説「max_depth を増やすべき(最終 bounce 寄与が 0.01 を下回るまで)」は **方向性として完全に正しい**: + +- 数値改善は d=8 → 28 で確実に進む(21-28% 残差縮小) +- ただし **実用収束は d=16**(理論累積より早い).d=28 まで増やしても得るのは 1% 程度の追加改善 +- 実機 (CoV=0.0695) には届かない(収束値 CoV=0.158 が下限).**残る残差は形改善で詰める必要がある** + +ユーザーの「形は変わっていない」観察も視覚的・物理的に正確.均一化フィルタ系の改善は数値には効くが形には効かない原則が再確認された. + ## A1 検証結果(2026-06-02 完了) 候補リストで当時 🥇 1 位だった **A1「内壁の非 Lambertian 性(光沢成分)」** を Step 8-14 候補 β として **3×3 グリッド感度分析**で検証したところ,**どの組み合わせも実機方向に作用しない(むしろ悪化)**ことが確定した. @@ -272,12 +340,15 @@ | Step 8-14 候補 | 内容 | 状態 | 推奨度 | | --- | --- | --- | --- | -| **Step 8-14 候補 α** | **A8 (R=0.89) + A9 (depth=16) の組み合わせ**(低コスト・既存技術) | 未実施 | ⭐⭐⭐ | +| ~~Step 8-14 候補 α 前半~~ | ~~A9 max_depth 8 → 16+~~ | **✓ 2026-06-03 完了(数値改善のみクリア — 形不変)** | — | +| **Step 8-14 候補 α 後半** | **A8 反射率 R=0.85 → 0.89(d=16 ベース)**(低コスト・数値改善目的) | 未実施 | ⭐⭐ | | ~~Step 8-14 候補 β~~ | ~~A1 内壁の非 Lambertian 性(光沢成分混合 BSDF)~~ | **✓ 2026-06-02 完了(クリア — 実機方向に作用しない)** | — | | ~~Step 8-14 候補 γ~~ | ~~D1 ROI 整合の再確認~~ | **✓ 2026-06-02 完了(クリア — 物理差 < 1%)** | — | -| ~~Step 8-14 候補 δ~~ | ~~β + α 全部~~ | **β クリアで意義消失** | — | +| ~~Step 8-14 候補 δ~~ | ~~β + α 全部~~ | **β/α 前半クリアで意義消失** | — | +| **Step 8-14 候補 ε(新規)** | **B1 筐体幾何精度の検討**(形改善の最後の砦) | 未実施 | ⭐⭐⭐ | +| **Step 8-14 候補 ζ(新規)** | **LED 軸方向のティルト仮説**(評価板中央向きに数度傾ける) | 未実施 | ⭐⭐ | -→ **A1 除外確定により Step 8-14 は α(A8 + A9)が筋**.低コストで既存技術延長.レンダ時間も 1 ジョブ 8 分程度で済む. +→ **A1/A9 がともに「形改善ではない」と確定**したため,残る残差 (CoV 0.158 vs TARGET 0.069) の **形改善は構造的に光分布を再配分する要素 (B1 / ζ) が本筋**.均一化フィルタ系 (A8) は数値改善のみ期待. ## 関連ドキュメント (Related Documents) diff --git "a/docs/06_TEST/TEST_01_\343\202\255\343\203\243\343\203\252\343\203\226\343\203\254\343\203\274\343\202\267\343\203\247\343\203\263\346\244\234\350\250\274\345\261\245\346\255\264.md" "b/docs/06_TEST/TEST_01_\343\202\255\343\203\243\343\203\252\343\203\226\343\203\254\343\203\274\343\202\267\343\203\247\343\203\263\346\244\234\350\250\274\345\261\245\346\255\264.md" index 685464e..43db593 100644 --- "a/docs/06_TEST/TEST_01_\343\202\255\343\203\243\343\203\252\343\203\226\343\203\254\343\203\274\343\202\267\343\203\247\343\203\263\346\244\234\350\250\274\345\261\245\346\255\264.md" +++ "b/docs/06_TEST/TEST_01_\343\202\255\343\203\243\343\203\252\343\203\226\343\203\254\343\203\274\343\202\267\343\203\247\343\203\263\346\244\234\350\250\274\345\261\245\346\255\264.md" @@ -47,6 +47,8 @@ | 8-14 候補 γ (D1) ROI 整合確認 | 変更なし | — | — | — | — | — | **クリア(ROI 物理整合差 < 1%)** | | 8-14 候補 β (A1) 混合 BSDF 最良 | R0.85 + blendbsdf w0.05 α0.6 | linear | 207.82* | 0.1851 | 24.44 | 1.323 | 残差 2.66× (悪化) | | 8-14 候補 β (A1) 混合 BSDF 最悪 | R0.85 + blendbsdf w0.30 α0.1 | linear | 207.82* | 0.2433 | 26.81 | 1.366 | **クリア(実機方向に作用しない)** | +| 8-14 候補 α 前半 (A9) d=16 | R0.85, **d=16**, spp1024 | linear | 207.82* | 0.1604 | 20.59 | 1.259 | **2.31×**(数値改善あり、形不変) | +| 8-14 候補 α 前半 (A9) d=28 | R0.85, **d=28**, spp1024 | linear | 207.82* | 0.1583 | 20.24 | 1.254 | **2.28×**(収束、形不変) | \* mean アンカー固定.R, d, spp は `reflectance`, `max_depth`, `samples_per_pixel`. @@ -567,6 +569,58 @@ - **副次成果**: スキーマ層に `blendbsdf` + `roughconductor` のサポートが入った.今後の物理モデル試行(例:thindielectric 評価板の薄板透過モデル,特殊壁面の光沢追加)でも再利用可能 - **次のアクション**: A8 (反射率 R=0.85→0.89) を試す Step 8-14 候補 α,または A9 (max_depth 8→16+) を試す案,または両方の組み合わせを実施.[TECH_10 § Step 8-14 候補選定](../05_TECH/TECH_10_残差主因候補の網羅整理.md#step-8-14-候補選定次のアクション) 参照. +### Step 8-14 候補 α 前半: A9 max_depth スイープ {8, 12, 16, 20, 28} + +- **日付**: 2026-06-03 +- **位置付け**: TECH_10 で 🥈 2 位だった **A9「max_depth 8 → 16+」** の感度分析.「反射回数が少ないものの影響は強いが,0.85 の 8 乗 ≈ 0.27 で 0.01 まで続けるべき」というユーザー仮説を 5 値スイープで検証. +- **仮説**: 現状 max_depth=8 では多重反射の累積寄与の **理論累積 100% (R/(1-R)=5.67) に対して 72.7% しか取り込めていない**.残り 27% を打ち切っており,これが形状指標(特に勾配と動径 min/max)を歪めている. + +| max_depth | 最終 bounce 寄与 R^n | 累積 R·(1-R^n)/(1-R) | 理論累積に対する割合 | +| --- | --- | --- | --- | +| 8(現状) | 0.273 | 4.12 | **72.7%** | +| 12 | 0.142 | 4.87 | 85.8% | +| 16 | 0.074 | 5.25 | 92.6% | +| 20 | 0.039 | 5.45 | 96.1% | +| **28** | **0.013** | **5.60** | **98.7%** | + +- **実装**: `scripts/sweep_max_depth.py` で `configs/scene_baseline.yaml` の `render.max_depth` のみを書き換えた 5 つの一時 YAML を生成 → sm-render → sm-evaluate (`configs/eval_baseline_dng_target.yaml`, linear + mean アンカー 207.82).約 85 分. +- **結果**: 全 4 形状指標が **単調に実機方向に改善**: + +| max_depth | mean | CoV | 勾配% | C/P | 動径 min/max | +| --- | --- | --- | --- | --- | --- | +| **8** | 207.82 | 0.1817 | 24.01 | 1.316 | 0.628 | +| 12 | 207.82 | 0.1651 | 21.35 | 1.272 | 0.671 | +| 16 | 207.82 | 0.1604 | 20.59 | 1.259 | 0.683 | +| 20 | 207.82 | 0.1589 | 20.35 | 1.256 | 0.687 | +| **28** | 207.82 | **0.1583** | **20.24** | **1.254** | **0.689** | +| **実機 TARGET** | — | **0.0695** | **8.35** | **1.091** | **0.860** | + +- **残差縮小率 (d=8 → 28)**: + +| 指標 | d=8 | d=28 | 縮小率 = (d8 − d28) / (d8 − TARGET) | +| --- | --- | --- | --- | +| CoV | 0.1817 | 0.1583 | **20.9%** | +| 勾配 | 24.01% | 20.24% | **24.1%** | +| C/P | 1.316 | 1.254 | **27.6%** | +| 動径 min/max | 0.628 | 0.689 | **26.3%** | + +- **収束観察**: 実用的な収束は **d=16 で達成**(d=16 → 28 で CoV 1.3% のみ動く).理論累積 (R^n) より早く収束.これは **「直接光と初期反射が支配的,後段の累積はわずかな ambient しか足さない」** という物理的直感と整合. +- **「形」の観察**([`output/_slide_panels/max_depth_sweep_shape.png`](#) 2 段比較 + `max_depth_sweep_relative.png` mean=1 正規化): + - 上段 共有スケール: 全 5 画像とも **下端中央が明るく上端が暗い Y 軸単調勾配**で、 contrast が d=8→28 で緩む + - 下段 独立スケール(各画像 1%-99% 自己 contrast 最大化): 全 5 画像とも **同じ形を異なる contrast で再現** → **形は完全に不変** + - mean=1 正規化: d=8 で [0.3〜1.3]、d=28 で [0.4〜1.2] と **相対輝度の範囲が狭まる**(ambient 化が進む)が,**空間パターン (Y 単調勾配) は不変** +- **解釈・学び**: + - **「精度(数値指標)」は明確に改善**.ユーザー仮説「max_depth を増やすべき」は方向性として正しく,d=8 → 28 で残差を 21〜28% 縮められた + - **「形」は変わらない**.多重反射の累積は **空間的にほぼ均一な ambient 成分**であり、これを足しても暗い領域と明るい領域の **絶対差は縮むが空間配置は不変**.「LED が下にしかなく評価板の中央〜上端は内壁経由でしか光が届かない」という **配置と幾何に由来する非対称性は除去できない** + - これは TEST_03 の主張「Step 8-1〜8-7 で形は全く変わっていない」と整合.**max_depth スイープも同じパターン**で,「均一化フィルタ系の改善は数値には効くが形には効かない」が再確認された + - **新キャリブベースラインは d=16 で十分**(d=28 と CoV 1.3% 差,レンダ時間 17 分 vs 30 分).「物理整合の極致」を求めるなら d=28 + - **A9 は数値改善には有効だが形改善の主因ではない**ことが確定.TECH_10 優先順位を更新(A9 を「数値改善のみクリア」化,A8 を 🥇 1 位継続,**形改善の本筋候補は B1 幾何精度に集約**) +- **副次成果**: `scripts/sweep_max_depth.py` + `scripts/build_max_depth_panel.py`(収束カーブ + 数値表 + 輝度マップ)+ shape 比較スクリプトのテンプレ.今後 spp など他のレンダパラメータ感度分析にも流用可能 +- **次のアクション**: + - 数値改善目的: d=16 を新キャリブベースラインに採用(要検討) + - 形改善目的: **B1 筐体幾何精度の検討**(3D プリンタの実物理表面の偏り・LED 軸方向のティルト仮説等) + - A8 反射率 R=0.89 も次の試行候補(数値改善は予想,形改善は予想薄) + ## Step 8-N 候補: LSC 順方向モデル(不採用 — Step 8-13 で方針確定) > **2026-06-02 注記**: 本セクションは当時「実機側を変更しない前提」で「シミュ側に LSC モデル化を追加して残差を埋める」案として立案されていた.[Step 8-13](#step-8-13-新方針-実機-dng--lsc-linear-でのシミュ突合) で **実機側に LSC 順方向適用** するアプローチが MiniTias-Evaluation で確立した結果,**本案は不採用**となった.以下は歴史的記録として残す.詳細な却下理由は [TECH_07 §検討した代替案](../05_TECH/TECH_07_LSC取り扱い方針.md) 参照. diff --git "a/docs/06_TEST/TEST_02_\343\202\267\343\203\237\343\203\245\343\203\254\343\203\274\343\202\267\343\203\247\343\203\263\345\256\237\350\241\214\350\250\230\351\214\262.md" "b/docs/06_TEST/TEST_02_\343\202\267\343\203\237\343\203\245\343\203\254\343\203\274\343\202\267\343\203\247\343\203\263\345\256\237\350\241\214\350\250\230\351\214\262.md" index d8407ad..8f7caf8 100644 --- "a/docs/06_TEST/TEST_02_\343\202\267\343\203\237\343\203\245\343\203\254\343\203\274\343\202\267\343\203\247\343\203\263\345\256\237\350\241\214\350\250\230\351\214\262.md" +++ "b/docs/06_TEST/TEST_02_\343\202\267\343\203\237\343\203\245\343\203\254\343\203\274\343\202\267\343\203\247\343\203\263\345\256\237\350\241\214\350\250\230\351\214\262.md" @@ -38,6 +38,12 @@ | `output/_slide_panels/blend_bsdf_metrics.png` | 8-14 候補 β | — | — | 9 ジョブの数値表 (CoV/勾配/C/P/動径 min/max) | TEST_01 Step 8-14 β 数値追跡用 | | `output/led_group_sanity/{bottom,right,left}/` | LED sanity check | `configs/scene_baseline.yaml` から lights を各 LED 基板(6 個ずつ)のみに絞った一時 YAML × 3 種 (d=8, spp=1024) | (評価なし,目視確認) | mean: bottom=98.82 / right=47.10 / left=47.08 | [TEST_07 LED グループ別動作確認](TEST_07_LEDグループ別動作確認.md) — 方向性・対称性・線形性の sanity check | | `output/_slide_panels/led_group_sanity{,_focus,_right_left}.png` | LED sanity check | — | — | 1×3 / 1×2 / 6 列の比較パネル | TEST_07 視覚確認用 | +| `output/max_depth_sweep/d{8,12,16,20,28}/` (5 ディレクトリ) | 8-14 候補 α 前半 | `configs/scene_baseline.yaml` を `scripts/sweep_max_depth.py` で `render.max_depth` のみ書き換えた 5 種 (R=0.85, spp=1024) | `configs/eval_baseline_dng_target.yaml` (linear + mean アンカー 207.82) | d=28 で CoV=0.1583, 勾配=20.24%, C/P=1.254, 動径 min/max=0.689 — [TEST_01 § Step 8-14 候補 α 前半 結果表](TEST_01_キャリブレーション検証履歴.md#step-8-14-候補-α-前半-a9-max_depth-スイープ-8-12-16-20-28) 参照 | TEST_01 Step 8-14 候補 α 前半 検証.**A9 は数値改善のみで形改善ではない**ことを確定.d=16 が新キャリブベースライン候補 | +| `output/max_depth_sweep/summary.csv` | 8-14 候補 α 前半 | — | — | 5 ジョブ集約 CSV (max_depth, elapsed_s + 12 指標カラム) | `scripts/sweep_max_depth.py` の集約出力.`scripts/build_max_depth_panel.py` の入力 | +| `output/_slide_panels/max_depth_sweep.png` | 8-14 候補 α 前半 | — | — | 1×5 輝度マップ比較パネル (shared vmin/vmax) | TEST_01 数値追跡用 | +| `output/_slide_panels/max_depth_convergence.png` | 8-14 候補 α 前半 | — | — | 収束カーブ 2×2 (CoV/勾配/C/P/動径 min/max vs max_depth) | TEST_01 収束観察用 | +| `output/_slide_panels/max_depth_sweep_shape.png` | 8-14 候補 α 前半 | — | — | 上下 2 段比較 (上=共有スケール / 下=独立スケール 自己 contrast 最大化) | TEST_01 「形」追跡用 — 形不変の視覚証拠 | +| `output/_slide_panels/max_depth_sweep_relative.png` | 8-14 候補 α 前半 | — | — | 1×5 mean=1 正規化 (相対輝度 1.0=画像平均) | TEST_01 ambient 化観察用 | ### sweep_summary.csv の値(参考、TEST_01 line と一致確認用) @@ -73,6 +79,8 @@ | blend BSDF 比較パネル生成 | `uv run python scripts/build_blend_bsdf_panel.py` (要 `output/blend_sweep/summary.csv` + 各 `luminance.npy`) | | LED グループ別 sanity check (TEST_07) | `$env:DRJIT_LIBLLVM_PATH = "C:\Program Files\LLVM\bin\LLVM-C.dll"; uv run python scripts/sanity_led_groups.py` (3 ジョブ × 約 8.5 分 = 約 25 分) | | LED sanity check 比較パネル生成 | `uv run python scripts/build_led_group_sanity_panel.py` (要 `output/led_group_sanity/{bottom,right,left}/luminance.npy` + `output/baseline/luminance.npy`) | +| max_depth 5 値スイープ (Step 8-14 α 前半) | `$env:DRJIT_LIBLLVM_PATH = "C:\Program Files\LLVM\bin\LLVM-C.dll"; uv run python scripts/sweep_max_depth.py` (5 ジョブ × 8.5〜30 分 = 約 85 分) | +| max_depth 比較パネル生成 | `uv run python scripts/build_max_depth_panel.py` (1×5 輝度マップ + 2×2 収束カーブ.要 `output/max_depth_sweep/summary.csv` + 各 `luminance.npy`) | ## 過去の整理実績 (Cleanup History) diff --git a/scripts/build_max_depth_panel.py b/scripts/build_max_depth_panel.py new file mode 100644 index 0000000..ab299ec --- /dev/null +++ b/scripts/build_max_depth_panel.py @@ -0,0 +1,149 @@ +"""max_depth スイープの比較パネル生成. + +scripts/sweep_max_depth.py の出力から以下を生成: + - 1×5 輝度マップパネル (shared vmin/vmax) + - 収束カーブ (max_depth vs CoV / 勾配 / C/P / 動径 min/max) + - 数値表 + +実行方法: + uv run python scripts/build_max_depth_panel.py + +前提条件: + - output/max_depth_sweep/summary.csv + d{N}/luminance.npy が揃っていること + +出力: + - output/_slide_panels/max_depth_sweep.png (1×5 輝度マップ) + - output/_slide_panels/max_depth_convergence.png (収束カーブ) +""" + +from __future__ import annotations + +import csv +import sys +from pathlib import Path + +import matplotlib +import numpy as np + +matplotlib.use("Agg") +import matplotlib.font_manager as fm +import matplotlib.pyplot as plt + +ROOT = Path(__file__).resolve().parents[1] +SWEEP_BASE = ROOT / "output" / "max_depth_sweep" +SUMMARY_CSV = SWEEP_BASE / "summary.csv" +OUT_LUM_PANEL = ROOT / "output" / "_slide_panels" / "max_depth_sweep.png" +OUT_CONV_PANEL = ROOT / "output" / "_slide_panels" / "max_depth_convergence.png" + +# 実機 TARGET (linear + LSC,eval_baseline_dng_target.yaml と整合) +TARGET = {"cov": 0.0695, "gradient": 8.35, "cp": 1.091, "rmm": 0.860} + + +def _setup_japanese_font() -> None: + for p in ("C:/Windows/Fonts/meiryo.ttc", "C:/Windows/Fonts/YuGothM.ttc"): + if Path(p).exists(): + fm.fontManager.addfont(p) + plt.rcParams["font.family"] = fm.FontProperties(fname=p).get_name() + return + + +def main() -> int: + _setup_japanese_font() + + if not SUMMARY_CSV.exists(): + print(f"エラー: {SUMMARY_CSV} が見つかりません", file=sys.stderr) + return 1 + + with SUMMARY_CSV.open(encoding="utf-8", newline="") as f: + rows = list(csv.DictReader(f)) + if not rows: + print(f"エラー: summary.csv が空", file=sys.stderr) + return 1 + + depths = [int(r["max_depth"]) for r in rows] + covs = [float(r["cov"]) for r in rows] + grads = [float(r["gradient_magnitude"]) for r in rows] + cps = [float(r["center_periphery_ratio"]) for r in rows] + rmms = [float(r["radial_min_max_ratio"]) for r in rows] + means = [float(r["mean"]) for r in rows] + + # 各 npy 読み込み + lums = [] + for r in rows: + npy = SWEEP_BASE / r["job"] / "luminance.npy" + if not npy.exists(): + print(f"警告: {npy} 見つからずスキップ", file=sys.stderr) + lums.append(None) + continue + arr = np.load(npy) + lums.append(arr.mean(axis=-1) if arr.ndim == 3 else arr) + + # 共有 vmin/vmax + valid = [x for x in lums if x is not None] + stacked = np.stack(valid) + vmin = float(np.percentile(stacked, 1)) + vmax = float(np.percentile(stacked, 99)) + + # ============ 1×5 輝度マップパネル ============ + n = len(rows) + fig, axes = plt.subplots(1, n, figsize=(4.5 * n, 5.5)) + if n == 1: + axes = [axes] + for ax, d, lum, cov, grad in zip(axes, depths, lums, covs, grads): + if lum is None: + ax.set_title(f"d={d} (missing)") + ax.set_xticks([]); ax.set_yticks([]) + continue + im = ax.imshow(lum, cmap="hot", vmin=vmin, vmax=vmax) + ax.set_title(f"max_depth = {d}\nCoV={cov:.4f} 勾配={grad:.2f}%", fontsize=11) + ax.set_xticks([]); ax.set_yticks([]) + plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04) + + fig.suptitle("max_depth スイープ — 輝度マップ (shared vmin/vmax)", fontsize=14, fontweight="bold") + plt.tight_layout() + OUT_LUM_PANEL.parent.mkdir(parents=True, exist_ok=True) + plt.savefig(OUT_LUM_PANEL, dpi=110, bbox_inches="tight") + print(f"輝度マップパネル保存: {OUT_LUM_PANEL}") + + # ============ 収束カーブ ============ + fig, axes = plt.subplots(2, 2, figsize=(13, 9)) + metrics = [ + ("CoV", covs, TARGET["cov"], "CoV (低いほど均一)"), + ("勾配 [%]", grads, TARGET["gradient"], "勾配 [%] (低いほど均一)"), + ("C/P 比", cps, TARGET["cp"], "C/P 比 (1.0 が理想)"), + ("動径 min/max", rmms, TARGET["rmm"], "動径 min/max (1.0 が理想)"), + ] + for ax, (label, vals, target, ylabel) in zip(axes.flat, metrics): + ax.plot(depths, vals, marker="o", linewidth=2, markersize=10, color="C0", label="シミュ") + ax.axhline(target, color="red", linestyle="--", linewidth=1.5, + label=f"実機 TARGET ({target})") + for d, v in zip(depths, vals): + ax.annotate(f"{v:.4f}", (d, v), textcoords="offset points", + xytext=(0, 10), ha="center", fontsize=9) + ax.set_xlabel("max_depth") + ax.set_ylabel(ylabel) + ax.set_title(label) + ax.grid(alpha=0.3) + ax.legend(loc="best", fontsize=9) + + fig.suptitle( + "max_depth スイープ — 形状指標の収束 (赤破線=実機 TARGET)", + fontsize=14, fontweight="bold", + ) + plt.tight_layout() + plt.savefig(OUT_CONV_PANEL, dpi=110, bbox_inches="tight") + print(f"収束カーブ保存: {OUT_CONV_PANEL}") + + # ============ コンソールサマリ ============ + print() + print("=== サマリ ===") + print(f" {'max_depth':<10}{'mean':<10}{'CoV':<10}{'勾配%':<10}{'C/P':<10}{'r min/max':<10}") + for d, mean, cov, grad, cp, rmm in zip(depths, means, covs, grads, cps, rmms): + print(f" {d:<10}{mean:<10.2f}{cov:<10.4f}{grad:<10.2f}{cp:<10.4f}{rmm:<10.4f}") + print(f" 実機 TARGET: - {TARGET['cov']:<10}{TARGET['gradient']:<10}" + f"{TARGET['cp']:<10}{TARGET['rmm']:<10}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/sweep_max_depth.py b/scripts/sweep_max_depth.py new file mode 100644 index 0000000..802b50f --- /dev/null +++ b/scripts/sweep_max_depth.py @@ -0,0 +1,184 @@ +"""Step 8-14 候補 α 前半: max_depth スイープ. + +R=0.85 のまま max_depth ∈ {8, 12, 16, 20, 28} を 5 値スイープして +多重反射の取りこぼし量と「形」「数値」の収束を観察する. + +理論累積(R/(1-R) を 100% とした場合): + depth= 8 → 72.7% (現状ベースライン) + depth=12 → 85.8% + depth=16 → 92.6% + depth=20 → 96.1% + depth=28 → 98.7% + +実行方法: + uv run python scripts/sweep_max_depth.py + +前提条件: + - configs/scene_baseline.yaml が存在すること + - configs/eval_baseline_dng_target.yaml が存在すること + - sm-render / sm-evaluate が PATH に通っていること + +出力: + - output/max_depth_sweep/d{N}/luminance.npy (各ジョブのレンダリング結果) + - output/max_depth_sweep/d{N}/metrics.csv (各ジョブの評価指標) + - output/max_depth_sweep/summary.csv (全 5 ジョブの集約結果) + - configs/_tmp_depth/scene_d{N}.yaml (一時 YAML,実行後も残す) + +TECH_10 参照: 残差主因候補 §A9「max_depth 8 → 16+」 +""" + +from __future__ import annotations + +import csv +import subprocess +import sys +import time +from pathlib import Path + +import yaml + +# --------------------------------------------------------------------------- +# 定数 +# --------------------------------------------------------------------------- + +ROOT = Path(__file__).resolve().parents[1] +TEMPLATE_YAML = ROOT / "configs" / "scene_baseline.yaml" +EVAL_CONFIG_YAML = ROOT / "configs" / "eval_baseline_dng_target.yaml" +TMP_DIR = ROOT / "configs" / "_tmp_depth" +OUTPUT_BASE = ROOT / "output" / "max_depth_sweep" + +# スイープ値(R=0.85 で最終 bounce の寄与が 0.27 → 0.01 まで) +DEPTHS = [8, 12, 16, 20, 28] + +SUMMARY_EXTRA_COLS = [ + "cov", "std", "max_min_ratio", "mean", "max", "min", + "center_periphery_ratio", "gradient_magnitude", + "radial_min_max_ratio", "radial_min_distance", "radial_min", "radial_max", +] + + +def _generate_tmp_yaml(depth: int) -> Path: + """指定 max_depth の一時 YAML を生成する.""" + TMP_DIR.mkdir(parents=True, exist_ok=True) + + with TEMPLATE_YAML.open(encoding="utf-8") as f: + data = yaml.safe_load(f) + + # geometry.meshes の STL 相対パスを絶対化 + template_base = TEMPLATE_YAML.parent + for item in data.get("geometry", {}).get("meshes", []): + rel_path = item.get("path") + if rel_path and not Path(rel_path).is_absolute(): + item["path"] = str((template_base / rel_path).resolve()) + + # render.max_depth を書き換え + data["render"]["max_depth"] = depth + data["name"] = f"baseline_minitias_depth_{depth}" + + tmp_path = TMP_DIR / f"scene_d{depth}.yaml" + with tmp_path.open("w", encoding="utf-8") as f: + yaml.dump(data, f, allow_unicode=True, default_flow_style=False, sort_keys=False) + return tmp_path + + +def _run(cmd: list[str], label: str) -> int: + """外部コマンドを実行.""" + print(f" [run] {' '.join(cmd)}") + result = subprocess.run(cmd, check=False) # noqa: S603 + if result.returncode != 0: + print(f" [ERROR] {label} failed (returncode={result.returncode})", file=sys.stderr) + return result.returncode + + +def _read_metrics(csv_path: Path) -> dict: + """metrics.csv の 1 行目を読む.""" + with csv_path.open(encoding="utf-8", newline="") as f: + rows = list(csv.DictReader(f)) + if not rows: + msg = f"metrics.csv が空: {csv_path}" + raise ValueError(msg) + return rows[0] + + +def main() -> int: + if not TEMPLATE_YAML.exists(): + print(f"エラー: テンプレート YAML が見つかりません: {TEMPLATE_YAML}", file=sys.stderr) + return 1 + if not EVAL_CONFIG_YAML.exists(): + print(f"エラー: 評価 config YAML が見つかりません: {EVAL_CONFIG_YAML}", file=sys.stderr) + return 1 + + OUTPUT_BASE.mkdir(parents=True, exist_ok=True) + + print(f"=== max_depth sweep: {len(DEPTHS)} jobs ===") + print(f" depths: {DEPTHS}") + print(f" output: {OUTPUT_BASE}") + print() + + failed: list[str] = [] + rows: list[dict] = [] + total_start = time.time() + + for i, d in enumerate(DEPTHS, start=1): + job_name = f"d{d}" + output_dir = OUTPUT_BASE / job_name + metrics_csv = output_dir / "metrics.csv" + + print(f"--- Job {i}/{len(DEPTHS)}: {job_name} ---") + job_start = time.time() + + tmp_yaml = _generate_tmp_yaml(d) + print(f" tmp YAML: {tmp_yaml}") + + if _run(["sm-render", "--scene", str(tmp_yaml), "--output", str(output_dir)], + f"sm-render ({job_name})") != 0: + failed.append(f"{job_name} [render]") + print(" -> スキップ: render 失敗\n") + continue + + npy = output_dir / "luminance.npy" + if _run(["sm-evaluate", "--image", str(npy), "--config", str(EVAL_CONFIG_YAML), + "--output", str(output_dir)], f"sm-evaluate ({job_name})") != 0: + failed.append(f"{job_name} [evaluate]") + print(" -> スキップ: evaluate 失敗\n") + continue + + try: + m = _read_metrics(metrics_csv) + except (FileNotFoundError, ValueError) as exc: + print(f" [ERROR] metrics.csv 読み込み失敗: {exc}", file=sys.stderr) + failed.append(f"{job_name} [metrics_read]") + print() + continue + + elapsed = time.time() - job_start + cov = m.get("cov", "N/A") + grad = m.get("gradient_magnitude", "N/A") + print(f" -> 完了 ({elapsed:.1f}s) CoV={cov}, grad={grad}%") + + row: dict = {"job": job_name, "max_depth": d, "elapsed_s": f"{elapsed:.1f}"} + for col in SUMMARY_EXTRA_COLS: + row[col] = m.get(col, "") + rows.append(row) + print() + + summary_csv = OUTPUT_BASE / "summary.csv" + if rows: + fieldnames = ["job", "max_depth", "elapsed_s", *SUMMARY_EXTRA_COLS] + with summary_csv.open("w", encoding="utf-8", newline="") as f: + writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore") + writer.writeheader() + writer.writerows(rows) + print(f"集約 CSV 保存: {summary_csv}") + + total_elapsed = time.time() - total_start + print(f"\n=== 完了: {len(rows)}/{len(DEPTHS)} 成功, 総時間 {total_elapsed:.1f}s ===") + + if failed: + print("失敗ジョブ:", failed, file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main())