ArmanRV commited on
Commit
7fe1f3e
·
verified ·
1 Parent(s): e2cc592

Update app.py

Browse files

убрали

Overlay (перенос принта/фактуры)

авто/ручную маску логотипа

режим Text/Logo и любые “подсказки” в garment_desc

Files changed (1) hide show
  1. app.py +25 -272
app.py CHANGED
@@ -4,9 +4,7 @@ import time
4
  import tempfile
5
 
6
  import gradio as gr
7
- import numpy as np
8
- from PIL import Image, ImageFilter, ImageEnhance
9
-
10
  from gradio_client import Client, handle_file
11
  from huggingface_hub import login
12
 
@@ -37,7 +35,7 @@ def reset_client():
37
 
38
  def get_client():
39
  """
40
- Keep it compatible with old gradio_client (no hf_token=...).
41
  We rely on huggingface_hub login() above.
42
  """
43
  global _client
@@ -46,8 +44,6 @@ def get_client():
46
  return _client
47
 
48
 
49
- # ---------------- IO helpers ----------------
50
-
51
  def clamp_int(x, lo, hi):
52
  try:
53
  x = int(x)
@@ -56,25 +52,17 @@ def clamp_int(x, lo, hi):
56
  return max(lo, min(hi, x))
57
 
58
 
59
- def clamp_float(x, lo, hi):
60
- try:
61
- x = float(x)
62
- except Exception:
63
- x = lo
64
- return max(lo, min(hi, x))
65
-
66
-
67
  def pil_save_temp(
68
  pil_img: Image.Image,
69
  suffix: str = ".jpg",
70
- max_side: int = 1400,
71
  quality: int = 92
72
  ) -> str:
73
  """
74
- Reduce upload timeouts:
75
  - convert to RGB
76
  - downscale to max_side
77
- - save JPEG optimized
78
  """
79
  img = pil_img.convert("RGB")
80
  w, h = img.size
@@ -90,190 +78,23 @@ def pil_save_temp(
90
  return path
91
 
92
 
93
- # ---------------- Quality / Mode ----------------
94
-
95
- def apply_mode_defaults(mode: str, denoise_steps: int, seed: int, crop_center: bool, garment_desc: str):
96
- denoise_steps = clamp_int(denoise_steps, 10, 40)
97
- seed = clamp_int(seed, 0, 999999)
98
-
99
- if mode == "Balanced":
100
- denoise_steps = max(denoise_steps, 28)
101
- elif mode == "Quality":
102
- denoise_steps = max(denoise_steps, 38)
103
- elif mode == "Text/Logo":
104
- denoise_steps = 40
105
- crop_center = False
106
- if seed == 0:
107
- seed = 42
108
- if garment_desc and "text" not in garment_desc.lower() and "logo" not in garment_desc.lower():
109
- garment_desc = garment_desc.strip() + ", with clear text/logo print"
110
-
111
- return denoise_steps, seed, crop_center, garment_desc
112
-
113
-
114
- # ---------------- Overlay (preserve print/texture) ----------------
115
-
116
- def _to_np(img: Image.Image) -> np.ndarray:
117
- return np.asarray(img.convert("RGB"), dtype=np.float32) / 255.0
118
-
119
-
120
- def _to_pil(arr: np.ndarray) -> Image.Image:
121
- arr = np.clip(arr * 255.0, 0, 255).astype(np.uint8)
122
- return Image.fromarray(arr, mode="RGB")
123
-
124
-
125
- def make_auto_mask_from_garment(garment_rgb: Image.Image) -> Image.Image:
126
- """
127
- Heuristic mask for print/text: edge density + center focus.
128
- """
129
- g = garment_rgb.convert("L")
130
- edges = g.filter(ImageFilter.FIND_EDGES).filter(ImageFilter.GaussianBlur(radius=1.2))
131
- arr = np.asarray(edges, dtype=np.uint8)
132
-
133
- m = float(arr.mean())
134
- s = float(arr.std())
135
- thr = int(min(255, max(25, m + 0.7 * s)))
136
-
137
- mask = (arr > thr).astype(np.uint8) * 255
138
- mask_img = Image.fromarray(mask, mode="L").filter(ImageFilter.GaussianBlur(radius=2.0))
139
-
140
- w, h = mask_img.size
141
- focus = Image.new("L", (w, h), 0)
142
- fx0, fy0, fx1, fy1 = int(w * 0.18), int(h * 0.12), int(w * 0.82), int(h * 0.88)
143
- focus.paste(255, (fx0, fy0, fx1, fy1))
144
- focus = focus.filter(ImageFilter.GaussianBlur(radius=12.0))
145
-
146
- mask_arr = (np.asarray(mask_img, dtype=np.float32) / 255.0) * (np.asarray(focus, dtype=np.float32) / 255.0)
147
- return Image.fromarray(np.clip(mask_arr * 255.0, 0, 255).astype(np.uint8), mode="L")
148
-
149
-
150
- def apply_overlay_preserve(
151
- out_rgb: Image.Image,
152
- garment_rgb: Image.Image,
153
- strength: float,
154
- detail_strength: float,
155
- box_scale: float,
156
- offset_x: int,
157
- offset_y: int,
158
- use_drawn_mask: bool,
159
- garment_mask_draw: Image.Image | None,
160
- ) -> Image.Image:
161
- """
162
- Blend garment colors + high-frequency detail into torso region.
163
- If a drawn mask is provided, it wins (best for exact logos/text).
164
- """
165
- strength = clamp_float(strength, 0.0, 1.0)
166
- detail_strength = clamp_float(detail_strength, 0.0, 2.0)
167
- box_scale = clamp_float(box_scale, 0.6, 1.4)
168
-
169
- if strength <= 0.0 and detail_strength <= 0.0:
170
- return out_rgb
171
-
172
- out = out_rgb.convert("RGB")
173
- g = garment_rgb.convert("RGB")
174
-
175
- ow, oh = out.size
176
-
177
- # approximate torso box
178
- x0 = int(ow * 0.22)
179
- x1 = int(ow * 0.78)
180
- y0 = int(oh * 0.20)
181
- y1 = int(oh * 0.78)
182
-
183
- bw, bh = (x1 - x0), (y1 - y0)
184
-
185
- cx = (x0 + x1) // 2 + int(offset_x)
186
- cy = (y0 + y1) // 2 + int(offset_y)
187
- bw2 = int(bw * box_scale)
188
- bh2 = int(bh * box_scale)
189
-
190
- x0b = max(0, cx - bw2 // 2)
191
- x1b = min(ow, cx + bw2 // 2)
192
- y0b = max(0, cy - bh2 // 2)
193
- y1b = min(oh, cy + bh2 // 2)
194
-
195
- box_w, box_h = (x1b - x0b), (y1b - y0b)
196
- if box_w <= 10 or box_h <= 10:
197
- return out
198
-
199
- g_fit = g.resize((box_w, box_h), Image.LANCZOS)
200
-
201
- # mask
202
- if use_drawn_mask and garment_mask_draw is not None:
203
- gm = garment_mask_draw
204
- if gm.mode == "RGBA":
205
- mask = gm.split()[-1]
206
- else:
207
- mask = gm.convert("L")
208
- mask = mask.resize((box_w, box_h), Image.LANCZOS).filter(ImageFilter.GaussianBlur(radius=2.0))
209
- else:
210
- mask = make_auto_mask_from_garment(g).resize((box_w, box_h), Image.LANCZOS)
211
-
212
- m = np.asarray(mask, dtype=np.float32) / 255.0
213
- m = np.clip(m, 0.0, 1.0)
214
-
215
- out_arr = _to_np(out)
216
- crop = out_arr[y0b:y1b, x0b:x1b, :]
217
-
218
- g_arr = _to_np(g_fit)
219
-
220
- # 1) color/print transfer
221
- if strength > 0:
222
- alpha = strength * (m[..., None])
223
- crop = crop * (1.0 - alpha) + g_arr * alpha
224
-
225
- # 2) texture/detail transfer
226
- if detail_strength > 0:
227
- g_blur = _to_np(g_fit.filter(ImageFilter.GaussianBlur(radius=3.0)))
228
- detail = g_arr - g_blur
229
- alpha2 = detail_strength * (m[..., None]) * 0.65
230
- crop = np.clip(crop + detail * alpha2, 0.0, 1.0)
231
-
232
- out_arr[y0b:y1b, x0b:x1b, :] = crop
233
- return _to_pil(out_arr)
234
-
235
-
236
- # ---------------- Main inference ----------------
237
-
238
- def tryon_remote(
239
- person_pil,
240
- garment_pil,
241
- garment_desc,
242
- auto_mask,
243
- crop_center,
244
- denoise_steps,
245
- seed,
246
- mode_quality,
247
- overlay_enable,
248
- overlay_strength,
249
- overlay_detail,
250
- overlay_box_scale,
251
- overlay_offset_x,
252
- overlay_offset_y,
253
- use_garment_mask_draw,
254
- garment_mask_draw,
255
- ):
256
  if person_pil is None:
257
  return None, "❌ Загрузите фото человека"
258
  if garment_pil is None:
259
  return None, "❌ Загрузите одежду"
260
 
261
- denoise_steps, seed, crop_center, garment_desc = apply_mode_defaults(
262
- mode=mode_quality,
263
- denoise_steps=denoise_steps,
264
- seed=seed,
265
- crop_center=crop_center,
266
- garment_desc=garment_desc or "a t-shirt"
267
- )
268
 
269
- # Save smaller for upload stability (still good enough for text with overlay)
270
- p_path = pil_save_temp(person_pil, ".jpg", max_side=1400, quality=92)
271
- g_path = pil_save_temp(garment_pil, ".jpg", max_side=1400, quality=95)
272
 
273
  try:
274
  last_err = None
275
 
276
- # more attempts to survive ZeroGPU queue
277
  for attempt in range(1, 7):
278
  try:
279
  client = get_client()
@@ -281,7 +102,7 @@ def tryon_remote(
281
  result = client.predict(
282
  dict={"background": handle_file(p_path), "layers": [], "composite": None},
283
  garm_img=handle_file(g_path),
284
- garment_des=garment_desc,
285
  is_checked=bool(auto_mask),
286
  is_checked_crop=bool(crop_center),
287
  denoise_steps=int(denoise_steps),
@@ -293,52 +114,20 @@ def tryon_remote(
293
  result = result[0]
294
 
295
  out = Image.open(result).convert("RGB")
296
-
297
- if overlay_enable:
298
- out = apply_overlay_preserve(
299
- out_rgb=out,
300
- garment_rgb=garment_pil.convert("RGB"),
301
- strength=float(overlay_strength),
302
- detail_strength=float(overlay_detail),
303
- box_scale=float(overlay_box_scale),
304
- offset_x=int(overlay_offset_x),
305
- offset_y=int(overlay_offset_y),
306
- use_drawn_mask=bool(use_garment_mask_draw),
307
- garment_mask_draw=garment_mask_draw,
308
- )
309
-
310
- return out, (
311
- f"✅ Готово | mode={mode_quality}, steps={denoise_steps}, seed={seed}, crop={crop_center} | "
312
- f"overlay={'on' if overlay_enable else 'off'}"
313
- )
314
 
315
  except Exception as e:
316
  last_err = e
317
  msg = str(e).lower()
318
 
319
- is_timeout = ("write operation timed out" in msg) or ("timed out" in msg)
320
- is_queue = ("queue" in msg) or ("too busy" in msg) or ("overloaded" in msg) or ("capacity" in msg)
321
- is_zerogpu = ("zerogpu" in msg)
322
 
323
- # Handle queue/timeouts: reset client and wait longer
324
- if is_timeout or is_queue or is_zerogpu:
325
  reset_client()
326
-
327
- # escalate waiting
328
- wait_s = 6.0 * attempt # 6s, 12s, 18s, ...
329
- # on later attempts, compress more to reduce upload chance of timeout
330
- if attempt >= 3:
331
- try:
332
- os.remove(p_path); os.remove(g_path)
333
- except Exception:
334
- pass
335
- p_path = pil_save_temp(person_pil, ".jpg", max_side=1100, quality=85)
336
- g_path = pil_save_temp(garment_pil, ".jpg", max_side=1100, quality=88)
337
-
338
- time.sleep(wait_s)
339
  continue
340
 
341
- # generic backoff
342
  time.sleep(1.2 * attempt)
343
 
344
  return None, f"❌ Ошибка Space: {str(last_err)[:250]}"
@@ -355,8 +144,6 @@ def reset_ui():
355
  return None, None, None, "Ожидание..."
356
 
357
 
358
- # ---------------- UI ----------------
359
-
360
  CUSTOM_CSS = """
361
  footer {display:none !important;}
362
  #api-info {display:none !important;}
@@ -365,11 +152,7 @@ button[aria-label="Settings"] {display:none !important;}
365
  """
366
 
367
  with gr.Blocks(title="Virtual Try-On Rendez-vous") as demo:
368
- gr.Markdown(
369
- "# Virtual Try-On Rendez-vous\n\n"
370
- "**Максимум по тексту/фактуре:** выбери *Text/Logo* и включи *Overlay*.\n"
371
- "Если авто-маска промахивается — включи *Use garment mask (draw)* и нарисуй маску по тексту/логотипу."
372
- )
373
 
374
  with gr.Row():
375
  with gr.Column():
@@ -377,40 +160,16 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous") as demo:
377
  garment = gr.Image(label="Одежда", type="pil", height=320)
378
 
379
  garment_desc = gr.Textbox(
380
- label="Описание одежды (чем точнее — тем лучше)",
381
- value="a t-shirt with clear text/logo print"
382
  )
383
 
384
- mode_quality = gr.Radio(
385
- ["Balanced", "Quality", "Text/Logo"],
386
- value="Text/Logo",
387
- label="Режим качества"
388
- )
389
-
390
- with gr.Accordion("Настройки генерации", open=False):
391
  auto_mask = gr.Checkbox(label="Auto-mask (Space)", value=True)
392
  crop_center = gr.Checkbox(label="Crop по центру", value=True)
393
- denoise_steps = gr.Slider(10, 40, value=28, step=1, label="Denoise steps")
394
  seed = gr.Slider(0, 999999, value=42, step=1, label="Seed")
395
 
396
- with gr.Accordion("Сохранение текста/текстуры (Overlay)", open=True):
397
- overlay_enable = gr.Checkbox(label="Enable Overlay", value=True)
398
- overlay_strength = gr.Slider(0, 1, value=0.55, step=0.01, label="Overlay strength (цвет/принт)")
399
- overlay_detail = gr.Slider(0, 2, value=1.10, step=0.05, label="Overlay detail (фактура/буквы)")
400
- overlay_box_scale = gr.Slider(0.6, 1.4, value=1.0, step=0.02, label="Overlay box scale")
401
- overlay_offset_x = gr.Slider(-300, 300, value=0, step=1, label="Overlay offset X (px)")
402
- overlay_offset_y = gr.Slider(-300, 300, value=0, step=1, label="Overlay offset Y (px)")
403
-
404
- use_garment_mask_draw = gr.Checkbox(
405
- label="Use garment mask (draw) — нарисуй маску по тексту/принту",
406
- value=False
407
- )
408
- garment_mask_draw = gr.Image(
409
- label="Garment mask (опционально): нарисуй белым по тексту/принту",
410
- type="pil",
411
- height=260,
412
- )
413
-
414
  with gr.Row():
415
  run = gr.Button("Примерить", variant="primary")
416
  reset = gr.Button("Сбросить", variant="secondary")
@@ -422,13 +181,7 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous") as demo:
422
 
423
  run.click(
424
  fn=tryon_remote,
425
- inputs=[
426
- person, garment, garment_desc,
427
- auto_mask, crop_center, denoise_steps, seed, mode_quality,
428
- overlay_enable, overlay_strength, overlay_detail, overlay_box_scale,
429
- overlay_offset_x, overlay_offset_y,
430
- use_garment_mask_draw, garment_mask_draw
431
- ],
432
  outputs=[out, status]
433
  )
434
 
@@ -441,7 +194,7 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous") as demo:
441
  if __name__ == "__main__":
442
  demo.launch(
443
  server_name="0.0.0.0",
444
- server_port=7860, # Spaces default
445
  share=False,
446
  debug=False,
447
  css=CUSTOM_CSS,
 
4
  import tempfile
5
 
6
  import gradio as gr
7
+ from PIL import Image
 
 
8
  from gradio_client import Client, handle_file
9
  from huggingface_hub import login
10
 
 
35
 
36
  def get_client():
37
  """
38
+ Compatible with older gradio_client (no hf_token=...).
39
  We rely on huggingface_hub login() above.
40
  """
41
  global _client
 
44
  return _client
45
 
46
 
 
 
47
  def clamp_int(x, lo, hi):
48
  try:
49
  x = int(x)
 
52
  return max(lo, min(hi, x))
53
 
54
 
 
 
 
 
 
 
 
 
55
  def pil_save_temp(
56
  pil_img: Image.Image,
57
  suffix: str = ".jpg",
58
+ max_side: int = 1600,
59
  quality: int = 92
60
  ) -> str:
61
  """
62
+ Make uploads more stable (reduce write timeouts):
63
  - convert to RGB
64
  - downscale to max_side
65
+ - save optimized JPEG
66
  """
67
  img = pil_img.convert("RGB")
68
  w, h = img.size
 
78
  return path
79
 
80
 
81
+ def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center, denoise_steps, seed):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  if person_pil is None:
83
  return None, "❌ Загрузите фото человека"
84
  if garment_pil is None:
85
  return None, "❌ Загрузите одежду"
86
 
87
+ denoise_steps = clamp_int(denoise_steps, 10, 40)
88
+ seed = clamp_int(seed, 0, 999999)
 
 
 
 
 
89
 
90
+ # Save temp files (compressed for stability)
91
+ p_path = pil_save_temp(person_pil, ".jpg", max_side=1600, quality=92)
92
+ g_path = pil_save_temp(garment_pil, ".jpg", max_side=1600, quality=92)
93
 
94
  try:
95
  last_err = None
96
 
97
+ # retries to survive ZeroGPU overload/queue/timeouts
98
  for attempt in range(1, 7):
99
  try:
100
  client = get_client()
 
102
  result = client.predict(
103
  dict={"background": handle_file(p_path), "layers": [], "composite": None},
104
  garm_img=handle_file(g_path),
105
+ garment_des=(garment_desc or "a t-shirt"),
106
  is_checked=bool(auto_mask),
107
  is_checked_crop=bool(crop_center),
108
  denoise_steps=int(denoise_steps),
 
114
  result = result[0]
115
 
116
  out = Image.open(result).convert("RGB")
117
+ return out, f"✅ Готово (steps={denoise_steps}, seed={seed}, crop={crop_center})"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  except Exception as e:
120
  last_err = e
121
  msg = str(e).lower()
122
 
123
+ is_timeout = ("write operation timed out" in msg) or ("read operation timed out" in msg) or ("timed out" in msg)
124
+ is_busy = ("too many requests" in msg) or ("queue" in msg) or ("too busy" in msg) or ("overloaded" in msg) or ("capacity" in msg) or ("zerogpu" in msg)
 
125
 
126
+ if is_timeout or is_busy:
 
127
  reset_client()
128
+ time.sleep(6.0 * attempt) # 6s, 12s, 18s, ...
 
 
 
 
 
 
 
 
 
 
 
 
129
  continue
130
 
 
131
  time.sleep(1.2 * attempt)
132
 
133
  return None, f"❌ Ошибка Space: {str(last_err)[:250]}"
 
144
  return None, None, None, "Ожидание..."
145
 
146
 
 
 
147
  CUSTOM_CSS = """
148
  footer {display:none !important;}
149
  #api-info {display:none !important;}
 
152
  """
153
 
154
  with gr.Blocks(title="Virtual Try-On Rendez-vous") as demo:
155
+ gr.Markdown("# Virtual Try-On Rendez-vous")
 
 
 
 
156
 
157
  with gr.Row():
158
  with gr.Column():
 
160
  garment = gr.Image(label="Одежда", type="pil", height=320)
161
 
162
  garment_desc = gr.Textbox(
163
+ label="Описание одежды",
164
+ value="a t-shirt"
165
  )
166
 
167
+ with gr.Accordion("Настройки", open=False):
 
 
 
 
 
 
168
  auto_mask = gr.Checkbox(label="Auto-mask (Space)", value=True)
169
  crop_center = gr.Checkbox(label="Crop по центру", value=True)
170
+ denoise_steps = gr.Slider(10, 40, value=25, step=1, label="Denoise steps")
171
  seed = gr.Slider(0, 999999, value=42, step=1, label="Seed")
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  with gr.Row():
174
  run = gr.Button("Примерить", variant="primary")
175
  reset = gr.Button("Сбросить", variant="secondary")
 
181
 
182
  run.click(
183
  fn=tryon_remote,
184
+ inputs=[person, garment, garment_desc, auto_mask, crop_center, denoise_steps, seed],
 
 
 
 
 
 
185
  outputs=[out, status]
186
  )
187
 
 
194
  if __name__ == "__main__":
195
  demo.launch(
196
  server_name="0.0.0.0",
197
+ server_port=7860,
198
  share=False,
199
  debug=False,
200
  css=CUSTOM_CSS,