diff --git a/CLAUDE.md b/CLAUDE.md index 27f04d7..247f2ef 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,11 +5,8 @@ ## 開発進捗 -現在の進捗: GUIDE_01 フェーズ 5(規約整備)— `GUIDE_06〜09` 作成中 -※ フェーズ 1(方針決定)完了: [docs/03_PLAN/PLAN_01_要件定義書.md](docs/03_PLAN/PLAN_01_要件定義書.md) -※ フェーズ 2(技術選定)完了: [docs/02_ENV/ENV_01_技術スタック.md](docs/02_ENV/ENV_01_技術スタック.md) -※ フェーズ 3(環境構築)完了: [docs/02_ENV/ENV_02_環境構築手順.md](docs/02_ENV/ENV_02_環境構築手順.md), [docs/02_ENV/ENV_03_管理者用環境構築手順.md](docs/02_ENV/ENV_03_管理者用環境構築手順.md) -※ フェーズ 4(仕様設計)完了: [docs/04_SPEC/SPEC_01_アーキテクチャ設計.md](docs/04_SPEC/SPEC_01_アーキテクチャ設計.md), [docs/04_SPEC/SPEC_02_シーン定義仕様.md](docs/04_SPEC/SPEC_02_シーン定義仕様.md) +現在の進捗: フェーズ 1〜6 完了.フェーズ 7(実装開始)に移行. +次のアクション: `/implement Step 1: 均一性評価モジュールの移植`([PLAN_02 参照](docs/03_PLAN/PLAN_02_開発ステップ.md)) ※ ステップ完了時にここを更新すること. ## 必須ルール(コード実装時) @@ -48,24 +45,29 @@ - ファイル命名規則: docs/01_GUIDE/GUIDE_03_ファイル命名規則.md - Git 運用ルール: docs/01_GUIDE/GUIDE_04_Git運用ルール.md - エージェント運用ルール: docs/01_GUIDE/GUIDE_05_エージェント運用ルール.md -- ※ コーディング規約,テスト方針等はプロジェクト立ち上げ時に作成する(GUIDE_01 参照) +- コーディング規約: docs/01_GUIDE/GUIDE_06_コーディング規約.md +- テスト方針: docs/01_GUIDE/GUIDE_08_テスト方針.md ### 02_ENV(環境) - +- 技術スタック: docs/02_ENV/ENV_01_技術スタック.md +- 環境構築手順: docs/02_ENV/ENV_02_環境構築手順.md +- 管理者用環境構築手順: docs/02_ENV/ENV_03_管理者用環境構築手順.md ### 03_PLAN(計画) - +- 要件定義書: docs/03_PLAN/PLAN_01_要件定義書.md +- 開発ステップ: docs/03_PLAN/PLAN_02_開発ステップ.md ### 04_SPEC(仕様・設計) - +- アーキテクチャ設計: docs/04_SPEC/SPEC_01_アーキテクチャ設計.md +- シーン定義仕様: docs/04_SPEC/SPEC_02_シーン定義仕様.md ### 05_TECH(技術設計) - + ### 06_TEST(テスト) - + diff --git "a/docs/01_GUIDE/GUIDE_06_\343\202\263\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\350\246\217\347\264\204.md" "b/docs/01_GUIDE/GUIDE_06_\343\202\263\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\350\246\217\347\264\204.md" new file mode 100644 index 0000000..d77a5f9 --- /dev/null +++ "b/docs/01_GUIDE/GUIDE_06_\343\202\263\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\350\246\217\347\264\204.md" @@ -0,0 +1,124 @@ +# コーディング規約 (Coding Standards) + +## 基本方針 (Basic Policy) + +- ruff を唯一の lint / format ツールとして使用する.設定は `pyproject.toml` の `[tool.ruff]` に集約. +- 標準(PEP 8 + Effective Python)から逸脱しない範囲で,ruff の自動修正に従う. +- プロジェクト固有のルールはこのファイルに最小限で書く. +- 別ツール(black, isort, mypy 等)は導入しない(ruff で完結). + +## 適用 ruff ルール + +`pyproject.toml` の `[tool.ruff.lint]` で `select = ["E", "F", "W", "I", "B", "UP", "N"]` を選択. + +| ルール | 内容 | +| --- | --- | +| E (pycodestyle) | PEP 8 形式エラー | +| F (pyflakes) | 未使用変数・未定義シンボル等 | +| W (pycodestyle) | 形式警告 | +| I (isort) | import 文の並び順 | +| B (flake8-bugbear) | バグになりやすいパターン検出 | +| UP (pyupgrade) | Python 3.12 機能で書き換え可能なコードの提案 | +| N (pep8-naming) | クラス・関数・変数命名 | + +行長は 100 文字(Mitsuba API や数式リテラルが長くなりがちなため). + +実行コマンド: + +```powershell +uv run ruff check . # 検査のみ +uv run ruff check . --fix # 自動修正 +uv run ruff format . # フォーマット適用 +``` + +## 型ヒント (Type Hints) + +- **公開関数(モジュールから export される関数・クラス)**: 引数と戻り値に型ヒント必須 +- **内部ヘルパ・テストコード**: 任意.書ける所は書く +- **Mitsuba / Dr.Jit の戻り値**: 型ヒントが不完全.`Any` または `np.ndarray` でフォールバック可 + +例: + +```python +import numpy as np + + +def to_grayscale(image: np.ndarray) -> np.ndarray: + """RGB → 輝度変換....""" + ... +``` + +静的型チェック(mypy / pyright)は当面導入しない.必要が出てから追加. + +## docstring + +- **公開関数・クラス**: docstring 必須 +- **書式**: Google スタイル(`Args:` / `Returns:` / `Raises:`)に統一 +- **言語**: 日本語可.数式は LaTeX 表記(`$Y = 0.2126 R + ...$`)を許可 +- **数式の出典**: 引用 `[1]` を残し,参考文献は同モジュールの末尾コメントまたは docstring 末尾に列挙 + +例: + +```python +def to_grayscale(image: np.ndarray) -> np.ndarray: + """RGB 画像を Rec.709 輝度係数でグレースケールに変換する [1]. + + $Y = 0.2126 R + 0.7152 G + 0.0722 B$ + + Args: + image: 入力 RGB 画像(H x W x 3, uint8). + + Returns: + 輝度画像(H x W, float64, 値域 0.0〜255.0). + + References: + [1] ITU-R BT.709 + """ +``` + +## 命名規則 (Naming) + +ruff `N` ルールでチェック. + +| 対象 | スタイル | 例 | +| --- | --- | --- | +| モジュール | snake_case | `uniformity.py`, `scene_builder.py` | +| パッケージ | snake_case | `sm_tias_lightsim` | +| 関数・変数 | snake_case | `to_grayscale`, `roi_image` | +| クラス | PascalCase | `SceneBuilder`, `LedLight` | +| 定数 | UPPER_SNAKE_CASE | `REC709_COEFF_R` | +| プライベート | 接頭辞 `_` | `_validate_roi` | + +## コメント + +- **WHY を書く**:何をしているかは識別子で表現.コメントは「なぜこの実装か」「数式の出典」「制約」に限定する. +- **TODO** は `# TODO(rintoHasegawa): 内容` 形式で記述する. + +## import 順序 + +ruff `I` で自動整列.以下の順: + +1. 標準ライブラリ +2. サードパーティ(`mitsuba`, `numpy`, `trimesh` 等) +3. プロジェクト内(`from sm_tias_lightsim import ...`) + +手動整列不要. + +## CLI 引数 + +- 短縮形より長い形を優先(`--scene` を主,`-s` は併用) +- `argparse` 標準を使用 +- ヘルプ文は日本語可 + +## エラー処理(最小限) + +- ユーザー入力(YAML / STL ファイル)の問題は `ValueError` に統一して raise +- 内部論理エラーは `RuntimeError` +- Pydantic を使うバリデーションは `pydantic.ValidationError` をそのまま伝播 +- これ以上の例外設計は GUIDE_09(エラーハンドリング方針)が必要になった時点で追加する + +## 参考 (References) + +- [PEP 8 — Style Guide for Python Code](https://peps.python.org/pep-0008/) +- [Google Python Style Guide — Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) +- [ruff documentation](https://docs.astral.sh/ruff/) diff --git "a/docs/01_GUIDE/GUIDE_08_\343\203\206\343\202\271\343\203\210\346\226\271\351\207\235.md" "b/docs/01_GUIDE/GUIDE_08_\343\203\206\343\202\271\343\203\210\346\226\271\351\207\235.md" new file mode 100644 index 0000000..f77c9dc --- /dev/null +++ "b/docs/01_GUIDE/GUIDE_08_\343\203\206\343\202\271\343\203\210\346\226\271\351\207\235.md" @@ -0,0 +1,101 @@ +# テスト方針 (Test Policy) + +## 基本方針 (Basic Policy) + +- フレームワーク: **pytest** +- 配置: `tests/` 配下に `src/sm_tias_lightsim/` のサブパッケージ構造をミラー +- カバレッジ目標: **数値目標なし**.代わりに「核となる計算ロジックは必ず単体テスト」というルールを徹底する +- 通常実行: `uv run pytest` +- 重いテストを含む実行: `uv run pytest -m "" --no-strict-markers` / デフォルトでスキップは `-m "not slow"` + +## テストの配置 + +``` +tests/ +├── conftest.py # 共通フィクスチャ +├── fixtures/ # テストデータ(小さい STL / 既知の輝度マップ等) +├── geometry/ +│ └── test_loader.py +├── scene/ +│ ├── test_builder.py +│ └── test_schema.py +├── render/ +│ └── test_runner.py # 軽量シーンの end-to-end +├── test_uniformity.py # MiniTias-Evaluation 互換の数値検証 +└── evaluation/ + ├── test_roi.py + └── test_zones.py +``` + +## ファイル・関数命名 + +- ファイル: `test_<対象モジュール>.py` +- 関数: `test_<振る舞い>` 例: `test_grayscale_conversion_uses_rec709_coefficients` +- クラス(任意): `Test<クラス名>` で振る舞いごとにグループ化可能 + +## 必ずテストを書く対象 (Mandatory) + +- **核計算ロジック**: `uniformity.py`, `evaluation/zones.py`, `evaluation/radial.py` + - 既知の入力(合成輝度マップ)に対して期待値を確認 + - MiniTias-Evaluation の元実装と数値が一致することを保証 +- **入力バリデーション**: `scene/schema.py` + - 不正 YAML が `ValidationError` を投げること + - 必須フィールド欠落・参照不整合を検出すること +- **データ変換**: `geometry/transforms.py`(mm ↔ m 等) + - 変換が可逆であること +- **YAML → Mitsuba scene 変換**: `scene/builder.py` + - 期待される scene dict を生成すること(実レンダリングはせず構造のみ確認) + +## テストを必須としない対象 (Optional) + +- **CLI のエントリポイント**(`cli/render.py`, `cli/evaluate.py`) + - 統合テスト 1 本でカバー +- **可視化**(`evaluation/visualize.py`) + - 図の見栄えはテストしにくい.「ファイルが生成されたか」程度の smoke テストで十分 + +## モック方針 (Mocking) + +- **Mitsuba をモックしない**.小さい合成シーン(64×64 等)で実物を回す + - 理由: モックすると物理量の妥当性検証ができなくなる.実機との比較が目的のため +- **ファイル I/O はモックしない**.`tmp_path` フィクスチャで実ファイルを使う +- **乱数を含むテスト**は必ず `seed` を固定する + +## 重いテストの扱い (Slow Tests) + +レンダリングを伴うテストは `@pytest.mark.slow` を付けて区別: + +```python +import pytest + + +@pytest.mark.slow +def test_render_baseline_matches_reference(): + ... +``` + +- 通常実行(高速): `uv run pytest -m "not slow"` +- 全テスト実行(CI 等): `uv run pytest` + +マーカー定義は `pyproject.toml` の `[tool.pytest.ini_options]` に登録済み. + +## /implement パイプラインでのテスト + +`/implement` の流れ: + +1. **Phase 1(実装 / coder)**: 機能実装 → 人間が動作確認 +2. **Phase 2(テスト / tester)**: この方針に従いテストを追加 +3. **Phase 3(リファクタ / refactorer)**: テストを safety net として品質改善 +4. **Phase 4**: ドキュメント更新 + +Phase 2 でテストが書きにくいと感じたら,それは設計が悪いシグナル.Phase 3 でモジュールを分割するか責務を見直す. + +## テスト失敗時の運用 + +- ルール変更による失敗: 規約・仕様を改める根拠があれば SPEC / GUIDE を先に更新してからテストを書き直す +- バグ起因の失敗: 修正してから再実行 +- 単に flaky なテスト: `seed` 固定漏れか,乱数依存の境界値.`seed` を見直す + +## 参考 (References) + +- [pytest documentation](https://docs.pytest.org/) +- [Mitsuba 3 — Differentiable rendering tutorials](https://mitsuba.readthedocs.io/en/stable/) (小規模シーンの組み立て例) diff --git "a/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" "b/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" new file mode 100644 index 0000000..ec1047e --- /dev/null +++ "b/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" @@ -0,0 +1,139 @@ +# 開発ステップ (Development Steps) + +## 全体方針 (Overall Policy) + +実装は **核計算ロジックから外側に向かって積み上げる**: + +1. **数値計算(評価指標)** が先.既知のテストデータで動作する状態を作る +2. **入力スキーマと I/O** を整える +3. **シーン構築 → 最小レンダー** で物理シミュレーションを動かす +4. **CLI として 2 段階パイプラインを完成** +5. **基準設計でキャリブレーション** +6. **設計バリエーションを比較** + +各ステップ完了時に [GUIDE_04](../01_GUIDE/GUIDE_04_Git運用ルール.md) に従ってコミット・PR を作成する.`/implement <ステップ>` でコーディング → テスト → リファクタの自動パイプラインを通す. + +## ステップ一覧 (Steps) + +| Step | 名前 | 成果物 | 依存 | +| --- | --- | --- | --- | +| 1 | 均一性評価モジュールの移植 | `uniformity.py`, `evaluation/zones.py`, `evaluation/radial.py` + テスト | なし | +| 2 | YAML スキーマ定義 | `scene/schema.py` + バリデーションテスト | Step 1 | +| 3 | ジオメトリ層 | `geometry/loader.py`, `geometry/transforms.py` + テスト | Step 2 | +| 4 | シーン構築 + 最小レンダー | `scene/builder.py`, `scene/lights.py`, `scene/materials.py`, `scene/sensors.py`, `render/runner.py`, `render/output.py` | Step 3 | +| 5 | render CLI | `cli/render.py` + 統合テスト | Step 4 | +| 6 | evaluate CLI + 可視化 | `cli/evaluate.py`, `evaluation/roi.py`, `evaluation/visualize.py` | Step 1, 5 | +| 7 | 基準設計のキャリブレーション | `configs/scene_baseline.yaml`, `configs/meshes/*.stl`, キャリブ報告 | Step 5, 6 | +| 8 | 設計バリエーション比較 | バリエーション YAML 複数 + 比較レポート | Step 7 | + +## 各ステップの詳細 + +### Step 1: 均一性評価モジュールの移植 + +- **目的**: 評価ロジックを最初に確定.ステップ 7 で「実測との比較」をするための物差しを先に作る +- **やること**: + - `MiniTias-Evaluation/src/uniformity.py` を `src/sm_tias_lightsim/uniformity.py` にコピー + - 元の SHA を docstring に明記(取得元: `https://github.com/rintoHasegawa/MiniTias-Evaluation`) + - `evaluation/zones.py`:3 ゾーン分析(中心 d<0.33 / 中間 / 周辺 d≥0.66) + - `evaluation/radial.py`:放射状プロファイル + - テスト:合成輝度マップ(既知の CoV)に対して期待値が出ること +- **完了条件**: `uv run pytest tests/test_uniformity.py` が通る + +### Step 2: YAML スキーマ定義 + +- **目的**: 設計ファイルの形式を確定し,不正入力を早期に弾く +- **やること**: + - 依存追加: `uv add pydantic pyyaml` + - `scene/schema.py`:[SPEC_02_シーン定義仕様](../04_SPEC/SPEC_02_シーン定義仕様.md) のトップレベル構造を Pydantic モデルで定義 + - 参照整合性チェック(meshes が存在しない materials を参照していないか等) + - YAML loader(PyYAML)でファイル → dict → Pydantic model + - テスト:正常 YAML がパースできる/不正 YAML が `ValidationError` を投げる +- **完了条件**: 各種異常系(必須欠落・型違反・参照不整合)でテストが通る + +### Step 3: ジオメトリ層 + +- **目的**: STL を Mitsuba が扱える単位・座標系で読めるようにする +- **やること**: + - `geometry/loader.py`:trimesh で STL 読込.頂点・面・単位タグを保持 + - `geometry/transforms.py`:mm → m 変換等 + - テスト:単位変換が可逆/簡単な四面体 STL を読み頂点数が一致する +- **完了条件**: テストフィクスチャの STL が正しく読める + +### Step 4: シーン構築 + 最小レンダー + +- **目的**: 実物のシミュレーションが動く最小構成を作る +- **やること**: + - `scene/lights.py`:YAML 光源定義 → Mitsuba emitter + - `scene/materials.py`:YAML 材料 → Mitsuba BSDF + - `scene/sensors.py`:YAML センサ → Mitsuba sensor + - `scene/builder.py`:上記を統合.YAML 全体 → Mitsuba scene dict + - `render/runner.py`:variant 設定 + `mi.render` 実行.輝度配列を返す + - `render/output.py`:HDR 配列 → PNG / `luminance.npy` 出力 + - 最小シーン(1 LED + 白板 + センサ)でレンダリングが成功すること + - テスト:scene dict 構造の正しさ,最小レンダーの smoke test(`@pytest.mark.slow`) +- **完了条件**: 64×64 程度の合成シーンを最後までレンダリングできる + +### Step 5: render CLI + +- **目的**: コマンドラインから 1 行でシミュレーションを動かす +- **やること**: + - `cli/render.py`:`sm-render --scene --output ` を実装 + - `pyproject.toml` の `[project.scripts]` に登録(`sm-render = "sm_tias_lightsim.cli.render:main"`) + - テスト: 1 LED + 白板の最小シーン YAML から PNG が生成されること +- **完了条件**: `uv run sm-render --scene tests/fixtures/minimal.yaml --output /tmp/test/` が動く + +### Step 6: evaluate CLI + 可視化 + +- **目的**: シミュ画像と実測画像の両方に同じ評価コマンドを使える状態にする +- **やること**: + - `evaluation/roi.py`:`image_pixel` 型 ROI 抽出(最初は矩形のみ.`scene_face` は後回し可) + - `evaluation/visualize.py`:輝度マップ / ヒストグラム / 放射プロファイル / ゾーンマップを matplotlib で出力 + - `cli/evaluate.py`:`sm-evaluate --image --config --output ` を実装 + - テスト: 実測 PNG(MiniTias-Evaluation サンプル)と既知の指標値が一致すること +- **完了条件**: シミュ PNG と実測 PNG の両方で同じ指標 CSV が出る + +### Step 7: 基準設計のキャリブレーション + +- **目的**: シミュレーションの妥当性を実測値で検証.以降の改良案の基準を作る +- **やること**: + - 現行 MiniTIAS のジオメトリを Fusion 360 から STL でエクスポート + - LED の配置(位置・向き・個数)を `configs/scene_baseline.yaml` に記述 + - 材料パラメータの初期値を仮置き(ディフューザー透過率・筐体内壁反射率) + - `sm-render → sm-evaluate` で指標を算出 + - 実測値(CoV ≈ 0.0426, 中心/周辺比 ≈ 1.064, 勾配 ≈ 6%)と比較 + - 差分が大きい場合は SPEC_01 の「キャリブレーション戦略」順で原因切り分け + - 一致したら **許容範囲を確定**: + - 仮置き目標:CoV ±0.005,中心/周辺比 ±0.02,勾配 ±1.5% + - データを見て調整.確定値を本ファイルの末尾に追記 +- **完了条件**: 実測値と仮置き許容範囲内で一致.数値を本ファイルに記録 + +### Step 8: 設計バリエーション比較 + +- **目的**: 改良案を複数試して,実装に進む候補を絞る +- **やること**: + - LED 個数・配置を変えた YAML を 3〜5 パターン作成(例: リング 4 → 6 → 8 個) + - ディフューザー散乱パラメータを変えた YAML を 2〜3 パターン作成 + - 各パターンを `sm-render → sm-evaluate` で評価 + - 比較表(pandas DataFrame → CSV / Markdown)を生成 + - 有望な候補を Fusion 360 へフィードバック +- **完了条件**: 比較表が生成され,最低 1 つの改良案がベースラインより CoV 低下を達成 + +## キャリブレーション目標値(暫定) + +Step 7 で実数値を見てから確定する.それまでの仮置き: + +| 指標 | 実測値(参考) | 仮置き許容範囲 | +| --- | --- | --- | +| CoV | 0.0426 | ±0.005 | +| 中心/周辺比 | 1.064 | ±0.02 | +| 勾配 (%) | 6.23 | ±1.5 | + +確定値は Step 7 完了時に本表を上書きする. + +## マイルストーン (Milestones) + +- **M1(Step 1〜2 完了)**: 評価ロジック + スキーマが動く.まだ実物シミュは動かない +- **M2(Step 4〜5 完了)**: シミュレーションの 1 周(YAML → PNG)が動く +- **M3(Step 6 完了)**: シミュ・実測の同一評価パイプラインが完成 +- **M4(Step 7 完了)**: 基準設計と実測の整合が取れた.ここまでが「環境完成」 +- **M5(Step 8)**: 改良案が複数比較できる.プロジェクトの本来目的を達成 diff --git a/pyproject.toml b/pyproject.toml index d31bdec..7e0940c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,3 +26,19 @@ "pytest>=9.0.3", "ruff>=0.15.12", ] + +[tool.ruff] +line-length = 100 +target-version = "py312" + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "B", "UP", "N"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" + +[tool.pytest.ini_options] +markers = [ + "slow: 重いテスト(レンダリングを伴う等).通常実行は -m 'not slow' で除外", +]