commit 9fdcb36c5bd3ec49117de935fdf2f05bf54eb714 Author: Naqqash Khan Date: Sun Feb 22 01:01:27 2026 +0100 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b1fed2 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# ComfyUI-CLIPScore 🐱‍👤📊 + +**Research-grade CLIP Score node** for ComfyUI. Raw cosine similarity (-1 to +1) between prompts and Flux/SD images. + +[![CLIP Score Demo](https://i.imgur.com/coaDjiA.png)](https://github.com/emniko/ComfyUI-CLIPScore) + +## Features +- ✅ **RGBA support** (VAE Decode → RGB auto) +- ✅ **Batch-first** scoring +- ✅ **Raw cosine scores** (0.25-0.35 = excellent) +- ✅ **Flux/SD3/SDXL** ready + +## Score Scale +| Score | Rating | Example | +| --------- | ----------- | -------------------- | +| ≥0.30 | 🎖️ Excellent | Perfect prompt match | +| 0.25-0.29 | 👍 Good | Strong alignment | +| 0.20-0.24 | ⚠️ Fair | Usable | +| <0.20 | ❌ Poor | Mismatch | + +## Install +```bash +cd ComfyUI/custom_nodes +git clone https://github.com/yourusername/ComfyUI-CLIPScore.git +cd ComfyUI-CLIPScore +pip install -r requirements.txt diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..3ffb4f3 --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .clip_score_node import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] \ No newline at end of file diff --git a/clip_score_node.py b/clip_score_node.py new file mode 100644 index 0000000..efcc1b0 --- /dev/null +++ b/clip_score_node.py @@ -0,0 +1,91 @@ +""" +ComfyUI Custom Node: CLIP Score Calculator +""" + +import torch +import numpy as np +from PIL import Image +from transformers import CLIPProcessor, CLIPModel + +class CLIPScoreNode: + """Calculates true CLIP cosine similarity between prompt and image.""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "prompt": ("STRING", {"multiline": True, "default": ""}), + "model_name": ( + [ + "openai/clip-vit-base-patch16", + "openai/clip-vit-base-patch32", + "openai/clip-vit-large-patch14", + ], + {"default": "openai/clip-vit-base-patch16"}, + ), + }, + } + + RETURN_TYPES = ("FLOAT", "STRING") + RETURN_NAMES = ("clip_score", "score_text") + FUNCTION = "calculate_clip_score" + CATEGORY = "metrics" + OUTPUT_NODE = True + + def calculate_clip_score(self, image, prompt, model_name): + try: + # Load CLIP (cached after first run) + model = CLIPModel.from_pretrained(model_name) + processor = CLIPProcessor.from_pretrained(model_name) + + # Handle ComfyUI IMAGE: [B,H,W,C] or [H,W,C] + if len(image.shape) == 4: + img = image[0] # First from batch + else: + img = image + + # [H,W,C] float32 [0,1] → numpy uint8 → PIL RGB + img_np = (img.cpu().numpy() * 255).astype(np.uint8) + if img_np.shape[-1] == 4: # RGBA → RGB + img_np = img_np[:, :, :3] + pil_image = Image.fromarray(img_np, mode='RGB') + + # GPU inference + device = next(model.parameters()).device + inputs = processor(text=[prompt], images=pil_image, return_tensors="pt", padding=True) + inputs = {k: v.to(device) for k, v in inputs.items()} + model.to(device) + + # Raw CLIP score: cosine similarity of normalized embeddings + with torch.no_grad(): + outputs = model(**inputs) + image_embeds = outputs.image_embeds + text_embeds = outputs.text_embeds + + # L2 normalize + cosine similarity (standard CLIP score) + image_embeds /= image_embeds.norm(p=2, dim=-1, keepdim=True) + 1e-8 + text_embeds /= text_embeds.norm(p=2, dim=-1, keepdim=True) + 1e-8 + score = (image_embeds @ text_embeds.T)[0, 0].item() + + # Cleanup + model.cpu() + del model, inputs + + except Exception as e: + score = 0.0 + print(f"CLIP error: {str(e)}") + + score_str = f"CLIP Score: {score:.4f}" + print(score_str) + + return (score, score_str) + + +NODE_CLASS_MAPPINGS = { + "CLIPScoreNode": CLIPScoreNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "CLIPScoreNode": "CLIP Score Calculator", +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fd7dcc8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +torch +transformers>=4.36.0 +pillow +numpy \ No newline at end of file