ArmanRV commited on
Commit
90f2241
·
verified ·
1 Parent(s): fef7d41

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -37
app.py CHANGED
@@ -2,7 +2,7 @@
2
  import os
3
  import time
4
  import tempfile
5
- from typing import Optional, Tuple
6
 
7
  import gradio as gr
8
  import spaces # ZeroGPU runtime requires at least one @spaces.GPU-decorated function
@@ -17,7 +17,7 @@ SPACE = "yisol/IDM-VTON"
17
  API_NAME = "/tryon"
18
 
19
  # ----------------------------
20
- # Auth for company demo (no HF accounts needed)
21
  # Set these in HF Space Secrets:
22
  # DEMO_USER=companydemo
23
  # DEMO_PASS=your-strong-password
@@ -26,6 +26,23 @@ DEMO_USER = os.getenv("DEMO_USER", "").strip()
26
  DEMO_PASS = os.getenv("DEMO_PASS", "").strip()
27
  APP_AUTH = (DEMO_USER, DEMO_PASS) if (DEMO_USER and DEMO_PASS) else None
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  # ----------------------------
30
  # HF token (optional)
31
  # ----------------------------
@@ -47,12 +64,10 @@ else:
47
  # ----------------------------
48
  _client: Optional[Client] = None
49
 
50
-
51
  def reset_client():
52
  global _client
53
  _client = None
54
 
55
-
56
  def get_client() -> Client:
57
  """
58
  gradio_client differs by version. Newer versions support hf_token=...
@@ -69,7 +84,6 @@ def get_client() -> Client:
69
  _client = Client(SPACE)
70
  return _client
71
 
72
-
73
  # ----------------------------
74
  # Helpers
75
  # ----------------------------
@@ -80,7 +94,6 @@ def clamp_int(x, lo, hi):
80
  x = lo
81
  return max(lo, min(hi, x))
82
 
83
-
84
  def save_pil_temp(pil_img: Image.Image, suffix: str = ".png") -> str:
85
  f = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
86
  path = f.name
@@ -88,6 +101,16 @@ def save_pil_temp(pil_img: Image.Image, suffix: str = ".png") -> str:
88
  pil_img.save(path, format="PNG") # no resize/compress
89
  return path
90
 
 
 
 
 
 
 
 
 
 
 
91
 
92
  # ----------------------------
93
  # Simple global rate limit (anti spam)
@@ -96,7 +119,6 @@ def save_pil_temp(pil_img: Image.Image, suffix: str = ".png") -> str:
96
  # ----------------------------
97
  _last_call_ts = 0.0
98
 
99
-
100
  def allow_call(min_interval_sec: float = 3.0) -> Tuple[bool, str]:
101
  global _last_call_ts
102
  now = time.time()
@@ -106,22 +128,25 @@ def allow_call(min_interval_sec: float = 3.0) -> Tuple[bool, str]:
106
  _last_call_ts = now
107
  return True, ""
108
 
109
-
110
  # ----------------------------
111
  # Core inference (remote call)
112
  # ZeroGPU: keep duration LOW to avoid burning quota
113
  # ----------------------------
114
  @spaces.GPU(duration=20)
115
- def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center, denoise_steps, seed):
116
- # anti spam
117
  ok, msg = allow_call(3.0)
118
  if not ok:
119
  return None, msg
120
 
121
  if person_pil is None:
122
  return None, "❌ Загрузите фото человека"
 
 
 
 
 
123
  if garment_pil is None:
124
- return None, "❌ Загрузите одежду"
125
 
126
  denoise_steps = clamp_int(denoise_steps, 10, 40)
127
  seed = clamp_int(seed, 0, 999999)
@@ -137,9 +162,6 @@ def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center,
137
  last_err = None
138
 
139
  for attempt in range(1, 7):
140
- # Status per attempt (helps user confidence)
141
- # We'll update status by returning it only on success/final fail,
142
- # but we can still encode attempt info in the final error message.
143
  try:
144
  client = get_client()
145
 
@@ -158,12 +180,11 @@ def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center,
158
  result = result[0]
159
 
160
  out = Image.open(result).convert("RGB")
161
- return out, f"✅ Готово (steps={denoise_steps}, seed={seed}, crop={crop_center})"
162
 
163
  except Exception as e:
164
  last_err = e
165
- msg = str(e)
166
- msg_l = msg.lower()
167
 
168
  is_timeout = (
169
  "write operation timed out" in msg_l
@@ -182,26 +203,13 @@ def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center,
182
 
183
  is_expired = "expired zerogpu proxy token" in msg_l or "zerogpu proxy token" in msg_l
184
 
185
- # Make retry reason human-friendly
186
- if is_expired:
187
- reason = "истёк токен ZeroGPU"
188
- elif is_busy:
189
- reason = "очередь/перегрузка"
190
- elif is_timeout:
191
- reason = "таймаут сети"
192
- else:
193
- reason = "ошибка"
194
-
195
- # Retry on known transient errors
196
  if is_timeout or is_busy or is_expired:
197
  reset_client()
198
  time.sleep(4.0 * attempt)
199
  continue
200
 
201
- # Unknown error: short backoff and continue retrying a few times anyway
202
  time.sleep(1.2 * attempt)
203
 
204
- # Final fail
205
  tail = str(last_err)[:240] if last_err else "unknown error"
206
  return None, f"❌ Ошибка Space после 6 попыток: {tail}"
207
 
@@ -212,11 +220,28 @@ def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center,
212
  except Exception:
213
  pass
214
 
 
 
 
 
 
 
 
 
 
215
 
216
- def reset_ui():
217
- return None, None, "", "Ожидание...", None
218
 
 
 
 
 
 
219
 
 
 
 
220
  CUSTOM_CSS = """
221
  footer {display:none !important;}
222
  #api-info {display:none !important;}
@@ -224,15 +249,28 @@ div[class*="footer"] {display:none !important;}
224
  button[aria-label="Settings"] {display:none !important;}
225
  """
226
 
 
 
 
 
227
  with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
228
  gr.Markdown("# Virtual Try-On Rendez-vous")
229
 
230
  with gr.Row():
231
  with gr.Column():
232
  person = gr.Image(label="Фото человека", type="pil", height=420)
233
- garment = gr.Image(label="Одежда", type="pil", height=320)
234
 
235
- garment_desc = gr.Textbox(label="Описание одежды", value="")
 
 
 
 
 
 
 
 
 
 
236
 
237
  with gr.Accordion("Настройки", open=False):
238
  auto_mask = gr.Checkbox(label="Auto-mask (Space)", value=True)
@@ -249,17 +287,32 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
249
  with gr.Column():
250
  out = gr.Image(label="Результат", type="pil", height=760)
251
 
252
- # Better user feedback during processing (shows spinner and disables button)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  run.click(
254
  fn=tryon_remote,
255
- inputs=[person, garment, garment_desc, auto_mask, crop_center, denoise_steps, seed],
256
  outputs=[out, status],
257
  )
258
 
 
259
  reset.click(
260
  fn=reset_ui,
261
  inputs=[],
262
- outputs=[person, garment, garment_desc, status, out],
263
  )
264
 
265
  if __name__ == "__main__":
 
2
  import os
3
  import time
4
  import tempfile
5
+ from typing import Optional, Tuple, List
6
 
7
  import gradio as gr
8
  import spaces # ZeroGPU runtime requires at least one @spaces.GPU-decorated function
 
17
  API_NAME = "/tryon"
18
 
19
  # ----------------------------
20
+ # Demo auth (no HF accounts needed)
21
  # Set these in HF Space Secrets:
22
  # DEMO_USER=companydemo
23
  # DEMO_PASS=your-strong-password
 
26
  DEMO_PASS = os.getenv("DEMO_PASS", "").strip()
27
  APP_AUTH = (DEMO_USER, DEMO_PASS) if (DEMO_USER and DEMO_PASS) else None
28
 
29
+ # ----------------------------
30
+ # Garment catalog folder in repo
31
+ # ----------------------------
32
+ GARMENT_DIR = "garments"
33
+ ALLOWED_EXTS = (".png", ".jpg", ".jpeg", ".webp")
34
+
35
+ def list_garments() -> List[str]:
36
+ try:
37
+ files = []
38
+ for f in os.listdir(GARMENT_DIR):
39
+ if f.lower().endswith(ALLOWED_EXTS) and not f.startswith("."):
40
+ files.append(f)
41
+ files.sort()
42
+ return files
43
+ except Exception:
44
+ return []
45
+
46
  # ----------------------------
47
  # HF token (optional)
48
  # ----------------------------
 
64
  # ----------------------------
65
  _client: Optional[Client] = None
66
 
 
67
  def reset_client():
68
  global _client
69
  _client = None
70
 
 
71
  def get_client() -> Client:
72
  """
73
  gradio_client differs by version. Newer versions support hf_token=...
 
84
  _client = Client(SPACE)
85
  return _client
86
 
 
87
  # ----------------------------
88
  # Helpers
89
  # ----------------------------
 
94
  x = lo
95
  return max(lo, min(hi, x))
96
 
 
97
  def save_pil_temp(pil_img: Image.Image, suffix: str = ".png") -> str:
98
  f = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
99
  path = f.name
 
101
  pil_img.save(path, format="PNG") # no resize/compress
102
  return path
103
 
104
+ def load_garment_pil(filename: str) -> Optional[Image.Image]:
105
+ if not filename:
106
+ return None
107
+ path = os.path.join(GARMENT_DIR, filename)
108
+ if not os.path.exists(path):
109
+ return None
110
+ try:
111
+ return Image.open(path).convert("RGB")
112
+ except Exception:
113
+ return None
114
 
115
  # ----------------------------
116
  # Simple global rate limit (anti spam)
 
119
  # ----------------------------
120
  _last_call_ts = 0.0
121
 
 
122
  def allow_call(min_interval_sec: float = 3.0) -> Tuple[bool, str]:
123
  global _last_call_ts
124
  now = time.time()
 
128
  _last_call_ts = now
129
  return True, ""
130
 
 
131
  # ----------------------------
132
  # Core inference (remote call)
133
  # ZeroGPU: keep duration LOW to avoid burning quota
134
  # ----------------------------
135
  @spaces.GPU(duration=20)
136
+ def tryon_remote(person_pil, garment_filename, garment_desc, auto_mask, crop_center, denoise_steps, seed):
 
137
  ok, msg = allow_call(3.0)
138
  if not ok:
139
  return None, msg
140
 
141
  if person_pil is None:
142
  return None, "❌ Загрузите фото человека"
143
+
144
+ if not garment_filename:
145
+ return None, "❌ Выберите одежду из списка"
146
+
147
+ garment_pil = load_garment_pil(garment_filename)
148
  if garment_pil is None:
149
+ return None, "❌ Не удалось загрузить выбранную одежду (проверьте файл в папке garments/)"
150
 
151
  denoise_steps = clamp_int(denoise_steps, 10, 40)
152
  seed = clamp_int(seed, 0, 999999)
 
162
  last_err = None
163
 
164
  for attempt in range(1, 7):
 
 
 
165
  try:
166
  client = get_client()
167
 
 
180
  result = result[0]
181
 
182
  out = Image.open(result).convert("RGB")
183
+ return out, f"✅ Готово (одежда: {garment_filename}, steps={denoise_steps}, seed={seed})"
184
 
185
  except Exception as e:
186
  last_err = e
187
+ msg_l = str(e).lower()
 
188
 
189
  is_timeout = (
190
  "write operation timed out" in msg_l
 
203
 
204
  is_expired = "expired zerogpu proxy token" in msg_l or "zerogpu proxy token" in msg_l
205
 
 
 
 
 
 
 
 
 
 
 
 
206
  if is_timeout or is_busy or is_expired:
207
  reset_client()
208
  time.sleep(4.0 * attempt)
209
  continue
210
 
 
211
  time.sleep(1.2 * attempt)
212
 
 
213
  tail = str(last_err)[:240] if last_err else "unknown error"
214
  return None, f"❌ Ошибка Space после 6 попыток: {tail}"
215
 
 
220
  except Exception:
221
  pass
222
 
223
+ # ----------------------------
224
+ # UI helpers
225
+ # ----------------------------
226
+ def refresh_garments():
227
+ files = list_garments()
228
+ value = files[0] if files else None
229
+ preview = load_garment_pil(value) if value else None
230
+ status = "✅ Каталог обновлён" if files else "⚠️ В папке garments/ пока нет изображений"
231
+ return gr.Dropdown(choices=files, value=value), preview, status
232
 
233
+ def on_select_garment(filename: str):
234
+ return load_garment_pil(filename)
235
 
236
+ def reset_ui():
237
+ files = list_garments()
238
+ value = files[0] if files else None
239
+ preview = load_garment_pil(value) if value else None
240
+ return None, value, preview, "", "Ожидание...", None
241
 
242
+ # ----------------------------
243
+ # UI
244
+ # ----------------------------
245
  CUSTOM_CSS = """
246
  footer {display:none !important;}
247
  #api-info {display:none !important;}
 
249
  button[aria-label="Settings"] {display:none !important;}
250
  """
251
 
252
+ initial_files = list_garments()
253
+ initial_value = initial_files[0] if initial_files else None
254
+ initial_preview = load_garment_pil(initial_value) if initial_value else None
255
+
256
  with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
257
  gr.Markdown("# Virtual Try-On Rendez-vous")
258
 
259
  with gr.Row():
260
  with gr.Column():
261
  person = gr.Image(label="Фото человека", type="pil", height=420)
 
262
 
263
+ with gr.Row():
264
+ garment_selector = gr.Dropdown(
265
+ choices=initial_files,
266
+ value=initial_value,
267
+ label="Выберите одежду из каталога (garments/)"
268
+ )
269
+ refresh_btn = gr.Button("🔄 Обновить каталог", variant="secondary")
270
+
271
+ garment_preview = gr.Image(label="Предпросмотр одежды", type="pil", height=320)
272
+
273
+ garment_desc = gr.Textbox(label="Описание одежды (опционально)", value="")
274
 
275
  with gr.Accordion("Настройки", open=False):
276
  auto_mask = gr.Checkbox(label="Auto-mask (Space)", value=True)
 
287
  with gr.Column():
288
  out = gr.Image(label="Результат", type="pil", height=760)
289
 
290
+ # Preview updates
291
+ garment_selector.change(
292
+ fn=on_select_garment,
293
+ inputs=[garment_selector],
294
+ outputs=[garment_preview]
295
+ )
296
+
297
+ # Refresh catalog (after uploading new files)
298
+ refresh_btn.click(
299
+ fn=refresh_garments,
300
+ inputs=[],
301
+ outputs=[garment_selector, garment_preview, status]
302
+ )
303
+
304
+ # Run try-on
305
  run.click(
306
  fn=tryon_remote,
307
+ inputs=[person, garment_selector, garment_desc, auto_mask, crop_center, denoise_steps, seed],
308
  outputs=[out, status],
309
  )
310
 
311
+ # Reset UI
312
  reset.click(
313
  fn=reset_ui,
314
  inputs=[],
315
+ outputs=[person, garment_selector, garment_preview, garment_desc, status, out],
316
  )
317
 
318
  if __name__ == "__main__":