Varhal commited on
Commit
7785d0c
·
verified ·
1 Parent(s): 63aef07

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +363 -416
app.py CHANGED
@@ -1,14 +1,12 @@
1
- prod = False
2
- port = 8080
3
- show_options = False
4
- if prod:
5
- port = 8081
6
- # show_options = False
7
 
8
  import os
9
  import random
10
  import time
11
- import gradio as gr
12
  import numpy as np
13
  import spaces
14
  import imageio
@@ -21,20 +19,21 @@ from diffusers import (
21
  ControlNetModel,
22
  DPMSolverMultistepScheduler,
23
  StableDiffusionControlNetPipeline,
24
- # StableDiffusionInpaintPipeline,
25
- # AutoencoderKL,
26
  )
 
27
  from controlnet_aux_local import NormalBaeDetector
28
 
29
  MAX_SEED = np.iinfo(np.int32).max
30
  API_KEY = os.environ.get("API_KEY", None)
31
  # os.environ['HF_HOME'] = '/data/.huggingface'
32
-
33
  print("CUDA version:", torch.version.cuda)
34
  print("loading everything")
35
- compiled = False
36
  api = HfApi()
37
 
 
38
  class Preprocessor:
39
  MODEL_ID = "lllyasviel/Annotators"
40
 
@@ -47,128 +46,153 @@ class Preprocessor:
47
  return
48
  elif name == "NormalBae":
49
  print("Loading NormalBae")
50
- self.model = NormalBaeDetector.from_pretrained(self.MODEL_ID).to("cuda")
51
- torch.cuda.empty_cache()
 
 
52
  self.name = name
53
  else:
54
  raise ValueError
55
  return
56
 
57
  def __call__(self, image: Image.Image, **kwargs) -> Image.Image:
 
 
 
 
58
  return self.model(image, **kwargs)
59
 
60
- if gr.NO_RELOAD:
61
- # Controlnet Normal
62
- model_id = "lllyasviel/control_v11p_sd15_normalbae"
63
- print("initializing controlnet")
64
- controlnet = ControlNetModel.from_pretrained(
65
- model_id,
66
- torch_dtype=torch.float16,
67
- attn_implementation="flash_attention_2",
68
- ).to("cuda")
69
-
70
- # Scheduler
71
- scheduler = DPMSolverMultistepScheduler.from_pretrained(
72
- # "runwayml/stable-diffusion-v1-5",
73
- # "stable-diffusion-v1-5/stable-diffusion-v1-5",
74
- "ashllay/stable-diffusion-v1-5-archive",
75
- solver_order=2,
76
- subfolder="scheduler",
77
- use_karras_sigmas=True,
78
- final_sigmas_type="sigma_min",
79
- algorithm_type="sde-dpmsolver++",
80
- prediction_type="epsilon",
81
- thresholding=False,
82
- denoise_final=True,
83
- device_map="cuda",
84
- torch_dtype=torch.float16,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  )
 
 
 
 
 
 
 
 
 
86
 
87
- # Stable Diffusion Pipeline URL
88
- # base_model_url = "https://huggingface.co/broyang/hentaidigitalart_v20/blob/main/realcartoon3d_v15.safetensors"
89
- base_model_url = "https://huggingface.co/Lykon/AbsoluteReality/blob/main/AbsoluteReality_1.8.1_pruned.safetensors"
90
- # vae_url = "https://huggingface.co/stabilityai/sd-vae-ft-mse-original/blob/main/vae-ft-mse-840000-ema-pruned.safetensors"
91
-
92
- # print('loading vae')
93
- # vae = AutoencoderKL.from_single_file(vae_url, torch_dtype=torch.float16).to("cuda")
94
- # vae.to(memory_format=torch.channels_last)
95
-
96
- print('loading pipe')
97
- pipe = StableDiffusionControlNetPipeline.from_single_file(
98
- base_model_url,
99
- safety_checker=None,
100
- controlnet=controlnet,
101
- scheduler=scheduler,
102
- # vae=vae,
103
- torch_dtype=torch.float16,
104
- ).to("cuda")
105
-
106
- # print('loading inpainting pipe')
107
- # inpaint_pipe = StableDiffusionInpaintPipeline.from_pretrained(
108
- # "runwayml/stable-diffusion-inpainting",
109
- # torch_dtype=torch.float16,
110
- # ).to("cuda")
111
-
112
- print("loading preprocessor")
113
- preprocessor = Preprocessor()
114
- preprocessor.load("NormalBae")
115
- pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="EasyNegativeV2.safetensors", token="EasyNegativeV2",)
116
- pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="badhandv4.pt", token="badhandv4")
117
- pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="fcNeg-neg.pt", token="fcNeg-neg")
118
- pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="HDA_Ahegao.pt", token="HDA_Ahegao")
119
- pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="HDA_Bondage.pt", token="HDA_Bondage")
120
- pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="HDA_pet_play.pt", token="HDA_pet_play")
121
- pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="HDA_unconventional maid.pt", token="HDA_unconventional_maid")
122
- pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="HDA_NakedHoodie.pt", token="HDA_NakedHoodie")
123
- pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="HDA_NunDress.pt", token="HDA_NunDress")
124
- pipe.load_textual_inversion("broyang/hentaidigitalart_v20", weight_name="HDA_Shibari.pt", token="HDA_Shibari")
125
- pipe.to("cuda")
126
-
127
- print("---------------Loaded controlnet pipeline---------------")
128
  torch.cuda.empty_cache()
129
  gc.collect()
130
  print(f"CUDA memory allocated: {torch.cuda.max_memory_allocated(device='cuda') / 1e9:.2f} GB")
131
- print("Model Compiled!")
132
-
133
- def generate_furniture_mask(image, furniture_type):
134
- image_np = np.array(image)
135
- height, width = image_np.shape[:2]
136
-
137
- mask = np.zeros((height, width), dtype=np.uint8)
138
-
139
- if furniture_type == "sofa":
140
- cv2.rectangle(mask, (width//4, int(height*0.6)), (width*3//4, height), 255, -1)
141
- elif furniture_type == "table":
142
- cv2.rectangle(mask, (width//3, height//3), (width*2//3, height*2//3), 255, -1)
143
- elif furniture_type == "chair":
144
- cv2.circle(mask, (width*3//5, height*2//3), height//6, 255, -1)
145
-
146
- return Image.fromarray(mask)
147
-
148
- def randomize_seed_fn(seed: int, randomize_seed: bool) -> int:
149
- if randomize_seed:
150
- seed = random.randint(0, MAX_SEED)
151
- return seed
 
152
 
153
  def get_additional_prompt():
154
  prompt = "hyperrealistic photography,extremely detailed,(intricate details),unity 8k wallpaper,ultra detailed"
155
  top = ["tank top", "blouse", "button up shirt", "sweater", "corset top"]
156
- bottom = ["short skirt", "athletic shorts", "jean shorts", "pleated skirt", "short skirt", "leggings", "high-waisted shorts"]
157
- accessory = ["knee-high boots", "gloves", "Thigh-high stockings", "Garter belt", "choker", "necklace", "headband", "headphones"]
 
 
158
  return f"{prompt}, {random.choice(top)}, {random.choice(bottom)}, {random.choice(accessory)}, score_9"
159
- # outfit = ["schoolgirl outfit", "playboy outfit", "red dress", "gala dress", "cheerleader outfit", "nurse outfit", "Kimono"]
160
 
161
  def get_prompt(prompt, additional_prompt):
162
  interior = "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"
163
- default = "hyperrealistic photography,extremely detailed,(intricate details),unity 8k wallpaper,ultra detailed"
164
- default2 = f"professional 3d model {prompt},octane render,highly detailed,volumetric,dramatic lighting,hyperrealistic photography,extremely detailed,(intricate details),unity 8k wallpaper,ultra detailed"
165
  randomize = get_additional_prompt()
166
- # nude = "NSFW,((nude)),medium bare breasts,hyperrealistic photography,extremely detailed,(intricate details),unity 8k wallpaper,ultra detailed"
167
- # bodypaint = "((fully naked with no clothes)),nude naked seethroughxray,invisiblebodypaint,rating_newd,NSFW"
168
  lab_girl = "hyperrealistic photography, extremely detailed, shy assistant wearing minidress boots and gloves, laboratory background, score_9, 1girl"
169
  pet_play = "hyperrealistic photography, extremely detailed, playful, blush, glasses, collar, score_9, HDA_pet_play"
170
  bondage = "hyperrealistic photography, extremely detailed, submissive, glasses, score_9, HDA_Bondage"
171
- # ahegao = "((invisible clothing)), hyperrealistic photography,exposed vagina,sexy,nsfw,HDA_Ahegao"
172
  ahegao2 = "(invisiblebodypaint),rating_newd,HDA_Ahegao"
173
  athleisure = "hyperrealistic photography, extremely detailed, 1girl athlete, exhausted embarrassed sweaty,outdoors, ((athleisure clothing)), score_9"
174
  atompunk = "((atompunk world)), hyperrealistic photography, extremely detailed, short hair, bodysuit, glasses, neon cyberpunk background, score_9"
@@ -176,330 +200,253 @@ def get_prompt(prompt, additional_prompt):
176
  nundress = "hyperrealistic photography, extremely detailed, shy, blushing, fantasy background, score_9, HDA_NunDress"
177
  naked_hoodie = "hyperrealistic photography, extremely detailed, medium hair, cityscape, (neon lights), score_9, HDA_NakedHoodie"
178
  abg = "(1girl, asian body covered in words, words on body, tattoos of (words) on body),(masterpiece, best quality),medium breasts,(intricate details),unity 8k wallpaper,ultra detailed,(pastel colors),beautiful and aesthetic,see-through (clothes),detailed,solo"
179
- # shibari = "extremely detailed, hyperrealistic photography, earrings, blushing, lace choker, tattoo, medium hair, score_9, HDA_Shibari"
180
  shibari2 = "octane render, highly detailed, volumetric, HDA_Shibari"
181
-
182
  if prompt == "":
183
- girls = [randomize, pet_play, bondage, lab_girl, athleisure, atompunk, maid, nundress, naked_hoodie, abg, shibari2, ahegao2]
184
- prompts_nsfw = [abg, shibari2, ahegao2]
185
- prompt = f"{random.choice(girls)}"
186
- prompt = f"boho chic"
187
- # print(f"-------------{preset}-------------")
 
 
 
 
 
 
 
 
 
 
 
 
188
  else:
189
- prompt = f"Photo from Pinterest of {prompt} {interior}"
190
- # prompt = default2
191
- return f"{prompt} f{additional_prompt}"
 
 
 
 
192
 
193
- style_list = [
194
- {
195
- "name": "None",
196
- "prompt": ""
197
- },
198
- {
199
- "name": "Minimalistic",
200
- "prompt": "Minimalist interior design,clean lines,neutral colors,uncluttered space,functional furniture,lots of natural light"
201
- },
202
- {
203
- "name": "Boho",
204
- "prompt": "Bohemian chic interior,eclectic mix of patterns and textures,vintage furniture,plants,woven textiles,warm earthy colors"
205
- },
206
- {
207
- "name": "Farmhouse",
208
- "prompt": "Modern farmhouse interior,rustic wood elements,shiplap walls,neutral color palette,industrial accents,cozy textiles"
209
- },
210
- {
211
- "name": "Saudi Prince",
212
- "prompt": "Opulent gold interior,luxurious ornate furniture,crystal chandeliers,rich fabrics,marble floors,intricate Arabic patterns"
213
- },
214
- {
215
- "name": "Neoclassical",
216
- "prompt": "Neoclassical interior design,elegant columns,ornate moldings,symmetrical layout,refined furniture,muted color palette"
217
- },
218
- {
219
- "name": "Eclectic",
220
- "prompt": "Eclectic interior design,mix of styles and eras,bold color combinations,diverse furniture pieces,unique art objects"
221
- },
222
- {
223
- "name": "Parisian",
224
- "prompt": "Parisian apartment interior,all-white color scheme,ornate moldings,herringbone wood floors,elegant furniture,large windows"
225
- },
226
- {
227
- "name": "Hollywood",
228
- "prompt": "Hollywood Regency interior,glamorous and luxurious,bold colors,mirrored surfaces,velvet upholstery,gold accents"
229
- },
230
- {
231
- "name": "Scandinavian",
232
- "prompt": "Scandinavian interior design,light wood tones,white walls,minimalist furniture,cozy textiles,hygge atmosphere"
233
- },
234
- {
235
- "name": "Beach",
236
- "prompt": "Coastal beach house interior,light blue and white color scheme,weathered wood,nautical accents,sheer curtains,ocean view"
237
- },
238
- {
239
- "name": "Japanese",
240
- "prompt": "Traditional Japanese interior,tatami mats,shoji screens,low furniture,zen garden view,minimalist decor,natural materials"
241
- },
242
- {
243
- "name": "Midcentury Modern",
244
- "prompt": "Mid-century modern interior,1950s-60s style furniture,organic shapes,warm wood tones,bold accent colors,large windows"
245
- },
246
- {
247
- "name": "Retro Futurism",
248
- "prompt": "Neon (atompunk world) retro cyberpunk background",
249
- },
250
- {
251
- "name": "Texan",
252
- "prompt": "Western cowboy interior,rustic wood beams,leather furniture,cowhide rugs,antler chandeliers,southwestern patterns"
253
- },
254
- {
255
- "name": "Matrix",
256
- "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"
257
- }
258
- ]
259
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  styles = {k["name"]: (k["prompt"]) for k in style_list}
261
  STYLE_NAMES = list(styles.keys())
262
 
 
263
  def apply_style(style_name):
264
- if style_name in styles:
265
- p = styles.get(style_name, "none")
266
- return p
267
-
268
-
269
- css = """
270
- h1, h2, h3 {
271
- text-align: center;
272
- display: block;
273
- }
274
- footer {
275
- visibility: hidden;
276
- }
277
- .gradio-container {
278
- max-width: 1100px !important;
279
- }
280
- .gr-image {
281
- display: flex;
282
- justify-content: center;
283
- align-items: center;
284
- width: 100%;
285
- height: 512px;
286
- overflow: hidden;
287
- }
288
- .gr-image img {
289
- width: 100%;
290
- height: 100%;
291
- object-fit: cover;
292
- object-position: center;
293
- }
294
- """
295
- with gr.Blocks(theme="bethecloud/storj_theme", css=css) as demo:
296
- #############################################################################
297
- with gr.Row():
298
- with gr.Accordion("Advanced options", open=show_options, visible=show_options):
299
- num_images = gr.Slider(
300
- label="Images", minimum=1, maximum=4, value=1, step=1
301
- )
302
- image_resolution = gr.Slider(
303
- label="Image resolution",
304
- minimum=256,
305
- maximum=1024,
306
- value=512,
307
- step=256,
308
- )
309
- preprocess_resolution = gr.Slider(
310
- label="Preprocess resolution",
311
- minimum=128,
312
- maximum=1024,
313
- value=512,
314
- step=1,
315
- )
316
- num_steps = gr.Slider(
317
- label="Number of steps", minimum=1, maximum=100, value=15, step=1
318
- ) # 20/4.5 or 12 without lora, 4 with lora
319
- guidance_scale = gr.Slider(
320
- label="Guidance scale", minimum=0.1, maximum=30.0, value=5.5, step=0.1
321
- ) # 5 without lora, 2 with lora
322
- seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
323
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
324
- a_prompt = gr.Textbox(
325
- label="Additional prompt",
326
- value = "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"
327
- )
328
- n_prompt = gr.Textbox(
329
- label="Negative prompt",
330
- value="EasyNegativeV2, fcNeg, (badhandv4:1.4), (worst quality, low quality, bad quality, normal quality:2.0), (bad hands, missing fingers, extra fingers:2.0)",
331
- )
332
- #############################################################################
333
- # input text
334
- with gr.Column():
335
- prompt = gr.Textbox(
336
- label="Custom Design",
337
- placeholder="Enter a description (optional)",
338
- )
339
- # design options
340
- with gr.Row(visible=True):
341
- style_selection = gr.Radio(
342
- show_label=True,
343
- container=True,
344
- interactive=True,
345
- choices=STYLE_NAMES,
346
- value="None",
347
- label="Design Styles",
348
- )
349
- # input image
350
- with gr.Row(equal_height=True):
351
- with gr.Column(scale=1, min_width=300):
352
- image = gr.Image(
353
- label="Input",
354
- sources=["upload"],
355
- show_label=True,
356
- mirror_webcam=True,
357
- type="pil",
358
- )
359
- # run button
360
- with gr.Column():
361
- run_button = gr.Button(value="Use this one", size="lg", visible=False)
362
- # output image
363
- with gr.Column(scale=1, min_width=300):
364
- result = gr.Image(
365
- label="Output",
366
- interactive=False,
367
- type="pil",
368
- show_share_button= False,
369
- )
370
- # Use this image button
371
- with gr.Column():
372
- use_ai_button = gr.Button(value="Use this one", size="lg", visible=False)
373
- config = [
374
- image,
375
- style_selection,
376
- prompt,
377
- a_prompt,
378
- n_prompt,
379
- num_images,
380
- image_resolution,
381
- preprocess_resolution,
382
- num_steps,
383
- guidance_scale,
384
- seed,
385
- ]
386
-
387
- with gr.Row():
388
- helper_text = gr.Markdown("## Tap and hold (on mobile) to save the image.", visible=True)
389
-
390
- # image processing
391
- @gr.on(triggers=[image.upload, prompt.submit, run_button.click], inputs=config, outputs=result, show_progress="minimal")
392
- def auto_process_image(image, style_selection, prompt, a_prompt, n_prompt, num_images, image_resolution, preprocess_resolution, num_steps, guidance_scale, seed, progress=gr.Progress(track_tqdm=True)):
393
- return process_image(image, style_selection, prompt, a_prompt, n_prompt, num_images, image_resolution, preprocess_resolution, num_steps, guidance_scale, seed)
394
-
395
- # AI image processing
396
- @gr.on(triggers=[use_ai_button.click], inputs=[result] + config, outputs=[image, result], show_progress="minimal")
397
- def submit(previous_result, image, style_selection, prompt, a_prompt, n_prompt, num_images, image_resolution, preprocess_resolution, num_steps, guidance_scale, seed, progress=gr.Progress(track_tqdm=True)):
398
- # First, yield the previous result to update the input image immediately
399
- yield previous_result, gr.update()
400
- # Then, process the new input image
401
- new_result = process_image(previous_result, style_selection, prompt, a_prompt, n_prompt, num_images, image_resolution, preprocess_resolution, num_steps, guidance_scale, seed)
402
- # Finally, yield the new result
403
- yield previous_result, new_result
404
-
405
- # Turn off buttons when processing
406
- @gr.on(triggers=[image.upload, use_ai_button.click, run_button.click], inputs=None, outputs=[run_button, use_ai_button], show_progress="hidden")
407
- def turn_buttons_off():
408
- return gr.update(visible=False), gr.update(visible=False)
409
-
410
- # Turn on buttons when processing is complete
411
- @gr.on(triggers=[result.change], inputs=None, outputs=[use_ai_button, run_button], show_progress="hidden")
412
- def turn_buttons_on():
413
- return gr.update(visible=True), gr.update(visible=True)
414
-
415
- @spaces.GPU(duration=12)
416
- @torch.inference_mode()
417
- def process_image(
418
- image,
419
- style_selection,
420
- prompt,
421
- a_prompt,
422
- n_prompt,
423
- num_images,
424
- image_resolution,
425
- preprocess_resolution,
426
- num_steps,
427
- guidance_scale,
428
- seed,
429
  ):
430
- seed = random.randint(0, MAX_SEED)
431
- generator = torch.cuda.manual_seed(seed)
432
-
433
- preprocessor.load("NormalBae")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  control_image = preprocessor(
435
  image=image,
436
  image_resolution=image_resolution,
437
  detect_resolution=preprocess_resolution,
438
  )
439
-
440
- if style_selection is not None and style_selection != "None":
441
- prompt = f"Photo from Pinterest of {apply_style(style_selection)} {prompt},{a_prompt}"
 
 
 
 
 
442
  else:
443
- prompt = str(get_prompt(prompt, a_prompt))
 
 
444
  negative_prompt = str(n_prompt)
445
- print(prompt)
446
-
447
- # Generate the initial room image
448
- initial_result = pipe(
449
- prompt=prompt,
450
- negative_prompt=negative_prompt,
451
- guidance_scale=guidance_scale,
452
- num_images_per_prompt=1,
453
- num_inference_steps=num_steps,
454
- generator=generator,
455
- image=control_image,
456
- ).images[0]
457
-
458
- # # Randomly choose whether to add furniture and which type
459
- # furniture_types = ["sofa", "table", "chair", "dresser", "bookshelf", "desk", "coffee table"]
460
- # furniture_type = random.choice(furniture_types)
461
-
462
-
463
- # furniture_mask = generate_furniture_mask(initial_result, furniture_type)
464
- # furniture_prompt = f"{prompt}, with a {furniture_type} in the style of {style_selection}"
465
- # print(furniture_prompt)
466
-
467
- # # Use the inpainting pipeline to add furniture
468
- # final_result = inpaint_pipe(
469
- # prompt=furniture_prompt,
470
- # image=initial_result,
471
- # mask_image=furniture_mask,
472
- # negative_prompt=negative_prompt,
473
- # num_inference_steps=num_steps,
474
- # guidance_scale=guidance_scale,
475
- # generator=generator,
476
- # ).images[0]
477
-
478
- # Save and upload results
479
- timestamp = int(time.time())
480
- img_path = f"{timestamp}.jpg"
481
- results_path = f"{timestamp}_out.jpg"
482
- imageio.imsave(img_path, image)
483
- imageio.imsave(results_path, initial_result)
484
- api.upload_file(
485
- path_or_fileobj=img_path,
486
- path_in_repo=img_path,
487
- repo_id="broyang/interior-ai-outputs",
488
- repo_type="dataset",
489
- token=API_KEY,
490
- run_as_future=True,
491
- )
492
- api.upload_file(
493
- path_or_fileobj=results_path,
494
- path_in_repo=results_path,
495
- repo_id="broyang/interior-ai-outputs",
496
- repo_type="dataset",
497
- token=API_KEY,
498
- run_as_future=True,
499
- )
 
 
 
 
 
 
 
500
  return initial_result
501
 
502
- if prod:
503
- demo.queue(max_size=20).launch(server_name="localhost", server_port=port)
504
- else:
505
- demo.queue(api_open=False).launch(show_api=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Configuration
2
+ prod = False # This variable is no longer used for launching, but kept for potential future use
3
+ port = 8080 # This variable is no longer used for launching, but kept for potential future use
4
+ show_options = False # This variable is no longer used for UI visibility
 
 
5
 
6
  import os
7
  import random
8
  import time
9
+ # Removed gradio import as UI is being removed
10
  import numpy as np
11
  import spaces
12
  import imageio
 
19
  ControlNetModel,
20
  DPMSolverMultistepScheduler,
21
  StableDiffusionControlNetPipeline,
22
+ # StableDiffusionInpaintPipeline, # Commented out as inpainting part was commented
23
+ # AutoencoderKL, # Commented out as VAE part was commented
24
  )
25
+ # Assuming controlnet_aux_local is a local package or needs to be installed separately
26
  from controlnet_aux_local import NormalBaeDetector
27
 
28
  MAX_SEED = np.iinfo(np.int32).max
29
  API_KEY = os.environ.get("API_KEY", None)
30
  # os.environ['HF_HOME'] = '/data/.huggingface'
 
31
  print("CUDA version:", torch.version.cuda)
32
  print("loading everything")
33
+ compiled = False # This variable is no longer explicitly set to True after compilation print
34
  api = HfApi()
35
 
36
+
37
  class Preprocessor:
38
  MODEL_ID = "lllyasviel/Annotators"
39
 
 
46
  return
47
  elif name == "NormalBae":
48
  print("Loading NormalBae")
49
+ # Ensure model is moved to cuda if available
50
+ self.model = NormalBaeDetector.from_pretrained(self.MODEL_ID).to("cuda" if torch.cuda.is_available() else "cpu")
51
+ if torch.cuda.is_available():
52
+ torch.cuda.empty_cache()
53
  self.name = name
54
  else:
55
  raise ValueError
56
  return
57
 
58
  def __call__(self, image: Image.Image, **kwargs) -> Image.Image:
59
+ # Ensure model is on the correct device before calling
60
+ device = "cuda" if torch.cuda.is_available() else "cpu"
61
+ if self.model.device.type != device:
62
+ self.model.to(device)
63
  return self.model(image, **kwargs)
64
 
65
+ # Load models and preprocessor directly without gr.NO_RELOAD check
66
+ # This block will execute when the script is imported or run
67
+ # Controlnet Normal
68
+ model_id = "lllyasviel/control_v11p_sd15_normalbae"
69
+ print("initializing controlnet")
70
+ # Ensure models are loaded onto the correct device
71
+ device = "cuda" if torch.cuda.is_available() else "cpu"
72
+ controlnet = ControlNetModel.from_pretrained(
73
+ model_id,
74
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, # Use float32 if CUDA is not available
75
+ attn_implementation="flash_attention_2" if torch.cuda.is_available() else None, # Flash attention only for CUDA
76
+ ).to(device)
77
+ # Scheduler
78
+ scheduler = DPMSolverMultistepScheduler.from_pretrained(
79
+ "ashllay/stable-diffusion-v1-5-archive",
80
+ solver_order=2,
81
+ subfolder="scheduler",
82
+ use_karras_sigmas=True,
83
+ final_sigmas_type="sigma_min",
84
+ algorithm_type="sde-dpmsolver++",
85
+ prediction_type="epsilon",
86
+ thresholding=False,
87
+ denoise_final=True,
88
+ # device_map="cuda", # device_map can sometimes cause issues, better to move after loading
89
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, # Use float32 if CUDA is not available
90
+ )
91
+ # Move scheduler to device after loading
92
+ scheduler.to(device)
93
+
94
+ # Stable Diffusion Pipeline URL
95
+ base_model_url = "https://huggingface.co/Lykon/AbsoluteReality/blob/main/AbsoluteReality_1.8.1_pruned.safetensors"
96
+ print('loading pipe')
97
+ pipe = StableDiffusionControlNetPipeline.from_single_file(
98
+ base_model_url,
99
+ safety_checker=None,
100
+ controlnet=controlnet,
101
+ scheduler=scheduler,
102
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, # Use float32 if CUDA is not available
103
+ ).to(device)
104
+
105
+ print("loading preprocessor")
106
+ preprocessor = Preprocessor()
107
+ preprocessor.load("NormalBae") # Preprocessor is loaded here
108
+
109
+ # Load textual inversions
110
+ try:
111
+ pipe.load_textual_inversion(
112
+ "broyang/hentaidigitalart_v20", weight_name="EasyNegativeV2.safetensors", token="EasyNegativeV2",
113
+ )
114
+ pipe.load_textual_inversion(
115
+ "broyang/hentaidigitalart_v20", weight_name="badhandv4.pt", token="badhandv4"
116
+ )
117
+ pipe.load_textual_inversion(
118
+ "broyang/hentaidigitalart_v20", weight_name="fcNeg-neg.pt", token="fcNeg-neg"
119
+ )
120
+ pipe.load_textual_inversion(
121
+ "broyang/hentaidigitalart_v20", weight_name="HDA_Ahegao.pt", token="HDA_Ahegao"
122
+ )
123
+ pipe.load_textual_inversion(
124
+ "broyang/hentaidigitalart_v20", weight_name="HDA_Bondage.pt", token="HDA_Bondage"
125
+ )
126
+ pipe.load_textual_inversion(
127
+ "broyang/hentaidigitalart_v20", weight_name="HDA_pet_play.pt", token="HDA_pet_play"
128
+ )
129
+ pipe.load_textual_inversion(
130
+ "broyang/hentaidigitalart_v20",
131
+ weight_name="HDA_unconventional maid.pt",
132
+ token="HDA_unconventional_maid",
133
+ )
134
+ pipe.load_textual_inversion(
135
+ "broyang/hentaidigitalart_v20", weight_name="HDA_NakedHoodie.pt", token="HDA_NakedHoodie"
136
  )
137
+ pipe.load_textual_inversion(
138
+ "broyang/hentaidigitalart_v20", weight_name="HDA_NunDress.pt", token="HDA_NunDress"
139
+ )
140
+ pipe.load_textual_inversion(
141
+ "broyang/hentaidigitalart_v20", weight_name="HDA_Shibari.pt", token="HDA_Shibari"
142
+ )
143
+ except Exception as e:
144
+ print(f"Error loading textual inversions: {e}")
145
+ # Handle cases where loading textual inversions might fail, e.g., file not found
146
 
147
+ print("---------------Loaded controlnet pipeline---------------")
148
+ if torch.cuda.is_available():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  torch.cuda.empty_cache()
150
  gc.collect()
151
  print(f"CUDA memory allocated: {torch.cuda.max_memory_allocated(device='cuda') / 1e9:.2f} GB")
152
+ # Removed "Model Compiled!" print as compilation is not explicitly handled here
153
+
154
+
155
+ # Removed generate_furniture_mask as inpainting part was commented out
156
+ # def generate_furniture_mask(image, furniture_type):
157
+ # image_np = np.array(image)
158
+ # height, width = image_np.shape[:2]
159
+ # mask = np.zeros((height, width), dtype=np.uint8)
160
+ # if furniture_type == "sofa":
161
+ # cv2.rectangle(mask, (width // 4, int(height * 0.6)), (width * 3 // 4, height), 255, -1)
162
+ # elif furniture_type == "table":
163
+ # cv2.rectangle(mask, (width // 3, height // 3), (width * 2 // 3, height * 2 // 3), 255, -1)
164
+ # elif furniture_type == "chair":
165
+ # cv2.circle(mask, (width * 3 // 5, height * 2 // 3), height // 6, 255, -1)
166
+ # return Image.fromarray(mask)
167
+
168
+ # Removed randomize_seed_fn as the logic is directly in process_image
169
+ # def randomize_seed_fn(seed: int, randomize_seed: bool) -> int:
170
+ # if randomize_seed:
171
+ # seed = random.randint(0, MAX_SEED)
172
+ # return seed
173
+
174
 
175
  def get_additional_prompt():
176
  prompt = "hyperrealistic photography,extremely detailed,(intricate details),unity 8k wallpaper,ultra detailed"
177
  top = ["tank top", "blouse", "button up shirt", "sweater", "corset top"]
178
+ bottom = ["short skirt", "athletic shorts", "jean shorts", "pleated skirt", "short skirt",
179
+ "leggings", "high-waisted shorts"]
180
+ accessory = ["knee-high boots", "gloves", "Thigh-high stockings", "Garter belt", "choker",
181
+ "necklace", "headband", "headphones"]
182
  return f"{prompt}, {random.choice(top)}, {random.choice(bottom)}, {random.choice(accessory)}, score_9"
183
+
184
 
185
  def get_prompt(prompt, additional_prompt):
186
  interior = "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"
187
+ # default = "hyperrealistic photography,extremely detailed,(intricate details),unity 8k wallpaper,ultra detailed" # Not used
188
+ # default2 = f"professional 3d model {prompt},octane render,highly detailed,volumetric,dramatic lighting,hyperrealistic photography,extremely detailed,(intricate details),unity 8k wallpaper,ultra detailed" # Not used
189
  randomize = get_additional_prompt()
190
+ # nude = "NSFW,((nude)),medium bare breasts,hyperrealistic photography,extremely detailed,(intricate details),unity 8k wallpaper,ultra detailed" # Not used
191
+ # bodypaint = "((fully naked with no clothes)),nude naked seethroughxray,invisiblebodypaint,rating_newd,NSFW" # Not used
192
  lab_girl = "hyperrealistic photography, extremely detailed, shy assistant wearing minidress boots and gloves, laboratory background, score_9, 1girl"
193
  pet_play = "hyperrealistic photography, extremely detailed, playful, blush, glasses, collar, score_9, HDA_pet_play"
194
  bondage = "hyperrealistic photography, extremely detailed, submissive, glasses, score_9, HDA_Bondage"
195
+ # ahegao = "((invisible clothing)), hyperrealistic photography,exposed vagina,sexy,nsfw,HDA_Ahegao" # Not used
196
  ahegao2 = "(invisiblebodypaint),rating_newd,HDA_Ahegao"
197
  athleisure = "hyperrealistic photography, extremely detailed, 1girl athlete, exhausted embarrassed sweaty,outdoors, ((athleisure clothing)), score_9"
198
  atompunk = "((atompunk world)), hyperrealistic photography, extremely detailed, short hair, bodysuit, glasses, neon cyberpunk background, score_9"
 
200
  nundress = "hyperrealistic photography, extremely detailed, shy, blushing, fantasy background, score_9, HDA_NunDress"
201
  naked_hoodie = "hyperrealistic photography, extremely detailed, medium hair, cityscape, (neon lights), score_9, HDA_NakedHoodie"
202
  abg = "(1girl, asian body covered in words, words on body, tattoos of (words) on body),(masterpiece, best quality),medium breasts,(intricate details),unity 8k wallpaper,ultra detailed,(pastel colors),beautiful and aesthetic,see-through (clothes),detailed,solo"
203
+ # shibari = "extremely detailed, hyperrealistic photography, earrings, blushing, lace choker, tattoo, medium hair, score_9, HDA_Shibari" # Not used
204
  shibari2 = "octane render, highly detailed, volumetric, HDA_Shibari"
205
+
206
  if prompt == "":
207
+ # This block seems to generate prompts for 'girls' which might not be relevant for interior design API
208
+ # Consider if this random girl prompt generation is needed for the interior design API
209
+ girls = [randomize, pet_play, bondage, lab_girl, athleisure, atompunk, maid, nundress,
210
+ naked_hoodie, abg, shibari2, ahegao2]
211
+ # prompts_nsfw = [abg, shibari2, ahegao2] # Not used
212
+ # prompt = f"{random.choice(girls)}" # This line would overwrite the input prompt
213
+ prompt = f"boho chic" # This line also overwrites the input prompt
214
+ # The logic here seems inconsistent with using an input 'prompt'.
215
+ # Assuming the intention is to use the input 'prompt' for custom designs,
216
+ # and apply a style or default interior context.
217
+ # Let's revise this to prioritize the input prompt.
218
+ if additional_prompt:
219
+ # Combine input prompt with additional prompt
220
+ return f"{prompt}, {additional_prompt}"
221
+ else:
222
+ # If no additional prompt, just use the input prompt with interior context
223
+ return f"Photo from Pinterest of {prompt} {interior}"
224
  else:
225
+ # If a prompt is provided, use it with the interior context
226
+ # The original logic here was redundant with the 'if prompt == ""' block
227
+ # Let's simplify based on whether a prompt is provided
228
+ if additional_prompt:
229
+ return f"Photo from Pinterest of {prompt} {interior}, {additional_prompt}"
230
+ else:
231
+ return f"Photo from Pinterest of {prompt} {interior}"
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
+ style_list = [
235
+ {"name": "None",
236
+ "prompt": ""
237
+ },
238
+ {"name": "Minimalistic",
239
+ "prompt": "Minimalist interior design,clean lines,neutral colors,uncluttered space,functional furniture,lots of natural light"
240
+ },
241
+ {"name": "Boho",
242
+ "prompt": "Bohemian chic interior,eclectic mix of patterns and textures,vintage furniture,plants,woven textiles,warm earthy colors"
243
+ },
244
+ {"name": "Farmhouse",
245
+ "prompt": "Modern farmhouse interior,rustic wood elements,shiplap walls,neutral color palette,industrial accents,cozy textiles"
246
+ },
247
+ {"name": "Saudi Prince",
248
+ "prompt": "Opulent gold interior,luxurious ornate furniture,crystal chandeliers,rich fabrics,marble floors,intricate Arabic patterns"
249
+ },
250
+ {"name": "Neoclassical",
251
+ "prompt": "Neoclassical interior design,elegant columns,ornate moldings,symmetrical layout,refined furniture,muted color palette"
252
+ },
253
+ {"name": "Eclectic",
254
+ "prompt": "Eclectic interior design,mix of styles and eras,bold color combinations,diverse furniture pieces,unique art objects"
255
+ },
256
+ {"name": "Parisian",
257
+ "prompt": "Parisian apartment interior,all-white color scheme,ornate moldings,herringbone wood floors,elegant furniture,large windows"
258
+ },
259
+ {"name": "Hollywood",
260
+ "prompt": "Hollywood Regency interior,glamorous and luxurious,bold colors,mirrored surfaces,velvet upholstery,gold accents"
261
+ },
262
+ {"name": "Scandinavian",
263
+ "prompt": "Scandinavian interior design,light wood tones,white walls,minimalist furniture,cozy textiles,hygge atmosphere"
264
+ },
265
+ {"name": "Beach",
266
+ "prompt": "Coastal beach house interior,light blue and white color scheme,weathered wood,nautical accents,sheer curtains,ocean view"
267
+ },
268
+ {"name": "Japanese",
269
+ "prompt": "Traditional Japanese interior,tatami mats,shoji screens,low furniture,zen garden view,minimalist decor,natural materials"
270
+ },
271
+ {"name": "Midcentury Modern",
272
+ "prompt": "Mid-century modern interior,1950s-60s style furniture,organic shapes,warm wood tones,bold accent colors,large windows"
273
+ },
274
+ {"name": "Retro Futurism",
275
+ "prompt": "Neon (atompunk world) retro cyberpunk background",
276
+ },
277
+ {"name": "Texan",
278
+ "prompt": "Western cowboy interior,rustic wood beams,leather furniture,cowhide rugs,antler chandeliers,southwestern patterns"
279
+ },
280
+ {"name": "Matrix",
281
+ "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"
282
+ }]
283
  styles = {k["name"]: (k["prompt"]) for k in style_list}
284
  STYLE_NAMES = list(styles.keys())
285
 
286
+
287
  def apply_style(style_name):
288
+ # Ensure style_name exists in styles dictionary
289
+ return styles.get(style_name, "") # Return empty string if style not found
290
+
291
+
292
+ # Removed css variable as it was for Gradio UI
293
+ # css = """..."""
294
+
295
+ # Removed gr.Blocks context manager and everything inside it
296
+
297
+
298
+ # Modified process_image to be a standalone function callable by an API endpoint
299
+ # Removed @spaces.GPU and @torch.inference_mode decorators if the API framework handles this
300
+ # Added type hints for clarity
301
+ def process_image_api(
302
+ image: Image.Image,
303
+ style_selection: str = "None",
304
+ prompt: str = "",
305
+ a_prompt: str = "",
306
+ n_prompt: str = "EasyNegativeV2, fcNeg, (badhandv4:1.4), (worst quality, low quality, bad quality, normal quality:2.0), (bad hands, missing fingers, extra fingers:2.0)",
307
+ num_images: int = 1, # Kept for potential future use, but pipeline currently generates 1
308
+ image_resolution: int = 512,
309
+ preprocess_resolution: int = 512,
310
+ num_steps: int = 15,
311
+ guidance_scale: float = 5.5,
312
+ seed: int = -1, # Use -1 to indicate random seed if not provided
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  ):
314
+ """
315
+ Processes an input image to generate a new image based on style and prompts.
316
+
317
+ Args:
318
+ image: Input PIL Image.
319
+ style_selection: Name of the design style to apply.
320
+ prompt: Custom design prompt.
321
+ a_prompt: Additional positive prompt.
322
+ n_prompt: Negative prompt.
323
+ num_images: Number of images to generate (currently only 1 supported by pipeline).
324
+ image_resolution: Resolution for the output image.
325
+ preprocess_resolution: Resolution for the preprocessor.
326
+ num_steps: Number of inference steps.
327
+ guidance_scale: Guidance scale for the diffusion process.
328
+ seed: Random seed for reproducibility. Use -1 for random seed.
329
+
330
+ Returns:
331
+ A PIL Image of the generated result.
332
+ """
333
+ # Use provided seed or generate a random one
334
+ current_seed = seed if seed != -1 else random.randint(0, MAX_SEED)
335
+ generator = torch.cuda.manual_seed(current_seed) if torch.cuda.is_available() else torch.manual_seed(current_seed)
336
+
337
+ # Ensure preprocessor is loaded
338
+ if preprocessor.name != "NormalBae":
339
+ preprocessor.load("NormalBae")
340
+
341
+ # Ensure preprocessor model is on the correct device
342
+ preprocessor.model.to("cuda" if torch.cuda.is_available() else "cpu")
343
+
344
+ # Generate control image
345
  control_image = preprocessor(
346
  image=image,
347
  image_resolution=image_resolution,
348
  detect_resolution=preprocess_resolution,
349
  )
350
+
351
+ # Construct the full prompt
352
+ if style_selection and style_selection != "None":
353
+ # Apply selected style and combine with custom prompt and additional prompt
354
+ style_prompt = apply_style(style_selection)
355
+ # Combine prompts, ensuring no empty strings lead to awkward commas
356
+ prompt_parts = [f"Photo from Pinterest of {prompt}" if prompt else None, style_prompt if style_prompt else None, a_prompt if a_prompt else None]
357
+ full_prompt = ", ".join(filter(None, prompt_parts))
358
  else:
359
+ # Use custom prompt and additional prompt with default interior context
360
+ full_prompt = get_prompt(prompt, a_prompt)
361
+
362
  negative_prompt = str(n_prompt)
363
+ print(f"Using prompt: {full_prompt}")
364
+ print(f"Using negative prompt: {negative_prompt}")
365
+ print(f"Using seed: {current_seed}")
366
+
367
+
368
+ # Generate the image using the pipeline
369
+ # Ensure the pipeline is on the correct device
370
+ pipe.to("cuda" if torch.cuda.is_available() else "cpu")
371
+
372
+ with torch.no_grad(): # Use no_grad for inference to save memory and speed
373
+ initial_result = pipe(
374
+ prompt=full_prompt,
375
+ negative_prompt=negative_prompt,
376
+ guidance_scale=guidance_scale,
377
+ num_images_per_prompt=1, # Pipeline always generates 1 image here
378
+ num_inference_steps=num_steps,
379
+ generator=generator,
380
+ image=control_image,
381
+ ).images[0]
382
+
383
+ # Save and upload results (optional, depending on API requirements)
384
+ # This part might be handled by the API caller or a separate service
385
+ # Keeping it for now as it was in the original script
386
+ try:
387
+ timestamp = int(time.time())
388
+ # Saving input image is generally not needed for API response, but keeping for consistency
389
+ # img_path = f"{timestamp}_input.jpg"
390
+ results_path = f"{timestamp}_output.jpg"
391
+ # imageio.imsave(img_path, image) # Removed saving input image
392
+ imageio.imsave(results_path, initial_result)
393
+
394
+ # Uploading files might not be desired for a general API,
395
+ # consider making this optional or removing if the API just returns the image
396
+ if API_KEY: # Only attempt upload if API_KEY is available
397
+ print(f"Uploading result image to broyang/interior-ai-outputs/{results_path}")
398
+ try:
399
+ api.upload_file(
400
+ path_or_fileobj=results_path,
401
+ path_in_repo=results_path,
402
+ repo_id="broyang/interior-ai-outputs",
403
+ repo_type="dataset",
404
+ token=API_KEY,
405
+ run_as_future=True, # Asynchronous upload
406
+ )
407
+ # Removed input image upload
408
+ # api.upload_file(
409
+ # path_or_fileobj=img_path,
410
+ # path_in_repo=img_path,
411
+ # repo_id="broyang/interior-ai-outputs",
412
+ # repo_type="dataset",
413
+ # token=API_KEY,
414
+ # run_as_future=True,
415
+ # )
416
+ except Exception as e:
417
+ print(f"Error uploading file to Hugging Face Hub: {e}")
418
+ else:
419
+ print("Hugging Face API Key not found, skipping file upload.")
420
+
421
+ except Exception as e:
422
+ print(f"Error saving or uploading image: {e}")
423
+
424
+
425
  return initial_result
426
 
427
+ # The script now defines the process_image_api function.
428
+ # To use this as an API, you would typically import this script
429
+ # into a web framework like FastAPI and define an endpoint that
430
+ # calls process_image_api with the appropriate parameters from the request.
431
+
432
+ # Example of how you might call the function (this part is for demonstration,
433
+ # you would remove it when integrating into a web framework):
434
+ # if __name__ == "__main__":
435
+ # # Create a dummy input image (e.g., a black square)
436
+ # dummy_image = Image.new('RGB', (512, 512), color = 'red')
437
+ # print("Generating a sample image...")
438
+ # # Call the processing function with sample parameters
439
+ # generated_image = process_image_api(
440
+ # image=dummy_image,
441
+ # style_selection="Boho",
442
+ # prompt="cozy living room",
443
+ # a_prompt="warm lighting",
444
+ # num_steps=20,
445
+ # guidance_scale=7.0,
446
+ # seed=42
447
+ # )
448
+ # # You can now save or display the generated_image
449
+ # generated_image.save("sample_output.jpg")
450
+ # print("Sample image generated and saved as sample_output.jpg")
451
+
452
+ # Removed the demo.queue().launch() calls