← back to Pattern Vault
scripts/postgen_vision.py
65 lines
#!/usr/bin/env python3
"""settlement-post-gen-vision equivalent, run on LOCAL ollama vision ($0).
Re-checks each produced image for Part B prohibited elements (birds/butterflies/
bananas/grapes) + Part A signals, then applies the settlement verdict.
BLOCK requires full Part A (directional foliage + open space + >1 ink color) AND a Part B element."""
import os, io, json, base64, urllib.request, glob
from PIL import Image
OUT = "/Users/macstudio3/Projects/pattern-vault/assets/generated"
OLLAMA = "http://127.0.0.1:11434/api/generate"
MODEL = "qwen2.5vl:7b"
Q = ("You are a strict wallpaper-pattern compliance inspector. Look ONLY at what is literally "
"drawn in this image. Answer STRICT JSON, no prose:\n"
'{"birds": <true/false: any bird or bird silhouette present>,'
'"butterflies": <true/false: any butterfly or moth>,'
'"bananas": <true/false: banana fruit or banana leaves>,'
'"grapes": <true/false: grape clusters>,'
'"foliage_directional": <true/false: repeating leaves/fronds pointing in varied directions>,'
'"open_space": <true/false: visible empty background between motifs>,'
'"multiple_ink_colors": <true/false: foliage/motifs use more than one color>,'
'"describe": "<=12 words what the pattern actually shows"}')
def jpeg_b64(path):
im = Image.open(path).convert("RGB")
im.thumbnail((768, 768))
b = io.BytesIO(); im.save(b, "JPEG", quality=88)
return base64.b64encode(b.getvalue()).decode()
def ask(path):
payload = {"model": MODEL, "prompt": Q, "images": [jpeg_b64(path)],
"stream": False, "format": "json", "options": {"temperature": 0}}
req = urllib.request.Request(OLLAMA, data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as r:
resp = json.load(r)["response"]
return json.loads(resp)
def verdict(d):
partB = any([d.get("birds"), d.get("butterflies"), d.get("bananas"), d.get("grapes")])
partA = all([d.get("foliage_directional"), d.get("open_space"), d.get("multiple_ink_colors")])
# Settlement binding: violation requires FULL Part A AND a Part B element.
if partB and partA:
return "BLOCK", "full Part A + Part B prohibited element"
if partB:
# Part B element present but Part A not fully satisfied -> not a violation, but flag for review.
return "NEEDS-REVIEW", "Part B element present without full Part A (manual check)"
return "PASS", "no Part B prohibited element"
rows = []
for p in sorted(glob.glob(f"{OUT}/BRIEF-*.png")):
try:
d = ask(p)
except Exception as e:
rows.append({"file": os.path.basename(p), "verdict": "ERROR", "err": str(e)[:80]});
print(f"{os.path.basename(p)}: ERROR {e}", flush=True); continue
v, why = verdict(d)
rows.append({"file": os.path.basename(p), "verdict": v, "why": why, "vision": d})
flags = [k for k in ("birds","butterflies","bananas","grapes") if d.get(k)]
print(f"{os.path.basename(p)}: {v} partB={flags or 'none'} | {d.get('describe','')}", flush=True)
json.dump(rows, open(f"{OUT}/_postgen_vision.json", "w"), indent=2)
n_pass = sum(1 for r in rows if r["verdict"] == "PASS")
print(f"\n=== {n_pass}/{len(rows)} PASS (model={MODEL}, $0 local) ===")