← 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
A scripts/controlnet-anchored-reroll-2.py
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 →