Newer
Older
SmTIAS-LightSim / scripts / build_spp_shapes6_summary.py
"""spp スイープ追試(--set shapes6)の計算時間表と 6 形状のパネル図を作る(TEST_19 追試).

入力:
    output/spp_noise_shapes6/summary.csv            sweep_spp_noise.py --set shapes6 の集約
    output/spp_noise_shapes6/<条件>_spp8192_s0/render.png
    output/spp_noise_shapes6/geometry/<形状>.png    sm-visualize --no-bake --style slide の撮影
出力:
    spp_time_table_mean_sd.csv     spp ごとの平均±標準偏差(n = 6 形状 × seed 2)
    shapes6_geometry_panel.png     6 形状の 3D 配置図(2×3)
    shapes6_render_panel.png       6 形状のレンダ結果(spp=8192,2×3)

実行方法:
    uv run python scripts/build_spp_shapes6_summary.py
"""

from __future__ import annotations

from pathlib import Path

import matplotlib.pyplot as plt
import pandas as pd

ROOT = Path(__file__).resolve().parents[1]
BASE = ROOT / "output" / "spp_noise_shapes6"

# (条件名, 形状名, 表示ラベル).上段が球,下段が同じ内面積の立方体
SHAPES = [
    ("sphere_d270_th000", "sphere_d270", "球 φ270"),
    ("sphere_d340_th000", "sphere_d340", "球 φ340"),
    ("sphere_d500_th000", "sphere_d500", "球 φ500"),
    ("cube_L187_th000", "cube_L187", "立方体 L=187"),
    ("cube_L234_th000", "cube_L234", "立方体 L=234"),
    ("cube_L340_th000", "cube_L340", "立方体 L=340"),
]


def write_table() -> pd.DataFrame:
    """spp ごとの計算時間(平均±標準偏差)を CSV に書く."""
    d = pd.read_csv(BASE / "summary.csv")
    d["t_fixed"] = d.t_total - d.t_render
    rows = []
    for spp, x in d.groupby("spp"):
        rows.append({
            "spp": spp,
            "n": len(x),
            "合計時間[s]": f"{x.t_total.mean():.1f}±{x.t_total.std():.1f}",
            "GPU処理[s]": f"{x.t_render.mean():.1f}±{x.t_render.std():.1f}",
            "CPU処理[s]": f"{x.t_fixed.mean():.1f}±{x.t_fixed.std():.1f}",
            "GPU処理の割合[%]": f"{100 * x.t_render.mean() / x.t_total.mean():.0f}",
            "GPU SD/平均[%]": f"{100 * x.t_render.std() / x.t_render.mean():.0f}",
        })
    table = pd.DataFrame(rows)
    table.to_csv(BASE / "spp_time_table_mean_sd.csv", index=False, encoding="utf-8-sig")
    print(table.to_string(index=False))
    print()
    print(d.groupby(["condition", "spp"]).t_render.mean().unstack().round(2))
    return table


def draw_panel(images: list[Path], out_path: Path, crop: bool) -> None:
    """6 枚の画像を 2×3 に並べてラベルを付ける."""
    fig, axes = plt.subplots(2, 3, figsize=(10, 7))
    for ax, image, (_, _, label) in zip(axes.ravel(), images, SHAPES, strict=True):
        img = plt.imread(image)
        if crop:
            # sm-visualize のスクリーンショットは左右の余白が広いので切り詰める
            h, w = img.shape[:2]
            img = img[int(h * 0.05):int(h * 0.97), int(w * 0.15):int(w * 0.85)]
        ax.imshow(img)
        ax.set_title(label, fontsize=16)
        ax.axis("off")
    fig.tight_layout()
    fig.savefig(out_path, dpi=200)
    plt.close(fig)
    print("saved", out_path)


def main() -> None:
    """表と 2 枚のパネル図を作る."""
    plt.rcParams["font.family"] = ["Yu Gothic", "Meiryo", "sans-serif"]
    write_table()
    draw_panel([BASE / f"{cond}_spp8192_s0" / "render.png" for cond, _, _ in SHAPES],
               BASE / "shapes6_render_panel.png", crop=False)
    draw_panel([BASE / "geometry" / f"{shape}.png" for _, shape, _ in SHAPES],
               BASE / "shapes6_geometry_panel.png", crop=True)


if __name__ == "__main__":
    main()