LPX55 commited on
Commit
c4bfaad
·
1 Parent(s): 391c5a6

♻️ refactor(app): refactor and restructure the application code for better organization and maintainability

Browse files

- refactor the code into separate functions and classes for better modularity
- reorganize the layout and structure of the application for improved readability and maintainability
- update variable names and comments for better clarity and understanding

✨ feat(app): add new features and functionality to the application

- add new user interface elements and components for improved user experience
- implement new algorithms and models for enhanced image processing capabilities
- add support for additional image formats and sizes

🐛 fix(app): fix bugs and issues in the application code

- fix errors and exceptions in the image processing pipeline
- resolve issues with user interface elements and components
- improve overall stability and reliability of the application

💄 style(app): improve code style and formatting for better readability and maintainability

- update code formatting and indentation for consistency and clarity
- improve variable naming and commenting for better understanding
- remove redundant and unnecessary code for improved efficiency

📝 docs(app): update documentation and comments for better clarity and understanding

- add comments and docstrings to explain code functionality and purpose
- update documentation to reflect changes and improvements in the application
- improve overall clarity and understanding of the codebase

Files changed (2) hide show
  1. app.py +384 -59
  2. app_old.py +357 -0
app.py CHANGED
@@ -39,18 +39,66 @@ vae = AutoencoderKL.from_pretrained(
39
  "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16
40
  ).to("cuda")
41
 
42
- # Move pipeline loading into a function to enable lazy loading
43
- def load_pipeline(model_name):
44
- pipe = StableDiffusionXLFillPipeline.from_pretrained(
45
- MODELS[model_name],
46
- torch_dtype=torch.float16,
47
- vae=vae,
48
- controlnet=model,
49
- )
50
- pipe.to("cuda")
51
- pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)
52
- return pipe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  def can_expand(source_width, source_height, target_width, target_height, alignment):
55
  """Checks if the image can be expanded based on the alignment."""
56
  if alignment in ("Left", "Right") and source_width >= target_width:
@@ -73,6 +121,8 @@ def prepare_image_and_mask(image, width, height, overlap_percentage, resize_opti
73
  # Apply resize option using percentages
74
  if resize_option == "Full":
75
  resize_percentage = 100
 
 
76
  elif resize_option == "50%":
77
  resize_percentage = 50
78
  elif resize_option == "33%":
@@ -175,12 +225,17 @@ def preview_image_and_mask(image, width, height, overlap_percentage, resize_opti
175
 
176
  return preview
177
 
 
178
  @spaces.GPU(duration=24)
179
- def inpaint(prompt, image, model_name, paste_back):
180
  global pipe
181
  if pipe.config.model_name != MODELS[model_name]:
182
- # Lazily load the pipeline for the selected model
183
- pipe = load_pipeline(model_name)
 
 
 
 
184
 
185
  mask = Image.fromarray(image["mask"]).convert("L")
186
  image = Image.fromarray(image["image"])
@@ -194,7 +249,6 @@ def inpaint(prompt, image, model_name, paste_back):
194
 
195
  @spaces.GPU(duration=24)
196
  def outpaint(image, width, height, overlap_percentage, num_inference_steps, resize_option, custom_resize_percentage, prompt_input, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
197
- # Use the currently loaded pipeline
198
  background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
199
 
200
  if not can_expand(background.width, background.height, width, height, alignment):
@@ -227,6 +281,39 @@ def outpaint(image, width, height, overlap_percentage, num_inference_steps, resi
227
 
228
  yield background, cnet_image
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  def clear_result():
231
  """Clears the result ImageSlider."""
232
  return gr.update(value=None)
@@ -272,6 +359,13 @@ css = """
272
  .gradio-container {
273
  width: 1200px !important;
274
  }
 
 
 
 
 
 
 
275
  """
276
 
277
  title = """<h1 align="center">Diffusers Image Outpaint</h1>
@@ -285,67 +379,298 @@ title = """<h1 align="center">Diffusers Image Outpaint</h1>
285
  </div>
286
  """
287
 
288
- with gr.Blocks(css=css) as demo:
289
  gr.Markdown("# Diffusers Inpaint and Outpaint")
290
 
291
  with gr.Tabs():
292
  with gr.TabItem("Inpaint"):
293
  with gr.Column():
294
- # inpaint_image = gr.Image(type="pil", label="Input Image", tool="sketch")
295
- inpaint_image = gr.ImageEditor(type="pil", label="Input Image")
296
-
297
- inpaint_prompt = gr.Textbox(label="Prompt", info="Describe what to inpaint the mask with", lines=3)
298
- inpaint_model = gr.Dropdown(choices=list(MODELS.keys()), value="RealVisXL V5.0 Lightning", label="Model")
299
- inpaint_paste_back = gr.Checkbox(True, label="Paste back original")
300
- inpaint_button = gr.Button("Generate Inpaint")
301
- inpaint_result = ImageSlider(label="Inpaint Result")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
  with gr.TabItem("Outpaint"):
304
  with gr.Column():
305
- outpaint_image = gr.Image(type="pil", label="Input Image")
306
- outpaint_prompt = gr.Textbox(label="Prompt (Optional)")
307
- with gr.Row():
308
- width_slider = gr.Slider(label="Target Width", minimum=720, maximum=1536, step=8, value=720)
309
- height_slider = gr.Slider(label="Target Height", minimum=720, maximum=1536, step=8, value=1280)
310
- alignment_dropdown = gr.Dropdown(choices=["Middle", "Left", "Right", "Top", "Bottom"], value="Middle", label="Alignment")
311
-
312
- with gr.Accordion("Advanced settings", open=False):
313
- num_inference_steps = gr.Slider(label="Steps", minimum=4, maximum=12, step=1, value=8)
314
- overlap_percentage = gr.Slider(label="Mask overlap (%)", minimum=1, maximum=50, value=10, step=1)
315
- with gr.Row():
316
- overlap_top = gr.Checkbox(label="Overlap Top", value=True)
317
- overlap_right = gr.Checkbox(label="Overlap Right", value=True)
318
- with gr.Row():
319
- overlap_left = gr.Checkbox(label="Overlap Left", value=True)
320
- overlap_bottom = gr.Checkbox(label="Overlap Bottom", value=True)
321
- resize_option = gr.Radio(label="Resize input image", choices=["Full", "50%", "33%", "25%", "Custom"], value="Full")
322
- custom_resize_percentage = gr.Slider(label="Custom resize (%)", minimum=1, maximum=100, step=1, value=50, visible=False)
323
-
324
- outpaint_button = gr.Button("Generate Outpaint")
325
- preview_button = gr.Button("Preview alignment and mask")
326
- outpaint_result = ImageSlider(label="Outpaint Result")
327
- preview_image = gr.Image(label="Preview")
328
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  # Set up event handlers
330
- inpaint_button.click(
331
- fn=inpaint,
332
- inputs=[inpaint_prompt, inpaint_image, inpaint_model, inpaint_paste_back],
333
- outputs=inpaint_result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  )
335
 
336
- outpaint_button.click(
337
- fn=outpaint,
338
- inputs=[outpaint_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
339
- resize_option, custom_resize_percentage, outpaint_prompt, alignment_dropdown,
340
  overlap_left, overlap_right, overlap_top, overlap_bottom],
341
- outputs=outpaint_result
342
  )
343
 
344
  preview_button.click(
345
  fn=preview_image_and_mask,
346
- inputs=[outpaint_image, width_slider, height_slider, overlap_percentage, resize_option, custom_resize_percentage, alignment_dropdown,
347
  overlap_left, overlap_right, overlap_top, overlap_bottom],
348
- outputs=preview_image
 
349
  )
350
 
351
  resize_option.change(
@@ -354,4 +679,4 @@ with gr.Blocks(css=css) as demo:
354
  outputs=[custom_resize_percentage]
355
  )
356
 
357
- demo.launch(share=False)
 
39
  "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16
40
  ).to("cuda")
41
 
42
+ pipe = StableDiffusionXLFillPipeline.from_pretrained(
43
+ "SG161222/RealVisXL_V5.0_Lightning",
44
+ torch_dtype=torch.float16,
45
+ vae=vae,
46
+ controlnet=model,
47
+ variant="fp16",
48
+ )
49
+
50
+ pipe = StableDiffusionXLFillPipeline.from_pretrained(
51
+ "GraydientPlatformAPI/lustify-lightning",
52
+ torch_dtype=torch.float16,
53
+ vae=vae,
54
+ controlnet=model,
55
+ )
56
+
57
+ pipe.to("cuda")
58
+
59
+ pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)
60
+
61
+ @spaces.GPU(duration=16)
62
+ def fill_image(prompt, image, model_selection, paste_back):
63
+ (
64
+ prompt_embeds,
65
+ negative_prompt_embeds,
66
+ pooled_prompt_embeds,
67
+ negative_pooled_prompt_embeds,
68
+ ) = pipe.encode_prompt(prompt, "cuda", True)
69
 
70
+ source = image["background"]
71
+ mask = image["layers"][0]
72
+
73
+ alpha_channel = mask.split()[3]
74
+ binary_mask = alpha_channel.point(lambda p: p > 0 and 255)
75
+ cnet_image = source.copy()
76
+ cnet_image.paste(0, (0, 0), binary_mask)
77
+
78
+ for image in pipe(
79
+ prompt_embeds=prompt_embeds,
80
+ negative_prompt_embeds=negative_prompt_embeds,
81
+ pooled_prompt_embeds=pooled_prompt_embeds,
82
+ negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
83
+ image=cnet_image,
84
+ ):
85
+ yield image, cnet_image
86
+
87
+ print(f"{model_selection=}")
88
+ print(f"{paste_back=}")
89
+
90
+ if paste_back:
91
+ image = image.convert("RGBA")
92
+ cnet_image.paste(image, (0, 0), binary_mask)
93
+ else:
94
+ cnet_image = image
95
+
96
+ yield source, cnet_image
97
+
98
+
99
+ def clear_result():
100
+ return gr.update(value=None)
101
+
102
  def can_expand(source_width, source_height, target_width, target_height, alignment):
103
  """Checks if the image can be expanded based on the alignment."""
104
  if alignment in ("Left", "Right") and source_width >= target_width:
 
121
  # Apply resize option using percentages
122
  if resize_option == "Full":
123
  resize_percentage = 100
124
+ elif resize_option == "80%":
125
+ resize_percentage = 80
126
  elif resize_option == "50%":
127
  resize_percentage = 50
128
  elif resize_option == "33%":
 
225
 
226
  return preview
227
 
228
+
229
  @spaces.GPU(duration=24)
230
+ def inpaint(prompt, image, inpaint_model, paste_back):
231
  global pipe
232
  if pipe.config.model_name != MODELS[model_name]:
233
+ pipe = StableDiffusionXLFillPipeline.from_pretrained(
234
+ MODELS[model_name],
235
+ torch_dtype=torch.float16,
236
+ vae=vae,
237
+ controlnet=model,
238
+ ).to("cuda")
239
 
240
  mask = Image.fromarray(image["mask"]).convert("L")
241
  image = Image.fromarray(image["image"])
 
249
 
250
  @spaces.GPU(duration=24)
251
  def outpaint(image, width, height, overlap_percentage, num_inference_steps, resize_option, custom_resize_percentage, prompt_input, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
 
252
  background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
253
 
254
  if not can_expand(background.width, background.height, width, height, alignment):
 
281
 
282
  yield background, cnet_image
283
 
284
+ @spaces.GPU(duration=24)
285
+ def infer(image, width, height, overlap_percentage, num_inference_steps, resize_option, custom_resize_percentage, prompt_input, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
286
+ background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
287
+
288
+ if not can_expand(background.width, background.height, width, height, alignment):
289
+ alignment = "Middle"
290
+
291
+ cnet_image = background.copy()
292
+ cnet_image.paste(0, (0, 0), mask)
293
+
294
+ final_prompt = f"{prompt_input} , high quality, 4k"
295
+
296
+ (
297
+ prompt_embeds,
298
+ negative_prompt_embeds,
299
+ pooled_prompt_embeds,
300
+ negative_pooled_prompt_embeds,
301
+ ) = pipe.encode_prompt(final_prompt, "cuda", True)
302
+
303
+ for image in pipe(
304
+ prompt_embeds=prompt_embeds,
305
+ negative_prompt_embeds=negative_prompt_embeds,
306
+ pooled_prompt_embeds=pooled_prompt_embeds,
307
+ negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
308
+ image=cnet_image,
309
+ num_inference_steps=num_inference_steps
310
+ ):
311
+ yield cnet_image, image
312
+
313
+ image = image.convert("RGBA")
314
+ cnet_image.paste(image, (0, 0), mask)
315
+
316
+ yield background, cnet_image
317
  def clear_result():
318
  """Clears the result ImageSlider."""
319
  return gr.update(value=None)
 
359
  .gradio-container {
360
  width: 1200px !important;
361
  }
362
+ .contain {
363
+ overflow-y: scroll !important;
364
+ padding: 10px 40px !important;
365
+ }
366
+ div#component-17 {
367
+ height: auto !important;
368
+ }
369
  """
370
 
371
  title = """<h1 align="center">Diffusers Image Outpaint</h1>
 
379
  </div>
380
  """
381
 
382
+ with gr.Blocks(css=css, fill_height=True) as demo:
383
  gr.Markdown("# Diffusers Inpaint and Outpaint")
384
 
385
  with gr.Tabs():
386
  with gr.TabItem("Inpaint"):
387
  with gr.Column():
388
+ with gr.Row():
389
+ with gr.Column():
390
+ prompt = gr.Textbox(
391
+ label="Prompt",
392
+ info="Describe what to inpaint the mask with",
393
+ lines=3,
394
+ )
395
+ with gr.Column():
396
+ model_selection = gr.Dropdown(
397
+ choices=list(MODELS.keys()),
398
+ value="RealVisXL V5.0 Lightning",
399
+ label="Model",
400
+ )
401
+
402
+ with gr.Row():
403
+ run_button = gr.Button("Generate")
404
+ paste_back = gr.Checkbox(True, label="Paste back original")
405
+
406
+ with gr.Row(equal_height=False):
407
+ input_image = gr.ImageMask(
408
+ type="pil", label="Input Image", crop_size=(1024, 1024), layers=False
409
+ )
410
+
411
+ result = ImageSlider(
412
+ interactive=False,
413
+ label="Generated Image",
414
+ )
415
+
416
+ use_as_input_button = gr.Button("Use as Input Image", visible=False)
417
+
418
+ def use_output_as_input(output_image):
419
+ return gr.update(value=output_image[1])
420
+
421
+ use_as_input_button.click(
422
+ fn=use_output_as_input, inputs=[result], outputs=[input_image]
423
+ )
424
+
425
+ run_button.click(
426
+ fn=clear_result,
427
+ inputs=None,
428
+ outputs=result,
429
+ ).then(
430
+ fn=lambda: gr.update(visible=False),
431
+ inputs=None,
432
+ outputs=use_as_input_button,
433
+ ).then(
434
+ fn=fill_image,
435
+ inputs=[prompt, input_image, model_selection, paste_back],
436
+ outputs=result,
437
+ ).then(
438
+ fn=lambda: gr.update(visible=True),
439
+ inputs=None,
440
+ outputs=use_as_input_button,
441
+ )
442
+
443
+ prompt.submit(
444
+ fn=clear_result,
445
+ inputs=None,
446
+ outputs=result,
447
+ ).then(
448
+ fn=lambda: gr.update(visible=False),
449
+ inputs=None,
450
+ outputs=use_as_input_button,
451
+ ).then(
452
+ fn=fill_image,
453
+ inputs=[prompt, input_image, model_selection, paste_back],
454
+ outputs=result,
455
+ ).then(
456
+ fn=lambda: gr.update(visible=True),
457
+ inputs=None,
458
+ outputs=use_as_input_button,
459
+ )
460
 
461
  with gr.TabItem("Outpaint"):
462
  with gr.Column():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
 
464
+ with gr.Row():
465
+ with gr.Column():
466
+ input_image = gr.Image(
467
+ type="pil",
468
+ label="Input Image"
469
+ )
470
+
471
+ with gr.Row():
472
+ with gr.Column(scale=2):
473
+ prompt_input = gr.Textbox(label="Prompt (Optional)")
474
+ with gr.Column(scale=1):
475
+ runout_button = gr.Button("Generate")
476
+
477
+ with gr.Row():
478
+ target_ratio = gr.Radio(
479
+ label="Expected Ratio",
480
+ choices=["9:16", "16:9", "1:1", "Custom"],
481
+ value="1:1",
482
+ scale=2
483
+ )
484
+
485
+ alignment_dropdown = gr.Dropdown(
486
+ choices=["Middle", "Left", "Right", "Top", "Bottom"],
487
+ value="Middle",
488
+ label="Alignment"
489
+ )
490
+
491
+ with gr.Accordion(label="Advanced settings", open=False) as settings_panel:
492
+ with gr.Column():
493
+ with gr.Row():
494
+ width_slider = gr.Slider(
495
+ label="Target Width",
496
+ minimum=720,
497
+ maximum=1536,
498
+ step=8,
499
+ value=1280, # Set a default value
500
+ )
501
+ height_slider = gr.Slider(
502
+ label="Target Height",
503
+ minimum=720,
504
+ maximum=1536,
505
+ step=8,
506
+ value=1280, # Set a default value
507
+ )
508
+
509
+ num_inference_steps = gr.Slider(label="Steps", minimum=4, maximum=12, step=1, value=8)
510
+ with gr.Group():
511
+ overlap_percentage = gr.Slider(
512
+ label="Mask overlap (%)",
513
+ minimum=1,
514
+ maximum=50,
515
+ value=10,
516
+ step=1
517
+ )
518
+ with gr.Row():
519
+ overlap_top = gr.Checkbox(label="Overlap Top", value=True)
520
+ overlap_right = gr.Checkbox(label="Overlap Right", value=True)
521
+ with gr.Row():
522
+ overlap_left = gr.Checkbox(label="Overlap Left", value=True)
523
+ overlap_bottom = gr.Checkbox(label="Overlap Bottom", value=True)
524
+ with gr.Row():
525
+ resize_option = gr.Radio(
526
+ label="Resize input image",
527
+ choices=["Full", "80%", "50%", "33%", "25%", "Custom"],
528
+ value="Full"
529
+ )
530
+ custom_resize_percentage = gr.Slider(
531
+ label="Custom resize (%)",
532
+ minimum=1,
533
+ maximum=100,
534
+ step=1,
535
+ value=50,
536
+ visible=False
537
+ )
538
+
539
+ with gr.Column():
540
+ preview_button = gr.Button("Preview alignment and mask")
541
+
542
+
543
+ gr.Examples(
544
+ examples=[
545
+ ["./examples/example_1.webp", 1280, 720, "Middle"],
546
+ ["./examples/example_2.jpg", 1440, 810, "Left"],
547
+ ["./examples/example_3.jpg", 1024, 1024, "Top"],
548
+ ["./examples/example_3.jpg", 1024, 1024, "Bottom"],
549
+ ],
550
+ inputs=[input_image, width_slider, height_slider, alignment_dropdown],
551
+ )
552
+
553
+
554
+
555
+ with gr.Column():
556
+ result = ImageSlider(
557
+ interactive=False,
558
+ label="Generated Image",
559
+ )
560
+ use_as_input_button = gr.Button("Use as Input Image", visible=False)
561
+
562
+ history_gallery = gr.Gallery(label="History", columns=6, object_fit="contain", interactive=False)
563
+ preview_image = gr.Image(label="Preview")
564
+
565
+
566
+
567
+ def use_output_as_input(output_image):
568
+ """Sets the generated output as the new input image."""
569
+ return gr.update(value=output_image[1])
570
+
571
+ use_as_input_button.click(
572
+ fn=use_output_as_input,
573
+ inputs=[result],
574
+ outputs=[input_image]
575
+ )
576
+
577
  # Set up event handlers
578
+ run_button.click(
579
+ fn=fill_image,
580
+ inputs=[prompt, input_image, model_selection, paste_back],
581
+ outputs=result,
582
+ )
583
+
584
+ target_ratio.change(
585
+ fn=preload_presets,
586
+ inputs=[target_ratio, width_slider, height_slider],
587
+ outputs=[width_slider, height_slider, settings_panel],
588
+ queue=False
589
+ )
590
+
591
+ width_slider.change(
592
+ fn=select_the_right_preset,
593
+ inputs=[width_slider, height_slider],
594
+ outputs=[target_ratio],
595
+ queue=False
596
+ )
597
+
598
+ height_slider.change(
599
+ fn=select_the_right_preset,
600
+ inputs=[width_slider, height_slider],
601
+ outputs=[target_ratio],
602
+ queue=False
603
+ )
604
+
605
+ resize_option.change(
606
+ fn=toggle_custom_resize_slider,
607
+ inputs=[resize_option],
608
+ outputs=[custom_resize_percentage],
609
+ queue=False
610
+ )
611
+
612
+ runout_button.click( # Clear the result
613
+ fn=clear_result,
614
+ inputs=None,
615
+ outputs=result,
616
+ ).then( # Generate the new image
617
+ fn=infer,
618
+ inputs=[input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
619
+ resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
620
+ overlap_left, overlap_right, overlap_top, overlap_bottom],
621
+ outputs=result,
622
+ ).then( # Update the history gallery
623
+ fn=lambda x, history: update_history(x[1], history),
624
+ inputs=[result, history_gallery],
625
+ outputs=history_gallery,
626
+ ).then( # Show the "Use as Input Image" button
627
+ fn=lambda: gr.update(visible=True),
628
+ inputs=None,
629
+ outputs=use_as_input_button,
630
+ )
631
+
632
+ prompt_input.submit( # Clear the result
633
+ fn=clear_result,
634
+ inputs=None,
635
+ outputs=result,
636
+ ).then( # Generate the new image
637
+ fn=infer,
638
+ inputs=[input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
639
+ resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
640
+ overlap_left, overlap_right, overlap_top, overlap_bottom],
641
+ outputs=result,
642
+ ).then( # Update the history gallery
643
+ fn=lambda x, history: update_history(x[1], history),
644
+ inputs=[result, history_gallery],
645
+ outputs=history_gallery,
646
+ ).then( # Show the "Use as Input Image" button
647
+ fn=lambda: gr.update(visible=True),
648
+ inputs=None,
649
+ outputs=use_as_input_button,
650
+ )
651
+
652
+ preview_button.click(
653
+ fn=preview_image_and_mask,
654
+ inputs=[input_image, width_slider, height_slider, overlap_percentage, resize_option, custom_resize_percentage, alignment_dropdown,
655
+ overlap_left, overlap_right, overlap_top, overlap_bottom],
656
+ outputs=preview_image,
657
+ queue=False
658
  )
659
 
660
+ runout_button.click(
661
+ fn=infer,
662
+ inputs=[input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
663
+ resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
664
  overlap_left, overlap_right, overlap_top, overlap_bottom],
665
+ outputs=result,
666
  )
667
 
668
  preview_button.click(
669
  fn=preview_image_and_mask,
670
+ inputs=[input_image, width_slider, height_slider, overlap_percentage, resize_option, custom_resize_percentage, alignment_dropdown,
671
  overlap_left, overlap_right, overlap_top, overlap_bottom],
672
+ outputs=preview_image,
673
+ queue=False
674
  )
675
 
676
  resize_option.change(
 
679
  outputs=[custom_resize_percentage]
680
  )
681
 
682
+ demo.launch(show_error=True)
app_old.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import spaces
3
+ import torch
4
+ from diffusers import AutoencoderKL, TCDScheduler
5
+ from diffusers.models.model_loading_utils import load_state_dict
6
+ from gradio_imageslider import ImageSlider
7
+ from huggingface_hub import hf_hub_download
8
+
9
+ from controlnet_union import ControlNetModel_Union
10
+ from pipeline_fill_sd_xl import StableDiffusionXLFillPipeline
11
+
12
+ from PIL import Image, ImageDraw
13
+ import numpy as np
14
+
15
+ MODELS = {
16
+ "RealVisXL V5.0 Lightning": "SG161222/RealVisXL_V5.0_Lightning",
17
+ "Lustify Lightning": "GraydientPlatformAPI/lustify-lightning",
18
+ "Juggernaut XL Lightning": "RunDiffusion/Juggernaut-XL-Lightning",
19
+ }
20
+
21
+ config_file = hf_hub_download(
22
+ "xinsir/controlnet-union-sdxl-1.0",
23
+ filename="config_promax.json",
24
+ )
25
+
26
+ config = ControlNetModel_Union.load_config(config_file)
27
+ controlnet_model = ControlNetModel_Union.from_config(config)
28
+ model_file = hf_hub_download(
29
+ "xinsir/controlnet-union-sdxl-1.0",
30
+ filename="diffusion_pytorch_model_promax.safetensors",
31
+ )
32
+ state_dict = load_state_dict(model_file)
33
+ model, _, _, _, _ = ControlNetModel_Union._load_pretrained_model(
34
+ controlnet_model, state_dict, model_file, "xinsir/controlnet-union-sdxl-1.0"
35
+ )
36
+ model.to(device="cuda", dtype=torch.float16)
37
+
38
+ vae = AutoencoderKL.from_pretrained(
39
+ "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16
40
+ ).to("cuda")
41
+
42
+ # Move pipeline loading into a function to enable lazy loading
43
+ def load_pipeline(model_name):
44
+ pipe = StableDiffusionXLFillPipeline.from_pretrained(
45
+ MODELS[model_name],
46
+ torch_dtype=torch.float16,
47
+ vae=vae,
48
+ controlnet=model,
49
+ )
50
+ pipe.to("cuda")
51
+ pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)
52
+ return pipe
53
+
54
+ def can_expand(source_width, source_height, target_width, target_height, alignment):
55
+ """Checks if the image can be expanded based on the alignment."""
56
+ if alignment in ("Left", "Right") and source_width >= target_width:
57
+ return False
58
+ if alignment in ("Top", "Bottom") and source_height >= target_height:
59
+ return False
60
+ return True
61
+
62
+ def prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
63
+ target_size = (width, height)
64
+
65
+ # Calculate the scaling factor to fit the image within the target size
66
+ scale_factor = min(target_size[0] / image.width, target_size[1] / image.height)
67
+ new_width = int(image.width * scale_factor)
68
+ new_height = int(image.height * scale_factor)
69
+
70
+ # Resize the source image to fit within target size
71
+ source = image.resize((new_width, new_height), Image.LANCZOS)
72
+
73
+ # Apply resize option using percentages
74
+ if resize_option == "Full":
75
+ resize_percentage = 100
76
+ elif resize_option == "50%":
77
+ resize_percentage = 50
78
+ elif resize_option == "33%":
79
+ resize_percentage = 33
80
+ elif resize_option == "25%":
81
+ resize_percentage = 25
82
+ else: # Custom
83
+ resize_percentage = custom_resize_percentage
84
+
85
+ # Calculate new dimensions based on percentage
86
+ resize_factor = resize_percentage / 100
87
+ new_width = int(source.width * resize_factor)
88
+ new_height = int(source.height * resize_factor)
89
+
90
+ # Ensure minimum size of 64 pixels
91
+ new_width = max(new_width, 64)
92
+ new_height = max(new_height, 64)
93
+
94
+ # Resize the image
95
+ source = source.resize((new_width, new_height), Image.LANCZOS)
96
+
97
+ # Calculate the overlap in pixels based on the percentage
98
+ overlap_x = int(new_width * (overlap_percentage / 100))
99
+ overlap_y = int(new_height * (overlap_percentage / 100))
100
+
101
+ # Ensure minimum overlap of 1 pixel
102
+ overlap_x = max(overlap_x, 1)
103
+ overlap_y = max(overlap_y, 1)
104
+
105
+ # Calculate margins based on alignment
106
+ if alignment == "Middle":
107
+ margin_x = (target_size[0] - new_width) // 2
108
+ margin_y = (target_size[1] - new_height) // 2
109
+ elif alignment == "Left":
110
+ margin_x = 0
111
+ margin_y = (target_size[1] - new_height) // 2
112
+ elif alignment == "Right":
113
+ margin_x = target_size[0] - new_width
114
+ margin_y = (target_size[1] - new_height) // 2
115
+ elif alignment == "Top":
116
+ margin_x = (target_size[0] - new_width) // 2
117
+ margin_y = 0
118
+ elif alignment == "Bottom":
119
+ margin_x = (target_size[0] - new_width) // 2
120
+ margin_y = target_size[1] - new_height
121
+
122
+ # Adjust margins to eliminate gaps
123
+ margin_x = max(0, min(margin_x, target_size[0] - new_width))
124
+ margin_y = max(0, min(margin_y, target_size[1] - new_height))
125
+
126
+ # Create a new background image and paste the resized source image
127
+ background = Image.new('RGB', target_size, (255, 255, 255))
128
+ background.paste(source, (margin_x, margin_y))
129
+
130
+ # Create the mask
131
+ mask = Image.new('L', target_size, 255)
132
+ mask_draw = ImageDraw.Draw(mask)
133
+
134
+ # Calculate overlap areas
135
+ white_gaps_patch = 2
136
+
137
+ left_overlap = margin_x + overlap_x if overlap_left else margin_x + white_gaps_patch
138
+ right_overlap = margin_x + new_width - overlap_x if overlap_right else margin_x + new_width - white_gaps_patch
139
+ top_overlap = margin_y + overlap_y if overlap_top else margin_y + white_gaps_patch
140
+ bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height - white_gaps_patch
141
+
142
+ if alignment == "Left":
143
+ left_overlap = margin_x + overlap_x if overlap_left else margin_x
144
+ elif alignment == "Right":
145
+ right_overlap = margin_x + new_width - overlap_x if overlap_right else margin_x + new_width
146
+ elif alignment == "Top":
147
+ top_overlap = margin_y + overlap_y if overlap_top else margin_y
148
+ elif alignment == "Bottom":
149
+ bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height
150
+
151
+
152
+ # Draw the mask
153
+ mask_draw.rectangle([
154
+ (left_overlap, top_overlap),
155
+ (right_overlap, bottom_overlap)
156
+ ], fill=0)
157
+
158
+ return background, mask
159
+
160
+ def preview_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
161
+ background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
162
+
163
+ # Create a preview image showing the mask
164
+ preview = background.copy().convert('RGBA')
165
+
166
+ # Create a semi-transparent red overlay
167
+ red_overlay = Image.new('RGBA', background.size, (255, 0, 0, 64)) # Reduced alpha to 64 (25% opacity)
168
+
169
+ # Convert black pixels in the mask to semi-transparent red
170
+ red_mask = Image.new('RGBA', background.size, (0, 0, 0, 0))
171
+ red_mask.paste(red_overlay, (0, 0), mask)
172
+
173
+ # Overlay the red mask on the background
174
+ preview = Image.alpha_composite(preview, red_mask)
175
+
176
+ return preview
177
+
178
+ @spaces.GPU(duration=24)
179
+ def inpaint(prompt, image, model_name, paste_back):
180
+ global pipe
181
+ if pipe.config.model_name != MODELS[model_name]:
182
+ # Lazily load the pipeline for the selected model
183
+ pipe = load_pipeline(model_name)
184
+
185
+ mask = Image.fromarray(image["mask"]).convert("L")
186
+ image = Image.fromarray(image["image"])
187
+
188
+ result = pipe(prompt=prompt, image=image, mask_image=mask).images[0]
189
+
190
+ if paste_back:
191
+ result.paste(image, (0, 0), Image.fromarray(255 - np.array(mask)))
192
+
193
+ return result
194
+
195
+ @spaces.GPU(duration=24)
196
+ def outpaint(image, width, height, overlap_percentage, num_inference_steps, resize_option, custom_resize_percentage, prompt_input, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
197
+ # Use the currently loaded pipeline
198
+ background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
199
+
200
+ if not can_expand(background.width, background.height, width, height, alignment):
201
+ alignment = "Middle"
202
+
203
+ cnet_image = background.copy()
204
+ cnet_image.paste(0, (0, 0), mask)
205
+
206
+ final_prompt = f"{prompt_input} , high quality, 4k"
207
+
208
+ (
209
+ prompt_embeds,
210
+ negative_prompt_embeds,
211
+ pooled_prompt_embeds,
212
+ negative_pooled_prompt_embeds,
213
+ ) = pipe.encode_prompt(final_prompt, "cuda", True)
214
+
215
+ for image in pipe(
216
+ prompt_embeds=prompt_embeds,
217
+ negative_prompt_embeds=negative_prompt_embeds,
218
+ pooled_prompt_embeds=pooled_prompt_embeds,
219
+ negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
220
+ image=cnet_image,
221
+ num_inference_steps=num_inference_steps
222
+ ):
223
+ yield cnet_image, image
224
+
225
+ image = image.convert("RGBA")
226
+ cnet_image.paste(image, (0, 0), mask)
227
+
228
+ yield background, cnet_image
229
+
230
+ def clear_result():
231
+ """Clears the result ImageSlider."""
232
+ return gr.update(value=None)
233
+
234
+ def preload_presets(target_ratio, ui_width, ui_height):
235
+ """Updates the width and height sliders based on the selected aspect ratio."""
236
+ if target_ratio == "9:16":
237
+ changed_width = 720
238
+ changed_height = 1280
239
+ return changed_width, changed_height, gr.update()
240
+ elif target_ratio == "16:9":
241
+ changed_width = 1280
242
+ changed_height = 720
243
+ return changed_width, changed_height, gr.update()
244
+ elif target_ratio == "1:1":
245
+ changed_width = 1024
246
+ changed_height = 1024
247
+ return changed_width, changed_height, gr.update()
248
+ elif target_ratio == "Custom":
249
+ return ui_width, ui_height, gr.update(open=True)
250
+
251
+ def select_the_right_preset(user_width, user_height):
252
+ if user_width == 720 and user_height == 1280:
253
+ return "9:16"
254
+ elif user_width == 1280 and user_height == 720:
255
+ return "16:9"
256
+ elif user_width == 1024 and user_height == 1024:
257
+ return "1:1"
258
+ else:
259
+ return "Custom"
260
+
261
+ def toggle_custom_resize_slider(resize_option):
262
+ return gr.update(visible=(resize_option == "Custom"))
263
+
264
+ def update_history(new_image, history):
265
+ """Updates the history gallery with the new image."""
266
+ if history is None:
267
+ history = []
268
+ history.insert(0, new_image)
269
+ return history
270
+
271
+ css = """
272
+ .gradio-container {
273
+ width: 1200px !important;
274
+ }
275
+ """
276
+
277
+ title = """<h1 align="center">Diffusers Image Outpaint</h1>
278
+ <div align="center">Drop an image you would like to extend, pick your expected ratio and hit Generate.</div>
279
+ <div style="display: flex; justify-content: center; align-items: center; text-align: center;">
280
+ <p style="display: flex;gap: 6px;">
281
+ <a href="https://huggingface.co/spaces/fffiloni/diffusers-image-outpout?duplicate=true">
282
+ <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-md.svg" alt="Duplicate this Space">
283
+ </a> to skip the queue and enjoy faster inference on the GPU of your choice
284
+ </p>
285
+ </div>
286
+ """
287
+
288
+ with gr.Blocks(css=css) as demo:
289
+ gr.Markdown("# Diffusers Inpaint and Outpaint")
290
+
291
+ with gr.Tabs():
292
+ with gr.TabItem("Inpaint"):
293
+ with gr.Column():
294
+ # inpaint_image = gr.Image(type="pil", label="Input Image", tool="sketch")
295
+ inpaint_image = gr.ImageEditor(type="pil", label="Input Image")
296
+
297
+ inpaint_prompt = gr.Textbox(label="Prompt", info="Describe what to inpaint the mask with", lines=3)
298
+ inpaint_model = gr.Dropdown(choices=list(MODELS.keys()), value="RealVisXL V5.0 Lightning", label="Model")
299
+ inpaint_paste_back = gr.Checkbox(True, label="Paste back original")
300
+ inpaint_button = gr.Button("Generate Inpaint")
301
+ inpaint_result = ImageSlider(label="Inpaint Result")
302
+
303
+ with gr.TabItem("Outpaint"):
304
+ with gr.Column():
305
+ outpaint_image = gr.Image(type="pil", label="Input Image")
306
+ outpaint_prompt = gr.Textbox(label="Prompt (Optional)")
307
+ with gr.Row():
308
+ width_slider = gr.Slider(label="Target Width", minimum=720, maximum=1536, step=8, value=720)
309
+ height_slider = gr.Slider(label="Target Height", minimum=720, maximum=1536, step=8, value=1280)
310
+ alignment_dropdown = gr.Dropdown(choices=["Middle", "Left", "Right", "Top", "Bottom"], value="Middle", label="Alignment")
311
+
312
+ with gr.Accordion("Advanced settings", open=False):
313
+ num_inference_steps = gr.Slider(label="Steps", minimum=4, maximum=12, step=1, value=8)
314
+ overlap_percentage = gr.Slider(label="Mask overlap (%)", minimum=1, maximum=50, value=10, step=1)
315
+ with gr.Row():
316
+ overlap_top = gr.Checkbox(label="Overlap Top", value=True)
317
+ overlap_right = gr.Checkbox(label="Overlap Right", value=True)
318
+ with gr.Row():
319
+ overlap_left = gr.Checkbox(label="Overlap Left", value=True)
320
+ overlap_bottom = gr.Checkbox(label="Overlap Bottom", value=True)
321
+ resize_option = gr.Radio(label="Resize input image", choices=["Full", "50%", "33%", "25%", "Custom"], value="Full")
322
+ custom_resize_percentage = gr.Slider(label="Custom resize (%)", minimum=1, maximum=100, step=1, value=50, visible=False)
323
+
324
+ outpaint_button = gr.Button("Generate Outpaint")
325
+ preview_button = gr.Button("Preview alignment and mask")
326
+ outpaint_result = ImageSlider(label="Outpaint Result")
327
+ preview_image = gr.Image(label="Preview")
328
+
329
+ # Set up event handlers
330
+ inpaint_button.click(
331
+ fn=inpaint,
332
+ inputs=[inpaint_prompt, inpaint_image, inpaint_model, inpaint_paste_back],
333
+ outputs=inpaint_result
334
+ )
335
+
336
+ outpaint_button.click(
337
+ fn=outpaint,
338
+ inputs=[outpaint_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
339
+ resize_option, custom_resize_percentage, outpaint_prompt, alignment_dropdown,
340
+ overlap_left, overlap_right, overlap_top, overlap_bottom],
341
+ outputs=outpaint_result
342
+ )
343
+
344
+ preview_button.click(
345
+ fn=preview_image_and_mask,
346
+ inputs=[outpaint_image, width_slider, height_slider, overlap_percentage, resize_option, custom_resize_percentage, alignment_dropdown,
347
+ overlap_left, overlap_right, overlap_top, overlap_bottom],
348
+ outputs=preview_image
349
+ )
350
+
351
+ resize_option.change(
352
+ fn=lambda x: gr.update(visible=(x == "Custom")),
353
+ inputs=[resize_option],
354
+ outputs=[custom_resize_percentage]
355
+ )
356
+
357
+ demo.launch(share=False)