File size: 3,881 Bytes
96cb557
 
 
 
 
 
 
 
 
 
 
 
 
 
dae6d4e
96cb557
 
 
 
 
 
 
dae6d4e
96cb557
dae6d4e
 
 
 
 
 
 
 
 
 
96cb557
 
dae6d4e
96cb557
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dae6d4e
 
96cb557
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dae6d4e
 
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
"""
Waifu-Inpaint-XL Gradio App
----------------------------
Free-GPU-friendly inpainting UI for ShinoharaHare/Waifu-Inpaint-XL.
Works as-is on: HF Spaces (ZeroGPU), Kaggle Notebooks, Google Colab.

Setup:
    pip install -r requirements.txt
    huggingface-cli login   # needed once, model is gated

Run:
    python app.py
"""

import spaces  # MUST be imported before torch/anything CUDA-related, ZeroGPU requirement
import os
import torch
import gradio as gr
from diffusers import StableDiffusionXLInpaintPipeline
from PIL import Image

MODEL_ID = "ShinoharaHare/Waifu-Inpaint-XL"
DTYPE = torch.float16

# Load once at startup. Moving to 'cuda' here is fine under ZeroGPU -- the actual
# GPU device is only allocated when a @spaces.GPU-decorated function is called.
pipe = StableDiffusionXLInpaintPipeline.from_pretrained(
    MODEL_ID,
    torch_dtype=DTYPE,
    use_safetensors=True,
)
pipe.to("cuda")
pipe.enable_vae_slicing()
pipe.enable_attention_slicing()


@spaces.GPU(duration=60)  # seconds of GPU time requested per call; raise if you increase steps/variations
def run_inpaint(
    editor_value,          # gr.ImageEditor output: {"background":..., "layers":[...], "composite":...}
    prompt,
    negative_prompt,
    steps,
    guidance,
    num_variations,
    seed,
):
    if editor_value is None or editor_value.get("background") is None:
        raise gr.Error("Upload an image first.")

    base_image = editor_value["background"].convert("RGB")

    # Build mask from the drawn layer (painted area = white = inpaint region)
    if not editor_value.get("layers"):
        raise gr.Error("Paint over the area you want to inpaint (use the brush tool).")
    mask_layer = editor_value["layers"][0]
    mask = mask_layer.split()[-1].convert("L")  # alpha channel -> grayscale mask

    results = []
    base_seed = int(seed) if seed >= 0 else torch.seed()
    for i in range(int(num_variations)):
        gen = torch.Generator(device="cuda").manual_seed(base_seed + i)
        out = pipe(
            prompt=prompt,
            negative_prompt=negative_prompt or None,
            image=base_image,
            mask_image=mask,
            num_inference_steps=int(steps),
            guidance_scale=float(guidance),
            height=base_image.height,
            width=base_image.width,
            generator=gen,
        ).images[0]
        results.append(out)

    return results


with gr.Blocks(title="Waifu-Inpaint-XL") as demo:
    gr.Markdown("## Waifu-Inpaint-XL — paint a mask, describe the change, generate")

    with gr.Row():
        with gr.Column():
            editor = gr.ImageEditor(
                label="Upload image, then paint the mask (brush tool)",
                type="pil",
                brush=gr.Brush(colors=["#ffffff"], default_size=25),
            )
            prompt = gr.Textbox(label="Prompt", placeholder="orange striped sweater, red sparkle eyes")
            negative_prompt = gr.Textbox(label="Negative prompt (optional)", value="blurry, low quality, extra limbs")
            with gr.Row():
                steps = gr.Slider(10, 50, value=28, step=1, label="Steps")
                guidance = gr.Slider(1, 12, value=5.0, step=0.5, label="Guidance scale")
            with gr.Row():
                num_variations = gr.Slider(1, 6, value=1, step=1, label="Variations to generate")
                seed = gr.Number(value=-1, label="Seed (-1 = random)")
            run_btn = gr.Button("Generate", variant="primary")

        with gr.Column():
            gallery = gr.Gallery(label="Results", columns=3, height=500)

    run_btn.click(
        fn=run_inpaint,
        inputs=[editor, prompt, negative_prompt, steps, guidance, num_variations, seed],
        outputs=gallery,
    )

if __name__ == "__main__":
    # Spaces already serves a public URL -- do NOT pass share=True here (errors on Spaces).
    demo.launch()