Newer
Older
SmTIAS-Evaluation / src / visualization / plotter.py
"""輝度マップ・ヒストグラム・空間分析可視化モジュール."""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy


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

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


def plot_luminance_map(luminance: numpy.ndarray, output_path: str) -> None:
    """輝度画像をヒートマップとして保存する.

    Args:
        luminance: 輝度画像の NumPy 配列(H x W, float64).
        output_path: 出力先ファイルパス(PNG).
    """
    _ensure_parent(output_path)

    fig, ax = plt.subplots(figsize=(8, 6))
    im = ax.imshow(luminance, cmap="hot", interpolation="nearest")
    fig.colorbar(im, ax=ax, label="Luminance")
    ax.set_title("Luminance Map")
    ax.set_xlabel("X (px)")
    ax.set_ylabel("Y (px)")
    fig.tight_layout()
    fig.savefig(output_path, dpi=150)
    plt.close(fig)

    print(f"輝度マップを保存しました: {output_path}")


def plot_histogram(luminance: numpy.ndarray, output_path: str) -> None:
    """輝度ヒストグラムを生成して保存する.

    Args:
        luminance: 輝度画像の NumPy 配列(H x W, float64).
        output_path: 出力先ファイルパス(PNG).
    """
    _ensure_parent(output_path)

    fig, ax = plt.subplots(figsize=(8, 5))
    ax.hist(
        luminance.ravel(), bins=256, range=(0, 255), color="steelblue", edgecolor="none"
    )
    ax.set_title("Luminance Histogram")
    ax.set_xlabel("Luminance")
    ax.set_ylabel("Pixel Count")
    fig.tight_layout()
    fig.savefig(output_path, dpi=150)
    plt.close(fig)

    print(f"輝度ヒストグラムを保存しました: {output_path}")


def plot_radial_profile(radial_profile: numpy.ndarray, output_path: str) -> None:
    """放射状輝度プロファイルを折れ線グラフとして保存する.

    Args:
        radial_profile: 放射状プロファイルの NumPy 配列(N x 2, float64).
            列 0 = 正規化距離, 列 1 = 平均輝度.
        output_path: 出力先ファイルパス(PNG).
    """
    _ensure_parent(output_path)

    fig, ax = plt.subplots(figsize=(8, 5))
    ax.plot(radial_profile[:, 0], radial_profile[:, 1], marker="o", color="steelblue")
    ax.set_title("Radial Luminance Profile")
    ax.set_xlabel("Normalized Distance from Center")
    ax.set_ylabel("Mean Luminance")
    ax.grid(True, linestyle="--", alpha=0.5)
    fig.tight_layout()
    fig.savefig(output_path, dpi=150)
    plt.close(fig)

    print(f"放射状プロファイルを保存しました: {output_path}")


def plot_zone_map(
    luminance: numpy.ndarray,
    zone_map: numpy.ndarray,
    zone_stats: dict,
    output_path: str,
) -> None:
    """輝度マップにゾーン境界をオーバーレイして保存する.

    "hot" カラーマップの輝度ヒートマップ上に、3 ゾーンの境界を
    contour で描画し、各ゾーンの平均輝度を注釈として追加する.

    Args:
        luminance: 輝度画像の NumPy 配列(H x W, float64).
        zone_map: ゾーンマップの NumPy 配列(H x W, uint8).
            0=center, 1=middle, 2=periphery.
        zone_stats: ゾーン別統計の辞書(calc_zone_stats の戻り値).
        output_path: 出力先ファイルパス(PNG).
    """
    _ensure_parent(output_path)

    fig, ax = plt.subplots(figsize=(8, 6))

    # 輝度ヒートマップ
    im = ax.imshow(luminance, cmap="hot", interpolation="nearest")
    fig.colorbar(im, ax=ax, label="Luminance")

    # ゾーン境界をコンター線で描画(境界値は 0.5, 1.5 でゾーン間を区切る)
    ax.contour(zone_map, levels=[0.5, 1.5], colors=["cyan", "lime"], linewidths=1.5)

    # 各ゾーンの重心位置に平均輝度を注釈
    zone_labels = [
        (0, zone_stats["center_mean"], "Center"),
        (1, zone_stats["middle_mean"], "Middle"),
        (2, zone_stats["periphery_mean"], "Periphery"),
    ]
    for zone_id, mean_val, label in zone_labels:
        ys, xs = numpy.where(zone_map == zone_id)
        if ys.size > 0:
            cy_pos = int(numpy.mean(ys))
            cx_pos = int(numpy.mean(xs))
            ax.annotate(
                f"{label}\n{mean_val:.1f}",
                xy=(cx_pos, cy_pos),
                ha="center",
                va="center",
                color="white",
                fontsize=8,
                bbox={"boxstyle": "round,pad=0.2", "facecolor": "black", "alpha": 0.5},
            )

    ax.set_title("Zone Map (center / middle / periphery)")
    ax.set_xlabel("X (px)")
    ax.set_ylabel("Y (px)")
    fig.tight_layout()
    fig.savefig(output_path, dpi=150)
    plt.close(fig)

    print(f"ゾーンマップを保存しました: {output_path}")