""" Soul Threading HF Space v12 Primeo-Fused Single-platform Hugging Face / ZeroGPU-ready Gradio app. Fuses: - v11 white-only real-template scanner - v11 no-hero-spam role-safe composition - v11 pocket/flap/overlay underlay continuity - Primeo-style pipeline objects, warnings, quality score, productionReady flag - Divergent layout brancher + Quality Tribunal """ from __future__ import annotations import json import math import tempfile import zipfile from dataclasses import asdict, dataclass, field from pathlib import Path from typing import Any, Dict, List, Optional, Tuple import cv2 import gradio as gr import numpy as np from PIL import Image, ImageDraw APP_VERSION = "soul-threading-hf-space-v12-primeo-fused" DEFAULT_BRANCH_COUNT = 4 try: import spaces except Exception: class spaces: # type: ignore @staticmethod def GPU(*args, **kwargs): def deco(fn): return fn return deco # ============================================================================= # Primeo-inspired pipeline data shapes # ============================================================================= @dataclass class BoundingBox: x1: float y1: float x2: float y2: float @property def width(self) -> float: return self.x2 - self.x1 @property def height(self) -> float: return self.y2 - self.y1 def to_xyxy(self) -> List[float]: return [self.x1, self.y1, self.x2, self.y2] @dataclass class PiecePlan: piece_id: str role: str crop_center: List[float] zoom: float protected_regions: List[str] = field(default_factory=list) avoid_regions: List[str] = field(default_factory=list) reason: str = "" continuity_parent: Optional[str] = None composition_mode: str = "role_safe_crop" @dataclass class LayoutCandidate: candidate_id: str description: str piece_plans: List[PiecePlan] score: float = 0.0 warnings: List[str] = field(default_factory=list) production_ready: bool = False @dataclass class PipelineOutput: version: str selected_candidate_id: str production_ready: bool quality_score: float status_message: str warnings: List[str] files: Dict[str, Any] analysis: Dict[str, Any] selected_plan: Dict[str, Any] all_candidates: List[Dict[str, Any]] scan_reports: List[Dict[str, Any]] piece_reports: List[Dict[str, Any]] # ============================================================================= # Utility # ============================================================================= def safe_name(name: str) -> str: return "".join(c if c.isalnum() or c in "._-" else "_" for c in name).strip("_") or "piece" def odd(n: int) -> int: return n if n % 2 else n + 1 def close_kernel(width: int, height: int) -> int: longest = max(width, height) if longest >= 7000: return 41 if longest >= 5000: return 35 if longest >= 3000: return 25 if longest >= 1500: return 15 return 7 def image_to_rgb_white_bg(path_or_img: Any) -> Image.Image: img = Image.open(path_or_img).convert("RGBA") if not isinstance(path_or_img, Image.Image) else path_or_img.convert("RGBA") bg = Image.new("RGBA", img.size, (255, 255, 255, 255)) bg.alpha_composite(img) return bg.convert("RGB") # ============================================================================= # White-only template scanner # ============================================================================= def get_white_only_mask(template_path: Path) -> Tuple[np.ndarray, Dict[str, Any]]: """ The user rule: only the white printable area marked off by colored seam/bleed/blur/safe lines is usable. Colored lines are restrictions, never pieces. """ img = Image.open(template_path).convert("RGBA") arr = np.array(img) rgb = arr[:, :, :3] h, w = rgb.shape[:2] r, g, b = rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2] hsv = cv2.cvtColor(rgb, cv2.COLOR_RGB2HSV) sat = hsv[:, :, 1] val = hsv[:, :, 2] white = (r >= 238) & (g >= 238) & (b >= 238) & (sat <= 35) & (val >= 238) mask = white.astype(np.uint8) * 255 raw_ratio = float(white.mean()) # If full background is white, drop edge-connected background. if raw_ratio > 0.70: flood = mask.copy() flood_mask = np.zeros((h + 2, w + 2), np.uint8) cv2.floodFill(flood, flood_mask, (0, 0), 0) mask = flood # Close tiny guide/text holes inside the white printable region. # This does NOT admit colored guide lines because the seed mask is white-only. k = odd(close_kernel(w, h)) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((k, k), np.uint8), iterations=1) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8), iterations=1) return mask, { "template": template_path.name, "canvas_size": [int(w), int(h)], "white_ratio_before_repair": round(raw_ratio, 6), "white_ratio_after_repair": round(float((mask > 0).mean()), 6), "scanner_rule": "white_printable_area_only", "colored_lines_are_pieces": False, "guide_lines_exported": False, "close_kernel_px": int(k), } def extract_components(template_path: Path) -> Tuple[int, int, List[Dict[str, Any]], Dict[str, Any]]: mask, report = get_white_only_mask(template_path) h, w = mask.shape n, labels, stats, _ = cv2.connectedComponentsWithStats(mask, 8) comps: List[Dict[str, Any]] = [] rejected: List[Dict[str, Any]] = [] for i in range(1, n): x, y, cw, ch, area = [int(v) for v in stats[i]] area_ratio = area / max(w * h, 1) density = area / max(cw * ch, 1) reason: Optional[str] = None if area_ratio < 0.0015: reason = "too_small" elif cw < 80 or ch < 80: reason = "too_thin" elif density < 0.18: reason = "too_sparse" elif area_ratio > 0.92: reason = "likely_background" if reason: rejected.append({ "bbox": [x, y, cw, ch], "area_ratio": round(area_ratio, 6), "density": round(density, 4), "reason": reason, }) continue comps.append({ "component_id": f"shape_{i:02d}", "bbox": [x, y, cw, ch], "mask": (labels == i).astype(np.uint8) * 255, "area": int(area), "area_ratio": round(area_ratio, 6), "density": round(density, 4), }) comps.sort(key=lambda c: (c["bbox"][1], c["bbox"][0])) report["component_count"] = len(comps) report["rejected_count"] = len(rejected) report["rejected_examples"] = rejected[:20] return w, h, comps, report # ============================================================================= # Artwork analysis # ============================================================================= def cv_subject_box(img: Image.Image) -> Dict[str, Any]: arr = np.array(img.convert("RGB")) h, w = arr.shape[:2] gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY) edges = cv2.Canny(gray, 70, 170) hsv = cv2.cvtColor(arr, cv2.COLOR_RGB2HSV) sat = hsv[:, :, 1] val = hsv[:, :, 2] heat = (edges.astype(np.float32) / 255.0) * 0.60 heat += (sat.astype(np.float32) / 255.0) * 0.30 heat += ((255 - val).astype(np.float32) / 255.0) * 0.10 heat = cv2.GaussianBlur(heat, (31, 31), 0) ys, xs = np.where(heat > np.percentile(heat, 86)) if len(xs) < 30: box = [int(w * 0.25), int(h * 0.15), int(w * 0.75), int(h * 0.85)] else: box = [ int(np.percentile(xs, 4)), int(np.percentile(ys, 4)), int(np.percentile(xs, 96)), int(np.percentile(ys, 96)), ] x1, y1, x2, y2 = box cx = ((x1 + x2) / 2) / max(w, 1) cy = ((y1 + y2) / 2) / max(h, 1) # Rough text map from long horizontal high-contrast components. text_risk = detect_text_like_regions(arr) return { "bbox_xyxy": box, "center_xy_norm": [round(cx, 4), round(cy, 4)], "method": "opencv_saliency", "text_like_regions": text_risk, } def detect_text_like_regions(rgb: np.ndarray) -> List[Dict[str, Any]]: gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY) edges = cv2.Canny(gray, 80, 180) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 5)) joined = cv2.dilate(edges, kernel, iterations=1) n, labels, stats, _ = cv2.connectedComponentsWithStats(joined, 8) h, w = gray.shape[:2] regs = [] for i in range(1, n): x, y, cw, ch, area = [int(v) for v in stats[i]] if area < 300: continue aspect = cw / max(ch, 1) if aspect >= 3.0 and cw > w * 0.08 and ch < h * 0.18: regs.append({ "bbox_xyxy_norm": [round(x/w, 4), round(y/h, 4), round((x+cw)/w, 4), round((y+ch)/h, 4)], "aspect": round(aspect, 2), "reason": "text_like_horizontal_component", }) return regs[:12] def analyze_artwork(img: Image.Image, focal_hint: str = "") -> Dict[str, Any]: subject = cv_subject_box(img) cx, cy = subject["center_xy_norm"] front_center = [float(np.clip(cx, 0.40, 0.56)), float(np.clip(cy, 0.34, 0.58))] return { "version": APP_VERSION, "source_size": list(img.size), "focal_hint": focal_hint, "primary_subject": subject, "composition_law": "hero_front_only; abstract_support_elsewhere; pocket_inherits_front_underlay", "zones": { "front_hero": {"center": front_center, "zoom": 1.05}, "front_hero_tight": {"center": front_center, "zoom": 1.22}, "back_atmosphere": {"center": [0.72, 0.48], "zoom": 1.28}, "back_wide": {"center": [0.52, 0.50], "zoom": 1.02}, "hood_atmosphere_left": {"center": [0.25, 0.20], "zoom": 1.38}, "hood_atmosphere_right": {"center": [0.78, 0.22], "zoom": 1.38}, "sleeve_left_motion": {"center": [0.22, 0.55], "zoom": 1.40}, "sleeve_right_motion": {"center": [0.82, 0.56], "zoom": 1.40}, "trim_texture": {"center": [0.50, 0.88], "zoom": 1.75}, "label_texture": {"center": [0.50, 0.18], "zoom": 2.20}, }, } # ============================================================================= # Template and role handling # ============================================================================= def classify_piece(template_name: str, component_index: int, bbox: List[int], canvas_size: Tuple[int, int]) -> str: n = template_name.lower() if "front" in n: return "front_body" if component_index == 0 else "trim" if "back" in n: return "back_body" if component_index == 0 else "trim" if "hood" in n: return "hood" if "sleeve" in n: return "sleeve" if component_index < 2 else "trim" if "pocket" in n or "kangaroo" in n: return "overlay_pocket" if any(word in n for word in ["flap", "placket", "overlay", "guard", "welt"]): return "overlay" if "label" in n: return "inside_label" # Fallback geometry hints. _, _, pw, ph = bbox ratio = pw / max(ph, 1) if ratio > 1.5 and ph < canvas_size[1] * 0.25: return "trim" return "garment_piece" def template_files_from_zip(template_zip: str, work: Path) -> List[Path]: tdir = work / "template" tdir.mkdir(parents=True, exist_ok=True) with zipfile.ZipFile(template_zip, "r") as z: z.extractall(tdir) files = [ p for p in tdir.rglob("*") if p.suffix.lower() in [".png", ".jpg", ".jpeg", ".webp"] and "__macosx" not in str(p).lower() ] def sort_key(p: Path): n = p.name.lower() if "front" in n: return (0, n) if "back" in n: return (1, n) if "hood" in n: return (2, n) if "sleeve" in n: return (3, n) if "pocket" in n or "kangaroo" in n: return (4, n) if "label" in n: return (5, n) return (9, n) return sorted(files, key=sort_key) # ============================================================================= # Divergent brancher and quality tribunal # ============================================================================= def build_candidate(candidate_id: str, description: str, roles: List[Tuple[str, str]], analysis: Dict[str, Any]) -> LayoutCandidate: z = analysis["zones"] piece_plans: List[PiecePlan] = [] for piece_key, role in roles: if role == "front_body": zone = "front_hero_tight" if candidate_id == "hero_tight" else "front_hero" plan = PiecePlan(piece_key, role, z[zone]["center"], z[zone]["zoom"], ["face", "main_subject"], ["seams"], "hero anchor on front only", composition_mode=zone) elif role == "back_body": zone = "back_wide" if candidate_id == "gallery_wrap" else "back_atmosphere" plan = PiecePlan(piece_key, role, z[zone]["center"], z[zone]["zoom"], [], ["face", "primary_text"], "supporting back crop avoids front hero repeat", composition_mode=zone) elif role == "hood": zone = "hood_atmosphere_left" if len(piece_plans) % 2 == 0 else "hood_atmosphere_right" plan = PiecePlan(piece_key, role, z[zone]["center"], z[zone]["zoom"], [], ["face", "eyes", "text"], "hood receives abstract atmosphere only", composition_mode=zone) elif role == "sleeve": zone = "sleeve_left_motion" if len(piece_plans) % 2 == 0 else "sleeve_right_motion" plan = PiecePlan(piece_key, role, z[zone]["center"], z[zone]["zoom"], [], ["face", "text"], "sleeve receives motion/texture flow", composition_mode=zone) elif role in ["overlay_pocket", "overlay"]: plan = PiecePlan(piece_key, role, [0.5, 0.7], 1.0, ["parent_underlay"], ["fresh_crop"], "overlay inherits rendered parent underlay", continuity_parent="front_body", composition_mode="exact_parent_underlay") elif role == "inside_label": zone = "label_texture" plan = PiecePlan(piece_key, role, z[zone]["center"], z[zone]["zoom"], [], ["face", "text"], "label receives simple hidden detail", composition_mode=zone) else: zone = "trim_texture" plan = PiecePlan(piece_key, role, z[zone]["center"], z[zone]["zoom"], [], ["face", "text"], "trim receives low-detail texture", composition_mode=zone) piece_plans.append(plan) return LayoutCandidate(candidate_id=candidate_id, description=description, piece_plans=piece_plans) def branch_layouts(piece_roles: List[Tuple[str, str]], analysis: Dict[str, Any], branch_count: int = DEFAULT_BRANCH_COUNT) -> List[LayoutCandidate]: candidates = [ build_candidate("balanced_safe", "Default safe AOP: hero front, support crops everywhere else.", piece_roles, analysis), build_candidate("hero_tight", "Tighter front hero with stricter support crops.", piece_roles, analysis), build_candidate("gallery_wrap", "Wider atmospheric back crop while preserving no-hero-spam.", piece_roles, analysis), build_candidate("abstract_safe", "Maximum seam safety: abstract/motion crops dominate all non-front pieces.", piece_roles, analysis), ] return candidates[:max(1, min(branch_count, len(candidates)))] def quality_tribunal(candidate: LayoutCandidate, piece_roles: List[Tuple[str, str]], analysis: Dict[str, Any], require_pocket_continuity: bool = True) -> LayoutCandidate: score = 100.0 warnings: List[str] = [] front_hero_count = 0 nonfront_hero_count = 0 overlay_count = 0 for plan in candidate.piece_plans: if plan.role == "front_body" and "hero" in plan.composition_mode: front_hero_count += 1 if plan.role != "front_body" and "hero" in plan.composition_mode: nonfront_hero_count += 1 if plan.role in ["overlay_pocket", "overlay"]: overlay_count += 1 if require_pocket_continuity and plan.composition_mode != "exact_parent_underlay": score -= 30 warnings.append(f"{plan.piece_id}: overlay did not inherit front underlay.") if plan.role in ["hood", "sleeve", "trim"] and ("face" not in plan.avoid_regions and "eyes" not in plan.avoid_regions): score -= 10 warnings.append(f"{plan.piece_id}: risky non-front piece did not avoid face/eyes.") if plan.zoom < 0.85: score -= 5 warnings.append(f"{plan.piece_id}: low zoom could expose weak crop edges.") if front_hero_count == 0: score -= 20 warnings.append("No clear front hero assignment.") if nonfront_hero_count > 0: score -= 35 * nonfront_hero_count warnings.append("Hero spam detected on non-front piece.") text_regions = analysis.get("primary_subject", {}).get("text_like_regions", []) if text_regions: score -= min(8, len(text_regions) * 2) warnings.append(f"Text-like regions detected: {len(text_regions)}. Avoid seam-heavy pieces.") score = max(0.0, min(100.0, score)) candidate.score = round(score, 2) candidate.warnings = warnings candidate.production_ready = score >= 75 and nonfront_hero_count == 0 return candidate def choose_best_candidate(candidates: List[LayoutCandidate]) -> LayoutCandidate: return sorted(candidates, key=lambda c: (c.production_ready, c.score), reverse=True)[0] # ============================================================================= # Rendering # ============================================================================= def crop_cover(img: Image.Image, target_w: int, target_h: int, center: List[float], zoom: float) -> Tuple[Image.Image, Dict[str, Any]]: sw, sh = img.size tr = target_w / max(target_h, 1) sr = sw / max(sh, 1) if sr > tr: crop_h = sh / zoom crop_w = crop_h * tr else: crop_w = sw / zoom crop_h = crop_w / max(tr, 0.0001) cw, ch = max(2, int(round(crop_w))), max(2, int(round(crop_h))) cx, cy = center[0] * sw, center[1] * sh x1 = max(0, min(int(round(cx - cw / 2)), sw - cw)) y1 = max(0, min(int(round(cy - ch / 2)), sh - ch)) rect = [x1, y1, x1 + cw, y1 + ch] crop = img.crop(tuple(rect)).resize((target_w, target_h), Image.Resampling.LANCZOS) return crop, {"source_crop_xyxy": rect, "center": center, "zoom": zoom} def apply_mask(img: Image.Image, mask: Image.Image) -> Image.Image: out = img.convert("RGBA") out.putalpha(mask.convert("L")) return out def overlay_from_front_underlay(front_img: Image.Image, target_w: int, target_h: int) -> Tuple[Image.Image, Dict[str, Any]]: fw, fh = front_img.size ratio = target_w / max(target_h, 1) cover_w = int(fw * 0.64) cover_h = int(cover_w / max(ratio, 0.001)) if cover_h > int(fh * 0.34): cover_h = int(fh * 0.30) cover_w = int(cover_h * ratio) x1 = max(0, int((fw - cover_w) / 2)) y1 = min(max(0, int(fh * 0.58)), max(0, fh - cover_h)) x2, y2 = min(fw, x1 + cover_w), min(fh, y1 + cover_h) crop = front_img.crop((x1, y1, x2, y2)).resize((target_w, target_h), Image.Resampling.LANCZOS) return crop, { "method": "sample_rendered_front_underlay", "front_coverage_xyxy": [x1, y1, x2, y2], "rule": "pocket/flap/overlay inherits front render, not a fresh crop", } def make_contact_sheet(paths: List[str], out_path: Path) -> None: thumbs = [] for p in paths: im = Image.open(p).convert("RGBA") bg = Image.new("RGBA", im.size, (255, 255, 255, 255)) bg.alpha_composite(im) bg = bg.convert("RGB") bg.thumbnail((260, 260)) tile = Image.new("RGB", (290, 330), "white") tile.paste(bg, ((290 - bg.width) // 2, 10)) d = ImageDraw.Draw(tile) d.text((10, 292), Path(p).name[:36], fill=(0, 0, 0)) thumbs.append(tile) cols = 3 rows = max(1, math.ceil(len(thumbs) / cols)) sheet = Image.new("RGB", (cols * 290, rows * 330), "white") for i, im in enumerate(thumbs): sheet.paste(im, ((i % cols) * 290, (i // cols) * 330)) sheet.save(out_path, quality=92) def find_plan(selected: LayoutCandidate, piece_key: str, role: str) -> PiecePlan: for plan in selected.piece_plans: if plan.piece_id == piece_key: return plan for plan in selected.piece_plans: if plan.role == role: return plan return PiecePlan(piece_key, role, [0.5, 0.5], 1.2, [], ["face", "text"], "fallback role-safe plan") # ============================================================================= # Main pipeline # ============================================================================= @spaces.GPU(duration=180) def run_pipeline( artwork_file, template_zip_file, focal_hint: str, branch_count: int, require_pocket_continuity: bool, export_full_canvases: bool, ): if artwork_file is None: raise gr.Error("Upload artwork first.") if template_zip_file is None: raise gr.Error("Upload an AOP template ZIP first.") work = Path(tempfile.mkdtemp(prefix="soul_threading_v12_")) out_dir = work / "outputs" print_dir = out_dir / "print" manifest_dir = out_dir / "manifest" preview_dir = out_dir / "preview" for d in [print_dir, manifest_dir, preview_dir]: d.mkdir(parents=True, exist_ok=True) artwork = Image.open(artwork_file).convert("RGBA") analysis = analyze_artwork(artwork, focal_hint=focal_hint or "") template_files = template_files_from_zip(template_zip_file, work) scan_reports: List[Dict[str, Any]] = [] piece_meta: List[Dict[str, Any]] = [] roles_for_brancher: List[Tuple[str, str]] = [] # Pre-scan so brancher sees the whole garment. scanned_templates = [] for template_path in template_files: width, height, comps, scan_report = extract_components(template_path) scan_reports.append(scan_report) scanned_templates.append((template_path, width, height, comps)) for idx, comp in enumerate(comps): role = classify_piece(template_path.name, idx, comp["bbox"], (width, height)) piece_key = f"{safe_name(template_path.stem)}__piece_{idx + 1:02d}_{role}" roles_for_brancher.append((piece_key, role)) if not roles_for_brancher: raise gr.Error("No printable white areas found in the template ZIP.") candidates = branch_layouts(roles_for_brancher, analysis, int(branch_count)) candidates = [quality_tribunal(c, roles_for_brancher, analysis, require_pocket_continuity) for c in candidates] selected = choose_best_candidate(candidates) gallery: List[str] = [] front_underlay: Optional[Image.Image] = None piece_reports: List[Dict[str, Any]] = [] for template_path, width, height, comps in scanned_templates: if not comps: piece_reports.append({"template": template_path.name, "status": "no white printable pieces found"}) continue full_canvas = Image.new("RGBA", (width, height), (0, 0, 0, 0)) for idx, comp in enumerate(comps): x, y, pw, ph = comp["bbox"] role = classify_piece(template_path.name, idx, comp["bbox"], (width, height)) piece_key = f"{safe_name(template_path.stem)}__piece_{idx + 1:02d}_{role}" plan = find_plan(selected, piece_key, role) mask = Image.fromarray(comp["mask"], "L").crop((x, y, x + pw, y + ph)) if role in ["overlay_pocket", "overlay"] and front_underlay is not None and require_pocket_continuity: crop, crop_report = overlay_from_front_underlay(front_underlay, pw, ph) reason = "overlay continuity: sampled from rendered front underlay" else: crop, crop_report = crop_cover(artwork, pw, ph, plan.crop_center, plan.zoom) crop_report["composition_mode"] = plan.composition_mode reason = plan.reason piece = apply_mask(crop, mask) piece_name = f"{piece_key}.png" piece_path = print_dir / piece_name piece.save(piece_path) gallery.append(str(piece_path)) full_canvas.alpha_composite(piece, (x, y)) if role == "front_body" and front_underlay is None: front_underlay = piece.copy() piece_reports.append({ "template": template_path.name, "piece": piece_name, "role": role, "bbox": comp["bbox"], "assignment_reason": reason, "crop_report": crop_report, "white_only_scanner": True, "colored_lines_used_as_pieces": False, "status": "mapped", }) if export_full_canvases: full_canvas.save(print_dir / f"{safe_name(template_path.stem)}__full_canvas.png") sheet_path = preview_dir / "contact_sheet.jpg" make_contact_sheet(gallery, sheet_path) status_msg = "Production ready." if selected.production_ready else "Needs review: Quality Tribunal found risks." pipeline_output = PipelineOutput( version=APP_VERSION, selected_candidate_id=selected.candidate_id, production_ready=selected.production_ready, quality_score=selected.score, status_message=status_msg, warnings=selected.warnings, files={ "piece_png_count": len(gallery), "contact_sheet": "preview/contact_sheet.jpg", "zip_name": "soul_threading_v12_primeo_fused_outputs.zip", }, analysis=analysis, selected_plan=asdict(selected), all_candidates=[asdict(c) for c in candidates], scan_reports=scan_reports, piece_reports=piece_reports, ) manifest = asdict(pipeline_output) (manifest_dir / "mapping_manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8") (manifest_dir / "mapping_process_log.txt").write_text( "v12 Primeo-fused pipeline: one HF Space, white-only scanner, divergent layout brancher, Quality Tribunal, pocket continuity.\n", encoding="utf-8", ) zip_path = work / "soul_threading_v12_primeo_fused_outputs.zip" with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as z: for p in out_dir.rglob("*"): z.write(p, p.relative_to(out_dir)) return str(zip_path), gallery[:60], str(sheet_path), json.dumps(manifest, indent=2) # ============================================================================= # Gradio UI # ============================================================================= with gr.Blocks(title="Soul Threading v12 Primeo-Fused") as demo: gr.Markdown( """ # Soul Threading v12 Primeo-Fused 🧵 **One-platform Hugging Face / ZeroGPU-ready mapper.** This folds Primeo's pipeline thinking into the v11 production core: - white-only real-template scanner - no hero spam - pocket/flap/overlay continuity - divergent layout brancher - Quality Tribunal - production ZIP export """ ) with gr.Tab("Map Garment"): with gr.Row(): artwork = gr.Image(type="filepath", label="Artwork") template_zip = gr.File(file_types=[".zip"], label="AOP template ZIP") with gr.Row(): focal_hint = gr.Textbox(label="Focal hint", value="main character / face / hero subject", lines=1) branch_count = gr.Slider(1, 4, value=4, step=1, label="Divergent layout branches") require_pocket = gr.Checkbox(value=True, label="Require pocket/flap continuity") export_full = gr.Checkbox(value=True, label="Export full template canvases too") btn = gr.Button("Run Soul Threading v12 Mapper", variant="primary") out_zip = gr.File(label="Download production ZIP") gallery = gr.Gallery(label="Mapped print pieces", columns=3, height=720) sheet = gr.Image(label="Contact sheet") manifest = gr.Textbox(label="Manifest / Quality Tribunal", lines=28) btn.click( run_pipeline, inputs=[artwork, template_zip, focal_hint, branch_count, require_pocket, export_full], outputs=[out_zip, gallery, sheet, manifest], api_name="map_aop_v12", ) with gr.Tab("Rules"): gr.Markdown( """ ## Locked production rules ```txt Only white printable template areas become garment pieces. Colored seam / bleed / blur / safe lines are restrictions, never pieces. The front body gets the hero by default. Back, hood, sleeves, trim and label get support crops. Pocket/flap/overlay inherits the rendered parent underlay. Exported print PNGs are transparent outside the piece shape. ``` ## Primeo ideas fused in ```txt PipelineOutput PiecePlan qualityScore warnings productionReady divergent branch candidates Quality Tribunal ``` """ ) with gr.Tab("Next Upgrade Slots"): gr.Markdown( """ ## ZeroGPU model slots This app is ZeroGPU-ready and uses `@spaces.GPU` on the pipeline call. Next optional upgrades can happen inside this same Space: ```txt Grounding DINO detector SAM/SAM2 segmentation assist Florence OCR/text-region assist VLM art director template memory database ``` The deterministic export engine stays in charge of final production math. """ ) demo.queue(max_size=8).launch()