milliyin commited on
Commit
684664f
Β·
verified Β·
1 Parent(s): ec7c4f5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +315 -16
app.py CHANGED
@@ -1,3 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # ───────── UI ↔ Backend bridge ─────────
2
  def call_backend_with_retry(input_image: Image.Image, category: str, gender: str, *, max_retries: int = MAX_RETRIES):
3
 
@@ -52,19 +130,240 @@ def call_backend_with_retry(input_image: Image.Image, category: str, gender: str
52
  def disable_button():
53
  return gr.update(interactive=False)
54
 
55
- def enable_button():
56
- return gr.update(interactive=True)
57
-
58
- # In your button click event, update the outputs list:
59
- # wire button β†’ backend
60
- run_btn.click(
61
- fn=disable_button,
62
- inputs=None,
63
- outputs=run_btn
64
- ).then(
65
- fn=call_backend_with_retry,
66
- inputs=[input_img, category, gender],
67
- outputs=[out_overlay, out_bg, out_status, run_btn], # Make sure run_btn is included here
68
- concurrency_limit=1,
69
- show_progress=True,
70
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import base64
4
+ import time
5
+ import logging
6
+ from pathlib import Path
7
+
8
+ import gradio as gr
9
+ from gradio_client import Client
10
+ from PIL import Image
11
+
12
+ # ───────── Logging ─────────
13
+ logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # ───────── Constants ─────────
17
+ PREDICT_TIMEOUT = 600
18
+ GPU_WARM_WINDOW = 900
19
+ MAX_RETRIES = 1
20
+
21
+ # ───────── Backend connection ─────────
22
+ HF_TOKEN = os.getenv("HF_TOKEN")
23
+ if not HF_TOKEN:
24
+ raise ValueError("HF_TOKEN environment variable is required")
25
+
26
+ backend_status = {
27
+ "client": None,
28
+ "connected": False,
29
+ "last_check": None,
30
+ "error_message": ""
31
+ }
32
+
33
+ def check_backend_connection():
34
+ """Ping the HF Space and cache the client object."""
35
+ try:
36
+ test_client = Client("milliyin/backend", hf_token=HF_TOKEN)
37
+ backend_status.update({
38
+ "client": test_client,
39
+ "connected": True,
40
+ "error_message": "",
41
+ "last_check": time.time(),
42
+ })
43
+ logger.info("βœ… Backend connection established")
44
+ return True, "🟒 Model is ready"
45
+ except Exception as e:
46
+ backend_status.update({
47
+ "client": None,
48
+ "connected": False,
49
+ "last_check": time.time(),
50
+ "error_message": str(e),
51
+ })
52
+ err = str(e).lower()
53
+ if "timeout" in err or "read operation timed out" in err:
54
+ return False, "🟑 Model is starting up. Please wait 3‑4 min."
55
+ return False, f"πŸ”΄ Backend error: {e}"
56
+
57
+ # initial probe
58
+ check_backend_connection()
59
+
60
+ # ───────── Helpers ─────────
61
+ def image_to_base64(image: Image.Image) -> str:
62
+ if image is None:
63
+ return ""
64
+ if image.mode != "RGB":
65
+ image = image.convert("RGB")
66
+ buf = io.BytesIO()
67
+ image.save(buf, format="PNG")
68
+ return base64.b64encode(buf.getvalue()).decode()
69
+
70
+ def base64_to_image(b64: str) -> Image.Image | None:
71
+ if not b64:
72
+ return None
73
+ try:
74
+ return Image.open(io.BytesIO(base64.b64decode(b64))).convert("RGB")
75
+ except Exception as e:
76
+ logger.error(f"Failed to decode base64 β†’ image: {e}")
77
+ return None
78
+
79
  # ───────── UI ↔ Backend bridge ─────────
80
  def call_backend_with_retry(input_image: Image.Image, category: str, gender: str, *, max_retries: int = MAX_RETRIES):
81
 
 
130
  def disable_button():
131
  return gr.update(interactive=False)
132
 
133
+ # ───────── CSS ─────────
134
+ custom_css = """
135
+ .gradio-container {
136
+ background: linear-gradient(135deg, #3b4371 0%, #2d1b69 25%, #673ab7 50%, #8e24aa 75%, #6a1b9a 100%);
137
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
138
+ min-height: 100vh;
139
+ }
140
+ .contain {
141
+ background: rgba(255, 255, 255, 0.95);
142
+ border-radius: 15px;
143
+ padding: 25px;
144
+ margin: 15px;
145
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
146
+ backdrop-filter: blur(10px);
147
+ }
148
+ .title-container {
149
+ text-align: center;
150
+ margin-bottom: 25px;
151
+ padding: 20px;
152
+ background: linear-gradient(135deg, #673ab7, #8e24aa);
153
+ border-radius: 12px;
154
+ box-shadow: 0 5px 20px rgba(103, 58, 183, 0.4);
155
+ }
156
+ .title-container h1 {
157
+ color: white;
158
+ font-size: 2.2em;
159
+ font-weight: bold;
160
+ margin: 0;
161
+ text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3);
162
+ }
163
+ .info-bar {
164
+ background: linear-gradient(135deg, #7c4dff, #6a1b9a);
165
+ padding: 12px;
166
+ border-radius: 8px;
167
+ margin-bottom: 20px;
168
+ color: white;
169
+ text-align: center;
170
+ font-weight: 500;
171
+ box-shadow: 0 3px 12px rgba(124, 77, 255, 0.3);
172
+ }
173
+ .section-header {
174
+ background: linear-gradient(135deg, #e1bee7, #d1c4e9);
175
+ padding: 12px;
176
+ border-radius: 8px;
177
+ margin-bottom: 15px;
178
+ border-left: 4px solid #673ab7;
179
+ }
180
+ .section-header h3 {
181
+ margin: 0;
182
+ color: #333;
183
+ font-weight: 600;
184
+ }
185
+ .input-group {
186
+ background: rgba(255, 255, 255, 0.85);
187
+ padding: 18px;
188
+ border-radius: 12px;
189
+ margin-bottom: 15px;
190
+ border: 1px solid rgba(103, 58, 183, 0.2);
191
+ box-shadow: 0 3px 12px rgba(103, 58, 183, 0.1);
192
+ }
193
+ .result-section {
194
+ background: rgba(255, 255, 255, 0.9);
195
+ padding: 18px;
196
+ border-radius: 12px;
197
+ border: 1px solid rgba(103, 58, 183, 0.2);
198
+ box-shadow: 0 3px 12px rgba(103, 58, 183, 0.1);
199
+ }
200
+ .tip-box {
201
+ background: linear-gradient(135deg, #f3e5f5, #e8eaf6);
202
+ padding: 10px;
203
+ border-radius: 6px;
204
+ margin: 8px 0;
205
+ border-left: 3px solid #673ab7;
206
+ color: #4a148c;
207
+ font-weight: 500;
208
+ }
209
+ button.primary {
210
+ background: linear-gradient(135deg, #673ab7, #8e24aa) !important;
211
+ border: none !important;
212
+ border-radius: 20px !important;
213
+ padding: 12px 25px !important;
214
+ color: white !important;
215
+ font-weight: bold !important;
216
+ font-size: 15px !important;
217
+ box-shadow: 0 5px 15px rgba(103, 58, 183, 0.4) !important;
218
+ }
219
+ button.primary:hover {
220
+ box-shadow: 0 8px 25px rgba(103, 58, 183, 0.6) !important;
221
+ opacity: 0.9 !important;
222
+ transform: translateY(-2px) !important;
223
+ }
224
+ label {
225
+ color: #4a148c !important;
226
+ font-weight: 600 !important;
227
+ }
228
+ input, textarea, select {
229
+ border: 1px solid rgba(103, 58, 183, 0.3) !important;
230
+ border-radius: 6px !important;
231
+ }
232
+ input:focus, textarea:focus, select:focus {
233
+ border-color: #673ab7 !important;
234
+ box-shadow: 0 0 0 2px rgba(103, 58, 183, 0.2) !important;
235
+ }
236
+ .gr-slider input[type="range"] {
237
+ accent-color: #673ab7 !important;
238
+ }
239
+ input[type="checkbox"] {
240
+ accent-color: #673ab7 !important;
241
+ }
242
+ .preserve-aspect-ratio img {
243
+ object-fit: contain !important;
244
+ width: auto !important;
245
+ max-height: 512px !important;
246
+ }
247
+ .social-links {
248
+ text-align: center;
249
+ margin: 20px 0;
250
+ }
251
+ .social-links a {
252
+ margin: 0 10px;
253
+ padding: 8px 16px;
254
+ background: #667eea;
255
+ color: white;
256
+ text-decoration: none;
257
+ border-radius: 8px;
258
+ transition: all 0.3s ease;
259
+ }
260
+ .social-links a:hover {
261
+ background: #764ba2;
262
+ transform: translateY(-2px);
263
+ }
264
+ .feature-box {
265
+ background: #f8fafc;
266
+ border: 1px solid #e2e8f0;
267
+ padding: 20px;
268
+ border-radius: 12px;
269
+ margin: 10px 0;
270
+ }
271
+ """
272
+
273
+ # ───────── Gradio Blocks ─────────
274
+ with gr.Blocks(css=custom_css, title="Jewellery Photography Preview") as demo:
275
+ # Hero
276
+ gr.HTML("""
277
+ <div style="text-align: center; margin-bottom: 20px;">
278
+ <h1 style="font-size: 2.5em;">🎨 Raresence: AI-Powered Jewellery Photo Preview</h1>
279
+ <p style="color: #666;">Upload a jewellery image, select model, and get professional photos instantly</p>
280
+ </div>
281
+ """)
282
+
283
+ # Status banner
284
+ status_html = gr.HTML()
285
+
286
+ def _update_status():
287
+ ok, msg = check_backend_connection()
288
+ cls = "status-ready" if ok else ("status-starting" if "🟑" in msg else "status-error")
289
+ return f'<div class="status-banner {cls}">{msg}</div>'
290
+
291
+ status_html.value = _update_status()
292
+ gr.Button("πŸ”„ Check Status").click(fn=_update_status, outputs=status_html)
293
+
294
+ with gr.Column():
295
+ with gr.Row():
296
+
297
+ with gr.Column(scale=0.4):
298
+ gr.HTML("""
299
+ <div class="feature-box"">
300
+ <h3>πŸ–ΌοΈ Upload Jewellery Image</h3>
301
+ <p style="color: #666; font-size: 14px;">Select a clear jewellery image for best results</p>
302
+ </div>
303
+ """)
304
+ gr.Markdown("β€Ž")
305
+ gr.Markdown("β€Ž")
306
+ input_img = gr.Image(label="Upload image", type="pil", height=400)
307
+
308
+ # with gr.Column():
309
+
310
+ with gr.Column():
311
+ gr.HTML("""
312
+ <div class="feature-box">
313
+ <h3>🎨 AI Generated Results</h3>
314
+ <p style="color: #666; font-size: 14px;">Preview overlay detection and final professional background</p>
315
+ </div>
316
+ """)
317
+
318
+ with gr.Tabs():
319
+ with gr.TabItem("Final result"):
320
+ info2 = gr.Markdown(value="### Final result")
321
+ out_bg = gr.Image(height=400)
322
+ with gr.TabItem("Detection overlay"):
323
+ info1 = gr.Markdown(value="### Detection overlay")
324
+ out_overlay = gr.Image(height=400)
325
+ run_btn = gr.Button("🎯 Generate", elem_id="button", variant="primary")
326
+
327
+ with gr.Row():
328
+ with gr.Column(scale=0.4):
329
+ gr.Markdown(value="Setting")
330
+ category = gr.Dropdown(label="Jewellery category", choices=["Rings", "Bracelets", "Watches", "Earrings"], value="Bracelets")
331
+ gender = gr.Dropdown(label="Model gender", choices=["male", "female"], value="female")
332
+
333
+
334
+ out_status = gr.Text(label="Status", interactive=False)
335
+ # ──────── Footer ────────
336
+ gr.HTML("""
337
+ <div style="text-align:center;padding:40px 20px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:16px;margin:30px 0;">
338
+ <h3 style="color:#333;">πŸš€ Powered by Snapwear AI</h3>
339
+ <p style="color:#666;">
340
+ Experience the future of virtual fashion and garment visualization.
341
+ </p>
342
+ <div class="social-links">
343
+ <a href="https://snapwear.io" target="_blank">🌐 Website</a>
344
+ <a href="https://www.instagram.com/snapwearai/" target="_blank">πŸ“Έ Instagram</a>
345
+ <a href="https://huggingface.co/spaces/SnapwearAI/Snapwear-Texture-Transfer" target="_blank">🎨 Pattern Transfer</a>
346
+ </div>
347
+ <p style="font-size:12px;color:#999;margin-top:20px;">
348
+ Β© 2024 Snapwear AI. Professional AI tools for fashion and design.
349
+ </p>
350
+ </div>
351
+ """)
352
+
353
+ # wire button β†’ backend
354
+ run_btn.click(
355
+ fn=disable_button,
356
+ inputs=None,
357
+ outputs=run_btn
358
+ ).then(
359
+ fn=call_backend_with_retry,
360
+ inputs=[input_img, category, gender],
361
+ outputs=[out_overlay, out_bg, out_status, run_btn],
362
+ concurrency_limit=1,
363
+ show_progress=True,
364
+ )
365
+
366
+
367
+ # ───────── Launch ─────────
368
+ if __name__ == "__main__":
369
+ demo.queue(max_size=20, default_concurrency_limit=1).launch(share=False)