Spaces:
Runtime error
Runtime error
1lint
commited on
Commit
•
a9103c4
0
Parent(s):
init commit
Browse files- .gitignore +2 -0
- app.py +328 -0
- footer.html +15 -0
- header.html +25 -0
- inpaint_pipeline.py +247 -0
- 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 |
+
|