Spaces:
Runtime error
Runtime error
| """ | |
| Naari Studio - Virtual Try-On Application | |
| HuggingFace Spaces compatible Gradio interface. | |
| Features: | |
| - Garment Virtual Try-On (placeholder for IDM-VTON integration) | |
| - Jewelry Virtual Try-On (Necklace, Earrings, Maang Tikka, Nose Ring, Bangles, Rings) | |
| - AI-Powered Jewelry Generation via Replicate trained model | |
| Powered by: | |
| - cvzone PoseModule and MediaPipe Face Mesh for accurate landmark detection | |
| - Replicate trained model (ganeshgowri-asa/naari-jewelry-vton:f6b844b4) for AI generation | |
| """ | |
| import gradio as gr | |
| from PIL import Image | |
| import numpy as np | |
| from typing import Optional, Tuple, Dict, Any | |
| import logging | |
| import os | |
| import json | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # HuggingFace Spaces GPU decorator | |
| try: | |
| import spaces | |
| SPACES_AVAILABLE = True | |
| logger.info("HuggingFace Spaces module loaded") | |
| except ImportError: | |
| SPACES_AVAILABLE = False | |
| logger.warning("HuggingFace Spaces module not available - running without @spaces.GPU decorator") | |
| # Create a dummy decorator that does nothing | |
| class spaces: | |
| def GPU(duration=60): | |
| def decorator(func): | |
| return func | |
| return decorator | |
| from jewelry_engine import ( | |
| apply_jewelry, | |
| remove_jewelry_background, | |
| jewelry_tryon_api, | |
| get_available_options, | |
| get_generation_engine, | |
| JEWELRY_TYPES, | |
| METAL_TYPES, | |
| STONE_TYPES, | |
| STYLE_OPTIONS, | |
| REPLICATE_AVAILABLE | |
| ) | |
| # Import the new Jewelry VTON Model for realistic try-on | |
| from jewelry_vton_model import ( | |
| jewelry_vton, | |
| check_vton_availability, | |
| JewelryType, | |
| JewelryVTONModel | |
| ) | |
| # ============================================================================ | |
| # IMAGE PROCESSING UTILITIES | |
| # ============================================================================ | |
| MAX_IMAGE_DIMENSION = 4096 | |
| def resize_image_if_needed(image: Optional[np.ndarray], max_dim: int = MAX_IMAGE_DIMENSION) -> Optional[np.ndarray]: | |
| """ | |
| Resize image if any dimension exceeds max_dim, maintaining aspect ratio. | |
| Args: | |
| image: Input image as numpy array (RGB/RGBA) | |
| max_dim: Maximum allowed dimension (default 4096) | |
| Returns: | |
| Resized image as numpy array, or original if no resize needed | |
| """ | |
| if image is None: | |
| return None | |
| height, width = image.shape[:2] | |
| # Check if resize is needed | |
| if width <= max_dim and height <= max_dim: | |
| return image | |
| # Calculate new dimensions maintaining aspect ratio | |
| if width > height: | |
| new_width = max_dim | |
| new_height = int(height * (max_dim / width)) | |
| else: | |
| new_height = max_dim | |
| new_width = int(width * (max_dim / height)) | |
| logger.info(f"Resizing image from {width}x{height} to {new_width}x{new_height} (max dimension: {max_dim})") | |
| # Convert to PIL, resize with LANCZOS, convert back | |
| pil_image = Image.fromarray(image) | |
| resized_pil = pil_image.resize((new_width, new_height), Image.Resampling.LANCZOS) | |
| return np.array(resized_pil) | |
| # Theme configuration | |
| THEME = gr.themes.Soft( | |
| primary_hue="purple", | |
| secondary_hue="pink", | |
| neutral_hue="slate", | |
| ) | |
| # CSS for better styling | |
| CSS = """ | |
| .gradio-container { | |
| max-width: 1400px !important; | |
| margin: auto !important; | |
| } | |
| .tab-nav button { | |
| font-size: 16px !important; | |
| font-weight: 600 !important; | |
| } | |
| .result-image { | |
| min-height: 400px; | |
| } | |
| .main-tabs > .tab-nav { | |
| background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); | |
| border-radius: 10px 10px 0 0; | |
| padding: 5px; | |
| } | |
| .main-tabs > .tab-nav button { | |
| color: white !important; | |
| font-size: 18px !important; | |
| } | |
| .main-tabs > .tab-nav button.selected { | |
| background: rgba(255,255,255,0.2) !important; | |
| border-radius: 5px; | |
| } | |
| .jewelry-section { | |
| border: 2px solid #e0e0e0; | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin: 10px 0; | |
| } | |
| footer { | |
| visibility: hidden; | |
| } | |
| """ | |
| # ============================================================================ | |
| # JEWELRY TRY-ON FUNCTIONS | |
| # ============================================================================ | |
| def process_necklace(person_image: Optional[np.ndarray], | |
| jewelry_image: Optional[np.ndarray], | |
| opacity: float) -> Tuple[Optional[np.ndarray], str]: | |
| """Process necklace try-on request with GPU acceleration.""" | |
| if person_image is None: | |
| return None, "Please upload a person photo." | |
| if jewelry_image is None: | |
| return None, "Please upload a necklace image." | |
| # Resize images if needed to prevent "image too large" errors | |
| person_image = resize_image_if_needed(person_image) | |
| jewelry_image = resize_image_if_needed(jewelry_image) | |
| # Convert numpy to PIL | |
| person_pil = Image.fromarray(person_image) | |
| jewelry_pil = Image.fromarray(jewelry_image) | |
| # Apply jewelry | |
| result, message = apply_jewelry(person_pil, jewelry_pil, "necklace", opacity) | |
| if result is not None: | |
| return np.array(result.convert('RGB')), message | |
| return None, message | |
| def process_earrings(person_image: Optional[np.ndarray], | |
| jewelry_image: Optional[np.ndarray], | |
| opacity: float) -> Tuple[Optional[np.ndarray], str]: | |
| """Process earrings try-on request with GPU acceleration.""" | |
| if person_image is None: | |
| return None, "Please upload a person photo." | |
| if jewelry_image is None: | |
| return None, "Please upload an earring image." | |
| # Resize images if needed to prevent "image too large" errors | |
| person_image = resize_image_if_needed(person_image) | |
| jewelry_image = resize_image_if_needed(jewelry_image) | |
| person_pil = Image.fromarray(person_image) | |
| jewelry_pil = Image.fromarray(jewelry_image) | |
| result, message = apply_jewelry(person_pil, jewelry_pil, "earrings", opacity) | |
| if result is not None: | |
| return np.array(result.convert('RGB')), message | |
| return None, message | |
| def process_maang_tikka(person_image: Optional[np.ndarray], | |
| jewelry_image: Optional[np.ndarray], | |
| opacity: float) -> Tuple[Optional[np.ndarray], str]: | |
| """Process maang tikka try-on request with GPU acceleration.""" | |
| if person_image is None: | |
| return None, "Please upload a person photo." | |
| if jewelry_image is None: | |
| return None, "Please upload a maang tikka image." | |
| # Resize images if needed to prevent "image too large" errors | |
| person_image = resize_image_if_needed(person_image) | |
| jewelry_image = resize_image_if_needed(jewelry_image) | |
| person_pil = Image.fromarray(person_image) | |
| jewelry_pil = Image.fromarray(jewelry_image) | |
| result, message = apply_jewelry(person_pil, jewelry_pil, "maang_tikka", opacity) | |
| if result is not None: | |
| return np.array(result.convert('RGB')), message | |
| return None, message | |
| def process_nose_ring(person_image: Optional[np.ndarray], | |
| jewelry_image: Optional[np.ndarray], | |
| opacity: float, | |
| side: str, | |
| style: str) -> Tuple[Optional[np.ndarray], str]: | |
| """Process nose ring try-on request with GPU acceleration.""" | |
| if person_image is None: | |
| return None, "Please upload a person photo." | |
| if jewelry_image is None: | |
| return None, "Please upload a nose ring image." | |
| # Resize images if needed to prevent "image too large" errors | |
| person_image = resize_image_if_needed(person_image) | |
| jewelry_image = resize_image_if_needed(jewelry_image) | |
| person_pil = Image.fromarray(person_image) | |
| jewelry_pil = Image.fromarray(jewelry_image) | |
| result, message = apply_jewelry(person_pil, jewelry_pil, "nose_ring", opacity, | |
| side=side, ring_style=style) | |
| if result is not None: | |
| return np.array(result.convert('RGB')), message | |
| return None, message | |
| def remove_background(image: Optional[np.ndarray]) -> Optional[np.ndarray]: | |
| """Remove background from jewelry image using rembg with GPU acceleration.""" | |
| if image is None: | |
| return None | |
| pil_image = Image.fromarray(image) | |
| result = remove_jewelry_background(pil_image) | |
| return np.array(result.convert('RGBA')) | |
| # ============================================================================ | |
| # AI-POWERED JEWELRY TRY-ON (Replicate Model) | |
| # ============================================================================ | |
| def process_ai_jewelry_tryon( | |
| person_image: Optional[np.ndarray], | |
| jewelry_prompt: str, | |
| jewelry_type: str, | |
| metal_type: str, | |
| stones: str, | |
| style: str, | |
| opacity: float | |
| ) -> Tuple[Optional[np.ndarray], str]: | |
| """ | |
| Process AI-powered jewelry try-on using the trained Replicate model. | |
| Args: | |
| person_image: Person photo as numpy array | |
| jewelry_prompt: Text prompt for jewelry generation | |
| jewelry_type: Type of jewelry (necklace, earrings, etc.) | |
| metal_type: Metal type (gold, silver, etc.) | |
| stones: Stone type (diamond, ruby, etc.) | |
| style: Style variant | |
| opacity: Overlay opacity | |
| Returns: | |
| Tuple of (result image, status message) | |
| """ | |
| if person_image is None: | |
| return None, "Please upload a person photo." | |
| if not jewelry_prompt or jewelry_prompt.strip() == "": | |
| jewelry_prompt = "beautiful jewelry" | |
| # Resize image if needed | |
| person_image = resize_image_if_needed(person_image) | |
| try: | |
| # Call the jewelry try-on API | |
| result = jewelry_tryon_api( | |
| person_image=person_image, | |
| jewelry_prompt=jewelry_prompt, | |
| jewelry_type=jewelry_type, | |
| metal_type=metal_type, | |
| stones=stones, | |
| style=style if style else None, | |
| opacity=opacity | |
| ) | |
| if result["success"] and result["image"] is not None: | |
| result_array = np.array(result["image"].convert('RGB')) | |
| return result_array, result["message"] | |
| else: | |
| return None, result["message"] | |
| except Exception as e: | |
| logger.error(f"AI jewelry try-on error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None, f"Error: {str(e)}" | |
| def get_styles_for_jewelry_type(jewelry_type: str) -> list: | |
| """Get available styles for a given jewelry type.""" | |
| jewelry_type = jewelry_type.lower().replace(" ", "_").replace("-", "_") | |
| return STYLE_OPTIONS.get(jewelry_type, ["default"]) | |
| # ============================================================================ | |
| # REALISTIC JEWELRY VTON (Person + Jewelry Image → Realistic Output) | |
| # ============================================================================ | |
| def process_jewelry_vton( | |
| person_image: Optional[np.ndarray], | |
| jewelry_image: Optional[np.ndarray], | |
| jewelry_type: str, | |
| metal_type: str, | |
| style: str, | |
| custom_prompt: str, | |
| strength: float | |
| ) -> Tuple[Optional[np.ndarray], str]: | |
| """ | |
| Process realistic jewelry virtual try-on using the trained Replicate model. | |
| This takes a person image AND a jewelry reference image, then uses AI to | |
| realistically composite the jewelry onto the person - similar to how | |
| IDM-VTON works for garments. | |
| Args: | |
| person_image: Person photo as numpy array | |
| jewelry_image: Reference jewelry image to apply | |
| jewelry_type: Type of jewelry (necklace, earrings, etc.) | |
| metal_type: Metal type (gold, silver, etc.) | |
| style: Style description | |
| custom_prompt: Additional prompt text | |
| strength: Transformation strength (0.0-1.0) | |
| Returns: | |
| Tuple of (result image, status message) | |
| """ | |
| if person_image is None: | |
| return None, "Please upload a person photo." | |
| if jewelry_image is None: | |
| return None, "Please upload a jewelry reference image." | |
| # Check VTON model availability | |
| vton_status = check_vton_availability() | |
| if not vton_status["available"]: | |
| if not vton_status["replicate_installed"]: | |
| return None, "Error: Replicate package not installed. Install with: pip install replicate" | |
| if not vton_status["api_token_set"]: | |
| return None, "Error: REPLICATE_API_TOKEN environment variable not set. Please configure your API token." | |
| return None, "Error: VTON model not available." | |
| # Resize images if needed | |
| person_image = resize_image_if_needed(person_image) | |
| jewelry_image = resize_image_if_needed(jewelry_image) | |
| try: | |
| # Convert to PIL for the VTON model | |
| person_pil = Image.fromarray(person_image) | |
| jewelry_pil = Image.fromarray(jewelry_image) | |
| # Run the VTON model | |
| result_pil, message = jewelry_vton( | |
| person_image=person_pil, | |
| jewelry_image=jewelry_pil, | |
| jewelry_type=jewelry_type, | |
| metal_type=metal_type, | |
| style=style, | |
| custom_prompt=custom_prompt, | |
| strength=strength | |
| ) | |
| if result_pil is not None: | |
| result_array = np.array(result_pil.convert('RGB')) | |
| return result_array, message | |
| else: | |
| return None, message | |
| except Exception as e: | |
| logger.error(f"Jewelry VTON error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None, f"Error: {str(e)}" | |
| def update_style_dropdown(jewelry_type: str): | |
| """Update style dropdown choices based on jewelry type selection.""" | |
| styles = get_styles_for_jewelry_type(jewelry_type) | |
| return gr.Dropdown(choices=styles, value=styles[0] if styles else "default") | |
| # ============================================================================ | |
| # API ENDPOINT FUNCTION (for programmatic access) | |
| # ============================================================================ | |
| def api_jewelry_tryon( | |
| person_image_path: str, | |
| jewelry_prompt: str, | |
| jewelry_type: str = "necklace", | |
| metal_type: str = "gold", | |
| stones: str = "none", | |
| style: str = None | |
| ) -> Dict[str, Any]: | |
| """ | |
| API endpoint for jewelry try-on. | |
| This function provides a programmatic interface for the /api/jewelry-tryon endpoint. | |
| It can be called via Gradio's API mode. | |
| Args: | |
| person_image_path: Path to person image file | |
| jewelry_prompt: Text description of desired jewelry | |
| jewelry_type: Type of jewelry (necklace, earrings, bangles, rings, maang_tikka, nose_ring) | |
| metal_type: Metal type (gold, silver, rose gold, platinum, oxidized silver, antique gold) | |
| stones: Stone type (diamond, ruby, emerald, sapphire, pearl, kundan, polki, none) | |
| style: Style variant (depends on jewelry type) | |
| Returns: | |
| Dictionary with success status, result image path, and message | |
| """ | |
| try: | |
| result = jewelry_tryon_api( | |
| person_image=person_image_path, | |
| jewelry_prompt=jewelry_prompt, | |
| jewelry_type=jewelry_type, | |
| metal_type=metal_type, | |
| stones=stones, | |
| style=style | |
| ) | |
| # Convert PIL image to numpy for Gradio | |
| if result["success"] and result["image"]: | |
| return { | |
| "success": True, | |
| "image": np.array(result["image"].convert('RGB')), | |
| "message": result["message"], | |
| "prompt_used": result["prompt_used"] | |
| } | |
| else: | |
| return { | |
| "success": False, | |
| "image": None, | |
| "message": result["message"], | |
| "prompt_used": result.get("prompt_used", "") | |
| } | |
| except Exception as e: | |
| logger.error(f"API endpoint error: {e}") | |
| return { | |
| "success": False, | |
| "image": None, | |
| "message": f"Error: {str(e)}", | |
| "prompt_used": "" | |
| } | |
| # ============================================================================ | |
| # GARMENT TRY-ON FUNCTIONS (Placeholder for IDM-VTON integration) | |
| # ============================================================================ | |
| def process_garment_tryon(person_image: Optional[np.ndarray], | |
| garment_image: Optional[np.ndarray], | |
| garment_type: str, | |
| denoise_steps: int, | |
| seed: int) -> Tuple[Optional[np.ndarray], str]: | |
| """ | |
| Process garment virtual try-on request. | |
| This is a placeholder for IDM-VTON integration. | |
| In production, this would call the IDM-VTON model for garment try-on. | |
| """ | |
| if person_image is None: | |
| return None, "Please upload a person photo." | |
| if garment_image is None: | |
| return None, "Please upload a garment image." | |
| # Resize images if needed to prevent "image too large" errors | |
| person_image = resize_image_if_needed(person_image) | |
| garment_image = resize_image_if_needed(garment_image) | |
| # Placeholder response - replace with actual IDM-VTON integration | |
| return None, "Garment try-on is coming soon! This feature requires IDM-VTON model integration." | |
| # ============================================================================ | |
| # UI COMPONENTS | |
| # ============================================================================ | |
| def create_jewelry_tab(jewelry_type: str, | |
| jewelry_label: str, | |
| description: str, | |
| process_fn, | |
| show_side: bool = False, | |
| show_style: bool = False): | |
| """Create a jewelry try-on tab with consistent layout.""" | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown(f"### {description}") | |
| person_input = gr.Image( | |
| label="Person Photo", | |
| type="numpy", | |
| sources=["upload", "webcam"], | |
| height=300 | |
| ) | |
| jewelry_input = gr.Image( | |
| label=f"{jewelry_label} Image", | |
| type="numpy", | |
| sources=["upload"], | |
| height=300 | |
| ) | |
| with gr.Row(): | |
| remove_bg_btn = gr.Button("Remove Background", variant="secondary", size="sm") | |
| opacity_slider = gr.Slider( | |
| minimum=0.1, | |
| maximum=1.0, | |
| value=1.0, | |
| step=0.1, | |
| label="Opacity" | |
| ) | |
| # Optional side selector for nose rings | |
| side_dropdown = None | |
| style_dropdown = None | |
| if show_side: | |
| side_dropdown = gr.Dropdown( | |
| choices=["left", "right", "septum"], | |
| value="left", | |
| label="Placement Side" | |
| ) | |
| if show_style: | |
| style_dropdown = gr.Dropdown( | |
| choices=["stud", "hoop", "nath"], | |
| value="stud", | |
| label="Ring Style" | |
| ) | |
| try_on_btn = gr.Button(f"Try On {jewelry_label}", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| output_image = gr.Image( | |
| label="Result", | |
| type="numpy", | |
| height=500, | |
| elem_classes="result-image" | |
| ) | |
| status_text = gr.Textbox( | |
| label="Status", | |
| interactive=False, | |
| lines=2 | |
| ) | |
| # Connect the remove background button | |
| remove_bg_btn.click( | |
| fn=remove_background, | |
| inputs=[jewelry_input], | |
| outputs=[jewelry_input] | |
| ) | |
| # Connect the try-on button based on available options | |
| if show_side and show_style: | |
| try_on_btn.click( | |
| fn=process_fn, | |
| inputs=[person_input, jewelry_input, opacity_slider, side_dropdown, style_dropdown], | |
| outputs=[output_image, status_text] | |
| ) | |
| elif show_side: | |
| # Wrap function to add default style | |
| def wrapped_fn(person, jewelry, opacity, side): | |
| return process_fn(person, jewelry, opacity, side, "stud") | |
| try_on_btn.click( | |
| fn=wrapped_fn, | |
| inputs=[person_input, jewelry_input, opacity_slider, side_dropdown], | |
| outputs=[output_image, status_text] | |
| ) | |
| else: | |
| try_on_btn.click( | |
| fn=process_fn, | |
| inputs=[person_input, jewelry_input, opacity_slider], | |
| outputs=[output_image, status_text] | |
| ) | |
| def create_ai_vton_tab(): | |
| """ | |
| Create the AI VTON tab - realistic jewelry try-on with person + jewelry image. | |
| This is the recommended method: upload your photo AND a jewelry image, | |
| and the trained model will realistically composite the jewelry onto you. | |
| """ | |
| vton_status = check_vton_availability() | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown(""" | |
| ### AI Jewelry VTON (Recommended) | |
| **Realistic jewelry try-on using your trained Replicate model!** | |
| Upload your photo AND a jewelry reference image. | |
| The AI will realistically composite the jewelry onto you. | |
| This uses the model trained with 150 jewelry images for realistic results. | |
| **Powered by:** Replicate trained model (ganeshgowri-asa/naari-jewelry-vton) | |
| """) | |
| person_input = gr.Image( | |
| label="Your Photo", | |
| type="numpy", | |
| sources=["upload", "webcam"], | |
| height=250 | |
| ) | |
| jewelry_input = gr.Image( | |
| label="Jewelry Reference Image", | |
| type="numpy", | |
| sources=["upload"], | |
| height=250 | |
| ) | |
| with gr.Row(): | |
| remove_bg_btn = gr.Button("Remove Jewelry Background", variant="secondary", size="sm") | |
| # Jewelry type selection | |
| jewelry_type_dropdown = gr.Dropdown( | |
| choices=["necklace", "earrings", "maang_tikka", "nose_ring", "bangles", "rings"], | |
| value="necklace", | |
| label="Jewelry Type", | |
| info="Select the type of jewelry being applied" | |
| ) | |
| # Options in an accordion | |
| with gr.Accordion("Customization Options", open=False): | |
| with gr.Row(): | |
| metal_type_dropdown = gr.Dropdown( | |
| choices=METAL_TYPES, | |
| value="gold", | |
| label="Metal Type" | |
| ) | |
| style_dropdown = gr.Dropdown( | |
| choices=["elegant", "traditional", "modern", "bridal", "casual"], | |
| value="elegant", | |
| label="Style" | |
| ) | |
| custom_prompt = gr.Textbox( | |
| label="Additional Description (optional)", | |
| placeholder="e.g., 'intricate kundan work', 'minimalist design'", | |
| lines=2 | |
| ) | |
| strength_slider = gr.Slider( | |
| minimum=0.3, | |
| maximum=1.0, | |
| value=0.75, | |
| step=0.05, | |
| label="Transformation Strength", | |
| info="Higher = more change, Lower = closer to original" | |
| ) | |
| # API status indicator | |
| api_status_text = "Available" if vton_status["available"] else "Not configured (set REPLICATE_API_TOKEN)" | |
| api_status = gr.Markdown(f"**Replicate API Status:** {api_status_text}") | |
| tryon_btn = gr.Button( | |
| "Try On Jewelry", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| with gr.Column(scale=1): | |
| output_image = gr.Image( | |
| label="Result", | |
| type="numpy", | |
| height=500, | |
| elem_classes="result-image" | |
| ) | |
| status_text = gr.Textbox( | |
| label="Status", | |
| interactive=False, | |
| lines=3 | |
| ) | |
| # Connect remove background button | |
| remove_bg_btn.click( | |
| fn=remove_background, | |
| inputs=[jewelry_input], | |
| outputs=[jewelry_input] | |
| ) | |
| # Connect the try-on button | |
| tryon_btn.click( | |
| fn=process_jewelry_vton, | |
| inputs=[ | |
| person_input, | |
| jewelry_input, | |
| jewelry_type_dropdown, | |
| metal_type_dropdown, | |
| style_dropdown, | |
| custom_prompt, | |
| strength_slider | |
| ], | |
| outputs=[output_image, status_text] | |
| ) | |
| def create_ai_jewelry_tab(): | |
| """Create the AI-powered jewelry generation tab with customization options.""" | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown(""" | |
| ### AI Jewelry Generation | |
| Generate custom jewelry on your photo using AI! | |
| Select jewelry type, customize options, and describe your desired piece. | |
| **Powered by:** Replicate trained model (ganeshgowri-asa/naari-jewelry-vton) | |
| """) | |
| person_input = gr.Image( | |
| label="Person Photo", | |
| type="numpy", | |
| sources=["upload", "webcam"], | |
| height=300 | |
| ) | |
| # Jewelry type selection | |
| jewelry_type_dropdown = gr.Dropdown( | |
| choices=list(JEWELRY_TYPES.keys()), | |
| value="necklace", | |
| label="Jewelry Type", | |
| info="Select the type of jewelry to generate" | |
| ) | |
| # Customization options in an accordion | |
| with gr.Accordion("Customization Options", open=True): | |
| with gr.Row(): | |
| metal_type_dropdown = gr.Dropdown( | |
| choices=METAL_TYPES, | |
| value="gold", | |
| label="Metal Type" | |
| ) | |
| stones_dropdown = gr.Dropdown( | |
| choices=STONE_TYPES, | |
| value="none", | |
| label="Stones" | |
| ) | |
| style_dropdown = gr.Dropdown( | |
| choices=STYLE_OPTIONS.get("necklace", ["default"]), | |
| value=STYLE_OPTIONS.get("necklace", ["default"])[0], | |
| label="Style" | |
| ) | |
| # Text prompt for additional customization | |
| jewelry_prompt = gr.Textbox( | |
| label="Jewelry Description (optional)", | |
| placeholder="Describe additional details... e.g., 'intricate floral pattern', 'minimalist design'", | |
| lines=2 | |
| ) | |
| opacity_slider = gr.Slider( | |
| minimum=0.1, | |
| maximum=1.0, | |
| value=1.0, | |
| step=0.1, | |
| label="Opacity" | |
| ) | |
| # API status indicator | |
| api_status = gr.Markdown( | |
| f"**Replicate API Status:** {'Available' if REPLICATE_AVAILABLE else 'Not configured (set REPLICATE_API_TOKEN)'}" | |
| ) | |
| generate_btn = gr.Button( | |
| "Generate Jewelry", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| with gr.Column(scale=1): | |
| output_image = gr.Image( | |
| label="Result", | |
| type="numpy", | |
| height=500, | |
| elem_classes="result-image" | |
| ) | |
| status_text = gr.Textbox( | |
| label="Status", | |
| interactive=False, | |
| lines=3 | |
| ) | |
| # Update style dropdown when jewelry type changes | |
| jewelry_type_dropdown.change( | |
| fn=update_style_dropdown, | |
| inputs=[jewelry_type_dropdown], | |
| outputs=[style_dropdown] | |
| ) | |
| # Connect the generate button | |
| generate_btn.click( | |
| fn=process_ai_jewelry_tryon, | |
| inputs=[ | |
| person_input, | |
| jewelry_prompt, | |
| jewelry_type_dropdown, | |
| metal_type_dropdown, | |
| stones_dropdown, | |
| style_dropdown, | |
| opacity_slider | |
| ], | |
| outputs=[output_image, status_text] | |
| ) | |
| def create_garment_tab(): | |
| """Create the garment virtual try-on tab.""" | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown(""" | |
| ### Garment Virtual Try-On | |
| Upload a person photo and a garment image to see how it looks! | |
| **Coming Soon:** This feature will use IDM-VTON for realistic garment try-on. | |
| """) | |
| person_input = gr.Image( | |
| label="Person Photo", | |
| type="numpy", | |
| sources=["upload", "webcam"], | |
| height=300 | |
| ) | |
| garment_input = gr.Image( | |
| label="Garment Image", | |
| type="numpy", | |
| sources=["upload"], | |
| height=300 | |
| ) | |
| garment_type = gr.Dropdown( | |
| choices=["upper_body", "lower_body", "full_body"], | |
| value="upper_body", | |
| label="Garment Type" | |
| ) | |
| with gr.Accordion("Advanced Settings", open=False): | |
| denoise_steps = gr.Slider( | |
| minimum=10, | |
| maximum=50, | |
| value=30, | |
| step=5, | |
| label="Denoise Steps" | |
| ) | |
| seed = gr.Slider( | |
| minimum=-1, | |
| maximum=2147483647, | |
| value=42, | |
| step=1, | |
| label="Seed (-1 for random)" | |
| ) | |
| try_on_btn = gr.Button("Try On Garment", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| output_image = gr.Image( | |
| label="Result", | |
| type="numpy", | |
| height=500, | |
| elem_classes="result-image" | |
| ) | |
| status_text = gr.Textbox( | |
| label="Status", | |
| interactive=False, | |
| lines=2 | |
| ) | |
| # Connect the try-on button | |
| try_on_btn.click( | |
| fn=process_garment_tryon, | |
| inputs=[person_input, garment_input, garment_type, denoise_steps, seed], | |
| outputs=[output_image, status_text] | |
| ) | |
| def create_app(): | |
| """Create the Gradio application with both garment and jewelry tabs.""" | |
| with gr.Blocks(theme=THEME, css=CSS, title="Naari Studio - Virtual Try-On") as app: | |
| # Header | |
| gr.Markdown(""" | |
| # Naari Studio - Virtual Try-On | |
| Experience AI-powered virtual try-on for garments and jewelry! | |
| **Tips for best results:** | |
| - Use a well-lit, front-facing photo | |
| - Ensure face/shoulders are clearly visible | |
| - Use jewelry images with transparent backgrounds for best results | |
| - Click "Remove Background" to auto-remove jewelry image backgrounds | |
| """) | |
| # Main tabs for Garment vs Jewelry | |
| with gr.Tabs(elem_classes="main-tabs") as main_tabs: | |
| # ================================================================ | |
| # GARMENT TAB | |
| # ================================================================ | |
| with gr.Tab("Garment Try-On", id="garment"): | |
| gr.Markdown(""" | |
| ## Garment Virtual Try-On | |
| Try on clothes virtually using AI! Upload your photo and a garment image. | |
| """) | |
| create_garment_tab() | |
| # ================================================================ | |
| # JEWELRY TAB | |
| # ================================================================ | |
| with gr.Tab("Jewelry Try-On", id="jewelry"): | |
| gr.Markdown(""" | |
| ## Jewelry Virtual Try-On | |
| Try on various types of jewelry using AI generation or image overlay. | |
| - **AI VTON (Recommended)**: Upload person + jewelry image for realistic try-on using our trained model | |
| - **AI Generate**: Create custom jewelry with text prompts | |
| - **Upload & Overlay**: Simple image overlay for quick preview | |
| """) | |
| # Jewelry sub-tabs | |
| with gr.Tabs(): | |
| # AI VTON Tab - Realistic Try-On with Person + Jewelry Image | |
| with gr.Tab("AI VTON"): | |
| create_ai_vton_tab() | |
| # AI Generation Tab | |
| with gr.Tab("AI Generate"): | |
| create_ai_jewelry_tab() | |
| with gr.Tab("Necklace"): | |
| create_jewelry_tab( | |
| jewelry_type="necklace", | |
| jewelry_label="Necklace", | |
| description="Try on necklaces - works best with visible shoulders and neck area. Uses pose detection landmarks 9, 10, 11, 12 for accurate positioning.", | |
| process_fn=process_necklace | |
| ) | |
| with gr.Tab("Earrings"): | |
| create_jewelry_tab( | |
| jewelry_type="earrings", | |
| jewelry_label="Earrings", | |
| description="Try on earrings - upload a single earring image (will be mirrored for both ears). Uses face mesh earlobe landmarks for accurate positioning.", | |
| process_fn=process_earrings | |
| ) | |
| with gr.Tab("Maang Tikka"): | |
| create_jewelry_tab( | |
| jewelry_type="maang_tikka", | |
| jewelry_label="Maang Tikka", | |
| description="Try on traditional Indian forehead jewelry - works best with visible forehead. Uses face mesh hairline landmarks for accurate positioning.", | |
| process_fn=process_maang_tikka | |
| ) | |
| with gr.Tab("Nose Ring"): | |
| create_jewelry_tab( | |
| jewelry_type="nose_ring", | |
| jewelry_label="Nose Ring", | |
| description="Try on nose rings/nath - select placement side and style. Uses face mesh nostril landmarks for accurate positioning.", | |
| process_fn=process_nose_ring, | |
| show_side=True, | |
| show_style=True | |
| ) | |
| # ================================================================ | |
| # API DOCUMENTATION TAB | |
| # ================================================================ | |
| with gr.Tab("API", id="api"): | |
| gr.Markdown(""" | |
| ## API Documentation | |
| ### /api/jewelry-tryon Endpoint | |
| The jewelry try-on functionality is available as a programmatic API. | |
| You can call it using Gradio's API client or direct HTTP requests. | |
| #### Parameters: | |
| | Parameter | Type | Required | Description | | |
| |-----------|------|----------|-------------| | |
| | `person_image` | Image | Yes | Person photo (uploaded file) | | |
| | `jewelry_prompt` | string | Yes | Text description of desired jewelry | | |
| | `jewelry_type` | string | No | Type: necklace, earrings, bangles, rings, maang_tikka, nose_ring (default: necklace) | | |
| | `metal_type` | string | No | Metal: gold, silver, rose gold, platinum, oxidized silver, antique gold (default: gold) | | |
| | `stones` | string | No | Stones: diamond, ruby, emerald, sapphire, pearl, kundan, polki, none (default: none) | | |
| | `style` | string | No | Style variant (depends on jewelry type) | | |
| #### Example Python Usage: | |
| ```python | |
| from gradio_client import Client | |
| client = Client("GaneshGowri/naari-avatar") | |
| result = client.predict( | |
| person_image="path/to/image.jpg", | |
| jewelry_prompt="elegant bridal necklace", | |
| jewelry_type="necklace", | |
| metal_type="gold", | |
| stones="kundan", | |
| style="choker", | |
| api_name="/api/jewelry-tryon" | |
| ) | |
| ``` | |
| #### Style Options by Jewelry Type: | |
| | Jewelry Type | Available Styles | | |
| |--------------|-----------------| | |
| | Necklace | choker, princess, matinee, opera, statement, layered, pendant | | |
| | Earrings | studs, drops, hoops, chandeliers, jhumkas, cuffs | | |
| | Bangles | traditional, modern, kada, charm, cuff, tennis | | |
| | Rings | solitaire, band, cluster, eternity, cocktail, stackable | | |
| | Maang Tikka | bridal, simple, elaborate, kundan, pearl | | |
| | Nose Ring | stud, hoop, nath, septum | | |
| #### Response: | |
| Returns a dictionary with: | |
| - `success`: boolean indicating operation success | |
| - `image`: Result image (numpy array) | |
| - `message`: Status message | |
| - `prompt_used`: Full prompt sent to the model | |
| """) | |
| # API test interface | |
| gr.Markdown("### Try the API") | |
| with gr.Row(): | |
| with gr.Column(): | |
| api_person_image = gr.Image( | |
| label="Person Image", | |
| type="numpy", | |
| sources=["upload"] | |
| ) | |
| api_prompt = gr.Textbox( | |
| label="Jewelry Prompt", | |
| value="elegant gold necklace with diamonds" | |
| ) | |
| api_jewelry_type = gr.Dropdown( | |
| choices=list(JEWELRY_TYPES.keys()), | |
| value="necklace", | |
| label="Jewelry Type" | |
| ) | |
| api_metal = gr.Dropdown( | |
| choices=METAL_TYPES, | |
| value="gold", | |
| label="Metal Type" | |
| ) | |
| api_stones = gr.Dropdown( | |
| choices=STONE_TYPES, | |
| value="diamond", | |
| label="Stones" | |
| ) | |
| api_test_btn = gr.Button("Test API", variant="primary") | |
| with gr.Column(): | |
| api_output = gr.Image(label="API Result", type="numpy") | |
| api_status = gr.Textbox(label="API Response", lines=4) | |
| def test_api(person_image, prompt, jewelry_type, metal, stones): | |
| if person_image is None: | |
| return None, "Error: Please upload a person image" | |
| result = api_jewelry_tryon( | |
| person_image_path=person_image, | |
| jewelry_prompt=prompt, | |
| jewelry_type=jewelry_type, | |
| metal_type=metal, | |
| stones=stones | |
| ) | |
| return result.get("image"), json.dumps({ | |
| "success": result["success"], | |
| "message": result["message"], | |
| "prompt_used": result["prompt_used"] | |
| }, indent=2) | |
| api_test_btn.click( | |
| fn=test_api, | |
| inputs=[api_person_image, api_prompt, api_jewelry_type, api_metal, api_stones], | |
| outputs=[api_output, api_status] | |
| ) | |
| # Footer | |
| gr.Markdown(""" | |
| --- | |
| **Naari Studio** - AI-Powered Virtual Try-On | |
| Built with: | |
| - cvzone PoseModule & MediaPipe Face Mesh for landmark detection | |
| - Replicate trained model (ganeshgowri-asa/naari-jewelry-vton:f6b844b4) for AI jewelry generation | |
| - Gradio for the web interface | |
| [GitHub](https://github.com/ganeshgowri/naari-vton) | Powered by HuggingFace Spaces | |
| """) | |
| return app | |
| # Create and launch the app | |
| app = create_app() | |
| if __name__ == "__main__": | |
| app.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True | |
| ) |