[object Object]

← 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

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 →