diff --git a/CLAUDE.md b/CLAUDE.md index 8cb2c68..6d0623a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ ## 開発進捗 -最新: CNN のハイパーパラメータを変えても性能はほぼ不変と判明(TEST_06).次は CNN と色特徴の併用・効いている特徴の分析 +最新: 判定モデルの書き出しと DeepTIAS のセグメンテーションの Python 再現(IoU 0.958)を完了し,引き渡し仕様 SPEC_04 を作成(Issue #9).次は TIASjudge 側への組み込み(TIASjudge #13) ※ 本欄は**最新ステップ 1 行のみ上書き更新**.詳細な進捗履歴(動機・設計判断・失敗パターン)は [docs/PROGRESS.md](docs/PROGRESS.md) に追記する.運用ルールは `.claude/rules/progress-log.md` を参照(該当ファイル編集時に自動ロードされる). ## 必須ルール(コード実装時) @@ -91,6 +91,7 @@ - アーキテクチャ設計: docs/04_SPEC/SPEC_01_アーキテクチャ設計.md - 学習・評価仕様: docs/04_SPEC/SPEC_02_学習・評価仕様.md - リモート実行仕様: docs/04_SPEC/SPEC_03_リモート実行仕様.md +- TIASjudge 引き渡し仕様: docs/04_SPEC/SPEC_04_TIASjudge引き渡し仕様.md ### 05_TECH(技術設計) @@ -104,3 +105,4 @@ - クラス不均衡対策の実験: docs/06_TEST/TEST_04_クラス不均衡対策の実験.md - 色特徴分類器の実験: docs/06_TEST/TEST_05_色特徴分類器の実験.md - ハイパーパラメータの比較: docs/06_TEST/TEST_06_ハイパーパラメータの比較.md +- セグメンテーションの再現と判定モデルの書き出し: docs/06_TEST/TEST_07_セグメンテーションの再現と判定モデルの書き出し.md diff --git "a/docs/02_ENV/ENV_01_\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.md" "b/docs/02_ENV/ENV_01_\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.md" index 0225d88..26d075c 100644 --- "a/docs/02_ENV/ENV_01_\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.md" +++ "b/docs/02_ENV/ENV_01_\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.md" @@ -31,6 +31,7 @@ | --- | --- | --- | | 画像 IO | Pillow | 学習時の読み込み.軽量 | | 数値計算 | NumPy | — | +| 舌のセグメンテーション | onnxruntime・opencv-python-headless | DeepTIAS 2.7 の検出モデル(ONNX に変換)と ResUNet(ONNX)を CPU で推論し,大津の 2 値化・連結成分などの後処理を行う(SPEC_04).開発用の依存に `onnx`(テスト用のダミーモデルの生成).検出モデルの変換だけは TensorFlow・tf2onnx を一時的に使う(本体の依存に入れない) | | 評価指標 | scikit-learn | 混同行列・macro F1・クラス別 recall・患者単位の層化分割 | | 図の出力 | matplotlib | 学習曲線・混同行列を PNG で出力する(サーバ上で描画し,画像ファイルだけ回収する) | | ルールベース比較 | TIASjudge の `core/` ロジックを移植または参照 | ベースライン算出用.OpenCV が必要になった場合はサーバに GUI が無いため `opencv-python-headless` を使う | diff --git "a/docs/02_ENV/ENV_03_\347\256\241\347\220\206\350\200\205\347\224\250\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" "b/docs/02_ENV/ENV_03_\347\256\241\347\220\206\350\200\205\347\224\250\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" index 1443770..e343c27 100644 --- "a/docs/02_ENV/ENV_03_\347\256\241\347\220\206\350\200\205\347\224\250\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" +++ "b/docs/02_ENV/ENV_03_\347\256\241\347\220\206\350\200\205\347\224\250\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" @@ -161,6 +161,20 @@ ※ 研究室 PC から Newton への鍵が未登録の場合はパスワードを求められる. +## セグメンテーション用データの配置 (Segmentation Data) + +舌のセグメンテーションの検証([TEST_07](../06_TEST/TEST_07_セグメンテーションの再現と判定モデルの書き出し.md))に使う,ResUNet の開発時の正解マスク付きデータとモデルファイルの置き場所.2026-09-28 に実施. + +| もの | 置き場所 | +| --- | --- | +| 原本(画像・正解マスク・開発時の画像ごとの記録) | 研究室 PC の `C:\Users\Papin\clinical-data\TongueSegmentation\`(OneDrive の同期フォルダの外) | +| 作業用コピー(`input/` のみ.1804 ファイル・68 MB) | Newton・Archimedes の `~/data/TongueSegmentation/input/`(`drwx------`) | +| モデル(`ResUNet.onnx`・`Detection_Normal.pb`・変換した `Detection_Normal.onnx`) | Newton・Archimedes の `~/models/TongueSegmentation/`(`drwx------`.Newton で変換し,Archimedes には未配置なら同じ手順で変換する) | + +- 手元 PC の OneDrive にあった画像と画像ごとの記録は,研究室 PC にコピーして件数・サイズを照合した後に削除した(OneDrive の「大量削除の検出」で一度復元されたため,削除を確定し直した).コードとモデルは手元に残している +- サーバへの転送は研究室 PC で tar ファイルを作って `scp` で送り,サーバで展開した(手元 PC は経由しない).Windows の `tar` で作った tar は権限の情報のため展開に失敗することがあるので,`tar --delay-directory-restore -xf` で展開してから `chmod -R u+rwX,go-rwx` で整える.cmd のパイプ(`tar -cf - | ssh ... tar -xf -`)は研究室 PC 上で止まったため使わない +- 展開後の tar ファイルは研究室 PC・サーバとも削除した + ## 学外アクセス用の踏み台 (Off-campus Access) 研究室 PC を ProxyJump の踏み台にする設定は SmTIAS-LightSim で構築済みのため,本プロジェクトでの追加作業は不要(未設定の場合は同プロジェクトの `docs/02_ENV/ENV_02_環境構築手順.md`「学外から接続する(研究室 PC 経由)」に従う). diff --git "a/docs/02_ENV/ENV_04_\351\226\213\347\231\272\343\202\263\343\203\236\343\203\263\343\203\211.md" "b/docs/02_ENV/ENV_04_\351\226\213\347\231\272\343\202\263\343\203\236\343\203\263\343\203\211.md" index b36406b..69484cc 100644 --- "a/docs/02_ENV/ENV_04_\351\226\213\347\231\272\343\202\263\343\203\236\343\203\263\343\203\211.md" +++ "b/docs/02_ENV/ENV_04_\351\226\213\347\231\272\343\202\263\343\203\236\343\203\263\343\203\211.md" @@ -67,6 +67,7 @@ | 5-fold 交差検証 | `uv run tias-train cv --config <設定>` | 学習・評価して `runs/<実験ID>/` に書き出す.最後に実験 ID を出力する([SPEC_02](../04_SPEC/SPEC_02_学習・評価仕様.md)「交差検証の実行」) | | 手元での試運転 | `uv run tias-train cv --config configs/cv_synthetic.yaml` | 合成データ・小さなモデル・事前学習なし.CPU で十数秒 | | 色特徴分類器の交差検証 | `uv run tias-train color-cv --config <設定>` | CNN と同じ分割で色特徴+ロジスティック回帰/勾配ブースティングを評価する([SPEC_02](../04_SPEC/SPEC_02_学習・評価仕様.md)「色特徴分類器」).GPU 不要.手元の試運転は `configs/color_synthetic.yaml` | +| 判定モデルの書き出し | `uv run tias-train color-export --config <設定>` | 全データの患者単位 5-fold で C を選び,全データで学習した色特徴+ロジスティック回帰を `runs/<実験ID>/color_model.json` に書き出す(回収対象).GPU 不要.手元の試運転は `configs/export/color_synthetic.yaml` | | 回収した結果の集計 | `uv run python scripts/summarize_cv.py runs` | `runs/*_cv_*` を読み,モデル × 学習率 × 不均衡対策 × 条件ごとの表と,「除外 − 込み」「対策あり − 対策なし」の対応比較を Markdown で出す([TEST_03](../06_TEST/TEST_03_交差検証結果.md) の表) | 設定ファイル: @@ -80,6 +81,7 @@ | `configs/imbalance/*.yaml` | クラス不均衡の対策(`_cwinv`: 重み inverse・`_cwsqrt`: 重み sqrt_inverse・`_balanced`: バランス・サンプリング).元の設定(ConvNeXt-Tiny・ResNet-50 lr 1e-3 の各 6 本)から対策のキーだけを変えたもの | | `configs/hparam/*.yaml` | ハイパーパラメータの比較(`cv_hp__<変更>[_s43 \| _s44]`).基準の設定(ConvNeXt-Tiny・重み inverse/ResNet-50 lr 1e-3.ややあり除外)から 1 項目だけを変えたもの([TEST_06](../06_TEST/TEST_06_ハイパーパラメータの比較.md)) | | `configs/color/*.yaml` | 色特徴分類器(`cv_color__[_s43 \| _s44][_cwinv]`).分割・程度の設定は同名の CNN の設定と同一 | +| `configs/export/*.yaml` | 判定モデルの書き出し(`color_logistic_`.特徴・候補は `configs/color/` と同じ.`color_synthetic` は手元用) | **サーバでの実行例**: @@ -99,6 +101,26 @@ - 事前学習の重みは初回にサーバが Hugging Face からダウンロードする(`~/.cache/huggingface`) - ログに出る `lr_scheduler.step() before optimizer.step()` の警告は,混合精度で最初の数ステップの勾配がオーバーフローして捨てられたときに出るもので無害 +## セグメンテーションの評価 (Segmentation) + +DeepTIAS 2.7 の手順(検出 → ResUNet → 後処理)を Python(onnxruntime・OpenCV)で再現したものを,正解マスク付きデータで評価する.データ・モデルの場所は設定の `data.root`・`models.root`(環境変数 `TIAS_SEG_DATA_ROOT`・`TIAS_SEG_MODEL_ROOT` で上書き.`tias-remote run` が渡す `TIAS_DATA_ROOT` は使わない). + +| 目的 | コマンド | 備考 | +| --- | --- | --- | +| 合成データの生成 | `uv run python scripts/make_synthetic_segmentation.py` | 出力は `data/synthetic_seg/input/`(`train`・`test/cropped`・`test_extra/cropped`・`test/fullsize` の `sample/`・`label/`).作り直しは `--overwrite` | +| ダミーの ONNX の生成 | `uv run python scripts/make_dummy_seg_models.py` | 出力は `data/synthetic_seg/models/`(入出力の形だけを合わせた ResUNet・検出モデル.`onnx` は開発用の依存) | +| 評価 | `uv run tias-seg eval --config <設定>` | `runs/<実験ID>/` に `metrics.json`(集計値)・`figures/`(IoU のヒストグラム)を書く.手元の試運転は `configs/seg/eval_synthetic.yaml`(CPU で数秒) | +| 検出モデルの ONNX 変換 | `uv run --with tensorflow --with tf2onnx python scripts/convert_detection_to_onnx.py --pb --out ` | サーバで一時的な依存を入れて実行する(本体の依存には入れない).入出力の候補は `--list-nodes` | + +**サーバでの実行例**(モデルは `~/models/TongueSegmentation/` に `ResUNet.onnx`(と変換した `Detection_Normal.onnx`)を置く): + +```bash +uv run tias-remote run --name seg1 --cpu --server Newton -- tias-seg eval --config configs/seg/eval_newton.yaml +uv run tias-remote pull <実験ID> +``` + +- 検出モデルの ONNX が無ければ `detector` の方式を省いて(警告を出して)続ける + ## テスト (Test) | 目的 | コマンド | 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" index 82a45c8..35958e4 100644 --- "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" @@ -60,7 +60,7 @@ - **成果物**: 実験群(クラス不均衡対策・モデル比較・入力解像度・マスク外領域の埋め方)とその記録 - **完了条件**: 各要因の効果が**独立した実験として**切り分けて記録されている - **注意**: 共用サーバのため使用する GPU を明示的に指定する -- **進捗**: クラス不均衡対策([TEST_04](../06_TEST/TEST_04_クラス不均衡対策の実験.md))・色特徴分類器との比較([TEST_05](../06_TEST/TEST_05_色特徴分類器の実験.md))・ハイパーパラメータの比較([TEST_06](../06_TEST/TEST_06_ハイパーパラメータの比較.md))を実施 +- **進捗**: クラス不均衡対策([TEST_04](../06_TEST/TEST_04_クラス不均衡対策の実験.md))・色特徴分類器との比較([TEST_05](../06_TEST/TEST_05_色特徴分類器の実験.md))・ハイパーパラメータの比較([TEST_06](../06_TEST/TEST_06_ハイパーパラメータの比較.md))を実施.TIASjudge への組み込みに向けて,判定モデルの書き出しとセグメンテーションの再現を実施([TEST_07](../06_TEST/TEST_07_セグメンテーションの再現と判定モデルの書き出し.md)・[SPEC_04](../04_SPEC/SPEC_04_TIASjudge引き渡し仕様.md)) ## Step 8: 結論 (Conclusion) diff --git "a/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" "b/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" index d782fe5..074c0bb 100644 --- "a/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" +++ "b/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" @@ -20,6 +20,8 @@ │ ├── evaluation/ # 指標・混同行列・レポート生成 │ ├── baseline/ # ルールベース比較(扱いは未定) │ ├── remote/ # リモート実行(tar over ssh・tmux・回収) +│ ├── segmentation/ # 舌のセグメンテーション(DeepTIAS 2.7 の手順の Python 版) +│ ├── deploy/ # 配布用の参照実装(TIASjudge にコピーする単一ファイル.NumPy のみ) │ └── cli/ # エントリポイント ├── scripts/ # 合成データ生成・manifest 生成 ├── tests/ @@ -135,11 +137,12 @@ | 回収する(`runs/<実験ID>/` 配下) | 回収しない | | --- | --- | -| `config.yaml`・`history.csv`・`metrics.json`・`confusion_matrix.csv`・`log.txt`・`dataset_summary.json` | 上記以外のすべて(`split.json`・個票の予測・画像・manifest 等) | +| `config.yaml`・`history.csv`・`metrics.json`・`confusion_matrix.csv`・`log.txt`・`dataset_summary.json`・`color_model.json` | 上記以外のすべて(`split.json`・個票の予測(`predictions.csv`)・画像・manifest 等) | | `figures/*.png` | `figures/` 以外にある `*.png` | | `checkpoints/*.pt`(**明示的に指定したときのみ**) | 指定が無いときの `checkpoints/` | - ジョブのログ(`runs/_remote_logs/<ジョブ名>.log`)はファイルとしては回収せず,`tias-remote logs` で表示して確認する(ログに個人情報を出さない規則が前提.`.claude/rules/coding-style.md`) +- `color_model.json` は `tias-train color-export` が書き出す判定モデル(特徴の定義・標準化の平均と尺度・係数・切片・クラス・学習条件)で,患者 ID・ファイル名・画像単位の値を含まない - データディレクトリ(`~/data/TIASJudgeModel/`)からは何も回収しない - 個票(画像ごとの予測)が必要な分析はサーバ上で行い,集計結果だけを上記の名前で書き出して持ち帰る @@ -150,6 +153,8 @@ | `tias-dataset build-manifest` | `images/` を走査して `manifest.csv` を書き出す(既存があれば `--overwrite` が必要) | サーバ | | `tias-train cv --config PATH` | 5-fold 交差検証で学習・評価し,`runs/<実験ID>/` に書き出す([SPEC_02「交差検証の実行」](SPEC_02_学習・評価仕様.md)).最後に実験 ID を出力する | サーバ(GPU) | | `tias-train color-cv --config PATH` | 色特徴分類器([SPEC_02「色特徴分類器」](SPEC_02_学習・評価仕様.md))を同じ分割で交差検証し,同じ形式で書き出す(学習曲線・重みは出さない).最後に実験 ID を出力する | サーバ(CPU) | +| `tias-train color-export --config PATH` | 色特徴+ロジスティック回帰の最終モデルを全データ(`data.severities` 適用後)で学習し,TIASjudge 用の `color_model.json` と C の選択の記録(`metrics.json`)を書き出す.NumPy だけの参照実装は `src/tias_judge_model/deploy/color_model.py` | サーバ(CPU) | +| `tias-seg eval --config PATH` | Python 版セグメンテーション(DeepTIAS 2.7 の再現.onnxruntime・OpenCV)を正解マスク付きデータで評価し,IoU・Dice の集計値を `metrics.json` に書き出す(画像ごとの値はサーバ上の `predictions.csv`) | サーバ(CPU) | | `tias-dataset survey` | データセットを集計して `runs/<実験ID>/` に集計値だけを書き出す(下記) | サーバ(結果の要約のみ手元へ) | | 学習 | 設定 YAML を受け取って学習する | サーバ | | 評価 | 学習済み run の評価・レポート生成 | サーバ | diff --git "a/docs/04_SPEC/SPEC_02_\345\255\246\347\277\222\343\203\273\350\251\225\344\276\241\344\273\225\346\247\230.md" "b/docs/04_SPEC/SPEC_02_\345\255\246\347\277\222\343\203\273\350\251\225\344\276\241\344\273\225\346\247\230.md" index 21952d1..44910ed 100644 --- "a/docs/04_SPEC/SPEC_02_\345\255\246\347\277\222\343\203\273\350\251\225\344\276\241\344\273\225\346\247\230.md" +++ "b/docs/04_SPEC/SPEC_02_\345\255\246\347\277\222\343\203\273\350\251\225\344\276\241\344\273\225\346\247\230.md" @@ -224,6 +224,16 @@ - 学習用データに無いクラスの予測確率は 0 とし,5 クラスの形を保つ - 出力は CNN と同じ名前・同じ形式(「出力」).ただし `history.csv`・学習曲線・重み(`checkpoints/`)は出さない.`metrics.json` の `environment.model_name` は `color_logistic`・`color_hist_gb`,最良エポックは `null` で,fold ごとの採用した候補(`selected_params`)と候補ごとの検証 macro F1(`candidates`),特徴名(`color_classifier`),特徴抽出の所要秒数(`timing.feature_extraction_seconds`)を加える +### 判定モデルの書き出し (Model Export) + +TIASjudge に渡す最終モデルは `tias-train color-export` で作る(引き渡しの手順は [SPEC_04](SPEC_04_TIASjudge引き渡し仕様.md).結果は [TEST_07](../06_TEST/TEST_07_セグメンテーションの再現と判定モデルの書き出し.md)). + +- 当面は `color.classifier: logistic` のみ(`hist_gb` は停止する) +- C の選び方: 全データで交差検証と同じ患者単位 5-fold(`split.seed`)を組み,学習側と held_out の両方に `data.severities` を適用して,候補ごとの held_out macro F1 の fold 平均が最良の C を採る(厳密な改善で更新し,同点なら先の候補).この値は C を選ぶためのもので,性能の評価には交差検証(`color-cv`)の値を使う +- 最終モデルは `data.severities` 適用後の全データで学習する(乱数は `split.seed`,`train.class_weighting` は sample_weight).学習データに 1 枚も無いクラスがあれば停止する +- 出力は `color_model.json`(回収対象.患者 ID・ファイル名・画像単位の値を含まない)と,C の選択の記録(`metrics.json` の `c_selection`) +- NumPy だけの参照実装 `src/tias_judge_model/deploy/color_model.py` は,学習側と同じ特徴(ビット一致)と予測確率(差 1e-12 以下)を返す.本パッケージの他のモジュールに依存させない(TIASjudge にそのままコピーして使うため) + ## 評価指標 (Metrics) | 指標 | 役割 | diff --git "a/docs/04_SPEC/SPEC_04_TIASjudge\345\274\225\343\201\215\346\270\241\343\201\227\344\273\225\346\247\230.md" "b/docs/04_SPEC/SPEC_04_TIASjudge\345\274\225\343\201\215\346\270\241\343\201\227\344\273\225\346\247\230.md" new file mode 100644 index 0000000..4ee2596 --- /dev/null +++ "b/docs/04_SPEC/SPEC_04_TIASjudge\345\274\225\343\201\215\346\270\241\343\201\227\344\273\225\346\247\230.md" @@ -0,0 +1,95 @@ +# TIASjudge 引き渡し仕様 (TIASjudge Handoff) + +TIASjudge に「撮影画像 → 舌のセグメンテーション → 舌色 5 分類」の判定モードを組み込むために,本リポジトリが引き渡すものと,TIASjudge 側で守るべき処理の手順を定める(本リポジトリの Issue #9.TIASjudge 側は TIASjudge の Issue #13).検証結果は [TEST_07](../06_TEST/TEST_07_セグメンテーションの再現と判定モデルの書き出し.md). + +## 全体の流れ (Pipeline) + +```text +撮影画像(TIAS・色補正なし.cv2.imread の BGR) + → 1. 舌の検出(Detection_Normal.onnx): 舌の枠 + → 2. 舌の分割(ResUNet.onnx): 枠の中の舌マスク → 元の画像サイズのマスク + → 3. マスク外を黒 (0,0,0) にした RGB 画像 + → 4. 色特徴(107 次元)→ ロジスティック回帰(color_model.json): 5 クラスの確率 +``` + +1〜2 は DeepTIAS 2.7 の「画像のみ」モードの手順を再現したもの(DeepTIAS の実行ファイルの逆コンパイルで確認).3 は DeepTIAS の `2-segmentation/5-extraction` と同じ形で,判定モデルの学習データもこの形である. + +## 入力の前提 (Input Requirements) + +- **TIAS の機材で撮影した,色補正をしていない画像**に限る.判定モデルは補正なしの RGB で学習しているため,TIASjudge の変換行列(ROI 診断で使う多項式変換)を通した画像や,他の機材で撮った画像には使えない.画面にこの前提を明示する +- 既存のルールベース診断(ROI クリック → 変換行列 → SVM)は置き換えず,**別の判定モード**として追加する(TIASjudge の「診断結果は旧実装と一致させる」ルールのため) + +## 引き渡すもの (Deliverables) + +| もの | 内容 | 入手先 | +| --- | --- | --- | +| `color_model.json` | 判定モデル(特徴の定義・標準化の値・係数・切片・クラス・学習条件・来歴).約 28 KB | `tias-train color-export` の出力(サーバで実行し `tias-remote pull` で回収.[ENV_04](../02_ENV/ENV_04_開発コマンド.md)) | +| `deploy/color_model.py` | 判定モデルの参照実装(NumPy と標準ライブラリのみ.単一ファイル) | `src/tias_judge_model/deploy/color_model.py` をそのままコピーする | +| `segmentation/`(`detection.py`・`resunet.py`・`pipeline.py`) | セグメンテーションの参照実装(NumPy・OpenCV・onnxruntime のみ.パッケージ内では相互にだけ依存) | `src/tias_judge_model/segmentation/` から 3 ファイルをコピーし,import 文のパッケージ名を合わせる | +| `ResUNet.onnx` | 舌の分割モデル(約 71 MB.入力 `input.1` (1,3,256,256) float32,出力 (1,2,256,256)) | DeepTIAS 2.7 の `models/` に同梱のもの(開発元のファイルとハッシュが一致) | +| `Detection_Normal.onnx` | 舌の検出モデル(約 12 MB) | DeepTIAS 2.7 の `models/Detection_Normal.pb` を `scripts/convert_detection_to_onnx.py` で変換したもの(サーバの `~/models/TongueSegmentation/` にある) | + +- モデルファイル(`.onnx`・`color_model.json`)はリポジトリに入れない(臨床データから学習したもの.配布物への同梱の可否は「未決事項」) +- TIASjudge 側に必要な依存: `numpy`・`opencv-python`(既存)・`onnxruntime`(追加).TensorFlow・PyTorch・scikit-learn は不要 + +## 1. 舌の検出 (Detection) + +`segmentation/detection.py` の `OnnxDetector.detect(bgr)`. + +- 入力: 画像を **RGB に並べ替えた** uint8 の `(1, H, W, 3)`(入力名 `image_tensor:0`.TF の物体検出 API の既定.DeepTIAS も Bitmap 経由で RGB を渡している).画像サイズは任意 +- 出力: `detection_boxes`(正規化座標 [ymin, xmin, ymax, xmax])・`detection_scores` +- スコアが **0.5 を超える**枠のうち最高の 1 つを採用する(0.5 ちょうどは不採用.同点は先の枠) +- 正規化座標に画像の高さ・幅を掛けて **切り捨て**で画素の枠にし,画像の内側に収める +- 枠が無い(0.5 を超えない)場合は「舌を検出できなかった」として判定を行わず,利用者に撮り直し等を促す + +## 2. 舌の分割 (Segmentation) + +`segmentation/pipeline.py` の `segment_in_rect(bgr, rect, runner, params)`(`runner` は `resunet.py` の `ResUNetRunner`). + +1. 枠で切り出した画像を `cv2.resize`(線形補間)で 256×256 にする +2. **BGR のまま** 255 で割り,CHW の float32 にして ResUNet に入れる(学習時の `cv2.imread` の BGR /255 と同じ) +3. 出力の**チャンネル 0**(舌)にシグモイドを掛け,×255 を切り捨てて uint8 にする +4. 大津の方法で 2 値化する +5. 輪郭(Canny → 外側の輪郭)が 0 個または 2 個を超える場合だけ,面積 500 画素未満の前景の島を除き,次に同じく小さい背景の穴を埋める(8 近傍) +6. 枠のサイズに線形補間で戻し,127 を超える画素を舌とする +7. 元の画像サイズのマスクに配置する(枠の外は背景) + +設定値(`SegmentationParams`): `foreground_channel=0`・`min_area=500`・`mask_threshold=127`.[TEST_07](../06_TEST/TEST_07_セグメンテーションの再現と判定モデルの書き出し.md) の評価(IoU 0.958)はこの値で行った. + +- マスクが空(舌の画素が 0)なら判定を行わず,分割に失敗したことを示す +- 結果の確認のため,マスクの輪郭を撮影画像に重ねて表示する(TIASjudge の Issue #13) + +## 3. マスクの適用 (Masking) + +- 撮影画像(BGR)を **RGB に並べ替え**,マスクの外を (0, 0, 0) にする +- 判定モデルは「RGB がすべて 0 の画素」を背景とみなす.舌の中にもともと (0, 0, 0) の画素があれば背景扱いになるが,学習データも同じ扱いなので問題ない + +## 4. 舌色の判定 (Classification) + +`deploy/color_model.py`. + +```python +model = load_color_model("color_model.json") +probs = predict_proba(rgb_masked, model) # (5,) の確率.並びは model の classes の順 +``` + +- 長辺が `max_side`(512)を超える画像は最近傍法で縮小してから特徴を取る(学習時と同じ.`predict_proba` の中で行う) +- クラスの順は `color_model.json` の `classes`(0 淡白・1 淡紅・2 紅・3 暗赤紅・4 紫)に従う.最大の確率のクラスを判定結果とし,5 クラスの確率も表示する +- 参照実装は学習側と同じ予測を返す(特徴はビット一致,確率の差 1e-12 以下.本リポジトリのテストで確認).TIASjudge 側では,同じ画像に対して参照実装と同じ確率が出ることをテストで確かめる + +## 性能の目安 (Expected Performance) + +| 段階 | 指標 | 値 | 出典 | +| --- | --- | --- | --- | +| セグメンテーション | 撮影画像からの舌の IoU(検出+ResUNet.170 枚) | 平均 0.958・最小 0.864 | TEST_07 | +| 舌色の判定 | 「あり」と淡紅での macro F1(5-fold × 3 seed) | 0.630(ややあり込み)〜0.644(除外) | [TEST_05](../06_TEST/TEST_05_色特徴分類器の実験.md) | +| 舌色の判定 | 同・正解率 | 0.72〜0.75 | TEST_05 | + +判定の性能は医師のラベルとの一致がまだ 7 割程度であり,**当面は参考表示とする**.ルールベース診断との比較([PLAN_02](../03_PLAN/PLAN_02_開発ステップ.md) Step 6)の結果を見て扱いを決める. + +## 未決事項 (Open Items) + +- **どちらの判定モデルを渡すか**: ややあり込み(`configs/export/color_logistic_with_mild.yaml`)か除外(`color_logistic_without_mild.yaml`)か.除外のほうが「あり」と淡紅の成績がやや良く,ややありの画像を淡紅と判定しやすい(約 55 %.込みは約 33 %.TEST_05).臨床で「ややあり」を淡紅と判断することが多い点とは除外のほうが整合する +- **DeepTIAS のモデルを TIASjudge の配布物に含めてよいか**(権利) +- 配布サイズ(`ResUNet.onnx` 約 71 MB で単体 exe が大きくなる) +- 学習データ(舌色データセット)のマスクと,本実装のマスクがビット単位で一致するかは未確認(元の撮影画像が無いため.TEST_07「注意」) diff --git "a/docs/06_TEST/TEST_07_\343\202\273\343\202\260\343\203\241\343\203\263\343\203\206\343\203\274\343\202\267\343\203\247\343\203\263\343\201\256\345\206\215\347\217\276\343\201\250\345\210\244\345\256\232\343\203\242\343\203\207\343\203\253\343\201\256\346\233\270\343\201\215\345\207\272\343\201\227.md" "b/docs/06_TEST/TEST_07_\343\202\273\343\202\260\343\203\241\343\203\263\343\203\206\343\203\274\343\202\267\343\203\247\343\203\263\343\201\256\345\206\215\347\217\276\343\201\250\345\210\244\345\256\232\343\203\242\343\203\207\343\203\253\343\201\256\346\233\270\343\201\215\345\207\272\343\201\227.md" new file mode 100644 index 0000000..70d90da --- /dev/null +++ "b/docs/06_TEST/TEST_07_\343\202\273\343\202\260\343\203\241\343\203\263\343\203\206\343\203\274\343\202\267\343\203\247\343\203\263\343\201\256\345\206\215\347\217\276\343\201\250\345\210\244\345\256\232\343\203\242\343\203\207\343\203\253\343\201\256\346\233\270\343\201\215\345\207\272\343\201\227.md" @@ -0,0 +1,74 @@ +# セグメンテーションの再現と判定モデルの書き出し (Segmentation Reproduction & Model Export) + +TIASjudge への組み込み(Issue #9)に向けて,(1) DeepTIAS 2.7 の舌セグメンテーション(検出+ResUNet)を Python で再現して正解マスク付きデータで IoU を測り,(2) 色特徴+ロジスティック回帰の最終モデルを全データで学習して書き出した結果.引き渡し仕様は [SPEC_04](../04_SPEC/SPEC_04_TIASjudge引き渡し仕様.md). + +## セグメンテーションの評価 (Segmentation Evaluation) + +| 項目 | 内容 | +| --- | --- | +| 実行日時 | 2026-09-28 23:28〜23:34(約 5 分) | +| 実行場所 | Newton の CPU(`tias-remote run --cpu`) | +| コマンド | `tias-seg eval --config configs/seg/eval_newton.yaml` | +| データ | ResUNet の開発時の正解マスク付きデータ(研究室 PC から両サーバの `~/data/TongueSegmentation/input/` に配置.ENV_03) | +| モデル | DeepTIAS 2.7 同梱の `ResUNet.onnx`(開発元フォルダの ONNX とハッシュが一致)と,`Detection_Normal.pb` を `scripts/convert_detection_to_onnx.py`(tf2onnx 1.17・opset 13)で変換した `Detection_Normal.onnx` | +| コード | `405ab01-dirty`(本ブランチの実装を未コミットのまま使用) | + +評価セットは,開発時に切り出し済みの画像(`cropped`.学習時と同じ入力条件)と,撮影画像そのもの(`fullsize`).`fullsize` では舌の枠の決め方を 3 通り比べた. + +| 評価セット | 枚数 | 枠の決め方 | IoU 平均 | 中央値 | 10 % 点 | 最小 | IoU 0.8 未満 | 1 枚の秒数 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| test(切り出し済み) | 170 | 画像全体 | **0.959** | 0.966 | 0.926 | 0.880 | 0 | 0.25 | +| test_extra(切り出し済み) | 180 | 画像全体 | 0.956 | 0.965 | 0.916 | 0.858 | 0 | 0.21 | +| train(切り出し済み.学習に使ったもの) | 382 | 画像全体 | 0.981 | 0.986 | 0.965 | 0.850 | 0 | 0.23 | +| test(撮影画像) | 170 | **検出モデル** | **0.958** | 0.967 | 0.918 | 0.864 | 0 | 0.39 | +| test(撮影画像) | 170 | 正解マスクの外接矩形 | 0.947 | 0.951 | 0.914 | 0.860 | 0 | 0.23 | +| test(撮影画像) | 170 | 検出なし(画像全体) | 0.458 | 0.444 | 0.238 | 0.103 | 162 | 0.24 | + +- IoU は舌(前景)の IoU.正解マスク(JPEG)は 128 以上を前景とした.小領域除去(後処理)の有無で平均は変わらなかった(後処理が働いたのは検出なしで 42 枚,それ以外は 0〜2 枚) +- 出力チャンネル 0 を舌として使うと IoU 0.96,チャンネル 1 を使うと 0.003〜0.04 で,**チャンネル 0 が舌**であることを確認した(DeepTIAS の C# の読み取りと一致) +- 画像ごとの IoU はサーバ上の `predictions.csv` にだけ置き,回収していない(回収したのは集計値と IoU のヒストグラムのみ) + +### 分かったこと (Findings) + +1. **DeepTIAS の手順を Python で再現できた**.撮影画像から検出モデル → ResUNet の流れで IoU 0.958(最小 0.864).DeepTIAS の開発時の報告値(ResUNet のモデル名に付いた 0.9518)と同じ水準で,切り出し済みの評価セット(0.959)とも差が無い +2. **検出は必須**.検出を省いて撮影画像全体に ResUNet を掛けると IoU 0.46 に落ちる(ResUNet は切り出し後の画像で学習されているため) +3. 変換した検出モデルは 170 枚すべてで舌を見つけた(スコア 0.5 超).正解マスクの外接矩形よりも IoU がやや高いのは,検出の枠に余白があり,学習時の切り出し方に近いためと考えられる +4. 1 枚あたり約 0.4 秒(Newton の CPU.検出+ResUNet+後処理).TIASjudge の手元 PC でも実用的な速さと見込まれる + +### 注意 (Caveats) + +- DeepTIAS の細部で確かめられていない値がある(検出モデルへ渡す色の順,枠のサイズに戻したマスクの 2 値化のしきい値,輪郭の数え方の Canny のしきい値など).いずれも設定で変えられ,IoU には大きく効かないと見られるが,学習データのマスクとビット単位で一致するかは確かめていない +- **学習データ(舌色データセット)のマスクとの一致は未確認**.舌色データセットはマスク適用後の画像しか無く,元の撮影画像が無いため.元画像が残っていれば,同じ画像で DeepTIAS の出力と比べられる +- OpenCV のバージョン(DeepTIAS は 3.x,本実装は 5.0)による resize・大津の 2 値化の微小な差はありうる + +## 判定モデルの書き出し (Model Export) + +| 項目 | 内容 | +| --- | --- | +| 実行日時 | 2026-09-28 23:26〜23:28 | +| 実行場所 | Archimedes の CPU | +| コマンド | `tias-train color-export --config configs/export/color_logistic_.yaml` | +| 分類器 | 色特徴(107 次元)+ロジスティック回帰(class_weighting なし).C は全データの患者単位 5-fold(seed 42)の held_out macro F1 の平均で選ぶ | + +| 設定 | 学習枚数 | 採用した C | C の候補ごとの fold 平均 macro F1(0.01 / 0.1 / 1 / 10 / 100) | +| --- | --- | --- | --- | +| ややあり除外 | 754(淡白 53・淡紅 337・紅 61・暗赤紅 247・紫 56) | 0.1 | 0.636 / **0.669** / 0.645 / 0.602 / 0.546 | +| ややあり込み | 1085(淡白 104・淡紅 337・紅 119・暗赤紅 380・紫 145) | 0.1 | 0.575 / **0.580** / 0.553 / 0.528 / 0.491 | + +- ここでの macro F1 は C を選ぶための値で,held_out にも `data.severities` を適用している(除外の設定では held_out にもややありが無い).評価指標としては TEST_05 の値(common macro F1 0.630〜0.644)を使う +- 書き出した `color_model.json`(約 28 KB)には,特徴の定義・標準化の値・係数・切片・クラス・学習条件・来歴が入り,患者 ID・ファイル名・画像単位の値は含まない(テストで確認) +- NumPy だけの参照実装 `src/tias_judge_model/deploy/color_model.py` で読み込み,学習側と同じ予測になることを確かめた(特徴はビット一致,予測確率の差は 1e-12 以下.合成データでのテスト) + +## 次の一手 (Next Steps) + +- TIASjudge 側(TIASjudge の Issue #13)で,SPEC_04 に従って判定モードを組み込む +- どちらの判定モデル(ややあり込み/除外)を使うかを決める(SPEC_04「未決事項」) +- 元の撮影画像が残っていれば,舌色データセットのマスクとの一致を確かめる + +## 実験 ID (Run IDs) + +手元の `runs/` に回収済み(`.gitignore` 対象). + +```bash +ls -d runs/20260928-232850_eval_newton_405ab01-dirty runs/*_color_logistic_*_405ab01-dirty +``` diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index d731f5a..0d7261d 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -130,3 +130,16 @@ - 失敗パターン: seed 1 つ(5 fold)の比較は fold 間のばらつき(標準偏差 0.03〜0.10)に埋もれて当てにならない → 今後の比較は最低 3 seed で行う - 示唆: 性能の頭打ち(0.63〜0.65)はハイパーパラメータではなくデータ量・ラベルのあいまいさによる.伸ばすなら CNN と色特徴の併用など情報を増やす方向 - 対応済み: 本実験と TEST_04 の重み(計約 27 GB)が Archimedes の共用ディスク(残り 35 GB・98%)を圧迫していたため,ユーザーの判断で削除した(空き 52 GB に回復).重みを保存するかを設定で選べるようにする対応は未実施 + +## TIASjudge への組み込み準備(Issue #9) + +- [x] **判定モデルの書き出し(`tias-train color-export`・NumPy だけの参照実装)と,DeepTIAS 2.7 の舌セグメンテーションの Python 再現(`tias-seg eval`)を実装し,サーバで検証**: TIASjudge で「撮影画像 → セグメンテーション → 舌色判定」を行いたいというユーザーの要望から.TIASjudge 側の組み込みは TIASjudge の Issue #13.結果は [TEST_07](06_TEST/TEST_07_セグメンテーションの再現と判定モデルの書き出し.md),引き渡しの手順は [SPEC_04](04_SPEC/SPEC_04_TIASjudge引き渡し仕様.md). + - 調査: 学習データの切り出しは DeepTIAS 2.7.ソースが無かったため実行ファイルを逆コンパイルし(ilspycmd.作業用の一時フォルダ),処理の順序を確認した.**切り出し画像はどちらのモードでも色補正されていない**(色補正は検出・分割に使う画像にだけかかる)→ 判定モデルは TIAS の補正なし画像専用 + - 調査: ResUNet の学習コードと正解マスク付きデータは別フォルダ(OneDrive)にあり,同梱の ONNX とハッシュが一致した.データは研究室 PC に移して手元から削除し,サーバへは研究室 PC から直送した(ENV_03「セグメンテーション用データの配置」) + - 設計判断 ①: 参照実装は TIASjudge にコピーして使う前提で,本パッケージに依存させない単一ファイル(`deploy/color_model.py`)にした.学習側との一致(特徴のビット一致・確率の差 1e-12 以下)をテストで保証する + - 設計判断 ②: 検出モデル(TF1 の .pb)は tf2onnx で ONNX に変換し,TIASjudge が onnxruntime だけで動くようにした.変換は一時的な依存で行い,本体の依存に TensorFlow を入れない + - 設計判断 ③: セグメンテーション用の環境変数は `TIAS_SEG_DATA_ROOT`・`TIAS_SEG_MODEL_ROOT` と別名にした(`tias-remote run` が舌色データを指す `TIAS_DATA_ROOT` を強制的に渡すため) + - 結果: 撮影画像から検出+ResUNet で IoU 0.958(検出の失敗 0/170).検出を省くと 0.46 で,検出は必須.出力チャンネル 0 が舌であることを確認.判定モデルは込み・除外とも C=0.1 + - 失敗パターン: 研究室 PC 上の cmd のパイプ(tar | ssh)が止まり,Windows の tar で作った tar は権限のため展開に失敗した → tar ファイルを scp で送り `--delay-directory-restore` で展開.展開失敗時のエラーにファイル名が大量に出たので,今後はエラー出力を件数で確かめる + - 失敗パターン: OneDrive で大量に削除すると「大量削除の検出」で復元される → 削除後に OneDrive の確認に「削除する」と答える必要がある + - 未確認: 舌色データセットのマスクとのビット単位の一致(元の撮影画像が無いため).どちらの判定モデルを渡すか(SPEC_04「未決事項」)