diff --git a/CLAUDE.md b/CLAUDE.md index 27cdcd4..2941616 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,6 +14,7 @@ 補足: **輝度マップ「形」の追跡を導入**([TEST_03 輝度マップ形状追跡](docs/06_TEST/TEST_03_輝度マップ形状追跡.md) + `scripts/build_luminance_evolution_panel.py`).重要な発見: **これまでの全 7 改善ステップ (8-1〜8-7) で輝度マップの「形」は全く変わっていない**.OETF は単調変換で形を変えず,発光面サイズ変更も形を変えない.**実機は中央付近にピークを持つ楕円状,シミュは下端のみ明の Y 軸単調勾配**.**LSC モデル化は「数値残差縮小」と「形の構造的改善」の両方を担う唯一の改善候補**.以後の改善判断は「数値 + 形」の 2 軸で行う. 補足: **Step 8-8 完了**.linear 評価モードに mean 揃え正規化を導入(`evaluation/normalize.py` の `apply_mean_anchor` + `EvaluationConfig.normalization` スキーマ + CLI 統合 + OETF 排他バリデーション).OETF 露出アンカー(Step 8-3)と同じ思想を linear ドメインに対称展開.`configs/eval_baseline_linear.yaml` で有効化(target_mean=146.7146).形状指標 (CoV/勾配/C/P/max-min) は厳密不変,mean のみ target_mean に揃う.動作確認: α=0.961934 で baseline luminance.npy が mean=146.7146, CoV=0.182577 で評価可能.テスト 62 件追加(既存 441 + 新規 62 = 503 件全通過). 補足: **Step 8-9 完了**.LED 18 個の位置を Fusion 360 設計 STL (`configs/meshes/SmTIASv3.stl`) から抽出して `scene_baseline.yaml` を更新.`scripts/extract_led_positions_from_stl.py` で連結成分分割→ LED/カバー識別→ bottom/right/left 自動命名.主な変更: 下基板 LED 配置を「等間隔 8.7mm」→「3+ギャップ+3 (4.2mm+10.8mm+4.2mm)」へ訂正(実機写真の中央電流制限抵抗 2 個と整合).左右基板も実位置に修正.**形状指標は ~1% 以内のほぼ不変**(多重反射が支配的,LED 数 mm 誤差は ROI 上で平滑化).絶対 mean は +10% 増加(α 値 0.962→0.873).既知差分 #2 をクローズ.TEST_03 比較パネルに Step 8-9 を追加(5 枚並列). +補足: **LED 基板カバーを 3 枚構成に展開**.従来は下基板用 `LED基板カバー.stl` 1 枚のみだったが,`scripts/extract_led_positions_from_stl.py --export-covers configs/meshes/` で SmTIASv3.stl から `LED基板カバー下/右/左.stl` の 3 ファイルを書き出し,`scene_baseline.yaml` の geometry を `led_cover_bottom/right/left` の 3 メッシュエントリに展開.右・左基板の LED が「カバーなしで浮く」状態を解消.多重反射計算への影響は軽微だが物理的に正確. 次のアクション(キャリブ再開時): 残差に **LSC(ISP レンズシェーディング)順方向モデル**を当てる(物理動機づけ低自由度形、overfitting 回避・取得パラメータ数明記). 実装済みモジュール: diff --git "a/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274.stl" "b/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274.stl" deleted file mode 100644 index f0a7fde..0000000 --- "a/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274.stl" +++ /dev/null Binary files differ diff --git "a/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274\344\270\213.stl" "b/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274\344\270\213.stl" new file mode 100644 index 0000000..187bff6 --- /dev/null +++ "b/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274\344\270\213.stl" Binary files differ diff --git "a/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274\345\217\263.stl" "b/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274\345\217\263.stl" new file mode 100644 index 0000000..306edf2 --- /dev/null +++ "b/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274\345\217\263.stl" Binary files differ diff --git "a/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274\345\267\246.stl" "b/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274\345\267\246.stl" new file mode 100644 index 0000000..33c6e1f --- /dev/null +++ "b/configs/meshes/LED\345\237\272\346\235\277\343\202\253\343\203\220\343\203\274\345\267\246.stl" Binary files differ diff --git a/configs/scene_baseline.yaml b/configs/scene_baseline.yaml index e65cc8c..3eb42ae 100644 --- a/configs/scene_baseline.yaml +++ b/configs/scene_baseline.yaml @@ -15,8 +15,16 @@ path: meshes/スマホホルダ.stl unit: mm material: holder_dark - - id: led_cover - path: meshes/LED基板カバー.stl + - 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 diff --git "a/docs/04_SPEC/SPEC_03_\345\237\272\346\272\226\350\250\255\350\250\210\343\201\256\347\211\251\347\220\206\346\247\213\346\210\220.md" "b/docs/04_SPEC/SPEC_03_\345\237\272\346\272\226\350\250\255\350\250\210\343\201\256\347\211\251\347\220\206\346\247\213\346\210\220.md" index 62ae479..1297526 100644 --- "a/docs/04_SPEC/SPEC_03_\345\237\272\346\272\226\350\250\255\350\250\210\343\201\256\347\211\251\347\220\206\346\247\213\346\210\220.md" +++ "b/docs/04_SPEC/SPEC_03_\345\237\272\346\272\226\350\250\255\350\250\210\343\201\256\347\211\251\347\220\206\346\247\213\346\210\220.md" @@ -172,7 +172,7 @@ | 部品 | 材料定義名 | 材料モデル | reflectance | 備考 | | --- | --- | --- | --- | --- | | 前側 / 後側(筐体) | `printer_white` | `diffuse` | 0.85 | 3D プリンタ白フィラメント(暫定値.実物の素材データシートで再校正) | -| LED 基板カバー | `printer_white` | `diffuse` | 0.85 | **同じ 3D プリンタ白素材.不透明な白プラの枠**で LED の周囲を囲む構造であり、LED 光が透過するディフューザーではない | +| LED 基板カバー 3 枚(下 / 右 / 左) | `printer_white` | `diffuse` | 0.85 | **同じ 3D プリンタ白素材.不透明な白プラの枠**で LED の周囲を囲む構造.Step 8-9 で 1 枚(下基板用)から **3 枚構成(下/右/左の各基板用)** に展開.`meshes/LED基板カバー{下,右,左}.stl`(`configs/meshes/SmTIASv3.stl` から `scripts/extract_led_positions_from_stl.py --export-covers` で抽出) | | 評価板 | `printer_white` | `diffuse` | 0.85 | 3D プリンタ白.照度マップ上の輝度から照度への換算は `E ≈ pixel × π / 0.85` | | スマホホルダ | `holder_dark` | `diffuse` | 0.50 | 別素材(推定.要確認) | | スマホ本体(SH-02M) | `phone_body_dark` | `diffuse` | 0.05 | 樹脂・金属の黒系 | diff --git a/scripts/extract_led_positions_from_stl.py b/scripts/extract_led_positions_from_stl.py index 0bd2350..354dc75 100644 --- a/scripts/extract_led_positions_from_stl.py +++ b/scripts/extract_led_positions_from_stl.py @@ -73,7 +73,11 @@ def _component_stats(mesh: trimesh.Trimesh, face_indices: list[int]) -> dict[str, Any]: - """連結成分の統計(頂点・面数・bbox 中心・extents)を計算する.""" + """連結成分の統計(頂点・面数・bbox 中心・extents)を計算する. + + `face_indices` は STL 内の元 face index リストで,後で独立 STL として + 書き出す際に頂点・面の再マッピングに使う. + """ face_verts_idx = np.unique(mesh.faces[face_indices].flatten()) verts = mesh.vertices[face_verts_idx] bmin = verts.min(axis=0) @@ -85,9 +89,28 @@ "extents": bmax - bmin, "bmin": bmin, "bmax": bmax, + "face_indices": face_indices, } +def _export_component_as_stl(mesh: trimesh.Trimesh, face_indices: list[int], + output_path: Path) -> None: + """連結成分を独立 STL ファイルとして書き出す. + + 元メッシュの face_indices に対応する頂点・面を抽出し,頂点 index を再マッピング + してから新規 Trimesh として書き出す.元の座標系(ワールド)は保持する. + """ + face_verts_idx = np.unique(mesh.faces[face_indices].flatten()) + old_to_new = {int(old): new for new, old in enumerate(face_verts_idx)} + new_vertices = mesh.vertices[face_verts_idx] + new_faces = np.array( + [[old_to_new[int(v)] for v in mesh.faces[fi]] for fi in face_indices] + ) + sub_mesh = trimesh.Trimesh(vertices=new_vertices, faces=new_faces, process=False) + output_path.parent.mkdir(parents=True, exist_ok=True) + sub_mesh.export(str(output_path), file_type="stl") + + def _infer_led_direction(led_center: np.ndarray, led_extents: np.ndarray, cover_center: np.ndarray) -> np.ndarray: """LED 発光面の法線方向を推定する. @@ -256,6 +279,11 @@ help="出力 YAML パス(省略時は標準出力)") parser.add_argument("--radiance", type=float, default=DEFAULT_RADIANCE, help=f"各 LED の radiance 値(既定 {DEFAULT_RADIANCE})") + parser.add_argument("--export-covers", type=Path, default=None, metavar="DIR", + help=( + "LED 基板カバーを bottom/right/left ごとに独立 STL として書き出す." + " 指定したディレクトリに LED基板カバー{bottom,right,left}.stl を保存." + )) parser.add_argument("--summary", action="store_true", help="LED 位置サマリーを stderr に表示") args = parser.parse_args() @@ -286,6 +314,17 @@ flush=True) all_leds.extend(leds_with_ids) + # カバー書き出し(--export-covers 指定時のみ). + # ファイル名は既存 `LED基板カバー.stl` と整合させて日本語化(下/右/左). + jp_labels = {"bottom": "下", "right": "右", "left": "左", "top": "上"} + if args.export_covers is not None: + for cover in covers: + label = _classify_cover_position(cover["center"]) + jp = jp_labels.get(label, label) + out_path = args.export_covers / f"LED基板カバー{jp}.stl" + _export_component_as_stl(mesh, cover["face_indices"], out_path) + print(f" exported cover [{label}] → {out_path}", flush=True) + print(f"\n# Total: {len(all_leds)} LED(s) from {len(args.stl_files)} STL file(s)", flush=True) # YAML 形式で出力