|
|
|
|
|
import os |
|
|
import io |
|
|
import base64 |
|
|
import time |
|
|
import gradio as gr |
|
|
from PIL import Image |
|
|
import logging |
|
|
import numpy as np |
|
|
from gradio_client import Client |
|
|
import json |
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
HF_TOKEN = os.getenv("HF_TOKEN") |
|
|
if not HF_TOKEN: |
|
|
raise ValueError("HF_TOKEN environment variable is required") |
|
|
|
|
|
|
|
|
try: |
|
|
client = Client("SnapwearAI/Pattern-Transfer-Backend", hf_token=HF_TOKEN) |
|
|
logger.info("β
Backend client established") |
|
|
backend_connected = True |
|
|
except Exception as e: |
|
|
logger.warning(f"β οΈ Backend connection failed: {e}") |
|
|
client = None |
|
|
backend_connected = False |
|
|
|
|
|
|
|
|
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: 400px; |
|
|
} |
|
|
#col-showcase { |
|
|
margin: 0 auto; |
|
|
max-width: 1200px; |
|
|
} |
|
|
#button { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
color: #ffffff; |
|
|
font-weight: 600; |
|
|
font-size: 16px; |
|
|
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); |
|
|
} |
|
|
.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-processing { |
|
|
background: #cce5ff; |
|
|
border: 1px solid #99ccff; |
|
|
color: #004085; |
|
|
} |
|
|
.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): |
|
|
"""Convert PIL Image to base64 string.""" |
|
|
if image is None: |
|
|
return "" |
|
|
|
|
|
if hasattr(image, 'mode') and image.mode != 'RGB': |
|
|
image = image.convert('RGB') |
|
|
|
|
|
buffer = io.BytesIO() |
|
|
image.save(buffer, format="PNG") |
|
|
buffer.seek(0) |
|
|
return base64.b64encode(buffer.getvalue()).decode('utf-8') |
|
|
|
|
|
def base64_to_image(b64_string): |
|
|
"""Convert base64 string to PIL Image.""" |
|
|
if not b64_string: |
|
|
return None |
|
|
|
|
|
try: |
|
|
image_data = base64.b64decode(b64_string) |
|
|
return Image.open(io.BytesIO(image_data)) |
|
|
except Exception as e: |
|
|
logger.error(f"Failed to decode base64 image: {e}") |
|
|
return None |
|
|
|
|
|
def try_connect_backend(): |
|
|
"""Try to connect to backend and return status""" |
|
|
global client, backend_connected |
|
|
|
|
|
try: |
|
|
test_client = Client("SnapwearAI/Pattern-Transfer-Backend", hf_token=HF_TOKEN) |
|
|
client = test_client |
|
|
backend_connected = True |
|
|
return "π’ Backend is ready! You can now generate pattern transfers.", True |
|
|
except Exception as e: |
|
|
client = None |
|
|
backend_connected = False |
|
|
error_str = str(e).lower() |
|
|
if "timeout" in error_str or "read operation timed out" in error_str: |
|
|
return "π‘ Backend is starting up (this takes 5-6 minutes on first load). Please wait and try again.", False |
|
|
else: |
|
|
return f"π΄ Backend error: {str(e)}", False |
|
|
|
|
|
def call_backend_with_retry(print_image, product_image, max_retries=3): |
|
|
"""Call the backend with proper error handling and queue awareness.""" |
|
|
global client, backend_connected |
|
|
|
|
|
|
|
|
if not print_image: |
|
|
return None, "β Please upload a print/pattern image" |
|
|
|
|
|
if not product_image: |
|
|
return None, "β Please upload a product image" |
|
|
|
|
|
|
|
|
if not client or not backend_connected: |
|
|
|
|
|
status_msg, is_ready = try_connect_backend() |
|
|
if not is_ready: |
|
|
return None, status_msg |
|
|
|
|
|
|
|
|
guidance_scale = 50.0 |
|
|
num_steps = 50 |
|
|
|
|
|
for attempt in range(max_retries): |
|
|
try: |
|
|
logger.info(f"Calling backend (attempt {attempt + 1}/{max_retries})") |
|
|
|
|
|
|
|
|
print_b64 = image_to_base64(print_image) |
|
|
product_b64 = image_to_base64(product_image) |
|
|
|
|
|
logger.info("Images converted to base64") |
|
|
|
|
|
|
|
|
start_time = time.time() |
|
|
|
|
|
|
|
|
try: |
|
|
result = client.predict( |
|
|
print_b64, |
|
|
product_b64, |
|
|
guidance_scale, |
|
|
num_steps, |
|
|
api_name="/predict" |
|
|
) |
|
|
except Exception as prediction_error: |
|
|
|
|
|
error_str = str(prediction_error).lower() |
|
|
if "queue" in error_str or "position" in error_str: |
|
|
|
|
|
return None, f"π Request queued. {str(prediction_error)}" |
|
|
else: |
|
|
raise prediction_error |
|
|
|
|
|
processing_time = time.time() - start_time |
|
|
logger.info(f"Backend call completed in {processing_time:.2f}s") |
|
|
|
|
|
|
|
|
if result and len(result) >= 2: |
|
|
result_b64, status = result[0], result[1] |
|
|
|
|
|
if result_b64: |
|
|
result_image = base64_to_image(result_b64) |
|
|
if result_image: |
|
|
logger.info("Successfully received and decoded result image") |
|
|
|
|
|
if "Generated in" not in status: |
|
|
status = f"{status} (Total time: {processing_time:.1f}s)" |
|
|
return result_image, status |
|
|
else: |
|
|
return None, "β Failed to decode result image" |
|
|
else: |
|
|
return None, status or "β No image returned" |
|
|
else: |
|
|
return None, "β Invalid response from backend" |
|
|
|
|
|
except Exception as e: |
|
|
error_str = str(e).lower() |
|
|
if "timeout" in error_str: |
|
|
|
|
|
backend_connected = False |
|
|
client = None |
|
|
return None, "π‘ Backend timed out. It may be starting up or busy with other requests. Please try again in a few moments." |
|
|
elif "queue" in error_str or "busy" in error_str: |
|
|
return None, f"π Server is busy processing other requests. Please wait and try again. {str(e)}" |
|
|
|
|
|
logger.error(f"Backend call attempt {attempt + 1} failed: {e}") |
|
|
if attempt == max_retries - 1: |
|
|
return None, f"β Backend error: {str(e)}" |
|
|
time.sleep(3) |
|
|
|
|
|
return None, "β All attempts failed" |
|
|
|
|
|
|
|
|
with gr.Blocks(css=css, title="AI Style Transfer Studio - Pattern & Color Transfer") as demo: |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div class="hero-section"> |
|
|
<h1 style="font-size:48px;margin:0;background:linear-gradient(45deg,#fff,#f0f8ff);-webkit-background-clip:text;-webkit-text-fill-color:transparent;"> |
|
|
π¨ Snapwear Pattern Mockup Studio |
|
|
</h1> |
|
|
<h2 style="font-size:24px;margin:10px 0;opacity:0.9;"> |
|
|
Transform Any Pattern onto Any Product Instantly |
|
|
</h2> |
|
|
<p style="font-size:18px;margin:15px 0;opacity:0.8;"> |
|
|
β’ Instant results β’ Perfect for designers, brands & creators |
|
|
</p> |
|
|
<div class="social-links"> |
|
|
<a href="https://snapwear.io" target="_blank">π Official Website</a> |
|
|
<a href="https://www.instagram.com/snapwearai/" target="_blank">πΈ Instagram</a> |
|
|
<a href="https://huggingface.co/spaces/SnapwearAI/Snapwear-Virtual-Try-On" target="_blank">π Try Virtual Try-On</a> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
|
|
|
if backend_connected: |
|
|
initial_status = '<div class="status-banner status-ready">π’ Model is ready! You can generate pattern transfers.</div>' |
|
|
else: |
|
|
initial_status = '<div class="status-banner status-starting">π‘ Model may be starting up. Click "Check Status" to verify.</div>' |
|
|
|
|
|
status_display = gr.HTML(value=initial_status) |
|
|
|
|
|
|
|
|
check_status_btn = gr.Button("π Check Status", size="sm") |
|
|
|
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div style="background:#e8f4fd;border:1px solid #bee5eb;border-radius:12px;padding:20px;margin:20px 0;"> |
|
|
<h3 style="color:#0c5460;margin:0 0 10px 0;">βΉοΈ How It Works</h3> |
|
|
<div style="color:#0c5460;margin:0;"> |
|
|
<p><strong>First Time:</strong> Backend takes 5-6 minutes to start up after being idle.</p> |
|
|
<p><strong>Multiple Users:</strong> Requests are processed one at a time to ensure quality. You'll be queued if others are using the system.</p> |
|
|
<p><strong>Processing Time:</strong> 30-60 seconds per request once processing begins.</p> |
|
|
<p><strong>Queue Updates:</strong> You'll see your position and estimated wait time.</p> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:20px;margin:30px 0;"> |
|
|
<div class="feature-box"> |
|
|
<h3>π Instant Transfer</h3> |
|
|
<p>Apply any pattern to any product in 30-60 seconds</p> |
|
|
</div> |
|
|
<div class="feature-box"> |
|
|
<h3>π― Perfect Mapping</h3> |
|
|
<p>Preserves product shape, lighting, and texture for realistic results</p> |
|
|
</div> |
|
|
<div class="feature-box"> |
|
|
<h3>π¨ Endless Possibilities</h3> |
|
|
<p>Transfer prints, patterns, textures, and colors across any product type</p> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(elem_id="col-left"): |
|
|
gr.HTML('<div class="step-header">Step 1: Upload Pattern/Print π¨</div>') |
|
|
with gr.Column(elem_id="col-mid"): |
|
|
gr.HTML('<div class="step-header">Step 2: Upload Product π¦</div>') |
|
|
with gr.Column(elem_id="col-right"): |
|
|
gr.HTML('<div class="step-header">Step 3: Generate Magic β¨</div>') |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(elem_id="col-left"): |
|
|
print_image = gr.Image( |
|
|
label="Pattern/Print Image", |
|
|
type="pil", |
|
|
height=400, |
|
|
) |
|
|
gr.HTML('<p style="text-align:center;color:#666;font-size:14px;">Upload any pattern, print, texture, or design you want to transfer</p>') |
|
|
|
|
|
|
|
|
if os.path.exists("Assets/print"): |
|
|
print_examples = [os.path.join("Assets/print", f) for f in os.listdir("Assets/print")][:10] |
|
|
if print_examples: |
|
|
gr.Examples( |
|
|
label="β¨ Example Patterns", |
|
|
inputs=print_image, |
|
|
examples_per_page=10, |
|
|
examples=print_examples, |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Column(elem_id="col-mid"): |
|
|
product_image = gr.Image( |
|
|
label="Product Image", |
|
|
type="pil", |
|
|
height=400, |
|
|
) |
|
|
gr.HTML('<p style="text-align:center;color:#666;font-size:14px;">Upload the product you want to apply the pattern to</p>') |
|
|
|
|
|
|
|
|
if os.path.exists("Assets/product"): |
|
|
product_examples = [os.path.join("Assets/product", f) for f in os.listdir("Assets/product")][:12] |
|
|
if product_examples: |
|
|
gr.Examples( |
|
|
label="π¦ Example Products", |
|
|
inputs=product_image, |
|
|
examples_per_page=12, |
|
|
examples=product_examples, |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Column(elem_id="col-right"): |
|
|
result_img = gr.Image( |
|
|
label="β¨ Transformed Result", |
|
|
show_share_button=True, |
|
|
height=400 |
|
|
) |
|
|
|
|
|
|
|
|
status_text = gr.Text( |
|
|
label="Generation Status", |
|
|
interactive=False, |
|
|
placeholder="Upload images and click generate..." |
|
|
) |
|
|
|
|
|
|
|
|
generate_btn = gr.Button( |
|
|
"π Transform Pattern", |
|
|
elem_id="button", |
|
|
size="lg", |
|
|
variant="primary" |
|
|
) |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div style="font-size:12px;color:#666;text-align:center;margin-top:10px;"> |
|
|
π‘ If busy, you'll be automatically queued and see position updates |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div class="showcase-section"> |
|
|
<h2 style="text-align:center;color:#333;margin-bottom:30px;"> |
|
|
π Showcase: Pattern & Color Transfer Examples |
|
|
</h2> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
gr.HTML('<h3 style="text-align:center;color:#667eea;margin:20px 0;">π¨ Pattern Transfer Showcase</h3>') |
|
|
|
|
|
try: |
|
|
if os.path.exists("Assets/examples"): |
|
|
showcase_examples = [ |
|
|
[os.path.join("Assets/examples", "1_product.jpg"), os.path.join("Assets/examples", "1_print.jpg"), os.path.join("Assets/examples", "1_result.jpg")], |
|
|
[os.path.join("Assets/examples", "2_product.jpg"), os.path.join("Assets/examples", "2_print.jpg"), os.path.join("Assets/examples", "2_result.jpg")], |
|
|
[os.path.join("Assets/examples", "3_product.jpg"), os.path.join("Assets/examples", "3_print.jpg"), os.path.join("Assets/examples", "3_result.jpg")], |
|
|
[os.path.join("Assets/examples", "4_product.jpg"), os.path.join("Assets/examples", "4_print.jpg"), os.path.join("Assets/examples", "4_result.jpg")], |
|
|
] |
|
|
pattern_showcase = gr.Examples( |
|
|
examples=showcase_examples, |
|
|
inputs=[product_image, print_image, result_img], |
|
|
label="Pattern Transfer Examples - Click any example to try it yourself!", |
|
|
examples_per_page=4, |
|
|
) |
|
|
except: |
|
|
gr.HTML("<p style='text-align:center;color:#666;'>Pattern transfer examples will appear here once example files are added to Assets/examples/</p>") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
gr.HTML('<h3 style="text-align:center;color:#764ba2;margin:20px 0;">π Color Transfer Showcase</h3>') |
|
|
|
|
|
try: |
|
|
if os.path.exists("Assets/examples/color"): |
|
|
color_examples = [ |
|
|
[os.path.join("Assets/examples/color", "1_product.jpg"), os.path.join("Assets/examples/color", "1_print.jpg"), os.path.join("Assets/examples/color", "1_result.jpg")], |
|
|
[os.path.join("Assets/examples/color", "2_product.jpg"), os.path.join("Assets/examples/color", "2_print.jpg"), os.path.join("Assets/examples/color", "2_result.jpg")], |
|
|
[os.path.join("Assets/examples/color", "3_product.jpg"), os.path.join("Assets/examples/color", "3_print.jpg"), os.path.join("Assets/examples/color", "3_result.jpg")], |
|
|
] |
|
|
color_showcase = gr.Examples( |
|
|
examples=color_examples, |
|
|
inputs=[product_image, print_image, result_img], |
|
|
label="Color Transfer Examples - Perfect for recoloring products!", |
|
|
examples_per_page=3, |
|
|
) |
|
|
except: |
|
|
gr.HTML("<p style='text-align:center;color:#666;'>Color transfer examples will appear here once example files are added to Assets/examples/color/</p>") |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div style="background:#f8fafc;border:1px solid #e2e8f0;padding:30px;border-radius:16px;margin:30px 0;"> |
|
|
<h2 style="text-align:center;color:#333;margin-bottom:25px;">π― Perfect For</h2> |
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:20px;"> |
|
|
<div style="text-align:center;padding:15px;"> |
|
|
<h3 style="color:#667eea;">π Fashion Designers</h3> |
|
|
<p style="color:#666;">Visualize patterns on garments before production</p> |
|
|
</div> |
|
|
<div style="text-align:center;padding:15px;"> |
|
|
<h3 style="color:#667eea;">ποΈ E-commerce Brands</h3> |
|
|
<p style="color:#666;">Show product variations without inventory</p> |
|
|
</div> |
|
|
<div style="text-align:center;padding:15px;"> |
|
|
<h3 style="color:#667eea;">π¨ Print-on-Demand</h3> |
|
|
<p style="color:#666;">Preview designs on products instantly</p> |
|
|
</div> |
|
|
<div style="text-align:center;padding:15px;"> |
|
|
<h3 style="color:#667eea;">π± Content Creators</h3> |
|
|
<p style="color:#666;">Create unique visuals for social media</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
def update_status_display(): |
|
|
"""Check backend status and update display""" |
|
|
status_msg, is_ready = try_connect_backend() |
|
|
|
|
|
if is_ready: |
|
|
css_class = "status-ready" |
|
|
elif "starting up" in status_msg: |
|
|
css_class = "status-starting" |
|
|
else: |
|
|
css_class = "status-error" |
|
|
|
|
|
status_html = f'<div class="status-banner {css_class}">{status_msg}</div>' |
|
|
return status_html |
|
|
|
|
|
|
|
|
check_status_btn.click( |
|
|
fn=update_status_display, |
|
|
outputs=[status_display] |
|
|
) |
|
|
|
|
|
|
|
|
generate_btn.click( |
|
|
fn=call_backend_with_retry, |
|
|
inputs=[print_image, product_image], |
|
|
outputs=[result_img, status_text], |
|
|
show_progress="full", |
|
|
concurrency_limit=1, |
|
|
) |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div style="text-align:center;padding:40px 20px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:16px;margin:30px 0;"> |
|
|
<h3 style="color:#333;">π Powered by Snapwear AI</h3> |
|
|
<p style="color:#666;"> |
|
|
Transform your creative vision with our models.<br/> |
|
|
</p> |
|
|
<div class="social-links"> |
|
|
<a href="https://snapwear.io" target="_blank">π Website</a> |
|
|
<a href="https://www.instagram.com/snapwearai/" target="_blank">πΈ Instagram</a> |
|
|
<a href="https://huggingface.co/spaces/SnapwearAI/Snapwear-Virtual-Try-On" target="_blank">π Virtual Try-On</a> |
|
|
</div> |
|
|
<p style="font-size:12px;color:#999;margin-top:20px;"> |
|
|
Β© 2024 Snapwear AI. Professional AI tools for fashion and design. |
|
|
</p> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.queue( |
|
|
max_size=20, |
|
|
default_concurrency_limit=1, |
|
|
api_open=False |
|
|
).launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
share=False, |
|
|
show_api=False |
|
|
) |