ui-copilot / scripts /embed_benchmarks.py
ksri77's picture
feat: Module 3b — Benchmark Library (CLIP similarity scoring, cosine top-k, graceful fallback)
332c49d
Raw
History Blame Contribute Delete
3.48 kB
"""
Offline script — pre-compute CLIP embeddings for the benchmark library.
Usage:
python scripts/embed_benchmarks.py
Expected directory layout:
data/benchmarks/images/
excellent/
cards/
stripe_card_001.png
stripe_card_001.json ← metadata (same stem)
tables/
dashboards/
...
poor/
(same category structure)
Output:
data/benchmarks/embeddings.npz
embeddings — float32 (N, 512) — one row per image
metadata — object (N,) — serialised JSON strings
Run this whenever images are added or changed. The app reads only the .npz
file at runtime; raw images are never committed (see .gitignore).
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import numpy as np
ROOT = Path(__file__).resolve().parents[1]
IMAGES_DIR = ROOT / "data" / "benchmarks" / "images"
OUTPUT = ROOT / "data" / "benchmarks" / "embeddings.npz"
def main() -> None:
try:
from sentence_transformers import SentenceTransformer
except ImportError:
print("ERROR: sentence-transformers is not installed.")
print(" pip install sentence-transformers")
sys.exit(1)
if not IMAGES_DIR.exists():
print(f"ERROR: {IMAGES_DIR} does not exist.")
print(" Place benchmark images under data/benchmarks/images/")
sys.exit(1)
image_exts = {".png", ".jpg", ".jpeg", ".webp"}
image_paths = [
p for p in IMAGES_DIR.rglob("*")
if p.suffix.lower() in image_exts
]
if not image_paths:
print(f"No images found under {IMAGES_DIR}")
sys.exit(1)
print(f"Found {len(image_paths)} images — loading CLIP model…")
model = SentenceTransformer("clip-ViT-B-32")
from PIL import Image
embeddings: list[np.ndarray] = []
metadata: list[str] = []
skipped = 0
for path in sorted(image_paths):
meta_path = path.with_suffix(".json")
if not meta_path.exists():
print(f" SKIP {path.name} — no matching .json metadata file")
skipped += 1
continue
with open(meta_path, encoding="utf-8") as f:
meta = json.load(f)
# Infer quality from directory structure if not in JSON
if "quality" not in meta:
parts = path.parts
if "excellent" in parts:
meta["quality"] = "excellent"
elif "poor" in parts:
meta["quality"] = "poor"
try:
img = Image.open(path).convert("RGB")
emb = np.array(model.encode(img), dtype=np.float32)
except Exception as exc:
print(f" SKIP {path.name}{exc}")
skipped += 1
continue
embeddings.append(emb)
metadata.append(json.dumps(meta))
print(f" OK {path.name} ({meta.get('product', '?')} / {meta.get('category', '?')})")
if not embeddings:
print("No embeddings produced — nothing to save.")
sys.exit(1)
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
np.savez(
str(OUTPUT),
embeddings=np.stack(embeddings),
metadata=np.array(metadata, dtype=object),
)
print(f"\nSaved {len(embeddings)} embeddings → {OUTPUT}")
if skipped:
print(f"Skipped {skipped} files (missing metadata or load error)")
if __name__ == "__main__":
main()