diff --git a/CLAUDE.md b/CLAUDE.md index a75a2c8..d4e24b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,7 +23,7 @@ ## 開発進捗 -最新: フェーズ 7(実装)— Step 8 基準設計キャリブレーションの **Step 8-14(物理モデル精緻化)** 進行中.[TEST_10](docs/06_TEST/TEST_10_後側ロフト変形w1p4実験.md) の**初の幾何変更実験**(後側ロフト,blend w0.05_a0.1・spp=1024・d=16)を全灯ロフト重みスイープ w ∈ {0.5, 1.4, 2.0, 4.0} まで拡張完了.**bottom 単灯 peak/mean 1.672→1.508 と幾何レバーが形を動かすことは初実証**したが,**灯間 NCC は不動=対称なキャビティ拡大では非対称相補性(実機 0.85/0.61/0.41 がアンカー)に近づかず,全灯はロフト重みに実質不感**(CoV 0.170〜0.177 で横ばい,w4.0 のみ微悪化・絶対照度 -29%).実機側はロフト w=1.4 版を印刷し初の実機測定 `MiniTIAS_QM_20260726_183416.dng` を取得済み(SmTIAS-Evaluation 分析待ち=シミュ予測「従来筐体とほぼ同じ」の検証).次は実験4(幾何の漏れ点検=漏れの定量化・開口の閉じテスト).詳細な経緯・設計判断・失敗パターンは [docs/PROGRESS.md](docs/PROGRESS.md). +最新: フェーズ 7(実装)— Step 8 基準設計キャリブレーションの **Step 8-14(物理モデル精緻化)** 進行中.[TEST_12](docs/06_TEST/TEST_12_シェーディング法線アーティファクトの発見と修正.md) で **Step 8 残差の支配成分がレンダリングバグ(PLY 自動スムーズ法線 × 評価板の巨大三角形)だったことを発見・修正**(TEST_11 球体筐体実験の完全軸対称化で顕在化,builder に `face_normals: true` を強制).修正後は現行筐体全灯 **CoV 0.1717→0.0610**(実機 0.0695 を下回る)・勾配 6.8%(実機 8.35%)・C/P 1.073(実機 1.091)=**振幅系指標は実機とほぼ整合し,「残差 2.4×」と Y 軸単調勾配の見かけは大半がアーティファクトだったと判明**.球ロフト(CoV 0.0628・完全同心円)は現行筐体と同等=現行筐体はすでに理想積分球級で球形化の優位性なし(TEST_11 の結論は撤回・BEFORE FIX 注記参照).単灯再検証(TEST_12 追補)で **TEST_09 中核結論「3 灯同形=非対称相補性を再現できない」も撤回**: 修正後は灯間 NCC(平滑)0.85/0.93/0.73 の非対称で「left-right 最違い」は実機(0.92/0.66/0.44)と一致(振幅・最類似ペアは不一致=残課題).ロフト w1.4 の修正後再検証では**「全灯・単灯とも実質不感」が維持**(実機 w1.4 測定への予測も不変)される一方「bottom 平ら化=幾何レバー実証」は撤回相当に弱まった.実験4(漏れ点検)の主動機は大幅に弱まり,残る差分は「形」(相補性の振幅・実機の斜め構造)の切り分けへ再設定.実機側はロフト w=1.4 版 `MiniTIAS_QM_20260726_183416.dng` が SmTIAS-Evaluation 分析待ち.詳細な経緯・設計判断・失敗パターンは [docs/PROGRESS.md](docs/PROGRESS.md). 実装済みモジュール: - `src/sm_tias_lightsim/uniformity.py` — Rec.709 輝度変換 + CoV/std/max-min(SmTIAS-Evaluation コピー) @@ -80,6 +80,10 @@ - 起動・テスト・lint 等の開発コマンドは `docs/02_ENV/ENV_04_開発コマンド.md` にまとめる.**存在する場合は実行前に必ず参照し,推測でコマンドを試さない** - コマンドが新しく確定・変更された時は同ファイルに反映する(無ければその時点で作成する.`/implement` の Phase 4 でも見直される) +### シミュレーション実験の進め方 + +- **幾何(メッシュ・光源・センサ位置)を変えるシーンは,本番レンダを回す前に必ず 3D で配置確認する**(`sm-visualize --no-bake --screenshot` / `scripts/show_led_geometry.py`).確認画像をユーザーに提示し,位置の OK をもらってからレンダを実行する(2026-08-18 ユーザー指示) + ### 手動確認が必要な作業(自分で完了させないこと) 以下は実装完了後にユーザーへ報告し,確認・実施を依頼すること. diff --git a/configs/_tmp_fix_bottom.yaml b/configs/_tmp_fix_bottom.yaml new file mode 100644 index 0000000..4390f6a --- /dev/null +++ b/configs/_tmp_fix_bottom.yaml @@ -0,0 +1,196 @@ +name: fix_single_bottom +description: 'TEST_12 相対結論再確認: bottom 単灯(面法線修正後, blend w0.05_a0.1, d=16, spp=1024)' +geometry: + meshes: + - id: housing_front + path: meshes/前側.stl + unit: mm + material: printer_white + - id: housing_back + path: meshes/後側.stl + unit: mm + material: printer_white + - id: phone_holder + path: meshes/スマホホルダ.stl + unit: mm + material: holder_dark + - id: led_cover_bottom + path: meshes/LED基板カバー下.stl + unit: mm + material: printer_white + - id: led_cover_right + path: meshes/LED基板カバー右.stl + unit: mm + material: printer_white + - id: led_cover_left + path: meshes/LED基板カバー左.stl + unit: mm + material: printer_white + - id: phone_body + path: meshes/SH-02M.stl + unit: mm + material: phone_body_dark + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx + holder_dark: + type: diffuse + reflectance: + - 0.5 + - 0.5 + - 0.5 + phone_body_dark: + type: diffuse + reflectance: + - 0.05 + - 0.05 + - 0.05 +lights: +- id: led_bottom_0 + type: area + shape: rectangle + center: + - -10.0 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_1 + type: area + shape: rectangle + center: + - -5.8 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_2 + type: area + shape: rectangle + center: + - -1.6 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_3 + type: area + shape: rectangle + center: + - 9.2 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_4 + type: area + shape: rectangle + center: + - 13.4 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_5 + type: area + shape: rectangle + center: + - 17.6 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + spp: 1024 diff --git a/configs/_tmp_fix_bottom_w1p4.yaml b/configs/_tmp_fix_bottom_w1p4.yaml new file mode 100644 index 0000000..813ea4b --- /dev/null +++ b/configs/_tmp_fix_bottom_w1p4.yaml @@ -0,0 +1,196 @@ +name: bottom_w1p4 +description: 'TEST_12 追補: ロフト w1.4 bottom 単灯(面法線修正後)' +geometry: + meshes: + - id: housing_front + path: meshes/前側.stl + unit: mm + material: printer_white + - id: housing_back + path: meshes/後側_w1p4.stl + unit: mm + material: printer_white + - id: phone_holder + path: meshes/スマホホルダ.stl + unit: mm + material: holder_dark + - id: led_cover_bottom + path: meshes/LED基板カバー下.stl + unit: mm + material: printer_white + - id: led_cover_right + path: meshes/LED基板カバー右.stl + unit: mm + material: printer_white + - id: led_cover_left + path: meshes/LED基板カバー左.stl + unit: mm + material: printer_white + - id: phone_body + path: meshes/SH-02M.stl + unit: mm + material: phone_body_dark + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx + holder_dark: + type: diffuse + reflectance: + - 0.5 + - 0.5 + - 0.5 + phone_body_dark: + type: diffuse + reflectance: + - 0.05 + - 0.05 + - 0.05 +lights: +- id: led_bottom_0 + type: area + shape: rectangle + center: + - -10.0 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_1 + type: area + shape: rectangle + center: + - -5.8 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_2 + type: area + shape: rectangle + center: + - -1.6 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_3 + type: area + shape: rectangle + center: + - 9.2 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_4 + type: area + shape: rectangle + center: + - 13.4 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_5 + type: area + shape: rectangle + center: + - 17.6 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + spp: 1024 diff --git a/configs/_tmp_fix_full_w1p4.yaml b/configs/_tmp_fix_full_w1p4.yaml new file mode 100644 index 0000000..b9b8779 --- /dev/null +++ b/configs/_tmp_fix_full_w1p4.yaml @@ -0,0 +1,412 @@ +name: full_w1p4 +description: 'TEST_12 追補: ロフト w1.4 全灯(面法線修正後)' +geometry: + meshes: + - id: housing_front + path: meshes/前側.stl + unit: mm + material: printer_white + - id: housing_back + path: meshes/後側_w1p4.stl + unit: mm + material: printer_white + - id: phone_holder + path: meshes/スマホホルダ.stl + unit: mm + material: holder_dark + - id: led_cover_bottom + path: meshes/LED基板カバー下.stl + unit: mm + material: printer_white + - id: led_cover_right + path: meshes/LED基板カバー右.stl + unit: mm + material: printer_white + - id: led_cover_left + path: meshes/LED基板カバー左.stl + unit: mm + material: printer_white + - id: phone_body + path: meshes/SH-02M.stl + unit: mm + material: phone_body_dark + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx + holder_dark: + type: diffuse + reflectance: + - 0.5 + - 0.5 + - 0.5 + phone_body_dark: + type: diffuse + reflectance: + - 0.05 + - 0.05 + - 0.05 +lights: +- id: led_bottom_0 + type: area + shape: rectangle + center: + - -10.0 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_1 + type: area + shape: rectangle + center: + - -5.8 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_2 + type: area + shape: rectangle + center: + - -1.6 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_3 + type: area + shape: rectangle + center: + - 9.2 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_4 + type: area + shape: rectangle + center: + - 13.4 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_5 + type: area + shape: rectangle + center: + - 17.6 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_0 + type: area + shape: rectangle + center: + - 65.9987 + - -24.9738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_1 + type: area + shape: rectangle + center: + - 65.9987 + - -20.7738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_2 + type: area + shape: rectangle + center: + - 65.9987 + - -16.5738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_3 + type: area + shape: rectangle + center: + - 65.9987 + - -5.7738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_4 + type: area + shape: rectangle + center: + - 65.9987 + - -1.5738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_5 + type: area + shape: rectangle + center: + - 65.9987 + - 2.6262 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_0 + type: area + shape: rectangle + center: + - -66.0067 + - -32.5952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_1 + type: area + shape: rectangle + center: + - -66.0067 + - -28.3952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_2 + type: area + shape: rectangle + center: + - -66.0067 + - -24.1952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_3 + type: area + shape: rectangle + center: + - -66.0067 + - -13.3952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_4 + type: area + shape: rectangle + center: + - -66.0067 + - -9.1952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_5 + type: area + shape: rectangle + center: + - -66.0067 + - -4.9952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + spp: 1024 diff --git a/configs/_tmp_fix_left.yaml b/configs/_tmp_fix_left.yaml new file mode 100644 index 0000000..015e71e --- /dev/null +++ b/configs/_tmp_fix_left.yaml @@ -0,0 +1,196 @@ +name: fix_single_left +description: 'TEST_12 相対結論再確認: left 単灯(面法線修正後, blend w0.05_a0.1, d=16, spp=1024)' +geometry: + meshes: + - id: housing_front + path: meshes/前側.stl + unit: mm + material: printer_white + - id: housing_back + path: meshes/後側.stl + unit: mm + material: printer_white + - id: phone_holder + path: meshes/スマホホルダ.stl + unit: mm + material: holder_dark + - id: led_cover_bottom + path: meshes/LED基板カバー下.stl + unit: mm + material: printer_white + - id: led_cover_right + path: meshes/LED基板カバー右.stl + unit: mm + material: printer_white + - id: led_cover_left + path: meshes/LED基板カバー左.stl + unit: mm + material: printer_white + - id: phone_body + path: meshes/SH-02M.stl + unit: mm + material: phone_body_dark + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx + holder_dark: + type: diffuse + reflectance: + - 0.5 + - 0.5 + - 0.5 + phone_body_dark: + type: diffuse + reflectance: + - 0.05 + - 0.05 + - 0.05 +lights: +- id: led_left_0 + type: area + shape: rectangle + center: + - -66.0067 + - -32.5952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_1 + type: area + shape: rectangle + center: + - -66.0067 + - -28.3952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_2 + type: area + shape: rectangle + center: + - -66.0067 + - -24.1952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_3 + type: area + shape: rectangle + center: + - -66.0067 + - -13.3952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_4 + type: area + shape: rectangle + center: + - -66.0067 + - -9.1952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_5 + type: area + shape: rectangle + center: + - -66.0067 + - -4.9952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + spp: 1024 diff --git a/configs/_tmp_fix_left_w1p4.yaml b/configs/_tmp_fix_left_w1p4.yaml new file mode 100644 index 0000000..47fedf0 --- /dev/null +++ b/configs/_tmp_fix_left_w1p4.yaml @@ -0,0 +1,196 @@ +name: left_w1p4 +description: 'TEST_12 追補: ロフト w1.4 left 単灯(面法線修正後)' +geometry: + meshes: + - id: housing_front + path: meshes/前側.stl + unit: mm + material: printer_white + - id: housing_back + path: meshes/後側_w1p4.stl + unit: mm + material: printer_white + - id: phone_holder + path: meshes/スマホホルダ.stl + unit: mm + material: holder_dark + - id: led_cover_bottom + path: meshes/LED基板カバー下.stl + unit: mm + material: printer_white + - id: led_cover_right + path: meshes/LED基板カバー右.stl + unit: mm + material: printer_white + - id: led_cover_left + path: meshes/LED基板カバー左.stl + unit: mm + material: printer_white + - id: phone_body + path: meshes/SH-02M.stl + unit: mm + material: phone_body_dark + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx + holder_dark: + type: diffuse + reflectance: + - 0.5 + - 0.5 + - 0.5 + phone_body_dark: + type: diffuse + reflectance: + - 0.05 + - 0.05 + - 0.05 +lights: +- id: led_left_0 + type: area + shape: rectangle + center: + - -66.0067 + - -32.5952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_1 + type: area + shape: rectangle + center: + - -66.0067 + - -28.3952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_2 + type: area + shape: rectangle + center: + - -66.0067 + - -24.1952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_3 + type: area + shape: rectangle + center: + - -66.0067 + - -13.3952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_4 + type: area + shape: rectangle + center: + - -66.0067 + - -9.1952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_5 + type: area + shape: rectangle + center: + - -66.0067 + - -4.9952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + spp: 1024 diff --git a/configs/_tmp_fix_right.yaml b/configs/_tmp_fix_right.yaml new file mode 100644 index 0000000..7a45a34 --- /dev/null +++ b/configs/_tmp_fix_right.yaml @@ -0,0 +1,196 @@ +name: fix_single_right +description: 'TEST_12 相対結論再確認: right 単灯(面法線修正後, blend w0.05_a0.1, d=16, spp=1024)' +geometry: + meshes: + - id: housing_front + path: meshes/前側.stl + unit: mm + material: printer_white + - id: housing_back + path: meshes/後側.stl + unit: mm + material: printer_white + - id: phone_holder + path: meshes/スマホホルダ.stl + unit: mm + material: holder_dark + - id: led_cover_bottom + path: meshes/LED基板カバー下.stl + unit: mm + material: printer_white + - id: led_cover_right + path: meshes/LED基板カバー右.stl + unit: mm + material: printer_white + - id: led_cover_left + path: meshes/LED基板カバー左.stl + unit: mm + material: printer_white + - id: phone_body + path: meshes/SH-02M.stl + unit: mm + material: phone_body_dark + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx + holder_dark: + type: diffuse + reflectance: + - 0.5 + - 0.5 + - 0.5 + phone_body_dark: + type: diffuse + reflectance: + - 0.05 + - 0.05 + - 0.05 +lights: +- id: led_right_0 + type: area + shape: rectangle + center: + - 65.9987 + - -24.9738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_1 + type: area + shape: rectangle + center: + - 65.9987 + - -20.7738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_2 + type: area + shape: rectangle + center: + - 65.9987 + - -16.5738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_3 + type: area + shape: rectangle + center: + - 65.9987 + - -5.7738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_4 + type: area + shape: rectangle + center: + - 65.9987 + - -1.5738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_5 + type: area + shape: rectangle + center: + - 65.9987 + - 2.6262 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + spp: 1024 diff --git a/configs/_tmp_fix_right_w1p4.yaml b/configs/_tmp_fix_right_w1p4.yaml new file mode 100644 index 0000000..148bc2b --- /dev/null +++ b/configs/_tmp_fix_right_w1p4.yaml @@ -0,0 +1,196 @@ +name: right_w1p4 +description: 'TEST_12 追補: ロフト w1.4 right 単灯(面法線修正後)' +geometry: + meshes: + - id: housing_front + path: meshes/前側.stl + unit: mm + material: printer_white + - id: housing_back + path: meshes/後側_w1p4.stl + unit: mm + material: printer_white + - id: phone_holder + path: meshes/スマホホルダ.stl + unit: mm + material: holder_dark + - id: led_cover_bottom + path: meshes/LED基板カバー下.stl + unit: mm + material: printer_white + - id: led_cover_right + path: meshes/LED基板カバー右.stl + unit: mm + material: printer_white + - id: led_cover_left + path: meshes/LED基板カバー左.stl + unit: mm + material: printer_white + - id: phone_body + path: meshes/SH-02M.stl + unit: mm + material: phone_body_dark + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx + holder_dark: + type: diffuse + reflectance: + - 0.5 + - 0.5 + - 0.5 + phone_body_dark: + type: diffuse + reflectance: + - 0.05 + - 0.05 + - 0.05 +lights: +- id: led_right_0 + type: area + shape: rectangle + center: + - 65.9987 + - -24.9738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_1 + type: area + shape: rectangle + center: + - 65.9987 + - -20.7738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_2 + type: area + shape: rectangle + center: + - 65.9987 + - -16.5738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_3 + type: area + shape: rectangle + center: + - 65.9987 + - -5.7738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_4 + type: area + shape: rectangle + center: + - 65.9987 + - -1.5738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_5 + type: area + shape: rectangle + center: + - 65.9987 + - 2.6262 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + spp: 1024 diff --git a/configs/_tmp_fn_full_base.yaml b/configs/_tmp_fn_full_base.yaml new file mode 100644 index 0000000..3b8b17d --- /dev/null +++ b/configs/_tmp_fn_full_base.yaml @@ -0,0 +1,433 @@ +# Step 8-14 感度分析用: 内壁の非 Lambertian 性(光沢成分混合)テンプレート +# +# scene_baseline.yaml から `printer_white` 材料のみを blendbsdf に変更したもの. +# 他の設定(geometry / lights / sensor / render)は scene_baseline.yaml と完全に同じ. +# +# blendbsdf 構成: +# component[0]: diffuse (reflectance=0.85) ... 純粋 Lambertian 拡散反射 +# component[1]: roughconductor (alpha=0.3) ... GGX マイクロファセット鏡面反射 +# weight=0.15: component[1](鏡面)の混合比 = 15% specular +# +# スイープ対象パラメータ: +# weight ∈ {0.05, 0.15, 0.30} × alpha ∈ {0.1, 0.3, 0.6} の 9 組合せ +# → scripts/sweep_blend_bsdf.py が本ファイルを読んで weight/alpha を書き換えて実行する +# +# 参考: TECH_10 残差主因候補 §A1「内壁の非 Lambertian 性(光沢成分混合 BSDF)」 +name: baseline_minitias_blend +description: > + SmTIAS 基準設計シーン(Step 8-14 感度分析). + printer_white を diffuse + roughconductor の blendbsdf に変更して + 内壁の光沢成分が輝度分布「形」に与える影響を観察する. + weight=0.15, alpha=0.3 はスイープの中間値(起点). + LED 位置・センサ・レンダリング設定は scene_baseline.yaml と同一. +geometry: + meshes: + - id: housing_front + path: meshes/前側.stl + unit: mm + material: printer_white + - id: housing_back + path: meshes/後側.stl + unit: mm + material: printer_white + - id: phone_holder + path: meshes/スマホホルダ.stl + unit: mm + material: holder_dark + - id: led_cover_bottom + path: meshes/LED基板カバー下.stl + unit: mm + material: printer_white + - id: led_cover_right + path: meshes/LED基板カバー右.stl + unit: mm + material: printer_white + - id: led_cover_left + path: meshes/LED基板カバー左.stl + unit: mm + material: printer_white + - id: phone_body + path: meshes/SH-02M.stl + unit: mm + material: phone_body_dark + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx + holder_dark: + type: diffuse + reflectance: + - 0.5 + - 0.5 + - 0.5 + phone_body_dark: + type: diffuse + reflectance: + - 0.05 + - 0.05 + - 0.05 +lights: +- id: led_bottom_0 + type: area + shape: rectangle + center: + - -10.0 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_1 + type: area + shape: rectangle + center: + - -5.8 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_2 + type: area + shape: rectangle + center: + - -1.6 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_3 + type: area + shape: rectangle + center: + - 9.2 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_4 + type: area + shape: rectangle + center: + - 13.4 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_bottom_5 + type: area + shape: rectangle + center: + - 17.6 + - -71.0 + - -66.1 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_0 + type: area + shape: rectangle + center: + - 65.9987 + - -24.9738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_1 + type: area + shape: rectangle + center: + - 65.9987 + - -20.7738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_2 + type: area + shape: rectangle + center: + - 65.9987 + - -16.5738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_3 + type: area + shape: rectangle + center: + - 65.9987 + - -5.7738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_4 + type: area + shape: rectangle + center: + - 65.9987 + - -1.5738 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_right_5 + type: area + shape: rectangle + center: + - 65.9987 + - 2.6262 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_0 + type: area + shape: rectangle + center: + - -66.0067 + - -32.5952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_1 + type: area + shape: rectangle + center: + - -66.0067 + - -28.3952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_2 + type: area + shape: rectangle + center: + - -66.0067 + - -24.1952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_3 + type: area + shape: rectangle + center: + - -66.0067 + - -13.3952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_4 + type: area + shape: rectangle + center: + - -66.0067 + - -9.1952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +- id: led_left_5 + type: area + shape: rectangle + center: + - -66.0067 + - -4.9952 + - -66.0243 + size: + - 3.5 + - 2.8 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + spp: 1024 + diff --git "a/configs/meshes/\347\220\203\343\203\255\343\203\225\343\203\210_r100.stl" "b/configs/meshes/\347\220\203\343\203\255\343\203\225\343\203\210_r100.stl" new file mode 100644 index 0000000..7b25787 --- /dev/null +++ "b/configs/meshes/\347\220\203\343\203\255\343\203\225\343\203\210_r100.stl" Binary files differ diff --git "a/configs/meshes/\347\220\203\346\256\273_r100.stl" "b/configs/meshes/\347\220\203\346\256\273_r100.stl" new file mode 100644 index 0000000..ad9f217 --- /dev/null +++ "b/configs/meshes/\347\220\203\346\256\273_r100.stl" Binary files differ diff --git "a/configs/meshes/\347\220\203\346\256\273_r100_fit.stl" "b/configs/meshes/\347\220\203\346\256\273_r100_fit.stl" new file mode 100644 index 0000000..106c785 --- /dev/null +++ "b/configs/meshes/\347\220\203\346\256\273_r100_fit.stl" Binary files differ diff --git a/configs/scene_sphere_r100.yaml b/configs/scene_sphere_r100.yaml new file mode 100644 index 0000000..9e03cb7 --- /dev/null +++ b/configs/scene_sphere_r100.yaml @@ -0,0 +1,93 @@ +# TEST_11 球体筐体実験: 幾何レバーの理想極限(完全閉球 + 完全間接照明) +# +# 現行筐体(前側/後側/カバー/ホルダ/スマホ)をすべて外し, +# 評価板だけを現行と同位置に残して内半径 100mm の閉じた球殻で囲む. +# 光源は 1 灯のみ.軸上 z=-90(-Z 極壁から 50mm 手前)から壁向き (0,0,-1) に発光し, +# 評価板への直接光は幾何的にゼロ(間接照明思想の理想化.板は発光面の裏側). +# ※ 壁 5mm 手前だと直接照射キャップが半径 31mm と小さく 1/r^2 特異も生じて +# path tracer の分散が実用不能なレベルになるため(spp=64 で相対ノイズ 89%), +# 壁から 50mm に離して NEE 可視領域を球面積の 25% に拡大した. +# ptracer は orthographic センサ非対応(sample_direction 未実装)で使えない. +# +# 生成メッシュ: scripts/generate_sphere_housing.py +# uv run python scripts/generate_sphere_housing.py --radius 100 --thickness 3 --center 0 0 -40 +# 内包チェック: 評価板の最遠角 (49.5, 51, 12.5) は中心 (0,0,-40) から 88.4mm < 100mm +# +# 材料・センサ・レンダ設定は TEST_10 確定条件(blend w0.05_a0.1・spp=1024・d=16)を踏襲. +# radiance は現行 per-LED 校正値をそのまま使用(形状指標はスケール不変). +name: sphere_r100_single +description: > + 球体筐体実験(TEST_11).内半径 100mm の完全閉球殻 + 評価板(現行同位置)のみ. + 単灯を -Z 極付近に壁向きで置き,漏れゼロ・完全間接の理想積分球条件で + 照明均一性の幾何的上限を測る. +geometry: + meshes: + - id: sphere_housing + path: meshes/球殻_r100.stl + unit: mm + material: printer_white + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 # specular 比(TEST_10 確定: 形を壊さない最弱の光沢) + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx +lights: +- id: led_pole_single + type: area + shape: rectangle + center: + - 0.0 + - 0.0 + - -90.0 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + # TEST_10 基準は spp=1024 だが,本シーンは単灯・完全間接で per-pixel 分散が大きく + # spp=64 実測 σ=9.3% → 1024 でも 2.3% 残る.CoV 汚染を 1% 未満に抑えるため 8192 とする + # (unbiased なので比較の公平性は損なわれない.spp=64 で 17 秒 → 8192 で約 36 分) + spp: 8192 diff --git a/configs/scene_sphere_r100_fit.yaml b/configs/scene_sphere_r100_fit.yaml new file mode 100644 index 0000000..73fc727 --- /dev/null +++ b/configs/scene_sphere_r100_fit.yaml @@ -0,0 +1,89 @@ +# TEST_11 球体筐体実験・改訂版: 評価板が球に「すっぽり嵌まる」配置 +# +# scene_sphere_r100.yaml(板が球内に浮く配置)からの変更点: +# - 球中心を (0,0,-40) → (0,0,-92) へ +Z 側に移動(メッシュ 球殻_r100_fit.stl) +# 板の裏面 z=-4.5 での球断面円半径が 48.4mm となり,板(半幅 X 49.5 / Y 51mm)が +# 断面を隙間なく塞ぐ=キャビティは「球のお椀 + 評価板のフタ」になる +# (X 辺 1.1mm / Y 辺 2.6mm のオーバーラップ.四隅は球壁を貫通して外に出るが +# メッシュ相互貫入は Mitsuba で許容される) +# - LED を z=-90 → z=-142 へ移動(新しい -Z 極 z=-192 から 50mm 手前,壁向き維持) +# +# 幾何的帰結(レンダ画像に現れる想定): +# - 板の縁を回り込む光の経路が消える(板の裏 +Z 側は球殻とのみ接する閉ポケット) +# - orthographic 画像の四隅(板裏面の断面円 48.4mm より外側)はセンサ光線が +# 先に球内壁に当たるため板ではなく壁が写る +# - 球の +Z キャップ(z=-4.5〜+8)は板の実体・楕円窓穴の内部に埋没する +name: sphere_r100_fit_single +description: > + 球体筐体実験(TEST_11 改訂).内半径 100mm の閉球殻を中心 (0,0,-92) に置き, + 評価板(現行同位置)が球断面のフタとして隙間なく嵌まる配置. + 単灯を -Z 極付近に壁向きで置き,漏れゼロ・完全間接の理想積分球条件を維持する. +geometry: + meshes: + - id: sphere_housing + path: meshes/球殻_r100_fit.stl + unit: mm + material: printer_white + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 # specular 比(TEST_10 確定: 形を壊さない最弱の光沢) + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx +lights: +- id: led_pole_single + type: area + shape: rectangle + center: + - 0.0 + - 0.0 + - -142.0 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + # 単灯・完全間接の分散対策(scene_sphere_r100.yaml と同じ根拠) + spp: 8192 diff --git a/configs/scene_sphere_r100_loft.yaml b/configs/scene_sphere_r100_loft.yaml new file mode 100644 index 0000000..585e0f9 --- /dev/null +++ b/configs/scene_sphere_r100_loft.yaml @@ -0,0 +1,89 @@ +# TEST_11 球体筐体実験・ロフト一体化版: 評価板の輪郭と球を遷移スカートで接続 +# +# scene_sphere_r100_fit.yaml(球を板に貫入させる配置)からの変更点: +# - 筐体メッシュを 球殻_r100_fit.stl → 球ロフト_r100.stl に変更 +# (scripts/generate_sphere_loft_housing.py で生成.板輪郭の 1.5mm 内側の矩形リムを +# z=0 に置き=板の実体に 4.5mm 食い込むシール,z=-40 の球断面円 85.4mm まで +# Hermite ブレンドのロフトで接続,以深は球 r=100・中心 (0,0,-92) のお椀) +# - 板の四隅の貫通・縁の隙間が両方とも解消され,キャビティは +# 「球お椀+ロフトスカート+評価板のフタ」の一体構造になる +# - orthographic 画像はほぼ全面が板(四隅の壁写り込みが fit 版より減る) +# +# LED 単灯 (0,0,-142)・壁向き(直接光ゼロ)・材料 blend w0.05_a0.1・spp=8192 は +# fit 版と同一. +name: sphere_r100_loft_single +description: > + 球体筐体実験(TEST_11 ロフト一体化版).球 r=100mm のお椀と評価板輪郭を + ロフト遷移で一体化した閉キャビティ.評価板(現行同位置)がフタとして + 隙間なく嵌まる.単灯を -Z 極付近に壁向きで置き,漏れゼロ・完全間接の + 理想積分球条件を維持する. +geometry: + meshes: + - id: sphere_housing + path: meshes/球ロフト_r100.stl + unit: mm + material: printer_white + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 # specular 比(TEST_10 確定: 形を壊さない最弱の光沢) + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx +lights: +- id: led_pole_single + type: area + shape: rectangle + center: + - 0.0 + - 0.0 + - -142.0 + size: + - 2.8 + - 3.5 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + # TEST_09/10 系と条件統一(ユーザー判断: 比較の公平性 > 単灯の MC ノイズ低減) + # 参考: 単灯・完全間接のため残留ノイズは spp=1024 で約 2%(CoV への汚染は +0.002 程度) + spp: 1024 diff --git a/configs/scene_sphere_r100_loft_disk.yaml b/configs/scene_sphere_r100_loft_disk.yaml new file mode 100644 index 0000000..33cc54a --- /dev/null +++ b/configs/scene_sphere_r100_loft_disk.yaml @@ -0,0 +1,83 @@ +# TEST_11 球体筐体実験・円形光源版: 光源も軸対称に理想化 +# +# scene_sphere_r100_loft.yaml(矩形 LED 2.8×3.5mm)からの変更点: +# - 発光面を shape: rectangle → shape: disk(半径 1.7662mm)に変更 +# 面積 9.8mm² を保存(π×1.7662² ≈ 2.8×3.5)=同じ radiance で全光束も不変 +# - 矩形 LED は実機由来のアーティファクトで系の軸対称性をわずかに破る最後の +# 要素だったため,板・球・ロフトと同軸の円板にして完全な軸対称セットアップにする +# +# 位置 (0,0,-142)・向き (0,0,-1)=壁向き(評価板への直接光ゼロ)・radiance・ +# 筐体メッシュ(球ロフト一体)・材料・センサ・レンダ設定(spp=1024・d=16)は +# loft 版と同一. +name: sphere_r100_loft_disk +description: > + 球体筐体実験(TEST_11 円形光源版).球お椀+板輪郭ロフト一体キャビティに, + 矩形 LED と同面積の円板単灯(半径 1.7662mm)を軸上に壁向きで置いた + 完全軸対称の理想積分球条件. +geometry: + meshes: + - id: sphere_housing + path: meshes/球ロフト_r100.stl + unit: mm + material: printer_white + - id: evaluation_board + path: meshes/照明ムラ補正ボード.stl + unit: mm + material: printer_white +materials: + printer_white: + type: blendbsdf + weight: 0.05 # specular 比(TEST_10 確定: 形を壊さない最弱の光沢) + components: + - type: diffuse + reflectance: + - 0.85 + - 0.85 + - 0.85 + - type: roughconductor + alpha: 0.1 + distribution: ggx +lights: +- id: led_pole_disk + type: area + shape: disk + center: + - 0.0 + - 0.0 + - -142.0 + radius: 1.7662 # 矩形 LED 2.8×3.5mm と同面積 9.8mm² の円板 + direction: + - 0.0 + - 0.0 + - -1.0 + radiance: + - 2807.2469840551203 + - 2807.2469840551203 + - 2807.2469840551203 +sensor: + type: orthographic + position: + - 0.0 + - 0.0 + - -50.0 + target: + - 0.0 + - 0.0 + - 0.0 + up: + - 0.0 + - 1.0 + - 0.0 + ortho_scale: + - 50.0 + - 52.0 + film: + width: 512 + height: 512 + rfilter: gaussian +render: + variant: llvm_ad_rgb + integrator: path + max_depth: 16 + # TEST_09/10 系と条件統一(比較の公平性 > 単灯の MC ノイズ低減) + spp: 1024 diff --git "a/docs/02_ENV/ENV_02_\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" "b/docs/02_ENV/ENV_02_\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" index fd62943..0a5f5c5 100644 --- "a/docs/02_ENV/ENV_02_\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" +++ "b/docs/02_ENV/ENV_02_\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" @@ -138,6 +138,17 @@ - ネットワーク状態を確認.Mitsuba 3 の wheel はサイズが大きい(〜100 MB) - 企業ネットワーク内ではプロキシ設定が必要:`uv` は環境変数 `HTTPS_PROXY` を尊重する +### scalar_rgb variant や ad-hoc スクリプトで `LLVM API initialization failed` が出る + +- `llvm_init.py` の自動探索が働くのは **sm-render 等の CLI が llvm_* variant を要求した場合のみ**. + 以下の 2 ケースでは自動探索が呼ばれず,LLVM がインストール済みでも drjit 自身の探索が + 「jitc_llvm_init(): LLVM API initialization failed」で失敗することがある(2026-08-17 の新規構築マシンで確認): + - `variant: scalar_rgb` のシーンを sm-render で実行する場合(drjit は scalar でも LLVM を初期化しようとする) + - `uv run python` で mitsuba を直接 import する ad-hoc スクリプト +- 対処: コマンド実行前に `$env:DRJIT_LIBLLVM_PATH = 'C:\Program Files\LLVM\bin\LLVM-C.dll'` を明示設定する + (または「DRJIT_LIBLLVM_PATH の設定」セクションの手順で User scope に永続化する). +- LLVM 22.1.8(winget 導入)で llvm_ad_rgb / scalar_rgb とも動作確認済み(明示設定時). + ### `LLVM-C.dll could not be found` エラーが出る - コード側が自動探索を試みるが,LLVM が標準パス以外にインストールされているか, diff --git "a/docs/04_SPEC/SPEC_02_\343\202\267\343\203\274\343\203\263\345\256\232\347\276\251\344\273\225\346\247\230.md" "b/docs/04_SPEC/SPEC_02_\343\202\267\343\203\274\343\203\263\345\256\232\347\276\251\344\273\225\346\247\230.md" index 80a499e..305bef6 100644 --- "a/docs/04_SPEC/SPEC_02_\343\202\267\343\203\274\343\203\263\345\256\232\347\276\251\344\273\225\346\247\230.md" +++ "b/docs/04_SPEC/SPEC_02_\343\202\267\343\203\274\343\203\263\345\256\232\347\276\251\344\273\225\346\247\230.md" @@ -103,9 +103,17 @@ - id: led_ring_segment type: area - shape: rectangle + shape: rectangle # rectangle | disk center: [0, 0, 20] - size: [10, 10] # mm + size: [10, 10] # mm(shape=rectangle で必須) + direction: [0, 0, -1] + radiance: [50, 50, 50] + + - id: led_disk # 円形面光源(軸対称の理想化光源等,TEST_11 で追加) + type: area + shape: disk + center: [0, 0, -142] + radius: 1.766 # mm(shape=disk で必須,正値) direction: [0, 0, -1] radiance: [50, 50, 50] @@ -123,7 +131,7 @@ | type | Mitsuba 対応 emitter | 用途 | 主要パラメータ | | --- | --- | --- | --- | | `point` | `point` | 点光源(LED 単体モデル等) | `position`, `intensity`, `profile` | -| `area` | `area`(rectangle 形状) | 面光源(矩形) | `center`, `size`, `direction`, `radiance` | +| `area` | `area`(rectangle / disk 形状) | 面光源(矩形または円板.`shape` に応じて `size` / `radius` が必須) | `center`, `size` または `radius`, `direction`, `radiance` | | `directional` | `directional` | 平行光源(無限遠光源) | `direction`, `irradiance` | | `spot` | `spot` | スポット光源(円錐配光・LED 指向性モデル,Step 8-10 で追加) | `position`, `direction`, `cutoff_angle`, `beam_width`, `intensity` | 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 4277fb8..e5f049a 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" @@ -1,6 +1,8 @@ # シミュレーション実行記録 (Simulation Run Catalog) > ⚠️ **BEFORE FIX 注記**: 本索引のうち面光源スケール軸バグ修正([TEST_08](TEST_08_面光源スケール軸バグの発見と修正.md))**以前**に生成された出力(`baseline` / `baseline_v3_leds` / `led_group_sanity` / `max_depth_sweep` / `reflectance_sweep` / `sweep_reflectance` / `blend_sweep` / `led_distribution_sweep` と対応する `_slide_panels` 図)は,**全 LED が 2m ストリップ化したバグ込み**の結果.実験記録として保持するが数値はそのまま信用しないこと.修正後の出力は `sanity_fix_1024` / `baseline_fix_1024` / `depth_bottom` / `refl_bottom` / `distrib_bottom`,図は `*_roi.png`(修正前を明記復元した版は `*_buggy.png`). +> +> ⚠️ **BEFORE FIX 注記 (2) シェーディング法線アーティファクト**: **2026-08-18 の面法線修正([TEST_12](TEST_12_シェーディング法線アーティファクトの発見と修正.md))以前の全レンダ出力**(`depth_bottom` / `refl_bottom` / `blend_bottom` / `loft_w1p4` / `sphere_r100{,_loft,_loft_disk}` 等を含む,評価板メッシュを使う全シーン)には,PLY 自動スムーズ法線による**評価面の偽の異方性(現行筐体全灯で CoV を 0.061→0.172 に見せる支配的な系統誤差)**が乗っている.絶対値・形の数値は信用せず,同一条件同士の相対比較としてのみ参照すること.修正後の基準出力は `full_base_fix` / `sphere_r100_loft_disk_fix`. 本ドキュメントは `output/` 配下に蓄積されたシミュレーション・評価結果ディレクトリを **「何の試行か」が分かる形に索引化** したものである.`output/` は `.gitignore` で追跡対象外(容量・再生成可能性のため)であり,**手元の `output/` が無くてもこのドキュメントだけ読めば「過去にどんな試行があったか・どう再現するか」が辿れる**ことを目的とする. @@ -64,6 +66,22 @@ | `output/loft_w1p4/full_{w2p0,w4p0}/` (2 ディレクトリ) | TEST_10 拡張 | `configs/_tmp_loft_w1p4/{w2p0,w4p0}_full.yaml`(full.yaml の後側を `後側_{w2p0,w4p0}.stl` に差し替え) | `configs/eval_baseline_dng_target_shape.yaml` | ロフト重み 0.5→4.0 で全灯指標は横ばい(w4.0 のみ微悪化 CoV 0.1772)— [TEST_10 §拡張](TEST_10_後側ロフト変形w1p4実験.md) 参照 | TEST_10 全灯ロフト重みスイープ.ロフトは全灯分布に実質不感と確定 | | `output/_slide_panels/loft_full_sweep_shape_roi.png` | TEST_10 拡張 | — | — | 全灯 w0.5/1.4/2.0/4.0 + 実機の 1×5 形比較パネル | TEST_10 拡張視覚比較用(`scripts/build_loft_w1p4_panel.py` が summary.csv とあわせて生成) | | `output/_slide_panels/loft_full_sweep_relative.png` | TEST_10 拡張 | — | — | 同スイープの相対輝度版(シミュ 4 条件のみ 1×4・各セル max=1.0・全セル共通スケール・右カラーバー) | 均一性の絶対比較用.mean/max 0.64〜0.67 で横ばい(実機参照値 0.899 は TEST_10 の表を参照) | +| `output/sphere_r100/` | TEST_11 | `configs/scene_sphere_r100.yaml`(完全閉球殻 r=100mm 中心 (0,0,-40) + 評価板のみ,単灯 (0,0,-90) 壁向き=直接光ゼロ,blend w0.05_a0.1・d=16・**spp=8192**) | `configs/eval_baseline_dng_target_shape.yaml` | CoV 0.1350 / peak/mean 1.323 / angular_ratio 0.126 / NCC 対実機 0.460 — [TEST_11](TEST_11_球体筐体実験.md) 参照 | TEST_11 球体筐体実験.幾何の理想極限(積分球)でも実機の均一性に届かないことの上限実証 | +| `output/sphere_r100/summary.csv` | TEST_11 | — | — | 現行全灯基準・球体単灯・実機の形指標集約 (label, peak_mean, top_bot, cov_roi, ncc_vs_real) | `scripts/build_sphere_panel.py` の集約出力 | +| `output/_slide_panels/sphere_r100_{shape_roi,relative}.png` | TEST_11 | — | — | 現行全灯/球体単灯/実機の 1×3 形比較パネル+相対輝度版(各セル max=1.0・共通スケール) | TEST_11 初版の視覚比較(スクリプトは 4 セル版に更新済みのため再生成すると 1×4 になる) | +| `output/sphere_r100_loft/` | TEST_11 改訂 | `configs/scene_sphere_r100_loft.yaml`(球お椀+板輪郭ロフト一体メッシュ `球ロフト_r100.stl`,単灯 (0,0,-142) 壁向き,blend w0.05_a0.1・d=16・spp=1024=TEST_09/10 と条件統一) | `configs/eval_baseline_dng_target_shape.yaml` | CoV 0.1204 / peak/mean 1.392 / angular_ratio 0.110 / NCC 対実機 0.445 — [TEST_11 §改訂](TEST_11_球体筐体実験.md) 参照 | TEST_11 改訂(板嵌め込み・一体化).幾何理想極限の CoV 床 ≈0.12 を確定 | +| `output/sphere_r100_loft/summary.csv` | TEST_11 改訂 | — | — | 現行全灯・球浮き配置・球ロフト・実機の 4 行形指標集約 | `scripts/build_sphere_panel.py`(4 セル版)の集約出力 | +| `output/_slide_panels/sphere_loft_{shape_roi,relative}.png` | TEST_11 改訂 | — | — | 現行全灯・球浮き・球ロフト矩形・球ロフト円形・実機の 1×5 形比較パネル+相対輝度版 | TEST_11 改訂/追補の視覚比較・発表スライド素材 | +| `output/sphere_r100_loft_disk/` | TEST_11 追補 | `configs/scene_sphere_r100_loft_disk.yaml`(ロフト一体キャビティ+同面積円板単灯 r=1.7662mm=完全軸対称,spp=1024・d=16) | `configs/eval_baseline_dng_target_shape.yaml` | CoV 0.1205 ほか全指標が矩形版と MC ノイズ内で一致 — [TEST_11 §追補](TEST_11_球体筐体実験.md) 参照 | TEST_11 追補(円形光源).発光面形状の不感を確認.**以後の球実験の正準構成** | +| `output/sphere_r100{,_fit,_loft}/geometry/` | TEST_11 | — | — | レンダ前 3D 配置確認のスクリーンショット(`--no-bake` レイアウト・LED 4 アングル・path ベイク) | 幾何変更時のレンダ前 3D 確認ワークフロー(CLAUDE.md 必須ルール)の記録.fit 版はレンダ未実施(ロフト版に置換) | +| `output/full_base_fix/` | TEST_12 | `configs/_tmp_fn_full_base.yaml`(scene_baseline_blend の w=0.05/α=0.1/d=16 版=TEST_10 full_base の再構成.修正前コードで保存済み結果と一致検証済み) | `configs/eval_baseline_dng_target_shape.yaml` | **CoV 0.0610 / 勾配 6.83% / C/P 1.073**(面法線修正後)— [TEST_12](TEST_12_シェーディング法線アーティファクトの発見と修正.md) 参照 | **面法線修正後の現行筐体全灯の新基準**.実機(CoV 0.0695)と数値レベルで整合 | +| `output/sphere_r100_loft_disk_fix/` | TEST_12 | `configs/scene_sphere_r100_loft_disk.yaml`(面法線修正後の再レンダ) | 同上 | CoV 0.0628 / angular_ratio 0.005(完全同心円) | 面法線修正後の球体筐体の正準結果.現行筐体とほぼ同等=球形化の優位性消滅 | +| `output/_slide_panels/sphere_fix_{shape_roi,relative}.png` | TEST_12 | — | — | 現行筐体 fix/球ロフト円形 fix/実機の 1×3 パネル(修正後のみで比較) | TEST_12 視覚比較・発表スライド素材(`scripts/build_sphere_panel.py` 現行版が生成) | +| `output/singles_fix/{bottom,left,right}/` | TEST_12 追補 | `configs/_tmp_fix_{bottom,left,right}.yaml`(検証済み `_tmp_fn_full_base.yaml` から灯グループ抽出.blend w0.05_a0.1・d=16・spp=1024) | 形指標のみ(`scripts/build_singles_fix_summary.py`) | 灯間 NCC(平滑)が修正前 0.99 均等 → **修正後 0.85/0.93/0.73 の非対称** — [TEST_12 §追補](TEST_12_シェーディング法線アーティファクトの発見と修正.md) 参照 | TEST_09 中核結論「3 灯同形」の撤回根拠.単灯 spp=1024 はノイズ 5.3% > 構造 4% のため raw 指標はノイズ支配(以後の単灯は spp≥4096 推奨) | +| `output/singles_fix/{summary,pairwise_ncc}.csv`・`output/_slide_panels/singles_fix_shape_roi.png` | TEST_12 追補 | — | — | 単灯形指標(修正前後・w1p4 対比,平滑後 p/m 併記)・灯間 NCC(raw/smooth)・3×4 パネル | `scripts/build_singles_fix_summary.py` の出力 | +| `output/full_w1p4_fix/`・`output/singles_fix/{bottom,left,right}_w1p4/` | TEST_12 追補 | `configs/_tmp_fix_{full,bottom,left,right}_w1p4.yaml`(後側を `後側_w1p4.stl` に差し替え,blend w0.05_a0.1・d=16・spp=1024) | 全灯のみ `eval_baseline_dng_target_shape.yaml` | 全灯 CoV 0.0605(基準 fix 0.0610 と差 1%)=**ロフト不感は修正後も維持**.単灯は平滑 p/m 差 ≤0.9%・灯間 NCC は +0.05〜0.12 同形化 — [TEST_12 §ロフト再検証](TEST_12_シェーディング法線アーティファクトの発見と修正.md) 参照 | TEST_10 相対結論の再検証.「bottom 平ら化=幾何レバー実証」は撤回相当に弱まり,不感性の結論が生存 | +| `output/_slide_panels/full_vs_singles_fix.png` | TEST_12 追補 | — | — | シミュ(面法線修正後)×実機の 2 行 × 全灯/bottom/left/right 4 列パネル(全セル同一平滑化・NCC 対実機併記) | 全灯と単灯の形の対応比較(`scripts/build_full_vs_singles_panel.py` が生成).平滑後 p/m はシミュ 1.07 前後 vs 実機 1.10〜1.16 | +| `output/_slide_panels/sphere_loft_disk_before_after.png`・`normal_artifact_explainer.png` | TEST_12 | — | — | 球ロフト円形光源の修正前後 1×2 比較(相対輝度・共通スケール)/法線アーティファクトの機構説明図(模式図+実物メッシュの補間法線再現+修正前マップの 4 パネル) | 発表スライド素材(アドホック生成.修正前後比較は CoV 0.1205→0.0628 の視覚版) | ### sweep_summary.csv の値(参考、TEST_01 line と一致確認用) @@ -111,6 +129,16 @@ | loft 重みスイープ レンダ (TEST_10 拡張) | `$env:DRJIT_LIBLLVM_PATH = "C:\Program Files\LLVM\bin\LLVM-C.dll"; uv run sm-render --scene configs/_tmp_loft_w1p4/{w2p0,w4p0}_full.yaml --output output/loft_w1p4/full_{w2p0,w4p0}/` | | loft_w1p4 全灯評価 (TEST_10) | `uv run sm-evaluate --image output/loft_w1p4/{full,full_base}/luminance.npy --config configs/eval_baseline_dng_target_shape.yaml --output output/loft_w1p4/{full,full_base}/` | | loft_w1p4 形指標解析・パネル生成 (TEST_10) | `uv run python scripts/build_loft_w1p4_panel.py` (要 `output/loft_w1p4/` と元メッシュ基準 `output/blend_bottom/` の各 `luminance.npy` + 実機 `lightsim/luminance_maps/*.npy`.summary.csv と 4×3 パネルを再生成) | +| 球殻メッシュ生成 (TEST_11) | `uv run python scripts/generate_sphere_housing.py --radius 100 --thickness 3 --center 0 0 -40 --output configs/meshes/球殻_r100.stl` | +| 球体筐体レンダ (TEST_11) | `$env:DRJIT_LIBLLVM_PATH = "C:\Program Files\LLVM\bin\LLVM-C.dll"; uv run sm-render --scene configs/scene_sphere_r100.yaml --output output/sphere_r100/` (spp=8192 で約 36 分) | +| 球体筐体評価 (TEST_11) | `uv run sm-evaluate --image output/sphere_r100/luminance.npy --config configs/eval_baseline_dng_target_shape.yaml --output output/sphere_r100/` | +| 球体筐体パネル生成 (TEST_11) | `uv run python scripts/build_sphere_panel.py` (4 セル版.要 `output/sphere_r100{,_loft}/` と `output/loft_w1p4/full_base/` の `luminance.npy` + 実機 `lightsim/luminance_maps/full_*.npy`) | +| 球ロフト一体メッシュ生成 (TEST_11 改訂) | `uv run python scripts/generate_sphere_loft_housing.py --output configs/meshes/球ロフト_r100.stl` | +| 球ロフト版レンダ (TEST_11 改訂) | `$env:DRJIT_LIBLLVM_PATH = "C:\Program Files\LLVM\bin\LLVM-C.dll"; uv run sm-render --scene configs/scene_sphere_r100_loft.yaml --output output/sphere_r100_loft/` (spp=1024 約 7.4 分) | +| 球ロフト版評価 (TEST_11 改訂) | `uv run sm-evaluate --image output/sphere_r100_loft/luminance.npy --config configs/eval_baseline_dng_target_shape.yaml --output output/sphere_r100_loft/` | +| レンダ前 3D 配置確認 (幾何変更時必須) | `uv run sm-visualize --scene --no-bake --screenshot ` + `uv run python scripts/show_led_geometry.py --scene --screenshot ` | +| 球ロフト円形光源版レンダ・評価 (TEST_11 追補) | `$env:DRJIT_LIBLLVM_PATH = "C:\Program Files\LLVM\bin\LLVM-C.dll"; uv run sm-render --scene configs/scene_sphere_r100_loft_disk.yaml --output output/sphere_r100_loft_disk/` → `uv run sm-evaluate --image output/sphere_r100_loft_disk/luminance.npy --config configs/eval_baseline_dng_target_shape.yaml --output output/sphere_r100_loft_disk/` | +| 単灯再検証レンダ・集計 (TEST_12 追補) | `$env:DRJIT_LIBLLVM_PATH = "C:\Program Files\LLVM\bin\LLVM-C.dll"; uv run sm-render --scene configs/_tmp_fix_{bottom,left,right}.yaml --output output/singles_fix/{bottom,left,right}/` → `uv run python scripts/build_singles_fix_summary.py` | ## 過去の整理実績 (Cleanup History) diff --git "a/docs/06_TEST/TEST_09_\347\205\247\346\230\216\345\210\206\345\270\203\343\201\256\345\275\242\344\270\215\344\270\200\350\207\264\343\201\256\345\216\237\345\233\240\345\210\207\343\202\212\345\210\206\343\201\221.md" "b/docs/06_TEST/TEST_09_\347\205\247\346\230\216\345\210\206\345\270\203\343\201\256\345\275\242\344\270\215\344\270\200\350\207\264\343\201\256\345\216\237\345\233\240\345\210\207\343\202\212\345\210\206\343\201\221.md" index 4179775..fa18428 100644 --- "a/docs/06_TEST/TEST_09_\347\205\247\346\230\216\345\210\206\345\270\203\343\201\256\345\275\242\344\270\215\344\270\200\350\207\264\343\201\256\345\216\237\345\233\240\345\210\207\343\202\212\345\210\206\343\201\221.md" +++ "b/docs/06_TEST/TEST_09_\347\205\247\346\230\216\345\210\206\345\270\203\343\201\256\345\275\242\344\270\215\344\270\200\350\207\264\343\201\256\345\216\237\345\233\240\345\210\207\343\202\212\345\210\206\343\201\221.md" @@ -1,5 +1,14 @@ # 照明分布の形不一致の原因切り分け (Shape Mismatch Root-Cause Investigation) +> ⚠️ **BEFORE FIX 注記**: 本ドキュメントの数値はすべて**シェーディング法線アーティファクト +> ([TEST_12](TEST_12_シェーディング法線アーティファクトの発見と修正.md),2026-08-18 修正)込み**である. +> 修正後の単灯再検証(TEST_12 追補)により,中核結論のうち **「シミュは 3 灯がほぼ同形(灯間 NCC +> 0.95x/0.99)=非対称相補性を原理的に再現できない → B1 非対称幾何が唯一の残存候補」は撤回**. +> 高相関は全マップ共通のアーティファクト構造が作った見かけで,修正後のシミュは非対称な相補性を +> 部分的に再現する(left-right が最も異なる点は実機と一致).「Y 軸単調勾配」「単灯 peak/mean +> 1.5〜1.7」等の絶対量も大半がアーティファクト起因.レバー探索の経緯・実機アンカーの定量化の +> 記録として本文を保持する. + 本ドキュメントは,[TEST_08 面光源スケール軸バグ](TEST_08_面光源スケール軸バグの発見と修正.md) の修正後もなお残る**「照明分布の“形”が実機と一致しない」問題**について,原因を実験で切り分けていく継続記録である. > **最優先の判定基準**: 本調査は CoV/勾配などの**数値の絶対量ではなく,実機マップとの「形の一致」**を唯一の判定基準とする.形が一致しない限り「筐体を仮想的に変えて結果を予測する」というツール本来の目的(筐体改善の事前検討)が成立しないため.判定には実機マップを参照とした **NCC/SSIM + 視覚比較 + 放射プロファイル**を用い,peak/mean(中心の尖り具合)を補助指標とする. diff --git "a/docs/06_TEST/TEST_10_\345\276\214\345\201\264\343\203\255\343\203\225\343\203\210\345\244\211\345\275\242w1p4\345\256\237\351\250\223.md" "b/docs/06_TEST/TEST_10_\345\276\214\345\201\264\343\203\255\343\203\225\343\203\210\345\244\211\345\275\242w1p4\345\256\237\351\250\223.md" index dc83318..1cafd71 100644 --- "a/docs/06_TEST/TEST_10_\345\276\214\345\201\264\343\203\255\343\203\225\343\203\210\345\244\211\345\275\242w1p4\345\256\237\351\250\223.md" +++ "b/docs/06_TEST/TEST_10_\345\276\214\345\201\264\343\203\255\343\203\225\343\203\210\345\244\211\345\275\242w1p4\345\256\237\351\250\223.md" @@ -1,5 +1,12 @@ # 後側ロフト変形 w=1.4 実験 (Rear Housing Loft Deformation Experiment) +> ⚠️ **BEFORE FIX 注記**: 本ドキュメントの数値はすべて**シェーディング法線アーティファクト +> ([TEST_12](TEST_12_シェーディング法線アーティファクトの発見と修正.md),2026-08-18 修正)込み**である. +> 修正後の再検証(TEST_12 §ロフト w1.4 の再検証)による結論の生死: **「全灯に実質不感」と +> 「left/right 不動」「対称拡大は同形化方向」は維持**(実機 w1.4 測定への予測も不変). +> 一方 **「bottom 単灯 peak/mean 1.672→1.508 の明確な平ら化=幾何レバー初実証」は撤回相当に弱まる** +> (修正後の平滑指標では -0.9% の微小効果.-10% はアーティファクト期の見かけが大半). + ## 背景と目的 (Background & Purpose) [TEST_09](TEST_09_照明分布の形不一致の原因切り分け.md) で光源側・材料側のレバーが出尽くし,実機との形の不一致(peak/mean の平らさ・灯間 NCC の非対称な相補性)の残差は **B1 筐体幾何に確定** した.本実験では,実機再印刷の前段として **後側筐体のロフト形状を変形させた STL** をシミュレーションで評価し,筐体幾何の変更が照明分布の形に与える影響を定量化する. diff --git "a/docs/06_TEST/TEST_11_\347\220\203\344\275\223\347\255\220\344\275\223\345\256\237\351\250\223.md" "b/docs/06_TEST/TEST_11_\347\220\203\344\275\223\347\255\220\344\275\223\345\256\237\351\250\223.md" new file mode 100644 index 0000000..cc8d165 --- /dev/null +++ "b/docs/06_TEST/TEST_11_\347\220\203\344\275\223\347\255\220\344\275\223\345\256\237\351\250\223.md" @@ -0,0 +1,130 @@ +# 球体筐体実験 (Spherical Housing Experiment) + +> ⚠️ **BEFORE FIX 注記**: 本ドキュメントの数値はすべて**シェーディング法線アーティファクト +> ([TEST_12](TEST_12_シェーディング法線アーティファクトの発見と修正.md),2026-08-18 修正)込み**で +> 測定されたものである.本実験の完全軸対称化がこのバグの発見につながった.修正後の再測定では +> **現行筐体 CoV 0.0610 / 球ロフト 0.0628 と両者ほぼ同等**(実機 0.0695)であり, +> 「幾何理想極限でも CoV 床 ≈0.12」「球で現行比 -30% 改善」という本文の結論は**撤回**する. +> 現行筐体はすでに理想積分球に近い混合性能を持つ,が修正後の結論. +> 実験設計・経緯・修正前の相対比較の記録として本文を保持する. + +## 背景と目的 (Background & Purpose) + +[TEST_10](TEST_10_後側ロフト変形w1p4実験.md) で,ロフト重み(対称なフレア拡大)は体積 +66% まで振っても全灯分布に実質不感と確定した.本実験は幾何レバーを**極端側に振り切る**: 実機筐体の構成(前側/後側/カバー/ホルダ/スマホ)をすべて外し,評価板だけを共通土台として残して**完全に閉じた球殻**で囲み,**単灯・完全間接照明**という理想積分球条件で照明均一性がどこまで到達するかの**上限(upper bound)**を測る. + +- 球(積分球)は拡散反射による光の混合が理論上最も効く形状であり,漏れゼロ・対称・完全間接の組は「幾何+閉じ方の理想極限」に相当する +- この上限が実機(CoV 0.0695・mean/max 0.899)に届くかどうかで,「実機との残差のうち幾何で説明できる範囲」を切り分けられる +- 光源が 1 灯のため,灯間 NCC(非対称相補性)は本実験の検証対象外 + +## シーン構成の事実記録 (Scene Facts) + +| 要素 | 内容 | +| --- | --- | +| 球殻 | `configs/meshes/球殻_r100.stl`.内半径 100mm・厚 3mm・中心 (0, 0, -40).`scripts/generate_sphere_housing.py` で生成(icosphere subdiv=6 の内外 2 面 163,840 faces,watertight,体積 388,361mm³ で理論値と一致=内面法線反転の正当性確認済み).開口なしの完全閉球 | +| 評価板 | `照明ムラ補正ボード.stl` を現行と同位置・同材料のまま内包(最遠角 (49.5, 51, 12.5) は中心から 88.4mm < 100mm) | +| 光源 | **1 灯のみ**.現行と同じ 2.8×3.5mm area LED を軸上 (0, 0, -90)(-Z 極壁から 50mm 手前)に置き,向き (0, 0, -1) =壁向き.発光半球が後方壁側を向くため**評価板への直接光は幾何的にゼロ** | +| 材料 | 球殻・評価板とも blend w0.05_a0.1(TEST_10 確定条件) | +| radiance | 2807.247 W/(m²·sr)(現行 per-LED 校正値をそのまま使用.形状指標はスケール不変) | +| センサ | 現行と完全同一(orthographic Z=-50・ortho_scale (50, 52)・512²) | +| レンダ | path・d=16・**spp=8192**(下記「分散対策の経緯」参照).llvm_ad_rgb で約 36 分 | +| config / 出力 | `configs/scene_sphere_r100.yaml` → `output/sphere_r100/` | + +### 分散対策の経緯(LED 位置と spp) + +当初案「壁 5mm 手前 (z=-135)」は path tracer の分散が実用不能だった(spp=64 で相対ノイズ σ=89%.直接照射キャップが半径 31mm と小さく NEE がほぼ外れる+壁 5mm の 1/r² 特異で firefly).対策として: + +- **LED を z=-90(壁から 50mm)へ移動**: NEE 可視領域が球面積の 25% に拡大し σ=9.3%@spp64 まで 10 倍改善.「評価板への直接光ゼロ」は厳密に維持される(板は発光面の裏側) +- **spp=1024 → 8192**: 残留ノイズ実測 σ=0.79%(CoV 0.135 への汚染は二乗和で +0.02% と無視可能).unbiased なので比較の公平性は不変 +- Mitsuba 3.8 の `ptracer`(光源側追跡)は orthographic センサ非対応(`Endpoint::sample_direction(): not implemented`)で本用途には使えない + +## 結果 (Results) + +実行日: 2026-08-17.数値は `output/sphere_r100/{summary.csv,metrics.csv}`,比較図は `output/_slide_panels/sphere_r100_{shape_roi,relative}.png`.評価は TEST_10 と同一パイプライン(`eval_baseline_dng_target_shape.yaml`・SIM_ROI・mean アンカー 207.82). + +| 指標 | 現行筐体 全灯 18 灯 (full_base) | **球体 r100 単灯** | 実機 全灯 18 灯 | +| --- | --- | --- | --- | +| CoV (ROI) | 0.1717 | **0.1350** | 0.0695 | +| peak/mean (ROI) | 1.491 | **1.323** | 1.155 | +| top/bot (ROI) | 0.878 | **1.021** | 0.919 | +| C/P ratio | 1.328 | 1.353 | 1.091 | +| gradient [%] | 24.72 | 26.10 | 8.35 | +| angular_ratio | 0.352 | **0.126** | — | +| centroid offset (norm) | 0.043 | **0.005** | — | +| NCC 対実機 (ROI) | 0.744 | **0.460** | 1.0 | +| mean/max(相対輝度) | 0.671 | **0.756** | 0.899 | +| min/max(相対輝度) | 0.241 | **0.531** | 0.660 | + +分布の形: 中央明・周辺減光の滑らかな軸対称ドーム.断面プロファイルに不連続なし(最大段差 <1% =ノイズレベル,楕円窓縁の段差は生じない).周辺減光は Y 方向(-38%)が X 方向(-15%)より強く,これは楕円窓・ROI 形状を含む評価板系の共通特徴(3 セル同一条件のため比較は成立). + +## 読み取れること (Findings) + +1. **幾何の理想極限でも CoV 0.135 まで**(現行比 -21%,mean/max 0.671→0.756).漏れゼロ・完全対称・完全間接の積分球ですら実機の 0.0695 / 0.899 には遠く及ばない.**筐体幾何の追い込みだけではシミュ上の均一性を実機水準まで持っていけない**ことの上限実証. +2. **形は質的に変わる**: Y 軸単調勾配(angular_ratio 0.352・centroid offset 0.043)→ 中心対称ドーム(0.126・0.005,top/bot 1.02).幾何+光源配置の対称化で「分布の非対称性」はほぼ消せる=幾何レバーは「形の対称性」には強く効く. +3. しかし **NCC 対実機はむしろ低下**(0.744 → 0.460).実機の形(Y 勾配+斜め構造)は「理想混合の形」とは構造的に別物であり,実機マップを幾何の理想化で近づけることはできない.実機の平坦さは ISP レンズシェーディング補正(SPEC_03 既知差分 #6)等の非幾何要因が本命,という Step 8-14 の推定と整合する. +4. **残る CoV 0.135 の物理源**: R=0.85 の有限反射率では「未混合の一次照射成分」が消えない.一次照射キャップ(-Z 極周辺)を評価板の各点が異なる見込み角で見るため,cos 依存のドーム状落ち込みが残る.積分球の理想(壁面ポートに試料を置く)と違い,板がキャビティ内に浮いた面である限りこの成分は幾何では消せない.改善余地はむしろ反射率 R↑(未混合成分の相対比↓)や光源多灯化・対称配置側にある. + +## 設計への示唆 (Implications) + +- 球体筐体は「対称できれいな形」を作るが,均一性の絶対量では現行筐体比 2 割改善にとどまる.**実機再設計の優先レバーとしては弱い**(製作性の悪さに見合わない). +- TEST_09/10 から続く結論がさらに強化された: 実機との残差(絶対均一性・非対称相補性)は**筐体幾何の外**にある.非対称幾何(漏れ・開口の非対称)の検証は TEST_09 実験4(幾何の漏れ点検)として別途残っている. +- 本実験で単灯・高 spp・幾何スクリプト生成のワークフロー(`generate_sphere_housing.py` → シーン YAML → `build_sphere_panel.py`)が整ったため,仮想幾何の理想化実験は今後低コストで追加できる(例: 半径スイープ,R=0.92 球,多灯球面配置). + +## 改訂 — 評価板嵌め込み・ロフト一体化版 (Revision: Loft-blended Housing) + +初版(上記)は評価板が球内に**浮いた**配置で,板の縁と球壁の間の隙間を光が回り込めた.ユーザー指摘「評価板がすっぽり嵌まるように置きたい」を受けて 2 段階で改訂した(2026-08-18): + +1. **fit 版**(`configs/scene_sphere_r100_fit.yaml`・`球殻_r100_fit.stl`): 球中心を (0,0,-92) へ移動し板裏面で断面円 48.4mm =板が断面を塞ぐ配置.ただし矩形と円の不整合で四隅が球壁を貫通・ROI 四隅に壁が写る.3D 確認段階でロフト版に置き換え(**レンダ未実施**) +2. **ロフト一体化版**(`configs/scene_sphere_r100_loft.yaml`・`球ロフト_r100.stl`): Fusion のロフトに相当する「板輪郭矩形 → 球断面円」の遷移スカートをスクリプト生成(`scripts/generate_sphere_loft_housing.py`.板輪郭 1.5mm 内側のリムを z=0=板実体に 4.5mm 食い込む高さに置いてシール,z=-40 の断面円 85.4mm へ Hermite ブレンド=球側接線連続,以深は球 r=100・中心 (0,0,-92) のお椀.watertight 縫合).LED は新 -Z 極(z=-192)の 50mm 手前 z=-142・壁向き(直接光ゼロ維持) + +あわせて 2 つの運用変更(ユーザー指示): + +- **レンダ前の 3D 配置確認を必須化**(`sm-visualize --no-bake` / `show_led_geometry.py` で確認画像を提示 → OK 後にレンダ.CLAUDE.md 必須ルールに追記) +- **spp を 8192 → 1024 に統一**(TEST_09/10 系との条件統一を優先.残留ノイズ実測 2.3%,CoV への汚染 +0.002 で解釈に影響なし) + +### 結果(ロフト一体化版) + +実行日: 2026-08-18.数値は `output/sphere_r100_loft/{summary.csv,metrics.csv}`,比較図は `output/_slide_panels/sphere_loft_{shape_roi,relative}.png`(1×4 パネルに拡張). + +| 指標 | 現行筐体 全灯 | 球体 浮き配置 単灯 | **球ロフト一体 単灯** | 実機 全灯 | +| --- | --- | --- | --- | --- | +| CoV (ROI) | 0.1717 | 0.1350 | **0.1204** | 0.0695 | +| peak/mean (ROI) | 1.491 | 1.323 | **1.392** | 1.155 | +| top/bot (ROI) | 0.878 | 1.021 | **1.020** | 0.919 | +| C/P ratio | 1.328 | 1.353 | **1.310** | 1.091 | +| gradient [%] | 24.72 | 26.10 | **23.66** | 8.35 | +| angular_ratio | 0.352 | 0.126 | **0.110** | — | +| NCC 対実機 (ROI) | 0.744 | 0.460 | **0.445** | 1.0 | +| mean/max(相対輝度) | 0.671 | 0.756 | **0.718** | 0.899 | +| min/max(相対輝度) | 0.241 | 0.531 | **0.502** | 0.660 | + +### 読み取れること(改訂版) + +1. **縁のシール+一体化で CoV 0.135 → 0.120**(現行筐体比 -30%).板縁の隙間への回り込み・ROI 内の壁写り込みが消えた効果で,均一性の幾何上限がさらに更新された. +2. ただし **peak/mean は 1.323 → 1.392 と微増**(中央の明帯がやや細く尖る.mean/max も 0.756 → 0.718).くびれ(ロフトスカート)が板の周縁を覆う立体角を狭め,周辺照度が相対的に下がったためと解釈できる=**キャビティを閉じるほど CoV は下がるが中央集中はわずかに強まる**トレードオフ. +3. 対称性はさらに向上(angular_ratio 0.110・centroid offset 0.005・top/bot 1.02). +4. **結論は初版と不変**: 幾何+閉じ方の理想極限を突き詰めても CoV ≈ 0.12 が床で,実機 0.0695(ISP LSC 込み)には届かない.NCC 対実機 0.445 =実機の形とは別物のまま.残差の本命は非幾何要因(LSC 等)という結論がむしろ強化された. + +## 追補 — 円形光源版(完全軸対称化)(Addendum: Disk Emitter) + +ロフト一体化版の光源は矩形 LED(2.8×3.5mm)のままで,系の軸対称性を破る最後の要素だった.ユーザー指示により発光面を**同面積(9.8mm²)の円板(半径 1.7662mm)**に置換し,幾何・光源とも完全軸対称の構成にした(2026-08-18). + +- スキーマ拡張: area 光源に `shape: disk`(`radius` 必須)を追加(`rectangle`+`size` は後方互換).SPEC_02 更新,スケール実寸検証を含むテスト追加 +- config: `configs/scene_sphere_r100_loft_disk.yaml` → `output/sphere_r100_loft_disk/`.発光面形状以外はロフト版と完全同一(位置 (0,0,-142)・壁向き・radiance・spp=1024・d=16) + +### 結果(円形光源版) + +| 指標 | 球ロフト 矩形 LED | 球ロフト 円形光源 | +| --- | --- | --- | +| CoV (ROI) | 0.12044 | 0.12047 | +| peak/mean (ROI) | 1.3923 | 1.3931 | +| angular_ratio | 0.1098 | 0.1098 | +| centroid offset (norm) | 0.00472 | 0.00472 | +| NCC 対実機 (ROI) | 0.4452 | 0.4453 | + +**全指標が MC ノイズ(σ≈2.3%)内で完全一致**.完全間接キャビティでは発光面の形(矩形 vs 円板)は結果に影響しない=「Lambertian 拡散壁の多重反射が光源形状を洗い流す」(TEST_09 §追加観察)の再確認であり,矩形 LED の非軸対称性は残差要因ではないことが確定した.以後,**円形光源版(完全軸対称)を球体筐体実験の正準構成**とする. + +## 参照 (References) + +- [TEST_10_後側ロフト変形w1p4実験](TEST_10_後側ロフト変形w1p4実験.md): 幾何レバー(対称変形)の不感性 +- [TEST_02_シミュレーション実行記録](TEST_02_シミュレーション実行記録.md): 出力索引・再現コマンド +- [SPEC_03_基準設計の物理構成](../04_SPEC/SPEC_03_基準設計の物理構成.md): 既知差分 #6(ISP レンズシェーディング) diff --git "a/docs/06_TEST/TEST_12_\343\202\267\343\202\247\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\346\263\225\347\267\232\343\202\242\343\203\274\343\203\206\343\202\243\343\203\225\343\202\241\343\202\257\343\203\210\343\201\256\347\231\272\350\246\213\343\201\250\344\277\256\346\255\243.md" "b/docs/06_TEST/TEST_12_\343\202\267\343\202\247\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\346\263\225\347\267\232\343\202\242\343\203\274\343\203\206\343\202\243\343\203\225\343\202\241\343\202\257\343\203\210\343\201\256\347\231\272\350\246\213\343\201\250\344\277\256\346\255\243.md" new file mode 100644 index 0000000..644ba90 --- /dev/null +++ "b/docs/06_TEST/TEST_12_\343\202\267\343\202\247\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\346\263\225\347\267\232\343\202\242\343\203\274\343\203\206\343\202\243\343\203\225\343\202\241\343\202\257\343\203\210\343\201\256\347\231\272\350\246\213\343\201\250\344\277\256\346\255\243.md" @@ -0,0 +1,195 @@ +# シェーディング法線アーティファクトの発見と修正 (Shading Normal Artifact) + +## 背景 — 発見の経緯 (Background) + +[TEST_11](TEST_11_球体筐体実験.md) で幾何・光源を完全軸対称(球ロフト一体キャビティ+軸上の円板単灯)にしたにもかかわらず,輝度マップが同心円にならず**横長の楕円帯**(X 方向に伸びた明帯)を示した.ユーザー指摘「3D は上下左右で違いがないのに輝度マップが円状にならないのは違和感がある」を起点に原因を切り分けた(2026-08-18). + +定量観察: 中心からの同心円環で角度別平均をとると,どの半径でも**右=左 > 上=下**のきれいな 2 回対称で,X/Y 比は 1.15〜1.26(浮き配置・ロフト版とも). + +## 切り分け (Diagnosis) + +診断シーン(spp=256・他条件同一)による消去法: + +| # | 診断シーン | X/Y 比 | 判定 | +| --- | --- | --- | --- | +| D1 | 球殻+**円形プレート**(完全軸対称) | 1.00 | センサ・パイプラインはシロ(ortho_scale 50×52 も正しく処理) | +| D2 | 球殻+**素の矩形プレート**(99×102,飾りなし) | 0.99 | 矩形輪郭はシロ | +| D3 | ロフト筐体+円形プレート r=70 | 1.00 | ロフトの矩形→円スカートもシロ | +| D4 | ロフト筐体+素の矩形プレート | 1.00 | 同上 | +| — | (両筐体+**実物の評価板**) | 1.15〜1.26 | **評価板メッシュだけが非対称を生む** | +| D5 | ロフト筐体+実物の評価板+ **`face_normals=true`** | **1.000** | **原因確定=シェーディング法線** | + +補助調査: 評価板 STL の裏面(評価対象面)はレイキャストで無穴・完全平面(全ヒット z=-4.5)を確認済み.よって幾何そのものではなく描画法線の問題. + +## 原因 (Root Cause) + +3 つの要因の組み合わせ: + +1. **builder.py が STL→PLY 変換時に法線を書き出していなかった**(`trimesh.Trimesh(...).export(ply)` は頂点座標と面のみ) +2. 法線のない PLY に対して **Mitsuba は既定でスムーズ頂点法線を自動生成**する(`face_normals` 未指定 = false) +3. 評価板の裏面は **26 枚の巨大三角形(最大 4794mm²)**で構成され,**裏面の全 28 頂点がエッジ上**(内部頂点ゼロ)にある.エッジ頂点の平均化法線は側面との混合で**約 47° 傾く**ため,見えている裏面全体がこの傾いた法線の Phong 補間で描画される + +結果として,三角形分割の走り方に応じた大規模で滑らかな偽の異方性(cos 因子の歪み)が評価対象面の輝度マップに乗る.素の矩形プレート(D2/D4)で出なかったのは,トリメッシュ生成の box は分割が対称で汚染パターンが相殺されたため. + +## 修正 (Fix) + +`scene/builder.py` の PLY shape dict に **`face_normals: True`** を追加(2026-08-18).機械部品 STL は平面+シャープエッジ構成であり,面法線が物理的に正しい.曲面(球殻 icosphere subdiv=6,辺長 ≈3mm)のファセット化誤差はスムーズ法線の系統誤差より桁違いに小さい.再発防止テスト(`face_normals` キーの存在検証)を追加. + +## 影響の定量化 (Requantification) + +修正前後の同一シーン比較(spp=1024・d=16・blend w0.05_a0.1・mean アンカー 207.82,2026-08-18). +再構成 full_base シーン(`configs/_tmp_fn_full_base.yaml`)は修正前コードで保存済み結果と一致 +(ROI mean 5 桁一致・CoV 差 0.0001)を確認してから使用した. + +| 指標 | 現行筐体 全灯 修正前 | **現行筐体 全灯 修正後** | 球ロフト円形 修正前 | **球ロフト円形 修正後** | 実機 全灯 | +| --- | --- | --- | --- | --- | --- | +| CoV | 0.1717 | **0.0610** | 0.1205 | **0.0628** | 0.0695 | +| peak/mean (ROI) | 1.491 | **1.305** | 1.393 | **1.195** | 1.155 | +| top/bot (ROI) | 0.878 | **0.962** | 1.020 | **1.007** | 0.919 | +| C/P ratio | 1.328 | **1.073** | — | **1.163** | 1.091 | +| gradient [%] | 24.72 | **6.83** | — | **14.01** | 8.35 | +| angular_ratio | 0.352 | **0.341** | 0.110 | **0.005** | — | +| radial min/max | 0.638 | **0.888** | — | **0.787** | 0.860 | +| NCC 対実機 | 0.744 | **0.616** | 0.445 | **0.436** | 1.0 | +| mean/max・min/max | 0.671・0.241 | **0.766・0.549** | 0.718・0.502 | **0.837・0.671** | 0.899・0.660 | + +図: `output/_slide_panels/sphere_fix_{shape_roi,relative}.png`(修正後のみの 1×3 比較). +出力: `output/full_base_fix/`・`output/sphere_r100_loft_disk_fix/`. + +## 読み取れること (Findings) + +1. **アーティファクトはシミュの不均一性の支配的成分だった**.現行筐体全灯の CoV 0.1717 のうち + 実に 6 割超がシェーディング法線起因で,修正後は **0.0610 と実機の 0.0695 を下回る**. + Step 8-13 以降「実機との残差 ≈2.4×」としてきたものの大半は物理モデルの誤差ではなく + 本アーティファクトだった. +2. **現行筐体シミュは修正後,実機と数値レベルでほぼ整合する**: 勾配 6.83% vs 実機 8.35%, + C/P 1.073 vs 1.091,radial min/max 0.888 vs 0.860.長年の「Y 軸単調勾配」の見かけの強さも + 大幅に減った(top/bot 0.878 → 0.962,実機 0.919). +3. **球体筐体の優位性は消滅**: 修正後は現行筐体 0.0610 vs 球ロフト 0.0628 でほぼ同等= + **現行筐体はすでに理想積分球に近い混合性能を持っている**.TEST_11 の「幾何レバーで + CoV -30%」という差分もほぼ全てアーティファクトの差だった. +4. 球ロフトの angular_ratio は 0.005(完全な同心円)となり,軸対称セットアップの + 検証としても閉じた. +5. NCC 対実機は 0.744 → 0.616 と低下しており,**残る差分は「形」**(実機は右下明・左上暗の + 斜め構造,シミュは上端暗)に移った.振幅系の指標が揃った今,形の残差が次の主題になる. + +## 過去結論への影響 (Impact on Past Conclusions) + +本アーティファクトは評価板メッシュを使う**全過去レンダ**(Step 8 キャリブ履歴・TEST_09/10 の全実験) +に共通して乗っている.取り扱い: + +- **絶対値・形の結論は要再検証**: 「残差 2.63×/2.4×」「Y 軸単調勾配」「単灯 peak/mean 1.5〜1.7」 + 等の絶対量はアーティファクト込みの数値であり,修正後の再測定で再基準化が必要. +- **相対比較の結論は多くが生き残る見込み**: 比較対象同士が同じアーティファクトを共有するため, + 「ロフト重みに不感(TEST_10)」「材料・光源レバーの序列(TEST_09)」等の相対結論は + 方向性としては保持されると考えられる.ただし灯間 NCC 等の形ベース指標は感度が変わりうるため, + 結論を引用する際は「修正前データに基づく」ことを明記する. +- `output/` の修正前出力は歴史的記録として保持([TEST_02](TEST_02_シミュレーション実行記録.md) + 冒頭の BEFORE FIX 注記を参照). + +## 追補 — 単灯相対結論の再検証 (Addendum: Single-Light Re-verification) + +TEST_09 実験5 の中核結論「シミュは 3 灯がほぼ同形(灯間 NCC 0.95x)=実機の非対称相補性 +(0.85/0.61/0.41)を原理的に再現できない → 残差の唯一の候補は B1 非対称幾何」を, +基準単灯 3 灯(blend w0.05_a0.1・d=16・spp=1024,`configs/_tmp_fix_{bottom,left,right}.yaml`)の +修正後再レンダで再検証した(2026-08-18.ロフト w1p4 系はユーザー判断でスキップ). + +### 灯間 NCC(SIM_ROI・mean 正規化) + +raw マップの NCC はノイズで希釈される(無相関ノイズが相関を 0 方向へ引っ張る.単灯 +spp=1024 はノイズ 5.3% > 構造振幅 3.8〜4.4% でノイズ支配)ため, +平滑化(median 5px + gaussian σ=4)後の構造のみの NCC を正とする: + +| セット | bottom-left | bottom-right | left-right | +| --- | --- | --- | --- | +| 修正前(平滑) | 0.987 | 0.992 | 0.986 | +| **修正後(平滑)** | **0.854** | **0.934** | **0.727** | +| 実機(平滑) | 0.917 | 0.662 | 0.440 | +| (参考)raw: 修正前 / 修正後 / 実機 | 0.888 / 0.363 / 0.853 | 0.891 / 0.392 / 0.606 | 0.869 / 0.329 / 0.414 | + +### 単灯の形(summary.csv 抜粋) + +| 灯 | peak/mean(修正前→修正後) | top/bot(修正前→修正後) | 実機 | +| --- | --- | --- | --- | +| bottom | 1.672 → 1.648 | 0.872 → 0.968 | p/m 1.218・t/b 0.931 | +| left | 1.613 → 1.473 | 0.875 → 0.956 | p/m 1.267・t/b 0.922 | +| right | 1.546 → 1.521 | 0.890 → 0.963 | p/m 1.201・t/b 0.946 | + +※ 修正後の単灯マップはほぼ平坦で raw の peak/mean はノイズスパイク支配.以後の単灯実験は +spp 増強(≥4096)または平滑化後指標を使うこと. + +### 判定 + +1. **「3 灯ほぼ同形」は撤回**: 修正前の高相関(0.99)は全マップ共通のアーティファクト構造が + 作っていた見かけだった.修正後のシミュは灯ごとに明確に異なるフットプリントを持つ. +2. **非対称相補性は部分的に再現される**: 3 ペアの NCC は 0.73〜0.93 と非均等で, + 「left-right が最も異なる」順序は実機(0.44 が最小)と一致する.ただし差の振幅は + 実機(0.92→0.44)より弱く(0.93→0.73),最類似ペアも実機 b-l vs シミュ b-r で異なる. +3. **単灯の平らさ残差は縮小して残る**: top/bot は 0.96 前後で実機(0.92〜0.95)とほぼ整合. + peak/mean の差は残るが raw 指標がノイズ支配のため要再測定(高 spp). +4. **含意**: TEST_09 の「等方な変更では 3 ペアを一様にしか動かせない → 非対称幾何(漏れ・開口) + の検証が必須」という実験4 の主動機は,アーティファクト起因として大幅に弱まった. + 残る差分(相補性の振幅・最類似ペアの並び・実機の斜め構造)の切り分けを優先課題として + 再設定するのが妥当. + +成果物: `output/singles_fix/{bottom,left,right}/`・`output/singles_fix/{summary,pairwise_ncc}.csv` +(raw / smooth 両方を記録,解釈は smooth を正とする)・ +図 `output/_slide_panels/singles_fix_shape_roi.png`・集計 `scripts/build_singles_fix_summary.py`. + +### ロフト w1.4 の再検証(TEST_10 相対結論) + +続けてロフト w=1.4(`後側_w1p4.stl`)を修正版で再レンダ(全灯 1 + 単灯 3, +`configs/_tmp_fix_{full,bottom,left,right}_w1p4.yaml` → `output/full_w1p4_fix/`・ +`output/singles_fix/{bottom,left,right}_w1p4/`,2026-08-18). + +全灯(`eval_baseline_dng_target_shape.yaml`): + +| 指標 | 基準 fix | w1p4 fix | 差 | +| --- | --- | --- | --- | +| CoV | 0.06103 | 0.06051 | -0.9% | +| gradient [%] | 6.83 | 6.75 | -1.2% | +| C/P | 1.0733 | 1.0723 | -0.1% | +| angular_ratio | 0.3413 | 0.3332 | -2.4% | + +単灯(平滑後 peak/mean)と灯間 NCC(平滑): + +| | bottom | left | right | | b-l | b-r | l-r | +| --- | --- | --- | --- | --- | --- | --- | --- | +| 基準 fix | 1.0692 | 1.0745 | 1.0732 | 基準 fix | 0.854 | 0.934 | 0.727 | +| w1p4 fix | 1.0600 | 1.0750 | 1.0676 | w1p4 fix | 0.909 | 0.958 | 0.844 | +| 実機 | 1.1224 | 1.1567 | 1.1122 | 実機 | 0.917 | 0.662 | 0.440 | + +TEST_10 の 4 結論の判定: + +1. **④「ロフトは全灯に実質不感」— 維持**(全指標差 ≈1% でノイズ同等).実機 w1.4 測定 + `MiniTIAS_QM_20260726_183416` への予測「従来筐体とほぼ同じ」も維持される. +2. **③「対称なキャビティ拡大は 3 灯を同形化する方向」— 維持・明確化**: 灯間 NCC は + w1p4 で +0.05〜+0.12 上昇(0.85/0.93/0.73 → 0.91/0.96/0.84)=実機の非対称 + (0.92/0.66/0.44)から遠ざかる.NCC 対実機も 3 灯とも微減で実機方向に近づかない(②と整合). +3. **①「bottom 単灯の明確な平ら化=幾何レバー初実証」— 大幅に弱まる**: 平滑後 peak/mean の + 変化は bottom -0.9%・right -0.5%・left ±0 で微小.TEST_10 の raw -10%(1.672→1.508)は + アーティファクト期の見かけが大半だった(修正後 raw の -12% はヘビーテイルノイズの + 最大値ゆらぎで信頼できない.smooth を正とする). +4. **②「left/right 不動」— 維持**. + +総括: ロフト重み変形は修正後データでは「全灯・単灯とも実質不感で,むしろ 3 灯を同形化する +(実機の非対称からは遠ざかる)小さな混合強化」と再解釈される.TEST_10 の相対結論のうち +不感性(③④)は生き残り,幾何レバーの実証(①)は撤回相当に弱まった. + +## 再現コマンド (Reproduction) + +```powershell +# 修正版レンダ(builder が face_normals=true を自動適用) +$env:DRJIT_LIBLLVM_PATH = "C:\Program Files\LLVM\bin\LLVM-C.dll" +uv run sm-render --scene configs/_tmp_fn_full_base.yaml --output output/full_base_fix/ +uv run sm-render --scene configs/scene_sphere_r100_loft_disk.yaml --output output/sphere_r100_loft_disk_fix/ +uv run sm-evaluate --image output/full_base_fix/luminance.npy --config configs/eval_baseline_dng_target_shape.yaml --output output/full_base_fix/ +uv run sm-evaluate --image output/sphere_r100_loft_disk_fix/luminance.npy --config configs/eval_baseline_dng_target_shape.yaml --output output/sphere_r100_loft_disk_fix/ +uv run python scripts/build_sphere_panel.py +``` + +## 参照 (References) + +- [TEST_11_球体筐体実験](TEST_11_球体筐体実験.md): 発見の起点となった実験(結論の改訂を含む) +- [TEST_08_面光源スケール軸バグの発見と修正](TEST_08_面光源スケール軸バグの発見と修正.md): 同種の系統誤差発見の前例(実測グラウンドトゥルースとの突合・消去法診断の有効性) +- [TEST_02_シミュレーション実行記録](TEST_02_シミュレーション実行記録.md): 再現コマンド diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 7ea94fc..6e9881e 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -72,3 +72,9 @@ - **left/right 拡張 (2026-07-14)**: 時間短縮のため縮小グリッド w∈{0.05,0.3}×α∈{0.1,0.6}(+w0.6_a0.1)を left/right 単灯でも実走(8 ジョブ).**「w 単調悪化」は 3 灯共通**(w0.6_a0.1 で left 2.03/right 2.20 まで尖る).3 灯揃う 5 材料条件の灯間 NCC は w に対し一様に低下(0.96→0.92→0.86)するが**常に 3 ペア均等で実機の非対称(0.85/0.61/0.41)に近づかず**=「等方な材料変更は 3 ペアを一様にしか動かせない」をスイープ全域で確認.解析スクリプトは --side 引数で 3 灯対応化. - **TEST_10 後側ロフト変形 w=1.4 実験完了 — 幾何レバーが形を動かすことを初実証・対称変形では相補性は不動 (2026-07-26)**: 実験4 に先行し,ユーザーが Fusion で後側筐体のロフトパラメータを 0.5→1.4 に変えた `後側_w1p4.stl`(下側フレア部 Z=-150〜-86 が外側へ最大 10mm 膨張・体積 +6.3%,LED/評価板側は不変)をシミュで評価.TEST_09 パネルの見た目からユーザーが選定した blend w=0.05・α=0.1(形を壊さない最弱光沢で幾何の効きを残す意図)・spp=1024・d=16 で単灯 3+全灯 2(元メッシュ全灯 blend 基準含む)の 5 レンダ.**結果 ①**: bottom 単灯 peak/mean 1.672→1.508 と明確に平ら化=**筐体幾何が形を動かせることの初実証**(TEST_09 の光源側・材料側レバーはすべて不発だった).ただし left/right は不動で NCC 対実機は 3 灯とも微減.**結果 ②**: 灯間 NCC は 0.95x→0.96x と不動(むしろ同形化)=**対称なキャビティ拡大では実機の非対称相補性(0.85/0.61/0.41)に近づかない**.TEST_09 実験5 の「等方な材料変更では 3 ペアを一様にしか動かせない」が対称幾何変更にも当てはまると判明.**結果 ③**: 全灯 CoV 0.1717→0.1701 でほぼ不変=印刷リスクは低いが期待効果も小.**次への示唆**: 非対称相補性の再現には灯ごとに光路が変わる非対称幾何(開口・仕切り・カバー形状)が必要=実験4(幾何の漏れ点検)の優先度を再確認.失敗パターン: 全灯 config を `_tmp_loft_w1p4/` に置いたら scene_baseline.yaml 由来の相対メッシュパスが config 起点解決で file not found→絶対パス化で解決.成果物: `output/loft_w1p4/`(summary.csv 含む),図 `output/_slide_panels/loft_w1p4_shape_roi.png`,記録 [TEST_10](06_TEST/TEST_10_後側ロフト変形w1p4実験.md). - **TEST_10 拡張 — 全灯ロフト重みスイープで「ロフトは全灯に実質不感」を確定 (2026-07-28)**: ユーザー作成の `後側_{w2p0,w4p0}.stl`(w4.0 は体積 +66%・元外形を Z に 43mm はみ出しスマホ面と交差)を追加し,全灯のみ同条件(blend w0.05_a0.1・spp=1024・d=16)で w ∈ {0.5, 1.4, 2.0, 4.0} をスイープ.CoV 0.170〜0.177・NCC 対実機 0.737〜0.747 で全指標横ばい(最大の w4.0 のみ微悪化)=**ロフト(フレア部の対称な膨らみ)は全灯の均一性・形に対して実質不感なパラメータ**.単灯 bottom で見えた平ら化も全灯合成では消える.副次: w4.0 は絶対照度が -29%(キャビティ拡大で光が薄まる).また実機全灯マップを `MiniTIAS_QM_20260601_124500.dng` から単灯と同一手順で npy 化(CoV=0.0695 で Step 8-13 記録値と一致検証済み,`lightsim/luminance_maps/full_*.npy`)し,4×3 形パネルの full 行を実機込みに拡充.均一性の絶対比較用に各セル max=1.0 の相対輝度パネル(シミュ mean/max 0.64〜0.67 vs 実機 0.899)も追加.解析・パネル生成は `scripts/build_loft_w1p4_panel.py` に集約.実機側: ロフト w=1.4 版を印刷・組込みし初の実機測定 `MiniTIAS_QM_20260726_183416.dng`(全灯・定量モード)を取得済み=SmTIAS-Evaluation での分析待ち(シミュ予測「従来筐体とほぼ同じ」の検証ポイント).記録 [TEST_10 §拡張](06_TEST/TEST_10_後側ロフト変形w1p4実験.md). +- **TEST_11 球体筐体実験完了 — 幾何の理想極限(完全閉球・積分球)でも実機の均一性に届かないことを上限実証 (2026-08-17)**: ユーザー発案「筐体を球体に変えたらどうなるか」を,実機構成にとらわれない理想化実験として設計(評価板のみ現行同位置で共通,他メッシュ全撤去,`scripts/generate_sphere_housing.py` によるスクリプト生成の完全閉球殻 r=100mm,単灯を壁向き配置=評価板への直接光ゼロ,blend w0.05_a0.1・d=16).**結果 ①**: CoV 0.1717→**0.1350**(-21%)・mean/max 0.671→0.756 と改善するが,**実機(0.0695/0.899)には遠く及ばない**=漏れゼロ・完全対称・完全間接の理想積分球ですらこの水準で,筐体幾何の追い込みだけでは実機水準の均一性に到達できないことの上限実証.**結果 ②**: 形は Y 単調勾配→中心対称ドームへ質的変化(angular_ratio 0.352→0.126)だが NCC 対実機はむしろ低下(0.744→0.460)=実機の形は「理想混合の形」と構造的に別物で,実機の平坦さは ISP LSC 等の非幾何要因が本命という推定を補強.残る CoV 0.135 の物理源は R=0.85 の未混合一次照射成分(幾何では消せない).失敗パターン: ① 単灯を壁 5mm 手前に置く案 → path tracer の NEE がほぼ外れ spp=64 で相対ノイズ 89% と実用不能 → 壁 50mm 手前へ移動(直接光ゼロは維持)+spp=8192 で残留 0.79% に解決 / ② ptracer への切替案 → orthographic センサ非対応(sample_direction 未実装,Step 8-12 の知見と同根)で不成立.副次: 新マシンに環境を新規構築(uv・LLVM 22・`uv sync`)し,full_base 再評価で保存済み metrics と完全一致を確認=評価パイプラインのマシン間再現性を実証.成果物: `configs/scene_sphere_r100.yaml`,`output/sphere_r100/`,図 `output/_slide_panels/sphere_r100_{shape_roi,relative}.png`,解析 `scripts/build_sphere_panel.py`,記録 [TEST_11](06_TEST/TEST_11_球体筐体実験.md).次: 実験4(幾何の漏れ点検)は引き続き未着手で残存. +- **TEST_11 改訂 — 評価板嵌め込み・ロフト一体化版で幾何上限を更新(CoV 床 ≈0.12 を確定)(2026-08-18)**: ユーザー指摘「初版は板が球内に浮いて縁に隙間がある.すっぽり嵌めたい」を受け,① 球を板に貫入させる fit 版(四隅貫通・ROI 四隅に壁が写る難あり,3D 確認のみでレンダ未実施)→ ② Fusion のロフト相当の「板輪郭矩形→球断面円」遷移スカートをスクリプト生成し球お椀と一体化した版(`scripts/generate_sphere_loft_housing.py`,Hermite ブレンドで球側接線連続・板実体に食い込むリムでシール・watertight)へ 2 段階で改訂.結果: CoV 0.135→**0.120**(現行筐体比 -30%)・angular_ratio 0.110 と均一性/対称性は更に向上したが peak/mean は 1.32→1.39 と微増(閉じるほど中央集中がわずかに強まるトレードオフ).**結論は不変=幾何の理想極限でも実機 0.0695 に届かず,残差の本命は非幾何要因(ISP LSC 等)**.運用変更 2 点(ユーザー指示): (a) **幾何変更時はレンダ前に 3D 配置確認→OK 後にレンダ**を必須化(CLAUDE.md 必須ルール追記),(b) **spp は 8192→1024 に統一**(TEST_09/10 との条件統一優先.残留ノイズ 2.3%・CoV 汚染 +0.002 で許容).成果物: `configs/scene_sphere_r100_loft.yaml`,`configs/meshes/球ロフト_r100.stl`,`output/sphere_r100_loft/`,図 `output/_slide_panels/sphere_loft_{shape_roi,relative}.png`(1×4 パネル),記録 [TEST_11 §改訂](06_TEST/TEST_11_球体筐体実験.md). +- **TEST_11 追補 — 円形光源(disk)対応と完全軸対称化・発光面形状の不感を確認 (2026-08-18)**: ユーザー指示「光源を矩形でなく円形にして軸上に置きたい」に対応.スキーマの area 光源に `shape: disk`(radius 必須検証付き,rectangle は後方互換)を追加し `lights.py` を rectangle/disk 共通の半幅スケールに一般化(SPEC_02 更新・スケール実寸検証テスト追加・fast 898 件全パス).3D 確認ツール 2 種(sm-visualize マーカー・show_led_geometry)も disk 描画対応.同面積円板(r=1.7662mm=9.8mm² 保存)でレンダした結果,**全指標が矩形版と MC ノイズ内で完全一致**(CoV 0.12047 vs 0.12044)=完全間接キャビティでは発光面形状は不感(TEST_09「Lambertian 壁が光源形状を洗い流す」の再確認)で,矩形 LED の非軸対称性は残差要因でないことを確定.以後は円形光源版を球実験の正準構成とする.成果物: `configs/scene_sphere_r100_loft_disk.yaml`,`output/sphere_r100_loft_disk/`,1×5 比較パネル更新,記録 [TEST_11 §追補](06_TEST/TEST_11_球体筐体実験.md). +- **TEST_12 シェーディング法線アーティファクトの発見と修正 — Step 8 残差の支配成分がレンダリングバグと判明 (2026-08-18)**: 完全軸対称化した球実験の輝度マップが同心円にならない違和感(ユーザー指摘)を起点に消去法診断(円形/矩形プレート×浮き球/ロフト筐体の 4 診断=すべて対称,実物評価板のみ非対称,`face_normals=true` で完全対称化)でバグを特定.**原因**: builder の PLY 出力に法線が無く Mitsuba がスムーズ頂点法線を自動生成 → 評価板裏面は巨大三角形 26 枚・全頂点エッジ上で法線が約 47° 傾き,評価面全体が汚染補間される.**修正**: builder で `face_normals: true` を強制(1 行+再発防止テスト).**影響**: 現行筐体全灯 CoV 0.1717→**0.0610**(実機 0.0695 を下回る),勾配 24.7→6.8%(実機 8.35%),C/P 1.328→1.073(実機 1.091)=**Step 8-13 以降の「残差 2.4×」の大半と「Y 軸単調勾配」の見かけはこのアーティファクトだった**.球ロフトは CoV 0.0628・angular_ratio 0.005(完全同心円)で現行筐体とほぼ同等=**現行筐体はすでに理想積分球級の混合性能**(TEST_11 の「球で -30% 改善」も撤回).振幅系指標が実機と揃った今,残る差分は「形」(実機の斜め構造 vs シミュの上端暗,NCC 0.616)に移った.過去の絶対値・形の結論は要再検証,相対比較の結論(ロフト不感・レバー序列)は共有アーティファクトのため方向性は保持見込み(各文書の BEFORE FIX 注記参照).検証手順: 再構成 full_base を修正前コードで保存済み結果と一致確認(CoV 差 1e-4)→ 修正後再レンダで前後差を分離.成果物: `output/{full_base_fix,sphere_r100_loft_disk_fix}/`,図 `output/_slide_panels/sphere_fix_{shape_roi,relative}.png`,記録 [TEST_12](06_TEST/TEST_12_シェーディング法線アーティファクトの発見と修正.md). +- **TEST_12 追補 — 単灯再検証で TEST_09 中核結論「3 灯同形」を撤回・非対称相補性の部分再現を確認 (2026-08-18)**: 基準単灯 3 灯(blend w0.05_a0.1)を修正版で再レンダ(ロフト w1p4 系はユーザー判断でスキップ).灯間 NCC(平滑)は修正前 0.99 均等 → **修正後 0.85/0.93/0.73 の非対称**=「シミュは 3 灯ほぼ同形で非対称相補性を再現できない → B1 非対称幾何が唯一の候補」(TEST_09)はアーティファクトが作った見かけと判明し撤回(TEST_09 冒頭に BEFORE FIX 注記).修正後シミュは「left-right が最も異なる」点で実機(0.92/0.66/0.44)と一致するが,差の振幅は弱く最類似ペアは不一致=実験4(漏れ点検)の主動機は大幅に弱まり,残る課題は相補性の振幅・並びと実機の斜め構造の切り分けに再設定.計測上の学び: 単灯 spp=1024 はノイズ 5.3% > 構造振幅 4% で raw 指標(特に peak/mean・NCC)はノイズ支配 → 灯間 NCC は raw/smooth 併記とし解釈は smooth を正(`pairwise_ncc.csv`),以後の単灯実験は spp≥4096 推奨.成果物: `output/singles_fix/`,`scripts/build_singles_fix_summary.py`,図 `output/_slide_panels/singles_fix_shape_roi.png`,記録 [TEST_12 §追補](06_TEST/TEST_12_シェーディング法線アーティファクトの発見と修正.md). +- **TEST_12 追補 2 — ロフト w1.4 の修正後再検証で TEST_10 の判定を確定 (2026-08-18)**: ロフト w1.4(全灯 1+単灯 3)を修正版で再レンダ.**④「全灯に実質不感」は維持**(CoV 0.0610→0.0605,全指標差 ≈1%=実機 w1.4 測定への予測「従来筐体とほぼ同じ」も不変).**③「対称拡大は 3 灯を同形化」も維持・明確化**(灯間 NCC 平滑 0.85/0.93/0.73→0.91/0.96/0.84 で実機の非対称から遠ざかる).一方 **①「bottom 単灯の平ら化=幾何レバー初実証」は撤回相当に弱まる**(平滑 p/m 変化 -0.9% のみ.TEST_10 の raw -10% はアーティファクト期の見かけが大半.修正後 raw p/m はヘビーテイルノイズの最大値ゆらぎで不採用).②「left/right 不動」維持.TEST_10 冒頭に BEFORE FIX 注記(結論の生死を明記).成果物: `output/full_w1p4_fix/`・`output/singles_fix/*_w1p4/`,summary/pairwise CSV と 3×4 パネル更新,記録 [TEST_12 §ロフト再検証](06_TEST/TEST_12_シェーディング法線アーティファクトの発見と修正.md). diff --git a/scripts/build_full_vs_singles_panel.py b/scripts/build_full_vs_singles_panel.py new file mode 100644 index 0000000..7985bde --- /dev/null +++ b/scripts/build_full_vs_singles_panel.py @@ -0,0 +1,127 @@ +"""全灯と単灯 3 灯の形比較パネル生成(面法線修正後). + +行 = シミュ(面法線修正後)/ 実機,列 = 全灯 / bottom / left / right の 2×4 グリッド. +単灯シミュ(spp=1024)はノイズ 5.3% > 構造振幅 4% のため,全セルに同一の平滑化 +(median 5px + gaussian σ=4)を適用した「形の比較専用」表示とする. +各セルは mean 正規化+独立 1–99% 正規化,シミュセルには対応する実機マップとの +NCC(平滑後)を併記する. + +生成するファイル: + output/_slide_panels/full_vs_singles_fix.png + +使い方: + uv run python scripts/build_full_vs_singles_panel.py + +前提: + - output/full_base_fix/luminance.npy・output/singles_fix/{bottom,left,right}/luminance.npy + - lightsim/luminance_maps/{full,bottom,left,right}_*.npy(実機) +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +from scipy.ndimage import gaussian_filter, median_filter +from skimage.transform import resize + +from sm_tias_lightsim.evaluation.roi_presets import SIM_ROI, extract_roi_xywh +from sm_tias_lightsim.evaluation.shape_metrics import calc_ncc + +matplotlib.rcParams["font.family"] = ["Meiryo", "Yu Gothic", "DejaVu Sans"] + +ROOT = Path(__file__).resolve().parents[1] +PANEL_PATH = ROOT / "output" / "_slide_panels" / "full_vs_singles_fix.png" + +COLS = ["full", "bottom", "left", "right"] +COL_LABELS = { + "full": "全灯 18 灯", + "bottom": "bottom 単灯", + "left": "left 単灯", + "right": "right 単灯", +} +SIM = { + "full": ROOT / "output" / "full_base_fix" / "luminance.npy", + "bottom": ROOT / "output" / "singles_fix" / "bottom" / "luminance.npy", + "left": ROOT / "output" / "singles_fix" / "left" / "luminance.npy", + "right": ROOT / "output" / "singles_fix" / "right" / "luminance.npy", +} +REAL = { + "full": ROOT / "lightsim" / "luminance_maps" / "full_MiniTIAS_QM_20260601_124500.npy", + "bottom": ROOT / "lightsim" / "luminance_maps" / "bottom_SmTIAS_QM_20260615_141420.npy", + "left": ROOT / "lightsim" / "luminance_maps" / "left_SmTIAS_QM_20260615_150300.npy", + "right": ROOT / "lightsim" / "luminance_maps" / "right_SmTIAS_QM_20260615_170948.npy", +} + +CELL_SHAPE = (450, 464) + + +def _load_smooth(path: Path, is_sim: bool) -> np.ndarray: + """luminance を読み込み(シミュは SIM_ROI 抽出後)平滑化して返す.""" + arr = np.load(path) + if arr.ndim == 3: + arr = 0.2126 * arr[..., 0] + 0.7152 * arr[..., 1] + 0.0722 * arr[..., 2] + arr = arr.astype(np.float64) + if is_sim: + arr = extract_roi_xywh(arr, SIM_ROI) + return gaussian_filter(median_filter(arr, size=5), sigma=4) + + +def main() -> int: + """2×4 形比較パネルを生成するメインエントリポイント. + + Returns: + 終了コード(成功なら 0,入力不足なら 1). + """ + for paths in (SIM, REAL): + for p in paths.values(): + if not p.exists(): + print(f"エラー: {p} が見つかりません", file=sys.stderr) + return 1 + + sim = {c: _load_smooth(SIM[c], True) for c in COLS} + real = {c: _load_smooth(REAL[c], False) for c in COLS} + + fig, axes = plt.subplots(2, 4, figsize=(4 * 4.2, 2 * 4.4), constrained_layout=True) + for r, (row_label, maps) in enumerate((("シミュ(面法線修正後)", sim), ("実機", real))): + for c, col in enumerate(COLS): + ax = axes[r][c] + img = maps[col] + disp = resize(img / img.mean(), CELL_SHAPE, anti_aliasing=True) + ax.imshow( + disp, + cmap="hot", + vmin=float(np.percentile(disp, 1.0)), + vmax=float(np.percentile(disp, 99.0)), + aspect="equal", + ) + h = img.shape[0] + pm = img.max() / img.mean() + tb = img[: h // 2].mean() / img[h // 2 :].mean() + title = f"p/m={pm:.3f} t/b={tb:.3f}" + if r == 0: + ncc = calc_ncc(resize(img, real[col].shape, anti_aliasing=True), real[col]) + title = f"{COL_LABELS[col]}\n{title} NCC対実機={ncc:.3f}" + ax.set_title(title, fontsize=10, pad=6) + ax.set_axis_off() + axes[r][0].text( + -0.06, 0.5, row_label, ha="right", va="center", rotation=90, + transform=axes[r][0].transAxes, fontsize=12, fontweight="bold", + ) + fig.suptitle( + "全灯 vs 単灯 3 灯の形比較(面法線修正後)— blend w0.05_a0.1, spp=1024, d=16, ROI 揃え\n" + "全セル同一平滑化(median5+gauss σ4)・mean 正規化・独立 1–99% 表示(形の比較専用)", + fontsize=12, + ) + PANEL_PATH.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(PANEL_PATH, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"パネル保存: {PANEL_PATH}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/build_singles_fix_summary.py b/scripts/build_singles_fix_summary.py new file mode 100644 index 0000000..8c1f5a5 --- /dev/null +++ b/scripts/build_singles_fix_summary.py @@ -0,0 +1,219 @@ +"""TEST_12 相対結論再確認: 面法線修正後の単灯ジョブの形指標・灯間 NCC 集計. + +TEST_09/10 の相対結論を面法線修正後のデータで再検証する: + 1. 「シミュは 3 灯ほぼ同形(灯間 NCC 0.95x)=実機の非対称相補性を再現できない」 + 2. ロフトレバー(w1p4 で bottom のみ平ら化,left/right・灯間 NCC 不動) +単灯の形(peak/mean・top/bot・NCC 対実機)も修正前後で対比する. +raw 指標は MC ノイズ支配(単灯 spp=1024 はノイズ > 構造振幅)のため, +平滑化(median 5px + gaussian σ=4)後の値を併記し解釈は smooth を正とする. + +生成するファイル: + output/singles_fix/summary.csv -- 単灯形指標(修正前後・w1p4 対比付き) + output/singles_fix/pairwise_ncc.csv -- 灯間 NCC(raw / smooth) + output/_slide_panels/singles_fix_shape_roi.png -- 3 行(灯)× 4 列のパネル + +使い方: + uv run python scripts/build_singles_fix_summary.py + +前提: + - output/singles_fix/{bottom,left,right}{,_w1p4}/luminance.npy(面法線修正後) + - output/blend_bottom/{,left_,right_}w0.05_a0.1/luminance.npy(修正前,対比用) + - lightsim/luminance_maps/{bottom,left,right}_*.npy(実機単灯) +""" + +from __future__ import annotations + +import csv +import sys +from itertools import combinations +from pathlib import Path + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +from skimage.transform import resize + +from sm_tias_lightsim.evaluation.roi_presets import SIM_ROI, extract_roi_xywh +from sm_tias_lightsim.evaluation.shape_metrics import calc_ncc + +matplotlib.rcParams["font.family"] = ["Meiryo", "Yu Gothic", "DejaVu Sans"] + +ROOT = Path(__file__).resolve().parents[1] +SUMMARY_CSV = ROOT / "output" / "singles_fix" / "summary.csv" +PAIRWISE_CSV = ROOT / "output" / "singles_fix" / "pairwise_ncc.csv" +PANEL_PATH = ROOT / "output" / "_slide_panels" / "singles_fix_shape_roi.png" + +SIDES = ["bottom", "left", "right"] +REAL = { + "bottom": ROOT / "lightsim" / "luminance_maps" / "bottom_SmTIAS_QM_20260615_141420.npy", + "left": ROOT / "lightsim" / "luminance_maps" / "left_SmTIAS_QM_20260615_150300.npy", + "right": ROOT / "lightsim" / "luminance_maps" / "right_SmTIAS_QM_20260615_170948.npy", +} +OLD_BASE = { + "bottom": ROOT / "output" / "blend_bottom" / "w0.05_a0.1" / "luminance.npy", + "left": ROOT / "output" / "blend_bottom" / "left_w0.05_a0.1" / "luminance.npy", + "right": ROOT / "output" / "blend_bottom" / "right_w0.05_a0.1" / "luminance.npy", +} +FIX_BASE = {s: ROOT / "output" / "singles_fix" / s / "luminance.npy" for s in SIDES} +FIX_W1P4 = {s: ROOT / "output" / "singles_fix" / f"{s}_w1p4" / "luminance.npy" for s in SIDES} + +CELL_SHAPE = (450, 464) + + +def _load_luminance(path: Path) -> np.ndarray: + """luminance.npy を読み込み 2D 輝度にする(3D RGB は Rec.709 変換).""" + arr = np.load(path) + if arr.ndim == 3: + arr = 0.2126 * arr[..., 0] + 0.7152 * arr[..., 1] + 0.0722 * arr[..., 2] + return arr.astype(np.float64) + + +def _shape_metrics(img: np.ndarray, real: np.ndarray | None) -> dict[str, float]: + """TEST_09/10 共通の形指標(peak/mean・top/bot・NCC 対実機).""" + h = img.shape[0] + m = { + "peak_mean": float(img.max() / img.mean()), + "top_bot": float(img[: h // 2].mean() / img[h // 2 :].mean()), + } + if real is not None: + m["ncc"] = float(calc_ncc(resize(img, real.shape, anti_aliasing=True), real)) + else: + m["ncc"] = float("nan") + return m + + +def _pairwise_ncc(maps: dict[str, np.ndarray]) -> dict[str, float]: + """3 灯マップの灯間 NCC(mean 正規化・フル frame,TEST_10 の流儀).""" + out = {} + for a, b in combinations(SIDES, 2): + ma, mb = maps[a], maps[b] + if ma.shape != mb.shape: + mb = resize(mb, ma.shape, anti_aliasing=True) + out[f"{a}_vs_{b}"] = float(calc_ncc(ma / ma.mean(), mb / mb.mean())) + return out + + +def main() -> int: + """集計 CSV とパネルを生成するメインエントリポイント. + + Returns: + 終了コード(成功なら 0,入力不足なら 1). + """ + for paths in (FIX_BASE, FIX_W1P4, REAL, OLD_BASE): + for p in paths.values(): + if not p.exists(): + print(f"エラー: {p} が見つかりません", file=sys.stderr) + return 1 + + # raw の指標・NCC は MC ノイズ支配(単灯 spp=1024 はノイズ > 構造振幅)のため, + # 平滑化(median 5px + gaussian σ=4)後の値を併記する.解釈は smooth を正とする + from scipy.ndimage import gaussian_filter, median_filter + + def _smooth(m: np.ndarray) -> np.ndarray: + return gaussian_filter(median_filter(m, size=5), sigma=4) + + real = {s: _load_luminance(REAL[s]) for s in SIDES} + sets = { + "old": {s: extract_roi_xywh(_load_luminance(OLD_BASE[s]), SIM_ROI) for s in SIDES}, + "fix": {s: extract_roi_xywh(_load_luminance(FIX_BASE[s]), SIM_ROI) for s in SIDES}, + "fix_w1p4": {s: extract_roi_xywh(_load_luminance(FIX_W1P4[s]), SIM_ROI) for s in SIDES}, + } + + SUMMARY_CSV.parent.mkdir(parents=True, exist_ok=True) + with SUMMARY_CSV.open("w", encoding="utf-8", newline="") as f: + writer = csv.writer(f) + writer.writerow( + ["side", "version", "peak_mean", "peak_mean_smooth", "top_bot", "ncc_vs_real"] + ) + for s in SIDES: + for ver, maps in sets.items(): + m = _shape_metrics(maps[s], real[s]) + pm_s = _shape_metrics(_smooth(maps[s]), None)["peak_mean"] + writer.writerow( + [ + s, + ver, + f"{m['peak_mean']:.4f}", + f"{pm_s:.4f}", + f"{m['top_bot']:.4f}", + f"{m['ncc']:.4f}", + ] + ) + rm = _shape_metrics(real[s], real[s]) + rpm_s = _shape_metrics(_smooth(real[s]), None)["peak_mean"] + writer.writerow( + [ + s, + "real", + f"{rm['peak_mean']:.4f}", + f"{rpm_s:.4f}", + f"{rm['top_bot']:.4f}", + "1.0000", + ] + ) + print(f"summary 保存: {SUMMARY_CSV}") + + with PAIRWISE_CSV.open("w", encoding="utf-8", newline="") as f: + writer = csv.writer(f) + writer.writerow(["set", "bottom_vs_left", "bottom_vs_right", "left_vs_right"]) + for label, maps in [ + ("old", sets["old"]), + ("fix", sets["fix"]), + ("fix_w1p4", sets["fix_w1p4"]), + ("real", real), + ]: + smoothed = {s: _smooth(m) for s, m in maps.items()} + for suffix, mm in (("raw", maps), ("smooth", smoothed)): + ncc = _pairwise_ncc(mm) + writer.writerow( + [ + f"{label}_{suffix}", + f"{ncc['bottom_vs_left']:.4f}", + f"{ncc['bottom_vs_right']:.4f}", + f"{ncc['left_vs_right']:.4f}", + ] + ) + print(f"pairwise 保存: {PAIRWISE_CSV}") + + # パネル: 行=灯,列=修正前 / 修正後基準 / 修正後 w1p4 / 実機(各セル独立 1–99% 正規化) + cols = [ + ("修正前(スムーズ法線)", sets["old"]), + ("修正後(面法線)基準", sets["fix"]), + ("修正後(面法線)w1p4", sets["fix_w1p4"]), + ] + fig, axes = plt.subplots(3, 4, figsize=(4 * 4.2, 3 * 4.2), constrained_layout=True) + for r, s in enumerate(SIDES): + cells = [maps[s] for _, maps in cols] + [real[s]] + titles = [label for label, _ in cols] + ["実機"] + for c, (img, title) in enumerate(zip(cells, titles, strict=True)): + ax = axes[r][c] + disp = resize(img / img.mean(), CELL_SHAPE, anti_aliasing=True) + ax.imshow( + disp, + cmap="hot", + vmin=float(np.percentile(disp, 1.0)), + vmax=float(np.percentile(disp, 99.0)), + aspect="equal", + ) + m = _shape_metrics(img, None) + t = f"p/m={m['peak_mean']:.3f} t/b={m['top_bot']:.3f}" + ax.set_title(f"{title}\n{t}" if r == 0 else t, fontsize=10, pad=6) + ax.set_axis_off() + axes[r][0].text( + -0.06, 0.5, s, ha="right", va="center", rotation=90, + transform=axes[r][0].transAxes, fontsize=13, fontweight="bold", + ) + fig.suptitle( + "TEST_12 相対結論再確認 — 単灯 blend w0.05_a0.1, spp=1024, d=16, ROI 揃え\n" + "各セル独立 1–99% 正規化(形の比較専用)・全セル共通解像度", + fontsize=12, + ) + PANEL_PATH.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(PANEL_PATH, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"パネル保存: {PANEL_PATH}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/build_sphere_panel.py b/scripts/build_sphere_panel.py new file mode 100644 index 0000000..eb5388a --- /dev/null +++ b/scripts/build_sphere_panel.py @@ -0,0 +1,186 @@ +"""TEST_11/12 球体筐体実験(面法線修正後)の形比較パネル生成. + +セル = 現行筐体全灯(面法線修正)/ 球ロフト円形光源(面法線修正)/ 実機全灯 の 1×3. +TEST_10 と同じ流儀: シミュは SIM_ROI 抽出,実機はフル解像度のまま, +全セルを共通解像度へ resize して比較する. +TEST_12 のシェーディング法線バグ修正前の出力(output/sphere_r100{,_loft,_loft_disk} 等)は +法線条件が異なるため本パネルには混ぜない. + +生成するファイル: + output/sphere_r100_loft_disk_fix/summary.csv -- 形指標(peak/mean・top/bot・CoV・NCC) + output/_slide_panels/sphere_fix_shape_roi.png -- 1×3 形比較パネル(独立 1–99% 正規化) + output/_slide_panels/sphere_fix_relative.png -- 相対輝度(max=1.0・共通スケール) + +使い方: + uv run python scripts/build_sphere_panel.py + +前提: + - output/full_base_fix/luminance.npy(現行筐体全灯,configs/_tmp_fn_full_base.yaml) + - output/sphere_r100_loft_disk_fix/luminance.npy(球ロフト円形光源) + - lightsim/luminance_maps/full_MiniTIAS_QM_20260601_124500.npy(実機全灯) +""" + +from __future__ import annotations + +import csv +import sys +from pathlib import Path + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +from skimage.transform import resize + +from sm_tias_lightsim.evaluation.roi_presets import SIM_ROI, extract_roi_xywh +from sm_tias_lightsim.evaluation.shape_metrics import calc_ncc + +matplotlib.rcParams["font.family"] = ["Meiryo", "Yu Gothic", "DejaVu Sans"] + +ROOT = Path(__file__).resolve().parents[1] +SUMMARY_CSV = ROOT / "output" / "sphere_r100_loft_disk_fix" / "summary.csv" +SHAPE_PANEL_PATH = ROOT / "output" / "_slide_panels" / "sphere_fix_shape_roi.png" +REL_PANEL_PATH = ROOT / "output" / "_slide_panels" / "sphere_fix_relative.png" + +# (ラベル, luminance.npy パス, シミュか否か).シミュのみ SIM_ROI を抽出する +# TEST_12 面法線修正後の出力のみで比較する(修正前の出力とはシェーディング法線が +# 異なり混在比較は不可.修正前の記録は TEST_11 の表を参照) +CELLS = [ + ( + "現行筐体 全灯 18 灯(面法線修正)", + ROOT / "output" / "full_base_fix" / "luminance.npy", + True, + ), + ( + "球ロフト 円形光源(面法線修正)", + ROOT / "output" / "sphere_r100_loft_disk_fix" / "luminance.npy", + True, + ), + ( + "実機 全灯 18 灯", + ROOT / "lightsim" / "luminance_maps" / "full_MiniTIAS_QM_20260601_124500.npy", + False, + ), +] + +CELL_SHAPE = (450, 464) +VMIN_PERCENTILE = 1.0 +VMAX_PERCENTILE = 99.0 + + +def _load_luminance(path: Path) -> np.ndarray: + """luminance.npy を読み込み 2D 輝度にする(3D RGB は Rec.709 変換).""" + arr = np.load(path) + if arr.ndim == 3: + arr = 0.2126 * arr[..., 0] + 0.7152 * arr[..., 1] + 0.0722 * arr[..., 2] + return arr.astype(np.float64) + + +def _metrics(img: np.ndarray, real: np.ndarray | None) -> dict[str, float]: + """TEST_09/10/11 共通の形指標(peak/mean・top/bot・CoV・NCC 対実機)を算出する.""" + h = img.shape[0] + m = { + "peak_mean": float(img.max() / img.mean()), + "top_bot": float(img[: h // 2].mean() / img[h // 2 :].mean()), + "cov": float(img.std() / img.mean()), + } + if real is not None: + m["ncc"] = float(calc_ncc(resize(img, real.shape, anti_aliasing=True), real)) + else: + m["ncc"] = float("nan") + return m + + +def main() -> int: + """パネルと summary CSV を生成するメインエントリポイント. + + Returns: + 終了コード(成功なら 0,入力不足なら 1). + """ + real = _load_luminance(CELLS[-1][1]) + loaded: list[tuple[str, np.ndarray, dict[str, float]]] = [] + for label, path, is_sim in CELLS: + if not path.exists(): + print(f"エラー: {path} が見つかりません", file=sys.stderr) + return 1 + img = _load_luminance(path) + if is_sim: + img = extract_roi_xywh(img, SIM_ROI) + m = _metrics(img, real if is_sim else None) + if not is_sim: + m["ncc"] = 1.0 + loaded.append((label, img, m)) + + SUMMARY_CSV.parent.mkdir(parents=True, exist_ok=True) + with SUMMARY_CSV.open("w", encoding="utf-8", newline="") as f: + writer = csv.writer(f) + writer.writerow(["label", "peak_mean", "top_bot", "cov_roi", "ncc_vs_real"]) + for label, _, m in loaded: + writer.writerow( + [ + label, + f"{m['peak_mean']:.4f}", + f"{m['top_bot']:.4f}", + f"{m['cov']:.4f}", + f"{m['ncc']:.4f}", + ] + ) + print(f"summary 保存: {SUMMARY_CSV}") + + # 形比較パネル(各セル独立 1–99% 正規化) + n = len(loaded) + fig, axes = plt.subplots(1, n, figsize=(n * 4.2, 4.6), constrained_layout=True) + for ax, (label, img, m) in zip(axes, loaded, strict=True): + disp = resize(img / img.mean(), CELL_SHAPE, anti_aliasing=True) + ax.imshow( + disp, + cmap="hot", + vmin=float(np.percentile(disp, VMIN_PERCENTILE)), + vmax=float(np.percentile(disp, VMAX_PERCENTILE)), + aspect="equal", + ) + ax.set_title( + f"{label}\np/m={m['peak_mean']:.3f} t/b={m['top_bot']:.3f} NCC={m['ncc']:.3f}", + fontsize=10, + pad=6, + ) + ax.set_axis_off() + fig.suptitle( + "TEST_11 球体筐体 — blend w0.05_a0.1, d=16, ROI 揃え\n" + "各セル独立 1–99% 正規化(形の比較専用)・全セル共通解像度", + fontsize=12, + ) + SHAPE_PANEL_PATH.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(SHAPE_PANEL_PATH, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"パネル保存: {SHAPE_PANEL_PATH}") + + # 相対輝度(各セル max=1.0,全セル共通スケール).実機も含めて絶対均一性を並べる. + # max は resize 後(MC ノイズ平滑化後)に取る(TEST_10 の流儀を踏襲) + rel_cells = [] + for label, img, _ in loaded: + disp = resize(img, CELL_SHAPE, anti_aliasing=True) + rel_cells.append((label, disp / disp.max())) + vmin = min(float(r.min()) for _, r in rel_cells) + fig2, axes2 = plt.subplots(1, n, figsize=(n * 4.4, 5.0), constrained_layout=True) + for ax, (label, rel) in zip(axes2, rel_cells, strict=True): + im = ax.imshow(rel, cmap="hot", vmin=vmin, vmax=1.0, aspect="equal") + ax.set_title( + f"{label}\nmean/max={rel.mean():.3f} min/max={rel.min():.3f}", + fontsize=10, + pad=6, + ) + ax.set_axis_off() + fig2.colorbar(im, ax=axes2, shrink=0.85, label="相対輝度(max=1.0)") + fig2.suptitle( + "TEST_11 球体筐体 — 相対輝度(各セル max=1.0,全セル共通スケール)\n" + "完全均一なら全面 1.0.mean/max・min/max が 1 に近いほど均一", + fontsize=12, + ) + fig2.savefig(REL_PANEL_PATH, dpi=150, bbox_inches="tight") + plt.close(fig2) + print(f"パネル保存: {REL_PANEL_PATH}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/generate_sphere_housing.py b/scripts/generate_sphere_housing.py new file mode 100644 index 0000000..d3f8b7d --- /dev/null +++ b/scripts/generate_sphere_housing.py @@ -0,0 +1,123 @@ +# 球体筐体実験(TEST_11)用の球殻メッシュ生成スクリプト. +# +# 現行筐体(前側/後側)を理想化した「完全閉球」の白色キャビティに置き換え, +# 幾何レバーの理想極限(漏れゼロの積分球)で照明均一性の上限を測る実験に使う. +# 評価板(照明ムラ補正ボード)を内包できる中心・半径を引数で指定する. +"""球殻(integrating sphere 状の閉じた白キャビティ)STL を生成する. + +内半径 r の icosphere(法線を内向きに反転)と外半径 r+t の icosphere を +連結した watertight な厚み付き殻を作り,バイナリ STL として出力する. +単位は mm(プロジェクトのシーン YAML 規約に合わせる). + +使用例: + uv run python scripts/generate_sphere_housing.py \ + --radius 100 --thickness 3 --center 0 0 -40 \ + --output configs/meshes/球殻_r100.stl +""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +import numpy as np +import trimesh + + +def build_sphere_shell( + radius: float, + thickness: float, + center: tuple[float, float, float], + subdivisions: int, +) -> trimesh.Trimesh: + """厚み付き球殻メッシュを生成する. + + 内面(半径 radius)は法線をキャビティ側(中心向き)に反転させ, + 外面(半径 radius + thickness)は外向きのまま連結する. + 2 つの閉曲面から成るため全体として watertight な実体殻になる. + + Args: + radius: 内半径(mm).キャビティの半径. + thickness: 殻厚(mm). + center: 球中心座標 (x, y, z)(mm). + subdivisions: icosphere の分割回数.6 で辺長 ≈ radius * 0.03 程度. + + Returns: + 球殻メッシュ(mm 単位). + + Raises: + ValueError: radius または thickness が正でない場合. + """ + if radius <= 0: + raise ValueError(f"内半径は正の値を指定してください: {radius}") + if thickness <= 0: + raise ValueError(f"殻厚は正の値を指定してください: {thickness}") + + inner = trimesh.creation.icosphere(subdivisions=subdivisions, radius=radius) + # 内面の法線は実体(殻)から見て外側=キャビティ中心向きが正しいため反転する + inner.invert() + outer = trimesh.creation.icosphere(subdivisions=subdivisions, radius=radius + thickness) + + shell = trimesh.util.concatenate([outer, inner]) + shell.apply_translation(np.asarray(center, dtype=np.float64)) + return shell + + +def _parse_args(argv: list[str] | None = None) -> argparse.Namespace: + """コマンドライン引数をパースして返す.""" + parser = argparse.ArgumentParser( + prog="generate_sphere_housing", + description="球体筐体実験用の閉じた球殻 STL(mm 単位)を生成する.", + ) + parser.add_argument("--radius", type=float, default=100.0, help="内半径(mm).既定 100") + parser.add_argument("--thickness", type=float, default=3.0, help="殻厚(mm).既定 3") + parser.add_argument( + "--center", + type=float, + nargs=3, + default=(0.0, 0.0, -40.0), + metavar=("X", "Y", "Z"), + help="球中心座標(mm).既定 0 0 -40(評価板を内包する位置)", + ) + parser.add_argument( + "--subdivisions", + type=int, + default=6, + help="icosphere 分割回数.既定 6(面数 81920 x 2,辺長 ≈ 3mm)", + ) + parser.add_argument( + "--output", + type=Path, + default=Path("configs/meshes/球殻_r100.stl"), + help="出力 STL パス.既定 configs/meshes/球殻_r100.stl", + ) + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + """球殻を生成して STL 出力し,検証情報を表示する.""" + args = _parse_args(argv) + + shell = build_sphere_shell( + radius=args.radius, + thickness=args.thickness, + center=tuple(args.center), + subdivisions=args.subdivisions, + ) + + args.output.parent.mkdir(parents=True, exist_ok=True) + shell.export(args.output) + + # 期待体積 = (4/3)π((r+t)^3 - r^3).メッシュ体積との一致で内面反転の正しさを確認できる + expected_volume = ( + 4.0 / 3.0 * np.pi * ((args.radius + args.thickness) ** 3 - args.radius**3) + ) + print(f"出力: {args.output}") + print(f"faces: {len(shell.faces)}, watertight: {shell.is_watertight}") + print(f"bounds (mm): {shell.bounds.round(2).tolist()}") + print(f"体積 (mm^3): {shell.volume:.0f}(理論値 {expected_volume:.0f})") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/generate_sphere_loft_housing.py b/scripts/generate_sphere_loft_housing.py new file mode 100644 index 0000000..6d17653 --- /dev/null +++ b/scripts/generate_sphere_loft_housing.py @@ -0,0 +1,247 @@ +# TEST_11 改訂(球体筐体実験)用: 評価板の矩形輪郭と球を一体化した筐体メッシュ生成. +# +# 球単体では矩形の評価板と断面円の形が合わず,四隅の貫通か縁の隙間のどちらかが生じる. +# Fusion 360 のロフトに相当する「矩形 → 円」の遷移スカートをスクリプトで生成し, +# 球のお椀部分と 1 つの watertight ソリッドに縫合することで,評価板がフタとして +# 隙間なく嵌まる筐体を作る. +"""球+ロフト一体型筐体(評価板輪郭に整合する閉キャビティ)STL を生成する. + +構成(上から下へ,単位 mm): + 1. リム矩形: 評価板輪郭から rim_inset だけ内側の矩形.z=rim_z(板の実体に + 食い込む高さ)に置き,板とのオーバーラップで隙間なくシールする + 2. ロフト部: 矩形 → 球断面円(z=cut_z)への極座標 Hermite ブレンド. + 球側の端点勾配を球面の接線に一致させ滑らかに接続する + 3. 球部: 半径 radius・中心 (0,0,center_z) の球の z ≤ cut_z 部分(お椀) + +内面と外面(thickness だけオフセット)を生成し,リム帯で縫合した閉ソリッドを出力する. + +使用例: + uv run python scripts/generate_sphere_loft_housing.py \ + --output configs/meshes/球ロフト_r100.stl +""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +import numpy as np +import trimesh + + +def _rect_radius(theta: np.ndarray, half_x: float, half_y: float) -> np.ndarray: + """角度 theta ごとの矩形境界までの半径(極座標表現)を返す.""" + cos_t = np.abs(np.cos(theta)) + sin_t = np.abs(np.sin(theta)) + with np.errstate(divide="ignore"): + rx = np.where(cos_t > 1e-12, half_x / cos_t, np.inf) + ry = np.where(sin_t > 1e-12, half_y / sin_t, np.inf) + return np.minimum(rx, ry) + + +def _build_surface( + radius: float, + center_z: float, + half_x: float, + half_y: float, + rim_z: float, + cut_z: float, + n_theta: int, + n_loft: int, + n_sphere: int, +) -> tuple[np.ndarray, np.ndarray]: + """リム矩形 → ロフト → 球お椀の 1 枚曲面(頂点とリング構造)を生成する. + + Returns: + (vertices, rings): vertices は (N, 3),rings は (n_ring, n_theta) の頂点 index. + リングは上(リム)から下(極直前)の順.末尾に極頂点を 1 つ追加している. + """ + theta = np.linspace(0.0, 2.0 * np.pi, n_theta, endpoint=False) + rect_r = _rect_radius(theta, half_x, half_y) + + cut_d = cut_z - center_z + cut_r = float(np.sqrt(radius**2 - cut_d**2)) + # 球面の cut_z における dρ/dz(下向きに進むと ρ が増える側)を Hermite 端点勾配に使う + drho_dz = -cut_d / cut_r + # u: 0(リム)→ 1(カット面).dz/du = cut_z - rim_z + m1 = drho_dz * (cut_z - rim_z) + + rings_list: list[np.ndarray] = [] + z_list: list[np.ndarray] = [] + + # ロフト部(リム u=0 を含み,カット面 u=1 は球部の先頭リングに任せる) + for u in np.linspace(0.0, 1.0, n_loft, endpoint=False): + h00 = 2 * u**3 - 3 * u**2 + 1 + h01 = -2 * u**3 + 3 * u**2 + h11 = u**3 - u**2 + rho = h00 * rect_r + h01 * cut_r + h11 * m1 + rings_list.append(rho) + z_list.append(np.full(n_theta, rim_z + u * (cut_z - rim_z))) + + # 球部(カット面 → 極直前).角度パラメータで均等分割 + phi_cut = float(np.arccos(cut_d / radius)) + for phi in np.linspace(phi_cut, np.pi, n_sphere, endpoint=False): + rings_list.append(np.full(n_theta, radius * np.sin(phi))) + z_list.append(np.full(n_theta, center_z + radius * np.cos(phi))) + + n_ring = len(rings_list) + vertices = np.empty((n_ring * n_theta + 1, 3), dtype=np.float64) + rings = np.arange(n_ring * n_theta).reshape(n_ring, n_theta) + for i, (rho, z) in enumerate(zip(rings_list, z_list, strict=True)): + vertices[rings[i], 0] = rho * np.cos(theta) + vertices[rings[i], 1] = rho * np.sin(theta) + vertices[rings[i], 2] = z + vertices[-1] = (0.0, 0.0, center_z - radius) # 極頂点 + return vertices, rings + + +def _stitch_rings(rings: np.ndarray, pole_index: int) -> np.ndarray: + """隣接リング間を四角形 2 分割で三角形化し,最下リングは極頂点へファン接続する.""" + n_ring, n_theta = rings.shape + faces: list[tuple[int, int, int]] = [] + for i in range(n_ring - 1): + a, b = rings[i], rings[i + 1] + for j in range(n_theta): + k = (j + 1) % n_theta + faces.append((a[j], b[j], b[k])) + faces.append((a[j], b[k], a[k])) + bottom = rings[-1] + for j in range(n_theta): + k = (j + 1) % n_theta + faces.append((bottom[j], pole_index, bottom[k])) + return np.asarray(faces, dtype=np.int64) + + +def build_loft_housing( + radius: float, + thickness: float, + center_z: float, + board_half_x: float, + board_half_y: float, + rim_inset: float, + rim_z: float, + cut_z: float, + n_theta: int = 256, + n_loft: int = 48, + n_sphere: int = 160, +) -> trimesh.Trimesh: + """球+ロフト一体型筐体の閉ソリッド(内面+外面+リム帯)を生成する. + + Args: + radius: 球内半径(mm). + thickness: 殻厚(mm).外面は内面を径方向におおむね thickness オフセットした形. + center_z: 球中心の Z 座標(mm).X, Y は 0 固定. + board_half_x: 評価板輪郭の X 半幅(mm). + board_half_y: 評価板輪郭の Y 半幅(mm). + rim_inset: リム矩形を板輪郭から内側へ寄せる量(mm).板とのオーバーラップ代. + rim_z: リム矩形の Z 座標(mm).板の実体に食い込む高さに置いてシールする. + cut_z: ロフトと球の接続平面の Z 座標(mm). + n_theta: 周方向分割数. + n_loft: ロフト部の軸方向リング数. + n_sphere: 球部の軸方向リング数. + + Returns: + watertight な筐体ソリッドメッシュ(mm 単位). + + Raises: + ValueError: 生成メッシュが watertight にならなかった場合. + """ + hx_in = board_half_x - rim_inset + hy_in = board_half_y - rim_inset + v_in, rings_in = _build_surface( + radius, center_z, hx_in, hy_in, rim_z, cut_z, n_theta, n_loft, n_sphere + ) + v_out, rings_out = _build_surface( + radius + thickness, + center_z, + hx_in + thickness, + hy_in + thickness, + rim_z, + cut_z, + n_theta, + n_loft, + n_sphere, + ) + + f_in = _stitch_rings(rings_in, len(v_in) - 1) + f_out = _stitch_rings(rings_out, len(v_out) - 1) + + offset = len(v_in) + vertices = np.vstack([v_in, v_out]) + # リム帯: 内面上端リングと外面上端リングを接続して閉じる + top_in, top_out = rings_in[0], rings_out[0] + offset + band: list[tuple[int, int, int]] = [] + for j in range(n_theta): + k = (j + 1) % n_theta + band.append((top_in[j], top_out[j], top_out[k])) + band.append((top_in[j], top_out[k], top_in[k])) + faces = np.vstack([f_in, f_out + offset, np.asarray(band, dtype=np.int64)]) + + mesh = trimesh.Trimesh(vertices=vertices, faces=faces, process=False) + mesh.fix_normals() # 閉ソリッドとして法線を外向きに統一(内面は自動的にキャビティ向き) + if not mesh.is_watertight: + raise ValueError("生成メッシュが watertight になりませんでした(縫合パラメータを確認)") + return mesh + + +def _parse_args(argv: list[str] | None = None) -> argparse.Namespace: + """コマンドライン引数をパースして返す.""" + parser = argparse.ArgumentParser( + prog="generate_sphere_loft_housing", + description="評価板輪郭と球を一体化した筐体 STL(mm 単位)を生成する.", + ) + parser.add_argument("--radius", type=float, default=100.0, help="球内半径(mm).既定 100") + parser.add_argument("--thickness", type=float, default=3.0, help="殻厚(mm).既定 3") + parser.add_argument( + "--center-z", type=float, default=-92.0, help="球中心の Z 座標(mm).既定 -92" + ) + parser.add_argument( + "--board-half", type=float, nargs=2, default=(49.5, 51.0), metavar=("HX", "HY"), + help="評価板輪郭の半幅 X Y(mm).既定 49.5 51(99×102mm 板)", + ) + parser.add_argument( + "--rim-inset", + type=float, + default=1.5, + help="リムを板輪郭から内側へ寄せる量(mm).既定 1.5", + ) + parser.add_argument( + "--rim-z", type=float, default=0.0, + help="リムの Z 座標(mm).既定 0(板裏面 -4.5 より 4.5mm 食い込みシール)", + ) + parser.add_argument( + "--cut-z", type=float, default=-40.0, help="ロフト⇄球の接続平面 Z(mm).既定 -40" + ) + parser.add_argument( + "--output", + type=Path, + default=Path("configs/meshes/球ロフト_r100.stl"), + help="出力 STL パス.既定 configs/meshes/球ロフト_r100.stl", + ) + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + """筐体を生成して STL 出力し,検証情報を表示する.""" + args = _parse_args(argv) + mesh = build_loft_housing( + radius=args.radius, + thickness=args.thickness, + center_z=args.center_z, + board_half_x=args.board_half[0], + board_half_y=args.board_half[1], + rim_inset=args.rim_inset, + rim_z=args.rim_z, + cut_z=args.cut_z, + ) + args.output.parent.mkdir(parents=True, exist_ok=True) + mesh.export(args.output) + print(f"出力: {args.output}") + print(f"faces: {len(mesh.faces)}, watertight: {mesh.is_watertight}") + print(f"bounds (mm): {mesh.bounds.round(2).tolist()}") + print(f"体積 (mm^3): {mesh.volume:.0f}(正値なら法線外向きで正常)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/show_led_geometry.py b/scripts/show_led_geometry.py index 278000b..d7bb2b6 100644 --- a/scripts/show_led_geometry.py +++ b/scripts/show_led_geometry.py @@ -36,7 +36,8 @@ def _led_polydata(light, *, buggy: bool) -> pv.PolyData: """LED の発光面 PolyData を返す. - fixed: light.size どおりの実サイズ長方形(修正後). + fixed: shape=rectangle は light.size どおりの実サイズ長方形, + shape=disk は light.radius の実サイズ円板. buggy: 修正前の変換が実際に作っていた形=幅 light.size[0] × 高さ 2000mm の縦ストリップ (全 LED が direction=(0,0,-1)/up=(0,1,0) のため local X→world X・local Y→world Y で Y 方向に伸びる).忠実に 4 隅から構成する. @@ -45,6 +46,14 @@ direction = np.array(light.direction, dtype=float) direction = direction / np.linalg.norm(direction) if not buggy: + if getattr(light, "shape", None) == "disk": + return pv.Disc( + center=center.tolist(), + inner=0.0, + outer=light.radius, + normal=direction.tolist(), + c_res=64, + ) return pv.Plane( center=center.tolist(), direction=direction.tolist(), @@ -159,10 +168,12 @@ scene_config = load_scene_yaml(args.scene) mode = "BUGGY (2m strip)" if args.buggy else "FIXED" - print( - f"[{mode}] LED 数: {len(scene_config.lights)} size(mm): {scene_config.lights[0].size} " - f"direction: {scene_config.lights[0].direction}" - ) + first = scene_config.lights[0] + if getattr(first, "shape", None) == "disk": + dims = f"radius(mm): {first.radius}" + else: + dims = f"size(mm): {first.size}" + print(f"[{mode}] LED 数: {len(scene_config.lights)} {dims} direction: {first.direction}") if args.screenshot: plotter = _build_plotter(scene_config, off_screen=True, buggy=args.buggy) diff --git a/src/sm_tias_lightsim/scene/builder.py b/src/sm_tias_lightsim/scene/builder.py index 259b26c..52d03e2 100644 --- a/src/sm_tias_lightsim/scene/builder.py +++ b/src/sm_tias_lightsim/scene/builder.py @@ -124,9 +124,15 @@ scaled = scale_vertices(loaded, target_unit="m") ply_path = tempdir / f"{mesh_cfg.id}.ply" _save_loaded_mesh_to_ply(scaled, ply_path) + # face_normals=True でシェーディングに面法線を使う(TEST_12 バグ修正). + # PLY に法線を書き出していないため,指定なしでは Mitsuba がスムーズ頂点法線を + # 自動生成する.機械部品 STL は平面+シャープエッジ構成で,エッジ頂点の平均化 + # 法線(約 45° 傾く)が巨大三角形の全面に補間され,評価板の輝度マップに + # 大規模な偽の異方性(CoV を倍増させる系統誤差)を生んでいた. scene_dict[f"mesh_{mesh_cfg.id}"] = { "type": "ply", "filename": str(ply_path), + "face_normals": True, "bsdf": {"type": "ref", "id": f"mat_{mesh_cfg.material}"}, } diff --git a/src/sm_tias_lightsim/scene/lights.py b/src/sm_tias_lightsim/scene/lights.py index d37d83b..e9c91bc 100644 --- a/src/sm_tias_lightsim/scene/lights.py +++ b/src/sm_tias_lightsim/scene/lights.py @@ -19,18 +19,20 @@ def _build_area_light_transform( center: tuple[float, float, float], - size: tuple[float, float], + half_extents: tuple[float, float], direction: tuple[float, float, float], position_scale: float, ) -> mi.ScalarTransform4f: - """AreaLight の中心・サイズ・方向から Mitsuba の to_world Transform を組み立てる. + """AreaLight の中心・半幅・方向から Mitsuba の to_world Transform を組み立てる. - Mitsuba の rectangle 形状は XZ 平面上の -1〜+1 の正方形(Y 軸正方向が前面). + Mitsuba の rectangle / disk 形状はどちらも XY 平面上の単位図形 + (rectangle は ±1 の正方形,disk は半径 1 の円)で法線が +Z. direction ベクトルが面の法線方向を指すように look_at を構成する. Args: center: 面中心位置(元単位). - size: 面サイズ (width, height)(元単位). + half_extents: X / Y 方向の半幅 (hx, hy)(元単位). + rectangle は (width/2, height/2),disk は (radius, radius) を渡す. direction: 放射方向ベクトル(単位ベクトルでなくても可). position_scale: 座標スケール係数(mm→m なら 0.001). @@ -38,7 +40,7 @@ Mitsuba ScalarTransform4f. """ cx, cy, cz = [c * position_scale for c in center] - sw, sh = size[0] * position_scale, size[1] * position_scale + hx, hy = half_extents[0] * position_scale, half_extents[1] * position_scale dx, dy, dz = direction norm = (dx**2 + dy**2 + dz**2) ** 0.5 @@ -60,8 +62,8 @@ transform = mi.ScalarTransform4f().look_at(origin=origin, target=target, up=up) - # サイズに合わせてスケール(rectangle は ±1 なので half-size でスケール) - scale = mi.ScalarTransform4f().scale([sw / 2.0, sh / 2.0, 1.0]) + # 単位図形(±1 / 半径 1)を半幅でスケールして実寸に合わせる + scale = mi.ScalarTransform4f().scale([hx, hy, 1.0]) return transform @ scale @@ -77,7 +79,8 @@ Returns: Mitsuba scene dict のキーに使える dict. - PointLight → {"type": "point", "position": [...], "intensity": {...}} - - AreaLight → {"type": "rectangle", "to_world": ..., "emitter": {"type": "area", ...}} + - AreaLight → {"type": "rectangle" | "disk", "to_world": ..., + "emitter": {"type": "area", ...}} - DirectionalLight → {"type": "directional", "direction": [...], "irradiance": {...}} Raises: @@ -95,14 +98,19 @@ } if isinstance(light, AreaLight): + # 半幅: rectangle は size の半分,disk は radius(スキーマで必須検証済み) + if light.shape == "disk": + half_extents = (light.radius, light.radius) + else: + half_extents = (light.size[0] / 2.0, light.size[1] / 2.0) to_world = _build_area_light_transform( center=light.center, - size=light.size, + half_extents=half_extents, direction=light.direction, position_scale=position_scale, ) return { - "type": "rectangle", + "type": light.shape, "to_world": to_world, "emitter": { "type": "area", diff --git a/src/sm_tias_lightsim/scene/schema.py b/src/sm_tias_lightsim/scene/schema.py index 4403901..6eee4fa 100644 --- a/src/sm_tias_lightsim/scene/schema.py +++ b/src/sm_tias_lightsim/scene/schema.py @@ -214,16 +214,38 @@ class AreaLight(_FrozenBase): - """面光源(矩形).""" + """面光源(矩形または円板). + + shape="rectangle" は `size`(幅×高さ),shape="disk" は `radius` で + 発光面の大きさを指定する(どちらも該当フィールドが必須). + """ id: str = Field(..., description="シーン内ユニーク ID") type: Literal["area"] - shape: Literal["rectangle"] = Field(..., description="面形状") + shape: Literal["rectangle", "disk"] = Field(..., description="面形状") center: Vec3 = Field(..., description="中心位置 [mm]") - size: tuple[float, float] = Field(..., description="サイズ [mm, mm]") + size: tuple[float, float] | None = Field( + None, description="矩形サイズ [mm, mm](shape=rectangle で必須)" + ) + radius: float | None = Field(None, description="円板半径 [mm](shape=disk で必須,正値)") direction: Vec3 = Field(..., description="放射方向ベクトル") radiance: float | Vec3 = Field(..., description="放射輝度(スカラまたは RGB タプル)") + @model_validator(mode="after") + def _check_shape_params(self) -> AreaLight: + """shape に応じた必須フィールド(size / radius)の整合を検証する.""" + if self.shape == "rectangle" and self.size is None: + msg = f"AreaLight '{self.id}': shape=rectangle には size が必須です" + raise ValueError(msg) + if self.shape == "disk": + if self.radius is None: + msg = f"AreaLight '{self.id}': shape=disk には radius が必須です" + raise ValueError(msg) + if self.radius <= 0: + msg = f"AreaLight '{self.id}': radius は正値を指定してください({self.radius})" + raise ValueError(msg) + return self + class DirectionalLight(_FrozenBase): """平行光源(無限遠光源).""" diff --git a/src/sm_tias_lightsim/visualize/viewer.py b/src/sm_tias_lightsim/visualize/viewer.py index 089c44e..09b28ed 100644 --- a/src/sm_tias_lightsim/visualize/viewer.py +++ b/src/sm_tias_lightsim/visualize/viewer.py @@ -1907,19 +1907,28 @@ elif isinstance(light, AreaLight): center_m = [c * _MM_TO_M for c in light.center] - size_m = [light.size[0] * _MM_TO_M, light.size[1] * _MM_TO_M] dir_vec = np.array(light.direction, dtype=np.float64) dir_norm = np.linalg.norm(dir_vec) if dir_norm > _DIR_NORM_EPSILON: dir_vec = dir_vec / dir_norm - plane = pv.Plane( - center=center_m, - direction=dir_vec.tolist(), - i_size=size_m[0], - j_size=size_m[1], - ) + if light.shape == "disk": + plane = pv.Disc( + center=center_m, + inner=0.0, + outer=light.radius * _MM_TO_M, + normal=dir_vec.tolist(), + c_res=64, + ) + else: + size_m = [light.size[0] * _MM_TO_M, light.size[1] * _MM_TO_M] + plane = pv.Plane( + center=center_m, + direction=dir_vec.tolist(), + i_size=size_m[0], + j_size=size_m[1], + ) plotter.add_mesh(plane, color="orange", opacity=0.7, label=f"AreaLight:{light.id}") arrow = pv.Arrow( diff --git a/tests/scene/test_builder.py b/tests/scene/test_builder.py index 3c1b22d..23f74b7 100644 --- a/tests/scene/test_builder.py +++ b/tests/scene/test_builder.py @@ -219,6 +219,20 @@ assert scene_dict["mesh_sphere"]["type"] == "ply" +def test_build_scene_dict_mesh_uses_face_normals( + minimal_scene_config: SceneConfig, tmp_path: Path +): + """mesh の face_normals が True であること. + + 仕様: PLY に法線を書き出さないため,指定なしでは Mitsuba がスムーズ頂点法線を + 自動生成し,シャープエッジ構成の機械部品 STL で評価面の輝度マップに偽の異方性を + 生む(TEST_12).面法線の強制はこの系統誤差の再発防止. + """ + scene_dict, tempdir = build_scene(minimal_scene_config, project_root=tmp_path) + shutil.rmtree(tempdir, ignore_errors=True) + assert scene_dict["mesh_sphere"]["face_normals"] is True + + def test_build_scene_dict_mesh_bsdf_is_ref(minimal_scene_config: SceneConfig, tmp_path: Path): """mesh の bsdf が {"type": "ref", "id": "mat_"} であること. diff --git a/tests/scene/test_lights.py b/tests/scene/test_lights.py index 447a825..860579a 100644 --- a/tests/scene/test_lights.py +++ b/tests/scene/test_lights.py @@ -310,3 +310,55 @@ ) result = build_light_dict(light, position_scale=0.001) assert result["irradiance"]["value"] == pytest.approx([1.0, 0.8, 0.6]) + + +# --------------------------------------------------------------------------- +# AreaLight (disk) +# --------------------------------------------------------------------------- + + +def test_area_light_disk_shape_type_is_disk(): + """shape=disk の AreaLight から生成される dict の type が "disk" であること. + + 仕様: AreaLight → {"type": "rectangle" | "disk", ...} (SPEC_01 scene/lights 責務) + """ + light = AreaLight( + id="led_disk", + type="area", + shape="disk", + center=(0.0, 0.0, -142.0), + radius=1.766, + direction=(0.0, 0.0, -1.0), + radiance=50.0, + ) + result = build_light_dict(light, position_scale=0.001) + assert result["type"] == "disk" + assert "emitter" in result + assert result["emitter"]["type"] == "area" + + +def test_area_light_disk_to_world_scales_by_radius(): + """disk 光源の to_world が X/Y 軸を radius,法線軸を 1 でスケールすること. + + Mitsuba の disk は半径 1 の単位円板なので,to_world の 3×3 部の列ノルムが + (radius, radius, 1) × position_scale 相当になることを検証する + (TEST_08 のスケール軸バグ再発防止と同種の実寸検証.load_dict は使わない). + """ + light = AreaLight( + id="led_disk", + type="area", + shape="disk", + center=(0.0, 0.0, 0.0), + radius=5.0, + direction=(0.0, 0.0, -1.0), + radiance=50.0, + ) + result = build_light_dict(light, position_scale=0.001) + m = result["to_world"].matrix + col_norms = sorted( + (m[0, j] ** 2 + m[1, j] ** 2 + m[2, j] ** 2) ** 0.5 for j in range(3) + ) + # 半径 5mm → 0.005m の列が 2 本,法線軸(スケール 1.0)の列が 1 本 + assert col_norms[0] == pytest.approx(0.005, rel=1e-6) + assert col_norms[1] == pytest.approx(0.005, rel=1e-6) + assert col_norms[2] == pytest.approx(1.0, rel=1e-6) diff --git a/tests/scene/test_schema.py b/tests/scene/test_schema.py index 9eed135..ad2f562 100644 --- a/tests/scene/test_schema.py +++ b/tests/scene/test_schema.py @@ -589,3 +589,66 @@ config = SceneConfig.model_validate(valid_minimal_dict) with pytest.raises(ValidationError): config.name = "modified" # type: ignore[misc] + + +# --------------------------------------------------------------------------- +# AreaLight の shape 別バリデーション(rectangle / disk) +# --------------------------------------------------------------------------- + + +def test_area_light_disk_valid(): + """shape=disk は radius 指定で正常に構築できること. + + 仕様: AreaLight shape=disk には radius が必須 (SPEC_02 lights セクション) + """ + light = AreaLight( + id="led_disk", + type="area", + shape="disk", + center=(0.0, 0.0, -142.0), + radius=1.766, + direction=(0.0, 0.0, -1.0), + radiance=50.0, + ) + assert light.radius == pytest.approx(1.766) + assert light.size is None + + +def test_area_light_disk_requires_radius(): + """shape=disk で radius が無いとき ValidationError が発生すること.""" + with pytest.raises(ValidationError): + AreaLight( + id="led_disk", + type="area", + shape="disk", + center=(0.0, 0.0, -142.0), + direction=(0.0, 0.0, -1.0), + radiance=50.0, + ) + + +def test_area_light_disk_rejects_nonpositive_radius(): + """shape=disk で radius が 0 以下のとき ValidationError が発生すること.""" + with pytest.raises(ValidationError): + AreaLight( + id="led_disk", + type="area", + shape="disk", + center=(0.0, 0.0, -142.0), + radius=0.0, + direction=(0.0, 0.0, -1.0), + radiance=50.0, + ) + + +def test_area_light_rectangle_requires_size(): + """shape=rectangle で size が無いとき ValidationError が発生すること.""" + with pytest.raises(ValidationError): + AreaLight( + id="led_area", + type="area", + shape="rectangle", + center=(0.0, 0.0, 20.0), + direction=(0.0, 0.0, -1.0), + radiance=50.0, + )