"""`tias-seg` コマンド: Python 版セグメンテーション(DeepTIAS 2.7 の再現)の評価.
サブコマンド:
- `eval --config PATH [--out-root runs]`: 正解マスク付きデータで ResUNet(+検出)の IoU・Dice を
測り,`runs/<実験ID>/` に書き出す.最後に実験 ID を標準出力へ出す(`tias-remote pull` 用)
出力(SPEC_01「実験の出力」):
- 回収対象: `config.yaml`・`metrics.json`(集計値のみ)・`log.txt`・`figures/*.png`
(IoU のヒストグラム.画像は描かない)
- サーバ上にだけ置く: `predictions.csv`(画像ごとの IoU.ファイル名の stem を含む)
実データを読むためサーバで実行する(手元では合成データの設定 `configs/seg/eval_synthetic.yaml`).
"""
from __future__ import annotations
import argparse
import logging
import os
import sys
import time
from collections.abc import Sequence
from dataclasses import asdict
from datetime import datetime
from pathlib import Path
import cv2
import onnxruntime
from pydantic import ValidationError
from tias_judge_model.cli.dataset import (
CONFIG_FILENAME,
FIGURES_DIRNAME,
configure_logging,
make_run_id,
resolve_git_commit,
resolved_config_document,
run_in_new_run_dir,
write_csv,
write_json,
write_yaml,
)
from tias_judge_model.evaluation.segmentation_eval import (
ious_by_method,
log_summary,
run_segmentation_eval,
summarize_segmentation,
)
from tias_judge_model.evaluation.segmentation_figures import (
iou_histogram_filename,
plot_iou_histograms,
)
from tias_judge_model.schema import SegEvalConfig, load_seg_eval_config
logger = logging.getLogger(__name__)
METRICS_FILENAME = "metrics.json"
PREDICTIONS_FILENAME = "predictions.csv"
_EPILOG = """\
使用例(サーバ上で実行する.手元では configs/seg/eval_synthetic.yaml で合成データを使う):
uv run tias-seg eval --config configs/seg/eval_synthetic.yaml
uv run tias-remote run --name seg1 --cpu -- tias-seg eval --config configs/seg/eval_newton.yaml
環境変数:
TIAS_SEG_DATA_ROOT data.root を上書き(tias-remote run が渡す TIAS_DATA_ROOT は使わない)
TIAS_SEG_MODEL_ROOT models.root を上書き
TIAS_GIT_COMMIT 実験 ID に入れる git の短縮ハッシュ(tias-remote run が渡す.無ければ nogit)
"""
def _cmd_eval(args: argparse.Namespace) -> int:
started = time.monotonic()
config = load_seg_eval_config(args.config)
git_commit = resolve_git_commit(os.environ)
run_id = make_run_id(datetime.now(), args.config.stem, git_commit)
return run_in_new_run_dir(
args.out_root,
run_id,
lambda run_dir: _run_eval(
config, run_dir, run_id=run_id, git_commit=git_commit, started=started
),
)
def _run_eval(
config: SegEvalConfig, run_dir: Path, *, run_id: str, git_commit: str, started: float
) -> None:
logger.info("セグメンテーションの評価の実験 ID: %s", run_id)
write_yaml(
run_dir / CONFIG_FILENAME,
resolved_config_document(config, run_id=run_id, git_commit=git_commit, command="seg eval"),
)
result = run_segmentation_eval(config)
# 画像ごとの値(個票.ファイル名の stem を含む.回収しない).
write_csv(run_dir / PREDICTIONS_FILENAME, [asdict(s) for s in result.scores])
sets = summarize_segmentation(result, config)
log_summary(sets)
for set_name in result.methods:
plot_iou_histograms(
ious_by_method(result, set_name),
f"{set_name}: IoU with postprocess",
run_dir / FIGURES_DIRNAME / iou_histogram_filename(set_name),
)
metrics = {
"run_id": run_id,
"git_commit": git_commit,
"detector": {"used": result.detector_used, "note": result.detector_note},
"resunet": config.resunet.model_dump(mode="json"),
"postprocess": config.postprocess.model_dump(mode="json"),
"label_threshold": config.data.label_threshold,
"sets": sets,
"timing": {
"total_seconds": round(time.monotonic() - started, 2),
"loading_seconds": round(result.loading_seconds, 2),
},
"environment": {
"onnxruntime_version": onnxruntime.__version__,
"opencv_version": cv2.__version__,
},
}
write_json(run_dir / METRICS_FILENAME, metrics)
logger.info(
"出力先: %s(全体の所要 %.1f 秒・うち画像の読み込み %.1f 秒)",
run_dir,
metrics["timing"]["total_seconds"],
result.loading_seconds,
)
def _parse_args(argv: Sequence[str] | None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="tias-seg",
description="Python 版セグメンテーション(DeepTIAS 2.7 の再現)を評価する",
epilog=_EPILOG,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
sub = parser.add_subparsers(dest="subcommand", required=True)
p_eval = sub.add_parser(
"eval",
help="正解マスク付きデータで IoU・Dice を測って runs/<実験ID>/ に書き出す",
description=(
"設定の評価セットごとに ResUNet(切り出し済みは画像全体,撮影画像は検出なし・正解の"
"外接矩形・検出モデル)を掛け,前景の IoU・Dice を後処理の有無で集計する."
"最後に実験 ID を標準出力へ出す."
),
)
p_eval.add_argument("--config", type=Path, required=True, help="評価の設定 YAML.")
p_eval.add_argument(
"--out-root",
type=Path,
default=Path("runs"),
help="実験ディレクトリの親(既定: runs).",
)
return parser.parse_args(argv)
def main(argv: Sequence[str] | None = None) -> int:
"""tias-seg コマンドのメインエントリポイント.
Args:
argv: 引数リスト.None のとき sys.argv[1:] を使用.
Returns:
終了コード(成功 0・入力の問題 1).
"""
args = _parse_args(argv)
configure_logging()
try:
return _cmd_eval(args)
except ValidationError as exc:
logger.error("設定が不正である:\n%s", exc)
return 1
except (ValueError, OSError) as exc:
logger.error("%s", exc)
return 1
if __name__ == "__main__":
sys.exit(main())