bhartiyashesh's picture
Deploy Interior AI Designer
96975ba
"""
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)
# ============================================================
@spaces.GPU(duration=20)
@torch.inference_mode()
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,
)