Spaces:
Sleeping
Sleeping
| """ | |
| Interior AI Designer API | |
| Hugging Face Spaces deployment with public API endpoint | |
| """ | |
| import os | |
| import random | |
| import time | |
| import gradio as gr | |
| import numpy as np | |
| import spaces # Required for Hugging Face Spaces GPU | |
| import torch | |
| import gc | |
| from PIL import Image | |
| from diffusers import ( | |
| ControlNetModel, | |
| DPMSolverMultistepScheduler, | |
| StableDiffusionControlNetPipeline, | |
| ) | |
| from controlnet_aux_local import NormalBaeDetector | |
| MAX_SEED = np.iinfo(np.int32).max | |
| # ============================================================ | |
| # Model Loading (runs once at startup) | |
| # ============================================================ | |
| class Preprocessor: | |
| MODEL_ID = "lllyasviel/Annotators" | |
| def __init__(self): | |
| self.model = None | |
| self.name = "" | |
| def load(self, name: str) -> None: | |
| if name == self.name: | |
| return | |
| elif name == "NormalBae": | |
| print("Loading NormalBae") | |
| self.model = NormalBaeDetector.from_pretrained(self.MODEL_ID).to("cuda") | |
| torch.cuda.empty_cache() | |
| self.name = name | |
| else: | |
| raise ValueError(f"Unknown preprocessor: {name}") | |
| def __call__(self, image: Image.Image, **kwargs) -> Image.Image: | |
| return self.model(image, **kwargs) | |
| # Load models at startup | |
| if gr.NO_RELOAD: | |
| print("CUDA version:", torch.version.cuda) | |
| print("Loading models...") | |
| # ControlNet | |
| model_id = "lllyasviel/control_v11p_sd15_normalbae" | |
| controlnet = ControlNetModel.from_pretrained( | |
| model_id, | |
| torch_dtype=torch.float16, | |
| ).to("cuda") | |
| # Scheduler | |
| scheduler = DPMSolverMultistepScheduler.from_pretrained( | |
| "ashllay/stable-diffusion-v1-5-archive", | |
| solver_order=2, | |
| subfolder="scheduler", | |
| use_karras_sigmas=True, | |
| final_sigmas_type="sigma_min", | |
| algorithm_type="sde-dpmsolver++", | |
| prediction_type="epsilon", | |
| thresholding=False, | |
| denoise_final=True, | |
| torch_dtype=torch.float16, | |
| ) | |
| # Stable Diffusion Pipeline | |
| base_model_url = "https://huggingface.co/Lykon/AbsoluteReality/blob/main/AbsoluteReality_1.8.1_pruned.safetensors" | |
| pipe = StableDiffusionControlNetPipeline.from_single_file( | |
| base_model_url, | |
| safety_checker=None, | |
| controlnet=controlnet, | |
| scheduler=scheduler, | |
| torch_dtype=torch.float16, | |
| ).to("cuda") | |
| # Preprocessor | |
| preprocessor = Preprocessor() | |
| preprocessor.load("NormalBae") | |
| # Optional: Load textual inversions for better negative prompts | |
| try: | |
| pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="EasyNegativeV2.safetensors", token="EasyNegativeV2") | |
| pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="badhandv4.pt", token="badhandv4") | |
| pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="fcNeg-neg.pt", token="fcNeg-neg") | |
| except Exception as e: | |
| print(f"Could not load textual inversions: {e}") | |
| pipe.to("cuda") | |
| torch.cuda.empty_cache() | |
| gc.collect() | |
| print(f"Models loaded! CUDA memory: {torch.cuda.max_memory_allocated(device='cuda') / 1e9:.2f} GB") | |
| # ============================================================ | |
| # Style Definitions | |
| # ============================================================ | |
| STYLE_LIST = [ | |
| {"name": "None", "prompt": ""}, | |
| {"name": "Minimalistic", "prompt": "Minimalist interior design,clean lines,neutral colors,uncluttered space,functional furniture,lots of natural light"}, | |
| {"name": "Boho", "prompt": "Bohemian chic interior,eclectic mix of patterns and textures,vintage furniture,plants,woven textiles,warm earthy colors"}, | |
| {"name": "Farmhouse", "prompt": "Modern farmhouse interior,rustic wood elements,shiplap walls,neutral color palette,industrial accents,cozy textiles"}, | |
| {"name": "Saudi Prince", "prompt": "Opulent gold interior,luxurious ornate furniture,crystal chandeliers,rich fabrics,marble floors,intricate Arabic patterns"}, | |
| {"name": "Neoclassical", "prompt": "Neoclassical interior design,elegant columns,ornate moldings,symmetrical layout,refined furniture,muted color palette"}, | |
| {"name": "Eclectic", "prompt": "Eclectic interior design,mix of styles and eras,bold color combinations,diverse furniture pieces,unique art objects"}, | |
| {"name": "Parisian", "prompt": "Parisian apartment interior,all-white color scheme,ornate moldings,herringbone wood floors,elegant furniture,large windows"}, | |
| {"name": "Hollywood", "prompt": "Hollywood Regency interior,glamorous and luxurious,bold colors,mirrored surfaces,velvet upholstery,gold accents"}, | |
| {"name": "Scandinavian", "prompt": "Scandinavian interior design,light wood tones,white walls,minimalist furniture,cozy textiles,hygge atmosphere"}, | |
| {"name": "Beach", "prompt": "Coastal beach house interior,light blue and white color scheme,weathered wood,nautical accents,sheer curtains,ocean view"}, | |
| {"name": "Japanese", "prompt": "Traditional Japanese interior,tatami mats,shoji screens,low furniture,zen garden view,minimalist decor,natural materials"}, | |
| {"name": "Midcentury Modern", "prompt": "Mid-century modern interior,1950s-60s style furniture,organic shapes,warm wood tones,bold accent colors,large windows"}, | |
| {"name": "Retro Futurism", "prompt": "Neon (atompunk world) retro cyberpunk background"}, | |
| {"name": "Texan", "prompt": "Western cowboy interior,rustic wood beams,leather furniture,cowhide rugs,antler chandeliers,southwestern patterns"}, | |
| {"name": "Matrix", "prompt": "Futuristic cyberpunk interior,neon accent lighting,holographic plants,sleek black surfaces,advanced gaming setup,transparent screens,Blade Runner inspired decor,high-tech minimalist furniture"}, | |
| ] | |
| STYLES = {s["name"]: s["prompt"] for s in STYLE_LIST} | |
| STYLE_NAMES = list(STYLES.keys()) | |
| # ============================================================ | |
| # Core Processing Function (API Endpoint) | |
| # ============================================================ | |
| def redesign_interior( | |
| image: Image.Image, | |
| style: str = "Minimalistic", | |
| custom_prompt: str = "", | |
| num_steps: int = 15, | |
| guidance_scale: float = 5.5, | |
| seed: int = -1, | |
| image_resolution: int = 768, | |
| ) -> Image.Image: | |
| """ | |
| Redesign an interior image with the specified style. | |
| Args: | |
| image: Input room/interior image (PIL Image) | |
| style: Design style name (e.g., "Minimalistic", "Boho", "Japanese") | |
| custom_prompt: Additional custom prompt to append | |
| num_steps: Number of inference steps (default: 15) | |
| guidance_scale: Guidance scale for generation (default: 5.5) | |
| seed: Random seed (-1 for random) | |
| image_resolution: Output image resolution (default: 768) | |
| Returns: | |
| Redesigned interior image (PIL Image) | |
| """ | |
| # Set seed | |
| if seed == -1: | |
| seed = random.randint(0, MAX_SEED) | |
| generator = torch.cuda.manual_seed(seed) | |
| # Preprocess image with NormalBae | |
| preprocessor.load("NormalBae") | |
| control_image = preprocessor( | |
| image=image, | |
| image_resolution=image_resolution, | |
| detect_resolution=image_resolution, | |
| ) | |
| # Build prompt | |
| base_prompt = "Photo from Pinterest of" | |
| style_prompt = STYLES.get(style, "") | |
| additional_prompt = "design-style interior designed (interior space), tungsten white balance, captured with a DSLR camera using f/10 aperture, 1/60 sec shutter speed, ISO 400, 20mm focal length" | |
| if style_prompt: | |
| prompt = f"{base_prompt} {style_prompt} {custom_prompt}, {additional_prompt}" | |
| else: | |
| prompt = f"{base_prompt} {custom_prompt}, {additional_prompt}" if custom_prompt else f"boho chic interior, {additional_prompt}" | |
| negative_prompt = "EasyNegativeV2, fcNeg, (badhandv4:1.4), (worst quality, low quality, bad quality, normal quality:2.0), (bad hands, missing fingers, extra fingers:2.0)" | |
| print(f"Prompt: {prompt}") | |
| print(f"Style: {style}, Seed: {seed}") | |
| # Generate | |
| start = time.time() | |
| result = pipe( | |
| prompt=prompt, | |
| negative_prompt=negative_prompt, | |
| guidance_scale=guidance_scale, | |
| num_images_per_prompt=1, | |
| num_inference_steps=num_steps, | |
| generator=generator, | |
| image=control_image, | |
| ).images[0] | |
| print(f"Generation completed in {time.time() - start:.2f}s") | |
| torch.cuda.empty_cache() | |
| return result | |
| # ============================================================ | |
| # Gradio Interface | |
| # ============================================================ | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# 🏠 Interior AI Designer") | |
| gr.Markdown("Upload a room photo and select a design style to reimagine your space!") | |
| with gr.Row(): | |
| with gr.Column(): | |
| input_image = gr.Image( | |
| label="Upload Room Image", | |
| type="pil", | |
| sources=["upload", "clipboard"], | |
| ) | |
| style_dropdown = gr.Dropdown( | |
| label="Design Style", | |
| choices=STYLE_NAMES, | |
| value="Minimalistic", | |
| ) | |
| custom_prompt = gr.Textbox( | |
| label="Custom Prompt (optional)", | |
| placeholder="Add specific details like 'with plants' or 'blue accents'", | |
| ) | |
| with gr.Accordion("Advanced Options", open=False): | |
| num_steps = gr.Slider( | |
| label="Inference Steps", | |
| minimum=10, maximum=50, value=15, step=1, | |
| ) | |
| guidance_scale = gr.Slider( | |
| label="Guidance Scale", | |
| minimum=1.0, maximum=20.0, value=5.5, step=0.5, | |
| ) | |
| seed = gr.Slider( | |
| label="Seed (-1 for random)", | |
| minimum=-1, maximum=MAX_SEED, value=-1, step=1, | |
| ) | |
| image_resolution = gr.Slider( | |
| label="Resolution", | |
| minimum=512, maximum=1024, value=768, step=128, | |
| ) | |
| generate_btn = gr.Button("🎨 Redesign Interior", variant="primary", size="lg") | |
| with gr.Column(): | |
| output_image = gr.Image(label="Redesigned Interior", type="pil") | |
| # Examples | |
| gr.Examples( | |
| examples=[ | |
| ["Minimalistic"], | |
| ["Boho"], | |
| ["Japanese"], | |
| ["Scandinavian"], | |
| ["Matrix"], | |
| ], | |
| inputs=[style_dropdown], | |
| label="Try these styles", | |
| ) | |
| # Connect the button to the function | |
| generate_btn.click( | |
| fn=redesign_interior, | |
| inputs=[input_image, style_dropdown, custom_prompt, num_steps, guidance_scale, seed, image_resolution], | |
| outputs=output_image, | |
| api_name="redesign", # This enables the API endpoint | |
| ) | |
| gr.Markdown(""" | |
| --- | |
| ### 📡 API Usage | |
| This app exposes a public API! You can call it programmatically: | |
| ```python | |
| from gradio_client import Client | |
| client = Client("YOUR_USERNAME/interior-ai-designer") | |
| result = client.predict( | |
| image="path/to/room.jpg", | |
| style="Minimalistic", | |
| custom_prompt="", | |
| num_steps=15, | |
| guidance_scale=5.5, | |
| seed=-1, | |
| image_resolution=768, | |
| api_name="/redesign" | |
| ) | |
| print(result) # Path to output image | |
| ``` | |
| """) | |
| # Launch | |
| if __name__ == "__main__": | |
| demo.queue(max_size=10).launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| ) | |