"""セグメンテーションの指標(前景の IoU・Dice)と画像ごとの値の集計.
集計値(平均・中央値・分位点・最小・しきい値未満の件数)だけを metrics.json に書くための
純関数.画像ごとの値はサーバ上の個票(predictions.csv)にだけ書く.
"""
from __future__ import annotations
from collections.abc import Sequence
from typing import Any
import numpy as np
def iou_and_dice(pred: np.ndarray, target: np.ndarray) -> tuple[float, float]:
"""前景の IoU と Dice を求める.
両方とも前景が無い場合は一致とみなして 1.0 とする.
Args:
pred: `(H, W)` の bool(予測の前景).
target: `(H, W)` の bool(正解の前景).
Returns:
(IoU, Dice).
Raises:
ValueError: 形状が一致しない場合.
"""
pred = np.asarray(pred, dtype=bool)
target = np.asarray(target, dtype=bool)
if pred.shape != target.shape:
raise ValueError(f"予測と正解の形状が一致しない({pred.shape} と {target.shape})")
intersection = int(np.count_nonzero(pred & target))
union = int(np.count_nonzero(pred | target))
total = int(np.count_nonzero(pred)) + int(np.count_nonzero(target))
if union == 0:
return 1.0, 1.0
return intersection / union, 2.0 * intersection / total
def summarize_scores(values: Sequence[float], threshold: float) -> dict[str, Any]:
"""画像ごとの指標を集計する.
Args:
values: 画像ごとの値.
threshold: この値未満の件数を数える.
Returns:
`n`・`mean`・`median`・`p10`・`p25`・`min`・`max`・`n_below`・`below_threshold`.
値が 0 件なら `n` が 0 で他は None.
"""
arr = np.asarray(values, dtype=np.float64)
if arr.size == 0:
return {
"n": 0,
"mean": None,
"median": None,
"p10": None,
"p25": None,
"min": None,
"max": None,
"n_below": None,
"below_threshold": threshold,
}
return {
"n": int(arr.size),
"mean": float(arr.mean()),
"median": float(np.median(arr)),
"p10": float(np.percentile(arr, 10)),
"p25": float(np.percentile(arr, 25)),
"min": float(arr.min()),
"max": float(arr.max()),
"n_below": int(np.count_nonzero(arr < threshold)),
"below_threshold": threshold,
}