ArmanRV commited on
Commit
9481783
·
verified ·
1 Parent(s): f364720

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -310
app.py CHANGED
@@ -1,8 +1,7 @@
1
  # -*- coding: utf-8 -*-
2
  import os
3
- import re
4
  import time
5
- from typing import List, Optional, Tuple, Dict
6
 
7
  import spaces
8
  import gradio as gr
@@ -94,7 +93,7 @@ DEMO_PASS = os.getenv("DEMO_PASS", "").strip()
94
  APP_AUTH = (DEMO_USER, DEMO_PASS) if (DEMO_USER and DEMO_PASS) else None
95
 
96
  # =========================
97
- # Garments dataset autoload
98
  # =========================
99
  GARMENT_DIR = "garments"
100
  ALLOWED_EXTS = (".png", ".jpg", ".jpeg", ".webp")
@@ -162,9 +161,6 @@ def build_gallery_items(files: List[str]):
162
  return [(garment_path(f), "") for f in files]
163
 
164
 
165
- # =========================
166
- # Small helpers
167
- # =========================
168
  def clamp_int(x, lo, hi):
169
  try:
170
  x = int(x)
@@ -173,18 +169,10 @@ def clamp_int(x, lo, hi):
173
  return max(lo, min(hi, x))
174
 
175
 
176
- def clamp_float(x, lo, hi):
177
- try:
178
- x = float(x)
179
- except Exception:
180
- x = lo
181
- return max(lo, min(hi, x))
182
-
183
-
184
  _last_call_ts = 0.0
185
 
186
 
187
- def allow_call(min_interval_sec: float = 2.0) -> Tuple[bool, str]:
188
  global _last_call_ts
189
  now = time.time()
190
  if now - _last_call_ts < min_interval_sec:
@@ -194,150 +182,8 @@ def allow_call(min_interval_sec: float = 2.0) -> Tuple[bool, str]:
194
  return True, ""
195
 
196
 
197
- def round_to_multiple(x: int, m: int = 8) -> int:
198
- return max(m, int(round(x / m) * m))
199
-
200
-
201
- def pick_target_size_keep_aspect(w: int, h: int, max_side: int) -> Tuple[int, int]:
202
- """
203
- (tw, th) <= max_side по большей стороне, кратно 8
204
- """
205
- if w <= 0 or h <= 0:
206
- return 768, 1024
207
- scale = min(max_side / float(max(w, h)), 1.0)
208
- tw = round_to_multiple(int(w * scale), 8)
209
- th = round_to_multiple(int(h * scale), 8)
210
- tw = max(512, tw)
211
- th = max(512, th)
212
- if max(tw, th) > max_side:
213
- scale2 = max_side / float(max(tw, th))
214
- tw = round_to_multiple(int(tw * scale2), 8)
215
- th = round_to_multiple(int(th * scale2), 8)
216
- return tw, th
217
-
218
-
219
- def letterbox(img: Image.Image, target_w: int, target_h: int, fill=(127, 127, 127)) -> Tuple[Image.Image, Dict[str, int]]:
220
- """
221
- Resize with aspect + padding to (target_w,target_h).
222
- meta: x,y,w,h for core region inside padded canvas
223
- """
224
- src_w, src_h = img.size
225
- if src_w <= 0 or src_h <= 0:
226
- out = img.resize((target_w, target_h))
227
- return out, {"x": 0, "y": 0, "w": target_w, "h": target_h, "src_w": src_w, "src_h": src_h}
228
-
229
- scale = min(target_w / src_w, target_h / src_h)
230
- new_w = max(1, int(src_w * scale))
231
- new_h = max(1, int(src_h * scale))
232
-
233
- img_rs = img.resize((new_w, new_h), Image.LANCZOS)
234
- canvas = Image.new("RGB", (target_w, target_h), fill)
235
- x = (target_w - new_w) // 2
236
- y = (target_h - new_h) // 2
237
- canvas.paste(img_rs, (x, y))
238
- return canvas, {"x": x, "y": y, "w": new_w, "h": new_h, "src_w": src_w, "src_h": src_h}
239
-
240
-
241
- def unletterbox(img_lb: Image.Image, meta: Dict[str, int]) -> Image.Image:
242
- x, y, w, h = meta["x"], meta["y"], meta["w"], meta["h"]
243
- return img_lb.crop((x, y, x + w, y + h))
244
-
245
-
246
- def paste_into_canvas(canvas_mode: str, canvas_size: Tuple[int, int], core_img: Image.Image, meta: Dict[str, int], fill):
247
- """
248
- Вклеивает core_img в канвас (target_w,target_h) по meta x,y.
249
- """
250
- x, y, w, h = meta["x"], meta["y"], meta["w"], meta["h"]
251
- canvas = Image.new(canvas_mode, canvas_size, fill)
252
- if core_img.size != (w, h):
253
- core_img = core_img.resize((w, h), Image.BILINEAR)
254
- canvas.paste(core_img, (x, y))
255
- return canvas
256
-
257
-
258
- def infer_garment_class_from_path(relpath: str) -> str:
259
- """
260
- 'upper_body' | 'lower_body' | 'dresses'
261
- """
262
- s = (relpath or "").lower().replace("\\", "/")
263
- if any(k in s for k in ["dress", "dresses", "sarafan", "plate", "плать", "сараф"]):
264
- return "dresses"
265
- if any(k in s for k in ["pants", "trouser", "jeans", "skirt", "short", "брюк", "джин", "юбк", "шорт"]):
266
- return "lower_body"
267
- return "upper_body"
268
-
269
-
270
- def guess_garment_description(relpath: str) -> str:
271
- s = (relpath or "").lower().replace("\\", "/")
272
- mapping = [
273
- (["shearling", "дублен", "sheepskin"], "a shearling jacket"),
274
- (["coat", "пальт", "overcoat"], "a coat"),
275
- (["jacket", "куртк", "парка", "parka", "bomber"], "a jacket"),
276
- (["blazer", "пидж", "suit"], "a blazer"),
277
- (["hoodie", "худи"], "a hoodie"),
278
- (["sweater", "свит", "jumper"], "a sweater"),
279
- (["shirt", "рубаш"], "a shirt"),
280
- (["tshirt", "tee", "футбол"], "a t-shirt"),
281
- (["dress", "плать", "sarafan"], "a dress"),
282
- (["pants", "jeans", "брюк", "джин"], "pants"),
283
- (["skirt", "юбк"], "a skirt"),
284
- ]
285
- for keys, desc in mapping:
286
- if any(k in s for k in keys):
287
- return desc
288
-
289
- base = os.path.splitext(os.path.basename(s))[0]
290
- base = re.sub(r"[_\-]+", " ", base)
291
- base = re.sub(r"\d+", " ", base)
292
- base = re.sub(r"\s+", " ", base).strip()
293
- if len(base) >= 3:
294
- return "a " + " ".join(base.split()[:4])
295
- return "a piece of clothing"
296
-
297
-
298
- def apply_safety_clamp(mask_full: Image.Image, meta: Dict[str, int], garment_class: str, clamp_strength: float) -> Image.Image:
299
- """
300
- Универсальная страховка от “уехало вниз/вверх”:
301
- - upper_body: оставляем маску выше линии бёдер (чем больше clamp_strength, тем “выше” граница)
302
- - lower_body: оставляем маску ниже линии талии/бёдер (чем больше clamp_strength, тем “ниже” граница)
303
- - dresses: не трогаем
304
-
305
- clamp_strength: 0..1 (0 = почти не влияет, 1 = сильнее)
306
- """
307
- if garment_class == "dresses":
308
- return mask_full
309
-
310
- tw, th = mask_full.size
311
- x, y, w, h = meta["x"], meta["y"], meta["w"], meta["h"]
312
-
313
- # базовые линии (проценты по core высоте) — эмпирика для full-body
314
- # upper_body: граница где-то около 0.60..0.72 от высоты core
315
- # lower_body: граница около 0.34..0.48 от высоты core
316
- clamp_strength = clamp_float(clamp_strength, 0.0, 1.0)
317
-
318
- if garment_class == "upper_body":
319
- lo, hi = 0.60, 0.72
320
- frac = lo + (hi - lo) * (1.0 - clamp_strength) # clamp_strength↑ => граница ближе к lo (выше)
321
- cut_y = y + int(frac * h)
322
- keep = mask_full.crop((0, 0, tw, max(0, min(th, cut_y))))
323
- out = Image.new("L", (tw, th), 0)
324
- out.paste(keep, (0, 0))
325
- return out
326
-
327
- if garment_class == "lower_body":
328
- lo, hi = 0.34, 0.48
329
- frac = lo + (hi - lo) * (clamp_strength) # clamp_strength↑ => граница ближе к hi (ниже)
330
- cut_y = y + int(frac * h)
331
- keep = mask_full.crop((0, max(0, min(th, cut_y)), tw, th))
332
- out = Image.new("L", (tw, th), 0)
333
- out.paste(keep, (0, max(0, min(th, cut_y))))
334
- return out
335
-
336
- return mask_full
337
-
338
-
339
  # =========================
340
- # Model init (local IDM-VTON)
341
  # =========================
342
  base_path = "yisol/IDM-VTON"
343
 
@@ -387,75 +233,58 @@ pipe.unet_encoder = UNet_Encoder
387
 
388
 
389
  # =========================
390
- # Inference
391
  # =========================
392
  @spaces.GPU
393
  def start_tryon(
394
  human_pil: Image.Image,
395
  garm_img: Image.Image,
396
- garm_relpath: str = "",
397
- garment_type_override: str = "auto", # auto | upper_body | lower_body | dresses
398
  auto_mask: bool = True,
399
- safety_clamp: bool = True,
400
- clamp_strength: float = 0.55, # 0..1
401
- denoise_steps: int = 34,
402
- guidance_scale: float = 3.8,
403
- strength: float = 0.90,
404
- seed: int = -1,
405
- max_side: int = 1024,
406
- prompt_override: str = "",
407
- negative_prompt: str = "monochrome, lowres, bad anatomy, worst quality, low quality",
408
  ) -> Image.Image:
409
  device = "cuda" if torch.cuda.is_available() else "cpu"
410
  dtype = torch.float16 if device == "cuda" else torch.float32
411
 
412
  if device == "cuda":
413
  openpose_model.preprocessor.body_estimation.model.to(device)
414
-
415
  pipe.to(device)
416
  pipe.unet_encoder.to(device)
417
 
 
 
418
  human_img_orig = human_pil.convert("RGB")
419
- src_w, src_h = human_img_orig.size
420
 
421
- target_w, target_h = pick_target_size_keep_aspect(src_w, src_h, max_side=max_side)
422
-
423
- # letterbox for model canvas (important: gray padding)
424
- human_lb, lb_meta = letterbox(human_img_orig, target_w, target_h, fill=(127, 127, 127))
425
- garm_img = garm_img.convert("RGB")
426
- garm_lb, _ = letterbox(garm_img, target_w, target_h, fill=(127, 127, 127))
427
-
428
- # Core region (no padding) IMPORTANT for preprocessors
429
- human_core = unletterbox(human_lb, lb_meta)
430
- x, y, w, h = lb_meta["x"], lb_meta["y"], lb_meta["w"], lb_meta["h"]
431
-
432
- # garment class
433
- if garment_type_override and garment_type_override != "auto":
434
- cloth_class = garment_type_override
435
  else:
436
- cloth_class = infer_garment_class_from_path(garm_relpath)
 
 
437
 
438
- # ---- MASK (compute on core -> paste to full) ----
439
  if auto_mask:
440
- human_core_384 = human_core.resize((384, 512), Image.BILINEAR)
441
- keypoints = openpose_model(human_core_384)
442
- model_parse, _ = parsing_model(human_core_384)
443
-
444
- mask_core_384, _ = get_mask_location("hd", cloth_class, model_parse, keypoints)
445
- mask_core = mask_core_384.resize((w, h), Image.BILINEAR)
446
-
447
- mask_full = Image.new("L", (target_w, target_h), 0)
448
- mask_full.paste(mask_core, (x, y))
449
-
450
- if safety_clamp:
451
- mask_full = apply_safety_clamp(mask_full, lb_meta, cloth_class, clamp_strength)
452
- mask = mask_full
453
  else:
454
- mask = Image.new("L", (target_w, target_h), 0)
455
 
456
- # ---- DensePose (compute on core -> paste to full) ----
457
- human_dp = _apply_exif_orientation(human_core.resize((384, 512), Image.BILINEAR))
458
- human_dp = convert_PIL_to_numpy(human_dp, format="BGR")
459
 
460
  args = apply_net.create_argument_parser().parse_args(
461
  (
@@ -469,28 +298,18 @@ def start_tryon(
469
  "cuda" if device == "cuda" else "cpu",
470
  )
471
  )
472
- pose_core = args.func(args, human_dp)
473
- pose_core = pose_core[:, :, ::-1]
474
- pose_core = Image.fromarray(pose_core).resize((w, h), Image.BILINEAR)
475
- pose_img = paste_into_canvas("RGB", (target_w, target_h), pose_core, lb_meta, (127, 127, 127))
476
 
477
- # ---- prompts (not fixed) ----
478
- garment_desc = guess_garment_description(garm_relpath)
479
- if prompt_override and prompt_override.strip():
480
- garment_desc = prompt_override.strip()
 
481
 
482
- prompt_main = f"model is wearing {garment_desc}"
483
- prompt_cloth = f"a photo of {garment_desc}"
484
-
485
- # ---- params ----
486
- denoise_steps = clamp_int(denoise_steps, 15, 60)
487
- guidance_scale = clamp_float(guidance_scale, 0.0, 12.0)
488
- strength = clamp_float(strength, 0.50, 1.00)
489
- max_side = clamp_int(max_side, 640, 2048)
490
-
491
- seed = int(seed) if seed is not None else -1
492
- if seed < 0:
493
- seed = int.from_bytes(os.urandom(2), "big") + int(time.time() * 1000) % 1000000
494
 
495
  with torch.no_grad():
496
  if device == "cuda":
@@ -527,7 +346,8 @@ def start_tryon(
527
  )
528
 
529
  pose_t = tensor_transfrom(pose_img).unsqueeze(0).to(device=device, dtype=dtype)
530
- garm_t = tensor_transfrom(garm_lb).unsqueeze(0).to(device=device, dtype=dtype)
 
531
  generator = torch.Generator(device).manual_seed(seed)
532
 
533
  images = pipe(
@@ -537,28 +357,28 @@ def start_tryon(
537
  negative_pooled_prompt_embeds=negative_pooled_prompt_embeds.to(device=device, dtype=dtype),
538
  num_inference_steps=denoise_steps,
539
  generator=generator,
540
- strength=strength,
541
  pose_img=pose_t,
542
  text_embeds_cloth=prompt_embeds_c.to(device=device, dtype=dtype),
543
  cloth=garm_t,
544
  mask_image=mask,
545
- image=human_lb,
546
- height=target_h,
547
- width=target_w,
548
- ip_adapter_image=garm_lb,
549
- guidance_scale=guidance_scale,
550
  )[0]
551
 
552
- out_img_lb = images[0].convert("RGB")
553
-
554
- # remove padding and return to original resolution
555
- out_core = unletterbox(out_img_lb, lb_meta)
556
- out_final = out_core.resize((src_w, src_h), Image.LANCZOS)
557
- return out_final
558
 
559
 
560
  # =========================
561
- # UI
562
  # =========================
563
  CUSTOM_CSS = """
564
  footer {display:none !important;}
@@ -583,23 +403,10 @@ def on_gallery_select(files_list: List[str], evt: gr.SelectData):
583
  return files_list[idx], f"👕 Выбрано: {files_list[idx]}"
584
 
585
 
586
- def tryon_ui(
587
- person_pil,
588
- selected_filename,
589
- garment_type_override,
590
- auto_mask,
591
- safety_clamp,
592
- clamp_strength,
593
- steps,
594
- cfg,
595
- strength,
596
- seed,
597
- max_side,
598
- prompt_override,
599
- ):
600
  yield None, "⏳ Обработка... (первый запуск может быть дольше)"
601
 
602
- ok, msg = allow_call(2.0)
603
  if not ok:
604
  yield None, msg
605
  return
@@ -620,24 +427,16 @@ def tryon_ui(
620
  out_img = start_tryon(
621
  human_pil=person_pil,
622
  garm_img=garm,
623
- garm_relpath=selected_filename,
624
- garment_type_override=str(garment_type_override),
625
- auto_mask=bool(auto_mask),
626
- safety_clamp=bool(safety_clamp),
627
- clamp_strength=float(clamp_strength),
628
- denoise_steps=int(steps),
629
- guidance_scale=float(cfg),
630
- strength=float(strength),
631
- seed=int(seed),
632
- max_side=int(max_side),
633
- prompt_override=str(prompt_override or "").strip(),
634
  )
635
  yield out_img, "✅ Готово"
636
  except Exception as e:
637
  yield None, f"❌ Ошибка: {type(e).__name__}: {str(e)[:220]}"
638
 
639
 
640
- # preload garments
641
  ensure_garments_downloaded()
642
  _initial_files = list_garments()
643
  _initial_items = build_gallery_items(_initial_files)
@@ -664,36 +463,6 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
664
  allow_preview=True,
665
  )
666
 
667
- with gr.Accordion("⚙️ Настройки", open=False):
668
- garment_type_override = gr.Dropdown(
669
- choices=["auto", "upper_body", "lower_body", "dresses"],
670
- value="auto",
671
- label="Тип одежды (override)",
672
- )
673
- auto_mask = gr.Checkbox(value=True, label="Auto mask (parsing + openpose)")
674
-
675
- safety_clamp = gr.Checkbox(
676
- value=True,
677
- label="Safety clamp (защита от съезда зоны редактирования)",
678
- )
679
- clamp_strength = gr.Slider(
680
- 0.0, 1.0, value=0.55, step=0.01,
681
- label="Clamp strength (0 = мягко, 1 = сильнее)",
682
- )
683
-
684
- steps = gr.Slider(15, 60, value=34, step=1, label="Шаги (num_inference_steps)")
685
- cfg = gr.Slider(0.0, 12.0, value=3.8, step=0.1, label="Guidance scale (CFG)")
686
- strength = gr.Slider(0.50, 1.00, value=0.90, step=0.01, label="Strength")
687
-
688
- seed = gr.Number(value=-1, precision=0, label="Seed (-1 = случайный)")
689
- max_side = gr.Slider(768, 1536, value=1024, step=64, label="Макс. сторона (динамический размер)")
690
-
691
- prompt_override = gr.Textbox(
692
- value="",
693
- label="Описание одежды (опц.)",
694
- placeholder="Напр.: a blazer / a dress / a t-shirt ... (если пусто — авто по имени файла)",
695
- )
696
-
697
  run = gr.Button("Примерить", variant="primary")
698
  status = gr.Textbox(value="Ожидание...", interactive=False)
699
 
@@ -714,20 +483,7 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
714
 
715
  run.click(
716
  fn=tryon_ui,
717
- inputs=[
718
- person,
719
- selected_garment_state,
720
- garment_type_override,
721
- auto_mask,
722
- safety_clamp,
723
- clamp_strength,
724
- steps,
725
- cfg,
726
- strength,
727
- seed,
728
- max_side,
729
- prompt_override,
730
- ],
731
  outputs=[out, status],
732
  concurrency_limit=1,
733
  )
 
1
  # -*- coding: utf-8 -*-
2
  import os
 
3
  import time
4
+ from typing import List, Optional, Tuple
5
 
6
  import spaces
7
  import gradio as gr
 
93
  APP_AUTH = (DEMO_USER, DEMO_PASS) if (DEMO_USER and DEMO_PASS) else None
94
 
95
  # =========================
96
+ # Garments dataset autoload (UX only, doesn't affect quality)
97
  # =========================
98
  GARMENT_DIR = "garments"
99
  ALLOWED_EXTS = (".png", ".jpg", ".jpeg", ".webp")
 
161
  return [(garment_path(f), "") for f in files]
162
 
163
 
 
 
 
164
  def clamp_int(x, lo, hi):
165
  try:
166
  x = int(x)
 
169
  return max(lo, min(hi, x))
170
 
171
 
 
 
 
 
 
 
 
 
172
  _last_call_ts = 0.0
173
 
174
 
175
+ def allow_call(min_interval_sec: float = 2.5) -> Tuple[bool, str]:
176
  global _last_call_ts
177
  now = time.time()
178
  if now - _last_call_ts < min_interval_sec:
 
182
  return True, ""
183
 
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  # =========================
186
+ # Model init (baseline IDM-VTON)
187
  # =========================
188
  base_path = "yisol/IDM-VTON"
189
 
 
233
 
234
 
235
  # =========================
236
+ # Inference (baseline like your original)
237
  # =========================
238
  @spaces.GPU
239
  def start_tryon(
240
  human_pil: Image.Image,
241
  garm_img: Image.Image,
 
 
242
  auto_mask: bool = True,
243
+ crop_center: bool = True,
244
+ denoise_steps: int = 25,
245
+ seed: int = 42,
 
 
 
 
 
 
246
  ) -> Image.Image:
247
  device = "cuda" if torch.cuda.is_available() else "cpu"
248
  dtype = torch.float16 if device == "cuda" else torch.float32
249
 
250
  if device == "cuda":
251
  openpose_model.preprocessor.body_estimation.model.to(device)
 
252
  pipe.to(device)
253
  pipe.unet_encoder.to(device)
254
 
255
+ # fixed resolution baseline
256
+ garm_img = garm_img.convert("RGB").resize((768, 1024))
257
  human_img_orig = human_pil.convert("RGB")
 
258
 
259
+ # crop center baseline
260
+ if crop_center:
261
+ width, height = human_img_orig.size
262
+ target_width = int(min(width, height * (3 / 4)))
263
+ target_height = int(min(height, width * (4 / 3)))
264
+ left = (width - target_width) / 2
265
+ top = (height - target_height) / 2
266
+ right = (width + target_width) / 2
267
+ bottom = (height + target_height) / 2
268
+ cropped_img = human_img_orig.crop((left, top, right, bottom))
269
+ crop_size = cropped_img.size
270
+ human_img = cropped_img.resize((768, 1024))
 
 
271
  else:
272
+ human_img = human_img_orig.resize((768, 1024))
273
+ crop_size = None
274
+ left = top = 0
275
 
276
+ # mask baseline (upper_body)
277
  if auto_mask:
278
+ keypoints = openpose_model(human_img.resize((384, 512)))
279
+ model_parse, _ = parsing_model(human_img.resize((384, 512)))
280
+ mask, _ = get_mask_location("hd", "upper_body", model_parse, keypoints)
281
+ mask = mask.resize((768, 1024))
 
 
 
 
 
 
 
 
 
282
  else:
283
+ mask = Image.new("L", (768, 1024), 0)
284
 
285
+ # DensePose baseline
286
+ human_img_arg = _apply_exif_orientation(human_img.resize((384, 512)))
287
+ human_img_arg = convert_PIL_to_numpy(human_img_arg, format="BGR")
288
 
289
  args = apply_net.create_argument_parser().parse_args(
290
  (
 
298
  "cuda" if device == "cuda" else "cpu",
299
  )
300
  )
301
+ pose_img = args.func(args, human_img_arg)
302
+ pose_img = pose_img[:, :, ::-1]
303
+ pose_img = Image.fromarray(pose_img).resize((768, 1024))
 
304
 
305
+ # fixed prompts baseline
306
+ garment_des = "a garment"
307
+ prompt_main = "model is wearing " + garment_des
308
+ prompt_cloth = "a photo of " + garment_des
309
+ negative_prompt = "monochrome, lowres, bad anatomy, worst quality, low quality"
310
 
311
+ denoise_steps = clamp_int(denoise_steps, 20, 40)
312
+ seed = clamp_int(seed, 0, 999999)
 
 
 
 
 
 
 
 
 
 
313
 
314
  with torch.no_grad():
315
  if device == "cuda":
 
346
  )
347
 
348
  pose_t = tensor_transfrom(pose_img).unsqueeze(0).to(device=device, dtype=dtype)
349
+ garm_t = tensor_transfrom(garm_img).unsqueeze(0).to(device=device, dtype=dtype)
350
+
351
  generator = torch.Generator(device).manual_seed(seed)
352
 
353
  images = pipe(
 
357
  negative_pooled_prompt_embeds=negative_pooled_prompt_embeds.to(device=device, dtype=dtype),
358
  num_inference_steps=denoise_steps,
359
  generator=generator,
360
+ strength=1.0,
361
  pose_img=pose_t,
362
  text_embeds_cloth=prompt_embeds_c.to(device=device, dtype=dtype),
363
  cloth=garm_t,
364
  mask_image=mask,
365
+ image=human_img,
366
+ height=1024,
367
+ width=768,
368
+ ip_adapter_image=garm_img.resize((768, 1024)),
369
+ guidance_scale=2.0,
370
  )[0]
371
 
372
+ out_img = images[0]
373
+ if crop_center and crop_size is not None:
374
+ out_img_rs = out_img.resize(crop_size)
375
+ human_img_orig.paste(out_img_rs, (int(left), int(top)))
376
+ return human_img_orig
377
+ return out_img
378
 
379
 
380
  # =========================
381
+ # UI (simple baseline)
382
  # =========================
383
  CUSTOM_CSS = """
384
  footer {display:none !important;}
 
403
  return files_list[idx], f"👕 Выбрано: {files_list[idx]}"
404
 
405
 
406
+ def tryon_ui(person_pil, selected_filename):
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  yield None, "⏳ Обработка... (первый запуск может быть дольше)"
408
 
409
+ ok, msg = allow_call(2.5)
410
  if not ok:
411
  yield None, msg
412
  return
 
427
  out_img = start_tryon(
428
  human_pil=person_pil,
429
  garm_img=garm,
430
+ auto_mask=True,
431
+ crop_center=True,
432
+ denoise_steps=25,
433
+ seed=42,
 
 
 
 
 
 
 
434
  )
435
  yield out_img, "✅ Готово"
436
  except Exception as e:
437
  yield None, f"❌ Ошибка: {type(e).__name__}: {str(e)[:220]}"
438
 
439
 
 
440
  ensure_garments_downloaded()
441
  _initial_files = list_garments()
442
  _initial_items = build_gallery_items(_initial_files)
 
463
  allow_preview=True,
464
  )
465
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  run = gr.Button("Примерить", variant="primary")
467
  status = gr.Textbox(value="Ожидание...", interactive=False)
468
 
 
483
 
484
  run.click(
485
  fn=tryon_ui,
486
+ inputs=[person, selected_garment_state],
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  outputs=[out, status],
488
  concurrency_limit=1,
489
  )