← back to Dw Pairs Well

tools/pattern-texture-vision-stub.py

55 lines

#!/usr/bin/env python3
"""DESIGN-ONLY STUB — texture-aware local-vision confirm for pattern grouping.

DTD exception #3: the color-invariant dHash FALSE-SPLITS organic textures
(grasscloth/linen/raffia/cork — fibers are random; Aloha Grass → 7 false
clusters). For texture multi-colorway groups that DON'T have a trusted mfr_sku
root (or that are suspiciously huge), we confirm with a FREE local Ollama
vision pass (qwen2.5vl on Mac1/Mac2) instead of the dHash gate.

THIS IS A STUB. It is NOT wired to run a 74k job. It documents the intended
call shape so the gated apply step can implement it. Cost = $0 (local Ollama).

Intended flow (per candidate group, post mfr_root/T2 keying):
  1. Pull the group's member image_urls (read-only, from the mirror).
  2. For each pair (or representative-vs-member), ask qwen2.5vl ONE question:
       "Are these two images the SAME woven/textured pattern in different
        colorways, ignoring color? Answer YES or NO."
     -> tolerant of random-fiber noise that breaks dHash; sensitive to a real
        motif/weave-structure change (so a generic-title over-merge still splits).
  3. Members the model says match the representative stay in the cluster;
     non-matches split into their own pattern_id (suffix ::v2, ::v3 …).

Hard gotchas (from MEMORY):
  - Normalize each image Pillow -> JPEG before sending to Ollama; it rejects
    CMYK/WebP (word-salad otherwise).
  - Use OLLAMA_KV_CACHE_TYPE=f16 for vision (q4_0 KV cache breaks vision).
  - Mac1 text inference can wedge while vision still serves; prove health via
    /api/generate num_predict:1 before a batch, fail over to Mac2 127.0.0.1.
  - This is the SAME local-hybrid pattern as enrich-ai-tags (qwen2.5vl), $0.

Reference (do not duplicate): tools/pattern-image-verify.py is the dHash gate
for the NON-texture bucket; this stub is its texture-safe sibling.
"""
import os, sys

OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://127.0.0.1:11434")
VISION_MODEL = os.environ.get("VISION_MODEL", "qwen2.5vl")

PROMPT = ("Are these two wallcovering images the SAME woven/textured pattern in "
          "different colorways, ignoring color and minor fiber noise? "
          "Answer with a single word: YES or NO.")

def confirm_texture_group(member_urls):
    """STUB — returns clusters. Not implemented; raises so nobody runs it blind."""
    raise NotImplementedError(
        "Design-only stub. Implement during the GATED apply step: normalize each "
        "image to JPEG (Pillow), POST to %s /api/chat with model=%s and PROMPT, "
        "parse YES/NO, cluster. $0 local. See module docstring." % (OLLAMA_URL, VISION_MODEL))

if __name__ == "__main__":
    print(__doc__)
    print(f"\n[stub] OLLAMA_URL={OLLAMA_URL}  VISION_MODEL={VISION_MODEL}  cost=$0 (local)")
    print("[stub] Not runnable — design reference only. Exiting 0.")
    sys.exit(0)