← back to Enrich Local Hybrid

enrich-palette.py

40 lines

#!/usr/bin/env python3
"""Ground-truth palette + a NORMALIZED image for the VL.
- palette: exact hex + area-% from real pixels (more accurate than any VLM), near-dup merged.
- image_b64: the image re-encoded as a clean RGB JPEG, long-edge-capped. Normalizing through
  Pillow guarantees ollama/llama-server can load it (catalog images are often CMYK JPEG / WebP /
  odd encodings that Pillow reads but the VL image loader rejects → "Failed to load image").
Usage: enrich-palette.py <image> [k]   → prints {"palette":[...], "image_b64":"..."}"""
import sys, json, io, base64, os
from PIL import Image

def _dist(a, b):
    return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5

def analyze(path, k=6, merge_thresh=30, vl_max=int(os.environ.get("ENRICH_VL_MAX", "1024"))):
    im = Image.open(path).convert("RGB")            # CMYK/RGBA/P/WebP -> RGB
    # --- palette from a small copy ---
    small = im.copy(); small.thumbnail((220, 220))
    q = small.quantize(colors=16, method=Image.MEDIANCUT)
    pal = q.getpalette(); counts = sorted(q.getcolors(), reverse=True)
    total = sum(c for c, _ in counts) or 1
    buckets = []
    for count, idx in counts:
        rgb = tuple(pal[idx*3:idx*3+3]); pct = count / total * 100
        for b in buckets:
            if _dist(rgb, b["rgb"]) <= merge_thresh:
                b["pct"] += pct; break
        else:
            buckets.append({"rgb": rgb, "pct": pct})
    buckets = [b for b in buckets if b["pct"] >= 2][:k]
    out = [{"hex": "#%02X%02X%02X" % b["rgb"], "percentage": round(b["pct"])} for b in buckets]
    diff = 100 - sum(c["percentage"] for c in out)
    if out: out[0]["percentage"] += diff
    # --- normalized VL image (clean JPEG ollama always loads) ---
    vl = im.copy(); vl.thumbnail((vl_max, vl_max))
    buf = io.BytesIO(); vl.save(buf, format="JPEG", quality=90)
    return {"palette": out, "image_b64": base64.b64encode(buf.getvalue()).decode()}

if __name__ == "__main__":
    print(json.dumps(analyze(sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 6)))