Spaces:
Running
Running
Fix VLM: use Llama-4-Scout via nscale (confirmed working with images), Kimi-K2.6 and Qwen3.5-9B via together as fallbacks
e9ba07a verified | """ | |
| Garment Image -> 2D Sewing Pattern + Chat Editing + 3D Preview + Agentic Refinement | |
| """ | |
| import json, os, re, traceback, copy | |
| from typing import Dict, Optional, Tuple, List | |
| import gradio as gr | |
| from PIL import Image | |
| from pattern_generator import generate_pattern_from_analysis, get_pattern_pieces | |
| from garment_3d import create_3d_figure | |
| from refinement_loop import refinement_loop, render_3d_to_image | |
| GARMENT_ANALYSIS_PROMPT = """You are a professional fashion pattern maker. Analyze this garment image and extract precise sewing pattern parameters. | |
| Return ONLY a JSON object (no markdown, no explanation) with this exact structure: | |
| { | |
| "garment_type": "<one of: shirt, blouse, top, t-shirt, dress, skirt, pants, trousers, jeans, jacket, coat, blazer, hoodie, vest>", | |
| "description": "<brief description of the garment style, fit, and key features>", | |
| "measurements": { | |
| "bust": <number 75-130>, "waist": <number 55-110>, "hip": <number 80-130>, | |
| "shoulder_width": <number 35-55>, "bodice_length": <number 35-75>, | |
| "sleeve_length": <number 15-75>, "skirt_length": <number 30-120>, | |
| "pant_length": <number 30-110>, "neckline_depth": <number 3-25>, | |
| "neckline_width": <number 5-15>, "bicep": <number 25-45>, | |
| "wrist": <number 15-25>, "cap_height": <number 8-18>, | |
| "collar_height": <number 3-10>, "flare": <number 0-15> | |
| }, | |
| "features": { | |
| "has_collar": <true/false>, "collar_type": "<standard/mandarin/peter_pan/none>", | |
| "has_cuffs": <true/false>, "has_pockets": <true/false>, | |
| "pocket_type": "<patch/welt/none>", "has_hood": <true/false>, | |
| "fit": "<fitted/regular/oversized/loose>" | |
| } | |
| } | |
| Be precise. Estimate realistic measurements in cm for an average adult. | |
| Only include measurements relevant to the garment type. | |
| """ | |
| EDIT_PROMPT_TEMPLATE = """You are a fashion pattern editing assistant. The user wants to edit their garment pattern. | |
| Current pattern parameters: | |
| {current_json} | |
| User request: {user_message} | |
| Apply the edit and return ONLY the complete updated JSON (no markdown, no explanation) with the same structure. Keep all unchanged values the same. Only modify what the user asked to change. | |
| {{ | |
| "garment_type": "<type>", | |
| "description": "<updated description>", | |
| "measurements": {{ | |
| "bust": <number>, "waist": <number>, "hip": <number>, | |
| "shoulder_width": <number>, "bodice_length": <number>, | |
| "sleeve_length": <number>, "skirt_length": <number>, | |
| "pant_length": <number>, "neckline_depth": <number>, | |
| "neckline_width": <number>, "bicep": <number>, "wrist": <number>, | |
| "cap_height": <number>, "collar_height": <number>, "flare": <number> | |
| }}, | |
| "features": {{ | |
| "has_collar": <bool>, "collar_type": "<type>", | |
| "has_cuffs": <bool>, "has_pockets": <bool>, | |
| "pocket_type": "<type>", "has_hood": <bool>, | |
| "fit": "<type>" | |
| }} | |
| }}""" | |
| # Verified working VLMs (tested 2026-04-25) | |
| # Llama-4-Scout: confirmed image support, answers in content field | |
| # Kimi-K2.6: image support, answers in reasoning field | |
| # Qwen3.5-9B: image support unclear, answers in reasoning field | |
| VISION_MODELS = [ | |
| ("meta-llama/Llama-4-Scout-17B-16E-Instruct", "nscale", "Llama-4-Scout 17B"), | |
| ("moonshotai/Kimi-K2.6", "together", "Kimi K2.6"), | |
| ("Qwen/Qwen3.5-9B", "together", "Qwen 3.5 9B"), | |
| ] | |
| def _extract_response_text(message): | |
| content = message.get('content', '') or '' | |
| reasoning = message.get('reasoning', '') or '' | |
| return content.strip() or reasoning.strip() or '' | |
| def _extract_json_from_text(text): | |
| json_match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', text) | |
| if json_match: return json_match.group(1) | |
| json_match = re.search(r'\{[\s\S]*\}', text) | |
| if json_match: return json_match.group() | |
| return None | |
| def _call_vlm(messages, timeout=180): | |
| import requests | |
| hf_token = os.environ.get("HF_TOKEN", "") | |
| if not hf_token: return None | |
| for model_id, provider, display_name in VISION_MODELS: | |
| try: | |
| url = f"https://router.huggingface.co/{provider}/v1/chat/completions" | |
| headers = {"Authorization": f"Bearer {hf_token}", "Content-Type": "application/json"} | |
| payload = {"model": model_id, "messages": messages, "max_tokens": 2000, "temperature": 0.1} | |
| print(f"[VLM] Trying {display_name} via {provider}...") | |
| response = requests.post(url, headers=headers, json=payload, timeout=timeout) | |
| if response.status_code == 200: | |
| result = response.json() | |
| text = _extract_response_text(result['choices'][0]['message']) | |
| if not text: continue | |
| json_str = _extract_json_from_text(text) | |
| if not json_str: continue | |
| analysis = json.loads(json_str) | |
| analysis['_model_used'] = display_name | |
| print(f"[VLM] OK: {display_name} detected {analysis.get('garment_type','?')}") | |
| return analysis | |
| else: print(f"[VLM] {display_name}: HTTP {response.status_code} - {response.text[:200]}") | |
| except Exception as e: | |
| print(f"[VLM] {display_name} failed: {e}"); continue | |
| return None | |
| def analyze_with_vlm(image): | |
| import base64 | |
| from io import BytesIO | |
| hf_token = os.environ.get("HF_TOKEN", "") | |
| if not hf_token: return None | |
| max_dim = 1024 | |
| if max(image.size) > max_dim: | |
| ratio = max_dim / max(image.size) | |
| image = image.resize((int(image.size[0]*ratio), int(image.size[1]*ratio)), Image.LANCZOS) | |
| buf = BytesIO() | |
| image.convert('RGB').save(buf, format='JPEG', quality=85) | |
| img_b64 = base64.b64encode(buf.getvalue()).decode('utf-8') | |
| messages = [{"role": "user", "content": [ | |
| {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}}, | |
| {"type": "text", "text": GARMENT_ANALYSIS_PROMPT} | |
| ]}] | |
| return _call_vlm(messages) | |
| def get_default_analysis(garment_type="shirt"): | |
| defaults = { | |
| "shirt": {"garment_type":"shirt","description":"Standard button-up shirt","measurements":{"bust":96,"waist":80,"shoulder_width":44,"bodice_length":72,"sleeve_length":62,"neckline_depth":8,"neckline_width":7,"bicep":32,"wrist":18,"cap_height":14,"collar_height":4,"flare":0},"features":{"has_collar":True,"collar_type":"standard","has_cuffs":True,"has_pockets":True,"pocket_type":"patch","has_hood":False,"fit":"regular"}}, | |
| "dress": {"garment_type":"dress","description":"A-line dress","measurements":{"bust":90,"waist":72,"hip":96,"shoulder_width":40,"bodice_length":42,"sleeve_length":25,"skirt_length":55,"neckline_depth":12,"neckline_width":8,"bicep":28,"wrist":17,"cap_height":12,"flare":8},"features":{"has_collar":False,"collar_type":"none","has_cuffs":False,"has_pockets":False,"pocket_type":"none","has_hood":False,"fit":"fitted"}}, | |
| "pants": {"garment_type":"pants","description":"Straight-leg trousers","measurements":{"waist":78,"hip":98,"thigh":56,"knee":40,"ankle":26,"pant_length":100,"crotch_depth":27,"waistband_height":4,"flare":0},"features":{"has_pockets":True,"pocket_type":"welt","has_collar":False,"has_hood":False,"fit":"regular"}}, | |
| "skirt": {"garment_type":"skirt","description":"A-line knee skirt","measurements":{"waist":72,"hip":96,"skirt_length":55,"waistband_height":4,"flare":6},"features":{"has_pockets":False,"has_collar":False,"has_hood":False,"fit":"regular"}}, | |
| "jacket": {"garment_type":"jacket","description":"Tailored blazer","measurements":{"bust":100,"waist":86,"shoulder_width":46,"jacket_length":70,"sleeve_length":62,"neckline_depth":15,"neckline_width":9,"bicep":34,"wrist":20,"cap_height":15,"collar_height":6,"flare":0},"features":{"has_collar":True,"collar_type":"standard","has_cuffs":False,"has_pockets":True,"pocket_type":"welt","has_hood":False,"fit":"regular"}}, | |
| "hoodie": {"garment_type":"hoodie","description":"Pullover hoodie","measurements":{"bust":108,"waist":100,"shoulder_width":50,"jacket_length":68,"sleeve_length":65,"neckline_depth":10,"neckline_width":8,"bicep":36,"wrist":22,"cap_height":13,"head_circumference":57,"flare":0},"features":{"has_collar":False,"collar_type":"none","has_cuffs":True,"has_pockets":True,"pocket_type":"patch","has_hood":True,"fit":"oversized"}}, | |
| "vest": {"garment_type":"vest","description":"Classic vest","measurements":{"bust":96,"waist":80,"shoulder_width":42,"vest_length":55,"neckline_depth":18,"neckline_width":8,"flare":0},"features":{"has_collar":False,"has_cuffs":False,"has_pockets":False,"has_hood":False,"fit":"fitted"}}, | |
| } | |
| return defaults.get(garment_type, defaults["shirt"]) | |
| _current_analysis = {"data": None} | |
| def _generate_all_outputs(analysis): | |
| garment_type = analysis.get('garment_type', 'shirt') | |
| measurements = analysis.get('measurements', {}) | |
| features = analysis.get('features', {}) | |
| params = {**measurements, **features} | |
| pattern_pieces = get_pattern_pieces(garment_type, params) | |
| pattern_image, summary = generate_pattern_from_analysis(analysis) | |
| fig_3d = create_3d_figure(analysis, pattern_pieces=pattern_pieces) | |
| display = {k: v for k, v in analysis.items() if k != '_model_used'} | |
| model_info = f"\n\n*AI: {analysis.get('_model_used', 'Default')}*" if analysis.get('_model_used') else "" | |
| desc = analysis.get('description', 'No description') | |
| summary = f"**Garment:** {desc}\n\n{summary}{model_info}" | |
| return pattern_image, fig_3d, summary, json.dumps(display, indent=2) | |
| def process_image(image, garment_type_override="Auto-detect"): | |
| if image is None and garment_type_override == "Auto-detect": | |
| return None, None, "Please upload a garment image or select a type.", "{}", [] | |
| analysis = None | |
| if image is not None: | |
| try: analysis = analyze_with_vlm(image) | |
| except Exception as e: print(f"VLM failed: {e}") | |
| if analysis is None: | |
| gt = garment_type_override.lower() if garment_type_override != "Auto-detect" else "shirt" | |
| analysis = get_default_analysis(gt) | |
| if image is not None and garment_type_override == "Auto-detect": | |
| analysis['_model_used'] = 'Default (set HF_TOKEN for AI)' | |
| if garment_type_override != "Auto-detect": | |
| analysis['garment_type'] = garment_type_override.lower() | |
| _current_analysis["data"] = copy.deepcopy(analysis) | |
| try: | |
| p2d, p3d, summary, j = _generate_all_outputs(analysis) | |
| return p2d, p3d, summary, j, [] | |
| except Exception as e: | |
| traceback.print_exc(); return None, None, f"Error: {e}", "{}", [] | |
| def process_text(description): | |
| if not description.strip(): return None, None, "Enter a description.", "{}", [] | |
| analysis = None | |
| hf_token = os.environ.get("HF_TOKEN", "") | |
| if hf_token: | |
| messages = [{"role": "user", "content": f"Based on this garment description, extract sewing pattern parameters.\n\nDescription: {description}\n\nReturn ONLY JSON with: garment_type, description, measurements (bust, waist, hip, shoulder_width, bodice_length, sleeve_length, skirt_length, pant_length, neckline_depth, neckline_width, bicep, wrist, cap_height, collar_height, flare), features (has_collar, collar_type, has_cuffs, has_pockets, pocket_type, has_hood, fit)."}] | |
| analysis = _call_vlm(messages, timeout=90) | |
| if analysis is None: | |
| desc_lower = description.lower() | |
| for gt in ['hoodie','jacket','coat','blazer','dress','skirt','pants','trousers','jeans','vest','shirt','blouse','top']: | |
| if gt in desc_lower: analysis = get_default_analysis(gt); analysis['description'] = description; break | |
| if analysis is None: analysis = get_default_analysis("shirt"); analysis['description'] = description | |
| _current_analysis["data"] = copy.deepcopy(analysis) | |
| try: | |
| p2d, p3d, summary, j = _generate_all_outputs(analysis) | |
| return p2d, p3d, summary, j, [] | |
| except Exception as e: return None, None, f"Error: {e}", "{}", [] | |
| def process_manual(gt,bust,waist,hip,shoulder,bodice,sleeve,skirt,pant,neck,flare_c,collar,ctype,cuffs,pockets,hood,fit): | |
| analysis = {"garment_type":gt.lower(),"description":f"Custom {gt.lower()}","measurements":{"bust":bust,"waist":waist,"hip":hip,"shoulder_width":shoulder,"bodice_length":bodice,"sleeve_length":sleeve,"skirt_length":skirt,"pant_length":pant,"neckline_depth":neck,"neckline_width":7,"bicep":30,"wrist":18,"cap_height":14,"collar_height":5,"flare":flare_c},"features":{"has_collar":collar,"collar_type":ctype.lower(),"has_cuffs":cuffs,"has_pockets":pockets,"pocket_type":"patch","has_hood":hood,"fit":fit.lower()}} | |
| _current_analysis["data"] = copy.deepcopy(analysis) | |
| try: | |
| p2d, p3d, summary, j = _generate_all_outputs(analysis) | |
| return p2d, p3d, summary, j, [] | |
| except Exception as e: return None, None, f"Error: {e}", "{}", [] | |
| def chat_edit(message, history): | |
| if not message.strip(): return history, None, None, "Please enter an edit request.", "{}" | |
| current = _current_analysis.get("data") or get_default_analysis("shirt") | |
| _current_analysis["data"] = current | |
| current_clean = {k: v for k, v in current.items() if k != '_model_used'} | |
| edit_prompt = EDIT_PROMPT_TEMPLATE.format(current_json=json.dumps(current_clean, indent=2), user_message=message) | |
| updated = None | |
| if os.environ.get("HF_TOKEN", ""): | |
| try: updated = _call_vlm([{"role": "user", "content": edit_prompt}], timeout=90) | |
| except: pass | |
| if updated is None: | |
| updated = copy.deepcopy(current); msg_lower = message.lower() | |
| if "long sleeve" in msg_lower: updated['measurements']['sleeve_length'] = 65 | |
| elif "short sleeve" in msg_lower: updated['measurements']['sleeve_length'] = 25 | |
| if "no collar" in msg_lower: updated['features']['has_collar'] = False; updated['features']['collar_type'] = 'none' | |
| if "add collar" in msg_lower: updated['features']['has_collar'] = True; updated['features']['collar_type'] = 'standard' | |
| if "add hood" in msg_lower: updated['features']['has_hood'] = True | |
| if "no hood" in msg_lower: updated['features']['has_hood'] = False | |
| if "oversized" in msg_lower: updated['features']['fit'] = 'oversized'; updated['measurements']['bust'] = updated['measurements'].get('bust', 96) + 10 | |
| if "fitted" in msg_lower: updated['features']['fit'] = 'fitted' | |
| if "flare" in msg_lower: updated['measurements']['flare'] = max(updated['measurements'].get('flare', 0), 8) | |
| updated['_model_used'] = 'Rule-based edit' | |
| if 'garment_type' not in updated: updated['garment_type'] = current.get('garment_type', 'shirt') | |
| _current_analysis["data"] = copy.deepcopy(updated) | |
| try: p2d, p3d, summary, j = _generate_all_outputs(updated) | |
| except Exception as e: p2d, p3d, summary, j = None, None, f"Error: {e}", "{}" | |
| ai_msg = f"Applied: {message}\n" | |
| changes = [] | |
| for k in set(list(current.get('measurements',{}).keys()) + list(updated.get('measurements',{}).keys())): | |
| ov, nv = current.get('measurements',{}).get(k), updated.get('measurements',{}).get(k) | |
| if ov != nv and ov is not None and nv is not None: changes.append(f" {k}: {ov} → {nv}") | |
| for k in set(list(current.get('features',{}).keys()) + list(updated.get('features',{}).keys())): | |
| ov, nv = current.get('features',{}).get(k), updated.get('features',{}).get(k) | |
| if ov != nv: changes.append(f" {k}: {ov} → {nv}") | |
| ai_msg += ("\n".join(changes)) if changes else "No changes." | |
| history = history or []; history.append((message, ai_msg)) | |
| return history, p2d, p3d, summary, j | |
| def run_refinement(image, garment_type_override, max_iters): | |
| if image is None: | |
| yield None, None, None, "Please upload a garment image.", "{}", None; return | |
| analysis = None | |
| try: analysis = analyze_with_vlm(image) | |
| except Exception as e: print(f"VLM failed: {e}") | |
| if analysis is None: | |
| gt = garment_type_override.lower() if garment_type_override != "Auto-detect" else "shirt" | |
| analysis = get_default_analysis(gt) | |
| if garment_type_override != "Auto-detect": analysis['garment_type'] = garment_type_override.lower() | |
| def gen_fn(a): return _generate_all_outputs(a) | |
| result = refinement_loop(original_image=image, initial_analysis=analysis, generate_fn=gen_fn, | |
| max_iterations=int(max_iters), target_composite=0.82, plateau_patience=3, lr=0.7) | |
| log_lines = [f"## Refinement Results\n", f"**Converged:** {'✅ Yes' if result['converged'] else '❌ No'}", | |
| f"**Iterations:** {result['total_iterations']}", f"**Best Score:** {result['best_score']:.4f}"] | |
| if result['scores']: log_lines.append(f"**Scores:** {' → '.join(f'{s:.3f}' for s in result['scores'])}") | |
| log_lines.append("") | |
| for step in result['history']: | |
| it, status, metrics = step['iteration'], step.get('status','?'), step.get('metrics',{}) | |
| log_lines.append(f"### Iteration {it} — {status}") | |
| if metrics: log_lines.append(f"SSIM={metrics.get('ssim',0):.3f} | Edge={metrics.get('edge_ssim',0):.3f} | Composite={metrics.get('composite',0):.3f}") | |
| if step.get('new_best'): log_lines.append("⭐ **New best!**") | |
| diffs = step.get('vlm_differences', []) | |
| if diffs: log_lines.append("**Differences:** " + "; ".join(diffs[:3])) | |
| adj = step.get('adjustments', {}) | |
| if adj: log_lines.append("**Adjustments:** " + ", ".join(f"{k}={v}" for k, v in adj.items())) | |
| reason = step.get('reason', '') | |
| if reason: log_lines.append(f"*{reason}*") | |
| log_lines.append("") | |
| best = result['best_analysis']; _current_analysis["data"] = copy.deepcopy(best) | |
| try: p2d, p3d, summary, j = _generate_all_outputs(best) | |
| except: p2d, p3d, summary, j = None, None, "Error", "{}" | |
| last_proj = None | |
| for step in reversed(result['history']): | |
| if 'projection' in step: last_proj = step['projection']; break | |
| yield p2d, p3d, last_proj, "\n".join(log_lines), j, summary | |
| CSS = """ | |
| .main-header { text-align: center; margin-bottom: 20px; } | |
| .info-box { padding: 15px; border-radius: 10px; background: #f0f7ff; border: 1px solid #cce0ff; margin: 10px 0; } | |
| .ref-box { padding: 10px; border-radius: 8px; background: #f8f8f8; border: 1px solid #e0e0e0; font-size: 0.85em; } | |
| """ | |
| with gr.Blocks(title="Garment Pattern Studio") as demo: | |
| gr.HTML("""<div class="main-header"><h1>🧵 Garment Pattern Studio</h1> | |
| <p style="font-size:1.1em;color:#555;">Analyze garments, edit with chat, preview in 3D, refine with AI agent</p></div> | |
| <div class="info-box"><b>Powered by:</b> Llama-4-Scout · Kimi K2.6 · Qwen 3.5 via | |
| <a href="https://huggingface.co/docs/inference-providers">HF Inference Providers</a> | |
| | <b>3D view built from actual 2D pattern pieces</b></div>""") | |
| with gr.Tab("📸 From Image"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| input_image = gr.Image(type="pil", label="Upload Garment Image", height=350) | |
| garment_override = gr.Dropdown(choices=["Auto-detect","Shirt","Dress","Skirt","Pants","Jacket","Hoodie","Vest"], value="Auto-detect", label="Garment Type Override") | |
| analyze_btn = gr.Button("Analyze & Generate", variant="primary", size="lg") | |
| with gr.Column(scale=2): | |
| with gr.Row(): | |
| with gr.Column(): out_pattern_2d = gr.Image(label="2D Sewing Pattern", height=400) | |
| with gr.Column(): out_3d = gr.Plot(label="3D Garment Preview") | |
| out_summary = gr.Markdown(label="Pattern Summary") | |
| with gr.Accordion("Raw JSON", open=False): out_json = gr.Code(language="json") | |
| analyze_btn.click(process_image, inputs=[input_image, garment_override], outputs=[out_pattern_2d, out_3d, out_summary, out_json]) | |
| with gr.Tab("✍️ From Text"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| text_input = gr.Textbox(label="Describe the garment", placeholder="e.g., A fitted A-line dress with cap sleeves", lines=3) | |
| text_btn = gr.Button("Generate Pattern", variant="primary", size="lg") | |
| gr.Examples(examples=[["A classic dress shirt with long sleeves and button-down collar"],["A flared midi skirt with high waist"],["An oversized hoodie with kangaroo pocket"],["A fitted blazer with notched lapel collar"],["Slim-fit straight-leg jeans with pockets"],["A knee-length A-line dress with cap sleeves"]], inputs=text_input) | |
| with gr.Column(scale=2): | |
| with gr.Row(): | |
| with gr.Column(): txt_pattern_2d = gr.Image(label="2D Pattern", height=400) | |
| with gr.Column(): txt_3d = gr.Plot(label="3D Preview") | |
| txt_summary = gr.Markdown() | |
| with gr.Accordion("Raw JSON", open=False): txt_json = gr.Code(language="json") | |
| text_btn.click(process_text, inputs=[text_input], outputs=[txt_pattern_2d, txt_3d, txt_summary, txt_json]) | |
| with gr.Tab("📐 Manual"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| m_type = gr.Dropdown(choices=["Shirt","Dress","Skirt","Pants","Jacket","Hoodie","Vest"], value="Shirt", label="Garment Type") | |
| gr.Markdown("### Measurements (cm)") | |
| with gr.Row(): m_bust = gr.Slider(70,130,value=92,step=1,label="Bust"); m_waist = gr.Slider(55,110,value=74,step=1,label="Waist") | |
| with gr.Row(): m_hip = gr.Slider(75,130,value=96,step=1,label="Hip"); m_shoulder = gr.Slider(35,55,value=42,step=1,label="Shoulder") | |
| with gr.Row(): m_bodice = gr.Slider(30,80,value=42,step=1,label="Bodice Length"); m_sleeve = gr.Slider(10,75,value=60,step=1,label="Sleeve Length") | |
| with gr.Row(): m_skirt = gr.Slider(25,120,value=55,step=1,label="Skirt Length"); m_pant = gr.Slider(25,115,value=100,step=1,label="Pant Length") | |
| with gr.Row(): m_neck = gr.Slider(3,25,value=8,step=1,label="Neckline Depth"); m_flare = gr.Slider(0,20,value=0,step=1,label="Hem Flare") | |
| gr.Markdown("### Features") | |
| with gr.Row(): m_collar = gr.Checkbox(value=True,label="Collar"); m_ctype = gr.Dropdown(["Standard","Mandarin","Peter_pan"],value="Standard",label="Collar Type") | |
| with gr.Row(): m_cuffs = gr.Checkbox(value=True,label="Cuffs"); m_pockets = gr.Checkbox(value=False,label="Pockets") | |
| with gr.Row(): m_hood = gr.Checkbox(value=False,label="Hood"); m_fit = gr.Dropdown(["Fitted","Regular","Oversized","Loose"],value="Regular",label="Fit") | |
| manual_btn = gr.Button("Generate Pattern", variant="primary", size="lg") | |
| with gr.Column(scale=2): | |
| with gr.Row(): | |
| with gr.Column(): man_pattern_2d = gr.Image(label="2D Pattern", height=400) | |
| with gr.Column(): man_3d = gr.Plot(label="3D Preview") | |
| man_summary = gr.Markdown() | |
| with gr.Accordion("Raw JSON", open=False): man_json = gr.Code(language="json") | |
| 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]) | |
| with gr.Tab("💬 Chat & Edit"): | |
| gr.Markdown("### Edit pattern with natural language\nGenerate a pattern first, then refine here.") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| chatbot = gr.Chatbot(label="Pattern Editor", height=400) | |
| chat_input = gr.Textbox(label="Edit instruction", placeholder="e.g., Make sleeves longer, Add a hood", lines=2) | |
| with gr.Row(): chat_send = gr.Button("Apply Edit", variant="primary"); chat_clear = gr.Button("Clear") | |
| with gr.Column(scale=2): | |
| with gr.Row(): | |
| with gr.Column(): chat_pattern_2d = gr.Image(label="Updated 2D", height=400) | |
| with gr.Column(): chat_3d = gr.Plot(label="Updated 3D") | |
| chat_summary = gr.Markdown() | |
| with gr.Accordion("JSON", open=False): chat_json = gr.Code(language="json") | |
| def clear_chat(): return [], None, None, "", "{}" | |
| chat_send.click(chat_edit, inputs=[chat_input, chatbot], outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json]) | |
| chat_input.submit(chat_edit, inputs=[chat_input, chatbot], outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json]) | |
| chat_clear.click(clear_chat, outputs=[chatbot, chat_pattern_2d, chat_3d, chat_summary, chat_json]) | |
| with gr.Tab("🔄 Agentic Refinement"): | |
| gr.Markdown("""### ⚠️ Work In Progress — Iterative Refinement Loop | |
| Upload a garment image. The AI agent will iteratively refine pattern parameters. | |
| See [README](https://huggingface.co/spaces/vikashmakeit/garment-to-pattern) for full design docs. | |
| **Status:** Core components (projection, similarity, convergence loop) work. VLM feedback integration needs further testing.""") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| refine_image = gr.Image(type="pil", label="Upload Garment Image", height=300) | |
| refine_type = gr.Dropdown(choices=["Auto-detect","Shirt","Dress","Skirt","Pants","Jacket","Hoodie","Vest"], value="Auto-detect", label="Garment Type") | |
| refine_iters = gr.Slider(1, 15, value=5, step=1, label="Max Iterations") | |
| refine_btn = gr.Button("🚀 Start Refinement", variant="primary", size="lg") | |
| with gr.Column(scale=2): | |
| with gr.Row(): | |
| with gr.Column(): refine_2d = gr.Image(label="Best 2D Pattern", height=350) | |
| with gr.Column(): refine_proj = gr.Image(label="3D→2D Projection", height=350) | |
| with gr.Row(): | |
| with gr.Column(): refine_3d = gr.Plot(label="Best 3D Preview") | |
| with gr.Column(): refine_log = gr.Markdown(label="Refinement Log") | |
| refine_summary = gr.Markdown() | |
| with gr.Accordion("Best Parameters JSON", open=False): refine_json = gr.Code(language="json") | |
| refine_btn.click(run_refinement, inputs=[refine_image, refine_type, refine_iters], | |
| outputs=[refine_2d, refine_3d, refine_proj, refine_log, refine_json, refine_summary]) | |
| gr.HTML("""<div class="ref-box" style="margin-top:20px;"><h4>Research References</h4><ul> | |
| <li><b>ChatGarment</b> (2024) [<a href="https://arxiv.org/abs/2412.17811">Paper</a>]</li> | |
| <li><b>NGL-Prompter</b> (2025) [<a href="https://arxiv.org/abs/2602.20700">Paper</a>]</li> | |
| <li><b>RRVF</b> (2025) — Render-compare visual feedback [<a href="https://arxiv.org/abs/2507.20766">Paper</a>]</li> | |
| <li><b>SceneAssistant</b> (2026) — Agentic VLM refinement [<a href="https://arxiv.org/abs/2603.12238">Paper</a>]</li></ul></div>""") | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860, css=CSS, theme=gr.themes.Soft()) | |