#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""art_qc.py —— 生成图/画作的"美术学体检"（8.4 §4 的产品化）
五项代理指标（全部是可计算代理，不是美术学判断本身——8.1 映射表的定位）：
  1. tone_range     明暗动态范围（五调子的粗代理：亮度 p5–p95 越宽对比越足）
  2. focus_count    视觉焦点数（分块能量显著峰——"动线混乱/处处是重点"的粗检测）
  3. color_harmony  主色相峰数量与间距（互补/类似色和谐的粗检测）
  4. neg_space      负空间占比（低饱和低亮度像素占比——水墨留白的粗代理）
  5. detail_grad    中心-边缘细节密度梯度（主体是否比背景细）
依赖：pip install pillow numpy
用法：python art_qc.py --dir generated/ --out qc_report.csv
"""
import argparse, csv, os
import numpy as np
from PIL import Image

def load_rgb(p, side=512):
    im = Image.open(p).convert("RGB")
    im.thumbnail((side, side))
    return np.asarray(im, dtype=np.float32) / 255.0

def analyze(img):
    lum = img @ np.array([0.299, 0.587, 0.114], dtype=np.float32)
    h, w = lum.shape

    # 1. 明暗动态范围
    tone = float(np.percentile(lum, 95) - np.percentile(lum, 5))

    # 2. 视觉焦点：把图分 6×6 块，块内"梯度能量"作为注意力的粗代理，数显著峰
    gy, gx = np.gradient(lum)
    energy = (gx**2 + gy**2)
    bh, bw = h // 6, w // 6
    block_e = np.array([[energy[i*bh:(i+1)*bh, j*bw:(j+1)*bw].mean()
                         for j in range(6)] for i in range(6)])
    thr = block_e.mean() + 2 * block_e.std()
    focus = int((block_e > thr).sum())

    # 3. 色相主峰：HSV 色相 36 桶直方图，平滑后数显著峰
    hsv = np.asarray(Image.fromarray((img * 255).astype(np.uint8)).convert("HSV"), dtype=np.float32)
    hue = hsv[..., 0].flatten() / 179.0 * 360.0
    hist, _ = np.histogram(hue, bins=36, range=(0, 360), weights=None)
    hist = hist / max(hist.sum(), 1)
    kernel = np.array([1, 2, 3, 2, 1], dtype=np.float32); kernel /= kernel.sum()
    smooth = np.convolve(np.r_[hist[-2:], hist, hist[:2]], kernel, "same")[2:-2]
    peaks = [i for i in range(36) if smooth[i] == smooth.max(i-2, i+3) and smooth[i] > smooth.mean() * 1.8]
    gaps = sorted((b - a) % 360 for a, b in zip(peaks, peaks[1:])) if len(peaks) > 1 else []
    harmony = {"peak_count": len(peaks), "peak_deg": [int(p*10) for p in peaks],
               "min_gap_deg": int(min(gaps)) if gaps else None}   # ≈180 → 互补色和谐

    # 4. 负空间：低饱和且低亮的像素占比
    sat = hsv[..., 1].flatten() / 255.0
    neg = float(((sat < 0.15) & (lum.flatten() < 0.35)).mean())

    # 5. 中心-边缘细节梯度
    ch, cw = h // 3, w // 3
    center_e = energy[ch:2*ch, cw:2*cw].mean()
    edge_e = energy.mean()
    grad = float(center_e / max(edge_e, 1e-6))

    return {"tone_range": round(tone, 3), "focus_count": focus,
            "color_harmony": harmony, "neg_space": round(neg, 3),
            "detail_grad": round(grad, 3)}

def main():
    ap = argparse.ArgumentParser(description="美术学体检（代理指标版）")
    ap.add_argument("--dir", required=True)
    ap.add_argument("--out", default="qc_report.csv")
    args = ap.parse_args()

    exts = {".jpg", ".jpeg", ".png", ".webp"}
    rows = []
    for r, _, fs in os.walk(args.dir):
        for f in sorted(fs):
            if os.path.splitext(f)[1].lower() not in exts:
                continue
            p = os.path.join(r, f)
            try:
                rows.append({"file": os.path.relpath(p, args.dir), **analyze(load_rgb(p))})
            except Exception as e:
                rows.append({"file": os.path.relpath(p, args.dir), "error": str(e)})

    keys = ["file", "tone_range", "focus_count", "neg_space", "detail_grad",
            "color_harmony.peak_count", "color_harmony.min_gap_deg", "error"]
    with open(args.out, "w", newline="", encoding="utf-8-sig") as f:
        wtr = csv.DictWriter(f, fieldnames=keys)
        wtr.writeheader()
        for r in rows:
            flat = dict(r)
            if "color_harmony" in r:
                flat["color_harmony.peak_count"] = r["color_harmony"]["peak_count"]
                flat["color_harmony.min_gap_deg"] = r["color_harmony"]["min_gap_deg"]
            wtr.writerow({k: flat.get(k, "") for k in keys})

    print(f"体检完成：{len(rows)} 张 → {args.out}")
    print("解读提示（8.4 §4）：tone_range<0.5 需查对比；focus_count>3 动线混乱嫌疑；")
    print("neg_space 高 + peak_count 少 ≈ 留白型构图；min_gap≈180 出现 → 互补色和谐。")
    print("这些是代理指标：初筛/统计用，艺术终审永远留给人（8.7）。")

if __name__ == "__main__":
    main()
