[object Object]

← back to Wallco Ai

fuzzy-seam scanner: detect pil-heal blur-band 'fuzzy cross' the edges-agent misses

5d63f4bc556aba0820bbf059fd96bce1dce806af · 2026-05-27 07:56:26 -0700 · Steve Abrams

The 2026-05-26 pil-mid-heal/pil-edge-heal passes smeared a blurred strip across
the H/V midlines, producing a visible fuzzy T/+ at tile center. edges-agent
measures discontinuity (a hard jump) so a smooth blur band PASSES it. This
detector keys on the actual physics: a thin blurred line at the exact midline,
flanked by sharp content on both sides, isolated (not periodic motif gaps).
Validated 6/6 known-bad FAIL, 0/120 random round-1 false-positives.

Files touched

Diff

commit 5d63f4bc556aba0820bbf059fd96bce1dce806af
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 27 07:56:26 2026 -0700

    fuzzy-seam scanner: detect pil-heal blur-band 'fuzzy cross' the edges-agent misses
    
    The 2026-05-26 pil-mid-heal/pil-edge-heal passes smeared a blurred strip across
    the H/V midlines, producing a visible fuzzy T/+ at tile center. edges-agent
    measures discontinuity (a hard jump) so a smooth blur band PASSES it. This
    detector keys on the actual physics: a thin blurred line at the exact midline,
    flanked by sharp content on both sides, isolated (not periodic motif gaps).
    Validated 6/6 known-bad FAIL, 0/120 random round-1 false-positives.
---
 scripts/fuzzy_seam_scan.py | 218 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 218 insertions(+)

diff --git a/scripts/fuzzy_seam_scan.py b/scripts/fuzzy_seam_scan.py
new file mode 100644
index 0000000..b823925
--- /dev/null
+++ b/scripts/fuzzy_seam_scan.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python3
+"""fuzzy-seam scanner — detects the pil-mid-heal / pil-edge-heal "fuzzy cross"
+defect: the heal pass blends a BLURRED strip across the horizontal and/or
+vertical midline, producing a visible smeared T/+ at the center of the tile.
+
+Why a new detector: the edges-agent measures pixel DISCONTINUITY (a hard ΔE
+jump) and the scratch intermediate-color scanner needs a near-perfect 2-tone
+rest. A blur band is *smooth* (low ΔE) and shows up on any palette, so both
+miss it (confirmed: 53100/53191/50099 score loc≈1 on the old scanner).
+
+Signal: the heal band has LOWER local sharpness (mean squared Laplacian) than
+the regions flanking it. We build per-row and per-col sharpness profiles, look
+for a localized VALLEY at the center (where pil splits the 2x2 grid / wraps),
+and score band_sharpness / flank_sharpness. Ratio << 1 => blurred seam.
+
+READ-ONLY. Never writes PG, never modifies a PNG. $0, local numpy+PIL.
+"""
+import argparse, json, os, subprocess, sys, time
+import numpy as np
+from PIL import Image
+
+GEN_DIR = "/Users/stevestudio2/Projects/wallco-ai/data/generated"
+
+# --- tunables (validated against the 2026-05-26 heal cohort) ---
+# The heal seam is a THIN blurred line at the EXACT geometric midline, flanked
+# by NORMAL-sharpness content on BOTH sides. Natural smooth regions are either
+# off-center or wide (one flank is also smooth) — that's how we tell them apart.
+CORE_FRAC   = 0.013   # half-width of the seam CORE band (~13px @1024 => 26px core)
+NF_LO_FRAC  = 0.018   # near-flank starts just outside the core
+NF_HI_FRAC  = 0.060   # near-flank ends here (~18..62px out each side)
+SEARCH_PX   = 9       # seam sits at center ± this (pil splits at H/2,W/2)
+FAIL_RATIO  = 0.45    # core < 45% of the WEAKER flank => seam
+WARN_RATIO  = 0.62
+FLANK_MIN   = 0.50    # both flanks must be >= this * global-median sharpness (genuinely sharp)
+THIN_RATIO  = 0.60    # core must be < 60% of the inner shoulder => razor-thin line, not a wide smooth zone
+DOWN_MAX    = 1024    # cap working size for speed
+
+
+def _sharpness_profiles(path):
+    im = Image.open(path).convert("L")
+    if max(im.size) > DOWN_MAX:
+        im.thumbnail((DOWN_MAX, DOWN_MAX))
+    a = np.asarray(im, dtype=np.float32)
+    H, W = a.shape
+    # 4-neighbour Laplacian on the interior
+    lap = np.zeros_like(a)
+    lap[1:-1, 1:-1] = (
+        -4.0 * a[1:-1, 1:-1]
+        + a[:-2, 1:-1] + a[2:, 1:-1]
+        + a[1:-1, :-2] + a[1:-1, 2:]
+    )
+    sq = lap * lap
+    Rh = sq.mean(axis=1)   # sharpness per row  -> finds horizontal seam (band of rows)
+    Rv = sq.mean(axis=0)   # sharpness per col  -> finds vertical seam
+    return Rh, Rv, H, W
+
+
+MAX_REGIONS = 2       # >2 valley regions on an axis => periodic motif gaps, not a seam
+
+
+def _axis(prof):
+    """Analyze one sharpness profile for a heal seam at the geometric midline.
+
+    A heal seam is a THIN blurred line at center, flanked by SHARP content on
+    both sides, and ISOLATED (a periodic motif pattern produces many such
+    valleys at the motif period — that's a pattern gap, not a defect).
+
+    Returns dict(ratio, flank_norm, pos, regions, center_valley)."""
+    n = len(prof)
+    c = n // 2
+    pos_v = prof[prof > 0]
+    gmed = float(np.median(pos_v)) if pos_v.size else 1.0
+    gmed = gmed if gmed > 1e-6 else 1.0
+    core = max(3, int(round(n * CORE_FRAC)))
+    nflo = max(core + 1, int(round(n * NF_LO_FRAC)))
+    nfhi = max(nflo + 4, int(round(n * NF_HI_FRAC)))
+
+    shoulder = max(core + 2, int(round(core * 2.2)))   # immediately outside the core
+
+    def valley_ratio(pos):
+        core_s = prof[pos - core:pos + core].mean()
+        left   = np.median(prof[pos - nfhi:pos - nflo])
+        right  = np.median(prof[pos + nflo:pos + nfhi])
+        flank  = min(left, right)            # far flanks: both sides must be sharp
+        # inner shoulders: sharpness must RECOVER right outside the core (thin line,
+        # not a wide smooth zone). A wide smooth region keeps the shoulder low too.
+        sl = np.median(prof[pos - shoulder:pos - core])
+        sr = np.median(prof[pos + core:pos + shoulder])
+        shoulder_min = min(sl, sr)
+        if flank <= 1e-6 or shoulder_min <= 1e-6:
+            return None, 0.0, 9.99
+        return core_s / flank, flank / gmed, core_s / shoulder_min
+
+    # strongest seam evidence within the center search window
+    best = (9.99, 0.0, 9.99, c)
+    for pos in range(c - SEARCH_PX, c + SEARCH_PX + 1):
+        if pos - nfhi < 0 or pos + nfhi > n:
+            continue
+        r, fn, thin = valley_ratio(pos)
+        if r is not None and r < best[0]:
+            best = (r, fn, thin, pos)
+    ratio, flank_norm, thin, pos = best
+
+    # count valley regions across the whole axis (periodicity guard)
+    hits = []
+    for p in range(nfhi, n - nfhi, 2):
+        r, fn, th = valley_ratio(p)
+        if r is not None and fn >= FLANK_MIN and r <= FAIL_RATIO and th <= THIN_RATIO:
+            hits.append(p)
+    regions, last = 0, -10 ** 9
+    for p in hits:
+        if p - last > 2 * core + 6:
+            regions += 1
+        last = p
+    return {"ratio": round(float(ratio), 3), "flank_norm": round(float(flank_norm), 2),
+            "thin": round(float(thin), 2), "pos": int(pos), "regions": int(regions)}
+
+
+def _verdict(ax):
+    if ax["regions"] > MAX_REGIONS:      # periodic motif gaps, not a seam
+        return "PASS"
+    if ax["flank_norm"] < FLANK_MIN:     # flanks not sharp -> wide/natural smooth zone
+        return "PASS"
+    if ax["thin"] > THIN_RATIO:          # not a razor-thin line -> wide smooth zone
+        return "PASS"
+    if ax["ratio"] <= FAIL_RATIO:
+        return "FAIL"
+    if ax["ratio"] <= WARN_RATIO:
+        return "WARN"
+    return "PASS"
+
+
+def scan(path):
+    Rh, Rv, H, W = _sharpness_profiles(path)
+    h, v = _axis(Rh), _axis(Rv)   # horizontal seam (rows) / vertical seam (cols)
+    vh, vv = _verdict(h), _verdict(v)
+    order = {"FAIL": 2, "WARN": 1, "PASS": 0}
+    verdict = vh if order[vh] >= order[vv] else vv
+    return {
+        "h_ratio": h["ratio"], "h_flank": h["flank_norm"], "h_regions": h["regions"], "h_verdict": vh,
+        "v_ratio": v["ratio"], "v_flank": v["flank_norm"], "v_regions": v["regions"], "v_verdict": vv,
+        "worst": min(h["ratio"], v["ratio"]), "verdict": verdict,
+        "axis": ("both" if vh == "FAIL" and vv == "FAIL" else ("h" if order[vh] >= order[vv] else "v")),
+    }
+
+
+def _resolve(local_path):
+    if local_path and os.path.exists(local_path):
+        return local_path
+    if local_path:
+        alt = os.path.join(GEN_DIR, os.path.basename(local_path))
+        if os.path.exists(alt):
+            return alt
+    return None
+
+
+def _cohort_rows():
+    sql = ("SELECT id, generator, COALESCE(local_path,'') FROM spoon_all_designs "
+           "WHERE is_published=true AND generator IN ('pil-mid-heal','pil-edge-heal') "
+           "ORDER BY id;")
+    out = subprocess.run(["psql", "dw_unified", "-tAc", sql],
+                         capture_output=True, text=True).stdout.strip().splitlines()
+    rows = []
+    for ln in out:
+        if ln.count("|") >= 2:
+            rid, gen, lp = ln.split("|", 2)
+            rows.append((int(rid), gen, lp))
+    return rows
+
+
+def main():
+    ap = argparse.ArgumentParser()
+    ap.add_argument("--path")
+    ap.add_argument("--id", type=int)
+    ap.add_argument("--cohort", action="store_true", help="scan all published heal designs")
+    ap.add_argument("--shard", type=int, default=0)
+    ap.add_argument("--shards", type=int, default=1)
+    ap.add_argument("--out", default="/Users/stevestudio2/Projects/wallco-ai/data/fuzzy-seam-scan.json")
+    ap.add_argument("--json", action="store_true")
+    args = ap.parse_args()
+
+    if args.path:
+        print(json.dumps(scan(args.path), indent=2)); return
+    if args.id:
+        sql = f"SELECT COALESCE(local_path,'') FROM spoon_all_designs WHERE id={args.id};"
+        lp = subprocess.run(["psql","dw_unified","-tAc",sql],capture_output=True,text=True).stdout.strip()
+        p = _resolve(lp)
+        if not p: print(json.dumps({"error":"png missing","id":args.id})); return
+        r = scan(p); r["id"]=args.id
+        print(json.dumps(r, indent=2)); return
+
+    if args.cohort:
+        rows = _cohort_rows()
+        rows = [r for i, r in enumerate(rows) if i % args.shards == args.shard]
+        results, missing, t0 = [], 0, time.time()
+        for i, (rid, gen, lp) in enumerate(rows):
+            p = _resolve(lp)
+            if not p:
+                missing += 1; continue
+            try:
+                r = scan(p); r["id"]=rid; r["generator"]=gen; r["file"]=os.path.basename(p)
+                results.append(r)
+            except Exception as e:
+                missing += 1
+            if (i+1) % 250 == 0:
+                print(f"  ...{i+1}/{len(rows)} ({time.time()-t0:.0f}s)", flush=True, file=sys.stderr)
+        out = args.out if args.shards == 1 else args.out.replace(".json", f".shard{args.shard}.json")
+        json.dump({"results":results,"missing":missing}, open(out,"w"))
+        fails = sum(1 for r in results if r["verdict"]=="FAIL")
+        warns = sum(1 for r in results if r["verdict"]=="WARN")
+        print(f"shard {args.shard}/{args.shards}: scanned={len(results)} FAIL={fails} WARN={warns} missing={missing} -> {out}", flush=True)
+        return
+
+    ap.print_help()
+
+
+if __name__ == "__main__":
+    main()

← 306c86f task#25: chinoiserie tile-class SAMPLE on ComfyUI (Mac2, cir  ·  back to Wallco Ai  ·  Fix fuzzy edges + blurred center-cross in all 3 seam healers fdd35e6 →