Spaces:
Runtime error
Runtime error
| import os | |
| from PIL import Image, ImageStat | |
| Image.MAX_IMAGE_PIXELS = 200_000_000 | |
| from engine.hoodie_rules import AOP_HOODIE_MAPPING_RULES | |
| from engine.hf_processors import run_optional_ai_processors | |
| def norm(name): | |
| return name.lower().replace(" ", "_") | |
| def pct_box(w, h, x1, y1, x2, y2): | |
| return (int(w * x1), int(h * y1), int(w * x2), int(h * y2)) | |
| def region_score(img): | |
| stat = ImageStat.Stat(img.convert("RGB")) | |
| contrast = sum(stat.stddev) / 3 | |
| saturation_hint = max(stat.mean) - min(stat.mean) | |
| brightness = sum(stat.mean) / 3 | |
| return contrast + saturation_hint + (255 - abs(140 - brightness)) * 0.12 | |
| def analyze_regions(image, prompt, ai_results=None): | |
| ai_results = ai_results or {} | |
| w, h = image.size | |
| p = (prompt or "").lower() | |
| base = [ | |
| ("PRIMARY_FOCAL_REGION", pct_box(w, h, 0.18, 0.10, 0.82, 0.82)), | |
| ("SECONDARY_FOCAL_REGION", pct_box(w, h, 0.04, 0.00, 0.75, 0.50)), | |
| ("ENVIRONMENT_REGION", pct_box(w, h, 0.00, 0.00, 1.00, 1.00)), | |
| ("LOWER_ANCHOR_REGION", pct_box(w, h, 0.05, 0.58, 0.95, 1.00)), | |
| ("LEFT_EDGE_FLOW_REGION", pct_box(w, h, 0.00, 0.04, 0.38, 0.96)), | |
| ("RIGHT_EDGE_FLOW_REGION", pct_box(w, h, 0.62, 0.04, 1.00, 0.96)), | |
| ] | |
| regions = [] | |
| for region_type, box in base: | |
| crop = image.crop(box) | |
| score = region_score(crop) | |
| if region_type == "PRIMARY_FOCAL_REGION": | |
| if any(k in p for k in ["face", "head", "person", "character", "dj", "main", "center", "figure", "subject"]): | |
| score += 45 | |
| if region_type == "SECONDARY_FOCAL_REGION": | |
| if any(k in p for k in ["eye", "monster", "upper", "teeth", "dramatic", "background"]): | |
| score += 35 | |
| if region_type == "LOWER_ANCHOR_REGION": | |
| if any(k in p for k in ["pocket", "deck", "table", "lower", "base", "ground", "smoke"]): | |
| score += 30 | |
| if region_type == "ENVIRONMENT_REGION": | |
| if any(k in p for k in ["pattern", "repeat", "tile", "smoke", "flame", "abstract", "texture"]): | |
| score += 30 | |
| if region_type == "LEFT_EDGE_FLOW_REGION": | |
| if any(k in p for k in ["left sleeve", "left edge", "flame", "smoke"]): | |
| score += 20 | |
| if region_type == "RIGHT_EDGE_FLOW_REGION": | |
| if any(k in p for k in ["right sleeve", "right edge", "purple", "smoke"]): | |
| score += 20 | |
| regions.append({ | |
| "type": region_type, | |
| "box": box, | |
| "score": round(score, 2), | |
| "source": "local_region_analysis", | |
| }) | |
| for category, result in ai_results.items(): | |
| if not result.get("ok"): | |
| continue | |
| for det in result.get("detections", []): | |
| det_type = str(det.get("type", category)).lower() | |
| box = det.get("box") | |
| if not box or len(box) != 4: | |
| continue | |
| region_type = None | |
| if det_type in ["face", "person", "object", "subject"]: | |
| region_type = "PRIMARY_FOCAL_REGION" | |
| elif det_type in ["text", "ocr"]: | |
| region_type = "TEXT_REGION" | |
| elif det_type == "logo": | |
| region_type = "LOGO_REGION" | |
| if region_type: | |
| regions.append({ | |
| "type": region_type, | |
| "box": tuple(map(int, box)), | |
| "score": float(det.get("score", 85)), | |
| "source": "hf_optional_ai", | |
| }) | |
| return sorted(regions, key=lambda r: r["score"], reverse=True) | |
| def cover_resize_no_padding(img, target_w, target_h): | |
| src_w, src_h = img.size | |
| scale = max(target_w / src_w, target_h / src_h) | |
| new_w, new_h = int(src_w * scale), int(src_h * scale) | |
| resized = img.resize((new_w, new_h), Image.LANCZOS) | |
| left = max(0, (new_w - target_w) // 2) | |
| top = max(0, (new_h - target_h) // 2) | |
| return resized.crop((left, top, left + target_w, top + target_h)) | |
| def contain_resize_with_transparency(img, target_w, target_h): | |
| src_w, src_h = img.size | |
| scale = min(target_w / src_w, target_h / src_h) | |
| new_w, new_h = int(src_w * scale), int(src_h * scale) | |
| resized = img.resize((new_w, new_h), Image.LANCZOS) | |
| canvas = Image.new("RGBA", (target_w, target_h), (0, 0, 0, 0)) | |
| canvas.paste(resized, ((target_w - new_w) // 2, (target_h - new_h) // 2)) | |
| return canvas | |
| def tile_texture(img, target_w, target_h): | |
| tile_size = max(300, min(900, target_w, target_h)) | |
| tile = cover_resize_no_padding(img, tile_size, tile_size) | |
| out = Image.new("RGBA", (target_w, target_h)) | |
| for x in range(0, target_w, tile.width): | |
| for y in range(0, target_h, tile.height): | |
| out.paste(tile, (x, y)) | |
| return out.crop((0, 0, target_w, target_h)) | |
| def get_mask_path(template_id, piece_name): | |
| candidates = [ | |
| f"template_assets/{template_id}/masks/{piece_name}.png", | |
| f"template_assets/{template_id}/masks/{norm(piece_name)}.png", | |
| f"template_assets/{template_id}/{piece_name}.png", | |
| f"template_assets/{template_id}/{norm(piece_name)}.png", | |
| ] | |
| for path in candidates: | |
| if os.path.exists(path): | |
| return path | |
| return None | |
| def apply_piece_mask(mapped_img, template_id, piece_name): | |
| mask_path = get_mask_path(template_id, piece_name) | |
| if not mask_path: | |
| return mapped_img, None | |
| mask_img = Image.open(mask_path).convert("RGBA").resize(mapped_img.size, Image.LANCZOS) | |
| alpha = mask_img.getchannel("A") | |
| out = mapped_img.convert("RGBA") | |
| out.putalpha(alpha) | |
| return out, mask_path | |
| def choose_region(piece_name, regions): | |
| rule = AOP_HOODIE_MAPPING_RULES.get(norm(piece_name)) | |
| if not rule: | |
| return regions[0], "safe_cover" | |
| for preferred in rule["preferred"]: | |
| for region in regions: | |
| if region["type"] == preferred and region["type"] not in rule["forbidden"]: | |
| return region, rule["strategy"] | |
| for region in regions: | |
| if region["type"] not in rule["forbidden"]: | |
| return region, rule["strategy"] | |
| env = next((r for r in regions if r["type"] == "ENVIRONMENT_REGION"), regions[0]) | |
| return env, "fallback_texture" | |
| def apply_strategy(image, region, strategy, target_w, target_h): | |
| crop = image.crop(region["box"]).convert("RGBA") | |
| if strategy == "texture_band": | |
| return tile_texture(crop, target_w, target_h) | |
| if strategy == "vertical_texture": | |
| return cover_resize_no_padding(crop, target_w, target_h) | |
| if strategy == "dark_texture": | |
| return cover_resize_no_padding(crop, target_w, target_h) | |
| if strategy == "fallback_texture": | |
| return cover_resize_no_padding(crop, target_w, target_h) | |
| if strategy == "centered_focal": | |
| return cover_resize_no_padding(crop, target_w, target_h) | |
| if strategy == "wide_dramatic": | |
| return cover_resize_no_padding(crop, target_w, target_h) | |
| if strategy == "lower_anchor": | |
| return cover_resize_no_padding(crop, target_w, target_h) | |
| return cover_resize_no_padding(crop, target_w, target_h) | |
| def map_all(image_path, template_id, pieces, prompt, use_optional_ai=False): | |
| image = Image.open(image_path).convert("RGBA") | |
| ai_results = run_optional_ai_processors(image_path, prompt) if use_optional_ai else {} | |
| regions = analyze_regions(image, prompt, ai_results) | |
| outputs = {} | |
| decisions = [] | |
| warnings = [] | |
| used_boxes = {} | |
| mask_usage = {} | |
| for piece_name, size in pieces.items(): | |
| target_w, target_h = size | |
| region, strategy = choose_region(piece_name, regions) | |
| mapped = apply_strategy(image, region, strategy, target_w, target_h) | |
| mapped, mask_path = apply_piece_mask(mapped, template_id, piece_name) | |
| outputs[piece_name] = mapped | |
| decision = { | |
| "piece": piece_name, | |
| "region": region["type"], | |
| "strategy": strategy, | |
| "box": region["box"], | |
| "score": region["score"], | |
| "output_size": [target_w, target_h], | |
| "mask_applied": bool(mask_path), | |
| "mask_path": mask_path, | |
| } | |
| decisions.append(decision) | |
| key = str(region["box"]) | |
| used_boxes[key] = used_boxes.get(key, 0) + 1 | |
| mask_usage[piece_name] = bool(mask_path) | |
| risky_small = norm(piece_name) in [ | |
| "left_sleeve", | |
| "right_sleeve", | |
| "collar", | |
| "left_cuff", | |
| "right_cuff", | |
| "waistband", | |
| "kangaroo_pocket", | |
| ] | |
| if risky_small and region["type"] in [ | |
| "TEXT_REGION", | |
| "LOGO_REGION", | |
| "PRIMARY_FOCAL_REGION", | |
| ]: | |
| warnings.append( | |
| f"{piece_name}: readable text, logo, or primary focal content may be on a risky small panel." | |
| ) | |
| if not mask_path: | |
| warnings.append( | |
| f"{piece_name}: no piece mask found, output is rectangular. Add template_assets/{template_id}/masks/{piece_name}.png for true shaped AOP output." | |
| ) | |
| front_decisions = [ | |
| d for d in decisions if norm(d["piece"]) in ["front", "front_body"] | |
| ] | |
| if front_decisions and front_decisions[0]["region"] != "PRIMARY_FOCAL_REGION": | |
| warnings.append("Main focal point was not placed on the front panel.") | |
| if used_boxes and max(used_boxes.values()) > 3: | |
| warnings.append("Too many panels are using the same crop. Design may look repetitive.") | |
| if not any(mask_usage.values()): | |
| warnings.append( | |
| "No template masks were applied. Current outputs are rectangular crops, not true garment-shaped production pieces." | |
| ) | |
| return outputs, { | |
| "prompt": prompt, | |
| "template_id": template_id, | |
| "plain_engine_logic": [ | |
| "Big panels get meaning.", | |
| "Small panels get texture.", | |
| "Risky seams get atmosphere.", | |
| "Pockets get lower anchors.", | |
| "Cuffs get color bands.", | |
| "Hoods get continuation, not the whole story.", | |
| "True AOP piece shape requires alpha masks from template PNGs.", | |
| ], | |
| "regions": regions, | |
| "decisions": decisions, | |
| "warnings": warnings, | |
| "optional_ai_processors": ai_results, | |
| } |