← back to Wallco Ai
docs: mural-pipeline.md — full room-size scenic-mural architecture + runbook [yolo T4, local-only]
34f8c547a45fb4fdf742642571f4face2ee91a37 · 2026-06-03 09:55:44 -0700 · Steve Abrams
Documents geometry (21,600x19,800 master, 6in sky/grass bleed, 24/36/54in panel
slicing), the build pipeline (cover-fit then SDXL outpaint then tiled ESRGAN, in
the order model size limits force), both key findings (1448px ESRGAN cap leads to
tiling; mirror-bleed artifact leads to generative outpaint), every command, and
what stays Steve-gated (paid batch, demotion --apply, settlement-before-publish).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 34f8c547a45fb4fdf742642571f4face2ee91a37
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jun 3 09:55:44 2026 -0700
docs: mural-pipeline.md — full room-size scenic-mural architecture + runbook [yolo T4, local-only]
Documents geometry (21,600x19,800 master, 6in sky/grass bleed, 24/36/54in panel
slicing), the build pipeline (cover-fit then SDXL outpaint then tiled ESRGAN, in
the order model size limits force), both key findings (1448px ESRGAN cap leads to
tiling; mirror-bleed artifact leads to generative outpaint), every command, and
what stays Steve-gated (paid batch, demotion --apply, settlement-before-publish).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
docs/mural-pipeline.md | 142 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 142 insertions(+)
diff --git a/docs/mural-pipeline.md b/docs/mural-pipeline.md
new file mode 100644
index 0000000..1fd21d0
--- /dev/null
+++ b/docs/mural-pipeline.md
@@ -0,0 +1,142 @@
+# Room-size scenic-mural pipeline
+
+Recreate Monterey (and other scenic) murals as ONE continuous **12 ft W × 11 ft H**
+wall scene, sliced into customer-width vertical **panels** (24" / 36" / 54") for
+print. Built 2026-06-03. Brief: *"the entire design is all the panels — create
+room size to fit."*
+
+This is the **non-repeating scenic** path. It is NOT for repeating patterns:
+patterns tile infinitely so their native repeat IS the 150-DPI master (see
+`build-tif.py` `compute_max_print` → 240"×132" for `seamless_tile`).
+
+---
+
+## Geometry (@ 150 DPI)
+
+| Thing | Inches | Pixels | Notes |
+|--------|-------------|-----------------|-------|
+| Master | 144 × 132 | 21,600 × 19,800 | the whole wall = "room size", 428 MP |
+| Focal | 144 × 120 | 21,600 × 18,000 | the protected scene |
+| Bleed | 6" top + 6" bottom | 900 px each | forgiving sky (top) / grass (bottom) — installer shift/trim zone |
+
+**Panels** (customer picks width, master is sliced vertically, full height):
+
+| Width | Panels for 12 ft | Per-panel px |
+|-------|------------------|--------------|
+| 24" | 6 (even) | 3,600 |
+| 36" | 4 (even) | 5,400 |
+| 54" | 3 (2 full + 36" remainder) | 8,100 · 8,100 · 5,400 |
+
+Because the master is 21,600 px across the full 144", **any** vertical slice is
+automatically 150 DPI — no per-panel rescaling. Assemble all panels of one width
+side-by-side → the complete scene.
+
+---
+
+## Why the old murals can't be physical (and the demotion)
+
+Scenic murals were generated at **1024×1024**. 1024 px across 144" = **~7 DPI** —
+unusable for print. So every mural that isn't a real 12 ft master must be
+**removed from physical sale and kept as a digital download** (the digital path
+serves an on-demand upscale, not the TIF).
+
+`scripts/demote-lowres-murals.py` (REPORT-ONLY) scans `kind IN ('mural','mural_panel')`
+for `tif_w_px < 21600` → **1,652 rows, 644 published**. It writes
+`data/mural-demotion-plan.jsonl` and makes **zero DB changes**. Applying it
+(unpublish-physical → digital-only) is **Steve-gated** — customer-facing on
+hundreds of live rows, and there is **no `digital_only` schema column yet** (only
+`digital_file_at`), so the surfacing mechanism must be decided before `--apply`.
+
+---
+
+## The build pipeline (`scripts/build-mural-master.py`)
+
+Method chosen by DTD 2026-06-03 (Steve): **upscale existing art + generative
+outpaint bleed**, with **tiled ESRGAN** for true detail.
+
+Order is forced by model size limits (SDXL inpaint can't do 21k px), so:
+
+```
+source 1024²
+ └─ cover_fit → low-res focal box 1080×900 (6:5, scale-to-cover + center-crop, NO stretch)
+ └─ outpaint_bleed → adds 45px sky (top) + 45px grass (bottom) at low res
+ via lucataco/sdxl-inpainting (Replicate, latest version, no pinned hash)
+ → 1080×990 low-res master
+ └─ esrgan_tiled → 1080 → 21,600 px
+ nightmareai/real-esrgan x4, chained, but the model caps INPUT at
+ ~1448px (2.09M px GPU limit) — so each x4 level >1448px is TILED
+ (≤1200px tiles, 128px overlap, feather-blended) then restitched.
+ Loop runs a level only while max×2 < target (no huge overshoot),
+ LANCZOS finishes the <2× remainder.
+ → 21,600 × 19,800 master
+ └─ slice_panels → 24"/36"/54" vertical TIFs + manifest.json
+```
+
+Fallbacks: no Replicate token → pure-LANCZOS upscale + mirror+fade bleed (honest,
+logged). Outpaint failure → mirror+fade bleed.
+
+### Key finding — the 1448px ESRGAN cap
+Replicate `real-esrgan` rejects inputs > ~1,448 px (`16.7M > GPU max 2.09M`). So
+naive chained ESRGAN is effectively ONE pass (1024→4096); everything above is
+LANCZOS. **Tiled ESRGAN** (split→upscale→restitch) is what gets true reconstructed
+detail to 21,600 px. For wallco's flat screen-print silhouette art the difference
+is nearly invisible (hard edges reconstruct cleanly even via LANCZOS), but tiled
+is correct for detailed scenics.
+
+### Key finding — bleed must be generated, not mirrored
+The first proof used a deterministic mirror+fade bleed: where the tree canopy
+touched the top edge it produced **upside-down branches in the "sky"**. So the top
+bleed needs real generated sky (SDXL outpaint), which is why `--bleed=outpaint` is
+the default. Mirror+fade remains as `--bleed=mirror` (fine where edges are already
+clean sky/ground). Outpainted content is sky/grass only (negative-prompted against
+birds/butterflies/people) — low settlement risk, but **run the settlement gate on
+the final master before any publish** per the project hard rule.
+
+---
+
+## Commands
+
+```bash
+# Geometry self-test — zero cost, no DB/net
+python3 scripts/build-mural-master.py --self-test
+
+# Cost estimate before spending — no calls made
+python3 scripts/build-mural-master.py --estimate 12
+# → 12×11ft = 21,600×19,800; ~17 real-esrgan calls + 1 outpaint per mural
+# → ~$0.28/mural, ~$3.30 for the 12-mural batch
+
+# Curate the first batch (read-only DB → manifest)
+python3 scripts/curate-monterey-batch.py # → data/mural-batch-monterey.json (12 hue-spread)
+
+# Build ONE master (PAID — Replicate spend, multi-GB master on /Volumes/Henry)
+python3 scripts/build-mural-master.py <id> # full 12×11ft, default outpaint+tiled
+python3 scripts/build-mural-master.py <id> --print-ft 4 --height-ft 4 # bounded proof
+python3 scripts/build-mural-master.py <id> --bleed mirror --upscale lanczos # cheap/offline
+
+# Build the curated batch (PAID) — the manifest carries the exact command
+# for id in $(...ids...); do python3 scripts/build-mural-master.py $id; done
+
+# Visual QA of a built master (no spend)
+python3 scripts/mural-contact-sheet.py <id> # → <master_dir>/contact-sheet.png
+
+# Demotion of low-res murals to digital-only (REPORT ONLY; apply is Steve-gated)
+python3 scripts/demote-lowres-murals.py # → data/mural-demotion-plan.jsonl
+```
+
+Masters land under `/Volumes/Henry/wallco-ai-archive/mural-masters/<id>/`
+(`WALLCO_MURAL_DIR` override), each with `master_*.tif`, `panels_<w>in/`, and
+`summary.json`.
+
+Env knobs: `WALLCO_MURAL_PRINT_FT`, `WALLCO_ESRGAN_PASSES` (build-tif),
+`WALLCO_MURAL_DIR`, `REPLICATE_API_TOKEN` (else read from
+`~/Projects/animate-museum-posts/.env`).
+
+---
+
+## Parked for Steve (gated — never run autonomously)
+
+1. **The paid 12×11ft batch** — built, proven, curated (12 ids in the manifest).
+ Run `--estimate 12` to confirm ~$3.30, then the manifest's batch command.
+2. **The low-res demotion `--apply`** — 644 live rows; customer-facing; needs a
+ `digital_only` surfacing decision first. Report-only today.
+3. **Publish** any new master only after the settlement gate returns OK.
← 1327a3c loop tick3: 2867 never-gated analysis — prompt triage unreli
·
back to Wallco Ai
·
Q3: read-only top-12 Monterey manifest generator (one-comman ef20e20 →