1lint commited on
Commit
a9103c4
0 Parent(s):

init commit

Browse files
Files changed (6) hide show
  1. .gitignore +2 -0
  2. app.py +328 -0
  3. footer.html +15 -0
  4. header.html +25 -0
  5. inpaint_pipeline.py +247 -0
  6. style.css +38 -0
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ __pycache__/
2
+ WIP/
app.py ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # inpaint pipeline with fix to avoid noise added to latents during final iteration of denoising loop
3
+ from inpaint_pipeline import SDInpaintPipeline as StableDiffusionInpaintPipelineLegacy
4
+
5
+ from diffusers import (
6
+ StableDiffusionPipeline,
7
+ StableDiffusionImg2ImgPipeline,
8
+ StableDiffusionInpaintPipelineLegacy # uncomment this line to use original inpaint pipeline
9
+ )
10
+
11
+ import diffusers.schedulers
12
+ import gradio as gr
13
+ import torch
14
+ import random
15
+ from multiprocessing import cpu_count
16
+ import json
17
+
18
+ import importlib
19
+
20
+ _xformers_available = importlib.util.find_spec("xformers") is not None
21
+ device = "cuda" if torch.cuda.is_available() else "cpu"
22
+ low_vram_mode = False
23
+
24
+ # scheduler dict includes superclass SchedulerMixin (it still generates reasonable images)
25
+ scheduler_dict = {
26
+ k: v
27
+ for k, v in diffusers.schedulers.__dict__.items()
28
+ if "Scheduler" in k and "Flax" not in k
29
+ }
30
+ scheduler_dict.pop(
31
+ "VQDiffusionScheduler"
32
+ ) # requires unique parameter, unlike other schedulers
33
+ scheduler_names = list(scheduler_dict.keys())
34
+ default_scheduler = scheduler_names[3] # expected to be DPM Multistep
35
+
36
+ model_ids = [
37
+ "andite/anything-v4.0",
38
+ "hakurei/waifu-diffusion",
39
+ "prompthero/openjourney-v2",
40
+ "runwayml/stable-diffusion-v1-5",
41
+ "johnslegers/epic-diffusion",
42
+ "stabilityai/stable-diffusion-2-1",
43
+ ]
44
+
45
+ loaded_model_id = ""
46
+
47
+
48
+ def load_pipe(
49
+ model_id, scheduler_name, pipe_class=StableDiffusionPipeline, pipe_kwargs="{}"
50
+ ):
51
+ global pipe, loaded_model_id
52
+
53
+ scheduler = scheduler_dict[scheduler_name]
54
+
55
+ # load new weights from disk only when changing model_id
56
+ if model_id != loaded_model_id:
57
+ pipe = pipe_class.from_pretrained(
58
+ model_id,
59
+ # torch_dtype=torch.float16,
60
+ # revision='fp16',
61
+ safety_checker=None,
62
+ requires_safety_checker=False,
63
+ scheduler=scheduler.from_pretrained(model_id, subfolder="scheduler"),
64
+ **json.loads(pipe_kwargs),
65
+ )
66
+ loaded_model_id = model_id
67
+
68
+ # if same model_id, instantiate new pipeline with same underlying pytorch objects to avoid reloading weights from disk
69
+ elif pipe_class != pipe.__class__ or not isinstance(pipe.scheduler, scheduler):
70
+ pipe.components["scheduler"] = scheduler.from_pretrained(
71
+ model_id, subfolder="scheduler"
72
+ )
73
+ pipe = pipe_class(**pipe.components)
74
+
75
+ if device == 'cuda':
76
+ pipe = pipe.to(device)
77
+ if _xformers_available:
78
+ pipe.enable_xformers_memory_efficient_attention()
79
+ print("using xformers")
80
+ if low_vram_mode:
81
+ pipe.enable_attention_slicing()
82
+ print("using attention slicing to lower VRAM")
83
+
84
+ return pipe
85
+
86
+
87
+ pipe = None
88
+ pipe = load_pipe(model_ids[0], default_scheduler)
89
+
90
+
91
+ def generate(
92
+ model_name,
93
+ scheduler_name,
94
+ prompt,
95
+ guidance,
96
+ steps,
97
+ n_images=1,
98
+ width=512,
99
+ height=512,
100
+ seed=0,
101
+ image=None,
102
+ strength=0.5,
103
+ inpaint_image=None,
104
+ inpaint_strength=0.5,
105
+ neg_prompt="",
106
+ pipe_class=StableDiffusionPipeline,
107
+ pipe_kwargs="{}",
108
+ ):
109
+
110
+ if seed == -1:
111
+ seed = random.randint(0, 2147483647)
112
+
113
+ generator = torch.Generator("cuda").manual_seed(seed)
114
+
115
+ pipe = load_pipe(
116
+ model_id=model_name,
117
+ scheduler_name=scheduler_name,
118
+ pipe_class=pipe_class,
119
+ pipe_kwargs=pipe_kwargs,
120
+ )
121
+
122
+ status_message = (
123
+ f"Prompt: '{prompt}' | Seed: {seed} | Guidance: {guidance} | Scheduler: {scheduler_name} | Steps: {steps}"
124
+ )
125
+
126
+ if pipe_class == StableDiffusionPipeline:
127
+ status_message = "Text to Image " + status_message
128
+
129
+ result = pipe(
130
+ prompt,
131
+ negative_prompt=neg_prompt,
132
+ num_images_per_prompt=n_images,
133
+ num_inference_steps=int(steps),
134
+ guidance_scale=guidance,
135
+ width=width,
136
+ height=height,
137
+ generator=generator,
138
+ )
139
+
140
+ elif pipe_class == StableDiffusionImg2ImgPipeline:
141
+
142
+ status_message = "Image to Image " + status_message
143
+ print(image.size)
144
+ image = image.resize((width, height))
145
+ print(image.size)
146
+
147
+ result = pipe(
148
+ prompt,
149
+ negative_prompt=neg_prompt,
150
+ num_images_per_prompt=n_images,
151
+ image=image,
152
+ num_inference_steps=int(steps),
153
+ strength=strength,
154
+ guidance_scale=guidance,
155
+ generator=generator,
156
+ )
157
+
158
+ elif pipe_class == StableDiffusionInpaintPipelineLegacy:
159
+ status_message = "Inpainting " + status_message
160
+
161
+ init_image = inpaint_image["image"].resize((width, height))
162
+ mask = inpaint_image["mask"].resize((width, height))
163
+
164
+ result = pipe(
165
+ prompt,
166
+ negative_prompt=neg_prompt,
167
+ num_images_per_prompt=n_images,
168
+ image=init_image,
169
+ mask_image=mask,
170
+ num_inference_steps=int(steps),
171
+ strength=inpaint_strength,
172
+ guidance_scale=guidance,
173
+ generator=generator,
174
+ )
175
+
176
+ else:
177
+ None, f"Unhandled pipeline class: {pipe_class}"
178
+
179
+ return result.images, status_message
180
+
181
+
182
+ default_img_size = 512
183
+
184
+ with open("header.html") as fp:
185
+ header = fp.read()
186
+
187
+ with open("footer.html") as fp:
188
+ footer = fp.read()
189
+
190
+ with gr.Blocks(css="style.css") as demo:
191
+
192
+ pipe_state = gr.State(lambda: StableDiffusionPipeline)
193
+
194
+ gr.HTML(header)
195
+
196
+ with gr.Row():
197
+
198
+ with gr.Column(scale=70):
199
+
200
+ # with gr.Row():
201
+ prompt = gr.Textbox(
202
+ label="Prompt", placeholder="<Shift+Enter> to generate", lines=2
203
+ )
204
+ neg_prompt = gr.Textbox(label="Negative Prompt", placeholder="", lines=2)
205
+
206
+ with gr.Column(scale=30):
207
+ model_name = gr.Dropdown(
208
+ label="Model", choices=model_ids, value=loaded_model_id
209
+ )
210
+ scheduler_name = gr.Dropdown(
211
+ label="Scheduler", choices=scheduler_names, value=default_scheduler
212
+ )
213
+ generate_button = gr.Button(value="Generate", elem_id="generate-button")
214
+
215
+ with gr.Row():
216
+
217
+ with gr.Column():
218
+
219
+ with gr.Tab("Text to Image") as tab:
220
+ tab.select(lambda: StableDiffusionPipeline, [], pipe_state)
221
+
222
+ with gr.Tab("Image to image") as tab:
223
+ tab.select(lambda: StableDiffusionImg2ImgPipeline, [], pipe_state)
224
+
225
+ image = gr.Image(
226
+ label="Image to Image",
227
+ source="upload",
228
+ tool="editor",
229
+ type="pil",
230
+ elem_id="image_upload",
231
+ ).style(height=default_img_size)
232
+ strength = gr.Slider(
233
+ label="Denoising strength",
234
+ minimum=0,
235
+ maximum=1,
236
+ step=0.02,
237
+ value=0.8,
238
+ )
239
+
240
+ with gr.Tab("Inpainting") as tab:
241
+ tab.select(lambda: StableDiffusionInpaintPipelineLegacy, [], pipe_state)
242
+
243
+ inpaint_image = gr.Image(
244
+ label="Inpainting",
245
+ source="upload",
246
+ tool="sketch",
247
+ type="pil",
248
+ elem_id="image_upload",
249
+ ).style(height=default_img_size)
250
+ inpaint_strength = gr.Slider(
251
+ label="Denoising strength",
252
+ minimum=0,
253
+ maximum=1,
254
+ step=0.02,
255
+ value=0.8,
256
+ )
257
+
258
+ with gr.Row():
259
+ batch_size = gr.Slider(
260
+ label="Batch Size", value=1, minimum=1, maximum=8, step=1
261
+ )
262
+ seed = gr.Slider(-1, 2147483647, label="Seed", value=-1, step=1)
263
+
264
+ with gr.Row():
265
+ guidance = gr.Slider(
266
+ label="Guidance scale", value=7.5, minimum=0, maximum=20
267
+ )
268
+ steps = gr.Slider(
269
+ label="Steps", value=20, minimum=1, maximum=100, step=1
270
+ )
271
+
272
+ with gr.Row():
273
+ width = gr.Slider(
274
+ label="Width",
275
+ value=default_img_size,
276
+ minimum=64,
277
+ maximum=1024,
278
+ step=32,
279
+ )
280
+ height = gr.Slider(
281
+ label="Height",
282
+ value=default_img_size,
283
+ minimum=64,
284
+ maximum=1024,
285
+ step=32,
286
+ )
287
+
288
+ with gr.Column():
289
+ gallery = gr.Gallery(
290
+ label="Generated images", show_label=False, elem_id="gallery"
291
+ ).style(height=default_img_size, grid=2)
292
+
293
+ generation_details = gr.Markdown()
294
+
295
+ pipe_kwargs = gr.Textbox(label="Pipe kwargs", value="{\n\t\n}")
296
+
297
+ # if torch.cuda.is_available():
298
+ # giga = 2**30
299
+ # vram_guage = gr.Slider(0, torch.cuda.memory_reserved(0)/giga, label='VRAM Allocated to Reserved (GB)', value=0, step=1)
300
+ # demo.load(lambda : torch.cuda.memory_allocated(0)/giga, inputs=[], outputs=vram_guage, every=0.5, show_progress=False)
301
+
302
+ gr.HTML(footer)
303
+
304
+ inputs = [
305
+ model_name,
306
+ scheduler_name,
307
+ prompt,
308
+ guidance,
309
+ steps,
310
+ batch_size,
311
+ width,
312
+ height,
313
+ seed,
314
+ image,
315
+ strength,
316
+ inpaint_image,
317
+ inpaint_strength,
318
+ neg_prompt,
319
+ pipe_state,
320
+ pipe_kwargs,
321
+ ]
322
+ outputs = [gallery, generation_details]
323
+
324
+ prompt.submit(generate, inputs=inputs, outputs=outputs)
325
+ generate_button.click(generate, inputs=inputs, outputs=outputs)
326
+
327
+ demo.queue(concurrency_count=cpu_count())
328
+ demo.launch()
footer.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!-- based on https://huggingface.co/spaces/stabilityai/stable-diffusion/blob/main/app.py -->
3
+
4
+
5
+ <div class="footer">
6
+ <p>Model Architecture by <a href="https://huggingface.co/stabilityai" style="text-decoration: underline;" target="_blank">StabilityAI</a> - Pipelines by 🤗 Hugging Face
7
+ </p>
8
+ </div>
9
+ <div class="acknowledgments">
10
+ <p><h4>LICENSE</h4>
11
+ The model is licensed with a <a href="https://huggingface.co/stabilityai/stable-diffusion-2/blob/main/LICENSE-MODEL" style="text-decoration: underline;" target="_blank">CreativeML OpenRAIL++</a> license. The authors claim no rights on the outputs you generate, you are free to use them and are accountable for their use which must not go against the provisions set in this license. The license forbids you from sharing any content that violates any laws, produce any harm to a person, disseminate any personal information that would be meant for harm, spread misinformation and target vulnerable groups. For the full list of restrictions please <a href="https://huggingface.co/spaces/CompVis/stable-diffusion-license" target="_blank" style="text-decoration: underline;" target="_blank">read the license</a></p>
12
+ <p><h4>Biases and content acknowledgment</h4>
13
+ Despite how impressive being able to turn text into image is, beware to the fact that this model may output content that reinforces or exacerbates societal biases, as well as realistic faces, pornography and violence. The model was trained on the <a href="https://laion.ai/blog/laion-5b/" style="text-decoration: underline;" target="_blank">LAION-5B dataset</a>, which scraped non-curated image-text-pairs from the internet (the exception being the removal of illegal content) and is meant for research purposes. You can read more in the <a href="https://huggingface.co/CompVis/stable-diffusion-v1-4" style="text-decoration: underline;" target="_blank">model card</a></p>
14
+ </div>
15
+
header.html ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!-- based on https://huggingface.co/spaces/stabilityai/stable-diffusion/blob/main/app.py -->
3
+
4
+ <div style="text-align: center; margin: 0 auto;">
5
+ <div
6
+ style="
7
+ display: inline-flex;
8
+ align-items: center;
9
+ gap: 0.8rem;
10
+ font-size: 1.75rem;
11
+ "
12
+ >
13
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 32 32" style="enable-background:new 0 0 512 512;" xml:space="preserve" width="32" height="32"><path style="fill:#FCD577;" d="M29.545 29.791V2.21c-1.22 0 -2.21 -0.99 -2.21 -2.21H4.665c0 1.22 -0.99 2.21 -2.21 2.21v27.581c1.22 0 2.21 0.99 2.21 2.21H27.335C27.335 30.779 28.325 29.791 29.545 29.791z"/><path x="98.205" y="58.928" style="fill:#99B6C6;" width="315.577" height="394.144" d="M6.138 3.683H25.861V28.317H6.138V3.683z"/><path x="98.205" y="58.928" style="fill:#7BD4EF;" width="315.577" height="131.317" d="M6.138 3.683H25.861V11.89H6.138V3.683z"/><g><path style="fill:#7190A5;" d="M14.498 10.274c0 1.446 0.983 1.155 1.953 1.502l0.504 5.317c0 0 -5.599 0.989 -6.026 2.007l0.27 -2.526c0.924 -1.462 1.286 -4.864 1.419 -6.809l0.086 0.006C12.697 9.876 14.498 10.166 14.498 10.274z"/><path style="fill:#7190A5;" d="M21.96 17.647c0 0 -0.707 1.458 -1.716 1.903c0 0 -1.502 -0.827 -1.502 -0.827c-2.276 -1.557 -2.366 -8.3 -2.366 -8.3c0 -1.718 -0.185 -1.615 -1.429 -1.615c-1.167 0 -2.127 -0.606 -2.242 0.963l-0.086 -0.006c0.059 -0.859 0.074 -1.433 0.074 -1.433c0 -1.718 1.449 -3.11 3.237 -3.11s3.237 1.392 3.237 3.11C19.168 8.332 19.334 15.617 21.96 17.647z"/></g><path style="fill:#6C8793;" d="M12.248 24.739c1.538 0.711 3.256 1.591 3.922 2.258c-1.374 0.354 -2.704 0.798 -3.513 1.32h-2.156c-1.096 -0.606 -2.011 -1.472 -2.501 -2.702c-1.953 -4.907 2.905 -8.664 2.905 -8.664c0.001 -0.001 0.002 -0.002 0.003 -0.003c0.213 -0.214 0.523 -0.301 0.811 -0.21l0.02 0.006c-0.142 0.337 -0.03 0.71 0.517 1.108c1.264 0.919 3.091 1.131 4.416 1.143c-1.755 1.338 -3.42 3.333 -4.367 5.618L12.248 24.739z"/><path style="fill:#577484;" d="M16.17 26.997c-0.666 -0.666 -2.385 -1.548 -3.922 -2.258l0.059 -0.126c0.947 -2.284 2.612 -4.28 4.367 -5.618c0.001 0 0.001 0 0.001 0c0.688 -0.525 1.391 -0.948 2.068 -1.247c0.001 0 0.001 0 0.001 0c1.009 -0.446 1.964 -0.617 2.742 -0.44c0.61 0.138 1.109 0.492 1.439 1.095c1.752 3.205 0.601 9.913 0.601 9.913H12.657C13.466 27.796 14.796 27.352 16.17 26.997z"/><path style="fill:#F7DEB0;" d="M14.38 13.1c-0.971 -0.347 -1.687 -1.564 -1.687 -3.01c0 -0.107 0.004 -0.213 0.011 -0.318c0.116 -1.569 1.075 -2.792 2.242 -2.792c1.244 0 2.253 1.392 2.253 3.11c0 0 -0.735 6.103 1.542 7.66c-0.677 0.299 -1.38 0.722 -2.068 1.247c0 0 0 0 -0.001 0c-1.326 -0.012 -3.152 -0.223 -4.416 -1.143c-0.547 -0.398 -0.659 -0.771 -0.517 -1.108c0.426 -1.018 3.171 -1.697 3.171 -1.697L14.38 13.1z"/><path style="fill:#E5CA9E;" d="M14.38 13.1c0 0 1.019 0.216 1.544 -0.309c0 0 -0.401 1.04 -1.346 1.04"/><g><path style="fill:#EAC36E;" points="437.361,0 413.79,58.926 472.717,35.356 " d="M27.335 0L25.862 3.683L29.545 2.21"/><path style="fill:#EAC36E;" points="437.361,512 413.79,453.074 472.717,476.644 " d="M27.335 32L25.862 28.317L29.545 29.791"/><path style="fill:#EAC36E;" points="74.639,512 98.21,453.074 39.283,476.644 " d="M4.665 32L6.138 28.317L2.455 29.791"/><path style="fill:#EAC36E;" points="39.283,35.356 98.21,58.926 74.639,0 " d="M2.455 2.21L6.138 3.683L4.665 0"/><path style="fill:#EAC36E;" d="M26.425 28.881H5.574V3.119h20.851v25.761H26.425zM6.702 27.754h18.597V4.246H6.702V27.754z"/></g><g><path style="fill:#486572;" d="M12.758 21.613c-0.659 0.767 -1.245 1.613 -1.722 2.531l0.486 0.202C11.82 23.401 12.241 22.483 12.758 21.613z"/><path style="fill:#486572;" d="M21.541 25.576l-0.37 0.068c-0.553 0.101 -1.097 0.212 -1.641 0.331l-0.071 -0.201l-0.059 -0.167c-0.019 -0.056 -0.035 -0.112 -0.052 -0.169l-0.104 -0.338l-0.088 -0.342c-0.112 -0.457 -0.197 -0.922 -0.235 -1.393c-0.035 -0.47 -0.032 -0.947 0.042 -1.417c0.072 -0.47 0.205 -0.935 0.422 -1.369c-0.272 0.402 -0.469 0.856 -0.606 1.329c-0.138 0.473 -0.207 0.967 -0.234 1.462c-0.024 0.496 0.002 0.993 0.057 1.487l0.046 0.37l0.063 0.367c0.011 0.061 0.02 0.123 0.033 0.184l0.039 0.182l0.037 0.174c-0.677 0.157 -1.351 0.327 -2.019 0.514c-0.131 0.037 -0.262 0.075 -0.392 0.114l0.004 -0.004c-0.117 -0.095 -0.232 -0.197 -0.35 -0.275c-0.059 -0.041 -0.117 -0.084 -0.177 -0.122l-0.179 -0.112c-0.239 -0.147 -0.482 -0.279 -0.727 -0.406c-0.489 -0.252 -0.985 -0.479 -1.484 -0.697c-0.998 -0.433 -2.01 -0.825 -3.026 -1.196c0.973 0.475 1.937 0.969 2.876 1.499c0.469 0.266 0.932 0.539 1.379 0.832c0.223 0.146 0.442 0.297 0.648 0.456l0.154 0.119c0.05 0.041 0.097 0.083 0.145 0.124c0.002 0.002 0.004 0.003 0.005 0.005c-0.339 0.109 -0.675 0.224 -1.009 0.349c-0.349 0.132 -0.696 0.273 -1.034 0.431c-0.338 0.159 -0.668 0.337 -0.973 0.549c0.322 -0.186 0.662 -0.334 1.01 -0.463c0.347 -0.129 0.701 -0.239 1.056 -0.34c0.394 -0.111 0.79 -0.208 1.19 -0.297c0.006 0.006 0.013 0.013 0.019 0.019l0.03 -0.03c0.306 -0.068 0.614 -0.132 0.922 -0.192c0.727 -0.14 1.457 -0.258 2.189 -0.362c0.731 -0.103 1.469 -0.195 2.197 -0.265l0.374 -0.036L21.541 25.576z"/></g></svg>
14
+
15
+ <h1 style="font-weight: 1000; margin-bottom: 8px;margin-top:8px">
16
+ Stable Diffusion Pipeline UI
17
+ </h1>
18
+ </div>
19
+ <p style="margin-bottom: 4px; font-size: 100%; line-height: 24px;">
20
+ Stable Diffusion WebUI with first class support for HuggingFace Diffusers Pipelines and Diffusion Schedulers, made in the style of <a style="text-decoration: underline;" href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">Automatic1111's WebUI</a> and <a style="text-decoration: underline;" href="https://huggingface.co/spaces/Evel/Evel_Space">Evel_Space</a>.
21
+ </p>
22
+ <p> Supports Text-to-Image, Image to Image, and Inpainting modes, with fast switching between pipeline modes by reusing loaded model weights already in memory.
23
+ </p>
24
+ </div>
25
+
inpaint_pipeline.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ import torch
15
+ from typing import Optional, Union, List, Callable
16
+ import PIL
17
+ import numpy as np
18
+
19
+ from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_inpaint_legacy import preprocess_image, deprecate, StableDiffusionInpaintPipelineLegacy, StableDiffusionPipelineOutput, PIL_INTERPOLATION
20
+
21
+ def preprocess_mask(mask, scale_factor=8):
22
+ mask = mask.convert("L")
23
+ w, h = mask.size
24
+ w, h = map(lambda x: x - x % 32, (w, h)) # resize to integer multiple of 32
25
+
26
+ #input_mask = mask.resize((w, h), resample=PIL_INTERPOLATION["nearest"])
27
+ input_mask = np.array(mask).astype(np.float32) / 255.0
28
+ input_mask = np.tile(input_mask, (3, 1, 1))
29
+ input_mask = input_mask[None].transpose(0, 1, 2, 3) # add batch dimension
30
+ input_mask = 1 - input_mask # repaint white, keep black
31
+ input_mask = torch.round(torch.from_numpy(input_mask))
32
+
33
+ mask = mask.resize((w // scale_factor, h // scale_factor), resample=PIL_INTERPOLATION["nearest"])
34
+ mask = np.array(mask).astype(np.float32) / 255.0
35
+ mask = np.tile(mask, (4, 1, 1))
36
+ mask = mask[None].transpose(0, 1, 2, 3) # add batch dimension
37
+ mask = 1 - mask # repaint white, keep black
38
+ mask = torch.round(torch.from_numpy(mask))
39
+
40
+ return mask, input_mask
41
+
42
+
43
+
44
+ class SDInpaintPipeline(StableDiffusionInpaintPipelineLegacy):
45
+
46
+ # forward call is same as StableDiffusionInpaintPipelineLegacy, but with line added to avoid noise added to final latents right before decoding step
47
+ @torch.no_grad()
48
+ def __call__(
49
+ self,
50
+ prompt: Union[str, List[str]],
51
+ image: Union[torch.FloatTensor, PIL.Image.Image] = None,
52
+ mask_image: Union[torch.FloatTensor, PIL.Image.Image] = None,
53
+ strength: float = 0.8,
54
+ num_inference_steps: Optional[int] = 50,
55
+ guidance_scale: Optional[float] = 7.5,
56
+ negative_prompt: Optional[Union[str, List[str]]] = None,
57
+ num_images_per_prompt: Optional[int] = 1,
58
+ add_predicted_noise: Optional[bool] = False,
59
+ eta: Optional[float] = 0.0,
60
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
61
+ output_type: Optional[str] = "pil",
62
+ return_dict: bool = True,
63
+ callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,
64
+ callback_steps: Optional[int] = 1,
65
+ preserve_unmasked_image: bool = True,
66
+ **kwargs,
67
+ ):
68
+ r"""
69
+ Function invoked when calling the pipeline for generation.
70
+
71
+ Args:
72
+ prompt (`str` or `List[str]`):
73
+ The prompt or prompts to guide the image generation.
74
+ image (`torch.FloatTensor` or `PIL.Image.Image`):
75
+ `Image`, or tensor representing an image batch, that will be used as the starting point for the
76
+ process. This is the image whose masked region will be inpainted.
77
+ mask_image (`torch.FloatTensor` or `PIL.Image.Image`):
78
+ `Image`, or tensor representing an image batch, to mask `image`. White pixels in the mask will be
79
+ replaced by noise and therefore repainted, while black pixels will be preserved. If `mask_image` is a
80
+ PIL image, it will be converted to a single channel (luminance) before use. If it's a tensor, it should
81
+ contain one color channel (L) instead of 3, so the expected shape would be `(B, H, W, 1)`.
82
+ strength (`float`, *optional*, defaults to 0.8):
83
+ Conceptually, indicates how much to inpaint the masked area. Must be between 0 and 1. When `strength`
84
+ is 1, the denoising process will be run on the masked area for the full number of iterations specified
85
+ in `num_inference_steps`. `image` will be used as a reference for the masked area, adding more noise to
86
+ that region the larger the `strength`. If `strength` is 0, no inpainting will occur.
87
+ num_inference_steps (`int`, *optional*, defaults to 50):
88
+ The reference number of denoising steps. More denoising steps usually lead to a higher quality image at
89
+ the expense of slower inference. This parameter will be modulated by `strength`, as explained above.
90
+ guidance_scale (`float`, *optional*, defaults to 7.5):
91
+ Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).
92
+ `guidance_scale` is defined as `w` of equation 2. of [Imagen
93
+ Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >
94
+ 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
95
+ usually at the expense of lower image quality.
96
+ negative_prompt (`str` or `List[str]`, *optional*):
97
+ The prompt or prompts not to guide the image generation. Ignored when not using guidance (i.e., ignored
98
+ if `guidance_scale` is less than `1`).
99
+ num_images_per_prompt (`int`, *optional*, defaults to 1):
100
+ The number of images to generate per prompt.
101
+ add_predicted_noise (`bool`, *optional*, defaults to True):
102
+ Use predicted noise instead of random noise when constructing noisy versions of the original image in
103
+ the reverse diffusion process
104
+ eta (`float`, *optional*, defaults to 0.0):
105
+ Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to
106
+ [`schedulers.DDIMScheduler`], will be ignored for others.
107
+ generator (`torch.Generator`, *optional*):
108
+ One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)
109
+ to make generation deterministic.
110
+ output_type (`str`, *optional*, defaults to `"pil"`):
111
+ The output format of the generate image. Choose between
112
+ [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`.
113
+ return_dict (`bool`, *optional*, defaults to `True`):
114
+ Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a
115
+ plain tuple.
116
+ callback (`Callable`, *optional*):
117
+ A function that will be called every `callback_steps` steps during inference. The function will be
118
+ called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.
119
+ callback_steps (`int`, *optional*, defaults to 1):
120
+ The frequency at which the `callback` function will be called. If not specified, the callback will be
121
+ called at every step.
122
+ preserve_unmasked_image (`bool`, *optional*, defaults to `True`):
123
+ Whether or not to preserve the unmasked portions of the original image in the inpainted output. If False,
124
+ inpainting of the masked latents may produce noticeable distortion of unmasked portions of the decoded
125
+ image.
126
+
127
+ Returns:
128
+ [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] or `tuple`:
129
+ [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] if `return_dict` is True, otherwise a `tuple.
130
+ When returning a tuple, the first element is a list with the generated images, and the second element is a
131
+ list of `bool`s denoting whether the corresponding generated image likely represents "not-safe-for-work"
132
+ (nsfw) content, according to the `safety_checker`.
133
+ """
134
+ message = "Please use `image` instead of `init_image`."
135
+ init_image = deprecate("init_image", "0.13.0", message, take_from=kwargs)
136
+ image = init_image or image
137
+
138
+ # 1. Check inputs
139
+ self.check_inputs(prompt, strength, callback_steps)
140
+
141
+ # 2. Define call parameters
142
+ batch_size = 1 if isinstance(prompt, str) else len(prompt)
143
+ device = self._execution_device
144
+ # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
145
+ # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
146
+ # corresponds to doing no classifier free guidance.
147
+ do_classifier_free_guidance = guidance_scale > 1.0
148
+
149
+ # 3. Encode input prompt
150
+ text_embeddings = self._encode_prompt(
151
+ prompt, device, num_images_per_prompt, do_classifier_free_guidance, negative_prompt
152
+ )
153
+
154
+ # 4. Preprocess image and mask
155
+ if not isinstance(image, torch.FloatTensor):
156
+ image = preprocess_image(image)
157
+
158
+ # get mask corresponding to input latents as well as image
159
+ if not isinstance(mask_image, torch.FloatTensor):
160
+ mask_image, input_mask_image = preprocess_mask(mask_image, self.vae_scale_factor)
161
+
162
+ # 5. set timesteps
163
+ self.scheduler.set_timesteps(num_inference_steps, device=device)
164
+ timesteps, num_inference_steps = self.get_timesteps(num_inference_steps, strength, device)
165
+ latent_timestep = timesteps[:1].repeat(batch_size * num_images_per_prompt)
166
+
167
+ # 6. Prepare latent variables
168
+ # encode the init image into latents and scale the latents
169
+ latents, init_latents_orig, noise = self.prepare_latents(
170
+ image, latent_timestep, batch_size, num_images_per_prompt, text_embeddings.dtype, device, generator
171
+ )
172
+
173
+ # 7. Prepare mask latent
174
+ mask = mask_image.to(device=self.device, dtype=latents.dtype)
175
+ mask = torch.cat([mask] * batch_size * num_images_per_prompt)
176
+
177
+ # 8. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline
178
+ extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
179
+
180
+ # 9. Denoising loop
181
+ num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
182
+ with self.progress_bar(total=num_inference_steps) as progress_bar:
183
+ for i, t in enumerate(timesteps):
184
+
185
+ # expand the latents if we are doing classifier free guidance
186
+ latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
187
+ latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
188
+
189
+ # predict the noise residual
190
+ noise_pred = self.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
191
+
192
+ # perform guidance
193
+ if do_classifier_free_guidance:
194
+ noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
195
+ noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
196
+
197
+ # compute the previous noisy sample x_t -> x_t-1
198
+ latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample
199
+ # masking
200
+ if add_predicted_noise:
201
+ init_latents_proper = self.scheduler.add_noise(
202
+ init_latents_orig, noise_pred_uncond, torch.tensor([t])
203
+ )
204
+ else:
205
+ init_latents_proper = self.scheduler.add_noise(init_latents_orig, noise, torch.tensor([t]))
206
+
207
+ latents = (init_latents_proper * mask) + (latents * (1 - mask))
208
+
209
+ # call the callback, if provided
210
+ if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
211
+ progress_bar.update()
212
+ if callback is not None and i % callback_steps == 0:
213
+ callback(i, t, latents)
214
+
215
+ # use original latents corresponding to unmasked portions of the image
216
+ # necessary step because noise is still added to "init_latents_proper" after final denoising step
217
+ latents = (init_latents_orig * mask) + (latents * (1 - mask))
218
+
219
+ # 10. Post-processing
220
+ if preserve_unmasked_image:
221
+ # decode latents
222
+ latents = 1 / 0.18215 * latents
223
+ inpaint_image = self.vae.decode(latents).sample
224
+
225
+ # restore unmasked parts of image with original image
226
+ input_mask_image = input_mask_image.to(inpaint_image)
227
+ image = image.to(inpaint_image)
228
+ image = (image * input_mask_image) + (inpaint_image * (1 - input_mask_image)) # use original unmasked portions of image to avoid degradation
229
+
230
+ # post-processing of image
231
+ image = (image / 2 + 0.5).clamp(0, 1)
232
+ # we always cast to float32 as this does not cause significant overhead and is compatible with bfloa16
233
+ image = image.cpu().permute(0, 2, 3, 1).float().numpy()
234
+ else:
235
+ image = self.decode_latents(latents)
236
+
237
+ # 11. Run safety checker
238
+ image, has_nsfw_concept = self.run_safety_checker(image, device, text_embeddings.dtype)
239
+
240
+ # 12. Convert to PIL
241
+ if output_type == "pil":
242
+ image = self.numpy_to_pil(image)
243
+
244
+ if not return_dict:
245
+ return (image, has_nsfw_concept)
246
+
247
+ return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept)
style.css ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ #image_upload{min-height: 512px}
3
+ #image_upload [data-testid="image"], #image_upload [data-testid="image"] > div{min-height: 512px}
4
+ #image_upload .touch-none{display: flex}
5
+
6
+
7
+ #generate-button {
8
+ color:white;
9
+ border-color: orangered;
10
+ background: orange;
11
+ height: 45px;
12
+ }
13
+
14
+ .footer {
15
+ margin-bottom: 45px;
16
+ margin-top: 35px;
17
+ text-align: center;
18
+ border-bottom: 1px solid #e5e5e5;
19
+ }
20
+ .footer>p {
21
+ font-size: .8rem;
22
+ display: inline-block;
23
+ padding: 0 10px;
24
+ transform: translateY(10px);
25
+ background: white;
26
+ }
27
+ .dark .footer {
28
+ border-color: #303030;
29
+ }
30
+ .dark .footer>p {
31
+ background: #0b0f19;
32
+ }
33
+ .acknowledgments h4{
34
+ margin: 1.25em 0 .25em 0;
35
+ font-weight: bold;
36
+ font-size: 115%;
37
+ }
38
+