import logging from dataclasses import dataclass, field from typing import Any, Dict, List, Optional logger = logging.getLogger(__name__) @dataclass class InpaintingTemplate: """Data class representing an inpainting template.""" key: str name: str category: str icon: str description: str # Prompt templates prompt_template: str negative_prompt: str # Pipeline mode selection use_controlnet: bool = True # False = use pure SDXL Inpainting model (more stable) mask_dilation: int = 0 # Pixels to expand mask for better edge blending # ControlNet parameters (only used when use_controlnet=True) controlnet_conditioning_scale: float = 0.7 preferred_conditioning: str = "canny" # "canny" or "depth" preserve_structure_in_mask: bool = False edge_guidance_mode: str = "boundary" # Generation parameters guidance_scale: float = 7.5 num_inference_steps: int = 25 strength: float = 0.99 # Use 0.99 instead of 1.0 to avoid noise issues # Mask parameters feather_radius: int = 3 # Minimal feathering, let pipeline handle blending # Prompt enhancement enhance_prompt: bool = True # UI metadata difficulty: str = "medium" recommended_models: List[str] = field(default_factory=lambda: ["sdxl_base"]) example_prompts: List[str] = field(default_factory=list) usage_tips: List[str] = field(default_factory=list) class InpaintingTemplateManager: """ Manages inpainting templates for various use cases. Templates are categorized into two pipeline modes: - Pure Inpainting (use_controlnet=False): For replacement/removal tasks - ControlNet Inpainting (use_controlnet=True): For structure-preserving tasks Example: >>> manager = InpaintingTemplateManager() >>> template = manager.get_template("object_replacement") >>> if not template.use_controlnet: ... # Use pure SDXL Inpainting pipeline ... pass """ TEMPLATES: Dict[str, InpaintingTemplate] = { # 1. OBJECT REPLACEMENT - Replace one object with another "object_replacement": InpaintingTemplate( key="object_replacement", name="Object Replacement", category="Replacement", icon="🔄", description="Replace objects naturally - uses dedicated inpainting model for best results", prompt_template="{content}, photorealistic, natural lighting, seamlessly integrated, high quality, detailed", negative_prompt=( "blurry, low quality, distorted, deformed, " "visible seams, harsh edges, unnatural, " "cartoon, anime, illustration, drawing" ), # Pipeline mode use_controlnet=False, # Pure inpainting for stable results mask_dilation=5, # Expand mask for seamless blending # Generation parameters guidance_scale=8.0, num_inference_steps=25, strength=0.99, # Mask parameters feather_radius=3, # Not used for Pure Inpainting but kept for compatibility controlnet_conditioning_scale=0.0, preferred_conditioning="canny", # Placeholder, not used in Pure Inpainting mode preserve_structure_in_mask=False, edge_guidance_mode="none", enhance_prompt=True, difficulty="easy", recommended_models=["realvis_xl", "juggernaut_xl"], example_prompts=[ "elegant ceramic vase with fresh roses", "modern minimalist desk lamp, chrome finish", "vintage leather-bound book with gold lettering" ], usage_tips=[ "🎯 Purpose: Replace an object with something completely different.", "", "💡 Example Prompts:", " • elegant ceramic vase with fresh roses", " • modern minimalist desk lamp, chrome finish", " • vintage leather-bound book with gold lettering", "", "💡 Tips:", " • Draw mask slightly larger than the object", " • Describe the NEW object in detail", " • Include material, color, style for better results" ] ), # 2. OBJECT REMOVAL - Remove and fill with background (NO PROMPT NEEDED) "removal": InpaintingTemplate( key="removal", name="Remove Object", category="Removal", icon="🗑️", description="Remove unwanted objects - just draw mask, no prompt needed", prompt_template="seamless background, natural texture continuation, photorealistic, high quality", negative_prompt=( "object, item, thing, foreground element, new object, " "visible patch, inconsistent texture, " "blurry, low quality, artificial" ), # Pipeline mode use_controlnet=False, # Pure inpainting for clean removal mask_dilation=8, # Larger expansion to cover shadows/reflections # Generation parameters guidance_scale=7.0, # Lower guidance for natural fill num_inference_steps=20, strength=0.99, # Mask parameters feather_radius=5, # More feathering for seamless blend # Not used for Pure Inpainting but kept for compatibility controlnet_conditioning_scale=0.0, preferred_conditioning="canny", preserve_structure_in_mask=False, edge_guidance_mode="none", enhance_prompt=False, # Do NOT enhance - keep it simple difficulty="easy", recommended_models=["realvis_xl", "juggernaut_xl"], example_prompts=[], # No prompts needed for removal usage_tips=[ "🎯 Purpose: Remove unwanted objects from image.", "", "📝 No prompt needed! Just:", " 1. Draw white mask over the object", " 2. Include shadows in your mask", " 3. Click Generate", "", "💡 Tips:", " • Make mask larger than the object", " • If artifacts remain, draw a bigger mask and retry" ] ), # CONTROLNET TEMPLATES (Structure Preserving) # 3. CLOTHING CHANGE - Change clothes while keeping body "clothing_change": InpaintingTemplate( key="clothing_change", name="Clothing Change", category="Replacement", icon="👕", description="Change clothing style while preserving body structure", prompt_template="{content}, photorealistic, realistic fabric, natural fit, high quality", negative_prompt=( "wrong proportions, distorted body, floating fabric, " "mismatched lighting, naked, nudity, " "cartoon, anime, illustration" ), # Pipeline mode use_controlnet=True, # Need ControlNet to preserve body mask_dilation=3, # Small expansion for clothing edges # ControlNet parameters controlnet_conditioning_scale=0.4, preferred_conditioning="depth", # Depth preserves body structure preserve_structure_in_mask=False, edge_guidance_mode="soft", # Generation parameters guidance_scale=8.0, num_inference_steps=25, strength=1.0, # Full repaint for clothing # Mask parameters feather_radius=5, enhance_prompt=True, difficulty="medium", recommended_models=["juggernaut_xl", "realvis_xl"], example_prompts=[ "tailored charcoal suit with silk tie", "navy blazer with gold buttons", "elegant black evening dress", "casual white t-shirt", "cozy cream sweater", "leather motorcycle jacket", "formal white dress shirt", "vintage denim jacket", "red cocktail dress", "professional grey blazer" ], usage_tips=[ "🎯 Purpose: Change clothing while keeping body shape.", "", "🤖 Recommended Models:", " • JuggernautXL - Best for formal wear", " • RealVisXL - Great for casual clothing", "", "💡 Tips:", " • Mask only the clothing area", " • Include fabric type: 'silk', 'cotton', 'wool'", " • Body proportions are preserved automatically" ] ), # 4. COLOR CHANGE - Change color only, keep structure "change_color": InpaintingTemplate( key="change_color", name="Change Color", category="Color", icon="🎨", description="Change color only - strictly preserves shape and texture", prompt_template="{content} color, solid uniform {content}, flat color, smooth surface", negative_prompt=( "different shape, changed structure, new pattern, " "texture change, deformed, distorted, " "gradient, multiple colors, pattern" ), # Pipeline mode use_controlnet=True, # Need ControlNet to preserve exact shape mask_dilation=0, # No expansion - precise color change # ControlNet parameters controlnet_conditioning_scale=0.85, # High: strict structure preservation preferred_conditioning="canny", # Canny preserves edges exactly preserve_structure_in_mask=True, # Keep all edges edge_guidance_mode="boundary", # Generation parameters guidance_scale=12.0, # High: force the exact color num_inference_steps=15, strength=1.0, # Mask parameters feather_radius=2, # Very small enhance_prompt=False, # Use color prompt directly difficulty="easy", recommended_models=["juggernaut_xl", "realvis_xl"], example_prompts=[ "vibrant red", "deep navy blue", "bright yellow", "emerald green", "soft pink", "pure white", "charcoal grey", "royal purple", "coral orange", "golden brown" ], usage_tips=[ "🎯 Purpose: Change color only, shape stays exactly the same.", "", "💡 Tips:", " • Enter ONLY the color name", " • Use modifiers: 'bright', 'dark', 'pastel'", " • Shape and texture are preserved exactly" ] ), } # Category display order CATEGORIES = ["Color", "Replacement", "Removal"] def __init__(self): """Initialize the InpaintingTemplateManager.""" logger.info(f"InpaintingTemplateManager initialized with {len(self.TEMPLATES)} templates") def get_all_templates(self) -> Dict[str, InpaintingTemplate]: """Get all available templates.""" return self.TEMPLATES def get_template(self, key: str) -> Optional[InpaintingTemplate]: """Get a specific template by key.""" return self.TEMPLATES.get(key) def get_templates_by_category(self, category: str) -> List[InpaintingTemplate]: """Get all templates in a specific category.""" return [t for t in self.TEMPLATES.values() if t.category == category] def get_categories(self) -> List[str]: """Get list of all categories in display order.""" return self.CATEGORIES def get_template_choices_sorted(self) -> List[str]: """Get template choices formatted for Gradio dropdown.""" display_list = [] for category in self.CATEGORIES: templates = self.get_templates_by_category(category) for template in sorted(templates, key=lambda t: t.name): display_name = f"{template.icon} {template.name}" display_list.append(display_name) return display_list def get_template_key_from_display(self, display_name: str) -> Optional[str]: """Get template key from display name.""" if not display_name: return None for key, template in self.TEMPLATES.items(): if f"{template.icon} {template.name}" == display_name: return key return None def get_parameters_for_template(self, key: str) -> Dict[str, Any]: """Get recommended parameters for a template.""" template = self.get_template(key) if not template: return {} return { "use_controlnet": template.use_controlnet, "mask_dilation": template.mask_dilation, "controlnet_conditioning_scale": template.controlnet_conditioning_scale, "preferred_conditioning": template.preferred_conditioning, "preserve_structure_in_mask": template.preserve_structure_in_mask, "edge_guidance_mode": template.edge_guidance_mode, "guidance_scale": template.guidance_scale, "num_inference_steps": template.num_inference_steps, "strength": template.strength, "feather_radius": template.feather_radius, "enhance_prompt": template.enhance_prompt, } def build_prompt(self, key: str, content: str) -> str: """Build complete prompt from template and user content.""" template = self.get_template(key) if not template: return content return template.prompt_template.format(content=content) def get_negative_prompt(self, key: str) -> str: """Get negative prompt for a template.""" template = self.get_template(key) if not template: return "" return template.negative_prompt def get_usage_tips(self, key: str) -> List[str]: """Get usage tips for a template.""" template = self.get_template(key) if not template: return [] return template.usage_tips def get_recommended_models(self, key: str) -> List[str]: """Get recommended models for a template.""" template = self.get_template(key) if not template: return ["sdxl_base"] return template.recommended_models def get_example_prompts(self, key: str) -> List[str]: """Get example prompts for a template.""" template = self.get_template(key) if not template: return [] return template.example_prompts def get_primary_recommended_model(self, key: str) -> str: """Get the primary recommended model for a template.""" models = self.get_recommended_models(key) return models[0] if models else "sdxl_base"