← back to Wallco Ai
variants-9roots: PIVOT to img2img-low-denoise cleanup — variants now preserve the root's lush composition+palette and just clean it (kill smudge/seam), denoise sweep 0.34-0.61, distinct 'variants-i2i-cleanup' generator tag
7d5a23e482b4adb3d075a3a85eb49f058f29810a · 2026-06-12 08:44:38 -0700 · Steve Abrams
Files touched
M scripts/variants-9roots.py
Diff
commit 7d5a23e482b4adb3d075a3a85eb49f058f29810a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jun 12 08:44:38 2026 -0700
variants-9roots: PIVOT to img2img-low-denoise cleanup — variants now preserve the root's lush composition+palette and just clean it (kill smudge/seam), denoise sweep 0.34-0.61, distinct 'variants-i2i-cleanup' generator tag
---
scripts/variants-9roots.py | 217 ++++++++++++++++++++++-----------------------
1 file changed, 108 insertions(+), 109 deletions(-)
diff --git a/scripts/variants-9roots.py b/scripts/variants-9roots.py
index 182bd97..bac1053 100644
--- a/scripts/variants-9roots.py
+++ b/scripts/variants-9roots.py
@@ -1,35 +1,35 @@
#!/usr/bin/env python3
-"""variants-9roots.py — generate 10 COMPOSITION variants per Wave-1 root for Steve
-to CURATE (Steve, 2026-06-12: "make 10 each for variety to choose. review live as
-its created completely new skus").
-
-DESIGN CHANGE (2026-06-12): Steve curates the variety himself, so the generator
-enforces ONLY the non-negotiable hard rule — the tile must be EDGE-SEAMLESS on both
-axes (his original "all edges line up and repeat" requirement). Coverage and
-subject-presence are NO LONGER auto-rejects — they are Steve's visual call in the
-curator. This lifts the ~80% rejection of the strict triple-gate so we actually land
-~10 children per root instead of ~1.
-
-Per root we sweep motif SCALE (motif_px 120->210) + focal crop for composition
-variety; for each step we re-roll the seed until the raw motif tile clears
-EDGE-PASS on BOTH axes (in-memory verify-edge-seamless), then composite onto the
-root's held-constant real natural-fibre ground, re-verify the written composite,
-and INSERT exactly one child SKU. Up to 10 children per root.
+"""variants-9roots.py — generate 10 CLEANUP variants per Wave-1 root for Steve to
+curate (Steve, 2026-06-12: "the new variants should be cleaning up the root item ...
+look like the root, just cleaner. make 10 each for variety to choose").
+
+METHOD (corrected 2026-06-12 after two wrong attempts): the variant must LOOK LIKE
+THE ROOT, only cleaner + seamless — NOT a sparse isolated motif. The ControlNet /
+focal-crop seam-fix pipeline was the wrong tool (it manufactures sparse single-motif
+tiles). The right tool is IMG2IMG at low denoise: VAEEncode the ROOT, KSampler at
+denoise<1 through SeamlessTile + MakeCircularVAE (circular-pad) → the output keeps
+the root's full composition + palette, repaints out the smudge/seam, and tiles
+seamlessly by construction. (Proven on root 2656 @ denoise 0.45 — kept the lush
+red-panda botanical, killed the seam-block + smears.)
+
+We sweep DENOISE across the 10 variants: low (~0.34) = barely-cleaned faithful,
+high (~0.61) = more repainted / looser / cleaner. Steve picks the cleanup level he
+likes per root. EDGE-SEAMLESS is the hard gate (his "edges line up" rule); the
+img2img is seamless by construction but we still verify the written pixels.
SACRED ROOTS: never overwrites a root. Every variant -> NEW file + NEW id,
-is_published=FALSE, user_removed=FALSE, parent_design_id=<root>. Never published.
-all_designs is INSERT-only — a roll that never clears edges inserts NOTHING (no
-orphan). DATABASE_URL self-resolves, never echoed. Does NOT use make_seamless.py /
-force-edge-seamless.py (smear sources). Settlement gate untouched (children of
-already-passed roots, same prompt class).
+is_published=FALSE, user_removed=FALSE, parent_design_id=<root>. NEW generator tag
+'variants-i2i-cleanup' so the variant curator shows ONLY these (not the old sparse
+junk). all_designs INSERT-only — a roll that fails the edge gate inserts NOTHING.
+DATABASE_URL self-resolves, never echoed. Does NOT use make_seamless.py /
+force-edge-seamless.py. Settlement gate untouched (children of passed roots).
-Resumable: a (root,variant) cell already recorded in the ledger is skipped.
+Resumable: a (root,variant) cell already in the ledger is skipped.
Usage:
- python3 scripts/variants-9roots.py # 9 roots x 10 variants
- python3 scripts/variants-9roots.py --max-retries 6 # seeds per cell
- python3 scripts/variants-9roots.py --roots 2656 # one root
- python3 scripts/variants-9roots.py --contact-only # rebuild contact sheet
+ python3 scripts/variants-9roots.py # 9 roots x 10 variants
+ python3 scripts/variants-9roots.py --roots 2656 # one root first
+ python3 scripts/variants-9roots.py --contact-only
"""
import argparse, datetime, json, os, sys, time
from pathlib import Path
@@ -41,93 +41,81 @@ GENDIR = os.path.join(ROOT, 'data', 'generated')
sys.path.insert(0, os.path.join(ROOT, 'scripts'))
from importlib import import_module
cnar = import_module('controlnet-anchored-redo')
+nsr = import_module('native-seamless-redo') # gen_img2img_seamless
+sfv = import_module('seam-fix-variants') # psql
edgeverify = import_module('verify-edge-seamless')
LEDGER = os.path.join(ROOT, 'data', 'variants-9roots.jsonl')
CONTACT = '/tmp/variants_9roots.png'
+GEN_TAG = 'variants-i2i-cleanup'
-# 9 Wave-1 roots, ground HELD CONSTANT per root (Steve: hold ground, vary comp).
+# 9 Wave-1 roots, ground recorded per root (metadata only — img2img keeps the
+# root's OWN ground; we do NOT swap in a fibre ground, that would destroy the
+# root look Steve wants preserved).
ROOT_GROUND = {
2656: 'raffia', 2766: 'natural linen', 3391: 'sisal',
53999: 'grasscloth', 54002: 'raffia', 54092: 'sisal',
54110: 'paperweave', 54112: 'grasscloth', 54290: 'paperweave',
}
-# 10 composition steps — motif SCALE is the lever (sizes the canny hint → motif
-# size + spacing). Small motif = busy/dense repeat; large = open/grand. cn_strength
-# eases down as the motif grows so the bigger figure keeps open ground around it.
-VARIANTS = []
-for i in range(10):
- VARIANTS.append({
- 'v': i + 1,
- 'motif_px': 120 + 10 * i,
- 'crop_frac': round(0.22 + 0.013 * i, 3),
- 'cn_strength': round(0.52 - 0.01 * i, 2),
- 'cn_end': 0.55,
- })
-
-
-def edge_only_variant(root, vcfg, ground, max_retries, pass_tol, warn_tol):
- """Roll seeds until the motif tile clears EDGE-PASS on BOTH axes (the ONLY hard
- gate), then composite onto the fibre ground, re-verify, and INSERT one child.
- Returns the result dict (ok=True with new_id/out on success)."""
- 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')
- focal, crop_box = cnar.find_focal_crop(root_img, vcfg['crop_frac'])
- hint = cnar.build_canny_hint(focal, vcfg['motif_px'])
- hint_png = os.path.join(GENDIR, f"{int(time.time()*1000)}_{root}_var_hint.png")
- hint.save(hint_png, 'PNG')
-
+# 10 cleanup steps — DENOISE sweep. Low = faithful (barely cleaned), high = more
+# repainted/cleaner. Centered on the proven-good 0.45 band.
+VARIANTS = [{'v': i + 1, 'denoise': round(0.34 + 0.03 * i, 2)} for i in range(10)]
+
+CLEAN_EMPHASIS = (', cleaned up and crisp, no smudges, no muddy blending, no haze, '
+ 'no seams, refined wallpaper, preserve the original composition and palette')
+
+
+def insert_i2i(root, out_path, positive, negative, denoise):
+ """INSERT one cleanup child — replicates cnar.insert_child's bounded explicit-id
+ INSERT (dw_admin can't touch the sequence; all_designs is INSERT-only) but stamps
+ the DISTINCT generator 'variants-i2i-cleanup' + an i2i tag so the variant curator
+ can show only these. Inherits category/kind/dims/dominant_hex/seed from the root."""
+ pos = positive.replace("'", "''")
+ neg = negative.replace("'", "''")
+ new_id = int(sfv.psql(
+ "WITH nid AS (SELECT COALESCE(MAX(id),0)+1 AS id FROM all_designs) "
+ "INSERT INTO all_designs "
+ "(id, category, kind, prompt, negative_prompt, local_path, image_url, dominant_hex, "
+ " width_in, height_in, seed, tags, is_published, user_removed, generator, "
+ " parent_design_id, source_dw_sku) "
+ "SELECT nid.id, "
+ f" s.category, s.kind, '{pos}', '{neg}', '{out_path}', "
+ " '/designs/img/by-id/' || nid.id, s.dominant_hex, "
+ " s.width_in, s.height_in, s.seed, "
+ " (COALESCE(s.tags, ARRAY[]::text[])) || "
+ f" ARRAY['{GEN_TAG}','img2img-seamless','denoise-{denoise}','i2i-cleanup-from-{root}']::text[], "
+ f" FALSE, FALSE, '{GEN_TAG}', {root}, s.source_dw_sku "
+ f"FROM (SELECT * FROM all_designs WHERE id={root} LIMIT 1) s, nid RETURNING id;"))
+ return new_id
+
+
+def cleanup_variant(root, vcfg, max_retries, pass_tol, warn_tol, src1024_png, positive, negative):
+ """img2img cleanup of the ROOT at vcfg['denoise']; gate ONLY on edge-seamless
+ (both axes); insert one tagged child on success."""
+ dn = vcfg['denoise']
attempts = []
for attempt in range(1, max_retries + 1):
seed = int.from_bytes(os.urandom(4), 'big') % (2**31 - 1)
- cn_strength = max(0.30, vcfg['cn_strength'] - 0.03 * (attempt - 1))
ts = int(time.time() * 1000)
- motif_png = os.path.join(GENDIR, f"{ts}_{seed}_var_motif.png")
+ out_png = os.path.join(GENDIR, f"{ts}_{seed}_i2iclean.png")
try:
- cnar.gen_controlnet_seamless(positive, negative, seed, hint_png,
- cn_strength, vcfg['cn_end'], motif_png)
+ nsr.gen_img2img_seamless(positive, negative, seed, src1024_png, dn, out_png)
except Exception as e:
attempts.append({'attempt': attempt, 'seed': seed, 'error': str(e)[:160]})
continue
- arr = np.asarray(Image.open(motif_png).convert('RGB'))
+ arr = np.asarray(Image.open(out_png).convert('RGB'))
ev = edgeverify.verify(arr[:, :, :3], pass_tol, warn_tol)
- h_v = ev['axes']['horizontal']['verdict']
- v_v = ev['axes']['vertical']['verdict']
- base_hex = cnar.dominant_hex(motif_png)
- cov = cnar.coverage_pct(motif_png, base_hex)
- attempts.append({'attempt': attempt, 'seed': seed, 'edge': ev['verdict'],
- 'h': h_v, 'v': v_v, 'coverage_pct': round(cov, 1)})
- print(f" root {root} V{vcfg['v']} attempt {attempt}/{max_retries}: "
- f"edge={ev['verdict']} h={h_v} v={v_v} cov={cov:.1f}% seed={seed}",
- file=sys.stderr)
- # EDGE-ONLY hard gate — coverage/subject are Steve's visual call now.
+ h_v = ev['axes']['horizontal']['verdict']; v_v = ev['axes']['vertical']['verdict']
+ attempts.append({'attempt': attempt, 'seed': seed, 'edge': ev['verdict'], 'h': h_v, 'v': v_v})
+ print(f" root {root} V{vcfg['v']} (denoise {dn}) attempt {attempt}/{max_retries}: "
+ f"edge={ev['verdict']} h={h_v} v={v_v} seed={seed}", file=sys.stderr)
if not (ev['verdict'] == 'PASS' and h_v == 'PASS' and v_v == 'PASS'):
continue
- final_png = os.path.join(GENDIR, f"{ts}_{seed}_var.png")
- cnar.composite_real_fibre(motif_png, ground, base_hex, final_png)
- fev = edgeverify.verify_path(Path(final_png), pass_tol, warn_tol)
- if not (fev['verdict'] == 'PASS'
- and fev['axes']['horizontal']['verdict'] == 'PASS'
- and fev['axes']['vertical']['verdict'] == 'PASS'):
- continue # composite regressed the wrap — reroll
- 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, 'animal': animal, 'ground': ground, 'seed': seed,
- 'base_hex': base_hex, 'coverage_pct': round(cov, 1),
- 'cn_strength': round(cn_strength, 2), 'attempts_used': attempt,
- 'edge_verdict': fev['verdict'], 'new_id': new_id, 'out': final_png, 'ok': True,
- }
+ new_id = insert_i2i(root, out_png, positive, negative, dn)
+ return {'ts': datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z'),
+ 'root_id': root, 'denoise': dn, 'seed': seed, 'edge_verdict': ev['verdict'],
+ 'new_id': new_id, 'out': out_png, 'attempts_used': attempt, 'ok': True}
return {'root_id': root, 'ok': False,
'error': f'no edge-seamless tile in {max_retries} retries', 'attempts': attempts}
@@ -139,7 +127,7 @@ def done_cells():
for line in f:
try:
r = json.loads(line)
- if r.get('ok'):
+ if r.get('ok') and r.get('generator') == GEN_TAG:
done.add((int(r['root_id']), int(r['variant_n'])))
except Exception:
pass
@@ -157,12 +145,11 @@ def build_contact():
r = json.loads(line)
except Exception:
continue
- if r.get('ok') and r.get('out') and os.path.exists(r['out']):
+ if r.get('ok') and r.get('generator') == GEN_TAG and r.get('out') and os.path.exists(r['out']):
cells[(int(r['root_id']), int(r['variant_n']))] = r['out']
except FileNotFoundError:
return
- roots = sorted(ROOT_GROUND)
- TH = 150; pad_left, pad_top = 70, 24
+ roots = sorted(ROOT_GROUND); TH = 150; pad_left, pad_top = 70, 24
sheet = Image.new('RGB', (pad_left + 10 * TH, pad_top + len(roots) * TH), (245, 244, 240))
d = ImageDraw.Draw(sheet)
for c in range(10):
@@ -186,7 +173,7 @@ def build_contact():
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--roots')
- ap.add_argument('--max-retries', type=int, default=5)
+ ap.add_argument('--max-retries', type=int, default=3)
ap.add_argument('--pass-tol', type=float, default=edgeverify.PASS_TOL)
ap.add_argument('--warn-tol', type=float, default=edgeverify.WARN_TOL)
ap.add_argument('--contact-only', action='store_true')
@@ -201,29 +188,41 @@ def main():
done = done_cells()
pass_n = skip_n = fail_n = 0
for root in roots:
- ground = ROOT_GROUND[root]
+ # Prep the root once per root: meta, prompt (root content + cleanup), 1024 src.
+ try:
+ meta = cnar.root_meta_one(root)
+ except Exception as e:
+ print(f"root {root}: meta error {e}", file=sys.stderr); continue
+ rp = (meta.get('prompt') or '').strip()
+ positive = rp + CLEAN_EMPHASIS
+ _, negative = cnar.build_prompt(cnar.extract_subject(rp), cnar.short_animal(rp))
+ src = meta.get('local_path')
+ if not src or not os.path.exists(src):
+ print(f"root {root}: no source file", file=sys.stderr); continue
+ src1024 = os.path.join(GENDIR, f"_src1024_{root}.png")
+ Image.open(src).convert('RGB').resize((cnar.RENDER, cnar.RENDER), Image.LANCZOS).save(src1024)
+
for vc in VARIANTS:
vn = vc['v']
if (root, vn) in done:
- skip_n += 1
- print(f"=> root {root} V{vn}: SKIP (in ledger)", file=sys.stderr)
- continue
- print(f"\n=== root {root} V{vn}/10 ground={ground} motif_px={vc['motif_px']} "
- f"crop={vc['crop_frac']} (EDGE-ONLY gate) ===", file=sys.stderr)
+ skip_n += 1; print(f"=> root {root} V{vn}: SKIP (in ledger)", file=sys.stderr); continue
+ print(f"\n=== root {root} V{vn}/10 denoise={vc['denoise']} (img2img cleanup, EDGE gate) ===",
+ file=sys.stderr)
try:
- r = edge_only_variant(root, vc, ground, args.max_retries, args.pass_tol, args.warn_tol)
+ r = cleanup_variant(root, vc, args.max_retries, args.pass_tol, args.warn_tol,
+ src1024, positive, negative)
except Exception as e:
- r = {'root_id': root, 'ok': False, 'error': str(e), 'ground': ground}
+ r = {'root_id': root, 'ok': False, 'error': str(e)}
r['variant_n'] = vn
- r['variant_cfg'] = {'motif_px': vc['motif_px'], 'crop_frac': vc['crop_frac']}
- r['generator'] = 'variants-9roots'
- r['variant'] = f'comp-v{vn}'
+ r['variant_cfg'] = {'denoise': vc['denoise']}
+ r['generator'] = GEN_TAG
+ r['variant'] = f'cleanup-v{vn}'
with open(LEDGER, 'a') as q:
q.write(json.dumps(r) + '\n')
if r.get('ok'):
pass_n += 1
- print(f"=> root {root} V{vn}: OK new_id={r.get('new_id')} edge={r.get('edge_verdict')} "
- f"cov={r.get('coverage_pct')}% attempts={r.get('attempts_used')}", file=sys.stderr)
+ print(f"=> root {root} V{vn}: OK new_id={r.get('new_id')} denoise={vc['denoise']} "
+ f"edge={r.get('edge_verdict')} attempts={r.get('attempts_used')}", file=sys.stderr)
else:
fail_n += 1
print(f"=> root {root} V{vn}: no edge-clear ({str(r.get('error'))[:70]})", file=sys.stderr)
← 8e69f5d Add self-healing supervisor for variants-9roots regen (relau
·
back to Wallco Ai
·
variant-curator API: show only the new img2img-cleanup varia e4a15f7 →