← back to Wallco Ai
Add controlnet-anchored-redo.py: SDXL canny-CN anchored native-seamless redo
c63a1ffba50c6ea8cd7b9d20d3537895850d6bfc · 2026-06-11 16:29:32 -0700 · Steve Abrams
Uses the controlnet-canny-sdxl-1.0 model installed on Mac1 ComfyUI to anchor a
single focal motif (DTD 3/3: cropped focal canny hint on blank canvas, not the
full busy root) while circular-pad SeamlessTile+MakeCircularVAE keeps the tile
seamless (zero smear). Composites onto real procedural fibre ground. Inserts a
sacred curator-mode child (is_published/user_removed FALSE, parent=root) with
explicit id (dw_admin has no sequence USAGE) and image_url inline (table is in a
no-replica-identity publication so UPDATE/DELETE error).
Files touched
A scripts/controlnet-anchored-redo.py
Diff
commit c63a1ffba50c6ea8cd7b9d20d3537895850d6bfc
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jun 11 16:29:32 2026 -0700
Add controlnet-anchored-redo.py: SDXL canny-CN anchored native-seamless redo
Uses the controlnet-canny-sdxl-1.0 model installed on Mac1 ComfyUI to anchor a
single focal motif (DTD 3/3: cropped focal canny hint on blank canvas, not the
full busy root) while circular-pad SeamlessTile+MakeCircularVAE keeps the tile
seamless (zero smear). Composites onto real procedural fibre ground. Inserts a
sacred curator-mode child (is_published/user_removed FALSE, parent=root) with
explicit id (dw_admin has no sequence USAGE) and image_url inline (table is in a
no-replica-identity publication so UPDATE/DELETE error).
---
scripts/controlnet-anchored-redo.py | 469 ++++++++++++++++++++++++++++++++++++
1 file changed, 469 insertions(+)
diff --git a/scripts/controlnet-anchored-redo.py b/scripts/controlnet-anchored-redo.py
new file mode 100644
index 0000000..f0cd50c
--- /dev/null
+++ b/scripts/controlnet-anchored-redo.py
@@ -0,0 +1,469 @@
+#!/usr/bin/env python3
+"""controlnet-anchored-redo.py — REDO of the BOUNDED 5-root sanity sample using
+an SDXL CANNY ControlNet to anchor the subject while hitting an OPEN composition.
+
+WHY THIS SUPERSEDES native-seamless-redo.py (2026-06-11):
+ The img2img redo anchored the subject by starting from the root latent, but the
+ root tiles are EDGE-TO-EDGE BUSY toiles (sunflowers everywhere / barcode vertical
+ stripes / an abstract red-cyan blob). img2img inherits that busyness, fighting the
+ graphic-designer open-composition brief. The ControlNet model
+ `controlnet-canny-sdxl-1.0.safetensors` was installed on Mac1 ComfyUI after Steve's
+ approval, which unlocks the right architecture.
+
+DTD VERDICT (3/3, 2026-06-11): feed the ControlNet a CROPPED single focal motif of
+the root (the one clean animal head) centred on an otherwise-BLANK canvas, at
+LOW-MODERATE strength (~0.5), NOT the full busy root. A canny map of the full root
+is a dense spatial prior that re-imposes the edge-to-edge clutter and makes an open
+layout structurally impossible. Cropping decouples WHICH-animal (canny anchor) from
+LAYOUT (blank surround + open prompt + circular-pad), so all four goals co-exist.
+
+THE FOUR GOALS, each by mechanism:
+ 1. ZERO smear — native circular-pad seamless: SeamlessTile + MakeCircularVAE,
+ txt2img at denoise 1.0. The tile wraps by construction. We NEVER touch
+ make_seamless.py / force-edge-seamless.py (retired smear sources).
+ 2. SUBJECT anchored — the root's single focal crop -> Canny edge map, fed through
+ ControlNetApplyAdvanced (canny SDXL CN) at strength ~0.5 over a partial step
+ window. The motif identity (giraffe head, frog, etc.) survives; the model is
+ free to rebuild open surround.
+ 3. REAL fibre ground — the clean two-tone open motif tile is composited onto a
+ seamless procedural natural-fibre swatch (make-fibre-ground.py) via the
+ deterministic seamless-bg-swap path. Both layers wrap; motif kept byte-for-byte.
+ 4. OPEN / less busy — blank canny surround + open/sparse prompt + a focal-scale
+ canny crop sized so the primary motif lands ~150-200px tall in a 512 render.
+
+SACRED ROOTS: never overwrites a root. Each candidate -> NEW file + NEW id with
+is_published=FALSE, user_removed=FALSE, parent_design_id=<root>. NEVER published live.
+The composite's edges are verified with verify-edge-seamless.py on the WRITTEN file;
+the real verdict is recorded (a WARN/FAIL is recorded as such, never a fake PASS).
+
+NOT a bulk run. Caps at --ids. DATABASE_URL self-resolves (never echoed).
+
+Usage:
+ python3 scripts/controlnet-anchored-redo.py --ids 2662,54266,54076,54610,53896
+ python3 scripts/controlnet-anchored-redo.py --ids 2662 --cn-strength 0.55
+"""
+import argparse, datetime, json, os, re, subprocess, sys, time
+from pathlib import Path
+from urllib.parse import quote
+
+import numpy as np
+from PIL import Image, ImageFilter
+from scipy.ndimage import gaussian_filter
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+GENDIR = os.path.join(ROOT, 'data', 'generated')
+FIBREDIR = os.path.join(ROOT, 'data', 'fibre-grounds')
+QUEUE = os.path.join(ROOT, 'data', 'seam-fix-queue.jsonl')
+
+sys.path.insert(0, os.path.join(ROOT, 'scripts'))
+from importlib import import_module
+sfv = import_module('seam-fix-variants') # psql / root_meta
+edgeverify = import_module('verify-edge-seamless') # true-toroidal edge gate
+mkfibre = import_module('make-fibre-ground') # procedural fibre swatch
+
+COMFY = os.environ.get('COMFY_URL', 'http://192.168.1.133:8188')
+MODEL = os.environ.get('COMFY_MODEL', 'sd_xl_base_1.0.safetensors')
+CN_MODEL = os.environ.get('COMFY_CN_MODEL', 'controlnet-canny-sdxl-1.0.safetensors')
+SAMPLER = os.environ.get('COMFY_SAMPLER', 'dpmpp_2m_sde')
+SCHEDULER = os.environ.get('COMFY_SCHEDULER', 'karras')
+POLL_TIMEOUT = int(os.environ.get('COMFY_POLL_TIMEOUT_SEC', '900'))
+RENDER = 1024 # SDXL native; verify+brief are framed at 512 — we downscale notes
+
+# Rotate the natural ground across the sample so Steve sees range, not one note.
+GROUNDS = ['grasscloth', 'raffia', 'natural linen', 'sisal', 'paperweave']
+
+
+# ---------- subject extraction ----------
+def extract_subject(prompt: str) -> str:
+ if not prompt:
+ return 'a single tipsy animal holding one cocktail'
+ parts = [p.strip() for p in prompt.split(',')]
+ subject = ', '.join(parts[:2]) if len(parts) >= 2 else parts[0]
+ subject = re.split(r'\.\s', subject)[0]
+ return subject.strip().rstrip('.')
+
+
+def short_animal(prompt: str) -> str:
+ """Just the animal noun (for a tighter focal prompt)."""
+ p = (prompt or '').lower()
+ for animal in ['giraffe', 'tree frog', 'frog', 'orangutan', 'elephant',
+ 'red panda', 'panda', 'monkey', 'lemur', 'sloth', 'fox',
+ 'otter', 'raccoon', 'koala', 'parrot', 'flamingo']:
+ if animal in p:
+ return animal
+ return 'animal'
+
+
+# ---------- focal crop ----------
+def _detail_density(gray):
+ """Local edge-energy map: where the subject's fine detail concentrates."""
+ gx = np.abs(np.diff(gray, axis=1, append=gray[:, -1:]))
+ gy = np.abs(np.diff(gray, axis=0, append=gray[-1:, :]))
+ energy = gx + gy
+ return gaussian_filter(energy, sigma=24)
+
+
+def find_focal_crop(root_img, crop_frac=0.42):
+ """Find the densest-detail square region away from the tile edges — that is
+ where the animal subject (eyes/face/contours) concentrates vs. flat ground or
+ repetitive filler. Returns a centred square crop of the root.
+
+ crop_frac sets crop side as a fraction of the root's short edge; smaller =
+ tighter focal motif = more open surround once placed on the blank canvas."""
+ g = np.asarray(root_img.convert('L'), np.float64)
+ H, W = g.shape
+ side = int(min(H, W) * crop_frac)
+ side = max(64, min(side, min(H, W)))
+ dens = _detail_density(g)
+ # integral image for fast windowed sum of density
+ ii = dens.cumsum(0).cumsum(1)
+ ii = np.pad(ii, ((1, 0), (1, 0)))
+
+ def win_sum(y, x):
+ return (ii[y + side, x + side] - ii[y, x + side]
+ - ii[y + side, x] + ii[y, x])
+
+ # bias slightly toward centre (avoid grabbing a cropped half-subject at edge)
+ best, by, bx = -1.0, 0, 0
+ step = max(8, side // 16)
+ cy, cx = (H - side) / 2.0, (W - side) / 2.0
+ for y in range(0, H - side + 1, step):
+ for x in range(0, W - side + 1, step):
+ centre_bias = 1.0 - 0.35 * (abs(y - cy) / max(cy, 1) + abs(x - cx) / max(cx, 1)) / 2.0
+ s = win_sum(y, x) * centre_bias
+ if s > best:
+ best, by, bx = s, y, x
+ return root_img.crop((bx, by, bx + side, by + side)), (bx, by, side)
+
+
+def build_canny_hint(focal_crop, motif_px=180):
+ """Place the focal crop, scaled so the subject reads ~motif_px tall in a 512
+ frame (scaled to RENDER), centred on a BLACK canvas. Black surround => Canny
+ yields no edges there => CN exerts no constraint => the model leaves it open.
+
+ CRITICAL: feather the crop's outer ring to black with a radial alpha so the
+ crop's square boundary does NOT survive as a hard edge through Canny — a hard
+ square produced the faint frame-overlay ghost-box in the first smoke test.
+ The feather kills the box; only the subject's interior contours remain as
+ canny edges."""
+ target = int(round(motif_px * (RENDER / 512.0)))
+ target = max(96, min(target, RENDER - 32))
+ crop = focal_crop.convert('RGB').resize((target, target), Image.LANCZOS)
+
+ # Feather to black over a LONG, VERY gradual radial ramp so NO detectable
+ # edge forms at any radius — the first feather (sharp ring at r=0.82) created
+ # a circular gradient that Canny picked up as a ring outline ('framed cameo'
+ # defect). Here alpha falls linearly from 1.0 at the centre to 0 at r=1.0,
+ # and we blur it heavily, so the luminance gradient everywhere is below
+ # Canny's gradient threshold — only the SUBJECT's own internal contours
+ # survive as edges, no box and no ring.
+ yy, xx = np.mgrid[0:target, 0:target].astype(np.float64)
+ cx = cy = (target - 1) / 2.0
+ r = np.sqrt(((xx - cx) / cx) ** 2 + ((yy - cy) / cy) ** 2) # 0 centre .. ~1.41 corners
+ alpha = np.clip(1.0 - r, 0.0, 1.0) # gentle whole-radius ramp
+ alpha = gaussian_filter(alpha, sigma=target * 0.06) # heavy blur => no hard ring
+ crop_arr = np.asarray(crop, np.float64) * alpha[..., None] # fade toward black
+
+ canvas = np.zeros((RENDER, RENDER, 3), np.float64)
+ off = (RENDER - target) // 2
+ canvas[off:off + target, off:off + target] = crop_arr
+ return Image.fromarray(np.clip(canvas, 0, 255).astype(np.uint8), 'RGB')
+
+
+# ---------- comfy plumbing ----------
+def comfy_post(path, data=None, timeout=30):
+ url = f'{COMFY}{path}'
+ if data is not None:
+ r = subprocess.run(
+ ['curl', '-sf', '-m', str(timeout), '-H', 'Content-Type: application/json',
+ '-X', 'POST', url, '-d', '@-'],
+ input=json.dumps(data), capture_output=True, text=True)
+ else:
+ r = subprocess.run(['curl', '-sf', '-m', str(timeout), url],
+ capture_output=True, text=True)
+ if r.returncode != 0:
+ raise RuntimeError(f'comfy {path} failed rc={r.returncode}: {r.stderr[:200]}')
+ return r.stdout
+
+
+def comfy_upload_image(local_path, name_hint):
+ r = subprocess.run(
+ ['curl', '-sf', '-m', '60', '-X', 'POST', f'{COMFY}/upload/image',
+ '-F', f'image=@{local_path}', '-F', 'overwrite=true'],
+ capture_output=True, text=True)
+ if r.returncode != 0:
+ raise RuntimeError(f'comfy upload failed: {r.stderr[:200]}')
+ name = json.loads(r.stdout).get('name')
+ if not name:
+ raise RuntimeError(f'comfy upload returned no name: {r.stdout[:200]}')
+ return name
+
+
+def build_prompt(subject, animal):
+ positive = (
+ f"A single {subject}. ONE clean bold flat two-tone {animal} as the SOLE "
+ "focal motif, large and isolated and centred, with generous EMPTY "
+ "negative space of bare flat ground all around it. Open airy wallcovering "
+ "repeat, sparse and minimal, calm and uncluttered, NOT busy, NOT "
+ "edge-to-edge, NOT a dense all-over print, plenty of breathing room. The "
+ "figure is a SOLID flat dark-ink silhouette in one deep archival ink "
+ "colour on a clean light flat ground, STRONG figure-ground contrast, "
+ "crisp graphic two-tone block-print, hard clean confident edges, no "
+ "shading, no gradient, no depth, no relief, NOT pale, NOT washed out, NOT "
+ "faded. Elegant heritage toile, refined block-print designer wallcovering."
+ )
+ negative = (
+ "busy, cluttered, dense, edge-to-edge, all-over print, tessellated "
+ "densely, packed motifs, many animals, crowded figures, repeated heads, "
+ "rows of faces, sunflowers, foliage filling the background, leaves filling "
+ "the background, vines everywhere, jungle scene, vertical stripes, barcode "
+ "stripes, columns of shapes, abstract shapes, abstract waves, abstract "
+ "blobs, leaf blades, unrecognizable subject, embossed, relief, raised "
+ "texture, 3D, dimensional, depth, drop shadow, ambient occlusion, ghost "
+ "layer, ghosted, faded background copies, gradient fill, halftone, "
+ "ben-day dots, outline only, hollow shape, pale, washed out, faded, "
+ "pastel, low contrast, ghostly, translucent, neon, fluorescent, "
+ "saturated, rainbow, more than 4 colours, seam visible, hard seam, frame, "
+ "border, picture frame, pasted-on rectangle, plate behind motif, "
+ "signature, watermark, text, blurry, low quality"
+ )
+ return positive, negative
+
+
+def gen_controlnet_seamless(positive, negative, seed, hint_local, cn_strength,
+ cn_end, out_path):
+ """txt2img native-seamless with a canny ControlNet anchoring the focal motif.
+
+ Graph: CheckpointLoader -> SeamlessTile(model) + MakeCircularVAE(vae);
+ LoadImage(hint) -> Canny -> ControlNetApplyAdvanced(positive,negative) at
+ strength<1 over [0, cn_end]; EmptyLatentImage(seamless via circular VAE on
+ decode); KSampler(denoise 1.0) -> VAEDecode(circular vae) -> SaveImage."""
+ hint_name = comfy_upload_image(hint_local, 'cn_hint')
+ workflow = {
+ '4': {'class_type': 'CheckpointLoaderSimple', 'inputs': {'ckpt_name': MODEL}},
+ '1000': {'class_type': 'SeamlessTile', 'inputs': {'model': ['4', 0], 'tiling': 'enable', 'copy_model': 'Make a copy'}},
+ '2000': {'class_type': 'MakeCircularVAE', 'inputs': {'vae': ['4', 2], 'tiling': 'enable', 'copy_vae': 'Make a copy'}},
+ '6': {'class_type': 'CLIPTextEncode', 'inputs': {'text': positive, 'clip': ['4', 1]}},
+ '7': {'class_type': 'CLIPTextEncode', 'inputs': {'text': negative, 'clip': ['4', 1]}},
+ '20': {'class_type': 'ControlNetLoader', 'inputs': {'control_net_name': CN_MODEL}},
+ '10': {'class_type': 'LoadImage', 'inputs': {'image': hint_name}},
+ '21': {'class_type': 'Canny', 'inputs': {'image': ['10', 0], 'low_threshold': 0.45, 'high_threshold': 0.85}},
+ '22': {'class_type': 'ControlNetApplyAdvanced', 'inputs': {
+ 'positive': ['6', 0], 'negative': ['7', 0],
+ 'control_net': ['20', 0], 'image': ['21', 0],
+ 'strength': cn_strength, 'start_percent': 0.0, 'end_percent': cn_end}},
+ '5': {'class_type': 'EmptyLatentImage', 'inputs': {'width': RENDER, 'height': RENDER, 'batch_size': 1}},
+ '3': {'class_type': 'KSampler', 'inputs': {
+ 'seed': seed, 'steps': 30, 'cfg': 7.0, 'sampler_name': SAMPLER,
+ 'scheduler': SCHEDULER, 'denoise': 1.0,
+ 'model': ['1000', 0], 'positive': ['22', 0], 'negative': ['22', 1],
+ 'latent_image': ['5', 0]}},
+ '8': {'class_type': 'VAEDecode', 'inputs': {'samples': ['3', 0], 'vae': ['2000', 0]}},
+ '9': {'class_type': 'SaveImage', 'inputs': {'filename_prefix': f'wallco_cnar_{seed}', 'images': ['8', 0]}},
+ }
+ submit = comfy_post('/prompt', {'prompt': workflow, 'client_id': 'wallco-cnar'})
+ pid = json.loads(submit).get('prompt_id')
+ if not pid:
+ raise RuntimeError('ComfyUI did not return prompt_id')
+ start = time.time()
+ images = None
+ last_err = None
+ while time.time() - start < POLL_TIMEOUT:
+ time.sleep(2)
+ try:
+ hist = json.loads(comfy_post(f'/history/{pid}', timeout=5))
+ except Exception:
+ continue
+ entry = hist.get(pid)
+ if not entry:
+ continue
+ status = entry.get('status', {})
+ if status.get('status_str') == 'error':
+ msgs = status.get('messages', [])
+ last_err = json.dumps(msgs)[:400]
+ raise RuntimeError(f'ComfyUI execution error: {last_err}')
+ if entry.get('outputs', {}).get('9', {}).get('images'):
+ images = entry['outputs']['9']['images']
+ break
+ if not images:
+ raise RuntimeError(f'ComfyUI timed out after {POLL_TIMEOUT}s (pid={pid}) {last_err or ""}')
+ im = images[0]
+ view = (f"/view?filename={quote(im['filename'])}"
+ f"&subfolder={quote(im.get('subfolder',''))}"
+ f"&type={quote(im.get('type','output'))}")
+ raw = subprocess.run(['curl', '-sf', '-m', '60', f'{COMFY}{view}'], capture_output=True)
+ if raw.returncode != 0 or len(raw.stdout) < 1000:
+ raise RuntimeError(f'ComfyUI image pull failed ({view})')
+ with open(out_path, 'wb') as f:
+ f.write(raw.stdout)
+ return out_path
+
+
+# ---------- composite ----------
+def dominant_hex(png_path):
+ im = Image.open(png_path).convert('RGB').resize((128, 128))
+ arr = np.asarray(im).reshape(-1, 3)
+ q = (arr // 16 * 16).astype(np.uint8)
+ vals, counts = np.unique(q, axis=0, return_counts=True)
+ r, g, b = vals[counts.argmax()]
+ return f'#{r:02x}{g:02x}{b:02x}'
+
+
+def coverage_pct(png_path, base_hex, tol=26):
+ """Rough motif-coverage estimate: fraction of pixels NOT near the ground hex."""
+ arr = np.asarray(Image.open(png_path).convert('RGB').resize((256, 256)), np.float64)
+ base = np.array([int(base_hex[i:i + 2], 16) for i in (1, 3, 5)], np.float64)
+ d = np.sqrt(((arr - base) ** 2).sum(2))
+ return float((d > tol).mean()) * 100.0
+
+
+def composite_real_fibre(motif_png, ground, base_hex, out_path):
+ slug = ground.replace(' ', '-')
+ fibre_png = os.path.join(FIBREDIR, f'{slug}.png')
+ if not os.path.exists(fibre_png):
+ os.makedirs(FIBREDIR, exist_ok=True)
+ mkfibre.make_fibre(ground, 1024, 11).save(fibre_png, 'PNG')
+ cmd = ['python3', os.path.join(ROOT, 'scripts', 'seamless-bg-swap-file.py'),
+ '--src', motif_png,
+ '--texture-image', fibre_png, '--texture-name', f'real-{slug}',
+ '--base-hex', base_hex,
+ '--strength', '0.6', '--feather', '40',
+ '--mask-tolerance', '20', '--mask-feather', '1.2',
+ '--no-sanitize', '--out', out_path]
+ r = subprocess.run(cmd, capture_output=True, text=True, timeout=180)
+ if r.returncode != 0 or not os.path.exists(out_path):
+ raise RuntimeError(f'bg-swap composite failed rc={r.returncode}: {r.stderr[-300:]}')
+ return r.stdout
+
+
+def insert_child(root, out_path, positive, negative, ground, cn_strength):
+ """Insert the child row. dw_admin has INSERT on all_designs but NO USAGE on
+ spoon_all_designs_id_seq, so we supply an EXPLICIT id = max(id)+1 (never touch
+ the sequence). Safe for this bounded, single-threaded 5-row sanity sample —
+ no concurrent inserts. is_published / user_removed FALSE; parent = root."""
+ pos = positive.replace("'", "''")
+ neg = negative.replace("'", "''")
+ # image_url is built IN the INSERT (not a later UPDATE): all_designs is in a
+ # logical-replication PUBLICATION with no replica identity, so UPDATE without
+ # a PK identity errors. The new id is the MAX(id)+1 computed once in a CTE so
+ # image_url can reference the same value the row is inserted with.
+ 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, "
+ f" (COALESCE(s.tags, ARRAY[]::text[])) || "
+ f" ARRAY['controlnet-anchored-redo','canny-sdxl-cn','real-fibre-{ground.replace(' ','-')}','open-composition','focal-crop','redo-sample-from-{root}']::text[], "
+ f" FALSE, FALSE, 'controlnet-anchored-redo', {root}, s.source_dw_sku "
+ f"FROM (SELECT * FROM all_designs WHERE id={root} LIMIT 1) s, nid RETURNING id;"))
+ return new_id
+
+
+def root_meta_one(root):
+ """Local meta fetch with LIMIT 1 — some root ids are duplicated in all_designs
+ (snapshot import dupes), and sfv.root_meta json.loads-es ALL matching rows and
+ chokes on 'Extra data'. We take exactly one."""
+ row = sfv.psql(
+ "SELECT row_to_json(t) FROM (SELECT id, category, kind, prompt, local_path "
+ f"FROM all_designs WHERE id={root} LIMIT 1) t;")
+ if not row:
+ raise RuntimeError(f'no design {root}')
+ return json.loads(row.splitlines()[0])
+
+
+def process(root, ground, cn_strength, cn_end, crop_frac, motif_px):
+ os.makedirs(GENDIR, exist_ok=True)
+ meta = 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')
+ if not prompt:
+ pr = sfv.psql(f"SELECT COALESCE(prompt,'') FROM all_designs WHERE id={root} LIMIT 1;").splitlines()
+ prompt = pr[0] if pr else ''
+ subject = extract_subject(prompt)
+ animal = short_animal(prompt)
+ positive, negative = build_prompt(subject, animal)
+
+ root_img = Image.open(src).convert('RGB')
+ focal, crop_box = find_focal_crop(root_img, crop_frac)
+ hint = build_canny_hint(focal, motif_px)
+
+ seed = int.from_bytes(os.urandom(4), 'big') % (2**31 - 1)
+ ts = int(time.time() * 1000)
+ hint_png = os.path.join(GENDIR, f'{ts}_{seed}_cnar_hint.png')
+ motif_png = os.path.join(GENDIR, f'{ts}_{seed}_cnar_motif.png')
+ final_png = os.path.join(GENDIR, f'{ts}_{seed}_cnar.png')
+ hint.save(hint_png, 'PNG')
+
+ # 1+2. controlnet-anchored native-seamless txt2img (zero smear, subject anchored)
+ gen_controlnet_seamless(positive, negative, seed, hint_png, cn_strength, cn_end, motif_png)
+
+ # composition stats on the raw motif tile (before fibre)
+ base_hex = dominant_hex(motif_png)
+ cov = coverage_pct(motif_png, base_hex)
+
+ # 3. real-fibre composite (deterministic, both layers wrap)
+ composite_real_fibre(motif_png, ground, base_hex, final_png)
+
+ # 4. true-toroidal edge gate on the FINAL composite
+ ev = edgeverify.verify_path(Path(final_png))
+
+ new_id = 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-redo',
+ 'generator': 'controlnet-anchored-redo',
+ 'category': meta.get('category'), 'subject': subject, 'animal': animal,
+ 'ground': ground, 'seed': seed, 'base_hex': base_hex,
+ 'cn_strength': cn_strength, 'cn_end': cn_end, 'crop_box': crop_box,
+ 'coverage_pct': round(cov, 1),
+ 'technique': 'canny-SDXL-CN-anchored native-seamless txt2img (SeamlessTile+MakeCircularVAE, denoise 1.0, cropped focal canny hint) + deterministic real-fibre composite — NO smear',
+ 'edge_verdict': ev.get('verdict'), 'edge_seamless': bool(ev.get('ok')),
+ 'edge_axes': {'h': ev.get('axes', {}).get('horizontal', {}),
+ 'v': ev.get('axes', {}).get('vertical', {})},
+ 'new_id': new_id, 'hint': hint_png, 'motif': motif_png, 'out': final_png, 'ok': True,
+ }
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument('--ids', required=True)
+ ap.add_argument('--ground', help='force one ground for all (else rotates)')
+ ap.add_argument('--cn-strength', type=float, default=0.5, help='canny CN strength (DTD: ~0.5)')
+ ap.add_argument('--cn-end', type=float, default=0.55, help='CN end_percent (release control late so layout opens)')
+ ap.add_argument('--crop-frac', type=float, default=0.32, help='focal crop side / root short edge (tighter = one head, less filler)')
+ ap.add_argument('--motif-px', type=int, default=175, help='target motif height in a 512 frame')
+ args = ap.parse_args()
+ ids = [int(x) for x in args.ids.split(',') if x.strip()]
+
+ results = []
+ for i, rid in enumerate(ids):
+ ground = args.ground or GROUNDS[i % len(GROUNDS)]
+ try:
+ r = process(rid, ground, args.cn_strength, args.cn_end, args.crop_frac, args.motif_px)
+ except Exception as e:
+ r = {'root_id': rid, 'ok': False, 'error': str(e), 'ground': 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', '?'))
+ ax = r.get('edge_axes', {})
+ h = (ax.get('h') or {}).get('verdict'); vv = (ax.get('v') or {}).get('verdict')
+ print(f"root {rid} ground={r.get('ground')}: edge={v} h={h} v={vv} "
+ f"cov={r.get('coverage_pct')}% new_id={r.get('new_id')}", 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()
← fc72875 Wire seam-fix wave cadence (DTD verdict C): gated one-wave-p
·
back to Wallco Ai
·
TODO: ControlNet redo sample landed (1 PASS, 2 near-miss WAR b9e1b4d →