# -*- coding: utf-8 -*-
"""Track B: OpenCVで静止画から状態特徴を抽出（LLM不使用・API課金ゼロ）。
液面ROIを推定し、色/濁り/油膜/水位/豚崩れ/撮影品質を数値化。
非対称Brix補正用の brix_under も付与。出力: trackB_claude/features.csv
実行（フレーム生成後）: python trackB_claude/cv_features.py

各特徴は「人間が見ている軸」の代理。最終スコアは common/compare_models.py で
これら＋Brix＋時間帯から学習（説明可能）。feature_design.md の設計に対応。
"""
import os, sys, glob
import cv2, numpy as np, pandas as pd
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(ROOT, "common"))
import data_loader

FRAMES = os.path.join(ROOT, "data", "frames")

def liquid_roi(img):
    """寸胴の液面ROIを円検出で推定。失敗時は中央クロップ。戻り: (マスク, (cx,cy,r))"""
    h, w = img.shape[:2]
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray = cv2.medianBlur(gray, 5)
    circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1.2, minDist=w,
                               param1=100, param2=40,
                               minRadius=int(min(h, w) * 0.25),
                               maxRadius=int(min(h, w) * 0.6))
    mask = np.zeros((h, w), np.uint8)
    if circles is not None:
        cx, cy, r = np.round(circles[0][0]).astype(int)
    else:
        cx, cy, r = w // 2, h // 2, int(min(h, w) * 0.42)
    cv2.circle(mask, (cx, cy), int(r * 0.92), 255, -1)  # 内側だけ（縁を除く）
    return mask, (cx, cy, r), (h, w)

def extract(path):
    img = cv2.imread(path)
    if img is None:
        return None
    mask, (cx, cy, r), (h, w) = liquid_roi(img)
    roi = img[mask == 255]
    if len(roi) < 100:
        return None
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[mask == 255].astype(float)
    lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)[mask == 255].astype(float)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    groi = gray[mask == 255].astype(float)

    f = {}
    # --- 色・濁りの質 ---
    f["L_mean"]   = lab[:, 0].mean()              # 明るさ（白濁度の代理：高=白い）
    f["a_mean"]   = lab[:, 1].mean() - 128        # 赤み（褐色・豚感）
    f["b_mean"]   = lab[:, 2].mean() - 128        # 黄み
    f["sat_mean"] = hsv[:, 1].mean()              # 彩度（高=濃い色味）
    f["hue_mean"] = hsv[:, 0].mean()
    # --- 濁り/不透明度の代理（均一性・コントラスト）---
    f["L_std"]    = lab[:, 0].std()               # ばらつき小=均一に濁る（微乳化寄り）
    f["contrast"] = groi.std()
    # --- 表面油（鏡面ハイライト面積率）---
    bright = (groi > 230)
    f["highlight_ratio"] = float(bright.mean())   # 高=油の照り/反射が強い
    # --- 豚崩れ（ROI内エッジ密度＝浮遊物・崩れの代理）---
    edges = cv2.Canny(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 50, 150)
    f["edge_density"] = float((edges[mask == 255] > 0).mean())
    # --- 水位（ROI半径 / 画像短辺。大きいほど液面が縁に近い代理）---
    f["water_level_proxy"] = float(r / (min(h, w) / 2))
    # --- 撮影品質 ---
    f["blowout"]   = float((gray > 245).mean())   # 白飛び（湯気）
    f["darkness"]  = float((gray < 30).mean())    # 暗すぎ
    f["sharpness"] = float(cv2.Laplacian(gray, cv2.CV_64F).var())
    return f

def still_for(name):
    base = os.path.splitext(str(name))[0]
    p = os.path.join(FRAMES, f"{base}.jpg")
    return p if os.path.exists(p) else None

def run():
    df = data_loader.load()
    rows = []
    for _, r in df.iterrows():
        img = still_for(r.get("video"))
        if not img:
            continue
        feats = extract(img)
        if feats is None:
            print("SKIP", os.path.basename(img)); continue
        feats.update({
            "video": r["video"], "brix": r["brix"], "slot": int(r["dinner"]),
            "brix_under": max(0.0, r["target"] - r["brix"]),  # 非対称Brix補正
            "total": r["total"], "soup": r["soup"],
        })
        rows.append(feats); print("OK", os.path.basename(img))
    out = pd.DataFrame(rows)
    out.to_csv(os.path.join(ROOT, "trackB_claude", "features.csv"),
               index=False, encoding="utf-8-sig")
    print(f"\n{len(out)}件 → trackB_claude/features.csv（{out.shape[1]}列）")

if __name__ == "__main__":
    run()
