Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
import os
|
| 3 |
import time
|
|
|
|
|
|
|
| 4 |
from typing import List, Optional, Tuple
|
| 5 |
|
| 6 |
import spaces
|
|
@@ -242,12 +244,6 @@ pipe.unet_encoder = UNet_Encoder
|
|
| 242 |
|
| 243 |
# =========================
|
| 244 |
# Inference (BASELINE params)
|
| 245 |
-
# - num_inference_steps: 30
|
| 246 |
-
# - guidance_scale: 2.0
|
| 247 |
-
# - strength: 1.0
|
| 248 |
-
# - seed: 42
|
| 249 |
-
# - auto_mask: True (upper_body)
|
| 250 |
-
# - crop_center: True
|
| 251 |
# =========================
|
| 252 |
@spaces.GPU
|
| 253 |
def start_tryon(
|
|
@@ -405,42 +401,64 @@ def on_gallery_select(files_list: List[str], evt: gr.SelectData):
|
|
| 405 |
return None, "⚠️ Каталог пуст"
|
| 406 |
idx = int(evt.index) if evt.index is not None else 0
|
| 407 |
idx = max(0, min(idx, len(files_list) - 1))
|
| 408 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
|
| 411 |
def tryon_ui(person_pil, selected_filename):
|
| 412 |
# динамическое ожидание для UX (каждые 2–3 сек)
|
| 413 |
for msg in [
|
| 414 |
-
"
|
| 415 |
-
"
|
| 416 |
-
"
|
| 417 |
-
"
|
|
|
|
|
|
|
| 418 |
]:
|
| 419 |
-
yield None, msg
|
| 420 |
time.sleep(2.3)
|
| 421 |
|
| 422 |
ok, msg = allow_call(2.5)
|
| 423 |
if not ok:
|
| 424 |
-
yield None, msg
|
| 425 |
return
|
| 426 |
|
| 427 |
if person_pil is None:
|
| 428 |
-
yield None, "❌ Загрузите фото человека"
|
| 429 |
return
|
| 430 |
if not selected_filename:
|
| 431 |
-
yield None, "❌ Выберите одежду (клик по превью)"
|
| 432 |
return
|
| 433 |
|
| 434 |
garm = load_garment_pil(selected_filename)
|
| 435 |
if garm is None:
|
| 436 |
-
yield None, "❌ Не удалось загрузить выбранную одежду"
|
| 437 |
return
|
| 438 |
|
| 439 |
try:
|
| 440 |
out_img = start_tryon(human_pil=person_pil, garm_img=garm)
|
| 441 |
-
yield out_img, "✅ Готово"
|
| 442 |
except Exception as e:
|
| 443 |
-
yield None, f"❌ Ошибка: {type(e).__name__}: {str(e)[:220]}"
|
| 444 |
|
| 445 |
|
| 446 |
# boot
|
|
@@ -474,9 +492,8 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
|
|
| 474 |
label="Раздел каталога",
|
| 475 |
)
|
| 476 |
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
selected_label = gr.Markdown("👕 Выберите одежду ниже")
|
| 480 |
|
| 481 |
garment_gallery = gr.Gallery(
|
| 482 |
label="Одежда для примерки",
|
|
@@ -492,6 +509,11 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
|
|
| 492 |
with gr.Column():
|
| 493 |
out = gr.Image(label="Результат", type="pil", height=760)
|
| 494 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 495 |
gender.change(
|
| 496 |
fn=refresh_catalog,
|
| 497 |
inputs=[gender],
|
|
@@ -504,16 +526,24 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
|
|
| 504 |
outputs=[selected_garment_state, selected_label],
|
| 505 |
)
|
| 506 |
|
| 507 |
-
refresh_btn.click(
|
| 508 |
-
fn=refresh_catalog,
|
| 509 |
-
inputs=[gender],
|
| 510 |
-
outputs=[garment_gallery, garment_files_state, selected_garment_state, status, selected_label],
|
| 511 |
-
)
|
| 512 |
-
|
| 513 |
run.click(
|
| 514 |
fn=tryon_ui,
|
| 515 |
inputs=[person, selected_garment_state],
|
| 516 |
-
outputs=[out, status],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
concurrency_limit=1,
|
| 518 |
)
|
| 519 |
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
import os
|
| 3 |
import time
|
| 4 |
+
import json
|
| 5 |
+
from datetime import datetime
|
| 6 |
from typing import List, Optional, Tuple
|
| 7 |
|
| 8 |
import spaces
|
|
|
|
| 244 |
|
| 245 |
# =========================
|
| 246 |
# Inference (BASELINE params)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
# =========================
|
| 248 |
@spaces.GPU
|
| 249 |
def start_tryon(
|
|
|
|
| 401 |
return None, "⚠️ Каталог пуст"
|
| 402 |
idx = int(evt.index) if evt.index is not None else 0
|
| 403 |
idx = max(0, min(idx, len(files_list) - 1))
|
| 404 |
+
# НЕ показываем имя файла
|
| 405 |
+
return files_list[idx], "👕 Одежда выбрана"
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
# =========================
|
| 409 |
+
# Feedback (like/dislike)
|
| 410 |
+
# =========================
|
| 411 |
+
FEEDBACK_DIR = "./feedback"
|
| 412 |
+
FEEDBACK_PATH = os.path.join(FEEDBACK_DIR, "feedback.jsonl")
|
| 413 |
+
|
| 414 |
+
|
| 415 |
+
def save_feedback(is_like: bool, garment_name: str):
|
| 416 |
+
os.makedirs(FEEDBACK_DIR, exist_ok=True)
|
| 417 |
+
record = {
|
| 418 |
+
"timestamp": datetime.utcnow().isoformat(),
|
| 419 |
+
"like": bool(is_like),
|
| 420 |
+
"garment": garment_name or "",
|
| 421 |
+
}
|
| 422 |
+
with open(FEEDBACK_PATH, "a", encoding="utf-8") as f:
|
| 423 |
+
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
| 424 |
+
return "Спасибо за отзыв 🙏"
|
| 425 |
|
| 426 |
|
| 427 |
def tryon_ui(person_pil, selected_filename):
|
| 428 |
# динамическое ожидание для UX (каждые 2–3 сек)
|
| 429 |
for msg in [
|
| 430 |
+
"🧵 Анализируем посадку ткани…",
|
| 431 |
+
"📏 Подбираем пропорции…",
|
| 432 |
+
"🎨 Сохраняем цвет и фактуру…",
|
| 433 |
+
"🪡 Прорисовываем швы и детали…",
|
| 434 |
+
"✨ Наводим финальный лоск…",
|
| 435 |
+
"🪄 Почти готово…",
|
| 436 |
]:
|
| 437 |
+
yield None, msg, gr.update(visible=False)
|
| 438 |
time.sleep(2.3)
|
| 439 |
|
| 440 |
ok, msg = allow_call(2.5)
|
| 441 |
if not ok:
|
| 442 |
+
yield None, msg, gr.update(visible=False)
|
| 443 |
return
|
| 444 |
|
| 445 |
if person_pil is None:
|
| 446 |
+
yield None, "❌ Загрузите фото человека", gr.update(visible=False)
|
| 447 |
return
|
| 448 |
if not selected_filename:
|
| 449 |
+
yield None, "❌ Выберите одежду (клик по превью)", gr.update(visible=False)
|
| 450 |
return
|
| 451 |
|
| 452 |
garm = load_garment_pil(selected_filename)
|
| 453 |
if garm is None:
|
| 454 |
+
yield None, "❌ Не удалось загрузить выбранную одежду", gr.update(visible=False)
|
| 455 |
return
|
| 456 |
|
| 457 |
try:
|
| 458 |
out_img = start_tryon(human_pil=person_pil, garm_img=garm)
|
| 459 |
+
yield out_img, "✅ Готово", gr.update(visible=True)
|
| 460 |
except Exception as e:
|
| 461 |
+
yield None, f"❌ Ошибка: {type(e).__name__}: {str(e)[:220]}", gr.update(visible=False)
|
| 462 |
|
| 463 |
|
| 464 |
# boot
|
|
|
|
| 492 |
label="Раздел каталога",
|
| 493 |
)
|
| 494 |
|
| 495 |
+
# Убрали кнопку обновления каталога и вывод имени файла
|
| 496 |
+
selected_label = gr.Markdown("👕 Выберите одежду ниже")
|
|
|
|
| 497 |
|
| 498 |
garment_gallery = gr.Gallery(
|
| 499 |
label="Одежда для примерки",
|
|
|
|
| 509 |
with gr.Column():
|
| 510 |
out = gr.Image(label="Результат", type="pil", height=760)
|
| 511 |
|
| 512 |
+
# Кнопки фидбэка появляются после успешной примерки
|
| 513 |
+
with gr.Row(visible=False) as feedback_row:
|
| 514 |
+
like_btn = gr.Button("👍 Нравится")
|
| 515 |
+
dislike_btn = gr.Button("👎 Не нравится")
|
| 516 |
+
|
| 517 |
gender.change(
|
| 518 |
fn=refresh_catalog,
|
| 519 |
inputs=[gender],
|
|
|
|
| 526 |
outputs=[selected_garment_state, selected_label],
|
| 527 |
)
|
| 528 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
run.click(
|
| 530 |
fn=tryon_ui,
|
| 531 |
inputs=[person, selected_garment_state],
|
| 532 |
+
outputs=[out, status, feedback_row],
|
| 533 |
+
concurrency_limit=1,
|
| 534 |
+
)
|
| 535 |
+
|
| 536 |
+
like_btn.click(
|
| 537 |
+
fn=lambda garment: save_feedback(True, garment),
|
| 538 |
+
inputs=[selected_garment_state],
|
| 539 |
+
outputs=[status],
|
| 540 |
+
concurrency_limit=1,
|
| 541 |
+
)
|
| 542 |
+
|
| 543 |
+
dislike_btn.click(
|
| 544 |
+
fn=lambda garment: save_feedback(False, garment),
|
| 545 |
+
inputs=[selected_garment_state],
|
| 546 |
+
outputs=[status],
|
| 547 |
concurrency_limit=1,
|
| 548 |
)
|
| 549 |
|