vikashmakeit commited on
Commit
775c082
·
verified ·
1 Parent(s): dd071f2

Pass actual 2D pattern pieces to 3D builder — 3D view now matches 2D patterns

Browse files
Files changed (1) hide show
  1. app.py +53 -98
app.py CHANGED
@@ -5,7 +5,7 @@ import json, os, re, traceback, copy
5
  from typing import Dict, Optional, Tuple, List
6
  import gradio as gr
7
  from PIL import Image
8
- from pattern_generator import generate_pattern_from_analysis
9
  from garment_3d import create_3d_figure
10
 
11
  GARMENT_ANALYSIS_PROMPT = """You are a professional fashion pattern maker. Analyze this garment image and extract precise sewing pattern parameters.
@@ -90,7 +90,6 @@ def _extract_json_from_text(text):
90
  return None
91
 
92
  def _call_vlm(messages, timeout=180):
93
- """Call a VLM model via HF Inference Providers."""
94
  import requests, base64
95
  from io import BytesIO
96
  hf_token = os.environ.get("HF_TOKEN", "")
@@ -153,13 +152,24 @@ def get_default_analysis(garment_type="shirt"):
153
  }
154
  return defaults.get(garment_type, defaults["shirt"])
155
 
156
- # Global state for current analysis
157
  _current_analysis = {"data": None}
158
 
159
  def _generate_all_outputs(analysis):
160
- """Generate 2D pattern, 3D figure, and summary from analysis."""
 
 
 
 
 
 
 
 
 
161
  pattern_image, summary = generate_pattern_from_analysis(analysis)
162
- fig_3d = create_3d_figure(analysis)
 
 
 
163
  display = {k: v for k, v in analysis.items() if k != '_model_used'}
164
  model_info = f"\n\n*AI: {analysis.get('_model_used', 'Default')}*" if analysis.get('_model_used') else ""
165
  desc = analysis.get('description', 'No description')
@@ -229,7 +239,6 @@ def process_manual(gt,bust,waist,hip,shoulder,bodice,sleeve,skirt,pant,neck,flar
229
  return None, None, f"Error: {e}", "{}", []
230
 
231
  def chat_edit(message, history):
232
- """Chat-based pattern editing."""
233
  if not message.strip():
234
  return history, None, None, "Please enter an edit request.", "{}"
235
  current = _current_analysis.get("data")
@@ -238,9 +247,7 @@ def chat_edit(message, history):
238
  _current_analysis["data"] = current
239
  current_clean = {k: v for k, v in current.items() if k != '_model_used'}
240
  edit_prompt = EDIT_PROMPT_TEMPLATE.format(
241
- current_json=json.dumps(current_clean, indent=2),
242
- user_message=message
243
- )
244
  updated = None
245
  hf_token = os.environ.get("HF_TOKEN", "")
246
  if hf_token:
@@ -257,18 +264,15 @@ def chat_edit(message, history):
257
  elif "short sleeve" in msg_lower or "shorter sleeve" in msg_lower:
258
  updated['measurements']['sleeve_length'] = 25
259
  if "no collar" in msg_lower or "remove collar" in msg_lower:
260
- updated['features']['has_collar'] = False
261
- updated['features']['collar_type'] = 'none'
262
  if "add collar" in msg_lower:
263
- updated['features']['has_collar'] = True
264
- updated['features']['collar_type'] = 'standard'
265
  if "add hood" in msg_lower or "hoodie" in msg_lower:
266
  updated['features']['has_hood'] = True
267
  if "no hood" in msg_lower or "remove hood" in msg_lower:
268
  updated['features']['has_hood'] = False
269
  if "add pocket" in msg_lower:
270
- updated['features']['has_pockets'] = True
271
- updated['features']['pocket_type'] = 'patch'
272
  if "no pocket" in msg_lower or "remove pocket" in msg_lower:
273
  updated['features']['has_pockets'] = False
274
  if "oversized" in msg_lower or "loose" in msg_lower:
@@ -278,14 +282,10 @@ def chat_edit(message, history):
278
  updated['features']['fit'] = 'fitted'
279
  if "flare" in msg_lower or "a-line" in msg_lower:
280
  updated['measurements']['flare'] = max(updated['measurements'].get('flare', 0), 8)
281
- if "straight" in msg_lower:
282
- updated['measurements']['flare'] = 0
283
- if "mini" in msg_lower:
284
- updated['measurements']['skirt_length'] = 30
285
- if "midi" in msg_lower:
286
- updated['measurements']['skirt_length'] = 80
287
- if "maxi" in msg_lower:
288
- updated['measurements']['skirt_length'] = 110
289
  updated['_model_used'] = 'Rule-based edit'
290
  if 'garment_type' not in updated:
291
  updated['garment_type'] = current.get('garment_type', 'shirt')
@@ -296,22 +296,14 @@ def chat_edit(message, history):
296
  p2d, p3d, summary, j = None, None, f"Error: {e}", "{}"
297
  ai_msg = f"Applied edit: {message}\n\n"
298
  changes = []
299
- old_m = current.get('measurements', {})
300
- new_m = updated.get('measurements', {})
301
- old_f = current.get('features', {})
302
- new_f = updated.get('features', {})
303
- for k in set(list(old_m.keys()) + list(new_m.keys())):
304
- ov, nv = old_m.get(k), new_m.get(k)
305
  if ov != nv and ov is not None and nv is not None:
306
  changes.append(f" {k}: {ov} -> {nv}")
307
- for k in set(list(old_f.keys()) + list(new_f.keys())):
308
- ov, nv = old_f.get(k), new_f.get(k)
309
- if ov != nv:
310
- changes.append(f" {k}: {ov} -> {nv}")
311
- if changes:
312
- ai_msg += "Changes:\n" + "\n".join(changes)
313
- else:
314
- ai_msg += "No changes detected."
315
  history = history or []
316
  history.append((message, ai_msg))
317
  return history, p2d, p3d, summary, j
@@ -326,21 +318,14 @@ with gr.Blocks(title="Garment Pattern Studio") as demo:
326
  gr.HTML("""
327
  <div class="main-header">
328
  <h1>🧵 Garment Pattern Studio</h1>
329
- <p style="font-size: 1.1em; color: #555;">
330
- Analyze garments, edit with chat, preview in 3D
331
- </p>
332
  </div>
333
  <div class="info-box">
334
  <b>Powered by:</b> Qwen 3.5 &middot; Gemma 4 &middot; Kimi K2.5 via
335
- <a href="https://huggingface.co/docs/inference-providers" target="_blank">HF Inference Providers</a>
336
- &nbsp;|&nbsp;
337
- <b>Research:</b>
338
- <a href="https://arxiv.org/abs/2412.17811">ChatGarment</a> &
339
- <a href="https://arxiv.org/abs/2602.20700">NGL-Prompter</a>
340
- </div>
341
- """)
342
 
343
- # === Tab 1: From Image ===
344
  with gr.Tab("From Image"):
345
  with gr.Row():
346
  with gr.Column(scale=1):
@@ -349,16 +334,12 @@ with gr.Blocks(title="Garment Pattern Studio") as demo:
349
  analyze_btn = gr.Button("Analyze & Generate", variant="primary", size="lg")
350
  with gr.Column(scale=2):
351
  with gr.Row():
352
- with gr.Column():
353
- out_pattern_2d = gr.Image(label="2D Sewing Pattern", height=400)
354
- with gr.Column():
355
- out_3d = gr.Plot(label="3D Garment Preview")
356
  out_summary = gr.Markdown(label="Pattern Summary")
357
- with gr.Accordion("Raw JSON", open=False):
358
- out_json = gr.Code(language="json", label="Analysis")
359
  analyze_btn.click(process_image, inputs=[input_image, garment_override], outputs=[out_pattern_2d, out_3d, out_summary, out_json])
360
 
361
- # === Tab 2: From Text ===
362
  with gr.Tab("From Text"):
363
  with gr.Row():
364
  with gr.Column(scale=1):
@@ -374,16 +355,12 @@ with gr.Blocks(title="Garment Pattern Studio") as demo:
374
  ], inputs=text_input)
375
  with gr.Column(scale=2):
376
  with gr.Row():
377
- with gr.Column():
378
- txt_pattern_2d = gr.Image(label="2D Pattern", height=400)
379
- with gr.Column():
380
- txt_3d = gr.Plot(label="3D Preview")
381
  txt_summary = gr.Markdown()
382
- with gr.Accordion("Raw JSON", open=False):
383
- txt_json = gr.Code(language="json")
384
  text_btn.click(process_text, inputs=[text_input], outputs=[txt_pattern_2d, txt_3d, txt_summary, txt_json])
385
 
386
- # === Tab 3: Manual Parameters ===
387
  with gr.Tab("Manual Parameters"):
388
  with gr.Row():
389
  with gr.Column(scale=1):
@@ -417,18 +394,14 @@ with gr.Blocks(title="Garment Pattern Studio") as demo:
417
  manual_btn = gr.Button("Generate Pattern", variant="primary", size="lg")
418
  with gr.Column(scale=2):
419
  with gr.Row():
420
- with gr.Column():
421
- man_pattern_2d = gr.Image(label="2D Pattern", height=400)
422
- with gr.Column():
423
- man_3d = gr.Plot(label="3D Preview")
424
  man_summary = gr.Markdown()
425
- with gr.Accordion("Raw JSON", open=False):
426
- man_json = gr.Code(language="json")
427
  manual_btn.click(process_manual, inputs=[m_type,m_bust,m_waist,m_hip,m_shoulder,m_bodice,m_sleeve,m_skirt,m_pant,m_neck,m_flare,m_collar,m_ctype,m_cuffs,m_pockets,m_hood,m_fit], outputs=[man_pattern_2d, man_3d, man_summary, man_json])
428
 
429
- # === Tab 4: Chat & Edit ===
430
  with gr.Tab("Chat & Edit"):
431
- gr.Markdown("### Edit your pattern with natural language\nFirst generate a pattern (from Image/Text/Manual tabs), then edit it here. Each edit updates both 2D and 3D views.")
432
  with gr.Row():
433
  with gr.Column(scale=1):
434
  chatbot = gr.Chatbot(label="Pattern Editor", height=400)
@@ -438,39 +411,21 @@ with gr.Blocks(title="Garment Pattern Studio") as demo:
438
  chat_clear = gr.Button("Clear Chat")
439
  with gr.Column(scale=2):
440
  with gr.Row():
441
- with gr.Column():
442
- chat_pattern_2d = gr.Image(label="Updated 2D Pattern", height=400)
443
- with gr.Column():
444
- chat_3d = gr.Plot(label="Updated 3D Preview")
445
  chat_summary = gr.Markdown()
446
- with gr.Accordion("Updated JSON", open=False):
447
- chat_json = gr.Code(language="json")
448
-
449
- def clear_chat():
450
- return [], None, None, "", "{}"
451
-
452
  chat_send.click(chat_edit, inputs=[chat_input, chatbot], outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
453
  chat_input.submit(chat_edit, inputs=[chat_input, chatbot], outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
454
  chat_clear.click(clear_chat, outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
455
 
456
- # References
457
- gr.HTML("""
458
- <div class="ref-box" style="margin-top: 20px;">
459
- <h4>Research References</h4>
460
- <ul>
461
- <li><b>ChatGarment</b> (Bian et al., 2024) -- VLM + multi-turn dialogue for garment editing
462
- [<a href="https://arxiv.org/abs/2412.17811">Paper</a>]
463
- [<a href="https://github.com/biansy000/ChatGarment">Code</a>]
464
- [<a href="https://huggingface.co/datasets/sy000/ChatGarmentDataset">Dataset</a>]</li>
465
- <li><b>NGL-Prompter</b> (2025) -- Training-free VLM pattern estimation
466
- [<a href="https://arxiv.org/abs/2602.20700">Paper</a>]</li>
467
- <li><b>SewFormer</b> (Liu et al., 2023) -- Two-level Transformer pattern reconstruction
468
- [<a href="https://arxiv.org/abs/2311.04218">Paper</a>]</li>
469
- <li><b>GarmentDiffusion</b> (2025) -- DiT-based multimodal pattern generation
470
- [<a href="https://arxiv.org/abs/2504.21476">Paper</a>]</li>
471
- </ul>
472
- </div>
473
- """)
474
 
475
  if __name__ == "__main__":
476
  demo.launch(server_name="0.0.0.0", server_port=7860, css=CSS, theme=gr.themes.Soft())
 
5
  from typing import Dict, Optional, Tuple, List
6
  import gradio as gr
7
  from PIL import Image
8
+ from pattern_generator import generate_pattern_from_analysis, get_pattern_pieces
9
  from garment_3d import create_3d_figure
10
 
11
  GARMENT_ANALYSIS_PROMPT = """You are a professional fashion pattern maker. Analyze this garment image and extract precise sewing pattern parameters.
 
90
  return None
91
 
92
  def _call_vlm(messages, timeout=180):
 
93
  import requests, base64
94
  from io import BytesIO
95
  hf_token = os.environ.get("HF_TOKEN", "")
 
152
  }
153
  return defaults.get(garment_type, defaults["shirt"])
154
 
 
155
  _current_analysis = {"data": None}
156
 
157
  def _generate_all_outputs(analysis):
158
+ """Generate 2D pattern, 3D figure (from pattern pieces), and summary."""
159
+ garment_type = analysis.get('garment_type', 'shirt')
160
+ measurements = analysis.get('measurements', {})
161
+ features = analysis.get('features', {})
162
+ params = {**measurements, **features}
163
+
164
+ # Generate 2D pattern pieces
165
+ pattern_pieces = get_pattern_pieces(garment_type, params)
166
+
167
+ # Generate 2D pattern image and summary
168
  pattern_image, summary = generate_pattern_from_analysis(analysis)
169
+
170
+ # Generate 3D figure FROM the actual pattern pieces
171
+ fig_3d = create_3d_figure(analysis, pattern_pieces=pattern_pieces)
172
+
173
  display = {k: v for k, v in analysis.items() if k != '_model_used'}
174
  model_info = f"\n\n*AI: {analysis.get('_model_used', 'Default')}*" if analysis.get('_model_used') else ""
175
  desc = analysis.get('description', 'No description')
 
239
  return None, None, f"Error: {e}", "{}", []
240
 
241
  def chat_edit(message, history):
 
242
  if not message.strip():
243
  return history, None, None, "Please enter an edit request.", "{}"
244
  current = _current_analysis.get("data")
 
247
  _current_analysis["data"] = current
248
  current_clean = {k: v for k, v in current.items() if k != '_model_used'}
249
  edit_prompt = EDIT_PROMPT_TEMPLATE.format(
250
+ current_json=json.dumps(current_clean, indent=2), user_message=message)
 
 
251
  updated = None
252
  hf_token = os.environ.get("HF_TOKEN", "")
253
  if hf_token:
 
264
  elif "short sleeve" in msg_lower or "shorter sleeve" in msg_lower:
265
  updated['measurements']['sleeve_length'] = 25
266
  if "no collar" in msg_lower or "remove collar" in msg_lower:
267
+ updated['features']['has_collar'] = False; updated['features']['collar_type'] = 'none'
 
268
  if "add collar" in msg_lower:
269
+ updated['features']['has_collar'] = True; updated['features']['collar_type'] = 'standard'
 
270
  if "add hood" in msg_lower or "hoodie" in msg_lower:
271
  updated['features']['has_hood'] = True
272
  if "no hood" in msg_lower or "remove hood" in msg_lower:
273
  updated['features']['has_hood'] = False
274
  if "add pocket" in msg_lower:
275
+ updated['features']['has_pockets'] = True; updated['features']['pocket_type'] = 'patch'
 
276
  if "no pocket" in msg_lower or "remove pocket" in msg_lower:
277
  updated['features']['has_pockets'] = False
278
  if "oversized" in msg_lower or "loose" in msg_lower:
 
282
  updated['features']['fit'] = 'fitted'
283
  if "flare" in msg_lower or "a-line" in msg_lower:
284
  updated['measurements']['flare'] = max(updated['measurements'].get('flare', 0), 8)
285
+ if "straight" in msg_lower: updated['measurements']['flare'] = 0
286
+ if "mini" in msg_lower: updated['measurements']['skirt_length'] = 30
287
+ if "midi" in msg_lower: updated['measurements']['skirt_length'] = 80
288
+ if "maxi" in msg_lower: updated['measurements']['skirt_length'] = 110
 
 
 
 
289
  updated['_model_used'] = 'Rule-based edit'
290
  if 'garment_type' not in updated:
291
  updated['garment_type'] = current.get('garment_type', 'shirt')
 
296
  p2d, p3d, summary, j = None, None, f"Error: {e}", "{}"
297
  ai_msg = f"Applied edit: {message}\n\n"
298
  changes = []
299
+ for k in set(list(current.get('measurements',{}).keys()) + list(updated.get('measurements',{}).keys())):
300
+ ov, nv = current.get('measurements',{}).get(k), updated.get('measurements',{}).get(k)
 
 
 
 
301
  if ov != nv and ov is not None and nv is not None:
302
  changes.append(f" {k}: {ov} -> {nv}")
303
+ for k in set(list(current.get('features',{}).keys()) + list(updated.get('features',{}).keys())):
304
+ ov, nv = current.get('features',{}).get(k), updated.get('features',{}).get(k)
305
+ if ov != nv: changes.append(f" {k}: {ov} -> {nv}")
306
+ ai_msg += ("Changes:\n" + "\n".join(changes)) if changes else "No changes detected."
 
 
 
 
307
  history = history or []
308
  history.append((message, ai_msg))
309
  return history, p2d, p3d, summary, j
 
318
  gr.HTML("""
319
  <div class="main-header">
320
  <h1>🧵 Garment Pattern Studio</h1>
321
+ <p style="font-size: 1.1em; color: #555;">Analyze garments, edit with chat, preview in 3D</p>
 
 
322
  </div>
323
  <div class="info-box">
324
  <b>Powered by:</b> Qwen 3.5 &middot; Gemma 4 &middot; Kimi K2.5 via
325
+ <a href="https://huggingface.co/docs/inference-providers">HF Inference Providers</a>
326
+ &nbsp;|&nbsp; <b>3D view is built from the actual 2D pattern pieces</b>
327
+ </div>""")
 
 
 
 
328
 
 
329
  with gr.Tab("From Image"):
330
  with gr.Row():
331
  with gr.Column(scale=1):
 
334
  analyze_btn = gr.Button("Analyze & Generate", variant="primary", size="lg")
335
  with gr.Column(scale=2):
336
  with gr.Row():
337
+ with gr.Column(): out_pattern_2d = gr.Image(label="2D Sewing Pattern", height=400)
338
+ with gr.Column(): out_3d = gr.Plot(label="3D Garment Preview")
 
 
339
  out_summary = gr.Markdown(label="Pattern Summary")
340
+ with gr.Accordion("Raw JSON", open=False): out_json = gr.Code(language="json", label="Analysis")
 
341
  analyze_btn.click(process_image, inputs=[input_image, garment_override], outputs=[out_pattern_2d, out_3d, out_summary, out_json])
342
 
 
343
  with gr.Tab("From Text"):
344
  with gr.Row():
345
  with gr.Column(scale=1):
 
355
  ], inputs=text_input)
356
  with gr.Column(scale=2):
357
  with gr.Row():
358
+ with gr.Column(): txt_pattern_2d = gr.Image(label="2D Pattern", height=400)
359
+ with gr.Column(): txt_3d = gr.Plot(label="3D Preview")
 
 
360
  txt_summary = gr.Markdown()
361
+ with gr.Accordion("Raw JSON", open=False): txt_json = gr.Code(language="json")
 
362
  text_btn.click(process_text, inputs=[text_input], outputs=[txt_pattern_2d, txt_3d, txt_summary, txt_json])
363
 
 
364
  with gr.Tab("Manual Parameters"):
365
  with gr.Row():
366
  with gr.Column(scale=1):
 
394
  manual_btn = gr.Button("Generate Pattern", variant="primary", size="lg")
395
  with gr.Column(scale=2):
396
  with gr.Row():
397
+ with gr.Column(): man_pattern_2d = gr.Image(label="2D Pattern", height=400)
398
+ with gr.Column(): man_3d = gr.Plot(label="3D Preview")
 
 
399
  man_summary = gr.Markdown()
400
+ with gr.Accordion("Raw JSON", open=False): man_json = gr.Code(language="json")
 
401
  manual_btn.click(process_manual, inputs=[m_type,m_bust,m_waist,m_hip,m_shoulder,m_bodice,m_sleeve,m_skirt,m_pant,m_neck,m_flare,m_collar,m_ctype,m_cuffs,m_pockets,m_hood,m_fit], outputs=[man_pattern_2d, man_3d, man_summary, man_json])
402
 
 
403
  with gr.Tab("Chat & Edit"):
404
+ gr.Markdown("### Edit your pattern with natural language\nFirst generate a pattern, then edit here. Both 2D and 3D update together.")
405
  with gr.Row():
406
  with gr.Column(scale=1):
407
  chatbot = gr.Chatbot(label="Pattern Editor", height=400)
 
411
  chat_clear = gr.Button("Clear Chat")
412
  with gr.Column(scale=2):
413
  with gr.Row():
414
+ with gr.Column(): chat_pattern_2d = gr.Image(label="Updated 2D Pattern", height=400)
415
+ with gr.Column(): chat_3d = gr.Plot(label="Updated 3D Preview")
 
 
416
  chat_summary = gr.Markdown()
417
+ with gr.Accordion("Updated JSON", open=False): chat_json = gr.Code(language="json")
418
+ def clear_chat(): return [], None, None, "", "{}"
 
 
 
 
419
  chat_send.click(chat_edit, inputs=[chat_input, chatbot], outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
420
  chat_input.submit(chat_edit, inputs=[chat_input, chatbot], outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
421
  chat_clear.click(clear_chat, outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json])
422
 
423
+ gr.HTML("""<div class="ref-box" style="margin-top: 20px;"><h4>Research References</h4><ul>
424
+ <li><b>ChatGarment</b> (2024) [<a href="https://arxiv.org/abs/2412.17811">Paper</a>] [<a href="https://huggingface.co/datasets/sy000/ChatGarmentDataset">Dataset</a>]</li>
425
+ <li><b>NGL-Prompter</b> (2025) [<a href="https://arxiv.org/abs/2602.20700">Paper</a>]</li>
426
+ <li><b>SewFormer</b> (2023) [<a href="https://arxiv.org/abs/2311.04218">Paper</a>]</li>
427
+ <li><b>GarmentDiffusion</b> (2025) [<a href="https://arxiv.org/abs/2504.21476">Paper</a>]</li>
428
+ </ul></div>""")
 
 
 
 
 
 
 
 
 
 
 
 
429
 
430
  if __name__ == "__main__":
431
  demo.launch(server_name="0.0.0.0", server_port=7860, css=CSS, theme=gr.themes.Soft())