[object Object]

← back to Wallco Ai

element_copy_heal v2: wrap-edge-only crisp palette-reconcile (DTD verdict C)

0fdef022aef6fc7e629b6d1e152b36a825f6a7d6 · 2026-06-12 09:40:25 -0700 · Steve Abrams

Replaces v1 half-roll patch-copy which sliced a visible line through the frog's
face on root 2766 and worsened the top/bottom edge. v2 heals ONLY the 4 physical
tile-wrap edges, and only the columns/rows where the outermost wrap pixels carry
different palette labels (a motif crossing the wrap). Each rewritten pixel is
snapped to a crisp dominant-palette color (no averaging = no smudge), only the
disagreeing lines are touched (no drawn line), and the interior mids (x=W/2,
y=H/2 = motif edges, NOT seams) are never touched. On 2766: wrap-column
disagreement 17 -> 0, frog motif fully preserved, no smudge/line.

Files touched

Diff

commit 0fdef022aef6fc7e629b6d1e152b36a825f6a7d6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jun 12 09:40:25 2026 -0700

    element_copy_heal v2: wrap-edge-only crisp palette-reconcile (DTD verdict C)
    
    Replaces v1 half-roll patch-copy which sliced a visible line through the frog's
    face on root 2766 and worsened the top/bottom edge. v2 heals ONLY the 4 physical
    tile-wrap edges, and only the columns/rows where the outermost wrap pixels carry
    different palette labels (a motif crossing the wrap). Each rewritten pixel is
    snapped to a crisp dominant-palette color (no averaging = no smudge), only the
    disagreeing lines are touched (no drawn line), and the interior mids (x=W/2,
    y=H/2 = motif edges, NOT seams) are never touched. On 2766: wrap-column
    disagreement 17 -> 0, frog motif fully preserved, no smudge/line.
---
 scripts/element_copy_heal.py | 180 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 180 insertions(+)

diff --git a/scripts/element_copy_heal.py b/scripts/element_copy_heal.py
new file mode 100644
index 0000000..57b433e
--- /dev/null
+++ b/scripts/element_copy_heal.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+r"""
+element_copy_heal (v2) — CRISP tile-seam heal that fixes ONLY the real physical
+tile-WRAP edges, with NO averaging/blur band (the old heal_band_mid smudge that
+Steve rejected 2026-06-12) and NO drawn discontinuity lines (the v1 half-roll
+patch-copy sliced a visible line through the frog's face on root 2766).
+
+DTD verdict C (2026-06-12, Claude+Codex unanimous): only the 4 physical wrap
+boundaries (x=0<->W, y=0<->H) are true tile joins; x=W/2 / y=H/2 are arbitrary
+interior sample lines that must NEVER be healed as seams (doing so cut the frog's
+face in v1). Motif continuity ACROSS a wrap is provided by the neighbor repeat,
+not by interior smoothing or synthetic cuts.
+
+METHOD (low-color / engraving path — the common wallco case):
+  1. Quantize the whole tile to its dominant palette -> a crisp label per pixel.
+     We only WRITE a thin edge band; the interior keeps original RGB so stipple
+     shading is preserved.
+  2. top<->bottom wrap: for each COLUMN where the outermost top pixel and
+     outermost bottom pixel carry DIFFERENT palette labels (a motif crosses the
+     wrap), vote a single wrap label across both outer bands and snap the outer
+     `band` px of BOTH edges on that column to that crisp palette color. Every
+     written pixel is a crisp palette color (no averaged smudge); only the
+     disagreeing columns are touched (no continuous drawn line); interior
+     untouched.
+  3. left<->right wrap: transpose.
+  4. interior mids: skipped entirely.
+
+Non-low-color designs fall back to a wrap-only crisp mirror on disagreeing
+lines (still no interior, still no full-image blur).
+
+Public entry point:
+    heal_array(arr, boxes, *, two_tone=None, edge_band=12) -> healed uint8 array
+`boxes` from seam-defect-boxes.py (used only to decide WHICH wrap edges need
+work; interior boxes ignored).
+
+NEVER imports make_seamless.py / force-edge-seamless.py (smear sources).
+NEVER calls cv2.inpaint / diffusion (those blur). Pure crisp palette-reconcile.
+"""
+from __future__ import annotations
+import numpy as np
+from PIL import Image
+from collections import Counter
+
+EDGE_BAND_DEFAULT = 12
+LOWCOLOR_MAX = 6
+
+
+def _rgb_to_lab(arr):
+    rgb = arr.astype('float32') / 255.0
+    def f(t):
+        return np.where(t > 0.04045, ((t + 0.055) / 1.055) ** 2.4, t / 12.92)
+    rl, gl, bl = f(rgb[..., 0]), f(rgb[..., 1]), f(rgb[..., 2])
+    X = rl * 0.4124564 + gl * 0.3575761 + bl * 0.1804375
+    Y = rl * 0.2126729 + gl * 0.7151522 + bl * 0.0721750
+    Z = rl * 0.0193339 + gl * 0.1191920 + bl * 0.9503041
+    def fl(t):
+        d = 6 / 29
+        return np.where(t > d ** 3, t ** (1 / 3), t / (3 * d * d) + 4 / 29)
+    fx, fy, fz = fl(X / 0.95047), fl(Y / 1.0), fl(Z / 1.08883)
+    return np.stack([116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)], axis=-1)
+
+
+def dominant_palette(arr, k=8, min_pct=3.0):
+    im = Image.fromarray(arr).convert('RGB')
+    q = im.quantize(colors=k, method=Image.Quantize.MEDIANCUT,
+                    dither=Image.Dither.NONE).convert('RGB')
+    flat = np.asarray(q).reshape(-1, 3)
+    cnt = Counter(map(tuple, flat))
+    total = sum(cnt.values())
+    return [c for c, n in cnt.most_common() if 100.0 * n / total >= min_pct]
+
+
+def is_low_color(arr):
+    return len(dominant_palette(arr)) <= LOWCOLOR_MAX
+
+
+def _palette_labels(arr, palette):
+    pal = np.array(palette, dtype='uint8')
+    pal_lab = _rgb_to_lab(pal.reshape(1, -1, 3)).reshape(-1, 3)
+    lab = _rgb_to_lab(arr).reshape(-1, 3)
+    d = ((lab[:, None, :] - pal_lab[None, :, :]) ** 2).sum(-1)
+    idx = d.argmin(1).reshape(arr.shape[:2])
+    return idx, pal
+
+
+def _reconcile_wrap(out, labels, pal, axis, band):
+    """Make the two opposite wrap edges along axis carry the SAME crisp palette
+    content where they currently disagree. axis='h': top<->bottom (write columns);
+    axis='v': left<->right (write rows). Returns count of lines rewritten."""
+    H, W = labels.shape
+    n = 0
+    if axis == 'h':
+        disagree = labels[0, :] != labels[H - 1, :]
+        top_band = labels[0:band, :]
+        bot_band = labels[H - band:H, :]
+        for x in np.where(disagree)[0]:
+            votes = np.concatenate([top_band[:, x], bot_band[:, x]])
+            wrap = int(np.bincount(votes).argmax())
+            color = pal[wrap]
+            out[0:band, x, :] = color
+            out[H - band:H, x, :] = color
+            n += 1
+    else:
+        disagree = labels[:, 0] != labels[:, W - 1]
+        left_band = labels[:, 0:band]
+        right_band = labels[:, W - band:W]
+        for y in np.where(disagree)[0]:
+            votes = np.concatenate([left_band[y, :], right_band[y, :]])
+            wrap = int(np.bincount(votes).argmax())
+            color = pal[wrap]
+            out[y, 0:band, :] = color
+            out[y, W - band:W, :] = color
+            n += 1
+    return n
+
+
+def _wrap_mirror_disagree(out, arr, axis, band):
+    H, W = arr.shape[:2]
+    g = arr.astype('float32') @ np.array([0.299, 0.587, 0.114])
+    n = 0
+    if axis == 'h':
+        disagree = np.abs(g[0, :] - g[H - 1, :]) > 24
+        for x in np.where(disagree)[0]:
+            src = arr[band, x, :]
+            out[0:band, x, :] = src
+            out[H - band:H, x, :] = src
+            n += 1
+    else:
+        disagree = np.abs(g[:, 0] - g[:, W - 1]) > 24
+        for y in np.where(disagree)[0]:
+            src = arr[y, band, :]
+            out[y, 0:band, :] = src
+            out[y, W - band:W, :] = src
+            n += 1
+    return n
+
+
+def heal_array(arr, boxes, *, two_tone=None, edge_band=EDGE_BAND_DEFAULT):
+    """Heal ONLY the physical wrap edges flagged in boxes; interior mid boxes are
+    ignored (motif edges, not seams). Returns a NEW uint8 array."""
+    out = arr.copy()
+    if two_tone is None:
+        two_tone = is_low_color(arr)
+    have_ledge = any(b['kind'] in ('left_edge', 'right_edge') for b in boxes)
+    have_tedge = any(b['kind'] in ('top_edge', 'bottom_edge') for b in boxes)
+    if two_tone:
+        palette = dominant_palette(arr)
+        labels, pal = _palette_labels(arr, palette)
+        if have_tedge:
+            _reconcile_wrap(out, labels, pal, 'h', edge_band)
+        if have_ledge:
+            _reconcile_wrap(out, labels, pal, 'v', edge_band)
+    else:
+        if have_tedge:
+            _wrap_mirror_disagree(out, arr, 'h', edge_band)
+        if have_ledge:
+            _wrap_mirror_disagree(out, arr, 'v', edge_band)
+    return out
+
+
+if __name__ == '__main__':
+    import argparse, json, sys
+    from pathlib import Path
+    sys.path.insert(0, str(Path(__file__).resolve().parent))
+    seam = __import__('seam-defect-boxes')
+    ap = argparse.ArgumentParser()
+    ap.add_argument('--path', required=True)
+    ap.add_argument('--out', required=True)
+    ap.add_argument('--edge-band', type=int, default=EDGE_BAND_DEFAULT)
+    a = ap.parse_args()
+    src = np.asarray(Image.open(a.path).convert('RGB'))
+    scan_b = seam.scan(src)
+    healed = heal_array(src, scan_b['boxes'], edge_band=a.edge_band)
+    Image.fromarray(healed).save(a.out)
+    scan_a = seam.scan(healed)
+    print(json.dumps({
+        'before': scan_b['scores'], 'before_verdict': scan_b['verdict'],
+        'after': scan_a['scores'], 'after_verdict': scan_a['verdict'],
+        'two_tone': is_low_color(src), 'out': a.out,
+    }))

← d99cb67 Fix variants montage 0/90: count PASS rows from both i2i + o  ·  back to Wallco Ai  ·  TODO: seam joint-heal v2 + four-horsemen verdict + non-tilin af64e05 →