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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +24 -323
app.py CHANGED
@@ -1,100 +1,22 @@
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
 
82
  """Single‑shot call (no more than `max_retries` times)."""
83
 
84
  if input_image is None:
85
- return None, None, "❌ Please upload an image."
86
 
87
  if not backend_status["connected"]:
88
  ok, msg = check_backend_connection()
89
  if not ok:
90
- return None, None, msg
91
 
92
  client: Client = backend_status["client"]
93
  img_b64 = image_to_base64(input_image)
94
 
95
  for attempt in range(max_retries):
96
  try:
97
- logger.info(f"Backend callΒ #{attempt+1}")
98
  start = time.time()
99
  result = client.predict(
100
  img_b64,
@@ -116,254 +38,33 @@ def call_backend_with_retry(input_image: Image.Image, category: str, gender: str
116
 
117
  if not status.startswith("βœ…"):
118
  status = "βœ… " + status
119
- status += f" (⏱ {dt:.1f}s)"
120
- return overlay_img, bg_img, status
121
 
122
  except Exception as e:
123
- logger.error(f"AttemptΒ {attempt+1} failed: {e}")
124
  if attempt == max_retries - 1:
125
- return None, None, f"❌ {e}"
126
  time.sleep(1)
127
 
128
- return None, None, "❌ Unknown error",gr.update(interactive=True)
129
 
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)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # ───────── UI ↔ Backend bridge ─────────
2
  def call_backend_with_retry(input_image: Image.Image, category: str, gender: str, *, max_retries: int = MAX_RETRIES):
3
 
4
  """Single‑shot call (no more than `max_retries` times)."""
5
 
6
  if input_image is None:
7
+ return None, None, "❌ Please upload an image.", gr.update(interactive=True)
8
 
9
  if not backend_status["connected"]:
10
  ok, msg = check_backend_connection()
11
  if not ok:
12
+ return None, None, msg, gr.update(interactive=True)
13
 
14
  client: Client = backend_status["client"]
15
  img_b64 = image_to_base64(input_image)
16
 
17
  for attempt in range(max_retries):
18
  try:
19
+ logger.info(f"Backend call #{attempt+1}")
20
  start = time.time()
21
  result = client.predict(
22
  img_b64,
 
38
 
39
  if not status.startswith("βœ…"):
40
  status = "βœ… " + status
41
+ status += f" (⏱ {dt:.1f}s)"
42
+ return overlay_img, bg_img, status, gr.update(interactive=True)
43
 
44
  except Exception as e:
45
+ logger.error(f"Attempt {attempt+1} failed: {e}")
46
  if attempt == max_retries - 1:
47
+ return None, None, f"❌ {e}", gr.update(interactive=True)
48
  time.sleep(1)
49
 
50
+ return None, None, "❌ Unknown error", gr.update(interactive=True)
51
 
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
+ )