import os import io import json import base64 import time import numpy as np import logging import gradio as gr from PIL import Image from gradio_client import Client # Setup logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # ───────── Backend connection with health monitoring ───────── HF_TOKEN = os.getenv("HF_TOKEN") if not HF_TOKEN: raise ValueError("HF_TOKEN environment variable is required") # Backend connection state backend_status = { "client": None, "connected": False, "last_check": None, "error_message": "" } def check_backend_connection(): """Check backend connection and update status""" global backend_status try: test_client = Client("SnapwearAI/Image_to_flatlay", hf_token=HF_TOKEN) backend_status["client"] = test_client backend_status["connected"] = True backend_status["error_message"] = "" backend_status["last_check"] = time.time() logger.info("✅ Backend connection established") return True, "🟢 Backend is ready for Image To Flatlay" except Exception as e: backend_status["client"] = None backend_status["connected"] = False backend_status["last_check"] = time.time() error_str = str(e).lower() if "timeout" in error_str or "read operation timed out" in error_str: backend_status["error_message"] = "Backend is starting up (5-6 minutes on first load)" return False, "🟡 Backend is starting up. Please wait 5-6 minutes and try again." else: backend_status["error_message"] = f"Connection error: {str(e)}" return False, f"🔴 Backend error: {str(e)}" # Initial connection attempt try: success, status_msg = check_backend_connection() if success: logger.info("Backend client established") else: logger.warning(f"Initial backend connection failed: {status_msg}") except Exception as e: logger.error(f"Failed to connect to backend: {e}") backend_status["connected"] = False backend_status["error_message"] = str(e) def update_backend_status(): """Check and update backend status""" success, status_msg = check_backend_connection() if success: css_class = "status-ready" elif "starting up" in status_msg: css_class = "status-starting" else: css_class = "status-error" status_html = f'
{status_msg}
' return status_html # ───────── Styling ───────── css = """ body, .gradio-container { font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif; } #col-left, #col-mid, #col-right { margin: 0 auto; max-width: 430px; } #col-showcase { margin: 0 auto; max-width: 1100px; } #button { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; font-weight: 600; font-size: 18px; border: none; border-radius: 12px; padding: 12px 24px; transition: all 0.3s ease; } #button:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(102,126,234,0.3); } #button:disabled { background: #ccc !important; cursor: not-allowed; transform: none; box-shadow: none; } .hero-section { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px 20px; border-radius: 20px; margin: 20px 0; text-align: center; } .feature-box { background: #f8fafc; border: 1px solid #e2e8f0; padding: 20px; border-radius: 12px; margin: 10px 0; border-left: 4px solid #667eea; } .showcase-section { background: #ffffff; border: 1px solid #e2e8f0; padding: 30px; border-radius: 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); margin: 20px 0; } .step-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 15px; border-radius: 12px; text-align: center; font-weight: 600; margin: 10px 0; } .social-links { text-align: center; margin: 20px 0; } .social-links a { margin: 0 10px; padding: 8px 16px; background: #667eea; color: white; text-decoration: none; border-radius: 8px; transition: all 0.3s ease; } .social-links a:hover { background: #764ba2; transform: translateY(-2px); } .error-message { color: #dc3545; font-weight: 500; } .success-message { color: #28a745; font-weight: 500; } .status-banner { padding: 15px; border-radius: 12px; margin: 10px 0; text-align: center; font-weight: 600; } .status-ready { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; } .status-starting { background: #fff3cd; border: 1px solid #ffeaa7; color: #856404; } .status-error { background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; } .queue-info { background: #e8f4fd; border: 1px solid #bee5eb; padding: 12px; border-radius: 8px; margin: 10px 0; text-align: center; font-size: 14px; color: #0c5460; } """ def image_to_base64(image: Image.Image) -> str: """ Convert a PIL Image to a base64‐encoded PNG string. """ if image is None: return "" if image.mode not in ("RGB", "RGBA"): image = image.convert("RGB") buffer = io.BytesIO() image.save(buffer, format="PNG", optimize=True) buffer.seek(0) return base64.b64encode(buffer.getvalue()).decode("utf-8") def base64_to_image(b64_str: str) -> Image.Image: """ Decode a base64 string (with or without data URL prefix) into a PIL Image. """ if not b64_str: return None try: if b64_str.startswith("data:"): b64_str = b64_str.split(",", 1)[1] data = base64.b64decode(b64_str) return Image.open(io.BytesIO(data)).convert("RGBA") except Exception as e: logger.error(f"Failed to decode base64 image: {e}") return None # ───────── Section 2: Flatley Image Generation ───────── def generate_flatlay(image, prompt): """ 1. Convert ImageEditor data to JSON payload. 2. Use `mask_b64` directly. 3. Call backend `/predict` endpoint. 4. Decode returned base64 and return as PIL Image. """ # Check backend connection first if not backend_status["connected"] or not backend_status["client"]: success, status_msg = check_backend_connection() if not success: return None, 0, status_msg current_client = backend_status["client"] # Validate inputs if not image: return None if not prompt: return None # 1) Prepare JSON payload payload_str = image_to_base64(image) # 2) Invoke backend from gradio_client import Client HF_TOKEN = os.getenv("HF_TOKEN") client = Client("SnapwearAI/Image_to_flatlay", hf_token=HF_TOKEN) try: result_b64 = current_client.predict( payload_str, prompt, api_name="/predict" ) except Exception as e: logger.error(f"Image generation call failed: {e}") return None # 3) Decode and return result_img = base64_to_image(result_b64) if result_b64 else None return result_img # ───────── Gradio App (Single Canvas) ───────── # ───────── Main UI ───────── with gr.Blocks(css=css, title="Snapwear Image to Flatlay") as demo: # ──────── Hero Section ──────── gr.HTML("""

👕 Snapwear Image To Flatlay

Transform your model’s cloth into a flatlay.

Disclaimer: This demo is free for trials only. Any solicitation for payment based on the free features we provide on this HuggingFace Space is a fraudulent act.

""") # ──────── Backend Status Section ──────── with gr.Row(): with gr.Column(): # Initial status display if backend_status["connected"]: initial_status = '
🟢 Image To Flatlay is ready!
' else: initial_status = '
🟡 Model may be starting up. Click "Check Status" to verify.
' status_display = gr.HTML(value=initial_status) # Status check button check_status_btn = gr.Button("🔄 Check Status", size="sm") # ──────── Key Features ──────── gr.HTML("""

👗 Model-to-Flatlay Conversion

Automatically transform a model-worn garment into a professional flatlay presentation

⚙️ Automated Garment Extraction

Isolate clothing from the model image with pixel-perfect precision for crisp layouts

🚀 Rapid Processing

Generate flatlay images in under 60 seconds with one click

""") # ──────── Main Interface ──────── with gr.Row(): with gr.Column(scale=1, elem_id="col-left"): gr.HTML('
Step 1: Upload Model Image 🖼️
') model_image = gr.Image( label="Model Image", type="pil", image_mode="RGBA", height=600 ) gr.HTML('
' '⚠️ Important: First upload the model image wearing clothes.
') gr.Examples( label="Example Model Images", inputs=model_image, examples_per_page=12, examples=[f"examples/{i}.jpg" for i in range(1, 3)] if os.path.exists("examples") else [], ) # ③ Generated Image with gr.Column(scale=1, elem_id="col-right"): gr.HTML('
Step 2: Prompt and Generate ✨👗
') result_preview = gr.Image(label="Generated flatlay",show_share_button=True, height=600) with gr.Column(): prompt_box = gr.Textbox(label="Clothing Prompt", placeholder="Describe the garment to flatlay...") # ✅ Adding prompt examples here gr.Examples( label="Prompt Examples", examples=[ "t-shirt", "dress" ], inputs=prompt_box ) gen_button = gr.Button("Generate Flatlay", elem_id="button") # ──────── Event Handlers ──────── # Status check button check_status_btn.click( fn=update_backend_status, outputs=[status_display] ) gen_button.click( fn=generate_flatlay, inputs=[model_image, prompt_box], outputs=[result_preview], concurrency_limit=1, # Match backend queue system show_progress=True ) # ──────── Look-Book Grid ──────── # Virtual try-on examples lookbook_rows = [ [f"lookbook/model{i}.jpg", f"lookbook/result{i}.jpg"] for i in range(1, 4) if os.path.exists("lookbook") # adjust range to your file count ] if lookbook_rows: gr.HTML("""

🌟 Image to Flatlay Showcase

""") gr.Examples( examples=lookbook_rows, inputs=[model_image, result_preview], label=None, examples_per_page=4, ) # ──────── Model Comparison Grid ──────── if os.path.exists("examples/Grid.jpg"): gr.HTML("""

🔬 Model Comparison Analysis

See how Snapwear Image to Flatlay compares against leading Models

""") # Display the comparison grid image with gr.Row(): with gr.Column(): comparison_image = gr.Image( value="examples/Grid.jpg", label="Image to Flatlay Model Comparison", show_label=True, interactive=False, height=600, show_download_button=True, show_share_button=False ) gr.HTML("""

🎯 Perfect For

🛍️ Online Retailers

Generate consistent flatlay product shots for catalogs and listings

👗 Fashion Designers

Showcase apparel collections in clean, styled layouts

🛒 E-Commerce Sellers

Create professional flatlays to boost click-through and sales

📱 Social Media Marketers

Design eye-catching flatlay visuals for Instagram and Pinterest

""") # ──────── Footer ──────── gr.HTML("""

🚀 Powered by Snapwear AI

Experience the future of virtual Photoshoot.

© 2024 Snapwear AI. Professional AI tools for fashion and design.

""") # ───────── Launch App ───────── if __name__ == "__main__": demo.queue( max_size=20, default_concurrency_limit=1, # Single concurrent request to match backend api_open=False ).launch( server_name="0.0.0.0", server_port=7860, share=False, show_api=False )