File size: 11,633 Bytes
96975ba
 
 
 
62cc7ef
 
 
 
 
 
96975ba
62cc7ef
96975ba
7f9e35f
62cc7ef
 
 
 
 
2be296c
 
62cc7ef
 
96975ba
 
 
62cc7ef
2be296c
 
 
 
 
 
 
 
 
 
 
 
53a2cd6
2be296c
 
 
96975ba
53a2cd6
2be296c
 
62cc7ef
96975ba
 
8c988dd
96975ba
 
 
 
8c988dd
 
 
 
 
4d4920b
8c988dd
 
0024d74
8c988dd
 
 
 
 
 
 
 
 
 
50bfbb3
96975ba
8c988dd
96975ba
8c988dd
 
 
 
 
 
 
9e112ae
96975ba
8c988dd
 
6ab8222
96975ba
 
 
 
 
 
 
 
 
8c988dd
 
96975ba
62cc7ef
 
96975ba
 
 
62cc7ef
96975ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62cc7ef
96975ba
 
62cc7ef
 
96975ba
 
 
62cc7ef
96975ba
 
 
 
 
 
 
 
 
 
 
 
 
52c8e07
96975ba
 
 
 
 
 
 
 
7f9e35f
96975ba
 
 
 
 
 
62cc7ef
19def46
96975ba
52c8e07
 
 
 
96975ba
52c8e07
19def46
96975ba
 
 
 
 
 
 
62cc7ef
96975ba
 
 
b85b7f4
96975ba
 
 
 
 
 
7fba5e7
 
 
b85b7f4
7fba5e7
 
 
 
b85b7f4
96975ba
 
b85b7f4
96975ba
 
 
 
 
 
 
 
 
 
9234160
96975ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9234160
96975ba
 
 
 
 
 
 
 
 
 
 
 
01be129
96975ba
 
 
 
 
 
003422e
96975ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
003422e
96975ba
 
 
 
50cbabf
96975ba
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
"""
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,
    )