[object Object]

← back to Wallco Ai

Add controlnet-anchored-reroll-2: seam-retry loop for the two WARN roots (54076/54266); in-mem motif edge-verify, one INSERT per root, tighter crop, scope-locked to the 2 approved roots

9ec758eff13e75164f795de3d76a1e7bef3ebfe3 · 2026-06-11 16:41:24 -0700 · Steve Abrams

Files touched

Diff

commit 9ec758eff13e75164f795de3d76a1e7bef3ebfe3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jun 11 16:41:24 2026 -0700

    Add controlnet-anchored-reroll-2: seam-retry loop for the two WARN roots (54076/54266); in-mem motif edge-verify, one INSERT per root, tighter crop, scope-locked to the 2 approved roots
---
 scripts/controlnet-anchored-reroll-2.py | 252 ++++++++++++++++++++++++++++++++
 1 file changed, 252 insertions(+)

diff --git a/scripts/controlnet-anchored-reroll-2.py b/scripts/controlnet-anchored-reroll-2.py
new file mode 100644
index 0000000..fd69de4
--- /dev/null
+++ b/scripts/controlnet-anchored-reroll-2.py
@@ -0,0 +1,252 @@
+#!/usr/bin/env python3
+"""controlnet-anchored-reroll-2.py — TARGETED re-roll of EXACTLY two roots that
+missed the brief in the 5-root ControlNet sample (Steve-approved 2026-06-11):
+
+  54076 orangutan — WARN: a real visible white horizontal seam + the subject was
+                    sliced inside a glass rim. (current child 10000013)
+  54266 tree frog — WARN: ~50% coverage, busy/abstract; a champagne-tower
+                    structure dominated and the frog was lost. (child 10000012)
+
+This is a SURGICAL superset of controlnet-anchored-redo.py. It reuses that
+script's proven machinery (cropped-focal canny hint, SeamlessTile +
+MakeCircularVAE circular-pad txt2img at denoise 1.0 = zero smear by
+construction, deterministic real-fibre composite) and adds the THREE things the
+sample exposed as missing:
+
+  1. SEAM-RETRY LOOP (the core fix). The 5-root sample WARNed because the
+     circular-pad txt2img sometimes lands a motif whose structure crosses a tile
+     boundary badly — verified: both failing motif-only tiles scored WARN with
+     tiled_cross_seam_max 37-41 (>> warn_tol). So we now re-roll the SEED and
+     regenerate the MOTIF-ONLY tile, re-running verify-edge-seamless IN MEMORY on
+     the raw motif array, and only proceed to composite + DB-insert once the
+     motif clears cross-seam PASS on BOTH axes. Cap MAX_RETRIES (default 6). If a
+     root never clears, we STOP and report it — we do NOT ship a WARN and do NOT
+     expand scope.
+
+  2. MINIMIZE ORPHANS. all_designs lives in publication dw_unified_pub with NO
+     replica identity / PK, so dw_admin can only INSERT (never UPDATE/DELETE) —
+     a failed attempt's row can never be cleaned up. Therefore the ENTIRE retry
+     loop runs at the IMAGE/FILE level; we INSERT the DB row ONLY for the final
+     passing candidate. Exactly one new id per root, max. (Per Steve's CRITICAL.)
+
+  3. TIGHTER CROP / SMALLER MOTIF so motif copies do not touch the tile boundary
+     (a boundary-touching motif is what WARNs the line-art). Smaller crop_frac +
+     motif_px keeps the figure well inside, leaving open ground all the way to
+     the wrap line. Per-root overrides below also drop the clutter:
+       - frog: lower coverage target (reject too-busy rolls), single focal frog;
+       - orangutan: smaller motif + lower canny strength so the figure is NOT
+         redrawn inside a glass rim, and the open prompt drives layout.
+
+SACRED ROOTS: never overwrites a root. Final candidate -> NEW file + NEW id,
+is_published=FALSE, user_removed=FALSE, parent_design_id=<root>. Never published
+live. The settlement gate is untouched (these are children of already-passed
+roots; no new prompt class). DATABASE_URL self-resolves, never echoed. Does NOT
+use make_seamless.py / force-edge-seamless.py (smear sources).
+
+ONLY operates on 54076 and 54266. Refuses any other id.
+
+Usage:
+  python3 scripts/controlnet-anchored-reroll-2.py
+  python3 scripts/controlnet-anchored-reroll-2.py --max-retries 6
+  python3 scripts/controlnet-anchored-reroll-2.py --ids 54076   # one at a time
+"""
+import argparse, datetime, json, os, sys, time
+from pathlib import Path
+
+import numpy as np
+from PIL import Image
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+GENDIR = os.path.join(ROOT, 'data', 'generated')
+QUEUE = os.path.join(ROOT, 'data', 'seam-fix-queue.jsonl')
+
+sys.path.insert(0, os.path.join(ROOT, 'scripts'))
+from importlib import import_module
+cnar = import_module('controlnet-anchored-redo')    # reuse the proven machinery
+sfv = import_module('seam-fix-variants')            # psql
+edgeverify = import_module('verify-edge-seamless')  # true-toroidal edge gate (in-mem)
+
+# HARD SCOPE: only these two roots may be touched.
+ALLOWED_ROOTS = {54076, 54266}
+
+# Per-root config. Each root gets its own ground + crop/motif/canny tuning that
+# directly addresses its specific WARN. Tighter crop_frac + smaller motif_px =
+# figure sits well inside the boundary = no boundary-touching structure to break
+# the wrap, and more open negative space around it.
+ROOT_CFG = {
+    54076: {  # orangutan: kill the white horizontal seam + stop the glass-rim slice
+        'ground': 'natural linen',
+        'crop_frac': 0.26,     # tighter than the sample's 0.32 -> a single focal blob
+        'motif_px': 150,       # smaller than 175 -> motif lands well inside the frame
+        'cn_strength': 0.40,   # lower than 0.5 -> the open prompt wins layout vs. glass
+        'cn_end': 0.45,        # release control earlier so surround opens up
+        'cov_lo': 8.0, 'cov_hi': 28.0,   # accept an open, non-busy motif tile
+    },
+    54266: {  # frog: drop the champagne-tower clutter, single focal frog, low coverage
+        'ground': 'raffia',
+        'crop_frac': 0.24,     # very tight -> grab the frog, not the coupe tower
+        'motif_px': 150,
+        'cn_strength': 0.38,   # weak anchor: the tower structure must NOT come back
+        'cn_end': 0.42,
+        'cov_lo': 8.0, 'cov_hi': 30.0,   # Steve's ~20-30% coverage target, generous space
+    },
+}
+
+# Coverage gate widened a touch — the real discriminator is the edge verdict;
+# coverage just rejects an over-busy roll (too high) or an empty one (too low).
+
+
+def _verify_array(arr, pass_tol=edgeverify.PASS_TOL, warn_tol=edgeverify.WARN_TOL):
+    """In-memory true-toroidal verify of a motif array. No file round-trip."""
+    return edgeverify.verify(np.asarray(arr)[:, :, :3], pass_tol, warn_tol)
+
+
+def reroll_one(root, cfg, max_retries, pass_tol, warn_tol):
+    """Image/file-level retry loop for ONE root. Returns the result dict for the
+    FINAL passing candidate (and inserts exactly one DB row), or a dict with
+    ok=False and the per-attempt edge history if it never clears."""
+    os.makedirs(GENDIR, exist_ok=True)
+    meta = cnar.root_meta_one(root)
+    src = meta.get('local_path')
+    if not src or not os.path.exists(src):
+        return {'root_id': root, 'ok': False, 'error': 'no source file'}
+
+    prompt = meta.get('prompt') or ''
+    subject = cnar.extract_subject(prompt)
+    animal = cnar.short_animal(prompt)
+    positive, negative = cnar.build_prompt(subject, animal)
+
+    root_img = Image.open(src).convert('RGB')
+    # The focal crop is deterministic per root, so re-rolling the crop position
+    # would just return the same window. We re-roll the SEED (and, on later
+    # attempts, gently jitter the canny strength down) to get a different motif
+    # while keeping the same open prompt + circular-pad seamless construction.
+    focal, crop_box = cnar.find_focal_crop(root_img, cfg['crop_frac'])
+    hint = cnar.build_canny_hint(focal, cfg['motif_px'])
+    hint_png = os.path.join(GENDIR, f'{int(time.time()*1000)}_{root}_reroll2_hint.png')
+    hint.save(hint_png, 'PNG')
+
+    attempts = []
+    ground = cfg['ground']
+    for attempt in range(1, max_retries + 1):
+        seed = int.from_bytes(os.urandom(4), 'big') % (2**31 - 1)
+        # Late attempts: nudge the canny strength further down so a stubborn
+        # boundary-crossing structure (glass rim / tower strut) is given even
+        # less authority and the model is freer to lay out an open, wrapping tile.
+        cn_strength = max(0.30, cfg['cn_strength'] - 0.03 * (attempt - 1))
+        cn_end = cfg['cn_end']
+        ts = int(time.time() * 1000)
+        motif_png = os.path.join(GENDIR, f'{ts}_{seed}_reroll2_motif.png')
+        try:
+            cnar.gen_controlnet_seamless(positive, negative, seed, hint_png,
+                                         cn_strength, cn_end, motif_png)
+        except Exception as e:
+            attempts.append({'attempt': attempt, 'seed': seed, 'error': str(e)[:200]})
+            continue
+
+        # --- in-memory edge verify on the RAW MOTIF tile (the real gate) ---
+        motif_arr = np.asarray(Image.open(motif_png).convert('RGB'))
+        ev = _verify_array(motif_arr, pass_tol, warn_tol)
+        base_hex = cnar.dominant_hex(motif_png)
+        cov = cnar.coverage_pct(motif_png, base_hex)
+        h_v = ev['axes']['horizontal']['verdict']
+        v_v = ev['axes']['vertical']['verdict']
+        cov_ok = cfg['cov_lo'] <= cov <= cfg['cov_hi']
+        motif_pass = (ev['verdict'] == 'PASS' and h_v == 'PASS' and v_v == 'PASS')
+        attempts.append({
+            'attempt': attempt, 'seed': seed, 'cn_strength': round(cn_strength, 2),
+            'motif_edge': ev['verdict'], 'h': h_v, 'v': v_v,
+            'h_tiled': ev['axes']['horizontal']['tiled_cross_seam_max'],
+            'v_tiled': ev['axes']['vertical']['tiled_cross_seam_max'],
+            'coverage_pct': round(cov, 1), 'cov_ok': cov_ok,
+            'isolated_motif': ev['isolated_motif'], 'motif': motif_png,
+        })
+        print(f"  root {root} attempt {attempt}/{max_retries}: motif edge={ev['verdict']} "
+              f"h={h_v} v={v_v} cov={cov:.1f}% cov_ok={cov_ok} seed={seed}", file=sys.stderr)
+
+        if not (motif_pass and cov_ok):
+            continue   # re-roll; do NOT composite, do NOT insert
+
+        # --- motif cleared BOTH axes + coverage OK: finalize this candidate ---
+        final_png = os.path.join(GENDIR, f'{ts}_{seed}_reroll2.png')
+        cnar.composite_real_fibre(motif_png, ground, base_hex, final_png)
+        # Re-verify the FINAL composite (the bg-swap provably wraps, but verify
+        # the written file so the recorded verdict is on the shipped pixels).
+        fev = edgeverify.verify_path(Path(final_png), pass_tol, warn_tol)
+        fh = fev['axes']['horizontal']['verdict']
+        fv = fev['axes']['vertical']['verdict']
+        if not (fev['verdict'] == 'PASS' and fh == 'PASS' and fv == 'PASS'):
+            # Composite somehow regressed the wrap — record + keep rerolling.
+            attempts[-1]['composite_edge'] = fev['verdict']
+            attempts[-1]['composite_regressed'] = True
+            print(f"  root {root} attempt {attempt}: composite regressed to "
+                  f"{fev['verdict']} (h={fh} v={fv}) — rerolling", file=sys.stderr)
+            continue
+
+        # INSERT exactly one DB row — only now, only for this passing candidate.
+        new_id = cnar.insert_child(root, final_png, positive, negative, ground, cn_strength)
+        return {
+            'ts': datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z'),
+            'root_id': root, 'variant': 'controlnet-anchored-reroll-2',
+            'generator': 'controlnet-anchored-reroll-2',
+            'category': meta.get('category'), 'subject': subject, 'animal': animal,
+            'ground': ground, 'seed': seed, 'base_hex': base_hex,
+            'cn_strength': round(cn_strength, 2), 'cn_end': cn_end,
+            'crop_box': crop_box, 'coverage_pct': round(cov, 1),
+            'attempts_used': attempt, 'attempts': attempts,
+            'technique': ('canny-SDXL-CN-anchored native-seamless txt2img '
+                          '(SeamlessTile+MakeCircularVAE, denoise 1.0, tighter focal '
+                          'canny crop) + SEAM-RETRY-until-motif-PASS-both-axes + '
+                          'deterministic real-fibre composite — NO smear'),
+            'edge_verdict': fev['verdict'], 'edge_seamless': bool(fev['ok']),
+            'edge_axes': {'h': fev['axes']['horizontal'], 'v': fev['axes']['vertical']},
+            'motif_edge_verdict': ev['verdict'],
+            'new_id': new_id, 'hint': hint_png, 'motif': motif_png, 'out': final_png,
+            'ok': True,
+        }
+
+    # Never cleared within the cap.
+    return {
+        'root_id': root, 'ok': False,
+        'error': f'no motif cleared PASS-both-axes + coverage in {max_retries} retries',
+        'ground': ground, 'attempts': attempts,
+    }
+
+
+def main():
+    ap = argparse.ArgumentParser()
+    ap.add_argument('--ids', default='54076,54266',
+                    help='subset of the two allowed roots (default both)')
+    ap.add_argument('--max-retries', type=int, default=6)
+    ap.add_argument('--pass-tol', type=float, default=edgeverify.PASS_TOL)
+    ap.add_argument('--warn-tol', type=float, default=edgeverify.WARN_TOL)
+    args = ap.parse_args()
+
+    ids = [int(x) for x in args.ids.split(',') if x.strip()]
+    bad = [i for i in ids if i not in ALLOWED_ROOTS]
+    if bad:
+        print(json.dumps({'error': f'roots {bad} are OUT OF SCOPE — only '
+                          f'{sorted(ALLOWED_ROOTS)} are permitted'}), file=sys.stderr)
+        sys.exit(2)
+
+    results = []
+    for rid in ids:
+        cfg = ROOT_CFG[rid]
+        try:
+            r = reroll_one(rid, cfg, args.max_retries, args.pass_tol, args.warn_tol)
+        except Exception as e:
+            r = {'root_id': rid, 'ok': False, 'error': str(e), 'ground': cfg['ground']}
+        results.append(r)
+        if r.get('ok'):
+            with open(QUEUE, 'a') as q:
+                q.write(json.dumps(r) + '\n')
+        v = r.get('edge_verdict', r.get('error', '?'))
+        print(f"=> root {rid}: {'OK' if r.get('ok') else 'FAIL'} edge={v} "
+              f"new_id={r.get('new_id')} attempts={r.get('attempts_used')}", file=sys.stderr)
+
+    print(json.dumps(results, indent=2))
+    sys.exit(0 if all(r.get('ok') for r in results) else 1)
+
+
+if __name__ == '__main__':
+    main()

← b9e1b4d TODO: ControlNet redo sample landed (1 PASS, 2 near-miss WAR  ·  back to Wallco Ai  ·  reroll-2: DTD verdict A — drop canny for the 2 glassware-ent 95d401a →