vikashmakeit commited on
Commit
df4727f
·
verified ·
1 Parent(s): d0a37b9

Add main Gradio app with VLM integration

Browse files
Files changed (1) hide show
  1. app.py +569 -0
app.py ADDED
@@ -0,0 +1,569 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Garment Image → 2D Sewing Pattern Demo
3
+
4
+ Uses a VLM (via HF Inference API) to analyze garment images and extract
5
+ structured parameters, then generates flat 2D sewing pattern pieces.
6
+
7
+ Approach inspired by:
8
+ - ChatGarment (arxiv:2412.17811): VLM → JSON → GarmentCode → 2D patterns
9
+ - NGL-Prompter (arxiv:2602.20700): Training-free VLM → semantic params → patterns
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import re
15
+ import traceback
16
+ from typing import Dict, Optional, Tuple
17
+
18
+ import gradio as gr
19
+ from PIL import Image
20
+
21
+ from pattern_generator import generate_pattern_from_analysis, get_pattern_pieces, render_pattern_pieces
22
+
23
+ # ── VLM Analysis ──
24
+
25
+ GARMENT_ANALYSIS_PROMPT = """You are a professional fashion pattern maker. Analyze this garment image and extract precise sewing pattern parameters.
26
+
27
+ Return ONLY a JSON object (no markdown, no explanation) with this exact structure:
28
+
29
+ {
30
+ "garment_type": "<one of: shirt, blouse, top, t-shirt, dress, skirt, pants, trousers, jeans, jacket, coat, blazer, hoodie, vest>",
31
+ "description": "<brief description of the garment style, fit, and key features>",
32
+ "measurements": {
33
+ "bust": <number 75-130, estimated bust circumference in cm>,
34
+ "waist": <number 55-110, estimated waist circumference in cm>,
35
+ "hip": <number 80-130, estimated hip circumference in cm>,
36
+ "shoulder_width": <number 35-55, shoulder width in cm>,
37
+ "bodice_length": <number 35-75, from shoulder to waist/hem for tops>,
38
+ "sleeve_length": <number 15-75, sleeve length in cm, 15=cap sleeve, 25=short, 45=3/4, 60-70=long>,
39
+ "skirt_length": <number 30-120, for skirts/dresses: 30=mini, 55=knee, 80=midi, 110=maxi>,
40
+ "pant_length": <number 30-110, for pants: 30=shorts, 70=cropped, 100=full>,
41
+ "neckline_depth": <number 3-25, how deep the neckline is>,
42
+ "neckline_width": <number 5-15, how wide the neckline opening is>,
43
+ "bicep": <number 25-45, bicep circumference>,
44
+ "wrist": <number 15-25, wrist circumference>,
45
+ "cap_height": <number 8-18, sleeve cap height>,
46
+ "collar_height": <number 3-10, if collar present>,
47
+ "flare": <number 0-15, extra hem width for A-line/flared styles>
48
+ },
49
+ "features": {
50
+ "has_collar": <true/false>,
51
+ "collar_type": "<standard, mandarin, peter_pan, or none>",
52
+ "has_cuffs": <true/false>,
53
+ "has_pockets": <true/false>,
54
+ "pocket_type": "<patch, welt, or none>",
55
+ "has_hood": <true/false>,
56
+ "fit": "<fitted, regular, oversized, or loose>"
57
+ }
58
+ }
59
+
60
+ Be precise with the garment type. Estimate realistic measurements for an average adult body.
61
+ Only include measurements relevant to the garment type (e.g., skip pant_length for a shirt).
62
+ """
63
+
64
+ def analyze_with_vlm(image: Image.Image) -> Dict:
65
+ """Analyze garment image using HF Inference API (Qwen2.5-VL)."""
66
+ import requests
67
+ import base64
68
+ from io import BytesIO
69
+
70
+ hf_token = os.environ.get("HF_TOKEN", "")
71
+ if not hf_token:
72
+ return None
73
+
74
+ buf = BytesIO()
75
+ image_rgb = image.convert('RGB')
76
+ image_rgb.save(buf, format='JPEG', quality=85)
77
+ img_b64 = base64.b64encode(buf.getvalue()).decode('utf-8')
78
+
79
+ models = [
80
+ "Qwen/Qwen2.5-VL-72B-Instruct",
81
+ "Qwen/Qwen2.5-VL-32B-Instruct",
82
+ "Qwen/Qwen2.5-VL-7B-Instruct",
83
+ "meta-llama/Llama-4-Scout-17B-16E-Instruct",
84
+ ]
85
+
86
+ for model_id in models:
87
+ try:
88
+ url = f"https://router.huggingface.co/hf-inference/models/{model_id}/v1/chat/completions"
89
+ headers = {"Authorization": f"Bearer {hf_token}", "Content-Type": "application/json"}
90
+ payload = {
91
+ "model": model_id,
92
+ "messages": [{
93
+ "role": "user",
94
+ "content": [
95
+ {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}},
96
+ {"type": "text", "text": GARMENT_ANALYSIS_PROMPT}
97
+ ]
98
+ }],
99
+ "max_tokens": 2000,
100
+ "temperature": 0.1,
101
+ }
102
+
103
+ response = requests.post(url, headers=headers, json=payload, timeout=120)
104
+
105
+ if response.status_code == 200:
106
+ result = response.json()
107
+ content = result['choices'][0]['message']['content']
108
+
109
+ json_match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', content)
110
+ if json_match:
111
+ json_str = json_match.group(1)
112
+ else:
113
+ json_match = re.search(r'\{[\s\S]*\}', content)
114
+ if json_match:
115
+ json_str = json_match.group()
116
+ else:
117
+ continue
118
+
119
+ analysis = json.loads(json_str)
120
+ analysis['_model_used'] = model_id
121
+ return analysis
122
+
123
+ except Exception as e:
124
+ print(f"Model {model_id} failed: {e}")
125
+ continue
126
+
127
+ return None
128
+
129
+
130
+ def get_default_analysis(garment_type: str = "shirt") -> Dict:
131
+ """Return default analysis when VLM is unavailable."""
132
+ defaults = {
133
+ "shirt": {
134
+ "garment_type": "shirt",
135
+ "description": "Standard button-up shirt with collar and long sleeves",
136
+ "measurements": {
137
+ "bust": 96, "waist": 80, "shoulder_width": 44,
138
+ "bodice_length": 72, "sleeve_length": 62,
139
+ "neckline_depth": 8, "neckline_width": 7,
140
+ "bicep": 32, "wrist": 18, "cap_height": 14,
141
+ "collar_height": 4, "flare": 0
142
+ },
143
+ "features": {
144
+ "has_collar": True, "collar_type": "standard",
145
+ "has_cuffs": True, "has_pockets": True,
146
+ "pocket_type": "patch", "has_hood": False, "fit": "regular"
147
+ }
148
+ },
149
+ "dress": {
150
+ "garment_type": "dress",
151
+ "description": "A-line dress with fitted bodice and flared skirt",
152
+ "measurements": {
153
+ "bust": 90, "waist": 72, "hip": 96,
154
+ "shoulder_width": 40, "bodice_length": 42,
155
+ "sleeve_length": 25, "skirt_length": 55,
156
+ "neckline_depth": 12, "neckline_width": 8,
157
+ "bicep": 28, "wrist": 17, "cap_height": 12,
158
+ "flare": 8
159
+ },
160
+ "features": {
161
+ "has_collar": False, "collar_type": "none",
162
+ "has_cuffs": False, "has_pockets": False,
163
+ "pocket_type": "none", "has_hood": False, "fit": "fitted"
164
+ }
165
+ },
166
+ "pants": {
167
+ "garment_type": "pants",
168
+ "description": "Classic straight-leg trousers",
169
+ "measurements": {
170
+ "waist": 78, "hip": 98, "thigh": 56,
171
+ "knee": 40, "ankle": 26,
172
+ "pant_length": 100, "crotch_depth": 27,
173
+ "waistband_height": 4, "flare": 0
174
+ },
175
+ "features": {
176
+ "has_pockets": True, "pocket_type": "welt",
177
+ "has_collar": False, "has_hood": False, "fit": "regular"
178
+ }
179
+ },
180
+ "skirt": {
181
+ "garment_type": "skirt",
182
+ "description": "A-line knee-length skirt",
183
+ "measurements": {
184
+ "waist": 72, "hip": 96,
185
+ "skirt_length": 55, "waistband_height": 4,
186
+ "flare": 6
187
+ },
188
+ "features": {
189
+ "has_pockets": False, "has_collar": False,
190
+ "has_hood": False, "fit": "regular"
191
+ }
192
+ },
193
+ "jacket": {
194
+ "garment_type": "jacket",
195
+ "description": "Tailored blazer with notched collar",
196
+ "measurements": {
197
+ "bust": 100, "waist": 86, "shoulder_width": 46,
198
+ "jacket_length": 70, "sleeve_length": 62,
199
+ "neckline_depth": 15, "neckline_width": 9,
200
+ "bicep": 34, "wrist": 20, "cap_height": 15,
201
+ "collar_height": 6, "flare": 0
202
+ },
203
+ "features": {
204
+ "has_collar": True, "collar_type": "standard",
205
+ "has_cuffs": False, "has_pockets": True,
206
+ "pocket_type": "welt", "has_hood": False, "fit": "regular"
207
+ }
208
+ },
209
+ "hoodie": {
210
+ "garment_type": "hoodie",
211
+ "description": "Pullover hoodie with kangaroo pocket",
212
+ "measurements": {
213
+ "bust": 108, "waist": 100, "shoulder_width": 50,
214
+ "jacket_length": 68, "sleeve_length": 65,
215
+ "neckline_depth": 10, "neckline_width": 8,
216
+ "bicep": 36, "wrist": 22, "cap_height": 13,
217
+ "head_circumference": 57, "flare": 0
218
+ },
219
+ "features": {
220
+ "has_collar": False, "collar_type": "none",
221
+ "has_cuffs": True, "has_pockets": True,
222
+ "pocket_type": "patch", "has_hood": True, "fit": "oversized"
223
+ }
224
+ },
225
+ "vest": {
226
+ "garment_type": "vest",
227
+ "description": "Classic vest/waistcoat",
228
+ "measurements": {
229
+ "bust": 96, "waist": 80, "shoulder_width": 42,
230
+ "vest_length": 55, "neckline_depth": 18,
231
+ "neckline_width": 8, "flare": 0
232
+ },
233
+ "features": {
234
+ "has_collar": False, "has_cuffs": False,
235
+ "has_pockets": False, "has_hood": False, "fit": "fitted"
236
+ }
237
+ }
238
+ }
239
+ return defaults.get(garment_type, defaults["shirt"])
240
+
241
+
242
+ # ── Main App Functions ──
243
+
244
+ def process_image(image: Optional[Image.Image], garment_type_override: str = "Auto-detect") -> Tuple:
245
+ """Main processing: analyze image → generate pattern."""
246
+ if image is None and garment_type_override == "Auto-detect":
247
+ return None, "Please upload a garment image or select a garment type.", "{}"
248
+
249
+ analysis = None
250
+ model_info = ""
251
+
252
+ if image is not None:
253
+ try:
254
+ analysis = analyze_with_vlm(image)
255
+ if analysis:
256
+ model_info = f"\n\n*Analysis by: {analysis.get('_model_used', 'VLM')}*"
257
+ except Exception as e:
258
+ print(f"VLM analysis failed: {e}")
259
+ traceback.print_exc()
260
+
261
+ if analysis is None:
262
+ if garment_type_override != "Auto-detect":
263
+ gt = garment_type_override.lower()
264
+ elif image is not None:
265
+ gt = "shirt"
266
+ model_info = "\n\n*⚠️ VLM analysis unavailable. Using default parameters. Set HF_TOKEN for AI-powered analysis.*"
267
+ else:
268
+ gt = "shirt"
269
+ analysis = get_default_analysis(gt)
270
+
271
+ if garment_type_override != "Auto-detect":
272
+ analysis['garment_type'] = garment_type_override.lower()
273
+
274
+ try:
275
+ pattern_image, summary = generate_pattern_from_analysis(analysis)
276
+ description = analysis.get('description', 'No description')
277
+ summary = f"**Garment:** {description}\n\n{summary}{model_info}"
278
+ display_analysis = {k: v for k, v in analysis.items() if k != '_model_used'}
279
+ return pattern_image, summary, json.dumps(display_analysis, indent=2)
280
+ except Exception as e:
281
+ traceback.print_exc()
282
+ return None, f"Error generating pattern: {str(e)}", "{}"
283
+
284
+
285
+ def process_text_description(description: str) -> Tuple:
286
+ """Generate pattern from text description."""
287
+ import requests
288
+
289
+ hf_token = os.environ.get("HF_TOKEN", "")
290
+
291
+ if not description.strip():
292
+ return None, "Please enter a garment description.", "{}"
293
+
294
+ # Try VLM-based analysis first
295
+ if hf_token:
296
+ TEXT_PROMPT = f"""You are a professional fashion pattern maker. Based on this garment description, extract precise sewing pattern parameters.
297
+
298
+ Description: {description}
299
+
300
+ Return ONLY a JSON object (no markdown, no explanation) with this exact structure:
301
+
302
+ {{
303
+ "garment_type": "<one of: shirt, blouse, top, t-shirt, dress, skirt, pants, trousers, jeans, jacket, coat, blazer, hoodie, vest>",
304
+ "description": "{description}",
305
+ "measurements": {{
306
+ "bust": <number>, "waist": <number>, "hip": <number>,
307
+ "shoulder_width": <number>, "bodice_length": <number>,
308
+ "sleeve_length": <number>, "skirt_length": <number>,
309
+ "pant_length": <number>, "neckline_depth": <number>,
310
+ "neckline_width": <number>, "bicep": <number>, "wrist": <number>,
311
+ "cap_height": <number>, "collar_height": <number>, "flare": <number>
312
+ }},
313
+ "features": {{
314
+ "has_collar": <true/false>, "collar_type": "<standard/mandarin/peter_pan/none>",
315
+ "has_cuffs": <true/false>, "has_pockets": <true/false>,
316
+ "pocket_type": "<patch/welt/none>", "has_hood": <true/false>,
317
+ "fit": "<fitted/regular/oversized/loose>"
318
+ }}
319
+ }}
320
+
321
+ Only include measurements relevant to the garment type. Use realistic values in cm."""
322
+
323
+ try:
324
+ models = ["Qwen/Qwen2.5-72B-Instruct", "meta-llama/Llama-3.3-70B-Instruct"]
325
+ for model_id in models:
326
+ try:
327
+ url = f"https://router.huggingface.co/hf-inference/models/{model_id}/v1/chat/completions"
328
+ headers = {"Authorization": f"Bearer {hf_token}", "Content-Type": "application/json"}
329
+ payload = {
330
+ "model": model_id,
331
+ "messages": [{"role": "user", "content": TEXT_PROMPT}],
332
+ "max_tokens": 1500, "temperature": 0.1
333
+ }
334
+ response = requests.post(url, headers=headers, json=payload, timeout=60)
335
+ if response.status_code == 200:
336
+ content = response.json()['choices'][0]['message']['content']
337
+ json_match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', content)
338
+ if json_match:
339
+ json_str = json_match.group(1)
340
+ else:
341
+ json_match = re.search(r'\{[\s\S]*\}', content)
342
+ json_str = json_match.group() if json_match else None
343
+ if json_str:
344
+ analysis = json.loads(json_str)
345
+ pattern_image, summary = generate_pattern_from_analysis(analysis)
346
+ summary += f"\n\n*Analysis by: {model_id}*"
347
+ return pattern_image, summary, json.dumps(analysis, indent=2)
348
+ except Exception as e:
349
+ print(f"Text model {model_id} failed: {e}")
350
+ continue
351
+ except Exception as e:
352
+ print(f"Text analysis failed: {e}")
353
+
354
+ # Fallback: keyword matching
355
+ desc_lower = description.lower()
356
+ for gt in ['hoodie', 'jacket', 'coat', 'blazer', 'dress', 'skirt', 'pants', 'trousers', 'jeans', 'vest', 'shirt', 'blouse', 'top']:
357
+ if gt in desc_lower:
358
+ analysis = get_default_analysis(gt)
359
+ analysis['description'] = description
360
+ pattern_image, summary = generate_pattern_from_analysis(analysis)
361
+ summary += "\n\n*Using default parameters (set HF_TOKEN for AI-powered analysis)*"
362
+ return pattern_image, summary, json.dumps(analysis, indent=2)
363
+
364
+ analysis = get_default_analysis("shirt")
365
+ analysis['description'] = description
366
+ pattern_image, summary = generate_pattern_from_analysis(analysis)
367
+ summary += "\n\n*Could not detect garment type — defaulting to shirt*"
368
+ return pattern_image, summary, json.dumps(analysis, indent=2)
369
+
370
+
371
+ def process_manual_params(
372
+ garment_type, bust, waist, hip, shoulder, bodice_len,
373
+ sleeve_len, skirt_len, pant_len, neckline_depth,
374
+ has_collar, collar_type, has_cuffs, has_pockets, has_hood, flare, fit
375
+ ):
376
+ """Generate pattern from manual parameter input."""
377
+ analysis = {
378
+ "garment_type": garment_type.lower(),
379
+ "description": f"Custom {garment_type.lower()} with manual measurements",
380
+ "measurements": {
381
+ "bust": bust, "waist": waist, "hip": hip,
382
+ "shoulder_width": shoulder, "bodice_length": bodice_len,
383
+ "sleeve_length": sleeve_len, "skirt_length": skirt_len,
384
+ "pant_length": pant_len, "neckline_depth": neckline_depth,
385
+ "neckline_width": 7, "bicep": 30, "wrist": 18,
386
+ "cap_height": 14, "collar_height": 5, "flare": flare,
387
+ },
388
+ "features": {
389
+ "has_collar": has_collar, "collar_type": collar_type.lower(),
390
+ "has_cuffs": has_cuffs, "has_pockets": has_pockets,
391
+ "pocket_type": "patch", "has_hood": has_hood,
392
+ "fit": fit.lower()
393
+ }
394
+ }
395
+
396
+ pattern_image, summary = generate_pattern_from_analysis(analysis)
397
+ return pattern_image, summary, json.dumps(analysis, indent=2)
398
+
399
+
400
+ # ── Gradio UI ──
401
+
402
+ CSS = """
403
+ .main-header { text-align: center; margin-bottom: 20px; }
404
+ .info-box { padding: 15px; border-radius: 10px; background: #f0f7ff; border: 1px solid #cce0ff; margin: 10px 0; }
405
+ .ref-box { padding: 10px; border-radius: 8px; background: #f8f8f8; border: 1px solid #e0e0e0; font-size: 0.85em; }
406
+ """
407
+
408
+ with gr.Blocks(css=CSS, title="Garment → 2D Sewing Pattern", theme=gr.themes.Soft()) as demo:
409
+ gr.HTML("""
410
+ <div class="main-header">
411
+ <h1>🧵 Garment Image → 2D Sewing Pattern</h1>
412
+ <p style="font-size: 1.1em; color: #555;">
413
+ Upload a garment image or describe one — get flat 2D sewing pattern pieces
414
+ </p>
415
+ </div>
416
+ """)
417
+
418
+ gr.HTML("""
419
+ <div class="info-box">
420
+ <b>How it works:</b> A Vision-Language Model analyzes the garment image to identify type, style, and proportions.
421
+ These parameters feed into a parametric pattern generator that produces anatomically-correct 2D sewing pattern pieces
422
+ with seam allowances, grain lines, and notches.
423
+ <br><br>
424
+ <b>Based on research:</b>
425
+ <a href="https://arxiv.org/abs/2412.17811" target="_blank">ChatGarment</a> (VLM → JSON → 2D patterns) &
426
+ <a href="https://arxiv.org/abs/2602.20700" target="_blank">NGL-Prompter</a> (training-free VLM approach)
427
+ </div>
428
+ """)
429
+
430
+ with gr.Tabs():
431
+ # Tab 1: Image Input
432
+ with gr.Tab("📸 From Image", id="image_tab"):
433
+ with gr.Row():
434
+ with gr.Column(scale=1):
435
+ input_image = gr.Image(type="pil", label="Upload Garment Image", height=400)
436
+ garment_override = gr.Dropdown(
437
+ choices=["Auto-detect", "Shirt", "Dress", "Skirt", "Pants", "Jacket", "Hoodie", "Vest"],
438
+ value="Auto-detect",
439
+ label="Garment Type (override auto-detection)"
440
+ )
441
+ analyze_btn = gr.Button("🔍 Analyze & Generate Pattern", variant="primary", size="lg")
442
+
443
+ with gr.Column(scale=2):
444
+ output_pattern = gr.Image(label="2D Sewing Pattern", height=500)
445
+ output_summary = gr.Markdown(label="Pattern Summary")
446
+ with gr.Accordion("Raw Analysis JSON", open=False):
447
+ output_json = gr.Code(language="json", label="Analysis Parameters")
448
+
449
+ analyze_btn.click(
450
+ process_image,
451
+ inputs=[input_image, garment_override],
452
+ outputs=[output_pattern, output_summary, output_json]
453
+ )
454
+
455
+ # Tab 2: Text Description
456
+ with gr.Tab("✍️ From Text", id="text_tab"):
457
+ with gr.Row():
458
+ with gr.Column(scale=1):
459
+ text_input = gr.Textbox(
460
+ label="Describe the garment",
461
+ placeholder="e.g., 'A fitted A-line dress with short cap sleeves, V-neckline, and knee length'",
462
+ lines=4
463
+ )
464
+ text_btn = gr.Button("🧵 Generate Pattern", variant="primary", size="lg")
465
+
466
+ gr.Examples(
467
+ examples=[
468
+ ["A classic men's dress shirt with long sleeves, button-down collar, and chest pocket"],
469
+ ["A flared midi skirt with high waist, A-line silhouette"],
470
+ ["Slim-fit straight-leg jeans with front and back pockets"],
471
+ ["An oversized hoodie with kangaroo pocket and drawstring hood"],
472
+ ["A fitted blazer with notched lapel collar and two welt pockets"],
473
+ ["A knee-length A-line dress with cap sleeves and round neckline"],
474
+ ],
475
+ inputs=text_input
476
+ )
477
+
478
+ with gr.Column(scale=2):
479
+ text_output_pattern = gr.Image(label="2D Sewing Pattern", height=500)
480
+ text_output_summary = gr.Markdown(label="Pattern Summary")
481
+ with gr.Accordion("Raw Analysis JSON", open=False):
482
+ text_output_json = gr.Code(language="json", label="Analysis Parameters")
483
+
484
+ text_btn.click(
485
+ process_text_description,
486
+ inputs=[text_input],
487
+ outputs=[text_output_pattern, text_output_summary, text_output_json]
488
+ )
489
+
490
+ # Tab 3: Manual Parameters
491
+ with gr.Tab("📐 Manual Parameters", id="manual_tab"):
492
+ with gr.Row():
493
+ with gr.Column(scale=1):
494
+ m_type = gr.Dropdown(
495
+ choices=["Shirt", "Dress", "Skirt", "Pants", "Jacket", "Hoodie", "Vest"],
496
+ value="Shirt", label="Garment Type"
497
+ )
498
+
499
+ gr.Markdown("### Body Measurements (cm)")
500
+ with gr.Row():
501
+ m_bust = gr.Slider(70, 130, value=92, step=1, label="Bust")
502
+ m_waist = gr.Slider(55, 110, value=74, step=1, label="Waist")
503
+ with gr.Row():
504
+ m_hip = gr.Slider(75, 130, value=96, step=1, label="Hip")
505
+ m_shoulder = gr.Slider(35, 55, value=42, step=1, label="Shoulder Width")
506
+
507
+ gr.Markdown("### Garment Dimensions (cm)")
508
+ with gr.Row():
509
+ m_bodice = gr.Slider(30, 80, value=42, step=1, label="Bodice/Top Length")
510
+ m_sleeve = gr.Slider(10, 75, value=60, step=1, label="Sleeve Length")
511
+ with gr.Row():
512
+ m_skirt = gr.Slider(25, 120, value=55, step=1, label="Skirt Length")
513
+ m_pant = gr.Slider(25, 115, value=100, step=1, label="Pant Length")
514
+ with gr.Row():
515
+ m_neck = gr.Slider(3, 25, value=8, step=1, label="Neckline Depth")
516
+ m_flare = gr.Slider(0, 20, value=0, step=1, label="Hem Flare")
517
+
518
+ gr.Markdown("### Features")
519
+ with gr.Row():
520
+ m_collar = gr.Checkbox(value=True, label="Collar")
521
+ m_collar_type = gr.Dropdown(["Standard", "Mandarin", "Peter_pan"], value="Standard", label="Collar Type")
522
+ with gr.Row():
523
+ m_cuffs = gr.Checkbox(value=True, label="Cuffs")
524
+ m_pockets = gr.Checkbox(value=False, label="Pockets")
525
+ with gr.Row():
526
+ m_hood = gr.Checkbox(value=False, label="Hood")
527
+ m_fit = gr.Dropdown(["Fitted", "Regular", "Oversized", "Loose"], value="Regular", label="Fit")
528
+
529
+ manual_btn = gr.Button("🧵 Generate Pattern", variant="primary", size="lg")
530
+
531
+ with gr.Column(scale=2):
532
+ manual_output_pattern = gr.Image(label="2D Sewing Pattern", height=500)
533
+ manual_output_summary = gr.Markdown(label="Pattern Summary")
534
+ with gr.Accordion("Raw Parameters JSON", open=False):
535
+ manual_output_json = gr.Code(language="json", label="Parameters")
536
+
537
+ manual_btn.click(
538
+ process_manual_params,
539
+ inputs=[m_type, m_bust, m_waist, m_hip, m_shoulder, m_bodice,
540
+ m_sleeve, m_skirt, m_pant, m_neck,
541
+ m_collar, m_collar_type, m_cuffs, m_pockets, m_hood, m_flare, m_fit],
542
+ outputs=[manual_output_pattern, manual_output_summary, manual_output_json]
543
+ )
544
+
545
+ # References
546
+ gr.HTML("""
547
+ <div class="ref-box" style="margin-top: 20px;">
548
+ <h4>📚 Research References</h4>
549
+ <ul>
550
+ <li><b>ChatGarment</b> (Bian et al., 2024) — VLM → GarmentCode JSON → sewing patterns
551
+ [<a href="https://arxiv.org/abs/2412.17811">Paper</a>]
552
+ [<a href="https://github.com/biansy000/ChatGarment">Code</a>]
553
+ [<a href="https://huggingface.co/datasets/sy000/ChatGarmentDataset">Dataset</a>]</li>
554
+ <li><b>NGL-Prompter</b> (2025) — Training-free VLM + Natural Garment Language
555
+ [<a href="https://arxiv.org/abs/2602.20700">Paper</a>]</li>
556
+ <li><b>SewFormer</b> (Liu et al., 2023) — Two-level Transformer for pattern reconstruction
557
+ [<a href="https://arxiv.org/abs/2311.04218">Paper</a>]</li>
558
+ <li><b>GarmentDiffusion</b> (2025) — DiT-based multimodal pattern generation
559
+ [<a href="https://arxiv.org/abs/2504.21476">Paper</a>]</li>
560
+ <li><b>GarmageNet</b> (Style3D, 2025) — Geometry image diffusion for sewing patterns
561
+ [<a href="https://arxiv.org/abs/2504.01483">Paper</a>]
562
+ [<a href="https://huggingface.co/datasets/Style3D/GarmageSet">Dataset</a>]</li>
563
+ </ul>
564
+ </div>
565
+ """)
566
+
567
+
568
+ if __name__ == "__main__":
569
+ demo.launch(server_name="0.0.0.0", server_port=7860)