Newer
Older
SmTIAS-Evaluation / src / export / exporter.py
"""CSV・PNG 出力モジュール."""

import csv
from pathlib import Path

import numpy as np

SUMMARY_FIELDNAMES = ["image_name", "mean", "std", "cov", "max_min_ratio", "max", "min"]
SPATIAL_FIELDNAMES = [
    "image_name",
    "center_mean",
    "middle_mean",
    "periphery_mean",
    "center_periphery_ratio",
    "gradient_magnitude",
]


def _ensure_parent_dir(output_path: str) -> None:
    """出力ファイルの親ディレクトリを作成する.

    Args:
        output_path: 出力先ファイルパス.
    """
    Path(output_path).parent.mkdir(parents=True, exist_ok=True)


def ensure_output_dirs(base_path: str) -> None:
    """output/figures/, output/results/ ディレクトリを作成する.

    Args:
        base_path: 出力ルートディレクトリのパス.
    """
    base = Path(base_path)
    (base / "figures").mkdir(parents=True, exist_ok=True)
    (base / "results").mkdir(parents=True, exist_ok=True)
    print(f"出力ディレクトリを確認しました: {base}")


def export_results(results: dict, output_path: str) -> None:
    """均一性指標を CSV ファイルに出力する.

    CSV の形式は 1 行目がヘッダー,2 行目が値.

    Args:
        results: 均一性指標の辞書(calc_uniformity の戻り値).
        output_path: 出力先ファイルパス(CSV).
    """
    _ensure_parent_dir(output_path)

    with Path(output_path).open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=list(results.keys()))
        writer.writeheader()
        writer.writerow(results)

    print(f"結果を保存しました: {output_path}")


def export_summary(all_results: list[dict], output_path: str) -> None:
    """バッチ解析の集約結果を CSV ファイルに出力する.

    CSV の形式は 1 行目がヘッダー,2 行目以降が各画像の値.

    Args:
        all_results: 均一性指標の辞書リスト.各要素は
            {"image_name": "xxx", "mean": ..., "std": ..., "cov": ...,
             "max_min_ratio": ..., "max": ..., "min": ...} の形式.
        output_path: 出力先ファイルパス(CSV).
    """
    _ensure_parent_dir(output_path)

    with Path(output_path).open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=SUMMARY_FIELDNAMES, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(all_results)

    print(f"まとめ CSV を保存しました: {output_path}")


def export_spatial_summary(all_results: list[dict], output_path: str) -> None:
    """空間解析の集約結果を CSV ファイルに出力する.

    CSV の形式は 1 行目がヘッダー,2 行目以降が各画像の値.

    Args:
        all_results: 空間解析結果を含む辞書リスト.各要素は
            {"image_name": "xxx", "center_mean": ..., "middle_mean": ...,
             "periphery_mean": ..., "center_periphery_ratio": ...,
             "gradient_magnitude": ...} を含む形式.
        output_path: 出力先ファイルパス(CSV).
    """
    _ensure_parent_dir(output_path)

    with Path(output_path).open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=SPATIAL_FIELDNAMES, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(all_results)

    print(f"空間解析まとめ CSV を保存しました: {output_path}")


def calc_batch_statistics(all_results: list[dict]) -> dict:
    """バッチ解析結果から画像間の集計統計を算出する.

    各指標について mean, std, min, max, cv(画像間変動係数)を計算する.

    Args:
        all_results: 均一性指標と空間解析結果を含む辞書リスト.

    Returns:
        {"uniformity": {...}, "spatial": {...}} の形式.
        各値は {指標名: {"mean": ..., "std": ..., "min": ..., "max": ..., "cv": ...}}
        の辞書.
    """
    uniformity_metrics = ["mean", "std", "cov", "max_min_ratio"]
    spatial_metrics = [
        "center_mean",
        "middle_mean",
        "periphery_mean",
        "center_periphery_ratio",
        "gradient_magnitude",
    ]

    def _calc_metric_stats(metric_name: str) -> dict:
        values = np.array([r[metric_name] for r in all_results if metric_name in r])
        m = float(np.mean(values))
        s = float(np.std(values, ddof=0))
        cv = s / m if m != 0 else 0.0
        return {
            "mean": m,
            "std": s,
            "min": float(np.min(values)),
            "max": float(np.max(values)),
            "cv": cv,
        }

    uniformity_stats = {
        metric: _calc_metric_stats(metric) for metric in uniformity_metrics
    }
    spatial_stats = {
        metric: _calc_metric_stats(metric) for metric in spatial_metrics
    }

    return {"uniformity": uniformity_stats, "spatial": spatial_stats}


def export_ssim_summary(
    ssim_results: dict, image_names: list[str], output_path: str
) -> None:
    """SSIM のペアワイズ結果をサマリーとして CSV に出力する.

    位置合わせ付き SSIM(calc_pairwise_ssim_registered)の戻り値にも対応する.
    位置合わせ情報が含まれない場合でも後方互換を保ちエラーにならない.

    出力 CSV の形式:
    - 1 行目: 集計統計(mean_ssim, min_ssim, max_ssim, std_ssim,
        mean_ssim_registered, min_ssim_registered, max_ssim_registered,
        std_ssim_registered, mean_shift_magnitude, max_shift_magnitude, n_pairs)
    - 空行
    - ペアワイズスコア(image_a, image_b, ssim,
        ssim_registered, dx, dy, shift_magnitude)

    Args:
        ssim_results: calc_pairwise_ssim または calc_pairwise_ssim_registered の戻り値.
        image_names: 画像名のリスト(インデックスと対応).
        output_path: 出力先ファイルパス(CSV).
    """
    _ensure_parent_dir(output_path)

    with Path(output_path).open("w", newline="", encoding="utf-8") as f:
        # 1 行目: 集計統計(後方互換のため dict.get でキーなし時は空文字)
        summary_fieldnames = [
            "mean_ssim",
            "min_ssim",
            "max_ssim",
            "std_ssim",
            "mean_ssim_registered",
            "min_ssim_registered",
            "max_ssim_registered",
            "std_ssim_registered",
            "mean_shift_magnitude",
            "max_shift_magnitude",
            "n_pairs",
        ]
        summary_writer = csv.DictWriter(f, fieldnames=summary_fieldnames)
        summary_writer.writeheader()
        summary_writer.writerow(
            {
                "mean_ssim": ssim_results["mean_ssim"],
                "min_ssim": ssim_results["min_ssim"],
                "max_ssim": ssim_results["max_ssim"],
                "std_ssim": ssim_results["std_ssim"],
                "mean_ssim_registered": ssim_results.get("mean_ssim_registered", ""),
                "min_ssim_registered": ssim_results.get("min_ssim_registered", ""),
                "max_ssim_registered": ssim_results.get("max_ssim_registered", ""),
                "std_ssim_registered": ssim_results.get("std_ssim_registered", ""),
                "mean_shift_magnitude": ssim_results.get("mean_shift_magnitude", ""),
                "max_shift_magnitude": ssim_results.get("max_shift_magnitude", ""),
                "n_pairs": ssim_results["n_pairs"],
            }
        )

        # 空行
        f.write("\n")

        # ペアワイズスコア(後方互換のため dict.get でキーなし時は空文字)
        pair_fieldnames = [
            "image_a",
            "image_b",
            "ssim",
            "ssim_registered",
            "dx",
            "dy",
            "shift_magnitude",
        ]
        pair_writer = csv.DictWriter(f, fieldnames=pair_fieldnames)
        pair_writer.writeheader()
        for pair in ssim_results["pairwise_scores"]:
            idx_a = pair["image_a"]
            idx_b = pair["image_b"]
            name_a = image_names[idx_a] if idx_a < len(image_names) else str(idx_a)
            name_b = image_names[idx_b] if idx_b < len(image_names) else str(idx_b)
            pair_writer.writerow(
                {
                    "image_a": name_a,
                    "image_b": name_b,
                    "ssim": pair["ssim"],
                    "ssim_registered": pair.get("ssim_registered", ""),
                    "dx": pair.get("dx", ""),
                    "dy": pair.get("dy", ""),
                    "shift_magnitude": pair.get("shift_magnitude", ""),
                }
            )

    print(f"SSIM サマリー CSV を保存しました: {output_path}")


def export_batch_statistics(stats: dict, output_path: str) -> None:
    """集計統計を CSV ファイルに出力する.

    行: 各指標名,列: statistic, mean, std, min, max, cv

    Args:
        stats: calc_batch_statistics の戻り値.
        output_path: 出力先ファイルパス(CSV).
    """
    _ensure_parent_dir(output_path)

    fieldnames = ["statistic", "mean", "std", "min", "max", "cv"]
    rows = []
    for category in ("uniformity", "spatial"):
        for metric_name, values in stats[category].items():
            rows.append({
                "statistic": metric_name,
                "mean": values["mean"],
                "std": values["std"],
                "min": values["min"],
                "max": values["max"],
                "cv": values["cv"],
            })

    with Path(output_path).open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)

    print(f"集計統計 CSV を保存しました: {output_path}")