ArmanRV commited on
Commit
05591af
·
verified ·
1 Parent(s): c73664f

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +252 -0
app.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+ import time
4
+ import tempfile
5
+ import gradio as gr
6
+ from PIL import Image, ImageFilter, ImageEnhance
7
+ from gradio_client import Client, handle_file
8
+ from huggingface_hub import login
9
+
10
+ SPACE = "yisol/IDM-VTON"
11
+ API_NAME = "/tryon"
12
+
13
+ HF_TOKEN = "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"
14
+
15
+ print("HF_TOKEN set:", bool(HF_TOKEN), "len:", len(HF_TOKEN) if HF_TOKEN else 0)
16
+
17
+ if HF_TOKEN and HF_TOKEN != "PASTE_YOUR_HF_TOKEN_HERE":
18
+ try:
19
+ login(token=HF_TOKEN, add_to_git_credential=False)
20
+ print("HF login: OK")
21
+ except Exception as e:
22
+ print("HF login: FAILED:", str(e)[:200])
23
+ else:
24
+ print("HF login: skipped (token placeholder or empty)")
25
+
26
+ _client = None
27
+
28
+
29
+ def get_client():
30
+ global _client
31
+ if _client is None:
32
+ _client = Client(SPACE)
33
+ return _client
34
+
35
+
36
+ def pil_save_temp(pil_img: Image.Image, suffix: str) -> str:
37
+ f = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
38
+ path = f.name
39
+ f.close()
40
+ pil_img.save(path)
41
+ return path
42
+
43
+
44
+ def clamp_int(x, lo, hi):
45
+ try:
46
+ x = int(x)
47
+ except Exception:
48
+ x = lo
49
+ return max(lo, min(hi, x))
50
+
51
+
52
+ def postprocess_text_boost(img: Image.Image, boost: int) -> Image.Image:
53
+ boost = clamp_int(boost, 0, 100)
54
+ if boost <= 0:
55
+ return img
56
+
57
+ t = boost / 100.0
58
+ out = img.convert("RGB")
59
+
60
+ out = out.filter(
61
+ ImageFilter.UnsharpMask(
62
+ radius=1.2 + 1.0 * t,
63
+ percent=int(120 + 80 * t),
64
+ threshold=2
65
+ )
66
+ )
67
+ out = ImageEnhance.Contrast(out).enhance(1.0 + 0.10 * t)
68
+ out = ImageEnhance.Sharpness(out).enhance(1.0 + 0.30 * t)
69
+
70
+ w, h = out.size
71
+ box = (int(w * 0.25), int(h * 0.22), int(w * 0.75), int(h * 0.72))
72
+
73
+ region = out.crop(box)
74
+ region = region.filter(
75
+ ImageFilter.UnsharpMask(
76
+ radius=1.8 + 1.6 * t,
77
+ percent=int(160 + 120 * t),
78
+ threshold=2
79
+ )
80
+ )
81
+ region = ImageEnhance.Contrast(region).enhance(1.0 + 0.15 * t)
82
+ region = ImageEnhance.Sharpness(region).enhance(1.0 + 0.45 * t)
83
+
84
+ blended = Image.blend(out.crop(box), region, alpha=min(0.55 + 0.35 * t, 0.9))
85
+ out.paste(blended, box)
86
+ return out
87
+
88
+
89
+ def apply_mode_defaults(mode: str, denoise_steps: int, seed: int, crop_center: bool, garment_desc: str):
90
+ denoise_steps = clamp_int(denoise_steps, 10, 40)
91
+ seed = clamp_int(seed, 0, 999999)
92
+
93
+ if mode == "Balanced":
94
+ denoise_steps = max(denoise_steps, 28)
95
+ elif mode == "Quality":
96
+ denoise_steps = max(denoise_steps, 38)
97
+ elif mode == "Text/Logo":
98
+ denoise_steps = 40
99
+ crop_center = False
100
+ if seed == 0:
101
+ seed = 42
102
+ if garment_desc and "text" not in garment_desc.lower() and "logo" not in garment_desc.lower():
103
+ garment_desc = garment_desc.strip() + ", with clear text/logo print"
104
+
105
+ return denoise_steps, seed, crop_center, garment_desc
106
+
107
+
108
+ def tryon_remote(
109
+ person_pil,
110
+ garment_pil,
111
+ garment_desc,
112
+ auto_mask,
113
+ crop_center,
114
+ denoise_steps,
115
+ seed,
116
+ mode,
117
+ text_boost,
118
+ ):
119
+ if person_pil is None:
120
+ return None, "❌ Загрузите фото человека"
121
+ if garment_pil is None:
122
+ return None, "❌ Загрузите одежду"
123
+
124
+ denoise_steps, seed, crop_center, garment_desc = apply_mode_defaults(
125
+ mode=mode,
126
+ denoise_steps=denoise_steps,
127
+ seed=seed,
128
+ crop_center=crop_center,
129
+ garment_desc=garment_desc or "a t-shirt"
130
+ )
131
+
132
+ p_path = pil_save_temp(person_pil.convert("RGB"), ".png")
133
+ g_path = pil_save_temp(garment_pil.convert("RGB"), ".png")
134
+
135
+ try:
136
+ client = get_client()
137
+
138
+ last_err = None
139
+ for attempt in range(1, 4):
140
+ try:
141
+ result = client.predict(
142
+ dict={
143
+ "background": handle_file(p_path),
144
+ "layers": [],
145
+ "composite": None
146
+ },
147
+ garm_img=handle_file(g_path),
148
+ garment_des=garment_desc,
149
+ is_checked=bool(auto_mask),
150
+ is_checked_crop=bool(crop_center),
151
+ denoise_steps=int(denoise_steps),
152
+ seed=int(seed),
153
+ api_name=API_NAME
154
+ )
155
+
156
+ if isinstance(result, (list, tuple)):
157
+ result = result[0]
158
+
159
+ out = Image.open(result).convert("RGB")
160
+ out = postprocess_text_boost(out, text_boost)
161
+
162
+ return out, f"✅ Готово (mode={mode}, steps={denoise_steps}, seed={seed}, crop={crop_center})"
163
+
164
+ except Exception as e:
165
+ last_err = e
166
+ msg = str(e)
167
+
168
+ if "ZeroGPU" in msg or "Unlogged user" in msg or "quota" in msg.lower():
169
+ return None, (
170
+ "❌ ZeroGPU-квота/нагрузка Hugging Face.\n"
171
+ "Если HF login = OK, то это лимит/очередь Space. Попробуйте позже или смените seed."
172
+ )
173
+
174
+ time.sleep(1.2 * attempt)
175
+
176
+ return None, f"❌ Ошибка Space: {str(last_err)[:250]}"
177
+
178
+ finally:
179
+ for path in (p_path, g_path):
180
+ try:
181
+ os.remove(path)
182
+ except Exception:
183
+ pass
184
+
185
+
186
+ def reset_ui():
187
+ return None, None, None, "Ожидание..."
188
+
189
+
190
+ CUSTOM_CSS = """
191
+ footer {display:none !important;}
192
+ #api-info {display:none !important;}
193
+ div[class*="footer"] {display:none !important;}
194
+ button[aria-label="Settings"] {display:none !important;}
195
+ """
196
+
197
+ with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
198
+ gr.Markdown(
199
+ "# Virtual Try-On Rendez-vous\n\n"
200
+ "**Для текста/логотипов:** режим *Text/Logo*, steps=40, попробуйте разные *seed*."
201
+ )
202
+
203
+ with gr.Row():
204
+ with gr.Column():
205
+ person = gr.Image(label="Фото человека", type="pil", height=420)
206
+ garment = gr.Image(label="Одежда", type="pil", height=320)
207
+
208
+ garment_desc = gr.Textbox(
209
+ label="Описание одежды (чем точнее — тем лучше)",
210
+ value="a t-shirt with clear text/logo print"
211
+ )
212
+
213
+ mode = gr.Radio(
214
+ ["Balanced", "Quality", "Text/Logo"],
215
+ value="Text/Logo",
216
+ label="Режим качества"
217
+ )
218
+
219
+ with gr.Accordion("Настройки", open=False):
220
+ auto_mask = gr.Checkbox(label="Auto-mask", value=True)
221
+ crop_center = gr.Checkbox(label="Crop по центру", value=True)
222
+ denoise_steps = gr.Slider(10, 40, value=28, step=1, label="Denoise steps")
223
+ seed = gr.Slider(0, 999999, value=42, step=1, label="Seed")
224
+ text_boost = gr.Slider(0, 100, value=55, step=1, label="Text boost (резкость/контраст принта)")
225
+
226
+ with gr.Row():
227
+ run = gr.Button("Примерить", variant="primary")
228
+ reset = gr.Button("Сбросить", variant="secondary")
229
+
230
+ status = gr.Textbox(value="Ожидание...", interactive=False)
231
+
232
+ with gr.Column():
233
+ out = gr.Image(label="Результат", type="pil", height=760)
234
+
235
+ run.click(
236
+ fn=tryon_remote,
237
+ inputs=[person, garment, garment_desc, auto_mask, crop_center, denoise_steps, seed, mode, text_boost],
238
+ outputs=[out, status]
239
+ )
240
+
241
+ reset.click(
242
+ fn=reset_ui,
243
+ inputs=[],
244
+ outputs=[person, garment, out, status]
245
+ )
246
+
247
+ if __name__ == "__main__":
248
+ demo.launch(
249
+ server_name="0.0.0.0",
250
+ server_port=7863,
251
+ share=False
252
+ )