"""comfyui_judge_image — panel multi-juez: vota good/bad la calidad de una imagen. Agregadora del grupo `comfyui-judge`. Es el "modelo adversario" que distingue producto bueno de malo combinando tres jueces independientes: - estetico (`comfyui_score_aesthetic`): calidad visual LAION-V2, 0-10 - fidelidad (`comfyui_score_clip_alignment`): parecido al prompt via CLIP, 0-1 - critico (`comfyui_critique_image_llm`): veredicto de un LLM-vision, good/bad + 0-10 Cada juez emite un voto good/bad; el veredicto final es por MAYORIA. El score agregado es la media (ponderable) de los tres normalizados a 0-10. Si un juez falla, se excluye del voto y se anota: el panel sigue votando con los restantes (no crashea). Solo devuelve ``ok=False`` si los tres jueces fallan. Impura: compone tres funciones impuras (subprocesos torch + API Anthropic). """ import json import os import sys sys.path.insert(0, os.path.dirname(__file__)) from comfyui_score_aesthetic import comfyui_score_aesthetic # noqa: E402 from comfyui_score_clip_alignment import comfyui_score_clip_alignment # noqa: E402 from comfyui_critique_image_llm import comfyui_critique_image_llm # noqa: E402 DEFAULT_WEIGHTS = {"aesthetic": 1.0, "clip": 1.0, "llm": 1.0} def comfyui_judge_image( image_path: str, prompt: str, *, weights: dict = None, threshold: float = 6.0, clip_threshold: float = 0.24, server: str = "127.0.0.1:8188", model: str = "claude-opus-4-8", venv_python: str = "~/ComfyUI/.venv/bin/python3", ) -> dict: """Juzga una imagen con el panel de tres jueces y devuelve el veredicto por mayoria. Args: image_path: ruta a la imagen en disco local. prompt: prompt original de la generacion (usado por fidelidad y critico). weights: pesos {aesthetic, clip, llm} para el score agregado; None = iguales. No afecta al voto por mayoria (1 juez = 1 voto). keyword-only. threshold: umbral 0-10 para el voto del juez estetico (score>=threshold => good). keyword-only. clip_threshold: umbral 0-1 para el voto de fidelidad (score>=clip_threshold => good). keyword-only. server: host:port del server ComfyUI (pasado a fidelidad). keyword-only. model: modelo Anthropic para el juez critico. keyword-only. venv_python: python del venv ComfyUI para los jueces estetico/fidelidad. keyword-only. Returns: dict ``{ok, verdict, score, votes, reasons, error, details}``. ``verdict`` 'good'|'bad' por mayoria de votos; ``score`` media ponderada 0-10 de los jueces que respondieron; ``votes`` = {clip, aesthetic, llm} cada uno 'good'|'bad'| 'failed'; ``reasons`` agrega las razones del critico + notas de jueces caidos; ``details`` lleva el dict crudo de cada juez. ``ok=False`` solo si los tres fallan. """ w = dict(DEFAULT_WEIGHTS) if weights: w.update({k: float(v) for k, v in weights.items() if k in w}) aes = comfyui_score_aesthetic(image_path, venv_python=venv_python) clip = comfyui_score_clip_alignment(image_path, prompt, server=server, venv_python=venv_python) llm = comfyui_critique_image_llm(image_path, prompt, model=model) votes = {} reasons = [] weighted = [] # (peso, score_0_10) # Juez estetico if aes.get("ok"): good = aes["score_0_10"] >= threshold votes["aesthetic"] = "good" if good else "bad" weighted.append((w["aesthetic"], aes["score_0_10"])) reasons.append(f"estetico={aes['score_0_10']:.2f}/10 ({votes['aesthetic']})") else: votes["aesthetic"] = "failed" reasons.append(f"estetico FALLO: {aes.get('error', '')}") # Juez de fidelidad if clip.get("ok"): good = clip["score_0_1"] >= clip_threshold votes["clip"] = "good" if good else "bad" weighted.append((w["clip"], clip["score_0_1"] * 10.0)) reasons.append(f"fidelidad={clip['score_0_1']:.3f} ({votes['clip']})") else: votes["clip"] = "failed" reasons.append(f"fidelidad FALLO: {clip.get('error', '')}") # Juez critico LLM if llm.get("ok"): votes["llm"] = llm["verdict"] weighted.append((w["llm"], llm["score_0_10"])) reasons.append(f"critico={llm['score_0_10']:.1f}/10 ({llm['verdict']})") reasons.extend(f" - {r}" for r in llm.get("reasons", [])) else: votes["llm"] = "failed" reasons.append(f"critico FALLO: {llm.get('error', '')}") n_good = sum(1 for v in votes.values() if v == "good") n_bad = sum(1 for v in votes.values() if v == "bad") n_alive = n_good + n_bad if n_alive == 0: return {"ok": False, "verdict": "", "score": 0.0, "votes": votes, "reasons": reasons, "error": "los tres jueces fallaron", "details": {"aesthetic": aes, "clip": clip, "llm": llm}} # Mayoria; empate => 'bad' (conservador: ante la duda, no es producto). verdict = "good" if n_good > n_bad else "bad" tot_w = sum(pw for pw, _ in weighted) score = sum(pw * s for pw, s in weighted) / tot_w if tot_w else 0.0 return {"ok": True, "verdict": verdict, "score": score, "votes": votes, "reasons": reasons, "error": "", "details": {"aesthetic": aes, "clip": clip, "llm": llm}} def _main(argv): if len(argv) < 1: sys.stderr.write('uso: comfyui_judge_image IMAGEN ["prompt original"]\n') return 2 image_path = argv[0] prompt = " ".join(argv[1:]) res = comfyui_judge_image(image_path, prompt) print(json.dumps(res, indent=2, ensure_ascii=False)) return 0 if res["ok"] else 1 if __name__ == "__main__": sys.exit(_main(sys.argv[1:]))