# セグメンテーションの再現と判定モデルの書き出し (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_<with_mild \| without_mild>.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 に従って判定モードを組み込む
- TIASjudge にはややあり除外のモデルを渡すと決めた（2026-09-29．SPEC_04「渡す判定モデル」）
- 元の撮影画像が残っていれば，舌色データセットのマスクとの一致を確かめる

## 実験 ID (Run IDs)

手元の `runs/` に回収済み（`.gitignore` 対象）．

```bash
ls -d runs/20260928-232850_eval_newton_405ab01-dirty runs/*_color_logistic_*_405ab01-dirty
```
