diff --git a/CLAUDE.md b/CLAUDE.md index cd40bcd..d11d783 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,6 +41,7 @@ 補足: **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× 議論の前提が担保された. 補足: **scene_baseline.yaml の max_depth を 8 → 16 へ更新 (2026-06-03)**.Step 8-14 候補 α 前半の結論「d=16 が実用収束点」を反映.SPEC_03 の既知差分 #6 (未対処の max_depth=8) を解消.SPEC_03 の関連箇所 4 箇所 (パラメータ表 / 多重反射の説明 / 制限セクション / 既知差分一覧) を一括更新.TEST_02 の `output/baseline/` 行に「d=8 時代の古い結果」注記を追加(output/baseline/luminance.npy は 2026-05-18 生成で d=8、現 scene_baseline.yaml の d=16 とは不整合.必要時に再生成).影響: 今後の sm-render は自動的に d=16 で実行され、理論累積 92.6% を取り込む.計算時間は d=8 の約 2× (8.5 分 → 17 分) に増加するが、形状指標 4 つすべてが d=8 時代より実機方向に改善 (CoV 0.1817 → 0.1604, 勾配 24.01% → 20.59%).次のスイープ系 (A8 反射率, A10 サブサーフェス散乱等) は自動的に d=16 ベースで実行される. +補足: **Step 8-14 候補 α 後半 (A8 反射率スイープ) 完了 — 数値改善のみ・形は逆効果 / 「数値の偽物の一致」の初の定量実証 (2026-06-03)**.`scripts/sweep_reflectance.py` で d=16 新基準のもと R ∈ {0.75, 0.80, 0.85, 0.89, 0.92} を 5 値スイープ.新指標 (shape_metrics) を初めて感度分析に投入.結果: 既存 4 指標 (CoV/勾配/C/P/動径 min/max) は単調改善 (R=0.75 → 0.92 で CoV 0.186→0.142 で残差 24% 縮小,勾配 24.7→17.7%,rmin/max 0.620→0.731) するが,**新指標 angular_ratio は 0.408→0.494 で実機方向と逆に増加**.実機は中央ピーク楕円 (同心円的 = angular_ratio 小予想) なので **形は実機から遠ざかる**.TECH_11 で予想していた「数値の偽物の一致」を初めて現実データで実証.物理的解釈: R 上昇で ambient 増 → 半径方向の偏りは圧縮されるが角度方向のばらつきは相対的に圧縮されにくい → 「半径 var / 角度 var」バランス崩れ angular_ratio 増加.「Y 単調勾配の contrast を緩める代わりに角度方向のムラを相対的に強調」する効果.**A1/A9 検証より強い発見**: 「均一化フィルタ系は形を変えない」を超えて「数値改善が形を逆方向に動かす」ケースの実証.TECH_10 優先順位を再更新: A8 を「数値改善のみクリア」化.**形改善の本筋は B1 筐体幾何精度に最終確定**(A1/A8/A9 すべてクリア済で構造的再配分系のみ残る).副次発見: R=0.85 の CoV=0.1604 が max_depth スイープ d=16 と完全一致 (別 sweep 間の数値整合性 OK).centroid_y_norm 0.045→0.036, eccentricity 0.444→0.414 はわずか改善 (但し angular_ratio 悪化が支配).新指標を導入していなければ気づけない発見であり、形状指標見直し (TECH_11) の正当性が定量データで裏付けられた.[TECH_10 § A8 検証結果](docs/05_TECH/TECH_10_残差主因候補の網羅整理.md#a8-検証結果2026-06-03-完了) / [TEST_01 § Step 8-14 候補 α 後半](docs/06_TEST/TEST_01_キャリブレーション検証履歴.md#step-8-14-候補-α-後半-a8-反射率-5-値スイープ-r075-080-085-089-092) 参照.次のアクション: **B1 筐体幾何精度の検討 / A10 サブサーフェス散乱 / ζ LED ティルト** の構造的再配分系候補に進む. 補足: **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 軸方向ティルト仮説 のいずれかを実施. 補足: **形状指標の本質的見直しと新指標導入 (2026-06-03)**.Step 8-14 で「A9 max_depth スイープで数値が実機方向に動いても形は全く変わらない」「A1 非 Lambertian でも形は実機に近づかない」が続けて確定したため,ユーザー指摘「現状 4 指標は不均一性の絶対量しか測れず,Y 軸単調勾配と中央ピーク楕円を構造的に区別できない」に対応する **形を直接評価する 5 指標** を追加実装.`src/sm_tias_lightsim/evaluation/shape_metrics.py` (新規): `calc_intensity_centroid`(輝度重心 5 値)/ `calc_angular_radial_decomposition`(極座標分解,angular_ratio: 0=同心円, 1=方向依存)/ `calc_ssim`(scikit-image 依存, shape_reference 指定時)/ `calc_ncc`(正規化相関係数)/ `calc_intensity_pca`(weighted PCA 主軸・eccentricity)の純関数 5 つ.`MetricsConfig` に `shape_metrics: bool = False` / `shape_reference: str | None = None` を追加(後方互換).`cli/evaluate.py` に `_run_shape_metrics` 関数追加 + コンソールサマリ拡張.`configs/eval_baseline_dng_target_shape.yaml` を新規作成 (shape_metrics=true, shape_reference=null).`pyproject.toml` に `scikit-image>=0.21.0` を追加.[TECH_11 形状指標の見直しと新指標導入](docs/05_TECH/TECH_11_形状指標の見直しと新指標導入.md) で背景・計算式・シミュ vs 実機予想値・過去判断への遡及的注記を記録.TEST_01 俯瞰サマリ上に「形状指標は絶対量のみ測る」注記を追加.TECH_10 A9/A1 検証結果に「新指標での再評価が必要」注記を追加.今後の評価方針: **数値指標 4 + 形指標 5 + 視覚比較の 3 軸評価**で「数値改善のみで形が変わらない候補」と「形を変えうる候補」を明確に区別する. 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 015e52a..4eb248e 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" @@ -8,12 +8,12 @@ | --- | --- | --- | --- | --- | | ~~🥇 1 位~~ | ~~A1. 内壁の非 Lambertian 性(光沢成分)~~ | **✓ クリア** | — | **2026-06-02 検証完了**:3×3 グリッドで全 9 パターンが実機方向に作用しない(むしろ悪化)と判明.純 Lambertian が実機挙動に最も近い.[§ A1 検証結果](#a1-検証結果2026-06-02-完了) 参照 | | ~~🥈 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 クリアで **形改善の最後の砦**として最有力 | +| ~~🥇 1 位~~ | ~~A8. 反射率 R=0.85 → 0.89(物理整合点)~~ | **✓ 数値改善のみクリア(形は逆効果)** | — | **2026-06-03 検証完了**:5 値スイープ {0.75, 0.80, 0.85, 0.89, 0.92} で d=16 ベース.既存 4 指標は単調改善するが **angular_ratio は 0.408 → 0.494 で増加(実機方向と逆)**.「形状指標の偽物の一致」を初めて定量実証.[§ A8 検証結果](#a8-検証結果2026-06-03-完了) 参照 | +| **🥇 1 位(形改善・確定)** | **B1. 筐体内部の幾何精度** | **大の可能性** | 高 | 3D プリンタの実物理表面 vs 設計表面.A1/A8/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-03 更新**: A1 に続き **A9 max_depth も「数値改善のみクリア・形改善ではない」と確定**(5 値スイープで d=28 まで CoV 21% 縮小、ただし「形」は Y 軸単調勾配のまま不変).**形改善の最有力は B1 筐体幾何精度に集約**.数値改善目的の最有力は A8 反射率 R=0.89(だが形改善は予想薄). +→ **2026-06-03 二次更新**: A1/A9 に続き **A8 反射率も「数値改善のみクリア・形は逆効果」と確定**(5 値スイープで R=0.92 まで CoV 24% 縮小、**ただし angular_ratio は 0.408→0.494 で実機方向と逆**).[TECH_11](TECH_11_形状指標の見直しと新指標導入.md) で予想していた「数値の偽物の一致」を初めて定量実証.**形改善の本筋は B1 筐体幾何精度で確定**.次の候補は B1 / A10 サブサーフェス散乱 / ζ LED ティルト など構造的再配分系. ## 背景 (Background) @@ -41,7 +41,7 @@ | 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 を導出.d=16 ベース新方針下で再評価.A9 と同系統の均一化フィルタなので **数値改善は期待,形改善は予想薄** | +| ~~A8~~ | ~~反射率 R = 0.85 → 0.89~~ | R=0.85(暫定) | **✓ 数値改善のみクリア(形は逆効果)** | **2026-06-03 検証完了**:5 値スイープ {0.75, 0.80, 0.85, 0.89, 0.92} で d=16 ベース.既存 4 指標は実機方向に単調改善(CoV 0.186→0.142, 残差 24% 縮小)するが **新指標 angular_ratio は 0.408→0.494 で実機方向と逆に増加(実機は同心円的予想)**.[§ A8 検証結果](#a8-検証結果2026-06-03-完了) 参照 | | ~~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,67 @@ | A2/A3 LED 配光の個体差 | データシート Lambertian 整合済([TECH_09](TECH_09_LEDデータシート調査結果.md)).実物の個体差ばらつきは実測ゴニオフォトメータが必要で高コスト | | E1/E2 環境・運用系 | 新品撮影・通常室温で影響軽微 | +## A8 検証結果(2026-06-03 完了) + +候補リストで **A8「反射率 R=0.85 → 0.89(物理整合点)」** を 5 値スイープ {0.75, 0.80, 0.85, 0.89, 0.92} で検証したところ,**既存 4 指標は実機方向に改善するが、新指標 angular_ratio は実機方向と逆に動く**ことが判明.[TECH_11](TECH_11_形状指標の見直しと新指標導入.md) で予想していた「**数値の偽物の一致**」を初めて定量実証した. + +### 検証手順 + +1. **仮説**: ① R 上昇で既存 4 指標は ambient 増加で改善方向 ② 新指標 angular_ratio は不変または同心円方向(実機の中央ピーク楕円に整合) +2. **実装**: `scripts/sweep_reflectance.py` で `configs/scene_baseline.yaml` (**d=16 新基準**) の `materials.printer_white.reflectance` のみ書き換え.評価は `configs/eval_baseline_dng_target_shape.yaml` で **新指標 (shape_metrics) を初投入**.約 85 分. +3. **再現性確認**: R=0.85 の CoV=0.1604 は max_depth スイープ d=16 (CoV=0.1604) と **完全一致** → 別 sweep 間の数値整合性 OK + +### 結果 + +| R | CoV | 勾配% | C/P | rmin/max | **angular_ratio** | centroid_y_norm | eccentricity | +| --- | --- | --- | --- | --- | --- | --- | --- | +| 0.75 | 0.1861 | 24.67 | 1.327 | 0.620 | **0.4079** | 0.0453 | 0.444 | +| 0.80 | 0.1735 | 22.67 | 1.293 | 0.649 | **0.4282** | 0.0428 | 0.436 | +| 0.85 | 0.1604 | 20.59 | 1.259 | 0.684 | **0.4536** | 0.0400 | 0.426 | +| **0.89** | 0.1501 | 18.96 | 1.234 | 0.710 | **0.4761** | 0.0377 | 0.419 | +| 0.92 | **0.1424** | **17.72** | **1.215** | **0.731** | **0.4938** | 0.0358 | 0.414 | +| **実機 TARGET** | **0.0695** | **8.35** | **1.091** | **0.860** | **小予想** | **0** | — | + +### ★ 形状指標見直しの初の定量実証 ★ + +| 指標 | R 0.75→0.92 の動き | 実機方向か | +| --- | --- | --- | +| CoV | 0.186 → 0.142 で **改善 24%** | ✓ | +| 勾配 | 24.7 → 17.7 で **改善 28%** | ✓ | +| C/P | 1.327 → 1.215 で **改善** | ✓ | +| rmin/max | 0.620 → 0.731 で **改善** | ✓ | +| **angular_ratio** | **0.408 → 0.494 で増加 21%** | **✗ 逆方向(実機は同心円的)** | + +→ **既存 4 指標が「改善」と判定する一方で、新指標 angular_ratio は「形は実機から遠ざかる」と判定**.[TECH_11](TECH_11_形状指標の見直しと新指標導入.md) で予想していた「数値の偽物の一致」が **現実に起きていることの初の定量証拠**. + +### 物理的解釈 + +- R 上昇 → ambient 成分増加 → 半径方向の偏り(評価板下端 vs 上端の差)が圧縮 → CoV/勾配/rmin/max 改善 +- 角度方向のばらつき(X 方向の波打ち等)は **相対的に圧縮されにくい** +- 結果「半径 var / 角度 var」バランスが崩れ angular_ratio が **増加** +- R 上昇は **「Y 単調勾配の contrast を緩める代わりに角度方向のムラを相対的に強調する」**.実機の「中央ピーク楕円(=同心円的)」とは **形が遠ざかる** + +### 判定と意味 + +- **A8 は数値改善には有効だが形改善ではない**(**むしろ形は逆効果**).候補リストから「数値改善のみクリア」として除外 +- A1/A9 検証より **強い発見**: 「均一化フィルタ系の改善は形を変えない」を超えて **「数値改善が形を逆方向に動かす」ケースの実証** +- **TECH_11 の評価方針(数値 + 形 + 視覚の 3 軸)の妥当性が定量データで裏付けられた**.今後の試行で新指標必須 +- TECH_10 優先順位を再更新: A8 をクリア化.**形改善の本筋は B1 幾何精度に最終確定** + +### 副次発見 + +- 反射率を上げると centroid_y_norm がわずか減少(0.045→0.036)= 重心が画像中心に近づく方向(**形改善の弱い兆候**) +- eccentricity がわずか減少(0.444→0.414)= 楕円率がわずか改善 +- ただし angular_ratio の悪化が支配的で、全体としては形悪化と判定 + +### ユーザー仮説の評価 + +ユーザー要請「反射率を変えて結果を見たい」は **重要な実証実験**として完了.結果として: + +- 数値だけ見れば「R=0.92 が最良」と判定される(CoV 0.1424) +- しかし新指標も合わせて見ると **R=0.75 が最も同心円的(実機方向に近い形)** +- これは **新指標を導入していなければ気づけない発見**であり、形状指標見直しの **正当性が定量データで裏付けられた** + ## A9 検証結果(2026-06-03 完了) 候補リストで **A9「max_depth 8 → 16+」** を 5 値スイープ {8, 12, 16, 20, 28} で検証したところ,**数値指標は実機方向に単調改善するが「形」は完全に不変**であることが確定した.均一化フィルタとして働くが空間再配分は起こさない. @@ -345,7 +406,7 @@ | Step 8-14 候補 | 内容 | 状態 | 推奨度 | | --- | --- | --- | --- | | ~~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 候補 α 後半~~ | ~~A8 反射率 R=0.85 → 0.89(d=16 ベース)~~ | **✓ 2026-06-03 完了(数値改善のみクリア — angular_ratio は実機方向と逆)** | — | | ~~Step 8-14 候補 β~~ | ~~A1 内壁の非 Lambertian 性(光沢成分混合 BSDF)~~ | **✓ 2026-06-02 完了(クリア — 実機方向に作用しない)** | — | | ~~Step 8-14 候補 γ~~ | ~~D1 ROI 整合の再確認~~ | **✓ 2026-06-02 完了(クリア — 物理差 < 1%)** | — | | ~~Step 8-14 候補 δ~~ | ~~β + α 全部~~ | **β/α 前半クリアで意義消失** | — | 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 b8fa373..ae00bc4 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" @@ -623,6 +623,65 @@ - 形改善目的: **B1 筐体幾何精度の検討**(3D プリンタの実物理表面の偏り・LED 軸方向のティルト仮説等) - A8 反射率 R=0.89 も次の試行候補(数値改善は予想,形改善は予想薄) +### Step 8-14 候補 α 後半: A8 反射率 5 値スイープ R∈{0.75, 0.80, 0.85, 0.89, 0.92} + +- **日付**: 2026-06-03 +- **位置付け**: Step 8-14 候補 α の後半.**新指標 (shape_metrics) を初めて感度分析に投入**して,反射率変化が「数値」と「形」のどちらに効くかを定量検証. +- **仮説**: + 1. 既存 4 指標 (CoV/勾配/C/P/動径 min/max) は R 上昇で実機方向に改善(多重反射 ambient ゲイン) + 2. 新指標 angular_ratio (0=完全同心円, 1=完全角度依存) は不変または同心円方向(実機の中央ピーク楕円は同心円的) + 3. SSIM/NCC は不変(shape_reference 無指定なので NaN) +- **実装**: `scripts/sweep_reflectance.py` で `configs/scene_baseline.yaml` (d=16 新基準) の `materials.printer_white.reflectance` のみを書き換え.評価は `configs/eval_baseline_dng_target_shape.yaml` (新指標含む).約 85 分. +- **結果**: + +| R | mean | CoV | 勾配% | C/P | rmin/max | **angular_ratio** | centroid_y_norm | eccentricity | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 0.75 | 207.82 | 0.1861 | 24.67 | 1.327 | 0.620 | **0.4079** | 0.0453 | 0.444 | +| 0.80 | 207.82 | 0.1735 | 22.67 | 1.293 | 0.649 | **0.4282** | 0.0428 | 0.436 | +| **0.85** | 207.82 | **0.1604** | 20.59 | 1.259 | 0.684 | **0.4536** | 0.0400 | 0.426 | +| **0.89** | 207.82 | **0.1501** | 18.96 | 1.234 | 0.710 | **0.4761** | 0.0377 | 0.419 | +| 0.92 | 207.82 | **0.1424** | 17.72 | 1.215 | 0.731 | **0.4938** | 0.0358 | 0.414 | +| **実機 TARGET** | — | **0.0695** | **8.35** | **1.091** | **0.860** | **小予想** | **0** | — | + +- **★ 形状指標見直しの初の定量実証 ★**: + +| 指標 | R 0.75→0.92 の動き | 実機方向か | +| --- | --- | --- | +| CoV | 0.186 → 0.142 で **改善 24%** | ✓ | +| 勾配 | 24.7 → 17.7 で **改善 28%** | ✓ | +| C/P | 1.327 → 1.215 で **改善** | ✓ | +| rmin/max | 0.620 → 0.731 で **改善** | ✓ | +| **angular_ratio** | **0.408 → 0.494 で増加 21%** | **✗ 逆方向(実機は同心円的)** | +| centroid_y_norm | 0.045 → 0.036 で減少 | ✓ わずか | +| eccentricity | 0.444 → 0.414 で減少 | ✓ わずか | + +→ **既存 4 指標が「改善」と判定する一方で、新指標 angular_ratio は「形は実機から遠ざかる」と判定**.[TECH_11](../05_TECH/TECH_11_形状指標の見直しと新指標導入.md) で予想していた「数値の偽物の一致」が **現実に起きていることの初の定量証拠**. + +- **物理的解釈**: + - R 上昇 → ambient 成分増加 → 半径方向の偏り(評価板下端 vs 上端の差)が圧縮される(→ CoV/勾配/rmin/max 改善) + - しかし角度方向のばらつき(X 方向の波打ち等)は **相対的に圧縮されにくい** + - 結果として「半径 var / 角度 var」のバランスが崩れ、angular_ratio が **増加** + - つまり R 上昇は **「Y 単調勾配の contrast を緩める代わりに角度方向のムラを相対的に強調する」**.実機の「中央ピーク楕円(=同心円的)」とは **形が遠ざかる** +- **解釈・学び**: + - **「均一化フィルタ系の改善は数値には効くが形には効かない」を超えて、「数値改善が形を逆方向に動かす」ケースの実証**.これは A1/A9 検証より強い発見 + - **A8 も A1/A9 と同様に「数値改善のみ・形改善ではない」**と判定.むしろ形は逆効果 + - TECH_10 優先順位を再更新: A8 を「数値改善のみクリア」化.**形改善の本筋は B1 幾何精度に集約**(これでほぼ確定) + - **TECH_11 の評価方針(数値 + 形 + 視覚の 3 軸)の妥当性が定量データで裏付けられた**.今後の試行で新指標必須 + - 副次成果: 反射率スイープのテンプレ + 新指標統合評価パイプラインが整備された +- **再現性確認**: R=0.85 の CoV=0.1604 は max_depth スイープ d=16 (CoV=0.1604) と **完全一致** ← 別 sweep 間の数値整合性 OK +- **ユーザー仮説の評価**: 「反射率を変えて結果を見たい」要請は重要な実証実験として完了.結果として: 数値だけ見れば R=0.92 が最良 (CoV 0.1424) と判定されるが、新指標も合わせて見ると **R=0.75 が最も同心円的(実機方向に近い形)**.**新指標を導入していなければ気づけない発見**であり、形状指標見直しの **正当性が定量データで裏付けられた** +- **次のアクション**: + - **数値改善のみが目的**なら R=0.89(Step 8-6 物理整合点)または R=0.92 採用候補(要検討) + - **形改善目的**: B1 筐体幾何精度 / A10 サブサーフェス散乱 / ζ LED ティルト など、構造的に光分布を再配分する候補に進む + - 既存 max_depth スイープを新指標で再評価して angular_ratio の同様パターンをクロスチェック(オプション) + +### 俯瞰サマリへの 8-14 α 後半追記 + +| Step | 設定 | 空間 | mean | CoV | 勾配% | C/P | CoV 残差倍率 | +| --- | --- | --- | --- | --- | --- | --- | --- | +| 8-14 候補 α 後半 (A8) R=0.89 | **R=0.89**, d=16, spp1024 | linear | 207.82* | 0.1501 | 18.96 | 1.234 | **2.16×**(数値改善あり、**angular_ratio 増 = 形逆効果**) | +| 8-14 候補 α 後半 (A8) R=0.92 | **R=0.92**, d=16, spp1024 | linear | 207.82* | 0.1424 | 17.72 | 1.215 | **2.05×**(数値最良、**angular_ratio 0.494 で実機方向と逆**) | + ## 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 fa015e2..004c4fb 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" @@ -44,6 +44,11 @@ | `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 化観察用 | +| `output/reflectance_sweep/r{075,080,085,089,092}/` (5 ディレクトリ) | 8-14 候補 α 後半 | `configs/scene_baseline.yaml` (d=16 新基準) を `scripts/sweep_reflectance.py` で `materials.printer_white.reflectance` のみ {0.75, 0.80, 0.85, 0.89, 0.92} に書き換えた 5 種 (spp=1024) | `configs/eval_baseline_dng_target_shape.yaml` (新指標 5 つ含む) | R=0.92 で CoV=0.1424, 勾配=17.72%, angular_ratio=0.4938 — [TEST_01 § Step 8-14 候補 α 後半 結果表](TEST_01_キャリブレーション検証履歴.md#step-8-14-候補-α-後半-a8-反射率-5-値スイープ-r075-080-085-089-092) 参照 | TEST_01 Step 8-14 候補 α 後半 検証.**「数値の偽物の一致」を初の定量実証**.A8 数値改善のみクリア.**新指標 (shape_metrics) を初投入**したスイープ | +| `output/reflectance_sweep/summary.csv` | 8-14 候補 α 後半 | — | — | 5 ジョブ集約 CSV (reflectance + 既存 12 指標 + 新 12 指標) | `scripts/sweep_reflectance.py` の集約出力 | +| `output/_slide_panels/reflectance_sweep.png` | 8-14 候補 α 後半 | — | — | 2 段比較パネル (上=共有スケール / 下=独立スケール) | TEST_01 視覚追跡用 | +| `output/_slide_panels/reflectance_convergence.png` | 8-14 候補 α 後半 | — | — | 既存 4 指標カーブ vs R | TEST_01 数値追跡用 | +| `output/_slide_panels/reflectance_shape_metrics.png` | 8-14 候補 α 後半 | — | — | 新指標カーブ (centroid_y / angular_ratio / eccentricity) vs R | TEST_01 「形」追跡用 — **angular_ratio が単調増加 = 形は実機方向と逆を視覚的に確認** | ### sweep_summary.csv の値(参考、TEST_01 line と一致確認用) @@ -81,6 +86,8 @@ | 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`) | +| 反射率 5 値スイープ (Step 8-14 α 後半) | `$env:DRJIT_LIBLLVM_PATH = "C:\Program Files\LLVM\bin\LLVM-C.dll"; uv run python scripts/sweep_reflectance.py` (5 ジョブ × 約 17 分 = 約 85 分) | +| 反射率比較パネル生成 | `uv run python scripts/build_reflectance_panel.py` (輝度マップ 2 段 + 既存指標 + 新指標カーブ.要 `output/reflectance_sweep/summary.csv` + 各 `luminance.npy`) | ## 過去の整理実績 (Cleanup History) diff --git a/scripts/build_reflectance_panel.py b/scripts/build_reflectance_panel.py new file mode 100644 index 0000000..f71c867 --- /dev/null +++ b/scripts/build_reflectance_panel.py @@ -0,0 +1,179 @@ +"""反射率スイープの比較パネル生成. + +scripts/sweep_reflectance.py の出力から以下を生成: + - 1×5 輝度マップパネル (shared vmin/vmax + 独立スケール 2 段比較) + - 既存 4 指標の収束カーブ + - 新 5 指標 (shape_metrics) の変化カーブ + - 数値表 + +実行方法: + uv run python scripts/build_reflectance_panel.py + +出力: + - output/_slide_panels/reflectance_sweep.png (1×5 輝度マップ 2 段) + - output/_slide_panels/reflectance_convergence.png (既存 4 指標カーブ) + - output/_slide_panels/reflectance_shape_metrics.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" / "reflectance_sweep" +SUMMARY_CSV = SWEEP_BASE / "summary.csv" +OUT_LUM_PANEL = ROOT / "output" / "_slide_panels" / "reflectance_sweep.png" +OUT_CONV_PANEL = ROOT / "output" / "_slide_panels" / "reflectance_convergence.png" +OUT_SHAPE_PANEL = ROOT / "output" / "_slide_panels" / "reflectance_shape_metrics.png" + +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)) + + reflectances = [float(r["reflectance"]) 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] + # 新指標 + centroid_y = [float(r["intensity_centroid_y_norm"]) for r in rows] + ang_ratios = [float(r["angular_ratio"]) for r in rows] + eccs = [float(r["pca_eccentricity"]) for r in rows] + + # npy 読み込み + lums = [] + for r in rows: + npy = SWEEP_BASE / r["job"] / "luminance.npy" + if not npy.exists(): + lums.append(None) + continue + arr = np.load(npy) + lums.append(arr.mean(axis=-1) if arr.ndim == 3 else arr) + + valid = [x for x in lums if x is not None] + stacked = np.stack(valid) + vmin_shared = float(np.percentile(stacked, 1)) + vmax_shared = float(np.percentile(stacked, 99)) + + # ============ 輝度マップ 2 段比較 (shared / independent) ============ + n = len(rows) + fig, axes = plt.subplots(2, n, figsize=(4.5 * n, 10)) + for j, (r, lum, cov, grad) in enumerate(zip(reflectances, lums, covs, grads)): + # 上段: shared + ax_t = axes[0, j] + im_t = ax_t.imshow(lum, cmap="hot", vmin=vmin_shared, vmax=vmax_shared) + ax_t.set_title(f"R = {r}\nCoV={cov:.4f} 勾配={grad:.2f}%", fontsize=11) + ax_t.set_xticks([]); ax_t.set_yticks([]) + plt.colorbar(im_t, ax=ax_t, fraction=0.046, pad=0.04) + # 下段: independent + ax_b = axes[1, j] + vmin_i = float(np.percentile(lum, 1)) + vmax_i = float(np.percentile(lum, 99)) + im_b = ax_b.imshow(lum, cmap="hot", vmin=vmin_i, vmax=vmax_i) + ax_b.set_title(f"独立スケール [{vmin_i:.0f}, {vmax_i:.0f}]", fontsize=10) + ax_b.set_xticks([]); ax_b.set_yticks([]) + plt.colorbar(im_b, ax=ax_b, fraction=0.046, pad=0.04) + + axes[0, 0].set_ylabel("① 共有スケール", fontsize=12, fontweight="bold") + axes[1, 0].set_ylabel("② 独立スケール\n(形比較用)", fontsize=12, fontweight="bold") + fig.suptitle("反射率スイープ (d=16) — 共有 vs 独立スケール 2 段比較", + 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}") + + # ============ 既存 4 指標の収束カーブ ============ + fig, axes = plt.subplots(2, 2, figsize=(13, 9)) + metrics_existing = [ + ("CoV", covs, TARGET["cov"], "CoV (低いほど均一)"), + ("勾配 [%]", grads, TARGET["gradient"], "勾配 [%]"), + ("C/P 比", cps, TARGET["cp"], "C/P 比"), + ("動径 min/max", rmms, TARGET["rmm"], "動径 min/max"), + ] + for ax, (label, vals, target, ylabel) in zip(axes.flat, metrics_existing): + ax.plot(reflectances, vals, marker="o", linewidth=2, markersize=10, color="C0", label="シミュ") + ax.axhline(target, color="red", linestyle="--", linewidth=1.5, label=f"実機 TARGET ({target})") + for r, v in zip(reflectances, vals): + ax.annotate(f"{v:.4f}", (r, v), textcoords="offset points", xytext=(0, 10), + ha="center", fontsize=9) + ax.set_xlabel("反射率 R") + ax.set_ylabel(ylabel) + ax.set_title(label) + ax.grid(alpha=0.3) + ax.legend(loc="best", fontsize=9) + + fig.suptitle("反射率スイープ — 既存 4 指標の変化 (赤破線=実機 TARGET)", + fontsize=14, fontweight="bold") + plt.tight_layout() + plt.savefig(OUT_CONV_PANEL, dpi=110, bbox_inches="tight") + print(f"既存指標カーブ保存: {OUT_CONV_PANEL}") + + # ============ 新指標カーブ ============ + fig, axes = plt.subplots(1, 3, figsize=(16, 5)) + new_metrics = [ + ("輝度重心 Y (norm)", centroid_y, 0.0, "centroid_y_norm"), + ("angular_ratio (0=同心円, 1=角度依存)", ang_ratios, 0.0, "angular_ratio"), + ("PCA eccentricity", eccs, 0.0, "eccentricity"), + ] + for ax, (label, vals, ideal, ylabel) in zip(axes, new_metrics): + ax.plot(reflectances, vals, marker="o", linewidth=2, markersize=10, color="C2", label="シミュ") + ax.axhline(ideal, color="gray", linestyle=":", linewidth=1.5, label=f"理想 ({ideal})") + for r, v in zip(reflectances, vals): + ax.annotate(f"{v:.4f}", (r, v), textcoords="offset points", xytext=(0, 10), + ha="center", fontsize=9) + ax.set_xlabel("反射率 R") + ax.set_ylabel(ylabel) + ax.set_title(label) + ax.grid(alpha=0.3) + ax.legend(loc="best", fontsize=9) + + fig.suptitle("反射率スイープ — 新指標 (shape_metrics) の変化", + fontsize=14, fontweight="bold") + plt.tight_layout() + plt.savefig(OUT_SHAPE_PANEL, dpi=110, bbox_inches="tight") + print(f"新指標カーブ保存: {OUT_SHAPE_PANEL}") + + # ============ サマリ ============ + print() + print("=== サマリ ===") + h = ("R", "mean", "CoV", "勾配%", "C/P", "rmin/max", "centroid_y", "ang_ratio", "ecc") + print(f" {h[0]:<6}{h[1]:<10}{h[2]:<10}{h[3]:<10}{h[4]:<10}{h[5]:<10}{h[6]:<12}{h[7]:<12}{h[8]:<10}") + for r, mean, cov, grad, cp, rmm, cy, ar, ec in zip( + reflectances, means, covs, grads, cps, rmms, centroid_y, ang_ratios, eccs): + print(f" {r:<6}{mean:<10.2f}{cov:<10.4f}{grad:<10.2f}{cp:<10.4f}{rmm:<10.4f}" + f"{cy:<12.4f}{ar:<12.4f}{ec:<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_reflectance.py b/scripts/sweep_reflectance.py new file mode 100644 index 0000000..6e2c275 --- /dev/null +++ b/scripts/sweep_reflectance.py @@ -0,0 +1,185 @@ +"""Step 8-14 候補 α 後半: 反射率 5 値スイープ. + +d=16 ベース (Step 8-14 α 前半で確定) で R ∈ {0.75, 0.80, 0.85, 0.89, 0.92} を +スイープし,反射率変化が形状指標と新指標 (shape_metrics) に与える影響を観察する. + +過去 Step 8-2 で同様のスイープを d=8 / 旧評価方針で実施済.本スイープは: + - d=16 (Step 8-14 α 前半の収束値) + - 評価 config: eval_baseline_dng_target_shape.yaml (新 5 指標を含む) + - mean アンカー正規化で形状指標は線形ゲイン不変,shape_metrics で形不変を定量確認 + +実行方法: + uv run python scripts/sweep_reflectance.py + +出力: + - output/reflectance_sweep/r{N}/luminance.npy + - output/reflectance_sweep/r{N}/metrics.csv + - output/reflectance_sweep/summary.csv + - configs/_tmp_reflectance/scene_r{N}.yaml (一時 YAML) + +TECH_10 参照: 残差主因候補 §A8「反射率 R=0.85→0.89」 +""" + +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_shape.yaml" +TMP_DIR = ROOT / "configs" / "_tmp_reflectance" +OUTPUT_BASE = ROOT / "output" / "reflectance_sweep" + +REFLECTANCES = [0.75, 0.80, 0.85, 0.89, 0.92] + +SUMMARY_EXTRA_COLS = [ + # 既存 4 指標 + "cov", "std", "max_min_ratio", "mean", "max", "min", + "center_periphery_ratio", "gradient_magnitude", + "radial_min_max_ratio", "radial_min_distance", "radial_min", "radial_max", + # 新 5 指標 (shape_metrics) + "intensity_centroid_x_px", "intensity_centroid_y_px", + "intensity_centroid_x_norm", "intensity_centroid_y_norm", + "intensity_centroid_offset_norm", + "angular_variance", "radial_variance", "angular_ratio", + "pca_primary_axis_angle_deg", "pca_eccentricity", + "ssim", "ncc", +] + + +def _r_tag(r: float) -> str: + """R 値をファイル名タグに変換 (0.85 → r085).""" + return f"r{int(round(r * 100)):03d}" + + +def _generate_tmp_yaml(reflectance: float) -> Path: + """指定 reflectance の一時 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()) + + # materials.printer_white.reflectance を書き換え + data["materials"]["printer_white"]["reflectance"] = [reflectance, reflectance, reflectance] + data["name"] = f"baseline_minitias_{_r_tag(reflectance)}" + + tmp_path = TMP_DIR / f"scene_{_r_tag(reflectance)}.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: + 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"=== reflectance sweep: {len(REFLECTANCES)} jobs ===") + print(f" reflectances: {REFLECTANCES}") + print(f" output: {OUTPUT_BASE}") + print() + + failed: list[str] = [] + rows: list[dict] = [] + total_start = time.time() + + for i, r in enumerate(REFLECTANCES, start=1): + job_name = _r_tag(r) + output_dir = OUTPUT_BASE / job_name + metrics_csv = output_dir / "metrics.csv" + + print(f"--- Job {i}/{len(REFLECTANCES)}: {job_name} (R={r}) ---") + job_start = time.time() + + tmp_yaml = _generate_tmp_yaml(r) + 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") + a_ratio = m.get("angular_ratio", "N/A") + print(f" -> 完了 ({elapsed:.1f}s) CoV={cov}, grad={grad}%, angular_ratio={a_ratio}") + + row: dict = {"job": job_name, "reflectance": r, "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", "reflectance", "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(REFLECTANCES)} 成功, 総時間 {total_elapsed:.1f}s ===") + + if failed: + print("失敗ジョブ:", failed, file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main())