← back to Wallco Ai
Fix fuzzy edges + blurred center-cross in all 3 seam healers (smartfix/mid/edge): replace 24-25px 2-D GaussianBlur bands with narrow ~6px 1-D directional cross-fade; drop edge-heal's indefensible interior-midline blur pass
fdd35e6302a52db513430f9abb81ec4d2193efa8 · 2026-05-27 08:00:13 -0700 · Steve Abrams
Files touched
M scripts/edge_seam_heal.pyM scripts/mid_seam_heal.pyM server.js
Diff
commit fdd35e6302a52db513430f9abb81ec4d2193efa8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 27 08:00:13 2026 -0700
Fix fuzzy edges + blurred center-cross in all 3 seam healers (smartfix/mid/edge): replace 24-25px 2-D GaussianBlur bands with narrow ~6px 1-D directional cross-fade; drop edge-heal's indefensible interior-midline blur pass
---
scripts/edge_seam_heal.py | 81 +++++++++++++-------
scripts/mid_seam_heal.py | 67 +++++++++++++----
server.js | 185 +++++++++++++++++++++++++++++++++++++++-------
3 files changed, 263 insertions(+), 70 deletions(-)
diff --git a/scripts/edge_seam_heal.py b/scripts/edge_seam_heal.py
index 7c728dd..115d5a1 100644
--- a/scripts/edge_seam_heal.py
+++ b/scripts/edge_seam_heal.py
@@ -117,37 +117,63 @@ def fetch_targets(grid: str, limit: int | None, ids: list[int] | None):
def heal_image(src_path: Path, out_path: Path):
- """Combined seam-heal — feathers BOTH the interior midlines and the wrap edges.
-
- The edges_only cohort is mislabelled-clean on mids: in the 6-sample
- validation the "clean" midlines actually scored 7.5-10.9 ΔE (WARN range),
- so an edge-only feather lands the result at WARN, not PASS — and the strict
- gate won't promote WARN. Healing both in one pass took all 6 samples to a
- clean PASS (edges 1.3-3.2, mids 1.7-2.5). So this tool always does both:
-
- 1) interior-midline blur (the mid_seam_heal operation)
- 2) np.roll by (H/2, W/2) → wrap seam moves to the interior → blur → roll
- back (np.roll is exactly invertible, so only the feathered bands differ
- from the source; the rest is byte-identical — composition preserved).
+ """Narrow SHARP cross-fade at the WRAP edges only.
+
+ OLD method did TWO wide 2-D GaussianBlur(SIGMA=4.0) passes over BAND_PX*2=24px
+ bands:
+ 1) it blurred the INTERIOR midlines — but interior midlines are NOT a tiling
+ boundary (tiles meet at the EDGES), so this only smeared a visible fuzzy
+ cross through the middle of the displayed tile to satisfy the edges-agent
+ midline lens. Pure metric-gaming (the docstring used to justify it by the
+ midlines scoring 7.5-10.9 ΔE — that's natural pattern content, not a seam).
+ 2) it blurred a 24px band at the wrap edges via np.roll, leaving a fuzzy dark
+ frame on every VISIBLE outer edge (corners double-blurred).
+ Both defects were visible on the product page (e.g. #52277).
+
+ NEW method: drop the interior-midline pass entirely (if a design has a real
+ internal-grid seam that's mid_seam_heal's job, or a reject) and heal ONLY the
+ true wrap edges with a ~6px band low-passed along the seam normal (1-D), so
+ motif detail stays crisp and no brightness halo forms. np.roll stays exactly
+ invertible: every pixel outside the thin edge bands is byte-identical to the
+ source, so composition is preserved.
"""
- from PIL import Image, ImageFilter
+ from PIL import Image
import numpy as np
img = Image.open(src_path).convert('RGB')
W, H = img.size
- arr = np.asarray(img).copy()
hm, wm = H // 2, W // 2
-
- # 1) interior midlines
- blur = np.asarray(img.filter(ImageFilter.GaussianBlur(radius=SIGMA)))
- arr[hm - BAND_PX:hm + BAND_PX, :, :] = blur[hm - BAND_PX:hm + BAND_PX, :, :]
- arr[:, wm - BAND_PX:wm + BAND_PX, :] = blur[:, wm - BAND_PX:wm + BAND_PX, :]
-
- # 2) wrap edges via the offset trick
- rolled = np.roll(arr, (hm, wm), axis=(0, 1))
- rblur = np.asarray(Image.fromarray(rolled).filter(ImageFilter.GaussianBlur(radius=SIGMA)))
- rolled[hm - BAND_PX:hm + BAND_PX, :, :] = rblur[hm - BAND_PX:hm + BAND_PX, :, :]
- rolled[:, wm - BAND_PX:wm + BAND_PX, :] = rblur[:, wm - BAND_PX:wm + BAND_PX, :]
- healed = np.roll(rolled, (-hm, -wm), axis=(0, 1))
+ band = max(4, min(W, H) // 160) # ~6px @1024 (was BAND_PX=12 -> 24px band)
+
+ def hsmooth(s): # horizontal-only low-pass: vertical detail untouched
+ w, h = s.size
+ return s.resize((max(2, w // 3), h), Image.BILINEAR).resize((w, h), Image.BILINEAR)
+
+ def vsmooth(s): # vertical-only low-pass: horizontal detail untouched
+ w, h = s.size
+ return s.resize((w, max(2, h // 3)), Image.BILINEAR).resize((w, h), Image.BILINEAR)
+
+ def qmask(size, axis): # opaque at seam center, 0 at band edges
+ w, h = size
+ m = Image.new('L', size, 0); px = m.load()
+ if axis == 'x':
+ for x in range(w):
+ a = int(255 * (1 - abs(x - band) / band) ** 2)
+ for y in range(h): px[x, y] = a
+ else:
+ for y in range(h):
+ a = int(255 * (1 - abs(y - band) / band) ** 2)
+ for x in range(w): px[x, y] = a
+ return m
+
+ # Offset trick: roll by (H/2, W/2) so the wrap edges meet at the interior cross,
+ # heal that cross narrowly + sharply, then roll back so the heal lands on the
+ # (originally) outer edges.
+ rolled = Image.fromarray(np.roll(np.asarray(img), (hm, wm), axis=(0, 1)))
+ vstrip = rolled.crop((wm - band, 0, wm + band, H))
+ rolled.paste(hsmooth(vstrip), (wm - band, 0), qmask(vstrip.size, 'x'))
+ hstrip = rolled.crop((0, hm - band, W, hm + band))
+ rolled.paste(vsmooth(hstrip), (0, hm - band), qmask(hstrip.size, 'y'))
+ healed = np.roll(np.asarray(rolled), (-hm, -wm), axis=(0, 1))
Image.fromarray(healed).save(out_path, 'PNG', optimize=True)
return out_path.stat().st_size
@@ -210,7 +236,8 @@ def persist(results: list[dict]):
pn = r['panels'] if r['panels'] not in (None, '') else 'NULL'
local_path = sql_escape(r['out_path'])
img_url = sql_escape(f"/designs/img/{r['out_filename']}")
- msg = sql_escape(f"edge_heal of #{r['id']}: PIL combined mids+edges band-blur BAND={BAND_PX} SIGMA={SIGMA}")
+ band_px = max(4, 1024 // 160)
+ msg = sql_escape(f"edge_heal of #{r['id']}: PIL narrow sharp 1-D edge cross-fade band~{band_px}px (no interior-midline blur)")
rows_for_sql.append(
f"({r['id']}, '{kind}', '{cat}', {dh}, {pal}, {wi}, {hi}, {pn}, "
f"'{local_path}', '{img_url}', '{msg}', {seed}, '{msg}')"
diff --git a/scripts/mid_seam_heal.py b/scripts/mid_seam_heal.py
index 2b63341..e7f8267 100755
--- a/scripts/mid_seam_heal.py
+++ b/scripts/mid_seam_heal.py
@@ -118,22 +118,56 @@ def fetch_targets(grid: str, limit: int | None, ids: list[int] | None):
def heal_image(src_path: Path, out_path: Path):
- from PIL import Image, ImageFilter
- import numpy as np
+ """Narrow SHARP cross-fade at the interior midlines (the SDXL '2x2 internal
+ grid' seam).
+
+ OLD method blurred a BAND_PX*2 = 24px band at each interior midline with a
+ 2-D GaussianBlur(SIGMA=4.0). On the SINGLE tile that wallco displays whole,
+ that smeared a visible fuzzy cross straight through the middle of the product
+ image (and a dark halo where motifs got averaged). Worse, when the midline
+ was flagged on natural pattern content rather than a real internal seam, the
+ blur was pure cosmetic damage.
+
+ NEW method: a ~6px band, low-passed ONLY along the seam normal (1-D), so motif
+ silhouettes stay crisp, no brightness halo forms, and the heal is near-
+ invisible on false-positive rows. A real internal-grid seam is still joined;
+ a quadratic alpha mask keeps the blend opaque at the seam, zero at band edges.
+ """
+ from PIL import Image
img = Image.open(src_path).convert('RGB')
W, H = img.size
- arr = np.asarray(img).copy()
-
- blurred = img.filter(ImageFilter.GaussianBlur(radius=SIGMA))
- blur_arr = np.asarray(blurred)
-
- hm = H // 2
- arr[hm - BAND_PX:hm + BAND_PX, :, :] = blur_arr[hm - BAND_PX:hm + BAND_PX, :, :]
-
- wm = W // 2
- arr[:, wm - BAND_PX:wm + BAND_PX, :] = blur_arr[:, wm - BAND_PX:wm + BAND_PX, :]
-
- Image.fromarray(arr).save(out_path, 'PNG', optimize=True)
+ hm, wm = H // 2, W // 2
+ band = max(4, min(W, H) // 160) # ~6px @1024 (was BAND_PX=12 -> 24px band)
+
+ def hsmooth(s): # horizontal-only low-pass: vertical detail untouched
+ w, h = s.size
+ return s.resize((max(2, w // 3), h), Image.BILINEAR).resize((w, h), Image.BILINEAR)
+
+ def vsmooth(s): # vertical-only low-pass: horizontal detail untouched
+ w, h = s.size
+ return s.resize((w, max(2, h // 3)), Image.BILINEAR).resize((w, h), Image.BILINEAR)
+
+ def qmask(size, axis): # opaque at seam center, 0 at band edges
+ w, h = size
+ m = Image.new('L', size, 0); px = m.load()
+ if axis == 'x':
+ for x in range(w):
+ a = int(255 * (1 - abs(x - band) / band) ** 2)
+ for y in range(h): px[x, y] = a
+ else:
+ for y in range(h):
+ a = int(255 * (1 - abs(y - band) / band) ** 2)
+ for x in range(w): px[x, y] = a
+ return m
+
+ # Vertical seam at x=wm (smooth horizontally across it)
+ vstrip = img.crop((wm - band, 0, wm + band, H))
+ img.paste(hsmooth(vstrip), (wm - band, 0), qmask(vstrip.size, 'x'))
+ # Horizontal seam at y=hm (smooth vertically across it)
+ hstrip = img.crop((0, hm - band, W, hm + band))
+ img.paste(vsmooth(hstrip), (0, hm - band), qmask(hstrip.size, 'y'))
+
+ img.save(out_path, 'PNG', optimize=True)
return out_path.stat().st_size
@@ -209,8 +243,9 @@ def persist(results: list[dict]):
pn = r['panels'] if r['panels'] not in (None, '') else 'NULL'
local_path = sql_escape(r['out_path'])
img_url = sql_escape(f"/designs/img/{r['out_filename']}")
- prompt = sql_escape(f"mid_heal of #{r['id']}: PIL band-blur BAND={BAND_PX} SIGMA={SIGMA}")
- notes = sql_escape(f"mid_heal of #{r['id']}: PIL band-blur BAND={BAND_PX} SIGMA={SIGMA}")
+ band_px = max(4, 1024 // 160)
+ prompt = sql_escape(f"mid_heal of #{r['id']}: PIL narrow sharp 1-D midline cross-fade band~{band_px}px")
+ notes = sql_escape(f"mid_heal of #{r['id']}: PIL narrow sharp 1-D midline cross-fade band~{band_px}px")
rows_for_sql.append(
f"({r['id']}, '{kind}', '{cat}', {dh}, {pal}, {wi}, {hi}, {pn}, "
f"'{local_path}', '{img_url}', '{prompt}', {seed}, '{notes}')"
diff --git a/server.js b/server.js
index 5b7f01e..f6d3ac8 100644
--- a/server.js
+++ b/server.js
@@ -1101,6 +1101,130 @@ app.get('/api/admin/dogs/list', (req, res) => {
}
});
+// ── /admin/cactus-curator ─────────────────────────────────────────────────
+// Grid + density slider over EVERY cactus design (incl. unpublished), ranked
+// "most like our dw_unified catalog patterns" = a composite of vision-quality
+// (free local Ollama VL, "looks like a real sellable wallcovering") + seam-
+// quality (free 6-lens edges scan, print-readiness). Per card the admin
+// commits one of 4 verdicts — see POST /api/cactus-decision/:id:
+// 1 bad → remove from all (unpublish + user_removed + quarantine PNG)
+// 2 digital → unpublish, sell as digital file (stamps digital_file_at date)
+// 3 fix → keep but needs fixing (flags needs_fixing_at date)
+// 4 live → keep, go live in web viewer (publish + web_viewer flag)
+app.get('/admin/cactus-curator', (req, res) => {
+ if (!isAdmin(req)) return res.status(404).type('html').send('<h1>404</h1>');
+ res.sendFile(path.join(__dirname, 'public', 'admin', 'cactus-curator.html'));
+});
+
+// JSON list of every cactus design joined to its rank sidecar. rank_score is
+// computed in SQL so it always reflects the latest scores: 0.6*vision +
+// 0.4*seam when both present, else whichever exists. Default hides already-
+// removed rows; ?include=removed shows them.
+app.get('/api/admin/cactus/list', (req, res) => {
+ if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
+ const includeRemoved = String(req.query.include || '') === 'removed';
+ try {
+ const rawJson = psqlQuery(
+ "SELECT COALESCE(json_agg(t ORDER BY t.rank_score DESC NULLS LAST, t.id), '[]'::json) FROM (" +
+ "SELECT d.id, d.category, d.dominant_hex, d.is_published, " +
+ "COALESCE(d.user_removed,false) AS user_removed, " +
+ "COALESCE(d.web_viewer,false) AS web_viewer, " +
+ "to_char(d.digital_file_at,'YYYY-MM-DD') AS digital_file_at, " +
+ "to_char(d.needs_fixing_at,'YYYY-MM-DD') AS needs_fixing_at, " +
+ "d.created_at, " +
+ "r.seam_score, r.seam_verdict, r.vision_score, " +
+ "CASE " +
+ "WHEN r.vision_score IS NOT NULL AND r.seam_score IS NOT NULL " +
+ "THEN round(0.6*r.vision_score + 0.4*r.seam_score, 1) " +
+ "WHEN r.vision_score IS NOT NULL THEN r.vision_score " +
+ "WHEN r.seam_score IS NOT NULL THEN r.seam_score " +
+ "ELSE NULL END AS rank_score " +
+ "FROM all_designs d " +
+ "LEFT JOIN wallco_cactus_rank r ON r.design_id = d.id " +
+ "WHERE d.category ILIKE '%cactus%' " +
+ (includeRemoved ? '' : "AND NOT COALESCE(d.user_removed,false) ") +
+ ") t;"
+ );
+ const items = JSON.parse(rawJson || '[]');
+ res.json({ ok: true, count: items.length, items });
+ } catch (e) {
+ res.status(500).json({ error: e.message });
+ }
+});
+
+// Apply one cactus verdict. body { action: 'bad'|'digital'|'fix'|'live' }.
+function applyCactusDecision(id, action) {
+ const raw = psqlQuery(`SELECT row_to_json(t) FROM (SELECT id, local_path FROM all_designs WHERE id=${id}) t;`);
+ if (!raw) throw new Error('design not found');
+ const row = JSON.parse(raw);
+ if (action === 'bad') {
+ // 1) Bad Pattern — remove from all surfaces + quarantine the PNG.
+ psqlExecLocal(`UPDATE all_designs SET is_published=FALSE, user_removed=TRUE, web_viewer=FALSE WHERE id=${id};`);
+ if (row.local_path && fs.existsSync(row.local_path)) {
+ try {
+ const qDir = path.join(__dirname, 'data', 'generated_cactus_quarantine');
+ fs.mkdirSync(qDir, { recursive: true });
+ const dest = path.join(qDir, path.basename(row.local_path));
+ if (!fs.existsSync(dest)) fs.renameSync(row.local_path, dest);
+ } catch (e) { console.warn('[cactus bad] quarantine move failed:', e.message); }
+ }
+ } else if (action === 'digital') {
+ // 2) Unpublish, sell as digital file — stamp the date field.
+ psqlExecLocal(`UPDATE all_designs
+ SET is_published=FALSE, web_viewer=FALSE, digital_file_at=now(),
+ tags = array_remove(COALESCE(tags, ARRAY[]::text[]), 'digital-file') || ARRAY['digital-file']::text[]
+ WHERE id=${id};`);
+ } else if (action === 'fix') {
+ // 3) Keep but needs fixing — flag + date, leave publish state as-is.
+ psqlExecLocal(`UPDATE all_designs
+ SET needs_fixing_at=now(),
+ tags = array_remove(COALESCE(tags, ARRAY[]::text[]), 'needs-fixing') || ARRAY['needs-fixing']::text[]
+ WHERE id=${id};`);
+ } else if (action === 'live') {
+ // 4) Keep, go live in a web viewer — publish + flag, clear fix/digital state.
+ psqlExecLocal(`UPDATE all_designs
+ SET is_published=TRUE, web_viewer=TRUE, user_removed=FALSE,
+ needs_fixing_at=NULL, digital_file_at=NULL,
+ tags = array_remove(COALESCE(tags, ARRAY[]::text[]), 'needs-fixing')
+ WHERE id=${id};`);
+ } else {
+ throw new Error('bad action');
+ }
+ fs.appendFileSync(path.join(__dirname, 'data', 'cactus-decisions.jsonl'),
+ JSON.stringify({ ts: new Date().toISOString(), id, action }) + '\n');
+}
+
+app.post('/api/cactus-decision/:id', express.json({ limit: '2kb' }), (req, res) => {
+ if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
+ const id = parseInt(req.params.id, 10);
+ if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
+ const action = String(req.body?.action || '').toLowerCase();
+ if (!['bad', 'digital', 'fix', 'live'].includes(action)) {
+ return res.status(400).json({ error: 'action must be bad|digital|fix|live' });
+ }
+ try {
+ applyCactusDecision(id, action);
+ res.json({ ok: true, id, action });
+ } catch (e) {
+ res.status(e.message === 'design not found' ? 404 : 500).json({ error: e.message });
+ }
+});
+
+// Bulk variant — one HTTP call for a multi-select action bar.
+app.post('/api/cactus-decision/bulk', express.json({ limit: '64kb' }), (req, res) => {
+ if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
+ const ids = Array.isArray(req.body?.ids) ? req.body.ids.map(n => parseInt(n, 10)).filter(Number.isFinite) : [];
+ const action = String(req.body?.action || '').toLowerCase();
+ if (!ids.length) return res.status(400).json({ error: 'ids[] required' });
+ if (!['bad', 'digital', 'fix', 'live'].includes(action)) return res.status(400).json({ error: 'bad action' });
+ let ok = 0, err = 0; const results = [];
+ for (const id of ids) {
+ try { applyCactusDecision(id, action); results.push({ id, ok: true }); ok++; }
+ catch (e) { results.push({ id, ok: false, error: e.message.slice(0, 200) }); err++; }
+ }
+ res.json({ ok: true, total: ids.length, applied: ok, errored: err, action, results });
+});
+
// ── /admin/elements — curated library of "solid opaque middle" designs.
// Browse-first surface for composing new designs from quality-vetted
// existing tiles. Per the elements skill (~/.claude/skills/elements).
@@ -8291,7 +8415,7 @@ RETURNING id`);
fs.writeFileSync(rawPath, Buffer.from(data, 'base64'));
const seamlessPy = require('child_process').spawnSync('python3', ['-c', `
import sys
-from PIL import Image, ImageFilter
+from PIL import Image
src = Image.open(sys.argv[1]).convert('RGB')
W, H = src.size
# Step 1: Photoshop Offset (W/2 right, H/2 down) with wraparound
@@ -8300,33 +8424,40 @@ shifted.paste(src.crop((W//2, H//2, W, H)), (0, 0))
shifted.paste(src.crop((0, H//2, W//2, H)), (W - W//2, 0))
shifted.paste(src.crop((W//2, 0, W, H//2)), (0, H - H//2))
shifted.paste(src.crop((0, 0, W//2, H//2)), (W - W//2, H - H//2))
-# Step 2: feather-blend a band at the new center cross (where original outer edges meet)
-# Band width = max(8, min(W,H)//40). Blend uses a gaussian on a thin strip.
-band = max(8, min(W, H) // 40)
+# Step 2: NARROW SHARP cross-fade at the new center cross (where the original outer
+# edges meet). The OLD heal used a 25px 2-D GaussianBlur(radius=12) here; after the
+# Step-3 offset-back that blurred band landed on all four VISIBLE outer edges,
+# producing the "fuzzy edges" + dark vignette defect (and double-blurred corners
+# where the two bands overlap). Fix: (a) ~6px band instead of 25px, and (b) low-pass
+# ONLY across the seam normal (1-D) so motif silhouettes stay crisp and no brightness
+# halo forms. A 1-D low-pass is exactly what hides the seam discontinuity; the 2-D
+# blur was smearing detail in the orthogonal direction for no benefit.
+band = max(4, min(W, H) // 160) # ~6px @1024 (was max(8, min//40) = 25px)
cx, cy = W // 2, H // 2
-# Vertical band around x=cx
-strip = shifted.crop((cx - band, 0, cx + band, H))
-strip_blur = strip.filter(ImageFilter.GaussianBlur(radius=band // 2))
-# Mask: opaque at center, 0 at band edges
-mask = Image.new('L', strip.size, 0)
-for x in range(strip.size[0]):
- d = abs(x - band) / band # 0 at center, 1 at edges
- a = int(255 * (1 - d) ** 2)
- for y in range(0, strip.size[1], 4):
- for yy in range(min(4, strip.size[1] - y)):
- mask.putpixel((x, y + yy), a)
-shifted.paste(strip_blur, (cx - band, 0), mask)
-# Horizontal band around y=cy
-strip2 = shifted.crop((0, cy - band, W, cy + band))
-strip2_blur = strip2.filter(ImageFilter.GaussianBlur(radius=band // 2))
-mask2 = Image.new('L', strip2.size, 0)
-for y in range(strip2.size[1]):
- d = abs(y - band) / band
- a = int(255 * (1 - d) ** 2)
- for x in range(0, strip2.size[0], 4):
- for xx in range(min(4, strip2.size[0] - x)):
- mask2.putpixel((x + xx, y), a)
-shifted.paste(strip2_blur, (0, cy - band), mask2)
+def hsmooth(strip): # horizontal-only low-pass — vertical detail untouched
+ w, h = strip.size
+ return strip.resize((max(2, w // 3), h), Image.BILINEAR).resize((w, h), Image.BILINEAR)
+def vsmooth(strip): # vertical-only low-pass — horizontal detail untouched
+ w, h = strip.size
+ return strip.resize((w, max(2, h // 3)), Image.BILINEAR).resize((w, h), Image.BILINEAR)
+def qmask(size, axis): # quadratic falloff: opaque at seam center, 0 at band edges
+ w, h = size
+ m = Image.new('L', size, 0); px = m.load()
+ if axis == 'x':
+ for x in range(w):
+ a = int(255 * (1 - abs(x - band) / band) ** 2)
+ for y in range(h): px[x, y] = a
+ else:
+ for y in range(h):
+ a = int(255 * (1 - abs(y - band) / band) ** 2)
+ for x in range(w): px[x, y] = a
+ return m
+# Vertical seam at x=cx (smooth horizontally across it)
+vstrip = shifted.crop((cx - band, 0, cx + band, H))
+shifted.paste(hsmooth(vstrip), (cx - band, 0), qmask(vstrip.size, 'x'))
+# Horizontal seam at y=cy (smooth vertically across it)
+hstrip = shifted.crop((0, cy - band, W, cy + band))
+shifted.paste(vsmooth(hstrip), (0, cy - band), qmask(hstrip.size, 'y'))
# Step 3: Offset back (W/2, H/2) → original orientation
out = Image.new('RGB', (W, H))
out.paste(shifted.crop((cx, cy, W, H)), (0, 0))
← 5d63f4b fuzzy-seam scanner: detect pil-heal blur-band 'fuzzy cross'
·
back to Wallco Ai
·
Add cactus curator: ranked grid + density slider + 4-verdict fbc35e7 →